chore: bump version to 3.34.0
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"packages/agent": {
|
||||
"name": "@oh-my-pi/pi-agent-core",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"dependencies": {
|
||||
"@oh-my-pi/pi-ai": "workspace:*",
|
||||
"@oh-my-pi/pi-tui": "workspace:*",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"packages/ai": {
|
||||
"name": "@oh-my-pi/pi-ai",
|
||||
"version": "0.42.0",
|
||||
"version": "3.34.0",
|
||||
"bin": {
|
||||
"pi-ai": "./src/cli.ts",
|
||||
},
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
"packages/coding-agent": {
|
||||
"name": "@oh-my-pi/pi-coding-agent",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"bin": {
|
||||
"omp": "src/cli.ts",
|
||||
},
|
||||
@@ -95,7 +95,7 @@
|
||||
},
|
||||
"packages/git-tool": {
|
||||
"name": "@oh-my-pi/pi-git-tool",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.0",
|
||||
"vitest": "^3.2.4",
|
||||
@@ -103,7 +103,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@oh-my-pi/pi-tui",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"dependencies": {
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"chalk": "^5.5.0",
|
||||
@@ -652,8 +652,6 @@
|
||||
|
||||
"jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
|
||||
|
||||
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||
@@ -970,12 +968,6 @@
|
||||
|
||||
"@oh-my-pi/pi-git-tool/@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
|
||||
|
||||
"@openai/agents/openai": ["openai@6.15.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-F1Lvs5BoVvmZtzkUEVyh8mDQPPFolq4F+xdsx/DO8Hee8YF3IGAlZqUIsF+DVGhqf4aU0a3bTghsxB6OIsRy1g=="],
|
||||
|
||||
"@openai/agents-core/openai": ["openai@6.15.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-F1Lvs5BoVvmZtzkUEVyh8mDQPPFolq4F+xdsx/DO8Hee8YF3IGAlZqUIsF+DVGhqf4aU0a3bTghsxB6OIsRy1g=="],
|
||||
|
||||
"@openai/agents-openai/openai": ["openai@6.15.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-F1Lvs5BoVvmZtzkUEVyh8mDQPPFolq4F+xdsx/DO8Hee8YF3IGAlZqUIsF+DVGhqf4aU0a3bTghsxB6OIsRy1g=="],
|
||||
|
||||
"@types/ndjson/@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
|
||||
|
||||
"@types/through/@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.34.0] - 2026-01-09
|
||||
|
||||
## [3.33.0] - 2026-01-08
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-agent-core",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -30,7 +30,7 @@ export function agentLoop(
|
||||
context: AgentContext,
|
||||
config: AgentLoopConfig,
|
||||
signal?: AbortSignal,
|
||||
streamFn?: StreamFn
|
||||
streamFn?: StreamFn,
|
||||
): EventStream<AgentEvent, AgentMessage[]> {
|
||||
const stream = createAgentStream();
|
||||
|
||||
@@ -66,7 +66,7 @@ export function agentLoopContinue(
|
||||
context: AgentContext,
|
||||
config: AgentLoopConfig,
|
||||
signal?: AbortSignal,
|
||||
streamFn?: StreamFn
|
||||
streamFn?: StreamFn,
|
||||
): EventStream<AgentEvent, AgentMessage[]> {
|
||||
if (context.messages.length === 0) {
|
||||
throw new Error("Cannot continue: no messages in context");
|
||||
@@ -94,13 +94,13 @@ export function agentLoopContinue(
|
||||
function createAgentStream(): EventStream<AgentEvent, AgentMessage[]> {
|
||||
return new EventStream<AgentEvent, AgentMessage[]>(
|
||||
(event: AgentEvent) => event.type === "agent_end",
|
||||
(event: AgentEvent) => (event.type === "agent_end" ? event.messages : [])
|
||||
(event: AgentEvent) => (event.type === "agent_end" ? event.messages : []),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeMessagesForProvider(
|
||||
messages: Context["messages"],
|
||||
model: AgentLoopConfig["model"]
|
||||
model: AgentLoopConfig["model"],
|
||||
): Context["messages"] {
|
||||
if (model.provider !== "cerebras") {
|
||||
return messages;
|
||||
@@ -133,7 +133,7 @@ async function runLoop(
|
||||
config: AgentLoopConfig,
|
||||
signal: AbortSignal | undefined,
|
||||
stream: EventStream<AgentEvent, AgentMessage[]>,
|
||||
streamFn?: StreamFn
|
||||
streamFn?: StreamFn,
|
||||
): Promise<void> {
|
||||
let firstTurn = true;
|
||||
// Check for steering messages at start (user may have typed while waiting)
|
||||
@@ -187,7 +187,7 @@ async function runLoop(
|
||||
stream,
|
||||
config.getSteeringMessages,
|
||||
config.getToolContext,
|
||||
config.interruptMode
|
||||
config.interruptMode,
|
||||
);
|
||||
toolResults.push(...toolExecution.toolResults);
|
||||
steeringAfterTools = toolExecution.steeringMessages ?? null;
|
||||
@@ -234,7 +234,7 @@ async function streamAssistantResponse(
|
||||
config: AgentLoopConfig,
|
||||
signal: AbortSignal | undefined,
|
||||
stream: EventStream<AgentEvent, AgentMessage[]>,
|
||||
streamFn?: StreamFn
|
||||
streamFn?: StreamFn,
|
||||
): Promise<AssistantMessage> {
|
||||
// Apply context transform if configured (AgentMessage[] → AgentMessage[])
|
||||
let messages = context.messages;
|
||||
@@ -291,7 +291,7 @@ async function streamAssistantResponse(
|
||||
stopReason: "aborted",
|
||||
errorMessage,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
};
|
||||
if (addedPartial) {
|
||||
context.messages[context.messages.length - 1] = abortedMessage;
|
||||
} else {
|
||||
@@ -360,7 +360,7 @@ async function executeToolCalls(
|
||||
stream: EventStream<AgentEvent, AgentMessage[]>,
|
||||
getSteeringMessages?: AgentLoopConfig["getSteeringMessages"],
|
||||
getToolContext?: AgentLoopConfig["getToolContext"],
|
||||
interruptMode: AgentLoopConfig["interruptMode"] = "immediate"
|
||||
interruptMode: AgentLoopConfig["interruptMode"] = "immediate",
|
||||
): Promise<{ toolResults: ToolResultMessage[]; steeringMessages?: AgentMessage[] }> {
|
||||
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
||||
const results: ToolResultMessage[] = [];
|
||||
@@ -400,7 +400,7 @@ async function executeToolCalls(
|
||||
partialResult,
|
||||
});
|
||||
},
|
||||
toolContext
|
||||
toolContext,
|
||||
);
|
||||
} catch (e) {
|
||||
result = {
|
||||
@@ -451,7 +451,7 @@ async function executeToolCalls(
|
||||
|
||||
function skipToolCall(
|
||||
toolCall: Extract<AssistantMessage["content"][number], { type: "toolCall" }>,
|
||||
stream: EventStream<AgentEvent, AgentMessage[]>
|
||||
stream: EventStream<AgentEvent, AgentMessage[]>,
|
||||
): ToolResultMessage {
|
||||
const result: AgentToolResult<any> = {
|
||||
content: [{ type: "text", text: "Skipped due to queued user message." }],
|
||||
|
||||
@@ -264,7 +264,7 @@ export class Agent {
|
||||
async prompt(input: string | AgentMessage | AgentMessage[], images?: ImageContent[]) {
|
||||
if (this._state.isStreaming) {
|
||||
throw new Error(
|
||||
"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion."
|
||||
"Agent is already processing a prompt. Use steer() or followUp() to queue messages, or wait for completion.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ export class Agent {
|
||||
(c) =>
|
||||
(c.type === "thinking" && c.thinking.trim().length > 0) ||
|
||||
(c.type === "text" && c.text.trim().length > 0) ||
|
||||
(c.type === "toolCall" && c.name.trim().length > 0)
|
||||
(c.type === "toolCall" && c.name.trim().length > 0),
|
||||
);
|
||||
if (!onlyEmpty) {
|
||||
this.appendMessage(partial);
|
||||
|
||||
@@ -24,7 +24,7 @@ class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, Assista
|
||||
if (event.type === "done") return event.message;
|
||||
if (event.type === "error") return event.error;
|
||||
throw new Error("Unexpected event type");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ export function streamProxy(model: Model<any>, context: Context, options: ProxyS
|
||||
*/
|
||||
function processProxyEvent(
|
||||
proxyEvent: ProxyAssistantMessageEvent,
|
||||
partial: AssistantMessage
|
||||
partial: AssistantMessage,
|
||||
): AssistantMessageEvent | undefined {
|
||||
switch (proxyEvent.type) {
|
||||
case "start":
|
||||
|
||||
@@ -198,7 +198,7 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
||||
params: Static<TParameters>,
|
||||
signal?: AbortSignal,
|
||||
onUpdate?: AgentToolUpdateCallback<TDetails>,
|
||||
context?: AgentToolContext
|
||||
context?: AgentToolContext,
|
||||
) => Promise<AgentToolResult<TDetails>>;
|
||||
|
||||
/** Optional custom rendering for tool call display (returns UI component) */
|
||||
|
||||
@@ -20,7 +20,7 @@ class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMe
|
||||
if (event.type === "done") return event.message;
|
||||
if (event.type === "error") return event.error;
|
||||
throw new Error("Unexpected event type");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ function createModel(): Model<"openai-responses"> {
|
||||
|
||||
function createAssistantMessage(
|
||||
content: AssistantMessage["content"],
|
||||
stopReason: AssistantMessage["stopReason"] = "stop"
|
||||
stopReason: AssistantMessage["stopReason"] = "stop",
|
||||
): AssistantMessage {
|
||||
return {
|
||||
role: "assistant",
|
||||
@@ -209,7 +209,7 @@ describe("agentLoop with AgentMessage", () => {
|
||||
},
|
||||
convertToLlm: (messages) => {
|
||||
convertedMessages = messages.filter(
|
||||
(m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult"
|
||||
(m) => m.role === "user" || m.role === "assistant" || m.role === "toolResult",
|
||||
) as Message[];
|
||||
return convertedMessages;
|
||||
},
|
||||
@@ -274,7 +274,7 @@ describe("agentLoop with AgentMessage", () => {
|
||||
// First call: return tool call
|
||||
const message = createAssistantMessage(
|
||||
[{ type: "toolCall", id: "tool-1", name: "echo", arguments: { value: "hello" } }],
|
||||
"toolUse"
|
||||
"toolUse",
|
||||
);
|
||||
stream.push({ type: "done", reason: "toolUse", message });
|
||||
} else {
|
||||
@@ -355,7 +355,7 @@ describe("agentLoop with AgentMessage", () => {
|
||||
// Check if interrupt message is in context on second call
|
||||
if (callIndex === 1) {
|
||||
sawInterruptInContext = ctx.messages.some(
|
||||
(m) => m.role === "user" && typeof m.content === "string" && m.content === "interrupt"
|
||||
(m) => m.role === "user" && typeof m.content === "string" && m.content === "interrupt",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ describe("agentLoop with AgentMessage", () => {
|
||||
{ type: "toolCall", id: "tool-1", name: "echo", arguments: { value: "first" } },
|
||||
{ type: "toolCall", id: "tool-2", name: "echo", arguments: { value: "second" } },
|
||||
],
|
||||
"toolUse"
|
||||
"toolUse",
|
||||
);
|
||||
mockStream.push({ type: "done", reason: "toolUse", message });
|
||||
} else {
|
||||
@@ -390,7 +390,7 @@ describe("agentLoop with AgentMessage", () => {
|
||||
|
||||
// Second tool should be skipped
|
||||
const toolEnds = events.filter(
|
||||
(e): e is Extract<AgentEvent, { type: "tool_execution_end" }> => e.type === "tool_execution_end"
|
||||
(e): e is Extract<AgentEvent, { type: "tool_execution_end" }> => e.type === "tool_execution_end",
|
||||
);
|
||||
expect(toolEnds.length).toBe(2);
|
||||
expect(toolEnds[0].isError).toBeFalsy();
|
||||
@@ -405,7 +405,7 @@ describe("agentLoop with AgentMessage", () => {
|
||||
e.type === "message_start" &&
|
||||
e.message.role === "user" &&
|
||||
typeof e.message.content === "string" &&
|
||||
e.message.content === "interrupt"
|
||||
e.message.content === "interrupt",
|
||||
);
|
||||
expect(queuedMessageEvent).toBeDefined();
|
||||
|
||||
|
||||
@@ -414,7 +414,8 @@ describe("Agent.continue()", () => {
|
||||
it("should continue and process tool results", async () => {
|
||||
const agent = new Agent({
|
||||
initialState: {
|
||||
systemPrompt: "You are a helpful assistant. After getting a calculation result, state the answer clearly.",
|
||||
systemPrompt:
|
||||
"You are a helpful assistant. After getting a calculation result, state the answer clearly.",
|
||||
model,
|
||||
thinkingLevel: "off",
|
||||
tools: [calculateTool],
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.34.0] - 2026-01-09
|
||||
|
||||
## [0.42.0] - 2026-01-09
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-ai",
|
||||
"version": "0.42.0",
|
||||
"version": "3.34.0",
|
||||
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -46,7 +46,7 @@ async function login(provider: OAuthProvider): Promise<void> {
|
||||
},
|
||||
async () => {
|
||||
return await promptFn("Paste the authorization code:");
|
||||
}
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -71,7 +71,7 @@ async function login(provider: OAuthProvider): Promise<void> {
|
||||
if (info.instructions) console.log(info.instructions);
|
||||
console.log();
|
||||
},
|
||||
(msg) => console.log(msg)
|
||||
(msg) => console.log(msg),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -82,7 +82,7 @@ async function login(provider: OAuthProvider): Promise<void> {
|
||||
if (info.instructions) console.log(info.instructions);
|
||||
console.log();
|
||||
},
|
||||
(msg) => console.log(msg)
|
||||
(msg) => console.log(msg),
|
||||
);
|
||||
break;
|
||||
case "openai-codex":
|
||||
|
||||
@@ -35,11 +35,7 @@ import {
|
||||
import { getCodexInstructions } from "./openai-codex/prompts/codex";
|
||||
import { buildCodexPiBridge } from "./openai-codex/prompts/pi-codex-bridge";
|
||||
import { buildCodexSystemPrompt } from "./openai-codex/prompts/system-prompt";
|
||||
import {
|
||||
type CodexRequestOptions,
|
||||
type RequestBody,
|
||||
transformRequestBody,
|
||||
} from "./openai-codex/request-transformer";
|
||||
import { type CodexRequestOptions, type RequestBody, transformRequestBody } from "./openai-codex/request-transformer";
|
||||
import { parseCodexError, parseCodexSseStream } from "./openai-codex/response-handler";
|
||||
import { transformMessages } from "./transorm-messages";
|
||||
|
||||
|
||||
@@ -29,11 +29,12 @@ export interface ApiOptionsMap {
|
||||
}
|
||||
|
||||
// Compile-time exhaustiveness check - this will fail if ApiOptionsMap doesn't have all KnownApi keys
|
||||
type _CheckExhaustive = ApiOptionsMap extends Record<Api, StreamOptions>
|
||||
? Record<Api, StreamOptions> extends ApiOptionsMap
|
||||
? true
|
||||
: ["ApiOptionsMap is missing some KnownApi values", Exclude<Api, keyof ApiOptionsMap>]
|
||||
: ["ApiOptionsMap doesn't extend Record<KnownApi, StreamOptions>"];
|
||||
type _CheckExhaustive =
|
||||
ApiOptionsMap extends Record<Api, StreamOptions>
|
||||
? Record<Api, StreamOptions> extends ApiOptionsMap
|
||||
? true
|
||||
: ["ApiOptionsMap is missing some KnownApi values", Exclude<Api, keyof ApiOptionsMap>]
|
||||
: ["ApiOptionsMap doesn't extend Record<KnownApi, StreamOptions>"];
|
||||
const _exhaustive: _CheckExhaustive = true;
|
||||
|
||||
// Helper type to get options for a specific API
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { MODELS } from "../src/models.generated.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Model } from "../src/types.js";
|
||||
import { MODELS } from "../src/models.generated";
|
||||
import { complete } from "../src/stream";
|
||||
import type { Model } from "../src/types";
|
||||
|
||||
describe("OpenCode Zen Models Smoke Test", () => {
|
||||
describe.skipIf(!process.env.OPENCODE_API_KEY)("OpenCode Zen Models Smoke Test", () => {
|
||||
const zenModels = Object.values(MODELS.opencode);
|
||||
|
||||
zenModels.forEach((model) => {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.34.0] - 2026-01-09
|
||||
|
||||
### Added
|
||||
|
||||
- Added caching for system environment detection to improve startup performance
|
||||
|
||||
@@ -263,7 +263,7 @@ export default function (pi: ExtensionAPI) {
|
||||
return new Text(
|
||||
`${theme.fg("success", "✓ Added ") + theme.fg("accent", `#${added.id}`)} ${theme.fg("muted", added.text)}`,
|
||||
0,
|
||||
0
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function (pi: HookAPI) {
|
||||
`Custom compaction: summarizing ${allMessages.length} messages (${tokensBefore.toLocaleString()} tokens) with ${
|
||||
model.id
|
||||
}...`,
|
||||
"info"
|
||||
"info",
|
||||
);
|
||||
|
||||
// Convert messages to readable text format
|
||||
|
||||
@@ -96,7 +96,7 @@ export default function (pi: HookAPI) {
|
||||
const response = await complete(
|
||||
ctx.model!,
|
||||
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
||||
{ apiKey, signal: loader.signal }
|
||||
{ apiKey, signal: loader.signal },
|
||||
);
|
||||
|
||||
if (response.stopReason === "aborted") {
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function (pi: HookAPI) {
|
||||
const response = await complete(
|
||||
ctx.model!,
|
||||
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
||||
{ apiKey, signal: loader.signal }
|
||||
{ apiKey, signal: loader.signal },
|
||||
);
|
||||
|
||||
if (response.stopReason === "aborted") {
|
||||
|
||||
@@ -27,7 +27,7 @@ if (customModel) {
|
||||
const available = modelRegistry.getAvailable();
|
||||
console.log(
|
||||
"Available models:",
|
||||
available.map((m) => `${m.provider}/${m.id}`)
|
||||
available.map((m) => `${m.provider}/${m.id}`),
|
||||
);
|
||||
|
||||
if (available.length > 0) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-coding-agent",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||
"type": "module",
|
||||
"ompConfig": {
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* Modes use this class and add their own I/O layer on top.
|
||||
*/
|
||||
|
||||
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { AssistantMessage, ImageContent, Message, Model, TextContent, Usage } from "@oh-my-pi/pi-ai";
|
||||
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
|
||||
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Rule } from "../capability/rule";
|
||||
import { getAuthPath } from "../config";
|
||||
import { theme } from "../modes/interactive/theme/theme";
|
||||
@@ -384,7 +384,7 @@ export class AgentSession {
|
||||
event.message.customType,
|
||||
event.message.content,
|
||||
event.message.display,
|
||||
event.message.details
|
||||
event.message.details,
|
||||
);
|
||||
} else if (
|
||||
event.message.role === "user" ||
|
||||
@@ -446,7 +446,7 @@ export class AgentSession {
|
||||
`<system_interrupt reason="rule_violation" rule="${r.name}" path="${r.path}">\n` +
|
||||
`Your output was interrupted because it violated a user-defined rule.\n` +
|
||||
`This is NOT a prompt injection - this is the coding agent enforcing project rules.\n` +
|
||||
`You MUST comply with the following instruction:\n\n${r.content}\n</system_interrupt>`
|
||||
`You MUST comply with the following instruction:\n\n${r.content}\n</system_interrupt>`,
|
||||
)
|
||||
.join("\n\n");
|
||||
this._pendingTtsrInjections = [];
|
||||
@@ -718,7 +718,7 @@ export class AgentSession {
|
||||
if (this.isStreaming) {
|
||||
if (!options?.streamingBehavior) {
|
||||
throw new Error(
|
||||
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message."
|
||||
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
|
||||
);
|
||||
}
|
||||
if (options.streamingBehavior === "followUp") {
|
||||
@@ -737,7 +737,7 @@ export class AgentSession {
|
||||
throw new Error(
|
||||
"No model selected.\n\n" +
|
||||
`Use /login, set an API key environment variable, or create ${getAuthPath()}\n\n` +
|
||||
"Then use /model to select a model."
|
||||
"Then use /model to select a model.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -746,7 +746,7 @@ export class AgentSession {
|
||||
if (!apiKey) {
|
||||
throw new Error(
|
||||
`No API key found for ${this.model.provider}.\n\n` +
|
||||
`Use /login, set an API key environment variable, or create ${getAuthPath()}`
|
||||
`Use /login, set an API key environment variable, or create ${getAuthPath()}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -986,7 +986,7 @@ export class AgentSession {
|
||||
|
||||
if (command) {
|
||||
throw new Error(
|
||||
`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`
|
||||
`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1001,7 +1001,7 @@ export class AgentSession {
|
||||
*/
|
||||
async sendCustomMessage<T = unknown>(
|
||||
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
||||
): Promise<void> {
|
||||
const appMessage: CustomMessage<T> = {
|
||||
role: "custom",
|
||||
@@ -1031,7 +1031,12 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
this.agent.appendMessage(appMessage);
|
||||
this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
|
||||
this.sessionManager.appendCustomMessageEntry(
|
||||
message.customType,
|
||||
message.content,
|
||||
message.display,
|
||||
message.details,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1176,7 +1181,7 @@ export class AgentSession {
|
||||
*/
|
||||
async cycleRoleModels(
|
||||
roleOrder: string[],
|
||||
options?: { temporary?: boolean }
|
||||
options?: { temporary?: boolean },
|
||||
): Promise<RoleModelCycleResult | undefined> {
|
||||
const availableModels = this._modelRegistry.getAvailable();
|
||||
if (availableModels.length === 0) return undefined;
|
||||
@@ -1189,7 +1194,7 @@ export class AgentSession {
|
||||
for (const role of roleOrder) {
|
||||
const roleModelStr =
|
||||
role === "default"
|
||||
? this.settingsManager.getModelRole("default") ?? `${currentModel.provider}/${currentModel.id}`
|
||||
? (this.settingsManager.getModelRole("default") ?? `${currentModel.provider}/${currentModel.id}`)
|
||||
: this.settingsManager.getModelRole(role);
|
||||
if (!roleModelStr) continue;
|
||||
|
||||
@@ -1454,7 +1459,7 @@ export class AgentSession {
|
||||
this.model,
|
||||
apiKey,
|
||||
customInstructions,
|
||||
this._compactionAbortController.signal
|
||||
this._compactionAbortController.signal,
|
||||
);
|
||||
summary = result.summary;
|
||||
firstKeptEntryId = result.firstKeptEntryId;
|
||||
@@ -1627,7 +1632,7 @@ export class AgentSession {
|
||||
this.model,
|
||||
apiKey,
|
||||
undefined,
|
||||
this._autoCompactionAbortController.signal
|
||||
this._autoCompactionAbortController.signal,
|
||||
);
|
||||
summary = compactResult.summary;
|
||||
firstKeptEntryId = compactResult.firstKeptEntryId;
|
||||
@@ -1684,7 +1689,7 @@ export class AgentSession {
|
||||
throw new Error(
|
||||
`Context overflow: ${
|
||||
error instanceof Error ? error.message : "compaction failed"
|
||||
}. Your input may be too large for the context window.`
|
||||
}. Your input may be too large for the context window.`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
@@ -1722,7 +1727,7 @@ export class AgentSession {
|
||||
const err = message.errorMessage;
|
||||
// Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection error
|
||||
return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error/i.test(
|
||||
err
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1876,7 +1881,7 @@ export class AgentSession {
|
||||
async executeBash(
|
||||
command: string,
|
||||
onChunk?: (chunk: string) => void,
|
||||
options?: { excludeFromContext?: boolean }
|
||||
options?: { excludeFromContext?: boolean },
|
||||
): Promise<BashResult> {
|
||||
this._bashAbortController = new AbortController();
|
||||
|
||||
@@ -2107,7 +2112,7 @@ export class AgentSession {
|
||||
*/
|
||||
async navigateTree(
|
||||
targetId: string,
|
||||
options: { summarize?: boolean; customInstructions?: string } = {}
|
||||
options: { summarize?: boolean; customInstructions?: string } = {},
|
||||
): Promise<{ editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: BranchSummaryEntry }> {
|
||||
const oldLeafId = this.sessionManager.getLeafId();
|
||||
|
||||
@@ -2130,7 +2135,7 @@ export class AgentSession {
|
||||
const { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(
|
||||
this.sessionManager,
|
||||
oldLeafId,
|
||||
targetId
|
||||
targetId,
|
||||
);
|
||||
|
||||
// Prepare event data
|
||||
|
||||
@@ -44,7 +44,10 @@ export class AuthStorage {
|
||||
* @param authPath - Primary path for reading/writing auth.json
|
||||
* @param fallbackPaths - Additional paths to check when reading (legacy support)
|
||||
*/
|
||||
constructor(private authPath: string, private fallbackPaths: string[] = []) {}
|
||||
constructor(
|
||||
private authPath: string,
|
||||
private fallbackPaths: string[] = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Set a runtime API key override (not persisted to disk).
|
||||
@@ -173,7 +176,7 @@ export class AuthStorage {
|
||||
onAuth: (info: { url: string; instructions?: string }) => void;
|
||||
onPrompt: (prompt: { message: string; placeholder?: string }) => Promise<string>;
|
||||
onProgress?: (message: string) => void;
|
||||
}
|
||||
},
|
||||
): Promise<void> {
|
||||
let credentials: OAuthCredentials;
|
||||
|
||||
@@ -181,7 +184,7 @@ export class AuthStorage {
|
||||
case "anthropic":
|
||||
credentials = await loginAnthropic(
|
||||
(url) => callbacks.onAuth({ url }),
|
||||
() => callbacks.onPrompt({ message: "Paste the authorization code:" })
|
||||
() => callbacks.onPrompt({ message: "Paste the authorization code:" }),
|
||||
);
|
||||
break;
|
||||
case "github-copilot":
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* a summary of the branch being left so context isn't lost.
|
||||
*/
|
||||
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import branchSummaryPrompt from "../../prompts/branch-summary.md" with { type: "text" };
|
||||
import branchSummaryPreamble from "../../prompts/branch-summary-preamble.md" with { type: "text" };
|
||||
import {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* and after compaction the session is reloaded.
|
||||
*/
|
||||
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { AssistantMessage, Model, Usage } from "@oh-my-pi/pi-ai";
|
||||
import { complete, completeSimple } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import compactionSummaryPrompt from "../../prompts/compaction-summary.md" with { type: "text" };
|
||||
import compactionTurnPrefixPrompt from "../../prompts/compaction-turn-prefix.md" with { type: "text" };
|
||||
import compactionUpdateSummaryPrompt from "../../prompts/compaction-update-summary.md" with { type: "text" };
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Shared utilities for compaction and branch summarization.
|
||||
*/
|
||||
|
||||
import type { Message } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Message } from "@oh-my-pi/pi-ai";
|
||||
import summarizationSystemPrompt from "../../prompts/summarization-system.md" with { type: "text" };
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* They can provide custom rendering for tool calls and results in the TUI.
|
||||
*/
|
||||
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import type { Static, TSchema } from "@sinclair/typebox";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme";
|
||||
@@ -140,7 +140,7 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
|
||||
params: Static<TParams>,
|
||||
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
|
||||
ctx: CustomToolContext,
|
||||
signal?: AbortSignal
|
||||
signal?: AbortSignal,
|
||||
): Promise<AgentToolResult<TDetails>>;
|
||||
|
||||
/** Called on session lifecycle events - use to reconstruct state or cleanup resources */
|
||||
@@ -154,7 +154,7 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
|
||||
|
||||
/** Factory function that creates a custom tool or array of tools */
|
||||
export type CustomToolFactory = (
|
||||
pi: CustomToolAPI
|
||||
pi: CustomToolAPI,
|
||||
) => CustomTool<any, any> | CustomTool<any, any>[] | Promise<CustomTool<any, any> | CustomTool<any, any>[]>;
|
||||
|
||||
/** Loaded custom tool with metadata and wrapped AgentTool */
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Extension runner - executes extensions and manages their lifecycle.
|
||||
*/
|
||||
|
||||
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
||||
import type { KeyId } from "@oh-my-pi/pi-tui";
|
||||
import { theme } from "../../modes/interactive/theme/theme";
|
||||
import type { ModelRegistry } from "../model-registry";
|
||||
@@ -53,7 +53,7 @@ export type BranchHandler = (entryId: string) => Promise<{ cancelled: boolean }>
|
||||
|
||||
export type NavigateTreeHandler = (
|
||||
targetId: string,
|
||||
options?: { summarize?: boolean }
|
||||
options?: { summarize?: boolean },
|
||||
) => Promise<{ cancelled: boolean }>;
|
||||
|
||||
const noOpUIContext: ExtensionUIContext = {
|
||||
@@ -94,7 +94,7 @@ export class ExtensionRunner {
|
||||
extensions: LoadedExtension[],
|
||||
cwd: string,
|
||||
sessionManager: SessionManager,
|
||||
modelRegistry: ModelRegistry
|
||||
modelRegistry: ModelRegistry,
|
||||
) {
|
||||
this.extensions = extensions;
|
||||
this.uiContext = noOpUIContext;
|
||||
@@ -215,7 +215,7 @@ export class ExtensionRunner {
|
||||
|
||||
if (ExtensionRunner.RESERVED_SHORTCUTS.has(normalizedKey)) {
|
||||
console.warn(
|
||||
`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`
|
||||
`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -223,7 +223,7 @@ export class ExtensionRunner {
|
||||
const existing = allShortcuts.get(normalizedKey);
|
||||
if (existing) {
|
||||
console.warn(
|
||||
`Extension shortcut conflict: '${key}' registered by both ${existing.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`
|
||||
`Extension shortcut conflict: '${key}' registered by both ${existing.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,
|
||||
);
|
||||
}
|
||||
allShortcuts.set(normalizedKey, shortcut);
|
||||
@@ -309,7 +309,7 @@ export class ExtensionRunner {
|
||||
}
|
||||
|
||||
private isSessionBeforeEvent(
|
||||
type: string
|
||||
type: string,
|
||||
): type is "session_before_switch" | "session_before_branch" | "session_before_compact" | "session_before_tree" {
|
||||
return (
|
||||
type === "session_before_switch" ||
|
||||
@@ -320,7 +320,7 @@ export class ExtensionRunner {
|
||||
}
|
||||
|
||||
async emit(
|
||||
event: ExtensionEvent
|
||||
event: ExtensionEvent,
|
||||
): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {
|
||||
const ctx = this.createContext();
|
||||
let result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;
|
||||
@@ -428,7 +428,7 @@ export class ExtensionRunner {
|
||||
|
||||
async emitBeforeAgentStart(
|
||||
prompt: string,
|
||||
images?: ImageContent[]
|
||||
images?: ImageContent[],
|
||||
): Promise<BeforeAgentStartCombinedResult | undefined> {
|
||||
const ctx = this.createContext();
|
||||
const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
* - Interact with the user via UI primitives
|
||||
*/
|
||||
|
||||
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
||||
import type { Component, KeyId, TUI } from "@oh-my-pi/pi-tui";
|
||||
import type { Static, TSchema } from "@sinclair/typebox";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme";
|
||||
@@ -67,8 +67,8 @@ export interface ExtensionUIContext {
|
||||
factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
done: (result: T) => void
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
): Promise<T>;
|
||||
|
||||
/** Set the text in the core input editor. */
|
||||
@@ -178,7 +178,7 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
|
||||
params: Static<TParams>,
|
||||
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
|
||||
ctx: ExtensionContext,
|
||||
signal?: AbortSignal
|
||||
signal?: AbortSignal,
|
||||
): Promise<AgentToolResult<TDetails>>;
|
||||
|
||||
/** Called on session lifecycle events - use to reconstruct state or cleanup resources */
|
||||
@@ -490,7 +490,7 @@ export interface MessageRenderOptions {
|
||||
export type MessageRenderer<T = unknown> = (
|
||||
message: CustomMessage<T>,
|
||||
options: MessageRenderOptions,
|
||||
theme: Theme
|
||||
theme: Theme,
|
||||
) => Component | undefined;
|
||||
|
||||
// ============================================================================
|
||||
@@ -535,17 +535,17 @@ export interface ExtensionAPI {
|
||||
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
||||
on(
|
||||
event: "session_before_switch",
|
||||
handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>
|
||||
handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>,
|
||||
): void;
|
||||
on(event: "session_switch", handler: ExtensionHandler<SessionSwitchEvent>): void;
|
||||
on(
|
||||
event: "session_before_branch",
|
||||
handler: ExtensionHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>
|
||||
handler: ExtensionHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>,
|
||||
): void;
|
||||
on(event: "session_branch", handler: ExtensionHandler<SessionBranchEvent>): void;
|
||||
on(
|
||||
event: "session_before_compact",
|
||||
handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>
|
||||
handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,
|
||||
): void;
|
||||
on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
|
||||
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
|
||||
@@ -580,7 +580,7 @@ export interface ExtensionAPI {
|
||||
options: {
|
||||
description?: string;
|
||||
handler: (ctx: ExtensionContext) => Promise<void> | void;
|
||||
}
|
||||
},
|
||||
): void;
|
||||
|
||||
/** Register a CLI flag. */
|
||||
@@ -590,7 +590,7 @@ export interface ExtensionAPI {
|
||||
description?: string;
|
||||
type: "boolean" | "string";
|
||||
default?: boolean | string;
|
||||
}
|
||||
},
|
||||
): void;
|
||||
|
||||
/** Get the value of a registered CLI flag. */
|
||||
@@ -610,7 +610,7 @@ export interface ExtensionAPI {
|
||||
/** Send a custom message to the session. */
|
||||
sendMessage<T = unknown>(
|
||||
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
||||
): void;
|
||||
|
||||
/** Append a custom entry to the session for state persistence (not sent to LLM). */
|
||||
@@ -663,7 +663,7 @@ type HandlerFn = (...args: unknown[]) => Promise<unknown>;
|
||||
|
||||
export type SendMessageHandler = <T = unknown>(
|
||||
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
||||
) => void;
|
||||
|
||||
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Tool wrappers for extensions.
|
||||
*/
|
||||
|
||||
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme";
|
||||
import type { ExtensionRunner } from "./runner";
|
||||
import type { ExtensionContext, RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types";
|
||||
@@ -26,7 +26,7 @@ export function wrapRegisteredTool(registeredTool: RegisteredTool, getContext: (
|
||||
definition.renderResult?.(
|
||||
result,
|
||||
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
||||
theme as Theme
|
||||
theme as Theme,
|
||||
)
|
||||
: undefined,
|
||||
};
|
||||
@@ -37,7 +37,7 @@ export function wrapRegisteredTool(registeredTool: RegisteredTool, getContext: (
|
||||
*/
|
||||
export function wrapRegisteredTools(
|
||||
registeredTools: RegisteredTool[],
|
||||
getContext: () => ExtensionContext
|
||||
getContext: () => ExtensionContext,
|
||||
): AgentTool[] {
|
||||
return registeredTools.map((rt) => wrapRegisteredTool(rt, getContext));
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: Exten
|
||||
params: Record<string, unknown>,
|
||||
signal?: AbortSignal,
|
||||
onUpdate?: AgentToolUpdateCallback<T>,
|
||||
context?: AgentToolContext
|
||||
context?: AgentToolContext,
|
||||
) => {
|
||||
// Emit tool_call event - extensions can block execution
|
||||
if (runner.hasHandlers("tool_call")) {
|
||||
@@ -113,7 +113,8 @@ export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: Exten
|
||||
if (resultResult.isError === true && !executionError) {
|
||||
// Extension marks a successful result as error
|
||||
const textBlocks = (modifiedContent ?? []).filter((c): c is TextContent => c.type === "text");
|
||||
const errorText = textBlocks.map((t) => t.text).join("\n") || "Tool result marked as error by extension";
|
||||
const errorText =
|
||||
textBlocks.map((t) => t.text).join("\n") || "Tool result marked as error by extension";
|
||||
throw new Error(errorText);
|
||||
}
|
||||
if (resultResult.isError === false && executionError) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Hook runner - executes hooks and manages their lifecycle.
|
||||
*/
|
||||
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import { theme } from "../../modes/interactive/theme/theme";
|
||||
import type { ModelRegistry } from "../model-registry";
|
||||
import type { SessionManager } from "../session-manager";
|
||||
@@ -272,7 +272,7 @@ export class HookRunner {
|
||||
* Check if event type is a session "before_*" event that can be cancelled.
|
||||
*/
|
||||
private isSessionBeforeEvent(
|
||||
type: string
|
||||
type: string,
|
||||
): type is "session_before_switch" | "session_before_branch" | "session_before_compact" | "session_before_tree" {
|
||||
return (
|
||||
type === "session_before_switch" ||
|
||||
@@ -287,7 +287,7 @@ export class HookRunner {
|
||||
* Returns the result from session before_* / tool_result events (if any handler returns one).
|
||||
*/
|
||||
async emit(
|
||||
event: HookEvent
|
||||
event: HookEvent,
|
||||
): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {
|
||||
const ctx = this.createContext();
|
||||
let result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;
|
||||
@@ -400,7 +400,7 @@ export class HookRunner {
|
||||
*/
|
||||
async emitBeforeAgentStart(
|
||||
prompt: string,
|
||||
images?: import("@oh-my-pi/pi-ai").ImageContent[]
|
||||
images?: import("@oh-my-pi/pi-ai").ImageContent[],
|
||||
): Promise<BeforeAgentStartEventResult | undefined> {
|
||||
const ctx = this.createContext();
|
||||
let result: BeforeAgentStartEventResult | undefined;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* and interact with the user via UI primitives.
|
||||
*/
|
||||
|
||||
import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
||||
import type { Component, TUI } from "@oh-my-pi/pi-tui";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme";
|
||||
import type { CompactionPreparation, CompactionResult } from "../compaction/index";
|
||||
@@ -97,8 +97,8 @@ export interface HookUIContext {
|
||||
factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
done: (result: T) => void
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
): Promise<T>;
|
||||
|
||||
/**
|
||||
@@ -632,7 +632,7 @@ export interface HookMessageRenderOptions {
|
||||
export type HookMessageRenderer<T = unknown> = (
|
||||
message: HookMessage<T>,
|
||||
options: HookMessageRenderOptions,
|
||||
theme: Theme
|
||||
theme: Theme,
|
||||
) => Component | undefined;
|
||||
|
||||
/**
|
||||
@@ -658,7 +658,7 @@ export interface HookAPI {
|
||||
on(event: "session_branch", handler: HookHandler<SessionBranchEvent>): void;
|
||||
on(
|
||||
event: "session_before_compact",
|
||||
handler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>
|
||||
handler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,
|
||||
): void;
|
||||
on(event: "session_compact", handler: HookHandler<SessionCompactEvent>): void;
|
||||
on(event: "session_shutdown", handler: HookHandler<SessionShutdownEvent>): void;
|
||||
@@ -692,7 +692,7 @@ export interface HookAPI {
|
||||
*/
|
||||
sendMessage<T = unknown>(
|
||||
message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">,
|
||||
triggerTurn?: boolean
|
||||
triggerTurn?: boolean,
|
||||
): void;
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* and provides a transformer to convert them to LLM-compatible messages.
|
||||
*/
|
||||
|
||||
import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
|
||||
|
||||
export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
|
||||
|
||||
@@ -136,7 +136,7 @@ export function createBranchSummaryMessage(summary: string, fromId: string, time
|
||||
export function createCompactionSummaryMessage(
|
||||
summary: string,
|
||||
tokensBefore: number,
|
||||
timestamp: string
|
||||
timestamp: string,
|
||||
): CompactionSummaryMessage {
|
||||
return {
|
||||
role: "compactionSummary",
|
||||
@@ -152,7 +152,7 @@ export function createCustomMessage(
|
||||
content: string | (TextContent | ImageContent)[],
|
||||
display: boolean,
|
||||
details: unknown | undefined,
|
||||
timestamp: string
|
||||
timestamp: string,
|
||||
): CustomMessage {
|
||||
return {
|
||||
role: "custom",
|
||||
|
||||
@@ -39,7 +39,7 @@ const ModelDefinitionSchema = Type.Object({
|
||||
Type.Literal("anthropic-messages"),
|
||||
Type.Literal("google-generative-ai"),
|
||||
Type.Literal("google-vertex"),
|
||||
])
|
||||
]),
|
||||
),
|
||||
reasoning: Type.Boolean(),
|
||||
input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
|
||||
@@ -66,7 +66,7 @@ const ProviderConfigSchema = Type.Object({
|
||||
Type.Literal("anthropic-messages"),
|
||||
Type.Literal("google-generative-ai"),
|
||||
Type.Literal("google-vertex"),
|
||||
])
|
||||
]),
|
||||
),
|
||||
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
||||
authHeader: Type.Optional(Type.Boolean()),
|
||||
@@ -126,7 +126,7 @@ export class ModelRegistry {
|
||||
constructor(
|
||||
readonly authStorage: AuthStorage,
|
||||
private modelsJsonPath: string | undefined = undefined,
|
||||
private fallbackPaths: string[] = []
|
||||
private fallbackPaths: string[] = [],
|
||||
) {
|
||||
// Set up fallback resolver for custom provider API keys
|
||||
this.authStorage.setFallbackResolver((provider) => {
|
||||
@@ -189,7 +189,9 @@ export class ModelRegistry {
|
||||
// Update github-copilot base URL based on OAuth credentials
|
||||
const copilotCred = this.authStorage.get("github-copilot");
|
||||
if (copilotCred?.type === "oauth") {
|
||||
const domain = copilotCred.enterpriseUrl ? normalizeDomain(copilotCred.enterpriseUrl) ?? undefined : undefined;
|
||||
const domain = copilotCred.enterpriseUrl
|
||||
? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)
|
||||
: undefined;
|
||||
const baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);
|
||||
this.models = combined.map((m) => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
|
||||
} else {
|
||||
@@ -265,7 +267,7 @@ export class ModelRegistry {
|
||||
return emptyCustomModelsResult(`Failed to parse models.json: ${error.message}\n\nFile: ${modelsJsonPath}`);
|
||||
}
|
||||
return emptyCustomModelsResult(
|
||||
`Failed to load models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsJsonPath}`
|
||||
`Failed to load models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsJsonPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -279,7 +281,7 @@ export class ModelRegistry {
|
||||
// Override-only config: just needs baseUrl (to override built-in)
|
||||
if (!providerConfig.baseUrl) {
|
||||
throw new Error(
|
||||
`Provider ${providerName}: must specify either "baseUrl" (for override) or "models" (for replacement).`
|
||||
`Provider ${providerName}: must specify either "baseUrl" (for override) or "models" (for replacement).`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -297,7 +299,7 @@ export class ModelRegistry {
|
||||
|
||||
if (!hasProviderApi && !hasModelApi) {
|
||||
throw new Error(
|
||||
`Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`
|
||||
`Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -329,7 +331,9 @@ export class ModelRegistry {
|
||||
|
||||
// Merge headers: provider headers are base, model headers override
|
||||
let headers =
|
||||
providerConfig.headers || modelDef.headers ? { ...providerConfig.headers, ...modelDef.headers } : undefined;
|
||||
providerConfig.headers || modelDef.headers
|
||||
? { ...providerConfig.headers, ...modelDef.headers }
|
||||
: undefined;
|
||||
|
||||
// If authHeader is true, add Authorization header with resolved API key
|
||||
if (providerConfig.authHeader && providerConfig.apiKey) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Model resolution, scoping, and initial selection
|
||||
*/
|
||||
|
||||
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
|
||||
import chalk from "chalk";
|
||||
import { minimatch } from "minimatch";
|
||||
import { isValidThinkingLevel } from "../cli/args";
|
||||
@@ -25,6 +25,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
||||
cerebras: "zai-glm-4.6",
|
||||
zai: "glm-4.6",
|
||||
mistral: "devstral-medium-latest",
|
||||
opencode: "claude-sonnet-4-5",
|
||||
};
|
||||
|
||||
export interface ScopedModel {
|
||||
@@ -79,7 +80,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
||||
const provider = modelPattern.substring(0, slashIndex);
|
||||
const modelId = modelPattern.substring(slashIndex + 1);
|
||||
const providerMatch = availableModels.find(
|
||||
(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase()
|
||||
(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),
|
||||
);
|
||||
if (providerMatch) {
|
||||
return providerMatch;
|
||||
@@ -97,7 +98,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
||||
const matches = availableModels.filter(
|
||||
(m) =>
|
||||
m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
|
||||
m.name?.toLowerCase().includes(modelPattern.toLowerCase())
|
||||
m.name?.toLowerCase().includes(modelPattern.toLowerCase()),
|
||||
);
|
||||
|
||||
if (matches.length === 0) {
|
||||
@@ -351,7 +352,7 @@ export async function restoreModelFromSession(
|
||||
savedModelId: string,
|
||||
currentModel: Model<Api> | undefined,
|
||||
shouldPrintMessages: boolean,
|
||||
modelRegistry: ModelRegistry
|
||||
modelRegistry: ModelRegistry,
|
||||
): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
|
||||
const restoredModel = modelRegistry.find(savedProvider, savedModelId);
|
||||
|
||||
@@ -427,7 +428,7 @@ export async function restoreModelFromSession(
|
||||
*/
|
||||
export async function findSmolModel(
|
||||
modelRegistry: ModelRegistry,
|
||||
savedModel?: string
|
||||
savedModel?: string,
|
||||
): Promise<Model<Api> | undefined> {
|
||||
const availableModels = modelRegistry.getAvailable();
|
||||
if (availableModels.length === 0) return undefined;
|
||||
@@ -470,7 +471,7 @@ export async function findSmolModel(
|
||||
*/
|
||||
export async function findSlowModel(
|
||||
modelRegistry: ModelRegistry,
|
||||
savedModel?: string
|
||||
savedModel?: string,
|
||||
): Promise<Model<Api> | undefined> {
|
||||
const availableModels = modelRegistry.getAvailable();
|
||||
if (availableModels.length === 0) return undefined;
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
*/
|
||||
|
||||
import { join } from "node:path";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import { Agent, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import chalk from "chalk";
|
||||
// Import discovery to register all providers on startup
|
||||
@@ -248,7 +248,7 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
|
||||
*/
|
||||
export async function discoverModels(
|
||||
authStorage: AuthStorage,
|
||||
agentDir: string = getDefaultAgentDir()
|
||||
agentDir: string = getDefaultAgentDir(),
|
||||
): Promise<ModelRegistry> {
|
||||
const primaryPath = join(agentDir, "models.json");
|
||||
// Get all models.json paths (user-level only), excluding the primary
|
||||
@@ -288,7 +288,7 @@ export function discoverSkills(cwd?: string, _agentDir?: string, settings?: Skil
|
||||
*/
|
||||
export function discoverContextFiles(
|
||||
cwd?: string,
|
||||
_agentDir?: string
|
||||
_agentDir?: string,
|
||||
): Array<{ path: string; content: string; depth?: number }> {
|
||||
return loadContextFilesInternal({
|
||||
cwd: cwd ?? process.cwd(),
|
||||
@@ -436,11 +436,11 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
||||
const component = tool.renderResult?.(
|
||||
result,
|
||||
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
||||
theme
|
||||
theme,
|
||||
);
|
||||
// Return empty component if undefined to match Component type requirement
|
||||
return component ?? ({ render: () => [] } as unknown as Component);
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
[TOOL_DEFINITION_MARKER]: true,
|
||||
};
|
||||
@@ -465,19 +465,19 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
||||
};
|
||||
|
||||
api.on("session_start", async (_event, ctx) =>
|
||||
runOnSession({ reason: "start", previousSessionFile: undefined }, ctx)
|
||||
runOnSession({ reason: "start", previousSessionFile: undefined }, ctx),
|
||||
);
|
||||
api.on("session_switch", async (event, ctx) =>
|
||||
runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx)
|
||||
runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx),
|
||||
);
|
||||
api.on("session_branch", async (event, ctx) =>
|
||||
runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx)
|
||||
runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx),
|
||||
);
|
||||
api.on("session_tree", async (_event, ctx) =>
|
||||
runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx)
|
||||
runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx),
|
||||
);
|
||||
api.on("session_shutdown", async (_event, ctx) =>
|
||||
runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx)
|
||||
runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx),
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -659,7 +659,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
if (options.hasUI && serverNames.length > 0) {
|
||||
process.stderr.write(
|
||||
chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}...
|
||||
`)
|
||||
`),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -728,7 +728,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
configuredPaths,
|
||||
cwd,
|
||||
eventBus,
|
||||
settingsManager.getDisabledExtensions()
|
||||
settingsManager.getDisabledExtensions(),
|
||||
);
|
||||
time("discoverAndLoadExtensions");
|
||||
for (const { path, error } of extensionsResult.errors) {
|
||||
@@ -905,7 +905,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
transformContext: extensionRunner
|
||||
? async (messages) => {
|
||||
return extensionRunner.emitContext(messages);
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
steeringMode: settingsManager.getSteeringMode(),
|
||||
followUpMode: settingsManager.getFollowUpMode(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import type { ImageContent, Message, TextContent, Usage } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Message, TextContent, Usage } from "@oh-my-pi/pi-ai";
|
||||
import { nanoid } from "nanoid";
|
||||
import { getAgentDir as getDefaultAgentDir } from "../config";
|
||||
import { resizeImage } from "../utils/image-resize";
|
||||
@@ -304,7 +304,7 @@ function toError(value: unknown): Error {
|
||||
export function buildSessionContext(
|
||||
entries: SessionEntry[],
|
||||
leafId?: string | null,
|
||||
byId?: Map<string, SessionEntry>
|
||||
byId?: Map<string, SessionEntry>,
|
||||
): SessionContext {
|
||||
// Build uuid index if not available
|
||||
if (!byId) {
|
||||
@@ -382,7 +382,7 @@ export function buildSessionContext(
|
||||
messages.push(entry.message);
|
||||
} else if (entry.type === "custom_message") {
|
||||
messages.push(
|
||||
createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp)
|
||||
createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp),
|
||||
);
|
||||
} else if (entry.type === "branch_summary" && entry.summary) {
|
||||
messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
|
||||
@@ -540,7 +540,7 @@ function getSortedSessions(sessionDir: string, storage: SessionStorage): RecentS
|
||||
/** Exported for testing */
|
||||
export function findMostRecentSession(
|
||||
sessionDir: string,
|
||||
storage: SessionStorage = new FileSessionStorage()
|
||||
storage: SessionStorage = new FileSessionStorage(),
|
||||
): string | null {
|
||||
const sessions = getSortedSessions(sessionDir, storage);
|
||||
return sessions[0]?.path || null;
|
||||
@@ -645,7 +645,7 @@ async function truncateForPersistence<T>(obj: T, key?: string): Promise<T> {
|
||||
const newItem = await truncateForPersistence(item, key);
|
||||
if (newItem !== item) changed = true;
|
||||
return newItem;
|
||||
})
|
||||
}),
|
||||
);
|
||||
return changed ? (result as T) : obj;
|
||||
}
|
||||
@@ -800,7 +800,7 @@ class NdjsonFileWriter {
|
||||
export function getRecentSessions(
|
||||
sessionDir: string,
|
||||
limit = 3,
|
||||
storage: SessionStorage = new FileSessionStorage()
|
||||
storage: SessionStorage = new FileSessionStorage(),
|
||||
): RecentSessionInfo[] {
|
||||
return getSortedSessions(sessionDir, storage).slice(0, limit);
|
||||
}
|
||||
@@ -1021,7 +1021,7 @@ export class SessionManager {
|
||||
async () => {
|
||||
await this._closePersistWriterInternal();
|
||||
},
|
||||
{ ignoreError: true }
|
||||
{ ignoreError: true },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1229,7 +1229,7 @@ export class SessionManager {
|
||||
firstKeptEntryId: string,
|
||||
tokensBefore: number,
|
||||
details?: T,
|
||||
fromExtension?: boolean
|
||||
fromExtension?: boolean,
|
||||
): string {
|
||||
const entry: CompactionEntry<T> = {
|
||||
type: "compaction",
|
||||
@@ -1272,7 +1272,7 @@ export class SessionManager {
|
||||
customType: string,
|
||||
content: string | (TextContent | ImageContent)[],
|
||||
display: boolean,
|
||||
details?: T
|
||||
details?: T,
|
||||
): string {
|
||||
const entry: CustomMessageEntry<T> = {
|
||||
type: "custom_message",
|
||||
@@ -1632,7 +1632,7 @@ export class SessionManager {
|
||||
static async open(
|
||||
path: string,
|
||||
sessionDir?: string,
|
||||
storage: SessionStorage = new FileSessionStorage()
|
||||
storage: SessionStorage = new FileSessionStorage(),
|
||||
): Promise<SessionManager> {
|
||||
// Extract cwd from session header if possible, otherwise use process.cwd()
|
||||
const entries = loadEntriesFromFile(path, storage);
|
||||
@@ -1653,7 +1653,7 @@ export class SessionManager {
|
||||
static async continueRecent(
|
||||
cwd: string,
|
||||
sessionDir?: string,
|
||||
storage: SessionStorage = new FileSessionStorage()
|
||||
storage: SessionStorage = new FileSessionStorage(),
|
||||
): Promise<SessionManager> {
|
||||
const dir = sessionDir ?? getDefaultSessionDir(cwd, storage);
|
||||
const mostRecent = findMostRecentSession(dir, storage);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
||||
import type { TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { homedir } from "node:os";
|
||||
import path from "node:path";
|
||||
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* - Session artifacts for debugging
|
||||
*/
|
||||
|
||||
import type { Usage } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Usage } from "@oh-my-pi/pi-ai";
|
||||
import type { Theme } from "../../../modes/interactive/theme/theme";
|
||||
import taskDescriptionTemplate from "../../../prompts/tools/task.md" with { type: "text" };
|
||||
import { formatDuration } from "../render-utils";
|
||||
|
||||
@@ -59,12 +59,12 @@ export const taskSchema = Type.Object({
|
||||
model: Type.Optional(
|
||||
Type.String({
|
||||
description: "Model override for all tasks (fuzzy matching, e.g. 'sonnet', 'opus')",
|
||||
})
|
||||
}),
|
||||
),
|
||||
output: Type.Optional(
|
||||
Type.Any({
|
||||
description: "JTD schema for structured subagent output (used by the complete tool)",
|
||||
})
|
||||
}),
|
||||
),
|
||||
tasks: Type.Array(taskItemSchema, {
|
||||
description: "Tasks to run in parallel",
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* 5. Parent can send { type: "abort" } to request cancellation
|
||||
*/
|
||||
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentSessionEvent } from "../../agent-session";
|
||||
import { parseModelPattern, parseModelString } from "../../model-resolver";
|
||||
import { createAgentSession, discoverAuthStorage, discoverModels } from "../../sdk";
|
||||
@@ -88,7 +88,7 @@ let pendingAbort = false;
|
||||
*/
|
||||
function resolveModelOverride(
|
||||
override: string | undefined,
|
||||
modelRegistry: { getAvailable: () => Model<Api>[]; find: (provider: string, id: string) => Model<Api> | undefined }
|
||||
modelRegistry: { getAvailable: () => Model<Api>[]; find: (provider: string, id: string) => Model<Api> | undefined },
|
||||
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel } {
|
||||
if (!override) return {};
|
||||
|
||||
@@ -190,7 +190,7 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
|
||||
() => {
|
||||
void session.abort();
|
||||
},
|
||||
{ once: true, signal: sessionAbortController.signal }
|
||||
{ once: true, signal: sessionAbortController.signal },
|
||||
);
|
||||
|
||||
// Initialize extensions (equivalent to CLI's extension initialization)
|
||||
|
||||
@@ -63,7 +63,7 @@ async function runInteractiveMode(
|
||||
lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined,
|
||||
initialMessage?: string,
|
||||
initialImages?: ImageContent[],
|
||||
fdPath: string | undefined = undefined
|
||||
fdPath: string | undefined = undefined,
|
||||
): Promise<void> {
|
||||
const mode = new InteractiveMode(session, version, changelogMarkdown, setExtensionUIContext, lspServers, fdPath);
|
||||
|
||||
@@ -120,7 +120,7 @@ async function runInteractiveMode(
|
||||
|
||||
async function prepareInitialMessage(
|
||||
parsed: Args,
|
||||
autoResizeImages: boolean
|
||||
autoResizeImages: boolean,
|
||||
): Promise<{
|
||||
initialMessage?: string;
|
||||
initialImages?: ImageContent[];
|
||||
@@ -262,7 +262,7 @@ async function buildSessionOptions(
|
||||
scopedModels: ScopedModel[],
|
||||
sessionManager: SessionManager | undefined,
|
||||
modelRegistry: ModelRegistry,
|
||||
settingsManager: SettingsManager
|
||||
settingsManager: SettingsManager,
|
||||
): Promise<CreateAgentSessionOptions> {
|
||||
const options: CreateAgentSessionOptions = {
|
||||
cwd: parsed.cwd ?? process.cwd(),
|
||||
@@ -484,7 +484,7 @@ export async function main(args: string[]) {
|
||||
scopedModels,
|
||||
sessionManager,
|
||||
modelRegistry,
|
||||
settingsManager
|
||||
settingsManager,
|
||||
);
|
||||
sessionOptions.authStorage = authStorage;
|
||||
sessionOptions.modelRegistry = modelRegistry;
|
||||
@@ -575,7 +575,7 @@ export async function main(args: string[]) {
|
||||
lspServers,
|
||||
initialMessage,
|
||||
initialImages,
|
||||
fdPath
|
||||
fdPath,
|
||||
);
|
||||
} else {
|
||||
await runPrintMode(session, mode, parsed.messages, initialMessage, initialImages);
|
||||
|
||||
@@ -33,7 +33,9 @@ export class AssistantMessageComponent extends Container {
|
||||
|
||||
if (
|
||||
message.content.length > 0 &&
|
||||
message.content.some((c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()))
|
||||
message.content.some(
|
||||
(c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()),
|
||||
)
|
||||
) {
|
||||
this.contentContainer.addChild(new Spacer(1));
|
||||
}
|
||||
@@ -61,7 +63,7 @@ export class AssistantMessageComponent extends Container {
|
||||
new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), {
|
||||
color: (text: string) => theme.fg("thinkingText", text),
|
||||
italic: true,
|
||||
})
|
||||
}),
|
||||
);
|
||||
this.contentContainer.addChild(new Spacer(1));
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export class CustomMessageComponent extends Container {
|
||||
this.box.addChild(
|
||||
new Markdown(text, 0, 0, getMarkdownTheme(), {
|
||||
color: (value: string) => theme.fg("customMessageText", value),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +181,9 @@ export class FooterComponent implements Component {
|
||||
// Calculate context percentage from last message (input + output + cacheRead + cacheWrite)
|
||||
const contextTokens = lastAssistantMessage
|
||||
? lastAssistantMessage.usage.input +
|
||||
lastAssistantMessage.usage.output +
|
||||
lastAssistantMessage.usage.cacheRead +
|
||||
lastAssistantMessage.usage.cacheWrite
|
||||
lastAssistantMessage.usage.output +
|
||||
lastAssistantMessage.usage.cacheRead +
|
||||
lastAssistantMessage.usage.cacheWrite
|
||||
: 0;
|
||||
const contextWindow = state.model?.contextWindow || 0;
|
||||
const contextPercentValue = contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0;
|
||||
|
||||
@@ -90,7 +90,7 @@ export class HookMessageComponent extends Container {
|
||||
this.box.addChild(
|
||||
new Markdown(text, 0, 0, getMarkdownTheme(), {
|
||||
color: (text: string) => theme.fg("customMessageText", text),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export class ModelSelectorComponent extends Container {
|
||||
scopedModels: ReadonlyArray<ScopedModelItem>,
|
||||
onSelect: (model: Model<any>, role: string) => void,
|
||||
onCancel: () => void,
|
||||
options?: { temporaryOnly?: boolean }
|
||||
options?: { temporaryOnly?: boolean },
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -327,7 +327,7 @@ export class ModelSelectorComponent extends Container {
|
||||
const maxVisible = 10;
|
||||
const startIndex = Math.max(
|
||||
0,
|
||||
Math.min(this.selectedIndex - Math.floor(maxVisible / 2), this.filteredModels.length - maxVisible)
|
||||
Math.min(this.selectedIndex - Math.floor(maxVisible / 2), this.filteredModels.length - maxVisible),
|
||||
);
|
||||
const endIndex = Math.min(startIndex + maxVisible, this.filteredModels.length);
|
||||
|
||||
@@ -419,7 +419,7 @@ export class ModelSelectorComponent extends Container {
|
||||
const menuWidth = Math.max(
|
||||
visibleWidth(headerText),
|
||||
visibleWidth(hintText),
|
||||
...actionLines.map((line) => visibleWidth(line))
|
||||
...actionLines.map((line) => visibleWidth(line)),
|
||||
);
|
||||
|
||||
// Menu header
|
||||
|
||||
@@ -21,7 +21,7 @@ export class OAuthSelectorComponent extends Container {
|
||||
mode: "login" | "logout",
|
||||
authStorage: AuthStorage,
|
||||
onSelect: (providerId: string) => void,
|
||||
onCancel: () => void
|
||||
onCancel: () => void,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
||||
@@ -225,9 +225,9 @@ export class StatusLineComponent implements Component {
|
||||
|
||||
const contextTokens = lastAssistantMessage
|
||||
? lastAssistantMessage.usage.input +
|
||||
lastAssistantMessage.usage.output +
|
||||
lastAssistantMessage.usage.cacheRead +
|
||||
lastAssistantMessage.usage.cacheWrite
|
||||
lastAssistantMessage.usage.output +
|
||||
lastAssistantMessage.usage.cacheRead +
|
||||
lastAssistantMessage.usage.cacheWrite
|
||||
: 0;
|
||||
const contextWindow = state.model?.contextWindow || 0;
|
||||
const contextPercent = contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { AssistantMessage, ImageContent, Message, OAuthProvider } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { AssistantMessage, ImageContent, Message, OAuthProvider } from "@oh-my-pi/pi-ai";
|
||||
import type { SlashCommand } from "@oh-my-pi/pi-tui";
|
||||
import {
|
||||
CombinedAutocompleteProvider,
|
||||
@@ -181,8 +181,10 @@ export class InteractiveMode {
|
||||
version: string,
|
||||
changelogMarkdown: string | undefined = undefined,
|
||||
private setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
|
||||
private lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined = undefined,
|
||||
fdPath: string | undefined = undefined
|
||||
private lspServers:
|
||||
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>
|
||||
| undefined = undefined,
|
||||
fdPath: string | undefined = undefined,
|
||||
) {
|
||||
this.session = session;
|
||||
this.version = version;
|
||||
@@ -268,7 +270,7 @@ export class InteractiveMode {
|
||||
const autocompleteProvider = new CombinedAutocompleteProvider(
|
||||
[...slashCommands, ...fileSlashCommands, ...hookCommands, ...customCommands],
|
||||
process.cwd(),
|
||||
fdPath
|
||||
fdPath,
|
||||
);
|
||||
this.editor.setAutocompleteProvider(autocompleteProvider);
|
||||
}
|
||||
@@ -453,7 +455,7 @@ export class InteractiveMode {
|
||||
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
this.chatContainer.addChild(
|
||||
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1)
|
||||
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
||||
);
|
||||
this.ui.requestRender();
|
||||
|
||||
@@ -583,7 +585,7 @@ export class InteractiveMode {
|
||||
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
this.chatContainer.addChild(
|
||||
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1)
|
||||
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
||||
);
|
||||
this.ui.requestRender();
|
||||
|
||||
@@ -649,8 +651,8 @@ export class InteractiveMode {
|
||||
_factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
done: (result: T) => void
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
) => undefined as T,
|
||||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
@@ -666,7 +668,7 @@ export class InteractiveMode {
|
||||
*/
|
||||
private async emitCustomToolSessionEvent(
|
||||
reason: "start" | "switch" | "branch" | "tree" | "shutdown",
|
||||
previousSessionFile?: string
|
||||
previousSessionFile?: string,
|
||||
): Promise<void> {
|
||||
const event = { reason, previousSessionFile };
|
||||
const uiContext = this.session.extensionRunner?.getUIContext();
|
||||
@@ -736,7 +738,7 @@ export class InteractiveMode {
|
||||
() => {
|
||||
this.hideHookSelector();
|
||||
resolve(undefined);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.editorContainer.clear();
|
||||
@@ -780,7 +782,7 @@ export class InteractiveMode {
|
||||
() => {
|
||||
this.hideHookInput();
|
||||
resolve(undefined);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.editorContainer.clear();
|
||||
@@ -817,7 +819,7 @@ export class InteractiveMode {
|
||||
() => {
|
||||
this.hideHookEditor();
|
||||
resolve(undefined);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.editorContainer.clear();
|
||||
@@ -858,8 +860,8 @@ export class InteractiveMode {
|
||||
factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
done: (result: T) => void
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
): Promise<T> {
|
||||
const savedText = this.editor.getText();
|
||||
|
||||
@@ -1197,7 +1199,7 @@ export class InteractiveMode {
|
||||
(spinner) => theme.fg("accent", spinner),
|
||||
(text) => theme.fg("muted", text),
|
||||
`Working${theme.format.ellipsis} (esc to interrupt)`,
|
||||
getSymbolTheme().spinnerFrames
|
||||
getSymbolTheme().spinnerFrames,
|
||||
);
|
||||
this.statusContainer.addChild(this.loadingAnimation);
|
||||
this.startVoiceProgressTimer();
|
||||
@@ -1243,7 +1245,7 @@ export class InteractiveMode {
|
||||
},
|
||||
tool,
|
||||
this.ui,
|
||||
this.sessionManager.getCwd()
|
||||
this.sessionManager.getCwd(),
|
||||
);
|
||||
component.setExpanded(this.toolOutputExpanded);
|
||||
this.chatContainer.addChild(component);
|
||||
@@ -1313,7 +1315,7 @@ export class InteractiveMode {
|
||||
},
|
||||
tool,
|
||||
this.ui,
|
||||
this.sessionManager.getCwd()
|
||||
this.sessionManager.getCwd(),
|
||||
);
|
||||
component.setExpanded(this.toolOutputExpanded);
|
||||
this.chatContainer.addChild(component);
|
||||
@@ -1384,7 +1386,7 @@ export class InteractiveMode {
|
||||
(spinner) => theme.fg("accent", spinner),
|
||||
(text) => theme.fg("muted", text),
|
||||
`${reasonText}Auto-compacting${theme.format.ellipsis} (esc to cancel)`,
|
||||
getSymbolTheme().spinnerFrames
|
||||
getSymbolTheme().spinnerFrames,
|
||||
);
|
||||
this.statusContainer.addChild(this.autoCompactionLoader);
|
||||
this.ui.requestRender();
|
||||
@@ -1440,7 +1442,7 @@ export class InteractiveMode {
|
||||
(spinner) => theme.fg("warning", spinner),
|
||||
(text) => theme.fg("muted", text),
|
||||
`Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s${theme.format.ellipsis} (esc to cancel)`,
|
||||
getSymbolTheme().spinnerFrames
|
||||
getSymbolTheme().spinnerFrames,
|
||||
);
|
||||
this.statusContainer.addChild(this.retryLoader);
|
||||
this.ui.requestRender();
|
||||
@@ -1536,7 +1538,7 @@ export class InteractiveMode {
|
||||
message.exitCode,
|
||||
message.cancelled,
|
||||
message.truncated ? ({ truncated: true } as TruncationResult) : undefined,
|
||||
message.fullOutputPath
|
||||
message.fullOutputPath,
|
||||
);
|
||||
this.chatContainer.addChild(component);
|
||||
break;
|
||||
@@ -1569,7 +1571,7 @@ export class InteractiveMode {
|
||||
for (const file of message.files) {
|
||||
const text = `${theme.fg("dim", `${theme.tree.last} `)}${theme.fg("muted", "Read")} ${theme.fg(
|
||||
"accent",
|
||||
file.path
|
||||
file.path,
|
||||
)} ${theme.fg("dim", `(${file.lineCount} lines)`)}`;
|
||||
this.chatContainer.addChild(new Text(text, 0, 0));
|
||||
}
|
||||
@@ -1609,7 +1611,7 @@ export class InteractiveMode {
|
||||
*/
|
||||
private renderSessionContext(
|
||||
sessionContext: SessionContext,
|
||||
options: { updateFooter?: boolean; populateHistory?: boolean } = {}
|
||||
options: { updateFooter?: boolean; populateHistory?: boolean } = {},
|
||||
): void {
|
||||
this.pendingTools.clear();
|
||||
|
||||
@@ -1632,7 +1634,7 @@ export class InteractiveMode {
|
||||
{ showImages: this.settingsManager.getShowImages() },
|
||||
tool,
|
||||
this.ui,
|
||||
this.sessionManager.getCwd()
|
||||
this.sessionManager.getCwd(),
|
||||
);
|
||||
component.setExpanded(this.toolOutputExpanded);
|
||||
this.chatContainer.addChild(component);
|
||||
@@ -2115,8 +2117,8 @@ export class InteractiveMode {
|
||||
theme.fg("muted", `New version ${newVersion} is available. Run: `) +
|
||||
theme.fg("accent", "omp update"),
|
||||
1,
|
||||
0
|
||||
)
|
||||
0,
|
||||
),
|
||||
);
|
||||
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
||||
this.ui.requestRender();
|
||||
@@ -2208,7 +2210,7 @@ export class InteractiveMode {
|
||||
this.updateEditorTopBorder();
|
||||
this.ui.requestRender();
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
return { component: selector, focus: selector };
|
||||
});
|
||||
@@ -2383,7 +2385,7 @@ export class InteractiveMode {
|
||||
done();
|
||||
this.ui.requestRender();
|
||||
},
|
||||
options
|
||||
options,
|
||||
);
|
||||
return { component: selector, focus: selector };
|
||||
});
|
||||
@@ -2418,7 +2420,7 @@ export class InteractiveMode {
|
||||
() => {
|
||||
done();
|
||||
this.ui.requestRender();
|
||||
}
|
||||
},
|
||||
);
|
||||
return { component: selector, focus: selector.getMessageList() };
|
||||
});
|
||||
@@ -2477,7 +2479,7 @@ export class InteractiveMode {
|
||||
(spinner) => theme.fg("accent", spinner),
|
||||
(text) => theme.fg("muted", text),
|
||||
"Summarizing branch... (esc to cancel)",
|
||||
getSymbolTheme().spinnerFrames
|
||||
getSymbolTheme().spinnerFrames,
|
||||
);
|
||||
this.statusContainer.addChild(summaryLoader);
|
||||
this.ui.requestRender();
|
||||
@@ -2521,7 +2523,7 @@ export class InteractiveMode {
|
||||
(entryId, label) => {
|
||||
this.sessionManager.appendLabelChange(entryId, label);
|
||||
this.ui.requestRender();
|
||||
}
|
||||
},
|
||||
);
|
||||
return { component: selector, focus: selector };
|
||||
});
|
||||
@@ -2542,7 +2544,7 @@ export class InteractiveMode {
|
||||
},
|
||||
() => {
|
||||
void this.shutdown();
|
||||
}
|
||||
},
|
||||
);
|
||||
return { component: selector, focus: selector.getSessionList() };
|
||||
});
|
||||
@@ -2575,7 +2577,7 @@ export class InteractiveMode {
|
||||
if (mode === "logout") {
|
||||
const providers = this.session.modelRegistry.authStorage.list();
|
||||
const loggedInProviders = providers.filter(
|
||||
(p) => this.session.modelRegistry.authStorage.get(p)?.type === "oauth"
|
||||
(p) => this.session.modelRegistry.authStorage.get(p)?.type === "oauth",
|
||||
);
|
||||
if (loggedInProviders.length === 0) {
|
||||
this.showStatus("No OAuth providers logged in. Use /login first.");
|
||||
@@ -2640,9 +2642,15 @@ export class InteractiveMode {
|
||||
await this.session.modelRegistry.refresh();
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
this.chatContainer.addChild(
|
||||
new Text(theme.fg("success", `${theme.status.success} Successfully logged in to ${providerId}`), 1, 0)
|
||||
new Text(
|
||||
theme.fg("success", `${theme.status.success} Successfully logged in to ${providerId}`),
|
||||
1,
|
||||
0,
|
||||
),
|
||||
);
|
||||
this.chatContainer.addChild(
|
||||
new Text(theme.fg("dim", `Credentials saved to ${getAuthPath()}`), 1, 0),
|
||||
);
|
||||
this.chatContainer.addChild(new Text(theme.fg("dim", `Credentials saved to ${getAuthPath()}`), 1, 0));
|
||||
this.ui.requestRender();
|
||||
} catch (error: unknown) {
|
||||
this.showError(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
@@ -2654,9 +2662,15 @@ export class InteractiveMode {
|
||||
await this.session.modelRegistry.refresh();
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
this.chatContainer.addChild(
|
||||
new Text(theme.fg("success", `${theme.status.success} Successfully logged out of ${providerId}`), 1, 0)
|
||||
new Text(
|
||||
theme.fg("success", `${theme.status.success} Successfully logged out of ${providerId}`),
|
||||
1,
|
||||
0,
|
||||
),
|
||||
);
|
||||
this.chatContainer.addChild(
|
||||
new Text(theme.fg("dim", `Credentials removed from ${getAuthPath()}`), 1, 0),
|
||||
);
|
||||
this.chatContainer.addChild(new Text(theme.fg("dim", `Credentials removed from ${getAuthPath()}`), 1, 0));
|
||||
this.ui.requestRender();
|
||||
} catch (error: unknown) {
|
||||
this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
@@ -2666,7 +2680,7 @@ export class InteractiveMode {
|
||||
() => {
|
||||
done();
|
||||
this.ui.requestRender();
|
||||
}
|
||||
},
|
||||
);
|
||||
return { component: selector, focus: selector };
|
||||
});
|
||||
@@ -2682,8 +2696,8 @@ export class InteractiveMode {
|
||||
process.platform === "darwin"
|
||||
? ["open", urlOrPath]
|
||||
: process.platform === "win32"
|
||||
? ["cmd", "/c", "start", "", urlOrPath]
|
||||
: ["xdg-open", urlOrPath];
|
||||
? ["cmd", "/c", "start", "", urlOrPath]
|
||||
: ["xdg-open", urlOrPath];
|
||||
Bun.spawn(args, { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
|
||||
} catch {
|
||||
// Best-effort: browser opening is non-critical
|
||||
@@ -3001,7 +3015,9 @@ export class InteractiveMode {
|
||||
this.pendingTools.clear();
|
||||
|
||||
this.chatContainer.addChild(new Spacer(1));
|
||||
this.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1));
|
||||
this.chatContainer.addChild(
|
||||
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
||||
);
|
||||
this.ui.requestRender();
|
||||
}
|
||||
|
||||
@@ -3035,8 +3051,8 @@ export class InteractiveMode {
|
||||
new Text(
|
||||
`${theme.fg("accent", `${theme.status.success} Debug log written`)}\n${theme.fg("muted", debugLogPath)}`,
|
||||
1,
|
||||
1
|
||||
)
|
||||
1,
|
||||
),
|
||||
);
|
||||
this.ui.requestRender();
|
||||
}
|
||||
@@ -3070,7 +3086,7 @@ export class InteractiveMode {
|
||||
this.ui.requestRender();
|
||||
}
|
||||
},
|
||||
{ excludeFromContext }
|
||||
{ excludeFromContext },
|
||||
);
|
||||
|
||||
if (this.bashComponent) {
|
||||
@@ -3078,7 +3094,7 @@ export class InteractiveMode {
|
||||
result.exitCode,
|
||||
result.cancelled,
|
||||
result.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,
|
||||
result.fullOutputPath
|
||||
result.fullOutputPath,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -3126,7 +3142,7 @@ export class InteractiveMode {
|
||||
(spinner) => theme.fg("accent", spinner),
|
||||
(text) => theme.fg("muted", text),
|
||||
label,
|
||||
getSymbolTheme().spinnerFrames
|
||||
getSymbolTheme().spinnerFrames,
|
||||
);
|
||||
this.statusContainer.addChild(compactingLoader);
|
||||
this.ui.requestRender();
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function runPrintMode(
|
||||
mode: "text" | "json",
|
||||
messages: string[],
|
||||
initialMessage?: string,
|
||||
initialImages?: ImageContent[]
|
||||
initialImages?: ImageContent[],
|
||||
): Promise<void> {
|
||||
// Extension runner already has no-op UI context by default (set in loader)
|
||||
// Set up extensions for print mode (no UI)
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* Spawns the agent in RPC mode and provides a typed API for all operations.
|
||||
*/
|
||||
|
||||
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
||||
import type { Subprocess } from "bun";
|
||||
import type { SessionStats } from "../../core/agent-session";
|
||||
import type { BashResult } from "../../core/bash-executor";
|
||||
@@ -114,7 +114,7 @@ export class RpcClient {
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}),
|
||||
)
|
||||
.getReader() as ReadableStreamDefaultReader<string>;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* Responses and events are emitted as JSON lines on stdout.
|
||||
*/
|
||||
|
||||
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
||||
import type { SessionStats } from "../../core/agent-session";
|
||||
import type { BashResult } from "../../core/bash-executor";
|
||||
import type { CompactionResult } from "../../core/compaction/index";
|
||||
|
||||
@@ -33,7 +33,7 @@ const DEFAULT_OPTIONS: Required<ImageResizeOptions> = {
|
||||
*/
|
||||
async function resizeImageWithImageMagick(
|
||||
img: ImageContent,
|
||||
opts: Required<ImageResizeOptions>
|
||||
opts: Required<ImageResizeOptions>,
|
||||
): Promise<ResizedImage> {
|
||||
// Try to get dimensions first
|
||||
const dims = await getImageDimensionsWithImageMagick(img.data);
|
||||
@@ -47,7 +47,7 @@ async function resizeImageWithImageMagick(
|
||||
opts.maxWidth,
|
||||
opts.maxHeight,
|
||||
opts.maxBytes,
|
||||
opts.jpegQuality
|
||||
opts.jpegQuality,
|
||||
);
|
||||
|
||||
if (result) {
|
||||
@@ -77,7 +77,7 @@ async function resizeImageWithImageMagick(
|
||||
/** Helper to pick the smaller of two buffers */
|
||||
function pickSmaller(
|
||||
a: { buffer: Buffer; mimeType: string },
|
||||
b: { buffer: Buffer; mimeType: string }
|
||||
b: { buffer: Buffer; mimeType: string },
|
||||
): { buffer: Buffer; mimeType: string } {
|
||||
return a.buffer.length <= b.buffer.length ? a : b;
|
||||
}
|
||||
@@ -145,9 +145,11 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
||||
async function tryBothFormats(
|
||||
width: number,
|
||||
height: number,
|
||||
jpegQuality: number
|
||||
jpegQuality: number,
|
||||
): Promise<{ buffer: Buffer; mimeType: string }> {
|
||||
const resized = await sharp!(buffer).resize(width, height, { fit: "inside", withoutEnlargement: true }).toBuffer();
|
||||
const resized = await sharp!(buffer)
|
||||
.resize(width, height, { fit: "inside", withoutEnlargement: true })
|
||||
.toBuffer();
|
||||
|
||||
const [pngBuffer, jpegBuffer] = await Promise.all([
|
||||
sharp!(resized).png({ compressionLevel: 9 }).toBuffer(),
|
||||
|
||||
@@ -11,8 +11,8 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { Agent } from "@oh-my-pi/pi-agent-core";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AgentSession } from "../src/core/agent-session";
|
||||
import { AuthStorage } from "../src/core/auth-storage";
|
||||
|
||||
@@ -11,8 +11,8 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { Agent } from "@oh-my-pi/pi-agent-core";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AgentSession, type AgentSessionEvent } from "../src/core/agent-session";
|
||||
import { AuthStorage } from "../src/core/auth-storage";
|
||||
@@ -201,7 +201,7 @@ describe.skipIf(!API_KEY)("AgentSession compaction e2e", () => {
|
||||
|
||||
// Check that no auto_compaction events were emitted for manual compaction
|
||||
const autoCompactionEvents = events.filter(
|
||||
(e) => e.type === "auto_compaction_start" || e.type === "auto_compaction_end"
|
||||
(e) => e.type === "auto_compaction_start" || e.type === "auto_compaction_end",
|
||||
);
|
||||
// Manual compaction doesn't emit auto_compaction events
|
||||
expect(autoCompactionEvents.length).toBe(0);
|
||||
|
||||
@@ -6,8 +6,8 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@oh-my-pi/pi-ai";
|
||||
import { Agent } from "@oh-my-pi/pi-agent-core";
|
||||
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@oh-my-pi/pi-ai";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AgentSession } from "../src/core/agent-session";
|
||||
import { AuthStorage } from "../src/core/auth-storage";
|
||||
@@ -24,7 +24,7 @@ class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMe
|
||||
if (event.type === "done") return event.message;
|
||||
if (event.type === "error") return event.error;
|
||||
throw new Error("Unexpected event type");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ describe("AgentSession concurrent prompt guard", () => {
|
||||
|
||||
// Second prompt should reject
|
||||
await expect(session.prompt("Second message")).rejects.toThrow(
|
||||
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message."
|
||||
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
|
||||
@@ -6,8 +6,8 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { Agent } from "@oh-my-pi/pi-agent-core";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AgentSession } from "../src/core/agent-session";
|
||||
import { AuthStorage } from "../src/core/auth-storage";
|
||||
@@ -49,7 +49,7 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
||||
|
||||
function createHook(
|
||||
onBeforeCompact?: (event: SessionBeforeCompactEvent) => { cancel?: boolean; compaction?: any } | undefined,
|
||||
onCompact?: (event: SessionCompactEvent) => void
|
||||
onCompact?: (event: SessionCompactEvent) => void,
|
||||
): LoadedHook {
|
||||
const handlers = new Map<string, ((event: any, ctx: any) => Promise<any>)[]>();
|
||||
|
||||
@@ -154,7 +154,7 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => {
|
||||
await session.compact();
|
||||
|
||||
const beforeCompactEvents = capturedEvents.filter(
|
||||
(e): e is SessionBeforeCompactEvent => e.type === "session_before_compact"
|
||||
(e): e is SessionBeforeCompactEvent => e.type === "session_before_compact",
|
||||
);
|
||||
const compactEvents = capturedEvents.filter((e): e is SessionCompactEvent => e.type === "session_compact");
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getModel, type Model } from "@oh-my-pi/pi-ai";
|
||||
import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { getModel, type Model } from "@oh-my-pi/pi-ai";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AgentSession } from "../src/core/agent-session";
|
||||
import { AuthStorage } from "../src/core/auth-storage";
|
||||
@@ -47,7 +47,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra
|
||||
|
||||
async function createSession(
|
||||
modelId: "claude-opus-4-5-thinking" | "claude-sonnet-4-5",
|
||||
thinkingLevel: ThinkingLevel = "high"
|
||||
thinkingLevel: ThinkingLevel = "high",
|
||||
) {
|
||||
const toolSession: ToolSession = {
|
||||
cwd: tempDir,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { beforeEach, describe, expect, it } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { AssistantMessage, Usage } from "@oh-my-pi/pi-ai";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import {
|
||||
type CompactionSettings,
|
||||
calculateContextTokens,
|
||||
@@ -216,7 +216,7 @@ describe("findCutPoint", () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
entries.push(createMessageEntry(createUserMessage(`User ${i}`)));
|
||||
entries.push(
|
||||
createMessageEntry(createAssistantMessage(`Assistant ${i}`, createMockUsage(0, 100, (i + 1) * 1000, 0)))
|
||||
createMessageEntry(createAssistantMessage(`Assistant ${i}`, createMockUsage(0, 100, (i + 1) * 1000, 0))),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ initTheme("dark");
|
||||
|
||||
// Load the real fixture that caused the bug
|
||||
const fixtureMessage: AssistantMessage = JSON.parse(
|
||||
readFileSync(join(import.meta.dir, "fixtures/assistant-message-with-thinking-code.json"), "utf-8")
|
||||
readFileSync(join(import.meta.dir, "fixtures/assistant-message-with-thinking-code.json"), "utf-8"),
|
||||
);
|
||||
|
||||
// Extract thinking and text content
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { Agent } from "@oh-my-pi/pi-agent-core";
|
||||
import { getModel } from "@oh-my-pi/pi-ai";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AgentSession } from "../src/core/agent-session";
|
||||
import { AuthStorage } from "../src/core/auth-storage";
|
||||
@@ -145,7 +145,7 @@ export function buildTestTree(
|
||||
session: SessionManager,
|
||||
structure: {
|
||||
messages: Array<{ role: "user" | "assistant"; text: string; branchFrom?: string }>;
|
||||
}
|
||||
},
|
||||
): Map<string, string> {
|
||||
const ids = new Map<string, string>();
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.34.0] - 2026-01-09
|
||||
|
||||
## [3.33.0] - 2026-01-08
|
||||
|
||||
## [3.32.0] - 2026-01-08
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-git-tool",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"description": "Structured Git tool with safety guards and typed output",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.34.0] - 2026-01-09
|
||||
|
||||
## [3.33.0] - 2026-01-08
|
||||
|
||||
## [3.32.0] - 2026-01-08
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-tui",
|
||||
"version": "3.33.0",
|
||||
"version": "3.34.0",
|
||||
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
Reference in New Issue
Block a user