@bitkyc08/opencodex 2.39.0 → 2.40.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/README.md +49 -21
- package/gui/dist/assets/index-BHe2rl_C.js +112 -0
- package/gui/dist/assets/index-CJSb3HPe.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/cursor/catalog.ts +140 -7
- package/src/adapters/cursor/discovery.ts +79 -87
- package/src/adapters/cursor/effort-map.ts +11 -0
- package/src/adapters/cursor/request-builder.ts +31 -4
- package/src/adapters/cursor.ts +17 -0
- package/src/adapters/openai-chat.ts +95 -9
- package/src/adapters/openai-responses.ts +73 -9
- package/src/bridge.ts +58 -13
- package/src/claude/auth-mode.ts +6 -9
- package/src/claude/gateway-cache.ts +23 -3
- package/src/claude/model-info.ts +13 -2
- package/src/cli/access.ts +30 -0
- package/src/cli/account-extended.ts +19 -10
- package/src/cli/capabilities.ts +40 -1
- package/src/cli/claude-agent-startup-sync.ts +3 -0
- package/src/cli/claude.ts +134 -36
- package/src/cli/connect.ts +226 -0
- package/src/cli/dispatch.ts +126 -21
- package/src/cli/gui-pair-client.ts +170 -0
- package/src/cli/gui.ts +87 -0
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +76 -22
- package/src/cli/models-runtime-subcommands.ts +34 -0
- package/src/cli/models-runtime.ts +4 -0
- package/src/cli/models.ts +2 -1
- package/src/cli/opencode.ts +2 -1
- package/src/cli/provider-runtime.ts +31 -1
- package/src/cli/registry.ts +32 -2
- package/src/cli/runtime-api.ts +10 -1
- package/src/cli/status.ts +26 -0
- package/src/cli/system-command.ts +9 -3
- package/src/client/connect.ts +650 -0
- package/src/client/hub-client.ts +457 -0
- package/src/client/hub-relay.ts +288 -0
- package/src/client/machine-api.ts +139 -0
- package/src/client/machine-auth.ts +54 -0
- package/src/client/machine-listener.ts +143 -0
- package/src/client/runtime.ts +93 -0
- package/src/client/state.ts +175 -0
- package/src/clients/config-export.ts +16 -5
- package/src/codex/auth-api.ts +169 -15
- package/src/codex/auth-context.ts +61 -6
- package/src/codex/catalog/aggregation.ts +17 -13
- package/src/codex/catalog/metadata.ts +28 -0
- package/src/codex/catalog/provider-fetch.ts +38 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/desired-state.ts +13 -3
- package/src/codex/inject.ts +212 -31
- package/src/codex/journal.ts +54 -6
- package/src/codex/main-account.ts +105 -33
- package/src/codex/native-main-claim.ts +23 -1
- package/src/codex/native-residue.ts +7 -0
- package/src/codex/reset-credit-auto-redeem.ts +237 -0
- package/src/combos/request.ts +15 -3
- package/src/combos/types.ts +12 -0
- package/src/config/paths.ts +15 -1
- package/src/config/provider-validation.ts +40 -0
- package/src/config.ts +447 -3
- package/src/generated/compatibility-version.json +224 -108
- package/src/images/artifacts.ts +73 -37
- package/src/images/fulfill.ts +6 -1
- package/src/images/index.ts +1 -1
- package/src/images/plan.ts +16 -2
- package/src/images/synthetic-tool.ts +5 -0
- package/src/images/xai-client.ts +34 -1
- package/src/integrations/cursor-detect.ts +133 -0
- package/src/integrations/cursor-seen.ts +31 -0
- package/src/lib/errors.ts +18 -0
- package/src/lib/gui-pair-capability.ts +104 -0
- package/src/lib/lab-live-route-production.ts +2 -1
- package/src/lib/process-control.ts +4 -3
- package/src/lib/service-secrets.ts +161 -1
- package/src/lib/windows-system-proxy.ts +115 -0
- package/src/oauth/index.ts +3 -2
- package/src/oauth/pool-settings-capability.ts +55 -0
- package/src/providers/api-keys.ts +3 -1
- package/src/providers/codex-capacity.ts +34 -4
- package/src/providers/derive.ts +10 -0
- package/src/providers/fastwire.ts +10 -2
- package/src/providers/key-store.ts +197 -0
- package/src/providers/model-rename-migration.ts +3 -0
- package/src/providers/openai-sidecar.ts +3 -3
- package/src/providers/quota.ts +91 -42
- package/src/providers/registry.ts +33 -3
- package/src/reasoning-effort.ts +32 -0
- package/src/remote/protocol.ts +109 -0
- package/src/responses/citation-markers.ts +101 -0
- package/src/responses/parser.ts +43 -3
- package/src/responses/spill-store.ts +9 -4
- package/src/router.ts +72 -8
- package/src/server/auth-cors.ts +37 -1
- package/src/server/gui-session.ts +449 -0
- package/src/server/gui-static.ts +24 -7
- package/src/server/images.ts +237 -10
- package/src/server/index.ts +356 -30
- package/src/server/management/agent-settings-routes.ts +7 -1
- package/src/server/management/api-key-rotation.ts +74 -0
- package/src/server/management/combo-routes.ts +18 -6
- package/src/server/management/config-routes.ts +22 -3
- package/src/server/management/context.ts +5 -1
- package/src/server/management/cursor-integration-routes.ts +98 -0
- package/src/server/management/logs-usage-routes.ts +2 -1
- package/src/server/management/model-routes.ts +79 -1
- package/src/server/management/model-rows.ts +23 -1
- package/src/server/management/oauth-account-routes.ts +135 -2
- package/src/server/management/provider-routes.ts +143 -9
- package/src/server/management/route-registry.ts +10 -0
- package/src/server/management/session-routes.ts +13 -0
- package/src/server/management/system-routes.ts +13 -1
- package/src/server/management-api.ts +8 -3
- package/src/server/management-auth.ts +124 -82
- package/src/server/models-capabilities.ts +124 -0
- package/src/server/ports.ts +7 -0
- package/src/server/proxy-liveness.ts +7 -0
- package/src/server/request-log.ts +11 -5
- package/src/server/responses/agent-task-recovery.ts +1 -0
- package/src/server/responses/codex-auth-error.ts +3 -1
- package/src/server/responses/collaboration.ts +26 -1
- package/src/server/responses/combo-stream-preflight.ts +25 -6
- package/src/server/responses/compact.ts +32 -12
- package/src/server/responses/context-overflow.ts +49 -0
- package/src/server/responses/core.ts +196 -12
- package/src/server/responses/fetch-helpers.ts +2 -1
- package/src/server/responses/outbound-body-guard.ts +110 -0
- package/src/server/responses/ws-upstream.ts +87 -6
- package/src/server/responses-self-named-namespace-scrub.ts +181 -0
- package/src/server/system-env.ts +78 -25
- package/src/service.ts +218 -42
- package/src/types/config.ts +134 -0
- package/src/types/provider.ts +54 -1
- package/src/types/request.ts +2 -0
- package/src/types.ts +5 -0
- package/src/update/job.ts +33 -5
- package/src/usage/cost.ts +32 -1
- package/src/usage/expected-prices.ts +16 -0
- package/src/usage/log.ts +6 -2
- package/src/usage/summary.ts +48 -6
- package/gui/dist/assets/index-D-lchsPw.js +0 -112
- package/gui/dist/assets/index-uvENYLin.css +0 -1
|
@@ -180,13 +180,40 @@ function catalogLimitNote(kept: readonly OcxTool[], omitted: readonly OcxTool[])
|
|
|
180
180
|
: `[opencodex] Cursor's transport limit allows ${kept.length} of ${kept.length + omitted.length} client tools this turn. Omitted and unavailable this turn: ${omittedSummary}.`;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* True when this turn should take Cursor's fast variant.
|
|
185
|
+
*
|
|
186
|
+
* Reads the tier DECISION rather than the raw caller field so one authority owns precedence:
|
|
187
|
+
* `decideTier` has already applied config `fastMode`, the caller's `service_tier`, and the
|
|
188
|
+
* route's eligibility, so `fastMode: false` correctly suppresses a caller's Fast request.
|
|
189
|
+
* A `{kind:"set"}` decision on a Cursor route means canonical Fast survived that gate.
|
|
190
|
+
*/
|
|
191
|
+
export function cursorFastRequested(parsed: OcxParsedRequest): boolean {
|
|
192
|
+
return parsed.options.tierDecision?.kind === "set";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Whether the wire this request will carry expresses the fast variant, for tier telemetry.
|
|
197
|
+
*
|
|
198
|
+
* Recomputed from the same pure inputs the builder uses rather than read off a built
|
|
199
|
+
* request: `tierLogForRunTurn` runs BEFORE `runTurn` (server/responses/core.ts), and
|
|
200
|
+
* `createCursorRequest` is not pure — it mints conversation ids — so rebuilding there would
|
|
201
|
+
* report a request that was never sent.
|
|
202
|
+
*/
|
|
203
|
+
export function cursorRequestEmitsFastVariant(parsed: OcxParsedRequest): boolean {
|
|
204
|
+
if (!cursorFastRequested(parsed)) return false;
|
|
205
|
+
const model = normalizeCursorModelId(parsed.modelId, parsed.options.reasoning, true);
|
|
206
|
+
return model.modelId.endsWith("-fast")
|
|
207
|
+
|| (model.requestedModelParameters ?? []).some(p => p.id === "fast" && p.value === "true");
|
|
208
|
+
}
|
|
209
|
+
|
|
183
210
|
/**
|
|
184
211
|
* Resolve a `cursor/<model>` selection + Codex reasoning effort to Cursor's requested model shape.
|
|
185
212
|
* Most models encode effort in a flat id (`claude-4.6-opus-high`). Grok Fast is parameterized
|
|
186
213
|
* instead: current Cursor clients send the matching Grok base id plus `effort` and `fast` parameters.
|
|
187
214
|
* A fully-qualified id (one that is not a known effort base) passes through unchanged.
|
|
188
215
|
*/
|
|
189
|
-
function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
216
|
+
function normalizeCursorModelId(modelId: string, reasoning?: string, fast?: boolean): {
|
|
190
217
|
modelId: string;
|
|
191
218
|
requestedModelParameters?: readonly CursorRequestedModelParameter[];
|
|
192
219
|
routingLevel?: CursorRoutingLevel;
|
|
@@ -201,7 +228,7 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
|
201
228
|
const id = selection.modelId;
|
|
202
229
|
// Grok Fast stays parameterized: current Cursor clients send the base id
|
|
203
230
|
// plus effort/fast parameters instead of the flattened -fast id.
|
|
204
|
-
const grokFast = cursorGrokFastSelection(id, reasoning);
|
|
231
|
+
const grokFast = cursorGrokFastSelection(id, reasoning, fast);
|
|
205
232
|
if (grokFast) {
|
|
206
233
|
return {
|
|
207
234
|
...selection,
|
|
@@ -212,7 +239,7 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
|
212
239
|
],
|
|
213
240
|
};
|
|
214
241
|
}
|
|
215
|
-
const resolved = resolveCursorSelection(id, reasoning);
|
|
242
|
+
const resolved = resolveCursorSelection(id, reasoning, undefined, { fast });
|
|
216
243
|
return {
|
|
217
244
|
...selection,
|
|
218
245
|
...(resolved.maxMode ? { maxMode: true } : {}),
|
|
@@ -455,7 +482,7 @@ export function createCursorRequest(
|
|
|
455
482
|
const visibleTools = cursorToolsForActivePrompt(parsed.context.tools, activeText, parsed.options.toolChoice);
|
|
456
483
|
const budget = applyCursorToolBudget(visibleTools, parsed.options.toolChoice);
|
|
457
484
|
const limitNote = catalogLimitNote(budget.tools, budget.omitted);
|
|
458
|
-
const model = normalizeCursorModelId(parsed.modelId, parsed.options.reasoning);
|
|
485
|
+
const model = normalizeCursorModelId(parsed.modelId, parsed.options.reasoning, cursorFastRequested(parsed));
|
|
459
486
|
const request: CursorRunRequest = {
|
|
460
487
|
modelId: model.modelId,
|
|
461
488
|
...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
|
package/src/adapters/cursor.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
cursorClientThreadOwner,
|
|
13
13
|
cursorCoveredPrefixDigest,
|
|
14
14
|
cursorInstructionDigest,
|
|
15
|
+
cursorRequestEmitsFastVariant,
|
|
15
16
|
} from "./cursor/request-builder";
|
|
16
17
|
import {
|
|
17
18
|
createLiveCursorTransport,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
invalidateCursorCheckpoint,
|
|
27
28
|
} from "./cursor/checkpoint-store";
|
|
28
29
|
import { debugProviderDiagnostic } from "../lib/debug";
|
|
30
|
+
import { createAdapterTierMetadata } from "../providers/fastwire";
|
|
29
31
|
import { estimateTokens } from "../lib/token-estimate";
|
|
30
32
|
import { rememberCursorThreadConversation } from "./cursor/thread-continuity";
|
|
31
33
|
import { runCursorTurnWithRetry } from "./cursor/transport-retry";
|
|
@@ -100,6 +102,21 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
|
|
|
100
102
|
return {
|
|
101
103
|
name: "cursor",
|
|
102
104
|
|
|
105
|
+
// Cursor emits Fast as a model variant, so the generic "no field emitted" fallback in
|
|
106
|
+
// adapters/registry.ts would report every Fast turn as downgraded. This recomputes the
|
|
107
|
+
// variant from the same pure inputs the builder uses: tierLogForRunTurn runs BEFORE
|
|
108
|
+
// runTurn, and createCursorRequest mints conversation ids, so rebuilding it here would
|
|
109
|
+
// describe a request that was never sent.
|
|
110
|
+
tierLogForRunTurn(parsed) {
|
|
111
|
+
const fast = cursorRequestEmitsFastVariant(parsed);
|
|
112
|
+
return createAdapterTierMetadata(
|
|
113
|
+
parsed.options.tierObservation,
|
|
114
|
+
parsed.options.tierDecision,
|
|
115
|
+
fast ? "cursor-variant" : null,
|
|
116
|
+
fast ? "fast" : null,
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
|
|
103
120
|
buildRequest() {
|
|
104
121
|
return {
|
|
105
122
|
url: provider.baseUrl || CURSOR_API_URL,
|
|
@@ -323,6 +323,39 @@ function reasoningTextFrom(record: Record<string, unknown>): string | undefined
|
|
|
323
323
|
: undefined;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
interface ReasoningDetailSegment {
|
|
327
|
+
key: string;
|
|
328
|
+
text: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Structured `reasoning_details` array (MiniMax M-series with `reasoning_split`).
|
|
333
|
+
* Each segment's key scopes cumulative-snapshot tracking: upstream repeats the
|
|
334
|
+
* full text-so-far under a stable `id`/`index` instead of sending increments.
|
|
335
|
+
*/
|
|
336
|
+
function reasoningDetailSegmentsFrom(record: Record<string, unknown>): ReasoningDetailSegment[] {
|
|
337
|
+
const raw = record.reasoning_details;
|
|
338
|
+
if (!Array.isArray(raw)) return [];
|
|
339
|
+
const segments: ReasoningDetailSegment[] = [];
|
|
340
|
+
for (let i = 0; i < raw.length; i++) {
|
|
341
|
+
const item: unknown = raw[i];
|
|
342
|
+
if (!isRecord(item)) continue;
|
|
343
|
+
if (typeof item.text !== "string" || item.text.length === 0) continue;
|
|
344
|
+
const key = typeof item.id === "string" && item.id.length > 0
|
|
345
|
+
? `id:${item.id}`
|
|
346
|
+
: typeof item.index === "number"
|
|
347
|
+
? `i:${item.index}`
|
|
348
|
+
: `n:${i}`;
|
|
349
|
+
segments.push({ key, text: item.text });
|
|
350
|
+
}
|
|
351
|
+
return segments;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Single-segment `reasoning_details` entry for replaying preserved reasoning (MiniMax wire shape). */
|
|
355
|
+
function reasoningDetailSegmentForWire(text: string): Record<string, unknown> {
|
|
356
|
+
return { type: "reasoning.text", id: "reasoning-text-1", format: "MiniMax-response-v1", index: 0, text };
|
|
357
|
+
}
|
|
358
|
+
|
|
326
359
|
function invalidChoicesEvent(usage?: OcxUsage): Extract<AdapterEvent, { type: "error" }> {
|
|
327
360
|
return {
|
|
328
361
|
type: "error",
|
|
@@ -766,9 +799,17 @@ function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderCon
|
|
|
766
799
|
}
|
|
767
800
|
}
|
|
768
801
|
if (reasoningContent.length > 0 && modelInList(provider.preserveReasoningContentModels, parsed.modelId)) {
|
|
769
|
-
|
|
802
|
+
// MiniMax's interleaved-thinking contract requires the structured
|
|
803
|
+
// reasoning_details array back on the next turn; a reasoning_content
|
|
804
|
+
// string is the native-format pass-back the docs mark unsupported.
|
|
805
|
+
if (modelInList(provider.reasoningDetailsModels, parsed.modelId)) {
|
|
806
|
+
chatMsg.reasoning_details = [reasoningDetailSegmentForWire(reasoningContent)];
|
|
807
|
+
} else {
|
|
808
|
+
chatMsg.reasoning_content = reasoningContent;
|
|
809
|
+
}
|
|
770
810
|
}
|
|
771
|
-
|
|
811
|
+
const hasReplayedReasoning = chatMsg.reasoning_content !== undefined || chatMsg.reasoning_details !== undefined;
|
|
812
|
+
if (chatMsg.content === undefined && toolCalls.length === 0 && !hasReplayedReasoning) break;
|
|
772
813
|
flushPendingToolCalls();
|
|
773
814
|
const wireToolCalls = toolCalls.map(tc => {
|
|
774
815
|
let id = tc.id;
|
|
@@ -784,7 +825,7 @@ function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderCon
|
|
|
784
825
|
}));
|
|
785
826
|
if (!chatMsg.content) chatMsg.content = emptyAssistantContent(provider);
|
|
786
827
|
}
|
|
787
|
-
if (
|
|
828
|
+
if (hasReplayedReasoning && chatMsg.content === undefined && chatMsg.tool_calls === undefined) {
|
|
788
829
|
chatMsg.content = emptyAssistantContent(provider);
|
|
789
830
|
}
|
|
790
831
|
out.push(chatMsg);
|
|
@@ -829,10 +870,15 @@ function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderCon
|
|
|
829
870
|
&& modelInList(provider.requiresReasoningPlaceholderModels ?? provider.preserveReasoningContentModels, parsed.modelId)
|
|
830
871
|
? " "
|
|
831
872
|
: undefined);
|
|
873
|
+
const orphanReasoningFields: Record<string, unknown> = !orphanReasoning
|
|
874
|
+
? {}
|
|
875
|
+
: modelInList(provider.reasoningDetailsModels, parsed.modelId)
|
|
876
|
+
? { reasoning_details: [reasoningDetailSegmentForWire(orphanReasoning)] }
|
|
877
|
+
: { reasoning_content: orphanReasoning };
|
|
832
878
|
out.push({
|
|
833
879
|
role: "assistant",
|
|
834
880
|
content: emptyAssistantContent(provider),
|
|
835
|
-
...
|
|
881
|
+
...orphanReasoningFields,
|
|
836
882
|
tool_calls: [{
|
|
837
883
|
id: toolCallId,
|
|
838
884
|
type: "function",
|
|
@@ -1393,12 +1439,14 @@ function canSerializeOpenAIChatServiceTier(
|
|
|
1393
1439
|
}
|
|
1394
1440
|
|
|
1395
1441
|
export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
1442
|
+
let lastRequestedModelId: string | undefined;
|
|
1396
1443
|
return {
|
|
1397
1444
|
name: "openai-chat",
|
|
1398
1445
|
|
|
1399
1446
|
formatErrorBody: formatOpenAIChatErrorBody,
|
|
1400
1447
|
|
|
1401
1448
|
buildRequest(parsed: OcxParsedRequest) {
|
|
1449
|
+
lastRequestedModelId = parsed.modelId;
|
|
1402
1450
|
const { url, headers, hasCredential } = openAIChatTransport(provider);
|
|
1403
1451
|
const messages = frameAgentRouterMessages(provider.baseUrl, messagesToChatFormat(parsed, provider));
|
|
1404
1452
|
const tools = toolsToChatFormatForProvider(parsed, provider);
|
|
@@ -1444,10 +1492,18 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
1444
1492
|
}
|
|
1445
1493
|
if (parsed.options.stopSequences !== undefined) body.stop = parsed.options.stopSequences;
|
|
1446
1494
|
const reasoningDisabled = modelInList(provider.noReasoningModels, parsed.modelId);
|
|
1447
|
-
|
|
1495
|
+
// Some gateways accept a reasoning-effort field on a plain turn but reject the
|
|
1496
|
+
// effort + tools combination. `noReasoningModels` would fix that only by
|
|
1497
|
+
// stripping reasoning everywhere, costing the model its whole picker. This keeps
|
|
1498
|
+
// the ladder advertised and drops the wire field for tool-bearing requests only.
|
|
1499
|
+
const omitReasoningEffortWithTools = !!tools
|
|
1500
|
+
&& modelInList(provider.omitReasoningEffortWithToolsModels, parsed.modelId);
|
|
1501
|
+
const reasoningEffort = omitReasoningEffortWithTools
|
|
1502
|
+
? undefined
|
|
1503
|
+
: mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
1448
1504
|
const nativeOpenAI = isNativeOpenAIChatTarget(provider);
|
|
1449
1505
|
let reasoningLog: AdapterRequest["reasoningLog"];
|
|
1450
|
-
if (!reasoningDisabled && provider.reasoningWireFormat === "gateway-object" && parsed.options.reasoning === "none") {
|
|
1506
|
+
if (!reasoningDisabled && !omitReasoningEffortWithTools && provider.reasoningWireFormat === "gateway-object" && parsed.options.reasoning === "none") {
|
|
1451
1507
|
if (nativeOpenAI) {
|
|
1452
1508
|
body.reasoning_effort = "none";
|
|
1453
1509
|
reasoningLog = {
|
|
@@ -1666,6 +1722,14 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
1666
1722
|
let pendingUsage: OcxUsage | undefined;
|
|
1667
1723
|
let finishReason: string | undefined;
|
|
1668
1724
|
let sawUserFacingOutput = false;
|
|
1725
|
+
// MiniMax-style structured reasoning: each stream chunk repeats a detail's
|
|
1726
|
+
// full text-so-far, so deltas are derived by prefix-diffing per segment key.
|
|
1727
|
+
// A piece that does not extend the previous snapshot is appended whole, which
|
|
1728
|
+
// keeps incremental senders parseable on the same path.
|
|
1729
|
+
const reasoningDetailSnapshots = new Map<string, string>();
|
|
1730
|
+
// Gate on the routed model, not list length: a mixed openai-chat provider
|
|
1731
|
+
// can list MiniMax ids without putting every sibling on MiniMax semantics.
|
|
1732
|
+
const reasoningDetailsOptIn = modelInList(provider.reasoningDetailsModels, lastRequestedModelId ?? "");
|
|
1669
1733
|
|
|
1670
1734
|
const handleDataLine = function* (line: string): Generator<AdapterEvent, "continue" | "terminate"> {
|
|
1671
1735
|
const rawPayload = sseFieldValue(line, "data");
|
|
@@ -1722,8 +1786,23 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
1722
1786
|
if (typeof choice.finish_reason === "string" && choice.finish_reason) finishReason = choice.finish_reason;
|
|
1723
1787
|
const delta = choice.delta;
|
|
1724
1788
|
if (delta) {
|
|
1725
|
-
const
|
|
1726
|
-
if (
|
|
1789
|
+
const detailSegments = reasoningDetailsOptIn ? reasoningDetailSegmentsFrom(delta) : [];
|
|
1790
|
+
if (detailSegments.length > 0) {
|
|
1791
|
+
for (const segment of detailSegments) {
|
|
1792
|
+
const prev = reasoningDetailSnapshots.get(segment.key) ?? "";
|
|
1793
|
+
if (segment.text === prev) continue;
|
|
1794
|
+
if (segment.text.startsWith(prev)) {
|
|
1795
|
+
reasoningDetailSnapshots.set(segment.key, segment.text);
|
|
1796
|
+
yield { type: "reasoning_raw_delta", text: segment.text.slice(prev.length) };
|
|
1797
|
+
} else {
|
|
1798
|
+
reasoningDetailSnapshots.set(segment.key, prev + segment.text);
|
|
1799
|
+
yield { type: "reasoning_raw_delta", text: segment.text };
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
} else {
|
|
1803
|
+
const reasoningText = reasoningTextFrom(delta);
|
|
1804
|
+
if (reasoningText !== undefined) yield { type: "reasoning_raw_delta", text: reasoningText };
|
|
1805
|
+
}
|
|
1727
1806
|
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
1728
1807
|
sawUserFacingOutput = true;
|
|
1729
1808
|
yield { type: "text_delta", text: delta.content };
|
|
@@ -2015,7 +2094,14 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
2015
2094
|
}
|
|
2016
2095
|
|
|
2017
2096
|
const msg = rawMessage as Record<string, unknown>;
|
|
2018
|
-
|
|
2097
|
+
let reasoningText = reasoningTextFrom(msg);
|
|
2098
|
+
if (reasoningText === undefined && modelInList(provider.reasoningDetailsModels, lastRequestedModelId ?? "")) {
|
|
2099
|
+
// MiniMax split-reasoning responses carry the same thinking in both
|
|
2100
|
+
// reasoning_content and reasoning_details; the array is the fallback
|
|
2101
|
+
// when only the structured form arrives.
|
|
2102
|
+
const segments = reasoningDetailSegmentsFrom(msg);
|
|
2103
|
+
if (segments.length > 0) reasoningText = segments.map(s => s.text).join("");
|
|
2104
|
+
}
|
|
2019
2105
|
if (reasoningText !== undefined) events.push({ type: "reasoning_raw_delta", text: reasoningText });
|
|
2020
2106
|
if (typeof msg.content === "string") events.push({ type: "text_delta", text: msg.content });
|
|
2021
2107
|
const rawToolCalls = msg.tool_calls;
|
|
@@ -467,7 +467,12 @@ function normalizeConfiguredReasoningSummaryDelivery(
|
|
|
467
467
|
* namespace, tool_search, web_search, custom) plus extensions (defer_loading,
|
|
468
468
|
* parallel_tool_calls, tool_search_call/output items). Spark's serving path only
|
|
469
469
|
* supports flat function tools and hosted web_search. This function:
|
|
470
|
-
* - Flattens namespace tools → promotes inner functions to top level
|
|
470
|
+
* - Flattens MCP-style namespace tools → promotes inner functions to top level. The reserved
|
|
471
|
+
* `functions` group is kept as a group (#3217): Codex 0.147+ sends every ordinary client tool
|
|
472
|
+
* inside it on Responses Lite, the backend accepts the group as-is, and flattening it changes
|
|
473
|
+
* what the backend answers with — a `custom_tool_call` carrying `namespace: "exec"`, which
|
|
474
|
+
* codex-rs concatenates into the unroutable `execexec`. Traced on a live proxy: with the
|
|
475
|
+
* group intact the same backend returns the bare `exec` call and the turn completes.
|
|
471
476
|
* - Drops unsupported tool types (tool_search, custom)
|
|
472
477
|
* - Strips defer_loading from function tools
|
|
473
478
|
* - Strips namespace from input items
|
|
@@ -482,12 +487,39 @@ function stripSparkCompatibility(body: unknown): unknown {
|
|
|
482
487
|
let changed = false;
|
|
483
488
|
|
|
484
489
|
const SPARK_SAFE_TOOL_TYPES = new Set(["function", "web_search", "web_search_preview"]);
|
|
490
|
+
// Inside the reserved group Codex sends freeform `custom` tools (code-mode `exec`) and the
|
|
491
|
+
// backend accepts them there; the top-level "drop custom" rule stays for flattened groups.
|
|
492
|
+
const SPARK_SAFE_FUNCTIONS_GROUP_CHILD_TYPES = new Set(["function", "custom"]);
|
|
493
|
+
const filterSparkFunctionsGroup = (group: Record<string, unknown>): Record<string, unknown> | undefined => {
|
|
494
|
+
if (!Array.isArray(group.tools)) return undefined;
|
|
495
|
+
let groupChanged = false;
|
|
496
|
+
const children: unknown[] = [];
|
|
497
|
+
for (const child of group.tools) {
|
|
498
|
+
if (!isPlainObject(child) || typeof child.type !== "string" || !SPARK_SAFE_FUNCTIONS_GROUP_CHILD_TYPES.has(child.type)) {
|
|
499
|
+
groupChanged = true;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (child.type === "function" && "defer_loading" in child) {
|
|
503
|
+
const { defer_loading: _, ...rest } = child;
|
|
504
|
+
groupChanged = true;
|
|
505
|
+
children.push(rest);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
children.push(child);
|
|
509
|
+
}
|
|
510
|
+
if (children.length === 0) return undefined;
|
|
511
|
+
return groupChanged ? { ...group, tools: children } : group;
|
|
512
|
+
};
|
|
485
513
|
|
|
486
514
|
let tools = body.tools;
|
|
487
515
|
if (Array.isArray(tools)) {
|
|
488
516
|
const flattened: unknown[] = [];
|
|
489
517
|
for (const t of tools) {
|
|
490
|
-
if (isPlainObject(t) && t.type === "namespace") {
|
|
518
|
+
if (isPlainObject(t) && t.type === "namespace" && t.name === SPARK_RESERVED_FUNCTIONS_NAMESPACE) {
|
|
519
|
+
const kept = filterSparkFunctionsGroup(t);
|
|
520
|
+
if (kept !== t) changed = true;
|
|
521
|
+
if (kept) flattened.push(kept);
|
|
522
|
+
} else if (isPlainObject(t) && t.type === "namespace") {
|
|
491
523
|
changed = true;
|
|
492
524
|
if (Array.isArray(t.tools)) {
|
|
493
525
|
for (const inner of t.tools) flattened.push(inner);
|
|
@@ -527,7 +559,11 @@ function stripSparkCompatibility(body: unknown): unknown {
|
|
|
527
559
|
const innerTools = item.tools as unknown[];
|
|
528
560
|
const filteredInner: unknown[] = [];
|
|
529
561
|
for (const t of innerTools) {
|
|
530
|
-
if (isPlainObject(t) && t.type === "namespace") {
|
|
562
|
+
if (isPlainObject(t) && t.type === "namespace" && t.name === SPARK_RESERVED_FUNCTIONS_NAMESPACE) {
|
|
563
|
+
const kept = filterSparkFunctionsGroup(t);
|
|
564
|
+
if (kept !== t) changed = true;
|
|
565
|
+
if (kept) filteredInner.push(kept);
|
|
566
|
+
} else if (isPlainObject(t) && t.type === "namespace") {
|
|
531
567
|
changed = true;
|
|
532
568
|
if (Array.isArray(t.tools)) {
|
|
533
569
|
for (const fn of t.tools) filteredInner.push(fn);
|
|
@@ -574,6 +610,9 @@ function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
|
574
610
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
575
611
|
}
|
|
576
612
|
|
|
613
|
+
/** Codex's reserved client-tool group on Responses Lite; carries no wire prefix. */
|
|
614
|
+
const SPARK_RESERVED_FUNCTIONS_NAMESPACE = "functions";
|
|
615
|
+
|
|
577
616
|
/**
|
|
578
617
|
* Apply the routed provider's real effort ladder to an existing Responses reasoning field.
|
|
579
618
|
* Native forward requests keep the server-owned native clamp; unknown third-party ladders stay
|
|
@@ -2286,6 +2325,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
2286
2325
|
let doneText = "";
|
|
2287
2326
|
let snapshot = "";
|
|
2288
2327
|
let usage: OcxUsage | undefined;
|
|
2328
|
+
let compactionEncryptedContent: string | undefined;
|
|
2289
2329
|
for await (const event of decodeServerSentEvents(response.body, { translatorBudget: budget })) {
|
|
2290
2330
|
let payload: unknown;
|
|
2291
2331
|
try { payload = JSON.parse(event.data); } catch { continue; }
|
|
@@ -2320,6 +2360,17 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
2320
2360
|
return;
|
|
2321
2361
|
case "response.completed":
|
|
2322
2362
|
{
|
|
2363
|
+
const responsePayload = isPlainObject(payload.response) ? payload.response : undefined;
|
|
2364
|
+
const output = Array.isArray(responsePayload?.output) ? responsePayload.output : [];
|
|
2365
|
+
const compaction = output.find(item => isPlainObject(item) && item.type === "compaction");
|
|
2366
|
+
if (isPlainObject(compaction) && typeof compaction.encrypted_content === "string") {
|
|
2367
|
+
const nextEncryptedContent = compaction.encrypted_content;
|
|
2368
|
+
const previousBytes = budgetEncoder.encode(compactionEncryptedContent ?? "").byteLength;
|
|
2369
|
+
const reservation = budget.reserveTransient(budgetEncoder.encode(nextEncryptedContent).byteLength, { kind: "retained_collectors" });
|
|
2370
|
+
compactionEncryptedContent = nextEncryptedContent;
|
|
2371
|
+
reservation.commitRetained();
|
|
2372
|
+
budget.releaseRetained(previousBytes, { kind: "retained_collectors" });
|
|
2373
|
+
}
|
|
2323
2374
|
const next = responsesPayloadText(payload.response);
|
|
2324
2375
|
const previousBytes = budgetEncoder.encode(snapshot).byteLength;
|
|
2325
2376
|
const reservation = budget.reserveTransient(budgetEncoder.encode(next).byteLength, { kind: "retained_collectors" });
|
|
@@ -2336,7 +2387,11 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
2336
2387
|
const text = snapshot || doneText || deltas;
|
|
2337
2388
|
if (text) yield { type: "text_delta", text };
|
|
2338
2389
|
budget.releaseRetained(budgetEncoder.encode(deltas).byteLength + budgetEncoder.encode(doneText).byteLength + budgetEncoder.encode(snapshot).byteLength, { kind: "retained_collectors" });
|
|
2339
|
-
yield {
|
|
2390
|
+
yield {
|
|
2391
|
+
type: "done",
|
|
2392
|
+
...(usage ? { usage } : {}),
|
|
2393
|
+
...(compactionEncryptedContent ? { compactionEncryptedContent } : {}),
|
|
2394
|
+
};
|
|
2340
2395
|
},
|
|
2341
2396
|
|
|
2342
2397
|
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
@@ -2354,14 +2409,23 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
2354
2409
|
if (payload.status === "incomplete") {
|
|
2355
2410
|
return [{ type: "incomplete", reason: responsesErrorMessage(payload) }];
|
|
2356
2411
|
}
|
|
2412
|
+
const usage = usageFromResponsesPayload(payload);
|
|
2413
|
+
const output = Array.isArray(payload.output) ? payload.output : [];
|
|
2414
|
+
const compaction = output.find(item => isPlainObject(item) && item.type === "compaction");
|
|
2415
|
+
const compactionEncryptedContent = isPlainObject(compaction) && typeof compaction.encrypted_content === "string"
|
|
2416
|
+
? compaction.encrypted_content
|
|
2417
|
+
: undefined;
|
|
2357
2418
|
const text = responsesPayloadText(payload);
|
|
2358
|
-
if (!text) {
|
|
2359
|
-
// A completed turn with
|
|
2360
|
-
//
|
|
2419
|
+
if (!text && !compactionEncryptedContent) {
|
|
2420
|
+
// A completed turn with neither text nor a native compaction blob cannot become a
|
|
2421
|
+
// replacement-history item. A ciphertext-only native completion is valid, though.
|
|
2361
2422
|
return [{ type: "error", message: "upstream compaction returned no summary text" }];
|
|
2362
2423
|
}
|
|
2363
|
-
const
|
|
2364
|
-
|
|
2424
|
+
return [...(text ? [{ type: "text_delta" as const, text }] : []), {
|
|
2425
|
+
type: "done",
|
|
2426
|
+
...(usage ? { usage } : {}),
|
|
2427
|
+
...(compactionEncryptedContent ? { compactionEncryptedContent } : {}),
|
|
2428
|
+
}];
|
|
2365
2429
|
},
|
|
2366
2430
|
};
|
|
2367
2431
|
}
|
package/src/bridge.ts
CHANGED
|
@@ -28,6 +28,11 @@ import {
|
|
|
28
28
|
awaitThoughtSignatureDurability,
|
|
29
29
|
} from "./responses/thought-signature-replay";
|
|
30
30
|
import { resolveStallTimeoutSec } from "./stall-timeout";
|
|
31
|
+
import {
|
|
32
|
+
createCitationMarkerFilter,
|
|
33
|
+
stripCitationMarkers,
|
|
34
|
+
type CitationMarkerFilter,
|
|
35
|
+
} from "./responses/citation-markers";
|
|
31
36
|
import { normalizeDeclaredToolName } from "./types";
|
|
32
37
|
import { usageDisplayTotalTokens } from "./usage/totals";
|
|
33
38
|
import { appendSafeWebSearchSource, safeWebSearchSources } from "./web-search/sources";
|
|
@@ -435,7 +440,14 @@ export function bridgeToResponsesSSE(
|
|
|
435
440
|
const stallSec = resolveStallTimeoutSec(options?.stallTimeoutSec);
|
|
436
441
|
const maxStallTicks = Math.ceil((stallSec * 1000) / heartbeatMs);
|
|
437
442
|
|
|
438
|
-
let currentMsg: {
|
|
443
|
+
let currentMsg: {
|
|
444
|
+
itemId: string;
|
|
445
|
+
outputIndex: number;
|
|
446
|
+
text: string;
|
|
447
|
+
textBytes: number;
|
|
448
|
+
citationFilter: CitationMarkerFilter;
|
|
449
|
+
phase?: OcxMessagePhase;
|
|
450
|
+
} | null = null;
|
|
439
451
|
let currentReasoning: { itemId: string; outputIndex: number; text: string; textBytes: number } | null = null;
|
|
440
452
|
let currentRawReasoning: { itemId: string; outputIndex: number; text: string; textBytes: number } | null = null;
|
|
441
453
|
// Anthropic extended-thinking round-trip state: the signature signs the CURRENT thinking
|
|
@@ -565,6 +577,17 @@ export function bridgeToResponsesSSE(
|
|
|
565
577
|
|
|
566
578
|
const closeCurrentMessage = (inferredPhase?: OcxMessagePhase) => {
|
|
567
579
|
if (!currentMsg) return;
|
|
580
|
+
// Release anything the citation filter was holding for this message, then strip the
|
|
581
|
+
// accumulated text: closeCurrentMessage re-sends it in output_text.done and
|
|
582
|
+
// output_item.done, so filtering only the deltas would leave the markers in both.
|
|
583
|
+
const trailing = currentMsg.citationFilter.flush();
|
|
584
|
+
if (trailing) {
|
|
585
|
+
emit("response.output_text.delta", {
|
|
586
|
+
item_id: currentMsg.itemId, output_index: currentMsg.outputIndex,
|
|
587
|
+
content_index: 0, delta: trailing,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
const messageText = stripCitationMarkers(currentMsg.text);
|
|
568
591
|
// Chat Completions has no message-phase field. Keep its live item provisional, then
|
|
569
592
|
// classify it only when the next adapter event proves whether this text led into more
|
|
570
593
|
// work or completed the turn. Explicit adapter phases always outrank this inference.
|
|
@@ -574,15 +597,15 @@ export function bridgeToResponsesSSE(
|
|
|
574
597
|
// Finalize the text part (Responses protocol). Without these .done events Codex never
|
|
575
598
|
// commits the content part and renders the message as truncated / cut off.
|
|
576
599
|
emit("response.output_text.done", {
|
|
577
|
-
item_id: currentMsg.itemId, output_index: currentMsg.outputIndex, content_index: 0, text:
|
|
600
|
+
item_id: currentMsg.itemId, output_index: currentMsg.outputIndex, content_index: 0, text: messageText,
|
|
578
601
|
});
|
|
579
602
|
emit("response.content_part.done", {
|
|
580
603
|
item_id: currentMsg.itemId, output_index: currentMsg.outputIndex, content_index: 0,
|
|
581
|
-
part: { type: "output_text", text:
|
|
604
|
+
part: { type: "output_text", text: messageText, annotations },
|
|
582
605
|
});
|
|
583
606
|
const item = {
|
|
584
607
|
type: "message", id: currentMsg.itemId, status: "completed", role: "assistant",
|
|
585
|
-
content: [{ type: "output_text", text:
|
|
608
|
+
content: [{ type: "output_text", text: messageText, annotations }],
|
|
586
609
|
...(phase ? { phase } : {}),
|
|
587
610
|
};
|
|
588
611
|
emit("response.output_item.done", { output_index: currentMsg.outputIndex, item });
|
|
@@ -936,7 +959,11 @@ export function bridgeToResponsesSSE(
|
|
|
936
959
|
item_id: itemId, output_index: outputIndex, content_index: 0,
|
|
937
960
|
part: { type: "output_text", text: "", annotations: [] },
|
|
938
961
|
});
|
|
939
|
-
currentMsg = {
|
|
962
|
+
currentMsg = {
|
|
963
|
+
itemId, outputIndex, text: "", textBytes: 0,
|
|
964
|
+
citationFilter: createCitationMarkerFilter(),
|
|
965
|
+
...(event.phase ? { phase: event.phase } : {}),
|
|
966
|
+
};
|
|
940
967
|
}
|
|
941
968
|
({ value: currentMsg.text, bytes: currentMsg.textBytes } = appendString(
|
|
942
969
|
currentMsg.text,
|
|
@@ -944,10 +971,16 @@ export function bridgeToResponsesSSE(
|
|
|
944
971
|
event.text,
|
|
945
972
|
"retained_collectors",
|
|
946
973
|
));
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
974
|
+
// A citation span can straddle a delta boundary, so the filter withholds an
|
|
975
|
+
// unterminated tail and releases it at close (#3150). The accumulator above
|
|
976
|
+
// keeps the raw text; it is stripped once in closeCurrentMessage.
|
|
977
|
+
const visible = currentMsg.citationFilter.push(event.text);
|
|
978
|
+
if (visible) {
|
|
979
|
+
emit("response.output_text.delta", {
|
|
980
|
+
item_id: currentMsg.itemId, output_index: currentMsg.outputIndex,
|
|
981
|
+
content_index: 0, delta: visible,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
951
984
|
break;
|
|
952
985
|
}
|
|
953
986
|
case "thinking_delta": {
|
|
@@ -1223,10 +1256,12 @@ export function bridgeToResponsesSSE(
|
|
|
1223
1256
|
// Exactly one compaction item per turn; codex-rs takes the first and fatals on 0.
|
|
1224
1257
|
const item = {
|
|
1225
1258
|
type: "compaction", id: `cmp_${uuid()}`,
|
|
1226
|
-
encrypted_content: encodeCompactionSummary(compactionText),
|
|
1259
|
+
encrypted_content: event.compactionEncryptedContent ?? encodeCompactionSummary(compactionText),
|
|
1227
1260
|
};
|
|
1228
1261
|
emit("response.output_item.done", { output_index: outputIndex, item });
|
|
1229
|
-
retainFinishedItem(item as OutputItem,
|
|
1262
|
+
retainFinishedItem(item as OutputItem, event.compactionEncryptedContent
|
|
1263
|
+
? bytesOf(event.compactionEncryptedContent)
|
|
1264
|
+
: compactionTextBytes);
|
|
1230
1265
|
outputIndex++;
|
|
1231
1266
|
}
|
|
1232
1267
|
// Recognize every adapter's truncation vocabulary, not just the canonical pair.
|
|
@@ -1574,6 +1609,7 @@ function buildResponseJSONWithBudget(
|
|
|
1574
1609
|
let sawTerminal = false;
|
|
1575
1610
|
let compactionText = "";
|
|
1576
1611
|
let compactionTextBytes = 0;
|
|
1612
|
+
let compactionEncryptedContent: string | undefined;
|
|
1577
1613
|
|
|
1578
1614
|
let currentText = "";
|
|
1579
1615
|
let currentTextBytes = 0;
|
|
@@ -1618,6 +1654,10 @@ function buildResponseJSONWithBudget(
|
|
|
1618
1654
|
const flushText = (inferredPhase?: OcxMessagePhase) => {
|
|
1619
1655
|
if (!currentText) return;
|
|
1620
1656
|
const phase = currentTextPhase ?? inferredPhase;
|
|
1657
|
+
// ChatGPT-backend citation markers arrive as literal private-use characters that the
|
|
1658
|
+
// Codex TUI prints verbatim (#3150). Strip them here rather than at the accumulator so
|
|
1659
|
+
// the retained byte accounting above still describes what the upstream actually sent.
|
|
1660
|
+
const text = stripCitationMarkers(currentText);
|
|
1621
1661
|
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
1622
1662
|
const annotations = pendingWebSources.map(s => ({
|
|
1623
1663
|
type: "url_citation", url: s.url, ...(s.title ? { title: s.title } : {}), start_index: 0, end_index: 0,
|
|
@@ -1625,7 +1665,7 @@ function buildResponseJSONWithBudget(
|
|
|
1625
1665
|
pendingWebSources = [];
|
|
1626
1666
|
const item = {
|
|
1627
1667
|
type: "message", id: `msg_${uuid()}`, role: "assistant", status: "completed",
|
|
1628
|
-
content: [{ type: "output_text", text
|
|
1668
|
+
content: [{ type: "output_text", text, annotations }],
|
|
1629
1669
|
...(phase ? { phase } : {}),
|
|
1630
1670
|
} as OutputItem;
|
|
1631
1671
|
pushOutput(item, currentTextBytes);
|
|
@@ -1915,6 +1955,7 @@ function buildResponseJSONWithBudget(
|
|
|
1915
1955
|
break;
|
|
1916
1956
|
case "done":
|
|
1917
1957
|
usage = e.usage;
|
|
1958
|
+
compactionEncryptedContent = e.compactionEncryptedContent;
|
|
1918
1959
|
sawTerminal = true;
|
|
1919
1960
|
endTurn = e.endTurn;
|
|
1920
1961
|
cleanDone = e.stopReason === undefined;
|
|
@@ -1967,7 +2008,11 @@ function buildResponseJSONWithBudget(
|
|
|
1967
2008
|
&& sawTerminal
|
|
1968
2009
|
&& !isTruncatedStopReason(rawStopReason)
|
|
1969
2010
|
) {
|
|
1970
|
-
|
|
2011
|
+
const item = {
|
|
2012
|
+
type: "compaction", id: `cmp_${uuid()}`,
|
|
2013
|
+
encrypted_content: compactionEncryptedContent ?? encodeCompactionSummary(compactionText),
|
|
2014
|
+
};
|
|
2015
|
+
pushOutput(item, compactionEncryptedContent ? bytesOf(compactionEncryptedContent) : compactionTextBytes);
|
|
1971
2016
|
}
|
|
1972
2017
|
|
|
1973
2018
|
const failure = errorEvent ? adapterFailureFromEvent(errorEvent) : undefined;
|
package/src/claude/auth-mode.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Claude auth-mode resolution.
|
|
3
3
|
*
|
|
4
|
-
* The resolver answers
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* The admission-key axis is separate and untouched: when the proxy requires an
|
|
11
|
-
* admission key, `buildClaudeEnv` injects it regardless of mode.
|
|
4
|
+
* The resolver answers which authentication mode the Claude launcher should honor.
|
|
5
|
+
* Native passthrough additionally needs an `sk-ant-` credential on the incoming request,
|
|
6
|
+
* so the field is `markerMode`, not `effectiveAuthMode` (devlog/_plan/260726_claude_auth_auto/002
|
|
7
|
+
* R2-1). The launchers use subscription mode to keep proxy-owned marker and admission
|
|
8
|
+
* credentials out of Claude's environment; proxy mode may inject them for gateway auth.
|
|
12
9
|
*/
|
|
13
10
|
import type { OcxConfig } from "../types";
|
|
14
11
|
import type { AuthDetectResult, AuthSourceId } from "./auth-detect";
|
|
@@ -18,7 +15,7 @@ export type MarkerMode = "proxy" | "subscription";
|
|
|
18
15
|
export type AuthModeOrigin = "manual" | "auto-present" | "auto-absent" | "auto-unknown";
|
|
19
16
|
|
|
20
17
|
export interface ResolvedAuthMode {
|
|
21
|
-
/**
|
|
18
|
+
/** Proxy-owned auth mode for launchers. NOT a claim about native auth. */
|
|
22
19
|
markerMode: MarkerMode;
|
|
23
20
|
origin: AuthModeOrigin;
|
|
24
21
|
/** The detector source that proved presence (origin auto-present only). */
|