@bitkyc08/opencodex 2.55.0 → 2.57.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/bin/ocx.mjs +10 -0
- package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
- package/gui/dist/assets/{index-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
- package/gui/dist/index.html +2 -2
- package/package.json +4 -3
- package/src/adapters/base.ts +21 -0
- package/src/adapters/codebuddy/adapter.ts +2 -1
- package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
- package/src/adapters/command-code.ts +1 -1
- package/src/adapters/cursor/envelope-echo.ts +8 -2
- package/src/adapters/cursor/transport-retry.ts +46 -1
- package/src/adapters/cursor.ts +4 -0
- package/src/adapters/google.ts +7 -7
- package/src/adapters/kiro/adapter.ts +42 -1
- package/src/adapters/kiro/payload.ts +17 -3
- package/src/adapters/kiro/reasoning.ts +70 -7
- package/src/adapters/kiro/stream.ts +8 -2
- package/src/adapters/kiro/wire.ts +2 -1
- package/src/adapters/kiro-events.ts +21 -13
- package/src/adapters/kiro-retry.ts +23 -4
- package/src/adapters/openai-chat/errors.ts +116 -0
- package/src/adapters/openai-chat/messages.ts +346 -0
- package/src/adapters/openai-chat/passthrough.ts +146 -0
- package/src/adapters/openai-chat/response-events.ts +117 -0
- package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
- package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
- package/src/adapters/openai-chat/tool-schema.ts +495 -0
- package/src/adapters/openai-chat/wire.ts +50 -0
- package/src/adapters/openai-chat.ts +40 -1452
- package/src/adapters/openai-responses/canonical-forward.ts +202 -0
- package/src/adapters/openai-responses/image-gen.ts +406 -0
- package/src/adapters/openai-responses/internal.ts +3 -0
- package/src/adapters/openai-responses/passthrough.ts +642 -0
- package/src/adapters/openai-responses/prompt-cache.ts +83 -0
- package/src/adapters/openai-responses/reasoning.ts +220 -0
- package/src/adapters/openai-responses/request-strips.ts +185 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
- package/src/adapters/openai-responses/tool-schema.ts +293 -0
- package/src/adapters/openai-responses/web-search.ts +156 -0
- package/src/adapters/openai-responses.ts +4 -2625
- package/src/bridge/errors.ts +58 -0
- package/src/bridge/internal.ts +174 -0
- package/src/bridge/response-json.ts +630 -0
- package/src/bridge/sse.ts +1462 -0
- package/src/bridge.ts +5 -2204
- package/src/chat/inbound.ts +12 -1
- package/src/claude/desktop-profile.ts +66 -9
- package/src/claude/outbound.ts +18 -0
- package/src/cli/account-main.ts +1 -1
- package/src/cli/capabilities.ts +2 -2
- package/src/cli/combo.ts +10 -1
- package/src/cli/index.ts +48 -5
- package/src/cli/registry.ts +2 -1
- package/src/cli/system-command.ts +4 -4
- package/src/clients/config-export.ts +7 -3
- package/src/codex/account-label.ts +14 -3
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-store.ts +184 -35
- package/src/codex/account-usability.ts +21 -0
- package/src/codex/auth-api/account-list.ts +507 -0
- package/src/codex/auth-api/http.ts +32 -0
- package/src/codex/auth-api/login-flow.ts +566 -0
- package/src/codex/auth-api/login-state.ts +64 -0
- package/src/codex/auth-api/main-account-probe.ts +331 -0
- package/src/codex/auth-api/pool-mode-gate.ts +274 -0
- package/src/codex/auth-api/pool-quota-probe.ts +512 -0
- package/src/codex/auth-api/reset-credit-service.ts +431 -0
- package/src/codex/auth-api/routes.ts +425 -0
- package/src/codex/auth-api/runtime-config.ts +48 -0
- package/src/codex/auth-api.ts +27 -3118
- package/src/codex/auth-context.ts +252 -35
- package/src/codex/catalog/aggregation.ts +80 -1
- package/src/codex/catalog/auto-review.ts +507 -0
- package/src/codex/catalog/build-entries.ts +981 -0
- package/src/codex/catalog/combo-member.ts +375 -0
- package/src/codex/catalog/derive-entry.ts +229 -0
- package/src/codex/catalog/effort.ts +0 -1
- package/src/codex/catalog/gated-native-warn.ts +63 -0
- package/src/codex/catalog/gather-capture.ts +533 -0
- package/src/codex/catalog/model-hints.ts +691 -0
- package/src/codex/catalog/model-visibility.ts +305 -0
- package/src/codex/catalog/provider-fetch.ts +52 -2942
- package/src/codex/catalog/provider-models.ts +685 -0
- package/src/codex/catalog/remote.ts +30 -0
- package/src/codex/catalog/restore.ts +132 -0
- package/src/codex/catalog/retained-sync.ts +714 -0
- package/src/codex/catalog/routed-gather.ts +895 -0
- package/src/codex/catalog/subagent-roster.ts +176 -0
- package/src/codex/catalog/sync.ts +52 -2698
- package/src/codex/cli-install-provenance.ts +7 -1
- package/src/codex/convergence.ts +7 -2
- package/src/codex/desktop-app/types.ts +11 -2
- package/src/codex/desktop-app/windows.ts +5 -5
- package/src/codex/inject/config-toml.ts +563 -0
- package/src/codex/inject/remove.ts +192 -0
- package/src/codex/inject/restore.ts +567 -0
- package/src/codex/inject/routing-classify.ts +109 -0
- package/src/codex/inject/routing-target.ts +125 -0
- package/src/codex/inject.ts +89 -1444
- package/src/codex/lineage.ts +458 -0
- package/src/codex/model-entitlements.ts +152 -15
- package/src/codex/pool-refresh-backoff.ts +161 -0
- package/src/codex/quota-rejection.ts +104 -15
- package/src/codex/routing/active-account.ts +194 -0
- package/src/codex/routing/cache-affinity.ts +70 -0
- package/src/codex/routing/cooldown-math.ts +285 -0
- package/src/codex/routing/health-store.ts +402 -0
- package/src/codex/routing/probe-lease.ts +358 -0
- package/src/codex/routing/selection.ts +780 -0
- package/src/codex/routing/thread-affinity.ts +586 -0
- package/src/codex/routing/transient-hold-dispatch.ts +141 -0
- package/src/codex/routing.ts +370 -2271
- package/src/codex/shim-fingerprint.ts +223 -0
- package/src/codex/shim-inspect.ts +175 -0
- package/src/codex/shim-probe.ts +367 -0
- package/src/codex/shim-restore-lock.ts +169 -0
- package/src/codex/shim-state-file.ts +151 -0
- package/src/codex/shim-templates.ts +265 -0
- package/src/codex/shim.ts +48 -1268
- package/src/codex/warmup.ts +1 -1
- package/src/combos/failover.ts +85 -0
- package/src/combos/request.ts +17 -10
- package/src/combos/types.ts +23 -2
- package/src/config/diagnostics.ts +705 -0
- package/src/config/feature-flags.ts +55 -0
- package/src/config/live-reconcile.ts +403 -0
- package/src/config/load-degrade.ts +880 -0
- package/src/config/mutation-lock.ts +244 -0
- package/src/config/openai-tier-backup.ts +268 -0
- package/src/config/pending-teardown.ts +31 -0
- package/src/config/persist-unlocked.ts +92 -0
- package/src/config/proxy-env.ts +188 -0
- package/src/config/salvage.ts +244 -0
- package/src/config/schema/config-schema.ts +640 -0
- package/src/config/schema/leaf-validators.ts +855 -0
- package/src/config/warn-memo.ts +28 -0
- package/src/config.ts +234 -4481
- package/src/generated/compatibility-version.json +649 -121
- package/src/images/loop.ts +1 -1
- package/src/lib/errors.ts +17 -0
- package/src/lib/request-execution-budget.ts +198 -23
- package/src/lib/spend-reservation-ledger.ts +958 -0
- package/src/lib/state-store-registrations.ts +6 -2
- package/src/lib/test-home-guard.ts +85 -1
- package/src/lib/upstream-retry.ts +132 -21
- package/src/lib/windows-elevation.ts +76 -14
- package/src/lib/workflow-budget.ts +553 -30
- package/src/oauth/index.ts +2 -2
- package/src/oauth/key-providers.ts +2 -2
- package/src/providers/kiro-models.ts +4 -3
- package/src/providers/label.ts +19 -1
- package/src/providers/model-discovery.ts +16 -0
- package/src/providers/quota/account-cache.ts +441 -0
- package/src/providers/quota/antigravity.ts +295 -0
- package/src/providers/quota/report-cache.ts +320 -0
- package/src/providers/quota/vendor-probes-key.ts +1243 -0
- package/src/providers/quota/vendor-probes-oauth.ts +590 -0
- package/src/providers/quota.ts +324 -3079
- package/src/providers/registry/entries-core.ts +1228 -0
- package/src/providers/registry/entries-extended.ts +1213 -0
- package/src/providers/registry/model-seeds.ts +912 -0
- package/src/providers/registry/types.ts +352 -0
- package/src/providers/registry.ts +24 -3536
- package/src/responses/continuation-ownership.ts +29 -0
- package/src/responses/reasoning-envelope.ts +6 -3
- package/src/responses/state/replay-fingerprint.ts +80 -0
- package/src/responses/state/snapshot-codec.ts +104 -0
- package/src/responses/state/spill-failure.ts +118 -0
- package/src/responses/state/spill-queue.ts +665 -0
- package/src/responses/state/temp-recovery.ts +257 -0
- package/src/responses/state.ts +82 -1143
- package/src/routing/identity-domains.ts +456 -0
- package/src/routing/probe-lease.ts +613 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/chat-native.ts +37 -9
- package/src/server/index/bounded-request.ts +88 -0
- package/src/server/index/live-sideband.ts +601 -0
- package/src/server/index/serve-options.ts +1766 -0
- package/src/server/index/startup-warnings.ts +213 -0
- package/src/server/index/websocket-handler.ts +339 -0
- package/src/server/index.ts +45 -2552
- package/src/server/inspection-tee.ts +107 -0
- package/src/server/live.ts +46 -1
- package/src/server/management/combo-routes.ts +10 -1
- package/src/server/management/route-registry.ts +26 -23
- package/src/server/management/shared.ts +8 -5
- package/src/server/management/workflow-budget-routes.ts +133 -0
- package/src/server/management-api.ts +12 -0
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +14 -19
- package/src/server/request-log-conversation.ts +9 -7
- package/src/server/request-log.ts +372 -4
- package/src/server/response-log-body.ts +153 -0
- package/src/server/responses/account-change-state.ts +307 -0
- package/src/server/responses/adapter-continuation.ts +540 -0
- package/src/server/responses/adapter-delivery.ts +208 -0
- package/src/server/responses/adapter-dispatch.ts +1042 -0
- package/src/server/responses/codex-ws-wire.ts +5 -0
- package/src/server/responses/collaboration.ts +74 -4
- package/src/server/responses/combo-session-recall.ts +68 -8
- package/src/server/responses/compact.ts +113 -17
- package/src/server/responses/completion-policy.ts +33 -0
- package/src/server/responses/core-auth.ts +529 -0
- package/src/server/responses/core-codex-account.ts +907 -0
- package/src/server/responses/core-combo-failure.ts +210 -0
- package/src/server/responses/core-combo.ts +787 -0
- package/src/server/responses/core-errors.ts +170 -0
- package/src/server/responses/core-lifetime.ts +95 -0
- package/src/server/responses/core-normalize.ts +350 -0
- package/src/server/responses/core-opaque-recovery.ts +380 -0
- package/src/server/responses/core-options.ts +159 -0
- package/src/server/responses/core-replay.ts +298 -0
- package/src/server/responses/core.ts +192 -8893
- package/src/server/responses/encrypted-payload.ts +0 -1
- package/src/server/responses/input-admission.ts +126 -6
- package/src/server/responses/passthrough-delivery.ts +869 -0
- package/src/server/responses/passthrough-dispatch.ts +1494 -0
- package/src/server/responses/passthrough-error.ts +38 -2
- package/src/server/responses/passthrough-execution.ts +54 -0
- package/src/server/responses/request-prepare.ts +1080 -0
- package/src/server/responses/request-send-budget.ts +259 -0
- package/src/server/responses/request-sidecar-auth.ts +149 -0
- package/src/server/responses/request-spend.ts +147 -0
- package/src/server/responses/request-transport.ts +803 -0
- package/src/server/responses/response-effects.ts +157 -0
- package/src/server/responses/run-turn-execution.ts +476 -0
- package/src/server/responses/sidecar-execution.ts +463 -0
- package/src/server/responses/terminal-guard.ts +65 -4
- package/src/server/responses-image-gen-repair.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +9 -5
- package/src/server/workflow-refusal.ts +84 -0
- package/src/service/windows-ops.ts +210 -16
- package/src/service/windows-scheduler.ts +28 -21
- package/src/service.ts +1 -1
- package/src/types/config.ts +34 -1
- package/src/types/request.ts +8 -5
- package/src/types/tools.ts +24 -0
- package/src/types.ts +2 -0
- package/src/update/index.ts +10 -0
- package/src/update/stop-contract.d.mts +1 -0
- package/src/update/stop-contract.mjs +19 -0
- package/src/update/stop-decision.d.mts +1 -1
- package/src/update/stop-decision.mjs +12 -3
- package/src/usage/log.ts +147 -1
- package/src/usage/summary.ts +171 -21
- package/src/vision/anthropic-describe.ts +1 -1
- package/src/vision/describe.ts +5 -5
- package/src/web-search/anthropic-executor.ts +1 -1
- package/src/web-search/exa-executor.ts +1 -1
- package/src/web-search/executor.ts +1 -1
- package/src/web-search/gemini-executor.ts +1 -1
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/ollama-executor.ts +1 -1
- package/src/web-search/parse.ts +67 -14
- package/src/web-search/passthrough-bridge.ts +64 -31
- package/src/web-search/xai-executor.ts +1 -1
|
@@ -25,8 +25,12 @@ import {
|
|
|
25
25
|
applyUpstreamRecoveryInit,
|
|
26
26
|
fetchWithResetRetry,
|
|
27
27
|
fetchWithTransientRetry,
|
|
28
|
+
isNonReplayableResponse,
|
|
29
|
+
isReplayRefusalCode,
|
|
30
|
+
isReplayRefusalResponse,
|
|
28
31
|
prepareSameTarget429Wait,
|
|
29
32
|
type UpstreamSendRecovery,
|
|
33
|
+
UPSTREAM_RESET_REPLAY_REFUSED_CODE,
|
|
30
34
|
} from "../lib/upstream-retry";
|
|
31
35
|
import {
|
|
32
36
|
isTranslatorBudgetExceededError,
|
|
@@ -51,7 +55,9 @@ import { linkAbortSignal } from "./responses";
|
|
|
51
55
|
import {
|
|
52
56
|
addFinalRequestLog,
|
|
53
57
|
beginRequestAttempt,
|
|
54
|
-
|
|
58
|
+
noteProviderAttemptSend,
|
|
59
|
+
recordKeyAttemptFailure,
|
|
60
|
+
recordKeyWireAttemptUsage,
|
|
55
61
|
recordFirstOutput,
|
|
56
62
|
recordAttemptCredentialSource,
|
|
57
63
|
sealRequestAttemptIdentity,
|
|
@@ -344,10 +350,12 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
344
350
|
const encoding = new Headers(init.headers).get("accept-encoding");
|
|
345
351
|
if (!headers.has("accept-encoding") && encoding) headers.set("accept-encoding", encoding);
|
|
346
352
|
if (init.signal?.aborted) throw init.signal.reason;
|
|
347
|
-
|
|
348
|
-
|
|
353
|
+
noteProviderAttemptSend(logCtx, route.providerName, activeProvider, logCtx.usageLogInputTokens, transportRecovery ?? recovery);
|
|
354
|
+
const dispatched = await ((activeProvider as OcxProviderTransport).fetch ?? execute)(request.url, applyUpstreamRecoveryInit({
|
|
349
355
|
...init, method: request.method, headers, body: request.body,
|
|
350
356
|
}, transportRecovery));
|
|
357
|
+
if (!dispatched.ok) await recordKeyAttemptFailure(logCtx, dispatched, init.signal ?? upstream.signal);
|
|
358
|
+
return dispatched;
|
|
351
359
|
},
|
|
352
360
|
}),
|
|
353
361
|
);
|
|
@@ -375,6 +383,11 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
375
383
|
let retries = 0;
|
|
376
384
|
while (
|
|
377
385
|
response.status === 429
|
|
386
|
+
// A 429 this proxy synthesized for a refused reset replay is not a provider rate
|
|
387
|
+
// limit: waiting and re-sending here is exactly the duplicate inference the refusal
|
|
388
|
+
// exists to stop. It kept the same shape under the old 502 only because 502 never
|
|
389
|
+
// matched this branch.
|
|
390
|
+
&& !isNonReplayableResponse(response)
|
|
378
391
|
&& retryPolicy
|
|
379
392
|
&& retries < retryPolicy.attempts
|
|
380
393
|
&& transientSendAvailable()
|
|
@@ -388,7 +401,9 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
388
401
|
if (upstream.signal.aborted) throw upstream.signal.reason;
|
|
389
402
|
response = await send(activeRequest, "rate-limit-429");
|
|
390
403
|
}
|
|
391
|
-
|
|
404
|
+
// Same reason as above, plus a second one: rotating here would write a cooldown against
|
|
405
|
+
// a key that rate-limited nothing, and that false signal outlives the request.
|
|
406
|
+
while (response.status === 429 && !isNonReplayableResponse(response) && hasKeyPoolFailover(activeProvider)) {
|
|
392
407
|
const rotated = rotateProviderTransportOn429(config, route.providerName, activeProvider, {
|
|
393
408
|
retryAfter: response.headers.get("retry-after"),
|
|
394
409
|
now: Date.now(),
|
|
@@ -474,6 +489,12 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
474
489
|
if (isCyberPolicyCode(upstreamCode) || classified.code === CYBER_POLICY_ERROR_CODE) {
|
|
475
490
|
classified.code = CYBER_POLICY_ERROR_CODE;
|
|
476
491
|
classified.type = cyberPolicyErrorType(upstreamType);
|
|
492
|
+
} else if (isReplayRefusalResponse(response) || isReplayRefusalCode(upstreamCode)) {
|
|
493
|
+
// 429 classifies as a rate limit and a rate limit already carries a code, so the branch
|
|
494
|
+
// below -- which only fills an EMPTY code -- could never restore this one. Without it the
|
|
495
|
+
// client is told the provider throttled the turn, when what happened is that this proxy
|
|
496
|
+
// declined to send it a second time.
|
|
497
|
+
classified.code = UPSTREAM_RESET_REPLAY_REFUSED_CODE;
|
|
477
498
|
} else if (upstreamCode === "model_not_found") {
|
|
478
499
|
classified.code = "model_not_found";
|
|
479
500
|
classified.type = "invalid_request_error";
|
|
@@ -481,7 +502,10 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
481
502
|
classified.code = upstreamCode;
|
|
482
503
|
}
|
|
483
504
|
const status = isCyberPolicyCode(classified.code) ? 400 : response.status;
|
|
484
|
-
|
|
505
|
+
// A refusal this proxy made has no wait to report. Synthesizing one here would hand the
|
|
506
|
+
// client the default two-second retry for a rate limit that never happened, which is the
|
|
507
|
+
// duplicate send the refusal exists to prevent.
|
|
508
|
+
const retryAfter = isCyberPolicyCode(classified.code) || isReplayRefusalCode(classified.code)
|
|
485
509
|
? undefined
|
|
486
510
|
: resolveClientRetryAfter({
|
|
487
511
|
status: response.status,
|
|
@@ -509,8 +533,10 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
509
533
|
stallTimeoutSec: config.stallTimeoutSec,
|
|
510
534
|
onFirstOutput: logIds ? () => recordFirstOutput(logCtx, logIds.start) : undefined,
|
|
511
535
|
onUsage: usage => {
|
|
512
|
-
logCtx
|
|
513
|
-
|
|
536
|
+
if (!recordKeyWireAttemptUsage(logCtx, usage)) {
|
|
537
|
+
logCtx.usage = usage;
|
|
538
|
+
attempt.usage = usage;
|
|
539
|
+
}
|
|
514
540
|
},
|
|
515
541
|
onTerminal: (status: number, message?: string) => {
|
|
516
542
|
terminalStatus = status;
|
|
@@ -600,8 +626,10 @@ export async function handleNativeChatCompletions(options: HandleNativeChatOptio
|
|
|
600
626
|
if (!completion) return fail(502, "upstream response contained no choices", "upstream_error");
|
|
601
627
|
const usage = usageFromChat(completion.usage);
|
|
602
628
|
if (usage) {
|
|
603
|
-
logCtx
|
|
604
|
-
|
|
629
|
+
if (!recordKeyWireAttemptUsage(logCtx, usage)) {
|
|
630
|
+
logCtx.usage = usage;
|
|
631
|
+
attempt.usage = usage;
|
|
632
|
+
}
|
|
605
633
|
}
|
|
606
634
|
if (logIds) recordFirstOutput(logCtx, logIds.start);
|
|
607
635
|
try {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertServerAuthConfig,
|
|
3
|
+
corsHeaders,
|
|
4
|
+
managementCorsHeaders,
|
|
5
|
+
isAllowedRequestOrigin,
|
|
6
|
+
isAllowedManagementOrigin,
|
|
7
|
+
isApiAuthRequired,
|
|
8
|
+
isLoopbackHostname,
|
|
9
|
+
jsonResponse,
|
|
10
|
+
admissionFields,
|
|
11
|
+
resolveApiAuth,
|
|
12
|
+
resolveResponsesApiAuth,
|
|
13
|
+
requestPolicyView,
|
|
14
|
+
type DataPlaneAdmission,
|
|
15
|
+
type RequestPolicyView,
|
|
16
|
+
safeConfigDTO,
|
|
17
|
+
setCorsOrigin,
|
|
18
|
+
withCors,
|
|
19
|
+
withManagementCors,
|
|
20
|
+
} from "../auth-cors";
|
|
21
|
+
|
|
22
|
+
// Header-safe by construction: a key id reaches a response header, so anything outside this
|
|
23
|
+
// class could inject a header break or a control character into a response we control.
|
|
24
|
+
const REMOTE_CATALOG_KEY_ID_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
25
|
+
export const GUI_PAIRING_EXCHANGE_BODY_LIMIT = 4 * 1024;
|
|
26
|
+
export const REMOTE_WORKSPACE_PAIRING_BODY_LIMIT = 32 * 1024;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read at most `limit` bytes of a request body, or refuse.
|
|
30
|
+
*
|
|
31
|
+
* Returns null the moment the body is known to exceed `limit`, without retaining the excess.
|
|
32
|
+
* `req.text()` cannot express that: it buffers to completion first, so a caller who omits
|
|
33
|
+
* Content-Length or uses chunked framing decides how much memory the process spends. That
|
|
34
|
+
* matters here because the one caller is an unauthenticated endpoint.
|
|
35
|
+
*
|
|
36
|
+
* limit+1 is the stopping point rather than limit, so a body exactly at the limit is still
|
|
37
|
+
* accepted and only a genuinely over-limit body is rejected.
|
|
38
|
+
*/
|
|
39
|
+
export async function readBoundedRequestText(req: Request, limit: number): Promise<string | null> {
|
|
40
|
+
const body = req.body;
|
|
41
|
+
if (!body) return "";
|
|
42
|
+
const reader = body.getReader();
|
|
43
|
+
const chunks: Uint8Array[] = [];
|
|
44
|
+
let total = 0;
|
|
45
|
+
try {
|
|
46
|
+
for (;;) {
|
|
47
|
+
const { done, value } = await reader.read();
|
|
48
|
+
if (done) break;
|
|
49
|
+
if (!value || value.byteLength === 0) continue;
|
|
50
|
+
total += value.byteLength;
|
|
51
|
+
if (total > limit) return null;
|
|
52
|
+
chunks.push(value);
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
// Cancel rather than only releasing the lock: on the reject path the peer may still be
|
|
56
|
+
// sending, and an uncancelled body keeps that transfer alive.
|
|
57
|
+
await reader.cancel().catch(() => {});
|
|
58
|
+
}
|
|
59
|
+
const joined = new Uint8Array(total);
|
|
60
|
+
let offset = 0;
|
|
61
|
+
for (const chunk of chunks) {
|
|
62
|
+
joined.set(chunk, offset);
|
|
63
|
+
offset += chunk.byteLength;
|
|
64
|
+
}
|
|
65
|
+
return new TextDecoder().decode(joined);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Name WHICH configured credential was admitted, so a multi-key operator can attribute a
|
|
70
|
+
* catalog read.
|
|
71
|
+
*
|
|
72
|
+
* Scoped to configured keys on purpose: an environment token or a loopback bind has no key
|
|
73
|
+
* to name, and emitting one anyway would invent an attribution that does not exist. 200 only
|
|
74
|
+
* — this route emits no validator and therefore never answers 304.
|
|
75
|
+
*
|
|
76
|
+
* An id that fails the header-safe pattern is omitted rather than sanitized, with one warning
|
|
77
|
+
* that does NOT repeat the id: logging the offending value is how a malformed id becomes a
|
|
78
|
+
* log-injection vector instead of a dropped header.
|
|
79
|
+
*/
|
|
80
|
+
export function withRemoteCatalogKeyId(response: Response, admission: DataPlaneAdmission): Response {
|
|
81
|
+
if (response.status !== 200 || admission.kind !== "configured") return response;
|
|
82
|
+
if (!REMOTE_CATALOG_KEY_ID_PATTERN.test(admission.keyId)) {
|
|
83
|
+
console.warn("[remote-catalog] configured API key id is not header-safe; omitting x-opencodex-key-id");
|
|
84
|
+
return response;
|
|
85
|
+
}
|
|
86
|
+
response.headers.set("x-opencodex-key-id", admission.keyId);
|
|
87
|
+
return response;
|
|
88
|
+
}
|