@bitkyc08/opencodex 2.45.0 → 2.46.0-preview.20260907
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-J96sug5C.css → index-BFgUC17B.css} +1 -1
- package/gui/dist/assets/{index-CCfD72yq.js → index-NcAVXkST.js} +19 -19
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/raycast.svg +3 -0
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/openai-responses.ts +7 -0
- package/src/bridge.ts +27 -7
- package/src/claude/inbound.ts +23 -5
- package/src/claude/outbound.ts +87 -19
- package/src/cli/capabilities.ts +4 -1
- package/src/cli/dispatch.ts +1 -1
- package/src/cli/doctor.ts +2 -2
- package/src/cli/export-command.ts +22 -10
- package/src/cli/help.ts +1 -1
- package/src/cli/index.ts +34 -7
- package/src/cli/integrations.ts +34 -1
- package/src/cli/provider.ts +27 -15
- package/src/cli/registry.ts +2 -2
- package/src/cli/version-skew.ts +36 -3
- package/src/clients/aside-profiles.ts +8 -7
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export/raycast.ts +106 -0
- package/src/clients/config-export.ts +36 -0
- package/src/clients/model-presentation.ts +61 -0
- package/src/codex/catalog/sync.ts +44 -2
- package/src/codex/convergence.ts +7 -0
- package/src/generated/compatibility-version.json +59 -43
- package/src/generated/model-metadata.ts +1 -0
- package/src/images/loop.ts +10 -2
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/merge.ts +158 -25
- package/src/integrations/raycast-detect.ts +111 -0
- package/src/integrations/registry.ts +18 -0
- package/src/integrations/state.ts +82 -13
- package/src/integrations/writer.ts +46 -31
- package/src/lib/bounded-body.ts +22 -7
- package/src/oauth/anthropic-routing.ts +59 -14
- package/src/oauth/health.ts +3 -0
- package/src/providers/quota.ts +145 -9
- package/src/providers/registry.ts +31 -0
- package/src/responses/parser.ts +16 -3
- package/src/responses/reasoning-envelope.ts +3 -2
- package/src/server/grok-responses-control-frame.ts +43 -0
- package/src/server/management/config-routes.ts +6 -2
- package/src/server/management/integration-routes.ts +29 -2
- package/src/server/management/model-routes.ts +9 -1
- package/src/server/request-decompress.ts +34 -8
- package/src/server/responses/agent-task-recovery-cache.ts +43 -14
- package/src/server/responses/agent-task-recovery.ts +28 -16
- package/src/server/responses/core.ts +39 -6
- package/src/web-search/loop.ts +10 -2
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-NcAVXkST.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BFgUC17B.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg viewBox="0 0 228 228" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M57 147.207V171L-0.0518799 113.948L11.9031 102.084L57 147.207ZM80.7932 171H57L114.052 228.052L125.955 216.149L80.7932 171ZM216.11 125.89L228 114L114 0L102.11 11.8901L147.155 57H119.926L88.4822 25.5826L76.5792 37.4727L96.1583 57.0519H82.5307V145.521H171V131.907L190.579 151.486L202.469 139.583L171 108.048V80.8192L216.11 125.89ZM62.9515 51.0485L51.0614 62.9516L63.8203 75.7104L75.7104 63.8073L62.9515 51.0485ZM164.193 152.29L152.341 164.18L165.1 176.939L177.003 165.048L164.193 152.29ZM37.4208 76.5792L25.5307 88.4693L57 119.952V96.1454L37.4208 76.5792ZM131.842 171H108.048L139.531 202.469L151.421 190.579L131.842 171Z" fill="#FF6363"/>
|
|
3
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.46.0-preview.20260907",
|
|
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",
|
|
@@ -1118,9 +1118,14 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1118
1118
|
break;
|
|
1119
1119
|
}
|
|
1120
1120
|
case "content_block_start": {
|
|
1121
|
-
const block = data.content_block as { type: string; id?: string; name?: string; data?: string } | undefined;
|
|
1121
|
+
const block = data.content_block as { type: string; id?: string; name?: string; data?: string; thinking?: string } | undefined;
|
|
1122
1122
|
if (!block) break;
|
|
1123
1123
|
currentBlockType = block.type;
|
|
1124
|
+
if (block.type === "thinking") {
|
|
1125
|
+
// Preserve even a display:omitted block boundary. The bridge can then
|
|
1126
|
+
// distinguish consecutive empty signed blocks from signature updates.
|
|
1127
|
+
yield { type: "thinking_delta", thinking: typeof block.thinking === "string" ? block.thinking : "" };
|
|
1128
|
+
}
|
|
1124
1129
|
if (block.type === "tool_use") {
|
|
1125
1130
|
currentToolCallId = usableToolUseId(block.id);
|
|
1126
1131
|
currentToolCallName = toolNames.fromWire(block.name ?? "");
|
|
@@ -1151,8 +1156,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1151
1156
|
// later text blocks independent.
|
|
1152
1157
|
yield { type: "thinking_delta", thinking: delta.reasoning };
|
|
1153
1158
|
} else if (delta.type === "signature_delta" && typeof delta.signature === "string" && (currentBlockType === "thinking" || currentBlockType === "reasoning")) {
|
|
1154
|
-
//
|
|
1155
|
-
//
|
|
1159
|
+
// Anthropic SDKs replace the signature with this value. Forward updates
|
|
1160
|
+
// within the block; the bridge closes on the next semantic boundary.
|
|
1156
1161
|
yield { type: "thinking_signature", signature: delta.signature };
|
|
1157
1162
|
} else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string" && currentBlockType === "tool_use") {
|
|
1158
1163
|
// Forwarded immediately: the bridge maps each delta to a client-visible
|
|
@@ -630,6 +630,13 @@ function mapRoutedResponsesReasoningEffort(
|
|
|
630
630
|
if (provider.authMode === "forward") return body;
|
|
631
631
|
if (configuredReasoningEfforts(provider, modelId) === undefined) return body;
|
|
632
632
|
if (!isPlainObject(body) || !isPlainObject(body.reasoning)) return body;
|
|
633
|
+
const declaredEfforts = modelRecordValue(provider.modelReasoningEfforts, modelId) ?? provider.reasoningEfforts;
|
|
634
|
+
// An explicitly empty ladder means no effort control, not no reasoning output.
|
|
635
|
+
// Omit only effort so the upstream default applies; unknown/non-rankable ladders stay untouched.
|
|
636
|
+
if (declaredEfforts?.length === 0 && Object.hasOwn(body.reasoning, "effort")) {
|
|
637
|
+
const { effort: _effort, ...reasoning } = body.reasoning;
|
|
638
|
+
return { ...body, reasoning: Object.keys(reasoning).length > 0 ? reasoning : undefined };
|
|
639
|
+
}
|
|
633
640
|
const requested = body.reasoning.effort;
|
|
634
641
|
if (typeof requested !== "string") return body;
|
|
635
642
|
|
package/src/bridge.ts
CHANGED
|
@@ -945,6 +945,13 @@ export function bridgeToResponsesSSE(
|
|
|
945
945
|
}
|
|
946
946
|
if (event.type !== "done" && event.type !== "incomplete" && event.type !== "error") continue;
|
|
947
947
|
}
|
|
948
|
+
// Anthropic signature_delta supplies the latest signature, not an append-only
|
|
949
|
+
// fragment (anthropic-sdk-typescript MessageStream). Keep consecutive updates
|
|
950
|
+
// together; the next semantic event belongs to the following block.
|
|
951
|
+
if (pendingSignature !== undefined && event.type !== "thinking_signature" && event.type !== "heartbeat") {
|
|
952
|
+
if (currentReasoning) closeCurrentReasoning();
|
|
953
|
+
else flushHiddenReasoningEnvelope();
|
|
954
|
+
}
|
|
948
955
|
switch (event.type) {
|
|
949
956
|
case "assistant_boundary": {
|
|
950
957
|
// A guarded continuation starts a fresh assistant output item while keeping the
|
|
@@ -1054,15 +1061,21 @@ export function bridgeToResponsesSSE(
|
|
|
1054
1061
|
case "thinking_signature": {
|
|
1055
1062
|
pendingSignatureBytes = replaceRetainedString(pendingSignatureBytes, event.signature, "reasoning");
|
|
1056
1063
|
pendingSignature = event.signature;
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
// an envelope-only reasoning item now.
|
|
1060
|
-
if (!currentReasoning) flushHiddenReasoningEnvelope();
|
|
1064
|
+
// Delay closing until the next semantic event so a signature update cannot
|
|
1065
|
+
// create another block or become attached to the following thinking text.
|
|
1061
1066
|
break;
|
|
1062
1067
|
}
|
|
1063
1068
|
case "redacted_thinking": {
|
|
1069
|
+
if (currentMsg) closeCurrentMessage("commentary");
|
|
1070
|
+
if (currentReasoning) closeCurrentReasoning();
|
|
1071
|
+
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
1072
|
+
flushHiddenRawReasoning();
|
|
1073
|
+
if (currentToolCall) closeCurrentToolCall();
|
|
1064
1074
|
budget?.chargeRetained(bytesOf(event.data), { kind: "reasoning" });
|
|
1065
1075
|
pendingRedacted.push(event.data);
|
|
1076
|
+
// A redacted block is complete at content_block_start. Emit it here,
|
|
1077
|
+
// not with a later thinking block or after a tool call at turn end.
|
|
1078
|
+
flushHiddenReasoningEnvelope();
|
|
1066
1079
|
break;
|
|
1067
1080
|
}
|
|
1068
1081
|
case "kiro_redacted_reasoning": {
|
|
@@ -1816,6 +1829,9 @@ function buildResponseJSONWithBudget(
|
|
|
1816
1829
|
if (budget) releaseTranslatedEvent(e, budget);
|
|
1817
1830
|
continue;
|
|
1818
1831
|
}
|
|
1832
|
+
if (batchSignature !== undefined && e.type !== "thinking_signature" && e.type !== "heartbeat") {
|
|
1833
|
+
flushSummaryReasoning();
|
|
1834
|
+
}
|
|
1819
1835
|
switch (e.type) {
|
|
1820
1836
|
case "assistant_boundary":
|
|
1821
1837
|
flushText("commentary");
|
|
@@ -1860,19 +1876,23 @@ function buildResponseJSONWithBudget(
|
|
|
1860
1876
|
}
|
|
1861
1877
|
break;
|
|
1862
1878
|
case "thinking_signature":
|
|
1863
|
-
//
|
|
1864
|
-
//
|
|
1879
|
+
// Like streaming, retain the latest signature update until the next semantic
|
|
1880
|
+
// event. Flushing every update would manufacture signature-only siblings.
|
|
1865
1881
|
batchSignatureBytes = replaceBatchRetainedString(batchSignatureBytes, e.signature, "reasoning");
|
|
1866
1882
|
batchSignature = e.signature;
|
|
1867
|
-
flushSummaryReasoning();
|
|
1868
1883
|
break;
|
|
1869
1884
|
case "redacted_thinking":
|
|
1885
|
+
flushText("commentary");
|
|
1886
|
+
flushSummaryReasoning();
|
|
1887
|
+
flushRawReasoning();
|
|
1888
|
+
flushToolCall();
|
|
1870
1889
|
{
|
|
1871
1890
|
const dataBytes = bytesOf(e.data);
|
|
1872
1891
|
budget?.chargeRetained(dataBytes, { kind: "reasoning" });
|
|
1873
1892
|
batchRedactedBytes += dataBytes;
|
|
1874
1893
|
}
|
|
1875
1894
|
batchRedacted.push(e.data);
|
|
1895
|
+
flushSummaryReasoning();
|
|
1876
1896
|
break;
|
|
1877
1897
|
case "kiro_redacted_reasoning":
|
|
1878
1898
|
// Stash only — pushed after the trailing flushes. One blob per turn, so last wins.
|
package/src/claude/inbound.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Design (devlog/260711_claude_inbound/010, 003_evidence.md):
|
|
5
5
|
* - translate-and-replay: the produced body MUST pass the real responsesRequestSchema
|
|
6
6
|
* parse so routing/OAuth/pool/failover are inherited unchanged.
|
|
7
|
-
* - thinking/redacted_thinking
|
|
8
|
-
*
|
|
7
|
+
* - thinking/redacted_thinking replay is preserved in Responses reasoning items;
|
|
8
|
+
* signatures and redacted payloads travel in bounded ocxr1 envelopes.
|
|
9
9
|
* - thinking.budget_tokens is NEVER forwarded raw; it maps to an effort tier.
|
|
10
10
|
* - top_k is accepted and silently dropped (no Responses equivalent, CCR parity).
|
|
11
11
|
*/
|
|
@@ -17,6 +17,7 @@ export { resolveInboundModel, effortForThinkingBudget, effortFromOutputConfig, e
|
|
|
17
17
|
import { AnthropicRequestError, isRec, type Rec } from "./inbound-records";
|
|
18
18
|
import { resolveInboundModel, effortForThinkingBudget, effortFromOutputConfig, formatFromOutputConfig } from "./inbound-model-options";
|
|
19
19
|
import { systemToInstructions, toolsToResponses, toolChoiceToResponses } from "./inbound-content-options";
|
|
20
|
+
import { decodeReasoningEnvelope, encodeReasoningEnvelope, OCX_REASONING_PREFIX } from "../responses/reasoning-envelope";
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
|
|
@@ -234,9 +235,26 @@ function assistantMessageToItems(content: unknown, input: Rec[]): void {
|
|
|
234
235
|
input.push({ type: "function_call", call_id: raw.id, name: raw.name, arguments: JSON.stringify(raw.input ?? {}) });
|
|
235
236
|
break;
|
|
236
237
|
}
|
|
237
|
-
case "thinking":
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
case "thinking": {
|
|
239
|
+
flush();
|
|
240
|
+
const thinking = typeof raw.thinking === "string" ? raw.thinking : "";
|
|
241
|
+
const signature = typeof raw.signature === "string" ? raw.signature : "";
|
|
242
|
+
if (signature.startsWith(OCX_REASONING_PREFIX)) {
|
|
243
|
+
const owned = decodeReasoningEnvelope(signature);
|
|
244
|
+
if (!owned) throw new AnthropicRequestError("malformed ocxr1 reasoning signature");
|
|
245
|
+
if (Object.hasOwn(owned, "sig")) throw new AnthropicRequestError("OpenCodex reasoning continuity cannot be replayed as an Anthropic signature");
|
|
246
|
+
}
|
|
247
|
+
const encrypted = signature.length === 0 ? undefined : signature.startsWith(OCX_REASONING_PREFIX) ? signature : encodeReasoningEnvelope({ sig: signature });
|
|
248
|
+
if (thinking.length === 0 && !encrypted) break;
|
|
249
|
+
input.push({ type: "reasoning", id: `rs_${crypto.randomUUID().replace(/-/g, "")}`, summary: thinking.length > 0 ? [{ type: "summary_text", text: thinking }] : [], ...(encrypted ? { encrypted_content: encrypted } : {}) });
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case "redacted_thinking": {
|
|
253
|
+
flush();
|
|
254
|
+
const data = typeof raw.data === "string" ? raw.data : "";
|
|
255
|
+
if (data.length > 0) input.push({ type: "reasoning", id: `rs_${crypto.randomUUID().replace(/-/g, "")}`, summary: [], encrypted_content: encodeReasoningEnvelope({ red: [data] }) });
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
240
258
|
default:
|
|
241
259
|
break;
|
|
242
260
|
}
|
package/src/claude/outbound.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* - Transport-only `ping` events may appear at any point, including before
|
|
6
6
|
* message_start. Semantic framing stays message_start ->
|
|
7
7
|
* (content_block_start -> deltas -> content_block_stop)* -> message_delta -> message_stop.
|
|
8
|
-
* - thinking blocks get thinking_delta(s) then
|
|
9
|
-
*
|
|
8
|
+
* - thinking blocks get thinking_delta(s), then one signature_delta containing the
|
|
9
|
+
* genuine replay signature or a bounded ocxr1 fallback envelope.
|
|
10
10
|
* - message_delta.usage is cumulative; message_start embeds a full message snapshot.
|
|
11
11
|
* - errors: {type:"error", error:{type,message}}; may arrive mid-stream after HTTP 200.
|
|
12
12
|
*/
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type TranslatorBudget,
|
|
21
21
|
} from "../lib/translator-budget";
|
|
22
22
|
import { sseFieldOffset, sseFieldValue } from "../lib/sse-decoder";
|
|
23
|
+
import { decodeReasoningEnvelope, encodeReasoningEnvelope } from "../responses/reasoning-envelope";
|
|
23
24
|
|
|
24
25
|
type Rec = Record<string, unknown>;
|
|
25
26
|
|
|
@@ -214,6 +215,9 @@ interface OpenBlock {
|
|
|
214
215
|
callId?: string;
|
|
215
216
|
/** Last fixed-size reasoning identity (item + summary/content index) seen by this block. */
|
|
216
217
|
reasoningPartKey?: string;
|
|
218
|
+
thinkingBuf?: string;
|
|
219
|
+
thinkingBufBytes?: number;
|
|
220
|
+
reasoningSig?: string;
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
/** Streaming: Responses SSE bytes -> Anthropic Messages SSE bytes. */
|
|
@@ -230,6 +234,9 @@ export function responsesSseToAnthropicSse(
|
|
|
230
234
|
let bufferBytes = 0;
|
|
231
235
|
let started = false;
|
|
232
236
|
let terminated = false;
|
|
237
|
+
// Starting termination can still throw while closing a block or emitting its
|
|
238
|
+
// terminal frame. Only a delivered terminal forbids the bounded overflow error.
|
|
239
|
+
let terminalDelivered = false;
|
|
233
240
|
let cancelled = false;
|
|
234
241
|
let blockIndex = 0;
|
|
235
242
|
let open: OpenBlock | null = null;
|
|
@@ -253,6 +260,11 @@ export function responsesSseToAnthropicSse(
|
|
|
253
260
|
const bytes = queuedLiveFrameBytes.shift();
|
|
254
261
|
if (bytes !== undefined) translatorBudget.releaseRetained(bytes, { kind: "live_transient" });
|
|
255
262
|
};
|
|
263
|
+
const releaseThinkingBuffer = (block: OpenBlock | null | undefined) => {
|
|
264
|
+
if (block?.kind !== "thinking") return;
|
|
265
|
+
translatorBudget.releaseRetained(block.thinkingBufBytes ?? 0, { kind: "reasoning" });
|
|
266
|
+
block.thinkingBufBytes = 0;
|
|
267
|
+
};
|
|
256
268
|
|
|
257
269
|
return new ReadableStream<Uint8Array>({
|
|
258
270
|
start(controller) {
|
|
@@ -296,13 +308,14 @@ export function responsesSseToAnthropicSse(
|
|
|
296
308
|
open.webSearchArgsEmitted = true;
|
|
297
309
|
}
|
|
298
310
|
if (open.kind === "thinking") {
|
|
299
|
-
|
|
311
|
+
const signature = open.reasoningSig ?? encodeReasoningEnvelope({ txt: open.thinkingBuf ?? "" });
|
|
300
312
|
emit("content_block_delta", {
|
|
301
313
|
type: "content_block_delta", index: open.index,
|
|
302
|
-
delta: { type: "signature_delta", signature
|
|
314
|
+
delta: { type: "signature_delta", signature },
|
|
303
315
|
});
|
|
304
316
|
}
|
|
305
317
|
emit("content_block_stop", { type: "content_block_stop", index: open.index });
|
|
318
|
+
releaseThinkingBuffer(open);
|
|
306
319
|
if (open.callId) translatorBudget.closeCall(open.callId);
|
|
307
320
|
open = null;
|
|
308
321
|
};
|
|
@@ -315,7 +328,7 @@ export function responsesSseToAnthropicSse(
|
|
|
315
328
|
? { type: "text", text: "" }
|
|
316
329
|
: { type: "thinking", thinking: "", signature: "" };
|
|
317
330
|
emit("content_block_start", { type: "content_block_start", index, content_block: contentBlock });
|
|
318
|
-
open = { kind, index };
|
|
331
|
+
open = { kind, index, thinkingBuf: "", thinkingBufBytes: 0 };
|
|
319
332
|
};
|
|
320
333
|
const finish = (stopReason: string, usage: unknown) => {
|
|
321
334
|
if (terminated) return;
|
|
@@ -328,6 +341,7 @@ export function responsesSseToAnthropicSse(
|
|
|
328
341
|
usage: anthropicUsage(usage, webSearchRequests),
|
|
329
342
|
});
|
|
330
343
|
emit("message_stop", { type: "message_stop" });
|
|
344
|
+
terminalDelivered = true;
|
|
331
345
|
};
|
|
332
346
|
// upstreamDerived: transient upstream statuses become overloaded_error so the
|
|
333
347
|
// Anthropic-SDK client retries with backoff; proxy-internal exceptions stay
|
|
@@ -336,11 +350,15 @@ export function responsesSseToAnthropicSse(
|
|
|
336
350
|
// resets reach the reader catch (no failed-tail relay) and stay api_error —
|
|
337
351
|
// same as today, deliberate residual.
|
|
338
352
|
const fail = (status: number, message: string, upstreamDerived = false, code?: string) => {
|
|
339
|
-
|
|
353
|
+
// finish/fail sets terminated before closeOpenBlock. A closure-time
|
|
354
|
+
// allocation failure must still emit one error, without retrying closure.
|
|
355
|
+
if (terminated && (code !== "translation_buffer_limit" || terminalDelivered)) return;
|
|
340
356
|
terminated = true;
|
|
341
357
|
if (code === "translation_buffer_limit") {
|
|
358
|
+
releaseThinkingBuffer(open);
|
|
342
359
|
if (open?.callId) translatorBudget.closeCall(open.callId);
|
|
343
360
|
open = null;
|
|
361
|
+
terminalDelivered = true;
|
|
344
362
|
// No normal close frames are valid after overflow. Emit exactly one bounded
|
|
345
363
|
// typed terminal without consulting the exhausted budget.
|
|
346
364
|
controller.enqueue(encoder.encode(sseFrame("error", anthropicErrorBody(
|
|
@@ -357,10 +375,12 @@ export function responsesSseToAnthropicSse(
|
|
|
357
375
|
// Do not manufacture message_start before the terminal error. Earlier transport-only
|
|
358
376
|
// pings remain valid and do not turn the failure into a partial message.
|
|
359
377
|
emit("error", anthropicErrorBody(status, message, type, code));
|
|
378
|
+
terminalDelivered = true;
|
|
360
379
|
return;
|
|
361
380
|
}
|
|
362
381
|
closeOpenBlock();
|
|
363
382
|
emit("error", anthropicErrorBody(status, message, type, code));
|
|
383
|
+
terminalDelivered = true;
|
|
364
384
|
};
|
|
365
385
|
|
|
366
386
|
const handleFrame = (eventName: string, data: Rec) => {
|
|
@@ -374,8 +394,10 @@ export function responsesSseToAnthropicSse(
|
|
|
374
394
|
case "response.output_text.delta": {
|
|
375
395
|
if (typeof data.delta !== "string" || data.delta.length === 0) break;
|
|
376
396
|
ensureBlock("text");
|
|
397
|
+
const active = open;
|
|
398
|
+
if (!active || active.kind !== "text") break;
|
|
377
399
|
emit("content_block_delta", {
|
|
378
|
-
type: "content_block_delta", index:
|
|
400
|
+
type: "content_block_delta", index: active.index,
|
|
379
401
|
delta: { type: "text_delta", text: data.delta },
|
|
380
402
|
});
|
|
381
403
|
break;
|
|
@@ -384,6 +406,8 @@ export function responsesSseToAnthropicSse(
|
|
|
384
406
|
case "response.reasoning_text.delta": {
|
|
385
407
|
if (typeof data.delta !== "string" || data.delta.length === 0) break;
|
|
386
408
|
ensureBlock("thinking");
|
|
409
|
+
const active = open;
|
|
410
|
+
if (!active || active.kind !== "thinking") break;
|
|
387
411
|
// The JSON path joins reasoning summary/content parts with "\n\n"
|
|
388
412
|
// (responsesJsonToAnthropicMessage); mirror that at part and item boundaries
|
|
389
413
|
// so multi-part summaries do not glue into one run-on paragraph. Frames
|
|
@@ -395,15 +419,32 @@ export function responsesSseToAnthropicSse(
|
|
|
395
419
|
// components while retaining item and part equality, rather than dropping item_id and
|
|
396
420
|
// accidentally joining distinct malformed reasoning items.
|
|
397
421
|
const partKey = `${boundedReasoningIdentity(data.item_id)}:${slot}`;
|
|
398
|
-
|
|
422
|
+
const needsPartSeparator = active.reasoningPartKey !== undefined
|
|
423
|
+
&& active.reasoningPartKey !== partKey;
|
|
424
|
+
const appended = `${needsPartSeparator ? "\n\n" : ""}${data.delta}`;
|
|
425
|
+
const previous = active.thinkingBuf ?? "";
|
|
426
|
+
const previousBytes = active.thinkingBufBytes ?? 0;
|
|
427
|
+
const nextBytes = appendedUtf8Bytes(previous, previousBytes, appended);
|
|
428
|
+
const scope = { kind: "reasoning" } as const;
|
|
429
|
+
const reservation = translatorBudget.reserveTransient(nextBytes, scope);
|
|
430
|
+
try {
|
|
431
|
+
active.thinkingBuf = previous + appended;
|
|
432
|
+
active.thinkingBufBytes = nextBytes;
|
|
433
|
+
reservation.commitRetained();
|
|
434
|
+
translatorBudget.releaseRetained(previousBytes, scope);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
reservation.release();
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
if (needsPartSeparator) {
|
|
399
440
|
emit("content_block_delta", {
|
|
400
|
-
type: "content_block_delta", index:
|
|
441
|
+
type: "content_block_delta", index: active.index,
|
|
401
442
|
delta: { type: "thinking_delta", thinking: "\n\n" },
|
|
402
443
|
});
|
|
403
444
|
}
|
|
404
|
-
|
|
445
|
+
active.reasoningPartKey = partKey;
|
|
405
446
|
emit("content_block_delta", {
|
|
406
|
-
type: "content_block_delta", index:
|
|
447
|
+
type: "content_block_delta", index: active.index,
|
|
407
448
|
delta: { type: "thinking_delta", thinking: data.delta },
|
|
408
449
|
});
|
|
409
450
|
break;
|
|
@@ -499,10 +540,9 @@ export function responsesSseToAnthropicSse(
|
|
|
499
540
|
if (pair.completed) webSearchRequests++;
|
|
500
541
|
break;
|
|
501
542
|
}
|
|
502
|
-
if (!open) break;
|
|
503
543
|
// Close the matching open block (message/reasoning items close implicitly on
|
|
504
544
|
// the next block; function_call items must close here so tool input parses).
|
|
505
|
-
if (open.kind === "tool_use" && item.type === "function_call") {
|
|
545
|
+
if (open && open.kind === "tool_use" && item.type === "function_call") {
|
|
506
546
|
if (open.bufferWebSearchArgs && !open.webSearchArgsEmitted) {
|
|
507
547
|
const rawArgs = typeof item.arguments === "string" && item.arguments.length > 0
|
|
508
548
|
? item.arguments
|
|
@@ -518,8 +558,26 @@ export function responsesSseToAnthropicSse(
|
|
|
518
558
|
}
|
|
519
559
|
closeOpenBlock();
|
|
520
560
|
}
|
|
521
|
-
else if (open.kind === "text" && item.type === "message") closeOpenBlock();
|
|
522
|
-
else if (
|
|
561
|
+
else if (open && open.kind === "text" && item.type === "message") closeOpenBlock();
|
|
562
|
+
else if (item.type === "reasoning") {
|
|
563
|
+
const encrypted = typeof item.encrypted_content === "string" ? item.encrypted_content : "";
|
|
564
|
+
const env = encrypted ? decodeReasoningEnvelope(encrypted) : null;
|
|
565
|
+
const red = env?.red ?? [];
|
|
566
|
+
if (env?.sig && open?.kind !== "thinking") ensureBlock("thinking");
|
|
567
|
+
if (open?.kind === "thinking") {
|
|
568
|
+
if (env?.sig) open.reasoningSig = env.sig;
|
|
569
|
+
closeOpenBlock();
|
|
570
|
+
}
|
|
571
|
+
if (red.length > 0) {
|
|
572
|
+
ensureStarted();
|
|
573
|
+
closeOpenBlock();
|
|
574
|
+
}
|
|
575
|
+
for (const data of red) {
|
|
576
|
+
const idx = blockIndex++;
|
|
577
|
+
emit("content_block_start", { type: "content_block_start", index: idx, content_block: { type: "redacted_thinking", data } });
|
|
578
|
+
emit("content_block_stop", { type: "content_block_stop", index: idx });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
523
581
|
break;
|
|
524
582
|
}
|
|
525
583
|
case "response.completed": {
|
|
@@ -704,6 +762,7 @@ export function responsesSseToAnthropicSse(
|
|
|
704
762
|
fail(413, "upstream translation buffer exceeded the safe limit", false, "translation_buffer_limit");
|
|
705
763
|
} else fail(500, err instanceof Error ? err.message : String(err));
|
|
706
764
|
} finally {
|
|
765
|
+
releaseThinkingBuffer(open);
|
|
707
766
|
translatorBudget.releaseRetained(bufferBytes, { kind: "live_transient" });
|
|
708
767
|
if (pingTimer !== undefined) clearInterval(pingTimer);
|
|
709
768
|
reader.releaseLock();
|
|
@@ -717,6 +776,7 @@ export function responsesSseToAnthropicSse(
|
|
|
717
776
|
cancel(reason) {
|
|
718
777
|
cancelled = true;
|
|
719
778
|
while (queuedLiveFrameBytes.length > 0) releaseDeliveredFrame();
|
|
779
|
+
releaseThinkingBuffer(open);
|
|
720
780
|
if (open?.callId) translatorBudget.closeCall(open.callId);
|
|
721
781
|
if (pingTimer !== undefined) clearInterval(pingTimer);
|
|
722
782
|
return reader?.cancel(reason);
|
|
@@ -756,8 +816,15 @@ export function responsesJsonToAnthropicMessage(json: unknown, model: string): R
|
|
|
756
816
|
if (isRec(s) && typeof s.text === "string" && s.text.length > 0) parts.push(s.text);
|
|
757
817
|
}
|
|
758
818
|
}
|
|
759
|
-
|
|
760
|
-
|
|
819
|
+
const encrypted = typeof raw.encrypted_content === "string" ? raw.encrypted_content : "";
|
|
820
|
+
const env = encrypted ? decodeReasoningEnvelope(encrypted) : null;
|
|
821
|
+
// Legacy combined envelopes place redacted blocks before the signed block,
|
|
822
|
+
// matching the Anthropic adapter. New bridge output uses separate items.
|
|
823
|
+
for (const data of env?.red ?? []) content.push({ type: "redacted_thinking", data });
|
|
824
|
+
// env.txt may be locally hidden text. Do not expose it here or manufacture
|
|
825
|
+
// a new signed continuity carrier; hidden-summary replay remains limited.
|
|
826
|
+
if (parts.length > 0 || env?.sig) {
|
|
827
|
+
content.push({ type: "thinking", thinking: parts.join("\n\n"), signature: env?.sig ?? encodeReasoningEnvelope({ txt: parts.join("\n\n") }) });
|
|
761
828
|
}
|
|
762
829
|
break;
|
|
763
830
|
}
|
|
@@ -923,9 +990,10 @@ export async function collectAnthropicMessage(
|
|
|
923
990
|
} finally {
|
|
924
991
|
reader.releaseLock();
|
|
925
992
|
}
|
|
926
|
-
|
|
927
|
-
|
|
993
|
+
// Error is authoritative. In particular, do not allocate another copy of an
|
|
994
|
+
// unfinished thinking block after the translator reported closure overflow.
|
|
928
995
|
if (error) return error;
|
|
996
|
+
closeBlock();
|
|
929
997
|
return {
|
|
930
998
|
id: `msg_${uuid()}`,
|
|
931
999
|
type: "message",
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -148,7 +148,10 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
148
148
|
summary: "Configured providers with connectivity and selected models.",
|
|
149
149
|
// Local config + PROVIDER_REGISTRY. Does not call GET /api/providers.
|
|
150
150
|
routes: [],
|
|
151
|
-
flags: [
|
|
151
|
+
flags: [
|
|
152
|
+
{ name: "--json", value: "boolean", summary: "Emit the provider list as JSON." },
|
|
153
|
+
{ name: "--jsonl", value: "boolean", summary: "Emit one configured provider per JSON line." },
|
|
154
|
+
],
|
|
152
155
|
mutates: false,
|
|
153
156
|
json: "envelope",
|
|
154
157
|
details: ["Reads local config; drives no management API route."],
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -403,7 +403,7 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
403
403
|
},
|
|
404
404
|
config,
|
|
405
405
|
port: live.port,
|
|
406
|
-
}, ["mcode", "pi"]));
|
|
406
|
+
}, ["mcode", "pi", "raycast"]));
|
|
407
407
|
} catch (error) {
|
|
408
408
|
console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
409
409
|
}
|
package/src/cli/doctor.ts
CHANGED
|
@@ -1157,11 +1157,11 @@ export async function runDoctor(args: string[] = []): Promise<void> {
|
|
|
1157
1157
|
// No extra probe -- findLiveProxy already carried the version back.
|
|
1158
1158
|
{
|
|
1159
1159
|
const { packageVersion } = await import("./help");
|
|
1160
|
-
const { computeVersionSkew } = await import("./version-skew");
|
|
1160
|
+
const { computeVersionSkew, isConfirmedVersionMatch } = await import("./version-skew");
|
|
1161
1161
|
const skew = computeVersionSkew(packageVersion(), live?.version);
|
|
1162
1162
|
if (skew.skewed && skew.warning) {
|
|
1163
1163
|
console.log(`!! ${skew.warning}`);
|
|
1164
|
-
} else if (skew
|
|
1164
|
+
} else if (isConfirmedVersionMatch(skew)) {
|
|
1165
1165
|
console.log(`ok ocx ${skew.cliVersion} matches the running proxy`);
|
|
1166
1166
|
}
|
|
1167
1167
|
}
|
|
@@ -172,17 +172,29 @@ export async function handleExportCommand(argv: string[], deps: ExportCommandDep
|
|
|
172
172
|
|
|
173
173
|
const spec = EXPORT_CLIENTS[client];
|
|
174
174
|
const root = await runtimeBaseUrl(deps);
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
|
|
175
|
+
let built: { document: unknown; text: string };
|
|
176
|
+
if (client === "raycast") {
|
|
177
|
+
// The dial address alone cannot distinguish a wildcard authenticated bind
|
|
178
|
+
// from loopback. Let the live server resolve its admission/listener policy;
|
|
179
|
+
// saved config can differ from the process serving this request.
|
|
180
|
+
const exported = await runtimeRequest<{
|
|
181
|
+
client: string; format: string; config: unknown; text: string;
|
|
182
|
+
}>("/api/client-config?client=raycast", {}, { ...deps, baseUrl: root });
|
|
183
|
+
if (!exported || exported.client !== "raycast" || exported.format !== "yaml"
|
|
184
|
+
|| typeof exported.text !== "string" || exported.config === undefined) {
|
|
185
|
+
throw new RuntimeApiError("Management API returned an unexpected Raycast export payload.", 502, null);
|
|
186
|
+
}
|
|
187
|
+
built = { document: exported.config, text: exported.text };
|
|
188
|
+
} else {
|
|
189
|
+
const rows = await runtimeRequest<ExportProxyModelRow[]>("/api/models", {}, { ...deps, baseUrl: root });
|
|
190
|
+
if (!Array.isArray(rows)) {
|
|
191
|
+
throw new RuntimeApiError("Management API returned an unexpected /api/models payload.", 502, rows);
|
|
192
|
+
}
|
|
193
|
+
// Discovery can persist selection; preserve the existing exporters' flow.
|
|
194
|
+
const config = (deps.configImpl ?? loadConfig)();
|
|
195
|
+
const models = exportModelsFromProxyRows(rows, config);
|
|
196
|
+
built = buildClientConfigText(client, { baseUrl: proxyV1BaseUrl(root), models, config });
|
|
178
197
|
}
|
|
179
|
-
// Discovery can persist pending -> ready selection. Read from the caller's
|
|
180
|
-
// config source after the response, rather than filtering with a stale snapshot.
|
|
181
|
-
const config = (deps.configImpl ?? loadConfig)();
|
|
182
|
-
const models = exportModelsFromProxyRows(rows, config);
|
|
183
|
-
// The text is the client's OWN format — YAML, TOML and JSON5 clients would
|
|
184
|
-
// otherwise receive a JSON rendering their parser reads differently.
|
|
185
|
-
const built = buildClientConfigText(client, { baseUrl: proxyV1BaseUrl(root), models, config });
|
|
186
198
|
const clientConfig = built.document;
|
|
187
199
|
const text = built.text;
|
|
188
200
|
|
package/src/cli/help.ts
CHANGED
|
@@ -77,7 +77,7 @@ Usage:
|
|
|
77
77
|
ocx memory [--json] Alias of ocx observe memory
|
|
78
78
|
ocx api-key <sub> Alias of ocx access key
|
|
79
79
|
ocx access <sub> External API keys and endpoint information
|
|
80
|
-
ocx export --client <id> Print a client config wired to the running proxy (
|
|
80
|
+
ocx export --client <id> Print a client config wired to the running proxy (13 clients)
|
|
81
81
|
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
|
|
82
82
|
ocx grok <sub> Grok Build model selection and apply
|
|
83
83
|
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
|
package/src/cli/index.ts
CHANGED
|
@@ -92,6 +92,15 @@ import {
|
|
|
92
92
|
grokSyncFailureMessage,
|
|
93
93
|
reconcileEnsureDesiredIntegrations,
|
|
94
94
|
} from "./ensure-desired-integrations";
|
|
95
|
+
import { refreshOwnedCatalogIntegrations } from "../integrations/catalog-refresh";
|
|
96
|
+
import { loadExportModels } from "../server/management/model-rows";
|
|
97
|
+
|
|
98
|
+
import { removeOwnedConfigAfterDesktopCleanup } from "./uninstall-client-state";
|
|
99
|
+
import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
|
|
100
|
+
import { selfLaunchArgv } from "../lib/self-launch-argv";
|
|
101
|
+
import { initializeNodeLauncherContext } from "./launcher-context";
|
|
102
|
+
import { createLocalAttestationSecret } from "../lib/local-management-attestation";
|
|
103
|
+
import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
|
|
95
104
|
|
|
96
105
|
/**
|
|
97
106
|
* A failed shell-hook reconcile is not cosmetic: a stale hook keeps sourcing
|
|
@@ -105,13 +114,25 @@ function reportShellHookFailure(result: { state: "installed" | "absent" | "faile
|
|
|
105
114
|
console.warn(" Check ~/.zshrc for the '# opencodex claude-env hook' block.");
|
|
106
115
|
}
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
async function refreshOwnedRaycastCatalog(
|
|
118
|
+
config: ReturnType<typeof loadConfig>,
|
|
119
|
+
port: number,
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
try {
|
|
122
|
+
const outcomes = await refreshOwnedCatalogIntegrations({
|
|
123
|
+
models: () => loadExportModels(config),
|
|
124
|
+
config,
|
|
125
|
+
port,
|
|
126
|
+
}, ["raycast"]);
|
|
127
|
+
for (const outcome of outcomes) {
|
|
128
|
+
if (!outcome.ok) {
|
|
129
|
+
console.error(`⚠️ Raycast integration was not refreshed: ${outcome.reason}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error(`⚠️ Raycast integration was not refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
115
136
|
|
|
116
137
|
initializeNodeLauncherContext();
|
|
117
138
|
|
|
@@ -493,6 +514,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
493
514
|
},
|
|
494
515
|
);
|
|
495
516
|
if (!startupSync.ran) console.log(" Codex integration OFF; startup left Codex native.");
|
|
517
|
+
await refreshOwnedRaycastCatalog(config, port);
|
|
496
518
|
// #1046: one warning per startup, after BOTH writes. The server's cache
|
|
497
519
|
// invalidation happens first and the catalog sync second, so the mtime is only
|
|
498
520
|
// final here — and neither write site warns on its own, or a boot that hits
|
|
@@ -558,6 +580,9 @@ async function handleEnsure(options: { existingIsSuccess?: boolean } = {}): Prom
|
|
|
558
580
|
return null;
|
|
559
581
|
});
|
|
560
582
|
if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
|
|
583
|
+
// Do not refresh Raycast from saved config here: live bind/admission and
|
|
584
|
+
// secondary-listener settings may differ. Explicit sync or server startup
|
|
585
|
+
// owns catalog refresh; ensure must not overwrite a working destination.
|
|
561
586
|
// Ensure env file exists for already-running proxy (may have been deleted or pre-dates this feature).
|
|
562
587
|
const systemEnv = await injectSystemEnv(live.port, config).catch(() => ({ injected: false }));
|
|
563
588
|
reportShellHookFailure(reconcileShellHook(systemEnv.injected));
|
|
@@ -602,6 +627,8 @@ async function handleEnsure(options: { existingIsSuccess?: boolean } = {}): Prom
|
|
|
602
627
|
return null;
|
|
603
628
|
});
|
|
604
629
|
if (synced?.status === "skipped") console.log(" Codex integration OFF; startup left Codex native.");
|
|
630
|
+
// The child performs Raycast refresh with its actual startup config. The
|
|
631
|
+
// parent's pre-spawn snapshot is not authoritative for a client-file write.
|
|
605
632
|
// The child opens /healthz before its best-effort roster reconcile. Await the same idempotent
|
|
606
633
|
// operation in the parent so `ocx ensure` cannot report success while stale ocx-*.md files are
|
|
607
634
|
// still observable. Always use the live port, including fallback-port starts.
|