@getpaseo/server 0.1.97 → 0.1.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/server/server/agent/agent-manager.d.ts +11 -3
  2. package/dist/server/server/agent/agent-manager.js +96 -24
  3. package/dist/server/server/agent/agent-prompt.d.ts +1 -1
  4. package/dist/server/server/agent/agent-prompt.js +3 -10
  5. package/dist/server/server/agent/agent-sdk-types.d.ts +20 -9
  6. package/dist/server/server/agent/create-agent/create.d.ts +2 -0
  7. package/dist/server/server/agent/create-agent/create.js +8 -7
  8. package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
  9. package/dist/server/server/agent/lifecycle-command.js +9 -2
  10. package/dist/server/server/agent/mcp-server.js +254 -115
  11. package/dist/server/server/agent/provider-notices.d.ts +3 -0
  12. package/dist/server/server/agent/provider-notices.js +5 -0
  13. package/dist/server/server/agent/provider-registry.d.ts +8 -3
  14. package/dist/server/server/agent/provider-registry.js +58 -25
  15. package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
  16. package/dist/server/server/agent/provider-snapshot-manager.js +37 -16
  17. package/dist/server/server/agent/providers/acp-agent.d.ts +5 -3
  18. package/dist/server/server/agent/providers/acp-agent.js +32 -19
  19. package/dist/server/server/agent/providers/claude/agent.d.ts +2 -2
  20. package/dist/server/server/agent/providers/claude/agent.js +261 -167
  21. package/dist/server/server/agent/providers/claude/models.js +7 -3
  22. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +6 -4
  23. package/dist/server/server/agent/providers/codex-app-server-agent.js +48 -25
  24. package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -31
  25. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
  26. package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
  27. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +0 -1
  28. package/dist/server/server/agent/providers/generic-acp-agent.js +2 -108
  29. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -3
  30. package/dist/server/server/agent/providers/mock-load-test-agent.js +5 -5
  31. package/dist/server/server/agent/providers/mock-slow-provider.d.ts +2 -3
  32. package/dist/server/server/agent/providers/mock-slow-provider.js +3 -6
  33. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
  34. package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
  35. package/dist/server/server/agent/providers/opencode-agent.d.ts +6 -3
  36. package/dist/server/server/agent/providers/opencode-agent.js +61 -107
  37. package/dist/server/server/agent/providers/pi/agent.d.ts +2 -3
  38. package/dist/server/server/agent/providers/pi/agent.js +11 -63
  39. package/dist/server/server/agent/providers/pi/cli-runtime.js +2 -2
  40. package/dist/server/server/agent/providers/pi/runtime.d.ts +1 -1
  41. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +1 -1
  42. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +1 -1
  43. package/dist/server/server/bootstrap.d.ts +2 -0
  44. package/dist/server/server/bootstrap.js +32 -2
  45. package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
  46. package/dist/server/server/managed-processes/managed-processes.js +326 -0
  47. package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
  48. package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
  49. package/dist/server/server/session/agent-config/agent-config-session.d.ts +50 -0
  50. package/dist/server/server/session/agent-config/agent-config-session.js +98 -0
  51. package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
  52. package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
  53. package/dist/server/server/session/checkout/checkout-session.d.ts +142 -0
  54. package/dist/server/server/session/checkout/checkout-session.js +925 -0
  55. package/dist/server/server/session/daemon/daemon-session.d.ts +50 -0
  56. package/dist/server/server/session/daemon/daemon-session.js +98 -0
  57. package/dist/server/server/session/files/workspace-files-session.d.ts +43 -0
  58. package/dist/server/server/session/files/workspace-files-session.js +218 -0
  59. package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
  60. package/dist/server/server/session/project-config/project-config-session.js +125 -0
  61. package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
  62. package/dist/server/server/session/provider/provider-catalog-session.js +339 -0
  63. package/dist/server/server/session/voice/voice-session.d.ts +166 -0
  64. package/dist/server/server/session/voice/voice-session.js +893 -0
  65. package/dist/server/server/{voice → session/voice}/voice-turn-controller.d.ts +2 -2
  66. package/dist/server/server/{voice → session/voice}/voice-turn-controller.js +2 -2
  67. package/dist/server/server/session.d.ts +23 -207
  68. package/dist/server/server/session.js +2319 -5102
  69. package/dist/server/server/speech/providers/openai/runtime.js +3 -4
  70. package/dist/server/server/websocket-server.d.ts +1 -0
  71. package/dist/server/server/websocket-server.js +11 -0
  72. package/dist/server/server/workspace-archive-service.js +2 -3
  73. package/dist/server/server/workspace-directory.js +5 -5
  74. package/dist/server/server/workspace-reconciliation-service.js +2 -2
  75. package/dist/server/server/worktree-core.d.ts +1 -0
  76. package/dist/server/server/worktree-core.js +5 -1
  77. package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
  78. package/dist/server/services/quota-fetcher/manifest.js +47 -0
  79. package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
  80. package/dist/server/services/quota-fetcher/provider.js +2 -0
  81. package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
  82. package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
  83. package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
  84. package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
  85. package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
  86. package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
  87. package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
  88. package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
  89. package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
  90. package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
  91. package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
  92. package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
  93. package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
  94. package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
  95. package/dist/server/services/quota-fetcher/service.d.ts +28 -0
  96. package/dist/server/services/quota-fetcher/service.js +58 -0
  97. package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
  98. package/dist/server/services/quota-fetcher/usage.js +49 -0
  99. package/dist/server/utils/checkout-git.d.ts +6 -0
  100. package/dist/server/utils/directory-suggestions.js +98 -2
  101. package/package.json +5 -5
@@ -1,5 +1,8 @@
1
1
  import type { ChildProcess } from "node:child_process";
2
2
  import type { Logger } from "pino";
3
+ import { type SpawnProcessOptions } from "../../../../utils/spawn.js";
4
+ import { type ProcessTerminator } from "../../../../utils/tree-kill.js";
5
+ import type { ManagedProcessRegistry } from "../../../managed-processes/managed-processes.js";
3
6
  import { type ProviderRuntimeSettings } from "../../provider-launch-config.js";
4
7
  export interface OpenCodeServerAcquisition {
5
8
  server: {
@@ -24,6 +27,22 @@ export interface OpenCodeServerGeneration {
24
27
  url: string;
25
28
  refCount: number;
26
29
  retired: boolean;
30
+ managedProcessId?: string;
31
+ }
32
+ export type OpenCodePortAllocator = () => Promise<number>;
33
+ export type OpenCodeCommandPrefixResolver = () => Promise<{
34
+ command: string;
35
+ args: string[];
36
+ }>;
37
+ export type OpenCodeServerProcessSpawner = (command: string, args: string[], options: SpawnProcessOptions) => ChildProcess;
38
+ export interface OpenCodeServerManagerOptions {
39
+ logger: Logger;
40
+ runtimeSettings?: ProviderRuntimeSettings;
41
+ managedProcesses?: ManagedProcessRegistry;
42
+ terminateProcess?: ProcessTerminator;
43
+ portAllocator?: OpenCodePortAllocator;
44
+ resolveCommandPrefix?: OpenCodeCommandPrefixResolver;
45
+ spawnServerProcess?: OpenCodeServerProcessSpawner;
27
46
  }
28
47
  export declare class OpenCodeServerManager implements OpenCodeServerManagerLike {
29
48
  private static instance;
@@ -35,8 +54,13 @@ export declare class OpenCodeServerManager implements OpenCodeServerManagerLike
35
54
  private readonly logger;
36
55
  private readonly runtimeSettings?;
37
56
  private readonly runtimeSettingsKey;
38
- private constructor();
39
- static getInstance(logger: Logger, runtimeSettings?: ProviderRuntimeSettings): OpenCodeServerManager;
57
+ private readonly managedProcesses?;
58
+ private readonly terminateProcess;
59
+ private readonly portAllocator;
60
+ private readonly resolveCommandPrefix;
61
+ private readonly spawnServerProcess;
62
+ constructor(options: OpenCodeServerManagerOptions);
63
+ static getInstance(logger: Logger, runtimeSettings?: ProviderRuntimeSettings, options?: Omit<OpenCodeServerManagerOptions, "logger" | "runtimeSettings">): OpenCodeServerManager;
40
64
  private static registerExitHandler;
41
65
  ensureRunning(): Promise<{
42
66
  port: number;
@@ -55,5 +79,8 @@ export declare class OpenCodeServerManager implements OpenCodeServerManagerLike
55
79
  shutdown(): Promise<void>;
56
80
  private cleanupRetiredServers;
57
81
  private killServer;
82
+ private recordManagedServerProcess;
83
+ private removeManagedProcessRecordWhenResolved;
84
+ private removeManagedProcessId;
58
85
  }
59
86
  //# sourceMappingURL=server-manager.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import net from "node:net";
2
+ import os from "node:os";
2
3
  import { findExecutable } from "../../../../executable-resolution/executable-resolution.js";
3
4
  import { spawnProcess } from "../../../../utils/spawn.js";
4
5
  import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
@@ -6,19 +7,30 @@ import { createProviderEnvSpec, resolveProviderCommandPrefix, } from "../../prov
6
7
  const OPENCODE_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5000;
7
8
  const OPENCODE_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS = 1000;
8
9
  export class OpenCodeServerManager {
9
- constructor(logger, runtimeSettings) {
10
+ constructor(options) {
10
11
  this.currentServer = null;
11
12
  this.retiredServers = new Set();
12
13
  this.startPromise = null;
13
14
  this.forcedRefreshPromise = null;
14
- this.logger = logger;
15
- this.runtimeSettings = runtimeSettings;
16
- this.runtimeSettingsKey = JSON.stringify(runtimeSettings ?? {});
15
+ this.logger = options.logger;
16
+ this.runtimeSettings = options.runtimeSettings;
17
+ this.runtimeSettingsKey = JSON.stringify(this.runtimeSettings ?? {});
18
+ this.managedProcesses = options.managedProcesses;
19
+ this.terminateProcess = options.terminateProcess ?? terminateWithTreeKill;
20
+ this.portAllocator = options.portAllocator ?? findAvailablePort;
21
+ this.resolveCommandPrefix =
22
+ options.resolveCommandPrefix ??
23
+ (() => resolveProviderCommandPrefix(this.runtimeSettings?.command, resolveOpenCodeBinary));
24
+ this.spawnServerProcess = options.spawnServerProcess ?? spawnProcess;
17
25
  }
18
- static getInstance(logger, runtimeSettings) {
26
+ static getInstance(logger, runtimeSettings, options = {}) {
19
27
  const nextSettingsKey = JSON.stringify(runtimeSettings ?? {});
20
28
  if (!OpenCodeServerManager.instance) {
21
- OpenCodeServerManager.instance = new OpenCodeServerManager(logger, runtimeSettings);
29
+ OpenCodeServerManager.instance = new OpenCodeServerManager({
30
+ logger,
31
+ runtimeSettings,
32
+ ...options,
33
+ });
22
34
  OpenCodeServerManager.registerExitHandler();
23
35
  }
24
36
  else if (OpenCodeServerManager.instance.runtimeSettingsKey !== nextSettingsKey) {
@@ -128,11 +140,14 @@ export class OpenCodeServerManager {
128
140
  return server;
129
141
  }
130
142
  async startServer(launchEnv) {
131
- const port = await findAvailablePort();
143
+ const port = await this.portAllocator();
132
144
  const url = `http://127.0.0.1:${port}`;
133
- const launchPrefix = await resolveProviderCommandPrefix(this.runtimeSettings?.command, resolveOpenCodeBinary);
145
+ const launchPrefix = await this.resolveCommandPrefix();
146
+ const serverArgs = [...launchPrefix.args, "serve", "--port", String(port)];
147
+ const serverCwd = os.homedir();
134
148
  return new Promise((resolve, reject) => {
135
- const serverProcess = spawnProcess(launchPrefix.command, [...launchPrefix.args, "serve", "--port", String(port)], {
149
+ const serverProcess = this.spawnServerProcess(launchPrefix.command, serverArgs, {
150
+ cwd: serverCwd,
136
151
  detached: process.platform !== "win32",
137
152
  stdio: ["ignore", "pipe", "pipe"],
138
153
  ...createProviderEnvSpec({
@@ -140,6 +155,12 @@ export class OpenCodeServerManager {
140
155
  overlays: [launchEnv],
141
156
  }),
142
157
  });
158
+ const managedProcessRecord = this.recordManagedServerProcess({
159
+ process: serverProcess,
160
+ command: launchPrefix.command,
161
+ args: serverArgs,
162
+ port,
163
+ });
143
164
  let started = false;
144
165
  let stderrBuffer = "";
145
166
  let stdoutBuffer = "";
@@ -174,13 +195,17 @@ export class OpenCodeServerManager {
174
195
  if (output.includes("listening on") && !started) {
175
196
  started = true;
176
197
  clearTimeout(timeout);
177
- resolve({
178
- process: serverProcess,
179
- port,
180
- url,
181
- refCount: 0,
182
- retired: false,
183
- });
198
+ void (async () => {
199
+ const record = await managedProcessRecord;
200
+ resolve({
201
+ process: serverProcess,
202
+ port,
203
+ url,
204
+ refCount: 0,
205
+ retired: false,
206
+ ...(record ? { managedProcessId: record.id } : {}),
207
+ });
208
+ })();
184
209
  }
185
210
  });
186
211
  serverProcess.stderr?.on("data", (data) => {
@@ -190,10 +215,12 @@ export class OpenCodeServerManager {
190
215
  });
191
216
  serverProcess.on("error", (error) => {
192
217
  clearTimeout(timeout);
218
+ this.removeManagedProcessRecordWhenResolved(managedProcessRecord);
193
219
  const headline = error instanceof Error ? error.message : String(error);
194
220
  reject(new Error(buildStartupErrorMessage(headline)));
195
221
  });
196
222
  serverProcess.on("exit", (code) => {
223
+ this.removeManagedProcessRecordWhenResolved(managedProcessRecord);
197
224
  if (!started) {
198
225
  clearTimeout(timeout);
199
226
  reject(new Error(buildStartupErrorMessage(`OpenCode server exited with code ${code}`)));
@@ -231,7 +258,7 @@ export class OpenCodeServerManager {
231
258
  (server.process.signalCode !== null && server.process.signalCode !== undefined)) {
232
259
  return;
233
260
  }
234
- const result = await terminateWithTreeKill(server.process, {
261
+ const result = await this.terminateProcess(server.process, {
235
262
  gracefulTimeoutMs: OPENCODE_SERVER_GRACEFUL_SHUTDOWN_TIMEOUT_MS,
236
263
  forceTimeoutMs: OPENCODE_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS,
237
264
  onForceSignal: () => {
@@ -241,6 +268,45 @@ export class OpenCodeServerManager {
241
268
  if (result === "kill-timeout") {
242
269
  this.logger.warn({ timeoutMs: OPENCODE_SERVER_FORCE_SHUTDOWN_TIMEOUT_MS }, "OpenCode server did not report exit after SIGKILL");
243
270
  }
271
+ if (server.managedProcessId) {
272
+ await this.removeManagedProcessId(server.managedProcessId);
273
+ server.managedProcessId = undefined;
274
+ }
275
+ }
276
+ async recordManagedServerProcess(options) {
277
+ const pid = options.process.pid;
278
+ if (!this.managedProcesses || typeof pid !== "number" || pid <= 0) {
279
+ return null;
280
+ }
281
+ try {
282
+ return await this.managedProcesses.record({
283
+ owner: { provider: "opencode", kind: "helper-server" },
284
+ pid,
285
+ command: options.command,
286
+ args: options.args,
287
+ metadata: { port: options.port },
288
+ });
289
+ }
290
+ catch (error) {
291
+ this.logger.warn({ err: error, pid, port: options.port }, "Failed to record OpenCode helper process");
292
+ return null;
293
+ }
294
+ }
295
+ removeManagedProcessRecordWhenResolved(record) {
296
+ void record.then((resolved) => {
297
+ if (resolved) {
298
+ return this.removeManagedProcessId(resolved.id);
299
+ }
300
+ return undefined;
301
+ });
302
+ }
303
+ async removeManagedProcessId(id) {
304
+ try {
305
+ await this.managedProcesses?.remove(id);
306
+ }
307
+ catch (error) {
308
+ this.logger.warn({ err: error, id }, "Failed to remove OpenCode helper process record");
309
+ }
244
310
  }
245
311
  }
246
312
  OpenCodeServerManager.instance = null;
@@ -1,9 +1,10 @@
1
1
  import { type AssistantMessage as OpenCodeAssistantMessage, type Event as OpenCodeEvent, type FilePartInput as OpenCodeFilePartInput, type Message as OpenCodeMessage, type OpencodeClient, type Part as OpenCodePart, type TextPartInput as OpenCodeTextPartInput } from "@opencode-ai/sdk/v2/client";
2
2
  import type { Logger } from "pino";
3
- import { type AgentCapabilityFlags, type AgentClient, type AgentCreateSessionOptions, type AgentFeature, type AgentLaunchContext, type AgentMode, type AgentModelDefinition, type AgentPermissionRequest, type AgentPermissionResponse, type AgentPersistenceHandle, type AgentPromptInput, type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, type AgentSession, type AgentSessionConfig, type AgentSlashCommand, type AgentStreamEvent, type AgentTimelineItem, type AgentUsage, type ImportableProviderSession, type ImportProviderSessionContext, type ImportProviderSessionInput, type ListImportableSessionsOptions, type ResolveAgentCreateConfigInput, type ResolveAgentCreateConfigResult, type ListModelsOptions, type ListModesOptions, type ToolCallTimelineItem } from "../agent-sdk-types.js";
3
+ import { type AgentCapabilityFlags, type AgentClient, type AgentCreateSessionOptions, type AgentFeature, type AgentLaunchContext, type AgentMode, type AgentModelDefinition, type AgentPermissionRequest, type AgentPermissionResponse, type AgentPersistenceHandle, type AgentPromptInput, type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, type AgentSession, type AgentSessionConfig, type AgentSlashCommand, type AgentStreamEvent, type AgentTimelineItem, type AgentUsage, type FetchCatalogOptions, type ImportableProviderSession, type ImportProviderSessionContext, type ImportProviderSessionInput, type ListImportableSessionsOptions, type ResolveAgentCreateConfigInput, type ResolveAgentCreateConfigResult, type ProviderCatalog, type ToolCallTimelineItem } from "../agent-sdk-types.js";
4
4
  import { isDefaultAgentCreateConfigUnattended } from "../create-agent-mode.js";
5
5
  import { type ProviderRuntimeSettings } from "../provider-launch-config.js";
6
6
  import { type OpenCodeRuntime } from "./opencode/runtime.js";
7
+ import type { ManagedProcessRegistry } from "../../managed-processes/managed-processes.js";
7
8
  declare function resolveOpenCodeCreateConfig(input: ResolveAgentCreateConfigInput): ResolveAgentCreateConfigResult;
8
9
  declare function isOpenCodeCreateConfigUnattended(input: Parameters<typeof isDefaultAgentCreateConfigUnattended>[0]): boolean;
9
10
  type OpenCodeAgentConfig = AgentSessionConfig & {
@@ -103,6 +104,7 @@ export declare const __openCodeInternals: {
103
104
  };
104
105
  interface OpenCodeAgentClientDeps {
105
106
  runtime?: OpenCodeRuntime;
107
+ managedProcesses?: ManagedProcessRegistry;
106
108
  }
107
109
  export declare class OpenCodeAgentClient implements AgentClient {
108
110
  readonly provider: "opencode";
@@ -116,8 +118,7 @@ export declare class OpenCodeAgentClient implements AgentClient {
116
118
  constructor(logger: Logger, runtimeSettings?: ProviderRuntimeSettings, deps?: OpenCodeAgentClientDeps);
117
119
  createSession(config: AgentSessionConfig, launchContext?: AgentLaunchContext, options?: AgentCreateSessionOptions): Promise<AgentSession>;
118
120
  resumeSession(handle: AgentPersistenceHandle, overrides?: Partial<AgentSessionConfig>, launchContext?: AgentLaunchContext): Promise<AgentSession>;
119
- listModels(options: ListModelsOptions): Promise<AgentModelDefinition[]>;
120
- listModes(options: ListModesOptions): Promise<AgentMode[]>;
121
+ fetchCatalog(options: FetchCatalogOptions): Promise<ProviderCatalog>;
121
122
  listCommands(config: AgentSessionConfig): Promise<AgentSlashCommand[]>;
122
123
  listFeatures(config: AgentSessionConfig): Promise<AgentFeature[]>;
123
124
  listImportableSessions(options?: ListImportableSessionsOptions): Promise<ImportableProviderSession[]>;
@@ -127,6 +128,8 @@ export declare class OpenCodeAgentClient implements AgentClient {
127
128
  getDiagnostic(): Promise<{
128
129
  diagnostic: string;
129
130
  }>;
131
+ private fetchModelsFromClient;
132
+ private fetchModesFromClient;
130
133
  private assertConfig;
131
134
  private populateModelContextWindowCache;
132
135
  }
@@ -1,4 +1,3 @@
1
- import { homedir } from "node:os";
2
1
  import { createPathEquivalenceMatcher } from "../../../utils/path.js";
3
2
  import pLimit from "p-limit";
4
3
  import { z } from "zod";
@@ -11,7 +10,7 @@ import { execCommand } from "../../../utils/spawn.js";
11
10
  import { buildToolCallDisplayModel } from "@getpaseo/protocol/tool-call-display";
12
11
  import { mapOpencodeToolCall } from "./opencode/tool-call-mapper.js";
13
12
  import { OpenCodeServerManager } from "./opencode/server-manager.js";
14
- import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, buildBinaryDiagnosticRows, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
13
+ import { formatProviderDiagnostic, formatProviderDiagnosticError, buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
15
14
  import { runProviderTurn } from "./provider-runner.js";
16
15
  import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
17
16
  import { composeSystemPromptParts } from "../system-prompt.js";
@@ -398,14 +397,14 @@ function mapOpenCodeAgentToMode(agent) {
398
397
  };
399
398
  }
400
399
  function mergeOpenCodeModes(discoveredModes) {
401
- const modesById = new Map(DEFAULT_MODES.map((mode) => [mode.id, mode]));
402
- for (const mode of discoveredModes) {
403
- if (mode.id === OPENCODE_LEGACY_FULL_ACCESS_MODE_ID) {
404
- continue;
405
- }
406
- modesById.set(mode.id, mode);
400
+ const filtered = discoveredModes.filter((mode) => mode.id !== OPENCODE_LEGACY_FULL_ACCESS_MODE_ID);
401
+ // When discovery returns results, trust them exactly — don't inject hardcoded
402
+ // defaults that the user may have intentionally disabled in their OpenCode config.
403
+ // Fall back to DEFAULT_MODES only when discovery produced nothing.
404
+ if (filtered.length > 0) {
405
+ return sortOpenCodeModes(filtered);
407
406
  }
408
- return sortOpenCodeModes(Array.from(modesById.values()));
407
+ return sortOpenCodeModes([...DEFAULT_MODES]);
409
408
  }
410
409
  function sortOpenCodeModes(modes) {
411
410
  const order = new Map(DEFAULT_MODES.map((mode, index) => [mode.id, index]));
@@ -880,7 +879,9 @@ export class OpenCodeAgentClient {
880
879
  this.runtimeSettings = runtimeSettings;
881
880
  this.runtime =
882
881
  deps.runtime ??
883
- new ProductionOpenCodeRuntime(OpenCodeServerManager.getInstance(this.logger, runtimeSettings));
882
+ new ProductionOpenCodeRuntime(OpenCodeServerManager.getInstance(this.logger, runtimeSettings, {
883
+ managedProcesses: deps.managedProcesses,
884
+ }));
884
885
  }
885
886
  async createSession(config, launchContext, options) {
886
887
  const openCodeConfig = this.assertConfig(config);
@@ -938,70 +939,17 @@ export class OpenCodeAgentClient {
938
939
  throw error;
939
940
  }
940
941
  }
941
- async listModels(options) {
942
- const acquisition = await this.runtime.acquireServer({ force: options.force });
943
- const { url } = acquisition.server;
944
- const client = this.runtime.createClient({
945
- baseUrl: url,
946
- directory: options.cwd,
947
- });
948
- try {
949
- // Background model discovery can be legitimately slow while OpenCode refreshes
950
- // provider state, so allow longer than turn execution paths.
951
- const response = await openCodeMetadataLimit(() => withTimeout(client.provider.list({ directory: options.cwd }), OPENCODE_PROVIDER_LIST_TIMEOUT_MS, `OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`));
952
- if (response.error) {
953
- throw new Error(`Failed to fetch OpenCode providers: ${JSON.stringify(response.error)}`);
954
- }
955
- const providers = response.data;
956
- if (!providers) {
957
- return [];
958
- }
959
- const connectedProviderIds = new Set(providers.connected);
960
- // Providers with source "api" are managed by the OpenCode console/subscription (e.g. Pi
961
- // coding agent). They do not appear in `connected` (which only lists env/config providers)
962
- // but are fully usable — OpenCode authenticates them internally via the console session.
963
- const isAccessible = (provider) => connectedProviderIds.has(provider.id) || provider.source === "api";
964
- // Fail fast if no providers are accessible at all
965
- if (!providers.all.some(isAccessible)) {
966
- throw new Error("OpenCode has no connected providers. Please authenticate with at least one provider " +
967
- "(e.g., openai, anthropic), set appropriate environment variables (e.g., OPENAI_API_KEY), " +
968
- "or log in to OpenCode Go via the console.");
969
- }
970
- const models = [];
971
- this.modelContextWindows.clear();
972
- for (const provider of providers.all) {
973
- if (!isAccessible(provider)) {
974
- continue;
975
- }
976
- for (const [modelId, model] of Object.entries(provider.models)) {
977
- const definition = buildOpenCodeModelDefinition(provider, modelId, model);
978
- const contextWindowMaxTokens = extractOpenCodeModelContextWindow(model);
979
- if (contextWindowMaxTokens !== undefined) {
980
- this.modelContextWindows.set(buildOpenCodeModelLookupKey(provider.id, modelId), contextWindowMaxTokens);
981
- }
982
- models.push(definition);
983
- }
984
- }
985
- return models;
986
- }
987
- finally {
988
- acquisition.release();
989
- }
990
- }
991
- async listModes(options) {
942
+ async fetchCatalog(options) {
992
943
  const acquisition = await this.runtime.acquireServer({ force: options.force });
993
944
  const { url } = acquisition.server;
994
945
  const directory = options.cwd;
995
946
  const client = this.runtime.createClient({ baseUrl: url, directory });
996
947
  try {
997
- const response = await openCodeMetadataLimit(() => withTimeout(client.app.agents({ directory }), 10000, "OpenCode app.agents timed out after 10s"));
998
- if (response.error || !response.data) {
999
- return DEFAULT_MODES;
1000
- }
1001
- const discovered = response.data
1002
- .filter(isSelectableOpenCodeAgent)
1003
- .map(mapOpenCodeAgentToMode);
1004
- return mergeOpenCodeModes(discovered);
948
+ const [models, modes] = await Promise.all([
949
+ this.fetchModelsFromClient(client, directory),
950
+ this.fetchModesFromClient(client, directory),
951
+ ]);
952
+ return { models, modes };
1005
953
  }
1006
954
  finally {
1007
955
  acquisition.release();
@@ -1092,17 +1040,6 @@ export class OpenCodeAgentClient {
1092
1040
  defaultBinary: "opencode",
1093
1041
  });
1094
1042
  const availability = await checkProviderLaunchAvailable(launch);
1095
- const available = availability.available;
1096
- let serverStatus = "Not running";
1097
- let modelsValue = "Not checked";
1098
- let status = formatDiagnosticStatus(available);
1099
- try {
1100
- const { url } = await this.runtime.ensureServerRunning();
1101
- serverStatus = `Running (${url})`;
1102
- }
1103
- catch (error) {
1104
- serverStatus = `Unavailable (${toDiagnosticErrorMessage(error)})`;
1105
- }
1106
1043
  let authValue = "Not checked";
1107
1044
  const authCommand = availability.available
1108
1045
  ? (availability.resolvedPath ?? launch.command)
@@ -1120,37 +1057,13 @@ export class OpenCodeAgentClient {
1120
1057
  authValue = `Error - ${toDiagnosticErrorMessage(error)}`;
1121
1058
  }
1122
1059
  }
1123
- if (available) {
1124
- try {
1125
- const models = await this.listModels({ cwd: homedir(), force: false });
1126
- modelsValue = String(models.length);
1127
- }
1128
- catch (error) {
1129
- modelsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
1130
- status = formatDiagnosticStatus(available, {
1131
- source: "model fetch",
1132
- cause: error,
1133
- });
1134
- }
1135
- if (!modelsValue.startsWith("Error -")) {
1136
- try {
1137
- await this.listModes({ cwd: homedir(), force: false });
1138
- }
1139
- catch (error) {
1140
- status = formatDiagnosticStatus(available, {
1141
- source: "mode fetch",
1142
- cause: error,
1143
- });
1144
- }
1145
- }
1146
- }
1147
1060
  return {
1148
1061
  diagnostic: formatProviderDiagnostic("OpenCode", [
1062
+ ...(await buildCommandResolutionDiagnosticRows(launch, {
1063
+ knownBinaryNames: ["opencode"],
1064
+ })),
1149
1065
  ...(await buildBinaryDiagnosticRows(launch, availability)),
1150
- { label: "Server", value: serverStatus },
1151
1066
  { label: "Auth", value: authValue },
1152
- { label: "Models", value: modelsValue },
1153
- { label: "Status", value: status },
1154
1067
  ]),
1155
1068
  };
1156
1069
  }
@@ -1160,6 +1073,47 @@ export class OpenCodeAgentClient {
1160
1073
  };
1161
1074
  }
1162
1075
  }
1076
+ async fetchModelsFromClient(client, directory) {
1077
+ const response = await openCodeMetadataLimit(() => withTimeout(client.provider.list({ directory }), OPENCODE_PROVIDER_LIST_TIMEOUT_MS, `OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`));
1078
+ if (response.error) {
1079
+ throw new Error(`Failed to fetch OpenCode providers: ${JSON.stringify(response.error)}`);
1080
+ }
1081
+ const providers = response.data;
1082
+ if (!providers) {
1083
+ return [];
1084
+ }
1085
+ const connectedProviderIds = new Set(providers.connected);
1086
+ const isAccessible = (provider) => connectedProviderIds.has(provider.id) || provider.source === "api";
1087
+ if (!providers.all.some(isAccessible)) {
1088
+ throw new Error("OpenCode has no connected providers. Please authenticate with at least one provider " +
1089
+ "(e.g., openai, anthropic), set appropriate environment variables (e.g., OPENAI_API_KEY), " +
1090
+ "or log in to OpenCode Go via the console.");
1091
+ }
1092
+ const models = [];
1093
+ this.modelContextWindows.clear();
1094
+ for (const provider of providers.all) {
1095
+ if (!isAccessible(provider)) {
1096
+ continue;
1097
+ }
1098
+ for (const [modelId, model] of Object.entries(provider.models)) {
1099
+ const definition = buildOpenCodeModelDefinition(provider, modelId, model);
1100
+ const contextWindowMaxTokens = extractOpenCodeModelContextWindow(model);
1101
+ if (contextWindowMaxTokens !== undefined) {
1102
+ this.modelContextWindows.set(buildOpenCodeModelLookupKey(provider.id, modelId), contextWindowMaxTokens);
1103
+ }
1104
+ models.push(definition);
1105
+ }
1106
+ }
1107
+ return models;
1108
+ }
1109
+ async fetchModesFromClient(client, directory) {
1110
+ const response = await openCodeMetadataLimit(() => withTimeout(client.app.agents({ directory }), 10000, "OpenCode app.agents timed out after 10s"));
1111
+ if (response.error || !response.data) {
1112
+ return DEFAULT_MODES;
1113
+ }
1114
+ const discovered = response.data.filter(isSelectableOpenCodeAgent).map(mapOpenCodeAgentToMode);
1115
+ return mergeOpenCodeModes(discovered);
1116
+ }
1163
1117
  assertConfig(config) {
1164
1118
  if (config.provider !== "opencode") {
1165
1119
  throw new Error(`OpenCodeAgentClient received config for provider '${config.provider}'`);
@@ -1,6 +1,6 @@
1
1
  import type { Logger } from "pino";
2
2
  import { z } from "zod";
3
- import { type AgentCapabilityFlags, type AgentClient, type AgentLaunchContext, type AgentMode, type AgentModelDefinition, type AgentPermissionRequest, type AgentPermissionResponse, type AgentPersistenceHandle, type AgentPromptInput, type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, type AgentSession, type AgentSessionConfig, type AgentSlashCommand, type AgentStreamEvent, type ImportableProviderSession, type ImportProviderSessionContext, type ImportProviderSessionInput, type ListImportableSessionsOptions, type ListModesOptions, type ListModelsOptions } from "../../agent-sdk-types.js";
3
+ import { type AgentCapabilityFlags, type AgentClient, type AgentLaunchContext, type AgentMode, type AgentModelDefinition, type AgentPermissionRequest, type AgentPermissionResponse, type AgentPersistenceHandle, type AgentPromptInput, type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, type AgentSession, type AgentSessionConfig, type AgentSlashCommand, type AgentStreamEvent, type FetchCatalogOptions, type ImportableProviderSession, type ImportProviderSessionContext, type ImportProviderSessionInput, type ListImportableSessionsOptions, type ProviderCatalog } from "../../agent-sdk-types.js";
4
4
  import { type ProviderRuntimeSettings } from "../../provider-launch-config.js";
5
5
  import type { PiRuntime, PiRuntimeSession } from "./runtime.js";
6
6
  import type { PiSessionState } from "./rpc-types.js";
@@ -112,8 +112,7 @@ export declare class PiRpcAgentClient implements AgentClient {
112
112
  constructor(options: PiRpcAgentClientOptions);
113
113
  createSession(config: AgentSessionConfig, launchContext?: AgentLaunchContext): Promise<AgentSession>;
114
114
  resumeSession(handle: AgentPersistenceHandle, overrides?: Partial<AgentSessionConfig>, _launchContext?: AgentLaunchContext): Promise<AgentSession>;
115
- listModels(options: ListModelsOptions): Promise<AgentModelDefinition[]>;
116
- listModes(_options: ListModesOptions): Promise<AgentMode[]>;
115
+ fetchCatalog(options: FetchCatalogOptions): Promise<ProviderCatalog>;
117
116
  listImportableSessions(options?: ListImportableSessionsOptions): Promise<ImportableProviderSession[]>;
118
117
  importSession(input: ImportProviderSessionInput, context: ImportProviderSessionContext): Promise<import("../../agent-sdk-types.js").ImportedProviderSession>;
119
118
  isAvailable(): Promise<boolean>;
@@ -8,7 +8,7 @@ import { runProviderTurn } from "../provider-runner.js";
8
8
  import { checkProviderLaunchAvailable, resolveProviderLaunch, } from "../../provider-launch-config.js";
9
9
  import { renderPromptAttachmentAsText } from "../../prompt-attachments.js";
10
10
  import { composeSystemPromptParts } from "../../system-prompt.js";
11
- import { buildBinaryDiagnosticRows, formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, toDiagnosticErrorMessage, } from "../diagnostic-utils.js";
11
+ import { buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, formatProviderDiagnostic, formatProviderDiagnosticError, toDiagnosticErrorMessage, } from "../diagnostic-utils.js";
12
12
  import { getUserMessageText, streamPiHistory, } from "./history-mapper.js";
13
13
  import { PiCliRuntime } from "./cli-runtime.js";
14
14
  import { revertPiConversation } from "./rewind.js";
@@ -17,6 +17,7 @@ import { mapToolDetail, parseToolArgs, parseToolResult, resolveToolCallName, } f
17
17
  const PI_PROVIDER = "pi";
18
18
  const DEFAULT_PI_THINKING_LEVEL = "medium";
19
19
  const PI_BINARY_COMMAND = process.env.PI_COMMAND ?? process.env.PI_ACP_PI_COMMAND ?? "pi";
20
+ const PI_CATALOG_REQUEST_TIMEOUT_MS = 120000;
20
21
  const PASEO_PI_TREE_EXTENSION_COMMAND = "paseo_tree";
21
22
  const PASEO_PI_CAPTURE_EXTENSION_COMMAND = "paseo_capture_entries";
22
23
  const PASEO_PI_ENTRY_CAPTURE_MARKER = "PASEO_ENTRY_CAPTURE";
@@ -1537,18 +1538,16 @@ export class PiRpcAgentClient {
1537
1538
  throw error;
1538
1539
  }
1539
1540
  }
1540
- async listModels(options) {
1541
+ async fetchCatalog(options) {
1541
1542
  const runtimeSession = await this.runtime.startSession({ cwd: options.cwd });
1542
1543
  try {
1543
- return transformPiModels((await runtimeSession.getAvailableModels()).map(mapPiModel));
1544
+ const models = transformPiModels((await runtimeSession.getAvailableModels(PI_CATALOG_REQUEST_TIMEOUT_MS)).map(mapPiModel));
1545
+ return { models, modes: [] };
1544
1546
  }
1545
1547
  finally {
1546
1548
  await runtimeSession.close();
1547
1549
  }
1548
1550
  }
1549
- async listModes(_options) {
1550
- return [];
1551
- }
1552
1551
  async listImportableSessions(options) {
1553
1552
  return await listPiImportableSessions({
1554
1553
  ...options,
@@ -1567,81 +1566,30 @@ export class PiRpcAgentClient {
1567
1566
  });
1568
1567
  }
1569
1568
  async isAvailable() {
1570
- const launch = await this.resolvePiLaunch();
1571
- const availability = await checkProviderLaunchAvailable(launch);
1572
- if (!availability.available) {
1573
- return false;
1574
- }
1575
- const runtimeSession = await this.runtime.startSession({ cwd: homedir() }).catch(() => null);
1576
- if (!runtimeSession) {
1577
- return false;
1578
- }
1579
1569
  try {
1580
- return (await runtimeSession.getAvailableModels()).length > 0;
1570
+ const launch = await this.resolvePiLaunch();
1571
+ const availability = await checkProviderLaunchAvailable(launch);
1572
+ return availability.available;
1581
1573
  }
1582
1574
  catch {
1583
1575
  return false;
1584
1576
  }
1585
- finally {
1586
- await runtimeSession.close().catch(() => undefined);
1587
- }
1588
1577
  }
1589
1578
  async getDiagnostic() {
1590
1579
  try {
1591
1580
  const launch = await this.resolvePiLaunch();
1592
1581
  const availability = await checkProviderLaunchAvailable(launch);
1593
- const available = availability.available;
1594
1582
  const authConfigPath = join(homedir(), ".pi", "agent", "auth.json");
1595
- let modelsValue = "Not checked";
1596
- let configuredProvidersValue = "none";
1597
- let mcpToolsValue = "Not checked";
1598
- let status = formatDiagnosticStatus(available);
1599
- if (availability.available) {
1600
- const runtimeSession = await this.runtime
1601
- .startSession({ cwd: homedir() })
1602
- .catch((error) => {
1603
- status = formatDiagnosticStatus(false, {
1604
- source: "startup",
1605
- cause: error,
1606
- });
1607
- return null;
1608
- });
1609
- if (runtimeSession) {
1610
- try {
1611
- const models = await runtimeSession.getAvailableModels();
1612
- modelsValue = String(models.length);
1613
- const configuredProviders = Array.from(new Set(models.map((model) => model.provider))).sort();
1614
- configuredProvidersValue =
1615
- configuredProviders.length > 0 ? configuredProviders.join(", ") : "none";
1616
- const commands = await runtimeSession.getCommands();
1617
- mcpToolsValue = commands.some(isPiMcpAdapterCommand)
1618
- ? "yes (pi-mcp-adapter loaded)"
1619
- : "no (install pi-mcp-adapter)";
1620
- }
1621
- catch (error) {
1622
- modelsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
1623
- mcpToolsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
1624
- status = formatDiagnosticStatus(available, {
1625
- source: "model fetch",
1626
- cause: error,
1627
- });
1628
- }
1629
- finally {
1630
- await runtimeSession.close().catch(() => undefined);
1631
- }
1632
- }
1633
- }
1634
1583
  return {
1635
1584
  diagnostic: formatProviderDiagnostic("Pi", [
1585
+ ...(await buildCommandResolutionDiagnosticRows(launch, {
1586
+ knownBinaryNames: [launch.command],
1587
+ })),
1636
1588
  ...(await buildBinaryDiagnosticRows(launch, availability)),
1637
- { label: "Configured providers", value: configuredProvidersValue },
1638
1589
  {
1639
1590
  label: "Auth config (~/.pi/agent/auth.json)",
1640
1591
  value: existsSync(authConfigPath) ? "found" : "not found",
1641
1592
  },
1642
- { label: "Models", value: modelsValue },
1643
- { label: "Paseo MCP tools", value: mcpToolsValue },
1644
- { label: "Status", value: status },
1645
1593
  ]),
1646
1594
  };
1647
1595
  }
@@ -95,8 +95,8 @@ class PiCliRuntimeSession {
95
95
  const data = (await this.request({ type: "get_messages" }));
96
96
  return data.messages ?? [];
97
97
  }
98
- async getAvailableModels() {
99
- const data = (await this.request({ type: "get_available_models" }));
98
+ async getAvailableModels(timeoutMs) {
99
+ const data = (await this.request({ type: "get_available_models" }, timeoutMs));
100
100
  return data.models ?? [];
101
101
  }
102
102
  async setModel(provider, modelId) {
@@ -33,7 +33,7 @@ export interface PiRuntimeSession {
33
33
  abort(): Promise<void>;
34
34
  getState(): Promise<PiSessionState>;
35
35
  getMessages(): Promise<PiAgentMessage[]>;
36
- getAvailableModels(): Promise<PiModel[]>;
36
+ getAvailableModels(timeoutMs?: number): Promise<PiModel[]>;
37
37
  setModel(provider: string, modelId: string): Promise<PiModel>;
38
38
  setThinkingLevel(level: string): Promise<void>;
39
39
  getSessionStats(): Promise<PiSessionStats>;