@bitkyc08/opencodex 2.52.0 → 2.53.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/gui/dist/assets/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-BlO4Yl6q.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { debugProviderDiagnostic } from "../../lib/debug";
|
|
2
|
+
import { isDebugEnabled } from "../../lib/debug-settings";
|
|
3
|
+
import {
|
|
4
|
+
releaseTranslatedEvent,
|
|
5
|
+
retainTranslatedEvent,
|
|
6
|
+
type TranslatorBudget,
|
|
7
|
+
} from "../../lib/translator-budget";
|
|
8
|
+
import { resolveKiroApiRegion, resolveKiroRequestProfile } from "../../oauth/kiro";
|
|
9
|
+
import type {
|
|
10
|
+
AdapterEvent,
|
|
11
|
+
OcxParsedRequest,
|
|
12
|
+
OcxProviderConfig,
|
|
13
|
+
} from "../../types";
|
|
14
|
+
import type { ProviderAdapter } from "../base";
|
|
15
|
+
import type { AdapterFetchContext, AdapterRequest } from "../base";
|
|
16
|
+
import { safeKiroHttpErrorMessage } from "../kiro-errors";
|
|
17
|
+
import { calibrateKiroEstimate } from "../kiro-calibration";
|
|
18
|
+
import { normalizeKiroImages } from "../kiro-images";
|
|
19
|
+
import type { KiroCompletionMode } from "../kiro-constants";
|
|
20
|
+
import { fetchKiroWithRetry } from "../kiro-retry";
|
|
21
|
+
import { fingerprint, invocationId, osTag } from "../kiro-wire";
|
|
22
|
+
import { hasTrailingDeliveredFinalAnswer } from "./conversation";
|
|
23
|
+
import { buildKiroPayload } from "./payload";
|
|
24
|
+
import {
|
|
25
|
+
jsonStringSerializedUtf8Bytes,
|
|
26
|
+
parseKiroStream,
|
|
27
|
+
type KiroFallbackFactory,
|
|
28
|
+
} from "./stream";
|
|
29
|
+
import {
|
|
30
|
+
estimateKiroInputTokens,
|
|
31
|
+
estimateKiroLogInputTokens,
|
|
32
|
+
estimateKiroPayloadInputTokens,
|
|
33
|
+
kiroPayloadMessages,
|
|
34
|
+
kiroUpstreamContextWindow,
|
|
35
|
+
} from "./usage";
|
|
36
|
+
import {
|
|
37
|
+
AMZ_TARGET,
|
|
38
|
+
KIRO_FALLBACK_SERIALIZATION_ENVELOPE_BYTES,
|
|
39
|
+
KIRO_IDE_VERSION,
|
|
40
|
+
kiroCliUserAgent,
|
|
41
|
+
kiroRuntimeEndpoint,
|
|
42
|
+
NODE_VERSION,
|
|
43
|
+
SDK_VERSION,
|
|
44
|
+
type KiroWireClient,
|
|
45
|
+
} from "./wire";
|
|
46
|
+
|
|
47
|
+
// Adapter
|
|
48
|
+
export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
49
|
+
// Per-request closure (resolveAdapter builds a fresh adapter per request — server.ts:440 — so this
|
|
50
|
+
// is race-free) carrying the heuristic input-token estimate from buildRequest into the stream.
|
|
51
|
+
let inputTokens = 0;
|
|
52
|
+
let contextInputEstimate = 0;
|
|
53
|
+
let modelId: string | undefined;
|
|
54
|
+
let contextWindow: number | undefined;
|
|
55
|
+
let toolNameMap: Map<string, string> | undefined;
|
|
56
|
+
let conversationId: string | undefined;
|
|
57
|
+
let completionMode: KiroCompletionMode = "disabled";
|
|
58
|
+
let requestSnapshot: OcxParsedRequest | undefined;
|
|
59
|
+
let firstRequestBodyBytes = 0;
|
|
60
|
+
let requestAbortSignal: AbortSignal | undefined;
|
|
61
|
+
|
|
62
|
+
const build = async (
|
|
63
|
+
parsed: OcxParsedRequest,
|
|
64
|
+
forcedCompletionMode?: KiroCompletionMode,
|
|
65
|
+
): Promise<{
|
|
66
|
+
request: AdapterRequest;
|
|
67
|
+
nameMap: Map<string, string>;
|
|
68
|
+
conversationId: string;
|
|
69
|
+
completionMode: KiroCompletionMode;
|
|
70
|
+
inputTokens: number;
|
|
71
|
+
contextInputEstimate: number;
|
|
72
|
+
}> => {
|
|
73
|
+
if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
|
|
74
|
+
throw new Error("kiro token missing — run ocx login kiro");
|
|
75
|
+
}
|
|
76
|
+
const region = resolveKiroApiRegion(parsed._kiroAuthContext);
|
|
77
|
+
// Request-scoped: an AWS Builder ID account has no profile of its own and resolves to Kiro's
|
|
78
|
+
// fixed service profile here, without that value ever becoming the account's stored identity.
|
|
79
|
+
const requestProfile = resolveKiroRequestProfile(parsed._kiroAuthContext);
|
|
80
|
+
const resolvedProfileArn = requestProfile.profileArn;
|
|
81
|
+
const isApiKey = provider.apiKey.trim().startsWith("ksk_");
|
|
82
|
+
const profileArn = isApiKey ? undefined : resolvedProfileArn;
|
|
83
|
+
// Builder ID and Kiro API keys are accepted only on Kiro's CLI request path; enterprise
|
|
84
|
+
// profiles retain the IDE-shaped request. Builder ID now carries a profile ARN, so a truthy
|
|
85
|
+
// `profileArn` no longer implies "enterprise". The wire path reads the resolver's own verdict
|
|
86
|
+
// rather than re-deriving it, so the accountless path — where the auth type comes from the
|
|
87
|
+
// local import, not the request context — cannot send the fallback inside an IDE-shaped call.
|
|
88
|
+
const isBuilderId = requestProfile.builderIdFallback;
|
|
89
|
+
const wireClient: KiroWireClient = isApiKey || isBuilderId || !profileArn ? "cli" : "ide";
|
|
90
|
+
const fp = fingerprint().slice(0, 64);
|
|
91
|
+
const headers: Record<string, string> = wireClient === "cli" ? {
|
|
92
|
+
authorization: `Bearer ${provider.apiKey}`,
|
|
93
|
+
"content-type": "application/x-amz-json-1.0",
|
|
94
|
+
accept: "*/*",
|
|
95
|
+
"x-amz-target": AMZ_TARGET,
|
|
96
|
+
"user-agent": kiroCliUserAgent(true),
|
|
97
|
+
"x-amz-user-agent": kiroCliUserAgent(false),
|
|
98
|
+
"x-amzn-codewhisperer-optout": "true",
|
|
99
|
+
"amz-sdk-request": "attempt=1; max=3",
|
|
100
|
+
"amz-sdk-invocation-id": invocationId(),
|
|
101
|
+
...(isApiKey ? { tokentype: "API_KEY" } : {}),
|
|
102
|
+
} : {
|
|
103
|
+
authorization: `Bearer ${provider.apiKey}`,
|
|
104
|
+
"content-type": "application/x-amz-json-1.0",
|
|
105
|
+
accept: "application/vnd.amazon.eventstream",
|
|
106
|
+
"x-amz-target": AMZ_TARGET,
|
|
107
|
+
"user-agent": `aws-sdk-js/${SDK_VERSION} ua/2.1 os/${osTag()} lang/js md/nodejs#${NODE_VERSION} api/codewhispererstreaming#${SDK_VERSION} m/E KiroIDE-${KIRO_IDE_VERSION}-${fp}`,
|
|
108
|
+
"x-amz-user-agent": `aws-sdk-js/${SDK_VERSION} KiroIDE-${KIRO_IDE_VERSION}-${fp}`,
|
|
109
|
+
"x-amzn-codewhisperer-optout": "true",
|
|
110
|
+
"x-amzn-kiro-agent-mode": "vibe",
|
|
111
|
+
"amz-sdk-invocation-id": invocationId(),
|
|
112
|
+
};
|
|
113
|
+
if (profileArn) headers["x-amzn-kiro-profile-arn"] = profileArn;
|
|
114
|
+
const built = buildKiroPayload(parsed, profileArn, forcedCompletionMode, wireClient);
|
|
115
|
+
await normalizeKiroImages(built.payload);
|
|
116
|
+
// Apply what earlier turns of THIS conversation measured. An unseen conversation is
|
|
117
|
+
// unchanged, so a first turn behaves exactly as it would without calibration.
|
|
118
|
+
const rawContextInputEstimate = estimateKiroPayloadInputTokens(built.payload, parsed.modelId);
|
|
119
|
+
const contextInputEstimate = calibrateKiroEstimate(built.conversationId, rawContextInputEstimate);
|
|
120
|
+
const body = JSON.stringify(built.payload);
|
|
121
|
+
// Every field below is evaluated before the call, so an unguarded call re-encodes the
|
|
122
|
+
// whole request body on each request even when provider debug is off. Gate the details.
|
|
123
|
+
if (isDebugEnabled()) {
|
|
124
|
+
debugProviderDiagnostic("kiro", "request", {
|
|
125
|
+
region,
|
|
126
|
+
requestedModel: parsed.modelId,
|
|
127
|
+
completionMode: built.completionMode,
|
|
128
|
+
bodyBytes: new TextEncoder().encode(body).length,
|
|
129
|
+
messageCount: kiroPayloadMessages(parsed).length,
|
|
130
|
+
toolCount: parsed.context.tools?.length ?? 0,
|
|
131
|
+
hasProfileArn: Boolean(profileArn),
|
|
132
|
+
wireClient,
|
|
133
|
+
hasPreviousResponseId: Boolean(parsed.previousResponseId),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
request: {
|
|
138
|
+
url: kiroRuntimeEndpoint(provider, region),
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers,
|
|
141
|
+
body,
|
|
142
|
+
usageLog: { inputTokens: estimateKiroLogInputTokens(parsed), estimated: true },
|
|
143
|
+
},
|
|
144
|
+
nameMap: built.nameMap,
|
|
145
|
+
conversationId: built.conversationId,
|
|
146
|
+
completionMode: built.completionMode,
|
|
147
|
+
inputTokens: estimateKiroInputTokens(parsed),
|
|
148
|
+
contextInputEstimate,
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const fallbackFactory: KiroFallbackFactory = async (
|
|
153
|
+
returnedConversationId,
|
|
154
|
+
assistantText,
|
|
155
|
+
_sawReasoning,
|
|
156
|
+
budget,
|
|
157
|
+
) => {
|
|
158
|
+
if (!requestSnapshot) throw new Error("Kiro completion retry lost its request state");
|
|
159
|
+
if (requestAbortSignal?.aborted) {
|
|
160
|
+
throw requestAbortSignal.reason instanceof Error
|
|
161
|
+
? requestAbortSignal.reason
|
|
162
|
+
: new DOMException("Kiro request was cancelled", "AbortError");
|
|
163
|
+
}
|
|
164
|
+
const retryParsed = structuredClone(requestSnapshot);
|
|
165
|
+
retryParsed._providerContinuation = {
|
|
166
|
+
...(retryParsed._providerContinuation ?? {}),
|
|
167
|
+
...(returnedConversationId ? { kiro: { conversationId: returnedConversationId } } : {}),
|
|
168
|
+
};
|
|
169
|
+
// Reasoning is not replayable on the Kiro wire. Adding an empty assistant turn merely to mark
|
|
170
|
+
// that reasoning existed creates REQUEST_BODY_INVALID; only visible text earns a replay turn.
|
|
171
|
+
if (assistantText.trim()) {
|
|
172
|
+
retryParsed.context.messages.push({
|
|
173
|
+
role: "assistant",
|
|
174
|
+
content: [{ type: "text" as const, text: assistantText }],
|
|
175
|
+
phase: "commentary",
|
|
176
|
+
model: retryParsed.modelId,
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// The retry starts from the already measured first wire body, adds one JSON-escaped replay
|
|
181
|
+
// string, and only changes bounded Kiro-owned fields (completion prompt/tool, history wrapper,
|
|
182
|
+
// and <=256-byte conversation id). 64 KiB is a conservative envelope for those fixed fields.
|
|
183
|
+
// Reserve that complete upper bound while the first-attempt collectors are still charged so a
|
|
184
|
+
// near-cap turn fails before build() can materialize the retry payload or serialized body.
|
|
185
|
+
const retryBodyUpperBound = firstRequestBodyBytes
|
|
186
|
+
+ jsonStringSerializedUtf8Bytes(assistantText)
|
|
187
|
+
+ KIRO_FALLBACK_SERIALIZATION_ENVELOPE_BYTES;
|
|
188
|
+
const retryBodyReservation = budget.reserveTransient(retryBodyUpperBound, { kind: "request_copies" });
|
|
189
|
+
let retryBodyBytes = 0;
|
|
190
|
+
let retryBodyRetained = false;
|
|
191
|
+
let requestBodyReleased = false;
|
|
192
|
+
const releaseRequestBody = () => {
|
|
193
|
+
if (requestBodyReleased) return;
|
|
194
|
+
requestBodyReleased = true;
|
|
195
|
+
if (retryBodyRetained) budget.releaseRetained(retryBodyBytes, { kind: "request_copies" });
|
|
196
|
+
else retryBodyReservation.release();
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
const retry = await build(retryParsed, "text_fallback");
|
|
200
|
+
retryBodyBytes = Buffer.byteLength(retry.request.body);
|
|
201
|
+
if (retryBodyBytes > retryBodyUpperBound) {
|
|
202
|
+
throw new Error("Kiro retry serialization exceeded its pre-admitted upper bound");
|
|
203
|
+
}
|
|
204
|
+
retryBodyReservation.commitRetained();
|
|
205
|
+
retryBodyRetained = true;
|
|
206
|
+
budget.releaseRetained(retryBodyUpperBound - retryBodyBytes, { kind: "request_copies" });
|
|
207
|
+
const response = await fetchKiroWithRetry(retry.request, {
|
|
208
|
+
abortSignal: requestAbortSignal,
|
|
209
|
+
returnRawErrors: true,
|
|
210
|
+
stream: true,
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
response,
|
|
214
|
+
inputTokens: retry.inputTokens,
|
|
215
|
+
contextInputEstimate: retry.contextInputEstimate,
|
|
216
|
+
nameMap: retry.nameMap,
|
|
217
|
+
conversationId: retry.conversationId,
|
|
218
|
+
releaseRequestBody,
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
releaseRequestBody();
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
name: "kiro",
|
|
228
|
+
// A replayed history that already ENDS with a delivered final answer has nothing to ask Kiro.
|
|
229
|
+
// Before this hook the adapter still appended a trailing user turn — a neutral acknowledgement,
|
|
230
|
+
// but structurally still a prompt — and performed a real inference, so the model answered the
|
|
231
|
+
// closed task again and the finished turn behaved like a still-open goal.
|
|
232
|
+
//
|
|
233
|
+
// Suppressing the completion contract (above) removed the instruction to complete; it could not
|
|
234
|
+
// remove the inference. This is the boundary: no request is built, nothing is sent, and no token
|
|
235
|
+
// estimate is recorded.
|
|
236
|
+
//
|
|
237
|
+
// The forced-fallback build is deliberately NOT consulted here: this hook runs on the inbound
|
|
238
|
+
// turn only, and the adapter-owned bounded retry passes "text_fallback" through `build`
|
|
239
|
+
// directly, never through this path.
|
|
240
|
+
localTerminal(parsed: OcxParsedRequest) {
|
|
241
|
+
return hasTrailingDeliveredFinalAnswer(kiroPayloadMessages(parsed), parsed)
|
|
242
|
+
? { reason: "kiro_final_answer_already_delivered" }
|
|
243
|
+
: undefined;
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
async buildRequest(parsed: OcxParsedRequest, incoming) {
|
|
247
|
+
const built = await build(parsed);
|
|
248
|
+
modelId = parsed.modelId;
|
|
249
|
+
contextWindow = kiroUpstreamContextWindow(parsed.modelId);
|
|
250
|
+
inputTokens = built.inputTokens;
|
|
251
|
+
contextInputEstimate = built.contextInputEstimate;
|
|
252
|
+
toolNameMap = built.nameMap;
|
|
253
|
+
conversationId = built.conversationId;
|
|
254
|
+
completionMode = built.completionMode;
|
|
255
|
+
requestSnapshot = structuredClone(parsed);
|
|
256
|
+
firstRequestBodyBytes = Buffer.byteLength(built.request.body);
|
|
257
|
+
requestAbortSignal = incoming?.abortSignal;
|
|
258
|
+
return built.request;
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
|
|
262
|
+
return parseKiroStream(
|
|
263
|
+
response,
|
|
264
|
+
budget,
|
|
265
|
+
modelId,
|
|
266
|
+
inputTokens,
|
|
267
|
+
contextWindow,
|
|
268
|
+
toolNameMap,
|
|
269
|
+
conversationId,
|
|
270
|
+
completionMode,
|
|
271
|
+
completionMode === "required" ? fallbackFactory : undefined,
|
|
272
|
+
contextInputEstimate,
|
|
273
|
+
);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
fetchResponse(request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> {
|
|
277
|
+
// The normal Responses path supplies cancellation at fetch time rather than build time.
|
|
278
|
+
// Keep it for the adapter-owned bounded continuation so cancelling the client turn aborts
|
|
279
|
+
// both the first Kiro request and its one allowed completion retry.
|
|
280
|
+
if (ctx?.abortSignal) requestAbortSignal = ctx.abortSignal;
|
|
281
|
+
return fetchKiroWithRetry(request, ctx);
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
formatErrorBody(status: number, headers: Headers, payloadText: string): string {
|
|
285
|
+
return safeKiroHttpErrorMessage(status, headers, payloadText);
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// Kiro always returns an event stream, including for non-streaming Responses requests. Drain
|
|
289
|
+
// the decoder into a budget-owned batch so an upstream stream cannot grow this array without
|
|
290
|
+
// bound while the caller waits for the complete JSON response.
|
|
291
|
+
async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
|
|
292
|
+
const events: AdapterEvent[] = [];
|
|
293
|
+
try {
|
|
294
|
+
for await (const e of parseKiroStream(
|
|
295
|
+
response,
|
|
296
|
+
budget,
|
|
297
|
+
modelId,
|
|
298
|
+
inputTokens,
|
|
299
|
+
contextWindow,
|
|
300
|
+
toolNameMap,
|
|
301
|
+
conversationId,
|
|
302
|
+
completionMode,
|
|
303
|
+
completionMode === "required" ? fallbackFactory : undefined,
|
|
304
|
+
contextInputEstimate,
|
|
305
|
+
)) {
|
|
306
|
+
retainTranslatedEvent(e, budget, events.at(-1));
|
|
307
|
+
events.push(e);
|
|
308
|
+
}
|
|
309
|
+
return events;
|
|
310
|
+
} catch (error) {
|
|
311
|
+
for (const event of events) releaseTranslatedEvent(event, budget);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { hasRecordedTrailingDeliveredFinalAnswer } from "../../responses/turn-termination";
|
|
2
|
+
import type {
|
|
3
|
+
OcxAssistantMessage,
|
|
4
|
+
OcxMessage,
|
|
5
|
+
OcxParsedRequest,
|
|
6
|
+
} from "../../types";
|
|
7
|
+
import type { KiroImage } from "../kiro-images";
|
|
8
|
+
import type { KiroHistoryEntry, KiroToolResult, KiroToolUse } from "./wire";
|
|
9
|
+
|
|
10
|
+
export function validateKiroCapabilities(parsed: OcxParsedRequest): void {
|
|
11
|
+
const choice = parsed.options.toolChoice;
|
|
12
|
+
if (choice !== undefined && choice !== "auto" && choice !== "none") {
|
|
13
|
+
throw new Error("Kiro supports only automatic tool choice or tool_choice:none");
|
|
14
|
+
}
|
|
15
|
+
if (parsed.options.serviceTier !== undefined) {
|
|
16
|
+
throw new Error("Kiro does not support service tiers");
|
|
17
|
+
}
|
|
18
|
+
// Structured output is a real contract Kiro cannot honour: the wire has no
|
|
19
|
+
// schema-constrained response mode, so a caller expecting parseable JSON would receive
|
|
20
|
+
// prose and fail downstream. Refuse it.
|
|
21
|
+
//
|
|
22
|
+
// The rest of the Responses `text` object is not that. `text.verbosity` is a length
|
|
23
|
+
// preference and `text.format: {type:"text"}` is ordinary prose — the default output
|
|
24
|
+
// mode, which no capability flag governs and every correct client may send. Testing
|
|
25
|
+
// `_rawBody.text !== undefined` refused those turns for the mere PRESENCE of the key,
|
|
26
|
+
// the same mistake db040e70f removed one condition earlier where a permissive
|
|
27
|
+
// `parallel_tool_calls` hint was read as a requirement.
|
|
28
|
+
//
|
|
29
|
+
// Nothing needs stripping the way openai-responses strips a no-op verbosity:
|
|
30
|
+
// buildKiroPayload composes conversationState field by field from `parsed` and never
|
|
31
|
+
// spreads `_rawBody`, so a tolerated control is dropped by construction. The test
|
|
32
|
+
// asserts that absence so it stays true.
|
|
33
|
+
if (parsed._structuredOutput) {
|
|
34
|
+
throw new Error("Kiro does not support Responses structured output");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type KiroTurn =
|
|
39
|
+
| {
|
|
40
|
+
kind: "user";
|
|
41
|
+
content: string;
|
|
42
|
+
images: KiroImage[];
|
|
43
|
+
toolResults: KiroToolResult[];
|
|
44
|
+
/**
|
|
45
|
+
* True only for the proxy-generated acknowledgement that follows a delivered final answer.
|
|
46
|
+
* A flag rather than a content comparison: a real user message may legitimately quote the
|
|
47
|
+
* same sentence, and treating that as internal state would strip its thinking tags and
|
|
48
|
+
* completion retry.
|
|
49
|
+
*/
|
|
50
|
+
answerDeliveredAck?: boolean;
|
|
51
|
+
}
|
|
52
|
+
| {
|
|
53
|
+
kind: "assistant";
|
|
54
|
+
content: string;
|
|
55
|
+
toolUses: KiroToolUse[];
|
|
56
|
+
redactedReasoning?: string;
|
|
57
|
+
/**
|
|
58
|
+
* True when this assistant turn was the DELIVERED final answer (Responses
|
|
59
|
+
* `phase: "final_answer"`). A trailing assistant turn normally means the model stopped
|
|
60
|
+
* mid-task and needs a continuation prompt, but a delivered final answer already ended its
|
|
61
|
+
* turn — prompting it again restarts finished work as if a goal were still open.
|
|
62
|
+
*/
|
|
63
|
+
finalAnswer?: boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* True when the LAST content-bearing message is an assistant final answer that closed its turn.
|
|
68
|
+
*
|
|
69
|
+
* Mirrors the turn-merge rule: a tool call in that message, or any later user/tool-result message,
|
|
70
|
+
* means work continued, so the turn is no longer terminal. Empty assistant messages are skipped
|
|
71
|
+
* rather than treated as continuation, since they carry no visible turn.
|
|
72
|
+
*/
|
|
73
|
+
export function hasTrailingDeliveredFinalAnswer(messages: readonly OcxMessage[], parsed?: OcxParsedRequest): boolean {
|
|
74
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
75
|
+
const msg = messages[i];
|
|
76
|
+
if (msg.role !== "assistant") return false;
|
|
77
|
+
const aMsg = msg as OcxAssistantMessage;
|
|
78
|
+
const hasToolCall = (aMsg.content ?? []).some(part => part.type === "toolCall");
|
|
79
|
+
if (hasToolCall) return false;
|
|
80
|
+
const hasText = (aMsg.content ?? []).some(part => part.type === "text" && part.text.trim());
|
|
81
|
+
if (!hasText) continue;
|
|
82
|
+
return aMsg.phase === "final_answer"
|
|
83
|
+
|| (parsed !== undefined && hasRecordedTrailingDeliveredFinalAnswer(parsed, messages));
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function appendTurnText(target: string, next: string): string {
|
|
89
|
+
if (!next) return target;
|
|
90
|
+
return target ? `${target}\n\n${next}` : next;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function validateKiroConversationState(history: KiroHistoryEntry[], currentMessage: KiroHistoryEntry): void {
|
|
94
|
+
const entries = [...history, currentMessage];
|
|
95
|
+
const pendingToolUses = new Set<string>();
|
|
96
|
+
let previousRole: "user" | "assistant" | undefined;
|
|
97
|
+
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const user = entry.userInputMessage;
|
|
100
|
+
const assistant = entry.assistantResponseMessage;
|
|
101
|
+
if (Boolean(user) === Boolean(assistant)) {
|
|
102
|
+
throw new Error("Kiro conversation entries must contain exactly one message role");
|
|
103
|
+
}
|
|
104
|
+
const role = user ? "user" : "assistant";
|
|
105
|
+
if (role === previousRole) throw new Error("Kiro conversation roles must alternate");
|
|
106
|
+
previousRole = role;
|
|
107
|
+
|
|
108
|
+
if (user) {
|
|
109
|
+
const hasPayload = Boolean(user.content.trim())
|
|
110
|
+
|| Boolean(user.images?.length)
|
|
111
|
+
|| Boolean(user.userInputMessageContext?.toolResults?.length);
|
|
112
|
+
if (!hasPayload) throw new Error("Kiro user messages must not be empty");
|
|
113
|
+
for (const result of user.userInputMessageContext?.toolResults ?? []) {
|
|
114
|
+
if (!pendingToolUses.delete(result.toolUseId)) {
|
|
115
|
+
throw new Error(`Kiro tool result has no matching tool use ${JSON.stringify(result.toolUseId)}`);
|
|
116
|
+
}
|
|
117
|
+
if (!result.content.some(part => part.text.trim())) {
|
|
118
|
+
throw new Error(`Kiro tool result must not be empty ${JSON.stringify(result.toolUseId)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const toolUses = assistant?.toolUses ?? [];
|
|
125
|
+
if (!assistant?.content.trim() && toolUses.length === 0) {
|
|
126
|
+
throw new Error("Kiro assistant messages must not be empty");
|
|
127
|
+
}
|
|
128
|
+
for (const toolUse of toolUses) {
|
|
129
|
+
if (pendingToolUses.has(toolUse.toolUseId)) {
|
|
130
|
+
throw new Error(`Kiro conversation contains duplicate tool use ${JSON.stringify(toolUse.toolUseId)}`);
|
|
131
|
+
}
|
|
132
|
+
pendingToolUses.add(toolUse.toolUseId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (pendingToolUses.size > 0) throw new Error("Kiro conversation contains an unanswered tool use");
|
|
136
|
+
}
|