From 463415ae846cbcfef0df691e413a1a73a9213757 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 29 Aug 2026 00:00:41 +0200 Subject: [PATCH] fix(providers): tolerate slow Pi and OMP RPC startup (#4008) MCP and extension initialization can keep provider control-plane requests pending beyond 30 seconds even when the response will arrive. Use a configurable 60-second provider default and include the active RPC phase in timeout errors. --- docs/custom-providers.md | 8 +++-- docs/providers.md | 2 ++ .../agent/providers/jsonl-rpc-process.test.ts | 8 +++-- .../agent/providers/jsonl-rpc-process.ts | 14 ++++++--- .../server/agent/providers/omp/agent.test.ts | 8 +++++ .../src/server/agent/providers/omp/agent.ts | 6 +++- .../agent/providers/omp/cli-runtime.test.ts | 29 +++++++++++++++++-- .../server/agent/providers/omp/cli-runtime.ts | 4 ++- .../agent/providers/omp/protocol-session.ts | 3 +- .../agent/providers/omp/provider-config.ts | 8 ++++- .../server/agent/providers/pi/agent.test.ts | 12 +++++++- .../src/server/agent/providers/pi/agent.ts | 13 +++++++-- .../agent/providers/pi/cli-runtime.test.ts | 23 ++++++++++++++- .../server/agent/providers/pi/cli-runtime.ts | 2 ++ 14 files changed, 120 insertions(+), 20 deletions(-) diff --git a/docs/custom-providers.md b/docs/custom-providers.md index 6874b7e6e2..1470133d91 100644 --- a/docs/custom-providers.md +++ b/docs/custom-providers.md @@ -393,6 +393,7 @@ Custom OMP profiles should extend `omp`. They inherit the OMP adapter's `rpc-ui` }, "params": { "sessionDir": "~/.local/state/omp-work/omp/agent/sessions", + "rpcTimeoutMs": 60000, "smolModel": "openai/gpt-5-mini", "slowModel": "anthropic/claude-opus-4-1", "planModel": "openai/o3" @@ -403,7 +404,7 @@ Custom OMP profiles should extend `omp`. They inherit the OMP adapter's `rpc-ui` } ``` -`params.sessionDir` is used only for importing sessions that were started outside Paseo. If `command` or XDG env vars move OMP's state directory, set `params.sessionDir` to the resulting OMP JSONL session directory; launching and resuming still go through the configured command. +`params.sessionDir` is used only for importing sessions that were started outside Paseo. If `command` or XDG env vars move OMP's state directory, set `params.sessionDir` to the resulting OMP JSONL session directory; launching and resuming still go through the configured command. `params.rpcTimeoutMs` overrides the 60-second OMP control-plane RPC deadline. For other providers that keep Pi's `--mode rpc` API but write sessions somewhere else, extend `pi`, replace the command, and provide the JSONL session directory: @@ -416,7 +417,8 @@ For other providers that keep Pi's `--mode rpc` API but write sessions somewhere "label": "My Pi Fork", "command": ["my-pi-fork"], "params": { - "sessionDir": "~/.my-pi-fork/sessions" + "sessionDir": "~/.my-pi-fork/sessions", + "rpcTimeoutMs": 60000 } } } @@ -424,7 +426,7 @@ For other providers that keep Pi's `--mode rpc` API but write sessions somewhere } ``` -This session directory is also import-only. Launching and resuming still go through the configured command, so this example resumes with `my-pi-fork --mode rpc --session `. +This session directory is also import-only. Launching and resuming still go through the configured command, so this example resumes with `my-pi-fork --mode rpc --session `. `params.rpcTimeoutMs` overrides the 60-second Pi control-plane RPC deadline. --- diff --git a/docs/providers.md b/docs/providers.md index 3ec5f710a6..c33c299a50 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -67,6 +67,8 @@ Pi model records expose input capabilities through `model.input`. Only send raw Pi MCP support depends on the open-source `pi-mcp-adapter` extension being loaded for the agent cwd. Probe with Pi RPC `get_commands`; the adapter registers an extension command named `mcp` (often with `sourceInfo.source` containing `pi-mcp-adapter`). When Paseo injects MCP servers into Pi, write a per-agent MCP config and pass it with `--mcp-config` instead of modifying user or project MCP files. Because that flag replaces the Pi global config layer, preserve the existing `/mcp.json` in the generated file before overlaying injected servers. For local HTTP servers such as Paseo's own `/mcp/agents` endpoint, explicitly disable adapter OAuth (`auth: false`, `oauth: false`) in the generated config. +Pi and OMP control-plane RPCs wait 60 seconds by default. Override the provider's `params.rpcTimeoutMs` when extension or MCP startup on a slow host needs a different deadline. Timeout errors name the pending RPC phase and report both elapsed time and the configured deadline. This setting does not govern long-running Pi compaction or Pi extension UI results. + Pi import discovery reads Pi's persisted JSONL session files because Pi RPC does not expose a recent-session listing command. Resume and full history hydration still go through `pi --mode rpc` using the session file as `nativeHandle`. OMP is a first-class built-in provider, disabled by default. Its launch contract, typed runtime, agent/session behavior, history, permissions, imports, and test fake live under `providers/omp/`; only the provider-neutral JSONL child-process transport is shared with Pi. It launches `omp --mode rpc-ui`, uses OMP's `get_available_commands` RPC for slash-command discovery, bridges OMP `rpc-ui` approval dialogs into Paseo permissions, and imports terminal-started sessions from `~/.omp/agent/sessions` when enabled. diff --git a/packages/server/src/server/agent/providers/jsonl-rpc-process.test.ts b/packages/server/src/server/agent/providers/jsonl-rpc-process.test.ts index 025f1e7b80..960d61c1f0 100644 --- a/packages/server/src/server/agent/providers/jsonl-rpc-process.test.ts +++ b/packages/server/src/server/agent/providers/jsonl-rpc-process.test.ts @@ -62,6 +62,7 @@ interface InMemoryChildProcess extends ChildProcessWithoutNullStreams { interface StartProcessOptions { child?: ChildProcessWithoutNullStreams; + defaultRequestTimeoutMs?: number; } function createInMemoryChildProcess(): InMemoryChildProcess { @@ -89,6 +90,7 @@ function startProcess(options: StartProcessOptions = {}): JsonlRpcProcess { env: { JSONL_RPC_TEST_VALUE: "resolved-env" }, }, logger: pino({ level: "silent" }), + defaultRequestTimeoutMs: options.defaultRequestTimeoutMs, ...(child ? { spawn: () => child } : {}), }); } @@ -157,13 +159,13 @@ describe("JsonlRpcProcess", () => { test("includes buffered stderr when a request times out", async () => { const child = createInMemoryChildProcess(); - const transport = startProcess({ child }); + const transport = startProcess({ child, defaultRequestTimeoutMs: 50 }); try { child.stderr.write("still waiting"); - await expect(transport.request({ type: "hang" }, 50)).rejects.toThrow( - "JSONL RPC request timed out for hang\nstill waiting", + await expect(transport.request({ type: "hang" })).rejects.toThrow( + /JSONL RPC request timed out phase=hang elapsedMs=\d+ timeoutMs=50\nstill waiting/, ); } finally { await transport.close(); diff --git a/packages/server/src/server/agent/providers/jsonl-rpc-process.ts b/packages/server/src/server/agent/providers/jsonl-rpc-process.ts index 4daff1bd06..0d1c8a436a 100644 --- a/packages/server/src/server/agent/providers/jsonl-rpc-process.ts +++ b/packages/server/src/server/agent/providers/jsonl-rpc-process.ts @@ -50,6 +50,7 @@ export interface JsonlRpcProcessOptions { launch: JsonlRpcLaunch; logger: Logger; diagnosticName?: string; + defaultRequestTimeoutMs?: number; spawn?: (launch: JsonlRpcLaunch) => ChildProcessWithoutNullStreams; } @@ -134,7 +135,7 @@ export class JsonlRpcProcess { startRequest( command: { type: string; [key: string]: unknown }, - timeoutMs: number | null = JSONL_RPC_DEFAULT_TIMEOUT_MS, + timeoutMs?: number | null, ): { id: string; promise: Promise } { if (this.disposed) { return { @@ -144,12 +145,17 @@ export class JsonlRpcProcess { } const id = `req_${this.nextRequestId}`; this.nextRequestId += 1; + const requestTimeoutMs = + timeoutMs === undefined + ? (this.options.defaultRequestTimeoutMs ?? JSONL_RPC_DEFAULT_TIMEOUT_MS) + : timeoutMs; + const startedAt = Date.now(); const promise = new Promise((resolve, reject) => { - const timer = createRequestTimeout(timeoutMs, () => { + const timer = createRequestTimeout(requestTimeoutMs, () => { this.pending.delete(id); reject( new Error( - `${this.diagnosticName} request timed out for ${command.type}\n${this.stderrBuffer}`.trim(), + `${this.diagnosticName} request timed out phase=${command.type} elapsedMs=${Date.now() - startedAt} timeoutMs=${requestTimeoutMs}\n${this.stderrBuffer}`.trim(), ), ); }); @@ -161,7 +167,7 @@ export class JsonlRpcProcess { request( command: { type: string; [key: string]: unknown }, - timeoutMs: number | null = JSONL_RPC_DEFAULT_TIMEOUT_MS, + timeoutMs?: number | null, ): Promise { return this.startRequest(command, timeoutMs).promise; } diff --git a/packages/server/src/server/agent/providers/omp/agent.test.ts b/packages/server/src/server/agent/providers/omp/agent.test.ts index 30876ae9aa..34174a9841 100644 --- a/packages/server/src/server/agent/providers/omp/agent.test.ts +++ b/packages/server/src/server/agent/providers/omp/agent.test.ts @@ -4,8 +4,16 @@ import { setImmediate as waitForImmediate } from "node:timers/promises"; import type { PaseoToolCatalog } from "../../tools/types.js"; import type { OmpNoTurnScheduler, OmpProviderIdleScheduler } from "./agent.js"; import type { OmpUsagePollScheduler } from "./usage-poller.js"; +import { resolveOmpProviderParams } from "./provider-config.js"; import { OmpHarness } from "./test-utils/omp-harness.js"; +test("OMP RPC timeout defaults to 60 seconds and accepts an override", () => { + expect(resolveOmpProviderParams({}).runtimeProviderParams.rpcTimeoutMs).toBe(60_000); + expect( + resolveOmpProviderParams({ rpcTimeoutMs: 90_000 }).runtimeProviderParams.rpcTimeoutMs, + ).toBe(90_000); +}); + class ManualIdleScheduler implements OmpProviderIdleScheduler { private readonly retries: Array<() => void> = []; private readonly waiters: Array<{ count: number; resolve: () => void }> = []; diff --git a/packages/server/src/server/agent/providers/omp/agent.ts b/packages/server/src/server/agent/providers/omp/agent.ts index db996cf701..85b95c2dcd 100644 --- a/packages/server/src/server/agent/providers/omp/agent.ts +++ b/packages/server/src/server/agent/providers/omp/agent.ts @@ -828,12 +828,14 @@ function buildExtensionUiResponse( function createRuntime( logger: Logger, runtimeSettings: ProviderRuntimeSettings | undefined, + requestTimeoutMs: number, ): OmpRuntime { return new OmpCliRuntime({ logger, runtimeSettings, command: ["omp"], commandsRpcName: "get_available_commands", + requestTimeoutMs, }); } @@ -2213,7 +2215,9 @@ export class OmpAgentClient implements AgentClient { this.providerIdleScheduler = options.providerIdleScheduler; this.noTurnScheduler = options.noTurnScheduler; this.usagePollScheduler = options.usagePollScheduler; - this.runtime = options.runtime ?? createRuntime(options.logger, runtimeSettings); + this.runtime = + options.runtime ?? + createRuntime(options.logger, runtimeSettings, this.providerParams.rpcTimeoutMs); } private async configureNativePaseoTools( diff --git a/packages/server/src/server/agent/providers/omp/cli-runtime.test.ts b/packages/server/src/server/agent/providers/omp/cli-runtime.test.ts index 66cd3cb0c1..9df9da68dd 100644 --- a/packages/server/src/server/agent/providers/omp/cli-runtime.test.ts +++ b/packages/server/src/server/agent/providers/omp/cli-runtime.test.ts @@ -2,7 +2,7 @@ import type { ChildProcessWithoutNullStreams } from "node:child_process"; import { EventEmitter } from "node:events"; import { PassThrough } from "node:stream"; import pino from "pino"; -import { describe, expect, test } from "vitest"; +import { describe, expect, test, vi } from "vitest"; import { OmpCliRuntime } from "./cli-runtime.js"; import type { OmpRuntimeLaunch } from "./runtime.js"; @@ -49,11 +49,16 @@ function createOmpChild(options?: { return child; } -function createRuntime(child: OmpChild, launches: OmpRuntimeLaunch[] = []): OmpCliRuntime { +function createRuntime( + child: OmpChild, + launches: OmpRuntimeLaunch[] = [], + options?: { requestTimeoutMs?: number }, +): OmpCliRuntime { return new OmpCliRuntime({ logger: pino({ level: "silent" }), command: ["omp"], commandsRpcName: "get_available_commands", + requestTimeoutMs: options?.requestTimeoutMs, spawnProcess: (launch) => { launches.push(launch); return child; @@ -94,6 +99,26 @@ function withoutRequestId(command: Record): Record { + test("uses the configured RPC timeout and attributes the pending phase", async () => { + vi.useFakeTimers(); + const child = createOmpChild(); + const session = await createRuntime(child, [], { requestTimeoutMs: 100 }).startSession({ + cwd: "/workspace/project", + }); + + try { + const state = session.getState(); + const rejection = expect(state).rejects.toThrow( + "OMP RPC request timed out phase=get_state elapsedMs=100 timeoutMs=100", + ); + await vi.advanceTimersByTimeAsync(100); + await rejection; + } finally { + vi.useRealTimers(); + await session.close(); + } + }); + test("validates session state with the documented queued message count", async () => { const child = createOmpChild(); replyToCommands(child, () => ({ diff --git a/packages/server/src/server/agent/providers/omp/cli-runtime.ts b/packages/server/src/server/agent/providers/omp/cli-runtime.ts index 7b3f383965..240a8501a9 100644 --- a/packages/server/src/server/agent/providers/omp/cli-runtime.ts +++ b/packages/server/src/server/agent/providers/omp/cli-runtime.ts @@ -47,6 +47,7 @@ export interface OmpCliRuntimeOptions { runtimeSettings?: ProviderRuntimeSettings; command?: [string, ...string[]]; commandsRpcName?: "get_available_commands"; + requestTimeoutMs?: number; spawnProcess?: (launch: OmpRuntimeLaunch) => ChildProcessWithoutNullStreams; } @@ -79,13 +80,14 @@ export class OmpCliRuntime implements OmpRuntime { launch: processLaunch, logger: this.options.logger, diagnosticName: "OMP RPC", + defaultRequestTimeoutMs: this.options.requestTimeoutMs, ...(spawn ? { spawn: () => spawn(launch) } : {}), }; const process = new JsonlRpcProcess(processOptions); const handleAbort = () => void process.close(input.signal?.reason).catch(() => undefined); input.signal?.addEventListener("abort", handleAbort, { once: true }); try { - await establishOmpProtocol(process, this.options.logger); + await establishOmpProtocol(process, this.options.logger, this.options.requestTimeoutMs); input.signal?.throwIfAborted(); return new OmpCliRuntimeSession(process, this.commandsRpcName); } catch (error) { diff --git a/packages/server/src/server/agent/providers/omp/protocol-session.ts b/packages/server/src/server/agent/providers/omp/protocol-session.ts index ae2ad4b809..ba9ca7e797 100644 --- a/packages/server/src/server/agent/providers/omp/protocol-session.ts +++ b/packages/server/src/server/agent/providers/omp/protocol-session.ts @@ -16,12 +16,13 @@ export interface OmpProtocolTransport { export async function establishOmpProtocol( transport: OmpProtocolTransport, logger: Logger, + requestTimeoutMs = JSONL_RPC_DEFAULT_TIMEOUT_MS, ): Promise { const ready = await waitForReady(transport); if (!supportsJsonlRpcProtocolV2(ready)) return; const response = (await transport.request( { type: "negotiate_protocol", protocolVersion: 2 }, - JSONL_RPC_DEFAULT_TIMEOUT_MS, + requestTimeoutMs, )) as { protocolVersion?: unknown } | undefined; if (response?.protocolVersion !== 2) throw new Error("OMP did not accept RPC protocol v2"); logger.debug({}, "Negotiated OMP RPC protocol v2 (chunked frame transport)"); diff --git a/packages/server/src/server/agent/providers/omp/provider-config.ts b/packages/server/src/server/agent/providers/omp/provider-config.ts index cd384f9142..7035142fe5 100644 --- a/packages/server/src/server/agent/providers/omp/provider-config.ts +++ b/packages/server/src/server/agent/providers/omp/provider-config.ts @@ -8,6 +8,7 @@ import type { ProviderRuntimeSettings } from "../../provider-launch-config.js"; const OMP_SESSION_DIR = "~/.omp/agent/sessions"; const DEFAULT_OMP_MODE_ID = "full"; +const DEFAULT_OMP_RPC_TIMEOUT_MS = 60_000; export const MIN_SUPPORTED_OMP_VERSION = "16.3.9"; export { OMP_MODES }; @@ -15,6 +16,7 @@ export { OMP_MODES }; export const OmpProviderParamsSchema = z .object({ sessionDir: z.string().min(1).optional(), + rpcTimeoutMs: z.number().int().positive().default(DEFAULT_OMP_RPC_TIMEOUT_MS), smolModel: z.string().min(1).optional(), slowModel: z.string().min(1).optional(), planModel: z.string().min(1).optional(), @@ -23,6 +25,7 @@ export const OmpProviderParamsSchema = z export interface OmpRuntimeProviderParams { sessionDir: string; + rpcTimeoutMs: number; } export interface OmpModelRoleParams { @@ -130,7 +133,10 @@ export function resolveOmpProviderParams(providerParams: unknown): { } { const params = OmpProviderParamsSchema.parse(providerParams ?? {}); return { - runtimeProviderParams: { sessionDir: params.sessionDir ?? OMP_SESSION_DIR }, + runtimeProviderParams: { + sessionDir: params.sessionDir ?? OMP_SESSION_DIR, + rpcTimeoutMs: params.rpcTimeoutMs, + }, modelRoleParams: { ...(params.smolModel ? { smolModel: params.smolModel } : {}), ...(params.slowModel ? { slowModel: params.slowModel } : {}), diff --git a/packages/server/src/server/agent/providers/pi/agent.test.ts b/packages/server/src/server/agent/providers/pi/agent.test.ts index dafe769bef..a6e1f30316 100644 --- a/packages/server/src/server/agent/providers/pi/agent.test.ts +++ b/packages/server/src/server/agent/providers/pi/agent.test.ts @@ -17,13 +17,23 @@ import { pathToFileURL } from "node:url"; import { describe, expect, onTestFinished, test } from "vitest"; import type { AgentSession, AgentSessionConfig, AgentStreamEvent } from "../../agent-sdk-types.js"; -import { PiRpcAgentClient, PiRpcAgentSession, transformPiModels } from "./agent.js"; +import { + PiProviderParamsSchema, + PiRpcAgentClient, + PiRpcAgentSession, + transformPiModels, +} from "./agent.js"; import { FakePi } from "./test-utils/fake-pi.js"; import type { PiUsagePollScheduler } from "./usage-poller.js"; const ONE_BY_ONE_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII="; +test("Pi RPC timeout defaults to 60 seconds and accepts an override", () => { + expect(PiProviderParamsSchema.parse({}).rpcTimeoutMs).toBe(60_000); + expect(PiProviderParamsSchema.parse({ rpcTimeoutMs: 90_000 }).rpcTimeoutMs).toBe(90_000); +}); + function createClient( pi = new FakePi(), usagePollScheduler?: PiUsagePollScheduler, diff --git a/packages/server/src/server/agent/providers/pi/agent.ts b/packages/server/src/server/agent/providers/pi/agent.ts index 9251c682d0..24e8f02d06 100644 --- a/packages/server/src/server/agent/providers/pi/agent.ts +++ b/packages/server/src/server/agent/providers/pi/agent.ts @@ -95,6 +95,7 @@ const PASEO_PI_ENTRY_CAPTURE_MARKER = "PASEO_ENTRY_CAPTURE"; const PASEO_PI_SUBMITTED_USER_ENTRY_MARKER = "PASEO_SUBMITTED_USER_ENTRY"; const PASEO_PI_COMMAND_RESULT_MARKER = "PASEO_COMMAND_RESULT"; const DEFAULT_PI_EXTENSION_RESULT_TIMEOUT_MS = 30_000; +const DEFAULT_PI_RPC_TIMEOUT_MS = 60_000; const QUESTION_RESPONSE_HEADER = "Response"; const QUESTION_COMMENT_HEADER = "Comment"; const PI_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response..."; @@ -103,6 +104,7 @@ const COMBINED_ASK_USER_METADATA = "ask_user_select_optional_comment"; export const PiProviderParamsSchema = z .object({ sessionDir: z.string().min(1).optional(), + rpcTimeoutMs: z.number().int().positive().default(DEFAULT_PI_RPC_TIMEOUT_MS), extensionTimeoutMs: z.number().int().positive().default(DEFAULT_PI_EXTENSION_RESULT_TIMEOUT_MS), }) .strict(); @@ -1184,12 +1186,17 @@ function mapPiModel(model: PiModel, provider: AgentProvider): AgentModelDefiniti }; } -function createRuntime(logger: Logger, runtimeSettings?: ProviderRuntimeSettings): PiRuntime { +function createRuntime( + logger: Logger, + runtimeSettings: ProviderRuntimeSettings | undefined, + requestTimeoutMs: number, +): PiRuntime { return new PiCliRuntime({ logger, runtimeSettings, command: [PI_BINARY_COMMAND], commandsRpcName: "get_commands", + requestTimeoutMs, }); } @@ -2407,7 +2414,9 @@ export class PiRpcAgentClient implements AgentClient { this.logger = options.logger; this.runtimeSettings = options.runtimeSettings; this.providerParams = PiProviderParamsSchema.parse(options.providerParams ?? {}); - this.runtime = options.runtime ?? createRuntime(options.logger, options.runtimeSettings); + this.runtime = + options.runtime ?? + createRuntime(options.logger, options.runtimeSettings, this.providerParams.rpcTimeoutMs); this.usagePollScheduler = options.usagePollScheduler; } diff --git a/packages/server/src/server/agent/providers/pi/cli-runtime.test.ts b/packages/server/src/server/agent/providers/pi/cli-runtime.test.ts index b62627105c..9e2d300d95 100644 --- a/packages/server/src/server/agent/providers/pi/cli-runtime.test.ts +++ b/packages/server/src/server/agent/providers/pi/cli-runtime.test.ts @@ -34,12 +34,13 @@ function createPiChild(): PiChild { function createRuntime( child: PiChild, launches: PiRuntimeLaunch[] = [], - options?: { commandsRpcName?: string }, + options?: { commandsRpcName?: string; requestTimeoutMs?: number }, ): PiCliRuntime { return new PiCliRuntime({ logger: pino({ level: "silent" }), command: ["pi"], commandsRpcName: options?.commandsRpcName, + requestTimeoutMs: options?.requestTimeoutMs, spawnProcess: (launch) => { launches.push(launch); return child; @@ -293,6 +294,26 @@ describe("PiCliRuntime", () => { await rejection; }); + test("uses the configured RPC timeout and attributes the pending phase", async () => { + vi.useFakeTimers(); + const child = createPiChild(); + const session = await createRuntime(child, [], { requestTimeoutMs: 100 }).startSession({ + cwd: "/workspace/project", + }); + + try { + const state = session.getState(); + const rejection = expect(state).rejects.toThrow( + "Pi RPC request timed out phase=get_state elapsedMs=100 timeoutMs=100", + ); + await vi.advanceTimersByTimeAsync(100); + await rejection; + } finally { + vi.useRealTimers(); + await session.close(); + } + }); + test("compact waits beyond the default control-plane timeout for a late response", async () => { vi.useFakeTimers(); const child = createPiChild(); diff --git a/packages/server/src/server/agent/providers/pi/cli-runtime.ts b/packages/server/src/server/agent/providers/pi/cli-runtime.ts index 39851fd167..c0e7f28f6f 100644 --- a/packages/server/src/server/agent/providers/pi/cli-runtime.ts +++ b/packages/server/src/server/agent/providers/pi/cli-runtime.ts @@ -35,6 +35,7 @@ export interface PiCliRuntimeOptions { runtimeSettings?: ProviderRuntimeSettings; command?: [string, ...string[]]; commandsRpcName?: string; + requestTimeoutMs?: number; spawnProcess?: (launch: PiRuntimeLaunch) => ChildProcessWithoutNullStreams; } @@ -67,6 +68,7 @@ export class PiCliRuntime implements PiRuntime { launch: processLaunch, logger: this.options.logger, diagnosticName: "Pi RPC", + defaultRequestTimeoutMs: this.options.requestTimeoutMs, ...(spawn ? { spawn: () => spawn(launch) } : {}), }; const process = new JsonlRpcProcess(processOptions);