@pasko70/pibo 1.16.6 → 2.0.0
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/dist/agent-runtime/auth.js +28 -0
- package/dist/agent-runtime/capabilities.js +221 -0
- package/dist/agent-runtime/context-build.js +313 -0
- package/dist/agent-runtime/contract.js +45 -0
- package/dist/agent-runtime/errors.js +57 -0
- package/dist/agent-runtime/events.js +1 -0
- package/dist/agent-runtime/history.js +1 -0
- package/dist/agent-runtime/profile-validation.js +109 -0
- package/dist/agent-runtime/registry.js +608 -0
- package/dist/agent-runtime/resource-files.js +87 -0
- package/dist/agent-runtime/resource-service.js +635 -0
- package/dist/agent-runtime/resources.js +1 -0
- package/dist/agent-runtime/routed-session.js +931 -0
- package/dist/agent-runtime/testing/contract.js +51 -0
- package/dist/agent-runtime/testing/fake-adapter.js +192 -0
- package/dist/agent-runtime/types.js +3 -0
- package/dist/agent-runtimes/codex-native/adapter.js +905 -0
- package/dist/agent-runtimes/codex-native/auth.js +476 -0
- package/dist/agent-runtimes/codex-native/client.js +734 -0
- package/dist/agent-runtimes/codex-native/config.js +152 -0
- package/dist/agent-runtimes/codex-native/history.js +283 -0
- package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
- package/dist/agent-runtimes/codex-native/models.js +581 -0
- package/dist/agent-runtimes/codex-native/process.js +459 -0
- package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
- package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
- package/dist/agent-runtimes/codex-native/redaction.js +31 -0
- package/dist/agent-runtimes/codex-native/requests.js +378 -0
- package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
- package/dist/agent-runtimes/codex-native/thread.js +388 -0
- package/dist/agent-runtimes/codex-native/turn.js +849 -0
- package/dist/agent-runtimes/pi/adapter.js +653 -0
- package/dist/agent-runtimes/pi/auth.js +455 -0
- package/dist/agent-runtimes/pi/history.js +640 -0
- package/dist/agent-runtimes/pi/model-catalog.js +87 -0
- package/dist/agent-runtimes/pi/routed-session.js +1464 -0
- package/dist/agent-runtimes/pi/runtime.js +534 -0
- package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
- package/dist/apps/chat/agent-profiles.js +40 -23
- package/dist/apps/chat/agent-store.js +111 -64
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-request-normalizers.js +47 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -9
- package/dist/apps/chat/data/history-query-service.js +108 -0
- package/dist/apps/chat/data/session-query-service.js +24 -3
- package/dist/apps/chat/data/timeline-query-service.js +3 -3
- package/dist/apps/chat/model-catalog.js +1 -55
- package/dist/apps/chat/provider-auth-actions.js +171 -23
- package/dist/apps/chat/stream.js +12 -0
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/trace.js +126 -415
- package/dist/apps/chat/web-app.js +582 -157
- package/dist/apps/chat-ui/assets/{dist-BTLtT_Vf.js → dist-9v2ZXT6V.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CMk1aPug.js → dist-B1LVAytl.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CcwA_IWN.js → dist-C6LPDFXU.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DKOthbxr.js → dist-DVtXxfHc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ls2I6BIw.js → dist-DW4Qrfbd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D1rAtRWs.js → dist-DmbsHdMC.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C1JQRskW.js → dist-DyvwuSFO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DfpaylQ-.js → dist-I26VB0QO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D10bTFD5.js → dist-RtHbJRPV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-5KpmmBBZ.js → dist-sZgJkWZb.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
- package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
- package/dist/auth/login-actions.js +5 -251
- package/dist/cli-session/localSessionSource.js +8 -1
- package/dist/core/context-build.js +124 -20
- package/dist/core/default-profile.js +8 -0
- package/dist/core/profiles.js +30 -3
- package/dist/core/routed-session.js +5 -1446
- package/dist/core/runtime.js +5 -561
- package/dist/core/session-router.js +637 -83
- package/dist/data/cli.js +20 -3
- package/dist/data/schema.js +104 -1
- package/dist/data/session-store.js +24 -1
- package/dist/debug/events.js +25 -10
- package/dist/debug/failures.js +23 -3
- package/dist/debug/index.js +24 -6
- package/dist/debug/messages.js +87 -7
- package/dist/debug/persisted-payloads.js +31 -0
- package/dist/debug/pty.js +7 -6
- package/dist/debug/runtime-binding.js +29 -0
- package/dist/debug/session.js +137 -0
- package/dist/debug/summary.js +12 -0
- package/dist/debug/telemetry.js +28 -0
- package/dist/debug/tools.js +12 -2
- package/dist/debug/trace.js +154 -76
- package/dist/gateway/server.js +87 -2
- package/dist/gateway/tool.js +5 -5
- package/dist/index.js +21 -2
- package/dist/loops/tools.js +13 -12
- package/dist/mcp/agent-context.js +13 -4
- package/dist/mcp/client.js +10 -6
- package/dist/mcp/config.js +5 -2
- package/dist/mcp/runtime-session.js +166 -0
- package/dist/plugins/builtin.js +191 -53
- package/dist/plugins/codex-native.js +29 -0
- package/dist/plugins/registry.js +66 -2
- package/dist/runs/tools.js +26 -25
- package/dist/session-ui/terminalRows.js +2 -2
- package/dist/sessions/pibo-data-store.js +125 -16
- package/dist/sessions/runtime-binding.js +107 -0
- package/dist/sessions/sqlite-store.js +239 -18
- package/dist/sessions/store.js +86 -7
- package/dist/shared/trace-engine.js +18 -9
- package/dist/shared/trace-event-projection.js +29 -11
- package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
- package/dist/shared/trace-order.js +9 -4
- package/dist/shared/trace-page-merge.js +1 -1
- package/dist/signals/projector.js +20 -2
- package/dist/signals/registry.js +11 -0
- package/dist/subagents/tool.js +8 -6
- package/dist/tools/codex-browser.js +20 -19
- package/dist/tools/codex-compat.js +10 -9
- package/dist/tools/codex-image-generation.js +8 -6
- package/dist/tools/contract.js +45 -0
- package/dist/tools/credential-registry.js +182 -0
- package/dist/tools/mcp-bridge.js +511 -0
- package/dist/tools/payload-writer.js +17 -0
- package/dist/tools/runtime/tool.js +10 -9
- package/dist/tools/schema.js +9 -0
- package/dist/tools/session-service.js +193 -0
- package/dist/tools/session-tool-set.js +85 -0
- package/dist/web-annotations/tools.js +17 -16
- package/docs/README.md +11 -0
- package/package.json +3 -2
- package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
- package/dist/apps/chat-ui/assets/index-C2YI0xfw.js +0 -237
- package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
import { normalizeSessionErrorDetails, runtimeSessionErrorDetails } from "../../core/session-errors.js";
|
|
2
|
+
import { CodexAppServerClientError, } from "./client.js";
|
|
3
|
+
import { redactCodexNativeSensitiveText, redactCodexNativeValue } from "./redaction.js";
|
|
4
|
+
import { validateCodexAppServerThreadItem, validateCodexAppServerThreadStatus, validateCodexAppServerTurn, } from "./thread.js";
|
|
5
|
+
const TOOL_ITEM_TYPES = new Set([
|
|
6
|
+
"collabAgentToolCall",
|
|
7
|
+
"commandExecution",
|
|
8
|
+
"dynamicToolCall",
|
|
9
|
+
"fileChange",
|
|
10
|
+
"imageGeneration",
|
|
11
|
+
"imageView",
|
|
12
|
+
"mcpToolCall",
|
|
13
|
+
"sleep",
|
|
14
|
+
"webSearch",
|
|
15
|
+
]);
|
|
16
|
+
export class CodexNativeTurnProtocolError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "CodexNativeTurnProtocolError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function deferred() {
|
|
23
|
+
let resolve;
|
|
24
|
+
let reject;
|
|
25
|
+
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
26
|
+
resolve = resolvePromise;
|
|
27
|
+
reject = rejectPromise;
|
|
28
|
+
});
|
|
29
|
+
return { promise, resolve, reject };
|
|
30
|
+
}
|
|
31
|
+
function isRecord(value) {
|
|
32
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
function requiredRecord(value, label) {
|
|
35
|
+
if (!isRecord(value))
|
|
36
|
+
throw new CodexNativeTurnProtocolError(`Codex ${label} is invalid.`);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function redactedObject(value) {
|
|
40
|
+
const selected = redactCodexNativeValue(value);
|
|
41
|
+
return isRecord(selected) ? selected : {};
|
|
42
|
+
}
|
|
43
|
+
function requiredString(value, label) {
|
|
44
|
+
if (typeof value !== "string" || !value)
|
|
45
|
+
throw new CodexNativeTurnProtocolError(`Codex ${label} is invalid.`);
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function requiredText(value, label) {
|
|
49
|
+
if (typeof value !== "string")
|
|
50
|
+
throw new CodexNativeTurnProtocolError(`Codex ${label} is invalid.`);
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function requiredInteger(value, label) {
|
|
54
|
+
if (!Number.isSafeInteger(value))
|
|
55
|
+
throw new CodexNativeTurnProtocolError(`Codex ${label} is invalid.`);
|
|
56
|
+
return Number(value);
|
|
57
|
+
}
|
|
58
|
+
function requiredNonNegativeInteger(value, label) {
|
|
59
|
+
const selected = requiredInteger(value, label);
|
|
60
|
+
if (selected < 0)
|
|
61
|
+
throw new CodexNativeTurnProtocolError(`Codex ${label} is invalid.`);
|
|
62
|
+
return selected;
|
|
63
|
+
}
|
|
64
|
+
function safeDiagnosticText(value) {
|
|
65
|
+
return redactCodexNativeSensitiveText(value)
|
|
66
|
+
.replace(/(?:[A-Za-z]:\\|\/)(?:[^\s"'<>|]+[\\/]){2,}[^\s"'<>|]*/g, "[redacted path]")
|
|
67
|
+
.slice(0, 4_000);
|
|
68
|
+
}
|
|
69
|
+
function errorMessage(value) {
|
|
70
|
+
if (typeof value === "string" && value.trim())
|
|
71
|
+
return safeDiagnosticText(value);
|
|
72
|
+
if (isRecord(value)) {
|
|
73
|
+
for (const key of ["message", "additionalDetails", "details", "reason"]) {
|
|
74
|
+
const candidate = value[key];
|
|
75
|
+
if (typeof candidate === "string" && candidate.trim())
|
|
76
|
+
return safeDiagnosticText(candidate);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return "Codex turn failed.";
|
|
80
|
+
}
|
|
81
|
+
function errorCode(value) {
|
|
82
|
+
if (!isRecord(value))
|
|
83
|
+
return undefined;
|
|
84
|
+
for (const key of ["code", "type", "kind"]) {
|
|
85
|
+
const candidate = value[key];
|
|
86
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
87
|
+
return candidate.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 128);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
function providerErrorDetails(value, message, retryable) {
|
|
93
|
+
const providerCode = errorCode(value);
|
|
94
|
+
return normalizeSessionErrorDetails(message, {
|
|
95
|
+
api: "codex-app-server",
|
|
96
|
+
provider: "openai-codex",
|
|
97
|
+
providerType: "codex",
|
|
98
|
+
...(providerCode ? { providerCode } : {}),
|
|
99
|
+
providerMessage: message,
|
|
100
|
+
origin: "provider",
|
|
101
|
+
...(retryable !== undefined ? { retryable } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function validateTurnStartResponse(value) {
|
|
105
|
+
const record = requiredRecord(value, "turn/start response");
|
|
106
|
+
if (!Object.hasOwn(record, "turn"))
|
|
107
|
+
throw new CodexNativeTurnProtocolError("Codex turn/start response is missing its turn.");
|
|
108
|
+
return validateCodexAppServerTurn(record.turn);
|
|
109
|
+
}
|
|
110
|
+
function validateTurnSteerResponse(value) {
|
|
111
|
+
return requiredString(requiredRecord(value, "turn/steer response").turnId, "steered turn id");
|
|
112
|
+
}
|
|
113
|
+
function tokenUsageBreakdown(value, label) {
|
|
114
|
+
const record = requiredRecord(value, label);
|
|
115
|
+
return {
|
|
116
|
+
inputTokens: requiredNonNegativeInteger(record.inputTokens, `${label} input token count`),
|
|
117
|
+
outputTokens: requiredNonNegativeInteger(record.outputTokens, `${label} output token count`),
|
|
118
|
+
cacheReadTokens: requiredNonNegativeInteger(record.cachedInputTokens, `${label} cached input token count`),
|
|
119
|
+
cacheWriteTokens: 0,
|
|
120
|
+
reasoningTokens: requiredNonNegativeInteger(record.reasoningOutputTokens, `${label} reasoning token count`),
|
|
121
|
+
totalTokens: requiredNonNegativeInteger(record.totalTokens, `${label} total token count`),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function validateTokenUsage(value) {
|
|
125
|
+
const record = requiredRecord(value, "thread token usage");
|
|
126
|
+
const usage = tokenUsageBreakdown(record.last, "last turn usage");
|
|
127
|
+
if (record.modelContextWindow !== undefined && record.modelContextWindow !== null) {
|
|
128
|
+
usage.contextWindow = requiredNonNegativeInteger(record.modelContextWindow, "model context window");
|
|
129
|
+
}
|
|
130
|
+
return usage;
|
|
131
|
+
}
|
|
132
|
+
function textArray(value) {
|
|
133
|
+
if (!Array.isArray(value))
|
|
134
|
+
return [];
|
|
135
|
+
return value.filter((entry) => typeof entry === "string")
|
|
136
|
+
.map(redactCodexNativeSensitiveText);
|
|
137
|
+
}
|
|
138
|
+
function toolDescriptor(item) {
|
|
139
|
+
if (!TOOL_ITEM_TYPES.has(item.type))
|
|
140
|
+
return undefined;
|
|
141
|
+
if (item.type === "commandExecution") {
|
|
142
|
+
return {
|
|
143
|
+
name: "codex_command",
|
|
144
|
+
args: redactCodexNativeValue({
|
|
145
|
+
command: item.command,
|
|
146
|
+
commandActions: item.commandActions,
|
|
147
|
+
source: item.source,
|
|
148
|
+
pluginId: item.pluginId,
|
|
149
|
+
scriptPath: item.scriptPath,
|
|
150
|
+
}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (item.type === "fileChange") {
|
|
154
|
+
return { name: "codex_file_change", args: redactCodexNativeValue({ changes: item.changes }) };
|
|
155
|
+
}
|
|
156
|
+
if (item.type === "mcpToolCall") {
|
|
157
|
+
const server = typeof item.server === "string" ? redactCodexNativeSensitiveText(item.server) : "mcp";
|
|
158
|
+
const tool = typeof item.tool === "string" ? redactCodexNativeSensitiveText(item.tool) : "tool";
|
|
159
|
+
return {
|
|
160
|
+
name: `${server}/${tool}`,
|
|
161
|
+
args: redactCodexNativeValue({ server, tool, arguments: item.arguments }),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (item.type === "dynamicToolCall") {
|
|
165
|
+
const tool = typeof item.tool === "string" ? redactCodexNativeSensitiveText(item.tool) : "dynamic_tool";
|
|
166
|
+
return { name: tool, args: redactCodexNativeValue({ tool, arguments: item.arguments }) };
|
|
167
|
+
}
|
|
168
|
+
if (item.type === "webSearch") {
|
|
169
|
+
return { name: "codex_web_search", args: redactCodexNativeValue({ query: item.query, action: item.action }) };
|
|
170
|
+
}
|
|
171
|
+
if (item.type === "imageView") {
|
|
172
|
+
return { name: "codex_image_view", args: { image: "[local image]" } };
|
|
173
|
+
}
|
|
174
|
+
if (item.type === "sleep") {
|
|
175
|
+
return { name: "codex_sleep", args: redactCodexNativeValue({ durationMs: item.durationMs }) };
|
|
176
|
+
}
|
|
177
|
+
if (item.type === "imageGeneration") {
|
|
178
|
+
return { name: "codex_image_generation", args: redactCodexNativeValue({ prompt: item.prompt }) };
|
|
179
|
+
}
|
|
180
|
+
const nativeTool = typeof item.tool === "string" ? item.tool : "agent_tool";
|
|
181
|
+
return {
|
|
182
|
+
name: `codex_${redactCodexNativeSensitiveText(nativeTool)}`,
|
|
183
|
+
args: redactCodexNativeValue({ tool: item.tool, prompt: item.prompt, receiverThreadIds: item.receiverThreadIds }),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function toolResult(item) {
|
|
187
|
+
if (item.type === "commandExecution") {
|
|
188
|
+
return redactCodexNativeValue({
|
|
189
|
+
status: item.status,
|
|
190
|
+
exitCode: item.exitCode,
|
|
191
|
+
durationMs: item.durationMs,
|
|
192
|
+
output: item.aggregatedOutput,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (item.type === "fileChange")
|
|
196
|
+
return redactCodexNativeValue({ status: item.status, changes: item.changes });
|
|
197
|
+
if (item.type === "mcpToolCall")
|
|
198
|
+
return redactCodexNativeValue({ status: item.status, result: item.result, error: item.error });
|
|
199
|
+
if (item.type === "dynamicToolCall") {
|
|
200
|
+
return redactCodexNativeValue({ status: item.status, success: item.success, contentItems: item.contentItems });
|
|
201
|
+
}
|
|
202
|
+
if (item.type === "webSearch")
|
|
203
|
+
return redactCodexNativeValue({ query: item.query, results: item.results });
|
|
204
|
+
if (item.type === "imageView")
|
|
205
|
+
return { status: item.status ?? "completed", image: "[local image]" };
|
|
206
|
+
if (item.type === "sleep")
|
|
207
|
+
return redactCodexNativeValue({ status: item.status, durationMs: item.durationMs });
|
|
208
|
+
if (item.type === "imageGeneration") {
|
|
209
|
+
return redactCodexNativeValue({ status: item.status, result: item.result, revisedPrompt: item.revisedPrompt });
|
|
210
|
+
}
|
|
211
|
+
return redactCodexNativeValue({ status: item.status, result: item.result, receiverThreadIds: item.receiverThreadIds });
|
|
212
|
+
}
|
|
213
|
+
function toolFailed(item) {
|
|
214
|
+
if (item.success === false)
|
|
215
|
+
return true;
|
|
216
|
+
if (typeof item.status !== "string")
|
|
217
|
+
return false;
|
|
218
|
+
return ["declined", "failed", "error"].includes(item.status.toLowerCase());
|
|
219
|
+
}
|
|
220
|
+
function newPendingTurn() {
|
|
221
|
+
const completion = deferred();
|
|
222
|
+
const turnIdReady = deferred();
|
|
223
|
+
void completion.promise.catch(() => { });
|
|
224
|
+
return {
|
|
225
|
+
completion,
|
|
226
|
+
turnIdReady,
|
|
227
|
+
turnIdReadySettled: false,
|
|
228
|
+
started: false,
|
|
229
|
+
terminal: false,
|
|
230
|
+
items: new Map(),
|
|
231
|
+
itemOrder: [],
|
|
232
|
+
completedItems: new Set(),
|
|
233
|
+
assistantIndices: new Map(),
|
|
234
|
+
assistantBuffers: new Map(),
|
|
235
|
+
completedAssistantItems: new Set(),
|
|
236
|
+
nextAssistantIndex: 0,
|
|
237
|
+
reasoningStartedItems: new Set(),
|
|
238
|
+
reasoningSummaryParts: new Map(),
|
|
239
|
+
reasoningContentParts: new Map(),
|
|
240
|
+
completedReasoningItems: new Set(),
|
|
241
|
+
startedTools: new Map(),
|
|
242
|
+
completedTools: new Set(),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export class CodexNativeTurnController {
|
|
246
|
+
client;
|
|
247
|
+
threads;
|
|
248
|
+
emit;
|
|
249
|
+
pending;
|
|
250
|
+
disposed = false;
|
|
251
|
+
unsubscribeNotifications;
|
|
252
|
+
unsubscribeDiagnostics;
|
|
253
|
+
constructor(client, threads, emit) {
|
|
254
|
+
this.client = client;
|
|
255
|
+
this.threads = threads;
|
|
256
|
+
this.emit = emit;
|
|
257
|
+
this.unsubscribeNotifications = client.subscribeNotifications((notification) => {
|
|
258
|
+
if (this.disposed)
|
|
259
|
+
return;
|
|
260
|
+
try {
|
|
261
|
+
this.handleNotification(notification.method, notification.params);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
this.handleProtocolFailure(error);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
this.unsubscribeDiagnostics = client.subscribeDiagnostics((diagnostic) => this.handleDiagnostic(diagnostic));
|
|
268
|
+
}
|
|
269
|
+
get streaming() {
|
|
270
|
+
return this.pending !== undefined;
|
|
271
|
+
}
|
|
272
|
+
get activeTurnId() {
|
|
273
|
+
return this.pending?.turnId;
|
|
274
|
+
}
|
|
275
|
+
async start(text, clientUserMessageId, modelOptions) {
|
|
276
|
+
if (this.disposed)
|
|
277
|
+
throw new Error("Codex native turn controller is disposed.");
|
|
278
|
+
if (this.pending)
|
|
279
|
+
throw new Error("Codex native session already has an active turn.");
|
|
280
|
+
const pending = newPendingTurn();
|
|
281
|
+
this.pending = pending;
|
|
282
|
+
const params = {
|
|
283
|
+
threadId: this.threads.thread.id,
|
|
284
|
+
input: [{ type: "text", text }],
|
|
285
|
+
...(clientUserMessageId ? { clientUserMessageId } : {}),
|
|
286
|
+
model: modelOptions.model,
|
|
287
|
+
effort: modelOptions.effort,
|
|
288
|
+
serviceTier: modelOptions.serviceTier,
|
|
289
|
+
...(modelOptions.summary !== undefined ? { summary: modelOptions.summary } : {}),
|
|
290
|
+
...(modelOptions.personality !== undefined ? { personality: modelOptions.personality } : {}),
|
|
291
|
+
};
|
|
292
|
+
try {
|
|
293
|
+
const response = await this.client.request("turn/start", params);
|
|
294
|
+
const turn = validateTurnStartResponse(response);
|
|
295
|
+
this.assignTurnId(pending, turn.id);
|
|
296
|
+
if (turn.status !== "inProgress")
|
|
297
|
+
this.finishTurn(pending, turn);
|
|
298
|
+
await pending.completion.promise;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (!pending.terminal && this.pending === pending) {
|
|
302
|
+
if (pending.started || pending.turnId) {
|
|
303
|
+
const normalized = error instanceof Error ? error : new Error("Codex turn/start failed after the native turn began.");
|
|
304
|
+
this.failActiveTurn(pending, normalized, runtimeSessionErrorDetails(safeDiagnosticText(normalized.message)));
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
this.pending = undefined;
|
|
308
|
+
this.resolveTurnId(pending, undefined);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async steer(text, clientUserMessageId) {
|
|
315
|
+
const pending = this.pending;
|
|
316
|
+
if (!pending || pending.terminal)
|
|
317
|
+
throw new Error("Codex native steering requires an active turn.");
|
|
318
|
+
const turnId = pending.turnId ?? await pending.turnIdReady.promise;
|
|
319
|
+
if (!turnId || pending.terminal || this.pending !== pending)
|
|
320
|
+
throw new Error("Codex native steering requires an active turn.");
|
|
321
|
+
const params = {
|
|
322
|
+
threadId: this.threads.thread.id,
|
|
323
|
+
expectedTurnId: turnId,
|
|
324
|
+
input: [{ type: "text", text }],
|
|
325
|
+
...(clientUserMessageId ? { clientUserMessageId } : {}),
|
|
326
|
+
};
|
|
327
|
+
const response = await this.client.request("turn/steer", params);
|
|
328
|
+
const returnedTurnId = validateTurnSteerResponse(response);
|
|
329
|
+
if (returnedTurnId !== turnId)
|
|
330
|
+
throw new CodexNativeTurnProtocolError("Codex turn/steer returned a different turn id.");
|
|
331
|
+
}
|
|
332
|
+
async interrupt() {
|
|
333
|
+
const pending = this.pending;
|
|
334
|
+
if (!pending || pending.terminal)
|
|
335
|
+
return;
|
|
336
|
+
const turnId = pending.turnId ?? await pending.turnIdReady.promise;
|
|
337
|
+
if (!turnId || pending.terminal || this.pending !== pending)
|
|
338
|
+
return;
|
|
339
|
+
await this.client.request("turn/interrupt", {
|
|
340
|
+
threadId: this.threads.thread.id,
|
|
341
|
+
turnId,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
dispose() {
|
|
345
|
+
if (this.disposed)
|
|
346
|
+
return;
|
|
347
|
+
this.disposed = true;
|
|
348
|
+
this.unsubscribeNotifications();
|
|
349
|
+
this.unsubscribeDiagnostics();
|
|
350
|
+
const pending = this.pending;
|
|
351
|
+
this.pending = undefined;
|
|
352
|
+
if (pending && !pending.terminal) {
|
|
353
|
+
pending.terminal = true;
|
|
354
|
+
this.resolveTurnId(pending, undefined);
|
|
355
|
+
pending.completion.reject(new CodexAppServerClientError("closed", "Codex native turn controller was disposed"));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
handleNotification(method, params) {
|
|
359
|
+
switch (method) {
|
|
360
|
+
case "turn/started":
|
|
361
|
+
this.handleTurnStarted(params);
|
|
362
|
+
return;
|
|
363
|
+
case "turn/completed":
|
|
364
|
+
this.handleTurnCompleted(params);
|
|
365
|
+
return;
|
|
366
|
+
case "item/started":
|
|
367
|
+
this.handleItemStarted(params);
|
|
368
|
+
return;
|
|
369
|
+
case "item/completed":
|
|
370
|
+
this.handleItemCompleted(params);
|
|
371
|
+
return;
|
|
372
|
+
case "item/agentMessage/delta":
|
|
373
|
+
this.handleAssistantDelta(params);
|
|
374
|
+
return;
|
|
375
|
+
case "item/reasoning/summaryPartAdded":
|
|
376
|
+
this.handleReasoningPartAdded(params);
|
|
377
|
+
return;
|
|
378
|
+
case "item/reasoning/summaryTextDelta":
|
|
379
|
+
this.handleReasoningDelta(params, "summaryIndex", true);
|
|
380
|
+
return;
|
|
381
|
+
case "item/reasoning/textDelta":
|
|
382
|
+
this.handleReasoningDelta(params, "contentIndex", false);
|
|
383
|
+
return;
|
|
384
|
+
case "item/commandExecution/outputDelta":
|
|
385
|
+
case "item/fileChange/outputDelta":
|
|
386
|
+
this.handleToolDelta(params, "delta");
|
|
387
|
+
return;
|
|
388
|
+
case "item/commandExecution/terminalInteraction":
|
|
389
|
+
this.handleTerminalInteraction(params);
|
|
390
|
+
return;
|
|
391
|
+
case "item/fileChange/patchUpdated":
|
|
392
|
+
this.handleToolDelta(params, "changes");
|
|
393
|
+
return;
|
|
394
|
+
case "item/mcpToolCall/progress":
|
|
395
|
+
this.handleToolDelta(params, "message");
|
|
396
|
+
return;
|
|
397
|
+
case "thread/tokenUsage/updated":
|
|
398
|
+
this.handleUsage(params);
|
|
399
|
+
return;
|
|
400
|
+
case "thread/status/changed":
|
|
401
|
+
this.handleThreadStatus(params);
|
|
402
|
+
return;
|
|
403
|
+
case "error":
|
|
404
|
+
this.handleTurnError(params);
|
|
405
|
+
return;
|
|
406
|
+
case "warning":
|
|
407
|
+
case "guardianWarning":
|
|
408
|
+
case "deprecationNotice":
|
|
409
|
+
case "configWarning":
|
|
410
|
+
this.handleWarning(method, params);
|
|
411
|
+
return;
|
|
412
|
+
case "model/rerouted":
|
|
413
|
+
this.handleModelRerouted(params);
|
|
414
|
+
return;
|
|
415
|
+
default:
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
handleTurnStarted(value) {
|
|
420
|
+
const params = requiredRecord(value, "turn/started notification");
|
|
421
|
+
if (requiredString(params.threadId, "turn/started thread id") !== this.threads.thread.id)
|
|
422
|
+
return;
|
|
423
|
+
const pending = this.pending;
|
|
424
|
+
if (!pending || pending.terminal)
|
|
425
|
+
return;
|
|
426
|
+
const turn = validateCodexAppServerTurn(params.turn);
|
|
427
|
+
this.assignTurnId(pending, turn.id);
|
|
428
|
+
this.ensureTurnStarted(pending, turn);
|
|
429
|
+
}
|
|
430
|
+
handleTurnCompleted(value) {
|
|
431
|
+
const params = requiredRecord(value, "turn/completed notification");
|
|
432
|
+
if (requiredString(params.threadId, "turn/completed thread id") !== this.threads.thread.id)
|
|
433
|
+
return;
|
|
434
|
+
const pending = this.pending;
|
|
435
|
+
if (!pending || pending.terminal)
|
|
436
|
+
return;
|
|
437
|
+
const turn = validateCodexAppServerTurn(params.turn);
|
|
438
|
+
this.assignTurnId(pending, turn.id);
|
|
439
|
+
this.finishTurn(pending, turn);
|
|
440
|
+
}
|
|
441
|
+
handleItemStarted(value) {
|
|
442
|
+
const params = this.scopedTurnParams(value, "item/started");
|
|
443
|
+
if (!params)
|
|
444
|
+
return;
|
|
445
|
+
requiredInteger(params.record.startedAtMs, "item start timestamp");
|
|
446
|
+
const item = validateCodexAppServerThreadItem(params.record.item);
|
|
447
|
+
this.rememberItem(params.pending, item);
|
|
448
|
+
this.startItem(params.pending, item);
|
|
449
|
+
}
|
|
450
|
+
handleItemCompleted(value) {
|
|
451
|
+
const params = this.scopedTurnParams(value, "item/completed");
|
|
452
|
+
if (!params)
|
|
453
|
+
return;
|
|
454
|
+
requiredInteger(params.record.completedAtMs, "item completion timestamp");
|
|
455
|
+
const item = validateCodexAppServerThreadItem(params.record.item);
|
|
456
|
+
this.completeItem(params.pending, item);
|
|
457
|
+
}
|
|
458
|
+
handleAssistantDelta(value) {
|
|
459
|
+
const params = this.scopedTurnParams(value, "agent message delta");
|
|
460
|
+
if (!params)
|
|
461
|
+
return;
|
|
462
|
+
const itemId = requiredString(params.record.itemId, "agent message item id");
|
|
463
|
+
const delta = redactCodexNativeSensitiveText(requiredText(params.record.delta, "agent message delta"));
|
|
464
|
+
this.ensureTurnStarted(params.pending);
|
|
465
|
+
const text = `${params.pending.assistantBuffers.get(itemId) ?? ""}${delta}`;
|
|
466
|
+
this.rememberItem(params.pending, { id: itemId, type: "agentMessage", text });
|
|
467
|
+
const contentIndex = this.assistantIndex(params.pending, itemId);
|
|
468
|
+
params.pending.assistantBuffers.set(itemId, text);
|
|
469
|
+
this.emit({ type: "assistant_delta", text: delta, contentIndex });
|
|
470
|
+
}
|
|
471
|
+
handleReasoningPartAdded(value) {
|
|
472
|
+
const params = this.scopedTurnParams(value, "reasoning summary part");
|
|
473
|
+
if (!params)
|
|
474
|
+
return;
|
|
475
|
+
const itemId = requiredString(params.record.itemId, "reasoning item id");
|
|
476
|
+
const index = requiredNonNegativeInteger(params.record.summaryIndex, "reasoning summary index");
|
|
477
|
+
this.ensureReasoningStarted(params.pending, itemId);
|
|
478
|
+
const parts = this.reasoningParts(params.pending.reasoningSummaryParts, itemId);
|
|
479
|
+
if (!parts.has(index))
|
|
480
|
+
parts.set(index, "");
|
|
481
|
+
}
|
|
482
|
+
handleReasoningDelta(value, indexKey, summary) {
|
|
483
|
+
const params = this.scopedTurnParams(value, "reasoning delta");
|
|
484
|
+
if (!params)
|
|
485
|
+
return;
|
|
486
|
+
const itemId = requiredString(params.record.itemId, "reasoning item id");
|
|
487
|
+
const contentIndex = requiredNonNegativeInteger(params.record[indexKey], `reasoning ${indexKey}`);
|
|
488
|
+
const delta = redactCodexNativeSensitiveText(requiredText(params.record.delta, "reasoning delta"));
|
|
489
|
+
this.ensureReasoningStarted(params.pending, itemId);
|
|
490
|
+
const parts = this.reasoningParts(summary ? params.pending.reasoningSummaryParts : params.pending.reasoningContentParts, itemId);
|
|
491
|
+
parts.set(contentIndex, `${parts.get(contentIndex) ?? ""}${delta}`);
|
|
492
|
+
this.emit({ type: "reasoning_delta", text: delta, contentIndex });
|
|
493
|
+
}
|
|
494
|
+
handleToolDelta(value, field) {
|
|
495
|
+
const params = this.scopedTurnParams(value, "tool update");
|
|
496
|
+
if (!params)
|
|
497
|
+
return;
|
|
498
|
+
const itemId = requiredString(params.record.itemId, "tool item id");
|
|
499
|
+
const item = params.pending.items.get(itemId);
|
|
500
|
+
if (!item)
|
|
501
|
+
return;
|
|
502
|
+
const descriptor = this.ensureToolStarted(params.pending, item);
|
|
503
|
+
if (!descriptor)
|
|
504
|
+
return;
|
|
505
|
+
const selected = params.record[field];
|
|
506
|
+
if (field !== "changes")
|
|
507
|
+
requiredText(selected, `tool ${field}`);
|
|
508
|
+
else if (!Array.isArray(selected))
|
|
509
|
+
throw new CodexNativeTurnProtocolError("Codex file change patch update is invalid.");
|
|
510
|
+
this.emit({
|
|
511
|
+
type: "tool_execution_updated",
|
|
512
|
+
toolCallId: itemId,
|
|
513
|
+
toolName: descriptor.name,
|
|
514
|
+
args: descriptor.args,
|
|
515
|
+
partialResult: redactCodexNativeValue({ [field]: selected }),
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
handleTerminalInteraction(value) {
|
|
519
|
+
const params = this.scopedTurnParams(value, "terminal interaction");
|
|
520
|
+
if (!params)
|
|
521
|
+
return;
|
|
522
|
+
const itemId = requiredString(params.record.itemId, "terminal interaction item id");
|
|
523
|
+
requiredString(params.record.processId, "terminal interaction process id");
|
|
524
|
+
const stdin = requiredText(params.record.stdin, "terminal interaction input");
|
|
525
|
+
const item = params.pending.items.get(itemId);
|
|
526
|
+
if (!item)
|
|
527
|
+
return;
|
|
528
|
+
const descriptor = this.ensureToolStarted(params.pending, item);
|
|
529
|
+
if (!descriptor)
|
|
530
|
+
return;
|
|
531
|
+
this.emit({
|
|
532
|
+
type: "tool_execution_updated",
|
|
533
|
+
toolCallId: itemId,
|
|
534
|
+
toolName: descriptor.name,
|
|
535
|
+
args: descriptor.args,
|
|
536
|
+
partialResult: { terminalInput: redactCodexNativeSensitiveText(stdin) },
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
handleUsage(value) {
|
|
540
|
+
const params = this.scopedTurnParams(value, "thread token usage");
|
|
541
|
+
if (!params)
|
|
542
|
+
return;
|
|
543
|
+
params.pending.usage = validateTokenUsage(params.record.tokenUsage);
|
|
544
|
+
}
|
|
545
|
+
handleThreadStatus(value) {
|
|
546
|
+
const params = requiredRecord(value, "thread status notification");
|
|
547
|
+
if (requiredString(params.threadId, "thread status thread id") !== this.threads.thread.id)
|
|
548
|
+
return;
|
|
549
|
+
this.threads.setStatus(validateCodexAppServerThreadStatus(params.status));
|
|
550
|
+
}
|
|
551
|
+
handleTurnError(value) {
|
|
552
|
+
const params = this.scopedTurnParams(value, "turn error");
|
|
553
|
+
if (!params)
|
|
554
|
+
return;
|
|
555
|
+
if (typeof params.record.willRetry !== "boolean")
|
|
556
|
+
throw new CodexNativeTurnProtocolError("Codex turn retry state is invalid.");
|
|
557
|
+
const message = errorMessage(params.record.error);
|
|
558
|
+
const details = providerErrorDetails(params.record.error, message, params.record.willRetry);
|
|
559
|
+
if (params.record.willRetry) {
|
|
560
|
+
const warningDetails = { willRetry: true };
|
|
561
|
+
const code = details.providerCode ?? details.code;
|
|
562
|
+
if (code)
|
|
563
|
+
warningDetails.code = code;
|
|
564
|
+
this.emit({ type: "warning", message, details: warningDetails });
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
params.pending.lastError = { message, details };
|
|
568
|
+
this.emit({ type: "error", message, details });
|
|
569
|
+
}
|
|
570
|
+
handleWarning(method, value) {
|
|
571
|
+
const params = requiredRecord(value, `${method} notification`);
|
|
572
|
+
if (params.threadId !== undefined && params.threadId !== null) {
|
|
573
|
+
if (requiredString(params.threadId, `${method} thread id`) !== this.threads.thread.id)
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const messageValue = params.message ?? params.summary ?? params.details;
|
|
577
|
+
if (typeof messageValue !== "string" || !messageValue.trim())
|
|
578
|
+
return;
|
|
579
|
+
this.emit({
|
|
580
|
+
type: "warning",
|
|
581
|
+
message: safeDiagnosticText(messageValue),
|
|
582
|
+
details: redactedObject({ method, summary: params.summary, details: params.details }),
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
handleModelRerouted(value) {
|
|
586
|
+
const params = this.scopedTurnParams(value, "model reroute");
|
|
587
|
+
if (!params)
|
|
588
|
+
return;
|
|
589
|
+
const fromModel = requiredString(params.record.fromModel, "rerouted source model");
|
|
590
|
+
const toModel = requiredString(params.record.toModel, "rerouted target model");
|
|
591
|
+
this.emit({
|
|
592
|
+
type: "warning",
|
|
593
|
+
message: `Codex rerouted the active turn from ${safeDiagnosticText(fromModel)} to ${safeDiagnosticText(toModel)}.`,
|
|
594
|
+
details: redactedObject({ reason: params.record.reason }),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
scopedTurnParams(value, label) {
|
|
598
|
+
const record = requiredRecord(value, `${label} notification`);
|
|
599
|
+
if (requiredString(record.threadId, `${label} thread id`) !== this.threads.thread.id)
|
|
600
|
+
return undefined;
|
|
601
|
+
const pending = this.pending;
|
|
602
|
+
if (!pending || pending.terminal)
|
|
603
|
+
return undefined;
|
|
604
|
+
const turnId = requiredString(record.turnId, `${label} turn id`);
|
|
605
|
+
if (!pending.turnId || pending.turnId !== turnId)
|
|
606
|
+
return undefined;
|
|
607
|
+
return { record, pending };
|
|
608
|
+
}
|
|
609
|
+
assignTurnId(pending, turnId) {
|
|
610
|
+
if (pending.turnId && pending.turnId !== turnId) {
|
|
611
|
+
throw new CodexNativeTurnProtocolError("Codex emitted events for a different active turn.");
|
|
612
|
+
}
|
|
613
|
+
pending.turnId = turnId;
|
|
614
|
+
this.resolveTurnId(pending, turnId);
|
|
615
|
+
}
|
|
616
|
+
resolveTurnId(pending, turnId) {
|
|
617
|
+
if (pending.turnIdReadySettled)
|
|
618
|
+
return;
|
|
619
|
+
pending.turnIdReadySettled = true;
|
|
620
|
+
pending.turnIdReady.resolve(turnId);
|
|
621
|
+
}
|
|
622
|
+
ensureTurnStarted(pending, turn) {
|
|
623
|
+
if (pending.started)
|
|
624
|
+
return;
|
|
625
|
+
pending.started = true;
|
|
626
|
+
this.threads.setStatus({ type: "active", activeFlags: [] });
|
|
627
|
+
this.emit({ type: "turn_started", turnId: pending.turnId ?? turn?.id });
|
|
628
|
+
}
|
|
629
|
+
rememberItem(pending, item) {
|
|
630
|
+
if (!pending.items.has(item.id))
|
|
631
|
+
pending.itemOrder.push(item.id);
|
|
632
|
+
pending.items.set(item.id, structuredClone(item));
|
|
633
|
+
}
|
|
634
|
+
startItem(pending, item) {
|
|
635
|
+
this.ensureTurnStarted(pending);
|
|
636
|
+
if (item.type === "reasoning")
|
|
637
|
+
this.ensureReasoningStarted(pending, item.id);
|
|
638
|
+
else if (item.type === "contextCompaction") {
|
|
639
|
+
this.emit({ type: "compaction_start", reason: "codex_context_compaction" });
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
this.ensureToolStarted(pending, item);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
completeItem(pending, item) {
|
|
646
|
+
this.rememberItem(pending, item);
|
|
647
|
+
if (pending.completedItems.has(item.id))
|
|
648
|
+
return;
|
|
649
|
+
pending.completedItems.add(item.id);
|
|
650
|
+
this.startItem(pending, item);
|
|
651
|
+
if (item.type === "agentMessage" && typeof item.text === "string") {
|
|
652
|
+
const text = redactCodexNativeSensitiveText(item.text);
|
|
653
|
+
pending.assistantBuffers.set(item.id, text);
|
|
654
|
+
pending.completedAssistantItems.add(item.id);
|
|
655
|
+
this.emit({
|
|
656
|
+
type: "assistant_message",
|
|
657
|
+
text,
|
|
658
|
+
contentIndex: this.assistantIndex(pending, item.id),
|
|
659
|
+
});
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (item.type === "reasoning") {
|
|
663
|
+
const summary = textArray(item.summary);
|
|
664
|
+
const content = textArray(item.content);
|
|
665
|
+
const assembled = summary.length > 0
|
|
666
|
+
? summary.join("\n")
|
|
667
|
+
: content.length > 0
|
|
668
|
+
? content.join("\n")
|
|
669
|
+
: this.assembledReasoning(pending, item.id);
|
|
670
|
+
pending.completedReasoningItems.add(item.id);
|
|
671
|
+
this.emit({ type: "reasoning_finished", text: assembled, contentIndex: 0 });
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (item.type === "contextCompaction") {
|
|
675
|
+
this.emit({
|
|
676
|
+
type: "compaction_end",
|
|
677
|
+
reason: "codex_context_compaction",
|
|
678
|
+
result: redactCodexNativeValue(item),
|
|
679
|
+
aborted: false,
|
|
680
|
+
});
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const descriptor = this.ensureToolStarted(pending, item);
|
|
684
|
+
if (!descriptor || pending.completedTools.has(item.id))
|
|
685
|
+
return;
|
|
686
|
+
pending.completedTools.add(item.id);
|
|
687
|
+
this.emit({
|
|
688
|
+
type: "tool_execution_finished",
|
|
689
|
+
toolCallId: item.id,
|
|
690
|
+
toolName: descriptor.name,
|
|
691
|
+
result: toolResult(item),
|
|
692
|
+
isError: toolFailed(item),
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
assistantIndex(pending, itemId) {
|
|
696
|
+
const existing = pending.assistantIndices.get(itemId);
|
|
697
|
+
if (existing !== undefined)
|
|
698
|
+
return existing;
|
|
699
|
+
const selected = pending.nextAssistantIndex++;
|
|
700
|
+
pending.assistantIndices.set(itemId, selected);
|
|
701
|
+
return selected;
|
|
702
|
+
}
|
|
703
|
+
ensureReasoningStarted(pending, itemId) {
|
|
704
|
+
this.ensureTurnStarted(pending);
|
|
705
|
+
this.rememberItem(pending, pending.items.get(itemId) ?? { id: itemId, type: "reasoning", summary: [], content: [] });
|
|
706
|
+
if (pending.reasoningStartedItems.has(itemId))
|
|
707
|
+
return;
|
|
708
|
+
pending.reasoningStartedItems.add(itemId);
|
|
709
|
+
this.emit({ type: "reasoning_started", contentIndex: 0 });
|
|
710
|
+
}
|
|
711
|
+
reasoningParts(store, itemId) {
|
|
712
|
+
let selected = store.get(itemId);
|
|
713
|
+
if (!selected) {
|
|
714
|
+
selected = new Map();
|
|
715
|
+
store.set(itemId, selected);
|
|
716
|
+
}
|
|
717
|
+
return selected;
|
|
718
|
+
}
|
|
719
|
+
assembledReasoning(pending, itemId) {
|
|
720
|
+
const assemble = (parts) => [...(parts?.entries() ?? [])]
|
|
721
|
+
.sort(([left], [right]) => left - right)
|
|
722
|
+
.map(([, text]) => text)
|
|
723
|
+
.filter(Boolean)
|
|
724
|
+
.join("\n");
|
|
725
|
+
return assemble(pending.reasoningSummaryParts.get(itemId)) || assemble(pending.reasoningContentParts.get(itemId));
|
|
726
|
+
}
|
|
727
|
+
ensureToolStarted(pending, item) {
|
|
728
|
+
const descriptor = toolDescriptor(item);
|
|
729
|
+
if (!descriptor)
|
|
730
|
+
return undefined;
|
|
731
|
+
const existing = pending.startedTools.get(item.id);
|
|
732
|
+
if (existing)
|
|
733
|
+
return existing;
|
|
734
|
+
pending.startedTools.set(item.id, descriptor);
|
|
735
|
+
this.emit({
|
|
736
|
+
type: "tool_call",
|
|
737
|
+
toolCallId: item.id,
|
|
738
|
+
toolName: descriptor.name,
|
|
739
|
+
args: descriptor.args,
|
|
740
|
+
argsComplete: true,
|
|
741
|
+
});
|
|
742
|
+
this.emit({
|
|
743
|
+
type: "tool_execution_started",
|
|
744
|
+
toolCallId: item.id,
|
|
745
|
+
toolName: descriptor.name,
|
|
746
|
+
args: descriptor.args,
|
|
747
|
+
});
|
|
748
|
+
return descriptor;
|
|
749
|
+
}
|
|
750
|
+
finishTurn(pending, turn) {
|
|
751
|
+
if (pending.terminal || this.pending !== pending)
|
|
752
|
+
return;
|
|
753
|
+
if (turn.status === "inProgress")
|
|
754
|
+
throw new CodexNativeTurnProtocolError("Codex turn/completed reported an in-progress turn.");
|
|
755
|
+
this.assignTurnId(pending, turn.id);
|
|
756
|
+
this.ensureTurnStarted(pending, turn);
|
|
757
|
+
for (const item of turn.items)
|
|
758
|
+
this.completeItem(pending, item);
|
|
759
|
+
this.finishIncompleteItems(pending, turn.status);
|
|
760
|
+
if (pending.usage)
|
|
761
|
+
this.emit({ type: "usage", usage: pending.usage });
|
|
762
|
+
const persistedItems = pending.itemOrder
|
|
763
|
+
.map((itemId) => pending.items.get(itemId))
|
|
764
|
+
.filter((item) => item !== undefined);
|
|
765
|
+
const persistedTurn = {
|
|
766
|
+
...turn,
|
|
767
|
+
items: persistedItems,
|
|
768
|
+
itemsView: "full",
|
|
769
|
+
};
|
|
770
|
+
this.threads.recordTurn(persistedTurn);
|
|
771
|
+
pending.terminal = true;
|
|
772
|
+
this.pending = undefined;
|
|
773
|
+
if (turn.status === "failed") {
|
|
774
|
+
const message = pending.lastError?.message ?? errorMessage(turn.error);
|
|
775
|
+
const details = pending.lastError?.details ?? providerErrorDetails(turn.error, message, false);
|
|
776
|
+
this.emit({ type: "turn_failed", message, details, turnId: turn.id });
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
this.emit({ type: "turn_completed", status: turn.status, turnId: turn.id });
|
|
780
|
+
}
|
|
781
|
+
pending.completion.resolve(persistedTurn);
|
|
782
|
+
}
|
|
783
|
+
finishIncompleteItems(pending, status) {
|
|
784
|
+
for (const [itemId, text] of pending.assistantBuffers) {
|
|
785
|
+
if (pending.completedAssistantItems.has(itemId) || !text)
|
|
786
|
+
continue;
|
|
787
|
+
pending.completedAssistantItems.add(itemId);
|
|
788
|
+
this.emit({ type: "assistant_message", text, contentIndex: this.assistantIndex(pending, itemId) });
|
|
789
|
+
}
|
|
790
|
+
for (const itemId of pending.reasoningStartedItems) {
|
|
791
|
+
if (pending.completedReasoningItems.has(itemId))
|
|
792
|
+
continue;
|
|
793
|
+
pending.completedReasoningItems.add(itemId);
|
|
794
|
+
this.emit({
|
|
795
|
+
type: "reasoning_finished",
|
|
796
|
+
text: this.assembledReasoning(pending, itemId),
|
|
797
|
+
contentIndex: 0,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
for (const [itemId, descriptor] of pending.startedTools) {
|
|
801
|
+
if (pending.completedTools.has(itemId))
|
|
802
|
+
continue;
|
|
803
|
+
pending.completedTools.add(itemId);
|
|
804
|
+
this.emit({
|
|
805
|
+
type: "tool_execution_finished",
|
|
806
|
+
toolCallId: itemId,
|
|
807
|
+
toolName: descriptor.name,
|
|
808
|
+
result: { status, message: "Codex turn ended before item/completed." },
|
|
809
|
+
isError: true,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
handleDiagnostic(diagnostic) {
|
|
814
|
+
if (this.disposed)
|
|
815
|
+
return;
|
|
816
|
+
const safeMessage = safeDiagnosticText(diagnostic.message);
|
|
817
|
+
if (diagnostic.level === "warning") {
|
|
818
|
+
if (this.pending)
|
|
819
|
+
this.emit({ type: "warning", message: safeMessage, details: { code: diagnostic.code } });
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const pending = this.pending;
|
|
823
|
+
if (!pending || pending.terminal)
|
|
824
|
+
return;
|
|
825
|
+
this.failActiveTurn(pending, new CodexAppServerClientError("process_exited", safeMessage), runtimeSessionErrorDetails(safeMessage));
|
|
826
|
+
}
|
|
827
|
+
handleProtocolFailure(error) {
|
|
828
|
+
const pending = this.pending;
|
|
829
|
+
if (!pending || pending.terminal)
|
|
830
|
+
return;
|
|
831
|
+
const normalized = error instanceof Error ? error : new CodexNativeTurnProtocolError("Codex emitted an invalid turn notification.");
|
|
832
|
+
const message = safeDiagnosticText(normalized.message);
|
|
833
|
+
this.failActiveTurn(pending, normalized, runtimeSessionErrorDetails(message));
|
|
834
|
+
void this.client.close().catch(() => { });
|
|
835
|
+
}
|
|
836
|
+
failActiveTurn(pending, error, details) {
|
|
837
|
+
if (pending.terminal || this.pending !== pending)
|
|
838
|
+
return;
|
|
839
|
+
pending.terminal = true;
|
|
840
|
+
this.ensureTurnStarted(pending);
|
|
841
|
+
this.finishIncompleteItems(pending, "failed");
|
|
842
|
+
this.threads.setStatus({ type: "systemError" });
|
|
843
|
+
this.pending = undefined;
|
|
844
|
+
this.resolveTurnId(pending, undefined);
|
|
845
|
+
const message = safeDiagnosticText(error.message || "Codex native turn failed.");
|
|
846
|
+
this.emit({ type: "turn_failed", message, details, turnId: pending.turnId });
|
|
847
|
+
pending.completion.reject(error);
|
|
848
|
+
}
|
|
849
|
+
}
|