@kodax-ai/kodax 0.7.39 → 0.7.40

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +58 -0
  3. package/README_CN.md +31 -0
  4. package/dist/chunks/chunk-6QO6HWGU.js +30 -0
  5. package/dist/chunks/chunk-CLS57NPX.js +460 -0
  6. package/dist/chunks/{chunk-SONW6AC7.js → chunk-EQ5DGS2W.js} +1 -1
  7. package/dist/chunks/chunk-EVIDQWMF.js +5 -0
  8. package/dist/chunks/{chunk-HUAU4KB3.js → chunk-FAVPT4P7.js} +1 -1
  9. package/dist/chunks/{chunk-SF7WD7E5.js → chunk-NDNILSTR.js} +1 -1
  10. package/dist/chunks/chunk-QZEDWITG.js +1226 -0
  11. package/dist/chunks/chunk-V4WSBIXB.js +2 -0
  12. package/dist/chunks/chunk-Z5EBDA6R.js +15 -0
  13. package/dist/chunks/compaction-config-A7XZ6H5Y.js +2 -0
  14. package/dist/chunks/{construction-bootstrap-XSE7ZABG.js → construction-bootstrap-OFPUZTXQ.js} +1 -1
  15. package/dist/chunks/{devtools-MOFU7YQF.js → devtools-EYGFOXEU.js} +1 -1
  16. package/dist/chunks/{dist-WKW4CBG6.js → dist-M57GIWR4.js} +1 -1
  17. package/dist/chunks/{dist-AMUYI7R5.js → dist-OTUF22DA.js} +1 -1
  18. package/dist/chunks/paste-5DSTHQGK.js +2 -0
  19. package/dist/chunks/{utils-3HW4KOGE.js → utils-DFMYJUTE.js} +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/kodax_cli.js +631 -619
  22. package/dist/sdk-agent.js +1 -1
  23. package/dist/sdk-coding.js +1 -1
  24. package/dist/sdk-llm.js +1 -1
  25. package/dist/sdk-repl.js +1 -1
  26. package/dist/sdk-skills.js +1 -1
  27. package/package.json +2 -1
  28. package/dist/chunks/chunk-4E76FLZ3.js +0 -2
  29. package/dist/chunks/chunk-7LQ2NCHF.js +0 -1221
  30. package/dist/chunks/chunk-N2VZ2MJF.js +0 -11
  31. package/dist/chunks/chunk-WEEQZYZS.js +0 -460
  32. package/dist/chunks/chunk-XI75LZIO.js +0 -30
  33. package/dist/chunks/compaction-config-YL4SWWII.js +0 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  > Full history for versions prior to v0.7.0: [CHANGELOG_ARCHIVE.md](docs/CHANGELOG_ARCHIVE.md)
6
6
 
7
+ ## [0.7.40] - 2026-05-13
8
+
9
+ ### Theme
10
+
11
+ **Envelope Spillover + Vision Bridge** — Two parallel-developed features close gaps in the child-agent communication path (FEATURE_121) and the REPL input path (FEATURE_134). FEATURE_121 routes child task summaries through the existing `tool-result-policy.ts` spillover system (50KB per-banner + 200KB envelope aggregate cap, mirror of claudecode's `MAX_TOOL_RESULTS_PER_MESSAGE_CHARS`), removing two prior hard truncations (`orchestration.ts:1033` 1600-char slice + `dispatch-child-tasks.ts:256` 200-char slice) that were silently losing 95%+ of child output bytes. A follow-up LLM blob summarizer fallback handles the residual `spillFailed + content > 100KB` data-loss edge that the main slice's inline fallback would otherwise resolve by blowing the Worker context window. FEATURE_134 adds 5 paste sources (bracketed paste / `@<path>` file refs / macOS Cmd+V auto-link / Windows Alt+V explicit / macOS-Linux Ctrl+V backup) on top of the existing `KodaXImageBlock` AI-layer vision serialization, completing the round-trip from screenshot in clipboard to multimodal `user` message at any of the 12 KodaX providers. A late P0 regression in REPL transcript rendering (transcript items invisible during agent execution under AMA mode on Windows ConPTY) was diagnosed as `useDeferredValue` starvation in Ink under Node.js (no DOM idle-scheduling bridge), fixed by removing the deferral indirection — the 200-item UUID-anchored cap from FEATURE_060 Tier 2 retains the perf protection that was the actual fix for SSH-resume O(N) blow-up.
12
+
13
+ ### Added
14
+
15
+ - **FEATURE_121 — Envelope Spillover Gap-Fix + LLM Blob Summarizer Fallback**. Two-commit landing: main slice (`0a0f844e`) + follow-up LLM blob summarizer (`ba0c82f9` + review fixes `05259ab2`). Removes the `orchestration.ts:1033` 1600-char `truncateText` + `dispatch-child-tasks.ts:256` 200-char `slice` two-layer hard truncation that silently dropped 95%+ of child task output (a 25KB audit report reached the Worker as ~50 tokens). Routes every `<task-completed>` banner through `applyToolResultGuardrail('child_task_summary', ...)` — 50KB head + spill-to-file under `getAgentConfigPath('tool-results')/<id>.txt`, banner now carries a preview + spill path that the Worker reads via standard Read tool. `composeIdleYieldUserMessage` in `@kodax/agent/orchestration/idle-yield.ts` gains a 200_000-chars aggregate envelope cap (mirror of claudecode's `MAX_TOOL_RESULTS_PER_MESSAGE_CHARS=200_000`) — when N banners individually fit but together exceed envelope budget, the enforcer calls `applyToolResultGuardrail(..., { forceSpill: true })` to reclaim space. Capability sections include a `LARGE CHILD OUTPUT (FEATURE_121 v0.7.40)` block teaching the Worker the spillover-path Read pattern. 4-alias × 3-case × 5-runs Layer 2 eval clears PARTIAL SHIP (3/4 aliases ≥80% on each case; mmx/m27 weak floor 60-80% documented in test guide). **Follow-up — LLM Blob Summarizer (last-resort fallback)**: when `persistToolOutput` fails (ENOSPC / EACCES / EROFS / SELinux denial) AND raw content > `LARGE_CONTENT_THRESHOLD_BYTES` (100KB), `dispatch-child-tasks` now calls `ctx.summarizeBlob(content, {maxChars: 8000})` to compress to a ~2-8KB lossy summary preserving file paths / line numbers / error codes / identifiers / findings verbatim, banner-wrapped with `[SPILL FAILED — original ${size} compressed via LLM summarizer; raw content unavailable. Worker: treat this summary as LOSSY...]`. The summarizer callback is injected into `KodaXToolExecutionContext` via lazy-once memoization in `runner-driven.ts` bound to the Worker's own provider/model — layer-independent (`@kodax/agent` stays unaware of LLM client). If the summarizer itself fails (provider error / abort), falls back to inline full content with an emergency banner `[SPILL FAILED AND LLM SUMMARIZER FAILED — original ${size} inlined as last-resort emergency dump...]` so the Worker never silently receives oversized opaque content. Honors the FEATURE_121 contract: **silent data loss is the worst outcome; over-budget but observable is acceptable**. 4-alias × 2-case × 3-runs Layer 2 eval clears SHIP gate (4/4 aliases × 100% retention on audit_report + grep_findings cases). Test guide: `docs/test-guides/FEATURE_121_v0.7.40_TEST_GUIDE.md`. Design doc: `docs/features/v0.7.40.md#feature_121-envelope-spillover-gap-fix--child-task-summary-接入-tool-result-policy`.
16
+ - **FEATURE_159 — MessageQueue as Single Source of Truth + Idle-Yield Mode-Split**. Main commit `948b8879` (Phase 3 mode-split synthetic + unified queued-followup predicate) + design `daa8e846` + follow-up `9d4c6ae4` (queue filter scope + verdict.summary echo) + test-isolation `29369a2a` (MessageQueue test isolation + compaction flake fix). User-reported during v0.7.40 RC: after Worker dispatched 3 child tasks and the main agent was still tool-calling, user typed a follow-up "你是派出了子Agent再做嘛?" — status bar showed `Queue 1` / `Queued follow-ups: 1`, but the prompt looked **swallowed** when the agent finished its investigation (not folded into the answer). Forensic showed two stacked failures: (1) `waitForWakeEvent` consumed messages via `MessageQueue.dequeue()` with no reverse notification to REPL, so the React `state.pendingInputs` retained stale entries and the `Queue N` indicator never cleared; (2) drained prompts were marked `_synthetic: true` and hidden from transcript — the user prompt appeared dropped even though it had landed in the model context. Fix flips the substrate: `MessageQueue` becomes the **single source of truth** with typed event subscribe + frozen snapshot read + `mode`/`id`/`predicate` filtering APIs; REPL reverses its sync direction (queue→React mirror via `subscribe`); `composeIdleYieldUserMessage` branches on `msg.mode` — real user prompts emit as **non-synthetic** user-bubble messages (visible in transcript), task-notifications stay synthetic (silent background framing). Net result: claudecode-parity messageQueueManager semantics + SDK-grade observable substrate, without importing claudecode's `commandLifecycle` module, `recordQueueOperation` file sink, or 3-tier priority (kept KodaX's binary user/background). Net −70 LoC, zero new modules. Test guide: `docs/test-guides/FEATURE_159_v0.7.40_TEST_GUIDE.md`. Design doc: `docs/features/v0.7.40.md#feature_159-messagequeue-as-single-source-of-truth--idle-yield-mode-split`.
17
+ - **FEATURE_134 — Image / Screenshot Paste Input (REPL Vision Bridge)**. Commit `2e9674bb`. Adds the REPL paste entry point on top of KodaX's existing `KodaXImageBlock` AI-layer vision serialization (already implemented in `packages/ai/src/providers/anthropic.ts:770` + `openai.ts:904` + `image-serialization.ts`). 5 paste sources per claudecode `usePasteHandler.ts` parity: (1) bracketed paste base infra (DEC 2004 `ESC[200~/201~` wrapping — note FEATURE_134's redundant `enableBracketedPasteMode()` pre-render write was removed in `ca009b3a` since `KeypressContext.tsx:134` already owns DEC 2004 lifecycle via Ink's managed stdout); (2) file path paste — `extractImagePaths` splits on `/` or `[A-Za-z]:\\` + extension; (3) macOS Cmd+V auto-link via `osascript NSPasteboard` read; (4) Windows Alt+V explicit keybind (Ctrl+V is system-paste-reserved on Windows, same as claudecode); (5) macOS/Linux Ctrl+V backup via `wl-paste` / `xclip` / `osascript`. Building blocks in `packages/repl/src/paste/`: `bracketed-paste-mode.ts` (DEC 2004 lifecycle), `image-normalize.ts` (jimp decode + clamp 2000px + PNG→JPEG quality ladder 80/60/40 to fit 3.75MB), `clipboard-image.ts` (cross-platform reader, never throws), `persist-image.ts` (writes to `$TMPDIR/kodax-paste/` + returns path-based `KodaXImageBlock`), `paste-handler.ts` (5-source orchestrator). Library choice: jimp (~10MB pure JS) over sharp (~30MB native binary) — jimp install never fails on cross-platform CI; claudecode picked sharp for broader use cases but KodaX only paste-uses, so jimp is the simpler fit. Integration: paste event → `@<resolved-path>` text-token translation via existing `common/input-artifacts.ts:preparePromptInputArtifacts` pipeline which already converts `@<path>` refs to `KodaXInputArtifact[]` on submit — no changes needed downstream. Test guide: `docs/test-guides/FEATURE_134_v0.7.40_TEST_GUIDE.md`. Design doc: `docs/features/v0.7.40.md#feature_134-image--screenshot-paste-input--repl-vision-bridge`.
18
+
19
+ ### Fixed
20
+
21
+ - **REPL transcript rendering starvation — `useDeferredValue` removed from `displayHistory` chain**. Commit `ca009b3a`. User-reported P0 (AMA mode, Windows ConPTY): pressing Enter on a query showed nothing in the transcript for the entire agent run — header banner + TodoListSurface + spinner + status bar rendered normally, but assistant thinking blocks / tool calls / tool results / assistant text remained invisible until task completion forced a re-render. Root cause: `useDeferredValue(displayHistory)` (introduced in v0.7.30 FEATURE_060 Tier 2 as a polish on top of the 200-item cap that was the real perf fix) marks transcript rebuild as low-priority React work. Under React DOM, this work flushes during browser idle time via the scheduler's idle-callback bridge. Ink uses react-reconciler without that bridge — under Node.js, high-priority work (spinner ticks @30fps, streaming setState bursts, tool-state updates) perpetually pre-empts the deferred work, so the low-priority track never flushes until a "big" state change like `setIsLoading(false)` forces a sync re-render. v0.7.40 surfaced the latent starvation: FEATURE_121's envelope spillover replaced `slice(0, 200)` child task truncation with up-to-50KB head + spill content, raising per-item `buildPromptSurfaceItems` cost from ~0.1ms (200 chars) to ~10-50ms (50KB) — single-item cost crossed the React scheduler's low-priority deferral threshold, starving the deferred update perpetually under high-frequency setState bursts. Fix: replace `useDeferredValue(displayHistory)` with direct passthrough. The 200-item UUID-anchored cap (the real perf protection from FEATURE_060 Tier 2) and the transcript-mode 30-message cap remain — together with React's built-in `useMemo` memoization (which prevents spinner ticks from triggering `buildPromptSurfaceItems` when history is unchanged), per-render cost stays bounded at O(min(N, 200)). Trade-off: long-session `kodax -c` resume on Windows-SSH may add ~10-40ms one-time first-paint cost — well below human perception threshold (~100ms). Length-thresholded fallback pattern documented inline (`displayHistory.length > 100 ? lazyDeferred : displayHistory`) for future repro.
22
+ - **FEATURE_121 v0.7.40 follow-up build break + memoize + emergency banner**. Commit `05259ab2`. Fixes three review-uncovered issues from `ba0c82f9`: (1) **CRITICAL build break** — `blob-summarizer.ts` imported `KodaXProvider` (not exported from `@kodax-ai/llm`); replaced with `KodaXBaseProvider` everywhere. `tsc --noEmit` missed this; `tsc -b` with declaration emission caught it during `npm run build:packages`. (2) **HIGH memoize** — `runner-driven.ts` `summarizeBlob` was rebuilding the factory closure (including `resolveProvider`) on every call; changed to lazy-once memoize (cached on first invocation, reused for the rest of the Worker run). (3) **MEDIUM emergency banner** — when LLM summarizer ITSELF fails AND we fall back to inline 100KB+ content, the Worker received the raw blob with no banner; now prepends `[SPILL FAILED AND LLM SUMMARIZER FAILED — original ${size} inlined as last-resort emergency dump]` so the Worker sees a clear signal to expect possible downstream truncation and re-run upstream with narrower scope.
23
+ - **`fix(build,v0.7.39): make 'npm run build' produce shippable dist/`** (commit `b77fa0a3`). Footgun caught during clean-room publish dry-run: `npm run build` previously ran `npm run build:packages && tsc` — the trailing bare `tsc` read root `tsconfig.json` (`outDir: ./dist`, `declaration: true`) and overwrote the esbuild bundle with unbundled `tsc` output containing bare `import '@kodax-ai/coding'` specifiers, which would have shipped a broken tarball — consumers running `npm install @kodax-ai/kodax` would hit `ERR_MODULE_NOT_FOUND`. Fix: `"build": "npm run build:packages && npm run build:bundle && tsc --emitDeclarationOnly"`. The `--emitDeclarationOnly` flag is the critical guard — tsc skips `.js` entirely and only writes `.d.ts` on top of the esbuild bundle. Now any caller (developer, CI, `release.mjs`) can run `npm run build` at any time and get a complete, shippable dist/. Side benefit: SDK consumers get real TypeScript types for all 5 subpath entries (was untyped before).
24
+ - **`fix(release,v0.7.40): bake KODAX_VERSION into bundle via esbuild --define`** (commit `b70048b7`). The previous v0.7.39 release bundled with `process.env.KODAX_VERSION` left unresolved, so the runtime version was `undefined` in the published `dist/kodax_cli.js` — visible to users as `KodaX undefined` in the banner. Fix: `scripts/build-bundle.mjs` reads root `package.json` at build time and injects via esbuild `--define`, so the bundled CLI carries the correct version literal. `release.mjs` flow unchanged: bumps `package.json` first, then `npm run build` (which reads the bumped version).
25
+ - **FEATURE_134 follow-up: Alt+V duplicate-image-file accumulation in temp dir**. User-reported regression during v0.7.40 RC validation: pressing Alt+V on the same screenshot created many identical-content files with different UUID names under `$TMPDIR/kodax-paste/`. Two root causes: (1) `prompt-input-controller.ts:triggerExplicitImagePaste` had no single-flight guard — OS-level key autorepeat (Windows ConPTY in particular) fires multiple Alt+V keypresses on a brief hold, each spawning a concurrent `readClipboardImage` + `persistImageAsBlock` pair; (2) `persist-image.ts` used `randomUUID()` filenames so even identical buffer content wrote N distinct files. Fix in [packages/repl/src/paste/persist-image.ts](packages/repl/src/paste/persist-image.ts): filename derived from `sha256(buffer).slice(0,16)` — identical content reuses one path, `writeFile` is idempotent on rewrite. Fix in [packages/repl/src/ui/utils/prompt-input-controller.ts](packages/repl/src/ui/utils/prompt-input-controller.ts): `explicitImagePasteInflightRef` boolean ref guard drops re-entrant invocations until the in-flight clipboard read settles. New tests: `persist-image.test.ts` adds "reuses the same path for identical content" + "produces distinct paths for different content"; `prompt-input-controller.test.ts` adds "Alt+V autorepeat fires the clipboard read only once (single-flight guard)". Together the two fixes cap temp-dir growth per unique screenshot at one file per session.
26
+ - **FEATURE_134 follow-up: Gemini-CLI vision via `@<path>` token injection** (commit `71d45783`). The ACP base class (`packages/ai/src/providers/acp-base.ts`) was silently dropping image blocks at the prompt flatten step (`.filter(b => b.type === 'text')`) before forwarding to the CLI bridge. New extension point `serializeImageBlockToPromptToken(block)` on `KodaXAcpProvider` defaults to `null` (preserves silent-drop for Codex-CLI which has no `codex exec --json` image surface), and `KodaXGeminiCliProvider` overrides to return `@<absolutePath>` — Gemini CLI 2.x's file-include syntax inlines any readable file (including images) into the model context. The CLI-bridge capability profile gets a new sibling `IMAGE_INPUT_CLI_BRIDGE_PROVIDER_CAPABILITY_PROFILE` so the policy gate sees the right metadata. 2 new tests in `cli-bridge-providers.test.ts` pin the exact wire format for both Gemini (`@<path>`) and Codex (null).
27
+ - **FEATURE_134 follow-up: paste tmp dir age-based GC at REPL bootstrap** (commit `0eb6cbb4`). Per-session content-hash dedup (commit `12589a46`) prevented Alt+V autorepeat from stacking duplicates within one session, but cross-session accumulation was still unbounded — files only cleared on OS tmpdir reboot. New `prunePasteTmpDir(now?)` exported from `packages/repl/src/paste/persist-image.ts` deletes `paste-*` files older than `PASTE_TMP_TTL_MS` (24h). Non-paste files in the same dir survive (e.g. user-dropped `notes.txt`); per-file errors swallowed so concurrent KodaX instance races don't break REPL startup. Wired in `InkREPL.tsx` bootstrap as a fire-and-forget dynamic import — never blocks first paint. Active-session paste files (always within TTL) are preserved across overnight idle. 4 new tests cover dir-not-exist / age-based deletion / non-paste preservation / TTL boundary.
28
+ - **FEATURE_134 follow-up: custom provider vision opt-in documented** (commit `db36dc69`). README.md + README_CN.md gain a complete `customProviders` example showing the `capabilityProfile.multimodalSupport: 'image-input'` nested shape. Built-in 12 providers ship with the flag already enabled; only custom providers needed opt-in instructions because the field was implicitly available via `KodaXCustomProviderConfig.capabilityProfile` but undocumented.
29
+ - **AMA in-turn compaction parity gap — three-phase lifecycle restored (microcompact + snapshot-aware trigger + graceful fallback)**. User-reported during v0.7.40 RC validation: status bar climbed from 124k → 138k → 150k across three AMA rounds with zhipu-coding/glm-5.1, no compaction / microcompaction / graceful prune ever firing despite crossing the 60% × 200k = 120k threshold. Latent since FEATURE_114 v0.7.36 (4→2 role consolidation) but masked by FEATURE_076's over-aggressive round-exit reshape collapsing context to ~1k after every round; once the v0.7.40 reshape fix above let context grow naturally, the gap surfaced. Three structural deltas vs SA path's `runCompactionLifecycle` ([compaction-orchestration.ts:358](packages/coding/src/agent-runtime/middleware/compaction-orchestration.ts#L358)): (1) **trigger metric mismatch** — the AMA `compactionHook` ([_internal/managed-task/compaction.ts](packages/coding/src/task-engine/_internal/managed-task/compaction.ts)) compared `estimateTokens(transcript)` to the threshold, but transcript-only estimate excludes the system prompt + tools schema (Worker role prompt + AGENTS.md + REPO INTELLIGENCE TOOLS teaching + repo-intel capsule + 12 tool definitions ≈ 20–35k tokens after FEATURE_114 worker consolidation + FEATURE_161 prompt growth). 200k-window 60% trigger needs API total > 120k, but the hook saw ~95–115k transcript and never fired — the status bar's 138k was the real total (system + tools + transcript) sourced from `streamResult.usage.totalTokens`. (2) **no microcompact phase** — SA path runs `microcompact(messages, DEFAULT_MICROCOMPACTION_CONFIG)` every turn at zero LLM cost ([run-substrate.ts:603](packages/coding/src/agent-runtime/run-substrate.ts#L603)) to prune old tool_results / image blocks past `maxAge=20` turns; AMA hook bypassed it entirely. (3) **no graceful degradation fallback** — SA path's third phase `applyGracefulDegradationGate` ([compaction-orchestration.ts:250](packages/coding/src/agent-runtime/middleware/compaction-orchestration.ts#L250)) deterministically prunes tool_results when LLM compact threw / returned `compacted: false` / left context still above `triggerTokens × pruningGapRatio`; AMA hook bailed silently on LLM failure, letting context grow unbounded. Fix in [packages/coding/src/task-engine/_internal/managed-task/compaction.ts](packages/coding/src/task-engine/_internal/managed-task/compaction.ts): hook now mirrors SA path's three-phase lifecycle — (Phase 1) `microcompact` every call (free, prunes old tool blocks); (Phase 2) `intelligentCompact` LLM summary with snapshot-aware trigger check `resolveContextTokenCount(transcript, snapshot)` instead of raw `estimateTokens` — `snapshot.currentTokens` carries the LAST LLM call's API-reported `usage.totalTokens` (system + tools + transcript) so the threshold check uses the same metric the status bar displays; the snapshot is refreshed by the LLM adapter ([runner-driven.ts](packages/coding/src/task-engine/runner-driven.ts) `buildRunnerLlmAdapter`) after every stream completion via the new `contextTokenSnapshotRef` shared between adapter (writer) and hook (reader); (Phase 3) `gracefulCompactDegradation` deterministic prune fallback when LLM compact failed / partial / circuit-breaker-tripped — the gate's "still over" check also uses snapshot-aware accounting for symmetry with Phase 2. Snapshot rebases to `createEstimatedContextTokenSnapshot(compacted)` after any compaction (LLM or graceful) so subsequent delta corrections start from the compacted baseline rather than the stale pre-compaction API total. New test file [packages/coding/src/task-engine/_internal/managed-task/compaction.test.ts](packages/coding/src/task-engine/_internal/managed-task/compaction.test.ts) pins all three phases with 9 tests covering: microcompact-only return path (below trigger); microcompact identity (no diff returns undefined); snapshot-aware trigger fires when transcript-only estimate is sub-threshold but API-total crosses; no-snapshot cold-start falls back to transcript estimate (unchanged baseline); graceful fallback on LLM throw / `compacted: false` / partial success above gap-ratio; snapshot rebase after successful LLM compaction; circuit-breaker semantics (3 strikes skip LLM but graceful still fires). Side fix in [packages/coding/vitest.config.ts](packages/coding/vitest.config.ts) + [packages/repl/vitest.config.ts](packages/repl/vitest.config.ts): `@kodax-ai/llm` alias updated from stale `packages/ai/` to `packages/llm/` (rename leftover from v0.7.40 directory rename above). **Behaviour delta vs pre-fix**: AMA mode compaction now triggers at the same effective API-total threshold as SA mode; graceful fallback prevents zhipu/kimi/mimo provider-side LLM summary failures from leading to monotonic context growth; microcompact's per-turn pruning recovers ~5–15% of context per long-running Worker turn even without crossing the LLM trigger threshold. Full coding suite green: **2446 tests pass** (239 test files, 23 todo; +9 new compaction hook tests).
30
+ - **FEATURE_076 follow-up: round-boundary reshape now preserves tool_use / tool_result chains across rounds (cache + re-read regression fix)**. User-reported during v0.7.40 release validation: status bar dropped from `121k/200k` to `1.1k/200k` after an AMA round, and follow-up rounds were re-reading files the prior worker had already read. Root cause: FEATURE_076 (v0.7.25)'s `reshapeToUserConversation` replaced `result.messages` wholesale with a synthetic `[user, assistant]` dialog at every round exit, discarding the entire `tool_use` / `tool_result` chain. The replacement was the correct fix for v0.7.25's actual problem (cross-round role-prompt pollution — Evaluator role prompts leaking into the next round's worker context as user messages) but over-corrected by stripping structurally useful content, with two concrete production costs: (1) **cross-round file re-reads** — round 2 worker had no visible `tool_result` for files round 1 already read, so common follow-ups like "now modify that file you reviewed" forced re-reads; (2) **provider prompt-cache miss on the dialog prefix** — round 1's first LLM call prefix was `[system, user, assistant(tool_use), user(tool_result), …]`, round 2's is `[system, user, assistant(final), user_2, …]`; the prefixes diverge immediately after the first user message, so the dialog portion gets zero cache reuse across rounds. Fix in [packages/coding/src/task-engine/_internal/round-boundary.ts](packages/coding/src/task-engine/_internal/round-boundary.ts): new `preserveTranscriptForRoundExit` helper runs a 4-step pipeline — (Step 1) strip the leading stale role-prompt system message (Runner.run leaves the last-active agent's role prompt at `transcript[0]`; round 2's entry agent injects its own at position 0, so keeping the previous one would create two conflicting `system` instructions back-to-back); CompactionSummary system messages are preserved via the now-exported `COMPACTION_SUMMARY_PREFIX` discriminator from `@kodax-ai/session-lineage`. (Step 2) Apply `normalizeLoadedSessionMessages` to strip V1-legacy role-prompt-wrapped trailing `{user, assistant}` pairs (`"You are the Evaluator role..."` phrased as a user message); no-op for V2 AMA where role prompts are system-message-shaped. (Step 3) Ensure the round's user prompt is observable — V2 sessions retain it via `runnerInput`, V1 paths may have lost it when normalisation stripped the wrapper. (Step 4) Ensure the transcript ends with a plain-text assistant carrying the sanitised final answer; **replaces** (not appends) when the last message is an assistant with array content (typically `emit_verdict` / `emit_handoff` tool_use blocks — KodaX protocol machinery whose user-facing payload is captured in `result.lastText`). The replace avoids two consecutive `role: 'assistant'` messages which Anthropic's API rejects on the next request. Net behaviour: round 2 worker sees what round 1 read/edited, prompt-cache prefix stays continuous, status bar reflects the actual context size (e.g. ~50k instead of 1.1k after a heavy worker round) — a side-effect users will perceive as the bar "no longer collapsing after each turn". Updated [packages/coding/src/task-engine/_internal/round-boundary.test.ts](packages/coding/src/task-engine/_internal/round-boundary.test.ts) with 7 new cases covering: V2 worker-shape preservation, terminal `emit_verdict` tool_use replacement (no consecutive-assistant violation), `CompactionSummary` system message preservation, dedup of already-correct trailing assistant, empty `result.messages`, compaction-system-only transcript, terminal `thinking`-only assistant. `COMPACTION_SUMMARY_PREFIX` exported from [packages/session-lineage/src/compaction/compaction.ts](packages/session-lineage/src/compaction/compaction.ts) so the producer-consumer pair no longer maintains the literal in two places. Test guide: regression case lives in [docs/test-guides/FEATURE_134_v0.7.40_TEST_GUIDE.md](docs/test-guides/FEATURE_134_v0.7.40_TEST_GUIDE.md) once that doc's "round-boundary cache + re-read" section is appended. **Behaviour delta vs FEATURE_076 baseline**: the v0.7.25 cross-round-coherence guarantee is preserved (stale role prompts still get stripped); only the over-corrective full-transcript replacement is reverted. Full test suite green at HEAD: **5745 tests pass** (was 5742; +3 new edge-case tests).
31
+ - **FEATURE_168 — AMA agent tool wiring (exclude-based, registry as source of truth)**. Commit `56330d1c` + doc `e902b194`. Root cause investigation surfaced from production trace 2026-05-15 (zhipu/glm51 Worker blocked at `emit_handoff` pending-children gate, told by the gate's own error message to call `task_stop`, model honestly responded that `task_stop` "is not registered as a callable tool" — and the model was correct). `runner-driven.ts::buildRunnerAgentChain` had used **include-mode hand-written `agent.tools` arrays** for all 5 AMA roles (Scout / Planner / Generator / Evaluator / Worker) since v0.7.26, while the SA path defaults to `listToolDefinitions()` minus `excludeTools`. The two paths drifted across three features: **FEATURE_120 v0.7.39** registered `send_message` + `task_stop`, taught them in the Worker prompt, gated `emit_handoff` with a "call `task_stop` first" error message — but never wired the `RunnableTool` instances into any AMA agent's tools array. **FEATURE_161 v0.7.40 prompt teaching** taught the Worker 8 repo-intel pull tools (`module_context` / `symbol_context` / `process_context` / `impact_estimate` + 4 shallow ones), `worker-role-prompt.ts:127` comment claimed "the 8 pull tools get stripped from the LLM-visible tool list (see agent-runtime/tool-resolution.ts)", but AMA path doesn't go through `tool-resolution.ts` — it reads `agent.tools` directly, so only 4 of the 8 ever landed in the schema. **Web tools / ask_user_question / worktree_create / worktree_remove / insert_after_anchor / undo**: registered + `permission` system gave them entries, but no AMA agent could see them. Total **17 registered tools silently dropped** from production AMA; no test layer caught it because no test asserted "agent.tools actually contains a schema entry with this name". Fix: switch AMA path to exclude-based wiring matching SA semantics. New `buildAgentToolsFromRegistry(role, ctx, budget, events, overrides)` helper enumerates `listToolDefinitions()` and applies `AMA_BASELINE_EXCLUDE ∪ <ROLE>_EXTRA_EXCLUDE`. Role-specific wraps (mutation-guarded bash/write/edit/multi_edit for Generator/Worker, read-only bash for Evaluator, `dispatch_child_task` per-role drain wrappers, FEATURE_097 throttle-aware `todo_update`) flow through the `overrides` map. Evaluator security boundary made **architectural, not prompt-dependent**: write/edit/multi_edit/insert_after_anchor/undo/dispatch_child_task/send_message/task_stop/worktree_create/worktree_remove/exit_plan_mode/todo_update/ask_user_question all hard-excluded from `EVALUATOR_EXTRA_EXCLUDE` — a prompt-jailbroken or tool-confused Evaluator is physically unable to mutate, dispatch, or change plan state. Planner kept as read-only inspection role (no bash, no mutation, no dispatch, no user interaction). Scout/Worker carry the full execution surface. Generator unchanged behaviorally; new tools (web/repo-intel/coordinator) added to schema. New contract test [packages/coding/src/task-engine/runner-driven-tool-wiring.test.ts](packages/coding/src/task-engine/runner-driven-tool-wiring.test.ts) pins each role's full tool-name set against `getAmaRoleExpectedToolNames(role)` derivation plus spot-checks for FEATURE_120 / FEATURE_161 coverage / Evaluator boundary / Planner boundary / no-orphan invariant — 50 assertions total. Any future EXCLUDE-set change or registry addition surfaces as a concrete test failure, not a silent production schema gap. **Test impact**: 2507/2507 coding-package tests pass (240 files, +50 new contract assertions). No regression in `runner-driven.test.ts` (130 tests including FEATURE_165 race-regression), `child-executor.test.ts` (30), `task-stop` / `send-message` handler tests (28), `worker-role-prompt.test.ts`. Design doc: [docs/features/v0.7.40.md#feature_168--ama-agent-tool-wiringexclude-based-registry-as-source-of-truth](docs/features/v0.7.40.md#feature_168--ama-agent-tool-wiringexclude-based-registry-as-source-of-truth).
32
+ - **FEATURE_169 — Pull-Tool Prompt Adoption Hardening** (commit `519af4b9`). Production trace after FEATURE_161 wiring + FEATURE_168 schema fix surfaced 3 residual adoption gaps the wiring alone did not close: (1) **Worker hand-feeding bash in `dispatch_child_task.objective`** — 18% of production dispatch (3/17 in 2026-05-15 audit) embedded literal `git diff v0.7.39..HEAD`-style command directives, overriding the child's prompt-side tool teaching; 0/17 objectives recommended a pull-tool family. (2) **Child agent prompt stayed read+grep-first** — `CHILD_AGENT_SYSTEM_PROMPT` had taught "3-8 parallel tool calls (glob + grep + key file reads)" since v0.7.18 with no mention of pull-tools; children defaulted to grep/read in review and exploration tasks. (3) **Worker self-review still picked `bash git diff` first** — F7 taught tool existence but not "for review tasks, use which one". Three localized prompt strengthenings (F0a / F0b / F1v2 / F3) ship; F2 (3-tier order injection) **rejected** post-eval as zero-value churn. F0a teaches Worker to keep dispatch objective as data ("scope: v0.7.39..HEAD") not command ("使用 `git diff v0.7.39..HEAD`"); F0b adds an explicit pull-tool-family recommendation to the dispatch objective teaching; F1v2 adds child-agent reverse-steering toward pull-tools when the task is review/exploration-shaped; F3 reframes the Worker change-review surface from `bash git diff` first to `module_context` / `symbol_context` first. No wiring change, no new tool, no new permission. Prompt eval (`tests/feature-169-pull-tool-adoption.eval.ts`) clears SHIP gate (4/4 aliases, F0a/F0b/F1v2/F3 each ≥80% on 3 cases). Design doc: `docs/features/v0.7.40.md#feature_169--pull-tool-prompt-adoption-hardening-worker-dispatch-objective--child-reverse-steering--change-review-reframe`.
33
+ - **FEATURE_134 follow-up: vision capability flag widened to 9 additional providers**. User empirically validated 2026-05-13 that `kimi-code` accepts and processes image input despite its v0.7.40 RC snapshot flag claiming `multimodalSupport: 'none'`. Root cause: AMA path (`runner-driven.ts` `provider.stream` direct call) bypasses the SA-path `applyProviderPolicyGate` (`run-substrate.ts:660`) where multimodal block enforcement lives, so the latent flag mismatch was never observable in production. The pre-v0.7.40 RC flag was over-conservative: every Anthropic-compat clone inherits the image-block forwarding serializer at `anthropic.ts:770`, and every OpenAI-compat clone inherits the `image_url` forwarding serializer at `openai.ts:904`. Flag widened in `packages/ai/src/providers/registry.ts` from `NATIVE_PROVIDER_CAPABILITY_PROFILE` to `IMAGE_INPUT_NATIVE_PROVIDER_CAPABILITY_PROFILE` for: **Anthropic-compat clones** — `kimi-code`, `zhipu-coding`, `mimo-coding`, `ark-coding`, `minimax-coding`; **OpenAI-compat clones** — `deepseek`, `kimi`, `qwen`, `zhipu`. Plus a separate follow-up commit (`71d45783`) wires **Gemini-CLI** vision via the new `serializeImageBlockToPromptToken` extension point on `KodaXAcpProvider` (Gemini CLI 2.x `@<path>` file-include syntax), bringing the final post-release total to **12 vision-capable providers (was 2)** — only `codex-cli` remains text-only because `codex exec --json --full-auto` has no image-input surface today. Production semantics: the flag controls only KodaX's SA-path artificial block; the actual model-level vision contract remains the upstream provider's responsibility — if a specific model alias is text-only, users now see the real API error from the provider instead of a KodaX-side `[Provider Policy] multimodal requests are unsupported` rejection. `capability-profile.test.ts` gains an explicit pin test asserting all 12 vision-capable providers report `multimodalSupport === 'image-input'` and `codex-cli` reports `'none'` so future regressions are caught.
34
+
35
+ ### Internal / architecture
36
+
37
+ - **Directory rename: `packages/ai/` → `packages/llm/` for package-name parity**. Closes the directory-vs-npm-name discrepancy left behind by FEATURE_147 v0.7.37, which renamed the npm package from `@kodax/ai` to `@kodax-ai/llm` but left the on-disk directory at `packages/ai/`. New contributors had to mentally translate "ai is actually llm"; prompt examples and tool-description hints saying "Audit packages/ai" pointed to a path that disagreed with the package name. Scope: `git mv packages/ai packages/llm` + 8 tsconfig path/reference updates (root + agent + coding + mcp + session-lineage) + 2 real relative imports in `tests/feature-116-active-cache-control.eval.ts` + ~30 prompt/comment/eval-fixture string updates (`worker-role-prompt.ts` / `role-prompt.ts` / `tools/registry.ts` / `tools/todo-list.test.ts` / `paste/persist-image.ts` / 5 `tests/*.eval.ts` files / 5 `benchmark/datasets/*` fixture files including `h2-plan-execute-boundary/cases.ts` path-prefix `mustNotTouchFiles` assertions). Frozen historical artifacts NOT touched: `benchmark/results/**` snapshots, `.agent/**` runner cache, `.repointel/**` index cache, `docs/CHANGELOG_ARCHIVE.md` + `docs/features/v0.7.0-39.md` historical design records, and `.claude/settings.local.json` user-private allowlist. Active operational docs (CHANGELOG, README, README_CN, DD.md, KNOWN_ISSUES.md, root CLAUDE.md, docs/CLAUDE.md) all updated. No behavior change; git rename detection preserves blame across the move. Pure naming-consistency refactor.
38
+ - **`@kodax/coding` blob summarizer module** (`packages/coding/src/tools/blob-summarizer.ts`, 187 lines). `createBlobSummarizer({provider, model, timeoutMs?})` returns a `SummarizeBlob` callback. Combines caller's `abortSignal` with a 30-second timeout via a fresh `AbortController` + listener fan-out. Throws `BlobSummarizerError` on empty input / empty output / provider error. System prompt + user prompt builder exported as constants (`SUMMARIZER_SYSTEM_PROMPT`, `buildSummarizerUserMessage`) so the Layer 2 eval can pin the EXACT production prompt text. 8 deterministic-shell unit tests cover the contract surface (timeout / abort / empty rejection / error wrapping / threshold constants).
39
+ - **`KodaXToolExecutionContext.summarizeBlob?` field** (`packages/coding/src/types.ts`). Optional callback shape `(content: string, options?: {readonly maxChars?: number; readonly abortSignal?: AbortSignal}) => Promise<string>`. Wired in `runner-driven.ts` baseCtx with lazy-once memoization bound to the Worker's own provider/model. The dispatch tool calls it via `ctx.summarizeBlob` without owning provider construction — preserves `@kodax/agent` layer independence (agent stays unaware of LLM client).
40
+ - **`GuardedToolResult.spillFailed?` flag** (`packages/coding/src/tools/tool-result-policy.ts`). Set when `persistToolOutput` throws and content was returned inline as the data-loss-guard fallback. Callers that need an LLM-summary follow-up (`dispatch-child-tasks` for `child_task_summary` >100KB) branch on this flag. The `console.warn` is intentionally NOT gated on `KODAX_DEBUG_TOOL_GUARDRAILS` — disk failure is a severe operational event an operator must see immediately.
41
+ - **`applyChildSummaryGuardrailWithSummarizer` helper in `dispatch-child-tasks.ts`**. Unifies the 4 dispatch call sites (async success / async crash / sync success / sync failure) through a single helper that chains: `applyToolResultGuardrail` → if `spillFailed` + content > 100KB + `ctx.summarizeBlob` exists, attempt LLM summarize → on success, banner-wrap with LOSSY marker → on summarizer failure, console.warn + fall back to inline full content with emergency banner. Symmetric error handling across all 4 sites; banner strings consistent.
42
+ - **3 new tracker entries marked Released**: FEATURE_121 (Envelope Spillover Gap-Fix), FEATURE_134 (Image / Screenshot Paste Input). Both originally `Planned` → now `v0.7.40 Released`. FEATURE_LIST.md `Current released version` bumped from v0.7.39 to v0.7.40.
43
+ - **Layer 2 eval datasets** at `benchmark/datasets/feature-121-envelope-spillover/` (3 cases: `preview_sufficient` / `detail_required` / `inline_no_spillover`) and `benchmark/datasets/feature-121-blob-summarizer/` (2 cases: `audit_report` / `grep_findings`). Eval drivers at `tests/feature-121-envelope-spillover.eval.ts` and `tests/feature-121-blob-summarizer.eval.ts` (gated on `KODAX_EVAL_F121_SUMMARIZER=1`). Raw outputs preserved per `EVAL_GUIDELINES.md` §"Raw output preservation" under `os.tmpdir()/kodax-eval-dumps/feature-121-*/`.
44
+
45
+ ### Test coverage delta
46
+
47
+ - New: 8 blob-summarizer unit tests + 25 blob-summarizer dataset shape tests + 27 envelope-spillover dataset shape tests + 9 tool-output-gc tests + 4 worker-role-prompt new tests (LARGE CHILD OUTPUT block + spill-path hint). Plus FEATURE_134's 52 new tests (45 foundation + 7 integration in `prompt-input-controller`).
48
+ - Total green at HEAD: **5745 tests pass** (507 test files, 1 skipped, 23 todo).
49
+
7
50
  ## [0.7.39] - 2026-05-12
8
51
 
9
52
  ### Theme
package/README.md CHANGED
@@ -81,6 +81,37 @@ If you need a custom base URL or an OpenAI/Anthropic-compatible endpoint, define
81
81
 
82
82
  `userAgentMode` defaults to `"compat"`, which sends `KodaX` instead of the official SDK User-Agent. Switch it to `"sdk"` only when your gateway expects the upstream SDK header.
83
83
 
84
+ #### Opting a custom provider into image / vision input (FEATURE_134 v0.7.40)
85
+
86
+ If your custom provider's underlying model supports image input (vision), add a `capabilityProfile.multimodalSupport: "image-input"` block so KodaX does not artificially block multimodal requests at the SA-path policy gate. The 12 built-in vision-capable providers (Anthropic, OpenAI, the 9 Anthropic-/OpenAI-compat clones — DeepSeek, Kimi, Kimi-code, Qwen, Zhipu, Zhipu-coding, MiniMax-coding, MiMo-coding, Ark-coding — plus Gemini-CLI via the CLI's `@<path>` file-include syntax) already ship with this flag enabled by default; only Codex-CLI and custom providers need to opt in.
87
+
88
+ ```json
89
+ {
90
+ "customProviders": [
91
+ {
92
+ "name": "my-vision-provider",
93
+ "protocol": "openai",
94
+ "baseUrl": "https://example.com/v1",
95
+ "apiKeyEnv": "MY_LLM_API_KEY",
96
+ "model": "my-vision-model",
97
+ "capabilityProfile": {
98
+ "transport": "native-api",
99
+ "conversationSemantics": "full-history",
100
+ "mcpSupport": "none",
101
+ "contextFidelity": "full",
102
+ "toolCallingFidelity": "full",
103
+ "sessionSupport": "full",
104
+ "longRunningSupport": "full",
105
+ "multimodalSupport": "image-input",
106
+ "evidenceSupport": "full"
107
+ }
108
+ }
109
+ ]
110
+ }
111
+ ```
112
+
113
+ The serializer layer (`packages/llm/src/providers/anthropic.ts:770` for Anthropic-compat, `openai.ts:904` for OpenAI-compat) forwards image blocks automatically through base-class inheritance. The flag only gates whether KodaX's policy layer pre-rejects multimodal requests — the model-level vision contract remains your upstream provider's responsibility. If the model is actually text-only, you'll see the real upstream API error instead of a KodaX-side rejection.
114
+
84
115
  ### 3. Start in REPL or run a one-shot task
85
116
 
86
117
  ```bash
@@ -674,6 +705,33 @@ kodax -h team # Multi-agent parallel execution
674
705
  kodax -h print # Print configuration
675
706
  ```
676
707
 
708
+ ### Environment Variables
709
+
710
+ KodaX recognizes a number of environment variables for tuning runtime behavior. The most commonly used ones are listed below; for the full list, search the repo for `process.env.KODAX_`.
711
+
712
+ #### `KODAX_MAX_OUTPUT_TOKENS`
713
+
714
+ Overrides the per-turn `max_tokens` value sent to **every** provider (Anthropic, OpenAI, Zhipu, Kimi, MiniMax, Qwen, DeepSeek, MiMo, Gemini, Codex, …). Set to a positive integer; unset or non-numeric values are ignored. This is an **explicit user intent**: when set, it wins over the provider's model descriptor cap, over the provider config default, and over the global `KODAX_MAX_TOKENS` fallback. The runtime's automatic safety caps (e.g. the v0.7.28 P2b RST-prone write-turn cap that limits write/edit turns to 8K tokens on Zhipu/Kimi/MiniMax) are **bypassed** when this variable is set, so the user override is also a way to opt out of those caps.
715
+
716
+ ```bash
717
+ # Allow up to 48K output tokens per turn (use a higher cap when generating long files)
718
+ export KODAX_MAX_OUTPUT_TOKENS=48000
719
+ kodax "generate the full implementation"
720
+
721
+ # Unset to restore default behavior
722
+ unset KODAX_MAX_OUTPUT_TOKENS
723
+ ```
724
+
725
+ Precedence used by every provider's `getEffectiveMaxOutputTokens()` (see `packages/llm/src/providers/base.ts`):
726
+
727
+ 1. One-shot per-request override (agent-loop escalation / context-overflow recovery — internal)
728
+ 2. **`KODAX_MAX_OUTPUT_TOKENS`** (this variable, explicit user intent)
729
+ 3. Active model descriptor's `maxOutputTokens` (FEATURE_098 per-model cap)
730
+ 4. Provider config default
731
+ 5. Global `KODAX_MAX_TOKENS` fallback
732
+
733
+ Related variables: `KODAX_MAX_TOKENS` (global fallback when no provider/model cap applies), `KODAX_RST_PRONE_PROVIDERS` and `KODAX_WRITE_TURN_MAX_TOKENS` (v0.7.28 P2b write-turn safety cap configuration), `KODAX_ESCALATED_MAX_OUTPUT_TOKENS` (escalation budget used by the agent loop when a turn returns `stop_reason: max_tokens`).
734
+
677
735
  ## Advanced Library Usage
678
736
 
679
737
  #### Simple Mode (runKodaX)
package/README_CN.md CHANGED
@@ -131,6 +131,37 @@ import { loadConfig } from '@kodax-ai/kodax/repl'; // REPL 配置 / session
131
131
 
132
132
  `userAgentMode` 默认 `"compat"`(发送 `KodaX` 而非上游 SDK 的 User-Agent);如果你的网关要求原生 SDK header,再切到 `"sdk"`。
133
133
 
134
+ #### 给自定义 provider 开图片 / vision 输入(FEATURE_134 v0.7.40)
135
+
136
+ 如果你的自定义 provider 后面的模型支持 vision,加 `capabilityProfile.multimodalSupport: "image-input"` 显式开启,KodaX 的 SA-path policy gate 就不会人为拦截多模态请求。内置的 12 个 vision-capable provider(Anthropic、OpenAI、9 个 Anthropic-/OpenAI-compat clone:DeepSeek / Kimi / Kimi-code / Qwen / Zhipu / Zhipu-coding / MiniMax-coding / MiMo-coding / Ark-coding,加 Gemini-CLI 通过 CLI 的 `@<path>` file-include 语法)已经默认开了这个 flag。只有 Codex-CLI 和自定义 provider 需要手动 opt-in。
137
+
138
+ ```json
139
+ {
140
+ "customProviders": [
141
+ {
142
+ "name": "my-vision-provider",
143
+ "protocol": "openai",
144
+ "baseUrl": "https://example.com/v1",
145
+ "apiKeyEnv": "MY_LLM_API_KEY",
146
+ "model": "my-vision-model",
147
+ "capabilityProfile": {
148
+ "transport": "native-api",
149
+ "conversationSemantics": "full-history",
150
+ "mcpSupport": "none",
151
+ "contextFidelity": "full",
152
+ "toolCallingFidelity": "full",
153
+ "sessionSupport": "full",
154
+ "longRunningSupport": "full",
155
+ "multimodalSupport": "image-input",
156
+ "evidenceSupport": "full"
157
+ }
158
+ }
159
+ ]
160
+ }
161
+ ```
162
+
163
+ 序列化层(Anthropic-compat 走 `packages/llm/src/providers/anthropic.ts:770`,OpenAI-compat 走 `openai.ts:904`)通过基类继承自动转发 image block。这个 flag 只控制 KodaX 自身是否预先拒绝多模态请求 —— 上游模型到底支不支持 vision 由 provider 自己决定。如果模型实际是 text-only,你会看到真实的上游 API 错误,而不是 KodaX 一侧的 `[Provider Policy] multimodal requests are unsupported` 预拦截。
164
+
134
165
  库模式下用 `registerCustomProviders()` 显式注册:
135
166
 
136
167
  ```typescript
@@ -0,0 +1,30 @@
1
+ // @kodax-ai/kodax — bundled distribution. See docs/ADR.md ADR-022 + ADR-024.
2
+ import{a as i}from"./chunk-V4WSBIXB.js";var H=class extends Error{static{i(this,"KodaXError")}code;constructor(e,n="KODAX_ERROR"){super(e),this.code=n,this.name="KodaXError"}},R=class extends H{static{i(this,"KodaXProviderError")}provider;constructor(e,n){super(e,"PROVIDER_ERROR"),this.provider=n,this.name="KodaXProviderError"}},de=class extends H{static{i(this,"KodaXRateLimitError")}retryAfter;constructor(e,n){super(e,"RATE_LIMIT_ERROR"),this.retryAfter=n,this.name="KodaXRateLimitError"}},Fe=class extends H{static{i(this,"KodaXNetworkError")}isTimeout;constructor(e,n=!1){super(e,"NETWORK_ERROR"),this.isTimeout=n,this.name="KodaXNetworkError"}},Ue=class extends H{static{i(this,"KodaXToolCallIdError")}constructor(e){super(e,"TOOL_CALL_ID_ERROR"),this.name="KodaXToolCallIdError"}};var Xe=32768,J=32e3,We=64e3,un=.5;var pn=["off","auto","quick","balanced","deep"],Rt={low:6e3,medium:1e4,high:2e4},At=4096;function $e(t){return t.reasoningCapability?t.reasoningCapability:t.supportsThinking?"native-toggle":"prompt-only"}i($e,"getReasoningCapability");function Q(t){return ke(t).enabled}i(Q,"isReasoningEnabled");function ke(t){if(typeof t=="boolean")return{enabled:t,mode:t?"auto":"off",depth:t?"medium":"off",taskType:"unknown",executionMode:"implementation"};let e=t?.mode??"off",n=t?.depth??St(e),o=t?.enabled??(e!=="off"&&n!=="off");return{enabled:o&&e!=="off"&&n!=="off",mode:e,depth:o?n:"off",taskType:t?.taskType??"unknown",executionMode:t?.executionMode??"implementation"}}i(ke,"normalizeReasoningRequest");function St(t){switch(t){case"quick":return"low";case"balanced":case"auto":return"medium";case"deep":return"high";default:return"off"}}i(St,"getDefaultThinkingDepthForMode");function q(t,e,n="unknown"){if(e==="off")return 0;let o={...Rt,...t.defaultThinkingBudgets??{}},s=t.taskBudgetOverrides?.[n]?.[e]??o[e];return t.thinkingBudgetCap?Math.min(s,t.thinkingBudgetCap):s}i(q,"resolveThinkingBudget");function Z(t,e,n=At){let o=Math.max(1024,e-n);return Math.max(1024,Math.min(t,o))}i(Z,"clampThinkingBudget");function je(t){switch(t){case"low":return"low";case"medium":return"medium";case"high":return"high";default:return}}i(je,"mapDepthToOpenAIReasoningEffort");import xe from"fs";import dn from"os";import He from"path";var Ge=null;function mn(t){return t==="budget"||t==="effort"||t==="toggle"||t==="none"}i(mn,"isReasoningOverride");function fn(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;let e=t.providerReasoningOverrides;return e===void 0?!0:!e||typeof e!="object"||Array.isArray(e)?!1:Object.values(e).every(mn)}i(fn,"isStoredConfig");function gn(){return process.env.KODAX_HOME??He.join(dn.homedir(),".kodax")}i(gn,"getKodaxDir");function Ot(){return process.env.KODAX_CONFIG_FILE??He.join(gn(),"config.json")}i(Ot,"getConfigFilePath");function ze(t,e){return Ge={filePath:t,config:e},e}i(ze,"updateStoredConfigCache");function hn(t){let e=JSON.parse(xe.readFileSync(t,"utf-8"));return fn(e)?e:{}}i(hn,"readStoredConfigFromDisk");function Ve(t){switch(t){case"native-budget":return"budget";case"native-effort":return"effort";case"native-toggle":return"toggle";case"none":return"none";default:return}}i(Ve,"reasoningCapabilityToOverride");function Ye(t){switch(t){case"budget":return"native-budget";case"effort":return"native-effort";case"toggle":return"native-toggle";default:return"none"}}i(Ye,"reasoningOverrideToCapability");function ee(t,e,n){return[t,e.baseUrl??"",n??e.model].join("|")}i(ee,"buildReasoningOverrideKey");function Je(){let t=Ot();if(Ge?.filePath===t)return Ge.config;try{if(xe.existsSync(t))return ze(t,hn(t))}catch{}return ze(t,{})}i(Je,"loadStoredConfig");function It(t){let e=Ot();try{xe.mkdirSync(He.dirname(e),{recursive:!0}),xe.writeFileSync(e,JSON.stringify(t,null,2)),ze(e,t)}catch(n){process.env.KODAX_DEBUG_OVERRIDES&&console.error("[ReasoningOverride] Failed to save config:",n)}}i(It,"saveStoredConfig");function be(t,e,n){let o=Je(),r=ee(t,e,n);return o.providerReasoningOverrides?.[r]}i(be,"loadReasoningOverride");function Qe(t,e,n,o){let r=Je(),s=ee(t,e,o);r.providerReasoningOverrides={...r.providerReasoningOverrides??{},[s]:n},It(r)}i(Qe,"saveReasoningOverride");function yn(t,e,n){let o=Je(),r=ee(t,e,n);if(!o.providerReasoningOverrides?.[r])return;let s={...o.providerReasoningOverrides};delete s[r],o.providerReasoningOverrides=Object.keys(s).length>0?s:void 0,It(o)}i(yn,"clearReasoningOverride");function Dt(t,e){if(!t)return;if(typeof t.get=="function")return t.get(e)??void 0;let n=t,o=e.toLowerCase(),r=n[e]??n[o]??n[o.replace(/\b\w/g,s=>s.toUpperCase())];if(r!==void 0)return Array.isArray(r)?r[0]:r}i(Dt,"readHeader");function Kt(t){if(!t)return;let e=t.trim();if(e.length===0)return;let n=Number(e);if(!(!Number.isFinite(n)||n<=0))return n}i(Kt,"parsePositiveNumber");function _n(t,e){if(!t)return;let n=t.trim();if(n.length===0)return;let o=Date.parse(n);if(!Number.isFinite(o))return;let r=o-e;return r>0?r:void 0}i(_n,"parseHttpDate");function ve(t,e){let n=e.baseBackoffMs??1e3,o=e.maxBackoffMs??3e4,r=e.maxHeaderWaitMs??12e4,s=e.now?e.now():Date.now(),a=Kt(Dt(t,"retry-after-ms"));if(a!==void 0){let _=Math.min(a,r);return{type:"header",waitMs:Math.round(_),source:"retry-after-ms",cappedFromHeader:_!==a}}let l=Dt(t,"retry-after");if(l!==void 0&&l.trim().length>0){let _=Kt(l);if(_!==void 0){let x=_*1e3,u=Math.min(x,r);return{type:"header",waitMs:Math.round(u),source:"retry-after-seconds",cappedFromHeader:u!==x}}let M=_n(l,s);if(M!==void 0){let x=Math.min(M,r);return{type:"header",waitMs:Math.round(x),source:"retry-after-date",cappedFromHeader:x!==M}}}let c=n*Math.pow(2,Math.max(0,e.attempt)),m=Math.min(c,o),f=e.withJitter!==!1?Math.random()*.25*m:0;return{type:"backoff",waitMs:Math.round(m+f),source:"exponential-backoff",attempt:e.attempt}}i(ve,"parseRetryAfter");function Me(t){if(!t||typeof t!="object")return;let e=t;return e.headers??e.response?.headers??e.cause?.headers??e.cause?.response?.headers}i(Me,"extractHeadersFromError");var me={transport:"native-api",conversationSemantics:"full-history",mcpSupport:"none",contextFidelity:"full",toolCallingFidelity:"full",sessionSupport:"full",longRunningSupport:"full",multimodalSupport:"none",evidenceSupport:"full"},L={...me,multimodalSupport:"image-input"},fe={transport:"cli-bridge",conversationSemantics:"last-user-message",mcpSupport:"none",contextFidelity:"lossy",toolCallingFidelity:"limited",sessionSupport:"stateless",longRunningSupport:"limited",multimodalSupport:"none",evidenceSupport:"limited"},Lt={...fe,multimodalSupport:"image-input"};function Ce(t){return{transport:t.transport,conversationSemantics:t.conversationSemantics,mcpSupport:t.mcpSupport,contextFidelity:t.contextFidelity??"full",toolCallingFidelity:t.toolCallingFidelity??"full",sessionSupport:t.sessionSupport??"full",longRunningSupport:t.longRunningSupport??"full",multimodalSupport:t.multimodalSupport??"none",evidenceSupport:t.evidenceSupport??"full"}}i(Ce,"normalizeCapabilityProfile");function F(t){return{...Ce(t)}}i(F,"cloneCapabilityProfile");function kn(t){if(!t)return;let e=parseInt(t,10);return Number.isFinite(e)&&e>0?e:void 0}i(kn,"parseEnvInt");var G=class{static{i(this,"KodaXBaseProvider")}maxOutputTokensOverride;setMaxOutputTokensOverride(e){this.maxOutputTokensOverride=e}getEffectiveMaxOutputTokens(e){if(this.maxOutputTokensOverride!==void 0)return this.maxOutputTokensOverride;let n=kn(process.env.KODAX_MAX_OUTPUT_TOKENS);if(n!==void 0)return n;let o=this.getModelDescriptor(e)?.maxOutputTokens;return o!==void 0?o:this.config.maxOutputTokens??32768}getStreamMaxDurationMs(){return this.config.streamMaxDurationMs}supportsNonStreamingFallback(){return!1}async complete(e,n,o,r,s,a){throw new R(`${this.name} does not support non-streaming fallback`)}isConfigured(){return!!process.env[this.config.apiKeyEnv]}getModel(){return this.config.model}getAvailableModels(){return this.config.models?.length?[...new Set([this.config.model,...this.config.models.map(e=>e.id)])]:[this.config.model]}getModelDescriptor(e){return!e||e===this.config.model?{id:this.config.model}:this.config.models?.find(n=>n.id===e)}getBaseUrl(){return this.config.baseUrl}getApiKeyEnv(){return this.config.apiKeyEnv}getCapabilityProfile(){return F(this.config.capabilityProfile??me)}getConfiguredReasoningCapability(e){let n=this.getModelDescriptor(e);return n?.reasoningCapability?n.reasoningCapability:$e(this.config)}getReasoningCapability(e){let n=be(this.name,this.config,e);return n?Ye(n):this.getConfiguredReasoningCapability(e)}getReasoningOverride(e){return be(this.name,this.config,e)}getReasoningOverrideKey(e){return ee(this.name,this.config,e)}persistReasoningCapabilityOverride(e,n){let o=Ve(e);o&&Qe(this.name,this.config,o,n)}shouldFallbackForReasoningError(e,...n){let o=e instanceof Error?e.message.toLowerCase():String(e).toLowerCase(),s=n.map(l=>l.toLowerCase()).some(l=>o.includes(l)),a=o.includes("parameter")||s;return o.includes("unknown parameter")||o.includes("invalid parameter")||o.includes("unsupported")&&a}shouldFallbackForSpecificReasoningError(e,...n){let o=e instanceof Error?e.message.toLowerCase():String(e).toLowerCase();return n.map(a=>a.toLowerCase()).some(a=>o.includes(a))?o.includes("unknown parameter")||o.includes("invalid parameter")||o.includes("unsupported"):!1}getReasoningFallbackChain(e){switch(e){case"native-budget":return["native-budget","native-toggle","none"];case"native-effort":return["native-effort","none"];case"native-toggle":return["native-toggle","none"];default:return["none"]}}getContextWindow(){return this.getEffectiveContextWindow()}getEffectiveContextWindow(e){let n=this.getModelDescriptor(e)?.contextWindow;return n!==void 0?n:this.config.contextWindow??2e5}getApiKey(){let e=process.env[this.config.apiKeyEnv];if(!e)throw new Error(`${this.config.apiKeyEnv} not set`);return e}shouldLogStreamDiagnostics(){return!!process.env.KODAX_DEBUG_STREAM}logStreamDiagnostic(...e){this.shouldLogStreamDiagnostics()&&console.error(...e)}normalizeReasoning(e){return ke(e)}onStaleConnection(){}isRateLimitError(e){if(!(e instanceof Error))return!1;let n=e.message.toLowerCase();return["rate","limit","\u901F\u7387","\u9891\u7387","1302","429","too many","overload","overwhelmed","503","529","busy"].some(o=>n.includes(o))}classifyRateLimitReason(e){let n=e instanceof Error?e.message.toLowerCase():String(e).toLowerCase();return n.includes("overload")||n.includes("overwhelmed")||n.includes("503")||n.includes("529")||n.includes("busy")?"overloaded":"rate-limit"}extractRetryAfterMs(e){let n=Me(e);if(!n)return;let o=ve(n,{attempt:0,withJitter:!1});return o.type==="header"?o.waitMs:void 0}parseContextOverflow(e){let n=String(e?.message??""),o=[/(\d[\d,]*)\s*tokens?.*?(\d[\d,]*)\s*(?:maximum|limit|context)/i,/maximum.*?(\d[\d,]*)\s*tokens?.*?requested.*?(\d[\d,]*)/i,/exceeds?\s+.*?(\d[\d,]*)\s*.*?(?:limit|max|上限).*?(\d[\d,]*)/i];for(let r of o){let s=n.match(r);if(s){let a=Number(s[1].replace(/,/g,"")),l=Number(s[2].replace(/,/g,"")),c=Math.min(a,l),m=Math.max(a,l);return Math.max(3e3,m-c-1e3)}}}isContextOverflowError(e){let n=String(e?.message??"").toLowerCase();return n.includes("prompt is too long")||n.includes("prompt too long")||n.includes("context length")||n.includes("context_length_exceeded")||n.includes("context window")||n.includes("\u4E0A\u4E0B\u6587\u957F\u5EA6")}async withRateLimit(e,n,o=3,r,s){for(let a=0;a<o;a++)try{let l=await e();return this.maxOutputTokensOverride=void 0,l}catch(l){if(this.isContextOverflowError(l)&&!this.maxOutputTokensOverride){let c=this.parseContextOverflow(l);if(c){this.maxOutputTokensOverride=c,r?.(a+1,o,0);continue}}if(this.isRateLimitError(l)){if(a===o-1)throw new de(`API rate limit exceeded after ${o} retries. Please wait and try again later.`,6e4);let c=Me(l)??{},m=ve(c,{attempt:a,baseBackoffMs:500,maxBackoffMs:32e3,withJitter:!0}),v=m.waitMs,f=this.classifyRateLimitReason(l);if(s?.({provider:this.name,waitMs:v,reason:f,source:m.source,attempt:a+1,maxAttempts:o}),r?r(a+1,o,v):s||console.log(`[Rate Limit] Retrying in ${v/1e3}s (${a+1}/${o})...`),n?.aborted)throw new DOMException("Request aborted","AbortError");if(await new Promise(_=>setTimeout(_,v)),n?.aborted)throw new DOMException("Request aborted","AbortError");continue}if(l instanceof Error){if((l.name==="AbortError"||l.name==="APIUserAbortError")&&n?.aborted)throw l.name==="AbortError"?l:new DOMException(l.message||"Request aborted","AbortError");let c=l?.cause?.code??l?.code??"";throw(c==="ECONNRESET"||c==="EPIPE")&&this.onStaleConnection(),new R(`${this.name} API error: ${l.message}`,this.name)}throw l}throw new H("Unexpected end of withRateLimit")}};import Cn from"@anthropic-ai/sdk";import{parse as xn}from"partial-json";function te(t){if(!t)return{};try{let e=JSON.parse(t);return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}catch{}try{let e=xn(t);return process.env.KODAX_DEBUG_TOOL_STREAM&&console.warn("[Tool Block Salvaged] partial JSON recovered, rawLength=",t.length),e&&typeof e=="object"&&!Array.isArray(e)?e:{}}catch{return{}}}i(te,"parseToolInputWithSalvage");function qe(t,e){let n=t[t.length-1];if(n&&ne(n)&&n.hint===e)return t;let o=e?{type:"cache-boundary",hint:e}:{type:"cache-boundary"};return[...t,o]}i(qe,"insertCacheBoundary");function ne(t){if(typeof t!="object"||t===null||t.type!=="cache-boundary")return!1;let e=Object.keys(t);return e.length<=2&&e.every(n=>n==="type"||n==="hint")}i(ne,"isCacheBoundary");function Ze(t,e){let n=[];for(let o of t){if(ne(o)){if(e==="attach"&&n.length>0){let r=n[n.length-1];n[n.length-1]={...r,cache_control:{type:"ephemeral"}}}continue}n.push(o)}return n}i(Ze,"lowerCacheBoundaries");function ge(t){return t.filter(e=>!ne(e))}i(ge,"stripCacheBoundaries");import{readFile as bn}from"node:fs/promises";import vn from"node:path";var Mn={".gif":"image/gif",".jpeg":"image/jpeg",".jpg":"image/jpeg",".png":"image/png",".webp":"image/webp"};function et(t,e){return e??Mn[vn.extname(t).toLowerCase()]??"image/png"}i(et,"resolveImageMediaType");async function tt(t){return(await bn(t)).toString("base64")}i(tt,"readImageFileAsBase64");async function Nt(t,e){let n=et(t,e),o=await tt(t);return`data:${n};base64,${o}`}i(Nt,"buildImageDataUrl");var Tn="KodaX";function En(t){return t.userAgentMode==="sdk"?void 0:{"User-Agent":Tn}}i(En,"getAnthropicCompatDefaultHeaders");function nt(t,e){if(!t)return e;let n=t.input_tokens!==void 0&&t.input_tokens!==null||t.cache_creation_input_tokens!==void 0&&t.cache_creation_input_tokens!==null||t.cache_read_input_tokens!==void 0&&t.cache_read_input_tokens!==null,o=typeof t.input_tokens=="number"?t.input_tokens:n?0:e?.inputTokens??0,r=typeof t.cache_creation_input_tokens=="number"?t.cache_creation_input_tokens:n?0:e?.cachedWriteTokens??0,s=typeof t.cache_read_input_tokens=="number"?t.cache_read_input_tokens:n?0:e?.cachedReadTokens??0,a=typeof t.output_tokens=="number"?t.output_tokens:e?.outputTokens??0,l=n?o+r+s:e?.inputTokens??0;if(![l,a].some(c=>!Number.isFinite(c)||c<0))return{inputTokens:l,outputTokens:a,totalTokens:l+a,cachedReadTokens:s||void 0,cachedWriteTokens:r||void 0}}i(nt,"normalizeAnthropicUsage");var K=class extends G{static{i(this,"KodaXAnthropicCompatProvider")}supportsThinking=!0;client;initClient(){let e=En(this.config);this.client=new Cn({apiKey:this.getApiKey(),baseURL:this.config.baseUrl,...e?{defaultHeaders:e}:{}})}onStaleConnection(){this.initClient()}applyCacheControlToSystem(e){if(!e.trim()||process.env.KODAX_DISABLE_PROMPT_CACHE==="1")return e;let n=qe([{type:"text",text:e}],"system");return Ze(n,"attach")}applyCacheControlToTools(e){if(e.length===0||process.env.KODAX_DISABLE_PROMPT_CACHE==="1")return e;let n=e.slice(),o=n[n.length-1];return n[n.length-1]={...o,cache_control:{type:"ephemeral"}},n}async stream(e,n,o,r,s,a){return this.withRateLimit(async()=>{let l=this.normalizeReasoning(r),c=s?.modelOverride??this.config.model,m=this.getEffectiveMaxOutputTokens(c),v=await this.convertMessages(e),f=l.enabled?this.getReasoningCapability(c):"none",_=l.enabled?this.getReasoningFallbackChain(f).filter(k=>k==="native-budget"||k==="native-toggle"||k==="none"):["none"],M=i(k=>{let P={model:c,max_tokens:m,system:this.applyCacheControlToSystem(this.buildSystemPrompt(o,e)),messages:v,tools:this.applyCacheControlToTools(n),stream:!0};if(k==="native-budget"){let S=q(this.config,l.depth,l.taskType);P.thinking={type:"enabled",budget_tokens:Z(S,m)}}else k==="native-toggle"&&(P.thinking={type:"enabled"});return P},"buildRequest");if(a?.aborted)throw new DOMException("Request aborted","AbortError");let x=[],u=[],y=[],g,b=null,p="",d="",T="",w="",W="",O="",$="",E=!1,j,A=Date.now(),_e=Date.now(),le,ue;for(let k of _)try{le=await this.client.messages.create(M(k),a?{signal:a}:{}),k!==f&&this.persistReasoningCapabilityOverride(k,c);break}catch(P){ue=P;let S=k==="native-budget"?["budget_tokens","thinking"]:k==="native-toggle"?["thinking"]:[];if(!this.shouldFallbackForReasoningError(P,...S))throw P}if(!le)throw ue??new R("All reasoning capability attempts failed without a captured error",this.name);let pe=Date.now(),C=0,I=0,D=3e4;for await(let k of le){if(a?.aborted)throw new DOMException("Request aborted","AbortError");let P=Date.now(),S=P-pe;if(S>D&&(C++,I+=S,this.logStreamDiagnostic(`[Stream] stall detected: ${Math.round(S/1e3)}s gap before ${k.type}`,{stallCount:C,totalStallMs:I,eventType:k.type})),pe=P,k.type==="content_block_start"||k.type==="content_block_stop"?s?.onHeartbeat?.(!0):s?.onHeartbeat?.(),k.type==="content_block_start"){A=Date.now();let h=k.content_block;b=h.type,process.env.KODAX_DEBUG_TOOL_STREAM&&h.type==="tool_use"&&console.error("[ToolStream] content_block_start:",{type:h.type,id:h.id,name:h.name}),h.type==="thinking"?(d="",T=h.signature??""):h.type==="redacted_thinking"?(b="redacted_thinking",w=h.data??""):h.type==="text"?p="":h.type==="tool_use"&&(W=h.id,O=h.name,$="")}else if(k.type==="content_block_delta"){A=Date.now();let h=k.delta;h.type==="thinking_delta"?(d+=h.thinking??"",s?.onThinkingDelta?.(h.thinking??"")):h.type==="text_delta"?(p+=h.text??"",s?.onTextDelta?.(h.text??"")):h.type==="input_json_delta"&&($+=h.partial_json??"",s?.onToolInputDelta?.(O,h.partial_json??"",W?{toolId:W}:void 0))}else if(k.type==="content_block_stop")A=Date.now(),b==="thinking"?d&&(y.push({type:"thinking",thinking:d,signature:T}),s?.onThinkingEnd?.(d)):b==="redacted_thinking"?(w&&y.push({type:"redacted_thinking",data:w}),w=""):b==="text"?p&&x.push({type:"text",text:p}):b==="tool_use"&&(!W||!O?console.error("[Tool Block Invalid] Missing tool id or name:",{id:JSON.stringify(W),name:JSON.stringify(O),input:$.slice(0,100)}):u.push({type:"tool_use",id:W,name:O,input:te($)})),b=null;else if(k.type==="message_stop"){if(E=!0,A=Date.now(),process.env.KODAX_DEBUG_STREAM){let h=Date.now()-_e;this.logStreamDiagnostic(`[Stream] message_stop received after ${h}ms`)}}else if(k.type==="message_delta"){A=Date.now(),g=nt(k.usage,g);let h=k.delta;h?.stop_reason&&(j=h.stop_reason,process.env.KODAX_DEBUG_STREAM&&this.logStreamDiagnostic(`[Stream] message_delta with stop_reason: ${j}`))}else k.type==="message_start"&&(A=Date.now(),g=nt(k.message?.usage,g),process.env.KODAX_DEBUG_STREAM&&this.logStreamDiagnostic("[Stream] message_start received"))}if(!E){let k=Date.now()-_e,P=Date.now()-A;if(a?.aborted){let h=a.reason instanceof Error?a.reason.message:typeof a.reason=="string"?a.reason:"Request aborted";throw this.logStreamDiagnostic("[Stream] Stream ended after abort before message_stop:",{duration:k,lastEventAge:P,reason:h,textBlocks:x.length,toolBlocks:u.length,thinkingBlocks:y.length}),new DOMException(h,"AbortError")}let S=new Error(`Stream incomplete: message_stop event not received. Duration: ${k}ms, Last event: ${P}ms ago. This may indicate a network disconnection or API timeout.`);throw S.name="StreamIncompleteError",this.logStreamDiagnostic("[Stream] Incomplete stream detected:",{duration:k,lastEventAge:P,textBlocks:x.length,toolBlocks:u.length,thinkingBlocks:y.length}),S}return{textBlocks:x,toolBlocks:u,thinkingBlocks:y,usage:g,stopReason:j}},a,3,s?.onRateLimit,s?.onRetryAfter)}supportsNonStreamingFallback(){return!0}async complete(e,n,o,r,s,a){return this.withRateLimit(async()=>{let l=this.normalizeReasoning(r),c=s?.modelOverride??this.config.model,m=this.getEffectiveMaxOutputTokens(c),v=await this.convertMessages(e),f=l.enabled?this.getReasoningCapability(c):"none",_=l.enabled?this.getReasoningFallbackChain(f).filter(p=>p==="native-budget"||p==="native-toggle"||p==="none"):["none"],M=i(p=>{let d={model:c,max_tokens:m,system:this.applyCacheControlToSystem(this.buildSystemPrompt(o,e)),messages:v,tools:this.applyCacheControlToTools(n)};if(p==="native-budget"){let T=q(this.config,l.depth,l.taskType);d.thinking={type:"enabled",budget_tokens:Z(T,m)}}else p==="native-toggle"&&(d.thinking={type:"enabled"});return d},"buildRequest"),x,u;for(let p of _)try{x=await this.client.messages.create(M(p),a?{signal:a}:{}),p!==f&&this.persistReasoningCapabilityOverride(p,c);break}catch(d){u=d;let T=p==="native-budget"?["budget_tokens","thinking"]:p==="native-toggle"?["thinking"]:[];if(!this.shouldFallbackForReasoningError(d,...T))throw d}if(!x)throw u??new R("All reasoning capability attempts failed without a captured error",this.name);let y=[],g=[],b=[];for(let p of x.content)p.type==="text"?(y.push({type:"text",text:p.text}),s?.onTextDelta?.(p.text)):p.type==="thinking"?(b.push({type:"thinking",thinking:p.thinking,signature:p.signature??""}),s?.onThinkingDelta?.(p.thinking),s?.onThinkingEnd?.(p.thinking)):p.type==="redacted_thinking"?b.push({type:"redacted_thinking",data:p.data}):p.type==="tool_use"&&g.push({type:"tool_use",id:p.id,name:p.name,input:typeof p.input=="object"&&p.input!==null?p.input:{}});return{textBlocks:y,toolBlocks:g,thinkingBlocks:b,usage:nt(x.usage),stopReason:x.stop_reason??void 0}},a,3,s?.onRateLimit,s?.onRetryAfter)}serializeSystemMessageContent(e){if(typeof e=="string")return e.trim();for(let n of e)if(ne(n))throw new R("cache-boundary marker reached system message serialization unlowered. Provider base class lowering must run before any wire-level serialization.",this.name);return e.filter(n=>n.type==="text").map(n=>n.text.trim()).filter(Boolean).join(`
3
+ `)}buildSystemPrompt(e,n){let o=n.filter(r=>r.role==="system").map(r=>this.serializeSystemMessageContent(r.content)).filter(Boolean);return[e,...o].filter(r=>typeof r=="string"&&r.trim().length>0).join(`
4
+
5
+ `)}async convertMessages(e){let n=[];for(let o of e.filter(r=>r.role!=="system")){let r=o.role==="user"?"user":"assistant";if(typeof o.content=="string"){n.push({role:r,content:o.content});continue}let s=[],a=[];for(let c of o.content)c.type==="thinking"?!this.config.strictThinkingSignature||typeof c.signature=="string"&&c.signature.length>0?s.push({type:"thinking",thinking:c.thinking,signature:c.signature??""}):c.thinking&&a.push(c.thinking):c.type==="redacted_thinking"&&(this.config.strictThinkingSignature||s.push({type:"redacted_thinking",data:c.data}));a.length>0&&o.role==="assistant"&&s.push({type:"text",text:`<prior_reasoning>
6
+ ${a.join(`
7
+
8
+ `)}
9
+ </prior_reasoning>`});for(let c of o.content)c.type==="tool_result"&&o.role==="user"&&s.push({type:"tool_result",tool_use_id:c.tool_use_id,content:c.content,...c.is_error===!0?{is_error:!0}:{}});for(let c of o.content)c.type==="tool_use"&&o.role==="assistant"&&s.push({type:"tool_use",id:c.id,name:c.name,input:c.input});for(let c of o.content)c.type==="text"?s.push({type:"text",text:c.text}):c.type==="image"&&o.role==="user"&&s.push({type:"image",source:{type:"base64",media_type:et(c.path,c.mediaType),data:await tt(c.path)}});if(r==="assistant"&&this.config.supportsThinking&&!this.config.strictThinkingSignature){let c=s.some(v=>v.type==="tool_use"),m=s.some(v=>v.type==="thinking"||v.type==="redacted_thinking");c&&!m&&s.unshift({type:"thinking",thinking:"...",signature:""})}let l=s.length===0||r==="assistant"&&s.every(c=>{let m=c;return m.type==="thinking"&&!m.thinking||m.type==="text"&&!m.text});n.push({role:o.role,content:l?[{type:"text",text:"..."}]:s})}return n}};import Pn from"openai";var wn="KodaX";function Rn(t){return t.userAgentMode==="sdk"?void 0:{"User-Agent":wn}}i(Rn,"getOpenAICompatDefaultHeaders");function Bt(t){if(!t)return;let e=typeof t.prompt_tokens=="number"?t.prompt_tokens:0,n=typeof t.completion_tokens=="number"?t.completion_tokens:0,o=typeof t.total_tokens=="number"?t.total_tokens:e+n;if([e,n,o].some(s=>!Number.isFinite(s)||s<0)||o<e||o<n)return;let r=typeof t.prompt_tokens_details?.cached_tokens=="number"&&t.prompt_tokens_details.cached_tokens>=0?t.prompt_tokens_details.cached_tokens:typeof t.prompt_cache_hit_tokens=="number"&&t.prompt_cache_hit_tokens>=0?t.prompt_cache_hit_tokens:void 0;return{inputTokens:e,outputTokens:n,totalTokens:o,...r!==void 0?{cachedReadTokens:r}:{}}}i(Bt,"normalizeOpenAIUsage");function An(t){return t?t.type==="function"&&"function"in t:!1}i(An,"isOpenAIFunctionToolCall");function Sn(t){return typeof t=="string"?t:Array.isArray(t)?t.map(e=>{if(!e||typeof e!="object")return"";let n=Reflect.get(e,"text");if(typeof n=="string")return n;if(n&&typeof n=="object"){let o=Reflect.get(n,"value");return typeof o=="string"?o:""}return""}).filter(Boolean).join(""):""}i(Sn,"extractOpenAIMessageText");function On(t){if(!t||typeof t!="object")return"";let e=Reflect.get(t,"reasoning_content");return typeof e=="string"?e:Array.isArray(e)?e.map(n=>typeof n=="string"?n:n&&typeof n=="object"&&"text"in n&&typeof n.text=="string"?n.text:"").join(""):""}i(On,"extractOpenAIMessageReasoning");var N=class extends G{static{i(this,"KodaXOpenAICompatProvider")}supportsThinking=!0;client;initClient(){let e=Rn(this.config);this.client=new Pn({apiKey:this.getApiKey(),baseURL:this.config.baseUrl,...e?{defaultHeaders:e}:{}})}onStaleConnection(){this.initClient()}stripCacheBoundariesFromMessages(e){return e.map(n=>{if(typeof n.content=="string")return n;let o=ge(n.content);return o.length===n.content.length?n:{...n,content:o}})}normalizeSystemForWire(e,n){let o=[];e&&e.trim().length>0&&o.push(e);let r=[];for(let s of n){if(s.role!=="system"){r.push(s);continue}let a=typeof s.content=="string"?s.content:Array.isArray(s.content)?s.content.filter(l=>typeof l=="object"&&l!==null&&l.type==="text"&&typeof l.text=="string").map(l=>l.text).join(`
10
+ `):"";a.trim().length>0&&o.push(a)}return{system:o.join(`
11
+
12
+ `),rest:r}}appendExtraBody(e,n){let o=typeof e.extra_body=="object"&&e.extra_body!==null?e.extra_body:{};e.extra_body={...o,...n}}resetReasoningCapabilityParams(e){delete e.reasoning_effort,delete e.thinking;let n=typeof e.extra_body=="object"&&e.extra_body!==null?{...e.extra_body}:void 0;if(n){if(delete n.enable_thinking,delete n.thinking_budget,delete n.thinking,Object.keys(n).length===0){delete e.extra_body;return}e.extra_body=n}}applyReasoningCapability(e,n,o){let r=e,s=this.getEffectiveMaxOutputTokens(e.model),a=Z(q(this.config,o.depth,o.taskType),s);switch(n){case"native-effort":{let l=je(o.depth);l&&(r.reasoning_effort=l);break}case"native-budget":{this.name==="qwen"?this.appendExtraBody(r,{enable_thinking:!0,thinking_budget:a}):this.name==="zhipu"&&(r.thinking={type:"enabled",budget_tokens:a});break}case"native-toggle":{this.name==="qwen"?this.appendExtraBody(r,{enable_thinking:!0}):this.name==="zhipu"&&(r.thinking={type:"enabled"});break}default:break}}getFallbackTerms(e){switch(e){case"native-budget":return["thinking_budget","budget_tokens","thinking"];case"native-effort":return["reasoning_effort"];case"native-toggle":return["enable_thinking","thinking"];default:return[]}}async stream(e,n,o,r,s,a){return this.withRateLimit(async()=>{let l=this.stripCacheBoundariesFromMessages(e),{system:c,rest:m}=this.normalizeSystemForWire(o,l),v=[{role:"system",content:c},...await this.convertMessages(m)],f=n.map(C=>({type:"function",function:{name:C.name,description:C.description,parameters:C.input_schema}}));if(a?.aborted)throw new DOMException("Request aborted","AbortError");let _=new Map,M="",x="",u,y=!0,g=null,b=Date.now(),p=this.normalizeReasoning(r),d=s?.modelOverride??this.config.model,T=Q(p)?this.getReasoningCapability(d):"none",w=Q(p)?this.getReasoningFallbackChain(T).filter(C=>C==="native-budget"||C==="native-effort"||C==="native-toggle"||C==="none"):["none"],W={model:d,messages:v,tools:f,max_completion_tokens:this.getEffectiveMaxOutputTokens(d),stream:!0},O,$;for(let C of w){for(;!O;){let I={...W};y&&(I.stream_options={include_usage:!0}),this.resetReasoningCapabilityParams(I),this.applyReasoningCapability(I,C,p);try{O=await this.client.chat.completions.create(I,a?{signal:a}:{}),C!==T&&this.persistReasoningCapabilityOverride(C,d)}catch(D){if($=D,y&&this.shouldFallbackForSpecificReasoningError(D,"stream_options","include_usage")){y=!1;continue}if(!this.shouldFallbackForReasoningError(D,...this.getFallbackTerms(C)))throw D;break}}if(O)break}if(!O)throw $??new R("All reasoning capability attempts failed without a captured error",this.name);let E=Date.now(),j=0,A=0,_e=3e4;for await(let C of O){if(a?.aborted)throw new DOMException("Request aborted","AbortError");let I=Date.now(),D=I-E;D>_e&&(j++,A+=D,this.logStreamDiagnostic(`[Stream] stall detected: ${Math.round(D/1e3)}s gap`,{stallCount:j,totalStallMs:A})),E=I,s?.onHeartbeat?.(),u=Bt(C.usage)??u;let k=C.choices[0],P=k?.delta;if(k?.finish_reason&&(g=k.finish_reason,process.env.KODAX_DEBUG_STREAM)){let h=Date.now()-b;this.logStreamDiagnostic(`[Stream] finish_reason: ${g} after ${h}ms`)}P?.content&&(M+=P.content,s?.onTextDelta?.(P.content));let S=this.extractReasoningDelta(P);if(S&&(x+=S,s?.onThinkingDelta?.(S)),P?.tool_calls)for(let h of P.tool_calls){let Y=_.get(h.index)??{id:"",name:"",arguments:""};h.id&&(Y.id=h.id),h.function?.name&&(Y.name=h.function.name),h.function?.arguments&&(Y.arguments+=h.function.arguments,s?.onToolInputDelta?.(Y.name,h.function.arguments,Y.id?{toolId:Y.id}:void 0)),_.set(h.index,Y)}}if(!g){let C=Date.now()-b;if(a?.aborted){let D=a.reason instanceof Error?a.reason.message:typeof a.reason=="string"?a.reason:"Request aborted";throw this.logStreamDiagnostic("[Stream] Stream ended after abort before finish_reason:",{duration:C,reason:D,textContentLength:M.length,toolCallsCount:_.size}),new DOMException(D,"AbortError")}let I=new Error(`Stream incomplete: finish_reason not received. Duration: ${C}ms. This may indicate a network disconnection or API timeout.`);throw I.name="StreamIncompleteError",this.logStreamDiagnostic("[Stream] Incomplete stream detected:",{duration:C,textContentLength:M.length,toolCallsCount:_.size}),I}let le=M?[{type:"text",text:M}]:[],ue=[],pe=[];x&&(pe.push({type:"thinking",thinking:x}),s?.onThinkingEnd?.(x));for(let[,C]of _)C.id&&C.name&&ue.push({type:"tool_use",id:C.id,name:C.name,input:te(C.arguments)});return{textBlocks:le,toolBlocks:ue,thinkingBlocks:pe,usage:u,stopReason:g??void 0}},a,3,s?.onRateLimit,s?.onRetryAfter)}supportsNonStreamingFallback(){return!0}async complete(e,n,o,r,s,a){return this.withRateLimit(async()=>{let l=this.stripCacheBoundariesFromMessages(e),{system:c,rest:m}=this.normalizeSystemForWire(o,l),v=[{role:"system",content:c},...await this.convertMessages(m)],f=n.map(E=>({type:"function",function:{name:E.name,description:E.description,parameters:E.input_schema}})),_=this.normalizeReasoning(r),M=s?.modelOverride??this.config.model,x=Q(_)?this.getReasoningCapability(M):"none",u=Q(_)?this.getReasoningFallbackChain(x).filter(E=>E==="native-budget"||E==="native-effort"||E==="native-toggle"||E==="none"):["none"],y={model:M,messages:v,tools:f,max_completion_tokens:this.getEffectiveMaxOutputTokens(M)},g,b;for(let E of u){let j={...y};this.resetReasoningCapabilityParams(j),this.applyReasoningCapability(j,E,_);try{g=await this.client.chat.completions.create(j,a?{signal:a}:{}),E!==x&&this.persistReasoningCapabilityOverride(E,M);break}catch(A){if(b=A,!this.shouldFallbackForReasoningError(A,...this.getFallbackTerms(E)))throw A}}if(!g)throw b??new R("All reasoning capability attempts failed without a captured error",this.name);let p=g.choices[0],d=p?.message,T=Sn(d?.content),w=On(d),W=(d?.tool_calls??[]).filter(An).map(E=>({type:"tool_use",id:E.id,name:E.function.name,input:te(E.function.arguments)}));T&&s?.onTextDelta?.(T);let O=T?[{type:"text",text:T}]:[],$=[];return w&&($.push({type:"thinking",thinking:w}),s?.onThinkingDelta?.(w),s?.onThinkingEnd?.(w)),{textBlocks:O,toolBlocks:W,thinkingBlocks:$,usage:Bt(g.usage),stopReason:p?.finish_reason??void 0}},a,3,s?.onRateLimit,s?.onRetryAfter)}extractReasoningDelta(e){let n=e?.reasoning_content;return typeof n=="string"?n:Array.isArray(n)?n.map(o=>typeof o=="string"?o:typeof o=="object"&&o!==null&&"text"in o&&typeof o.text=="string"?o.text:"").join(""):""}serializeAssistantMessage(e){let n=e.filter(c=>c.type==="text").map(c=>c.text).join(`
13
+ `),o=e.filter(c=>c.type==="tool_use").map(c=>({id:c.id,type:"function",function:{name:c.name,arguments:JSON.stringify(c.input??{})}})),r=e.filter(c=>c.type==="thinking"||c.type==="redacted_thinking").map(c=>c.type==="thinking"?c.thinking:"").filter(Boolean).join(`
14
+
15
+ `),s=e.some(c=>c.type==="thinking"||c.type==="redacted_thinking");if(!n&&o.length===0&&!s)return[];let a;n?a=n:o.length>0?a=null:a="...";let l={role:"assistant",content:a};return o.length>0&&(l.tool_calls=o),this.config.replayReasoningContent&&(l.reasoning_content=r||""),[l]}async serializeUserMessage(e){let n=[],o=e.filter(a=>a.type==="text").map(a=>a.text).join(`
16
+ `),r=e.filter(a=>a.type==="image");for(let a of e)a.type==="tool_result"&&n.push({role:"tool",tool_call_id:a.tool_use_id,content:a.content});if(r.length===0)return o&&n.push({role:"user",content:o}),n;let s=[];o&&s.push({type:"text",text:o});for(let a of r)s.push({type:"image_url",image_url:{url:await Nt(a.path,a.mediaType)}});return n.push({role:"user",content:s}),n}serializeSystemMessage(e){if(typeof e=="string")return[{role:"system",content:e}];let n=e.filter(o=>o.type==="text").map(o=>o.text).join(`
17
+ `);return n?[{role:"system",content:n}]:[]}async convertMessages(e){let n=[];for(let o of e){if(o.role==="system"){n.push(...this.serializeSystemMessage(o.content));continue}if(typeof o.content=="string"){n.push({role:o.role,content:o.content});continue}if(o.role==="assistant"){n.push(...this.serializeAssistantMessage(o.content));continue}n.push(...await this.serializeUserMessage(o.content))}return n}};import{spawn as In}from"node:child_process";import{Readable as Dn,Writable as Kn}from"node:stream";import ot from"node:process";import{ClientSideConnection as Ln,PROTOCOL_VERSION as Nn,ndJsonStream as Bn}from"@agentclientprotocol/sdk";var Te=class{static{i(this,"AcpClient")}client=null;agentProcess=null;options;constructor(e){this.options=e}async connect(){let e,n;if(this.options.inputStream&&this.options.outputStream)e=this.options.inputStream,n=this.options.outputStream;else if(this.options.command){let s=ot.platform==="win32"&&!this.options.command.endsWith(".cmd")?`${this.options.command}.cmd`:this.options.command;if(this.agentProcess=In(s,this.options.args??[],{cwd:this.options.cwd??ot.cwd(),stdio:["pipe","pipe","inherit"]}),!this.agentProcess.stdin||!this.agentProcess.stdout)throw new Error("Failed to create ACP stdio pipes");n=Kn.toWeb(this.agentProcess.stdin),e=Dn.toWeb(this.agentProcess.stdout)}else throw new Error("AcpClient requires either a command or I/O streams");let o=Bn(n,e);this.client=new Ln(()=>({sessionUpdate:i(async r=>{this.options.onSessionUpdate?.(r)},"sessionUpdate"),requestPermission:i(async r=>{let s=r.options??[],a=s.find(l=>l.kind==="allow_once"||l.kind==="allow_always")??s[0];return a?{outcome:{outcome:"selected",optionId:a.optionId}}:{outcome:{outcome:"cancelled"}}},"requestPermission")}),o),await this.client.initialize({protocolVersion:Nn,clientCapabilities:{},clientInfo:{name:"kodax-ai-acp-client",version:"1.0.0"}})}async createNewSession(){if(!this.client)throw new Error("Client not connected");return(await this.client.newSession({cwd:this.options.cwd??ot.cwd(),mcpServers:[]})).sessionId}async prompt(e,n,o,r){if(!this.client)throw new Error("Client not connected");let s={sessionId:n,prompt:[{type:"text",text:e}]};r?.model&&(s.model=r.model);let a=this.client.prompt(s);if(o){let l=i(()=>{this.client?.cancel({sessionId:n}).catch(()=>{})},"onAbort");o.addEventListener("abort",l),a=a.finally(()=>{o.removeEventListener("abort",l)})}return await a}disconnect(){this.agentProcess?.kill(),this.options.abort?.();try{this.client?.close?.()}catch{}this.client=null}};function Fn(t){if(!t||typeof t!="object")return;let e=t,n=typeof e.inputTokens=="number"?e.inputTokens:0,o=typeof e.outputTokens=="number"?e.outputTokens:0,r=typeof e.totalTokens=="number"?e.totalTokens:n+o;if(![n,o,r].some(s=>!Number.isFinite(s)||s<0)&&!(r<n||r<o))return{inputTokens:n,outputTokens:o,totalTokens:r,cachedReadTokens:typeof e.cachedReadTokens=="number"?e.cachedReadTokens:void 0,cachedWriteTokens:typeof e.cachedWriteTokens=="number"?e.cachedWriteTokens:void 0,thoughtTokens:typeof e.thoughtTokens=="number"?e.thoughtTokens:void 0}}i(Fn,"normalizeAcpUsage");var oe=class extends G{static{i(this,"KodaXAcpProvider")}_client=null;_sessionMap=new Map;_activeStreams=new Map;isConfigured(){return!0}getCapabilityProfile(){return F(fe)}serializeImageBlockToPromptToken(e){return null}stripCacheBoundariesFromMessages(e){return e.map(n=>{if(typeof n.content=="string")return n;let o=ge(n.content);return o.length===n.content.length?n:{...n,content:o}})}async stream(e,n,o,r,s,a){if(e=this.stripCacheBoundariesFromMessages(e),this.acpClientOptions.executor&&typeof this.acpClientOptions.executor.isInstalled=="function"&&!await this.acpClientOptions.executor.isInstalled())throw new Error(`${this.name} requires a local CLI environment, but the CLI was not found or is not configured correctly.`);let l=[],c=[],m=e[e.length-1],v="";if(m&&typeof m.content=="string")v=m.content;else if(m&&Array.isArray(m.content)){let y=[];for(let g of m.content)if(g.type==="text")y.push(g.text);else if(g.type==="image"){let b=this.serializeImageBlockToPromptToken(g);b&&y.push(b)}v=y.join(`
18
+ `)}let f={...this.acpClientOptions,onSessionUpdate:i(y=>{let g=y.update,b=y.sessionId;if(!("sessionUpdate"in g))return;let p=b?this._activeStreams.get(b):void 0;if(p)switch(g.sessionUpdate){case"agent_message_chunk":if(g.content?.type==="text"){let d=g.content.text;p.output.text+=d,p.streamOptions?.onTextDelta?.(d)}break;case"tool_call":{let d="{}",T=g.arguments??g.parameters;T&&(d=typeof T=="string"?T:JSON.stringify(T)),p.streamOptions?.onToolInputDelta?.(g.title,d);let w=`
19
+ > [Tool Use] ${g.title}: ${d}
20
+ `;p.output.text+=w,p.streamOptions?.onTextDelta?.(w);break}case"tool_call_update":if(g.status){let d=`> [Tool Result] ${g.status}
21
+
22
+ `;p.output.text+=d,p.streamOptions?.onTextDelta?.(d)}break}},"onSessionUpdate")},_=s?.sessionId??"default";this._client||(this._client=new Te(f),await this._client.connect());let M=this._sessionMap.get(_);M||(M=await this._client.createNewSession(),this._sessionMap.set(_,M));let x={text:""};this._activeStreams.set(M,{streamOptions:s,output:x});let u;try{u=await this._client.prompt(v,M,a,{model:s?.modelOverride})}catch(y){if(!(y instanceof Error&&y.name==="AbortError"))throw y}finally{this._activeStreams.delete(M)}return x.text&&l.push({type:"text",text:x.text}),{textBlocks:l,toolBlocks:c,thinkingBlocks:[],usage:Fn(u?.usage)}}disconnect(){this._client&&(this._client.disconnect(),this._client=null),this._activeStreams.clear(),this._sessionMap.clear()}};import{spawn as Un}from"node:child_process";import it from"node:process";var ie=class{static{i(this,"CLIExecutor")}config;_installedCache=null;constructor(e){this.config=e}async isInstalled(){return this._installedCache!==null?this._installedCache:(this._installedCache=await this.checkInstalled(),this._installedCache)}async*execute(e){let n=this.buildArgs(e),o={...it.env,...this.config.env},s=it.platform==="win32"&&!this.config.command.endsWith(".cmd")?`${this.config.command}.cmd`:this.config.command,a=Un(s,n,{cwd:this.config.cwd??it.cwd(),env:o,stdio:["ignore","pipe","pipe"]}),l="";a.stderr?.on("data",v=>{l+=v.toString()});let c=!1,m=i(()=>{c||a.kill("SIGTERM")},"abortHandler");e.signal?.addEventListener("abort",m),a.on("exit",()=>{c=!0});try{yield*this.parseOutputStream(a.stdout,e.signal),l.trim()&&console.error(`[CLIExecutor] stderr: ${l.trim()}`)}finally{e.signal?.removeEventListener("abort",m),c||a.kill()}}async*parseOutputStream(e,n){let o="";for await(let r of e){if(n?.aborted)break;o+=r.toString();let s=o.split(`
23
+ `);o=s.pop()??"";for(let a of s){if(!a.trim())continue;let l=this.parseLine(a.trim());l&&(yield l)}}if(o.trim()&&!n?.aborted){let r=this.parseLine(o.trim());r&&(yield r)}}};import{spawn as Xn}from"node:child_process";import Wn from"node:process";async function Ee(t){try{let e=Wn.platform==="win32",n=Xn(e?`${t}.cmd`:t,["--version"]);return await new Promise(o=>{n.on("close",r=>o(r===0)),n.on("error",()=>o(!1))})}catch{return!1}}i(Ee,"checkCliCommandInstalled");var Pe=class extends ie{static{i(this,"GeminiCLIExecutor")}model;constructor(e){super({command:"gemini",baseArgs:["--output-format","stream-json","--approval-mode","yolo"],timeout:3e5,...e}),this.model=e?.model??"gemini-2.5-pro"}async checkInstalled(){return Ee("gemini")}buildArgs(e){let n=["-m",e.model??this.model];return e.sessionId?(n.push("-r",e.sessionId),n.push(e.prompt)):n.push("-p",e.prompt),[...n,...this.config.baseArgs]}parseLine(e){if(!e.startsWith("{"))return null;try{let n=JSON.parse(e);return this.convertEvent(n)}catch{return null}}convertEvent(e){let n=e.timestamp?Date.parse(e.timestamp):Date.now();switch(e.type){case"init":return{type:"session_start",timestamp:n,sessionId:e.session_id??"",model:e.model??this.model,raw:e};case"message":return{type:"message",timestamp:n,role:e.role,content:e.content??"",delta:e.delta,raw:e};case"tool_use":return{type:"tool_use",timestamp:n,toolId:e.tool_id??"",toolName:e.tool_name??"",parameters:e.parameters??{},raw:e};case"tool_result":return{type:"tool_result",timestamp:n,toolId:e.tool_id??"",status:e.status==="success"?"success":"error",output:e.output??"",raw:e};case"error":return{type:"error",timestamp:n,errorType:"error",message:e.message??"Unknown error",raw:e};case"result":return{type:"complete",timestamp:n,status:e.status==="success"?"success":"failed",usage:e.stats?{inputTokens:e.stats.input_tokens??0,outputTokens:e.stats.output_tokens??0,totalTokens:e.stats.total_tokens??0}:void 0,raw:e};default:return null}}};import{TransformStream as Ft}from"node:stream/web";import{randomUUID as rt}from"node:crypto";function we(t){let e=new Ft,n=new Ft,o=e.readable.getReader(),r=n.writable.getWriter(),s=new TextDecoder,a=new TextEncoder,l="",c=rt(),m=new AbortController,v=new Map;(async()=>{try{for(;;){let{done:u,value:y}=await o.read();if(u)break;l+=s.decode(y,{stream:!0});let g=l.split(`
24
+ `);l=g.pop()??"";for(let b of g)if(b.trim())try{let p=JSON.parse(b);_(p).catch(d=>{console.error("[PseudoAcpServer] Failed to handle request:",d)})}catch{console.error("[PseudoAcpServer] Failed to parse message:",b)}}}catch(u){console.error("[PseudoAcpServer] Stream read error:",u)}})();let f=i(async u=>{let y=JSON.stringify(u)+`
25
+ `;await r.write(a.encode(y))},"sendMsg");async function _(u){if(u.method==="initialize")await f({jsonrpc:"2.0",id:u.id,result:{protocolVersion:u.params.protocolVersion,serverInfo:{name:"pseudo-acp-server",version:"1.0.0"},serverCapabilities:{}}});else if(u.method==="session/new"||u.method==="sessions/new")c=u.params?.sessionId??rt(),await f({jsonrpc:"2.0",id:u.id,result:{sessionId:c}});else if(u.method==="session/prompt"||u.method==="chat/prompt"){let y=new AbortController,g=u.params.sessionId;v.set(g,y);let b=i(()=>y.abort(),"onGlobalAbort");m.signal.addEventListener("abort",b);let d=await M(g,u.params.prompt,typeof u.params.model=="string"?u.params.model:void 0,y.signal).finally(()=>{v.delete(g),m.signal.removeEventListener("abort",b)});await f({jsonrpc:"2.0",id:u.id,result:d.usage?{stopReason:d.stopReason,usage:d.usage}:{stopReason:d.stopReason}})}else if(u.method==="session/cancel"||u.method==="chat/cancel"){let y=v.get(u.params.sessionId);y&&y.abort(),u.id!==void 0&&await f({jsonrpc:"2.0",id:u.id,result:{}})}else u.id!==void 0&&await f({jsonrpc:"2.0",id:u.id,result:{}})}i(_,"handleRequest");let M=i(async(u,y,g,b)=>{let p=y.find(d=>d.type==="text")?.text??"";try{let d=t.execute({prompt:p,model:g,sessionId:u==="default"?void 0:u,signal:b});for await(let T of d){let w=x(T);if(w&&await f({jsonrpc:"2.0",method:"session/update",params:{sessionId:u,update:w}}),T.type==="complete")return{stopReason:b.aborted?"cancelled":"end_turn",usage:T.usage}}return{stopReason:b.aborted?"cancelled":"end_turn"}}catch(d){return b.aborted||d instanceof Error&&d.name==="AbortError"?{stopReason:"cancelled"}:(console.error("[PseudoAcpServer] Error executing prompt:",d),await f({jsonrpc:"2.0",method:"session/update",params:{sessionId:u,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`
26
+ [Fatal Error: ${d}]
27
+ `}}}}),{stopReason:"end_turn"})}},"executePrompt"),x=i(u=>{switch(u.type){case"message":if(u.role==="assistant"&&u.content)return{sessionUpdate:"agent_message_chunk",content:{type:"text",text:u.content}};break;case"tool_use":return{sessionUpdate:"tool_call",title:u.toolName,arguments:u.parameters,status:"running",toolCallId:u.toolId||rt()};case"tool_result":return{sessionUpdate:"tool_call_update",toolCallId:u.toolId,status:u.status};case"error":return{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`
28
+ [Error: ${u.message}]
29
+ `}};case"complete":break}return null},"mapToAcpNotification");return{inputStream:n.readable,outputStream:e.writable,abort:i(()=>{m.abort(),e.readable.cancel().catch(()=>{}),n.writable.abort().catch(()=>{})},"abort"),executor:t}}i(we,"createPseudoAcpServer");import Ut from"node:fs";import Xt from"node:os";import Wt from"node:path";var $n="gpt-5.4",jn="auto-gemini-3",Gn=["gpt-5.4","gpt-5.3-codex","gpt-5.3-codex-spark"],zn=["auto-gemini-3","gemini-3.1-pro-preview","gemini-3-flash-preview","gemini-2.5-pro","gemini-2.5-flash"];function $t(t){let e=new Set,n=[];for(let o of t){let r=o.trim();if(!r)continue;let s=r.toLowerCase();e.has(s)||(e.add(s),n.push(r))}return n}i($t,"dedupePreserveOrder");function Hn(){let t=Wt.join(Xt.homedir(),".codex","config.toml");try{return Ut.readFileSync(t,"utf8").match(/^\s*model\s*=\s*"([^"]+)"/m)?.[1]?.trim()||null}catch{return null}}i(Hn,"readCodexConfiguredModel");function Vn(){let t=Wt.join(Xt.homedir(),".gemini","settings.json");try{let e=JSON.parse(Ut.readFileSync(t,"utf8")),n=typeof e.model=="string"?e.model.trim():"";return n||(typeof e.general?.model=="string"?e.general.model.trim():"")||null}catch{return null}}i(Vn,"readGeminiConfiguredModel");function re(){return Hn()||$n}i(re,"getCodexCliDefaultModel");function se(){return Vn()||jn}i(se,"getGeminiCliDefaultModel");function he(){return $t([re(),...Gn])}i(he,"getCodexCliKnownModels");function ye(){return $t([se(),...zn])}i(ye,"getGeminiCliKnownModels");var st=se(),Yn=ye(),Jn={transport:"cli-bridge",conversationSemantics:"last-user-message",mcpSupport:"none",contextFidelity:"lossy",toolCallingFidelity:"limited",sessionSupport:"stateless",longRunningSupport:"limited",multimodalSupport:"image-input",evidenceSupport:"limited"},Re=class extends oe{static{i(this,"KodaXGeminiCliProvider")}name="gemini-cli";supportsThinking=!1;config={apiKeyEnv:"GEMINI_CLI_API_KEY",model:st,models:Yn.filter(e=>e!==st).map(e=>({id:e,displayName:e})),supportsThinking:!1,reasoningCapability:"prompt-only",contextWindow:1048576};acpClientOptions;constructor(){super();let e=new Pe({model:st});this.acpClientOptions=we(e)}getCapabilityProfile(){return F(Jn)}serializeImageBlockToPromptToken(e){return/\s/.test(e.path)?(console.warn(`[gemini-cli] Image path contains whitespace and cannot be safely passed via @<path> syntax \u2014 dropping image block. Path: ${JSON.stringify(e.path)}`),null):`@${e.path}`}};var Ae=class extends ie{static{i(this,"CodexCLIExecutor")}model;constructor(e){super({command:"codex",baseArgs:["exec","--json","--full-auto"],timeout:3e5,...e}),this.model=e?.model??"gpt-5.4"}async checkInstalled(){return Ee("codex")}buildArgs(e){this.model=e.model??this.model;let n=e.model?["-m",e.model]:[];return e.sessionId?["exec","resume",e.sessionId,...n,e.prompt,...this.config.baseArgs.filter(o=>o!=="exec")]:[...this.config.baseArgs,...n,e.prompt]}parseLine(e){if(!e.startsWith("{"))return null;try{let n=JSON.parse(e);return this.convertEvent(n)}catch{return null}}convertEvent(e){let n=Date.now();switch(e.type){case"thread.started":return{type:"session_start",timestamp:n,sessionId:e.thread_id??"",model:this.model,raw:e};case"item.completed":return e.item?.type==="agent_message"?{type:"message",timestamp:n,role:"assistant",content:e.item.text??"",raw:e}:e.item?.type==="command_execution"?{type:"tool_use",timestamp:n,toolId:e.item.id,toolName:"Bash",parameters:{command:e.item.command},raw:e}:null;case"turn.completed":return{type:"complete",timestamp:n,status:"success",usage:e.usage?{inputTokens:e.usage.input_tokens,outputTokens:e.usage.output_tokens,totalTokens:e.usage.input_tokens+e.usage.output_tokens}:void 0,raw:e};case"error":case"turn.failed":return{type:"error",timestamp:n,errorType:e.type,message:e.message??"Unknown error",raw:e};default:return null}}};var at=re(),Qn=he(),Se=class extends oe{static{i(this,"KodaXCodexCliProvider")}name="codex-cli";supportsThinking=!1;config={apiKeyEnv:"CODEX_CLI_API_KEY",model:at,models:Qn.filter(e=>e!==at).map(e=>({id:e,displayName:e})),supportsThinking:!1,reasoningCapability:"prompt-only",contextWindow:128e3};acpClientOptions;constructor(){super();let e=new Ae({model:at});this.acpClientOptions=we(e)}};import qn from"@anthropic-ai/sdk";var jt=se(),Zn=ye(),Gt=re(),eo=he(),z={anthropic:{apiKeyEnv:"ANTHROPIC_API_KEY",model:"claude-sonnet-4-6",models:["claude-opus-4-6","claude-haiku-4-5"],reasoningCapability:"native-budget",capabilityProfile:L},openai:{apiKeyEnv:"OPENAI_API_KEY",model:"gpt-5.3-codex",models:["gpt-5.4","gpt-5.3-codex-spark"],reasoningCapability:"native-effort",capabilityProfile:L},deepseek:{apiKeyEnv:"DEEPSEEK_API_KEY",model:"deepseek-v4-flash",models:["deepseek-v4-pro"],reasoningCapability:"native-effort",capabilityProfile:L},kimi:{apiKeyEnv:"KIMI_API_KEY",model:"kimi-k2.6",models:["k2.5"],reasoningCapability:"native-effort",capabilityProfile:L},"kimi-code":{apiKeyEnv:"KIMI_API_KEY",model:"kimi-for-coding",reasoningCapability:"native-budget",capabilityProfile:L},qwen:{apiKeyEnv:"QWEN_API_KEY",model:"qwen3.5-plus",reasoningCapability:"native-budget",capabilityProfile:L},zhipu:{apiKeyEnv:"ZHIPU_API_KEY",model:"glm-5",models:["glm-5.1","glm-5-turbo"],reasoningCapability:"native-budget",capabilityProfile:L},"zhipu-coding":{apiKeyEnv:"ZHIPU_API_KEY",model:"glm-5",models:["glm-5.1","glm-5-turbo"],reasoningCapability:"native-budget",capabilityProfile:L},"minimax-coding":{apiKeyEnv:"MINIMAX_API_KEY",model:"MiniMax-M2.7",models:["MiniMax-M2.7-highspeed","MiniMax-M2.5","MiniMax-M2.5-highspeed","MiniMax-M2.1","MiniMax-M2.1-highspeed","MiniMax-M2"],reasoningCapability:"native-budget",capabilityProfile:L},"mimo-coding":{apiKeyEnv:"MIMO_API_KEY",model:"mimo-v2.5-pro",models:["mimo-v2.5"],reasoningCapability:"native-budget",capabilityProfile:L},"ark-coding":{apiKeyEnv:"ARK_API_KEY",model:"glm-5.1",models:["glm-4.7","kimi-k2.6","kimi-k2.5","minimax-latest","deepseek-v3.2","doubao-seed-2.0-code","doubao-seed-2.0-pro","doubao-seed-2.0-lite"],reasoningCapability:"native-budget",capabilityProfile:L},"gemini-cli":{apiKeyEnv:"GEMINI_API_KEY",model:jt,models:Zn.filter(t=>t!==jt),reasoningCapability:"prompt-only",capabilityProfile:Lt},"codex-cli":{apiKeyEnv:"OPENAI_API_KEY",model:Gt,models:eo.filter(t=>t!==Gt),reasoningCapability:"prompt-only",capabilityProfile:fe}};function X(t,e){let n=z[t];return{apiKeyEnv:n.apiKeyEnv,model:n.model,reasoningCapability:n.reasoningCapability,...e}}i(X,"buildProviderConfig");var ct=class extends K{static{i(this,"AnthropicProvider")}name="anthropic";config=X("anthropic",{models:[{id:"claude-opus-4-6",displayName:"Opus 4.6",thinkingBudgetCap:28e3},{id:"claude-haiku-4-5",displayName:"Haiku 4.5",thinkingBudgetCap:1e4}],supportsThinking:!0,contextWindow:2e5,maxOutputTokens:64e3,thinkingBudgetCap:28e3,strictThinkingSignature:!0});constructor(){super(),this.client=new qn({apiKey:this.getApiKey()})}},lt=class extends K{static{i(this,"ZhipuCodingProvider")}name="zhipu-coding";config=X("zhipu-coding",{baseUrl:"https://open.bigmodel.cn/api/anthropic",models:[{id:"glm-5.1",displayName:"GLM-5.1"},{id:"glm-5-turbo",displayName:"GLM-5 Turbo",contextWindow:128e3}],supportsThinking:!0,contextWindow:2e5,maxOutputTokens:16e3,thinkingBudgetCap:16e3,streamMaxDurationMs:3e5});constructor(){super(),this.initClient()}},ut=class extends K{static{i(this,"KimiCodeProvider")}name="kimi-code";config=X("kimi-code",{baseUrl:"https://api.kimi.com/coding/",supportsThinking:!0,contextWindow:256e3,maxOutputTokens:32e3});constructor(){super(),this.initClient()}},pt=class extends K{static{i(this,"MiniMaxCodingProvider")}name="minimax-coding";config=X("minimax-coding",{baseUrl:"https://api.minimaxi.com/anthropic",models:[{id:"MiniMax-M2.7",displayName:"MiniMax M2.7"},{id:"MiniMax-M2.7-highspeed",displayName:"MiniMax M2.7 Highspeed (higher-tier plan)"},{id:"MiniMax-M2.5",displayName:"MiniMax M2.5"},{id:"MiniMax-M2.5-highspeed",displayName:"MiniMax M2.5 Highspeed (higher-tier plan)"},{id:"MiniMax-M2.1",displayName:"MiniMax M2.1"},{id:"MiniMax-M2.1-highspeed",displayName:"MiniMax M2.1 Highspeed (higher-tier plan)"},{id:"MiniMax-M2",displayName:"MiniMax M2"}],supportsThinking:!0,contextWindow:204800,maxOutputTokens:32e3});constructor(){super(),this.initClient()}},dt=class extends K{static{i(this,"MimoCodingProvider")}name="mimo-coding";config=X("mimo-coding",{baseUrl:"https://token-plan-cn.xiaomimimo.com/anthropic",models:[{id:"mimo-v2.5",displayName:"MiMo V2.5"}],supportsThinking:!0,contextWindow:1e6,maxOutputTokens:32e3,thinkingBudgetCap:16e3});constructor(){super(),this.initClient()}},mt=class extends K{static{i(this,"ArkCodingProvider")}name="ark-coding";config=X("ark-coding",{baseUrl:"https://ark.cn-beijing.volces.com/api/coding",models:[{id:"glm-4.7",displayName:"GLM-4.7"},{id:"kimi-k2.6",displayName:"Kimi K2.6",contextWindow:256e3},{id:"kimi-k2.5",displayName:"Kimi K2.5",contextWindow:256e3},{id:"minimax-latest",displayName:"MiniMax Latest",contextWindow:204800},{id:"deepseek-v3.2",displayName:"DeepSeek V3.2",contextWindow:128e3},{id:"doubao-seed-2.0-code",displayName:"Doubao Seed 2.0 Code",contextWindow:256e3},{id:"doubao-seed-2.0-pro",displayName:"Doubao Seed 2.0 Pro",contextWindow:256e3},{id:"doubao-seed-2.0-lite",displayName:"Doubao Seed 2.0 Lite",contextWindow:256e3}],supportsThinking:!0,contextWindow:2e5,maxOutputTokens:32e3});constructor(){super(),this.initClient()}},ft=class extends N{static{i(this,"OpenAIProvider")}name="openai";config=X("openai",{models:[{id:"gpt-5.4",displayName:"GPT-5.4"},{id:"gpt-5.3-codex-spark",displayName:"GPT-5.3 Codex Spark"}],supportsThinking:!0,contextWindow:4e5,maxOutputTokens:32768});constructor(){super(),this.initClient()}},gt=class extends N{static{i(this,"DeepSeekProvider")}name="deepseek";config=X("deepseek",{baseUrl:"https://api.deepseek.com",models:[{id:"deepseek-v4-pro",displayName:"DeepSeek V4 Pro"}],supportsThinking:!0,contextWindow:1e6,maxOutputTokens:64e3,replayReasoningContent:!0});constructor(){super(),this.initClient()}},ht=class extends N{static{i(this,"KimiProvider")}name="kimi";config=X("kimi",{baseUrl:"https://api.moonshot.cn/v1",models:[{id:"k2.5",displayName:"K2.5"}],supportsThinking:!0,contextWindow:256e3,maxOutputTokens:32768,replayReasoningContent:!0});constructor(){super(),this.initClient()}},yt=class extends N{static{i(this,"QwenProvider")}name="qwen";config=X("qwen",{baseUrl:"https://dashscope.aliyuncs.com/compatible-mode/v1",supportsThinking:!0,contextWindow:256e3,maxOutputTokens:32768,replayReasoningContent:!0});constructor(){super(),this.initClient()}},_t=class extends N{static{i(this,"ZhipuProvider")}name="zhipu";config=X("zhipu",{baseUrl:"https://open.bigmodel.cn/api/paas/v4",models:[{id:"glm-5.1",displayName:"GLM-5.1"},{id:"glm-5-turbo",displayName:"GLM-5 Turbo",contextWindow:128e3}],supportsThinking:!0,contextWindow:2e5,maxOutputTokens:32768,replayReasoningContent:!0});constructor(){super(),this.initClient()}},U={anthropic:i(()=>new ct,"anthropic"),openai:i(()=>new ft,"openai"),deepseek:i(()=>new gt,"deepseek"),kimi:i(()=>new ht,"kimi"),"kimi-code":i(()=>new ut,"kimi-code"),qwen:i(()=>new yt,"qwen"),zhipu:i(()=>new _t,"zhipu"),"zhipu-coding":i(()=>new lt,"zhipu-coding"),"minimax-coding":i(()=>new pt,"minimax-coding"),"mimo-coding":i(()=>new dt,"mimo-coding"),"ark-coding":i(()=>new mt,"ark-coding"),"gemini-cli":i(()=>new Re,"gemini-cli"),"codex-cli":i(()=>new Se,"codex-cli")},kt=process.env.KODAX_PROVIDER??"zhipu-coding",zt=new Map;function to(t){if(B(t))return z[t].apiKeyEnv}i(to,"resolveApiKeyEnvForProvider");function Ht(t){let e=t??kt,n=U[e];if(!n)throw new R(`Unknown provider: ${e}. Available: ${Object.keys(U).join(", ")}`,e);let o=to(e),r=o?process.env[o]:void 0,s=zt.get(e);if(s&&s.apiKey===r)return s.instance;let a=n();return zt.set(e,{apiKey:r,instance:a}),a}i(Ht,"getProvider");function Vt(t){return B(t)?!!process.env[z[t].apiKeyEnv]:!1}i(Vt,"isProviderConfigured");function Yt(t){return B(t)?z[t].model:null}i(Yt,"getProviderModel");function Jt(t,e){if(!B(t))return"unknown";let n=z[t],o=e??n.model;return n.modelReasoningCapabilities?.[o]??n.reasoningCapability}i(Jt,"getProviderConfiguredReasoningCapability");function Qt(t){return B(t)?F(z[t].capabilityProfile):null}i(Qt,"getProviderConfiguredCapabilityProfile");function qt(){let t=[];for(let e of Object.keys(U)){let n=z[e];t.push({name:e,model:n.model,models:n.models?[n.model,...n.models]:[n.model],configured:!!process.env[n.apiKeyEnv],reasoningCapability:n.reasoningCapability,capabilityProfile:F(n.capabilityProfile)})}return t}i(qt,"getProviderList");function Zt(t){let e=z[t];return e?e.models?[e.model,...e.models]:[e.model]:[]}i(Zt,"getProviderModels");function B(t){return t in U}i(B,"isProviderName");var no=new Set(["compat","sdk"]);function vt(t){if(!t.name||!t.baseUrl||!t.apiKeyEnv||!t.model)throw new Error(`Custom provider requires name, baseUrl, apiKeyEnv, and model. Got: ${JSON.stringify({name:t.name,baseUrl:t.baseUrl,apiKeyEnv:t.apiKeyEnv,model:t.model})}`);if(t.protocol!=="anthropic"&&t.protocol!=="openai")throw new Error(`Unknown protocol "${t.protocol}" for custom provider "${t.name}". Must be "anthropic" or "openai".`);if(t.userAgentMode!==void 0&&!no.has(t.userAgentMode))throw new Error(`Unknown userAgentMode "${t.userAgentMode}" for custom provider "${t.name}". Must be "compat" or "sdk".`)}i(vt,"validateCustomProviderConfig");function oo(t){let e=t.models?.length?t.models.map(n=>typeof n=="string"?{id:n}:n):void 0;return{apiKeyEnv:t.apiKeyEnv,model:t.model,baseUrl:t.baseUrl,models:e,userAgentMode:t.userAgentMode,supportsThinking:t.supportsThinking??!1,reasoningCapability:t.reasoningCapability??"none",capabilityProfile:t.capabilityProfile,contextWindow:t.contextWindow,maxOutputTokens:t.maxOutputTokens,thinkingBudgetCap:t.thinkingBudgetCap}}i(oo,"buildProviderConfig");function Oe(t){vt(t);let e=oo(t);return t.protocol==="anthropic"?new xt(t.name,e):new bt(t.name,e)}i(Oe,"createCustomProvider");var xt=class extends K{static{i(this,"DynamicAnthropicProvider")}name;config;constructor(e,n){super(),this.name=e,this.config=n,this.initClient()}},bt=class extends N{static{i(this,"DynamicOpenAIProvider")}name;config;constructor(e,n){super(),this.name=e,this.config=n,this.initClient()}};var ae=new Map,Mt=new Map;function en(t){let e=new Set,n=new Map,o=new Map;for(let r of t){if(vt(r),e.has(r.name))throw new Error(`Duplicate custom provider name: "${r.name}". Each custom provider must have a unique name.`);r.name in U&&console.warn(`[kodax] Custom provider "${r.name}" shadows a built-in provider. The built-in provider will be used. Choose a different name to use your custom provider.`),e.add(r.name),n.set(r.name,r),o.set(r.name,()=>Oe(r))}ae.clear(),Mt.clear();for(let[r,s]of n)ae.set(r,s);for(let[r,s]of o)Mt.set(r,s)}i(en,"registerCustomProviders");function Ie(t){let e=Mt.get(t);return e?e():void 0}i(Ie,"getCustomProvider");function ce(t){return ae.has(t)}i(ce,"isCustomProviderName");function De(){return[...ae.keys()]}i(De,"getCustomProviderNames");function tn(){let t=[];for(let[e,n]of ae){let o=!!process.env[n.apiKeyEnv],r=(n.models??[]).map(a=>typeof a=="string"?a:a.id),s=n.model&&r.length?[...new Set([n.model,...r])]:[n.model];t.push({name:e,model:n.model,models:s,configured:o,reasoningCapability:n.reasoningCapability??"none",capabilityProfile:F(n.capabilityProfile??me),custom:!0})}return t}i(tn,"getCustomProviderList");function nn(t){let e=ae.get(t);if(!e)return;let n=(e.models??[]).map(o=>typeof o=="string"?o:o.id);return e.model&&n.length?[...new Set([e.model,...n])]:[e.model]}i(nn,"getCustomProviderModels");var V=new Map,io=0;function Ct(t){let e=V.get(t);if(!(!e||e.length===0))return e[e.length-1]}i(Ct,"getActiveRuntimeProviderRegistration");function ro(t){for(let[e,n]of V){let o=n.filter(r=>r.id!==t);if(o.length!==n.length){o.length===0?V.delete(e):V.set(e,o);return}}}i(ro,"removeRuntimeProviderRegistration");function on(t,e){let n=t.trim();if(!n)throw new Error("Model provider name cannot be empty.");if(B(n))throw new Error(`Runtime model provider "${n}" conflicts with a built-in provider.`);if(ce(n))throw new Error(`Runtime model provider "${n}" conflicts with an existing config-defined custom provider.`);let o={id:`runtime-provider:${++io}`,name:n,factory:e},r=V.get(n)??[];return V.set(n,[...r,o]),()=>{ro(o.id)}}i(on,"registerModelProvider");function Ke(t){let e=Ct(t);return e?e.factory():void 0}i(Ke,"getRuntimeModelProvider");function Le(t){return Ct(t)!==void 0}i(Le,"isRuntimeModelProviderName");function Ne(){return Array.from(V.keys()).filter(t=>Ct(t)!==void 0)}i(Ne,"getRuntimeModelProviderNames");function rn(){V.clear()}i(rn,"clearRuntimeModelProviders");function sn(t){if(B(t))return U[t]();let e=Ke(t);if(e)return e;let n=Ie(t);if(n)return n;let o=Tt();throw new Error(`Unknown provider: ${t}. Available: ${o.join(", ")}`)}i(sn,"resolveProvider");function an(t){return B(t)||Le(t)||ce(t)}i(an,"isKnownProvider");function Tt(){let t=Object.keys(U),e=Ne(),n=De();return[...new Set([...t,...e,...n])]}i(Tt,"getAvailableProviderNames");var cn={anthropic:{"claude-opus-4-6":{inputPer1M:15,outputPer1M:75,cachePer1M:1.875},"claude-sonnet-4-6":{inputPer1M:3,outputPer1M:15,cachePer1M:.375},"claude-haiku-4-5":{inputPer1M:.8,outputPer1M:4,cachePer1M:.08}},openai:{"gpt-5.4":{inputPer1M:30,outputPer1M:120},"gpt-5.3-codex-spark":{inputPer1M:10,outputPer1M:40}},deepseek:{"deepseek-v4-flash":{inputPer1M:.14,outputPer1M:.28,cachePer1M:.028},"deepseek-v4-pro":{inputPer1M:1.68,outputPer1M:3.36,cachePer1M:.14}},kimi:{"k2.5":{inputPer1M:.005,outputPer1M:.015},"kimi-k2.6":{inputPer1M:.005,outputPer1M:.015}},"kimi-code":{"kimi-for-coding":{inputPer1M:.005,outputPer1M:.015}},qwen:{"qwen3.5-plus":{inputPer1M:.003,outputPer1M:.006}},zhipu:{"glm-5":{inputPer1M:.05,outputPer1M:.1},"glm-5.1":{inputPer1M:.05,outputPer1M:.1},"glm-5-turbo":{inputPer1M:.01,outputPer1M:.03}},"zhipu-coding":{"glm-5":{inputPer1M:.05,outputPer1M:.1},"glm-5.1":{inputPer1M:.05,outputPer1M:.1},"glm-5-turbo":{inputPer1M:.01,outputPer1M:.03}},"minimax-coding":{"MiniMax-M2.7":{inputPer1M:.01,outputPer1M:.03},"MiniMax-M2.7-highspeed":{inputPer1M:.01,outputPer1M:.03},"MiniMax-M2.5":{inputPer1M:.01,outputPer1M:.03},"MiniMax-M2.5-highspeed":{inputPer1M:.01,outputPer1M:.03},"MiniMax-M2.1":{inputPer1M:.01,outputPer1M:.03},"MiniMax-M2.1-highspeed":{inputPer1M:.01,outputPer1M:.03},"MiniMax-M2":{inputPer1M:.01,outputPer1M:.03}},"mimo-coding":{"mimo-v2.5-pro":{inputPer1M:.01,outputPer1M:.03},"mimo-v2.5":{inputPer1M:.01,outputPer1M:.03}},"ark-coding":{"glm-5.1":{inputPer1M:.005,outputPer1M:.015},"glm-4.7":{inputPer1M:.005,outputPer1M:.015},"kimi-k2.6":{inputPer1M:.005,outputPer1M:.015},"kimi-k2.5":{inputPer1M:.005,outputPer1M:.015},"minimax-latest":{inputPer1M:.005,outputPer1M:.015},"deepseek-v3.2":{inputPer1M:.005,outputPer1M:.015},"doubao-seed-2.0-code":{inputPer1M:.005,outputPer1M:.015},"doubao-seed-2.0-pro":{inputPer1M:.005,outputPer1M:.015},"doubao-seed-2.0-lite":{inputPer1M:.005,outputPer1M:.015}},"gemini-cli":{},"codex-cli":{}};function Et(t,e,n){let o=n?.[t]?.[e];return o||cn[t]?.[e]}i(Et,"getCostRate");function Pt(t,e,n,o=0){let r=e/1e6*t.inputPer1M,s=n/1e6*t.outputPer1M,a=t.cachePer1M?o/1e6*t.cachePer1M:0;return r+s+a}i(Pt,"calculateCost");function so(){return{records:[],retries:[]}}i(so,"createCostTracker");function ao(t,e){let n={timestamp:Date.now(),provider:e.provider,waitMs:e.waitMs,reason:e.reason,source:e.source};return{records:t.records,retries:[...t.retries,n]}}i(ao,"recordRetry");function wt(t,e,n){let o=Et(e.provider,e.model,n),r=(e.cacheReadTokens??0)+(e.cacheWriteTokens??0),s=o?Pt(o,e.inputTokens,e.outputTokens,r):0,a={timestamp:Date.now(),provider:e.provider,model:e.model,inputTokens:e.inputTokens,outputTokens:e.outputTokens,cacheReadTokens:e.cacheReadTokens??0,cacheWriteTokens:e.cacheWriteTokens??0,cost:s,role:e.role};return{records:[...t.records,a],retries:t.retries}}i(wt,"recordUsage");function co(t){let e=0,n=0,o=0,r=0,s=0,a=0,l={},c={};for(let f of t.records){e+=f.cost,n+=f.inputTokens,o+=f.outputTokens,r+=f.cacheReadTokens+f.cacheWriteTokens,s+=f.cacheReadTokens,a+=f.cacheWriteTokens;let _=l[f.provider];l[f.provider]={cost:(_?.cost??0)+f.cost,calls:(_?.calls??0)+1,inputTokens:(_?.inputTokens??0)+f.inputTokens,outputTokens:(_?.outputTokens??0)+f.outputTokens};let M=f.role??"default",x=c[M];c[M]={cost:(x?.cost??0)+f.cost,calls:(x?.calls??0)+1,inputTokens:(x?.inputTokens??0)+f.inputTokens,outputTokens:(x?.outputTokens??0)+f.outputTokens}}let m=0;for(let f of t.retries)m+=f.waitMs;let v=r>0?s/r:0;return{totalCost:e,totalInputTokens:n,totalOutputTokens:o,totalCacheTokens:r,totalCacheReadTokens:s,totalCacheWriteTokens:a,cacheHitRate:v,callCount:t.records.length,retryCount:t.retries.length,retryWaitMs:m,byProvider:l,byRole:c}}i(co,"getSummary");function Be(t){return t<.01?`$${t.toFixed(4)}`:t<1?`$${t.toFixed(3)}`:`$${t.toFixed(2)}`}i(Be,"formatCost");function lo(t){let e=[];if(e.push(`Session Cost: ${Be(t.totalCost)} (${t.callCount} calls)`),e.push(`Tokens: ${t.totalInputTokens.toLocaleString()} in / ${t.totalOutputTokens.toLocaleString()} out`),t.totalCacheTokens>0){let r=(t.cacheHitRate*100).toFixed(0);e.push(`Cache: ${t.totalCacheTokens.toLocaleString()} tokens (${t.totalCacheReadTokens.toLocaleString()} read / ${t.totalCacheWriteTokens.toLocaleString()} write, ${r}% hit rate)`)}if(t.retryCount>0){let r=(t.retryWaitMs/1e3).toFixed(1);e.push(`Retries: ${t.retryCount} (${r}s total wait)`)}e.push("");let n=Object.entries(t.byProvider).sort((r,s)=>s[1].cost-r[1].cost);if(n.length>0){e.push("By Provider:");for(let[r,s]of n)e.push(` ${r}: ${Be(s.cost)} (${s.calls} calls, ${s.inputTokens.toLocaleString()} in / ${s.outputTokens.toLocaleString()} out)`);e.push("")}let o=Object.entries(t.byRole).sort((r,s)=>s[1].cost-r[1].cost);if(o.length>1){e.push("By Role:");for(let[r,s]of o)e.push(` ${r}: ${Be(s.cost)} (${s.calls} calls)`)}return e.join(`
30
+ `)}i(lo,"formatCostReport");var ln={inputTokens:0,outputTokens:0,totalTokens:0},uo=3e4;async function po(t){let e=new AbortController,n=t.timeoutMs??uo,o,r=i(l=>{o||(o=l),e.abort()},"recordAbort"),s=setTimeout(()=>r("timeout"),n),a=i(()=>r("parent"),"onParentAbort");t.abortSignal&&(t.abortSignal.aborted?r("parent"):t.abortSignal.addEventListener("abort",a,{once:!0}));try{let l=await t.provider.stream([...t.messages],[],t.system,t.reasoning??{mode:"off"},{modelOverride:t.model},e.signal),c=l.usage??ln,m=l.textBlocks??[],v=l.toolBlocks??[],f=m.map(M=>M.text).join("");if(v.length>0)return{text:f,usage:c,costTracker:t.costTracker,stopReason:"error",error:new Error(`sideQuery: provider returned ${v.length} tool_use block(s); sideQuery expects text-only output`)};let _=t.costTracker;return _&&(_=wt(_,{provider:t.provider.name,model:t.model,inputTokens:c.inputTokens,outputTokens:c.outputTokens,cacheReadTokens:c.cachedReadTokens,cacheWriteTokens:c.cachedWriteTokens,role:t.querySource})),{text:f,usage:c,costTracker:_,stopReason:mo(l.stopReason)}}catch(l){let c=l instanceof Error?l:new Error(String(l)),m="error";return e.signal.aborted&&(m=o==="timeout"?"timeout":"aborted"),{text:"",usage:ln,costTracker:t.costTracker,stopReason:m,error:c}}finally{clearTimeout(s),t.abortSignal&&t.abortSignal.removeEventListener("abort",a)}}i(po,"sideQuery");function mo(t){return t==="max_tokens"?"max_tokens":"end_turn"}i(mo,"mapStopReason");export{H as a,R as b,de as c,Fe as d,Ue as e,Xe as f,J as g,We as h,un as i,pn as j,Rt as k,At as l,$e as m,Q as n,ke as o,St as p,q,Z as r,je as s,Ve as t,Ye as u,ee as v,be as w,Qe as x,yn as y,ve as z,Me as A,Ce as B,G as C,te as D,qe as E,ne as F,Ze as G,ge as H,K as I,N as J,re as K,se as L,he as M,ye as N,z as O,U as P,kt as Q,Ht as R,Vt as S,Yt as T,Jt as U,Qt as V,qt as W,Zt as X,B as Y,Oe as Z,en as _,Ie as $,ce as aa,De as ba,tn as ca,nn as da,on as ea,Ke as fa,Le as ga,Ne as ha,rn as ia,sn as ja,an as ka,Tt as la,cn as ma,Et as na,Pt as oa,so as pa,ao as qa,wt as ra,co as sa,Be as ta,lo as ua,po as va};