@bitkyc08/opencodex 2.7.40 → 2.7.41

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 (110) hide show
  1. package/README.md +63 -12
  2. package/assets/claude-code-models.gif +0 -0
  3. package/gui/dist/assets/index-B2J4t3te.css +1 -0
  4. package/gui/dist/assets/index-BmvM6wRb.js +65 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +3 -2
  7. package/src/adapters/google.ts +48 -9
  8. package/src/adapters/kiro-events.ts +15 -3
  9. package/src/adapters/kiro.ts +292 -28
  10. package/src/adapters/openai-chat.ts +63 -15
  11. package/src/adapters/openai-responses.ts +18 -0
  12. package/src/bridge.ts +76 -21
  13. package/src/chat/outbound.ts +66 -34
  14. package/src/claude/auth-detect.ts +229 -0
  15. package/src/claude/auth-mode-migration.ts +32 -0
  16. package/src/claude/auth-mode.ts +62 -0
  17. package/src/claude/desktop-3p-guard.ts +35 -0
  18. package/src/claude/desktop-3p.ts +121 -21
  19. package/src/claude/desktop-health.ts +26 -0
  20. package/src/claude/desktop-profile.ts +263 -0
  21. package/src/claude/inbound-debug.ts +4 -0
  22. package/src/claude/model-info.ts +9 -3
  23. package/src/cli/account-extended.ts +34 -0
  24. package/src/cli/account.ts +3 -1
  25. package/src/cli/claude-desktop.ts +152 -0
  26. package/src/cli/claude.ts +37 -3
  27. package/src/cli/doctor.ts +187 -6
  28. package/src/cli/help.ts +13 -1
  29. package/src/cli/index.ts +151 -57
  30. package/src/cli/status-oauth.ts +68 -0
  31. package/src/cli/status.ts +4 -0
  32. package/src/codex/account-lifecycle.ts +31 -0
  33. package/src/codex/auth-api.ts +133 -26
  34. package/src/codex/auth-collision.ts +55 -11
  35. package/src/codex/auth-context.ts +50 -6
  36. package/src/codex/catalog/provider-fetch.ts +6 -2
  37. package/src/codex/home.ts +61 -1
  38. package/src/codex/inject.ts +28 -66
  39. package/src/codex/injected-marker.ts +72 -0
  40. package/src/codex/journal.ts +39 -3
  41. package/src/codex/main-account-cache.ts +25 -0
  42. package/src/codex/model-cache.ts +20 -1
  43. package/src/codex/paths.ts +5 -0
  44. package/src/codex/routing.ts +138 -11
  45. package/src/codex/subagent-model-fallback.ts +455 -0
  46. package/src/codex/sync.ts +17 -0
  47. package/src/combos/failover.ts +10 -2
  48. package/src/combos/index.ts +1 -0
  49. package/src/combos/types.ts +9 -0
  50. package/src/config.ts +117 -0
  51. package/src/grok/inject.ts +339 -0
  52. package/src/grok/status.ts +88 -0
  53. package/src/grok/sync.ts +66 -0
  54. package/src/lib/destination-policy.ts +13 -0
  55. package/src/lib/errors.ts +59 -0
  56. package/src/lib/privacy.ts +9 -0
  57. package/src/lib/process-control.ts +57 -4
  58. package/src/oauth/health.ts +375 -0
  59. package/src/oauth/index.ts +69 -22
  60. package/src/oauth/kiro.ts +5 -3
  61. package/src/oauth/log.ts +48 -0
  62. package/src/oauth/store.ts +55 -6
  63. package/src/providers/alibaba-region-backup.ts +75 -0
  64. package/src/providers/alibaba-region-migration.ts +143 -0
  65. package/src/providers/alibaba-region-startup.ts +36 -0
  66. package/src/providers/api-keys.ts +5 -5
  67. package/src/providers/derive.ts +22 -1
  68. package/src/providers/free-directory.ts +181 -0
  69. package/src/providers/key-failover.ts +2 -2
  70. package/src/providers/kiro-models.ts +3 -2
  71. package/src/providers/openai-tiers.ts +1 -1
  72. package/src/providers/provider-id-rewrite.ts +150 -0
  73. package/src/providers/registry.ts +49 -2
  74. package/src/responses/parser.ts +49 -20
  75. package/src/responses/state.ts +156 -2
  76. package/src/router.ts +73 -5
  77. package/src/server/chat-completions.ts +78 -22
  78. package/src/server/claude-messages.ts +8 -0
  79. package/src/server/images.ts +2 -1
  80. package/src/server/index.ts +50 -30
  81. package/src/server/live.ts +2 -1
  82. package/src/server/management/agent-settings-routes.ts +337 -18
  83. package/src/server/management/api-access.ts +141 -0
  84. package/src/server/management/combo-routes.ts +3 -3
  85. package/src/server/management/config-routes.ts +4 -4
  86. package/src/server/management/logs-usage-routes.ts +64 -4
  87. package/src/server/management/model-routes.ts +114 -8
  88. package/src/server/management/oauth-account-routes.ts +31 -5
  89. package/src/server/management/provider-routes.ts +42 -11
  90. package/src/server/management/shared.ts +81 -2
  91. package/src/server/management/system-routes.ts +6 -1
  92. package/src/server/management-api.ts +24 -7
  93. package/src/server/port-reclaim.ts +261 -0
  94. package/src/server/request-log.ts +11 -5
  95. package/src/server/responses/collaboration.ts +11 -6
  96. package/src/server/responses/compact.ts +2 -1
  97. package/src/server/responses/core.ts +358 -155
  98. package/src/server/responses/passthrough-error.ts +53 -0
  99. package/src/server/search.ts +2 -1
  100. package/src/server/system-env.ts +23 -6
  101. package/src/server/windows-tcp-drop.ts +174 -0
  102. package/src/service.ts +43 -3
  103. package/src/types.ts +74 -6
  104. package/src/update/index.ts +30 -19
  105. package/src/update/job.ts +30 -15
  106. package/src/usage/log.ts +158 -2
  107. package/src/usage/summary.ts +8 -4
  108. package/src/web-search/loop.ts +4 -1
  109. package/gui/dist/assets/index-CMip1DzF.css +0 -1
  110. package/gui/dist/assets/index-cydcmbzC.js +0 -52
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-cydcmbzC.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-CMip1DzF.css">
19
+ <script type="module" crossorigin src="/assets/index-BmvM6wRb.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-B2J4t3te.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.7.40",
3
+ "version": "2.7.41",
4
4
  "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -21,6 +21,7 @@
21
21
  "gui/dist",
22
22
  "assets/banner.png",
23
23
  "assets/architecture.png",
24
+ "assets/claude-code-models.gif",
24
25
  "assets/codex-app-picker.png",
25
26
  "README.md",
26
27
  "LICENSE"
@@ -89,7 +90,7 @@
89
90
  "type": "git",
90
91
  "url": "git+https://github.com/lidge-jun/opencodex.git"
91
92
  },
92
- "homepage": "https://opencodex.me/",
93
+ "homepage": "https://lidge-jun.github.io/opencodex/",
93
94
  "bugs": {
94
95
  "url": "https://github.com/lidge-jun/opencodex/issues"
95
96
  },
@@ -87,6 +87,35 @@ function toolResultImageParts(content: string | OcxContentPart[]): unknown[] {
87
87
  return parts;
88
88
  }
89
89
 
90
+ /**
91
+ * Antigravity translates these Gemini `contents` into Anthropic `messages` for Claude models, and
92
+ * Anthropic rejects a text block whose `text` is empty or absent. An empty Gemini text part reaches
93
+ * that upstream as `{"type":"text"}` — a proto3 empty string is omitted from the translated JSON —
94
+ * and 400s with `messages.N.content.M.text.text: Field required` (issue #420). An empty `parts: []`
95
+ * model turn fails the same way. Gemini itself accepts both shapes, which is why this only ever
96
+ * surfaced on Claude-on-Antigravity; the guard lives here because this is where the parts are
97
+ * built. Mirrors the Anthropic adapter's own empty-block guard (src/adapters/anthropic.ts).
98
+ */
99
+ const GEMINI_EMPTY_PLACEHOLDER = "(empty)";
100
+ const GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER = "(empty tool output)";
101
+
102
+ /** A Gemini text part, or undefined when the value cannot form a valid non-empty text block. */
103
+ function geminiTextPart(text: unknown): { text: string } | undefined {
104
+ return typeof text === "string" && text.length > 0 ? { text } : undefined;
105
+ }
106
+
107
+ /**
108
+ * Text for `functionResponse.response.result`. `contentPartsToText` collapses an empty array — or one
109
+ * holding only empty text — to its "[image]" marker, which would claim an image the turn does not
110
+ * actually carry (`toolResultImageParts` adds none). Fall back to the placeholder unless the content
111
+ * has something representable.
112
+ */
113
+ function geminiToolResultText(content: string | OcxContentPart[]): string {
114
+ if (typeof content === "string") return content || GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
115
+ const hasContent = content.some(p => p.type === "image" || (typeof p.text === "string" && p.text.length > 0));
116
+ return hasContent ? contentPartsToText(content) : GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER;
117
+ }
118
+
90
119
  function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?: unknown; contents: unknown[] } {
91
120
  // Neutralize Codex's GPT-5 identity line (Gemini/Antigravity share this path) so a routed model
92
121
  // never misreports as GPT-5/OpenAI, and never leaks the proxy identity upstream.
@@ -105,18 +134,22 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
105
134
  case "user":
106
135
  case "developer": {
107
136
  if (typeof msg.content === "string") {
108
- contents.push({ role: "user", parts: [{ text: msg.content }] });
137
+ contents.push({ role: "user", parts: [{ text: msg.content || GEMINI_EMPTY_PLACEHOLDER }] });
109
138
  } else {
110
- const parts = (msg.content as OcxContentPart[]).map(p => {
139
+ const parts: unknown[] = [];
140
+ for (const p of msg.content as OcxContentPart[]) {
111
141
  if (p.type === "image") {
112
142
  const data = parseDataUrl(p.imageUrl);
113
143
  // Gemini takes base64 via inline_data; a remote URL needs a mime type we don't have, so
114
144
  // fall back to a short marker rather than inlining the URL as a huge text blob.
115
- return data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[image: ${p.imageUrl}]` };
145
+ parts.push(data ? { inline_data: { mime_type: data.mediaType, data: data.base64 } } : { text: `[image: ${p.imageUrl}]` });
146
+ continue;
116
147
  }
117
- return { text: p.text };
118
- });
119
- contents.push({ role: "user", parts });
148
+ // Drop empty/malformed text instead of emitting `{ text: "" }` or a bare `{}` part.
149
+ const textPart = geminiTextPart(p.text);
150
+ if (textPart) parts.push(textPart);
151
+ }
152
+ contents.push({ role: "user", parts: parts.length > 0 ? parts : [{ text: GEMINI_EMPTY_PLACEHOLDER }] });
120
153
  }
121
154
  break;
122
155
  }
@@ -124,8 +157,10 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
124
157
  const aMsg = msg as OcxAssistantMessage;
125
158
  const parts: unknown[] = [];
126
159
  for (const p of aMsg.content) {
127
- if (p.type === "text") parts.push({ text: (p as OcxTextContent).text });
128
- else if (p.type === "toolCall") {
160
+ if (p.type === "text") {
161
+ const textPart = geminiTextPart((p as OcxTextContent).text);
162
+ if (textPart) parts.push(textPart);
163
+ } else if (p.type === "toolCall") {
129
164
  const tc = p as OcxToolCall;
130
165
  // Preserve the thought signature on the function-call part so Antigravity/Gemini-3
131
166
  // reasoning continuity survives history-driven (stateless) turns, not just same-process
@@ -142,6 +177,10 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
142
177
  parts.push(part);
143
178
  }
144
179
  }
180
+ // A turn with nothing Gemini can represent (e.g. thinking-only) would serialize as
181
+ // `parts: []`, which the Anthropic translation rejects. Skip it, as the Anthropic
182
+ // adapter does for its own empty assistant content.
183
+ if (parts.length === 0) break;
145
184
  contents.push({ role: "model", parts });
146
185
  break;
147
186
  }
@@ -151,7 +190,7 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
151
190
  // tool-result screenshots (e.g. Computer Use) ride along as inline_data instead of being
152
191
  // flattened to a "[image]" marker the model can't actually see.
153
192
  const responseId = geminiToolCallId(msg.toolCallId);
154
- const functionResponse: Record<string, unknown> = { name: namespacedToolName(msg.toolNamespace, msg.toolName), response: { result: contentPartsToText(msg.content) } };
193
+ const functionResponse: Record<string, unknown> = { name: namespacedToolName(msg.toolNamespace, msg.toolName), response: { result: geminiToolResultText(msg.content) } };
155
194
  // Mirror the matching functionCall id so Claude-on-Antigravity can pair this result with its
156
195
  // `tool_use` block (-> Anthropic `tool_result.tool_use_id`).
157
196
  if (responseId !== undefined) functionResponse.id = responseId;
@@ -6,7 +6,7 @@ export type ParsedKiroEvent =
6
6
  | { type: "reasoning"; data?: string }
7
7
  | { type: "tool"; name?: string; toolUseId?: string; input?: string; stop?: boolean }
8
8
  | { type: "truncation"; data: string }
9
- | { type: "metadata"; usage?: OcxUsage; contextUsagePercentage?: number }
9
+ | { type: "metadata"; usage?: OcxUsage; contextUsagePercentage?: number; stopReason?: string }
10
10
  | { type: "message_metadata"; conversationId?: string }
11
11
  | { type: "invalid_state"; message?: string }
12
12
  | { type: "error"; reason?: string; message?: string };
@@ -89,8 +89,18 @@ export function parseKiroEvent(eventType: string, payload: Uint8Array): ParsedKi
89
89
  // Unknown event types are intentionally ignored without parsing or logging their payload.
90
90
  if (!KNOWN_EVENT_TYPES.has(eventType)) return null;
91
91
  const parsed = parseObject(eventType, payload);
92
- const truncationReason = kiroTruncationReason(parsed);
93
- if (truncationReason) return { type: "truncation", data: truncationReason };
92
+ // A metadataEvent's `stopReason` is Kiro's own terminal verdict and must reach the parser
93
+ // intact. The generic truncation sniffer matches substrings ("max_tokens", "length",
94
+ // "context_length") across several keys, so it would swallow MAX_TOKENS before the switch
95
+ // below ever runs. Gate on position rather than on the value: a value allowlist would keep
96
+ // eating future reasons such as LENGTH_LIMIT, which matches the pattern today.
97
+ const nativeStopReason = eventType === "metadataEvent"
98
+ ? optionalString(eventType, parsed, "stopReason")
99
+ : undefined;
100
+ if (nativeStopReason === undefined) {
101
+ const truncationReason = kiroTruncationReason(parsed);
102
+ if (truncationReason) return { type: "truncation", data: truncationReason };
103
+ }
94
104
 
95
105
  switch (eventType) {
96
106
  case "assistantResponseEvent":
@@ -141,12 +151,14 @@ export function parseKiroEvent(eventType: string, payload: Uint8Array): ParsedKi
141
151
  ) {
142
152
  return malformed(eventType, "contextUsagePercentage must be a finite number");
143
153
  }
154
+ const stopReason = optionalString(eventType, parsed, "stopReason");
144
155
  return {
145
156
  type: "metadata",
146
157
  ...(parseTokenUsage(eventType, parsed.tokenUsage) !== undefined
147
158
  ? { usage: parseTokenUsage(eventType, parsed.tokenUsage) }
148
159
  : {}),
149
160
  ...(typeof contextUsagePercentage === "number" ? { contextUsagePercentage } : {}),
161
+ ...(stopReason !== undefined ? { stopReason } : {}),
150
162
  };
151
163
  }
152
164
  case "invalidStateEvent":