@bitkyc08/opencodex 2.56.0 → 2.57.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/bin/ocx.mjs +10 -0
- package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
- package/gui/dist/assets/{index-D4zuyIxQ.js → index-Cz7CLdif.js} +21 -21
- package/gui/dist/index.html +2 -2
- package/package.json +3 -3
- package/src/adapters/codebuddy/adapter.ts +2 -1
- package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
- package/src/adapters/command-code.ts +1 -1
- package/src/adapters/cursor/envelope-echo.ts +8 -2
- package/src/adapters/google.ts +7 -7
- package/src/adapters/kiro/payload.ts +17 -3
- package/src/adapters/kiro/reasoning.ts +70 -7
- package/src/adapters/kiro/stream.ts +8 -2
- package/src/adapters/kiro/wire.ts +2 -1
- package/src/adapters/kiro-events.ts +21 -13
- package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
- package/src/adapters/openai-chat/tool-schema.ts +25 -7
- package/src/adapters/openai-chat.ts +8 -8
- package/src/adapters/openai-responses/passthrough.ts +32 -1
- package/src/bridge/errors.ts +26 -2
- package/src/bridge/response-json.ts +7 -1
- package/src/bridge/sse.ts +19 -1
- package/src/claude/desktop-profile.ts +66 -9
- package/src/claude/outbound.ts +18 -0
- package/src/cli/account-main.ts +1 -1
- package/src/cli/capabilities.ts +2 -2
- package/src/cli/combo.ts +10 -1
- package/src/cli/index.ts +48 -5
- package/src/cli/registry.ts +2 -1
- package/src/cli/system-command.ts +4 -4
- package/src/clients/config-export.ts +7 -3
- package/src/codex/account-label.ts +14 -3
- package/src/codex/account-store.ts +113 -26
- package/src/codex/account-usability.ts +21 -0
- package/src/codex/auth-api/login-flow.ts +14 -2
- package/src/codex/auth-api/reset-credit-service.ts +11 -2
- package/src/codex/auth-context.ts +157 -7
- package/src/codex/catalog/aggregation.ts +80 -1
- package/src/codex/catalog/model-visibility.ts +1 -0
- package/src/codex/catalog/remote.ts +30 -0
- package/src/codex/catalog/retained-sync.ts +9 -1
- package/src/codex/catalog/routed-gather.ts +38 -1
- package/src/codex/cli-install-provenance.ts +7 -1
- package/src/codex/convergence.ts +7 -2
- package/src/codex/desktop-app/types.ts +11 -2
- package/src/codex/desktop-app/windows.ts +5 -5
- package/src/codex/inject/restore.ts +29 -2
- package/src/codex/inject.ts +9 -9
- package/src/codex/model-entitlements.ts +152 -15
- package/src/codex/pool-refresh-backoff.ts +12 -3
- package/src/codex/quota-rejection.ts +104 -15
- package/src/codex/routing/cache-affinity.ts +70 -0
- package/src/codex/routing/cooldown-math.ts +10 -0
- package/src/codex/routing/selection.ts +79 -2
- package/src/codex/routing/thread-affinity.ts +50 -2
- package/src/codex/routing/transient-hold-dispatch.ts +141 -0
- package/src/codex/routing.ts +29 -49
- package/src/codex/warmup.ts +1 -1
- package/src/combos/failover.ts +85 -0
- package/src/combos/request.ts +17 -10
- package/src/combos/types.ts +23 -2
- package/src/config/pending-teardown.ts +31 -0
- package/src/generated/compatibility-version.json +163 -135
- package/src/images/loop.ts +1 -1
- package/src/lib/errors.ts +17 -0
- package/src/lib/request-execution-budget.ts +147 -21
- package/src/lib/spend-reservation-ledger.ts +18 -0
- package/src/lib/state-store-registrations.ts +6 -2
- package/src/lib/test-home-guard.ts +85 -1
- package/src/lib/upstream-retry.ts +77 -10
- package/src/lib/windows-elevation.ts +76 -14
- package/src/oauth/index.ts +2 -2
- package/src/oauth/key-providers.ts +2 -2
- package/src/providers/kiro-models.ts +4 -3
- package/src/providers/label.ts +19 -1
- package/src/providers/model-discovery.ts +16 -0
- package/src/providers/registry/entries-core.ts +7 -0
- package/src/providers/registry/entries-extended.ts +9 -0
- package/src/providers/registry/model-seeds.ts +4 -0
- package/src/responses/reasoning-envelope.ts +6 -3
- package/src/routing/identity-domains.ts +21 -14
- package/src/routing/probe-lease.ts +103 -1
- package/src/server/chat-completions.ts +3 -1
- package/src/server/chat-native.ts +37 -9
- package/src/server/index/live-sideband.ts +37 -1
- package/src/server/index/websocket-handler.ts +6 -2
- package/src/server/index.ts +5 -5
- package/src/server/inspection-tee.ts +107 -0
- package/src/server/live.ts +46 -1
- package/src/server/management/combo-routes.ts +10 -1
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +14 -19
- package/src/server/request-log.ts +127 -3
- package/src/server/response-log-body.ts +153 -0
- package/src/server/responses/account-change-state.ts +74 -0
- package/src/server/responses/adapter-continuation.ts +33 -7
- package/src/server/responses/adapter-delivery.ts +5 -11
- package/src/server/responses/adapter-dispatch.ts +84 -13
- package/src/server/responses/codex-ws-wire.ts +5 -0
- package/src/server/responses/collaboration.ts +74 -4
- package/src/server/responses/combo-session-recall.ts +68 -8
- package/src/server/responses/compact.ts +54 -13
- package/src/server/responses/core-auth.ts +2 -0
- package/src/server/responses/core-codex-account.ts +51 -3
- package/src/server/responses/core-combo.ts +103 -23
- package/src/server/responses/core-errors.ts +18 -0
- package/src/server/responses/core-replay.ts +105 -32
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/encrypted-payload.ts +0 -1
- package/src/server/responses/input-admission.ts +126 -6
- package/src/server/responses/passthrough-delivery.ts +19 -6
- package/src/server/responses/passthrough-dispatch.ts +28 -10
- package/src/server/responses/passthrough-error.ts +38 -2
- package/src/server/responses/request-prepare.ts +132 -22
- package/src/server/responses/request-send-budget.ts +97 -2
- package/src/server/responses/request-spend.ts +147 -0
- package/src/server/responses/request-transport.ts +62 -3
- package/src/server/responses/run-turn-execution.ts +59 -31
- package/src/server/responses/sidecar-execution.ts +7 -13
- package/src/server/responses/terminal-guard.ts +65 -4
- package/src/server/responses-undeclared-tool-guard.ts +9 -5
- package/src/service/windows-ops.ts +210 -16
- package/src/service/windows-scheduler.ts +28 -21
- package/src/service.ts +1 -1
- package/src/types/config.ts +4 -1
- package/src/types/request.ts +8 -5
- package/src/types/tools.ts +24 -0
- package/src/types.ts +2 -0
- package/src/update/index.ts +10 -0
- package/src/update/stop-contract.d.mts +1 -0
- package/src/update/stop-contract.mjs +19 -0
- package/src/update/stop-decision.d.mts +1 -1
- package/src/update/stop-decision.mjs +12 -3
- package/src/usage/log.ts +1 -1
- package/src/vision/anthropic-describe.ts +1 -1
- package/src/vision/describe.ts +5 -5
- package/src/web-search/anthropic-executor.ts +1 -1
- package/src/web-search/exa-executor.ts +1 -1
- package/src/web-search/executor.ts +1 -1
- package/src/web-search/gemini-executor.ts +1 -1
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/ollama-executor.ts +1 -1
- package/src/web-search/parse.ts +67 -14
- package/src/web-search/passthrough-bridge.ts +64 -31
- package/src/web-search/xai-executor.ts +1 -1
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-Cz7CLdif.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C5-RdDmD.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.57.0",
|
|
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",
|
|
@@ -76,11 +76,11 @@
|
|
|
76
76
|
"@bufbuild/protobuf": "^2.14.0",
|
|
77
77
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
78
78
|
"@napi-rs/keyring": "1.3.0",
|
|
79
|
-
"bun": "1.4.
|
|
79
|
+
"bun": "1.4.0",
|
|
80
80
|
"zod": "4.4.3"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@types/bun": "1.4.
|
|
83
|
+
"@types/bun": "1.4.0",
|
|
84
84
|
"typescript": "7.0.2"
|
|
85
85
|
},
|
|
86
86
|
"overrides": {
|
|
@@ -4,6 +4,7 @@ import { mapReasoningEffort } from "../../reasoning-effort";
|
|
|
4
4
|
import { buildSystemPrompt } from "../coding-agent/protocol";
|
|
5
5
|
import { baseScopedEnv, runCodingAgentTurn, type CodingAgentDeps, type SpawnFn } from "../coding-agent/turn";
|
|
6
6
|
import { CODEBUDDY_PROFILES, type CodeBuddyProfile } from "./profiles";
|
|
7
|
+
import { guardCodeBuddyScaffolding } from "./scaffold-guard";
|
|
7
8
|
|
|
8
9
|
export type { SpawnFn } from "../coding-agent/turn";
|
|
9
10
|
export type CodeBuddyAdapterDeps = CodingAgentDeps;
|
|
@@ -75,7 +76,7 @@ export function createCodeBuddyAdapter(provider: OcxProviderConfig, deps: CodeBu
|
|
|
75
76
|
provider,
|
|
76
77
|
parsed,
|
|
77
78
|
incoming,
|
|
78
|
-
emit,
|
|
79
|
+
emit: guardCodeBuddyScaffolding(emit),
|
|
79
80
|
buildArgs: (resolved, req, prov) => buildArgs(resolved as CodeBuddyProfile, req, prov),
|
|
80
81
|
buildEnv: (resolved, apiKey) => buildChildEnv(resolved as CodeBuddyProfile, apiKey),
|
|
81
82
|
deps,
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { AdapterEvent } from "../../types";
|
|
2
|
+
|
|
3
|
+
/** Error code for a CodeBuddy turn whose output contains vendor agent scaffolding. */
|
|
4
|
+
export const CODEBUDDY_SCAFFOLD_ERROR_CODE = "vendor_scaffold_detected";
|
|
5
|
+
|
|
6
|
+
// The observed control protocol uses FULLWIDTH VERTICAL LINE (U+FF5C). Detection stays
|
|
7
|
+
// deliberately narrower than the marker spelling: a calls control line must be followed by an
|
|
8
|
+
// invoke line for a functions.* tool. That distinguishes an agent scaffold from prose quoting or
|
|
9
|
+
// discussing one tag.
|
|
10
|
+
const DSML_CALLS_LINE = "<||dsml|| calls>";
|
|
11
|
+
const DSML_INVOKE_PREFIX = "<||dsml|| invoke name=\"functions.";
|
|
12
|
+
|
|
13
|
+
export interface CodeBuddyScaffoldFilterResult {
|
|
14
|
+
/** Bytes released from a suffix withheld by an earlier event on this channel. */
|
|
15
|
+
releasedPending: string;
|
|
16
|
+
/** Safe bytes belonging to the event currently being processed. */
|
|
17
|
+
text: string;
|
|
18
|
+
/** The earlier pending event still owns the extended candidate. */
|
|
19
|
+
pendingContinues: boolean;
|
|
20
|
+
fail: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ScanResult {
|
|
24
|
+
safe: string;
|
|
25
|
+
held: string;
|
|
26
|
+
fail: boolean;
|
|
27
|
+
fence: "`" | "~" | null;
|
|
28
|
+
lineStart: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function prefixAtEnd(text: string, at: number, expected: string): boolean {
|
|
32
|
+
const rest = text.slice(at).toLowerCase();
|
|
33
|
+
return rest.length < expected.length && expected.startsWith(rest);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Scan complete bytes and retain only a bounded suffix that can still become a control sequence.
|
|
38
|
+
*
|
|
39
|
+
* Control tags are recognized only at column zero and outside fenced Markdown. Inline code,
|
|
40
|
+
* quoted strings, blockquotes, indented source, and prose all add syntax before the tag and are
|
|
41
|
+
* therefore forwarded unchanged. A calls line alone is harmless; refusal requires the observed
|
|
42
|
+
* two-line calls-plus-functions-invoke grammar.
|
|
43
|
+
*/
|
|
44
|
+
function scan(
|
|
45
|
+
text: string,
|
|
46
|
+
initialFence: "`" | "~" | null,
|
|
47
|
+
initialLineStart: boolean,
|
|
48
|
+
): ScanResult {
|
|
49
|
+
let fence = initialFence;
|
|
50
|
+
let lineStart = initialLineStart;
|
|
51
|
+
let index = 0;
|
|
52
|
+
|
|
53
|
+
while (index < text.length) {
|
|
54
|
+
if (lineStart) {
|
|
55
|
+
const fenceMarkers = fence ? [fence.repeat(3)] : ["```", "~~~"];
|
|
56
|
+
const completeFence = fenceMarkers.find(marker => text.startsWith(marker, index));
|
|
57
|
+
if (completeFence) {
|
|
58
|
+
fence = fence ? null : (completeFence[0] as "`" | "~");
|
|
59
|
+
index += completeFence.length;
|
|
60
|
+
lineStart = false;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (fenceMarkers.some(marker => prefixAtEnd(text, index, marker))) {
|
|
64
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!fence) {
|
|
68
|
+
const lowered = text.slice(index).toLowerCase();
|
|
69
|
+
if (lowered.startsWith(DSML_CALLS_LINE)) {
|
|
70
|
+
const afterCalls = index + DSML_CALLS_LINE.length;
|
|
71
|
+
let invokeAt = -1;
|
|
72
|
+
if (text[afterCalls] === "\n") invokeAt = afterCalls + 1;
|
|
73
|
+
else if (text[afterCalls] === "\r" && text[afterCalls + 1] === "\n") invokeAt = afterCalls + 2;
|
|
74
|
+
else if (afterCalls === text.length || (text[afterCalls] === "\r" && afterCalls + 1 === text.length)) {
|
|
75
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (invokeAt >= 0) {
|
|
79
|
+
const invokeRest = text.slice(invokeAt).toLowerCase();
|
|
80
|
+
if (invokeRest.startsWith(DSML_INVOKE_PREFIX)) {
|
|
81
|
+
return { safe: text.slice(0, index), held: "", fail: true, fence, lineStart };
|
|
82
|
+
}
|
|
83
|
+
if (invokeRest.length === 0 || DSML_INVOKE_PREFIX.startsWith(invokeRest)) {
|
|
84
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else if (prefixAtEnd(text, index, DSML_CALLS_LINE)) {
|
|
88
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const char = text[index]!;
|
|
94
|
+
index += 1;
|
|
95
|
+
lineStart = char === "\n";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { safe: text, held: "", fail: false, fence, lineStart };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Streaming DSML control-sequence filter for one text or reasoning channel. */
|
|
102
|
+
export class CodeBuddyScaffoldFilter {
|
|
103
|
+
private pending = "";
|
|
104
|
+
private failed = false;
|
|
105
|
+
private fence: "`" | "~" | null = null;
|
|
106
|
+
private lineStart = true;
|
|
107
|
+
|
|
108
|
+
/** True while an earlier event owns an unresolved marker or fence prefix. */
|
|
109
|
+
hasPending(): boolean {
|
|
110
|
+
return this.pending.length > 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
push(chunk: string): CodeBuddyScaffoldFilterResult {
|
|
114
|
+
if (this.failed) {
|
|
115
|
+
return { releasedPending: "", text: "", pendingContinues: false, fail: false };
|
|
116
|
+
}
|
|
117
|
+
if (!chunk) {
|
|
118
|
+
return {
|
|
119
|
+
releasedPending: "",
|
|
120
|
+
text: "",
|
|
121
|
+
pendingContinues: this.hasPending(),
|
|
122
|
+
fail: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const priorPending = this.pending;
|
|
127
|
+
const result = scan(priorPending + chunk, this.fence, this.lineStart);
|
|
128
|
+
this.pending = result.held;
|
|
129
|
+
this.fence = result.fence;
|
|
130
|
+
this.lineStart = result.lineStart;
|
|
131
|
+
this.failed = result.fail;
|
|
132
|
+
|
|
133
|
+
const releasedLength = Math.min(priorPending.length, result.safe.length);
|
|
134
|
+
return {
|
|
135
|
+
releasedPending: result.safe.slice(0, releasedLength),
|
|
136
|
+
text: result.safe.slice(releasedLength),
|
|
137
|
+
pendingContinues: priorPending.length > 0 && result.safe.length === 0 && result.held.length > 0,
|
|
138
|
+
fail: result.fail,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Release a suffix that never completed the two-line control grammar. */
|
|
143
|
+
flush(): CodeBuddyScaffoldFilterResult {
|
|
144
|
+
if (this.failed) {
|
|
145
|
+
return { releasedPending: "", text: "", pendingContinues: false, fail: false };
|
|
146
|
+
}
|
|
147
|
+
const text = this.pending;
|
|
148
|
+
this.pending = "";
|
|
149
|
+
return { releasedPending: text, text: "", pendingContinues: false, fail: false };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function codeBuddyScaffoldErrorMessage(): string {
|
|
154
|
+
return "CodeBuddy CLI emitted vendor tool-call markup in an assistant output channel. This route"
|
|
155
|
+
+ " runs the CLI with its own tools and MCP servers disabled and Codex owns tool control, so"
|
|
156
|
+
+ " the turn was refused rather than forwarding or executing vendor agent scaffolding.";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Guard both streamed channels while preserving event order around withheld marker prefixes. */
|
|
160
|
+
export function guardCodeBuddyScaffolding(emit: (event: AdapterEvent) => void): (event: AdapterEvent) => void {
|
|
161
|
+
const textFilter = new CodeBuddyScaffoldFilter();
|
|
162
|
+
const thinkingFilter = new CodeBuddyScaffoldFilter();
|
|
163
|
+
type PendingChannel = "text" | "thinking";
|
|
164
|
+
type EventSlot = { resolved: boolean; event?: AdapterEvent };
|
|
165
|
+
const eventQueue: EventSlot[] = [];
|
|
166
|
+
const pendingSlots = new Map<PendingChannel, EventSlot>();
|
|
167
|
+
let closed = false;
|
|
168
|
+
|
|
169
|
+
const channelEvent = (channel: PendingChannel, text: string): AdapterEvent => channel === "text"
|
|
170
|
+
? { type: "text_delta", text }
|
|
171
|
+
: { type: "thinking_delta", thinking: text };
|
|
172
|
+
|
|
173
|
+
const drainResolved = (): void => {
|
|
174
|
+
while (eventQueue[0]?.resolved) {
|
|
175
|
+
const slot = eventQueue.shift()!;
|
|
176
|
+
if (slot.event) emit(slot.event);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const enqueueResolved = (event: AdapterEvent): void => {
|
|
181
|
+
eventQueue.push({ resolved: true, event });
|
|
182
|
+
drainResolved();
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const resolvePendingSlot = (channel: PendingChannel, text: string): void => {
|
|
186
|
+
const slot = pendingSlots.get(channel);
|
|
187
|
+
if (!slot) return;
|
|
188
|
+
slot.resolved = true;
|
|
189
|
+
if (text) slot.event = channelEvent(channel, text);
|
|
190
|
+
pendingSlots.delete(channel);
|
|
191
|
+
drainResolved();
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const enqueuePendingSlot = (channel: PendingChannel): void => {
|
|
195
|
+
const slot: EventSlot = { resolved: false };
|
|
196
|
+
eventQueue.push(slot);
|
|
197
|
+
pendingSlots.set(channel, slot);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const flushAllPending = (): void => {
|
|
201
|
+
for (const channel of ["text", "thinking"] as const) {
|
|
202
|
+
if (!pendingSlots.has(channel)) continue;
|
|
203
|
+
const filter = channel === "text" ? textFilter : thinkingFilter;
|
|
204
|
+
resolvePendingSlot(channel, filter.flush().releasedPending);
|
|
205
|
+
}
|
|
206
|
+
drainResolved();
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const refuse = (): void => {
|
|
210
|
+
if (closed) return;
|
|
211
|
+
flushAllPending();
|
|
212
|
+
closed = true;
|
|
213
|
+
emit({
|
|
214
|
+
type: "error",
|
|
215
|
+
message: codeBuddyScaffoldErrorMessage(),
|
|
216
|
+
status: 502,
|
|
217
|
+
errorType: "upstream_error",
|
|
218
|
+
code: CODEBUDDY_SCAFFOLD_ERROR_CODE,
|
|
219
|
+
retryable: false,
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return (event: AdapterEvent): void => {
|
|
224
|
+
if (closed) return;
|
|
225
|
+
if (event.type === "text_delta" || event.type === "thinking_delta") {
|
|
226
|
+
const channel: PendingChannel = event.type === "text_delta" ? "text" : "thinking";
|
|
227
|
+
const filter = channel === "text" ? textFilter : thinkingFilter;
|
|
228
|
+
const hadPending = filter.hasPending();
|
|
229
|
+
const cleaned = filter.push(event.type === "text_delta" ? event.text : event.thinking);
|
|
230
|
+
if (hadPending && !cleaned.pendingContinues) resolvePendingSlot(channel, cleaned.releasedPending);
|
|
231
|
+
if (cleaned.text) {
|
|
232
|
+
enqueueResolved(event.type === "text_delta"
|
|
233
|
+
? { ...event, text: cleaned.text }
|
|
234
|
+
: { ...event, thinking: cleaned.text });
|
|
235
|
+
}
|
|
236
|
+
if (filter.hasPending() && !cleaned.pendingContinues) enqueuePendingSlot(channel);
|
|
237
|
+
if (cleaned.fail) refuse();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (event.type === "done" || event.type === "error" || event.type === "incomplete") {
|
|
241
|
+
flushAllPending();
|
|
242
|
+
closed = true;
|
|
243
|
+
emit(event);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
enqueueResolved(event);
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -469,7 +469,7 @@ async function fetchCommandCode(request: AdapterRequest, ctx: AdapterFetchContex
|
|
|
469
469
|
const timer = setTimeout(() => timeout.abort(new DOMException("Timeout elapsed", "TimeoutError")), ctx?.timeoutMs ?? 200_000);
|
|
470
470
|
const callerSignal = ctx?.abortSignal ?? new AbortController().signal;
|
|
471
471
|
try {
|
|
472
|
-
return await executor(request.url, {
|
|
472
|
+
return await (ctx?.executor ?? executor)(request.url, {
|
|
473
473
|
method: request.method,
|
|
474
474
|
headers: request.headers,
|
|
475
475
|
body: request.body,
|
|
@@ -217,7 +217,13 @@ export type RoutingCommentaryDecision =
|
|
|
217
217
|
| { kind: "flush" }
|
|
218
218
|
| { kind: "hallucination" };
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
// The Korean alternative is deliberately asymmetric: it has a left boundary and no right one.
|
|
221
|
+
// Korean attaches particles directly to the noun, so the real sentences this detector exists to
|
|
222
|
+
// catch read "네이티브 셸이 차단되어..." and "네이티브 셸과 Read가...". A mirrored
|
|
223
|
+
// (?![\p{L}\p{M}\p{N}_]) lookahead would see the 이/과 particle as a letter and stop matching
|
|
224
|
+
// every one of them, which is why the negative cases below only probe the left side. Adding the
|
|
225
|
+
// right boundary looks like an obvious fix and disables the check; do not.
|
|
226
|
+
const ROUTING_NATIVE_TOOL_NAME = /\b(shell|read|grep|list|bash)\b|(?<![\p{L}\p{M}\p{N}_])네이티브\s*(?:셸|쉘)/giu;
|
|
221
227
|
const ROUTING_TOOL_HINT =
|
|
222
228
|
/(?:\b(?:shell|read|grep|list|bash)\b|exec_command|shell_command|브리지|네이티브\s*(?:셸|쉘))/iu;
|
|
223
229
|
const ROUTING_FAILURE_CLAIM =
|
|
@@ -276,7 +282,7 @@ export class CursorRoutingCommentarySniffer {
|
|
|
276
282
|
private matchesHallucination(): boolean {
|
|
277
283
|
if (!ROUTING_FAILURE_CLAIM.test(this.buffered)) return false;
|
|
278
284
|
const nativeTools = new Set(
|
|
279
|
-
[...this.buffered.matchAll(ROUTING_NATIVE_TOOL_NAME)].map(match => match[1]?.toLowerCase()),
|
|
285
|
+
[...this.buffered.matchAll(ROUTING_NATIVE_TOOL_NAME)].map(match => match[1]?.toLowerCase() ?? "shell"),
|
|
280
286
|
);
|
|
281
287
|
if (nativeTools.size === 0) return false;
|
|
282
288
|
return ROUTING_REDIRECT_CLAIM.test(this.buffered) || nativeTools.size >= 2;
|
package/src/adapters/google.ts
CHANGED
|
@@ -796,14 +796,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
796
796
|
// body, URL or credential.
|
|
797
797
|
const requestedTextFormat = parsed.options.textFormat;
|
|
798
798
|
if (requestedTextFormat) {
|
|
799
|
-
if (provider.googleMode === "cloud-code-assist") {
|
|
800
|
-
// Not implemented
|
|
801
|
-
//
|
|
802
|
-
// upstream cannot do it — silence would return unconstrained prose as success,
|
|
803
|
-
// which is the failure this
|
|
799
|
+
if (provider.googleMode === "cloud-code-assist" && !parsed.modelId.startsWith("gemini-")) {
|
|
800
|
+
// Not implemented by opencodex for non-Gemini models (including Claude)
|
|
801
|
+
// served through the Cloud Code Assist envelope. This is not a claim that
|
|
802
|
+
// the upstream cannot do it — silence would return unconstrained prose as success,
|
|
803
|
+
// which is the failure this refusal exists to prevent.
|
|
804
804
|
throw new Error(
|
|
805
|
-
"google cloud-code-assist structured output is not implemented by opencodex — "
|
|
806
|
-
+ "remove response_format or route this model through
|
|
805
|
+
"google cloud-code-assist structured output is not implemented by opencodex for non-Gemini models — "
|
|
806
|
+
+ "remove response_format or route this model through a direct provider",
|
|
807
807
|
);
|
|
808
808
|
}
|
|
809
809
|
if (isImageCapableModel(parsed.modelId)) {
|
|
@@ -38,7 +38,12 @@ import {
|
|
|
38
38
|
validateKiroConversationState,
|
|
39
39
|
type KiroTurn,
|
|
40
40
|
} from "./conversation";
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
injectKiroThinkingTags,
|
|
43
|
+
kiroNativeEffortField,
|
|
44
|
+
kiroReasoningContent,
|
|
45
|
+
KIRO_NATIVE_EFFORTS,
|
|
46
|
+
} from "./reasoning";
|
|
42
47
|
import { kiroPayloadMessages, userContentText } from "./usage";
|
|
43
48
|
import {
|
|
44
49
|
kiroToolWireNames,
|
|
@@ -388,7 +393,11 @@ export function buildKiroPayload(
|
|
|
388
393
|
assistantResponseMessage: {
|
|
389
394
|
content: turn.content,
|
|
390
395
|
...(turn.toolUses.length > 0 ? { toolUses: turn.toolUses } : {}),
|
|
391
|
-
|
|
396
|
+
// Replayed on the field it was received on: the GPT-5.6 signature is not base64 and is
|
|
397
|
+
// rejected when sent as `redactedContent`.
|
|
398
|
+
...(turn.redactedReasoning
|
|
399
|
+
? { reasoningContent: kiroReasoningContent(turn.redactedReasoning) }
|
|
400
|
+
: {}),
|
|
392
401
|
},
|
|
393
402
|
}
|
|
394
403
|
: {
|
|
@@ -447,7 +456,12 @@ export function buildKiroPayload(
|
|
|
447
456
|
if (!KIRO_NATIVE_EFFORTS.includes(effort)) {
|
|
448
457
|
throw new Error(`Kiro ${normalizeKiroModelId(parsed.modelId)} does not support reasoning effort ${JSON.stringify(effort)}`);
|
|
449
458
|
}
|
|
450
|
-
|
|
459
|
+
// Model eligibility still owns unsupported-effort validation above; wire eligibility
|
|
460
|
+
// is narrower for luna/terra, whose unverified rungs retain the thinking-tag path.
|
|
461
|
+
const verifiedEffortField = kiroNativeEffortField(parsed.modelId, effort);
|
|
462
|
+
if (verifiedEffortField) {
|
|
463
|
+
payload.additionalModelRequestFields = { [verifiedEffortField]: { effort } };
|
|
464
|
+
}
|
|
451
465
|
}
|
|
452
466
|
if (profileArn) payload.profileArn = profileArn;
|
|
453
467
|
return { payload, nameMap, conversationId, completionMode };
|
|
@@ -4,21 +4,46 @@ import type { OcxParsedRequest } from "../../types";
|
|
|
4
4
|
export type KiroReasoningMode = "native" | "emulated";
|
|
5
5
|
|
|
6
6
|
// Kiro takes a verified native effort field for these models, and each model family names it
|
|
7
|
-
// differently: the
|
|
8
|
-
// Models absent from this table fall back to emulated thinking
|
|
7
|
+
// differently: the GPT-5.6 family's `reasoning.effort` versus the Claude-specific
|
|
8
|
+
// `output_config.effort`. Models absent from this table fall back to emulated thinking
|
|
9
|
+
// instructions.
|
|
10
|
+
//
|
|
11
|
+
// The GPT-5.6 entries are measured against the live runtime rather than inferred from the vendor
|
|
12
|
+
// schema: the field is accepted (HTTP 200) and the encrypted reasoning blob that comes back grows
|
|
13
|
+
// with the effort. On one fixed hard prompt — a primality search plus a 20-bit recurrence count —
|
|
14
|
+
// luna's blob measured 5,130 chars at `low`, 16,686 at `medium`, 30,670 at `high` and 48,594 at
|
|
15
|
+
// `max`, against 13,118 with no effort signal at all; terra's measured 34,590 and 38,106 at native
|
|
16
|
+
// `max` against 11,758 and 17,598 bare, two repetitions each. The channel this replaces — the
|
|
17
|
+
// emulated `<thinking_mode>` tag block, which was all those models used to receive — measured
|
|
18
|
+
// 21,202 (`low`) and 28,302 (`max`) for luna, i.e. between that model's native `medium` and
|
|
19
|
+
// `high`, never reaching native `max`. `gpt-5.6-sol`'s native `max` cross-checked at 30,498 on the
|
|
20
|
+
// same prompt. Terra's absence from this table was therefore an omission rather than a capability
|
|
21
|
+
// difference: what the earlier Sol-only scope recorded was not reproducible here.
|
|
9
22
|
export const KIRO_NATIVE_EFFORT_FIELDS: Record<string, "reasoning" | "output_config"> = {
|
|
10
23
|
"gpt-5.6-sol": "reasoning",
|
|
24
|
+
"gpt-5.6-terra": "reasoning",
|
|
25
|
+
"gpt-5.6-luna": "reasoning",
|
|
11
26
|
"claude-opus-5": "output_config",
|
|
12
27
|
};
|
|
13
28
|
|
|
14
29
|
export const KIRO_NATIVE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
15
30
|
|
|
16
|
-
|
|
17
|
-
|
|
31
|
+
// The newly enabled models have evidence for these rungs only. Keep the previous
|
|
32
|
+
// emulation for xhigh, and never widen their native wire when the shared ladder grows.
|
|
33
|
+
const KIRO_LUNA_TERRA_NATIVE_EFFORTS = new Set(["low", "medium", "high", "max"]);
|
|
34
|
+
|
|
35
|
+
export function kiroNativeEffortField(
|
|
36
|
+
modelId: string,
|
|
37
|
+
effort?: string,
|
|
38
|
+
): "reasoning" | "output_config" | undefined {
|
|
39
|
+
const model = normalizeKiroModelId(modelId);
|
|
40
|
+
if ((model === "gpt-5.6-luna" || model === "gpt-5.6-terra")
|
|
41
|
+
&& effort !== undefined && !KIRO_LUNA_TERRA_NATIVE_EFFORTS.has(effort)) return undefined;
|
|
42
|
+
return KIRO_NATIVE_EFFORT_FIELDS[model];
|
|
18
43
|
}
|
|
19
44
|
|
|
20
|
-
export function kiroReasoningMode(modelId: string): KiroReasoningMode {
|
|
21
|
-
return kiroNativeEffortField(modelId) ? "native" : "emulated";
|
|
45
|
+
export function kiroReasoningMode(modelId: string, effort?: string): KiroReasoningMode {
|
|
46
|
+
return kiroNativeEffortField(modelId, effort) ? "native" : "emulated";
|
|
22
47
|
}
|
|
23
48
|
|
|
24
49
|
export function kiroThinkingBudget(parsed: OcxParsedRequest): number | undefined {
|
|
@@ -38,7 +63,7 @@ export function kiroThinkingBudget(parsed: OcxParsedRequest): number | undefined
|
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
export function injectKiroThinkingTags(content: string, parsed: OcxParsedRequest): string {
|
|
41
|
-
if (kiroReasoningMode(parsed.modelId) !== "emulated") return content;
|
|
66
|
+
if (kiroReasoningMode(parsed.modelId, parsed.options.reasoning) !== "emulated") return content;
|
|
42
67
|
const budget = kiroThinkingBudget(parsed);
|
|
43
68
|
if (!budget) return content;
|
|
44
69
|
const instruction = [
|
|
@@ -54,3 +79,41 @@ export function injectKiroThinkingTags(content: string, parsed: OcxParsedRequest
|
|
|
54
79
|
content,
|
|
55
80
|
].join("\n");
|
|
56
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The blob from a Kiro `reasoningContentEvent` has two possible homes on a replayed assistant
|
|
85
|
+
* turn, and the wire validates the SHAPE of each rather than its content: `signature` takes the
|
|
86
|
+
* emitted string verbatim, while `redactedContent` is a base64 member. The `.KTR~~…` value every
|
|
87
|
+
* GPT-5.6 capture returns is NOT valid base64, which is exactly why replaying it as
|
|
88
|
+
* `redactedContent` — what this proxy did before the field was measured — came back as
|
|
89
|
+
* REQUEST_BODY_INVALID ("Improperly formed request").
|
|
90
|
+
*
|
|
91
|
+
* The blob travels as ONE opaque string: adapter event, `ocxr1:` reasoning envelope, then
|
|
92
|
+
* `OcxAssistantMessage.kiroRedactedReasoning`. The field it arrived on therefore rides that same
|
|
93
|
+
* string, instead of a second parallel value that could drift from it. Provider data cannot forge
|
|
94
|
+
* the tag: the other channel is base64, whose alphabet has no colon.
|
|
95
|
+
*/
|
|
96
|
+
export const KIRO_REASONING_SIGNATURE_TAG = "signature:";
|
|
97
|
+
|
|
98
|
+
export function tagKiroReasoningBlob(field: "signature" | "redactedContent", data: string): string {
|
|
99
|
+
return field === "signature" ? KIRO_REASONING_SIGNATURE_TAG + data : data;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The wire field a stored blob arrived on, and its untagged value. */
|
|
103
|
+
export function splitKiroReasoningBlob(value: string): { field: "signature" | "redactedContent"; data: string } {
|
|
104
|
+
return value.startsWith(KIRO_REASONING_SIGNATURE_TAG)
|
|
105
|
+
? { field: "signature", data: value.slice(KIRO_REASONING_SIGNATURE_TAG.length) }
|
|
106
|
+
: { field: "redactedContent", data: value };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The `reasoningContent` object on an `assistantResponseMessage`. Exactly one member is set: the
|
|
111
|
+
* wire validates the shape, so the two cannot be substituted for each other.
|
|
112
|
+
*/
|
|
113
|
+
export type KiroReasoningContent = { signature: string } | { redactedContent: string };
|
|
114
|
+
|
|
115
|
+
/** `reasoningContent` for a replayed `assistantResponseMessage`, carrying the blob verbatim. */
|
|
116
|
+
export function kiroReasoningContent(value: string): KiroReasoningContent {
|
|
117
|
+
const { field, data } = splitKiroReasoningBlob(value);
|
|
118
|
+
return field === "signature" ? { signature: data } : { redactedContent: data };
|
|
119
|
+
}
|
|
@@ -19,6 +19,7 @@ import { noteKiroTransientThrottle } from "../kiro-retry";
|
|
|
19
19
|
import { KiroThinkingParser } from "../kiro-thinking";
|
|
20
20
|
import { isCompleteKiroToolInput, kiroTruncationErrorMessage } from "../kiro-truncation";
|
|
21
21
|
import { isValidKiroConversationId } from "../kiro-wire";
|
|
22
|
+
import { tagKiroReasoningBlob } from "./reasoning";
|
|
22
23
|
import { estimateKiroTokens, kiroUpstreamContextWindow } from "./usage";
|
|
23
24
|
|
|
24
25
|
// Stream parsing (shared by parseStream + parseResponse)
|
|
@@ -633,8 +634,13 @@ async function* parseKiroAttemptEvents(
|
|
|
633
634
|
if (ev.data) {
|
|
634
635
|
yield* emitRetained(stage({ type: "reasoning_raw_delta", text: ev.data }));
|
|
635
636
|
}
|
|
636
|
-
|
|
637
|
-
|
|
637
|
+
// The blob is replayed on the field it arrived on, so remember that field here — this is
|
|
638
|
+
// the only place that still knows it. See kiro/reasoning.ts for why the distinction is
|
|
639
|
+
// load-bearing rather than cosmetic.
|
|
640
|
+
if (ev.signature) {
|
|
641
|
+
yield* emitRetained(stage({ type: "kiro_redacted_reasoning", data: tagKiroReasoningBlob("signature", ev.signature) }));
|
|
642
|
+
} else if (ev.redactedContent) {
|
|
643
|
+
yield* emitRetained(stage({ type: "kiro_redacted_reasoning", data: tagKiroReasoningBlob("redactedContent", ev.redactedContent) }));
|
|
638
644
|
}
|
|
639
645
|
break;
|
|
640
646
|
case "context_usage":
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "../../types";
|
|
2
2
|
import type { KiroImage } from "../kiro-images";
|
|
3
|
+
import type { KiroReasoningContent } from "./reasoning";
|
|
3
4
|
|
|
4
5
|
export const AMZ_TARGET = "AmazonCodeWhispererStreamingService.GenerateAssistantResponse";
|
|
5
6
|
export const SDK_VERSION = "1.0.27";
|
|
@@ -51,7 +52,7 @@ export interface KiroHistoryEntry {
|
|
|
51
52
|
assistantResponseMessage?: {
|
|
52
53
|
content: string;
|
|
53
54
|
toolUses?: KiroToolUse[];
|
|
54
|
-
reasoningContent?:
|
|
55
|
+
reasoningContent?: KiroReasoningContent;
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -3,7 +3,7 @@ import { kiroTruncationReason } from "./kiro-truncation";
|
|
|
3
3
|
|
|
4
4
|
export type ParsedKiroEvent =
|
|
5
5
|
| { type: "content"; data?: string; modelId?: string }
|
|
6
|
-
| { type: "reasoning"; data?: string; redactedContent?: string }
|
|
6
|
+
| { type: "reasoning"; data?: string; signature?: string; redactedContent?: string }
|
|
7
7
|
| { type: "context_usage"; contextUsagePercentage: number }
|
|
8
8
|
| { type: "tool"; name?: string; toolUseId?: string; input?: string; stop?: boolean }
|
|
9
9
|
| { type: "truncation"; data: string }
|
|
@@ -138,18 +138,26 @@ export function parseKiroEvent(eventType: string, payload: Uint8Array): ParsedKi
|
|
|
138
138
|
: {}),
|
|
139
139
|
};
|
|
140
140
|
case "reasoningContentEvent":
|
|
141
|
-
// `text` is plaintext reasoning;
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
// `text` is plaintext reasoning; the GPT-5.6 family (sol/terra/luna) instead returns an
|
|
142
|
+
// encrypted blob, and the field it arrives on has to be replayed unchanged (see
|
|
143
|
+
// kiro/reasoning.ts): `signature` carries the `.KTR~~…` value verbatim and is what every
|
|
144
|
+
// capture of those models sent, while `redactedContent` — the base64 shape a capture has
|
|
145
|
+
// never shown — stays accepted for any model that sends it. Keyed off the wire field, not the
|
|
146
|
+
// model id. Any of the three may be absent on a bare event.
|
|
147
|
+
{
|
|
148
|
+
const text = optionalString(eventType, parsed, "text");
|
|
149
|
+
const signature = optionalString(eventType, parsed, "signature");
|
|
150
|
+
const redacted = optionalString(eventType, parsed, "redactedContent");
|
|
151
|
+
return {
|
|
152
|
+
type: "reasoning",
|
|
153
|
+
...(text !== undefined ? { data: text } : {}),
|
|
154
|
+
...(signature !== undefined
|
|
155
|
+
? { signature }
|
|
156
|
+
: redacted !== undefined
|
|
157
|
+
? { redactedContent: redacted }
|
|
158
|
+
: {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
153
161
|
case "toolUseEvent":
|
|
154
162
|
return {
|
|
155
163
|
type: "tool",
|