@yansigit/opencodex 2.33.1 → 2.35.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/README.md +3 -3
- package/gui/dist/assets/index-BjCaHxdz.js +112 -0
- package/gui/dist/assets/index-DLkXOXLC.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -3
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +40 -3
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +9 -9
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-shell.ts +103 -6
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +74 -39
- package/src/adapters/cursor/request-builder.ts +32 -10
- package/src/adapters/cursor/tool-definitions.ts +13 -10
- package/src/adapters/cursor/tool-result-normalize.ts +35 -3
- package/src/adapters/cursor/types.ts +16 -4
- package/src/adapters/cursor.ts +97 -70
- package/src/adapters/google-antigravity-replay.ts +105 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +41 -12
- package/src/adapters/google-http.ts +12 -11
- package/src/adapters/google.ts +156 -65
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +334 -24
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -26
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +27 -7
- package/src/cli/account-extended.ts +10 -3
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/telemetry-commands.ts +25 -0
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +48 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/model-metadata.ts +13 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -37
- package/src/codex/catalog/sync.ts +127 -2
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +123 -18
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +198 -27
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/autonomous-remediation.ts +21 -0
- package/src/config/provider-validation.ts +14 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +191 -17
- package/src/generated/compatibility-version.json +229 -153
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/aistudio-credentials.ts +65 -0
- package/src/oauth/aistudio-native-daemon.ts +54 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/login-cli.ts +19 -20
- package/src/oauth/types.ts +15 -0
- package/src/providers/codex-capacity.ts +5 -2
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +73 -14
- package/src/providers/registry.ts +160 -68
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +23 -0
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/effort-policy.ts +16 -0
- package/src/server/index.ts +152 -80
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/logs-usage-routes.ts +1 -1
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/provider-routes.ts +137 -3
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +21 -0
- package/src/server/request-log.ts +46 -20
- package/src/server/responses/collaboration.ts +42 -5
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +571 -137
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +14 -6
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +35 -5
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +15 -4
- package/src/telemetry/dispatcher.ts +44 -0
- package/src/telemetry/fingerprint.ts +24 -0
- package/src/telemetry/hook.ts +43 -0
- package/src/telemetry/ledger.ts +54 -0
- package/src/telemetry/types.ts +23 -0
- package/src/types/config.ts +66 -14
- package/src/types/provider.ts +77 -0
- package/src/types/request.ts +16 -10
- package/src/types/tools.ts +20 -25
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +17 -7
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-CIDo4y4k.js +0 -102
- package/gui/dist/assets/index-DrSQdTRd.css +0 -1
- package/src/server/aistudio-ws-hub.ts +0 -295
package/src/types/tools.ts
CHANGED
|
@@ -32,16 +32,19 @@ export function namespacedToolName(namespace: string | undefined, name: string):
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Codex
|
|
35
|
+
* Codex unified-exec name normalization.
|
|
36
36
|
*
|
|
37
37
|
* Codex's code-mode shell tool is declared as `exec` (a freeform custom tool whose own
|
|
38
38
|
* description mentions the nested `await tools.exec_command(...)` helper). Routed models —
|
|
39
39
|
* DeepSeek in particular — sometimes echo that helper name as the tool-call name, emitting
|
|
40
|
-
* `exec_command` instead of the declared `exec`. Accept
|
|
41
|
-
* when the request catalog actually declares `exec` and does not itself declare the
|
|
42
|
-
* name (an MCP server may legitimately advertise
|
|
40
|
+
* `exec_command` or `apply_patch` instead of the declared `exec`. Accept these nested helper
|
|
41
|
+
* names only when the request catalog actually declares `exec` and does not itself declare the
|
|
42
|
+
* emitted name (an MCP server may legitimately advertise one under its own namespace). Some
|
|
43
|
+
* hosted adapters also strip a unique namespace prefix or replace its separator with `_`; those
|
|
44
|
+
* forms are accepted only when one declaration can be identified unambiguously.
|
|
43
45
|
*/
|
|
44
46
|
const LEGACY_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
|
|
47
|
+
const CODE_MODE_HELPER_TOOL_NAMES = [...LEGACY_SHELL_BRIDGE_TOOL_NAMES, "apply_patch"] as const;
|
|
45
48
|
|
|
46
49
|
export function normalizeDeclaredToolName(
|
|
47
50
|
name: string,
|
|
@@ -52,34 +55,26 @@ export function normalizeDeclaredToolName(
|
|
|
52
55
|
if (declared.has("exec")) {
|
|
53
56
|
// When the catalog explicitly declares any legacy shell bridge name, the environment
|
|
54
57
|
// genuinely exposes that tool — turn normalization off so a call is never mis-routed
|
|
55
|
-
// to exec
|
|
58
|
+
// to `exec`.
|
|
56
59
|
if ((LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy))) {
|
|
57
60
|
return name;
|
|
58
61
|
}
|
|
59
|
-
if ((
|
|
62
|
+
if ((CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(name)) {
|
|
60
63
|
return "exec";
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return name;
|
|
74
|
-
}
|
|
75
|
-
matched = dec;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (matched !== undefined) {
|
|
79
|
-
return matched;
|
|
80
|
-
}
|
|
66
|
+
// Models may strip a namespace prefix or replace separators with underscores. Resolve only
|
|
67
|
+
// when exactly one declaration matches, so an ambiguous bare name remains fail-closed.
|
|
68
|
+
let matched: string | undefined;
|
|
69
|
+
for (const dec of declared) {
|
|
70
|
+
const matchesSuffix = !name.includes(":") && !name.includes("__")
|
|
71
|
+
&& (dec.endsWith(`:${name}`) || dec.endsWith(`__${name}`));
|
|
72
|
+
const matchesSanitized = dec.replace(/[^A-Za-z0-9_-]/g, "_") === name;
|
|
73
|
+
if (!matchesSuffix && !matchesSanitized) continue;
|
|
74
|
+
if (matched !== undefined) return name;
|
|
75
|
+
matched = dec;
|
|
81
76
|
}
|
|
82
|
-
return name;
|
|
77
|
+
return matched ?? name;
|
|
83
78
|
}
|
|
84
79
|
|
|
85
80
|
export function toolChoiceAliases(tool: Pick<OcxTool, "namespace" | "name">): string[] {
|
package/src/types.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface ExpectedPriceOverlay {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
|
|
42
|
-
const GPT56_SOL: Cost4 = { input:
|
|
42
|
+
const GPT56_SOL: Cost4 = { input: 4, output: 20, cacheRead: 0.4, cacheWrite: 5 };
|
|
43
43
|
/**
|
|
44
44
|
* Daybreak aliases. `daybreak-*-latest` never appears in the pricing table itself — only its
|
|
45
45
|
* current snapshot does — so these tuples are the snapshot's published rates and carry
|
|
@@ -137,6 +137,10 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
137
137
|
// Daybreak aliases: priced as their current snapshots (red -> gpt-5.6-cyber,
|
|
138
138
|
// blue -> gpt-5.6-sol). The alias ids carry no rows of their own upstream, hence
|
|
139
139
|
// verified-derived. Blue deliberately reuses GPT56_SOL rather than duplicating the tuple.
|
|
140
|
+
// The `gpt-` selector is native to ChatGPT/Codex accounts. The bare aliases below belong to
|
|
141
|
+
// the separately billed API-key catalog; keep those namespaces disjoint so pricing metadata
|
|
142
|
+
// cannot make an unroutable provider/model pair look supported.
|
|
143
|
+
{ provider: "openai", modelId: "gpt-daybreak-blue-latest", cost4: GPT56_SOL, source: `alias of gpt-5.6-sol ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
140
144
|
{ provider: "openai-apikey", modelId: "daybreak-red-latest", cost4: DAYBREAK_RED, source: `alias of gpt-5.6-cyber ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
141
145
|
{ provider: "openai-apikey", modelId: "daybreak-blue-latest", cost4: GPT56_SOL, source: `alias of gpt-5.6-sol ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
142
146
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-low", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
@@ -306,6 +310,8 @@ export function findExpectedPriceOverlay(
|
|
|
306
310
|
/** OpenAI Fast price multipliers retained as a compatibility export. */
|
|
307
311
|
export const PRIORITY_MULTIPLIERS: Readonly<Record<string, number>> = {
|
|
308
312
|
"gpt-5.6-sol": 2,
|
|
313
|
+
"gpt-daybreak-blue-latest": 2,
|
|
314
|
+
"daybreak-blue-latest": 2,
|
|
309
315
|
// Post-price-cut Fast tables (https://openai.com/api-fast-mode/, 2026-08-05):
|
|
310
316
|
// Terra 4/24/0.40/5 and Luna 0.40/2.40/0.04/0.50 are both 2× the corrected
|
|
311
317
|
// standard tuples; the stale bases made these look like 1.6/0.4 (#907).
|
|
@@ -340,13 +346,19 @@ const XAI_PRIORITY_PRICING = "https://docs.x.ai/developers/advanced-api-usage/pr
|
|
|
340
346
|
*/
|
|
341
347
|
export const PRIORITY_PRICING_RULES: readonly PriorityPricingRule[] = [
|
|
342
348
|
...["openai", "openai-apikey"].flatMap(provider =>
|
|
343
|
-
Object.entries(PRIORITY_MULTIPLIERS)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
349
|
+
Object.entries(PRIORITY_MULTIPLIERS)
|
|
350
|
+
// Daybreak's `gpt-` selector belongs to ChatGPT/Codex accounts; its bare selector belongs
|
|
351
|
+
// to the separately billed API-key catalog. All ordinary GPT ids remain valid on both.
|
|
352
|
+
.filter(([modelId]) => provider === "openai"
|
|
353
|
+
? modelId !== "daybreak-blue-latest"
|
|
354
|
+
: modelId !== "gpt-daybreak-blue-latest")
|
|
355
|
+
.map(([modelId, multiplier]): PriorityPricingRule => ({
|
|
356
|
+
provider,
|
|
357
|
+
modelId,
|
|
358
|
+
multiplier,
|
|
359
|
+
source: OPENAI_FAST_PRICING,
|
|
360
|
+
verifiedAt: "2026-08-05",
|
|
361
|
+
})),
|
|
350
362
|
),
|
|
351
363
|
...["grok-4.5", "grok-4.6"].map((modelId): PriorityPricingRule => ({
|
|
352
364
|
provider: "xai",
|
|
@@ -432,6 +444,29 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
432
444
|
verifiedAt: "2026-08-03",
|
|
433
445
|
})),
|
|
434
446
|
),
|
|
447
|
+
{
|
|
448
|
+
// The native Codex selector aliases gpt-5.6-sol and shares its 272k long-context tier.
|
|
449
|
+
provider: "openai",
|
|
450
|
+
modelId: "gpt-daybreak-blue-latest",
|
|
451
|
+
thresholdInputTokens: 272_000,
|
|
452
|
+
inclusive: false,
|
|
453
|
+
multiplier: OPENAI_LONG_CONTEXT,
|
|
454
|
+
confirmedPriorityRelation: "exclusive",
|
|
455
|
+
source: OPENAI_PRICING_DOC,
|
|
456
|
+
verifiedAt: "2026-08-11",
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
// The bare selector is the separately billed API-key alias. Daybreak Red has no tier row:
|
|
460
|
+
// the cyber snapshot's four long-context cells are all "-" on the pricing page.
|
|
461
|
+
provider: "openai-apikey",
|
|
462
|
+
modelId: "daybreak-blue-latest",
|
|
463
|
+
thresholdInputTokens: 272_000,
|
|
464
|
+
inclusive: false,
|
|
465
|
+
multiplier: OPENAI_LONG_CONTEXT,
|
|
466
|
+
confirmedPriorityRelation: "exclusive",
|
|
467
|
+
source: OPENAI_PRICING_DOC,
|
|
468
|
+
verifiedAt: "2026-08-11",
|
|
469
|
+
},
|
|
435
470
|
{
|
|
436
471
|
provider: "xai",
|
|
437
472
|
modelId: "grok-4.5",
|
|
@@ -454,22 +489,6 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
454
489
|
source: "https://docs.x.ai/developers/pricing",
|
|
455
490
|
verifiedAt: "2026-08-18",
|
|
456
491
|
},
|
|
457
|
-
{
|
|
458
|
-
// daybreak-blue-latest aliases gpt-5.6-sol, which publishes the full long-context row
|
|
459
|
-
// ($10 / $1 / $12.50 / $45). Scoped to openai-apikey ON PURPOSE: Daybreak is not
|
|
460
|
-
// routable on the Codex-login `openai` provider, so adding the alias to the shared
|
|
461
|
-
// OPENAI_GPT56_CONTEXT_MODELS list (expanded across both providers above) would mint a
|
|
462
|
-
// tier for a provider/model pair that cannot exist.
|
|
463
|
-
// daybreak-red-latest has NO tier row: the cyber snapshot's four long-context cells are
|
|
464
|
-
// all "-" on the pricing page (verified 2026-08-11).
|
|
465
|
-
provider: "openai-apikey",
|
|
466
|
-
modelId: "daybreak-blue-latest",
|
|
467
|
-
thresholdInputTokens: 272_000,
|
|
468
|
-
inclusive: false,
|
|
469
|
-
multiplier: OPENAI_LONG_CONTEXT,
|
|
470
|
-
source: OPENAI_PRICING_DOC,
|
|
471
|
-
verifiedAt: "2026-08-11",
|
|
472
|
-
},
|
|
473
492
|
...["minimax", "minimax-cn"].map((provider): ContextTier => ({
|
|
474
493
|
provider,
|
|
475
494
|
modelId: "MiniMax-M3",
|
package/src/usage/log.ts
CHANGED
|
@@ -13,6 +13,15 @@ import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
|
13
13
|
export type UsageStatus = "reported" | "unreported" | "unsupported" | "estimated";
|
|
14
14
|
export type CodexUsageAccountLogLabel = "main" | `p${string}`;
|
|
15
15
|
|
|
16
|
+
export const OAUTH_ACCOUNT_LOG_LABEL_RE = /^[a-f0-9]{8,64}(?:-\d+)?$/;
|
|
17
|
+
|
|
18
|
+
export function isPersistableAccountLogLabel(value: unknown): value is string {
|
|
19
|
+
return (
|
|
20
|
+
value === "main"
|
|
21
|
+
|| (typeof value === "string" && (CODEX_ACCOUNT_LOG_LABEL_RE.test(value) || OAUTH_ACCOUNT_LOG_LABEL_RE.test(value)))
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
export function isCodexUsageAccountLogLabel(value: unknown): value is CodexUsageAccountLogLabel {
|
|
17
26
|
return value === "main" || (typeof value === "string" && CODEX_ACCOUNT_LOG_LABEL_RE.test(value));
|
|
18
27
|
}
|
|
@@ -30,6 +39,7 @@ export type AttemptRecoveryKind =
|
|
|
30
39
|
| "anthropic-oauth-429"
|
|
31
40
|
| "cursor-oauth-auth"
|
|
32
41
|
| "cursor-oauth-429"
|
|
42
|
+
| "oauth-account-429"
|
|
33
43
|
| "image-413"
|
|
34
44
|
| "opaque-blob-rejection"
|
|
35
45
|
| "empty-completion";
|
|
@@ -53,7 +63,7 @@ export interface PersistedUsageAttempt {
|
|
|
53
63
|
recoveryKinds: AttemptRecoveryKind[];
|
|
54
64
|
usageStatus: UsageStatus;
|
|
55
65
|
/** Stable non-PII identity for the account that served this attempt. */
|
|
56
|
-
accountLogLabel?:
|
|
66
|
+
accountLogLabel?: string;
|
|
57
67
|
inputTokenEstimate?: number;
|
|
58
68
|
usage?: OcxUsage;
|
|
59
69
|
totalTokens?: number;
|
|
@@ -70,6 +80,7 @@ export interface PersistedUsageAttempt {
|
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
export interface PersistedUsageEntry {
|
|
83
|
+
requestedAlias?: string;
|
|
73
84
|
requestId: string;
|
|
74
85
|
timestamp: number;
|
|
75
86
|
provider: string;
|
|
@@ -81,8 +92,8 @@ export interface PersistedUsageEntry {
|
|
|
81
92
|
admissionKind?: "configured" | "environment" | "loopback";
|
|
82
93
|
/** The inbound wire, not the client product — see `surface`. */
|
|
83
94
|
inboundProtocol?: "responses" | "chat" | "messages";
|
|
84
|
-
/** Stable non-PII identity for account
|
|
85
|
-
accountLogLabel?:
|
|
95
|
+
/** Stable non-PII identity for the account that served this request. */
|
|
96
|
+
accountLogLabel?: string;
|
|
86
97
|
/** Best-effort chat/session correlation for Logs grouping (#330). */
|
|
87
98
|
conversationId?: string;
|
|
88
99
|
resolvedModel?: string;
|
|
@@ -220,8 +231,7 @@ const ATTEMPT_RECOVERY_KINDS = new Set<AttemptRecoveryKind>([
|
|
|
220
231
|
"key-429",
|
|
221
232
|
"rate-limit-429",
|
|
222
233
|
"anthropic-oauth-429",
|
|
223
|
-
"
|
|
224
|
-
"cursor-oauth-429",
|
|
234
|
+
"oauth-account-429",
|
|
225
235
|
"image-413",
|
|
226
236
|
"opaque-blob-rejection",
|
|
227
237
|
"empty-completion",
|
|
@@ -367,7 +377,7 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
367
377
|
sendCount: attempt.sendCount as number,
|
|
368
378
|
recoveryKinds,
|
|
369
379
|
usageStatus: attempt.usageStatus as UsageStatus,
|
|
370
|
-
...(
|
|
380
|
+
...(isPersistableAccountLogLabel(attempt.accountLogLabel)
|
|
371
381
|
? { accountLogLabel: attempt.accountLogLabel }
|
|
372
382
|
: {}),
|
|
373
383
|
...(isNonNegativeFiniteNumber(attempt.inputTokenEstimate)
|
|
@@ -454,7 +464,7 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
454
464
|
: {}),
|
|
455
465
|
...(isKnownAdmissionKind(entry.admissionKind) ? { admissionKind: entry.admissionKind } : {}),
|
|
456
466
|
...(isKnownInboundProtocol(entry.inboundProtocol) ? { inboundProtocol: entry.inboundProtocol } : {}),
|
|
457
|
-
...(
|
|
467
|
+
...(isPersistableAccountLogLabel(entry.accountLogLabel)
|
|
458
468
|
? { accountLogLabel: entry.accountLogLabel }
|
|
459
469
|
: {}),
|
|
460
470
|
...(typeof entry.conversationId === "string" && entry.conversationId.trim()
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* Never throws; every error string passes redactSecretString.
|
|
10
10
|
*/
|
|
11
11
|
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
12
|
-
import { signalWithTimeout } from "../lib/abort";
|
|
12
|
+
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
13
|
+
import { readBoundedResponseBytes } from "../lib/bounded-body";
|
|
13
14
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
14
15
|
import { redactSecretString } from "../lib/redact";
|
|
15
|
-
import type
|
|
16
|
+
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
16
17
|
import type { SidecarOutcome, SidecarSettings } from "./executor";
|
|
17
18
|
|
|
18
19
|
const EXA_SEARCH_URL = "https://api.exa.ai/search";
|
|
@@ -47,14 +48,44 @@ export async function runExaWebSearch(
|
|
|
47
48
|
}),
|
|
48
49
|
{ abortSignal: linkedSignal.signal, label: "exa-web-search-sidecar" },
|
|
49
50
|
);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
52
|
+
try {
|
|
53
|
+
let bounded: Awaited<ReturnType<typeof readBoundedResponseBytes>> | null = null;
|
|
54
|
+
try {
|
|
55
|
+
bounded = await readBoundedResponseBytes(res, {
|
|
56
|
+
maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
|
|
57
|
+
signal: linkedSignal.signal,
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
const reason = linkedSignal.signal.reason;
|
|
61
|
+
if (linkedSignal.signal.aborted && reason instanceof Error && reason.name === "TimeoutError") {
|
|
62
|
+
throw reason;
|
|
63
|
+
}
|
|
64
|
+
// Preserve the previous status-only/shapeless fallback for body read
|
|
65
|
+
// failures; fetch/header failures are still classified by the outer catch.
|
|
66
|
+
}
|
|
67
|
+
if (bounded?.oversized) {
|
|
68
|
+
const prefix = res.ok ? "exa sidecar response" : `exa sidecar HTTP ${res.status} response`;
|
|
69
|
+
return { text: "", sources: [], error: `${prefix} exceeded byte bound` };
|
|
70
|
+
}
|
|
71
|
+
// Response.text()/json() replace malformed UTF-8, so preserve that
|
|
72
|
+
// behavior while moving the byte bound ahead of decoding and parsing.
|
|
73
|
+
const text = bounded ? new TextDecoder().decode(bounded.bytes) : "";
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
// Scrub BEFORE truncating: slicing first can cut the literal key at the
|
|
76
|
+
// boundary, leaving an unscrubbable key prefix in the surviving text.
|
|
77
|
+
return { text: "", sources: [], error: `exa sidecar HTTP ${res.status}: ${scrub(text).slice(0, 200)}` };
|
|
78
|
+
}
|
|
79
|
+
let payload: unknown = null;
|
|
80
|
+
try {
|
|
81
|
+
payload = JSON.parse(text);
|
|
82
|
+
} catch {
|
|
83
|
+
// The mapper owns the stable malformed/empty JSON outcome.
|
|
84
|
+
}
|
|
85
|
+
return mapExaSearchResponse(payload);
|
|
86
|
+
} finally {
|
|
87
|
+
detachBodyGuard();
|
|
55
88
|
}
|
|
56
|
-
const payload = await res.json().catch(() => null);
|
|
57
|
-
return mapExaSearchResponse(payload);
|
|
58
89
|
} catch (e) {
|
|
59
90
|
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
60
91
|
console.warn(`[web-search] exa sidecar ${kind} (${Date.now() - t0}ms)`);
|
package/src/web-search/loop.ts
CHANGED
|
@@ -318,10 +318,11 @@ export interface WebSearchLoopDeps {
|
|
|
318
318
|
/** Called before each routed-model dispatch in the loop, for attempt telemetry. Same-target 429 replays pass the `rate-limit-429` recovery kind. */
|
|
319
319
|
onAttemptSend?: (recovery?: AttemptRecoveryKind) => void;
|
|
320
320
|
/**
|
|
321
|
-
* 429
|
|
322
|
-
* or null when the pool is exhausted
|
|
321
|
+
* 429 failover hook: rotate the provider's active credential and return a rebuilt adapter,
|
|
322
|
+
* or null when the pool is exhausted. Async hooks support OAuth refresh; existing synchronous
|
|
323
|
+
* key-pool hooks remain valid.
|
|
323
324
|
*/
|
|
324
|
-
on429?: (retryAfterHeader: string | null) => ProviderAdapter | null
|
|
325
|
+
on429?: (retryAfterHeader: string | null) => ProviderAdapter | null | Promise<ProviderAdapter | null>;
|
|
325
326
|
/** Opt-in same-target 429 policy (key-auth providers). When present, 429 replays on the SAME key before on429 rotation. */
|
|
326
327
|
retryOn429Policy?: Required<RateLimitRetryPolicy> | null;
|
|
327
328
|
/** Called only when the final bridged Responses stream reaches completed or incomplete. */
|
|
@@ -529,7 +530,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
529
530
|
// 429 key-failover parity with the normal routed path: rotate pool keys until one responds
|
|
530
531
|
// or the pool is exhausted (deps.on429 returns null — cooldown map guarantees termination).
|
|
531
532
|
while (prepared.response.status === 429 && deps.on429) {
|
|
532
|
-
const rotated = deps.on429(prepared.response.headers.get("retry-after"));
|
|
533
|
+
const rotated = await deps.on429(prepared.response.headers.get("retry-after"));
|
|
533
534
|
if (!rotated) break;
|
|
534
535
|
// Never let a broken body's cancel promise outlive the cumulative header deadline. Observe
|
|
535
536
|
// it, but proceed immediately to the rotated fetch under the SAME deadline signal.
|