@iislee/opencodex 2.31.0 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
package/src/adapters/image.ts
CHANGED
|
@@ -18,6 +18,6 @@ export function parseDataUrl(url: string): { mediaType: string; base64: string }
|
|
|
18
18
|
*/
|
|
19
19
|
export function contentPartsToText(content: string | OcxContentPart[]): string {
|
|
20
20
|
if (typeof content === "string") return content;
|
|
21
|
-
const text = content.map(p =>
|
|
21
|
+
const text = content.map(p => p.type === "text" ? p.text : p.type === "image" ? "[image]" : "[video]").join("");
|
|
22
22
|
return text || "[image]";
|
|
23
23
|
}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export const KIRO_COMPLETION_TOOL_NAME = "codex_kiro_final_answer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Request-scoped CodeWhisperer service profile for AWS Builder ID accounts.
|
|
5
|
+
*
|
|
6
|
+
* Builder ID is a personal identity with no AWS account behind it, so AWS never mints an
|
|
7
|
+
* account-scoped `profile/<id>` ARN for it. The Kiro CLI resolves this the same way: it carries
|
|
8
|
+
* this fixed service profile on Builder ID requests. The embedded account id is Amazon's own, not
|
|
9
|
+
* the user's, which is why sending it is not the same as synthesizing an account identity.
|
|
10
|
+
*
|
|
11
|
+
* Request-scoped is load-bearing. This value must never be persisted into `KiroOAuthMetadata`,
|
|
12
|
+
* never seed region inference (it is `us-east-1` and would pin every Builder ID account there),
|
|
13
|
+
* and never participate in account matching.
|
|
14
|
+
*/
|
|
15
|
+
export const KIRO_BUILDER_ID_SERVICE_PROFILE_ARN =
|
|
16
|
+
"arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX";
|
|
2
17
|
export const KIRO_CONTINUATION_MESSAGE =
|
|
3
18
|
"Continue from the prior conversation. Do not quote or mention this instruction.";
|
|
4
19
|
export const KIRO_COMPLETION_RETRY_MESSAGE =
|
|
@@ -7,6 +22,18 @@ export const KIRO_COMPLETION_RETRY_MESSAGE =
|
|
|
7
22
|
export const KIRO_TOOL_RESULT_CARRIER_MESSAGE = "The requested tool result is attached.";
|
|
8
23
|
export const KIRO_EMPTY_TOOL_RESULT_MESSAGE = "The tool completed without textual output.";
|
|
9
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Placeholder for the user turn Kiro requires after an assistant turn that ALREADY delivered its
|
|
27
|
+
* final answer.
|
|
28
|
+
*
|
|
29
|
+
* The protocol needs a trailing user turn, but the usual continuation/retry text instructs the
|
|
30
|
+
* model to keep working, which reopens a finished task and reads as a still-open goal. This states
|
|
31
|
+
* the delivered state and explicitly withholds a new request, so the turn stays structurally valid
|
|
32
|
+
* without asking for more work.
|
|
33
|
+
*/
|
|
34
|
+
export const KIRO_ANSWER_DELIVERED_MESSAGE =
|
|
35
|
+
"The previous final answer was delivered to the user and that task is closed. No new request has been made yet. Do not repeat, revise, or continue that work; wait for the user's next instruction.";
|
|
36
|
+
|
|
10
37
|
export const KIRO_COMPLETION_INSTRUCTIONS =
|
|
11
38
|
`When tools are available, ordinary assistant text is mid-task commentary and does not end the turn. Continue using tools after progress updates. When the task is fully complete and no more tool calls are needed, call ${KIRO_COMPLETION_TOOL_NAME} exactly once with the complete user-facing final answer in \`answer\`. Do not provide the final answer as ordinary assistant text.`;
|
|
12
39
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OcxParsedRequest, OcxTool } from "../types";
|
|
2
2
|
import { namespacedToolName } from "../types";
|
|
3
3
|
import { normalizeKiroModelId } from "../providers/kiro-models";
|
|
4
|
+
import { isCodexCodeModeExecTool } from "./tool-catalog-nudge";
|
|
4
5
|
import { createKiroToolNameRegistry, type KiroToolNameRegistry } from "./kiro-wire";
|
|
5
6
|
|
|
6
7
|
const MAX_KIRO_TOOL_DESCRIPTION_UNVERIFIED = 1024;
|
|
@@ -103,7 +104,7 @@ function ensureRootObjectType(schema: unknown): Record<string, unknown> {
|
|
|
103
104
|
// Seed with the root's own properties/required so a schema like
|
|
104
105
|
// { type:"object", properties:{path}, required:["path"], oneOf:[...] } keeps them.
|
|
105
106
|
if (obj.properties && typeof obj.properties === "object") {
|
|
106
|
-
Object.assign(props,
|
|
107
|
+
Object.assign(props, sanitizeSchemaMap(obj.properties) as Record<string, unknown>);
|
|
107
108
|
}
|
|
108
109
|
if (Array.isArray(obj.required)) {
|
|
109
110
|
for (const r of obj.required) if (typeof r === "string") required.add(r);
|
|
@@ -118,7 +119,7 @@ function ensureRootObjectType(schema: unknown): Record<string, unknown> {
|
|
|
118
119
|
if (!variant || typeof variant !== "object" || Array.isArray(variant)) continue;
|
|
119
120
|
const v = variant as Record<string, unknown>;
|
|
120
121
|
if (v.properties && typeof v.properties === "object") {
|
|
121
|
-
Object.assign(props,
|
|
122
|
+
Object.assign(props, sanitizeSchemaMap(v.properties) as Record<string, unknown>);
|
|
122
123
|
}
|
|
123
124
|
if (mergeRequired && Array.isArray(v.required)) {
|
|
124
125
|
for (const r of v.required) if (typeof r === "string") required.add(r);
|
|
@@ -172,6 +173,18 @@ function omittedToolCatalogNotice(kept: number, omitted: readonly OcxTool[], reg
|
|
|
172
173
|
return `[opencodex] Kiro's outbound catalog budget allows ${kept} of ${kept + omitted.length} client tools this turn. Omitted and unavailable this turn: ${summary}.`;
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
function boundedCatalogPriority(tool: OcxTool): number {
|
|
177
|
+
if (tool.loadedFromToolSearch) return 0;
|
|
178
|
+
// Codex code mode reaches shell, file edits, apply_patch and every MCP helper ONLY as nested
|
|
179
|
+
// `tools.<name>(...)` calls inside this one tool. Dropping it does not shrink the catalog, it
|
|
180
|
+
// makes the rest of the catalog uncallable -- so it outranks the search gateway and filler.
|
|
181
|
+
// It stays BEHIND `loadedFromToolSearch` because those are tools the model asked for by name
|
|
182
|
+
// this turn (#2475). Cursor pins its execution path the same way (request-builder.ts, #399).
|
|
183
|
+
if (isCodexCodeModeExecTool(tool)) return 1;
|
|
184
|
+
if (tool.toolSearch) return 2;
|
|
185
|
+
return 3;
|
|
186
|
+
}
|
|
187
|
+
|
|
175
188
|
export function convertKiroToolContext(
|
|
176
189
|
parsed: OcxParsedRequest,
|
|
177
190
|
registry: KiroToolNameRegistry = createKiroToolNameRegistry(),
|
|
@@ -181,9 +194,7 @@ export function convertKiroToolContext(
|
|
|
181
194
|
// Validate every listed name even when tool_choice:none emulates a tool-free turn.
|
|
182
195
|
for (const tool of tools) registry.alias(namespacedToolName(tool.namespace, tool.name));
|
|
183
196
|
const effectiveTools = parsed.options.toolChoice === "none" ? [] : tools;
|
|
184
|
-
const
|
|
185
|
-
let omittedAt = effectiveTools.length;
|
|
186
|
-
for (const [index, tool] of effectiveTools.entries()) {
|
|
197
|
+
const convertedEntries = effectiveTools.map((tool, index) => {
|
|
187
198
|
const description = tool.description || `Tool: ${tool.name}`;
|
|
188
199
|
// Send the full namespaced wire name (e.g. mcp__chrome-devtools__navigate_page) so Kiro echoes
|
|
189
200
|
// it back; the bridge's toolNsMap is keyed by this name and restores the MCP namespace Codex
|
|
@@ -198,19 +209,47 @@ export function convertKiroToolContext(
|
|
|
198
209
|
inputSchema: { json: ensureRootObjectType(sanitizeKiroSchema(tool.parameters ?? {})) },
|
|
199
210
|
},
|
|
200
211
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
return { tool, index, converted };
|
|
213
|
+
});
|
|
214
|
+
const exceedsBudget = convertedEntries.length > MAX_KIRO_TOOL_COUNT
|
|
215
|
+
|| serializedToolCatalogBytes(convertedEntries.map(entry => entry.converted)) > MAX_KIRO_TOOL_CATALOG_BYTES;
|
|
216
|
+
const candidates = exceedsBudget
|
|
217
|
+
? convertedEntries.toSorted((a, b) => boundedCatalogPriority(a.tool) - boundedCatalogPriority(b.tool) || a.index - b.index)
|
|
218
|
+
: convertedEntries;
|
|
219
|
+
// Reserve a seat for the code-mode execution path before filling the rest.
|
|
220
|
+
//
|
|
221
|
+
// Priority alone cannot save it. `loadedFromToolSearch` tools outrank it and arrive unbounded
|
|
222
|
+
// (the Responses parser pushes every `tool_search_output` spec), so a session that accumulated
|
|
223
|
+
// MAX_KIRO_TOOL_COUNT loaded tools would exhaust the budget before reaching tier 1 and drop the
|
|
224
|
+
// one tool through which all of them are actually callable.
|
|
225
|
+
//
|
|
226
|
+
// Reservation rather than eviction: this lowers the room the fill loop sees, so it admits one
|
|
227
|
+
// fewer tool. It never removes a tool that already fit, which is what keeps #2475's loaded-result
|
|
228
|
+
// guarantee intact -- Cursor's `evictNonExecutionPath` exempts only the execution path and could
|
|
229
|
+
// evict a loaded tool instead.
|
|
230
|
+
const reserved = candidates.find(entry => isCodexCodeModeExecTool(entry.tool));
|
|
231
|
+
const admitted = new Set<number>();
|
|
232
|
+
const filled: unknown[] = [];
|
|
233
|
+
for (const entry of candidates) {
|
|
234
|
+
if (entry === reserved) continue;
|
|
235
|
+
// Measure the projected FINAL array: the byte budget is computed over the serialized array, so
|
|
236
|
+
// subtracting a standalone size would misjudge it by the separators JSON adds between entries.
|
|
237
|
+
const projected = reserved ? [...filled, entry.converted, reserved.converted] : [...filled, entry.converted];
|
|
204
238
|
if (
|
|
205
|
-
|
|
206
|
-
|| serializedToolCatalogBytes(
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
convertedTools.push(converted);
|
|
239
|
+
projected.length > MAX_KIRO_TOOL_COUNT
|
|
240
|
+
|| serializedToolCatalogBytes(projected) > MAX_KIRO_TOOL_CATALOG_BYTES
|
|
241
|
+
) break;
|
|
242
|
+
filled.push(entry.converted);
|
|
243
|
+
admitted.add(entry.index);
|
|
212
244
|
}
|
|
213
|
-
|
|
245
|
+
if (reserved) admitted.add(reserved.index);
|
|
246
|
+
// Rebuild in sorted-candidate order so the wire order stays loaded -> exec -> gateway -> filler.
|
|
247
|
+
// Pushing the reserved entry after the loop would place it last instead.
|
|
248
|
+
const convertedTools = candidates.filter(entry => admitted.has(entry.index)).map(entry => entry.converted);
|
|
249
|
+
// Derive omissions by set difference. The old `candidates.slice(omittedAt)` assumed every
|
|
250
|
+
// candidate after the first rejection was omitted, which stops being true once one of them was
|
|
251
|
+
// reserved and admitted: the notice would name `exec` unavailable while it is on the wire.
|
|
252
|
+
const omittedTools = candidates.filter(entry => !admitted.has(entry.index)).map(entry => entry.tool);
|
|
214
253
|
return {
|
|
215
254
|
tools: convertedTools,
|
|
216
255
|
systemAdditions: omittedTools.length > 0 ? [omittedToolCatalogNotice(convertedTools.length, omittedTools, registry)] : [],
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { decodeEventStream } from "../lib/eventstream-decoder";
|
|
2
2
|
import { estimateTokens } from "../lib/token-estimate";
|
|
3
3
|
import { debugProviderDiagnostic } from "../lib/debug";
|
|
4
|
-
import { resolveKiroApiRegion,
|
|
4
|
+
import { resolveKiroApiRegion, resolveKiroRequestProfile } from "../oauth/kiro";
|
|
5
5
|
import { KIRO_MODEL_CONTEXT_WINDOWS, normalizeKiroModelId } from "../providers/kiro-models";
|
|
6
6
|
import { modelRecordValue } from "../reasoning-effort";
|
|
7
7
|
import { parseKiroEvent } from "./kiro-events";
|
|
@@ -33,17 +33,21 @@ import type {
|
|
|
33
33
|
OcxTextContent,
|
|
34
34
|
OcxToolCall,
|
|
35
35
|
OcxToolResultMessage,
|
|
36
|
+
OcxTool,
|
|
36
37
|
OcxUsage,
|
|
37
38
|
} from "../types";
|
|
39
|
+
import { hasRecordedTrailingDeliveredFinalAnswer } from "../responses/turn-termination";
|
|
38
40
|
import type { ProviderAdapter } from "./base";
|
|
39
41
|
import type { AdapterFetchContext, AdapterRequest } from "./base";
|
|
40
42
|
import { extractKiroImages, normalizeKiroImages, type KiroImage } from "./kiro-images";
|
|
41
43
|
import { sniffImageDimensions } from "./anthropic-image-guard";
|
|
42
44
|
import { fetchKiroWithRetry, noteKiroTransientThrottle } from "./kiro-retry";
|
|
43
45
|
import { convertKiroToolContext } from "./kiro-tools";
|
|
46
|
+
import { normalizeEmptyExecToolResultText } from "./exec-tool-result-normalize";
|
|
44
47
|
import { identifyRoutedModel } from "./identity";
|
|
45
|
-
import { buildNonOpenAIToolCatalogNudgeFromNames } from "./tool-catalog-nudge";
|
|
48
|
+
import { buildNonOpenAIToolCatalogNudgeFromNames, isBareShellBridgeTool, isCodexCodeModeExecTool } from "./tool-catalog-nudge";
|
|
46
49
|
import {
|
|
50
|
+
KIRO_ANSWER_DELIVERED_MESSAGE,
|
|
47
51
|
KIRO_COMPLETION_INSTRUCTIONS,
|
|
48
52
|
KIRO_COMPLETION_RETRY_MESSAGE,
|
|
49
53
|
KIRO_COMPLETION_TOOL_NAME,
|
|
@@ -318,21 +322,78 @@ function validateKiroCapabilities(parsed: OcxParsedRequest): void {
|
|
|
318
322
|
if (choice !== undefined && choice !== "auto" && choice !== "none") {
|
|
319
323
|
throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
|
|
320
324
|
}
|
|
321
|
-
if (parsed.options.parallelToolCalls === true) {
|
|
322
|
-
throw new Error("Kiro does not support parallel tool calls");
|
|
323
|
-
}
|
|
324
325
|
if (parsed.options.serviceTier !== undefined) {
|
|
325
326
|
throw new Error("Kiro does not support service tiers");
|
|
326
327
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
328
|
+
// Structured output is a real contract Kiro cannot honour: the wire has no
|
|
329
|
+
// schema-constrained response mode, so a caller expecting parseable JSON would receive
|
|
330
|
+
// prose and fail downstream. Refuse it.
|
|
331
|
+
//
|
|
332
|
+
// The rest of the Responses `text` object is not that. `text.verbosity` is a length
|
|
333
|
+
// preference and `text.format: {type:"text"}` is ordinary prose — the default output
|
|
334
|
+
// mode, which no capability flag governs and every correct client may send. Testing
|
|
335
|
+
// `_rawBody.text !== undefined` refused those turns for the mere PRESENCE of the key,
|
|
336
|
+
// the same mistake db040e70f removed one condition earlier where a permissive
|
|
337
|
+
// `parallel_tool_calls` hint was read as a requirement.
|
|
338
|
+
//
|
|
339
|
+
// Nothing needs stripping the way openai-responses strips a no-op verbosity:
|
|
340
|
+
// buildKiroPayload composes conversationState field by field from `parsed` and never
|
|
341
|
+
// spreads `_rawBody`, so a tolerated control is dropped by construction. The test
|
|
342
|
+
// asserts that absence so it stays true.
|
|
343
|
+
if (parsed._structuredOutput) {
|
|
344
|
+
throw new Error("Kiro does not support Responses structured output");
|
|
330
345
|
}
|
|
331
346
|
}
|
|
332
347
|
|
|
333
348
|
type KiroTurn =
|
|
334
|
-
| {
|
|
335
|
-
|
|
349
|
+
| {
|
|
350
|
+
kind: "user";
|
|
351
|
+
content: string;
|
|
352
|
+
images: KiroImage[];
|
|
353
|
+
toolResults: KiroToolResult[];
|
|
354
|
+
/**
|
|
355
|
+
* True only for the proxy-generated acknowledgement that follows a delivered final answer.
|
|
356
|
+
* A flag rather than a content comparison: a real user message may legitimately quote the
|
|
357
|
+
* same sentence, and treating that as internal state would strip its thinking tags and
|
|
358
|
+
* completion retry.
|
|
359
|
+
*/
|
|
360
|
+
answerDeliveredAck?: boolean;
|
|
361
|
+
}
|
|
362
|
+
| {
|
|
363
|
+
kind: "assistant";
|
|
364
|
+
content: string;
|
|
365
|
+
toolUses: KiroToolUse[];
|
|
366
|
+
redactedReasoning?: string;
|
|
367
|
+
/**
|
|
368
|
+
* True when this assistant turn was the DELIVERED final answer (Responses
|
|
369
|
+
* `phase: "final_answer"`). A trailing assistant turn normally means the model stopped
|
|
370
|
+
* mid-task and needs a continuation prompt, but a delivered final answer already ended its
|
|
371
|
+
* turn — prompting it again restarts finished work as if a goal were still open.
|
|
372
|
+
*/
|
|
373
|
+
finalAnswer?: boolean;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* True when the LAST content-bearing message is an assistant final answer that closed its turn.
|
|
378
|
+
*
|
|
379
|
+
* Mirrors the turn-merge rule: a tool call in that message, or any later user/tool-result message,
|
|
380
|
+
* means work continued, so the turn is no longer terminal. Empty assistant messages are skipped
|
|
381
|
+
* rather than treated as continuation, since they carry no visible turn.
|
|
382
|
+
*/
|
|
383
|
+
function hasTrailingDeliveredFinalAnswer(messages: readonly OcxMessage[], parsed?: OcxParsedRequest): boolean {
|
|
384
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
385
|
+
const msg = messages[i];
|
|
386
|
+
if (msg.role !== "assistant") return false;
|
|
387
|
+
const aMsg = msg as OcxAssistantMessage;
|
|
388
|
+
const hasToolCall = (aMsg.content ?? []).some(part => part.type === "toolCall");
|
|
389
|
+
if (hasToolCall) return false;
|
|
390
|
+
const hasText = (aMsg.content ?? []).some(part => part.type === "text" && part.text.trim());
|
|
391
|
+
if (!hasText) continue;
|
|
392
|
+
return aMsg.phase === "final_answer"
|
|
393
|
+
|| (parsed !== undefined && hasRecordedTrailingDeliveredFinalAnswer(parsed, messages));
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
336
397
|
|
|
337
398
|
function appendTurnText(target: string, next: string): string {
|
|
338
399
|
if (!next) return target;
|
|
@@ -441,8 +502,19 @@ export function buildKiroPayload(
|
|
|
441
502
|
const registry = createKiroToolNameRegistry();
|
|
442
503
|
const toolContext = convertKiroToolContext(parsed, registry);
|
|
443
504
|
const ordinaryTools = toolContext.tools;
|
|
505
|
+
// A turn whose history already ENDS with a delivered final answer has nothing to complete.
|
|
506
|
+
// Leaving completion "required" here would keep advertising codex_kiro_final_answer with its
|
|
507
|
+
// instructions, so the model answers again, or replies with ordinary text and trips the
|
|
508
|
+
// `needsFallback` retry, which ends its payload with KIRO_COMPLETION_RETRY_MESSAGE and reopens
|
|
509
|
+
// the finished task. Suppressing the mode is what actually closes that loop; the neutral
|
|
510
|
+
// acknowledgement below only stops the resume wording.
|
|
511
|
+
//
|
|
512
|
+
// Read from parsed messages because `completionMode` is needed to build the tool catalog, which
|
|
513
|
+
// happens before the turn list exists. `forcedCompletionMode` still wins: the fallback retry
|
|
514
|
+
// passes "text_fallback" explicitly and must not be silently downgraded.
|
|
515
|
+
const trailingDeliveredAnswer = hasTrailingDeliveredFinalAnswer(kiroPayloadMessages(parsed), parsed);
|
|
444
516
|
const completionMode: KiroCompletionMode = forcedCompletionMode
|
|
445
|
-
?? (ordinaryTools.length > 0 ? "required" : "disabled");
|
|
517
|
+
?? (ordinaryTools.length > 0 && !trailingDeliveredAnswer ? "required" : "disabled");
|
|
446
518
|
const kiroTools = completionMode === "disabled"
|
|
447
519
|
? ordinaryTools
|
|
448
520
|
: [...ordinaryTools, kiroCompletionTool()];
|
|
@@ -463,9 +535,32 @@ export function buildKiroPayload(
|
|
|
463
535
|
// name for a tool that was never advertised and pollute the collision domain.
|
|
464
536
|
const advertisedAlias = new Map<string, string>();
|
|
465
537
|
for (const [alias, wireName] of registry.nameMap) advertisedAlias.set(wireName, alias);
|
|
538
|
+
// Code mode is decided on the EMITTED catalog, not the requested list.
|
|
539
|
+
//
|
|
540
|
+
// `freeform` only exists on the requested tool objects -- `kiroToolWireNames` has already
|
|
541
|
+
// reduced the emitted catalog to strings -- so the predicates must read the objects. But the
|
|
542
|
+
// SHAPE that matters is the one the model receives: the count/byte budget can drop a requested
|
|
543
|
+
// `exec_command` while `exec` survives, and scanning the requested list would then find a shell
|
|
544
|
+
// bridge the model cannot call and suppress code mode for a catalog that is code-mode-shaped.
|
|
545
|
+
// Intersecting the two keeps `tool_choice: "none"` and budget omission correct for free: both
|
|
546
|
+
// empty the emitted set, so nothing can be named.
|
|
547
|
+
const emittedToolNames = new Set(kiroToolWireNames(kiroTools));
|
|
548
|
+
const emittedAlias = (tool: OcxTool): string | undefined => {
|
|
549
|
+
const wireName = namespacedToolName(tool.namespace, tool.name);
|
|
550
|
+
// Read the recorded mapping; `registry.alias()` would REGISTER a name here.
|
|
551
|
+
const alias = advertisedAlias.get(wireName) ?? wireName;
|
|
552
|
+
return emittedToolNames.has(alias) ? alias : undefined;
|
|
553
|
+
};
|
|
554
|
+
const requestedTools = parsed.context.tools ?? [];
|
|
555
|
+
const emittedCodeModeExec = requestedTools.find(tool => isCodexCodeModeExecTool(tool) && emittedAlias(tool));
|
|
556
|
+
const emittedShellBridge = requestedTools.some(tool => isBareShellBridgeTool(tool) && emittedAlias(tool));
|
|
557
|
+
const codeModeExecName = emittedCodeModeExec && !emittedShellBridge
|
|
558
|
+
? emittedAlias(emittedCodeModeExec)
|
|
559
|
+
: undefined;
|
|
466
560
|
const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeFromNames(
|
|
467
561
|
kiroToolWireNames(kiroTools),
|
|
468
562
|
name => advertisedAlias.get(name) ?? name,
|
|
563
|
+
codeModeExecName,
|
|
469
564
|
);
|
|
470
565
|
const boundedNudge = toolCatalogNudge ? boundedInjectedInstruction(toolCatalogNudge, injectedChars) : undefined;
|
|
471
566
|
if (boundedNudge) systemParts.push(boundedNudge);
|
|
@@ -486,15 +581,24 @@ export function buildKiroPayload(
|
|
|
486
581
|
turns.push({ kind: "user", content, images: [...images], toolResults: [...toolResults] });
|
|
487
582
|
}
|
|
488
583
|
};
|
|
489
|
-
const pushAssistant = (content: string, toolUses: KiroToolUse[], redactedReasoning?: string): void => {
|
|
584
|
+
const pushAssistant = (content: string, toolUses: KiroToolUse[], redactedReasoning?: string, finalAnswer?: boolean): void => {
|
|
490
585
|
const last = turns.at(-1);
|
|
491
586
|
if (last?.kind === "assistant") {
|
|
492
587
|
last.content = appendTurnText(last.content, content);
|
|
493
588
|
last.toolUses.push(...toolUses);
|
|
494
589
|
// Merged turns keep the newest blob: it covers the reasoning up to the merged turn's end.
|
|
495
590
|
if (redactedReasoning) last.redactedReasoning = redactedReasoning;
|
|
591
|
+
// A merged turn is final only if its LAST component was: commentary appended after a final
|
|
592
|
+
// answer means the model kept working, so the turn is no longer terminal.
|
|
593
|
+
last.finalAnswer = finalAnswer === true;
|
|
496
594
|
} else {
|
|
497
|
-
turns.push({
|
|
595
|
+
turns.push({
|
|
596
|
+
kind: "assistant",
|
|
597
|
+
content,
|
|
598
|
+
toolUses: [...toolUses],
|
|
599
|
+
...(redactedReasoning ? { redactedReasoning } : {}),
|
|
600
|
+
...(finalAnswer ? { finalAnswer: true } : {}),
|
|
601
|
+
});
|
|
498
602
|
}
|
|
499
603
|
};
|
|
500
604
|
|
|
@@ -524,14 +628,24 @@ export function buildKiroPayload(
|
|
|
524
628
|
const hasReasoning = aMsg.content.some(part => part.type === "thinking" && part.thinking.trim());
|
|
525
629
|
if (hasReasoning) continue;
|
|
526
630
|
}
|
|
527
|
-
|
|
631
|
+
// `phase` survives the Responses round trip (parser.ts assistant branch), so a replayed
|
|
632
|
+
// final answer is identifiable here rather than guessed from turn position.
|
|
633
|
+
pushAssistant(text, toolUses, aMsg.kiroRedactedReasoning, aMsg.phase === "final_answer" && toolUses.length === 0);
|
|
528
634
|
} else if (msg.role === "toolResult") {
|
|
529
635
|
const tr = msg as OcxToolResultMessage;
|
|
530
636
|
if (tr.containsEncryptedContent) {
|
|
531
637
|
throw new Error(`Kiro cannot translate encrypted output for tool call ${JSON.stringify(tr.toolCallId)}`);
|
|
532
638
|
}
|
|
533
639
|
const text = userContentText(tr.content);
|
|
534
|
-
|
|
640
|
+
// An empty code-mode exec result needs the SPECIFIC reason, not the generic fallback: the
|
|
641
|
+
// model otherwise reads a blank result, concludes its earlier context was lost, and restarts
|
|
642
|
+
// the task instead of calling text()/notify(). Checked before `text.trim()` because the
|
|
643
|
+
// wrapper form ("Script completed\nWall time ...\nOutput:\n") is non-blank and would
|
|
644
|
+
// otherwise pass through as if it were real output.
|
|
645
|
+
const resultText = normalizeEmptyExecToolResultText(text, {
|
|
646
|
+
toolName: tr.toolName,
|
|
647
|
+
toolNamespace: tr.toolNamespace,
|
|
648
|
+
}) ?? (text.trim() ? text : KIRO_EMPTY_TOOL_RESULT_MESSAGE);
|
|
535
649
|
const images = extractKiroImages(tr.content);
|
|
536
650
|
const toolUseId = normalizeToolId(tr.toolCallId);
|
|
537
651
|
if (!priorCalls.has(toolUseId)) {
|
|
@@ -552,12 +666,23 @@ export function buildKiroPayload(
|
|
|
552
666
|
if (turns.length === 0 || turns[0].kind === "assistant") {
|
|
553
667
|
turns.unshift({ kind: "user", content: KIRO_CONTINUATION_MESSAGE, images: [], toolResults: [] });
|
|
554
668
|
}
|
|
555
|
-
|
|
669
|
+
// Kiro requires the request to end with a user turn, so a trailing assistant turn always gets
|
|
670
|
+
// one appended (the pop below throws otherwise). What that turn SAYS is the load-bearing part.
|
|
671
|
+
//
|
|
672
|
+
// Normally a trailing assistant turn means the model stopped mid-task, and a continuation/retry
|
|
673
|
+
// prompt is correct. A DELIVERED final answer is the exception: the turn already ended, and
|
|
674
|
+
// telling that model to "continue" or to call the completion tool again reopens finished work —
|
|
675
|
+
// the completed-task-behaves-like-an-open-goal loop. It gets a neutral acknowledgement instead:
|
|
676
|
+
// structurally valid, but carrying no instruction to resume.
|
|
677
|
+
const trailing = turns.at(-1);
|
|
678
|
+
if (trailing?.kind === "assistant") {
|
|
679
|
+
const resumeText = completionMode === "text_fallback" ? KIRO_COMPLETION_RETRY_MESSAGE : KIRO_CONTINUATION_MESSAGE;
|
|
556
680
|
turns.push({
|
|
557
681
|
kind: "user",
|
|
558
|
-
content:
|
|
682
|
+
content: trailing.finalAnswer ? KIRO_ANSWER_DELIVERED_MESSAGE : resumeText,
|
|
559
683
|
images: [],
|
|
560
684
|
toolResults: [],
|
|
685
|
+
...(trailing.finalAnswer ? { answerDeliveredAck: true } : {}),
|
|
561
686
|
});
|
|
562
687
|
}
|
|
563
688
|
|
|
@@ -573,6 +698,8 @@ export function buildKiroPayload(
|
|
|
573
698
|
|
|
574
699
|
const currentTurn = turns.pop();
|
|
575
700
|
if (!currentTurn || currentTurn.kind !== "user") throw new Error("Kiro request must end with a user turn");
|
|
701
|
+
// Survives the pop as state, so the checks below never infer intent from user-supplied text.
|
|
702
|
+
const answerDeliveredAck = currentTurn.answerDeliveredAck === true;
|
|
576
703
|
const toEntry = (turn: KiroTurn): KiroHistoryEntry => turn.kind === "assistant"
|
|
577
704
|
? {
|
|
578
705
|
assistantResponseMessage: {
|
|
@@ -603,10 +730,17 @@ export function buildKiroPayload(
|
|
|
603
730
|
currentUim.userInputMessageContext = { ...(currentUim.userInputMessageContext ?? {}), tools: kiroTools };
|
|
604
731
|
}
|
|
605
732
|
if (completionMode === "text_fallback") {
|
|
606
|
-
|
|
733
|
+
// Never append the retry instruction onto the answer-delivered acknowledgement: it exists
|
|
734
|
+
// precisely to avoid asking a finished turn for another completion call, and appending here
|
|
735
|
+
// would reinstate the loop it prevents.
|
|
736
|
+
if (currentUim.content !== KIRO_COMPLETION_RETRY_MESSAGE && !answerDeliveredAck) {
|
|
607
737
|
currentUim.content = appendTurnText(currentUim.content, KIRO_COMPLETION_RETRY_MESSAGE);
|
|
608
738
|
}
|
|
609
|
-
} else if (
|
|
739
|
+
} else if (
|
|
740
|
+
!currentUim.userInputMessageContext?.toolResults
|
|
741
|
+
&& currentUim.content !== KIRO_CONTINUATION_MESSAGE
|
|
742
|
+
&& !answerDeliveredAck
|
|
743
|
+
) {
|
|
610
744
|
currentUim.content = injectKiroThinkingTags(currentUim.content, parsed);
|
|
611
745
|
}
|
|
612
746
|
|
|
@@ -1726,12 +1860,19 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1726
1860
|
throw new Error("kiro token missing — run ocx login kiro");
|
|
1727
1861
|
}
|
|
1728
1862
|
const region = resolveKiroApiRegion(parsed._kiroAuthContext);
|
|
1729
|
-
|
|
1863
|
+
// Request-scoped: an AWS Builder ID account has no profile of its own and resolves to Kiro's
|
|
1864
|
+
// fixed service profile here, without that value ever becoming the account's stored identity.
|
|
1865
|
+
const requestProfile = resolveKiroRequestProfile(parsed._kiroAuthContext);
|
|
1866
|
+
const resolvedProfileArn = requestProfile.profileArn;
|
|
1730
1867
|
const isApiKey = provider.apiKey.trim().startsWith("ksk_");
|
|
1731
1868
|
const profileArn = isApiKey ? undefined : resolvedProfileArn;
|
|
1732
|
-
// Builder ID and Kiro API keys
|
|
1733
|
-
//
|
|
1734
|
-
|
|
1869
|
+
// Builder ID and Kiro API keys are accepted only on Kiro's CLI request path; enterprise
|
|
1870
|
+
// profiles retain the IDE-shaped request. Builder ID now carries a profile ARN, so a truthy
|
|
1871
|
+
// `profileArn` no longer implies "enterprise". The wire path reads the resolver's own verdict
|
|
1872
|
+
// rather than re-deriving it, so the accountless path — where the auth type comes from the
|
|
1873
|
+
// local import, not the request context — cannot send the fallback inside an IDE-shaped call.
|
|
1874
|
+
const isBuilderId = requestProfile.builderIdFallback;
|
|
1875
|
+
const wireClient: KiroWireClient = isApiKey || isBuilderId || !profileArn ? "cli" : "ide";
|
|
1735
1876
|
const fp = fingerprint().slice(0, 64);
|
|
1736
1877
|
const headers: Record<string, string> = wireClient === "cli" ? {
|
|
1737
1878
|
authorization: `Bearer ${provider.apiKey}`,
|
|
@@ -1863,6 +2004,24 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
1863
2004
|
|
|
1864
2005
|
return {
|
|
1865
2006
|
name: "kiro",
|
|
2007
|
+
// A replayed history that already ENDS with a delivered final answer has nothing to ask Kiro.
|
|
2008
|
+
// Before this hook the adapter still appended a trailing user turn — a neutral acknowledgement,
|
|
2009
|
+
// but structurally still a prompt — and performed a real inference, so the model answered the
|
|
2010
|
+
// closed task again and the finished turn behaved like a still-open goal.
|
|
2011
|
+
//
|
|
2012
|
+
// Suppressing the completion contract (above) removed the instruction to complete; it could not
|
|
2013
|
+
// remove the inference. This is the boundary: no request is built, nothing is sent, and no token
|
|
2014
|
+
// estimate is recorded.
|
|
2015
|
+
//
|
|
2016
|
+
// The forced-fallback build is deliberately NOT consulted here: this hook runs on the inbound
|
|
2017
|
+
// turn only, and the adapter-owned bounded retry passes "text_fallback" through `build`
|
|
2018
|
+
// directly, never through this path.
|
|
2019
|
+
localTerminal(parsed: OcxParsedRequest) {
|
|
2020
|
+
return hasTrailingDeliveredFinalAnswer(kiroPayloadMessages(parsed), parsed)
|
|
2021
|
+
? { reason: "kiro_final_answer_already_delivered" }
|
|
2022
|
+
: undefined;
|
|
2023
|
+
},
|
|
2024
|
+
|
|
1866
2025
|
async buildRequest(parsed: OcxParsedRequest, incoming) {
|
|
1867
2026
|
const built = await build(parsed);
|
|
1868
2027
|
modelId = parsed.modelId;
|