@linzumi/cli 0.0.20-beta → 0.0.22-beta
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.
- package/README.md +65 -62
- package/bin/linzumi.js +10 -18
- package/dist/assets/linzumi-logo.svg +1 -0
- package/dist/index.js +9135 -0
- package/package.json +9 -4
- package/src/agentBootstrap.ts +0 -872
- package/src/authCache.ts +0 -157
- package/src/authResolution.ts +0 -77
- package/src/boundedCache.ts +0 -57
- package/src/channelSession.ts +0 -4301
- package/src/channelSessionSupport.ts +0 -308
- package/src/codexAppServer.ts +0 -380
- package/src/codexOutput.ts +0 -846
- package/src/codexRuntimeOptions.ts +0 -80
- package/src/dependencyStatus.ts +0 -198
- package/src/forwardTunnel.ts +0 -859
- package/src/forwardTunnelProtocol.ts +0 -324
- package/src/index.ts +0 -1080
- package/src/json.ts +0 -49
- package/src/kandanQueue.ts +0 -113
- package/src/kandanTls.ts +0 -86
- package/src/localCapabilities.ts +0 -143
- package/src/localCodexMessageState.ts +0 -135
- package/src/localCodexTurnState.ts +0 -108
- package/src/localConfig.ts +0 -99
- package/src/localEditor.ts +0 -1061
- package/src/localEditorRuntime.ts +0 -717
- package/src/localForwarding.ts +0 -523
- package/src/oauth.ts +0 -425
- package/src/pendingKandanMessageQueue.ts +0 -109
- package/src/phoenix.ts +0 -359
- package/src/portForwardApproval.ts +0 -181
- package/src/portForwardWatcher.ts +0 -404
- package/src/protocol.ts +0 -321
- package/src/runner.ts +0 -943
- package/src/runnerConsoleReporter.ts +0 -142
- package/src/runnerLogger.ts +0 -50
- package/src/streamDeltaCoalescing.ts +0 -129
- package/src/streamDeltaQueue.ts +0 -102
package/src/codexOutput.ts
DELETED
|
@@ -1,846 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
- Date: 2026-04-24
|
|
3
|
-
Spec: plans/2026-04-24-local-codex-channel-thread-binding-spec.md
|
|
4
|
-
Relationship: Normalizes Codex app-server thread items into the existing
|
|
5
|
-
Kandan `codex_*` structured message kinds reused by local-runner output.
|
|
6
|
-
|
|
7
|
-
- Date: 2026-04-24
|
|
8
|
-
Spec: plans/2026-04-24-local-codex-runner-deep-quality-spec.md
|
|
9
|
-
Relationship: Extracts Codex app-server `userMessage` turn items so local
|
|
10
|
-
TUI prompts can be mirrored into Kandan as human-authored thread history,
|
|
11
|
-
normalizes assistant-message deltas into the same structured Codex
|
|
12
|
-
assistant message kind used by completed local-runner output, and reduces
|
|
13
|
-
Codex web-search, reasoning, command-output, terminal-interaction, raw
|
|
14
|
-
function-call, raw custom-tool, and live file-change observations into
|
|
15
|
-
existing Kandan Codex structured output kinds.
|
|
16
|
-
*/
|
|
17
|
-
import { type JsonObject, type JsonRpcResponse, type JsonValue, isJsonObject } from "./protocol";
|
|
18
|
-
import { Buffer } from "node:buffer";
|
|
19
|
-
import {
|
|
20
|
-
arrayValue,
|
|
21
|
-
integerValue,
|
|
22
|
-
objectValue,
|
|
23
|
-
parseJsonObjectOrUndefined,
|
|
24
|
-
stringListValue,
|
|
25
|
-
stringValue,
|
|
26
|
-
} from "./json";
|
|
27
|
-
|
|
28
|
-
export type CodexOutputMessage = {
|
|
29
|
-
readonly itemKey: string;
|
|
30
|
-
readonly body: string;
|
|
31
|
-
readonly structured: JsonObject;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export type CodexUserInputMessage = {
|
|
35
|
-
readonly itemKey: string;
|
|
36
|
-
readonly body: string;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type CodexAssistantDelta = {
|
|
40
|
-
readonly itemKey: string;
|
|
41
|
-
readonly turnId: string | undefined;
|
|
42
|
-
readonly delta: string;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type CodexReasoningDelta = {
|
|
46
|
-
readonly itemKey: string;
|
|
47
|
-
readonly turnId: string | undefined;
|
|
48
|
-
readonly delta: string;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export type CodexCommandOutputDelta = {
|
|
52
|
-
readonly itemKey: string;
|
|
53
|
-
readonly turnId: string | undefined;
|
|
54
|
-
readonly processId: string | undefined;
|
|
55
|
-
readonly stream: string;
|
|
56
|
-
readonly delta: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type CodexTerminalInput = {
|
|
60
|
-
readonly itemKey: string;
|
|
61
|
-
readonly turnId: string | undefined;
|
|
62
|
-
readonly processId: string | undefined;
|
|
63
|
-
readonly inputText: string;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export type CodexFileChangeDelta = {
|
|
67
|
-
readonly itemKey: string;
|
|
68
|
-
readonly turnId: string | undefined;
|
|
69
|
-
readonly patchText: string;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export type CodexWebSearchProgress = {
|
|
73
|
-
readonly itemKey: string;
|
|
74
|
-
readonly turnId: string | undefined;
|
|
75
|
-
readonly queries: string[];
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const maxVisibleWebSearchQueries = 6;
|
|
79
|
-
|
|
80
|
-
export function codexOutputMessagesForTurn(
|
|
81
|
-
response: JsonRpcResponse,
|
|
82
|
-
turnId: string,
|
|
83
|
-
): CodexOutputMessage[] {
|
|
84
|
-
if ("error" in response) {
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const thread = objectValue(objectValue(response.result)?.thread);
|
|
89
|
-
const turns = arrayValue(thread?.turns) ?? [];
|
|
90
|
-
const turn = turns
|
|
91
|
-
.filter(isJsonObject)
|
|
92
|
-
.find((candidate) => candidate.id === turnId);
|
|
93
|
-
const items = arrayValue(turn?.items) ?? [];
|
|
94
|
-
const itemObjects = items.filter(isJsonObject);
|
|
95
|
-
|
|
96
|
-
return codexOutputMessagesForItems(itemObjects)
|
|
97
|
-
.filter((message) => message.body.trim() !== "");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function codexUserInputMessagesForTurn(
|
|
101
|
-
response: JsonRpcResponse,
|
|
102
|
-
turnId: string,
|
|
103
|
-
): CodexUserInputMessage[] {
|
|
104
|
-
if ("error" in response) {
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const thread = objectValue(objectValue(response.result)?.thread);
|
|
109
|
-
const turns = arrayValue(thread?.turns) ?? [];
|
|
110
|
-
const turn = turns
|
|
111
|
-
.filter(isJsonObject)
|
|
112
|
-
.find((candidate) => candidate.id === turnId);
|
|
113
|
-
const items = arrayValue(turn?.items) ?? [];
|
|
114
|
-
|
|
115
|
-
return items
|
|
116
|
-
.filter(isJsonObject)
|
|
117
|
-
.flatMap((item, index) => codexUserInputMessageForItem(item, index))
|
|
118
|
-
.filter((message) => message.body.trim() !== "");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function codexOutputMessagesForItem(
|
|
122
|
-
item: JsonObject,
|
|
123
|
-
index: number,
|
|
124
|
-
): CodexOutputMessage[] {
|
|
125
|
-
const itemType = stringValue(item.type);
|
|
126
|
-
const itemKey = stringValue(item.id) ?? `item-${index}`;
|
|
127
|
-
const baseStructured = (
|
|
128
|
-
kind: string,
|
|
129
|
-
extra: JsonObject,
|
|
130
|
-
): JsonObject => ({
|
|
131
|
-
kind,
|
|
132
|
-
item_id: itemKey,
|
|
133
|
-
transcript_unit_id: `${kind}:${itemKey}`,
|
|
134
|
-
stream_state: "completed",
|
|
135
|
-
...extra,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
switch (itemType) {
|
|
139
|
-
case "reasoning": {
|
|
140
|
-
const content = stringListValue(item.content).join("\n");
|
|
141
|
-
const summaryParts = stringListValue(item.summary);
|
|
142
|
-
const body = content.trim() !== "" ? content : summaryParts.join("\n");
|
|
143
|
-
|
|
144
|
-
return [
|
|
145
|
-
{
|
|
146
|
-
itemKey,
|
|
147
|
-
body,
|
|
148
|
-
structured: baseStructured("codex_reasoning", {
|
|
149
|
-
content,
|
|
150
|
-
summary_parts: summaryParts,
|
|
151
|
-
}),
|
|
152
|
-
},
|
|
153
|
-
];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
case "agentMessage": {
|
|
157
|
-
const text = assistantVisibleText(item);
|
|
158
|
-
return [
|
|
159
|
-
{
|
|
160
|
-
itemKey,
|
|
161
|
-
body: text,
|
|
162
|
-
structured: baseStructured("codex_assistant_message", {
|
|
163
|
-
content: text,
|
|
164
|
-
phase: stringValue(item.phase) ?? "final_answer",
|
|
165
|
-
}),
|
|
166
|
-
},
|
|
167
|
-
];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
case "commandExecution": {
|
|
171
|
-
const command = stringValue(item.command) ?? "command";
|
|
172
|
-
const output = nonBlankStringValue(item.aggregatedOutput) ?? "";
|
|
173
|
-
const body = [`$ ${command}`, output]
|
|
174
|
-
.filter((part) => part.trim() !== "")
|
|
175
|
-
.join("\n\n");
|
|
176
|
-
|
|
177
|
-
return [
|
|
178
|
-
{
|
|
179
|
-
itemKey,
|
|
180
|
-
body,
|
|
181
|
-
structured: baseStructured("codex_command_execution", {
|
|
182
|
-
command,
|
|
183
|
-
cwd: stringValue(item.cwd) ?? "",
|
|
184
|
-
status: stringValue(item.status) ?? "",
|
|
185
|
-
process_id: stringValue(item.processId) ?? "",
|
|
186
|
-
duration_ms: integerValue(item.durationMs) ?? null,
|
|
187
|
-
exit_code: integerValue(item.exitCode) ?? null,
|
|
188
|
-
output,
|
|
189
|
-
}),
|
|
190
|
-
},
|
|
191
|
-
];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
case "terminalInput": {
|
|
195
|
-
const inputText =
|
|
196
|
-
nonBlankStringValue(item.inputText) ??
|
|
197
|
-
nonBlankStringValue(item.input) ??
|
|
198
|
-
nonBlankStringValue(item.text) ??
|
|
199
|
-
"";
|
|
200
|
-
|
|
201
|
-
return [
|
|
202
|
-
{
|
|
203
|
-
itemKey,
|
|
204
|
-
body: inputText,
|
|
205
|
-
structured: baseStructured("codex_terminal_input", {
|
|
206
|
-
process_id: stringValue(item.processId) ?? "",
|
|
207
|
-
input_text: inputText,
|
|
208
|
-
}),
|
|
209
|
-
},
|
|
210
|
-
];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
case "fileChange": {
|
|
214
|
-
const changes = normalizeFileChangeEntries(arrayValue(item.changes) ?? []);
|
|
215
|
-
const patchText =
|
|
216
|
-
nonBlankStringValue(item.patchText) ?? nonBlankStringValue(item.patch) ?? "";
|
|
217
|
-
const body =
|
|
218
|
-
patchText.trim() !== ""
|
|
219
|
-
? patchText
|
|
220
|
-
: changes.map((change) => change.path).join("\n");
|
|
221
|
-
|
|
222
|
-
return [
|
|
223
|
-
{
|
|
224
|
-
itemKey,
|
|
225
|
-
body,
|
|
226
|
-
structured: baseStructured("codex_file_change", {
|
|
227
|
-
status: stringValue(item.status) ?? "",
|
|
228
|
-
patch_text: patchText,
|
|
229
|
-
changes,
|
|
230
|
-
}),
|
|
231
|
-
},
|
|
232
|
-
];
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
default:
|
|
236
|
-
return [];
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function codexOutputMessagesForItems(
|
|
241
|
-
items: readonly JsonObject[],
|
|
242
|
-
): CodexOutputMessage[] {
|
|
243
|
-
const consumedCallOutputIds = new Set<string>();
|
|
244
|
-
|
|
245
|
-
return items.flatMap((item, index) => {
|
|
246
|
-
const itemType = stringValue(item.type);
|
|
247
|
-
|
|
248
|
-
switch (itemType) {
|
|
249
|
-
case "function_call": {
|
|
250
|
-
const output = findCallOutput(items, "function_call_output", stringValue(item.call_id));
|
|
251
|
-
const outputId = stringValue(output?.id);
|
|
252
|
-
if (outputId !== undefined) {
|
|
253
|
-
consumedCallOutputIds.add(outputId);
|
|
254
|
-
}
|
|
255
|
-
return commandMessageForFunctionCall(item, output, index);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
case "custom_tool_call": {
|
|
259
|
-
const output = findCallOutput(items, "custom_tool_call_output", stringValue(item.call_id));
|
|
260
|
-
const outputId = stringValue(output?.id);
|
|
261
|
-
if (outputId !== undefined) {
|
|
262
|
-
consumedCallOutputIds.add(outputId);
|
|
263
|
-
}
|
|
264
|
-
return messageForCustomToolCall(item, output, index);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
case "function_call_output":
|
|
268
|
-
case "custom_tool_call_output": {
|
|
269
|
-
const outputId = stringValue(item.id);
|
|
270
|
-
return outputId !== undefined && consumedCallOutputIds.has(outputId)
|
|
271
|
-
? []
|
|
272
|
-
: commandMessageForUnpairedToolOutput(item, index);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
default:
|
|
276
|
-
return codexOutputMessagesForItem(item, index);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export function codexAssistantDeltaFromNotification(
|
|
282
|
-
params: JsonObject,
|
|
283
|
-
): CodexAssistantDelta | undefined {
|
|
284
|
-
const delta = assistantDeltaText(params);
|
|
285
|
-
|
|
286
|
-
if (delta === undefined || delta === "") {
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
itemKey:
|
|
292
|
-
stringValue(params.itemId) ??
|
|
293
|
-
stringValue(params.item_id) ??
|
|
294
|
-
stringValue(objectValue(params.item)?.id) ??
|
|
295
|
-
"assistant-message",
|
|
296
|
-
turnId:
|
|
297
|
-
stringValue(params.turnId) ??
|
|
298
|
-
stringValue(params.turn_id) ??
|
|
299
|
-
stringValue(objectValue(params.turn)?.id),
|
|
300
|
-
delta,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export function codexUserInputMessageFromNotification(
|
|
305
|
-
params: JsonObject,
|
|
306
|
-
): CodexUserInputMessage | undefined {
|
|
307
|
-
const item = objectValue(params.item) ?? params;
|
|
308
|
-
const messages = codexUserInputMessageForItem(item, 0);
|
|
309
|
-
|
|
310
|
-
return messages[0];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export function codexAssistantStructuredMessage(
|
|
314
|
-
itemKey: string,
|
|
315
|
-
content: string,
|
|
316
|
-
streamState: "streaming" | "completed",
|
|
317
|
-
): JsonObject {
|
|
318
|
-
return {
|
|
319
|
-
kind: "codex_assistant_message",
|
|
320
|
-
item_id: itemKey,
|
|
321
|
-
transcript_unit_id: `codex_assistant_message:${itemKey}`,
|
|
322
|
-
stream_state: streamState,
|
|
323
|
-
content,
|
|
324
|
-
phase: "final_answer",
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export function codexReasoningDeltaFromNotification(
|
|
329
|
-
params: JsonObject,
|
|
330
|
-
): CodexReasoningDelta | undefined {
|
|
331
|
-
const delta = textDeltaFromParams(params);
|
|
332
|
-
|
|
333
|
-
if (delta === undefined || delta === "") {
|
|
334
|
-
return undefined;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
itemKey:
|
|
339
|
-
stringValue(params.itemId) ??
|
|
340
|
-
stringValue(params.item_id) ??
|
|
341
|
-
stringValue(objectValue(params.item)?.id) ??
|
|
342
|
-
"reasoning",
|
|
343
|
-
turnId:
|
|
344
|
-
stringValue(params.turnId) ??
|
|
345
|
-
stringValue(params.turn_id) ??
|
|
346
|
-
stringValue(objectValue(params.turn)?.id),
|
|
347
|
-
delta,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export function codexReasoningStructuredMessage(
|
|
352
|
-
itemKey: string,
|
|
353
|
-
content: string,
|
|
354
|
-
streamState: "streaming" | "completed",
|
|
355
|
-
): JsonObject {
|
|
356
|
-
return {
|
|
357
|
-
kind: "codex_reasoning",
|
|
358
|
-
item_id: itemKey,
|
|
359
|
-
transcript_unit_id: `codex_reasoning:${itemKey}`,
|
|
360
|
-
stream_state: streamState,
|
|
361
|
-
content,
|
|
362
|
-
summary_parts: content.trim() === "" ? [] : [content],
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export function codexCommandOutputDeltaFromNotification(
|
|
367
|
-
params: JsonObject,
|
|
368
|
-
): CodexCommandOutputDelta | undefined {
|
|
369
|
-
const delta =
|
|
370
|
-
nonBlankStringValue(params.deltaBase64) === undefined
|
|
371
|
-
? textDeltaFromParams(params)
|
|
372
|
-
: Buffer.from(String(params.deltaBase64), "base64").toString("utf8");
|
|
373
|
-
|
|
374
|
-
if (delta === undefined || delta === "") {
|
|
375
|
-
return undefined;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const processId = stringValue(params.processId) ?? stringValue(params.process_id);
|
|
379
|
-
|
|
380
|
-
return {
|
|
381
|
-
itemKey:
|
|
382
|
-
stringValue(params.itemId) ??
|
|
383
|
-
stringValue(params.item_id) ??
|
|
384
|
-
stringValue(objectValue(params.item)?.id) ??
|
|
385
|
-
processId ??
|
|
386
|
-
"command-output",
|
|
387
|
-
turnId:
|
|
388
|
-
stringValue(params.turnId) ??
|
|
389
|
-
stringValue(params.turn_id) ??
|
|
390
|
-
stringValue(objectValue(params.turn)?.id),
|
|
391
|
-
processId,
|
|
392
|
-
stream: stringValue(params.stream) ?? "stdout",
|
|
393
|
-
delta,
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export function codexCommandExecutionStructuredMessage(
|
|
398
|
-
itemKey: string,
|
|
399
|
-
fields: {
|
|
400
|
-
readonly command: string;
|
|
401
|
-
readonly output: string;
|
|
402
|
-
readonly cwd?: string | undefined;
|
|
403
|
-
readonly status?: string | undefined;
|
|
404
|
-
readonly processId?: string | undefined;
|
|
405
|
-
readonly durationMs?: number | undefined;
|
|
406
|
-
readonly exitCode?: number | null | undefined;
|
|
407
|
-
readonly stream?: string | undefined;
|
|
408
|
-
},
|
|
409
|
-
streamState: "streaming" | "completed",
|
|
410
|
-
): JsonObject {
|
|
411
|
-
return {
|
|
412
|
-
kind: "codex_command_execution",
|
|
413
|
-
item_id: itemKey,
|
|
414
|
-
transcript_unit_id: `codex_command_execution:${itemKey}`,
|
|
415
|
-
stream_state: streamState,
|
|
416
|
-
command: fields.command,
|
|
417
|
-
cwd: fields.cwd ?? "",
|
|
418
|
-
status: fields.status ?? streamState,
|
|
419
|
-
process_id: fields.processId ?? "",
|
|
420
|
-
duration_ms: fields.durationMs ?? null,
|
|
421
|
-
exit_code: fields.exitCode ?? null,
|
|
422
|
-
stream: fields.stream ?? "",
|
|
423
|
-
output: fields.output,
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export function codexFileChangeDeltaFromNotification(
|
|
428
|
-
params: JsonObject,
|
|
429
|
-
): CodexFileChangeDelta | undefined {
|
|
430
|
-
const patchText = textDeltaFromParams(params);
|
|
431
|
-
|
|
432
|
-
if (patchText === undefined || patchText === "") {
|
|
433
|
-
return undefined;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
itemKey:
|
|
438
|
-
stringValue(params.itemId) ??
|
|
439
|
-
stringValue(params.item_id) ??
|
|
440
|
-
stringValue(objectValue(params.item)?.id) ??
|
|
441
|
-
"file-change",
|
|
442
|
-
turnId:
|
|
443
|
-
stringValue(params.turnId) ??
|
|
444
|
-
stringValue(params.turn_id) ??
|
|
445
|
-
stringValue(objectValue(params.turn)?.id),
|
|
446
|
-
patchText,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
export function codexFileChangeStructuredMessage(
|
|
451
|
-
itemKey: string,
|
|
452
|
-
patchText: string,
|
|
453
|
-
streamState: "streaming" | "completed",
|
|
454
|
-
status: "started" | "completed",
|
|
455
|
-
): JsonObject {
|
|
456
|
-
return {
|
|
457
|
-
kind: "codex_file_change",
|
|
458
|
-
item_id: itemKey,
|
|
459
|
-
transcript_unit_id: `codex_file_change:${itemKey}`,
|
|
460
|
-
stream_state: streamState,
|
|
461
|
-
status,
|
|
462
|
-
patch_text: patchText,
|
|
463
|
-
changes: fileChangesFromPatch(patchText),
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export function codexTerminalInputFromNotification(
|
|
468
|
-
params: JsonObject,
|
|
469
|
-
): CodexTerminalInput | undefined {
|
|
470
|
-
const inputText =
|
|
471
|
-
nonBlankStringValue(params.stdin) ??
|
|
472
|
-
nonBlankStringValue(params.inputText) ??
|
|
473
|
-
nonBlankStringValue(params.input) ??
|
|
474
|
-
nonBlankStringValue(params.text);
|
|
475
|
-
|
|
476
|
-
if (inputText === undefined) {
|
|
477
|
-
return undefined;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const processId = stringValue(params.processId) ?? stringValue(params.process_id);
|
|
481
|
-
|
|
482
|
-
return {
|
|
483
|
-
itemKey:
|
|
484
|
-
stringValue(params.itemId) ??
|
|
485
|
-
stringValue(params.item_id) ??
|
|
486
|
-
stringValue(objectValue(params.item)?.id) ??
|
|
487
|
-
`terminal-input:${processId ?? "process"}`,
|
|
488
|
-
turnId:
|
|
489
|
-
stringValue(params.turnId) ??
|
|
490
|
-
stringValue(params.turn_id) ??
|
|
491
|
-
stringValue(objectValue(params.turn)?.id),
|
|
492
|
-
processId,
|
|
493
|
-
inputText,
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
export function codexWebSearchProgressFromNotification(
|
|
498
|
-
params: JsonObject,
|
|
499
|
-
): CodexWebSearchProgress | undefined {
|
|
500
|
-
const item = objectValue(params.item) ?? params;
|
|
501
|
-
const itemType = stringValue(item.type);
|
|
502
|
-
|
|
503
|
-
if (
|
|
504
|
-
itemType !== "web_search_call" &&
|
|
505
|
-
itemType !== "webSearchCall" &&
|
|
506
|
-
itemType !== "webSearch"
|
|
507
|
-
) {
|
|
508
|
-
return undefined;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const queries = webSearchQueriesForItem(item);
|
|
512
|
-
|
|
513
|
-
if (queries.length === 0) {
|
|
514
|
-
return undefined;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
return {
|
|
518
|
-
itemKey:
|
|
519
|
-
stringValue(item.id) ??
|
|
520
|
-
stringValue(params.itemId) ??
|
|
521
|
-
stringValue(params.item_id) ??
|
|
522
|
-
"web-search",
|
|
523
|
-
turnId:
|
|
524
|
-
stringValue(params.turnId) ??
|
|
525
|
-
stringValue(params.turn_id) ??
|
|
526
|
-
stringValue(objectValue(params.turn)?.id),
|
|
527
|
-
queries,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
export function codexWebSearchStructuredMessage(
|
|
532
|
-
itemKey: string,
|
|
533
|
-
queries: readonly string[],
|
|
534
|
-
streamState: "streaming" | "completed",
|
|
535
|
-
): JsonObject {
|
|
536
|
-
const body = webSearchProgressBody(queries);
|
|
537
|
-
return {
|
|
538
|
-
kind: "codex_reasoning",
|
|
539
|
-
item_id: itemKey,
|
|
540
|
-
transcript_unit_id: `codex_reasoning:${itemKey}`,
|
|
541
|
-
stream_state: streamState,
|
|
542
|
-
content: body,
|
|
543
|
-
summary_parts: ["Searches", ...visibleWebSearchQueries(queries)],
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
export function webSearchProgressBody(queries: readonly string[]): string {
|
|
548
|
-
const visibleQueries = visibleWebSearchQueries(queries);
|
|
549
|
-
const hiddenCount = dedupeStrings(queries).length - visibleQueries.length;
|
|
550
|
-
const rows = [
|
|
551
|
-
"Searches",
|
|
552
|
-
...visibleQueries.map(query => `- ${query}`),
|
|
553
|
-
...(hiddenCount > 0 ? [`- +${hiddenCount} more`] : []),
|
|
554
|
-
];
|
|
555
|
-
|
|
556
|
-
return rows.join("\n");
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function codexUserInputMessageForItem(
|
|
560
|
-
item: JsonObject,
|
|
561
|
-
index: number,
|
|
562
|
-
): CodexUserInputMessage[] {
|
|
563
|
-
const itemType = stringValue(item.type);
|
|
564
|
-
const role = stringValue(item.role);
|
|
565
|
-
const userAuthored =
|
|
566
|
-
itemType === "userMessage" ||
|
|
567
|
-
itemType === "user_message" ||
|
|
568
|
-
(itemType === "message" && role === "user");
|
|
569
|
-
|
|
570
|
-
if (!userAuthored) {
|
|
571
|
-
return [];
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const itemKey = stringValue(item.id) ?? `item-${index}`;
|
|
575
|
-
const content = arrayValue(item.content) ?? [];
|
|
576
|
-
const textParts = content
|
|
577
|
-
.filter(isJsonObject)
|
|
578
|
-
.filter((part) => {
|
|
579
|
-
const partType = stringValue(part.type);
|
|
580
|
-
|
|
581
|
-
return partType === "text" || partType === "input_text";
|
|
582
|
-
})
|
|
583
|
-
.map((part) => nonBlankStringValue(part.text) ?? "")
|
|
584
|
-
.filter((text) => text.trim() !== "");
|
|
585
|
-
const body =
|
|
586
|
-
textParts.length > 0
|
|
587
|
-
? textParts.join("\n\n")
|
|
588
|
-
: nonBlankStringValue(item.text) ??
|
|
589
|
-
nonBlankStringValue(item.message) ??
|
|
590
|
-
"";
|
|
591
|
-
|
|
592
|
-
return [{ itemKey, body }];
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function assistantVisibleText(item: JsonObject): string {
|
|
596
|
-
const direct =
|
|
597
|
-
stringValue(item.reply) ??
|
|
598
|
-
stringValue(item.summary) ??
|
|
599
|
-
stringValue(item.content) ??
|
|
600
|
-
stringValue(item.text) ??
|
|
601
|
-
"";
|
|
602
|
-
|
|
603
|
-
const parsed = parseJsonObjectOrUndefined(direct);
|
|
604
|
-
if (parsed === undefined) {
|
|
605
|
-
return direct;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
return (
|
|
609
|
-
stringValue(parsed.reply) ??
|
|
610
|
-
stringValue(parsed.summary) ??
|
|
611
|
-
stringValue(parsed.content) ??
|
|
612
|
-
stringValue(parsed.message) ??
|
|
613
|
-
stringValue(parsed.output_text) ??
|
|
614
|
-
direct
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
function assistantDeltaText(params: JsonObject): string | undefined {
|
|
619
|
-
return textDeltaFromParams(params);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function textDeltaFromParams(params: JsonObject): string | undefined {
|
|
623
|
-
const delta = params.delta;
|
|
624
|
-
|
|
625
|
-
if (typeof delta === "string") {
|
|
626
|
-
return delta;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (isJsonObject(delta)) {
|
|
630
|
-
return (
|
|
631
|
-
stringValue(delta.text) ??
|
|
632
|
-
stringValue(delta.content) ??
|
|
633
|
-
stringValue(delta.delta) ??
|
|
634
|
-
stringValue(delta.message)
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
return (
|
|
639
|
-
stringValue(params.text) ??
|
|
640
|
-
stringValue(params.content) ??
|
|
641
|
-
stringValue(params.message)
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function findCallOutput(
|
|
646
|
-
items: readonly JsonObject[],
|
|
647
|
-
outputType: string,
|
|
648
|
-
callId: string | undefined,
|
|
649
|
-
): JsonObject | undefined {
|
|
650
|
-
if (callId === undefined) {
|
|
651
|
-
return undefined;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
return items.find(
|
|
655
|
-
item => stringValue(item.type) === outputType && stringValue(item.call_id) === callId,
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
function commandMessageForFunctionCall(
|
|
660
|
-
item: JsonObject,
|
|
661
|
-
output: JsonObject | undefined,
|
|
662
|
-
index: number,
|
|
663
|
-
): CodexOutputMessage[] {
|
|
664
|
-
const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
|
|
665
|
-
const name = stringValue(item.name) ?? "function_call";
|
|
666
|
-
const args = parseJsonObjectOrUndefined(nonBlankStringValue(item.arguments) ?? "");
|
|
667
|
-
const command =
|
|
668
|
-
name === "exec_command"
|
|
669
|
-
? nonBlankStringValue(args?.cmd) ?? name
|
|
670
|
-
: `${name}${toolArgumentsSummary(item.arguments)}`;
|
|
671
|
-
const cwd = nonBlankStringValue(args?.cwd) ?? "";
|
|
672
|
-
const outputText = toolOutputText(output);
|
|
673
|
-
const body = [`$ ${command}`, outputText].filter(part => part.trim() !== "").join("\n\n");
|
|
674
|
-
|
|
675
|
-
return [{
|
|
676
|
-
itemKey,
|
|
677
|
-
body,
|
|
678
|
-
structured: codexCommandExecutionStructuredMessage(
|
|
679
|
-
itemKey,
|
|
680
|
-
{
|
|
681
|
-
command,
|
|
682
|
-
cwd,
|
|
683
|
-
output: outputText,
|
|
684
|
-
status: output === undefined ? "started" : "completed",
|
|
685
|
-
},
|
|
686
|
-
"completed",
|
|
687
|
-
),
|
|
688
|
-
}];
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function messageForCustomToolCall(
|
|
692
|
-
item: JsonObject,
|
|
693
|
-
output: JsonObject | undefined,
|
|
694
|
-
index: number,
|
|
695
|
-
): CodexOutputMessage[] {
|
|
696
|
-
const name = stringValue(item.name) ?? "custom_tool_call";
|
|
697
|
-
const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
|
|
698
|
-
const input = nonBlankStringValue(item.input) ?? nonBlankStringValue(item.arguments) ?? "";
|
|
699
|
-
|
|
700
|
-
if (name === "apply_patch") {
|
|
701
|
-
return [{
|
|
702
|
-
itemKey,
|
|
703
|
-
body: input,
|
|
704
|
-
structured: {
|
|
705
|
-
kind: "codex_file_change",
|
|
706
|
-
item_id: itemKey,
|
|
707
|
-
transcript_unit_id: `codex_file_change:${itemKey}`,
|
|
708
|
-
stream_state: "completed",
|
|
709
|
-
status: output === undefined ? "started" : "completed",
|
|
710
|
-
patch_text: input,
|
|
711
|
-
changes: fileChangesFromPatch(input),
|
|
712
|
-
},
|
|
713
|
-
}];
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const outputText = toolOutputText(output);
|
|
717
|
-
const command = `${name}${toolArgumentsSummary(item.input)}`;
|
|
718
|
-
return [{
|
|
719
|
-
itemKey,
|
|
720
|
-
body: [`$ ${command}`, outputText].filter(part => part.trim() !== "").join("\n\n"),
|
|
721
|
-
structured: codexCommandExecutionStructuredMessage(
|
|
722
|
-
itemKey,
|
|
723
|
-
{ command, output: outputText, status: output === undefined ? "started" : "completed" },
|
|
724
|
-
"completed",
|
|
725
|
-
),
|
|
726
|
-
}];
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function commandMessageForUnpairedToolOutput(
|
|
730
|
-
item: JsonObject,
|
|
731
|
-
index: number,
|
|
732
|
-
): CodexOutputMessage[] {
|
|
733
|
-
const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
|
|
734
|
-
const output = toolOutputText(item);
|
|
735
|
-
|
|
736
|
-
return [{
|
|
737
|
-
itemKey,
|
|
738
|
-
body: output,
|
|
739
|
-
structured: codexCommandExecutionStructuredMessage(
|
|
740
|
-
itemKey,
|
|
741
|
-
{ command: "tool output", output, status: "completed" },
|
|
742
|
-
"completed",
|
|
743
|
-
),
|
|
744
|
-
}];
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
function toolArgumentsSummary(value: JsonValue | undefined): string {
|
|
748
|
-
const text = nonBlankStringValue(value);
|
|
749
|
-
return text === undefined ? "" : ` ${text}`;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function toolOutputText(item: JsonObject | undefined): string {
|
|
753
|
-
if (item === undefined) {
|
|
754
|
-
return "";
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
return (
|
|
758
|
-
nonBlankStringValue(item.output) ??
|
|
759
|
-
nonBlankStringValue(item.result) ??
|
|
760
|
-
nonBlankStringValue(item.content) ??
|
|
761
|
-
""
|
|
762
|
-
);
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function fileChangesFromPatch(patchText: string): JsonObject[] {
|
|
766
|
-
return patchText
|
|
767
|
-
.split("\n")
|
|
768
|
-
.flatMap((line): JsonObject[] => {
|
|
769
|
-
const updatePrefix = "*** Update File: ";
|
|
770
|
-
const addPrefix = "*** Add File: ";
|
|
771
|
-
const deletePrefix = "*** Delete File: ";
|
|
772
|
-
|
|
773
|
-
if (line.startsWith(updatePrefix)) {
|
|
774
|
-
return [{ path: line.slice(updatePrefix.length).trim(), diff: "", kind: "update", move_path: "" }];
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
if (line.startsWith(addPrefix)) {
|
|
778
|
-
return [{ path: line.slice(addPrefix.length).trim(), diff: "", kind: "add", move_path: "" }];
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (line.startsWith(deletePrefix)) {
|
|
782
|
-
return [{ path: line.slice(deletePrefix.length).trim(), diff: "", kind: "delete", move_path: "" }];
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
return [];
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
function webSearchQueriesForItem(item: JsonObject): string[] {
|
|
790
|
-
const action = objectValue(item.action);
|
|
791
|
-
const values = [
|
|
792
|
-
stringValue(item.query),
|
|
793
|
-
stringValue(action?.query),
|
|
794
|
-
...stringListValue(item.queries),
|
|
795
|
-
...stringListValue(action?.queries),
|
|
796
|
-
];
|
|
797
|
-
|
|
798
|
-
return dedupeStrings(values);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function visibleWebSearchQueries(queries: readonly string[]): string[] {
|
|
802
|
-
return dedupeStrings(queries).slice(0, maxVisibleWebSearchQueries);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
function dedupeStrings(values: readonly (string | undefined)[]): string[] {
|
|
806
|
-
return values.reduce<string[]>((acc, value) => {
|
|
807
|
-
if (value === undefined || value.trim() === "" || acc.includes(value)) {
|
|
808
|
-
return acc;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return [...acc, value];
|
|
812
|
-
}, []);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
function normalizeFileChangeEntries(values: readonly JsonValue[]): JsonObject[] {
|
|
816
|
-
return values.filter(isJsonObject).map((change) => ({
|
|
817
|
-
path: stringValue(change.path) ?? "",
|
|
818
|
-
diff: nonBlankStringValue(change.diff) ?? "",
|
|
819
|
-
kind: fileChangeKind(change.kind),
|
|
820
|
-
move_path:
|
|
821
|
-
stringValue(change.move_path) ??
|
|
822
|
-
stringValue(change.movePath) ??
|
|
823
|
-
fileChangeMovePath(change.kind) ??
|
|
824
|
-
"",
|
|
825
|
-
}));
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
function nonBlankStringValue(value: JsonValue | undefined): string | undefined {
|
|
829
|
-
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
function fileChangeKind(value: JsonValue | undefined): string {
|
|
833
|
-
if (isJsonObject(value)) {
|
|
834
|
-
return stringValue(value.type) ?? "";
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
return stringValue(value) ?? "";
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
function fileChangeMovePath(value: JsonValue | undefined): string | undefined {
|
|
841
|
-
if (!isJsonObject(value)) {
|
|
842
|
-
return undefined;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
return stringValue(value.move_path) ?? stringValue(value.movePath);
|
|
846
|
-
}
|