Skip to content

feat: persist conversations, isolate sessions, and fix request-pipeline reliability - #109

Open
RvVeen wants to merge 12 commits into
tickernelz:masterfrom
Servoy:pr/request-and-conversation
Open

feat: persist conversations, isolate sessions, and fix request-pipeline reliability#109
RvVeen wants to merge 12 commits into
tickernelz:masterfrom
Servoy:pr/request-and-conversation

Conversation

@RvVeen

@RvVeen RvVeen commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR bundles the request/response pipeline work that all touches the same core
files (request.ts, request-handler.ts, sdk-client.ts). It persists Kiro
conversations across turns, isolates parallel sessions, routes Pro accounts to
the runtime endpoint, and fixes several reliability issues around streaming,
retries, and history trimming.

Problem

  • Context loss: every request minted a fresh conversationId via
    crypto.randomUUID(), so each agentic turn started a brand-new Kiro session
    and the model lost prior context.
  • History trimmed far too aggressively: the payload was trimmed at 600KB,
    over 6x below Kiro's real limit, discarding older turns prematurely.
  • Parallel sessions collided: multiple OpenCode sessions in one workspace
    shared conversation state.
  • Pro-only models unavailable: glm-5, minimax, etc. are only served by
    runtime.kiro.dev, but all requests went to q.amazonaws.com.
  • Streaming/tool-call edge cases: tool calls could be double-emitted, long
    tool names exceeded Kiro's limit, and thinking output was not surfaced.
  • Retry backoff counted against the request timeout budget, causing
    premature failures on rate-limited accounts.

Solution

  • Conversation persistence: mint a stable conversationId +
    agentContinuationId per (workspace, fingerprint) and persist them in
    SQLite so agentic turns continue the same Kiro conversation. Reset a stale
    conversationId on a 400 ValidationException.
  • Session isolation: key conversation state on the OpenCode x-session-id
    header so parallel sessions in one workspace stay distinct.
  • Configurable history trim: raise the trim threshold to a configurable
    max_payload_bytes (default 4MB). Kiro's hard limit is structure-dependent
    (verified against the live API): a single message survives up to ~7.6MB, but
    many-entry histories are rejected as low as ~5.9MB, so the default stays
    safely below the lowest observed failure. Adds [PAYLOAD]/[TRIM] debug
    logging.
  • Pro endpoint routing: accounts with a profileArn use runtime.kiro.dev
    (which serves the additional models); free Builder ID accounts keep using
    q.amazonaws.com, since the runtime endpoint 400s without a profileArn.
  • Streaming/tool-call hardening: robust inline + bracket tool-call parsing
    without double-emission, per-tool stop tracking, tool-name shortening within
    Kiro's limit, and thinking passthrough.
  • Retry budget fix: exclude backoff sleep from the request timeout budget.
  • Add a cross-process reauth lock (pid + TTL) so concurrent instances don't
    re-authenticate simultaneously, with a bounded OAuth timeout.

Configuration

// kiro.json
{
  "max_payload_bytes": 4000000
}

Testing

  • bun test — all tests pass (adds coverage for the request pipeline,
    streaming/tool-call parsing, image cache, and the conversation/reauth-lock
    parts of the SQLite store)
  • bun run typecheck — clean
  • bun run build — clean
  • Live end-to-end verification against runtime.eu-central-1.kiro.dev,
    including multi-turn context retention and payload-trim behavior on oversized
    histories.

@tickernelz

Copy link
Copy Markdown
Owner

I tried to merge this PR into the latest master while processing the open PRs newest-to-oldest, but it currently has merge conflicts in:

  • src/core/request/error-handler.ts
  • src/core/request/request-handler.ts
  • src/plugin.ts
  • src/plugin/config/schema.ts

Please rebase or merge the latest master into this branch and resolve these conflicts. Once the branch is updated, we can rerun the integration gates and merge it.

@RvVeen
RvVeen force-pushed the pr/request-and-conversation branch from 1e71f36 to 10f21e9 Compare August 10, 2026 09:02
RvVeen added 11 commits August 10, 2026 11:44
Accounts with a profileArn (Kiro Pro / Q Developer Pro) use the
runtime.kiro.dev endpoint, which serves additional models (glm-5,
minimax, etc.) not available on q.amazonaws.com. Free Builder ID
accounts (no profileArn) keep using q.amazonaws.com, since
runtime.kiro.dev returns 400 without a profileArn.

Adds resolveKiroEndpoint() and tears down stale cached SDK clients on
token rotation or endpoint change.

(cherry picked from commit 0c0320c)
Handle inline content_block_start tool calls and bracket-format tool
calls without double-emitting, track per-tool stop state, and map
shortened Kiro tool names back to their original names on the way out.
Shorten long tool names when building history to stay within Kiro's
limits, and thread thinkingRequested through the response handler so
thinking output is surfaced when requested.

(cherry picked from commit da71f35)
Track slept time (excludedMs) so long backoff waits don't count against
the overall retry deadline, and thread it through the error handler.
Harden locked-operations against contention and tidy account selection.

(cherry picked from commit 9b5e23b)
OpenCode strips image parts from conversation state on later turns.
Cache converted Kiro images per (workspace, fingerprint) and re-attach
them to currentMessage when a turn arrives without fresh images, so the
model keeps seeing them. Never restores onto tool-result turns (Kiro
400s on images+toolResults). Gated by the image_carry_forward config
flag (default on); Kiro bills per request, not per token, so re-sending
has no billing impact.

(cherry picked from commit 0416dcc)
Add THINKING_BUDGETS mapping (low/medium/high/default) capped at Kiro's
200k max_thinking_length, used to translate OpenCode's reasoningEffort
selection into an explicit thinking budget.

(cherry picked from commit aad98f5)
Mint a stable conversationId + agentContinuationId per (workspace,
fingerprint) and persist them in SQLite so agentic turns continue the
same Kiro conversation instead of starting fresh each request. Isolate
parallel sessions in one workspace via the x-session-id header, and
reset a stale conversationId on 400 ValidationException. Add a
cross-process reauth lock (pid + TTL) so concurrent instances don't
re-authenticate simultaneously, with a bounded oauth timeout. Dispose
SDK clients, image cache, and DB on plugin shutdown.

(cherry picked from commit b25be3a)
Add unit tests for the SDK client endpoint routing, stream/tool-call
transformer, response handler, error/retry handling, event-stream
parser, image cache, and the conversation/reauth-lock parts of the
SQLite store.
We trimmed conversation history at 600KB, over 6x too aggressive, which
discarded older turns prematurely and caused context loss in longer
agentic sessions. Kiro rejects oversized payloads with
CONTENT_LENGTH_EXCEEDS_THRESHOLD; the hard limit is structure-dependent
(verified against the live API): a single message survives up to ~7.6MB,
but many-entry histories are rejected as low as ~5.9MB.

Raise the trim threshold to a configurable max_payload_bytes (default
4MB), which stays safely below the lowest observed failure regardless of
history shape while allowing far more context. Add [PAYLOAD] and [TRIM]
debug logging so payload growth and trim events are visible.

(cherry picked from commit ef53269)
Kiro SDK does not send a stop event for tool calls with no parameters
(e.g. time_currentTime). The stream ends with the tool call still in
activeToolCalls, which was incorrectly treated as a 64K truncation.

Now: tool calls that received no input at stream end are treated as
complete. Only calls with partial input are flagged as truncated.
resolveKiroEndpoint used auth.region (SSO/IDC home region) to pick the
endpoint host, while transformToSdkRequest signs using the profile ARN's
region when available. For IAM Identity Center accounts these can differ,
causing a host/signing-region mismatch that surfaces as a spurious 400
that looks like model/region unavailability.

Also reword the bare "Invalid model ID" 400 into an actionable message —
Kiro rolls out model availability per region gradually, so this explains
that instead of leaving a cryptic server string.
While rebasing this branch onto upstream/master, three issues surfaced
during review that needed fixing on top of the mechanical conflict
resolution:

1. Test API drift: the tool-name-registry API on upstream
   (createToolNameRegistry/restoreToolName, per-request scoped) diverged
   from this branch's standalone shortenToolName/buildToolNameMaps
   helpers. Updated tests to use the registry API that survived conflict
   resolution, and aligned description/schema length expectations with
   upstream's sanitizeCodeWhispererSchema defaults. Also fixed two tests
   that assumed pre-upstream ErrorHandler/AccountManager behavior
   (expiresAt reset on bearer-invalid, markUnhealthy mock) that changed
   upstream in tickernelz#114.

2. Duplicate/dead code introduced by the rebase:
   - request-handler.ts's uiEffort budget lookup used its own
     THINKING_BUDGETS in constants.ts, a scale that disagreed with
     plugin/effort.ts's version (the one getEffectiveEffort/
     budgetToEffort actually use to map a budget back to an effort level
     for the Kiro wire request). UI 'high' silently became wire 'max'
     instead of 'high'. Now imports THINKING_BUDGETS from plugin/effort.ts
     so there is a single source of truth.
   - withInProcessLock() in locked-operations.ts was exported but never
     called anywhere — dead code left over from a lock-hardening commit
     not carried into this branch's account-write path. Removed.
   - image-cache.ts's defaultCacheDir() duplicated the platform config-dir
     lookup already in plugin/config/loader.ts (getConfigDir, now
     exported). Reuses it instead of a fourth copy of the same win32/XDG
     branching.
   - request.ts had a redundant DEBUG/OPENCODE_LOG_LEVEL guard around a
     logger.debug() call that already gates on those env vars internally.

3. Regression: RequestHandler.kiroRequestQueue serializes all Kiro API
   calls across sessions/workspaces and predates this branch (introduced
   in 'Match Kiro CLI retry pacing', still present on upstream/master).
   The session-id extraction change earlier in this branch replaced the
   enqueueKiroRequest() wrapper in handle() with a direct call to
   handleKiroRequest(), silently dropping the serialization. Restored the
   queue with sessionId extracted before enqueueing, so per-session
   conversation isolation and global request pacing both hold.
@RvVeen
RvVeen force-pushed the pr/request-and-conversation branch from 10f21e9 to 057ebd6 Compare August 10, 2026 09:47
mock.module() in Bun registers globally for the whole test run, not just
the file that calls it. accounts.test.ts and error-handler.test.ts mocked
plugin/storage/sqlite.js with a kiroDb stub missing getConversationId/
setConversationId/deleteConversationId — methods this branch adds for
conversation persistence. When either file's mock.module() call executed
before tool-compatibility.test.ts in the same bun test process, it
clobbered the real kiroDb with the incomplete stub, and
tool-compatibility.test.ts crashed on kiroDb.getConversationId is not a
function.

This didn't reproduce locally (file execution order happened to be
favorable) but failed in CI (different order) — the classic run-order-
dependent flakiness that stays invisible until someone else runs it.
Completed both mocks to match the full kiroDb interface.
@RvVeen

RvVeen commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Hi @tickernelz Sorry for the delay... I was on holiday :). But the issue is fixed. I did a rebase with master, fixed some things, and it is all running and tested again.

After this one, I will create another pull request that fixes Windows ARM support (OpenCodeUI / Terminal), which is introduced in #101

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants