@bitkyc08/opencodex 2.55.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-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
- package/gui/dist/index.html +2 -2
- package/package.json +4 -3
- package/src/adapters/base.ts +21 -0
- 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/cursor/transport-retry.ts +46 -1
- package/src/adapters/cursor.ts +4 -0
- package/src/adapters/google.ts +7 -7
- package/src/adapters/kiro/adapter.ts +42 -1
- 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/kiro-retry.ts +23 -4
- package/src/adapters/openai-chat/errors.ts +116 -0
- package/src/adapters/openai-chat/messages.ts +346 -0
- package/src/adapters/openai-chat/passthrough.ts +146 -0
- package/src/adapters/openai-chat/response-events.ts +117 -0
- package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
- package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
- package/src/adapters/openai-chat/tool-schema.ts +495 -0
- package/src/adapters/openai-chat/wire.ts +50 -0
- package/src/adapters/openai-chat.ts +40 -1452
- package/src/adapters/openai-responses/canonical-forward.ts +202 -0
- package/src/adapters/openai-responses/image-gen.ts +406 -0
- package/src/adapters/openai-responses/internal.ts +3 -0
- package/src/adapters/openai-responses/passthrough.ts +642 -0
- package/src/adapters/openai-responses/prompt-cache.ts +83 -0
- package/src/adapters/openai-responses/reasoning.ts +220 -0
- package/src/adapters/openai-responses/request-strips.ts +185 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
- package/src/adapters/openai-responses/tool-schema.ts +293 -0
- package/src/adapters/openai-responses/web-search.ts +156 -0
- package/src/adapters/openai-responses.ts +4 -2625
- package/src/bridge/errors.ts +58 -0
- package/src/bridge/internal.ts +174 -0
- package/src/bridge/response-json.ts +630 -0
- package/src/bridge/sse.ts +1462 -0
- package/src/bridge.ts +5 -2204
- package/src/chat/inbound.ts +12 -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-lifecycle.ts +3 -0
- package/src/codex/account-store.ts +184 -35
- package/src/codex/account-usability.ts +21 -0
- package/src/codex/auth-api/account-list.ts +507 -0
- package/src/codex/auth-api/http.ts +32 -0
- package/src/codex/auth-api/login-flow.ts +566 -0
- package/src/codex/auth-api/login-state.ts +64 -0
- package/src/codex/auth-api/main-account-probe.ts +331 -0
- package/src/codex/auth-api/pool-mode-gate.ts +274 -0
- package/src/codex/auth-api/pool-quota-probe.ts +512 -0
- package/src/codex/auth-api/reset-credit-service.ts +431 -0
- package/src/codex/auth-api/routes.ts +425 -0
- package/src/codex/auth-api/runtime-config.ts +48 -0
- package/src/codex/auth-api.ts +27 -3118
- package/src/codex/auth-context.ts +252 -35
- package/src/codex/catalog/aggregation.ts +80 -1
- package/src/codex/catalog/auto-review.ts +507 -0
- package/src/codex/catalog/build-entries.ts +981 -0
- package/src/codex/catalog/combo-member.ts +375 -0
- package/src/codex/catalog/derive-entry.ts +229 -0
- package/src/codex/catalog/effort.ts +0 -1
- package/src/codex/catalog/gated-native-warn.ts +63 -0
- package/src/codex/catalog/gather-capture.ts +533 -0
- package/src/codex/catalog/model-hints.ts +691 -0
- package/src/codex/catalog/model-visibility.ts +305 -0
- package/src/codex/catalog/provider-fetch.ts +52 -2942
- package/src/codex/catalog/provider-models.ts +685 -0
- package/src/codex/catalog/remote.ts +30 -0
- package/src/codex/catalog/restore.ts +132 -0
- package/src/codex/catalog/retained-sync.ts +714 -0
- package/src/codex/catalog/routed-gather.ts +895 -0
- package/src/codex/catalog/subagent-roster.ts +176 -0
- package/src/codex/catalog/sync.ts +52 -2698
- 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/config-toml.ts +563 -0
- package/src/codex/inject/remove.ts +192 -0
- package/src/codex/inject/restore.ts +567 -0
- package/src/codex/inject/routing-classify.ts +109 -0
- package/src/codex/inject/routing-target.ts +125 -0
- package/src/codex/inject.ts +89 -1444
- package/src/codex/lineage.ts +458 -0
- package/src/codex/model-entitlements.ts +152 -15
- package/src/codex/pool-refresh-backoff.ts +161 -0
- package/src/codex/quota-rejection.ts +104 -15
- package/src/codex/routing/active-account.ts +194 -0
- package/src/codex/routing/cache-affinity.ts +70 -0
- package/src/codex/routing/cooldown-math.ts +285 -0
- package/src/codex/routing/health-store.ts +402 -0
- package/src/codex/routing/probe-lease.ts +358 -0
- package/src/codex/routing/selection.ts +780 -0
- package/src/codex/routing/thread-affinity.ts +586 -0
- package/src/codex/routing/transient-hold-dispatch.ts +141 -0
- package/src/codex/routing.ts +370 -2271
- package/src/codex/shim-fingerprint.ts +223 -0
- package/src/codex/shim-inspect.ts +175 -0
- package/src/codex/shim-probe.ts +367 -0
- package/src/codex/shim-restore-lock.ts +169 -0
- package/src/codex/shim-state-file.ts +151 -0
- package/src/codex/shim-templates.ts +265 -0
- package/src/codex/shim.ts +48 -1268
- 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/diagnostics.ts +705 -0
- package/src/config/feature-flags.ts +55 -0
- package/src/config/live-reconcile.ts +403 -0
- package/src/config/load-degrade.ts +880 -0
- package/src/config/mutation-lock.ts +244 -0
- package/src/config/openai-tier-backup.ts +268 -0
- package/src/config/pending-teardown.ts +31 -0
- package/src/config/persist-unlocked.ts +92 -0
- package/src/config/proxy-env.ts +188 -0
- package/src/config/salvage.ts +244 -0
- package/src/config/schema/config-schema.ts +640 -0
- package/src/config/schema/leaf-validators.ts +855 -0
- package/src/config/warn-memo.ts +28 -0
- package/src/config.ts +234 -4481
- package/src/generated/compatibility-version.json +649 -121
- package/src/images/loop.ts +1 -1
- package/src/lib/errors.ts +17 -0
- package/src/lib/request-execution-budget.ts +198 -23
- package/src/lib/spend-reservation-ledger.ts +958 -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 +132 -21
- package/src/lib/windows-elevation.ts +76 -14
- package/src/lib/workflow-budget.ts +553 -30
- 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/quota/account-cache.ts +441 -0
- package/src/providers/quota/antigravity.ts +295 -0
- package/src/providers/quota/report-cache.ts +320 -0
- package/src/providers/quota/vendor-probes-key.ts +1243 -0
- package/src/providers/quota/vendor-probes-oauth.ts +590 -0
- package/src/providers/quota.ts +324 -3079
- package/src/providers/registry/entries-core.ts +1228 -0
- package/src/providers/registry/entries-extended.ts +1213 -0
- package/src/providers/registry/model-seeds.ts +912 -0
- package/src/providers/registry/types.ts +352 -0
- package/src/providers/registry.ts +24 -3536
- package/src/responses/continuation-ownership.ts +29 -0
- package/src/responses/reasoning-envelope.ts +6 -3
- package/src/responses/state/replay-fingerprint.ts +80 -0
- package/src/responses/state/snapshot-codec.ts +104 -0
- package/src/responses/state/spill-failure.ts +118 -0
- package/src/responses/state/spill-queue.ts +665 -0
- package/src/responses/state/temp-recovery.ts +257 -0
- package/src/responses/state.ts +82 -1143
- package/src/routing/identity-domains.ts +456 -0
- package/src/routing/probe-lease.ts +613 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/chat-native.ts +37 -9
- package/src/server/index/bounded-request.ts +88 -0
- package/src/server/index/live-sideband.ts +601 -0
- package/src/server/index/serve-options.ts +1766 -0
- package/src/server/index/startup-warnings.ts +213 -0
- package/src/server/index/websocket-handler.ts +339 -0
- package/src/server/index.ts +45 -2552
- 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/management/route-registry.ts +26 -23
- package/src/server/management/shared.ts +8 -5
- package/src/server/management/workflow-budget-routes.ts +133 -0
- package/src/server/management-api.ts +12 -0
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +14 -19
- package/src/server/request-log-conversation.ts +9 -7
- package/src/server/request-log.ts +372 -4
- package/src/server/response-log-body.ts +153 -0
- package/src/server/responses/account-change-state.ts +307 -0
- package/src/server/responses/adapter-continuation.ts +540 -0
- package/src/server/responses/adapter-delivery.ts +208 -0
- package/src/server/responses/adapter-dispatch.ts +1042 -0
- 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 +113 -17
- package/src/server/responses/completion-policy.ts +33 -0
- package/src/server/responses/core-auth.ts +529 -0
- package/src/server/responses/core-codex-account.ts +907 -0
- package/src/server/responses/core-combo-failure.ts +210 -0
- package/src/server/responses/core-combo.ts +787 -0
- package/src/server/responses/core-errors.ts +170 -0
- package/src/server/responses/core-lifetime.ts +95 -0
- package/src/server/responses/core-normalize.ts +350 -0
- package/src/server/responses/core-opaque-recovery.ts +380 -0
- package/src/server/responses/core-options.ts +159 -0
- package/src/server/responses/core-replay.ts +298 -0
- package/src/server/responses/core.ts +192 -8893
- 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 +869 -0
- package/src/server/responses/passthrough-dispatch.ts +1494 -0
- package/src/server/responses/passthrough-error.ts +38 -2
- package/src/server/responses/passthrough-execution.ts +54 -0
- package/src/server/responses/request-prepare.ts +1080 -0
- package/src/server/responses/request-send-budget.ts +259 -0
- package/src/server/responses/request-sidecar-auth.ts +149 -0
- package/src/server/responses/request-spend.ts +147 -0
- package/src/server/responses/request-transport.ts +803 -0
- package/src/server/responses/response-effects.ts +157 -0
- package/src/server/responses/run-turn-execution.ts +476 -0
- package/src/server/responses/sidecar-execution.ts +463 -0
- package/src/server/responses/terminal-guard.ts +65 -4
- package/src/server/responses-image-gen-repair.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +9 -5
- package/src/server/workflow-refusal.ts +84 -0
- 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 +34 -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 +147 -1
- package/src/usage/summary.ts +171 -21
- 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",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"skill:surface:check": "bun scripts/generate-ocx-skill-surface.ts --check",
|
|
54
54
|
"structure:index": "bun scripts/structure-ssot.ts --fix",
|
|
55
55
|
"structure:check": "bun scripts/structure-ssot.ts",
|
|
56
|
+
"ratchet:update": "bun scripts/file-size-ratchet.ts --update",
|
|
56
57
|
"generate:model-metadata": "bun scripts/generate-model-metadata.ts",
|
|
57
58
|
"build:gui": "cd gui && bun install --frozen-lockfile && bun run build && cd .. && bun run prepare:package",
|
|
58
59
|
"build:remote-workspace-helper": "cargo build --release --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
|
|
@@ -75,11 +76,11 @@
|
|
|
75
76
|
"@bufbuild/protobuf": "^2.14.0",
|
|
76
77
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
77
78
|
"@napi-rs/keyring": "1.3.0",
|
|
78
|
-
"bun": "1.4.
|
|
79
|
+
"bun": "1.4.0",
|
|
79
80
|
"zod": "4.4.3"
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
|
-
"@types/bun": "1.4.
|
|
83
|
+
"@types/bun": "1.4.0",
|
|
83
84
|
"typescript": "7.0.2"
|
|
84
85
|
},
|
|
85
86
|
"overrides": {
|
package/src/adapters/base.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AdapterEvent, OcxParsedRequest } from "../types";
|
|
2
2
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
3
3
|
import type { RequestExecutionBudget } from "../lib/request-execution-budget";
|
|
4
|
+
import type { AttemptRecoveryKind } from "../usage/log";
|
|
4
5
|
import type { AdapterTierMetadata } from "../providers/fastwire";
|
|
5
6
|
|
|
6
7
|
/** Metadata about the caller's incoming request, for auth-forwarding adapters. */
|
|
@@ -20,6 +21,16 @@ export interface IncomingMeta {
|
|
|
20
21
|
* the anthropic and openai-chat adapters; others ignore it.
|
|
21
22
|
*/
|
|
22
23
|
imageTierBias?: number;
|
|
24
|
+
/**
|
|
25
|
+
* The enclosing request's send budget, for adapters that own their upstream transport.
|
|
26
|
+
*
|
|
27
|
+
* A `runTurn` adapter never receives an `AdapterFetchContext`, so the budget that bounds every
|
|
28
|
+
* other leg could not reach it: Cursor re-sends a whole turn up to three times inside one
|
|
29
|
+
* adapter call, and the request cap counted that as one send. Optional, and absent means
|
|
30
|
+
* unlimited, because adapter unit tests build a meta with neither a budget nor a request
|
|
31
|
+
* behind it (#4546).
|
|
32
|
+
*/
|
|
33
|
+
sendBudget?: RequestExecutionBudget;
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
export interface ProviderAdapter {
|
|
@@ -147,6 +158,16 @@ export interface AdapterFetchContext {
|
|
|
147
158
|
* adapter entry as one send is how a nested 3x3 ladder stayed invisible to a request cap.
|
|
148
159
|
*/
|
|
149
160
|
sendBudget?: RequestExecutionBudget;
|
|
161
|
+
/**
|
|
162
|
+
* Observes every physical upstream send this adapter makes, including its own inner retries.
|
|
163
|
+
*
|
|
164
|
+
* `ordinal` counts from 1 within this fetch call, so a caller that already recorded the entry
|
|
165
|
+
* send records only ordinals above 1 and an adapter that never retries internally logs exactly
|
|
166
|
+
* what it logs today. Kiro and Cursor were unpinnable without this: they report one send per
|
|
167
|
+
* adapter call however many requests they actually made, so their inner ladders were invisible
|
|
168
|
+
* to `sendCount` and no regression could assert a count for them (#4546).
|
|
169
|
+
*/
|
|
170
|
+
onPhysicalSend?: (send: { ordinal: number; recovery?: AttemptRecoveryKind }) => void;
|
|
150
171
|
}
|
|
151
172
|
|
|
152
173
|
/**
|
|
@@ -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;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CursorRunRequest, CursorServerMessage } from "./types";
|
|
2
2
|
import type { CursorTransport, CursorTransportFactory, CursorTransportFactoryInput } from "./transport";
|
|
3
|
-
import {
|
|
3
|
+
import type { RequestExecutionBudget } from "../../lib/request-execution-budget";
|
|
4
|
+
import type { AttemptRecoveryKind } from "../../usage/log";
|
|
5
|
+
import { SendBudgetExhaustedError, abortError, retryBackoffDelayMs, sleepWithAbort } from "../../lib/upstream-retry";
|
|
4
6
|
import { debugProviderDiagnostic } from "../../lib/debug";
|
|
5
7
|
import { isCursorRootEnvelopeError, safeCursorErrorMessage } from "./cursor-errors";
|
|
6
8
|
|
|
@@ -11,6 +13,27 @@ export const CURSOR_RETRY_ATTEMPTS = 3;
|
|
|
11
13
|
export const CURSOR_RETRY_BASE_MS = 250;
|
|
12
14
|
export const CURSOR_RETRY_MAX_MS = 2_000;
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Fixed identity for the Cursor upstream in the request budget's target ledger. A literal, not
|
|
18
|
+
* anything derived from the turn: the ledger is read back in diagnostics, so it must not become
|
|
19
|
+
* a place where a session or credential identity leaks.
|
|
20
|
+
*/
|
|
21
|
+
export const CURSOR_BUDGET_TARGET_KEY = "cursor";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* How one Cursor turn participates in the enclosing logical request (#4546).
|
|
25
|
+
*
|
|
26
|
+
* Both fields are optional and the whole object defaults to empty, which is what keeps a
|
|
27
|
+
* context-free unit call unlimited: this transport is exercised directly by tests that build no
|
|
28
|
+
* request at all, and a mandatory budget would have made every one of them a budget test.
|
|
29
|
+
*/
|
|
30
|
+
export interface CursorTurnExecutionOptions {
|
|
31
|
+
/** Absent means unlimited; present means every retry is a physical send the request pays for. */
|
|
32
|
+
sendBudget?: RequestExecutionBudget;
|
|
33
|
+
/** Observes each physical run request; `ordinal` counts from 1 within this turn. */
|
|
34
|
+
onPhysicalSend?: (send: { ordinal: number; recovery?: AttemptRecoveryKind }) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
/**
|
|
15
38
|
* True only for clearly transient failures that occur BEFORE the run request is committed to the
|
|
16
39
|
* wire (connection refused/reset/timeout, immediate HTTP/2 GOAWAY, gRPC/Connect "unavailable").
|
|
@@ -66,6 +89,11 @@ function requestUncommitted(transport: CursorTransport): boolean {
|
|
|
66
89
|
* - the failing transport reports the run request was not committed to the wire,
|
|
67
90
|
* - the error is a transient pre-commit failure.
|
|
68
91
|
* Otherwise the error propagates (the adapter maps it to a user-facing message).
|
|
92
|
+
*
|
|
93
|
+
* `execution` carries the enclosing request's send budget. Each attempt here is a real re-send
|
|
94
|
+
* of the whole turn, so an outer cap that counted one adapter entry counted at most a third of
|
|
95
|
+
* what went upstream; when a budget is present every attempt is admitted against it and an
|
|
96
|
+
* exhausted request stops before opening another transport (#4546).
|
|
69
97
|
*/
|
|
70
98
|
export async function runCursorTurnWithRetry(
|
|
71
99
|
makeTransport: (input: CursorTransportFactoryInput) => CursorTransport,
|
|
@@ -73,9 +101,26 @@ export async function runCursorTurnWithRetry(
|
|
|
73
101
|
request: CursorRunRequest,
|
|
74
102
|
signal: AbortSignal | undefined,
|
|
75
103
|
onEvent: (message: CursorServerMessage, transport: CursorTransport) => void,
|
|
104
|
+
execution: CursorTurnExecutionOptions = {},
|
|
76
105
|
): Promise<void> {
|
|
77
106
|
for (let attempt = 0; ; attempt++) {
|
|
78
107
|
if (signal?.aborted) throw abortError(signal);
|
|
108
|
+
// Admitted before the transport is built: a refused send must not open a connection, and
|
|
109
|
+
// the refusal must reach the adapter as the typed exhaustion rather than as a run failure
|
|
110
|
+
// that the retry predicate below could read as transient.
|
|
111
|
+
const decision = execution.sendBudget?.reserveDispatch({
|
|
112
|
+
sendClass: "transient",
|
|
113
|
+
targetKey: CURSOR_BUDGET_TARGET_KEY,
|
|
114
|
+
});
|
|
115
|
+
if (decision && (!decision.allowed || !decision.permit.use())) {
|
|
116
|
+
throw new SendBudgetExhaustedError(CURSOR_BUDGET_TARGET_KEY);
|
|
117
|
+
}
|
|
118
|
+
execution.onPhysicalSend?.({
|
|
119
|
+
ordinal: attempt + 1,
|
|
120
|
+
// Cursor retries only pre-commit transport failures, so every retry send is the
|
|
121
|
+
// connection-reset class; there is no re-send of a turn the server may have accepted.
|
|
122
|
+
...(attempt > 0 ? { recovery: "connection-reset" as const } : {}),
|
|
123
|
+
});
|
|
79
124
|
const transport = makeTransport(input);
|
|
80
125
|
let emittedAny = false;
|
|
81
126
|
let closed = false;
|
package/src/adapters/cursor.ts
CHANGED
|
@@ -403,6 +403,10 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
|
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
},
|
|
406
|
+
// Cursor's retry ladder re-sends the WHOLE turn, so each attempt is a physical send
|
|
407
|
+
// the enclosing request pays for. A meta without a budget -- every adapter unit test,
|
|
408
|
+
// and any caller predating this -- keeps the adapter's own three attempts (#4546).
|
|
409
|
+
incoming.sendBudget ? { sendBudget: incoming.sendBudget } : {},
|
|
406
410
|
);
|
|
407
411
|
};
|
|
408
412
|
|
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)) {
|
|
@@ -45,6 +45,10 @@ import {
|
|
|
45
45
|
type KiroWireClient,
|
|
46
46
|
} from "./wire";
|
|
47
47
|
|
|
48
|
+
/** The physical-send observer an `AdapterFetchContext` may carry, and the record it receives. */
|
|
49
|
+
type KiroPhysicalSendObserver = NonNullable<AdapterFetchContext["onPhysicalSend"]>;
|
|
50
|
+
type KiroPhysicalSend = Parameters<KiroPhysicalSendObserver>[0];
|
|
51
|
+
|
|
48
52
|
// Adapter
|
|
49
53
|
export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
50
54
|
// Per-request closure (resolveAdapter builds a fresh adapter per request — server.ts:440 — so this
|
|
@@ -62,6 +66,25 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
62
66
|
// Captured the same way as the abort signal, because the text-fallback rebuild below runs
|
|
63
67
|
// outside the fetchResponse frame and used to construct a context without either (#4546).
|
|
64
68
|
let requestSendBudget: RequestExecutionBudget | undefined;
|
|
69
|
+
// Captured for the same reason, and needed for the same leg to be COUNTABLE rather than merely
|
|
70
|
+
// bounded: the rebuild's sends were paid for out of the request budget but reported by nobody,
|
|
71
|
+
// so no regression could pin how many requests one Kiro turn actually makes.
|
|
72
|
+
let requestOnPhysicalSend: KiroPhysicalSendObserver | undefined;
|
|
73
|
+
// One ordinal sequence across the whole turn. `fetchKiroWithRetry` numbers from 1 inside each
|
|
74
|
+
// call, and the caller reads ordinal 1 as the send it already recorded itself; forwarding the
|
|
75
|
+
// rebuild's raw ordinals would therefore drop its first send — the very send that makes the
|
|
76
|
+
// fallback a second request rather than a continuation of the first.
|
|
77
|
+
let physicalSendsObserved = 0;
|
|
78
|
+
const forwardPhysicalSend = (
|
|
79
|
+
send: KiroPhysicalSend,
|
|
80
|
+
ordinalBase: number,
|
|
81
|
+
defaultRecovery?: KiroPhysicalSend["recovery"],
|
|
82
|
+
): void => {
|
|
83
|
+
const ordinal = ordinalBase + send.ordinal;
|
|
84
|
+
if (ordinal > physicalSendsObserved) physicalSendsObserved = ordinal;
|
|
85
|
+
const recovery = send.recovery ?? defaultRecovery;
|
|
86
|
+
requestOnPhysicalSend?.({ ordinal, ...(recovery ? { recovery } : {}) });
|
|
87
|
+
};
|
|
65
88
|
|
|
66
89
|
const build = async (
|
|
67
90
|
parsed: OcxParsedRequest,
|
|
@@ -208,6 +231,9 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
208
231
|
retryBodyReservation.commitRetained();
|
|
209
232
|
retryBodyRetained = true;
|
|
210
233
|
budget.releaseRetained(retryBodyUpperBound - retryBodyBytes, { kind: "request_copies" });
|
|
234
|
+
// Fixed before the rebuild dispatches, so the leg's ordinals continue the first attempt's
|
|
235
|
+
// sequence even though this call's own counter restarts at 1.
|
|
236
|
+
const fallbackOrdinalBase = physicalSendsObserved;
|
|
211
237
|
const response = await fetchKiroWithRetry(retry.request, {
|
|
212
238
|
abortSignal: requestAbortSignal,
|
|
213
239
|
returnRawErrors: true,
|
|
@@ -215,6 +241,12 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
215
241
|
// The text-fallback rebuild used to construct a fresh context and drop the budget,
|
|
216
242
|
// so everything after the first send escaped the per-request cap.
|
|
217
243
|
...(requestSendBudget ? { sendBudget: requestSendBudget } : {}),
|
|
244
|
+
// And reported nothing, so the sends it paid for were invisible. Its own first send is
|
|
245
|
+
// the completion retry itself: the first attempt produced progress without a final
|
|
246
|
+
// answer, which is the same recovery class the generic empty-completion guard records.
|
|
247
|
+
...(requestOnPhysicalSend
|
|
248
|
+
? { onPhysicalSend: (send: KiroPhysicalSend) => forwardPhysicalSend(send, fallbackOrdinalBase, "empty-completion") }
|
|
249
|
+
: {}),
|
|
218
250
|
});
|
|
219
251
|
return {
|
|
220
252
|
response,
|
|
@@ -286,7 +318,16 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
286
318
|
// both the first Kiro request and its one allowed completion retry.
|
|
287
319
|
if (ctx?.abortSignal) requestAbortSignal = ctx.abortSignal;
|
|
288
320
|
if (ctx?.sendBudget) requestSendBudget = ctx.sendBudget;
|
|
289
|
-
|
|
321
|
+
if (ctx?.onPhysicalSend) requestOnPhysicalSend = ctx.onPhysicalSend;
|
|
322
|
+
// Reset per fetch call, because `ordinal` is defined within one call and the caller records
|
|
323
|
+
// ordinal 1 of each new attempt itself. The text fallback that follows this attempt then
|
|
324
|
+
// continues THIS attempt's sequence rather than an earlier one's.
|
|
325
|
+
physicalSendsObserved = 0;
|
|
326
|
+
// Routed through the same forwarder as the fallback so both legs share one ordinal
|
|
327
|
+
// sequence; a context without an observer is passed through untouched.
|
|
328
|
+
return fetchKiroWithRetry(request, requestOnPhysicalSend
|
|
329
|
+
? { ...ctx, onPhysicalSend: (send: KiroPhysicalSend) => forwardPhysicalSend(send, 0) }
|
|
330
|
+
: ctx);
|
|
290
331
|
},
|
|
291
332
|
|
|
292
333
|
formatErrorBody(status: number, headers: Headers, payloadText: string): string {
|
|
@@ -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 };
|