@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.
Files changed (138) hide show
  1. package/README.md +25 -17
  2. package/gui/dist/assets/index-B7_K1Hsj.js +115 -0
  3. package/gui/dist/assets/index-ltx3L-WS.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/command-code.ts +23 -2
  7. package/src/adapters/cursor/native-exec.ts +63 -11
  8. package/src/adapters/cursor/tool-definitions.ts +1 -1
  9. package/src/adapters/cursor/tool-schemas.ts +59 -0
  10. package/src/adapters/kiro.ts +54 -8
  11. package/src/adapters/openai-chat.ts +23 -6
  12. package/src/adapters/openai-responses.ts +3 -1
  13. package/src/adapters/opencode-go.ts +35 -0
  14. package/src/claude/agents-inject.ts +12 -5
  15. package/src/claude/desktop-3p-library.ts +89 -0
  16. package/src/claude/desktop-3p.ts +166 -95
  17. package/src/claude/desktop-discovery-inputs.ts +44 -0
  18. package/src/claude/desktop-profile.ts +1 -1
  19. package/src/claude/desktop-remote-store-artifact.ts +185 -0
  20. package/src/claude/desktop-remote-store-io.ts +91 -0
  21. package/src/claude/desktop-remote-store-state.ts +127 -0
  22. package/src/claude/desktop-remote-store.ts +269 -0
  23. package/src/claude/inbound-model-options.ts +10 -2
  24. package/src/claude/inbound-records.ts +7 -0
  25. package/src/claude/inbound.ts +1 -1
  26. package/src/claude/model-info.ts +16 -3
  27. package/src/cli/account-api.ts +7 -1
  28. package/src/cli/aside-profiles.ts +17 -0
  29. package/src/cli/capabilities.ts +39 -0
  30. package/src/cli/claude-agent-startup-sync.ts +3 -1
  31. package/src/cli/claude-desktop.ts +127 -18
  32. package/src/cli/connect.ts +27 -14
  33. package/src/cli/dispatch.ts +30 -16
  34. package/src/cli/export-command.ts +3 -1
  35. package/src/cli/index.ts +28 -16
  36. package/src/cli/integrations.ts +46 -12
  37. package/src/cli/opencode.ts +11 -4
  38. package/src/cli/runtime-api.ts +7 -2
  39. package/src/cli/uninstall-client-state.ts +78 -0
  40. package/src/client/connect.ts +476 -223
  41. package/src/client/hub-client.ts +81 -0
  42. package/src/client/hub-relay.ts +38 -6
  43. package/src/client/lifecycle-lock.ts +129 -0
  44. package/src/client/state.ts +71 -42
  45. package/src/clients/aside-profiles.ts +224 -0
  46. package/src/codex/account-lifecycle.ts +5 -9
  47. package/src/codex/auth-api.ts +50 -4
  48. package/src/codex/catalog/metadata.ts +0 -2
  49. package/src/codex/catalog/provider-fetch.ts +7 -5
  50. package/src/codex/catalog/sync.ts +88 -24
  51. package/src/codex/convergence.ts +2 -0
  52. package/src/codex/inject.ts +45 -14
  53. package/src/codex/journal.ts +6 -6
  54. package/src/codex/quota-refresh-outcome.ts +27 -0
  55. package/src/codex/shim.ts +7 -1
  56. package/src/combos/resolve.ts +10 -7
  57. package/src/config.ts +18 -5
  58. package/src/generated/compatibility-version.json +209 -105
  59. package/src/images/loop.ts +5 -2
  60. package/src/integrations/aside-profile-context.ts +270 -0
  61. package/src/integrations/aside-profile-journal.ts +229 -0
  62. package/src/integrations/aside-profiles.ts +176 -0
  63. package/src/integrations/catalog-refresh.ts +40 -0
  64. package/src/integrations/config-io.ts +16 -1
  65. package/src/integrations/owned-refresh.ts +14 -2
  66. package/src/integrations/state.ts +3 -1
  67. package/src/integrations/writer.ts +3 -1
  68. package/src/lab/fabric/producer-isolate.ts +84 -46
  69. package/src/lib/account-selection-events.ts +32 -0
  70. package/src/lib/errors.ts +9 -1
  71. package/src/lib/provider-outbound.ts +2 -45
  72. package/src/lib/proxy-env.ts +67 -0
  73. package/src/oauth/account-quota-rank.ts +9 -1
  74. package/src/oauth/anthropic-routing.ts +91 -23
  75. package/src/oauth/generic-account-failover.ts +15 -53
  76. package/src/oauth/index.ts +67 -68
  77. package/src/oauth/pool-settings-capability.ts +2 -1
  78. package/src/oauth/store.ts +91 -7
  79. package/src/oauth/types.ts +8 -0
  80. package/src/providers/api-key-selection.ts +110 -0
  81. package/src/providers/api-keys.ts +38 -31
  82. package/src/providers/context-cap.ts +27 -3
  83. package/src/providers/key-failover.ts +27 -17
  84. package/src/providers/provider-id-rewrite.ts +10 -8
  85. package/src/providers/registry.ts +1 -0
  86. package/src/responses/custom-tool-compat.ts +10 -0
  87. package/src/responses/function-call-compat.ts +173 -0
  88. package/src/responses/namespace-tool-compat.ts +27 -4
  89. package/src/responses/parser.ts +8 -0
  90. package/src/responses/task-input.ts +36 -0
  91. package/src/responses/tool-name-aliases.ts +79 -0
  92. package/src/router.ts +2 -0
  93. package/src/routing/capability.ts +10 -3
  94. package/src/routing/compatibility/assemble.ts +16 -1
  95. package/src/routing/evaluator.ts +5 -1
  96. package/src/server/auth-cors.ts +3 -0
  97. package/src/server/chat-native.ts +44 -9
  98. package/src/server/claude-messages.ts +71 -36
  99. package/src/server/grok-responses-snapshot-repair.ts +333 -0
  100. package/src/server/index.ts +26 -10
  101. package/src/server/management/account-selection-stream.ts +70 -0
  102. package/src/server/management/aside-profile-routes.ts +231 -0
  103. package/src/server/management/config-routes.ts +36 -41
  104. package/src/server/management/context.ts +3 -0
  105. package/src/server/management/integration-routes.ts +61 -3
  106. package/src/server/management/logs-usage-routes.ts +1 -0
  107. package/src/server/management/model-routes.ts +20 -9
  108. package/src/server/management/model-rows.ts +10 -2
  109. package/src/server/management/oauth-account-routes.ts +7 -0
  110. package/src/server/management/provider-routes.ts +10 -8
  111. package/src/server/management/route-registry.ts +20 -2
  112. package/src/server/management-auth.ts +16 -0
  113. package/src/server/relay-eager.ts +17 -5
  114. package/src/server/relay.ts +43 -4
  115. package/src/server/request-log.ts +29 -1
  116. package/src/server/responses/agent-task-recovery-cache.ts +11 -0
  117. package/src/server/responses/agent-task-recovery.ts +24 -4
  118. package/src/server/responses/codex-ws-pool.ts +4 -4
  119. package/src/server/responses/codex-ws-session.ts +2 -2
  120. package/src/server/responses/collaboration.ts +2 -1
  121. package/src/server/responses/combo-stream-preflight.ts +26 -7
  122. package/src/server/responses/core.ts +668 -151
  123. package/src/server/responses/fetch-helpers.ts +6 -1
  124. package/src/server/responses/ws-upstream.ts +8 -3
  125. package/src/server/responses-custom-tool-repair.ts +29 -9
  126. package/src/server/responses-function-tool-repair.ts +183 -0
  127. package/src/server/responses-snapshot-codec.ts +14 -0
  128. package/src/server/responses-snapshot-repair.ts +1 -13
  129. package/src/server/responses-undeclared-tool-guard.ts +1 -68
  130. package/src/server/sse-payload-rewrite.ts +3 -1
  131. package/src/service.ts +6 -2
  132. package/src/types/config.ts +23 -16
  133. package/src/types/provider.ts +14 -3
  134. package/src/types/request.ts +2 -0
  135. package/src/usage/log.ts +9 -0
  136. package/src/web-search/loop.ts +5 -2
  137. package/gui/dist/assets/index-DS1NE4Jn.css +0 -1
  138. 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
- return base(input, { ...withUpstreamHttpVersion(input, init, provider), timeout: 0 });
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, wsUpstreamUrlFor(url), headers) : null)
169
- ?? new CodexWsSession(wsUpstreamUrlFor(url), headers);
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
- const customAliasItemNames = new Map<string, string>();
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
- customAliasItemNames.clear();
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
- if (upstreamItemId && aliased) {
199
- customAliasItemNames.set(upstreamItemId, parsed.item.name);
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
- && customAliasItemNames.has(upstreamItemId)
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
- return [];
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
- && customAliasItemNames.has(upstreamItemId)
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, customAliasItemNames.get(upstreamItemId)!),
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
- try { await reader.cancel(error); } catch { /* already closed */ }
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>7</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
@@ -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
- * Optional full picker ordering for the Codex model catalog, independent of the
423
- * 5-slot `subagentModels` spawn_agent cap. DISPLAY-ONLY: it controls the visual order of
424
- * the Codex model picker for large routed catalogs (10-20+ models) that would otherwise sort
425
- * arbitrarily and reshuffle on every rebuild. Values are routed `<provider>/<model>` catalog
426
- * slugs (matched by exact slug or `provider/id`); native OpenAI passthrough rows and
427
- * account-qualified native rows are not reordered (order native rows via `subagentModels`).
428
- * Listed routed rows appear in array order; rows not listed keep their normal display order.
429
- * `subagentModels`-featured rows keep their top position. When unset or empty, catalog
430
- * priority is unchanged. This changes ONLY what the user sees in the picker: the spawn_agent
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
- /** Provider-level Codex-visible context caps. Values only lower known model context windows. */
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
- * What `enabled: false` still refuses is the PRE-DISPATCH preference: steering a request
812
- * upstream has not refused toward the account with more known headroom. That moves a healthy
813
- * request, so it stays a real choice. `providers.<name>.oauthAccountFailover` overrides this
814
- * per provider in either direction; reactive 429 rotation remains presence-driven.
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;
@@ -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
- * What an explicit `false` still refuses is the pre-dispatch preference that steers a HEALTHY
445
- * request toward the account with more known headroom. It beats the global
446
- * `oauthAccountFailover` in either direction; reactive 429 rotation remains presence-driven.
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;
@@ -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,