@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.
- package/gui/dist/assets/{index-DW-DYWmz.js → index-DxJ7kXj9.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +42 -0
- package/src/adapters/client-fingerprint.ts +9 -5
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +17 -0
- package/src/adapters/cursor/cursor-errors.ts +49 -0
- package/src/adapters/cursor/live-models.ts +36 -2
- package/src/adapters/cursor/live-transport.ts +55 -4
- package/src/adapters/cursor/native-exec.ts +9 -0
- package/src/adapters/cursor/protobuf-request.ts +160 -9
- package/src/adapters/cursor/request-builder.ts +9 -1
- package/src/adapters/cursor/tool-definitions.ts +7 -2
- package/src/adapters/google-antigravity-wire.ts +1 -1
- package/src/adapters/google.ts +30 -12
- package/src/adapters/openai-responses-url.ts +5 -3
- package/src/adapters/registry.ts +3 -1
- package/src/adapters/tool-catalog-nudge.ts +76 -9
- package/src/bridge.ts +53 -9
- package/src/codex/app-server-processes.ts +69 -35
- package/src/codex/catalog/provider-fetch.ts +5 -1
- package/src/config.ts +1 -0
- package/src/generated/compatibility-version.json +40 -32
- package/src/lib/windows-elevation.ts +8 -2
- package/src/oauth/google-antigravity.ts +7 -2
- package/src/providers/antigravity-models.ts +126 -17
- package/src/providers/derive.ts +11 -1
- package/src/responses/parser.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +16 -1
- package/src/responses/thought-signature-replay.ts +17 -1
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +2 -10
- package/src/server/management/provider-routes.ts +22 -0
- package/src/server/request-log.ts +11 -3
- package/src/server/responses/core.ts +2 -0
- 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 ||
|
|
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
|
|
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:
|
|
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
|
-
? {
|
|
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
|
/**
|