@getpaseo/server 0.10.0 → 0.10.2

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 (43) hide show
  1. package/dist/server/server/agent/agent-manager.js +1 -0
  2. package/dist/server/server/agent/provider-registry.js +8 -0
  3. package/dist/server/server/agent/providers/claude/model-manifest.d.ts +9 -1
  4. package/dist/server/server/agent/providers/claude/model-manifest.js +10 -1
  5. package/dist/server/server/agent/providers/codex/rewind.d.ts +1 -0
  6. package/dist/server/server/agent/providers/codex/rewind.js +13 -14
  7. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +3 -1
  8. package/dist/server/server/agent/providers/codex-app-server-agent.js +14 -13
  9. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +16 -6
  10. package/dist/server/server/agent/providers/opencode/v2/api.d.ts +1 -1
  11. package/dist/server/server/agent/providers/opencode/v2/permissions.js +3 -2
  12. package/dist/server/server/agent/providers/opencode/v2/session.d.ts +2 -1
  13. package/dist/server/server/agent/providers/opencode/v2/session.js +43 -20
  14. package/dist/server/server/agent/providers/opencode/v2/turns.d.ts +20 -5
  15. package/dist/server/server/agent/providers/opencode/v2/turns.js +168 -72
  16. package/dist/server/server/agent/providers/opencode/v2/usage.d.ts +21 -0
  17. package/dist/server/server/agent/providers/opencode/v2/usage.js +48 -0
  18. package/dist/server/server/agent/providers/opencode-agent.js +24 -0
  19. package/dist/server/server/agent/tools/paseo-tools.js +6 -4
  20. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js → desktop-attachment-bridge-9b9ae7a48611e7619ae3dcfc5573a81d.js} +1 -1
  21. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js.br → desktop-attachment-bridge-9b9ae7a48611e7619ae3dcfc5573a81d.js.br} +0 -0
  22. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js.gz → desktop-attachment-bridge-9b9ae7a48611e7619ae3dcfc5573a81d.js.gz} +0 -0
  23. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-d5d3402a752965d3037191484127123b.js → desktop-attachment-store-850db74e505f3e0b8091b8a319bf6d95.js} +1 -1
  24. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-d5d3402a752965d3037191484127123b.js.br → desktop-attachment-store-850db74e505f3e0b8091b8a319bf6d95.js.br} +0 -0
  25. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-850db74e505f3e0b8091b8a319bf6d95.js.gz +0 -0
  26. package/dist/server/web-ui/_expo/static/js/web/{index-136fa41d4adb7ec5c6392839cc354136.js → index-48b52108bb348a6334547c3c03400d50.js} +463 -462
  27. package/dist/server/web-ui/_expo/static/js/web/index-48b52108bb348a6334547c3c03400d50.js.br +0 -0
  28. package/dist/server/web-ui/_expo/static/js/web/{index-136fa41d4adb7ec5c6392839cc354136.js.gz → index-48b52108bb348a6334547c3c03400d50.js.gz} +0 -0
  29. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js → indexeddb-attachment-store-8233162f2e9e360c5680127250516411.js} +1 -1
  30. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js.br → indexeddb-attachment-store-8233162f2e9e360c5680127250516411.js.br} +0 -0
  31. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-8233162f2e9e360c5680127250516411.js.gz +0 -0
  32. package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js → native-file-attachment-store-eb2d324071f66d94742cf09a46103e22.js} +3 -3
  33. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-eb2d324071f66d94742cf09a46103e22.js.br +0 -0
  34. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-eb2d324071f66d94742cf09a46103e22.js.gz +0 -0
  35. package/dist/server/web-ui/index.html +1 -1
  36. package/dist/server/web-ui/index.html.br +0 -0
  37. package/dist/server/web-ui/index.html.gz +0 -0
  38. package/package.json +6 -6
  39. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d5d3402a752965d3037191484127123b.js.gz +0 -0
  40. package/dist/server/web-ui/_expo/static/js/web/index-136fa41d4adb7ec5c6392839cc354136.js.br +0 -0
  41. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js.gz +0 -0
  42. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js.br +0 -0
  43. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js.gz +0 -0
@@ -1204,6 +1204,7 @@ export class AgentManager {
1204
1204
  if (agent.runtimeInfo) {
1205
1205
  agent.runtimeInfo = { ...agent.runtimeInfo, model: normalizedModelId };
1206
1206
  }
1207
+ this.refreshSessionPersistence(agent);
1207
1208
  this.touchUpdatedAt(agent);
1208
1209
  this.emitState(agent);
1209
1210
  }
@@ -262,6 +262,8 @@ function wrapClientProvider(provider, inner, profileModels, additionalModels, pr
262
262
  const listImportableSessions = inner.listImportableSessions?.bind(inner);
263
263
  const importSession = inner.importSession?.bind(inner);
264
264
  const listFeatures = inner.listFeatures?.bind(inner);
265
+ const archiveNativeSession = inner.archiveNativeSession?.bind(inner);
266
+ const unarchiveNativeSession = inner.unarchiveNativeSession?.bind(inner);
265
267
  return {
266
268
  provider,
267
269
  capabilities: inner.capabilities,
@@ -332,6 +334,12 @@ function wrapClientProvider(provider, inner, profileModels, additionalModels, pr
332
334
  };
333
335
  }
334
336
  : undefined,
337
+ archiveNativeSession: archiveNativeSession
338
+ ? async (handle) => await archiveNativeSession({ ...handle, provider: inner.provider })
339
+ : undefined,
340
+ unarchiveNativeSession: unarchiveNativeSession
341
+ ? async (handle) => await unarchiveNativeSession({ ...handle, provider: inner.provider })
342
+ : undefined,
335
343
  getCatalogCacheKey: inner.getCatalogCacheKey?.bind(inner),
336
344
  isAvailable: (signal, options) => inner.isAvailable(signal, options),
337
345
  getDiagnostic: inner.getDiagnostic?.bind(inner),
@@ -53,10 +53,18 @@ export declare const CLAUDE_MODEL_MANIFEST: readonly [{
53
53
  readonly effortLevels: readonly ["low", "medium", "high", "xhigh", "max"];
54
54
  readonly supportsThinkingDisabled: true;
55
55
  readonly supportsFastMode: true;
56
+ }, {
57
+ readonly id: "claude-sonnet-5-5";
58
+ readonly label: "Sonnet 5.5";
59
+ readonly description: "Sonnet 5.5 · Best for everyday tasks";
60
+ readonly minimumClaudeCodeVersion: "2.1.284";
61
+ readonly defaultThinkingOptionId: "medium";
62
+ readonly contextWindowMaxTokens: 1000000;
63
+ readonly effortLevels: readonly ["low", "medium", "high", "xhigh", "max"];
56
64
  }, {
57
65
  readonly id: "claude-sonnet-5";
58
66
  readonly label: "Sonnet 5";
59
- readonly description: "Sonnet 5 · Best for everyday tasks";
67
+ readonly description: "Sonnet 5 · Previous release";
60
68
  readonly contextWindowMaxTokens: 200000;
61
69
  readonly effortLevels: readonly ["low", "medium", "high", "xhigh", "max"];
62
70
  readonly supportsThinkingDisabled: true;
@@ -71,10 +71,19 @@ export const CLAUDE_MODEL_MANIFEST = [
71
71
  supportsThinkingDisabled: true,
72
72
  supportsFastMode: true,
73
73
  },
74
+ {
75
+ id: "claude-sonnet-5-5",
76
+ label: "Sonnet 5.5",
77
+ description: "Sonnet 5.5 · Best for everyday tasks",
78
+ minimumClaudeCodeVersion: "2.1.284",
79
+ defaultThinkingOptionId: "medium",
80
+ contextWindowMaxTokens: 1000000,
81
+ effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
82
+ },
74
83
  {
75
84
  id: "claude-sonnet-5",
76
85
  label: "Sonnet 5",
77
- description: "Sonnet 5 · Best for everyday tasks",
86
+ description: "Sonnet 5 · Previous release",
78
87
  contextWindowMaxTokens: 200000,
79
88
  effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
80
89
  supportsThinkingDisabled: true,
@@ -18,6 +18,7 @@ export declare function revertCodexConversation(input: {
18
18
  cwd?: string | null;
19
19
  model?: string | null;
20
20
  serviceTier?: string | null;
21
+ config?: Record<string, unknown> | null;
21
22
  userMessageTurns: CodexUserMessageTurnIndex;
22
23
  setThreadId: (threadId: string) => void | Promise<void>;
23
24
  }): Promise<void>;
@@ -38,33 +38,32 @@ export async function revertCodexConversation(input) {
38
38
  if (numTurns < 0) {
39
39
  throw new Error(`Codex user message ${input.messageId} is outside the current thread`);
40
40
  }
41
+ // Codex does not carry the parent thread's config into a fork; without it the
42
+ // forked thread falls back to the default model provider.
43
+ const forkParams = {
44
+ threadId: input.threadId,
45
+ cwd: input.cwd ?? null,
46
+ model: input.model ?? null,
47
+ serviceTier: input.serviceTier ?? null,
48
+ ...(input.config ? { config: input.config } : {}),
49
+ excludeTurns: false,
50
+ persistExtendedHistory: true,
51
+ };
41
52
  const historyMode = await readCodexThreadHistoryMode(input.client, input.threadId);
42
53
  if (historyMode === "paginated") {
43
54
  if (!targetTurn.turnId) {
44
55
  throw new Error(`Codex could not find the turn containing user message ${input.messageId}`);
45
56
  }
46
57
  const forked = await forkCodexThread(input.client, {
47
- threadId: input.threadId,
58
+ ...forkParams,
48
59
  beforeTurnId: targetTurn.turnId,
49
- cwd: input.cwd ?? null,
50
- model: input.model ?? null,
51
- serviceTier: input.serviceTier ?? null,
52
- excludeTurns: false,
53
- persistExtendedHistory: true,
54
60
  });
55
61
  await input.setThreadId(forked.thread.id);
56
62
  return;
57
63
  }
58
64
  // Fork is non-destructive: the old thread file stays on disk and remains
59
65
  // recoverable with `codex resume <old-uuid>` if the rewind target was wrong.
60
- const forked = await forkCodexThread(input.client, {
61
- threadId: input.threadId,
62
- cwd: input.cwd ?? null,
63
- model: input.model ?? null,
64
- serviceTier: input.serviceTier ?? null,
65
- excludeTurns: false,
66
- persistExtendedHistory: true,
67
- });
66
+ const forked = await forkCodexThread(input.client, forkParams);
68
67
  const forkedThreadId = forked.thread.id;
69
68
  // Codex rollback is chat-only by design. File edits from rewound turns stay
70
69
  // on disk; a future file primitive would be a separate capability.
@@ -21,6 +21,7 @@ interface CodexAppServerAgentDeps {
21
21
  extends: string;
22
22
  };
23
23
  customCodexConfig?: CodexCustomProviderConfig | null;
24
+ codexHome?: string;
24
25
  _createCodexClient?: (child: ChildProcessWithoutNullStreams, logger: Logger, getTraceContext: () => CodexAppServerTraceContext) => CodexAppServerClientLike;
25
26
  resolveSlashCommandInvocation?: (prompt: AgentPromptInput) => Promise<{
26
27
  commandName: string;
@@ -31,7 +32,7 @@ export declare function normalizeCodexOutputSchema(schema: unknown): Record<stri
31
32
  export declare function codexMicrosoftStoreBinaryCandidates(packageRoot: string, entries: Dirent[]): string[];
32
33
  export declare function findCodexMicrosoftStoreBinary(): Promise<string | null>;
33
34
  export declare function findDefaultCodexBinary(): Promise<string | null>;
34
- export declare function listCodexSkills(cwd: string, workspaceGitService?: Pick<WorkspaceGitService, "resolveRepoRoot">): Promise<AgentSlashCommand[]>;
35
+ export declare function listCodexSkills(cwd: string, codexHome: string, workspaceGitService?: Pick<WorkspaceGitService, "resolveRepoRoot">): Promise<AgentSlashCommand[]>;
35
36
  export declare function toAgentUsage(tokenUsage: unknown): AgentUsage | undefined;
36
37
  export declare function planStepsToMarkdown(steps: Array<{
37
38
  step: string;
@@ -126,6 +127,7 @@ export declare class CodexAppServerAgentSession implements AgentSession {
126
127
  private readonly logger;
127
128
  private readonly config;
128
129
  private readonly asyncQuestions;
130
+ private readonly codexHome;
129
131
  private currentMode;
130
132
  private hasWorkflowModeOverride;
131
133
  private readonly providerOptions;
@@ -321,8 +321,8 @@ async function checkCodexLaunchAvailable(launch) {
321
321
  resolvePath: findDefaultCodexBinary,
322
322
  });
323
323
  }
324
- function resolveCodexHomeDir() {
325
- return process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
324
+ function resolveCodexHomeDir(env) {
325
+ return env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
326
326
  }
327
327
  function decodeEscapedChar(next) {
328
328
  if (next === "n")
@@ -423,8 +423,7 @@ function parseFrontMatter(markdown) {
423
423
  }
424
424
  return { frontMatter, body };
425
425
  }
426
- async function listCodexCustomPrompts() {
427
- const codexHome = resolveCodexHomeDir();
426
+ async function listCodexCustomPrompts(codexHome) {
428
427
  const promptsDir = path.join(codexHome, "prompts");
429
428
  let entries;
430
429
  try {
@@ -457,7 +456,7 @@ async function listCodexCustomPrompts() {
457
456
  const commands = parsedCommands.filter((cmd) => cmd !== null);
458
457
  return commands.sort((a, b) => a.name.localeCompare(b.name));
459
458
  }
460
- export async function listCodexSkills(cwd, workspaceGitService) {
459
+ export async function listCodexSkills(cwd, codexHome, workspaceGitService) {
461
460
  const candidates = [];
462
461
  candidates.push(path.join(cwd, ".codex", "skills"));
463
462
  const repoRoot = workspaceGitService
@@ -467,7 +466,7 @@ export async function listCodexSkills(cwd, workspaceGitService) {
467
466
  candidates.push(path.join(path.dirname(cwd), ".codex", "skills"));
468
467
  candidates.push(path.join(repoRoot, ".codex", "skills"));
469
468
  }
470
- candidates.push(path.join(resolveCodexHomeDir(), "skills"));
469
+ candidates.push(path.join(codexHome, "skills"));
471
470
  const candidateReads = await Promise.all(candidates.map(async (dir) => {
472
471
  let entries;
473
472
  try {
@@ -2532,6 +2531,7 @@ export class CodexAppServerAgentSession {
2532
2531
  this.providerOptions = CodexProviderOptionsSchema.parse(config.providerOptions ?? {});
2533
2532
  this.config = config;
2534
2533
  this.asyncQuestions = new CodexAsyncQuestions(resumeHandle?.metadata?.asyncQuestions);
2534
+ this.codexHome = deps.codexHome ?? resolveCodexHomeDir(process.env);
2535
2535
  this.config.thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
2536
2536
  if (this.config.featureValues?.fast_mode && codexModelSupportsFastMode(this.config.model)) {
2537
2537
  this.serviceTier = "fast";
@@ -3023,8 +3023,7 @@ export class CodexAppServerAgentSession {
3023
3023
  async buildCommandPromptInput(commandName, args) {
3024
3024
  if (commandName.startsWith("prompts:")) {
3025
3025
  const promptName = commandName.slice("prompts:".length);
3026
- const codexHome = resolveCodexHomeDir();
3027
- const promptPath = path.join(codexHome, "prompts", `${promptName}.md`);
3026
+ const promptPath = path.join(this.codexHome, "prompts", `${promptName}.md`);
3028
3027
  const raw = await fs.readFile(promptPath, "utf8");
3029
3028
  const parsed = parseFrontMatter(raw);
3030
3029
  return expandCodexCustomPrompt(parsed.body, args);
@@ -3688,6 +3687,7 @@ export class CodexAppServerAgentSession {
3688
3687
  cwd: this.config.cwd ?? null,
3689
3688
  model: this.config.model ?? null,
3690
3689
  serviceTier: this.serviceTier,
3690
+ config: this.buildCodexInnerConfig(),
3691
3691
  userMessageTurns: this.codexUserMessageTurns(),
3692
3692
  setThreadId: async (threadId) => {
3693
3693
  this.currentThreadId = threadId;
@@ -3783,7 +3783,7 @@ export class CodexAppServerAgentSession {
3783
3783
  this.client = null;
3784
3784
  }
3785
3785
  async listCommands() {
3786
- const prompts = await listCodexCustomPrompts();
3786
+ const prompts = await listCodexCustomPrompts(this.codexHome);
3787
3787
  if (this.connectionState === "disconnected") {
3788
3788
  await this.connect();
3789
3789
  }
@@ -3797,7 +3797,7 @@ export class CodexAppServerAgentSession {
3797
3797
  kind: "skill",
3798
3798
  }));
3799
3799
  const fallbackSkills = this.cachedSkills === null
3800
- ? await listCodexSkills(this.config.cwd, this.deps.workspaceGitService)
3800
+ ? await listCodexSkills(this.config.cwd, this.codexHome, this.deps.workspaceGitService)
3801
3801
  : [];
3802
3802
  const builtin = [
3803
3803
  {
@@ -5602,9 +5602,10 @@ export class CodexAppServerAgentClient {
5602
5602
  this.goalsEnabledPromise = null;
5603
5603
  this.autoReviewEnabledPromise = null;
5604
5604
  }
5605
- sessionDeps() {
5605
+ sessionDeps(launchEnv) {
5606
5606
  return {
5607
5607
  ...this.deps,
5608
+ codexHome: resolveCodexHomeDir(buildCodexAppServerEnv(this.runtimeSettings, launchEnv)),
5608
5609
  customCodexConfig: this.customProviderConfig(),
5609
5610
  };
5610
5611
  }
@@ -5690,7 +5691,7 @@ export class CodexAppServerAgentClient {
5690
5691
  const sessionConfig = { ...config, provider: CODEX_PROVIDER };
5691
5692
  const goalsEnabled = await this.resolveGoalsEnabled();
5692
5693
  const autoReviewEnabled = await this.resolveAutoReviewEnabled();
5693
- const session = new CodexAppServerAgentSession(sessionConfig, null, this.logger, () => this.spawnAppServer(launchContext?.env, { goalsEnabled, agentId: launchContext?.agentId }), this.sessionDeps(), options?.persistSession === false, goalsEnabled, autoReviewEnabled, launchContext?.agentId);
5694
+ const session = new CodexAppServerAgentSession(sessionConfig, null, this.logger, () => this.spawnAppServer(launchContext?.env, { goalsEnabled, agentId: launchContext?.agentId }), this.sessionDeps(launchContext?.env), options?.persistSession === false, goalsEnabled, autoReviewEnabled, launchContext?.agentId);
5694
5695
  await session.connect();
5695
5696
  return session;
5696
5697
  }
@@ -5704,7 +5705,7 @@ export class CodexAppServerAgentClient {
5704
5705
  };
5705
5706
  const goalsEnabled = await this.resolveGoalsEnabled();
5706
5707
  const autoReviewEnabled = await this.resolveAutoReviewEnabled();
5707
- const session = new CodexAppServerAgentSession(merged, handle, this.logger, () => this.spawnAppServer(launchContext?.env, { goalsEnabled, agentId: launchContext?.agentId }), this.sessionDeps(), false, goalsEnabled, autoReviewEnabled, launchContext?.agentId, options?.purpose ?? "interactive");
5708
+ const session = new CodexAppServerAgentSession(merged, handle, this.logger, () => this.spawnAppServer(launchContext?.env, { goalsEnabled, agentId: launchContext?.agentId }), this.sessionDeps(launchContext?.env), false, goalsEnabled, autoReviewEnabled, launchContext?.agentId, options?.purpose ?? "interactive");
5708
5709
  await session.connect();
5709
5710
  return session;
5710
5711
  }
@@ -164,6 +164,14 @@ function deriveOpencodeTaskDetail(input, output, error, metadata) {
164
164
  actions: [],
165
165
  };
166
166
  }
167
+ function toOpencodeEditInput(value) {
168
+ return {
169
+ filePath: value.filePath,
170
+ oldString: nonEmptyString(value.oldString),
171
+ newString: nonEmptyString(value.newString),
172
+ unifiedDiff: undefined,
173
+ };
174
+ }
167
175
  const OpencodeEditInputSchema = z.union([
168
176
  z
169
177
  .object({
@@ -172,12 +180,12 @@ const OpencodeEditInputSchema = z.union([
172
180
  newString: z.string().optional(),
173
181
  })
174
182
  .passthrough()
175
- .transform((value) => ({
176
- filePath: value.filePath,
177
- oldString: nonEmptyString(value.oldString),
178
- newString: nonEmptyString(value.newString),
179
- unifiedDiff: undefined,
180
- })),
183
+ .transform(toOpencodeEditInput),
184
+ // OpenCode v2's edit tool names the file `path`.
185
+ z
186
+ .object({ path: z.string(), oldString: z.string(), newString: z.string() })
187
+ .passthrough()
188
+ .transform((value) => toOpencodeEditInput({ ...value, filePath: value.path })),
181
189
  ToolEditInputSchema,
182
190
  ]);
183
191
  const OpencodeEditOutputSchema = z.union([z.string().transform(() => null), ToolEditOutputSchema]);
@@ -199,6 +207,8 @@ const OpencodeKnownToolDetailSchema = z.union([
199
207
  toolDetailBranchByToolName("edit", OpencodeEditInputSchema, OpencodeEditOutputSchema, toEditToolDetail),
200
208
  toolDetailBranchByToolName("apply_patch", OpencodeEditInputSchema, OpencodeEditOutputSchema, toEditToolDetail),
201
209
  toolDetailBranchByToolName("apply_patch", OpencodeApplyPatchTextInputSchema, z.unknown(), (input) => toEditToolDetail(input, null)),
210
+ // OpenCode v2 reports apply_patch as `patch`.
211
+ toolDetailBranchByToolName("patch", OpencodeApplyPatchTextInputSchema, z.unknown(), (input) => toEditToolDetail(input, null)),
202
212
  toolDetailBranchByToolName("apply_diff", OpencodeEditInputSchema, OpencodeEditOutputSchema, toEditToolDetail),
203
213
  toolDetailBranchByToolName("search", ToolSearchInputSchema, z.unknown(), (input) => toSearchToolDetail({ input, toolName: "search" })),
204
214
  toolDetailBranchByToolName("grep", ToolSearchInputSchema, OpencodeGrepOutputSchema, (input, output) => toSearchToolDetail({ input, output, toolName: "grep" })),
@@ -1,7 +1,7 @@
1
1
  import type { OpenCodeClient } from "@opencode/client";
2
2
  export interface V2Api {
3
3
  server: OpenCodeClient["server"];
4
- session: Pick<OpenCodeClient["session"], "create" | "get" | "list" | "active" | "remove" | "switchAgent" | "switchModel" | "environment" | "instructions" | "prompt" | "command" | "compact" | "wait" | "interrupt" | "revert" | "log"> & {
4
+ session: Pick<OpenCodeClient["session"], "create" | "get" | "list" | "active" | "remove" | "switchAgent" | "switchModel" | "environment" | "instructions" | "prompt" | "command" | "compact" | "interrupt" | "revert" | "log"> & {
5
5
  form: Pick<OpenCodeClient["session"]["form"], "list" | "reply" | "cancel">;
6
6
  };
7
7
  plugin: Pick<OpenCodeClient["plugin"], "list">;
@@ -94,10 +94,11 @@ export class SessionPermissions {
94
94
  title: form.title,
95
95
  input: {
96
96
  questions: form.fields.map((field) => ({
97
- header: field.key,
98
- question: field.title ?? field.key,
97
+ header: field.title ?? field.key,
98
+ question: field.description ?? field.title ?? field.key,
99
99
  options: "options" in field ? field.options : undefined,
100
100
  multiple: field.type === "multiselect",
101
+ allowOther: "custom" in field && field.custom === true,
101
102
  })),
102
103
  },
103
104
  };
@@ -20,6 +20,7 @@ export declare class OpenCodeV2Session implements AgentSession {
20
20
  private readonly permissions;
21
21
  private readonly turns;
22
22
  private readonly children;
23
+ private readonly usage;
23
24
  private stream;
24
25
  private streamAbort;
25
26
  private exited;
@@ -63,7 +64,7 @@ export declare class OpenCodeV2Session implements AgentSession {
63
64
  describePersistence(): AgentPersistenceHandle;
64
65
  run(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<import("../../../agent-sdk-types.js").AgentRunResult>;
65
66
  startTurn(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<{
66
- turnId: `${string}-${string}-${string}-${string}-${string}`;
67
+ turnId: string;
67
68
  }>;
68
69
  steerActiveTurn(prompt: AgentPromptInput, options: SteerActiveTurnOptions): Promise<SteerResult>;
69
70
  interrupt(): Promise<void>;
@@ -13,6 +13,7 @@ import { features } from "./configuration.js";
13
13
  import { commands } from "./commands.js";
14
14
  import { messages } from "./history.js";
15
15
  import { SessionPermissions } from "./permissions.js";
16
+ import { SessionUsage } from "./usage.js";
16
17
  export class OpenCodeV2Session {
17
18
  constructor(connection, info, config, logger, persist, requiresPaseoPlugin, unbind, bindChild, acquire, moved) {
18
19
  this.connection = connection;
@@ -57,11 +58,19 @@ export class OpenCodeV2Session {
57
58
  await this.reconcile();
58
59
  return { info: this.info, history: this.history };
59
60
  },
61
+ reportReconciliationError: (error) => this.logger.warn({ error: toDiagnosticErrorMessage(error) }, "OpenCode turn reconciliation failed; retrying"),
60
62
  clearPermissions: async () => {
61
63
  for (const request of this.permissions.list())
62
64
  await this.permissions.respondToPermission(request.id, { behavior: "deny" });
63
65
  },
64
66
  });
67
+ this.usage = new SessionUsage({
68
+ client: () => this.client,
69
+ cwd: config.cwd,
70
+ info: () => this.info,
71
+ emit: (event) => this.emit(event),
72
+ reportError: (error) => this.logger.warn({ error: toDiagnosticErrorMessage(error) }, "OpenCode context usage update failed"),
73
+ });
65
74
  }
66
75
  get id() {
67
76
  return this.info.id;
@@ -239,24 +248,42 @@ export class OpenCodeV2Session {
239
248
  }
240
249
  async setModel(model) {
241
250
  await this.reconnectIfExited();
242
- const selected = model
243
- ? modelRef(model, this.config.thinkingOptionId)
244
- : (await this.client.model.default({ location: { directory: this.config.cwd } })).data;
251
+ const location = { directory: this.config.cwd };
252
+ const selected = model ? modelRef(model) : (await this.client.model.default({ location })).data;
245
253
  if (!selected)
246
254
  throw new Error("OpenCode has no default model");
247
- await this.client.session.switchModel({
248
- sessionID: this.id,
249
- model: {
250
- id: selected.id,
251
- providerID: selected.providerID,
252
- variant: this.config.thinkingOptionId,
253
- },
254
- });
255
+ const catalog = await this.client.model.list({ location });
256
+ const target = catalog.data.find((entry) => entry.enabled && entry.providerID === selected.providerID && entry.id === selected.id);
257
+ if (!target)
258
+ throw new Error(`OpenCode model unavailable: ${selected.providerID}/${selected.id}`);
259
+ const retainedVariant = this.config.thinkingOptionId;
260
+ const variant = retainedVariant !== "default" && target.variants.some((entry) => entry.id === retainedVariant)
261
+ ? retainedVariant
262
+ : undefined;
263
+ const nextModel = {
264
+ id: selected.id,
265
+ providerID: selected.providerID,
266
+ ...(variant ? { variant } : {}),
267
+ };
268
+ await this.client.session.switchModel({ sessionID: this.id, model: nextModel });
269
+ this.info.model = nextModel;
255
270
  this.config.model = model ?? undefined;
271
+ this.config.thinkingOptionId = variant;
272
+ this.emit({
273
+ type: "thinking_option_changed",
274
+ provider: "opencode",
275
+ thinkingOptionId: variant ?? null,
276
+ });
256
277
  this.emit({
257
278
  type: "model_changed",
258
279
  provider: "opencode",
259
- runtimeInfo: await this.getRuntimeInfo(),
280
+ runtimeInfo: {
281
+ provider: "opencode",
282
+ sessionId: this.id,
283
+ model: `${selected.providerID}/${selected.id}`,
284
+ modeId: this.info.agent ?? null,
285
+ thinkingOptionId: variant ?? null,
286
+ },
260
287
  });
261
288
  }
262
289
  async setThinkingOption(variant) {
@@ -359,9 +386,7 @@ export class OpenCodeV2Session {
359
386
  await this.client.session.environment({ sessionID: this.id, variables: this.launchEnv });
360
387
  await this.reconcile();
361
388
  await this.children.reconcile(this.id);
362
- const active = await this.client.session.active();
363
- if (active[this.id])
364
- this.turns.observeActiveTurn();
389
+ await this.turns.reconcile();
365
390
  }
366
391
  scheduleReconcile() {
367
392
  if (this.refreshTimer || this.closed)
@@ -404,12 +429,9 @@ export class OpenCodeV2Session {
404
429
  return;
405
430
  }
406
431
  this.permissions.observe(event);
407
- if (event.type === "session.execution.failed")
408
- this.turns.executionError = event.data.error.message;
432
+ this.usage.observe(event);
433
+ this.turns.observe(event);
409
434
  this.scheduleReconcile();
410
- if (event.type !== "session.execution.started" || this.turns.id)
411
- return;
412
- this.turns.observeActiveTurn();
413
435
  }
414
436
  async consume(ready, fail) {
415
437
  const signal = AbortSignal.any([this.abort.signal, this.streamAbort.signal]);
@@ -448,6 +470,7 @@ export class OpenCodeV2Session {
448
470
  return;
449
471
  this.closed = true;
450
472
  this.abort.abort();
473
+ this.turns.close();
451
474
  this.streamAbort.abort();
452
475
  if (this.refreshTimer)
453
476
  clearTimeout(this.refreshTimer);
@@ -1,4 +1,4 @@
1
- import type { SessionInfo, SessionMessageInfo } from "@opencode/client";
1
+ import type { OpenCodeEvent, SessionInfo, SessionMessageInfo } from "@opencode/client";
2
2
  import type { AgentPromptInput, AgentRunOptions, AgentStreamEvent, SteerActiveTurnOptions, SteerResult } from "../../../agent-sdk-types.js";
3
3
  import type { V2Api } from "./api.js";
4
4
  interface TurnSnapshot {
@@ -13,28 +13,43 @@ interface TurnOptions {
13
13
  emit(event: AgentStreamEvent): void;
14
14
  reconcile(): Promise<TurnSnapshot>;
15
15
  clearPermissions(): Promise<void>;
16
+ reportReconciliationError(error: unknown): void;
16
17
  }
17
18
  export declare class SessionTurns {
18
19
  private readonly options;
19
20
  private turn;
20
21
  private stopping;
21
22
  private stopFailed;
22
- executionError: string | null;
23
+ private revision;
24
+ private execution;
25
+ private sequence;
26
+ private refresh;
27
+ private dirty;
28
+ private retryTimer;
23
29
  constructor(options: TurnOptions);
24
30
  get id(): string | undefined;
25
31
  get hasStructuredOutput(): boolean;
26
32
  fail(error: Error): void;
33
+ private release;
34
+ close(): void;
35
+ private createTurn;
27
36
  startTurn(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<{
28
- turnId: `${string}-${string}-${string}-${string}-${string}`;
37
+ turnId: string;
29
38
  }>;
30
39
  private submit;
40
+ observe(event: OpenCodeEvent): void;
41
+ private requestReconciliation;
42
+ private scheduleRecovery;
43
+ reconcile(): Promise<void>;
44
+ private drainReconciliation;
45
+ private reconcileTurn;
31
46
  private dispatch;
32
47
  private finish;
33
- private readExecutionError;
48
+ private readExecution;
34
49
  private promptInput;
35
50
  steerActiveTurn(prompt: AgentPromptInput, options: SteerActiveTurnOptions): Promise<SteerResult>;
36
51
  interrupt(): Promise<void>;
37
- observeActiveTurn(): void;
52
+ private observeActiveTurn;
38
53
  }
39
54
  export {};
40
55
  //# sourceMappingURL=turns.d.ts.map