Compare commits

..

188 Commits

Author SHA1 Message Date
Vogel 75e4b8eb99 fix(app): refine beta channel badge
Opencode-Session: ses_0e22a6e11ffe3aB3BmVLK4TQ7X
Opencode-Message: msg_f1dd85d480018fpawmUVWJV63c
Opencode-Agent: build
2026-07-01 09:22:28 -04:00
Aarav Sareen 6697cf3fd8 feat(app): minor fixes (#34748) 2026-07-01 21:10:53 +08:00
opencode-agent[bot] 87aa62d1e2 chore: generate 2026-07-01 12:41:15 +00:00
usrnk1 3136b1ba97 feat(desktop): improve markdown heading spacing in session timeline (#34738) 2026-07-01 20:39:12 +08:00
Aarav Sareen 246dea3a58 feat(app): more v2 ui alignment (#34465)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-07-01 12:14:51 +00:00
opencode-agent[bot] 4b1c7f28a0 chore: generate 2026-07-01 12:08:40 +00:00
Aarav Sareen 9410eda4e3 feat(app): composer improvements (#34720)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-07-01 12:06:41 +00:00
usrnk1 f014686b07 feat(desktop): session tab hover preview popover (#34678)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-07-01 17:16:45 +08:00
opencode-agent[bot] 8b68dc0d7f chore: generate 2026-07-01 09:05:06 +00:00
Brendan Allan 2f4d36d64e fix(desktop): clean stale draft stores (#34651) 2026-07-01 09:03:27 +00:00
opencode-agent[bot] 6f2ff35656 chore: generate 2026-07-01 08:17:22 +00:00
Julian Coy cd00422298 fix(app): question UI fixes and UX improvements (#34116)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
2026-07-01 08:15:31 +00:00
opencode-agent[bot] 34ee94232f chore: generate 2026-07-01 07:40:25 +00:00
Aarav Sareen 4d91f0cf28 feat(app): v2 wsl ui (#34233)
Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
2026-07-01 07:39:00 +00:00
opencode-agent[bot] bf18cc971f chore: generate 2026-07-01 07:06:04 +00:00
Brendan Allan 5e1a1ed2e2 fix(app): scope session page errors (#34254) 2026-07-01 15:04:18 +08:00
opencode-agent[bot] bd7f7ad98b chore: generate 2026-07-01 07:03:41 +00:00
Brendan Allan af72cec799 feat(desktop): scope tabs to windows (#34669) 2026-07-01 07:02:10 +00:00
Aiden Cline 55552c521f fix(provider): force openai reasoning variants (#34702)
deploy / deploy (push) Has been cancelled
nix-eval / nix-eval (push) Has been cancelled
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404, x86_64-linux) (push) Has been cancelled
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404-arm, aarch64-linux) (push) Has been cancelled
nix-hashes / compute-hash (macos-15-intel, x86_64-darwin) (push) Has been cancelled
nix-hashes / compute-hash (macos-latest, aarch64-darwin) (push) Has been cancelled
nix-hashes / update-hashes (push) Has been cancelled
publish / version (push) Has been cancelled
publish / build-cli (push) Has been cancelled
publish / sign-cli-windows (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Has been cancelled
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Has been cancelled
publish / publish (push) Has been cancelled
storybook / storybook build (push) Has been cancelled
docs-locale-sync / sync-locales (push) Has been cancelled
generate / generate (push) Has been cancelled
typecheck / typecheck (push) Has been cancelled
2026-06-30 22:45:39 -05:00
opencode-agent[bot] 2b611a5b14 chore: update nix node_modules hashes 2026-07-01 01:44:56 +00:00
opencode-agent[bot] 45a437cbb2 fix(app): clamp virtualizer range to scroll bounds (#34588)
Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-07-01 11:28:34 +10:00
opencode-agent[bot] 439ec9b76f chore: generate 2026-07-01 00:53:45 +00:00
opencode-agent[bot] ae3fefb42a fix(session-ui): recognize more inline file paths (#34688)
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-07-01 00:52:10 +00:00
Aiden Cline f1407e41c4 fix(core): stop replaying stale GitHub Copilot Responses item IDs (#34686) 2026-06-30 19:19:02 -05:00
opencode-agent[bot] c88ca972cf chore: generate 2026-06-30 23:59:52 +00:00
Frank 928c37bc02 zen: kk2.7, mm3, sonnet5
deploy / deploy (push) Has been cancelled
2026-06-30 19:56:25 -04:00
opencode afff74eb2c sync release versions for v1.17.12 2026-06-30 19:48:01 +00:00
Aiden Cline 3a669d528b fix(provider): enable sonnet 5 adaptive thinking (#34673) 2026-06-30 14:03:05 -05:00
Jack 6789214b31 fix(console): remove expired Go MiniMax promo (#34663) 2026-07-01 01:42:05 +08:00
James Long 6636683323 refactor: remove remaining default layer aliases (#34660) 2026-06-30 13:17:34 -04:00
opencode-agent[bot] 3af9f64265 chore: generate 2026-06-30 17:07:39 +00:00
James Long 5a23bdcb69 refactor(core): remove domain layer exports (#34625) 2026-06-30 13:05:15 -04:00
opencode-agent[bot] e78726854a chore: generate 2026-06-30 16:19:11 +00:00
James Long 472d0f376e refactor(core): remove infrastructure layer exports (#34624) 2026-06-30 12:17:12 -04:00
Brendan Allan c8fde60ad4 fix(app): hide missing workspace branch (#34649) 2026-07-01 00:05:09 +08:00
opencode-agent[bot] a4b6047e64 fix(core): drop legacy config filename (#34645)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 11:03:53 -05:00
James Long 6387f955cf refactor(core): remove session layer exports (#34623) 2026-06-30 11:44:08 -04:00
James Long 5691f36e98 refactor(core): remove tool layer exports (#34622) 2026-06-30 11:18:41 -04:00
opencode-agent[bot] 74e7a378bb chore: generate 2026-06-30 15:02:51 +00:00
usrnk1 fced9c5a2f feat(desktop): polish tooltips and session search (#34632) 2026-06-30 23:00:25 +08:00
opencode-agent[bot] 20445ca031 fix(cli): hide auto approval aliases in help (#34641)
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 14:52:57 +00:00
James Long 6fc9405618 refactor(core): route aggregate layers through nodes (#34621) 2026-06-30 10:46:44 -04:00
James Long c86066efef test(opencode): update help snapshots (#34639) 2026-06-30 10:22:21 -04:00
usrnk1 fa9ba2938e feat(desktop): make error view draggable (#34627) 2026-06-30 22:12:43 +08:00
usrnk1 63e24880e5 feat(desktop): polish inline session title editing (#34607) 2026-06-30 22:12:22 +08:00
opencode-agent[bot] eaf42c4ab0 chore: update nix node_modules hashes 2026-06-30 13:55:24 +00:00
Aiden Cline 8e10ab0aa7 chore: bump venice provider (#34629) 2026-06-30 08:40:09 -05:00
runvip b329fcceb2 fix(docs): fix Russian translation for index.mdx (#34001)
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
2026-06-30 16:47:44 +05:30
opencode-agent[bot] bd83177c48 chore: generate 2026-06-30 11:07:49 +00:00
Brendan Allan 06cb0de0b1 feat(app): autocomplete mcp resources (#34597) 2026-06-30 19:05:53 +08:00
opencode-agent[bot] 3ca89ac796 fix(app): autocomplete configured references (#34308)
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-06-30 18:45:37 +08:00
Brendan Allan 3aa2860058 fix(app): restore prompt cursor on focus (#34175) 2026-06-30 18:40:20 +08:00
usrnk1 ad63cf1590 feat(app): hide separators around active tabs (#34591) 2026-06-30 18:38:42 +08:00
Brendan Allan aa56750f8b fix(desktop): persist last active url (#34595) 2026-06-30 18:32:30 +08:00
opencode-agent[bot] 4aaed42640 fix(app): preserve macos titlebar inset (#34594)
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 18:17:55 +08:00
usrnk1 f69f677a32 feat(desktop): add hover background to session title and single-click edit (#34589) 2026-06-30 17:46:52 +08:00
opencode-agent[bot] 8289883de8 chore: generate 2026-06-30 09:11:40 +00:00
usrnk1 003c22b4a3 fix(desktop): context menu button / tab intermittent issue (#34420)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-06-30 17:09:38 +08:00
opencode-agent[bot] 1982d9832b fix(session-ui): align code copy tooltip with v2 style (#34459)
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 17:01:38 +08:00
opencode-agent[bot] 90f0576222 fix(session-ui): deselect review line on draft cancel (#34585)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-30 16:26:20 +08:00
usrnk1 e34822db6b fix(desktop): preserve selected model during session promotion (#34466) 2026-06-30 16:23:39 +08:00
Frank b19ae6547f sync 2026-06-30 03:23:50 -04:00
opencode-agent[bot] 6b074ed718 fix(app): suppress middle-click tab auxclick (#34355)
deploy / deploy (push) Has been cancelled
nix-eval / nix-eval (push) Has been cancelled
publish / version (push) Has been cancelled
publish / build-cli (push) Has been cancelled
publish / sign-cli-windows (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Has been cancelled
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Has been cancelled
publish / publish (push) Has been cancelled
storybook / storybook build (push) Has been cancelled
docs-locale-sync / sync-locales (push) Has been cancelled
generate / generate (push) Has been cancelled
typecheck / typecheck (push) Has been cancelled
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 16:32:25 +10:00
opencode-agent[bot] e687eb936b fix(app): register export logs globally (#34352)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 16:31:09 +10:00
opencode-agent[bot] 797cb530e5 chore: generate 2026-06-30 05:09:55 +00:00
Dax 0a5bed2bc2 feat(tui): add yolo permission mode (#33279)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-06-30 00:08:29 -05:00
opencode-agent[bot] ba41dadc91 ci: skip issue actions for team authors (#34556)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-06-29 23:48:58 -05:00
James Long 451876b0bd refactor(opencode): remove core service layer exports (#34518) 2026-06-29 23:32:16 -04:00
James Long 91561bbdfb refactor(opencode): remove app service layer exports (#34517) 2026-06-29 23:03:57 -04:00
opencode-agent[bot] 018ce34b39 revert(core): skip fff in node runtime (#34549)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 12:58:57 +10:00
opencode-agent[bot] ce82b21073 chore: generate 2026-06-30 02:55:10 +00:00
James Long 1f37c26574 refactor(opencode): use layer nodes in remaining harnesses (#34516) 2026-06-29 22:53:42 -04:00
opencode-agent[bot] cb3e28d207 fix(app): collapse review pane by default (#34165)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 12:47:00 +10:00
opencode-agent[bot] 8f1e13f299 fix(core): skip fff in node runtime (#34353)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-30 12:38:47 +10:00
Luke Parker 2fe68b5e91 fix(ui): prevent tool status blank frame (#34547)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-30 12:26:04 +10:00
opencode-agent[bot] 2ace237938 chore: generate 2026-06-30 01:23:03 +00:00
James Long 5b62211167 refactor(opencode): build runtimes from layer nodes (#34515) 2026-06-29 21:21:28 -04:00
Frank 60b6229c4e zen: track region
deploy / deploy (push) Has been cancelled
2026-06-29 19:57:13 -04:00
opencode-agent[bot] 4caec80e67 chore: generate 2026-06-29 23:03:24 +00:00
Frank e9d4ca7e0f zen: track region 2026-06-29 18:57:32 -04:00
Aiden Cline d71454c701 fix(core): expose models.dev modes as models (#34521) 2026-06-29 17:51:06 -05:00
Dustin Deus 884c256033 fix(console): cancel upstream provider requests (#34467)
Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box>
2026-06-29 23:28:04 +02:00
opencode-agent[bot] 78235385dd chore: generate 2026-06-29 20:53:48 +00:00
Aiden Cline fd213e6df6 fix(mcp): prefer content over structured output (#34505) 2026-06-29 15:51:52 -05:00
James Long 3726052307 refactor(opencode): use layer nodes in server tests (#34503) 2026-06-29 16:50:59 -04:00
opencode-agent[bot] 7d33a6f7c9 chore: generate 2026-06-29 20:36:56 +00:00
James Long 7a035d7fc0 refactor(opencode): bind instance bootstrap node (#34502) 2026-06-29 16:35:10 -04:00
opencode-agent[bot] 9151af7045 chore: generate 2026-06-29 20:24:42 +00:00
James Long 15bcbb1d7a refactor(opencode): migrate session tests to layer nodes (#34494) 2026-06-29 16:22:50 -04:00
opencode-agent[bot] 4d3294727c chore: generate 2026-06-29 20:07:10 +00:00
James Long aae0e89519 refactor(opencode): use layer nodes in plugin tests (#34495) 2026-06-29 16:04:58 -04:00
opencode-agent[bot] 93a7b4ab76 chore: generate 2026-06-29 19:58:09 +00:00
James Long 0ebe74b625 refactor(opencode): migrate llm tests to layer nodes (#34479) 2026-06-29 15:55:58 -04:00
James Long b9dae8593c refactor(opencode): migrate compaction and workspace tests to layer nodes (#34478) 2026-06-29 15:42:52 -04:00
opencode-agent[bot] d8f9388610 chore: generate 2026-06-29 18:52:55 +00:00
James Long be14739cce refactor(core): convert config tests to nodes (#34474) 2026-06-29 14:51:13 -04:00
James Long 762588c251 refactor(core): convert prompt tests to nodes (#34470) 2026-06-29 14:25:33 -04:00
opencode-agent[bot] d6e54e9042 chore: generate 2026-06-29 17:58:20 +00:00
James Long d4fd528152 refactor(core): convert more opencode tests to nodes (#34464) 2026-06-29 13:56:28 -04:00
opencode-agent[bot] de185559dd chore: generate 2026-06-29 16:58:51 +00:00
James Long bc5ce5eab1 refactor(core): convert opencode tests to nodes (#34453) 2026-06-29 12:56:44 -04:00
opencode-agent[bot] b10d617c80 chore: generate 2026-06-29 16:17:54 +00:00
Shoubhit Dash 18466b8020 feat(llm): add tool schema projections (#34454) 2026-06-29 21:45:42 +05:30
opencode-agent[bot] 71ec022b47 chore: generate 2026-06-29 15:50:24 +00:00
Shoubhit Dash f7eeb08942 fix(llm): narrow raw overlays (#34448) 2026-06-29 21:18:06 +05:30
opencode-agent[bot] 9205dfe724 chore: generate 2026-06-29 15:37:20 +00:00
James Long a3776429aa refactor(core): finish test layer node conversion (#34385) 2026-06-29 11:35:17 -04:00
opencode-agent[bot] c0e43c0c65 chore: generate 2026-06-29 13:57:06 +00:00
Shoubhit Dash 08c5a2a5e8 feat(llm): enforce request precedence (#34440) 2026-06-29 19:24:37 +05:30
opencode-agent[bot] 7077c70d60 chore: generate 2026-06-29 13:41:59 +00:00
Shoubhit Dash 1fd8bf526d feat(llm): add model defaults and compatibility data (#34436) 2026-06-29 19:10:00 +05:30
opencode-agent[bot] 6d9539f469 fix: exempt org issues from compliance close (#34431) 2026-06-29 12:59:30 +00:00
Shoubhit Dash e5101d9651 test(llm): lock event reducer laws (#34423) 2026-06-29 17:22:46 +05:30
Shoubhit Dash b0151e1d02 test(llm): verify generate reducer law (#34418) 2026-06-29 17:01:45 +05:30
opencode-agent[bot] c3637753bc chore: generate 2026-06-29 10:58:22 +00:00
Shoubhit Dash 48fc9e3cc3 feat(llm): add response reducer (#34417) 2026-06-29 16:26:33 +05:30
opencode-agent[bot] 82a482b36d chore: generate 2026-06-29 10:45:44 +00:00
Aarav Sareen 7fac84319d feat(app): align slash popover to v2 tokens (#34286)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
2026-06-29 18:44:18 +08:00
Aarav Sareen 0a5e617da8 feat(app): update message part ui to v2 (#34394) 2026-06-29 18:43:06 +08:00
Jack b5f92c9f48 docs: fix Kimi K2.7 Go model ID (#34413) 2026-06-29 18:39:43 +08:00
Aarav Sareen 2070fd9bc2 feat(app): improve projects sidebar reactivity (#34391)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-06-29 18:20:26 +08:00
opencode-agent[bot] 48dc05eee7 chore: generate 2026-06-29 10:13:23 +00:00
Aarav Sareen 84bb706537 feat(app): new timeline header (#34192) 2026-06-29 18:11:32 +08:00
opencode-agent[bot] be8cfa7e08 chore: generate 2026-06-29 09:51:40 +00:00
Aarav Sareen 56a789d926 feat(app): show loader on session hover (#34224) 2026-06-29 17:50:14 +08:00
Aarav Sareen 5409151ad4 feat(app): sticky session list header (#34220)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-06-29 17:49:10 +08:00
Frank f90d154465 zen: budget 2026-06-29 05:46:56 -04:00
Frank 078385d386 zen: budget 2026-06-29 05:46:56 -04:00
Filip beaaa174ea fix(app): disable empty server chevron (#34292) 2026-06-29 15:28:01 +08:00
opencode-agent[bot] fb59606bb4 test(core): fix layer node replacement type expectation (#34386)
deploy / deploy (push) Has been cancelled
nix-eval / nix-eval (push) Has been cancelled
publish / version (push) Has been cancelled
publish / build-cli (push) Has been cancelled
publish / sign-cli-windows (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Has been cancelled
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Has been cancelled
publish / publish (push) Has been cancelled
generate / generate (push) Has been cancelled
typecheck / typecheck (push) Has been cancelled
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-29 00:03:02 -05:00
opencode-agent[bot] 846d548154 chore: generate 2026-06-29 03:49:43 +00:00
James Long 84336e4f91 refactor(core): refine layer node replacements (#34377) 2026-06-28 23:48:18 -04:00
OpeOginni 01a5c69244 feat(tui): integrate ServerAuth headers into transport configuration for external served TUI thread (#29876) 2026-06-28 19:24:27 -05:00
opencode-agent[bot] d78f91afeb chore: generate 2026-06-28 23:36:32 +00:00
Brendan Allan 33762292f7 fix(app): wrap model.set in startTransition (#34351) 2026-06-29 07:35:09 +08:00
opencode-agent[bot] 5d6aa3b41a chore: generate 2026-06-28 23:18:35 +00:00
OpeOginni 683aca5dbe feat(desktop): Display stored totals for Tokens and Cost in Desktop Session Context (#28887) 2026-06-28 23:17:08 +00:00
Aiden Cline 92025e9a47 fix(mcp): clarify debug oauth probe (#34350) 2026-06-28 18:16:29 -05:00
Max Anderson 411e053572 fix(mcp): reconnect after OAuth even when server is disabled
Closes #33915
2026-06-28 18:07:10 -05:00
Frank b862d178bf zen: update alert role
deploy / deploy (push) Has been cancelled
2026-06-28 18:53:32 -04:00
Frank bda0ddc207 zen: new inference 2026-06-28 13:48:42 -04:00
Brendan Allan 58ba99e505 fix(desktop): avoid destroyed window permission checks (#34300) 2026-06-28 20:06:15 +08:00
Filip 6ee817d041 fix(app): disable add project when given server is offline (#34294) 2026-06-28 18:45:05 +08:00
Kit Langton dfeb1b5051 feat(client): generate complete protocol client (#34164)
deploy / deploy (push) Has been cancelled
nix-eval / nix-eval (push) Has been cancelled
publish / version (push) Has been cancelled
publish / build-cli (push) Has been cancelled
publish / sign-cli-windows (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Has been cancelled
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Has been cancelled
publish / publish (push) Has been cancelled
containers / build (push) Has been cancelled
generate / generate (push) Has been cancelled
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404, x86_64-linux) (push) Has been cancelled
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404-arm, aarch64-linux) (push) Has been cancelled
nix-hashes / compute-hash (macos-15-intel, x86_64-darwin) (push) Has been cancelled
nix-hashes / compute-hash (macos-latest, aarch64-darwin) (push) Has been cancelled
storybook / storybook build (push) Has been cancelled
typecheck / typecheck (push) Has been cancelled
nix-hashes / update-hashes (push) Has been cancelled
2026-06-27 22:25:40 -04:00
opencode-agent[bot] 61a7f6db35 chore: update nix node_modules hashes 2026-06-28 01:20:08 +00:00
Dax 6446b8ae3b fix(sdk): preserve V2Event name for SSE streams (#34171) 2026-06-27 21:05:47 -04:00
Brendan Allan ae53163cad fix(app): transition draft project updates (#34252) 2026-06-28 02:58:07 +08:00
opencode-agent[bot] 41202819a4 chore: generate 2026-06-27 18:11:37 +00:00
James Long a31698f99b refactor(core): move more tests to nodes (#34248) 2026-06-27 18:10:07 +00:00
opencode-agent[bot] 6248542c49 chore: generate 2026-06-27 17:20:42 +00:00
James Long d25c91e5eb refactor(core): move session test to nodes (#34245) 2026-06-27 13:19:14 -04:00
James Long 5d63020dcd test(core): cover app node builder graphs (#34244) 2026-06-27 16:54:18 +00:00
opencode-agent[bot] 062f54590e chore: generate 2026-06-27 16:30:47 +00:00
James Long a76c6918d2 refactor(core): rename app node modules (#34238) 2026-06-27 12:29:21 -04:00
Ben Guthrie 2b91a6f210 fix(tui): register prompt.skills keybinds (#34180) 2026-06-27 11:06:14 -05:00
opencode-agent[bot] 10579cceb2 chore: generate 2026-06-27 15:38:27 +00:00
Aarav Sareen 25702e01ce feat(app): new debug bar (#34237) 2026-06-27 15:37:02 +00:00
James Long ecc5c44d9a refactor(core): make node build bind maps conditionally (#34218) 2026-06-27 11:09:07 -04:00
Aarav Sareen f5a0b920a2 feat(app): minor visual updates (#34205) 2026-06-27 18:07:17 +08:00
Brendan Allan 2caa016fe1 fix(app): batch new session tab navigation (#34196) 2026-06-27 17:13:09 +08:00
opencode-agent[bot] 6861fedd09 chore: generate 2026-06-27 07:43:42 +00:00
Luke Parker 3d072112ce fix(app): reconcile session pages with concurrent events (#34042) 2026-06-27 17:42:17 +10:00
OpeOginni bdfea046db fix(desktop): recognize normal auth metadata input prompts in connect provider dialog (#33024)
deploy / deploy (push) Has been cancelled
nix-eval / nix-eval (push) Has been cancelled
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404, x86_64-linux) (push) Has been cancelled
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404-arm, aarch64-linux) (push) Has been cancelled
nix-hashes / compute-hash (macos-15-intel, x86_64-darwin) (push) Has been cancelled
nix-hashes / compute-hash (macos-latest, aarch64-darwin) (push) Has been cancelled
nix-hashes / update-hashes (push) Has been cancelled
publish / version (push) Has been cancelled
publish / build-cli (push) Has been cancelled
publish / sign-cli-windows (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404-arm platform_flag:--linux --arm64 target:aarch64-unknown-linux-gnu]) (push) Has been cancelled
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Has been cancelled
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Has been cancelled
publish / publish (push) Has been cancelled
storybook / storybook build (push) Has been cancelled
docs-locale-sync / sync-locales (push) Has been cancelled
generate / generate (push) Has been cancelled
typecheck / typecheck (push) Has been cancelled
2026-06-27 05:19:59 +00:00
Aarav Sareen a2d08fb63b feat(app): update home screen alignment + markdown styles (#34172) 2026-06-27 13:00:52 +08:00
opencode-agent[bot] 5a55135d89 fix(ui): make select hover feedback immediate (#34121)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-27 12:47:32 +08:00
opencode-agent[bot] 8870d36e0f fix(app): space home sessions from scrollbar (#34132)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-27 12:46:33 +08:00
opencode-agent[bot] eb923c27ca chore: update nix node_modules hashes 2026-06-27 04:27:56 +00:00
opencode-agent[bot] 9903abc704 fix(opencode): allow empty provider config default (#34167)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-26 23:19:42 -05:00
opencode-agent[bot] 225a1fbf35 chore: generate 2026-06-27 04:10:30 +00:00
Dax Raad 93159bccbf feat(core): port v2 runtime fixes onto dev
Cherry-picks the packages/core changes from the v2 branch onto dev:
- combined ordered stdout/stderr in AppProcess + bash structured output
- edit/apply-patch return FileDiff info with status and line stats
- ignore no-op model switches; record reasoning timestamps
- return unexpected local tool defects to the model and continue
- keep OAuth account metadata out of request bodies
- nest OpenAI reasoning effort/summary options
- load OpenCode provider config asynchronously; batch plugin boot
- export latest public event manifest

Includes the supporting schema reasoning time field and regenerated
client/SDK types.
2026-06-27 00:07:25 -04:00
opencode-agent[bot] fab8ec4f54 fix(app): migrate composer tooltips to v2 (#34147)
Co-authored-by: Jay V <air@live.ca>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2026-06-27 14:01:45 +10:00
opencode-agent[bot] a3035c53ea chore: update nix node_modules hashes 2026-06-27 03:47:28 +00:00
Aiden Cline 36c416e143 fix(mcp): request refresh token scope (#34125) 2026-06-26 22:27:43 -05:00
Aiden Cline e1e0304a96 fix(mcp): surface OAuth completion errors (#34145) 2026-06-26 22:21:16 -05:00
opencode-agent[bot] 71c3a7c8f2 chore: generate 2026-06-27 02:47:33 +00:00
James Long ecdfff5a42 refactor(core): separate out location node functionality and integrate into v2 (#34119) 2026-06-26 22:46:07 -04:00
opencode-agent[bot] 4b948c5d74 fix(app): hide home session archive action (#34136)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-27 02:23:36 +00:00
opencode-agent[bot] cd56c51e2d fix(app): keep bare slash as plain inline code (#34122)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-26 22:21:01 +00:00
opencode-agent[bot] 43e39d7f68 fix(tui): use generated event union (#34118)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-26 18:04:53 -04:00
Frank 7a17925495 zen: new inference
deploy / deploy (push) Has been cancelled
2026-06-26 16:25:07 -04:00
Brendan Allan 5acb2530b4 fix(app): centralize notification state (#34105) 2026-06-26 19:24:33 +00:00
opencode-agent[bot] 44a6787359 chore: generate 2026-06-26 19:18:18 +00:00
Kit Langton 42e6b7db32 feat(sdk): expose live event stream (#34098) 2026-06-26 21:16:33 +02:00
opencode-agent[bot] 2c02f8bace chore: update nix node_modules hashes 2026-06-26 18:53:34 +00:00
Affan Ali 2ec20e576b fix(app): slow tooltip display for models (#30745)
Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com>
2026-06-27 02:53:08 +08:00
opencode-agent[bot] 20f47fec7a chore: generate 2026-06-26 18:51:42 +00:00
Kit Langton 65210f2d97 feat(api): add finite durable session history pages (#34097) 2026-06-26 14:49:47 -04:00
Brendan Allan af0b7ffae7 fix(app): animate prompt selectors after loading (#34101) 2026-06-27 02:36:01 +08:00
799 changed files with 25162 additions and 67759 deletions
-7
View File
@@ -8,13 +8,6 @@ inputs:
runs:
using: "composite"
steps:
# node-gyp@latest (invoked via bunx for native install scripts) requires Node >=22;
# some runner images ship an older system Node on PATH
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
- name: Get baseline download URL
id: bun-url
shell: bash
+2 -7
View File
@@ -6,7 +6,6 @@ on:
branches:
- ci
- dev
- v2
- beta
- fix/npm-native-binary-install
- snapshot-*
@@ -32,9 +31,6 @@ permissions:
contents: write
packages: write
env:
OPENCODE_CHANNEL: ${{ (github.ref_name == 'v2' && 'next') || '' }}
jobs:
version:
runs-on: blacksmith-4vcpu-ubuntu-2404
@@ -126,7 +122,7 @@ jobs:
- build-cli
- version
runs-on: blacksmith-4vcpu-windows-2025
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
if: github.repository == 'anomalyco/opencode'
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
@@ -225,7 +221,7 @@ jobs:
needs:
- build-cli
- version
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
if: github.repository == 'anomalyco/opencode'
continue-on-error: false
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
@@ -451,7 +447,6 @@ jobs:
path: packages/opencode/dist
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
if: github.ref_name != 'v2'
with:
name: opencode-cli-signed-windows
path: packages/opencode/dist
+5 -2
View File
@@ -4,7 +4,6 @@ on:
push:
branches:
- dev
- v2
pull_request:
workflow_dispatch:
@@ -75,9 +74,13 @@ jobs:
working-directory: packages/client
run: bun run check:generated
- name: Run HttpApi exerciser gates
if: runner.os == 'Linux'
working-directory: packages/opencode
run: bun run test:httpapi
e2e:
name: e2e (${{ matrix.settings.name }})
if: github.ref_name != 'v2' && github.head_ref != 'v2'
strategy:
fail-fast: false
matrix:
+2 -2
View File
@@ -2,9 +2,9 @@ name: typecheck
on:
push:
branches: [dev, v2]
branches: [dev]
pull_request:
branches: [dev, v2]
branches: [dev]
workflow_dispatch:
jobs:
-36
View File
@@ -1,36 +0,0 @@
export default {
id: "Orchestrator",
setup: async (ctx) => {
await ctx.agent.transform((agents) => {
agents.update("orchestrator", (agent) => {
agent.description = "Coordinates work by delegating implementation tasks to the minion subagent."
agent.mode = "primary"
agent.system = [
"You are Orchestrator, the primary coordinating agent for this repository. You do meta work only: you coordinate, brief, and synthesize — you do not perform the work itself.",
"Delegate ALL actual work to the minion subagent — implementation, exploration, discovery, searching the codebase, reading files to understand a problem, and even trivial one-line edits. Task size is never a reason to do it yourself, and there is no 'final integration' exception.",
"You are not hard-banned from tools, but direct tool use is reserved for coordination overhead: a quick peek to phrase a better brief, a fast read-only check to verify a minion's reported result, or answering a question about coordination state. If a tool call is producing the answer or the artifact the user asked for, that call belongs to a minion, not you.",
"Exploration is work. If the user asks how something works or where something lives, delegate the investigation to a minion rather than exploring yourself.",
"Always start minion subagents in the background. Even if you have nothing else to coordinate right now, the user may assign you new work while a Minion runs, and you must stay free to receive it. Never poll; you will be notified when they finish.",
"Give each minion a clear, self-contained brief: the goal, constraints, expected output, and any files or context already known from the user or previous minion reports.",
"Synthesize minion results, decide next steps, and report back concisely.",
].join("\n")
})
agents.update("minion", (agent) => {
agent.description = "Subagent that executes focused tasks delegated by Orchestrator."
agent.mode = "subagent"
agent.model = { providerID: "opencode", id: "glm-5.2" }
agent.system = [
"You are minion, a focused execution subagent for this repository.",
"Complete the specific task delegated to you by Orchestrator using the available tools.",
"Inspect the codebase before making assumptions, make targeted changes when requested, and verify your work when feasible.",
"Follow the repository's AGENTS.md conventions: respect the style guide, run `bun typecheck` from the affected package directory after code changes, never run tests from the repo root, and do not modify packages/opencode unless the task explicitly says V1 work.",
"If the task is ambiguous or you hit a blocker, stop and report your findings instead of guessing.",
"Keep your final response concise: summarize what you did, list important files changed or findings, and call out blockers or verification gaps.",
"Do not delegate to other subagents; execute the assigned work yourself.",
].join("\n")
agent.permissions.push({ action: "subagent", resource: "*", effect: "deny" })
})
})
},
}
-144
View File
@@ -1,144 +0,0 @@
---
name: debug-opencode
description: Use when interactively running, debugging, or verifying opencode's own V2 CLI/TUI or server during development in this repo — starting the dev TUI, driving it with termctrl, comparing V2 against the legacy TUI, hitting the V2 server/API directly, reading log files, or attaching Bun's inspector.
---
# Debugging opencode itself
Workflow for interactively exercising the V2 CLI/TUI and server while developing in this repo. All commands below run from `packages/cli` unless noted otherwise.
## Migration context
- The TUI is being ported from legacy APIs to the new V2 APIs. New and migrated TUI behavior should use `sdk.client.v2` and the location-scoped data in `packages/tui/src/context/data.tsx` instead of adding dependencies on legacy sync state.
- Preserve established TUI behavior unless the task intentionally changes it. When behavior, copy, keyboard interaction, or layout is unclear, compare the local V2 TUI with the latest released legacy TUI (see "Comparing V2 against the legacy TUI" below) rather than guessing.
## Server/client model
opencode V2 is a client/server system, not a single monolithic process:
- **Server process** runs the Effect HTTP API (`packages/server`) and owns all domain state: sessions, database, plugins, permissions, Location services. It's started by the `serve` command (`packages/cli/src/commands/handlers/serve.ts`).
- **TUI process** is a separate process that runs no application logic itself — it's an HTTP/SSE client of the server via the generated SDK (`createOpencodeClient` / `sdk.client.v2`).
- **Discovery**: CLI processes find the shared server through a JSON registration file at `~/.local/state/opencode/service.json` (or `service-local.json` for the local/dev channel) containing `{id, version, url, pid}`. A separate password file under `~/.config/opencode/service.json` provides HTTP Basic auth. Before reusing a registration, the client calls `GET /health` to confirm the server is alive, authenticated, and version-compatible.
- **Sharing**: because of this registration/health-check dance, many concurrent `opencode`/TUI invocations converge on one shared background daemon rather than each spawning their own. If no compatible healthy daemon is found, a new one is spawned detached (`serve --service`) and registers itself.
- **`bun dev service start|status|stop|restart`** manages this shared background daemon's lifecycle directly — useful when you need to force a fresh server, confirm one is running, or kill a stuck one.
- **Standalone mode** (`--standalone`) opts a single invocation out of the shared daemon: it spawns a private one-off `serve --stdio --port 0` child tied to that invocation's lifetime, with its own random password. Use this to isolate a debugging session from your other running opencode sessions.
- Every log line is tagged `role=server` or `role=cli` and a per-process `run=<id>`, so you can distinguish server-side and client-side activity in one shared log file (see "Logs" below) even when both roles are interleaved from concurrent processes.
## Starting the dev TUI
- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI.
- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server.
## Interactive debugging with termctrl
- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots.
- Use a dedicated session name and do not reuse or kill an unrelated session.
```bash
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
termctrl wait opencode-v2-dev "Ask anything" --timeout 20000
termctrl show opencode-v2-dev
```
- Wait for visible text before interacting instead of relying on fixed sleeps. Use the text expected from the screen under test, such as `Ask anything` or `Connect a provider`.
- Drive the running TUI with `termctrl send`. Prefix typed input with `text:` and send control keys separately so the interaction matches real terminal input.
```bash
termctrl send opencode-v2-dev 'text:example prompt' enter
termctrl send opencode-v2-dev ctrl-c
```
- Use `termctrl show` after each meaningful interaction and inspect the full visible screen for rendering errors, stale state, error toasts, and unexpected exits.
- Save PNG evidence for every user-visible bug and fix. Do not save text captures; inspect the rendered PNG. Write temporary captures outside the repository unless the artifact is intended to be committed.
```bash
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2-tui.png
```
- For resize-sensitive changes, resize the viewport, wait for the expected content, and capture the screen again:
```bash
termctrl resize opencode-v2-dev --cols 100 --rows 30
termctrl show opencode-v2-dev
```
- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change.
- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
- Always clean up the Terminal Control session when the check is complete:
```bash
termctrl stop opencode-v2-dev
```
## Comparing V2 against the legacy TUI
Run both versions in separate Terminal Control sessions and save PNG-only captures at equivalent states:
```bash
# From packages/cli: local V2 TUI
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
# Released legacy TUI behavior reference
termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2.png
termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png
```
- Use the same viewport and send equivalent inputs to both sessions before comparing screenshots. The released CLI is a behavioral reference, not a source of V2 API design; keep the local implementation on V2 endpoints.
- Stop both sessions after comparison: `termctrl stop opencode-v2-dev` and `termctrl stop opencode-legacy`.
## Server/API debugging
- Use `bun dev api --help` from `packages/cli` to inspect the API debugging command. It sends one request to the V2 server using the same daemon discovery/auth path as the CLI.
- Use `bun dev api` to introspect the server-side data backing the TUI. This is useful when debugging UI bugs: compare what the screen renders with the raw session, message, event, agent, or health data returned by the API to determine whether the bug is in the server state, the client data layer, or the TUI rendering.
- `bun dev api` accepts either an OpenAPI operation ID or a raw HTTP method plus path:
```bash
bun dev api get /health
bun dev api get /openapi.json
bun dev api <operationId> --param key=value
```
- Pass JSON request bodies with `--data`/`-d`; the command sets `content-type: application/json` automatically unless you provide a header. Add extra headers with `--header`/`-H name:value`.
- If no compatible background server is registered, `bun dev api` starts one through the daemon service. Use `bun dev service status`, `bun dev service restart`, and `bun dev service stop` when you need explicit lifecycle control.
- Prefer raw method/path calls for quick server debugging and operation IDs when exercising documented OpenAPI routes with path or query parameters.
## Logs
- Log files live under `~/.local/share/opencode/log/`. In a local/dev checkout the active file is `opencode-local.log`; `opencode.log` is used for non-local (released) channel installs. Both are append-only, shared across every CLI and server process on the machine.
- Each line is structured `key=value` text: `timestamp`, `level`, `run=<id>` (per-process run ID), `message`, and a `role=cli` or `role=server` tag. Use `run=` to isolate one process's activity and `role=` to separate client-side from server-side log lines, since a shared daemon interleaves many processes' output in one file.
- Tail the live file while reproducing an issue instead of guessing from stale output:
```bash
tail -f ~/.local/share/opencode/log/opencode-local.log
```
- Filter to one run or role when the file is noisy:
```bash
grep 'run=8fc3b1d5' ~/.local/share/opencode/log/opencode-local.log
grep 'role=server' ~/.local/share/opencode/log/opencode-local.log
```
- `OPENCODE_LOG_LEVEL` controls verbosity (default `INFO`); set it before starting `bun dev` or `serve` to get `DEBUG` output for a specific repro.
- `OPENCODE_PRINT_LOGS=1` additionally tees log output to stderr of the process that emitted it, which is useful when a process fails before you'd think to check the shared log file.
- `termctrl logs <session>` surfaces stdout/stderr for a Terminal Control session specifically (e.g. inspector output or startup failures before the TUI renderer starts) — use the log file above for anything emitted by a separate server/daemon process instead.
## Debugger
- To debug the V2 CLI or TUI with Bun's inspector, launch the CLI entrypoint through Terminal Control with an inspector URL, then attach a debugger to that URL:
```bash
termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \
bun run --inspect=ws://localhost:6499/ src/index.ts
```
- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches.
- Use `termctrl logs opencode-v2-debug` for inspector output or startup failures emitted before the TUI renderer starts. Use `termctrl show` for the visible full-screen TUI.
## Verification
- Run `bun typecheck` from `packages/cli` after CLI adapter changes.
- Run `bun typecheck` and `bun test` from `packages/tui` after shared TUI changes. Do not run tests from the repository root.
- Treat automated checks and Terminal Control smoke tests as complementary. For user-visible changes, verify initial render, the changed interaction, Ctrl-C exit behavior, and save a screenshot of the corrected state.
-31
View File
@@ -1,31 +0,0 @@
---
name: sample-skill
description: Use when the user says sample skill, skill demo, or asks how an opencode SKILL.md should be structured; demonstrates a tiny project-local skill with practical assistant workflow guidance.
---
# Sample Skill
This is a minimal project-local opencode skill. It exists as a reference for how a skill is structured and as a tiny reusable workflow the assistant can load when the user asks for a skill example.
## When To Use
- Use when the user asks for a sample skill or skill template.
- Use when demonstrating the required `SKILL.md` frontmatter and body format.
- Do not use for unrelated coding tasks just because a skill exists.
## Workflow
- Confirm the specific outcome the user wants if the request is ambiguous.
- Inspect the relevant files before changing anything.
- Make the smallest correct change.
- Verify the result with a focused read, typecheck, test, or other lightweight check when available.
- Summarize the changed files and any required restart or reload step.
## Example Response Style
When this skill is relevant, keep responses direct and actionable:
```text
I created a project-local skill at .opencode/skills/sample-skill/SKILL.md.
Restart opencode for the new skill to be discovered by future sessions.
```
+3 -6
View File
@@ -1,7 +1,6 @@
- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
- Do not modify `packages/opencode` unless the user explicitly asks for V1 work. `packages/opencode` is the V1 implementation and is present for reference only. New implementation changes should land in the V2 package set: `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required.
- The default branch in this repo is `dev`.
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
@@ -155,10 +154,8 @@ const table = sqliteTable("session", {
- Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry.
- Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op.
- Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
- Preserve one explicit `llm.stream(request)` call per step and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
- Preserve one explicit `llm.stream(request)` call per provider turn and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
- Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary.
- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe step boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's step allowance; a batch of steers resets it once.
- One step is one logical LLM call; its durable record covers only the model-visible span. Do not write "provider turn", and do not use bare "turn" for a single call: "turn" is reserved for the future assistant-turn unit containing all steps from prompt promotion until the session would go idle.
- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe provider-turn boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once.
- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
- Keep the System Context algebra and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Checkpoint persistence Session-owned. The runner composes all context producers explicitly in `loadSystemContext`; there is no context registry.
- The durable Applied record is what the model was last told, per source. Reconcile narrates drift as chronological System updates and never rewrites the baseline; only completed compaction rebaselines, and move or committed revert resets the checkpoint. Unavailable sources keep the model's prior belief, blocking only a session's first baseline.
- Keep the System Context algebra, registry, and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned.
+23 -38
View File
@@ -9,7 +9,7 @@ The structured collection of contextual facts presented to the model as initial
_Avoid_: System prompt
**Session History**:
The projected chronological conversation selected for a **Step** after applying the active compaction and **Context Epoch** cutoffs.
The projected chronological conversation selected for a provider turn after applying the active compaction and **Context Epoch** cutoffs.
_Avoid_: Session Context
**Context Source**:
@@ -31,13 +31,13 @@ The full **System Context** rendered at the start of a **Context Epoch**.
_Avoid_: Live system prompt
**Context Snapshot**:
The overwriteable model-hidden JSON state used to compare each **Context Source** with the value last admitted to a **Step**.
The overwriteable model-hidden JSON state used to compare each **Context Source** with the value last admitted to a provider turn.
**Unavailable Context**:
An expected temporary inability to observe a **Context Source** value; the runtime retains its prior effective state and emits no update, or omits it until first successfully loaded.
**Safe Step Boundary**:
The point immediately before a provider request, after durable input promotion and any required tool settlement, where context changes may be admitted chronologically.
**Safe Provider-Turn Boundary**:
The point immediately before a provider call, after durable input promotion and any required tool settlement, where context changes may be admitted chronologically.
**Admitted Prompt**:
A durable user input accepted into the Session inbox but not yet included in **Session History**.
@@ -45,24 +45,11 @@ A durable user input accepted into the Session inbox but not yet included in **S
**Prompt Promotion**:
The durable transition that removes an **Admitted Prompt** from pending input and appends its user message to **Session History**.
**Step**:
One logical LLM call spanning pre-flight context checkpoint preparation, input promotion, request build, and compaction check; the provider stream; and tool settlement.
_Avoid_: provider turn, turn (unqualified)
**Physical Attempt**:
One actual provider request on the wire in service of a **Step**; most Steps have one Physical Attempt, while overflow-triggered compaction recovery may give one Step two.
**Assistant Turn**:
A reserved name for the not-yet-modeled unit containing all **Steps** from prompt promotion until the assistant yields the floor; do not reify it until something durable needs it.
**Settlement**:
The terminal transition for a unit of work: Step and tool settlement are durable, while drain and execution settlement are coordinator-observed.
**Execution**:
One session-scoped coordinator busy period from first wake until idle. An Execution is process-local coordination rather than a durable domain entity.
**Provider Turn**:
One request to a model provider and the response projected from that request.
**Session Drain**:
One process-local execution span that promotes eligible input and runs required **Steps** until no immediate continuation remains. A Session Drain has no durable identity or transcript boundary.
One process-local execution span that promotes eligible input and runs required **Provider Turns** until no immediate continuation remains. A Session Drain has no durable identity or transcript boundary.
**Model Tool Output**:
The bounded projection of a Core-executed tool result persisted in Session history and replayed to the model. A tool may shape this projection semantically, but the Tool Registry enforces the final size limit.
@@ -102,25 +89,23 @@ _Avoid_: Response envelope
- A **System Context** is an opaque carrier composed from zero or more **Context Sources**.
- **Session History** contains projected conversational messages and admitted **Mid-Conversation System Messages**; the active **Baseline System Context** remains separate provider-request state.
- The **System Context Registry** uses stable-keyed scoped contributions to assemble the current **System Context**; contributor removal naturally removes its sources at the next **Safe Step Boundary**.
- The **System Context Registry** uses stable-keyed scoped contributions to assemble the current **System Context**; contributor removal naturally removes its sources at the next **Safe Provider-Turn Boundary**.
- A changed **Context Source** may produce one **Mid-Conversation System Message** containing its newly effective state.
- A **Mid-Conversation System Message** persists the exact combined rendered text sent to the model.
- The current **Context Snapshot** advances atomically with the corresponding durable **Mid-Conversation System Message**.
- A **Context Snapshot** stores one codec-encoded JSON value and, for removable dynamic sources, a pre-rendered removal message per stable **Context Source** key.
- Changes from multiple **Context Sources** admitted at one safe boundary combine into one **Mid-Conversation System Message**.
- Context changes are sampled and admitted lazily at a **Safe Step Boundary**, never pushed asynchronously when their source changes.
- At a **Safe Step Boundary**, newly promoted user input or settled tool results precede any combined **Mid-Conversation System Message**.
- Context changes are sampled and admitted lazily at a **Safe Provider-Turn Boundary**, never pushed asynchronously when their source changes.
- At a **Safe Provider-Turn Boundary**, newly promoted user input or settled tool results precede any combined **Mid-Conversation System Message**.
- An **Admitted Prompt** is replayable pending input, not yet model-visible **Session History**.
- **Prompt Promotion** atomically consumes the pending inbox entry and appends its model-visible user message.
- Steering prompts promote at the next **Safe Step Boundary** while the current **Session Drain** still requires continuation. Promoting any newly admitted user input resets the selected agent's step allowance; multiple prompts promoted at one boundary reset it once.
- Steering prompts promote at the next **Safe Provider-Turn Boundary** while the current **Session Drain** still requires continuation. Promoting any newly admitted user input resets the selected agent's provider-turn allowance; multiple prompts promoted at one boundary reset it once.
- A queued prompt does not promote while the current **Session Drain** requires continuation. The runner promotes one queued prompt when the Session would otherwise become idle, then reevaluates continuation before promoting another.
- A **Session Drain** is process-local coordination rather than a durable domain entity. Durable recovery must reason from prompts, projected history, physical attempts, and tool state rather than inventing an enclosing execution identity.
- An **Execution** contains one or more **Session Drains**; a **Session Drain** contains one reserved assistant-turn span at a time; that span contains **Steps**; and each **Step** contains one or more **Physical Attempts** plus any tool calls it requires.
- A **Step** record covers only the model-visible span from first assistant output through tool settlement; pre-flight leaves no record, and one Step settles at most one record.
- The first **Step** renders the latest complete **Baseline System Context** and initializes its **Context Snapshot** without emitting a redundant **Mid-Conversation System Message**; unavailable initial context blocks the Step instead of persisting an incomplete baseline.
- A **Session Drain** is process-local coordination rather than a durable domain entity. Durable recovery must reason from prompts, projected history, provider attempts, and tool state rather than inventing an enclosing execution identity.
- The first provider turn renders the latest complete **Baseline System Context** and initializes its **Context Snapshot** without emitting a redundant **Mid-Conversation System Message**; unavailable initial context blocks the turn instead of persisting an incomplete baseline.
- Initial **System Context** preparation precedes the first durable input promotion so an unavailable baseline leaves that input pending and retryable; ordinary reconciliation remains after promotion.
- Compaction starts a new **Context Epoch** with a freshly rendered **Baseline System Context** and **Context Snapshot**; prior **Mid-Conversation System Messages** remain durable audit history but leave projected model history.
- A newly registered core or plugin-defined **Context Source** absent from the current snapshot emits its baseline rendering once at the next **Safe Step Boundary**.
- A newly registered core or plugin-defined **Context Source** absent from the current snapshot emits its baseline rendering once at the next **Safe Provider-Turn Boundary**.
- **Context Source** keys are stable and namespaced; duplicate keys fail composition. `SystemContext.combine(...)` preserves caller order; the **System Context Registry** evaluates producers concurrently and combines them in stable contribution-key order so rendered context remains deterministic.
- Each **Context Source** loader returns one coherent typed value. `SystemContext.make(...)` hides that value type so differently typed sources compose uniformly. Its codec compares and stores that value; its pure renderers produce model-visible baseline, update, and removal text only when needed.
- `SystemContext.initialize(...)` observes a composed **System Context** once and produces a fresh **Baseline System Context** with its **Context Snapshot**.
@@ -128,26 +113,26 @@ _Avoid_: Response envelope
- `SystemContext.replace(...)` renders a fresh generation after completed compaction or another baseline-replacing transition; it reports replacement blocked while previously admitted context is unavailable.
- **Unavailable Context** uses stale-while-revalidate semantics and is distinct from a successfully loaded absence, which may emit removal text.
- Ordinary **Context Source** loaders return values directly; loaders that intentionally use stale-while-revalidate may explicitly return **Unavailable Context**.
- Nested project instruction discovery after successful reads remains a follow-up; when implemented, discovered instructions must be admitted durably at the next **Safe Step Boundary**.
- Nested project instruction discovery after successful reads remains a follow-up; when implemented, discovered instructions must be admitted durably at the next **Safe Provider-Turn Boundary**.
- Location-scoped services naturally re-resolve effective context when a moved session next runs in its destination location.
- Moving a Session clears its active **Context Epoch**, so the destination must initialize a complete baseline before another prompt can promote.
- Instruction discovery, source identity, persistence, and file loading belong to the instruction service; the **System Context** abstraction only composes effectful producers and renders loaded values.
- The first instruction-service slice observes global and upward project `AGENTS.md` files as one ordered aggregate **Context Source** at each **Safe Step Boundary**.
- The first instruction-service slice observes global and upward project `AGENTS.md` files as one ordered aggregate **Context Source** at each **Safe Provider-Turn Boundary**.
- Built-in and instruction context producers register through the **System Context Registry** with stable contribution keys. Plugin-defined context registration and hot-reload lifecycle remain a follow-up built on the same scoped registry seam.
- Selected-agent available-skill guidance is a **Context Source** composed with Location-wide registry sources immediately before Context Epoch admission. It lists only names and descriptions permitted for that agent; skill bodies and locations are exposed only through the permission-checked `skill` tool.
- The selected agent and model are sampled when a **Step** starts. Changes admitted after that boundary apply to the next Step and do not restart the current Step.
- The selected agent and model are sampled when a provider turn starts. Changes admitted after that boundary apply to the next provider turn and do not restart the current turn.
- Selected-agent available-skill guidance remains a **Context Source**. An agent switch that changes that guidance produces a **Mid-Conversation System Message** while preserving the current baseline.
- Local tool authorization and pending permission requests retain the effective agent of the **Step** that issued the call; a later agent switch cannot change that call's policy.
- Context source changes never wake idle sessions; the next naturally scheduled **Safe Step Boundary** loads and compares current values lazily.
- Once admitted, a **Mid-Conversation System Message** remains durable even if the following **Physical Attempt** fails and is replayed unchanged on retry.
- Local tool authorization and pending permission requests retain the effective agent of the provider turn that issued the call; a later agent switch cannot change that call's policy.
- Context source changes never wake idle sessions; the next naturally scheduled **Safe Provider-Turn Boundary** loads and compares current values lazily.
- Once admitted, a **Mid-Conversation System Message** remains durable even if the following provider attempt fails and is replayed unchanged on retry.
- **Mid-Conversation System Messages** remain durable Session-message history; normal user-facing transcript surfaces may hide them.
- The date **Context Source** initially preserves host-local calendar-date behavior; a configured user timezone may replace that default later.
- A **Context Epoch** begins with one immutable **Baseline System Context**.
- A **Baseline System Context** is stored durably and reused verbatim across process restarts within its **Context Epoch**.
- A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix.
- Completed compaction starts a new **Context Epoch** on the next **Physical Attempt**, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history.
- A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next **Step**.
- **Native Continuation Metadata** remains in durable history. Step projection includes it only for a successful exact originating provider/model match; failed Steps and incompatible models omit opaque metadata, while non-empty visible reasoning lowers to ordinary assistant text after a model switch. This conservative relation may widen only when recorded provider tests establish compatibility.
- Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history.
- A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn.
- **Native Continuation Metadata** remains in durable history. Provider-turn projection includes it only for a successful exact originating provider/model match; failed turns and incompatible models omit opaque metadata, while non-empty visible reasoning lowers to ordinary assistant text after a model switch. This conservative relation may widen only when recorded provider tests establish compatibility.
- **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding.
- **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing.
- The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`.
+119 -196
View File
@@ -14,7 +14,6 @@
},
"devDependencies": {
"@actions/artifact": "5.0.1",
"@ast-grep/cli": "0.44.0",
"@tsconfig/bun": "catalog:",
"@types/mime-types": "3.0.1",
"@typescript/native-preview": "catalog:",
@@ -30,7 +29,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@dnd-kit/abstract": "0.5.0",
"@dnd-kit/dom": "0.5.0",
@@ -93,13 +92,12 @@
},
"packages/cli": {
"name": "@opencode-ai/cli",
"version": "1.17.13",
"version": "1.17.12",
"bin": {
"opencode2": "./bin/opencode2.cjs",
"lildax": "./bin/lildax.cjs",
},
"dependencies": {
"@effect/platform-node": "catalog:",
"@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/server": "workspace:*",
@@ -108,28 +106,26 @@
"@opentui/solid": "catalog:",
"@parcel/watcher": "2.5.1",
"effect": "catalog:",
"jsonc-parser": "3.3.1",
"semver": "catalog:",
"solid-js": "catalog:",
},
"devDependencies": {
"@opencode-ai/script": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@types/semver": "catalog:",
"@typescript/native-preview": "catalog:",
},
},
"packages/client": {
"name": "@opencode-ai/client",
"version": "1.17.13",
"dependencies": {
"@opencode-ai/protocol": "workspace:*",
"@opencode-ai/schema": "workspace:*",
},
"devDependencies": {
"@effect/platform-node": "catalog:",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/httpapi-codegen": "workspace:*",
"@opencode-ai/server": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
@@ -142,23 +138,9 @@
"effect",
],
},
"packages/codemode": {
"name": "@opencode-ai/codemode",
"version": "0.0.1",
"dependencies": {
"acorn": "8.15.0",
"effect": "catalog:",
"typescript": "catalog:",
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
},
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -194,7 +176,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -221,7 +203,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/openai": "3.0.48",
@@ -243,7 +225,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -267,7 +249,7 @@
},
"packages/console/support": {
"name": "@opencode-ai/console-support",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@opencode-ai/console-core": "workspace:*",
@@ -287,7 +269,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "1.17.13",
"version": "1.17.12",
"bin": {
"opencode": "./bin/opencode",
},
@@ -318,7 +300,6 @@
"@effect/sql-sqlite-bun": "catalog:",
"@ff-labs/fff-bun": "0.9.4",
"@lydell/node-pty": "catalog:",
"@modelcontextprotocol/sdk": "1.29.0",
"@npmcli/arborist": "9.4.0",
"@npmcli/config": "10.8.1",
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
@@ -340,7 +321,7 @@
"drizzle-orm": "catalog:",
"effect": "catalog:",
"fuzzysort": "3.1.0",
"gitlab-ai-provider": "6.10.0",
"gitlab-ai-provider": "6.9.3",
"glob": "13.0.5",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
@@ -382,7 +363,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@zip.js/zip.js": "2.7.62",
"effect": "catalog:",
@@ -436,7 +417,7 @@
},
"packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -450,7 +431,7 @@
},
"packages/effect-sqlite-node": {
"name": "@opencode-ai/effect-sqlite-node",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"effect": "catalog:",
},
@@ -462,7 +443,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@hono/standard-validator": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -494,7 +475,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -510,7 +491,7 @@
},
"packages/http-recorder": {
"name": "@opencode-ai/http-recorder",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@effect/platform-node": "4.0.0-beta.83",
"@effect/platform-node-shared": "4.0.0-beta.83",
@@ -541,7 +522,7 @@
},
"packages/llm": {
"name": "@opencode-ai/llm",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@opencode-ai/schema": "workspace:*",
"@smithy/eventstream-codec": "4.2.14",
@@ -560,7 +541,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.17.13",
"version": "1.17.12",
"bin": {
"opencode": "./bin/opencode",
},
@@ -597,7 +578,6 @@
"@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
"@opencode-ai/client": "workspace:*",
"@opencode-ai/llm": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/protocol": "workspace:*",
@@ -633,7 +613,7 @@
"drizzle-orm": "catalog:",
"effect": "catalog:",
"fuzzysort": "3.1.0",
"gitlab-ai-provider": "6.10.0",
"gitlab-ai-provider": "6.9.3",
"glob": "13.0.5",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
@@ -691,13 +671,9 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@ai-sdk/provider": "3.0.8",
"@opencode-ai/client": "workspace:*",
"@opencode-ai/llm": "workspace:*",
"@opencode-ai/protocol": "workspace:*",
"@opencode-ai/schema": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"effect": "catalog:",
"zod": "catalog:",
@@ -706,7 +682,6 @@
"@opentui/core": "catalog:",
"@opentui/keymap": "catalog:",
"@opentui/solid": "catalog:",
"@tsconfig/bun": "catalog:",
"@tsconfig/node22": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
@@ -725,7 +700,6 @@
},
"packages/protocol": {
"name": "@opencode-ai/protocol",
"version": "1.17.11",
"dependencies": {
"@opencode-ai/schema": "workspace:*",
"effect": "catalog:",
@@ -734,12 +708,10 @@
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
},
},
"packages/schema": {
"name": "@opencode-ai/schema",
"version": "1.17.11",
"dependencies": {
"effect": "catalog:",
},
@@ -747,7 +719,6 @@
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
"typescript": "catalog:",
},
},
"packages/script": {
@@ -765,7 +736,6 @@
"dependencies": {
"@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/server": "workspace:*",
"effect": "catalog:",
},
@@ -777,7 +747,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -792,11 +762,10 @@
},
"packages/server": {
"name": "@opencode-ai/server",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/protocol": "workspace:*",
"@opencode-ai/simulation": "workspace:*",
"drizzle-orm": "catalog:",
"effect": "catalog:",
},
@@ -808,7 +777,7 @@
},
"packages/session-ui": {
"name": "@opencode-ai/session-ui",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -850,24 +819,9 @@
"vite": "catalog:",
},
},
"packages/simulation": {
"name": "@opencode-ai/simulation",
"version": "1.17.13",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/llm": "workspace:*",
"@opentui/core": "catalog:",
"effect": "catalog:",
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
},
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -880,7 +834,7 @@
},
"packages/stats/app": {
"name": "@opencode-ai/stats-app",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@ibm/plex": "6.4.1",
"@opencode-ai/stats-core": "workspace:*",
@@ -913,7 +867,7 @@
},
"packages/stats/core": {
"name": "@opencode-ai/stats-core",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0",
@@ -932,7 +886,7 @@
},
"packages/stats/server": {
"name": "@opencode-ai/stats-server",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:",
@@ -973,18 +927,15 @@
},
"packages/tui": {
"name": "@opencode-ai/tui",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/simulation": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@opentui/core": "catalog:",
"@opentui/keymap": "catalog:",
"@opentui/solid": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
"clipboardy": "4.0.0",
"diff": "catalog:",
"effect": "catalog:",
@@ -1003,7 +954,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@kobalte/core": "catalog:",
"@pierre/diffs": "catalog:",
@@ -1054,7 +1005,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.17.13",
"version": "1.17.12",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -1254,22 +1205,6 @@
"@anycable/core": ["@anycable/core@0.9.2", "", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="],
"@ast-grep/cli": ["@ast-grep/cli@0.44.0", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "@ast-grep/cli-darwin-arm64": "0.44.0", "@ast-grep/cli-darwin-x64": "0.44.0", "@ast-grep/cli-linux-arm64-gnu": "0.44.0", "@ast-grep/cli-linux-x64-gnu": "0.44.0", "@ast-grep/cli-win32-arm64-msvc": "0.44.0", "@ast-grep/cli-win32-ia32-msvc": "0.44.0", "@ast-grep/cli-win32-x64-msvc": "0.44.0" }, "bin": { "sg": "sg", "ast-grep": "ast-grep" } }, "sha512-Jf4PuP7XjzsMa3m9gYxmzV8KyWZc4w1ZzKe/t0+90wWxmSasQJe6AtMkJxHEi98MGgfAF1nWziqjDd0/6EsBjA=="],
"@ast-grep/cli-darwin-arm64": ["@ast-grep/cli-darwin-arm64@0.44.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bF7euu/hF/cYg4510z8110vh60rrqfrBdsfRqVGd6xqNSPENu7CJnTVN/Z4Nk5U1NM8YKzUD+dYx1ySUJ0CUNQ=="],
"@ast-grep/cli-darwin-x64": ["@ast-grep/cli-darwin-x64@0.44.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0fI9caQGp1dFcmBATNlVytIRdAeYb91v1D2xjMIi1bSX+l8Uj846JUiaimUGBuBZmyFq+BScoWM4RnprEmZMpQ=="],
"@ast-grep/cli-linux-arm64-gnu": ["@ast-grep/cli-linux-arm64-gnu@0.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JB6EUnqEtGGtyg1GqNquld/++1CvaWD7r84IwwhddX1qx0NmDoHyn2mKd8vnQ24Z0RkV3g7y7foMLakELbGtDw=="],
"@ast-grep/cli-linux-x64-gnu": ["@ast-grep/cli-linux-x64-gnu@0.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rNL0LsI682D9EMzfaGVEtZa1xaqTtGb2I+Zk4ZzidX6u+fF7f79wdqyKahKjXzoIrGkuhkoL3gcyLKAtQd9+qg=="],
"@ast-grep/cli-win32-arm64-msvc": ["@ast-grep/cli-win32-arm64-msvc@0.44.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-lqD0MhGQAddh2YoV/brKQ6GVcFLmRiTBwIElutwedaUvRCdasTGukFPYuSWk/iI8Kv19xom6s7l+mGuZ7v+xwQ=="],
"@ast-grep/cli-win32-ia32-msvc": ["@ast-grep/cli-win32-ia32-msvc@0.44.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-ZJrnS+2OkNfwyr6yrN69glP67uybBxDvl9mqZvh1J44vB3OFn9U9c+cVAoZIAo7JD5F4rZNxwyu3gcy4+xuwEA=="],
"@ast-grep/cli-win32-x64-msvc": ["@ast-grep/cli-win32-x64-msvc@0.44.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OJEo7f95YYaSuS1byUB7ZctbzxoA7/wCoAol+pt6pvfdW/8Wq+L1qU28glwx7dQ0HgTsnPZbWpXQwmZpCBHhZg=="],
"@astrojs/check": ["@astrojs/check@0.9.6", "", { "dependencies": { "@astrojs/language-server": "^2.16.1", "chokidar": "^4.0.1", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "bin/astro-check.js" } }, "sha512-jlaEu5SxvSgmfGIFfNgcn5/f+29H61NJzEMfAZ82Xopr4XBchXB1GVlcJsE+elUlsYSbXlptZLX+JMG3b/wZEA=="],
"@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
@@ -1988,8 +1923,6 @@
"@opencode-ai/client": ["@opencode-ai/client@workspace:packages/client"],
"@opencode-ai/codemode": ["@opencode-ai/codemode@workspace:packages/codemode"],
"@opencode-ai/console-app": ["@opencode-ai/console-app@workspace:packages/console/app"],
"@opencode-ai/console-core": ["@opencode-ai/console-core@workspace:packages/console/core"],
@@ -2036,8 +1969,6 @@
"@opencode-ai/session-ui": ["@opencode-ai/session-ui@workspace:packages/session-ui"],
"@opencode-ai/simulation": ["@opencode-ai/simulation@workspace:packages/simulation"],
"@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
"@opencode-ai/stats-app": ["@opencode-ai/stats-app@workspace:packages/stats/app"],
@@ -3076,9 +3007,9 @@
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
@@ -3246,7 +3177,7 @@
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
"body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="],
"bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="],
@@ -3414,7 +3345,7 @@
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
"content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
@@ -3424,7 +3355,7 @@
"cookie-es": ["cookie-es@2.0.1", "", {}, "sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
@@ -3526,7 +3457,7 @@
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
@@ -3732,7 +3663,7 @@
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
"express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
@@ -3790,7 +3721,7 @@
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
"find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
@@ -3828,7 +3759,7 @@
"framer-motion": ["framer-motion@8.5.5", "", { "dependencies": { "@motionone/dom": "^10.15.3", "hey-listen": "^1.0.8", "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
@@ -3880,7 +3811,7 @@
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"gitlab-ai-provider": ["gitlab-ai-provider@6.10.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-oWEZ06rDO6JjB7INHO882wyBAQqCZVHiDHwCs5M+VPmdDj8TzhGXcYesA2CcV5RoI5lfHLKwGp5uKFB62VWpqw=="],
"gitlab-ai-provider": ["gitlab-ai-provider@6.9.3", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-lWo6b6es5+k9iXaDIvE9ECzyK4zfEza4+dQ5FN8SJpEuVRi3ZBCpHIOTa32QoYEDCBaiPh+tcyca86PfNodmlg=="],
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
@@ -4392,11 +4323,11 @@
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
@@ -4882,7 +4813,7 @@
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
@@ -5068,7 +4999,7 @@
"semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
"send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
"seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="],
@@ -5078,7 +5009,7 @@
"seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="],
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
"serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
@@ -5394,7 +5325,7 @@
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
@@ -5760,8 +5691,6 @@
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="],
"@astrojs/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"@astrojs/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
"@astrojs/sitemap/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
@@ -5972,14 +5901,16 @@
"@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
"@mdx-js/mdx/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"@modelcontextprotocol/sdk/hono": ["hono@4.12.23", "", {}, "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA=="],
"@modelcontextprotocol/sdk/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
"@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
@@ -6048,14 +5979,10 @@
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
"@opencode-ai/cli/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
"@opencode-ai/core/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
"@opencode-ai/core/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
"@opencode-ai/desktop/@actions/artifact": ["@actions/artifact@4.0.0", "", { "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.1", "@actions/http-client": "^2.1.0", "@azure/core-http": "^3.0.5", "@azure/storage-blob": "^12.15.0", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-HCc2jMJRAfviGFAh0FsOR/jNfWhirxl7W6z8zDtttt0GltwxBLdEIjLiweOPFl9WbyJRW1VWnPUSAixJqcWUMQ=="],
"@opencode-ai/desktop/marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
@@ -6066,8 +5993,6 @@
"@opencode-ai/llm/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"@opencode-ai/script/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
"@opencode-ai/ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
@@ -6084,8 +6009,6 @@
"@oxc-resolver/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"@pierre/diffs/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
"@pierre/diffs/diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
@@ -6118,12 +6041,8 @@
"@shikijs/themes/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="],
"@slack/bolt/express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
"@slack/bolt/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
"@slack/bolt/raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
"@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="],
"@slack/socket-mode/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="],
@@ -6152,6 +6071,8 @@
"@storybook/csf-plugin/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
"@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
@@ -6188,6 +6109,10 @@
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.107", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8nT08pGPy25rleJNk56ep00UHK6kCtCmu+ZNqVVSSPDieADlIZqcaN1iRXAFBoCH0Fb9F6C2EjFDaySdsargfQ=="],
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="],
@@ -6222,8 +6147,6 @@
"astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
"astro/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"astro/common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="],
"astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
@@ -6244,6 +6167,10 @@
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"builder-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"builder-util/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -6310,8 +6237,6 @@
"engine.io-client/ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="],
"esast-util-from-js/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"esbuild-plugin-copy/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
@@ -6324,10 +6249,16 @@
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
@@ -6364,12 +6295,12 @@
"light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
"lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="],
"micromark-extension-mdxjs/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
@@ -6394,6 +6325,8 @@
"node-gyp/undici": ["undici@6.26.0", "", {}, "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A=="],
"node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="],
@@ -6408,8 +6341,6 @@
"opencode/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
"opencode/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
"opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
@@ -6450,6 +6381,8 @@
"proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
@@ -6458,8 +6391,14 @@
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
"serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"shiki/@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="],
"shiki/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="],
@@ -6486,8 +6425,6 @@
"tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"terser/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
@@ -6502,12 +6439,10 @@
"tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="],
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
"unplugin/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"unused-filename/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
@@ -6522,8 +6457,6 @@
"verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
"vite-plugin-dynamic-import/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
"vitest/@vitest/expect": ["@vitest/expect@4.1.7", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.7", "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w=="],
@@ -6776,6 +6709,28 @@
"@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
"@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
"@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
"@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
"@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"@modelcontextprotocol/sdk/express/send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
"@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
"@modelcontextprotocol/sdk/express/type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
"@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
"@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
@@ -6898,34 +6853,6 @@
"@shikijs/stream/@shikijs/core/@shikijs/types": ["@shikijs/types@4.2.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw=="],
"@slack/bolt/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"@slack/bolt/express/body-parser": ["body-parser@1.20.5", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA=="],
"@slack/bolt/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"@slack/bolt/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"@slack/bolt/express/cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
"@slack/bolt/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"@slack/bolt/express/finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
"@slack/bolt/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
"@slack/bolt/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
"@slack/bolt/express/path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
"@slack/bolt/express/send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
"@slack/bolt/express/serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
"@slack/bolt/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
"@slack/bolt/raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
@@ -6948,8 +6875,6 @@
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@storybook/csf-plugin/unplugin/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"@storybook/csf-plugin/unplugin/webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
@@ -6958,6 +6883,8 @@
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
@@ -7022,6 +6949,8 @@
"babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"builder-util/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
@@ -7080,8 +7009,12 @@
"esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"filelist/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"iconv-corefoundation/cli-truncate/slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="],
@@ -7118,6 +7051,8 @@
"rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -7132,6 +7067,8 @@
"tw-to-css/tailwindcss/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
"type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"unplugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"unzipper/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
@@ -7324,6 +7261,10 @@
"@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
"@modelcontextprotocol/sdk/express/type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
@@ -7356,20 +7297,6 @@
"@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
"@slack/bolt/express/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"@slack/bolt/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"@slack/bolt/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"@slack/bolt/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"@slack/bolt/express/send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
"@slack/bolt/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
"@slack/bolt/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
@@ -7498,10 +7425,6 @@
"@sentry/bundler-plugin-core/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@slack/bolt/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"@slack/bolt/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-zjYaBa+TKuHVBO0z0YqOOFLOxpxj2hB6Iq1o+BXBeVc=",
"aarch64-linux": "sha256-S2QDDoKcg8QCqjxCnMmLp/3xmiVHAbCJ0CuHQRjDBbE=",
"aarch64-darwin": "sha256-GtdfHbk4fhvosgo6iEjXrWSxteK+eNrT0P9swEccbew=",
"x86_64-darwin": "sha256-zmWFdPSTS/DpU40CgHA/KKiSIs0PlI092TzLv/cG0jE="
"x86_64-linux": "sha256-Ulflihjr8JDRVyNxSchoqaey6z/12256zs1FOiw+4vo=",
"aarch64-linux": "sha256-wP6p30a9f7s3tua5qbIq/10OHdbJb8xUni5KxE3F+/k=",
"aarch64-darwin": "sha256-7+4skFiJ+tLSjNG6WR53bT/IektA5B7gEcWLYpkK4z8=",
"x86_64-darwin": "sha256-NL945zD4J6HomH5SF0XIO8ZFCbIh2MTsJFtTyuxm2Q0="
}
}
+1 -5
View File
@@ -2,20 +2,17 @@
"$schema": "https://json.schemastore.org/package.json",
"name": "opencode",
"description": "AI-powered development tool",
"version": "0.0.0",
"private": true,
"type": "module",
"packageManager": "bun@1.3.14",
"scripts": {
"dev": "bun run --cwd packages/cli --conditions=browser src/index.ts",
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev:desktop": "bun --cwd packages/desktop dev",
"dev:web": "bun --cwd packages/app dev",
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"lint": "oxlint",
"lint:effect-patterns": "ast-grep scan -c script/ast-grep/sgconfig.yml packages/core/src packages/server/src packages/protocol/src packages/cli/src",
"test:lint-rules": "ast-grep test -c script/ast-grep/sgconfig.yml",
"typecheck": "bun turbo typecheck",
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
"postinstall": "bun run --cwd packages/core fix-node-pty",
@@ -97,7 +94,6 @@
},
"devDependencies": {
"@actions/artifact": "5.0.1",
"@ast-grep/cli": "0.44.0",
"@tsconfig/bun": "catalog:",
"@types/mime-types": "3.0.1",
"@typescript/native-preview": "catalog:",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.17.13",
"version": "1.17.12",
"description": "",
"type": "module",
"exports": {
@@ -177,7 +177,7 @@ export function DialogCustomProvider(props: Props) {
>
<div class="flex flex-col gap-6 px-2.5 pb-3 overflow-y-auto max-h-[60vh]">
<div class="px-2.5 flex gap-4 items-center">
<ProviderIcon id="session.synthetic" class="size-5 shrink-0 icon-strong-base" />
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
<div class="text-16-medium text-text-strong">{language.t("provider.custom.title")}</div>
</div>
@@ -79,7 +79,6 @@ const ModelList: Component<{
class="w-full"
placement="right-start"
gutter={12}
openDelay={0}
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
>
{node}
@@ -226,7 +226,7 @@ const SettingsProvidersContent: Component = () => {
>
<div class="flex flex-col min-w-0">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<ProviderIcon id="session.synthetic" class="size-5 shrink-0 icon-strong-base" />
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
<span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
</div>
@@ -223,7 +223,7 @@ export const SettingsProvidersV2: Component = () => {
<div class="settings-v2-provider-row" data-component="custom-provider-section">
<div class="settings-v2-provider-lead">
<ProviderIcon
id="session.synthetic"
id="synthetic"
width={PROVIDER_ICON_SIZE}
height={PROVIDER_ICON_SIZE}
class="settings-v2-provider-icon shrink-0"
+8 -7
View File
@@ -713,13 +713,14 @@ function TitlebarUpdateIconButton(props: { state: TitlebarUpdatePillState }) {
}
function ChannelIndicator() {
const channel = import.meta.env.VITE_OPENCODE_CHANNEL
if (channel !== "beta" && channel !== "dev") return null
return (
<>
{["beta", "dev"].includes(import.meta.env.VITE_OPENCODE_CHANNEL) && (
<div class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono">
{import.meta.env.VITE_OPENCODE_CHANNEL.toUpperCase()}
</div>
)}
</>
<div
class={`text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono ${channel === "beta" ? "bg-[#0488D1]" : "bg-icon-interactive-base"}`}
>
{channel === "beta" ? "β" : channel.toUpperCase()}
</div>
)
}
@@ -7,7 +7,7 @@ describe("file watcher invalidation", () => {
const refresh: string[] = []
invalidateFromWatcher(
{
type: "filesystem.changed",
type: "file.watcher.updated",
properties: {
file: "src/new.ts",
event: "add",
@@ -32,7 +32,7 @@ describe("file watcher invalidation", () => {
invalidateFromWatcher(
{
type: "filesystem.changed",
type: "file.watcher.updated",
properties: {
file: "src/open.ts",
event: "change",
@@ -63,7 +63,7 @@ describe("file watcher invalidation", () => {
invalidateFromWatcher(
{
type: "filesystem.changed",
type: "file.watcher.updated",
properties: {
file: "src",
event: "change",
@@ -81,7 +81,7 @@ describe("file watcher invalidation", () => {
invalidateFromWatcher(
{
type: "filesystem.changed",
type: "file.watcher.updated",
properties: {
file: "src/file.ts",
event: "change",
@@ -111,7 +111,7 @@ describe("file watcher invalidation", () => {
invalidateFromWatcher(
{
type: "filesystem.changed",
type: "file.watcher.updated",
properties: {
file: ".git/index.lock",
event: "change",
+1 -1
View File
@@ -16,7 +16,7 @@ type WatcherOps = {
}
export function invalidateFromWatcher(event: WatcherEvent, ops: WatcherOps) {
if (event.type !== "filesystem.changed") return
if (event.type !== "file.watcher.updated") return
const props =
typeof event.properties === "object" && event.properties ? (event.properties as Record<string, unknown>) : undefined
const rawPath = typeof props?.file === "string" ? props.file : undefined
+1 -1
View File
@@ -820,7 +820,7 @@ export default function Page() {
)
const stopVcs = sdk().event.listen((evt) => {
if (evt.details.type !== "filesystem.changed") return
if (evt.details.type !== "file.watcher.updated") return
const props =
typeof evt.details.properties === "object" && evt.details.properties
? (evt.details.properties as Record<string, unknown>)
-98
View File
@@ -1,98 +0,0 @@
# V2 CLI and TUI development guide
## Migration context
- The TUI is being ported from legacy APIs to the new V2 APIs. New and migrated TUI behavior should use `sdk.client.v2` and the location-scoped data in `packages/tui/src/context/data.tsx` instead of adding dependencies on legacy sync state.
- Preserve established TUI behavior unless the task intentionally changes it. When behavior, copy, keyboard interaction, or layout is unclear, compare the local V2 TUI with the latest released legacy TUI.
- Run both versions in separate Terminal Control sessions and save PNG-only captures at equivalent states:
```bash
# From packages/cli: local V2 TUI
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
# Released legacy TUI behavior reference
termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2.png
termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png
```
- Use the same viewport and send equivalent inputs to both sessions before comparing screenshots. The released CLI is a behavioral reference, not a source of V2 API design; keep the local implementation on V2 endpoints.
- Stop both sessions after comparison: `termctrl stop opencode-v2-dev` and `termctrl stop opencode-legacy`.
## Interactive debugging
- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI.
- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server.
- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots.
- Use a dedicated session name and do not reuse or kill an unrelated session.
```bash
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
termctrl wait opencode-v2-dev "Ask anything" --timeout 20000
termctrl show opencode-v2-dev
```
- Wait for visible text before interacting instead of relying on fixed sleeps. Use the text expected from the screen under test, such as `Ask anything` or `Connect a provider`.
- Drive the running TUI with `termctrl send`. Prefix typed input with `text:` and send control keys separately so the interaction matches real terminal input.
```bash
termctrl send opencode-v2-dev 'text:example prompt' enter
termctrl send opencode-v2-dev ctrl-c
```
- Use `termctrl show` after each meaningful interaction and inspect the full visible screen for rendering errors, stale state, error toasts, and unexpected exits.
- Save PNG evidence for every user-visible bug and fix. Do not save text captures; inspect the rendered PNG. Write temporary captures outside the repository unless the artifact is intended to be committed.
```bash
termctrl save opencode-v2-dev --format png --out /tmp/opencode/v2-tui.png
```
- For resize-sensitive changes, resize the viewport, wait for the expected content, and capture the screen again:
```bash
termctrl resize opencode-v2-dev --cols 100 --rows 30
termctrl show opencode-v2-dev
```
- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change.
- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
- Always clean up the Terminal Control session when the check is complete:
```bash
termctrl stop opencode-v2-dev
```
## Server/API debugging
- Use `bun dev api --help` from `packages/cli` to inspect the API debugging command. It sends one request to the V2 server using the same daemon discovery/auth path as the CLI.
- Use `bun dev api` to introspect the server-side data backing the TUI. This is useful when debugging UI bugs: compare what the screen renders with the raw session, message, event, agent, or health data returned by the API to determine whether the bug is in the server state, the client data layer, or the TUI rendering.
- `bun dev api` accepts either an OpenAPI operation ID or a raw HTTP method plus path:
```bash
bun dev api get /health
bun dev api get /openapi.json
bun dev api <operationId> --param key=value
```
- Pass JSON request bodies with `--data`/`-d`; the command sets `content-type: application/json` automatically unless you provide a header. Add extra headers with `--header`/`-H name:value`.
- If no compatible background server is registered, `bun dev api` starts one through the daemon service. Use `bun dev service status`, `bun dev service restart`, and `bun dev service stop` when you need explicit lifecycle control.
- Prefer raw method/path calls for quick server debugging and operation IDs when exercising documented OpenAPI routes with path or query parameters.
## Debugger
- To debug the V2 CLI or TUI with Bun's inspector, launch the CLI entrypoint through Terminal Control with an inspector URL, then attach a debugger to that URL:
```bash
termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \
bun run --inspect=ws://localhost:6499/ src/index.ts
```
- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches.
- Use `termctrl logs opencode-v2-debug` for inspector output or startup failures emitted before the TUI renderer starts. Use `termctrl show` for the visible full-screen TUI.
## Verification
- Run `bun typecheck` from `packages/cli` after CLI adapter changes.
- Run `bun typecheck` and `bun test` from `packages/tui` after shared TUI changes. Do not run tests from the repository root.
- Treat automated checks and Terminal Control smoke tests as complementary. For user-visible changes, verify initial render, the changed interaction, Ctrl-C exit behavior, and save a screenshot of the corrected state.
@@ -31,11 +31,11 @@ function run(target) {
const envPath = process.env.OPENCODE_BIN_PATH
const scriptDir = path.dirname(fs.realpathSync(__filename))
const cached = path.join(scriptDir, ".opencode2")
const cached = path.join(scriptDir, ".lildax")
const platform = { darwin: "darwin", linux: "linux", win32: "windows" }[os.platform()] || os.platform()
const arch = { x64: "x64", arm64: "arm64", arm: "arm" }[os.arch()] || os.arch()
const base = "@opencode-ai/cli-" + platform + "-" + arch
const binary = platform === "windows" ? "opencode2.exe" : "opencode2"
const binary = platform === "windows" ? "lildax.exe" : "lildax"
function supportsAvx2() {
if (arch !== "x64") return false
@@ -121,7 +121,7 @@ function findBinary(startDir) {
const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir))
if (!resolved) {
console.error(
"It seems that your package manager failed to install the right opencode2 CLI package. Try manually installing " +
"It seems that your package manager failed to install the right lildax CLI package. Try manually installing " +
names.map((name) => `"${name}"`).join(" or ") +
" package",
)
+2 -6
View File
@@ -1,11 +1,11 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/cli",
"version": "1.17.13",
"version": "1.17.12",
"type": "module",
"license": "MIT",
"bin": {
"opencode2": "./bin/opencode2.cjs"
"lildax": "./bin/lildax.cjs"
},
"files": [
"bin"
@@ -17,7 +17,6 @@
},
"dependencies": {
"@effect/platform-node": "catalog:",
"@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/server": "workspace:*",
@@ -26,15 +25,12 @@
"@opentui/solid": "catalog:",
"@parcel/watcher": "2.5.1",
"effect": "catalog:",
"jsonc-parser": "3.3.1",
"semver": "catalog:",
"solid-js": "catalog:"
},
"devDependencies": {
"@opencode-ai/script": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@types/semver": "catalog:",
"@typescript/native-preview": "catalog:"
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ import pkg from "../package.json"
import { modelsData } from "./generate"
const dir = path.resolve(import.meta.dirname, "..")
const binary = "opencode2"
const binary = "lildax"
process.chdir(dir)
await rm("dist", { recursive: true, force: true })
+6 -7
View File
@@ -25,15 +25,14 @@ for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" }
}
console.log("binaries", binaries)
const version = Object.values(binaries)[0]
const name = pkg.name
await $`mkdir -p ./dist/${name}/bin`
await $`cp ./bin/opencode2.cjs ./dist/${name}/bin/opencode2`
await Bun.file(`./dist/${name}/package.json`).write(
await $`mkdir -p ./dist/${pkg.name}/bin`
await $`cp ./bin/lildax.cjs ./dist/${pkg.name}/bin/lildax`
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
name,
bin: { opencode2: "./bin/opencode2" },
name: pkg.name,
bin: { lildax: "./bin/lildax" },
version,
license: pkg.license,
repository: { type: "git", url: "git+https://github.com/anomalyco/opencode.git" },
@@ -51,4 +50,4 @@ await Promise.all(
publish(`./dist/${name.replace("@opencode-ai/", "")}`, name, version),
),
)
await publish(`./dist/${name}`, name, version)
await publish(`./dist/${pkg.name}`, pkg.name, version)
+5 -75
View File
@@ -5,30 +5,6 @@ declare const OPENCODE_CLI_NAME: string | undefined
export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME : "opencode", {
description: "OpenCode 2.0 preview command line interface",
params: {
directory: Argument.string("directory").pipe(
Argument.withDescription("Directory to start OpenCode in"),
Argument.optional,
),
standalone: Flag.boolean("standalone").pipe(
Flag.withDescription("Run with a private server instead of the background service"),
Flag.withDefault(false),
),
server: Flag.string("server").pipe(
Flag.withDescription("Connect to a server URL instead of the background service"),
Flag.optional,
),
continue: Flag.boolean("continue").pipe(
Flag.withAlias("c"),
Flag.withDescription("Continue the last session"),
Flag.withDefault(false),
),
session: Flag.string("session").pipe(
Flag.withAlias("s"),
Flag.withDescription("Session ID to continue"),
Flag.optional,
),
},
commands: [
Spec.make("api", {
description: "Make a request to the running server",
@@ -50,43 +26,6 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
description: "Debugging and troubleshooting tools",
commands: [Spec.make("agents", { description: "List all agents" })],
}),
Spec.make("mcp", {
description: "Manage MCP (Model Context Protocol) servers",
commands: [
Spec.make("list", { description: "List configured MCP servers and their status" }),
Spec.make("add", {
description: "Add an MCP server to your configuration",
params: {
name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")),
command: Argument.string("command").pipe(
Argument.withDescription("Command and arguments for a local server, passed after --"),
Argument.variadic({ min: 0 }),
),
url: Flag.string("url").pipe(Flag.withDescription("URL for a remote MCP server"), Flag.optional),
header: Flag.keyValuePair("header").pipe(
Flag.withDescription("HTTP header for a remote server, as name=value"),
Flag.optional,
),
env: Flag.keyValuePair("env").pipe(
Flag.withDescription("Environment variable for a local server, as name=value"),
Flag.optional,
),
global: Flag.boolean("global").pipe(
Flag.withDescription("Write to the global config instead of the project config"),
Flag.withDefault(false),
),
},
}),
Spec.make("auth", {
description: "Authenticate with an OAuth-capable remote MCP server",
params: { name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")) },
}),
Spec.make("logout", {
description: "Remove stored OAuth credentials for an MCP server",
params: { name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")) },
}),
],
}),
Spec.make("migrate", { description: "Migrate v1 data to v2" }),
Spec.make("service", {
description: "Manage the background server",
@@ -95,27 +34,18 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
Spec.make("restart", { description: "Restart the background server" }),
Spec.make("status", { description: "Show background server status" }),
Spec.make("stop", { description: "Stop the background server" }),
Spec.make("get", {
description: "Get service configuration",
params: { key: Argument.string("key").pipe(Argument.optional) },
}),
Spec.make("set", {
description: "Set service configuration",
params: { key: Argument.string("key"), value: Argument.string("value") },
}),
Spec.make("unset", {
description: "Unset service configuration",
params: { key: Argument.string("key") },
Spec.make("password", {
description: "Get or set the server password",
params: { value: Argument.string("value").pipe(Argument.optional) },
}),
],
}),
Spec.make("serve", {
description: "Start the v2 API server",
params: {
hostname: Flag.string("hostname").pipe(Flag.optional),
hostname: Flag.string("hostname").pipe(Flag.withDefault("127.0.0.1")),
port: Flag.integer("port").pipe(Flag.optional),
service: Flag.boolean("service").pipe(Flag.withDefault(false)),
stdio: Flag.boolean("stdio").pipe(Flag.withDefault(false)),
register: Flag.boolean("register").pipe(Flag.withDefault(false)),
},
}),
],
+4 -6
View File
@@ -2,8 +2,7 @@ import { EOL } from "node:os"
import { Effect, Option } from "effect"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../services/service-config"
import { Daemon } from "../../services/daemon"
const methods = new Set(["delete", "get", "head", "options", "patch", "post", "put"])
@@ -18,9 +17,8 @@ type OpenApi = {
export default Runtime.handler(
Commands.commands.api,
Effect.fn("cli.api")(function* (input) {
const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options))
const daemon = yield* Daemon.Service
const transport = yield* daemon.transport()
const params = Option.getOrElse(input.param, () => ({}))
const request = yield* resolveRequest(transport, input.request, params)
const headers = new Headers(transport.headers)
@@ -60,7 +58,7 @@ export function rawRequest(input: readonly string[]) {
}
function resolveRequest(
transport: Service.Transport,
transport: { url: string; headers: RequestInit["headers"] },
input: readonly string[],
params: Record<string, string>,
) {
@@ -1,18 +1,14 @@
import { EOL } from "os"
import { Effect } from "effect"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import * as Effect from "effect/Effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../../services/service-config"
import { Daemon } from "../../../services/daemon"
export default Runtime.handler(
Commands.commands.debug.commands.agents,
Effect.fn("cli.debug.agents")(function* () {
const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
const daemon = yield* Daemon.Service
const client = yield* daemon.client()
const response = yield* Effect.promise(() => client.v2.agent.list({ location: { directory: process.cwd() } }))
process.stdout.write(
JSON.stringify(
+6 -79
View File
@@ -1,86 +1,13 @@
import { NodeFileSystem } from "@effect/platform-node"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { Effect, Option, Redacted } from "effect"
import { Service } from "@opencode-ai/client/effect"
import { Env } from "../../env"
import { ServiceConfig } from "../../services/service-config"
import { Standalone } from "../../services/standalone"
import { Updater } from "../../services/updater"
import { Effect } from "effect"
import { Daemon } from "../../services/daemon"
export default Runtime.handler(Commands, (input) =>
export default Runtime.handler(Commands, () =>
Effect.gen(function* () {
const directory = Option.getOrUndefined(input.directory)
if (directory !== undefined) process.chdir(directory)
const updater = yield* Updater.Service
yield* updater.check().pipe(Effect.forkScoped)
const server = Option.getOrUndefined(input.server)
if (server !== undefined && input.standalone)
return yield* Effect.fail(new Error("--server and --standalone cannot be combined"))
const transport = yield* Effect.gen(function* () {
if (server !== undefined) {
const password = yield* Env.password
const explicit = {
url: server,
headers: password
? { authorization: "Basic " + btoa("opencode:" + Redacted.value(password)) }
: undefined,
} satisfies Service.Transport
// Fail loudly before entering the TUI: an explicit server that is
// unreachable or rejects auth should not present as reconnect churn.
const response = yield* Effect.tryPromise(() =>
fetch(new URL("/api/health", server), { headers: explicit.headers, signal: AbortSignal.timeout(5_000) }),
).pipe(Effect.mapError((cause) => new Error(`Could not reach server at ${server}`, { cause })))
if (response.status === 401)
return yield* Effect.fail(
new Error(
password
? `Server at ${server} rejected the password`
: `Server at ${server} requires a password; set OPENCODE_PASSWORD`,
),
)
if (!response.ok)
return yield* Effect.fail(new Error(`Server at ${server} responded with status ${response.status}`))
return explicit
}
if (input.standalone) return yield* Standalone.transport()
const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options)
return found ?? (yield* Service.start(options))
})
const daemon = yield* Daemon.Service
const transport = yield* daemon.transport()
const { runTui } = yield* Effect.promise(() => import("../../tui"))
// The TUI re-runs discover whenever its event stream drops. For an explicit
// --server or a standalone child the transport is fixed, so reconnects
// retry the same address; for the managed service discovery re-reads the
// registration and may start a replacement.
const serviceOptions = server === undefined && !input.standalone ? yield* ServiceConfig.options() : undefined
const discover = serviceOptions
? () =>
Effect.runPromise(
Effect.gen(function* () {
const found = yield* Service.discover(serviceOptions)
return found ?? (yield* Service.start(serviceOptions))
}).pipe(Effect.provide(NodeFileSystem.layer)),
)
: () => Promise.resolve(transport)
// Restart the managed service in place; start() resolves once the
// replacement is healthy and the reconnect loop reattaches on its own.
// Only meaningful in service mode: --server is not ours to restart and a
// standalone child cannot be respawned.
const reload = serviceOptions
? () =>
Effect.runPromise(
Effect.gen(function* () {
yield* Service.stop(serviceOptions)
yield* Service.start(serviceOptions)
}).pipe(Effect.provide(NodeFileSystem.layer)),
)
: undefined
yield* runTui(
transport,
{ continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
discover,
reload,
)
yield* runTui(transport)
}),
)
@@ -1,58 +0,0 @@
import { EOL } from "node:os"
import path from "node:path"
import { Effect, Option } from "effect"
import { applyEdits, modify } from "jsonc-parser"
import { Global } from "@opencode-ai/core/global"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
export default Runtime.handler(
Commands.commands.mcp.commands.add,
Effect.fn("cli.mcp.add")(function* (input) {
const url = Option.getOrUndefined(input.url)
const headers = Option.getOrUndefined(input.header)
const environment = Option.getOrUndefined(input.env)
// The CLI framework strands `--` operands on the root command, so read the local server command
// straight from argv after `--`. This also lets the command carry its own flags (e.g. `npx -y`).
const dash = process.argv.indexOf("--")
const command = dash === -1 ? [...input.command] : process.argv.slice(dash + 1)
const hasCommand = command.length > 0
if (url && hasCommand)
return yield* Effect.fail(new Error("Provide either --url <url> or a command after --, not both"))
if (!url && !hasCommand) return yield* Effect.fail(new Error("Provide either --url <url> or a command after --"))
if (url && !URL.canParse(url)) return yield* Effect.fail(new Error(`Invalid URL: ${url}`))
if (url && environment) return yield* Effect.fail(new Error("--env is only valid for local MCP servers"))
if (hasCommand && headers) return yield* Effect.fail(new Error("--header is only valid for remote MCP servers"))
const server = url
? { type: "remote" as const, url, ...(headers ? { headers } : {}) }
: { type: "local" as const, command, ...(environment ? { environment } : {}) }
const configPath = yield* Effect.promise(() => resolveConfigPath(input.global ? Global.Path.config : process.cwd()))
yield* Effect.promise(() => write(configPath, input.name, server))
process.stdout.write(`MCP server "${input.name}" added to ${configPath}` + EOL)
}),
)
async function resolveConfigPath(directory: string) {
const candidates = [
path.join(directory, "opencode.json"),
path.join(directory, "opencode.jsonc"),
path.join(directory, ".opencode", "opencode.json"),
path.join(directory, ".opencode", "opencode.jsonc"),
]
for (const candidate of candidates) {
if (await Bun.file(candidate).exists()) return candidate
}
return candidates[0]
}
async function write(configPath: string, name: string, server: unknown) {
const file = Bun.file(configPath)
const text = (await file.exists()) ? await file.text() : "{}"
const edits = modify(text, ["mcp", "servers", name], server, {
formattingOptions: { tabSize: 2, insertSpaces: true },
})
await Bun.write(configPath, applyEdits(text, edits))
}
@@ -1,66 +0,0 @@
import { EOL } from "node:os"
import { Effect } from "effect"
import {
createOpencodeClient,
type IntegrationAttemptStatus,
type IntegrationOAuthMethod,
type OpencodeClient,
} from "@opencode-ai/sdk/v2/client"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../../services/service-config"
import { resolveIntegration } from "./resolve"
const location = { directory: process.cwd() }
export default Runtime.handler(
Commands.commands.mcp.commands.auth,
Effect.fn("cli.mcp.auth")(function* (input) {
const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
const integration = yield* resolveIntegration(client, input.name, location)
if (!integration)
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
const method = integration.methods.find(
(candidate): candidate is IntegrationOAuthMethod => candidate.type === "oauth",
)
if (!method)
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
const started = yield* Effect.promise(() =>
client.v2.integration.connect.oauth({ integrationID: integration.id, methodID: method.id, inputs: {}, location }),
)
const attempt = started.data?.data
if (!attempt) return yield* Effect.fail(new Error(started.error?.message ?? "Failed to start OAuth attempt"))
if (attempt.mode === "code")
return yield* Effect.fail(new Error("This server requires manual code entry, which the CLI does not support"))
process.stdout.write(attempt.instructions + EOL + attempt.url + EOL)
const result = yield* poll(client, attempt.attemptID)
if (result.status === "complete") {
process.stdout.write(`Authenticated with ${input.name}` + EOL)
return
}
const reason = result.status === "failed" ? `: ${result.message}` : ""
return yield* Effect.fail(new Error(`Authentication ${result.status}${reason}`))
}),
)
const poll = (
client: OpencodeClient,
attemptID: string,
): Effect.Effect<Exclude<IntegrationAttemptStatus, { status: "pending" }>> =>
Effect.gen(function* () {
const response = yield* Effect.promise(() => client.v2.integration.attempt.status({ attemptID, location }))
const status = response.data?.data
if (!status || status.status === "pending") {
yield* Effect.sleep("1 second")
return yield* poll(client, attemptID)
}
return status
})
@@ -1,55 +0,0 @@
import { EOL } from "node:os"
import { Effect } from "effect"
import { createOpencodeClient, type McpServer } from "@opencode-ai/sdk/v2/client"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../../services/service-config"
export default Runtime.handler(
Commands.commands.mcp.commands.list,
Effect.fn("cli.mcp.list")(function* () {
const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
const response = yield* Effect.promise(() => client.v2.mcp.list({ location: { directory: process.cwd() } }))
const servers = (response.data?.data ?? []).toSorted((a, b) => a.name.localeCompare(b.name))
if (servers.length === 0) {
process.stdout.write("No MCP servers configured" + EOL)
return
}
const width = Math.max(...servers.map((server) => server.name.length))
const lines = servers.map(
(server) => `${icon(server.status)} ${server.name.padEnd(width)} ${describe(server.status)}`,
)
process.stdout.write(lines.join(EOL) + EOL)
}),
)
function icon(status: McpServer["status"]) {
switch (status.status) {
case "connected":
return "✓"
case "needs_auth":
return "⚠"
case "failed":
case "needs_client_registration":
return "✗"
default:
return "○"
}
}
function describe(status: McpServer["status"]) {
switch (status.status) {
case "needs_auth":
return "needs authentication"
case "needs_client_registration":
return `needs client registration: ${status.error}`
case "failed":
return `failed: ${status.error}`
default:
return status.status
}
}
@@ -1,39 +0,0 @@
import { EOL } from "node:os"
import { Effect } from "effect"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../../services/service-config"
import { resolveIntegration } from "./resolve"
const location = { directory: process.cwd() }
export default Runtime.handler(
Commands.commands.mcp.commands.logout,
Effect.fn("cli.mcp.logout")(function* (input) {
const options = yield* ServiceConfig.options()
const found = yield* Service.discover(options)
const transport = found ?? (yield* Service.start(options))
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
const integration = yield* resolveIntegration(client, input.name, location)
if (!integration) {
process.stdout.write(`No stored credentials for ${input.name}` + EOL)
return
}
const credentials = integration.connections.filter((connection) => connection.type === "credential")
if (credentials.length === 0) {
process.stdout.write(`No stored credentials for ${input.name}` + EOL)
return
}
yield* Effect.forEach(
credentials,
(connection) => Effect.promise(() => client.v2.credential.remove({ credentialID: connection.id, location })),
{ discard: true },
)
process.stdout.write(`Removed OAuth credentials for ${input.name}` + EOL)
}),
)
@@ -1,17 +0,0 @@
import { Effect } from "effect"
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client"
// Resolve through the MCP-owned integrationID rather than matching integration names: the shared
// integration registry also holds provider/plugin integrations, whose names could collide with a server.
// Fails when the server is unknown; returns undefined when the server has no integration (e.g. a local
// or anonymous server), leaving that case for the caller to interpret.
export const resolveIntegration = (client: OpencodeClient, name: string, location: { directory: string }) =>
Effect.gen(function* () {
const servers = yield* Effect.promise(() => client.v2.mcp.list({ location }))
const server = (servers.data?.data ?? []).find((entry) => entry.name === name)
if (!server) return yield* Effect.fail(new Error(`MCP server not found: ${name}`))
const integrationID = server.integrationID
if (!integrationID) return undefined
const found = yield* Effect.promise(() => client.v2.integration.get({ integrationID, location }))
return found.data?.data
})
@@ -1,4 +1,4 @@
import { Effect } from "effect"
import * as Effect from "effect/Effect"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
+13 -121
View File
@@ -3,134 +3,30 @@ import { Credential } from "@opencode-ai/core/credential"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
import { Project } from "@opencode-ai/core/project"
import { Global } from "@opencode-ai/core/global"
import { Context, Effect, FileSystem, Layer, Option, Redacted, Schedule, Schema } from "effect"
import { Context, Layer, Option } from "effect"
import * as Effect from "effect/Effect"
import { HttpRouter, HttpServer } from "effect/unstable/http"
import { createServer } from "node:http"
import { createRoutes } from "@opencode-ai/server/routes"
import { ServerAuth } from "@opencode-ai/server/auth"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { Env } from "../../env"
import { ServiceConfig } from "../../services/service-config"
import { Updater } from "../../services/updater"
import { randomBytes, randomUUID } from "crypto"
import path from "path"
import { Daemon } from "../../services/daemon"
export default Runtime.handler(
Commands.commands.serve,
Effect.fn("cli.serve")(function* (input) {
if (input.service) yield* Effect.sync(() => process.chdir(Global.Path.home))
return yield* Effect.scoped(
Effect.gen(function* () {
const standalonePassword = yield* Env.password
// Keep the lease credential out of the environment inherited by any
// process this server spawns.
if (input.stdio) {
delete process.env.OPENCODE_PASSWORD
delete process.env.OPENCODE_SERVER_PASSWORD
}
const config = input.service ? yield* ServiceConfig.read() : {}
const password = input.service
? yield* ServiceConfig.password()
: standalonePassword
? Redacted.value(standalonePassword)
: randomBytes(32).toString("base64url")
if (!password) return yield* Effect.fail(new Error("Missing server password"))
const hostname = Option.getOrUndefined(input.hostname) ?? config.hostname ?? "127.0.0.1"
const port = Option.isSome(input.port)
? input.port
: config.port === undefined
? Option.none<number>()
: Option.some(config.port)
const address = yield* listen(hostname, port, password)
yield* Effect.tryPromise(() =>
createOpencodeClient({
baseUrl: HttpServer.formatAddress(address),
headers: ServerAuth.headers({ password }),
}).v2.health.get({}),
)
if (input.service) yield* register(address)
const url = HttpServer.formatAddress(address)
console.log(input.stdio ? JSON.stringify({ url }) : `server listening on ${url}`)
if (!input.service && !input.stdio && !standalonePassword) console.log(`server password ${password}`)
const updater = yield* Updater.Service
yield* updater.check().pipe(Effect.schedule(Schedule.spaced("10 minutes")), Effect.forkScoped)
return yield* input.stdio ? waitForStdinClose() : Effect.never
}).pipe(Effect.annotateLogs({ role: "server" })),
const daemon = yield* Daemon.Service
const address = yield* listen(input.hostname, input.port, yield* daemon.password())
if (input.register) yield* daemon.register(address)
console.log(`server listening on ${HttpServer.formatAddress(address)}`)
return yield* Effect.never
}),
)
}),
)
// Server-side half of the registration protocol. The registration embeds the
// password so the file alone is enough for any client to discover and
// authenticate. The file arbitrates ownership after concurrent starts; it is
// not a startup lock: the atomic rename elects the latest writer, the watcher
// self-evicts losers, and the finalizer id-guard keeps an exiting server from
// deleting its successor's registration.
// Written and read through Service.Info so the file the server registers is
// provably the contract clients discover with.
const infoJson = Schema.fromJsonString(Service.Info)
const encodeInfo = Schema.encodeEffect(infoJson)
const decodeInfo = Schema.decodeUnknownEffect(infoJson)
const register = Effect.fnUntraced(function* (address: HttpServer.Address) {
const fs = yield* FileSystem.FileSystem
const { file } = yield* ServiceConfig.options()
const id = randomUUID()
const secret = yield* ServiceConfig.password()
const temp = file + "." + id + ".tmp"
yield* fs.makeDirectory(path.dirname(file), { recursive: true })
const encoded = yield* encodeInfo({
id,
version: InstallationVersion,
url: HttpServer.formatAddress(address),
pid: process.pid,
password: secret,
})
yield* fs.writeFileString(temp, encoded, { mode: 0o600 })
yield* fs.rename(temp, file)
const currentID = fs.readFileString(file).pipe(
Effect.flatMap(decodeInfo),
Effect.map((info) => info.id),
Effect.orElseSucceed(() => undefined),
)
yield* currentID.pipe(
Effect.flatMap((current) =>
current === id
? Effect.void
: Effect.try({ try: () => process.kill(process.pid, "SIGTERM"), catch: (cause) => cause }).pipe(Effect.ignore),
),
Effect.repeat(Schedule.spaced("10 seconds")),
Effect.forkScoped,
)
yield* Effect.addFinalizer(() =>
currentID.pipe(
Effect.flatMap((current) => (current === id ? fs.remove(file) : Effect.void)),
Effect.ignore,
),
)
})
function waitForStdinClose() {
return Effect.callback<void>((resume) => {
const close = () => resume(Effect.void)
process.stdin.once("end", close)
process.stdin.once("close", close)
process.stdin.resume()
if (process.stdin.readableEnded || process.stdin.destroyed) close()
return Effect.sync(() => {
process.stdin.off("end", close)
process.stdin.off("close", close)
process.stdin.pause()
})
})
}
function listen(hostname: string, port: Option.Option<number>, password: string) {
if (Option.isSome(port)) return bind(hostname, port.value, password)
const next = (port: number): ReturnType<typeof bind> =>
@@ -141,14 +37,10 @@ function listen(hostname: string, port: Option.Option<number>, password: string)
}
function bind(hostname: string, port: number, password: string) {
const server = createServer()
return Layer.build(
HttpRouter.serve(createRoutes(password), { disableListenLog: true }).pipe(
Layer.provideMerge(NodeHttpServer.layer(() => server, { port, host: hostname })),
Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node, Project.node]))),
HttpRouter.serve(createRoutes(password), { disableListenLog: true, disableLogger: true }).pipe(
Layer.provideMerge(NodeHttpServer.layer(() => createServer(), { port, host: hostname })),
Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node]))),
),
).pipe(
Effect.tap(() => Effect.addFinalizer(() => Effect.sync(() => server.closeAllConnections()))),
Effect.map((context) => Context.get(context, HttpServer.HttpServer).address),
)
).pipe(Effect.map((context) => Context.get(context, HttpServer.HttpServer).address))
}
@@ -1,12 +0,0 @@
import { EOL } from "os"
import { Effect, Option } from "effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
export default Runtime.handler(
Commands.commands.service.commands.get,
Effect.fn("cli.service.get")(function* (input) {
process.stdout.write((yield* ServiceConfig.get(Option.getOrUndefined(input.key))) + EOL)
}),
)
@@ -0,0 +1,16 @@
import { EOL } from "os"
import { Option } from "effect"
import * as Effect from "effect/Effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { Daemon } from "../../../services/daemon"
export default Runtime.handler(
Commands.commands.service.commands.password,
Effect.fn("cli.service.password")(function* (input) {
const daemon = yield* Daemon.Service
const value = Option.getOrUndefined(input.value)
if (value !== undefined) yield* daemon.stop()
process.stdout.write((yield* daemon.password(value)) + EOL)
}),
)
@@ -1,16 +1,14 @@
import { EOL } from "os"
import { Effect } from "effect"
import { Service } from "@opencode-ai/client/effect"
import * as Effect from "effect/Effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
import { Daemon } from "../../../services/daemon"
export default Runtime.handler(
Commands.commands.service.commands.restart,
Effect.fn("cli.service.restart")(function* () {
const options = yield* ServiceConfig.options()
yield* Service.stop(options)
const transport = yield* Service.start(options)
process.stdout.write(transport.url + EOL)
const daemon = yield* Daemon.Service
yield* daemon.stop()
process.stdout.write((yield* daemon.start()) + EOL)
}),
)
@@ -1,11 +0,0 @@
import { Effect } from "effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
export default Runtime.handler(
Commands.commands.service.commands.set,
Effect.fn("cli.service.set")(function* (input) {
yield* ServiceConfig.set(input.key, input.value)
}),
)
@@ -1,14 +1,12 @@
import { EOL } from "os"
import { Effect } from "effect"
import { Service } from "@opencode-ai/client/effect"
import * as Effect from "effect/Effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
import { Daemon } from "../../../services/daemon"
export default Runtime.handler(
Commands.commands.service.commands.start,
Effect.fn("cli.service.start")(function* () {
const transport = yield* Service.start(yield* ServiceConfig.options())
process.stdout.write(transport.url + EOL)
process.stdout.write((yield* (yield* Daemon.Service).start()) + EOL)
}),
)
@@ -1,14 +1,13 @@
import { EOL } from "os"
import { Effect } from "effect"
import { Service } from "@opencode-ai/client/effect"
import * as Effect from "effect/Effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
import { Daemon } from "../../../services/daemon"
export default Runtime.handler(
Commands.commands.service.commands.status,
Effect.fn("cli.service.status")(function* () {
const found = yield* Service.discover(yield* ServiceConfig.options())
process.stdout.write((found ? found.url : "stopped") + EOL)
const url = yield* (yield* Daemon.Service).status()
process.stdout.write((url ? `running ${url}` : "stopped") + EOL)
}),
)
@@ -1,12 +1,11 @@
import { Effect } from "effect"
import { Service } from "@opencode-ai/client/effect"
import * as Effect from "effect/Effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
import { Daemon } from "../../../services/daemon"
export default Runtime.handler(
Commands.commands.service.commands.stop,
Effect.fn("cli.service.stop")(function* () {
yield* Service.stop(yield* ServiceConfig.options())
yield* (yield* Daemon.Service).stop()
}),
)
@@ -1,11 +0,0 @@
import { Effect } from "effect"
import { Commands } from "../../commands"
import { Runtime } from "../../../framework/runtime"
import { ServiceConfig } from "../../../services/service-config"
export default Runtime.handler(
Commands.commands.service.commands.unset,
Effect.fn("cli.service.unset")(function* (input) {
yield* ServiceConfig.unset(input.key)
}),
)
-15
View File
@@ -1,15 +0,0 @@
import { Config } from "effect"
// Every environment variable the CLI reads, in one place. Consumers yield
// these instead of touching process.env so the full surface stays visible,
// typed, and redacted where secret.
// The opencode server password: sent by clients connecting to an explicit
// --server, and adopted by a manually run or standalone server. The legacy
// name is still honored.
export const password = Config.redacted("OPENCODE_PASSWORD").pipe(
Config.orElse(() => Config.redacted("OPENCODE_SERVER_PASSWORD")),
Config.withDefault(undefined),
)
export * as Env from "./env"
+6 -17
View File
@@ -1,8 +1,7 @@
import { Effect, FileSystem, Scope } from "effect"
import { Command } from "effect/unstable/cli"
import * as Effect from "effect/Effect"
import * as Command from "effect/unstable/cli/Command"
import { Spec } from "./spec"
import { Global } from "@opencode-ai/core/global"
import { Updater } from "../services/updater"
import { Daemon } from "../services/daemon"
export type Input<Value> =
Value extends Spec.Node<infer _Name, infer Command, infer _Commands>
@@ -11,21 +10,11 @@ export type Input<Value> =
? Input
: never
type RuntimeHandler = (
input: unknown,
) => Effect.Effect<void, unknown, FileSystem.FileSystem | Global.Service | Updater.Service | Scope.Scope>
type RuntimeHandler = (input: unknown) => Effect.Effect<void, unknown, Daemon.Service>
type Loader<Node extends Spec.Any> = () => Promise<{
default: (
input: Input<Node>,
) => Effect.Effect<void, any, FileSystem.FileSystem | Global.Service | Updater.Service | Scope.Scope>
default: (input: Input<Node>) => Effect.Effect<void, any, Daemon.Service>
}>
type ProvidedCommand = Command.Command<
string,
unknown,
unknown,
unknown,
FileSystem.FileSystem | Global.Service | Updater.Service | Scope.Scope
>
type ProvidedCommand = Command.Command<string, unknown, unknown, unknown, Daemon.Service>
export type Handlers<Node extends Spec.Any> = keyof Node["commands"] extends never
? Loader<Node>
+1 -1
View File
@@ -1,4 +1,4 @@
import { Command } from "effect/unstable/cli"
import * as Command from "effect/unstable/cli/Command"
type Options<Config extends Command.Command.Config, Commands extends ReadonlyArray<Any>> = {
readonly description?: string
+7 -36
View File
@@ -1,22 +1,11 @@
#!/usr/bin/env bun
import { NodeFileSystem, NodeRuntime, NodeServices } from "@effect/platform-node"
import { Effect, Layer, Logger, References } from "effect"
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import * as NodeServices from "@effect/platform-node/NodeServices"
import * as Effect from "effect/Effect"
import { Commands } from "./commands/commands"
import { Runtime } from "./framework/runtime"
import { Logging } from "@opencode-ai/core/observability/logging"
import { Updater } from "./services/updater"
import { InstallationChannel, InstallationVersion, InstallationLocal } from "@opencode-ai/core/installation/version"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Global } from "@opencode-ai/core/global"
import { AppProcess } from "@opencode-ai/core/process"
const LoggingLayer = Logger.layer(Logging.loggers(), { mergeWithExisting: false }).pipe(
Layer.provide(NodeFileSystem.layer),
Layer.orDie,
Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())),
)
import { Daemon } from "./services/daemon"
const Handlers = Runtime.handlers(Commands, {
$: () => import("./commands/handlers/default"),
@@ -24,38 +13,20 @@ const Handlers = Runtime.handlers(Commands, {
debug: {
agents: () => import("./commands/handlers/debug/agents"),
},
mcp: {
list: () => import("./commands/handlers/mcp/list"),
add: () => import("./commands/handlers/mcp/add"),
auth: () => import("./commands/handlers/mcp/auth"),
logout: () => import("./commands/handlers/mcp/logout"),
},
migrate: () => import("./commands/handlers/migrate"),
service: {
start: () => import("./commands/handlers/service/start"),
restart: () => import("./commands/handlers/service/restart"),
status: () => import("./commands/handlers/service/status"),
stop: () => import("./commands/handlers/service/stop"),
get: () => import("./commands/handlers/service/get"),
set: () => import("./commands/handlers/service/set"),
unset: () => import("./commands/handlers/service/unset"),
password: () => import("./commands/handlers/service/password"),
},
serve: () => import("./commands/handlers/serve"),
})
Effect.logInfo("cli starting", {
version: InstallationVersion,
channel: InstallationChannel,
local: InstallationLocal,
args: process.argv.slice(2),
}).pipe(
Effect.flatMap(() => Runtime.run(Commands, Handlers, { version: InstallationVersion })),
Effect.annotateLogs({ role: "cli" }),
Effect.provide(Updater.layer),
Effect.provide(AppNodeBuilder.build(LayerNode.group([Global.node, AppProcess.node]))),
Effect.provide(LoggingLayer),
Runtime.run(Commands, Handlers, { version: "local" }).pipe(
Effect.provide(Daemon.layer),
Effect.provide(NodeServices.layer),
Effect.scoped,
Effect.tap(() => Effect.sync(() => process.exit(0))),
NodeRuntime.runMain,
)
+192
View File
@@ -0,0 +1,192 @@
import { Global } from "@opencode-ai/core/global"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { ServerAuth } from "@opencode-ai/server/auth"
import { Context, Effect, FileSystem, Layer, Option, Schedule, Schema, Scope } from "effect"
import { HttpServer } from "effect/unstable/http"
import { randomBytes, randomUUID } from "crypto"
import { spawn } from "node:child_process"
import path from "path"
export interface Interface {
readonly client: () => Effect.Effect<ReturnType<typeof createOpencodeClient>, unknown>
readonly transport: () => Effect.Effect<{ url: string; headers: RequestInit["headers"] }, unknown>
readonly start: () => Effect.Effect<string, Error>
readonly status: () => Effect.Effect<string | undefined>
readonly stop: () => Effect.Effect<void, unknown>
readonly password: (value?: string) => Effect.Effect<string, unknown>
readonly register: (address: HttpServer.Address) => Effect.Effect<void, unknown, Scope.Scope>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/cli/Daemon") {}
const Registration = Schema.Struct({
id: Schema.optional(Schema.String),
version: Schema.optional(Schema.String),
url: Schema.String,
pid: Schema.Int.check(Schema.isGreaterThan(0)),
})
type Registration = typeof Registration.Type
function sameRegistration(left: Registration, right: Registration) {
return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid
}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
const directory = Global.Path.state
const file = path.join(directory, "server.json")
const passwordFile = path.join(directory, "password")
const decodeRegistration = Schema.decodeUnknownEffect(Schema.fromJsonString(Registration))
const password = Effect.fn("cli.daemon.password")(function* (value?: string) {
const existing = yield* fs.readFileString(passwordFile).pipe(Effect.catch(() => Effect.succeed(undefined)))
if (value === undefined && existing) return existing
// Keep one private credential across server restarts so discovered clients
// can reconnect without exposing a password flag or environment variable.
const generated = value ?? randomBytes(32).toString("base64url")
const temp = passwordFile + ".tmp"
yield* fs.makeDirectory(directory, { recursive: true })
yield* fs.writeFileString(temp, generated, { mode: 0o600 })
yield* fs.rename(temp, passwordFile)
return generated
})
const registration = Effect.fnUntraced(function* () {
return yield* fs.readFileString(file).pipe(Effect.flatMap(decodeRegistration))
})
const createClient = Effect.fnUntraced(function* (url: string) {
return createOpencodeClient({ baseUrl: url, headers: ServerAuth.headers({ password: yield* password() }) })
})
const healthy = Effect.fnUntraced(function* () {
const info = yield* registration()
const client = yield* createClient(info.url)
const response = yield* Effect.tryPromise(() => client.v2.health.get({ signal: AbortSignal.timeout(2_000) }))
if (response.data?.healthy === true) return info
return yield* Effect.fail(new Error("Registered server is not healthy"))
})
const compatible = Effect.fnUntraced(function* () {
const info = yield* healthy()
if (info.version === InstallationVersion) return info
return yield* Effect.fail(new Error("Registered server version does not match the client"))
})
const signal = (pid: number, signal: NodeJS.Signals) =>
Effect.try({ try: () => process.kill(pid, signal), catch: (cause) => cause }).pipe(Effect.ignore)
const awaitStopped = Effect.fnUntraced(function* (pid: number) {
const running = yield* Effect.try({ try: () => process.kill(pid, 0), catch: () => false }).pipe(
Effect.orElseSucceed(() => false),
)
if (!running) return true
return yield* Effect.fail(new Error(`Server process ${pid} is still running`))
})
const stopProcess = Effect.fnUntraced(function* (info: Registration) {
const current = yield* healthy().pipe(Effect.option)
if (Option.isNone(current) || !sameRegistration(current.value, info)) return
yield* signal(info.pid, "SIGTERM")
const stopped = yield* awaitStopped(info.pid).pipe(
Effect.retry(Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100)))),
Effect.option,
)
if (Option.isSome(stopped)) return
const latest = yield* healthy().pipe(Effect.option)
if (Option.isNone(latest) || !sameRegistration(latest.value, info)) return
yield* signal(info.pid, "SIGKILL")
yield* awaitStopped(info.pid).pipe(
Effect.retry(Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100)))),
)
})
const start = Effect.fn("cli.daemon.start")(function* () {
const existing = yield* healthy().pipe(Effect.option)
const found = Option.getOrUndefined(existing)
const compiled = path.basename(process.execPath).replace(/\.exe$/, "") !== "bun"
if (found?.version === InstallationVersion && compiled) return found.url
if (found) yield* stopProcess(found).pipe(Effect.ignore)
const entrypoint = compiled ? undefined : process.argv[1]
if (!compiled && entrypoint === undefined)
return yield* Effect.fail(new Error("Failed to resolve CLI entrypoint"))
yield* Effect.try({
try: () => {
spawn(process.execPath, [...(entrypoint ? [entrypoint] : []), "serve", "--register"], {
detached: true,
stdio: "ignore",
}).unref()
},
catch: (cause) => new Error("Failed to start server", { cause }),
})
return yield* compatible().pipe(
Effect.retry(Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100)))),
Effect.map((info) => info.url),
Effect.mapError(() => new Error("Failed to start server")),
)
})
const transport = Effect.fn("cli.daemon.transport")(function* () {
return { url: yield* start(), headers: ServerAuth.headers({ password: yield* password() }) }
})
const client = Effect.fn("cli.daemon.client")(function* () {
const connection = yield* transport()
return createOpencodeClient({ baseUrl: connection.url, headers: connection.headers })
})
const status = Effect.fn("cli.daemon.status")(function* () {
const existing = yield* healthy().pipe(Effect.option)
const found = Option.getOrUndefined(existing)
if (found?.version === InstallationVersion) return found.url
if (found) return undefined
yield* fs.remove(file).pipe(Effect.ignore)
return undefined
})
const stop = Effect.fn("cli.daemon.stop")(function* () {
const existing = yield* healthy().pipe(Effect.option)
// A stale registration may point at a PID that has since been reused by
// another process. Only signal the PID after authenticating the server.
if (Option.isNone(existing)) return yield* fs.remove(file).pipe(Effect.ignore)
yield* stopProcess(existing.value)
yield* fs.remove(file).pipe(Effect.ignore)
})
const register = Effect.fn("cli.daemon.register")(function* (address: HttpServer.Address) {
const id = randomUUID()
const temp = file + "." + id + ".tmp"
yield* fs.makeDirectory(directory, { recursive: true })
yield* fs.writeFileString(
temp,
JSON.stringify({ id, version: InstallationVersion, url: HttpServer.formatAddress(address), pid: process.pid }),
{ mode: 0o600 },
)
yield* fs.rename(temp, file)
yield* registration().pipe(
Effect.flatMap((info) => (info.id === id ? Effect.void : signal(process.pid, "SIGTERM"))),
Effect.catch(() => signal(process.pid, "SIGTERM")),
Effect.repeat(Schedule.spaced("10 seconds")),
Effect.forkScoped,
)
yield* Effect.addFinalizer(() =>
registration().pipe(
Effect.flatMap((info) => (info.id === id ? fs.remove(file) : Effect.void)),
Effect.ignore,
),
)
})
return Service.of({ client, transport, start, status, stop, password, register })
}),
)
export * as Daemon from "./daemon"
-143
View File
@@ -1,143 +0,0 @@
import { Global } from "@opencode-ai/core/global"
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
import { Service } from "@opencode-ai/client/effect"
import { Effect, FileSystem, Schema } from "effect"
import { randomBytes } from "crypto"
import path from "path"
// The CLI's service configuration file, plus the Service.Options binding that
// points the client package's service operations at this CLI: which
// registration file (by channel), which version, and how to spawn opencode.
export const Info = Schema.Struct({
hostname: Schema.optional(Schema.String),
port: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(65_535))),
password: Schema.optional(Schema.String),
})
export type Info = typeof Info.Type
const keys = ["hostname", "port", "password"] as const
type Key = (typeof keys)[number]
const decodeInfo = Schema.decodeUnknownEffect(Schema.fromJsonString(Info))
function configKey(key: string): Key {
if (keys.includes(key as Key)) return key as Key
throw new Error(`Unknown service config key: ${key}`)
}
const env = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
const global = yield* Global.Service
const filename = InstallationChannel === "local" ? "service-local.json" : "service.json"
return {
fs,
file: path.join(global.state, filename),
configFile: path.join(global.config, filename),
}
})
export const options = Effect.fnUntraced(function* () {
const { file } = yield* env
const compiled = path.basename(process.execPath).replace(/\.exe$/, "") !== "bun"
const entrypoint = compiled ? undefined : process.argv[1]
if (!compiled && entrypoint === undefined) return yield* Effect.fail(new Error("Failed to resolve CLI entrypoint"))
return {
file,
version: InstallationVersion,
command: [process.execPath, ...(entrypoint ? [entrypoint] : []), "serve", "--service"],
}
})
export const read = Effect.fn("cli.service-config.read")(function* () {
const { fs, configFile } = yield* env
return yield* fs.readFileString(configFile).pipe(
Effect.flatMap(decodeInfo),
Effect.catch(() => Effect.succeed({} as Info)),
)
})
const write = Effect.fn("cli.service-config.write")(function* (value: Info) {
const { fs, configFile } = yield* env
const temp = configFile + ".tmp"
yield* fs.makeDirectory(path.dirname(configFile), { recursive: true })
yield* fs.writeFileString(temp, JSON.stringify(value, null, 2) + "\n", { mode: 0o600 })
yield* fs.rename(temp, configFile)
})
export const password = Effect.fn("cli.service-config.password")(function* (value?: string) {
const existing = yield* read()
if (value === undefined && existing.password) return existing.password
const next = value ?? randomBytes(32).toString("base64url")
// Keep one private credential across server restarts so discovered clients
// can reconnect without exposing a password flag or environment variable.
yield* write({ ...existing, password: next })
return next
})
export const get = Effect.fn("cli.service-config.get")(function* (key?: string) {
if (key === undefined) {
const { password: _password, ...safe } = yield* read()
return JSON.stringify(safe, null, 2)
}
switch (configKey(key)) {
case "hostname": {
return (yield* read()).hostname ?? ""
}
case "port": {
const port = (yield* read()).port
return port === undefined ? "" : String(port)
}
case "password": {
return yield* password()
}
}
})
export const set = Effect.fn("cli.service-config.set")(function* (key: string, value: string) {
switch (configKey(key)) {
case "hostname": {
yield* Service.stop(yield* options())
yield* write({ ...(yield* read()), hostname: value })
return
}
case "port": {
const port = Number(value)
if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error("Port must be between 1 and 65535")
yield* Service.stop(yield* options())
yield* write({ ...(yield* read()), port })
return
}
case "password": {
yield* Service.stop(yield* options())
yield* password(value)
return
}
}
})
export const unset = Effect.fn("cli.service-config.unset")(function* (key: string) {
switch (configKey(key)) {
case "hostname": {
yield* Service.stop(yield* options())
const { hostname: _hostname, ...next } = yield* read()
yield* write(next)
return
}
case "port": {
yield* Service.stop(yield* options())
const { port: _port, ...next } = yield* read()
yield* write(next)
return
}
case "password": {
yield* Service.stop(yield* options())
const { password: _password, ...next } = yield* read()
yield* write(next)
return
}
}
})
export * as ServiceConfig from "./service-config"
-44
View File
@@ -1,44 +0,0 @@
import { ServerAuth } from "@opencode-ai/server/auth"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Effect, Schema, Stream } from "effect"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { randomBytes } from "node:crypto"
import path from "node:path"
const Ready = Schema.Struct({ url: Schema.String })
const decodeReady = Schema.decodeUnknownPromise(Schema.fromJsonString(Ready))
function command(password: string) {
const compiled = path.basename(process.execPath).replace(/\.exe$/, "") !== "bun"
const entrypoint = compiled ? [] : process.argv[1] ? [process.argv[1]] : []
if (!compiled && entrypoint.length === 0) throw new Error("Failed to resolve CLI entrypoint")
return ChildProcess.make(process.execPath, [...entrypoint, "serve", "--stdio", "--port", "0"], {
cwd: process.cwd(),
// Explicit entry wins over anything inherited, so a user-exported
// OPENCODE_PASSWORD cannot shadow the child's lease credential.
env: { OPENCODE_PASSWORD: password },
extendEnv: true,
// The server treats EOF on this pipe as the end of its ownership lease.
// The OS closes it even when the TUI is killed before Effect finalizers run.
stdin: "pipe",
stderr: "ignore",
killSignal: "SIGTERM",
forceKillAfter: "3 seconds",
})
}
export const transport = Effect.fn("cli.standalone.transport")(
function* () {
const password = randomBytes(32).toString("base64url")
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
const proc = yield* spawner.spawn(command(password))
const output = yield* proc.stdout.pipe(Stream.decodeText(), Stream.splitLines, Stream.take(1), Stream.mkString)
if (!output) return yield* Effect.fail(new Error("Standalone server exited before reporting readiness"))
const ready = yield* Effect.tryPromise(() => decodeReady(output))
return { url: ready.url, headers: ServerAuth.headers({ password }), pid: proc.pid }
},
Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node)),
)
export * as Standalone from "./standalone"
-33
View File
@@ -1,33 +0,0 @@
import { describe, expect, test } from "bun:test"
import { action, decodePolicy } from "./updater"
describe("updater", () => {
test("reads autoupdate from JSONC", () => {
expect(decodePolicy('{ // preference\n "autoupdate": "notify",\n}')).toBe("notify")
expect(decodePolicy('{ "autoupdate": false }')).toBe(false)
expect(decodePolicy('{ "autoupdate": "invalid" }')).toBeUndefined()
})
test("automatically updates patches and minors", () => {
expect(action("1.2.3", "1.2.4", true)).toBe("upgrade")
expect(action("1.2.3", "1.3.0", true)).toBe("upgrade")
expect(action("1.2.3", "1.2.4", "notify")).toBe("upgrade")
expect(action("1.2.3", "1.3.0", "notify")).toBe("upgrade")
})
test("skips when autoupdate is disabled", () => {
expect(action("1.2.3", "1.2.4", false)).toBe("none")
})
test("never automatically updates majors", () => {
expect(action("1.2.3", "2.0.0", true)).toBe("none")
})
test("reports up-to-date only when versions match", () => {
expect(action("1.2.3", "1.2.3", true)).toBe("none")
})
test("upgrades when latest is lower (rollback)", () => {
expect(action("1.2.4", "1.2.3", true)).toBe("upgrade")
})
})
-156
View File
@@ -1,156 +0,0 @@
import { Global } from "@opencode-ai/core/global"
import { Flag } from "@opencode-ai/core/flag/flag"
import { AppProcess } from "@opencode-ai/core/process"
import {
InstallationChannel,
InstallationLocal,
InstallationVersion,
} from "@opencode-ai/core/installation/version"
import { Context, Duration, Effect, FileSystem, Layer } from "effect"
import { ChildProcess } from "effect/unstable/process"
import { parse, type ParseError } from "jsonc-parser"
import path from "node:path"
import semver from "semver"
export type Policy = boolean | "notify"
export type Action = "none" | "upgrade"
type Method = "npm" | "pnpm" | "bun" | "yarn"
const packageName = "@opencode-ai/cli"
export interface Interface {
readonly check: () => Effect.Effect<void>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/cli/Updater") {}
export function decodePolicy(text: string): Policy | undefined {
// The CLI only projects this host-level preference instead of initializing
// the location-scoped server configuration graph.
const errors: ParseError[] = []
const input: unknown = parse(text, errors, { allowTrailingComma: true })
if (errors.length || typeof input !== "object" || input === null || !("autoupdate" in input)) return
const value = input.autoupdate
if (typeof value === "boolean" || value === "notify") return value
}
export function action(current: string, latest: string, policy: Policy): Action {
if (policy === false) return "none"
if (!semver.valid(current) || !semver.valid(latest) || semver.eq(latest, current)) return "none"
// Major upgrades are never installed automatically.
if (semver.major(latest) !== semver.major(current)) return "none"
return "upgrade"
}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
const global = yield* Global.Service
const appProcess = yield* AppProcess.Service
const channel = InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")
const readPolicy = Effect.fnUntraced(function* () {
const values = yield* Effect.forEach(["config.json", "opencode.json", "opencode.jsonc"], (name) =>
fs
.readFileString(path.join(global.config, name))
.pipe(Effect.map(decodePolicy), Effect.catch(() => Effect.succeed(undefined))),
)
return values.findLast((value) => value !== undefined) ?? true
})
const run = Effect.fnUntraced(function* (command: string[], timeout: Duration.Input = "10 seconds") {
return yield* appProcess
.run(ChildProcess.make(command[0], command.slice(1)), {
timeout,
maxOutputBytes: 100_000,
maxErrorBytes: 100_000,
})
.pipe(
Effect.map((result) => ({
code: result.exitCode,
stdout: result.stdout.toString("utf8"),
stderr: result.stderr.toString("utf8"),
})),
Effect.catch(() => Effect.succeed({ code: 1, stdout: "", stderr: "" })),
)
})
const method = Effect.fnUntraced(function* () {
const checks: ReadonlyArray<{ method: Method; command: string[] }> = [
{ method: "npm", command: ["npm", "list", "-g", "--depth=0", packageName] },
{ method: "pnpm", command: ["pnpm", "list", "-g", "--depth=0", packageName] },
{ method: "bun", command: ["bun", "pm", "ls", "-g"] },
{ method: "yarn", command: ["yarn", "global", "list"] },
]
const results = yield* Effect.forEach(
checks,
(check) => run(check.command).pipe(Effect.map((result) => ({ check, result }))),
{ concurrency: "unbounded" },
)
return results.find((result) => result.result.stdout.includes(packageName))?.check.method
})
const latest = Effect.fnUntraced(function* () {
const response = yield* Effect.tryPromise({
try: () =>
fetch(
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/${encodeURIComponent(InstallationChannel)}`,
{ headers: { "User-Agent": `opencode/${InstallationVersion}` }, signal: AbortSignal.timeout(10_000) },
),
catch: (cause) => new Error("Failed to check for updates", { cause }),
})
if (!response.ok) return yield* Effect.fail(new Error(`Update check failed with status ${response.status}`))
const data = yield* Effect.tryPromise({
try: () => response.json(),
catch: (cause) => new Error("Failed to read update information", { cause }),
})
if (typeof data !== "object" || data === null || !("version" in data) || typeof data.version !== "string") {
return yield* Effect.fail(new Error("Update information did not include a version"))
}
return data.version
})
const upgrade = Effect.fnUntraced(function* (method: Method, version: string) {
const target = `${packageName}@${version}`
const commands: Record<Method, string[]> = {
npm: ["npm", "install", "--global", target],
pnpm: ["pnpm", "install", "--global", target],
bun: ["bun", "install", "--global", target],
yarn: ["yarn", "global", "add", target],
}
const result = yield* run(commands[method], "5 minutes")
if (result.code === 0) return
return yield* Effect.fail(new Error(result.stderr.trim() || `Failed to update with ${method}`))
})
const check = Effect.fn("cli.updater.check")(function* () {
if (InstallationLocal || Flag.OPENCODE_DISABLE_AUTOUPDATE)
return yield* Effect.logInfo("update check skipped", {
reason: InstallationLocal ? "local-install" : "disabled",
version: InstallationVersion,
channel: InstallationChannel,
})
const policy = yield* readPolicy()
if (policy === false) return yield* Effect.logInfo("update check skipped", { reason: "policy-disabled" })
return yield* Effect.gen(function* () {
const version = yield* latest()
yield* Effect.logInfo("update check", {
current: InstallationVersion,
latest: version,
})
const next = action(InstallationVersion, version, policy)
if (next === "none") return yield* Effect.logInfo("update check done", { action: "up-to-date" })
const detected = yield* method()
if (!detected) return yield* Effect.logWarning("automatic update skipped: installation method not found")
yield* upgrade(detected, version)
yield* Effect.logInfo("updated OpenCode", { from: InstallationVersion, to: version, method: detected })
})
}, Effect.catchCause((cause) => Effect.logWarning("automatic update failed", { cause })))
return Service.of({ check })
}),
)
export * as Updater from "./updater"
+28 -45
View File
@@ -3,52 +3,35 @@ import { TuiConfig } from "@opencode-ai/tui/config"
import { Effect } from "effect"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Global } from "@opencode-ai/core/global"
import { loadBuiltinPlugins } from "@opencode-ai/tui/builtins"
import { OpenCode } from "@opencode-ai/client/promise"
import type { Service } from "@opencode-ai/client/effect"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import type { Args } from "@opencode-ai/tui/context/args"
export function runTui(
transport: Service.Transport,
args: Args,
discover?: () => Promise<Service.Transport>,
reload?: () => Promise<void>,
) {
export function runTui(transport: { url: string; headers: RequestInit["headers"] }) {
const config = TuiConfig.resolve({}, { terminalSuspend: false })
let disposeSlots: (() => void) | undefined
return Effect.gen(function* () {
const options = { baseUrl: transport.url, headers: transport.headers }
const api = OpenCode.make(options)
const directory = yield* Effect.tryPromise(() => api.file.list({ location: { directory: process.cwd() } })).pipe(
Effect.map((response) => response.location.directory),
Effect.catch(() =>
Effect.tryPromise(() => api.location.get()).pipe(Effect.map((response) => response.directory)),
),
)
return yield* run({
client: createOpencodeClient({ ...options, directory }),
api,
discover: discover
? async () => {
const next = await discover()
return {
client: createOpencodeClient({ baseUrl: next.url, headers: next.headers, directory }),
api: OpenCode.make({ baseUrl: next.url, headers: next.headers }),
}
}
: undefined,
reload,
args,
config,
pluginHost: {
async start(input) {
disposeSlots = await loadBuiltinPlugins(input.api, input.runtime)
},
async dispose() {
disposeSlots?.()
},
},
})
return run({
...transport,
args: {},
config,
fetch: gracefulFetch,
pluginHost: {
async start() {},
async dispose() {},
},
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
}
const legacyDefaults: Record<string, unknown> = {
"/config/providers": { providers: [], default: {} },
"/provider": { all: [], default: {}, connected: [] },
"/agent": [],
"/config": {},
}
const gracefulFetch = Object.assign(
async (input: RequestInfo | URL, init?: RequestInit) => {
const response = await fetch(input, init)
if (response.status !== 404) return response
const fallback = legacyDefaults[new URL(input instanceof Request ? input.url : input).pathname]
if (fallback === undefined) return response
return Response.json(fallback)
},
{ preconnect: fetch.preconnect },
)
@@ -1,15 +0,0 @@
import { Effect } from "effect"
import path from "node:path"
import { Standalone } from "../../src/services/standalone"
process.argv[1] = path.join(import.meta.dir, "../../src/index.ts")
await Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
const transport = yield* Standalone.transport()
console.log(`${transport.pid} ${transport.url}`)
return yield* Effect.never
}),
),
)
-26
View File
@@ -1,26 +0,0 @@
import { NodeFileSystem } from "@effect/platform-node"
import { Global } from "@opencode-ai/core/global"
import { expect, test } from "bun:test"
import { Effect } from "effect"
import fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import { ServiceConfig } from "../src/services/service-config"
test("local channel stores service config with the local service filename", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-service-"))
try {
await Effect.runPromise(
ServiceConfig.set("hostname", "127.0.0.2").pipe(
Effect.provide(Global.layerWith({ config: path.join(root, "config"), state: path.join(root, "state") })),
Effect.provide(NodeFileSystem.layer),
),
)
expect(await Bun.file(path.join(root, "config", "service-local.json")).json()).toEqual({
hostname: "127.0.0.2",
})
expect(await Bun.file(path.join(root, "config", "service.json")).exists()).toBe(false)
} finally {
await fs.rm(root, { recursive: true, force: true })
}
})
-65
View File
@@ -1,65 +0,0 @@
import { expect, test } from "bun:test"
import path from "node:path"
test("standalone server exits when its owner is killed", async () => {
const owner = Bun.spawn([process.execPath, path.join(import.meta.dir, "fixture/standalone-owner.ts")], {
cwd: path.join(import.meta.dir, ".."),
env: process.env,
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
})
const line = await Promise.race([readLine(owner.stdout), Bun.sleep(10_000).then(() => undefined)])
const [rawPID, url] = line?.split(" ") ?? []
const pid = Number(rawPID)
try {
expect(pid).toBeGreaterThan(0)
expect(url).toStartWith("http://127.0.0.1:")
expect(running(pid)).toBe(true)
owner.kill("SIGKILL")
await owner.exited
expect(await waitForExit(pid)).toBe(true)
} finally {
owner.kill("SIGKILL")
if (running(pid)) process.kill(pid, "SIGKILL")
}
})
async function readLine(stream: ReadableStream<Uint8Array>) {
const reader = stream.getReader()
const decoder = new TextDecoder()
const chunks: string[] = []
while (true) {
const result = await reader.read()
if (result.done) break
chunks.push(decoder.decode(result.value, { stream: true }))
const output = chunks.join("")
const newline = output.indexOf("\n")
if (newline !== -1) {
reader.releaseLock()
return output.slice(0, newline)
}
}
reader.releaseLock()
return chunks.join("") + decoder.decode()
}
async function waitForExit(pid: number, attempts = 100): Promise<boolean> {
if (!running(pid)) return true
if (attempts === 0) return false
await Bun.sleep(50)
return waitForExit(pid, attempts - 1)
}
function running(pid: number) {
if (!Number.isSafeInteger(pid) || pid <= 0) return false
try {
process.kill(pid, 0)
return true
} catch {
return false
}
}
+6 -18
View File
@@ -1,30 +1,16 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/client",
"version": "1.17.13",
"private": true,
"type": "module",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/anomalyco/opencode.git",
"directory": "packages/client"
},
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"exports": {
"./promise": "./src/promise/index.ts",
"./promise/api": "./src/promise/api.ts",
"./effect": "./src/effect/index.ts",
"./effect/api": "./src/effect/api.ts"
".": "./src/index.ts",
"./effect": "./src/effect.ts"
},
"scripts": {
"build": "bun run script/build-package.ts",
"generate": "bun run script/build.ts",
"check:generated": "bun run generate && git diff --exit-code -- src/promise/generated src/effect/generated src/effect/api",
"check:generated": "bun run generate && git diff --exit-code -- src/generated src/generated-effect",
"test": "bun test --timeout 5000",
"typecheck": "tsgo --noEmit"
},
@@ -42,7 +28,9 @@
},
"devDependencies": {
"@effect/platform-node": "catalog:",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/httpapi-codegen": "workspace:*",
"@opencode-ai/server": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/env bun
import { $ } from "bun"
import { fileURLToPath } from "node:url"
process.chdir(fileURLToPath(new URL("..", import.meta.url)))
await $`rm -rf dist`
await $`bun tsc -p tsconfig.build.json`
+8 -19
View File
@@ -1,23 +1,16 @@
import { NodeFileSystem } from "@effect/platform-node"
import { compile, emitEffectImported, emitEffectShape, emitPromise, write } from "@opencode-ai/httpapi-codegen"
import {
ClientApi,
effectOmitEndpoints,
endpointNames,
groupNames,
promiseOmitEndpoints,
} from "@opencode-ai/protocol/client"
import { compile, emitEffectImported, emitPromise, write } from "@opencode-ai/httpapi-codegen"
import { ClientApi, endpointNames, groupNames, omitEndpoints } from "../src/contract"
import { Effect } from "effect"
import { fileURLToPath } from "url"
const promiseContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
const effectContract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: effectOmitEndpoints })
const contract = compile(ClientApi, { groupNames, endpointNames, omitEndpoints })
await Effect.runPromise(
Effect.all(
[
write(
emitPromise(promiseContract, {
emitPromise(contract, {
outputTypes: {
"events.subscribe": {
name: "OpenCodeEventEncoded",
@@ -25,17 +18,13 @@ await Effect.runPromise(
},
},
}),
fileURLToPath(new URL("../src/promise/generated", import.meta.url)),
fileURLToPath(new URL("../src/generated", import.meta.url)),
),
write(
emitEffectImported(effectContract, { module: "../../contract", api: "ClientApi" }),
fileURLToPath(new URL("../src/effect/generated", import.meta.url)),
),
write(
emitEffectShape(effectContract, { module: "../../contract", api: "ClientApi" }),
fileURLToPath(new URL("../src/effect/api", import.meta.url)),
emitEffectImported(contract, { module: "../contract", api: "ClientApi" }),
fileURLToPath(new URL("../src/generated-effect", import.meta.url)),
),
],
{ concurrency: 3, discard: true },
{ concurrency: 2, discard: true },
).pipe(Effect.provide(NodeFileSystem.layer)),
)
-45
View File
@@ -1,45 +0,0 @@
#!/usr/bin/env bun
import { Script } from "@opencode-ai/script"
import { $ } from "bun"
import { rm } from "node:fs/promises"
import { fileURLToPath } from "node:url"
process.chdir(fileURLToPath(new URL("..", import.meta.url)))
const originalText = await Bun.file("package.json").text()
const pkg = JSON.parse(originalText) as {
name: string
version: string
exports: Record<string, string | { import: string; types: string }>
}
const tarball = `${pkg.name.replace("@", "").replace("/", "-")}-${pkg.version}.tgz`
if ((await $`npm view ${pkg.name}@${pkg.version} version`.nothrow()).exitCode === 0) {
console.log(`already published ${pkg.name}@${pkg.version}`)
process.exit(0)
}
try {
await $`bun run typecheck`
await $`bun run build`
pkg.exports = Object.fromEntries(
Object.entries(pkg.exports).map(([key, value]) => {
if (typeof value !== "string") return [key, value]
return [
key,
{
import: value.replace("./src/", "./dist/").replace(/\.ts$/, ".js"),
types: value.replace("./src/", "./dist/").replace(/\.ts$/, ".d.ts"),
},
]
}),
)
await Bun.write("package.json", JSON.stringify(pkg, null, 2) + "\n")
await rm(tarball, { force: true })
await $`bun pm pack`
await $`npm publish ${tarball} --tag ${Script.channel} --access public`
} finally {
await Bun.write("package.json", originalText)
await rm(tarball, { force: true })
}
+53 -7
View File
@@ -1,7 +1,53 @@
export {
ClientApi,
effectOmitEndpoints,
endpointNames,
groupNames,
promiseOmitEndpoints,
} from "@opencode-ai/protocol/client"
import { makeDefaultApi } from "@opencode-ai/protocol/api"
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware>()(
"@opencode-ai/client/LocationMiddleware",
) {}
class SessionLocationMiddleware extends HttpApiMiddleware.Service<SessionLocationMiddleware>()(
"@opencode-ai/client/SessionLocationMiddleware",
{ error: [InvalidRequestError, SessionNotFoundError] },
) {}
export const ClientApi = makeDefaultApi({
locationMiddleware: LocationMiddleware,
sessionLocationMiddleware: SessionLocationMiddleware,
})
export const groupNames = {
"server.health": "health",
"server.location": "location",
"server.agent": "agents",
"server.session": "sessions",
"server.message": "messages",
"server.model": "models",
"server.provider": "providers",
"server.integration": "integrations",
"server.credential": "credentials",
"server.permission": "permissions",
"server.fs": "files",
"server.command": "commands",
"server.skill": "skills",
"server.event": "events",
"server.pty": "ptys",
"server.question": "questions",
"server.reference": "references",
"server.projectCopy": "projectCopies",
} as const
export const endpointNames = {
"session.messages": "list",
"integration.connect.key": "connectKey",
"integration.connect.oauth": "connectOauth",
"integration.attempt.status": "attemptStatus",
"integration.attempt.complete": "attemptComplete",
"integration.attempt.cancel": "attemptCancel",
"permission.request.list": "listRequests",
"permission.saved.list": "listSaved",
"permission.saved.remove": "removeSaved",
"question.request.list": "listRequests",
} as const
export const omitEndpoints = new Set(["fs.read", "pty.connect", "pty.connectToken"])
@@ -1,30 +1,10 @@
// TODO: Keep additional network capabilities inside Schema and Protocol as the client grows; /effect must never import
// Core or Server. Preserve these datatype exports so internal model reorganizations do not require caller migrations.
import type { Effect } from "effect"
export * from "./generated/index"
export type {
AgentApi,
AppApi,
CatalogApi,
CommandApi,
EventApi,
IntegrationApi,
ModelApi,
PluginApi,
ProviderApi,
ReferenceApi,
SessionApi,
SkillApi,
} from "./api.js"
export { Service } from "./service.js"
export * from "./generated-effect/index"
export { Agent } from "@opencode-ai/schema/agent"
export { Command } from "@opencode-ai/schema/command"
export { Credential } from "@opencode-ai/schema/credential"
export { Event } from "@opencode-ai/schema/event"
export { EventLog } from "@opencode-ai/schema/event-log"
export { FileSystem } from "@opencode-ai/schema/filesystem"
export { Form } from "@opencode-ai/schema/form"
export { Integration } from "@opencode-ai/schema/integration"
export { Location } from "@opencode-ai/schema/location"
export { Model } from "@opencode-ai/schema/model"
@@ -43,4 +23,3 @@ export { SessionMessage } from "@opencode-ai/schema/session-message"
export { Skill } from "@opencode-ai/schema/skill"
export { Prompt } from "@opencode-ai/schema/prompt"
export type { OpenCodeEvent } from "@opencode-ai/protocol/groups/event"
export type OpenCodeClient = Effect.Success<ReturnType<typeof import("./generated/client").make>>
-8
View File
@@ -1,8 +0,0 @@
import type { ModelApi, ProviderApi } from "./api/api.js"
export type * from "./api/api.js"
export interface CatalogApi<E = never> {
readonly provider: ProviderApi<E>
readonly model: ModelApi<E>
}
@@ -1,3 +0,0 @@
[
"api.ts"
]
-891
View File
@@ -1,891 +0,0 @@
// Generated by @opencode-ai/httpapi-codegen. Do not edit.
import type { Effect, Stream } from "effect"
import type { HttpApiClient } from "effect/unstable/httpapi"
import type { ClientApi } from "../../contract"
type RawClient = HttpApiClient.ForApi<typeof ClientApi>
type EffectValue<A> = A extends Effect.Effect<infer Success, any, any> ? Success : never
type StreamValue<A> = A extends Stream.Stream<infer Success, any, any> ? Success : never
export type Endpoint0_0Output = EffectValue<ReturnType<RawClient["server.health"]["health.get"]>>
export type HealthGetOperation<E = never> = () => Effect.Effect<Endpoint0_0Output, E>
export interface HealthApi<E = never> {
readonly get: HealthGetOperation<E>
}
type Endpoint1_0Request = Parameters<RawClient["server.location"]["location.get"]>[0]
export type Endpoint1_0Input = { readonly location?: Endpoint1_0Request["query"]["location"] }
export type Endpoint1_0Output = EffectValue<ReturnType<RawClient["server.location"]["location.get"]>>
export type LocationGetOperation<E = never> = (input?: Endpoint1_0Input) => Effect.Effect<Endpoint1_0Output, E>
export interface LocationApi<E = never> {
readonly get: LocationGetOperation<E>
}
type Endpoint2_0Request = Parameters<RawClient["server.agent"]["agent.list"]>[0]
export type Endpoint2_0Input = { readonly location?: Endpoint2_0Request["query"]["location"] }
export type Endpoint2_0Output = EffectValue<ReturnType<RawClient["server.agent"]["agent.list"]>>
export type AgentListOperation<E = never> = (input?: Endpoint2_0Input) => Effect.Effect<Endpoint2_0Output, E>
export interface AgentApi<E = never> {
readonly list: AgentListOperation<E>
}
type Endpoint3_0Request = Parameters<RawClient["server.plugin"]["plugin.list"]>[0]
export type Endpoint3_0Input = { readonly location?: Endpoint3_0Request["query"]["location"] }
export type Endpoint3_0Output = EffectValue<ReturnType<RawClient["server.plugin"]["plugin.list"]>>
export type PluginListOperation<E = never> = (input?: Endpoint3_0Input) => Effect.Effect<Endpoint3_0Output, E>
export interface PluginApi<E = never> {
readonly list: PluginListOperation<E>
}
type Endpoint4_0Request = Parameters<RawClient["server.session"]["session.list"]>[0]
export type Endpoint4_0Input = {
readonly workspace?: Endpoint4_0Request["query"]["workspace"]
readonly limit?: Endpoint4_0Request["query"]["limit"]
readonly order?: Endpoint4_0Request["query"]["order"]
readonly search?: Endpoint4_0Request["query"]["search"]
readonly parentID?: Endpoint4_0Request["query"]["parentID"]
readonly directory?: Endpoint4_0Request["query"]["directory"]
readonly project?: Endpoint4_0Request["query"]["project"]
readonly subpath?: Endpoint4_0Request["query"]["subpath"]
readonly cursor?: Endpoint4_0Request["query"]["cursor"]
}
export type Endpoint4_0Output = EffectValue<ReturnType<RawClient["server.session"]["session.list"]>>
export type SessionListOperation<E = never> = (input?: Endpoint4_0Input) => Effect.Effect<Endpoint4_0Output, E>
type Endpoint4_1Request = Parameters<RawClient["server.session"]["session.create"]>[0]
export type Endpoint4_1Input = {
readonly id?: Endpoint4_1Request["payload"]["id"]
readonly agent?: Endpoint4_1Request["payload"]["agent"]
readonly model?: Endpoint4_1Request["payload"]["model"]
readonly location?: Endpoint4_1Request["payload"]["location"]
}
export type Endpoint4_1Output = EffectValue<ReturnType<RawClient["server.session"]["session.create"]>>["data"]
export type SessionCreateOperation<E = never> = (input?: Endpoint4_1Input) => Effect.Effect<Endpoint4_1Output, E>
export type Endpoint4_2Output = EffectValue<ReturnType<RawClient["server.session"]["session.active"]>>
export type SessionActiveOperation<E = never> = () => Effect.Effect<Endpoint4_2Output, E>
type Endpoint4_3Request = Parameters<RawClient["server.session"]["session.get"]>[0]
export type Endpoint4_3Input = { readonly sessionID: Endpoint4_3Request["params"]["sessionID"] }
export type Endpoint4_3Output = EffectValue<ReturnType<RawClient["server.session"]["session.get"]>>["data"]
export type SessionGetOperation<E = never> = (input: Endpoint4_3Input) => Effect.Effect<Endpoint4_3Output, E>
type Endpoint4_4Request = Parameters<RawClient["server.session"]["session.fork"]>[0]
export type Endpoint4_4Input = {
readonly sessionID: Endpoint4_4Request["params"]["sessionID"]
readonly messageID?: Endpoint4_4Request["payload"]["messageID"]
}
export type Endpoint4_4Output = EffectValue<ReturnType<RawClient["server.session"]["session.fork"]>>["data"]
export type SessionForkOperation<E = never> = (input: Endpoint4_4Input) => Effect.Effect<Endpoint4_4Output, E>
type Endpoint4_5Request = Parameters<RawClient["server.session"]["session.switchAgent"]>[0]
export type Endpoint4_5Input = {
readonly sessionID: Endpoint4_5Request["params"]["sessionID"]
readonly agent: Endpoint4_5Request["payload"]["agent"]
}
export type Endpoint4_5Output = EffectValue<ReturnType<RawClient["server.session"]["session.switchAgent"]>>
export type SessionSwitchAgentOperation<E = never> = (input: Endpoint4_5Input) => Effect.Effect<Endpoint4_5Output, E>
type Endpoint4_6Request = Parameters<RawClient["server.session"]["session.switchModel"]>[0]
export type Endpoint4_6Input = {
readonly sessionID: Endpoint4_6Request["params"]["sessionID"]
readonly model: Endpoint4_6Request["payload"]["model"]
}
export type Endpoint4_6Output = EffectValue<ReturnType<RawClient["server.session"]["session.switchModel"]>>
export type SessionSwitchModelOperation<E = never> = (input: Endpoint4_6Input) => Effect.Effect<Endpoint4_6Output, E>
type Endpoint4_7Request = Parameters<RawClient["server.session"]["session.rename"]>[0]
export type Endpoint4_7Input = {
readonly sessionID: Endpoint4_7Request["params"]["sessionID"]
readonly title: Endpoint4_7Request["payload"]["title"]
}
export type Endpoint4_7Output = EffectValue<ReturnType<RawClient["server.session"]["session.rename"]>>
export type SessionRenameOperation<E = never> = (input: Endpoint4_7Input) => Effect.Effect<Endpoint4_7Output, E>
type Endpoint4_8Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
export type Endpoint4_8Input = {
readonly sessionID: Endpoint4_8Request["params"]["sessionID"]
readonly id?: Endpoint4_8Request["payload"]["id"]
readonly prompt: Endpoint4_8Request["payload"]["prompt"]
readonly delivery?: Endpoint4_8Request["payload"]["delivery"]
readonly resume?: Endpoint4_8Request["payload"]["resume"]
}
export type Endpoint4_8Output = EffectValue<ReturnType<RawClient["server.session"]["session.prompt"]>>["data"]
export type SessionPromptOperation<E = never> = (input: Endpoint4_8Input) => Effect.Effect<Endpoint4_8Output, E>
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.command"]>[0]
export type Endpoint4_9Input = {
readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
readonly id?: Endpoint4_9Request["payload"]["id"]
readonly command: Endpoint4_9Request["payload"]["command"]
readonly arguments?: Endpoint4_9Request["payload"]["arguments"]
readonly agent?: Endpoint4_9Request["payload"]["agent"]
readonly model?: Endpoint4_9Request["payload"]["model"]
readonly files?: Endpoint4_9Request["payload"]["files"]
readonly agents?: Endpoint4_9Request["payload"]["agents"]
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
readonly resume?: Endpoint4_9Request["payload"]["resume"]
}
export type Endpoint4_9Output = EffectValue<ReturnType<RawClient["server.session"]["session.command"]>>["data"]
export type SessionCommandOperation<E = never> = (input: Endpoint4_9Input) => Effect.Effect<Endpoint4_9Output, E>
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
export type Endpoint4_10Input = {
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
readonly id?: Endpoint4_10Request["payload"]["id"]
readonly skill: Endpoint4_10Request["payload"]["skill"]
readonly resume?: Endpoint4_10Request["payload"]["resume"]
}
export type Endpoint4_10Output = EffectValue<ReturnType<RawClient["server.session"]["session.skill"]>>
export type SessionSkillOperation<E = never> = (input: Endpoint4_10Input) => Effect.Effect<Endpoint4_10Output, E>
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
export type Endpoint4_11Input = {
readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
readonly text: Endpoint4_11Request["payload"]["text"]
readonly description?: Endpoint4_11Request["payload"]["description"]
readonly metadata?: Endpoint4_11Request["payload"]["metadata"]
}
export type Endpoint4_11Output = EffectValue<ReturnType<RawClient["server.session"]["session.synthetic"]>>
export type SessionSyntheticOperation<E = never> = (input: Endpoint4_11Input) => Effect.Effect<Endpoint4_11Output, E>
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
export type Endpoint4_12Input = {
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
readonly id?: Endpoint4_12Request["payload"]["id"]
readonly command: Endpoint4_12Request["payload"]["command"]
}
export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.session"]["session.shell"]>>
export type SessionShellOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
export type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>
export type SessionCompactOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
export type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] }
export type Endpoint4_14Output = EffectValue<ReturnType<RawClient["server.session"]["session.wait"]>>
export type SessionWaitOperation<E = never> = (input: Endpoint4_14Input) => Effect.Effect<Endpoint4_14Output, E>
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
export type Endpoint4_15Input = {
readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
readonly messageID: Endpoint4_15Request["payload"]["messageID"]
readonly files?: Endpoint4_15Request["payload"]["files"]
}
export type Endpoint4_15Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.stage"]>>["data"]
export type SessionRevertStageOperation<E = never> = (input: Endpoint4_15Input) => Effect.Effect<Endpoint4_15Output, E>
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
export type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
export type Endpoint4_16Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.clear"]>>
export type SessionRevertClearOperation<E = never> = (input: Endpoint4_16Input) => Effect.Effect<Endpoint4_16Output, E>
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
export type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
export type Endpoint4_17Output = EffectValue<ReturnType<RawClient["server.session"]["session.revert.commit"]>>
export type SessionRevertCommitOperation<E = never> = (input: Endpoint4_17Input) => Effect.Effect<Endpoint4_17Output, E>
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.context"]>[0]
export type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
export type Endpoint4_18Output = EffectValue<ReturnType<RawClient["server.session"]["session.context"]>>["data"]
export type SessionContextOperation<E = never> = (input: Endpoint4_18Input) => Effect.Effect<Endpoint4_18Output, E>
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context.entry.list"]>[0]
export type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
export type Endpoint4_19Output = EffectValue<
ReturnType<RawClient["server.session"]["session.context.entry.list"]>
>["data"]
export type SessionListContextEntriesOperation<E = never> = (
input: Endpoint4_19Input,
) => Effect.Effect<Endpoint4_19Output, E>
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context.entry.put"]>[0]
export type Endpoint4_20Input = {
readonly sessionID: Endpoint4_20Request["params"]["sessionID"]
readonly key: Endpoint4_20Request["params"]["key"]
readonly value: Endpoint4_20Request["payload"]["value"]
}
export type Endpoint4_20Output = EffectValue<ReturnType<RawClient["server.session"]["session.context.entry.put"]>>
export type SessionPutContextEntryOperation<E = never> = (
input: Endpoint4_20Input,
) => Effect.Effect<Endpoint4_20Output, E>
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.context.entry.remove"]>[0]
export type Endpoint4_21Input = {
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
readonly key: Endpoint4_21Request["params"]["key"]
}
export type Endpoint4_21Output = EffectValue<ReturnType<RawClient["server.session"]["session.context.entry.remove"]>>
export type SessionRemoveContextEntryOperation<E = never> = (
input: Endpoint4_21Input,
) => Effect.Effect<Endpoint4_21Output, E>
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.log"]>[0]
export type Endpoint4_22Input = {
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
readonly after?: Endpoint4_22Request["query"]["after"]
readonly follow?: Endpoint4_22Request["query"]["follow"]
}
export type Endpoint4_22Output = StreamValue<EffectValue<ReturnType<RawClient["server.session"]["session.log"]>>>
export type SessionLogOperation<E = never> = (input: Endpoint4_22Input) => Stream.Stream<Endpoint4_22Output, E>
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
export type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] }
export type Endpoint4_23Output = EffectValue<ReturnType<RawClient["server.session"]["session.interrupt"]>>
export type SessionInterruptOperation<E = never> = (input: Endpoint4_23Input) => Effect.Effect<Endpoint4_23Output, E>
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.background"]>[0]
export type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
export type Endpoint4_24Output = EffectValue<ReturnType<RawClient["server.session"]["session.background"]>>
export type SessionBackgroundOperation<E = never> = (input: Endpoint4_24Input) => Effect.Effect<Endpoint4_24Output, E>
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.message"]>[0]
export type Endpoint4_25Input = {
readonly sessionID: Endpoint4_25Request["params"]["sessionID"]
readonly messageID: Endpoint4_25Request["params"]["messageID"]
}
export type Endpoint4_25Output = EffectValue<ReturnType<RawClient["server.session"]["session.message"]>>["data"]
export type SessionMessageOperation<E = never> = (input: Endpoint4_25Input) => Effect.Effect<Endpoint4_25Output, E>
export interface SessionApi<E = never> {
readonly list: SessionListOperation<E>
readonly create: SessionCreateOperation<E>
readonly active: SessionActiveOperation<E>
readonly get: SessionGetOperation<E>
readonly fork: SessionForkOperation<E>
readonly switchAgent: SessionSwitchAgentOperation<E>
readonly switchModel: SessionSwitchModelOperation<E>
readonly rename: SessionRenameOperation<E>
readonly prompt: SessionPromptOperation<E>
readonly command: SessionCommandOperation<E>
readonly skill: SessionSkillOperation<E>
readonly synthetic: SessionSyntheticOperation<E>
readonly shell: SessionShellOperation<E>
readonly compact: SessionCompactOperation<E>
readonly wait: SessionWaitOperation<E>
readonly revertStage: SessionRevertStageOperation<E>
readonly revertClear: SessionRevertClearOperation<E>
readonly revertCommit: SessionRevertCommitOperation<E>
readonly context: SessionContextOperation<E>
readonly listContextEntries: SessionListContextEntriesOperation<E>
readonly putContextEntry: SessionPutContextEntryOperation<E>
readonly removeContextEntry: SessionRemoveContextEntryOperation<E>
readonly log: SessionLogOperation<E>
readonly interrupt: SessionInterruptOperation<E>
readonly background: SessionBackgroundOperation<E>
readonly message: SessionMessageOperation<E>
}
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
export type Endpoint5_0Input = {
readonly sessionID: Endpoint5_0Request["params"]["sessionID"]
readonly limit?: Endpoint5_0Request["query"]["limit"]
readonly order?: Endpoint5_0Request["query"]["order"]
readonly cursor?: Endpoint5_0Request["query"]["cursor"]
}
export type Endpoint5_0Output = EffectValue<ReturnType<RawClient["server.message"]["session.messages"]>>
export type MessageListOperation<E = never> = (input: Endpoint5_0Input) => Effect.Effect<Endpoint5_0Output, E>
export interface MessageApi<E = never> {
readonly list: MessageListOperation<E>
}
type Endpoint6_0Request = Parameters<RawClient["server.model"]["model.list"]>[0]
export type Endpoint6_0Input = { readonly location?: Endpoint6_0Request["query"]["location"] }
export type Endpoint6_0Output = EffectValue<ReturnType<RawClient["server.model"]["model.list"]>>
export type ModelListOperation<E = never> = (input?: Endpoint6_0Input) => Effect.Effect<Endpoint6_0Output, E>
type Endpoint6_1Request = Parameters<RawClient["server.model"]["model.default"]>[0]
export type Endpoint6_1Input = { readonly location?: Endpoint6_1Request["query"]["location"] }
export type Endpoint6_1Output = EffectValue<ReturnType<RawClient["server.model"]["model.default"]>>
export type ModelDefaultOperation<E = never> = (input?: Endpoint6_1Input) => Effect.Effect<Endpoint6_1Output, E>
export interface ModelApi<E = never> {
readonly list: ModelListOperation<E>
readonly default: ModelDefaultOperation<E>
}
type Endpoint7_0Request = Parameters<RawClient["server.generate"]["generate.text"]>[0]
export type Endpoint7_0Input = {
readonly location?: Endpoint7_0Request["query"]["location"]
readonly prompt: Endpoint7_0Request["payload"]["prompt"]
readonly model?: Endpoint7_0Request["payload"]["model"]
}
export type Endpoint7_0Output = EffectValue<ReturnType<RawClient["server.generate"]["generate.text"]>>["data"]
export type GenerateTextOperation<E = never> = (input: Endpoint7_0Input) => Effect.Effect<Endpoint7_0Output, E>
export interface GenerateApi<E = never> {
readonly text: GenerateTextOperation<E>
}
type Endpoint8_0Request = Parameters<RawClient["server.provider"]["provider.list"]>[0]
export type Endpoint8_0Input = { readonly location?: Endpoint8_0Request["query"]["location"] }
export type Endpoint8_0Output = EffectValue<ReturnType<RawClient["server.provider"]["provider.list"]>>
export type ProviderListOperation<E = never> = (input?: Endpoint8_0Input) => Effect.Effect<Endpoint8_0Output, E>
type Endpoint8_1Request = Parameters<RawClient["server.provider"]["provider.get"]>[0]
export type Endpoint8_1Input = {
readonly providerID: Endpoint8_1Request["params"]["providerID"]
readonly location?: Endpoint8_1Request["query"]["location"]
}
export type Endpoint8_1Output = EffectValue<ReturnType<RawClient["server.provider"]["provider.get"]>>
export type ProviderGetOperation<E = never> = (input: Endpoint8_1Input) => Effect.Effect<Endpoint8_1Output, E>
export interface ProviderApi<E = never> {
readonly list: ProviderListOperation<E>
readonly get: ProviderGetOperation<E>
}
type Endpoint9_0Request = Parameters<RawClient["server.integration"]["integration.list"]>[0]
export type Endpoint9_0Input = { readonly location?: Endpoint9_0Request["query"]["location"] }
export type Endpoint9_0Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.list"]>>
export type IntegrationListOperation<E = never> = (input?: Endpoint9_0Input) => Effect.Effect<Endpoint9_0Output, E>
type Endpoint9_1Request = Parameters<RawClient["server.integration"]["integration.get"]>[0]
export type Endpoint9_1Input = {
readonly integrationID: Endpoint9_1Request["params"]["integrationID"]
readonly location?: Endpoint9_1Request["query"]["location"]
}
export type Endpoint9_1Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.get"]>>
export type IntegrationGetOperation<E = never> = (input: Endpoint9_1Input) => Effect.Effect<Endpoint9_1Output, E>
type Endpoint9_2Request = Parameters<RawClient["server.integration"]["integration.connect.key"]>[0]
export type Endpoint9_2Input = {
readonly integrationID: Endpoint9_2Request["params"]["integrationID"]
readonly location?: Endpoint9_2Request["query"]["location"]
readonly key: Endpoint9_2Request["payload"]["key"]
readonly label?: Endpoint9_2Request["payload"]["label"]
}
export type Endpoint9_2Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.connect.key"]>>
export type IntegrationConnectKeyOperation<E = never> = (input: Endpoint9_2Input) => Effect.Effect<Endpoint9_2Output, E>
type Endpoint9_3Request = Parameters<RawClient["server.integration"]["integration.connect.oauth"]>[0]
export type Endpoint9_3Input = {
readonly integrationID: Endpoint9_3Request["params"]["integrationID"]
readonly location?: Endpoint9_3Request["query"]["location"]
readonly methodID: Endpoint9_3Request["payload"]["methodID"]
readonly inputs: Endpoint9_3Request["payload"]["inputs"]
readonly label?: Endpoint9_3Request["payload"]["label"]
}
export type Endpoint9_3Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.connect.oauth"]>>
export type IntegrationConnectOauthOperation<E = never> = (
input: Endpoint9_3Input,
) => Effect.Effect<Endpoint9_3Output, E>
type Endpoint9_4Request = Parameters<RawClient["server.integration"]["integration.attempt.status"]>[0]
export type Endpoint9_4Input = {
readonly attemptID: Endpoint9_4Request["params"]["attemptID"]
readonly location?: Endpoint9_4Request["query"]["location"]
}
export type Endpoint9_4Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.attempt.status"]>>
export type IntegrationAttemptStatusOperation<E = never> = (
input: Endpoint9_4Input,
) => Effect.Effect<Endpoint9_4Output, E>
type Endpoint9_5Request = Parameters<RawClient["server.integration"]["integration.attempt.complete"]>[0]
export type Endpoint9_5Input = {
readonly attemptID: Endpoint9_5Request["params"]["attemptID"]
readonly location?: Endpoint9_5Request["query"]["location"]
readonly code?: Endpoint9_5Request["payload"]["code"]
}
export type Endpoint9_5Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.attempt.complete"]>>
export type IntegrationAttemptCompleteOperation<E = never> = (
input: Endpoint9_5Input,
) => Effect.Effect<Endpoint9_5Output, E>
type Endpoint9_6Request = Parameters<RawClient["server.integration"]["integration.attempt.cancel"]>[0]
export type Endpoint9_6Input = {
readonly attemptID: Endpoint9_6Request["params"]["attemptID"]
readonly location?: Endpoint9_6Request["query"]["location"]
}
export type Endpoint9_6Output = EffectValue<ReturnType<RawClient["server.integration"]["integration.attempt.cancel"]>>
export type IntegrationAttemptCancelOperation<E = never> = (
input: Endpoint9_6Input,
) => Effect.Effect<Endpoint9_6Output, E>
export interface IntegrationApi<E = never> {
readonly list: IntegrationListOperation<E>
readonly get: IntegrationGetOperation<E>
readonly connectKey: IntegrationConnectKeyOperation<E>
readonly connectOauth: IntegrationConnectOauthOperation<E>
readonly attemptStatus: IntegrationAttemptStatusOperation<E>
readonly attemptComplete: IntegrationAttemptCompleteOperation<E>
readonly attemptCancel: IntegrationAttemptCancelOperation<E>
}
type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
export type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["location"] }
export type Endpoint10_0Output = EffectValue<ReturnType<RawClient["server.mcp"]["mcp.list"]>>
export type ServerMcpListOperation<E = never> = (input?: Endpoint10_0Input) => Effect.Effect<Endpoint10_0Output, E>
export interface ServerMcpApi<E = never> {
readonly list: ServerMcpListOperation<E>
}
type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
export type Endpoint11_0Input = {
readonly credentialID: Endpoint11_0Request["params"]["credentialID"]
readonly location?: Endpoint11_0Request["query"]["location"]
readonly label: Endpoint11_0Request["payload"]["label"]
}
export type Endpoint11_0Output = EffectValue<ReturnType<RawClient["server.credential"]["credential.update"]>>
export type CredentialUpdateOperation<E = never> = (input: Endpoint11_0Input) => Effect.Effect<Endpoint11_0Output, E>
type Endpoint11_1Request = Parameters<RawClient["server.credential"]["credential.remove"]>[0]
export type Endpoint11_1Input = {
readonly credentialID: Endpoint11_1Request["params"]["credentialID"]
readonly location?: Endpoint11_1Request["query"]["location"]
}
export type Endpoint11_1Output = EffectValue<ReturnType<RawClient["server.credential"]["credential.remove"]>>
export type CredentialRemoveOperation<E = never> = (input: Endpoint11_1Input) => Effect.Effect<Endpoint11_1Output, E>
export interface CredentialApi<E = never> {
readonly update: CredentialUpdateOperation<E>
readonly remove: CredentialRemoveOperation<E>
}
type Endpoint12_0Request = Parameters<RawClient["server.project"]["project.current"]>[0]
export type Endpoint12_0Input = { readonly location?: Endpoint12_0Request["query"]["location"] }
export type Endpoint12_0Output = EffectValue<ReturnType<RawClient["server.project"]["project.current"]>>
export type ProjectCurrentOperation<E = never> = (input?: Endpoint12_0Input) => Effect.Effect<Endpoint12_0Output, E>
type Endpoint12_1Request = Parameters<RawClient["server.project"]["project.directories"]>[0]
export type Endpoint12_1Input = {
readonly projectID: Endpoint12_1Request["params"]["projectID"]
readonly location?: Endpoint12_1Request["query"]["location"]
}
export type Endpoint12_1Output = EffectValue<ReturnType<RawClient["server.project"]["project.directories"]>>
export type ProjectDirectoriesOperation<E = never> = (input: Endpoint12_1Input) => Effect.Effect<Endpoint12_1Output, E>
export interface ProjectApi<E = never> {
readonly current: ProjectCurrentOperation<E>
readonly directories: ProjectDirectoriesOperation<E>
}
type Endpoint13_0Request = Parameters<RawClient["server.form"]["form.request.list"]>[0]
export type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] }
export type Endpoint13_0Output = EffectValue<ReturnType<RawClient["server.form"]["form.request.list"]>>
export type FormListRequestsOperation<E = never> = (input?: Endpoint13_0Input) => Effect.Effect<Endpoint13_0Output, E>
type Endpoint13_1Request = Parameters<RawClient["server.form"]["session.form.list"]>[0]
export type Endpoint13_1Input = { readonly sessionID: Endpoint13_1Request["params"]["sessionID"] }
export type Endpoint13_1Output = EffectValue<ReturnType<RawClient["server.form"]["session.form.list"]>>["data"]
export type FormListOperation<E = never> = (input: Endpoint13_1Input) => Effect.Effect<Endpoint13_1Output, E>
type Endpoint13_2Request = Parameters<RawClient["server.form"]["session.form.create"]>[0]
export type Endpoint13_2Input = {
readonly sessionID: Endpoint13_2Request["params"]["sessionID"]
readonly id?: Endpoint13_2Request["payload"]["id"]
readonly title?: Endpoint13_2Request["payload"]["title"]
readonly metadata?: Endpoint13_2Request["payload"]["metadata"]
readonly mode: Endpoint13_2Request["payload"]["mode"]
readonly fields?: Endpoint13_2Request["payload"]["fields"]
readonly url?: Endpoint13_2Request["payload"]["url"]
}
export type Endpoint13_2Output = EffectValue<ReturnType<RawClient["server.form"]["session.form.create"]>>["data"]
export type FormCreateOperation<E = never> = (input: Endpoint13_2Input) => Effect.Effect<Endpoint13_2Output, E>
type Endpoint13_3Request = Parameters<RawClient["server.form"]["session.form.get"]>[0]
export type Endpoint13_3Input = {
readonly sessionID: Endpoint13_3Request["params"]["sessionID"]
readonly formID: Endpoint13_3Request["params"]["formID"]
}
export type Endpoint13_3Output = EffectValue<ReturnType<RawClient["server.form"]["session.form.get"]>>["data"]
export type FormGetOperation<E = never> = (input: Endpoint13_3Input) => Effect.Effect<Endpoint13_3Output, E>
type Endpoint13_4Request = Parameters<RawClient["server.form"]["session.form.state"]>[0]
export type Endpoint13_4Input = {
readonly sessionID: Endpoint13_4Request["params"]["sessionID"]
readonly formID: Endpoint13_4Request["params"]["formID"]
}
export type Endpoint13_4Output = EffectValue<ReturnType<RawClient["server.form"]["session.form.state"]>>["data"]
export type FormStateOperation<E = never> = (input: Endpoint13_4Input) => Effect.Effect<Endpoint13_4Output, E>
type Endpoint13_5Request = Parameters<RawClient["server.form"]["session.form.reply"]>[0]
export type Endpoint13_5Input = {
readonly sessionID: Endpoint13_5Request["params"]["sessionID"]
readonly formID: Endpoint13_5Request["params"]["formID"]
readonly answer: Endpoint13_5Request["payload"]["answer"]
}
export type Endpoint13_5Output = EffectValue<ReturnType<RawClient["server.form"]["session.form.reply"]>>
export type FormReplyOperation<E = never> = (input: Endpoint13_5Input) => Effect.Effect<Endpoint13_5Output, E>
type Endpoint13_6Request = Parameters<RawClient["server.form"]["session.form.cancel"]>[0]
export type Endpoint13_6Input = {
readonly sessionID: Endpoint13_6Request["params"]["sessionID"]
readonly formID: Endpoint13_6Request["params"]["formID"]
}
export type Endpoint13_6Output = EffectValue<ReturnType<RawClient["server.form"]["session.form.cancel"]>>
export type FormCancelOperation<E = never> = (input: Endpoint13_6Input) => Effect.Effect<Endpoint13_6Output, E>
export interface FormApi<E = never> {
readonly listRequests: FormListRequestsOperation<E>
readonly list: FormListOperation<E>
readonly create: FormCreateOperation<E>
readonly get: FormGetOperation<E>
readonly state: FormStateOperation<E>
readonly reply: FormReplyOperation<E>
readonly cancel: FormCancelOperation<E>
}
type Endpoint14_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0]
export type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] }
export type Endpoint14_0Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.request.list"]>>
export type PermissionListRequestsOperation<E = never> = (
input?: Endpoint14_0Input,
) => Effect.Effect<Endpoint14_0Output, E>
type Endpoint14_1Request = Parameters<RawClient["server.permission"]["permission.saved.list"]>[0]
export type Endpoint14_1Input = { readonly projectID?: Endpoint14_1Request["query"]["projectID"] }
export type Endpoint14_1Output = EffectValue<
ReturnType<RawClient["server.permission"]["permission.saved.list"]>
>["data"]
export type PermissionListSavedOperation<E = never> = (
input?: Endpoint14_1Input,
) => Effect.Effect<Endpoint14_1Output, E>
type Endpoint14_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0]
export type Endpoint14_2Input = { readonly id: Endpoint14_2Request["params"]["id"] }
export type Endpoint14_2Output = EffectValue<ReturnType<RawClient["server.permission"]["permission.saved.remove"]>>
export type PermissionRemoveSavedOperation<E = never> = (
input: Endpoint14_2Input,
) => Effect.Effect<Endpoint14_2Output, E>
type Endpoint14_3Request = Parameters<RawClient["server.permission"]["session.permission.create"]>[0]
export type Endpoint14_3Input = {
readonly sessionID: Endpoint14_3Request["params"]["sessionID"]
readonly id?: Endpoint14_3Request["payload"]["id"]
readonly action: Endpoint14_3Request["payload"]["action"]
readonly resources: Endpoint14_3Request["payload"]["resources"]
readonly save?: Endpoint14_3Request["payload"]["save"]
readonly metadata?: Endpoint14_3Request["payload"]["metadata"]
readonly source?: Endpoint14_3Request["payload"]["source"]
readonly agent?: Endpoint14_3Request["payload"]["agent"]
}
export type Endpoint14_3Output = EffectValue<
ReturnType<RawClient["server.permission"]["session.permission.create"]>
>["data"]
export type PermissionCreateOperation<E = never> = (input: Endpoint14_3Input) => Effect.Effect<Endpoint14_3Output, E>
type Endpoint14_4Request = Parameters<RawClient["server.permission"]["session.permission.list"]>[0]
export type Endpoint14_4Input = { readonly sessionID: Endpoint14_4Request["params"]["sessionID"] }
export type Endpoint14_4Output = EffectValue<
ReturnType<RawClient["server.permission"]["session.permission.list"]>
>["data"]
export type PermissionListOperation<E = never> = (input: Endpoint14_4Input) => Effect.Effect<Endpoint14_4Output, E>
type Endpoint14_5Request = Parameters<RawClient["server.permission"]["session.permission.get"]>[0]
export type Endpoint14_5Input = {
readonly sessionID: Endpoint14_5Request["params"]["sessionID"]
readonly requestID: Endpoint14_5Request["params"]["requestID"]
}
export type Endpoint14_5Output = EffectValue<
ReturnType<RawClient["server.permission"]["session.permission.get"]>
>["data"]
export type PermissionGetOperation<E = never> = (input: Endpoint14_5Input) => Effect.Effect<Endpoint14_5Output, E>
type Endpoint14_6Request = Parameters<RawClient["server.permission"]["session.permission.reply"]>[0]
export type Endpoint14_6Input = {
readonly sessionID: Endpoint14_6Request["params"]["sessionID"]
readonly requestID: Endpoint14_6Request["params"]["requestID"]
readonly reply: Endpoint14_6Request["payload"]["reply"]
readonly message?: Endpoint14_6Request["payload"]["message"]
}
export type Endpoint14_6Output = EffectValue<ReturnType<RawClient["server.permission"]["session.permission.reply"]>>
export type PermissionReplyOperation<E = never> = (input: Endpoint14_6Input) => Effect.Effect<Endpoint14_6Output, E>
export interface PermissionApi<E = never> {
readonly listRequests: PermissionListRequestsOperation<E>
readonly listSaved: PermissionListSavedOperation<E>
readonly removeSaved: PermissionRemoveSavedOperation<E>
readonly create: PermissionCreateOperation<E>
readonly list: PermissionListOperation<E>
readonly get: PermissionGetOperation<E>
readonly reply: PermissionReplyOperation<E>
}
type Endpoint15_0Request = Parameters<RawClient["server.fs"]["fs.list"]>[0]
export type Endpoint15_0Input = {
readonly location?: Endpoint15_0Request["query"]["location"]
readonly path?: Endpoint15_0Request["query"]["path"]
}
export type Endpoint15_0Output = EffectValue<ReturnType<RawClient["server.fs"]["fs.list"]>>
export type FileListOperation<E = never> = (input?: Endpoint15_0Input) => Effect.Effect<Endpoint15_0Output, E>
type Endpoint15_1Request = Parameters<RawClient["server.fs"]["fs.find"]>[0]
export type Endpoint15_1Input = {
readonly location?: Endpoint15_1Request["query"]["location"]
readonly query: Endpoint15_1Request["query"]["query"]
readonly type?: Endpoint15_1Request["query"]["type"]
readonly limit?: Endpoint15_1Request["query"]["limit"]
}
export type Endpoint15_1Output = EffectValue<ReturnType<RawClient["server.fs"]["fs.find"]>>
export type FileFindOperation<E = never> = (input: Endpoint15_1Input) => Effect.Effect<Endpoint15_1Output, E>
export interface FileApi<E = never> {
readonly list: FileListOperation<E>
readonly find: FileFindOperation<E>
}
type Endpoint16_0Request = Parameters<RawClient["server.command"]["command.list"]>[0]
export type Endpoint16_0Input = { readonly location?: Endpoint16_0Request["query"]["location"] }
export type Endpoint16_0Output = EffectValue<ReturnType<RawClient["server.command"]["command.list"]>>
export type CommandListOperation<E = never> = (input?: Endpoint16_0Input) => Effect.Effect<Endpoint16_0Output, E>
export interface CommandApi<E = never> {
readonly list: CommandListOperation<E>
}
type Endpoint17_0Request = Parameters<RawClient["server.skill"]["skill.list"]>[0]
export type Endpoint17_0Input = { readonly location?: Endpoint17_0Request["query"]["location"] }
export type Endpoint17_0Output = EffectValue<ReturnType<RawClient["server.skill"]["skill.list"]>>
export type SkillListOperation<E = never> = (input?: Endpoint17_0Input) => Effect.Effect<Endpoint17_0Output, E>
export interface SkillApi<E = never> {
readonly list: SkillListOperation<E>
}
export type Endpoint18_0Output = StreamValue<EffectValue<ReturnType<RawClient["server.event"]["event.subscribe"]>>>
export type EventSubscribeOperation<E = never> = () => Stream.Stream<Endpoint18_0Output, E>
export type Endpoint18_1Output = StreamValue<EffectValue<ReturnType<RawClient["server.event"]["event.changes"]>>>
export type EventChangesOperation<E = never> = () => Stream.Stream<Endpoint18_1Output, E>
export interface EventApi<E = never> {
readonly subscribe: EventSubscribeOperation<E>
readonly changes: EventChangesOperation<E>
}
type Endpoint19_0Request = Parameters<RawClient["server.pty"]["pty.list"]>[0]
export type Endpoint19_0Input = { readonly location?: Endpoint19_0Request["query"]["location"] }
export type Endpoint19_0Output = EffectValue<ReturnType<RawClient["server.pty"]["pty.list"]>>
export type PtyListOperation<E = never> = (input?: Endpoint19_0Input) => Effect.Effect<Endpoint19_0Output, E>
type Endpoint19_1Request = Parameters<RawClient["server.pty"]["pty.create"]>[0]
export type Endpoint19_1Input = {
readonly location?: Endpoint19_1Request["query"]["location"]
readonly command?: Endpoint19_1Request["payload"]["command"]
readonly args?: Endpoint19_1Request["payload"]["args"]
readonly cwd?: Endpoint19_1Request["payload"]["cwd"]
readonly title?: Endpoint19_1Request["payload"]["title"]
readonly env?: Endpoint19_1Request["payload"]["env"]
}
export type Endpoint19_1Output = EffectValue<ReturnType<RawClient["server.pty"]["pty.create"]>>
export type PtyCreateOperation<E = never> = (input?: Endpoint19_1Input) => Effect.Effect<Endpoint19_1Output, E>
type Endpoint19_2Request = Parameters<RawClient["server.pty"]["pty.get"]>[0]
export type Endpoint19_2Input = {
readonly ptyID: Endpoint19_2Request["params"]["ptyID"]
readonly location?: Endpoint19_2Request["query"]["location"]
}
export type Endpoint19_2Output = EffectValue<ReturnType<RawClient["server.pty"]["pty.get"]>>
export type PtyGetOperation<E = never> = (input: Endpoint19_2Input) => Effect.Effect<Endpoint19_2Output, E>
type Endpoint19_3Request = Parameters<RawClient["server.pty"]["pty.update"]>[0]
export type Endpoint19_3Input = {
readonly ptyID: Endpoint19_3Request["params"]["ptyID"]
readonly location?: Endpoint19_3Request["query"]["location"]
readonly title?: Endpoint19_3Request["payload"]["title"]
readonly size?: Endpoint19_3Request["payload"]["size"]
}
export type Endpoint19_3Output = EffectValue<ReturnType<RawClient["server.pty"]["pty.update"]>>
export type PtyUpdateOperation<E = never> = (input: Endpoint19_3Input) => Effect.Effect<Endpoint19_3Output, E>
type Endpoint19_4Request = Parameters<RawClient["server.pty"]["pty.remove"]>[0]
export type Endpoint19_4Input = {
readonly ptyID: Endpoint19_4Request["params"]["ptyID"]
readonly location?: Endpoint19_4Request["query"]["location"]
}
export type Endpoint19_4Output = EffectValue<ReturnType<RawClient["server.pty"]["pty.remove"]>>
export type PtyRemoveOperation<E = never> = (input: Endpoint19_4Input) => Effect.Effect<Endpoint19_4Output, E>
export interface PtyApi<E = never> {
readonly list: PtyListOperation<E>
readonly create: PtyCreateOperation<E>
readonly get: PtyGetOperation<E>
readonly update: PtyUpdateOperation<E>
readonly remove: PtyRemoveOperation<E>
}
type Endpoint20_0Request = Parameters<RawClient["server.shell"]["shell.list"]>[0]
export type Endpoint20_0Input = { readonly location?: Endpoint20_0Request["query"]["location"] }
export type Endpoint20_0Output = EffectValue<ReturnType<RawClient["server.shell"]["shell.list"]>>
export type ShellListOperation<E = never> = (input?: Endpoint20_0Input) => Effect.Effect<Endpoint20_0Output, E>
type Endpoint20_1Request = Parameters<RawClient["server.shell"]["shell.create"]>[0]
export type Endpoint20_1Input = {
readonly location?: Endpoint20_1Request["query"]["location"]
readonly command: Endpoint20_1Request["payload"]["command"]
readonly cwd?: Endpoint20_1Request["payload"]["cwd"]
readonly timeout?: Endpoint20_1Request["payload"]["timeout"]
readonly metadata?: Endpoint20_1Request["payload"]["metadata"]
}
export type Endpoint20_1Output = EffectValue<ReturnType<RawClient["server.shell"]["shell.create"]>>
export type ShellCreateOperation<E = never> = (input: Endpoint20_1Input) => Effect.Effect<Endpoint20_1Output, E>
type Endpoint20_2Request = Parameters<RawClient["server.shell"]["shell.get"]>[0]
export type Endpoint20_2Input = {
readonly id: Endpoint20_2Request["params"]["id"]
readonly location?: Endpoint20_2Request["query"]["location"]
}
export type Endpoint20_2Output = EffectValue<ReturnType<RawClient["server.shell"]["shell.get"]>>
export type ShellGetOperation<E = never> = (input: Endpoint20_2Input) => Effect.Effect<Endpoint20_2Output, E>
type Endpoint20_3Request = Parameters<RawClient["server.shell"]["shell.output"]>[0]
export type Endpoint20_3Input = {
readonly id: Endpoint20_3Request["params"]["id"]
readonly location?: Endpoint20_3Request["query"]["location"]
readonly cursor?: Endpoint20_3Request["query"]["cursor"]
readonly limit?: Endpoint20_3Request["query"]["limit"]
}
export type Endpoint20_3Output = EffectValue<ReturnType<RawClient["server.shell"]["shell.output"]>>
export type ShellOutputOperation<E = never> = (input: Endpoint20_3Input) => Effect.Effect<Endpoint20_3Output, E>
type Endpoint20_4Request = Parameters<RawClient["server.shell"]["shell.remove"]>[0]
export type Endpoint20_4Input = {
readonly id: Endpoint20_4Request["params"]["id"]
readonly location?: Endpoint20_4Request["query"]["location"]
}
export type Endpoint20_4Output = EffectValue<ReturnType<RawClient["server.shell"]["shell.remove"]>>
export type ShellRemoveOperation<E = never> = (input: Endpoint20_4Input) => Effect.Effect<Endpoint20_4Output, E>
export interface ShellApi<E = never> {
readonly list: ShellListOperation<E>
readonly create: ShellCreateOperation<E>
readonly get: ShellGetOperation<E>
readonly output: ShellOutputOperation<E>
readonly remove: ShellRemoveOperation<E>
}
type Endpoint21_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0]
export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] }
export type Endpoint21_0Output = EffectValue<ReturnType<RawClient["server.question"]["question.request.list"]>>
export type QuestionListRequestsOperation<E = never> = (
input?: Endpoint21_0Input,
) => Effect.Effect<Endpoint21_0Output, E>
type Endpoint21_1Request = Parameters<RawClient["server.question"]["session.question.list"]>[0]
export type Endpoint21_1Input = { readonly sessionID: Endpoint21_1Request["params"]["sessionID"] }
export type Endpoint21_1Output = EffectValue<ReturnType<RawClient["server.question"]["session.question.list"]>>["data"]
export type QuestionListOperation<E = never> = (input: Endpoint21_1Input) => Effect.Effect<Endpoint21_1Output, E>
type Endpoint21_2Request = Parameters<RawClient["server.question"]["session.question.reply"]>[0]
export type Endpoint21_2Input = {
readonly sessionID: Endpoint21_2Request["params"]["sessionID"]
readonly requestID: Endpoint21_2Request["params"]["requestID"]
readonly answers: Endpoint21_2Request["payload"]["answers"]
}
export type Endpoint21_2Output = EffectValue<ReturnType<RawClient["server.question"]["session.question.reply"]>>
export type QuestionReplyOperation<E = never> = (input: Endpoint21_2Input) => Effect.Effect<Endpoint21_2Output, E>
type Endpoint21_3Request = Parameters<RawClient["server.question"]["session.question.reject"]>[0]
export type Endpoint21_3Input = {
readonly sessionID: Endpoint21_3Request["params"]["sessionID"]
readonly requestID: Endpoint21_3Request["params"]["requestID"]
}
export type Endpoint21_3Output = EffectValue<ReturnType<RawClient["server.question"]["session.question.reject"]>>
export type QuestionRejectOperation<E = never> = (input: Endpoint21_3Input) => Effect.Effect<Endpoint21_3Output, E>
export interface QuestionApi<E = never> {
readonly listRequests: QuestionListRequestsOperation<E>
readonly list: QuestionListOperation<E>
readonly reply: QuestionReplyOperation<E>
readonly reject: QuestionRejectOperation<E>
}
type Endpoint22_0Request = Parameters<RawClient["server.reference"]["reference.list"]>[0]
export type Endpoint22_0Input = { readonly location?: Endpoint22_0Request["query"]["location"] }
export type Endpoint22_0Output = EffectValue<ReturnType<RawClient["server.reference"]["reference.list"]>>
export type ReferenceListOperation<E = never> = (input?: Endpoint22_0Input) => Effect.Effect<Endpoint22_0Output, E>
export interface ReferenceApi<E = never> {
readonly list: ReferenceListOperation<E>
}
type Endpoint23_0Request = Parameters<RawClient["server.projectCopy"]["projectCopy.create"]>[0]
export type Endpoint23_0Input = {
readonly projectID: Endpoint23_0Request["params"]["projectID"]
readonly location?: Endpoint23_0Request["query"]["location"]
readonly strategy: Endpoint23_0Request["payload"]["strategy"]
readonly directory: Endpoint23_0Request["payload"]["directory"]
readonly name?: Endpoint23_0Request["payload"]["name"]
}
export type Endpoint23_0Output = EffectValue<ReturnType<RawClient["server.projectCopy"]["projectCopy.create"]>>
export type ProjectCopyCreateOperation<E = never> = (input: Endpoint23_0Input) => Effect.Effect<Endpoint23_0Output, E>
type Endpoint23_1Request = Parameters<RawClient["server.projectCopy"]["projectCopy.remove"]>[0]
export type Endpoint23_1Input = {
readonly projectID: Endpoint23_1Request["params"]["projectID"]
readonly location?: Endpoint23_1Request["query"]["location"]
readonly directory: Endpoint23_1Request["payload"]["directory"]
readonly force: Endpoint23_1Request["payload"]["force"]
}
export type Endpoint23_1Output = EffectValue<ReturnType<RawClient["server.projectCopy"]["projectCopy.remove"]>>
export type ProjectCopyRemoveOperation<E = never> = (input: Endpoint23_1Input) => Effect.Effect<Endpoint23_1Output, E>
type Endpoint23_2Request = Parameters<RawClient["server.projectCopy"]["projectCopy.refresh"]>[0]
export type Endpoint23_2Input = {
readonly projectID: Endpoint23_2Request["params"]["projectID"]
readonly location?: Endpoint23_2Request["query"]["location"]
}
export type Endpoint23_2Output = EffectValue<ReturnType<RawClient["server.projectCopy"]["projectCopy.refresh"]>>
export type ProjectCopyRefreshOperation<E = never> = (input: Endpoint23_2Input) => Effect.Effect<Endpoint23_2Output, E>
export interface ProjectCopyApi<E = never> {
readonly create: ProjectCopyCreateOperation<E>
readonly remove: ProjectCopyRemoveOperation<E>
readonly refresh: ProjectCopyRefreshOperation<E>
}
type Endpoint24_0Request = Parameters<RawClient["server.vcs"]["vcs.status"]>[0]
export type Endpoint24_0Input = { readonly location?: Endpoint24_0Request["query"]["location"] }
export type Endpoint24_0Output = EffectValue<ReturnType<RawClient["server.vcs"]["vcs.status"]>>
export type VcsStatusOperation<E = never> = (input?: Endpoint24_0Input) => Effect.Effect<Endpoint24_0Output, E>
type Endpoint24_1Request = Parameters<RawClient["server.vcs"]["vcs.diff"]>[0]
export type Endpoint24_1Input = {
readonly location?: Endpoint24_1Request["query"]["location"]
readonly mode: Endpoint24_1Request["query"]["mode"]
readonly context?: Endpoint24_1Request["query"]["context"]
}
export type Endpoint24_1Output = EffectValue<ReturnType<RawClient["server.vcs"]["vcs.diff"]>>
export type VcsDiffOperation<E = never> = (input: Endpoint24_1Input) => Effect.Effect<Endpoint24_1Output, E>
export interface VcsApi<E = never> {
readonly status: VcsStatusOperation<E>
readonly diff: VcsDiffOperation<E>
}
export interface AppApi<E = never> {
readonly health: HealthApi<E>
readonly location: LocationApi<E>
readonly agent: AgentApi<E>
readonly plugin: PluginApi<E>
readonly session: SessionApi<E>
readonly message: MessageApi<E>
readonly model: ModelApi<E>
readonly generate: GenerateApi<E>
readonly provider: ProviderApi<E>
readonly integration: IntegrationApi<E>
readonly "server.mcp": ServerMcpApi<E>
readonly credential: CredentialApi<E>
readonly project: ProjectApi<E>
readonly form: FormApi<E>
readonly permission: PermissionApi<E>
readonly file: FileApi<E>
readonly command: CommandApi<E>
readonly skill: SkillApi<E>
readonly event: EventApi<E>
readonly pty: PtyApi<E>
readonly shell: ShellApi<E>
readonly question: QuestionApi<E>
readonly reference: ReferenceApi<E>
readonly projectCopy: ProjectCopyApi<E>
readonly vcs: VcsApi<E>
}
@@ -1,1075 +0,0 @@
// Generated by @opencode-ai/httpapi-codegen. Do not edit.
import { Effect, Stream, Schema } from "effect"
import { Sse } from "effect/unstable/encoding"
import { HttpClientError } from "effect/unstable/http"
import { HttpApiClient } from "effect/unstable/httpapi"
import { ClientApi } from "../../contract"
import { ClientError } from "./client-error"
type RawClient = HttpApiClient.ForApi<typeof ClientApi>
const mapClientError = <E>(error: E) =>
HttpClientError.isHttpClientError(error) || Schema.isSchemaError(error) || Sse.Retry.is(error)
? new ClientError({ cause: error })
: error
const Endpoint0_0 = (raw: RawClient["server.health"]) => () =>
raw["health.get"]({}).pipe(Effect.mapError(mapClientError))
const adaptGroup0 = (raw: RawClient["server.health"]) => ({ get: Endpoint0_0(raw) })
type Endpoint1_0Request = Parameters<RawClient["server.location"]["location.get"]>[0]
type Endpoint1_0Input = { readonly location?: Endpoint1_0Request["query"]["location"] }
const Endpoint1_0 = (raw: RawClient["server.location"]) => (input?: Endpoint1_0Input) =>
raw["location.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup1 = (raw: RawClient["server.location"]) => ({ get: Endpoint1_0(raw) })
type Endpoint2_0Request = Parameters<RawClient["server.agent"]["agent.list"]>[0]
type Endpoint2_0Input = { readonly location?: Endpoint2_0Request["query"]["location"] }
const Endpoint2_0 = (raw: RawClient["server.agent"]) => (input?: Endpoint2_0Input) =>
raw["agent.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup2 = (raw: RawClient["server.agent"]) => ({ list: Endpoint2_0(raw) })
type Endpoint3_0Request = Parameters<RawClient["server.plugin"]["plugin.list"]>[0]
type Endpoint3_0Input = { readonly location?: Endpoint3_0Request["query"]["location"] }
const Endpoint3_0 = (raw: RawClient["server.plugin"]) => (input?: Endpoint3_0Input) =>
raw["plugin.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup3 = (raw: RawClient["server.plugin"]) => ({ list: Endpoint3_0(raw) })
type Endpoint4_0Request = Parameters<RawClient["server.session"]["session.list"]>[0]
type Endpoint4_0Input = {
readonly workspace?: Endpoint4_0Request["query"]["workspace"]
readonly limit?: Endpoint4_0Request["query"]["limit"]
readonly order?: Endpoint4_0Request["query"]["order"]
readonly search?: Endpoint4_0Request["query"]["search"]
readonly parentID?: Endpoint4_0Request["query"]["parentID"]
readonly directory?: Endpoint4_0Request["query"]["directory"]
readonly project?: Endpoint4_0Request["query"]["project"]
readonly subpath?: Endpoint4_0Request["query"]["subpath"]
readonly cursor?: Endpoint4_0Request["query"]["cursor"]
}
const Endpoint4_0 = (raw: RawClient["server.session"]) => (input?: Endpoint4_0Input) =>
raw["session.list"]({
query: {
workspace: input?.["workspace"],
limit: input?.["limit"],
order: input?.["order"],
search: input?.["search"],
parentID: input?.["parentID"],
directory: input?.["directory"],
project: input?.["project"],
subpath: input?.["subpath"],
cursor: input?.["cursor"],
},
}).pipe(Effect.mapError(mapClientError))
type Endpoint4_1Request = Parameters<RawClient["server.session"]["session.create"]>[0]
type Endpoint4_1Input = {
readonly id?: Endpoint4_1Request["payload"]["id"]
readonly agent?: Endpoint4_1Request["payload"]["agent"]
readonly model?: Endpoint4_1Request["payload"]["model"]
readonly location?: Endpoint4_1Request["payload"]["location"]
}
const Endpoint4_1 = (raw: RawClient["server.session"]) => (input?: Endpoint4_1Input) =>
raw["session.create"]({
payload: { id: input?.["id"], agent: input?.["agent"], model: input?.["model"], location: input?.["location"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
const Endpoint4_2 = (raw: RawClient["server.session"]) => () =>
raw["session.active"]({}).pipe(Effect.mapError(mapClientError))
type Endpoint4_3Request = Parameters<RawClient["server.session"]["session.get"]>[0]
type Endpoint4_3Input = { readonly sessionID: Endpoint4_3Request["params"]["sessionID"] }
const Endpoint4_3 = (raw: RawClient["server.session"]) => (input: Endpoint4_3Input) =>
raw["session.get"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint4_4Request = Parameters<RawClient["server.session"]["session.fork"]>[0]
type Endpoint4_4Input = {
readonly sessionID: Endpoint4_4Request["params"]["sessionID"]
readonly messageID?: Endpoint4_4Request["payload"]["messageID"]
}
const Endpoint4_4 = (raw: RawClient["server.session"]) => (input: Endpoint4_4Input) =>
raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint4_5Request = Parameters<RawClient["server.session"]["session.switchAgent"]>[0]
type Endpoint4_5Input = {
readonly sessionID: Endpoint4_5Request["params"]["sessionID"]
readonly agent: Endpoint4_5Request["payload"]["agent"]
}
const Endpoint4_5 = (raw: RawClient["server.session"]) => (input: Endpoint4_5Input) =>
raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint4_6Request = Parameters<RawClient["server.session"]["session.switchModel"]>[0]
type Endpoint4_6Input = {
readonly sessionID: Endpoint4_6Request["params"]["sessionID"]
readonly model: Endpoint4_6Request["payload"]["model"]
}
const Endpoint4_6 = (raw: RawClient["server.session"]) => (input: Endpoint4_6Input) =>
raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint4_7Request = Parameters<RawClient["server.session"]["session.rename"]>[0]
type Endpoint4_7Input = {
readonly sessionID: Endpoint4_7Request["params"]["sessionID"]
readonly title: Endpoint4_7Request["payload"]["title"]
}
const Endpoint4_7 = (raw: RawClient["server.session"]) => (input: Endpoint4_7Input) =>
raw["session.rename"]({ params: { sessionID: input["sessionID"] }, payload: { title: input["title"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint4_8Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
type Endpoint4_8Input = {
readonly sessionID: Endpoint4_8Request["params"]["sessionID"]
readonly id?: Endpoint4_8Request["payload"]["id"]
readonly prompt: Endpoint4_8Request["payload"]["prompt"]
readonly delivery?: Endpoint4_8Request["payload"]["delivery"]
readonly resume?: Endpoint4_8Request["payload"]["resume"]
}
const Endpoint4_8 = (raw: RawClient["server.session"]) => (input: Endpoint4_8Input) =>
raw["session.prompt"]({
params: { sessionID: input["sessionID"] },
payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint4_9Request = Parameters<RawClient["server.session"]["session.command"]>[0]
type Endpoint4_9Input = {
readonly sessionID: Endpoint4_9Request["params"]["sessionID"]
readonly id?: Endpoint4_9Request["payload"]["id"]
readonly command: Endpoint4_9Request["payload"]["command"]
readonly arguments?: Endpoint4_9Request["payload"]["arguments"]
readonly agent?: Endpoint4_9Request["payload"]["agent"]
readonly model?: Endpoint4_9Request["payload"]["model"]
readonly files?: Endpoint4_9Request["payload"]["files"]
readonly agents?: Endpoint4_9Request["payload"]["agents"]
readonly delivery?: Endpoint4_9Request["payload"]["delivery"]
readonly resume?: Endpoint4_9Request["payload"]["resume"]
}
const Endpoint4_9 = (raw: RawClient["server.session"]) => (input: Endpoint4_9Input) =>
raw["session.command"]({
params: { sessionID: input["sessionID"] },
payload: {
id: input["id"],
command: input["command"],
arguments: input["arguments"],
agent: input["agent"],
model: input["model"],
files: input["files"],
agents: input["agents"],
delivery: input["delivery"],
resume: input["resume"],
},
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint4_10Request = Parameters<RawClient["server.session"]["session.skill"]>[0]
type Endpoint4_10Input = {
readonly sessionID: Endpoint4_10Request["params"]["sessionID"]
readonly id?: Endpoint4_10Request["payload"]["id"]
readonly skill: Endpoint4_10Request["payload"]["skill"]
readonly resume?: Endpoint4_10Request["payload"]["resume"]
}
const Endpoint4_10 = (raw: RawClient["server.session"]) => (input: Endpoint4_10Input) =>
raw["session.skill"]({
params: { sessionID: input["sessionID"] },
payload: { id: input["id"], skill: input["skill"], resume: input["resume"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint4_11Request = Parameters<RawClient["server.session"]["session.synthetic"]>[0]
type Endpoint4_11Input = {
readonly sessionID: Endpoint4_11Request["params"]["sessionID"]
readonly text: Endpoint4_11Request["payload"]["text"]
readonly description?: Endpoint4_11Request["payload"]["description"]
readonly metadata?: Endpoint4_11Request["payload"]["metadata"]
}
const Endpoint4_11 = (raw: RawClient["server.session"]) => (input: Endpoint4_11Input) =>
raw["session.synthetic"]({
params: { sessionID: input["sessionID"] },
payload: { text: input["text"], description: input["description"], metadata: input["metadata"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint4_12Request = Parameters<RawClient["server.session"]["session.shell"]>[0]
type Endpoint4_12Input = {
readonly sessionID: Endpoint4_12Request["params"]["sessionID"]
readonly id?: Endpoint4_12Request["payload"]["id"]
readonly command: Endpoint4_12Request["payload"]["command"]
}
const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12Input) =>
raw["session.shell"]({
params: { sessionID: input["sessionID"] },
payload: { id: input["id"], command: input["command"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] }
const Endpoint4_14 = (raw: RawClient["server.session"]) => (input: Endpoint4_14Input) =>
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_15Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
type Endpoint4_15Input = {
readonly sessionID: Endpoint4_15Request["params"]["sessionID"]
readonly messageID: Endpoint4_15Request["payload"]["messageID"]
readonly files?: Endpoint4_15Request["payload"]["files"]
}
const Endpoint4_15 = (raw: RawClient["server.session"]) => (input: Endpoint4_15Input) =>
raw["session.revert.stage"]({
params: { sessionID: input["sessionID"] },
payload: { messageID: input["messageID"], files: input["files"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint4_16Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
type Endpoint4_16Input = { readonly sessionID: Endpoint4_16Request["params"]["sessionID"] }
const Endpoint4_16 = (raw: RawClient["server.session"]) => (input: Endpoint4_16Input) =>
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_17Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
type Endpoint4_17Input = { readonly sessionID: Endpoint4_17Request["params"]["sessionID"] }
const Endpoint4_17 = (raw: RawClient["server.session"]) => (input: Endpoint4_17Input) =>
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_18Request = Parameters<RawClient["server.session"]["session.context"]>[0]
type Endpoint4_18Input = { readonly sessionID: Endpoint4_18Request["params"]["sessionID"] }
const Endpoint4_18 = (raw: RawClient["server.session"]) => (input: Endpoint4_18Input) =>
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint4_19Request = Parameters<RawClient["server.session"]["session.context.entry.list"]>[0]
type Endpoint4_19Input = { readonly sessionID: Endpoint4_19Request["params"]["sessionID"] }
const Endpoint4_19 = (raw: RawClient["server.session"]) => (input: Endpoint4_19Input) =>
raw["session.context.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint4_20Request = Parameters<RawClient["server.session"]["session.context.entry.put"]>[0]
type Endpoint4_20Input = {
readonly sessionID: Endpoint4_20Request["params"]["sessionID"]
readonly key: Endpoint4_20Request["params"]["key"]
readonly value: Endpoint4_20Request["payload"]["value"]
}
const Endpoint4_20 = (raw: RawClient["server.session"]) => (input: Endpoint4_20Input) =>
raw["session.context.entry.put"]({
params: { sessionID: input["sessionID"], key: input["key"] },
payload: { value: input["value"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint4_21Request = Parameters<RawClient["server.session"]["session.context.entry.remove"]>[0]
type Endpoint4_21Input = {
readonly sessionID: Endpoint4_21Request["params"]["sessionID"]
readonly key: Endpoint4_21Request["params"]["key"]
}
const Endpoint4_21 = (raw: RawClient["server.session"]) => (input: Endpoint4_21Input) =>
raw["session.context.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint4_22Request = Parameters<RawClient["server.session"]["session.log"]>[0]
type Endpoint4_22Input = {
readonly sessionID: Endpoint4_22Request["params"]["sessionID"]
readonly after?: Endpoint4_22Request["query"]["after"]
readonly follow?: Endpoint4_22Request["query"]["follow"]
}
const Endpoint4_22 = (raw: RawClient["server.session"]) => (input: Endpoint4_22Input) =>
Stream.unwrap(
raw["session.log"]({
params: { sessionID: input["sessionID"] },
query: { after: input["after"], follow: input["follow"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
),
)
type Endpoint4_23Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
type Endpoint4_23Input = { readonly sessionID: Endpoint4_23Request["params"]["sessionID"] }
const Endpoint4_23 = (raw: RawClient["server.session"]) => (input: Endpoint4_23Input) =>
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_24Request = Parameters<RawClient["server.session"]["session.background"]>[0]
type Endpoint4_24Input = { readonly sessionID: Endpoint4_24Request["params"]["sessionID"] }
const Endpoint4_24 = (raw: RawClient["server.session"]) => (input: Endpoint4_24Input) =>
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint4_25Request = Parameters<RawClient["server.session"]["session.message"]>[0]
type Endpoint4_25Input = {
readonly sessionID: Endpoint4_25Request["params"]["sessionID"]
readonly messageID: Endpoint4_25Request["params"]["messageID"]
}
const Endpoint4_25 = (raw: RawClient["server.session"]) => (input: Endpoint4_25Input) =>
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
const adaptGroup4 = (raw: RawClient["server.session"]) => ({
list: Endpoint4_0(raw),
create: Endpoint4_1(raw),
active: Endpoint4_2(raw),
get: Endpoint4_3(raw),
fork: Endpoint4_4(raw),
switchAgent: Endpoint4_5(raw),
switchModel: Endpoint4_6(raw),
rename: Endpoint4_7(raw),
prompt: Endpoint4_8(raw),
command: Endpoint4_9(raw),
skill: Endpoint4_10(raw),
synthetic: Endpoint4_11(raw),
shell: Endpoint4_12(raw),
compact: Endpoint4_13(raw),
wait: Endpoint4_14(raw),
revertStage: Endpoint4_15(raw),
revertClear: Endpoint4_16(raw),
revertCommit: Endpoint4_17(raw),
context: Endpoint4_18(raw),
listContextEntries: Endpoint4_19(raw),
putContextEntry: Endpoint4_20(raw),
removeContextEntry: Endpoint4_21(raw),
log: Endpoint4_22(raw),
interrupt: Endpoint4_23(raw),
background: Endpoint4_24(raw),
message: Endpoint4_25(raw),
})
type Endpoint5_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
type Endpoint5_0Input = {
readonly sessionID: Endpoint5_0Request["params"]["sessionID"]
readonly limit?: Endpoint5_0Request["query"]["limit"]
readonly order?: Endpoint5_0Request["query"]["order"]
readonly cursor?: Endpoint5_0Request["query"]["cursor"]
}
const Endpoint5_0 = (raw: RawClient["server.message"]) => (input: Endpoint5_0Input) =>
raw["session.messages"]({
params: { sessionID: input["sessionID"] },
query: { limit: input["limit"], order: input["order"], cursor: input["cursor"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup5 = (raw: RawClient["server.message"]) => ({ list: Endpoint5_0(raw) })
type Endpoint6_0Request = Parameters<RawClient["server.model"]["model.list"]>[0]
type Endpoint6_0Input = { readonly location?: Endpoint6_0Request["query"]["location"] }
const Endpoint6_0 = (raw: RawClient["server.model"]) => (input?: Endpoint6_0Input) =>
raw["model.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint6_1Request = Parameters<RawClient["server.model"]["model.default"]>[0]
type Endpoint6_1Input = { readonly location?: Endpoint6_1Request["query"]["location"] }
const Endpoint6_1 = (raw: RawClient["server.model"]) => (input?: Endpoint6_1Input) =>
raw["model.default"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup6 = (raw: RawClient["server.model"]) => ({ list: Endpoint6_0(raw), default: Endpoint6_1(raw) })
type Endpoint7_0Request = Parameters<RawClient["server.generate"]["generate.text"]>[0]
type Endpoint7_0Input = {
readonly location?: Endpoint7_0Request["query"]["location"]
readonly prompt: Endpoint7_0Request["payload"]["prompt"]
readonly model?: Endpoint7_0Request["payload"]["model"]
}
const Endpoint7_0 = (raw: RawClient["server.generate"]) => (input: Endpoint7_0Input) =>
raw["generate.text"]({
query: { location: input["location"] },
payload: { prompt: input["prompt"], model: input["model"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
const adaptGroup7 = (raw: RawClient["server.generate"]) => ({ text: Endpoint7_0(raw) })
type Endpoint8_0Request = Parameters<RawClient["server.provider"]["provider.list"]>[0]
type Endpoint8_0Input = { readonly location?: Endpoint8_0Request["query"]["location"] }
const Endpoint8_0 = (raw: RawClient["server.provider"]) => (input?: Endpoint8_0Input) =>
raw["provider.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint8_1Request = Parameters<RawClient["server.provider"]["provider.get"]>[0]
type Endpoint8_1Input = {
readonly providerID: Endpoint8_1Request["params"]["providerID"]
readonly location?: Endpoint8_1Request["query"]["location"]
}
const Endpoint8_1 = (raw: RawClient["server.provider"]) => (input: Endpoint8_1Input) =>
raw["provider.get"]({ params: { providerID: input["providerID"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup8 = (raw: RawClient["server.provider"]) => ({ list: Endpoint8_0(raw), get: Endpoint8_1(raw) })
type Endpoint9_0Request = Parameters<RawClient["server.integration"]["integration.list"]>[0]
type Endpoint9_0Input = { readonly location?: Endpoint9_0Request["query"]["location"] }
const Endpoint9_0 = (raw: RawClient["server.integration"]) => (input?: Endpoint9_0Input) =>
raw["integration.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint9_1Request = Parameters<RawClient["server.integration"]["integration.get"]>[0]
type Endpoint9_1Input = {
readonly integrationID: Endpoint9_1Request["params"]["integrationID"]
readonly location?: Endpoint9_1Request["query"]["location"]
}
const Endpoint9_1 = (raw: RawClient["server.integration"]) => (input: Endpoint9_1Input) =>
raw["integration.get"]({
params: { integrationID: input["integrationID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint9_2Request = Parameters<RawClient["server.integration"]["integration.connect.key"]>[0]
type Endpoint9_2Input = {
readonly integrationID: Endpoint9_2Request["params"]["integrationID"]
readonly location?: Endpoint9_2Request["query"]["location"]
readonly key: Endpoint9_2Request["payload"]["key"]
readonly label?: Endpoint9_2Request["payload"]["label"]
}
const Endpoint9_2 = (raw: RawClient["server.integration"]) => (input: Endpoint9_2Input) =>
raw["integration.connect.key"]({
params: { integrationID: input["integrationID"] },
query: { location: input["location"] },
payload: { key: input["key"], label: input["label"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint9_3Request = Parameters<RawClient["server.integration"]["integration.connect.oauth"]>[0]
type Endpoint9_3Input = {
readonly integrationID: Endpoint9_3Request["params"]["integrationID"]
readonly location?: Endpoint9_3Request["query"]["location"]
readonly methodID: Endpoint9_3Request["payload"]["methodID"]
readonly inputs: Endpoint9_3Request["payload"]["inputs"]
readonly label?: Endpoint9_3Request["payload"]["label"]
}
const Endpoint9_3 = (raw: RawClient["server.integration"]) => (input: Endpoint9_3Input) =>
raw["integration.connect.oauth"]({
params: { integrationID: input["integrationID"] },
query: { location: input["location"] },
payload: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint9_4Request = Parameters<RawClient["server.integration"]["integration.attempt.status"]>[0]
type Endpoint9_4Input = {
readonly attemptID: Endpoint9_4Request["params"]["attemptID"]
readonly location?: Endpoint9_4Request["query"]["location"]
}
const Endpoint9_4 = (raw: RawClient["server.integration"]) => (input: Endpoint9_4Input) =>
raw["integration.attempt.status"]({
params: { attemptID: input["attemptID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint9_5Request = Parameters<RawClient["server.integration"]["integration.attempt.complete"]>[0]
type Endpoint9_5Input = {
readonly attemptID: Endpoint9_5Request["params"]["attemptID"]
readonly location?: Endpoint9_5Request["query"]["location"]
readonly code?: Endpoint9_5Request["payload"]["code"]
}
const Endpoint9_5 = (raw: RawClient["server.integration"]) => (input: Endpoint9_5Input) =>
raw["integration.attempt.complete"]({
params: { attemptID: input["attemptID"] },
query: { location: input["location"] },
payload: { code: input["code"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint9_6Request = Parameters<RawClient["server.integration"]["integration.attempt.cancel"]>[0]
type Endpoint9_6Input = {
readonly attemptID: Endpoint9_6Request["params"]["attemptID"]
readonly location?: Endpoint9_6Request["query"]["location"]
}
const Endpoint9_6 = (raw: RawClient["server.integration"]) => (input: Endpoint9_6Input) =>
raw["integration.attempt.cancel"]({
params: { attemptID: input["attemptID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup9 = (raw: RawClient["server.integration"]) => ({
list: Endpoint9_0(raw),
get: Endpoint9_1(raw),
connectKey: Endpoint9_2(raw),
connectOauth: Endpoint9_3(raw),
attemptStatus: Endpoint9_4(raw),
attemptComplete: Endpoint9_5(raw),
attemptCancel: Endpoint9_6(raw),
})
type Endpoint10_0Request = Parameters<RawClient["server.mcp"]["mcp.list"]>[0]
type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["location"] }
const Endpoint10_0 = (raw: RawClient["server.mcp"]) => (input?: Endpoint10_0Input) =>
raw["mcp.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup10 = (raw: RawClient["server.mcp"]) => ({ list: Endpoint10_0(raw) })
type Endpoint11_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
type Endpoint11_0Input = {
readonly credentialID: Endpoint11_0Request["params"]["credentialID"]
readonly location?: Endpoint11_0Request["query"]["location"]
readonly label: Endpoint11_0Request["payload"]["label"]
}
const Endpoint11_0 = (raw: RawClient["server.credential"]) => (input: Endpoint11_0Input) =>
raw["credential.update"]({
params: { credentialID: input["credentialID"] },
query: { location: input["location"] },
payload: { label: input["label"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint11_1Request = Parameters<RawClient["server.credential"]["credential.remove"]>[0]
type Endpoint11_1Input = {
readonly credentialID: Endpoint11_1Request["params"]["credentialID"]
readonly location?: Endpoint11_1Request["query"]["location"]
}
const Endpoint11_1 = (raw: RawClient["server.credential"]) => (input: Endpoint11_1Input) =>
raw["credential.remove"]({
params: { credentialID: input["credentialID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup11 = (raw: RawClient["server.credential"]) => ({ update: Endpoint11_0(raw), remove: Endpoint11_1(raw) })
type Endpoint12_0Request = Parameters<RawClient["server.project"]["project.current"]>[0]
type Endpoint12_0Input = { readonly location?: Endpoint12_0Request["query"]["location"] }
const Endpoint12_0 = (raw: RawClient["server.project"]) => (input?: Endpoint12_0Input) =>
raw["project.current"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint12_1Request = Parameters<RawClient["server.project"]["project.directories"]>[0]
type Endpoint12_1Input = {
readonly projectID: Endpoint12_1Request["params"]["projectID"]
readonly location?: Endpoint12_1Request["query"]["location"]
}
const Endpoint12_1 = (raw: RawClient["server.project"]) => (input: Endpoint12_1Input) =>
raw["project.directories"]({
params: { projectID: input["projectID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup12 = (raw: RawClient["server.project"]) => ({
current: Endpoint12_0(raw),
directories: Endpoint12_1(raw),
})
type Endpoint13_0Request = Parameters<RawClient["server.form"]["form.request.list"]>[0]
type Endpoint13_0Input = { readonly location?: Endpoint13_0Request["query"]["location"] }
const Endpoint13_0 = (raw: RawClient["server.form"]) => (input?: Endpoint13_0Input) =>
raw["form.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint13_1Request = Parameters<RawClient["server.form"]["session.form.list"]>[0]
type Endpoint13_1Input = { readonly sessionID: Endpoint13_1Request["params"]["sessionID"] }
const Endpoint13_1 = (raw: RawClient["server.form"]) => (input: Endpoint13_1Input) =>
raw["session.form.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint13_2Request = Parameters<RawClient["server.form"]["session.form.create"]>[0]
type Endpoint13_2Input = {
readonly sessionID: Endpoint13_2Request["params"]["sessionID"]
readonly id?: Endpoint13_2Request["payload"]["id"]
readonly title?: Endpoint13_2Request["payload"]["title"]
readonly metadata?: Endpoint13_2Request["payload"]["metadata"]
readonly mode: Endpoint13_2Request["payload"]["mode"]
readonly fields?: Endpoint13_2Request["payload"]["fields"]
readonly url?: Endpoint13_2Request["payload"]["url"]
}
const Endpoint13_2 = (raw: RawClient["server.form"]) => (input: Endpoint13_2Input) =>
raw["session.form.create"]({
params: { sessionID: input["sessionID"] },
payload: {
id: input["id"],
title: input["title"],
metadata: input["metadata"],
mode: input["mode"],
fields: input["fields"],
url: input["url"],
},
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint13_3Request = Parameters<RawClient["server.form"]["session.form.get"]>[0]
type Endpoint13_3Input = {
readonly sessionID: Endpoint13_3Request["params"]["sessionID"]
readonly formID: Endpoint13_3Request["params"]["formID"]
}
const Endpoint13_3 = (raw: RawClient["server.form"]) => (input: Endpoint13_3Input) =>
raw["session.form.get"]({ params: { sessionID: input["sessionID"], formID: input["formID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint13_4Request = Parameters<RawClient["server.form"]["session.form.state"]>[0]
type Endpoint13_4Input = {
readonly sessionID: Endpoint13_4Request["params"]["sessionID"]
readonly formID: Endpoint13_4Request["params"]["formID"]
}
const Endpoint13_4 = (raw: RawClient["server.form"]) => (input: Endpoint13_4Input) =>
raw["session.form.state"]({ params: { sessionID: input["sessionID"], formID: input["formID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint13_5Request = Parameters<RawClient["server.form"]["session.form.reply"]>[0]
type Endpoint13_5Input = {
readonly sessionID: Endpoint13_5Request["params"]["sessionID"]
readonly formID: Endpoint13_5Request["params"]["formID"]
readonly answer: Endpoint13_5Request["payload"]["answer"]
}
const Endpoint13_5 = (raw: RawClient["server.form"]) => (input: Endpoint13_5Input) =>
raw["session.form.reply"]({
params: { sessionID: input["sessionID"], formID: input["formID"] },
payload: { answer: input["answer"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint13_6Request = Parameters<RawClient["server.form"]["session.form.cancel"]>[0]
type Endpoint13_6Input = {
readonly sessionID: Endpoint13_6Request["params"]["sessionID"]
readonly formID: Endpoint13_6Request["params"]["formID"]
}
const Endpoint13_6 = (raw: RawClient["server.form"]) => (input: Endpoint13_6Input) =>
raw["session.form.cancel"]({ params: { sessionID: input["sessionID"], formID: input["formID"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup13 = (raw: RawClient["server.form"]) => ({
listRequests: Endpoint13_0(raw),
list: Endpoint13_1(raw),
create: Endpoint13_2(raw),
get: Endpoint13_3(raw),
state: Endpoint13_4(raw),
reply: Endpoint13_5(raw),
cancel: Endpoint13_6(raw),
})
type Endpoint14_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0]
type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] }
const Endpoint14_0 = (raw: RawClient["server.permission"]) => (input?: Endpoint14_0Input) =>
raw["permission.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint14_1Request = Parameters<RawClient["server.permission"]["permission.saved.list"]>[0]
type Endpoint14_1Input = { readonly projectID?: Endpoint14_1Request["query"]["projectID"] }
const Endpoint14_1 = (raw: RawClient["server.permission"]) => (input?: Endpoint14_1Input) =>
raw["permission.saved.list"]({ query: { projectID: input?.["projectID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint14_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0]
type Endpoint14_2Input = { readonly id: Endpoint14_2Request["params"]["id"] }
const Endpoint14_2 = (raw: RawClient["server.permission"]) => (input: Endpoint14_2Input) =>
raw["permission.saved.remove"]({ params: { id: input["id"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint14_3Request = Parameters<RawClient["server.permission"]["session.permission.create"]>[0]
type Endpoint14_3Input = {
readonly sessionID: Endpoint14_3Request["params"]["sessionID"]
readonly id?: Endpoint14_3Request["payload"]["id"]
readonly action: Endpoint14_3Request["payload"]["action"]
readonly resources: Endpoint14_3Request["payload"]["resources"]
readonly save?: Endpoint14_3Request["payload"]["save"]
readonly metadata?: Endpoint14_3Request["payload"]["metadata"]
readonly source?: Endpoint14_3Request["payload"]["source"]
readonly agent?: Endpoint14_3Request["payload"]["agent"]
}
const Endpoint14_3 = (raw: RawClient["server.permission"]) => (input: Endpoint14_3Input) =>
raw["session.permission.create"]({
params: { sessionID: input["sessionID"] },
payload: {
id: input["id"],
action: input["action"],
resources: input["resources"],
save: input["save"],
metadata: input["metadata"],
source: input["source"],
agent: input["agent"],
},
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint14_4Request = Parameters<RawClient["server.permission"]["session.permission.list"]>[0]
type Endpoint14_4Input = { readonly sessionID: Endpoint14_4Request["params"]["sessionID"] }
const Endpoint14_4 = (raw: RawClient["server.permission"]) => (input: Endpoint14_4Input) =>
raw["session.permission.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint14_5Request = Parameters<RawClient["server.permission"]["session.permission.get"]>[0]
type Endpoint14_5Input = {
readonly sessionID: Endpoint14_5Request["params"]["sessionID"]
readonly requestID: Endpoint14_5Request["params"]["requestID"]
}
const Endpoint14_5 = (raw: RawClient["server.permission"]) => (input: Endpoint14_5Input) =>
raw["session.permission.get"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint14_6Request = Parameters<RawClient["server.permission"]["session.permission.reply"]>[0]
type Endpoint14_6Input = {
readonly sessionID: Endpoint14_6Request["params"]["sessionID"]
readonly requestID: Endpoint14_6Request["params"]["requestID"]
readonly reply: Endpoint14_6Request["payload"]["reply"]
readonly message?: Endpoint14_6Request["payload"]["message"]
}
const Endpoint14_6 = (raw: RawClient["server.permission"]) => (input: Endpoint14_6Input) =>
raw["session.permission.reply"]({
params: { sessionID: input["sessionID"], requestID: input["requestID"] },
payload: { reply: input["reply"], message: input["message"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup14 = (raw: RawClient["server.permission"]) => ({
listRequests: Endpoint14_0(raw),
listSaved: Endpoint14_1(raw),
removeSaved: Endpoint14_2(raw),
create: Endpoint14_3(raw),
list: Endpoint14_4(raw),
get: Endpoint14_5(raw),
reply: Endpoint14_6(raw),
})
type Endpoint15_0Request = Parameters<RawClient["server.fs"]["fs.list"]>[0]
type Endpoint15_0Input = {
readonly location?: Endpoint15_0Request["query"]["location"]
readonly path?: Endpoint15_0Request["query"]["path"]
}
const Endpoint15_0 = (raw: RawClient["server.fs"]) => (input?: Endpoint15_0Input) =>
raw["fs.list"]({ query: { location: input?.["location"], path: input?.["path"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint15_1Request = Parameters<RawClient["server.fs"]["fs.find"]>[0]
type Endpoint15_1Input = {
readonly location?: Endpoint15_1Request["query"]["location"]
readonly query: Endpoint15_1Request["query"]["query"]
readonly type?: Endpoint15_1Request["query"]["type"]
readonly limit?: Endpoint15_1Request["query"]["limit"]
}
const Endpoint15_1 = (raw: RawClient["server.fs"]) => (input: Endpoint15_1Input) =>
raw["fs.find"]({
query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup15 = (raw: RawClient["server.fs"]) => ({ list: Endpoint15_0(raw), find: Endpoint15_1(raw) })
type Endpoint16_0Request = Parameters<RawClient["server.command"]["command.list"]>[0]
type Endpoint16_0Input = { readonly location?: Endpoint16_0Request["query"]["location"] }
const Endpoint16_0 = (raw: RawClient["server.command"]) => (input?: Endpoint16_0Input) =>
raw["command.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup16 = (raw: RawClient["server.command"]) => ({ list: Endpoint16_0(raw) })
type Endpoint17_0Request = Parameters<RawClient["server.skill"]["skill.list"]>[0]
type Endpoint17_0Input = { readonly location?: Endpoint17_0Request["query"]["location"] }
const Endpoint17_0 = (raw: RawClient["server.skill"]) => (input?: Endpoint17_0Input) =>
raw["skill.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup17 = (raw: RawClient["server.skill"]) => ({ list: Endpoint17_0(raw) })
const Endpoint18_0 = (raw: RawClient["server.event"]) => () =>
Stream.unwrap(
raw["event.subscribe"]({}).pipe(
Effect.mapError(mapClientError),
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
),
)
const Endpoint18_1 = (raw: RawClient["server.event"]) => () =>
Stream.unwrap(
raw["event.changes"]({}).pipe(
Effect.mapError(mapClientError),
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
),
)
const adaptGroup18 = (raw: RawClient["server.event"]) => ({ subscribe: Endpoint18_0(raw), changes: Endpoint18_1(raw) })
type Endpoint19_0Request = Parameters<RawClient["server.pty"]["pty.list"]>[0]
type Endpoint19_0Input = { readonly location?: Endpoint19_0Request["query"]["location"] }
const Endpoint19_0 = (raw: RawClient["server.pty"]) => (input?: Endpoint19_0Input) =>
raw["pty.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint19_1Request = Parameters<RawClient["server.pty"]["pty.create"]>[0]
type Endpoint19_1Input = {
readonly location?: Endpoint19_1Request["query"]["location"]
readonly command?: Endpoint19_1Request["payload"]["command"]
readonly args?: Endpoint19_1Request["payload"]["args"]
readonly cwd?: Endpoint19_1Request["payload"]["cwd"]
readonly title?: Endpoint19_1Request["payload"]["title"]
readonly env?: Endpoint19_1Request["payload"]["env"]
}
const Endpoint19_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint19_1Input) =>
raw["pty.create"]({
query: { location: input?.["location"] },
payload: {
command: input?.["command"],
args: input?.["args"],
cwd: input?.["cwd"],
title: input?.["title"],
env: input?.["env"],
},
}).pipe(Effect.mapError(mapClientError))
type Endpoint19_2Request = Parameters<RawClient["server.pty"]["pty.get"]>[0]
type Endpoint19_2Input = {
readonly ptyID: Endpoint19_2Request["params"]["ptyID"]
readonly location?: Endpoint19_2Request["query"]["location"]
}
const Endpoint19_2 = (raw: RawClient["server.pty"]) => (input: Endpoint19_2Input) =>
raw["pty.get"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint19_3Request = Parameters<RawClient["server.pty"]["pty.update"]>[0]
type Endpoint19_3Input = {
readonly ptyID: Endpoint19_3Request["params"]["ptyID"]
readonly location?: Endpoint19_3Request["query"]["location"]
readonly title?: Endpoint19_3Request["payload"]["title"]
readonly size?: Endpoint19_3Request["payload"]["size"]
}
const Endpoint19_3 = (raw: RawClient["server.pty"]) => (input: Endpoint19_3Input) =>
raw["pty.update"]({
params: { ptyID: input["ptyID"] },
query: { location: input["location"] },
payload: { title: input["title"], size: input["size"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint19_4Request = Parameters<RawClient["server.pty"]["pty.remove"]>[0]
type Endpoint19_4Input = {
readonly ptyID: Endpoint19_4Request["params"]["ptyID"]
readonly location?: Endpoint19_4Request["query"]["location"]
}
const Endpoint19_4 = (raw: RawClient["server.pty"]) => (input: Endpoint19_4Input) =>
raw["pty.remove"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup19 = (raw: RawClient["server.pty"]) => ({
list: Endpoint19_0(raw),
create: Endpoint19_1(raw),
get: Endpoint19_2(raw),
update: Endpoint19_3(raw),
remove: Endpoint19_4(raw),
})
type Endpoint20_0Request = Parameters<RawClient["server.shell"]["shell.list"]>[0]
type Endpoint20_0Input = { readonly location?: Endpoint20_0Request["query"]["location"] }
const Endpoint20_0 = (raw: RawClient["server.shell"]) => (input?: Endpoint20_0Input) =>
raw["shell.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint20_1Request = Parameters<RawClient["server.shell"]["shell.create"]>[0]
type Endpoint20_1Input = {
readonly location?: Endpoint20_1Request["query"]["location"]
readonly command: Endpoint20_1Request["payload"]["command"]
readonly cwd?: Endpoint20_1Request["payload"]["cwd"]
readonly timeout?: Endpoint20_1Request["payload"]["timeout"]
readonly metadata?: Endpoint20_1Request["payload"]["metadata"]
}
const Endpoint20_1 = (raw: RawClient["server.shell"]) => (input: Endpoint20_1Input) =>
raw["shell.create"]({
query: { location: input["location"] },
payload: { command: input["command"], cwd: input["cwd"], timeout: input["timeout"], metadata: input["metadata"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint20_2Request = Parameters<RawClient["server.shell"]["shell.get"]>[0]
type Endpoint20_2Input = {
readonly id: Endpoint20_2Request["params"]["id"]
readonly location?: Endpoint20_2Request["query"]["location"]
}
const Endpoint20_2 = (raw: RawClient["server.shell"]) => (input: Endpoint20_2Input) =>
raw["shell.get"]({ params: { id: input["id"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint20_3Request = Parameters<RawClient["server.shell"]["shell.output"]>[0]
type Endpoint20_3Input = {
readonly id: Endpoint20_3Request["params"]["id"]
readonly location?: Endpoint20_3Request["query"]["location"]
readonly cursor?: Endpoint20_3Request["query"]["cursor"]
readonly limit?: Endpoint20_3Request["query"]["limit"]
}
const Endpoint20_3 = (raw: RawClient["server.shell"]) => (input: Endpoint20_3Input) =>
raw["shell.output"]({
params: { id: input["id"] },
query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint20_4Request = Parameters<RawClient["server.shell"]["shell.remove"]>[0]
type Endpoint20_4Input = {
readonly id: Endpoint20_4Request["params"]["id"]
readonly location?: Endpoint20_4Request["query"]["location"]
}
const Endpoint20_4 = (raw: RawClient["server.shell"]) => (input: Endpoint20_4Input) =>
raw["shell.remove"]({ params: { id: input["id"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup20 = (raw: RawClient["server.shell"]) => ({
list: Endpoint20_0(raw),
create: Endpoint20_1(raw),
get: Endpoint20_2(raw),
output: Endpoint20_3(raw),
remove: Endpoint20_4(raw),
})
type Endpoint21_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0]
type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] }
const Endpoint21_0 = (raw: RawClient["server.question"]) => (input?: Endpoint21_0Input) =>
raw["question.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint21_1Request = Parameters<RawClient["server.question"]["session.question.list"]>[0]
type Endpoint21_1Input = { readonly sessionID: Endpoint21_1Request["params"]["sessionID"] }
const Endpoint21_1 = (raw: RawClient["server.question"]) => (input: Endpoint21_1Input) =>
raw["session.question.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint21_2Request = Parameters<RawClient["server.question"]["session.question.reply"]>[0]
type Endpoint21_2Input = {
readonly sessionID: Endpoint21_2Request["params"]["sessionID"]
readonly requestID: Endpoint21_2Request["params"]["requestID"]
readonly answers: Endpoint21_2Request["payload"]["answers"]
}
const Endpoint21_2 = (raw: RawClient["server.question"]) => (input: Endpoint21_2Input) =>
raw["session.question.reply"]({
params: { sessionID: input["sessionID"], requestID: input["requestID"] },
payload: { answers: input["answers"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint21_3Request = Parameters<RawClient["server.question"]["session.question.reject"]>[0]
type Endpoint21_3Input = {
readonly sessionID: Endpoint21_3Request["params"]["sessionID"]
readonly requestID: Endpoint21_3Request["params"]["requestID"]
}
const Endpoint21_3 = (raw: RawClient["server.question"]) => (input: Endpoint21_3Input) =>
raw["session.question.reject"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup21 = (raw: RawClient["server.question"]) => ({
listRequests: Endpoint21_0(raw),
list: Endpoint21_1(raw),
reply: Endpoint21_2(raw),
reject: Endpoint21_3(raw),
})
type Endpoint22_0Request = Parameters<RawClient["server.reference"]["reference.list"]>[0]
type Endpoint22_0Input = { readonly location?: Endpoint22_0Request["query"]["location"] }
const Endpoint22_0 = (raw: RawClient["server.reference"]) => (input?: Endpoint22_0Input) =>
raw["reference.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup22 = (raw: RawClient["server.reference"]) => ({ list: Endpoint22_0(raw) })
type Endpoint23_0Request = Parameters<RawClient["server.projectCopy"]["projectCopy.create"]>[0]
type Endpoint23_0Input = {
readonly projectID: Endpoint23_0Request["params"]["projectID"]
readonly location?: Endpoint23_0Request["query"]["location"]
readonly strategy: Endpoint23_0Request["payload"]["strategy"]
readonly directory: Endpoint23_0Request["payload"]["directory"]
readonly name?: Endpoint23_0Request["payload"]["name"]
}
const Endpoint23_0 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint23_0Input) =>
raw["projectCopy.create"]({
params: { projectID: input["projectID"] },
query: { location: input["location"] },
payload: { strategy: input["strategy"], directory: input["directory"], name: input["name"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint23_1Request = Parameters<RawClient["server.projectCopy"]["projectCopy.remove"]>[0]
type Endpoint23_1Input = {
readonly projectID: Endpoint23_1Request["params"]["projectID"]
readonly location?: Endpoint23_1Request["query"]["location"]
readonly directory: Endpoint23_1Request["payload"]["directory"]
readonly force: Endpoint23_1Request["payload"]["force"]
}
const Endpoint23_1 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint23_1Input) =>
raw["projectCopy.remove"]({
params: { projectID: input["projectID"] },
query: { location: input["location"] },
payload: { directory: input["directory"], force: input["force"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint23_2Request = Parameters<RawClient["server.projectCopy"]["projectCopy.refresh"]>[0]
type Endpoint23_2Input = {
readonly projectID: Endpoint23_2Request["params"]["projectID"]
readonly location?: Endpoint23_2Request["query"]["location"]
}
const Endpoint23_2 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint23_2Input) =>
raw["projectCopy.refresh"]({
params: { projectID: input["projectID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup23 = (raw: RawClient["server.projectCopy"]) => ({
create: Endpoint23_0(raw),
remove: Endpoint23_1(raw),
refresh: Endpoint23_2(raw),
})
type Endpoint24_0Request = Parameters<RawClient["server.vcs"]["vcs.status"]>[0]
type Endpoint24_0Input = { readonly location?: Endpoint24_0Request["query"]["location"] }
const Endpoint24_0 = (raw: RawClient["server.vcs"]) => (input?: Endpoint24_0Input) =>
raw["vcs.status"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint24_1Request = Parameters<RawClient["server.vcs"]["vcs.diff"]>[0]
type Endpoint24_1Input = {
readonly location?: Endpoint24_1Request["query"]["location"]
readonly mode: Endpoint24_1Request["query"]["mode"]
readonly context?: Endpoint24_1Request["query"]["context"]
}
const Endpoint24_1 = (raw: RawClient["server.vcs"]) => (input: Endpoint24_1Input) =>
raw["vcs.diff"]({ query: { location: input["location"], mode: input["mode"], context: input["context"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup24 = (raw: RawClient["server.vcs"]) => ({ status: Endpoint24_0(raw), diff: Endpoint24_1(raw) })
const adaptClient = (raw: RawClient) => ({
health: adaptGroup0(raw["server.health"]),
location: adaptGroup1(raw["server.location"]),
agent: adaptGroup2(raw["server.agent"]),
plugin: adaptGroup3(raw["server.plugin"]),
session: adaptGroup4(raw["server.session"]),
message: adaptGroup5(raw["server.message"]),
model: adaptGroup6(raw["server.model"]),
generate: adaptGroup7(raw["server.generate"]),
provider: adaptGroup8(raw["server.provider"]),
integration: adaptGroup9(raw["server.integration"]),
"server.mcp": adaptGroup10(raw["server.mcp"]),
credential: adaptGroup11(raw["server.credential"]),
project: adaptGroup12(raw["server.project"]),
form: adaptGroup13(raw["server.form"]),
permission: adaptGroup14(raw["server.permission"]),
file: adaptGroup15(raw["server.fs"]),
command: adaptGroup16(raw["server.command"]),
skill: adaptGroup17(raw["server.skill"]),
event: adaptGroup18(raw["server.event"]),
pty: adaptGroup19(raw["server.pty"]),
shell: adaptGroup20(raw["server.shell"]),
question: adaptGroup21(raw["server.question"]),
reference: adaptGroup22(raw["server.reference"]),
projectCopy: adaptGroup23(raw["server.projectCopy"]),
vcs: adaptGroup24(raw["server.vcs"]),
})
export const make = (options?: { readonly baseUrl?: URL | string }) =>
HttpApiClient.make(ClientApi, options).pipe(Effect.map(adaptClient))
-165
View File
@@ -1,165 +0,0 @@
import { Effect, FileSystem, Option, Schedule, Schema } from "effect"
import { spawn } from "node:child_process"
import { homedir } from "node:os"
import { join } from "node:path"
// Find, start, and stop the local opencode background service.
//
// The service daemon advertises itself through a registration file in the
// user's state directory: url, pid, version, and the private password, with
// 0600 permissions. That file is the complete discovery contract — reading it
// is all a client needs to connect. The daemon's own configuration (port,
// persisted password) is CLI-owned and never read here.
export type Transport = {
readonly url: string
readonly headers?: RequestInit["headers"]
}
export type Options = {
// Absolute path to the service registration file. Defaults to
// opencode/service.json in the XDG state directory.
readonly file?: string
// When set, discovery only returns a server reporting this exact version,
// and start() replaces a healthy server whose version differs.
readonly version?: string
// Argv used to spawn the service. Defaults to ["opencode", "serve",
// "--service"] resolved from PATH.
readonly command?: ReadonlyArray<string>
}
// Read-only lookup: registration file plus health check and version gate.
// Never spawns; escalation to start() is the caller's policy.
export const discover = Effect.fn("service.discover")(function* (options: Options = {}) {
const info = yield* read(options.file)
if (info === undefined) return undefined
if (options.version !== undefined && info.version !== options.version) return undefined
const found = yield* probe(info)
return found?.transport
})
// Idempotent ensure-running: reuses a healthy compatible server, replaces a
// version-mismatched one, and otherwise spawns the service command detached.
export const start = Effect.fn("service.start")(function* (options: Options = {}) {
const compatible = yield* discover(options)
if (compatible !== undefined) return compatible
const mismatched = yield* find(options)
if (mismatched !== undefined) yield* kill(mismatched.info, options).pipe(Effect.ignore)
const [command, ...args] = options.command ?? ["opencode", "serve", "--service"]
if (command === undefined) return yield* Effect.fail(new Error("Missing service command"))
yield* Effect.try({
try: () => {
spawn(command, args, { detached: true, stdio: "ignore" }).unref()
},
catch: (cause) => new Error("Failed to start server", { cause }),
})
return yield* discover(options).pipe(
Effect.flatMap((found) =>
found === undefined ? Effect.fail(new Error("Server is not ready")) : Effect.succeed(found),
),
Effect.retry(poll),
Effect.mapError(() => new Error("Failed to start server")),
)
})
export const stop = Effect.fn("service.stop")(function* (options: Options = {}) {
const fs = yield* FileSystem.FileSystem
const existing = yield* find(options)
if (existing !== undefined) yield* kill(existing.info, options)
yield* fs.remove(options.file ?? fallback()).pipe(Effect.ignore)
})
function fallback() {
const state = process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state")
return join(state, "opencode", "service.json")
}
function auth(password: string): RequestInit["headers"] {
return { authorization: "Basic " + btoa("opencode:" + password) }
}
export const Info = Schema.Struct({
id: Schema.optional(Schema.String),
version: Schema.optional(Schema.String),
url: Schema.String,
pid: Schema.Int.check(Schema.isGreaterThan(0)),
password: Schema.optional(Schema.String),
})
export type Info = typeof Info.Type
const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Info))
// A missing or corrupt file means no valid info; callers treat both
// the same (the registering server self-evicts, clients rediscover).
const read = Effect.fnUntraced(function* (file?: string) {
const fs = yield* FileSystem.FileSystem
const text = yield* fs.readFileString(file ?? fallback()).pipe(Effect.option)
if (Option.isNone(text)) return undefined
return yield* decode(text.value).pipe(Effect.option, Effect.map(Option.getOrUndefined))
})
type LocalService = {
readonly info: Info
readonly transport: Transport
}
const probe = Effect.fnUntraced(function* (info: Info) {
const headers = info.password === undefined ? undefined : auth(info.password)
const healthy = yield* Effect.tryPromise(() =>
fetch(new URL("/api/health", info.url), {
headers,
signal: AbortSignal.timeout(2_000),
}),
).pipe(
Effect.map((response) => response.ok),
Effect.orElseSucceed(() => false),
)
if (!healthy) return undefined
return { info, transport: { url: info.url, headers } } satisfies LocalService
})
// Health-checked lookup without the version gate: lifecycle operations must be
// able to see (and replace or stop) a server from a different version.
const find = Effect.fnUntraced(function* (options: Options) {
const info = yield* read(options.file)
if (info === undefined) return undefined
return yield* probe(info)
})
// 50ms cadence bounded at ~5s, shared by stop escalation and start readiness.
const poll = Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100)))
const signal = (pid: number, name: NodeJS.Signals) =>
Effect.try({ try: () => process.kill(pid, name), catch: (cause) => cause }).pipe(Effect.ignore)
const stopped = Effect.fnUntraced(function* (pid: number) {
const running = yield* Effect.try({ try: () => process.kill(pid, 0), catch: () => false }).pipe(
Effect.orElseSucceed(() => false),
)
if (!running) return true
return yield* Effect.fail(new Error(`Server process ${pid} is still running`))
})
function same(left: Info, right: Info) {
return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid
}
const kill = Effect.fnUntraced(function* (info: Info, options: Options) {
// A stale registration may point at a PID that has since been reused by
// another process. Only signal the PID after authenticating the server.
const current = yield* find(options)
if (current === undefined || !same(current.info, info)) return
yield* signal(info.pid, "SIGTERM")
const done = yield* stopped(info.pid).pipe(Effect.retry(poll), Effect.option)
if (Option.isSome(done)) return
const latest = yield* find(options)
if (latest === undefined || !same(latest.info, info)) return
yield* signal(info.pid, "SIGKILL")
yield* stopped(info.pid).pipe(Effect.retry(poll))
})
export * as Service from "./service.js"
@@ -0,0 +1,706 @@
// Generated by @opencode-ai/httpapi-codegen. Do not edit.
import { Effect, Stream, Schema } from "effect"
import { Sse } from "effect/unstable/encoding"
import { HttpClientError } from "effect/unstable/http"
import { HttpApiClient } from "effect/unstable/httpapi"
import { ClientApi } from "../contract"
import { ClientError } from "./client-error"
type RawClient = HttpApiClient.ForApi<typeof ClientApi>
const mapClientError = <E>(error: E) =>
HttpClientError.isHttpClientError(error) || Schema.isSchemaError(error) || Sse.Retry.is(error)
? new ClientError({ cause: error })
: error
const Endpoint0_0 = (raw: RawClient["server.health"]) => () =>
raw["health.get"]({}).pipe(Effect.mapError(mapClientError))
const adaptGroup0 = (raw: RawClient["server.health"]) => ({ get: Endpoint0_0(raw) })
type Endpoint1_0Request = Parameters<RawClient["server.location"]["location.get"]>[0]
type Endpoint1_0Input = { readonly location?: Endpoint1_0Request["query"]["location"] }
const Endpoint1_0 = (raw: RawClient["server.location"]) => (input?: Endpoint1_0Input) =>
raw["location.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup1 = (raw: RawClient["server.location"]) => ({ get: Endpoint1_0(raw) })
type Endpoint2_0Request = Parameters<RawClient["server.agent"]["agent.list"]>[0]
type Endpoint2_0Input = { readonly location?: Endpoint2_0Request["query"]["location"] }
const Endpoint2_0 = (raw: RawClient["server.agent"]) => (input?: Endpoint2_0Input) =>
raw["agent.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup2 = (raw: RawClient["server.agent"]) => ({ list: Endpoint2_0(raw) })
type Endpoint3_0Request = Parameters<RawClient["server.session"]["session.list"]>[0]
type Endpoint3_0Input = {
readonly workspace?: Endpoint3_0Request["query"]["workspace"]
readonly limit?: Endpoint3_0Request["query"]["limit"]
readonly order?: Endpoint3_0Request["query"]["order"]
readonly search?: Endpoint3_0Request["query"]["search"]
readonly directory?: Endpoint3_0Request["query"]["directory"]
readonly project?: Endpoint3_0Request["query"]["project"]
readonly subpath?: Endpoint3_0Request["query"]["subpath"]
readonly cursor?: Endpoint3_0Request["query"]["cursor"]
}
const Endpoint3_0 = (raw: RawClient["server.session"]) => (input?: Endpoint3_0Input) =>
raw["session.list"]({
query: {
workspace: input?.["workspace"],
limit: input?.["limit"],
order: input?.["order"],
search: input?.["search"],
directory: input?.["directory"],
project: input?.["project"],
subpath: input?.["subpath"],
cursor: input?.["cursor"],
},
}).pipe(Effect.mapError(mapClientError))
type Endpoint3_1Request = Parameters<RawClient["server.session"]["session.create"]>[0]
type Endpoint3_1Input = {
readonly id?: Endpoint3_1Request["payload"]["id"]
readonly agent?: Endpoint3_1Request["payload"]["agent"]
readonly model?: Endpoint3_1Request["payload"]["model"]
readonly location?: Endpoint3_1Request["payload"]["location"]
}
const Endpoint3_1 = (raw: RawClient["server.session"]) => (input?: Endpoint3_1Input) =>
raw["session.create"]({
payload: { id: input?.["id"], agent: input?.["agent"], model: input?.["model"], location: input?.["location"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
const Endpoint3_2 = (raw: RawClient["server.session"]) => () =>
raw["session.active"]({}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint3_3Request = Parameters<RawClient["server.session"]["session.get"]>[0]
type Endpoint3_3Input = { readonly sessionID: Endpoint3_3Request["params"]["sessionID"] }
const Endpoint3_3 = (raw: RawClient["server.session"]) => (input: Endpoint3_3Input) =>
raw["session.get"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint3_4Request = Parameters<RawClient["server.session"]["session.switchAgent"]>[0]
type Endpoint3_4Input = {
readonly sessionID: Endpoint3_4Request["params"]["sessionID"]
readonly agent: Endpoint3_4Request["payload"]["agent"]
}
const Endpoint3_4 = (raw: RawClient["server.session"]) => (input: Endpoint3_4Input) =>
raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint3_5Request = Parameters<RawClient["server.session"]["session.switchModel"]>[0]
type Endpoint3_5Input = {
readonly sessionID: Endpoint3_5Request["params"]["sessionID"]
readonly model: Endpoint3_5Request["payload"]["model"]
}
const Endpoint3_5 = (raw: RawClient["server.session"]) => (input: Endpoint3_5Input) =>
raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint3_6Request = Parameters<RawClient["server.session"]["session.prompt"]>[0]
type Endpoint3_6Input = {
readonly sessionID: Endpoint3_6Request["params"]["sessionID"]
readonly id?: Endpoint3_6Request["payload"]["id"]
readonly prompt: Endpoint3_6Request["payload"]["prompt"]
readonly delivery?: Endpoint3_6Request["payload"]["delivery"]
readonly resume?: Endpoint3_6Request["payload"]["resume"]
}
const Endpoint3_6 = (raw: RawClient["server.session"]) => (input: Endpoint3_6Input) =>
raw["session.prompt"]({
params: { sessionID: input["sessionID"] },
payload: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint3_7Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
type Endpoint3_7Input = { readonly sessionID: Endpoint3_7Request["params"]["sessionID"] }
const Endpoint3_7 = (raw: RawClient["server.session"]) => (input: Endpoint3_7Input) =>
raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint3_8Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
type Endpoint3_8Input = { readonly sessionID: Endpoint3_8Request["params"]["sessionID"] }
const Endpoint3_8 = (raw: RawClient["server.session"]) => (input: Endpoint3_8Input) =>
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint3_9Request = Parameters<RawClient["server.session"]["session.revert.stage"]>[0]
type Endpoint3_9Input = {
readonly sessionID: Endpoint3_9Request["params"]["sessionID"]
readonly messageID: Endpoint3_9Request["payload"]["messageID"]
readonly files?: Endpoint3_9Request["payload"]["files"]
}
const Endpoint3_9 = (raw: RawClient["server.session"]) => (input: Endpoint3_9Input) =>
raw["session.revert.stage"]({
params: { sessionID: input["sessionID"] },
payload: { messageID: input["messageID"], files: input["files"] },
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint3_10Request = Parameters<RawClient["server.session"]["session.revert.clear"]>[0]
type Endpoint3_10Input = { readonly sessionID: Endpoint3_10Request["params"]["sessionID"] }
const Endpoint3_10 = (raw: RawClient["server.session"]) => (input: Endpoint3_10Input) =>
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint3_11Request = Parameters<RawClient["server.session"]["session.revert.commit"]>[0]
type Endpoint3_11Input = { readonly sessionID: Endpoint3_11Request["params"]["sessionID"] }
const Endpoint3_11 = (raw: RawClient["server.session"]) => (input: Endpoint3_11Input) =>
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint3_12Request = Parameters<RawClient["server.session"]["session.context"]>[0]
type Endpoint3_12Input = { readonly sessionID: Endpoint3_12Request["params"]["sessionID"] }
const Endpoint3_12 = (raw: RawClient["server.session"]) => (input: Endpoint3_12Input) =>
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint3_13Request = Parameters<RawClient["server.session"]["session.history"]>[0]
type Endpoint3_13Input = {
readonly sessionID: Endpoint3_13Request["params"]["sessionID"]
readonly limit?: Endpoint3_13Request["query"]["limit"]
readonly after?: Endpoint3_13Request["query"]["after"]
}
const Endpoint3_13 = (raw: RawClient["server.session"]) => (input: Endpoint3_13Input) =>
raw["session.history"]({
params: { sessionID: input["sessionID"] },
query: { limit: input["limit"], after: input["after"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint3_14Request = Parameters<RawClient["server.session"]["session.events"]>[0]
type Endpoint3_14Input = {
readonly sessionID: Endpoint3_14Request["params"]["sessionID"]
readonly after?: Endpoint3_14Request["query"]["after"]
}
const Endpoint3_14 = (raw: RawClient["server.session"]) => (input: Endpoint3_14Input) =>
Stream.unwrap(
raw["session.events"]({ params: { sessionID: input["sessionID"] }, query: { after: input["after"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
),
)
type Endpoint3_15Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
type Endpoint3_15Input = { readonly sessionID: Endpoint3_15Request["params"]["sessionID"] }
const Endpoint3_15 = (raw: RawClient["server.session"]) => (input: Endpoint3_15Input) =>
raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint3_16Request = Parameters<RawClient["server.session"]["session.message"]>[0]
type Endpoint3_16Input = {
readonly sessionID: Endpoint3_16Request["params"]["sessionID"]
readonly messageID: Endpoint3_16Request["params"]["messageID"]
}
const Endpoint3_16 = (raw: RawClient["server.session"]) => (input: Endpoint3_16Input) =>
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
const adaptGroup3 = (raw: RawClient["server.session"]) => ({
list: Endpoint3_0(raw),
create: Endpoint3_1(raw),
active: Endpoint3_2(raw),
get: Endpoint3_3(raw),
switchAgent: Endpoint3_4(raw),
switchModel: Endpoint3_5(raw),
prompt: Endpoint3_6(raw),
compact: Endpoint3_7(raw),
wait: Endpoint3_8(raw),
stage: Endpoint3_9(raw),
clear: Endpoint3_10(raw),
commit: Endpoint3_11(raw),
context: Endpoint3_12(raw),
history: Endpoint3_13(raw),
events: Endpoint3_14(raw),
interrupt: Endpoint3_15(raw),
message: Endpoint3_16(raw),
})
type Endpoint4_0Request = Parameters<RawClient["server.message"]["session.messages"]>[0]
type Endpoint4_0Input = {
readonly sessionID: Endpoint4_0Request["params"]["sessionID"]
readonly limit?: Endpoint4_0Request["query"]["limit"]
readonly order?: Endpoint4_0Request["query"]["order"]
readonly cursor?: Endpoint4_0Request["query"]["cursor"]
}
const Endpoint4_0 = (raw: RawClient["server.message"]) => (input: Endpoint4_0Input) =>
raw["session.messages"]({
params: { sessionID: input["sessionID"] },
query: { limit: input["limit"], order: input["order"], cursor: input["cursor"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup4 = (raw: RawClient["server.message"]) => ({ list: Endpoint4_0(raw) })
type Endpoint5_0Request = Parameters<RawClient["server.model"]["model.list"]>[0]
type Endpoint5_0Input = { readonly location?: Endpoint5_0Request["query"]["location"] }
const Endpoint5_0 = (raw: RawClient["server.model"]) => (input?: Endpoint5_0Input) =>
raw["model.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup5 = (raw: RawClient["server.model"]) => ({ list: Endpoint5_0(raw) })
type Endpoint6_0Request = Parameters<RawClient["server.provider"]["provider.list"]>[0]
type Endpoint6_0Input = { readonly location?: Endpoint6_0Request["query"]["location"] }
const Endpoint6_0 = (raw: RawClient["server.provider"]) => (input?: Endpoint6_0Input) =>
raw["provider.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint6_1Request = Parameters<RawClient["server.provider"]["provider.get"]>[0]
type Endpoint6_1Input = {
readonly providerID: Endpoint6_1Request["params"]["providerID"]
readonly location?: Endpoint6_1Request["query"]["location"]
}
const Endpoint6_1 = (raw: RawClient["server.provider"]) => (input: Endpoint6_1Input) =>
raw["provider.get"]({ params: { providerID: input["providerID"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup6 = (raw: RawClient["server.provider"]) => ({ list: Endpoint6_0(raw), get: Endpoint6_1(raw) })
type Endpoint7_0Request = Parameters<RawClient["server.integration"]["integration.list"]>[0]
type Endpoint7_0Input = { readonly location?: Endpoint7_0Request["query"]["location"] }
const Endpoint7_0 = (raw: RawClient["server.integration"]) => (input?: Endpoint7_0Input) =>
raw["integration.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint7_1Request = Parameters<RawClient["server.integration"]["integration.get"]>[0]
type Endpoint7_1Input = {
readonly integrationID: Endpoint7_1Request["params"]["integrationID"]
readonly location?: Endpoint7_1Request["query"]["location"]
}
const Endpoint7_1 = (raw: RawClient["server.integration"]) => (input: Endpoint7_1Input) =>
raw["integration.get"]({
params: { integrationID: input["integrationID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint7_2Request = Parameters<RawClient["server.integration"]["integration.connect.key"]>[0]
type Endpoint7_2Input = {
readonly integrationID: Endpoint7_2Request["params"]["integrationID"]
readonly location?: Endpoint7_2Request["query"]["location"]
readonly key: Endpoint7_2Request["payload"]["key"]
readonly label?: Endpoint7_2Request["payload"]["label"]
}
const Endpoint7_2 = (raw: RawClient["server.integration"]) => (input: Endpoint7_2Input) =>
raw["integration.connect.key"]({
params: { integrationID: input["integrationID"] },
query: { location: input["location"] },
payload: { key: input["key"], label: input["label"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint7_3Request = Parameters<RawClient["server.integration"]["integration.connect.oauth"]>[0]
type Endpoint7_3Input = {
readonly integrationID: Endpoint7_3Request["params"]["integrationID"]
readonly location?: Endpoint7_3Request["query"]["location"]
readonly methodID: Endpoint7_3Request["payload"]["methodID"]
readonly inputs: Endpoint7_3Request["payload"]["inputs"]
readonly label?: Endpoint7_3Request["payload"]["label"]
}
const Endpoint7_3 = (raw: RawClient["server.integration"]) => (input: Endpoint7_3Input) =>
raw["integration.connect.oauth"]({
params: { integrationID: input["integrationID"] },
query: { location: input["location"] },
payload: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint7_4Request = Parameters<RawClient["server.integration"]["integration.attempt.status"]>[0]
type Endpoint7_4Input = {
readonly attemptID: Endpoint7_4Request["params"]["attemptID"]
readonly location?: Endpoint7_4Request["query"]["location"]
}
const Endpoint7_4 = (raw: RawClient["server.integration"]) => (input: Endpoint7_4Input) =>
raw["integration.attempt.status"]({
params: { attemptID: input["attemptID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint7_5Request = Parameters<RawClient["server.integration"]["integration.attempt.complete"]>[0]
type Endpoint7_5Input = {
readonly attemptID: Endpoint7_5Request["params"]["attemptID"]
readonly location?: Endpoint7_5Request["query"]["location"]
readonly code?: Endpoint7_5Request["payload"]["code"]
}
const Endpoint7_5 = (raw: RawClient["server.integration"]) => (input: Endpoint7_5Input) =>
raw["integration.attempt.complete"]({
params: { attemptID: input["attemptID"] },
query: { location: input["location"] },
payload: { code: input["code"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint7_6Request = Parameters<RawClient["server.integration"]["integration.attempt.cancel"]>[0]
type Endpoint7_6Input = {
readonly attemptID: Endpoint7_6Request["params"]["attemptID"]
readonly location?: Endpoint7_6Request["query"]["location"]
}
const Endpoint7_6 = (raw: RawClient["server.integration"]) => (input: Endpoint7_6Input) =>
raw["integration.attempt.cancel"]({
params: { attemptID: input["attemptID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup7 = (raw: RawClient["server.integration"]) => ({
list: Endpoint7_0(raw),
get: Endpoint7_1(raw),
connectKey: Endpoint7_2(raw),
connectOauth: Endpoint7_3(raw),
attemptStatus: Endpoint7_4(raw),
attemptComplete: Endpoint7_5(raw),
attemptCancel: Endpoint7_6(raw),
})
type Endpoint8_0Request = Parameters<RawClient["server.credential"]["credential.update"]>[0]
type Endpoint8_0Input = {
readonly credentialID: Endpoint8_0Request["params"]["credentialID"]
readonly location?: Endpoint8_0Request["query"]["location"]
readonly label: Endpoint8_0Request["payload"]["label"]
}
const Endpoint8_0 = (raw: RawClient["server.credential"]) => (input: Endpoint8_0Input) =>
raw["credential.update"]({
params: { credentialID: input["credentialID"] },
query: { location: input["location"] },
payload: { label: input["label"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint8_1Request = Parameters<RawClient["server.credential"]["credential.remove"]>[0]
type Endpoint8_1Input = {
readonly credentialID: Endpoint8_1Request["params"]["credentialID"]
readonly location?: Endpoint8_1Request["query"]["location"]
}
const Endpoint8_1 = (raw: RawClient["server.credential"]) => (input: Endpoint8_1Input) =>
raw["credential.remove"]({
params: { credentialID: input["credentialID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup8 = (raw: RawClient["server.credential"]) => ({ update: Endpoint8_0(raw), remove: Endpoint8_1(raw) })
type Endpoint9_0Request = Parameters<RawClient["server.permission"]["permission.request.list"]>[0]
type Endpoint9_0Input = { readonly location?: Endpoint9_0Request["query"]["location"] }
const Endpoint9_0 = (raw: RawClient["server.permission"]) => (input?: Endpoint9_0Input) =>
raw["permission.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint9_1Request = Parameters<RawClient["server.permission"]["permission.saved.list"]>[0]
type Endpoint9_1Input = { readonly projectID?: Endpoint9_1Request["query"]["projectID"] }
const Endpoint9_1 = (raw: RawClient["server.permission"]) => (input?: Endpoint9_1Input) =>
raw["permission.saved.list"]({ query: { projectID: input?.["projectID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint9_2Request = Parameters<RawClient["server.permission"]["permission.saved.remove"]>[0]
type Endpoint9_2Input = { readonly id: Endpoint9_2Request["params"]["id"] }
const Endpoint9_2 = (raw: RawClient["server.permission"]) => (input: Endpoint9_2Input) =>
raw["permission.saved.remove"]({ params: { id: input["id"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint9_3Request = Parameters<RawClient["server.permission"]["session.permission.create"]>[0]
type Endpoint9_3Input = {
readonly sessionID: Endpoint9_3Request["params"]["sessionID"]
readonly id?: Endpoint9_3Request["payload"]["id"]
readonly action: Endpoint9_3Request["payload"]["action"]
readonly resources: Endpoint9_3Request["payload"]["resources"]
readonly save?: Endpoint9_3Request["payload"]["save"]
readonly metadata?: Endpoint9_3Request["payload"]["metadata"]
readonly source?: Endpoint9_3Request["payload"]["source"]
readonly agent?: Endpoint9_3Request["payload"]["agent"]
}
const Endpoint9_3 = (raw: RawClient["server.permission"]) => (input: Endpoint9_3Input) =>
raw["session.permission.create"]({
params: { sessionID: input["sessionID"] },
payload: {
id: input["id"],
action: input["action"],
resources: input["resources"],
save: input["save"],
metadata: input["metadata"],
source: input["source"],
agent: input["agent"],
},
}).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint9_4Request = Parameters<RawClient["server.permission"]["session.permission.list"]>[0]
type Endpoint9_4Input = { readonly sessionID: Endpoint9_4Request["params"]["sessionID"] }
const Endpoint9_4 = (raw: RawClient["server.permission"]) => (input: Endpoint9_4Input) =>
raw["session.permission.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint9_5Request = Parameters<RawClient["server.permission"]["session.permission.get"]>[0]
type Endpoint9_5Input = {
readonly sessionID: Endpoint9_5Request["params"]["sessionID"]
readonly requestID: Endpoint9_5Request["params"]["requestID"]
}
const Endpoint9_5 = (raw: RawClient["server.permission"]) => (input: Endpoint9_5Input) =>
raw["session.permission.get"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint9_6Request = Parameters<RawClient["server.permission"]["session.permission.reply"]>[0]
type Endpoint9_6Input = {
readonly sessionID: Endpoint9_6Request["params"]["sessionID"]
readonly requestID: Endpoint9_6Request["params"]["requestID"]
readonly reply: Endpoint9_6Request["payload"]["reply"]
readonly message?: Endpoint9_6Request["payload"]["message"]
}
const Endpoint9_6 = (raw: RawClient["server.permission"]) => (input: Endpoint9_6Input) =>
raw["session.permission.reply"]({
params: { sessionID: input["sessionID"], requestID: input["requestID"] },
payload: { reply: input["reply"], message: input["message"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup9 = (raw: RawClient["server.permission"]) => ({
listRequests: Endpoint9_0(raw),
listSaved: Endpoint9_1(raw),
removeSaved: Endpoint9_2(raw),
create: Endpoint9_3(raw),
list: Endpoint9_4(raw),
get: Endpoint9_5(raw),
reply: Endpoint9_6(raw),
})
type Endpoint10_0Request = Parameters<RawClient["server.fs"]["fs.list"]>[0]
type Endpoint10_0Input = {
readonly location?: Endpoint10_0Request["query"]["location"]
readonly path?: Endpoint10_0Request["query"]["path"]
}
const Endpoint10_0 = (raw: RawClient["server.fs"]) => (input?: Endpoint10_0Input) =>
raw["fs.list"]({ query: { location: input?.["location"], path: input?.["path"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint10_1Request = Parameters<RawClient["server.fs"]["fs.find"]>[0]
type Endpoint10_1Input = {
readonly location?: Endpoint10_1Request["query"]["location"]
readonly query: Endpoint10_1Request["query"]["query"]
readonly type?: Endpoint10_1Request["query"]["type"]
readonly limit?: Endpoint10_1Request["query"]["limit"]
}
const Endpoint10_1 = (raw: RawClient["server.fs"]) => (input: Endpoint10_1Input) =>
raw["fs.find"]({
query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup10 = (raw: RawClient["server.fs"]) => ({ list: Endpoint10_0(raw), find: Endpoint10_1(raw) })
type Endpoint11_0Request = Parameters<RawClient["server.command"]["command.list"]>[0]
type Endpoint11_0Input = { readonly location?: Endpoint11_0Request["query"]["location"] }
const Endpoint11_0 = (raw: RawClient["server.command"]) => (input?: Endpoint11_0Input) =>
raw["command.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup11 = (raw: RawClient["server.command"]) => ({ list: Endpoint11_0(raw) })
type Endpoint12_0Request = Parameters<RawClient["server.skill"]["skill.list"]>[0]
type Endpoint12_0Input = { readonly location?: Endpoint12_0Request["query"]["location"] }
const Endpoint12_0 = (raw: RawClient["server.skill"]) => (input?: Endpoint12_0Input) =>
raw["skill.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup12 = (raw: RawClient["server.skill"]) => ({ list: Endpoint12_0(raw) })
const Endpoint13_0 = (raw: RawClient["server.event"]) => () =>
Stream.unwrap(
raw["event.subscribe"]({}).pipe(
Effect.mapError(mapClientError),
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
),
)
const adaptGroup13 = (raw: RawClient["server.event"]) => ({ subscribe: Endpoint13_0(raw) })
type Endpoint14_0Request = Parameters<RawClient["server.pty"]["pty.list"]>[0]
type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] }
const Endpoint14_0 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_0Input) =>
raw["pty.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint14_1Request = Parameters<RawClient["server.pty"]["pty.create"]>[0]
type Endpoint14_1Input = {
readonly location?: Endpoint14_1Request["query"]["location"]
readonly command?: Endpoint14_1Request["payload"]["command"]
readonly args?: Endpoint14_1Request["payload"]["args"]
readonly cwd?: Endpoint14_1Request["payload"]["cwd"]
readonly title?: Endpoint14_1Request["payload"]["title"]
readonly env?: Endpoint14_1Request["payload"]["env"]
}
const Endpoint14_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_1Input) =>
raw["pty.create"]({
query: { location: input?.["location"] },
payload: {
command: input?.["command"],
args: input?.["args"],
cwd: input?.["cwd"],
title: input?.["title"],
env: input?.["env"],
},
}).pipe(Effect.mapError(mapClientError))
type Endpoint14_2Request = Parameters<RawClient["server.pty"]["pty.get"]>[0]
type Endpoint14_2Input = {
readonly ptyID: Endpoint14_2Request["params"]["ptyID"]
readonly location?: Endpoint14_2Request["query"]["location"]
}
const Endpoint14_2 = (raw: RawClient["server.pty"]) => (input: Endpoint14_2Input) =>
raw["pty.get"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
type Endpoint14_3Request = Parameters<RawClient["server.pty"]["pty.update"]>[0]
type Endpoint14_3Input = {
readonly ptyID: Endpoint14_3Request["params"]["ptyID"]
readonly location?: Endpoint14_3Request["query"]["location"]
readonly title?: Endpoint14_3Request["payload"]["title"]
readonly size?: Endpoint14_3Request["payload"]["size"]
}
const Endpoint14_3 = (raw: RawClient["server.pty"]) => (input: Endpoint14_3Input) =>
raw["pty.update"]({
params: { ptyID: input["ptyID"] },
query: { location: input["location"] },
payload: { title: input["title"], size: input["size"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint14_4Request = Parameters<RawClient["server.pty"]["pty.remove"]>[0]
type Endpoint14_4Input = {
readonly ptyID: Endpoint14_4Request["params"]["ptyID"]
readonly location?: Endpoint14_4Request["query"]["location"]
}
const Endpoint14_4 = (raw: RawClient["server.pty"]) => (input: Endpoint14_4Input) =>
raw["pty.remove"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup14 = (raw: RawClient["server.pty"]) => ({
list: Endpoint14_0(raw),
create: Endpoint14_1(raw),
get: Endpoint14_2(raw),
update: Endpoint14_3(raw),
remove: Endpoint14_4(raw),
})
type Endpoint15_0Request = Parameters<RawClient["server.question"]["question.request.list"]>[0]
type Endpoint15_0Input = { readonly location?: Endpoint15_0Request["query"]["location"] }
const Endpoint15_0 = (raw: RawClient["server.question"]) => (input?: Endpoint15_0Input) =>
raw["question.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
type Endpoint15_1Request = Parameters<RawClient["server.question"]["session.question.list"]>[0]
type Endpoint15_1Input = { readonly sessionID: Endpoint15_1Request["params"]["sessionID"] }
const Endpoint15_1 = (raw: RawClient["server.question"]) => (input: Endpoint15_1Input) =>
raw["session.question.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
)
type Endpoint15_2Request = Parameters<RawClient["server.question"]["session.question.reply"]>[0]
type Endpoint15_2Input = {
readonly sessionID: Endpoint15_2Request["params"]["sessionID"]
readonly requestID: Endpoint15_2Request["params"]["requestID"]
readonly answers: Endpoint15_2Request["payload"]["answers"]
}
const Endpoint15_2 = (raw: RawClient["server.question"]) => (input: Endpoint15_2Input) =>
raw["session.question.reply"]({
params: { sessionID: input["sessionID"], requestID: input["requestID"] },
payload: { answers: input["answers"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint15_3Request = Parameters<RawClient["server.question"]["session.question.reject"]>[0]
type Endpoint15_3Input = {
readonly sessionID: Endpoint15_3Request["params"]["sessionID"]
readonly requestID: Endpoint15_3Request["params"]["requestID"]
}
const Endpoint15_3 = (raw: RawClient["server.question"]) => (input: Endpoint15_3Input) =>
raw["session.question.reject"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe(
Effect.mapError(mapClientError),
)
const adaptGroup15 = (raw: RawClient["server.question"]) => ({
listRequests: Endpoint15_0(raw),
list: Endpoint15_1(raw),
reply: Endpoint15_2(raw),
reject: Endpoint15_3(raw),
})
type Endpoint16_0Request = Parameters<RawClient["server.reference"]["reference.list"]>[0]
type Endpoint16_0Input = { readonly location?: Endpoint16_0Request["query"]["location"] }
const Endpoint16_0 = (raw: RawClient["server.reference"]) => (input?: Endpoint16_0Input) =>
raw["reference.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError))
const adaptGroup16 = (raw: RawClient["server.reference"]) => ({ list: Endpoint16_0(raw) })
type Endpoint17_0Request = Parameters<RawClient["server.projectCopy"]["projectCopy.create"]>[0]
type Endpoint17_0Input = {
readonly projectID: Endpoint17_0Request["params"]["projectID"]
readonly location?: Endpoint17_0Request["query"]["location"]
readonly strategy: Endpoint17_0Request["payload"]["strategy"]
readonly directory: Endpoint17_0Request["payload"]["directory"]
readonly name?: Endpoint17_0Request["payload"]["name"]
}
const Endpoint17_0 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint17_0Input) =>
raw["projectCopy.create"]({
params: { projectID: input["projectID"] },
query: { location: input["location"] },
payload: { strategy: input["strategy"], directory: input["directory"], name: input["name"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint17_1Request = Parameters<RawClient["server.projectCopy"]["projectCopy.remove"]>[0]
type Endpoint17_1Input = {
readonly projectID: Endpoint17_1Request["params"]["projectID"]
readonly location?: Endpoint17_1Request["query"]["location"]
readonly directory: Endpoint17_1Request["payload"]["directory"]
readonly force: Endpoint17_1Request["payload"]["force"]
}
const Endpoint17_1 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint17_1Input) =>
raw["projectCopy.remove"]({
params: { projectID: input["projectID"] },
query: { location: input["location"] },
payload: { directory: input["directory"], force: input["force"] },
}).pipe(Effect.mapError(mapClientError))
type Endpoint17_2Request = Parameters<RawClient["server.projectCopy"]["projectCopy.refresh"]>[0]
type Endpoint17_2Input = {
readonly projectID: Endpoint17_2Request["params"]["projectID"]
readonly location?: Endpoint17_2Request["query"]["location"]
}
const Endpoint17_2 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint17_2Input) =>
raw["projectCopy.refresh"]({
params: { projectID: input["projectID"] },
query: { location: input["location"] },
}).pipe(Effect.mapError(mapClientError))
const adaptGroup17 = (raw: RawClient["server.projectCopy"]) => ({
create: Endpoint17_0(raw),
remove: Endpoint17_1(raw),
refresh: Endpoint17_2(raw),
})
const adaptClient = (raw: RawClient) => ({
health: adaptGroup0(raw["server.health"]),
location: adaptGroup1(raw["server.location"]),
agents: adaptGroup2(raw["server.agent"]),
sessions: adaptGroup3(raw["server.session"]),
messages: adaptGroup4(raw["server.message"]),
models: adaptGroup5(raw["server.model"]),
providers: adaptGroup6(raw["server.provider"]),
integrations: adaptGroup7(raw["server.integration"]),
credentials: adaptGroup8(raw["server.credential"]),
permissions: adaptGroup9(raw["server.permission"]),
files: adaptGroup10(raw["server.fs"]),
commands: adaptGroup11(raw["server.command"]),
skills: adaptGroup12(raw["server.skill"]),
events: adaptGroup13(raw["server.event"]),
ptys: adaptGroup14(raw["server.pty"]),
questions: adaptGroup15(raw["server.question"]),
references: adaptGroup16(raw["server.reference"]),
projectCopies: adaptGroup17(raw["server.projectCopy"]),
})
export const make = (options?: { readonly baseUrl?: URL | string }) =>
HttpApiClient.make(ClientApi, options).pipe(Effect.map(adaptClient))
@@ -2,177 +2,116 @@ import type {
HealthGetOutput,
LocationGetInput,
LocationGetOutput,
AgentListInput,
AgentListOutput,
PluginListInput,
PluginListOutput,
SessionListInput,
SessionListOutput,
SessionCreateInput,
SessionCreateOutput,
SessionActiveOutput,
SessionGetInput,
SessionGetOutput,
SessionForkInput,
SessionForkOutput,
SessionSwitchAgentInput,
SessionSwitchAgentOutput,
SessionSwitchModelInput,
SessionSwitchModelOutput,
SessionRenameInput,
SessionRenameOutput,
SessionPromptInput,
SessionPromptOutput,
SessionCommandInput,
SessionCommandOutput,
SessionSkillInput,
SessionSkillOutput,
SessionSyntheticInput,
SessionSyntheticOutput,
SessionShellInput,
SessionShellOutput,
SessionCompactInput,
SessionCompactOutput,
SessionWaitInput,
SessionWaitOutput,
SessionRevertStageInput,
SessionRevertStageOutput,
SessionRevertClearInput,
SessionRevertClearOutput,
SessionRevertCommitInput,
SessionRevertCommitOutput,
SessionContextInput,
SessionContextOutput,
SessionListContextEntriesInput,
SessionListContextEntriesOutput,
SessionPutContextEntryInput,
SessionPutContextEntryOutput,
SessionRemoveContextEntryInput,
SessionRemoveContextEntryOutput,
SessionLogInput,
SessionLogOutput,
SessionInterruptInput,
SessionInterruptOutput,
SessionBackgroundInput,
SessionBackgroundOutput,
SessionMessageInput,
SessionMessageOutput,
MessageListInput,
MessageListOutput,
ModelListInput,
ModelListOutput,
ModelDefaultInput,
ModelDefaultOutput,
GenerateTextInput,
GenerateTextOutput,
ProviderListInput,
ProviderListOutput,
ProviderGetInput,
ProviderGetOutput,
IntegrationListInput,
IntegrationListOutput,
IntegrationGetInput,
IntegrationGetOutput,
IntegrationConnectKeyInput,
IntegrationConnectKeyOutput,
IntegrationConnectOauthInput,
IntegrationConnectOauthOutput,
IntegrationAttemptStatusInput,
IntegrationAttemptStatusOutput,
IntegrationAttemptCompleteInput,
IntegrationAttemptCompleteOutput,
IntegrationAttemptCancelInput,
IntegrationAttemptCancelOutput,
ServerMcpListInput,
ServerMcpListOutput,
CredentialUpdateInput,
CredentialUpdateOutput,
CredentialRemoveInput,
CredentialRemoveOutput,
ProjectCurrentInput,
ProjectCurrentOutput,
ProjectDirectoriesInput,
ProjectDirectoriesOutput,
FormListRequestsInput,
FormListRequestsOutput,
FormListInput,
FormListOutput,
FormCreateInput,
FormCreateOutput,
FormGetInput,
FormGetOutput,
FormStateInput,
FormStateOutput,
FormReplyInput,
FormReplyOutput,
FormCancelInput,
FormCancelOutput,
PermissionListRequestsInput,
PermissionListRequestsOutput,
PermissionListSavedInput,
PermissionListSavedOutput,
PermissionRemoveSavedInput,
PermissionRemoveSavedOutput,
PermissionCreateInput,
PermissionCreateOutput,
PermissionListInput,
PermissionListOutput,
PermissionGetInput,
PermissionGetOutput,
PermissionReplyInput,
PermissionReplyOutput,
FileReadInput,
FileReadOutput,
FileListInput,
FileListOutput,
FileFindInput,
FileFindOutput,
CommandListInput,
CommandListOutput,
SkillListInput,
SkillListOutput,
EventSubscribeOutput,
EventChangesOutput,
PtyListInput,
PtyListOutput,
PtyCreateInput,
PtyCreateOutput,
PtyGetInput,
PtyGetOutput,
PtyUpdateInput,
PtyUpdateOutput,
PtyRemoveInput,
PtyRemoveOutput,
ShellListInput,
ShellListOutput,
ShellCreateInput,
ShellCreateOutput,
ShellGetInput,
ShellGetOutput,
ShellOutputInput,
ShellOutputOutput,
ShellRemoveInput,
ShellRemoveOutput,
QuestionListRequestsInput,
QuestionListRequestsOutput,
QuestionListInput,
QuestionListOutput,
QuestionReplyInput,
QuestionReplyOutput,
QuestionRejectInput,
QuestionRejectOutput,
ReferenceListInput,
ReferenceListOutput,
ProjectCopyCreateInput,
ProjectCopyCreateOutput,
ProjectCopyRemoveInput,
ProjectCopyRemoveOutput,
ProjectCopyRefreshInput,
ProjectCopyRefreshOutput,
VcsStatusInput,
VcsStatusOutput,
VcsDiffInput,
VcsDiffOutput,
AgentsListInput,
AgentsListOutput,
SessionsListInput,
SessionsListOutput,
SessionsCreateInput,
SessionsCreateOutput,
SessionsActiveOutput,
SessionsGetInput,
SessionsGetOutput,
SessionsSwitchAgentInput,
SessionsSwitchAgentOutput,
SessionsSwitchModelInput,
SessionsSwitchModelOutput,
SessionsPromptInput,
SessionsPromptOutput,
SessionsCompactInput,
SessionsCompactOutput,
SessionsWaitInput,
SessionsWaitOutput,
SessionsStageInput,
SessionsStageOutput,
SessionsClearInput,
SessionsClearOutput,
SessionsCommitInput,
SessionsCommitOutput,
SessionsContextInput,
SessionsContextOutput,
SessionsHistoryInput,
SessionsHistoryOutput,
SessionsEventsInput,
SessionsEventsOutput,
SessionsInterruptInput,
SessionsInterruptOutput,
SessionsMessageInput,
SessionsMessageOutput,
MessagesListInput,
MessagesListOutput,
ModelsListInput,
ModelsListOutput,
ProvidersListInput,
ProvidersListOutput,
ProvidersGetInput,
ProvidersGetOutput,
IntegrationsListInput,
IntegrationsListOutput,
IntegrationsGetInput,
IntegrationsGetOutput,
IntegrationsConnectKeyInput,
IntegrationsConnectKeyOutput,
IntegrationsConnectOauthInput,
IntegrationsConnectOauthOutput,
IntegrationsAttemptStatusInput,
IntegrationsAttemptStatusOutput,
IntegrationsAttemptCompleteInput,
IntegrationsAttemptCompleteOutput,
IntegrationsAttemptCancelInput,
IntegrationsAttemptCancelOutput,
CredentialsUpdateInput,
CredentialsUpdateOutput,
CredentialsRemoveInput,
CredentialsRemoveOutput,
PermissionsListRequestsInput,
PermissionsListRequestsOutput,
PermissionsListSavedInput,
PermissionsListSavedOutput,
PermissionsRemoveSavedInput,
PermissionsRemoveSavedOutput,
PermissionsCreateInput,
PermissionsCreateOutput,
PermissionsListInput,
PermissionsListOutput,
PermissionsGetInput,
PermissionsGetOutput,
PermissionsReplyInput,
PermissionsReplyOutput,
FilesListInput,
FilesListOutput,
FilesFindInput,
FilesFindOutput,
CommandsListInput,
CommandsListOutput,
SkillsListInput,
SkillsListOutput,
EventsSubscribeOutput,
PtysListInput,
PtysListOutput,
PtysCreateInput,
PtysCreateOutput,
PtysGetInput,
PtysGetOutput,
PtysUpdateInput,
PtysUpdateOutput,
PtysRemoveInput,
PtysRemoveOutput,
QuestionsListRequestsInput,
QuestionsListRequestsOutput,
QuestionsListInput,
QuestionsListOutput,
QuestionsReplyInput,
QuestionsReplyOutput,
QuestionsRejectInput,
QuestionsRejectOutput,
ReferencesListInput,
ReferencesListOutput,
ProjectCopiesCreateInput,
ProjectCopiesCreateOutput,
ProjectCopiesRemoveInput,
ProjectCopiesRemoveOutput,
ProjectCopiesRefreshInput,
ProjectCopiesRefreshOutput,
} from "./types"
import { ClientError } from "./client-error"
@@ -196,7 +135,6 @@ interface RequestDescriptor {
readonly successStatus: number
readonly declaredStatuses: ReadonlyArray<number>
readonly empty: boolean
readonly binary?: true
}
export function make(options: ClientOptions) {
@@ -242,7 +180,6 @@ export function make(options: ClientOptions) {
const request = async <A>(descriptor: RequestDescriptor, requestOptions?: RequestOptions): Promise<A> => {
const response = await execute(descriptor, requestOptions)
if (response.status !== descriptor.successStatus) return responseError(response, descriptor)
if (descriptor.binary) return new Uint8Array(await response.arrayBuffer()) as A
if (descriptor.empty) {
try {
await response.body?.cancel()
@@ -331,9 +268,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
agent: {
list: (input?: AgentListInput, requestOptions?: RequestOptions) =>
request<AgentListOutput>(
agents: {
list: (input?: AgentsListInput, requestOptions?: RequestOptions) =>
request<AgentsListOutput>(
{
method: "GET",
path: `/api/agent`,
@@ -345,23 +282,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
plugin: {
list: (input?: PluginListInput, requestOptions?: RequestOptions) =>
request<PluginListOutput>(
{
method: "GET",
path: `/api/plugin`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
},
session: {
list: (input?: SessionListInput, requestOptions?: RequestOptions) =>
request<SessionListOutput>(
sessions: {
list: (input?: SessionsListInput, requestOptions?: RequestOptions) =>
request<SessionsListOutput>(
{
method: "GET",
path: `/api/session`,
@@ -370,7 +293,6 @@ export function make(options: ClientOptions) {
limit: input?.["limit"],
order: input?.["order"],
search: input?.["search"],
parentID: input?.["parentID"],
directory: input?.["directory"],
project: input?.["project"],
subpath: input?.["subpath"],
@@ -382,8 +304,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
create: (input?: SessionCreateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionCreateOutput }>(
create: (input?: SessionsCreateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionsCreateOutput }>(
{
method: "POST",
path: `/api/session`,
@@ -400,7 +322,7 @@ export function make(options: ClientOptions) {
requestOptions,
).then((value) => value.data),
active: (requestOptions?: RequestOptions) =>
request<SessionActiveOutput>(
request<{ readonly data: SessionsActiveOutput }>(
{
method: "GET",
path: `/api/session/active`,
@@ -409,9 +331,9 @@ export function make(options: ClientOptions) {
empty: false,
},
requestOptions,
),
get: (input: SessionGetInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionGetOutput }>(
).then((value) => value.data),
get: (input: SessionsGetInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionsGetOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}`,
@@ -421,20 +343,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
fork: (input: SessionForkInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionForkOutput }>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/fork`,
body: { messageID: input["messageID"] },
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
switchAgent: (input: SessionSwitchAgentInput, requestOptions?: RequestOptions) =>
request<SessionSwitchAgentOutput>(
switchAgent: (input: SessionsSwitchAgentInput, requestOptions?: RequestOptions) =>
request<SessionsSwitchAgentOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/agent`,
@@ -445,8 +355,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
switchModel: (input: SessionSwitchModelInput, requestOptions?: RequestOptions) =>
request<SessionSwitchModelOutput>(
switchModel: (input: SessionsSwitchModelInput, requestOptions?: RequestOptions) =>
request<SessionsSwitchModelOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/model`,
@@ -457,20 +367,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
rename: (input: SessionRenameInput, requestOptions?: RequestOptions) =>
request<SessionRenameOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/rename`,
body: { title: input["title"] },
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
prompt: (input: SessionPromptInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionPromptOutput }>(
prompt: (input: SessionsPromptInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionsPromptOutput }>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/prompt`,
@@ -481,77 +379,19 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
command: (input: SessionCommandInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionCommandOutput }>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/command`,
body: {
id: input["id"],
command: input["command"],
arguments: input["arguments"],
agent: input["agent"],
model: input["model"],
files: input["files"],
agents: input["agents"],
delivery: input["delivery"],
resume: input["resume"],
},
successStatus: 200,
declaredStatuses: [409, 404, 500, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
skill: (input: SessionSkillInput, requestOptions?: RequestOptions) =>
request<SessionSkillOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/skill`,
body: { id: input["id"], skill: input["skill"], resume: input["resume"] },
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
synthetic: (input: SessionSyntheticInput, requestOptions?: RequestOptions) =>
request<SessionSyntheticOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/synthetic`,
body: { text: input["text"], description: input["description"], metadata: input["metadata"] },
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
shell: (input: SessionShellInput, requestOptions?: RequestOptions) =>
request<SessionShellOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/shell`,
body: { id: input["id"], command: input["command"] },
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
compact: (input: SessionCompactInput, requestOptions?: RequestOptions) =>
request<SessionCompactOutput>(
compact: (input: SessionsCompactInput, requestOptions?: RequestOptions) =>
request<SessionsCompactOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`,
successStatus: 204,
declaredStatuses: [404, 409, 503, 500, 400, 401],
declaredStatuses: [404, 503, 400, 401],
empty: true,
},
requestOptions,
),
wait: (input: SessionWaitInput, requestOptions?: RequestOptions) =>
request<SessionWaitOutput>(
wait: (input: SessionsWaitInput, requestOptions?: RequestOptions) =>
request<SessionsWaitOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/wait`,
@@ -561,42 +401,42 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
revertStage: (input: SessionRevertStageInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionRevertStageOutput }>(
stage: (input: SessionsStageInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionsStageOutput }>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`,
body: { messageID: input["messageID"], files: input["files"] },
successStatus: 200,
declaredStatuses: [404, 409, 500, 400, 401],
declaredStatuses: [404, 500, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
revertClear: (input: SessionRevertClearInput, requestOptions?: RequestOptions) =>
request<SessionRevertClearOutput>(
clear: (input: SessionsClearInput, requestOptions?: RequestOptions) =>
request<SessionsClearOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`,
successStatus: 204,
declaredStatuses: [404, 409, 500, 400, 401],
declaredStatuses: [404, 500, 400, 401],
empty: true,
},
requestOptions,
),
revertCommit: (input: SessionRevertCommitInput, requestOptions?: RequestOptions) =>
request<SessionRevertCommitOutput>(
commit: (input: SessionsCommitInput, requestOptions?: RequestOptions) =>
request<SessionsCommitOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`,
successStatus: 204,
declaredStatuses: [404, 409, 400, 401],
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
context: (input: SessionContextInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionContextOutput }>(
context: (input: SessionsContextInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionsContextOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/context`,
@@ -606,54 +446,32 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
listContextEntries: (input: SessionListContextEntriesInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionListContextEntriesOutput }>(
history: (input: SessionsHistoryInput, requestOptions?: RequestOptions) =>
request<SessionsHistoryOutput>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/context-entry`,
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
putContextEntry: (input: SessionPutContextEntryInput, requestOptions?: RequestOptions) =>
request<SessionPutContextEntryOutput>(
{
method: "PUT",
path: `/api/session/${encodeURIComponent(input.sessionID)}/context-entry/${encodeURIComponent(input.key)}`,
body: { value: input["value"] },
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
removeContextEntry: (input: SessionRemoveContextEntryInput, requestOptions?: RequestOptions) =>
request<SessionRemoveContextEntryOutput>(
{
method: "DELETE",
path: `/api/session/${encodeURIComponent(input.sessionID)}/context-entry/${encodeURIComponent(input.key)}`,
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
log: (input: SessionLogInput, requestOptions?: RequestOptions): AsyncIterable<SessionLogOutput> =>
sse<SessionLogOutput>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/log`,
query: { after: input["after"], follow: input["follow"] },
path: `/api/session/${encodeURIComponent(input.sessionID)}/history`,
query: { limit: input["limit"], after: input["after"] },
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
),
interrupt: (input: SessionInterruptInput, requestOptions?: RequestOptions) =>
request<SessionInterruptOutput>(
events: (input: SessionsEventsInput, requestOptions?: RequestOptions): AsyncIterable<SessionsEventsOutput> =>
sse<SessionsEventsOutput>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/event`,
query: { after: input["after"] },
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
),
interrupt: (input: SessionsInterruptInput, requestOptions?: RequestOptions) =>
request<SessionsInterruptOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/interrupt`,
@@ -663,19 +481,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
background: (input: SessionBackgroundInput, requestOptions?: RequestOptions) =>
request<SessionBackgroundOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/background`,
successStatus: 204,
declaredStatuses: [404, 400, 401],
empty: true,
},
requestOptions,
),
message: (input: SessionMessageInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionMessageOutput }>(
message: (input: SessionsMessageInput, requestOptions?: RequestOptions) =>
request<{ readonly data: SessionsMessageOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/message/${encodeURIComponent(input.messageID)}`,
@@ -686,9 +493,9 @@ export function make(options: ClientOptions) {
requestOptions,
).then((value) => value.data),
},
message: {
list: (input: MessageListInput, requestOptions?: RequestOptions) =>
request<MessageListOutput>(
messages: {
list: (input: MessagesListInput, requestOptions?: RequestOptions) =>
request<MessagesListOutput>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/message`,
@@ -700,9 +507,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
model: {
list: (input?: ModelListInput, requestOptions?: RequestOptions) =>
request<ModelListOutput>(
models: {
list: (input?: ModelsListInput, requestOptions?: RequestOptions) =>
request<ModelsListOutput>(
{
method: "GET",
path: `/api/model`,
@@ -713,37 +520,10 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
default: (input?: ModelDefaultInput, requestOptions?: RequestOptions) =>
request<ModelDefaultOutput>(
{
method: "GET",
path: `/api/model/default`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [503, 401, 400],
empty: false,
},
requestOptions,
),
},
generate: {
text: (input: GenerateTextInput, requestOptions?: RequestOptions) =>
request<{ readonly data: GenerateTextOutput }>(
{
method: "POST",
path: `/api/generate`,
query: { location: input["location"] },
body: { prompt: input["prompt"], model: input["model"] },
successStatus: 200,
declaredStatuses: [400, 503, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
},
provider: {
list: (input?: ProviderListInput, requestOptions?: RequestOptions) =>
request<ProviderListOutput>(
providers: {
list: (input?: ProvidersListInput, requestOptions?: RequestOptions) =>
request<ProvidersListOutput>(
{
method: "GET",
path: `/api/provider`,
@@ -754,8 +534,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
get: (input: ProviderGetInput, requestOptions?: RequestOptions) =>
request<ProviderGetOutput>(
get: (input: ProvidersGetInput, requestOptions?: RequestOptions) =>
request<ProvidersGetOutput>(
{
method: "GET",
path: `/api/provider/${encodeURIComponent(input.providerID)}`,
@@ -767,9 +547,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
integration: {
list: (input?: IntegrationListInput, requestOptions?: RequestOptions) =>
request<IntegrationListOutput>(
integrations: {
list: (input?: IntegrationsListInput, requestOptions?: RequestOptions) =>
request<IntegrationsListOutput>(
{
method: "GET",
path: `/api/integration`,
@@ -780,8 +560,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
get: (input: IntegrationGetInput, requestOptions?: RequestOptions) =>
request<IntegrationGetOutput>(
get: (input: IntegrationsGetInput, requestOptions?: RequestOptions) =>
request<IntegrationsGetOutput>(
{
method: "GET",
path: `/api/integration/${encodeURIComponent(input.integrationID)}`,
@@ -792,8 +572,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
connectKey: (input: IntegrationConnectKeyInput, requestOptions?: RequestOptions) =>
request<IntegrationConnectKeyOutput>(
connectKey: (input: IntegrationsConnectKeyInput, requestOptions?: RequestOptions) =>
request<IntegrationsConnectKeyOutput>(
{
method: "POST",
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/key`,
@@ -805,8 +585,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
connectOauth: (input: IntegrationConnectOauthInput, requestOptions?: RequestOptions) =>
request<IntegrationConnectOauthOutput>(
connectOauth: (input: IntegrationsConnectOauthInput, requestOptions?: RequestOptions) =>
request<IntegrationsConnectOauthOutput>(
{
method: "POST",
path: `/api/integration/${encodeURIComponent(input.integrationID)}/connect/oauth`,
@@ -818,8 +598,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
attemptStatus: (input: IntegrationAttemptStatusInput, requestOptions?: RequestOptions) =>
request<IntegrationAttemptStatusOutput>(
attemptStatus: (input: IntegrationsAttemptStatusInput, requestOptions?: RequestOptions) =>
request<IntegrationsAttemptStatusOutput>(
{
method: "GET",
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
@@ -830,8 +610,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
attemptComplete: (input: IntegrationAttemptCompleteInput, requestOptions?: RequestOptions) =>
request<IntegrationAttemptCompleteOutput>(
attemptComplete: (input: IntegrationsAttemptCompleteInput, requestOptions?: RequestOptions) =>
request<IntegrationsAttemptCompleteOutput>(
{
method: "POST",
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}/complete`,
@@ -843,8 +623,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
attemptCancel: (input: IntegrationAttemptCancelInput, requestOptions?: RequestOptions) =>
request<IntegrationAttemptCancelOutput>(
attemptCancel: (input: IntegrationsAttemptCancelInput, requestOptions?: RequestOptions) =>
request<IntegrationsAttemptCancelOutput>(
{
method: "DELETE",
path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`,
@@ -856,23 +636,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
"server.mcp": {
list: (input?: ServerMcpListInput, requestOptions?: RequestOptions) =>
request<ServerMcpListOutput>(
{
method: "GET",
path: `/api/mcp`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
},
credential: {
update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) =>
request<CredentialUpdateOutput>(
credentials: {
update: (input: CredentialsUpdateInput, requestOptions?: RequestOptions) =>
request<CredentialsUpdateOutput>(
{
method: "PATCH",
path: `/api/credential/${encodeURIComponent(input.credentialID)}`,
@@ -884,8 +650,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
remove: (input: CredentialRemoveInput, requestOptions?: RequestOptions) =>
request<CredentialRemoveOutput>(
remove: (input: CredentialsRemoveInput, requestOptions?: RequestOptions) =>
request<CredentialsRemoveOutput>(
{
method: "DELETE",
path: `/api/credential/${encodeURIComponent(input.credentialID)}`,
@@ -897,124 +663,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
project: {
current: (input?: ProjectCurrentInput, requestOptions?: RequestOptions) =>
request<ProjectCurrentOutput>(
{
method: "GET",
path: `/api/project/current`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
directories: (input: ProjectDirectoriesInput, requestOptions?: RequestOptions) =>
request<ProjectDirectoriesOutput>(
{
method: "GET",
path: `/api/project/${encodeURIComponent(input.projectID)}/directories`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
},
form: {
listRequests: (input?: FormListRequestsInput, requestOptions?: RequestOptions) =>
request<FormListRequestsOutput>(
{
method: "GET",
path: `/api/form/request`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
list: (input: FormListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: FormListOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form`,
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
create: (input: FormCreateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: FormCreateOutput }>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form`,
body: {
id: input["id"],
title: input["title"],
metadata: input["metadata"],
mode: input["mode"],
fields: input["fields"],
url: input["url"],
},
successStatus: 200,
declaredStatuses: [404, 409, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
get: (input: FormGetInput, requestOptions?: RequestOptions) =>
request<{ readonly data: FormGetOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}`,
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
state: (input: FormStateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: FormStateOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/state`,
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
).then((value) => value.data),
reply: (input: FormReplyInput, requestOptions?: RequestOptions) =>
request<FormReplyOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/reply`,
body: { answer: input["answer"] },
successStatus: 204,
declaredStatuses: [404, 409, 400, 401],
empty: true,
},
requestOptions,
),
cancel: (input: FormCancelInput, requestOptions?: RequestOptions) =>
request<FormCancelOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/cancel`,
successStatus: 204,
declaredStatuses: [404, 409, 400, 401],
empty: true,
},
requestOptions,
),
},
permission: {
listRequests: (input?: PermissionListRequestsInput, requestOptions?: RequestOptions) =>
request<PermissionListRequestsOutput>(
permissions: {
listRequests: (input?: PermissionsListRequestsInput, requestOptions?: RequestOptions) =>
request<PermissionsListRequestsOutput>(
{
method: "GET",
path: `/api/permission/request`,
@@ -1025,8 +676,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
listSaved: (input?: PermissionListSavedInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionListSavedOutput }>(
listSaved: (input?: PermissionsListSavedInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionsListSavedOutput }>(
{
method: "GET",
path: `/api/permission/saved`,
@@ -1037,8 +688,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
removeSaved: (input: PermissionRemoveSavedInput, requestOptions?: RequestOptions) =>
request<PermissionRemoveSavedOutput>(
removeSaved: (input: PermissionsRemoveSavedInput, requestOptions?: RequestOptions) =>
request<PermissionsRemoveSavedOutput>(
{
method: "DELETE",
path: `/api/permission/saved/${encodeURIComponent(input.id)}`,
@@ -1048,8 +699,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
create: (input: PermissionCreateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionCreateOutput }>(
create: (input: PermissionsCreateInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionsCreateOutput }>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`,
@@ -1068,8 +719,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
list: (input: PermissionListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionListOutput }>(
list: (input: PermissionsListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionsListOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`,
@@ -1079,8 +730,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
get: (input: PermissionGetInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionGetOutput }>(
get: (input: PermissionsGetInput, requestOptions?: RequestOptions) =>
request<{ readonly data: PermissionsGetOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}`,
@@ -1090,8 +741,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
reply: (input: PermissionReplyInput, requestOptions?: RequestOptions) =>
request<PermissionReplyOutput>(
reply: (input: PermissionsReplyInput, requestOptions?: RequestOptions) =>
request<PermissionsReplyOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}/reply`,
@@ -1103,22 +754,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
file: {
read: (input: FileReadInput, requestOptions?: RequestOptions) =>
request<FileReadOutput>(
{
method: "GET",
path: `/api/fs/read/${encodePath(input.path)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
binary: true,
},
requestOptions,
),
list: (input?: FileListInput, requestOptions?: RequestOptions) =>
request<FileListOutput>(
files: {
list: (input?: FilesListInput, requestOptions?: RequestOptions) =>
request<FilesListOutput>(
{
method: "GET",
path: `/api/fs/list`,
@@ -1129,8 +767,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
find: (input: FileFindInput, requestOptions?: RequestOptions) =>
request<FileFindOutput>(
find: (input: FilesFindInput, requestOptions?: RequestOptions) =>
request<FilesFindOutput>(
{
method: "GET",
path: `/api/fs/find`,
@@ -1142,9 +780,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
command: {
list: (input?: CommandListInput, requestOptions?: RequestOptions) =>
request<CommandListOutput>(
commands: {
list: (input?: CommandsListInput, requestOptions?: RequestOptions) =>
request<CommandsListOutput>(
{
method: "GET",
path: `/api/command`,
@@ -1156,9 +794,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
skill: {
list: (input?: SkillListInput, requestOptions?: RequestOptions) =>
request<SkillListOutput>(
skills: {
list: (input?: SkillsListInput, requestOptions?: RequestOptions) =>
request<SkillsListOutput>(
{
method: "GET",
path: `/api/skill`,
@@ -1170,21 +808,16 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
event: {
subscribe: (requestOptions?: RequestOptions): AsyncIterable<EventSubscribeOutput> =>
sse<EventSubscribeOutput>(
events: {
subscribe: (requestOptions?: RequestOptions): AsyncIterable<EventsSubscribeOutput> =>
sse<EventsSubscribeOutput>(
{ method: "GET", path: `/api/event`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
requestOptions,
),
changes: (requestOptions?: RequestOptions): AsyncIterable<EventChangesOutput> =>
sse<EventChangesOutput>(
{ method: "GET", path: `/api/event/changes`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
requestOptions,
),
},
pty: {
list: (input?: PtyListInput, requestOptions?: RequestOptions) =>
request<PtyListOutput>(
ptys: {
list: (input?: PtysListInput, requestOptions?: RequestOptions) =>
request<PtysListOutput>(
{
method: "GET",
path: `/api/pty`,
@@ -1195,8 +828,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
create: (input?: PtyCreateInput, requestOptions?: RequestOptions) =>
request<PtyCreateOutput>(
create: (input?: PtysCreateInput, requestOptions?: RequestOptions) =>
request<PtysCreateOutput>(
{
method: "POST",
path: `/api/pty`,
@@ -1214,8 +847,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
get: (input: PtyGetInput, requestOptions?: RequestOptions) =>
request<PtyGetOutput>(
get: (input: PtysGetInput, requestOptions?: RequestOptions) =>
request<PtysGetOutput>(
{
method: "GET",
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
@@ -1226,8 +859,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
update: (input: PtyUpdateInput, requestOptions?: RequestOptions) =>
request<PtyUpdateOutput>(
update: (input: PtysUpdateInput, requestOptions?: RequestOptions) =>
request<PtysUpdateOutput>(
{
method: "PUT",
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
@@ -1239,8 +872,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
remove: (input: PtyRemoveInput, requestOptions?: RequestOptions) =>
request<PtyRemoveOutput>(
remove: (input: PtysRemoveInput, requestOptions?: RequestOptions) =>
request<PtysRemoveOutput>(
{
method: "DELETE",
path: `/api/pty/${encodeURIComponent(input.ptyID)}`,
@@ -1252,77 +885,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
shell: {
list: (input?: ShellListInput, requestOptions?: RequestOptions) =>
request<ShellListOutput>(
{
method: "GET",
path: `/api/shell`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
create: (input: ShellCreateInput, requestOptions?: RequestOptions) =>
request<ShellCreateOutput>(
{
method: "POST",
path: `/api/shell`,
query: { location: input["location"] },
body: {
command: input["command"],
cwd: input["cwd"],
timeout: input["timeout"],
metadata: input["metadata"],
},
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
get: (input: ShellGetInput, requestOptions?: RequestOptions) =>
request<ShellGetOutput>(
{
method: "GET",
path: `/api/shell/${encodeURIComponent(input.id)}`,
query: { location: input["location"] },
successStatus: 200,
declaredStatuses: [404, 401, 400],
empty: false,
},
requestOptions,
),
output: (input: ShellOutputInput, requestOptions?: RequestOptions) =>
request<ShellOutputOutput>(
{
method: "GET",
path: `/api/shell/${encodeURIComponent(input.id)}/output`,
query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] },
successStatus: 200,
declaredStatuses: [404, 401, 400],
empty: false,
},
requestOptions,
),
remove: (input: ShellRemoveInput, requestOptions?: RequestOptions) =>
request<ShellRemoveOutput>(
{
method: "DELETE",
path: `/api/shell/${encodeURIComponent(input.id)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [404, 401, 400],
empty: true,
},
requestOptions,
),
},
question: {
listRequests: (input?: QuestionListRequestsInput, requestOptions?: RequestOptions) =>
request<QuestionListRequestsOutput>(
questions: {
listRequests: (input?: QuestionsListRequestsInput, requestOptions?: RequestOptions) =>
request<QuestionsListRequestsOutput>(
{
method: "GET",
path: `/api/question/request`,
@@ -1333,8 +898,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
list: (input: QuestionListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: QuestionListOutput }>(
list: (input: QuestionsListInput, requestOptions?: RequestOptions) =>
request<{ readonly data: QuestionsListOutput }>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/question`,
@@ -1344,8 +909,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
reply: (input: QuestionReplyInput, requestOptions?: RequestOptions) =>
request<QuestionReplyOutput>(
reply: (input: QuestionsReplyInput, requestOptions?: RequestOptions) =>
request<QuestionsReplyOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/question/${encodeURIComponent(input.requestID)}/reply`,
@@ -1356,8 +921,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
reject: (input: QuestionRejectInput, requestOptions?: RequestOptions) =>
request<QuestionRejectOutput>(
reject: (input: QuestionsRejectInput, requestOptions?: RequestOptions) =>
request<QuestionsRejectOutput>(
{
method: "POST",
path: `/api/session/${encodeURIComponent(input.sessionID)}/question/${encodeURIComponent(input.requestID)}/reject`,
@@ -1368,9 +933,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
reference: {
list: (input?: ReferenceListInput, requestOptions?: RequestOptions) =>
request<ReferenceListOutput>(
references: {
list: (input?: ReferencesListInput, requestOptions?: RequestOptions) =>
request<ReferencesListOutput>(
{
method: "GET",
path: `/api/reference`,
@@ -1382,9 +947,9 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
projectCopy: {
create: (input: ProjectCopyCreateInput, requestOptions?: RequestOptions) =>
request<ProjectCopyCreateOutput>(
projectCopies: {
create: (input: ProjectCopiesCreateInput, requestOptions?: RequestOptions) =>
request<ProjectCopiesCreateOutput>(
{
method: "POST",
path: `/experimental/project/${encodeURIComponent(input.projectID)}/copy`,
@@ -1396,8 +961,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
remove: (input: ProjectCopyRemoveInput, requestOptions?: RequestOptions) =>
request<ProjectCopyRemoveOutput>(
remove: (input: ProjectCopiesRemoveInput, requestOptions?: RequestOptions) =>
request<ProjectCopiesRemoveOutput>(
{
method: "DELETE",
path: `/experimental/project/${encodeURIComponent(input.projectID)}/copy`,
@@ -1409,8 +974,8 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
refresh: (input: ProjectCopyRefreshInput, requestOptions?: RequestOptions) =>
request<ProjectCopyRefreshOutput>(
refresh: (input: ProjectCopiesRefreshInput, requestOptions?: RequestOptions) =>
request<ProjectCopiesRefreshOutput>(
{
method: "POST",
path: `/experimental/project/${encodeURIComponent(input.projectID)}/copy/refresh`,
@@ -1422,45 +987,11 @@ export function make(options: ClientOptions) {
requestOptions,
),
},
vcs: {
status: (input?: VcsStatusInput, requestOptions?: RequestOptions) =>
request<VcsStatusOutput>(
{
method: "GET",
path: `/api/vcs/status`,
query: { location: input?.["location"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
diff: (input: VcsDiffInput, requestOptions?: RequestOptions) =>
request<VcsDiffOutput>(
{
method: "GET",
path: `/api/vcs/diff`,
query: { location: input["location"], mode: input["mode"], context: input["context"] },
successStatus: 200,
declaredStatuses: [401, 400],
empty: false,
},
requestOptions,
),
},
}
}
function encodePath(value: string): string {
return value.split("/").map(encodeURIComponent).join("/")
}
function appendQuery(params: URLSearchParams, key: string, value: unknown): void {
if (value === undefined) return
if (value === null) {
params.append(key, "null")
return
}
if (value === undefined || value === null) return
if (Array.isArray(value)) {
for (const item of value) appendQuery(params, key, item)
return
+2807
View File
@@ -0,0 +1,2807 @@
import type { OpenCodeEventEncoded } from "@opencode-ai/protocol/groups/event"
export type JsonValue =
| null
| boolean
| number
| string
| ReadonlyArray<JsonValue>
| { readonly [key: string]: JsonValue }
export type UnauthorizedError = { readonly _tag: "UnauthorizedError"; readonly message: string }
export const isUnauthorizedError = (value: unknown): value is UnauthorizedError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnauthorizedError"
export type InvalidRequestError = {
readonly _tag: "InvalidRequestError"
readonly message: string
readonly kind?: string | undefined
readonly field?: string | undefined
}
export const isInvalidRequestError = (value: unknown): value is InvalidRequestError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "InvalidRequestError"
export type InvalidCursorError = { readonly _tag: "InvalidCursorError"; readonly message: string }
export const isInvalidCursorError = (value: unknown): value is InvalidCursorError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "InvalidCursorError"
export type SessionNotFoundError = {
readonly _tag: "SessionNotFoundError"
readonly sessionID: string
readonly message: string
}
export const isSessionNotFoundError = (value: unknown): value is SessionNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionNotFoundError"
export type ConflictError = {
readonly _tag: "ConflictError"
readonly message: string
readonly resource?: string | undefined
}
export const isConflictError = (value: unknown): value is ConflictError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ConflictError"
export type ServiceUnavailableError = {
readonly _tag: "ServiceUnavailableError"
readonly message: string
readonly service?: string | undefined
}
export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError"
export type MessageNotFoundError = {
readonly _tag: "MessageNotFoundError"
readonly sessionID: string
readonly messageID: string
readonly message: string
}
export const isMessageNotFoundError = (value: unknown): value is MessageNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "MessageNotFoundError"
export type UnknownError = {
readonly _tag: "UnknownError"
readonly message: string
readonly ref?: string | undefined
}
export const isUnknownError = (value: unknown): value is UnknownError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnknownError"
export type ProviderNotFoundError = {
readonly _tag: "ProviderNotFoundError"
readonly providerID: string
readonly message: string
}
export const isProviderNotFoundError = (value: unknown): value is ProviderNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ProviderNotFoundError"
export type PermissionNotFoundError = {
readonly _tag: "PermissionNotFoundError"
readonly requestID: string
readonly message: string
}
export const isPermissionNotFoundError = (value: unknown): value is PermissionNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "PermissionNotFoundError"
export type PtyNotFoundError = { readonly _tag: "PtyNotFoundError"; readonly ptyID: string; readonly message: string }
export const isPtyNotFoundError = (value: unknown): value is PtyNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "PtyNotFoundError"
export type QuestionNotFoundError = {
readonly _tag: "QuestionNotFoundError"
readonly requestID: string
readonly message: string
}
export const isQuestionNotFoundError = (value: unknown): value is QuestionNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "QuestionNotFoundError"
export type ProjectCopyError = {
readonly name: "ProjectCopyError"
readonly data: { readonly message: string; readonly forceRequired?: boolean | undefined }
}
export const isProjectCopyError = (value: unknown): value is ProjectCopyError =>
typeof value === "object" && value !== null && "name" in value && value["name"] === "ProjectCopyError"
export type HealthGetOutput = { readonly healthy: true }
export type LocationGetInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type LocationGetOutput = {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
export type AgentsListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type AgentsListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly request: {
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}
readonly system?: string
readonly description?: string
readonly mode: "subagent" | "primary" | "all"
readonly hidden: boolean
readonly color?: string | "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info"
readonly steps?: number
readonly permissions: ReadonlyArray<{
readonly action: string
readonly resource: string
readonly effect: "allow" | "deny" | "ask"
}>
}>
}
export type SessionsListInput = {
readonly workspace?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["workspace"]
readonly limit?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["limit"]
readonly order?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["order"]
readonly search?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["search"]
readonly directory?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["directory"]
readonly project?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["project"]
readonly subpath?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["subpath"]
readonly cursor?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["cursor"]
}
export type SessionsListOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly parentID?: string
readonly projectID: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}>
readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
}
export type SessionsCreateInput = {
readonly id?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["id"]
readonly agent?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["agent"]
readonly model?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["model"]
readonly location?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["location"]
}
export type SessionsCreateOutput = {
readonly data: {
readonly id: string
readonly parentID?: string
readonly projectID: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}["data"]
export type SessionsActiveOutput = { readonly data: { readonly [x: string]: { readonly type: "running" } } }["data"]
export type SessionsGetInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionsGetOutput = {
readonly data: {
readonly id: string
readonly parentID?: string
readonly projectID: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}["data"]
export type SessionsSwitchAgentInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly agent: { readonly agent: string }["agent"]
}
export type SessionsSwitchAgentOutput = void
export type SessionsSwitchModelInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly model: {
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}["model"]
}
export type SessionsSwitchModelOutput = void
export type SessionsPromptInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["id"]
readonly prompt: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["prompt"]
readonly delivery?: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["delivery"]
readonly resume?: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["resume"]
}
export type SessionsPromptOutput = {
readonly data: {
readonly admittedSeq: number
readonly id: string
readonly sessionID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
readonly timeCreated: number
readonly promotedSeq?: number
}
}["data"]
export type SessionsCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionsCompactOutput = void
export type SessionsWaitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionsWaitOutput = void
export type SessionsStageInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly messageID: { readonly messageID: string; readonly files?: boolean | undefined }["messageID"]
readonly files?: { readonly messageID: string; readonly files?: boolean | undefined }["files"]
}
export type SessionsStageOutput = {
readonly data: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}["data"]
export type SessionsClearInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionsClearOutput = void
export type SessionsCommitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionsCommitOutput = void
export type SessionsContextInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionsContextOutput = {
readonly data: ReadonlyArray<
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "agent-switched"
readonly agent: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "model-switched"
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly sessionID: string
readonly text: string
readonly type: "synthetic"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "system"
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "shell"
readonly callID: string
readonly command: string
readonly output: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "assistant"
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly id: string; readonly text: string }
| {
readonly type: "reasoning"
readonly id: string
readonly text: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
readonly time?: { readonly created: number; readonly completed?: number }
}
| {
readonly type: "tool"
readonly id: string
readonly name: string
readonly provider?: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
readonly state:
| { readonly status: "pending"; readonly input: string }
| {
readonly status: "running"
readonly input: { readonly [x: string]: JsonValue }
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
| {
readonly status: "completed"
readonly input: { readonly [x: string]: JsonValue }
readonly attachments?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly structured: { readonly [x: string]: JsonValue }
readonly result?: JsonValue
}
| {
readonly status: "error"
readonly input: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly structured: { readonly [x: string]: JsonValue }
readonly error: { readonly type: "unknown"; readonly message: string }
readonly result?: JsonValue
}
readonly time: {
readonly created: number
readonly ran?: number
readonly completed?: number
readonly pruned?: number
}
}
>
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
readonly finish?: string
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly error?: { readonly type: "unknown"; readonly message: string }
}
| {
readonly type: "compaction"
readonly reason: "auto" | "manual"
readonly summary: string
readonly recent: string
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
}
>
}["data"]
export type SessionsHistoryInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly limit?: { readonly limit?: number | undefined; readonly after?: number | undefined }["limit"]
readonly after?: { readonly limit?: number | undefined; readonly after?: number | undefined }["after"]
}
export type SessionsHistoryOutput = {
readonly data: ReadonlyArray<
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.agent.switched"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly agent: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.model.switched"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.moved"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subdirectory?: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.prompted"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.prompt.admitted"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.context.updated"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.synthetic"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.shell.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly callID: string
readonly command: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.shell.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly callID: string
readonly output: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.step.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly snapshot?: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.step.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly finish: string
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly snapshot?: string
readonly files?: ReadonlyArray<string>
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.step.failed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly error: { readonly type: "unknown"; readonly message: string }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.text.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly textID: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.text.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly textID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.input.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly name: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.input.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.called"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly tool: string
readonly input: { readonly [x: string]: JsonValue }
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.progress"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.success"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly result?: JsonValue
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.failed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly error: { readonly type: "unknown"; readonly message: string }
readonly result?: JsonValue
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.reasoning.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly reasoningID: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.reasoning.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly reasoningID: string
readonly text: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.retried"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly attempt: number
readonly error: {
readonly message: string
readonly statusCode?: number
readonly isRetryable: boolean
readonly responseHeaders?: { readonly [x: string]: string }
readonly responseBody?: string
readonly metadata?: { readonly [x: string]: string }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.compaction.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly reason: "auto" | "manual"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.compaction.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly reason: "auto" | "manual"
readonly text: string
readonly recent: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.revert.staged"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly revert: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.revert.cleared"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly timestamp: number; readonly sessionID: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.revert.committed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string }
}
>
readonly hasMore: boolean
}
export type SessionsEventsInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly after?: { readonly after?: number | undefined }["after"]
}
export type SessionsEventsOutput =
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.agent.switched"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly agent: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.model.switched"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.moved"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subdirectory?: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.prompted"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.prompt.admitted"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.context.updated"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.synthetic"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.shell.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly callID: string
readonly command: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.shell.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly callID: string
readonly output: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.step.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly snapshot?: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.step.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly finish: string
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly snapshot?: string
readonly files?: ReadonlyArray<string>
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.step.failed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly error: { readonly type: "unknown"; readonly message: string }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.text.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly textID: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.text.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly textID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.tool.input.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly name: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.tool.input.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.tool.called"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly tool: string
readonly input: { readonly [x: string]: unknown }
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.tool.progress"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: unknown }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.tool.success"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: unknown }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly result?: unknown
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.tool.failed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly error: { readonly type: "unknown"; readonly message: string }
readonly result?: unknown
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.reasoning.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly reasoningID: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.reasoning.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly reasoningID: string
readonly text: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.retried"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly attempt: number
readonly error: {
readonly message: string
readonly statusCode?: number
readonly isRetryable: boolean
readonly responseHeaders?: { readonly [x: string]: string }
readonly responseBody?: string
readonly metadata?: { readonly [x: string]: string }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.compaction.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly reason: "auto" | "manual"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.compaction.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly reason: "auto" | "manual"
readonly text: string
readonly recent: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.revert.staged"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly revert: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.revert.cleared"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly timestamp: number; readonly sessionID: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.next.revert.committed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string }
}
export type SessionsInterruptInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionsInterruptOutput = void
export type SessionsMessageInput = {
readonly sessionID: { readonly sessionID: string; readonly messageID: string }["sessionID"]
readonly messageID: { readonly sessionID: string; readonly messageID: string }["messageID"]
}
export type SessionsMessageOutput = {
readonly data:
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "agent-switched"
readonly agent: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "model-switched"
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly sessionID: string
readonly text: string
readonly type: "synthetic"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "system"
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "shell"
readonly callID: string
readonly command: string
readonly output: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "assistant"
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly id: string; readonly text: string }
| {
readonly type: "reasoning"
readonly id: string
readonly text: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
readonly time?: { readonly created: number; readonly completed?: number }
}
| {
readonly type: "tool"
readonly id: string
readonly name: string
readonly provider?: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
readonly state:
| { readonly status: "pending"; readonly input: string }
| {
readonly status: "running"
readonly input: { readonly [x: string]: JsonValue }
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
| {
readonly status: "completed"
readonly input: { readonly [x: string]: JsonValue }
readonly attachments?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly structured: { readonly [x: string]: JsonValue }
readonly result?: JsonValue
}
| {
readonly status: "error"
readonly input: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly structured: { readonly [x: string]: JsonValue }
readonly error: { readonly type: "unknown"; readonly message: string }
readonly result?: JsonValue
}
readonly time: {
readonly created: number
readonly ran?: number
readonly completed?: number
readonly pruned?: number
}
}
>
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
readonly finish?: string
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly error?: { readonly type: "unknown"; readonly message: string }
}
| {
readonly type: "compaction"
readonly reason: "auto" | "manual"
readonly summary: string
readonly recent: string
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
}
}["data"]
export type MessagesListInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly limit?: {
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly cursor?: string | undefined
}["limit"]
readonly order?: {
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly cursor?: string | undefined
}["order"]
readonly cursor?: {
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly cursor?: string | undefined
}["cursor"]
}
export type MessagesListOutput = {
readonly data: ReadonlyArray<
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "agent-switched"
readonly agent: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "model-switched"
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly sessionID: string
readonly text: string
readonly type: "synthetic"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "system"
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "shell"
readonly callID: string
readonly command: string
readonly output: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "assistant"
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly id: string; readonly text: string }
| {
readonly type: "reasoning"
readonly id: string
readonly text: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
readonly time?: { readonly created: number; readonly completed?: number }
}
| {
readonly type: "tool"
readonly id: string
readonly name: string
readonly provider?: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
readonly state:
| { readonly status: "pending"; readonly input: string }
| {
readonly status: "running"
readonly input: { readonly [x: string]: JsonValue }
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
| {
readonly status: "completed"
readonly input: { readonly [x: string]: JsonValue }
readonly attachments?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly structured: { readonly [x: string]: JsonValue }
readonly result?: JsonValue
}
| {
readonly status: "error"
readonly input: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly structured: { readonly [x: string]: JsonValue }
readonly error: { readonly type: "unknown"; readonly message: string }
readonly result?: JsonValue
}
readonly time: {
readonly created: number
readonly ran?: number
readonly completed?: number
readonly pruned?: number
}
}
>
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
readonly finish?: string
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly error?: { readonly type: "unknown"; readonly message: string }
}
| {
readonly type: "compaction"
readonly reason: "auto" | "manual"
readonly summary: string
readonly recent: string
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
}
>
readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
}
export type ModelsListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ModelsListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly providerID: string
readonly family?: string
readonly name: string
readonly api:
| {
readonly id: string
readonly type: "aisdk"
readonly package: string
readonly url?: string
readonly settings?: { readonly [x: string]: JsonValue }
}
| {
readonly id: string
readonly type: "native"
readonly url?: string
readonly settings: { readonly [x: string]: JsonValue }
}
readonly capabilities: {
readonly tools: boolean
readonly input: ReadonlyArray<string>
readonly output: ReadonlyArray<string>
}
readonly request: {
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
readonly variant?: string
}
readonly variants: ReadonlyArray<{
readonly id: string
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}>
readonly time: { readonly released: number }
readonly cost: ReadonlyArray<{
readonly tier?: { readonly type: "context"; readonly size: number }
readonly input: number
readonly output: number
readonly cache: { readonly read: number; readonly write: number }
}>
readonly status: "alpha" | "beta" | "deprecated" | "active"
readonly enabled: boolean
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
}>
}
export type ProvidersListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProvidersListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly integrationID?: string
readonly name: string
readonly disabled?: boolean
readonly api:
| {
readonly type: "aisdk"
readonly package: string
readonly url?: string
readonly settings?: { readonly [x: string]: JsonValue }
}
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
readonly request: {
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}
}>
}
export type ProvidersGetInput = {
readonly providerID: { readonly providerID: string }["providerID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProvidersGetOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly integrationID?: string
readonly name: string
readonly disabled?: boolean
readonly api:
| {
readonly type: "aisdk"
readonly package: string
readonly url?: string
readonly settings?: { readonly [x: string]: JsonValue }
}
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
readonly request: {
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}
}
}
export type IntegrationsListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationsListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly name: string
readonly methods: ReadonlyArray<
| {
readonly id: string
readonly type: "oauth"
readonly label: string
readonly prompts?: ReadonlyArray<
| {
readonly type: "text"
readonly key: string
readonly message: string
readonly placeholder?: string
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
| {
readonly type: "select"
readonly key: string
readonly message: string
readonly options: ReadonlyArray<{
readonly label: string
readonly value: string
readonly hint?: string
}>
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
>
}
| { readonly type: "key"; readonly label?: string }
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
>
readonly connections: ReadonlyArray<
| { readonly type: "credential"; readonly id: string; readonly label: string }
| { readonly type: "env"; readonly name: string }
>
}>
}
export type IntegrationsGetInput = {
readonly integrationID: { readonly integrationID: string }["integrationID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationsGetOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly name: string
readonly methods: ReadonlyArray<
| {
readonly id: string
readonly type: "oauth"
readonly label: string
readonly prompts?: ReadonlyArray<
| {
readonly type: "text"
readonly key: string
readonly message: string
readonly placeholder?: string
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
| {
readonly type: "select"
readonly key: string
readonly message: string
readonly options: ReadonlyArray<{
readonly label: string
readonly value: string
readonly hint?: string
}>
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
>
}
| { readonly type: "key"; readonly label?: string }
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
>
readonly connections: ReadonlyArray<
| { readonly type: "credential"; readonly id: string; readonly label: string }
| { readonly type: "env"; readonly name: string }
>
} | null
}
export type IntegrationsConnectKeyInput = {
readonly integrationID: { readonly integrationID: string }["integrationID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
}
export type IntegrationsConnectKeyOutput = void
export type IntegrationsConnectOauthInput = {
readonly integrationID: { readonly integrationID: string }["integrationID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly methodID: {
readonly methodID: string
readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined
}["methodID"]
readonly inputs: {
readonly methodID: string
readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined
}["inputs"]
readonly label?: {
readonly methodID: string
readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined
}["label"]
}
export type IntegrationsConnectOauthOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly attemptID: string
readonly url: string
readonly instructions: string
readonly mode: "auto" | "code"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
}
export type IntegrationsAttemptStatusInput = {
readonly attemptID: { readonly attemptID: string }["attemptID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationsAttemptStatusOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data:
| {
readonly status: "pending"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
| {
readonly status: "complete"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
| {
readonly status: "failed"
readonly message: string
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
| {
readonly status: "expired"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
}
export type IntegrationsAttemptCompleteInput = {
readonly attemptID: { readonly attemptID: string }["attemptID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly code?: { readonly code?: string | undefined }["code"]
}
export type IntegrationsAttemptCompleteOutput = void
export type IntegrationsAttemptCancelInput = {
readonly attemptID: { readonly attemptID: string }["attemptID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationsAttemptCancelOutput = void
export type CredentialsUpdateInput = {
readonly credentialID: { readonly credentialID: string }["credentialID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly label: { readonly label: string }["label"]
}
export type CredentialsUpdateOutput = void
export type CredentialsRemoveInput = {
readonly credentialID: { readonly credentialID: string }["credentialID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type CredentialsRemoveOutput = void
export type PermissionsListRequestsInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PermissionsListRequestsOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
}>
}
export type PermissionsListSavedInput = {
readonly projectID?: { readonly projectID?: string | undefined }["projectID"]
}
export type PermissionsListSavedOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly projectID: string
readonly action: string
readonly resource: string
}>
}["data"]
export type PermissionsRemoveSavedInput = { readonly id: { readonly id: string }["id"] }
export type PermissionsRemoveSavedOutput = void
export type PermissionsCreateInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["id"]
readonly action: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["action"]
readonly resources: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["resources"]
readonly save?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["save"]
readonly metadata?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["metadata"]
readonly source?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["source"]
readonly agent?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["agent"]
}
export type PermissionsCreateOutput = {
readonly data: { readonly id: string; readonly effect: "allow" | "deny" | "ask" }
}["data"]
export type PermissionsListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type PermissionsListOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
}>
}["data"]
export type PermissionsGetInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
}
export type PermissionsGetOutput = {
readonly data: {
readonly id: string
readonly sessionID: string
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
}
}["data"]
export type PermissionsReplyInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
readonly reply: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["reply"]
readonly message?: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["message"]
}
export type PermissionsReplyOutput = void
export type FilesListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly path?: string | undefined
}["location"]
readonly path?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly path?: string | undefined
}["path"]
}
export type FilesListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
}
export type FilesFindInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["location"]
readonly query: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["query"]
readonly type?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["type"]
readonly limit?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["limit"]
}
export type FilesFindOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
}
export type CommandsListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type CommandsListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly name: string
readonly template: string
readonly description?: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly subtask?: boolean
}>
}
export type SkillsListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type SkillsListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly name: string
readonly description?: string
readonly slash?: boolean
readonly location: string
readonly content: string
}>
}
export type EventsSubscribeOutput = OpenCodeEventEncoded
export type PtysListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PtysListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}>
}
export type PtysCreateInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly command?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["command"]
readonly args?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["args"]
readonly cwd?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["cwd"]
readonly title?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["title"]
readonly env?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["env"]
}
export type PtysCreateOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
export type PtysGetInput = {
readonly ptyID: { readonly ptyID: string }["ptyID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PtysGetOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
export type PtysUpdateInput = {
readonly ptyID: { readonly ptyID: string }["ptyID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly title?: {
readonly title?: string
readonly size?: { readonly rows: number; readonly cols: number }
}["title"]
readonly size?: { readonly title?: string; readonly size?: { readonly rows: number; readonly cols: number } }["size"]
}
export type PtysUpdateOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
export type PtysRemoveInput = {
readonly ptyID: { readonly ptyID: string }["ptyID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PtysRemoveOutput = void
export type QuestionsListRequestsInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type QuestionsListRequestsOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly questions: ReadonlyArray<{
readonly question: string
readonly header: string
readonly options: ReadonlyArray<{ readonly label: string; readonly description: string }>
readonly multiple?: boolean
readonly custom?: boolean
}>
readonly tool?: { readonly messageID: string; readonly callID: string }
}>
}
export type QuestionsListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type QuestionsListOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly questions: ReadonlyArray<{
readonly question: string
readonly header: string
readonly options: ReadonlyArray<{ readonly label: string; readonly description: string }>
readonly multiple?: boolean
readonly custom?: boolean
}>
readonly tool?: { readonly messageID: string; readonly callID: string }
}>
}["data"]
export type QuestionsReplyInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
readonly answers: { readonly answers: ReadonlyArray<ReadonlyArray<string>> }["answers"]
}
export type QuestionsReplyOutput = void
export type QuestionsRejectInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
}
export type QuestionsRejectOutput = void
export type ReferencesListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ReferencesListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly name: string
readonly path: string
readonly description?: string
readonly hidden?: boolean
readonly source:
| { readonly type: "local"; readonly path: string; readonly description?: string; readonly hidden?: boolean }
| {
readonly type: "git"
readonly repository: string
readonly branch?: string
readonly description?: string
readonly hidden?: boolean
}
}>
}
export type ProjectCopiesCreateInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly strategy: { readonly strategy: string; readonly directory: string; readonly name?: string }["strategy"]
readonly directory: { readonly strategy: string; readonly directory: string; readonly name?: string }["directory"]
readonly name?: { readonly strategy: string; readonly directory: string; readonly name?: string }["name"]
}
export type ProjectCopiesCreateOutput = { readonly directory: string }
export type ProjectCopiesRemoveInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly directory: { readonly directory: string; readonly force: boolean }["directory"]
readonly force: { readonly directory: string; readonly force: boolean }["force"]
}
export type ProjectCopiesRemoveOutput = void
export type ProjectCopiesRefreshInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProjectCopiesRefreshOutput = void
+2
View File
@@ -0,0 +1,2 @@
export * from "./generated/index"
export type { EventsSubscribeOutput as OpenCodeEvent } from "./generated/types"
-41
View File
@@ -1,41 +0,0 @@
import type {
AgentApi as EffectAgentApi,
CommandApi as EffectCommandApi,
EventApi as EffectEventApi,
IntegrationApi as EffectIntegrationApi,
ModelApi as EffectModelApi,
PluginApi as EffectPluginApi,
ProviderApi as EffectProviderApi,
ReferenceApi as EffectReferenceApi,
SessionApi as EffectSessionApi,
SkillApi as EffectSkillApi,
} from "../effect/api/api.js"
import type { Effect, Stream } from "effect"
type PromisifyOperation<Operation> = Operation extends (
...args: infer Args
) => Effect.Effect<infer Success, unknown, unknown>
? (...args: Args) => Promise<Success>
: Operation extends (...args: infer Args) => Stream.Stream<infer Success, unknown, unknown>
? (...args: Args) => AsyncIterable<Success>
: Operation
type PromisifyApi<Api> = {
readonly [Name in keyof Api]: PromisifyOperation<Api[Name]>
}
export type AgentApi = PromisifyApi<EffectAgentApi<unknown>>
export type CommandApi = PromisifyApi<EffectCommandApi<unknown>>
export type EventApi = PromisifyApi<EffectEventApi<unknown>>
export type IntegrationApi = PromisifyApi<EffectIntegrationApi<unknown>>
export type ModelApi = PromisifyApi<EffectModelApi<unknown>>
export type PluginApi = PromisifyApi<EffectPluginApi<unknown>>
export type ProviderApi = PromisifyApi<EffectProviderApi<unknown>>
export type ReferenceApi = PromisifyApi<EffectReferenceApi<unknown>>
export type SessionApi = PromisifyApi<EffectSessionApi<unknown>>
export type SkillApi = PromisifyApi<EffectSkillApi<unknown>>
export interface CatalogApi {
readonly provider: ProviderApi
readonly model: ModelApi
}
@@ -1,6440 +0,0 @@
import type { OpenCodeEventEncoded } from "@opencode-ai/protocol/groups/event"
export type JsonValue =
| null
| boolean
| number
| string
| ReadonlyArray<JsonValue>
| { readonly [key: string]: JsonValue }
export type UnauthorizedError = { readonly _tag: "UnauthorizedError"; readonly message: string }
export const isUnauthorizedError = (value: unknown): value is UnauthorizedError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnauthorizedError"
export type InvalidRequestError = {
readonly _tag: "InvalidRequestError"
readonly message: string
readonly kind?: string | undefined
readonly field?: string | undefined
}
export const isInvalidRequestError = (value: unknown): value is InvalidRequestError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "InvalidRequestError"
export type InvalidCursorError = { readonly _tag: "InvalidCursorError"; readonly message: string }
export const isInvalidCursorError = (value: unknown): value is InvalidCursorError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "InvalidCursorError"
export type SessionNotFoundError = {
readonly _tag: "SessionNotFoundError"
readonly sessionID: string
readonly message: string
}
export const isSessionNotFoundError = (value: unknown): value is SessionNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionNotFoundError"
export type MessageNotFoundError = {
readonly _tag: "MessageNotFoundError"
readonly sessionID: string
readonly messageID: string
readonly message: string
}
export const isMessageNotFoundError = (value: unknown): value is MessageNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "MessageNotFoundError"
export type ConflictError = {
readonly _tag: "ConflictError"
readonly message: string
readonly resource?: string | undefined
}
export const isConflictError = (value: unknown): value is ConflictError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ConflictError"
export type CommandNotFoundError = {
readonly _tag: "CommandNotFoundError"
readonly command: string
readonly message: string
}
export const isCommandNotFoundError = (value: unknown): value is CommandNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "CommandNotFoundError"
export type CommandEvaluationError = {
readonly _tag: "CommandEvaluationError"
readonly command: string
readonly message: string
}
export const isCommandEvaluationError = (value: unknown): value is CommandEvaluationError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "CommandEvaluationError"
export type SkillNotFoundError = {
readonly _tag: "SkillNotFoundError"
readonly skill: string
readonly message: string
}
export const isSkillNotFoundError = (value: unknown): value is SkillNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SkillNotFoundError"
export type SessionBusyError = {
readonly _tag: "SessionBusyError"
readonly sessionID: string
readonly message: string
}
export const isSessionBusyError = (value: unknown): value is SessionBusyError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionBusyError"
export type ServiceUnavailableError = {
readonly _tag: "ServiceUnavailableError"
readonly message: string
readonly service?: string | undefined
}
export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError"
export type UnknownError = {
readonly _tag: "UnknownError"
readonly message: string
readonly ref?: string | undefined
}
export const isUnknownError = (value: unknown): value is UnknownError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnknownError"
export type ProviderNotFoundError = {
readonly _tag: "ProviderNotFoundError"
readonly providerID: string
readonly message: string
}
export const isProviderNotFoundError = (value: unknown): value is ProviderNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ProviderNotFoundError"
export type FormNotFoundError = { readonly _tag: "FormNotFoundError"; readonly id: string; readonly message: string }
export const isFormNotFoundError = (value: unknown): value is FormNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "FormNotFoundError"
export type FormAlreadySettledError = {
readonly _tag: "FormAlreadySettledError"
readonly id: string
readonly message: string
}
export const isFormAlreadySettledError = (value: unknown): value is FormAlreadySettledError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "FormAlreadySettledError"
export type FormInvalidAnswerError = {
readonly _tag: "FormInvalidAnswerError"
readonly id: string
readonly message: string
}
export const isFormInvalidAnswerError = (value: unknown): value is FormInvalidAnswerError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "FormInvalidAnswerError"
export type PermissionNotFoundError = {
readonly _tag: "PermissionNotFoundError"
readonly requestID: string
readonly message: string
}
export const isPermissionNotFoundError = (value: unknown): value is PermissionNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "PermissionNotFoundError"
export type PtyNotFoundError = { readonly _tag: "PtyNotFoundError"; readonly ptyID: string; readonly message: string }
export const isPtyNotFoundError = (value: unknown): value is PtyNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "PtyNotFoundError"
export type ShellNotFoundError = { readonly _tag: "ShellNotFoundError"; readonly id: string; readonly message: string }
export const isShellNotFoundError = (value: unknown): value is ShellNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ShellNotFoundError"
export type QuestionNotFoundError = {
readonly _tag: "QuestionNotFoundError"
readonly requestID: string
readonly message: string
}
export const isQuestionNotFoundError = (value: unknown): value is QuestionNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "QuestionNotFoundError"
export type ProjectCopyError = {
readonly name: "ProjectCopyError"
readonly data: { readonly message: string; readonly forceRequired?: boolean | undefined }
}
export const isProjectCopyError = (value: unknown): value is ProjectCopyError =>
typeof value === "object" && value !== null && "name" in value && value["name"] === "ProjectCopyError"
export type HealthGetOutput = { readonly healthy: true }
export type LocationGetInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type LocationGetOutput = {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
export type AgentListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type AgentListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly request: {
readonly settings: { readonly [x: string]: JsonValue }
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}
readonly system?: string
readonly description?: string
readonly mode: "subagent" | "primary" | "all"
readonly hidden: boolean
readonly color?: string | "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info"
readonly steps?: number
readonly permissions: ReadonlyArray<{
readonly action: string
readonly resource: string
readonly effect: "allow" | "deny" | "ask"
}>
}>
}
export type PluginListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PluginListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{ readonly id: string }>
}
export type SessionListInput = {
readonly workspace?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["workspace"]
readonly limit?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["limit"]
readonly order?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["order"]
readonly search?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["search"]
readonly parentID?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["parentID"]
readonly directory?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["directory"]
readonly project?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["project"]
readonly subpath?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["subpath"]
readonly cursor?: {
readonly workspace?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly parentID?: string | null | undefined
readonly directory?: string | undefined
readonly project?: string | undefined
readonly subpath?: string | undefined
readonly cursor?: string | undefined
}["cursor"]
}
export type SessionListOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly parentID?: string
readonly projectID: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}>
readonly watermarks: { readonly [x: string]: number }
readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
}
export type SessionCreateInput = {
readonly id?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["id"]
readonly agent?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["agent"]
readonly model?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["model"]
readonly location?: {
readonly id?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["location"]
}
export type SessionCreateOutput = {
readonly data: {
readonly id: string
readonly parentID?: string
readonly projectID: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}["data"]
export type SessionActiveOutput = {
readonly data: { readonly [x: string]: { readonly type: "running" } }
readonly watermarks: { readonly [x: string]: number }
}
export type SessionGetInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionGetOutput = {
readonly data: {
readonly id: string
readonly parentID?: string
readonly projectID: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}["data"]
export type SessionForkInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly messageID?: { readonly messageID?: string | undefined }["messageID"]
}
export type SessionForkOutput = {
readonly data: {
readonly id: string
readonly parentID?: string
readonly projectID: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}["data"]
export type SessionSwitchAgentInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly agent: { readonly agent: string }["agent"]
}
export type SessionSwitchAgentOutput = void
export type SessionSwitchModelInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly model: {
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}["model"]
}
export type SessionSwitchModelOutput = void
export type SessionRenameInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly title: { readonly title: string }["title"]
}
export type SessionRenameOutput = void
export type SessionPromptInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["id"]
readonly prompt: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["prompt"]
readonly delivery?: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["delivery"]
readonly resume?: {
readonly id?: string | null
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["resume"]
}
export type SessionPromptOutput = {
readonly data: {
readonly admittedSeq: number
readonly id: string
readonly sessionID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
readonly timeCreated: number
readonly promotedSeq?: number
}
}["data"]
export type SessionCommandInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["id"]
readonly command: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["command"]
readonly arguments?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["arguments"]
readonly agent?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["agent"]
readonly model?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["model"]
readonly files?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["files"]
readonly agents?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["agents"]
readonly delivery?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["delivery"]
readonly resume?: {
readonly id?: string | null
readonly command: string
readonly arguments?: string | null
readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly files?: ReadonlyArray<{
readonly uri: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | null
}["resume"]
}
export type SessionCommandOutput = {
readonly data: {
readonly admittedSeq: number
readonly id: string
readonly sessionID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
readonly timeCreated: number
readonly promotedSeq?: number
}
}["data"]
export type SessionSkillInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: {
readonly id?: string | undefined
readonly skill: string
readonly resume?: boolean | undefined
}["id"]
readonly skill: {
readonly id?: string | undefined
readonly skill: string
readonly resume?: boolean | undefined
}["skill"]
readonly resume?: {
readonly id?: string | undefined
readonly skill: string
readonly resume?: boolean | undefined
}["resume"]
}
export type SessionSkillOutput = void
export type SessionSyntheticInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly text: {
readonly text: string
readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue }
}["text"]
readonly description?: {
readonly text: string
readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue }
}["description"]
readonly metadata?: {
readonly text: string
readonly description?: string | null
readonly metadata?: { readonly [x: string]: JsonValue }
}["metadata"]
}
export type SessionSyntheticOutput = void
export type SessionShellInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: { readonly id?: string | undefined; readonly command: string }["id"]
readonly command: { readonly id?: string | undefined; readonly command: string }["command"]
}
export type SessionShellOutput = void
export type SessionCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionCompactOutput = void
export type SessionWaitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionWaitOutput = void
export type SessionRevertStageInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly messageID: { readonly messageID: string; readonly files?: boolean | undefined }["messageID"]
readonly files?: { readonly messageID: string; readonly files?: boolean | undefined }["files"]
}
export type SessionRevertStageOutput = {
readonly data: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}["data"]
export type SessionRevertClearInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionRevertClearOutput = void
export type SessionRevertCommitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionRevertCommitOutput = void
export type SessionContextInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionContextOutput = {
readonly data: ReadonlyArray<
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "agent-switched"
readonly agent: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "model-switched"
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly sessionID: string
readonly text: string
readonly description?: string
readonly type: "synthetic"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "system"
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "skill"
readonly name: string
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "shell"
readonly shell: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
readonly metadata: { readonly [x: string]: JsonValue }
readonly time: {
readonly started: number | "Infinity" | "-Infinity" | "NaN"
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
}
}
readonly output?: {
readonly output: string
readonly cursor: number
readonly size: number
readonly truncated: boolean
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "assistant"
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| {
readonly type: "reasoning"
readonly text: string
readonly state?: { readonly [x: string]: JsonValue }
readonly time?: { readonly created: number; readonly completed?: number }
}
| {
readonly type: "tool"
readonly id: string
readonly name: string
readonly executed?: boolean
readonly providerState?: { readonly [x: string]: JsonValue }
readonly providerResultState?: { readonly [x: string]: JsonValue }
readonly state:
| { readonly status: "pending"; readonly input: string }
| {
readonly status: "running"
readonly input: { readonly [x: string]: JsonValue }
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
| {
readonly status: "completed"
readonly input: { readonly [x: string]: JsonValue }
readonly attachments?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly structured: { readonly [x: string]: JsonValue }
readonly result?: JsonValue
}
| {
readonly status: "error"
readonly input: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly structured: { readonly [x: string]: JsonValue }
readonly error:
| {
readonly type: "provider.rate-limit"
readonly message: string
readonly retryAfterMs?: number
}
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| {
readonly type: "aborted"
readonly message: string
readonly reason?: "user" | "shutdown" | "timeout"
}
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly result?: JsonValue
}
readonly time: {
readonly created: number
readonly ran?: number
readonly completed?: number
readonly pruned?: number
}
}
>
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly error?:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly retry?: {
readonly attempt: number
readonly at: number
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly type: "compaction"
readonly reason: "auto" | "manual"
readonly summary: string
readonly recent: string
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
}
>
}["data"]
export type SessionListContextEntriesInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionListContextEntriesOutput = {
readonly data: ReadonlyArray<{ readonly key: string; readonly value: JsonValue }>
}["data"]
export type SessionPutContextEntryInput = {
readonly sessionID: { readonly sessionID: string; readonly key: string }["sessionID"]
readonly key: { readonly sessionID: string; readonly key: string }["key"]
readonly value: { readonly value: JsonValue }["value"]
}
export type SessionPutContextEntryOutput = void
export type SessionRemoveContextEntryInput = {
readonly sessionID: { readonly sessionID: string; readonly key: string }["sessionID"]
readonly key: { readonly sessionID: string; readonly key: string }["key"]
}
export type SessionRemoveContextEntryOutput = void
export type SessionLogInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly after?: { readonly after?: number | undefined; readonly follow?: boolean | undefined }["after"]
readonly follow?: { readonly after?: number | undefined; readonly follow?: boolean | undefined }["follow"]
}
export type SessionLogOutput =
| (
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.agent.selected"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly agent: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.model.selected"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.moved"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.renamed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly title: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.forked"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.promoted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly inputID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.admitted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly inputID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.succeeded"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.failed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| {
readonly type: "aborted"
readonly message: string
readonly reason?: "user" | "shutdown" | "timeout"
}
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.interrupted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly reason: "user" | "shutdown" | "superseded" }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.context.updated"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.synthetic"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly text: string
readonly description?: string
readonly metadata?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.skill.activated"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.shell.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly shell: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number
readonly metadata: { readonly [x: string]: unknown }
readonly time: { readonly started: number; readonly completed?: number }
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.shell.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly shell: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number
readonly metadata: { readonly [x: string]: unknown }
readonly time: { readonly started: number; readonly completed?: number }
}
readonly output: {
readonly output: string
readonly cursor: number
readonly size: number
readonly truncated: boolean
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.step.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly snapshot?: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.step.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly snapshot?: string
readonly files?: ReadonlyArray<string>
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.step.failed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| {
readonly type: "aborted"
readonly message: string
readonly reason?: "user" | "shutdown" | "timeout"
}
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.text.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly assistantMessageID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.text.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.reasoning.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly state?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.reasoning.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly text: string
readonly state?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.input.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly name: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.input.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly text: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.called"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly input: { readonly [x: string]: unknown }
readonly executed: boolean
readonly state?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.progress"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: unknown }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.success"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: unknown }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly result?: unknown
readonly executed: boolean
readonly resultState?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.failed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| {
readonly type: "aborted"
readonly message: string
readonly reason?: "user" | "shutdown" | "timeout"
}
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly result?: unknown
readonly executed: boolean
readonly resultState?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.retry.scheduled"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly attempt: number
readonly at: number
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| {
readonly type: "aborted"
readonly message: string
readonly reason?: "user" | "shutdown" | "timeout"
}
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.compaction.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.compaction.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly reason: "auto" | "manual"
readonly text: string
readonly recent: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.revert.staged"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly revert: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.revert.cleared"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.revert.committed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly to: string }
}
)
| { readonly type: "log.synced"; readonly aggregateID: string; readonly seq?: number }
export type SessionInterruptInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionInterruptOutput = void
export type SessionBackgroundInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type SessionBackgroundOutput = void
export type SessionMessageInput = {
readonly sessionID: { readonly sessionID: string; readonly messageID: string }["sessionID"]
readonly messageID: { readonly sessionID: string; readonly messageID: string }["messageID"]
}
export type SessionMessageOutput = {
readonly data:
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "agent-switched"
readonly agent: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "model-switched"
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly sessionID: string
readonly text: string
readonly description?: string
readonly type: "synthetic"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "system"
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "skill"
readonly name: string
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "shell"
readonly shell: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
readonly metadata: { readonly [x: string]: JsonValue }
readonly time: {
readonly started: number | "Infinity" | "-Infinity" | "NaN"
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
}
}
readonly output?: {
readonly output: string
readonly cursor: number
readonly size: number
readonly truncated: boolean
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "assistant"
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| {
readonly type: "reasoning"
readonly text: string
readonly state?: { readonly [x: string]: JsonValue }
readonly time?: { readonly created: number; readonly completed?: number }
}
| {
readonly type: "tool"
readonly id: string
readonly name: string
readonly executed?: boolean
readonly providerState?: { readonly [x: string]: JsonValue }
readonly providerResultState?: { readonly [x: string]: JsonValue }
readonly state:
| { readonly status: "pending"; readonly input: string }
| {
readonly status: "running"
readonly input: { readonly [x: string]: JsonValue }
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
| {
readonly status: "completed"
readonly input: { readonly [x: string]: JsonValue }
readonly attachments?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly structured: { readonly [x: string]: JsonValue }
readonly result?: JsonValue
}
| {
readonly status: "error"
readonly input: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly structured: { readonly [x: string]: JsonValue }
readonly error:
| {
readonly type: "provider.rate-limit"
readonly message: string
readonly retryAfterMs?: number
}
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| {
readonly type: "aborted"
readonly message: string
readonly reason?: "user" | "shutdown" | "timeout"
}
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly result?: JsonValue
}
readonly time: {
readonly created: number
readonly ran?: number
readonly completed?: number
readonly pruned?: number
}
}
>
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly error?:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly retry?: {
readonly attempt: number
readonly at: number
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly type: "compaction"
readonly reason: "auto" | "manual"
readonly summary: string
readonly recent: string
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
}
}["data"]
export type MessageListInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly limit?: {
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly cursor?: string | undefined
}["limit"]
readonly order?: {
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly cursor?: string | undefined
}["order"]
readonly cursor?: {
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly cursor?: string | undefined
}["cursor"]
}
export type MessageListOutput = {
readonly data: ReadonlyArray<
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "agent-switched"
readonly agent: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "model-switched"
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly type: "user"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly sessionID: string
readonly text: string
readonly description?: string
readonly type: "synthetic"
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "system"
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
readonly type: "skill"
readonly name: string
readonly text: string
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "shell"
readonly shell: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
readonly metadata: { readonly [x: string]: JsonValue }
readonly time: {
readonly started: number | "Infinity" | "-Infinity" | "NaN"
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
}
}
readonly output?: {
readonly output: string
readonly cursor: number
readonly size: number
readonly truncated: boolean
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number }
readonly type: "assistant"
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| {
readonly type: "reasoning"
readonly text: string
readonly state?: { readonly [x: string]: JsonValue }
readonly time?: { readonly created: number; readonly completed?: number }
}
| {
readonly type: "tool"
readonly id: string
readonly name: string
readonly executed?: boolean
readonly providerState?: { readonly [x: string]: JsonValue }
readonly providerResultState?: { readonly [x: string]: JsonValue }
readonly state:
| { readonly status: "pending"; readonly input: string }
| {
readonly status: "running"
readonly input: { readonly [x: string]: JsonValue }
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
| {
readonly status: "completed"
readonly input: { readonly [x: string]: JsonValue }
readonly attachments?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly structured: { readonly [x: string]: JsonValue }
readonly result?: JsonValue
}
| {
readonly status: "error"
readonly input: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly structured: { readonly [x: string]: JsonValue }
readonly error:
| {
readonly type: "provider.rate-limit"
readonly message: string
readonly retryAfterMs?: number
}
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| {
readonly type: "aborted"
readonly message: string
readonly reason?: "user" | "shutdown" | "timeout"
}
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly result?: JsonValue
}
readonly time: {
readonly created: number
readonly ran?: number
readonly completed?: number
readonly pruned?: number
}
}
>
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly error?:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly retry?: {
readonly attempt: number
readonly at: number
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly type: "compaction"
readonly reason: "auto" | "manual"
readonly summary: string
readonly recent: string
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number }
}
>
readonly watermark?: number
readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
}
export type ModelListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ModelListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly providerID: string
readonly family?: string
readonly name: string
readonly api:
| {
readonly id: string
readonly type: "aisdk"
readonly package: string
readonly url?: string
readonly settings?: { readonly [x: string]: JsonValue }
}
| {
readonly id: string
readonly type: "native"
readonly url?: string
readonly settings: { readonly [x: string]: JsonValue }
}
readonly capabilities: {
readonly tools: boolean
readonly input: ReadonlyArray<string>
readonly output: ReadonlyArray<string>
}
readonly request: {
readonly settings: { readonly [x: string]: JsonValue }
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
readonly variant?: string
}
readonly variants: ReadonlyArray<{
readonly id: string
readonly settings: { readonly [x: string]: JsonValue }
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}>
readonly time: { readonly released: number }
readonly cost: ReadonlyArray<{
readonly tier?: { readonly type: "context"; readonly size: number }
readonly input: number
readonly output: number
readonly cache: { readonly read: number; readonly write: number }
}>
readonly status: "alpha" | "beta" | "deprecated" | "active"
readonly enabled: boolean
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
}>
}
export type ModelDefaultInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ModelDefaultOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly providerID: string
readonly family?: string
readonly name: string
readonly api:
| {
readonly id: string
readonly type: "aisdk"
readonly package: string
readonly url?: string
readonly settings?: { readonly [x: string]: JsonValue }
}
| {
readonly id: string
readonly type: "native"
readonly url?: string
readonly settings: { readonly [x: string]: JsonValue }
}
readonly capabilities: {
readonly tools: boolean
readonly input: ReadonlyArray<string>
readonly output: ReadonlyArray<string>
}
readonly request: {
readonly settings: { readonly [x: string]: JsonValue }
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
readonly variant?: string
}
readonly variants: ReadonlyArray<{
readonly id: string
readonly settings: { readonly [x: string]: JsonValue }
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}>
readonly time: { readonly released: number }
readonly cost: ReadonlyArray<{
readonly tier?: { readonly type: "context"; readonly size: number }
readonly input: number
readonly output: number
readonly cache: { readonly read: number; readonly write: number }
}>
readonly status: "alpha" | "beta" | "deprecated" | "active"
readonly enabled: boolean
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
} | null
}
export type GenerateTextInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly prompt: {
readonly prompt: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
}["prompt"]
readonly model?: {
readonly prompt: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
}["model"]
}
export type GenerateTextOutput = { readonly data: { readonly text: string } }["data"]
export type ProviderListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProviderListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly integrationID?: string
readonly name: string
readonly disabled?: boolean
readonly api:
| {
readonly type: "aisdk"
readonly package: string
readonly url?: string
readonly settings?: { readonly [x: string]: JsonValue }
}
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
readonly request: {
readonly settings: { readonly [x: string]: JsonValue }
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}
}>
}
export type ProviderGetInput = {
readonly providerID: { readonly providerID: string }["providerID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProviderGetOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly integrationID?: string
readonly name: string
readonly disabled?: boolean
readonly api:
| {
readonly type: "aisdk"
readonly package: string
readonly url?: string
readonly settings?: { readonly [x: string]: JsonValue }
}
| { readonly type: "native"; readonly url?: string; readonly settings: { readonly [x: string]: JsonValue } }
readonly request: {
readonly settings: { readonly [x: string]: JsonValue }
readonly headers: { readonly [x: string]: string }
readonly body: { readonly [x: string]: JsonValue }
}
}
}
export type IntegrationListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly name: string
readonly methods: ReadonlyArray<
| {
readonly id: string
readonly type: "oauth"
readonly label: string
readonly prompts?: ReadonlyArray<
| {
readonly type: "text"
readonly key: string
readonly message: string
readonly placeholder?: string
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
| {
readonly type: "select"
readonly key: string
readonly message: string
readonly options: ReadonlyArray<{
readonly label: string
readonly value: string
readonly hint?: string
}>
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
>
}
| { readonly type: "key"; readonly label?: string }
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
>
readonly connections: ReadonlyArray<
| { readonly type: "credential"; readonly id: string; readonly label: string }
| { readonly type: "env"; readonly name: string }
>
}>
}
export type IntegrationGetInput = {
readonly integrationID: { readonly integrationID: string }["integrationID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationGetOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly name: string
readonly methods: ReadonlyArray<
| {
readonly id: string
readonly type: "oauth"
readonly label: string
readonly prompts?: ReadonlyArray<
| {
readonly type: "text"
readonly key: string
readonly message: string
readonly placeholder?: string
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
| {
readonly type: "select"
readonly key: string
readonly message: string
readonly options: ReadonlyArray<{
readonly label: string
readonly value: string
readonly hint?: string
}>
readonly when?: { readonly key: string; readonly op: "eq" | "neq"; readonly value: string }
}
>
}
| { readonly type: "key"; readonly label?: string }
| { readonly type: "env"; readonly names: ReadonlyArray<string> }
>
readonly connections: ReadonlyArray<
| { readonly type: "credential"; readonly id: string; readonly label: string }
| { readonly type: "env"; readonly name: string }
>
} | null
}
export type IntegrationConnectKeyInput = {
readonly integrationID: { readonly integrationID: string }["integrationID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly key: { readonly key: string; readonly label?: string | undefined }["key"]
readonly label?: { readonly key: string; readonly label?: string | undefined }["label"]
}
export type IntegrationConnectKeyOutput = void
export type IntegrationConnectOauthInput = {
readonly integrationID: { readonly integrationID: string }["integrationID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly methodID: {
readonly methodID: string
readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined
}["methodID"]
readonly inputs: {
readonly methodID: string
readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined
}["inputs"]
readonly label?: {
readonly methodID: string
readonly inputs: { readonly [x: string]: string }
readonly label?: string | undefined
}["label"]
}
export type IntegrationConnectOauthOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly attemptID: string
readonly url: string
readonly instructions: string
readonly mode: "auto" | "code"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
}
export type IntegrationAttemptStatusInput = {
readonly attemptID: { readonly attemptID: string }["attemptID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationAttemptStatusOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data:
| {
readonly status: "pending"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
| {
readonly status: "complete"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
| {
readonly status: "failed"
readonly message: string
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
| {
readonly status: "expired"
readonly time: {
readonly created: number | "Infinity" | "-Infinity" | "NaN"
readonly expires: number | "Infinity" | "-Infinity" | "NaN"
}
}
}
export type IntegrationAttemptCompleteInput = {
readonly attemptID: { readonly attemptID: string }["attemptID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly code?: { readonly code?: string | undefined }["code"]
}
export type IntegrationAttemptCompleteOutput = void
export type IntegrationAttemptCancelInput = {
readonly attemptID: { readonly attemptID: string }["attemptID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type IntegrationAttemptCancelOutput = void
export type ServerMcpListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ServerMcpListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly name: string
readonly status:
| { readonly status: "connected" }
| { readonly status: "pending" }
| { readonly status: "disabled" }
| { readonly status: "failed"; readonly error: string }
| { readonly status: "needs_auth" }
| { readonly status: "needs_client_registration"; readonly error: string }
readonly integrationID?: string
}>
}
export type CredentialUpdateInput = {
readonly credentialID: { readonly credentialID: string }["credentialID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly label: { readonly label: string }["label"]
}
export type CredentialUpdateOutput = void
export type CredentialRemoveInput = {
readonly credentialID: { readonly credentialID: string }["credentialID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type CredentialRemoveOutput = void
export type ProjectCurrentInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProjectCurrentOutput = { readonly id: string; readonly directory: string }
export type ProjectDirectoriesInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProjectDirectoriesOutput = ReadonlyArray<{ readonly directory: string; readonly strategy?: string }>
export type FormListRequestsInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type FormListRequestsOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form"
readonly fields: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
>
}
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url"
readonly url: string
}
>
}
export type FormListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type FormListOutput = {
readonly data: ReadonlyArray<
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form"
readonly fields: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
>
}
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url"
readonly url: string
}
>
}["data"]
export type FormCreateInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: {
readonly id?: string | null
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url"
readonly fields?: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
> | null
readonly url?: string | null
}["id"]
readonly title?: {
readonly id?: string | null
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url"
readonly fields?: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
> | null
readonly url?: string | null
}["title"]
readonly metadata?: {
readonly id?: string | null
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url"
readonly fields?: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
> | null
readonly url?: string | null
}["metadata"]
readonly mode: {
readonly id?: string | null
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url"
readonly fields?: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
> | null
readonly url?: string | null
}["mode"]
readonly fields?: {
readonly id?: string | null
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url"
readonly fields?: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
> | null
readonly url?: string | null
}["fields"]
readonly url?: {
readonly id?: string | null
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form" | "url"
readonly fields?: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
> | null
readonly url?: string | null
}["url"]
}
export type FormCreateOutput = {
readonly data:
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form"
readonly fields: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
>
}
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url"
readonly url: string
}
}["data"]
export type FormGetInput = {
readonly sessionID: { readonly sessionID: string; readonly formID: string }["sessionID"]
readonly formID: { readonly sessionID: string; readonly formID: string }["formID"]
}
export type FormGetOutput = {
readonly data:
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "form"
readonly fields: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "number"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "integer"
readonly minimum?: number | "Infinity" | "-Infinity" | "NaN"
readonly maximum?: number | "Infinity" | "-Infinity" | "NaN"
readonly default?: number | "Infinity" | "-Infinity" | "NaN"
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | "Infinity" | "-Infinity" | "NaN" | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
>
}
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly mode: "url"
readonly url: string
}
}["data"]
export type FormStateInput = {
readonly sessionID: { readonly sessionID: string; readonly formID: string }["sessionID"]
readonly formID: { readonly sessionID: string; readonly formID: string }["formID"]
}
export type FormStateOutput = {
readonly data:
| { readonly status: "pending" }
| {
readonly status: "answered"
readonly answer: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> }
}
| { readonly status: "cancelled" }
}["data"]
export type FormReplyInput = {
readonly sessionID: { readonly sessionID: string; readonly formID: string }["sessionID"]
readonly formID: { readonly sessionID: string; readonly formID: string }["formID"]
readonly answer: {
readonly answer: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> }
}["answer"]
}
export type FormReplyOutput = void
export type FormCancelInput = {
readonly sessionID: { readonly sessionID: string; readonly formID: string }["sessionID"]
readonly formID: { readonly sessionID: string; readonly formID: string }["formID"]
}
export type FormCancelOutput = void
export type PermissionListRequestsInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PermissionListRequestsOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
}>
}
export type PermissionListSavedInput = { readonly projectID?: { readonly projectID?: string | undefined }["projectID"] }
export type PermissionListSavedOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly projectID: string
readonly action: string
readonly resource: string
}>
}["data"]
export type PermissionRemoveSavedInput = { readonly id: { readonly id: string }["id"] }
export type PermissionRemoveSavedOutput = void
export type PermissionCreateInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["id"]
readonly action: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["action"]
readonly resources: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["resources"]
readonly save?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["save"]
readonly metadata?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["metadata"]
readonly source?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["source"]
readonly agent?: {
readonly id?: string | null
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
readonly agent?: string | null
}["agent"]
}
export type PermissionCreateOutput = {
readonly data: { readonly id: string; readonly effect: "allow" | "deny" | "ask" }
}["data"]
export type PermissionListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type PermissionListOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
}>
}["data"]
export type PermissionGetInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
}
export type PermissionGetOutput = {
readonly data: {
readonly id: string
readonly sessionID: string
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: JsonValue }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
}
}["data"]
export type PermissionReplyInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
readonly reply: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["reply"]
readonly message?: { readonly reply: "once" | "always" | "reject"; readonly message?: string | undefined }["message"]
}
export type PermissionReplyOutput = void
export type FileReadInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly path: string
}
export type FileReadOutput = globalThis.Uint8Array
export type FileListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly path?: string | undefined
}["location"]
readonly path?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly path?: string | undefined
}["path"]
}
export type FileListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
}
export type FileFindInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["location"]
readonly query: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["query"]
readonly type?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["type"]
readonly limit?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly query: string
readonly type?: "file" | "directory" | undefined
readonly limit?: number | undefined
}["limit"]
}
export type FileFindOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{ readonly path: string; readonly type: "file" | "directory" }>
}
export type CommandListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type CommandListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly name: string
readonly template: string
readonly description?: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly subtask?: boolean
}>
}
export type SkillListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type SkillListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly name: string
readonly description?: string
readonly slash?: boolean
readonly autoinvoke?: boolean
readonly location: string
readonly content: string
}>
}
export type EventSubscribeOutput =
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "models-dev.refreshed"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "integration.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "integration.connection.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly integrationID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "catalog.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "agent.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.created"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly info: {
readonly id: string
readonly slug: string
readonly projectID: string
readonly workspaceID?: string
readonly directory: string
readonly path?: string
readonly parentID?: string
readonly summary?: {
readonly additions: number
readonly deletions: number
readonly files: number
readonly diffs?: ReadonlyArray<{
readonly file?: string
readonly patch?: string
readonly additions: number
readonly deletions: number
readonly status?: "added" | "deleted" | "modified"
}>
}
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly share?: { readonly url: string }
readonly title: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly version: string
readonly metadata?: { readonly [x: string]: any }
readonly time: {
readonly created: number
readonly updated: number
readonly compacting?: number
readonly archived?: number
}
readonly permission?: ReadonlyArray<{
readonly permission: string
readonly pattern: string
readonly action: "allow" | "deny" | "ask"
}>
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
}
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.updated"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly info: {
readonly id: string
readonly slug: string
readonly projectID: string
readonly workspaceID?: string
readonly directory: string
readonly path?: string
readonly parentID?: string
readonly summary?: {
readonly additions: number
readonly deletions: number
readonly files: number
readonly diffs?: ReadonlyArray<{
readonly file?: string
readonly patch?: string
readonly additions: number
readonly deletions: number
readonly status?: "added" | "deleted" | "modified"
}>
}
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly share?: { readonly url: string }
readonly title: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly version: string
readonly metadata?: { readonly [x: string]: any }
readonly time: {
readonly created: number
readonly updated: number
readonly compacting?: number
readonly archived?: number
}
readonly permission?: ReadonlyArray<{
readonly permission: string
readonly pattern: string
readonly action: "allow" | "deny" | "ask"
}>
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
}
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.deleted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly info: {
readonly id: string
readonly slug: string
readonly projectID: string
readonly workspaceID?: string
readonly directory: string
readonly path?: string
readonly parentID?: string
readonly summary?: {
readonly additions: number
readonly deletions: number
readonly files: number
readonly diffs?: ReadonlyArray<{
readonly file?: string
readonly patch?: string
readonly additions: number
readonly deletions: number
readonly status?: "added" | "deleted" | "modified"
}>
}
readonly cost?: number
readonly tokens?: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly share?: { readonly url: string }
readonly title: string
readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly version: string
readonly metadata?: { readonly [x: string]: any }
readonly time: {
readonly created: number
readonly updated: number
readonly compacting?: number
readonly archived?: number
}
readonly permission?: ReadonlyArray<{
readonly permission: string
readonly pattern: string
readonly action: "allow" | "deny" | "ask"
}>
readonly revert?: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
}
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "message.updated"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly info:
| {
readonly id: string
readonly sessionID: string
readonly role: "user"
readonly time: { readonly created: number }
readonly format?:
| (
| { readonly type: "text" }
| {
readonly type: "json_schema"
readonly schema: { readonly [x: string]: any }
readonly retryCount?: number | undefined | undefined
}
)
| undefined
readonly summary?:
| {
readonly title?: string | undefined
readonly body?: string | undefined
readonly diffs: ReadonlyArray<{
readonly file?: string
readonly patch?: string
readonly additions: number
readonly deletions: number
readonly status?: "added" | "deleted" | "modified"
}>
}
| undefined
readonly agent: string
readonly model: {
readonly providerID: string
readonly modelID: string
readonly variant?: string | undefined
}
readonly system?: string | undefined
readonly tools?: { readonly [x: string]: boolean } | undefined
}
| {
readonly id: string
readonly sessionID: string
readonly role: "assistant"
readonly time: { readonly created: number; readonly completed?: number | undefined }
readonly error?:
| {
readonly name: "ProviderAuthError"
readonly data: { readonly providerID: string; readonly message: string }
}
| {
readonly name: "UnknownError"
readonly data: { readonly message: string; readonly ref?: string | undefined }
}
| { readonly name: "MessageOutputLengthError"; readonly data: {} }
| { readonly name: "MessageAbortedError"; readonly data: { readonly message: string } }
| {
readonly name: "StructuredOutputError"
readonly data: { readonly message: string; readonly retries: number }
}
| {
readonly name: "ContextOverflowError"
readonly data: { readonly message: string; readonly responseBody?: string | undefined }
}
| { readonly name: "ContentFilterError"; readonly data: { readonly message: string } }
| {
readonly name: "APIError"
readonly data: {
readonly message: string
readonly statusCode?: number | undefined
readonly isRetryable: boolean
readonly responseHeaders?: { readonly [x: string]: string } | undefined
readonly responseBody?: string | undefined
readonly metadata?: { readonly [x: string]: string } | undefined
}
}
| undefined
readonly parentID: string
readonly modelID: string
readonly providerID: string
readonly mode: string
readonly agent: string
readonly path: { readonly cwd: string; readonly root: string }
readonly summary?: boolean | undefined
readonly cost: number
readonly tokens: {
readonly total?: number | undefined
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly structured?: any | undefined
readonly variant?: string | undefined
readonly finish?: string | undefined
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "message.removed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly messageID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "message.part.updated"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly part:
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "text"
readonly text: string
readonly synthetic?: boolean | undefined
readonly ignored?: boolean | undefined
readonly time?: { readonly start: number; readonly end?: number | undefined } | undefined
readonly metadata?: { readonly [x: string]: any } | undefined
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "subtask"
readonly prompt: string
readonly description: string
readonly agent: string
readonly model?: { readonly providerID: string; readonly modelID: string } | undefined
readonly command?: string | undefined
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "reasoning"
readonly text: string
readonly metadata?: { readonly [x: string]: any } | undefined
readonly time: { readonly start: number; readonly end?: number | undefined }
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "file"
readonly mime: string
readonly filename?: string | undefined
readonly url: string
readonly source?:
| (
| {
readonly text: { readonly value: string; readonly start: number; readonly end: number }
readonly type: "file"
readonly path: string
}
| {
readonly text: { readonly value: string; readonly start: number; readonly end: number }
readonly type: "symbol"
readonly path: string
readonly range: {
readonly start: { readonly line: number; readonly character: number }
readonly end: { readonly line: number; readonly character: number }
}
readonly name: string
readonly kind: number
}
| {
readonly text: { readonly value: string; readonly start: number; readonly end: number }
readonly type: "resource"
readonly clientName: string
readonly uri: string
}
)
| undefined
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "tool"
readonly callID: string
readonly tool: string
readonly state:
| { readonly status: "pending"; readonly input: { readonly [x: string]: any }; readonly raw: string }
| {
readonly status: "running"
readonly input: { readonly [x: string]: any }
readonly title?: string | undefined
readonly metadata?: { readonly [x: string]: any } | undefined
readonly time: { readonly start: number }
}
| {
readonly status: "completed"
readonly input: { readonly [x: string]: any }
readonly output: string
readonly title: string
readonly metadata: { readonly [x: string]: any }
readonly time: {
readonly start: number
readonly end: number
readonly compacted?: number | undefined
}
readonly attachments?:
| ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "file"
readonly mime: string
readonly filename?: string | undefined
readonly url: string
readonly source?:
| (
| {
readonly text: {
readonly value: string
readonly start: number
readonly end: number
}
readonly type: "file"
readonly path: string
}
| {
readonly text: {
readonly value: string
readonly start: number
readonly end: number
}
readonly type: "symbol"
readonly path: string
readonly range: {
readonly start: { readonly line: number; readonly character: number }
readonly end: { readonly line: number; readonly character: number }
}
readonly name: string
readonly kind: number
}
| {
readonly text: {
readonly value: string
readonly start: number
readonly end: number
}
readonly type: "resource"
readonly clientName: string
readonly uri: string
}
)
| undefined
}>
| undefined
}
| {
readonly status: "error"
readonly input: { readonly [x: string]: any }
readonly error: string
readonly metadata?: { readonly [x: string]: any } | undefined
readonly time: { readonly start: number; readonly end: number }
}
readonly metadata?: { readonly [x: string]: any } | undefined
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "step-start"
readonly snapshot?: string | undefined
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "step-finish"
readonly reason: string
readonly snapshot?: string | undefined
readonly cost: number
readonly tokens: {
readonly total?: number | undefined
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "snapshot"
readonly snapshot: string
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "patch"
readonly hash: string
readonly files: ReadonlyArray<string>
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "agent"
readonly name: string
readonly source?: { readonly value: string; readonly start: number; readonly end: number } | undefined
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "retry"
readonly attempt: number
readonly error: {
readonly name: "APIError"
readonly data: {
readonly message: string
readonly statusCode?: number | undefined
readonly isRetryable: boolean
readonly responseHeaders?: { readonly [x: string]: string } | undefined
readonly responseBody?: string | undefined
readonly metadata?: { readonly [x: string]: string } | undefined
}
}
readonly time: { readonly created: number }
}
| {
readonly id: string
readonly sessionID: string
readonly messageID: string
readonly type: "compaction"
readonly auto: boolean
readonly overflow?: boolean | undefined
readonly tail_start_id?: string | undefined
}
readonly time: number
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "message.part.removed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly messageID: string; readonly partID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.agent.selected"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly agent: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.model.selected"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.moved"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.renamed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly title: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.forked"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly parentID: string; readonly from?: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.promoted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly inputID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.prompt.admitted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly inputID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.succeeded"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.failed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.execution.interrupted"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly reason: "user" | "shutdown" | "superseded" }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.context.updated"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.synthetic"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly text: string
readonly description?: string
readonly metadata?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.skill.activated"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly name: string; readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.shell.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly shell: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number
readonly metadata: { readonly [x: string]: unknown }
readonly time: { readonly started: number; readonly completed?: number }
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.shell.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly shell: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number
readonly metadata: { readonly [x: string]: unknown }
readonly time: { readonly started: number; readonly completed?: number }
}
readonly output: {
readonly output: string
readonly cursor: number
readonly size: number
readonly truncated: boolean
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.step.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly snapshot?: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.step.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly snapshot?: string
readonly files?: ReadonlyArray<string>
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.step.failed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.text.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly assistantMessageID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.text.delta"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly delta: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.text.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.reasoning.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly state?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.reasoning.delta"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly delta: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.reasoning.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly text: string
readonly state?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.input.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly name: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.input.delta"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly delta: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.input.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly text: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.called"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly input: { readonly [x: string]: unknown }
readonly executed: boolean
readonly state?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.progress"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: unknown }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.success"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: unknown }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly result?: unknown
readonly executed: boolean
readonly resultState?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.tool.failed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
readonly result?: unknown
readonly executed: boolean
readonly resultState?: { readonly [x: string]: unknown }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.retry.scheduled"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly assistantMessageID: string
readonly attempt: number
readonly at: number
readonly error:
| { readonly type: "provider.rate-limit"; readonly message: string; readonly retryAfterMs?: number }
| { readonly type: "provider.auth"; readonly message: string }
| { readonly type: "provider.quota"; readonly message: string }
| { readonly type: "provider.content-filter"; readonly message: string }
| { readonly type: "provider.transport"; readonly message: string }
| { readonly type: "provider.internal"; readonly message: string }
| { readonly type: "provider.invalid-output"; readonly message: string }
| { readonly type: "provider.invalid-request"; readonly message: string }
| { readonly type: "provider.no-route"; readonly message: string }
| { readonly type: "provider.unknown"; readonly message: string }
| {
readonly type: "permission.rejected"
readonly message: string
readonly permission: string
readonly resources: ReadonlyArray<string>
}
| { readonly type: "tool.unknown"; readonly message: string; readonly name: string }
| { readonly type: "tool.stale"; readonly message: string; readonly name?: string }
| { readonly type: "tool.execution"; readonly message: string }
| { readonly type: "tool.result-missing"; readonly message: string; readonly callID?: string }
| { readonly type: "aborted"; readonly message: string; readonly reason?: "user" | "shutdown" | "timeout" }
| { readonly type: "unknown"; readonly message: string; readonly agent?: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.compaction.started"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly reason: "auto" | "manual" }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.compaction.delta"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.compaction.ended"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly reason: "auto" | "manual"
readonly text: string
readonly recent: string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.revert.staged"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly revert: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.revert.cleared"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.revert.committed"
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly to: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "filesystem.changed"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly file: string; readonly event: "add" | "change" | "unlink" }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "reference.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "permission.v2.asked"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly id: string
readonly sessionID: string
readonly action: string
readonly resources: ReadonlyArray<string>
readonly save?: ReadonlyArray<string>
readonly metadata?: { readonly [x: string]: unknown }
readonly source?: { readonly type: "tool"; readonly messageID: string; readonly callID: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "permission.v2.replied"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly requestID: string
readonly reply: "once" | "always" | "reject"
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "plugin.added"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly id: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "project.directories.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly projectID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "command.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "config.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "skill.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "pty.created"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly info: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "pty.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly info: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "pty.exited"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly id: string; readonly exitCode: number }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "pty.deleted"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly id: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "shell.created"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly info: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number
readonly metadata: { readonly [x: string]: unknown }
readonly time: { readonly started: number; readonly completed?: number }
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "shell.exited"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly id: string
readonly exit?: number
readonly status: "running" | "exited" | "timeout" | "killed"
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "shell.deleted"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly id: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "question.v2.asked"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly id: string
readonly sessionID: string
readonly questions: ReadonlyArray<{
readonly question: string
readonly header: string
readonly options: ReadonlyArray<{ readonly label: string; readonly description: string }>
readonly multiple?: boolean
readonly custom?: boolean
}>
readonly tool?: { readonly messageID: string; readonly callID: string }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "question.v2.replied"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly requestID: string
readonly answers: ReadonlyArray<ReadonlyArray<string>>
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "question.v2.rejected"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly requestID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "form.created"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly form:
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: unknown }
readonly mode: "form"
readonly fields: ReadonlyArray<
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | boolean
}>
readonly type: "string"
readonly format?: "email" | "uri" | "date" | "date-time"
readonly minLength?: number
readonly maxLength?: number
readonly pattern?: string
readonly placeholder?: string
readonly default?: string
readonly options?: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly custom?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | boolean
}>
readonly type: "number"
readonly minimum?: number
readonly maximum?: number
readonly default?: number
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | boolean
}>
readonly type: "integer"
readonly minimum?: number
readonly maximum?: number
readonly default?: number
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | boolean
}>
readonly type: "boolean"
readonly default?: boolean
}
| {
readonly key: string
readonly title?: string
readonly description?: string
readonly required?: boolean
readonly when?: ReadonlyArray<{
readonly key: string
readonly op: "eq" | "neq"
readonly value: string | number | boolean
}>
readonly type: "multiselect"
readonly options: ReadonlyArray<{
readonly value: string
readonly label: string
readonly description?: string
}>
readonly minItems?: number
readonly maxItems?: number
readonly custom?: boolean
readonly default?: ReadonlyArray<string>
}
>
}
| {
readonly id: string
readonly sessionID: string
readonly title?: string
readonly metadata?: { readonly [x: string]: unknown }
readonly mode: "url"
readonly url: string
}
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "form.replied"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly id: string
readonly sessionID: string
readonly answer: { readonly [x: string]: string | number | boolean | ReadonlyArray<string> }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "form.cancelled"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly id: string; readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "todo.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly todos: ReadonlyArray<{ readonly content: string; readonly status: string; readonly priority: string }>
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.status"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly status:
| { readonly type: "idle" }
| {
readonly type: "retry"
readonly attempt: number
readonly message: string
readonly action?: {
readonly reason: string
readonly provider: string
readonly title: string
readonly message: string
readonly label: string
readonly link?: string
}
readonly next: number
}
| { readonly type: "busy" }
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.idle"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "tui.prompt.append"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly text: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "tui.command.execute"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly command:
| "session.list"
| "session.new"
| "session.share"
| "session.interrupt"
| "session.background"
| "session.compact"
| "session.page.up"
| "session.page.down"
| "session.line.up"
| "session.line.down"
| "session.half.page.up"
| "session.half.page.down"
| "session.first"
| "session.last"
| "prompt.clear"
| "prompt.submit"
| "agent.cycle"
| string
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "tui.toast.show"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly title?: string
readonly message: string
readonly variant: "info" | "success" | "warning" | "error"
readonly duration?: number | undefined
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "tui.session.select"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "installation.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly version: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "installation.update-available"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly version: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "vcs.branch.updated"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly branch?: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "mcp.status.changed"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly server: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "permission.asked"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly id: string
readonly sessionID: string
readonly permission: string
readonly patterns: ReadonlyArray<string>
readonly metadata: { readonly [x: string]: unknown }
readonly always: ReadonlyArray<string>
readonly tool?: { readonly messageID: string; readonly callID: string } | undefined
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "permission.replied"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly requestID: string
readonly reply: "once" | "always" | "reject"
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "question.asked"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly id: string
readonly sessionID: string
readonly questions: ReadonlyArray<{
readonly question: string
readonly header: string
readonly options: ReadonlyArray<{ readonly label: string; readonly description: string }>
readonly multiple?: boolean | undefined
readonly custom?: boolean | undefined
}>
readonly tool?: { readonly messageID: string; readonly callID: string } | undefined
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "question.replied"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID: string
readonly requestID: string
readonly answers: ReadonlyArray<ReadonlyArray<string>>
}
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "question.rejected"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly sessionID: string; readonly requestID: string }
}
| {
readonly id: string
readonly created: number
readonly metadata?: { readonly [x: string]: unknown }
readonly type: "session.error"
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly sessionID?: string | undefined
readonly error?:
| {
readonly name: "ProviderAuthError"
readonly data: { readonly providerID: string; readonly message: string }
}
| {
readonly name: "UnknownError"
readonly data: { readonly message: string; readonly ref?: string | undefined }
}
| { readonly name: "MessageOutputLengthError"; readonly data: {} }
| { readonly name: "MessageAbortedError"; readonly data: { readonly message: string } }
| {
readonly name: "StructuredOutputError"
readonly data: { readonly message: string; readonly retries: number }
}
| {
readonly name: "ContextOverflowError"
readonly data: { readonly message: string; readonly responseBody?: string | undefined }
}
| { readonly name: "ContentFilterError"; readonly data: { readonly message: string } }
| {
readonly name: "APIError"
readonly data: {
readonly message: string
readonly statusCode?: number | undefined
readonly isRetryable: boolean
readonly responseHeaders?: { readonly [x: string]: string } | undefined
readonly responseBody?: string | undefined
readonly metadata?: { readonly [x: string]: string } | undefined
}
}
| undefined
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: unknown } | undefined
readonly location?: { readonly directory: string; readonly workspaceID?: string } | undefined
readonly type: "server.connected"
readonly data: {}
}
export type EventChangesOutput =
| { readonly type: "log.hint"; readonly aggregateID: string; readonly seq: number }
| { readonly type: "log.sweep_required" }
export type PtyListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PtyListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}>
}
export type PtyCreateInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly command?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["command"]
readonly args?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["args"]
readonly cwd?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["cwd"]
readonly title?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["title"]
readonly env?: {
readonly command?: string
readonly args?: ReadonlyArray<string>
readonly cwd?: string
readonly title?: string
readonly env?: { readonly [x: string]: string }
}["env"]
}
export type PtyCreateOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
export type PtyGetInput = {
readonly ptyID: { readonly ptyID: string }["ptyID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PtyGetOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
export type PtyUpdateInput = {
readonly ptyID: { readonly ptyID: string }["ptyID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly title?: {
readonly title?: string
readonly size?: { readonly rows: number; readonly cols: number }
}["title"]
readonly size?: { readonly title?: string; readonly size?: { readonly rows: number; readonly cols: number } }["size"]
}
export type PtyUpdateOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly title: string
readonly command: string
readonly args: ReadonlyArray<string>
readonly cwd: string
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
}
}
export type PtyRemoveInput = {
readonly ptyID: { readonly ptyID: string }["ptyID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type PtyRemoveOutput = void
export type ShellListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ShellListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
readonly metadata: { readonly [x: string]: JsonValue }
readonly time: {
readonly started: number | "Infinity" | "-Infinity" | "NaN"
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
}
}>
}
export type ShellCreateInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly command: {
readonly command: string
readonly cwd?: string
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["command"]
readonly cwd?: {
readonly command: string
readonly cwd?: string
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["cwd"]
readonly timeout?: {
readonly command: string
readonly cwd?: string
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["timeout"]
readonly metadata?: {
readonly command: string
readonly cwd?: string
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["metadata"]
}
export type ShellCreateOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
readonly metadata: { readonly [x: string]: JsonValue }
readonly time: {
readonly started: number | "Infinity" | "-Infinity" | "NaN"
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
}
}
}
export type ShellGetInput = {
readonly id: { readonly id: string }["id"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ShellGetOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly id: string
readonly status: "running" | "exited" | "timeout" | "killed"
readonly command: string
readonly cwd: string
readonly shell: string
readonly file: string
readonly pid?: number
readonly exit?: number | "Infinity" | "-Infinity" | "NaN"
readonly metadata: { readonly [x: string]: JsonValue }
readonly time: {
readonly started: number | "Infinity" | "-Infinity" | "NaN"
readonly completed?: number | "Infinity" | "-Infinity" | "NaN"
}
}
}
export type ShellOutputInput = {
readonly id: { readonly id: string }["id"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly cursor?: number | undefined
readonly limit?: number | undefined
}["location"]
readonly cursor?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly cursor?: number | undefined
readonly limit?: number | undefined
}["cursor"]
readonly limit?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly cursor?: number | undefined
readonly limit?: number | undefined
}["limit"]
}
export type ShellOutputOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: {
readonly output: string
readonly cursor: number
readonly size: number
readonly truncated: boolean
}
}
export type ShellRemoveInput = {
readonly id: { readonly id: string }["id"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ShellRemoveOutput = void
export type QuestionListRequestsInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type QuestionListRequestsOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly questions: ReadonlyArray<{
readonly question: string
readonly header: string
readonly options: ReadonlyArray<{ readonly label: string; readonly description: string }>
readonly multiple?: boolean
readonly custom?: boolean
}>
readonly tool?: { readonly messageID: string; readonly callID: string }
}>
}
export type QuestionListInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
export type QuestionListOutput = {
readonly data: ReadonlyArray<{
readonly id: string
readonly sessionID: string
readonly questions: ReadonlyArray<{
readonly question: string
readonly header: string
readonly options: ReadonlyArray<{ readonly label: string; readonly description: string }>
readonly multiple?: boolean
readonly custom?: boolean
}>
readonly tool?: { readonly messageID: string; readonly callID: string }
}>
}["data"]
export type QuestionReplyInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
readonly answers: { readonly answers: ReadonlyArray<ReadonlyArray<string>> }["answers"]
}
export type QuestionReplyOutput = void
export type QuestionRejectInput = {
readonly sessionID: { readonly sessionID: string; readonly requestID: string }["sessionID"]
readonly requestID: { readonly sessionID: string; readonly requestID: string }["requestID"]
}
export type QuestionRejectOutput = void
export type ReferenceListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ReferenceListOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly name: string
readonly path: string
readonly description?: string
readonly hidden?: boolean
readonly source:
| { readonly type: "local"; readonly path: string; readonly description?: string; readonly hidden?: boolean }
| {
readonly type: "git"
readonly repository: string
readonly branch?: string
readonly description?: string
readonly hidden?: boolean
}
}>
}
export type ProjectCopyCreateInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly strategy: { readonly strategy: string; readonly directory: string; readonly name?: string }["strategy"]
readonly directory: { readonly strategy: string; readonly directory: string; readonly name?: string }["directory"]
readonly name?: { readonly strategy: string; readonly directory: string; readonly name?: string }["name"]
}
export type ProjectCopyCreateOutput = { readonly directory: string }
export type ProjectCopyRemoveInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
readonly directory: { readonly directory: string; readonly force: boolean }["directory"]
readonly force: { readonly directory: string; readonly force: boolean }["force"]
}
export type ProjectCopyRemoveOutput = void
export type ProjectCopyRefreshInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type ProjectCopyRefreshOutput = void
export type VcsStatusInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}["location"]
}
export type VcsStatusOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly file: string
readonly additions: number
readonly deletions: number
readonly status: "added" | "deleted" | "modified"
}>
}
export type VcsDiffInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly mode: "working" | "branch"
readonly context?: number | undefined
}["location"]
readonly mode: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly mode: "working" | "branch"
readonly context?: number | undefined
}["mode"]
readonly context?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
readonly mode: "working" | "branch"
readonly context?: number | undefined
}["context"]
}
export type VcsDiffOutput = {
readonly location: {
readonly directory: string
readonly workspaceID?: string
readonly project: { readonly id: string; readonly directory: string }
}
readonly data: ReadonlyArray<{
readonly file?: string
readonly patch?: string
readonly additions: number
readonly deletions: number
readonly status?: "added" | "deleted" | "modified"
}>
}
-16
View File
@@ -1,16 +0,0 @@
export * from "./generated/index"
export type {
AgentApi,
CatalogApi,
CommandApi,
EventApi,
IntegrationApi,
ModelApi,
PluginApi,
ProviderApi,
ReferenceApi,
SessionApi,
SkillApi,
} from "./api.js"
export type { EventSubscribeOutput as OpenCodeEvent } from "./generated/types"
export type OpenCodeClient = ReturnType<typeof import("./generated/client").make>
-10
View File
@@ -1,10 +0,0 @@
import { Effect } from "effect"
import { OpenCode as EffectOpenCode, type AppApi as EffectApi } from "../src/effect"
type EffectClient = Effect.Success<ReturnType<typeof EffectOpenCode.make>>
declare const effectClient: EffectClient
const effectApi: EffectApi<unknown> = effectClient
void effectApi
@@ -3,7 +3,6 @@ import { Schema } from "effect"
import { AgentV2 } from "@opencode-ai/core/agent"
import { Location as CoreLocation } from "@opencode-ai/core/location"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProjectV2 } from "@opencode-ai/core/project"
import { SessionV2 } from "@opencode-ai/core/session"
import { SessionInput as CoreSessionInput } from "@opencode-ai/core/session/input"
import { SessionMessage as CoreSessionMessage } from "@opencode-ai/core/session/message"
@@ -20,21 +19,17 @@ import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Workspace } from "@opencode-ai/schema/workspace"
import { Api } from "@opencode-ai/server/api"
import { compile, emitPromise } from "@opencode-ai/httpapi-codegen"
import { ClientApi, endpointNames, groupNames, promiseOmitEndpoints } from "../src/contract"
import { ClientApi, endpointNames, groupNames, omitEndpoints } from "../src/contract"
test("Core and Server reuse the authoritative Schema and Protocol values", () => {
expect(AgentV2.ID).toBe(Agent.ID)
expect(CoreLocation.Ref).toBe(Location.Ref)
expect(ModelV2.Ref).toBe(Model.Ref)
expect(SessionV2.Info).toBe(Session.Info)
expect(ProjectV2.Current).toBe(Project.Current)
expect(ProjectV2.Directory).toBe(Project.Directory)
expect(ProjectV2.Directories).toBe(Project.Directories)
expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted)
expect(CoreSessionMessage.Message).toBe(SessionMessage.Message)
expect(CorePrompt).toBe(Prompt)
expect(Api.groups["server.session"].identifier).toBe("server.session")
expect(Api.groups["server.project"].identifier).toBe("server.project")
expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups))
expect(Session.ID.create()).toStartWith("ses_")
expect(Project.ID.global).toBe("global")
@@ -43,8 +38,8 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
})
test("client and Server contracts generate identically", () => {
const server = compile(Api, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
const client = compile(ClientApi, { groupNames, endpointNames, omitEndpoints: promiseOmitEndpoints })
const server = compile(Api, { groupNames, endpointNames, omitEndpoints })
const client = compile(ClientApi, { groupNames, endpointNames, omitEndpoints })
expect(emitPromise(client)).toEqual(emitPromise(server))
})
+71 -62
View File
@@ -1,39 +1,27 @@
import { expect, test } from "bun:test"
import { DateTime, Effect, Stream } from "effect"
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
import {
AbsolutePath,
Agent,
Event,
Location,
Model,
OpenCode,
Prompt,
Session,
SessionMessage,
} from "../src/effect/index"
import { AbsolutePath, Agent, Location, Model, OpenCode, Prompt, Session, SessionMessage } from "../src/effect"
const synced = { type: "log.synced" as const, aggregateID: "ses_test", seq: Event.Seq.make(1) }
test("session.get returns the decoded Effect projection", async () => {
test("sessions.get returns the decoded Effect projection", async () => {
const httpClient = HttpClient.make((request) =>
Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(session))),
)
const result = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
return yield* client.session.get({ sessionID: Session.ID.make("ses_test") })
return yield* client.sessions.get({ sessionID: Session.ID.make("ses_test") })
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(DateTime.toEpochMillis(result.time.created)).toBe(1_717_171_717_000)
})
test("event.subscribe exposes and decodes the native Effect event stream", async () => {
test("events.subscribe exposes and decodes the native Effect event stream", async () => {
const httpClient = HttpClient.make((request) =>
Effect.succeed(
HttpClientResponse.fromWeb(
request,
new Response(
`data: ${JSON.stringify({ id: "evt_connected", created: 0, type: "server.connected", data: {} })}\n\n` +
`data: ${JSON.stringify({ id: "evt_connected", type: "server.connected", data: {} })}\n\n` +
`data: ${JSON.stringify(modelSwitchedEvent)}\n\n`,
{ headers: { "content-type": "text/event-stream" } },
),
@@ -42,17 +30,17 @@ test("event.subscribe exposes and decodes the native Effect event stream", async
)
const events = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
return yield* client.event.subscribe().pipe(Stream.runCollect)
return yield* client.events.subscribe().pipe(Stream.runCollect)
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(Array.from(events).map((event) => event.type)).toEqual(["server.connected", "session.model.selected"])
expect(Array.from(events).map((event) => event.type)).toEqual(["server.connected", "session.next.model.switched"])
const durable = events[1]
if (durable?.type !== "session.model.selected") throw new Error("Expected model event")
expect(DateTime.toEpochMillis(durable.created)).toBe(1_717_171_717_000)
if (durable?.type !== "session.next.model.switched") throw new Error("Expected model event")
expect(DateTime.toEpochMillis(durable.data.timestamp)).toBe(1_717_171_717_000)
expect(durable.durable).toEqual({ aggregateID: "ses_test", seq: 1, version: 1 })
})
test("event.subscribe terminates on Effect protocol decode failures", async () => {
test("events.subscribe terminates on Effect protocol decode failures", async () => {
const httpClient = HttpClient.make((request) =>
Effect.succeed(
HttpClientResponse.fromWeb(
@@ -65,27 +53,39 @@ test("event.subscribe terminates on Effect protocol decode failures", async () =
)
const error = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
return yield* client.event.subscribe().pipe(Stream.runCollect, Effect.flip)
return yield* client.events.subscribe().pipe(Stream.runCollect, Effect.flip)
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(error._tag).toBe("ClientError")
})
test("session methods retain decoded Effect inputs and outputs", async () => {
const logQueries: Array<Record<string, string>> = []
const historyQueries: Array<Record<string, string>> = []
let historyPage = 0
const httpClient = HttpClient.make((request) => {
const url = request.url
if (url.includes("/log")) {
logQueries.push(Object.fromEntries(request.urlParams.params))
if (url.includes("/event")) {
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\ndata: ${JSON.stringify(synced)}\n\n`, {
new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\n`, {
headers: { "content-type": "text/event-stream" },
}),
),
)
}
if (url.includes("/history")) {
historyPage++
historyQueries.push(Object.fromEntries(request.urlParams.params))
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
Response.json(
historyPage === 1 ? { data: [modelSwitchedEvent], hasMore: true } : { data: [], hasMore: false },
),
),
)
}
if (url.includes("/prompt")) {
return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(admission)))
}
@@ -97,10 +97,7 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
}
if (url.endsWith("/api/session/active")) {
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
Response.json({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } }),
),
HttpClientResponse.fromWeb(request, Response.json({ data: { ses_test: { type: "running" } } })),
)
}
if (request.method === "POST" && url.endsWith("/api/session")) {
@@ -110,46 +107,54 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 204 })))
}
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
Response.json({ data: [session.data], watermarks: { ses_test: 3 }, cursor: { next: "next" } }),
),
HttpClientResponse.fromWeb(request, Response.json({ data: [session.data], cursor: { next: "next" } })),
)
})
const result = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
const page = yield* client.session.list({ limit: 10 })
const active = yield* client.session.active()
const created = yield* client.session.create({
const page = yield* client.sessions.list({ limit: 10 })
const active = yield* client.sessions.active()
const created = yield* client.sessions.create({
location: Location.Ref.make({ directory: AbsolutePath.make("/tmp/project") }),
})
yield* client.session.switchAgent({ sessionID: Session.ID.make("ses_test"), agent: Agent.ID.make("build") })
yield* client.session.switchModel({
yield* client.sessions.switchAgent({ sessionID: Session.ID.make("ses_test"), agent: Agent.ID.make("build") })
yield* client.sessions.switchModel({
sessionID: Session.ID.make("ses_test"),
model: Model.Ref.make({ id: "claude", providerID: "anthropic" }),
})
const admitted = yield* client.session.prompt({
const admitted = yield* client.sessions.prompt({
sessionID: Session.ID.make("ses_test"),
prompt: Prompt.make({ text: "Hello" }),
resume: false,
})
yield* client.session.compact({ sessionID: Session.ID.make("ses_test") })
yield* client.session.wait({ sessionID: Session.ID.make("ses_test") })
const context = yield* client.session.context({ sessionID: Session.ID.make("ses_test") })
const log = yield* client.session
.log({ sessionID: Session.ID.make("ses_test"), after: Event.Seq.make(0) })
yield* client.sessions.compact({ sessionID: Session.ID.make("ses_test") })
yield* client.sessions.wait({ sessionID: Session.ID.make("ses_test") })
const context = yield* client.sessions.context({ sessionID: Session.ID.make("ses_test") })
const history = yield* client.sessions.history({
sessionID: Session.ID.make("ses_test"),
after: 0,
limit: 1,
})
const historyNext = history.hasMore
? yield* client.sessions.history({
sessionID: Session.ID.make("ses_test"),
after: history.data.at(-1)?.durable?.seq,
limit: 2,
})
: undefined
const events = yield* client.sessions
.events({ sessionID: Session.ID.make("ses_test"), after: 0 })
.pipe(Stream.runCollect)
yield* client.session.interrupt({ sessionID: Session.ID.make("ses_test") })
const message = yield* client.session.message({
yield* client.sessions.interrupt({ sessionID: Session.ID.make("ses_test") })
const message = yield* client.sessions.message({
sessionID: Session.ID.make("ses_test"),
messageID: SessionMessage.ID.make("msg_model"),
})
return { page, active, created, admitted, context, log, message }
return { page, active, created, admitted, context, history, historyNext, events, message }
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(DateTime.toEpochMillis(result.page.data[0].time.created)).toBe(1_717_171_717_000)
expect(result.active).toEqual({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } })
expect(result.page.watermarks).toEqual({ ses_test: 3 })
expect(result.active).toEqual({ ses_test: { type: "running" } })
expect(Object.getPrototypeOf(result.page.data[0])).toBe(Object.prototype)
expect(Object.getPrototypeOf(result.created)).toBe(Object.prototype)
expect(result.created.id).toBe("ses_test")
@@ -157,17 +162,16 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
expect(Object.getPrototypeOf(result.admitted.prompt)).toBe(Object.prototype)
expect(DateTime.toEpochMillis(result.admitted.timeCreated)).toBe(1_717_171_717_000)
expect(result.context).toEqual([])
expect(logQueries[0]).toEqual({ after: "0" })
const logged = Array.from(result.log)
expect(logged.map((item) => item.type)).toEqual(["session.model.selected", "log.synced"])
expect(logged[0]?.type === "session.model.selected" && DateTime.toEpochMillis(logged[0].created)).toBe(
1_717_171_717_000,
)
expect(logged.at(-1)).toEqual(synced)
expect(DateTime.toEpochMillis(result.history.data[0].data.timestamp)).toBe(1_717_171_717_000)
expect(result.history).toEqual(expect.objectContaining({ hasMore: true }))
expect(result.historyNext).toEqual({ data: [], hasMore: false })
expect(historyQueries[0]).toEqual({ limit: "1", after: "0" })
expect(historyQueries[1]).toEqual({ limit: "2", after: "1" })
expect(DateTime.toEpochMillis(result.events[0].data.timestamp)).toBe(1_717_171_717_000)
expect(result.message).toEqual(expect.objectContaining({ id: "msg_model", type: "model-switched" }))
})
test("session.log retains the typed SessionNotFoundError", async () => {
test("sessions.history retains the typed SessionNotFoundError", async () => {
const httpClient = HttpClient.make((request) =>
Effect.succeed(
HttpClientResponse.fromWeb(
@@ -181,7 +185,11 @@ test("session.log retains the typed SessionNotFoundError", async () => {
)
const error = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
return yield* client.session.log({ sessionID: Session.ID.make("ses_missing") }).pipe(Stream.runCollect, Effect.flip)
return yield* client.sessions
.history({
sessionID: Session.ID.make("ses_missing"),
})
.pipe(Effect.flip)
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(error._tag).toBe("SessionNotFoundError")
@@ -227,11 +235,12 @@ const modelSwitchedMessage = {
const modelSwitchedEvent = {
id: "evt_model",
created: 1_717_171_717_000,
type: "session.model.selected",
type: "session.next.model.switched",
durable: { aggregateID: "ses_test", seq: 1, version: 1 },
data: {
timestamp: 1_717_171_717_000,
sessionID: "ses_test",
messageID: "msg_model",
model: { id: "claude", providerID: "anthropic" },
},
}
@@ -12,7 +12,7 @@ const server = resolve(import.meta.dir, "../../server")
describe("public import boundaries", () => {
test("isolates each public entrypoint", async () => {
const root = await bundleInputs("@opencode-ai/client/promise", "browser")
const root = await bundleInputs("@opencode-ai/client", "browser")
expect(within(root, effect)).toEqual([])
expect(within(root, schema)).toEqual([])
@@ -20,9 +20,7 @@ describe("public import boundaries", () => {
expect(within(root, core)).toEqual([])
expect(within(root, server)).toEqual([])
// The effect entry includes local service lifecycle (node spawn/fs), so it
// bundles for bun; the boundary assertions below are what matter.
const network = await bundleInputs("@opencode-ai/client/effect", "bun")
const network = await bundleInputs("@opencode-ai/client/effect", "browser")
expect(within(network, effect).length).toBeGreaterThan(0)
expect(within(network, schema).length).toBeGreaterThan(0)
+69 -152
View File
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"
import { isSessionNotFoundError, isUnauthorizedError, OpenCode } from "../src/promise/index"
import { isSessionNotFoundError, isUnauthorizedError, OpenCode } from "../src"
test("exposes every standard HTTP API group", () => {
const client = OpenCode.make({ baseUrl: "http://localhost:3000" })
@@ -7,32 +7,25 @@ test("exposes every standard HTTP API group", () => {
expect(Object.keys(client)).toEqual([
"health",
"location",
"agent",
"plugin",
"session",
"message",
"model",
"generate",
"provider",
"integration",
"server.mcp",
"credential",
"project",
"form",
"permission",
"file",
"command",
"skill",
"event",
"pty",
"shell",
"question",
"reference",
"projectCopy",
"vcs",
"agents",
"sessions",
"messages",
"models",
"providers",
"integrations",
"credentials",
"permissions",
"files",
"commands",
"skills",
"events",
"ptys",
"questions",
"references",
"projectCopies",
])
expect(Object.keys(client.message)).toEqual(["list"])
expect(Object.keys(client.integration)).toEqual([
expect(Object.keys(client.messages)).toEqual(["list"])
expect(Object.keys(client.integrations)).toEqual([
"list",
"get",
"connectKey",
@@ -41,96 +34,11 @@ test("exposes every standard HTTP API group", () => {
"attemptComplete",
"attemptCancel",
])
expect(Object.keys(client.file)).toEqual(["read", "list", "find"])
expect(Object.keys(client.vcs)).toEqual(["status", "diff"])
expect(Object.keys(client.pty)).toEqual(["list", "create", "get", "update", "remove"])
expect(Object.keys(client.shell)).toEqual(["list", "create", "get", "output", "remove"])
expect(Object.keys(client.project)).toEqual(["current", "directories"])
expect(Object.keys(client.files)).toEqual(["list", "find"])
expect(Object.keys(client.ptys)).toEqual(["list", "create", "get", "update", "remove"])
})
test("file.read returns binary content from the public HTTP contract", async () => {
let request: Request | undefined
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input) => {
request = input instanceof Request ? input : new Request(input)
return new Response(new Uint8Array([104, 105]))
},
})
const content = await client.file.read({
path: "src/a b#c.ts",
location: { directory: "/tmp/project" },
})
expect(Array.from(content)).toEqual([104, 105])
expect(request?.url).toBe(
"http://localhost:3000/api/fs/read/src/a%20b%23c.ts?location%5Bdirectory%5D=%2Ftmp%2Fproject",
)
})
test("project methods use the public HTTP contract", async () => {
const requests: string[] = []
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
requests.push(url)
if (url.includes("/directories")) return Response.json([])
return Response.json({ id: "proj_test", directory: "/tmp/project" })
},
})
const current = await client.project.current({ location: { workspace: "wrk_test" } })
const directories = await client.project.directories({
projectID: current.id,
location: { directory: current.directory },
})
expect(current).toEqual({ id: "proj_test", directory: "/tmp/project" })
expect(directories).toEqual([])
expect(requests).toEqual([
"http://localhost:3000/api/project/current?location%5Bworkspace%5D=wrk_test",
"http://localhost:3000/api/project/proj_test/directories?location%5Bdirectory%5D=%2Ftmp%2Fproject",
])
})
test("shell list and remove use the public HTTP contract", async () => {
const requests: Array<{ method: string; url: string }> = []
const shell = {
id: "sh_test",
status: "running",
command: "pwd",
cwd: "/tmp/project",
shell: "/bin/zsh",
file: "/tmp/opencode-shell",
metadata: { sessionID: "ses_test" },
time: { started: 1_717_171_717_000 },
}
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input, init) => {
const request = input instanceof Request ? input : new Request(input, init)
requests.push({ method: request.method, url: request.url })
if (request.method === "DELETE") return new Response(null, { status: 204 })
return Response.json({
location: { directory: "/tmp/project", project: { id: "proj_test", directory: "/tmp/project" } },
data: [shell],
})
},
})
const result = await client.shell.list({ location: { directory: "/tmp/project" } })
await client.shell.remove({ id: shell.id })
expect(result.data).toEqual([shell])
expect(requests).toEqual([
{ method: "GET", url: "http://localhost:3000/api/shell?location%5Bdirectory%5D=%2Ftmp%2Fproject" },
{ method: "DELETE", url: "http://localhost:3000/api/shell/sh_test" },
])
})
test("session.get returns the wire projection", async () => {
test("sessions.get returns the wire projection", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input) => {
@@ -141,35 +49,35 @@ test("session.get returns the wire projection", async () => {
},
})
const result = await client.session.get({ sessionID: "ses_test" })
const result = await client.sessions.get({ sessionID: "ses_test" })
expect(result.time.created).toBe(1_717_171_717_000)
})
test("event.subscribe exposes the Promise event stream wire projection", async () => {
test("events.subscribe exposes the Promise event stream wire projection", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () =>
new Response(
`: heartbeat\n\ndata: ${JSON.stringify({ id: "evt_connected", created: 0, type: "server.connected", data: {} })}\n\n` +
`: heartbeat\n\ndata: ${JSON.stringify({ id: "evt_connected", type: "server.connected", data: {} })}\n\n` +
`data: ${JSON.stringify(modelSwitchedEvent)}\n\n`,
{ headers: { "content-type": "text/event-stream" } },
),
})
const events = []
for await (const event of client.event.subscribe()) events.push(event)
for await (const event of client.events.subscribe()) events.push(event)
expect(events).toEqual([{ id: "evt_connected", created: 0, type: "server.connected", data: {} }, modelSwitchedEvent])
expect(events[1]?.type === "session.model.selected" && events[1].created).toBe(1_717_171_717_000)
expect(events).toEqual([{ id: "evt_connected", type: "server.connected", data: {} }, modelSwitchedEvent])
expect(events[1]?.type === "session.next.model.switched" && events[1].data.timestamp).toBe(1_717_171_717_000)
})
test("event.subscribe terminates on malformed Promise SSE data", async () => {
test("events.subscribe terminates on malformed Promise SSE data", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () => new Response("data: {not-json}\n\n", { headers: { "content-type": "text/event-stream" } }),
})
await expect(client.event.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
await expect(client.events.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
name: "ClientError",
reason: "MalformedResponse",
})
@@ -177,6 +85,7 @@ test("event.subscribe terminates on malformed Promise SSE data", async () => {
test("session methods use the public HTTP contract", async () => {
const requests: Array<{ url: string; init?: RequestInit }> = []
let historyPage = 0
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input, init) => {
@@ -187,52 +96,59 @@ test("session methods use the public HTTP contract", async () => {
headers: { "content-type": "text/event-stream" },
})
}
if (url.includes("/log")) {
return new Response(`data: ${JSON.stringify(modelSwitchedEvent)}\n\ndata: ${JSON.stringify(synced)}\n\n`, {
headers: { "content-type": "text/event-stream" },
})
if (url.includes("/history")) {
historyPage++
return Response.json(
historyPage === 1 ? { data: [modelSwitchedEvent], hasMore: true } : { data: [], hasMore: false },
)
}
if (url.includes("/prompt")) return Response.json(admission)
if (url.includes("/context")) return Response.json({ data: [] })
if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage })
if (url.endsWith("/api/session/active"))
return Response.json({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } })
if (url.endsWith("/api/session/active")) return Response.json({ data: { ses_test: { type: "running" } } })
if (init?.method === "POST" && url.endsWith("/api/session")) return Response.json(session)
if (init?.method === "POST") return new Response(null, { status: 204 })
return Response.json({ data: [session.data], cursor: { next: "next" } })
},
})
const page = await client.session.list({ limit: 10, order: "desc", parentID: null })
const active = await client.session.active()
const created = await client.session.create({ location: { directory: "/tmp/project" } })
await client.session.switchAgent({ sessionID: "ses_test", agent: "build" })
await client.session.switchModel({
const page = await client.sessions.list({ limit: 10, order: "desc" })
const active = await client.sessions.active()
const created = await client.sessions.create({ location: { directory: "/tmp/project" } })
await client.sessions.switchAgent({ sessionID: "ses_test", agent: "build" })
await client.sessions.switchModel({
sessionID: "ses_test",
model: { id: "claude", providerID: "anthropic" },
})
const admitted = await client.session.prompt({
const admitted = await client.sessions.prompt({
sessionID: "ses_test",
prompt: { text: "Hello" },
resume: false,
})
await client.session.compact({ sessionID: "ses_test" })
await client.session.wait({ sessionID: "ses_test" })
const context = await client.session.context({ sessionID: "ses_test" })
const log = []
for await (const item of client.session.log({ sessionID: "ses_test", after: 0 })) log.push(item)
await client.session.interrupt({ sessionID: "ses_test" })
const message = await client.session.message({ sessionID: "ses_test", messageID: "msg_model" })
await client.sessions.compact({ sessionID: "ses_test" })
await client.sessions.wait({ sessionID: "ses_test" })
const context = await client.sessions.context({ sessionID: "ses_test" })
const history = await client.sessions.history({ sessionID: "ses_test", after: 0, limit: 1 })
const historyAfter = history.data.at(-1)?.durable?.seq
const historyNext = history.hasMore
? await client.sessions.history({ sessionID: "ses_test", after: historyAfter, limit: 2 })
: undefined
const events = []
for await (const event of client.sessions.events({ sessionID: "ses_test", after: 0 })) events.push(event)
await client.sessions.interrupt({ sessionID: "ses_test" })
const message = await client.sessions.message({ sessionID: "ses_test", messageID: "msg_model" })
expect(page.cursor.next).toBe("next")
expect(active).toEqual({ data: { ses_test: { type: "running" } }, watermarks: { ses_test: 3 } })
expect(active).toEqual({ ses_test: { type: "running" } })
expect(created.id).toBe("ses_test")
expect(admitted.id).toBe("msg_test")
expect(context).toEqual([])
expect(log).toEqual([modelSwitchedEvent, synced])
expect(history).toEqual({ data: [modelSwitchedEvent], hasMore: true })
expect(historyNext).toEqual({ data: [], hasMore: false })
expect(events).toEqual([modelSwitchedEvent])
expect(message).toEqual(modelSwitchedMessage)
expect(requests.map((request) => [request.init?.method, request.url])).toEqual([
["GET", "http://localhost:3000/api/session?limit=10&order=desc&parentID=null"],
["GET", "http://localhost:3000/api/session?limit=10&order=desc"],
["GET", "http://localhost:3000/api/session/active"],
["POST", "http://localhost:3000/api/session"],
["POST", "http://localhost:3000/api/session/ses_test/agent"],
@@ -241,7 +157,9 @@ test("session methods use the public HTTP contract", async () => {
["POST", "http://localhost:3000/api/session/ses_test/compact"],
["POST", "http://localhost:3000/api/session/ses_test/wait"],
["GET", "http://localhost:3000/api/session/ses_test/context"],
["GET", "http://localhost:3000/api/session/ses_test/log?after=0"],
["GET", "http://localhost:3000/api/session/ses_test/history?limit=1&after=0"],
["GET", "http://localhost:3000/api/session/ses_test/history?limit=2&after=1"],
["GET", "http://localhost:3000/api/session/ses_test/event?after=0"],
["POST", "http://localhost:3000/api/session/ses_test/interrupt"],
["GET", "http://localhost:3000/api/session/ses_test/message/msg_model"],
])
@@ -261,14 +179,14 @@ test("middleware errors remain declared client errors", async () => {
})
try {
await client.session.create({})
await client.sessions.create({})
throw new Error("Expected request to fail")
} catch (error) {
expect(isUnauthorizedError(error)).toBe(true)
}
})
test("session.log decodes SessionNotFoundError", async () => {
test("sessions.history decodes SessionNotFoundError", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () =>
@@ -279,7 +197,7 @@ test("session.log decodes SessionNotFoundError", async () => {
})
try {
await client.session.log({ sessionID: "ses_missing" })[Symbol.asyncIterator]().next()
await client.sessions.history({ sessionID: "ses_missing" })
throw new Error("Expected request to fail")
} catch (error) {
expect(isSessionNotFoundError(error)).toBe(true)
@@ -324,15 +242,14 @@ const modelSwitchedMessage = {
model: { id: "claude", providerID: "anthropic" },
}
const synced = { type: "log.synced", aggregateID: "ses_test", seq: 1 }
const modelSwitchedEvent = {
id: "evt_model",
created: 1_717_171_717_000,
type: "session.model.selected",
type: "session.next.model.switched",
durable: { aggregateID: "ses_test", seq: 1, version: 1 },
data: {
timestamp: 1_717_171_717_000,
sessionID: "ses_test",
messageID: "msg_model",
model: { id: "claude", providerID: "anthropic" },
},
}
-11
View File
@@ -1,11 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"noEmit": false,
"declaration": true
},
"include": ["src"]
}
-2
View File
@@ -3,8 +3,6 @@
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"allowImportingTsExtensions": false,
"allowJs": false,
"noUncheckedIndexedAccess": false
},
"include": ["src"]
-15
View File
@@ -1,15 +0,0 @@
# @opencode-ai/codemode
- This local package owns confined execution over explicit schema-described tools. Applications own authorization, persistence, external authority, and tool-specific delivery semantics.
- Do not add a speculative generic permission or approval policy. A host omits tools it does not expose and enforces domain authorization inside each provided tool.
- Keep Code Mode unaware of host session, channel, and conversation models. The hosting application supplies trusted execution scope around it.
- Tool schemas are the model-facing Interface. Keep arguments minimal and natural to the operation; never add unrelated IDs as ambient capability tokens.
## Future Design Notes
- If a captured user-visible output channel returns (an earlier `output.text`/`output.file`/`output.image` API was removed from v1), keep `output` as its name, distinct from the program return value: `return` stays the structured result for the model, while `output.*` describes artifacts the host may render into a conversation or UI after execution. Keep this host-neutral and let applications decide how captured output is delivered. In v1, hosts collect media host-side (outside the sandbox) instead.
- Improve the sandbox failure taxonomy. Distinguish parse/compile mistakes, unsupported syntax, user-thrown errors, invalid returned data, tool refusal, tool internal failure, timeout, and genuine runtime defects so agents can recover accurately instead of treating everything as a generic execution failure.
- Preserve the public/private error split. Tool authors should be able to return a safe model-visible message while retaining a private cause for host diagnostics. Unknown host failures must remain sanitized by default.
- Think deliberately about richer binary boundaries before allowing `Blob`, `File`, `ArrayBuffer`, streams, or typed arrays beyond today's JSON-like values. If CodeMode supports binary tool args/results, use explicit tagged data shapes and clear size limits rather than relying on ambient runtime serialization.
- Keep host capabilities explicit. Globals such as `fetch`, `crypto`, filesystem handles, extra modules, or network clients should be opt-in runtime capabilities with obvious policy defaults, not ambient authority. Default to unavailable unless a host deliberately provides the capability.
- If `fetch` is added, model it as a host-provided outbound capability with policy controls: allowed origins, methods, headers, response size, timeout, and whether response bodies may be returned, emitted, or only summarized through a tool.
-338
View File
@@ -1,338 +0,0 @@
# @opencode-ai/codemode
Effect-native confined code execution over explicit, schema-described tools.
CodeMode lets a model write a small JavaScript program that can call only the tools supplied by the host. The program can sequence calls, transform plain data, branch, loop, and run independent calls in parallel without receiving ambient filesystem, process, network, module, or application authority.
The package is currently private to this workspace. Its API is designed around three uses:
```ts
// One execution
yield * CodeMode.execute({ tools, code })
// A reusable runtime
const runtime = CodeMode.make({ tools, limits })
yield * runtime.execute(code)
// One agent-facing code tool
const codeTool = runtime.agentTool()
```
## Install
Within this workspace:
```json
{
"dependencies": {
"@opencode-ai/codemode": "workspace:*"
}
}
```
Hosts interact with CodeMode through `effect` (tool `run` implementations, `Effect`-typed results), so they should depend on `effect` themselves.
## Quick Start
Define tools with Effect Schema, then place them in the object tree exposed to programs as `tools`:
```ts
import { CodeMode, Tool } from "@opencode-ai/codemode"
import { Effect, Schema } from "effect"
const lookupOrder = Tool.make({
description: "Look up an order by ID",
input: Schema.Struct({ id: Schema.String }),
output: Schema.Struct({ id: Schema.String, status: Schema.String }),
run: ({ id }) => Effect.succeed({ id, status: "open" }),
})
const runtime = CodeMode.make({
tools: {
orders: {
lookup: lookupOrder,
},
},
})
const result =
yield *
runtime.execute(`
const order = await tools.orders.lookup({ id: "order_42" })
return { id: order.id, needsAttention: order.status !== "complete" }
`)
```
`result` is always an `ExecuteResult`. Program, validation, limit, and tool failures are returned as diagnostics rather than failing the Effect. Host interruption remains interruption.
Successful result values are JSON-safe data. A program that returns `undefined`, including by reaching the end without `return`, produces `null`; nested `undefined` values are normalized to `null` as well.
## API
### `Tool.make`
```ts
const tool = Tool.make({
description,
input, // Effect Schema (validating) or JSON Schema (render-only)
output, // optional; same choice
run,
})
```
`input` and `output` each accept a validating Effect Schema or a render-only JSON Schema document (the natural shape for adapter-provided tools whose schemas arrive as JSON Schema, e.g. MCP definitions). Effect Schema input is decoded before `run` is invoked, and `run` returns the encoded representation of an Effect Schema `output`, which CodeMode decodes and copies before exposing it to the program. JSON Schemas only shape the model-visible signature; values pass through unvalidated (they still cross the plain-data boundary).
`output` is optional. Without it the tool's signature advertises `Promise<unknown>` and the host result is exposed as-is.
The description and schemas are part of the model-visible tool contract. Keep descriptions concrete and put authorization in `run` or in the service it calls.
### `CodeMode.execute`
Use `CodeMode.execute` for a single execution:
```ts
const result =
yield *
CodeMode.execute({
tools: { orders: { lookup: lookupOrder } },
code: `return await tools.orders.lookup({ id: "order_42" })`,
limits: { maxToolCalls: 10 },
onToolCallStart: (call) => Effect.logDebug("CodeMode tool started", call),
onToolCallEnd: (call) => Effect.logDebug("CodeMode tool settled", call),
})
```
The Effect environment is inferred from the supplied tools. CodeMode does not erase service requirements introduced by tool implementations.
### `CodeMode.make`
Use `CodeMode.make` when the tool set and execution policy are reused:
```ts
const runtime = CodeMode.make({
tools: { orders: { lookup: lookupOrder } },
limits: { timeoutMs: 30_000 },
})
runtime.catalog() // structured tool descriptions
runtime.instructions() // model-facing syntax and tool guide
runtime.execute(source) // ExecuteResult
runtime.agentTool() // { name, description, input, output, execute }
```
`catalog`, `instructions`, and `agentTool` are projections of the same configured tool tree. `agentTool().description` is exactly `instructions()`.
### Results
```ts
type ExecuteResult = ExecuteSuccess | ExecuteFailure
interface ExecuteSuccess {
readonly ok: true
readonly value: Schema.Json
readonly logs?: ReadonlyArray<string>
readonly truncated?: boolean
readonly toolCalls: ReadonlyArray<ToolCall>
}
interface ExecuteFailure {
readonly ok: false
readonly error: Diagnostic
readonly logs?: ReadonlyArray<string>
readonly truncated?: boolean
readonly toolCalls: ReadonlyArray<ToolCall>
}
```
`toolCalls` contains the names of calls admitted by the runtime in call order. It is retained on failure so hosts can audit partial execution without exposing inputs or host failures. `truncated` is present when the value or logs were cut to fit `maxOutputBytes` (see Execution Limits).
### Tool-call hooks
`onToolCallStart` receives `{ index, name, input }` after input decoding and before tool execution. The input is decoded host-side data and may include values produced by schema transformations; applications should avoid logging sensitive tool arguments indiscriminately.
`onToolCallEnd` receives `{ index, name, input, durationMs, outcome, message? }` when an admitted call settles. `outcome` is `"success"` or `"failure"`; `message` is the model-safe failure message and is present only on failure. Interrupted calls (for example when the execution timeout fires) do not produce an end event. Both hooks are Effect-returning and must not fail.
## Discovery
The agent-tool instructions use a budgeted catalog. Every tool namespace is always listed with its tool count regardless of budget, and as many complete tool signatures (each with a one-line description) as fit an estimated-token budget are inlined. Selection is round-robin across namespaces for fairness: in each round (namespaces alphabetical), every namespace still holding un-inlined tools attempts to place its next-cheapest signature line against the shared budget, and a namespace whose next line does not fit drops out while the others keep going - so every namespace gets some representation before any namespace gets everything. The instructions state exactly how comprehensive the list is, both overall (`COMPLETE list` vs `PARTIAL - N of M shown`) and per namespace (`(3 tools)`, `(3 tools, 1 shown)`, `(3 tools, none shown)`).
The default budget is 2,000 estimated tokens (characters / 4, the same heuristic OpenCode uses). Override it when constructing a runtime:
```ts
const runtime = CodeMode.make({
tools,
discovery: { maxInlineCatalogTokens: 6_000 },
})
```
The budget must be a non-negative safe integer.
The runtime search tool is always registered - including when the catalog is fully inlined - so a speculative `tools.$codemode.search` call never fails as an unknown tool. It is only advertised in the instructions when the inlined list is partial:
```ts
const matches = await tools.$codemode.search({
query: "order status",
namespace: "orders", // optional: scope to one top-level namespace
limit: 10,
})
```
`search` performs deterministic, additive field-weighted matching. The query is tokenized (camelCase boundaries split; every non-alphanumeric character is a separator; empties and `*` are dropped), and each term scores every tool: exact path or path-segment match (20), path substring (8), description substring (4), and searchable-text substring (2). Each term also carries naive singular variants (trailing `s`/`es` stripped), and a field check passes when the term or any variant matches - so a plural query term (`issues`) still finds a tool whose text only says `issue`, without changing the weights. The searchable text also includes the input schema's property names and their description strings, so a query naming a parameter finds its tool, and substring matching means partial words match. Scores sum across terms; matches are sorted by score (ties broken alphabetically by path) and capped at `limit` results (default 10).
Each result contains the path, description, and generated TypeScript signature, so no second lookup is needed. The result signature is the pretty, JSDoc-annotated multiline form: each described input/output field carries its schema `description` as a `/** ... */` comment, and constraints TypeScript cannot express ride along as tags (`@deprecated`, `@default`, `@format`, `@minItems`, `@maxItems`). The inline catalog in the instructions keeps the compact single-line form.
```ts
tools.github.list_issues(input: {
/** Repository owner */
owner: string
/** Cursor from the previous response's pageInfo */
after?: string
/**
* Results per page
* @default 30
*/
perPage?: number
}): Promise<unknown>
```
Result paths are rendered as JavaScript expressions rooted at `tools` (`tools.orders.lookup`, or `tools.context7["resolve-library-id"]` for non-identifier segments), so each `path` is directly usable as the call site. An empty query browses the catalog alphabetically by path; combined with `namespace` (`{ query: "", namespace: "orders" }`) it lists everything in that namespace. A query that names one tool path exactly (canonical path, `tools.`-prefixed path, or rendered JavaScript expression) is treated as a lookup and returns that tool alone.
The instructions are structured markdown, ordered so the workflow sits at the top and the catalog at the bottom: a `## Workflow` section with numbered steps (find a tool via search when the catalog is partial, or pick from the inlined list when it is complete; call the exact path as-is; `JSON.parse` string results; return only the needed fields), a `## Rules` section holding only guidance the workflow does not already cover (only listed/search-result tools exist inside `tools`; filter and aggregate collections in code; treat `Promise<unknown>` results as shapeless until verified; run independent calls through `Promise.all`; enumerate `tools` with `Object.keys`/`for...in`; browse a namespace via search when it is advertised), a short `## Syntax` section that assumes standard JavaScript and names only what is unusual (TypeScript annotations stripped; the data-boundary serialization of Date/Map/Set/RegExp) or missing (classes, generators, `for await...of`, `.then`/`.catch`/`.finally`), and the budgeted `## Available tools` catalog. Example call forms use explicit `<namespace>.<tool>`/`<field>` placeholders - never a real or fabricated tool name.
A host cannot define its own `$codemode` top-level namespace.
## Supported Programs
CodeMode executes a deliberately bounded JavaScript subset. It supports:
- Plain data literals, property access, assignment, and destructuring.
- `if`, conditional expressions, `switch`, `for`, `for...of` (arrays, strings, Maps, Sets), `for...in` (own keys of plain objects, index strings of arrays, and namespace/tool names of `tools` references - anything else is an error suggesting `for...of` or `Object.keys`, rather than real JS's surprising behavior of indices for strings and zero iterations for Maps/Sets), `while`, and `do...while`.
- Arrow functions and function declarations with closures, defaults, rest parameters, and destructuring.
- Optional chaining, nullish coalescing, templates, spread (arrays, strings, Maps, Sets), and `try`/`catch`.
- Common array, string, number, `Object`, `Math`, and `JSON` operations. Mutating array methods include `push`/`pop`/`shift`/`unshift`/`splice` (removes in place and returns the removed elements)/`fill`/`copyWithin`; array `keys`/`values`/`entries` return **arrays** (matching the Map/Set convention) and work with `for...of` and spread. String methods include `localeCompare` (locale/options arguments ignored), `normalize`, and the `trimLeft`/`trimRight` aliases. `Object.keys` also accepts arrays (index strings, as in JS) and tool references: `Object.keys(tools)` lists the top-level namespaces and `Object.keys(tools.ns)` the names at that node (a callable tool enumerates as `[]`; an unknown path is an `UnknownTool` diagnostic). `Object.values`/`Object.entries` on a tool reference fail with a pointer at `Object.keys(tools)` and `tools.$codemode.search`.
- `Date` - `Date.now()`/`Date.parse()`/`Date.UTC()`, `new Date(...)`, the getter methods, and date arithmetic/comparison via the time value. Dates stringify as ISO (`toString` included, for determinism across host timezones).
- Regular expressions - `/literals/` and `new RegExp(...)` with `test`/`exec` (stateful `lastIndex` for `g`), plus string `match`/`matchAll`/`replace`/`replaceAll`/`split`/`search` with patterns. Match results are arrays carrying `index` and named `groups` as own properties (`input` is omitted). Invalid patterns, invalid flags, and missing-`g` calls fail with catchable errors that say what was wrong and how to fix it (escaping hints, the exact `/pattern/g` to write). Patterns run on the host engine, so pathological backtracking is bounded only by the execution timeout. Function replacers are not supported.
- `Map` and `Set` - construction from entries/arrays/strings, `get`/`set`/`add`/`has`/`delete`/`clear`/`size`/`forEach`, and `keys`/`values`/`entries` returning **arrays** (not iterators).
- First-class promises - an un-awaited `tools.ns.tool(...)` is a promise value whose call starts immediately on a supervised fiber; `await` resolves it (awaiting a non-promise value is a no-op, and `return tools.ns.tool(...)` resolves like an async-function return). `Promise.all`, `Promise.allSettled`, and `Promise.race` accept any array mixing promises and plain values (built inline, beforehand, or via spread); `Promise.resolve`/`Promise.reject` construct settled promises. `Promise.allSettled` rejection reasons are the same plain `{ name?, message }` data a `catch` binding sees, and `Promise.race` interrupts its losing in-flight calls. At most 8 tool calls run concurrently. When a program completes, still-running un-awaited calls are awaited before the execution ends; a failure from a call that was never awaited surfaces as an unhandled-rejection diagnostic.
- `throw value` and `throw new Error(message)` for explicit program failure. `Error` (and `TypeError`/`RangeError`/`SyntaxError`/`ReferenceError`/`EvalError`/`URIError`) are real constructors, callable with or without `new`; error values are plain `{ name, message }` data that additionally satisfy `instanceof Error` (a specific type matches itself and `Error`, as in JS). Every caught failure - thrown errors, interpreter runtime errors, and tool failures - is `instanceof Error` in a `catch` block; a thrown non-error value (`throw "text"`) is not, matching JS. Caught failures carry the `name` the equivalent real-JS failure would have - `JSON.parse` and invalid regex patterns produce a `SyntaxError` (satisfying `instanceof SyntaxError`), an unknown identifier a `ReferenceError`, assigning to a constant a `TypeError`, a bad `normalize` form a `RangeError`; failures with no specific analogue (including tool failures) are named `"Error"`. `instanceof` also recognizes `Date`, `RegExp`, `Map`, `Set`, `Array`, `Object`, and `Promise`; any other right-hand side is a catchable error.
Inside a program, Date/RegExp/Map/Set values stay live everywhere: the internal data checkpoints (`Object.*` helpers, spread, coercion inputs) preserve the instances, so `Object.values({ d: date })[0].getTime()` and a spread copy of an object holding a Map keep working. Only at the host boundary (final result, tool arguments, `JSON.stringify`) do the four value types serialize exactly as `JSON.stringify` would: a Date becomes its ISO string (`null` when invalid) and RegExp/Map/Set become `{}`. Promise values never cross a data boundary: an un-awaited promise in a result or tool argument produces a diagnostic that says to await it, instead of serializing to `{}`.
It does not expose `eval`, dynamic imports, modules, classes, generators, timers, host globals, prototype mutation, custom promise constructors (`new Promise`), promise chaining (`.then`/`.catch`/`.finally` - `await` with `try`/`catch` is the supported style), or arbitrary method calls. Unsupported syntax returns an `UnsupportedSyntax` diagnostic with a source location when available.
CodeMode is an orchestration language, not a general JavaScript runtime.
## Execution Limits
The limits are exactly three knobs:
| Limit | Default | Bounds |
| ---------------- | -------------------: | -------------------------------------------------------------------- |
| `timeoutMs` | none - no timeout | Wall-clock execution time. |
| `maxToolCalls` | none - unlimited | Tool calls admitted during the execution. |
| `maxOutputBytes` | none - no truncation | Model-facing output: the serialized result value plus captured logs. |
No limit has a default, on purpose: execution budgets are host policy, not library policy - a host that wants a bound sets one; a host that can interrupt the execution fiber (as OpenCode does on user cancel) may set no timeout, and a host with its own tool-output truncation (as OpenCode has) may leave `maxOutputBytes` unset. A host with neither should set `maxOutputBytes`, or oversized results silently flood model context.
Pass only the overrides you need:
```ts
const runtime = CodeMode.make({
tools,
limits: {
maxToolCalls: 20,
timeoutMs: 60_000,
},
})
```
Limits are safe integers. `timeoutMs` must be at least `1`; the others may be `0`. Invalid configuration throws a `RangeError` when `CodeMode.make` or `CodeMode.execute` is called. An explicitly `undefined` value is the same as leaving the limit unset.
Exceeding a configured `maxOutputBytes` never fails the execution. An oversized result value is replaced by its truncated serialized text plus an explanatory marker, logs are kept from the start until the remaining budget is exhausted (with a final marker line noting the cut), and the result carries `truncated: true`.
When configured, the timeout interrupts in-flight tool Effects, including eagerly started calls the program has not awaited (their fibers are supervised by the execution). The interpreter yields cooperatively between steps, so the timeout also interrupts pure busy loops (`while (true) {}`) - no separate work budget exists. Tool implementations remain responsible for making their external operations interruptible or independently bounded.
Two interpreter internals are fixed constants rather than knobs: at most 8 tool calls run concurrently, and values crossing a data boundary may nest at most 32 levels deep (deeper values fail as `InvalidDataValue`, which reads better than a native stack-overflow error). Neither is part of the public contract.
## Diagnostics
Failures are data:
| Kind | Meaning |
| ----------------------- | -------------------------------------------------------------------------------------------------------- |
| `ParseError` | Source is empty or cannot be parsed. |
| `UnsupportedSyntax` | Parsed JavaScript is outside the supported subset. |
| `UnknownTool` | A program referenced a tool the host did not provide. |
| `InvalidToolInput` | Tool input failed schema decoding or safe-data copying. |
| `InvalidToolOutput` | Tool output failed schema decoding or safe-data copying. |
| `InvalidDataValue` | Program data violated the plain-data contract (depth, circularity, blocked properties, non-data values). |
| `ToolCallLimitExceeded` | Calls exceeded `maxToolCalls`. |
| `TimeoutExceeded` | Execution exceeded `timeoutMs`. |
| `ToolFailure` | A tool refused or failed. |
| `ExecutionFailure` | The program threw or another execution error occurred. |
Unknown host failures, defects, invalid outputs, and copying failures are sanitized. To return a safe operational refusal, fail with `toolError`:
```ts
import { toolError } from "@opencode-ai/codemode"
run: ({ id }) => (authorized(id) ? loadOrder(id) : Effect.fail(toolError("Order is unavailable")))
```
Only the supplied message is model-visible. The optional cause is never returned in `ExecuteResult`; hosts should perform any required internal logging before crossing this boundary.
## Authority Boundary
CodeMode confines programs to the supplied tool tree, but it does not decide what those tools may do.
The host owns:
- Authentication and authorization.
- Tool selection and immutable scope.
- Credentials and network clients.
- Persistence, idempotency, approval, and durable side effects.
- Logging and redaction policy.
CodeMode owns:
- Parsing and interpreting the supported subset without `eval`.
- Schema boundaries around tool calls.
- Plain-data copying and blocked prototype members.
- Resource limits, call accounting, and normalized diagnostics.
- Model-facing tool discovery and instructions.
A program cannot gain authority through prose or generated code. It can only exercise authority already present in the supplied tools. Do not expose a broad tool and expect the prompt to restrict it.
## Laws
The public contract is guided by these equivalences:
- `CodeMode.execute({ ...options, code })` is equivalent to `CodeMode.make(options).execute(code)`.
- `CodeMode.make(options).agentTool().execute({ code })` is equivalent to `CodeMode.make(options).execute(code)`.
- `CodeMode.make(options).agentTool().description` equals `CodeMode.make(options).instructions()`.
- A tool implementation is not invoked unless its input has decoded successfully.
- A tool result is not visible to the program unless its output has decoded and crossed the plain-data boundary successfully.
- Unknown host failures do not become model-visible diagnostics; `ToolError` is the explicit safe-message channel.
- Host interruption remains interruption rather than an `ExecuteFailure`.
## Non-Goals
- Generic permission prompts or approval workflows.
- Durable pause/resume, replay, or storage adapters.
- Exactly-once external side effects.
- Application authorization or product policy.
- A filesystem or process sandbox for arbitrary JavaScript.
- Compatibility with the full JavaScript language or npm ecosystem.
Applications that need approval or durable consequences should model those above CodeMode and expose only the currently authorized tools.
## Testing
From the package directory:
```sh
bun test
bun run typecheck
```
The direct suite covers public projections, discovery, schema boundaries, diagnostic sanitization, resource limits, tool-call observation, and interruption.
-1218
View File
@@ -1,1218 +0,0 @@
# CodeMode - Status, Decisions, and Remaining Work
This document is the working plan for `@opencode-ai/codemode` and its OpenCode integration.
It captures every locked decision, everything already implemented, and a detailed TODO of what
remains - enough context that someone (human or agent) can pick up any item cold.
Tracking issue: https://github.com/anomalyco/opencode/issues/34787
Working branch: `codemode-v2` (base: `dev`)
---
## 1. What this is
CodeMode gives a model one `execute` tool that runs JavaScript/TypeScript programs against a
tree of schema-described tools (`tools.<namespace>.<tool>(input)`), instead of exposing dozens
of MCP tools individually. The point is **control flow**: sequencing, filtering, and composing
tool calls in one program instead of round-tripping through the agent loop, plus not flooding
the context window when users connect many MCP servers.
Architecture split (locked):
- **`packages/codemode` (`@opencode-ai/codemode`)** - the generic, host-agnostic runtime:
a hand-rolled, Effect-native, tree-walking interpreter over acorn ASTs (TypeScript stripped
via `typescript`'s `transpileModule`), the tool runtime/data boundary, discovery/search, and
`Tool.make`. It knows nothing about OpenCode, MCP, permissions, or rendering.
- **`packages/opencode`** - the OpenCode integration: an MCP adapter that converts MCP tool
definitions into `Tool.make(...)` definitions, permission gating, host-side attachment
collection, the agent-facing `execute` tool, and TUI progress rendering.
This package was seeded from the experiments workspace implementation
(`experiments/agents/packages/codemode`, package `@agents/codemode`) and then modified here.
The older vendored interpreter in `packages/opencode/src/session/rune/` was superseded by this
package and was **deleted** in Wave 3 (done, see below).
---
## 2. Locked decisions
From issue #34787 and design discussion. Do not relitigate these casually.
### Core direction
- Generic CodeMode lives in its own package: `@opencode-ai/codemode` (repo scope convention;
the issue's `@opencode/codemode` name was normalized to the `@opencode-ai/*` convention).
- **Keep the hand-rolled interpreter.** No QuickJS/V8/sandbox-engine dependency. We own and
test the whole surface; the model only needs orchestration syntax, not a full runtime.
- Naming: `CodeMode`, `Tool`, `ToolError`, `UnknownTool` (diagnostic kind), `$codemode`
reserved discovery namespace. (Historical names - "rune", "capability" - are dead.)
- Existing OpenCode core tools (bash/edit/patch/...) stay registered normally for v1.
CodeMode covers MCP tools, user-registered tools, and deferred tools only.
- Test runner is `bun test`; typecheck is `tsgo --noEmit` (repo conventions). Not vitest.
- **Never reference external prior-art implementations** (other companies' code-execution
products/blog posts) in code, comments, commit messages, or docs in this repo.
### MCP / tools
- The MCP adapter lives in OpenCode, not here. It converts MCP definitions into ordinary
`Tool.make(...)` definitions and hands CodeMode a plain tool tree.
- Permissions stay in the OpenCode adapter (each tool's `run` wraps the permission ask).
CodeMode stays dumb - no permission model in this package.
- Namespace collisions: last write wins (plain JS object override). No `tools.mcp.*` prefix,
no `_2` suffixing, no cleverness. OpenCode groups flat `server_tool` MCP names into
`tools.<server>.<tool>` namespaces before handing them over.
### Discovery / search
- **Search only - no separate `describe`.** `tools.$codemode.search({ query?, namespace?,
limit? })` over the final tool tree, owned by this package.
- Search result item shape: `{ path, description, signature }` in an `{ items, total }`
wrapper. The `signature` string embeds the full input/output TypeScript types - in search
results it is the pretty, JSDoc-annotated multiline form (Fix 7), so per-field schema
`description`s and constraints (`@default`, `@format`, `@deprecated`, `@minItems`,
`@maxItems`) ride along as field comments. The original spec's separate `input`/`output`
raw-schema fields are deliberately NOT added: shapes are already fully expressed in the
TypeScript signature and schema annotations now arrive as JSDoc - intent satisfied, letter
deviated. Result `path`s render a JavaScript expression rooted at `tools` (for example
`tools.github.list_issues` or `tools.context7["resolve-library-id"]`) so each is directly
usable as the call site; the internal `ToolDescription.path` stays unprefixed.
- Default limit: **10** (done). Exact-path lookup goes through search too: a query equal to a
canonical tool path, `tools.`-prefixed path, or rendered JavaScript expression returns that
tool alone (done).
- Signatures render **native payloads**: `Promise<Issue>`, NOT `Promise<Result<Issue>>`.
There is no result envelope; attachments never appear in return types (they are collected
host-side, see below).
- Tools without an output schema render `unknown` as their return type.
### Schemas / Tool.make
- `Tool.make` carries rich metadata so search can render real signatures.
- Support **Effect Schema** (first-class, validating) and **JSON Schema** (initially
render-only - used for TypeScript rendering; the adapter may validate on its own). Leave
room for Standard Schema later.
- Tool implementations are **Effect-based** for v1 (`run` returns `Effect`). Promise
normalization for plugin authors can come later.
### Attachments / output
- **No `output.text/file/image` API in v1.** (Deleted in Wave 2.)
- Tool calls return native structured payloads into the sandbox. Files/images emitted by
child tools **never enter the sandbox** - the OpenCode adapter strips and accumulates them
host-side as calls happen, then returns them on the outer `execute` tool result as ordinary
tool-result attachments (OpenCode already has `Tool.ExecuteResult.attachments` -> vision
plumbing in `message-v2.ts`).
- No base64 in CodeMode values, ever. The model routes nothing; it can't accidentally dump
image bytes into context or drop attachments.
### Runtime behavior
- Limits are EXACTLY the three public knobs: `{ timeoutMs, maxToolCalls, maxOutputBytes }` -
matching the original locked spec exactly. NO limit has a default (user direction, Fix 6
for the first two; extended to `maxOutputBytes` in the truncation-layering fix below):
absent = no timeout / unlimited calls / no output truncation - budgets are host policy.
A host without its own output bounding should set `maxOutputBytes` explicitly, or
oversized results silently flood model context. OpenCode's adapter policy (user
direction): NO limits at all - no timeout, unlimited tool calls (each child call is
permission-gated; user cancel interrupts the execution fiber and its children), and no
CodeMode truncation (output bounding is OpenCode's native tool-output truncation).
The internal limit system that Wave 2 kept behind
an `@internal` `InternalExecutionLimits` type (maxOperations, maxDataBytes, maxValueDepth,
maxCollectionLength, maxSourceBytes, maxAuditBytes, maxConcurrency) was deleted outright in
Fix 5 (see Post-wave fixes). Two internals survive as fixed constants, not knobs:
`TOOL_CALL_CONCURRENCY = 8` (the fork semaphore) and `MAX_VALUE_DEPTH = 32` (the `copyIn`
boundary depth check, kept only because it beats a native stack-overflow RangeError as an
error message; still reports `InvalidDataValue`).
- Truncation layering RESOLVED (user direction): CodeMode truncation is off in OpenCode.
`execute` is a normal `Tool.define` tool, so OpenCode's native tool-output truncation
(50KB / 2000 lines in `tool.ts` + `truncate.ts`, full output dumped to a file) applies to
it with no special-casing - verified by tracing `wrap()` in `tool.ts:130-144` (the
`metadata.truncated` exemption never fires for `execute`). One truncation layer, the
host's. `maxOutputBytes` remains available for hosts without their own bounding.
- Pure-JS built-ins only. **No ambient authority**: no fs, child processes, network/fetch,
process/env, or timers in v1. The agent has the bash tool for that.
- Forgiving JS semantics are locked (see section 3, Wave 1a/1b-i) - missing props read `undefined`,
`typeof` never throws, NaN/Infinity flow in-sandbox, etc.
- `console.*` is captured into `logs` on the result; the host appends them to model-facing
output. Not a tool call; costs no tool budget.
- Simple tool-call **start/end hooks** for nested progress: `onToolCallStart({ index, name,
input })` and `onToolCallEnd({ index, name, input, durationMs, outcome, message? })`.
Interrupted calls fire no end event. No `CurrentToolCall` context service (removed in
Wave 2).
---
## 3. Current status (what is already done on `codemode-v2`)
Everything below is committed and pushed on `codemode-v2` (six commits, in pairs of
generic-package + OpenCode-integration: waves 0-5, Fixes 4-9, then the DSL-expansion pass /
real-JS error names / truncation layering). Verification: from `packages/codemode`,
`bun test` (211 pass / 0 fail across `codemode/parity/stdlib/promise/enumeration/signature`)
and `bun run typecheck`; from `packages/opencode`, `bun run typecheck` and
`bun test test/tool/` (all green - the adapter suites are `test/tool/code-mode.test.ts`,
43 tests, and `test/tool/code-mode-integration.test.ts`, 16 tests, moved from
`test/session/` by the registry promotion; registry coverage in
`test/tool/registry.test.ts`).
### Wave 0 - scaffold (done)
- `packages/codemode` created from the experiments implementation: `src/{index,codemode,tool,
tool-error,tool-runtime}.ts`, README, AGENTS.md, tests.
- `package.json`: name `@opencode-ai/codemode`, deps `acorn@8.15.0`, `typescript: catalog:`,
`effect: catalog:` (both repos pin effect `4.0.0-beta.83`; opencode's effect patch only
touches `unstable/httpapi`, which this package doesn't use).
- Tests converted vitest -> `bun:test`. Only src change from verbatim: the `CurrentToolCall`
Context.Service key string renamed to `@opencode-ai/codemode/CurrentToolCall`.
### Wave 1a - forgiving JS semantics (done)
Ported from the old opencode rune work; `test/parity.test.ts` (24 tests) is the acceptance
spec. The seeded interpreter was deliberately strict; these behaviors replaced that:
- **H1**: NaN/Infinity flow as in-sandbox values (`copyIn` admits them; `NaN`/`Infinity` are
bindable globals; `charCodeAt` returns real NaN). Normalized to `null` only at the data
boundary (`copyOut` - single chokepoint for final results AND tool-call arguments), matching
`JSON.stringify`. Guards like `Number.isNaN(x)` / `parseInt(x) || 0` work.
- **H2/H3**: unknown property reads on strings/numbers/arrays -> `undefined` (incl. under
`?.`), instead of throwing. This was the real-transcript failure: models write
`result?.login ?? result` against JSON-string tool results.
- **H4**: `typeof undeclaredIdentifier` -> `"undefined"` (short-circuits before resolution).
- **H5**: `Boolean`/`String`/`Number` accepted as array callbacks (`filter(Boolean)`).
- **H6**: `{...null}` / `{...undefined}` object spread is a no-op. Array spread of
null/undefined still throws (real JS throws too).
### Wave 1b-i - stdlib value types: Date, RegExp, Map, Set (done)
`src/values.ts` holds `SandboxDate/SandboxRegExp/SandboxMap/SandboxSet` (own module so both
`codemode.ts` and `tool-runtime.ts` import without a cycle). Design:
- Opaque-by-default: all four join `isRuntimeReference`, with explicit carve-outs (member
access allowlists, Date in binary/unary ops, Map/Set in spread/for...of, console formatting,
`containsOpaqueReference` for operator guards; the `runtimeValueBytes` byte-accounting
carve-out died with that machinery in Fix 5).
- **JSON semantics at every boundary and checkpoint**: Date -> ISO string (invalid -> null),
RegExp/Map/Set -> `{}`. `copyIn` also converts host `Date`/`RegExp`/`Map`/`Set` instances the
same way (a host tool may legitimately return them). (Narrowed by the DSL-expansion pass:
intra-sandbox checkpoints now preserve the instances; JSON forms apply at the host
boundary only.)
- Date: `Date.now/parse/UTC`, `new Date(epoch|string|components)`, getters + UTC variants,
`end - start`, `a < b`, `+date`; `toString` is ISO for cross-host determinism.
- RegExp: literals + `new RegExp`, `test`/`exec` (stateful `lastIndex` for `g`), string
`match/matchAll/replace/replaceAll/split/search`. Match results are plain arrays carrying
`index`/named `groups` as own properties (enabled by a general array own-property read fix);
`input` omitted deliberately. Function replacers unsupported (clear error). Patterns run on
the host engine - catastrophic backtracking is bounded only by `timeoutMs` (accepted, in
README).
- Map/Set: full method sets; `keys/values/entries` return **arrays** (not iterators);
`for...of` + spread work; `Object.fromEntries(map)`, `Array.from(map|set)`; SameValueZero
keys (NaN findable). (The incremental byte totals and `maxCollectionLength`/`maxDataBytes`
enforcement this wave added were deleted in Fix 5.)
- Rode along, same spirit: `typeof` never throws for any value (`typeof fn` -> `"function"`),
`!` works on any value, `for...of` over strings, `{...sandboxValue}` no-op, template
interpolation renders `/regex/` and ISO dates directly.
### Wave 2 - API layer (done)
The package's public contract, reshaped for the Wave 3 adapter. 101 tests / 0 fail after this
wave; both packages typecheck clean.
- **`Tool.make` schema flexibility** (`src/tool.ts`): `input`/`output` each accept an Effect
Schema (validating, decoded both directions as before) OR a raw JSON Schema document
(render-only - no validation, values pass through; rendering handles `$defs`/`definitions`
- `$ref`). `output` is **optional** -> signature renders `Promise<unknown>` and the host
result is exposed as-is. Discrimination via `Schema.isSchema`. New helpers exported from
`tool.ts`: `inputTypeScript`/`outputTypeScript`/`decodeInput`/`decodeOutput`/
`jsonSchemaToTypeScript`; `tool-runtime.ts` consumes them (no direct `Schema.*` use there
anymore). Types `JsonSchema`/`ToolSchema` exported from the index. Note: an empty
`Schema.Struct({})` renders as `{ } | Array<unknown>` (effect's JSON Schema emission) -
cosmetic, fixed in Wave 4.
- **`output.*` API deleted**: `OutputItem`(+Schema), result `output` fields, the `output`
global/namespace dispatch, `invokeOutput`/`outputItem`/helpers, interpreter output fields,
instructions line, README section, seeded tests. AGENTS.md keeps a rephrased
future-design note (channel name stays `output` if it ever returns).
- **Hooks**: `CurrentToolCall` removed entirely (class, provideService, `Services` Exclude
special-casing, index export). `onToolCall` -> `onToolCallStart({ index, name, input })` +
`onToolCallEnd({ index, name, input, durationMs, outcome: "success"|"failure", message? })`.
End fires symmetrically via `Effect.tap`/`tapError` around the settling portion (host run +
output decode + boundary copy; search too - its post-record body is wrapped in `Effect.try`
so failures are typed and observable). `message` is the model-safe failure message
(`ToolError`/`ToolRuntimeError` message, else "Tool execution failed"). Interrupted calls
fire no end event (timeout kills the whole execution anyway).
- **Limits collapse**: public `ExecutionLimits` = `{ timeoutMs?, maxToolCalls?,
maxOutputBytes? }` (defaults 10_000 / 100 / 32_000). This wave kept the other knobs as
internal defaults reachable through an `@internal` `InternalExecutionLimits` type; Fix 5
later deleted that type and the internal limit system entirely.
- **`maxOutputBytes` truncation** (CodeMode-owned, never fails): applied via `boundOutput` in
a final `Effect.map` over every result path (success/timeout/normalized failure). Oversized
serialized values become truncated text + ` [result truncated: N bytes exceeds the M-byte
output limit; return a smaller value]`; logs keep leading lines within the remaining budget
- `[logs truncated: showing K of N lines]`; result gains `truncated: true` (also added to
`ExecuteResultSchema`). UTF-8-safe truncation (no split code points). (The in-sandbox
`maxDataBytes` check that used to throw first on oversized raw values died in Fix 5 -
truncation is now the only result-size mechanism.)
- **Search polish**: default limit 12 -> **10** (`defaultSearchLimit`); exact-path lookup - a
trimmed query equal to one tool path (optionally `tools.`-prefixed) returns that tool alone
(`total: 1`), bypassing ranking. Tokenization/ranking/shape unchanged.
### Wave 3 - OpenCode MCP adapter (done)
`packages/opencode/src/session/code-mode.ts` rewritten as a thin adapter over this package;
the vendored rune interpreter is gone. Same `define(mcpTools, mcpDefs, servers)` signature, so
`tools.ts` gating (flag on + MCP tools exist -> single `execute` tool, early-return suppresses
per-MCP registration; MCP resource tools unaffected) is unchanged.
- **Tool tree**: `groupByServer` (longest-sanitized-prefix, ported) groups flat `server_tool`
keys into `CatalogEntry`s carrying the raw MCP `inputSchema`/`outputSchema` as render-only
JSON Schema; `toolTree` turns each into `Tool.make({ description, input, output?, run })`
under `tools.<server>.<tool>`. The agent-facing description is
`CodeMode.make({ tools }).instructions()` over a preview tree (placeholder runs, never
invoked) - so signature rendering, the inline-vs-search switch, and `$codemode.search`
availability all come from this package and stay consistent with execution.
- **`run` path**: per-child permission ask first (`ctx.ask({ permission: entry.key, patterns:
["*"], always: ["*"] })`, exactly the old gating; approving `execute` approves no child).
Denials and host failures are mapped to `toolError(message)` so they surface as safe,
catchable in-program failures (MCP `isError` text propagates as `e.message`; without this
they'd be sanitized to "Tool execution failed"). Dispatch reuses the ai-sdk wrapper from
`catalog.convertTool` (`entry.tool.execute!`), which owns callTool timeouts/progress-reset.
- **Result shaping** (`toSandboxResult`): prefer `structuredContent`; else joined text
content; media (image/audio/resource blob/resource_link) NEVER enters the sandbox - blocks
are stripped into a per-execution `Attachment[]` accumulator, and a media-only result
becomes a marker payload (`"[1 image attached to the result]"`, noun/count adjusted). An
MCP-shaped result with nothing extractable becomes `null`; non-MCP values pass through.
No handles, no `Result<T>` envelope, no base64 in the sandbox, no data-size tuning (the
`maxDataBytes` budget that existed at the time was deleted in Fix 5).
- **Execute result**: `{ output: formatValue(value) + trailing "Logs:" section (success AND
error - logs are plain pre-formatted lines now), attachments: accumulated }` through the
existing `Tool.ExecuteResult.attachments` -> `message-v2.ts` vision plumbing; attachments
ride on both success and error results. Diagnostic `suggestions` not already contained in
the message are appended to error output. Native outer truncation stays on (adapter never
sets `metadata.truncated`); CodeMode's own `maxOutputBytes` (32 KB default at the time)
cut first - since the truncation-layering fix, native truncation is the only layer.
Limits: `{ timeoutMs: 30_000 }` at the time (matched the default MCP request timeout);
killed in Fix 6 - the adapter now passes no limits at all.
- **Progress**: `onToolCallStart`/`onToolCallEnd` -> `ctx.metadata({ toolCalls })` with
`{ tool, status: running|completed|error, input? }` per call index - the exact shape the
TUI `Execute` component (`packages/tui/src/routes/session/index.tsx`) already renders.
`$codemode.search` calls stream through the same channel.
- **Deletions/deps**: `src/session/rune/` (all five files) and
`test/session/rune-parity.test.ts` (superseded by this package's `test/parity.test.ts`)
deleted; `acorn` removed from opencode deps, `typescript` moved back to devDependencies,
`"@opencode-ai/codemode": "workspace:*"` added; `bun install` run (lockfile updated).
- **Tests**: both opencode suites rewritten against the adapter design -
`code-mode.test.ts` (34: grouping, description/signature rendering incl. the large-catalog
search fallback, execution, permission flow + denial, metadata streaming, attachment
accumulation + media-only marker, logs on success/error, truncation marker,
`toSandboxResult`/`formatValue`/`withLogs` units) and `code-mode-integration.test.ts`
(16: real in-memory MCP server; native structured results, attachment accumulation, isError
propagation, logs, permissions, live metadata). Old envelope/attachment-handle/`$rune`
describe/`renderType`/`rankTools` tests died with the old design (58+17+24 -> 34+16).
### Wave 4 - instructions/prompting + polish (done)
Instructions are now the budgeted-catalog + prompting-guidance form; verified e2e against a
real MCP config. Package still 101 tests / 0 fail; opencode adapter suites still 34 + 16; both
packages typecheck clean.
- **Budgeted catalog** (`discoveryPlan` in `tool-runtime.ts`): the all-or-nothing
inline/search modes are gone - `DiscoveryMode` deleted, `DiscoveryOptions` is just
`{ maxInlineCatalogBytes? }` (default 16,000 UTF-8 bytes; later converted to
`maxInlineCatalogTokens`, default 4,000 estimated tokens - see Post-wave fixes). Port of
the old opencode
`describe()` `PREVIEW_BUDGET` algorithm, adapted to `ToolDescription`: every namespace is
ALWAYS listed with its tool count; full signature lines
(` - <signature> // <first line of description, capped at 120 chars>`) are inlined
cheapest-first (line byte length, path tiebreak) within each namespace, namespaces processed
alphabetically; once one line does not fit, inlining stops for every remaining namespace
(counts only), exactly like the ported algorithm (this stop-everything behavior was later
replaced by round-robin fairness in Fix 8). The header states comprehensiveness
precisely: "Available tools (COMPLETE list - ...)" vs "Available tools (PARTIAL - N of M
shown; find the rest with tools.$codemode.search)"; namespace labels are `(N tools)` /
`(N tools, K shown)` / `(N tools, none shown)`. An empty tree renders "No tools are
currently available."
- **Search always registered** (documented decision): `DiscoveryPlan.searchIndex` is required
and built unconditionally (new exported `ToolRuntime.searchIndex(tools)`; `SearchEntry` type
exported); `CodeMode.execute` (one-shot) passes it too, preserving the
`execute`==`make().execute` law. A speculative `tools.$codemode.search` call on a small
catalog now succeeds instead of `UnknownTool`, and unknown-tool suggestions always point at
search. Search is _advertised_ in the instructions only when the inlined list is PARTIAL,
keeping small-catalog instructions tight.
- **Prompting content** in `instructions()`, mapping 1:1 to the section 5 transcript failures:
parse-string-results-as-JSON, return-small, console-for-intermediates, and
read-the-description-before-calling guidance. (The flat prose layout this wave produced
was later replaced wholesale by the markdown-section restructure - see Post-wave fixes -
which also deleted this wave's worked example.)
- **Cosmetic renderer fixes** (`renderSchema` in `tool.ts`): an object schema with no
properties renders `{}` (was `{ }`), and the empty `Schema.Struct({})` emission
(`anyOf: [{ type: "object" }, { type: "array" }]`, no properties/items) collapses to `{}`
(was `{ } | Array<unknown>`).
- **Tests**: 4 package discovery tests rewritten for the budgeted behavior (COMPLETE small
catalog + search-still-registered; PARTIAL at budget 0; cheapest-first selection +
per-namespace labels + budget-exhaustion stopping later namespaces; mode-validation
assertion dropped); 3 opencode description assertions updated (COMPLETE/PARTIAL headers,
namespace labels, `(input: {})` rendering, cheapest-first op_0 shown / op_149 not).
- **E2E (verified, headless)**: from the repo root with `OPENCODE_EXPERIMENTAL_CODE_MODE=1`,
the scratch `.opencode/opencode.jsonc` (context7, github, playwright, sentry, memory,
sequential-thinking; left uncommitted/as-is), and `bun packages/opencode/src/index.ts run
--dangerously-skip-permissions -m opencode/claude-sonnet-4-5 "..."`. Confirmed: a single
`execute` tool registered alongside core tools (per-MCP registration suppressed; MCP
resource tools unaffected); the live description read back as "Available tools (PARTIAL -
56 of 88 shown; find the rest with tools.$codemode.search):" with correct per-namespace
labels (context7/github/memory fully shown; playwright/sentry/sequential-thinking "none
shown" - the alphabetical-exhaustion starvation Fix 8 later replaced with round-robin
fairness); programs executed with in-program `$codemode.search`
calls and returned the correct answer. NOT verified e2e (headless only; covered by
unit/integration tests instead): TUI child-call rendering, attachments becoming visible
images, output truncation.
### Wave 5 - Promise generalization (done)
First-class promise values in the interpreter; the direct-tool-call-only `Promise.all`
restriction (and its bespoke AST checks) is gone. Package suite is 136 tests / 0 fail (35 new
in `test/promise.test.ts`); adapter suites and both typechecks unchanged/green; the opencode
adapter needed **no changes**.
- **Decision: eager fork** (`const p = tools.a.b(x)` starts the call immediately on a
supervised child fiber; `await p` observes its settlement). Chosen over lazy because:
(1) it's spec-faithful - JS promise work starts at call time, so
`const a = t1(); const b = t2(); return [await a, await b]` gets real parallelism instead of
silently sequential awaits; (2) run-once is free - a fiber settles exactly once and
`Fiber.await` is idempotent, so `await p` twice or `Promise.all([p, p])` can never re-invoke
the tool (lazy needs a deferred/latch to match); (3) effect's structured concurrency does the
hard part - `Effect.forkChild` children are auto-supervised (interrupted when the parent
fiber exits) and `Effect.timeoutOrElse` is `raceFirst`, which runs the program on its own
raced fiber, so forked calls cannot escape the timeout (tested: in-flight forks are
interrupted, awaited or abandoned, direct or inside `Promise.all`).
- **Mechanics**: `SandboxPromise` in `values.ts` (fiber-backed for tool calls; fiberless
`immediate` effect for `Promise.resolve`/`reject`). Forks run
`semaphore.withPermit(invoke)` with `startImmediately: true` - a per-execution
`Semaphore.makeUnsafe(TOOL_CALL_CONCURRENCY)` (fixed 8, see Fix 5) caps live calls (the
"Effect.all or equivalent" cap lives where the work is, so combinator joins can be
sequential without losing parallelism), and the tool-call-count charge (`recordCall`) plus
`onToolCallStart` fire at the call site before any await. `await` of a non-promise is a passthrough no-op; a returned
top-level promise resolves like an async-function return (`return tools.a.b(x)` works
without await).
- **Promise combinators are normal functions over values**: `Promise.all`/`allSettled`/`race`
accept any array (or spreadable collection) mixing promises and plain data - inline, built
beforehand, spread, nested in variables. `allSettled` yields
`{ status: "fulfilled", value } | { status: "rejected", reason }` with reasons produced by
the same `caughtErrorValue` helper the `catch` binding uses (factored out of
`evaluateTryStatement`). `race` resolves/rejects with the first settlement and interrupts
losing in-flight calls; awaiting an interrupted loser afterwards is a catchable program
failure ("interrupted because another value settled a Promise.race first"), while any other
interrupt-only settlement keeps propagating as interruption (preserving the
host-interruption law). `Promise.resolve` flattens promises; `Promise.reject` rejects with
the reason via `ProgramThrow`.
- **Opaqueness/boundaries**: promises are runtime references - `typeof` -> `"object"` (real JS),
operators reject them, `copyIn` raises an await-hinting `InvalidDataValue` ("contains an
un-awaited Promise; await tool calls (...) before using their results") for results, tool
arguments, and `JSON.stringify` instead of `{}`. Property access on a promise is a
deliberate error (not the forgiving `undefined`): `.then/.catch/.finally` ->
`UnsupportedSyntax` pointing at `await` + try/catch; anything else -> "await it first".
`new Promise(...)` -> UnsupportedSyntax ("tool calls already return promises");
`Promise.<unknown>` lists the five available statics. `console.log(p)` prints
`[Promise (await it to get its value)]`.
- **Program-end drain**: on successful completion the interpreter awaits still-running
un-awaited fibers (like a runtime waiting on in-flight I/O at exit), so fire-and-forget
calls complete deterministically; a failure nobody could have handled surfaces as an
"Unhandled rejection from an un-awaited tool call: ..." diagnostic (kind preserved,
suggestion says to await) - keeping pre-wave failure visibility for un-awaited
statement-position calls. Settlement observation (await/all/allSettled/race) marks a
promise handled; failed executions skip the drain and children are interrupted by
supervision.
- **Deletions/updates**: `evaluatePromiseAll`, `evaluateParallelMap`, `isToolCallExpression`,
`isToolPath`, `forkForParallelCallback`, and `PromiseAllReference` deleted
(`PromiseMethodReference` over `all/allSettled/race/resolve/reject` replaces it);
`supportedSyntaxMessage`, the two instructions lines in `tool-runtime.ts`, and README
"Supported Programs" rewritten for the new surface.
- **Known divergences (deliberate)**: `p === q` on promises throws the operators-need-data
diagnostic instead of comparing identity; `{...promise}` errors instead of JS's silent `{}`;
a per-iteration `await` inside `items.map(async (i) => await tools.x(i))` runs sequentially
(interpreter callbacks compose synchronously) - the parallel idiom is mapping to un-awaited
calls and awaiting `Promise.all`, which the instructions show.
### Post-wave fixes
- **Key enumeration: `Object.keys(tools)` + `for...in` (done).** Motivating transcript: a
model tried to enumerate tool namespaces with `Object.keys(tools)` (failed with the generic
"Object.keys input must contain plain objects only." - `tools` is a `ToolReference`, not
plain data) and then `for (const key in tools)` ("Syntax 'ForInStatement' is not
supported"), and had to fall back to guessing namespace names from the instructions -
defeating discovery. Fixes, all in this package:
- `ToolRuntime.make` now returns a `keys(path)` capability (`namespaceKeys` in
`tool-runtime.ts`) threaded into the `Interpreter` alongside `invoke` - the interpreter
still never holds the host tool tree. `Object.keys(tools)` yields the top-level namespace
names (never `$codemode`, which is virtual - but `Object.keys(tools.$codemode)` yields
`["search"]`), `Object.keys(tools.ns)` the names at that node; a callable tool leaf
enumerates as `[]` (like `Object.keys` of a JS function); an unknown path throws an
`UnknownTool` diagnostic suggesting `Object.keys(tools)` and `$codemode.search` (matching
call-time unknown-tool behavior rather than silently returning `[]`).
- `Object.values`/`Object.entries` (and every other `Object.*` helper) on a tool reference
now fail with "...not plain data. Use Object.keys(tools) for names, or
tools.$codemode.search({ query }) for signatures." instead of the generic message.
- `Object.keys(array)` returns index strings (`["0", "1", ...]`) like real JS (was a
Backlog item).
- `for...in` (ForInStatement) iterates own enumerable string keys of plain objects, index
strings of arrays, and namespace/tool names of tool references - sharing the interpreter's
`enumerableKeys` helper with the `Object.keys` tool path. const/let declarations and bare
identifiers bind the key; break/continue work. Anything else (strings, Map/Set, numbers,
null, ...) is a clear error suggesting `for...of` or `Object.keys` - deliberately smaller
than real JS (which yields indices for strings and zero iterations for Maps/Sets/null).
- `supportedSyntaxMessage`, the instructions loops line, and README "Supported Programs"
mention the new surface; tests in `test/enumeration.test.ts` (14, incl. the exact
transcript program) plus one adapter-level assertion that `Object.keys(tools)` returns
MCP server names.
- **Search ranking, namespace scoping, prefixed result paths (done).**
Motivation: the Wave 4 e2e run showed a model retrying calls because search-result paths
lacked the `tools.` prefix (a Backlog item), and the word-set ranker missed
parameter-name and partial-word queries. Fixes:
- **Ranking ported from the pre-rebuild implementation** (the `searchTextFor`/`tokenize`/
`rankTools` algorithm in `packages/opencode/src/session/code-mode.ts` at git HEAD),
replacing the word-set ranker in `tool-runtime.ts`. Searchable text per tool = path +
description + input-schema property names + their `description` strings - extracted by
the new `inputProperties` helper in `tool.ts` (Effect Schemas via
`Schema.toJsonSchemaDocument`, the same emission signature rendering uses; JSON Schemas
read `properties` directly, resolving a trivial top-level `$ref`; try/catch falls back to
path + description). Queries tokenize on camelCase boundaries + non-alphanumeric
separators (empties and `*` dropped). Additive per-term scoring: exact path or
path-segment match 20, path substring 8, description substring 4, searchable-text
substring 2; summed across terms, filtered to score > 0, sorted score desc then path asc
(Fix 8 later made each field check accept the term OR a naive singular variant).
An empty query now browses ALPHABETICALLY by path (was declaration order). Kept:
`{ path, description, signature }` result items, default limit 10, exact-path instant
lookup, input validation errors.
- **Namespace scoping**: `tools.$codemode.search({ query?, namespace?, limit? })` -
`namespace` (validated as a string when provided) filters `SearchEntry`s to one top-level
namespace before ranking; `{ query: "", namespace: "github" }` lists that namespace
alphabetically. `searchSignature` updated.
- **Callable result paths**: search-result `path`s are rendered as JavaScript expressions
rooted at `tools` (`tools.github.list_issues`, or bracket notation for non-identifier
segments), directly usable as the call site. Internal `ToolDescription.path` stays
unprefixed; only the search RESULT items are rendered this way. Exact-path queries accept
canonical paths and rendered expressions.
- **Instructions** (`discoveryPlan`): an explicit calling-convention line and a browse
hint on the search advertisement (both since absorbed into the `## Rules` section by
the instructions restructure below).
- **Tests**: package search/discovery tests updated (prefixed paths, alphabetical browse)
plus new coverage for namespace scoping, parameter-name matching, partial-word substring
matching, alphabetical empty-query order, and prefixed exact-path lookup; one adapter
assertion updated to the prefixed path (suites stay 35 + 16, green).
- **Instructions restructure: markdown sections, placeholder-only call forms (done).**
The flat prose instructions (which mixed a real catalog tool with fabricated result
fields in the worked example) are replaced by structured markdown in `discoveryPlan`,
ordered so the workflow sits at the top (the least likely part of a long description to
be truncated or skimmed away) and the catalog at the bottom (the per-section content
described here was later condensed by Fix 8 - Workflow/Rules deduped, Syntax inverted):
- **Intro** (2 lines): "Write a CodeMode program... Return code only." + "Execute
JavaScript in a confined runtime with access to the tools listed below under
`tools.*`." (the second line drops the tools clause when the tree is empty).
- **`## Workflow`**: numbered steps - find a tool via `tools.$codemode.search` -> read
the `{ path, description, signature }` matches -> call by path -> `typeof res ===
"string" ? JSON.parse(res) : res` -> return only the needed fields. When the catalog is
COMPLETE the search/read steps collapse into "Pick a tool from the list under
`## Available tools`" and the steps renumber (4 instead of 5).
- **`## Rules`**: call-by-exact-path; TEXT-is-JSON -> JSON.parse; return small (never raw
payloads); filter/aggregate large collections in code instead of per-item round-trips;
console.log/warn/error/dir/table for intermediates; `Promise.all` parallelism (no
.then/.catch - await + try/catch); `Object.keys(tools)`/`for...in` enumeration;
browse-one-namespace via search (PARTIAL only); and host-side media handling (files/
images never enter the program; a media-only call yields a small text marker - wording
verified against the adapter's `toSandboxResult`/`mediaMarker`).
- **`## Syntax`**: the dense syntax lines unchanged, minus the Promise.all and console
lines (moved into Rules) and the `for (const ns in tools)` fragment (redundant with
the enumeration rule).
- **`## Available tools`**: the budgeted catalog unchanged, with the COMPLETE/PARTIAL
header merged into the section heading (no trailing colon); the search-signature
advertisement follows when PARTIAL (its description-reading and browse clauses moved
to Workflow/Rules).
- Every call form in Workflow/Rules uses explicit `<namespace>.<tool>`/`<field>`
placeholders - the example builder that derived a worked example from the first inlined
catalog tool (`exampleArguments` + the example-selection machinery) is DELETED, so no
real catalog tool is cherry-picked into examples and no fabricated names or fields
appear anywhere in the instructions. Zero tools keep "No tools are currently
available." under minimal sections (intro + Syntax + Available tools).
- **Tests**: the package worked-example test replaced by section-structure/placeholder
assertions (section order; JSON.parse + return-small rules present; no
`total_count`/`list_issues`/real-tool example lines; browse hint only when PARTIAL;
zero-tool minimal sections) - 156 pass / 0 fail; adapter suites gain the same
assertions on the built description (still 35 + 16, green).
**Fix 4 - token-budgeted catalog (was bytes)** (user direction: signatures need a token
budget; namespaces must always be present):
- `src/token.ts` added: copy of `@opencode-ai/core/util/token` (`round(chars / 4)`), so
the package stays dependency-free; keep in sync if the core heuristic changes.
- `DiscoveryOptions.maxInlineCatalogBytes` -> `maxInlineCatalogTokens` (default 4,000
estimated tokens ~ the old 16,000 bytes at 4 chars/token - behavior parity, not a size
reduction). `discoveryPlan` charges `estimate(catalogLine(tool))` per line; cheapest-first
- stop-on-first-miss unchanged at the time (stop-on-first-miss replaced by round-robin in
Fix 8). Namespace stub lines were and remain unbudgeted - every
namespace always appears with its tool count, even at budget 0 (asserted in package and
adapter tests).
- Ripple: chars/4 rounding erases small line-length differences, so equal-cost lines fall
to the lexicographic path tiebreak; the adapter's PARTIAL test now asserts the
lexicographic tail (`op_99`) is excluded instead of `op_149`. Fixed-prose measurements
(2026-07): preamble ~44 + Workflow ~146 + Rules ~362 + Syntax ~453 ~ 1,100 tokens fixed;
worst-case net description ~ fixed + 4,000 ~ 5,100 estimated tokens.
**Fix 5 - internal limits removed** (user direction: only the three PUBLIC limits survive as
configurable knobs; the internal limit system dies):
- `ExecutionLimits` (`timeoutMs` 10_000 / `maxToolCalls` 100 / `maxOutputBytes` 32_000 at
the time; Fix 6 later removed the first two defaults. Same validation: safe integers,
timeoutMs >= 1, others >= 0, RangeError otherwise) is now
the ENTIRE limit surface - exactly the shape section 2's original locked spec named.
`ResolvedExecutionLimits` shrank to those three fields; the `@internal`
`InternalExecutionLimits` type is deleted.
- **Deleted outright**: `maxOperations` and the whole operation-budget machinery
(`recordWork`/`recordOperation`/`budget.operations`, plus the `workUnits`/
`cheapArrayMethods` cost helpers); `maxSourceBytes` (the pre-parse source-size check);
`maxDataBytes` (every byte-accounting path: `runtimeValueBytes`, `boundedProgramValue`,
the container-size caches (`containerSizes`/`objectCounts`), Map/Set incremental `bytes`
fields in `values.ts`, string-growth `limitString` checks, tool-argument/result byte
checks in `tool-runtime.ts`, and the final-result size check); `maxAuditBytes` (log and
audit-trail byte accounting - `toolCalls` records and the start/end hooks are unchanged);
`maxCollectionLength` (every array-length/object-field-count check - this knob was
actively harmful: an MCP tool returning 20k rows failed). The `OperationLimitExceeded`
and `AuditLimitExceeded` diagnostic kinds are gone from the `DiagnosticKind` union and
`ExecuteResultSchema` (fine - the package is unreleased).
- **Fixed constants, not knobs**: `TOOL_CALL_CONCURRENCY = 8` (codemode.ts; the fork
semaphore) and `MAX_VALUE_DEPTH = 32` (tool-runtime.ts; the `copyIn` depth check - kept
only because it produces a clearer error than a native stack-overflow RangeError; still
`InvalidDataValue`). The `DataLimits` plumbing through `tool-runtime.ts` is gone -
`copyIn(value, label)` needs no limits argument, and `ToolRuntime.make` takes just
`(tools, maxToolCalls, hooks?, searchIndex?)`.
- **Verified fact**: timeout interruption does NOT depend on the operation budget - the
Effect fiber runtime auto-yields between interpreter steps, so `timeoutMs` interrupts
even a pure `while (true) {}` loop (empirically verified: a 200ms timeout fired at
~225ms with maxOperations set to MAX_SAFE_INTEGER before the deletion). A regression
test in `codemode.test.ts` asserts exactly this (`while(true){}` + `timeoutMs: 200` ->
`TimeoutExceeded`, elapsed well under a few seconds).
- **Kept (correctness, not budgets)**: circular detection (`copyIn` walks +
`rejectCircularInsertion` on mutations), plain-objects-only, blocked properties
(`__proto__`/`constructor`/`prototype`), data-only checks, and all three public-limit
behaviors unchanged.
- Behavior deltas beyond the intended kills: in-sandbox structures deeper than 32 levels
now fail at the data boundary (`copyIn`) instead of at construction; array index
assignment allows any non-negative integer index (holes permitted, message now "must be
a non-negative integer"); interpreter-produced deep/hostile structures that overflow the
native stack during a walk still normalize to the existing "Execution exceeded the
maximum nesting depth." data diagnostic - failures remain data everywhere.
- Tests: deleted the knob-only tests (stdlib Map/Set collection-length growth x2,
enumeration operation-budget, codemode maxDataBytes/maxSourceBytes/maxOperations/
maxConcurrency-RangeError assertions, and the adapter's runaway-loop-via-operation-limit
test - superseded by the package timeout regression test); rewrote the helpers that used
`InternalExecutionLimits` as a convenience to plain `ExecutionLimits`
(promise/enumeration/stdlib run helpers). Package suite: 154 pass / 0 fail; adapter
suites: 34 + 16.
**Fix 6 - no default timeout / tool-call cap** (user direction): `timeoutMs` and
`maxToolCalls` lost their defaults (were 10_000 / 100) - absent now means no timeout /
unlimited calls. Budgets are host policy, not library policy; `maxOutputBytes` kept its
32,000 default at the time (removed later - see the truncation-layering entry: absent now
means no truncation). `ResolvedExecutionLimits` carries `number | undefined` for both, the
timeout wrapper is only applied when configured, and `ToolRuntime.make` treats undefined
`maxToolCalls` as uncapped. Validation is unchanged when values ARE provided (safe integers,
timeoutMs >= 1, others >= 0). The OpenCode adapter is unaffected in behavior it sets
(explicit 30s timeout) but now runs with unlimited tool calls. Immediately after, per user
direction, the adapter's 30s timeout was killed too: `CODE_LIMITS` is deleted and OpenCode
passes NO limits - no timeout, no tool-call cap. Rationale: user cancel interrupts the
execution fiber and structured concurrency takes the program and in-flight child calls down
with it; every child call is permission-gated; output truncation (32KB default) is the only
active bound. New regression test: 150 tool calls succeed with no limits configured (would
have tripped the old default 100). Package suite: 155 pass / 0 fail.
**Fix 7 - JSDoc-annotated search signatures**: `tools.$codemode.search` result signatures are
now the pretty, indented multiline form with per-field JSDoc - ported from the pre-rebuild
rune renderer in this repo's git history (`renderType(def, { pretty })`/`docTags`/`jsdoc`/
`renderObject`), adapted to the current renderer's conventions (`Array<T>`, `unknown`
fallback, existing `$defs`/`$ref` handling and empty-object `{}` collapse; the old
`Result<T>`/`returnType` machinery was deliberately not ported - payloads stay native).
Semantics: each described input/output field carries its schema `description` as a
`/** ... */` comment at the right indent (nested objects recurse deeper); constraints TS can't
express surface as JSDoc tags - `@deprecated`, `@default <json>` (unserializable defaults
skipped), `@format`, `@minItems`/`@maxItems`; `*/` inside text is neutralized to `* /`;
multiline descriptions become `*`-prefixed blocks with blank edges trimmed; undescribed,
untagged fields get no comment. Implementation: `renderSchema` in `tool.ts` grew a
`RenderContext` (`{ definitions, pretty }`), a `MAX_RENDER_DEPTH = 8` recursion ceiling plus
a `$ref` `seen` guard (the renderer previously had neither - a cyclic `$defs` would have
looped; it now degrades to the ref name/`unknown`), and try/catch totality on the public
helpers (`toTypeScript`/`jsonSchemaToTypeScript`/`inputTypeScript`/`outputTypeScript` never
throw - pathological schemas render `unknown`); each helper takes an optional trailing
`pretty = false` parameter, so existing callers are unchanged and compact output stays
byte-identical (inline `catalogLine`s and the token budget depend on it). `SearchEntry`
gained an eagerly-computed `signature` field (built once per tool at index-build time in
`toSearchEntry` - rendering is cheap and the search hot path stays allocation-free); both
ranked results and exact-path lookups serve it. Works for both tool kinds: Effect Schema
annotations (`Schema.String.annotate({ description })`) flow through the emitted JSON
Schema, and raw JSON Schema (MCP) property metadata is read directly - both covered in
`test/signature.test.ts` (12 tests) plus one strengthened adapter assertion (MCP property
description appears as JSDoc in a live search result; the tool description/catalog contains
no `/**`). README search section updated with an example. Package suite: 167 pass / 0 fail;
adapter suites: 34 + 16.
**Fix 8 - condensed instructions + round-robin catalog fairness + plural-aware search**
(user direction: the fixed instruction prose was too verbose; two discovery fixes ride
along). All in `tool-runtime.ts`; no interpreter changes.
- **Syntax section inverted**: the three dense allowlist lines (~453 estimated tokens)
are replaced by four short lines (~188) built on "models already know JavaScript; name
only what is unusual or missing": (1) standard modern JS works - functions/closures,
destructuring, template literals, loops, try/catch, spread, optional chaining, the
usual Array/String/Object/Math/JSON methods, plus Date/RegExp/Map/Set and
Promise.all/allSettled/race/resolve/reject; (2) TypeScript type annotations are
stripped before execution, decorators are not supported; (3) NOT supported (each fails
with a message naming the alternative): classes, generators, for await...of,
.then/.catch/.finally (use await with try/catch), `x instanceof Error` (caught errors
are plain `{ name, message }` objects), splice; (4) the data-boundary note (Dates ->
ISO strings; Map/Set/RegExp -> `{}`). Every claim was verified against the interpreter
before writing: probed empirically - classes/generators/for-await/.then/.catch/
.finally/`instanceof Error`/splice/decorators/BigInt/labeled statements/tagged
templates/object getters all fail with clear diagnostics; TS annotations/`as`/
interfaces/type aliases are stripped and TS **enums actually work** (transpileModule
compiles them to an IIFE the interpreter runs), hence enums deliberately unmentioned.
`supportedSyntaxMessage` (the in-diagnostic text in `codemode.ts`) is untouched.
- **Workflow/Rules deduped**: the call-by-exact-path, JSON.parse-string-results, and
return-small content now lives ONLY in the numbered Workflow steps (with their
compliance-driving justifications inline: "most tools return JSON as a string", "raw
payloads get truncated and waste context"); Rules keeps only bullets adding new
content - filter/aggregate collections in code, console.\* intermediates (logs ride
back), Promise.all parallelism, Object.keys/for...in enumeration, browse-namespace
(PARTIAL only), and the media rule compressed to one line. The no-.then/.catch
guidance moved to the Syntax not-supported line. Content upgrades: the PARTIAL search
step gained query-style guidance (`- short phrases like "list issues" work best`; a
clearly-a-query-string example, not a tool name), and the exact-path guidance is now
"call it with the result's `path` as-is (never guess segments)" / COMPLETE: "use it
as-is rather than guessing segments".
- **Fixed-prose measurements** (instructions split on `"\n## "`, catalog budget 0,
bytes/3.7 - same method as Fix 4; chars/4 in parentheses):
preamble 44 -> 44 (41 -> 41), Workflow 146 -> 187 (135 -> 171), Rules 362 -> 191
(332 -> 176), Syntax 453 -> 188 (419 -> 174); fixed prose total 1,005 -> 610 (927 -> 562),
~ 40% reduction with no behavioral content dropped. Workflow grew slightly because it
absorbed the deduped parse/return-small justifications.
- **Round-robin namespace inlining** (`discoveryPlan`): the ported stop-on-first-miss
behavior (alphabetically-late namespaces starved to "none shown" while an early
namespace inlines everything) is replaced by round-robin fairness - in each round
(namespaces alphabetical), every namespace still holding un-inlined tools attempts to
place its next-cheapest line against the shared token budget; a namespace whose next
line does not fit is done while the others keep going; stop when all are done. Every
namespace gets some representation before any namespace gets everything. Kept:
`estimate` (chars/4) budget accounting, unbudgeted namespace stub lines, per-namespace
`(N tools)`/`(N tools, K shown)`/`(N tools, none shown)` labels, COMPLETE vs PARTIAL
header, alphabetical namespace order in the output, cheapest-first within each
namespace's shown set.
- **Plural/singular search fix**: `tokenize`d terms matched one-directionally (term must
be substring of indexed text), so query "issues" missed a tool whose text only says
"issue". Now each term expands to `termForms` - the term plus naive singular variants
(trailing "es" stripped when length > 3, trailing "s" when length > 2) - and each of
the four field checks passes when ANY form matches. Weights, exact-path lookup, and
namespace scoping untouched. A true plural path match still outranks a singular-only
description match (path substring 8 + searchable 2 > description 4 + searchable 2).
- **Tests**: package instruction/structure assertions updated to the new text; new
syntax-section test (leads with "Standard modern JavaScript works", names the
verified not-supported list, keeps the data-boundary note); the budget-exhaustion
test rewritten to assert the new fairness (alpha.expensive not fitting must NOT
prevent beta.cheap from showing: PARTIAL 2 of 3, `- beta (1 tool)` fully shown); new
plural/singular test (query "issues" finds a singular-only tool; ranking still
prefers the true "issues" path match). Adapter: description assertions updated; the
large-catalog PARTIAL test now asserts `zeta_only_tool` IS shown (`- zeta (1 tool)` +
its inlined line) - it was "none shown" under starvation. README updated (budgeted
catalog paragraph -> round-robin; search paragraph -> singular variants;
instructions-structure paragraph -> new section contents). Package suite: 169 pass /
0 fail; adapter suites: 34 + 16.
**Fix 9 - prompting trims per user review of Fix 8** (user reviewed the condensed
instructions and directed further cuts):
- Default `maxInlineCatalogTokens` 4,000 -> **2,000** (user wants ~2k tokens of signatures
auto-inlined; round-robin fairness from Fix 8 spreads it across all namespaces).
- Console rule and files/images rule DROPPED from `## Rules`. Replaced by a single
`unknown`-treatment warning: "A result typed `Promise<unknown>` has no guaranteed
shape - verify what actually came back before relying on its fields." (Deliberately
does NOT suggest console.log - user review: naming it there nudges models to log AND
return the same data; the prompt stays console-neutral, neither for nor against.)
The media-stripping MECHANISM is unchanged and still tested; only the prose about it
is gone - the `[N images attached]` marker is self-explanatory in context.
- Kept as-is per user: the JSON.parse workflow step (maps to the original motivating
transcript failure; NOT copied from prior art - see section 5 note), the browse-namespace rule
(undecided), no no-fetch/ambient-authority rule added (proposed, not approved).
- Explicitly REJECTED for now: auto-parsing JSON-looking text results at the adapter
boundary ("could get weird" - type flips, program-sees vs tool-sent divergence). Logged
as a next-iteration follow-up below.
**DSL-expansion pass - interpreter-surface batch from section 4** (the deferred medium-tier JS
parity items, done as one focused pass; no public API or limit changes):
- **`instanceof` + real Error values**: the `errorConstructors` names (`Error`,
`TypeError`, `RangeError`, `SyntaxError`, `ReferenceError`, `EvalError`, `URIError`) are
bound globals (`ErrorConstructorReference`, callable with or without `new`; `typeof` ->
`"function"`). Error values stay the same plain `{ name, message }` null-prototype
objects as before - the constructor name additionally rides on a NON-ENUMERABLE symbol
key (`ErrorBrand`), which every `Object.entries`-based walk (copyIn/copyOut, spread,
JSON.stringify) is blind to, so serialization is byte-identical to the old shape and the
brand is lost on spread/boundary copies exactly like JS loses the prototype.
`caughtErrorValue` produces `{ name, message }` wrappers via `createErrorValue`, so
caught interpreter AND tool failures are `instanceof Error` and carry the `name` the
equivalent real-JS failure would have (follow-up fix, user-directed - "closest to real
JS"): `InterpreterRuntimeError` gained an `errorName` field ("Error" default) set
fluently at throw sites via `.as(name)` - `JSON.parse` failures are `"SyntaxError"` (and
now include the engine's position detail in the message; safe - derived from the
program-supplied string), invalid regex patterns/flags `"SyntaxError"`, unknown
identifiers and TDZ access `"ReferenceError"`, assignment to a constant `"TypeError"`,
a bad `normalize` form `"RangeError"`; a host Error reaching the catch path directly
keeps its own name when it is one of the standard seven. Tool failures and everything
without a specific analogue stay `"Error"` - internal class names never leak. Specific
names satisfy the specific `instanceof` (`e instanceof SyntaxError`), matching JS.
The operator is handled in `evaluateBinaryExpression`
BEFORE the data-only operand check (like `typeof`, it observes any lhs - promises and
functions included); recognized rhs: the error constructors (a specific type matches its
own brand or `Error`, never a sibling), `Date`/`RegExp`/`Map`/`Set` (sandbox classes),
`Array`, `Object` (any object/function-ish value), `Promise` (`SandboxPromise`), and
`Number`/`String`/`Boolean` (always false - no boxed values exist); anything else is a
catchable error naming the recognized constructors.
- **Array methods**: `splice` (mutating, returns the removed elements; insertions run
`rejectCircularInsertion` like push/unshift; one-arg form removes to the end, undefined
delete count removes nothing), `fill` (circular-checked value) and `copyWithin`
(host-delegated), and `keys`/`values`/`entries` returning **arrays** (the Map/Set
convention - for...of and spread work either way). The `retryableArrayMethods`
"rewrite using map/filter" hint set emptied out and was deleted with its branch; unknown
array properties still read `undefined`.
- **String methods**: `localeCompare(that)` (locale/options arguments ignored - host
default locale; the dominant use is a sort comparator), `normalize(form?)` (invalid form
-> catchable error naming the four valid forms), `trimLeft`/`trimRight` as
trimStart/trimEnd aliases.
- **Actionable regex failures**: `toHostRegex` and `constructRegExp` now show the
offending pattern (or flags) plus the engine reason (deduped "Invalid regular
expression:" prefix via `regexFailureReason`) and a shared escaping hint
(`escapeRegexHint`); flags failures list the valid flag letters; the
replaceAll/matchAll missing-`g` errors spell out the exact `/pattern/g` to write and
the single-match alternative.
- **copyIn split (the important one)**: `copyIn(value, label, preserveSandboxValues =
false)` - recursion moved to a private `copyBounded`; `boundedData` (every intra-sandbox
checkpoint: `Object.*` helpers, coercion/Array.from/join inputs, template
interpolation, expression-result checkpoints) is now `copyIn(value, label, true)`,
which passes `SandboxDate`/`SandboxRegExp`/`SandboxMap`/`SandboxSet` through **by
reference as leaves** (contents not walked - Map/Set members are validated at their
mutation sites) while keeping the depth (`MAX_VALUE_DEPTH`), circularity,
plain-objects-only, blocked-property, and data-only checks; un-awaited promises keep
the await-hinting rejection in BOTH modes (deliberate - JS-parity pass-through was
considered and skipped to preserve the nudge). The HOST boundary (final result,
tool-call arguments, `JSON.stringify`, tool-result intake) uses the default mode and
still serializes JSON forms (Date -> ISO, RegExp/Map/Set -> `{}`); host instances met on
the preserving path are defensively wrapped into sandbox equivalents. Ripple: the
`Object.*` helpers treat sandbox values as empty objects (`Object.keys(map)` -> `[]`,
assign sources contribute nothing, hasOwn -> false - JS has no own enumerable props
there), so interpreter internals (`.map`/`.time`/`.regex`) can never leak; the
template-literal sandbox carve-out collapsed into `boundedData`. Object/array spread
already preserved instances (reference copies, no checkpoint) - now tested.
- **Console formatting**: `formatConsoleArgument` is total and deep
(`formatConsoleValue`): numbers render via `String` (`NaN`/`Infinity`/`-Infinity`
literally - never the JSON `null`; finite numbers match their JSON form), nested
strings are JSON-quoted, sandbox values keep their friendly forms at ANY depth (ISO
date, `/regex/flags`, `Map(n) [...]`, `Set(n) [...]`), opaque references become
in-place `[CodeMode reference]` markers instead of collapsing the whole argument,
cycles render `[Circular]` (reachable via Map/Set members, which mutation never
checkpoints), and depth beyond `MAX_CONSOLE_DEPTH = 32` (fixed constant, not a knob)
degrades to `...` - console can no longer fail a program. `console.table` guards with
`containsOpaqueReference` (sandbox cells render, e.g. ISO dates) and its row/cell
walkers treat sandbox values as scalar cells.
- **Prose**: the instructions Syntax not-supported line dropped its `instanceof
Error`/splice mentions (nothing else reworded); README updated (checkpoint
preservation vs boundary serialization, error values/`instanceof`, new array/string
methods, regex-failure behavior); `supportedSyntaxMessage` left untouched (it lists
supported syntax, was already non-exhaustive, and stays accurate).
- **Tests**: package suite 169 -> 209 (parity: Error/instanceof + real-JS error-name
coverage, splice/fill/copyWithin/keys/values/entries, localeCompare/normalize/trim-alias
describes; stdlib: checkpoint survival incl. tool-arg boundary pinning, stdlib
`instanceof`, regex-message assertions; codemode: NaN/Infinity + nested/cyclic console
rendering, table cells, caught-tool-failure `instanceof`); adapter suites unchanged
(34 + 16, green); both packages `tsgo --noEmit` clean.
**Truncation layering - CodeMode truncation off in OpenCode** (user direction; resolves the
section 4 outer-truncation item the OPPOSITE way from "kill the outer one"):
- `maxOutputBytes` lost its 32,000 default and now behaves exactly like the other two
limits: absent = no truncation. All three limits are uniformly no-default - budgets are
host policy. `ResolvedExecutionLimits.maxOutputBytes` is `number | undefined`;
`boundOutput` only runs when the host set the limit. Explicit values validate as before
(safe integer >= 0).
- OpenCode continues to pass NO limits, which now also means no CodeMode truncation.
`execute` is a normal `Tool.define` tool, so OpenCode's native tool-output truncation
applies with no special-casing - verified by tracing `wrap()` (`tool.ts:130-144`,
50KB/2000-line thresholds in `truncate.ts`, full output dumped to a file under
`tool-output/`): the `metadata.truncated` self-truncation exemption never fires for
`execute` (its metadata never sets that key). One truncation layer, the host's - and it
is the richer one (file dump + explore/grep hint vs an inline marker).
- Hosts without their own output bounding set `maxOutputBytes` explicitly; README table
and prose updated, adapter comment rewritten. Tests: codemode +1 (absent limit -> 100KB
value + 50KB log line pass through unbounded, `truncated` undefined); the adapter test
that relied on the old default now asserts the oversized result reaches the shared
wrapper un-truncated. Suites: 210 + 50, tsgo clean both.
**Docs polish** (post-API-review): stale `DiscoveryOptions` JSDoc fixed (claimed default
4,000 and alphabetical cheapest-first - now 2,000 and round-robin, matching Fix 8/9 reality)
and the README's incorrect "`effect` as a peer dependency" line corrected (`effect` is a
regular dependency; hosts depend on it themselves because the API surface is Effect-typed).
**Registry promotion + permission-aware catalog** (the "promote to a proper tool service"
restructure; fixes the section 4 permission-advertising bug):
- **The adapter moved** `src/session/code-mode.ts` -> `src/tool/code-mode.ts` and is now a
registry-resident tool service on the TaskTool precedent: `CodeModeTool =
Tool.define(CODE_MODE_TOOL, ...)` whose init depends on `MCP.Service`, `Agent.Service`,
and `Session.Service`. It is yielded in `ToolRegistry.layer`, gated into `builtin` by
`flags.experimentalCodeMode` (like the lsp/plan experiments), and `MCP.node` joined the
registry's `node.deps` (`MCP.node` has no ToolRegistry dependency, so no cycle). The
session-level special-casing in `session/tools.ts` (ad-hoc `SessionCodeMode.define` +
append) is deleted; the early return that suppresses raw per-MCP registration when the
flag is on stays session-side, keyed on the same flag+tool-count condition.
- **Enablement** lives in `ToolRegistry.tools()` next to the WebSearchTool check: the MCP
tool count is consulted once (an Effect) before the synchronous filter, and code mode
passes the predicate iff `flags.experimentalCodeMode` && count > 0.
- **Description split on the `describeTask` precedent**: the tool's static base
description is a two-line summary; `describeCodeMode(agent)` in `registry.tools()`
appends the full CodeMode instructions (workflow/rules/syntax + grouped catalog,
`catalogInstructions` in the adapter) at the same composition point as task - so
`plugin.trigger("tool.definition")` sees the base description first.
- **Permission-aware catalog + dispatch** (the bug fix): the visibility predicate from
`llm/request.ts` `resolveTools` is hoisted to `Permission.visibleTools(tools, ruleset)`
(a record filter over `Permission.disabled` - only a hard `deny` with pattern `"*"`
hides a tool; ask-level rules stay fully visible and prompt at call time) and
`resolveTools` now uses it, so the two paths cannot drift. `describeCodeMode` filters
with the merged agent+session ruleset that `SessionTools.resolve` passes into the
registry before building the catalog/search index; `execute` rebuilds the runtime per
execution from a fresh, filtered `mcp.tools()` snapshot using the same merged ruleset
(`Agent.get(ctx.agent)` + `Session.get(ctx.sessionID)`, matching the merge
`SessionTools.context` wires into `ctx.ask`) - a denied tool is not dispatchable
even if the model guesses its name and yields the normal unknown-tool diagnostic.
Documented gap (out of scope by design): per-message `user.tools[key] === false` arrives
at request-prep after descriptions are built and has no child-call equivalent.
- **Preserved behavior**: cancellation race + pre-aborted-signal guard, `toSandboxResult`
unwrap order, attachment accumulation, `CODE_MODE_TOOL` at all title sites, no execution
limits (native truncation only), `displayInput`, per-child `ctx.ask` gating (now wired
through `Tool.Context` exactly like every registry tool).
- **Explicit non-goal**: memoizing the catalog builder keyed on (ToolsChanged generation,
permission ruleset) was considered and deliberately skipped - the per-turn rebuild is
cheap (grouping + string rendering); revisit only if profiling shows it matters.
- **Tests**: the two adapter suites moved to `test/tool/{code-mode,code-mode-integration}
.test.ts` (mocked `MCP.Service`/`Agent.Service`/`Session.Service` replacing the direct
`define(...)` construction; description assertions target `catalogInstructions`, the
registry's composition input) and gained permission coverage: deny excluded from
catalog/search, ask-level stays visible and callable, denied tool undispatchable
(unknown-tool diagnostic), `Permission.visibleTools` semantics. `test/tool/
registry.test.ts` gained four registry-level tests: registered with flag+MCP tools,
excluded without MCP tools, excluded with flag off, and deny/ask catalog filtering
through `registry.tools()`. Suites: 43 + 16 adapter tests, 16 registry tests, all green.
**Shared MCP invocation middle (`McpInvoke.invoke`)** (closes the section 4 "plugin hooks skip
child calls" gap):
- `packages/opencode/src/mcp/invoke.ts` extracts the duplicated "invoke an MCP tool"
middle into one shared `McpInvoke.invoke(input)`: plugin `tool.execute.before` hook ->
permission ask (`{ permission: key, patterns: ["*"], always: ["*"] }` via the caller's
`ctx.ask`) -> dispatch through the ai-sdk tool's execute inside the `Tool.execute`
tracing span (`tool.name`/`tool.call_id`/`session.id`/`message.id` attributes) ->
plugin `tool.execute.after` hook. It returns the RAW result the ai-sdk execute
resolved with; each caller keeps its own shaping edge - the legacy per-MCP loop in
`SessionTools.resolve` applies its existing model-facing shaping/truncation, code
mode applies `toSandboxResult`. It lives under `src/mcp/` because both callers
already depend on MCP and the function is about invoking an MCP-backed ai-sdk tool,
not about sessions or code mode.
- **After-hook payload**: fired inside `McpInvoke.invoke` with the raw MCP result -
which is exactly what the legacy loop always passed (the raw `CallToolResult`, not
the shaped `{title, output, metadata}`), so legacy behavior is preserved bit-for-bit
and the hook payload cannot drift between callers. No callback/edge-firing design
was needed.
- **Synthetic child callID**: code-mode child calls pass `${parentCallID}/${n}` as the
hook/span callID (`parentCallID` = the `execute` call's `ctx.callID`, falling back to
the entry key; `n` = per-execution counter starting at 1, shared across all child
calls in one program). callID is an opaque string - nothing parses it. The ai-sdk
`toolCallId` (`options.toolCallId`) stays each caller's existing value
(`ctx.callID ?? entry.key` for code mode).
- **Child-scoped hook failures**: `CodeModeTool` (which now also yields
`Plugin.Service`) wraps the whole child call - hooks, ask, dispatch - in
`toCatchable` (the generalization of the old `askPermission` catchCause), so a plugin
hook failure fails ONLY that child call as a catchable in-program `toolError`; other
calls in the same program keep running and interruption still propagates as
interruption. Legacy semantics unchanged: a hook failure fails the tool call.
- **Tests**: `test/tool/code-mode.test.ts` +2 (child calls fire before/after with the
MCP key and `parent/1`, `parent/2` ids, after hook carries the raw MCP result; a
failing before hook is caught in-program, gates dispatch, and leaves the outer
execute ok) - both code-mode harnesses gained a `Plugin.Service` mock (pass-through
trigger by default, overridable). New `test/session/tools.test.ts` (3 tests) pins
`SessionTools.resolve` at the real-registry seam (LayerNode.compile, fake MCP layer):
flag on + MCP tools -> `execute` present, raw MCP keys suppressed; flag off -> raw
keys present, `execute` absent; and the legacy raw-MCP execute fires before/after
hooks keyed by the ai-sdk toolCallId with the raw result payload. Suites: adapter
45 + 16, session/tool/permission all green; this package untouched (211 pass).
**Signature rendering + compound-assignment parity fixes** (externally reported, both
verified real with failing tests before fixing):
- **Non-identifier property names in rendered signatures** (`src/tool.ts`): `renderSchema`
emitted raw property names, so schema properties like `foo-bar`/`@type`/`x.y`/`123`
rendered invalid TypeScript (`{ foo-bar?: string }`). Fixed with a `renderKey` helper -
bare identifiers stay bare, everything else is `JSON.stringify`-quoted - applied in the
single `field` closure both the compact and pretty renderings share. The
`identifierSegment` regex now lives in `tool.ts` (exported) and `tool-runtime.ts`'s
bracket-notation `toolExpression` imports it: one source of truth for "is this a bare
identifier" across object keys and tool paths. Tests: `signature.test.ts` +4 (compact,
pretty with JSDoc on a quoted key, JSON Schema input+output, Effect Schema struct).
- **Numeric schema unions keep their real alternatives** (`src/tool.ts`): the old
`anyOf`/`oneOf` renderer collapsed any union containing `{ type: "number" }` to just
`number`, dropping real JSON Schema alternatives (`string | number`, `number | null`,
etc.). The collapse is now restricted to Effect's number-schema artifact
(`number | "NaN" | "Infinity" | "-Infinity"`, emitted as single-value string enums),
while raw JSON Schema unions render every branch. Tests: `signature.test.ts` +3.
- **Compound assignment now matches binary-operator semantics** (`src/codemode.ts`):
`applyCompoundAssignment` did raw JS ops on interpreter wrapper objects, so `x += y`
diverged from `x = x + y` (sandbox Date `d += 1` produced `"[object Object]1"`;
`d -= 400` gave `NaN` instead of epoch arithmetic). The operator table + coercion moved
verbatim out of `evaluateBinaryExpression` into a shared `applyBinaryOperator`;
compound assignment validates against a `compoundOperators` set (`+=` ... `>>>=`) and
dispatches through it (`operator.slice(0, -1)`). Logical assignments (`&&=`/`||=`/`??=`)
keep their separate short-circuit path (`evaluateLogicalAssignment`), and both
assignment call sites still wrap results in `boundedData`. Deliberate side effect:
compound assignment now rejects opaque references, consistent with binary operators.
Tests: `parity.test.ts` +5 (Date `+=` concat parity, Date `-=`/`/=` epoch parity,
string `+=` object/array, member-target compound, 13-case operator sweep vs real JS).
Package suite: 220 pass.
---
## 4. Remaining work (detailed TODO)
### Next DSL-expansion pass (done - see the DSL-expansion pass entry in section 3)
Batch these together - per user direction: important, but deliberately deferred to one
focused interpreter-surface pass rather than picked off piecemeal.
- [x] Medium-tier JS parity items deferred from the original audit: caught errors are plain
`{ name, message }` objects, not `instanceof Error` (and `Error` isn't a value -
`x instanceof Error` is unsupported syntax); `splice` (still a
"rewrite using map/filter" hint) and array `entries()/keys()/values()`;
`localeCompare`/`normalize`/`trimLeft`/`trimRight`; friendlier regex-y error messages.
(`fill`/`copyWithin` - which the hint set also covered - were implemented too since
they are trivial host delegations, so the hint set is gone entirely.)
- [x] `Date`/`Map`/`Set`/`RegExp` values passing through `Object.*` helpers and coercion
checkpoints take their JSON forms (e.g. `Object.values({ d: date })` yields the ISO
string, not the Date - calling `.getTime()` on it then fails). Currently deliberate
(documented in README) but flagged as important: fix in this pass by letting sandbox
values survive `Object.*`/spread checkpoints instead of JSON-serializing them.
- [x] `console.log(NaN)` prints `"null"` (goes through the boundary chokepoint) - could
special-case number formatting in `formatConsoleArgument`.
- [x] Sandbox values nested inside logged containers print `[CodeMode reference]`
(`console.log({ m: map })`) - could deep-format instead.
### Next iteration: text-result handling (deliberate follow-up, user-directed)
- [ ] Revisit how MCP text results reach the program. Today: `structuredContent` when the
server sends it, else joined text as a plain string (the program JSON.parses it,
guided by a workflow step). Considered and deferred: (a) conservative boundary
auto-parse (text starting with `{`/`[` that parses cleanly becomes an object) -
rejected for now as potentially confusing (type flips; program sees something other
than what the tool sent); (b) raw-envelope passthrough with the envelope shape
stamped into every output schema - rejected (more digging per call, verbose
signatures). Result quality is dominated by whether servers declare output schemas;
revisit once real usage shows which failure modes matter.
### Next iteration: stdlib surface (prioritized)
Current instructions say "usual Array/String/Object/Math/JSON methods," but the interpreter is
intentionally a subset. Keep CodeMode focused on orchestration and data shaping, not a full host
runtime, but close the high-friction gaps models are likely to reach for.
- [ ] **P0: tighten wording first** - change instructions/docs to say "common stdlib subset"
until the surface is broader. This avoids misleading the model into assuming every JS
helper exists.
- [ ] **P1: URL parsing helpers** - add `URL` and `URLSearchParams`. These are high-value for
tool orchestration (query strings, ids in URLs, API links), deterministic, and do not add
ambient host authority.
- [ ] **P2: Math completion** - add the missing standard deterministic `Math` methods
(`sin`/`cos`/`tan`, inverse/hyperbolic variants, `atan2`, `log1p`, `expm1`, `imul`,
`fround`, `clz32`, etc.). Decide explicitly on `Math.random`: likely acceptable because
`Date.now()` is already exposed, but document the nondeterminism if enabled.
- [ ] **P3: base64 helpers** - add string-only `atob`/`btoa` equivalents. Useful for API/tool
payload cleanup and does not require opening the broader binary boundary.
- [ ] **P4: small crypto helper** - consider `crypto.randomUUID()` only, not full `crypto`.
UUID generation is a common orchestration need; broader crypto can wait until there is a
concrete use case and a clear capability boundary.
- [ ] **P5: text/binary primitives** - consider `TextEncoder`/`TextDecoder` first, then
`ArrayBuffer`/typed arrays/`DataView`/`Blob`/`File` only with an explicit boundary design
(serialization, size limits, and how values cross tool args/results). This is reasonable
but lower priority than URL/base64 because CodeMode is still plain-data oriented.
- [ ] **P6: date/formatting conveniences** - consider `Date` setters and common formatting
helpers (`toUTCString`, maybe `Intl` later). Lower priority; most orchestration can use
existing getters, `Date.parse`, `Date.UTC`, and ISO strings.
- [ ] **P7: environment/config access** - do not expose raw `process.env` as a global ambient
authority. If this becomes useful, add an explicit host-provided/whitelisted capability
(for example a small env/config tool or injected read-only object) so secrets are not
accidentally exposed to arbitrary CodeMode programs.
Explicit non-goals for now: `structuredClone`, `WeakMap`/`WeakSet`, and timers
(`setTimeout`/`setInterval`/`queueMicrotask`). They do not materially improve the current tool
orchestration use case.
### Wiring-review findings (subagent code review of the OpenCode integration, triaged)
Pre-PR fixes (user-approved cut):
- [x] **Cancellation does not interrupt the interpreter** - the no-limits rationale claimed
"user cancel interrupts the execution fiber," but `tools.ts` runs tools via
`run.promise` -> `Effect.runPromise` (`effect/bridge.ts:64-66`) with NO abort wiring;
on cancel the ai-sdk abandons the promise, child MCP calls abort (they hold
`ctx.abort`) but the interpreter fiber spun on - `while(true){}` or a try/catch
loop was uncancellable with no timeout backstop. Verified by hand, not just the
reviewer. FIXED in the adapter: `Effect.raceFirst(runtime.execute(code), cancelled)`
where `cancelled` is an `Effect.callback` abort-signal watcher (listener removed on
interruption) resuming with an `ok: false` "Execution cancelled." result - the abort
winning the race interrupts the execution fiber (interpreter auto-yield makes busy
loops preemptible, same mechanism as timeoutMs) and returning a value keeps the
runner's post-abort `completeToolCall` bookkeeping on its normal path. A pre-aborted
signal short-circuits at entry before the program starts (racing alone still lets
the loser run its first steps). Tests: +2 adapter (child call triggers abort
deterministically then the program enters `while(true){}` - would hang if
interruption broke; pre-aborted signal runs nothing). Adapter suite 34 -> 36.
(Wiring abort->interrupt into the shared `tools.ts` runner for ALL tools remains a
worthwhile separate change.)
- [x] **Permission-denied/disabled MCP tools are still advertised in the catalog** - the
non-code-mode path filters them from the model's view (`llm/request.ts:208-213`);
code mode builds the catalog from all of `mcp.tools()`, so the model is invited to
call tools that can only fail at permission time, and per-message `tools[key]=false`
disabling has no child-call equivalent. Fix: filter the catalog with the same
ruleset.
DONE (see the "Registry promotion + permission-aware catalog" entry in section 3): the
shared `Permission.visibleTools` predicate filters both the appended
catalog/description (`describeCodeMode`, agent ruleset) and the execute-time tool
tree (merged agent+session ruleset) - hard-denied tools are neither advertised nor
dispatchable. Ask-level tools stay visible/callable. Per-message
`tools[key] === false` remains a documented gap by design (it arrives at
request-prep, after descriptions are built).
- [x] Style: `code-mode.ts` is the only `src/session` sibling without the
`export * as ... from "./..."` self-reexport footer, forcing a star import at
`tools.ts:26` (AGENTS.md violation). Add footer + import the projection.
DONE: added `export * as SessionCodeMode from "./code-mode"` footer; `tools.ts` now
imports the named `SessionCodeMode` projection.
- [x] Trivial: latent `groupByServer` fallback bug - `key.slice(0, key.indexOf("_"))` is
`slice(0, -1)` when no underscore (unreachable today; guard or drop); dead
`CODE_MODE_TOOL` export (integration points hardcode `"execute"` - use it or inline
it).
DONE: no-underscore key now falls back to the whole key (test pins it); the four
`title: "execute"` sites in `code-mode.ts` now reference `CODE_MODE_TOOL`.
Post-MVP (logged, not blocking an experimental flag):
- [x] **Plugin `tool.execute.before/after` hooks skip child calls** - legacy MCP
registration fires them per tool (`tools.ts:419-441`); under code mode only the
outer `execute` fires them, so auditing/intercepting plugins silently lose MCP
coverage when the flag flips.
DONE (see the "Shared MCP invocation middle" entry in section 3): both paths now run
`McpInvoke.invoke` (`src/mcp/invoke.ts`) - hooks AND the `Tool.execute` span fire
for child calls with synthetic `${parentCallID}/${n}` callIDs; hook failures are
child-scoped, catchable in-program errors.
- [x] Description/preview rebuilt every assistant turn - `registry.tools()` re-runs
`groupByServer` + a throwaway `CodeMode.make(...).instructions()` per turn
(`describeCodeMode`). DECIDED as an explicit non-goal: memoizing the catalog
builder keyed on (ToolsChanged generation, permission ruleset) was considered and
deliberately skipped - the per-turn rebuild is cheap (grouping + string
rendering); revisit only if profiling shows it matters. A second `CodeMode.make`
per execution is inherent (description precedes execution).
- [ ] Child permission rejection round-trips through the defect channel - `ctx.ask`
defect (`tools.ts:90` orDie) recovered via `catchCause` + `Cause.squash`
(`code-mode.ts:238-245`). Works, interrupts preserved, but fragile coupling;
exposing the typed rejection on `Tool.Context.ask` would be cleaner.
- [ ] No collision guard on the `execute` tool id (a plugin/custom tool named `execute`
is silently shadowed; a log line would do).
- [ ] Style nits: triple-nested `yield*` in `tools.ts:101-107` argument position (bind
first, like neighbors); single-use micro-helpers (`toJsonSchema` is a bare cast);
comment density far above session-neighbor norm; adapter tests use raw
`Effect.runPromise` + hand-built layers with `as any` instead of the
`testEffect`/`LayerNode.compile` fixture pattern (`test/tool/grep.test.ts:25-31`)
and star-import `Truncate`.
- [ ] Reviewer observation worth keeping: MCP server instructions (`sys.mcp`,
`session/system.ts:110-126`) still inject prose referencing server-native tool
names that are no longer directly callable under code mode.
### Backlog / loose ends (non-blocking, any order)
- [ ] `evaluateUpdateExpression` (`++`/`--`) still uses raw `Number(current)`, so `d++` on a
sandbox Date yields `NaN` where `d += 1` now uses epoch semantics (and real JS `d++`
would give epoch+0 numeric). Pre-existing, out of scope of the compound-assignment
parity fix; route it through `applyBinaryOperator` if it ever matters.
- [ ] Media-only marker could name what it attached when MCP provides names: `image`/`audio`
blocks carry no filename (mime + data only) so the generic
`[N images attached to the result]` stays, but `resource`/`resource_link` blocks have
URIs/names we could surface, e.g. `[2 files attached: chart.png, data.csv]`. Minor.
- [x] Truncation layering decided (user direction): the OPPOSITE of killing the outer layer -
CodeMode truncation off in OpenCode (`maxOutputBytes` lost its default; absent = no
truncation, uniform with the other two limits), native tool-output truncation is the
single active layer (verified: `execute` flows through `tool.ts` `wrap()` like any
normal tool, no exemption). See the section 3 entry.
- [x] Flaky wall-clock assertion removed from `test/promise.test.ts`: the parallelism test
now relies solely on the deterministic `trace.maxActive > 1` counter (which proves
true temporal overlap). The timeout tests were never flaky - 100ms timeout vs 60s
tool sleeps (600x margin) with counter-based assertions.
- [ ] Attachment propagation believed correct but unverified end-to-end at the OpenCode
wiring layer (codemode strips -> `Tool.ExecuteResult.attachments` -> processor
normalizes -> `FilePart`s visible to the model). Code-reviewed as sound; confirm with
one interactive session (an image-returning MCP tool) when convenient. Same session
can eyeball TUI child-call rendering via `metadata.toolCalls`.
- [x] Commit hygiene: all work committed and pushed on `codemode-v2` as six commits, in
generic-package + OpenCode-integration pairs (waves 0-5; Fixes 4-9; DSL pass +
error names + truncation layering). Future work: commit only when explicitly asked;
push with `--no-verify` per repo convention. The scratch `.opencode/opencode.jsonc`
stays uncommitted.
- [ ] MVP scope decided (user direction): the interactive e2e eyeball is NOT required -
remaining pre-PR work is essentially just opening the PR. Attachment-propagation
verification (below) stays parked as post-MVP.
---
## 5. Context and gotchas for whoever picks this up
- **Motivating failure (why forgiving semantics + prompting matter):** in a real transcript,
the model wrote `me.result?.login ?? me.result` where the tool result was a JSON _string_ -
the old strict interpreter threw (`String property 'login' is not available`); then the
model returned a raw 105KB payload, which native truncation dumped to a file, costing a
subagent round-trip to extract one number. Interpreter forgiveness stops the crashes;
Wave 4 prompting stops the payload dumping. Both are needed.
- Realistically **all MCP tools render `Promise<unknown>`** (no outputSchema), so the
instructions prose is the only lever for result-shape behavior in the dominant case.
- **`copyIn` has two roles, split by a mode flag** (DSL-expansion pass): host<->sandbox
boundary (default mode - final result, tool arguments, `JSON.stringify`, tool-result
intake; sandbox value types serialize to JSON forms) AND intra-sandbox data checkpoint
(`boundedData` = `copyIn(value, label, true)` - sandbox value instances pass through by
reference as leaves, everything else keeps the same plain-data validation). If you add a
new value type, follow the Wave 1b-i pattern: class in `values.ts`, opaque-by-default via
`isRuntimeReference`, explicit carve-outs, JSON form in `copyIn`'s boundary mode plus
pass-through in its preserving mode, console formatting (`formatConsoleValue`), tests -
and make sure the `Object.*` helpers treat it as an empty object so class fields never
leak.
- The interpreter throws synchronously inside `Effect.gen`/`Effect.sync` freely; everything is
normalized by `catchCause` -> `normalizeError` into `Diagnostic` data. Program failures are
**data, never Effect failures**; only interruption propagates.
- `parseProgram` wraps source in `async function __codemode__() { ... }`, transpiles TS, then
slices between the first `{` and last `}` - line/col diagnostics are offset accordingly
(`sourceLocation`). Don't inject prologue code; it breaks the offsets.
- OpenCode wraps every tool's output with auto-truncation (`Tool.define` wrapper,
`truncate.output`, 2000 lines / 50KB, saves full output to disk and appends a hint) unless
`metadata.truncated` is set. The `execute` tool currently rides that for free.
- Effect version: both repos pin `effect@4.0.0-beta.83` via bun catalogs. This package uses
v4-only APIs (`Schema.Decoder`, `Schema.toJsonSchemaDocument`, `Context.Service`,
`Cause.hasInterruptsOnly`, `Effect.timeoutOrElse`). The effect-smol checkout referenced in
the workspace is the implementation source of truth for v4 behavior questions.
- File map (this package): `src/codemode.ts` - types/limits/parser/Interpreter/execute/make;
`src/tool-runtime.ts` - tool tree, `copyIn`/`copyOut`, search/discovery, invoke path;
`src/tool.ts` - `Tool.make` + JSON-Schema->TS rendering; `src/values.ts` - sandbox value
types; `src/tool-error.ts` - `ToolError`; tests in `test/{codemode,parity,stdlib}.test.ts`.
- OpenCode file map (integration points): `src/tool/code-mode.ts` (the adapter, now a
registry tool service - `CodeModeTool` + `catalogInstructions`; formerly
`src/session/code-mode.ts`); `src/tool/registry.ts` (`describeCodeMode`, enablement in
`tools()`, `MCP.node` dep); `src/session/tools.ts` (raw-MCP-registration suppression
when the flag is on); `src/permission/index.ts` (`Permission.visibleTools`, the shared
visibility predicate, also used by `src/session/llm/request.ts` `resolveTools`);
`src/mcp/index.ts` (`MCP.tools()`/`MCP.defs()`); `src/mcp/catalog.ts` (`convertTool`,
`server_tool` naming); `src/tool/tool.ts` (`ExecuteResult.attachments`, truncation
wrapper); `src/session/message-v2.ts` (attachments -> vision);
`packages/tui/src/routes/session/index.tsx` (`Execute` progress component);
`src/effect/runtime-flags.ts` (feature flag).
-26
View File
@@ -1,26 +0,0 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/codemode",
"version": "0.0.1",
"description": "Effect-native confined code execution over schema-described tools",
"private": true,
"type": "module",
"license": "MIT",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"typecheck": "tsgo --noEmit",
"test": "bun test"
},
"dependencies": {
"acorn": "8.15.0",
"effect": "catalog:",
"typescript": "catalog:"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:"
}
}
-4126
View File
@@ -1,4126 +0,0 @@
import { parse } from "acorn"
import { Cause, Effect, Exit, Fiber, Schema, Semaphore } from "effect"
import { DiagnosticCategory, ModuleKind, ScriptTarget, flattenDiagnosticMessageText, transpileModule } from "typescript"
import {
copyIn,
copyOut,
isBlockedMember,
ToolReference,
ToolRuntime,
ToolRuntimeError,
type HostTools,
type SafeObject,
type ToolCall,
type ToolDescription,
type Services,
} from "./tool-runtime.js"
import type { Definition } from "./tool.js"
import { ToolError } from "./tool-error.js"
import { isSandboxValue, SandboxDate, SandboxMap, SandboxPromise, SandboxRegExp, SandboxSet } from "./values.js"
/** A tool call admitted during an execution. */
export type { ToolCall, ToolCallStarted, ToolDescription } from "./tool-runtime.js"
export { ToolError, toolError } from "./tool-error.js"
/** Resource budgets enforced independently during each CodeMode program execution. */
export type ExecutionLimits = {
/** Maximum wall-clock execution time in milliseconds. No default: absent means no timeout. */
readonly timeoutMs?: number
/** Maximum number of tool calls admitted by the runtime. No default: absent means unlimited. */
readonly maxToolCalls?: number
/**
* Maximum UTF-8 bytes of model-facing output: the serialized result value plus captured
* logs. Excess output is truncated with an explanatory marker instead of failing. No
* default: absent means no truncation (for hosts with their own output bounding).
*/
readonly maxOutputBytes?: number
}
/** Controls how much of the tool catalog is inlined in agent instructions. */
export type DiscoveryOptions = {
/**
* Estimated-token budget (chars/4, default 2000) for inlined full tool signatures in agent
* instructions. Signatures that fit are inlined round-robin across namespaces; every
* namespace is always listed with its tool count regardless of budget, and
* `tools.$codemode.search` is always registered.
*/
readonly maxInlineCatalogTokens?: number
}
type ToolTree<R = never> = {
readonly [name: string]: Definition<R> | ToolTree<R>
}
type ResolvedExecutionLimits = {
/** Undefined means no timeout. */
readonly timeoutMs: number | undefined
/** Undefined means unlimited tool calls. */
readonly maxToolCalls: number | undefined
/** Undefined means no output truncation. */
readonly maxOutputBytes: number | undefined
}
/** Options for one CodeMode execution. */
export type ExecuteOptions<Tools extends Record<string, unknown> = {}> = {
/** Source for one program in the supported JavaScript subset. */
code: string
/** Explicit tool tree exposed to the program as `tools`. */
tools?: Tools & ToolTree<Services<Tools>>
/** Per-execution overrides for the default resource limits. */
limits?: ExecutionLimits
/** Observes decoded tool input immediately before tool execution. */
onToolCallStart?: (call: ToolRuntime.ToolCallStarted) => Effect.Effect<void, never, Services<Tools>>
/** Observes each admitted tool call as it settles, with outcome and duration. */
onToolCallEnd?: (call: ToolRuntime.ToolCallEnded) => Effect.Effect<void, never, Services<Tools>>
}
/** A normalized program diagnostic safe to return across an agent tool boundary. */
export type Diagnostic = {
readonly kind: DiagnosticKind
readonly message: string
readonly location?: { readonly line: number; readonly column: number }
readonly suggestions?: ReadonlyArray<string>
}
/** A JSON value that can cross the confined interpreter boundary. */
export type DataValue = Schema.Json
/** Successful execution after the result has crossed the plain-data boundary. */
export type ExecuteSuccess = {
readonly ok: true
readonly value: DataValue
readonly logs?: ReadonlyArray<string>
/** Present when the value or logs were truncated to fit `maxOutputBytes`. */
readonly truncated?: boolean
readonly toolCalls: ReadonlyArray<ToolCall>
}
/** Failed execution with calls admitted before the diagnostic was produced. */
export type ExecuteFailure = {
readonly ok: false
readonly error: Diagnostic
readonly logs?: ReadonlyArray<string>
/** Present when the logs were truncated to fit `maxOutputBytes`. */
readonly truncated?: boolean
readonly toolCalls: ReadonlyArray<ToolCall>
}
/** Result of executing a CodeMode program. Program failures are data, not Effect failures. */
export type ExecuteResult = ExecuteSuccess | ExecuteFailure
/** Reusable CodeMode configuration shared by `execute` and `agentTool`. */
export type CodeModeOptions<Tools extends Record<string, unknown> = {}> = Omit<ExecuteOptions<Tools>, "code"> & {
/** Progressive-disclosure configuration for the agent-facing tool catalog. */
readonly discovery?: DiscoveryOptions
}
/** Input schema for the single agent-facing tool produced by `runtime.agentTool()`. */
export const ExecuteInputSchema = Schema.Struct({ code: Schema.String })
const DiagnosticKindSchema = Schema.Literals([
"ParseError",
"UnsupportedSyntax",
"UnknownTool",
"InvalidToolInput",
"InvalidToolOutput",
"InvalidDataValue",
"ToolCallLimitExceeded",
"TimeoutExceeded",
"ToolFailure",
"ExecutionFailure",
])
/** Structured success or diagnostic result schema returned by CodeMode execution. */
export const ExecuteResultSchema = Schema.Union([
Schema.Struct({
ok: Schema.Literal(true),
value: Schema.Json,
logs: Schema.optionalKey(Schema.Array(Schema.String)),
truncated: Schema.optionalKey(Schema.Boolean),
toolCalls: Schema.Array(Schema.Struct({ name: Schema.String })),
}),
Schema.Struct({
ok: Schema.Literal(false),
error: Schema.Struct({
kind: DiagnosticKindSchema,
message: Schema.String,
location: Schema.optionalKey(Schema.Struct({ line: Schema.Number, column: Schema.Number })),
suggestions: Schema.optionalKey(Schema.Array(Schema.String)),
}),
logs: Schema.optionalKey(Schema.Array(Schema.String)),
truncated: Schema.optionalKey(Schema.Boolean),
toolCalls: Schema.Array(Schema.Struct({ name: Schema.String })),
}),
])
/** Agent-facing projection of a configured CodeMode runtime. */
export type AgentToolDefinition<R = never> = {
readonly name: "code"
readonly description: string
readonly input: typeof ExecuteInputSchema
readonly output: typeof ExecuteResultSchema
readonly execute: (input: { readonly code: string }) => Effect.Effect<ExecuteResult, never, R>
}
/** Reusable confined runtime over one explicit tool tree. */
export type CodeModeRuntime<R = never> = {
/** Lists schema-described tool paths provided by the host. */
readonly catalog: () => ReadonlyArray<ToolDescription>
/** Builds model-facing syntax guidance and visible tool signatures. */
readonly instructions: () => string
/** Projects the configured runtime as one agent-facing `code` tool. */
readonly agentTool: () => AgentToolDefinition<R>
/** Executes a program using this runtime's configured host tools. */
readonly execute: (code: string) => Effect.Effect<ExecuteResult, never, R>
}
type SourcePosition = {
line: number
column: number
}
type SourceLocation = {
start: SourcePosition
end: SourcePosition
}
type AstNode = {
type: string
loc?: SourceLocation
[key: string]: unknown
}
type ProgramNode = AstNode & {
type: "Program"
body: Array<AstNode>
}
type Binding = {
mutable: boolean
value: unknown
// Absent means initialized. `false` marks a parameter binding seeded into its scope but not
// yet bound, so a default that forward-references a later parameter sees a TDZ error (as in JS)
// rather than silently resolving to an outer binding of the same name.
initialized?: boolean
}
type StatementResult =
| { kind: "none" }
| { kind: "value"; value: unknown }
| { kind: "return"; value: unknown }
| { kind: "break" }
| { kind: "continue" }
type MemberReference = {
target: SafeObject | Array<unknown>
key: string | number
}
class CodeModeFunction {
constructor(
readonly parameters: ReadonlyArray<AstNode>,
readonly body: AstNode,
readonly capturedScopes: ReadonlyArray<Map<string, Binding>>,
) {}
}
class IntrinsicReference {
constructor(
readonly receiver: unknown,
readonly name: string,
) {}
}
class ComputedValue {
constructor(readonly value: unknown) {}
}
class PromiseNamespace {}
type PromiseMethodName = "all" | "allSettled" | "race" | "resolve" | "reject"
class PromiseMethodReference {
constructor(readonly name: PromiseMethodName) {}
}
// A built-in global namespace (`Object`, `Math`, `JSON`, `Array`, ...); members resolve to a
// GlobalMethodReference, except known constants (e.g. `Math.PI`) which resolve to a value.
type GlobalNamespaceName = "Object" | "Math" | "JSON" | "Array" | "console" | "Date" | "RegExp" | "Map" | "Set"
class GlobalNamespace {
constructor(readonly name: GlobalNamespaceName) {}
}
class GlobalMethodReference {
constructor(
readonly namespace: GlobalNamespaceName | "Number" | "String",
readonly name: string,
) {}
}
class CoercionFunction {
constructor(readonly name: "Number" | "String" | "Boolean" | "parseInt" | "parseFloat") {}
}
class ProgramThrow {
constructor(readonly value: unknown) {}
}
class ErrorConstructorReference {
constructor(readonly name: string) {}
}
// Non-enumerable so spread/copyOut preserve the plain `{ name, message }` data shape.
const ErrorBrand: unique symbol = Symbol("codemode.error")
const brandError = (errorValue: SafeObject, name: string): SafeObject => {
Object.defineProperty(errorValue, ErrorBrand, { value: name })
return errorValue
}
const createErrorValue = (name: string, message: string): SafeObject =>
brandError(Object.assign(Object.create(null) as SafeObject, { name, message }), name)
const errorBrandName = (value: unknown): string | undefined =>
value !== null && typeof value === "object"
? ((value as Record<PropertyKey, unknown>)[ErrorBrand] as string | undefined)
: undefined
/** Stable categories produced by program, schema, tool, and limit failures. */
export type DiagnosticKind =
| "ParseError"
| "UnsupportedSyntax"
| "UnknownTool"
| "InvalidToolInput"
| "InvalidToolOutput"
| "InvalidDataValue"
| "ToolCallLimitExceeded"
| "TimeoutExceeded"
| "ToolFailure"
| "ExecutionFailure"
const arrayMethods = new Set([
"map",
"filter",
"find",
"findIndex",
"findLast",
"findLastIndex",
"some",
"every",
"includes",
"join",
"reduce",
"reduceRight",
"flatMap",
"forEach",
"sort",
"toSorted",
"slice",
"concat",
"indexOf",
"lastIndexOf",
"at",
"flat",
"reverse",
"toReversed",
"with",
"push",
"pop",
"shift",
"unshift",
"splice",
"fill",
"copyWithin",
"keys",
"values",
"entries",
])
const mathConstants = new Set(["PI", "E", "LN2", "LN10", "LOG2E", "LOG10E", "SQRT2", "SQRT1_2"])
const numberMethods = new Set(["toFixed", "toPrecision", "toExponential", "toString"])
const stringMethods = new Set([
"toLowerCase",
"toUpperCase",
"trim",
"trimStart",
"trimEnd",
"trimLeft",
"trimRight",
"split",
"slice",
"substring",
"substr",
"includes",
"startsWith",
"endsWith",
"indexOf",
"lastIndexOf",
"replace",
"replaceAll",
"repeat",
"padStart",
"padEnd",
"charAt",
"charCodeAt",
"codePointAt",
"at",
"concat",
"toString",
"match",
"matchAll",
"search",
"localeCompare",
"normalize",
])
const numberConstants = new Set(["MAX_SAFE_INTEGER", "MIN_SAFE_INTEGER", "MAX_VALUE", "MIN_VALUE", "EPSILON"])
const numberStatics = new Set(["isInteger", "isFinite", "isNaN", "isSafeInteger", "parseInt", "parseFloat"])
const stringStatics = new Set(["fromCharCode", "fromCodePoint"])
const consoleMethods = new Set(["log", "info", "debug", "warn", "error", "dir", "table"])
const promiseStatics = new Set<PromiseMethodName>(["all", "allSettled", "race", "resolve", "reject"])
const errorConstructors = new Set([
"Error",
"TypeError",
"RangeError",
"SyntaxError",
"ReferenceError",
"EvalError",
"URIError",
])
const valueConstructors = new Set(["Date", "RegExp", "Map", "Set"])
const dateMethods = new Set([
"getTime",
"valueOf",
"toISOString",
"toJSON",
"toString",
"getFullYear",
"getMonth",
"getDate",
"getDay",
"getHours",
"getMinutes",
"getSeconds",
"getMilliseconds",
"getUTCFullYear",
"getUTCMonth",
"getUTCDate",
"getUTCDay",
"getUTCHours",
"getUTCMinutes",
"getUTCSeconds",
"getUTCMilliseconds",
"getTimezoneOffset",
])
const dateStatics = new Set(["now", "parse", "UTC"])
const regexpMethods = new Set(["test", "exec", "toString"])
// Read-only host regex fields surfaced as plain values.
const regexpProperties = new Set([
"source",
"flags",
"lastIndex",
"global",
"ignoreCase",
"multiline",
"sticky",
"unicode",
"dotAll",
])
const mapMethods = new Set(["get", "set", "has", "delete", "clear", "forEach", "keys", "values", "entries"])
const setMethods = new Set(["add", "has", "delete", "clear", "forEach", "keys", "values", "entries"])
const OptionalShortCircuit: unique symbol = Symbol("codemode.optional-short-circuit")
const supportedSyntaxMessage =
"Supported orchestration syntax: tools.* calls (they return promises - resolve them with await), data literals, destructuring, optional chaining, template literals, conditionals, switch, loops (incl. for...of and for...in over object/array/tools keys), arrow functions, spread, try/catch, array methods (map/filter/find/findIndex/some/every/reduce/flatMap/forEach/sort/slice/concat/indexOf/lastIndexOf/at/flat/reverse/includes/join), string methods (incl. match/matchAll/replace/split with regular expressions), Date/RegExp/Map/Set, Object/Math/JSON helpers, captured console.log/warn/error/dir/table, and Promise.all/allSettled/race/resolve/reject over arrays mixing promises and plain values for parallel tool calls (promise chaining with .then/.catch is not supported - use await with try/catch)."
const unsupportedSyntax = (kind: string, node: AstNode): InterpreterRuntimeError =>
new InterpreterRuntimeError(
`Syntax '${kind}' is not supported in CodeMode. ${supportedSyntaxMessage}`,
node,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
/** How many eagerly forked tool calls may run at once. Fixed; not a configurable knob. */
const TOOL_CALL_CONCURRENCY = 8
/** Console formatting recursion ceiling; deeper values render as "...". Fixed; not a knob. */
const MAX_CONSOLE_DEPTH = 32
const validateLimit = <Value extends number | undefined>(
name: keyof ExecutionLimits,
value: Value,
minimum: number,
): Value => {
if (value !== undefined && (!Number.isSafeInteger(value) || value < minimum)) {
throw new RangeError(`${name} must be a safe integer greater than or equal to ${minimum}.`)
}
return value
}
// No limit has a default: absent means no timeout / unlimited calls / no output truncation -
// budgets are host policy, not library policy. A host without its own output bounding should
// pass maxOutputBytes explicitly, or oversized results flood model context.
const resolveExecutionLimits = (limits?: ExecutionLimits): ResolvedExecutionLimits => ({
timeoutMs: validateLimit("timeoutMs", limits?.timeoutMs, 1),
maxToolCalls: validateLimit("maxToolCalls", limits?.maxToolCalls, 0),
maxOutputBytes: validateLimit("maxOutputBytes", limits?.maxOutputBytes, 0),
})
class InterpreterRuntimeError extends Error {
readonly node?: AstNode
/**
* The constructor name a program observes when it catches this failure (`caught.name`, and
* the brand behind `caught instanceof SyntaxError` etc.). "Error" unless the failing
* operation names a standard type in real JS - e.g. JSON.parse and invalid regex patterns
* throw SyntaxError, an unknown identifier is a ReferenceError, a bad normalize form is a
* RangeError.
*/
errorName: string = "Error"
constructor(
message: string,
node?: AstNode,
readonly kind: DiagnosticKind = "ExecutionFailure",
readonly suggestions?: ReadonlyArray<string>,
) {
super(message)
this.name = "InterpreterRuntimeError"
if (node) {
this.node = node
}
}
as(errorName: string): this {
this.errorName = errorName
return this
}
}
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null
const asNode = (value: unknown, context: string): AstNode => {
if (!isRecord(value) || typeof value.type !== "string") {
throw new InterpreterRuntimeError(`Invalid AST node while reading ${context}.`)
}
return value as AstNode
}
const getArray = (node: AstNode, key: string): Array<unknown> => {
const value = node[key]
if (!Array.isArray(value)) {
throw new InterpreterRuntimeError(`Expected '${key}' to be an array.`, node)
}
return value
}
const getString = (node: AstNode, key: string): string => {
const value = node[key]
if (typeof value !== "string") {
throw new InterpreterRuntimeError(`Expected '${key}' to be a string.`, node)
}
return value
}
const getBoolean = (node: AstNode, key: string): boolean => {
const value = node[key]
if (typeof value !== "boolean") {
throw new InterpreterRuntimeError(`Expected '${key}' to be a boolean.`, node)
}
return value
}
const getOptionalNode = (node: AstNode, key: string): AstNode | undefined => {
const value = node[key]
if (value === undefined || value === null) {
return undefined
}
return asNode(value, key)
}
const getNode = (node: AstNode, key: string): AstNode => {
const value = node[key]
return asNode(value, key)
}
const parseProgram = (code: string): ProgramNode => {
const transpiled = transpileModule(`async function __codemode__() {\n${code}\n}`, {
reportDiagnostics: true,
compilerOptions: {
target: ScriptTarget.ESNext,
module: ModuleKind.ESNext,
},
})
const diagnostic = transpiled.diagnostics?.find((item) => item.category === DiagnosticCategory.Error)
if (diagnostic) {
throw new InterpreterRuntimeError(
`Failed to parse TypeScript: ${flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`,
undefined,
"ParseError",
)
}
const bodyStart = transpiled.outputText.indexOf("{") + 1
const bodyEnd = transpiled.outputText.lastIndexOf("}")
const executableCode = transpiled.outputText.slice(bodyStart, bodyEnd)
const parsed = parse(executableCode, {
ecmaVersion: "latest",
sourceType: "script",
allowReturnOutsideFunction: true,
allowAwaitOutsideFunction: true,
locations: true,
}) as unknown
if (!isRecord(parsed) || parsed.type !== "Program" || !Array.isArray(parsed.body)) {
throw new InterpreterRuntimeError("Failed to parse script as a Program node.")
}
return parsed as ProgramNode
}
const formatLocation = (node?: AstNode): string => {
if (!node || !node.loc) {
return ""
}
const location = sourceLocation(node)
return ` (line ${location.line}, col ${location.column})`
}
const sourceLocation = (node: AstNode): { readonly line: number; readonly column: number } => ({
line: Math.max(1, (node.loc?.start.line ?? 2) - 1),
column: Math.max(1, (node.loc?.start.column ?? 4) - 3),
})
const publicErrorMessage = (message: string): string =>
message.replace(/\/(?:Users|home|private|tmp|var\/folders)\/[^\s"'`]+/g, "<redacted-path>")
const normalizeError = (error: unknown): Diagnostic => {
if (error instanceof InterpreterRuntimeError) {
return {
kind: error.kind,
message: `${error.message}${formatLocation(error.node)}`,
...(error.node?.loc ? { location: sourceLocation(error.node) } : {}),
...(error.suggestions ? { suggestions: error.suggestions } : {}),
}
}
if (error instanceof ToolRuntimeError) {
return {
kind: error.kind,
message: error.message,
...(error.suggestions.length > 0 ? { suggestions: error.suggestions } : {}),
}
}
if (error instanceof ToolError) {
return { kind: "ToolFailure", message: publicErrorMessage(error.message) }
}
if (error instanceof ProgramThrow) {
const value = error.value
let message: string
if (containsRuntimeReference(value)) {
// A thrown tool/function reference must not leak its internal structure.
message = "a non-data value"
} else if (typeof value === "string") {
message = value
} else if (
value !== null &&
typeof value === "object" &&
typeof (value as { message?: unknown }).message === "string"
) {
message = (value as { message: string }).message
} else {
try {
message = JSON.stringify(copyOut(value)) ?? String(value)
} catch {
message = String(value)
}
}
return { kind: "ExecutionFailure", message: `Uncaught: ${message}` }
}
if (error instanceof RangeError && /call stack|recursion/i.test(error.message)) {
return {
kind: "ExecutionFailure",
message: "Execution exceeded the maximum nesting depth.",
}
}
if (error instanceof Error) {
return {
kind: error.name === "SyntaxError" ? "ParseError" : "ExecutionFailure",
message: publicErrorMessage(error.message),
}
}
// A non-Error thrown by a host tool (raw string / number / Symbol) still routes through
// path redaction so filesystem paths can never leak through the catch-all branch.
return {
kind: "ExecutionFailure",
message: publicErrorMessage(String(error)),
}
}
// Shared by catch bindings, Promise.allSettled rejection reasons, and Promise.race losers.
const caughtErrorValue = (thrown: unknown): unknown => {
if (thrown instanceof ProgramThrow) return thrown.value
if (thrown instanceof InterpreterRuntimeError) return createErrorValue(thrown.errorName, thrown.message)
const name = thrown instanceof Error && errorConstructors.has(thrown.name) ? thrown.name : "Error"
return createErrorValue(name, normalizeError(thrown).message)
}
const boundedData = (value: unknown, label: string): unknown => copyIn(value, label, true)
const isRuntimeReference = (value: unknown): boolean =>
value instanceof CodeModeFunction ||
value instanceof ToolReference ||
value instanceof IntrinsicReference ||
value instanceof GlobalNamespace ||
value instanceof GlobalMethodReference ||
value instanceof PromiseNamespace ||
value instanceof PromiseMethodReference ||
value instanceof SandboxPromise ||
value instanceof CoercionFunction ||
value instanceof ErrorConstructorReference ||
isSandboxValue(value)
const containsRuntimeReference = (value: unknown, seen = new Set<object>()): boolean => {
if (isRuntimeReference(value)) return true
if (value === null || typeof value !== "object") return false
if (seen.has(value)) return false
seen.add(value)
const contains = Array.isArray(value)
? value.some((item) => containsRuntimeReference(item, seen))
: Object.values(value).some((item) => containsRuntimeReference(item, seen))
seen.delete(value)
return contains
}
// Like containsRuntimeReference, but sandbox value types (Date/RegExp/Map/Set) count as data:
// operators and switch treat them as ordinary object operands (identity equality, ToPrimitive
// coercion) rather than rejecting them as opaque interpreter machinery.
const containsOpaqueReference = (value: unknown, seen = new Set<object>()): boolean => {
if (isSandboxValue(value)) return false
if (isRuntimeReference(value)) return true
if (value === null || typeof value !== "object") return false
if (seen.has(value)) return false
seen.add(value)
const contains = Array.isArray(value)
? value.some((item) => containsOpaqueReference(item, seen))
: Object.values(value).some((item) => containsOpaqueReference(item, seen))
seen.delete(value)
return contains
}
// `typeof` never throws in JS; map every interpreter value to its JS-visible category.
// A SandboxPromise falls through to the final `typeof value` and reports "object", exactly
// like a real JS promise.
const typeofValue = (value: unknown): string => {
if (
value instanceof CodeModeFunction ||
value instanceof CoercionFunction ||
value instanceof IntrinsicReference ||
value instanceof GlobalMethodReference ||
value instanceof PromiseMethodReference ||
value instanceof PromiseNamespace ||
value instanceof ErrorConstructorReference
)
return "function"
if (value instanceof ToolReference) return value.path.length > 0 ? "function" : "object"
if (value instanceof GlobalNamespace) {
return value.name === "Math" || value.name === "JSON" || value.name === "console" ? "object" : "function"
}
return typeof value
}
// `x instanceof C` against the constructors CodeMode knows. Like `typeof`, it observes any
// left-hand value (opaque references included) without coercing it. Error checks use the
// error brand: `instanceof Error` accepts every branded error; a specific error type matches
// its own brand only (as in JS, where TypeError instances are also Error instances).
const instanceofValue = (lhs: unknown, rhs: unknown, node: AstNode): boolean => {
if (rhs instanceof ErrorConstructorReference) {
const brand = errorBrandName(lhs)
return brand !== undefined && (rhs.name === "Error" || brand === rhs.name)
}
if (rhs instanceof GlobalNamespace) {
switch (rhs.name) {
case "Date":
return lhs instanceof SandboxDate
case "RegExp":
return lhs instanceof SandboxRegExp
case "Map":
return lhs instanceof SandboxMap
case "Set":
return lhs instanceof SandboxSet
case "Array":
return Array.isArray(lhs)
case "Object":
return lhs !== null && (typeof lhs === "object" || typeofValue(lhs) === "function")
}
}
if (rhs instanceof PromiseNamespace) return lhs instanceof SandboxPromise
// Number/String/Boolean wrap primitives in JS; no boxed values exist in CodeMode, so
// `x instanceof Number` is always false - exactly what it is for primitives in JS.
if (rhs instanceof CoercionFunction && (rhs.name === "Number" || rhs.name === "String" || rhs.name === "Boolean")) {
return false
}
throw new InterpreterRuntimeError(
"The right-hand side of 'instanceof' must be a constructor CodeMode knows: Error (or a specific error type like TypeError), Date, RegExp, Map, Set, Array, Object, or Promise.",
node,
)
}
// A regex engine failure message without the engine's own "Invalid regular expression:"
// prefix, so composed diagnostics read as one sentence instead of stuttering the phrase.
const regexFailureReason = (error: unknown): string =>
(error instanceof Error ? error.message : String(error)).replace(/^Invalid regular expression:\s*/i, "")
const escapeRegexHint =
'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.'
// A string method's pattern argument as a host regex: a sandbox regex passes its own host
// instance through (so `g` lastIndex semantics follow the spec across calls); a string becomes
// a pattern, exactly as String.prototype.match/matchAll/search do (`extraFlags` adds matchAll's
// implicit `g`). Invalid patterns fail as catchable program errors that say what was wrong
// with the pattern and how to fix it.
const toHostRegex = (arg: unknown, method: string, node: AstNode, extraFlags = ""): RegExp => {
if (arg instanceof SandboxRegExp) return arg.regex
if (typeof arg === "string") {
try {
return new RegExp(arg, extraFlags)
} catch (error) {
throw new InterpreterRuntimeError(
`String.${method} received the string ${JSON.stringify(arg)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`,
node,
).as("SyntaxError")
}
}
throw new InterpreterRuntimeError(
`String.${method} expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${arg === null ? "null" : typeof arg}.`,
node,
)
}
// A host match result as a sandbox value: a plain array of the full match and captures, with
// `index` and named `groups` attached as own array properties (readable, and dropped at data
// boundaries exactly like JSON.stringify drops them in JS). `input` is omitted - it duplicates
// the whole subject string per match.
const matchToValue = (match: RegExpMatchArray): Array<unknown> => {
const result: Array<unknown> = Array.from(match, (group) => group)
if (match.index !== undefined) (result as Record<string, unknown> & Array<unknown>).index = match.index
if (match.groups) {
const groups: SafeObject = Object.create(null) as SafeObject
for (const [key, group] of Object.entries(match.groups)) {
if (!isBlockedMember(key)) groups[key] = group
}
;(result as Record<string, unknown> & Array<unknown>).groups = groups
}
return result
}
const invokeStringMethod = (value: string, name: string, args: Array<unknown>, node: AstNode): unknown => {
const str = (index: number): string => {
const arg = args[index]
if (typeof arg !== "string")
throw new InterpreterRuntimeError(`String.${name} expects argument ${index + 1} to be a string.`, node)
return arg
}
const num = (index: number): number => {
const arg = args[index]
if (typeof arg !== "number")
throw new InterpreterRuntimeError(`String.${name} expects argument ${index + 1} to be a number.`, node)
return arg
}
const optNum = (index: number): number | undefined => (args[index] === undefined ? undefined : num(index))
const optStr = (index: number): string | undefined => (args[index] === undefined ? undefined : str(index))
let result: unknown
switch (name) {
case "toLowerCase":
result = value.toLowerCase()
break
case "toUpperCase":
result = value.toUpperCase()
break
case "trim":
result = value.trim()
break
// trimLeft/trimRight are the legacy aliases of trimStart/trimEnd, kept because models write them.
case "trimStart":
case "trimLeft":
result = value.trimStart()
break
case "trimEnd":
case "trimRight":
result = value.trimEnd()
break
// Locale/options arguments are ignored: comparison runs with the host default locale, and
// the common use is a sort comparator where any consistent order works.
case "localeCompare":
result = value.localeCompare(str(0))
break
case "normalize": {
const form = optStr(0)
try {
result = value.normalize(form)
} catch {
throw new InterpreterRuntimeError(
`String.normalize expects the form "NFC", "NFD", "NFKC", or "NFKD" (got ${JSON.stringify(form)}).`,
node,
).as("RangeError")
}
break
}
case "split": {
if (args.length === 0) {
result = [value]
break
}
if (args[0] instanceof SandboxRegExp) {
result = value.split((args[0] as SandboxRegExp).regex, optNum(1))
break
}
const requestedLimit = optNum(1)
result = value.split(str(0), requestedLimit === undefined ? undefined : requestedLimit >>> 0)
break
}
case "slice":
result = value.slice(optNum(0), optNum(1))
break
case "includes":
result = value.includes(str(0), optNum(1))
break
case "startsWith":
result = value.startsWith(str(0), optNum(1))
break
case "endsWith":
result = value.endsWith(str(0), optNum(1))
break
case "indexOf":
result = value.indexOf(str(0), optNum(1))
break
case "lastIndexOf":
result = value.lastIndexOf(str(0), optNum(1))
break
case "replace":
case "replaceAll": {
if (args[0] instanceof CodeModeFunction || args[1] instanceof CodeModeFunction) {
throw new InterpreterRuntimeError(
`String.${name} does not support function replacers in CodeMode; use match/matchAll and rebuild the string instead.`,
node,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
}
if (args[0] instanceof SandboxRegExp) {
const pattern = (args[0] as SandboxRegExp).regex
const replacement = str(1)
if (name === "replaceAll" && !pattern.global) {
throw new InterpreterRuntimeError(
`String.replaceAll requires a regular expression with the global (g) flag: write /${pattern.source}/${pattern.flags}g, or use String.replace to replace only the first match.`,
node,
)
}
result = name === "replace" ? value.replace(pattern, replacement) : value.replaceAll(pattern, replacement)
break
}
if (name === "replace") {
result = value.replace(str(0), str(1))
break
}
result = value.replaceAll(str(0), str(1))
break
}
case "match": {
const pattern = toHostRegex(args[0], name, node)
const matched = value.match(pattern)
if (matched === null) return null
// A global match is a plain array of matched strings; a non-global match carries
// index/groups own properties, so bypass the copying data checkpoint to keep them.
if (pattern.global) return boundedData(matched, "String.match result")
return matchToValue(matched)
}
case "matchAll": {
const pattern = toHostRegex(args[0], name, node, "g")
if (!pattern.global) {
throw new InterpreterRuntimeError(
`String.matchAll requires a regular expression with the global (g) flag: write /${pattern.source}/${pattern.flags}g, or use String.match for a single match.`,
node,
)
}
// Materialized as an array (not an iterator); each entry is a match array with
// index/groups own properties. Match count is bounded by the subject length.
return Array.from(value.matchAll(pattern), matchToValue)
}
case "search": {
result = value.search(toHostRegex(args[0], name, node))
break
}
case "repeat": {
const count = num(0)
if (!Number.isFinite(count) || count < 0)
throw new InterpreterRuntimeError("String.repeat expects a finite non-negative count.", node)
result = value.repeat(count)
break
}
case "padStart":
result = value.padStart(num(0), optStr(1))
break
case "padEnd":
result = value.padEnd(num(0), optStr(1))
break
case "charAt":
result = value.charAt(optNum(0) ?? 0)
break
case "at":
result = value.at(optNum(0) ?? 0)
break
case "substring":
result = value.substring(optNum(0) ?? 0, optNum(1))
break
case "substr":
result = value.substr(optNum(0) ?? 0, optNum(1))
break
// JS charCodeAt returns NaN out of range; NaN flows as an ordinary in-sandbox value
// (normalized to null only at the data boundary - see copyOut), so return it as-is.
case "charCodeAt":
result = value.charCodeAt(optNum(0) ?? 0)
break
case "codePointAt":
result = value.codePointAt(optNum(0) ?? 0)
break
case "toString":
result = value
break
case "concat": {
result = value.concat(...args.map((_, index) => str(index)))
break
}
default:
throw new InterpreterRuntimeError(`String method '${name}' is not available in CodeMode.`, node)
}
return boundedData(result, `String.${name} result`)
}
const invokeNumberMethod = (value: number, name: string, args: Array<unknown>, node: AstNode): unknown => {
const optNum = (index: number): number | undefined => {
const arg = args[index]
if (arg === undefined) return undefined
if (typeof arg !== "number") throw new InterpreterRuntimeError(`Number.${name} expects a number argument.`, node)
return arg
}
let result: unknown
switch (name) {
case "toFixed":
result = value.toFixed(optNum(0))
break
case "toExponential":
result = value.toExponential(optNum(0))
break
case "toPrecision": {
const digits = optNum(0)
result = digits === undefined ? value.toString() : value.toPrecision(digits)
break
}
case "toString": {
const radix = optNum(0)
if (radix !== undefined && (radix < 2 || radix > 36)) {
throw new InterpreterRuntimeError("Number.toString radix must be between 2 and 36.", node)
}
result = value.toString(radix)
break
}
default:
throw new InterpreterRuntimeError(`Number method '${name}' is not available in CodeMode.`, node)
}
return boundedData(result, `Number.${name} result`)
}
// JavaScript's String(...) without tripping over CodeMode's null-prototype data objects.
const coerceToString = (value: unknown): string => {
if (value === null) return "null"
if (value === undefined) return "undefined"
// Sandbox values stringify deterministically: Date as ISO (not the host's locale/timezone
// toString), RegExp as its literal form, Map/Set with their JS Object.prototype tags.
if (value instanceof SandboxDate)
return Number.isFinite(value.time) ? new Date(value.time).toISOString() : "Invalid Date"
if (value instanceof SandboxRegExp) return `/${value.regex.source}/${value.regex.flags}`
if (value instanceof SandboxMap) return "[object Map]"
if (value instanceof SandboxSet) return "[object Set]"
if (typeof value === "object") {
return Array.isArray(value)
? value.map((item) => (item === null || item === undefined ? "" : coerceToString(item))).join(",")
: "[object Object]"
}
return String(value)
}
/** Compound assignment operators (`x op= y`), each applying the binary operator `op`. */
const compoundOperators = new Set(["+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>="])
const coerceToNumber = (value: unknown): number => {
if (value instanceof SandboxDate) return value.time
if (isSandboxValue(value)) return Number.NaN
return value !== null && typeof value === "object" && !Array.isArray(value) ? Number.NaN : Number(value)
}
const invokeCoercion = (ref: CoercionFunction, args: Array<unknown>, node: AstNode): unknown => {
// Sandbox values coerce before the data checkpoint (which would JSON-serialize them):
// Number(date) is its time value, String(date) its ISO form, Boolean(x) is true.
const raw = args[0]
if (isSandboxValue(raw)) {
if (ref.name === "Boolean") return true
if (ref.name === "Number") return coerceToNumber(raw)
if (ref.name === "String") return coerceToString(raw)
if (ref.name === "parseInt") return parseInt(coerceToString(raw))
return parseFloat(coerceToString(raw))
}
const value = boundedData(args[0], `${ref.name} input`)
if (ref.name === "Number") return coerceToNumber(value)
if (ref.name === "Boolean") return Boolean(value)
if (ref.name === "parseInt") {
const radix = args[1]
if (radix !== undefined && typeof radix !== "number")
throw new InterpreterRuntimeError("parseInt expects a numeric radix.", node)
return parseInt(coerceToString(value), radix)
}
if (ref.name === "parseFloat") return parseFloat(coerceToString(value))
return coerceToString(value)
}
const invokeObjectMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
const requireObject = (): Record<string, unknown> => {
const value = boundedData(args[0], `Object.${name} input`)
// Sandbox values (Date/RegExp/Map/Set) have no own enumerable properties in JS, so the
// Object.* helpers see them as empty objects - never their interpreter internals.
if (isSandboxValue(value)) return {}
if (value === null || typeof value !== "object" || Array.isArray(value)) {
throw new InterpreterRuntimeError(`Object.${name} expects a data object.`, node)
}
return value as Record<string, unknown>
}
const guardedSet = (out: Record<string, unknown>, key: string, item: unknown): void => {
if (isBlockedMember(key)) throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, node)
out[key] = item
}
switch (name) {
case "keys": {
// Object.keys(array) yields index strings (["0", "1", ...]) exactly as in JS; objects
// yield their own enumerable keys. (Tool references never reach here - the interpreter
// resolves them against the host tool tree first.)
const value = boundedData(args[0], "Object.keys input")
if (isSandboxValue(value)) return []
if (Array.isArray(value)) return Object.keys(value)
if (value === null || typeof value !== "object") {
throw new InterpreterRuntimeError("Object.keys expects a data object or array.", node)
}
return Object.keys(value)
}
case "values":
return Object.values(requireObject())
case "entries":
return Object.entries(requireObject()).map(([key, item]) => [key, item])
case "hasOwn":
return Object.hasOwn(requireObject(), String(args[1]))
case "assign": {
const out: Record<string, unknown> = Object.create(null)
for (const source of args) {
if (source === null || source === undefined) continue
const value = boundedData(source, "Object.assign input")
// A sandbox value source contributes nothing (no own enumerable properties in JS).
if (isSandboxValue(value)) continue
if (value === null || typeof value !== "object" || Array.isArray(value))
throw new InterpreterRuntimeError("Object.assign expects data objects.", node)
for (const [key, item] of Object.entries(value)) guardedSet(out, key, item)
}
return out
}
case "fromEntries": {
// A Map is the idiomatic fromEntries source; use its entries directly (the data
// checkpoint would serialize a Map to {}).
if (args[0] instanceof SandboxMap) {
const out: Record<string, unknown> = Object.create(null)
for (const [key, item] of (args[0] as SandboxMap).map.entries()) guardedSet(out, coerceToString(key), item)
return out
}
const pairs = boundedData(args[0], "Object.fromEntries input")
if (!Array.isArray(pairs))
throw new InterpreterRuntimeError("Object.fromEntries expects an array of [key, value] pairs.", node)
const out: Record<string, unknown> = Object.create(null)
for (const pair of pairs) {
if (!Array.isArray(pair))
throw new InterpreterRuntimeError("Object.fromEntries expects [key, value] pairs.", node)
guardedSet(out, String(pair[0]), pair[1])
}
return out
}
default:
throw new InterpreterRuntimeError(`Object.${name} is not available in CodeMode.`, node)
}
}
const invokeMathMethod = (name: string, args: Array<unknown>, node: AstNode): number => {
const nums = args.map((arg) => {
if (typeof arg !== "number") throw new InterpreterRuntimeError(`Math.${name} expects number arguments.`, node)
return arg
})
const [a = Number.NaN, b = Number.NaN] = nums
switch (name) {
case "max":
return Math.max(...nums)
case "min":
return Math.min(...nums)
case "abs":
return Math.abs(a)
case "floor":
return Math.floor(a)
case "ceil":
return Math.ceil(a)
case "round":
return Math.round(a)
case "trunc":
return Math.trunc(a)
case "sign":
return Math.sign(a)
case "sqrt":
return Math.sqrt(a)
case "cbrt":
return Math.cbrt(a)
case "pow":
return Math.pow(a, b)
case "hypot":
return Math.hypot(...nums)
case "log":
return Math.log(a)
case "log2":
return Math.log2(a)
case "log10":
return Math.log10(a)
case "exp":
return Math.exp(a)
default:
throw new InterpreterRuntimeError(`Math.${name} is not available in CodeMode.`, node)
}
}
const invokeJsonMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
switch (name) {
case "stringify": {
const replacer = args[1]
if (Array.isArray(replacer) || replacer instanceof CodeModeFunction) {
throw new InterpreterRuntimeError(
"JSON.stringify replacers are not supported in CodeMode.",
node,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
}
const space = args[2]
const indent = typeof space === "number" || typeof space === "string" ? space : undefined
// copyIn first so only Data Values serialize, never a CodeModeFunction/ToolReference.
return JSON.stringify(copyOut(copyIn(args[0], "JSON.stringify value")), null, indent)
}
case "parse": {
const text = args[0]
if (typeof text !== "string") throw new InterpreterRuntimeError("JSON.parse expects a string.", node)
let parsed: unknown
try {
parsed = JSON.parse(text)
} catch (error) {
// The engine reason is derived from the program-supplied string (token/position), so
// it is safe to surface - and the position is exactly what a model needs to fix it.
throw new InterpreterRuntimeError(
`JSON.parse received invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
node,
).as("SyntaxError")
}
return copyIn(parsed, "JSON.parse result")
}
default:
throw new InterpreterRuntimeError(`JSON.${name} is not available in CodeMode.`, node)
}
}
const invokeArrayStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => {
switch (name) {
case "isArray":
return Array.isArray(args[0])
case "of":
return [...args]
case "from": {
if (args.length > 1) {
throw new InterpreterRuntimeError(
"Array.from(...) does not support a map function in CodeMode; call .map() on the result instead.",
node,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
}
// Map/Set materialize directly (the data checkpoint would serialize them to {}).
if (args[0] instanceof SandboxMap)
return Array.from((args[0] as SandboxMap).map.entries(), ([key, item]) => [key, item])
if (args[0] instanceof SandboxSet) return Array.from((args[0] as SandboxSet).set.values())
const source = boundedData(args[0], "Array.from input")
if (typeof source === "string") return Array.from(source)
if (Array.isArray(source)) return [...source]
if (
source !== null &&
typeof source === "object" &&
typeof (source as { length?: unknown }).length === "number"
) {
return Array.from(source as ArrayLike<unknown>)
}
throw new InterpreterRuntimeError("Array.from expects an array, string, Map, Set, or array-like value.", node)
}
default:
throw new InterpreterRuntimeError(`Array.${name} is not available in CodeMode.`, node)
}
}
const invokeNumberStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => {
const value = args[0]
switch (name) {
case "isInteger":
return Number.isInteger(value)
case "isFinite":
return Number.isFinite(value)
case "isNaN":
return Number.isNaN(value)
case "isSafeInteger":
return Number.isSafeInteger(value)
case "parseInt": {
const radix = args[1]
if (radix !== undefined && typeof radix !== "number")
throw new InterpreterRuntimeError("Number.parseInt expects a numeric radix.", node)
return parseInt(coerceToString(value), radix)
}
case "parseFloat":
return parseFloat(coerceToString(value))
default:
throw new InterpreterRuntimeError(`Number.${name} is not available in CodeMode.`, node)
}
}
const invokeStringStatic = (name: string, args: Array<unknown>, node: AstNode): unknown => {
const codes = args.map((arg) => {
if (typeof arg !== "number") throw new InterpreterRuntimeError(`String.${name} expects number arguments.`, node)
return arg
})
switch (name) {
case "fromCharCode":
return String.fromCharCode(...codes)
case "fromCodePoint":
return String.fromCodePoint(...codes)
default:
throw new InterpreterRuntimeError(`String.${name} is not available in CodeMode.`, node)
}
}
const invokeDateStatic = (name: string, args: Array<unknown>, node: AstNode): number => {
switch (name) {
case "now":
return Date.now()
case "parse":
return Date.parse(coerceToString(args[0]))
case "UTC": {
const parts = args.map((arg) => coerceToNumber(arg))
return Date.UTC(...(parts as Parameters<typeof Date.UTC>))
}
default:
throw new InterpreterRuntimeError(`Date.${name} is not available in CodeMode.`, node)
}
}
const invokeDateMethod = (value: SandboxDate, name: string, node: AstNode): unknown => {
const hosted = new Date(value.time)
switch (name) {
case "getTime":
case "valueOf":
return value.time
case "toISOString": {
if (!Number.isFinite(value.time)) throw new InterpreterRuntimeError("Invalid time value.", node)
return hosted.toISOString()
}
// toJSON of an invalid date is null in JS (never a throw); toString stays ISO for
// determinism across host timezones/locales.
case "toJSON":
return Number.isFinite(value.time) ? hosted.toISOString() : null
case "toString":
return coerceToString(value)
case "getFullYear":
return hosted.getFullYear()
case "getMonth":
return hosted.getMonth()
case "getDate":
return hosted.getDate()
case "getDay":
return hosted.getDay()
case "getHours":
return hosted.getHours()
case "getMinutes":
return hosted.getMinutes()
case "getSeconds":
return hosted.getSeconds()
case "getMilliseconds":
return hosted.getMilliseconds()
case "getUTCFullYear":
return hosted.getUTCFullYear()
case "getUTCMonth":
return hosted.getUTCMonth()
case "getUTCDate":
return hosted.getUTCDate()
case "getUTCDay":
return hosted.getUTCDay()
case "getUTCHours":
return hosted.getUTCHours()
case "getUTCMinutes":
return hosted.getUTCMinutes()
case "getUTCSeconds":
return hosted.getUTCSeconds()
case "getUTCMilliseconds":
return hosted.getUTCMilliseconds()
case "getTimezoneOffset":
return hosted.getTimezoneOffset()
default:
throw new InterpreterRuntimeError(`Date method '${name}' is not available in CodeMode.`, node)
}
}
const invokeRegExpMethod = (value: SandboxRegExp, name: string, args: Array<unknown>, node: AstNode): unknown => {
switch (name) {
// test/exec run on the sandbox regex's own host instance, so `g`-flag lastIndex advances
// across calls per the spec.
case "test":
return value.regex.test(coerceToString(args[0]))
case "exec": {
const matched = value.regex.exec(coerceToString(args[0]))
if (matched === null) return null
return matchToValue(matched)
}
case "toString":
return coerceToString(value)
default:
throw new InterpreterRuntimeError(`RegExp method '${name}' is not available in CodeMode.`, node)
}
}
const invokeGlobalMethod = (ref: GlobalMethodReference, args: Array<unknown>, node: AstNode): unknown => {
if (ref.namespace === "console")
throw new InterpreterRuntimeError(`console.${ref.name} is not available in CodeMode.`, node)
if (ref.namespace === "Object") return invokeObjectMethod(ref.name, args, node)
if (ref.namespace === "Math") return invokeMathMethod(ref.name, args, node)
if (ref.namespace === "Array") return invokeArrayStatic(ref.name, args, node)
if (ref.namespace === "Number") return invokeNumberStatic(ref.name, args, node)
if (ref.namespace === "String") return invokeStringStatic(ref.name, args, node)
if (ref.namespace === "Date") {
if (!dateStatics.has(ref.name))
throw new InterpreterRuntimeError(`Date.${ref.name} is not available in CodeMode.`, node)
return invokeDateStatic(ref.name, args, node)
}
if (ref.namespace === "RegExp" || ref.namespace === "Map" || ref.namespace === "Set") {
throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available in CodeMode.`, node)
}
return invokeJsonMethod(ref.name, args, node)
}
// Iterable spread sources: arrays, strings (code points), Maps (entry pairs), and Sets (values).
const spreadItems = (spread: unknown): Array<unknown> | undefined => {
if (Array.isArray(spread)) return spread
if (typeof spread === "string") return Array.from(spread)
if (spread instanceof SandboxMap)
return Array.from(spread.map.entries(), ([key, item]): Array<unknown> => [key, item])
if (spread instanceof SandboxSet) return Array.from(spread.set.values())
return undefined
}
// Every identifier a parameter pattern binds, used to seed TDZ slots before defaults run.
const collectPatternNames = (pattern: AstNode, out: Array<string> = []): Array<string> => {
switch (pattern.type) {
case "Identifier":
out.push(getString(pattern, "name"))
break
case "AssignmentPattern":
collectPatternNames(getNode(pattern, "left"), out)
break
case "RestElement":
collectPatternNames(getNode(pattern, "argument"), out)
break
case "ArrayPattern":
for (const element of getArray(pattern, "elements")) {
if (element !== null) collectPatternNames(asNode(element, "elements"), out)
}
break
case "ObjectPattern":
for (const property of getArray(pattern, "properties")) {
const prop = asNode(property, "properties")
collectPatternNames(prop.type === "RestElement" ? getNode(prop, "argument") : getNode(prop, "value"), out)
}
break
}
return out
}
class Interpreter<R> {
private scopes: Array<Map<string, Binding>>
private readonly invokeTool: (path: ReadonlyArray<string>, args: Array<unknown>) => Effect.Effect<unknown, unknown, R>
// Enumerable namespace/tool names at a node of the host tool tree, threaded from
// ToolRuntime.make like invokeTool: the interpreter never holds the tree itself.
private readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>
private readonly logs: Array<string>
private lastValue: unknown
// Caps how many eagerly forked tool calls run at once (the parallel-call concurrency cap).
private readonly callPermits: Semaphore.Semaphore
// Fiber-backed promises whose settlement no program construct has observed yet. Successful
// program completion drains these (like a runtime waiting on in-flight work at exit) and
// surfaces a never-awaited failure as an unhandled-rejection diagnostic.
private readonly pendingSettlements = new Set<SandboxPromise>()
constructor(
invokeTool: (path: ReadonlyArray<string>, args: Array<unknown>) => Effect.Effect<unknown, unknown, R>,
toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>,
logs: Array<string> = [],
) {
const globalScope = new Map<string, Binding>()
this.scopes = [globalScope]
this.invokeTool = invokeTool
this.toolKeys = toolKeys
this.logs = logs
this.lastValue = undefined
this.callPermits = Semaphore.makeUnsafe(TOOL_CALL_CONCURRENCY)
globalScope.set("tools", { mutable: false, value: new ToolReference([]) })
globalScope.set("Promise", { mutable: false, value: new PromiseNamespace() })
globalScope.set("undefined", { mutable: false, value: undefined })
globalScope.set("Object", { mutable: false, value: new GlobalNamespace("Object") })
globalScope.set("Math", { mutable: false, value: new GlobalNamespace("Math") })
globalScope.set("JSON", { mutable: false, value: new GlobalNamespace("JSON") })
globalScope.set("Number", { mutable: false, value: new CoercionFunction("Number") })
globalScope.set("String", { mutable: false, value: new CoercionFunction("String") })
globalScope.set("Boolean", { mutable: false, value: new CoercionFunction("Boolean") })
globalScope.set("Array", { mutable: false, value: new GlobalNamespace("Array") })
globalScope.set("console", { mutable: false, value: new GlobalNamespace("console") })
globalScope.set("parseInt", { mutable: false, value: new CoercionFunction("parseInt") })
globalScope.set("parseFloat", { mutable: false, value: new CoercionFunction("parseFloat") })
globalScope.set("Date", { mutable: false, value: new GlobalNamespace("Date") })
globalScope.set("RegExp", { mutable: false, value: new GlobalNamespace("RegExp") })
globalScope.set("Map", { mutable: false, value: new GlobalNamespace("Map") })
globalScope.set("Set", { mutable: false, value: new GlobalNamespace("Set") })
// Error constructors are real values, so `x instanceof Error` works and `Error("msg")`
// (with or without `new`) constructs a branded { name, message } error object.
for (const name of errorConstructors) {
globalScope.set(name, { mutable: false, value: new ErrorConstructorReference(name) })
}
// NaN/Infinity flow as ordinary in-sandbox values (normalized to null only at the data
// boundary - see copyOut), so their global bindings must exist too, e.g. `reduce(max, -Infinity)`.
globalScope.set("NaN", { mutable: false, value: NaN })
globalScope.set("Infinity", { mutable: false, value: Infinity })
}
run(program: ProgramNode): Effect.Effect<unknown, unknown, R> {
const self = this
// Run the program body in its own module scope on top of the builtin global scope, so
// top-level declarations (`let undefined = 5`, `const Object = ...`) shadow builtins like
// JS module scope, instead of colliding with the seeded globals.
this.pushScope()
return Effect.gen(function* () {
self.hoistFunctions(program.body)
let value: unknown = undefined
let returned = false
for (const statement of program.body) {
const result = yield* self.evaluateStatement(statement)
if (result.kind === "return") {
value = result.value
returned = true
break
}
if (result.kind === "break" || result.kind === "continue") {
throw new InterpreterRuntimeError(`Unexpected '${result.kind}' outside of a loop.`, statement)
}
if (result.kind === "value") {
self.lastValue = result.value
}
}
if (!returned) value = self.lastValue
// The program body runs inside an implicit async function, so a returned promise
// resolves before crossing the data boundary - `return tools.ns.tool(...)` works
// without an explicit await, exactly as in JS.
if (value instanceof SandboxPromise) value = yield* self.settlePromise(value)
yield* self.drainPendingSettlements()
return value
}).pipe(Effect.ensuring(Effect.sync(() => self.popScope())))
}
// Awaits every fiber-backed promise the program abandoned (fire-and-forget tool calls), so
// their work completes before the execution ends - mirroring a JS runtime waiting on
// in-flight I/O at exit. A failure nobody could have handled becomes an unhandled-rejection
// diagnostic (interrupted calls, e.g. Promise.race losers, are ignored).
private drainPendingSettlements(): Effect.Effect<void, unknown, never> {
const self = this
return Effect.gen(function* () {
for (const promise of [...self.pendingSettlements]) {
const exit = yield* self.observePromise(promise)
if (Exit.isSuccess(exit) || Cause.hasInterruptsOnly(exit.cause)) continue
const failure = normalizeError(Cause.squash(exit.cause))
throw new InterpreterRuntimeError(
`Unhandled rejection from an un-awaited tool call: ${failure.message}`,
undefined,
failure.kind,
["Await tool calls - `const result = await tools.ns.tool(...)` - so failures can be caught and handled."],
)
}
})
}
// Eagerly starts a tool call on a supervised child fiber (so the execution timeout and
// scope teardown interrupt it) gated by the concurrency semaphore, and wraps the fiber in a
// first-class promise value. `startImmediately` makes the runtime admit the call - charging
// the tool-call budget and firing onToolCallStart - at the call site, before any await.
private createToolCallPromise(
path: ReadonlyArray<string>,
args: Array<unknown>,
): Effect.Effect<SandboxPromise, never, R> {
const self = this
return Effect.map(
Effect.forkChild(this.callPermits.withPermit(Effect.suspend(() => self.invokeTool(path, args))), {
startImmediately: true,
}),
(fiber) => {
const promise = new SandboxPromise(fiber)
self.pendingSettlements.add(promise)
return promise
},
)
}
// The promise's settlement as an Exit, marking it observed for unhandled-rejection tracking.
// Fiber settlement is idempotent, so observing the same promise repeatedly (await twice,
// Promise.all([p, p])) never re-runs the underlying call.
private observePromise(promise: SandboxPromise): Effect.Effect<Exit.Exit<unknown, unknown>> {
this.pendingSettlements.delete(promise)
return promise.fiber !== undefined ? Fiber.await(promise.fiber) : Effect.exit(promise.immediate ?? Effect.void)
}
// `await promise`: succeed with the fulfilled value or re-raise the failure so try/catch
// observes it exactly like a synchronous throw at the await site.
private settlePromise(promise: SandboxPromise, node?: AstNode): Effect.Effect<unknown, unknown, never> {
const self = this
return Effect.flatMap(this.observePromise(promise), (exit) => self.unwrapPromiseExit(promise, exit, node))
}
private unwrapPromiseExit(
promise: SandboxPromise | undefined,
exit: Exit.Exit<unknown, unknown>,
node?: AstNode,
): Effect.Effect<unknown, unknown> {
if (Exit.isSuccess(exit)) return Effect.succeed(exit.value)
// A call Promise.race interrupted after losing settles as a catchable program failure;
// any other interruption is execution teardown (timeout/host) and must keep propagating
// as interruption rather than becoming program-visible data.
if (promise?.interrupted === true && Cause.hasInterruptsOnly(exit.cause)) {
return Effect.fail(
new InterpreterRuntimeError(
"This tool call was interrupted because another value settled a Promise.race first.",
node,
),
)
}
return Effect.failCause(exit.cause)
}
private evaluateStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
switch (node.type) {
case "ExpressionStatement":
return Effect.map(this.evaluateExpression(getNode(node, "expression")), (value) => ({ kind: "value", value }))
case "VariableDeclaration":
return Effect.map(this.evaluateVariableDeclaration(node), () => ({ kind: "none" }))
case "ReturnStatement": {
const argumentNode = getOptionalNode(node, "argument")
return argumentNode
? Effect.map(this.evaluateExpression(argumentNode), (value) => ({ kind: "return", value }))
: Effect.succeed({ kind: "return", value: undefined })
}
case "BlockStatement":
return this.evaluateBlock(node)
case "IfStatement":
return this.evaluateIfStatement(node)
case "SwitchStatement":
return this.evaluateSwitchStatement(node)
case "WhileStatement":
return this.evaluateWhileStatement(node)
case "DoWhileStatement":
return this.evaluateDoWhileStatement(node)
case "ForStatement":
return this.evaluateForStatement(node)
case "ForOfStatement":
return this.evaluateForOfStatement(node)
case "ForInStatement":
return this.evaluateForInStatement(node)
case "BreakStatement":
return Effect.succeed(this.evaluateBreakStatement(node))
case "ContinueStatement":
return Effect.succeed(this.evaluateContinueStatement(node))
case "ThrowStatement":
return this.evaluateThrowStatement(node)
case "TryStatement":
return this.evaluateTryStatement(node)
case "EmptyStatement":
return Effect.succeed({ kind: "none" })
case "FunctionDeclaration":
return Effect.succeed({ kind: "none" }) // bound ahead of time by hoistFunctions
default:
throw unsupportedSyntax(node.type, node)
}
}
private evaluateBlock(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
this.pushScope()
const self = this
return Effect.gen(function* () {
const body = getArray(node, "body")
self.hoistFunctions(body)
for (const statementValue of body) {
const statement = asNode(statementValue, "body")
const result = yield* self.evaluateStatement(statement)
if (result.kind === "value") {
self.lastValue = result.value
continue
}
if (result.kind !== "none") {
return result
}
}
return { kind: "none" } satisfies StatementResult
}).pipe(Effect.ensuring(Effect.sync(() => self.popScope())))
}
private createFunction(node: AstNode): CodeModeFunction {
if (node.generator === true) {
throw new InterpreterRuntimeError(
"Generator functions are not supported in CodeMode.",
node,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
}
return new CodeModeFunction(
getArray(node, "params").map((parameter, index) => asNode(parameter, `params[${index}]`)),
getNode(node, "body"),
this.scopes.slice(),
)
}
// Function declarations are hoisted: bound in their scope before the body runs, so a
// program can call a helper defined further down (matching JavaScript).
private hoistFunctions(statements: Array<unknown>): void {
for (const statementValue of statements) {
if (!isRecord(statementValue) || statementValue.type !== "FunctionDeclaration") continue
const node = statementValue as AstNode
this.declare(getString(getNode(node, "id"), "name"), this.createFunction(node), true, node)
}
}
private evaluateIfStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
const testNode = getNode(node, "test")
const consequentNode = getNode(node, "consequent")
const alternateNode = getOptionalNode(node, "alternate")
return Effect.flatMap(this.evaluateExpression(testNode), (test) =>
test
? this.evaluateStatement(consequentNode)
: alternateNode
? this.evaluateStatement(alternateNode)
: Effect.succeed({ kind: "none" }),
)
}
private evaluateSwitchStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
const self = this
this.pushScope()
return Effect.gen(function* () {
const discriminant = yield* self.evaluateExpression(getNode(node, "discriminant"))
if (containsOpaqueReference(discriminant)) {
throw new InterpreterRuntimeError(
"Switch discriminants must be data values in CodeMode.",
node,
"InvalidDataValue",
)
}
const cases = getArray(node, "cases").map((value, index) => asNode(value, `cases[${index}]`))
let defaultIndex: number | undefined
let selected: number | undefined
for (const [index, branch] of cases.entries()) {
const test = getOptionalNode(branch, "test")
if (!test) {
defaultIndex = index
continue
}
const candidate = yield* self.evaluateExpression(test)
if (containsOpaqueReference(candidate)) {
throw new InterpreterRuntimeError(
"Switch case values must be data values in CodeMode.",
test,
"InvalidDataValue",
)
}
if (candidate === discriminant) {
selected = index
break
}
}
const start = selected ?? defaultIndex
if (start === undefined) return { kind: "none" } satisfies StatementResult
for (let index = start; index < cases.length; index += 1) {
for (const statementValue of getArray(cases[index]!, "consequent")) {
const result = yield* self.evaluateStatement(asNode(statementValue, "consequent"))
if (result.kind === "break") return { kind: "none" } satisfies StatementResult
if (result.kind === "return" || result.kind === "continue") return result
if (result.kind === "value") self.lastValue = result.value
}
}
return { kind: "none" } satisfies StatementResult
}).pipe(Effect.ensuring(Effect.sync(() => self.popScope())))
}
private evaluateWhileStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
const testNode = getNode(node, "test")
const bodyNode = getNode(node, "body")
const self = this
return Effect.gen(function* () {
while (yield* self.evaluateExpression(testNode)) {
const result = yield* self.evaluateStatement(bodyNode)
if (result.kind === "continue") {
continue
}
if (result.kind === "break") {
return { kind: "none" } satisfies StatementResult
}
if (result.kind === "return") {
return result
}
if (result.kind === "value") {
self.lastValue = result.value
}
}
return { kind: "none" } satisfies StatementResult
})
}
private evaluateDoWhileStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
const bodyNode = getNode(node, "body")
const testNode = getNode(node, "test")
const self = this
return Effect.gen(function* () {
do {
const result = yield* self.evaluateStatement(bodyNode)
if (result.kind === "continue") {
continue
}
if (result.kind === "break") {
return { kind: "none" } satisfies StatementResult
}
if (result.kind === "return") {
return result
}
if (result.kind === "value") {
self.lastValue = result.value
}
} while (yield* self.evaluateExpression(testNode))
return { kind: "none" } satisfies StatementResult
})
}
private evaluateForStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
this.pushScope()
const self = this
return Effect.gen(function* () {
const initNode = getOptionalNode(node, "init")
const testNode = getOptionalNode(node, "test")
const updateNode = getOptionalNode(node, "update")
const bodyNode = getNode(node, "body")
if (initNode) {
if (initNode.type === "VariableDeclaration") {
yield* self.evaluateVariableDeclaration(initNode)
} else {
yield* self.evaluateExpression(initNode)
}
}
const perIterationBindings =
initNode?.type === "VariableDeclaration" && getString(initNode, "kind") !== "var"
? Array.from(self.currentScope().keys())
: []
while (testNode ? yield* self.evaluateExpression(testNode) : true) {
let iterationScope: Map<string, Binding> | undefined
if (perIterationBindings.length > 0) {
iterationScope = new Map(
perIterationBindings.map((name) => {
const binding = self.currentScope().get(name)!
return [name, { ...binding }]
}),
)
self.scopes.push(iterationScope)
}
const result = yield* self.evaluateStatement(bodyNode).pipe(
Effect.ensuring(
Effect.sync(() => {
if (iterationScope) self.popScope()
}),
),
)
if (result.kind === "return") {
return result
}
if (result.kind === "break") {
return { kind: "none" } satisfies StatementResult
}
if (result.kind === "value") {
self.lastValue = result.value
}
if (iterationScope) {
const loopScope = self.currentScope()
for (const name of perIterationBindings) {
loopScope.set(name, { ...iterationScope.get(name)! })
}
}
if (updateNode) {
yield* self.evaluateExpression(updateNode)
}
if (result.kind === "continue") {
continue
}
}
return { kind: "none" } satisfies StatementResult
}).pipe(Effect.ensuring(Effect.sync(() => self.popScope())))
}
private evaluateForOfStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
if (getBoolean(node, "await")) {
throw new InterpreterRuntimeError("for await...of is not supported.", node)
}
const self = this
return Effect.gen(function* () {
const left = getNode(node, "left")
const right = yield* self.evaluateExpression(getNode(node, "right"))
const body = getNode(node, "body")
// Arrays iterate in place; strings iterate code points; Maps iterate [key, value]
// pairs and Sets iterate values over a snapshot (mutation during iteration is safe).
const iterable = Array.isArray(right) ? right : spreadItems(right)
if (iterable === undefined) {
throw new InterpreterRuntimeError("for...of requires an array, string, Map, or Set value in CodeMode.", node)
}
let declaration: { readonly pattern: AstNode; readonly mutable: boolean } | undefined
let assignmentName: string | undefined
if (left.type === "VariableDeclaration") {
const declarations = getArray(left, "declarations")
if (declarations.length !== 1) {
throw new InterpreterRuntimeError("for...of supports one declared binding.", left)
}
const declarator = asNode(declarations[0], "declarations[0]")
declaration = { pattern: getNode(declarator, "id"), mutable: getString(left, "kind") !== "const" }
} else if (left.type === "Identifier") {
assignmentName = getString(left, "name")
} else {
throw new InterpreterRuntimeError("Unsupported for...of binding.", left)
}
for (const value of iterable) {
if (declaration) {
self.pushScope()
yield* self.declarePattern(declaration.pattern, value, declaration.mutable, left)
} else if (assignmentName) {
self.setIdentifierValue(assignmentName, value, left)
}
const result = yield* self.evaluateStatement(body).pipe(
Effect.ensuring(
Effect.sync(() => {
if (declaration) self.popScope()
}),
),
)
if (result.kind === "return") {
return result
}
if (result.kind === "break") {
return { kind: "none" }
}
if (result.kind === "value") {
self.lastValue = result.value
}
if (result.kind === "continue") {
continue
}
}
return { kind: "none" }
})
}
// Own enumerable string keys of a value, shared by `for...in` and `Object.keys` over tool
// references: plain data objects enumerate their own keys, arrays their index strings (plus
// any own non-index properties, e.g. match results' index/groups - exactly Object.keys in
// JS), and a tool reference the namespace/tool names at its path in the host tool tree.
// Returns undefined for everything else so callers can raise a contextual error.
private enumerableKeys(value: unknown): Array<string> | undefined {
if (value instanceof ToolReference) {
return [...this.toolKeys(value.path)]
}
if (Array.isArray(value)) {
return Object.keys(value)
}
if (value !== null && typeof value === "object" && !isRuntimeReference(value)) {
return Object.keys(value)
}
return undefined
}
private evaluateForInStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
const self = this
return Effect.gen(function* () {
const left = getNode(node, "left")
const right = yield* self.evaluateExpression(getNode(node, "right"))
const body = getNode(node, "body")
// Keys are snapshotted up front (mutation during iteration is safe): plain objects
// enumerate their own keys, arrays their index strings, and tool references the
// namespace/tool names at that node - the same enumeration Object.keys performs.
// Anything else (strings, Maps, Sets, numbers, null, ...) is a deliberate error rather
// than real JS's surprising behavior (indices for strings, zero iterations for
// Maps/Sets/null): the hint points at the constructs that do what the program means.
const keys = self.enumerableKeys(right)
if (keys === undefined) {
throw new InterpreterRuntimeError(
"for...in requires a plain object, array, or tools reference in CodeMode. Use for...of for arrays/strings/Maps/Sets, or Object.keys(value) for a key list.",
node,
)
}
let declaration: { readonly pattern: AstNode; readonly mutable: boolean } | undefined
let assignmentName: string | undefined
if (left.type === "VariableDeclaration") {
const declarations = getArray(left, "declarations")
if (declarations.length !== 1) {
throw new InterpreterRuntimeError("for...in supports one declared binding.", left)
}
const declarator = asNode(declarations[0], "declarations[0]")
declaration = { pattern: getNode(declarator, "id"), mutable: getString(left, "kind") !== "const" }
} else if (left.type === "Identifier") {
assignmentName = getString(left, "name")
} else {
throw new InterpreterRuntimeError("Unsupported for...in binding.", left)
}
for (const key of keys) {
if (declaration) {
self.pushScope()
yield* self.declarePattern(declaration.pattern, key, declaration.mutable, left)
} else if (assignmentName) {
self.setIdentifierValue(assignmentName, key, left)
}
const result = yield* self.evaluateStatement(body).pipe(
Effect.ensuring(
Effect.sync(() => {
if (declaration) self.popScope()
}),
),
)
if (result.kind === "return") {
return result
}
if (result.kind === "break") {
return { kind: "none" }
}
if (result.kind === "value") {
self.lastValue = result.value
}
if (result.kind === "continue") {
continue
}
}
return { kind: "none" }
})
}
private evaluateBreakStatement(node: AstNode): StatementResult {
const labelNode = getOptionalNode(node, "label")
if (labelNode) {
throw new InterpreterRuntimeError("Labeled break is not supported in v1.", node)
}
return { kind: "break" }
}
private evaluateContinueStatement(node: AstNode): StatementResult {
const labelNode = getOptionalNode(node, "label")
if (labelNode) {
throw new InterpreterRuntimeError("Labeled continue is not supported in v1.", node)
}
return { kind: "continue" }
}
private evaluateThrowStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
const argument = getNode(node, "argument")
return Effect.flatMap(this.evaluateExpression(argument), (value) => Effect.fail(new ProgramThrow(value)))
}
private evaluateTryStatement(node: AstNode): Effect.Effect<StatementResult, unknown, R> {
const body = getNode(node, "block")
const handler = getOptionalNode(node, "handler")
const finalizer = getOptionalNode(node, "finalizer")
const self = this
const attempted = Effect.matchCauseEffect(this.evaluateStatement(body), {
onFailure: (cause) => {
if (cause.reasons.some(Cause.isInterruptReason) || !handler) {
return Effect.failCause(cause)
}
// The program sees a plain { message } error (or the thrown value itself) - see
// caughtErrorValue, shared with Promise.allSettled rejection reasons.
const caught = caughtErrorValue(Cause.squash(cause))
const parameter = getOptionalNode(handler, "param")
self.pushScope()
return Effect.gen(function* () {
if (parameter) yield* self.declarePattern(parameter, caught, true, handler)
return yield* self.evaluateStatement(getNode(handler, "body"))
}).pipe(Effect.ensuring(Effect.sync(() => self.popScope())))
},
onSuccess: Effect.succeed,
})
if (!finalizer) return attempted
const isAbrupt = (result: StatementResult): boolean =>
result.kind === "return" || result.kind === "break" || result.kind === "continue"
return Effect.matchCauseEffect(attempted, {
onFailure: (cause) =>
cause.reasons.some(Cause.isInterruptReason)
? Effect.failCause(cause)
: Effect.flatMap(this.evaluateStatement(finalizer), (final) =>
isAbrupt(final) ? Effect.succeed(final) : Effect.failCause(cause),
),
onSuccess: (result) =>
Effect.flatMap(this.evaluateStatement(finalizer), (final) =>
isAbrupt(final) ? Effect.succeed(final) : Effect.succeed(result),
),
})
}
private evaluateVariableDeclaration(node: AstNode): Effect.Effect<void, unknown, R> {
const kind = getString(node, "kind")
const declarations = getArray(node, "declarations")
const self = this
return Effect.gen(function* () {
for (const declarationValue of declarations) {
const declaration = asNode(declarationValue, "declarations")
if (declaration.type !== "VariableDeclarator") {
throw new InterpreterRuntimeError("Unsupported variable declaration shape.", declaration)
}
const init = getOptionalNode(declaration, "init")
const value = init ? yield* self.evaluateExpression(init) : undefined
yield* self.declarePattern(getNode(declaration, "id"), value, kind !== "const", declaration)
}
})
}
private declarePattern(
pattern: AstNode,
value: unknown,
mutable: boolean,
node: AstNode,
): Effect.Effect<void, unknown, R> {
const self = this
return Effect.gen(function* () {
if (pattern.type === "Identifier") {
self.declare(getString(pattern, "name"), value, mutable, node)
return
}
// Default values: `x = expr` / `{ a = 1 }` - the default is evaluated only when the value is undefined.
if (pattern.type === "AssignmentPattern") {
const resolved = value === undefined ? yield* self.evaluateExpression(getNode(pattern, "right")) : value
yield* self.declarePattern(getNode(pattern, "left"), resolved, mutable, node)
return
}
if (pattern.type === "ObjectPattern") {
if (value === null || typeof value !== "object" || Array.isArray(value) || isRuntimeReference(value)) {
throw new InterpreterRuntimeError(
"Object destructuring requires a data object value.",
pattern,
"InvalidDataValue",
)
}
const consumed = new Set<string>()
for (const propertyValue of getArray(pattern, "properties")) {
const property = asNode(propertyValue, "properties")
// Object rest: `{ a, ...others }` - gather the not-yet-consumed own keys.
if (property.type === "RestElement") {
const rest: SafeObject = Object.create(null) as SafeObject
for (const [key, item] of Object.entries(value as SafeObject)) {
if (!consumed.has(key) && !isBlockedMember(key)) rest[key] = item
}
yield* self.declarePattern(getNode(property, "argument"), rest, mutable, property)
continue
}
if (
property.type !== "Property" ||
getBoolean(property, "computed") ||
getString(property, "kind") !== "init"
) {
throw new InterpreterRuntimeError("Only named object destructuring properties are supported.", property)
}
const keyNode = getNode(property, "key")
const key = keyNode.type === "Identifier" ? getString(keyNode, "name") : String(keyNode.value)
if (isBlockedMember(key)) {
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, keyNode)
}
consumed.add(key)
yield* self.declarePattern(getNode(property, "value"), (value as SafeObject)[key], mutable, property)
}
return
}
if (pattern.type === "ArrayPattern") {
if (!Array.isArray(value)) {
throw new InterpreterRuntimeError("Array destructuring requires an array value.", pattern)
}
for (const [index, item] of getArray(pattern, "elements").entries()) {
if (item === null) continue
const element = asNode(item, `elements[${index}]`)
// Array rest: `[head, ...tail]` - binds the remaining elements (must be last).
if (element.type === "RestElement") {
yield* self.declarePattern(getNode(element, "argument"), value.slice(index), mutable, element)
break
}
yield* self.declarePattern(element, value[index], mutable, pattern)
}
return
}
throw new InterpreterRuntimeError(`Unsupported binding pattern '${pattern.type}'.`, pattern)
})
}
private evaluateExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
switch (node.type) {
case "Literal": {
// A regex literal parses as a Literal node carrying { pattern, flags }; construct the
// sandbox regex from those (the host `value` instance is never exposed).
const regex = node.regex
if (isRecord(regex) && typeof regex.pattern === "string") {
return Effect.sync(() =>
this.constructRegExp([regex.pattern, typeof regex.flags === "string" ? regex.flags : ""], node),
)
}
return Effect.sync(() => boundedData(node.value, "Literal"))
}
case "Identifier":
return Effect.sync(() => this.getIdentifierValue(getString(node, "name"), node))
case "BinaryExpression":
return this.evaluateBinaryExpression(node)
case "LogicalExpression":
return this.evaluateLogicalExpression(node)
case "UnaryExpression":
return this.evaluateUnaryExpression(node)
case "AssignmentExpression":
return this.evaluateAssignmentExpression(node)
case "CallExpression":
return this.evaluateCallExpression(node)
case "ArrowFunctionExpression":
case "FunctionExpression":
return Effect.sync(() => this.createFunction(node))
case "MemberExpression":
return this.readMember(node)
case "ChainExpression":
return Effect.map(this.evaluateExpression(getNode(node, "expression")), (value) =>
value === OptionalShortCircuit ? undefined : value,
)
case "ObjectExpression":
return this.evaluateObjectExpression(node)
case "ArrayExpression":
return this.evaluateArrayExpression(node)
case "TemplateLiteral":
return this.evaluateTemplateLiteral(node)
case "ConditionalExpression":
return this.evaluateConditionalExpression(node)
case "UpdateExpression":
return this.evaluateUpdateExpression(node)
case "AwaitExpression": {
// `await` resolves a promise value; awaiting anything else is a passthrough no-op,
// matching real JS semantics for non-thenables.
const self = this
return Effect.flatMap(this.evaluateExpression(getNode(node, "argument")), (value) =>
value instanceof SandboxPromise ? self.settlePromise(value, node) : Effect.succeed(value),
)
}
case "NewExpression":
return this.evaluateNewExpression(node)
default:
throw unsupportedSyntax(node.type, node)
}
}
private evaluateNewExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const callee = getNode(node, "callee")
if (callee.type !== "Identifier") {
throw unsupportedSyntax("NewExpression", node)
}
const name = getString(callee, "name")
const argNodes = getArray(node, "arguments")
const self = this
if (name === "Promise") {
throw new InterpreterRuntimeError(
"new Promise(...) is not supported in CodeMode; tool calls already return promises - call the tool and await the result.",
node,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
}
if (errorConstructors.has(name)) {
return Effect.gen(function* () {
const arg =
argNodes.length > 0 ? yield* self.evaluateExpression(asNode(argNodes[0], "arguments[0]")) : undefined
return createErrorValue(name, arg === undefined ? "" : coerceToString(arg))
})
}
if (valueConstructors.has(name)) {
return Effect.gen(function* () {
const args = yield* self.evaluateCallArguments(argNodes)
switch (name) {
case "Date":
return self.constructDate(args)
case "RegExp":
return self.constructRegExp(args, node)
case "Map":
return self.constructMap(args[0], node)
default:
return self.constructSet(args[0], node)
}
})
}
throw unsupportedSyntax("NewExpression", node)
}
private constructDate(args: Array<unknown>): SandboxDate {
if (args.length === 0) return new SandboxDate(Date.now())
if (args.length === 1) {
const arg = args[0]
if (arg instanceof SandboxDate) return new SandboxDate(arg.time)
if (typeof arg === "number") return new SandboxDate(new Date(arg).getTime())
if (typeof arg === "string") return new SandboxDate(Date.parse(arg))
return new SandboxDate(Number.NaN)
}
// new Date(year, month, day?, hours?, ...) - local-time component form.
const parts = args.map((arg) => coerceToNumber(arg))
return new SandboxDate(new Date(...(parts as [number, number])).getTime())
}
private constructRegExp(args: Array<unknown>, node: AstNode): SandboxRegExp {
const first = args[0]
const pattern =
first instanceof SandboxRegExp ? first.regex.source : first === undefined ? "" : coerceToString(first)
const flagsArg = args[1]
if (flagsArg !== undefined && typeof flagsArg !== "string") {
throw new InterpreterRuntimeError(
`RegExp flags must be a string of flag characters (e.g. "g", "gi"), not ${flagsArg === null ? "null" : typeof flagsArg}.`,
node,
)
}
const flags = flagsArg ?? (first instanceof SandboxRegExp ? first.regex.flags : "")
try {
return new SandboxRegExp(pattern, flags)
} catch (error) {
// Say which part was rejected and how to fix it, instead of passing the engine
// message through bare. A flags failure names the flags; a pattern failure gets the
// escaping hint (the usual cause is an unescaped metacharacter in a built-up string).
const reason = regexFailureReason(error)
throw new InterpreterRuntimeError(
/flag/i.test(reason)
? `new RegExp(...) received invalid flags ${JSON.stringify(flags)} (${reason}). Valid flags are d, g, i, m, s, u, v, and y.`
: `new RegExp(...) received ${JSON.stringify(pattern)}, which is not a valid regular expression pattern (${reason}). ${escapeRegexHint}`,
node,
).as("SyntaxError")
}
}
private constructMap(init: unknown, node: AstNode): SandboxMap {
const target = new SandboxMap()
if (init === undefined || init === null) return target
const entries = Array.isArray(init)
? init
: init instanceof SandboxMap
? Array.from(init.map.entries(), ([key, item]): Array<unknown> => [key, item])
: undefined
if (entries === undefined) {
throw new InterpreterRuntimeError(
"new Map(...) expects an array of [key, value] pairs, a Map, or no argument.",
node,
)
}
for (const pair of entries) {
if (!Array.isArray(pair)) {
throw new InterpreterRuntimeError("new Map(...) expects [key, value] pairs.", node)
}
target.map.set(pair[0], pair[1])
}
return target
}
private constructSet(init: unknown, node: AstNode): SandboxSet {
const target = new SandboxSet()
if (init === undefined || init === null) return target
const items = Array.isArray(init)
? init
: init instanceof SandboxSet
? Array.from(init.set.values())
: typeof init === "string"
? Array.from(init)
: undefined
if (items === undefined) {
throw new InterpreterRuntimeError("new Set(...) expects an array, Set, string, or no argument.", node)
}
for (const item of items) target.set.add(item)
return target
}
private evaluateBinaryExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const operator = getString(node, "operator")
const self = this
return Effect.gen(function* () {
const lhs = yield* self.evaluateExpression(getNode(node, "left"))
const rhs = yield* self.evaluateExpression(getNode(node, "right"))
// Like `typeof`, `instanceof` observes any value without coercing it (a promise or
// function operand is a legitimate question, not an error), so it is handled before
// the data-only operand check.
if (operator === "instanceof") return instanceofValue(lhs, rhs, node)
return boundedData(self.applyBinaryOperator(operator, lhs, rhs, node), "Binary expression result")
})
}
/**
* Applies a binary operator to two already-evaluated operands with CodeMode's coercion
* semantics. Shared by binary expressions and compound assignment (`x op= y` must behave
* exactly like `x = x op y`, coercion included).
*/
private applyBinaryOperator(operator: string, lhs: unknown, rhs: unknown, node: AstNode): unknown {
if (containsOpaqueReference(lhs) || containsOpaqueReference(rhs)) {
throw new InterpreterRuntimeError("Binary operators require data values in CodeMode.", node, "InvalidDataValue")
}
// Data objects/arrays are null-prototype, so JS's ToPrimitive throws an opaque host
// "No default value" TypeError when an operator coerces them. Coerce to their JS string
// form first (as String(x) / template literals do) so operators behave like JavaScript.
// A Date follows its ToPrimitive hints: string for `+` (concatenation), its time value
// for arithmetic and ordering - so `end - start` and `a < b` work as in JS.
// Identity (=== / !==) and the right operand of `in` keep their raw object value.
const coerceOperand = (operand: unknown): unknown => {
if (operand instanceof SandboxDate) return operator === "+" ? coerceToString(operand) : operand.time
return operand !== null && typeof operand === "object" ? coerceToString(operand) : operand
}
const bothObjects = lhs !== null && typeof lhs === "object" && rhs !== null && typeof rhs === "object"
const l = coerceOperand(lhs)
const r = coerceOperand(rhs)
switch (operator) {
case "+":
return (l as string) + (r as string)
case "-":
return (l as number) - (r as number)
case "*":
return (l as number) * (r as number)
case "/":
return (l as number) / (r as number)
case "%":
return (l as number) % (r as number)
case "**":
return (l as number) ** (r as number)
// Two objects compare by identity in JS (no ToPrimitive); only object-vs-primitive coerces.
case "==":
return bothObjects ? lhs === rhs : l == r
case "===":
return lhs === rhs
case "!=":
return bothObjects ? lhs !== rhs : l != r
case "!==":
return lhs !== rhs
case "<":
return (l as string) < (r as string)
case "<=":
return (l as string) <= (r as string)
case ">":
return (l as string) > (r as string)
case ">=":
return (l as string) >= (r as string)
case "&":
return (l as number) & (r as number)
case "|":
return (l as number) | (r as number)
case "^":
return (l as number) ^ (r as number)
case "<<":
return (l as number) << (r as number)
case ">>":
return (l as number) >> (r as number)
case ">>>":
return (l as number) >>> (r as number)
case "in":
if (rhs === null || typeof rhs !== "object") {
throw new InterpreterRuntimeError("The 'in' operator requires a data object on the right-hand side.", node)
}
// Own properties only, so arrays don't leak the host Array.prototype (map/constructor/...).
return Object.hasOwn(rhs as object, coerceOperand(lhs) as PropertyKey)
default:
throw new InterpreterRuntimeError(`Unsupported binary operator '${operator}'.`, node)
}
}
private evaluateLogicalExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const operator = getString(node, "operator")
return Effect.flatMap(this.evaluateExpression(getNode(node, "left")), (left) => {
if (operator === "&&") return left ? this.evaluateExpression(getNode(node, "right")) : Effect.succeed(left)
if (operator === "||") return left ? Effect.succeed(left) : this.evaluateExpression(getNode(node, "right"))
if (operator === "??")
return left !== null && left !== undefined
? Effect.succeed(left)
: this.evaluateExpression(getNode(node, "right"))
throw new InterpreterRuntimeError(`Unsupported logical operator '${operator}'.`, node)
})
}
private evaluateUnaryExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const operator = getString(node, "operator")
const argument = getNode(node, "argument")
// `typeof undeclaredIdentifier` is `"undefined"` in JS (never a ReferenceError), so
// feature-detection guards like `typeof x !== "undefined"` don't crash. Short-circuit before
// evaluating the argument; a declared-but-TDZ binding still falls through to the normal throw.
if (operator === "typeof" && argument.type === "Identifier" && !this.resolveBinding(getString(argument, "name"))) {
return Effect.succeed("undefined")
}
return Effect.map(this.evaluateExpression(argument), (value) => {
// `typeof` and `!` never throw in JS - they observe any value (functions and runtime
// references included) without coercing it, so feature detection and negation work.
if (operator === "typeof") return typeofValue(value)
if (operator === "!") return !value
if (containsOpaqueReference(value)) {
throw new InterpreterRuntimeError("Unary operators require data values in CodeMode.", node, "InvalidDataValue")
}
// Numeric/bitwise unary operators ToPrimitive their operand; a Date yields its time value
// (`+date` is the epoch-ms idiom), other null-prototype data objects/arrays coerce to
// their JS string form first (see evaluateBinaryExpression).
const operand =
value instanceof SandboxDate
? value.time
: value !== null && typeof value === "object"
? coerceToString(value)
: value
let result: unknown
switch (operator) {
case "+":
result = +(operand as number)
break
case "-":
result = -(operand as number)
break
case "~":
result = ~(operand as number)
break
default:
throw new InterpreterRuntimeError(`Unsupported unary operator '${operator}'.`, node)
}
return boundedData(result, "Unary expression result")
})
}
private evaluateAssignmentExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const left = getNode(node, "left")
const operator = getString(node, "operator")
const self = this
return Effect.gen(function* () {
if (operator === "??=" || operator === "||=" || operator === "&&=") {
return yield* self.evaluateLogicalAssignment(node, left, operator)
}
const rightValue = yield* self.evaluateExpression(getNode(node, "right"))
if (left.type === "Identifier") {
const name = getString(left, "name")
if (operator === "=") return self.setIdentifierValue(name, rightValue, left)
const next = boundedData(
self.applyCompoundAssignment(operator, self.getIdentifierValue(name, left), rightValue, node),
"Assignment result",
)
return self.setIdentifierValue(name, next, left)
}
if (left.type === "MemberExpression") {
if (operator === "=") return yield* self.writeMember(left, rightValue)
return yield* self.modifyMember(left, (current) => {
const next = boundedData(
self.applyCompoundAssignment(operator, current, rightValue, node),
"Assignment result",
)
return Effect.succeed({ write: true, next, result: next })
})
}
throw new InterpreterRuntimeError("Assignment target must be an Identifier or MemberExpression.", left)
})
}
private evaluateLogicalAssignment(
node: AstNode,
left: AstNode,
operator: string,
): Effect.Effect<unknown, unknown, R> {
const self = this
const shouldAssign = (current: unknown): boolean =>
operator === "??=" ? current === null || current === undefined : operator === "||=" ? !current : Boolean(current)
if (left.type === "Identifier") {
const name = getString(left, "name")
return Effect.gen(function* () {
const current = self.getIdentifierValue(name, left)
if (!shouldAssign(current)) return current
const rightValue = yield* self.evaluateExpression(getNode(node, "right"))
return self.setIdentifierValue(name, rightValue, left)
})
}
if (left.type === "MemberExpression") {
// Resolve the member exactly once; evaluate the RHS only if we actually assign.
return self.modifyMember(left, (current) =>
shouldAssign(current)
? Effect.map(self.evaluateExpression(getNode(node, "right")), (rightValue) => ({
write: true,
next: rightValue,
result: rightValue,
}))
: Effect.succeed({ write: false, next: current, result: current }),
)
}
throw new InterpreterRuntimeError("Assignment target must be an Identifier or MemberExpression.", left)
}
private evaluateUpdateExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const operator = getString(node, "operator")
const argument = getNode(node, "argument")
const prefix = getBoolean(node, "prefix")
const increment = operator === "++" ? 1 : operator === "--" ? -1 : undefined
if (increment === undefined) {
throw new InterpreterRuntimeError(`Unsupported update operator '${operator}'.`, node)
}
if (argument.type === "Identifier") {
return Effect.sync(() => {
const name = getString(argument, "name")
const current = Number(this.getIdentifierValue(name, argument))
const next = current + increment
this.setIdentifierValue(name, next, argument)
return prefix ? next : current
})
}
if (argument.type === "MemberExpression") {
return this.modifyMember(argument, (current) => {
const value = Number(current)
const next = value + increment
return Effect.succeed({ write: true, next, result: prefix ? next : value })
})
}
throw new InterpreterRuntimeError("Update target must be an Identifier or MemberExpression.", argument)
}
private evaluateCallExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
const callee = getNode(node, "callee")
const argNodes = getArray(node, "arguments")
const self = this
return Effect.gen(function* () {
const callable = yield* self.evaluateExpression(callee)
if (callable === OptionalShortCircuit) return OptionalShortCircuit
if ((callable === null || callable === undefined) && node.optional === true) return OptionalShortCircuit
const args = yield* self.evaluateCallArguments(argNodes)
if (callable instanceof ToolReference) {
if (callable.path.length === 0) throw new InterpreterRuntimeError("The tools root is not callable.", callee)
// An un-awaited tool call is a first-class promise value; the call itself starts now.
return yield* self.createToolCallPromise(callable.path, args)
}
if (callable instanceof PromiseMethodReference) {
return yield* self.invokePromiseMethod(callable, args, node)
}
if (callable instanceof CodeModeFunction) {
return yield* self.invokeFunction(callable, args)
}
if (callable instanceof IntrinsicReference) {
return yield* self.invokeIntrinsic(callable, args, node)
}
if (callable instanceof GlobalMethodReference) {
if (callable.namespace === "console") return self.invokeConsole(callable.name, args, node)
if (callable.namespace === "Object" && args[0] instanceof ToolReference) {
return self.invokeObjectMethodOnTools(callable.name, args[0] as ToolReference, node)
}
return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`)
}
if (callable instanceof CoercionFunction) {
return boundedData(invokeCoercion(callable, args, node), `${callable.name} result`)
}
// `Error("msg")` without `new` constructs an error exactly like `new Error("msg")`, as in JS.
if (callable instanceof ErrorConstructorReference) {
return createErrorValue(callable.name, args[0] === undefined ? "" : coerceToString(args[0]))
}
throw new InterpreterRuntimeError("Only tools are callable in CodeMode.", callee)
})
}
// Object.* over a tool reference: `Object.keys(tools)` / `Object.keys(tools.ns)` enumerate
// namespace/tool names from the host tool tree - the discovery idiom a model reaches for
// first. Every other Object helper cannot produce data from a tool reference, so it fails
// with a pointer at the working idioms instead of the generic plain-objects-only message.
private invokeObjectMethodOnTools(name: string, ref: ToolReference, node: AstNode): unknown {
if (name === "keys") {
return boundedData(this.enumerableKeys(ref)!, "Object.keys result")
}
throw new InterpreterRuntimeError(
`Object.${name}(...) cannot read tool references: they are not plain data. Use Object.keys(tools) for names, or tools.$codemode.search({ query }) for signatures.`,
node,
"InvalidDataValue",
)
}
private invokeConsole(name: string, args: Array<unknown>, node: AstNode): undefined {
if (!consoleMethods.has(name))
throw new InterpreterRuntimeError(`console.${name} is not available in CodeMode.`, node)
this.logs.push(publicErrorMessage(this.formatConsoleMessage(name, args, node)))
return undefined
}
private formatConsoleMessage(name: string, args: Array<unknown>, node: AstNode): string {
if (name === "dir") return args.length === 0 ? "undefined" : this.formatConsoleArgument(args[0])
if (name === "table") return this.formatConsoleTable(args[0], args[1], node)
const prefix = name === "warn" ? "[warn] " : name === "error" ? "[error] " : name === "debug" ? "[debug] " : ""
return `${prefix}${args.map((arg) => this.formatConsoleArgument(arg)).join(" ")}`
}
// Console arguments format deeply and totally: values render as a debugger would show them
// rather than as boundary JSON - numbers keep NaN/Infinity (JSON would say null), sandbox
// values keep their friendly forms at ANY depth (ISO date, /regex/flags, Map(n) [...],
// Set(n) [...]), opaque runtime references become "[CodeMode reference]" markers in place,
// and plain objects/arrays render JSON-style. Formatting never fails the program: cycles
// render "[Circular]" and extreme depth degrades to "...".
private formatConsoleArgument(value: unknown): string {
if (value === undefined) return "undefined"
// A top-level string prints bare; nested strings are JSON-quoted (see formatConsoleValue).
if (typeof value === "string") return value
return this.formatConsoleValue(value, new Set(), 0)
}
private formatConsoleValue(value: unknown, seen: Set<object>, depth: number): string {
// Nested undefined renders as null, matching what JSON boundary output would show.
if (value === null || value === undefined) return "null"
if (typeof value === "string") return JSON.stringify(value)
// String(value) keeps NaN/Infinity/-Infinity readable; finite numbers match their JSON form.
if (typeof value === "number" || typeof value === "boolean") return String(value)
if (typeof value !== "object") return String(value)
if (value instanceof SandboxPromise) return "[Promise (await it to get its value)]"
if (value instanceof SandboxDate) return coerceToString(value)
if (value instanceof SandboxRegExp) return coerceToString(value)
if (depth > MAX_CONSOLE_DEPTH) return "..."
if (seen.has(value)) return "[Circular]"
if (value instanceof SandboxMap) {
seen.add(value)
try {
const entries = Array.from(value.map.entries(), ([key, item]): Array<unknown> => [key, item])
return `Map(${value.map.size}) ${this.formatConsoleValue(entries, seen, depth + 1)}`
} finally {
seen.delete(value)
}
}
if (value instanceof SandboxSet) {
seen.add(value)
try {
return `Set(${value.set.size}) ${this.formatConsoleValue(Array.from(value.set.values()), seen, depth + 1)}`
} finally {
seen.delete(value)
}
}
if (isRuntimeReference(value)) return "[CodeMode reference]"
seen.add(value)
try {
if (Array.isArray(value)) {
return `[${value.map((item) => this.formatConsoleValue(item, seen, depth + 1)).join(",")}]`
}
return `{${Object.entries(value)
.map(([key, item]) => `${JSON.stringify(key)}:${this.formatConsoleValue(item, seen, depth + 1)}`)
.join(",")}}`
} finally {
seen.delete(value)
}
}
private formatConsoleTable(value: unknown, columnsArgument: unknown, node: AstNode): string {
if (value === undefined) return "undefined"
// Sandbox values are legitimate table data (cells render their friendly forms); only
// truly opaque references (functions, tools, promises) collapse to the marker.
if (containsOpaqueReference(value)) return "[CodeMode reference]"
const data = boundedData(value, "console.table argument")
const columns = this.consoleTableColumns(columnsArgument, node)
const rows = this.consoleTableRows(data, columns)
const keys = columns ?? Array.from(new Set(rows.flatMap((row) => Object.keys(row.values))))
const header = ["(index)", ...keys].join("\t")
return [
header,
...rows.map((row) => [row.index, ...keys.map((key) => this.formatConsoleTableCell(row.values[key]))].join("\t")),
].join("\n")
}
private consoleTableColumns(value: unknown, node: AstNode): ReadonlyArray<string> | undefined {
if (value === undefined) return undefined
if (containsRuntimeReference(value)) return undefined
const columns = copyOut(copyIn(value, "console.table columns"), true)
return Array.isArray(columns) ? columns.map((column) => String(column)) : undefined
}
private consoleTableRows(
data: unknown,
columns: ReadonlyArray<string> | undefined,
): Array<{ readonly index: string; readonly values: Record<string, unknown> }> {
if (Array.isArray(data)) {
return data.map((item, index) => ({ index: String(index), values: this.consoleTableValues(item, columns) }))
}
if (data !== null && typeof data === "object" && !isSandboxValue(data)) {
return Object.entries(data).map(([index, item]) => ({ index, values: this.consoleTableValues(item, columns) }))
}
return [{ index: "0", values: { Value: data } }]
}
private consoleTableValues(value: unknown, columns: ReadonlyArray<string> | undefined): Record<string, unknown> {
if (value !== null && typeof value === "object" && !Array.isArray(value) && !isSandboxValue(value)) {
const source = value as Record<string, unknown>
if (columns !== undefined) return Object.fromEntries(columns.map((column) => [column, source[column]]))
return Object.fromEntries(Object.entries(source))
}
return { Value: value }
}
private formatConsoleTableCell(value: unknown): string {
if (value === undefined) return ""
if (typeof value === "string") return value
return this.formatConsoleValue(value, new Set(), 0)
}
private evaluateCallArguments(argNodes: Array<unknown>): Effect.Effect<Array<unknown>, unknown, R> {
const self = this
return Effect.gen(function* () {
const args: Array<unknown> = []
for (const [index, arg] of argNodes.entries()) {
const argNode = asNode(arg, `arguments[${index}]`)
if (argNode.type === "SpreadElement") {
const spread = yield* self.evaluateExpression(getNode(argNode, "argument"))
const items = spreadItems(spread)
if (items === undefined)
throw new InterpreterRuntimeError(
"Spread arguments require an array, string, Map, or Set in CodeMode.",
argNode,
)
args.push(...items)
} else {
args.push(yield* self.evaluateExpression(argNode))
}
}
return args
})
}
// Promise.* over ordinary runtime values. Combinators accept ANY array (or spreadable
// collection) mixing promise values and plain data - built inline, beforehand, via spread,
// whatever - because tool calls already run eagerly on their own fibers; the combinators
// only observe settlements. Joining is therefore sequential (no extra fibers) without
// costing parallelism, and the concurrency cap stays where the work is: the fork semaphore.
private invokePromiseMethod(
ref: PromiseMethodReference,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> {
const self = this
if (ref.name === "resolve") {
// Promise.resolve of a promise is that promise (JS flattens); anything else is a
// promise already fulfilled with the value.
const value = args[0]
return Effect.succeed(
value instanceof SandboxPromise ? value : new SandboxPromise(undefined, Effect.succeed(value)),
)
}
if (ref.name === "reject") {
return Effect.sync(() => new SandboxPromise(undefined, Effect.fail(new ProgramThrow(args[0]))))
}
const items = Array.isArray(args[0]) ? args[0] : spreadItems(args[0])
if (items === undefined) {
throw new InterpreterRuntimeError(
`Promise.${ref.name} expects an array of promises or plain values (e.g. Promise.${ref.name}(items.map((item) => tools.ns.tool(item)))).`,
node,
)
}
switch (ref.name) {
case "all": {
// Mark every promise element observed up-front (Promise.all handles all of its
// members' failures, as in JS), then join in index order; the first failure rejects
// the whole call while unrelated in-flight members keep running.
const settles = items.map((item) =>
item instanceof SandboxPromise ? this.settlePromise(item, node) : Effect.succeed(item),
)
return Effect.gen(function* () {
const values: Array<unknown> = []
for (const settle of settles) values.push(yield* settle)
return values
})
}
case "allSettled": {
const observations = items.map((item) =>
item instanceof SandboxPromise
? Effect.map(this.observePromise(item), (exit) => ({ promise: item as SandboxPromise | undefined, exit }))
: Effect.succeed({ promise: undefined as SandboxPromise | undefined, exit: Exit.succeed(item as unknown) }),
)
return Effect.gen(function* () {
const outcomes: Array<unknown> = []
for (const observation of observations) {
const { exit, promise } = yield* observation
if (Exit.isSuccess(exit)) {
outcomes.push(
Object.assign(Object.create(null) as SafeObject, { status: "fulfilled", value: exit.value }),
)
continue
}
const raceInterrupted = promise?.interrupted === true && Cause.hasInterruptsOnly(exit.cause)
if (Cause.hasInterruptsOnly(exit.cause) && !raceInterrupted) {
// Execution teardown (timeout/host interruption), not a program-level rejection.
return yield* Effect.failCause(exit.cause)
}
const thrown = raceInterrupted
? new InterpreterRuntimeError(
"This tool call was interrupted because another value settled a Promise.race first.",
node,
)
: Cause.squash(exit.cause)
outcomes.push(
Object.assign(Object.create(null) as SafeObject, {
status: "rejected",
reason: caughtErrorValue(thrown),
}),
)
}
return outcomes
})
}
case "race": {
if (items.length === 0) {
throw new InterpreterRuntimeError(
"Promise.race([]) would never settle; provide at least one promise or value.",
node,
)
}
const observations = items.map((item, index) =>
item instanceof SandboxPromise
? Effect.map(this.observePromise(item), (exit) => ({ index, exit }))
: Effect.succeed({ index, exit: Exit.succeed(item as unknown) }),
)
return Effect.gen(function* () {
// First settlement (fulfilled OR rejected) wins; the observations never fail, so
// racing them yields exactly that. Losing in-flight calls are then interrupted.
const winner = yield* Effect.raceAll(observations)
for (const [index, item] of items.entries()) {
if (index === winner.index || !(item instanceof SandboxPromise) || item.fiber === undefined) continue
item.interrupted = true
yield* Fiber.interrupt(item.fiber)
}
const winningItem = items[winner.index]
return yield* self.unwrapPromiseExit(
winningItem instanceof SandboxPromise ? winningItem : undefined,
winner.exit,
node,
)
})
}
}
}
private invokeFunction(fn: CodeModeFunction, args: Array<unknown>): Effect.Effect<unknown, unknown, R> {
const self = this
return Effect.suspend(() => {
const savedScopes = self.scopes
self.scopes = [...fn.capturedScopes, new Map<string, Binding>()]
const run = Effect.gen(function* () {
// Seed every parameter name into the scope as a TDZ slot first, so a default that
// references another parameter resolves to that (uninitialized) param rather than
// silently falling through to an outer binding of the same name - matching JS.
const paramScope = self.currentScope()
for (const parameter of fn.parameters) {
for (const name of collectPatternNames(parameter)) {
paramScope.set(name, { mutable: true, value: undefined, initialized: false })
}
}
for (const [index, parameter] of fn.parameters.entries()) {
if (parameter.type === "RestElement") {
yield* self.declarePattern(getNode(parameter, "argument"), args.slice(index), true, parameter)
break
}
yield* self.declarePattern(parameter, args[index], true, parameter)
}
if (fn.body.type === "BlockStatement") {
const result = yield* self.evaluateStatement(fn.body)
return result.kind === "return" || result.kind === "value" ? result.value : undefined
}
return yield* self.evaluateExpression(fn.body)
})
return run.pipe(
Effect.ensuring(
Effect.sync(() => {
self.scopes = savedScopes
}),
),
)
})
}
private invokeIntrinsic(
ref: IntrinsicReference,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> {
if (typeof ref.receiver === "string") {
return Effect.succeed(invokeStringMethod(ref.receiver, ref.name, args, node))
}
if (typeof ref.receiver === "number") {
return Effect.succeed(invokeNumberMethod(ref.receiver, ref.name, args, node))
}
if (Array.isArray(ref.receiver)) {
return this.invokeArrayMethod(ref.receiver, ref.name, args, node)
}
if (ref.receiver instanceof SandboxDate) {
return Effect.succeed(invokeDateMethod(ref.receiver, ref.name, node))
}
if (ref.receiver instanceof SandboxRegExp) {
return Effect.succeed(invokeRegExpMethod(ref.receiver, ref.name, args, node))
}
if (ref.receiver instanceof SandboxMap) {
return this.invokeMapMethod(ref.receiver, ref.name, args, node)
}
if (ref.receiver instanceof SandboxSet) {
return this.invokeSetMethod(ref.receiver, ref.name, args, node)
}
throw new InterpreterRuntimeError(`Method '${ref.name}' is not available in CodeMode.`, node)
}
// Runs a Map/Set callback (forEach) accepting a user function or a builtin coercion,
// mirroring the array-method callback contract.
private applyCollectionCallback(
callback: unknown,
name: string,
node: AstNode,
): (args: Array<unknown>) => Effect.Effect<unknown, unknown, R> {
if (!(callback instanceof CodeModeFunction) && !(callback instanceof CoercionFunction)) {
throw new InterpreterRuntimeError(`${name} expects a function callback.`, node)
}
return (callbackArgs) =>
callback instanceof CoercionFunction
? Effect.succeed(invokeCoercion(callback, callbackArgs, node))
: this.invokeFunction(callback, callbackArgs)
}
private invokeMapMethod(
target: SandboxMap,
name: string,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> {
switch (name) {
case "get":
return Effect.succeed(target.map.get(args[0]))
case "has":
return Effect.succeed(target.map.has(args[0]))
case "set":
return Effect.sync(() => {
target.map.set(args[0], args[1])
return target
})
case "delete":
return Effect.sync(() => target.map.delete(args[0]))
case "clear":
return Effect.sync(() => {
target.map.clear()
return undefined
})
case "keys":
return Effect.sync(() => Array.from(target.map.keys()))
case "values":
return Effect.sync(() => Array.from(target.map.values()))
case "entries":
return Effect.sync(() => Array.from(target.map.entries(), ([key, item]): Array<unknown> => [key, item]))
case "forEach": {
const apply = this.applyCollectionCallback(args[0], "Map.forEach", node)
return Effect.gen(function* () {
// Snapshot iteration, matching the array-method callback contract.
for (const [key, item] of Array.from(target.map.entries())) yield* apply([item, key, target])
return undefined
})
}
default:
throw new InterpreterRuntimeError(`Map method '${name}' is not available in CodeMode.`, node)
}
}
private invokeSetMethod(
target: SandboxSet,
name: string,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> {
switch (name) {
case "has":
return Effect.succeed(target.set.has(args[0]))
case "add":
return Effect.sync(() => {
target.set.add(args[0])
return target
})
case "delete":
return Effect.sync(() => target.set.delete(args[0]))
case "clear":
return Effect.sync(() => {
target.set.clear()
return undefined
})
case "keys":
case "values":
return Effect.sync(() => Array.from(target.set.values()))
case "entries":
return Effect.sync(() => Array.from(target.set.values(), (item): Array<unknown> => [item, item]))
case "forEach": {
const apply = this.applyCollectionCallback(args[0], "Set.forEach", node)
return Effect.gen(function* () {
for (const item of Array.from(target.set.values())) yield* apply([item, item, target])
return undefined
})
}
default:
throw new InterpreterRuntimeError(`Set method '${name}' is not available in CodeMode.`, node)
}
}
private invokeArrayMethod(
target: Array<unknown>,
name: string,
args: Array<unknown>,
node: AstNode,
): Effect.Effect<unknown, unknown, R> {
const optNumber = (value: unknown, label: string): number | undefined => {
if (value === undefined) return undefined
if (typeof value !== "number")
throw new InterpreterRuntimeError(`Array.${name} expects ${label} to be a number.`, node)
return value
}
switch (name) {
case "join": {
if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
throw new InterpreterRuntimeError("Array.join expects zero arguments or one string separator.", node)
}
const input = boundedData(target, "Array.join input") as Array<unknown>
return Effect.succeed(
input.map((item) => coerceToString(item ?? "")).join(args.length === 0 ? "," : (args[0] as string)),
)
}
case "includes":
if (args.length === 0 || args.length > 2)
throw new InterpreterRuntimeError("Array.includes expects a value and optional start index.", node)
return Effect.succeed(target.includes(args[0], optNumber(args[1], "start index")))
case "indexOf":
return Effect.succeed(target.indexOf(args[0], optNumber(args[1], "start index")))
case "lastIndexOf":
return Effect.succeed(
args[1] === undefined
? target.lastIndexOf(args[0])
: target.lastIndexOf(args[0], optNumber(args[1], "start index")),
)
case "at":
return Effect.succeed(target.at(optNumber(args[0], "index") ?? 0))
case "slice":
return Effect.succeed(target.slice(optNumber(args[0], "start"), optNumber(args[1], "end")))
case "concat":
return Effect.succeed(target.concat(...args))
case "flat":
return Effect.succeed(target.flat(optNumber(args[0], "depth") ?? 1))
case "reverse":
return Effect.succeed([...target].reverse())
case "sort":
case "toSorted":
return this.sortArray(target, args[0], node)
case "toReversed":
return Effect.succeed([...target].reverse())
case "with": {
const index = optNumber(args[0], "index") ?? 0
const resolved = index < 0 ? target.length + index : index
if (resolved < 0 || resolved >= target.length) {
throw new InterpreterRuntimeError("Array.with index is out of range.", node)
}
const copied = [...target]
copied[resolved] = args[1]
return Effect.succeed(copied)
}
case "push": {
// Validate before mutating (so no rollback is needed): inserting a container into
// itself would create a cycle no later walk could survive.
for (const item of args) this.rejectCircularInsertion(target, item, "Array.push result", node)
target.push(...args)
return Effect.succeed(target.length)
}
case "unshift": {
for (const item of args) this.rejectCircularInsertion(target, item, "Array.unshift result", node)
target.unshift(...args)
return Effect.succeed(target.length)
}
case "pop":
return Effect.succeed(target.pop())
case "shift":
return Effect.succeed(target.shift())
case "splice": {
// Mutates in place and returns the removed elements, exactly like JS: one argument
// removes to the end, an undefined delete count removes nothing.
if (args.length === 0) return Effect.succeed(target.splice(0, 0))
const start = optNumber(args[0], "start") ?? 0
if (args.length === 1) return Effect.succeed(target.splice(start))
const deleteCount = optNumber(args[1], "delete count") ?? 0
const inserted = args.slice(2)
for (const item of inserted) this.rejectCircularInsertion(target, item, "Array.splice result", node)
return Effect.succeed(target.splice(start, deleteCount, ...inserted))
}
case "fill": {
this.rejectCircularInsertion(target, args[0], "Array.fill result", node)
return Effect.succeed(target.fill(args[0], optNumber(args[1], "start"), optNumber(args[2], "end")))
}
case "copyWithin":
return Effect.succeed(
target.copyWithin(
optNumber(args[0], "target index") ?? 0,
optNumber(args[1], "start") ?? 0,
optNumber(args[2], "end"),
),
)
// keys/values/entries return arrays (not iterators), matching the Map/Set convention;
// they work with for...of and spread either way.
case "keys":
return Effect.succeed(Array.from(target.keys()))
case "values":
return Effect.succeed([...target])
case "entries":
return Effect.succeed(Array.from(target.entries(), ([index, item]): Array<unknown> => [index, item]))
}
const callback = args[0]
if (!(callback instanceof CodeModeFunction) && !(callback instanceof CoercionFunction)) {
throw new InterpreterRuntimeError(`Array.${name} expects a function callback.`, node)
}
const self = this
// Accept a user arrow function or a builtin coercion callable (Boolean/String/Number), so the
// idioms `filter(Boolean)` / `map(String)` / `map(Number)` work as in JS. Coercions are
// synchronous; only CodeModeFunctions can await tool calls.
const apply = (callbackArgs: Array<unknown>): Effect.Effect<unknown, unknown, R> =>
callback instanceof CoercionFunction
? Effect.succeed(invokeCoercion(callback, callbackArgs, node))
: self.invokeFunction(callback, callbackArgs)
return Effect.gen(function* () {
// Iterate a snapshot taken at call time so a callback that mutates the array can't
// self-extend the loop - matching JS, where elements appended during iteration are not visited.
const items = target.slice()
switch (name) {
case "map": {
const values: Array<unknown> = []
for (const [index, item] of items.entries()) values.push(yield* apply([item, index, items]))
return values
}
case "flatMap": {
const values: Array<unknown> = []
for (const [index, item] of items.entries()) {
const mapped = yield* apply([item, index, items])
if (Array.isArray(mapped)) values.push(...mapped)
else values.push(mapped)
}
return values
}
case "filter": {
const values: Array<unknown> = []
for (const [index, item] of items.entries()) {
if (yield* apply([item, index, items])) values.push(item)
}
return values
}
case "find":
for (const [index, item] of items.entries()) {
if (yield* apply([item, index, items])) return item
}
return undefined
case "findIndex":
for (const [index, item] of items.entries()) {
if (yield* apply([item, index, items])) return index
}
return -1
case "some":
for (const [index, item] of items.entries()) {
if (yield* apply([item, index, items])) return true
}
return false
case "every":
for (const [index, item] of items.entries()) {
if (!(yield* apply([item, index, items]))) return false
}
return true
case "forEach":
for (const [index, item] of items.entries()) yield* apply([item, index, items])
return undefined
case "reduce": {
let accumulator: unknown
let start: number
if (args.length >= 2) {
accumulator = args[1]
start = 0
} else {
if (items.length === 0)
throw new InterpreterRuntimeError("Array.reduce of an empty array with no initial value.", node)
accumulator = items[0]
start = 1
}
for (let index = start; index < items.length; index += 1) {
accumulator = yield* apply([accumulator, items[index], index, items])
}
return accumulator
}
case "reduceRight": {
let accumulator: unknown
let start: number
if (args.length >= 2) {
accumulator = args[1]
start = items.length - 1
} else {
if (items.length === 0)
throw new InterpreterRuntimeError("Array.reduceRight of an empty array with no initial value.", node)
accumulator = items[items.length - 1]
start = items.length - 2
}
for (let index = start; index >= 0; index -= 1) {
accumulator = yield* apply([accumulator, items[index], index, items])
}
return accumulator
}
case "findLast":
for (let index = items.length - 1; index >= 0; index -= 1) {
if (yield* apply([items[index], index, items])) return items[index]
}
return undefined
case "findLastIndex":
for (let index = items.length - 1; index >= 0; index -= 1) {
if (yield* apply([items[index], index, items])) return index
}
return -1
}
throw new InterpreterRuntimeError(`Array method '${name}' is not available in CodeMode.`, node)
})
}
private sortArray(
target: Array<unknown>,
comparator: unknown,
node: AstNode,
): Effect.Effect<Array<unknown>, unknown, R> {
if (comparator !== undefined && !(comparator instanceof CodeModeFunction)) {
throw new InterpreterRuntimeError("Array.sort expects an arrow function comparator.", node)
}
if (!(comparator instanceof CodeModeFunction)) {
return Effect.sync(() =>
[...target].sort((a, b) => {
const left = coerceToString(a)
const right = coerceToString(b)
return left < right ? -1 : left > right ? 1 : 0
}),
)
}
const self = this
const mergeSort = (items: Array<unknown>): Effect.Effect<Array<unknown>, unknown, R> => {
if (items.length <= 1) return Effect.succeed(items)
const midpoint = Math.floor(items.length / 2)
return Effect.gen(function* () {
const left = yield* mergeSort(items.slice(0, midpoint))
const right = yield* mergeSort(items.slice(midpoint))
const merged: Array<unknown> = []
let leftIndex = 0
let rightIndex = 0
while (leftIndex < left.length && rightIndex < right.length) {
// Coerce the comparator's result like JS ToNumber (data objects -> NaN, never a host
// crash) and treat NaN as 0 - the spec's "no consistent order" -> keep the left element.
const order = coerceToNumber(yield* self.invokeFunction(comparator, [left[leftIndex], right[rightIndex]]))
if (Number.isNaN(order) || order <= 0) merged.push(left[leftIndex++])
else merged.push(right[rightIndex++])
}
return [...merged, ...left.slice(leftIndex), ...right.slice(rightIndex)]
})
}
// Per spec, undefined elements sort to the end and the comparator is never called on them.
const defined = target.filter((item) => item !== undefined)
const undefinedCount = target.length - defined.length
return Effect.map(mergeSort(defined), (items) => [...items, ...Array(undefinedCount).fill(undefined)])
}
private evaluateObjectExpression(node: AstNode): Effect.Effect<Record<string, unknown>, unknown, R> {
const objectValue: Record<string, unknown> = Object.create(null) as Record<string, unknown>
const properties = getArray(node, "properties")
const self = this
return Effect.gen(function* () {
for (const propertyValue of properties) {
const property = asNode(propertyValue, "properties")
if (property.type === "SpreadElement") {
const spread = yield* self.evaluateExpression(getNode(property, "argument"))
// JS treats `{ ...null }` / `{ ...undefined }` as a no-op, so the common
// `{ ...maybeOpts, override }` merge works when the operand is absent. Sandbox values
// (Date/RegExp/Map/Set) have no own enumerable properties in JS, so they are no-ops too.
if (spread === null || spread === undefined || isSandboxValue(spread)) continue
if (typeof spread !== "object" || Array.isArray(spread) || isRuntimeReference(spread)) {
throw new InterpreterRuntimeError(
"Object spread requires a data object in CodeMode.",
property,
"InvalidDataValue",
)
}
for (const [key, value] of Object.entries(spread)) {
if (isBlockedMember(key))
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, property)
objectValue[key] = value
}
continue
}
if (property.type !== "Property") {
throw new InterpreterRuntimeError("Only standard object properties are supported.", property)
}
if (getString(property, "kind") !== "init") {
throw new InterpreterRuntimeError("Only init object properties are supported.", property)
}
const keyNode = getNode(property, "key")
const valueNode = getNode(property, "value")
const computed = getBoolean(property, "computed")
let key: PropertyKey
if (computed) {
key = self.toPropertyKey(yield* self.evaluateExpression(keyNode), keyNode)
} else if (keyNode.type === "Identifier") {
key = getString(keyNode, "name")
} else if (keyNode.type === "Literal") {
key = self.toPropertyKey(keyNode.value, keyNode)
} else {
throw new InterpreterRuntimeError("Unsupported object property key shape.", keyNode)
}
if (isBlockedMember(String(key))) {
throw new InterpreterRuntimeError(`Property '${String(key)}' is not available in CodeMode.`, keyNode)
}
objectValue[String(key)] = yield* self.evaluateExpression(valueNode)
}
return objectValue
})
}
private evaluateArrayExpression(node: AstNode): Effect.Effect<Array<unknown>, unknown, R> {
const elements = getArray(node, "elements")
const values: Array<unknown> = []
const self = this
return Effect.gen(function* () {
for (const elementValue of elements) {
if (elementValue === null) {
values.push(undefined)
continue
}
const element = asNode(elementValue, "elements")
if (element.type === "SpreadElement") {
const spread = yield* self.evaluateExpression(getNode(element, "argument"))
const items = spreadItems(spread)
if (items === undefined)
throw new InterpreterRuntimeError(
"Array spread requires an array, string, Map, or Set in CodeMode.",
element,
)
values.push(...items)
} else {
values.push(yield* self.evaluateExpression(element))
}
}
return values
})
}
private evaluateTemplateLiteral(node: AstNode): Effect.Effect<string, unknown, R> {
const quasis = getArray(node, "quasis")
const expressions = getArray(node, "expressions")
let output = ""
const self = this
return Effect.gen(function* () {
for (let index = 0; index < quasis.length; index += 1) {
const quasi = asNode(quasis[index], "quasis")
const rawValue = quasi.value
if (!isRecord(rawValue) || typeof rawValue.cooked !== "string") {
throw new InterpreterRuntimeError("Invalid template literal quasi.", quasi)
}
output += rawValue.cooked
if (index < expressions.length) {
const raw = yield* self.evaluateExpression(asNode(expressions[index], "expressions"))
// The preserving checkpoint keeps sandbox values intact, so coerceToString renders
// them directly (ISO date, /regex/ literal form) instead of a JSON-serialized husk.
output += coerceToString(boundedData(raw, "Template interpolation"))
}
}
return output
})
}
private evaluateConditionalExpression(node: AstNode): Effect.Effect<unknown, unknown, R> {
return Effect.flatMap(this.evaluateExpression(getNode(node, "test")), (test) =>
this.evaluateExpression(getNode(node, test ? "consequent" : "alternate")),
)
}
private applyCompoundAssignment(operator: string, current: unknown, incoming: unknown, node: AstNode): unknown {
// `x op= y` is `x = x op y`: dispatch through the shared binary operator implementation
// so compound assignment inherits the same coercion semantics (Dates, data objects, ...).
// Only the arithmetic/bitwise operators are compoundable; logical assignments (&&=/||=/??=)
// short-circuit and are handled by evaluateLogicalAssignment before reaching here.
if (!compoundOperators.has(operator)) {
throw new InterpreterRuntimeError(`Unsupported assignment operator '${operator}'.`, node)
}
return this.applyBinaryOperator(operator.slice(0, -1), current, incoming, node)
}
private getMemberReference(
node: AstNode,
): Effect.Effect<
| MemberReference
| ToolReference
| PromiseMethodReference
| IntrinsicReference
| GlobalMethodReference
| ComputedValue
| typeof OptionalShortCircuit
| undefined,
unknown,
R
> {
const objectNode = getNode(node, "object")
const propertyNode = getNode(node, "property")
const computed = getBoolean(node, "computed")
const optional = node.optional === true
const self = this
return Effect.gen(function* () {
const objectValue = yield* self.evaluateExpression(objectNode)
if (objectValue === OptionalShortCircuit) return OptionalShortCircuit
if ((objectValue === null || objectValue === undefined) && optional) return OptionalShortCircuit
const key = computed
? self.toPropertyKey(yield* self.evaluateExpression(propertyNode), propertyNode)
: propertyNode.type === "Identifier"
? getString(propertyNode, "name")
: self.toPropertyKey(yield* self.evaluateExpression(propertyNode), propertyNode)
if (objectValue instanceof ToolReference) {
if (typeof key !== "string" || isBlockedMember(key)) {
throw new InterpreterRuntimeError("Tool paths must use safe string property names.", propertyNode)
}
return new ToolReference([...objectValue.path, key])
}
if (objectValue instanceof PromiseNamespace) {
if (typeof key === "string" && promiseStatics.has(key as PromiseMethodName)) {
return new PromiseMethodReference(key as PromiseMethodName)
}
throw new InterpreterRuntimeError(
`Promise.${String(key)} is not available in CodeMode. Available: Promise.all, Promise.allSettled, Promise.race, Promise.resolve, and Promise.reject; consume promises with await.`,
propertyNode,
)
}
if (objectValue instanceof GlobalNamespace) {
if (typeof key !== "string" || isBlockedMember(key)) {
throw new InterpreterRuntimeError(
`${objectValue.name}.${String(key)} is not available in CodeMode.`,
propertyNode,
)
}
if (objectValue.name === "Math" && mathConstants.has(key)) {
return new ComputedValue((Math as unknown as Record<string, number>)[key])
}
return new GlobalMethodReference(objectValue.name, key)
}
if (typeof objectValue === "string") {
if (key === "length") return new ComputedValue(objectValue.length)
if (typeof key === "number") return new ComputedValue(objectValue[key])
if (typeof key === "string" && /^\d+$/.test(key)) return new ComputedValue(objectValue[Number(key)])
if (typeof key === "string" && stringMethods.has(key)) return new IntrinsicReference(objectValue, key)
// Unknown property on a string reads as `undefined`, matching JS (`"x".foo === undefined`),
// instead of throwing - so defensive access like `result?.login ?? result` on a JSON-string
// tool result doesn't crash. (Optional chaining only guards null/undefined receivers, so a
// real string still reaches here.) Only the method allowlist above yields callables.
return new ComputedValue(undefined)
}
if (typeof objectValue === "number") {
if (typeof key === "string" && numberMethods.has(key)) return new IntrinsicReference(objectValue, key)
// Unknown property on a number reads as `undefined`, matching JS, rather than throwing.
return new ComputedValue(undefined)
}
// Number / String expose a small allowlist of statics; everything else stays opaque.
if (objectValue instanceof CoercionFunction && typeof key === "string" && !isBlockedMember(key)) {
if (objectValue.name === "Number" && numberConstants.has(key)) {
return new ComputedValue((Number as unknown as Record<string, number>)[key])
}
if (objectValue.name === "Number" && numberStatics.has(key)) return new GlobalMethodReference("Number", key)
if (objectValue.name === "String" && stringStatics.has(key)) return new GlobalMethodReference("String", key)
}
// Sandbox value types expose their method/property allowlists; any other key reads as
// `undefined`, consistent with unknown-property reads on strings/numbers/arrays.
if (objectValue instanceof SandboxDate) {
if (typeof key === "string" && dateMethods.has(key)) return new IntrinsicReference(objectValue, key)
return new ComputedValue(undefined)
}
if (objectValue instanceof SandboxRegExp) {
if (typeof key === "string" && regexpProperties.has(key)) {
return new ComputedValue((objectValue.regex as unknown as Record<string, unknown>)[key])
}
if (typeof key === "string" && regexpMethods.has(key)) return new IntrinsicReference(objectValue, key)
return new ComputedValue(undefined)
}
if (objectValue instanceof SandboxMap) {
if (key === "size") return new ComputedValue(objectValue.map.size)
if (typeof key === "string" && mapMethods.has(key)) return new IntrinsicReference(objectValue, key)
return new ComputedValue(undefined)
}
if (objectValue instanceof SandboxSet) {
if (key === "size") return new ComputedValue(objectValue.set.size)
if (typeof key === "string" && setMethods.has(key)) return new IntrinsicReference(objectValue, key)
return new ComputedValue(undefined)
}
// Any property access on a promise is a confused program (`p.then(...)`, `p.value`);
// reading `undefined` here would hide the missing await, so both paths get an explicit,
// await-hinting error instead of the forgiving unknown-property fallthrough.
if (objectValue instanceof SandboxPromise) {
if (key === "then" || key === "catch" || key === "finally") {
throw new InterpreterRuntimeError(
`Promise.prototype.${String(key)} is not supported in CodeMode; use await instead (with try/catch to handle failures) - e.g. \`const result = await tools.ns.tool(...)\`.`,
propertyNode,
"UnsupportedSyntax",
[supportedSyntaxMessage],
)
}
throw new InterpreterRuntimeError(
"This value is an un-awaited Promise and has no readable properties; await it first - e.g. `const result = await tools.ns.tool(...)`.",
objectNode,
"InvalidDataValue",
)
}
if (isRuntimeReference(objectValue)) {
throw new InterpreterRuntimeError(
"CodeMode runtime references are opaque and do not expose properties.",
objectNode,
"InvalidDataValue",
)
}
if (typeof objectValue !== "object" || objectValue === null) {
throw new InterpreterRuntimeError("Cannot access a property on a non-object value.", objectNode)
}
if (typeof key === "string" && isBlockedMember(key)) {
throw new InterpreterRuntimeError(`Property '${key}' is not available in CodeMode.`, propertyNode)
}
if (Array.isArray(objectValue)) {
if (
key !== "length" &&
!(typeof key === "string" && arrayMethods.has(key)) &&
typeof key !== "number" &&
!/^\d+$/.test(key)
) {
// Own non-index properties read through (match results carry index/groups); like JS,
// they are readable in place and dropped by JSON at data boundaries.
if (typeof key === "string" && Object.hasOwn(objectValue, key)) {
return new ComputedValue((objectValue as Record<string, unknown> & Array<unknown>)[key])
}
// Unknown property on an array reads as `undefined`, matching JS (`[1,2].foo === undefined`),
// instead of throwing - so defensive access under optional chaining behaves as expected.
return new ComputedValue(undefined)
}
return { target: objectValue, key }
}
return { target: objectValue as SafeObject, key }
})
}
private readMember(node: AstNode): Effect.Effect<unknown, unknown, R> {
return Effect.map(this.getMemberReference(node), (reference) => {
if (reference === OptionalShortCircuit) return OptionalShortCircuit
if (reference instanceof ComputedValue) return reference.value
if (
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
)
return reference
if (Array.isArray(reference.target)) {
if (typeof reference.key === "string" && arrayMethods.has(reference.key)) {
return new IntrinsicReference(reference.target, reference.key)
}
return reference.key === "length" ? reference.target.length : reference.target[Number(reference.key)]
}
return reference.target[String(reference.key)]
})
}
private writeMember(node: AstNode, value: unknown): Effect.Effect<unknown, unknown, R> {
return this.modifyMember(node, () => Effect.succeed({ write: true, next: value, result: value }))
}
// Resolves the member reference EXACTLY ONCE (so a side-effecting object/key expression
// runs once), then lets `compute` decide whether to write - enabling compound assignment,
// updates, plain writes, and short-circuiting logical assignment to share one safe path.
private modifyMember(
node: AstNode,
compute: (current: unknown) => Effect.Effect<{ write: boolean; next: unknown; result: unknown }, unknown, R>,
): Effect.Effect<unknown, unknown, R> {
const self = this
return Effect.gen(function* () {
const reference = yield* self.getMemberReference(node)
if (
reference === OptionalShortCircuit ||
reference instanceof ComputedValue ||
reference === undefined ||
reference instanceof ToolReference ||
reference instanceof PromiseMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
) {
throw new InterpreterRuntimeError("Only data fields may be assigned in CodeMode.", node)
}
if (Array.isArray(reference.target)) {
if (reference.key === "length")
throw new InterpreterRuntimeError("Array length cannot be assigned in CodeMode.", node)
if (typeof reference.key === "string" && arrayMethods.has(reference.key)) {
throw new InterpreterRuntimeError("Array methods cannot be assigned in CodeMode.", node)
}
}
const key = Array.isArray(reference.target) ? Number(reference.key) : String(reference.key)
const current = (reference.target as Record<PropertyKey, unknown>)[key]
const { write, next, result } = yield* compute(current)
if (write) self.assignToReference(reference, key, next, node)
return result
})
}
// Rejects inserting a value that (transitively) contains the container it is being inserted
// into - the mutation that would create a circular structure no later walk could survive.
private rejectCircularInsertion(
container: object,
value: unknown,
label: string,
node: AstNode,
seen = new Set<object>(),
): void {
if (value === container)
throw new InterpreterRuntimeError(`${label} contains a circular value.`, node, "InvalidDataValue")
if (value === null || typeof value !== "object" || isRuntimeReference(value) || seen.has(value)) return
seen.add(value)
const items = Array.isArray(value) ? value : Object.values(value)
for (const item of items) this.rejectCircularInsertion(container, item, label, node, seen)
seen.delete(value)
}
private assignToReference(reference: MemberReference, key: number | string, next: unknown, node: AstNode): void {
if (Array.isArray(reference.target)) {
const target = reference.target
const index = key as number
if (!Number.isInteger(index) || index < 0) {
throw new InterpreterRuntimeError(
"Array assignment index must be a non-negative integer.",
node,
"InvalidDataValue",
)
}
this.rejectCircularInsertion(target, next, "Array assignment result", node)
target[index] = next
return
}
const target = reference.target as SafeObject
const objectKey = key as string
this.rejectCircularInsertion(target, next, "Object assignment result", node)
target[objectKey] = next
}
private toPropertyKey(value: unknown, node: AstNode): string | number {
if (typeof value === "string" || typeof value === "number") {
return value
}
throw new InterpreterRuntimeError("Property key must be a string or number.", node)
}
private declare(name: string, value: unknown, mutable: boolean, node: AstNode): void {
const scope = this.currentScope()
// A pre-seeded parameter slot (initialized === false) is being bound for the first time;
// anything else already present is a genuine duplicate declaration.
const existing = scope.get(name)
if (existing && existing.initialized !== false) {
throw new InterpreterRuntimeError(`Identifier '${name}' has already been declared.`, node)
}
scope.set(name, { mutable, value, initialized: true })
}
private getIdentifierValue(name: string, node: AstNode): unknown {
const binding = this.resolveBinding(name)
if (!binding) {
throw new InterpreterRuntimeError(`Unknown identifier '${name}'.`, node).as("ReferenceError")
}
// A parameter default that forward-references a later (not-yet-bound) parameter - JS TDZ.
if (binding.initialized === false) {
throw new InterpreterRuntimeError(`Cannot access '${name}' before initialization.`, node).as("ReferenceError")
}
return binding.value
}
private setIdentifierValue(name: string, value: unknown, node: AstNode): unknown {
const binding = this.resolveBinding(name)
if (!binding) {
throw new InterpreterRuntimeError(`Unknown identifier '${name}'.`, node).as("ReferenceError")
}
if (!binding.mutable) {
throw new InterpreterRuntimeError(`Cannot assign to constant '${name}'.`, node).as("TypeError")
}
binding.value = value
return value
}
private resolveBinding(name: string): Binding | undefined {
for (let index = this.scopes.length - 1; index >= 0; index -= 1) {
const scope = this.scopes[index]
const binding = scope?.get(name)
if (binding) {
return binding
}
}
return undefined
}
private currentScope(): Map<string, Binding> {
const scope = this.scopes[this.scopes.length - 1]
if (!scope) {
throw new InterpreterRuntimeError("Interpreter scope stack is empty.")
}
return scope
}
private pushScope(): void {
this.scopes.push(new Map())
}
private popScope(): void {
this.scopes.pop()
}
}
/**
* Executes one Effect-native CodeMode program without constructing a reusable runtime.
*
* @example
* ```ts
* const result = yield* CodeMode.execute({
* tools: { lookup },
* code: `return await tools.lookup({ id: "order_42" })`,
* })
* ```
*/
const executeWithLimits = <const Tools extends Record<string, unknown>>(
options: ExecuteOptions<Tools>,
limits: ResolvedExecutionLimits,
searchIndex: ToolRuntime.DiscoveryPlan["searchIndex"],
): Effect.Effect<ExecuteResult, never, Services<Tools>> => {
const hooks = {
...(options.onToolCallStart === undefined ? {} : { onToolCallStart: options.onToolCallStart }),
...(options.onToolCallEnd === undefined ? {} : { onToolCallEnd: options.onToolCallEnd }),
}
const tools = ToolRuntime.make(
(options.tools ?? {}) as HostTools<Services<Tools>>,
limits.maxToolCalls,
hooks,
searchIndex,
)
const logs: Array<string> = []
const logged = () => (logs.length > 0 ? { logs: [...logs] } : {})
if (options.code.trim().length === 0) {
return Effect.succeed({
ok: false,
error: { kind: "ParseError", message: "Code cannot be empty." },
toolCalls: tools.calls,
})
}
const operation = Effect.gen(function* () {
const program = parseProgram(options.code)
const interpreter = new Interpreter<Services<Tools>>(tools.invoke, tools.keys, logs)
const value = yield* interpreter.run(program)
const result = copyOut(copyIn(value, "Execution result"), true) as DataValue
return {
ok: true,
value: result,
...logged(),
toolCalls: tools.calls,
} satisfies ExecuteResult
}).pipe((program) => {
const timeoutMs = limits.timeoutMs
if (timeoutMs === undefined) return program
return program.pipe(
Effect.timeoutOrElse({
duration: timeoutMs,
orElse: () =>
Effect.succeed({
ok: false,
error: { kind: "TimeoutExceeded", message: `Execution timed out after ${timeoutMs}ms.` },
...logged(),
toolCalls: tools.calls,
} satisfies ExecuteResult),
}),
)
})
return operation.pipe(
Effect.catchCause((cause) =>
Cause.hasInterruptsOnly(cause)
? Effect.interrupt
: Effect.succeed({
ok: false,
error: normalizeError(Cause.squash(cause)),
...logged(),
toolCalls: tools.calls,
} satisfies ExecuteResult),
),
Effect.map((result) => (limits.maxOutputBytes === undefined ? result : boundOutput(result, limits.maxOutputBytes))),
)
}
const utf8ByteLength = (value: string): number => new TextEncoder().encode(value).byteLength
// Truncates to a UTF-8 byte budget without splitting a code point (a split multi-byte
// sequence decodes to a replacement character, which is dropped).
const utf8Truncate = (value: string, maxBytes: number): string => {
const bytes = new TextEncoder().encode(value)
if (bytes.byteLength <= maxBytes) return value
const text = new TextDecoder("utf-8").decode(bytes.slice(0, Math.max(0, maxBytes)))
return text.endsWith("\uFFFD") ? text.slice(0, -1) : text
}
/**
* Bounds the model-facing output (serialized result value plus logs) to `maxOutputBytes`.
* Oversized values are replaced by their truncated serialized text with an explanatory marker,
* and logs are kept from the start until the remaining budget is exhausted. Truncation never
* fails the execution; `truncated: true` marks affected results. Only runs when the host set
* `maxOutputBytes` - with the limit absent, output passes through unbounded.
*/
const boundOutput = (result: ExecuteResult, maxOutputBytes: number): ExecuteResult => {
let truncated = false
let value: DataValue = null
let valueBytes = 0
if (result.ok) {
const serialized = JSON.stringify(result.value) ?? "null"
const bytes = utf8ByteLength(serialized)
if (bytes > maxOutputBytes) {
truncated = true
value = `${utf8Truncate(serialized, maxOutputBytes)} [result truncated: ${bytes} bytes exceeds the ${maxOutputBytes}-byte output limit; return a smaller value]`
valueBytes = maxOutputBytes
} else {
value = result.value
valueBytes = bytes
}
}
const logs = result.logs ?? []
const kept: Array<string> = []
const logBudget = Math.max(0, maxOutputBytes - valueBytes)
let logBytes = 0
for (const line of logs) {
const lineBytes = utf8ByteLength(line) + 1
if (logBytes + lineBytes > logBudget) break
logBytes += lineBytes
kept.push(line)
}
if (kept.length < logs.length) {
truncated = true
kept.push(`[logs truncated: showing ${kept.length} of ${logs.length} lines]`)
}
if (!truncated) return result
const logsPart = kept.length > 0 ? { logs: kept } : {}
return result.ok
? { ok: true, value, ...logsPart, truncated: true, toolCalls: result.toolCalls }
: { ok: false, error: result.error, ...logsPart, truncated: true, toolCalls: result.toolCalls }
}
export const execute = <const Tools extends Record<string, unknown>>(
options: ExecuteOptions<Tools>,
): Effect.Effect<ExecuteResult, never, Services<Tools>> => {
const tools = (options.tools ?? {}) as HostTools<Services<Tools>>
ToolRuntime.assertValidTools(tools)
return executeWithLimits(options, resolveExecutionLimits(options.limits), ToolRuntime.searchIndex(tools))
}
/**
* Creates an Effect-native runtime over explicit, schema-described tools.
*
* Use `execute` for host-driven execution or `agentTool` to expose one confined code tool to an
* agent framework. Tool requirements remain in the returned Effect environment.
*
* @example
* ```ts
* const runtime = CodeMode.make({ tools: { orders: { lookup } } })
* const code = runtime.agentTool()
* ```
*/
export const make = <const Tools extends Record<string, unknown> = {}>(
options: CodeModeOptions<Tools> = {} as CodeModeOptions<Tools>,
): CodeModeRuntime<Services<Tools>> => {
const tools = (options.tools ?? {}) as HostTools<Services<Tools>>
ToolRuntime.assertValidTools(tools)
const limits = resolveExecutionLimits(options.limits)
const discovery = ToolRuntime.discoveryPlan(tools, options.discovery?.maxInlineCatalogTokens)
const executeProgram = (code: string) => executeWithLimits<Tools>({ ...options, code }, limits, discovery.searchIndex)
const catalog = discovery.catalog
const instructions = discovery.instructions
return {
catalog: () => catalog,
instructions: () => instructions,
agentTool: () => ({
name: "code",
description: instructions,
input: ExecuteInputSchema,
output: ExecuteResultSchema,
execute: ({ code }) => executeProgram(code),
}),
execute: executeProgram,
}
}
/** Constructors for one-shot and reusable CodeMode execution. */
export const CodeMode = { make, execute }
-21
View File
@@ -1,21 +0,0 @@
export { ToolError, CodeMode, ExecuteInputSchema, ExecuteResultSchema, toolError } from "./codemode.js"
export { Tool } from "./tool.js"
export type { Definition as ToolDefinition, JsonSchema, ToolSchema } from "./tool.js"
export type { ToolCallEnded, ToolCallHooks } from "./tool-runtime.js"
export type {
AgentToolDefinition,
CodeModeOptions,
CodeModeRuntime,
DataValue,
Diagnostic,
DiagnosticKind,
DiscoveryOptions,
ExecuteFailure,
ExecuteOptions,
ExecuteResult,
ExecuteSuccess,
ExecutionLimits,
ToolCall,
ToolCallStarted,
ToolDescription,
} from "./codemode.js"
-10
View File
@@ -1,10 +0,0 @@
/**
* Token estimation for budgeting model-facing text. Copied from
* `@opencode-ai/core/util/token` (chars / 4) so this package stays
* dependency-free; keep the two in sync if the heuristic ever changes.
*/
export * as Token from "./token.js"
const CHARS_PER_TOKEN = 4
export const estimate = (input: string) => Math.max(0, Math.round(input.length / CHARS_PER_TOKEN))
-11
View File
@@ -1,11 +0,0 @@
import { Schema } from "effect"
/** Safe operational refusal from a standard tool pack, reported as `ToolFailure`. */
export class ToolError extends Schema.TaggedErrorClass<ToolError>()("ToolError", {
message: Schema.String,
cause: Schema.optionalKey(Schema.Defect()),
}) {}
/** Creates a tool refusal whose message is safe to include in an execution diagnostic. */
export const toolError = (message: string, cause?: unknown): ToolError =>
new ToolError({ message, ...(cause === undefined ? {} : { cause }) })
-830
View File
@@ -1,830 +0,0 @@
import { Cause, Effect } from "effect"
import { ToolError, toolError } from "./tool-error.js"
import {
decodeInput as decodeToolInput,
decodeOutput as decodeToolOutput,
identifierSegment,
inputProperties,
inputTypeScript,
isDefinition as isToolDefinition,
outputTypeScript,
type Definition,
} from "./tool.js"
import { estimate } from "./token.js"
import { SandboxDate, SandboxMap, SandboxPromise, SandboxRegExp, SandboxSet } from "./values.js"
export type HostTool<R = never> = (...args: Array<unknown>) => Effect.Effect<unknown, unknown, R>
export type HostTools<R = never> = {
[name: string]: HostTool<R> | Definition<R> | HostTools<R>
}
export type Services<Tools> = Tools extends (...args: Array<unknown>) => Effect.Effect<unknown, unknown, infer R>
? R
: Tools extends {
readonly _tag: "CodeModeTool"
readonly run: (input: unknown) => Effect.Effect<unknown, unknown, infer R>
}
? R
: Tools extends object
? string extends keyof Tools
? never
: Services<Tools[keyof Tools]>
: never
/** Minimal audit record retained for each admitted tool call. */
export type ToolCall = {
readonly name: string
}
/** Decoded tool call observed immediately before tool execution. */
export type ToolCallStarted = {
readonly index: number
readonly name: string
readonly input: unknown
}
/** Completed tool call observed immediately after tool execution settles. */
export type ToolCallEnded = {
readonly index: number
readonly name: string
readonly input: unknown
readonly durationMs: number
readonly outcome: "success" | "failure"
/** Model-safe failure message; present only when `outcome` is `"failure"`. */
readonly message?: string
}
/** Non-throwing observation hooks fired around each admitted tool call. */
export type ToolCallHooks<R = never> = {
readonly onToolCallStart?: ((call: ToolCallStarted) => Effect.Effect<void, never, R>) | undefined
readonly onToolCallEnd?: ((call: ToolCallEnded) => Effect.Effect<void, never, R>) | undefined
}
/** Model-visible description of one schema-backed tool. */
export type ToolDescription = {
readonly path: string
readonly description: string
readonly signature: string
}
export type SafeObject = Record<string, unknown>
const reservedNamespace = "$codemode"
const defaultMaxInlineCatalogTokens = 2_000
const defaultSearchLimit = 10
const searchSignature =
"tools.$codemode.search({ query?: string, namespace?: string, limit?: number }): Promise<{ items: Array<{ path: string; description: string; signature: string }>; total: number }>"
const toolExpression = (path: string) =>
"tools" +
path
.split(".")
.map((segment) => (identifierSegment.test(segment) ? `.${segment}` : `[${JSON.stringify(segment)}]`))
.join("")
export class ToolReference {
constructor(readonly path: ReadonlyArray<string>) {}
}
/**
* Maximum nesting depth for values crossing a data boundary. Fixed (not a configurable
* limit) purely because it produces a clearer diagnostic than a native stack-overflow
* RangeError would.
*/
const MAX_VALUE_DEPTH = 32
export class ToolRuntimeError extends Error {
constructor(
readonly kind:
| "UnknownTool"
| "InvalidToolInput"
| "InvalidToolOutput"
| "InvalidDataValue"
| "ToolCallLimitExceeded",
message: string,
readonly suggestions: ReadonlyArray<string> = [],
) {
super(message)
this.name = "ToolRuntimeError"
}
}
const isDefinition = <R>(value: HostTool<R> | Definition<R> | HostTools<R>): value is Definition<R> =>
isToolDefinition<R>(value)
const runHost = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, ToolError, R> =>
effect.pipe(
Effect.catchCause((cause) => {
if (Cause.hasInterruptsOnly(cause)) return Effect.interrupt
const error = Cause.squash(cause)
return Effect.fail(error instanceof ToolError ? error : toolError("Tool execution failed", error))
}),
)
const blockedMemberNames = new Set(["__proto__", "constructor", "prototype"])
export const isBlockedMember = (name: string): boolean => blockedMemberNames.has(name)
/**
* Validates and copies a value against the plain-data contract (depth, circularity, plain
* objects only, blocked properties, data-only leaves).
*
* Two modes share the walk:
* - **Boundary** (`preserveSandboxValues` false, the default): the host<->sandbox boundary -
* final results, tool-call arguments, `JSON.stringify`. Sandbox value types serialize
* exactly as JSON.stringify would: Date -> ISO string (invalid -> null), RegExp/Map/Set -> {}.
* - **Intra-sandbox checkpoint** (`preserveSandboxValues` true; see `boundedData` in
* codemode.ts): Date/RegExp/Map/Set instances pass through untouched (treated as leaves,
* contents not walked), so values flowing through `Object.*` helpers, coercion inputs, and
* other in-sandbox checkpoints stay fully usable (`.getTime()`, `.has()`, ...).
*
* Both modes reject un-awaited promises with an await-hinting diagnostic.
*/
export const copyIn = (value: unknown, label: string, preserveSandboxValues = false): unknown =>
copyBounded(value, label, 0, new Set(), preserveSandboxValues)
const copyBounded = (
value: unknown,
label: string,
depth: number,
seen: Set<object>,
preserveSandboxValues: boolean,
): unknown => {
if (depth > MAX_VALUE_DEPTH) {
throw new ToolRuntimeError("InvalidDataValue", `${label} exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`)
}
if (
value === null ||
value === undefined ||
typeof value === "string" ||
typeof value === "boolean" ||
// NaN/Infinity are allowed to exist as in-sandbox intermediates (matching real JS and a real
// engine) so defensive guards like `Number.isNaN(x)` / `parseInt(x) || 0` can run. They are
// normalized to `null` when the value leaves the sandbox - see copyOut - exactly as
// JSON.stringify already does at any tool boundary.
typeof value === "number"
) {
return value
}
if (typeof value !== "object") {
throw new ToolRuntimeError("InvalidDataValue", `${label} must contain data only.`)
}
// An un-awaited promise never crosses a data checkpoint as `{}`; the diagnostic tells the
// model exactly how to fix the program instead.
if (value instanceof SandboxPromise) {
throw new ToolRuntimeError(
"InvalidDataValue",
`${label} contains an un-awaited Promise; await tool calls (e.g. \`const result = await tools.ns.tool(...)\`) before using their results.`,
)
}
if (preserveSandboxValues) {
// Intra-sandbox checkpoints keep sandbox value instances alive as leaves; their contents
// are never walked here (Map/Set members are validated where mutation happens, and the
// real boundary still serializes them below).
if (
value instanceof SandboxDate ||
value instanceof SandboxRegExp ||
value instanceof SandboxMap ||
value instanceof SandboxSet
) {
return value
}
// Host instances cannot normally reach an intra-sandbox checkpoint (tool results cross
// the boundary first), but wrap them defensively rather than degrading to JSON forms.
if (value instanceof Date) return new SandboxDate(value.getTime())
if (value instanceof RegExp) return new SandboxRegExp(value.source, value.flags)
if (value instanceof Map) {
const wrapped = new SandboxMap()
for (const [key, item] of value.entries()) {
wrapped.map.set(copyBounded(key, label, depth + 1, seen, true), copyBounded(item, label, depth + 1, seen, true))
}
return wrapped
}
if (value instanceof Set) {
const wrapped = new SandboxSet()
for (const item of value.values()) wrapped.set.add(copyBounded(item, label, depth + 1, seen, true))
return wrapped
}
}
// Sandbox value types (and their host counterparts, which a host tool may legitimately
// return) serialize exactly as JSON.stringify would at the data boundary: a Date is its
// toJSON() ISO string (invalid -> null), and RegExp/Map/Set have no JSON form beyond {}.
if (value instanceof SandboxDate) {
return Number.isFinite(value.time) ? new Date(value.time).toISOString() : null
}
if (value instanceof Date) {
return Number.isFinite(value.getTime()) ? value.toISOString() : null
}
if (
value instanceof SandboxRegExp ||
value instanceof SandboxMap ||
value instanceof SandboxSet ||
value instanceof RegExp ||
value instanceof Map ||
value instanceof Set
) {
return Object.create(null) as SafeObject
}
if (seen.has(value)) {
throw new ToolRuntimeError("InvalidDataValue", `${label} contains a circular value.`)
}
seen.add(value)
if (Array.isArray(value)) {
const copied = value.map((item) => copyBounded(item, label, depth + 1, seen, preserveSandboxValues))
seen.delete(value)
return copied
}
const prototype = Object.getPrototypeOf(value)
if (prototype !== Object.prototype && prototype !== null) {
throw new ToolRuntimeError("InvalidDataValue", `${label} must contain plain objects only.`)
}
const copied: SafeObject = Object.create(null) as SafeObject
for (const [key, item] of Object.entries(value)) {
if (isBlockedMember(key)) {
throw new ToolRuntimeError("InvalidDataValue", `${label} contains blocked property '${key}'.`)
}
copied[key] = copyBounded(item, label, depth + 1, seen, preserveSandboxValues)
}
seen.delete(value)
return copied
}
export const copyOut = (value: unknown, undefinedAsNull = false): unknown => {
if (value === undefined && undefinedAsNull) return null
// Normalize non-finite numbers to null as the value crosses out of the sandbox (final return
// and tool-call arguments both funnel through here), matching JSON semantics - NaN/Infinity
// have no JSON representation, so JSON.stringify would produce null anyway.
if (typeof value === "number" && !Number.isFinite(value)) {
return null
}
if (Array.isArray(value)) {
return value.map((item) => copyOut(item, undefinedAsNull))
}
if (value !== null && typeof value === "object" && !(value instanceof ToolReference)) {
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, copyOut(item, undefinedAsNull)]))
}
return value
}
const definitions = <R>(
tools: HostTools<R>,
path: ReadonlyArray<string> = [],
): Array<{ path: string; definition: Definition<R> }> => {
const entries: Array<{ path: string; definition: Definition<R> }> = []
for (const [name, value] of Object.entries(tools)) {
const next = [...path, name]
if (isDefinition(value)) entries.push({ path: next.join("."), definition: value })
else if (typeof value !== "function") entries.push(...definitions(value, next))
}
return entries
}
const describeDefinition = <R>(path: string, definition: Definition<R>): ToolDescription => ({
path,
description: definition.description,
signature: `${toolExpression(path)}(input: ${inputTypeScript(definition)}): Promise<${outputTypeScript(definition)}>`,
})
const visibleDefinitions = <R>(tools: HostTools<R>) =>
definitions(tools).flatMap(({ path, definition }) => {
const description = describeDefinition(path, definition)
return [{ path, definition, description }]
})
export const catalog = <R>(tools: HostTools<R>): ReadonlyArray<ToolDescription> =>
visibleDefinitions(tools).map(({ description }) => description)
export type DiscoveryPlan = {
readonly catalog: ReadonlyArray<ToolDescription>
readonly instructions: string
readonly searchIndex: ReadonlyArray<SearchEntry>
}
export type SearchEntry = {
readonly description: ToolDescription
/**
* JSDoc-annotated multiline signature shown on search-result items; the compact
* single-line form (inline catalog lines) stays in `description.signature`.
*/
readonly signature: string
/** Top-level namespace (first path segment), matched by the search `namespace` option. */
readonly namespace: string
/** Lowercased path + description + input property names/descriptions, for substring matching. */
readonly searchText: string
}
/**
* Split a query into lowercased search terms. camelCase boundaries are split
* (`resolveLibrary` -> `resolve library`) and every non-alphanumeric character is a
* separator, so `resolve-library-id`, `resolveLibraryId`, and `resolve library id` all
* tokenize alike. Empties and the `*` wildcard are dropped.
*/
const tokenize = (query: string): Array<string> =>
query
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.toLowerCase()
.split(/[^a-z0-9]+/)
.filter((term) => term.length > 0 && term !== "*")
/**
* A term plus its naive singular variants (trailing "s"/"es" stripped), so a plural
* query term ("issues") still matches indexed text that only carries the singular
* ("issue"). Matching is one-directional substring containment, so the variants are
* needed only on the query side; scoring weights are unchanged - each field check
* passes when ANY form matches.
*/
const termForms = (term: string): Array<string> => {
const forms = [term]
if (term.endsWith("es") && term.length > 3) forms.push(term.slice(0, -2))
if (term.endsWith("s") && term.length > 2) forms.push(term.slice(0, -1))
return forms
}
const firstLine = (text: string) => text.split("\n", 1)[0]!.trim()
/** One-line description used on inline catalog lines; the full text stays in search results. */
const brief = (text: string, max = 120) => {
const line = firstLine(text)
return line.length > max ? line.slice(0, max - 1) + "..." : line
}
const catalogLine = (tool: ToolDescription) => {
const description = brief(tool.description)
return description === "" ? ` - ${tool.signature}` : ` - ${tool.signature} // ${description}`
}
const toSearchEntry = <R>(path: string, definition: Definition<R>, description: ToolDescription): SearchEntry => ({
description,
signature: `${toolExpression(path)}(input: ${inputTypeScript(definition, true)}): Promise<${outputTypeScript(definition, true)}>`,
namespace: path.split(".", 1)[0]!,
searchText: [
path,
definition.description,
...inputProperties(definition).flatMap(({ name, description: property }) =>
property === undefined ? [name] : [name, property],
),
]
.join("\n")
.toLowerCase(),
})
/** The runtime search index over every described tool. Search is always registered. */
export const searchIndex = <R>(tools: HostTools<R>): ReadonlyArray<SearchEntry> =>
visibleDefinitions(tools).map(({ path, definition, description }) => toSearchEntry(path, definition, description))
export const assertValidTools = <R>(tools: HostTools<R>): void => {
if (Object.hasOwn(tools, reservedNamespace)) {
throw new Error(`Tool namespace '${reservedNamespace}' is reserved for CodeMode discovery tools.`)
}
}
/**
* Budgeted catalog: every namespace is always listed with its tool count; full call
* signatures are inlined against the `maxInlineCatalogTokens` budget (estimated tokens,
* chars/4) round-robin across namespaces - in each round (namespaces alphabetical), every
* namespace still holding un-inlined tools attempts to place its next-cheapest line, and
* a namespace whose next line does not fit is done while the others keep going - so every
* namespace gets some representation before any namespace gets everything. The section
* states exactly how comprehensive it is - overall (COMPLETE vs PARTIAL) and per
* namespace. Namespace stub lines are never budgeted: every namespace appears with its
* tool count even at budget 0.
*/
export const discoveryPlan = <R>(
tools: HostTools<R>,
maxInlineCatalogTokens = defaultMaxInlineCatalogTokens,
): DiscoveryPlan => {
if (!Number.isSafeInteger(maxInlineCatalogTokens) || maxInlineCatalogTokens < 0) {
throw new RangeError("discovery.maxInlineCatalogTokens must be a non-negative safe integer")
}
const visible = visibleDefinitions(tools)
const described = visible.map(({ description }) => description)
const namespaces = new Map<string, Array<ToolDescription>>()
for (const tool of described) {
const [namespace = tool.path] = tool.path.split(".")
const group = namespaces.get(namespace) ?? []
group.push(tool)
namespaces.set(namespace, group)
}
const ordered = [...namespaces].sort(([left], [right]) => left.localeCompare(right))
// Select which signatures fit the budget before emitting, so the list can state
// exactly how comprehensive it is. Round-robin fairness: in each round (namespaces
// alphabetical), every namespace still holding un-inlined tools tries to place its
// next-cheapest line against the shared budget; a namespace whose next line does not
// fit is done - the others keep going - so every namespace gets some representation
// before any namespace gets everything.
const selections = ordered.map(([namespace, group]) => ({
namespace,
picked: new Set<ToolDescription>(),
queue: [...group].sort(
(left, right) =>
estimate(catalogLine(left)) - estimate(catalogLine(right)) || left.path.localeCompare(right.path),
),
}))
let used = 0
let active = selections.filter((selection) => selection.queue.length > 0)
while (active.length > 0) {
const stillActive: typeof active = []
for (const selection of active) {
const tool = selection.queue[0]!
const cost = estimate(catalogLine(tool))
if (used + cost > maxInlineCatalogTokens) continue
selection.queue.shift()
selection.picked.add(tool)
used += cost
if (selection.queue.length > 0) stillActive.push(selection)
}
active = stillActive
}
const shown = new Map<string, ReadonlySet<ToolDescription>>(
selections.map(({ namespace, picked }) => [namespace, picked]),
)
const totalShown = selections.reduce((total, { picked }) => total + picked.size, 0)
const complete = totalShown === described.length
const empty = described.length === 0
// Section order is deliberate: workflow first (the top is the least likely part of a long
// description to be truncated or skimmed away), then rules, then syntax, with the budgeted
// catalog at the bottom. Example call forms use placeholders - never a real or fabricated
// tool name - and show both dot and bracket notation so non-identifier names are not normalized.
const intro = [
"Write a CodeMode program to answer the request. Return code only.",
empty
? "Execute JavaScript in a confined runtime."
: complete
? "Execute JavaScript in a confined runtime. Inside this program, `tools` contains only the host-provided tools listed below; surrounding agent tools are not available unless listed here."
: "Execute JavaScript in a confined runtime. Inside this program, `tools` contains only the host-provided tools listed or searchable below; surrounding agent tools are not available unless listed here.",
...(empty ? [] : ["Do not infer or normalize tool names; use only exact signatures shown below or returned by search."]),
]
// The search step exists only when search is advertised (PARTIAL catalog); a COMPLETE
// catalog already shows every signature, so step 1 picks from the list instead.
const workflow = empty
? []
: [
"",
"## Workflow",
"",
...(complete
? [
"1. Pick a tool from the list under `## Available tools` - each line is the exact call signature; use it as-is rather than guessing segments.",
'2. Call it using the exact signature shown; bracket notation and quotes are part of the path.',
'3. Parse text results: `const data = typeof res === "string" ? JSON.parse(res) : res` - most tools return JSON as a string.',
"4. Return only the fields you need: `return { <field>: data.<field> }` - raw payloads get truncated and waste context.",
]
: [
'1. If the exact signature is not listed below, first search: `const { items } = await tools.$codemode.search({ query: "<intent + key nouns>" })`.',
"2. Read the matches: each item is `{ path, description, signature }` - read the description before using an unfamiliar tool.",
"3. Call the result's `path` as-is; bracket notation and quotes are part of the path.",
'4. Parse text results: `const data = typeof res === "string" ? JSON.parse(res) : res` - most tools return JSON as a string.',
"5. Return only the fields you need: `return { <field>: data.<field> }` - raw payloads get truncated and waste context.",
]),
]
const rules = empty
? []
: [
"",
"## Rules",
"",
complete
? "- Only tools listed here are available inside `tools`; tools from the surrounding agent/runtime are not implicitly exposed."
: "- Only tools listed here or returned by `tools.$codemode.search` are available inside `tools`; tools from the surrounding agent/runtime are not implicitly exposed.",
"- Filter, aggregate, and transform collections in code - never return them raw or call a tool per item across messages.",
"- A result typed `Promise<unknown>` has no guaranteed shape - verify what actually came back before relying on its fields.",
'- Run independent calls in parallel: `await Promise.all(items.map((item) => tools.<namespace>.<tool>(item)))`, or use `tools.<namespace>["tool-name"](item)` when the listed signature uses bracket notation.',
"- `Object.keys(tools)` lists namespaces; `Object.keys(tools.<namespace>)` lists its tools; `for...in` works on both.",
...(complete
? []
: ['- Browse one namespace: `await tools.$codemode.search({ query: "", namespace: "<name>" })`.']),
]
const syntax = [
"",
"## Syntax",
"",
"Standard modern JavaScript works: functions/closures, destructuring, template literals, loops, try/catch, spread, optional chaining, the usual Array/String/Object/Math/JSON methods, plus Date, RegExp, Map, Set, and Promise.all/allSettled/race/resolve/reject.",
"TypeScript type annotations are allowed and stripped before execution (decorators are not supported).",
"Not supported (each fails with a message naming the alternative): classes, generators, for await...of, .then/.catch/.finally (use await with try/catch).",
"Dates serialize to ISO strings at data boundaries; Map/Set/RegExp serialize to `{}`.",
]
const toolSection: Array<string> = [""]
if (empty) {
toolSection.push("## Available tools", "", "No tools are currently available.")
} else {
toolSection.push(
complete
? "## Available tools (COMPLETE list - every tool is shown below with its full call signature)"
: `## Available tools (PARTIAL - ${totalShown} of ${described.length} shown; find the rest with tools.$codemode.search)`,
"",
)
for (const [namespace, group] of ordered) {
const picked = shown.get(namespace)!
const count = `${group.length} tool${group.length === 1 ? "" : "s"}`
// Annotate only when a namespace is not fully shown, so a comprehensive
// namespace reads cleanly and a truncated one is unambiguous.
const label =
picked.size === group.length
? count
: picked.size === 0
? `${count}, none shown`
: `${count}, ${picked.size} shown`
toolSection.push(`- ${namespace} (${label})`)
for (const tool of group) if (picked.has(tool)) toolSection.push(catalogLine(tool))
}
if (!complete) {
toolSection.push("", "Search returns complete callable signatures:", `- ${searchSignature}`)
}
}
const lines = [...intro, ...workflow, ...rules, ...syntax, ...toolSection]
return {
catalog: described,
instructions: lines.join("\n"),
searchIndex: visible.map(({ path, definition, description }) => toSearchEntry(path, definition, description)),
}
}
/**
* The enumerable names at one node of the host tool tree - namespace names at the root,
* tool/namespace names below - powering `Object.keys(tools)` and `for...in` over tool
* references. A callable tool is a leaf and enumerates as `[]` (like `Object.keys` of a
* function in JS). An unknown path is an `UnknownTool` error pointing at the working
* discovery idioms, mirroring how calling an unknown tool fails.
*/
const namespaceKeys = <R>(
tools: HostTools<R>,
path: ReadonlyArray<string>,
searchEnabled: boolean,
): ReadonlyArray<string> => {
// The reserved discovery namespace is virtual (never present in the host tree); enumerate
// it explicitly so `Object.keys(tools.$codemode)` matches the callable surface.
if (searchEnabled && path.length === 1 && path[0] === reservedNamespace) return ["search"]
let value: HostTool<R> | Definition<R> | HostTools<R> = tools
for (const segment of path) {
if (
isBlockedMember(segment) ||
typeof value === "function" ||
isDefinition(value) ||
!Object.hasOwn(value, segment)
) {
throw new ToolRuntimeError(
"UnknownTool",
`Unknown tool namespace '${path.join(".")}'.`,
searchEnabled
? [
"Object.keys(tools) lists the available namespaces; tools.$codemode.search({ query }) finds described tools.",
]
: ["Object.keys(tools) lists the available namespaces."],
)
}
value = value[segment] as HostTool<R> | Definition<R> | HostTools<R>
}
if (typeof value === "function" || isDefinition(value)) return []
return Object.keys(value)
}
const resolve = <R>(
tools: HostTools<R>,
path: ReadonlyArray<string>,
searchEnabled: boolean,
): HostTool<R> | Definition<R> => {
let value: HostTool<R> | Definition<R> | HostTools<R> = tools
for (const segment of path) {
if (
isBlockedMember(segment) ||
typeof value === "function" ||
isDefinition(value) ||
!Object.hasOwn(value, segment)
) {
throw new ToolRuntimeError(
"UnknownTool",
`Unknown tool '${path.join(".")}'.`,
searchEnabled ? ["Use tools.$codemode.search({ query }) to find available described tools."] : [],
)
}
value = value[segment] as HostTool<R> | Definition<R> | HostTools<R>
}
if (typeof value !== "function" && !isDefinition(value)) {
throw new ToolRuntimeError("UnknownTool", `Tool '${path.join(".")}' is not callable.`)
}
return value
}
export type ToolRuntime<R = never> = {
readonly root: ToolReference
readonly calls: Array<ToolCall>
readonly invoke: (path: ReadonlyArray<string>, args: Array<unknown>) => Effect.Effect<unknown, unknown, R>
/** Enumerable namespace/tool names at one node of the host tool tree; see `namespaceKeys`. */
readonly keys: (path: ReadonlyArray<string>) => ReadonlyArray<string>
}
const failureMessage = (error: unknown): string =>
error instanceof ToolError || error instanceof ToolRuntimeError ? error.message : "Tool execution failed"
export const make = <R>(
tools: HostTools<R>,
/** Undefined means unlimited tool calls. */
maxToolCalls: number | undefined,
hooks?: ToolCallHooks<R>,
searchIndex?: ReadonlyArray<SearchEntry>,
): ToolRuntime<R> => {
const calls: Array<ToolCall> = []
const searchEnabled = searchIndex !== undefined
// Wraps the settling portion of a tool call so onToolCallEnd observes success and failure
// symmetrically. Interruption (e.g. the execution timeout) fires neither outcome.
const observeEnd = <A, E>(effect: Effect.Effect<A, E, R>, call: ToolCallStarted): Effect.Effect<A, E, R> => {
const onEnd = hooks?.onToolCallEnd
if (onEnd === undefined) return effect
const startedAt = Date.now()
return effect.pipe(
Effect.tap(() => onEnd({ ...call, durationMs: Date.now() - startedAt, outcome: "success" })),
Effect.tapError((error) =>
onEnd({ ...call, durationMs: Date.now() - startedAt, outcome: "failure", message: failureMessage(error) }),
),
)
}
const decodeOutput = (value: unknown, name: string) =>
Effect.try({
try: () => copyIn(value, `Result from tool '${name}'`),
catch: () => new ToolRuntimeError("InvalidToolOutput", `Invalid output from tool '${name}'.`),
})
const recordCall = (call: ToolCall): void => {
if (maxToolCalls !== undefined && calls.length >= maxToolCalls) {
throw new ToolRuntimeError("ToolCallLimitExceeded", `Execution exceeded its tool-call limit of ${maxToolCalls}.`)
}
calls.push(call)
}
return {
root: new ToolReference([]),
calls,
keys: (path) => namespaceKeys(tools, path, searchEnabled),
invoke: (path, args) =>
Effect.gen(function* () {
const name = path.join(".")
const externalArgs = args.map((arg) => copyOut(copyIn(arg, `Arguments for tool '${name}'`)))
const call = { name }
const recordAndObserve = (input: unknown) =>
Effect.sync(() => {
recordCall(call)
return calls.length - 1
}).pipe(Effect.tap((index) => hooks?.onToolCallStart?.({ index, name, input }) ?? Effect.void))
if (name === "$codemode.search") {
if (!searchEnabled) throw new ToolRuntimeError("UnknownTool", `Unknown tool '${name}'.`)
const input = externalArgs[0]
if (externalArgs.length !== 1 || input === null || typeof input !== "object" || Array.isArray(input)) {
throw new ToolRuntimeError(
"InvalidToolInput",
"tools.$codemode.search expects { query?: string; namespace?: string; limit?: number }.",
)
}
const request = input as { query?: unknown; namespace?: unknown; limit?: unknown }
if (request.query !== undefined && typeof request.query !== "string") {
throw new ToolRuntimeError(
"InvalidToolInput",
"tools.$codemode.search query must be a string when provided.",
)
}
if (request.namespace !== undefined && typeof request.namespace !== "string") {
throw new ToolRuntimeError(
"InvalidToolInput",
"tools.$codemode.search namespace must be a string when provided.",
)
}
if (
request.limit !== undefined &&
(typeof request.limit !== "number" || !Number.isSafeInteger(request.limit) || request.limit <= 0)
) {
throw new ToolRuntimeError(
"InvalidToolInput",
"tools.$codemode.search limit must be a positive safe integer when provided.",
)
}
const query = typeof request.query === "string" ? request.query : ""
const namespace = typeof request.namespace === "string" ? request.namespace : undefined
const index = yield* recordAndObserve(request)
return yield* observeEnd(
Effect.try({
try: () => {
const limit = typeof request.limit === "number" ? request.limit : defaultSearchLimit
const scoped =
namespace === undefined ? searchIndex : searchIndex.filter((entry) => entry.namespace === namespace)
// A query that names one tool path exactly (canonical path or rendered
// JavaScript expression) is a lookup, not a search: return that tool alone.
const trimmed = query.trim()
const pathQuery = trimmed.startsWith("tools.") ? trimmed.slice("tools.".length) : trimmed
const exact =
pathQuery === ""
? undefined
: scoped.find(
(entry) =>
entry.description.path === pathQuery || toolExpression(entry.description.path) === trimmed,
)
const terms = tokenize(query).map(termForms)
// Additive field-weighted scoring, summed across terms: exact path or path
// segment (20) > path substring (8) > description substring (4) > any
// searchable text, incl. input parameter names/descriptions (2). Each term
// matches a field when any of its forms (the term or a singular variant)
// does. An empty query browses everything, alphabetical by path.
const ranked =
exact !== undefined
? [exact]
: scoped
.map((entry) => {
const path = entry.description.path.toLowerCase()
const description = entry.description.description.toLowerCase()
const score = terms.reduce(
(total, forms) =>
total +
(forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
(forms.some((form) => path.includes(form)) ? 8 : 0) +
(forms.some((form) => description.includes(form)) ? 4 : 0) +
(forms.some((form) => entry.searchText.includes(form)) ? 2 : 0),
0,
)
return { entry, score }
})
.filter(({ score }) => terms.length === 0 || score > 0)
.sort(
(left, right) =>
right.score - left.score ||
left.entry.description.path.localeCompare(right.entry.description.path),
)
.map(({ entry }) => entry)
// Result paths are rendered as JavaScript expressions so each `path` is
// directly usable as the call site (`await tools.github.list({ ... })` or
// `await tools.ns["dashed-name"]({ ... })`). The signature is the pretty,
// JSDoc-annotated form (schema descriptions and constraints ride along as
// field comments).
const items = ranked.slice(0, limit).map(({ description, signature }) => ({
...description,
path: toolExpression(description.path),
signature,
}))
return copyIn({ items, total: ranked.length }, "Result from tool '$codemode.search'")
},
catch: (cause) => cause,
}),
{ index, name, input: request },
)
}
const tool = resolve(tools, path, searchEnabled)
let describedInput: unknown
if (isDefinition(tool)) {
if (externalArgs.length !== 1)
throw new ToolRuntimeError("InvalidToolInput", `Tool '${name}' expects exactly one input object.`)
describedInput = yield* Effect.try({
try: () => decodeToolInput(tool, externalArgs[0]),
catch: (cause) =>
new ToolRuntimeError("InvalidToolInput", `Invalid input for tool '${name}': ${String(cause)}`),
})
}
const input = isDefinition(tool) ? describedInput : externalArgs
const index = yield* recordAndObserve(input)
const currentCall = { index, name, input }
if (isDefinition(tool)) {
return yield* observeEnd(
Effect.gen(function* () {
const raw = yield* runHost(Effect.suspend(() => tool.run(describedInput)))
const result = yield* Effect.try({
try: () => decodeToolOutput(tool, raw),
catch: () => new ToolRuntimeError("InvalidToolOutput", `Invalid output from tool '${name}'.`),
})
return yield* decodeOutput(result, name)
}),
currentCall,
)
}
return yield* observeEnd(
Effect.gen(function* () {
return yield* decodeOutput(yield* runHost(Effect.suspend(() => tool(...externalArgs))), name)
}),
currentCall,
)
}),
}
}
export * as ToolRuntime from "./tool-runtime.js"
-348
View File
@@ -1,348 +0,0 @@
import { Effect, Schema } from "effect"
/**
* JSON Schema subset accepted for render-only tool schemas.
*
* A JSON-Schema-described side of a tool is used to generate the model-visible TypeScript
* signature only - CodeMode performs no validation against it. This is the natural shape for
* adapter-provided tools (e.g. MCP definitions) whose schemas arrive as JSON Schema documents.
*/
export type JsonSchema = {
readonly type?: string | ReadonlyArray<string>
readonly enum?: ReadonlyArray<unknown>
readonly const?: unknown
readonly anyOf?: ReadonlyArray<JsonSchema>
readonly oneOf?: ReadonlyArray<JsonSchema>
readonly properties?: Readonly<Record<string, JsonSchema>>
readonly required?: ReadonlyArray<string>
readonly items?: JsonSchema
readonly additionalProperties?: boolean | JsonSchema
readonly description?: string
readonly default?: unknown
readonly format?: string
readonly deprecated?: boolean
readonly minItems?: number
readonly maxItems?: number
readonly $ref?: string
readonly $defs?: Readonly<Record<string, JsonSchema>>
readonly definitions?: Readonly<Record<string, JsonSchema>>
}
/** Either a validating Effect Schema or a render-only JSON Schema document. */
export type ToolSchema = Schema.Decoder<unknown> | JsonSchema
/** Schema-backed tool definition consumed by a CodeMode tool tree. */
export type Definition<R = never> = {
readonly _tag: "CodeModeTool"
readonly description: string
readonly input: ToolSchema
readonly output: ToolSchema | undefined
readonly run: (input: unknown) => Effect.Effect<unknown, unknown, R>
}
/** The value `run` receives: the decoded type for Effect Schemas, `unknown` for JSON Schemas. */
export type InputType<S> = S extends Schema.Decoder<unknown> ? S["Type"] : unknown
/** The value `run` returns: the encoded type for Effect Schemas, `unknown` otherwise. */
export type ResultType<S> = S extends Schema.Decoder<unknown> ? S["Encoded"] : unknown
/** Options for defining one CodeMode tool. */
export type Options<I extends ToolSchema, O extends ToolSchema | undefined, R = never> = {
readonly description: string
readonly input: I
readonly output?: O
readonly run: (input: InputType<I>) => Effect.Effect<ResultType<O>, unknown, R>
}
export const isDefinition = <R = never>(value: unknown): value is Definition<R> =>
typeof value === "object" && value !== null && "_tag" in value && value._tag === "CodeModeTool"
const isEffectSchema = (schema: ToolSchema): schema is Schema.Decoder<unknown> & Schema.Top => Schema.isSchema(schema)
const renderLiteral = (value: unknown): string => JSON.stringify(value) ?? "unknown"
/**
* Bare TypeScript identifier - usable unquoted as an object key (and, in the tool runtime,
* with dot access as a tool-path segment). Anything else must be quoted/bracketed.
*/
export const identifierSegment = /^[A-Za-z_$][A-Za-z0-9_$]*$/
/** Renders a property name as a valid TS object key: bare when an identifier, quoted otherwise. */
const renderKey = (name: string): string => (identifierSegment.test(name) ? name : JSON.stringify(name))
const effectNumberSentinel = (schema: JsonSchema) =>
schema.type === "string" &&
Array.isArray(schema.enum) &&
schema.enum.length === 1 &&
(schema.enum[0] === "NaN" || schema.enum[0] === "Infinity" || schema.enum[0] === "-Infinity")
/**
* Recursion ceiling for schema rendering. Object, array, and union recursion all increment
* depth, so this bounds every recursion path - pathological or structurally cyclic schemas
* degrade to `unknown` instead of overflowing the stack (rendering must never throw).
*/
const MAX_RENDER_DEPTH = 8
type RenderContext = {
readonly definitions: Readonly<Record<string, JsonSchema>>
/** Indented, JSDoc-annotated multiline rendering (search results); compact single line otherwise. */
readonly pretty: boolean
}
/**
* Schema constraints a TypeScript type cannot express natively but a model benefits from,
* surfaced as JSDoc tags (`@deprecated`, `@default`, `@format`, `@minItems`, `@maxItems`).
*/
const docTags = (schema: JsonSchema): Array<string> => {
const tags: Array<string> = []
if (schema.deprecated === true) tags.push("@deprecated")
if (schema.default !== undefined) {
try {
const rendered = JSON.stringify(schema.default)
if (rendered !== undefined) tags.push(`@default ${rendered}`)
} catch {
// unserializable default: skip rather than emit a broken tag
}
}
if (typeof schema.format === "string") tags.push(`@format ${schema.format}`)
if (typeof schema.minItems === "number") tags.push(`@minItems ${schema.minItems}`)
if (typeof schema.maxItems === "number") tags.push(`@maxItems ${schema.maxItems}`)
return tags
}
/**
* Format a schema `description` plus `tags` as a JSDoc comment at the given indent,
* preserving multi-line text (a single line stays `/** ... *\/`; multiple lines become a
* `*`-prefixed block). `*\/` is neutralized so nothing can close the comment early, and
* blank leading/trailing lines are trimmed. Returns "" (else a trailing newline) so
* callers can prepend it directly to the field line.
*/
const jsdoc = (description: string | undefined, tags: ReadonlyArray<string>, pad: string): string => {
const lines = [...(description === undefined ? [] : description.split("\n")), ...tags].map((line) =>
line.replaceAll("*/", "* /").replace(/\s+$/, ""),
)
while (lines.length > 0 && lines[0]!.trim() === "") lines.shift()
while (lines.length > 0 && lines[lines.length - 1]!.trim() === "") lines.pop()
if (lines.length === 0) return ""
if (lines.length === 1) return `${pad}/** ${lines[0]} */\n`
const body = lines.map((line) => `${pad} *${line === "" ? "" : ` ${line}`}`).join("\n")
return `${pad}/**\n${body}\n${pad} */\n`
}
const renderSchema = (
schema: JsonSchema,
ctx: RenderContext,
depth = 0,
seen: ReadonlySet<string> = new Set(),
): string => {
if (depth > MAX_RENDER_DEPTH) return "unknown"
if (schema.$ref) {
const name = schema.$ref.split("/").pop()
if (!name || !ctx.definitions[name]) return name ?? "unknown"
if (seen.has(name)) return name // recursive type: reference by name rather than loop
return renderSchema(ctx.definitions[name], ctx, depth, new Set([...seen, name]))
}
if (schema.const !== undefined) return renderLiteral(schema.const)
if (schema.enum) return schema.enum.map(renderLiteral).join(" | ")
const alternatives = schema.anyOf ?? schema.oneOf
if (alternatives) {
// Effect's number schema emits `anyOf: [{ type: "number" }, { const: "NaN" },
// { const: "Infinity" }, { const: "-Infinity" }]`. Collapse only that artifact;
// real JSON Schema unions such as `string | number` or `number | null` must keep
// every branch.
if (
alternatives.some((item) => item.type === "number") &&
alternatives.every((item) => item.type === "number" || effectNumberSentinel(item))
)
return "number"
// An empty Schema.Struct({}) emits `anyOf: [{ type: "object" }, { type: "array" }]`
// (no properties/items); render the bare shape as {} instead of `{} | Array<unknown>`.
if (
alternatives.length === 2 &&
alternatives[0]?.type === "object" &&
alternatives[0].properties === undefined &&
alternatives[1]?.type === "array" &&
alternatives[1].items === undefined
) {
return "{}"
}
return alternatives.map((item) => renderSchema(item, ctx, depth + 1, seen)).join(" | ")
}
if (Array.isArray(schema.type)) {
return schema.type.map((item) => renderSchema({ type: item }, ctx, depth + 1, seen)).join(" | ")
}
if (schema.type === "string") return "string"
if (schema.type === "number" || schema.type === "integer") return "number"
if (schema.type === "boolean") return "boolean"
if (schema.type === "null") return "null"
if (schema.type === "array") return `Array<${renderSchema(schema.items ?? {}, ctx, depth + 1, seen)}>`
if (schema.type === "object" || schema.properties) {
const required = new Set(schema.required ?? [])
const properties = Object.entries(schema.properties ?? {})
const additional = schema.additionalProperties
const indexType =
additional && typeof additional === "object" ? renderSchema(additional, ctx, depth + 1, seen) : undefined
const field = ([name, value]: readonly [string, JsonSchema]) =>
`${renderKey(name)}${required.has(name) ? "" : "?"}: ${renderSchema(value, ctx, depth + 1, seen)}`
if (!ctx.pretty) {
const fields = properties.map(field)
if (indexType !== undefined) fields.push(`[key: string]: ${indexType}`)
return fields.length === 0 ? "{}" : `{ ${fields.join("; ")} }`
}
// Pretty: an indented block, each described field preceded by its JSDoc comment.
if (properties.length === 0 && indexType === undefined) return "{}"
const pad = " ".repeat(depth + 1)
const lines = properties.map(
(entry) => `${jsdoc(entry[1].description, docTags(entry[1]), pad)}${pad}${field(entry)}`,
)
if (indexType !== undefined) lines.push(`${pad}[key: string]: ${indexType}`)
return `{\n${lines.join("\n")}\n${" ".repeat(depth)}}`
}
return "unknown"
}
export const toTypeScript = (schema: Schema.Top, decoded = false, pretty = false): string => {
try {
const visible = decoded ? Schema.toType(schema) : schema
const document = Schema.toJsonSchemaDocument(visible) as {
readonly schema: JsonSchema
readonly definitions?: Readonly<Record<string, JsonSchema>>
}
return renderSchema(document.schema, { definitions: document.definitions ?? {}, pretty })
} catch {
return "unknown"
}
}
/** Renders a raw JSON Schema document as a TypeScript type string. */
export const jsonSchemaToTypeScript = (schema: JsonSchema, pretty = false): string => {
try {
return renderSchema(schema, { definitions: { ...(schema.definitions ?? {}), ...(schema.$defs ?? {}) }, pretty })
} catch {
return "unknown"
}
}
/** One input property of a tool, extracted best-effort from its input schema. */
export type InputProperty = {
readonly name: string
readonly description: string | undefined
readonly required: boolean
}
/**
* The property names, descriptions, and required flags of a tool's input schema - the raw
* material for search text. Best-effort: Effect Schemas go through their
* JSON Schema document (the same emission signature rendering uses); JSON Schemas are read
* directly, resolving a trivial top-level `$ref` into `$defs`/`definitions` when present.
* Anything unresolvable yields `[]` (search falls back to path + description).
*/
export const inputProperties = <R>(definition: Definition<R>): Array<InputProperty> => {
try {
const document = isEffectSchema(definition.input)
? (Schema.toJsonSchemaDocument(definition.input) as {
readonly schema: JsonSchema
readonly definitions?: Readonly<Record<string, JsonSchema>>
})
: {
schema: definition.input,
definitions: { ...(definition.input.definitions ?? {}), ...(definition.input.$defs ?? {}) },
}
const definitions = document.definitions ?? {}
let schema = document.schema
if (schema.$ref !== undefined) {
const name = schema.$ref.split("/").pop()
const resolved = name === undefined ? undefined : definitions[name]
if (resolved === undefined) return []
schema = resolved
}
const required = new Set(schema.required ?? [])
return Object.entries(schema.properties ?? {}).map(([name, value]) => ({
name,
description: typeof value.description === "string" ? value.description : undefined,
required: required.has(name),
}))
} catch {
return []
}
}
/**
* The model-visible TypeScript type of a tool's input. `pretty` renders an indented
* multiline block with schema descriptions and constraints as JSDoc comments on the
* fields; the default stays the compact single-line form.
*/
export const inputTypeScript = <R>(definition: Definition<R>, pretty = false): string =>
isEffectSchema(definition.input)
? toTypeScript(definition.input, false, pretty)
: jsonSchemaToTypeScript(definition.input, pretty)
/**
* The model-visible TypeScript type of a tool's result; tools without an output schema
* return `unknown`. `pretty` renders the JSDoc-annotated multiline form, as for inputs.
*/
export const outputTypeScript = <R>(definition: Definition<R>, pretty = false): string =>
definition.output === undefined
? "unknown"
: isEffectSchema(definition.output)
? toTypeScript(definition.output, true, pretty)
: jsonSchemaToTypeScript(definition.output, pretty)
/**
* Decodes tool input before `run` is invoked. Effect Schemas validate (throwing on failure);
* JSON-Schema-described inputs pass through unvalidated (render-only).
*/
export const decodeInput = <R>(definition: Definition<R>, value: unknown): unknown =>
isEffectSchema(definition.input) ? Schema.decodeUnknownSync(definition.input)(value) : value
/**
* Decodes a tool result before it is exposed to the program. Effect Schemas validate and
* transform (throwing on failure); JSON Schema outputs and tools without an output schema pass
* the host value through unchanged.
*/
export const decodeOutput = <R>(definition: Definition<R>, value: unknown): unknown =>
definition.output !== undefined && isEffectSchema(definition.output)
? Schema.decodeUnknownSync(definition.output)(value)
: value
/**
* Defines one schema-described tool available to a CodeMode program through `tools.*`.
*
* `input` and `output` each accept a validating Effect Schema or a render-only JSON Schema
* document. Effect Schema input is decoded before `run` is invoked, and `run` returns the
* encoded representation of an Effect Schema `output`, which CodeMode decodes before returning
* it to the program. JSON Schemas only shape the model-visible signature; values pass through
* unvalidated. `output` is optional - without it the signature advertises `unknown` and the
* host result is exposed as-is. The host tool remains responsible for authorization and
* durable side-effect handling.
*
* @example
* ```ts
* const lookup = Tool.make({
* description: "Look up an order",
* input: Schema.Struct({ id: Schema.String }),
* output: Schema.Struct({ status: Schema.String }),
* run: ({ id }) => Effect.succeed({ status: "open" }),
* })
*
* const fromJsonSchema = Tool.make({
* description: "Call an adapter-described tool",
* input: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
* run: (input) => callHost(input),
* })
* ```
*/
export const make = <I extends ToolSchema, const O extends ToolSchema | undefined = undefined, R = never>(
options: Options<I, O, R>,
): Definition<R> => ({
_tag: "CodeModeTool",
description: options.description,
input: options.input,
output: options.output,
run: (input) => options.run(input as InputType<I>),
})
/** Constructors for schema-backed tools exposed inside CodeMode programs. */
export const Tool = { make, isDefinition }
-34
View File
@@ -1,34 +0,0 @@
import type { Effect, Fiber } from "effect"
export class SandboxPromise {
interrupted = false
constructor(
readonly fiber: Fiber.Fiber<unknown, unknown> | undefined,
readonly immediate?: Effect.Effect<unknown, unknown>,
) {}
}
export class SandboxDate {
constructor(readonly time: number) {}
}
export class SandboxRegExp {
readonly regex: RegExp
constructor(pattern: string, flags: string) {
this.regex = new RegExp(pattern, flags)
}
}
export class SandboxMap {
readonly map = new Map<unknown, unknown>()
}
export class SandboxSet {
readonly set = new Set<unknown>()
}
export const isSandboxValue = (value: unknown): value is SandboxDate | SandboxRegExp | SandboxMap | SandboxSet =>
value instanceof SandboxDate ||
value instanceof SandboxRegExp ||
value instanceof SandboxMap ||
value instanceof SandboxSet

Some files were not shown because too many files have changed in this diff Show More