@yansigit/opencodex 2.33.0 → 2.33.1

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 (62) hide show
  1. package/gui/dist/assets/index-CIDo4y4k.js +102 -0
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +3 -1
  4. package/src/adapters/command-code.ts +101 -20
  5. package/src/adapters/cursor/envelope-echo.ts +162 -0
  6. package/src/adapters/cursor/live-transport.ts +3 -1
  7. package/src/adapters/cursor/native-exec-fs.ts +13 -12
  8. package/src/adapters/cursor/native-exec-network.ts +3 -5
  9. package/src/adapters/cursor/native-exec-policy.ts +47 -0
  10. package/src/adapters/cursor/native-exec-shell.ts +13 -25
  11. package/src/adapters/cursor/native-exec.ts +18 -10
  12. package/src/adapters/cursor/protobuf-events.ts +28 -2
  13. package/src/adapters/cursor/protobuf-request.ts +20 -3
  14. package/src/adapters/cursor/request-builder.ts +7 -0
  15. package/src/adapters/cursor/tool-definitions.ts +22 -1
  16. package/src/adapters/cursor/tool-result-normalize.ts +21 -8
  17. package/src/adapters/cursor/types.ts +7 -0
  18. package/src/adapters/cursor.ts +114 -0
  19. package/src/adapters/google-aistudio-parser.ts +49 -0
  20. package/src/adapters/google.ts +108 -16
  21. package/src/adapters/openai-responses.ts +1 -0
  22. package/src/chat/inbound.ts +15 -0
  23. package/src/cli/index.ts +1 -1
  24. package/src/codex/catalog/provider-fetch.ts +34 -0
  25. package/src/generated/compatibility-version.json +91 -47
  26. package/src/generated/model-metadata.ts +3 -0
  27. package/src/oauth/aistudio-native-daemon.ts +62 -0
  28. package/src/oauth/aistudio-session-sync.ts +95 -0
  29. package/src/oauth/google-aistudio-auth.ts +98 -0
  30. package/src/oauth/key-providers.ts +8 -0
  31. package/src/oauth/login-cli.ts +66 -1
  32. package/src/providers/derive.ts +1 -1
  33. package/src/providers/quota.ts +90 -38
  34. package/src/providers/registry.ts +24 -3
  35. package/src/router.ts +3 -0
  36. package/src/routing/account-pool/cooldown.ts +8 -0
  37. package/src/routing/account-pool/index.ts +1 -0
  38. package/src/server/aistudio-ws-hub.ts +295 -0
  39. package/src/server/auth-cors.ts +1 -0
  40. package/src/server/chat-completions.ts +2 -0
  41. package/src/server/index.ts +94 -0
  42. package/src/server/management/logs-usage-routes.ts +11 -5
  43. package/src/server/management/oauth-account-routes.ts +13 -3
  44. package/src/server/port-reclaim.ts +19 -1
  45. package/src/server/request-log-conversation.ts +12 -0
  46. package/src/server/request-log.ts +2 -1
  47. package/src/server/responses/core.ts +4 -3
  48. package/src/server/responses/policy-fallback.ts +1 -1
  49. package/src/server/ws-bridge.ts +2 -1
  50. package/src/smoke/fingerprint-cache.ts +133 -0
  51. package/src/smoke/live-scenarios.ts +33 -0
  52. package/src/smoke/runner.ts +119 -0
  53. package/src/types/provider.ts +2 -1
  54. package/src/types/request.ts +2 -0
  55. package/src/types/tools.ts +31 -7
  56. package/src/usage/command-code-manifest.ts +116 -0
  57. package/src/usage/cost.ts +2 -2
  58. package/src/usage/expected-prices.ts +83 -0
  59. package/src/usage/log.ts +2 -2
  60. package/src/usage/summary.ts +34 -12
  61. package/src/web-search/index.ts +16 -8
  62. package/gui/dist/assets/index-DKLr4LTE.js +0 -102
@@ -48,6 +48,10 @@ import {
48
48
  import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
49
49
  import { configuredReasoningEfforts, mapReasoningEffort } from "../reasoning-effort";
50
50
  import { normalizeAntigravityProviderError } from "../oauth/antigravity-routing";
51
+ import { buildAiStudioHeaders, parseGoogleCookieJar } from "../oauth/google-aistudio-auth";
52
+ import { cookieHeaderFromSession, loadAiStudioSession } from "../oauth/aistudio-session-sync";
53
+ import { parseMakerSuiteChunk } from "./google-aistudio-parser";
54
+ import { globalAiStudioRelayHub } from "../server/aistudio-ws-hub";
51
55
 
52
56
  const INLINE_ERROR_URL_USERINFO = /https?:\/\/[^\s"'<>]*@/gi;
53
57
 
@@ -540,6 +544,21 @@ function googlePartThoughtSignature(part: GoogleResponsePart): string | undefine
540
544
  return typeof nested === "string" && nested.length > 0 ? nested : undefined;
541
545
  }
542
546
 
547
+ function ensureThoughtSignatureBypassSentinel(contents: unknown[], modelId?: string): void {
548
+ if (modelId && !/gemini-(?:3.7|2.5)|thinking/i.test(modelId)) return;
549
+ for (const c of contents as { role?: string; parts?: unknown[] }[]) {
550
+ if (c?.role !== "model" || !Array.isArray(c.parts)) continue;
551
+ for (const p of c.parts) {
552
+ if (p && typeof p === "object") {
553
+ const partObj = p as Record<string, unknown>;
554
+ if (partObj.functionCall && !partObj.thoughtSignature && !partObj.thought_signature) {
555
+ partObj.thoughtSignature = ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL;
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+
543
562
  /**
544
563
  * Carry a Gemini thought signature with the exact function-call part that produced it. Google
545
564
  * validates the signature against that specific part, so it must ride the individual tool call
@@ -744,7 +763,46 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
744
763
  // Direct AI-Studio uses the canonical server transport (fetchWithTransientRetry), which
745
764
  // retries transient 5xx responses through providerFetch while preserving multi-key pool
746
765
  // 429 rotation and raw error formatting.
747
- ...(provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
766
+ ...(provider.googleMode === "ai-studio-web"
767
+ ? {
768
+ fetchResponse: async (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> => {
769
+ if (globalAiStudioRelayHub.hasActiveSessions()) {
770
+ const streamRes = await globalAiStudioRelayHub.dispatchStream(
771
+ {
772
+ url: request.url,
773
+ method: request.method,
774
+ headers: request.headers,
775
+ body: request.body,
776
+ },
777
+ ctx?.abortSignal,
778
+ );
779
+ const encoder = new TextEncoder();
780
+ const bodyStream = new ReadableStream({
781
+ async start(controller) {
782
+ try {
783
+ for await (const chunk of streamRes.chunks) {
784
+ controller.enqueue(encoder.encode(chunk));
785
+ }
786
+ controller.close();
787
+ } catch (err) {
788
+ controller.error(err);
789
+ }
790
+ },
791
+ });
792
+ return new Response(bodyStream, {
793
+ status: 200,
794
+ headers: { "Content-Type": "text/event-stream" },
795
+ });
796
+ }
797
+ return fetch(request.url, {
798
+ method: request.method,
799
+ headers: request.headers,
800
+ body: request.body,
801
+ signal: ctx?.abortSignal,
802
+ });
803
+ },
804
+ }
805
+ : provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
748
806
  ? {
749
807
  fetchResponse: (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> =>
750
808
  (provider.googleMode === "cloud-code-assist" ? fetchAntigravityWithRetry : fetchVertexWithRetry)(request, ctx),
@@ -882,19 +940,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
882
940
  const strippedModelTail = /claude/i.test(wireModelId) ? stripTrailingClaudePrefill(contents) : false;
883
941
  if (antigravityUsesReplayCache(wireModelId)) {
884
942
  applyAntigravityReplay(wireModelId, sessionId, contents);
885
- // If any functionCall still lacks a thoughtSignature on Gemini Antigravity,
886
- // supply the bypass sentinel so Antigravity does not reject the turn with HTTP 400.
887
- for (const c of contents as { role?: string; parts?: unknown[] }[]) {
888
- if (c?.role !== "model" || !Array.isArray(c.parts)) continue;
889
- for (const p of c.parts) {
890
- if (p && typeof p === "object") {
891
- const partObj = p as Record<string, unknown>;
892
- if (partObj.functionCall && !partObj.thoughtSignature && !partObj.thought_signature) {
893
- partObj.thoughtSignature = ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL;
894
- }
895
- }
896
- }
897
- }
943
+ ensureThoughtSignatureBypassSentinel(contents);
898
944
  } else {
899
945
  sanitizeAntigravityClaudeSignatures(contents);
900
946
  }
@@ -970,6 +1016,22 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
970
1016
  return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
971
1017
  }
972
1018
 
1019
+ if (provider.googleMode === "ai-studio-web") {
1020
+ const base = (provider.baseUrl || "https://alkalimakersuite-pa.clients6.google.com").replace(/\/+$/, "");
1021
+ const url = `${base}/v1internal:${method}${streamParam}`;
1022
+ const cookieInput = provider.apiKey || provider.headers?.["Cookie"] || cookieHeaderFromSession(loadAiStudioSession()) || "";
1023
+ const jar = parseGoogleCookieJar(cookieInput);
1024
+ const aiStudioHeaders = await buildAiStudioHeaders(jar, "https://aistudio.google.com");
1025
+ Object.assign(headers, aiStudioHeaders);
1026
+ const compiled = compileGoogleWireBody({ ...body, model: routedModelId });
1027
+ restoreGoogleToolName = compiled.restoreToolName;
1028
+ if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
1029
+ ensureThoughtSignatureBypassSentinel((compiled.body as { contents: unknown[] }).contents, routedModelId);
1030
+ }
1031
+ emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
1032
+ return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
1033
+ }
1034
+
973
1035
  // ai-studio (default): Generative Language API + x-goog-api-key.
974
1036
  const url = `${provider.baseUrl}/v1beta/models/${routedModelId}:${method}${streamParam}`;
975
1037
  const apiKey = provider.apiKey?.trim();
@@ -978,6 +1040,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
978
1040
 
979
1041
  const compiled = compileGoogleWireBody(body);
980
1042
  restoreGoogleToolName = compiled.restoreToolName;
1043
+ if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
1044
+ ensureThoughtSignatureBypassSentinel((compiled.body as { contents: unknown[] }).contents, routedModelId);
1045
+ }
981
1046
  emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
982
1047
  return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
983
1048
  },
@@ -1280,6 +1345,16 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1280
1345
  if (result === "content") sawContentEvent = true;
1281
1346
  continue;
1282
1347
  }
1348
+ if (provider.googleMode === "ai-studio-web") {
1349
+ const parsed = parseMakerSuiteChunk(line);
1350
+ if (parsed.text) {
1351
+ sawAnyFrame = true;
1352
+ sawTerminalSignal = true;
1353
+ sawContentEvent = true;
1354
+ yield { type: "text_delta", text: parsed.text };
1355
+ continue;
1356
+ }
1357
+ }
1283
1358
  sawLiveness = true;
1284
1359
  if (line.startsWith(":") || !line.trim()) continue;
1285
1360
  debugDroppedFrame("google", line);
@@ -1292,7 +1367,24 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1292
1367
  if (residual.startsWith(":")) {
1293
1368
  yield { type: "heartbeat" };
1294
1369
  } else if (!residual.startsWith("data:")) {
1295
- yield { type: "error", message: "upstream stream ended with an incomplete SSE frame — possible truncation" };
1370
+ try {
1371
+ const parsedErr = JSON.parse(residual);
1372
+ if (parsedErr.error?.message) {
1373
+ yield { type: "error", message: parsedErr.error.message };
1374
+ return;
1375
+ }
1376
+ } catch (err) {
1377
+ void err;
1378
+ }
1379
+ if (provider.googleMode === "ai-studio-web") {
1380
+ const parsed = parseMakerSuiteChunk(residual);
1381
+ if (parsed.text) {
1382
+ yield { type: "text_delta", text: parsed.text };
1383
+ yield { type: "done" };
1384
+ return;
1385
+ }
1386
+ }
1387
+ yield { type: "error", message: `upstream non-SSE response: ${residual.slice(0, 300)}` };
1296
1388
  return;
1297
1389
  } else if ((yield* handleDataLine(residual)) === "terminate") return;
1298
1390
  }
@@ -1344,7 +1436,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1344
1436
  // buffered adapter entry point, so collect the exact same events parseStream emits
1345
1437
  // instead of maintaining a second CCA JSON parser.
1346
1438
  const isSse = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
1347
- if (provider.googleMode === "cloud-code-assist" && isSse) {
1439
+ if ((provider.googleMode === "cloud-code-assist" && isSse) || provider.googleMode === "ai-studio-web") {
1348
1440
  const events: AdapterEvent[] = [];
1349
1441
  let previousTail: AdapterEvent | undefined;
1350
1442
  try {
@@ -38,6 +38,7 @@ export const FORWARD_HEADERS = [
38
38
  "x-codex-beta-features",
39
39
  "x-codex-installation-id",
40
40
  "x-codex-parent-thread-id",
41
+ "x-session-id",
41
42
  "x-codex-turn-metadata",
42
43
  "x-codex-turn-state",
43
44
  "x-codex-window-id",
@@ -10,6 +10,21 @@ export class ChatCompletionsRequestError extends Error {}
10
10
  type Rec = Record<string, unknown>;
11
11
  type ChatCompletionsRoutingBody = Rec & { model: string; messages: unknown[] };
12
12
 
13
+ /** Session/thread headers the Chat -> Responses bridge must preserve for provider affinity. */
14
+ export const CHAT_RESPONSES_SESSION_HEADERS = [
15
+ "session_id",
16
+ "session-id",
17
+ "x-session-id",
18
+ "thread-id",
19
+ ] as const;
20
+
21
+ export function copyChatResponsesSessionHeaders(source: Headers, target: Headers): void {
22
+ for (const name of CHAT_RESPONSES_SESSION_HEADERS) {
23
+ const value = source.get(name);
24
+ if (value) target.set(name, value);
25
+ }
26
+ }
27
+
13
28
  function isRec(v: unknown): v is Rec {
14
29
  return !!v && typeof v === "object" && !Array.isArray(v);
15
30
  }
package/src/cli/index.ts CHANGED
@@ -164,7 +164,7 @@ async function chooseListenPort(requestedPort?: number): Promise<number> {
164
164
  // Ghost LISTEN rows with a dead PID can outlive the process for a while.
165
165
  // SetTcpEntry(DELETE_TCB) needs elevation (often returns 317), so the only
166
166
  // reliable non-admin recovery is to wait for the OS to release the TCB.
167
- timeoutMs: 60_000,
167
+ timeoutMs: process.platform === "win32" ? 60_000 : 10_000,
168
168
  intervalMs: 100,
169
169
  scanIntervalMs: 500,
170
170
  killOcxHolders: false,
@@ -47,6 +47,7 @@ import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
47
47
  import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
48
48
  import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
49
49
  import { isCanonicalOpenAiForwardProvider, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers";
50
+ import { globalAiStudioRelayHub } from "../../server/aistudio-ws-hub";
50
51
  import {
51
52
  COMBO_NAMESPACE,
52
53
  comboModelId,
@@ -1245,6 +1246,39 @@ async function fetchProviderModelsWithAuth(
1245
1246
  }
1246
1247
  return merged;
1247
1248
  };
1249
+ if (prov.googleMode === "ai-studio-web") {
1250
+ clearProviderDiscoveryStatus(name);
1251
+ if (globalAiStudioRelayHub.hasActiveSessions()) {
1252
+ try {
1253
+ const streamRes = await globalAiStudioRelayHub.dispatchStream({
1254
+ url: "https://generativelanguage.googleapis.com/v1beta/models",
1255
+ method: "GET",
1256
+ });
1257
+ let rawBody = "";
1258
+ for await (const chunk of streamRes.chunks) {
1259
+ rawBody += chunk;
1260
+ }
1261
+ const json = JSON.parse(rawBody);
1262
+ if (Array.isArray(json?.models)) {
1263
+ const liveModels: CatalogModel[] = json.models.map((m: any) => {
1264
+ const rawId = typeof m.name === "string" ? m.name.replace(/^models\//, "") : "";
1265
+ return {
1266
+ id: rawId,
1267
+ provider: name,
1268
+ ...catalogHintsFromProviderConfig(name, prov, rawId, contextCap),
1269
+ };
1270
+ }).filter((m: any) => Boolean(m.id));
1271
+ if (liveModels.length > 0) {
1272
+ return observed(withConfiguredRetention(liveModels), "authoritative");
1273
+ }
1274
+ }
1275
+ } catch {
1276
+ /* fallback to configured models */
1277
+ }
1278
+ }
1279
+ return observed(configured, "authoritative");
1280
+ }
1281
+
1248
1282
  // Static catalogs never need an OAuth refresh or an upstream model request. Clear any
1249
1283
  // discovery failure left by an older live configuration even when the account is logged out.
1250
1284
  if (prov.liveModels === false) {