@bitkyc08/opencodex 2.43.0 → 2.44.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 +25 -17
- package/gui/dist/assets/index-B7_K1Hsj.js +115 -0
- package/gui/dist/assets/index-ltx3L-WS.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/command-code.ts +23 -2
- package/src/adapters/cursor/native-exec.ts +63 -11
- package/src/adapters/cursor/tool-definitions.ts +1 -1
- package/src/adapters/cursor/tool-schemas.ts +59 -0
- package/src/adapters/kiro.ts +54 -8
- package/src/adapters/openai-chat.ts +23 -6
- package/src/adapters/openai-responses.ts +3 -1
- package/src/adapters/opencode-go.ts +35 -0
- package/src/claude/agents-inject.ts +12 -5
- package/src/claude/desktop-3p-library.ts +89 -0
- package/src/claude/desktop-3p.ts +166 -95
- package/src/claude/desktop-discovery-inputs.ts +44 -0
- package/src/claude/desktop-profile.ts +1 -1
- package/src/claude/desktop-remote-store-artifact.ts +185 -0
- package/src/claude/desktop-remote-store-io.ts +91 -0
- package/src/claude/desktop-remote-store-state.ts +127 -0
- package/src/claude/desktop-remote-store.ts +269 -0
- package/src/claude/inbound-model-options.ts +10 -2
- package/src/claude/inbound-records.ts +7 -0
- package/src/claude/inbound.ts +1 -1
- package/src/claude/model-info.ts +16 -3
- package/src/cli/account-api.ts +7 -1
- package/src/cli/aside-profiles.ts +17 -0
- package/src/cli/capabilities.ts +39 -0
- package/src/cli/claude-agent-startup-sync.ts +3 -1
- package/src/cli/claude-desktop.ts +127 -18
- package/src/cli/connect.ts +27 -14
- package/src/cli/dispatch.ts +30 -16
- package/src/cli/export-command.ts +3 -1
- package/src/cli/index.ts +28 -16
- package/src/cli/integrations.ts +46 -12
- package/src/cli/opencode.ts +11 -4
- package/src/cli/runtime-api.ts +7 -2
- package/src/cli/uninstall-client-state.ts +78 -0
- package/src/client/connect.ts +476 -223
- package/src/client/hub-client.ts +81 -0
- package/src/client/hub-relay.ts +38 -6
- package/src/client/lifecycle-lock.ts +129 -0
- package/src/client/state.ts +71 -42
- package/src/clients/aside-profiles.ts +224 -0
- package/src/codex/account-lifecycle.ts +5 -9
- package/src/codex/auth-api.ts +50 -4
- package/src/codex/catalog/metadata.ts +0 -2
- package/src/codex/catalog/provider-fetch.ts +7 -5
- package/src/codex/catalog/sync.ts +88 -24
- package/src/codex/convergence.ts +2 -0
- package/src/codex/inject.ts +45 -14
- package/src/codex/journal.ts +6 -6
- package/src/codex/quota-refresh-outcome.ts +27 -0
- package/src/codex/shim.ts +7 -1
- package/src/combos/resolve.ts +10 -7
- package/src/config.ts +18 -5
- package/src/generated/compatibility-version.json +209 -105
- package/src/images/loop.ts +5 -2
- package/src/integrations/aside-profile-context.ts +270 -0
- package/src/integrations/aside-profile-journal.ts +229 -0
- package/src/integrations/aside-profiles.ts +176 -0
- package/src/integrations/catalog-refresh.ts +40 -0
- package/src/integrations/config-io.ts +16 -1
- package/src/integrations/owned-refresh.ts +14 -2
- package/src/integrations/state.ts +3 -1
- package/src/integrations/writer.ts +3 -1
- package/src/lab/fabric/producer-isolate.ts +84 -46
- package/src/lib/account-selection-events.ts +32 -0
- package/src/lib/errors.ts +9 -1
- package/src/lib/provider-outbound.ts +2 -45
- package/src/lib/proxy-env.ts +67 -0
- package/src/oauth/account-quota-rank.ts +9 -1
- package/src/oauth/anthropic-routing.ts +91 -23
- package/src/oauth/generic-account-failover.ts +15 -53
- package/src/oauth/index.ts +67 -68
- package/src/oauth/pool-settings-capability.ts +2 -1
- package/src/oauth/store.ts +91 -7
- package/src/oauth/types.ts +8 -0
- package/src/providers/api-key-selection.ts +110 -0
- package/src/providers/api-keys.ts +38 -31
- package/src/providers/context-cap.ts +27 -3
- package/src/providers/key-failover.ts +27 -17
- package/src/providers/provider-id-rewrite.ts +10 -8
- package/src/providers/registry.ts +1 -0
- package/src/responses/custom-tool-compat.ts +10 -0
- package/src/responses/function-call-compat.ts +173 -0
- package/src/responses/namespace-tool-compat.ts +27 -4
- package/src/responses/parser.ts +8 -0
- package/src/responses/task-input.ts +36 -0
- package/src/responses/tool-name-aliases.ts +79 -0
- package/src/router.ts +2 -0
- package/src/routing/capability.ts +10 -3
- package/src/routing/compatibility/assemble.ts +16 -1
- package/src/routing/evaluator.ts +5 -1
- package/src/server/auth-cors.ts +3 -0
- package/src/server/chat-native.ts +44 -9
- package/src/server/claude-messages.ts +71 -36
- package/src/server/grok-responses-snapshot-repair.ts +333 -0
- package/src/server/index.ts +26 -10
- package/src/server/management/account-selection-stream.ts +70 -0
- package/src/server/management/aside-profile-routes.ts +231 -0
- package/src/server/management/config-routes.ts +36 -41
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +61 -3
- package/src/server/management/logs-usage-routes.ts +1 -0
- package/src/server/management/model-routes.ts +20 -9
- package/src/server/management/model-rows.ts +10 -2
- package/src/server/management/oauth-account-routes.ts +7 -0
- package/src/server/management/provider-routes.ts +10 -8
- package/src/server/management/route-registry.ts +20 -2
- package/src/server/management-auth.ts +16 -0
- package/src/server/relay-eager.ts +17 -5
- package/src/server/relay.ts +43 -4
- package/src/server/request-log.ts +29 -1
- package/src/server/responses/agent-task-recovery-cache.ts +11 -0
- package/src/server/responses/agent-task-recovery.ts +24 -4
- package/src/server/responses/codex-ws-pool.ts +4 -4
- package/src/server/responses/codex-ws-session.ts +2 -2
- package/src/server/responses/collaboration.ts +2 -1
- package/src/server/responses/combo-stream-preflight.ts +26 -7
- package/src/server/responses/core.ts +668 -151
- package/src/server/responses/fetch-helpers.ts +6 -1
- package/src/server/responses/ws-upstream.ts +8 -3
- package/src/server/responses-custom-tool-repair.ts +29 -9
- package/src/server/responses-function-tool-repair.ts +183 -0
- package/src/server/responses-snapshot-codec.ts +14 -0
- package/src/server/responses-snapshot-repair.ts +1 -13
- package/src/server/responses-undeclared-tool-guard.ts +1 -68
- package/src/server/sse-payload-rewrite.ts +3 -1
- package/src/service.ts +6 -2
- package/src/types/config.ts +23 -16
- package/src/types/provider.ts +14 -3
- package/src/types/request.ts +2 -0
- package/src/usage/log.ts +9 -0
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/index-DS1NE4Jn.css +0 -1
- package/gui/dist/assets/index-Djowl68T.js +0 -112
|
@@ -61,6 +61,8 @@ export interface ProviderFetchOptions {
|
|
|
61
61
|
onCodexWsQuota?: CodexWsQuotaObserver;
|
|
62
62
|
/** Synchronous admission at actual credential dispatch, after pacing/backoff. */
|
|
63
63
|
beforeDispatch?: (headers: Headers) => void;
|
|
64
|
+
/** Revalidate/rebuild a queued request at its physical send boundary, after pacing. */
|
|
65
|
+
dispatchOverride?: (input: Parameters<typeof globalThis.fetch>[0], init: RequestInit, execute: typeof globalThis.fetch) => Promise<Response>;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export function providerFetch(
|
|
@@ -75,7 +77,10 @@ export function providerFetch(
|
|
|
75
77
|
const httpFetch = Object.assign(
|
|
76
78
|
async (input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) => {
|
|
77
79
|
options.beforeDispatch?.(new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined)));
|
|
78
|
-
|
|
80
|
+
const dispatchInit = { ...withUpstreamHttpVersion(input, init, provider), timeout: 0 };
|
|
81
|
+
return options.dispatchOverride
|
|
82
|
+
? options.dispatchOverride(input, dispatchInit, base)
|
|
83
|
+
: base(input, dispatchInit);
|
|
79
84
|
},
|
|
80
85
|
{ preconnect },
|
|
81
86
|
) as typeof globalThis.fetch;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// (passthrough relay, adapter parsers, usage sniffing) is unchanged.
|
|
14
14
|
|
|
15
15
|
import { compareBunVersions } from "../../lib/bun-stream-caps";
|
|
16
|
+
import { resolveProxyRoute } from "../../lib/proxy-env";
|
|
16
17
|
import type { CodexWsQuotaObserver } from "./codex-ws-metadata";
|
|
17
18
|
import { CODEX_RESPONSES_HTTP_URL, CODEX_RESPONSES_WS_URL, prepareCodexHttpInit, prepareCodexWsRequest } from "./codex-ws-request";
|
|
18
19
|
import { codexWsExchange } from "./codex-ws-exchange";
|
|
@@ -150,6 +151,10 @@ export function codexWsUpstreamFetch(
|
|
|
150
151
|
return sseFallback(url, init);
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
const wsUrl = wsUpstreamUrlFor(url);
|
|
155
|
+
const proxyRoute = resolveProxyRoute(new URL(wsUrl));
|
|
156
|
+
if (proxyRoute.kind === "fallback") return sseFallback(url, init);
|
|
157
|
+
const proxy = proxyRoute.kind === "proxy" ? proxyRoute.proxy : undefined;
|
|
153
158
|
// A genuine caller `originator` is already in these headers via the forward
|
|
154
159
|
// set. Never fabricate one here: pool/forward traffic must not impersonate
|
|
155
160
|
// Codex CLI, per the metadata-integrity contract. (The backend's fast lane
|
|
@@ -164,9 +169,9 @@ export function codexWsUpstreamFetch(
|
|
|
164
169
|
}
|
|
165
170
|
let session: CodexWsSession;
|
|
166
171
|
try {
|
|
167
|
-
const identity = codexWsReuseIdentity(url, headers, frameText);
|
|
168
|
-
session = (identity ? codexWsPool.acquire(identity,
|
|
169
|
-
?? new CodexWsSession(
|
|
172
|
+
const identity = codexWsReuseIdentity(url, headers, frameText, proxy);
|
|
173
|
+
session = (identity ? codexWsPool.acquire(identity, wsUrl, headers, proxy) : null)
|
|
174
|
+
?? new CodexWsSession(wsUrl, headers, false, undefined, proxy);
|
|
170
175
|
if (!session.busy && !session.reserve()) {
|
|
171
176
|
session.dispose();
|
|
172
177
|
return sseFallback(url, init);
|
|
@@ -93,7 +93,8 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
93
93
|
declaredNames?: ReadonlySet<string>,
|
|
94
94
|
): SseBlockRewrite {
|
|
95
95
|
const itemNames = new Map<string, { name: string; aliased: boolean; namespace?: string }>();
|
|
96
|
-
|
|
96
|
+
// Native helper aliases and genuine bare code-mode exec calls share completion repair.
|
|
97
|
+
const customExecItemNames = new Map<string, string>();
|
|
97
98
|
const repairItemNames = new Map<string, string>();
|
|
98
99
|
const ordinaryItemIds = new Set<string>();
|
|
99
100
|
const openCalls = new Map<string, OpenCustomCall>();
|
|
@@ -119,7 +120,7 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
119
120
|
}
|
|
120
121
|
pendingArguments = [];
|
|
121
122
|
itemNames.clear();
|
|
122
|
-
|
|
123
|
+
customExecItemNames.clear();
|
|
123
124
|
repairItemNames.clear();
|
|
124
125
|
ordinaryItemIds.clear();
|
|
125
126
|
};
|
|
@@ -195,15 +196,17 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
195
196
|
const wireName = routedCustomToolWireName(parsed.item);
|
|
196
197
|
const targetName = routedCustomToolTargetName(parsed.item, names, declaredNames);
|
|
197
198
|
const aliased = targetName !== undefined && targetName !== wireName;
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
const codeModeExec = targetName === "exec" && parsed.item.name === "exec"
|
|
200
|
+
&& parsed.item.namespace === undefined && declaresCodeModeExec(declaredNames);
|
|
201
|
+
if (upstreamItemId && (aliased || codeModeExec)) {
|
|
202
|
+
customExecItemNames.set(upstreamItemId, parsed.item.name);
|
|
200
203
|
if (type === "response.output_item.added") {
|
|
201
204
|
openCalls.set(upstreamItemId, { argumentsText: "", emittedInput: "", retainedBytes: 0 });
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
const repairable = wireName !== undefined && repairNames.has(wireName);
|
|
205
208
|
if (upstreamItemId && repairable) repairItemNames.set(upstreamItemId, parsed.item.name);
|
|
206
|
-
const restored = repairable || aliased
|
|
209
|
+
const restored = repairable || aliased || codeModeExec
|
|
207
210
|
? restoreRoutedCustomCalls(parsed, names, repairNames, declaredNames)
|
|
208
211
|
: { value: parsed, changed: false };
|
|
209
212
|
if (type === "response.output_item.done" && upstreamItemId) releaseCall(upstreamItemId);
|
|
@@ -259,7 +262,7 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
259
262
|
if (
|
|
260
263
|
type === "response.custom_tool_call_input.delta"
|
|
261
264
|
&& upstreamItemId
|
|
262
|
-
&&
|
|
265
|
+
&& customExecItemNames.has(upstreamItemId)
|
|
263
266
|
) {
|
|
264
267
|
const open = openCalls.get(upstreamItemId) ?? { argumentsText: "", emittedInput: "", retainedBytes: 0 };
|
|
265
268
|
const delta = typeof parsed.delta === "string" ? parsed.delta : "";
|
|
@@ -268,19 +271,34 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
268
271
|
open.argumentsText += delta;
|
|
269
272
|
open.retainedBytes += deltaBytes;
|
|
270
273
|
openCalls.set(upstreamItemId, open);
|
|
271
|
-
|
|
274
|
+
if (customExecItemNames.get(upstreamItemId) !== "exec"
|
|
275
|
+
|| mayBecomePatchEnvelope(open.argumentsText)
|
|
276
|
+
// JSON.parse accepts whitespace, escaped keys and arbitrary property order.
|
|
277
|
+
// Any object prefix may still wrap a patch; keep it until authoritative completion.
|
|
278
|
+
|| open.argumentsText.trimStart() === ""
|
|
279
|
+
|| open.argumentsText.trimStart().startsWith("{")) return [];
|
|
280
|
+
// If a held prefix turns out to be ordinary JavaScript, release the entire
|
|
281
|
+
// un-emitted suffix. Native custom input remains byte-exact.
|
|
282
|
+
const inputDelta = open.argumentsText.slice(open.emittedInput.length);
|
|
283
|
+
open.emittedInput = open.argumentsText;
|
|
284
|
+
return inputDelta ? [replaceSseDataPayload(block, JSON.stringify({ ...parsed, delta: inputDelta }))] : [];
|
|
272
285
|
}
|
|
273
286
|
if (
|
|
274
287
|
type === "response.custom_tool_call_input.done"
|
|
275
288
|
&& upstreamItemId
|
|
276
|
-
&&
|
|
289
|
+
&& customExecItemNames.has(upstreamItemId)
|
|
277
290
|
) {
|
|
278
291
|
const source = typeof parsed.input === "string"
|
|
279
292
|
? parsed.input
|
|
280
293
|
: openCalls.get(upstreamItemId)?.argumentsText ?? "";
|
|
294
|
+
const name = customExecItemNames.get(upstreamItemId)!;
|
|
295
|
+
const helper = name === "exec"
|
|
296
|
+
? resolveCodeModeHelperName(undefined, name, source, undefined, declaredNames)
|
|
297
|
+
: name;
|
|
298
|
+
releaseCall(upstreamItemId);
|
|
281
299
|
return [replaceSseDataPayload(block, JSON.stringify({
|
|
282
300
|
...parsed,
|
|
283
|
-
input: compileCodeModeHelperInput(source,
|
|
301
|
+
input: helper ? compileCodeModeHelperInput(source, helper) : source,
|
|
284
302
|
}))];
|
|
285
303
|
}
|
|
286
304
|
if (
|
|
@@ -312,6 +330,8 @@ export function createRoutedCustomToolRestoreBlockRewrite(
|
|
|
312
330
|
open.argumentsText += delta;
|
|
313
331
|
open.retainedBytes += deltaBytes;
|
|
314
332
|
openCalls.set(upstreamItemId, open);
|
|
333
|
+
// A helper alias will become JavaScript at completion, never raw patch/JSON.
|
|
334
|
+
if (itemNames.get(upstreamItemId)?.aliased) return [];
|
|
315
335
|
// Still accumulating toward the compact wrapper, or an unrecognized shape:
|
|
316
336
|
// suppress progressive emission and let the done event carry input.
|
|
317
337
|
if (FREEFORM_WRAP_PREFIX.startsWith(open.argumentsText)) return [];
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TRANSLATOR_MAX_TURN_BYTES,
|
|
3
|
+
TranslatorBudgetExceededError,
|
|
4
|
+
type TranslatorBudget,
|
|
5
|
+
} from "../lib/translator-budget";
|
|
6
|
+
import { repairFunctionCalls, type FunctionCallRepairSchemas } from "../responses/function-call-compat";
|
|
7
|
+
import { replaceSseDataPayload, sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
|
|
8
|
+
|
|
9
|
+
type Identity = {
|
|
10
|
+
itemId?: string;
|
|
11
|
+
outputIndex?: number;
|
|
12
|
+
item: Record<string, unknown>;
|
|
13
|
+
bytes: number;
|
|
14
|
+
};
|
|
15
|
+
type PendingCompletion = {
|
|
16
|
+
block: string;
|
|
17
|
+
itemId?: string;
|
|
18
|
+
outputIndex?: number;
|
|
19
|
+
bytes: number;
|
|
20
|
+
};
|
|
21
|
+
const ENTRY_OVERHEAD_BYTES = 64;
|
|
22
|
+
|
|
23
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function outputIndexOf(value: unknown): number | undefined {
|
|
28
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Ordinary deltas remain upstream previews; only authoritative completions are repaired. */
|
|
32
|
+
export function createResponsesFunctionToolRepairBlockRewrite(
|
|
33
|
+
schemas: FunctionCallRepairSchemas,
|
|
34
|
+
budget?: TranslatorBudget,
|
|
35
|
+
): SseBlockRewrite {
|
|
36
|
+
if (schemas.size === 0) return block => [block];
|
|
37
|
+
const byId = new Map<string, Identity>();
|
|
38
|
+
const byIndex = new Map<number, Identity>();
|
|
39
|
+
let pending: PendingCompletion[] = [];
|
|
40
|
+
let retainedBytes = 0;
|
|
41
|
+
let disposed = false;
|
|
42
|
+
|
|
43
|
+
const retain = (bytes: number): void => {
|
|
44
|
+
if (retainedBytes + bytes > TRANSLATOR_MAX_TURN_BYTES) {
|
|
45
|
+
throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
|
|
46
|
+
}
|
|
47
|
+
budget?.chargeRetained(bytes, { kind: "retained_collectors" });
|
|
48
|
+
retainedBytes += bytes;
|
|
49
|
+
};
|
|
50
|
+
const release = (bytes: number): void => {
|
|
51
|
+
budget?.releaseRetained(bytes, { kind: "retained_collectors" });
|
|
52
|
+
retainedBytes -= bytes;
|
|
53
|
+
};
|
|
54
|
+
const releaseIdentity = (identity: Identity): void => {
|
|
55
|
+
if (identity.itemId !== undefined) byId.delete(identity.itemId);
|
|
56
|
+
if (identity.outputIndex !== undefined) byIndex.delete(identity.outputIndex);
|
|
57
|
+
release(identity.bytes);
|
|
58
|
+
};
|
|
59
|
+
const dispose = (): void => {
|
|
60
|
+
if (disposed) return;
|
|
61
|
+
disposed = true;
|
|
62
|
+
release(retainedBytes);
|
|
63
|
+
byId.clear();
|
|
64
|
+
byIndex.clear();
|
|
65
|
+
pending = [];
|
|
66
|
+
};
|
|
67
|
+
const lookup = (itemId: string | undefined, index: number | undefined): Identity | undefined => {
|
|
68
|
+
const identity = itemId === undefined ? undefined : byId.get(itemId);
|
|
69
|
+
if (identity) return index === undefined || identity.outputIndex === undefined || identity.outputIndex === index ? identity : undefined;
|
|
70
|
+
const indexed = index === undefined ? undefined : byIndex.get(index);
|
|
71
|
+
return indexed && (itemId === undefined || indexed.itemId === undefined || indexed.itemId === itemId) ? indexed : undefined;
|
|
72
|
+
};
|
|
73
|
+
const register = (item: Record<string, unknown>, index: number | undefined): Identity | undefined => {
|
|
74
|
+
const itemId = typeof item.id === "string" && item.id ? item.id : undefined;
|
|
75
|
+
if (itemId === undefined && index === undefined) return undefined;
|
|
76
|
+
// Repeated snapshots replace metadata; never retain provider argument bodies here.
|
|
77
|
+
const previous = new Set<Identity>();
|
|
78
|
+
if (itemId !== undefined && byId.has(itemId)) previous.add(byId.get(itemId)!);
|
|
79
|
+
if (index !== undefined && byIndex.has(index)) previous.add(byIndex.get(index)!);
|
|
80
|
+
for (const identity of previous) releaseIdentity(identity);
|
|
81
|
+
const metadata = {
|
|
82
|
+
type: item.type,
|
|
83
|
+
name: item.name,
|
|
84
|
+
...("namespace" in item ? { namespace: item.namespace } : {}),
|
|
85
|
+
...(item.status !== undefined && item.status !== "in_progress" && item.status !== "completed" ? { status: item.status } : {}),
|
|
86
|
+
};
|
|
87
|
+
const bytes = ENTRY_OVERHEAD_BYTES + Buffer.byteLength(JSON.stringify([itemId, index, metadata]), "utf8");
|
|
88
|
+
retain(bytes);
|
|
89
|
+
const identity = { itemId, outputIndex: index, item: metadata, bytes };
|
|
90
|
+
if (itemId !== undefined) byId.set(itemId, identity);
|
|
91
|
+
if (index !== undefined) byIndex.set(index, identity);
|
|
92
|
+
return identity;
|
|
93
|
+
};
|
|
94
|
+
const repairCompletion = (block: string, event: Record<string, unknown>, identity: Identity): string => {
|
|
95
|
+
if (typeof event.arguments !== "string") return block;
|
|
96
|
+
const resolved = (typeof event.item_id !== "string" || event.item_id === "") && identity.itemId !== undefined
|
|
97
|
+
? { ...event, item_id: identity.itemId }
|
|
98
|
+
: event;
|
|
99
|
+
const repaired = repairFunctionCalls({ ...identity.item, status: identity.item.status ?? "completed", arguments: event.arguments }, schemas);
|
|
100
|
+
if (repaired.changed && isObject(repaired.value)) {
|
|
101
|
+
return replaceSseDataPayload(block, JSON.stringify({ ...resolved, arguments: repaired.value.arguments }));
|
|
102
|
+
}
|
|
103
|
+
return resolved === event ? block : replaceSseDataPayload(block, JSON.stringify(resolved));
|
|
104
|
+
};
|
|
105
|
+
const flushPending = (identity: Identity): string[] => {
|
|
106
|
+
const output: string[] = [];
|
|
107
|
+
const remaining: PendingCompletion[] = [];
|
|
108
|
+
for (const completion of pending) {
|
|
109
|
+
const matches = completion.itemId !== undefined
|
|
110
|
+
? completion.itemId === identity.itemId
|
|
111
|
+
&& (completion.outputIndex === undefined || identity.outputIndex === undefined || completion.outputIndex === identity.outputIndex)
|
|
112
|
+
: completion.outputIndex !== undefined && completion.outputIndex === identity.outputIndex;
|
|
113
|
+
if (!matches) { remaining.push(completion); continue; }
|
|
114
|
+
release(completion.bytes);
|
|
115
|
+
const payload = sseDataPayload(completion.block);
|
|
116
|
+
const event: unknown = payload === null ? undefined : JSON.parse(payload);
|
|
117
|
+
output.push(isObject(event) ? repairCompletion(completion.block, event, identity) : completion.block);
|
|
118
|
+
}
|
|
119
|
+
pending = remaining;
|
|
120
|
+
return output;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const rewrite: SseBlockRewrite = block => {
|
|
124
|
+
if (disposed) return [block];
|
|
125
|
+
const payload = sseDataPayload(block);
|
|
126
|
+
if (payload === null) return [block];
|
|
127
|
+
if (payload === "[DONE]") {
|
|
128
|
+
const unfinished = pending.map(entry => entry.block);
|
|
129
|
+
dispose();
|
|
130
|
+
return [...unfinished, block];
|
|
131
|
+
}
|
|
132
|
+
let event: unknown;
|
|
133
|
+
try { event = JSON.parse(payload); } catch { return [block]; }
|
|
134
|
+
if (!isObject(event)) return [block];
|
|
135
|
+
const index = outputIndexOf(event.output_index);
|
|
136
|
+
const itemId = typeof event.item_id === "string" && event.item_id ? event.item_id : undefined;
|
|
137
|
+
try {
|
|
138
|
+
if ((event.type === "response.output_item.added" || event.type === "response.output_item.done") && isObject(event.item)) {
|
|
139
|
+
const identity = register(event.item, index);
|
|
140
|
+
const replayed = identity ? flushPending(identity) : [];
|
|
141
|
+
const repaired = repairFunctionCalls(event, schemas);
|
|
142
|
+
const output = repaired.changed ? replaceSseDataPayload(block, JSON.stringify(repaired.value)) : block;
|
|
143
|
+
if (identity && (event.type === "response.output_item.done" || replayed.length > 0)) releaseIdentity(identity);
|
|
144
|
+
return event.type === "response.output_item.added" ? [output, ...replayed] : [...replayed, output];
|
|
145
|
+
}
|
|
146
|
+
if (event.type === "response.function_call_arguments.done" && typeof event.arguments === "string") {
|
|
147
|
+
const identity = lookup(itemId, index);
|
|
148
|
+
if (identity) {
|
|
149
|
+
const output = repairCompletion(block, event, identity);
|
|
150
|
+
releaseIdentity(identity);
|
|
151
|
+
return [output];
|
|
152
|
+
}
|
|
153
|
+
if (itemId !== undefined || index !== undefined) {
|
|
154
|
+
const bytes = ENTRY_OVERHEAD_BYTES + Buffer.byteLength(block, "utf8");
|
|
155
|
+
retain(bytes);
|
|
156
|
+
pending.push({ block, itemId, outputIndex: index, bytes });
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (typeof event.type === "string" && ["response.completed", "response.failed", "response.incomplete", "response.cancelled"].includes(event.type)) {
|
|
161
|
+
const replayed: string[] = [];
|
|
162
|
+
if (event.type === "response.completed" && isObject(event.response) && Array.isArray(event.response.output)
|
|
163
|
+
&& (event.response.status === undefined || event.response.status === "completed")) {
|
|
164
|
+
for (const [slot, item] of event.response.output.entries()) {
|
|
165
|
+
if (!isObject(item)) continue;
|
|
166
|
+
const identity = register(item, slot);
|
|
167
|
+
if (identity) replayed.push(...flushPending(identity));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
replayed.push(...pending.map(entry => entry.block));
|
|
171
|
+
const repaired = repairFunctionCalls(event, schemas);
|
|
172
|
+
dispose();
|
|
173
|
+
return [...replayed, repaired.changed ? replaceSseDataPayload(block, JSON.stringify(repaired.value)) : block];
|
|
174
|
+
}
|
|
175
|
+
return [block];
|
|
176
|
+
} catch (error) {
|
|
177
|
+
dispose();
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
rewrite.dispose = dispose;
|
|
182
|
+
return rewrite;
|
|
183
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Shared snapshot wire primitives; no retention state or policy. */
|
|
2
|
+
|
|
3
|
+
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type RetainedOutputItem = {
|
|
8
|
+
item: Record<string, unknown>;
|
|
9
|
+
sourceBytes: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function jsonBlock(event: Record<string, unknown>): string {
|
|
13
|
+
return `data: ${JSON.stringify(event)}`;
|
|
14
|
+
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES,
|
|
27
27
|
} from "./relay";
|
|
28
28
|
import { sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
|
|
29
|
+
import { isPlainObject, jsonBlock, type RetainedOutputItem } from "./responses-snapshot-codec";
|
|
29
30
|
|
|
30
31
|
const RESPONSE_EVENT_STATUSES: Readonly<Record<string, string>> = {
|
|
31
32
|
"response.created": "in_progress",
|
|
@@ -42,10 +43,6 @@ type RequestDefaults = {
|
|
|
42
43
|
tools: unknown[];
|
|
43
44
|
};
|
|
44
45
|
|
|
45
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
46
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
46
|
function isStructurallyValidToolChoice(value: unknown): boolean {
|
|
50
47
|
return (typeof value === "string" && value.trim().length > 0)
|
|
51
48
|
|| (isPlainObject(value) && typeof value.type === "string" && value.type.trim().length > 0);
|
|
@@ -179,15 +176,6 @@ type OpenItem = {
|
|
|
179
176
|
const MAX_OPEN_ITEMS = MAX_COMPLETED_OUTPUT_ITEMS;
|
|
180
177
|
const MAX_OPEN_ITEM_AGGREGATE_TEXT_BYTES = MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES;
|
|
181
178
|
|
|
182
|
-
type RetainedOutputItem = {
|
|
183
|
-
item: Record<string, unknown>;
|
|
184
|
-
sourceBytes: number;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
function jsonBlock(event: Record<string, unknown>): string {
|
|
188
|
-
return `data: ${JSON.stringify(event)}`;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
179
|
/**
|
|
192
180
|
* Stateful block rewrite: field backfills + lifecycle completion injection.
|
|
193
181
|
* `budget` bounds retained completed items (reconstruction only).
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectAmbiguousDottedAliases, dottedAliasIsUnambiguous, wireToolInnerName } from "../responses/tool-name-aliases";
|
|
1
2
|
import {
|
|
2
3
|
CODE_MODE_EXEC_TOOL_NAME,
|
|
3
4
|
dottedToolName,
|
|
@@ -75,32 +76,6 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
75
76
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
/**
|
|
79
|
-
* A dotted spelling is a safe alias only when it cannot ALSO be read as some other identity's
|
|
80
|
-
* canonical `ns__name`.
|
|
81
|
-
*
|
|
82
|
-
* `{namespace: "x__y", name: "z"}` produces the dotted spelling "x__y.z", which is exactly the
|
|
83
|
-
* canonical wire name of `{namespace: "x", name: "y.z"}`. If only the latter is declared, an
|
|
84
|
-
* echoed call for the former would still find "x__y.z" in the declared set and be authorized as
|
|
85
|
-
* a tool the caller never granted. Requiring both halves to be free of the `__` separator keeps
|
|
86
|
-
* a dotted alias from ever impersonating a canonical name.
|
|
87
|
-
*/
|
|
88
|
-
function dottedAliasIsUnambiguous(namespace: string, name: string): boolean {
|
|
89
|
-
return !namespace.includes("__") && !name.includes("__");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function wireToolInnerName(tool: unknown): string | undefined {
|
|
93
|
-
if (!isPlainObject(tool)) return undefined;
|
|
94
|
-
const nestedFunction = tool.type === "function" && isPlainObject(tool.function)
|
|
95
|
-
? tool.function
|
|
96
|
-
: undefined;
|
|
97
|
-
return typeof tool.name === "string" && tool.name.length > 0
|
|
98
|
-
? tool.name
|
|
99
|
-
: typeof nestedFunction?.name === "string" && nestedFunction.name.length > 0
|
|
100
|
-
? nestedFunction.name
|
|
101
|
-
: undefined;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
79
|
function addWireToolName(
|
|
105
80
|
names: Set<string>,
|
|
106
81
|
tool: unknown,
|
|
@@ -171,48 +146,6 @@ function addWireToolSpecs(
|
|
|
171
146
|
}
|
|
172
147
|
}
|
|
173
148
|
|
|
174
|
-
/**
|
|
175
|
-
* Dotted aliases that more than one declared identity would claim, plus dotted aliases that
|
|
176
|
-
* collide with a canonical or bare declared name.
|
|
177
|
-
*
|
|
178
|
-
* Resolved over the WHOLE catalog before any name is registered, so which identity "wins" can
|
|
179
|
-
* never depend on declaration order -- an order the caller controls.
|
|
180
|
-
*/
|
|
181
|
-
function collectAmbiguousDottedAliases(specGroups: readonly unknown[]): Set<string> {
|
|
182
|
-
const owners = new Map<string, string | null>();
|
|
183
|
-
const claim = (alias: string, identity: string): void => {
|
|
184
|
-
const owner = owners.get(alias);
|
|
185
|
-
if (owner === undefined) owners.set(alias, identity);
|
|
186
|
-
else if (owner !== identity) owners.set(alias, null);
|
|
187
|
-
};
|
|
188
|
-
for (const specs of specGroups) {
|
|
189
|
-
if (!Array.isArray(specs)) continue;
|
|
190
|
-
for (const spec of specs) {
|
|
191
|
-
if (!isPlainObject(spec)) continue;
|
|
192
|
-
if (spec.type === "namespace" && Array.isArray(spec.tools)) {
|
|
193
|
-
const namespace = typeof spec.name === "string" ? spec.name : undefined;
|
|
194
|
-
if (!namespace || namespace === BUILTIN_FUNCTIONS_NAMESPACE) continue;
|
|
195
|
-
for (const inner of spec.tools) {
|
|
196
|
-
const name = wireToolInnerName(inner);
|
|
197
|
-
if (!name) continue;
|
|
198
|
-
const identity = JSON.stringify([namespace, name]);
|
|
199
|
-
claim(dottedToolName(namespace, name), identity);
|
|
200
|
-
// A canonical or bare name already owned by a different identity poisons the dotted
|
|
201
|
-
// alias that would shadow it.
|
|
202
|
-
claim(namespacedToolName(namespace, name), identity);
|
|
203
|
-
claim(name, identity);
|
|
204
|
-
}
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
const name = wireToolInnerName(spec);
|
|
208
|
-
if (name) claim(name, JSON.stringify([undefined, name]));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const ambiguous = new Set<string>();
|
|
212
|
-
for (const [alias, owner] of owners) if (owner === null) ambiguous.add(alias);
|
|
213
|
-
return ambiguous;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
149
|
/**
|
|
217
150
|
* Tool names the OUTBOUND Responses body actually declared.
|
|
218
151
|
*
|
|
@@ -249,7 +249,9 @@ export function relaySseWithBlockRewrite(
|
|
|
249
249
|
} catch (error) {
|
|
250
250
|
releaseBuffer();
|
|
251
251
|
disposeRewrite();
|
|
252
|
-
|
|
252
|
+
// Cancelling one tee branch waits for its sibling. Surface the failure
|
|
253
|
+
// now so downstream can abort upstream and release the inspection branch.
|
|
254
|
+
void reader.cancel(error).catch(() => {});
|
|
253
255
|
controller.error(error);
|
|
254
256
|
}
|
|
255
257
|
},
|
package/src/service.ts
CHANGED
|
@@ -1926,7 +1926,7 @@ export function buildWindowsTaskXml(
|
|
|
1926
1926
|
<Enabled>true</Enabled>
|
|
1927
1927
|
<Hidden>false</Hidden>
|
|
1928
1928
|
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
1929
|
-
<Priority>
|
|
1929
|
+
<Priority>4</Priority>
|
|
1930
1930
|
<RestartOnFailure>
|
|
1931
1931
|
<Interval>PT1M</Interval>
|
|
1932
1932
|
<Count>3</Count>
|
|
@@ -2972,6 +2972,10 @@ export async function repairService(deps: RepairServiceDeps = {}): Promise<void>
|
|
|
2972
2972
|
const identityUpgradeNeeded = registrationHealthy
|
|
2973
2973
|
&& preferredSid !== undefined
|
|
2974
2974
|
&& !windowsTaskHasSessionRecoveryTriggers(triggers, preferredSid);
|
|
2975
|
+
// Omitted Priority also defaults to 7; background priority can starve health probes under CPU load.
|
|
2976
|
+
const priorityUpgradeNeeded = registrationHealthy && taskXmlOptionalValueEquals(
|
|
2977
|
+
taskXmlSection(taskXmlWithoutCommentsAndCdata(registeredXml), "Settings"), "Priority", "7",
|
|
2978
|
+
);
|
|
2975
2979
|
const refreshableLegacy = windowsTaskRegistrationRefreshableLegacy(
|
|
2976
2980
|
registeredXml,
|
|
2977
2981
|
deps.schedulerWscript,
|
|
@@ -2998,7 +3002,7 @@ export async function repairService(deps: RepairServiceDeps = {}): Promise<void>
|
|
|
2998
3002
|
// Re-register only when the registered XML is actually stale, so the ordinary repair
|
|
2999
3003
|
// stays free of `schtasks /create` and its UAC prompt.
|
|
3000
3004
|
let startExpectedXml = registeredXml;
|
|
3001
|
-
if (!registrationHealthy || identityUpgradeNeeded) {
|
|
3005
|
+
if (!registrationHealthy || identityUpgradeNeeded || priorityUpgradeNeeded) {
|
|
3002
3006
|
// The task was stopped above, so a failed replacement must not exit here: `/create /f`
|
|
3003
3007
|
// can be rejected, elevation can be cancelled, and staging or verification can fail.
|
|
3004
3008
|
// Any of those would leave a previously runnable proxy stopped and the user worse off
|
package/src/types/config.ts
CHANGED
|
@@ -409,6 +409,13 @@ export interface OcxConfig {
|
|
|
409
409
|
* one key at a time rather than widening a shared union.
|
|
410
410
|
*/
|
|
411
411
|
clientIntegrations?: OcxClientIntegrationsConfig;
|
|
412
|
+
/** Aside account-backed profile synchronization; individual overrides survive bulk refresh. */
|
|
413
|
+
asideProfileSync?: {
|
|
414
|
+
allProfiles?: boolean;
|
|
415
|
+
profiles?: Record<string, boolean>;
|
|
416
|
+
/** Stable provenance for the one legacy root ownership record, or no root owner. */
|
|
417
|
+
legacyProfileId?: number | null;
|
|
418
|
+
};
|
|
412
419
|
/**
|
|
413
420
|
* Up to 5 Codex-facing catalog ids to feature first. Values may be bare catalog ids,
|
|
414
421
|
* exact account-qualified "<selector>/<native-openai-model>" ids, or routed
|
|
@@ -419,17 +426,15 @@ export interface OcxConfig {
|
|
|
419
426
|
/** One-time featured-roster upgrade marker; later user ordering is preserved. */
|
|
420
427
|
subagentModelsVersion?: number;
|
|
421
428
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* candidate set is derived from each row's natural priority and is provably unaffected, even
|
|
432
|
-
* when every routed row is listed (see opencodex_spawn_priority / effectiveSubagentRoster).
|
|
429
|
+
* Display-only order for the Codex picker, independent of subagentModels.
|
|
430
|
+
* Routed-only lists order non-featured routed rows; featured and native rows keep
|
|
431
|
+
* their normal positions. Including a bare native id opts into ordering the complete
|
|
432
|
+
* picker: listed ids appear first in array order, followed by unlisted rows in their
|
|
433
|
+
* natural priority order. Exact catalog ids take precedence over equivalent raw/encoded
|
|
434
|
+
* routed ids; empty entries are ignored. The separate natural priority used by
|
|
435
|
+
* OpenCodex guidance is preserved. Native Codex's advertised five follow display
|
|
436
|
+
* priority and may change; exact-name override eligibility is not restricted by that list.
|
|
437
|
+
* Unset or empty leaves catalog priorities unchanged.
|
|
433
438
|
*/
|
|
434
439
|
modelPickerOrder?: string[];
|
|
435
440
|
/**
|
|
@@ -603,8 +608,10 @@ export interface OcxConfig {
|
|
|
603
608
|
* so absence is the only default state this feature has.
|
|
604
609
|
*/
|
|
605
610
|
quotaResetNotify?: OcxQuotaResetNotifyConfig;
|
|
606
|
-
/**
|
|
611
|
+
/** Active provider context limits; native long windows remain within their supported ceilings. */
|
|
607
612
|
providerContextCaps?: Record<string, number>;
|
|
613
|
+
/** Last selected provider caps; retained while a cap is switched off. Not an active limit. */
|
|
614
|
+
providerContextCapValues?: Record<string, number>;
|
|
608
615
|
/** Global Codex-visible context cap value (tokens). Falls back to DEFAULT_PROVIDER_CONTEXT_CAP. */
|
|
609
616
|
contextCapValue?: number;
|
|
610
617
|
/** Bind hostname. Default "127.0.0.1" (loopback only). Set "0.0.0.0" to expose on all interfaces. */
|
|
@@ -808,10 +815,10 @@ export interface OcxConfig {
|
|
|
808
815
|
* provider has 2 or more eligible stored accounts, the same consent rule an `apiKeyPool` of
|
|
809
816
|
* two keys already applies, and a single account remains a strict no-op.
|
|
810
817
|
*
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
*
|
|
818
|
+
* Proactive avoidance of an exhausted selected account requires `enabled: true`.
|
|
819
|
+
* A healthy selected account retains priority; an unknown quota is not exhaustion.
|
|
820
|
+
* `providers.<name>.oauthAccountFailover` overrides this per provider in either direction.
|
|
821
|
+
* Reactive 429 rotation remains presence-driven even when proactive routing is disabled.
|
|
815
822
|
*/
|
|
816
823
|
oauthAccountFailover?: {
|
|
817
824
|
enabled?: boolean;
|
package/src/types/provider.ts
CHANGED
|
@@ -8,6 +8,13 @@ import type { UpstreamHttpVersion, ReasoningSummaryDelivery, CodexAccountMode }
|
|
|
8
8
|
*/
|
|
9
9
|
export type RefreshPolicy = "proactive" | "lazy-only" | "disabled";
|
|
10
10
|
|
|
11
|
+
/** Request-owned identity of the configured key, before env/keychain resolution. */
|
|
12
|
+
export interface ProviderApiKeySelection {
|
|
13
|
+
entryId?: string;
|
|
14
|
+
reference?: string;
|
|
15
|
+
revision?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export interface OpenRouterProviderRouting {
|
|
12
19
|
/** OpenRouter provider slugs to try first, in priority order. */
|
|
13
20
|
order?: string[];
|
|
@@ -329,6 +336,10 @@ export interface OcxProviderConfig {
|
|
|
329
336
|
* `apiKey` seeds a one-entry pool on first management touch.
|
|
330
337
|
*/
|
|
331
338
|
apiKeyPool?: Array<{ id: string; key: string; label?: string; addedAt?: number }>;
|
|
339
|
+
/** Changes on manual selection (including re-selection) and committed automatic allocation. */
|
|
340
|
+
apiKeySelectionRevision?: string;
|
|
341
|
+
/** Runtime only. Never expose in management responses or persist a routed provider. */
|
|
342
|
+
_apiKeyAttempt?: ProviderApiKeySelection;
|
|
332
343
|
defaultModel?: string;
|
|
333
344
|
models?: string[];
|
|
334
345
|
/**
|
|
@@ -441,9 +452,9 @@ export interface OcxProviderConfig {
|
|
|
441
452
|
*
|
|
442
453
|
* Reactive 429 rotation is presence-driven and cannot be refused here — 2+ logged-in accounts
|
|
443
454
|
* activate it, and a 429 with an idle second account is a defect rather than a preference.
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
455
|
+
* Proactive exhaustion avoidance requires explicit `true`; a healthy selected account
|
|
456
|
+
* retains priority. This overrides global `oauthAccountFailover` in either direction.
|
|
457
|
+
* Reactive 429 rotation remains available even when proactive routing is disabled.
|
|
447
458
|
*/
|
|
448
459
|
oauthAccountFailover?: {
|
|
449
460
|
enabled?: boolean;
|
package/src/types/request.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface OcxParsedRequest {
|
|
|
68
68
|
_cursorConversationId?: string;
|
|
69
69
|
/** Stable upstream client thread identity, used only to derive provider-scoped continuation ids. */
|
|
70
70
|
_clientThreadId?: string;
|
|
71
|
+
/** True when promptCacheKey identifies a shared cache cohort rather than one conversation. */
|
|
72
|
+
_promptCacheKeyIsSharedCohort?: boolean;
|
|
71
73
|
/** Cursor-only thread owner; may be an opaque process-local Desktop session/thread identity. */
|
|
72
74
|
_cursorClientThreadId?: string;
|
|
73
75
|
/** Conversation/provider/account/model-bound namespace for reasoning replay state. */
|
package/src/usage/log.ts
CHANGED
|
@@ -55,9 +55,14 @@ export type AttemptRecoveryKind =
|
|
|
55
55
|
| "opaque-blob-rejection"
|
|
56
56
|
| "empty-completion";
|
|
57
57
|
|
|
58
|
+
/** Request-time upstream credential class, never a credential or account identifier. */
|
|
59
|
+
export type UsageCredentialSource = "grok-oauth" | "xai-api-key";
|
|
60
|
+
|
|
58
61
|
export interface PersistedUsageAttempt {
|
|
59
62
|
ordinal: number;
|
|
60
63
|
provider: string;
|
|
64
|
+
/** Absent on historic attempts and routes whose subscription attribution is unknown. */
|
|
65
|
+
credentialSource?: UsageCredentialSource;
|
|
61
66
|
model: string;
|
|
62
67
|
adapter: string;
|
|
63
68
|
status: number;
|
|
@@ -400,6 +405,10 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
400
405
|
return {
|
|
401
406
|
ordinal: attempt.ordinal as number,
|
|
402
407
|
provider: attempt.provider,
|
|
408
|
+
...(attempt.provider === "xai"
|
|
409
|
+
&& (attempt.credentialSource === "grok-oauth" || attempt.credentialSource === "xai-api-key")
|
|
410
|
+
? { credentialSource: attempt.credentialSource }
|
|
411
|
+
: {}),
|
|
403
412
|
model: attempt.model,
|
|
404
413
|
adapter: attempt.adapter,
|
|
405
414
|
status: attempt.status,
|