@pasko70/pibo 1.16.5 → 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-QXwINWYY.js → dist-9v2ZXT6V.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CvZNkkOi.js → dist-B1LVAytl.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-_SP-MjLq.js → dist-C6LPDFXU.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bad4Yz4P.js → dist-DVtXxfHc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-xy-zomwl.js → dist-DW4Qrfbd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DI4i80O2.js → dist-DmbsHdMC.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D17yAUqp.js → dist-DyvwuSFO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bc3Vbgjh.js → dist-I26VB0QO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BzqH7w58.js → dist-RtHbJRPV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CMDJ-WZm.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-C45B0rw7.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
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.16.5.vsix +0 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
const DEFAULT_STARTUP_TIMEOUT_MS = 10_000;
|
|
3
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
|
|
4
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 2_000;
|
|
5
|
+
const DEFAULT_KILL_TIMEOUT_MS = 500;
|
|
6
|
+
const DEFAULT_MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
7
|
+
const DEFAULT_MAX_PENDING_REQUESTS = 128;
|
|
8
|
+
const DEFAULT_MAX_STDERR_BYTES = 64 * 1024;
|
|
9
|
+
const DEFAULT_MAX_DIAGNOSTICS = 50;
|
|
10
|
+
const DEFAULT_OVERLOAD_RETRIES = 3;
|
|
11
|
+
const DEFAULT_OVERLOAD_RETRY_BASE_MS = 100;
|
|
12
|
+
const DEFAULT_OVERLOAD_RETRY_MAX_MS = 2_000;
|
|
13
|
+
const DEFAULT_OVERLOAD_RETRY_JITTER = 0.2;
|
|
14
|
+
const CODEX_APP_SERVER_OVERLOADED = -32001;
|
|
15
|
+
export class CodexAppServerClientError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
constructor(code, message, options) {
|
|
18
|
+
super(message, options);
|
|
19
|
+
this.name = "CodexAppServerClientError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class CodexAppServerServerRequestCancelledError extends Error {
|
|
24
|
+
constructor(message = "Codex App Server request was resolved before the client response.") {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "CodexAppServerServerRequestCancelledError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class CodexAppServerRpcResponseError extends Error {
|
|
30
|
+
rpcCode;
|
|
31
|
+
data;
|
|
32
|
+
constructor(error) {
|
|
33
|
+
super(redactCodexAppServerDiagnostic(error.message));
|
|
34
|
+
this.name = "CodexAppServerRpcResponseError";
|
|
35
|
+
this.rpcCode = error.code;
|
|
36
|
+
this.data = error.data;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function deferred() {
|
|
40
|
+
let resolve;
|
|
41
|
+
let reject;
|
|
42
|
+
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
43
|
+
resolve = resolvePromise;
|
|
44
|
+
reject = rejectPromise;
|
|
45
|
+
});
|
|
46
|
+
return { promise, resolve, reject };
|
|
47
|
+
}
|
|
48
|
+
function positiveInteger(value, fallback, name) {
|
|
49
|
+
const selected = value ?? fallback;
|
|
50
|
+
if (!Number.isSafeInteger(selected) || selected <= 0) {
|
|
51
|
+
throw new Error(`${name} must be a positive safe integer`);
|
|
52
|
+
}
|
|
53
|
+
return selected;
|
|
54
|
+
}
|
|
55
|
+
function nonNegativeInteger(value, fallback, name) {
|
|
56
|
+
const selected = value ?? fallback;
|
|
57
|
+
if (!Number.isSafeInteger(selected) || selected < 0) {
|
|
58
|
+
throw new Error(`${name} must be a non-negative safe integer`);
|
|
59
|
+
}
|
|
60
|
+
return selected;
|
|
61
|
+
}
|
|
62
|
+
function boundedRatio(value, fallback, name) {
|
|
63
|
+
const selected = value ?? fallback;
|
|
64
|
+
if (!Number.isFinite(selected) || selected < 0 || selected > 1) {
|
|
65
|
+
throw new Error(`${name} must be between 0 and 1`);
|
|
66
|
+
}
|
|
67
|
+
return selected;
|
|
68
|
+
}
|
|
69
|
+
function isRecord(value) {
|
|
70
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
71
|
+
}
|
|
72
|
+
function isRequestId(value) {
|
|
73
|
+
return typeof value === "string" || (typeof value === "number" && Number.isSafeInteger(value));
|
|
74
|
+
}
|
|
75
|
+
function requestIdKey(value) {
|
|
76
|
+
return `${typeof value}:${String(value)}`;
|
|
77
|
+
}
|
|
78
|
+
function validateInitializeResponse(value) {
|
|
79
|
+
if (!isRecord(value))
|
|
80
|
+
throw new CodexAppServerClientError("protocol_error", "Codex initialize returned a non-object result");
|
|
81
|
+
const { codexHome, platformFamily, platformOs, userAgent } = value;
|
|
82
|
+
for (const [field, fieldValue] of Object.entries({ codexHome, platformFamily, platformOs, userAgent })) {
|
|
83
|
+
if (typeof fieldValue !== "string") {
|
|
84
|
+
throw new CodexAppServerClientError("protocol_error", `Codex initialize result is missing ${field}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
codexHome: codexHome,
|
|
89
|
+
platformFamily: platformFamily,
|
|
90
|
+
platformOs: platformOs,
|
|
91
|
+
userAgent: userAgent,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function redactCodexAppServerDiagnostic(value) {
|
|
95
|
+
return value
|
|
96
|
+
.replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
|
|
97
|
+
.replace(/\b(?:sk|pk|ghp|github_pat|pibo)[-_][A-Za-z0-9_-]{8,}\b/g, "[redacted]")
|
|
98
|
+
.replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, "[redacted]")
|
|
99
|
+
.replace(/\b(access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|token|secret|password)\b(\s*["']?\s*[:=]\s*["']?)([^\s"'&,}]+)/gi, "$1$2[redacted]")
|
|
100
|
+
.replace(/`(?:\/|[A-Za-z]:[\\/])[^`\r\n]+`/g, "`[redacted path]`")
|
|
101
|
+
.replace(/\bfile:\/\/\/?[^\s"'`,)]+/gi, "file://[redacted path]")
|
|
102
|
+
.slice(0, 4_000);
|
|
103
|
+
}
|
|
104
|
+
function validateClientOptions(options) {
|
|
105
|
+
if (!options.command.trim())
|
|
106
|
+
throw new Error("command is required");
|
|
107
|
+
if (!options.clientInfo.name.trim() || !options.clientInfo.version.trim()) {
|
|
108
|
+
throw new Error("clientInfo.name and clientInfo.version are required");
|
|
109
|
+
}
|
|
110
|
+
positiveInteger(options.startupTimeoutMs, DEFAULT_STARTUP_TIMEOUT_MS, "startupTimeoutMs");
|
|
111
|
+
positiveInteger(options.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS, "requestTimeoutMs");
|
|
112
|
+
positiveInteger(options.shutdownTimeoutMs, DEFAULT_SHUTDOWN_TIMEOUT_MS, "shutdownTimeoutMs");
|
|
113
|
+
positiveInteger(options.killTimeoutMs, DEFAULT_KILL_TIMEOUT_MS, "killTimeoutMs");
|
|
114
|
+
positiveInteger(options.maxMessageBytes, DEFAULT_MAX_MESSAGE_BYTES, "maxMessageBytes");
|
|
115
|
+
positiveInteger(options.maxPendingRequests, DEFAULT_MAX_PENDING_REQUESTS, "maxPendingRequests");
|
|
116
|
+
nonNegativeInteger(options.maxStderrBytes, DEFAULT_MAX_STDERR_BYTES, "maxStderrBytes");
|
|
117
|
+
nonNegativeInteger(options.overloadRetry?.maxRetries, DEFAULT_OVERLOAD_RETRIES, "overloadRetry.maxRetries");
|
|
118
|
+
nonNegativeInteger(options.overloadRetry?.baseDelayMs, DEFAULT_OVERLOAD_RETRY_BASE_MS, "overloadRetry.baseDelayMs");
|
|
119
|
+
nonNegativeInteger(options.overloadRetry?.maxDelayMs, DEFAULT_OVERLOAD_RETRY_MAX_MS, "overloadRetry.maxDelayMs");
|
|
120
|
+
boundedRatio(options.overloadRetry?.jitterRatio, DEFAULT_OVERLOAD_RETRY_JITTER, "overloadRetry.jitterRatio");
|
|
121
|
+
if (options.fileCreationMask !== undefined
|
|
122
|
+
&& (!Number.isSafeInteger(options.fileCreationMask) || options.fileCreationMask < 0 || options.fileCreationMask > 0o777)) {
|
|
123
|
+
throw new Error("fileCreationMask must be an integer between 0 and 0o777");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function serializeMessage(message, maxMessageBytes) {
|
|
127
|
+
let serialized;
|
|
128
|
+
try {
|
|
129
|
+
serialized = JSON.stringify(message);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
throw new CodexAppServerClientError("serialization_error", "Codex App Server message is not JSON serializable", { cause: error });
|
|
133
|
+
}
|
|
134
|
+
if (serialized === undefined) {
|
|
135
|
+
throw new CodexAppServerClientError("serialization_error", "Codex App Server message serialized to undefined");
|
|
136
|
+
}
|
|
137
|
+
if (Buffer.byteLength(serialized, "utf8") > maxMessageBytes) {
|
|
138
|
+
throw new CodexAppServerClientError("message_too_large", `Codex App Server message exceeds ${maxMessageBytes} bytes`);
|
|
139
|
+
}
|
|
140
|
+
return `${serialized}\n`;
|
|
141
|
+
}
|
|
142
|
+
function timeoutError(label) {
|
|
143
|
+
return new CodexAppServerClientError("timeout", `${label} timed out`);
|
|
144
|
+
}
|
|
145
|
+
async function waitWithTimeout(promise, timeoutMs, label) {
|
|
146
|
+
let timer;
|
|
147
|
+
try {
|
|
148
|
+
return await Promise.race([
|
|
149
|
+
promise,
|
|
150
|
+
new Promise((_resolve, reject) => {
|
|
151
|
+
timer = setTimeout(() => reject(timeoutError(label)), timeoutMs);
|
|
152
|
+
}),
|
|
153
|
+
]);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
if (timer)
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function sleep(delayMs, signal) {
|
|
161
|
+
if (delayMs <= 0)
|
|
162
|
+
return;
|
|
163
|
+
if (signal?.aborted)
|
|
164
|
+
throw new CodexAppServerClientError("aborted", "Codex App Server request was aborted");
|
|
165
|
+
await new Promise((resolve, reject) => {
|
|
166
|
+
const timer = setTimeout(() => {
|
|
167
|
+
cleanup();
|
|
168
|
+
resolve();
|
|
169
|
+
}, delayMs);
|
|
170
|
+
const onAbort = () => {
|
|
171
|
+
cleanup();
|
|
172
|
+
reject(new CodexAppServerClientError("aborted", "Codex App Server request was aborted"));
|
|
173
|
+
};
|
|
174
|
+
const cleanup = () => {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
signal?.removeEventListener("abort", onAbort);
|
|
177
|
+
};
|
|
178
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export class CodexAppServerClient {
|
|
182
|
+
initializeResponse;
|
|
183
|
+
process;
|
|
184
|
+
startupTimeoutMs;
|
|
185
|
+
requestTimeoutMs;
|
|
186
|
+
shutdownTimeoutMs;
|
|
187
|
+
killTimeoutMs;
|
|
188
|
+
maxMessageBytes;
|
|
189
|
+
maxPendingRequests;
|
|
190
|
+
maxStderrBytes;
|
|
191
|
+
overloadMaxRetries;
|
|
192
|
+
overloadBaseDelayMs;
|
|
193
|
+
overloadMaxDelayMs;
|
|
194
|
+
overloadJitterRatio;
|
|
195
|
+
random;
|
|
196
|
+
onDiagnostic;
|
|
197
|
+
pending = new Map();
|
|
198
|
+
notificationListeners = new Set();
|
|
199
|
+
diagnosticListeners = new Set();
|
|
200
|
+
diagnostics = [];
|
|
201
|
+
spawned = deferred();
|
|
202
|
+
processClosed = deferred();
|
|
203
|
+
stderrChunks = [];
|
|
204
|
+
stdoutBuffer = Buffer.alloc(0);
|
|
205
|
+
stderrBytes = 0;
|
|
206
|
+
stderrTruncated = false;
|
|
207
|
+
nextRequestId = 0;
|
|
208
|
+
writeTail = Promise.resolve();
|
|
209
|
+
writeBackpressureCount = 0;
|
|
210
|
+
state = "starting";
|
|
211
|
+
processCloseResult;
|
|
212
|
+
serverRequestHandler;
|
|
213
|
+
closePromise;
|
|
214
|
+
stopPromise;
|
|
215
|
+
constructor(process, options, initializeResponse) {
|
|
216
|
+
this.process = process;
|
|
217
|
+
this.initializeResponse = initializeResponse;
|
|
218
|
+
this.startupTimeoutMs = positiveInteger(options.startupTimeoutMs, DEFAULT_STARTUP_TIMEOUT_MS, "startupTimeoutMs");
|
|
219
|
+
this.requestTimeoutMs = positiveInteger(options.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS, "requestTimeoutMs");
|
|
220
|
+
this.shutdownTimeoutMs = positiveInteger(options.shutdownTimeoutMs, DEFAULT_SHUTDOWN_TIMEOUT_MS, "shutdownTimeoutMs");
|
|
221
|
+
this.killTimeoutMs = positiveInteger(options.killTimeoutMs, DEFAULT_KILL_TIMEOUT_MS, "killTimeoutMs");
|
|
222
|
+
this.maxMessageBytes = positiveInteger(options.maxMessageBytes, DEFAULT_MAX_MESSAGE_BYTES, "maxMessageBytes");
|
|
223
|
+
this.maxPendingRequests = positiveInteger(options.maxPendingRequests, DEFAULT_MAX_PENDING_REQUESTS, "maxPendingRequests");
|
|
224
|
+
this.maxStderrBytes = nonNegativeInteger(options.maxStderrBytes, DEFAULT_MAX_STDERR_BYTES, "maxStderrBytes");
|
|
225
|
+
this.overloadMaxRetries = nonNegativeInteger(options.overloadRetry?.maxRetries, DEFAULT_OVERLOAD_RETRIES, "overloadRetry.maxRetries");
|
|
226
|
+
this.overloadBaseDelayMs = nonNegativeInteger(options.overloadRetry?.baseDelayMs, DEFAULT_OVERLOAD_RETRY_BASE_MS, "overloadRetry.baseDelayMs");
|
|
227
|
+
this.overloadMaxDelayMs = nonNegativeInteger(options.overloadRetry?.maxDelayMs, DEFAULT_OVERLOAD_RETRY_MAX_MS, "overloadRetry.maxDelayMs");
|
|
228
|
+
this.overloadJitterRatio = boundedRatio(options.overloadRetry?.jitterRatio, DEFAULT_OVERLOAD_RETRY_JITTER, "overloadRetry.jitterRatio");
|
|
229
|
+
this.random = options.random ?? Math.random;
|
|
230
|
+
this.onDiagnostic = options.onDiagnostic;
|
|
231
|
+
}
|
|
232
|
+
static async start(options) {
|
|
233
|
+
validateClientOptions(options);
|
|
234
|
+
let previousMask;
|
|
235
|
+
if (options.fileCreationMask !== undefined && globalThis.process.platform !== "win32") {
|
|
236
|
+
previousMask = globalThis.process.umask(options.fileCreationMask);
|
|
237
|
+
}
|
|
238
|
+
let process;
|
|
239
|
+
try {
|
|
240
|
+
process = spawn(options.command, [...(options.args ?? [])], {
|
|
241
|
+
cwd: options.cwd,
|
|
242
|
+
env: options.env ?? globalThis.process.env,
|
|
243
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
if (previousMask !== undefined)
|
|
248
|
+
globalThis.process.umask(previousMask);
|
|
249
|
+
}
|
|
250
|
+
const placeholder = {
|
|
251
|
+
codexHome: "",
|
|
252
|
+
platformFamily: "",
|
|
253
|
+
platformOs: "",
|
|
254
|
+
userAgent: "",
|
|
255
|
+
};
|
|
256
|
+
const client = new CodexAppServerClient(process, options, placeholder);
|
|
257
|
+
client.attachProcessListeners();
|
|
258
|
+
const deadline = Date.now() + client.startupTimeoutMs;
|
|
259
|
+
try {
|
|
260
|
+
await waitWithTimeout(client.spawned.promise, client.startupTimeoutMs, "Codex App Server spawn");
|
|
261
|
+
client.state = "initializing";
|
|
262
|
+
const initializeResult = await client.requestWithRetry("initialize", {
|
|
263
|
+
clientInfo: { ...options.clientInfo },
|
|
264
|
+
capabilities: options.capabilities ? structuredClone(options.capabilities) : { experimentalApi: false },
|
|
265
|
+
}, {
|
|
266
|
+
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
267
|
+
retryOverloaded: true,
|
|
268
|
+
}, true);
|
|
269
|
+
const initializeResponse = validateInitializeResponse(initializeResult);
|
|
270
|
+
Object.assign(client.initializeResponse, initializeResponse);
|
|
271
|
+
Object.freeze(client.initializeResponse);
|
|
272
|
+
await waitWithTimeout(client.writeMessage({ method: "initialized" }, true), Math.max(1, deadline - Date.now()), "Codex App Server initialized notification");
|
|
273
|
+
client.state = "ready";
|
|
274
|
+
return client;
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
const normalized = client.normalizeStartError(error);
|
|
278
|
+
client.failAll(normalized);
|
|
279
|
+
await client.stopProcess(false).catch(() => { });
|
|
280
|
+
throw normalized;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
get snapshot() {
|
|
284
|
+
return {
|
|
285
|
+
state: this.state,
|
|
286
|
+
...(this.process.pid !== undefined ? { pid: this.process.pid } : {}),
|
|
287
|
+
pendingRequests: this.pending.size,
|
|
288
|
+
writeBackpressureCount: this.writeBackpressureCount,
|
|
289
|
+
stderrBytes: this.stderrBytes,
|
|
290
|
+
stderrTruncated: this.stderrTruncated,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
getStderrDiagnostic() {
|
|
294
|
+
return redactCodexAppServerDiagnostic(Buffer.concat(this.stderrChunks).toString("utf8"));
|
|
295
|
+
}
|
|
296
|
+
getDiagnostics() {
|
|
297
|
+
return this.diagnostics.map((diagnostic) => ({ ...diagnostic }));
|
|
298
|
+
}
|
|
299
|
+
subscribeNotifications(listener) {
|
|
300
|
+
this.notificationListeners.add(listener);
|
|
301
|
+
return () => this.notificationListeners.delete(listener);
|
|
302
|
+
}
|
|
303
|
+
subscribeDiagnostics(listener) {
|
|
304
|
+
this.diagnosticListeners.add(listener);
|
|
305
|
+
return () => this.diagnosticListeners.delete(listener);
|
|
306
|
+
}
|
|
307
|
+
setServerRequestHandler(handler) {
|
|
308
|
+
this.serverRequestHandler = handler;
|
|
309
|
+
}
|
|
310
|
+
async request(method, params, options = {}) {
|
|
311
|
+
this.assertReady();
|
|
312
|
+
if (method === "initialize") {
|
|
313
|
+
throw new CodexAppServerClientError("protocol_error", "Codex App Server initialize may only be sent once during startup");
|
|
314
|
+
}
|
|
315
|
+
return await this.requestWithRetry(method, params, options, false);
|
|
316
|
+
}
|
|
317
|
+
async notify(method, params) {
|
|
318
|
+
this.assertReady();
|
|
319
|
+
if (method === "initialized") {
|
|
320
|
+
throw new CodexAppServerClientError("protocol_error", "Codex App Server initialized may only be sent once during startup");
|
|
321
|
+
}
|
|
322
|
+
await this.writeMessage({ method, ...(params === undefined ? {} : { params }) }, false);
|
|
323
|
+
}
|
|
324
|
+
async close() {
|
|
325
|
+
if (this.closePromise)
|
|
326
|
+
return this.closePromise;
|
|
327
|
+
this.closePromise = this.closeUnsafe();
|
|
328
|
+
return this.closePromise;
|
|
329
|
+
}
|
|
330
|
+
attachProcessListeners() {
|
|
331
|
+
this.process.once("spawn", () => this.spawned.resolve());
|
|
332
|
+
this.process.once("error", (error) => {
|
|
333
|
+
const wrapped = new CodexAppServerClientError("spawn_failed", "Codex App Server process failed to start", { cause: error });
|
|
334
|
+
this.spawned.reject(wrapped);
|
|
335
|
+
if (this.state !== "closing" && this.state !== "closed") {
|
|
336
|
+
this.state = "failed";
|
|
337
|
+
this.failAll(wrapped);
|
|
338
|
+
this.emitDiagnostic({ level: "error", code: "codex_process_error", message: wrapped.message });
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
this.process.once("close", (code, signal) => this.handleProcessClose(code, signal));
|
|
342
|
+
this.process.stdout.on("data", (chunk) => this.handleStdoutChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
343
|
+
this.process.stderr.on("data", (chunk) => this.captureStderr(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
344
|
+
}
|
|
345
|
+
normalizeStartError(error) {
|
|
346
|
+
if (error instanceof Error)
|
|
347
|
+
return error;
|
|
348
|
+
return new CodexAppServerClientError("spawn_failed", "Codex App Server startup failed");
|
|
349
|
+
}
|
|
350
|
+
assertReady() {
|
|
351
|
+
if (this.state === "ready")
|
|
352
|
+
return;
|
|
353
|
+
throw new CodexAppServerClientError("closed", `Codex App Server client is ${this.state}`);
|
|
354
|
+
}
|
|
355
|
+
async requestWithRetry(method, params, options, allowBeforeReady) {
|
|
356
|
+
if (!allowBeforeReady)
|
|
357
|
+
this.assertReady();
|
|
358
|
+
const timeoutMs = positiveInteger(options.timeoutMs, this.requestTimeoutMs, "request timeoutMs");
|
|
359
|
+
const deadline = Date.now() + timeoutMs;
|
|
360
|
+
const maxRetries = options.retryOverloaded === false ? 0 : this.overloadMaxRetries;
|
|
361
|
+
let attempt = 0;
|
|
362
|
+
while (true) {
|
|
363
|
+
if (options.signal?.aborted) {
|
|
364
|
+
throw new CodexAppServerClientError("aborted", "Codex App Server request was aborted");
|
|
365
|
+
}
|
|
366
|
+
const remaining = deadline - Date.now();
|
|
367
|
+
if (remaining <= 0)
|
|
368
|
+
throw timeoutError(`Codex App Server ${method} request`);
|
|
369
|
+
try {
|
|
370
|
+
return await this.requestOnce(method, params, remaining, options.signal, allowBeforeReady);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
if (!(error instanceof CodexAppServerRpcResponseError) || error.rpcCode !== CODEX_APP_SERVER_OVERLOADED || attempt >= maxRetries) {
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
const delayMs = this.overloadDelay(attempt);
|
|
377
|
+
attempt += 1;
|
|
378
|
+
if (delayMs >= deadline - Date.now())
|
|
379
|
+
throw timeoutError(`Codex App Server ${method} request`);
|
|
380
|
+
await sleep(delayMs, options.signal);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
overloadDelay(attempt) {
|
|
385
|
+
const exponential = Math.min(this.overloadMaxDelayMs, this.overloadBaseDelayMs * 2 ** attempt);
|
|
386
|
+
const jitter = exponential * this.overloadJitterRatio * (this.random() * 2 - 1);
|
|
387
|
+
return Math.max(0, Math.round(exponential + jitter));
|
|
388
|
+
}
|
|
389
|
+
async requestOnce(method, params, timeoutMs, signal, allowBeforeReady) {
|
|
390
|
+
if (!allowBeforeReady)
|
|
391
|
+
this.assertReady();
|
|
392
|
+
if (this.state === "closing" || this.state === "closed" || this.state === "failed") {
|
|
393
|
+
throw new CodexAppServerClientError("closed", `Codex App Server client is ${this.state}`);
|
|
394
|
+
}
|
|
395
|
+
if (this.pending.size >= this.maxPendingRequests) {
|
|
396
|
+
throw new CodexAppServerClientError("pending_limit", `Codex App Server pending request limit ${this.maxPendingRequests} reached`);
|
|
397
|
+
}
|
|
398
|
+
const id = this.allocateRequestId();
|
|
399
|
+
const key = requestIdKey(id);
|
|
400
|
+
let pending;
|
|
401
|
+
const response = new Promise((resolve, reject) => {
|
|
402
|
+
const timer = setTimeout(() => {
|
|
403
|
+
this.settlePending(key, undefined, timeoutError(`Codex App Server ${method} request`));
|
|
404
|
+
}, timeoutMs);
|
|
405
|
+
pending = { resolve, reject, timer, signal };
|
|
406
|
+
if (signal) {
|
|
407
|
+
pending.onAbort = () => {
|
|
408
|
+
this.settlePending(key, undefined, new CodexAppServerClientError("aborted", "Codex App Server request was aborted"));
|
|
409
|
+
};
|
|
410
|
+
signal.addEventListener("abort", pending.onAbort, { once: true });
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
this.pending.set(key, pending);
|
|
414
|
+
void response.catch(() => { });
|
|
415
|
+
if (signal?.aborted) {
|
|
416
|
+
this.settlePending(key, undefined, new CodexAppServerClientError("aborted", "Codex App Server request was aborted"));
|
|
417
|
+
return await response;
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
await this.writeMessage({ id, method, ...(params === undefined ? {} : { params }) }, allowBeforeReady);
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
this.settlePending(key, undefined, error instanceof Error ? error : new CodexAppServerClientError("write_failed", "Codex App Server request write failed"));
|
|
424
|
+
}
|
|
425
|
+
return await response;
|
|
426
|
+
}
|
|
427
|
+
allocateRequestId() {
|
|
428
|
+
const id = this.nextRequestId;
|
|
429
|
+
this.nextRequestId = this.nextRequestId >= Number.MAX_SAFE_INTEGER ? 0 : this.nextRequestId + 1;
|
|
430
|
+
return id;
|
|
431
|
+
}
|
|
432
|
+
writeMessage(message, allowBeforeReady) {
|
|
433
|
+
if (!allowBeforeReady && this.state !== "ready") {
|
|
434
|
+
return Promise.reject(new CodexAppServerClientError("closed", `Codex App Server client is ${this.state}`));
|
|
435
|
+
}
|
|
436
|
+
const line = serializeMessage(message, this.maxMessageBytes);
|
|
437
|
+
const write = this.writeTail.then(() => this.writeLine(line, allowBeforeReady));
|
|
438
|
+
this.writeTail = write.catch(() => { });
|
|
439
|
+
return write;
|
|
440
|
+
}
|
|
441
|
+
async writeLine(line, allowBeforeReady) {
|
|
442
|
+
if (this.state === "closing" || this.state === "closed" || this.state === "failed") {
|
|
443
|
+
throw new CodexAppServerClientError("closed", `Codex App Server client is ${this.state}`);
|
|
444
|
+
}
|
|
445
|
+
if (!allowBeforeReady && this.state !== "ready") {
|
|
446
|
+
throw new CodexAppServerClientError("closed", `Codex App Server client is ${this.state}`);
|
|
447
|
+
}
|
|
448
|
+
if (allowBeforeReady && this.state !== "initializing" && this.state !== "ready") {
|
|
449
|
+
throw new CodexAppServerClientError("closed", `Codex App Server client is ${this.state}`);
|
|
450
|
+
}
|
|
451
|
+
if (this.process.stdin.destroyed || !this.process.stdin.writable) {
|
|
452
|
+
throw new CodexAppServerClientError("write_failed", "Codex App Server stdin is not writable");
|
|
453
|
+
}
|
|
454
|
+
let accepted;
|
|
455
|
+
try {
|
|
456
|
+
accepted = this.process.stdin.write(line, "utf8");
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
throw new CodexAppServerClientError("write_failed", "Codex App Server stdin write failed", { cause: error });
|
|
460
|
+
}
|
|
461
|
+
if (accepted)
|
|
462
|
+
return;
|
|
463
|
+
this.writeBackpressureCount += 1;
|
|
464
|
+
await new Promise((resolve, reject) => {
|
|
465
|
+
const onDrain = () => {
|
|
466
|
+
cleanup();
|
|
467
|
+
resolve();
|
|
468
|
+
};
|
|
469
|
+
const onError = (error) => {
|
|
470
|
+
cleanup();
|
|
471
|
+
reject(new CodexAppServerClientError("write_failed", "Codex App Server stdin write failed", { cause: error }));
|
|
472
|
+
};
|
|
473
|
+
const onClose = () => {
|
|
474
|
+
cleanup();
|
|
475
|
+
reject(new CodexAppServerClientError("process_exited", "Codex App Server exited while a write was waiting for drain"));
|
|
476
|
+
};
|
|
477
|
+
const cleanup = () => {
|
|
478
|
+
this.process.stdin.removeListener("drain", onDrain);
|
|
479
|
+
this.process.stdin.removeListener("error", onError);
|
|
480
|
+
this.process.removeListener("close", onClose);
|
|
481
|
+
};
|
|
482
|
+
this.process.stdin.once("drain", onDrain);
|
|
483
|
+
this.process.stdin.once("error", onError);
|
|
484
|
+
this.process.once("close", onClose);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
handleStdoutChunk(chunk) {
|
|
488
|
+
if (this.state === "closed")
|
|
489
|
+
return;
|
|
490
|
+
let offset = 0;
|
|
491
|
+
while (offset < chunk.length) {
|
|
492
|
+
const newline = chunk.indexOf(0x0a, offset);
|
|
493
|
+
const end = newline < 0 ? chunk.length : newline;
|
|
494
|
+
const fragment = chunk.subarray(offset, end);
|
|
495
|
+
if (this.stdoutBuffer.length + fragment.length > this.maxMessageBytes) {
|
|
496
|
+
this.protocolFailure("Codex App Server stdout message exceeded the configured byte limit", "codex_message_too_large", "message_too_large");
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (fragment.length > 0)
|
|
500
|
+
this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, fragment]);
|
|
501
|
+
if (newline < 0)
|
|
502
|
+
return;
|
|
503
|
+
const line = this.stdoutBuffer.length > 0 && this.stdoutBuffer.at(-1) === 0x0d
|
|
504
|
+
? this.stdoutBuffer.subarray(0, -1)
|
|
505
|
+
: this.stdoutBuffer;
|
|
506
|
+
this.stdoutBuffer = Buffer.alloc(0);
|
|
507
|
+
if (line.length > 0)
|
|
508
|
+
this.handleStdoutLine(line);
|
|
509
|
+
if (this.state === "failed")
|
|
510
|
+
return;
|
|
511
|
+
offset = newline + 1;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
handleStdoutLine(line) {
|
|
515
|
+
let message;
|
|
516
|
+
try {
|
|
517
|
+
message = JSON.parse(line.toString("utf8"));
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
this.protocolFailure("Codex App Server emitted malformed JSON", "codex_malformed_json");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (!isRecord(message)) {
|
|
524
|
+
this.protocolFailure("Codex App Server emitted a non-object JSON message", "codex_invalid_message");
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (typeof message.method === "string") {
|
|
528
|
+
if (Object.hasOwn(message, "id")) {
|
|
529
|
+
if (!isRequestId(message.id)) {
|
|
530
|
+
this.protocolFailure("Codex App Server emitted a request with an invalid id", "codex_invalid_request_id");
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
void this.handleServerRequest({
|
|
534
|
+
id: message.id,
|
|
535
|
+
method: message.method,
|
|
536
|
+
...(Object.hasOwn(message, "params") ? { params: message.params } : {}),
|
|
537
|
+
});
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.handleNotification({
|
|
541
|
+
method: message.method,
|
|
542
|
+
...(Object.hasOwn(message, "params") ? { params: message.params } : {}),
|
|
543
|
+
});
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (!Object.hasOwn(message, "id") || !isRequestId(message.id)) {
|
|
547
|
+
this.protocolFailure("Codex App Server emitted a response without a valid id", "codex_invalid_response_id");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const hasResult = Object.hasOwn(message, "result");
|
|
551
|
+
const hasError = Object.hasOwn(message, "error");
|
|
552
|
+
if (hasResult === hasError) {
|
|
553
|
+
this.protocolFailure("Codex App Server response must contain exactly one of result or error", "codex_invalid_response");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const key = requestIdKey(message.id);
|
|
557
|
+
if (!this.pending.has(key)) {
|
|
558
|
+
this.emitDiagnostic({ level: "warning", code: "codex_unknown_response_id", message: "Codex App Server returned an unknown or expired request id" });
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (hasError) {
|
|
562
|
+
if (!isRecord(message.error) || typeof message.error.code !== "number" || typeof message.error.message !== "string") {
|
|
563
|
+
this.protocolFailure("Codex App Server returned a malformed error response", "codex_invalid_error_response");
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
this.settlePending(key, undefined, new CodexAppServerRpcResponseError({
|
|
567
|
+
code: message.error.code,
|
|
568
|
+
message: message.error.message,
|
|
569
|
+
...(Object.hasOwn(message.error, "data") ? { data: message.error.data } : {}),
|
|
570
|
+
}));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
this.settlePending(key, message.result, undefined);
|
|
574
|
+
}
|
|
575
|
+
handleNotification(notification) {
|
|
576
|
+
for (const listener of [...this.notificationListeners]) {
|
|
577
|
+
try {
|
|
578
|
+
listener(notification);
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
this.emitDiagnostic({ level: "warning", code: "codex_notification_listener_failed", message: "A Codex App Server notification listener failed" });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async handleServerRequest(request) {
|
|
586
|
+
const handler = this.serverRequestHandler;
|
|
587
|
+
if (!handler) {
|
|
588
|
+
await this.writeMessage({
|
|
589
|
+
id: request.id,
|
|
590
|
+
error: { code: -32601, message: `Unsupported server request method: ${request.method}` },
|
|
591
|
+
}, true).catch(() => { });
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
const result = await handler(request);
|
|
596
|
+
await this.writeMessage({ id: request.id, result: result === undefined ? null : result }, true);
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
if (error instanceof CodexAppServerServerRequestCancelledError)
|
|
600
|
+
return;
|
|
601
|
+
const message = redactCodexAppServerDiagnostic(error instanceof Error ? error.message : "Server request handler failed");
|
|
602
|
+
await this.writeMessage({ id: request.id, error: { code: -32000, message } }, true).catch(() => { });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
settlePending(key, value, error) {
|
|
606
|
+
const pending = this.pending.get(key);
|
|
607
|
+
if (!pending)
|
|
608
|
+
return;
|
|
609
|
+
this.pending.delete(key);
|
|
610
|
+
clearTimeout(pending.timer);
|
|
611
|
+
if (pending.signal && pending.onAbort)
|
|
612
|
+
pending.signal.removeEventListener("abort", pending.onAbort);
|
|
613
|
+
if (error)
|
|
614
|
+
pending.reject(error);
|
|
615
|
+
else
|
|
616
|
+
pending.resolve(value);
|
|
617
|
+
}
|
|
618
|
+
failAll(error) {
|
|
619
|
+
for (const key of [...this.pending.keys()])
|
|
620
|
+
this.settlePending(key, undefined, error);
|
|
621
|
+
}
|
|
622
|
+
protocolFailure(message, diagnosticCode, errorCode = "protocol_error") {
|
|
623
|
+
if (this.state === "failed" || this.state === "closing" || this.state === "closed")
|
|
624
|
+
return;
|
|
625
|
+
this.state = "failed";
|
|
626
|
+
const error = new CodexAppServerClientError(errorCode, message);
|
|
627
|
+
this.emitDiagnostic({ level: "error", code: diagnosticCode, message });
|
|
628
|
+
this.failAll(error);
|
|
629
|
+
void this.stopProcess(false).catch(() => { });
|
|
630
|
+
}
|
|
631
|
+
captureStderr(chunk) {
|
|
632
|
+
if (this.maxStderrBytes === 0) {
|
|
633
|
+
this.stderrTruncated = this.stderrTruncated || chunk.length > 0;
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const remaining = this.maxStderrBytes - this.stderrBytes;
|
|
637
|
+
if (remaining <= 0) {
|
|
638
|
+
this.stderrTruncated = true;
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const selected = chunk.subarray(0, remaining);
|
|
642
|
+
this.stderrChunks.push(selected);
|
|
643
|
+
this.stderrBytes += selected.length;
|
|
644
|
+
if (selected.length < chunk.length)
|
|
645
|
+
this.stderrTruncated = true;
|
|
646
|
+
}
|
|
647
|
+
handleProcessClose(code, signal) {
|
|
648
|
+
if (this.processCloseResult)
|
|
649
|
+
return;
|
|
650
|
+
const result = { code, signal };
|
|
651
|
+
this.processCloseResult = result;
|
|
652
|
+
this.processClosed.resolve(result);
|
|
653
|
+
const expected = this.state === "closing" || this.state === "closed";
|
|
654
|
+
if (expected) {
|
|
655
|
+
this.state = "closed";
|
|
656
|
+
this.failAll(new CodexAppServerClientError("closed", "Codex App Server client closed"));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this.state = "failed";
|
|
660
|
+
const suffix = this.getStderrDiagnostic();
|
|
661
|
+
const error = new CodexAppServerClientError("process_exited", `Codex App Server exited unexpectedly (code=${code ?? "null"}, signal=${signal ?? "null"})${suffix ? `: ${suffix}` : ""}`);
|
|
662
|
+
this.failAll(error);
|
|
663
|
+
this.emitDiagnostic({ level: "error", code: "codex_process_exited", message: error.message });
|
|
664
|
+
}
|
|
665
|
+
emitDiagnostic(diagnostic) {
|
|
666
|
+
const safe = { ...diagnostic, message: redactCodexAppServerDiagnostic(diagnostic.message) };
|
|
667
|
+
this.diagnostics.push(safe);
|
|
668
|
+
if (this.diagnostics.length > DEFAULT_MAX_DIAGNOSTICS)
|
|
669
|
+
this.diagnostics.shift();
|
|
670
|
+
try {
|
|
671
|
+
this.onDiagnostic?.(safe);
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
// Diagnostics must not destabilize the transport.
|
|
675
|
+
}
|
|
676
|
+
for (const listener of [...this.diagnosticListeners]) {
|
|
677
|
+
try {
|
|
678
|
+
listener(safe);
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
// Diagnostics must not destabilize the transport.
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async closeUnsafe() {
|
|
686
|
+
if (this.state === "closed")
|
|
687
|
+
return;
|
|
688
|
+
this.state = "closing";
|
|
689
|
+
this.failAll(new CodexAppServerClientError("closed", "Codex App Server client is closing"));
|
|
690
|
+
await this.stopProcess(true);
|
|
691
|
+
this.state = "closed";
|
|
692
|
+
}
|
|
693
|
+
stopProcess(graceful) {
|
|
694
|
+
if (this.stopPromise)
|
|
695
|
+
return this.stopPromise;
|
|
696
|
+
this.stopPromise = this.stopProcessUnsafe(graceful);
|
|
697
|
+
return this.stopPromise;
|
|
698
|
+
}
|
|
699
|
+
async stopProcessUnsafe(graceful) {
|
|
700
|
+
if (this.processCloseResult)
|
|
701
|
+
return;
|
|
702
|
+
if (graceful) {
|
|
703
|
+
if (!this.process.stdin.destroyed)
|
|
704
|
+
this.process.stdin.end();
|
|
705
|
+
if (await this.waitForProcessClose(this.shutdownTimeoutMs))
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
else if (!this.process.stdin.destroyed) {
|
|
709
|
+
this.process.stdin.destroy();
|
|
710
|
+
}
|
|
711
|
+
this.process.kill("SIGTERM");
|
|
712
|
+
if (await this.waitForProcessClose(this.killTimeoutMs))
|
|
713
|
+
return;
|
|
714
|
+
this.process.kill("SIGKILL");
|
|
715
|
+
await this.waitForProcessClose(this.killTimeoutMs);
|
|
716
|
+
}
|
|
717
|
+
async waitForProcessClose(timeoutMs) {
|
|
718
|
+
if (this.processCloseResult)
|
|
719
|
+
return true;
|
|
720
|
+
let timer;
|
|
721
|
+
try {
|
|
722
|
+
return await Promise.race([
|
|
723
|
+
this.processClosed.promise.then(() => true),
|
|
724
|
+
new Promise((resolve) => {
|
|
725
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
726
|
+
}),
|
|
727
|
+
]);
|
|
728
|
+
}
|
|
729
|
+
finally {
|
|
730
|
+
if (timer)
|
|
731
|
+
clearTimeout(timer);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|