@shawnstack/quickforge 1.3.18 → 1.3.20
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 +10 -10
- package/bin/quickforge.mjs +258 -49
- package/dist/assets/anthropic-Bj3HAZgj.js +39 -0
- package/dist/assets/azure-openai-responses-IdZZrSrI.js +1 -0
- package/dist/assets/github-copilot-headers-CMb2BbzT.js +1 -0
- package/dist/assets/google-Brt_lS1J.js +1 -0
- package/dist/assets/{google-shared-XhYUKiGZ.js → google-shared-CLc4ziON.js} +3 -3
- package/dist/assets/google-vertex-B6HsoZ34.js +1 -0
- package/dist/assets/{index-Dm7aEWvT.js → index-D0CVLdX_.js} +525 -489
- package/dist/assets/index-D0W9hAl_.css +3 -0
- package/dist/assets/{mistral-DxhS4Wkn.js → mistral-CenXqwPz.js} +3 -3
- package/dist/assets/openai-codex-responses-D9ffGwbj.js +7 -0
- package/dist/assets/openai-completions-eWdeSGBG.js +5 -0
- package/dist/assets/openai-responses-Cavpmjeu.js +1 -0
- package/dist/assets/{openai-responses-shared-f_P3e1nz.js → openai-responses-shared-DF3ZGaUx.js} +5 -3
- package/dist/assets/transform-messages-CmnxG9RB.js +1 -0
- package/dist/index.html +2 -2
- package/node_modules/@anthropic-ai/sdk/CHANGELOG.md +34 -0
- package/node_modules/@anthropic-ai/sdk/bin/migration-config.json +185 -0
- package/node_modules/@anthropic-ai/sdk/package.json +1 -1
- package/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +4 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +4 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/files.js +5 -5
- package/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +5 -5
- package/node_modules/@anthropic-ai/sdk/resources/beta/index.js +11 -9
- package/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +1 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.js +11 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.mjs +5 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.js +130 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.mjs +126 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.js +145 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.mjs +140 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.js +81 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.mjs +77 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.js +6 -0
- package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.mjs +3 -0
- package/node_modules/@anthropic-ai/sdk/tools/memory/node.js +12 -5
- package/node_modules/@anthropic-ai/sdk/tools/memory/node.mjs +12 -5
- package/node_modules/@anthropic-ai/sdk/version.js +1 -1
- package/node_modules/@anthropic-ai/sdk/version.mjs +1 -1
- package/node_modules/@aws-sdk/client-bedrock-runtime/package.json +5 -5
- package/node_modules/@aws-sdk/core/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
- package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
- package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
- package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
- package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
- package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
- package/node_modules/@aws-sdk/token-providers/package.json +3 -3
- package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
- package/node_modules/@mariozechner/pi-agent-core/README.md +14 -0
- package/node_modules/@mariozechner/pi-agent-core/dist/agent-loop.js +9 -0
- package/node_modules/@mariozechner/pi-agent-core/dist/agent.js +1 -1
- package/node_modules/@mariozechner/pi-agent-core/package.json +2 -2
- package/node_modules/@mariozechner/pi-ai/README.md +20 -31
- package/node_modules/@mariozechner/pi-ai/dist/env-api-keys.js +7 -0
- package/node_modules/@mariozechner/pi-ai/dist/index.js +2 -0
- package/node_modules/@mariozechner/pi-ai/dist/models.generated.js +2420 -1213
- package/node_modules/@mariozechner/pi-ai/dist/models.js +28 -20
- package/node_modules/@mariozechner/pi-ai/dist/providers/amazon-bedrock.js +11 -11
- package/node_modules/@mariozechner/pi-ai/dist/providers/anthropic.js +43 -26
- package/node_modules/@mariozechner/pi-ai/dist/providers/azure-openai-responses.js +12 -6
- package/node_modules/@mariozechner/pi-ai/dist/providers/cloudflare.js +10 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-shared.js +4 -13
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-vertex.js +4 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/google.js +4 -3
- package/node_modules/@mariozechner/pi-ai/dist/providers/mistral.js +8 -7
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-codex-responses.js +296 -41
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js +169 -153
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses-shared.js +14 -1
- package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses.js +22 -8
- package/node_modules/@mariozechner/pi-ai/dist/providers/register-builtins.js +0 -18
- package/node_modules/@mariozechner/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@mariozechner/pi-ai/dist/session-resources.js +22 -0
- package/node_modules/@mariozechner/pi-ai/dist/utils/diagnostics.js +25 -0
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/index.js +0 -10
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/openai-codex.js +25 -14
- package/node_modules/@mariozechner/pi-ai/dist/utils/overflow.js +14 -0
- package/node_modules/@mariozechner/pi-ai/package.json +2 -6
- package/package.json +3 -3
- package/server/agent-manager.mjs +279 -12
- package/server/auto-compaction.mjs +1 -2
- package/server/conversation-compaction.mjs +0 -5
- package/server/index.mjs +1 -0
- package/server/routes/static.mjs +1 -0
- package/server/routes/tools.mjs +3 -1
- package/server/session-utils.mjs +6 -1
- package/server/share-store.mjs +27 -4
- package/server/subagents.mjs +101 -0
- package/server/system-prompt.mjs +30 -1
- package/server/tools/definitions.mjs +18 -0
- package/server/tools/index.mjs +1013 -911
- package/dist/assets/anthropic-Ck2DxOfr.js +0 -39
- package/dist/assets/azure-openai-responses-DIoz5q4Z.js +0 -1
- package/dist/assets/github-copilot-headers-CrI0CIJ7.js +0 -1
- package/dist/assets/google-Dau-4ve_.js +0 -1
- package/dist/assets/google-gemini-cli-DttMmbGb.js +0 -2
- package/dist/assets/google-vertex-BeukMl44.js +0 -1
- package/dist/assets/index-DgJVElbv.css +0 -3
- package/dist/assets/openai-codex-responses-X3sTzNAa.js +0 -7
- package/dist/assets/openai-completions-CRB9Vm0w.js +0 -5
- package/dist/assets/openai-responses-DXluu3oi.js +0 -1
- package/dist/assets/transform-messages-CV4kCtBB.js +0 -1
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/LICENSE +0 -201
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/README.md +0 -62
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-cjs/index.js +0 -156
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/constants.js +0 -2
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromEnvSigningName.js +0 -16
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromSso.js +0 -80
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromStatic.js +0 -8
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js +0 -11
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js +0 -10
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/index.js +0 -4
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/nodeProvider.js +0 -5
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenExpiry.js +0 -7
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenKey.js +0 -7
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js +0 -8
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +0 -69
- package/node_modules/@mariozechner/pi-ai/dist/providers/google-gemini-cli.js +0 -779
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-antigravity.js +0 -377
- package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-gemini-cli.js +0 -482
|
@@ -8,11 +8,13 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
10
|
import { getEnvApiKey } from "../env-api-keys.js";
|
|
11
|
-
import {
|
|
11
|
+
import { clampThinkingLevel } from "../models.js";
|
|
12
|
+
import { registerSessionResourceCleanup } from "../session-resources.js";
|
|
13
|
+
import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
|
|
12
14
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
13
15
|
import { headersToRecord } from "../utils/headers.js";
|
|
14
16
|
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
|
|
15
|
-
import { buildBaseOptions
|
|
17
|
+
import { buildBaseOptions } from "./simple-options.js";
|
|
16
18
|
// ============================================================================
|
|
17
19
|
// Configuration
|
|
18
20
|
// ============================================================================
|
|
@@ -21,6 +23,7 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
|
21
23
|
const MAX_RETRIES = 3;
|
|
22
24
|
const BASE_DELAY_MS = 1000;
|
|
23
25
|
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
|
26
|
+
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
|
|
24
27
|
const CODEX_RESPONSE_STATUSES = new Set([
|
|
25
28
|
"completed",
|
|
26
29
|
"incomplete",
|
|
@@ -89,8 +92,12 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
89
92
|
const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
|
|
90
93
|
const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
|
|
91
94
|
const bodyJson = JSON.stringify(body);
|
|
92
|
-
const transport = options?.transport || "
|
|
93
|
-
|
|
95
|
+
const transport = options?.transport || "auto";
|
|
96
|
+
const websocketDisabledForSession = transport !== "sse" && isWebSocketSseFallbackActive(options?.sessionId);
|
|
97
|
+
if (websocketDisabledForSession) {
|
|
98
|
+
recordWebSocketSseFallback(options?.sessionId);
|
|
99
|
+
}
|
|
100
|
+
if (transport !== "sse" && !websocketDisabledForSession) {
|
|
94
101
|
let websocketStarted = false;
|
|
95
102
|
try {
|
|
96
103
|
await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, websocketHeaders, output, stream, model, () => {
|
|
@@ -108,9 +115,22 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
108
115
|
return;
|
|
109
116
|
}
|
|
110
117
|
catch (error) {
|
|
111
|
-
|
|
118
|
+
const aborted = options?.signal?.aborted;
|
|
119
|
+
if (aborted || isCodexNonTransportError(error)) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
appendAssistantMessageDiagnostic(output, createAssistantMessageDiagnostic("provider_transport_failure", error, {
|
|
123
|
+
configuredTransport: transport,
|
|
124
|
+
fallbackTransport: websocketStarted ? undefined : "sse",
|
|
125
|
+
eventsEmitted: websocketStarted,
|
|
126
|
+
phase: websocketStarted ? "after_message_stream_start" : "before_message_stream_start",
|
|
127
|
+
requestBytes: new TextEncoder().encode(bodyJson).byteLength,
|
|
128
|
+
}));
|
|
129
|
+
recordWebSocketFailure(options?.sessionId, error);
|
|
130
|
+
if (websocketStarted) {
|
|
112
131
|
throw error;
|
|
113
132
|
}
|
|
133
|
+
recordWebSocketSseFallback(options?.sessionId);
|
|
114
134
|
}
|
|
115
135
|
}
|
|
116
136
|
// Fetch with retry logic for rate limits and transient errors
|
|
@@ -194,7 +214,8 @@ export const streamSimpleOpenAICodexResponses = (model, context, options) => {
|
|
|
194
214
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
195
215
|
}
|
|
196
216
|
const base = buildBaseOptions(model, options, apiKey);
|
|
197
|
-
const
|
|
217
|
+
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
|
|
218
|
+
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
198
219
|
return streamOpenAICodexResponses(model, context, {
|
|
199
220
|
...base,
|
|
200
221
|
reasoningEffort,
|
|
@@ -211,7 +232,7 @@ function buildRequestBody(model, context, options) {
|
|
|
211
232
|
model: model.id,
|
|
212
233
|
store: false,
|
|
213
234
|
stream: true,
|
|
214
|
-
instructions: context.systemPrompt,
|
|
235
|
+
instructions: context.systemPrompt || "You are a helpful assistant.",
|
|
215
236
|
input: messages,
|
|
216
237
|
text: { verbosity: options?.textVerbosity || "low" },
|
|
217
238
|
include: ["reasoning.encrypted_content"],
|
|
@@ -229,24 +250,18 @@ function buildRequestBody(model, context, options) {
|
|
|
229
250
|
body.tools = convertResponsesTools(context.tools, { strict: null });
|
|
230
251
|
}
|
|
231
252
|
if (options?.reasoningEffort !== undefined) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
253
|
+
const effort = options.reasoningEffort === "none"
|
|
254
|
+
? (model.thinkingLevelMap?.off ?? "none")
|
|
255
|
+
: (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);
|
|
256
|
+
if (effort !== null) {
|
|
257
|
+
body.reasoning = {
|
|
258
|
+
effort,
|
|
259
|
+
summary: options.reasoningSummary ?? "auto",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
236
262
|
}
|
|
237
263
|
return body;
|
|
238
264
|
}
|
|
239
|
-
function clampReasoningEffort(modelId, effort) {
|
|
240
|
-
const id = modelId.includes("/") ? modelId.split("/").pop() : modelId;
|
|
241
|
-
if ((id.startsWith("gpt-5.2") || id.startsWith("gpt-5.3") || id.startsWith("gpt-5.4") || id.startsWith("gpt-5.5")) &&
|
|
242
|
-
effort === "minimal")
|
|
243
|
-
return "low";
|
|
244
|
-
if (id === "gpt-5.1" && effort === "xhigh")
|
|
245
|
-
return "high";
|
|
246
|
-
if (id === "gpt-5.1-codex-mini")
|
|
247
|
-
return effort === "high" || effort === "xhigh" ? "high" : "medium";
|
|
248
|
-
return effort;
|
|
249
|
-
}
|
|
250
265
|
function getServiceTierCostMultiplier(model, serviceTier) {
|
|
251
266
|
switch (serviceTier) {
|
|
252
267
|
case "flex":
|
|
@@ -300,6 +315,29 @@ async function processStream(response, output, stream, model, options) {
|
|
|
300
315
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
301
316
|
});
|
|
302
317
|
}
|
|
318
|
+
class CodexApiError extends Error {
|
|
319
|
+
code;
|
|
320
|
+
payload;
|
|
321
|
+
constructor(message, options) {
|
|
322
|
+
super(message);
|
|
323
|
+
this.name = "CodexApiError";
|
|
324
|
+
this.code = options?.code;
|
|
325
|
+
this.payload = options?.payload;
|
|
326
|
+
this.cause = options?.cause;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
class CodexProtocolError extends Error {
|
|
330
|
+
payload;
|
|
331
|
+
constructor(message, options) {
|
|
332
|
+
super(message);
|
|
333
|
+
this.name = "CodexProtocolError";
|
|
334
|
+
this.payload = options?.payload;
|
|
335
|
+
this.cause = options?.cause;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function isCodexNonTransportError(error) {
|
|
339
|
+
return error instanceof CodexApiError || error instanceof CodexProtocolError;
|
|
340
|
+
}
|
|
303
341
|
async function* mapCodexEvents(events) {
|
|
304
342
|
for await (const event of events) {
|
|
305
343
|
const type = typeof event.type === "string" ? event.type : undefined;
|
|
@@ -308,11 +346,16 @@ async function* mapCodexEvents(events) {
|
|
|
308
346
|
if (type === "error") {
|
|
309
347
|
const code = event.code || "";
|
|
310
348
|
const message = event.message || "";
|
|
311
|
-
throw new
|
|
349
|
+
throw new CodexApiError(`Codex error: ${message || code || JSON.stringify(event)}`, {
|
|
350
|
+
code: code || undefined,
|
|
351
|
+
payload: event,
|
|
352
|
+
});
|
|
312
353
|
}
|
|
313
354
|
if (type === "response.failed") {
|
|
314
|
-
const
|
|
315
|
-
|
|
355
|
+
const response = event.response;
|
|
356
|
+
const code = response?.error?.code;
|
|
357
|
+
const message = response?.error?.message;
|
|
358
|
+
throw new CodexApiError(message || "Codex response failed", { code, payload: event });
|
|
316
359
|
}
|
|
317
360
|
if (type === "response.done" || type === "response.completed" || type === "response.incomplete") {
|
|
318
361
|
const response = event.response;
|
|
@@ -359,7 +402,12 @@ async function* parseSSE(response) {
|
|
|
359
402
|
try {
|
|
360
403
|
yield JSON.parse(data);
|
|
361
404
|
}
|
|
362
|
-
catch {
|
|
405
|
+
catch (cause) {
|
|
406
|
+
throw new CodexProtocolError(`Invalid Codex SSE JSON: ${formatThrownValue(cause)}`, {
|
|
407
|
+
cause,
|
|
408
|
+
payload: data,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
363
411
|
}
|
|
364
412
|
}
|
|
365
413
|
idx = buffer.indexOf("\n\n");
|
|
@@ -383,12 +431,96 @@ async function* parseSSE(response) {
|
|
|
383
431
|
const OPENAI_BETA_RESPONSES_WEBSOCKETS = "responses_websockets=2026-02-06";
|
|
384
432
|
const SESSION_WEBSOCKET_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
385
433
|
const websocketSessionCache = new Map();
|
|
434
|
+
const websocketDebugStats = new Map();
|
|
435
|
+
const websocketSseFallbackSessions = new Set();
|
|
436
|
+
function getOrCreateWebSocketDebugStats(sessionId) {
|
|
437
|
+
let stats = websocketDebugStats.get(sessionId);
|
|
438
|
+
if (!stats) {
|
|
439
|
+
stats = {
|
|
440
|
+
requests: 0,
|
|
441
|
+
connectionsCreated: 0,
|
|
442
|
+
connectionsReused: 0,
|
|
443
|
+
cachedContextRequests: 0,
|
|
444
|
+
storeTrueRequests: 0,
|
|
445
|
+
fullContextRequests: 0,
|
|
446
|
+
deltaRequests: 0,
|
|
447
|
+
lastInputItems: 0,
|
|
448
|
+
websocketFailures: 0,
|
|
449
|
+
sseFallbacks: 0,
|
|
450
|
+
};
|
|
451
|
+
websocketDebugStats.set(sessionId, stats);
|
|
452
|
+
}
|
|
453
|
+
return stats;
|
|
454
|
+
}
|
|
455
|
+
export function getOpenAICodexWebSocketDebugStats(sessionId) {
|
|
456
|
+
const stats = websocketDebugStats.get(sessionId);
|
|
457
|
+
return stats ? { ...stats } : undefined;
|
|
458
|
+
}
|
|
459
|
+
export function resetOpenAICodexWebSocketDebugStats(sessionId) {
|
|
460
|
+
if (sessionId) {
|
|
461
|
+
websocketDebugStats.delete(sessionId);
|
|
462
|
+
websocketSseFallbackSessions.delete(sessionId);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
websocketDebugStats.clear();
|
|
466
|
+
websocketSseFallbackSessions.clear();
|
|
467
|
+
}
|
|
468
|
+
export function closeOpenAICodexWebSocketSessions(sessionId) {
|
|
469
|
+
const closeEntry = (entry) => {
|
|
470
|
+
if (entry.idleTimer)
|
|
471
|
+
clearTimeout(entry.idleTimer);
|
|
472
|
+
closeWebSocketSilently(entry.socket, 1000, "debug_close");
|
|
473
|
+
};
|
|
474
|
+
if (sessionId) {
|
|
475
|
+
const entry = websocketSessionCache.get(sessionId);
|
|
476
|
+
if (entry)
|
|
477
|
+
closeEntry(entry);
|
|
478
|
+
websocketSessionCache.delete(sessionId);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
for (const entry of websocketSessionCache.values()) {
|
|
482
|
+
closeEntry(entry);
|
|
483
|
+
}
|
|
484
|
+
websocketSessionCache.clear();
|
|
485
|
+
}
|
|
486
|
+
registerSessionResourceCleanup(closeOpenAICodexWebSocketSessions);
|
|
487
|
+
function isWebSocketSseFallbackActive(sessionId) {
|
|
488
|
+
return sessionId ? websocketSseFallbackSessions.has(sessionId) : false;
|
|
489
|
+
}
|
|
490
|
+
function recordWebSocketSseFallback(sessionId) {
|
|
491
|
+
if (!sessionId)
|
|
492
|
+
return;
|
|
493
|
+
const stats = getOrCreateWebSocketDebugStats(sessionId);
|
|
494
|
+
stats.sseFallbacks++;
|
|
495
|
+
stats.websocketFallbackActive = isWebSocketSseFallbackActive(sessionId);
|
|
496
|
+
}
|
|
497
|
+
function recordWebSocketFailure(sessionId, error) {
|
|
498
|
+
if (!sessionId)
|
|
499
|
+
return;
|
|
500
|
+
websocketSseFallbackSessions.add(sessionId);
|
|
501
|
+
const stats = getOrCreateWebSocketDebugStats(sessionId);
|
|
502
|
+
stats.websocketFailures++;
|
|
503
|
+
stats.lastWebSocketError = formatThrownValue(error);
|
|
504
|
+
stats.websocketFallbackActive = true;
|
|
505
|
+
}
|
|
386
506
|
function getWebSocketConstructor() {
|
|
387
507
|
const ctor = globalThis.WebSocket;
|
|
388
508
|
if (typeof ctor !== "function")
|
|
389
509
|
return null;
|
|
390
510
|
return ctor;
|
|
391
511
|
}
|
|
512
|
+
class WebSocketCloseError extends Error {
|
|
513
|
+
code;
|
|
514
|
+
reason;
|
|
515
|
+
wasClean;
|
|
516
|
+
constructor(message, options) {
|
|
517
|
+
super(message);
|
|
518
|
+
this.name = "WebSocketCloseError";
|
|
519
|
+
this.code = options?.code;
|
|
520
|
+
this.reason = options?.reason;
|
|
521
|
+
this.wasClean = options?.wasClean;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
392
524
|
function getWebSocketReadyState(socket) {
|
|
393
525
|
const readyState = socket.readyState;
|
|
394
526
|
return typeof readyState === "number" ? readyState : undefined;
|
|
@@ -480,6 +612,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
480
612
|
const socket = await connectWebSocket(url, headers, signal);
|
|
481
613
|
return {
|
|
482
614
|
socket,
|
|
615
|
+
reused: false,
|
|
483
616
|
release: ({ keep } = {}) => {
|
|
484
617
|
if (keep === false) {
|
|
485
618
|
closeWebSocketSilently(socket);
|
|
@@ -499,6 +632,8 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
499
632
|
cached.busy = true;
|
|
500
633
|
return {
|
|
501
634
|
socket: cached.socket,
|
|
635
|
+
entry: cached,
|
|
636
|
+
reused: true,
|
|
502
637
|
release: ({ keep } = {}) => {
|
|
503
638
|
if (!keep || !isWebSocketReusable(cached.socket)) {
|
|
504
639
|
closeWebSocketSilently(cached.socket);
|
|
@@ -514,6 +649,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
514
649
|
const socket = await connectWebSocket(url, headers, signal);
|
|
515
650
|
return {
|
|
516
651
|
socket,
|
|
652
|
+
reused: false,
|
|
517
653
|
release: () => {
|
|
518
654
|
closeWebSocketSilently(socket);
|
|
519
655
|
},
|
|
@@ -529,6 +665,8 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
529
665
|
websocketSessionCache.set(sessionId, entry);
|
|
530
666
|
return {
|
|
531
667
|
socket,
|
|
668
|
+
entry,
|
|
669
|
+
reused: false,
|
|
532
670
|
release: ({ keep } = {}) => {
|
|
533
671
|
if (!keep || !isWebSocketReusable(entry.socket)) {
|
|
534
672
|
closeWebSocketSilently(entry.socket);
|
|
@@ -545,11 +683,21 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
545
683
|
};
|
|
546
684
|
}
|
|
547
685
|
function extractWebSocketError(event) {
|
|
548
|
-
if (event && typeof event === "object"
|
|
549
|
-
const message = event.message;
|
|
686
|
+
if (event && typeof event === "object") {
|
|
687
|
+
const message = "message" in event ? event.message : undefined;
|
|
550
688
|
if (typeof message === "string" && message.length > 0) {
|
|
551
689
|
return new Error(message);
|
|
552
690
|
}
|
|
691
|
+
const nestedError = "error" in event ? event.error : undefined;
|
|
692
|
+
if (nestedError instanceof Error && nestedError.message.length > 0) {
|
|
693
|
+
return nestedError;
|
|
694
|
+
}
|
|
695
|
+
if (nestedError && typeof nestedError === "object" && "message" in nestedError) {
|
|
696
|
+
const nestedMessage = nestedError.message;
|
|
697
|
+
if (typeof nestedMessage === "string" && nestedMessage.length > 0) {
|
|
698
|
+
return new Error(nestedMessage);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
553
701
|
}
|
|
554
702
|
return new Error("WebSocket error");
|
|
555
703
|
}
|
|
@@ -557,9 +705,17 @@ function extractWebSocketCloseError(event) {
|
|
|
557
705
|
if (event && typeof event === "object") {
|
|
558
706
|
const code = "code" in event ? event.code : undefined;
|
|
559
707
|
const reason = "reason" in event ? event.reason : undefined;
|
|
708
|
+
const wasClean = "wasClean" in event ? event.wasClean : undefined;
|
|
560
709
|
const codeText = typeof code === "number" ? ` ${code}` : "";
|
|
561
|
-
|
|
562
|
-
|
|
710
|
+
let reasonText = typeof reason === "string" && reason.length > 0 ? ` ${reason}` : "";
|
|
711
|
+
if (!reasonText && code === WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE) {
|
|
712
|
+
reasonText = " message too big";
|
|
713
|
+
}
|
|
714
|
+
return new WebSocketCloseError(`WebSocket closed${codeText}${reasonText}`.trim(), {
|
|
715
|
+
code: typeof code === "number" ? code : undefined,
|
|
716
|
+
reason: typeof reason === "string" && reason.length > 0 ? reason : undefined,
|
|
717
|
+
wasClean: typeof wasClean === "boolean" ? wasClean : undefined,
|
|
718
|
+
});
|
|
563
719
|
}
|
|
564
720
|
return new Error("WebSocket closed");
|
|
565
721
|
}
|
|
@@ -595,12 +751,13 @@ async function* parseWebSocket(socket, signal) {
|
|
|
595
751
|
};
|
|
596
752
|
const onMessage = (event) => {
|
|
597
753
|
void (async () => {
|
|
598
|
-
|
|
599
|
-
return;
|
|
600
|
-
const text = await decodeWebSocketData(event.data);
|
|
601
|
-
if (!text)
|
|
602
|
-
return;
|
|
754
|
+
let text = null;
|
|
603
755
|
try {
|
|
756
|
+
if (!event || typeof event !== "object" || !("data" in event))
|
|
757
|
+
return;
|
|
758
|
+
text = await decodeWebSocketData(event.data);
|
|
759
|
+
if (!text)
|
|
760
|
+
return;
|
|
604
761
|
const parsed = JSON.parse(text);
|
|
605
762
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
606
763
|
if (type === "response.completed" || type === "response.done" || type === "response.incomplete") {
|
|
@@ -610,7 +767,14 @@ async function* parseWebSocket(socket, signal) {
|
|
|
610
767
|
queue.push(parsed);
|
|
611
768
|
wake();
|
|
612
769
|
}
|
|
613
|
-
catch {
|
|
770
|
+
catch (cause) {
|
|
771
|
+
failed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {
|
|
772
|
+
cause,
|
|
773
|
+
payload: text,
|
|
774
|
+
});
|
|
775
|
+
done = true;
|
|
776
|
+
wake();
|
|
777
|
+
}
|
|
614
778
|
})();
|
|
615
779
|
};
|
|
616
780
|
const onError = (event) => {
|
|
@@ -668,14 +832,92 @@ async function* parseWebSocket(socket, signal) {
|
|
|
668
832
|
signal?.removeEventListener("abort", onAbort);
|
|
669
833
|
}
|
|
670
834
|
}
|
|
835
|
+
function requestBodyWithoutInput(body) {
|
|
836
|
+
const { input: _input, previous_response_id: _previousResponseId, ...rest } = body;
|
|
837
|
+
return rest;
|
|
838
|
+
}
|
|
839
|
+
function responseInputsEqual(a, b) {
|
|
840
|
+
return JSON.stringify(a ?? []) === JSON.stringify(b ?? []);
|
|
841
|
+
}
|
|
842
|
+
function requestBodiesMatchExceptInput(a, b) {
|
|
843
|
+
return JSON.stringify(requestBodyWithoutInput(a)) === JSON.stringify(requestBodyWithoutInput(b));
|
|
844
|
+
}
|
|
845
|
+
function getCachedWebSocketInputDelta(body, continuation) {
|
|
846
|
+
if (!requestBodiesMatchExceptInput(body, continuation.lastRequestBody)) {
|
|
847
|
+
return undefined;
|
|
848
|
+
}
|
|
849
|
+
const currentInput = body.input ?? [];
|
|
850
|
+
const baseline = [...(continuation.lastRequestBody.input ?? []), ...continuation.lastResponseItems];
|
|
851
|
+
if (currentInput.length < baseline.length) {
|
|
852
|
+
return undefined;
|
|
853
|
+
}
|
|
854
|
+
const prefix = currentInput.slice(0, baseline.length);
|
|
855
|
+
if (!responseInputsEqual(prefix, baseline)) {
|
|
856
|
+
return undefined;
|
|
857
|
+
}
|
|
858
|
+
return currentInput.slice(baseline.length);
|
|
859
|
+
}
|
|
860
|
+
function buildCachedWebSocketRequestBody(entry, body) {
|
|
861
|
+
const continuation = entry.continuation;
|
|
862
|
+
if (!continuation) {
|
|
863
|
+
return body;
|
|
864
|
+
}
|
|
865
|
+
const delta = getCachedWebSocketInputDelta(body, continuation);
|
|
866
|
+
if (!delta || !continuation.lastResponseId) {
|
|
867
|
+
entry.continuation = undefined;
|
|
868
|
+
return body;
|
|
869
|
+
}
|
|
870
|
+
return {
|
|
871
|
+
...body,
|
|
872
|
+
previous_response_id: continuation.lastResponseId,
|
|
873
|
+
input: delta,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart) {
|
|
877
|
+
let started = false;
|
|
878
|
+
for await (const event of events) {
|
|
879
|
+
if (!started) {
|
|
880
|
+
started = true;
|
|
881
|
+
onStart();
|
|
882
|
+
stream.push({ type: "start", partial: output });
|
|
883
|
+
}
|
|
884
|
+
yield event;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
671
887
|
async function processWebSocketStream(url, body, headers, output, stream, model, onStart, options) {
|
|
672
|
-
const { socket, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
|
|
888
|
+
const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
|
|
673
889
|
let keepConnection = true;
|
|
890
|
+
const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
|
|
891
|
+
// ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
|
|
892
|
+
// WebSocket continuation still works via connection-scoped previous_response_id state.
|
|
893
|
+
const fullBody = body;
|
|
894
|
+
const requestBody = useCachedContext && entry ? buildCachedWebSocketRequestBody(entry, fullBody) : fullBody;
|
|
895
|
+
const stats = options?.sessionId ? getOrCreateWebSocketDebugStats(options.sessionId) : undefined;
|
|
896
|
+
if (stats) {
|
|
897
|
+
stats.requests++;
|
|
898
|
+
if (reused)
|
|
899
|
+
stats.connectionsReused++;
|
|
900
|
+
else
|
|
901
|
+
stats.connectionsCreated++;
|
|
902
|
+
if (useCachedContext)
|
|
903
|
+
stats.cachedContextRequests++;
|
|
904
|
+
if (requestBody.store === true)
|
|
905
|
+
stats.storeTrueRequests++;
|
|
906
|
+
stats.lastInputItems = requestBody.input?.length ?? 0;
|
|
907
|
+
if (requestBody.previous_response_id) {
|
|
908
|
+
stats.deltaRequests++;
|
|
909
|
+
stats.lastDeltaInputItems = requestBody.input?.length ?? 0;
|
|
910
|
+
stats.lastPreviousResponseId = requestBody.previous_response_id;
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
stats.fullContextRequests++;
|
|
914
|
+
stats.lastDeltaInputItems = undefined;
|
|
915
|
+
stats.lastPreviousResponseId = undefined;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
674
918
|
try {
|
|
675
|
-
socket.send(JSON.stringify({ type: "response.create", ...
|
|
676
|
-
|
|
677
|
-
stream.push({ type: "start", partial: output });
|
|
678
|
-
await processResponsesStream(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, model, {
|
|
919
|
+
socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
|
|
920
|
+
await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, onStart), output, stream, model, {
|
|
679
921
|
serviceTier: options?.serviceTier,
|
|
680
922
|
resolveServiceTier: resolveCodexServiceTier,
|
|
681
923
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
@@ -683,8 +925,21 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
|
|
|
683
925
|
if (options?.signal?.aborted) {
|
|
684
926
|
keepConnection = false;
|
|
685
927
|
}
|
|
928
|
+
else if (useCachedContext && entry && output.responseId) {
|
|
929
|
+
const responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {
|
|
930
|
+
includeSystemPrompt: false,
|
|
931
|
+
}).filter((item) => item.type !== "function_call_output");
|
|
932
|
+
entry.continuation = {
|
|
933
|
+
lastRequestBody: fullBody,
|
|
934
|
+
lastResponseId: output.responseId,
|
|
935
|
+
lastResponseItems: responseItems,
|
|
936
|
+
};
|
|
937
|
+
}
|
|
686
938
|
}
|
|
687
939
|
catch (error) {
|
|
940
|
+
if (entry) {
|
|
941
|
+
entry.continuation = undefined;
|
|
942
|
+
}
|
|
688
943
|
keepConnection = false;
|
|
689
944
|
throw error;
|
|
690
945
|
}
|