@bitkyc08/opencodex 2.24.2 → 2.25.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 (37) hide show
  1. package/gui/dist/assets/{index-DW-DYWmz.js → index-DxJ7kXj9.js} +1 -1
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/anthropic.ts +42 -0
  5. package/src/adapters/client-fingerprint.ts +9 -5
  6. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  7. package/src/adapters/command-code.ts +17 -0
  8. package/src/adapters/cursor/cursor-errors.ts +49 -0
  9. package/src/adapters/cursor/live-models.ts +36 -2
  10. package/src/adapters/cursor/live-transport.ts +55 -4
  11. package/src/adapters/cursor/native-exec.ts +9 -0
  12. package/src/adapters/cursor/protobuf-request.ts +160 -9
  13. package/src/adapters/cursor/request-builder.ts +9 -1
  14. package/src/adapters/cursor/tool-definitions.ts +7 -2
  15. package/src/adapters/google-antigravity-wire.ts +1 -1
  16. package/src/adapters/google.ts +30 -12
  17. package/src/adapters/openai-responses-url.ts +5 -3
  18. package/src/adapters/registry.ts +3 -1
  19. package/src/adapters/tool-catalog-nudge.ts +76 -9
  20. package/src/bridge.ts +53 -9
  21. package/src/codex/app-server-processes.ts +69 -35
  22. package/src/codex/catalog/provider-fetch.ts +5 -1
  23. package/src/config.ts +1 -0
  24. package/src/generated/compatibility-version.json +40 -32
  25. package/src/lib/windows-elevation.ts +8 -2
  26. package/src/oauth/google-antigravity.ts +7 -2
  27. package/src/providers/antigravity-models.ts +126 -17
  28. package/src/providers/derive.ts +11 -1
  29. package/src/responses/parser.ts +4 -0
  30. package/src/responses/reasoning-replay-cache.ts +16 -1
  31. package/src/responses/thought-signature-replay.ts +17 -1
  32. package/src/responses/truncated-stop-reason.ts +60 -0
  33. package/src/router.ts +2 -10
  34. package/src/server/management/provider-routes.ts +22 -0
  35. package/src/server/request-log.ts +11 -3
  36. package/src/server/responses/core.ts +2 -0
  37. package/src/types.ts +13 -1
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Whether a `done` event's `stopReason` means the turn was cut short rather than finishing, and
3
+ * which Responses `incomplete_details.reason` it maps to.
4
+ *
5
+ * `stopReason` is an open-ended string and adapters do not agree on a vocabulary: openai-chat and
6
+ * google normalize to `max_tokens`/`content_filter`, Command Code forwards the raw provider or AI
7
+ * SDK value (`length`, `content-filter`, `error`), and Anthropic forwards `stop_reason` verbatim
8
+ * (`refusal`, `model_context_window_exceeded`, ...). A guard that matched only the two canonical
9
+ * strings let those turns read as completed — and, on a compaction turn, install a half-written
10
+ * summary as replacement history (#422).
11
+ *
12
+ * Classifying here keeps that decision independent of which adapter produced the event, and keeps
13
+ * suppression and terminal status in agreement: a turn whose compaction item is withheld must not
14
+ * also report success, or codex-rs receives a completed response with zero compaction items and
15
+ * fatals.
16
+ *
17
+ * Unknown reasons are deliberately NOT truncated. This must never turn a healthy turn into a
18
+ * failure, and an unrecognized value is far more likely an ordinary stop.
19
+ */
20
+ type TruncationKind = "max_output_tokens" | "content_filter";
21
+
22
+ const TRUNCATED_STOP_REASONS = new Map<string, TruncationKind>([
23
+ // canonical (openai-chat, google)
24
+ ["max_tokens", "max_output_tokens"],
25
+ ["content_filter", "content_filter"],
26
+ // raw OpenAI / Command Code (AI SDK) finish reasons
27
+ ["length", "max_output_tokens"],
28
+ ["content-filter", "content_filter"],
29
+ // raw Anthropic stop reasons
30
+ ["max_output_tokens", "max_output_tokens"],
31
+ ["model_context_window_exceeded", "max_output_tokens"],
32
+ ["refusal", "content_filter"],
33
+ // Anthropic documents `pause_turn` as a long-running turn that the client is expected to
34
+ // CONTINUE. Whatever was produced so far is by definition unfinished, so it must not be
35
+ // installed as replacement history.
36
+ ["pause_turn", "max_output_tokens"],
37
+ // raw Gemini / Vertex finish reasons
38
+ ["malformed_function_call", "content_filter"],
39
+ ["malformed_response", "content_filter"],
40
+ ["unexpected_tool_call", "content_filter"],
41
+ ["safety", "content_filter"],
42
+ ["recitation", "content_filter"],
43
+ ["blocklist", "content_filter"],
44
+ ["prohibited_content", "content_filter"],
45
+ ["spii", "content_filter"],
46
+ ["image_safety", "content_filter"],
47
+ ["language", "content_filter"],
48
+ // Kiro
49
+ ["model_context_window_exceeded_exception", "max_output_tokens"],
50
+ ]);
51
+
52
+ /** The `incomplete_details.reason` a truncated stop maps to, or undefined for a normal stop. */
53
+ export function truncationReasonFor(stopReason: string | undefined): TruncationKind | undefined {
54
+ if (stopReason === undefined) return undefined;
55
+ return TRUNCATED_STOP_REASONS.get(stopReason.trim().toLowerCase());
56
+ }
57
+
58
+ export function isTruncatedStopReason(stopReason: string | undefined): boolean {
59
+ return truncationReasonFor(stopReason) !== undefined;
60
+ }
package/src/router.ts CHANGED
@@ -12,7 +12,7 @@ import { hasOwnProvider, resolveEnvValue } from "./config";
12
12
  import { assertProviderDestinationAllowed } from "./lib/destination-policy";
13
13
  import { redactSecretString, redactUrlForLog } from "./lib/redact";
14
14
  import { PROVIDER_REGISTRY, providerCodexAccountMode } from "./providers/registry";
15
- import { applyDirectReasoningEffortContracts } from "./providers/derive";
15
+ import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive";
16
16
  import {
17
17
  providerMatchesRegistryTransportWithStaticGuards,
18
18
  providerSupportsLiveModelDiscovery,
@@ -286,14 +286,6 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
286
286
  const modelReasoningEffortMap = mergeNestedRecord(registryEntry.modelReasoningEffortMap, provider.modelReasoningEffortMap);
287
287
  const modelReasoningEfforts = mergeStringArrayRecord(registryEntry.modelReasoningEfforts, provider.modelReasoningEfforts);
288
288
  const modelDefaultReasoningEfforts = mergeRecordFill(registryEntry.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts);
289
- // Key-login used to persist this exact low-only ClinePass capability seed. Once the gateway's
290
- // wider input ladder was live-verified, leaving that generated row untouched would keep old
291
- // installs clamped forever. This branch is reached only after canonical transport matching, so
292
- // same-named custom destinations and every other explicit ladder still retain user precedence.
293
- const repairLegacyClinePassReasoningEfforts = providerName === "cline-pass"
294
- && provider.reasoningWireFormat === "gateway-object"
295
- && provider.reasoningEfforts?.length === 1
296
- && provider.reasoningEfforts[0] === "low";
297
289
  const modelContextWindows = providerName === OPENAI_API_PROVIDER_ID
298
290
  ? mergePositiveNumberCaps(registryEntry.modelContextWindows, provider.modelContextWindows)
299
291
  : mergeRecordFill(registryEntry.modelContextWindows, provider.modelContextWindows);
@@ -374,7 +366,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
374
366
  ...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
375
367
  ...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
376
368
  ...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
377
- ...((provider.reasoningEfforts === undefined || repairLegacyClinePassReasoningEfforts)
369
+ ...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider))
378
370
  && registryEntry.reasoningEfforts !== undefined
379
371
  ? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
380
372
  : {}),
@@ -33,6 +33,7 @@ import { replaceProviderAccountSet } from "../../oauth/store";
33
33
  import { providerDestinationResolvedError } from "../../lib/destination-policy";
34
34
  import { reconcileLiveStateStores } from "../../lib/state-store-registrations";
35
35
  import { ProviderOutboundPolicyError, providerOutboundGet, providerOutboundPost, providerRedirectError } from "../../lib/provider-outbound";
36
+ import { fetchCursorUsableModels } from "../../adapters/cursor/live-models";
36
37
  import { parseAntigravityAvailableModels } from "../../providers/antigravity-models";
37
38
  import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
38
39
  import { deriveProviderPresets } from "../../providers/derive";
@@ -732,6 +733,27 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
732
733
  if (prov.authMode === "oauth" && !apiKey) {
733
734
  return jsonResponse({ ok: false, latencyMs: 0, error: "static catalog only — upstream not verified (not logged in)" });
734
735
  }
736
+ if (prov.adapter === "cursor") {
737
+ const started = Date.now();
738
+ const live = await fetchCursorUsableModels({
739
+ apiKey: apiKey ?? "",
740
+ baseUrl: prov.baseUrl,
741
+ });
742
+ const latencyMs = Date.now() - started;
743
+ if (!live.ok) {
744
+ return jsonResponse({
745
+ ok: false,
746
+ latencyMs,
747
+ error: `cursor discovery ${live.error}${live.detail ? `: ${live.detail}` : ""}`,
748
+ });
749
+ }
750
+ return jsonResponse({
751
+ ok: true,
752
+ latencyMs,
753
+ models: live.models.length,
754
+ message: `Connected. ${live.models.length} models.`,
755
+ });
756
+ }
735
757
  const project = prov.project ?? snapshot?.projectId;
736
758
  if (antigravity && !project) {
737
759
  return jsonResponse({ ok: false, latencyMs: 0, error: "Antigravity project unavailable — re-run `ocx login google-antigravity`" });
@@ -1004,10 +1004,14 @@ function finalizedUsage(
1004
1004
  const usageFallback = !finalUsage && estimate !== undefined
1005
1005
  ? { inputTokens: estimate, outputTokens: 0, estimated: true }
1006
1006
  : undefined;
1007
- const loggedUsage = finalUsage && estimate !== undefined
1007
+ const combinedInputTokens = finalUsage && estimate !== undefined
1008
+ ? Math.max(finalUsage.inputTokens, estimate)
1009
+ : undefined;
1010
+ const loggedUsage = finalUsage && combinedInputTokens !== undefined
1008
1011
  ? {
1009
1012
  ...finalUsage,
1010
- inputTokens: Math.max(finalUsage.inputTokens, estimate),
1013
+ inputTokens: combinedInputTokens,
1014
+ totalTokens: combinedInputTokens + finalUsage.outputTokens,
1011
1015
  estimated: true,
1012
1016
  }
1013
1017
  : finalUsage
@@ -1017,7 +1021,11 @@ function finalizedUsage(
1017
1021
  // ESTIMATE via capEstimateAtContextWindow, and Math.max preserves a real
1018
1022
  // provider-reported count, so it needs no further reduction.
1019
1023
  ? (finalUsage.estimated && contextWindow !== undefined && finalUsage.inputTokens > contextWindow
1020
- ? { ...finalUsage, inputTokens: contextWindow }
1024
+ ? {
1025
+ ...finalUsage,
1026
+ inputTokens: contextWindow,
1027
+ totalTokens: contextWindow + finalUsage.outputTokens,
1028
+ }
1021
1029
  : finalUsage)
1022
1030
  : usageFallback;
1023
1031
  const totalTokens = usageTotalTokens(loggedUsage);
@@ -14,6 +14,7 @@ import {
14
14
  bindReasoningReplayScope,
15
15
  reasoningReplayCodexCredentialIdentity,
16
16
  reasoningReplayDestinationIdentity,
17
+ durableReplayDestinationIdentity,
17
18
  reasoningReplayKeyCredentialIdentity,
18
19
  reasoningReplayOAuthCredentialIdentity,
19
20
  } from "../../responses/reasoning-replay-cache";
@@ -336,6 +337,7 @@ function bindRouteReasoningReplayScope(args: {
336
337
  ? {
337
338
  providerName,
338
339
  providerDestinationIdentity,
340
+ providerDestinationDurableIdentity: durableReplayDestinationIdentity(provider.baseUrl),
339
341
  adapterName,
340
342
  modelId: parsed.modelId,
341
343
  credentialIdentity,
package/src/types.ts CHANGED
@@ -5,6 +5,11 @@ export interface OcxReasoningReplayIdentity {
5
5
  providerName: string;
6
6
  /** Opaque process-local digest of the exact upstream destination. */
7
7
  providerDestinationIdentity: string;
8
+ /**
9
+ * The same destination, digested WITHOUT the process-local random key, so it can key a
10
+ * durable store. Absent when no base URL was resolvable.
11
+ */
12
+ providerDestinationDurableIdentity?: string;
8
13
  adapterName: string;
9
14
  modelId: string;
10
15
  /** Opaque process-local credential identity; never a raw token or API key. */
@@ -1392,8 +1397,15 @@ export interface OcxProviderConfig {
1392
1397
  * HTTP/2 streaming responses (issue #1668). "http1.1" / "h1" forces HTTP/1.1,
1393
1398
  * "http2" / "h2" forces HTTP/2. Absent or "auto" keeps Bun's default negotiation
1394
1399
  * (current behavior unchanged). Only meaningful for https: base URLs.
1395
- */
1400
+ */
1396
1401
  upstreamHttpVersion?: UpstreamHttpVersion;
1402
+ /**
1403
+ * Google only. When `false`, the AI Studio (direct) path sends Gemini Flash ids
1404
+ * unchanged to the wire instead of applying the `-tiered` suffix (`gemini-3.7-flash`
1405
+ * -> `gemini-3.7-flash-tiered`). Set this to `false` when the configured upstream still
1406
+ * serves the bare ids. Absent (default) keeps the rename.
1407
+ */
1408
+ directGeminiWireRenames?: boolean;
1397
1409
  /** Keep provider settings on disk but exclude it from routing and model/catalog listings. */
1398
1410
  disabled?: boolean;
1399
1411
  /**