Compare commits

..
Author SHA1 Message Date
Dax Raad a0f7731ef4 progress 2026-04-19 20:33:46 -04:00
Dax Raad 64dc81cee0 sync 2026-04-19 20:16:25 -04:00
Dax Raad eb36b4d381 sync 2026-04-19 20:14:01 -04:00
Dax Raad e4be557928 ci: skip beta smoke fixes for now 2026-04-19 20:01:30 -04:00
Dax Raad b9640fc7e4 core: fix session compaction test to properly enable prune config option 2026-04-19 19:53:43 -04:00
opencode-agent[bot] 29f05cb1ee chore: generate 2026-04-19 23:48:44 +00:00
Dax Raad 48acab48ad ci: skip Docker builds during preview releases to save time 2026-04-19 19:47:29 -04:00
Dax Raad 5ae74aa881 Merge branch 'nxl/improve-compaction-strategy' into dev 2026-04-19 19:38:46 -04:00
Dax Raad 6eddf08244 flip toolcall prune defaults 2026-04-19 19:34:44 -04:00
opencode-agent[bot] 9c7e52b8a1 chore: update nix node_modules hashes 2026-04-19 22:52:42 +00:00
SebastianandGitHub a824064c4c stabilize TUI theme persistence and KV writes (#23188) 2026-04-20 00:10:31 +02:00
opencode-agent[bot] 33b2795cc8 chore: generate 2026-04-19 09:47:36 +00:00
Luke ParkerandGitHub 10bd044c55 feat: add terminal font settings and built-in Nerd Font (#23391) 2026-04-19 19:46:34 +10:00
opencode c09bcfe531 sync release versions for v1.14.18 2026-04-19 09:36:56 +00:00
Brendan AllanandGitHub 83227be0ca fix(version): remove --target flag from beta release creation (#23403) 2026-04-19 17:05:03 +08:00
opencode-agent[bot] 8ee47a0533 chore: update nix node_modules hashes 2026-04-19 08:29:51 +00:00
Brendan AllanandGitHub a546e88f37 fix(desktop-electron): run JSON migration before spawning sidecar (#23396) 2026-04-19 15:53:47 +08:00
opencode-agent[bot] e998c9e9cb chore: update nix node_modules hashes 2026-04-19 07:35:27 +00:00
889087c966 fix(ripgrep): restore native rg backend (#22773)
Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
2026-04-19 06:58:15 +00:00
opencode-agent[bot] 7f3b64c7c4 chore: update nix node_modules hashes 2026-04-19 06:38:10 +00:00
DaxandGitHub e60a6e3a82 fix: change Free download button text to Download (#23388) 2026-04-19 02:19:40 -04:00
opencode-agent[bot] 135c8f0e99 chore: generate 2026-04-19 05:59:31 +00:00
opencode-agent[bot] f02504bb80 chore: generate 2026-04-19 05:58:31 +00:00
Dax Raad 40834fdf2f core: allow users with credits but no payment method to access zen mode 2026-04-19 01:57:16 -04:00
Aiden ClineandGitHub fc0588954b fix (#23385) 2026-04-19 00:45:44 -05:00
opencode-agent[bot] 75960e3bf3 chore: generate 2026-04-19 04:25:23 +00:00
Ariane EmoryandGitHub f14ac472a3 docs: document --dangerously-skip-permissions CLI flag (#23371) 2026-04-18 23:24:23 -05:00
opencode-agent[bot] 9ed93715ef chore: update nix node_modules hashes 2026-04-19 03:40:53 +00:00
Luke ParkerandGitHub b34ca44abe fix incorrect config directory by lazily loading electron-store (#23373) 2026-04-19 13:06:07 +10:00
opencode 40ba8f3570 sync release versions for v1.14.17 2026-04-19 03:02:14 +00:00
Luke ParkerandGitHub e543acf923 chore: bump electron and fix taskbar icon (#23368) 2026-04-19 02:35:02 +00:00
Dax Raad d183568644 core: ensure executable permissions are set before Docker builds
Fixes an issue where GitHub artifact downloads could strip executable bits
from binaries, causing Docker builds to fail when using unpacked dist files
directly rather than published tarballs. The chmod now runs before the
publish check to guarantee binaries are executable.
2026-04-18 22:32:53 -04:00
Dax Raad f27eb8f09e fix plugins reinstalling too often 2026-04-18 20:02:24 -04:00
Dax Raad ad0545335a ci 2026-04-18 19:29:21 -04:00
Dax Raad cfbbae7323 ci 2026-04-18 18:59:44 -04:00
Dax Raad 940f971ca0 ci: fix 2026-04-18 18:56:14 -04:00
Aiden ClineandGitHub 78ca49a1bc test: fix bedrock test (#23351) 2026-04-18 17:46:15 -05:00
1d54b0e540 Stefan/enterprise forms waitlist (#23158)
Co-authored-by: Ryan Vogel <me@ryan.ceo>
2026-04-18 18:30:28 -04:00
opencode-agent[bot] 7e971d8302 chore: generate 2026-04-18 21:37:45 +00:00
Frank 54b3b3fe05 zen: redeem go 2026-04-18 17:33:28 -04:00
Frank 9d012b0621 zen: redeem credit 2026-04-18 17:33:28 -04:00
opencode-agent[bot] fbb0a93e12 chore: update nix node_modules hashes 2026-04-18 21:32:47 +00:00
Aiden ClineandGitHub e2e7a8d722 fix: ensure display: summarized is sent by default for bedrock (#23343) 2026-04-18 16:04:00 -05:00
Aiden ClineandGitHub ce7923adaf chore: bump @ai-sdk/amazon-bedrock (#23341) 2026-04-18 16:00:46 -05:00
DaxandGitHub a26d53151b tui: allow full-session forks from the session dialog (#23339) 2026-04-18 20:20:23 +00:00
Dax Raad 5eaef6b758 release: avoid package.json drift during publish 2026-04-18 12:32:23 -04:00
opencode-agent[bot] c5c38cad9c chore: generate 2026-04-18 16:00:01 +00:00
Kit LangtonandGitHub 9918f389e7 fix: detect attachment mime from file contents (#23291) 2026-04-18 11:59:08 -04:00
opencode-agent[bot] dd8c424806 chore: generate 2026-04-18 15:21:48 +00:00
Dax Raad 078d8a07cf core: support OTEL_RESOURCE_ATTRIBUTES environment variable for custom telemetry attributes
Users can now pass custom OpenTelemetry resource attributes via the OTEL_RESOURCE_ATTRIBUTES environment variable (comma-separated key=value format). These attributes are automatically included in all telemetry data sent from both the main process and workspace environments, enabling better observability integration with existing monitoring systems that rely on custom resource tags.
2026-04-18 11:20:29 -04:00
Dax Raad 1ee712e549 core: fix early return when node_modules is missing during package install 2026-04-18 10:42:33 -04:00
Dax Raad 55315bdffa tui: fix sync loading indicator to properly show loading state on startup 2026-04-18 10:39:10 -04:00
Dax Raad 882b8e1e75 core: track retry attempts with detailed error context on assistant entries
users can now see when transient failures occur during assistant responses,
such as rate limits or provider overloads, giving visibility into what
issues were encountered and automatically resolved before the final response
2026-04-18 10:38:35 -04:00
opencode-agent[bot] 95edbc0ae6 chore: generate 2026-04-18 05:49:37 +00:00
Dax Raad 11cd4fb639 core: extract session entry stepping logic into dedicated module
Move the step function from session-entry.ts to session-entry-stepper.ts and remove immer dependency. Add static fromEvent factory methods to Synthetic, Assistant, and Compaction classes for cleaner event-to-entry conversion.
2026-04-18 01:48:21 -04:00
Aiden ClineandGitHub 9c16bd1e30 fix: make skills logic more token efficient (#23253) 2026-04-17 23:51:16 -05:00
opencode-agent[bot] 5e9d5c734e chore: generate 2026-04-18 03:52:28 +00:00
Kit LangtonandGitHub b382d1a467 docs(effect): track schema migration progress with concrete file checklists (#23242) 2026-04-18 03:51:30 +00:00
Kit LangtonandGitHub 23f31475e7 refactor(config): migrate config.ts root Info to Effect Schema (#23241) 2026-04-18 03:44:35 +00:00
OpeOginniandGitHub c0eab9e442 fix(desktop): adjust ui tool diff sticky header offset (#23149) 2026-04-18 11:31:38 +08:00
opencode-agent[bot] 8a1e85d0c8 chore: generate 2026-04-18 03:17:28 +00:00
Kit LangtonandGitHub 2793502db2 refactor(config): migrate agent.ts Info to Effect Schema (#23237) 2026-04-18 03:16:24 +00:00
opencode-agent[bot] 9f7bd0246c chore: generate 2026-04-18 03:05:59 +00:00
Kit LangtonandGitHub a6a4350d10 refactor(config): migrate permission.ts Info to Effect Schema (#23231) 2026-04-18 03:05:06 +00:00
Kit LangtonandGitHub 471b9f4dc4 refactor: use InstanceState context in worktree cleanup paths (#23019) 2026-04-17 23:04:16 -04:00
Kit LangtonandGitHub 24fb9b1296 fix: stop rewriting dev during release publish (#22982) 2026-04-18 02:53:19 +00:00
Kit LangtonandGitHub 3573019916 fix(generate): make openapi output deterministic by formatting in-place (#23228) 2026-04-17 22:31:21 -04:00
Kit LangtonandGitHub fc5b353144 refactor(config): migrate keybinds.ts to Effect Schema (#23227) 2026-04-18 02:28:45 +00:00
Kit LangtonandGitHub 1dd257b76a refactor: use instance state in small services (#23022) 2026-04-18 02:16:15 +00:00
Kit LangtonandGitHub 5fa1673341 refactor: use InstanceState context in File service (#23015) 2026-04-17 22:08:57 -04:00
opencode-agent[bot] daaa1c7e26 chore: generate 2026-04-18 02:03:30 +00:00
Kit LangtonandGitHub 1fae784b81 feat(effect-zod): add ZodPreprocess annotation for pre-parse transforms (#23222) 2026-04-18 02:02:37 +00:00
Aiden ClineandGitHub 81b7b58a5e fix: gh copilot issue w/ haiku (eager_input_streaming not supported) (#23223) 2026-04-17 20:57:48 -05:00
opencode-agent[bot] 866188a643 chore: generate 2026-04-18 01:48:50 +00:00
Kit LangtonandGitHub e6fd57165e refactor: remove ambient instance reads from lsp (#23023) 2026-04-17 21:47:59 -04:00
Kit LangtonandGitHub a5d99e7a3c refactor: pass formatter instance context explicitly (#23020) 2026-04-18 01:22:36 +00:00
opencode-agent[bot] a92c75e5f4 chore: generate 2026-04-18 01:21:01 +00:00
Kit LangtonandGitHub 826fd3350c refactor(config): migrate Server + Layout to Effect Schema (#23216) 2026-04-18 01:20:06 +00:00
Kit LangtonandGitHub 23a2d01282 fix(observability): standardize session telemetry attrs (#23213) 2026-04-17 21:14:23 -04:00
Kit LangtonandGitHub 5181f9b4e1 refactor(config): drop ZodOverride from PositiveInt in provider.ts (#23215) 2026-04-18 01:04:40 +00:00
opencode-agent[bot] f52ae28432 chore: generate 2026-04-18 00:56:33 +00:00
Kit LangtonandGitHub 36119ff173 feat(effect-zod): translate Schema.withDecodingDefault into zod .default() (#23207) 2026-04-17 20:55:38 -04:00
Kit LangtonandGitHub bb90f3bbf9 feat(effect-zod): translate well-known filters into native Zod methods (#23209) 2026-04-17 20:50:36 -04:00
Kit Langtonandopencode 05cdb7c107 refactor(v2): tag session unions and exhaustively match events (#23201) 2026-04-18 00:29:26 +00:00
Kit Langtonandopencode b493dabfe6 docs(effect): refresh migration status specs (#23206) 2026-04-18 00:29:26 +00:00
opencode-agent[bot]andopencode c4816f944e chore: generate 2026-04-18 00:29:26 +00:00
Kit Langtonandopencode 211136e3a8 feat(effect-zod): transform support + walk memoization + flattened checks (#23203) 2026-04-18 00:29:26 +00:00
opencode-agent[bot]andopencode cf0a53c501 chore: generate 2026-04-18 00:29:26 +00:00
Kit Langtonandopencode 2899984819 refactor(config): migrate provider (Model + Info) to Effect Schema (#23197) 2026-04-18 00:29:26 +00:00
Kit Langtonandopencode eafbe5c57c refactor(server): align route-span attrs with OTel semantic conventions (#23198) 2026-04-18 00:29:26 +00:00
Kit Langtonandopencode 7b98f544ff feat(effect-zod): add catchall (StructWithRest) support to the walker (#23186) 2026-04-18 00:29:26 +00:00
Kit Langtonandopencode b5aba5807c feat(tui): show session ID in sidebar on non-prod channels (#23185) 2026-04-18 00:29:26 +00:00
Kit Langtonandopencode d5c4c26b4b feat(server): auto-tag route spans with route params (session.id, message.id, …) (#23189) 2026-04-18 00:29:26 +00:00
opencode a35b8a95c2 release: v1.4.11 2026-04-18 00:29:16 +00:00
DaxandGitHub cded68a2e2 refactor(npm): use object-based package spec for install API (#23181) 2026-04-17 17:30:50 -04:00
Aiden ClineandGitHub 0068ccec35 fix: ensure copilot model list filters out disabled models (#23176) 2026-04-17 16:27:32 -05:00
opencode-agent[bot] 89e8994fd1 chore: generate 2026-04-17 21:08:00 +00:00
Kit LangtonandGitHub 5980b0a5ee feat(effect-zod): add tuple support; migrate config/plugin to Effect Schema (#23178) 2026-04-17 21:06:55 +00:00
opencode-agent[bot] 89029a20ef chore: generate 2026-04-17 21:00:20 +00:00
Kit LangtonandGitHub ce69bd97b9 refactor(config): migrate model-id and command to Effect Schema (#23175) 2026-04-17 20:59:24 +00:00
Kit LangtonandGitHub 999d8651aa feat(server): wrap remaining route handlers in request spans (#23169) 2026-04-17 16:52:40 -04:00
opencode-agent[bot] ed0f022502 chore: generate 2026-04-17 20:50:37 +00:00
Kit LangtonandGitHub b1307d5c2a refactor(config): migrate skills, formatter, console-state to Effect Schema (#23162) 2026-04-17 20:49:36 +00:00
opencode-agent[bot] dc16013b4f chore: generate 2026-04-17 20:47:05 +00:00
Kit LangtonandGitHub e7686dbd64 feat(effect-zod): translate Schema.check filters into zod .superRefine + promote LSP refinement to Effect layer (#23173) 2026-04-17 20:46:05 +00:00
James LongandGitHub 47f553f9ba fix(core): more explicit routing to fix workspace instance issue (#23171) 2026-04-17 16:39:34 -04:00
Kit LangtonandGitHub d11268ece7 refactor(config): migrate permission Action/Object/Rule leaves to Effect Schema (#23168) 2026-04-17 20:35:42 +00:00
Kit LangtonandGitHub 650a13a690 refactor(config): migrate lsp schemas to Effect Schema (#23167) 2026-04-17 20:34:47 +00:00
opencode-agent[bot] 54435325b6 chore: generate 2026-04-17 20:26:43 +00:00
Kit LangtonandGitHub 11fa257549 refactor(config): migrate mcp schemas to Effect Schema.Class (#23163) 2026-04-17 20:25:49 +00:00
Kit LangtonandGitHub 6af8ab0df2 docs(http-api): refresh bridge inventory and clarify Schema.Class vs Struct (#23164) 2026-04-17 16:20:57 -04:00
Kit LangtonandGitHub 984f5ed6eb fix(opencode): skip share sync for unshared sessions (#23159) 2026-04-17 20:15:24 +00:00
opencode-agent[bot]andopencode c2061c6bbf chore: generate 2026-04-17 20:13:34 +00:00
Daxandopencode b708e8431e docs(opencode): annotate plugin loader flow (#23160) 2026-04-17 20:13:34 +00:00
opencode 9b6c397171 release: v1.4.10 2026-04-17 20:13:25 +00:00
opencode-agent[bot] 9b0659d4f9 chore: generate 2026-04-17 19:30:28 +00:00
Kit LangtonandGitHub f83cecaaf6 fix(opencode): untrace streaming event hot paths (#23156) 2026-04-17 15:29:32 -04:00
James LongandGitHub aa05b9abe5 fix(core): pass OTEL config to workspace env (#23154) 2026-04-17 15:25:58 -04:00
Kit LangtonandGitHub 68834cfcc3 fix(opencode): normalize provider metadata and tag otel runs (#23140) 2026-04-17 15:22:08 -04:00
James LongandGitHub 5621373bc2 fix(core): move instance middleware after control plane routes (#23150) 2026-04-17 15:20:11 -04:00
opencode-agent[bot] 88582566bf chore: update nix node_modules hashes 2026-04-17 19:18:55 +00:00
opencode-agent[bot] d6e1362fee chore: generate 2026-04-17 19:15:07 +00:00
James LongandGitHub b275b8580d feat(tui): minor UX improvements for workspaces (#23146) 2026-04-17 15:14:05 -04:00
DaxandGitHub 467be08e67 refactor: consolidate npm exports and trace flock acquisition (#23151) 2026-04-17 18:58:37 +00:00
Aiden ClineandGitHub bbb422d125 chore: bump ai to 6.0.168 and @ai-sdk/gateway to 3.0.104 (#23145) 2026-04-17 13:47:22 -05:00
Dax Raad b1f076558c test: align plugin loader npm mocks
- switch plugin loader tests to the effect npm module
- return Option.none() for mocked npm entrypoints
- keep test fixtures aligned with the current Npm.add contract
2026-04-17 14:33:02 -04:00
Dax Raad 992435aaf8 do not flock until reify 2026-04-17 14:18:48 -04:00
Dax Raad 2f73e73e9d trace npm fully 2026-04-17 14:08:45 -04:00
James LongandGitHub 4c30a78cd9 fix: revert sdk generation script change (#23133) 2026-04-17 13:33:11 -04:00
James LongandGitHub a8c78fc005 fix(core): add historical sync on workspace connect (#23121) 2026-04-17 13:30:09 -04:00
opencode-agent[bot] fcb473ff64 chore: update nix node_modules hashes 2026-04-17 17:25:44 +00:00
James LongandGitHub 797953c88d when generating sdk only format sdk, much faster (#23122) 2026-04-17 13:01:22 -04:00
opencode-agent[bot] ce0cfb0ea5 chore: generate 2026-04-17 16:46:34 +00:00
Kit LangtonandGitHub 13dfe569ef tui: fix agent cycling and prompt metadata polish (#23115) 2026-04-17 12:45:29 -04:00
Aiden ClineandGitHub c491161c0c chore: bump @ai-sdk/anthropic to 3.0.71 and dependents (#23120) 2026-04-17 11:40:24 -05:00
fde3d9133b fix(opencode): pass EXA_API_KEY to websearch tool to avoid rate limits (#16362)
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-04-17 11:28:23 -05:00
Vladimir GlafirovandGitHub 0d582f9d3f chore: bump gitlab-ai-provider to 6.6.0 (#23057) 2026-04-17 11:22:43 -05:00
Dax Raadandopencode 1a59133168 Improve light mode dark mode copy 2026-04-17 16:19:57 +00:00
opencode 803d9eb7ad release: v1.4.9 2026-04-17 16:19:46 +00:00
Dax Raad a27d3c1623 tui: fix session resumption with --session-id flag to navigate after app initialization
Previously when passing a session ID directly, the route was set during initial
render which could cause navigation issues before the router was fully ready.
Now the session navigation happens after initialization completes, ensuring
the TUI properly loads the requested session when users resume with --session-id.
2026-04-17 11:41:24 -04:00
Dax Raad 551216a452 fix incorrect light mode in ghostty 2026-04-17 11:32:17 -04:00
opencode-agent[bot] 38cd3979f2 chore: update nix node_modules hashes 2026-04-17 15:31:50 +00:00
3fe602cda3 feat: add LLM Gateway provider (#7847)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-04-17 10:29:31 -05:00
opencode-agent[bot] 3a4b49095c chore: generate 2026-04-17 15:26:45 +00:00
Jen PersonandGitHub ac5b395c5d docs: adding Mistral to docs as a provider (it is already a provider, just docs update) #23070 (#23072) 2026-04-17 10:25:42 -05:00
OpeOginniandGitHub 8fbbca5f4b fix(opencode): rescrict github copilot opus 4.7 variants to "medium" (#23097) 2026-04-17 10:25:12 -05:00
Brendan AllanandGitHub 2415820ecd fix: conditionally show file tree in beta channel (#23099) 2026-04-17 15:13:59 +00:00
Frank 20103eb97b sync 2026-04-17 10:53:45 -04:00
Dax Raad 10c4ab9a3d roll back opentui 2026-04-17 10:51:02 -04:00
Dax Raad 7e39c9b950 back to opentui 0.1.99 2026-04-17 10:43:17 -04:00
opencode-agent[bot] cc063d4c32 chore: generate 2026-04-17 13:56:17 +00:00
Frank 3707e4a49c zen: routing logic 2026-04-17 09:54:47 -04:00
opencode-agent[bot] cb425ac927 chore: generate 2026-04-17 13:53:11 +00:00
James LongandGitHub 0f80c827ed feat(core): exponential backoff of workspace reconnect (#23083) 2026-04-17 09:52:10 -04:00
Dax Raad fffc496f41 remove log 2026-04-17 09:46:35 -04:00
opencode 06ae43920b release: v1.4.8 2026-04-17 13:37:06 +00:00
opencode-agent[bot] e78d75a003 chore: update nix node_modules hashes 2026-04-17 13:07:11 +00:00
SebastianandGitHub ec3ac0c4b0 upgrade opentui to 0.1.100 (#22928) 2026-04-17 14:29:46 +02:00
opencode-agent[bot] c57c5315c1 chore: generate 2026-04-17 07:27:13 +00:00
Brendan AllanandGitHub a726530735 fix(app): workspace loading and persist ready state (#23046) 2026-04-17 07:26:14 +00:00
DaxandGitHub d9950598d0 core: migrate config loading to Effect framework (#23032) 2026-04-17 06:44:01 +00:00
opencode-agent[bot] 81f0885879 chore: generate 2026-04-17 06:13:42 +00:00
DaxandGitHub 65b2a10e97 fade in prompt metadata transitions (#23037) 2026-04-17 06:12:41 +00:00
James LongandGitHub 7605acff65 refactor(core): move server routes around to clarify workspacing (#23031) 2026-04-17 02:06:20 -04:00
Dax Raad e7f8f7fa3b fix crash on experimental 2026-04-17 01:14:08 -04:00
James LongandGitHub 72d7cb717d remove accidental commit of daytona plugin (#23030) 2026-04-17 00:42:45 -04:00
opencode-agent[bot] f0caeb9b25 chore: generate 2026-04-17 04:32:17 +00:00
Aiden ClineandGitHub 76a141090e chore: delete filetime module (#22999) 2026-04-16 23:31:21 -05:00
DaxandGitHub 4bd5a158a5 fix: preserve prompt input across unmount/remount cycles (#22508) 2026-04-17 04:23:30 +00:00
opencode-agent[bot] dfaae14544 chore: update nix node_modules hashes 2026-04-17 04:14:26 +00:00
Brendan AllanandGitHub 79e9baf55a fix(app): use fetchQuery instead of ensureQueryData in global sync (#23025) 2026-04-17 03:54:19 +00:00
Brendan Allan a4882290aa Merge branch 'dev' into nxl/improve-compaction-strategy 2026-04-17 11:53:17 +08:00
Kit LangtonandGitHub 9ee89f7868 refactor: move project read routes onto HttpApi (#23003) 2026-04-17 03:48:12 +00:00
opencode-agent[bot] 67dbb3cf18 chore: generate 2026-04-17 03:37:21 +00:00
Kit LangtonandGitHub 4260c40efa refactor(tui): inline final Go shimmer settings (#23017) 2026-04-17 03:36:21 +00:00
James LongandGitHub 0bedea52b1 fix(tui): tui resiliency when workspace is dead, disable directory filter in session list (#23013) 2026-04-16 23:35:36 -04:00
fbbab9d6c8 feat(app): hide desktop titlebar tools behind settings (#19029)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <brendonovich@outlook.com>
2026-04-17 03:31:00 +00:00
Kit LangtonandGitHub cccb907a9b feat(tui): animated GO logo + radial pulse in free-limit upsell dialog (#22976) 2026-04-16 23:19:18 -04:00
Kit LangtonandGitHub ee7339f2c6 refactor: move provider and config provider routes onto HttpApi (#23004) 2026-04-16 23:10:45 -04:00
Kit LangtonandGitHub c51f3e35ca chore: retire namespace migration tooling + document module shape (#23010) 2026-04-17 02:48:40 +00:00
7b3bb9a761 fix: preserve plugin tool metadata in execute result (#22827)
Co-authored-by: jquense <jquense@ramp.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-04-16 21:38:21 -05:00
opencode-agent[bot] dc38f22bd8 chore: generate 2026-04-17 02:34:07 +00:00
DaxandGitHub 220e3e9a2b refactor: make formatter config opt-in (#22997) 2026-04-17 02:33:09 +00:00
Brendan AllanandGitHub f135c0b5ee app: use tanstack query to load session vcs state (#22277) 2026-04-17 02:27:08 +00:00
opencode-agent[bot] ebe6ea580d chore: generate 2026-04-17 02:26:48 +00:00
DaxandGitHub ee708040f6 fix: prefer real undo filenames over /dev/null (#23006) 2026-04-16 22:25:43 -04:00
Kit LangtonandGitHub 61c4815a37 refactor: unwrap FileWatcher namespace + self-reexport (redo) (#23000) 2026-04-17 02:20:43 +00:00
DaxandGitHub 01bb54a94d refactor: split config parsing steps (#22996) 2026-04-17 01:57:43 +00:00
Kit LangtonandGitHub f592c3846b refactor: convert Flag namespace to const object with getters (#22984) 2026-04-17 01:57:21 +00:00
Kit LangtonandGitHub c026e25088 refactor: eliminate account/ barrel, route consumers to sibling files (#22995) 2026-04-17 01:55:50 +00:00
Kit LangtonandGitHub 8ba73bed23 refactor: collapse auth/ barrel — merge auth.ts into index.ts + self-reexport (#22993) 2026-04-17 01:52:19 +00:00
Kit LangtonandGitHub 4f8986aa48 refactor: unwrap Question namespace + fix script to emit "." for index.ts (#22992) 2026-04-17 01:51:02 +00:00
Kit LangtonandGitHub 9c87a144e8 refactor: normalize AccountRepo to canonical Effect service pattern (#22991) 2026-04-17 01:43:57 +00:00
opencode-agent[bot] 5b9fa32255 chore: generate 2026-04-17 01:36:45 +00:00
DaxandGitHub f13778215a perf: speed up skill directory discovery (#22990) 2026-04-17 01:35:47 +00:00
DaxandGitHub 326471a25c refactor: split config lsp and formatter schemas (#22986) 2026-04-17 01:35:26 +00:00
DaxandGitHub 6405e3a7b1 tui: stabilize session dialog ordering (#22987) 2026-04-17 01:32:36 +00:00
Kit LangtonandGitHub 8afb625bab refactor: extract Diagnostic namespace into lsp/diagnostic.ts + self-reexport (#22983) 2026-04-17 01:19:01 +00:00
Kit LangtonandGitHub c59df636cc chore: delete empty v2/session-common + collapse patch barrel (#22981) 2026-04-17 01:02:09 +00:00
Kit LangtonandGitHub 94878d76f8 refactor: unwrap TuiPluginRuntime namespace + self-reexport (#22980) 2026-04-17 01:02:07 +00:00
Kit LangtonandGitHub 5022895e2b refactor: unwrap ExperimentalHttpApiServer namespace + self-reexport (#22979) 2026-04-17 01:01:24 +00:00
Kit LangtonandGitHub 54046e0b98 refactor: unwrap SessionV2 namespace + self-reexport (#22978) 2026-04-17 01:00:30 +00:00
Kit LangtonandGitHub d2cb1613ac refactor: unwrap SessionEntry namespace + self-reexport (#22977) 2026-04-17 00:59:42 +00:00
opencode-agent[bot] 266fb93422 chore: generate 2026-04-17 00:50:44 +00:00
Kit LangtonandGitHub 51d8219c46 refactor: unwrap session/ tier-2 namespaces + self-reexport (#22973) 2026-04-17 00:49:39 +00:00
Dax Raad d6af5a686c tui: convert TuiConfig namespace to ES module exports 2026-04-16 20:46:40 -04:00
Dax Raad 39342b0e75 tui: fix Windows terminal suspend and input undo keybindings
On Windows, native terminals don't support POSIX suspend (ctrl+z), so we now
assign ctrl+z to input undo instead of terminal suspend. Terminal suspend is
disabled on Windows to avoid conflicts with the undo functionality.
2026-04-16 20:37:58 -04:00
Kit LangtonandGitHub 54078c4cae refactor: unwrap Shell namespace + self-reexport (#22964) 2026-04-16 20:11:19 -04:00
Kit LangtonandGitHub c0bfccc15e tooling: add unwrap-and-self-reexport + batch-unwrap-pr scripts (#22929) 2026-04-16 20:11:17 -04:00
opencode-agent[bot] 53dc7b1649 chore: generate 2026-04-17 00:04:01 +00:00
Kit LangtonandGitHub 635970b0a1 refactor: unwrap ConfigSkills namespace + self-reexport (#22950) 2026-04-17 00:02:53 +00:00
Kit LangtonandGitHub 059b32c212 refactor: unwrap Protected namespace + self-reexport (#22938) 2026-04-17 00:02:51 +00:00
Kit LangtonandGitHub 2704ad9110 refactor: unwrap TuiConfig namespace + self-reexport (#22952) 2026-04-17 00:02:24 +00:00
Kit LangtonandGitHub 06d247c709 refactor: unwrap FileIgnore namespace + self-reexport (#22937) 2026-04-17 00:02:08 +00:00
Kit LangtonandGitHub 974fa1b8b1 refactor: unwrap PluginMeta namespace + self-reexport (#22945) 2026-04-17 00:02:05 +00:00
Kit LangtonandGitHub fb02744460 refactor: unwrap Agent namespace + self-reexport (#22935) 2026-04-17 00:01:44 +00:00
Kit LangtonandGitHub 79732ab175 refactor: unwrap UI namespace + self-reexport (#22951) 2026-04-17 00:01:41 +00:00
Kit LangtonandGitHub f6dbb2f3e0 refactor: unwrap Heap namespace + self-reexport (#22931) 2026-04-17 00:01:37 +00:00
Kit LangtonandGitHub fdd5b77bfd refactor: unwrap McpAuth namespace + self-reexport (#22942) 2026-04-17 00:01:12 +00:00
Kit LangtonandGitHub cde105e7a8 refactor: unwrap CopilotModels namespace + self-reexport (#22947) 2026-04-17 00:01:09 +00:00
Kit LangtonandGitHub 1291e82bb4 refactor: unwrap ACP namespace + self-reexport (#22936) 2026-04-17 00:00:50 +00:00
Kit LangtonandGitHub 19d15d9ff7 refactor: unwrap ConfigProvider namespace + self-reexport (#22949) 2026-04-17 00:00:48 +00:00
Kit LangtonandGitHub 4e27804160 refactor: unwrap McpOAuthCallback namespace + self-reexport (#22943) 2026-04-17 00:00:46 +00:00
Kit LangtonandGitHub bae80af1b4 refactor: unwrap Workspace namespace + self-reexport (#22934) 2026-04-17 00:00:15 +00:00
opencode-agent[bot] f9aa3d77cd chore: generate 2026-04-16 23:53:10 +00:00
Kit LangtonandGitHub 5d47ea0918 refactor: unwrap ConfigMCP namespace + self-reexport (#22948) 2026-04-16 19:52:04 -04:00
Kit LangtonandGitHub c03fa36257 refactor: unwrap Server namespace + self-reexport (#22970) 2026-04-16 23:51:01 +00:00
Kit LangtonandGitHub 1089fa0415 refactor: unwrap ServerProxy namespace + self-reexport (#22969) 2026-04-16 23:50:32 +00:00
Kit LangtonandGitHub 715786bbf9 refactor: unwrap FileTime namespace + self-reexport (#22966) 2026-04-16 23:50:15 +00:00
Kit LangtonandGitHub 218eca7c2b refactor: unwrap MDNS namespace + self-reexport (#22968) 2026-04-16 23:50:11 +00:00
Kit LangtonandGitHub 30fc791480 refactor: unwrap Ripgrep namespace + self-reexport (#22965) 2026-04-16 19:49:52 -04:00
Kit LangtonandGitHub e2d161dfdd refactor: unwrap Identifier namespace + self-reexport (#22963) 2026-04-16 23:48:24 +00:00
Kit LangtonandGitHub 23d48a7cf1 refactor: unwrap BusEvent namespace + self-reexport (#22962) 2026-04-16 23:46:49 +00:00
Aiden ClineandGitHub cb18f2ef40 fix: ensure azure sets prompt cache key by default (#22957) 2026-04-16 17:45:35 -05:00
Dax Raad dbe2ff52b2 fix tui otel profiling 2026-04-16 18:40:22 -04:00
Dax Raad 9db40996cc fix build script 2026-04-16 18:01:58 -04:00
opencode 9f201d6370 release: v1.4.7 2026-04-16 21:54:54 +00:00
Kit LangtonandGitHub 0e86466f99 refactor: unwrap Discovery namespace to flat exports + self-reexport (#22878) 2026-04-16 16:59:30 -04:00
Kit LangtonandGitHub 32548bcb4a refactor: unwrap ConfigPlugin namespace to flat exports + self-reexport (#22876) 2026-04-16 16:59:17 -04:00
James LongandGitHub 86c54c5acc fix(tui): minor logging cleanup (#22924) 2026-04-16 16:58:17 -04:00
Aiden ClineandGitHub ae584332b3 fix: uncomment import (#22923) 2026-04-16 15:56:29 -05:00
Kit LangtonandGitHub 1694c5bfe1 refactor: collapse file barrel into file/index.ts (#22901) 2026-04-16 16:56:09 -04:00
Kit LangtonandGitHub cdfbb26c00 refactor: collapse bus barrel into bus/index.ts (#22902) 2026-04-16 16:55:57 -04:00
thakrarsagarGitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
610c036ef1 fix(opencode): use low reasoning effort for GitHub Copilot gpt-5 models (#22824)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-04-16 15:44:58 -05:00
Kit LangtonandGitHub 2638e2acfa refactor: collapse plugin barrel into plugin/index.ts (#22914) 2026-04-16 20:37:13 +00:00
Kit LangtonandGitHub 49bbea5aed refactor: collapse snapshot barrel into snapshot/index.ts (#22916) 2026-04-16 20:36:45 +00:00
Kit LangtonandGitHub 5fccdc9fc7 refactor: collapse mcp barrel into mcp/index.ts (#22913) 2026-04-16 20:36:23 +00:00
Kit LangtonandGitHub 664b2c36e8 refactor: collapse git barrel into git/index.ts (#22909) 2026-04-16 20:36:07 +00:00
Kit LangtonandGitHub 964474a1b1 refactor: collapse permission barrel into permission/index.ts (#22915) 2026-04-16 20:36:04 +00:00
Kit LangtonandGitHub ab15fc1575 refactor: collapse npm barrel into npm/index.ts (#22911) 2026-04-16 20:36:02 +00:00
Kit LangtonandGitHub 99d392a4fb refactor: collapse skill barrel into skill/index.ts (#22912) 2026-04-16 20:35:43 +00:00
Kit LangtonandGitHub ae9a696607 refactor: collapse installation barrel into installation/index.ts (#22910) 2026-04-16 20:35:28 +00:00
Kit LangtonandGitHub bd51a0d35b refactor: collapse worktree barrel into worktree/index.ts (#22906) 2026-04-16 20:35:26 +00:00
Kit LangtonandGitHub 8c191b10c2 refactor: collapse ide barrel into ide/index.ts (#22904) 2026-04-16 20:35:04 +00:00
Kit LangtonandGitHub cb6a9253fe refactor: collapse sync barrel into sync/index.ts (#22907) 2026-04-16 20:34:33 +00:00
Kit LangtonandGitHub 23f97ac49d refactor: collapse global barrel into global/index.ts (#22905) 2026-04-16 20:33:52 +00:00
opencode-agent[bot] 021ab50fb1 chore: generate 2026-04-16 20:31:50 +00:00
Kit LangtonandGitHub 3fe906f517 refactor: collapse command barrel into command/index.ts (#22903) 2026-04-16 20:30:52 +00:00
James LongandGitHub a8d8a35cd3 feat(core): pass auth data to workspace (#22897) 2026-04-16 16:30:11 -04:00
Kit LangtonandGitHub 9b77430d0d refactor: collapse env barrel into env/index.ts (#22900) 2026-04-16 16:29:54 -04:00
Kit LangtonandGitHub 1045a43603 refactor: collapse format barrel into format/index.ts (#22898) 2026-04-16 16:29:51 -04:00
James LongandGitHub 26af77cd1e fix(core): fix detection of local installation channel (#22899) 2026-04-16 20:26:33 +00:00
Dax Raad 25a9de301a core: eager load config on startup for better traces and refactor npm install for improved error reporting
Config is now loaded eagerly during project bootstrap so users can see config loading in traces during startup. This helps diagnose configuration issues earlier in the initialization flow.

NPM installation logic has been refactored with a unified reify function and improved InstallFailedError that includes both the packages being installed and the target directory. This provides users with complete context when package installations fail, making it easier to identify which dependency or project directory caused the issue.
2026-04-16 16:23:19 -04:00
Kit LangtonandGitHub e0d71f124e tooling: add collapse-barrel.ts for single-namespace barrel migration (#22887) 2026-04-16 16:12:46 -04:00
Kit LangtonandGitHub 1c33b866ba fix: remove 10 more unnecessary as any casts in opencode core (#22882) 2026-04-16 20:11:05 +00:00
5e650fd9e2 fix(opencode): drop max_tokens for OpenAI reasoning models on Cloudflare AI Gateway (#22864)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-04-16 15:01:21 -05:00
Kit LangtonandGitHub 76275fc3ab refactor: move Pty into pty/index.ts with self-reexport (#22881) 2026-04-16 15:49:21 -04:00
Aiden ClineandGitHub 6c3b28db64 fix: ensure that double pasting doesnt happen after tui perf commit was merged (#22880) 2026-04-16 14:38:39 -05:00
Kit LangtonandGitHub 2fe9d94470 fix: remove 8 more unnecessary as any casts in opencode core (#22877) 2026-04-16 19:27:53 +00:00
Kit LangtonandGitHub 219b473e66 refactor: unwrap BashArity namespace to flat exports + self-reexport (#22874) 2026-04-16 15:24:24 -04:00
opencode-agent[bot] 7c1b30291c chore: update nix node_modules hashes 2026-04-16 19:19:52 +00:00
Aiden ClineandGitHub 47e0e2342c tweak: set display 'summarized' by default for opus 4.7 thorugh messages api (#22873) 2026-04-16 14:12:43 -05:00
Kit LangtonandGitHub bf4c107829 fix: remove 7 unnecessary as any casts in opencode core (#22840) 2026-04-16 15:07:02 -04:00
DaxandGitHub 9afbdc102c fix(test): make plugin loader theme source path separator-safe (#22870) 2026-04-16 14:45:17 -04:00
opencode-agent[bot] 370770122c chore: generate 2026-04-16 18:29:57 +00:00
Aiden ClineandGitHub 143817d44e chore: bump ai sdk deps for opus 4.7 (#22869) 2026-04-16 13:28:20 -05:00
Thomas ButlerandGitHub c60862fc9e fix: add missing glob dependency (#22851) 2026-04-16 13:21:04 -05:00
Dax Raad bee5f919fc core: reorganize ConfigPaths module export for cleaner dependency management 2026-04-16 13:33:54 -04:00
Dax Raad cefa7f04c6 core: reorganize ConfigPaths module export for cleaner dependency management 2026-04-16 13:32:22 -04:00
Dax Raad 03e20e6ac1 core: modularize config parsing to improve maintainability
Extract error handling, parsing logic, and variable substitution into dedicated
modules. This reduces duplication between tui.json and opencode.json parsing
and makes the config system easier to extend for future config formats.
2026-04-16 13:29:03 -04:00
Aiden ClineandGitHub c5deeee8c7 fix: ensure azure has store = true by default (#22764) 2026-04-16 12:19:01 -05:00
Dax Raad 8b1f0e2d90 core: add documentation comments to plugin configuration merge logic
Adds explanatory comments to config.ts and plugin.ts clarifying:

- How plugin specs are stored and normalized during config loading

- Why plugin_origins tracks provenance for location-sensitive decisions

- Why path-like specs are resolved early to prevent reinterpretation during merges

- How plugin deduplication works while keeping origin metadata for writes and diagnostics
2026-04-16 12:55:40 -04:00
Dax Raad 9bf2dfea35 core: refactor config schemas into separate modules for better maintainability 2026-04-16 12:47:09 -04:00
Dax Raad 33bb847a1d config: refactor 2026-04-16 12:40:24 -04:00
Dax Raad bfffc3c2c6 tui: ensure TUI plugins load with proper project context when multiple directories are open
Fixes potential plugin resolution issues when switching between projects by wrapping
plugin loading in Instance.provide(). This ensures each plugin resolves dependencies
relative to its correct project directory instead of inheriting context from whatever
instance happened to be active.

Also reorganizes config loading code into focused modules (command.ts, managed.ts,
plugin.ts) to make the codebase easier to maintain and test.
2026-04-16 12:40:24 -04:00
James LongandGitHub b28956f0db fix(core): better global sync event structure (#22858) 2026-04-16 12:35:37 -04:00
opencode-agent[bot] d82bc3a421 chore: generate 2026-04-16 16:26:12 +00:00
James LongandGitHub 06afd33291 refactor(tui): improve workspace management (#22691) 2026-04-16 12:24:40 -04:00
James LongandGitHub 305460b25f fix: add a few more tests for sync and session restore (#22837) 2026-04-16 12:15:44 -04:00
NacaiGitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
8c0205a84a fix: align stale bot message with actual 60-day threshold (#22842)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-04-16 11:01:35 -05:00
378c05f202 feat: Add support for claude opus 4.7 xhigh adaptive reasoning effort (#22833)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-04-16 10:57:36 -05:00
Jérôme BenoitandGitHub cc7acd90ab fix(nix): add shared package to bun install filters (#22665) 2026-04-16 10:43:15 -05:00
Frank a200f6fb8b zen: opus 4.7 2026-04-16 11:32:56 -04:00
Dax Raad 2b1696f1d1 Revert "tui: fix path comparison in theme installer to handle different path formats"
This reverts commit 8ab17f5ce0.
2026-04-16 11:28:19 -04:00
Dax Raad 8ab17f5ce0 tui: fix path comparison in theme installer to handle different path formats 2026-04-16 11:18:44 -04:00
Dax Raad 6ce481e95b move useful scripts to script folder 2026-04-16 10:09:14 -04:00
Shoubhit Dash 42771c1db3 fix(compaction): budget retained tail with media 2026-04-16 17:30:29 +05:30
Shoubhit Dash 2e18a603f0 merge dev 2026-04-16 17:30:14 +05:30
opencode-agent[bot] 7341718f92 chore: generate 2026-04-16 07:15:05 +00:00
DaxandGitHub ef90b93205 fix: restore .gitignore logic for config dirs and migrate to shared Npm service (#22772) 2026-04-16 03:14:06 -04:00
DaxandGitHub 3f7df08be9 perf: make vcs init non-blocking by forking git branch resolution (#22771) 2026-04-16 03:02:19 -04:00
opencode-agent[bot] ef6c26c730 chore: update nix node_modules hashes 2026-04-16 06:59:47 +00:00
opencode-agent[bot] 8b3b608ba9 chore: generate 2026-04-16 06:11:24 +00:00
Brendan AllanandGitHub 97918500d4 app: start migrating bootstrap data fetching to TanStack Query (#22756) 2026-04-16 06:10:23 +00:00
Brendan AllanandGitHub e2c0803962 Fix desktop download asset names for beta channel (#22766) 2026-04-16 06:10:03 +00:00
f418fd5632 beta badge for desktop app (#14471)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
2026-04-16 06:03:41 +00:00
DaxandGitHub 675a46e23e CLI perf: reduce deps (#22652) 2026-04-16 02:03:03 -04:00
opencode-agent[bot] 150ab07a83 chore: generate 2026-04-16 05:03:50 +00:00
Kit LangtonandGitHub 6b20838981 feat: unwrap provider namespaces to flat exports + barrel (#22760) 2026-04-16 05:02:50 +00:00
opencode-agent[bot] c8af8f96ce chore: generate 2026-04-16 03:57:53 +00:00
Kit LangtonandGitHub 5011465c81 feat: unwrap tool namespaces to flat exports + barrel (#22762) 2026-04-16 03:56:54 +00:00
Kit LangtonandGitHub f6cc228684 feat: unwrap cli-tui namespaces to flat exports + barrel (#22759) 2026-04-16 03:56:51 +00:00
Kit LangtonandGitHub 9f4b73b6a3 fix: clean up final 16 no-unused-vars warnings (#22751) 2026-04-16 03:54:21 +00:00
Kit LangtonandGitHub bd29004831 feat: enable type-aware no-misused-spread rule, fix 8 violations (#22749) 2026-04-16 03:50:50 +00:00
Kit LangtonandGitHub 8aa0f9fe95 feat: enable type-aware no-base-to-string rule, fix 56 violations (#22750) 2026-04-16 03:50:47 +00:00
Kit LangtonandGitHub c802695ee9 docs: add circular import rules to namespace treeshake spec (#22754) 2026-04-15 23:44:08 -04:00
opencode-agent[bot] 225a769411 chore: generate 2026-04-16 03:42:25 +00:00
Kit LangtonandGitHub 0e20382396 fix: resolve circular sibling imports causing runtime ReferenceError (#22752) 2026-04-15 23:41:34 -04:00
Kit LangtonandGitHub 509bc11f81 feat: unwrap lsp namespaces to flat exports + barrel (#22748) 2026-04-15 23:30:52 -04:00
Kit LangtonandGitHub f24207844f feat: unwrap storage namespaces to flat exports + barrel (#22747) 2026-04-15 23:30:49 -04:00
Kit LangtonandGitHub 1ca257e356 feat: unwrap config namespaces to flat exports + barrel (#22746) 2026-04-15 23:29:14 -04:00
Kit LangtonandGitHub d4cfbd020d feat: unwrap effect namespaces to flat exports + barrel (#22745) 2026-04-15 23:29:12 -04:00
Kit LangtonandGitHub 581d5208ca feat: unwrap share namespaces to flat exports + barrel (#22744) 2026-04-15 23:28:46 -04:00
Kit LangtonandGitHub a427a28fa9 feat: unwrap project namespaces to flat exports + barrel (#22743) 2026-04-15 23:28:46 -04:00
opencode-agent[bot] 0beaf04df5 chore: generate 2026-04-16 03:28:30 +00:00
Kit LangtonandGitHub 80f1f1b5b8 feat: enable type-aware no-floating-promises rule, fix all 177 violations (#22741)
publish-github-action / publish (push) Has been cancelled
2026-04-15 23:27:32 -04:00
Aiden Cline 9819eb0461 tweak: disable 2026-04-09 23:11:09 -05:00
Shoubhit Dash aa86fb75ad refactor compaction tail selection 2026-04-10 09:36:39 +05:30
Shoubhit Dash 6f5a3d30fd keep recent turns during session compaction 2026-04-10 09:34:01 +05:30
671 changed files with 47361 additions and 34658 deletions
-1
View File
@@ -594,7 +594,6 @@ OPENCODE_DISABLE_CLAUDE_CODE
OPENCODE_DISABLE_CLAUDE_CODE_PROMPT OPENCODE_DISABLE_CLAUDE_CODE_PROMPT
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS OPENCODE_DISABLE_CLAUDE_CODE_SKILLS
OPENCODE_DISABLE_DEFAULT_PLUGINS OPENCODE_DISABLE_DEFAULT_PLUGINS
OPENCODE_DISABLE_FILETIME_CHECK
OPENCODE_DISABLE_LSP_DOWNLOAD OPENCODE_DISABLE_LSP_DOWNLOAD
OPENCODE_DISABLE_MODELS_FETCH OPENCODE_DISABLE_MODELS_FETCH
OPENCODE_DISABLE_PRUNE OPENCODE_DISABLE_PRUNE
+1 -5
View File
@@ -1,10 +1,6 @@
{ {
"$schema": "https://opencode.ai/config.json", "$schema": "https://opencode.ai/config.json",
"provider": { "provider": {},
"opencode": {
"options": {},
},
},
"permission": { "permission": {
"edit": { "edit": {
"packages/opencode/migration/*": "deny", "packages/opencode/migration/*": "deny",
+1 -1
View File
@@ -7,7 +7,7 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
Accept: "application/vnd.github+json", Accept: "application/vnd.github+json",
"Content-Type": "application/json", "Content-Type": "application/json",
...options.headers, ...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers),
}, },
}) })
if (!response.ok) { if (!response.ok) {
+1 -1
View File
@@ -28,7 +28,7 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
Accept: "application/vnd.github+json", Accept: "application/vnd.github+json",
"Content-Type": "application/json", "Content-Type": "application/json",
...options.headers, ...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers),
}, },
}) })
if (!response.ok) { if (!response.ok) {
+17 -2
View File
@@ -1,9 +1,13 @@
{ {
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json", "$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
"options": {
"typeAware": true
},
"categories": { "categories": {
"suspicious": "warn" "suspicious": "warn"
}, },
"rules": { "rules": {
"typescript/no-base-to-string": "warn",
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield // Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
"require-yield": "off", "require-yield": "off",
// SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime // SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime
@@ -30,7 +34,18 @@
// postMessage target origin not relevant for this codebase // postMessage target origin not relevant for this codebase
"unicorn/require-post-message-target-origin": "off", "unicorn/require-post-message-target-origin": "off",
// Side-effectful constructors are intentional in some places // Side-effectful constructors are intentional in some places
"no-new": "off" "no-new": "off",
// Type-aware: catch unhandled promises
"typescript/no-floating-promises": "warn",
// Warn when spreading non-plain objects (Headers, class instances, etc.)
"typescript/no-misused-spread": "warn"
}, },
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts"] "options": {
"typeAware": true
},
"options": {
"typeAware": true
},
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts", "**/sdk.gen.ts"]
} }
+1
View File
@@ -14,6 +14,7 @@
- Use Bun APIs when possible, like `Bun.file()` - Use Bun APIs when possible, like `Bun.file()`
- Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity - Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream - Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
Reduce total variable count by inlining when a value is only used once. Reduce total variable count by inlining when a value is only used once.
+117 -53
View File
@@ -20,6 +20,7 @@
"glob": "13.0.5", "glob": "13.0.5",
"husky": "9.1.7", "husky": "9.1.7",
"oxlint": "1.60.0", "oxlint": "1.60.0",
"oxlint-tsgolint": "0.21.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"semver": "^7.6.0", "semver": "^7.6.0",
"sst": "3.18.10", "sst": "3.18.10",
@@ -28,7 +29,7 @@
}, },
"packages/app": { "packages/app": {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
@@ -82,7 +83,7 @@
}, },
"packages/console/app": { "packages/console/app": {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
@@ -116,7 +117,7 @@
}, },
"packages/console/core": { "packages/console/core": {
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@aws-sdk/client-sts": "3.782.0", "@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1", "@jsx-email/render": "1.1.1",
@@ -143,7 +144,7 @@
}, },
"packages/console/function": { "packages/console/function": {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "3.0.64", "@ai-sdk/anthropic": "3.0.64",
"@ai-sdk/openai": "3.0.48", "@ai-sdk/openai": "3.0.48",
@@ -167,7 +168,7 @@
}, },
"packages/console/mail": { "packages/console/mail": {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@jsx-email/all": "2.2.3", "@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3", "@jsx-email/cli": "1.4.3",
@@ -189,9 +190,35 @@
"cloudflare": "5.2.0", "cloudflare": "5.2.0",
}, },
}, },
"packages/core": {
"name": "@opencode-ai/core",
"version": "1.4.11",
"bin": {
"opencode": "./bin/opencode",
},
"dependencies": {
"@effect/platform-node": "catalog:",
"@npmcli/arborist": "catalog:",
"effect": "catalog:",
"glob": "13.0.5",
"mime-types": "3.0.2",
"minimatch": "10.2.5",
"npm-package-arg": "13.0.2",
"semver": "catalog:",
"xdg-basedir": "5.1.0",
"zod": "catalog:",
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@types/npm-package-arg": "6.1.4",
"@types/npmcli__arborist": "6.3.3",
"@types/semver": "catalog:",
},
},
"packages/desktop": { "packages/desktop": {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@opencode-ai/app": "workspace:*", "@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
@@ -224,8 +251,9 @@
}, },
"packages/desktop-electron": { "packages/desktop-electron": {
"name": "@opencode-ai/desktop-electron", "name": "@opencode-ai/desktop-electron",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:", "effect": "catalog:",
"electron-context-menu": "4.1.2", "electron-context-menu": "4.1.2",
"electron-log": "^5", "electron-log": "^5",
@@ -247,7 +275,7 @@
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:", "@typescript/native-preview": "catalog:",
"@valibot/to-json-schema": "1.6.0", "@valibot/to-json-schema": "1.6.0",
"electron": "40.4.1", "electron": "41.2.1",
"electron-builder": "^26", "electron-builder": "^26",
"electron-vite": "^5", "electron-vite": "^5",
"solid-js": "catalog:", "solid-js": "catalog:",
@@ -267,7 +295,7 @@
}, },
"packages/enterprise": { "packages/enterprise": {
"name": "@opencode-ai/enterprise", "name": "@opencode-ai/enterprise",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@opencode-ai/shared": "workspace:*", "@opencode-ai/shared": "workspace:*",
"@opencode-ai/ui": "workspace:*", "@opencode-ai/ui": "workspace:*",
@@ -296,7 +324,7 @@
}, },
"packages/function": { "packages/function": {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@octokit/auth-app": "8.0.1", "@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:", "@octokit/rest": "catalog:",
@@ -312,7 +340,7 @@
}, },
"packages/opencode": { "packages/opencode": {
"name": "opencode", "name": "opencode",
"version": "1.4.6", "version": "1.14.18",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -321,15 +349,15 @@
"@actions/github": "6.0.1", "@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.16.1", "@agentclientprotocol/sdk": "0.16.1",
"@ai-sdk/alibaba": "1.0.17", "@ai-sdk/alibaba": "1.0.17",
"@ai-sdk/amazon-bedrock": "4.0.93", "@ai-sdk/amazon-bedrock": "4.0.96",
"@ai-sdk/anthropic": "3.0.67", "@ai-sdk/anthropic": "3.0.71",
"@ai-sdk/azure": "3.0.49", "@ai-sdk/azure": "3.0.49",
"@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cerebras": "2.0.41",
"@ai-sdk/cohere": "3.0.27", "@ai-sdk/cohere": "3.0.27",
"@ai-sdk/deepinfra": "2.0.41", "@ai-sdk/deepinfra": "2.0.41",
"@ai-sdk/gateway": "3.0.97", "@ai-sdk/gateway": "3.0.104",
"@ai-sdk/google": "3.0.63", "@ai-sdk/google": "3.0.63",
"@ai-sdk/google-vertex": "4.0.109", "@ai-sdk/google-vertex": "4.0.112",
"@ai-sdk/groq": "3.0.31", "@ai-sdk/groq": "3.0.31",
"@ai-sdk/mistral": "3.0.27", "@ai-sdk/mistral": "3.0.27",
"@ai-sdk/openai": "3.0.53", "@ai-sdk/openai": "3.0.53",
@@ -364,8 +392,8 @@
"@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
"@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1",
"@opentelemetry/sdk-trace-node": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1",
"@opentui/core": "0.1.99", "@opentui/core": "0.1.101",
"@opentui/solid": "0.1.99", "@opentui/solid": "0.1.101",
"@parcel/watcher": "2.5.1", "@parcel/watcher": "2.5.1",
"@pierre/diffs": "catalog:", "@pierre/diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2", "@solid-primitives/event-bus": "1.1.2",
@@ -385,7 +413,7 @@
"drizzle-orm": "catalog:", "drizzle-orm": "catalog:",
"effect": "catalog:", "effect": "catalog:",
"fuzzysort": "3.1.0", "fuzzysort": "3.1.0",
"gitlab-ai-provider": "6.4.2", "gitlab-ai-provider": "6.6.0",
"glob": "13.0.5", "glob": "13.0.5",
"google-auth-library": "10.5.0", "google-auth-library": "10.5.0",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
@@ -403,7 +431,6 @@
"opentui-spinner": "0.0.6", "opentui-spinner": "0.0.6",
"partial-json": "0.1.7", "partial-json": "0.1.7",
"remeda": "catalog:", "remeda": "catalog:",
"ripgrep": "0.3.1",
"semver": "^7.6.3", "semver": "^7.6.3",
"solid-js": "catalog:", "solid-js": "catalog:",
"strip-ansi": "7.1.2", "strip-ansi": "7.1.2",
@@ -457,23 +484,23 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"effect": "catalog:", "effect": "catalog:",
"zod": "catalog:", "zod": "catalog:",
}, },
"devDependencies": { "devDependencies": {
"@opentui/core": "0.1.99", "@opentui/core": "0.1.101",
"@opentui/solid": "0.1.99", "@opentui/solid": "0.1.101",
"@tsconfig/node22": "catalog:", "@tsconfig/node22": "catalog:",
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:", "@typescript/native-preview": "catalog:",
"typescript": "catalog:", "typescript": "catalog:",
}, },
"peerDependencies": { "peerDependencies": {
"@opentui/core": ">=0.1.99", "@opentui/core": ">=0.1.101",
"@opentui/solid": ">=0.1.99", "@opentui/solid": ">=0.1.101",
}, },
"optionalPeers": [ "optionalPeers": [
"@opentui/core", "@opentui/core",
@@ -492,7 +519,7 @@
}, },
"packages/sdk/js": { "packages/sdk/js": {
"name": "@opencode-ai/sdk", "name": "@opencode-ai/sdk",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"cross-spawn": "catalog:", "cross-spawn": "catalog:",
}, },
@@ -507,7 +534,7 @@
}, },
"packages/shared": { "packages/shared": {
"name": "@opencode-ai/shared", "name": "@opencode-ai/shared",
"version": "1.4.6", "version": "1.14.18",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -515,6 +542,7 @@
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
"@npmcli/arborist": "catalog:", "@npmcli/arborist": "catalog:",
"effect": "catalog:", "effect": "catalog:",
"glob": "13.0.5",
"mime-types": "3.0.2", "mime-types": "3.0.2",
"minimatch": "10.2.5", "minimatch": "10.2.5",
"semver": "catalog:", "semver": "catalog:",
@@ -522,13 +550,15 @@
"zod": "catalog:", "zod": "catalog:",
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:", "@types/bun": "catalog:",
"@types/npmcli__arborist": "6.3.3",
"@types/semver": "catalog:", "@types/semver": "catalog:",
}, },
}, },
"packages/slack": { "packages/slack": {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1", "@slack/bolt": "^3.17.1",
@@ -563,7 +593,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
@@ -612,7 +642,7 @@
}, },
"packages/web": { "packages/web": {
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"version": "1.4.6", "version": "1.14.18",
"dependencies": { "dependencies": {
"@astrojs/cloudflare": "12.6.3", "@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1", "@astrojs/markdown-remark": "6.3.1",
@@ -671,6 +701,8 @@
"@npmcli/arborist": "9.4.0", "@npmcli/arborist": "9.4.0",
"@octokit/rest": "22.0.0", "@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806", "@openauthjs/openauth": "0.0.0-20250322224806",
"@opentui/core": "0.1.99",
"@opentui/solid": "0.1.99",
"@pierre/diffs": "1.1.0-beta.18", "@pierre/diffs": "1.1.0-beta.18",
"@playwright/test": "1.59.1", "@playwright/test": "1.59.1",
"@solid-primitives/storage": "4.3.3", "@solid-primitives/storage": "4.3.3",
@@ -686,7 +718,7 @@
"@types/node": "22.13.9", "@types/node": "22.13.9",
"@types/semver": "7.7.1", "@types/semver": "7.7.1",
"@typescript/native-preview": "7.0.0-dev.20251207.1", "@typescript/native-preview": "7.0.0-dev.20251207.1",
"ai": "6.0.158", "ai": "6.0.168",
"cross-spawn": "7.0.6", "cross-spawn": "7.0.6",
"diff": "8.0.2", "diff": "8.0.2",
"dompurify": "3.3.1", "dompurify": "3.3.1",
@@ -734,7 +766,7 @@
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="], "@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@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-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="], "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.96", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@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-Mc4Ias2jRMD1jOB6xWtKNPdhECeuCZyIlbr9EAGfBnyBt++sS13ziZh9qv9TdyMCAZJ7xoQcpbchoRJcKwPdpA=="],
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="], "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="],
@@ -754,11 +786,11 @@
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.46", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XRKR0zgRyegdmtK5CDUEjlyRp0Fo+XVCdoG+301U1SGtgRIAYG3ObVtgzVJBVpJdHFSLHuYeLTnNiQoUxD7+FQ=="], "@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.46", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XRKR0zgRyegdmtK5CDUEjlyRp0Fo+XVCdoG+301U1SGtgRIAYG3ObVtgzVJBVpJdHFSLHuYeLTnNiQoUxD7+FQ=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.97", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ERHmVGX30YKTwxObuHQzNqoOf8Nb5WwYMDBn34e3TGGVn0vLEXwMimo7uRVTbhhi4gfu9WtwYTE4x1+csZok1w=="], "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
"@ai-sdk/google": ["@ai-sdk/google@3.0.63", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RfOZWVMYSPu2sPRfGajrauWAZ9BSaRopSn+AszkKWQ1MFj8nhaXvCqRHB5pBQUaHTfZKagvOmMpNfa/s3gPLgQ=="], "@ai-sdk/google": ["@ai-sdk/google@3.0.63", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RfOZWVMYSPu2sPRfGajrauWAZ9BSaRopSn+AszkKWQ1MFj8nhaXvCqRHB5pBQUaHTfZKagvOmMpNfa/s3gPLgQ=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.109", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/google": "3.0.63", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-QzQ+DgOoSYlkU4mK0H+iaCaW1bl5zOimH9X2E2oylcVyUtAdCuduQ959Uw1ygW3l09J2K/ceEDtK8OUPHyOA7g=="], "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.112", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/google": "3.0.64", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cSfHCkM+9ZrFtQWIN1WlV93JPD+isGSdFxKj7u1L9m2aLVZajlXdcE41GL9hMt7ld7bZYE4NnZ+4VLxBAHE+Eg=="],
"@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="], "@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
@@ -1542,6 +1574,8 @@
"@opencode-ai/console-resource": ["@opencode-ai/console-resource@workspace:packages/console/resource"], "@opencode-ai/console-resource": ["@opencode-ai/console-resource@workspace:packages/console/resource"],
"@opencode-ai/core": ["@opencode-ai/core@workspace:packages/core"],
"@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"], "@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"],
"@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"], "@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"],
@@ -1582,7 +1616,7 @@
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="], "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="],
"@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], "@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="],
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],
@@ -1594,21 +1628,21 @@
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
"@opentui/core": ["@opentui/core@0.1.99", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.99", "@opentui/core-darwin-x64": "0.1.99", "@opentui/core-linux-arm64": "0.1.99", "@opentui/core-linux-x64": "0.1.99", "@opentui/core-win32-arm64": "0.1.99", "@opentui/core-win32-x64": "0.1.99", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-I3+AEgGzqNWIpWX9g2WOscSPwtQDNOm4KlBjxBWCZjLxkF07u77heWXF7OiAdhKLtNUW6TFiyt6yznqAZPdG3A=="], "@opentui/core": ["@opentui/core@0.1.101", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.101", "@opentui/core-darwin-x64": "0.1.101", "@opentui/core-linux-arm64": "0.1.101", "@opentui/core-linux-x64": "0.1.101", "@opentui/core-win32-arm64": "0.1.101", "@opentui/core-win32-x64": "0.1.101", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-8jUhNKnwCDO3Y2iiEmagoQLjgX5l1WbddQiwky8B5JU4FW0/WRHairBmU1kRAQBmhdeg57dVinSG4iu2PAtKEA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.99", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bzVrqeX2vb5iWrc/ftOUOqeUY8XO+qSgoTwj5TXHuwagavgwD3Hpeyjx8+icnTTeM4pao0som1WR9xfye6/X5Q=="], "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.101", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HtqZh8TIKCH1Nge5J0etBCpzYfPY4fVcq110uJm2As6D/dTTPv8r4J+KkrqoSphkpj/Y2b4t7KpqNHthXA0EVw=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.99", "", { "os": "darwin", "cpu": "x64" }, "sha512-VE4FrXBYpkxnvkqcCV1a8aN9jyyMJMihVW+V2NLCtp+4yQsj0AapG5TiUSN76XnmSZRptxDy5rBmEempeoIZbg=="], "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.101", "", { "os": "darwin", "cpu": "x64" }, "sha512-o5ClQWnGG1inRE2YZAatPw1jPEAJni00amcoIfKBj8e1WS+fQA+iQTq1xFunNcyNPObLDCVuW1X+NrbK9xmPvQ=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.99", "", { "os": "linux", "cpu": "arm64" }, "sha512-viXQsbpS7yHjYkl7+am32JdvG96QU9lvHh1UiZtpOxcNUUqiYmA2ZwZFPD2Bi54jNyj5l2hjH6YkD3DzE2FEWA=="], "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.101", "", { "os": "linux", "cpu": "arm64" }, "sha512-E/weY7DQpaPWGYDPD0CROHowUotqnVlk7Kb6l9+iZCrxm9s7HPRHkcMDVmcWDqHEqa/J879EJcqaUDzDArqC+w=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.99", "", { "os": "linux", "cpu": "x64" }, "sha512-WLoEFINOSp0tZSR9y4LUuGc7n4Y7H1wcpjUPzQ9vChkYDXrfZltEanzoDWbDcQ4kZQW5tHVC7LrZHpAsRLwFZg=="], "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.101", "", { "os": "linux", "cpu": "x64" }, "sha512-+Bfr8jLbbR1WREUMCCvSZ44G1+WU2lPqJx7x1StTa9iFNEdicxCdd0QQsO6cnKn5yW+2Pr/FdrqHbxSQw3ejbA=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.99", "", { "os": "win32", "cpu": "arm64" }, "sha512-yWMOLWCEO8HdrctU1dMkgZC8qGkiO4Dwr4/e11tTvVpRmYhDsP/IR89ZjEEtOwnKwFOFuB/MxvflqaEWVQ2g5Q=="], "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.101", "", { "os": "win32", "cpu": "arm64" }, "sha512-LTMIHJzJrVqS8mgpp+tuyVHuqYlicQTvFi/sTsJ6Xswf1asatsvZYsbQByhBLpFT80j10G7uvDa361S5gjCUDA=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.99", "", { "os": "win32", "cpu": "x64" }, "sha512-aYRlsL2w8YRL6vPd7/hrqlNVkXU3QowWb01TOvAcHS8UAsXaGFUr47kSDyjxDi1wg1MzmVduCfsC7T3NoThV1w=="], "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.101", "", { "os": "win32", "cpu": "x64" }, "sha512-VaMs5bg6y0tYKptaEK8Hy5wTp4m//wJRKUdW8uvrS9cFgxyovZGuw0+TfK3NgbdeX+8jWm8LEAiak4jle5BABg=="],
"@opentui/solid": ["@opentui/solid@0.1.99", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.99", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-DrqqO4h2V88FmeIP2cErYkMU0ZK5MrUsZw3w6IzZpoXyyiL4/9qpWzUq+CXx+r16VP2iGxDJwGKUmtFAzUch2Q=="], "@opentui/solid": ["@opentui/solid@0.1.101", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.101", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-STY2FQYtVS2rhUgpslG6mM0EAkgobBDF91+B+SNmvXIkJwP+ydP6UVgcuIo5McIbb9GIbAODx5X2Q48PSR7hgw=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1680,6 +1714,18 @@
"@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.96.0", "", { "os": "win32", "cpu": "x64" }, "sha512-0fI0P0W7bSO/GCP/N5dkmtB9vBqCA4ggo1WmXTnxNJVmFFOtcA1vYm1I9jl8fxo+sucW2WnlpnI4fjKdo3JKxA=="], "@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.96.0", "", { "os": "win32", "cpu": "x64" }, "sha512-0fI0P0W7bSO/GCP/N5dkmtB9vBqCA4ggo1WmXTnxNJVmFFOtcA1vYm1I9jl8fxo+sucW2WnlpnI4fjKdo3JKxA=="],
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.21.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P20j3MLqfwIT+94qGU3htC7dWp4pXGZW1p1p7FRUzu1aopq7c9nPCgf0W/WjktqQ57+iuTq9mbSlwWinl6+H1A=="],
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.21.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-81TmmuBcPedEA0MwRmObuQuXnCprS1UiHQWGe7pseqNAJzUWXeAPrayqKTACX92VpruJI+yvY0XJrFp11PpcTA=="],
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.21.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-sbjBr6zDduX8rNO0PTjhf7VYLCPWqdijWiMPp8e10qu6Tam1GdaVLaLlX8QrNupTgglO1GvqqgY/jcacWL8a6g=="],
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.21.0", "", { "os": "linux", "cpu": "x64" }, "sha512-jNrOcy53R5TJQfrK444Cm60bW9437xDoxPbm3AdvFSo/fhdFMllawc7uZC2Wzr+EAjTkW13K8R4QHzsUdBG9fQ=="],
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.21.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-xWeRxJJILDE4b9UqHEWGBxcBc1TUS6zWHhxcyxTZMwf4q3wdKeu0OHYAcwLGJzoSjEIf6FTjyfPiRNil2oqsdg=="],
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.21.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Ob9AA9teI8ckPo1whV1smLr5NrqwgBv/8boDbK0YZG+fKgNGRwr1hBj1ORgFWOQaUBv+5njp5A0RAfJJjQ95QQ=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-YdeJKaZckDQL1qa62a1aKq/goyq48aX3yOxaaWqWb4sau4Ee4IiLbamftNLU3zbePky6QsDj6thnSSzHRBjDfA=="], "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-YdeJKaZckDQL1qa62a1aKq/goyq48aX3yOxaaWqWb4sau4Ee4IiLbamftNLU3zbePky6QsDj6thnSSzHRBjDfA=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-7ANS7PpXCfq84xZQ8E5WPs14gwcuPcl+/8TFNXfpSu0CQBXz3cUo2fDpHT8v8HJN+Ut02eacvMAzTnc9s6X4tw=="], "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-7ANS7PpXCfq84xZQ8E5WPs14gwcuPcl+/8TFNXfpSu0CQBXz3cUo2fDpHT8v8HJN+Ut02eacvMAzTnc9s6X4tw=="],
@@ -2438,7 +2484,7 @@
"@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="], "@valibot/to-json-schema": ["@valibot/to-json-schema@1.6.0", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="],
"@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
@@ -2498,7 +2544,7 @@
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"ai": ["ai@6.0.158", "", { "dependencies": { "@ai-sdk/gateway": "3.0.95", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gLTp1UXFtMqKUi3XHs33K7UFglbvojkxF/aq337TxnLGOhHIW9+GyP2jwW4hYX87f1es+wId3VQoPRRu9zEStQ=="], "ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="],
"ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="], "ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="],
@@ -3008,7 +3054,7 @@
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"electron": ["electron@40.4.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-N1ZXybQZL8kYemO8vAeh9nrk4mSvqlAO8xs0QCHkXIvRnuB/7VGwEehjvQbsU5/f4bmTKpG+2GQERe/zmKpudQ=="], "electron": ["electron@41.2.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-teeRThiYGTPKf/2yOW7zZA1bhb91KEQ4yLBPOg7GxpmnkLFLugKgQaAKOrCgdzwsXh/5mFIfmkm+4+wACJKwaA=="],
"electron-builder": ["electron-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="], "electron-builder": ["electron-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="],
@@ -3288,7 +3334,7 @@
"get-tsconfig": ["get-tsconfig@4.13.8", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-J87BxkLXykmisLQ+KA4x2+O6rVf+PJrtFUO8lGyiRg4lyxJLJ8/v0sRAKdVZQOy6tR6lMRAF1NqzCf9BQijm0w=="], "get-tsconfig": ["get-tsconfig@4.13.8", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-J87BxkLXykmisLQ+KA4x2+O6rVf+PJrtFUO8lGyiRg4lyxJLJ8/v0sRAKdVZQOy6tR6lMRAF1NqzCf9BQijm0w=="],
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="], "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#20bd361", {}, "anomalyco-ghostty-web-20bd361", "sha512-dW0nwaiBBcun9y5WJSvm3HxDLe5o9V0xLCndQvWonRVubU8CS1PHxZpLffyPt1YujPWC13ez03aWxcuKBPYYGQ=="],
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
@@ -3296,7 +3342,7 @@
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"gitlab-ai-provider": ["gitlab-ai-provider@6.4.2", "", { "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-Wyw6uslCuipBOr/NYwAtpgXEUJj68iJY5aekad2DjePN99JetKVQBqkLgAy9PZp2EA4OuscfRQu9qKIBN/evNw=="], "gitlab-ai-provider": ["gitlab-ai-provider@6.6.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-jUxYnKA4XQaPc3wxACDZ8bPDXO0Mzx7cZaBDxbT2uGgLqtGZmSi+9tVNIg7louSS+s/ioVra3SoUz3iOFVhKPA=="],
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
@@ -4100,6 +4146,8 @@
"oxlint": ["oxlint@1.60.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.60.0", "@oxlint/binding-android-arm64": "1.60.0", "@oxlint/binding-darwin-arm64": "1.60.0", "@oxlint/binding-darwin-x64": "1.60.0", "@oxlint/binding-freebsd-x64": "1.60.0", "@oxlint/binding-linux-arm-gnueabihf": "1.60.0", "@oxlint/binding-linux-arm-musleabihf": "1.60.0", "@oxlint/binding-linux-arm64-gnu": "1.60.0", "@oxlint/binding-linux-arm64-musl": "1.60.0", "@oxlint/binding-linux-ppc64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-musl": "1.60.0", "@oxlint/binding-linux-s390x-gnu": "1.60.0", "@oxlint/binding-linux-x64-gnu": "1.60.0", "@oxlint/binding-linux-x64-musl": "1.60.0", "@oxlint/binding-openharmony-arm64": "1.60.0", "@oxlint/binding-win32-arm64-msvc": "1.60.0", "@oxlint/binding-win32-ia32-msvc": "1.60.0", "@oxlint/binding-win32-x64-msvc": "1.60.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-tnRzTWiWJ9pg3ftRWnD0+Oqh78L6ZSwcEudvCZaER0PIqiAnNyXj5N1dPwjmNpDalkKS9m/WMLN1CTPUBPmsgw=="], "oxlint": ["oxlint@1.60.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.60.0", "@oxlint/binding-android-arm64": "1.60.0", "@oxlint/binding-darwin-arm64": "1.60.0", "@oxlint/binding-darwin-x64": "1.60.0", "@oxlint/binding-freebsd-x64": "1.60.0", "@oxlint/binding-linux-arm-gnueabihf": "1.60.0", "@oxlint/binding-linux-arm-musleabihf": "1.60.0", "@oxlint/binding-linux-arm64-gnu": "1.60.0", "@oxlint/binding-linux-arm64-musl": "1.60.0", "@oxlint/binding-linux-ppc64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-musl": "1.60.0", "@oxlint/binding-linux-s390x-gnu": "1.60.0", "@oxlint/binding-linux-x64-gnu": "1.60.0", "@oxlint/binding-linux-x64-musl": "1.60.0", "@oxlint/binding-openharmony-arm64": "1.60.0", "@oxlint/binding-win32-arm64-msvc": "1.60.0", "@oxlint/binding-win32-ia32-msvc": "1.60.0", "@oxlint/binding-win32-x64-msvc": "1.60.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-tnRzTWiWJ9pg3ftRWnD0+Oqh78L6ZSwcEudvCZaER0PIqiAnNyXj5N1dPwjmNpDalkKS9m/WMLN1CTPUBPmsgw=="],
"oxlint-tsgolint": ["oxlint-tsgolint@0.21.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.21.0", "@oxlint-tsgolint/darwin-x64": "0.21.0", "@oxlint-tsgolint/linux-arm64": "0.21.0", "@oxlint-tsgolint/linux-x64": "0.21.0", "@oxlint-tsgolint/win32-arm64": "0.21.0", "@oxlint-tsgolint/win32-x64": "0.21.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-HiWPhANwRnN1pZJQ2SgNB3WRR+1etLJHmRzQ/MJhyINsEIaOUCjxhlXJKbEaVUwdnyXwRWqo/P9Fx21lz0/mSg=="],
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
"p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="], "p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="],
@@ -4462,8 +4510,6 @@
"rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], "rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="],
"ripgrep": ["ripgrep@0.3.1", "", { "bin": { "rg": "lib/rg.mjs", "ripgrep": "lib/rg.mjs" } }, "sha512-6bDtNIBh1qPviVIU685/4uv0Ap5t8eS4wiJhy/tR2LdIeIey9CVasENlGS+ul3HnTmGANIp7AjnfsztsRmALfQ=="],
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
"rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
@@ -5134,7 +5180,11 @@
"@ai-sdk/alibaba/@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=="], "@ai-sdk/alibaba/@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=="],
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.13", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ=="],
"@ai-sdk/amazon-bedrock/@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=="],
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
@@ -5148,7 +5198,9 @@
"@ai-sdk/fireworks/@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=="], "@ai-sdk/fireworks/@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=="],
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
"@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="],
"@ai-sdk/google-vertex/@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=="], "@ai-sdk/google-vertex/@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=="],
@@ -5558,6 +5610,18 @@
"@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="], "@opencode-ai/web/@shikijs/transformers": ["@shikijs/transformers@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/types": "3.20.0" } }, "sha512-PrHHMRr3Q5W1qB/42kJW6laqFyWdhrPF2hNR9qjOm1xcSiAO3hAHo7HaVyHE6pMyevmy3i51O8kuGGXC78uK3g=="],
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
"@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
"@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="],
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
"@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
"@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
"@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], "@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
"@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="], "@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="],
@@ -5664,7 +5728,7 @@
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="], "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@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-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="],
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="],
@@ -5882,7 +5946,7 @@
"nypm/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], "nypm/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FFX4P5Fd6lcQJc2OLngZQkbbJHa0IDDZi087Edb8qRZx6h90krtM61ArbMUL8us/7ZUwojCXnyJ/wQ2Eflx2jQ=="], "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
"opencode/@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-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=="],
+1 -1
View File
@@ -513,7 +513,7 @@ async function subscribeSessionEvents() {
const decoder = new TextDecoder() const decoder = new TextDecoder()
let text = "" let text = ""
;(async () => { void (async () => {
while (true) { while (true) {
try { try {
const { done, value } = await reader.read() const { done, value } = await reader.read()
-1
View File
@@ -236,7 +236,6 @@ new sst.cloudflare.x.SolidStart("Console", {
SALESFORCE_INSTANCE_URL, SALESFORCE_INSTANCE_URL,
ZEN_BLACK_PRICE, ZEN_BLACK_PRICE,
ZEN_LITE_PRICE, ZEN_LITE_PRICE,
new sst.Secret("ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES"),
new sst.Secret("ZEN_LIMITS"), new sst.Secret("ZEN_LIMITS"),
new sst.Secret("ZEN_SESSION_SECRET"), new sst.Secret("ZEN_SESSION_SECRET"),
...ZEN_MODELS, ...ZEN_MODELS,
+4 -4
View File
@@ -1,8 +1,8 @@
{ {
"nodeModules": { "nodeModules": {
"x86_64-linux": "sha256-VIgTxIjmZ4Bfwwdj/YFmRJdBpPHYhJSY31kh06EXX+0=", "x86_64-linux": "sha256-YcVW8AGN3TP34CoBoCw+Fx30RL1aveNvxr5eoeOgYeg=",
"aarch64-linux": "sha256-9118AS1ED0nrliURgZYBRuF/18RqXpUouhYJRlZ6jeA=", "aarch64-linux": "sha256-G/J3YFfrpEwXSHa25kNyUhYpwPhzNIZf/4v+RCfuslk=",
"aarch64-darwin": "sha256-ppo3MfSIGKQHJCdYEZiLFRc61PtcJ9J0kAXH1pNIonA=", "aarch64-darwin": "sha256-dNPYrGWKoafk4rHqc34U34TtiJGk87yUv5tKnliQcWs=",
"x86_64-darwin": "sha256-m+CZSOglBCTfNzbdBX6hXdDqqOzHNMzAddVp6BZVDtU=" "x86_64-darwin": "sha256-1LStvefCajGkbdXobMpk0IQyw9SQcQgGKE+U3Fc0Osw="
} }
} }
+1
View File
@@ -55,6 +55,7 @@ stdenvNoCC.mkDerivation {
--filter './packages/opencode' \ --filter './packages/opencode' \
--filter './packages/desktop' \ --filter './packages/desktop' \
--filter './packages/app' \ --filter './packages/app' \
--filter './packages/shared' \
--frozen-lockfile \ --frozen-lockfile \
--ignore-scripts \ --ignore-scripts \
--no-progress --no-progress
+18 -17
View File
@@ -7,6 +7,7 @@
sysctl, sysctl,
makeBinaryWrapper, makeBinaryWrapper,
models-dev, models-dev,
ripgrep,
installShellFiles, installShellFiles,
versionCheckHook, versionCheckHook,
writableTmpDirAsHomeHook, writableTmpDirAsHomeHook,
@@ -51,25 +52,25 @@ stdenvNoCC.mkDerivation (finalAttrs: {
runHook postBuild runHook postBuild
''; '';
installPhase = installPhase = ''
'' runHook preInstall
runHook preInstall
install -Dm755 dist/opencode-*/bin/opencode $out/bin/opencode install -Dm755 dist/opencode-*/bin/opencode $out/bin/opencode
install -Dm644 schema.json $out/share/opencode/schema.json install -Dm644 schema.json $out/share/opencode/schema.json
''
# bun runs sysctl to detect if dunning on rosetta2 wrapProgram $out/bin/opencode \
+ lib.optionalString stdenvNoCC.hostPlatform.isDarwin '' --prefix PATH : ${
wrapProgram $out/bin/opencode \ lib.makeBinPath (
--prefix PATH : ${ [
lib.makeBinPath [ ripgrep
sysctl
] ]
} # bun runs sysctl to detect if dunning on rosetta2
'' ++ lib.optional stdenvNoCC.hostPlatform.isDarwin sysctl
+ '' )
runHook postInstall }
'';
runHook postInstall
'';
postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) '' postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) ''
# trick yargs into also generating zsh completions # trick yargs into also generating zsh completions
+5 -2
View File
@@ -7,7 +7,7 @@
"packageManager": "bun@1.3.11", "packageManager": "bun@1.3.11",
"scripts": { "scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev:desktop": "bun --cwd packages/desktop tauri dev", "dev:desktop": "bun --cwd packages/desktop-electron dev",
"dev:web": "bun --cwd packages/app dev", "dev:web": "bun --cwd packages/app dev",
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
"dev:storybook": "bun --cwd packages/storybook storybook", "dev:storybook": "bun --cwd packages/storybook storybook",
@@ -34,6 +34,8 @@
"@types/cross-spawn": "6.0.6", "@types/cross-spawn": "6.0.6",
"@octokit/rest": "22.0.0", "@octokit/rest": "22.0.0",
"@hono/zod-validator": "0.4.2", "@hono/zod-validator": "0.4.2",
"@opentui/core": "0.1.99",
"@opentui/solid": "0.1.99",
"ulid": "3.0.1", "ulid": "3.0.1",
"@kobalte/core": "0.13.11", "@kobalte/core": "0.13.11",
"@types/luxon": "3.7.1", "@types/luxon": "3.7.1",
@@ -51,7 +53,7 @@
"drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-kit": "1.0.0-beta.19-d95b7a4",
"drizzle-orm": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4",
"effect": "4.0.0-beta.48", "effect": "4.0.0-beta.48",
"ai": "6.0.158", "ai": "6.0.168",
"cross-spawn": "7.0.6", "cross-spawn": "7.0.6",
"hono": "4.10.7", "hono": "4.10.7",
"hono-openapi": "1.1.2", "hono-openapi": "1.1.2",
@@ -87,6 +89,7 @@
"glob": "13.0.5", "glob": "13.0.5",
"husky": "9.1.7", "husky": "9.1.7",
"oxlint": "1.60.0", "oxlint": "1.60.0",
"oxlint-tsgolint": "0.21.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"semver": "^7.6.0", "semver": "^7.6.0",
"sst": "3.18.10", "sst": "3.18.10",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.4.6", "version": "1.14.18",
"description": "", "description": "",
"type": "module", "type": "module",
"exports": { "exports": {
+18 -9
View File
@@ -121,10 +121,10 @@ function SessionProviders(props: ParentProps) {
function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) { function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
return ( return (
<AppShellProviders> <AppShellProviders>
<Suspense fallback={<Loading />}> {/*<Suspense fallback={<Loading />}>*/}
{props.appChildren} {props.appChildren}
{props.children} {props.children}
</Suspense> {/*</Suspense>*/}
</AppShellProviders> </AppShellProviders>
) )
} }
@@ -184,32 +184,41 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) {
) )
return ( return (
<Show <Suspense
when={checkMode() === "blocking" ? !startupHealthCheck.loading : startupHealthCheck.state !== "pending"}
fallback={ fallback={
<div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base"> <div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base">
<Splash class="w-16 h-20 opacity-50 animate-pulse" /> <Splash class="w-16 h-20 opacity-50 animate-pulse" />
</div> </div>
} }
> >
{/*<Show
when={checkMode() === "blocking" ? !startupHealthCheck.loading : startupHealthCheck.state !== "pending"}
fallback={
<div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base">
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
</div>
}
>*/}
{checkMode() === "blocking" ? startupHealthCheck() : startupHealthCheck.latest}
<Show <Show
when={startupHealthCheck()} when={startupHealthCheck()}
fallback={ fallback={
<ConnectionError <ConnectionError
onRetry={() => { onRetry={() => {
if (checkMode() === "background") healthCheckActions.refetch() if (checkMode() === "background") void healthCheckActions.refetch()
}} }}
onServerSelected={(key) => { onServerSelected={(key) => {
setCheckMode("blocking") setCheckMode("blocking")
server.setActive(key) server.setActive(key)
healthCheckActions.refetch() void healthCheckActions.refetch()
}} }}
/> />
} }
> >
{props.children} {props.children}
</Show> </Show>
</Show> {/*</Show>*/}
</Suspense>
) )
} }
@@ -327,7 +327,7 @@ export function DialogConnectProvider(props: { provider: string }) {
if (loading()) return if (loading()) return
if (methods().length === 1) { if (methods().length === 1) {
auto = true auto = true
selectMethod(0) void selectMethod(0)
} }
}) })
@@ -373,7 +373,7 @@ export function DialogConnectProvider(props: { provider: string }) {
key={(m) => m?.label} key={(m) => m?.label}
onSelect={async (selected, index) => { onSelect={async (selected, index) => {
if (!selected) return if (!selected) return
selectMethod(index) void selectMethod(index)
}} }}
> >
{(i) => ( {(i) => (
@@ -348,8 +348,8 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
const open = (path: string) => { const open = (path: string) => {
const value = file.tab(path) const value = file.tab(path)
tabs().open(value) void tabs().open(value)
file.load(path) void file.load(path)
if (!view().reviewPanel.opened()) view().reviewPanel.open() if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all") layout.fileTree.setTab("all")
props.onOpenFile?.(path) props.onOpenFile?.(path)
@@ -344,7 +344,7 @@ export function DialogSelectServer() {
createEffect(() => { createEffect(() => {
items() items()
refreshHealth() void refreshHealth()
const interval = setInterval(refreshHealth, 10_000) const interval = setInterval(refreshHealth, 10_000)
onCleanup(() => clearInterval(interval)) onCleanup(() => clearInterval(interval))
}) })
@@ -498,7 +498,7 @@ export function DialogSelectServer() {
async function handleRemove(url: ServerConnection.Key) { async function handleRemove(url: ServerConnection.Key) {
server.remove(url) server.remove(url)
if ((await platform.getDefaultServer?.()) === url) { if ((await platform.getDefaultServer?.()) === url) {
platform.setDefaultServer?.(null) void platform.setDefaultServer?.(null)
} }
} }
@@ -536,7 +536,7 @@ export function DialogSelectServer() {
items={sortedItems} items={sortedItems}
key={(x) => x.http.url} key={(x) => x.http.url}
onSelect={(x) => { onSelect={(x) => {
if (x) select(x) if (x) void select(x)
}} }}
divider={true} divider={true}
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent" class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
+114 -99
View File
@@ -54,6 +54,8 @@ import { PromptImageAttachments } from "./prompt-input/image-attachments"
import { PromptDragOverlay } from "./prompt-input/drag-overlay" import { PromptDragOverlay } from "./prompt-input/drag-overlay"
import { promptPlaceholder } from "./prompt-input/placeholder" import { promptPlaceholder } from "./prompt-input/placeholder"
import { ImagePreview } from "@opencode-ai/ui/image-preview" import { ImagePreview } from "@opencode-ai/ui/image-preview"
import { useQuery } from "@tanstack/solid-query"
import { loadAgentsQuery, loadProvidersQuery } from "@/context/global-sync/bootstrap"
interface PromptInputProps { interface PromptInputProps {
class?: string class?: string
@@ -100,6 +102,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
export const PromptInput: Component<PromptInputProps> = (props) => { export const PromptInput: Component<PromptInputProps> = (props) => {
const sdk = useSDK() const sdk = useSDK()
const sync = useSync() const sync = useSync()
const local = useLocal() const local = useLocal()
const files = useFile() const files = useFile()
@@ -212,9 +215,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!view().reviewPanel.opened()) view().reviewPanel.open() if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all") layout.fileTree.setTab("all")
const tab = files.tab(item.path) const tab = files.tab(item.path)
tabs().open(tab) void tabs().open(tab)
tabs().setActive(tab) tabs().setActive(tab)
Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus()) void Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
} }
const recent = createMemo(() => { const recent = createMemo(() => {
@@ -1139,7 +1142,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
} }
if (working()) { if (working()) {
abort() void abort()
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
return return
@@ -1205,7 +1208,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return return
} }
if (working()) { if (working()) {
abort() void abort()
event.preventDefault() event.preventDefault()
} }
return return
@@ -1245,10 +1248,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
) { ) {
return return
} }
handleSubmit(event) void handleSubmit(event)
} }
} }
const agentsQuery = useQuery(() => loadAgentsQuery(sdk.directory))
const agentsLoading = () => agentsQuery.isLoading
const globalProvidersQuery = useQuery(() => loadProvidersQuery(null))
const providersQuery = useQuery(() => loadProvidersQuery(sdk.directory))
const providersLoading = () => agentsLoading() || providersQuery.isLoading || globalProvidersQuery.isLoading
return ( return (
<div class="relative size-full _max-h-[320px] flex flex-col gap-0"> <div class="relative size-full _max-h-[320px] flex flex-col gap-0">
<PromptPopover <PromptPopover
@@ -1444,53 +1455,89 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span> <span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span>
<div class="size-4 shrink-0" /> <div class="size-4 shrink-0" />
</div> </div>
<div class="flex items-center gap-1.5 min-w-0 flex-1"> <div class="flex items-center gap-1.5 min-w-0 flex-1 h-7">
<div data-component="prompt-agent-control"> <Show when={!agentsLoading()}>
<TooltipKeybind <div data-component="prompt-agent-control">
placement="top" <TooltipKeybind
gutter={4} placement="top"
title={language.t("command.agent.cycle")} gutter={4}
keybind={command.keybind("agent.cycle")} title={language.t("command.agent.cycle")}
> keybind={command.keybind("agent.cycle")}
<Select >
size="normal" <Select
options={agentNames()} size="normal"
current={local.agent.current()?.name ?? ""} options={agentNames()}
onSelect={(value) => { current={local.agent.current()?.name ?? ""}
local.agent.set(value) onSelect={(value) => {
restoreFocus() local.agent.set(value)
}} restoreFocus()
class="capitalize max-w-[160px] text-text-base" }}
valueClass="truncate text-13-regular text-text-base" class="capitalize max-w-[160px] text-text-base"
triggerStyle={control()} valueClass="truncate text-13-regular text-text-base"
triggerProps={{ "data-action": "prompt-agent" }} triggerStyle={control()}
variant="ghost" triggerProps={{ "data-action": "prompt-agent" }}
/> variant="ghost"
</TooltipKeybind> />
</div> </TooltipKeybind>
<Show when={store.mode !== "shell"}> </div>
<div data-component="prompt-model-control"> </Show>
<Show <Show when={!providersLoading()}>
when={providers.paid().length > 0} <Show when={store.mode !== "shell"}>
fallback={ <div data-component="prompt-model-control">
<Show
when={providers.paid().length > 0}
fallback={
<TooltipKeybind
placement="top"
gutter={4}
title={language.t("command.model.choose")}
keybind={command.keybind("model.choose")}
>
<Button
data-action="prompt-model"
as="div"
variant="ghost"
size="normal"
class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
style={control()}
onClick={() => {
void import("@/components/dialog-select-model-unpaid").then((x) => {
dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
})
}}
>
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon
id={local.model.current()?.provider?.id ?? ""}
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
/>
</Show>
<span class="truncate">
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
</span>
<Icon name="chevron-down" size="small" class="shrink-0" />
</Button>
</TooltipKeybind>
}
>
<TooltipKeybind <TooltipKeybind
placement="top" placement="top"
gutter={4} gutter={4}
title={language.t("command.model.choose")} title={language.t("command.model.choose")}
keybind={command.keybind("model.choose")} keybind={command.keybind("model.choose")}
> >
<Button <ModelSelectorPopover
data-action="prompt-model" model={local.model}
as="div" triggerAs={Button}
variant="ghost" triggerProps={{
size="normal" variant: "ghost",
class="min-w-0 max-w-[320px] text-13-regular text-text-base group" size: "normal",
style={control()} style: control(),
onClick={() => { class: "min-w-0 max-w-[320px] text-13-regular text-text-base group",
void import("@/components/dialog-select-model-unpaid").then((x) => { "data-action": "prompt-model",
dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
})
}} }}
onClose={restoreFocus}
> >
<Show when={local.model.current()?.provider?.id}> <Show when={local.model.current()?.provider?.id}>
<ProviderIcon <ProviderIcon
@@ -1503,67 +1550,35 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
{local.model.current()?.name ?? language.t("dialog.model.select.title")} {local.model.current()?.name ?? language.t("dialog.model.select.title")}
</span> </span>
<Icon name="chevron-down" size="small" class="shrink-0" /> <Icon name="chevron-down" size="small" class="shrink-0" />
</Button> </ModelSelectorPopover>
</TooltipKeybind> </TooltipKeybind>
} </Show>
> </div>
<div data-component="prompt-variant-control">
<TooltipKeybind <TooltipKeybind
placement="top" placement="top"
gutter={4} gutter={4}
title={language.t("command.model.choose")} title={language.t("command.model.variant.cycle")}
keybind={command.keybind("model.choose")} keybind={command.keybind("model.variant.cycle")}
> >
<ModelSelectorPopover <Select
model={local.model} size="normal"
triggerAs={Button} options={variants()}
triggerProps={{ current={local.model.variant.current() ?? "default"}
variant: "ghost", label={(x) => (x === "default" ? language.t("common.default") : x)}
size: "normal", onSelect={(value) => {
style: control(), local.model.variant.set(value === "default" ? undefined : value)
class: "min-w-0 max-w-[320px] text-13-regular text-text-base group", restoreFocus()
"data-action": "prompt-model",
}} }}
onClose={restoreFocus} class="capitalize max-w-[160px] text-text-base"
> valueClass="truncate text-13-regular text-text-base"
<Show when={local.model.current()?.provider?.id}> triggerStyle={control()}
<ProviderIcon triggerProps={{ "data-action": "prompt-model-variant" }}
id={local.model.current()?.provider?.id ?? ""} variant="ghost"
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150" />
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
/>
</Show>
<span class="truncate">
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
</span>
<Icon name="chevron-down" size="small" class="shrink-0" />
</ModelSelectorPopover>
</TooltipKeybind> </TooltipKeybind>
</Show> </div>
</div> </Show>
<div data-component="prompt-variant-control">
<TooltipKeybind
placement="top"
gutter={4}
title={language.t("command.model.variant.cycle")}
keybind={command.keybind("model.variant.cycle")}
>
<Select
size="normal"
options={variants()}
current={local.model.variant.current() ?? "default"}
label={(x) => (x === "default" ? language.t("common.default") : x)}
onSelect={(value) => {
local.model.variant.set(value === "default" ? undefined : value)
restoreFocus()
}}
class="capitalize max-w-[160px] text-text-base"
valueClass="truncate text-13-regular text-text-base"
triggerStyle={control()}
triggerProps={{ "data-action": "prompt-model-variant" }}
variant="ghost"
/>
</TooltipKeybind>
</div>
</Show> </Show>
</div> </div>
</div> </div>
@@ -295,7 +295,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const mode = input.mode() const mode = input.mode()
if (text.trim().length === 0 && images.length === 0 && input.commentCount() === 0) { if (text.trim().length === 0 && images.length === 0 && input.commentCount() === 0) {
if (input.working()) abort() if (input.working()) void abort()
return return
} }
@@ -24,7 +24,7 @@ function openSessionContext(args: {
}) { }) {
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open() if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all") if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
args.tabs.open("context") void args.tabs.open("context")
args.tabs.setActive("context") args.tabs.setActive("context")
} }
@@ -8,7 +8,7 @@ import { Spinner } from "@opencode-ai/ui/spinner"
import { showToast } from "@opencode-ai/ui/toast" import { showToast } from "@opencode-ai/ui/toast"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { getFilename } from "@opencode-ai/shared/util/path" import { getFilename } from "@opencode-ai/shared/util/path"
import { createEffect, createMemo, For, Show } from "solid-js" import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web" import { Portal } from "solid-js/web"
import { useCommand } from "@/context/command" import { useCommand } from "@/context/command"
@@ -16,6 +16,7 @@ import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout" import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { useServer } from "@/context/server" import { useServer } from "@/context/server"
import { useSettings } from "@/context/settings"
import { useSync } from "@/context/sync" import { useSync } from "@/context/sync"
import { useTerminal } from "@/context/terminal" import { useTerminal } from "@/context/terminal"
import { focusTerminalById } from "@/pages/session/helpers" import { focusTerminalById } from "@/pages/session/helpers"
@@ -134,6 +135,7 @@ export function SessionHeader() {
const server = useServer() const server = useServer()
const platform = usePlatform() const platform = usePlatform()
const language = useLanguage() const language = useLanguage()
const settings = useSettings()
const sync = useSync() const sync = useSync()
const terminal = useTerminal() const terminal = useTerminal()
const { params, view } = useSessionLayout() const { params, view } = useSessionLayout()
@@ -151,6 +153,11 @@ export function SessionHeader() {
}) })
const hotkey = createMemo(() => command.keybind("file.open")) const hotkey = createMemo(() => command.keybind("file.open"))
const os = createMemo(() => detectOS(platform)) const os = createMemo(() => detectOS(platform))
const isDesktopBeta = platform.platform === "desktop" && import.meta.env.VITE_OPENCODE_CHANNEL === "beta"
const search = createMemo(() => !isDesktopBeta || settings.general.showSearch())
const tree = createMemo(() => !isDesktopBeta || settings.general.showFileTree())
const term = createMemo(() => !isDesktopBeta || settings.general.showTerminal())
const status = createMemo(() => !isDesktopBeta || settings.general.showStatus())
const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({ const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({
finder: true, finder: true,
@@ -262,12 +269,16 @@ export function SessionHeader() {
.catch((err: unknown) => showRequestError(language, err)) .catch((err: unknown) => showRequestError(language, err))
} }
const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center")) const [centerMount, setCenterMount] = createSignal<HTMLElement | null>(null)
const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right")) const [rightMount, setRightMount] = createSignal<HTMLElement | null>(null)
onMount(() => {
setCenterMount(document.getElementById("opencode-titlebar-center"))
setRightMount(document.getElementById("opencode-titlebar-right"))
})
return ( return (
<> <>
<Show when={centerMount()}> <Show when={search() && centerMount()}>
{(mount) => ( {(mount) => (
<Portal mount={mount()}> <Portal mount={mount()}>
<Button <Button
@@ -415,24 +426,28 @@ export function SessionHeader() {
</div> </div>
</Show> </Show>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<Tooltip placement="bottom" value={language.t("status.popover.trigger")}> <Show when={status()}>
<StatusPopover /> <Tooltip placement="bottom" value={language.t("status.popover.trigger")}>
</Tooltip> <StatusPopover />
<TooltipKeybind </Tooltip>
title={language.t("command.terminal.toggle")} </Show>
keybind={command.keybind("terminal.toggle")} <Show when={term()}>
> <TooltipKeybind
<Button title={language.t("command.terminal.toggle")}
variant="ghost" keybind={command.keybind("terminal.toggle")}
class="group/terminal-toggle titlebar-icon w-8 h-6 p-0 box-border shrink-0"
onClick={toggleTerminal}
aria-label={language.t("command.terminal.toggle")}
aria-expanded={view().terminal.opened()}
aria-controls="terminal-panel"
> >
<Icon size="small" name={view().terminal.opened() ? "terminal-active" : "terminal"} /> <Button
</Button> variant="ghost"
</TooltipKeybind> class="group/terminal-toggle titlebar-icon w-8 h-6 p-0 box-border shrink-0"
onClick={toggleTerminal}
aria-label={language.t("command.terminal.toggle")}
aria-expanded={view().terminal.opened()}
aria-controls="terminal-panel"
>
<Icon size="small" name={view().terminal.opened() ? "terminal-active" : "terminal"} />
</Button>
</TooltipKeybind>
</Show>
<div class="hidden md:flex items-center gap-1 shrink-0"> <div class="hidden md:flex items-center gap-1 shrink-0">
<TooltipKeybind <TooltipKeybind
@@ -451,30 +466,32 @@ export function SessionHeader() {
</Button> </Button>
</TooltipKeybind> </TooltipKeybind>
<TooltipKeybind <Show when={tree()}>
title={language.t("command.fileTree.toggle")} <TooltipKeybind
keybind={command.keybind("fileTree.toggle")} title={language.t("command.fileTree.toggle")}
> keybind={command.keybind("fileTree.toggle")}
<Button
variant="ghost"
class="titlebar-icon w-8 h-6 p-0 box-border"
onClick={() => layout.fileTree.toggle()}
aria-label={language.t("command.fileTree.toggle")}
aria-expanded={layout.fileTree.opened()}
aria-controls="file-tree-panel"
> >
<div class="relative flex items-center justify-center size-4"> <Button
<Icon variant="ghost"
size="small" class="titlebar-icon w-8 h-6 p-0 box-border"
name={layout.fileTree.opened() ? "file-tree-active" : "file-tree"} onClick={() => layout.fileTree.toggle()}
classList={{ aria-label={language.t("command.fileTree.toggle")}
"text-icon-strong": layout.fileTree.opened(), aria-expanded={layout.fileTree.opened()}
"text-icon-weak": !layout.fileTree.opened(), aria-controls="file-tree-panel"
}} >
/> <div class="relative flex items-center justify-center size-4">
</div> <Icon
</Button> size="small"
</TooltipKeybind> name={layout.fileTree.opened() ? "file-tree-active" : "file-tree"}
classList={{
"text-icon-strong": layout.fileTree.opened(),
"text-icon-weak": !layout.fileTree.opened(),
}}
/>
</div>
</Button>
</TooltipKeybind>
</Show>
</div> </div>
</div> </div>
</div> </div>
@@ -44,7 +44,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
const close = () => { const close = () => {
const count = terminal.all().length const count = terminal.all().length
terminal.close(props.terminal.id) void terminal.close(props.terminal.id)
if (count === 1) { if (count === 1) {
props.onClose?.() props.onClose?.()
} }
@@ -19,6 +19,9 @@ import {
sansDefault, sansDefault,
sansFontFamily, sansFontFamily,
sansInput, sansInput,
terminalDefault,
terminalFontFamily,
terminalInput,
useSettings, useSettings,
} from "@/context/settings" } from "@/context/settings"
import { decode64 } from "@/utils/base64" import { decode64 } from "@/utils/base64"
@@ -106,6 +109,7 @@ export const SettingsGeneral: Component = () => {
permission.disableAutoAccept(params.id, value) permission.disableAutoAccept(params.id, value)
} }
const desktop = createMemo(() => platform.platform === "desktop")
const check = () => { const check = () => {
if (!platform.checkUpdate) return if (!platform.checkUpdate) return
@@ -180,6 +184,7 @@ export const SettingsGeneral: Component = () => {
const soundOptions = [noneSound, ...SOUND_OPTIONS] const soundOptions = [noneSound, ...SOUND_OPTIONS]
const mono = () => monoInput(settings.appearance.font()) const mono = () => monoInput(settings.appearance.font())
const sans = () => sansInput(settings.appearance.uiFont()) const sans = () => sansInput(settings.appearance.uiFont())
const terminal = () => terminalInput(settings.appearance.terminalFont())
const soundSelectProps = ( const soundSelectProps = (
enabled: () => boolean, enabled: () => boolean,
@@ -279,6 +284,74 @@ export const SettingsGeneral: Component = () => {
</div> </div>
) )
const AdvancedSection = () => (
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.advanced")}</h3>
<SettingsList>
<SettingsRow
title={language.t("settings.general.row.showFileTree.title")}
description={language.t("settings.general.row.showFileTree.description")}
>
<div data-action="settings-show-file-tree">
<Switch
checked={settings.general.showFileTree()}
onChange={(checked) => settings.general.setShowFileTree(checked)}
/>
</div>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.showNavigation.title")}
description={language.t("settings.general.row.showNavigation.description")}
>
<div data-action="settings-show-navigation">
<Switch
checked={settings.general.showNavigation()}
onChange={(checked) => settings.general.setShowNavigation(checked)}
/>
</div>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.showSearch.title")}
description={language.t("settings.general.row.showSearch.description")}
>
<div data-action="settings-show-search">
<Switch
checked={settings.general.showSearch()}
onChange={(checked) => settings.general.setShowSearch(checked)}
/>
</div>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.showTerminal.title")}
description={language.t("settings.general.row.showTerminal.description")}
>
<div data-action="settings-show-terminal">
<Switch
checked={settings.general.showTerminal()}
onChange={(checked) => settings.general.setShowTerminal(checked)}
/>
</div>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.showStatus.title")}
description={language.t("settings.general.row.showStatus.description")}
>
<div data-action="settings-show-status">
<Switch
checked={settings.general.showStatus()}
onChange={(checked) => settings.general.setShowStatus(checked)}
/>
</div>
</SettingsRow>
</SettingsList>
</div>
)
const AppearanceSection = () => ( const AppearanceSection = () => (
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.appearance")}</h3> <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.appearance")}</h3>
@@ -382,6 +455,29 @@ export const SettingsGeneral: Component = () => {
/> />
</div> </div>
</SettingsRow> </SettingsRow>
<SettingsRow
title={language.t("settings.general.row.terminalFont.title")}
description={language.t("settings.general.row.terminalFont.description")}
>
<div class="w-full sm:w-[220px]">
<TextField
data-action="settings-terminal-font"
label={language.t("settings.general.row.terminalFont.title")}
hideLabel
type="text"
value={terminal()}
onChange={(value) => settings.appearance.setTerminalFont(value)}
placeholder={terminalDefault}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
class="text-12-regular"
style={{ "font-family": terminalFontFamily(settings.appearance.terminalFont()) }}
/>
</div>
</SettingsRow>
</SettingsList> </SettingsList>
</div> </div>
) )
@@ -527,6 +623,7 @@ export const SettingsGeneral: Component = () => {
</div> </div>
) )
console.log(import.meta.env)
return ( return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"> <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]"> <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
@@ -609,6 +706,10 @@ export const SettingsGeneral: Component = () => {
) )
}} }}
</Show> </Show>
<Show when={desktop() && import.meta.env.VITE_OPENCODE_CHANNEL === "beta"}>
<AdvancedSection />
</Show>
</div> </div>
</div> </div>
) )
+6 -6
View File
@@ -11,7 +11,7 @@ import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { useSDK } from "@/context/sdk" import { useSDK } from "@/context/sdk"
import { useServer } from "@/context/server" import { useServer } from "@/context/server"
import { monoFontFamily, useSettings } from "@/context/settings" import { terminalFontFamily, useSettings } from "@/context/settings"
import type { LocalPTY } from "@/context/terminal" import type { LocalPTY } from "@/context/terminal"
import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters" import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
import { terminalWriter } from "@/utils/terminal-writer" import { terminalWriter } from "@/utils/terminal-writer"
@@ -191,7 +191,7 @@ export const Terminal = (props: TerminalProps) => {
const scrollY = typeof local.pty.scrollY === "number" ? local.pty.scrollY : undefined const scrollY = typeof local.pty.scrollY === "number" ? local.pty.scrollY : undefined
let ws: WebSocket | undefined let ws: WebSocket | undefined
let term: Term | undefined let term: Term | undefined
let ghostty: Ghostty let _ghostty: Ghostty
let serializeAddon: SerializeAddon let serializeAddon: SerializeAddon
let fitAddon: FitAddon let fitAddon: FitAddon
let handleResize: () => void let handleResize: () => void
@@ -300,7 +300,7 @@ export const Terminal = (props: TerminalProps) => {
}) })
createEffect(() => { createEffect(() => {
const font = monoFontFamily(settings.appearance.font()) const font = terminalFontFamily(settings.appearance.terminalFont())
if (!term) return if (!term) return
setOptionIfSupported(term, "fontFamily", font) setOptionIfSupported(term, "fontFamily", font)
scheduleFit() scheduleFit()
@@ -360,7 +360,7 @@ export const Terminal = (props: TerminalProps) => {
cols: restoreSize?.cols, cols: restoreSize?.cols,
rows: restoreSize?.rows, rows: restoreSize?.rows,
fontSize: 14, fontSize: 14,
fontFamily: monoFontFamily(settings.appearance.font()), fontFamily: terminalFontFamily(settings.appearance.terminalFont()),
allowTransparency: false, allowTransparency: false,
convertEol: false, convertEol: false,
theme: terminalColors(), theme: terminalColors(),
@@ -372,7 +372,7 @@ export const Terminal = (props: TerminalProps) => {
cleanup() cleanup()
return return
} }
ghostty = g _ghostty = g
term = t term = t
output = terminalWriter((data, done) => output = terminalWriter((data, done) =>
t.write(data, () => { t.write(data, () => {
@@ -415,7 +415,7 @@ export const Terminal = (props: TerminalProps) => {
if (local.autoFocus !== false) focusTerminal() if (local.autoFocus !== false) focusTerminal()
if (typeof document !== "undefined" && document.fonts) { if (typeof document !== "undefined" && document.fonts) {
document.fonts.ready.then(scheduleFit) void document.fonts.ready.then(scheduleFit)
} }
const onResize = t.onResize((size) => { const onResize = t.onResize((size) => {
+42 -33
View File
@@ -11,6 +11,7 @@ import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform" import { usePlatform } from "@/context/platform"
import { useCommand } from "@/context/command" import { useCommand } from "@/context/command"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
import { applyPath, backPath, forwardPath } from "./titlebar-history" import { applyPath, backPath, forwardPath } from "./titlebar-history"
type TauriDesktopWindow = { type TauriDesktopWindow = {
@@ -40,6 +41,7 @@ export function Titlebar() {
const platform = usePlatform() const platform = usePlatform()
const command = useCommand() const command = useCommand()
const language = useLanguage() const language = useLanguage()
const settings = useSettings()
const theme = useTheme() const theme = useTheme()
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
@@ -78,6 +80,7 @@ export function Titlebar() {
const canBack = createMemo(() => history.index > 0) const canBack = createMemo(() => history.index > 0)
const canForward = createMemo(() => history.index < history.stack.length - 1) const canForward = createMemo(() => history.index < history.stack.length - 1)
const hasProjects = createMemo(() => layout.projects.list().length > 0) const hasProjects = createMemo(() => layout.projects.list().length > 0)
const nav = createMemo(() => import.meta.env.VITE_OPENCODE_CHANNEL !== "beta" || settings.general.showNavigation())
const back = () => { const back = () => {
const next = backPath(history) const next = backPath(history)
@@ -252,41 +255,47 @@ export function Titlebar() {
</div> </div>
</div> </div>
</Show> </Show>
<Show when={hasProjects()}> <div
<div class="flex items-center shrink-0"
class="flex items-center gap-0 transition-transform" classList={{
classList={{ "-translate-x-[36px]": layout.sidebar.opened() && !!params.dir,
"translate-x-0": !layout.sidebar.opened(), "duration-180 ease-out": !layout.sidebar.opened(),
"-translate-x-[36px]": layout.sidebar.opened(), "duration-180 ease-in": layout.sidebar.opened(),
"duration-180 ease-out": !layout.sidebar.opened(), }}
"duration-180 ease-in": layout.sidebar.opened(), >
}} <Show when={hasProjects() && nav()}>
> <div class="flex items-center gap-0 transition-transform">
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}> <Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
<Button <Button
variant="ghost" variant="ghost"
icon="chevron-left" icon="chevron-left"
class="titlebar-icon w-6 h-6 p-0 box-border" class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canBack()} disabled={!canBack()}
onClick={back} onClick={back}
aria-label={language.t("common.goBack")} aria-label={language.t("common.goBack")}
/> />
</Tooltip> </Tooltip>
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}> <Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
<Button <Button
variant="ghost" variant="ghost"
icon="chevron-right" icon="chevron-right"
class="titlebar-icon w-6 h-6 p-0 box-border" class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canForward()} disabled={!canForward()}
onClick={forward} onClick={forward}
aria-label={language.t("common.goForward")} aria-label={language.t("common.goForward")}
/> />
</Tooltip> </Tooltip>
</div> </div>
</Show> </Show>
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
{["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>
</div> </div>
</div> </div>
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
</div> </div>
<div class="min-w-0 flex items-center justify-center pointer-events-none"> <div class="min-w-0 flex items-center justify-center pointer-events-none">
+57 -43
View File
@@ -26,6 +26,7 @@ import type { ProjectMeta } from "./global-sync/types"
import { SESSION_RECENT_LIMIT } from "./global-sync/types" import { SESSION_RECENT_LIMIT } from "./global-sync/types"
import { sanitizeProject } from "./global-sync/utils" import { sanitizeProject } from "./global-sync/utils"
import { formatServerError } from "@/utils/server-errors" import { formatServerError } from "@/utils/server-errors"
import { queryOptions, skipToken, useQueryClient } from "@tanstack/solid-query"
type GlobalStore = { type GlobalStore = {
ready: boolean ready: boolean
@@ -41,6 +42,9 @@ type GlobalStore = {
reload: undefined | "pending" | "complete" reload: undefined | "pending" | "complete"
} }
export const loadSessionsQuery = (directory: string) =>
queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
function createGlobalSync() { function createGlobalSync() {
const globalSDK = useGlobalSDK() const globalSDK = useGlobalSDK()
const language = useLanguage() const language = useLanguage()
@@ -67,6 +71,7 @@ function createGlobalSync() {
config: {}, config: {},
reload: undefined, reload: undefined,
}) })
const queryClient = useQueryClient()
let active = true let active = true
let projectWritten = false let projectWritten = false
@@ -198,46 +203,53 @@ function createGlobalSync() {
} }
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT) const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
const promise = loadRootSessionsWithFallback({ const promise = queryClient
directory, .fetchQuery({
limit, ...loadSessionsQuery(directory),
list: (query) => globalSDK.client.session.list(query), queryFn: () =>
}) loadRootSessionsWithFallback({
.then((x) => { directory,
const nonArchived = (x.data ?? []) limit,
.filter((s) => !!s?.id) list: (query) => globalSDK.client.session.list(query),
.filter((s) => !s.time?.archived) })
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) .then((x) => {
const limit = store.limit const nonArchived = (x.data ?? [])
const childSessions = store.session.filter((s) => !!s.parentID) .filter((s) => !!s?.id)
const sessions = trimSessions([...nonArchived, ...childSessions], { .filter((s) => !s.time?.archived)
limit, .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
permission: store.permission, const limit = store.limit
}) const childSessions = store.session.filter((s) => !!s.parentID)
setStore( const sessions = trimSessions([...nonArchived, ...childSessions], {
"sessionTotal", limit,
estimateRootSessionTotal({ permission: store.permission,
count: nonArchived.length, })
limit: x.limit, setStore(
limited: x.limited, "sessionTotal",
}), estimateRootSessionTotal({
) count: nonArchived.length,
setStore("session", reconcile(sessions, { key: "id" })) limit: x.limit,
cleanupDroppedSessionCaches(store, setStore, sessions, setSessionTodo) limited: x.limited,
sessionMeta.set(directory, { limit }) }),
}) )
.catch((err) => { setStore("session", reconcile(sessions, { key: "id" }))
console.error("Failed to load sessions", err) cleanupDroppedSessionCaches(store, setStore, sessions, setSessionTodo)
const project = getFilename(directory) sessionMeta.set(directory, { limit })
showToast({ })
variant: "error", .catch((err) => {
title: language.t("toast.session.listFailed.title", { project }), console.error("Failed to load sessions", err)
description: formatServerError(err, language.t), const project = getFilename(directory)
}) showToast({
variant: "error",
title: language.t("toast.session.listFailed.title", { project }),
description: formatServerError(err, language.t),
})
})
.then(() => null),
}) })
.then(() => {})
sessionLoads.set(directory, promise) sessionLoads.set(directory, promise)
promise.finally(() => { void promise.finally(() => {
sessionLoads.delete(directory) sessionLoads.delete(directory)
children.unpin(directory) children.unpin(directory)
}) })
@@ -250,7 +262,7 @@ function createGlobalSync() {
if (pending) return pending if (pending) return pending
children.pin(directory) children.pin(directory)
const promise = (async () => { const promise = Promise.resolve().then(async () => {
const child = children.ensureChild(directory) const child = children.ensureChild(directory)
const cache = children.vcsCache.get(directory) const cache = children.vcsCache.get(directory)
if (!cache) return if (!cache) return
@@ -269,11 +281,12 @@ function createGlobalSync() {
vcsCache: cache, vcsCache: cache,
loadSessions, loadSessions,
translate: language.t, translate: language.t,
queryClient,
}) })
})() })
booting.set(directory, promise) booting.set(directory, promise)
promise.finally(() => { void promise.finally(() => {
booting.delete(directory) booting.delete(directory)
children.unpin(directory) children.unpin(directory)
}) })
@@ -317,7 +330,7 @@ function createGlobalSync() {
setSessionTodo, setSessionTodo,
vcsCache: children.vcsCache.get(directory), vcsCache: children.vcsCache.get(directory),
loadLsp: () => { loadLsp: () => {
sdkFor(directory) void sdkFor(directory)
.lsp.status() .lsp.status()
.then((x) => { .then((x) => {
setStore("lsp", x.data ?? []) setStore("lsp", x.data ?? [])
@@ -346,6 +359,7 @@ function createGlobalSync() {
translate: language.t, translate: language.t,
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }), formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
setGlobalStore: setBootStore, setGlobalStore: setBootStore,
queryClient,
}) })
bootedAt = Date.now() bootedAt = Date.now()
} finally { } finally {
@@ -359,13 +373,13 @@ function createGlobalSync() {
eventFrame = undefined eventFrame = undefined
eventTimer = setTimeout(() => { eventTimer = setTimeout(() => {
eventTimer = undefined eventTimer = undefined
globalSDK.event.start() void globalSDK.event.start()
}, 0) }, 0)
}) })
} else { } else {
eventTimer = setTimeout(() => { eventTimer = setTimeout(() => {
eventTimer = undefined eventTimer = undefined
globalSDK.event.start() void globalSDK.event.start()
}, 0) }, 0)
} }
void bootstrap() void bootstrap()
+148 -121
View File
@@ -18,6 +18,8 @@ import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
import type { State, VcsCache } from "./types" import type { State, VcsCache } from "./types"
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils" import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
import { formatServerError } from "@/utils/server-errors" import { formatServerError } from "@/utils/server-errors"
import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
import { loadSessionsQuery } from "../global-sync"
type GlobalStore = { type GlobalStore = {
ready: boolean ready: boolean
@@ -71,6 +73,7 @@ export async function bootstrapGlobal(input: {
translate: (key: string, vars?: Record<string, string | number>) => string translate: (key: string, vars?: Record<string, string | number>) => string
formatMoreCount: (count: number) => string formatMoreCount: (count: number) => string
setGlobalStore: SetStoreFunction<GlobalStore> setGlobalStore: SetStoreFunction<GlobalStore>
queryClient: QueryClient
}) { }) {
const fast = [ const fast = [
() => () =>
@@ -80,11 +83,16 @@ export async function bootstrapGlobal(input: {
}), }),
), ),
() => () =>
retry(() => input.queryClient.fetchQuery({
input.globalSDK.provider.list().then((x) => { ...loadProvidersQuery(null),
input.setGlobalStore("provider", normalizeProviderList(x.data!)) queryFn: () =>
}), retry(() =>
), input.globalSDK.provider.list().then((x) => {
input.setGlobalStore("provider", normalizeProviderList(x.data!))
return null
}),
),
}),
] ]
const slow = [ const slow = [
@@ -172,6 +180,12 @@ function warmSessions(input: {
).then(() => undefined) ).then(() => undefined)
} }
export const loadProvidersQuery = (directory: string | null) =>
queryOptions<null>({ queryKey: [directory, "providers"], queryFn: skipToken })
export const loadAgentsQuery = (directory: string | null) =>
queryOptions<null>({ queryKey: [directory, "agents"], queryFn: skipToken })
export async function bootstrapDirectory(input: { export async function bootstrapDirectory(input: {
directory: string directory: string
sdk: OpencodeClient sdk: OpencodeClient
@@ -186,6 +200,7 @@ export async function bootstrapDirectory(input: {
project: Project[] project: Project[]
provider: ProviderListResponse provider: ProviderListResponse
} }
queryClient: QueryClient
}) { }) {
const loading = input.store.status !== "complete" const loading = input.store.status !== "complete"
const seededProject = projectID(input.directory, input.global.project) const seededProject = projectID(input.directory, input.global.project)
@@ -207,97 +222,7 @@ export async function bootstrapDirectory(input: {
input.setStore("lsp", []) input.setStore("lsp", [])
if (loading) input.setStore("status", "partial") if (loading) input.setStore("status", "partial")
const fast = [ const fast = [() => Promise.resolve(input.loadSessions(input.directory))]
() => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))),
() => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
]
const slow = [
() =>
seededProject
? Promise.resolve()
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
() =>
seededPath
? Promise.resolve()
: retry(() =>
input.sdk.path.get().then((x) => {
input.setStore("path", x.data!)
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
if (next) input.setStore("project", next)
}),
),
() =>
retry(() =>
input.sdk.vcs.get().then((x) => {
const next = x.data ?? input.store.vcs
input.setStore("vcs", next)
if (next) input.vcsCache.setStore("value", next)
}),
),
() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
() =>
retry(() =>
input.sdk.permission.list().then((x) => {
const ids = (x.data ?? []).map((perm) => perm?.sessionID).filter((id): id is string => !!id)
const grouped = groupBySession(
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
)
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
batch(() => {
for (const sessionID of Object.keys(input.store.permission)) {
if (grouped[sessionID]) continue
input.setStore("permission", sessionID, [])
}
for (const [sessionID, permissions] of Object.entries(grouped)) {
input.setStore(
"permission",
sessionID,
reconcile(
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
{ key: "id" },
),
)
}
}),
)
}),
),
() =>
retry(() =>
input.sdk.question.list().then((x) => {
const ids = (x.data ?? []).map((question) => question?.sessionID).filter((id): id is string => !!id)
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
batch(() => {
for (const sessionID of Object.keys(input.store.question)) {
if (grouped[sessionID]) continue
input.setStore("question", sessionID, [])
}
for (const [sessionID, questions] of Object.entries(grouped)) {
input.setStore(
"question",
sessionID,
reconcile(
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
{ key: "id" },
),
)
}
}),
)
}),
),
() => Promise.resolve(input.loadSessions(input.directory)),
() =>
retry(() =>
input.sdk.mcp.status().then((x) => {
input.setStore("mcp", x.data!)
input.setStore("mcp_ready", true)
}),
),
]
const errs = errors(await runAll(fast)) const errs = errors(await runAll(fast))
if (errs.length > 0) { if (errs.length > 0) {
@@ -310,36 +235,138 @@ export async function bootstrapDirectory(input: {
}) })
} }
await waitForPaint() ;(async () => {
const slowErrs = errors(await runAll(slow)) const slow = [
if (slowErrs.length > 0) { () =>
console.error("Failed to finish bootstrap instance", slowErrs[0]) input.queryClient.ensureQueryData({
const project = getFilename(input.directory) ...loadAgentsQuery(input.directory),
showToast({ queryFn: () =>
variant: "error", retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))).then(
title: input.translate("toast.project.reloadFailed.title", { project }), () => null,
description: formatServerError(slowErrs[0], input.translate), ),
}) }),
} () => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
() =>
seededProject
? Promise.resolve()
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
() =>
seededPath
? Promise.resolve()
: retry(() =>
input.sdk.path.get().then((x) => {
input.setStore("path", x.data!)
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
if (next) input.setStore("project", next)
}),
),
() =>
retry(() =>
input.sdk.vcs.get().then((x) => {
const next = x.data ?? input.store.vcs
input.setStore("vcs", next)
if (next) input.vcsCache.setStore("value", next)
}),
),
() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
() =>
retry(() =>
input.sdk.permission.list().then((x) => {
const ids = (x.data ?? []).map((perm) => perm?.sessionID).filter((id): id is string => !!id)
const grouped = groupBySession(
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
)
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
batch(() => {
for (const sessionID of Object.keys(input.store.permission)) {
if (grouped[sessionID]) continue
input.setStore("permission", sessionID, [])
}
for (const [sessionID, permissions] of Object.entries(grouped)) {
input.setStore(
"permission",
sessionID,
reconcile(
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
{ key: "id" },
),
)
}
}),
)
}),
),
() =>
retry(() =>
input.sdk.question.list().then((x) => {
const ids = (x.data ?? []).map((question) => question?.sessionID).filter((id): id is string => !!id)
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
batch(() => {
for (const sessionID of Object.keys(input.store.question)) {
if (grouped[sessionID]) continue
input.setStore("question", sessionID, [])
}
for (const [sessionID, questions] of Object.entries(grouped)) {
input.setStore(
"question",
sessionID,
reconcile(
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
{ key: "id" },
),
)
}
}),
)
}),
),
() => Promise.resolve(input.loadSessions(input.directory)),
() =>
retry(() =>
input.sdk.mcp.status().then((x) => {
input.setStore("mcp", x.data!)
input.setStore("mcp_ready", true)
}),
),
]
if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete") await waitForPaint()
const slowErrs = errors(await runAll(slow))
const rev = (providerRev.get(input.directory) ?? 0) + 1 if (slowErrs.length > 0) {
providerRev.set(input.directory, rev) console.error("Failed to finish bootstrap instance", slowErrs[0])
void retry(() => input.sdk.provider.list())
.then((x) => {
if (providerRev.get(input.directory) !== rev) return
input.setStore("provider", normalizeProviderList(x.data!))
input.setStore("provider_ready", true)
})
.catch((err) => {
if (providerRev.get(input.directory) !== rev) return
console.error("Failed to refresh provider list", err)
const project = getFilename(input.directory) const project = getFilename(input.directory)
showToast({ showToast({
variant: "error", variant: "error",
title: input.translate("toast.project.reloadFailed.title", { project }), title: input.translate("toast.project.reloadFailed.title", { project }),
description: formatServerError(err, input.translate), description: formatServerError(slowErrs[0], input.translate),
}) })
}
if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
const rev = (providerRev.get(input.directory) ?? 0) + 1
providerRev.set(input.directory, rev)
void input.queryClient.ensureQueryData({
...loadSessionsQuery(input.directory),
queryFn: () =>
retry(() => input.sdk.provider.list())
.then((x) => {
if (providerRev.get(input.directory) !== rev) return
input.setStore("provider", normalizeProviderList(x.data!))
input.setStore("provider_ready", true)
})
.catch((err) => {
if (providerRev.get(input.directory) !== rev) console.error("Failed to refresh provider list", err)
const project = getFilename(input.directory)
showToast({
variant: "error",
title: input.translate("toast.project.reloadFailed.title", { project }),
description: formatServerError(err, input.translate),
})
})
.then(() => null),
}) })
})()
} }
+1 -1
View File
@@ -582,7 +582,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
open(directory: string) { open(directory: string) {
const root = rootFor(directory) const root = rootFor(directory)
if (server.projects.list().find((x) => x.worktree === root)) return if (server.projects.list().find((x) => x.worktree === root)) return
globalSync.project.loadSessions(root) void globalSync.project.loadSessions(root)
server.projects.open(root) server.projects.open(root)
}, },
close(directory: string) { close(directory: string) {
+48
View File
@@ -23,6 +23,11 @@ export interface Settings {
autoSave: boolean autoSave: boolean
releaseNotes: boolean releaseNotes: boolean
followup: "queue" | "steer" followup: "queue" | "steer"
showFileTree: boolean
showNavigation: boolean
showSearch: boolean
showStatus: boolean
showTerminal: boolean
showReasoningSummaries: boolean showReasoningSummaries: boolean
shellToolPartsExpanded: boolean shellToolPartsExpanded: boolean
editToolPartsExpanded: boolean editToolPartsExpanded: boolean
@@ -34,6 +39,7 @@ export interface Settings {
fontSize: number fontSize: number
mono: string mono: string
sans: string sans: string
terminal: string
} }
keybinds: Record<string, string> keybinds: Record<string, string>
permissions: { permissions: {
@@ -45,13 +51,17 @@ export interface Settings {
export const monoDefault = "System Mono" export const monoDefault = "System Mono"
export const sansDefault = "System Sans" export const sansDefault = "System Sans"
export const terminalDefault = "JetBrainsMono Nerd Font Mono"
const monoFallback = const monoFallback =
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
const sansFallback = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' const sansFallback = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
const terminalFallback =
'"JetBrainsMono Nerd Font Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
const monoBase = monoFallback const monoBase = monoFallback
const sansBase = sansFallback const sansBase = sansFallback
const terminalBase = terminalFallback
function input(font: string | undefined) { function input(font: string | undefined) {
return font ?? "" return font ?? ""
@@ -84,11 +94,24 @@ export function sansFontFamily(font: string | undefined) {
return stack(font, sansBase) return stack(font, sansBase)
} }
export function terminalInput(font: string | undefined) {
return input(font)
}
export function terminalFontFamily(font: string | undefined) {
return stack(font, terminalBase)
}
const defaultSettings: Settings = { const defaultSettings: Settings = {
general: { general: {
autoSave: true, autoSave: true,
releaseNotes: true, releaseNotes: true,
followup: "steer", followup: "steer",
showFileTree: false,
showNavigation: false,
showSearch: false,
showStatus: false,
showTerminal: false,
showReasoningSummaries: false, showReasoningSummaries: false,
shellToolPartsExpanded: false, shellToolPartsExpanded: false,
editToolPartsExpanded: false, editToolPartsExpanded: false,
@@ -100,6 +123,7 @@ const defaultSettings: Settings = {
fontSize: 14, fontSize: 14,
mono: "", mono: "",
sans: "", sans: "",
terminal: "",
}, },
keybinds: {}, keybinds: {},
permissions: { permissions: {
@@ -162,6 +186,26 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setFollowup(value: "queue" | "steer") { setFollowup(value: "queue" | "steer") {
setStore("general", "followup", value === "queue" ? "steer" : value) setStore("general", "followup", value === "queue" ? "steer" : value)
}, },
showFileTree: withFallback(() => store.general?.showFileTree, defaultSettings.general.showFileTree),
setShowFileTree(value: boolean) {
setStore("general", "showFileTree", value)
},
showNavigation: withFallback(() => store.general?.showNavigation, defaultSettings.general.showNavigation),
setShowNavigation(value: boolean) {
setStore("general", "showNavigation", value)
},
showSearch: withFallback(() => store.general?.showSearch, defaultSettings.general.showSearch),
setShowSearch(value: boolean) {
setStore("general", "showSearch", value)
},
showStatus: withFallback(() => store.general?.showStatus, defaultSettings.general.showStatus),
setShowStatus(value: boolean) {
setStore("general", "showStatus", value)
},
showTerminal: withFallback(() => store.general?.showTerminal, defaultSettings.general.showTerminal),
setShowTerminal(value: boolean) {
setStore("general", "showTerminal", value)
},
showReasoningSummaries: withFallback( showReasoningSummaries: withFallback(
() => store.general?.showReasoningSummaries, () => store.general?.showReasoningSummaries,
defaultSettings.general.showReasoningSummaries, defaultSettings.general.showReasoningSummaries,
@@ -203,6 +247,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setUIFont(value: string) { setUIFont(value: string) {
setStore("appearance", "sans", value.trim() ? value : "") setStore("appearance", "sans", value.trim() ? value : "")
}, },
terminalFont: withFallback(() => store.appearance?.terminal, defaultSettings.appearance.terminal),
setTerminalFont(value: string) {
setStore("appearance", "terminal", value.trim() ? value : "")
},
}, },
keybinds: { keybinds: {
get: (action: string) => store.keybinds?.[action], get: (action: string) => store.keybinds?.[action],
+2 -2
View File
@@ -117,7 +117,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
entry?.value.clear() entry?.value.clear()
} }
removePersisted(Persist.workspace(dir, "terminal"), platform) void removePersisted(Persist.workspace(dir, "terminal"), platform)
const legacy = new Set(getLegacyTerminalStorageKeys(dir)) const legacy = new Set(getLegacyTerminalStorageKeys(dir))
for (const id of sessionIDs ?? []) { for (const id of sessionIDs ?? []) {
@@ -126,7 +126,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
} }
} }
for (const key of legacy) { for (const key of legacy) {
removePersisted({ key }, platform) void removePersisted({ key }, platform)
} }
} }
+2 -3
View File
@@ -1,15 +1,14 @@
import "solid-js"
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_OPENCODE_SERVER_HOST: string readonly VITE_OPENCODE_SERVER_HOST: string
readonly VITE_OPENCODE_SERVER_PORT: string readonly VITE_OPENCODE_SERVER_PORT: string
readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod"
} }
interface ImportMeta { interface ImportMeta {
readonly env: ImportMetaEnv readonly env: ImportMetaEnv
} }
declare module "solid-js" { export declare module "solid-js" {
namespace JSX { namespace JSX {
interface Directives { interface Directives {
sortable: true sortable: true
+3 -1
View File
@@ -565,7 +565,9 @@ export const dict = {
"settings.general.row.theme.title": "السمة", "settings.general.row.theme.title": "السمة",
"settings.general.row.theme.description": "تخصيص سمة OpenCode.", "settings.general.row.theme.description": "تخصيص سمة OpenCode.",
"settings.general.row.font.title": "خط الكود", "settings.general.row.font.title": "خط الكود",
"settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية والطرفيات", "settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "خط الواجهة", "settings.general.row.uiFont.title": "خط الواجهة",
"settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها", "settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
"settings.general.row.followup.title": "سلوك المتابعة", "settings.general.row.followup.title": "سلوك المتابعة",
+3 -1
View File
@@ -572,7 +572,9 @@ export const dict = {
"settings.general.row.theme.title": "Tema", "settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.", "settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
"settings.general.row.font.title": "Fonte de código", "settings.general.row.font.title": "Fonte de código",
"settings.general.row.font.description": "Personalize a fonte usada em blocos de código e terminais", "settings.general.row.font.description": "Personalize a fonte usada em blocos de código",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "Fonte da interface", "settings.general.row.uiFont.title": "Fonte da interface",
"settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface", "settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
"settings.general.row.followup.title": "Comportamento de acompanhamento", "settings.general.row.followup.title": "Comportamento de acompanhamento",
+3 -1
View File
@@ -637,7 +637,9 @@ export const dict = {
"settings.general.row.theme.title": "Tema", "settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Prilagodi temu OpenCode-a.", "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
"settings.general.row.font.title": "Font za kod", "settings.general.row.font.title": "Font za kod",
"settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda i terminalima", "settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "UI font", "settings.general.row.uiFont.title": "UI font",
"settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu", "settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu",
"settings.general.row.followup.title": "Ponašanje nadovezivanja", "settings.general.row.followup.title": "Ponašanje nadovezivanja",
+3 -1
View File
@@ -632,7 +632,9 @@ export const dict = {
"settings.general.row.theme.title": "Tema", "settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.", "settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
"settings.general.row.font.title": "Kode-skrifttype", "settings.general.row.font.title": "Kode-skrifttype",
"settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke og terminaler", "settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "UI-skrifttype", "settings.general.row.uiFont.title": "UI-skrifttype",
"settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen", "settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
"settings.general.row.followup.title": "Opfølgningsadfærd", "settings.general.row.followup.title": "Opfølgningsadfærd",
+3 -1
View File
@@ -582,7 +582,9 @@ export const dict = {
"settings.general.row.theme.title": "Thema", "settings.general.row.theme.title": "Thema",
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.", "settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
"settings.general.row.font.title": "Code-Schriftart", "settings.general.row.font.title": "Code-Schriftart",
"settings.general.row.font.description": "Die in Codeblöcken und Terminals verwendete Schriftart anpassen", "settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "UI-Schriftart", "settings.general.row.uiFont.title": "UI-Schriftart",
"settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen", "settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
"settings.general.row.followup.title": "Verhalten bei Folgefragen", "settings.general.row.followup.title": "Verhalten bei Folgefragen",
+14 -1
View File
@@ -719,6 +719,7 @@ export const dict = {
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.", "settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
"settings.general.section.appearance": "Appearance", "settings.general.section.appearance": "Appearance",
"settings.general.section.advanced": "Advanced",
"settings.general.section.notifications": "System notifications", "settings.general.section.notifications": "System notifications",
"settings.general.section.updates": "Updates", "settings.general.section.updates": "Updates",
"settings.general.section.sounds": "Sound effects", "settings.general.section.sounds": "Sound effects",
@@ -734,13 +735,25 @@ export const dict = {
"settings.general.row.theme.title": "Theme", "settings.general.row.theme.title": "Theme",
"settings.general.row.theme.description": "Customise how OpenCode is themed.", "settings.general.row.theme.description": "Customise how OpenCode is themed.",
"settings.general.row.font.title": "Code Font", "settings.general.row.font.title": "Code Font",
"settings.general.row.font.description": "Customise the font used in code blocks and terminals", "settings.general.row.font.description": "Customise the font used in code blocks",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "UI Font", "settings.general.row.uiFont.title": "UI Font",
"settings.general.row.uiFont.description": "Customise the font used throughout the interface", "settings.general.row.uiFont.description": "Customise the font used throughout the interface",
"settings.general.row.followup.title": "Follow-up behavior", "settings.general.row.followup.title": "Follow-up behavior",
"settings.general.row.followup.description": "Choose whether follow-up prompts steer immediately or wait in a queue", "settings.general.row.followup.description": "Choose whether follow-up prompts steer immediately or wait in a queue",
"settings.general.row.followup.option.queue": "Queue", "settings.general.row.followup.option.queue": "Queue",
"settings.general.row.followup.option.steer": "Steer", "settings.general.row.followup.option.steer": "Steer",
"settings.general.row.showFileTree.title": "File tree",
"settings.general.row.showFileTree.description": "Show the file tree toggle and panel in desktop sessions",
"settings.general.row.showNavigation.title": "Navigation controls",
"settings.general.row.showNavigation.description": "Show the back and forward buttons in the desktop title bar",
"settings.general.row.showSearch.title": "Command palette",
"settings.general.row.showSearch.description": "Show the search and command palette button in the desktop title bar",
"settings.general.row.showTerminal.title": "Terminal",
"settings.general.row.showTerminal.description": "Show the terminal button in the desktop title bar",
"settings.general.row.showStatus.title": "Server status",
"settings.general.row.showStatus.description": "Show the server status button in the desktop title bar",
"settings.general.row.reasoningSummaries.title": "Show reasoning summaries", "settings.general.row.reasoningSummaries.title": "Show reasoning summaries",
"settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline", "settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline",
"settings.general.row.shellToolPartsExpanded.title": "Expand shell tool parts", "settings.general.row.shellToolPartsExpanded.title": "Expand shell tool parts",
+3 -1
View File
@@ -640,7 +640,9 @@ export const dict = {
"settings.general.row.theme.title": "Tema", "settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.", "settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
"settings.general.row.font.title": "Fuente de código", "settings.general.row.font.title": "Fuente de código",
"settings.general.row.font.description": "Personaliza la fuente usada en bloques de código y terminales", "settings.general.row.font.description": "Personaliza la fuente usada en bloques de código",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "Fuente de la interfaz", "settings.general.row.uiFont.title": "Fuente de la interfaz",
"settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz", "settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz",
"settings.general.row.followup.title": "Comportamiento de seguimiento", "settings.general.row.followup.title": "Comportamiento de seguimiento",
+3 -1
View File
@@ -579,7 +579,9 @@ export const dict = {
"settings.general.row.theme.title": "Thème", "settings.general.row.theme.title": "Thème",
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.", "settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
"settings.general.row.font.title": "Police de code", "settings.general.row.font.title": "Police de code",
"settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code et les terminaux", "settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "Police de l'interface", "settings.general.row.uiFont.title": "Police de l'interface",
"settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface", "settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface",
"settings.general.row.followup.title": "Comportement de suivi", "settings.general.row.followup.title": "Comportement de suivi",
+3 -1
View File
@@ -569,7 +569,9 @@ export const dict = {
"settings.general.row.theme.title": "テーマ", "settings.general.row.theme.title": "テーマ",
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。", "settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
"settings.general.row.font.title": "コードフォント", "settings.general.row.font.title": "コードフォント",
"settings.general.row.font.description": "コードブロックとターミナルで使用するフォントをカスタマイズします", "settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "UIフォント", "settings.general.row.uiFont.title": "UIフォント",
"settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします", "settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
"settings.general.row.followup.title": "フォローアップの動作", "settings.general.row.followup.title": "フォローアップの動作",
+3 -3
View File
@@ -1,5 +1,3 @@
import { dict as en } from "./en"
export const dict = { export const dict = {
"command.category.suggested": "추천", "command.category.suggested": "추천",
"command.category.view": "보기", "command.category.view": "보기",
@@ -568,7 +566,9 @@ export const dict = {
"settings.general.row.theme.title": "테마", "settings.general.row.theme.title": "테마",
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정", "settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
"settings.general.row.font.title": "코드 글꼴", "settings.general.row.font.title": "코드 글꼴",
"settings.general.row.font.description": "코드 블록과 터미널에 사용되는 글꼴을 사용자 지정", "settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "UI 글꼴", "settings.general.row.uiFont.title": "UI 글꼴",
"settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정", "settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
"settings.general.row.followup.title": "후속 조치 동작", "settings.general.row.followup.title": "후속 조치 동작",
+3 -1
View File
@@ -640,7 +640,9 @@ export const dict = {
"settings.general.row.theme.title": "Tema", "settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.", "settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
"settings.general.row.font.title": "Kodefont", "settings.general.row.font.title": "Kodefont",
"settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker og terminaler", "settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "UI-skrift", "settings.general.row.uiFont.title": "UI-skrift",
"settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet", "settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
"settings.general.row.followup.title": "Oppfølgingsadferd", "settings.general.row.followup.title": "Oppfølgingsadferd",
+3 -1
View File
@@ -571,7 +571,9 @@ export const dict = {
"settings.general.row.theme.title": "Motyw", "settings.general.row.theme.title": "Motyw",
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.", "settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
"settings.general.row.font.title": "Czcionka kodu", "settings.general.row.font.title": "Czcionka kodu",
"settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu i terminalach", "settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "Czcionka interfejsu", "settings.general.row.uiFont.title": "Czcionka interfejsu",
"settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie", "settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
"settings.general.row.followup.title": "Zachowanie kontynuacji", "settings.general.row.followup.title": "Zachowanie kontynuacji",
+3 -1
View File
@@ -637,7 +637,9 @@ export const dict = {
"settings.general.row.theme.title": "Тема", "settings.general.row.theme.title": "Тема",
"settings.general.row.theme.description": "Настройте оформление OpenCode.", "settings.general.row.theme.description": "Настройте оформление OpenCode.",
"settings.general.row.font.title": "Шрифт кода", "settings.general.row.font.title": "Шрифт кода",
"settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода и терминалах", "settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "Шрифт интерфейса", "settings.general.row.uiFont.title": "Шрифт интерфейса",
"settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе", "settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
"settings.general.row.followup.title": "Поведение уточняющих вопросов", "settings.general.row.followup.title": "Поведение уточняющих вопросов",
+3 -1
View File
@@ -631,7 +631,9 @@ export const dict = {
"settings.general.row.theme.title": "ธีม", "settings.general.row.theme.title": "ธีม",
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม", "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
"settings.general.row.font.title": "ฟอนต์โค้ด", "settings.general.row.font.title": "ฟอนต์โค้ด",
"settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ดและเทอร์มินัล", "settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "ฟอนต์ UI", "settings.general.row.uiFont.title": "ฟอนต์ UI",
"settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ", "settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล", "settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
+3 -1
View File
@@ -644,7 +644,9 @@ export const dict = {
"settings.general.row.theme.title": "Tema", "settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.", "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
"settings.general.row.font.title": "Kod Yazı Tipi", "settings.general.row.font.title": "Kod Yazı Tipi",
"settings.general.row.font.description": "Kod bloklarında ve terminallerde kullanılan yazı tipini özelleştirin", "settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "Arayüz Yazı Tipi", "settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
"settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin", "settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
"settings.general.row.followup.title": "Takip davranışı", "settings.general.row.followup.title": "Takip davranışı",
+3 -1
View File
@@ -631,7 +631,9 @@ export const dict = {
"settings.general.row.theme.title": "主题", "settings.general.row.theme.title": "主题",
"settings.general.row.theme.description": "自定义 OpenCode 的主题。", "settings.general.row.theme.description": "自定义 OpenCode 的主题。",
"settings.general.row.font.title": "代码字体", "settings.general.row.font.title": "代码字体",
"settings.general.row.font.description": "自定义代码块和终端使用的字体", "settings.general.row.font.description": "自定义代码块使用的字体",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "界面字体", "settings.general.row.uiFont.title": "界面字体",
"settings.general.row.uiFont.description": "自定义整个界面使用的字体", "settings.general.row.uiFont.description": "自定义整个界面使用的字体",
"settings.general.row.followup.title": "跟进消息行为", "settings.general.row.followup.title": "跟进消息行为",
+3 -1
View File
@@ -626,7 +626,9 @@ export const dict = {
"settings.general.row.theme.title": "主題", "settings.general.row.theme.title": "主題",
"settings.general.row.theme.description": "自訂 OpenCode 的主題。", "settings.general.row.theme.description": "自訂 OpenCode 的主題。",
"settings.general.row.font.title": "程式碼字型", "settings.general.row.font.title": "程式碼字型",
"settings.general.row.font.description": "自訂程式碼區塊和終端機使用的字型", "settings.general.row.font.description": "自訂程式碼區塊使用的字型",
"settings.general.row.terminalFont.title": "Terminal Font",
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
"settings.general.row.uiFont.title": "介面字型", "settings.general.row.uiFont.title": "介面字型",
"settings.general.row.uiFont.description": "自訂整個介面使用的字型", "settings.general.row.uiFont.description": "自訂整個介面使用的字型",
"settings.general.row.followup.title": "後續追問行為", "settings.general.row.followup.title": "後續追問行為",
+7
View File
@@ -1,5 +1,12 @@
@import "@opencode-ai/ui/styles/tailwind"; @import "@opencode-ai/ui/styles/tailwind";
@font-face {
font-family: "JetBrainsMono Nerd Font Mono";
src: url("/assets/JetBrainsMonoNerdFontMono-Regular.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
@layer components { @layer components {
@keyframes session-progress-whip { @keyframes session-progress-whip {
0% { 0% {
+190 -184
View File
@@ -13,7 +13,7 @@ import {
type Accessor, type Accessor,
} from "solid-js" } from "solid-js"
import { makeEventListener } from "@solid-primitives/event-listener" import { makeEventListener } from "@solid-primitives/event-listener"
import { useNavigate, useParams } from "@solidjs/router" import { useLocation, useNavigate, useParams } from "@solidjs/router"
import { useLayout, LocalProject } from "@/context/layout" import { useLayout, LocalProject } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync" import { useGlobalSync } from "@/context/global-sync"
import { Persist, persisted } from "@/utils/persist" import { Persist, persisted } from "@/utils/persist"
@@ -127,14 +127,17 @@ export default function Layout(props: ParentProps) {
const theme = useTheme() const theme = useTheme()
const language = useLanguage() const language = useLanguage()
const initialDirectory = decode64(params.dir) const initialDirectory = decode64(params.dir)
const location = useLocation()
const route = createMemo(() => { const route = createMemo(() => {
const slug = params.dir const slug = params.dir
if (!slug) return { slug, dir: "" } if (!slug) return { slug, dir: "" }
const dir = decode64(slug) const dir = decode64(slug)
if (!dir) return { slug, dir: "" } if (!dir) return { slug, dir: "" }
const store = globalSync.peek(dir, { bootstrap: false })
return { return {
slug, slug,
dir: globalSync.peek(dir, { bootstrap: false })[0].path.directory || dir, store,
dir: store[0].path.directory || dir,
} }
}) })
const availableThemeEntries = createMemo(() => theme.ids().map((id) => [id, theme.themes()[id]] as const)) const availableThemeEntries = createMemo(() => theme.ids().map((id) => [id, theme.themes()[id]] as const))
@@ -956,7 +959,7 @@ export default function Layout(props: ParentProps) {
// warm up child store to prevent flicker // warm up child store to prevent flicker
globalSync.child(target.worktree) globalSync.child(target.worktree)
openProject(target.worktree) void openProject(target.worktree)
} }
function navigateSessionByUnseen(offset: number) { function navigateSessionByUnseen(offset: number) {
@@ -1094,7 +1097,7 @@ export default function Layout(props: ParentProps) {
disabled: !params.dir || !params.id, disabled: !params.dir || !params.id,
onSelect: () => { onSelect: () => {
const session = currentSessions().find((s) => s.id === params.id) const session = currentSessions().find((s) => s.id === params.id)
if (session) archiveSession(session) if (session) void archiveSession(session)
}, },
}, },
{ {
@@ -1360,11 +1363,11 @@ export default function Layout(props: ParentProps) {
if (!server.isLocal()) return if (!server.isLocal()) return
for (const directory of collectOpenProjectDeepLinks(urls)) { for (const directory of collectOpenProjectDeepLinks(urls)) {
openProject(directory) void openProject(directory)
} }
for (const link of collectNewSessionDeepLinks(urls)) { for (const link of collectNewSessionDeepLinks(urls)) {
openProject(link.directory, false) void openProject(link.directory, false)
const slug = base64Encode(link.directory) const slug = base64Encode(link.directory)
if (link.prompt) { if (link.prompt) {
setSessionHandoff(slug, { prompt: link.prompt }) setSessionHandoff(slug, { prompt: link.prompt })
@@ -1453,11 +1456,11 @@ export default function Layout(props: ParentProps) {
function resolve(result: string | string[] | null) { function resolve(result: string | string[] | null) {
if (Array.isArray(result)) { if (Array.isArray(result)) {
for (const directory of result) { for (const directory of result) {
openProject(directory, false) void openProject(directory, false)
} }
navigateToProject(result[0]) void navigateToProject(result[0])
} else if (result) { } else if (result) {
openProject(result) void openProject(result)
} }
} }
@@ -1825,7 +1828,7 @@ export default function Layout(props: ParentProps) {
const next = new Set(dirs) const next = new Set(dirs)
for (const directory of next) { for (const directory of next) {
if (loadedSessionDirs.has(directory)) continue if (loadedSessionDirs.has(directory)) continue
globalSync.project.loadSessions(directory) void globalSync.project.loadSessions(directory)
} }
loadedSessionDirs.clear() loadedSessionDirs.clear()
@@ -2100,196 +2103,198 @@ export default function Layout(props: ParentProps) {
</Show> </Show>
} }
> >
<> {(project) => (
<div class="shrink-0 pl-1 py-1"> <>
<div class="group/project flex items-start justify-between gap-2 py-2 pl-2 pr-0"> <div class="shrink-0 pl-1 py-1">
<div class="flex flex-col min-w-0"> <div class="group/project flex items-start justify-between gap-2 py-2 pl-2 pr-0">
<InlineEditor <div class="flex flex-col min-w-0">
id={`project:${projectId()}`} <InlineEditor
value={projectName} id={`project:${projectId()}`}
onSave={(next) => { value={projectName}
const item = project() onSave={(next) => {
if (!item) return const item = project()
renameProject(item, next) if (!item) return
}} void renameProject(item, next)
class="text-14-medium text-text-strong truncate" }}
displayClass="text-14-medium text-text-strong truncate" class="text-14-medium text-text-strong truncate"
stopPropagation displayClass="text-14-medium text-text-strong truncate"
/> stopPropagation
/>
<Tooltip <Tooltip
placement="bottom" placement="bottom"
gutter={2} gutter={2}
value={worktree()} value={worktree()}
class="shrink-0" class="shrink-0"
contentStyle={{ contentStyle={{
"max-width": "640px", "max-width": "640px",
transform: "translate3d(52px, 0, 0)", transform: "translate3d(52px, 0, 0)",
}} }}
> >
<span class="text-12-regular text-text-base truncate select-text"> <span class="text-12-regular text-text-base truncate select-text">
{worktree().replace(homedir(), "~")} {worktree().replace(homedir(), "~")}
</span> </span>
</Tooltip> </Tooltip>
</div>
<DropdownMenu modal={!sidebarHovering()}>
<DropdownMenu.Trigger
as={IconButton}
icon="dot-grid"
variant="ghost"
data-action="project-menu"
data-project={slug()}
class="shrink-0 size-6 rounded-md transition-opacity data-[expanded]:bg-surface-base-active"
classList={{
"opacity-100": panelProps.mobile || merged(),
"opacity-0 group-hover/project:opacity-100 group-focus-within/project:opacity-100 data-[expanded]:opacity-100":
!panelProps.mobile && !merged(),
}}
aria-label={language.t("common.moreOptions")}
/>
<DropdownMenu.Portal>
<DropdownMenu.Content class="mt-1">
<DropdownMenu.Item
onSelect={() => {
const item = project()
if (!item) return
showEditProjectDialog(item)
}}
>
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Item
data-action="project-workspaces-toggle"
data-project={slug()}
disabled={!canToggle()}
onSelect={() => {
const item = project()
if (!item) return
toggleProjectWorkspaces(item)
}}
>
<DropdownMenu.ItemLabel>
{workspacesEnabled()
? language.t("sidebar.workspaces.disable")
: language.t("sidebar.workspaces.enable")}
</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Item
data-action="project-clear-notifications"
data-project={slug()}
disabled={unseenCount() === 0}
onSelect={clearNotifications}
>
<DropdownMenu.ItemLabel>
{language.t("sidebar.project.clearNotifications")}
</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
data-action="project-close-menu"
data-project={slug()}
onSelect={() => {
const dir = worktree()
if (!dir) return
closeProject(dir)
}}
>
<DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div> </div>
<DropdownMenu modal={!sidebarHovering()}>
<DropdownMenu.Trigger
as={IconButton}
icon="dot-grid"
variant="ghost"
data-action="project-menu"
data-project={slug()}
class="shrink-0 size-6 rounded-md transition-opacity data-[expanded]:bg-surface-base-active"
classList={{
"opacity-100": panelProps.mobile || merged(),
"opacity-0 group-hover/project:opacity-100 group-focus-within/project:opacity-100 data-[expanded]:opacity-100":
!panelProps.mobile && !merged(),
}}
aria-label={language.t("common.moreOptions")}
/>
<DropdownMenu.Portal>
<DropdownMenu.Content class="mt-1">
<DropdownMenu.Item
onSelect={() => {
const item = project()
if (!item) return
showEditProjectDialog(item)
}}
>
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Item
data-action="project-workspaces-toggle"
data-project={slug()}
disabled={!canToggle()}
onSelect={() => {
const item = project()
if (!item) return
toggleProjectWorkspaces(item)
}}
>
<DropdownMenu.ItemLabel>
{workspacesEnabled()
? language.t("sidebar.workspaces.disable")
: language.t("sidebar.workspaces.enable")}
</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Item
data-action="project-clear-notifications"
data-project={slug()}
disabled={unseenCount() === 0}
onSelect={clearNotifications}
>
<DropdownMenu.ItemLabel>
{language.t("sidebar.project.clearNotifications")}
</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
data-action="project-close-menu"
data-project={slug()}
onSelect={() => {
const dir = worktree()
if (!dir) return
closeProject(dir)
}}
>
<DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div> </div>
</div>
<div class="flex-1 min-h-0 flex flex-col"> <div class="flex-1 min-h-0 flex flex-col">
<Show <Show
when={workspacesEnabled()} when={workspacesEnabled()}
fallback={ fallback={
<>
<div class="shrink-0 py-4">
<Button
size="large"
icon="new-session"
class="w-full"
onClick={() => {
const dir = worktree()
if (!dir) return
navigateWithSidebarReset(`/${base64Encode(dir)}/session`)
}}
>
{language.t("command.session.new")}
</Button>
</div>
<div class="flex-1 min-h-0">
<LocalWorkspace
ctx={workspaceSidebarCtx}
project={project()}
sortNow={sortNow}
mobile={panelProps.mobile}
/>
</div>
</>
}
>
<> <>
<div class="shrink-0 py-4"> <div class="shrink-0 py-4">
<Button <Button
size="large" size="large"
icon="new-session" icon="plus-small"
class="w-full" class="w-full"
onClick={() => { onClick={() => {
const dir = worktree() const item = project()
if (!dir) return if (!item) return
navigateWithSidebarReset(`/${base64Encode(dir)}/session`) void createWorkspace(item)
}} }}
> >
{language.t("command.session.new")} {language.t("workspace.new")}
</Button> </Button>
</div> </div>
<div class="flex-1 min-h-0"> <div class="relative flex-1 min-h-0">
<LocalWorkspace <DragDropProvider
ctx={workspaceSidebarCtx} onDragStart={handleWorkspaceDragStart}
project={project()!} onDragEnd={handleWorkspaceDragEnd}
sortNow={sortNow} onDragOver={handleWorkspaceDragOver}
mobile={panelProps.mobile} collisionDetector={closestCenter}
/> >
<DragDropSensors />
<ConstrainDragXAxis />
<div
ref={(el) => {
if (!panelProps.mobile) scrollContainerRef = el
}}
class="size-full flex flex-col py-2 gap-4 overflow-y-auto no-scrollbar [overflow-anchor:none]"
>
<SortableProvider ids={workspaces()}>
<For each={workspaces()}>
{(directory) => (
<SortableWorkspace
ctx={workspaceSidebarCtx}
directory={directory}
project={project()}
sortNow={sortNow}
mobile={panelProps.mobile}
/>
)}
</For>
</SortableProvider>
</div>
<DragOverlay>
<WorkspaceDragOverlay
sidebarProject={sidebarProject}
activeWorkspace={() => store.activeWorkspace}
workspaceLabel={workspaceLabel}
/>
</DragOverlay>
</DragDropProvider>
</div> </div>
</> </>
} </Show>
> </div>
<> </>
<div class="shrink-0 py-4"> )}
<Button
size="large"
icon="plus-small"
class="w-full"
onClick={() => {
const item = project()
if (!item) return
createWorkspace(item)
}}
>
{language.t("workspace.new")}
</Button>
</div>
<div class="relative flex-1 min-h-0">
<DragDropProvider
onDragStart={handleWorkspaceDragStart}
onDragEnd={handleWorkspaceDragEnd}
onDragOver={handleWorkspaceDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragXAxis />
<div
ref={(el) => {
if (!panelProps.mobile) scrollContainerRef = el
}}
class="size-full flex flex-col py-2 gap-4 overflow-y-auto no-scrollbar [overflow-anchor:none]"
>
<SortableProvider ids={workspaces()}>
<For each={workspaces()}>
{(directory) => (
<SortableWorkspace
ctx={workspaceSidebarCtx}
directory={directory}
project={project()!}
sortNow={sortNow}
mobile={panelProps.mobile}
/>
)}
</For>
</SortableProvider>
</div>
<DragOverlay>
<WorkspaceDragOverlay
sidebarProject={sidebarProject}
activeWorkspace={() => store.activeWorkspace}
workspaceLabel={workspaceLabel}
/>
</DragOverlay>
</DragDropProvider>
</div>
</>
</Show>
</div>
</>
</Show> </Show>
<div <div
@@ -2355,6 +2360,7 @@ export default function Layout(props: ParentProps) {
return ( return (
<div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text"> <div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
{autoselecting() ?? ""}
<Titlebar /> <Titlebar />
<div class="flex-1 min-h-0 min-w-0 flex"> <div class="flex-1 min-h-0 min-w-0 flex">
<div class="flex-1 min-h-0 relative"> <div class="flex-1 min-h-0 relative">
@@ -14,10 +14,11 @@ import { Spinner } from "@opencode-ai/ui/spinner"
import { Tooltip } from "@opencode-ai/ui/tooltip" import { Tooltip } from "@opencode-ai/ui/tooltip"
import { type Session } from "@opencode-ai/sdk/v2/client" import { type Session } from "@opencode-ai/sdk/v2/client"
import { type LocalProject } from "@/context/layout" import { type LocalProject } from "@/context/layout"
import { useGlobalSync } from "@/context/global-sync" import { loadSessionsQuery, useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items" import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
import { sortedRootSessions, workspaceKey } from "./helpers" import { sortedRootSessions, workspaceKey } from "./helpers"
import { useQuery } from "@tanstack/solid-query"
type InlineEditorComponent = (props: { type InlineEditorComponent = (props: {
id: string id: string
@@ -277,7 +278,7 @@ const WorkspaceSessionList = (props: {
class="flex w-full text-left justify-start text-14-regular text-text-weak pl-2 pr-10" class="flex w-full text-left justify-start text-14-regular text-text-weak pl-2 pr-10"
size="large" size="large"
onClick={(e: MouseEvent) => { onClick={(e: MouseEvent) => {
props.loadMore() void props.loadMore()
;(e.currentTarget as HTMLButtonElement).blur() ;(e.currentTarget as HTMLButtonElement).blur()
}} }}
> >
@@ -316,12 +317,11 @@ export const SortableWorkspace = (props: {
}) })
const open = createMemo(() => props.ctx.workspaceExpanded(props.directory, local())) const open = createMemo(() => props.ctx.workspaceExpanded(props.directory, local()))
const boot = createMemo(() => open() || active()) const boot = createMemo(() => open() || active())
const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false)
const count = createMemo(() => sessions()?.length ?? 0) const count = createMemo(() => sessions()?.length ?? 0)
const hasMore = createMemo(() => workspaceStore.sessionTotal > count()) const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
const busy = createMemo(() => props.ctx.isBusy(props.directory)) const busy = createMemo(() => props.ctx.isBusy(props.directory))
const wasBusy = createMemo((prev) => prev || busy(), false) const loading = () => query.isLoading
const loading = createMemo(() => open() && !booted() && count() === 0 && !wasBusy())
const touch = createMediaQuery("(hover: none)") const touch = createMediaQuery("(hover: none)")
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id))) const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
const loadMore = async () => { const loadMore = async () => {
@@ -426,7 +426,7 @@ export const SortableWorkspace = (props: {
mobile={props.mobile} mobile={props.mobile}
ctx={props.ctx} ctx={props.ctx}
showNew={showNew} showNew={showNew}
loading={loading} loading={() => query.isLoading && count() === 0}
sessions={sessions} sessions={sessions}
hasMore={hasMore} hasMore={hasMore}
loadMore={loadMore} loadMore={loadMore}
@@ -452,10 +452,10 @@ export const LocalWorkspace = (props: {
}) })
const slug = createMemo(() => base64Encode(props.project.worktree)) const slug = createMemo(() => base64Encode(props.project.worktree))
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow())) const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
const booted = createMemo((prev) => prev || workspace().store.status === "complete", false)
const count = createMemo(() => sessions()?.length ?? 0) const count = createMemo(() => sessions()?.length ?? 0)
const loading = createMemo(() => !booted() && count() === 0) const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
const hasMore = createMemo(() => workspace().store.sessionTotal > count()) const hasMore = createMemo(() => workspace().store.sessionTotal > count())
const loading = () => query.isLoading && count() === 0
const loadMore = async () => { const loadMore = async () => {
workspace().setStore("limit", (limit) => (limit ?? 0) + 5) workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
await globalSync.project.loadSessions(props.project.worktree) await globalSync.project.loadSessions(props.project.worktree)
+55 -142
View File
@@ -1,6 +1,6 @@
import type { Project, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useMutation } from "@tanstack/solid-query" import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query"
import { import {
batch, batch,
onCleanup, onCleanup,
@@ -13,6 +13,7 @@ import {
on, on,
onMount, onMount,
untrack, untrack,
createResource,
} from "solid-js" } from "solid-js"
import { makeEventListener } from "@solid-primitives/event-listener" import { makeEventListener } from "@solid-primitives/event-listener"
import { createMediaQuery } from "@solid-primitives/media" import { createMediaQuery } from "@solid-primitives/media"
@@ -323,6 +324,7 @@ export default function Page() {
const local = useLocal() const local = useLocal()
const file = useFile() const file = useFile()
const sync = useSync() const sync = useSync()
const queryClient = useQueryClient()
const dialog = useDialog() const dialog = useDialog()
const language = useLanguage() const language = useLanguage()
const sdk = useSDK() const sdk = useSDK()
@@ -432,7 +434,6 @@ export default function Page() {
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
const isChildSession = createMemo(() => !!info()?.parentID) const isChildSession = createMemo(() => !!info()?.parentID)
const diffs = createMemo(() => (params.id ? list(sync.data.session_diff[params.id]) : [])) const diffs = createMemo(() => (params.id ? list(sync.data.session_diff[params.id]) : []))
const sessionCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length))
const canReview = createMemo(() => !!sync.project) const canReview = createMemo(() => !!sync.project)
const reviewTab = createMemo(() => isDesktop()) const reviewTab = createMemo(() => isDesktop())
const tabState = createSessionTabs({ const tabState = createSessionTabs({
@@ -484,7 +485,7 @@ export default function Page() {
if (!tab) return if (!tab) return
const path = file.pathFromTab(tab) const path = file.pathFromTab(tab)
if (path) file.load(path) if (path) void file.load(path)
}) })
createEffect( createEffect(
@@ -518,26 +519,6 @@ export default function Page() {
deferRender: false, deferRender: false,
}) })
const [vcs, setVcs] = createStore<{
diff: {
git: VcsFileDiff[]
branch: VcsFileDiff[]
}
ready: {
git: boolean
branch: boolean
}
}>({
diff: {
git: [] as VcsFileDiff[],
branch: [] as VcsFileDiff[],
},
ready: {
git: false,
branch: false,
},
})
const [followup, setFollowup] = persisted( const [followup, setFollowup] = persisted(
Persist.workspace(sdk.directory, "followup", ["followup.v1"]), Persist.workspace(sdk.directory, "followup", ["followup.v1"]),
createStore<{ createStore<{
@@ -571,68 +552,6 @@ export default function Page() {
let todoTimer: number | undefined let todoTimer: number | undefined
let diffFrame: number | undefined let diffFrame: number | undefined
let diffTimer: number | undefined let diffTimer: number | undefined
const vcsTask = new Map<VcsMode, Promise<void>>()
const vcsRun = new Map<VcsMode, number>()
const bumpVcs = (mode: VcsMode) => {
const next = (vcsRun.get(mode) ?? 0) + 1
vcsRun.set(mode, next)
return next
}
const resetVcs = (mode?: VcsMode) => {
const list = mode ? [mode] : (["git", "branch"] as const)
list.forEach((item) => {
bumpVcs(item)
vcsTask.delete(item)
setVcs("diff", item, [])
setVcs("ready", item, false)
})
}
const loadVcs = (mode: VcsMode, force = false) => {
if (sync.project?.vcs !== "git") return Promise.resolve()
if (!force && vcs.ready[mode]) return Promise.resolve()
if (force) {
if (vcsTask.has(mode)) bumpVcs(mode)
vcsTask.delete(mode)
setVcs("ready", mode, false)
}
const current = vcsTask.get(mode)
if (current) return current
const run = bumpVcs(mode)
const task = sdk.client.vcs
.diff({ mode })
.then((result) => {
if (vcsRun.get(mode) !== run) return
setVcs("diff", mode, list(result.data))
setVcs("ready", mode, true)
})
.catch((error) => {
if (vcsRun.get(mode) !== run) return
console.debug("[session-review] failed to load vcs diff", { mode, error })
setVcs("diff", mode, [])
setVcs("ready", mode, true)
})
.finally(() => {
if (vcsTask.get(mode) === task) vcsTask.delete(mode)
})
vcsTask.set(mode, task)
return task
}
const refreshVcs = () => {
resetVcs()
const mode = untrack(vcsMode)
if (!mode) return
if (!untrack(wantsReview)) return
void loadVcs(mode, true)
}
createComputed((prev) => { createComputed((prev) => {
const open = desktopReviewOpen() const open = desktopReviewOpen()
@@ -663,21 +582,52 @@ export default function Page() {
list.push("turn") list.push("turn")
return list return list
}) })
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
const wantsReview = createMemo(() =>
isDesktop()
? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review")
: store.mobileTab === "changes",
)
const vcsMode = createMemo<VcsMode | undefined>(() => { const vcsMode = createMemo<VcsMode | undefined>(() => {
if (store.changes === "git" || store.changes === "branch") return store.changes if (store.changes === "git" || store.changes === "branch") return store.changes
}) })
const reviewDiffs = createMemo(() => { const vcsKey = createMemo(
if (store.changes === "git") return list(vcs.diff.git) () => ["session-vcs", sdk.directory, sync.data.vcs?.branch ?? "", sync.data.vcs?.default_branch ?? ""] as const,
if (store.changes === "branch") return list(vcs.diff.branch) )
const vcsQuery = createQuery(() => {
const mode = vcsMode()
const enabled = wantsReview() && sync.project?.vcs === "git"
return {
queryKey: [...vcsKey(), mode] as const,
enabled,
staleTime: Number.POSITIVE_INFINITY,
gcTime: 60 * 1000,
queryFn: mode
? () =>
sdk.client.vcs
.diff({ mode })
.then((result) => list(result.data))
.catch((error) => {
console.debug("[session-review] failed to load vcs diff", { mode, error })
return []
})
: skipToken,
}
})
const refreshVcs = () => void queryClient.invalidateQueries({ queryKey: vcsKey() })
const reviewDiffs = () => {
if (store.changes === "git" || store.changes === "branch")
// avoids suspense
return vcsQuery.isFetched ? (vcsQuery.data ?? []) : []
return turnDiffs() return turnDiffs()
}) }
const reviewCount = createMemo(() => reviewDiffs().length) const reviewCount = () => reviewDiffs().length
const hasReview = createMemo(() => reviewCount() > 0) const hasReview = () => reviewCount() > 0
const reviewReady = createMemo(() => { const reviewReady = () => {
if (store.changes === "git") return vcs.ready.git if (store.changes === "git" || store.changes === "branch") return !vcsQuery.isPending
if (store.changes === "branch") return vcs.ready.branch
return true return true
}) }
const newSessionWorktree = createMemo(() => { const newSessionWorktree = createMemo(() => {
if (store.newSessionWorktree === "create") return "create" if (store.newSessionWorktree === "create") return "create"
@@ -805,8 +755,9 @@ export default function Page() {
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
createEffect( const [sessionSync] = createResource(
on([() => sdk.directory, () => params.id] as const, ([, id]) => { () => [sdk.directory, params.id] as const,
([directory, id]) => {
if (refreshFrame !== undefined) cancelAnimationFrame(refreshFrame) if (refreshFrame !== undefined) cancelAnimationFrame(refreshFrame)
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer) if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
refreshFrame = undefined refreshFrame = undefined
@@ -817,13 +768,10 @@ export default function Page() {
const stale = !cached const stale = !cached
? false ? false
: (() => { : (() => {
const info = getSessionPrefetch(sdk.directory, id) const info = getSessionPrefetch(directory, id)
if (!info) return true if (!info) return true
return Date.now() - info.at > SESSION_PREFETCH_TTL return Date.now() - info.at > SESSION_PREFETCH_TTL
})() })()
untrack(() => {
void sync.session.sync(id)
})
refreshFrame = requestAnimationFrame(() => { refreshFrame = requestAnimationFrame(() => {
refreshFrame = undefined refreshFrame = undefined
@@ -835,7 +783,9 @@ export default function Page() {
}) })
}, 0) }, 0)
}) })
}),
return sync.session.sync(id)
},
) )
createEffect( createEffect(
@@ -897,27 +847,6 @@ export default function Page() {
), ),
) )
createEffect(
on(
() => sdk.directory,
() => {
resetVcs()
},
{ defer: true },
),
)
createEffect(
on(
() => [sync.data.vcs?.branch, sync.data.vcs?.default_branch] as const,
(next, prev) => {
if (prev === undefined || same(next, prev)) return
refreshVcs()
},
{ defer: true },
),
)
const stopVcs = sdk.event.listen((evt) => { const stopVcs = sdk.event.listen((evt) => {
if (evt.details.type !== "file.watcher.updated") return if (evt.details.type !== "file.watcher.updated") return
const props = const props =
@@ -1051,13 +980,6 @@ export default function Page() {
} }
} }
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
const wantsReview = createMemo(() =>
isDesktop()
? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review")
: store.mobileTab === "changes",
)
createEffect(() => { createEffect(() => {
const list = changesOptions() const list = changesOptions()
if (list.includes(store.changes)) return if (list.includes(store.changes)) return
@@ -1066,22 +988,12 @@ export default function Page() {
setStore("changes", next) setStore("changes", next)
}) })
createEffect(() => {
const mode = vcsMode()
if (!mode) return
if (!wantsReview()) return
void loadVcs(mode)
})
createEffect( createEffect(
on( on(
() => sync.data.session_status[params.id ?? ""]?.type, () => sync.data.session_status[params.id ?? ""]?.type,
(next, prev) => { (next, prev) => {
const mode = vcsMode()
if (!mode) return
if (!wantsReview()) return
if (next !== "idle" || prev === undefined || prev === "idle") return if (next !== "idle" || prev === undefined || prev === "idle") return
void loadVcs(mode, true) refreshVcs()
}, },
{ defer: true }, { defer: true },
), ),
@@ -1882,6 +1794,7 @@ export default function Page() {
return ( return (
<div class="relative bg-background-base size-full overflow-hidden flex flex-col"> <div class="relative bg-background-base size-full overflow-hidden flex flex-col">
{sessionSync() ?? ""}
<SessionHeader /> <SessionHeader />
<div class="flex-1 min-h-0 flex flex-col md:flex-row"> <div class="flex-1 min-h-0 flex flex-col md:flex-row">
<Show when={!isDesktop() && !!params.id}> <Show when={!isDesktop() && !!params.id}>
+1 -1
View File
@@ -117,7 +117,7 @@ export const createOpenReviewFile = (input: {
input.openTab(tab) input.openTab(tab)
input.setActive(tab) input.setActive(tab)
} }
if (maybePromise instanceof Promise) maybePromise.then(open) if (maybePromise instanceof Promise) void maybePromise.then(open)
else open() else open()
}) })
} }
@@ -19,6 +19,9 @@ import { useCommand } from "@/context/command"
import { useFile, type SelectedLineRange } from "@/context/file" import { useFile, type SelectedLineRange } from "@/context/file"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout" import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform"
import { useSettings } from "@/context/settings"
import { useSync } from "@/context/sync"
import { createFileTabListSync } from "@/pages/session/file-tab-scroll" import { createFileTabListSync } from "@/pages/session/file-tab-scroll"
import { FileTabContent } from "@/pages/session/file-tabs" import { FileTabContent } from "@/pages/session/file-tabs"
import { createOpenSessionFileTab, createSessionTabs, getTabReorderIndex, type Sizing } from "@/pages/session/helpers" import { createOpenSessionFileTab, createSessionTabs, getTabReorderIndex, type Sizing } from "@/pages/session/helpers"
@@ -39,6 +42,9 @@ export function SessionSidePanel(props: {
size: Sizing size: Sizing
}) { }) {
const layout = useLayout() const layout = useLayout()
const platform = usePlatform()
const settings = useSettings()
const sync = useSync()
const file = useFile() const file = useFile()
const language = useLanguage() const language = useLanguage()
const command = useCommand() const command = useCommand()
@@ -46,9 +52,15 @@ export function SessionSidePanel(props: {
const { sessionKey, tabs, view } = useSessionLayout() const { sessionKey, tabs, view } = useSessionLayout()
const isDesktop = createMediaQuery("(min-width: 768px)") const isDesktop = createMediaQuery("(min-width: 768px)")
const shown = createMemo(
() =>
platform.platform !== "desktop" ||
import.meta.env.VITE_OPENCODE_CHANNEL !== "beta" ||
settings.general.showFileTree(),
)
const reviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened()) const reviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
const fileOpen = createMemo(() => isDesktop() && layout.fileTree.opened()) const fileOpen = createMemo(() => isDesktop() && shown() && layout.fileTree.opened())
const open = createMemo(() => reviewOpen() || fileOpen()) const open = createMemo(() => reviewOpen() || fileOpen())
const reviewTab = createMemo(() => isDesktop()) const reviewTab = createMemo(() => isDesktop())
const panelWidth = createMemo(() => { const panelWidth = createMemo(() => {
@@ -341,98 +353,99 @@ export function SessionSidePanel(props: {
</div> </div>
</div> </div>
<div <Show when={shown()}>
id="file-tree-panel"
aria-hidden={!fileOpen()}
inert={!fileOpen()}
class="relative min-w-0 h-full shrink-0 overflow-hidden"
classList={{
"pointer-events-none": !fileOpen(),
"transition-[width] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none":
!props.size.active(),
}}
style={{ width: treeWidth() }}
>
<div <div
class="h-full flex flex-col overflow-hidden group/filetree" id="file-tree-panel"
classList={{ "border-l border-border-weaker-base": reviewOpen() }} aria-hidden={!fileOpen()}
inert={!fileOpen()}
class="relative min-w-0 h-full shrink-0 overflow-hidden"
classList={{
"pointer-events-none": !fileOpen(),
"transition-[width] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width] motion-reduce:transition-none":
!props.size.active(),
}}
style={{ width: treeWidth() }}
> >
<Tabs <div
variant="pill" class="h-full flex flex-col overflow-hidden group/filetree"
value={fileTreeTab()} classList={{ "border-l border-border-weaker-base": reviewOpen() }}
onChange={setFileTreeTabValue}
class="h-full"
data-scope="filetree"
> >
<Tabs.List> <Tabs
<Tabs.Trigger value="changes" class="flex-1" classes={{ button: "w-full" }}> variant="pill"
{props.reviewCount()}{" "} value={fileTreeTab()}
{language.t( onChange={setFileTreeTabValue}
props.reviewCount() === 1 ? "session.review.change.one" : "session.review.change.other", class="h-full"
)} data-scope="filetree"
</Tabs.Trigger> >
<Tabs.Trigger value="all" class="flex-1" classes={{ button: "w-full" }}> <Tabs.List>
{language.t("session.files.all")} <Tabs.Trigger value="changes" class="flex-1" classes={{ button: "w-full" }}>
</Tabs.Trigger> {props.reviewCount()}{" "}
</Tabs.List> {language.t(
<Tabs.Content value="changes" class="bg-background-stronger px-3 py-0"> props.reviewCount() === 1 ? "session.review.change.one" : "session.review.change.other",
<Switch> )}
<Match when={props.hasReview() || !props.diffsReady()}> </Tabs.Trigger>
<Show <Tabs.Trigger value="all" class="flex-1" classes={{ button: "w-full" }}>
when={props.diffsReady()} {language.t("session.files.all")}
fallback={ </Tabs.Trigger>
<div class="px-2 py-2 text-12-regular text-text-weak"> </Tabs.List>
{language.t("common.loading")} <Tabs.Content value="changes" class="bg-background-stronger px-3 py-0">
{language.t("common.loading.ellipsis")} <Switch>
</div> <Match when={props.hasReview() || !props.diffsReady()}>
} <Show
> when={props.diffsReady()}
fallback={
<div class="px-2 py-2 text-12-regular text-text-weak">
{language.t("common.loading")}
{language.t("common.loading.ellipsis")}
</div>
}
>
<FileTree
path=""
class="pt-3"
allowed={diffFiles()}
kinds={kinds()}
draggable={false}
active={props.activeDiff}
onFileClick={(node) => props.focusReviewDiff(node.path)}
/>
</Show>
</Match>
</Switch>
</Tabs.Content>
<Tabs.Content value="all" class="bg-background-stronger px-3 py-0">
<Switch>
<Match when={nofiles()}>{empty(language.t("session.files.empty"))}</Match>
<Match when={true}>
<FileTree <FileTree
path="" path=""
class="pt-3" class="pt-3"
allowed={diffFiles()} modified={diffFiles()}
kinds={kinds()} kinds={kinds()}
draggable={false} onFileClick={(node) => openTab(file.tab(node.path))}
active={props.activeDiff}
onFileClick={(node) => props.focusReviewDiff(node.path)}
/> />
</Show> </Match>
</Match> </Switch>
<Match when={true}>{empty(props.empty())}</Match> </Tabs.Content>
</Switch> </Tabs>
</Tabs.Content>
<Tabs.Content value="all" class="bg-background-stronger px-3 py-0">
<Switch>
<Match when={nofiles()}>{empty(language.t("session.files.empty"))}</Match>
<Match when={true}>
<FileTree
path=""
class="pt-3"
modified={diffFiles()}
kinds={kinds()}
onFileClick={(node) => openTab(file.tab(node.path))}
/>
</Match>
</Switch>
</Tabs.Content>
</Tabs>
</div>
<Show when={fileOpen()}>
<div onPointerDown={() => props.size.start()}>
<ResizeHandle
direction="horizontal"
edge="start"
size={layout.fileTree.width()}
min={200}
max={480}
onResize={(width) => {
props.size.touch()
layout.fileTree.resize(width)
}}
/>
</div> </div>
</Show> <Show when={fileOpen()}>
</div> <div onPointerDown={() => props.size.start()}>
<ResizeHandle
direction="horizontal"
edge="start"
size={layout.fileTree.width()}
min={200}
max={480}
onResize={(width) => {
props.size.touch()
layout.fileTree.resize(width)
}}
/>
</div>
</Show>
</div>
</Show>
</div> </div>
</aside> </aside>
</Show> </Show>
@@ -7,8 +7,10 @@ import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout" import { useLayout } from "@/context/layout"
import { useLocal } from "@/context/local" import { useLocal } from "@/context/local"
import { usePermission } from "@/context/permission" import { usePermission } from "@/context/permission"
import { usePlatform } from "@/context/platform"
import { usePrompt } from "@/context/prompt" import { usePrompt } from "@/context/prompt"
import { useSDK } from "@/context/sdk" import { useSDK } from "@/context/sdk"
import { useSettings } from "@/context/settings"
import { useSync } from "@/context/sync" import { useSync } from "@/context/sync"
import { useTerminal } from "@/context/terminal" import { useTerminal } from "@/context/terminal"
import { showToast } from "@opencode-ai/ui/toast" import { showToast } from "@opencode-ai/ui/toast"
@@ -39,8 +41,10 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
const language = useLanguage() const language = useLanguage()
const local = useLocal() const local = useLocal()
const permission = usePermission() const permission = usePermission()
const platform = usePlatform()
const prompt = usePrompt() const prompt = usePrompt()
const sdk = useSDK() const sdk = useSDK()
const settings = useSettings()
const sync = useSync() const sync = useSync()
const terminal = useTerminal() const terminal = useTerminal()
const layout = useLayout() const layout = useLayout()
@@ -66,6 +70,10 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
}) })
const activeFileTab = tabState.activeFileTab const activeFileTab = tabState.activeFileTab
const closableTab = tabState.closableTab const closableTab = tabState.closableTab
const shown = () =>
platform.platform !== "desktop" ||
import.meta.env.VITE_OPENCODE_CHANNEL !== "beta" ||
settings.general.showFileTree()
const idle = { type: "idle" as const } const idle = { type: "idle" as const }
const status = () => sync.data.session_status[params.id ?? ""] ?? idle const status = () => sync.data.session_status[params.id ?? ""] ?? idle
@@ -457,12 +465,16 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
keybind: "mod+shift+r", keybind: "mod+shift+r",
onSelect: () => view().reviewPanel.toggle(), onSelect: () => view().reviewPanel.toggle(),
}), }),
viewCommand({ ...(shown()
id: "fileTree.toggle", ? [
title: language.t("command.fileTree.toggle"), viewCommand({
keybind: "mod+\\", id: "fileTree.toggle",
onSelect: () => layout.fileTree.toggle(), title: language.t("command.fileTree.toggle"),
}), keybind: "mod+\\",
onSelect: () => layout.fileTree.toggle(),
}),
]
: []),
viewCommand({ viewCommand({
id: "input.focus", id: "input.focus",
title: language.t("command.input.focus"), title: language.t("command.input.focus"),
+1 -1
View File
@@ -469,7 +469,7 @@ export function persisted<T>(
state, state,
setState, setState,
init, init,
Object.assign(() => ready() === true, { Object.assign(() => (ready.loading ? false : ready.latest === true), {
promise: init instanceof Promise ? init : undefined, promise: init instanceof Promise ? init : undefined,
}), }),
] ]
+4 -1
View File
@@ -16,7 +16,10 @@ export function createSdkForServer({
return createOpencodeClient({ return createOpencodeClient({
...config, ...config,
headers: { ...config.headers, ...auth }, headers: {
...(config.headers instanceof Headers ? Object.fromEntries(config.headers.entries()) : config.headers),
...auth,
},
baseUrl: server.url, baseUrl: server.url,
}) })
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.4.6", "version": "1.14.18",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@@ -105,4 +105,4 @@ async function main() {
console.log(`✓ Sitemap generated at ${outputPath}`) console.log(`✓ Sitemap generated at ${outputPath}`)
} }
main() void main()
@@ -766,7 +766,7 @@ export default function Spotlight(props: SpotlightProps) {
} }
} }
initializeWebGPU() void initializeWebGPU()
onCleanup(() => { onCleanup(() => {
if (cleanupFunctionRef) { if (cleanupFunctionRef) {
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "المؤسسات", "nav.enterprise": "المؤسسات",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "تسجيل الدخول", "nav.login": "تسجيل الدخول",
"nav.free": "مجانا", "nav.free": "تحميل",
"nav.home": "الرئيسية", "nav.home": "الرئيسية",
"nav.openMenu": "فتح القائمة", "nav.openMenu": "فتح القائمة",
"nav.getStartedFree": "ابدأ مجانا", "nav.getStartedFree": "ابدأ مجانا",
@@ -558,6 +558,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "الاستخدام الحالي لـ", "workspace.monthlyLimit.currentUsage.beforeMonth": "الاستخدام الحالي لـ",
"workspace.monthlyLimit.currentUsage.beforeAmount": "هو $", "workspace.monthlyLimit.currentUsage.beforeAmount": "هو $",
"workspace.redeem.title": "استرداد قسيمة",
"workspace.redeem.subtitle": "استرد رمز القسيمة للحصول على رصيد أو مزايا.",
"workspace.redeem.placeholder": "أدخل رمز القسيمة",
"workspace.redeem.redeem": "استرداد",
"workspace.redeem.redeeming": "جارٍ الاسترداد...",
"workspace.redeem.success": "تم استرداد القسيمة بنجاح.",
"workspace.reload.title": "إعادة الشحن التلقائي", "workspace.reload.title": "إعادة الشحن التلقائي",
"workspace.reload.disabled.before": "إعادة الشحن التلقائي", "workspace.reload.disabled.before": "إعادة الشحن التلقائي",
"workspace.reload.disabled.state": "معطّل", "workspace.reload.disabled.state": "معطّل",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Entrar", "nav.login": "Entrar",
"nav.free": "Grátis", "nav.free": "Download",
"nav.home": "Início", "nav.home": "Início",
"nav.openMenu": "Abrir menu", "nav.openMenu": "Abrir menu",
"nav.getStartedFree": "Começar grátis", "nav.getStartedFree": "Começar grátis",
@@ -567,6 +567,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Uso atual para", "workspace.monthlyLimit.currentUsage.beforeMonth": "Uso atual para",
"workspace.monthlyLimit.currentUsage.beforeAmount": "é $", "workspace.monthlyLimit.currentUsage.beforeAmount": "é $",
"workspace.redeem.title": "Resgatar Cupom",
"workspace.redeem.subtitle": "Resgate um código de cupom para receber créditos ou vantagens.",
"workspace.redeem.placeholder": "Digite o código do cupom",
"workspace.redeem.redeem": "Resgatar",
"workspace.redeem.redeeming": "Resgatando...",
"workspace.redeem.success": "Cupom resgatado com sucesso.",
"workspace.reload.title": "Recarga Automática", "workspace.reload.title": "Recarga Automática",
"workspace.reload.disabled.before": "A recarga automática está", "workspace.reload.disabled.before": "A recarga automática está",
"workspace.reload.disabled.state": "desativada", "workspace.reload.disabled.state": "desativada",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Log ind", "nav.login": "Log ind",
"nav.free": "Gratis", "nav.free": "Download",
"nav.home": "Hjem", "nav.home": "Hjem",
"nav.openMenu": "Åbn menu", "nav.openMenu": "Åbn menu",
"nav.getStartedFree": "Kom i gang gratis", "nav.getStartedFree": "Kom i gang gratis",
@@ -563,6 +563,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Nuværende brug for", "workspace.monthlyLimit.currentUsage.beforeMonth": "Nuværende brug for",
"workspace.monthlyLimit.currentUsage.beforeAmount": "er $", "workspace.monthlyLimit.currentUsage.beforeAmount": "er $",
"workspace.redeem.title": "Indløs kupon",
"workspace.redeem.subtitle": "Indløs en kuponkode for at få kreditter eller fordele.",
"workspace.redeem.placeholder": "Indtast kuponkode",
"workspace.redeem.redeem": "Indløs",
"workspace.redeem.redeeming": "Indløser...",
"workspace.redeem.success": "Kuponen blev indløst.",
"workspace.reload.title": "Automatisk genopfyldning", "workspace.reload.title": "Automatisk genopfyldning",
"workspace.reload.disabled.before": "Automatisk genopfyldning er", "workspace.reload.disabled.before": "Automatisk genopfyldning er",
"workspace.reload.disabled.state": "deaktiveret", "workspace.reload.disabled.state": "deaktiveret",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Anmelden", "nav.login": "Anmelden",
"nav.free": "Kostenlos", "nav.free": "Download",
"nav.home": "Startseite", "nav.home": "Startseite",
"nav.openMenu": "Menü öffnen", "nav.openMenu": "Menü öffnen",
"nav.getStartedFree": "Kostenlos starten", "nav.getStartedFree": "Kostenlos starten",
@@ -566,6 +566,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Aktuelle Nutzung für", "workspace.monthlyLimit.currentUsage.beforeMonth": "Aktuelle Nutzung für",
"workspace.monthlyLimit.currentUsage.beforeAmount": "ist $", "workspace.monthlyLimit.currentUsage.beforeAmount": "ist $",
"workspace.redeem.title": "Gutschein einlösen",
"workspace.redeem.subtitle": "Löse einen Gutscheincode ein, um Guthaben oder Vorteile zu erhalten.",
"workspace.redeem.placeholder": "Gutscheincode eingeben",
"workspace.redeem.redeem": "Einlösen",
"workspace.redeem.redeeming": "Wird eingelöst...",
"workspace.redeem.success": "Gutschein erfolgreich eingelöst.",
"workspace.reload.title": "Auto-Reload", "workspace.reload.title": "Auto-Reload",
"workspace.reload.disabled.before": "Auto-Reload ist", "workspace.reload.disabled.before": "Auto-Reload ist",
"workspace.reload.disabled.state": "deaktiviert", "workspace.reload.disabled.state": "deaktiviert",
+8 -1
View File
@@ -8,7 +8,7 @@ export const dict = {
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.go": "Go", "nav.go": "Go",
"nav.login": "Login", "nav.login": "Login",
"nav.free": "Free", "nav.free": "Download",
"nav.home": "Home", "nav.home": "Home",
"nav.openMenu": "Open menu", "nav.openMenu": "Open menu",
"nav.getStartedFree": "Get started for free", "nav.getStartedFree": "Get started for free",
@@ -559,6 +559,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Current usage for", "workspace.monthlyLimit.currentUsage.beforeMonth": "Current usage for",
"workspace.monthlyLimit.currentUsage.beforeAmount": "is $", "workspace.monthlyLimit.currentUsage.beforeAmount": "is $",
"workspace.redeem.title": "Redeem Coupon",
"workspace.redeem.subtitle": "Redeem a coupon code to claim credits or perks.",
"workspace.redeem.placeholder": "Enter coupon code",
"workspace.redeem.redeem": "Redeem",
"workspace.redeem.redeeming": "Redeeming...",
"workspace.redeem.success": "Coupon redeemed successfully.",
"workspace.reload.title": "Auto Reload", "workspace.reload.title": "Auto Reload",
"workspace.reload.disabled.before": "Auto reload is", "workspace.reload.disabled.before": "Auto reload is",
"workspace.reload.disabled.state": "disabled", "workspace.reload.disabled.state": "disabled",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Iniciar sesión", "nav.login": "Iniciar sesión",
"nav.free": "Gratis", "nav.free": "Descargar",
"nav.home": "Inicio", "nav.home": "Inicio",
"nav.openMenu": "Abrir menú", "nav.openMenu": "Abrir menú",
"nav.getStartedFree": "Empezar gratis", "nav.getStartedFree": "Empezar gratis",
@@ -567,6 +567,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Uso actual para", "workspace.monthlyLimit.currentUsage.beforeMonth": "Uso actual para",
"workspace.monthlyLimit.currentUsage.beforeAmount": "es $", "workspace.monthlyLimit.currentUsage.beforeAmount": "es $",
"workspace.redeem.title": "Canjear cupón",
"workspace.redeem.subtitle": "Canjea un código de cupón para obtener crédito o beneficios.",
"workspace.redeem.placeholder": "Introduce el código del cupón",
"workspace.redeem.redeem": "Canjear",
"workspace.redeem.redeeming": "Canjeando...",
"workspace.redeem.success": "Cupón canjeado correctamente.",
"workspace.reload.title": "Auto Recarga", "workspace.reload.title": "Auto Recarga",
"workspace.reload.disabled.before": "La auto recarga está", "workspace.reload.disabled.before": "La auto recarga está",
"workspace.reload.disabled.state": "deshabilitada", "workspace.reload.disabled.state": "deshabilitada",
+8 -1
View File
@@ -12,7 +12,7 @@ export const dict = {
"nav.enterprise": "Entreprise", "nav.enterprise": "Entreprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Se connecter", "nav.login": "Se connecter",
"nav.free": "Gratuit", "nav.free": "Télécharger",
"nav.home": "Accueil", "nav.home": "Accueil",
"nav.openMenu": "Ouvrir le menu", "nav.openMenu": "Ouvrir le menu",
"nav.getStartedFree": "Commencer gratuitement", "nav.getStartedFree": "Commencer gratuitement",
@@ -569,6 +569,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "L'utilisation actuelle pour", "workspace.monthlyLimit.currentUsage.beforeMonth": "L'utilisation actuelle pour",
"workspace.monthlyLimit.currentUsage.beforeAmount": "est de", "workspace.monthlyLimit.currentUsage.beforeAmount": "est de",
"workspace.redeem.title": "Utiliser un coupon",
"workspace.redeem.subtitle": "Utilisez un code promo pour obtenir du crédit ou des avantages.",
"workspace.redeem.placeholder": "Saisissez le code promo",
"workspace.redeem.redeem": "Utiliser",
"workspace.redeem.redeeming": "Utilisation...",
"workspace.redeem.success": "Coupon utilisé avec succès.",
"workspace.reload.title": "Rechargement automatique", "workspace.reload.title": "Rechargement automatique",
"workspace.reload.disabled.before": "Le rechargement automatique est", "workspace.reload.disabled.before": "Le rechargement automatique est",
"workspace.reload.disabled.state": "désactivé", "workspace.reload.disabled.state": "désactivé",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Accedi", "nav.login": "Accedi",
"nav.free": "Gratis", "nav.free": "Scarica",
"nav.home": "Home", "nav.home": "Home",
"nav.openMenu": "Apri menu", "nav.openMenu": "Apri menu",
"nav.getStartedFree": "Inizia gratis", "nav.getStartedFree": "Inizia gratis",
@@ -565,6 +565,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Utilizzo attuale per", "workspace.monthlyLimit.currentUsage.beforeMonth": "Utilizzo attuale per",
"workspace.monthlyLimit.currentUsage.beforeAmount": "è $", "workspace.monthlyLimit.currentUsage.beforeAmount": "è $",
"workspace.redeem.title": "Riscatta Coupon",
"workspace.redeem.subtitle": "Riscatta un codice coupon per ottenere credito o vantaggi.",
"workspace.redeem.placeholder": "Inserisci il codice coupon",
"workspace.redeem.redeem": "Riscatta",
"workspace.redeem.redeeming": "Riscatto in corso...",
"workspace.redeem.success": "Coupon riscattato con successo.",
"workspace.reload.title": "Ricarica Auto", "workspace.reload.title": "Ricarica Auto",
"workspace.reload.disabled.before": "La ricarica auto è", "workspace.reload.disabled.before": "La ricarica auto è",
"workspace.reload.disabled.state": "disabilitata", "workspace.reload.disabled.state": "disabilitata",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "エンタープライズ", "nav.enterprise": "エンタープライズ",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "ログイン", "nav.login": "ログイン",
"nav.free": "無料", "nav.free": "ダウンロード",
"nav.home": "ホーム", "nav.home": "ホーム",
"nav.openMenu": "メニューを開く", "nav.openMenu": "メニューを開く",
"nav.getStartedFree": "無料ではじめる", "nav.getStartedFree": "無料ではじめる",
@@ -564,6 +564,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "現在の使用状況(", "workspace.monthlyLimit.currentUsage.beforeMonth": "現在の使用状況(",
"workspace.monthlyLimit.currentUsage.beforeAmount": ")は $", "workspace.monthlyLimit.currentUsage.beforeAmount": ")は $",
"workspace.redeem.title": "クーポンを利用",
"workspace.redeem.subtitle": "クーポンコードを利用して、クレジットや特典を受け取ります。",
"workspace.redeem.placeholder": "クーポンコードを入力",
"workspace.redeem.redeem": "利用する",
"workspace.redeem.redeeming": "利用中...",
"workspace.redeem.success": "クーポンを利用しました。",
"workspace.reload.title": "自動チャージ", "workspace.reload.title": "自動チャージ",
"workspace.reload.disabled.before": "自動チャージは", "workspace.reload.disabled.before": "自動チャージは",
"workspace.reload.disabled.state": "無効", "workspace.reload.disabled.state": "無効",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "엔터프라이즈", "nav.enterprise": "엔터프라이즈",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "로그인", "nav.login": "로그인",
"nav.free": "무료", "nav.free": "다운로드",
"nav.home": "홈", "nav.home": "홈",
"nav.openMenu": "메뉴 열기", "nav.openMenu": "메뉴 열기",
"nav.getStartedFree": "무료로 시작하기", "nav.getStartedFree": "무료로 시작하기",
@@ -558,6 +558,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "현재", "workspace.monthlyLimit.currentUsage.beforeMonth": "현재",
"workspace.monthlyLimit.currentUsage.beforeAmount": "사용량: $", "workspace.monthlyLimit.currentUsage.beforeAmount": "사용량: $",
"workspace.redeem.title": "쿠폰 사용",
"workspace.redeem.subtitle": "쿠폰 코드를 사용해 크레딧이나 혜택을 받으세요.",
"workspace.redeem.placeholder": "쿠폰 코드를 입력하세요",
"workspace.redeem.redeem": "사용",
"workspace.redeem.redeeming": "사용 중...",
"workspace.redeem.success": "쿠폰을 성공적으로 사용했습니다.",
"workspace.reload.title": "자동 충전", "workspace.reload.title": "자동 충전",
"workspace.reload.disabled.before": "자동 충전이", "workspace.reload.disabled.before": "자동 충전이",
"workspace.reload.disabled.state": "비활성화", "workspace.reload.disabled.state": "비활성화",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Logg inn", "nav.login": "Logg inn",
"nav.free": "Gratis", "nav.free": "Last ned",
"nav.home": "Hjem", "nav.home": "Hjem",
"nav.openMenu": "Åpne meny", "nav.openMenu": "Åpne meny",
"nav.getStartedFree": "Kom i gang gratis", "nav.getStartedFree": "Kom i gang gratis",
@@ -564,6 +564,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Gjeldende forbruk for", "workspace.monthlyLimit.currentUsage.beforeMonth": "Gjeldende forbruk for",
"workspace.monthlyLimit.currentUsage.beforeAmount": "er $", "workspace.monthlyLimit.currentUsage.beforeAmount": "er $",
"workspace.redeem.title": "Løs inn kupong",
"workspace.redeem.subtitle": "Løs inn en kupongkode for å få kreditt eller fordeler.",
"workspace.redeem.placeholder": "Skriv inn kupongkode",
"workspace.redeem.redeem": "Løs inn",
"workspace.redeem.redeeming": "Løser inn...",
"workspace.redeem.success": "Kupongen ble løst inn.",
"workspace.reload.title": "Auto-påfyll", "workspace.reload.title": "Auto-påfyll",
"workspace.reload.disabled.before": "Auto-påfyll er", "workspace.reload.disabled.before": "Auto-påfyll er",
"workspace.reload.disabled.state": "deaktivert", "workspace.reload.disabled.state": "deaktivert",
+8 -1
View File
@@ -10,7 +10,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Zaloguj się", "nav.login": "Zaloguj się",
"nav.free": "Darmowe", "nav.free": "Pobierz",
"nav.home": "Strona główna", "nav.home": "Strona główna",
"nav.openMenu": "Otwórz menu", "nav.openMenu": "Otwórz menu",
"nav.getStartedFree": "Zacznij za darmo", "nav.getStartedFree": "Zacznij za darmo",
@@ -565,6 +565,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Aktualne użycie za", "workspace.monthlyLimit.currentUsage.beforeMonth": "Aktualne użycie za",
"workspace.monthlyLimit.currentUsage.beforeAmount": "wynosi $", "workspace.monthlyLimit.currentUsage.beforeAmount": "wynosi $",
"workspace.redeem.title": "Zrealizuj kupon",
"workspace.redeem.subtitle": "Zrealizuj kod kuponu, aby otrzymać środki lub korzyści.",
"workspace.redeem.placeholder": "Wpisz kod kuponu",
"workspace.redeem.redeem": "Zrealizuj",
"workspace.redeem.redeeming": "Realizowanie...",
"workspace.redeem.success": "Kupon został zrealizowany.",
"workspace.reload.title": "Automatyczne doładowanie", "workspace.reload.title": "Automatyczne doładowanie",
"workspace.reload.disabled.before": "Automatyczne doładowanie jest", "workspace.reload.disabled.before": "Automatyczne doładowanie jest",
"workspace.reload.disabled.state": "wyłączone", "workspace.reload.disabled.state": "wyłączone",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Enterprise", "nav.enterprise": "Enterprise",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Войти", "nav.login": "Войти",
"nav.free": "Бесплатно", "nav.free": "Скачать",
"nav.home": "Главная", "nav.home": "Главная",
"nav.openMenu": "Открыть меню", "nav.openMenu": "Открыть меню",
"nav.getStartedFree": "Начать бесплатно", "nav.getStartedFree": "Начать бесплатно",
@@ -571,6 +571,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Текущее использование за", "workspace.monthlyLimit.currentUsage.beforeMonth": "Текущее использование за",
"workspace.monthlyLimit.currentUsage.beforeAmount": "составляет $", "workspace.monthlyLimit.currentUsage.beforeAmount": "составляет $",
"workspace.redeem.title": "Активировать купон",
"workspace.redeem.subtitle": "Активируйте код купона, чтобы получить кредит или бонусы.",
"workspace.redeem.placeholder": "Введите код купона",
"workspace.redeem.redeem": "Активировать",
"workspace.redeem.redeeming": "Активация...",
"workspace.redeem.success": "Купон успешно активирован.",
"workspace.reload.title": "Автопополнение", "workspace.reload.title": "Автопополнение",
"workspace.reload.disabled.before": "Автопополнение", "workspace.reload.disabled.before": "Автопополнение",
"workspace.reload.disabled.state": "отключено", "workspace.reload.disabled.state": "отключено",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "องค์กร", "nav.enterprise": "องค์กร",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "เข้าสู่ระบบ", "nav.login": "เข้าสู่ระบบ",
"nav.free": "ฟรี", "nav.free": "ดาวน์โหลด",
"nav.home": "หน้าหลัก", "nav.home": "หน้าหลัก",
"nav.openMenu": "เปิดเมนู", "nav.openMenu": "เปิดเมนู",
"nav.getStartedFree": "เริ่มต้นฟรี", "nav.getStartedFree": "เริ่มต้นฟรี",
@@ -560,6 +560,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "การใช้งานปัจจุบันสำหรับ", "workspace.monthlyLimit.currentUsage.beforeMonth": "การใช้งานปัจจุบันสำหรับ",
"workspace.monthlyLimit.currentUsage.beforeAmount": "คือ $", "workspace.monthlyLimit.currentUsage.beforeAmount": "คือ $",
"workspace.redeem.title": "แลกคูปอง",
"workspace.redeem.subtitle": "แลกรหัสคูปองเพื่อรับเครดิตหรือสิทธิพิเศษ",
"workspace.redeem.placeholder": "กรอกรหัสคูปอง",
"workspace.redeem.redeem": "แลก",
"workspace.redeem.redeeming": "กำลังแลก...",
"workspace.redeem.success": "แลกคูปองสำเร็จ",
"workspace.reload.title": "โหลดซ้ำอัตโนมัติ", "workspace.reload.title": "โหลดซ้ำอัตโนมัติ",
"workspace.reload.disabled.before": "การโหลดซ้ำอัตโนมัติ", "workspace.reload.disabled.before": "การโหลดซ้ำอัตโนมัติ",
"workspace.reload.disabled.state": "ปิดใช้งานอยู่", "workspace.reload.disabled.state": "ปิดใช้งานอยู่",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "Kurumsal", "nav.enterprise": "Kurumsal",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "Giriş", "nav.login": "Giriş",
"nav.free": "Ücretsiz", "nav.free": "İndir",
"nav.home": "Ana sayfa", "nav.home": "Ana sayfa",
"nav.openMenu": "Menüyü aç", "nav.openMenu": "Menüyü aç",
"nav.getStartedFree": "Ücretsiz başla", "nav.getStartedFree": "Ücretsiz başla",
@@ -567,6 +567,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Şu anki kullanım", "workspace.monthlyLimit.currentUsage.beforeMonth": "Şu anki kullanım",
"workspace.monthlyLimit.currentUsage.beforeAmount": "$", "workspace.monthlyLimit.currentUsage.beforeAmount": "$",
"workspace.redeem.title": "Kupon Kullan",
"workspace.redeem.subtitle": "Kredi veya avantajlardan yararlanmak için bir kupon kodu kullanın.",
"workspace.redeem.placeholder": "Kupon kodunu girin",
"workspace.redeem.redeem": "Kullan",
"workspace.redeem.redeeming": "Kullanılıyor...",
"workspace.redeem.success": "Kupon başarıyla kullanıldı.",
"workspace.reload.title": "Otomatik Yeniden Yükleme", "workspace.reload.title": "Otomatik Yeniden Yükleme",
"workspace.reload.disabled.before": "Otomatik yeniden yükleme:", "workspace.reload.disabled.before": "Otomatik yeniden yükleme:",
"workspace.reload.disabled.state": "devre dışı", "workspace.reload.disabled.state": "devre dışı",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "企业版", "nav.enterprise": "企业版",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "登录", "nav.login": "登录",
"nav.free": "免费", "nav.free": "下载",
"nav.home": "首页", "nav.home": "首页",
"nav.openMenu": "打开菜单", "nav.openMenu": "打开菜单",
"nav.getStartedFree": "免费开始", "nav.getStartedFree": "免费开始",
@@ -542,6 +542,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "当前", "workspace.monthlyLimit.currentUsage.beforeMonth": "当前",
"workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量为 $", "workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量为 $",
"workspace.redeem.title": "兑换优惠券",
"workspace.redeem.subtitle": "兑换优惠码以领取充值额度或权益。",
"workspace.redeem.placeholder": "输入优惠码",
"workspace.redeem.redeem": "兑换",
"workspace.redeem.redeeming": "兑换中...",
"workspace.redeem.success": "优惠券兑换成功。",
"workspace.reload.title": "自动充值", "workspace.reload.title": "自动充值",
"workspace.reload.disabled.before": "自动充值已", "workspace.reload.disabled.before": "自动充值已",
"workspace.reload.disabled.state": "禁用", "workspace.reload.disabled.state": "禁用",
+8 -1
View File
@@ -11,7 +11,7 @@ export const dict = {
"nav.enterprise": "企業", "nav.enterprise": "企業",
"nav.zen": "Zen", "nav.zen": "Zen",
"nav.login": "登入", "nav.login": "登入",
"nav.free": "免費", "nav.free": "下載",
"nav.home": "首頁", "nav.home": "首頁",
"nav.openMenu": "開啟選單", "nav.openMenu": "開啟選單",
"nav.getStartedFree": "免費開始使用", "nav.getStartedFree": "免費開始使用",
@@ -542,6 +542,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "目前", "workspace.monthlyLimit.currentUsage.beforeMonth": "目前",
"workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量為 $", "workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量為 $",
"workspace.redeem.title": "兌換優惠券",
"workspace.redeem.subtitle": "兌換優惠碼以領取儲值額度或權益。",
"workspace.redeem.placeholder": "輸入優惠碼",
"workspace.redeem.redeem": "兌換",
"workspace.redeem.redeeming": "兌換中...",
"workspace.redeem.success": "優惠券兌換成功。",
"workspace.reload.title": "自動儲值", "workspace.reload.title": "自動儲值",
"workspace.reload.disabled.before": "自動儲值已", "workspace.reload.disabled.before": "自動儲值已",
"workspace.reload.disabled.state": "停用", "workspace.reload.disabled.state": "停用",
@@ -1,5 +1,6 @@
import type { APIEvent } from "@solidjs/start/server" import type { APIEvent } from "@solidjs/start/server"
import { AWS } from "@opencode-ai/console-core/aws.js" import { AWS } from "@opencode-ai/console-core/aws.js"
import { Resource } from "@opencode-ai/console-resource"
import { i18n } from "~/i18n" import { i18n } from "~/i18n"
import { localeFromRequest } from "~/lib/language" import { localeFromRequest } from "~/lib/language"
import { createLead } from "~/lib/salesforce" import { createLead } from "~/lib/salesforce"
@@ -14,6 +15,64 @@ interface EnterpriseFormData {
message: string message: string
} }
const EMAIL_OCTOPUS_LIST_ID = "1b381e5e-39bd-11f1-ba4a-cdd4791f0c43"
function splitFullName(fullName: string) {
const parts = fullName
.trim()
.split(/\s+/)
.filter((p) => p.length > 0)
if (parts.length === 0) return { firstName: "", lastName: "" }
if (parts.length === 1) return { firstName: parts[0], lastName: "" }
return { firstName: parts[0], lastName: parts.slice(1).join(" ") }
}
function getEmailOctopusApiKey() {
if (process.env.EMAILOCTOPUS_API_KEY) return process.env.EMAILOCTOPUS_API_KEY
try {
return Resource.EMAILOCTOPUS_API_KEY.value
} catch {
return
}
}
function subscribe(email: string, fullName: string) {
const apiKey = getEmailOctopusApiKey()
if (!apiKey) {
console.warn("Skipping EmailOctopus subscribe: missing API key")
return Promise.resolve(false)
}
const name = splitFullName(fullName)
const fields: Record<string, string> = {}
if (name.firstName) fields.FirstName = name.firstName
if (name.lastName) fields.LastName = name.lastName
const payload: { email_address: string; fields?: Record<string, string> } = { email_address: email }
if (Object.keys(fields).length) payload.fields = fields
return fetch(`https://api.emailoctopus.com/lists/${EMAIL_OCTOPUS_LIST_ID}/contacts`, {
method: "PUT",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}).then(
(res) => {
if (!res.ok) {
console.error("EmailOctopus subscribe failed:", res.status, res.statusText)
return false
}
return true
},
(err) => {
console.error("Failed to subscribe enterprise email:", err)
return false
},
)
}
export async function POST(event: APIEvent) { export async function POST(event: APIEvent) {
const dict = i18n(localeFromRequest(event.request)) const dict = i18n(localeFromRequest(event.request))
try { try {
@@ -41,7 +100,7 @@ ${body.role}<br>
${body.company ? `${body.company}<br>` : ""}${body.email}<br> ${body.company ? `${body.company}<br>` : ""}${body.email}<br>
${body.phone ? `${body.phone}<br>` : ""}`.trim() ${body.phone ? `${body.phone}<br>` : ""}`.trim()
const [lead, mail] = await Promise.all([ const [lead, mail, octopus] = await Promise.all([
createLead({ createLead({
name: body.name, name: body.name,
role: body.role, role: body.role,
@@ -49,6 +108,9 @@ ${body.phone ? `${body.phone}<br>` : ""}`.trim()
email: body.email, email: body.email,
phone: body.phone, phone: body.phone,
message: body.message, message: body.message,
}).catch((err) => {
console.error("Failed to create Salesforce lead:", err)
return false
}), }),
AWS.sendEmail({ AWS.sendEmail({
to: "contact@anoma.ly", to: "contact@anoma.ly",
@@ -62,9 +124,14 @@ ${body.phone ? `${body.phone}<br>` : ""}`.trim()
return false return false
}, },
), ),
subscribe(body.email, body.name),
]) ])
if (!lead && !mail) { if (!lead && !mail && !octopus) {
if (import.meta.env.DEV) {
console.warn("Enterprise inquiry accepted in dev mode without integrations", { email: body.email })
return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
}
console.error("Enterprise inquiry delivery failed", { email: body.email }) console.error("Enterprise inquiry delivery failed", { email: body.email })
return Response.json({ error: dict["enterprise.form.error.internalServer"] }, { status: 500 }) return Response.json({ error: dict["enterprise.form.error.internalServer"] }, { status: 500 })
} }
@@ -298,7 +298,7 @@ export default function BlackSubscribe() {
// Resolve stripe promise once // Resolve stripe promise once
createEffect(() => { createEffect(() => {
stripePromise.then((s) => { void stripePromise.then((s) => {
if (s) setStripe(s) if (s) setStripe(s)
}) })
}) })
@@ -1,7 +1,7 @@
import type { APIEvent } from "@solidjs/start" import type { APIEvent } from "@solidjs/start"
import type { DownloadPlatform } from "../types" import type { DownloadPlatform } from "../types"
const assetNames: Record<string, string> = { const prodAssetNames: Record<string, string> = {
"darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg", "darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg",
"darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg", "darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg",
"windows-x64-nsis": "opencode-desktop-windows-x64.exe", "windows-x64-nsis": "opencode-desktop-windows-x64.exe",
@@ -10,6 +10,15 @@ const assetNames: Record<string, string> = {
"linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm", "linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm",
} satisfies Record<DownloadPlatform, string> } satisfies Record<DownloadPlatform, string>
const betaAssetNames: Record<string, string> = {
"darwin-aarch64-dmg": "opencode-electron-mac-arm64.dmg",
"darwin-x64-dmg": "opencode-electron-mac-x64.dmg",
"windows-x64-nsis": "opencode-electron-win-x64.exe",
"linux-x64-deb": "opencode-electron-linux-amd64.deb",
"linux-x64-appimage": "opencode-electron-linux-x86_64.AppImage",
"linux-x64-rpm": "opencode-electron-linux-x86_64.rpm",
} satisfies Record<DownloadPlatform, string>
// Doing this on the server lets us preserve the original name for platforms we don't care to rename for // Doing this on the server lets us preserve the original name for platforms we don't care to rename for
const downloadNames: Record<string, string> = { const downloadNames: Record<string, string> = {
"darwin-aarch64-dmg": "OpenCode Desktop.dmg", "darwin-aarch64-dmg": "OpenCode Desktop.dmg",
@@ -18,7 +27,7 @@ const downloadNames: Record<string, string> = {
} satisfies { [K in DownloadPlatform]?: string } } satisfies { [K in DownloadPlatform]?: string }
export async function GET({ params: { platform, channel } }: APIEvent) { export async function GET({ params: { platform, channel } }: APIEvent) {
const assetName = assetNames[platform] const assetName = channel === "stable" ? prodAssetNames[platform] : betaAssetNames[platform]
if (!assetName) return new Response(null, { status: 404 }) if (!assetName) return new Response(null, { status: 404 })
const resp = await fetch( const resp = await fetch(
@@ -37,5 +46,5 @@ export async function GET({ params: { platform, channel } }: APIEvent) {
const headers = new Headers(resp.headers) const headers = new Headers(resp.headers)
if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`) if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`)
return new Response(resp.body, { ...resp, headers }) return new Response(resp.body, { status: resp.status, statusText: resp.statusText, headers })
} }
@@ -77,7 +77,7 @@ export default function Download() {
const handleCopyClick = (command: string) => (event: Event) => { const handleCopyClick = (command: string) => (event: Event) => {
const button = event.currentTarget as HTMLButtonElement const button = event.currentTarget as HTMLButtonElement
navigator.clipboard.writeText(command) void navigator.clipboard.writeText(command)
button.setAttribute("data-copied", "") button.setAttribute("data-copied", "")
setTimeout(() => { setTimeout(() => {
button.removeAttribute("data-copied") button.removeAttribute("data-copied")
+2 -3
View File
@@ -12,7 +12,6 @@ import { Header } from "~/component/header"
import { Footer } from "~/component/footer" import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal" import { Legal } from "~/component/legal"
import { github } from "~/lib/github" import { github } from "~/lib/github"
import { createMemo } from "solid-js"
import { config } from "~/config" import { config } from "~/config"
import { useI18n } from "~/context/i18n" import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language" import { useLanguage } from "~/context/language"
@@ -30,12 +29,12 @@ function CopyStatus() {
export default function Home() { export default function Home() {
const i18n = useI18n() const i18n = useI18n()
const language = useLanguage() const language = useLanguage()
const githubData = createAsync(() => github()) const _githubData = createAsync(() => github())
const handleCopyClick = (event: Event) => { const handleCopyClick = (event: Event) => {
const button = event.currentTarget as HTMLButtonElement const button = event.currentTarget as HTMLButtonElement
const text = button.textContent const text = button.textContent
if (text) { if (text) {
navigator.clipboard.writeText(text) void navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "") button.setAttribute("data-copied", "")
setTimeout(() => { setTimeout(() => {
button.removeAttribute("data-copied") button.removeAttribute("data-copied")
@@ -9,6 +9,7 @@ import { Actor } from "@opencode-ai/console-core/actor.js"
import { Resource } from "@opencode-ai/console-resource" import { Resource } from "@opencode-ai/console-resource"
import { LiteData } from "@opencode-ai/console-core/lite.js" import { LiteData } from "@opencode-ai/console-core/lite.js"
import { BlackData } from "@opencode-ai/console-core/black.js" import { BlackData } from "@opencode-ai/console-core/black.js"
import { User } from "@opencode-ai/console-core/user.js"
export async function POST(input: APIEvent) { export async function POST(input: APIEvent) {
const body = await Billing.stripe().webhooks.constructEventAsync( const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -109,6 +110,8 @@ export async function POST(input: APIEvent) {
if (type === "lite") { if (type === "lite") {
const workspaceID = body.data.object.metadata?.workspaceID const workspaceID = body.data.object.metadata?.workspaceID
const userID = body.data.object.metadata?.userID const userID = body.data.object.metadata?.userID
const userEmail = body.data.object.metadata?.userEmail
const coupon = body.data.object.metadata?.coupon
const customerID = body.data.object.customer as string const customerID = body.data.object.customer as string
const invoiceID = body.data.object.latest_invoice as string const invoiceID = body.data.object.latest_invoice as string
const subscriptionID = body.data.object.id as string const subscriptionID = body.data.object.id as string
@@ -156,6 +159,10 @@ export async function POST(input: APIEvent) {
id: Identifier.create("lite"), id: Identifier.create("lite"),
userID: userID, userID: userID,
}) })
if (userEmail && coupon === LiteData.firstMonth100Coupon) {
await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
}
}) })
}) })
} }
+1 -1
View File
@@ -27,7 +27,7 @@ export default function Home() {
const callback = () => { const callback = () => {
const text = button.textContent const text = button.textContent
if (text) { if (text) {
navigator.clipboard.writeText(text) void navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "") button.setAttribute("data-copied", "")
setTimeout(() => { setTimeout(() => {
button.removeAttribute("data-copied") button.removeAttribute("data-copied")
@@ -116,9 +116,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setUseBalance = action(async (form: FormData) => { const setUseBalance = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const useBalance = form.get("useBalance")?.toString() === "true" const useBalance = (form.get("useBalance") as string | null) === "true"
return json( return json(
await withActor(async () => { await withActor(async () => {
@@ -3,6 +3,7 @@ import { BillingSection } from "./billing-section"
import { ReloadSection } from "./reload-section" import { ReloadSection } from "./reload-section"
import { PaymentSection } from "./payment-section" import { PaymentSection } from "./payment-section"
import { BlackSection } from "./black-section" import { BlackSection } from "./black-section"
import { RedeemSection } from "./redeem-section"
import { createMemo, Show } from "solid-js" import { createMemo, Show } from "solid-js"
import { createAsync, useParams } from "@solidjs/router" import { createAsync, useParams } from "@solidjs/router"
import { queryBillingInfo, querySessionInfo } from "../../common" import { queryBillingInfo, querySessionInfo } from "../../common"
@@ -21,6 +22,7 @@ export default function () {
<BlackSection /> <BlackSection />
</Show> </Show>
<BillingSection /> <BillingSection />
<RedeemSection />
<Show when={billingInfo()?.customerID}> <Show when={billingInfo()?.customerID}>
<ReloadSection /> <ReloadSection />
<MonthlyLimitSection /> <MonthlyLimitSection />
@@ -10,11 +10,11 @@ import { formError, localizeError } from "~/lib/form-error"
const setMonthlyLimit = action(async (form: FormData) => { const setMonthlyLimit = action(async (form: FormData) => {
"use server" "use server"
const limit = form.get("limit")?.toString() const limit = form.get("limit") as string | null
if (!limit) return { error: formError.limitRequired } if (!limit) return { error: formError.limitRequired }
const numericLimit = parseInt(limit) const numericLimit = parseInt(limit)
if (numericLimit < 0) return { error: formError.monthlyLimitInvalid } if (numericLimit < 0) return { error: formError.monthlyLimitInvalid }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -0,0 +1,61 @@
.root {
[data-slot="redeem-container"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
min-width: 20rem;
width: fit-content;
@media (max-width: 30rem) {
width: 100%;
}
}
[data-slot="redeem-form"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
[data-slot="input-row"] {
display: flex;
gap: var(--space-2);
align-items: stretch;
@media (max-width: 30rem) {
flex-direction: column;
}
}
input {
flex: 1;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-mono);
&:focus {
outline: none;
border-color: var(--color-accent);
}
&::placeholder {
color: var(--color-text-disabled);
}
}
[data-slot="form-error"] {
color: var(--color-danger);
font-size: var(--font-size-sm);
line-height: 1.4;
}
[data-slot="form-success"] {
color: var(--color-success, var(--color-accent));
font-size: var(--font-size-sm);
line-height: 1.4;
}
}
}
@@ -0,0 +1,71 @@
import { json, action, useParams, useSubmission } from "@solidjs/router"
import { Show } from "solid-js"
import { withActor } from "~/context/auth.withActor"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { User } from "@opencode-ai/console-core/user.js"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { CouponType } from "@opencode-ai/console-core/schema/billing.sql.js"
import styles from "./redeem-section.module.css"
import { queryBillingInfo } from "../../common"
import { useI18n } from "~/context/i18n"
import { formError, localizeError } from "~/lib/form-error"
const redeem = action(async (form: FormData) => {
"use server"
const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
const code = (form.get("code") as string | null)?.trim().toUpperCase()
if (!code) return { error: "Coupon code is required." }
if (!(CouponType as readonly string[]).includes(code)) return { error: "Invalid coupon code." }
return json(
await withActor(async () => {
const actor = Actor.assert("user")
const email = await User.getAuthEmail(actor.properties.userID)
if (!email) return { error: "No email on account." }
return Billing.redeemCoupon(email, code as (typeof CouponType)[number])
.then(() => ({ error: undefined, data: true }))
.catch((e) => ({ error: e.message as string }))
}, workspaceID),
{ revalidate: queryBillingInfo.key },
)
}, "billing.redeemCoupon")
export function RedeemSection() {
const params = useParams()
const i18n = useI18n()
const submission = useSubmission(redeem)
return (
<section class={styles.root}>
<div data-slot="section-title">
<h2>{i18n.t("workspace.redeem.title")}</h2>
<p>{i18n.t("workspace.redeem.subtitle")}</p>
</div>
<div data-slot="redeem-container">
<form action={redeem} method="post" data-slot="redeem-form">
<div data-slot="input-row">
<input
required
data-component="input"
name="code"
type="text"
autocomplete="off"
placeholder={i18n.t("workspace.redeem.placeholder")}
/>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? i18n.t("workspace.redeem.redeeming") : i18n.t("workspace.redeem.redeem")}
</button>
</div>
<Show when={submission.result && (submission.result as any).error}>
{(err: any) => <div data-slot="form-error">{localizeError(i18n.t, err())}</div>}
</Show>
<Show when={submission.result && !(submission.result as any).error && (submission.result as any).data}>
<div data-slot="form-success">{i18n.t("workspace.redeem.success")}</div>
</Show>
<input type="hidden" name="workspaceID" value={params.id} />
</form>
</div>
</section>
)
}
@@ -12,7 +12,7 @@ import { formError, formErrorReloadAmountMin, formErrorReloadTriggerMin, localiz
const reload = action(async (form: FormData) => { const reload = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Billing.reload(), workspaceID), { return json(await withActor(() => Billing.reload(), workspaceID), {
revalidate: queryBillingInfo.key, revalidate: queryBillingInfo.key,
@@ -21,11 +21,11 @@ const reload = action(async (form: FormData) => {
const setReload = action(async (form: FormData) => { const setReload = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const reloadValue = form.get("reload")?.toString() === "true" const reloadValue = (form.get("reload") as string | null) === "true"
const amountStr = form.get("reloadAmount")?.toString() const amountStr = form.get("reloadAmount") as string | null
const triggerStr = form.get("reloadTrigger")?.toString() const triggerStr = form.get("reloadTrigger") as string | null
const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
@@ -91,8 +91,8 @@ export function ReloadSection() {
const info = billingInfo()! const info = billingInfo()!
setStore("show", true) setStore("show", true)
setStore("reload", true) setStore("reload", true)
setStore("reloadAmount", info.reloadAmount.toString()) setStore("reloadAmount", String(info.reloadAmount))
setStore("reloadTrigger", info.reloadTrigger.toString()) setStore("reloadTrigger", String(info.reloadTrigger))
} }
function hide() { function hide() {
@@ -152,11 +152,11 @@ export function ReloadSection() {
data-component="input" data-component="input"
name="reloadAmount" name="reloadAmount"
type="number" type="number"
min={billingInfo()?.reloadAmountMin.toString()} min={String(billingInfo()?.reloadAmountMin ?? "")}
step="1" step="1"
value={store.reloadAmount} value={store.reloadAmount}
onInput={(e) => setStore("reloadAmount", e.currentTarget.value)} onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
placeholder={billingInfo()?.reloadAmount.toString()} placeholder={String(billingInfo()?.reloadAmount ?? "")}
disabled={!store.reload} disabled={!store.reload}
/> />
</div> </div>
@@ -166,11 +166,11 @@ export function ReloadSection() {
data-component="input" data-component="input"
name="reloadTrigger" name="reloadTrigger"
type="number" type="number"
min={billingInfo()?.reloadTriggerMin.toString()} min={String(billingInfo()?.reloadTriggerMin ?? "")}
step="1" step="1"
value={store.reloadTrigger} value={store.reloadTrigger}
onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)} onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
placeholder={billingInfo()?.reloadTrigger.toString()} placeholder={String(billingInfo()?.reloadTrigger ?? "")}
disabled={!store.reload} disabled={!store.reload}
/> />
</div> </div>
@@ -120,9 +120,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setLiteUseBalance = action(async (form: FormData) => { const setLiteUseBalance = action(async (form: FormData) => {
"use server" "use server"
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const useBalance = form.get("useBalance")?.toString() === "true" const useBalance = (form.get("useBalance") as string | null) === "true"
return json( return json(
await withActor(async () => { await withActor(async () => {
@@ -12,18 +12,18 @@ import { formError, localizeError } from "~/lib/form-error"
const removeKey = action(async (form: FormData) => { const removeKey = action(async (form: FormData) => {
"use server" "use server"
const id = form.get("id")?.toString() const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired } if (!id) return { error: formError.idRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key }) return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
}, "key.remove") }, "key.remove")
const createKey = action(async (form: FormData) => { const createKey = action(async (form: FormData) => {
"use server" "use server"
const name = form.get("name")?.toString().trim() const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.nameRequired } if (!name) return { error: formError.nameRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -24,13 +24,13 @@ const listMembers = query(async (workspaceID: string) => {
const inviteMember = action(async (form: FormData) => { const inviteMember = action(async (form: FormData) => {
"use server" "use server"
const email = form.get("email")?.toString().trim() const email = (form.get("email") as string | null)?.trim()
if (!email) return { error: formError.emailRequired } if (!email) return { error: formError.emailRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const role = form.get("role")?.toString() as (typeof UserRole)[number] const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired } if (!role) return { error: formError.roleRequired }
const limit = form.get("limit")?.toString() const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
return json( return json(
@@ -47,9 +47,9 @@ const inviteMember = action(async (form: FormData) => {
const removeMember = action(async (form: FormData) => { const removeMember = action(async (form: FormData) => {
"use server" "use server"
const id = form.get("id")?.toString() const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired } if (!id) return { error: formError.idRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -66,13 +66,13 @@ const removeMember = action(async (form: FormData) => {
const updateMember = action(async (form: FormData) => { const updateMember = action(async (form: FormData) => {
"use server" "use server"
const id = form.get("id")?.toString() const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired } if (!id) return { error: formError.idRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const role = form.get("role")?.toString() as (typeof UserRole)[number] const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired } if (!role) return { error: formError.roleRequired }
const limit = form.get("limit")?.toString() const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid } if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
@@ -118,7 +118,7 @@ function MemberRow(props: {
} }
setStore("editing", true) setStore("editing", true)
setStore("selectedRole", props.member.role) setStore("selectedRole", props.member.role)
setStore("limit", props.member.monthlyLimit?.toString() ?? "") setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "")
} }
function hide() { function hide() {
@@ -67,11 +67,11 @@ const getModelsInfo = query(async (workspaceID: string) => {
const updateModel = action(async (form: FormData) => { const updateModel = action(async (form: FormData) => {
"use server" "use server"
const model = form.get("model")?.toString() const model = form.get("model") as string | null
if (!model) return { error: formError.modelRequired } if (!model) return { error: formError.modelRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
const enabled = form.get("enabled")?.toString() === "true" const enabled = (form.get("enabled") as string | null) === "true"
return json( return json(
withActor(async () => { withActor(async () => {
if (enabled) { if (enabled) {
@@ -163,7 +163,7 @@ export function ModelSection() {
<form action={updateModel} method="post"> <form action={updateModel} method="post">
<input type="hidden" name="model" value={id} /> <input type="hidden" name="model" value={id} />
<input type="hidden" name="workspaceID" value={params.id} /> <input type="hidden" name="workspaceID" value={params.id} />
<input type="hidden" name="enabled" value={isEnabled().toString()} /> <input type="hidden" name="enabled" value={String(isEnabled())} />
<label data-slot="model-toggle-label"> <label data-slot="model-toggle-label">
<input <input
type="checkbox" type="checkbox"
@@ -21,9 +21,9 @@ function maskCredentials(credentials: string) {
const removeProvider = action(async (form: FormData) => { const removeProvider = action(async (form: FormData) => {
"use server" "use server"
const provider = form.get("provider")?.toString() const provider = form.get("provider") as string | null
if (!provider) return { error: formError.providerRequired } if (!provider) return { error: formError.providerRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Provider.remove({ provider }), workspaceID), { return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
revalidate: listProviders.key, revalidate: listProviders.key,
@@ -32,11 +32,11 @@ const removeProvider = action(async (form: FormData) => {
const saveProvider = action(async (form: FormData) => { const saveProvider = action(async (form: FormData) => {
"use server" "use server"
const provider = form.get("provider")?.toString() const provider = form.get("provider") as string | null
const credentials = form.get("credentials")?.toString() const credentials = form.get("credentials") as string | null
if (!provider) return { error: formError.providerRequired } if (!provider) return { error: formError.providerRequired }
if (!credentials) return { error: formError.apiKeyRequired } if (!credentials) return { error: formError.apiKeyRequired }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -59,10 +59,13 @@ function ProviderRow(props: { provider: Provider }) {
const params = useParams() const params = useParams()
const i18n = useI18n() const i18n = useI18n()
const providers = createAsync(() => listProviders(params.id!)) const providers = createAsync(() => listProviders(params.id!))
const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key) const saveSubmission = useSubmission(
saveProvider,
([fd]) => (fd.get("provider") as string | null) === props.provider.key,
)
const removeSubmission = useSubmission( const removeSubmission = useSubmission(
removeProvider, removeProvider,
([fd]) => fd.get("provider")?.toString() === props.provider.key, ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
) )
const [store, setStore] = createStore({ editing: false }) const [store, setStore] = createStore({ editing: false })
@@ -30,10 +30,10 @@ const getWorkspaceInfo = query(async (workspaceID: string) => {
const updateWorkspace = action(async (form: FormData) => { const updateWorkspace = action(async (form: FormData) => {
"use server" "use server"
const name = form.get("name")?.toString().trim() const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.workspaceNameRequired } if (!name) return { error: formError.workspaceNameRequired }
if (name.length > 255) return { error: formError.nameTooLong } if (name.length > 255) return { error: formError.nameTooLong }
const workspaceID = form.get("workspaceID")?.toString() const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired } if (!workspaceID) return { error: formError.workspaceRequired }
return json( return json(
await withActor( await withActor(
@@ -26,14 +26,14 @@ export function createDataDumper(sessionId: string, requestId: string, projectId
const minute = timestamp.substring(10, 12) const minute = timestamp.substring(10, 12)
const second = timestamp.substring(12, 14) const second = timestamp.substring(12, 14)
waitUntil( void waitUntil(
Resource.ZenDataNew.put( Resource.ZenDataNew.put(
`data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`, `data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`,
JSON.stringify({ timestamp, ...data }), JSON.stringify({ timestamp, ...data }),
), ),
) )
waitUntil( void waitUntil(
Resource.ZenDataNew.put( Resource.ZenDataNew.put(
`meta/${data.modelName}/${sessionId}/${requestId}.json`, `meta/${data.modelName}/${sessionId}/${requestId}.json`,
JSON.stringify({ timestamp, ...metadata }), JSON.stringify({ timestamp, ...metadata }),
@@ -45,6 +45,7 @@ import { LiteData } from "@opencode-ai/console-core/lite.js"
import { Resource } from "@opencode-ai/console-resource" import { Resource } from "@opencode-ai/console-resource"
import { i18n, type Key } from "~/i18n" import { i18n, type Key } from "~/i18n"
import { localeFromRequest } from "~/lib/language" import { localeFromRequest } from "~/lib/language"
import { createModelTpmLimiter } from "./modelTpmLimiter"
type ZenData = Awaited<ReturnType<typeof ZenData.list>> type ZenData = Awaited<ReturnType<typeof ZenData.list>>
type RetryOptions = { type RetryOptions = {
@@ -121,6 +122,8 @@ export async function handler(
const authInfo = await authenticate(modelInfo, zenApiKey) const authInfo = await authenticate(modelInfo, zenApiKey)
const billingSource = validateBilling(authInfo, modelInfo) const billingSource = validateBilling(authInfo, modelInfo)
logger.metric({ source: billingSource }) logger.metric({ source: billingSource })
const modelTpmLimiter = createModelTpmLimiter(modelInfo.providers)
const modelTpmLimits = await modelTpmLimiter?.check()
const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => { const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
const providerInfo = selectProvider( const providerInfo = selectProvider(
@@ -133,6 +136,7 @@ export async function handler(
trialProviders, trialProviders,
retry, retry,
stickyProvider, stickyProvider,
modelTpmLimits,
) )
validateModelSettings(billingSource, authInfo) validateModelSettings(billingSource, authInfo)
updateProviderKey(authInfo, providerInfo) updateProviderKey(authInfo, providerInfo)
@@ -229,6 +233,7 @@ export async function handler(
const usageInfo = providerInfo.normalizeUsage(json.usage) const usageInfo = providerInfo.normalizeUsage(json.usage)
const costInfo = calculateCost(modelInfo, usageInfo) const costInfo = calculateCost(modelInfo, usageInfo)
await trialLimiter?.track(usageInfo) await trialLimiter?.track(usageInfo)
await modelTpmLimiter?.track(providerInfo.id, providerInfo.model, usageInfo)
await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo) await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
await reload(billingSource, authInfo, costInfo) await reload(billingSource, authInfo, costInfo)
json.cost = calculateOccurredCost(billingSource, costInfo) json.cost = calculateOccurredCost(billingSource, costInfo)
@@ -278,6 +283,7 @@ export async function handler(
const usageInfo = providerInfo.normalizeUsage(usage) const usageInfo = providerInfo.normalizeUsage(usage)
const costInfo = calculateCost(modelInfo, usageInfo) const costInfo = calculateCost(modelInfo, usageInfo)
await trialLimiter?.track(usageInfo) await trialLimiter?.track(usageInfo)
await modelTpmLimiter?.track(providerInfo.id, providerInfo.model, usageInfo)
await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo) await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
await reload(billingSource, authInfo, costInfo) await reload(billingSource, authInfo, costInfo)
const cost = calculateOccurredCost(billingSource, costInfo) const cost = calculateOccurredCost(billingSource, costInfo)
@@ -433,12 +439,16 @@ export async function handler(
trialProviders: string[] | undefined, trialProviders: string[] | undefined,
retry: RetryOptions, retry: RetryOptions,
stickyProvider: string | undefined, stickyProvider: string | undefined,
modelTpmLimits: Record<string, number> | undefined,
) { ) {
const modelProvider = (() => { const modelProvider = (() => {
// Byok is top priority b/c if user set their own API key, we should use it
// instead of using the sticky provider for the same session
if (authInfo?.provider?.credentials) { if (authInfo?.provider?.credentials) {
return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider) return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
} }
// Always use the same provider for the same session
if (stickyProvider) { if (stickyProvider) {
const provider = modelInfo.providers.find((provider) => provider.id === stickyProvider) const provider = modelInfo.providers.find((provider) => provider.id === stickyProvider)
if (provider) return provider if (provider) return provider
@@ -451,10 +461,20 @@ export async function handler(
} }
if (retry.retryCount !== MAX_FAILOVER_RETRIES) { if (retry.retryCount !== MAX_FAILOVER_RETRIES) {
const providers = modelInfo.providers const allProviders = modelInfo.providers
.filter((provider) => !provider.disabled) .filter((provider) => !provider.disabled)
.filter((provider) => provider.weight !== 0)
.filter((provider) => !retry.excludeProviders.includes(provider.id)) .filter((provider) => !retry.excludeProviders.includes(provider.id))
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider)) .filter((provider) => {
if (!provider.tpmLimit) return true
const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0
return usage < provider.tpmLimit * 1_000_000
})
const topPriority = Math.min(...allProviders.map((p) => p.priority))
const providers = allProviders
.filter((p) => p.priority <= topPriority)
.flatMap((provider) => Array<typeof provider>(provider.weight).fill(provider))
// Use the last 4 characters of session ID to select a provider // Use the last 4 characters of session ID to select a provider
const identifier = sessionId.length ? sessionId : ip const identifier = sessionId.length ? sessionId : ip
@@ -742,7 +762,8 @@ export async function handler(
const billing = authInfo.billing const billing = authInfo.billing
const billingUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/billing` const billingUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/billing`
const membersUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/members` const membersUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/members`
if (!billing.paymentMethodID) throw new CreditsError(t("zen.api.error.noPaymentMethod", { billingUrl })) if (!billing.paymentMethodID && billing.balance <= 0)
throw new CreditsError(t("zen.api.error.noPaymentMethod", { billingUrl }))
if (billing.balance <= 0) throw new CreditsError(t("zen.api.error.insufficientBalance", { billingUrl })) if (billing.balance <= 0) throw new CreditsError(t("zen.api.error.insufficientBalance", { billingUrl }))
const now = new Date() const now = new Date()

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