@bitkyc08/opencodex 2.75.0 → 2.76.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 +6 -1
- package/bin/ocx.mjs +12 -1
- package/gui/dist/assets/App-CddorHdA.js +51 -0
- package/gui/dist/assets/App-DMudtISW.css +1 -0
- package/gui/dist/assets/{Tray-CGAD2AGk.js → Tray-DjNwwrWS.js} +1 -1
- package/gui/dist/assets/index-Btze1I3T.css +1 -0
- package/gui/dist/assets/index-CvnY7E6i.js +86 -0
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/opengateway.svg +12 -0
- package/package.json +1 -1
- package/src/adapters/anthropic/account-metadata.ts +17 -0
- package/src/adapters/anthropic/client-identity.ts +69 -0
- package/src/adapters/anthropic/passthrough.ts +23 -4
- package/src/adapters/anthropic.ts +18 -1
- package/src/adapters/command-code-project-context.ts +7 -11
- package/src/adapters/devin/cloud-direct/tool-schema.ts +12 -48
- package/src/adapters/kiro-retry.ts +23 -8
- package/src/adapters/openai-chat/messages.ts +1 -1
- package/src/adapters/registry.ts +8 -1
- package/src/adapters/zed.ts +377 -0
- package/src/bridge/sse.ts +4 -2
- package/src/chatgpt/app-server-shim/app-server-rewrite.ts +45 -0
- package/src/chatgpt/app-server-shim/bundle-trust.ts +77 -0
- package/src/chatgpt/app-server-shim/filter.ts +157 -0
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +99 -0
- package/src/chatgpt/app-server-shim/launcher.ts +91 -0
- package/src/claude/desktop-3p-startup.ts +62 -0
- package/src/claude/desktop-3p.ts +5 -0
- package/src/claude/first-party-settings.ts +14 -0
- package/src/claude/intercept/cli-catalog.ts +150 -0
- package/src/claude/intercept/cli-picker.ts +82 -0
- package/src/claude/intercept/listener.ts +21 -6
- package/src/claude/intercept/picker-bootstrap.ts +23 -5
- package/src/claude/intercept/picker-models.ts +57 -13
- package/src/claude/intercept/runtime.ts +30 -2
- package/src/claude/intercept/settings.ts +3 -0
- package/src/cli/agent.ts +172 -0
- package/src/cli/alias.ts +2 -1
- package/src/cli/capabilities-command.ts +3 -2
- package/src/cli/capabilities.ts +87 -0
- package/src/cli/chatgpt-command.ts +140 -0
- package/src/cli/combo.ts +144 -19
- package/src/cli/config-command.ts +3 -1
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +5 -24
- package/src/cli/init.ts +16 -2
- package/src/cli/integrations.ts +13 -0
- package/src/cli/internal-command.ts +12 -5
- package/src/cli/observe.ts +6 -1
- package/src/cli/registry.ts +6 -0
- package/src/cli/route-policy.ts +3 -1
- package/src/cli/runtime-api.ts +5 -2
- package/src/cli/status-oauth.ts +5 -0
- package/src/cli/status.ts +8 -1
- package/src/cli/stop-approval.ts +55 -16
- package/src/cli/stop-report.ts +5 -0
- package/src/clients/lazycodex.ts +48 -0
- package/src/clients/omo-role-models.ts +111 -0
- package/src/codex/account-credit-use.ts +99 -0
- package/src/codex/account-lifecycle.ts +2 -0
- package/src/codex/agent-role-models.ts +171 -0
- package/src/codex/auth-api/account-list.ts +10 -3
- package/src/codex/auth-api/routes.ts +34 -0
- package/src/codex/auth-context.ts +52 -11
- package/src/codex/catalog/discovered-natives.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +6 -1
- package/src/codex/catalog/model-hints.ts +9 -1
- package/src/codex/catalog/provider-models.ts +60 -0
- package/src/codex/catalog/routed-gather.ts +30 -5
- package/src/codex/convergence-types.ts +2 -1
- package/src/codex/main-account-external-usage.ts +62 -0
- package/src/codex/main-account-hard-lock.ts +24 -13
- package/src/codex/native-profile-processes.ts +53 -16
- package/src/codex/prompt-layers/toml-edit.ts +1 -1
- package/src/codex/quota-types.ts +3 -7
- package/src/codex/quota.ts +19 -4
- package/src/codex/role-auto-assign.ts +159 -0
- package/src/codex/role-sizing-limits.ts +2 -0
- package/src/codex/role-sizing.ts +216 -0
- package/src/codex/routing/selection.ts +36 -0
- package/src/codex/routing/thread-affinity.ts +2 -0
- package/src/codex/routing.ts +4 -4
- package/src/codex/subagent-model-fallback.ts +82 -26
- package/src/codex/subagent-selectable-models.ts +32 -0
- package/src/combos/index.ts +18 -0
- package/src/combos/jev-decision-contract.ts +20 -0
- package/src/combos/jev-dispatch.ts +18 -0
- package/src/combos/jev-model-backend.ts +127 -0
- package/src/combos/jev.ts +249 -33
- package/src/combos/types.ts +125 -2
- package/src/config/diagnostics.ts +18 -0
- package/src/config/macos-system-proxy.ts +72 -15
- package/src/config/owner-registry.ts +13 -4
- package/src/config/process-state.ts +17 -8
- package/src/config/proxy-env.ts +35 -5
- package/src/config/schema/config-schema.ts +30 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config/serving-runtimes.ts +95 -1
- package/src/generated/compatibility-version.json +336 -156
- package/src/lib/errors.ts +7 -7
- package/src/lib/jsonc.ts +13 -0
- package/src/lib/local-chat-completion.ts +144 -0
- package/src/lib/provider-outbound.ts +87 -1
- package/src/lib/provider-runtime-fetch.ts +23 -0
- package/src/lib/provider-tls-profile.ts +221 -0
- package/src/lib/request-execution-budget.ts +16 -6
- package/src/lib/spend-reservation-ledger.ts +54 -20
- package/src/lib/sse-decoder.ts +20 -17
- package/src/lib/synced-state-location.ts +96 -0
- package/src/lib/winsw.ts +14 -5
- package/src/oauth/anthropic-account-refusal.ts +53 -4
- package/src/oauth/anthropic-model-quota.ts +83 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +37 -0
- package/src/oauth/anthropic-routing.ts +112 -58
- package/src/oauth/health.ts +39 -3
- package/src/oauth/index.ts +14 -0
- package/src/oauth/kiro-account-load.ts +10 -0
- package/src/oauth/zed.ts +134 -0
- package/src/providers/model-discovery.ts +19 -8
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/provider-id-rewrite.ts +9 -2
- package/src/providers/quota/account-cache.ts +29 -7
- package/src/providers/quota/anthropic-cooldown-recovery.ts +18 -0
- package/src/providers/quota/anthropic-family-headers.ts +25 -0
- package/src/providers/quota/vendor-probes-oauth.ts +5 -1
- package/src/providers/quota-types.ts +4 -0
- package/src/providers/registry/entries-core.ts +18 -0
- package/src/providers/registry/entries-extended.ts +55 -0
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/xai-fast-model.ts +12 -4
- package/src/providers/zed.ts +598 -0
- package/src/responses/state/snapshot-select.ts +22 -9
- package/src/responses/state.ts +2 -3
- package/src/router.ts +9 -0
- package/src/server/anthropic-rate-limit-headers.ts +7 -0
- package/src/server/auth-cors.ts +7 -0
- package/src/server/chat-completions.ts +9 -4
- package/src/server/claude-messages.ts +59 -21
- package/src/server/index/claude-intercept-lifecycle.ts +71 -25
- package/src/server/index/optional-listeners.ts +4 -0
- package/src/server/index/spend-ledger-lifecycle.ts +4 -0
- package/src/server/management/agent-settings-routes.ts +74 -37
- package/src/server/management/claude-desktop-picker-routes.ts +7 -0
- package/src/server/management/claude-intercept-routes.ts +35 -0
- package/src/server/management/codex-agent-role-routes.ts +122 -0
- package/src/server/management/codex-role-auto-assign.ts +208 -0
- package/src/server/management/combo-routes.ts +62 -5
- package/src/server/management/config-routes.ts +35 -3
- package/src/server/management/context.ts +4 -1
- package/src/server/management/decision-discovery.ts +74 -0
- package/src/server/management/decision-model-validation.ts +80 -0
- package/src/server/management/decision-routes.ts +160 -0
- package/src/server/management/native-integration-routes.ts +16 -0
- package/src/server/management/provider-routes.ts +30 -34
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management/zed-provider-probe.ts +38 -0
- package/src/server/management-api.ts +12 -1
- package/src/server/messages-native-oauth.ts +7 -2
- package/src/server/messages-native.ts +7 -3
- package/src/server/relay.ts +3 -1
- package/src/server/request-log.ts +2 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +18 -3
- package/src/server/responses/compact.ts +14 -1
- package/src/server/responses/core-auth.ts +2 -0
- package/src/server/responses/core-combo.ts +67 -11
- package/src/server/responses/core-normalize.ts +21 -1
- package/src/server/responses/core-options.ts +11 -1
- package/src/server/responses/core.ts +5 -3
- package/src/server/responses/fetch-helpers.ts +6 -3
- package/src/server/responses/jev-model-invoke.ts +190 -0
- package/src/server/responses/passthrough-delivery.ts +16 -0
- package/src/server/responses/policy-fallback.ts +55 -3
- package/src/server/responses/policy-request-scope.ts +80 -0
- package/src/server/responses/request-prepare.ts +70 -18
- package/src/server/responses/request-transport.ts +54 -23
- package/src/server/responses/run-turn-execution.ts +1 -0
- package/src/server/responses/sidecar-execution.ts +9 -3
- package/src/server/responses-hosted-image-display.ts +225 -0
- package/src/service/managing-cli.ts +34 -4
- package/src/service/systemd-env.ts +75 -0
- package/src/service/windows-guarded-manager.ts +60 -19
- package/src/service/windows-scheduler.ts +22 -3
- package/src/service/windows-taskxml.ts +32 -10
- package/src/service-manager-probe.ts +72 -19
- package/src/types/config.ts +31 -1
- package/src/types/provider.ts +2 -0
- package/src/types/request.ts +2 -0
- package/src/update/index.ts +39 -6
- package/src/update/job.ts +63 -63
- package/src/update/npm-cache-preflight.d.mts +33 -1
- package/src/update/npm-cache-preflight.mjs +131 -18
- package/src/update/ownership-transaction.ts +19 -1
- package/src/update/restart-ownership.ts +80 -6
- package/src/update/transactional-install.d.mts +2 -0
- package/src/update/transactional-install.mjs +6 -1
- package/src/usage/jev-stats.ts +28 -1
- package/src/usage/log.ts +1 -1
- package/src/vision/anthropic-describe.ts +5 -3
- package/src/vision/index.ts +1 -0
- package/src/vision/plan.ts +2 -1
- package/src/vision/routed-describe.ts +29 -121
- package/src/web-search/alpha-search.ts +3 -3
- package/src/web-search/anthropic-executor.ts +6 -4
- package/src/web-search/index.ts +1 -1
- package/src/web-search/loop.ts +3 -3
- package/src/web-search/passthrough-bridge.ts +2 -1
- package/src/web-search/run-turn-loop.ts +1 -1
- package/src/web-search/sidecar-providers.ts +2 -1
- package/gui/dist/assets/App-ia4U0ufY.css +0 -1
- package/gui/dist/assets/App-xm0zGNOe.js +0 -51
- package/gui/dist/assets/index-DBT3Q31Y.css +0 -1
- package/gui/dist/assets/index-D_RGAT6Q.js +0 -86
- package/gui/dist/assets/quota-summary-CUFq48t-.js +0 -1
|
@@ -66,9 +66,11 @@ import { handleRoutingAnalyticsRoutes } from "./management/routing-analytics-rou
|
|
|
66
66
|
import { handleMetricsRoutes } from "./management/metrics-routes";
|
|
67
67
|
import { handleProviderRoutes } from "./management/provider-routes";
|
|
68
68
|
import { handleModelRoutes } from "./management/model-routes";
|
|
69
|
+
import { handleClaudeInterceptRoutes } from "./management/claude-intercept-routes";
|
|
69
70
|
import { handleAgentSettingsRoutes } from "./management/agent-settings-routes";
|
|
70
71
|
import { handleOauthAccountRoutes } from "./management/oauth-account-routes";
|
|
71
72
|
import { handleComboRoutes } from "./management/combo-routes";
|
|
73
|
+
import { handleDecisionRoutes } from "./management/decision-routes";
|
|
72
74
|
import { handleSystemRoutes } from "./management/system-routes";
|
|
73
75
|
import { handleSidebarRoutes } from "./management/sidebar-routes";
|
|
74
76
|
import { handleUsageTimelineRoutes } from "./management/usage-timeline-routes";
|
|
@@ -166,6 +168,12 @@ async function handleWorkflowBudgetRoutesOnDemand(ctx: ManagementContext): Promi
|
|
|
166
168
|
return handleWorkflowBudgetRoutes(ctx);
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
async function handleCodexAgentRoleRoutesOnDemand(ctx: ManagementContext): Promise<Response | null> {
|
|
172
|
+
if (!pathInManagementNamespace(ctx.url.pathname, "/api/codex-agent-roles")) return null;
|
|
173
|
+
const { handleCodexAgentRoleRoutes } = await import("./management/codex-agent-role-routes");
|
|
174
|
+
return handleCodexAgentRoleRoutes(ctx);
|
|
175
|
+
}
|
|
176
|
+
|
|
169
177
|
/**
|
|
170
178
|
* Lazy like the Lab and routing-profile handlers: the protocol planner reaches the router and
|
|
171
179
|
* the ingress eligibility rules, which no other dashboard request needs.
|
|
@@ -326,7 +334,8 @@ export async function handleManagementAPI(
|
|
|
326
334
|
}
|
|
327
335
|
let routed: Response | null | undefined;
|
|
328
336
|
try {
|
|
329
|
-
routed =
|
|
337
|
+
routed = await handleClaudeInterceptRoutes(ctx)
|
|
338
|
+
?? handleSessionRoutes(ctx)
|
|
330
339
|
?? (await handleLinkRoutesOnDemand(ctx))
|
|
331
340
|
?? (await handleRemoteWorkspaceRoutesOnDemand(ctx))
|
|
332
341
|
?? (await handleConfigRoutes(ctx))
|
|
@@ -336,6 +345,7 @@ export async function handleManagementAPI(
|
|
|
336
345
|
?? (await handleQuotaResetRoutesOnDemand(ctx))
|
|
337
346
|
?? (await handleLowQuotaRoutesOnDemand(ctx))
|
|
338
347
|
?? (await handleWorkflowBudgetRoutesOnDemand(ctx))
|
|
348
|
+
?? (await handleCodexAgentRoleRoutesOnDemand(ctx))
|
|
339
349
|
?? (await handleProtocolRoutesOnDemand(ctx))
|
|
340
350
|
?? (await handleGrokCouponRoutesOnDemand(ctx))
|
|
341
351
|
?? (await handleAnthropicResetGrantRoutesOnDemand(ctx))
|
|
@@ -351,6 +361,7 @@ export async function handleManagementAPI(
|
|
|
351
361
|
?? (await handleAgentSettingsRoutes(ctx))
|
|
352
362
|
?? (await handleCodexPromptRoutes(ctx))
|
|
353
363
|
?? (await handleOauthAccountRoutes(ctx))
|
|
364
|
+
?? (await handleDecisionRoutes(ctx))
|
|
354
365
|
?? (await handleComboRoutes(ctx))
|
|
355
366
|
?? (await handleSystemRoutes(ctx))
|
|
356
367
|
?? (await handleLabRoutesOnDemand(ctx))
|
|
@@ -55,6 +55,8 @@ function isRec(value: unknown): value is Rec {
|
|
|
55
55
|
export interface NativeOAuthBinding {
|
|
56
56
|
readonly selection: Selection;
|
|
57
57
|
readonly snapshot: OAuthAccessSnapshot;
|
|
58
|
+
/** Anthropic account UUID from the same stored generation, distinct from snapshot.accountId. */
|
|
59
|
+
readonly providerAccountUuid?: string;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/** The selection moved or became pooled while it was being resolved. Maps to a 409 retry. */
|
|
@@ -95,7 +97,9 @@ export async function resolveNativeOAuthBinding(config: OcxConfig): Promise<Nati
|
|
|
95
97
|
expectedCredentialGeneration: candidate.generation,
|
|
96
98
|
})) continue;
|
|
97
99
|
if (pooled(config)) break;
|
|
98
|
-
const
|
|
100
|
+
const row = getAccountCredentialWithStatus(PROVIDER, candidate.accountId);
|
|
101
|
+
if (!row || credentialGeneration(row.credential) !== candidate.generation) continue;
|
|
102
|
+
const binding = { selection: committed, snapshot: candidate, providerAccountUuid: row.credential.accountId };
|
|
99
103
|
if (nativeOAuthBindingIsCurrent(binding)) return binding;
|
|
100
104
|
}
|
|
101
105
|
// Re-read after a lost commit or a health change during credential resolution.
|
|
@@ -114,7 +118,8 @@ export function nativeOAuthBindingIsCurrent(binding: NativeOAuthBinding): boolea
|
|
|
114
118
|
&& selected?.revision === binding.selection.revision
|
|
115
119
|
&& !!row && !row.paused && !row.needsReauth && row.credential.expires > Date.now()
|
|
116
120
|
&& !getAnthropicAccountHealthSnapshot(binding.snapshot.accountId)
|
|
117
|
-
&& credentialGeneration(row.credential) === binding.snapshot.generation
|
|
121
|
+
&& credentialGeneration(row.credential) === binding.snapshot.generation
|
|
122
|
+
&& row.credential.accountId === binding.providerAccountUuid;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
/** Map a `tool_use` block's wire name back to the caller's name; other blocks are untouched. */
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* key selection, key failover and 429 replay, connection policy — so nothing the Responses
|
|
11
11
|
* pipeline enforces is bypassed.
|
|
12
12
|
*
|
|
13
|
-
* Authority.
|
|
14
|
-
*
|
|
13
|
+
* Authority. The ingress hands over caller `anthropic-beta` for allowlisting and an opaque
|
|
14
|
+
* observed CLI identity handle for first-party compatibility. Neither grants credential authority. The caller-forward passthrough in
|
|
15
15
|
* `claude-messages.ts` (the caller's own Anthropic credential) is a different branch decided
|
|
16
16
|
* before this one, and nothing here can reach it or be reached from it.
|
|
17
17
|
*
|
|
@@ -67,6 +67,7 @@ import type { OcxProviderTransport } from "../providers/xai-transport";
|
|
|
67
67
|
import { preservesPhysicalComboProvider, resolveComboId } from "../combos";
|
|
68
68
|
import { captureRouteStaticPolicy, routeModel, type RouteResult } from "../router";
|
|
69
69
|
import { POLICY_NAMESPACE, resolvePolicyProfileId } from "../routing/profile";
|
|
70
|
+
import type { AnthropicClientIdentity } from "../adapters/anthropic/client-identity";
|
|
70
71
|
import type { OcxConfig, OcxProviderConfig, OcxUsage } from "../types";
|
|
71
72
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
72
73
|
import {
|
|
@@ -145,9 +146,10 @@ export interface HandleNativeMessagesOptions {
|
|
|
145
146
|
selector?: NativeMessagesSelector;
|
|
146
147
|
/**
|
|
147
148
|
* The caller's `anthropic-beta` header, handed over by the ingress. The builder keeps only
|
|
148
|
-
* allowlisted values
|
|
149
|
+
* allowlisted values. Client identity is a separate bounded opaque handle.
|
|
149
150
|
*/
|
|
150
151
|
callerAnthropicBeta?: string | null;
|
|
152
|
+
clientIdentity?: AnthropicClientIdentity;
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
type FinishLog = (status: number, message?: string, meta?: FinalRequestLogMeta) => void;
|
|
@@ -410,6 +412,8 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
|
|
|
410
412
|
recordAttemptCredentialSource(attempt, route.providerName, activeProvider, "anthropic");
|
|
411
413
|
const built = buildAnthropicMessagesPassthroughRequest(activeProvider, route.modelId, body, config, {
|
|
412
414
|
callerAnthropicBeta: options.callerAnthropicBeta,
|
|
415
|
+
providerAccountUuid: oauthBinding?.providerAccountUuid,
|
|
416
|
+
clientIdentity: options.clientIdentity,
|
|
413
417
|
});
|
|
414
418
|
if (built.strippedOpaqueState && rejectUnrepresentable) throw new NativeOpaqueStateRefusal();
|
|
415
419
|
if (built.droppedBetas) addProtocolEntryReason(logCtx, "anthropic-beta-dropped");
|
package/src/server/relay.ts
CHANGED
|
@@ -779,7 +779,9 @@ export function firstOutputFromParsed(parsed: unknown): boolean {
|
|
|
779
779
|
const event = parsed as { type?: unknown; delta?: unknown };
|
|
780
780
|
return (event.type === "response.output_text.delta"
|
|
781
781
|
|| event.type === "response.reasoning_summary_text.delta"
|
|
782
|
-
|| event.type === "response.reasoning_text.delta"
|
|
782
|
+
|| event.type === "response.reasoning_text.delta"
|
|
783
|
+
|| event.type === "response.function_call_arguments.delta"
|
|
784
|
+
|| event.type === "response.custom_tool_call_input.delta")
|
|
783
785
|
&& typeof event.delta === "string"
|
|
784
786
|
&& event.delta.length > 0;
|
|
785
787
|
}
|
|
@@ -247,6 +247,8 @@ export interface RequestLogContext {
|
|
|
247
247
|
terminalSource?: "upstream" | "synthetic";
|
|
248
248
|
/** Bounded route-decision trace (RI-01); never contains secrets. */
|
|
249
249
|
routeDecision?: RouteDecisionTraceV1;
|
|
250
|
+
/** Full eligible provider/model membership from a policy route; never logged. */
|
|
251
|
+
policyEligibility?: ReadonlySet<string>;
|
|
250
252
|
/** Privacy-bounded JEV selection metadata; downstream usage is recorded on attempts[]. */
|
|
251
253
|
jevDecision?: PersistedJevDecisionV1;
|
|
252
254
|
/** Opt-in shadow evidence, normalized again at the logging boundary. */
|
|
@@ -396,9 +396,9 @@ export function createAdapterContinuations(
|
|
|
396
396
|
) {
|
|
397
397
|
const nextAccountId = await rotateAnthropicAccountOnResponse(response, {
|
|
398
398
|
config, accountId: transportState.anthropicPoolAccountId, sessionKey: anthropicSessionKey,
|
|
399
|
-
decision: transportState.anthropicRouteDecision, signal: upstream.signal,
|
|
400
|
-
canRetry: transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
401
|
-
allowAccountRefusal,
|
|
399
|
+
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, signal: upstream.signal,
|
|
400
|
+
canRetry: !sendBudgetExhausted() && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
401
|
+
allowAccountRefusal, allow429Recovery: allowAccountRefusal,
|
|
402
402
|
});
|
|
403
403
|
if (nextAccountId) {
|
|
404
404
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classifyAnthropic429 } from "../../oauth/anthropic-rate-limit-policy";
|
|
1
2
|
import { rotateAnthropicAccountOnResponse } from "../../oauth/anthropic-account-refusal";
|
|
2
3
|
import { isNonReplayableResponse } from "../../lib/upstream-retry";
|
|
3
4
|
import { isLocalUpstream } from "../../lib/local-upstream";
|
|
@@ -691,7 +692,8 @@ export async function prepareAdapterExchange(
|
|
|
691
692
|
refreshed = await refreshResolvedOAuthSelection(transportState.sentOAuthSnapshot);
|
|
692
693
|
} catch (err) {
|
|
693
694
|
const failed = transportState.sentOAuthSnapshot;
|
|
694
|
-
if (route.providerName === "google-antigravity" &&
|
|
695
|
+
if (route.providerName === "google-antigravity" && !antigravityAuthRotationAttempted
|
|
696
|
+
&& err instanceof OAuthLoginRequiredError && failed
|
|
695
697
|
&& getAccountSet(route.providerName)?.accounts.some(row =>
|
|
696
698
|
row.id === failed.accountId && row.needsReauth === true)) {
|
|
697
699
|
antigravityAuthRotationAttempted = true;
|
|
@@ -993,8 +995,8 @@ export async function prepareAdapterExchange(
|
|
|
993
995
|
) {
|
|
994
996
|
const nextAccountId = await rotateAnthropicAccountOnResponse(upstreamResponse, {
|
|
995
997
|
config, accountId: transportState.anthropicPoolAccountId, sessionKey: anthropicSessionKey,
|
|
996
|
-
decision: transportState.anthropicRouteDecision, signal: upstream.signal,
|
|
997
|
-
canRetry: transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
998
|
+
model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, signal: upstream.signal,
|
|
999
|
+
canRetry: !sendBudgetExhausted() && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
|
|
998
1000
|
});
|
|
999
1001
|
if (!nextAccountId) break;
|
|
1000
1002
|
try {
|
|
@@ -1091,6 +1093,11 @@ export async function prepareAdapterExchange(
|
|
|
1091
1093
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1092
1094
|
config.cacheRetention,
|
|
1093
1095
|
);
|
|
1096
|
+
bindRouteReasoningReplayScope({
|
|
1097
|
+
parsed, providerName: route.providerName, provider: route.provider,
|
|
1098
|
+
adapterName: transportState.activeAdapter.name,
|
|
1099
|
+
oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
|
|
1100
|
+
});
|
|
1094
1101
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.activeAdapter.name, logCtx.accountLogLabel);
|
|
1095
1102
|
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
|
|
1096
1103
|
// The replay IS this hop's send, so hand the reservation down and let the layer that
|
|
@@ -1190,6 +1197,13 @@ export async function prepareAdapterExchange(
|
|
|
1190
1197
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
1191
1198
|
config.cacheRetention,
|
|
1192
1199
|
);
|
|
1200
|
+
// Same contract as the Kiro refusal arm: the replay must carry the replacement
|
|
1201
|
+
// account's continuation owner and reasoning scope, never the account that just 429'd.
|
|
1202
|
+
bindRouteReasoningReplayScope({
|
|
1203
|
+
parsed, providerName: route.providerName, provider: route.provider,
|
|
1204
|
+
adapterName: transportState.activeAdapter.name,
|
|
1205
|
+
oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
|
|
1206
|
+
});
|
|
1193
1207
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.activeAdapter.name, logCtx.accountLogLabel);
|
|
1194
1208
|
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
|
|
1195
1209
|
// The replay IS this hop's send, so hand the reservation down and let the layer that
|
|
@@ -1414,6 +1428,7 @@ export async function prepareAdapterExchange(
|
|
|
1414
1428
|
status: upstreamResponse.status,
|
|
1415
1429
|
message,
|
|
1416
1430
|
upstreamRetryAfter,
|
|
1431
|
+
includeDefault: !(transportState.anthropicPoolAccountId && classifyAnthropic429(upstreamResponse.headers) === "request-scoped-unknown"),
|
|
1417
1432
|
});
|
|
1418
1433
|
return formatErrorResponse(
|
|
1419
1434
|
upstreamResponse.status,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { capturePoolQuotaWriter } from "../../codex/account-store";
|
|
2
|
+
import { previewXaiOauthWireModel } from "./core-normalize";
|
|
2
3
|
import {
|
|
3
4
|
admissionModelDeniedResponse,
|
|
4
5
|
AdmissionModelDeniedError,
|
|
@@ -196,6 +197,7 @@ import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel }
|
|
|
196
197
|
import { mapCodexAuthContextErrorToResponse, nativeMainRefreshFailureResponse } from "./codex-auth-error";
|
|
197
198
|
import { linkRequestSessionLane, sessionLaneIdFromRequest } from "../request-log-conversation";
|
|
198
199
|
import { recallComboForLane } from "./combo-session-recall";
|
|
200
|
+
import { redactHostedImageDisplayPaths } from "../responses-hosted-image-display";
|
|
199
201
|
|
|
200
202
|
export const COMPACT_RESPONSE_MAX_BYTES = 32 * 1024 * 1024;
|
|
201
203
|
|
|
@@ -622,6 +624,8 @@ export async function handleResponsesCompact(
|
|
|
622
624
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
623
625
|
return formatErrorResponse(400, "invalid_request_error", "Invalid compaction request body");
|
|
624
626
|
}
|
|
627
|
+
// Compaction forwards client history upstream too; keep local artifact paths out of it.
|
|
628
|
+
redactHostedImageDisplayPaths(body);
|
|
625
629
|
if (!options.compactionRoutingOverride) {
|
|
626
630
|
options = { ...options, compactionRoutingOverride: applyCompactionRoutingOverride(body, req.headers, config, { endpoint: "compact" }) };
|
|
627
631
|
}
|
|
@@ -670,7 +674,15 @@ export async function handleResponsesCompact(
|
|
|
670
674
|
// A compaction override picks the model, not the caller, so the key's scope
|
|
671
675
|
// is applied to what the override resolved to rather than to the selector
|
|
672
676
|
// the client sent.
|
|
673
|
-
|
|
677
|
+
// Routed xAI OAuth compaction must preview its Fast wire model here;
|
|
678
|
+
// native compact keeps checking the destination it dispatches directly.
|
|
679
|
+
const callerTier = (raw as Record<string, unknown>).service_tier;
|
|
680
|
+
assertRouteAllowedByScope(resolveAdmissionModelScope(config, admission), compactRequestedModel, {
|
|
681
|
+
providerName: route.providerName,
|
|
682
|
+
modelId: previewXaiOauthWireModel({ options: {
|
|
683
|
+
serviceTier: compactFastRow ? "priority" : typeof callerTier === "string" ? callerTier : undefined,
|
|
684
|
+
} }, route, config, "responses"),
|
|
685
|
+
});
|
|
674
686
|
} catch (err) {
|
|
675
687
|
if (err instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(err);
|
|
676
688
|
if (err instanceof NoEligiblePolicyCandidateError) {
|
|
@@ -691,6 +703,7 @@ export async function handleResponsesCompact(
|
|
|
691
703
|
logCtx.requestedModel = compactRequestedModel;
|
|
692
704
|
logCtx.model = selectedModelId;
|
|
693
705
|
logCtx.routeDecision = route.routeDecision;
|
|
706
|
+
logCtx.policyEligibility = route.policyEligibility;
|
|
694
707
|
logCtx.provider = route.codexAccountNamespace
|
|
695
708
|
? `${route.providerName}-${route.codexAccountNamespace}`
|
|
696
709
|
: route.providerName;
|
|
@@ -89,6 +89,8 @@ export function codexRouteCredentialDomainHeaders(
|
|
|
89
89
|
// Route-changing recursion retains typed admission, never an unscoped raw
|
|
90
90
|
// caller credential. Bearer admission is substituted or stripped below.
|
|
91
91
|
const routeMayChangeCredentialDomain = options.comboAttempt === true
|
|
92
|
+
// A JEV decision-model turn is a new credential domain even though it starts headerless.
|
|
93
|
+
|| options.internalDecisionCall === true
|
|
92
94
|
|| route.routeKind === "policy"
|
|
93
95
|
|| route.credentialDomainRewrite === true
|
|
94
96
|
|| credentialDomainWasRewritten;
|
|
@@ -31,7 +31,8 @@ import {
|
|
|
31
31
|
advanceComboAfterFailure,
|
|
32
32
|
comboFailureCooldownScope,
|
|
33
33
|
JEV_PROVIDER_ID,
|
|
34
|
-
|
|
34
|
+
jevDecisionBackendFor,
|
|
35
|
+
resolveJevComboDecision,
|
|
35
36
|
type ComboPick,
|
|
36
37
|
type JevCandidate,
|
|
37
38
|
type JevDecision,
|
|
@@ -48,7 +49,12 @@ import { routeConcreteModel, comboRouteDecisionTrace } from "../../router";
|
|
|
48
49
|
import { memoryModelRouteReason } from "./memory-models";
|
|
49
50
|
import { poolAccountProviderLabel } from "../../providers/label";
|
|
50
51
|
import { getAccountSet } from "../../oauth/store";
|
|
51
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
formatAnthropicProviderForLog,
|
|
54
|
+
getAnthropicAccountHealthSnapshot,
|
|
55
|
+
getAnthropicPoolRetryAfterSeconds,
|
|
56
|
+
getEligibleAnthropicAccounts,
|
|
57
|
+
} from "../../oauth/anthropic-routing";
|
|
52
58
|
import { codexAccountLogLabel } from "../../codex/account-label";
|
|
53
59
|
import { codexQuotaScopeForModel, getCodexQuotaHealthSnapshot } from "../../codex/routing";
|
|
54
60
|
import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
@@ -93,6 +99,7 @@ import { streamingContextOverflowResponse, jsonContextOverflowResponse } from ".
|
|
|
93
99
|
import { mandatoryResponsesReasoningReplayUnavailable } from "./core-replay";
|
|
94
100
|
import { settleOperatorReplacement } from "../../lib/upstream-retry";
|
|
95
101
|
import { createComboProtocolLanes, dispatchNativeComboChild } from "./core-combo-native";
|
|
102
|
+
import { createJevModelInvoker } from "./jev-model-invoke";
|
|
96
103
|
import { clientWireOf } from "../inference/client-wire";
|
|
97
104
|
|
|
98
105
|
/**
|
|
@@ -119,6 +126,33 @@ function cooledPoolAccountLabel(config: OcxConfig, providerName: string, modelId
|
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
|
|
129
|
+
/**
|
|
130
|
+
* True only for the Anthropic OAuth pool's OWN "all accounts cooled" 429. Its Retry-After is the
|
|
131
|
+
* earliest per-account cooldown restated (`getAnthropicPoolRetryAfterSeconds`), which the pool
|
|
132
|
+
* keeps enforcing locally, so it must not also park the combo target: an account added, resumed or
|
|
133
|
+
* cleared a minute later would otherwise sit ignored until the target expired, up to the 24h
|
|
134
|
+
* server-delay ceiling.
|
|
135
|
+
*
|
|
136
|
+
* Provenance, not just current state: the target must be the OAuth pool, no account may have been
|
|
137
|
+
* identified for the failure, and the pool must be holding the wait right now. An upstream 429 on
|
|
138
|
+
* a pool account always cools and names that account, and an API-key provider never qualifies, so
|
|
139
|
+
* a genuine upstream Retry-After still parks the target in full.
|
|
140
|
+
*/
|
|
141
|
+
export function isAnthropicPoolLocalRefusal(
|
|
142
|
+
config: OcxConfig,
|
|
143
|
+
providerName: string,
|
|
144
|
+
status: number,
|
|
145
|
+
failedAccount: string | undefined,
|
|
146
|
+
now = Date.now(),
|
|
147
|
+
): boolean {
|
|
148
|
+
return providerName === "anthropic"
|
|
149
|
+
&& config.providers[providerName]?.authMode === "oauth"
|
|
150
|
+
&& status === 429
|
|
151
|
+
&& failedAccount === undefined
|
|
152
|
+
&& getEligibleAnthropicAccounts(now).length === 0
|
|
153
|
+
&& getAnthropicPoolRetryAfterSeconds(now) !== null;
|
|
154
|
+
}
|
|
155
|
+
|
|
122
156
|
/**
|
|
123
157
|
* A combo's execution policy is DECLARED by the combo, not inherited from the single-target
|
|
124
158
|
* profile.
|
|
@@ -533,16 +567,30 @@ export async function executeComboResponses(
|
|
|
533
567
|
const decisionStartedAt = Date.now();
|
|
534
568
|
let decision: JevDecision;
|
|
535
569
|
try {
|
|
536
|
-
decision = await
|
|
570
|
+
decision = await resolveJevComboDecision({
|
|
537
571
|
body,
|
|
538
572
|
candidates: choices.map(choice => choice.candidate),
|
|
539
573
|
fallback,
|
|
540
574
|
config,
|
|
575
|
+
...(combo.decisionProvider ? { decisionProvider: combo.decisionProvider } : {}),
|
|
576
|
+
...(combo.decisionModel
|
|
577
|
+
? {
|
|
578
|
+
decisionModel: combo.decisionModel,
|
|
579
|
+
invokeModel: createJevModelInvoker({
|
|
580
|
+
req,
|
|
581
|
+
config,
|
|
582
|
+
options,
|
|
583
|
+
handleResponses: requestDispatchers.handleResponses,
|
|
584
|
+
}),
|
|
585
|
+
}
|
|
586
|
+
: {}),
|
|
587
|
+
...(combo.decisionTimeoutMs !== undefined ? { timeoutMs: combo.decisionTimeoutMs } : {}),
|
|
541
588
|
signal: options.abortSignal,
|
|
542
589
|
});
|
|
543
590
|
} catch (error) {
|
|
544
591
|
if (options.abortSignal?.aborted) return clientCancelledResponse();
|
|
545
592
|
decision = {
|
|
593
|
+
backend: jevDecisionBackendFor(combo),
|
|
546
594
|
...fallback,
|
|
547
595
|
gate: "network",
|
|
548
596
|
latencyMs: Math.max(0, Date.now() - decisionStartedAt),
|
|
@@ -561,6 +609,7 @@ export async function executeComboResponses(
|
|
|
561
609
|
},
|
|
562
610
|
gate: decision.gate,
|
|
563
611
|
latencyMs: decision.latencyMs,
|
|
612
|
+
backend: decision.backend,
|
|
564
613
|
...(decision.confidence !== undefined ? { confidence: decision.confidence } : {}),
|
|
565
614
|
...(decision.chosenProbability !== undefined
|
|
566
615
|
? { chosenProbability: decision.chosenProbability }
|
|
@@ -568,6 +617,7 @@ export async function executeComboResponses(
|
|
|
568
617
|
...(decision.usage ? { usage: decision.usage } : {}),
|
|
569
618
|
});
|
|
570
619
|
console.debug("[combo] JEV decision", {
|
|
620
|
+
backend: decision.backend,
|
|
571
621
|
targetKey: decision.targetKey,
|
|
572
622
|
effort: decision.effort,
|
|
573
623
|
gate: decision.gate,
|
|
@@ -988,8 +1038,20 @@ export async function executeComboResponses(
|
|
|
988
1038
|
});
|
|
989
1039
|
const failedTargetKey = targetKey(pick.target);
|
|
990
1040
|
let failedTargetCooldownRecorded = false;
|
|
1041
|
+
// The dispatch rewrote this to name the pool account that actually served the turn.
|
|
1042
|
+
const failedAccount = cooledPoolAccountLabel(
|
|
1043
|
+
config,
|
|
1044
|
+
pick.target.provider,
|
|
1045
|
+
pick.target.model,
|
|
1046
|
+
poolAccountProviderLabel(childLog.provider, pick.target.provider),
|
|
1047
|
+
);
|
|
1048
|
+
// The target still cools on the pool's own refusal (the anti-hammer guard), but for the local
|
|
1049
|
+
// fallback rather than the pool's restated per-account Retry-After.
|
|
1050
|
+
const poolLocalRefusal = isAnthropicPoolLocalRefusal(
|
|
1051
|
+
config, pick.target.provider, failure.response.status, failedAccount, failureNow,
|
|
1052
|
+
);
|
|
991
1053
|
const nextPick = advanceComboAfterFailure(config, pick, {
|
|
992
|
-
retryAfter: failure.retryAfter,
|
|
1054
|
+
retryAfter: poolLocalRefusal ? undefined : failure.retryAfter,
|
|
993
1055
|
resetAt: failure.resetAt,
|
|
994
1056
|
cooldownMs: combo.cooldownMs,
|
|
995
1057
|
now: failureNow,
|
|
@@ -998,13 +1060,7 @@ export async function executeComboResponses(
|
|
|
998
1060
|
status: failure.response.status,
|
|
999
1061
|
code: failure.upstreamCode,
|
|
1000
1062
|
message: failure.classificationText,
|
|
1001
|
-
|
|
1002
|
-
failedAccount: cooledPoolAccountLabel(
|
|
1003
|
-
config,
|
|
1004
|
-
pick.target.provider,
|
|
1005
|
-
pick.target.model,
|
|
1006
|
-
poolAccountProviderLabel(childLog.provider, pick.target.provider),
|
|
1007
|
-
),
|
|
1063
|
+
failedAccount,
|
|
1008
1064
|
onCooldownRecorded: target => {
|
|
1009
1065
|
failedTargetCooldownRecorded ||= targetKey(target) === failedTargetKey;
|
|
1010
1066
|
},
|
|
@@ -38,7 +38,26 @@ import { isInjectionDebugEnabled } from "../../lib/debug-settings";
|
|
|
38
38
|
import { injectionDebugLog } from "../../lib/injection-debug-log";
|
|
39
39
|
import { recordAttemptRequestedEffort } from "../request-log";
|
|
40
40
|
import type { ResolvedFastPolicy } from "../../providers/fastwire";
|
|
41
|
-
import { applyXaiOauthFastModel } from "../../providers/xai-fast-model";
|
|
41
|
+
import { applyXaiOauthFastModel, xaiOauthFastModelForDecision } from "../../providers/xai-fast-model";
|
|
42
|
+
|
|
43
|
+
/** Preview the billed xAI lane without mutating request state or resolving credentials. */
|
|
44
|
+
export function previewXaiOauthWireModel(
|
|
45
|
+
parsed: { options: Pick<OcxParsedRequest["options"], "serviceTier"> },
|
|
46
|
+
route: RouteResult,
|
|
47
|
+
config: OcxConfig,
|
|
48
|
+
inboundWire: InboundWire,
|
|
49
|
+
): string {
|
|
50
|
+
if (route.providerName !== "xai" || route.provider.authMode !== "oauth") return route.modelId;
|
|
51
|
+
const provider = resolveWireProtocolOverride(
|
|
52
|
+
route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy,
|
|
53
|
+
);
|
|
54
|
+
const policy = fastPolicyForModel(
|
|
55
|
+
provider, route.modelId, route.providerName, inboundWire, config.providers[route.providerName],
|
|
56
|
+
);
|
|
57
|
+
return xaiOauthFastModelForDecision(
|
|
58
|
+
{ ...route, provider }, decideTier(policy, config.fastMode, parsed.options.serviceTier),
|
|
59
|
+
) ?? route.modelId;
|
|
60
|
+
}
|
|
42
61
|
|
|
43
62
|
export const MAX_FAST_WIRE_CAPABILITY_WARNINGS = 256;
|
|
44
63
|
|
|
@@ -186,6 +205,7 @@ export async function applyFinalRouteRequestNormalization(args: {
|
|
|
186
205
|
logCtx.provider = route.providerName;
|
|
187
206
|
logCtx.providerAdapter = route.provider.adapter;
|
|
188
207
|
logCtx.routeDecision = route.routeDecision;
|
|
208
|
+
logCtx.policyEligibility = route.policyEligibility;
|
|
189
209
|
if (route.routeReason === "model-alias" || route.modelId !== responseModelId && responseModelId.includes("/")) logCtx.requestedAlias = responseModelId;
|
|
190
210
|
|
|
191
211
|
if (responsesUpstreamStreaming === false && route.provider.adapter === "openai-responses") {
|
|
@@ -19,6 +19,7 @@ import type { TransientSendBudget } from "../../lib/upstream-retry";
|
|
|
19
19
|
import type { RequestLogContext } from "../request-log";
|
|
20
20
|
import type { UpstreamHostAdmissionLease } from "../../codex/upstream-host-health";
|
|
21
21
|
import type { AccountLease } from "../../oauth/kiro-account-load";
|
|
22
|
+
import type { PolicyRequestScope } from "./policy-request-scope";
|
|
22
23
|
|
|
23
24
|
export interface ConsumedComboFailure {
|
|
24
25
|
response: Response;
|
|
@@ -57,6 +58,10 @@ export interface ClientEncoderOption {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export interface HandleResponsesOptions {
|
|
61
|
+
/** Internal request-owned policy authorization; never read from client headers or body. */
|
|
62
|
+
policyRequestScope?: PolicyRequestScope;
|
|
63
|
+
/** Internal concrete selector chosen from the original policy evaluation. */
|
|
64
|
+
policyFallbackCandidate?: { provider: string; model: string };
|
|
60
65
|
/** Internal routed-compaction recovery: one logical request, one emergency target. */
|
|
61
66
|
compactionRecoveryAttempted?: boolean;
|
|
62
67
|
compactionRecoveryPermit?: SingleUseDispatchPermit;
|
|
@@ -66,7 +71,7 @@ export interface HandleResponsesOptions {
|
|
|
66
71
|
/** Physical-send reports already delivered to the shared used setter, including booking settlement. */
|
|
67
72
|
onCompactionRecoverySendsReported?: (count: number) => void;
|
|
68
73
|
/** Private holder for the Kiro serving-account lease. */
|
|
69
|
-
accountLoad?: { lease: AccountLease | null };
|
|
74
|
+
accountLoad?: { lease: AccountLease | null; cancelled: boolean };
|
|
70
75
|
/** Internal Claude replay identity; consumed only by the final canonical Go transport. */
|
|
71
76
|
claudeGoAffinity?: { sessionLane?: string };
|
|
72
77
|
/** Validated Claude metadata identity; projected only into final canonical attempt headers. */
|
|
@@ -74,6 +79,11 @@ export interface HandleResponsesOptions {
|
|
|
74
79
|
/** Original live policy owner; separate from caller-specific routing/sidecar snapshots. */
|
|
75
80
|
codexAuthPolicy?: CodexAuthPolicyConfig;
|
|
76
81
|
turnAdmissionLease?: AdmissionLease;
|
|
82
|
+
/**
|
|
83
|
+
* A JEV decision-model call issued by a combo. It never carries caller credentials, is never
|
|
84
|
+
* rewritten by memory or shadow-call routing, and may not dispatch into a JEV combo.
|
|
85
|
+
*/
|
|
86
|
+
internalDecisionCall?: boolean;
|
|
77
87
|
/**
|
|
78
88
|
* How the caller proved data-plane admission (#1686).
|
|
79
89
|
*
|
|
@@ -43,10 +43,12 @@ export async function handleResponses(
|
|
|
43
43
|
): Promise<Response> {
|
|
44
44
|
const ownsBudget = options.translatorBudget === undefined;
|
|
45
45
|
const translatorBudget = options.translatorBudget ?? createTranslatorBudget();
|
|
46
|
-
const accountLoad = { lease: null as import("../../oauth/kiro-account-load").AccountLease | null };
|
|
47
46
|
const abortSignal = options.abortSignal ?? req.signal;
|
|
48
|
-
const
|
|
49
|
-
|
|
47
|
+
const accountLoad = { lease: null as import("../../oauth/kiro-account-load").AccountLease | null,
|
|
48
|
+
cancelled: abortSignal.aborted };
|
|
49
|
+
function release() { accountLoad.lease?.release(); accountLoad.lease = null; abortSignal.removeEventListener("abort", cancel); }
|
|
50
|
+
function cancel() { accountLoad.cancelled = true; release(); }
|
|
51
|
+
abortSignal.addEventListener("abort", cancel, { once: true });
|
|
50
52
|
try {
|
|
51
53
|
const response = await runWithCompactionRecovery(req, config, logCtx, {
|
|
52
54
|
...options,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ProviderRequestSlot,
|
|
16
16
|
} from "../../providers/request-pacing";
|
|
17
17
|
import { withUpstreamHttpVersion } from "../../lib/upstream-http-version";
|
|
18
|
+
import { providerTlsFetch } from "../../lib/provider-tls-profile";
|
|
18
19
|
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
19
20
|
import { configuredOutboundFetch } from "../../lib/proxy-env";
|
|
20
21
|
import { isLoopbackUrl, rewriteUpstream } from "../../plugins/upstream-hooks";
|
|
@@ -252,8 +253,10 @@ export function providerFetch(
|
|
|
252
253
|
{ preconnect: globalThis.fetch.preconnect?.bind(globalThis.fetch) },
|
|
253
254
|
) as typeof globalThis.fetch);
|
|
254
255
|
const base = customExecutor ?? configuredFetch;
|
|
256
|
+
const transport = options.providerName ? providerTlsFetch(options.providerName, provider, base) : base;
|
|
255
257
|
const preconnect = (...args: Parameters<typeof globalThis.fetch.preconnect>): void => {
|
|
256
|
-
|
|
258
|
+
// A TLS profile owns the handshake; a Bun preconnect would open a differently fingerprinted one.
|
|
259
|
+
if (transport === base) base.preconnect?.(...args);
|
|
257
260
|
};
|
|
258
261
|
// Rebuilt dispatches must use the same physical-send boundary as ordinary HTTP sends.
|
|
259
262
|
// Return the original 3xx so the response owner retains its retry/health/relay contract.
|
|
@@ -266,7 +269,7 @@ export function providerFetch(
|
|
|
266
269
|
// that decided for itself has already marked the init and this pass defers to that decision.
|
|
267
270
|
const dispatch = markEgressTransparentExecutor(Object.assign(
|
|
268
271
|
(input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) =>
|
|
269
|
-
sendWithConnectionPolicy(
|
|
272
|
+
sendWithConnectionPolicy(transport, input, init, egressBinding),
|
|
270
273
|
{ preconnect },
|
|
271
274
|
) as typeof globalThis.fetch);
|
|
272
275
|
const httpFetch = Object.assign(
|
|
@@ -281,7 +284,7 @@ export function providerFetch(
|
|
|
281
284
|
// the override may rebuild against a different host and select a different transport, and
|
|
282
285
|
// refusing on this destination would reject a request whose real route is fine.
|
|
283
286
|
if (options.dispatchOverride) egressFor(input);
|
|
284
|
-
else providerEgressSendInit(egressBinding,
|
|
287
|
+
else providerEgressSendInit(egressBinding, transport, input);
|
|
285
288
|
// The hook inspects the outgoing headers and refuses the send by throwing; it is not a
|
|
286
289
|
// mutator, and the copy it receives is deliberately not threaded onward. `Connection`
|
|
287
290
|
// is decided inside `dispatch`, which runs after this, so the fresh-connection policy
|