@bitkyc08/opencodex 2.67.0-preview.20260926 → 2.68.0-preview.20260927
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 +3 -0
- package/gui/dist/assets/App-B5-cAEd2.js +51 -0
- package/gui/dist/assets/App-DCBismRi.css +1 -0
- package/gui/dist/assets/Tray-DFnMiyD0.js +1 -0
- package/gui/dist/assets/{index-DtNmX7hW.css → index-BDUBS8PW.css} +1 -1
- package/gui/dist/assets/index-BcXblvem.js +86 -0
- package/gui/dist/assets/quota-summary-i84eOU3o.js +1 -0
- package/gui/dist/index.html +21 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-guard.ts +13 -1
- package/src/adapters/anthropic-image-normalize.ts +3 -2
- package/src/adapters/anthropic-output-schema.ts +36 -0
- package/src/adapters/anthropic.ts +10 -2
- package/src/adapters/base.ts +7 -0
- package/src/adapters/codebuddy/live-models.ts +180 -0
- package/src/adapters/coding-agent/protocol.ts +91 -12
- package/src/adapters/coding-agent/turn.ts +32 -27
- package/src/adapters/devin/cloud-direct/index.ts +1 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +18 -4
- package/src/adapters/devin.ts +9 -6
- package/src/adapters/google-http.ts +4 -4
- package/src/adapters/google.ts +93 -3
- package/src/adapters/kiro/adapter.ts +6 -2
- package/src/adapters/kiro/stream.ts +12 -2
- package/src/adapters/kiro/usage.ts +3 -1
- package/src/adapters/kiro-errors.ts +11 -1
- package/src/adapters/kiro-events.ts +27 -1
- package/src/adapters/kiro-refusal.ts +30 -0
- package/src/adapters/kiro-retry.ts +71 -31
- package/src/adapters/openai-chat/deepseek-artifact-schema.ts +45 -0
- package/src/adapters/openai-chat/messages.ts +31 -4
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +52 -7
- package/src/adapters/openai-chat/tool-call-id-remint.ts +65 -0
- package/src/adapters/openai-chat/tool-schema.ts +6 -2
- package/src/adapters/openai-chat.ts +2 -2
- package/src/adapters/openai-responses/muse-tool-choice.ts +31 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -3
- package/src/adapters/opencode-go-additional-tools.ts +19 -10
- package/src/adapters/physical-send.ts +10 -3
- package/src/adapters/registry.ts +2 -1
- package/src/adapters/responses-tool-schema.ts +31 -3
- package/src/adapters/run-turn-queue.ts +63 -23
- package/src/adapters/unique-tool-call-ids.ts +63 -0
- package/src/adapters/xai-web-search.ts +32 -2
- package/src/bridge/sse.ts +14 -2
- package/src/chat/inbound.ts +20 -6
- package/src/claude/agents-inject.ts +4 -0
- package/src/claude/desktop-3p.ts +53 -13
- package/src/claude/desktop-profile.ts +41 -4
- package/src/claude/inbound-model-options.ts +14 -2
- package/src/claude/inbound.ts +1 -1
- package/src/claude/intercept/connect-proxy.ts +17 -1
- package/src/claude/intercept/local-ca.ts +7 -1
- package/src/claude/message-threads.ts +28 -0
- package/src/cli/account-api.ts +13 -0
- package/src/cli/account-auth.ts +49 -2
- package/src/cli/account-extended.ts +4 -2
- package/src/cli/account.ts +4 -2
- package/src/cli/capabilities.ts +17 -0
- package/src/cli/claude.ts +3 -1
- package/src/cli/dispatch.ts +62 -5
- package/src/cli/index.ts +84 -79
- package/src/cli/minimax.ts +4 -2
- package/src/cli/opencode.ts +6 -1
- package/src/cli/restart-handoff.ts +110 -0
- package/src/cli/status.ts +51 -0
- package/src/client/connect.ts +34 -15
- package/src/client/link-ingress.ts +102 -0
- package/src/client/link-join.ts +37 -16
- package/src/client/link-relay.ts +228 -45
- package/src/client/link-state.ts +54 -0
- package/src/client/link-status.ts +39 -0
- package/src/client/link-teardown.ts +2 -2
- package/src/client/link-tunnel.ts +615 -86
- package/src/client/machine-api.ts +10 -4
- package/src/client/machine-listener.ts +57 -10
- package/src/client/runtime.ts +210 -35
- package/src/clients/config-export/constants.ts +1 -12
- package/src/clients/config-export/contracts.ts +2 -0
- package/src/clients/config-export/model-metadata.ts +9 -3
- package/src/clients/config-export/omp.ts +1 -1
- package/src/clients/config-export/zcode-store.ts +2 -2
- package/src/clients/config-export.ts +36 -3
- package/src/codex/app-server-processes.ts +32 -0
- package/src/codex/app-server-restart-service.ts +20 -3
- package/src/codex/catalog/access-programs.ts +50 -0
- package/src/codex/catalog/build-entries.ts +13 -0
- package/src/codex/catalog/derive-entry.ts +1 -0
- package/src/codex/catalog/model-hints.ts +11 -0
- package/src/codex/catalog/parsing.ts +6 -2
- package/src/codex/catalog/provider-models.ts +81 -0
- package/src/codex/catalog/retained-sync.ts +3 -0
- package/src/codex/codex-write-lock.ts +2 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desired-state.ts +14 -4
- package/src/codex/home.ts +21 -3
- package/src/codex/inject/restore.ts +17 -0
- package/src/codex/inject/routing-classify.ts +3 -2
- package/src/codex/inject-coordination.ts +4 -1
- package/src/codex/inject.ts +8 -1
- package/src/codex/journal.ts +6 -2
- package/src/codex/management-convergence.ts +9 -0
- package/src/codex/model-entitlements.ts +28 -5
- package/src/codex/routing/idle-window.ts +58 -0
- package/src/codex/routing-drift.ts +134 -0
- package/src/codex/routing-healer.ts +419 -0
- package/src/codex/routing.ts +10 -0
- package/src/codex/runtime.ts +188 -57
- package/src/codex/sibling-handoff.ts +71 -0
- package/src/codex/sibling-start.ts +139 -0
- package/src/codex/sync.ts +5 -3
- package/src/combos/failover.ts +32 -5
- package/src/combos/request.ts +11 -3
- package/src/combos/reset-window.ts +10 -5
- package/src/combos/resolve.ts +36 -6
- package/src/config/diagnostics.ts +2 -1
- package/src/config/load-degrade.ts +2 -0
- package/src/config/paths.ts +7 -1
- package/src/config/process-state.ts +10 -1
- package/src/config/schema/compaction-recovery.ts +15 -0
- package/src/config/schema/config-schema.ts +2 -0
- package/src/config/schema/leaf-validators.ts +9 -3
- package/src/generated/compatibility-version.json +360 -204
- package/src/images/loop.ts +47 -32
- package/src/integrations/catalog-refresh.ts +4 -0
- package/src/integrations/omp-yaml-source.ts +1 -0
- package/src/lib/claude-request-projection.ts +101 -0
- package/src/lib/local-account-switch-capability.ts +48 -0
- package/src/lib/local-upstream.ts +146 -0
- package/src/lib/package-tree-integrity.ts +25 -3
- package/src/lib/package-tree-retarget.ts +130 -0
- package/src/lib/process-control.ts +4 -1
- package/src/lib/system-restart-contract.ts +106 -0
- package/src/lib/upstream-retry.ts +57 -1
- package/src/link/ports.ts +9 -0
- package/src/link/ssh-argv.ts +16 -0
- package/src/link/ssh-runner.ts +70 -2
- package/src/link/tunnel-state.ts +58 -9
- package/src/oauth/account-quota-rank.ts +29 -18
- package/src/oauth/generic-account-failover.ts +179 -22
- package/src/oauth/index.ts +17 -4
- package/src/oauth/kiro-account-load.ts +107 -0
- package/src/oauth/kiro-device-login.ts +312 -0
- package/src/oauth/kiro-terminal-failover.ts +28 -0
- package/src/oauth/login-flow-state.ts +7 -1
- package/src/oauth/pool-settings-capability.ts +23 -14
- package/src/oauth/store.ts +56 -3
- package/src/oauth/types.ts +4 -0
- package/src/plugins/loader.ts +350 -0
- package/src/plugins/upstream-hooks.ts +119 -0
- package/src/protocols/encoders/adapter-events.ts +9 -2
- package/src/providers/account-quota-disk.ts +42 -4
- package/src/providers/codebuddy-models.ts +6 -2
- package/src/providers/command-code-efforts.ts +33 -10
- package/src/providers/kiro-account-state-disk.ts +44 -0
- package/src/providers/kiro-model-catalog.ts +162 -0
- package/src/providers/kiro-models.ts +5 -4
- package/src/providers/kiro-quota-metrics.ts +35 -0
- package/src/providers/kiro-usage.ts +118 -14
- package/src/providers/quota/account-cache.ts +43 -7
- package/src/providers/quota/antigravity.ts +8 -3
- package/src/providers/quota/kiro-account-probe.ts +12 -0
- package/src/providers/quota/vendor-probes-key.ts +31 -26
- package/src/providers/quota/vendor-probes-oauth.ts +16 -7
- package/src/providers/quota-types.ts +14 -0
- package/src/providers/quota.ts +21 -21
- package/src/providers/registry/entries-extended.ts +5 -3
- package/src/providers/request-pacing.ts +410 -12
- package/src/remote-control/workspace-codex-runtime.ts +4 -3
- package/src/responses/citation-markers.ts +132 -65
- package/src/responses/hosted-tool-policy.ts +14 -3
- package/src/responses/parser-content.ts +3 -2
- package/src/responses/parser.ts +4 -1
- package/src/responses/schema.ts +4 -0
- package/src/responses/visualization-directives.ts +182 -0
- package/src/server/chat-native-sse.ts +24 -12
- package/src/server/chat-native.ts +137 -68
- package/src/server/claude-messages.ts +147 -12
- package/src/server/index/link-listener.ts +7 -0
- package/src/server/index/optional-listeners.ts +4 -1
- package/src/server/index/package-tree-guard.ts +35 -16
- package/src/server/index/serve-options.ts +4 -2
- package/src/server/index/startup-warnings.ts +6 -0
- package/src/server/index.ts +7 -7
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/inference/context.ts +30 -2
- package/src/server/local-account-switch-auth.ts +79 -0
- package/src/server/management/agent-settings-routes.ts +11 -3
- package/src/server/management/config-routes.ts +28 -11
- package/src/server/management/context.ts +2 -0
- package/src/server/management/link-routes.ts +123 -39
- package/src/server/management/logs-usage-routes.ts +9 -42
- package/src/server/management/model-rows.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +67 -12
- package/src/server/management/route-registry.ts +5 -5
- package/src/server/management/sibling-guard.ts +60 -0
- package/src/server/management/storage-log-guard-routes.ts +72 -6
- package/src/server/management/system-restart.ts +92 -135
- package/src/server/management/system-routes.ts +1 -1
- package/src/server/management-api.ts +31 -3
- package/src/server/management-auth.ts +3 -0
- package/src/server/port-reclaim.ts +111 -1
- package/src/server/proxy-liveness.ts +16 -0
- package/src/server/relay.ts +16 -15
- package/src/server/request-log-filter.ts +28 -27
- package/src/server/request-log.ts +7 -3
- package/src/server/request-metrics.ts +28 -0
- package/src/server/responses/adapter-continuation.ts +151 -21
- package/src/server/responses/adapter-delivery.ts +21 -7
- package/src/server/responses/adapter-dispatch.ts +239 -51
- package/src/server/responses/codex-ws-pool.ts +5 -4
- package/src/server/responses/codex-ws-request.ts +4 -1
- package/src/server/responses/compact.ts +8 -4
- package/src/server/responses/compaction-recovery-policy.ts +116 -0
- package/src/server/responses/compaction-recovery.ts +330 -0
- package/src/server/responses/core-combo-failure.ts +1 -0
- package/src/server/responses/core-combo.ts +5 -1
- package/src/server/responses/core-lifetime.ts +22 -0
- package/src/server/responses/core-options.ts +16 -1
- package/src/server/responses/core.ts +14 -4
- package/src/server/responses/empty-completion-guard.ts +2 -0
- package/src/server/responses/fetch-helpers.ts +82 -31
- package/src/server/responses/passthrough-delivery.ts +3 -2
- package/src/server/responses/passthrough-dispatch.ts +12 -2
- package/src/server/responses/passthrough-execution.ts +2 -2
- package/src/server/responses/request-send-budget.ts +21 -5
- package/src/server/responses/request-transport.ts +116 -18
- package/src/server/responses/reset-replay.ts +32 -0
- package/src/server/responses/run-turn-execution.ts +136 -19
- package/src/server/responses/sidecar-execution.ts +41 -8
- package/src/server/responses/terminal-guard.ts +2 -0
- package/src/server/responses/ws-upstream.ts +44 -4
- package/src/server/restart-replacement.ts +382 -0
- package/src/server/startup-health-cache.ts +23 -4
- package/src/server/stop-teardown.ts +35 -1
- package/src/server/system-env.ts +3 -0
- package/src/service/windows-taskxml.ts +11 -7
- package/src/service/windows-wrapper-exit.ts +8 -0
- package/src/stall-timeout.ts +35 -10
- package/src/storage/policy-job.ts +4 -0
- package/src/storage/scanner.ts +177 -29
- package/src/storage/storage-mutation-coordinator.ts +17 -0
- package/src/tray/windows-tray.ps1 +50 -23
- package/src/types/config.ts +4 -0
- package/src/types/provider.ts +5 -1
- package/src/types/request.ts +13 -2
- package/src/update/index.ts +3 -1
- package/src/update/job.ts +6 -2
- package/src/update/mise-launcher-target.ts +100 -0
- package/src/update/worker-launch.ts +46 -0
- package/src/usage/log.ts +2 -0
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-B3VRRYSj.js +0 -51
- package/gui/dist/assets/App-BJsT8Icc.css +0 -1
- package/gui/dist/assets/Tray-TYAA0EQY.js +0 -1
- package/gui/dist/assets/index-9Ju7nHsu.js +0 -86
- package/gui/dist/assets/tray-data-xKfS2gSG.js +0 -1
|
@@ -380,7 +380,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
380
380
|
sawPartialText: false,
|
|
381
381
|
sawPartialThinking: false,
|
|
382
382
|
sawTerminalResult: false,
|
|
383
|
-
|
|
383
|
+
openToolBlocks: new Map(),
|
|
384
384
|
partialToolCallIds: toolBridge ? new Set<string>() : undefined,
|
|
385
385
|
};
|
|
386
386
|
|
|
@@ -422,6 +422,29 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
422
422
|
break;
|
|
423
423
|
}
|
|
424
424
|
if (message.type === "system" && message.subtype === "init") initValidated = true;
|
|
425
|
+
const rawEvent = message.type === "stream_event" && message.event !== null && typeof message.event === "object"
|
|
426
|
+
? message.event as Record<string, unknown>
|
|
427
|
+
: undefined;
|
|
428
|
+
const rawBlock = rawEvent?.content_block;
|
|
429
|
+
if (
|
|
430
|
+
!initValidated
|
|
431
|
+
&& rawEvent?.type === "content_block_start"
|
|
432
|
+
&& rawBlock !== null
|
|
433
|
+
&& typeof rawBlock === "object"
|
|
434
|
+
&& !Array.isArray(rawBlock)
|
|
435
|
+
&& (rawBlock as Record<string, unknown>).type === "tool_use"
|
|
436
|
+
) {
|
|
437
|
+
emitOnce({
|
|
438
|
+
type: "error",
|
|
439
|
+
message: "Coding-agent CLI called a tool before the tool bridge init handshake completed.",
|
|
440
|
+
status: 502,
|
|
441
|
+
errorType: "upstream_error",
|
|
442
|
+
code: "tool_bridge_init_missing",
|
|
443
|
+
retryable: false,
|
|
444
|
+
});
|
|
445
|
+
kill();
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
425
448
|
}
|
|
426
449
|
const mappedEvents = mapStreamMessageToEvents(message, state);
|
|
427
450
|
if (toolBridge && state.uncapturedToolUse) {
|
|
@@ -451,24 +474,6 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
451
474
|
break;
|
|
452
475
|
}
|
|
453
476
|
if (toolBridge && event.type === "tool_call_start") {
|
|
454
|
-
// The catalog is only advertised once the CLI has acknowledged the bridge server in
|
|
455
|
-
// its init handshake. A tool call that arrives before that acknowledgement means the
|
|
456
|
-
// model acted on a catalog this bridge never validated, so fail closed before the
|
|
457
|
-
// call is counted or renamed. Checking at arrival matters: a later init frame used to
|
|
458
|
-
// set initValidated and let an early call finish as a successful done(tool_use).
|
|
459
|
-
if (!initValidated) {
|
|
460
|
-
emitOnce({
|
|
461
|
-
type: "error",
|
|
462
|
-
message: "Coding-agent CLI called a tool before the tool bridge init handshake completed.",
|
|
463
|
-
status: 502,
|
|
464
|
-
errorType: "upstream_error",
|
|
465
|
-
code: "tool_bridge_init_missing",
|
|
466
|
-
retryable: false,
|
|
467
|
-
});
|
|
468
|
-
failClosed = true;
|
|
469
|
-
kill();
|
|
470
|
-
break;
|
|
471
|
-
}
|
|
472
477
|
toolCallStarts += 1;
|
|
473
478
|
if (toolCallStarts > toolBridge.maxTurnToolCalls) {
|
|
474
479
|
emitOnce({
|
|
@@ -527,8 +532,8 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
527
532
|
toolBridge
|
|
528
533
|
&& !terminalEmitted
|
|
529
534
|
&& event.type === "done"
|
|
530
|
-
&&
|
|
531
|
-
&& (state.completedToolCalls ?? 0) !==
|
|
535
|
+
&& (state.toolBlockStarts ?? 0) > 0
|
|
536
|
+
&& (state.completedToolCalls ?? 0) !== (state.toolBlockStarts ?? 0)
|
|
532
537
|
) {
|
|
533
538
|
// A terminal result that arrives while a captured tool call is still open must not
|
|
534
539
|
// become a successful completion the client can accept. The message_stop check after
|
|
@@ -552,8 +557,8 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
552
557
|
toolBridge
|
|
553
558
|
&& !terminalEmitted
|
|
554
559
|
&& event.type === "done"
|
|
555
|
-
&&
|
|
556
|
-
&& (state.completedToolCalls ?? 0) ===
|
|
560
|
+
&& (state.toolBlockStarts ?? 0) > 0
|
|
561
|
+
&& (state.completedToolCalls ?? 0) === (state.toolBlockStarts ?? 0)
|
|
557
562
|
) {
|
|
558
563
|
// Every captured call completed and the CLI settled with a successful result before
|
|
559
564
|
// message_stop (instead of parking on the never-answering capture server). Emitting
|
|
@@ -573,8 +578,8 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
573
578
|
toolBridge
|
|
574
579
|
&& !terminalEmitted
|
|
575
580
|
&& state.sawMessageStop
|
|
576
|
-
&&
|
|
577
|
-
&& (state.completedToolCalls ?? 0) !==
|
|
581
|
+
&& (state.toolBlockStarts ?? 0) > 0
|
|
582
|
+
&& (state.completedToolCalls ?? 0) !== (state.toolBlockStarts ?? 0)
|
|
578
583
|
) {
|
|
579
584
|
emitOnce({
|
|
580
585
|
type: "error",
|
|
@@ -588,8 +593,8 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
588
593
|
break;
|
|
589
594
|
}
|
|
590
595
|
if (toolBridge && !terminalEmitted && state.sawMessageStop && (state.completedToolCalls ?? 0) > 0) {
|
|
591
|
-
//
|
|
592
|
-
//
|
|
596
|
+
// Raw tool_use starts are gated before buffering, so every completed call was admitted
|
|
597
|
+
// after the bridge init handshake.
|
|
593
598
|
// The capture-only MCP handler never answers, so the CLI parks after message_stop.
|
|
594
599
|
// The completed tool_use blocks are this turn's structured output: end the leg here
|
|
595
600
|
// and terminate the tree; the client executes, and the next request continues.
|
|
@@ -17,21 +17,26 @@ export const STATED_RESET_MAX_WAIT_MS = 1_800_000;
|
|
|
17
17
|
/** Absolute maximum cumulative allowance, including explicit overrides. */
|
|
18
18
|
export const STATED_RESET_WAIT_CEILING_MS = 3_600_000;
|
|
19
19
|
|
|
20
|
-
function statedResetMaxWaitMs(): number {
|
|
20
|
+
function statedResetMaxWaitMs(defaultMs = STATED_RESET_MAX_WAIT_MS): number {
|
|
21
21
|
const raw = process.env.OPENCODEX_DEVIN_STATED_RESET_WAIT_MS?.trim();
|
|
22
|
-
if (!raw) return
|
|
22
|
+
if (!raw) return defaultMs;
|
|
23
23
|
const parsed = Number(raw);
|
|
24
|
-
if (!Number.isFinite(parsed) || parsed < 0) return
|
|
24
|
+
if (!Number.isFinite(parsed) || parsed < 0) return defaultMs;
|
|
25
25
|
// Zero explicitly disables local waiting. Values above one hour are capped.
|
|
26
26
|
return Math.min(Math.floor(parsed), STATED_RESET_WAIT_CEILING_MS);
|
|
27
27
|
}
|
|
28
28
|
export const statedResetMaxWaitMsForTests = statedResetMaxWaitMs;
|
|
29
29
|
|
|
30
|
+
export function devinStatedResetWaitMs(): number {
|
|
31
|
+
return statedResetMaxWaitMs(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
export interface StatedResetRetryOptions {
|
|
31
35
|
/** Test seam: defaults to the real cloud stream. */
|
|
32
36
|
stream?: (req: CloudChatRequest) => AsyncGenerator<CloudChatEvent>;
|
|
33
37
|
/** Test seam: must either honour the whole delay or reject on cancellation. */
|
|
34
38
|
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
39
|
+
onWaitHeartbeat?: () => void;
|
|
35
40
|
maxReplays?: number;
|
|
36
41
|
/** CUMULATIVE wait allowance, not a fresh allowance on every failure. */
|
|
37
42
|
maxWaitMs?: number;
|
|
@@ -136,7 +141,16 @@ export async function* streamChatEventsWithResetRetry(
|
|
|
136
141
|
// scheduling: waking a few milliseconds late must not reject an already
|
|
137
142
|
// approved one-hour retry. No later wait can spend this allowance again.
|
|
138
143
|
waitedMs += waitMs;
|
|
139
|
-
|
|
144
|
+
const heartbeat = options?.onWaitHeartbeat;
|
|
145
|
+
if (waitMs > 0) heartbeat?.();
|
|
146
|
+
const beat = heartbeat && waitMs > 0
|
|
147
|
+
? setInterval(heartbeat, Math.min(500, Math.max(100, Math.floor(waitMs / 2))))
|
|
148
|
+
: undefined;
|
|
149
|
+
try {
|
|
150
|
+
await sleep(waitMs, req.signal);
|
|
151
|
+
} finally {
|
|
152
|
+
if (beat !== undefined) clearInterval(beat);
|
|
153
|
+
}
|
|
140
154
|
if (req.signal?.aborted) throw abortError(req.signal);
|
|
141
155
|
}
|
|
142
156
|
}
|
package/src/adapters/devin.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { AdapterEvent, OcxAssistantMessage, OcxContentPart, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxTool, OcxToolCall, OcxToolResultMessage, OcxUsage } from "../types";
|
|
10
10
|
import { namespacedToolName } from "../types";
|
|
11
11
|
import type { IncomingMeta, ProviderAdapter } from "./base";
|
|
12
|
-
import { streamChatEventsWithResetRetry, allocateCascadeId, CloudChatError, type ChatHistoryItem, type ToolDef } from "./devin/cloud-direct";
|
|
12
|
+
import { streamChatEventsWithResetRetry, devinStatedResetWaitMs, allocateCascadeId, CloudChatError, type ChatHistoryItem, type ToolDef } from "./devin/cloud-direct";
|
|
13
13
|
import type { ContentPart } from "./devin/cloud-direct/chat";
|
|
14
14
|
import { getCachedCatalog, type CacheEntry } from "./devin/cloud-direct/catalog";
|
|
15
15
|
import { collapseDevinModelUid } from "./devin/live-models";
|
|
@@ -642,11 +642,12 @@ export function createDevinAdapter(
|
|
|
642
642
|
const maxOutputTokens = resolveDevinMaxOutputTokens(
|
|
643
643
|
provider, modelUid, parsed.options.maxOutputTokens,
|
|
644
644
|
);
|
|
645
|
+
// A combo child has not committed an outer response yet. Holding its preflight through
|
|
646
|
+
// a reset wait would also hold the next-target fallback with no client keepalive.
|
|
647
|
+
const resetWaitMs = incoming.comboAttempt ? 0 : devinStatedResetWaitMs();
|
|
645
648
|
// An admitted HTTP turn owns globally shared capacity until this call
|
|
646
|
-
// emits.
|
|
647
|
-
//
|
|
648
|
-
// never the raw trailer text that may reflect a credential. The
|
|
649
|
-
// refusal returns immediately so the caller can release its slot.
|
|
649
|
+
// emits. Without an explicit wait allowance, preserve the typed reset
|
|
650
|
+
// delay in generated diagnostic wording and return immediately.
|
|
650
651
|
for await (const event of streamChatEventsWithResetRetry({
|
|
651
652
|
apiKey,
|
|
652
653
|
apiServerUrl: host,
|
|
@@ -665,7 +666,9 @@ export function createDevinAdapter(
|
|
|
665
666
|
},
|
|
666
667
|
signal: incoming.abortSignal,
|
|
667
668
|
}, {
|
|
668
|
-
maxWaitMs:
|
|
669
|
+
maxWaitMs: resetWaitMs,
|
|
670
|
+
onWaitHeartbeat: resetWaitMs > 0 && parsed.stream
|
|
671
|
+
? () => emit({ type: "heartbeat", preflightReady: true }) : undefined,
|
|
669
672
|
execution: {
|
|
670
673
|
executor: incoming.providerFetch,
|
|
671
674
|
sendBudget: incoming.sendBudget,
|
|
@@ -14,10 +14,10 @@ import {
|
|
|
14
14
|
retryBackoffDelayMs,
|
|
15
15
|
sleepWithAbort,
|
|
16
16
|
SendBudgetExhaustedError,
|
|
17
|
+
TRANSIENT_RETRY_MAX_ATTEMPTS,
|
|
17
18
|
isConnectionResetError,
|
|
18
19
|
} from "../lib/upstream-retry";
|
|
19
20
|
|
|
20
|
-
const GOOGLE_RETRY_ATTEMPTS = 3;
|
|
21
21
|
const GOOGLE_RETRY_BASE_MS = 250;
|
|
22
22
|
const GOOGLE_RETRY_MAX_MS = 2_000;
|
|
23
23
|
|
|
@@ -60,7 +60,7 @@ export async function fetchGoogleWithRetry(
|
|
|
60
60
|
let retryDelayMs = 0;
|
|
61
61
|
let sendClass: SendClass = "transient";
|
|
62
62
|
let recovery: AttemptRecoveryKind | undefined;
|
|
63
|
-
for (let attempt = 0; attempt <
|
|
63
|
+
for (let attempt = 0; attempt < TRANSIENT_RETRY_MAX_ATTEMPTS; attempt++) {
|
|
64
64
|
if (ctx.abortSignal?.aborted) throw abortError(ctx.abortSignal);
|
|
65
65
|
try {
|
|
66
66
|
const res = await send({ url: activeRequest.url, sendClass, recovery,
|
|
@@ -105,7 +105,7 @@ export async function fetchGoogleWithRetry(
|
|
|
105
105
|
continue;
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
if (!retryableGoogleStatus(res.status) || attempt ===
|
|
108
|
+
if (!retryableGoogleStatus(res.status) || attempt === TRANSIENT_RETRY_MAX_ATTEMPTS - 1) {
|
|
109
109
|
return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
|
|
110
110
|
}
|
|
111
111
|
// A 429 may be a transient rate limit (retry) or hard quota exhaustion (do NOT retry —
|
|
@@ -141,7 +141,7 @@ export async function fetchGoogleWithRetry(
|
|
|
141
141
|
throw err;
|
|
142
142
|
}
|
|
143
143
|
lastError = err;
|
|
144
|
-
if (attempt ===
|
|
144
|
+
if (attempt === TRANSIENT_RETRY_MAX_ATTEMPTS - 1) throw err;
|
|
145
145
|
sendClass = "transient";
|
|
146
146
|
recovery = isConnectionResetError(err) ? "connection-reset" : undefined;
|
|
147
147
|
retryDelayMs = retryBackoffDelayMs(attempt, {
|
package/src/adapters/google.ts
CHANGED
|
@@ -231,6 +231,72 @@ function toolResultImageParts(content: string | OcxContentPart[]): unknown[] {
|
|
|
231
231
|
* surfaced on Claude-on-Antigravity; the guard lives here because this is where the parts are
|
|
232
232
|
* built. Mirrors the Anthropic adapter's own empty-block guard (src/adapters/anthropic.ts).
|
|
233
233
|
*/
|
|
234
|
+
/**
|
|
235
|
+
* A video URI Gemini fetches on its own behalf, as a `file_data` reference.
|
|
236
|
+
*
|
|
237
|
+
* Deliberately an allowlist of the forms Google documents, not "anything that is
|
|
238
|
+
* not a data: URL". `file_data` tells Gemini to go and get the bytes; pointing it
|
|
239
|
+
* at an arbitrary host would either fail upstream or make the proxy the reason a
|
|
240
|
+
* caller's private URL got dereferenced by Google. Anything not matched here
|
|
241
|
+
* keeps the existing `[video: …]` text marker.
|
|
242
|
+
*
|
|
243
|
+
* Returns the uri alone: the documented REST example for a YouTube part carries
|
|
244
|
+
* `file_data.file_uri` and nothing else, and the Files API knows the type of what
|
|
245
|
+
* it stored. An invented `mime_type` would be a guess on both paths.
|
|
246
|
+
*
|
|
247
|
+
* https://ai.google.dev/gemini-api/docs/generate-content/video-understanding
|
|
248
|
+
*/
|
|
249
|
+
function geminiFetchableVideoUri(url: string): string | null {
|
|
250
|
+
let parsed: URL;
|
|
251
|
+
try {
|
|
252
|
+
parsed = new URL(url);
|
|
253
|
+
} catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
if (parsed.protocol !== "https:") return null;
|
|
257
|
+
|
|
258
|
+
const host = parsed.hostname.toLowerCase();
|
|
259
|
+
const youtubeHosts = new Set([
|
|
260
|
+
"youtube.com",
|
|
261
|
+
"www.youtube.com",
|
|
262
|
+
"m.youtube.com",
|
|
263
|
+
"music.youtube.com",
|
|
264
|
+
"youtu.be",
|
|
265
|
+
"www.youtube-nocookie.com",
|
|
266
|
+
"youtube-nocookie.com",
|
|
267
|
+
]);
|
|
268
|
+
if (youtubeHosts.has(host)) return url;
|
|
269
|
+
|
|
270
|
+
// The Files API resource form, https://generativelanguage.googleapis.com/v1beta/files/<id>.
|
|
271
|
+
// Anchored at the start so the resumable-upload endpoint (/upload/v1beta/files/<id>) does
|
|
272
|
+
// not match: that URL is not a readable resource, and passing it as `file_data.file_uri`
|
|
273
|
+
// would have Gemini dereference something it cannot read. The version segment stays loose
|
|
274
|
+
// because this service is reachable as v1, v1beta and v1alpha.
|
|
275
|
+
if (host === "generativelanguage.googleapis.com" && /^\/v1[a-z0-9]*\/files\/[^/]+$/.test(parsed.pathname)) {
|
|
276
|
+
return url;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* The caller's requested video mode as GenerateContent spells it.
|
|
284
|
+
*
|
|
285
|
+
* `media_processing` sits on the part beside `inline_data`/`file_data` and takes
|
|
286
|
+
* `STATIC` (the default) or `AGENTIC`. `processing: "agentic"` — the spelling in
|
|
287
|
+
* the original request and in Google's Interactions API — is a different API and
|
|
288
|
+
* is ignored here, so forwarding it verbatim would have looked like a
|
|
289
|
+
* pass-through while agentic mode never actually engaged.
|
|
290
|
+
*
|
|
291
|
+
* Upper-cased and forwarded rather than checked against our own copy of the enum:
|
|
292
|
+
* that list is Google's to extend, and a stale allowlist here would silently
|
|
293
|
+
* downgrade a caller using a newer mode. An unrecognized value fails upstream
|
|
294
|
+
* naming the field, which is a better failure than us dropping it.
|
|
295
|
+
*/
|
|
296
|
+
function geminiMediaProcessing(processing: string | undefined): string | undefined {
|
|
297
|
+
return processing ? processing.toUpperCase() : undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
234
300
|
const GEMINI_EMPTY_PLACEHOLDER = "(empty)";
|
|
235
301
|
const GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER = "(empty tool output)";
|
|
236
302
|
const GEMINI_MISSING_TOOL_RESULT = "[missing tool_result for this tool_use in history]";
|
|
@@ -341,10 +407,34 @@ function messagesToGeminiFormat(
|
|
|
341
407
|
continue;
|
|
342
408
|
}
|
|
343
409
|
if (p.type === "video") {
|
|
410
|
+
// `media_processing` rides on the PART, so it applies to inline bytes
|
|
411
|
+
// exactly as it does to a fetched uri — emitting it on only one of the
|
|
412
|
+
// two would silently drop the mode for data: URLs.
|
|
413
|
+
const mediaProcessing = geminiMediaProcessing(p.processing);
|
|
414
|
+
const processingPart = mediaProcessing ? { media_processing: mediaProcessing } : {};
|
|
415
|
+
|
|
416
|
+
// Gemini accepts inline video bytes in the same Part union as images.
|
|
344
417
|
const data = parseDataUrl(p.videoUrl);
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
418
|
+
if (data) {
|
|
419
|
+
parts.push({
|
|
420
|
+
inline_data: { mime_type: data.mediaType, data: data.base64 },
|
|
421
|
+
...processingPart,
|
|
422
|
+
});
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
// Two URI forms Gemini fetches itself: a YouTube watch URL and a Files API
|
|
426
|
+
// uri. Those ARE valid file_data references (#3271), and flattening them to
|
|
427
|
+
// a text marker was the whole reason agentic video could not be reached —
|
|
428
|
+
// the video never arrived as a video. Every other remote URL keeps the
|
|
429
|
+
// marker: we have no mime type for it and no evidence Gemini can fetch it.
|
|
430
|
+
const fileUri = geminiFetchableVideoUri(p.videoUrl);
|
|
431
|
+
if (fileUri) {
|
|
432
|
+
// Emitted only when the caller asked for a mode, so no existing
|
|
433
|
+
// request gains a field it did not have.
|
|
434
|
+
parts.push({ file_data: { file_uri: fileUri }, ...processingPart });
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
parts.push({ text: `[video: ${p.videoUrl}]` });
|
|
348
438
|
continue;
|
|
349
439
|
}
|
|
350
440
|
if (p.type === "document") {
|
|
@@ -63,6 +63,7 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
63
63
|
let requestSnapshot: OcxParsedRequest | undefined;
|
|
64
64
|
let firstRequestBodyBytes = 0;
|
|
65
65
|
let requestAbortSignal: AbortSignal | undefined;
|
|
66
|
+
let requestExecutor: typeof globalThis.fetch | undefined;
|
|
66
67
|
// Captured the same way as the abort signal, because the text-fallback rebuild below runs
|
|
67
68
|
// outside the fetchResponse frame and used to construct a context without either (#4546).
|
|
68
69
|
let requestSendBudget: RequestExecutionBudget | undefined;
|
|
@@ -236,6 +237,8 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
236
237
|
const fallbackOrdinalBase = physicalSendsObserved;
|
|
237
238
|
const response = await fetchKiroWithRetry(retry.request, {
|
|
238
239
|
abortSignal: requestAbortSignal,
|
|
240
|
+
...(requestExecutor ? { executor: requestExecutor } : {}),
|
|
241
|
+
allowGatewayRotation: !_sawReasoning && !assistantText.trim(),
|
|
239
242
|
returnRawErrors: true,
|
|
240
243
|
stream: true,
|
|
241
244
|
// The text-fallback rebuild used to construct a fresh context and drop the budget,
|
|
@@ -318,6 +321,7 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
318
321
|
// Keep it for the adapter-owned bounded continuation so cancelling the client turn aborts
|
|
319
322
|
// both the first Kiro request and its one allowed completion retry.
|
|
320
323
|
if (ctx?.abortSignal) requestAbortSignal = ctx.abortSignal;
|
|
324
|
+
requestExecutor = ctx?.executor;
|
|
321
325
|
if (ctx?.sendBudget) requestSendBudget = ctx.sendBudget;
|
|
322
326
|
if (ctx?.onPhysicalSend) requestOnPhysicalSend = ctx.onPhysicalSend;
|
|
323
327
|
// Reset per fetch call, because `ordinal` is defined within one call and the caller records
|
|
@@ -327,8 +331,8 @@ export function createKiroAdapter(provider: OcxProviderConfig): ProviderAdapter
|
|
|
327
331
|
// Routed through the same forwarder as the fallback so both legs share one ordinal
|
|
328
332
|
// sequence; a context without an observer is passed through untouched.
|
|
329
333
|
return fetchKiroWithRetry(request, requestOnPhysicalSend
|
|
330
|
-
? { ...ctx, onPhysicalSend: (send: KiroPhysicalSend) => forwardPhysicalSend(send, 0) }
|
|
331
|
-
: ctx);
|
|
334
|
+
? { ...ctx, returnRawErrors: true, onPhysicalSend: (send: KiroPhysicalSend) => forwardPhysicalSend(send, 0) }
|
|
335
|
+
: { ...ctx, returnRawErrors: true });
|
|
332
336
|
},
|
|
333
337
|
|
|
334
338
|
formatErrorBody(status: number, headers: Headers, payloadText: string): string {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
classifyKiroHttpError,
|
|
13
13
|
classifyKiroStreamError,
|
|
14
14
|
safeKiroErrorMessage,
|
|
15
|
+
safeKiroHttpErrorMessage,
|
|
15
16
|
type KiroErrorClassification,
|
|
16
17
|
} from "../kiro-errors";
|
|
17
18
|
import { parseKiroEvent } from "../kiro-events";
|
|
@@ -175,6 +176,7 @@ function mergeKiroUsage(
|
|
|
175
176
|
...(sumOptional("cachedInputTokens") !== undefined ? { cachedInputTokens: sumOptional("cachedInputTokens") } : {}),
|
|
176
177
|
...(sumOptional("cacheReadInputTokens") !== undefined ? { cacheReadInputTokens: sumOptional("cacheReadInputTokens") } : {}),
|
|
177
178
|
...(sumOptional("cacheCreationInputTokens") !== undefined ? { cacheCreationInputTokens: sumOptional("cacheCreationInputTokens") } : {}),
|
|
179
|
+
...(sumOptional("providerCredits") !== undefined ? { providerCredits: sumOptional("providerCredits") } : {}),
|
|
178
180
|
...(sumOptional("reasoningOutputTokens") !== undefined ? { reasoningOutputTokens: sumOptional("reasoningOutputTokens") } : {}),
|
|
179
181
|
...(first.estimated || second.estimated ? { estimated: true } : {}),
|
|
180
182
|
};
|
|
@@ -319,6 +321,7 @@ async function* parseKiroAttemptEvents(
|
|
|
319
321
|
let completionAnswer: string | undefined;
|
|
320
322
|
let completionCalls = 0;
|
|
321
323
|
let authoritativeUsage: OcxUsage | undefined;
|
|
324
|
+
let providerCredits: number | undefined;
|
|
322
325
|
let stopReason: string | undefined;
|
|
323
326
|
const fallbackEvents: AdapterEvent[] = [];
|
|
324
327
|
const thinking = new InlineThinkTagParser(budget);
|
|
@@ -379,7 +382,11 @@ async function* parseKiroAttemptEvents(
|
|
|
379
382
|
contextUsageTotalFloor() ?? 0,
|
|
380
383
|
authoritativeTurnTotal,
|
|
381
384
|
);
|
|
382
|
-
return
|
|
385
|
+
return {
|
|
386
|
+
...base,
|
|
387
|
+
...(contextTotal > 0 ? { contextTotalTokens: contextTotal } : {}),
|
|
388
|
+
...(providerCredits !== undefined ? { providerCredits } : {}),
|
|
389
|
+
};
|
|
383
390
|
};
|
|
384
391
|
|
|
385
392
|
const classifiedTerminal = (failure: KiroErrorClassification): AdapterEvent => {
|
|
@@ -599,6 +606,9 @@ async function* parseKiroAttemptEvents(
|
|
|
599
606
|
const ev = parseKiroEvent(eventType, msg.payload);
|
|
600
607
|
if (!ev) continue;
|
|
601
608
|
switch (ev.type) {
|
|
609
|
+
case "metering":
|
|
610
|
+
if (ev.unit === "credit" || ev.unit === "credits") providerCredits = ev.usage;
|
|
611
|
+
break;
|
|
602
612
|
case "metadata":
|
|
603
613
|
if (ev.usage) authoritativeUsage = ev.usage;
|
|
604
614
|
if (ev.contextUsagePercentage !== undefined && ev.contextUsagePercentage > 0) {
|
|
@@ -1098,7 +1108,7 @@ export async function* parseKiroStream(
|
|
|
1098
1108
|
const failure = classifyKiroHttpError(fallback.response.status, fallback.response.headers, payload);
|
|
1099
1109
|
yield {
|
|
1100
1110
|
type: "error",
|
|
1101
|
-
message:
|
|
1111
|
+
message: safeKiroHttpErrorMessage(fallback.response.status, fallback.response.headers, payload),
|
|
1102
1112
|
status: failure.status,
|
|
1103
1113
|
errorType: failure.errorType,
|
|
1104
1114
|
code: failure.code,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { estimateTokens, estimateTokensFromCharacterCounts } from "../../lib/token-estimate";
|
|
2
2
|
import { KIRO_MODEL_CONTEXT_WINDOWS, normalizeKiroModelId } from "../../providers/kiro-models";
|
|
3
|
+
import { kiroObservedContextWindow } from "../../providers/kiro-model-catalog";
|
|
3
4
|
import { modelRecordValue } from "../../reasoning-effort";
|
|
4
5
|
import { sniffImageDimensions } from "../anthropic-image-guard";
|
|
5
6
|
import type { KiroImage } from "../kiro-images";
|
|
@@ -219,7 +220,8 @@ export function kiroUpstreamContextWindow(modelId: string | undefined): number |
|
|
|
219
220
|
if (!modelId) return undefined;
|
|
220
221
|
const normalizedModelId = normalizeKiroModelId(modelId);
|
|
221
222
|
if (normalizedModelId === "auto") return undefined;
|
|
222
|
-
const window =
|
|
223
|
+
const window = kiroObservedContextWindow(modelId)
|
|
224
|
+
?? modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, modelId)
|
|
223
225
|
?? modelRecordValue(KIRO_MODEL_CONTEXT_WINDOWS, normalizedModelId);
|
|
224
226
|
return typeof window === "number" && Number.isFinite(window) && window > 0 ? window : undefined;
|
|
225
227
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { parseUpstreamJsonPayload, safeUpstreamErrorString, sanitizeUpstreamErrorText } from "./upstream-http-error";
|
|
2
|
+
import { debugProviderDiagnostic } from "../lib/debug";
|
|
2
3
|
const DETAIL_KEYS = ["__type", "code", "error", "name", "reason", "message", "Message", "errorMessage"];
|
|
4
|
+
const KIRO_5XX_DIAGNOSTIC_STATUSES = new Set([500, 502, 503, 504]);
|
|
3
5
|
|
|
4
6
|
export interface KiroErrorClassification {
|
|
5
7
|
message: string;
|
|
@@ -204,5 +206,13 @@ export function classifyKiroEventError(reason: string | undefined, message: stri
|
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
export function safeKiroHttpErrorMessage(status: number, headers: Headers | Record<string, unknown>, payloadText: string): string {
|
|
207
|
-
|
|
209
|
+
const failure = classifyKiroFailure(headers, payloadText, status);
|
|
210
|
+
if (status >= 500) {
|
|
211
|
+
debugProviderDiagnostic("kiro", "http_error", {
|
|
212
|
+
status: KIRO_5XX_DIAGNOSTIC_STATUSES.has(status) ? status : "other_5xx",
|
|
213
|
+
code: failure.code === "server_is_overloaded" ? "server_is_overloaded" : "upstream_server_error",
|
|
214
|
+
});
|
|
215
|
+
return status === 504 ? "Kiro upstream gateway timeout" : "Kiro upstream service unavailable";
|
|
216
|
+
}
|
|
217
|
+
return failure.message;
|
|
208
218
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { OcxUsage } from "../types";
|
|
2
|
+
import { debugProviderDiagnostic } from "../lib/debug";
|
|
2
3
|
import { kiroTruncationReason } from "./kiro-truncation";
|
|
3
4
|
|
|
4
5
|
export type ParsedKiroEvent =
|
|
5
6
|
| { type: "content"; data?: string; modelId?: string }
|
|
6
7
|
| { type: "reasoning"; data?: string; signature?: string; redactedContent?: string }
|
|
7
8
|
| { type: "context_usage"; contextUsagePercentage: number }
|
|
9
|
+
| { type: "metering"; unit: string; usage: number; unitPlural?: string }
|
|
8
10
|
| { type: "tool"; name?: string; toolUseId?: string; input?: string; stop?: boolean }
|
|
9
11
|
| { type: "truncation"; data: string }
|
|
10
12
|
| { type: "metadata"; usage?: OcxUsage; contextUsagePercentage?: number; stopReason?: string }
|
|
@@ -17,10 +19,12 @@ const KNOWN_EVENT_TYPES = new Set([
|
|
|
17
19
|
"reasoningContentEvent",
|
|
18
20
|
"toolUseEvent",
|
|
19
21
|
"messageMetadataEvent",
|
|
22
|
+
"initial-response",
|
|
20
23
|
"metadataEvent",
|
|
21
24
|
// Authoritative context pressure. Every capture (kiro-cli 2.14.1 and 2.16.0) put the percentage
|
|
22
25
|
// HERE and left `metadataEvent` carrying only `stopReason`; metadataEvent's own
|
|
23
26
|
// contextUsagePercentage stays supported as a fallback rather than being dropped.
|
|
27
|
+
"meteringEvent",
|
|
24
28
|
"contextUsageEvent",
|
|
25
29
|
"invalidStateEvent",
|
|
26
30
|
"error",
|
|
@@ -111,7 +115,11 @@ function parseTokenUsage(eventType: string, value: unknown): OcxUsage | undefine
|
|
|
111
115
|
/** Decode a known Kiro event using its Smithy `:event-type` header. */
|
|
112
116
|
export function parseKiroEvent(eventType: string, payload: Uint8Array): ParsedKiroEvent | null {
|
|
113
117
|
// Unknown event types are intentionally ignored without parsing or logging their payload.
|
|
114
|
-
if (!KNOWN_EVENT_TYPES.has(eventType))
|
|
118
|
+
if (!KNOWN_EVENT_TYPES.has(eventType)) {
|
|
119
|
+
// The Smithy header is upstream-controlled too; a raw value can contain private data.
|
|
120
|
+
debugProviderDiagnostic("kiro", "unknown_event", { eventTypeLength: eventType.length });
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
115
123
|
const parsed = parseObject(eventType, payload);
|
|
116
124
|
// A metadataEvent's `stopReason` is Kiro's own terminal verdict and must reach the parser
|
|
117
125
|
// intact. The generic truncation sniffer matches substrings ("max_tokens", "length",
|
|
@@ -174,7 +182,25 @@ export function parseKiroEvent(eventType: string, payload: Uint8Array): ParsedKi
|
|
|
174
182
|
? { stop: optionalBoolean(eventType, parsed, "stop") }
|
|
175
183
|
: {}),
|
|
176
184
|
};
|
|
185
|
+
case "meteringEvent": {
|
|
186
|
+
const unit = optionalString(eventType, parsed, "unit");
|
|
187
|
+
if (unit === undefined) {
|
|
188
|
+
return malformed(eventType, "unit must be a string");
|
|
189
|
+
}
|
|
190
|
+
const unitPlural = optionalString(eventType, parsed, "unitPlural");
|
|
191
|
+
const rawUsage = parsed.usage !== undefined ? parsed.usage : parsed.amount;
|
|
192
|
+
if (typeof rawUsage !== "number" || !Number.isFinite(rawUsage) || rawUsage < 0) {
|
|
193
|
+
return malformed(eventType, "usage must be a finite non-negative number");
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
type: "metering",
|
|
197
|
+
unit,
|
|
198
|
+
usage: rawUsage,
|
|
199
|
+
...(unitPlural !== undefined ? { unitPlural } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
177
202
|
case "messageMetadataEvent":
|
|
203
|
+
case "initial-response":
|
|
178
204
|
return {
|
|
179
205
|
type: "message_metadata",
|
|
180
206
|
conversationId:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Account-scoped Kiro refusals. Unknown data never convicts an account. */
|
|
2
|
+
import { BOUNDED_BODY_MAX_BYTES } from "../lib/bounded-body";
|
|
3
|
+
export type KiroRefusalKind = "rate" | "monthly_quota" | "suspended" | "other";
|
|
4
|
+
export interface KiroRefusal { kind: KiroRefusalKind; resetAt?: number }
|
|
5
|
+
|
|
6
|
+
const SUSPENSION_WORDS = [
|
|
7
|
+
"temporarily suspended", "temporarily is suspended",
|
|
8
|
+
"locked your account", "locked it as a",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export function classifyKiroRefusal(status: number, bodyText: string): KiroRefusal {
|
|
12
|
+
if (Buffer.byteLength(bodyText, "utf8") > BOUNDED_BODY_MAX_BYTES)
|
|
13
|
+
return { kind: status === 429 ? "rate" : "other" };
|
|
14
|
+
let reason: unknown;
|
|
15
|
+
let message: unknown;
|
|
16
|
+
try {
|
|
17
|
+
const row: unknown = JSON.parse(bodyText);
|
|
18
|
+
if (row && typeof row === "object" && !Array.isArray(row)) {
|
|
19
|
+
reason = (row as Record<string, unknown>).reason;
|
|
20
|
+
message = (row as Record<string, unknown>).message;
|
|
21
|
+
}
|
|
22
|
+
} catch { /* Unrecognised bodies cannot establish an account verdict. */ }
|
|
23
|
+
if ((status === 400 || status === 429) && reason === "MONTHLY_REQUEST_COUNT")
|
|
24
|
+
return { kind: "monthly_quota" };
|
|
25
|
+
if (status === 403 && (reason === "TEMPORARILY_SUSPENDED"
|
|
26
|
+
|| (typeof message === "string" && SUSPENSION_WORDS.some(word => message.toLowerCase().includes(word)))))
|
|
27
|
+
return { kind: "suspended" };
|
|
28
|
+
if (status === 429) return { kind: "rate" };
|
|
29
|
+
return { kind: "other" };
|
|
30
|
+
}
|