@bitkyc08/opencodex 2.12.0 → 2.13.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 +1 -1
- package/gui/dist/assets/index-BHldBl6_.js +76 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +15 -0
- package/src/adapters/google-antigravity-replay.ts +269 -5
- package/src/adapters/openai-chat.ts +25 -18
- package/src/adapters/openai-responses.ts +63 -17
- package/src/bridge.ts +11 -5
- package/src/claude/inbound.ts +24 -3
- package/src/cli/account-api.ts +2 -0
- package/src/cli/account-extended.ts +262 -1
- package/src/cli/account.ts +3 -1
- package/src/cli/dispatch.ts +532 -0
- package/src/cli/doctor.ts +26 -21
- package/src/cli/help.ts +9 -278
- package/src/cli/index.ts +45 -506
- package/src/cli/lab.ts +171 -65
- package/src/cli/registry.ts +384 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/status.ts +9 -2
- package/src/cli/v2.ts +37 -2
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-store.ts +12 -1
- package/src/codex/admission.ts +2 -1
- package/src/codex/auth-api.ts +3 -2
- package/src/codex/catalog/metadata.ts +6 -4
- package/src/codex/catalog/parsing.ts +31 -7
- package/src/codex/catalog/provider-fetch.ts +2 -1
- package/src/codex/catalog/sync.ts +65 -11
- package/src/codex/features.ts +437 -31
- package/src/codex/history-job.ts +6 -11
- package/src/codex/history-provider.ts +10 -9
- package/src/codex/inject.ts +14 -1
- package/src/codex/native-profile-startup.ts +49 -3
- package/src/codex/native-residue.ts +14 -2
- package/src/codex/paths.ts +75 -1
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/shim.ts +876 -16
- package/src/codex/sync.ts +27 -2
- package/src/config.ts +148 -10
- package/src/generated/compatibility-version.json +2572 -0
- package/src/images/loop.ts +1 -1
- package/src/lab/artifacts/sanitize.ts +3 -3
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +480 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/types.ts +23 -0
- package/src/lab/events/types.ts +2 -1
- package/src/lab/events/validate.ts +2 -1
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +454 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +2 -0
- package/src/lab/ledger/store.ts +173 -0
- package/src/lab/live/executor.ts +29 -4
- package/src/lab/live/transport.ts +3 -1
- package/src/lab/observe/from-conformance.ts +12 -57
- package/src/lab/observe/from-live.ts +9 -1
- package/src/lab/paths.ts +74 -9
- package/src/lab/projection/schema.ts +14 -1
- package/src/lab/projection/verdicts.ts +89 -53
- package/src/lab/projection/verification.ts +83 -3
- package/src/lab/query/catalog.ts +34 -5
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +11 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/subject/installation-salt.ts +24 -4
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +2 -1
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/windows-elevation.ts +8 -1
- package/src/lib/windows-user-principal.ts +62 -2
- package/src/lib/winsw.ts +8 -2
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/google-antigravity.ts +27 -0
- package/src/oauth/health.ts +12 -48
- package/src/oauth/index.ts +122 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/nous.ts +749 -0
- package/src/oauth/store.ts +56 -3
- package/src/providers/codex-capacity.ts +1 -0
- package/src/providers/registry.ts +161 -16
- package/src/responses/custom-tool-compat.ts +182 -0
- package/src/responses/parser.ts +22 -0
- package/src/responses/reasoning-replay-cache.ts +168 -11
- package/src/responses/schema.ts +1 -1
- package/src/router.ts +5 -20
- package/src/routing/capability.ts +9 -3
- package/src/routing/compatibility/assemble.ts +159 -0
- package/src/routing/compatibility/behavior.ts +224 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/evaluator.ts +27 -32
- package/src/routing/profile.ts +118 -4
- package/src/routing/trace.ts +82 -28
- package/src/server/auth-cors.ts +7 -0
- package/src/server/background-lifecycle.ts +20 -7
- package/src/server/chat-completions.ts +2 -2
- package/src/server/claude-messages.ts +6 -3
- package/src/server/direct-local-http.ts +343 -0
- package/src/server/index.ts +80 -18
- package/src/server/lifecycle.ts +12 -4
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/management/agent-settings-routes.ts +27 -3
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +13 -0
- package/src/server/management/logs-usage-routes.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +57 -1
- package/src/server/management/provider-routes.ts +16 -0
- package/src/server/management/routing-profile-routes.ts +12 -24
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +68 -3
- package/src/server/proxy-liveness.ts +3 -2
- package/src/server/request-decompress.ts +9 -3
- package/src/server/request-log.ts +53 -4
- package/src/server/responses/collaboration.ts +93 -17
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +202 -15
- package/src/server/responses/policy-fallback.ts +1 -0
- package/src/server/responses-custom-tool-repair.ts +280 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/startup-action-control.ts +8 -1
- package/src/service.ts +278 -12
- package/src/types.ts +49 -3
- package/src/usage/expected-prices.ts +30 -0
- package/src/usage/log.ts +30 -4
- package/src/usage/summary.ts +153 -2
- package/src/web-search/loop.ts +1 -1
- package/gui/dist/assets/index-Cw1_mxO8.js +0 -70
- package/src/cli/internal-dispatch.ts +0 -20
package/src/routing/profile.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
import { codexAccountNamespaceEntries } from "../codex/account-namespaces";
|
|
15
15
|
import { listComboIds, resolveComboId } from "../combos";
|
|
16
16
|
import { hasOwnProvider } from "../config";
|
|
17
|
+
import { MAX_COMPATIBILITY_REQUIRED_SUITES } from "./compatibility/types";
|
|
17
18
|
import { POLICY_NAMESPACE } from "./profile-namespace";
|
|
18
19
|
|
|
19
20
|
export { POLICY_NAMESPACE };
|
|
@@ -36,6 +37,17 @@ export const DEFAULT_UNKNOWN_EVIDENCE: Record<"capability" | "health" | "quota"
|
|
|
36
37
|
cost: "penalize",
|
|
37
38
|
};
|
|
38
39
|
|
|
40
|
+
export const DEFAULT_DEGRADED_EVIDENCE: OcxRoutingUnknownEvidenceMode = "penalize";
|
|
41
|
+
export const DEFAULT_COMPATIBILITY_UNKNOWN_EVIDENCE: OcxRoutingUnknownEvidenceMode = "exclude";
|
|
42
|
+
|
|
43
|
+
export interface NormalizedRoutingProfileCompatibility {
|
|
44
|
+
requiredSuites: Array<{ suiteId: string; evidenceLayer: "protocol_conformance" | "live_route_compatibility" }>;
|
|
45
|
+
minStatus?: "PROBED" | "VERIFIED";
|
|
46
|
+
maxEvidenceAgeMs?: number;
|
|
47
|
+
unknownEvidence: OcxRoutingUnknownEvidenceMode;
|
|
48
|
+
degradedEvidence: OcxRoutingUnknownEvidenceMode;
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
export interface RoutingProfileValidationIssue {
|
|
40
52
|
path: Array<string | number>;
|
|
41
53
|
message: string;
|
|
@@ -63,6 +75,7 @@ export interface NormalizedRoutingProfile {
|
|
|
63
75
|
optimize: { latency: number; health: number; cost: number; quota: number };
|
|
64
76
|
limits: { maxEstimatedCostUsd?: number; onUnknownCost?: OcxRoutingUnknownCostCapMode };
|
|
65
77
|
unknownEvidence: Record<"capability" | "health" | "quota" | "cost", OcxRoutingUnknownEvidenceMode>;
|
|
78
|
+
compatibility?: NormalizedRoutingProfileCompatibility;
|
|
66
79
|
revision: string;
|
|
67
80
|
}
|
|
68
81
|
|
|
@@ -80,6 +93,7 @@ const REQUIRE_KEYS = [
|
|
|
80
93
|
] as const;
|
|
81
94
|
|
|
82
95
|
const UNKNOWN_EVIDENCE_KEYS = ["capability", "health", "quota", "cost"] as const;
|
|
96
|
+
const EVIDENCE_LAYERS = new Set(["protocol_conformance", "live_route_compatibility"]);
|
|
83
97
|
|
|
84
98
|
export function isValidPolicyId(id: string): boolean {
|
|
85
99
|
return POLICY_ID_PATTERN.test(id);
|
|
@@ -153,8 +167,6 @@ function aliasIssues(
|
|
|
153
167
|
message: "bare aliases in the OpenAI native family (gpt-*, o1-*, o3-*, o4-*, codex-*) are not allowed",
|
|
154
168
|
});
|
|
155
169
|
}
|
|
156
|
-
// Cross-namespace collisions: providers, combos, account namespaces, and
|
|
157
|
-
// sibling profile aliases all own public model ids that must stay unique.
|
|
158
170
|
if (hasOwnProvider(config.providers, alias)) {
|
|
159
171
|
issues.push({ path: ["alias"], message: `alias "${alias}" collides with configured provider name "${alias}"` });
|
|
160
172
|
}
|
|
@@ -342,6 +354,72 @@ export function routingProfileIssues(
|
|
|
342
354
|
}
|
|
343
355
|
}
|
|
344
356
|
|
|
357
|
+
if (body.compatibility !== undefined) {
|
|
358
|
+
if (!body.compatibility || typeof body.compatibility !== "object" || Array.isArray(body.compatibility)) {
|
|
359
|
+
issues.push({ path: ["compatibility"], message: "compatibility must be an object" });
|
|
360
|
+
} else {
|
|
361
|
+
const compatibility = body.compatibility as Record<string, unknown>;
|
|
362
|
+
if (compatibility.requiredSuites !== undefined) {
|
|
363
|
+
if (!Array.isArray(compatibility.requiredSuites)) {
|
|
364
|
+
issues.push({ path: ["compatibility", "requiredSuites"], message: "requiredSuites must be an array" });
|
|
365
|
+
} else {
|
|
366
|
+
if (compatibility.requiredSuites.length > MAX_COMPATIBILITY_REQUIRED_SUITES) {
|
|
367
|
+
issues.push({
|
|
368
|
+
path: ["compatibility", "requiredSuites"],
|
|
369
|
+
message: `requiredSuites must contain at most ${MAX_COMPATIBILITY_REQUIRED_SUITES} entries`,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const seen = new Set<string>();
|
|
373
|
+
compatibility.requiredSuites.forEach((rawSuite, index) => {
|
|
374
|
+
if (!rawSuite || typeof rawSuite !== "object" || Array.isArray(rawSuite)) {
|
|
375
|
+
issues.push({ path: ["compatibility", "requiredSuites", index], message: "suite entry must be an object" });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const suite = rawSuite as Record<string, unknown>;
|
|
379
|
+
const suiteId = typeof suite.suiteId === "string" ? suite.suiteId.trim() : "";
|
|
380
|
+
const layer = suite.evidenceLayer;
|
|
381
|
+
if (!suiteId) {
|
|
382
|
+
issues.push({ path: ["compatibility", "requiredSuites", index, "suiteId"], message: "suiteId is required" });
|
|
383
|
+
}
|
|
384
|
+
if (layer !== "protocol_conformance" && layer !== "live_route_compatibility") {
|
|
385
|
+
issues.push({
|
|
386
|
+
path: ["compatibility", "requiredSuites", index, "evidenceLayer"],
|
|
387
|
+
message: "evidenceLayer must be protocol_conformance or live_route_compatibility",
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (suiteId && typeof layer === "string" && EVIDENCE_LAYERS.has(layer)) {
|
|
391
|
+
const key = `${layer}:${suiteId}`;
|
|
392
|
+
if (seen.has(key)) {
|
|
393
|
+
issues.push({ path: ["compatibility", "requiredSuites", index], message: `duplicate required suite "${key}"` });
|
|
394
|
+
} else {
|
|
395
|
+
seen.add(key);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (compatibility.minStatus !== undefined
|
|
402
|
+
&& compatibility.minStatus !== "PROBED"
|
|
403
|
+
&& compatibility.minStatus !== "VERIFIED") {
|
|
404
|
+
issues.push({ path: ["compatibility", "minStatus"], message: 'minStatus must be "PROBED" or "VERIFIED"' });
|
|
405
|
+
}
|
|
406
|
+
if (compatibility.maxEvidenceAgeMs !== undefined
|
|
407
|
+
&& (typeof compatibility.maxEvidenceAgeMs !== "number"
|
|
408
|
+
|| !Number.isInteger(compatibility.maxEvidenceAgeMs)
|
|
409
|
+
|| compatibility.maxEvidenceAgeMs < 0)) {
|
|
410
|
+
issues.push({ path: ["compatibility", "maxEvidenceAgeMs"], message: "maxEvidenceAgeMs must be a non-negative integer" });
|
|
411
|
+
}
|
|
412
|
+
for (const key of ["unknownEvidence", "degradedEvidence"] as const) {
|
|
413
|
+
if (compatibility[key] !== undefined
|
|
414
|
+
&& compatibility[key] !== "allow"
|
|
415
|
+
&& compatibility[key] !== "penalize"
|
|
416
|
+
&& compatibility[key] !== "exclude") {
|
|
417
|
+
issues.push({ path: ["compatibility", key], message: `${key} must be "allow", "penalize", or "exclude"` });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
345
423
|
return issues;
|
|
346
424
|
}
|
|
347
425
|
|
|
@@ -372,6 +450,42 @@ function normalizedUnknownEvidence(raw: OcxRoutingProfileConfig): NormalizedRout
|
|
|
372
450
|
return out;
|
|
373
451
|
}
|
|
374
452
|
|
|
453
|
+
function normalizedCompatibility(
|
|
454
|
+
raw: OcxRoutingProfileConfig,
|
|
455
|
+
): NormalizedRoutingProfileCompatibility | undefined {
|
|
456
|
+
if (raw.compatibility === undefined) return undefined;
|
|
457
|
+
const compatibility = raw.compatibility;
|
|
458
|
+
const requiredSuites = (compatibility.requiredSuites ?? []).map(suite => ({
|
|
459
|
+
suiteId: suite.suiteId.trim(),
|
|
460
|
+
evidenceLayer: suite.evidenceLayer,
|
|
461
|
+
}));
|
|
462
|
+
const unknownEvidence = compatibility.unknownEvidence === "allow"
|
|
463
|
+
|| compatibility.unknownEvidence === "penalize"
|
|
464
|
+
|| compatibility.unknownEvidence === "exclude"
|
|
465
|
+
? compatibility.unknownEvidence
|
|
466
|
+
: DEFAULT_COMPATIBILITY_UNKNOWN_EVIDENCE;
|
|
467
|
+
const degradedEvidence = compatibility.degradedEvidence === "allow"
|
|
468
|
+
|| compatibility.degradedEvidence === "penalize"
|
|
469
|
+
|| compatibility.degradedEvidence === "exclude"
|
|
470
|
+
? compatibility.degradedEvidence
|
|
471
|
+
: DEFAULT_DEGRADED_EVIDENCE;
|
|
472
|
+
|
|
473
|
+
const hasControls = requiredSuites.length > 0
|
|
474
|
+
|| compatibility.minStatus !== undefined
|
|
475
|
+
|| compatibility.maxEvidenceAgeMs !== undefined
|
|
476
|
+
|| compatibility.unknownEvidence !== undefined
|
|
477
|
+
|| compatibility.degradedEvidence !== undefined;
|
|
478
|
+
if (!hasControls) return undefined;
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
requiredSuites,
|
|
482
|
+
...(compatibility.minStatus ? { minStatus: compatibility.minStatus } : {}),
|
|
483
|
+
...(compatibility.maxEvidenceAgeMs !== undefined ? { maxEvidenceAgeMs: compatibility.maxEvidenceAgeMs } : {}),
|
|
484
|
+
unknownEvidence,
|
|
485
|
+
degradedEvidence,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
375
489
|
function canonicalJson(value: unknown): string {
|
|
376
490
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
377
491
|
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
@@ -390,6 +504,7 @@ export function normalizeRoutingProfile(id: string, raw: OcxRoutingProfileConfig
|
|
|
390
504
|
const weights = { ...DEFAULT_PROFILE_WEIGHTS, ...(raw.optimize ?? {}) };
|
|
391
505
|
const weightSum = weights.latency + weights.health + weights.cost + weights.quota;
|
|
392
506
|
const safeSum = weightSum > 0 ? weightSum : 1;
|
|
507
|
+
const compatibility = normalizedCompatibility(raw);
|
|
393
508
|
const profile: Omit<NormalizedRoutingProfile, "revision"> = {
|
|
394
509
|
id,
|
|
395
510
|
alias: alias || null,
|
|
@@ -413,6 +528,7 @@ export function normalizeRoutingProfile(id: string, raw: OcxRoutingProfileConfig
|
|
|
413
528
|
: {}),
|
|
414
529
|
},
|
|
415
530
|
unknownEvidence: normalizedUnknownEvidence(raw),
|
|
531
|
+
...(compatibility ? { compatibility } : {}),
|
|
416
532
|
};
|
|
417
533
|
return { ...profile, revision: profileRevision(profile) };
|
|
418
534
|
}
|
|
@@ -427,7 +543,5 @@ export function getRoutingProfile(
|
|
|
427
543
|
}
|
|
428
544
|
|
|
429
545
|
export function listRoutingProfileIds(config: { routingProfiles?: Record<string, OcxRoutingProfileConfig> }): string[] {
|
|
430
|
-
// Code-unit comparison: deterministic across ICU versions/platforms, which
|
|
431
|
-
// matters for the API/CLI list contract (ids may contain ".", "_", "-").
|
|
432
546
|
return Object.keys(config.routingProfiles ?? {}).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
433
547
|
}
|
package/src/routing/trace.ts
CHANGED
|
@@ -102,6 +102,27 @@ export interface RouteCostEvidence {
|
|
|
102
102
|
capOutcome?: RouteCostCapOutcome;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
export interface RouteCompatibilitySuiteTrace {
|
|
106
|
+
suiteId: string;
|
|
107
|
+
evidenceLayer: string;
|
|
108
|
+
/** Privacy-safe prefix of the exact subject used for this layer/suite. */
|
|
109
|
+
subjectIdPrefix?: string;
|
|
110
|
+
verdict?: string;
|
|
111
|
+
minStatus?: string;
|
|
112
|
+
fresh?: boolean;
|
|
113
|
+
unknownPolicy?: string;
|
|
114
|
+
degradedPolicy?: string;
|
|
115
|
+
outcome: "satisfied" | "penalized" | "excluded" | "unknown";
|
|
116
|
+
reason?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface RouteCompatibilityEvidence {
|
|
120
|
+
/** Legacy single-subject prefix retained for persisted trace compatibility. */
|
|
121
|
+
subjectIdPrefix?: string;
|
|
122
|
+
suites: RouteCompatibilitySuiteTrace[];
|
|
123
|
+
truncated?: true;
|
|
124
|
+
}
|
|
125
|
+
|
|
105
126
|
export interface RouteScoreEvidence {
|
|
106
127
|
total: number;
|
|
107
128
|
components: {
|
|
@@ -111,6 +132,7 @@ export interface RouteScoreEvidence {
|
|
|
111
132
|
cost?: number;
|
|
112
133
|
latency?: number;
|
|
113
134
|
configuredPriority?: number;
|
|
135
|
+
compatibility?: number;
|
|
114
136
|
};
|
|
115
137
|
}
|
|
116
138
|
|
|
@@ -124,6 +146,7 @@ export interface RouteCandidateTrace {
|
|
|
124
146
|
health?: RouteHealthEvidence;
|
|
125
147
|
quota?: RouteQuotaEvidence;
|
|
126
148
|
cost?: RouteCostEvidence;
|
|
149
|
+
compatibility?: RouteCompatibilityEvidence;
|
|
127
150
|
score?: RouteScoreEvidence;
|
|
128
151
|
}
|
|
129
152
|
|
|
@@ -149,6 +172,7 @@ export interface RouteDecisionTraceV1 {
|
|
|
149
172
|
exclusions?: true;
|
|
150
173
|
requirements?: true;
|
|
151
174
|
strings?: true;
|
|
175
|
+
compatibility?: true;
|
|
152
176
|
};
|
|
153
177
|
}
|
|
154
178
|
|
|
@@ -202,6 +226,7 @@ export interface TraceCandidateInput {
|
|
|
202
226
|
health?: RouteHealthEvidence;
|
|
203
227
|
quota?: RouteQuotaEvidence;
|
|
204
228
|
cost?: RouteCostEvidence;
|
|
229
|
+
compatibility?: RouteCompatibilityEvidence;
|
|
205
230
|
}
|
|
206
231
|
|
|
207
232
|
export interface TraceBuildInput {
|
|
@@ -222,18 +247,23 @@ export interface TraceBuildInput {
|
|
|
222
247
|
now?: number;
|
|
223
248
|
}
|
|
224
249
|
|
|
250
|
+
interface ParseCaps {
|
|
251
|
+
candidates?: true;
|
|
252
|
+
exclusions?: true;
|
|
253
|
+
requirements?: true;
|
|
254
|
+
strings?: true;
|
|
255
|
+
compatibility?: true;
|
|
256
|
+
}
|
|
257
|
+
|
|
225
258
|
/** Bounded candidate copy: strings capped, exclusions sliced, score/evidence kept. */
|
|
226
|
-
function buildCandidate(input: TraceCandidateInput, budget:
|
|
259
|
+
function buildCandidate(input: TraceCandidateInput, budget: ParseCaps): RouteCandidateTrace {
|
|
227
260
|
const exclusions = input.exclusions.slice(0, MAX_EXCLUSIONS_PER_CANDIDATE);
|
|
228
261
|
if (exclusions.length < input.exclusions.length) budget.exclusions = true;
|
|
229
|
-
// Evidence reaches the builder from internal producers (bounded) or from
|
|
230
|
-
// caller-supplied dry-run input (unbounded). Whitelist + bound it through
|
|
231
|
-
// the same parsers the persisted-row normalizer uses so no unknown nested
|
|
232
|
-
// field or oversized string survives into the trace.
|
|
233
262
|
const capability = input.capability ? parseCapability(input.capability, budget) : undefined;
|
|
234
263
|
const health = input.health ? parseHealth(input.health) : undefined;
|
|
235
264
|
const quota = input.quota ? parseQuota(input.quota, budget) : undefined;
|
|
236
265
|
const cost = input.cost ? parseCost(input.cost, budget) : undefined;
|
|
266
|
+
const compatibility = input.compatibility ? parseCompatibility(input.compatibility, budget) : undefined;
|
|
237
267
|
return {
|
|
238
268
|
provider: capString(input.provider, budget),
|
|
239
269
|
model: capString(input.model, budget),
|
|
@@ -252,6 +282,7 @@ function buildCandidate(input: TraceCandidateInput, budget: { strings?: true; ex
|
|
|
252
282
|
...(health ? { health } : {}),
|
|
253
283
|
...(quota ? { quota } : {}),
|
|
254
284
|
...(cost ? { cost } : {}),
|
|
285
|
+
...(compatibility ? { compatibility } : {}),
|
|
255
286
|
};
|
|
256
287
|
}
|
|
257
288
|
|
|
@@ -278,7 +309,7 @@ function buildRequirement(requirement: RouteRequirementEvidence, budget: { strin
|
|
|
278
309
|
* pass provider/model NAME strings and opaque account references only.
|
|
279
310
|
*/
|
|
280
311
|
export function buildRouteDecisionTrace(input: TraceBuildInput): RouteDecisionTraceV1 {
|
|
281
|
-
const budget:
|
|
312
|
+
const budget: ParseCaps = {};
|
|
282
313
|
const now = input.now ?? Date.now();
|
|
283
314
|
const truncated: RouteDecisionTraceV1["truncated"] = {};
|
|
284
315
|
let selectedIndex = Number.isInteger(input.selected.candidateIndex ?? 0)
|
|
@@ -287,8 +318,6 @@ export function buildRouteDecisionTrace(input: TraceBuildInput): RouteDecisionTr
|
|
|
287
318
|
|
|
288
319
|
let candidates = (input.candidates ?? []).map(candidate => buildCandidate(candidate, budget));
|
|
289
320
|
if (candidates.length > MAX_TRACE_CANDIDATES) {
|
|
290
|
-
// Keep the selected candidate even when it sits beyond the slice: a trace
|
|
291
|
-
// whose selected candidate vanished would contradict the decision.
|
|
292
321
|
candidates = selectedIndex < MAX_TRACE_CANDIDATES
|
|
293
322
|
? candidates.slice(0, MAX_TRACE_CANDIDATES)
|
|
294
323
|
: [...candidates.slice(0, MAX_TRACE_CANDIDATES - 1), candidates[selectedIndex]!];
|
|
@@ -296,7 +325,6 @@ export function buildRouteDecisionTrace(input: TraceBuildInput): RouteDecisionTr
|
|
|
296
325
|
truncated.candidates = true;
|
|
297
326
|
}
|
|
298
327
|
if (candidates.length === 0) {
|
|
299
|
-
// Invariant: every decision names at least the selected route as a candidate.
|
|
300
328
|
candidates = [{
|
|
301
329
|
provider: capString(input.selected.provider, budget),
|
|
302
330
|
model: capString(input.selected.model, budget),
|
|
@@ -349,6 +377,7 @@ export function buildRouteDecisionTrace(input: TraceBuildInput): RouteDecisionTr
|
|
|
349
377
|
if (budget.strings) truncated.strings = true;
|
|
350
378
|
if (budget.exclusions) truncated.exclusions = true;
|
|
351
379
|
if (budget.candidates) truncated.candidates = true;
|
|
380
|
+
if (budget.compatibility) truncated.compatibility = true;
|
|
352
381
|
if (Object.keys(truncated).length > 0) trace.truncated = truncated;
|
|
353
382
|
|
|
354
383
|
return enforceByteBudget(trace);
|
|
@@ -369,9 +398,6 @@ function enforceByteBudget(trace: RouteDecisionTraceV1): RouteDecisionTraceV1 {
|
|
|
369
398
|
}));
|
|
370
399
|
const slimmed: RouteDecisionTraceV1 = { ...trace, truncated, candidates };
|
|
371
400
|
if (serializedByteLength(slimmed) <= MAX_TRACE_BYTES) return slimmed;
|
|
372
|
-
// Second stage: shrink candidates. The selected candidate must survive and
|
|
373
|
-
// `selected.candidateIndex` must keep pointing at it (same invariant as the
|
|
374
|
-
// candidate-cap branch above).
|
|
375
401
|
const half = Math.max(1, Math.floor(MAX_TRACE_CANDIDATES / 2));
|
|
376
402
|
const selectedIndex = trace.selected.candidateIndex;
|
|
377
403
|
const kept = selectedIndex < half
|
|
@@ -383,10 +409,6 @@ function enforceByteBudget(trace: RouteDecisionTraceV1): RouteDecisionTraceV1 {
|
|
|
383
409
|
candidates: kept,
|
|
384
410
|
selected: { ...slimmed.selected, candidateIndex: Math.min(selectedIndex, kept.length - 1) },
|
|
385
411
|
};
|
|
386
|
-
// Last resort: keep shrinking deterministically until the byte budget holds.
|
|
387
|
-
// Each stage reduces a bounded dimension, and a single candidate with no
|
|
388
|
-
// exclusions is provably below MAX_TRACE_BYTES given the string cap, so the
|
|
389
|
-
// loop terminates.
|
|
390
412
|
for (let stage = 0; stage < 4 && serializedByteLength(result) > MAX_TRACE_BYTES; stage++) {
|
|
391
413
|
if (stage === 0) {
|
|
392
414
|
result = {
|
|
@@ -420,16 +442,6 @@ function enforceByteBudget(trace: RouteDecisionTraceV1): RouteDecisionTraceV1 {
|
|
|
420
442
|
return result;
|
|
421
443
|
}
|
|
422
444
|
|
|
423
|
-
// ---- defensive parsing of persisted rows --------------------------------------
|
|
424
|
-
|
|
425
|
-
/** Caps applied by the normalizer; unioned into `truncated` on the result. */
|
|
426
|
-
interface ParseCaps {
|
|
427
|
-
candidates?: true;
|
|
428
|
-
exclusions?: true;
|
|
429
|
-
requirements?: true;
|
|
430
|
-
strings?: true;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
445
|
/** Defensive parse of one persisted exclusion reason. */
|
|
434
446
|
function parseExclusion(raw: unknown, caps: ParseCaps): RouteExclusionReason | null {
|
|
435
447
|
if (!isPlainRecord(raw)) return null;
|
|
@@ -571,13 +583,52 @@ function parseCost(raw: unknown, caps: ParseCaps): RouteCostEvidence | undefined
|
|
|
571
583
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
572
584
|
}
|
|
573
585
|
|
|
586
|
+
const MAX_COMPATIBILITY_SUITES = 8;
|
|
587
|
+
const COMPATIBILITY_OUTCOMES = new Set(["satisfied", "penalized", "excluded", "unknown"]);
|
|
588
|
+
|
|
589
|
+
function parseCompatibility(raw: unknown, caps: ParseCaps): RouteCompatibilityEvidence | undefined {
|
|
590
|
+
if (!isPlainRecord(raw)) return undefined;
|
|
591
|
+
const suitesRaw = Array.isArray(raw.suites) ? raw.suites : [];
|
|
592
|
+
if (suitesRaw.length > MAX_COMPATIBILITY_SUITES || raw.truncated === true) caps.compatibility = true;
|
|
593
|
+
const suites: RouteCompatibilitySuiteTrace[] = [];
|
|
594
|
+
for (const entry of suitesRaw.slice(0, MAX_COMPATIBILITY_SUITES)) {
|
|
595
|
+
if (!isPlainRecord(entry)) continue;
|
|
596
|
+
const suiteId = entry.suiteId;
|
|
597
|
+
const evidenceLayer = entry.evidenceLayer;
|
|
598
|
+
const outcome = entry.outcome;
|
|
599
|
+
if (typeof suiteId !== "string" || typeof evidenceLayer !== "string") continue;
|
|
600
|
+
if (typeof outcome !== "string" || !COMPATIBILITY_OUTCOMES.has(outcome)) continue;
|
|
601
|
+
const row: RouteCompatibilitySuiteTrace = {
|
|
602
|
+
suiteId: capString(suiteId, caps),
|
|
603
|
+
evidenceLayer: capString(evidenceLayer, caps),
|
|
604
|
+
outcome: outcome as RouteCompatibilitySuiteTrace["outcome"],
|
|
605
|
+
};
|
|
606
|
+
if (typeof entry.subjectIdPrefix === "string" && entry.subjectIdPrefix) {
|
|
607
|
+
row.subjectIdPrefix = capString(entry.subjectIdPrefix, caps);
|
|
608
|
+
}
|
|
609
|
+
for (const key of ["verdict", "minStatus", "reason", "unknownPolicy", "degradedPolicy"] as const) {
|
|
610
|
+
if (typeof entry[key] === "string") row[key] = capString(entry[key] as string, caps);
|
|
611
|
+
}
|
|
612
|
+
if (typeof entry.fresh === "boolean") row.fresh = entry.fresh;
|
|
613
|
+
suites.push(row);
|
|
614
|
+
}
|
|
615
|
+
const out: RouteCompatibilityEvidence = {
|
|
616
|
+
suites,
|
|
617
|
+
...(raw.truncated === true ? { truncated: true as const } : {}),
|
|
618
|
+
};
|
|
619
|
+
if (typeof raw.subjectIdPrefix === "string" && raw.subjectIdPrefix) {
|
|
620
|
+
out.subjectIdPrefix = capString(raw.subjectIdPrefix, caps);
|
|
621
|
+
}
|
|
622
|
+
return suites.length > 0 || out.subjectIdPrefix || out.truncated ? out : undefined;
|
|
623
|
+
}
|
|
624
|
+
|
|
574
625
|
/** Whitelisted score parse; requires a finite `total` and bounded components. */
|
|
575
626
|
function parseScore(raw: unknown): RouteScoreEvidence | undefined {
|
|
576
627
|
if (!isPlainRecord(raw)) return undefined;
|
|
577
628
|
if (!finiteNumber(raw.total)) return undefined;
|
|
578
629
|
const components = isPlainRecord(raw.components) ? raw.components : {};
|
|
579
630
|
const parsedComponents: RouteScoreEvidence["components"] = {};
|
|
580
|
-
for (const key of ["capability", "health", "quota", "cost", "latency", "configuredPriority"] as const) {
|
|
631
|
+
for (const key of ["capability", "health", "quota", "cost", "latency", "configuredPriority", "compatibility"] as const) {
|
|
581
632
|
if (finiteNumber(components[key])) parsedComponents[key] = components[key];
|
|
582
633
|
}
|
|
583
634
|
return { total: raw.total, components: parsedComponents };
|
|
@@ -604,6 +655,7 @@ function parseCandidate(raw: unknown, caps: ParseCaps): RouteCandidateTrace | nu
|
|
|
604
655
|
const health = parseHealth(raw.health);
|
|
605
656
|
const quota = parseQuota(raw.quota, caps);
|
|
606
657
|
const cost = parseCost(raw.cost, caps);
|
|
658
|
+
const compatibility = parseCompatibility(raw.compatibility, caps);
|
|
607
659
|
const score = parseScore(raw.score);
|
|
608
660
|
return {
|
|
609
661
|
provider: provider.slice(0, MAX_TRACE_STRING),
|
|
@@ -617,6 +669,7 @@ function parseCandidate(raw: unknown, caps: ParseCaps): RouteCandidateTrace | nu
|
|
|
617
669
|
...(health ? { health } : {}),
|
|
618
670
|
...(quota ? { quota } : {}),
|
|
619
671
|
...(cost ? { cost } : {}),
|
|
672
|
+
...(compatibility ? { compatibility } : {}),
|
|
620
673
|
...(score ? { score } : {}),
|
|
621
674
|
};
|
|
622
675
|
}
|
|
@@ -695,6 +748,7 @@ export function normalizeRouteDecisionTrace(raw: unknown): RouteDecisionTraceV1
|
|
|
695
748
|
if (incoming.exclusions === true || caps.exclusions) truncated.exclusions = true;
|
|
696
749
|
if (incoming.requirements === true || caps.requirements) truncated.requirements = true;
|
|
697
750
|
if (incoming.strings === true || caps.strings) truncated.strings = true;
|
|
751
|
+
if (incoming.compatibility === true || caps.compatibility) truncated.compatibility = true;
|
|
698
752
|
|
|
699
753
|
return {
|
|
700
754
|
version: 1,
|
|
@@ -717,6 +771,6 @@ export function normalizeRouteDecisionTrace(raw: unknown): RouteDecisionTraceV1
|
|
|
717
771
|
? { tieBreak: selected.tieBreak.slice(0, MAX_TRACE_STRING) }
|
|
718
772
|
: {}),
|
|
719
773
|
},
|
|
720
|
-
...(
|
|
774
|
+
...(Object.keys(truncated).length > 0 ? { truncated } : {}),
|
|
721
775
|
};
|
|
722
776
|
}
|
package/src/server/auth-cors.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
modelAdapterRecordConfigError,
|
|
7
7
|
modelPreferHostedToolsConfigError,
|
|
8
8
|
codexAutoStartEnabled,
|
|
9
|
+
nonBlankStringArrayConfigError,
|
|
9
10
|
positiveIntegerConfigError,
|
|
10
11
|
positiveIntegerRecordConfigError,
|
|
11
12
|
providerBaseUrlConfigError,
|
|
@@ -509,6 +510,11 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
509
510
|
if (defaultMaxOutputError) return `provider ${name} ${defaultMaxOutputError}`;
|
|
510
511
|
const maxOutputError = positiveIntegerRecordConfigError(raw.modelMaxOutputTokens, "modelMaxOutputTokens");
|
|
511
512
|
if (maxOutputError) return `provider ${name} ${maxOutputError}`;
|
|
513
|
+
const structuredOutputOptOutError = nonBlankStringArrayConfigError(
|
|
514
|
+
raw.noStructuredOutputModels,
|
|
515
|
+
"noStructuredOutputModels",
|
|
516
|
+
);
|
|
517
|
+
if (structuredOutputOptOutError) return `provider ${name} ${structuredOutputOptOutError}`;
|
|
512
518
|
const openRouterError = openRouterRoutingConfigError(typed);
|
|
513
519
|
if (openRouterError) return `provider ${name} ${openRouterError}`;
|
|
514
520
|
if (typed.authMode === "local") {
|
|
@@ -589,6 +595,7 @@ export function safeConfigDTO(config: OcxConfig): unknown {
|
|
|
589
595
|
"noTemperatureModels",
|
|
590
596
|
"noTopPModels",
|
|
591
597
|
"noPenaltyModels",
|
|
598
|
+
"noStructuredOutputModels",
|
|
592
599
|
"autoToolChoiceOnlyModels",
|
|
593
600
|
"preserveReasoningContentModels",
|
|
594
601
|
"requiresReasoningPlaceholderModels",
|
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
cancelQueuedStorageWorkerSpawns,
|
|
15
15
|
drainStorageWorkers,
|
|
16
16
|
} from "../storage/worker-lifecycle";
|
|
17
|
+
import {
|
|
18
|
+
acquireServerResourceOwner,
|
|
19
|
+
type ServerResourceOwnerLease,
|
|
20
|
+
} from "../lib/server-resource-ownership";
|
|
17
21
|
import {
|
|
18
22
|
startMemoryWatchdog,
|
|
19
23
|
type MemoryWatchdog,
|
|
@@ -29,6 +33,7 @@ type ProcessLoops = {
|
|
|
29
33
|
type LeaseOwner = {
|
|
30
34
|
token: symbol;
|
|
31
35
|
applyPolicy: PolicyApply;
|
|
36
|
+
resources: ServerResourceOwnerLease;
|
|
32
37
|
};
|
|
33
38
|
|
|
34
39
|
export type ServerBackgroundLifecycleLease = {
|
|
@@ -103,6 +108,7 @@ function removeOwner(owner: LeaseOwner): boolean {
|
|
|
103
108
|
|
|
104
109
|
function releaseOwnerSynchronously(owner: LeaseOwner): "inactive" | "shared" | "last" {
|
|
105
110
|
if (!removeOwner(owner)) return "inactive";
|
|
111
|
+
owner.resources.release();
|
|
106
112
|
const nextOwner = owners.at(-1);
|
|
107
113
|
if (nextOwner) {
|
|
108
114
|
setLivePolicyOwner(nextOwner.applyPolicy);
|
|
@@ -117,7 +123,8 @@ function releaseOwnerSynchronously(owner: LeaseOwner): "inactive" | "shared" | "
|
|
|
117
123
|
*
|
|
118
124
|
* The first live server starts the loops. Later servers only add a reference and
|
|
119
125
|
* become the current policy sink; out-of-order release restores the newest
|
|
120
|
-
* remaining sink. The final release owns timer and Worker teardown.
|
|
126
|
+
* remaining sink. The final release owns timer and Worker teardown. Resources
|
|
127
|
+
* registered while this server starts are released with this exact lease.
|
|
121
128
|
*/
|
|
122
129
|
export function acquireServerBackgroundLifecycle(
|
|
123
130
|
applyPolicy: PolicyApply,
|
|
@@ -129,13 +136,19 @@ export function acquireServerBackgroundLifecycle(
|
|
|
129
136
|
const owner: LeaseOwner = {
|
|
130
137
|
token: Symbol("server-background-lifecycle"),
|
|
131
138
|
applyPolicy,
|
|
139
|
+
resources: acquireServerResourceOwner(),
|
|
132
140
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
try {
|
|
142
|
+
if (!processLoops) {
|
|
143
|
+
processLoops = startProcessLoops(applyPolicy);
|
|
144
|
+
} else {
|
|
145
|
+
setLivePolicyOwner(applyPolicy);
|
|
146
|
+
}
|
|
147
|
+
owners.push(owner);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
owner.resources.release();
|
|
150
|
+
throw error;
|
|
137
151
|
}
|
|
138
|
-
owners.push(owner);
|
|
139
152
|
|
|
140
153
|
let releaseFlight: Promise<void> | null = null;
|
|
141
154
|
return {
|
|
@@ -166,4 +179,4 @@ export function acquireServerBackgroundLifecycle(
|
|
|
166
179
|
releaseFlight = Promise.resolve();
|
|
167
180
|
},
|
|
168
181
|
};
|
|
169
|
-
}
|
|
182
|
+
}
|
|
@@ -25,7 +25,7 @@ import type { OcxConfig } from "../types";
|
|
|
25
25
|
import { readJsonRequestBody } from "./request-decompress";
|
|
26
26
|
import {
|
|
27
27
|
addFinalRequestLog,
|
|
28
|
-
|
|
28
|
+
httpStatusForRequestLogTerminal,
|
|
29
29
|
recordFirstOutput,
|
|
30
30
|
type RequestLogContext,
|
|
31
31
|
type RequestLogEntry,
|
|
@@ -216,7 +216,7 @@ async function handleChatCompletionsWithBudget(
|
|
|
216
216
|
inboundWire: "chat",
|
|
217
217
|
translatorBudget,
|
|
218
218
|
...(logIds ? { onFirstOutput: () => recordFirstOutput(logCtx, logIds.start) } : {}),
|
|
219
|
-
onNativePassthroughTerminal: status => finalizeNativeLog(
|
|
219
|
+
onNativePassthroughTerminal: status => finalizeNativeLog(httpStatusForRequestLogTerminal(status, logCtx), { terminalStatus: status, closeReason: "terminal" }),
|
|
220
220
|
onNativePassthroughCancel: () => finalizeNativeLog(499, { closeReason: "client_cancel" }),
|
|
221
221
|
});
|
|
222
222
|
|
|
@@ -31,7 +31,7 @@ import { evidenceFromBody } from "../routing/request-evidence";
|
|
|
31
31
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
32
32
|
import type { OcxConfig } from "../types";
|
|
33
33
|
import { readJsonRequestBody } from "./request-decompress";
|
|
34
|
-
import { addFinalRequestLog,
|
|
34
|
+
import { addFinalRequestLog, httpStatusForRequestLogTerminal, recordFirstOutput, type RequestLogContext, type RequestLogEntry } from "./request-log";
|
|
35
35
|
import { conversationIdFromClaudeMetadata } from "./request-log-conversation";
|
|
36
36
|
import { responseWithDeferredRequestLog } from "./relay";
|
|
37
37
|
import { handleResponses } from "./responses";
|
|
@@ -605,7 +605,10 @@ async function handleClaudeMessagesWithBudget(
|
|
|
605
605
|
return await anthropicNativePassthrough(req, config, logCtx, logIds, anthropicBody, "/v1/messages");
|
|
606
606
|
}
|
|
607
607
|
if (isRec(anthropicBody) && effortOverride) {
|
|
608
|
-
anthropicBody.output_config = {
|
|
608
|
+
anthropicBody.output_config = {
|
|
609
|
+
...(isRec(anthropicBody.output_config) ? anthropicBody.output_config : {}),
|
|
610
|
+
effort: effortOverride,
|
|
611
|
+
};
|
|
609
612
|
delete anthropicBody.thinking;
|
|
610
613
|
}
|
|
611
614
|
const translation = anthropicToResponsesTranslation(anthropicBody, config.claudeCode);
|
|
@@ -735,7 +738,7 @@ async function handleClaudeMessagesWithBudget(
|
|
|
735
738
|
stripClaudeMainAuthForNoncanonicalForward: true,
|
|
736
739
|
translatorBudget,
|
|
737
740
|
...(logIds ? { onFirstOutput: () => recordFirstOutput(logCtx, logIds.start) } : {}),
|
|
738
|
-
onNativePassthroughTerminal: status => finalizeNativeLog(
|
|
741
|
+
onNativePassthroughTerminal: status => finalizeNativeLog(httpStatusForRequestLogTerminal(status, logCtx), { terminalStatus: status, closeReason: "terminal" }),
|
|
739
742
|
onNativePassthroughCancel: () => finalizeNativeLog(499, { closeReason: "client_cancel" }),
|
|
740
743
|
});
|
|
741
744
|
const response = logIds ? responseWithDeferredRequestLog(upstream, logIds.requestId, logIds.start, logCtx) : upstream;
|