From b635e8365135b649433b139d22d2de023e06ce53 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 20 Dec 2025 22:38:12 +0000 Subject: [PATCH] chore(pi): bump deps, drop steerable transport --- package.json | 6 +- pnpm-lock.yaml | 46 +- src/agents/pi-embedded.ts | 5 +- src/agents/steerable-agent-loop.ts | 473 --------------------- src/agents/steerable-provider-transport.ts | 85 ---- src/gateway/server.ts | 3 +- 6 files changed, 30 insertions(+), 588 deletions(-) delete mode 100644 src/agents/steerable-agent-loop.ts delete mode 100644 src/agents/steerable-provider-transport.ts diff --git a/package.json b/package.json index c7b802c4b..b9538c9a3 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "dependencies": { "@grammyjs/transformer-throttler": "^1.2.1", "@homebridge/ciao": "^1.3.4", - "@mariozechner/pi-agent-core": "^0.24.5", - "@mariozechner/pi-ai": "^0.24.5", - "@mariozechner/pi-coding-agent": "^0.24.5", + "@mariozechner/pi-agent-core": "^0.25.0", + "@mariozechner/pi-ai": "^0.25.0", + "@mariozechner/pi-coding-agent": "^0.25.0", "@sinclair/typebox": "^0.34.41", "@whiskeysockets/baileys": "7.0.0-rc.9", "ajv": "^8.17.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7144941c..78f742f18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,14 +15,14 @@ importers: specifier: ^1.3.4 version: 1.3.4 '@mariozechner/pi-agent-core': - specifier: ^0.24.5 - version: 0.24.5(ws@8.18.3)(zod@4.1.13) + specifier: ^0.25.0 + version: 0.25.0(ws@8.18.3)(zod@4.1.13) '@mariozechner/pi-ai': - specifier: ^0.24.5 - version: 0.24.5(ws@8.18.3)(zod@4.1.13) + specifier: ^0.25.0 + version: 0.25.0(ws@8.18.3)(zod@4.1.13) '@mariozechner/pi-coding-agent': - specifier: ^0.24.5 - version: 0.24.5(ws@8.18.3)(zod@4.1.13) + specifier: ^0.25.0 + version: 0.25.0(ws@8.18.3)(zod@4.1.13) '@sinclair/typebox': specifier: ^0.34.41 version: 0.34.41 @@ -811,21 +811,21 @@ packages: peerDependencies: lit: ^3.3.1 - '@mariozechner/pi-agent-core@0.24.5': - resolution: {integrity: sha512-36rj74NcZItzgsWCEpAlrcDxrTNsi5NlYgkU2/tnkBABItvFCHCSJyDUFns5c4Swv/iPkkeqKWPrpTReO1ta9A==} + '@mariozechner/pi-agent-core@0.25.0': + resolution: {integrity: sha512-aiM0GvkmHJtFudNGlXiuLr/IqRot1Sus9vqrarVf/gF5ooubYyGYhP6QotAfbFqI0z6HpFa2O3mx8KEp0AiBKg==} engines: {node: '>=20.0.0'} - '@mariozechner/pi-ai@0.24.5': - resolution: {integrity: sha512-7DKydy/xgOwDr3uZgFl41jAc3CNLsZebW0Z19bNQ9iI0Y1f5UmYTTELuirwaAa0vlKeS0+HoHU2t0e5TFo/I6g==} + '@mariozechner/pi-ai@0.25.0': + resolution: {integrity: sha512-N3INs/PNIEYx/U8tM6NaV75Gpx263o4b+YYxsD1Ag9ratdzz+JxL2ATYENi+Ma+BjsMaowPCMO2oeotHdsr/cA==} engines: {node: '>=20.0.0'} - '@mariozechner/pi-coding-agent@0.24.5': - resolution: {integrity: sha512-e0M4zoNWsXL2FinABlUEBKgVOWwShTjN3sOBqcvjssKndtpouHqAZDLSyvXbcFycQjImsh8Iq7/l1x2UjmylwA==} + '@mariozechner/pi-coding-agent@0.25.0': + resolution: {integrity: sha512-docYKq6zEVZcO5ngb0NTpayeipr+pLCMCeNfwdiC55zNI5nKMg1O4s6aMv2clJ4fUisHP0uhyK9URIohqSadbw==} engines: {node: '>=20.0.0'} hasBin: true - '@mariozechner/pi-tui@0.24.5': - resolution: {integrity: sha512-ajh81L/qLk4HczTrxPwNXluFa2tRGhAESiczroH0P5qYOG578KNFfVw6UxVm1QhQVjynWXjE0fJacAFeHu/leQ==} + '@mariozechner/pi-tui@0.25.0': + resolution: {integrity: sha512-7pU/EPFTYgyEsfcDBb+fzp6BQWr6tmykgMMGZx3Pxvet3NF5HmphAdLBitjmThri+M7lrGaJVrpIRHjQM1CPVQ==} engines: {node: '>=20.0.0'} '@mistralai/mistralai@1.10.0': @@ -3142,10 +3142,10 @@ snapshots: transitivePeerDependencies: - tailwindcss - '@mariozechner/pi-agent-core@0.24.5(ws@8.18.3)(zod@4.1.13)': + '@mariozechner/pi-agent-core@0.25.0(ws@8.18.3)(zod@4.1.13)': dependencies: - '@mariozechner/pi-ai': 0.24.5(ws@8.18.3)(zod@4.1.13) - '@mariozechner/pi-tui': 0.24.5 + '@mariozechner/pi-ai': 0.25.0(ws@8.18.3)(zod@4.1.13) + '@mariozechner/pi-tui': 0.25.0 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - bufferutil @@ -3154,7 +3154,7 @@ snapshots: - ws - zod - '@mariozechner/pi-ai@0.24.5(ws@8.18.3)(zod@4.1.13)': + '@mariozechner/pi-ai@0.25.0(ws@8.18.3)(zod@4.1.13)': dependencies: '@anthropic-ai/sdk': 0.71.2(zod@4.1.13) '@google/genai': 1.34.0 @@ -3174,11 +3174,11 @@ snapshots: - ws - zod - '@mariozechner/pi-coding-agent@0.24.5(ws@8.18.3)(zod@4.1.13)': + '@mariozechner/pi-coding-agent@0.25.0(ws@8.18.3)(zod@4.1.13)': dependencies: - '@mariozechner/pi-agent-core': 0.24.5(ws@8.18.3)(zod@4.1.13) - '@mariozechner/pi-ai': 0.24.5(ws@8.18.3)(zod@4.1.13) - '@mariozechner/pi-tui': 0.24.5 + '@mariozechner/pi-agent-core': 0.25.0(ws@8.18.3)(zod@4.1.13) + '@mariozechner/pi-ai': 0.25.0(ws@8.18.3)(zod@4.1.13) + '@mariozechner/pi-tui': 0.25.0 chalk: 5.6.2 cli-highlight: 2.1.11 diff: 8.0.2 @@ -3193,7 +3193,7 @@ snapshots: - ws - zod - '@mariozechner/pi-tui@0.24.5': + '@mariozechner/pi-tui@0.25.0': dependencies: '@types/mime-types': 2.1.4 chalk: 5.6.2 diff --git a/src/agents/pi-embedded.ts b/src/agents/pi-embedded.ts index 06b2ec59f..39e91ddcd 100644 --- a/src/agents/pi-embedded.ts +++ b/src/agents/pi-embedded.ts @@ -5,6 +5,7 @@ import { Agent, type AgentEvent, type AppMessage, + ProviderTransport, type ThinkingLevel, } from "@mariozechner/pi-agent-core"; import { @@ -50,7 +51,6 @@ import { loadWorkspaceSkillEntries, type SkillSnapshot, } from "./skills.js"; -import { SteerableProviderTransport } from "./steerable-provider-transport.js"; import { buildAgentSystemPrompt } from "./system-prompt.js"; import { loadWorkspaceBootstrapFiles } from "./workspace.js"; @@ -317,7 +317,6 @@ export async function runEmbeddedPiAgent(params: { const sessionManager = new SessionManager(false, params.sessionFile); const settingsManager = new SettingsManager(); - // TODO(steipete): Drop the steerable transport after pi-mono PR #259 lands and deps are bumped. const agent = new Agent({ initialState: { systemPrompt: systemPromptWithSkills, @@ -329,7 +328,7 @@ export async function runEmbeddedPiAgent(params: { }, messageTransformer, queueMode: settingsManager.getQueueMode(), - transport: new SteerableProviderTransport({ + transport: new ProviderTransport({ getApiKey: async (providerName) => { const key = await getApiKeyForProvider(providerName); if (!key) { diff --git a/src/agents/steerable-agent-loop.ts b/src/agents/steerable-agent-loop.ts deleted file mode 100644 index 69b2170b6..000000000 --- a/src/agents/steerable-agent-loop.ts +++ /dev/null @@ -1,473 +0,0 @@ -import type { - AgentContext, - AgentEvent, - AgentLoopConfig, - AgentTool, - AgentToolResult, - AssistantMessage, - Context, - Message, - QueuedMessage, - ToolResultMessage, - UserMessage, -} from "@mariozechner/pi-ai"; -import { streamSimple, validateToolArguments } from "@mariozechner/pi-ai"; -import type { TSchema } from "@sinclair/typebox"; - -class EventStream implements AsyncIterable { - private queue: T[] = []; - private waiting: ((value: IteratorResult) => void)[] = []; - private done = false; - private finalResultPromise: Promise; - private resolveFinalResult!: (result: R) => void; - - constructor( - private isComplete: (event: T) => boolean, - private extractResult: (event: T) => R, - ) { - this.finalResultPromise = new Promise((resolve) => { - this.resolveFinalResult = resolve; - }); - } - - push(event: T): void { - if (this.done) return; - - if (this.isComplete(event)) { - this.done = true; - this.resolveFinalResult(this.extractResult(event)); - } - - const waiter = this.waiting.shift(); - if (waiter) { - waiter({ value: event, done: false }); - } else { - this.queue.push(event); - } - } - - end(result?: R): void { - this.done = true; - if (result !== undefined) { - this.resolveFinalResult(result); - } - while (this.waiting.length > 0) { - const waiter = this.waiting.shift(); - if (waiter) { - waiter({ value: undefined as never, done: true }); - } - } - } - - async *[Symbol.asyncIterator](): AsyncIterator { - while (true) { - if (this.queue.length > 0) { - const next = this.queue.shift(); - if (next !== undefined) { - yield next; - } - } else if (this.done) { - return; - } else { - const result = await new Promise>((resolve) => - this.waiting.push(resolve), - ); - if (result.done) return; - yield result.value; - } - } - } - - result(): Promise { - return this.finalResultPromise; - } -} - -function createAgentStream(): EventStream< - AgentEvent, - AgentContext["messages"] -> { - return new EventStream( - (event) => event.type === "agent_end", - (event) => (event.type === "agent_end" ? event.messages : []), - ); -} - -export function agentLoop( - prompt: UserMessage, - context: AgentContext, - config: AgentLoopConfig, - signal?: AbortSignal, - streamFn?: typeof streamSimple, -): EventStream { - const stream = createAgentStream(); - - void (async () => { - const newMessages: AgentContext["messages"] = [prompt]; - const currentContext: AgentContext = { - ...context, - messages: [...context.messages, prompt], - }; - - stream.push({ type: "agent_start" }); - stream.push({ type: "turn_start" }); - stream.push({ type: "message_start", message: prompt }); - stream.push({ type: "message_end", message: prompt }); - - await runLoop( - currentContext, - newMessages, - config, - signal, - stream, - streamFn, - ); - })(); - - return stream; -} - -export function agentLoopContinue( - context: AgentContext, - config: AgentLoopConfig, - signal?: AbortSignal, - streamFn?: typeof streamSimple, -): EventStream { - const lastMessage = context.messages[context.messages.length - 1]; - if (!lastMessage) { - throw new Error("Cannot continue: no messages in context"); - } - if (lastMessage.role !== "user" && lastMessage.role !== "toolResult") { - throw new Error( - `Cannot continue from message role: ${lastMessage.role}. Expected 'user' or 'toolResult'.`, - ); - } - - const stream = createAgentStream(); - - void (async () => { - const newMessages: AgentContext["messages"] = []; - const currentContext: AgentContext = { ...context }; - - stream.push({ type: "agent_start" }); - stream.push({ type: "turn_start" }); - - await runLoop( - currentContext, - newMessages, - config, - signal, - stream, - streamFn, - ); - })(); - - return stream; -} - -async function runLoop( - currentContext: AgentContext, - newMessages: AgentContext["messages"], - config: AgentLoopConfig, - signal: AbortSignal | undefined, - stream: EventStream, - streamFn?: typeof streamSimple, -): Promise { - let hasMoreToolCalls = true; - let firstTurn = true; - const getQueuedMessages = config.getQueuedMessages; - let queuedMessages: QueuedMessage[] = getQueuedMessages - ? await getQueuedMessages() - : []; - let queuedAfterTools: QueuedMessage[] | null = null; - - while (hasMoreToolCalls || queuedMessages.length > 0) { - if (!firstTurn) { - stream.push({ type: "turn_start" }); - } else { - firstTurn = false; - } - - if (queuedMessages.length > 0) { - for (const { original, llm } of queuedMessages) { - stream.push({ type: "message_start", message: original }); - stream.push({ type: "message_end", message: original }); - if (llm) { - currentContext.messages.push(llm); - newMessages.push(llm); - } - } - queuedMessages = []; - } - - const message = await streamAssistantResponse( - currentContext, - config, - signal, - stream, - streamFn, - ); - newMessages.push(message); - - if (message.stopReason === "error" || message.stopReason === "aborted") { - stream.push({ type: "turn_end", message, toolResults: [] }); - stream.push({ type: "agent_end", messages: newMessages }); - stream.end(newMessages); - return; - } - - const toolCalls = message.content.filter((c) => c.type === "toolCall"); - hasMoreToolCalls = toolCalls.length > 0; - - const toolResults: ToolResultMessage[] = []; - if (hasMoreToolCalls) { - const toolExecution = await executeToolCalls( - currentContext.tools, - message, - signal, - stream, - config.getQueuedMessages, - ); - toolResults.push(...toolExecution.toolResults); - queuedAfterTools = toolExecution.queuedMessages ?? null; - currentContext.messages.push(...toolResults); - newMessages.push(...toolResults); - } - stream.push({ type: "turn_end", message, toolResults: toolResults }); - - if (queuedAfterTools && queuedAfterTools.length > 0) { - queuedMessages = queuedAfterTools; - queuedAfterTools = null; - } else { - queuedMessages = getQueuedMessages - ? await getQueuedMessages() - : []; - } - } - - stream.push({ type: "agent_end", messages: newMessages }); - stream.end(newMessages); -} - -async function streamAssistantResponse( - context: AgentContext, - config: AgentLoopConfig, - signal: AbortSignal | undefined, - stream: EventStream, - streamFn?: typeof streamSimple, -): Promise { - const processedMessages = config.preprocessor - ? await config.preprocessor(context.messages, signal) - : [...context.messages]; - const processedContext: Context = { - systemPrompt: context.systemPrompt, - messages: [...processedMessages].map((m) => { - if (m.role === "toolResult") { - const { details: _details, ...rest } = m; - return rest; - } - return m; - }), - tools: context.tools, - }; - - const streamFunction = streamFn || streamSimple; - const resolvedApiKey = - (config.getApiKey - ? await config.getApiKey(config.model.provider) - : undefined) || config.apiKey; - - const response = await streamFunction(config.model, processedContext, { - ...config, - apiKey: resolvedApiKey, - signal, - }); - - let partialMessage: AssistantMessage | null = null; - let addedPartial = false; - - for await (const event of response) { - switch (event.type) { - case "start": - partialMessage = event.partial; - context.messages.push(partialMessage); - addedPartial = true; - stream.push({ type: "message_start", message: { ...partialMessage } }); - break; - - case "text_start": - case "text_delta": - case "text_end": - case "thinking_start": - case "thinking_delta": - case "thinking_end": - case "toolcall_start": - case "toolcall_delta": - case "toolcall_end": - if (partialMessage) { - partialMessage = event.partial; - context.messages[context.messages.length - 1] = partialMessage; - stream.push({ - type: "message_update", - assistantMessageEvent: event, - message: { ...partialMessage }, - }); - } - break; - - case "done": - case "error": { - const finalMessage = await response.result(); - if (addedPartial) { - context.messages[context.messages.length - 1] = finalMessage; - } else { - context.messages.push(finalMessage); - } - if (!addedPartial) { - stream.push({ type: "message_start", message: { ...finalMessage } }); - } - stream.push({ type: "message_end", message: finalMessage }); - return finalMessage; - } - } - } - - return await response.result(); -} - -async function executeToolCalls( - tools: AgentTool[] | undefined, - assistantMessage: AssistantMessage, - signal: AbortSignal | undefined, - stream: EventStream, - getQueuedMessages?: AgentLoopConfig["getQueuedMessages"], -): Promise<{ - toolResults: ToolResultMessage[]; - queuedMessages?: QueuedMessage[]; -}> { - const toolCalls = assistantMessage.content.filter( - (c) => c.type === "toolCall", - ); - const results: ToolResultMessage[] = []; - let queuedMessages: QueuedMessage[] | undefined; - - for (let index = 0; index < toolCalls.length; index++) { - const toolCall = toolCalls[index]; - const tool = tools?.find((t) => t.name === toolCall.name); - - stream.push({ - type: "tool_execution_start", - toolCallId: toolCall.id, - toolName: toolCall.name, - args: toolCall.arguments, - }); - - let result: AgentToolResult; - let isError = false; - - try { - if (!tool) throw new Error(`Tool ${toolCall.name} not found`); - const validatedArgs = validateToolArguments(tool, toolCall); - result = await tool.execute( - toolCall.id, - validatedArgs, - signal, - (partialResult) => { - stream.push({ - type: "tool_execution_update", - toolCallId: toolCall.id, - toolName: toolCall.name, - args: toolCall.arguments, - partialResult, - }); - }, - ); - } catch (err) { - result = { - content: [ - { - type: "text", - text: err instanceof Error ? err.message : String(err), - }, - ], - details: {} as T, - }; - isError = true; - } - - stream.push({ - type: "tool_execution_end", - toolCallId: toolCall.id, - toolName: toolCall.name, - result, - isError, - }); - - const toolResultMessage: ToolResultMessage = { - role: "toolResult", - toolCallId: toolCall.id, - toolName: toolCall.name, - content: result.content, - details: result.details, - isError, - timestamp: Date.now(), - }; - - results.push(toolResultMessage); - stream.push({ type: "message_start", message: toolResultMessage }); - stream.push({ type: "message_end", message: toolResultMessage }); - - if (getQueuedMessages) { - const queued = await getQueuedMessages(); - if (queued.length > 0) { - queuedMessages = queued; - const remainingCalls = toolCalls.slice(index + 1); - for (const skipped of remainingCalls) { - results.push(skipToolCall(skipped, stream)); - } - break; - } - } - } - - return { toolResults: results, queuedMessages }; -} - -function skipToolCall( - toolCall: Extract, - stream: EventStream, -): ToolResultMessage { - const result: AgentToolResult = { - content: [{ type: "text", text: "Skipped due to queued user message." }], - details: {} as T, - }; - - stream.push({ - type: "tool_execution_start", - toolCallId: toolCall.id, - toolName: toolCall.name, - args: toolCall.arguments, - }); - stream.push({ - type: "tool_execution_end", - toolCallId: toolCall.id, - toolName: toolCall.name, - result, - isError: true, - }); - - const toolResultMessage: ToolResultMessage = { - role: "toolResult", - toolCallId: toolCall.id, - toolName: toolCall.name, - content: result.content, - details: result.details, - isError: true, - timestamp: Date.now(), - }; - - stream.push({ type: "message_start", message: toolResultMessage }); - stream.push({ type: "message_end", message: toolResultMessage }); - - return toolResultMessage; -} diff --git a/src/agents/steerable-provider-transport.ts b/src/agents/steerable-provider-transport.ts deleted file mode 100644 index 9fabfa7e8..000000000 --- a/src/agents/steerable-provider-transport.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { - AgentRunConfig, - AgentTransport, - ProviderTransportOptions, -} from "@mariozechner/pi-agent-core"; -import type { - AgentContext, - AgentLoopConfig, - Message, - UserMessage, -} from "@mariozechner/pi-ai"; -import { agentLoop, agentLoopContinue } from "./steerable-agent-loop.js"; - -export class SteerableProviderTransport implements AgentTransport { - private options: ProviderTransportOptions; - - constructor(options: ProviderTransportOptions = {}) { - this.options = options; - } - - private getModel(cfg: AgentRunConfig) { - let model = cfg.model; - if (this.options.corsProxyUrl && cfg.model.baseUrl) { - model = { - ...cfg.model, - baseUrl: `${this.options.corsProxyUrl}/?url=${encodeURIComponent(cfg.model.baseUrl)}`, - }; - } - return model; - } - - private buildContext(messages: Message[], cfg: AgentRunConfig): AgentContext { - return { - systemPrompt: cfg.systemPrompt, - messages, - tools: cfg.tools, - }; - } - - private buildLoopConfig( - model: AgentRunConfig["model"], - cfg: AgentRunConfig, - ): AgentLoopConfig { - return { - model, - reasoning: cfg.reasoning, - getApiKey: this.options.getApiKey, - getQueuedMessages: cfg.getQueuedMessages, - }; - } - - async *run( - messages: Message[], - userMessage: Message, - cfg: AgentRunConfig, - signal?: AbortSignal, - ) { - const model = this.getModel(cfg); - const context = this.buildContext(messages, cfg); - const pc = this.buildLoopConfig(model, cfg); - - for await (const ev of agentLoop( - userMessage as unknown as UserMessage, - context, - pc, - signal, - )) { - yield ev; - } - } - - async *continue( - messages: Message[], - cfg: AgentRunConfig, - signal?: AbortSignal, - ) { - const model = this.getModel(cfg); - const context = this.buildContext(messages, cfg); - const pc = this.buildLoopConfig(model, cfg); - - for await (const ev of agentLoopContinue(context, pc, signal)) { - yield ev; - } - } -} diff --git a/src/gateway/server.ts b/src/gateway/server.ts index 17a0c9e87..8d5ed6c2d 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -2407,7 +2407,8 @@ export async function startGatewayServer( const remoteAddr = ( socket as WebSocket & { _socket?: { remoteAddress?: string } } )._socket?.remoteAddress; - const canvasHostPortForWs = canvasHostServer?.port ?? (canvasHost ? port : undefined); + const canvasHostPortForWs = + canvasHostServer?.port ?? (canvasHost ? port : undefined); const canvasHostOverride = bridgeHost && bridgeHost !== "0.0.0.0" && bridgeHost !== "::" ? bridgeHost