Skip to content

Latest commit

 

History

History
695 lines (604 loc) · 38.9 KB

File metadata and controls

695 lines (604 loc) · 38.9 KB

Configuration reference

Full ~/.config/opencode/kiro-auth-plugin/kiro.json example and every supported option. See the root README for the short version.

Example kiro.json

{
  "auto_sync_kiro_cli": false,
  "account_selection_strategy": "lowest-usage",
  "distribute_across_processes": true,
  "per_request_spread": false,
  "quota_avoidance_enabled": true,
  "quota_reserve_threshold": 0.95,
  "stop_on_overage": true,
  "overage_threshold": 0,
  "default_region": "us-east-1",
  "idc_start_url": "https://your-company.awsapps.com/start",
  "idc_region": "us-east-1",
  "rate_limit_retry_delay_ms": 5000,
  "rate_limit_max_retries": 3,
  "max_request_iterations": 20,
  "sdk_response_timeout_enabled": false,
  "sdk_response_timeout_ms": 300000,
  "sdk_http_keep_alive": false,
  "stream_event_timeout_enabled": false,
  "request_timeout_ms": 120000,
  "stream_buffer_until_complete": false,
  "compaction_buffer_until_complete": true,
  "stream_max_attempts": 3,
  "stream_recovery_mode": "off",
  "stream_recovery_reuse_conversation_id_across_accounts": false,
  "token_expiry_buffer_ms": 300000,
  "token_keepalive_enabled": false,
  "token_keepalive_interval_ms": 600000,
  "usage_sync_max_retries": 3,
  "usage_tracking_enabled": true,
  "auto_effort_mapping": true,
  "enable_log_api_request": false,
  "diagnostic_log_level": "off",
  "log_retention_days": 7,
  "log_max_total_size_mb": 512,
  "log_compress_after_days": 1,
  "log_segment_size_mb": 16
}

New default keys are backfilled into an existing kiro.json automatically on load: when the plugin adds an option in a new version, it is appended to your file with its default value the next time the plugin loads. Backfill is additive only — it never changes, reorders, or removes keys you already set, never rewrites a file that is already complete, and leaves an unparseable file untouched.

Storage layout

The plugin config, logs, and refresh/keep-alive locks live under ~/.config/opencode/kiro-auth-plugin/ (%APPDATA%\opencode\kiro-auth-plugin\ on Windows). Existing safe files are migrated automatically once, so no manual action is needed. The SQLite database remains at ~/.config/opencode/kiro.db because moving a live database during an upgrade is unsafe.

Options

  • auto_sync_kiro_cli: Automatically sync sessions from Kiro CLI (default: false). kiro-cli stores only one token per auth method, so its auto-sync cannot represent multiple accounts and can overwrite a freshly-rotated plugin token with a stale one. Manual opencode auth login per account is the supported multi-account path; enable this only if you rely on kiro-cli.
  • account_selection_strategy: Account rotation strategy (default: lowest-usage). See the strategy table below.
  • distribute_across_processes: Spread simultaneously-started OpenCode processes across different accounts using a DB-backed atomic counter (default: true). Set to false to restore the old behavior where every process starts from the first account. See Account distribution across processes below.
  • per_request_spread: Re-pick the lowest-usage account on every single request instead of pinning to the process's assigned account (default: false). Overrides sticky pinning from account_selection_strategy. See Account distribution across processes below.
  • quota_avoidance_enabled: Softly avoid near-exhausted accounts when multiple accounts are registered (default: true). See Quota-aware account avoidance below.
  • quota_reserve_threshold: Usage-ratio cutoff (0-1, default: 0.95) above which an account is considered near-exhausted and gets soft-avoided. Only meaningful when quota_avoidance_enabled is true.
  • stop_on_overage: Stop selecting accounts that have entered AWS paid overage (default: true). See Overage protection below.
  • overage_threshold: Paid-overage invocations tolerated before stopping an account (default: 0, meaning stop on any overage). Only meaningful when stop_on_overage is true.
  • default_region: AWS region (us-east-1, us-west-2).
  • idc_start_url: Default IAM Identity Center Start URL (e.g. https://your-company.awsapps.com/start). Leave unset/blank to default to AWS Builder ID.
  • idc_region: IAM Identity Center (SSO OIDC) region (sso_region). Defaults to us-east-1.
  • rate_limit_retry_delay_ms: Delay between rate limit retries (1000-60000ms).
  • rate_limit_max_retries: Maximum retry attempts for rate limits (0-10).
  • max_request_iterations: Maximum loop iterations to prevent hangs (10-1000).
  • sdk_response_timeout_enabled: Opt into a fixed initial-response deadline covering client.send() and the first upstream stream event (default: false). It is disabled because a pending request is ambiguous: high-effort models can legitimately spend several minutes generating before their first event, and Windows sleep or network transitions can consume a wall-clock deadline. Caller cancellation still aborts the SDK request and releases the request queue. Override with KIRO_SDK_RESPONSE_TIMEOUT_ENABLED.
  • sdk_response_timeout_ms: Fixed SDK response deadline when sdk_response_timeout_enabled is true (30000-600000ms, default: 300000). Override with KIRO_SDK_RESPONSE_TIMEOUT_MS.
  • sdk_http_keep_alive: Reuse a completed SDK HTTP connection for a later request (default: false). The default gives every request a fresh socket to avoid Bun reusing a stale pooled connection. This does not serialize or cap active streams: the SDK still permits up to 50 concurrent sockets per client, and multiple OpenCode processes remain independent. The tradeoff is one additional TCP/TLS handshake per request. Set this to true only when connection reuse has proven stable in your runtime. Override with KIRO_SDK_HTTP_KEEP_ALIVE.
  • stream_event_timeout_enabled: Opt into a fixed inactivity deadline between upstream stream events (default: false). It is disabled because high-effort models can legitimately compute for several minutes between events, so event silence alone cannot distinguish generation from a stalled connection. Caller cancellation and SDK transport errors remain active. Override with KIRO_STREAM_EVENT_TIMEOUT_ENABLED.
  • request_timeout_ms: Stream-event inactivity deadline when stream_event_timeout_enabled is true (30000-600000ms, default: 120000). It starts only after the first raw event and is paused while the downstream consumer is not pulling. Override with KIRO_REQUEST_TIMEOUT_MS.
  • stream_buffer_until_complete: Consume and validate the complete Kiro event stream before exposing any output to OpenCode (default: false). Enable this when long-running agent tasks are more important than live token display. If the upstream connection resets after producing partial content or a partial tool call, the failed attempt remains private to the plugin and can be safely retried without duplicating downstream content or executing a tool twice. Successful responses are still returned as OpenAI-compatible SSE, but their chunks arrive only after Kiro finishes the complete response. Each failed upstream attempt may still consume Kiro quota. Because the response is validated before RequestHandler.handle() returns, this mode also holds the process-local Kiro request queue until the upstream response completes. Override with KIRO_STREAM_BUFFER_UNTIL_COMPLETE.
  • compaction_buffer_until_complete: Atomically buffer only requests that OpenCode marks with agent: "compaction" (default: true). The plugin consumes its private request marker before constructing the AWS request. Failed stream attempts expose no partial summary bytes, while ordinary chat remains live unless stream_buffer_until_complete is also enabled. Set this to false only to restore streaming compaction behavior. Override with KIRO_COMPACTION_BUFFER_UNTIL_COMPLETE.
  • stream_max_attempts: Maximum complete event-stream attempts (1-10, default: 3). This caps the total SDK sends for one inbound provider request — the initial send plus any pre-output stream retries — so 3 means at most three generateAssistantResponse calls for that request. It is distinct from max_request_iterations, which bounds the overall per-request loop that also covers HTTP-error retries and account switches. Both budgets apply at the same time: a stream retry is refused once stream_max_attempts is reached even if loop iterations remain, and the loop still stops at max_request_iterations regardless of remaining stream attempts. In normal live-stream mode, retries remain limited to failures before semantic output. With either global buffering or atomic compaction buffering enabled, this limit also covers failures after upstream output because none of that attempt has reached OpenCode yet. Override with KIRO_STREAM_MAX_ATTEMPTS.
  • stream_recovery_mode: Controls live recovery after semantic output (default: off). reasoning_restart is eligible only when no visible text, tool call, or raw tool intent reached OpenCode; exact_replay additionally permits visible text or tool calls but byte-exactly matches reasoning, visible text, and normalized tool calls. A replay publishes nothing until the entire delivered prefix matches, and divergence or early end fails closed. Every recovery attempt is a real SDK send and consumes quota. Override with KIRO_STREAM_RECOVERY_MODE.
  • stream_recovery_reuse_conversation_id_across_accounts: Recovery attempts on the same account always reuse one transformed semantic request. After an account switch, the default false performs a fresh transform and uses a new Kiro conversationId; unchanged normalized semantics remain correlated by the same recovery group. Set this experimental option to true only to reuse the original conversationId with a different account envelope. This has not been validated by a real Kiro cross-account A/B and does not guarantee deterministic model output. Override with KIRO_STREAM_RECOVERY_REUSE_CONVERSATION_ID_ACROSS_ACCOUNTS.
  • token_expiry_buffer_ms: Token refresh buffer time (30000-300000ms, default: 300000). An access token within this window of expiry is treated as expired and refreshed on next use.
  • token_keepalive_enabled: Opt-in background keep-alive that proactively rotates idle accounts' tokens before they expire (default: false). Recommended for multi-account setups or accounts left idle for long stretches, so a rarely-used account's token stays fresh instead of only refreshing on its next request. It only runs while OpenCode is running (it is an in-process timer, not an OS daemon) and cannot extend past the AWS IAM Identity Center session ceiling — an expired IdC session still requires a full opencode auth login. See Token keep-alive below.
  • token_keepalive_interval_ms: How often the keep-alive scan runs (60000-3600000ms, default: 600000 = 10 minutes). Only meaningful when token_keepalive_enabled is true.
  • usage_sync_max_retries: Retry attempts for usage sync (0-5, default: 3).
  • auth_server_port_start: Legacy/ignored (no local auth server).
  • auth_server_port_range: Legacy/ignored (no local auth server).
  • usage_tracking_enabled: Enable usage tracking and toast notifications (default: true). When enabled, the plugin refreshes each account's used quota into kiro.db after requests (60s cooldown per account) and shows the auth-menu label, startup toast, and ≥90% warning toast described in Reading usage below.
  • auto_effort_mapping: Automatically map OpenCode thinking budgets to Kiro effort levels for supported models (default: true). See docs/MODELS.md for the budget-to-effort table.
  • enable_log_api_request: Enable detailed API request logging (default: false). Keep this off unless you are actively diagnosing a request because records may contain prompt and tool payloads.
  • diagnostic_log_level: Emit privacy-safe request-shape, correlation, and stream-terminal diagnostics (off | basic | verbose, default: off). This is independent from enable_log_api_request: even verbose records only counts, bounded role sequences, enums, booleans, UUIDs, and truncated SHA-256 hashes. It never records prompt/reasoning text, tool names, arguments/results, signatures, account data, email, ARN, or raw session/message IDs. Override with KIRO_DIAGNOSTIC_LOG_LEVEL.
  • log_retention_days: Delete archived and detailed logs older than this many days (1-365, default: 7).
  • log_max_total_size_mb: Maximum combined size of managed logs (16-102400 MiB, default: 512). The oldest closed logs are removed first; active files are protected and bounded by rotation.
  • log_compress_after_days: Gzip an inactive API log segment after this many days (1-30, default: 1). Segments closed by size or date rotation are compressed immediately.
  • log_segment_size_mb: Rotate plugin.log and detailed API log segments at this size (1-256 MiB, default: 16).
  • enable_log_effort_debug: Log each request's inbound body shape (top-level keys and reasoning-related fields only, no message content) and the resolved Kiro effort (default: false). Independent from enable_log_api_request.

Log retention and compression

Detailed API logging no longer creates a request and response JSON file for every call. Records are appended as compact NDJSON to a process-specific segment. Closed segments and rotated plugin.log files are gzip-compressed in the background. Maintenance normally runs after startup/log activity and then at most once every 15 minutes; a large legacy backlog is drained through bounded follow-up batches.

Three independent limits prevent unbounded growth:

  • closed logs older than log_retention_days are deleted;
  • oldest closed logs are deleted when the directory exceeds log_max_total_size_mb;
  • a hard 1000-file safety limit gradually removes legacy per-request JSON files in batches, so upgrading a directory with hundreds of thousands of files does not block streaming.

enable_log_api_request remains off by default. When it is off, routine successful request payloads are not recorded. Failed upstream requests retain only a sanitized request/response pair: the request body is null, the account is a process-local alias, and no prompt, tool payload, email, raw account ID, token, or profileArn is written. Enabling the option records the original diagnostic request shape and may include prompt/tool payloads and account email. All log settings can also be overridden with KIRO_LOG_RETENTION_DAYS, KIRO_LOG_MAX_TOTAL_SIZE_MB, KIRO_LOG_COMPRESS_AFTER_DAYS, and KIRO_LOG_SEGMENT_SIZE_MB.

Diagnostic log levels

diagnostic_log_level is intended for the specific failure class where an assistant says it will perform another action but the persisted turn ends without a tool call. It changes logging only; it does not change request conversion, tool parsing, recovery, buffering, retries, account selection, or stream concurrency.

Level Additional records
off No diagnostic correlation or request-shape records. Existing stream health records remain unchanged.
basic Per-request trace, hashed OpenCode identities, role/tool counts, current-turn kind, and terminal provenance.
verbose Everything in basic, plus bounded role sequences, marker/repair counts, tool-set hashes, and image/reasoning-envelope counts.

When enabled, the chat.headers hook adds a random diagnosticTraceId and one-way 16-hex SHA-256 prefixes for the OpenCode session, agent, and message identities. The plugin validates and consumes those private headers at its request boundary; they are not copied into the CodeWhisperer SDK request. Hashes support cross-log correlation without writing raw identities, but they are still linkable metadata. Return the level to off after collecting a reproduction.

Kiro request shape diagnostics is written once after the inbound body has been transformed to the Kiro wire shape. basic fields compare input message, role, tool-use/result counts with history/current-message counts on the wire. verbose adds:

  • the last 64 role codes for input and wire messages;
  • whether wire history alternates user/assistant roles;
  • counts for empty assistant turns, synthetic marker hits, orphan repairs, flattened orphan results, and reasoning envelopes;
  • hashes of input and wire tool-name sets, never the names themselves;
  • inferred-tool and current-image counts.

The record is an observation of the actual transformed request. It does not infer intent from assistant prose and cannot prove that a model should have called a tool.

Stream observability logging

Stream health is tracked in plugin.log independently from enable_log_api_request, so you can measure upstream stream failures without recording prompt or tool payloads. A start record and a terminal record bound each inbound streaming request; attempt-level records carry the details needed to explain recovery.

Kiro stream request started (INFO) is written exactly once per inbound streaming request, unconditionally — it does not depend on enable_log_api_request, and non-streaming requests are not recorded. Fields: recoveryGroupId, semanticFingerprint, wireConversationId, conversationId, requestKind, sameSemanticAsInitial, sameConversationIdAsInitial, model, effectiveModel, effort, streamDeliveryMode, and processId. This is the denominator every stream-failure rate is measured against, so an account switch or HTTP-error retry inside the same inbound request still produces only one record. The string is a grep target for log-analysis scripts; it is exported as STREAM_REQUEST_STARTED_LOG from src/core/request/request-handler.ts and is treated as a stable contract.

With diagnostic_log_level enabled, start, attempt, warning, failure, and terminal records also carry diagnosticTraceId, sessionHash, agentHash, messageHash, and diagnosticLogLevel. Kiro stream attempt started is then emitted once for every actual SDK stream attempt, including recovery attempts. This distinguishes “the model said it would retry” from a real second SDK send.

Kiro stream ended without completion metadata (WARN, exported as STREAM_MISSING_COMPLETION_LOG, outcome: 'clean_eof_without_completion_metadata') fires when the upstream event stream ends cleanly but never sent completion metadata. The response still completes normally, exactly as before. This endpoint commonly omits completion metadata, so the warning is transport-shape telemetry, not evidence of truncation and never triggers recovery by itself.

Attempt-level stream records — the clean-EOF warning above and each stream failure outcome (retrying, exhausted, terminated_after_output, ignored_after_completion_metadata, recovered) — carry these shared fields:

Field Meaning
recoveryGroupId, semanticFingerprint, wireConversationId Stable recovery correlation, normalized semantics, and actual Kiro wire ID
requestKind, sameSemanticAsInitial Request classification and whether the transformed semantics drifted
sameConversationIdAsInitial Whether this attempt reused the first attempt's Kiro conversation ID
model, effectiveModel, effort, region Requested and resolved model settings and region
accountAlias Process-local alias; raw account IDs and email are omitted
streamAttempt, maxStreamAttempts Attempt number and the stream_max_attempts cap
streamDeliveryMode buffered or live, including compaction-only atomic buffering
sdkHttpKeepAlive The effective sdk_http_keep_alive value
processId, bunVersion OS process id and the Bun runtime version
upstreamEventCount, streamElapsedMs Raw upstream events and wall time for this attempt
emittedReasoningChars, emittedVisibleChars Character counts already observed in the two text channels
emittedToolCount, sawToolIntent Published tool count and any complete, partial, or discarded upstream tool intent

Kiro stream request terminal (INFO, exported as STREAM_TERMINAL_LOG) is written exactly once for every started stream, including success, failure, cancellation, and recovery. Recovery terminals add attemptsUsed, accountsTried, accountAliases, initialFailure, finalFailure, recovered, quotaRelevant, and terminalSource. Correlate it with the start and attempt records using recoveryGroupId; do not infer a root cause from the UI's generic error text alone.

At basic or verbose, terminal records add:

  • terminalProvenance: clean_eof, completion_metadata, semantic_truncation, caller_abort, recovery_exhausted, upstream_error, processing_error, or unknown;
  • downstreamFinishReason: stop or tool-calls only for a clean end, otherwise null;
  • downstreamFinishReasonProvenance: synthesized_from_tool_count for a clean end, otherwise null.

Kiro commonly ends cleanly without completion metadata. In that case the plugin's OpenAI-compatible terminal chunk is derived from the transformed tool count: zero tools becomes stop, one or more becomes tool-calls. This field makes that provenance explicit; it is not evidence that Kiro sent a native stop decision and must not be used to guess tool intent from natural-language text.

The last four are lengths, counts, and a boolean only. No reasoning text, reply text, or tool arguments are ever written to these records — a character count cannot reconstruct content. They exist so a failed attempt can be classified after the fact: an attempt with zero emitted characters, zero tool calls, and no tool intent is safe to reason about differently from one that already put output in front of you.

Account distribution across processes

If you run several OpenCode processes at once (multiple terminals, editor sessions, or CI jobs), each process previously started with its own process-local selection cursor at index 0 — so with no cross-process coordination, every process's first pick landed on the same account, piling all their traffic onto it while other registered accounts sat idle.

distribute_across_processes (default true) fixes this with an atomic counter stored in the plugin_meta table of kiro.db. On startup, each process claims the next counter value and uses it as its starting offset, so concurrently-launched processes begin on different accounts instead of converging on the first one. Each strategy applies that offset differently: sticky does a circular scan over the stable, id-sorted account list starting at the offset, picking the first selectable account at or after it (wrapping around without collapsing back to index 0); lowest-usage uses the offset only as a deterministic tie-breaker over that same id-sorted order, never overriding an account with genuinely lower usage; and round-robin starts its rotation cursor at the offset and cycles through the current pool of available accounts in their existing order. If the counter can't be read for any reason, the process falls back to offset 0 — startup is never blocked by this. Set it to false to restore the old single-offset behavior.

per_request_spread (default false) is a separate, opt-in trade-off: with it on, every request re-picks whichever account currently has the lowest usage, rather than staying pinned to the account the process was assigned. This maximizes spread across accounts but gives up the conversation affinity that a pinned/sticky account provides. Leave it off unless you specifically want per-request rebalancing over sticky conversation pinning.

An internal benchmark (4 concurrent workers × 5 requests each, real generateAssistantResponse calls, us-east-1, claude-opus-4-8) puts the trade-off in numbers. Both distribution modes reach the same throughput — roughly 1.6× the single-account baseline (~34 s → ~21 s wall time) — because that gain comes from spreading load off one account, not from re-picking per request. What differs is the tail: leaving per_request_spread off (the default distribute_across_processes-only mode) kept p95 latency lower (~3.8 s vs ~5.7 s), while turning it on spread quota consumption more evenly across accounts (a 10/10 split vs 15/5). So: keep it off if you care about stable tail latency and conversation pinning; turn it on if even quota draw-down across accounts matters more to you than p95. These are small-sample, directional figures, not an SLA.

Both keys are additive — see the automatic backfill note above; if you already have a ~/.config/opencode/kiro-auth-plugin/kiro.json, these are added with their default values the next time the plugin loads. No manual edit is required.

Token keep-alive

Kiro access tokens last ~1 hour and are normally refreshed on demand — the next request that needs an expired token triggers a refresh (the same model Kiro's own CLI uses). For a single active account that is enough. But with multiple accounts the rotation strategy may leave some accounts idle for long stretches, so an idle account's token only gets refreshed the next time it happens to be picked — which can be much later.

Set token_keepalive_enabled: true to run a lightweight background scan every token_keepalive_interval_ms (default 10 minutes) that proactively refreshes any healthy account whose token is within token_expiry_buffer_ms of expiry. This keeps idle-account tokens rotating so they are ready when selected.

Important properties:

  • In-process only. The scan runs while OpenCode is running. There is no OS-level daemon; nothing refreshes while OpenCode is closed. This matches how kiro-cli and the Kiro IDE behave (on-demand / while-open, no background daemon).
  • Leader-elected. If several OpenCode processes are open, a file lock ensures only one runs the scan, so accounts are not double-refreshed.
  • Bounded by the IdC session. AWS IAM Identity Center caps the session (commonly 8 hours, up to 90 days for Kiro). Once that ceiling is hit even a valid refresh token fails and you must run opencode auth login again — no keep-alive can extend past it.
  • Default off. Enable it explicitly; recommended for multi-account or long-idle setups.

Reasoning effort

Kiro's reasoning depth is controlled by a global effort setting in kiro.json, not by OpenCode's per-agent thinking level. Read the limitation below before trying to give different agents different reasoning depths.

  • effort (low | medium | high | xhigh | max, optional): sets Kiro's reasoning effort for every request sent through the plugin. Leave it unset to fall back to automatic budget-based mapping (below), or medium for thinking-enabled requests with no budget set.
  • auto_effort_mapping (default true): when effort isn't set, this maps OpenCode's thinking budget (thinkingConfig.thinkingBudget on a -thinking model variant) to a Kiro effort level automatically. See the budget table in docs/MODELS.md. Set to false to disable the mapping and always fall back to medium unless effort is explicit.

Which models support effort

Effort only changes behavior on effort-capable models:

  • claude-opus-4-5, claude-opus-4-6, claude-opus-4-6-1m, claude-opus-4-7, claude-opus-4-8, claude-opus-5
  • claude-sonnet-4-5, claude-sonnet-4-5-1m, claude-sonnet-4-6, claude-sonnet-4-6-1m, claude-sonnet-5
  • gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna

xhigh is honored on claude-opus-4-7, claude-opus-4-8, claude-opus-5, claude-sonnet-5, and the GPT 5.6 family; on other effort-capable models it's clamped down to max. Any model outside this list (Haiku, the open-weight models, etc.) ignores effort entirely.

-thinking model ids

Model ids ending in -thinking (e.g. claude-opus-4-8-thinking) trigger Kiro's reasoning mode directly, independent of the global effort key. Their thinkingConfig.thinkingBudget variant maps to an effort level through the same budget table (default medium when no budget is set). See docs/MODELS.md for the full variant catalog.

Debugging effort resolution

Set enable_log_effort_debug (default false) to log each request's inbound body shape (top-level keys and reasoning-related fields only, never message content) plus the effort level the plugin resolved, to ~/.config/opencode/kiro-auth-plugin/logs/plugin.log. This is independent from enable_log_api_request.

Limitation: per-agent thinking level isn't honored

OpenCode lets you set a thinking/reasoning level per agent, either with --variant high|low|max on the CLI or a per-agent variant in oh-my-openagent.json. This plugin cannot see that setting. Capturing real inbound request bodies with --variant high and --variant low produced byte-identical payloads: a plain OpenAI-style {model, max_tokens, messages, ...} body with no reasoningEffort, reasoning, or thinkingConfig field anywhere. OpenCode's orchestration layer consumes the variant upstream and never forwards it to this plugin's custom fetch.

In practice:

  • You can't give different agents different Kiro effort levels through OpenCode's per-agent variant mechanism.
  • Use the global effort key in kiro.json (applies to every request across every agent), or pin an agent to a -thinking model id with an explicit budget in provider.kiro-auth.models, to control effort.
  • Genuinely per-agent effort would need to be implemented at the OpenCode/omo orchestration layer, for example by mapping each agent to a distinct model id (model choice is forwarded), not inside this plugin.

Reasoning display

Reasoning-capable Kiro models (Claude Opus 4.x, and other reasoning-capable Kiro models) stream their chain-of-thought through a distinct event, separate from the final answer text. The plugin picks that event up and surfaces it as OpenCode's own reasoning block (reasoning_content), shown as "Thought: <duration>" plus a collapsible reasoning section above the final reply, instead of dropping it or merging it into the visible answer.

This is separate from the effort setting above:

  • Reasoning is emitted by default for reasoning models, regardless of effort or auto_effort_mapping.
  • effort only scales reasoning depth (how much the model thinks), not whether reasoning is shown. Even at low effort, any reasoning the model produces still streams into the reasoning block.
  • No config needed. There's no toggle for this — reasoning display is automatic for any model/request that has reasoning content to stream.

Account selection strategy

account_selection_strategy controls how the plugin picks which stored Kiro account handles the next request. It only matters once you have more than one account registered (see the root README for how accounts are added).

Value Behavior Default
lowest-usage Picks the healthy account with the lowest used quota on every request. Maximizes combined quota across accounts and keeps usage balanced.
round-robin Cycles through healthy accounts in order, one request each.
sticky Always uses the first account; only switches away when that account becomes unhealthy (rate-limited/403).

Regardless of strategy, failover is automatic: an unhealthy account is skipped in favor of the next healthy one. If all accounts are rate-limited, the plugin waits the minimum reset time and retries. A circuit breaker trips after 10 consecutive selection failures.

Example: round-robin across accounts

{
  "account_selection_strategy": "round-robin",
  "token_keepalive_enabled": true,
  "usage_tracking_enabled": true,
  "usage_sync_max_retries": 3,
  "default_region": "us-east-1"
}

With this config, add two or more accounts via opencode auth login per account, and the plugin cycles through them on each request instead of always favoring the account with the most quota left. token_keepalive_enabled is included here because round-robin can leave individual accounts idle between turns; see Token keep-alive.

Quota-aware account avoidance

quota_avoidance_enabled and quota_reserve_threshold add a soft layer on top of account_selection_strategy. It only kicks in once you have two or more accounts registered; with a single account it's bypassed entirely and that account is used normally (only the usual health/rate-limit checks apply).

With multiple accounts, the plugin splits them into two tiers on each request: accounts whose usage ratio (usedCount / limitCount) is below quota_reserve_threshold ("ample" tier) and accounts at or above it ("near-exhausted" tier). Whatever account_selection_strategy you've configured (sticky, round-robin, or lowest-usage) then runs within the ample tier first, so accounts with room are preferred before ones sitting near their limit. Accounts with an unknown quota (limitCount of 0, e.g. FEATURE_NOT_SUPPORTED) are treated as having a 0 ratio and are never avoided.

Drain fallback: if every account is at or above the threshold, avoidance doesn't block requests. The plugin falls back to using the near-exhausted tier and keeps draining remaining quota normally until an account actually returns a real 402 Quota error, at which point the existing hard account-switch takes over. There's no starvation.

This is a soft, proactive layer only. It does not change any of the existing hard behaviors:

  • A real 402 Quota error still hard-switches to the next account.
  • A 429 rate-limit response still triggers the existing rate-limit handling and account switch.
  • The ≥90%-usage warning toast (see Reading usage) still fires the same as before.

quota_reserve_threshold just tells the account selector to prefer accounts with more headroom before any of those hard limits are hit.

Env overrides: KIRO_QUOTA_AVOIDANCE_ENABLED (boolean), KIRO_QUOTA_RESERVE_THRESHOLD (number, 0-1).

Overage protection

AWS Kiro allows paid overage after the free quota is exhausted: the usage API can return HTTP 200 with currentOverages > 0, and AWS bills those extra invocations at $0.04 per invocation. By default this plugin treats that signal as a hard selection stop so it does not keep spending money silently.

With stop_on_overage: true and overage_threshold: 0, any account whose latest usage sync reports paid overage is excluded from selection. If every otherwise-usable account is blocked by overage, the plugin throws a hard-stop error instead of sleeping or retrying. A clean account that is merely rate-limited still takes precedence and follows the normal wait path.

To intentionally continue with paid overage, set:

{
  "stop_on_overage": false
}

Recovery is automatic after AWS resets the monthly quota: the next successful usage sync records currentOverages: 0, and the account becomes selectable again.

Removing accounts & the removal tombstone

Removing an account (opencode auth loginkiro-auth → "Remove a Kiro account (N stored)" → pick one) deletes it from kiro.db and records its account id in a removed_accounts table — a tombstone.

That tombstone matters because of auto_sync_kiro_cli. With auto-sync on (the default), the plugin re-scans your local kiro-cli database on every auth init and imports whatever sessions it finds there. Before the tombstone existed, this meant a removed account would simply get re-imported on the next startup if it was still present in kiro-cli's own store — the exact bug reported with recurring idc-placeholder+...@awsapps.local accounts. Now syncFromKiroCli checks the tombstone first and skips any account id it lists, so:

  • auto_sync_kiro_cli: true will not revive a removed account. Once removed, it stays removed across restarts and every subsequent sync, no matter how many times auto-sync runs.
  • Re-login clears the tombstone. If you deliberately want that account back, run opencode auth login and log in with it again (Builder ID or IAM Identity Center, matching the identity you removed). That login clears the tombstone entry and the account is stored normally again — auto-sync will also pick it up from kiro-cli afterward if it's still there.
  • This only affects this plugin's own kiro.db. Removing an account here does not touch kiro-cli's own credential store (data.sqlite3). If you also want kiro-cli itself to forget that identity, log it out there directly (kiro-cli logout or equivalent) — that's a separate, unrelated store from this plugin's tombstone.

Reading usage

The plugin surfaces live usage (usedCount/limitCount per account) in the opencode auth login menu label, a startup toast, and a ≥90%-usage warning toast — see the root README for details. There is no persistent status-bar widget. To check usage at any time without restarting OpenCode, query kiro.db directly:

python3 -c "import sqlite3;r=sqlite3.connect('$HOME/.config/opencode/kiro.db').execute('SELECT email,used_count,limit_count FROM accounts').fetchall();[print(f'{e}: {u}/{l} (left {l-u})') for e,u,l in r]"

On Windows, replace the path with %APPDATA%\opencode\kiro.db.