@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
@@ -10,8 +10,9 @@ import { FORWARD_HEADERS } from "../adapters/openai-responses";
10
10
  import { sseFieldValue } from "../lib/sse-decoder";
11
11
  import { enforceAnthropicImageLimits, sniffImageDimensions } from "../adapters/anthropic-image-guard";
12
12
  import { normalizeAnthropicImages } from "../adapters/anthropic-image-normalize";
13
- import { AnthropicRequestError, anthropicToResponsesTranslation, extractOcxEffortDirective, extractOcxRouteDirective, resolveInboundModel, type ClaudeCacheKeySource } from "../claude/inbound";
14
- import { resolveDesktop3pAlias } from "../claude/desktop-3p";
13
+ import { AnthropicRequestError, DesktopModelMappingUnavailableError, anthropicToResponsesTranslation, extractOcxEffortDirective, extractOcxRouteDirective, resolveInboundModel, type ClaudeCacheKeySource } from "../claude/inbound";
14
+ import { isKnownDesktop3pModelId, resolveDesktop3pAlias } from "../claude/desktop-3p";
15
+ import { resolveAlias, claudeCodeNativeAlias } from "../claude/alias";
15
16
  import { recordDesktopRequest } from "../claude/desktop-health";
16
17
  import { stripOneMillionMarker } from "../claude/context-windows";
17
18
  import { captureClaudeInbound } from "../claude/inbound-debug";
@@ -72,17 +73,37 @@ type Rec = Record<string, unknown>;
72
73
  * resolve a synthetic one.
73
74
  */
74
75
  function decodeClaudeFastSelector(raw: string, cc?: OcxConfig["claudeCode"]): string {
75
- const exact = resolveInboundModel(raw, cc);
76
- if (exact !== raw || !raw.endsWith("--fast")) return exact;
77
- const bare = raw.slice(0, -"--fast".length);
76
+ const model = stripOneMillionMarker(raw);
77
+ const exact = resolveInboundModel(model, cc);
78
+ if (!model.endsWith("--fast")) return exact;
79
+ const fullMapping = cc?.modelMap?.[model];
80
+ if (resolveAlias(model) || isKnownDesktop3pModelId(model)
81
+ || (typeof fullMapping === "string" && fullMapping.length > 0)) return exact;
82
+ const bare = model.slice(0, -"--fast".length);
83
+ // A classifier fallback is not an exact match for a registered Desktop base.
84
+ // Preserve established non-Desktop fallback behavior while decoding that base first.
85
+ if (exact !== model && !resolveDesktop3pAlias(bare)) return exact;
78
86
  const decodedBase = resolveInboundModel(bare, cc);
79
87
  return decodedBase === bare ? exact : `${decodedBase}--fast`;
80
88
  }
81
89
 
90
+ /** Restore the reversible Fable picker alias before Anthropic passthrough checks. */
91
+ function decodeFablePickerAlias(raw: string, cc?: OcxConfig["claudeCode"]): string {
92
+ const decoded = resolveInboundModel(raw, cc);
93
+ if (!decoded.startsWith("claude-fable-")) return raw;
94
+ return claudeCodeNativeAlias(decoded) === raw ? decoded : raw;
95
+ }
96
+
82
97
  function isRec(v: unknown): v is Rec {
83
98
  return !!v && typeof v === "object" && !Array.isArray(v);
84
99
  }
85
100
 
101
+ function desktopMappingUnavailableResponse(error: DesktopModelMappingUnavailableError): Response {
102
+ const response = anthropicErrorResponse(503, error.message, "api_error", "desktop_model_mapping_unavailable");
103
+ response.headers.set("Retry-After", "1");
104
+ return response;
105
+ }
106
+
86
107
  /** Resolve Claude-only sidecar overrides without mutating the shared server config. */
87
108
  export function buildClaudeReplayConfig(config: OcxConfig): OcxConfig {
88
109
  return {
@@ -649,6 +670,9 @@ async function handleClaudeMessagesWithBudget(
649
670
  effortOverride = extractOcxEffortDirective(anthropicBody);
650
671
  }
651
672
  }
673
+ if (isRec(anthropicBody) && typeof anthropicBody.model === "string") {
674
+ anthropicBody.model = decodeFablePickerAlias(anthropicBody.model, config.claudeCode);
675
+ }
652
676
  if (isRec(anthropicBody) && typeof anthropicBody.model === "string") {
653
677
  requestedModel = anthropicBody.model;
654
678
  // Decode for Fast only. A Claude alias is `claude-ocx-<provider>--<model>`, so it
@@ -712,8 +736,10 @@ async function handleClaudeMessagesWithBudget(
712
736
  cacheKeySource = translation.cacheKeySource;
713
737
  } catch (err) {
714
738
  const overflow = isTranslatorBudgetExceededError(err);
715
- const status = overflow ? 413 : err instanceof AnthropicRequestError ? 400 : 500;
739
+ const unavailable = err instanceof DesktopModelMappingUnavailableError;
740
+ const status = overflow ? 413 : unavailable ? 503 : err instanceof AnthropicRequestError ? 400 : 500;
716
741
  if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, status, { closeReason: "non_stream" });
742
+ if (unavailable) return desktopMappingUnavailableResponse(err);
717
743
  return anthropicErrorResponse(
718
744
  status,
719
745
  overflow ? "request translation buffer exceeded the safe limit" : err instanceof Error ? err.message : String(err),
@@ -1056,6 +1082,7 @@ export async function handleClaudeCountTokens(
1056
1082
  try {
1057
1083
  body = await readAnthropicBody(req, translatorBudget);
1058
1084
  } catch (err) {
1085
+ if (err instanceof DesktopModelMappingUnavailableError) return desktopMappingUnavailableResponse(err);
1059
1086
  if (err instanceof AnthropicRequestError) return anthropicErrorResponse(400, err.message);
1060
1087
  return anthropicErrorResponse(500, err instanceof Error ? err.message : String(err));
1061
1088
  } finally { translatorBudget.dispose(); }
@@ -1066,36 +1093,44 @@ export async function handleClaudeCountTokens(
1066
1093
  if (typeof raw.model !== "string" || raw.model.length === 0) {
1067
1094
  return anthropicErrorResponse(400, "model is required");
1068
1095
  }
1069
- let model = raw.model;
1070
- // Case-insensitive [1m] strip (audit 021 #7 — the CLI matches /\[1m\]/i).
1071
- const stripped = stripOneMillionMarker(model);
1072
- if (stripped !== model) {
1073
- model = stripped;
1074
- raw.model = model;
1075
- }
1076
- // ocx-route override (devlog 072): keep count_tokens consistent with messages.
1077
- const countRoute = extractOcxRouteDirective(raw);
1078
- if (countRoute) {
1079
- model = stripOneMillionMarker(countRoute);
1080
- raw.model = model;
1081
- }
1082
- // Fast-only: count_tokens never parsed an effort row, so it must not start. It returns a
1083
- // token estimate and sends no tier, so only the IDENTITY is corrected - without this the
1084
- // synthetic id reaches native passthrough as a model Anthropic has never heard of.
1085
- const countFastRow = parseFastOnlyRowId(
1086
- config, () => decodeClaudeFastSelector(model, config.claudeCode),
1087
- );
1088
- if (countFastRow) {
1089
- model = countFastRow.baseId;
1096
+ try {
1097
+ let model = raw.model;
1098
+ // Case-insensitive [1m] strip (audit 021 #7 — the CLI matches /\[1m\]/i).
1099
+ const stripped = stripOneMillionMarker(model);
1100
+ if (stripped !== model) {
1101
+ model = stripped;
1102
+ raw.model = model;
1103
+ }
1104
+ // ocx-route override (devlog 072): keep count_tokens consistent with messages.
1105
+ const countRoute = extractOcxRouteDirective(raw);
1106
+ if (countRoute) {
1107
+ model = stripOneMillionMarker(countRoute);
1108
+ raw.model = model;
1109
+ }
1110
+ model = decodeFablePickerAlias(model, config.claudeCode);
1090
1111
  raw.model = model;
1112
+ // Fast-only: count_tokens never parsed an effort row, so it must not start. It returns a
1113
+ // token estimate and sends no tier, so only the IDENTITY is corrected - without this the
1114
+ // synthetic id reaches native passthrough as a model Anthropic has never heard of.
1115
+ const countFastRow = parseFastOnlyRowId(
1116
+ config, () => decodeClaudeFastSelector(model, config.claudeCode),
1117
+ );
1118
+ if (countFastRow) {
1119
+ model = countFastRow.baseId;
1120
+ raw.model = model;
1121
+ }
1122
+ captureClaudeInbound("count_tokens", raw, resolveInboundModel(model, config.claudeCode), req.headers.get("anthropic-beta") ?? undefined);
1123
+ if (wantsNativePassthrough(req, config, requestPolicy, model)) {
1124
+ return await anthropicNativePassthrough(req, config, { model, provider: "anthropic-native", surface: "claude" }, undefined, raw, "/v1/messages/count_tokens");
1125
+ }
1126
+ const inputTokens = estimateClaudeRequestTokens(raw, model);
1127
+ return new Response(JSON.stringify({ input_tokens: inputTokens }), {
1128
+ status: 200,
1129
+ headers: { "Content-Type": "application/json" },
1130
+ });
1131
+ } catch (error) {
1132
+ if (error instanceof DesktopModelMappingUnavailableError) return desktopMappingUnavailableResponse(error);
1133
+ if (error instanceof AnthropicRequestError) return anthropicErrorResponse(400, error.message);
1134
+ throw error;
1091
1135
  }
1092
- captureClaudeInbound("count_tokens", raw, resolveInboundModel(model, config.claudeCode), req.headers.get("anthropic-beta") ?? undefined);
1093
- if (wantsNativePassthrough(req, config, requestPolicy, model)) {
1094
- return await anthropicNativePassthrough(req, config, { model, provider: "anthropic-native", surface: "claude" }, undefined, raw, "/v1/messages/count_tokens");
1095
- }
1096
- const inputTokens = estimateClaudeRequestTokens(raw, model);
1097
- return new Response(JSON.stringify({ input_tokens: inputTokens }), {
1098
- status: 200,
1099
- headers: { "Content-Type": "application/json" },
1100
- });
1101
1136
  }
@@ -0,0 +1,333 @@
1
+ /** Strict terminal reconstruction selected by the Grok compatibility marker. */
2
+ import type { TranslatorBudget } from "../lib/translator-budget";
3
+ import { MAX_COMPLETED_OUTPUT_ITEMS, MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES } from "./relay";
4
+ import { sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
5
+ import { isPlainObject, jsonBlock, type RetainedOutputItem } from "./responses-snapshot-codec";
6
+
7
+ type SparseTerminalOpenItem = {
8
+ type: string;
9
+ id?: string;
10
+ sourceBytes: number;
11
+ };
12
+
13
+ type SparseTerminalCompletedItem = RetainedOutputItem & {
14
+ visibleToGrok: boolean;
15
+ };
16
+
17
+ const MAX_GROK_OPEN_ITEM_IDENTITY_BYTES = MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES;
18
+
19
+ const GROK_TERMINAL_OUTPUT_ITEM_TYPES = new Set([
20
+ "message",
21
+ "reasoning",
22
+ "function_call",
23
+ "custom_tool_call",
24
+ "web_search_call",
25
+ "code_interpreter_call",
26
+ "mcp_call",
27
+ ]);
28
+
29
+ function hasValidOptionalId(item: Record<string, unknown>): boolean {
30
+ return !("id" in item)
31
+ || (typeof item.id === "string" && item.id.trim().length > 0);
32
+ }
33
+
34
+ function hasCompletedStatusWhenPresent(item: Record<string, unknown>): boolean {
35
+ return !("status" in item) || item.status === "completed";
36
+ }
37
+
38
+ function isNullableString(value: unknown): boolean {
39
+ return value === null || typeof value === "string";
40
+ }
41
+
42
+ function isValidOutputMessagePart(part: unknown): boolean {
43
+ if (!isPlainObject(part)) return false;
44
+ if (part.type === "output_text") {
45
+ return typeof part.text === "string"
46
+ && (!("annotations" in part) || Array.isArray(part.annotations))
47
+ && (!("logprobs" in part) || part.logprobs === null || Array.isArray(part.logprobs));
48
+ }
49
+ return part.type === "refusal" && typeof part.refusal === "string";
50
+ }
51
+
52
+ function isValidReasoningPart(part: unknown, type: "summary_text" | "reasoning_text"): boolean {
53
+ return isPlainObject(part) && part.type === type && typeof part.text === "string";
54
+ }
55
+
56
+ function isValidWebSearchAction(value: unknown): boolean {
57
+ if (!isPlainObject(value)) return false;
58
+ if (value.type === "search") {
59
+ return typeof value.query === "string"
60
+ && (!("sources" in value) || value.sources === null || (Array.isArray(value.sources)
61
+ && value.sources.every(source => isPlainObject(source)
62
+ && typeof source.type === "string" && typeof source.url === "string")));
63
+ }
64
+ if (value.type === "open_page") {
65
+ return !("url" in value) || isNullableString(value.url);
66
+ }
67
+ if (value.type === "find" || value.type === "find_in_page") {
68
+ return typeof value.url === "string" && typeof value.pattern === "string";
69
+ }
70
+ return false;
71
+ }
72
+
73
+ function isValidCodeInterpreterOutput(value: unknown): boolean {
74
+ return isPlainObject(value)
75
+ && ((value.type === "logs" && typeof value.logs === "string")
76
+ || (value.type === "image" && typeof value.url === "string"));
77
+ }
78
+
79
+ /**
80
+ * Validate the pre-field-backfill item carried by a real output_item.done.
81
+ * Missing ids, message status, and output-text annotations are allowed because
82
+ * the always-on field backfill safely supplies only those schema defaults.
83
+ * Contradictory values and semantic content repairs are never accepted as
84
+ * proof that an empty terminal snapshot was sparse.
85
+ */
86
+ function trustedGrokCompletedItem(
87
+ item: Record<string, unknown>,
88
+ ): { visibleToGrok: boolean } | null {
89
+ if (!hasValidOptionalId(item) || !hasCompletedStatusWhenPresent(item)) return null;
90
+
91
+ if (item.type === "message") {
92
+ if (item.role !== "assistant" || !Array.isArray(item.content)) return null;
93
+ if (!(item.content as unknown[]).every(isValidOutputMessagePart)) return null;
94
+ if ("phase" in item && item.phase !== "commentary" && item.phase !== "final_answer") return null;
95
+ return {
96
+ // grok-build currently turns only output_text parts into final Assistant
97
+ // content; refusal parts do not satisfy its visible-content gate.
98
+ visibleToGrok: item.content.some(part => isPlainObject(part)
99
+ && part.type === "output_text" && typeof part.text === "string" && part.text.length > 0),
100
+ };
101
+ }
102
+
103
+ if (item.type === "reasoning") {
104
+ if (!Array.isArray(item.summary)
105
+ || !item.summary.every(part => isValidReasoningPart(part, "summary_text"))) return null;
106
+ if ("content" in item && item.content !== null
107
+ && (!Array.isArray(item.content)
108
+ || !item.content.every(part => isValidReasoningPart(part, "reasoning_text")))) return null;
109
+ if ("encrypted_content" in item && !isNullableString(item.encrypted_content)) return null;
110
+ return { visibleToGrok: false };
111
+ }
112
+
113
+ if (item.type === "function_call") {
114
+ if (typeof item.call_id !== "string" || item.call_id.trim().length === 0
115
+ || typeof item.name !== "string" || item.name.trim().length === 0
116
+ || typeof item.arguments !== "string") return null;
117
+ return { visibleToGrok: true };
118
+ }
119
+
120
+ if (item.type === "custom_tool_call") {
121
+ if (typeof item.call_id !== "string" || item.call_id.trim().length === 0
122
+ || typeof item.name !== "string" || item.name.trim().length === 0
123
+ || typeof item.input !== "string") return null;
124
+ return { visibleToGrok: false };
125
+ }
126
+
127
+ if (item.type === "web_search_call") {
128
+ if (item.status !== "completed" || !isValidWebSearchAction(item.action)) return null;
129
+ return { visibleToGrok: false };
130
+ }
131
+
132
+ if (item.type === "code_interpreter_call") {
133
+ if (item.status !== "completed"
134
+ || typeof item.container_id !== "string" || item.container_id.trim().length === 0
135
+ || ("code" in item && !isNullableString(item.code))
136
+ || ("outputs" in item && item.outputs !== null
137
+ && (!Array.isArray(item.outputs) || !item.outputs.every(isValidCodeInterpreterOutput)))) return null;
138
+ return { visibleToGrok: false };
139
+ }
140
+
141
+ if (item.type === "mcp_call") {
142
+ if (typeof item.arguments !== "string"
143
+ || typeof item.name !== "string" || item.name.trim().length === 0
144
+ || typeof item.server_label !== "string" || item.server_label.trim().length === 0
145
+ || ("approval_request_id" in item && !isNullableString(item.approval_request_id))
146
+ || ("error" in item && !isNullableString(item.error))
147
+ || ("output" in item && !isNullableString(item.output))) return null;
148
+ return { visibleToGrok: false };
149
+ }
150
+
151
+ return null;
152
+ }
153
+
154
+ function plausibleGrokOpenItem(
155
+ item: Record<string, unknown>,
156
+ ): Omit<SparseTerminalOpenItem, "sourceBytes"> | null {
157
+ const type = typeof item.type === "string" ? item.type : "";
158
+ if (!GROK_TERMINAL_OUTPUT_ITEM_TYPES.has(type) || !hasValidOptionalId(item)) return null;
159
+ if ("status" in item && item.status !== "in_progress") return null;
160
+ if (type === "message") {
161
+ if ("role" in item && item.role !== "assistant") return null;
162
+ if ("content" in item && !Array.isArray(item.content)) return null;
163
+ }
164
+ return {
165
+ type,
166
+ ...(typeof item.id === "string" ? { id: item.id } : {}),
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Narrow client repair for grok-build's Responses consumer.
172
+ *
173
+ * grok-build streams text deltas but builds its durable Assistant item only
174
+ * from response.completed.response.output. Some native Responses streams put
175
+ * the durable items in output_item.done and finish with a missing or explicit
176
+ * empty output. Reconstruct only from real, unique, contiguous, bounded done
177
+ * events whose raw semantics are already valid. Any ambiguity stays byte-level
178
+ * fail-closed; the provider-opt-in snapshot repair above is unchanged.
179
+ */
180
+ export function createGrokResponsesSparseTerminalBlockRewrite(
181
+ budget?: TranslatorBudget,
182
+ ): SseBlockRewrite {
183
+ const openItems = new Map<number, SparseTerminalOpenItem>();
184
+ const completedItems = new Map<number, SparseTerminalCompletedItem>();
185
+ let aggregateItemBytes = 0;
186
+ let aggregateOpenItemBytes = 0;
187
+ let tainted = false;
188
+ let hasVisibleOutput = false;
189
+
190
+ const clearRetained = (): void => {
191
+ const retainedBytes = aggregateItemBytes + aggregateOpenItemBytes;
192
+ if (retainedBytes > 0) {
193
+ budget?.releaseRetained(retainedBytes, { kind: "retained_collectors" });
194
+ }
195
+ openItems.clear();
196
+ completedItems.clear();
197
+ aggregateItemBytes = 0;
198
+ aggregateOpenItemBytes = 0;
199
+ hasVisibleOutput = false;
200
+ };
201
+
202
+ const reset = (): void => {
203
+ clearRetained();
204
+ tainted = false;
205
+ };
206
+
207
+ const taintAndRelease = (): void => {
208
+ clearRetained();
209
+ tainted = true;
210
+ };
211
+
212
+ const retainCompletedItem = (
213
+ index: number,
214
+ item: Record<string, unknown>,
215
+ visibleToGrok: boolean,
216
+ ): void => {
217
+ if (tainted) return;
218
+ const sourceBytes = Buffer.byteLength(JSON.stringify(item), "utf8");
219
+ if (sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES
220
+ || completedItems.size >= MAX_COMPLETED_OUTPUT_ITEMS
221
+ || aggregateItemBytes + sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES) {
222
+ taintAndRelease();
223
+ return;
224
+ }
225
+ budget?.chargeRetained(sourceBytes, { kind: "retained_collectors" });
226
+ completedItems.set(index, { item, sourceBytes, visibleToGrok });
227
+ aggregateItemBytes += sourceBytes;
228
+ hasVisibleOutput = hasVisibleOutput || visibleToGrok;
229
+ };
230
+
231
+ const closeOpenItem = (index: number): void => {
232
+ const open = openItems.get(index);
233
+ if (!open) return;
234
+ openItems.delete(index);
235
+ aggregateOpenItemBytes -= open.sourceBytes;
236
+ budget?.releaseRetained(open.sourceBytes, { kind: "retained_collectors" });
237
+ };
238
+
239
+ const rewrite: SseBlockRewrite = (block: string): readonly string[] => {
240
+ const payload = sseDataPayload(block);
241
+ if (payload === null) return [block];
242
+ if (payload === "[DONE]") {
243
+ reset();
244
+ return [block];
245
+ }
246
+
247
+ let parsed: unknown;
248
+ try {
249
+ parsed = JSON.parse(payload);
250
+ } catch {
251
+ taintAndRelease();
252
+ return [block];
253
+ }
254
+ if (!isPlainObject(parsed) || typeof parsed.type !== "string") {
255
+ taintAndRelease();
256
+ return [block];
257
+ }
258
+
259
+ const type = parsed.type;
260
+ const outputIndex = Number.isInteger(parsed.output_index) && (parsed.output_index as number) >= 0
261
+ ? parsed.output_index as number
262
+ : undefined;
263
+
264
+ if (type === "response.output_item.added") {
265
+ const open = isPlainObject(parsed.item) ? plausibleGrokOpenItem(parsed.item) : null;
266
+ if (outputIndex === undefined || !open
267
+ || openItems.has(outputIndex) || completedItems.has(outputIndex)
268
+ || openItems.size >= MAX_COMPLETED_OUTPUT_ITEMS) {
269
+ taintAndRelease();
270
+ } else if (!tainted) {
271
+ const sourceBytes = Buffer.byteLength(JSON.stringify(open), "utf8");
272
+ if (sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES
273
+ || aggregateOpenItemBytes + sourceBytes > MAX_GROK_OPEN_ITEM_IDENTITY_BYTES) {
274
+ taintAndRelease();
275
+ } else {
276
+ budget?.chargeRetained(sourceBytes, { kind: "retained_collectors" });
277
+ openItems.set(outputIndex, { ...open, sourceBytes });
278
+ aggregateOpenItemBytes += sourceBytes;
279
+ }
280
+ }
281
+ return [block];
282
+ }
283
+
284
+ if (type === "response.output_item.done") {
285
+ const item = isPlainObject(parsed.item) ? parsed.item : null;
286
+ const proof = item ? trustedGrokCompletedItem(item) : null;
287
+ if (outputIndex === undefined || !proof || completedItems.has(outputIndex)) {
288
+ taintAndRelease();
289
+ return [block];
290
+ }
291
+ const open = openItems.get(outputIndex);
292
+ const doneId = typeof item!.id === "string" ? item!.id : undefined;
293
+ if (open && (open.type !== item!.type || open.id !== doneId)) {
294
+ taintAndRelease();
295
+ return [block];
296
+ }
297
+ closeOpenItem(outputIndex);
298
+ retainCompletedItem(outputIndex, item!, proof.visibleToGrok);
299
+ return [block];
300
+ }
301
+
302
+ const isTerminal = type === "response.completed"
303
+ || type === "response.failed"
304
+ || type === "response.incomplete";
305
+ if (!isTerminal) return [block];
306
+
307
+ let out = block;
308
+ if (type === "response.completed" && !tainted && isPlainObject(parsed.response)) {
309
+ const response = parsed.response;
310
+ const output = response.output;
311
+ const terminalStatusConsistent = !("status" in response) || response.status === "completed";
312
+ const outputIsAuthoritative = Array.isArray(output) && output.length > 0;
313
+ const outputIsSparse = !("output" in response)
314
+ || (Array.isArray(output) && output.length === 0);
315
+ if (!outputIsAuthoritative && outputIsSparse && terminalStatusConsistent
316
+ && completedItems.size > 0 && openItems.size === 0 && hasVisibleOutput) {
317
+ const ordered = [...completedItems.entries()].sort(([left], [right]) => left - right);
318
+ if (ordered.every(([index], position) => index === position)) {
319
+ out = jsonBlock({
320
+ ...parsed,
321
+ response: { ...response, output: ordered.map(([, retained]) => retained.item) },
322
+ });
323
+ }
324
+ }
325
+ }
326
+ reset();
327
+ return [out];
328
+ };
329
+
330
+ rewrite.dispose = reset;
331
+ return rewrite;
332
+ }
333
+
@@ -183,7 +183,8 @@ export { disableResponsesRequestTimeout, linkAbortSignal } from "./responses";
183
183
  import { handleClaudeCountTokens, handleClaudeMessages } from "./claude-messages";
184
184
  import { handleChatCompletions } from "./chat-completions";
185
185
  import { anthropicErrorResponse } from "../claude/outbound";
186
- import { buildDesktop3pRegistry } from "../claude/desktop-3p";
186
+ import { buildDesktop3pRegistry, generateDesktop3pModels } from "../claude/desktop-3p";
187
+ import { buildDesktopDiscoveryInputs } from "../claude/desktop-discovery-inputs";
187
188
  import { runClaudeAuthModeMigration } from "../claude/auth-mode-migration";
188
189
  import {
189
190
  bindNativeMainStartupLifecycle,
@@ -1358,6 +1359,10 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1358
1359
  if (!isAllowedRequestOrigin(req, policy)) {
1359
1360
  return withCors(formatErrorResponse(403, "origin_rejected", "cross-origin data-plane request blocked"), req, policy);
1360
1361
  }
1362
+ const wantsDesktopConfig = url.searchParams.get("format") === "desktop-config";
1363
+ if (wantsDesktopConfig && (url.searchParams.get("ids") === "cli" || url.searchParams.has("client_version"))) {
1364
+ return jsonResponse({ error: "Desktop config format cannot use CLI or client-version selectors" }, 400, req, policy);
1365
+ }
1361
1366
  // The Integrations page reports whether a Cursor client has reached this proxy; the
1362
1367
  // recorder keeps only a bounded User-Agent value and a timestamp, in memory.
1363
1368
  recordCursorSeen(req.headers);
@@ -1380,7 +1385,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1380
1385
  }
1381
1386
  throw error;
1382
1387
  }
1383
- const { accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, buildCatalogEntries, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeContextLimits, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxOutputTokens, nativeOpenAiContextTier, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, orderForSubagents, filterCatalogVisibleModels, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
1388
+ const { accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, buildCatalogEntries, configuredNativeAliasSlugs, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, exactComboCatalogSlugs, loadCatalogTemplate, NATIVE_OPENAI_MODELS, nativeContextLimits, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxOutputTokens, nativeOpenAiContextTier, nativeOpenAiSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, uniqueCatalogModelsForRawPublicList, visibleCodexAccountSelectors, visibleNativeSlugs, desktopVisibleNativeSlugs } = await import("../codex/catalog");
1384
1389
  const { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } = await import("../codex/catalog/native-models");
1385
1390
  const includeNativeOpenAi = shouldIncludeNativeOpenAi(config);
1386
1391
  const includeAccountBoundNativeOpenAi = shouldIncludeAccountBoundNativeOpenAi(config);
@@ -1430,11 +1435,12 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1430
1435
  const accountNativeSlugs = [...new Set(
1431
1436
  [...accountNativeSlugsBySelector.values()].flatMap(slugs => [...slugs]),
1432
1437
  )];
1433
- const desktopNativeSlugs = desktopVisibleNativeSlugs(config).filter(slug => (
1434
- !ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(slug) || availableBareGatedNativeSlugs.has(slug)
1435
- ));
1436
- const goEnabled = filterCatalogVisibleModels(goModels, config);
1437
- const goOrdered = orderForSubagents(goEnabled, config.subagentModels);
1438
+ const desktopInputs = buildDesktopDiscoveryInputs({
1439
+ config, models: goModels, modelEntitlements,
1440
+ desktopNativeCandidates: desktopVisibleNativeSlugs(config),
1441
+ });
1442
+ const desktopNativeSlugs = desktopInputs.nativeSlugs;
1443
+ const goOrdered = desktopInputs.routedModels;
1438
1444
  // Claude Code / Claude Desktop gateway model discovery (GET /v1/models with
1439
1445
  // Anthropic-style headers; 003 G1-G8 + devlog 131). Entries use the official
1440
1446
  // ModelInfo shape incl. capabilities (effort ladder / thinking) — Desktop 3P can
@@ -1443,7 +1449,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1443
1449
  // aliases; legacy claude-ocx-* ids keep decoding via resolveAlias. Detection:
1444
1450
  // anthropic-version header (Claude Code sends it) or explicit ?flavor=anthropic.
1445
1451
  // Codex catalog (client_version) and the OpenAI list shape below stay byte-identical.
1446
- const wantsAnthropicList = req.headers.get("anthropic-version") !== null
1452
+ const wantsAnthropicList = wantsDesktopConfig || req.headers.get("anthropic-version") !== null
1447
1453
  || url.searchParams.get("flavor") === "anthropic";
1448
1454
  /**
1449
1455
  * Whether a NATIVE slug may carry a Fast sibling.
@@ -1477,12 +1483,22 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1477
1483
  catalogFastRowEligible(config, m);
1478
1484
 
1479
1485
  if (wantsAnthropicList && !url.searchParams.has("client_version")) {
1486
+ if (wantsDesktopConfig) {
1487
+ const models = config.claudeCode?.enabled === false ? [] : generateDesktop3pModels(
1488
+ desktopInputs.nativeSlugs, desktopInputs.routedModels,
1489
+ config.claudeCode?.desktopProfile, desktopInputs.nativeContextCap,
1490
+ );
1491
+ const response = jsonResponse({ version: 1, models }, 200, req, policy);
1492
+ response.headers.set("Cache-Control", "no-store");
1493
+ return response;
1494
+ }
1480
1495
  if (config.claudeCode?.enabled === false) return jsonResponse({ data: [] }, 200, req, policy);
1481
1496
  // Build Desktop 3P registry so inbound alias resolution works for subsequent requests.
1482
1497
  buildDesktop3pRegistry(
1483
1498
  desktopNativeSlugs,
1484
- goOrdered.map(m => ({ provider: m.provider, id: m.id, contextWindow: m.contextWindow })),
1499
+ desktopInputs.routedModels,
1485
1500
  config.claudeCode?.desktopProfile,
1501
+ desktopInputs.nativeContextCap,
1486
1502
  );
1487
1503
  const { buildAnthropicModelInfos } = await import("../claude/model-info");
1488
1504
  const { resolveAutoContext } = await import("../claude/context-windows");
@@ -1503,7 +1519,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1503
1519
  resolveAutoContext(config.claudeCode),
1504
1520
  idStyle,
1505
1521
  activeDesktop3pAlias,
1506
- nativeContextLimits(config),
1522
+ desktopInputs.nativeContextCap,
1507
1523
  config.fastMode,
1508
1524
  // Explicit opt-out omits the Fast predicate.
1509
1525
  config.fastRows !== false
@@ -0,0 +1,70 @@
1
+ import { currentAccountSelectionRevision, subscribeAccountSelections } from "../../lib/account-selection-events";
2
+ import { registerOptionalShutdownHook } from "../../lib/optional-shutdown-hooks";
3
+
4
+ const MAX_SELECTION_STREAMS = 64;
5
+ const HEARTBEAT_MS = 15_000;
6
+ const encoder = new TextEncoder();
7
+ const connections = new Set<() => void>();
8
+
9
+ /** The management boundary admits the request; every frame revalidates current authority. */
10
+ export function accountSelectionStream(request: Request, validate: () => boolean): Response {
11
+ const authorized = () => {
12
+ try { return validate() === true; } catch { return false; }
13
+ };
14
+ if (!authorized()) return Response.json({ error: "Management session is no longer authorized" }, { status: 401 });
15
+ if (connections.size >= MAX_SELECTION_STREAMS) {
16
+ return Response.json({ error: "Too many account selection streams" }, {
17
+ status: 429, headers: { "Retry-After": "15" },
18
+ });
19
+ }
20
+ let cleanup = () => {};
21
+ const body = new ReadableStream<Uint8Array>({
22
+ start(controller) {
23
+ let closed = false;
24
+ let unsubscribe = () => {};
25
+ let heartbeat: ReturnType<typeof setInterval> | undefined;
26
+ const close = () => {
27
+ if (closed) return;
28
+ closed = true;
29
+ unsubscribe();
30
+ if (heartbeat) clearInterval(heartbeat);
31
+ request.signal.removeEventListener("abort", close);
32
+ connections.delete(close);
33
+ try { controller.close(); } catch { /* The consumer may already have cancelled. */ }
34
+ };
35
+ cleanup = close;
36
+ const send = (frame: string) => {
37
+ if (closed) return;
38
+ if (!authorized()) {
39
+ // Error clears queued frames as well, so a revoked consumer cannot drain them.
40
+ try { controller.error(new DOMException("Management session is no longer authorized", "NotAllowedError")); }
41
+ finally { close(); }
42
+ return;
43
+ }
44
+ // Reconnection sends a ready event, so a slow reader can reconcile without an
45
+ // unbounded queue or silently dropping a provider's latest invalidation.
46
+ if (controller.desiredSize !== null && controller.desiredSize <= 0) { close(); return; }
47
+ try { controller.enqueue(encoder.encode(frame)); } catch { close(); }
48
+ };
49
+ connections.add(close);
50
+ registerOptionalShutdownHook("account-selection-streams", () => {
51
+ for (const finish of [...connections]) finish();
52
+ });
53
+ if (request.signal.aborted) { close(); return; }
54
+ request.signal.addEventListener("abort", close, { once: true });
55
+ unsubscribe = subscribeAccountSelections(event => {
56
+ send(`event: account-selection\ndata: ${JSON.stringify(event)}\n\n`);
57
+ });
58
+ send(`event: ready\ndata: ${JSON.stringify({ revision: currentAccountSelectionRevision() })}\n\n`);
59
+ if (closed) return;
60
+ heartbeat = setInterval(() => send(": heartbeat\n\n"), HEARTBEAT_MS);
61
+ heartbeat.unref?.();
62
+ },
63
+ cancel() { cleanup(); },
64
+ }, { highWaterMark: 16 });
65
+ return new Response(body, { headers: {
66
+ "Content-Type": "text/event-stream; charset=utf-8",
67
+ "Cache-Control": "no-cache, no-transform",
68
+ "X-Accel-Buffering": "no",
69
+ } });
70
+ }