mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-09 19:09:49 -04:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1fc811394 | |||
| b8374b5a7c | |||
| b4665a8bf8 | |||
| 08096b5e61 | |||
| 2e43d41af0 | |||
| 518772c2ba | |||
| fc9021ccec | |||
| 3bebfe6b1b | |||
| 3b18c64782 | |||
| c6c599b872 | |||
| 6b41ae910c | |||
| 5c860d4142 | |||
| 4f9207daac | |||
| ae7d63272c | |||
| 5cc3a51357 | |||
| e357c90212 |
@@ -29,8 +29,9 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
"@dnd-kit/dom": "0.5.0",
|
||||
"@dnd-kit/helpers": "0.5.0",
|
||||
@@ -94,7 +95,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"bin": {
|
||||
"lildax": "./bin/lildax.cjs",
|
||||
},
|
||||
@@ -142,7 +143,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -156,7 +157,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -192,7 +193,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -219,7 +220,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -241,7 +242,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -265,7 +266,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
@@ -285,7 +286,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -379,7 +380,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"effect": "catalog:",
|
||||
@@ -433,7 +434,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -447,7 +448,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -459,7 +460,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -491,7 +492,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -507,7 +508,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||
@@ -538,7 +539,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
@@ -557,7 +558,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -688,7 +689,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -764,7 +765,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -779,7 +780,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
@@ -794,7 +795,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -838,7 +839,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -851,7 +852,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@opencode-ai/stats-core": "workspace:*",
|
||||
@@ -884,7 +885,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -903,7 +904,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -944,7 +945,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
@@ -971,7 +972,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -1022,7 +1023,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1087,6 +1088,7 @@
|
||||
},
|
||||
"catalog": {
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@corvu/drawer": "0.2.4",
|
||||
"@effect/opentelemetry": "4.0.0-beta.83",
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/sql-sqlite-bun": "4.0.0-beta.83",
|
||||
@@ -1468,6 +1470,10 @@
|
||||
|
||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="],
|
||||
|
||||
"@corvu/dialog": ["@corvu/dialog@0.2.4", "", { "dependencies": { "@corvu/utils": "~0.4.2", "solid-dismissible": "~0.1.1", "solid-focus-trap": "~0.1.8", "solid-presence": "~0.2.0", "solid-prevent-scroll": "~0.1.10" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-n54vJq+fOy8GVrnYBdJpD6JXNuyx7LOeMrRxwzAvZnYGpW8+AA12tnb/P/2emJj/HjOO5otheGKb0breshdFlA=="],
|
||||
|
||||
"@corvu/drawer": ["@corvu/drawer@0.2.4", "", { "dependencies": { "@corvu/dialog": "~0.2.4", "@corvu/utils": "~0.4.2", "@solid-primitives/memo": "^1.4.1", "solid-transition-size": "~0.1.4" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-7jQoGZ8ROB9CmXam2nMY2wEskU3IoFwZQywkF/7vrBc/edGsPv7mOVQ1GN6G+4nd7nrMZ3UtqHAhmRh9V0azlw=="],
|
||||
|
||||
"@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="],
|
||||
|
||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||
@@ -2668,6 +2674,8 @@
|
||||
|
||||
"@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="],
|
||||
|
||||
"@solid-primitives/memo": ["@solid-primitives/memo@1.5.0", "", { "dependencies": { "@solid-primitives/scheduled": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-nSMHpFdnOMP88t7lqtktUXJlhQdJk0BQs2v3jUqcn+OtFwUm27Oa/coKl1BHDxL/65jR5drjqxxvuhaIpiUi0w=="],
|
||||
|
||||
"@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="],
|
||||
|
||||
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="],
|
||||
@@ -5100,6 +5108,10 @@
|
||||
|
||||
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
|
||||
|
||||
"solid-dismissible": ["solid-dismissible@0.1.1", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-9kcKBJIMdS+586cA1g63HYWxKh3h89leeNHbPZ1csYjuni+NvPBtNr11l0iEX2AKKEt6FHk6qNhc/gjoYAW1pA=="],
|
||||
|
||||
"solid-focus-trap": ["solid-focus-trap@0.1.9", "", { "dependencies": { "@corvu/utils": "~0.4.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-LTyNki6GUJPRLXV5uMWPkYClB07SUMubbr2EkAddiR0CJCF/I283txilMU9RURSr/P8EewMfXWu2o3aWrK7A5A=="],
|
||||
|
||||
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
|
||||
|
||||
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
|
||||
@@ -5112,6 +5124,8 @@
|
||||
|
||||
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
|
||||
|
||||
"solid-transition-size": ["solid-transition-size@0.1.4", "", { "dependencies": { "@corvu/utils": "~0.3.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ocHVnbfy23CgfaH4cEUR/AFg0Y3CEL8Oh3n9Qv8OHFJgPh+zkmERKZQfi/xH5XvxDCizg8VjPrVUhiHB1Gza8g=="],
|
||||
|
||||
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
|
||||
|
||||
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
|
||||
@@ -6450,6 +6464,8 @@
|
||||
|
||||
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||
|
||||
"solid-transition-size/@corvu/utils": ["@corvu/utils@0.3.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.7" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ZWlyWEE8qV9+CB9OAyo2bTrZGXQN9ZeM+JfYv89zoR+lRACKTDuoOZEdiyL8Uc7U5dUSH1uTqKhTTnaHWb+wZA=="],
|
||||
|
||||
"sort-keys/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
|
||||
|
||||
"sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-DDHxztBr46Jxrb0uYvLYSezrqMgY+UrozRHvKdEyeXY=",
|
||||
"aarch64-linux": "sha256-Dp2ohpv1jXcFX2ri4fVmsnC9WEx60bviUSlcOtIJBmE=",
|
||||
"aarch64-darwin": "sha256-CXFJmkcRAS+liHf9TG5qoUG08VvKe5RusEGZTlnNer4=",
|
||||
"x86_64-darwin": "sha256-7iuCfAzggMDyJ7tX8UuS7T7DCbr9h+lskE+HVFUaD0Q="
|
||||
"x86_64-linux": "sha256-JTtn+wXTXg+yklvIMDLcGFaYhTU6ZrCgKT9JTNEQ3gA=",
|
||||
"aarch64-linux": "sha256-gXU6zyhvAZrZirkL/PlHdkHtEof/7PVSPCaE34Jnd4U=",
|
||||
"aarch64-darwin": "sha256-Q0oTG3uzOlD/X2kJingLle529lKFoTpyCW2rHXOZ6iE=",
|
||||
"x86_64-darwin": "sha256-LINvKHxPibTlJeNzfACQx0x+Yj5oROT6Du3I5AtqqXk="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"@shikijs/stream": "4.2.0",
|
||||
"ulid": "3.0.1",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@corvu/drawer": "0.2.4",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "24.12.2",
|
||||
"@types/semver": "7.7.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -47,6 +47,7 @@
|
||||
"vite-plugin-solid": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
"@dnd-kit/dom": "0.5.0",
|
||||
"@dnd-kit/helpers": "0.5.0",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,173 @@
|
||||
import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
|
||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
|
||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { createMemo, onCleanup, onMount, type Component, For, Show } from "solid-js"
|
||||
import { useLocal } from "@/context/local"
|
||||
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { ModelTooltip } from "./model-tooltip"
|
||||
|
||||
type ModelState = ReturnType<typeof useLocal>["model"]
|
||||
|
||||
export const DialogSelectModelUnpaidV2: Component<{ model?: ModelState }> = (props) => {
|
||||
const local = useLocal()
|
||||
const model = props.model ?? local.model
|
||||
const dialog = useDialog()
|
||||
const directory = () => decode64(local.slug())
|
||||
const providers = useProviders(directory)
|
||||
const language = useLanguage()
|
||||
const modelKey = (item: ReturnType<ModelState["list"]>[number]) => `${item.provider.id}:${item.id}`
|
||||
const currentKey = createMemo(() => {
|
||||
const c = model.current()
|
||||
return c ? `${c.provider.id}:${c.id}` : undefined
|
||||
})
|
||||
const isFree = (item: ReturnType<ModelState["list"]>[number]) =>
|
||||
item.provider.id === "opencode" && (!item.cost || item.cost.input === 0)
|
||||
|
||||
const openProviders = (provider?: string) => {
|
||||
void import("./dialog-connect-provider").then((x) => {
|
||||
const controller = x.useProviderConnectController()
|
||||
controller.select(provider)
|
||||
void dialog.show(() => <x.DialogConnectProvider controller={controller} directory={directory} />)
|
||||
})
|
||||
}
|
||||
|
||||
const selectModel = (item: ReturnType<ModelState["list"]>[number]) => {
|
||||
model.set({ modelID: item.id, providerID: item.provider.id }, { recent: true })
|
||||
dialog.close()
|
||||
}
|
||||
|
||||
// Focus starts on the dialog's close button, outside the list, so listen at the
|
||||
// document level while the dialog is mounted instead of on the list container.
|
||||
let listEl: HTMLDivElement | undefined
|
||||
onMount(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return
|
||||
if (!listEl) return
|
||||
const buttons = Array.from(listEl.querySelectorAll<HTMLButtonElement>("button"))
|
||||
if (buttons.length === 0) return
|
||||
const index = buttons.indexOf(document.activeElement as HTMLButtonElement)
|
||||
const next =
|
||||
index < 0 ? (e.key === "ArrowDown" ? 0 : buttons.length - 1) : index + (e.key === "ArrowDown" ? 1 : -1)
|
||||
buttons[(next + buttons.length) % buttons.length]?.focus()
|
||||
e.preventDefault()
|
||||
}
|
||||
document.addEventListener("keydown", handleKeyDown)
|
||||
onCleanup(() => document.removeEventListener("keydown", handleKeyDown))
|
||||
})
|
||||
|
||||
return (
|
||||
<DialogV2 containerClass="!h-[min(calc(100vh_-_16px),480px)] !w-[min(calc(100vw_-_16px),560px)]">
|
||||
<DialogHeader closeLabel={language.t("common.close")}>
|
||||
<DialogTitle>{language.t("dialog.model.select.title")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="h-px w-full shrink-0 bg-v2-border-border-muted" />
|
||||
<DialogBody class="min-h-0 flex-1 gap-0">
|
||||
<ScrollView class="min-h-0 flex-1 w-full">
|
||||
<div ref={listEl} class="flex min-h-full flex-col">
|
||||
<div class="flex h-fit w-full flex-col items-start gap-0.5 px-3.5 pb-3.5 pt-3">
|
||||
<div class="flex h-8 w-full flex-none select-none flex-row items-center gap-2 self-stretch px-2.5 pb-2 pt-1">
|
||||
<div class="flex h-5 flex-none flex-row items-center p-0 font-[440] text-[13px] leading-5 tracking-[-0.04px] text-v2-text-text-faint [font-family:Inter,var(--font-family-sans)] [font-variant-numeric:tabular-nums] [font-variation-settings:'slnt'_0]">
|
||||
{language.t("dialog.model.unpaid.freeModels.title")}
|
||||
</div>
|
||||
</div>
|
||||
<For each={model.list()}>
|
||||
{(item) => (
|
||||
<TooltipV2
|
||||
class="w-full"
|
||||
placement="right-start"
|
||||
gutter={6}
|
||||
openDelay={0}
|
||||
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item)} v2 />}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full scroll-my-3.5 flex-row items-center gap-2 rounded-md px-2.5 py-2 text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
|
||||
onClick={() => selectModel(item)}
|
||||
>
|
||||
<span class="min-w-0 truncate">{item.name}</span>
|
||||
<Show when={isFree(item)}>
|
||||
<Tag class="shrink-0">{language.t("model.tag.free")}</Tag>
|
||||
</Show>
|
||||
<Show when={item.latest}>
|
||||
<Tag class="shrink-0">{language.t("model.tag.latest")}</Tag>
|
||||
</Show>
|
||||
<Show when={currentKey() === modelKey(item)}>
|
||||
<Icon name="check" class="ml-auto size-4 shrink-0 text-v2-icon-icon-base" />
|
||||
</Show>
|
||||
</button>
|
||||
</TooltipV2>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-col p-2.5 pt-0">
|
||||
<div class="flex h-fit w-full flex-none grow-0 flex-col items-start gap-0.5 self-stretch rounded-lg bg-v2-background-bg-layer-02 p-1 shadow-[var(--v2-elevation-switch-off)]">
|
||||
<div class="flex h-8 w-full flex-none select-none flex-row items-center gap-2 self-stretch px-2.5 py-1.5">
|
||||
<div class="flex h-5 flex-none flex-row items-center p-0 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint [font-family:Inter,var(--font-family-sans)] [font-variant-numeric:tabular-nums] [font-variation-settings:'slnt'_0]">
|
||||
{language.t("dialog.model.unpaid.addMore.title")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex w-full flex-col">
|
||||
<For
|
||||
each={[...providers.popular()].sort((a, b) => {
|
||||
if (popularProviders.includes(a.id) && popularProviders.includes(b.id)) {
|
||||
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
|
||||
}
|
||||
return a.name.localeCompare(b.name)
|
||||
})}
|
||||
>
|
||||
{(provider) => (
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full scroll-my-3.5 flex-row items-center gap-2 rounded-[6px] px-2.5 py-2 text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
|
||||
onClick={() => openProviders(provider.id)}
|
||||
>
|
||||
<ProviderIcon id={provider.id} class="size-4 shrink-0 text-v2-icon-icon-muted" />
|
||||
<span class="min-w-0 truncate">{provider.name}</span>
|
||||
<Show when={provider.id === "opencode"}>
|
||||
<span class="min-w-0 truncate text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
|
||||
{language.t("dialog.provider.opencode.tagline")}
|
||||
</span>
|
||||
<Tag class="shrink-0">{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||
</Show>
|
||||
<Show when={provider.id === "opencode-go"}>
|
||||
<span class="min-w-0 truncate text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
|
||||
{language.t("dialog.provider.opencodeGo.tagline")}
|
||||
</span>
|
||||
<Tag class="shrink-0">{language.t("dialog.provider.tag.recommended")}</Tag>
|
||||
</Show>
|
||||
<Show when={provider.id === "anthropic"}>
|
||||
<span class="min-w-0 truncate text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
|
||||
{language.t("dialog.provider.anthropic.note")}
|
||||
</span>
|
||||
</Show>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-9 w-full scroll-my-3.5 flex-row items-center justify-start gap-2 rounded-[6px] px-2.5 py-2 text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0] hover:bg-v2-overlay-simple-overlay-hover focus:bg-v2-overlay-simple-overlay-hover focus:outline-none"
|
||||
onClick={() => openProviders()}
|
||||
>
|
||||
<span class="flex size-4 shrink-0 items-center justify-center text-v2-icon-icon-muted">
|
||||
<Icon name="dot-grid" size="small" />
|
||||
</span>
|
||||
<span class="min-w-0 truncate text-left text-[13px] font-[530] leading-5 tracking-[-0.04px] text-v2-text-text-base [font-family:Inter,var(--font-family-sans)] [font-variation-settings:'slnt'_0]">
|
||||
{language.t("dialog.provider.viewAll")}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollView>
|
||||
</DialogBody>
|
||||
</DialogV2>
|
||||
)
|
||||
}
|
||||
@@ -474,7 +474,7 @@ export function ModelSelectorPopoverV2(props: {
|
||||
}}
|
||||
onSelect={() => selectModel(item)}
|
||||
>
|
||||
<span class="min-w-0 truncate">{item.name}</span>
|
||||
<span class="min-w-0 truncate leading-5">{item.name}</span>
|
||||
<Show when={isFree(item.provider.id, item.cost)}>
|
||||
<TagV2 class="shrink-0">{language.t("model.tag.free")}</TagV2>
|
||||
</Show>
|
||||
|
||||
@@ -1,54 +1,144 @@
|
||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||
import { Popover } from "@opencode-ai/ui/popover"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { createSignal, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
|
||||
const helpIcon = (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
data-slot="icon-svg"
|
||||
>
|
||||
<path
|
||||
d="M6.94235 10.5714V10.4854C6.94617 9.76302 7.01879 9.18777 7.16022 8.75968C7.30546 8.33158 7.50804 7.98567 7.76796 7.72193C8.02787 7.45819 8.34321 7.21548 8.71397 6.99379C8.93948 6.85619 9.14206 6.69374 9.32171 6.50645C9.50518 6.31916 9.64851 6.10511 9.75171 5.86431C9.85874 5.62351 9.91225 5.35404 9.91225 5.0559C9.91225 4.69661 9.82625 4.38509 9.65424 4.12136C9.48607 3.85762 9.26055 3.65504 8.9777 3.51362C8.69486 3.36837 8.38143 3.29575 8.03743 3.29575C7.73165 3.29575 7.43733 3.35882 7.15448 3.48495C6.87546 3.61108 6.6423 3.80984 6.45501 4.08122C6.26772 4.3526 6.15878 4.70425 6.12821 5.13617H4.56299C4.59357 4.47109 4.76557 3.9054 5.07899 3.43908C5.39242 2.96894 5.80522 2.61156 6.31741 2.36694C6.83341 2.12231 7.40675 2 8.03743 2C8.72161 2 9.31789 2.13378 9.82625 2.40134C10.3384 2.66507 10.734 3.0301 11.0131 3.49642C11.2959 3.96273 11.4373 4.49976 11.4373 5.1075C11.4373 5.53177 11.3724 5.914 11.2424 6.25418C11.1124 6.59436 10.9251 6.89823 10.6805 7.16579C10.4397 7.43335 10.1492 7.67033 9.80905 7.87673C9.48033 8.08313 9.21468 8.301 9.0121 8.53034C8.80952 8.75585 8.66237 9.02341 8.57063 9.33302C8.4789 9.64262 8.42921 10.0268 8.42156 10.4854V10.5714H6.94235ZM7.72782 14C7.43351 14 7.17933 13.8949 6.96528 13.6847C6.75506 13.4744 6.64994 13.2203 6.64994 12.9221C6.64994 12.6278 6.75506 12.3755 6.96528 12.1653C7.17933 11.9551 7.43351 11.85 7.72782 11.85C8.02214 11.85 8.27441 11.9551 8.48463 12.1653C8.69868 12.3755 8.8057 12.6278 8.8057 12.9221C8.8057 13.1209 8.75601 13.3024 8.65663 13.4668C8.55726 13.6273 8.4273 13.7573 8.26676 13.8567C8.10623 13.9522 7.92658 14 7.72782 14Z"
|
||||
fill="var(--v2-icon-icon-base)"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const triggerClass =
|
||||
"size-7 !rounded-full shrink-0 bg-v2-background-bg-base shadow-[var(--v2-elevation-button-neutral)]"
|
||||
|
||||
// TODO: wire to changelog / seen-state when available
|
||||
const showPopover = () => true
|
||||
|
||||
export function HelpButton() {
|
||||
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
|
||||
|
||||
const [state, setState] = /* persisted(Persist.global("help-button"), */ createStore({ dismissed: false }) /* ) */
|
||||
const [shown, setShown] = createSignal(false)
|
||||
const platform = usePlatform()
|
||||
|
||||
return (
|
||||
<Show when={!state.dismissed}>
|
||||
<div class="fixed bottom-4 right-4 z-50 hidden md:block">
|
||||
<Popover
|
||||
open={shown()}
|
||||
onOpenChange={setShown}
|
||||
triggerAs="button"
|
||||
triggerProps={{
|
||||
type: "button",
|
||||
"aria-label": "Help",
|
||||
class:
|
||||
"size-7 rounded-full bg-background-base shadow-[var(--shadow-lg-border-base)] flex items-center justify-center text-text-base hover:text-text-strong transition-colors",
|
||||
}}
|
||||
trigger={<span aria-hidden="true">?</span>}
|
||||
class="[&_[data-slot=popover-body]]:p-0 w-[320px] max-w-[calc(100vw-40px)] bg-transparent border-0 shadow-none rounded-xl"
|
||||
gutter={8}
|
||||
placement="top-end"
|
||||
>
|
||||
<Show when={shown()}>
|
||||
<div class="relative flex flex-col gap-1 w-[320px] p-4 rounded-xl bg-background-strong shadow-[var(--shadow-lg-border-base)]">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
class="absolute top-3.5 right-3.5 size-6 rounded-md flex items-center justify-center text-text-base hover:text-text-strong hover:bg-surface-raised-base-hover transition-colors"
|
||||
onClick={() => {
|
||||
setShown(false)
|
||||
setState("dismissed", true)
|
||||
}}
|
||||
>
|
||||
<Icon name="xmark-small" />
|
||||
</button>
|
||||
<span class="text-14-regular text-text-strong">Lorem ipsum dolor sit amet</span>
|
||||
<p class="text-12-regular text-text-weak">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
</Popover>
|
||||
</div>
|
||||
</Show>
|
||||
<a
|
||||
href="https://opencode.ai"
|
||||
aria-label="Open the OpenCode website"
|
||||
data-component="icon-button-v2"
|
||||
data-size="large"
|
||||
class={`${triggerClass} fixed bottom-5 right-5 z-50 flex items-center justify-center`}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
platform.openLink(event.currentTarget.href)
|
||||
}}
|
||||
>
|
||||
{helpIcon}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
// can remove this after the tabs rollout has been out for a while
|
||||
export function TabsInfoPopup() {
|
||||
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
|
||||
|
||||
const [state, setState] = persisted(Persist.global("tabsInfoPopup"), createStore({ dismissed: false }))
|
||||
// setState({ dismissed: false }) // for testing
|
||||
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
||||
|
||||
return (
|
||||
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
||||
<Show when={!state.dismissed}>
|
||||
<div
|
||||
class="fixed bottom-14 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
|
||||
aria-label="Introducing Tabs. A faster, more intuitive way to work."
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Dismiss Tabs information"
|
||||
class="absolute top-3 right-3 z-10 size-5 flex items-center justify-center rounded-[4px] bg-[rgba(0,0,0,0.4)]"
|
||||
onClick={() => setState("dismissed", true)}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M4.25 11.75L11.75 4.25M11.75 11.75L4.25 4.25" stroke="white" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="relative block h-[232px] w-[184px] cursor-pointer overflow-hidden rounded-[4px] text-left"
|
||||
onClick={() => {
|
||||
setState("dismissed", true)
|
||||
setDrawerOpen(true)
|
||||
}}
|
||||
>
|
||||
<video
|
||||
src={introducingTabsVideo}
|
||||
class="absolute inset-0 h-full w-full object-cover"
|
||||
loop
|
||||
muted
|
||||
autoplay
|
||||
playsinline
|
||||
aria-hidden="true"
|
||||
onContextMenu={(event) => event.preventDefault()}
|
||||
/>
|
||||
<div class="absolute inset-x-0 bottom-0 flex w-full flex-col items-start gap-1.5 bg-[linear-gradient(180deg,rgba(0,0,0,0)_0%,#000000_100%)] px-3 py-5">
|
||||
<p class="w-full select-none text-[13px] font-[530] leading-none tracking-[-0.04px] text-[#FFFFFF]">
|
||||
Introducing Tabs
|
||||
</p>
|
||||
<p class="w-full select-none text-[13px] font-[440] leading-[140%] tracking-[-0.04px] text-[#808080]">
|
||||
A faster, more intuitive way to work.
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<DrawerContent>
|
||||
<div class="flex h-[52px] w-full shrink-0 items-center gap-4 self-stretch border-b border-v2-border-border-muted p-4">
|
||||
<p class="min-h-0 min-w-0 flex-1 text-[13px] font-[530] leading-5 tracking-[-0.04px] tabular-nums text-v2-text-text-muted">
|
||||
June 16
|
||||
</p>
|
||||
<DrawerClose
|
||||
as={IconButtonV2}
|
||||
type="button"
|
||||
size="small"
|
||||
variant="ghost-muted"
|
||||
aria-label="Close"
|
||||
icon={<IconV2 name="xmark-small" />}
|
||||
/>
|
||||
</div>
|
||||
<div class="relative flex w-full flex-col items-start gap-6 p-8">
|
||||
<p class="w-full shrink-0 self-stretch text-[21px] font-[610] leading-6 tracking-[-0.37px] tabular-nums text-v2-text-text-base">
|
||||
Introducing Tabs Navigation.
|
||||
</p>
|
||||
<p class="w-full flex-1 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base">
|
||||
We've introduced tabs as the primary navigation in OpenCode. Your most important session are now pinned at
|
||||
the top of your screen at all times. No more hunting through menus or losing your place mid-session. Switch
|
||||
contexts instantly, pick up exactly where you left off, and keep your focus where it belongs: on the
|
||||
sessions.
|
||||
</p>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Select } from "@opencode-ai/ui/select"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { ModelSelectorPopover, ModelSelectorPopoverV2 } from "@/components/dialog-select-model"
|
||||
import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
|
||||
import { DialogSelectModelUnpaidV2 } from "@/components/dialog-select-model-unpaid-v2"
|
||||
import { useCommand } from "@/context/command"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { usePermission } from "@/context/permission"
|
||||
@@ -1549,9 +1551,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
style: control(),
|
||||
onClose: restoreFocus,
|
||||
onUnpaidClick: () => {
|
||||
void import("@/components/dialog-select-model-unpaid").then((x) => {
|
||||
dialog.show(() => <x.DialogSelectModelUnpaid model={props.controls.model.selection} />)
|
||||
})
|
||||
if (props.controls.newLayoutDesigns) {
|
||||
dialog.show(() => <DialogSelectModelUnpaidV2 model={props.controls.model.selection} />)
|
||||
return
|
||||
}
|
||||
dialog.show(() => <DialogSelectModelUnpaid model={props.controls.model.selection} />)
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -1772,7 +1776,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
class="max-w-[160px] justify-start capitalize"
|
||||
style={control()}
|
||||
>
|
||||
<span class="truncate">
|
||||
<span class="truncate leading-5">
|
||||
{props.controls.model.selection.variant.current() ?? language.t("common.default")}
|
||||
</span>
|
||||
<span class="-ml-0.5 -mr-1 flex shrink-0">
|
||||
@@ -2059,11 +2063,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
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={props.controls.model.selection} />
|
||||
))
|
||||
})
|
||||
dialog.show(() => (
|
||||
<DialogSelectModelUnpaid model={props.controls.model.selection} />
|
||||
))
|
||||
}}
|
||||
>
|
||||
<Show when={props.controls.model.selection.current()?.provider?.id}>
|
||||
@@ -2230,30 +2232,47 @@ function ComposerModelControl(props: { state: ComposerModelControlState }) {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
data-action="prompt-model"
|
||||
as="div"
|
||||
variant="ghost"
|
||||
size="normal"
|
||||
class="min-w-0 max-w-[220px] justify-start text-[13px] font-[440] leading-5 text-v2-text-text-faint group"
|
||||
classList={{ "animate-in fade-in": props.state.shouldAnimate }}
|
||||
style={props.state.style}
|
||||
onClick={props.state.onUnpaidClick}
|
||||
<Show
|
||||
when={props.state.newLayoutDesigns}
|
||||
fallback={
|
||||
<Button
|
||||
data-action="prompt-model"
|
||||
as="div"
|
||||
variant="ghost"
|
||||
size="normal"
|
||||
class="min-w-0 max-w-[220px] justify-start text-[13px] font-[440] leading-5 text-v2-text-text-faint group"
|
||||
classList={{ "animate-in fade-in": props.state.shouldAnimate }}
|
||||
style={props.state.style}
|
||||
onClick={props.state.onUnpaidClick}
|
||||
>
|
||||
<Show when={props.state.providerID}>
|
||||
{(providerID) => (
|
||||
<ProviderIcon
|
||||
id={providerID()}
|
||||
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">{props.state.modelName}</span>
|
||||
<span class="-ml-1 shrink-0 flex size-fit">
|
||||
<Icon name="chevron-down" size="small" class="text-v2-icon-icon-muted" />
|
||||
</span>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Show when={props.state.providerID}>
|
||||
{(providerID) => (
|
||||
<ProviderIcon
|
||||
id={providerID()}
|
||||
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">{props.state.modelName}</span>
|
||||
<span class="-ml-1 shrink-0 flex size-fit">
|
||||
<Icon name="chevron-down" size="small" class="text-v2-icon-icon-muted" />
|
||||
</span>
|
||||
</Button>
|
||||
<ButtonV2
|
||||
data-action="prompt-model"
|
||||
variant="ghost-muted"
|
||||
size="normal"
|
||||
class="min-w-0 max-w-[220px] justify-start ![font-weight:440] group"
|
||||
classList={{ "animate-in fade-in": props.state.shouldAnimate }}
|
||||
style={props.state.style}
|
||||
onClick={props.state.onUnpaidClick}
|
||||
>
|
||||
<ModelControlContent state={props.state} v2 />
|
||||
</ButtonV2>
|
||||
</Show>
|
||||
</TooltipV2>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Taken from https://www.solid-ui.com/docs/components/drawer
|
||||
* Only used in one place hence not a v2 component yet... can be promoted to ui/v2 later
|
||||
*/
|
||||
|
||||
import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js"
|
||||
import { splitProps } from "solid-js"
|
||||
import type { ContentProps, DescriptionProps, DynamicProps, LabelProps, OverlayProps } from "@corvu/drawer"
|
||||
import DrawerPrimitive from "@corvu/drawer"
|
||||
|
||||
const Drawer = DrawerPrimitive
|
||||
|
||||
const DrawerTrigger = DrawerPrimitive.Trigger
|
||||
|
||||
const DrawerPortal = DrawerPrimitive.Portal
|
||||
|
||||
const DrawerClose = DrawerPrimitive.Close
|
||||
|
||||
type DrawerOverlayProps<T extends ValidComponent = "div"> = OverlayProps<T> & { class?: string }
|
||||
|
||||
const DrawerOverlay = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerOverlayProps<T>>) => {
|
||||
const [, rest] = splitProps(props as DrawerOverlayProps, ["class"])
|
||||
const drawerContext = DrawerPrimitive.useContext()
|
||||
const overlayStyle = () => {
|
||||
const state = drawerContext.transitionState()
|
||||
if (state === "opening" || state === "closing") return undefined
|
||||
const open = drawerContext.openPercentage()
|
||||
return {
|
||||
opacity: open,
|
||||
"backdrop-filter": `blur(${4 * open}px)`,
|
||||
}
|
||||
}
|
||||
return (
|
||||
<DrawerPrimitive.Overlay
|
||||
class={props.class}
|
||||
classList={{
|
||||
"fixed inset-0 z-[100] bg-v2-overlay-simple-overlay-scrim opacity-0 backdrop-blur-none transition-[opacity,backdrop-filter] duration-300 data-[opening]:opacity-100 data-[opening]:backdrop-blur-[4px] data-[closing]:opacity-0 data-[closing]:backdrop-blur-none": true,
|
||||
}}
|
||||
style={overlayStyle()}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerContentProps<T extends ValidComponent = "div"> = ContentProps<T> & {
|
||||
class?: string
|
||||
children?: JSX.Element
|
||||
}
|
||||
|
||||
const DrawerContent = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerContentProps<T>>) => {
|
||||
const [, rest] = splitProps(props as DrawerContentProps, ["class", "children"])
|
||||
return (
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
class={props.class}
|
||||
classList={{
|
||||
"group/drawer-content fixed inset-y-[6px] right-[6px] left-auto z-[100] flex h-auto max-h-[calc(100vh-12px)] w-[560px] max-w-[calc(100vw-12px)] flex-col items-start rounded-[8px] bg-v2-background-bg-base p-0 shadow-[var(--v2-elevation-overlay)] data-[transitioning]:transition-transform data-[transitioning]:duration-300 md:select-none": true,
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{props.children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
)
|
||||
}
|
||||
|
||||
const DrawerHeader: Component<ComponentProps<"div">> = (props) => {
|
||||
const [, rest] = splitProps(props, ["class"])
|
||||
return <div class={props.class} classList={{ "grid gap-1.5 p-4 text-center sm:text-left": true }} {...rest} />
|
||||
}
|
||||
|
||||
const DrawerFooter: Component<ComponentProps<"div">> = (props) => {
|
||||
const [, rest] = splitProps(props, ["class"])
|
||||
return <div class={props.class} classList={{ "mt-auto flex flex-col gap-2 p-4": true }} {...rest} />
|
||||
}
|
||||
|
||||
type DrawerTitleProps<T extends ValidComponent = "div"> = LabelProps<T> & { class?: string }
|
||||
|
||||
const DrawerTitle = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerTitleProps<T>>) => {
|
||||
const [, rest] = splitProps(props as DrawerTitleProps, ["class"])
|
||||
return (
|
||||
<DrawerPrimitive.Label
|
||||
class={props.class}
|
||||
classList={{ "text-base font-[530] leading-none tracking-[-0.04px] text-v2-text-text-base": true }}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type DrawerDescriptionProps<T extends ValidComponent = "div"> = DescriptionProps<T> & {
|
||||
class?: string
|
||||
}
|
||||
|
||||
const DrawerDescription = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerDescriptionProps<T>>) => {
|
||||
const [, rest] = splitProps(props as DrawerDescriptionProps, ["class"])
|
||||
return (
|
||||
<DrawerPrimitive.Description
|
||||
class={props.class}
|
||||
classList={{
|
||||
"text-[13px] font-[440] leading-[140%] tracking-[-0.04px] text-v2-text-text-muted": true,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
}
|
||||
Vendored
+10
@@ -12,6 +12,16 @@ interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.mp4" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
export declare module "solid-js" {
|
||||
namespace JSX {
|
||||
interface Directives {
|
||||
|
||||
@@ -42,7 +42,7 @@ const domains = [
|
||||
},
|
||||
] as const
|
||||
|
||||
describe("i18n parity", () => {
|
||||
describe.skipIf(!!process.env.CI)("i18n parity", () => {
|
||||
test("non-English locales have every English key", async () => {
|
||||
for (const domain of domains) {
|
||||
const source = await dictionary(domain.source)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createEffect, Suspense, type ParentProps } from "solid-js"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { DebugBar } from "@/components/debug-bar"
|
||||
import { HelpButton } from "@/components/help-button"
|
||||
import { HelpButton, TabsInfoPopup } from "@/components/help-button"
|
||||
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { setNavigate } from "@/utils/notification-click"
|
||||
@@ -39,6 +39,7 @@ export default function NewLayout(props: ParentProps) {
|
||||
<Suspense>{props.children}</Suspense>
|
||||
</main>
|
||||
{import.meta.env.DEV && <DebugBar inline />}
|
||||
<TabsInfoPopup />
|
||||
<HelpButton />
|
||||
<ToastRegion v2 />
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
|
||||
import { useCommand, type CommandOption } from "@/context/command"
|
||||
import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd"
|
||||
import { DebugBar } from "@/components/debug-bar"
|
||||
import { HelpButton } from "@/components/help-button"
|
||||
import { HelpButton, TabsInfoPopup } from "@/components/help-button"
|
||||
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
||||
import { useDirectoryPicker } from "@/components/directory-picker"
|
||||
import { ServerConnection, useServer } from "@/context/server"
|
||||
@@ -2395,6 +2395,7 @@ export default function LegacyLayout(props: ParentProps) {
|
||||
</div>
|
||||
{import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1" && <DebugBar />}
|
||||
</div>
|
||||
<TabsInfoPopup />
|
||||
<HelpButton />
|
||||
<ToastRegion v2={false} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { For } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
|
||||
import { SettingsProvider, useSettings } from "@/context/settings"
|
||||
|
||||
export default {
|
||||
title: "Composer/Revert Dock",
|
||||
id: "composer-revert-dock",
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `### Overview
|
||||
Real \`SessionRevertDock\` from app code, rendered above a mock composer card.
|
||||
|
||||
### Source path
|
||||
- \`packages/app/src/pages/session/composer/session-revert-dock.tsx\`
|
||||
|
||||
### Why the mock composer
|
||||
The live composer overlaps the dock's bottom by 18px (\`session-composer-region-controller.ts\` \`lift()\`).
|
||||
The card below reproduces that overlap so the collapsed/expanded cutoff behavior can be verified in isolation.
|
||||
|
||||
### Layout split
|
||||
Use the **Layout** button to toggle \`newLayoutDesigns\` and preview both the v2 dock and the legacy (v1) \`DockTray\` fallback.
|
||||
|
||||
### Notes
|
||||
- \`onRestore\` only mutates local story state, so nothing in the real session is affected.
|
||||
- Click the header to expand/collapse. Click "Restore message" to remove a row.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const messages = [
|
||||
"update current branch with latest changes from dev and fix conflicts if any",
|
||||
"investigate why the chat input loses focus after sending a message",
|
||||
"Debug why streaming responses sometimes duplicate the last token",
|
||||
"suggest a better title for this PR based on the diff",
|
||||
"add a storybook story for the revert dock",
|
||||
"reduce re-renders in the timeline component",
|
||||
]
|
||||
|
||||
const btn = (accent?: boolean) =>
|
||||
({
|
||||
padding: "6px 12px",
|
||||
"border-radius": "6px",
|
||||
border: "1px solid var(--v2-border-border-base, #0000001a)",
|
||||
background: accent ? "var(--v2-background-bg-contrast, #242424)" : "var(--v2-background-bg-base, #fff)",
|
||||
color: accent ? "var(--v2-text-text-contrast, #fafafa)" : "var(--v2-text-text-base, #161616)",
|
||||
cursor: "pointer",
|
||||
"font-size": "13px",
|
||||
}) as const
|
||||
|
||||
function Stage(props: { count: number }) {
|
||||
const settings = useSettings()
|
||||
const seed = () => messages.slice(0, props.count).map((text, index) => ({ id: `rolled-${index}`, text }))
|
||||
const [store, setStore] = createStore({ items: seed() })
|
||||
|
||||
const v2 = () => settings.general.newLayoutDesigns()
|
||||
const reset = () => setStore("items", seed())
|
||||
const restore = (id: string) =>
|
||||
setStore(
|
||||
"items",
|
||||
store.items.filter((item) => item.id !== id),
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={{ display: "grid", gap: "16px", "max-width": "720px" }}>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<button style={btn()} onClick={reset}>
|
||||
Reset ({props.count})
|
||||
</button>
|
||||
<button style={btn(v2())} onClick={() => settings.general.setNewLayoutDesigns(!v2())}>
|
||||
Layout: {v2() ? "v2" : "v1"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Reproduce the real composer stack: dock + card overlapping the dock's bottom by lift() = 18px */}
|
||||
<div style={{ display: "flex", "flex-direction": "column" }}>
|
||||
<SessionRevertDock items={store.items} onRestore={restore} />
|
||||
<div
|
||||
style={{ position: "relative", "z-index": 70, "margin-top": "-18px" }}
|
||||
class="min-h-24 w-full rounded-[12px] border border-v2-border-border-base bg-v2-background-bg-base px-4 py-3 text-[13px] text-v2-text-text-faint"
|
||||
>
|
||||
Ask anything...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-[12px] text-v2-text-text-faint">
|
||||
Restored so far:{" "}
|
||||
<For each={seed()}>
|
||||
{(item) => <span>{store.items.some((current) => current.id === item.id) ? "" : `“${item.text}” `}</span>}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const story = (count: number) => () => (
|
||||
<SettingsProvider>
|
||||
<Stage count={count} />
|
||||
</SettingsProvider>
|
||||
)
|
||||
|
||||
export const OneMessage = {
|
||||
name: "1 rolled back",
|
||||
render: story(1),
|
||||
}
|
||||
|
||||
export const ThreeMessages = {
|
||||
name: "3 rolled back",
|
||||
render: story(3),
|
||||
}
|
||||
|
||||
export const ManyMessages = {
|
||||
name: "6 rolled back (scrolls)",
|
||||
render: story(6),
|
||||
}
|
||||
@@ -3,7 +3,11 @@ import { createStore } from "solid-js/store"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { DockTray } from "@opencode-ai/ui/dock-surface"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useSettings } from "@/context/settings"
|
||||
|
||||
export function SessionRevertDock(props: {
|
||||
items: { id: string; text: string }[]
|
||||
@@ -12,6 +16,7 @@ export function SessionRevertDock(props: {
|
||||
onRestore: (id: string) => void
|
||||
}) {
|
||||
const language = useLanguage()
|
||||
const settings = useSettings()
|
||||
const [store, setStore] = createStore({
|
||||
collapsed: true,
|
||||
})
|
||||
@@ -31,69 +36,153 @@ export function SessionRevertDock(props: {
|
||||
)
|
||||
const preview = createMemo(() => props.items[0]?.text ?? "")
|
||||
|
||||
const onHeaderKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return
|
||||
event.preventDefault()
|
||||
toggle()
|
||||
}
|
||||
|
||||
return (
|
||||
<DockTray data-component="session-revert-dock">
|
||||
<Show
|
||||
when={settings.general.newLayoutDesigns()}
|
||||
fallback={
|
||||
<DockTray data-component="session-revert-dock">
|
||||
<div
|
||||
class="pl-3 pr-2 py-2 flex items-center gap-2"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={toggle}
|
||||
onKeyDown={onHeaderKeyDown}
|
||||
>
|
||||
<span class="shrink-0 text-14-regular text-text-strong cursor-default">{label()}</span>
|
||||
<Show when={store.collapsed && preview()}>
|
||||
<span class="min-w-0 flex-1 truncate text-14-regular text-text-base cursor-default">{preview()}</span>
|
||||
</Show>
|
||||
<div class="ml-auto shrink-0">
|
||||
<IconButton
|
||||
icon="chevron-down"
|
||||
size="normal"
|
||||
variant="ghost"
|
||||
style={{ transform: `rotate(${store.collapsed ? 180 : 0}deg)` }}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
toggle()
|
||||
}}
|
||||
aria-label={
|
||||
store.collapsed ? language.t("session.revertDock.expand") : language.t("session.revertDock.collapse")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={store.collapsed}>
|
||||
<div class="h-5" aria-hidden="true" />
|
||||
</Show>
|
||||
|
||||
<Show when={!store.collapsed}>
|
||||
<div class="px-3 pb-7 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar">
|
||||
<For each={props.items}>
|
||||
{(item) => (
|
||||
<div class="flex items-center gap-2 min-w-0 py-1">
|
||||
<span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
class="shrink-0"
|
||||
disabled={props.disabled || !!props.restoring}
|
||||
onClick={() => props.onRestore(item.id)}
|
||||
>
|
||||
{language.t("session.revertDock.restore")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</DockTray>
|
||||
}
|
||||
>
|
||||
<div
|
||||
class="pl-3 pr-2 py-2 flex items-center gap-2"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={toggle}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return
|
||||
event.preventDefault()
|
||||
toggle()
|
||||
}}
|
||||
data-component="session-revert-dock"
|
||||
class="w-full overflow-hidden rounded-xl border-[0.5px] border-v2-border-border-base bg-v2-background-bg-layer-01"
|
||||
>
|
||||
<span class="shrink-0 text-14-regular text-text-strong cursor-default">{label()}</span>
|
||||
<Show when={store.collapsed && preview()}>
|
||||
<span class="min-w-0 flex-1 truncate text-14-regular text-text-base cursor-default">{preview()}</span>
|
||||
<div
|
||||
class="flex h-[42px] items-center gap-2 pl-4 pr-2"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={toggle}
|
||||
onKeyDown={onHeaderKeyDown}
|
||||
>
|
||||
<IconV2 name="outline-reset" size="normal" class="text-v2-icon-icon-muted" />
|
||||
<span
|
||||
classList={{
|
||||
"font-[440] shrink-0 cursor-default text-[13px] leading-5 tracking-[-0.04px]": true,
|
||||
"text-v2-text-text-base": !store.collapsed,
|
||||
"text-v2-text-text-muted": store.collapsed,
|
||||
}}
|
||||
>
|
||||
{label()}
|
||||
</span>
|
||||
<Show when={store.collapsed && preview()}>
|
||||
<span class="min-w-0 flex-1 truncate cursor-default text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint">
|
||||
{preview()}
|
||||
</span>
|
||||
</Show>
|
||||
<div class="ml-auto shrink-0">
|
||||
<IconButtonV2
|
||||
icon={<IconV2 name="outline-chevron-down" size="small" />}
|
||||
size="large"
|
||||
variant="ghost-muted"
|
||||
style={{ transform: `rotate(${store.collapsed ? 180 : 0}deg)` }}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
toggle()
|
||||
}}
|
||||
aria-label={
|
||||
store.collapsed ? language.t("session.revertDock.expand") : language.t("session.revertDock.collapse")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sacrificial space the composer overlaps via its negative lift (18px), so the header stays fully visible */}
|
||||
<Show when={store.collapsed}>
|
||||
<div class="h-[18px]" aria-hidden="true" />
|
||||
</Show>
|
||||
|
||||
<Show when={!store.collapsed}>
|
||||
{/* Scroll viewport ends above the composer; the 18px sacrificial below is what the composer overlaps */}
|
||||
<div class="flex max-h-42 flex-col gap-2 overflow-y-auto px-4 pt-px pb-3 no-scrollbar">
|
||||
<For each={props.items}>
|
||||
{(item) => (
|
||||
<div class="flex h-6 min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 flex-1 truncate text-[13px] font-[400] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
||||
{item.text}
|
||||
</span>
|
||||
<ButtonV2
|
||||
size="small"
|
||||
variant="neutral"
|
||||
class="shrink-0"
|
||||
disabled={props.disabled || !!props.restoring}
|
||||
onClick={() => props.onRestore(item.id)}
|
||||
>
|
||||
{language.t("session.revertDock.restore")}
|
||||
</ButtonV2>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div class="h-[18px]" aria-hidden="true" />
|
||||
</Show>
|
||||
<div class="ml-auto shrink-0">
|
||||
<IconButton
|
||||
data-collapsed={store.collapsed ? "true" : "false"}
|
||||
icon="chevron-down"
|
||||
size="normal"
|
||||
variant="ghost"
|
||||
style={{ transform: `rotate(${store.collapsed ? 180 : 0}deg)` }}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
toggle()
|
||||
}}
|
||||
aria-label={
|
||||
store.collapsed ? language.t("session.revertDock.expand") : language.t("session.revertDock.collapse")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={store.collapsed}>
|
||||
<div class="h-5" aria-hidden="true" />
|
||||
</Show>
|
||||
|
||||
<Show when={!store.collapsed}>
|
||||
<div class="px-3 pb-7 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar">
|
||||
<For each={props.items}>
|
||||
{(item) => (
|
||||
<div class="flex items-center gap-2 min-w-0 py-1">
|
||||
<span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
class="shrink-0"
|
||||
disabled={props.disabled || !!props.restoring}
|
||||
onClick={() => props.onRestore(item.id)}
|
||||
>
|
||||
{language.t("session.revertDock.restore")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</DockTray>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function handler(
|
||||
type CostInfo = ReturnType<typeof calculateCost>
|
||||
|
||||
const MAX_FAILOVER_RETRIES = 3
|
||||
const MAX_429_RETRIES = 3
|
||||
const MAX_RETRYABLE_STATUS_RETRIES = 3
|
||||
const dict = i18n(localeFromRequest(input.request))
|
||||
const t = (key: Key, params?: Record<string, string | number>) => resolve(dict[key], params)
|
||||
const ADMIN_WORKSPACES = [
|
||||
@@ -213,7 +213,7 @@ export async function handler(
|
||||
logger.debug("REQUEST URL: " + reqUrl)
|
||||
logger.debug("REQUEST: " + reqBody.substring(0, 300) + "...")
|
||||
const isNewInference = providerInfo.id.startsWith("console.") || providerInfo.id.startsWith("console-go.")
|
||||
const res = await fetchWith429Retry(
|
||||
const res = await fetchWithRetryableStatus(
|
||||
reqUrl,
|
||||
{
|
||||
method: "POST",
|
||||
@@ -246,7 +246,7 @@ export async function handler(
|
||||
// abandoned Console requests do not leave orphaned inference work open.
|
||||
signal: input.request.signal,
|
||||
},
|
||||
{ count: isNewInference ? MAX_429_RETRIES : 0 },
|
||||
{ count: isNewInference ? MAX_RETRYABLE_STATUS_RETRIES : 0 },
|
||||
)
|
||||
|
||||
if (isNewInference) {
|
||||
@@ -307,7 +307,7 @@ export async function handler(
|
||||
logger.debug("STATUS: " + res.status + " " + res.statusText)
|
||||
|
||||
// Handle non-streaming response
|
||||
if (!isStream || [400, 404, 429].includes(res.status)) {
|
||||
if (!isStream || [400, 404, 429, 529].includes(res.status)) {
|
||||
const json = await res.json()
|
||||
await rateLimiter?.track()
|
||||
const usage = providerInfo.extractUsage(json)
|
||||
@@ -999,11 +999,11 @@ export async function handler(
|
||||
providerInfo.apiKey = authInfo.provider.credentials
|
||||
}
|
||||
|
||||
async function fetchWith429Retry(url: string, options: RequestInit, retry = { count: 0 }) {
|
||||
async function fetchWithRetryableStatus(url: string, options: RequestInit, retry = { count: 0 }) {
|
||||
const res = await fetch(url, options)
|
||||
if (res.status === 429 && retry.count < MAX_429_RETRIES) {
|
||||
if ([429, 529].includes(res.status) && retry.count < MAX_RETRYABLE_STATUS_RETRIES) {
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, retry.count) * 500))
|
||||
return fetchWith429Retry(url, options, { count: retry.count + 1 })
|
||||
return fetchWithRetryableStatus(url, options, { count: retry.count + 1 })
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -99,7 +99,7 @@ function build(key: string, remote: SelectableItem, url: string, prev?: Model):
|
||||
: undefined
|
||||
const prices = remote.billing?.token_prices
|
||||
// Copilot prices are AIC per billing batch; OpenCode stores USD per million tokens.
|
||||
const usdPerMillion = prices ? 10_000 / prices.batch_size : 0
|
||||
const usdPerMillion = prices && prices.batch_size > 0 ? 10_000 / prices.batch_size : 0
|
||||
|
||||
const model: CopilotModel = {
|
||||
id: key,
|
||||
|
||||
@@ -207,6 +207,13 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
},
|
||||
options: { headerTimeout: OPENAI_HEADER_TIMEOUT_DEFAULT },
|
||||
}),
|
||||
meta: () =>
|
||||
Effect.succeed({
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
return sdk.responses(modelID)
|
||||
},
|
||||
}),
|
||||
xai: () =>
|
||||
Effect.succeed({
|
||||
autoload: false,
|
||||
@@ -1064,11 +1071,17 @@ export type ConfigProvidersResult = Types.DeepMutable<Schema.Schema.Type<typeof
|
||||
|
||||
export function toPublicInfo(provider: Info): Info {
|
||||
return JSON.parse(
|
||||
JSON.stringify(provider, (_, value) => {
|
||||
if (typeof value === "function" || typeof value === "symbol" || value === undefined) return undefined
|
||||
if (typeof value === "bigint") return value.toString()
|
||||
return value
|
||||
}),
|
||||
JSON.stringify(
|
||||
{
|
||||
...provider,
|
||||
models: Object.fromEntries(Object.entries(provider.models).filter(([, model]) => Schema.is(Model)(model))),
|
||||
},
|
||||
(_, value) => {
|
||||
if (typeof value === "function" || typeof value === "symbol" || value === undefined) return undefined
|
||||
if (typeof value === "bigint") return value.toString()
|
||||
return value
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -884,6 +884,18 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
)
|
||||
case "@ai-sdk/amazon-bedrock/mantle":
|
||||
case "@ai-sdk/openai": {
|
||||
if (model.providerID === "meta") {
|
||||
return Object.fromEntries(
|
||||
OPENAI_EFFORTS.map((effort) => [
|
||||
effort,
|
||||
{
|
||||
reasoningEffort: effort,
|
||||
reasoningSummary: "auto",
|
||||
include: INCLUDE_ENCRYPTED_REASONING,
|
||||
},
|
||||
]),
|
||||
)
|
||||
}
|
||||
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
|
||||
const efforts = openaiReasoningEfforts(model.api.id, model.release_date)
|
||||
return Object.fromEntries(
|
||||
@@ -1128,11 +1140,20 @@ export function options(input: {
|
||||
|
||||
if (
|
||||
input.providerOptions?.setCacheKey !== false &&
|
||||
(input.model.providerID === "openai" || input.model.api.npm === "@ai-sdk/xai" || input.providerOptions?.setCacheKey)
|
||||
(input.model.providerID === "openai" ||
|
||||
input.model.api.npm === "@ai-sdk/openai" ||
|
||||
input.model.api.npm === "@ai-sdk/xai" ||
|
||||
input.providerOptions?.setCacheKey)
|
||||
) {
|
||||
result["promptCacheKey"] = input.sessionID
|
||||
}
|
||||
|
||||
if (input.model.providerID === "meta" && input.model.api.npm === "@ai-sdk/openai") {
|
||||
result["reasoningEffort"] = "high"
|
||||
result["reasoningSummary"] = "auto"
|
||||
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
||||
}
|
||||
|
||||
if (input.model.api.npm === "@ai-sdk/google" || input.model.api.npm === "@ai-sdk/google-vertex") {
|
||||
if (input.model.capabilities.reasoning) {
|
||||
result["thinkingConfig"] = {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
You are OpenCode, the best coding agent on the planet.
|
||||
You are based on a large language model trained by Meta MSL named Muse Spark.
|
||||
When asked who you are, identify yourself as OpenCode powered by Meta Muse Spark by name.
|
||||
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
|
||||
If the user asks for help or wants to give feedback inform them of the following:
|
||||
- ctrl+p to list available actions
|
||||
- To give feedback, users should report the issue at
|
||||
https://github.com/anomalyco/opencode
|
||||
When the user directly asks about OpenCode (eg. "can OpenCode do...", "does OpenCode have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific OpenCode feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from OpenCode docs. The list of available docs is available at https://opencode.ai/docs
|
||||
# Tone and style
|
||||
- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
|
||||
- Your output will be displayed on a command line interface. Your responses should be short and concise. You can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
|
||||
- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
|
||||
- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.
|
||||
# Professional objectivity
|
||||
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if OpenCode honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.
|
||||
# Task Management
|
||||
You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
|
||||
These tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
|
||||
It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.
|
||||
Examples:
|
||||
<example>
|
||||
user: Run the build and fix any type errors
|
||||
assistant: I'm going to use the TodoWrite tool to write the following items to the todo list:
|
||||
- Run the build
|
||||
- Fix any type errors
|
||||
I'm now going to run the build using Bash.
|
||||
Looks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.
|
||||
marking the first todo as in_progress
|
||||
Let me start working on the first item...
|
||||
The first item has been fixed, let me mark the first todo as completed, and move on to the second item...
|
||||
..
|
||||
..
|
||||
</example>
|
||||
In the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.
|
||||
<example>
|
||||
user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
|
||||
assistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.
|
||||
Adding the following todos to the todo list:
|
||||
1. Research existing metrics tracking in the codebase
|
||||
2. Design the metrics collection system
|
||||
3. Implement core metrics tracking functionality
|
||||
4. Create export functionality for different formats
|
||||
Let me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.
|
||||
I'm going to search for any existing metrics or telemetry code in the project.
|
||||
I've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...
|
||||
[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]
|
||||
</example>
|
||||
# Doing tasks
|
||||
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
|
||||
- Use the TodoWrite tool to plan the task if required
|
||||
- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.
|
||||
# Tool usage policy
|
||||
- When doing file search, prefer to use the Task tool in order to reduce context usage.
|
||||
- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.
|
||||
- When WebFetch returns a message about a redirect to a different host, you should immediately make a new WebFetch request with the redirect URL provided in the response.
|
||||
- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
|
||||
- If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to launch multiple agents in parallel, send a single message with multiple Task tool calls.
|
||||
- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
|
||||
- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool instead of running search commands directly.
|
||||
<example>
|
||||
user: Where are errors from the client handled?
|
||||
assistant: [Uses the Task tool to find the files that handle client errors instead of using Glob or Grep directly]
|
||||
</example>
|
||||
<example>
|
||||
user: What is the codebase structure?
|
||||
assistant: [Uses the Task tool]
|
||||
</example>
|
||||
IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
|
||||
# Code References
|
||||
When referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.
|
||||
<example>
|
||||
user: Where are errors from the client handled?
|
||||
assistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.
|
||||
</example>
|
||||
@@ -9,6 +9,7 @@ import PROMPT_BEAST from "./prompt/beast.txt"
|
||||
import PROMPT_GEMINI from "./prompt/gemini.txt"
|
||||
import PROMPT_GPT from "./prompt/gpt.txt"
|
||||
import PROMPT_KIMI from "./prompt/kimi.txt"
|
||||
import PROMPT_META from "./prompt/meta.txt"
|
||||
|
||||
import PROMPT_CODEX from "./prompt/codex.txt"
|
||||
import PROMPT_TRINITY from "./prompt/trinity.txt"
|
||||
@@ -24,6 +25,7 @@ import { MCP } from "@/mcp"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
|
||||
export function provider(model: Provider.Model) {
|
||||
if (model.api.id.includes("muse-spark")) return [PROMPT_META]
|
||||
if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3"))
|
||||
return [PROMPT_BEAST]
|
||||
if (model.api.id.includes("gpt")) {
|
||||
|
||||
@@ -187,6 +187,60 @@ test("converts Copilot AIC token prices to USD per million tokens", async () =>
|
||||
expect(models["ignored-non-chat-record"]).toBeUndefined()
|
||||
})
|
||||
|
||||
test("uses zero cost when Copilot reports a zero billing batch size", async () => {
|
||||
globalThis.fetch = mock(() =>
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
model_picker_enabled: true,
|
||||
id: "mercury-alpha",
|
||||
name: "Mercury Alpha",
|
||||
version: "mercury-alpha-2026-07-09",
|
||||
billing: {
|
||||
token_prices: {
|
||||
batch_size: 0,
|
||||
default: {
|
||||
input_price: 0,
|
||||
output_price: 0,
|
||||
cache_price: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
capabilities: {
|
||||
family: "mercury",
|
||||
limits: {
|
||||
max_context_window_tokens: 128000,
|
||||
max_output_tokens: 16384,
|
||||
max_prompt_tokens: 128000,
|
||||
},
|
||||
supports: {
|
||||
streaming: true,
|
||||
tool_calls: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
),
|
||||
) as unknown as typeof fetch
|
||||
|
||||
const model = (await CopilotModels.get("https://api.githubcopilot.com")).models["mercury-alpha"]
|
||||
|
||||
expect(model.cost).toEqual({
|
||||
input: 0,
|
||||
output: 0,
|
||||
cache: {
|
||||
read: 0,
|
||||
write: 0,
|
||||
},
|
||||
})
|
||||
expect(JSON.stringify(model)).not.toContain("null")
|
||||
})
|
||||
|
||||
test("records Copilot advertised responses endpoint for non-GPT model IDs", async () => {
|
||||
globalThis.fetch = mock(() =>
|
||||
Promise.resolve(
|
||||
|
||||
@@ -1426,6 +1426,32 @@ test("models.dev normalization fills required response fields", () => {
|
||||
expect(model.release_date).toBe("")
|
||||
})
|
||||
|
||||
test("public provider info omits invalid models", () => {
|
||||
const provider = Provider.fromModelsDevProvider({
|
||||
id: "test",
|
||||
name: "Test",
|
||||
env: [],
|
||||
models: {
|
||||
valid: {
|
||||
id: "valid",
|
||||
name: "Valid",
|
||||
cost: { input: 1, output: 1 },
|
||||
limit: { context: 128_000, output: 16_000 },
|
||||
},
|
||||
},
|
||||
} as unknown as ModelsDev.Provider)
|
||||
provider.models.invalid = {
|
||||
...provider.models.valid,
|
||||
id: ModelV2.ID.make("invalid"),
|
||||
cost: { ...provider.models.valid.cost, input: Number.NaN },
|
||||
}
|
||||
|
||||
const result = Provider.toPublicInfo(provider)
|
||||
|
||||
expect(result.models.valid).toBeDefined()
|
||||
expect(result.models.invalid).toBeUndefined()
|
||||
})
|
||||
|
||||
it.instance("model variants are generated for reasoning models", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect, Layer } from "effect"
|
||||
import type { Agent } from "../../src/agent/agent"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Skill } from "../../src/skill"
|
||||
import { Permission } from "../../src/permission"
|
||||
import type { Provider } from "../../src/provider/provider"
|
||||
import { SystemPrompt } from "../../src/session/system"
|
||||
import { MCP } from "../../src/mcp"
|
||||
import { testEffect } from "../lib/effect"
|
||||
@@ -83,6 +84,12 @@ const it = testEffect(
|
||||
)
|
||||
|
||||
describe("session.system", () => {
|
||||
test("selects the Meta prompt for Muse Spark model IDs", () => {
|
||||
expect(SystemPrompt.provider({ api: { id: "meta/muse-spark-preview" } } as Provider.Model)[0]).toContain(
|
||||
"Meta Muse Spark",
|
||||
)
|
||||
})
|
||||
|
||||
it.effect("skills output is sorted by name and stable across calls", () =>
|
||||
Effect.gen(function* () {
|
||||
const prompt = yield* SystemPrompt.Service
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -185,6 +185,14 @@
|
||||
background-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
|
||||
[data-component="task-tool-surface"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-info-structured"] {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
@@ -250,16 +258,30 @@
|
||||
|
||||
body[data-new-layout] {
|
||||
[data-component="task-tool-card"] {
|
||||
padding: 8px 12px 8px 10px;
|
||||
border-radius: 8px;
|
||||
gap: 8px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
border-color: transparent;
|
||||
background: var(--task-agent-background, light-dark(#fafafa, rgba(250, 250, 250, 0.15)));
|
||||
box-shadow: inset 0 0 0 0.5px var(--task-agent-border, var(--v2-border-border-base));
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
[data-component="task-tool-surface"] {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--v2-background-bg-layer-01);
|
||||
}
|
||||
|
||||
[data-component="task-tool-spinner"],
|
||||
[data-component="task-tool-title"] {
|
||||
color: var(--task-agent-color, var(--text-strong));
|
||||
[data-component="task-tool-icon"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
[data-component="icon"] {
|
||||
color: var(--task-agent-color, var(--v2-icon-icon-base));
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="task-tool-title"] {
|
||||
@@ -270,6 +292,7 @@ body[data-new-layout] {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.04px;
|
||||
color: var(--v2-text-text-base);
|
||||
flex: none;
|
||||
flex-grow: 0;
|
||||
}
|
||||
@@ -281,7 +304,7 @@ body[data-new-layout] {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.04px;
|
||||
color: light-dark(var(--v2-text-text-base), #fafafa);
|
||||
color: var(--v2-text-text-base);
|
||||
font-variation-settings: "slnt" 0;
|
||||
flex: none;
|
||||
flex-grow: 0;
|
||||
@@ -294,7 +317,11 @@ body[data-new-layout] {
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
border-color: transparent;
|
||||
background: var(--task-agent-background, light-dark(#fafafa, rgba(250, 250, 250, 0.15)));
|
||||
background: transparent;
|
||||
|
||||
[data-component="task-tool-surface"] {
|
||||
background: var(--v2-background-bg-layer-01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,22 +371,12 @@ const agentTones: Record<string, string> = {
|
||||
plan: "var(--icon-agent-plan-base)",
|
||||
}
|
||||
|
||||
const v2AgentTones: Record<string, { color: string; border: string; background: string }> = {
|
||||
build: {
|
||||
color: "var(--v2-agent-build-solid)",
|
||||
border: "var(--v2-agent-build-border)",
|
||||
background: "var(--v2-agent-build-background)",
|
||||
},
|
||||
explore: {
|
||||
color: "var(--v2-agent-explore-solid)",
|
||||
border: "var(--v2-agent-explore-border)",
|
||||
background: "var(--v2-agent-explore-background)",
|
||||
},
|
||||
plan: {
|
||||
color: "var(--v2-agent-plan-solid)",
|
||||
border: "var(--v2-agent-plan-border)",
|
||||
background: "var(--v2-agent-plan-background)",
|
||||
},
|
||||
const v2AgentTones: Record<string, string> = {
|
||||
build: "var(--v2-agent-build-solid)",
|
||||
explore: "var(--v2-agent-explore-solid)",
|
||||
plan: "var(--v2-agent-plan-solid)",
|
||||
review: "var(--v2-agent-review-solid)",
|
||||
writer: "var(--v2-agent-writer-solid)",
|
||||
}
|
||||
|
||||
const agentThemeColors: Record<string, string> = {
|
||||
@@ -433,19 +423,17 @@ function tone(name: string) {
|
||||
function taskAgent(
|
||||
raw: unknown,
|
||||
list?: readonly { name: string; color?: string }[],
|
||||
): { name?: string; color?: string; v2Color?: string; border?: string; background?: string } {
|
||||
): { name?: string; color?: string; v2Color?: string } {
|
||||
if (typeof raw !== "string" || !raw) return {}
|
||||
const key = raw.toLowerCase()
|
||||
const item = list?.find((entry) => entry.name === raw || entry.name.toLowerCase() === key)
|
||||
const v2Tone = item?.color ? undefined : v2AgentTones[key]
|
||||
const color = agentColor(item?.color, agentThemeColors) ?? agentTones[key] ?? tone(key)
|
||||
const v2Color = agentColor(item?.color, v2AgentThemeColors) ?? v2Tone?.color ?? color
|
||||
const v2Color = agentColor(item?.color, v2AgentThemeColors) ?? v2Tone ?? color
|
||||
return {
|
||||
name: item?.name ?? `${raw[0]!.toUpperCase()}${raw.slice(1)}`,
|
||||
color,
|
||||
v2Color,
|
||||
border: v2Tone?.border ?? `color-mix(in srgb, ${v2Color} 48%, transparent)`,
|
||||
background: v2Tone?.background ?? `color-mix(in srgb, ${v2Color} 12%, transparent)`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1937,8 +1925,6 @@ ToolRegistry.register({
|
||||
const title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
|
||||
const tone = createMemo(() => agent().color)
|
||||
const v2Tone = createMemo(() => agent().v2Color)
|
||||
const border = createMemo(() => agent().border)
|
||||
const background = createMemo(() => agent().background)
|
||||
const subtitle = createMemo(() => {
|
||||
const value =
|
||||
typeof props.input.description === "string" && props.input.description
|
||||
@@ -1983,25 +1969,34 @@ ToolRegistry.register({
|
||||
style={{
|
||||
"--task-agent-color": v2Tone(),
|
||||
"--task-agent-legacy-color": tone(),
|
||||
"--task-agent-border": border(),
|
||||
"--task-agent-background": background(),
|
||||
}}
|
||||
>
|
||||
<div data-slot="basic-tool-tool-info-structured">
|
||||
<div data-slot="basic-tool-tool-info-main">
|
||||
<Show when={running()}>
|
||||
<span data-component="task-tool-spinner" style={{ color: tone() ?? "var(--icon-interactive-base)" }}>
|
||||
<Show when={newLayout()} fallback={<Spinner />}>
|
||||
<SessionProgressIndicatorV2
|
||||
style={{ color: v2Tone() ?? "light-dark(var(--v2-text-text-base), #ffffff)" }}
|
||||
/>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
<span data-component="task-tool-title">{title()}</span>
|
||||
<Show when={subtitle()}>
|
||||
<span data-slot="basic-tool-tool-subtitle">{subtitle()}</span>
|
||||
</Show>
|
||||
<div data-component="task-tool-surface">
|
||||
<div data-slot="basic-tool-tool-info-structured">
|
||||
<div data-slot="basic-tool-tool-info-main">
|
||||
<Show
|
||||
when={running()}
|
||||
fallback={
|
||||
<Show when={newLayout()}>
|
||||
<span data-component="task-tool-icon">
|
||||
<Icon name="subagent" size="small" />
|
||||
</span>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<span data-component="task-tool-spinner" style={{ color: tone() ?? "var(--icon-interactive-base)" }}>
|
||||
<Show when={newLayout()} fallback={<Spinner />}>
|
||||
<SessionProgressIndicatorV2
|
||||
style={{ color: v2Tone() ?? "light-dark(var(--v2-text-text-base), #ffffff)" }}
|
||||
/>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
<span data-component="task-tool-title">{title()}</span>
|
||||
<Show when={subtitle()}>
|
||||
<span data-slot="basic-tool-tool-subtitle">{subtitle()}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={clickable()}>
|
||||
|
||||
@@ -359,6 +359,34 @@ const TOOL_SAMPLES = {
|
||||
title: "Agent (Explore)",
|
||||
metadata: { sessionId: "sub-session-1" },
|
||||
},
|
||||
"task build": {
|
||||
tool: "task",
|
||||
input: { description: "Implement the settings page", subagent_type: "build", prompt: "Build it" },
|
||||
output: "Implemented the settings page.",
|
||||
title: "Agent (Build)",
|
||||
metadata: { sessionId: "sub-session-2" },
|
||||
},
|
||||
"task plan": {
|
||||
tool: "task",
|
||||
input: { description: "Plan the migration strategy", subagent_type: "plan", prompt: "Plan it" },
|
||||
output: "Drafted a 4-step migration plan.",
|
||||
title: "Agent (Plan)",
|
||||
metadata: { sessionId: "sub-session-3" },
|
||||
},
|
||||
"task themed": {
|
||||
tool: "task",
|
||||
input: { description: "Review the diff for regressions", subagent_type: "review", prompt: "Review it" },
|
||||
output: "No regressions found.",
|
||||
title: "Agent (Review)",
|
||||
metadata: { sessionId: "sub-session-4" },
|
||||
},
|
||||
"task fallback": {
|
||||
tool: "task",
|
||||
input: { description: "Write release notes", subagent_type: "writer", prompt: "Write them" },
|
||||
output: "Release notes drafted.",
|
||||
title: "Agent (Writer)",
|
||||
metadata: { sessionId: "sub-session-5" },
|
||||
},
|
||||
webfetch: {
|
||||
tool: "webfetch",
|
||||
input: { url: "https://solidjs.com/docs/latest/api" },
|
||||
@@ -1216,6 +1244,7 @@ function Playground() {
|
||||
|
||||
const data = createMemo(() => ({
|
||||
session: [session()],
|
||||
agent: [{ name: "build" }, { name: "plan" }, { name: "explore" }, { name: "review" }],
|
||||
session_status: {},
|
||||
session_diff: {},
|
||||
message: { [session().id]: state.messages },
|
||||
@@ -1342,6 +1371,19 @@ function Playground() {
|
||||
])
|
||||
}
|
||||
|
||||
const addAgentTasksTurn = () => {
|
||||
addFullTurn("Run subagents with every agent type", [
|
||||
toolPart(TOOL_SAMPLES.task),
|
||||
toolPart(TOOL_SAMPLES["task build"]),
|
||||
toolPart(TOOL_SAMPLES["task plan"]),
|
||||
toolPart(TOOL_SAMPLES["task themed"]),
|
||||
toolPart(TOOL_SAMPLES["task fallback"]),
|
||||
toolPart(TOOL_SAMPLES.task, "running"),
|
||||
toolPart(TOOL_SAMPLES["task build"], "running"),
|
||||
toolPart(TOOL_SAMPLES["task plan"], "running"),
|
||||
])
|
||||
}
|
||||
|
||||
const addKitchenSink = () => {
|
||||
// User message variants
|
||||
addUser("short")
|
||||
@@ -1727,6 +1769,9 @@ function Playground() {
|
||||
<button style={btnStyle} onClick={addReasoningFullTurn}>
|
||||
full turn
|
||||
</button>
|
||||
<button style={btnStyle} onClick={addAgentTasksTurn}>
|
||||
agent tasks
|
||||
</button>
|
||||
<button style={btnAccent} onClick={addKitchenSink}>
|
||||
kitchen sink
|
||||
</button>
|
||||
@@ -1966,7 +2011,7 @@ function Playground() {
|
||||
{/* Main area: timeline preview */}
|
||||
<div
|
||||
ref={previewRef!}
|
||||
style={{ flex: "1", overflow: "auto", "min-width": "0", "background-color": "var(--background-stronger)" }}
|
||||
style={{ flex: "1", overflow: "auto", "min-width": "0", "background-color": "var(--v2-background-bg-base)" }}
|
||||
>
|
||||
<DataProvider data={data()} directory="/project">
|
||||
<FileComponentProvider component={FileStub}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -2,6 +2,11 @@ const dict: Record<string, string> = {
|
||||
"session.todo.title": "Todos",
|
||||
"session.todo.collapse": "Collapse todos",
|
||||
"session.todo.expand": "Expand todos",
|
||||
"session.revertDock.summary.one": "{{count}} rolled back message",
|
||||
"session.revertDock.summary.other": "{{count}} rolled back messages",
|
||||
"session.revertDock.collapse": "Collapse rolled back messages",
|
||||
"session.revertDock.expand": "Expand rolled back messages",
|
||||
"session.revertDock.restore": "Restore message",
|
||||
"prompt.loading": "Loading prompt...",
|
||||
"prompt.placeholder.normal": "Ask anything...",
|
||||
"prompt.placeholder.simple": "Ask anything...",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
const model_id = "claude-3-7-sonnet"
|
||||
|
||||
export const popularProviders = [
|
||||
"opencode",
|
||||
"opencode-go",
|
||||
"anthropic",
|
||||
"github-copilot",
|
||||
"openai",
|
||||
"google",
|
||||
"openrouter",
|
||||
"vercel",
|
||||
]
|
||||
|
||||
const provider = {
|
||||
id: "anthropic",
|
||||
models: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -68,6 +68,7 @@ const names = [
|
||||
"magnifying-glass-menu",
|
||||
"window-cursor",
|
||||
"task",
|
||||
"subagent",
|
||||
"stop",
|
||||
"layout-left",
|
||||
"layout-left-partial",
|
||||
|
||||
@@ -52,6 +52,7 @@ const icons = {
|
||||
"magnifying-glass-menu": `<path d="M2.08325 10.0002H4.58325M2.08325 5.41683H5.41659M2.08325 14.5835H5.41659M16.4583 13.9585L18.7499 16.2502M17.9166 10.0002C17.9166 12.9917 15.4915 15.4168 12.4999 15.4168C9.50838 15.4168 7.08325 12.9917 7.08325 10.0002C7.08325 7.00862 9.50838 4.5835 12.4999 4.5835C15.4915 4.5835 17.9166 7.00862 17.9166 10.0002Z" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
"window-cursor": `<path d="M17.9166 10.4167V3.75H2.08325V17.0833H10.4166M17.9166 13.5897L11.6666 11.6667L13.5897 17.9167L15.032 15.0321L17.9166 13.5897Z" stroke="currentColor" stroke-width="1.07143" stroke-linecap="square"/><path d="M5.00024 6.125C5.29925 6.12518 5.54126 6.36795 5.54126 6.66699C5.54108 6.96589 5.29914 7.20783 5.00024 7.20801C4.7012 7.20801 4.45843 6.966 4.45825 6.66699C4.45825 6.36784 4.70109 6.125 5.00024 6.125ZM7.91626 6.125C8.21541 6.125 8.45825 6.36784 8.45825 6.66699C8.45808 6.966 8.21531 7.20801 7.91626 7.20801C7.61736 7.20783 7.37542 6.96589 7.37524 6.66699C7.37524 6.36795 7.61726 6.12518 7.91626 6.125ZM10.8333 6.125C11.1324 6.125 11.3752 6.36784 11.3752 6.66699C11.3751 6.966 11.1323 7.20801 10.8333 7.20801C10.5342 7.20801 10.2914 6.966 10.2913 6.66699C10.2913 6.36784 10.5341 6.125 10.8333 6.125Z" fill="currentColor" stroke="currentColor" stroke-width="0.25" stroke-linecap="square"/>`,
|
||||
task: `<path d="M9.99992 2.0835V17.9168M7.08325 3.75016H2.08325V16.2502H7.08325M12.9166 16.2502H17.9166V3.75016H12.9166" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
subagent: `<path d="M4.5 5C4.5 4.72386 4.72386 4.5 5 4.5H11C11.2761 4.5 11.5 4.72386 11.5 5V11C11.5 11.2761 11.2761 11.5 11 11.5H5C4.72386 11.5 4.5 11.2761 4.5 11V5Z" fill="currentColor"/><path d="M13.5 2C13.7761 2 14 2.22386 14 2.5V13.5C14 13.7761 13.7761 14 13.5 14H2.5C2.22386 14 2 13.7761 2 13.5V2.5C2 2.22386 2.22386 2 2.5 2H13.5ZM3 13H13V3H3V13Z" fill="currentColor"/>`,
|
||||
stop: `<rect x="5" y="5" width="10" height="10" fill="currentColor"/>`,
|
||||
status: `<path d="M2 10V18H18V10M2 10V2H18V10M2 10H18M5 6H9M5 14H9" stroke="currentColor"/>`,
|
||||
"status-active": `<path d="M18 2H2V10H18V2Z" fill="currentColor" fill-opacity="0.1"/>
|
||||
@@ -110,7 +111,7 @@ const symbol = (name: keyof typeof icons) => `opencode-icon-${name}`
|
||||
let spriteInserted = false
|
||||
|
||||
function viewBox(name: keyof typeof icons) {
|
||||
return name === "magnifying-glass" || name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20"
|
||||
return name === "magnifying-glass" || name === "arrow-undo-down" || name === "subagent" ? "0 0 16 16" : "0 0 20 20"
|
||||
}
|
||||
|
||||
function ensureSprite() {
|
||||
|
||||
@@ -281,15 +281,17 @@
|
||||
--icon-agent-docs-base: #fcb239;
|
||||
--icon-agent-ask-base: #2090f5;
|
||||
--icon-agent-build-base: #034cff;
|
||||
--v2-agent-plan-solid: var(--v2-pink-900);
|
||||
--v2-agent-plan-border: rgba(200, 61, 139, 0.5);
|
||||
--v2-agent-plan-background: rgba(253, 236, 243, 0.33);
|
||||
--v2-agent-build-solid: var(--v2-blue-900);
|
||||
--v2-agent-build-border: rgba(59, 92, 246, 0.5);
|
||||
--v2-agent-build-background: rgba(236, 241, 254, 0.33);
|
||||
--v2-agent-plan-solid: var(--v2-pink-800);
|
||||
--v2-agent-plan-border: rgba(200, 61, 139, 0.2);
|
||||
--v2-agent-plan-background: rgba(253, 236, 243, 0.1);
|
||||
--v2-agent-build-solid: var(--v2-blue-800);
|
||||
--v2-agent-build-border: rgba(44, 71, 200, 0.1);
|
||||
--v2-agent-build-background: rgba(236, 241, 254, 0.1);
|
||||
--v2-agent-explore-solid: var(--v2-yellow-900);
|
||||
--v2-agent-explore-border: rgba(142, 114, 49, 0.5);
|
||||
--v2-agent-explore-background: rgba(254, 250, 236, 0.33);
|
||||
--v2-agent-explore-border: rgba(203, 159, 52, 0.2);
|
||||
--v2-agent-explore-background: rgba(254, 250, 236, 0.1);
|
||||
--v2-agent-review-solid: var(--v2-green-800);
|
||||
--v2-agent-writer-solid: var(--v2-purple-700);
|
||||
--icon-on-success-base: rgba(18, 201, 5, 0.9);
|
||||
--icon-on-success-hover: rgba(45, 186, 38, 0.9);
|
||||
--icon-on-success-selected: rgba(7, 137, 1, 0.9);
|
||||
@@ -551,14 +553,16 @@
|
||||
--icon-agent-ask-base: #2090f5;
|
||||
--icon-agent-build-base: #9dbefe;
|
||||
--v2-agent-plan-solid: var(--v2-pink-400);
|
||||
--v2-agent-plan-border: rgba(250, 188, 216, 0.5);
|
||||
--v2-agent-plan-background: rgba(247, 213, 228, 0.1);
|
||||
--v2-agent-build-solid: var(--v2-blue-400);
|
||||
--v2-agent-build-border: rgba(215, 226, 252, 0.5);
|
||||
--v2-agent-build-background: rgba(215, 226, 252, 0.1);
|
||||
--v2-agent-explore-solid: var(--v2-yellow-400);
|
||||
--v2-agent-explore-border: rgba(247, 229, 181, 0.5);
|
||||
--v2-agent-explore-background: rgba(252, 239, 208, 0.1);
|
||||
--v2-agent-plan-border: rgba(247, 153, 198, 0.2);
|
||||
--v2-agent-plan-background: rgba(170, 53, 118, 0.05);
|
||||
--v2-agent-build-solid: var(--v2-blue-300);
|
||||
--v2-agent-build-border: rgba(162, 188, 255, 0.2);
|
||||
--v2-agent-build-background: rgba(38, 63, 169, 0.05);
|
||||
--v2-agent-explore-solid: var(--v2-yellow-300);
|
||||
--v2-agent-explore-border: rgba(243, 218, 155, 0.2);
|
||||
--v2-agent-explore-background: rgba(172, 136, 51, 0.05);
|
||||
--v2-agent-review-solid: var(--v2-green-300);
|
||||
--v2-agent-writer-solid: var(--v2-purple-400);
|
||||
--icon-on-success-base: rgba(18, 201, 5, 0.9);
|
||||
--icon-on-success-hover: rgba(53, 192, 45, 0.9);
|
||||
--icon-on-success-selected: rgba(77, 225, 68, 0.9);
|
||||
|
||||
@@ -4,27 +4,31 @@ import { V2_AVATAR_DARK, V2_AVATAR_LIGHT } from "./avatar"
|
||||
const ref = (name: string): V2ColorValue => `var(--${name})`
|
||||
|
||||
const lightAgentTokens: Record<string, V2ColorValue> = {
|
||||
"v2-agent-plan-solid": ref("v2-pink-900"),
|
||||
"v2-agent-plan-border": "rgba(200, 61, 139, 0.5)",
|
||||
"v2-agent-plan-background": "rgba(253, 236, 243, 0.33)",
|
||||
"v2-agent-build-solid": ref("v2-blue-900"),
|
||||
"v2-agent-build-border": "rgba(59, 92, 246, 0.5)",
|
||||
"v2-agent-build-background": "rgba(236, 241, 254, 0.33)",
|
||||
"v2-agent-plan-solid": ref("v2-pink-800"),
|
||||
"v2-agent-plan-border": "rgba(200, 61, 139, 0.20)",
|
||||
"v2-agent-plan-background": "rgba(253, 236, 243, 0.10)",
|
||||
"v2-agent-build-solid": ref("v2-blue-800"),
|
||||
"v2-agent-build-border": "rgba(44, 71, 200, 0.20)",
|
||||
"v2-agent-build-background": "rgba(236, 241, 254, 0.10)",
|
||||
"v2-agent-explore-solid": ref("v2-yellow-900"),
|
||||
"v2-agent-explore-border": "rgba(142, 114, 49, 0.5)",
|
||||
"v2-agent-explore-background": "rgba(254, 250, 236, 0.33)",
|
||||
"v2-agent-explore-border": "rgba(203, 159, 52, 0.20)",
|
||||
"v2-agent-explore-background": "rgba(254, 250, 236, 0.1)",
|
||||
"v2-agent-review-solid": ref("v2-green-800"),
|
||||
"v2-agent-writer-solid": ref("v2-purple-700"),
|
||||
}
|
||||
|
||||
const darkAgentTokens: Record<string, V2ColorValue> = {
|
||||
"v2-agent-plan-solid": ref("v2-pink-400"),
|
||||
"v2-agent-plan-border": "rgba(250, 188, 216, 0.5)",
|
||||
"v2-agent-plan-background": "rgba(247, 213, 228, 0.1)",
|
||||
"v2-agent-build-solid": ref("v2-blue-400"),
|
||||
"v2-agent-build-border": "rgba(215, 226, 252, 0.5)",
|
||||
"v2-agent-build-background": "rgba(215, 226, 252, 0.1)",
|
||||
"v2-agent-explore-solid": ref("v2-yellow-400"),
|
||||
"v2-agent-explore-border": "rgba(247, 229, 181, 0.5)",
|
||||
"v2-agent-explore-background": "rgba(252, 239, 208, 0.1)",
|
||||
"v2-agent-plan-border": "rgba(247, 153, 198, 0.20)",
|
||||
"v2-agent-plan-background": "rgba(170, 53, 118, 0.05)",
|
||||
"v2-agent-build-solid": ref("v2-blue-300"),
|
||||
"v2-agent-build-border": "rgba(162, 188, 255, 0.20)",
|
||||
"v2-agent-build-background": "rgba(38, 63, 169, 0.05)",
|
||||
"v2-agent-explore-solid": ref("v2-yellow-300"),
|
||||
"v2-agent-explore-border": "rgba(243, 218, 155, 0.20)",
|
||||
"v2-agent-explore-background": "rgba(172, 136, 51, 0.05)",
|
||||
"v2-agent-review-solid": ref("v2-green-300"),
|
||||
"v2-agent-writer-solid": ref("v2-purple-400"),
|
||||
}
|
||||
|
||||
const light: Record<string, V2ColorValue> = {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 530;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
line-height: 20px;
|
||||
color: var(--v2-text-text-base);
|
||||
text-shadow: none;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface DialogProps extends ParentProps {
|
||||
size?: "normal" | "large" | "x-large"
|
||||
variant?: "default" | "settings"
|
||||
class?: ComponentProps<"div">["class"]
|
||||
containerClass?: ComponentProps<"div">["class"]
|
||||
classList?: ComponentProps<"div">["classList"]
|
||||
fit?: boolean
|
||||
}
|
||||
@@ -79,7 +80,7 @@ export function DialogHeader(props: DialogHeaderProps) {
|
||||
}
|
||||
|
||||
export function Dialog(props: DialogProps) {
|
||||
const [local] = splitProps(props, ["size", "variant", "class", "classList", "fit", "children"])
|
||||
const [local] = splitProps(props, ["size", "variant", "class", "containerClass", "classList", "fit", "children"])
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -88,7 +89,7 @@ export function Dialog(props: DialogProps) {
|
||||
data-fit={local.fit ? true : undefined}
|
||||
data-size={local.size || "normal"}
|
||||
>
|
||||
<div data-slot="dialog-container">
|
||||
<div data-slot="dialog-container" class={local.containerClass}>
|
||||
<Kobalte.Content
|
||||
data-slot="dialog-content"
|
||||
classList={{
|
||||
|
||||
@@ -133,6 +133,10 @@ const icons = {
|
||||
viewBox: "0 0 20 20",
|
||||
body: `<path d="M5.83333 4.16406L2.5 7.4974L5.83333 10.8307M3.33333 7.4974H17.9167V15.4141H10" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
},
|
||||
"outline-reset": {
|
||||
viewBox: "0 0 20 20",
|
||||
body: `<path d="M5.83333 4.16406L2.5 7.4974L5.83333 10.8307M3.33333 7.4974H17.9167V15.4141H10" stroke="currentColor" stroke-linecap="square"/>`,
|
||||
},
|
||||
archive: {
|
||||
viewBox: "0 0 16 16",
|
||||
body: `<path d="M13.1112 13.5555V14.0555H13.6112V13.5555H13.1112ZM2.889 13.5555H2.389L2.389 14.0555H2.889V13.5555ZM3.38901 5.55546L3.38901 5.05546L2.38901 5.05546L2.38901 5.55546L2.88901 5.55546L3.38901 5.55546ZM14.4446 2.44434H14.9446V1.94434L14.4446 1.94434L14.4446 2.44434ZM14.4446 5.55545L14.4446 6.05545L14.9446 6.05545V5.55545H14.4446ZM1.55566 5.55546L1.05566 5.55545L1.05566 6.05546L1.55566 6.05546L1.55566 5.55546ZM1.5557 2.44436L1.5557 1.94436L1.05571 1.94436L1.0557 2.44435L1.5557 2.44436ZM13.1112 5.55546H12.6112V13.5555H13.1112H13.6112V5.55546H13.1112ZM2.889 13.5555H3.389L3.38901 5.55546L2.88901 5.55546L2.38901 5.55546L2.389 13.5555H2.889ZM14.4446 2.44434H13.9446V5.55545H14.4446H14.9446V2.44434H14.4446ZM1.55566 5.55546L2.05566 5.55547L2.0557 2.44436L1.5557 2.44436L1.0557 2.44435L1.05566 5.55545L1.55566 5.55546ZM6.22234 8.22213V8.72213H9.7779V8.22213V7.72213H6.22234V8.22213ZM13.1112 13.5555V13.0555H2.889V13.5555V14.0555H13.1112V13.5555ZM1.5557 2.44436L1.5557 2.94436L14.4446 2.94434L14.4446 2.44434L14.4446 1.94434L1.5557 1.94436L1.5557 2.44436ZM14.4446 5.55545L14.4446 5.05545L1.55566 5.05546L1.55566 5.55546L1.55566 6.05546L14.4446 6.05545L14.4446 5.55545Z" fill="currentColor"/>`,
|
||||
|
||||
@@ -77,15 +77,17 @@
|
||||
--v2-state-fg-info: var(--v2-blue-800);
|
||||
--v2-state-border-info: var(--v2-blue-300);
|
||||
|
||||
--v2-agent-plan-solid: var(--v2-pink-900);
|
||||
--v2-agent-plan-border: rgba(200, 61, 139, 0.5);
|
||||
--v2-agent-plan-background: rgba(253, 236, 243, 0.33);
|
||||
--v2-agent-build-solid: var(--v2-blue-900);
|
||||
--v2-agent-build-border: rgba(59, 92, 246, 0.5);
|
||||
--v2-agent-build-background: rgba(236, 241, 254, 0.33);
|
||||
--v2-agent-plan-solid: var(--v2-pink-800);
|
||||
--v2-agent-plan-border: rgba(200, 61, 139, 0.2);
|
||||
--v2-agent-plan-background: rgba(253, 236, 243, 0.1);
|
||||
--v2-agent-build-solid: var(--v2-blue-800);
|
||||
--v2-agent-build-border: rgba(44, 71, 200, 0.1);
|
||||
--v2-agent-build-background: rgba(236, 241, 254, 0.1);
|
||||
--v2-agent-explore-solid: var(--v2-yellow-900);
|
||||
--v2-agent-explore-border: rgba(142, 114, 49, 0.5);
|
||||
--v2-agent-explore-background: rgba(254, 250, 236, 0.33);
|
||||
--v2-agent-explore-border: rgba(203, 159, 52, 0.2);
|
||||
--v2-agent-explore-background: rgba(254, 250, 236, 0.1);
|
||||
--v2-agent-review-solid: var(--v2-green-800);
|
||||
--v2-agent-writer-solid: var(--v2-purple-700);
|
||||
|
||||
/* ── Project avatar (fixed; theme-independent) ── */
|
||||
--v2-avatar-fg: #ffffffff;
|
||||
@@ -316,15 +318,17 @@
|
||||
--v2-state-fg-info: var(--v2-blue-800);
|
||||
--v2-state-border-info: var(--v2-blue-300);
|
||||
|
||||
--v2-agent-plan-solid: var(--v2-pink-900);
|
||||
--v2-agent-plan-border: rgba(200, 61, 139, 0.5);
|
||||
--v2-agent-plan-background: rgba(253, 236, 243, 0.33);
|
||||
--v2-agent-build-solid: var(--v2-blue-900);
|
||||
--v2-agent-build-border: rgba(59, 92, 246, 0.5);
|
||||
--v2-agent-build-background: rgba(236, 241, 254, 0.33);
|
||||
--v2-agent-plan-solid: var(--v2-pink-800);
|
||||
--v2-agent-plan-border: rgba(200, 61, 139, 0.2);
|
||||
--v2-agent-plan-background: rgba(253, 236, 243, 0.1);
|
||||
--v2-agent-build-solid: var(--v2-blue-800);
|
||||
--v2-agent-build-border: rgba(44, 71, 200, 0.1);
|
||||
--v2-agent-build-background: rgba(236, 241, 254, 0.1);
|
||||
--v2-agent-explore-solid: var(--v2-yellow-900);
|
||||
--v2-agent-explore-border: rgba(142, 114, 49, 0.5);
|
||||
--v2-agent-explore-background: rgba(254, 250, 236, 0.33);
|
||||
--v2-agent-explore-border: rgba(203, 159, 52, 0.2);
|
||||
--v2-agent-explore-background: rgba(254, 250, 236, 0.1);
|
||||
--v2-agent-review-solid: var(--v2-green-800);
|
||||
--v2-agent-writer-solid: var(--v2-purple-700);
|
||||
|
||||
--v2-elevation-raised:
|
||||
0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8),
|
||||
@@ -437,14 +441,16 @@
|
||||
--v2-state-border-info: var(--v2-blue-900);
|
||||
|
||||
--v2-agent-plan-solid: var(--v2-pink-400);
|
||||
--v2-agent-plan-border: rgba(250, 188, 216, 0.5);
|
||||
--v2-agent-plan-background: rgba(247, 213, 228, 0.1);
|
||||
--v2-agent-build-solid: var(--v2-blue-400);
|
||||
--v2-agent-build-border: rgba(215, 226, 252, 0.5);
|
||||
--v2-agent-build-background: rgba(215, 226, 252, 0.1);
|
||||
--v2-agent-explore-solid: var(--v2-yellow-400);
|
||||
--v2-agent-explore-border: rgba(247, 229, 181, 0.5);
|
||||
--v2-agent-explore-background: rgba(252, 239, 208, 0.1);
|
||||
--v2-agent-plan-border: rgba(247, 153, 198, 0.2);
|
||||
--v2-agent-plan-background: rgba(170, 53, 118, 0.05);
|
||||
--v2-agent-build-solid: var(--v2-blue-300);
|
||||
--v2-agent-build-border: rgba(162, 188, 255, 0.2);
|
||||
--v2-agent-build-background: rgba(38, 63, 169, 0.05);
|
||||
--v2-agent-explore-solid: var(--v2-yellow-300);
|
||||
--v2-agent-explore-border: rgba(243, 218, 155, 0.2);
|
||||
--v2-agent-explore-background: rgba(172, 136, 51, 0.05);
|
||||
--v2-agent-review-solid: var(--v2-green-300);
|
||||
--v2-agent-writer-solid: var(--v2-purple-400);
|
||||
|
||||
--v2-avatar-fg: #ffffffff;
|
||||
--v2-avatar-bg-orange: #723d22ff;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.17.15",
|
||||
"version": "1.17.18",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user