@myagentroam/node 0.1.3 → 0.1.6

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 (195) hide show
  1. package/README.md +9 -1
  2. package/dist/capabilities.d.ts +2 -0
  3. package/dist/capabilities.js +15 -4
  4. package/dist/claude-agent-sdk.d.ts +4 -16
  5. package/dist/claude-agent-sdk.js +18 -79
  6. package/dist/claude-channel.js +0 -1
  7. package/dist/codex-app-server.d.ts +5 -0
  8. package/dist/codex-app-server.js +26 -5
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +18 -2
  11. package/dist/connector/node-connector-options.d.ts +26 -0
  12. package/dist/connector/node-connector-options.js +1 -0
  13. package/dist/connector/node-operation-router.d.ts +9 -0
  14. package/dist/connector/node-operation-router.js +22 -0
  15. package/dist/connector.d.ts +62 -356
  16. package/dist/connector.js +714 -6176
  17. package/dist/control-outbound-scheduler.d.ts +26 -0
  18. package/dist/control-outbound-scheduler.js +121 -0
  19. package/dist/database.d.ts +4 -0
  20. package/dist/database.js +15 -10
  21. package/dist/event-buffer.js +0 -1
  22. package/dist/fake-runner.js +0 -1
  23. package/dist/health.js +0 -1
  24. package/dist/main.js +6 -1
  25. package/dist/migrations/v001.js +0 -1
  26. package/dist/migrations/v002.js +0 -1
  27. package/dist/migrations/v003.d.ts +4 -0
  28. package/dist/migrations/v003.js +44 -0
  29. package/dist/native-jsonl-reader.d.ts +14 -0
  30. package/dist/native-jsonl-reader.js +97 -0
  31. package/dist/native-session-history.d.ts +41 -4
  32. package/dist/native-session-history.js +340 -159
  33. package/dist/opencode-runtime.d.ts +33 -0
  34. package/dist/opencode-runtime.js +80 -0
  35. package/dist/opencode-server.d.ts +47 -0
  36. package/dist/opencode-server.js +250 -0
  37. package/dist/operational.js +10 -3
  38. package/dist/process-tree.js +0 -1
  39. package/dist/runner/abstract-runner.d.ts +153 -0
  40. package/dist/runner/abstract-runner.js +154 -0
  41. package/dist/runner/claude/managed-run-controller.d.ts +52 -0
  42. package/dist/runner/claude/managed-run-controller.js +326 -0
  43. package/dist/runner/claude-code-runner.d.ts +70 -0
  44. package/dist/runner/claude-code-runner.js +286 -0
  45. package/dist/runner/codex/conversation-parser.d.ts +59 -0
  46. package/dist/runner/codex/conversation-parser.js +816 -0
  47. package/dist/runner/codex/managed-run-controller.d.ts +80 -0
  48. package/dist/runner/codex/managed-run-controller.js +388 -0
  49. package/dist/runner/codex-runner.d.ts +85 -0
  50. package/dist/runner/codex-runner.js +365 -0
  51. package/dist/runner/fake-test-runner.d.ts +26 -0
  52. package/dist/runner/fake-test-runner.js +47 -0
  53. package/dist/runner/opencode/conversation-parser.d.ts +27 -0
  54. package/dist/runner/opencode/conversation-parser.js +217 -0
  55. package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
  56. package/dist/runner/opencode/managed-run-controller.js +451 -0
  57. package/dist/runner/opencode-runner.d.ts +60 -0
  58. package/dist/runner/opencode-runner.js +330 -0
  59. package/dist/runner/runner-registry.d.ts +19 -0
  60. package/dist/runner/runner-registry.js +75 -0
  61. package/dist/runner-command-engine.js +0 -1
  62. package/dist/runner-profiles.d.ts +4 -0
  63. package/dist/runner-profiles.js +76 -3
  64. package/dist/runner-usage.d.ts +2 -2
  65. package/dist/runner-usage.js +3 -6
  66. package/dist/runtime-state.js +26 -14
  67. package/dist/service/attachment-domain-state.d.ts +5 -0
  68. package/dist/service/attachment-domain-state.js +5 -0
  69. package/dist/service/conversation-history-service.d.ts +34 -0
  70. package/dist/service/conversation-history-service.js +158 -0
  71. package/dist/service/external-session-resume-service.d.ts +32 -0
  72. package/dist/service/external-session-resume-service.js +98 -0
  73. package/dist/service/fake-managed-run-service.d.ts +29 -0
  74. package/dist/service/fake-managed-run-service.js +62 -0
  75. package/dist/service/managed-runner-session-service.d.ts +12 -0
  76. package/dist/service/managed-runner-session-service.js +35 -0
  77. package/dist/service/native-session-projection-service.d.ts +51 -0
  78. package/dist/service/native-session-projection-service.js +271 -0
  79. package/dist/service/native-session-watch-service.d.ts +37 -0
  80. package/dist/service/native-session-watch-service.js +150 -0
  81. package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
  82. package/dist/service/node-connection-lifecycle-service.js +85 -0
  83. package/dist/service/node-control-channel.d.ts +32 -0
  84. package/dist/service/node-control-channel.js +85 -0
  85. package/dist/service/node-control-message-service.d.ts +28 -0
  86. package/dist/service/node-control-message-service.js +104 -0
  87. package/dist/service/node-request-service.d.ts +14 -0
  88. package/dist/service/node-request-service.js +50 -0
  89. package/dist/service/node-run-event-bridge.d.ts +40 -0
  90. package/dist/service/node-run-event-bridge.js +83 -0
  91. package/dist/service/node-terminal-channel.d.ts +29 -0
  92. package/dist/service/node-terminal-channel.js +182 -0
  93. package/dist/service/node-upgrade-coordinator.d.ts +29 -0
  94. package/dist/service/node-upgrade-coordinator.js +137 -0
  95. package/dist/service/node-workspace-coordinator.d.ts +32 -0
  96. package/dist/service/node-workspace-coordinator.js +80 -0
  97. package/dist/service/run-attachment-service.d.ts +37 -0
  98. package/dist/service/run-attachment-service.js +160 -0
  99. package/dist/service/run-domain-state.d.ts +4 -0
  100. package/dist/service/run-domain-state.js +4 -0
  101. package/dist/service/run-event-service.d.ts +31 -0
  102. package/dist/service/run-event-service.js +102 -0
  103. package/dist/service/run-workbench-service.d.ts +27 -0
  104. package/dist/service/run-workbench-service.js +113 -0
  105. package/dist/service/runner-channel-message-service.d.ts +13 -0
  106. package/dist/service/runner-channel-message-service.js +20 -0
  107. package/dist/service/runner-interaction-service.d.ts +40 -0
  108. package/dist/service/runner-interaction-service.js +246 -0
  109. package/dist/service/runner-service.d.ts +44 -0
  110. package/dist/service/runner-service.js +68 -0
  111. package/dist/service/session-catalog-service.d.ts +45 -0
  112. package/dist/service/session-catalog-service.js +224 -0
  113. package/dist/service/session-command-service.d.ts +23 -0
  114. package/dist/service/session-command-service.js +69 -0
  115. package/dist/service/session-context-service.d.ts +34 -0
  116. package/dist/service/session-context-service.js +87 -0
  117. package/dist/service/session-domain-state.d.ts +8 -0
  118. package/dist/service/session-domain-state.js +8 -0
  119. package/dist/service/session-identity-service.d.ts +24 -0
  120. package/dist/service/session-identity-service.js +86 -0
  121. package/dist/service/session-lifecycle-service.d.ts +31 -0
  122. package/dist/service/session-lifecycle-service.js +242 -0
  123. package/dist/service/session-message-service.d.ts +33 -0
  124. package/dist/service/session-message-service.js +157 -0
  125. package/dist/service/session-presentation-service.d.ts +33 -0
  126. package/dist/service/session-presentation-service.js +36 -0
  127. package/dist/service/session-query-service.d.ts +31 -0
  128. package/dist/service/session-query-service.js +77 -0
  129. package/dist/service/terminal-relay-state.d.ts +6 -0
  130. package/dist/service/terminal-relay-state.js +3 -0
  131. package/dist/service/terminal-service.d.ts +12 -0
  132. package/dist/service/terminal-service.js +52 -0
  133. package/dist/service/workbench-event-service.d.ts +21 -0
  134. package/dist/service/workbench-event-service.js +53 -0
  135. package/dist/service/workbench-manifest-service.d.ts +89 -0
  136. package/dist/service/workbench-manifest-service.js +70 -0
  137. package/dist/service/workspace-change-service.d.ts +9 -0
  138. package/dist/service/workspace-change-service.js +86 -0
  139. package/dist/service/workspace-content-search-service.d.ts +14 -0
  140. package/dist/service/workspace-content-search-service.js +477 -0
  141. package/dist/service/workspace-domain-state.d.ts +75 -0
  142. package/dist/service/workspace-domain-state.js +40 -0
  143. package/dist/service/workspace-file-service.d.ts +17 -0
  144. package/dist/service/workspace-file-service.js +109 -0
  145. package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
  146. package/dist/service/workspace-queue-workbench-service.js +226 -0
  147. package/dist/service/workspace-service.d.ts +15 -0
  148. package/dist/service/workspace-service.js +67 -0
  149. package/dist/service/workspace-session-service.d.ts +23 -0
  150. package/dist/service/workspace-session-service.js +139 -0
  151. package/dist/service/workspace-upload-service.d.ts +37 -0
  152. package/dist/service/workspace-upload-service.js +240 -0
  153. package/dist/service/workspace-watch-service.d.ts +18 -0
  154. package/dist/service/workspace-watch-service.js +120 -0
  155. package/dist/service/workspace-workbench-service.d.ts +47 -0
  156. package/dist/service/workspace-workbench-service.js +223 -0
  157. package/dist/service.js +2 -3
  158. package/dist/storage.js +0 -1
  159. package/dist/supervisor.d.ts +1 -0
  160. package/dist/supervisor.js +154 -0
  161. package/dist/terminal.d.ts +1 -0
  162. package/dist/terminal.js +1 -2
  163. package/dist/util/node-operation-parsers.d.ts +92 -0
  164. package/dist/util/node-operation-parsers.js +382 -0
  165. package/dist/util/runner-native-session-parsers.d.ts +72 -0
  166. package/dist/util/runner-native-session-parsers.js +381 -0
  167. package/dist/util/secret-environment.d.ts +1 -0
  168. package/dist/util/secret-environment.js +25 -0
  169. package/dist/workspace.d.ts +62 -2
  170. package/dist/workspace.js +539 -57
  171. package/package.json +3 -2
  172. package/dist/capabilities.js.map +0 -1
  173. package/dist/claude-agent-sdk.js.map +0 -1
  174. package/dist/claude-channel.js.map +0 -1
  175. package/dist/codex-app-server.js.map +0 -1
  176. package/dist/config.js.map +0 -1
  177. package/dist/connector.js.map +0 -1
  178. package/dist/database.js.map +0 -1
  179. package/dist/event-buffer.js.map +0 -1
  180. package/dist/fake-runner.js.map +0 -1
  181. package/dist/health.js.map +0 -1
  182. package/dist/main.js.map +0 -1
  183. package/dist/migrations/v001.js.map +0 -1
  184. package/dist/migrations/v002.js.map +0 -1
  185. package/dist/native-session-history.js.map +0 -1
  186. package/dist/operational.js.map +0 -1
  187. package/dist/process-tree.js.map +0 -1
  188. package/dist/runner-command-engine.js.map +0 -1
  189. package/dist/runner-profiles.js.map +0 -1
  190. package/dist/runner-usage.js.map +0 -1
  191. package/dist/runtime-state.js.map +0 -1
  192. package/dist/service.js.map +0 -1
  193. package/dist/storage.js.map +0 -1
  194. package/dist/terminal.js.map +0 -1
  195. package/dist/workspace.js.map +0 -1
@@ -0,0 +1,816 @@
1
+ const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
2
+ const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
3
+ export * from '../../util/runner-native-session-parsers.js';
4
+ export function codexOfficialConversationPage(session, result, input, managedTurnForNative) {
5
+ if (!isPlainRecord(result) || !isPlainRecord(result.thread))
6
+ return undefined;
7
+ const thread = result.thread;
8
+ if (thread.id !== session.externalSessionId || !Array.isArray(thread.turns))
9
+ return undefined;
10
+ const parsedTurns = thread.turns.flatMap((value, sequence) => {
11
+ const nativeTurnId = typeof value === 'object' && value !== null && typeof value.id === 'string'
12
+ ? value.id
13
+ : undefined;
14
+ const turn = codexOfficialTurn(session, value, sequence, nativeTurnId === undefined ? undefined : managedTurnForNative(nativeTurnId));
15
+ return turn === undefined || nativeTurnId === undefined ? [] : [{ nativeTurnId, turn }];
16
+ });
17
+ const allTurns = parsedTurns.map(({ nativeTurnId, turn }, index) => ({
18
+ ...turn,
19
+ rewind: { before: parsedTurns[index - 1]?.nativeTurnId ?? null, after: nativeTurnId }
20
+ }));
21
+ if (allTurns.length === 0)
22
+ return undefined;
23
+ const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
24
+ const end = nativePageEnd(input.cursor, session.id, allTurns.length);
25
+ const start = Math.max(0, end - limit);
26
+ return {
27
+ turns: allTurns.slice(start, end),
28
+ nextCursor: start > 0
29
+ ? Buffer.from(JSON.stringify({ sessionId: session.id, end: start })).toString('base64url')
30
+ : null
31
+ };
32
+ }
33
+ export function codexOfficialTurn(session, value, sequence, managedTurn) {
34
+ if (!isPlainRecord(value) || typeof value.id !== 'string' || !Array.isArray(value.items))
35
+ return undefined;
36
+ const startedAt = codexTimestamp(value.startedAt);
37
+ const completedAt = codexTimestamp(value.completedAt) ?? startedAt;
38
+ const items = codexOfficialItems(session, value.id, value.items, sequence, startedAt, completedAt);
39
+ if (items.length === 0)
40
+ return undefined;
41
+ if (managedTurn !== undefined) {
42
+ const localUser = managedTurn.items.find((item) => item.kind === 'user_message');
43
+ const officialNativeItemIds = codexNativeItemIds(value.items);
44
+ const officialAssistantCount = items.filter((item) => item.kind === 'assistant_message').length;
45
+ const officialAssistantMessage = officialAssistantCount > 0;
46
+ const managedAssistants = managedTurn.items.filter((item) => item.kind === 'assistant_message');
47
+ const managedAssistantCandidates = officialAssistantCount === 0 ? [] : managedAssistants.slice(-officialAssistantCount);
48
+ const assistantLength = (values) => values.reduce((length, item) => {
49
+ if (item.kind !== 'assistant_message' || !isPlainRecord(item.payload))
50
+ return length;
51
+ return length + (typeof item.payload.text === 'string' ? item.payload.text.length : 0);
52
+ }, 0);
53
+ const preferLocalAssistant = officialAssistantMessage &&
54
+ (assistantLength(managedAssistantCandidates) > assistantLength(items) ||
55
+ ((managedTurn.completedAt ?? 0) > (completedAt ?? 0) &&
56
+ assistantLength(managedAssistantCandidates) > 0));
57
+ const replacedManagedAssistantIds = new Set(preferLocalAssistant
58
+ ? []
59
+ : officialAssistantCount === 0
60
+ ? []
61
+ : managedAssistantCandidates.map((item) => item.id));
62
+ const localLiveItems = managedTurn.items.filter((item) => {
63
+ if (item.kind === 'user_message')
64
+ return false;
65
+ // `item/agentMessage/delta` uses a synthetic runtime ID. Once
66
+ // `thread/read` returns the final Agent message, it is the same reply
67
+ // without a shared native item ID and must replace that transient item.
68
+ if (item.kind === 'assistant_message' && replacedManagedAssistantIds.has(item.id))
69
+ return false;
70
+ const nativeItemId = conversationItemNativeId(item.id);
71
+ return nativeItemId === undefined || !officialNativeItemIds.has(nativeItemId);
72
+ });
73
+ const officialItems = [
74
+ ...retainManagedImageAttachments(managedTurn, value.id, items
75
+ .filter((item) => item.kind !== 'assistant_message' || !preferLocalAssistant)
76
+ .map((item) => item.kind === 'user_message' && localUser !== undefined
77
+ ? { ...localUser, turnId: managedTurn.id }
78
+ : { ...item, turnId: managedTurn.id, runId: managedTurn.runId }))
79
+ ];
80
+ if (localUser !== undefined && !officialItems.some((item) => item.kind === 'user_message'))
81
+ officialItems.unshift({ ...localUser, turnId: managedTurn.id });
82
+ const retainedLocalIds = new Set(localLiveItems.map((item) => item.id));
83
+ const consumedOfficialIds = new Set();
84
+ const officialUser = officialItems.find((item) => item.kind === 'user_message');
85
+ const officialAssistants = officialItems.filter((item) => item.kind === 'assistant_message');
86
+ const officialItemMarker = `:official:${value.id}:`;
87
+ const officialByNativeId = new Map(officialItems.flatMap((item) => {
88
+ const markerIndex = item.id.lastIndexOf(officialItemMarker);
89
+ return markerIndex < 0
90
+ ? []
91
+ : [[item.id.slice(markerIndex + officialItemMarker.length), item]];
92
+ }));
93
+ const reconciledItems = [];
94
+ for (const managedItem of managedTurn.items) {
95
+ let replacement;
96
+ if (managedItem.kind === 'user_message')
97
+ replacement = officialUser;
98
+ else {
99
+ const nativeId = conversationItemNativeId(managedItem.id);
100
+ if (nativeId !== undefined)
101
+ replacement = officialByNativeId.get(nativeId);
102
+ if (replacement === undefined &&
103
+ managedItem.kind === 'assistant_message' &&
104
+ replacedManagedAssistantIds.has(managedItem.id))
105
+ replacement = officialAssistants.find((item) => !consumedOfficialIds.has(item.id));
106
+ }
107
+ if (replacement !== undefined) {
108
+ if (!consumedOfficialIds.has(replacement.id)) {
109
+ reconciledItems.push(replacement);
110
+ consumedOfficialIds.add(replacement.id);
111
+ }
112
+ }
113
+ else if (retainedLocalIds.has(managedItem.id))
114
+ reconciledItems.push(managedItem);
115
+ }
116
+ for (const item of officialItems)
117
+ if (!consumedOfficialIds.has(item.id))
118
+ reconciledItems.push(item);
119
+ return {
120
+ ...managedTurn,
121
+ status: 'SUCCEEDED',
122
+ // Reconcile against the managed runtime order. Official items replace
123
+ // matching live projections in place; newly visible terminal items such
124
+ // as a plan are appended after the commentary and tools that preceded it.
125
+ items: reconciledItems,
126
+ startedAt,
127
+ completedAt
128
+ };
129
+ }
130
+ return {
131
+ id: `${session.id}:official:${value.id}`,
132
+ sessionId: session.id,
133
+ runId: null,
134
+ userItemId: items.find((item) => item.kind === 'user_message')?.id ?? null,
135
+ status: 'SUCCEEDED',
136
+ model: null,
137
+ effort: null,
138
+ access: null,
139
+ items,
140
+ startedAt,
141
+ completedAt
142
+ };
143
+ }
144
+ /**
145
+ * Codex's authoritative thread records omit MAR's Node-local generated-image
146
+ * references. Preserve those references like user-image metadata: cache expiry
147
+ * only makes the bytes unavailable, while the conversation still renders its
148
+ * image placeholder.
149
+ */
150
+ export function retainManagedImageAttachments(managedTurn, nativeTurnId, officialItems) {
151
+ const localAttachments = new Map();
152
+ for (const item of managedTurn.items) {
153
+ const nativeItemId = conversationItemNativeId(item.id);
154
+ if (nativeItemId === undefined || !isPlainRecord(item.payload))
155
+ continue;
156
+ if (!Array.isArray(item.payload.attachments) || item.payload.attachments.length === 0)
157
+ continue;
158
+ localAttachments.set(nativeItemId, item.payload.attachments);
159
+ }
160
+ if (localAttachments.size === 0)
161
+ return officialItems;
162
+ const marker = `:official:${nativeTurnId}:`;
163
+ return officialItems.map((item) => {
164
+ if (!isPlainRecord(item.payload) || Array.isArray(item.payload.attachments))
165
+ return item;
166
+ const markerIndex = item.id.lastIndexOf(marker);
167
+ if (markerIndex < 0)
168
+ return item;
169
+ const attachments = localAttachments.get(item.id.slice(markerIndex + marker.length));
170
+ return attachments === undefined
171
+ ? item
172
+ : { ...item, payload: { ...item.payload, attachments } };
173
+ });
174
+ }
175
+ export function codexNativeItemIds(values) {
176
+ return new Set(values.flatMap((value) => {
177
+ if (!isPlainRecord(value))
178
+ return [];
179
+ const id = typeof value.call_id === 'string'
180
+ ? value.call_id
181
+ : typeof value.id === 'string'
182
+ ? value.id
183
+ : undefined;
184
+ return id === undefined ? [] : [id];
185
+ }));
186
+ }
187
+ export function conversationItemNativeId(itemId) {
188
+ if (itemId.startsWith('codex:'))
189
+ return itemId.slice('codex:'.length);
190
+ const marker = ':codex:';
191
+ const markerIndex = itemId.lastIndexOf(marker);
192
+ return markerIndex < 0 ? undefined : itemId.slice(markerIndex + marker.length);
193
+ }
194
+ /** Keep a custom tool invocation and its output in one Workbench card. */
195
+ export function codexOfficialItems(session, nativeTurnId, values, turnSequence, startedAt, completedAt) {
196
+ const items = [];
197
+ const pairedToolIndexes = new Map();
198
+ // App Server preserves execution order within a Turn. Keep that order while
199
+ // merging a paired tool invocation with its output into one Workbench card.
200
+ for (const [itemSequence, value] of values.entries()) {
201
+ const item = codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt);
202
+ if (item === undefined)
203
+ continue;
204
+ const key = codexPairedToolCallId(value);
205
+ if (key === undefined) {
206
+ items.push(item);
207
+ continue;
208
+ }
209
+ const existingIndex = pairedToolIndexes.get(key);
210
+ if (existingIndex === undefined) {
211
+ pairedToolIndexes.set(key, items.length);
212
+ items.push(item);
213
+ continue;
214
+ }
215
+ const previous = items[existingIndex];
216
+ const invocation = isPlainRecord(value) && (value.type === 'custom_tool_call' || value.type === 'function_call')
217
+ ? item
218
+ : previous;
219
+ const result = invocation === item ? previous : item;
220
+ items[existingIndex] = mergeCodexCustomToolItems(invocation, result);
221
+ }
222
+ return items;
223
+ }
224
+ export function codexPairedToolCallId(value) {
225
+ if (!isPlainRecord(value))
226
+ return undefined;
227
+ if (value.type !== 'custom_tool_call' &&
228
+ value.type !== 'custom_tool_call_output' &&
229
+ value.type !== 'function_call' &&
230
+ value.type !== 'function_call_output')
231
+ return undefined;
232
+ return typeof value.call_id === 'string'
233
+ ? value.call_id
234
+ : typeof value.id === 'string'
235
+ ? value.id
236
+ : undefined;
237
+ }
238
+ export function mergeCodexCustomToolItems(invocation, result) {
239
+ if (invocation.kind !== 'tool_call' || result.kind !== 'tool_call')
240
+ return invocation;
241
+ const invocationPayload = isPlainRecord(invocation.payload) ? invocation.payload : {};
242
+ const resultPayload = isPlainRecord(result.payload) ? result.payload : {};
243
+ return {
244
+ ...invocation,
245
+ status: result.status,
246
+ completedAt: result.completedAt,
247
+ payload: {
248
+ ...invocationPayload,
249
+ outputSummary: resultPayload.outputSummary ?? invocationPayload.outputSummary,
250
+ errorCode: resultPayload.errorCode ?? invocationPayload.errorCode,
251
+ truncated: invocationPayload.truncated === true || resultPayload.truncated === true,
252
+ ...(resultPayload.attachments === undefined ? {} : { attachments: resultPayload.attachments })
253
+ }
254
+ };
255
+ }
256
+ export function coalesceImageGenerationItems(items) {
257
+ const merged = [];
258
+ for (const item of items) {
259
+ const payload = isPlainRecord(item.payload) ? item.payload : undefined;
260
+ const previous = merged.at(-1);
261
+ const previousPayload = previous !== undefined && isPlainRecord(previous.payload) ? previous.payload : undefined;
262
+ if (item.kind !== 'tool_call' ||
263
+ payload?.toolName !== 'imagegen' ||
264
+ previous?.kind !== 'tool_call' ||
265
+ previousPayload?.toolName !== 'imagegen') {
266
+ merged.push(item);
267
+ continue;
268
+ }
269
+ const attachments = [
270
+ ...(Array.isArray(previousPayload.attachments) ? previousPayload.attachments : []),
271
+ ...(Array.isArray(payload.attachments) ? payload.attachments : [])
272
+ ];
273
+ merged[merged.length - 1] = {
274
+ ...previous,
275
+ completedAt: item.completedAt ?? previous.completedAt,
276
+ payload: { ...previousPayload, attachments }
277
+ };
278
+ }
279
+ return merged;
280
+ }
281
+ export function codexPlanText(value) {
282
+ if (typeof value.type !== 'string' || typeof value.text !== 'string')
283
+ return undefined;
284
+ const text = value.text.trim();
285
+ if (text.length === 0)
286
+ return undefined;
287
+ if (value.type.toLocaleLowerCase() === 'plan')
288
+ return text;
289
+ if (value.type !== 'agentMessage' || !text.startsWith('<proposed_plan>'))
290
+ return undefined;
291
+ const plan = text
292
+ .slice('<proposed_plan>'.length)
293
+ .replace(/<\/proposed_plan>\s*$/, '')
294
+ .trim();
295
+ return plan.length > 0 ? plan : undefined;
296
+ }
297
+ /**
298
+ * Claude 标签式 Plan Mode 的最终计划以 `<proposed_plan>` 完整包裹输出;
299
+ * 未闭合或缺失标签的文本保持普通 assistant 消息。
300
+ */
301
+ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt) {
302
+ if (!isPlainRecord(value) || typeof value.type !== 'string')
303
+ return undefined;
304
+ const id = (value.type === 'custom_tool_call' ||
305
+ value.type === 'custom_tool_call_output' ||
306
+ value.type === 'function_call' ||
307
+ value.type === 'function_call_output') &&
308
+ typeof value.call_id === 'string'
309
+ ? value.call_id
310
+ : typeof value.id === 'string'
311
+ ? value.id
312
+ : typeof value.call_id === 'string'
313
+ ? value.call_id
314
+ : `${nativeTurnId}:${itemSequence}`;
315
+ const base = {
316
+ id: `${session.id}:official:${nativeTurnId}:${id}`,
317
+ sessionId: session.id,
318
+ turnId: `${session.id}:official:${nativeTurnId}`,
319
+ runId: null,
320
+ parentItemId: null,
321
+ sourceSequence: turnSequence * 10_000 + itemSequence,
322
+ revision: 0,
323
+ status: 'COMPLETED',
324
+ startedAt,
325
+ completedAt
326
+ };
327
+ if (value.type === 'userMessage') {
328
+ const text = codexContentText(value.content);
329
+ return text.length === 0
330
+ ? undefined
331
+ : {
332
+ ...base,
333
+ kind: 'user_message',
334
+ payload: { clientMessageId: null, text, contexts: [], delivery: 'ACCEPTED' }
335
+ };
336
+ }
337
+ const planText = codexPlanText(value);
338
+ if (planText !== undefined)
339
+ return { ...base, kind: 'plan', payload: { explanation: planText, steps: [] } };
340
+ if (value.type === 'agentMessage' &&
341
+ typeof value.text === 'string' &&
342
+ value.text.trim().length > 0)
343
+ return {
344
+ ...base,
345
+ kind: 'assistant_message',
346
+ payload: { text: value.text, format: 'markdown', source: 'codex-app-server' }
347
+ };
348
+ if (value.type === 'reasoning') {
349
+ const sections = Array.isArray(value.summary)
350
+ ? value.summary.flatMap((text, index) => typeof text === 'string' && text.trim().length > 0
351
+ ? [{ index, text: compactRunnerText(text, 10_000) }]
352
+ : [])
353
+ : [];
354
+ return { ...base, kind: 'reasoning_summary', payload: { sections } };
355
+ }
356
+ if (value.type === 'commandExecution') {
357
+ const output = typeof value.aggregatedOutput === 'string' ? value.aggregatedOutput : '';
358
+ return {
359
+ ...base,
360
+ kind: 'command_execution',
361
+ payload: {
362
+ command: typeof value.command === 'string'
363
+ ? compactRunnerText(value.command, 10_000)
364
+ : '(未提供命令)',
365
+ cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
366
+ outputPreview: compactRunnerText(output, 10_000),
367
+ outputRef: null,
368
+ exitCode: typeof value.exitCode === 'number' && Number.isInteger(value.exitCode)
369
+ ? value.exitCode
370
+ : null,
371
+ durationMs: typeof value.durationMs === 'number' &&
372
+ Number.isInteger(value.durationMs) &&
373
+ value.durationMs >= 0
374
+ ? value.durationMs
375
+ : null,
376
+ truncated: output.length > 10_000
377
+ }
378
+ };
379
+ }
380
+ if (value.type === 'mcpToolCall')
381
+ return {
382
+ ...base,
383
+ kind: 'tool_call',
384
+ payload: {
385
+ title: [
386
+ typeof value.server === 'string' ? value.server : '',
387
+ typeof value.tool === 'string' ? value.tool : ''
388
+ ]
389
+ .filter(Boolean)
390
+ .join('.'),
391
+ namespace: typeof value.server === 'string' ? value.server : '',
392
+ toolName: typeof value.tool === 'string' ? value.tool : '',
393
+ inputSummary: codexCompactJson(value.arguments),
394
+ outputSummary: codexCompactJson(value.result),
395
+ ...(value.error === undefined ? {} : { errorCode: codexCompactJson(value.error) })
396
+ }
397
+ };
398
+ if (value.type === 'webSearch')
399
+ return {
400
+ ...base,
401
+ kind: 'tool_call',
402
+ payload: {
403
+ title: 'Web Search',
404
+ namespace: 'web',
405
+ toolName: 'search',
406
+ inputSummary: typeof value.query === 'string' ? value.query : ''
407
+ }
408
+ };
409
+ if (value.type === 'imageGeneration')
410
+ return {
411
+ ...base,
412
+ kind: 'tool_call',
413
+ payload: {
414
+ title: 'imagegen',
415
+ namespace: 'codex',
416
+ toolName: 'imagegen',
417
+ inputSummary: typeof value.revisedPrompt === 'string' ? value.revisedPrompt : null,
418
+ outputSummary: value.status === 'completed' ? '图片生成完成' : '图片生成失败',
419
+ progressLabel: null,
420
+ errorCode: value.status === 'failed' ? 'IMAGE_GENERATION_FAILED' : null,
421
+ truncated: false
422
+ }
423
+ };
424
+ if (value.type === 'dynamicToolCall' ||
425
+ value.type === 'custom_tool_call' ||
426
+ value.type === 'custom_tool_call_output' ||
427
+ value.type === 'function_call' ||
428
+ value.type === 'function_call_output' ||
429
+ value.type === 'customToolCall' ||
430
+ value.type === 'customToolCallOutput' ||
431
+ value.type === 'functionCall' ||
432
+ value.type === 'functionCallOutput') {
433
+ const name = typeof value.tool === 'string'
434
+ ? value.tool
435
+ : typeof value.name === 'string'
436
+ ? value.name
437
+ : 'tool';
438
+ return {
439
+ ...base,
440
+ kind: 'tool_call',
441
+ payload: {
442
+ title: name,
443
+ namespace: typeof value.namespace === 'string' ? value.namespace : 'codex',
444
+ toolName: name,
445
+ inputSummary: codexCompactJson(value.arguments ?? value.input),
446
+ outputSummary: codexToolOutputSummary(value.contentItems ?? value.output),
447
+ progressLabel: null,
448
+ errorCode: null,
449
+ truncated: false
450
+ }
451
+ };
452
+ }
453
+ if (value.type === 'fileChange' && Array.isArray(value.changes))
454
+ return { ...base, kind: 'file_change', payload: { changes: value.changes } };
455
+ if (value.type === 'contextCompaction')
456
+ return {
457
+ ...base,
458
+ kind: 'context_compaction',
459
+ payload: { phase: 'COMPLETED', summary: '上下文已整理' }
460
+ };
461
+ return codexUnknownOfficialItem(base, value);
462
+ }
463
+ export function codexLiveConversationItem(value, completed, imageAttachments = []) {
464
+ if (typeof value.type !== 'string')
465
+ return undefined;
466
+ const nativeId = typeof value.call_id === 'string'
467
+ ? value.call_id
468
+ : typeof value.id === 'string'
469
+ ? value.id
470
+ : undefined;
471
+ if (nativeId === undefined)
472
+ return undefined;
473
+ const itemId = boundedConversationItemId('codex', nativeId);
474
+ if (value.type === 'reasoning') {
475
+ const sections = Array.isArray(value.summary)
476
+ ? value.summary.flatMap((text, index) => typeof text === 'string' && text.trim().length > 0
477
+ ? [{ index, text: compactRunnerText(text, 10_000) }]
478
+ : [])
479
+ : [];
480
+ return {
481
+ itemId,
482
+ kind: 'reasoning_summary',
483
+ status: completed ? 'COMPLETED' : 'IN_PROGRESS',
484
+ payload: { sections }
485
+ };
486
+ }
487
+ const planText = codexPlanText(value);
488
+ if (planText !== undefined)
489
+ return {
490
+ itemId,
491
+ kind: 'plan',
492
+ status: completed ? 'COMPLETED' : 'IN_PROGRESS',
493
+ payload: { explanation: compactRunnerText(planText, 20_000), steps: [] }
494
+ };
495
+ if (value.type === 'commandExecution') {
496
+ const output = typeof value.aggregatedOutput === 'string' ? value.aggregatedOutput : '';
497
+ return {
498
+ itemId,
499
+ kind: 'command_execution',
500
+ status: codexItemStatus(value.status, completed),
501
+ payload: {
502
+ command: typeof value.command === 'string'
503
+ ? compactRunnerText(value.command, 10_000)
504
+ : '(未提供命令)',
505
+ cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
506
+ outputPreview: compactRunnerText(output, 10_000),
507
+ outputRef: null,
508
+ exitCode: typeof value.exitCode === 'number' && Number.isInteger(value.exitCode)
509
+ ? value.exitCode
510
+ : null,
511
+ durationMs: typeof value.durationMs === 'number' &&
512
+ Number.isInteger(value.durationMs) &&
513
+ value.durationMs >= 0
514
+ ? value.durationMs
515
+ : null,
516
+ truncated: output.length > 10_000
517
+ }
518
+ };
519
+ }
520
+ if (value.type === 'mcpToolCall') {
521
+ const input = compactRunnerValue(value.arguments, 10_000);
522
+ const output = compactRunnerValue(value.result, 10_000);
523
+ const error = compactRunnerValue(value.error, 80);
524
+ const server = typeof value.server === 'string' ? value.server : 'mcp';
525
+ const tool = typeof value.tool === 'string' ? value.tool : 'tool';
526
+ return {
527
+ itemId,
528
+ kind: 'tool_call',
529
+ status: codexItemStatus(value.status, completed),
530
+ payload: {
531
+ namespace: compactRunnerText(server, 128),
532
+ toolName: compactRunnerText(tool, 128),
533
+ title: compactRunnerText(`${server}.${tool}`, 500),
534
+ inputSummary: input.text || null,
535
+ outputSummary: output.text || null,
536
+ progressLabel: null,
537
+ errorCode: error.text || null,
538
+ truncated: input.truncated || output.truncated || error.truncated
539
+ }
540
+ };
541
+ }
542
+ if (value.type === 'webSearch')
543
+ return {
544
+ itemId,
545
+ kind: 'tool_call',
546
+ status: completed ? 'COMPLETED' : 'IN_PROGRESS',
547
+ payload: {
548
+ namespace: 'web',
549
+ toolName: 'search',
550
+ title: 'Web Search',
551
+ inputSummary: typeof value.query === 'string' ? compactRunnerText(value.query, 10_000) : null,
552
+ outputSummary: null,
553
+ progressLabel: completed ? null : '正在搜索',
554
+ errorCode: null,
555
+ truncated: false
556
+ }
557
+ };
558
+ if (value.type === 'fileChange' && Array.isArray(value.changes))
559
+ return {
560
+ itemId,
561
+ kind: 'file_change',
562
+ status: completed ? 'COMPLETED' : 'IN_PROGRESS',
563
+ payload: { changes: value.changes },
564
+ merge: true
565
+ };
566
+ if (value.type === 'imageGeneration')
567
+ return {
568
+ itemId,
569
+ kind: 'tool_call',
570
+ status: codexItemStatus(value.status, completed),
571
+ payload: {
572
+ namespace: 'codex',
573
+ toolName: 'imagegen',
574
+ title: 'imagegen',
575
+ inputSummary: typeof value.revisedPrompt === 'string'
576
+ ? compactRunnerText(value.revisedPrompt, 10_000)
577
+ : null,
578
+ outputSummary: completed ? '图片生成完成' : null,
579
+ progressLabel: completed ? null : '正在生成图片',
580
+ errorCode: value.status === 'failed' ? 'IMAGE_GENERATION_FAILED' : null,
581
+ truncated: false,
582
+ ...(imageAttachments.length === 0 ? {} : { attachments: imageAttachments })
583
+ },
584
+ merge: true
585
+ };
586
+ if (value.type === 'contextCompaction')
587
+ return {
588
+ itemId,
589
+ kind: 'context_compaction',
590
+ status: completed ? 'COMPLETED' : 'IN_PROGRESS',
591
+ payload: { phase: completed ? 'COMPLETED' : 'STARTED', summary: null }
592
+ };
593
+ if (value.type === 'dynamicToolCall' ||
594
+ value.type === 'custom_tool_call' ||
595
+ value.type === 'custom_tool_call_output' ||
596
+ value.type === 'function_call' ||
597
+ value.type === 'function_call_output' ||
598
+ value.type === 'customToolCall' ||
599
+ value.type === 'customToolCallOutput' ||
600
+ value.type === 'functionCall' ||
601
+ value.type === 'functionCallOutput') {
602
+ const name = typeof value.tool === 'string'
603
+ ? value.tool
604
+ : typeof value.name === 'string'
605
+ ? value.name
606
+ : 'tool';
607
+ const input = compactRunnerValue(value.arguments ?? value.input, 10_000);
608
+ const output = compactRunnerValue(codexToolOutput(value.contentItems ?? value.output), 10_000);
609
+ if (value.type === 'custom_tool_call_output' ||
610
+ value.type === 'function_call_output' ||
611
+ value.type === 'customToolCallOutput' ||
612
+ value.type === 'functionCallOutput')
613
+ return {
614
+ itemId,
615
+ kind: 'tool_call',
616
+ status: codexItemStatus(value.status, completed),
617
+ payload: {
618
+ outputSummary: output.text || null,
619
+ ...(imageAttachments.length === 0 ? {} : { attachments: imageAttachments })
620
+ },
621
+ merge: true
622
+ };
623
+ return {
624
+ itemId,
625
+ kind: 'tool_call',
626
+ status: codexItemStatus(value.status, completed),
627
+ payload: {
628
+ namespace: typeof value.namespace === 'string' ? compactRunnerText(value.namespace, 128) : 'codex',
629
+ toolName: compactRunnerText(name, 128),
630
+ title: compactRunnerText(name, 500),
631
+ inputSummary: input.text || null,
632
+ outputSummary: output.text || null,
633
+ progressLabel: null,
634
+ errorCode: null,
635
+ truncated: input.truncated || output.truncated,
636
+ ...(imageAttachments.length === 0 ? {} : { attachments: imageAttachments })
637
+ }
638
+ };
639
+ }
640
+ return codexUnknownLiveItem(itemId, value.type, completed);
641
+ }
642
+ function codexUnknownOfficialItem(base, value) {
643
+ if (value.type === 'userMessage' || value.type === 'agentMessage')
644
+ return undefined;
645
+ const type = typeof value.type === 'string' ? value.type : 'unknown';
646
+ return {
647
+ ...base,
648
+ kind: 'unknown',
649
+ payload: { sourceEventType: `codex:${type}`, label: `Codex ${compactRunnerText(type, 128)}` }
650
+ };
651
+ }
652
+ function codexUnknownLiveItem(itemId, type, completed) {
653
+ if (type === 'userMessage' || type === 'agentMessage')
654
+ return undefined;
655
+ return {
656
+ itemId,
657
+ kind: 'unknown',
658
+ status: completed ? 'COMPLETED' : 'IN_PROGRESS',
659
+ payload: { sourceEventType: `codex:${type}`, label: `Codex ${compactRunnerText(type, 128)}` }
660
+ };
661
+ }
662
+ export function codexImageAttachments(value) {
663
+ const found = [];
664
+ let totalBytes = 0;
665
+ const visit = (current, depth) => {
666
+ if (found.length >= 5 || depth > 4 || current === null || current === undefined)
667
+ return;
668
+ if (Array.isArray(current)) {
669
+ for (const entry of current)
670
+ visit(entry, depth + 1);
671
+ return;
672
+ }
673
+ if (!isPlainRecord(current))
674
+ return;
675
+ const type = typeof current.type === 'string' ? current.type : '';
676
+ if (type === 'input_text' && typeof current.text === 'string') {
677
+ const embeddedImage = /["']image_url["']\s*:\s*["'](data:image\/[^"']+)["']/.exec(current.text)?.[1];
678
+ if (embeddedImage !== undefined) {
679
+ visit({ type: 'input_image', image_url: embeddedImage }, depth + 1);
680
+ }
681
+ }
682
+ const rawData = typeof current.data === 'string' ? current.data : undefined;
683
+ const rawUrl = isPlainRecord(current.image_url)
684
+ ? typeof current.image_url.url === 'string'
685
+ ? current.image_url.url
686
+ : undefined
687
+ : typeof current.image_url === 'string'
688
+ ? current.image_url
689
+ : undefined;
690
+ const data = rawData ?? (rawUrl?.startsWith('data:') ? rawUrl : undefined);
691
+ if ((type === 'image' || type === 'input_image' || type === 'output_image') &&
692
+ data !== undefined) {
693
+ const match = /^data:([^;,]+);base64,(.*)$/.exec(data);
694
+ const mime = typeof current.mimeType === 'string'
695
+ ? current.mimeType
696
+ : typeof current.mime === 'string'
697
+ ? current.mime
698
+ : (match?.[1] ?? 'image/png');
699
+ const dataBase64 = match?.[2] ?? data;
700
+ if (mime.startsWith('image/') &&
701
+ isCanonicalBase64(dataBase64) &&
702
+ dataBase64.length > 0 &&
703
+ Buffer.byteLength(dataBase64, 'base64') <= MAX_COMPOSER_ATTACHMENT_BYTES &&
704
+ totalBytes + Buffer.byteLength(dataBase64, 'base64') <= MAX_COMPOSER_ATTACHMENTS_BYTES) {
705
+ totalBytes += Buffer.byteLength(dataBase64, 'base64');
706
+ found.push({
707
+ name: typeof current.name === 'string' ? current.name : `生成图片-${found.length + 1}.png`,
708
+ mime,
709
+ dataBase64
710
+ });
711
+ }
712
+ }
713
+ for (const [key, child] of Object.entries(current)) {
714
+ if (key === 'data' || key === 'image_url')
715
+ continue;
716
+ visit(child, depth + 1);
717
+ }
718
+ };
719
+ visit(value, 0);
720
+ return found;
721
+ }
722
+ export function codexUserInputQuestions(value) {
723
+ if (!Array.isArray(value) || value.length === 0 || value.length > 3)
724
+ return [];
725
+ const questions = value.map((raw) => {
726
+ if (!isPlainRecord(raw) || typeof raw.id !== 'string' || typeof raw.question !== 'string')
727
+ return undefined;
728
+ const options = Array.isArray(raw.options)
729
+ ? raw.options.flatMap((option) => isPlainRecord(option) && typeof option.label === 'string'
730
+ ? [
731
+ {
732
+ value: compactRunnerText(option.label, 500),
733
+ label: compactRunnerText(option.label, 500),
734
+ description: typeof option.description === 'string'
735
+ ? compactRunnerText(option.description, 1_000)
736
+ : null
737
+ }
738
+ ]
739
+ : [])
740
+ : [];
741
+ return {
742
+ id: compactRunnerText(raw.id, 128),
743
+ prompt: compactRunnerText(raw.question, 5_000),
744
+ options,
745
+ allowOther: raw.isOther === true
746
+ };
747
+ });
748
+ return questions.some((question) => question === undefined)
749
+ ? []
750
+ : questions;
751
+ }
752
+ export function codexItemStatus(value, completed) {
753
+ if (value === 'failed')
754
+ return 'FAILED';
755
+ if (value === 'declined')
756
+ return 'DECLINED';
757
+ return completed || value === 'completed' ? 'COMPLETED' : 'IN_PROGRESS';
758
+ }
759
+ export function codexContentText(value) {
760
+ if (!Array.isArray(value))
761
+ return '';
762
+ return value
763
+ .flatMap((part) => {
764
+ if (typeof part === 'string')
765
+ return [part];
766
+ if (!isPlainRecord(part))
767
+ return [];
768
+ return typeof part.text === 'string' ? [part.text] : [];
769
+ })
770
+ .join('\n')
771
+ .trim();
772
+ }
773
+ export function codexCompactJson(value) {
774
+ if (value === undefined || value === null)
775
+ return '';
776
+ if (typeof value === 'string')
777
+ return compactRunnerText(value, 10_000);
778
+ try {
779
+ return compactRunnerText(JSON.stringify(value), 10_000);
780
+ }
781
+ catch {
782
+ return '';
783
+ }
784
+ }
785
+ export function codexToolOutputSummary(value) {
786
+ return codexCompactJson(codexToolOutput(value));
787
+ }
788
+ /** App Server custom output is an array of text blocks, not JSON to show verbatim. */
789
+ export function codexToolOutput(value) {
790
+ if (!Array.isArray(value))
791
+ return value;
792
+ const text = value.flatMap((block) => {
793
+ if (typeof block === 'string')
794
+ return [block];
795
+ if (!isPlainRecord(block))
796
+ return [];
797
+ if (typeof block.text === 'string') {
798
+ const visible = codexVisibleToolText(block.text);
799
+ return visible.length > 0 ? [visible] : [];
800
+ }
801
+ if (typeof block.content === 'string')
802
+ return [block.content];
803
+ return [];
804
+ });
805
+ return text.length > 0 ? text.join('') : value.some(isCodexImageBlock) ? '图片已生成' : value;
806
+ }
807
+ export function codexVisibleToolText(value) {
808
+ return value.replace(/(["']image_url["']\s*:\s*["'])data:image\/[^"']+(["'])/g, '$1[图片内容已提取]$2');
809
+ }
810
+ export function isCodexImageBlock(value) {
811
+ if (!isPlainRecord(value))
812
+ return false;
813
+ return value.type === 'image' || value.type === 'input_image' || value.type === 'output_image';
814
+ }
815
+ import { codexTimestamp, compactRunnerText, compactRunnerValue, boundedConversationItemId, isCanonicalBase64, isPlainRecord, nativePageEnd } from '../../util/node-operation-parsers.js';
816
+ export * from '../../util/node-operation-parsers.js';