@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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Lives outside cli.ts (which dispatches argv at module top level) so tests can import it.
|
|
11
11
|
*/
|
|
12
12
|
import { loadConfig, readAlivePid, readRuntimePort, verifyPidIdentity } from "../config";
|
|
13
|
+
import { directLocalHttpFetch } from "./direct-local-http";
|
|
13
14
|
|
|
14
15
|
export interface HealthzIdentity {
|
|
15
16
|
service?: unknown;
|
|
@@ -97,7 +98,7 @@ export async function proxyIdentityAt(
|
|
|
97
98
|
opts: { hostname?: string; expectedPid?: number } = {},
|
|
98
99
|
io: LivenessIo = {},
|
|
99
100
|
): Promise<{ pid: number | null } | null> {
|
|
100
|
-
const fetchFn = io.fetchFn ??
|
|
101
|
+
const fetchFn = io.fetchFn ?? directLocalHttpFetch;
|
|
101
102
|
const sleepFn = io.sleepFn ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
|
|
102
103
|
const nowFn = io.nowFn ?? Date.now;
|
|
103
104
|
const baseTimeoutMs = io.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
@@ -307,7 +308,7 @@ export async function probeReadiness(
|
|
|
307
308
|
opts: { hostname?: string; expectedPid?: number } = {},
|
|
308
309
|
io: ReadinessProbeIo = {},
|
|
309
310
|
): Promise<ReadinessProbeResult | null> {
|
|
310
|
-
const fetchFn = io.fetchFn ??
|
|
311
|
+
const fetchFn = io.fetchFn ?? directLocalHttpFetch;
|
|
311
312
|
try {
|
|
312
313
|
const res = await fetchFn(`http://${probeHostname(opts.hostname)}:${port}/readyz`, {
|
|
313
314
|
signal: AbortSignal.timeout(io.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS),
|
|
@@ -180,12 +180,18 @@ export function decodeRequestBody(
|
|
|
180
180
|
return assertBodySizeWithinLimit(decoded, maxBytes);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Parse a bounded JSON request body, transparently decoding compressed payloads.
|
|
185
|
+
*
|
|
186
|
+
* `options.signal`, when provided, replaces `req.signal` rather than composing with
|
|
187
|
+
* it. Callers that need both (for example request disconnect plus a deadline) must
|
|
188
|
+
* merge them into one AbortSignal before calling.
|
|
189
|
+
*/
|
|
184
190
|
export async function readBoundedJsonRequestBody(
|
|
185
191
|
req: Request,
|
|
186
192
|
maxBytes: number,
|
|
187
193
|
budget?: TranslatorBudget,
|
|
188
|
-
options?: { emptyBodyFallback?: unknown },
|
|
194
|
+
options?: { emptyBodyFallback?: unknown; signal?: AbortSignal },
|
|
189
195
|
): Promise<unknown> {
|
|
190
196
|
const encoding = req.headers.get("content-encoding");
|
|
191
197
|
const declaredLength = declaredBodyLength(req);
|
|
@@ -201,7 +207,7 @@ export async function readBoundedJsonRequestBody(
|
|
|
201
207
|
: undefined;
|
|
202
208
|
let raw: Uint8Array;
|
|
203
209
|
try {
|
|
204
|
-
raw = await readRequestBodyBytesCapped(req.body, maxBytes, req.signal);
|
|
210
|
+
raw = await readRequestBodyBytesCapped(req.body, maxBytes, options?.signal ?? req.signal);
|
|
205
211
|
} finally {
|
|
206
212
|
releaseReservation?.();
|
|
207
213
|
}
|
|
@@ -2,8 +2,10 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import type { ResponsesTerminalStatus } from "../bridge";
|
|
3
3
|
import {
|
|
4
4
|
classifyError,
|
|
5
|
+
CYBER_POLICY_ERROR_CODE,
|
|
5
6
|
httpStatusFromTerminalError as httpStatusFromClassifiedTerminalError,
|
|
6
7
|
isClientClosedMessage,
|
|
8
|
+
isCyberPolicyCode,
|
|
7
9
|
} from "../lib/errors";
|
|
8
10
|
import { CODEX_CONFIG_PATH, readRootTomlString } from "../codex/paths";
|
|
9
11
|
import { readCodexCatalogPath } from "../codex/catalog";
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
isKnownAdmissionKind,
|
|
17
19
|
isKnownInboundProtocol,
|
|
18
20
|
isKnownUsageSurface,
|
|
21
|
+
isCodexUsageAccountLogLabel,
|
|
19
22
|
isValidReasoningWireValue,
|
|
20
23
|
readRecentUsageEntries,
|
|
21
24
|
usageForFinalLog,
|
|
@@ -54,6 +57,8 @@ export interface RequestLogContext {
|
|
|
54
57
|
* product: widening that enum would merge Responses and Chat Completions,
|
|
55
58
|
* since both leave it undefined. */
|
|
56
59
|
inboundProtocol?: "responses" | "chat" | "messages";
|
|
60
|
+
/** Stable non-PII Codex Pool account identity for durable usage attribution. */
|
|
61
|
+
accountLogLabel?: string;
|
|
57
62
|
requestedModel?: string;
|
|
58
63
|
/** Internal structural combo identity; omitted from RequestLogEntry/JSONL. */
|
|
59
64
|
comboId?: string;
|
|
@@ -94,6 +99,8 @@ export interface RequestLogContext {
|
|
|
94
99
|
upstreamError?: string;
|
|
95
100
|
/** HTTP status derived from a terminal `response.failed` SSE payload (429/401/503/etc.). */
|
|
96
101
|
terminalHttpStatus?: number;
|
|
102
|
+
/** Recognized structured terminal code whose exact identity must survive status mapping. */
|
|
103
|
+
terminalErrorCode?: typeof CYBER_POLICY_ERROR_CODE;
|
|
97
104
|
/** Structured reason from `response.incomplete`; internal-only input to log classification. */
|
|
98
105
|
terminalIncompleteReason?: string;
|
|
99
106
|
affinity?: "reused" | "new_bind" | "rebound" | "cleared";
|
|
@@ -121,6 +128,7 @@ export interface RequestLogEntry {
|
|
|
121
128
|
* product: widening that enum would merge Responses and Chat Completions,
|
|
122
129
|
* since both leave it undefined. */
|
|
123
130
|
inboundProtocol?: "responses" | "chat" | "messages";
|
|
131
|
+
accountLogLabel?: string;
|
|
124
132
|
/** Best-effort chat/session correlation for Logs grouping (#330). */
|
|
125
133
|
conversationId?: string;
|
|
126
134
|
requestedModel?: string;
|
|
@@ -231,6 +239,9 @@ export function requestLogEntryFromPersistedUsage(entry: PersistedUsageEntry): R
|
|
|
231
239
|
...(entry.firstOutputMs !== undefined ? { firstOutputMs: entry.firstOutputMs } : {}),
|
|
232
240
|
...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
|
|
233
241
|
...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
|
|
242
|
+
...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
|
|
243
|
+
? { accountLogLabel: entry.accountLogLabel }
|
|
244
|
+
: {}),
|
|
234
245
|
...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
|
|
235
246
|
...(entry.requestedEffort ? { requestedEffort: entry.requestedEffort } : {}),
|
|
236
247
|
...(entry.effectiveEffort ? { effectiveEffort: entry.effectiveEffort } : {}),
|
|
@@ -327,6 +338,9 @@ export function addRequestLog(entry: RequestLogEntry) {
|
|
|
327
338
|
...(entry.apiKeyId ? { apiKeyId: entry.apiKeyId } : {}),
|
|
328
339
|
...(isKnownAdmissionKind(entry.admissionKind) ? { admissionKind: entry.admissionKind } : {}),
|
|
329
340
|
...(isKnownInboundProtocol(entry.inboundProtocol) ? { inboundProtocol: entry.inboundProtocol } : {}),
|
|
341
|
+
...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
|
|
342
|
+
? { accountLogLabel: entry.accountLogLabel }
|
|
343
|
+
: {}),
|
|
330
344
|
...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
|
|
331
345
|
...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
|
|
332
346
|
...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
|
|
@@ -446,12 +460,26 @@ export function recordAdapterReasoning(
|
|
|
446
460
|
}
|
|
447
461
|
}
|
|
448
462
|
|
|
449
|
-
export function requestLogErrorCode(
|
|
463
|
+
export function requestLogErrorCode(
|
|
464
|
+
status: number,
|
|
465
|
+
upstreamError?: string,
|
|
466
|
+
terminalErrorCode?: string,
|
|
467
|
+
): string | undefined {
|
|
450
468
|
if (status >= 200 && status < 400) return undefined;
|
|
469
|
+
// A structured terminal code is authoritative even when the provider message is localized,
|
|
470
|
+
// generic, or absent. Only preserve the one narrowly recognized policy code here: broadly
|
|
471
|
+
// forwarding arbitrary upstream codes would change unrelated request-log taxonomy.
|
|
472
|
+
if (isCyberPolicyCode(terminalErrorCode)) return CYBER_POLICY_ERROR_CODE;
|
|
473
|
+
const classifiedCode = upstreamError?.trim()
|
|
474
|
+
? classifyError(status, "upstream_error", upstreamError).code
|
|
475
|
+
: undefined;
|
|
451
476
|
// Defense in depth: mid-stream web-search aborts used to land as 502 with this message.
|
|
452
|
-
if (status === 499 ||
|
|
477
|
+
if (status === 499 || classifiedCode === "client_closed_request") {
|
|
453
478
|
return "client_closed_request";
|
|
454
479
|
}
|
|
480
|
+
// Keep the high-confidence message fallback for runtimes/providers that stripped the
|
|
481
|
+
// structured code before emitting response.failed.
|
|
482
|
+
if (classifiedCode === CYBER_POLICY_ERROR_CODE) return CYBER_POLICY_ERROR_CODE;
|
|
455
483
|
if (status === 400 || status === 409) return "invalid_request_error";
|
|
456
484
|
if (status === 401) return "invalid_api_key";
|
|
457
485
|
if (status === 403) {
|
|
@@ -719,9 +747,17 @@ function captureTerminalHttpStatus(
|
|
|
719
747
|
if (json.type !== "response.failed") return;
|
|
720
748
|
const error = json.response?.error;
|
|
721
749
|
if (!error || typeof error !== "object") return;
|
|
750
|
+
const terminalCode = error.code === null || typeof error.code === "string"
|
|
751
|
+
? error.code
|
|
752
|
+
: undefined;
|
|
753
|
+
if (isCyberPolicyCode(terminalCode)) {
|
|
754
|
+
logCtx.terminalErrorCode = CYBER_POLICY_ERROR_CODE;
|
|
755
|
+
} else {
|
|
756
|
+
delete logCtx.terminalErrorCode;
|
|
757
|
+
}
|
|
722
758
|
logCtx.terminalHttpStatus = httpStatusFromTerminalError({
|
|
723
759
|
type: typeof error.type === "string" ? error.type : undefined,
|
|
724
|
-
code:
|
|
760
|
+
code: terminalCode,
|
|
725
761
|
message: typeof error.message === "string" ? error.message : undefined,
|
|
726
762
|
});
|
|
727
763
|
}
|
|
@@ -777,7 +813,11 @@ export function addFinalRequestLog(
|
|
|
777
813
|
const effectiveStatus = status >= 500 && logCtx.upstreamError && isClientClosedMessage(logCtx.upstreamError)
|
|
778
814
|
? 499
|
|
779
815
|
: status;
|
|
780
|
-
const errorCode = requestLogErrorCode(
|
|
816
|
+
const errorCode = requestLogErrorCode(
|
|
817
|
+
effectiveStatus,
|
|
818
|
+
logCtx.upstreamError,
|
|
819
|
+
logCtx.terminalErrorCode,
|
|
820
|
+
);
|
|
781
821
|
// A response.failed whose classified status is 499 is still a client cancel, not an upstream
|
|
782
822
|
// terminal failure — keep /api/logs closeReason aligned with that.
|
|
783
823
|
const closeReason = effectiveStatus === 499
|
|
@@ -790,6 +830,10 @@ export function addFinalRequestLog(
|
|
|
790
830
|
Date.now() - (logCtx.activeAttemptStartedAt ?? start),
|
|
791
831
|
logCtx.usage,
|
|
792
832
|
);
|
|
833
|
+
// The final row and its active physical attempt describe the same terminal. Preserve the
|
|
834
|
+
// semantic code on both so detailed attempt telemetry cannot regress to a generic status code.
|
|
835
|
+
if (errorCode) logCtx.activeAttempt.errorCode = errorCode;
|
|
836
|
+
else delete logCtx.activeAttempt.errorCode;
|
|
793
837
|
}
|
|
794
838
|
const existing = finalizedUsage(
|
|
795
839
|
logCtx.providerAdapter ?? logCtx.provider,
|
|
@@ -815,6 +859,9 @@ export function addFinalRequestLog(
|
|
|
815
859
|
...(logCtx.apiKeyId ? { apiKeyId: logCtx.apiKeyId } : {}),
|
|
816
860
|
...(logCtx.admissionKind ? { admissionKind: logCtx.admissionKind } : {}),
|
|
817
861
|
...(logCtx.inboundProtocol ? { inboundProtocol: logCtx.inboundProtocol } : {}),
|
|
862
|
+
...(isCodexUsageAccountLogLabel(logCtx.accountLogLabel)
|
|
863
|
+
? { accountLogLabel: logCtx.accountLogLabel }
|
|
864
|
+
: {}),
|
|
818
865
|
...(logCtx.conversationId ? { conversationId: logCtx.conversationId } : {}),
|
|
819
866
|
...(logCtx.requestedModel ? { requestedModel: logCtx.requestedModel } : {}),
|
|
820
867
|
...(logCtx.requestedEffort ? { requestedEffort: logCtx.requestedEffort } : {}),
|
|
@@ -965,10 +1012,12 @@ export function sealRequestAttemptIdentity(
|
|
|
965
1012
|
attempt: PersistedUsageAttempt | undefined,
|
|
966
1013
|
provider: string,
|
|
967
1014
|
adapter: string,
|
|
1015
|
+
accountLogLabel?: string,
|
|
968
1016
|
): void {
|
|
969
1017
|
if (!attempt) return;
|
|
970
1018
|
attempt.provider = provider;
|
|
971
1019
|
attempt.adapter = adapter;
|
|
1020
|
+
if (isCodexUsageAccountLogLabel(accountLogLabel)) attempt.accountLogLabel = accountLogLabel;
|
|
972
1021
|
}
|
|
973
1022
|
|
|
974
1023
|
export function noteAttemptSend(
|
|
@@ -249,9 +249,24 @@ export async function multiAgentGuidanceText(
|
|
|
249
249
|
// Codex cannot actually spawn makes spawn_agent reject the override, so
|
|
250
250
|
// suppress positive model claims while the state is stale or unknown.
|
|
251
251
|
const catalogState = await (deps.collectCatalogState ?? defaultCollectCatalogState)();
|
|
252
|
+
// #1354 / #1395: `collectCodexAppServerCatalogState()` folds every app-server
|
|
253
|
+
// owned by the current user into ONE global observation, and the inbound
|
|
254
|
+
// request carries no sender PID or catalog fingerprint. So a stale process A
|
|
255
|
+
// makes the global state `stale` even when this request came from a fresh
|
|
256
|
+
// process B, and `unknown` can be reached by a process-enumeration failure
|
|
257
|
+
// that says nothing about any particular server.
|
|
258
|
+
//
|
|
259
|
+
// Emitting "do not set model or reasoning_effort overrides" off that global
|
|
260
|
+
// observation prohibits options the active `spawn_agent` tool legitimately
|
|
261
|
+
// advertises, for a request we cannot attribute to the stale process. The
|
|
262
|
+
// safe behaviour is to withhold OpenCodex's own disk-derived claims —
|
|
263
|
+
// preferred model, roster, fallback, custom guidance — and stay silent about
|
|
264
|
+
// overrides, leaving the active tool schema authoritative.
|
|
265
|
+
//
|
|
266
|
+
// `fresh` and `not_running` are unchanged: there we can positively describe
|
|
267
|
+
// the catalog, so the guidance below still applies.
|
|
252
268
|
if (catalogState.state === "stale" || catalogState.state === "unknown") {
|
|
253
|
-
return
|
|
254
|
-
+ "model or reasoning_effort overrides until Codex restarts.</multi_agent_mode>";
|
|
269
|
+
return null;
|
|
255
270
|
}
|
|
256
271
|
// codex-rs supplies the Proactive text on v2; the proxy only adds model-designation
|
|
257
272
|
// guidance, and only when there is something concrete to designate: a configured
|
|
@@ -381,32 +396,93 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
381
396
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
382
397
|
}
|
|
383
398
|
|
|
384
|
-
function
|
|
385
|
-
if (!isRecord(item) || item.type !== "message" || item.role !== "developer") return
|
|
386
|
-
if (!Array.isArray(item.content) || item.content.length !== 1) return
|
|
399
|
+
function generatedDeveloperText(item: unknown): string | undefined {
|
|
400
|
+
if (!isRecord(item) || item.type !== "message" || item.role !== "developer") return undefined;
|
|
401
|
+
if (!Array.isArray(item.content) || item.content.length !== 1) return undefined;
|
|
387
402
|
const [part] = item.content;
|
|
388
|
-
return isRecord(part) && part.type === "input_text" && part.text ===
|
|
403
|
+
return isRecord(part) && part.type === "input_text" && typeof part.text === "string"
|
|
404
|
+
? part.text
|
|
405
|
+
: undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function isGeneratedDeveloperItem(item: unknown, text: string): boolean {
|
|
409
|
+
return generatedDeveloperText(item) === text;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function isDeveloperPrefixItem(item: unknown): boolean {
|
|
413
|
+
if (!isRecord(item)) return false;
|
|
414
|
+
if (item.type === "additional_tools") return item.role === "developer";
|
|
415
|
+
const type = item.type ?? (typeof item.role === "string" ? "message" : undefined);
|
|
416
|
+
return type === "message" && (item.role === "system" || item.role === "developer");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function leadingDeveloperPrefixLength(items: readonly unknown[]): number {
|
|
420
|
+
let index = 0;
|
|
421
|
+
while (index < items.length && isDeveloperPrefixItem(items[index])) index += 1;
|
|
422
|
+
return index;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function isConversationalItem(item: unknown): boolean {
|
|
426
|
+
if (!isRecord(item)) return false;
|
|
427
|
+
if (item.type === "agent_message") return true;
|
|
428
|
+
const type = item.type ?? (typeof item.role === "string" ? "message" : undefined);
|
|
429
|
+
return type === "message" && (item.role === "user" || item.role === "assistant");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function statefulRawInsertionIndex(items: readonly unknown[], replayPrefixLen: number): number {
|
|
433
|
+
for (let index = replayPrefixLen; index < items.length; index += 1) {
|
|
434
|
+
if (isConversationalItem(items[index])) return index;
|
|
435
|
+
}
|
|
436
|
+
const last = items[items.length - 1];
|
|
437
|
+
return isRecord(last) && last.type === "compaction_trigger"
|
|
438
|
+
? items.length - 1
|
|
439
|
+
: items.length;
|
|
389
440
|
}
|
|
390
441
|
|
|
391
442
|
export function injectDeveloperMessage(parsed: OcxParsedRequest, text: string): void {
|
|
392
443
|
const raw = parsed._rawBody as { input?: unknown } | undefined;
|
|
444
|
+
const rawInput = raw && Array.isArray(raw.input) ? raw.input : undefined;
|
|
445
|
+
const replayPrefixLen = rawInput
|
|
446
|
+
? Math.min(parsed._replayPrefixLen ?? 0, rawInput.length)
|
|
447
|
+
: 0;
|
|
393
448
|
const devItem = { type: "message", role: "developer", content: [{ type: "input_text", text }] };
|
|
394
|
-
if (
|
|
395
|
-
const
|
|
396
|
-
|
|
449
|
+
if (rawInput) {
|
|
450
|
+
const replayPrefix = rawInput.slice(0, replayPrefixLen);
|
|
451
|
+
const taggedGuidance = text.startsWith("<multi_agent_mode>") && text.endsWith("</multi_agent_mode>");
|
|
452
|
+
const lastTaggedGuidance = taggedGuidance
|
|
453
|
+
? replayPrefix.map(generatedDeveloperText)
|
|
454
|
+
.filter(item => item?.startsWith("<multi_agent_mode>") && item.endsWith("</multi_agent_mode>"))
|
|
455
|
+
.at(-1)
|
|
456
|
+
: undefined;
|
|
457
|
+
if (taggedGuidance ? lastTaggedGuidance === text : replayPrefix.some(item => isGeneratedDeveloperItem(item, text))) {
|
|
397
458
|
return;
|
|
398
459
|
}
|
|
399
460
|
}
|
|
400
461
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
462
|
+
const statefulContinuation = parsed.previousResponseId !== undefined;
|
|
463
|
+
const message = { role: "developer" as const, content: text, timestamp: Date.now() };
|
|
464
|
+
const statefulRawIndex = statefulContinuation && rawInput
|
|
465
|
+
? statefulRawInsertionIndex(rawInput, replayPrefixLen)
|
|
466
|
+
: undefined;
|
|
467
|
+
|
|
468
|
+
// A previous_response_id delta can begin with tool/protocol items. Keep those first, then place
|
|
469
|
+
// changed guidance before the current conversation. Stateless requests keep guidance in the prefix.
|
|
470
|
+
if (statefulContinuation) {
|
|
471
|
+
const index = Math.min(
|
|
472
|
+
parsed._continuationConversationMessageIndex ?? parsed.context.messages.length,
|
|
473
|
+
parsed.context.messages.length,
|
|
474
|
+
);
|
|
475
|
+
parsed.context.messages.splice(index, 0, message);
|
|
476
|
+
} else {
|
|
477
|
+
const prefixLen = parsed.context.messages.findIndex(item => item.role !== "developer");
|
|
478
|
+
parsed.context.messages.splice(prefixLen < 0 ? parsed.context.messages.length : prefixLen, 0, message);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (rawInput) {
|
|
482
|
+
if (statefulContinuation) {
|
|
483
|
+
rawInput.splice(statefulRawIndex!, 0, devItem);
|
|
408
484
|
} else {
|
|
409
|
-
|
|
485
|
+
rawInput.splice(leadingDeveloperPrefixLength(rawInput), 0, devItem);
|
|
410
486
|
}
|
|
411
487
|
}
|
|
412
488
|
}
|
|
@@ -120,6 +120,7 @@ import {
|
|
|
120
120
|
} from "../relay";
|
|
121
121
|
import { hasResponsesItemIdRepair, relaySseWithResponsesItemIdRepair } from "../responses-item-id-repair";
|
|
122
122
|
import type { EffectiveSubagentRoster, SpawnAgentSurface } from "../../codex/catalog";
|
|
123
|
+
import { codexAuthContextLogLabel } from "../../codex/account-label";
|
|
123
124
|
|
|
124
125
|
import {
|
|
125
126
|
decodeRequestErrorResponse,
|
|
@@ -362,6 +363,7 @@ export async function handleResponsesCompact(
|
|
|
362
363
|
modelId: selectedModelId,
|
|
363
364
|
beginCodexAccountSelection: codexAccountSelectionForTurn(turnAdmissionLease),
|
|
364
365
|
});
|
|
366
|
+
logCtx.accountLogLabel = codexAuthContextLogLabel(authCtx, config);
|
|
365
367
|
const selected = headersForCodexAuthContext(req.headers, authCtx);
|
|
366
368
|
compactProvider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
|
|
367
369
|
for (const name of FORWARD_HEADERS) {
|
|
@@ -590,6 +592,7 @@ export async function handleResponsesCompact(
|
|
|
590
592
|
});
|
|
591
593
|
await upstream.body?.cancel().catch(() => undefined);
|
|
592
594
|
outcomeCtx = alternate.authCtx;
|
|
595
|
+
logCtx.accountLogLabel = codexAuthContextLogLabel(alternate.authCtx, config);
|
|
593
596
|
try {
|
|
594
597
|
upstream = await sendCompactAttempt(alternate.provider, alternate.headers, "single");
|
|
595
598
|
} catch (err) {
|