remote-codex 0.1.10 → 0.11.1

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 (46) hide show
  1. package/apps/supervisor-api/dist/chunk-6M32PPHZ.js +24507 -0
  2. package/apps/supervisor-api/dist/chunk-7AA2MFXK.js +24499 -0
  3. package/apps/supervisor-api/dist/chunk-HKBFCPHH.js +24511 -0
  4. package/apps/supervisor-api/dist/index.js +12525 -28436
  5. package/apps/supervisor-api/dist/worker-index.d.ts +2 -0
  6. package/apps/supervisor-api/dist/worker-index.js +33 -0
  7. package/apps/supervisor-web/dist/assets/{highlighted-body-OFNGDK62-CyMcatlD.js → highlighted-body-OFNGDK62-p31aS0f0.js} +1 -1
  8. package/apps/supervisor-web/dist/assets/index-BiuFei_K.css +32 -0
  9. package/apps/supervisor-web/dist/assets/index-D1R9CUnx.js +2161 -0
  10. package/apps/supervisor-web/dist/assets/{xterm-DbYWMNQ0.js → xterm-D92BViLH.js} +1 -1
  11. package/apps/supervisor-web/dist/index.html +2 -2
  12. package/package.json +2 -3
  13. package/packages/agent-runtime/src/index.ts +4 -0
  14. package/packages/agent-runtime/src/management-errors.ts +11 -0
  15. package/packages/agent-runtime/src/model-pricing.ts +325 -0
  16. package/packages/agent-runtime/src/registry.ts +19 -4
  17. package/packages/agent-runtime/src/runtime-errors.ts +97 -0
  18. package/packages/agent-runtime/src/types.ts +36 -3
  19. package/packages/agent-runtime/src/unavailable-runtime.ts +169 -0
  20. package/packages/claude/src/historyItems.ts +41 -5
  21. package/packages/claude/src/runtimeAdapter.test.ts +117 -6
  22. package/packages/claude/src/runtimeAdapter.ts +421 -65
  23. package/packages/codex/src/historyItems.test.ts +137 -0
  24. package/packages/codex/src/historyItems.ts +135 -17
  25. package/packages/codex/src/hookHistory.test.ts +59 -0
  26. package/packages/codex/src/index.ts +7 -0
  27. package/packages/codex/src/local-session-store.ts +390 -0
  28. package/packages/codex/src/management/codex-management-service.ts +454 -0
  29. package/packages/codex/src/management/codexHostConfig.test.ts +88 -0
  30. package/packages/codex/src/management/codexHostConfig.ts +188 -0
  31. package/packages/codex/src/management/errors.ts +20 -0
  32. package/packages/codex/src/modelPricing.test.ts +235 -0
  33. package/packages/codex/src/modelPricing.ts +9 -0
  34. package/packages/codex/src/runtime-errors.test.ts +72 -0
  35. package/packages/codex/src/runtime-errors.ts +37 -0
  36. package/packages/codex/src/runtimeAdapter.ts +15 -0
  37. package/packages/codex/src/thread-title.ts +1 -0
  38. package/packages/opencode/src/historyItems.test.ts +504 -0
  39. package/packages/opencode/src/historyItems.ts +896 -0
  40. package/packages/opencode/src/index.ts +2 -0
  41. package/packages/opencode/src/runtimeAdapter.test.ts +1444 -0
  42. package/packages/opencode/src/runtimeAdapter.ts +1473 -0
  43. package/packages/shared/src/agent-providers.ts +56 -0
  44. package/packages/shared/src/index.ts +240 -35
  45. package/apps/supervisor-web/dist/assets/index-BlAhoIuq.js +0 -379
  46. package/apps/supervisor-web/dist/assets/index-DI0NRNgr.css +0 -32
@@ -1,29 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { EventEmitter } from 'node:events';
3
+ import { execFile } from 'node:child_process';
3
4
  import fs from 'node:fs/promises';
5
+ import { createRequire } from 'node:module';
4
6
  import path from 'node:path';
5
-
6
- import {
7
- getSessionInfo as sdkGetSessionInfo,
8
- getSessionMessages as sdkGetSessionMessages,
9
- listSessions as sdkListSessions,
10
- query as sdkQuery,
11
- } from '@anthropic-ai/claude-agent-sdk';
12
- import type {
13
- GetSessionInfoOptions,
14
- GetSessionMessagesOptions,
15
- ListSessionsOptions,
16
- McpServerStatus,
17
- ModelInfo,
18
- Options as ClaudeQueryOptions,
19
- PermissionMode,
20
- Query,
21
- SandboxSettings,
22
- SDKMessage,
23
- SDKUserMessage,
24
- SDKSessionInfo,
25
- SessionMessage,
26
- } from '@anthropic-ai/claude-agent-sdk';
7
+ import { pathToFileURL } from 'node:url';
8
+ import { promisify } from 'node:util';
27
9
 
28
10
  import type {
29
11
  AgentActionQuestion,
@@ -43,12 +25,19 @@ import type {
43
25
  AgentSessionSummary,
44
26
  AgentTurn,
45
27
  InterruptAgentTurnInput,
28
+ ReadAgentSessionOptions,
46
29
  ResumeAgentSessionInput,
47
30
  StartAgentSessionInput,
48
31
  StartAgentSessionResult,
49
32
  StartAgentTurnInput,
50
33
  } from '../../agent-runtime/src/index';
51
- import { AgentRuntimeError } from '../../agent-runtime/src/index';
34
+ import type {
35
+ AgentBackendInstallationDto,
36
+ } from '../../shared/src/index';
37
+ import {
38
+ AgentRuntimeError,
39
+ markTransientAgentHistoryItem,
40
+ } from '../../agent-runtime/src/index';
52
41
  import {
53
42
  assistantMessageToHistoryItems,
54
43
  buildAgentTurn,
@@ -67,14 +56,135 @@ import {
67
56
  userMessageToHistoryItem,
68
57
  } from './historyItems';
69
58
 
70
- type ClaudeQueryFunction = typeof sdkQuery;
71
- type ClaudeListSessionsFunction = typeof sdkListSessions;
72
- type ClaudeGetSessionMessagesFunction = typeof sdkGetSessionMessages;
73
- type ClaudeGetSessionInfoFunction = typeof sdkGetSessionInfo;
74
- type ClaudePromptInput = Parameters<ClaudeQueryFunction>[0]['prompt'];
75
- type ClaudeMessageContent = SDKUserMessage['message']['content'];
59
+ const execFileAsync = promisify(execFile);
60
+
61
+ type ClaudePromptInput = string | AsyncIterable<SDKUserMessage>;
62
+ type ClaudeMessageContent =
63
+ | string
64
+ | Array<{
65
+ type: 'text';
66
+ text: string;
67
+ } | {
68
+ type: 'image';
69
+ source: {
70
+ type: 'base64';
71
+ media_type: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
72
+ data: string;
73
+ };
74
+ }>;
76
75
  type ClaudeMessageContentBlock = Exclude<ClaudeMessageContent, string>[number];
77
-
76
+ type ClaudeQueryFunction = (input: {
77
+ prompt: ClaudePromptInput;
78
+ options: ClaudeQueryOptions;
79
+ }) => Query;
80
+ type ClaudeListSessionsFunction = (_options: ListSessionsOptions) => Promise<SDKSessionInfo[]>;
81
+ type ClaudeGetSessionMessagesFunction = (
82
+ sessionId: string,
83
+ options: GetSessionMessagesOptions,
84
+ ) => Promise<SessionMessage[]>;
85
+ type ClaudeGetSessionInfoFunction = (
86
+ sessionId: string,
87
+ options: GetSessionInfoOptions,
88
+ ) => Promise<SDKSessionInfo | null>;
89
+ interface ClaudeSdkModule {
90
+ query: ClaudeQueryFunction;
91
+ listSessions: ClaudeListSessionsFunction;
92
+ getSessionMessages: ClaudeGetSessionMessagesFunction;
93
+ getSessionInfo: ClaudeGetSessionInfoFunction;
94
+ }
95
+ interface Query extends AsyncIterable<SDKMessage> {
96
+ close(): void;
97
+ interrupt(): Promise<void>;
98
+ supportedModels(): Promise<ModelInfo[]>;
99
+ mcpServerStatus(): Promise<McpServerStatus[]>;
100
+ }
101
+ interface SDKMessage {
102
+ type: string;
103
+ subtype?: string;
104
+ session_id?: string;
105
+ cwd?: string;
106
+ model?: string;
107
+ uuid?: string;
108
+ message?: unknown;
109
+ event?: unknown;
110
+ parent_tool_use_id?: string | null;
111
+ tool_use_result?: unknown;
112
+ tool_use_id?: string;
113
+ tool_name?: string;
114
+ elapsed_time_seconds?: number;
115
+ usage?: unknown;
116
+ modelUsage?: unknown;
117
+ errors?: string[];
118
+ stop_reason?: string;
119
+ }
120
+ interface SDKUserMessage {
121
+ type: 'user';
122
+ message: {
123
+ role: 'user';
124
+ content: ClaudeMessageContent;
125
+ };
126
+ parent_tool_use_id: string | null;
127
+ }
128
+ interface SDKSessionInfo {
129
+ sessionId: string;
130
+ cwd?: string;
131
+ firstPrompt?: string | null;
132
+ summary?: string | null;
133
+ customTitle?: string | null;
134
+ createdAt?: number | null;
135
+ lastModified?: number | null;
136
+ }
137
+ type SessionMessage = SDKMessage;
138
+ interface ModelInfo {
139
+ value: string;
140
+ displayName: string;
141
+ description: string;
142
+ supportedEffortLevels?: string[];
143
+ supportsEffort?: boolean;
144
+ }
145
+ type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
146
+ interface SandboxSettings {
147
+ enabled: boolean;
148
+ autoAllowBashIfSandboxed?: boolean;
149
+ allowUnsandboxedCommands?: boolean;
150
+ filesystem?: {
151
+ allowWrite?: string[];
152
+ denyWrite?: string[];
153
+ };
154
+ }
155
+ interface ClaudeQueryOptions {
156
+ includeHookEvents?: boolean;
157
+ permissionMode?: PermissionMode;
158
+ thinking?: {
159
+ type: string;
160
+ display: string;
161
+ };
162
+ env?: Record<string, string | undefined>;
163
+ cwd?: string;
164
+ sandbox?: SandboxSettings;
165
+ model?: string;
166
+ betas?: string[];
167
+ effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
168
+ resume?: string;
169
+ includePartialMessages?: boolean;
170
+ maxTurns?: number;
171
+ tools?: unknown;
172
+ allowDangerouslySkipPermissions?: boolean;
173
+ pathToClaudeCodeExecutable?: string;
174
+ }
175
+ interface GetSessionInfoOptions {}
176
+ interface GetSessionMessagesOptions {
177
+ includeSystemMessages?: boolean;
178
+ }
179
+ interface ListSessionsOptions {}
180
+ interface McpServerStatus {
181
+ name: string;
182
+ status: string;
183
+ tools?: Array<{
184
+ name: string;
185
+ description?: string | null;
186
+ }>;
187
+ }
78
188
  export interface ClaudeRuntimeAdapterOptions {
79
189
  home: string;
80
190
  command?: string;
@@ -87,6 +197,7 @@ export interface ClaudeRuntimeAdapterOptions {
87
197
  listSessions?: ClaudeListSessionsFunction;
88
198
  getSessionMessages?: ClaudeGetSessionMessagesFunction;
89
199
  getSessionInfo?: ClaudeGetSessionInfoFunction;
200
+ sdk?: ClaudeSdkModule;
90
201
  }
91
202
 
92
203
  interface ActiveClaudeTurn {
@@ -125,6 +236,22 @@ function mimeTypeForImagePath(filePath: string) {
125
236
  }
126
237
  }
127
238
 
239
+ function extensionForImageMediaType(mediaType: string | null | undefined) {
240
+ switch (mediaType?.toLowerCase()) {
241
+ case 'image/jpeg':
242
+ case 'image/jpg':
243
+ return 'jpg';
244
+ case 'image/png':
245
+ return 'png';
246
+ case 'image/gif':
247
+ return 'gif';
248
+ case 'image/webp':
249
+ return 'webp';
250
+ default:
251
+ return null;
252
+ }
253
+ }
254
+
128
255
  function resolvePromptAssetPath(assetPath: string, cwd: string | null | undefined) {
129
256
  if (!cwd) {
130
257
  return null;
@@ -139,6 +266,10 @@ function resolvePromptAssetPath(assetPath: string, cwd: string | null | undefine
139
266
  return null;
140
267
  }
141
268
 
269
+ function safeAssetFilePart(value: string) {
270
+ return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || randomUUID();
271
+ }
272
+
142
273
  async function* singleUserMessage(content: ClaudeMessageContent): AsyncIterable<SDKUserMessage> {
143
274
  yield {
144
275
  type: 'user',
@@ -865,7 +996,7 @@ function queryOptionsForRuntime(
865
996
  if (input.maxTurns !== undefined) {
866
997
  options.maxTurns = input.maxTurns;
867
998
  }
868
- if (input.tools !== undefined) {
999
+ if (input.tools !== undefined && (!Array.isArray(input.tools) || input.tools.length > 0)) {
869
1000
  options.tools = input.tools;
870
1001
  }
871
1002
  if (permission.allowDangerouslySkipPermissions) {
@@ -877,9 +1008,19 @@ function queryOptionsForRuntime(
877
1008
 
878
1009
  export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
879
1010
  readonly provider = 'claude' as const;
880
- readonly displayName = 'Claude';
1011
+ readonly displayName = 'Claude Code';
881
1012
  readonly description = 'Local Claude Code Agent SDK runtime.';
882
1013
  readonly capabilities = claudeCapabilities;
1014
+ readonly installation: AgentBackendInstallationDto = {
1015
+ packageName: '@anthropic-ai/claude-agent-sdk',
1016
+ installed: false,
1017
+ installedVersion: null,
1018
+ latestVersion: null,
1019
+ installCommand: 'npm install -g @anthropic-ai/claude-code @anthropic-ai/claude-agent-sdk',
1020
+ updateCommand: 'npm install -g @anthropic-ai/claude-code@latest @anthropic-ai/claude-agent-sdk@latest',
1021
+ busy: false,
1022
+ lastError: null,
1023
+ };
883
1024
  readonly managementSchema: AgentRuntimeManagementSchema = {
884
1025
  hostConfigFiles: [],
885
1026
  toolboxItems: [
@@ -899,10 +1040,10 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
899
1040
  lastError: null,
900
1041
  restartCount: 0,
901
1042
  };
902
- private readonly queryFactory: ClaudeQueryFunction;
903
- private readonly listSessionsFn: ClaudeListSessionsFunction;
904
- private readonly getSessionMessagesFn: ClaudeGetSessionMessagesFunction;
905
- private readonly getSessionInfoFn: ClaudeGetSessionInfoFunction;
1043
+ private queryFactory: ClaudeQueryFunction;
1044
+ private listSessionsFn: ClaudeListSessionsFunction;
1045
+ private getSessionMessagesFn: ClaudeGetSessionMessagesFunction;
1046
+ private getSessionInfoFn: ClaudeGetSessionInfoFunction;
906
1047
  private readonly activeTurns = new Map<string, ActiveClaudeTurn>();
907
1048
  private readonly knownSessionIds = new Set<string>();
908
1049
  private readonly sessionCwds = new Map<string, string>();
@@ -910,13 +1051,15 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
910
1051
  private readonly sessionApprovalModes = new Map<string, StartAgentSessionInput['approvalMode']>();
911
1052
  private readonly liveUserPrompts = new Map<string, Map<string, string>>();
912
1053
  private readonly clientApp: string;
1054
+ private sdkLoadError: string | null = null;
913
1055
 
914
1056
  constructor(private readonly options: ClaudeRuntimeAdapterOptions) {
915
1057
  super();
916
- this.queryFactory = options.query ?? sdkQuery;
917
- this.listSessionsFn = options.listSessions ?? sdkListSessions;
918
- this.getSessionMessagesFn = options.getSessionMessages ?? sdkGetSessionMessages;
919
- this.getSessionInfoFn = options.getSessionInfo ?? sdkGetSessionInfo;
1058
+ const sdk = options.sdk;
1059
+ this.queryFactory = options.query ?? sdk?.query ?? this.unavailableQueryFactory.bind(this);
1060
+ this.listSessionsFn = options.listSessions ?? sdk?.listSessions ?? this.unavailableListSessions.bind(this);
1061
+ this.getSessionMessagesFn = options.getSessionMessages ?? sdk?.getSessionMessages ?? this.unavailableGetSessionMessages.bind(this);
1062
+ this.getSessionInfoFn = options.getSessionInfo ?? sdk?.getSessionInfo ?? this.unavailableGetSessionInfo.bind(this);
920
1063
  this.clientApp = [
921
1064
  options.clientInfo?.name ?? 'remote-codex-supervisor',
922
1065
  options.clientInfo?.version,
@@ -929,6 +1072,29 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
929
1072
 
930
1073
  async start() {
931
1074
  await fs.mkdir(this.options.home, { recursive: true });
1075
+ if (!this.options.query && !this.options.sdk) {
1076
+ try {
1077
+ const sdk = await this.loadSdk();
1078
+ this.queryFactory = sdk.query;
1079
+ this.listSessionsFn = sdk.listSessions;
1080
+ this.getSessionMessagesFn = sdk.getSessionMessages;
1081
+ this.getSessionInfoFn = sdk.getSessionInfo;
1082
+ this.sdkLoadError = null;
1083
+ this.installation.installed = true;
1084
+ this.installation.lastError = null;
1085
+ } catch (error) {
1086
+ this.sdkLoadError = errorMessage(error);
1087
+ this.installation.installed = false;
1088
+ this.installation.lastError = this.sdkLoadError;
1089
+ this.status = {
1090
+ ...this.status,
1091
+ state: 'stopped',
1092
+ lastError: `Claude Code SDK is not installed or could not be loaded. ${this.sdkLoadError}`,
1093
+ };
1094
+ this.emit('status', this.getStatus());
1095
+ return;
1096
+ }
1097
+ }
932
1098
  this.status = {
933
1099
  ...this.status,
934
1100
  state: 'ready',
@@ -992,7 +1158,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
992
1158
 
993
1159
  async readSession(
994
1160
  providerSessionId: string,
995
- _options: { limit?: number; beforeTurnId?: string | null } = {},
1161
+ options: ReadAgentSessionOptions = {},
996
1162
  ): Promise<AgentSessionDetail> {
997
1163
  const [info, messages] = await this.withClaudeConfigEnv(async () => Promise.all([
998
1164
  this.getSessionInfoFn(providerSessionId, {} as GetSessionInfoOptions),
@@ -1015,14 +1181,19 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1015
1181
  rawSession: null,
1016
1182
  };
1017
1183
 
1018
- const turns = this.sessionMessagesToTurns(messages);
1184
+ const cwd = summary.cwd || this.sessionCwds.get(providerSessionId) || '';
1185
+ const historyAssetContext = {
1186
+ workspacePath: options.workspacePath || cwd,
1187
+ ...(options.localThreadId ? { localThreadId: options.localThreadId } : {}),
1188
+ };
1189
+ const turns = await this.sessionMessagesToTurns(messages, historyAssetContext);
1019
1190
  const activeTurn = [...this.activeTurns.values()].find(
1020
1191
  (turn) => turn.providerSessionId === providerSessionId,
1021
1192
  );
1022
1193
 
1023
1194
  return {
1024
1195
  ...summary,
1025
- cwd: summary.cwd || this.sessionCwds.get(providerSessionId) || '',
1196
+ cwd,
1026
1197
  turns: this.reconcileActiveTranscriptTurn(providerSessionId, turns, activeTurn),
1027
1198
  };
1028
1199
  }
@@ -1052,9 +1223,12 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1052
1223
  for await (const message of query) {
1053
1224
  rawMessages.push(message);
1054
1225
  if (message.type === 'system' && message.subtype === 'init') {
1055
- providerSessionId = message.session_id;
1056
- model = displayClaudeModel(input.model, message.model ?? model);
1057
- this.sessionCwds.set(providerSessionId, message.cwd);
1226
+ const sessionId = message.session_id;
1227
+ if (sessionId) {
1228
+ providerSessionId = sessionId;
1229
+ model = displayClaudeModel(input.model, message.model ?? model);
1230
+ this.sessionCwds.set(sessionId, message.cwd ?? input.cwd);
1231
+ }
1058
1232
  } else if ('session_id' in message && typeof message.session_id === 'string') {
1059
1233
  providerSessionId ??= message.session_id;
1060
1234
  }
@@ -1382,8 +1556,10 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1382
1556
  this.captureUsage(state, message);
1383
1557
 
1384
1558
  if (message.type === 'system' && message.subtype === 'init') {
1385
- this.sessionCwds.set(message.session_id, message.cwd);
1386
- this.sessionModels.set(message.session_id, message.model);
1559
+ if (message.session_id) {
1560
+ this.sessionCwds.set(message.session_id, message.cwd ?? '');
1561
+ this.sessionModels.set(message.session_id, message.model ?? null);
1562
+ }
1387
1563
  return;
1388
1564
  }
1389
1565
 
@@ -1430,15 +1606,15 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1430
1606
  if (delta) {
1431
1607
  const existing = state.items.get(delta.itemId);
1432
1608
  const nextItem: AgentHistoryItem = existing?.kind === 'agentMessage'
1433
- ? {
1609
+ ? markTransientAgentHistoryItem({
1434
1610
  ...existing,
1435
1611
  text: `${existing.text}${delta.delta}`,
1436
- }
1437
- : {
1612
+ })
1613
+ : markTransientAgentHistoryItem({
1438
1614
  id: delta.itemId,
1439
1615
  kind: 'agentMessage',
1440
1616
  text: delta.delta,
1441
- };
1617
+ });
1442
1618
  addOrUpdateItem(state, nextItem);
1443
1619
  this.emitRuntimeEvent({
1444
1620
  type: 'output.delta',
@@ -1523,10 +1699,15 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1523
1699
  }
1524
1700
 
1525
1701
  if (message.type === 'tool_progress') {
1526
- if (!state.items.has(message.tool_use_id)) {
1702
+ const toolUseId = message.tool_use_id;
1703
+ const toolName = message.tool_name;
1704
+ if (!toolUseId || !toolName) {
1705
+ return;
1706
+ }
1707
+ if (!state.items.has(toolUseId)) {
1527
1708
  const item = toolUseToHistoryItem({
1528
- id: message.tool_use_id,
1529
- name: message.tool_name,
1709
+ id: toolUseId,
1710
+ name: toolName,
1530
1711
  toolInput: {
1531
1712
  elapsed_time_seconds: message.elapsed_time_seconds,
1532
1713
  },
@@ -1536,14 +1717,18 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1536
1717
  addOrUpdateItem(state, item);
1537
1718
  this.emitItem(state, item, 'item.started');
1538
1719
  } else {
1539
- state.suppressedToolUseIds.add(message.tool_use_id);
1720
+ state.suppressedToolUseIds.add(toolUseId);
1540
1721
  }
1541
1722
  }
1542
1723
  return;
1543
1724
  }
1544
1725
 
1545
1726
  if (message.type === 'system' && message.subtype === 'permission_denied') {
1546
- const previous = state.items.get(message.tool_use_id);
1727
+ const toolUseId = message.tool_use_id;
1728
+ if (!toolUseId) {
1729
+ return;
1730
+ }
1731
+ const previous = state.items.get(toolUseId);
1547
1732
  const item: AgentHistoryItem = previous
1548
1733
  ? {
1549
1734
  ...previous,
@@ -1551,10 +1736,10 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1551
1736
  detailText: [previous.detailText ?? previous.text, '', message.message].join('\n'),
1552
1737
  }
1553
1738
  : {
1554
- id: message.tool_use_id,
1739
+ id: toolUseId,
1555
1740
  kind: 'toolCall',
1556
1741
  text: `${message.tool_name} denied`,
1557
- detailText: message.message,
1742
+ detailText: typeof message.message === 'string' ? message.message : null,
1558
1743
  status: 'denied',
1559
1744
  };
1560
1745
  addOrUpdateItem(state, item);
@@ -1620,6 +1805,44 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1620
1805
  this.emit('event', event);
1621
1806
  }
1622
1807
 
1808
+ private async loadSdk(): Promise<ClaudeSdkModule> {
1809
+ try {
1810
+ return await importOptionalPackage('@anthropic-ai/claude-agent-sdk') as unknown as ClaudeSdkModule;
1811
+ } catch (error) {
1812
+ throw new AgentRuntimeError(
1813
+ 'Install Claude Code support with npm install -g @anthropic-ai/claude-agent-sdk, or add @anthropic-ai/claude-agent-sdk to this checkout.',
1814
+ 'claude',
1815
+ 'provider_unavailable',
1816
+ undefined,
1817
+ error,
1818
+ );
1819
+ }
1820
+ }
1821
+
1822
+ private unavailableError() {
1823
+ return new AgentRuntimeError(
1824
+ this.sdkLoadError ?? 'Claude Code SDK is not installed.',
1825
+ 'claude',
1826
+ 'provider_unavailable',
1827
+ );
1828
+ }
1829
+
1830
+ private unavailableQueryFactory(): Query {
1831
+ throw this.unavailableError();
1832
+ }
1833
+
1834
+ private async unavailableListSessions(): Promise<SDKSessionInfo[]> {
1835
+ throw this.unavailableError();
1836
+ }
1837
+
1838
+ private async unavailableGetSessionMessages(): Promise<SessionMessage[]> {
1839
+ throw this.unavailableError();
1840
+ }
1841
+
1842
+ private async unavailableGetSessionInfo(): Promise<SDKSessionInfo | null> {
1843
+ throw this.unavailableError();
1844
+ }
1845
+
1623
1846
  private mapMcpServer(server: McpServerStatus): AgentMcpServer {
1624
1847
  return {
1625
1848
  name: server.name,
@@ -1634,7 +1857,103 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1634
1857
  };
1635
1858
  }
1636
1859
 
1637
- private sessionMessagesToTurns(messages: SessionMessage[]): AgentTurn[] {
1860
+ private async userMessageToHistoryItem(
1861
+ id: string,
1862
+ message: unknown,
1863
+ context: {
1864
+ localThreadId?: string;
1865
+ workspacePath?: string;
1866
+ },
1867
+ ): Promise<AgentHistoryItem> {
1868
+ if (!isRecord(message) || !Array.isArray(message.content)) {
1869
+ return userMessageToHistoryItem(id, message);
1870
+ }
1871
+
1872
+ const parts: string[] = [];
1873
+ for (let index = 0; index < message.content.length; index += 1) {
1874
+ const block = message.content[index];
1875
+ if (!isRecord(block)) {
1876
+ continue;
1877
+ }
1878
+ if (block.type === 'image') {
1879
+ const photoToken = await this.persistHistoryImageBlock({
1880
+ messageId: id,
1881
+ blockIndex: index,
1882
+ block,
1883
+ ...context,
1884
+ });
1885
+ if (photoToken) {
1886
+ parts.push(photoToken);
1887
+ }
1888
+ continue;
1889
+ }
1890
+ if (typeof block.text === 'string') {
1891
+ parts.push(block.text);
1892
+ continue;
1893
+ }
1894
+ if (typeof block.content === 'string') {
1895
+ parts.push(block.content);
1896
+ }
1897
+ }
1898
+
1899
+ return userMessageHistoryItem(id, parts.filter(Boolean).join('\n'));
1900
+ }
1901
+
1902
+ private async persistHistoryImageBlock(input: {
1903
+ messageId: string;
1904
+ blockIndex: number;
1905
+ block: Record<string, unknown>;
1906
+ localThreadId?: string;
1907
+ workspacePath?: string;
1908
+ }): Promise<string | null> {
1909
+ if (!input.localThreadId || !input.workspacePath) {
1910
+ return null;
1911
+ }
1912
+
1913
+ const source = isRecord(input.block.source) ? input.block.source : null;
1914
+ if (!source || source.type !== 'base64') {
1915
+ return null;
1916
+ }
1917
+
1918
+ const data = typeof source.data === 'string' ? source.data : null;
1919
+ if (!data) {
1920
+ return null;
1921
+ }
1922
+
1923
+ const extension = extensionForImageMediaType(
1924
+ typeof source.media_type === 'string' ? source.media_type : null,
1925
+ );
1926
+ if (!extension) {
1927
+ return null;
1928
+ }
1929
+
1930
+ const relativePath = `./.temp/threads/${input.localThreadId}/claude-history-${safeAssetFilePart(input.messageId)}-${input.blockIndex}.${extension}`;
1931
+ const targetPath = path.resolve(input.workspacePath, relativePath);
1932
+ const relativeToWorkspace = path.relative(input.workspacePath, targetPath);
1933
+ if (
1934
+ relativeToWorkspace === '' ||
1935
+ relativeToWorkspace.startsWith('..') ||
1936
+ path.isAbsolute(relativeToWorkspace)
1937
+ ) {
1938
+ return null;
1939
+ }
1940
+
1941
+ try {
1942
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
1943
+ await fs.writeFile(targetPath, Buffer.from(data, 'base64'));
1944
+ return `[PHOTO ${relativePath}]`;
1945
+ } catch {
1946
+ return null;
1947
+ }
1948
+ }
1949
+
1950
+ private async sessionMessagesToTurns(
1951
+ messages: SessionMessage[],
1952
+ context: {
1953
+ localThreadId?: string;
1954
+ workspacePath?: string;
1955
+ } = {},
1956
+ ): Promise<AgentTurn[]> {
1638
1957
  const turns: AgentTurn[] = [];
1639
1958
  let current: {
1640
1959
  providerTurnId: string;
@@ -1703,12 +2022,17 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1703
2022
  items: current.items,
1704
2023
  }));
1705
2024
  }
1706
- const userItem = userMessageToHistoryItem(message.uuid, message.message);
2025
+ const messageUuid = message.uuid ?? randomUUID();
2026
+ const userItem = await this.userMessageToHistoryItem(
2027
+ messageUuid,
2028
+ message.message,
2029
+ context,
2030
+ );
1707
2031
  current = {
1708
- providerTurnId: `claude-turn-${message.uuid}`,
1709
- startedAt: isoFromUuidV7(message.uuid),
2032
+ providerTurnId: `claude-turn-${messageUuid}`,
2033
+ startedAt: isoFromUuidV7(messageUuid),
1710
2034
  items: [userItem],
1711
- itemsById: new Map([[message.uuid, userItem]]),
2035
+ itemsById: new Map([[messageUuid, userItem]]),
1712
2036
  };
1713
2037
  continue;
1714
2038
  }
@@ -1723,7 +2047,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1723
2047
  current?.itemsById.delete(toolUseId);
1724
2048
  }
1725
2049
  for (const item of assistantMessageToHistoryItems({
1726
- messageId: message.uuid,
2050
+ messageId: message.uuid ?? randomUUID(),
1727
2051
  message: message.message,
1728
2052
  })) {
1729
2053
  upsertCurrentItem(item);
@@ -1787,3 +2111,35 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1787
2111
  this.emit('status', this.getStatus());
1788
2112
  }
1789
2113
  }
2114
+
2115
+ async function importOptionalPackage(specifier: string) {
2116
+ const dynamicImport = new Function('specifier', 'return import(specifier);') as (
2117
+ specifier: string,
2118
+ ) => Promise<unknown>;
2119
+ try {
2120
+ return await dynamicImport(specifier);
2121
+ } catch (localError) {
2122
+ const globalRoot = await npmGlobalRoot();
2123
+ if (!globalRoot) {
2124
+ throw localError;
2125
+ }
2126
+ try {
2127
+ const requireFromGlobal = createRequire(path.join(globalRoot, 'remote-codex-global.cjs'));
2128
+ const resolved = requireFromGlobal.resolve(specifier);
2129
+ return await dynamicImport(pathToFileURL(resolved).href);
2130
+ } catch {
2131
+ throw localError;
2132
+ }
2133
+ }
2134
+ }
2135
+
2136
+ async function npmGlobalRoot() {
2137
+ try {
2138
+ const { stdout } = await execFileAsync('npm', ['root', '-g'], {
2139
+ timeout: 3_000,
2140
+ });
2141
+ return stdout.trim() || null;
2142
+ } catch {
2143
+ return null;
2144
+ }
2145
+ }