Compare commits
90
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f201d6370 | ||
|
|
0e86466f99 | ||
|
|
32548bcb4a | ||
|
|
86c54c5acc | ||
|
|
ae584332b3 | ||
|
|
1694c5bfe1 | ||
|
|
cdfbb26c00 | ||
|
|
610c036ef1 | ||
|
|
2638e2acfa | ||
|
|
49bbea5aed | ||
|
|
5fccdc9fc7 | ||
|
|
664b2c36e8 | ||
|
|
964474a1b1 | ||
|
|
ab15fc1575 | ||
|
|
99d392a4fb | ||
|
|
ae9a696607 | ||
|
|
bd51a0d35b | ||
|
|
8c191b10c2 | ||
|
|
cb6a9253fe | ||
|
|
23f97ac49d | ||
|
|
021ab50fb1 | ||
|
|
3fe906f517 | ||
|
|
a8d8a35cd3 | ||
|
|
9b77430d0d | ||
|
|
1045a43603 | ||
|
|
26af77cd1e | ||
|
|
25a9de301a | ||
|
|
e0d71f124e | ||
|
|
1c33b866ba | ||
|
|
5e650fd9e2 | ||
|
|
76275fc3ab | ||
|
|
6c3b28db64 | ||
|
|
2fe9d94470 | ||
|
|
219b473e66 | ||
|
|
7c1b30291c | ||
|
|
47e0e2342c | ||
|
|
bf4c107829 | ||
|
|
9afbdc102c | ||
|
|
370770122c | ||
|
|
143817d44e | ||
|
|
c60862fc9e | ||
|
|
bee5f919fc | ||
|
|
cefa7f04c6 | ||
|
|
03e20e6ac1 | ||
|
|
c5deeee8c7 | ||
|
|
8b1f0e2d90 | ||
|
|
9bf2dfea35 | ||
|
|
33bb847a1d | ||
|
|
bfffc3c2c6 | ||
|
|
b28956f0db | ||
|
|
d82bc3a421 | ||
|
|
06afd33291 | ||
|
|
305460b25f | ||
|
|
8c0205a84a | ||
|
|
378c05f202 | ||
|
|
cc7acd90ab | ||
|
|
a200f6fb8b | ||
|
|
2b1696f1d1 | ||
|
|
8ab17f5ce0 | ||
|
|
6ce481e95b | ||
|
|
7341718f92 | ||
|
|
ef90b93205 | ||
|
|
3f7df08be9 | ||
|
|
ef6c26c730 | ||
|
|
8b3b608ba9 | ||
|
|
97918500d4 | ||
|
|
e2c0803962 | ||
|
|
f418fd5632 | ||
|
|
675a46e23e | ||
|
|
150ab07a83 | ||
|
|
6b20838981 | ||
|
|
c8af8f96ce | ||
|
|
5011465c81 | ||
|
|
f6cc228684 | ||
|
|
9f4b73b6a3 | ||
|
|
bd29004831 | ||
|
|
8aa0f9fe95 | ||
|
|
c802695ee9 | ||
|
|
225a769411 | ||
|
|
0e20382396 | ||
|
|
509bc11f81 | ||
|
|
f24207844f | ||
|
|
1ca257e356 | ||
|
|
d4cfbd020d | ||
|
|
581d5208ca | ||
|
|
a427a28fa9 | ||
|
|
0beaf04df5 | ||
|
|
80f1f1b5b8 | ||
|
|
343a564183 | ||
|
|
b0eae5e12f |
@@ -7,7 +7,7 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
|||||||
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...options.headers,
|
...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
|||||||
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...options.headers,
|
...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
+17
-2
@@ -1,9 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
|
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
|
||||||
|
"options": {
|
||||||
|
"typeAware": true
|
||||||
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"suspicious": "warn"
|
"suspicious": "warn"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"typescript/no-base-to-string": "warn",
|
||||||
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
|
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
|
||||||
"require-yield": "off",
|
"require-yield": "off",
|
||||||
// SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime
|
// SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime
|
||||||
@@ -30,7 +34,18 @@
|
|||||||
// postMessage target origin not relevant for this codebase
|
// postMessage target origin not relevant for this codebase
|
||||||
"unicorn/require-post-message-target-origin": "off",
|
"unicorn/require-post-message-target-origin": "off",
|
||||||
// Side-effectful constructors are intentional in some places
|
// Side-effectful constructors are intentional in some places
|
||||||
"no-new": "off"
|
"no-new": "off",
|
||||||
|
|
||||||
|
// Type-aware: catch unhandled promises
|
||||||
|
"typescript/no-floating-promises": "warn",
|
||||||
|
// Warn when spreading non-plain objects (Headers, class instances, etc.)
|
||||||
|
"typescript/no-misused-spread": "warn"
|
||||||
},
|
},
|
||||||
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts"]
|
"options": {
|
||||||
|
"typeAware": true
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"typeAware": true
|
||||||
|
},
|
||||||
|
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts", "**/sdk.gen.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"glob": "13.0.5",
|
"glob": "13.0.5",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"oxlint": "1.60.0",
|
"oxlint": "1.60.0",
|
||||||
|
"oxlint-tsgolint": "0.21.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"sst": "3.18.10",
|
"sst": "3.18.10",
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -82,7 +83,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -116,7 +117,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -143,7 +144,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.64",
|
"@ai-sdk/anthropic": "3.0.64",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
@@ -167,7 +168,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -191,7 +192,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -224,7 +225,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop-electron": {
|
"packages/desktop-electron": {
|
||||||
"name": "@opencode-ai/desktop-electron",
|
"name": "@opencode-ai/desktop-electron",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"electron-context-menu": "4.1.2",
|
"electron-context-menu": "4.1.2",
|
||||||
@@ -267,7 +268,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/shared": "workspace:*",
|
"@opencode-ai/shared": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
@@ -296,7 +297,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -312,7 +313,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -321,15 +322,15 @@
|
|||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.16.1",
|
"@agentclientprotocol/sdk": "0.16.1",
|
||||||
"@ai-sdk/alibaba": "1.0.17",
|
"@ai-sdk/alibaba": "1.0.17",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.93",
|
"@ai-sdk/amazon-bedrock": "4.0.94",
|
||||||
"@ai-sdk/anthropic": "3.0.67",
|
"@ai-sdk/anthropic": "3.0.70",
|
||||||
"@ai-sdk/azure": "3.0.49",
|
"@ai-sdk/azure": "3.0.49",
|
||||||
"@ai-sdk/cerebras": "2.0.41",
|
"@ai-sdk/cerebras": "2.0.41",
|
||||||
"@ai-sdk/cohere": "3.0.27",
|
"@ai-sdk/cohere": "3.0.27",
|
||||||
"@ai-sdk/deepinfra": "2.0.41",
|
"@ai-sdk/deepinfra": "2.0.41",
|
||||||
"@ai-sdk/gateway": "3.0.97",
|
"@ai-sdk/gateway": "3.0.102",
|
||||||
"@ai-sdk/google": "3.0.63",
|
"@ai-sdk/google": "3.0.63",
|
||||||
"@ai-sdk/google-vertex": "4.0.109",
|
"@ai-sdk/google-vertex": "4.0.111",
|
||||||
"@ai-sdk/groq": "3.0.31",
|
"@ai-sdk/groq": "3.0.31",
|
||||||
"@ai-sdk/mistral": "3.0.27",
|
"@ai-sdk/mistral": "3.0.27",
|
||||||
"@ai-sdk/openai": "3.0.53",
|
"@ai-sdk/openai": "3.0.53",
|
||||||
@@ -457,7 +458,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -492,7 +493,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -507,7 +508,7 @@
|
|||||||
},
|
},
|
||||||
"packages/shared": {
|
"packages/shared": {
|
||||||
"name": "@opencode-ai/shared",
|
"name": "@opencode-ai/shared",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -515,6 +516,7 @@
|
|||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@npmcli/arborist": "catalog:",
|
"@npmcli/arborist": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
"glob": "13.0.5",
|
||||||
"mime-types": "3.0.2",
|
"mime-types": "3.0.2",
|
||||||
"minimatch": "10.2.5",
|
"minimatch": "10.2.5",
|
||||||
"semver": "catalog:",
|
"semver": "catalog:",
|
||||||
@@ -522,13 +524,15 @@
|
|||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tsconfig/bun": "catalog:",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
|
"@types/npmcli__arborist": "6.3.3",
|
||||||
"@types/semver": "catalog:",
|
"@types/semver": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -563,7 +567,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -612,7 +616,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -734,7 +738,7 @@
|
|||||||
|
|
||||||
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.94", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.70", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XKE7wAjXejsIfNQvn3onvGUByhGHVM6W+xlL+1DAQLmjEb+ue4sOJIRehJ96rEvTXVVHRVyA6bSXx7ayxXfn5A=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="],
|
||||||
|
|
||||||
@@ -754,11 +758,11 @@
|
|||||||
|
|
||||||
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.46", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XRKR0zgRyegdmtK5CDUEjlyRp0Fo+XVCdoG+301U1SGtgRIAYG3ObVtgzVJBVpJdHFSLHuYeLTnNiQoUxD7+FQ=="],
|
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.46", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XRKR0zgRyegdmtK5CDUEjlyRp0Fo+XVCdoG+301U1SGtgRIAYG3ObVtgzVJBVpJdHFSLHuYeLTnNiQoUxD7+FQ=="],
|
||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.97", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ERHmVGX30YKTwxObuHQzNqoOf8Nb5WwYMDBn34e3TGGVn0vLEXwMimo7uRVTbhhi4gfu9WtwYTE4x1+csZok1w=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.102", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GrwDpaYJiVafrsA1MTbZtXPcQUI67g5AXiJo7Y1F8b+w+SiYHLk3ZIn1YmpQVoVAh2bjvxjj+Vo0AvfskuGH4g=="],
|
||||||
|
|
||||||
"@ai-sdk/google": ["@ai-sdk/google@3.0.63", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RfOZWVMYSPu2sPRfGajrauWAZ9BSaRopSn+AszkKWQ1MFj8nhaXvCqRHB5pBQUaHTfZKagvOmMpNfa/s3gPLgQ=="],
|
"@ai-sdk/google": ["@ai-sdk/google@3.0.63", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RfOZWVMYSPu2sPRfGajrauWAZ9BSaRopSn+AszkKWQ1MFj8nhaXvCqRHB5pBQUaHTfZKagvOmMpNfa/s3gPLgQ=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.109", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/google": "3.0.63", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-QzQ+DgOoSYlkU4mK0H+iaCaW1bl5zOimH9X2E2oylcVyUtAdCuduQ959Uw1ygW3l09J2K/ceEDtK8OUPHyOA7g=="],
|
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.111", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.70", "@ai-sdk/google": "3.0.64", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5gILpAWWI5idfal/MfoH3tlQeSnOJ9jfL8JB8m2fdc3ue/9xoXkYDpXpDL/nyJImFjMCi6eR0Fpvlo/IKEWDIg=="],
|
||||||
|
|
||||||
"@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
|
"@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
|
||||||
|
|
||||||
@@ -1680,6 +1684,18 @@
|
|||||||
|
|
||||||
"@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.96.0", "", { "os": "win32", "cpu": "x64" }, "sha512-0fI0P0W7bSO/GCP/N5dkmtB9vBqCA4ggo1WmXTnxNJVmFFOtcA1vYm1I9jl8fxo+sucW2WnlpnI4fjKdo3JKxA=="],
|
"@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.96.0", "", { "os": "win32", "cpu": "x64" }, "sha512-0fI0P0W7bSO/GCP/N5dkmtB9vBqCA4ggo1WmXTnxNJVmFFOtcA1vYm1I9jl8fxo+sucW2WnlpnI4fjKdo3JKxA=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.21.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P20j3MLqfwIT+94qGU3htC7dWp4pXGZW1p1p7FRUzu1aopq7c9nPCgf0W/WjktqQ57+iuTq9mbSlwWinl6+H1A=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.21.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-81TmmuBcPedEA0MwRmObuQuXnCprS1UiHQWGe7pseqNAJzUWXeAPrayqKTACX92VpruJI+yvY0XJrFp11PpcTA=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.21.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-sbjBr6zDduX8rNO0PTjhf7VYLCPWqdijWiMPp8e10qu6Tam1GdaVLaLlX8QrNupTgglO1GvqqgY/jcacWL8a6g=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.21.0", "", { "os": "linux", "cpu": "x64" }, "sha512-jNrOcy53R5TJQfrK444Cm60bW9437xDoxPbm3AdvFSo/fhdFMllawc7uZC2Wzr+EAjTkW13K8R4QHzsUdBG9fQ=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.21.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-xWeRxJJILDE4b9UqHEWGBxcBc1TUS6zWHhxcyxTZMwf4q3wdKeu0OHYAcwLGJzoSjEIf6FTjyfPiRNil2oqsdg=="],
|
||||||
|
|
||||||
|
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.21.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Ob9AA9teI8ckPo1whV1smLr5NrqwgBv/8boDbK0YZG+fKgNGRwr1hBj1ORgFWOQaUBv+5njp5A0RAfJJjQ95QQ=="],
|
||||||
|
|
||||||
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-YdeJKaZckDQL1qa62a1aKq/goyq48aX3yOxaaWqWb4sau4Ee4IiLbamftNLU3zbePky6QsDj6thnSSzHRBjDfA=="],
|
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-YdeJKaZckDQL1qa62a1aKq/goyq48aX3yOxaaWqWb4sau4Ee4IiLbamftNLU3zbePky6QsDj6thnSSzHRBjDfA=="],
|
||||||
|
|
||||||
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-7ANS7PpXCfq84xZQ8E5WPs14gwcuPcl+/8TFNXfpSu0CQBXz3cUo2fDpHT8v8HJN+Ut02eacvMAzTnc9s6X4tw=="],
|
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-7ANS7PpXCfq84xZQ8E5WPs14gwcuPcl+/8TFNXfpSu0CQBXz3cUo2fDpHT8v8HJN+Ut02eacvMAzTnc9s6X4tw=="],
|
||||||
@@ -4100,6 +4116,8 @@
|
|||||||
|
|
||||||
"oxlint": ["oxlint@1.60.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.60.0", "@oxlint/binding-android-arm64": "1.60.0", "@oxlint/binding-darwin-arm64": "1.60.0", "@oxlint/binding-darwin-x64": "1.60.0", "@oxlint/binding-freebsd-x64": "1.60.0", "@oxlint/binding-linux-arm-gnueabihf": "1.60.0", "@oxlint/binding-linux-arm-musleabihf": "1.60.0", "@oxlint/binding-linux-arm64-gnu": "1.60.0", "@oxlint/binding-linux-arm64-musl": "1.60.0", "@oxlint/binding-linux-ppc64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-musl": "1.60.0", "@oxlint/binding-linux-s390x-gnu": "1.60.0", "@oxlint/binding-linux-x64-gnu": "1.60.0", "@oxlint/binding-linux-x64-musl": "1.60.0", "@oxlint/binding-openharmony-arm64": "1.60.0", "@oxlint/binding-win32-arm64-msvc": "1.60.0", "@oxlint/binding-win32-ia32-msvc": "1.60.0", "@oxlint/binding-win32-x64-msvc": "1.60.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-tnRzTWiWJ9pg3ftRWnD0+Oqh78L6ZSwcEudvCZaER0PIqiAnNyXj5N1dPwjmNpDalkKS9m/WMLN1CTPUBPmsgw=="],
|
"oxlint": ["oxlint@1.60.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.60.0", "@oxlint/binding-android-arm64": "1.60.0", "@oxlint/binding-darwin-arm64": "1.60.0", "@oxlint/binding-darwin-x64": "1.60.0", "@oxlint/binding-freebsd-x64": "1.60.0", "@oxlint/binding-linux-arm-gnueabihf": "1.60.0", "@oxlint/binding-linux-arm-musleabihf": "1.60.0", "@oxlint/binding-linux-arm64-gnu": "1.60.0", "@oxlint/binding-linux-arm64-musl": "1.60.0", "@oxlint/binding-linux-ppc64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-musl": "1.60.0", "@oxlint/binding-linux-s390x-gnu": "1.60.0", "@oxlint/binding-linux-x64-gnu": "1.60.0", "@oxlint/binding-linux-x64-musl": "1.60.0", "@oxlint/binding-openharmony-arm64": "1.60.0", "@oxlint/binding-win32-arm64-msvc": "1.60.0", "@oxlint/binding-win32-ia32-msvc": "1.60.0", "@oxlint/binding-win32-x64-msvc": "1.60.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-tnRzTWiWJ9pg3ftRWnD0+Oqh78L6ZSwcEudvCZaER0PIqiAnNyXj5N1dPwjmNpDalkKS9m/WMLN1CTPUBPmsgw=="],
|
||||||
|
|
||||||
|
"oxlint-tsgolint": ["oxlint-tsgolint@0.21.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.21.0", "@oxlint-tsgolint/darwin-x64": "0.21.0", "@oxlint-tsgolint/linux-arm64": "0.21.0", "@oxlint-tsgolint/linux-x64": "0.21.0", "@oxlint-tsgolint/win32-arm64": "0.21.0", "@oxlint-tsgolint/win32-x64": "0.21.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-HiWPhANwRnN1pZJQ2SgNB3WRR+1etLJHmRzQ/MJhyINsEIaOUCjxhlXJKbEaVUwdnyXwRWqo/P9Fx21lz0/mSg=="],
|
||||||
|
|
||||||
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
|
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
|
||||||
|
|
||||||
"p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="],
|
"p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="],
|
||||||
@@ -5134,7 +5152,11 @@
|
|||||||
|
|
||||||
"@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
"@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.70", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hubTFcfnG3NbrlcDW0tU2fsZhRy/7dF5GCymu4DzBQUYliy2lb7tCeeMhDtFBaYa01qSBHRjkwGnsAdUtDPCwA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.13", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||||
|
|
||||||
@@ -5148,7 +5170,9 @@
|
|||||||
|
|
||||||
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="],
|
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.70", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hubTFcfnG3NbrlcDW0tU2fsZhRy/7dF5GCymu4DzBQUYliy2lb7tCeeMhDtFBaYa01qSBHRjkwGnsAdUtDPCwA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
"@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||||
|
|
||||||
@@ -5666,6 +5690,8 @@
|
|||||||
|
|
||||||
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="],
|
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="],
|
||||||
|
|
||||||
|
"ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="],
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="],
|
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="],
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uz8tIlkDgQJG9Js2Wh9JHzd4kI9+hYJqf9XXJLx60vyN5mRIqhr49iwR5zGP5Gl8odp2PeR3Gh2k+5bh3Z1HHw=="],
|
"ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uz8tIlkDgQJG9Js2Wh9JHzd4kI9+hYJqf9XXJLx60vyN5mRIqhr49iwR5zGP5Gl8odp2PeR3Gh2k+5bh3Z1HHw=="],
|
||||||
@@ -5882,7 +5908,7 @@
|
|||||||
|
|
||||||
"nypm/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
|
"nypm/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FFX4P5Fd6lcQJc2OLngZQkbbJHa0IDDZi087Edb8qRZx6h90krtM61ArbMUL8us/7ZUwojCXnyJ/wQ2Eflx2jQ=="],
|
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.70", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hubTFcfnG3NbrlcDW0tU2fsZhRy/7dF5GCymu4DzBQUYliy2lb7tCeeMhDtFBaYa01qSBHRjkwGnsAdUtDPCwA=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
|
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -513,7 +513,7 @@ async function subscribeSessionEvents() {
|
|||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
|
|
||||||
let text = ""
|
let text = ""
|
||||||
;(async () => {
|
void (async () => {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const { done, value } = await reader.read()
|
const { done, value } = await reader.read()
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-VIgTxIjmZ4Bfwwdj/YFmRJdBpPHYhJSY31kh06EXX+0=",
|
"x86_64-linux": "sha256-tYAb5Mo39UW1VEejYuo0jW0jzH2OyY/HrqgiZL3rmjY=",
|
||||||
"aarch64-linux": "sha256-9118AS1ED0nrliURgZYBRuF/18RqXpUouhYJRlZ6jeA=",
|
"aarch64-linux": "sha256-3zGKV5UwokXpmY0nT1mry3IhNf2EQYLKT7ac+/trmQA=",
|
||||||
"aarch64-darwin": "sha256-ppo3MfSIGKQHJCdYEZiLFRc61PtcJ9J0kAXH1pNIonA=",
|
"aarch64-darwin": "sha256-oKXAut7eu/eW5a43OT8+aFuH1F1tuIldTs+7PUXSCv4=",
|
||||||
"x86_64-darwin": "sha256-m+CZSOglBCTfNzbdBX6hXdDqqOzHNMzAddVp6BZVDtU="
|
"x86_64-darwin": "sha256-Az+9X1scOEhw3aOO8laKJoZjiuz3qlLTIk1bx25P/z4="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ stdenvNoCC.mkDerivation {
|
|||||||
--filter './packages/opencode' \
|
--filter './packages/opencode' \
|
||||||
--filter './packages/desktop' \
|
--filter './packages/desktop' \
|
||||||
--filter './packages/app' \
|
--filter './packages/app' \
|
||||||
|
--filter './packages/shared' \
|
||||||
--frozen-lockfile \
|
--frozen-lockfile \
|
||||||
--ignore-scripts \
|
--ignore-scripts \
|
||||||
--no-progress
|
--no-progress
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
"glob": "13.0.5",
|
"glob": "13.0.5",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"oxlint": "1.60.0",
|
"oxlint": "1.60.0",
|
||||||
|
"oxlint-tsgolint": "0.21.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"sst": "3.18.10",
|
"sst": "3.18.10",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -121,10 +121,10 @@ function SessionProviders(props: ParentProps) {
|
|||||||
function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
|
function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
|
||||||
return (
|
return (
|
||||||
<AppShellProviders>
|
<AppShellProviders>
|
||||||
<Suspense fallback={<Loading />}>
|
{/*<Suspense fallback={<Loading />}>*/}
|
||||||
{props.appChildren}
|
{props.appChildren}
|
||||||
{props.children}
|
{props.children}
|
||||||
</Suspense>
|
{/*</Suspense>*/}
|
||||||
</AppShellProviders>
|
</AppShellProviders>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -184,32 +184,41 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show
|
<Suspense
|
||||||
when={checkMode() === "blocking" ? !startupHealthCheck.loading : startupHealthCheck.state !== "pending"}
|
|
||||||
fallback={
|
fallback={
|
||||||
<div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base">
|
<div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base">
|
||||||
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
|
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{/*<Show
|
||||||
|
when={checkMode() === "blocking" ? !startupHealthCheck.loading : startupHealthCheck.state !== "pending"}
|
||||||
|
fallback={
|
||||||
|
<div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base">
|
||||||
|
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>*/}
|
||||||
|
{checkMode() === "blocking" ? startupHealthCheck() : startupHealthCheck.latest}
|
||||||
<Show
|
<Show
|
||||||
when={startupHealthCheck()}
|
when={startupHealthCheck()}
|
||||||
fallback={
|
fallback={
|
||||||
<ConnectionError
|
<ConnectionError
|
||||||
onRetry={() => {
|
onRetry={() => {
|
||||||
if (checkMode() === "background") healthCheckActions.refetch()
|
if (checkMode() === "background") void healthCheckActions.refetch()
|
||||||
}}
|
}}
|
||||||
onServerSelected={(key) => {
|
onServerSelected={(key) => {
|
||||||
setCheckMode("blocking")
|
setCheckMode("blocking")
|
||||||
server.setActive(key)
|
server.setActive(key)
|
||||||
healthCheckActions.refetch()
|
void healthCheckActions.refetch()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
{/*</Show>*/}
|
||||||
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
if (loading()) return
|
if (loading()) return
|
||||||
if (methods().length === 1) {
|
if (methods().length === 1) {
|
||||||
auto = true
|
auto = true
|
||||||
selectMethod(0)
|
void selectMethod(0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
|||||||
key={(m) => m?.label}
|
key={(m) => m?.label}
|
||||||
onSelect={async (selected, index) => {
|
onSelect={async (selected, index) => {
|
||||||
if (!selected) return
|
if (!selected) return
|
||||||
selectMethod(index)
|
void selectMethod(index)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => (
|
{(i) => (
|
||||||
|
|||||||
@@ -348,8 +348,8 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
|
|
||||||
const open = (path: string) => {
|
const open = (path: string) => {
|
||||||
const value = file.tab(path)
|
const value = file.tab(path)
|
||||||
tabs().open(value)
|
void tabs().open(value)
|
||||||
file.load(path)
|
void file.load(path)
|
||||||
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
props.onOpenFile?.(path)
|
props.onOpenFile?.(path)
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ export function DialogSelectServer() {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
items()
|
items()
|
||||||
refreshHealth()
|
void refreshHealth()
|
||||||
const interval = setInterval(refreshHealth, 10_000)
|
const interval = setInterval(refreshHealth, 10_000)
|
||||||
onCleanup(() => clearInterval(interval))
|
onCleanup(() => clearInterval(interval))
|
||||||
})
|
})
|
||||||
@@ -498,7 +498,7 @@ export function DialogSelectServer() {
|
|||||||
async function handleRemove(url: ServerConnection.Key) {
|
async function handleRemove(url: ServerConnection.Key) {
|
||||||
server.remove(url)
|
server.remove(url)
|
||||||
if ((await platform.getDefaultServer?.()) === url) {
|
if ((await platform.getDefaultServer?.()) === url) {
|
||||||
platform.setDefaultServer?.(null)
|
void platform.setDefaultServer?.(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,7 +536,7 @@ export function DialogSelectServer() {
|
|||||||
items={sortedItems}
|
items={sortedItems}
|
||||||
key={(x) => x.http.url}
|
key={(x) => x.http.url}
|
||||||
onSelect={(x) => {
|
onSelect={(x) => {
|
||||||
if (x) select(x)
|
if (x) void select(x)
|
||||||
}}
|
}}
|
||||||
divider={true}
|
divider={true}
|
||||||
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
|
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ import { PromptImageAttachments } from "./prompt-input/image-attachments"
|
|||||||
import { PromptDragOverlay } from "./prompt-input/drag-overlay"
|
import { PromptDragOverlay } from "./prompt-input/drag-overlay"
|
||||||
import { promptPlaceholder } from "./prompt-input/placeholder"
|
import { promptPlaceholder } from "./prompt-input/placeholder"
|
||||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||||
|
import { useQuery } from "@tanstack/solid-query"
|
||||||
|
import { loadAgentsQuery, loadProvidersQuery } from "@/context/global-sync/bootstrap"
|
||||||
|
|
||||||
interface PromptInputProps {
|
interface PromptInputProps {
|
||||||
class?: string
|
class?: string
|
||||||
@@ -100,6 +102,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
|
|||||||
|
|
||||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const files = useFile()
|
const files = useFile()
|
||||||
@@ -212,9 +215,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
const tab = files.tab(item.path)
|
const tab = files.tab(item.path)
|
||||||
tabs().open(tab)
|
void tabs().open(tab)
|
||||||
tabs().setActive(tab)
|
tabs().setActive(tab)
|
||||||
Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
|
void Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
|
||||||
}
|
}
|
||||||
|
|
||||||
const recent = createMemo(() => {
|
const recent = createMemo(() => {
|
||||||
@@ -1139,7 +1142,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (working()) {
|
if (working()) {
|
||||||
abort()
|
void abort()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
return
|
return
|
||||||
@@ -1205,7 +1208,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (working()) {
|
if (working()) {
|
||||||
abort()
|
void abort()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -1245,10 +1248,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleSubmit(event)
|
void handleSubmit(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const agentsQuery = useQuery(() => loadAgentsQuery(sdk.directory))
|
||||||
|
const agentsLoading = () => agentsQuery.isLoading
|
||||||
|
|
||||||
|
const globalProvidersQuery = useQuery(() => loadProvidersQuery(null))
|
||||||
|
const providersQuery = useQuery(() => loadProvidersQuery(sdk.directory))
|
||||||
|
|
||||||
|
const providersLoading = () => agentsLoading() || providersQuery.isLoading || globalProvidersQuery.isLoading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
|
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
|
||||||
<PromptPopover
|
<PromptPopover
|
||||||
@@ -1444,53 +1455,89 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
<span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span>
|
<span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span>
|
||||||
<div class="size-4 shrink-0" />
|
<div class="size-4 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1.5 min-w-0 flex-1">
|
<div class="flex items-center gap-1.5 min-w-0 flex-1 h-7">
|
||||||
<div data-component="prompt-agent-control">
|
<Show when={!agentsLoading()}>
|
||||||
<TooltipKeybind
|
<div data-component="prompt-agent-control">
|
||||||
placement="top"
|
<TooltipKeybind
|
||||||
gutter={4}
|
placement="top"
|
||||||
title={language.t("command.agent.cycle")}
|
gutter={4}
|
||||||
keybind={command.keybind("agent.cycle")}
|
title={language.t("command.agent.cycle")}
|
||||||
>
|
keybind={command.keybind("agent.cycle")}
|
||||||
<Select
|
>
|
||||||
size="normal"
|
<Select
|
||||||
options={agentNames()}
|
size="normal"
|
||||||
current={local.agent.current()?.name ?? ""}
|
options={agentNames()}
|
||||||
onSelect={(value) => {
|
current={local.agent.current()?.name ?? ""}
|
||||||
local.agent.set(value)
|
onSelect={(value) => {
|
||||||
restoreFocus()
|
local.agent.set(value)
|
||||||
}}
|
restoreFocus()
|
||||||
class="capitalize max-w-[160px] text-text-base"
|
}}
|
||||||
valueClass="truncate text-13-regular text-text-base"
|
class="capitalize max-w-[160px] text-text-base"
|
||||||
triggerStyle={control()}
|
valueClass="truncate text-13-regular text-text-base"
|
||||||
triggerProps={{ "data-action": "prompt-agent" }}
|
triggerStyle={control()}
|
||||||
variant="ghost"
|
triggerProps={{ "data-action": "prompt-agent" }}
|
||||||
/>
|
variant="ghost"
|
||||||
</TooltipKeybind>
|
/>
|
||||||
</div>
|
</TooltipKeybind>
|
||||||
<Show when={store.mode !== "shell"}>
|
</div>
|
||||||
<div data-component="prompt-model-control">
|
</Show>
|
||||||
<Show
|
<Show when={!providersLoading()}>
|
||||||
when={providers.paid().length > 0}
|
<Show when={store.mode !== "shell"}>
|
||||||
fallback={
|
<div data-component="prompt-model-control">
|
||||||
|
<Show
|
||||||
|
when={providers.paid().length > 0}
|
||||||
|
fallback={
|
||||||
|
<TooltipKeybind
|
||||||
|
placement="top"
|
||||||
|
gutter={4}
|
||||||
|
title={language.t("command.model.choose")}
|
||||||
|
keybind={command.keybind("model.choose")}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
data-action="prompt-model"
|
||||||
|
as="div"
|
||||||
|
variant="ghost"
|
||||||
|
size="normal"
|
||||||
|
class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
|
||||||
|
style={control()}
|
||||||
|
onClick={() => {
|
||||||
|
void import("@/components/dialog-select-model-unpaid").then((x) => {
|
||||||
|
dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
|
<ProviderIcon
|
||||||
|
id={local.model.current()?.provider?.id ?? ""}
|
||||||
|
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
||||||
|
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
<span class="truncate">
|
||||||
|
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
||||||
|
</span>
|
||||||
|
<Icon name="chevron-down" size="small" class="shrink-0" />
|
||||||
|
</Button>
|
||||||
|
</TooltipKeybind>
|
||||||
|
}
|
||||||
|
>
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
placement="top"
|
placement="top"
|
||||||
gutter={4}
|
gutter={4}
|
||||||
title={language.t("command.model.choose")}
|
title={language.t("command.model.choose")}
|
||||||
keybind={command.keybind("model.choose")}
|
keybind={command.keybind("model.choose")}
|
||||||
>
|
>
|
||||||
<Button
|
<ModelSelectorPopover
|
||||||
data-action="prompt-model"
|
model={local.model}
|
||||||
as="div"
|
triggerAs={Button}
|
||||||
variant="ghost"
|
triggerProps={{
|
||||||
size="normal"
|
variant: "ghost",
|
||||||
class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
|
size: "normal",
|
||||||
style={control()}
|
style: control(),
|
||||||
onClick={() => {
|
class: "min-w-0 max-w-[320px] text-13-regular text-text-base group",
|
||||||
void import("@/components/dialog-select-model-unpaid").then((x) => {
|
"data-action": "prompt-model",
|
||||||
dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
|
onClose={restoreFocus}
|
||||||
>
|
>
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
<Show when={local.model.current()?.provider?.id}>
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
@@ -1503,67 +1550,35 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
||||||
</span>
|
</span>
|
||||||
<Icon name="chevron-down" size="small" class="shrink-0" />
|
<Icon name="chevron-down" size="small" class="shrink-0" />
|
||||||
</Button>
|
</ModelSelectorPopover>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
}
|
</Show>
|
||||||
>
|
</div>
|
||||||
|
<div data-component="prompt-variant-control">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
placement="top"
|
placement="top"
|
||||||
gutter={4}
|
gutter={4}
|
||||||
title={language.t("command.model.choose")}
|
title={language.t("command.model.variant.cycle")}
|
||||||
keybind={command.keybind("model.choose")}
|
keybind={command.keybind("model.variant.cycle")}
|
||||||
>
|
>
|
||||||
<ModelSelectorPopover
|
<Select
|
||||||
model={local.model}
|
size="normal"
|
||||||
triggerAs={Button}
|
options={variants()}
|
||||||
triggerProps={{
|
current={local.model.variant.current() ?? "default"}
|
||||||
variant: "ghost",
|
label={(x) => (x === "default" ? language.t("common.default") : x)}
|
||||||
size: "normal",
|
onSelect={(value) => {
|
||||||
style: control(),
|
local.model.variant.set(value === "default" ? undefined : value)
|
||||||
class: "min-w-0 max-w-[320px] text-13-regular text-text-base group",
|
restoreFocus()
|
||||||
"data-action": "prompt-model",
|
|
||||||
}}
|
}}
|
||||||
onClose={restoreFocus}
|
class="capitalize max-w-[160px] text-text-base"
|
||||||
>
|
valueClass="truncate text-13-regular text-text-base"
|
||||||
<Show when={local.model.current()?.provider?.id}>
|
triggerStyle={control()}
|
||||||
<ProviderIcon
|
triggerProps={{ "data-action": "prompt-model-variant" }}
|
||||||
id={local.model.current()?.provider?.id ?? ""}
|
variant="ghost"
|
||||||
class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
|
/>
|
||||||
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
<span class="truncate">
|
|
||||||
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
|
|
||||||
</span>
|
|
||||||
<Icon name="chevron-down" size="small" class="shrink-0" />
|
|
||||||
</ModelSelectorPopover>
|
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</Show>
|
</div>
|
||||||
</div>
|
</Show>
|
||||||
<div data-component="prompt-variant-control">
|
|
||||||
<TooltipKeybind
|
|
||||||
placement="top"
|
|
||||||
gutter={4}
|
|
||||||
title={language.t("command.model.variant.cycle")}
|
|
||||||
keybind={command.keybind("model.variant.cycle")}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
size="normal"
|
|
||||||
options={variants()}
|
|
||||||
current={local.model.variant.current() ?? "default"}
|
|
||||||
label={(x) => (x === "default" ? language.t("common.default") : x)}
|
|
||||||
onSelect={(value) => {
|
|
||||||
local.model.variant.set(value === "default" ? undefined : value)
|
|
||||||
restoreFocus()
|
|
||||||
}}
|
|
||||||
class="capitalize max-w-[160px] text-text-base"
|
|
||||||
valueClass="truncate text-13-regular text-text-base"
|
|
||||||
triggerStyle={control()}
|
|
||||||
triggerProps={{ "data-action": "prompt-model-variant" }}
|
|
||||||
variant="ghost"
|
|
||||||
/>
|
|
||||||
</TooltipKeybind>
|
|
||||||
</div>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
const mode = input.mode()
|
const mode = input.mode()
|
||||||
|
|
||||||
if (text.trim().length === 0 && images.length === 0 && input.commentCount() === 0) {
|
if (text.trim().length === 0 && images.length === 0 && input.commentCount() === 0) {
|
||||||
if (input.working()) abort()
|
if (input.working()) void abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function openSessionContext(args: {
|
|||||||
}) {
|
}) {
|
||||||
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
|
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
|
||||||
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
|
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
|
||||||
args.tabs.open("context")
|
void args.tabs.open("context")
|
||||||
args.tabs.setActive("context")
|
args.tabs.setActive("context")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
|
|||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
const count = terminal.all().length
|
const count = terminal.all().length
|
||||||
terminal.close(props.terminal.id)
|
void terminal.close(props.terminal.id)
|
||||||
if (count === 1) {
|
if (count === 1) {
|
||||||
props.onClose?.()
|
props.onClose?.()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
const scrollY = typeof local.pty.scrollY === "number" ? local.pty.scrollY : undefined
|
const scrollY = typeof local.pty.scrollY === "number" ? local.pty.scrollY : undefined
|
||||||
let ws: WebSocket | undefined
|
let ws: WebSocket | undefined
|
||||||
let term: Term | undefined
|
let term: Term | undefined
|
||||||
let ghostty: Ghostty
|
let _ghostty: Ghostty
|
||||||
let serializeAddon: SerializeAddon
|
let serializeAddon: SerializeAddon
|
||||||
let fitAddon: FitAddon
|
let fitAddon: FitAddon
|
||||||
let handleResize: () => void
|
let handleResize: () => void
|
||||||
@@ -372,7 +372,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
cleanup()
|
cleanup()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ghostty = g
|
_ghostty = g
|
||||||
term = t
|
term = t
|
||||||
output = terminalWriter((data, done) =>
|
output = terminalWriter((data, done) =>
|
||||||
t.write(data, () => {
|
t.write(data, () => {
|
||||||
@@ -415,7 +415,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
if (local.autoFocus !== false) focusTerminal()
|
if (local.autoFocus !== false) focusTerminal()
|
||||||
|
|
||||||
if (typeof document !== "undefined" && document.fonts) {
|
if (typeof document !== "undefined" && document.fonts) {
|
||||||
document.fonts.ready.then(scheduleFit)
|
void document.fonts.ready.then(scheduleFit)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResize = t.onResize((size) => {
|
const onResize = t.onResize((size) => {
|
||||||
|
|||||||
@@ -252,41 +252,48 @@ export function Titlebar() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={hasProjects()}>
|
<div
|
||||||
<div
|
class="flex items-center shrink-0"
|
||||||
class="flex items-center gap-0 transition-transform"
|
classList={{
|
||||||
classList={{
|
"translate-x-0": !layout.sidebar.opened(),
|
||||||
"translate-x-0": !layout.sidebar.opened(),
|
"-translate-x-[36px]": layout.sidebar.opened(),
|
||||||
"-translate-x-[36px]": layout.sidebar.opened(),
|
"duration-180 ease-out": !layout.sidebar.opened(),
|
||||||
"duration-180 ease-out": !layout.sidebar.opened(),
|
"duration-180 ease-in": layout.sidebar.opened(),
|
||||||
"duration-180 ease-in": layout.sidebar.opened(),
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Show when={hasProjects()}>
|
||||||
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
<div class="flex items-center gap-0 transition-transform">
|
||||||
<Button
|
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
|
||||||
variant="ghost"
|
<Button
|
||||||
icon="chevron-left"
|
variant="ghost"
|
||||||
class="titlebar-icon w-6 h-6 p-0 box-border"
|
icon="chevron-left"
|
||||||
disabled={!canBack()}
|
class="titlebar-icon w-6 h-6 p-0 box-border"
|
||||||
onClick={back}
|
disabled={!canBack()}
|
||||||
aria-label={language.t("common.goBack")}
|
onClick={back}
|
||||||
/>
|
aria-label={language.t("common.goBack")}
|
||||||
</Tooltip>
|
/>
|
||||||
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
</Tooltip>
|
||||||
<Button
|
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
|
||||||
variant="ghost"
|
<Button
|
||||||
icon="chevron-right"
|
variant="ghost"
|
||||||
class="titlebar-icon w-6 h-6 p-0 box-border"
|
icon="chevron-right"
|
||||||
disabled={!canForward()}
|
class="titlebar-icon w-6 h-6 p-0 box-border"
|
||||||
onClick={forward}
|
disabled={!canForward()}
|
||||||
aria-label={language.t("common.goForward")}
|
onClick={forward}
|
||||||
/>
|
aria-label={language.t("common.goForward")}
|
||||||
</Tooltip>
|
/>
|
||||||
</div>
|
</Tooltip>
|
||||||
</Show>
|
</div>
|
||||||
|
</Show>
|
||||||
|
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
||||||
|
{["beta", "dev"].includes(import.meta.env.VITE_OPENCODE_CHANNEL) && (
|
||||||
|
<div class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono">
|
||||||
|
{import.meta.env.VITE_OPENCODE_CHANNEL.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="min-w-0 flex items-center justify-center pointer-events-none">
|
<div class="min-w-0 flex items-center justify-center pointer-events-none">
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type { ProjectMeta } from "./global-sync/types"
|
|||||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||||
import { sanitizeProject } from "./global-sync/utils"
|
import { sanitizeProject } from "./global-sync/utils"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
import { queryOptions, skipToken, useQueryClient } from "@tanstack/solid-query"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -41,6 +42,9 @@ type GlobalStore = {
|
|||||||
reload: undefined | "pending" | "complete"
|
reload: undefined | "pending" | "complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const loadSessionsQuery = (directory: string) =>
|
||||||
|
queryOptions<null>({ queryKey: [directory, "loadSessions"], queryFn: skipToken })
|
||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
@@ -67,6 +71,7 @@ function createGlobalSync() {
|
|||||||
config: {},
|
config: {},
|
||||||
reload: undefined,
|
reload: undefined,
|
||||||
})
|
})
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
let active = true
|
let active = true
|
||||||
let projectWritten = false
|
let projectWritten = false
|
||||||
@@ -198,46 +203,53 @@ function createGlobalSync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
|
const limit = Math.max(store.limit + SESSION_RECENT_LIMIT, SESSION_RECENT_LIMIT)
|
||||||
const promise = loadRootSessionsWithFallback({
|
const promise = queryClient
|
||||||
directory,
|
.ensureQueryData({
|
||||||
limit,
|
...loadSessionsQuery(directory),
|
||||||
list: (query) => globalSDK.client.session.list(query),
|
queryFn: () =>
|
||||||
})
|
loadRootSessionsWithFallback({
|
||||||
.then((x) => {
|
directory,
|
||||||
const nonArchived = (x.data ?? [])
|
limit,
|
||||||
.filter((s) => !!s?.id)
|
list: (query) => globalSDK.client.session.list(query),
|
||||||
.filter((s) => !s.time?.archived)
|
})
|
||||||
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
.then((x) => {
|
||||||
const limit = store.limit
|
const nonArchived = (x.data ?? [])
|
||||||
const childSessions = store.session.filter((s) => !!s.parentID)
|
.filter((s) => !!s?.id)
|
||||||
const sessions = trimSessions([...nonArchived, ...childSessions], {
|
.filter((s) => !s.time?.archived)
|
||||||
limit,
|
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
||||||
permission: store.permission,
|
const limit = store.limit
|
||||||
})
|
const childSessions = store.session.filter((s) => !!s.parentID)
|
||||||
setStore(
|
const sessions = trimSessions([...nonArchived, ...childSessions], {
|
||||||
"sessionTotal",
|
limit,
|
||||||
estimateRootSessionTotal({
|
permission: store.permission,
|
||||||
count: nonArchived.length,
|
})
|
||||||
limit: x.limit,
|
setStore(
|
||||||
limited: x.limited,
|
"sessionTotal",
|
||||||
}),
|
estimateRootSessionTotal({
|
||||||
)
|
count: nonArchived.length,
|
||||||
setStore("session", reconcile(sessions, { key: "id" }))
|
limit: x.limit,
|
||||||
cleanupDroppedSessionCaches(store, setStore, sessions, setSessionTodo)
|
limited: x.limited,
|
||||||
sessionMeta.set(directory, { limit })
|
}),
|
||||||
})
|
)
|
||||||
.catch((err) => {
|
setStore("session", reconcile(sessions, { key: "id" }))
|
||||||
console.error("Failed to load sessions", err)
|
cleanupDroppedSessionCaches(store, setStore, sessions, setSessionTodo)
|
||||||
const project = getFilename(directory)
|
sessionMeta.set(directory, { limit })
|
||||||
showToast({
|
})
|
||||||
variant: "error",
|
.catch((err) => {
|
||||||
title: language.t("toast.session.listFailed.title", { project }),
|
console.error("Failed to load sessions", err)
|
||||||
description: formatServerError(err, language.t),
|
const project = getFilename(directory)
|
||||||
})
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: language.t("toast.session.listFailed.title", { project }),
|
||||||
|
description: formatServerError(err, language.t),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(() => null),
|
||||||
})
|
})
|
||||||
|
.then(() => {})
|
||||||
|
|
||||||
sessionLoads.set(directory, promise)
|
sessionLoads.set(directory, promise)
|
||||||
promise.finally(() => {
|
void promise.finally(() => {
|
||||||
sessionLoads.delete(directory)
|
sessionLoads.delete(directory)
|
||||||
children.unpin(directory)
|
children.unpin(directory)
|
||||||
})
|
})
|
||||||
@@ -250,8 +262,9 @@ function createGlobalSync() {
|
|||||||
if (pending) return pending
|
if (pending) return pending
|
||||||
|
|
||||||
children.pin(directory)
|
children.pin(directory)
|
||||||
const promise = (async () => {
|
const promise = Promise.resolve().then(async () => {
|
||||||
const child = children.ensureChild(directory)
|
const child = children.ensureChild(directory)
|
||||||
|
child[1]("bootstrapPromise", promise!)
|
||||||
const cache = children.vcsCache.get(directory)
|
const cache = children.vcsCache.get(directory)
|
||||||
if (!cache) return
|
if (!cache) return
|
||||||
const sdk = sdkFor(directory)
|
const sdk = sdkFor(directory)
|
||||||
@@ -269,11 +282,12 @@ function createGlobalSync() {
|
|||||||
vcsCache: cache,
|
vcsCache: cache,
|
||||||
loadSessions,
|
loadSessions,
|
||||||
translate: language.t,
|
translate: language.t,
|
||||||
|
queryClient,
|
||||||
})
|
})
|
||||||
})()
|
})
|
||||||
|
|
||||||
booting.set(directory, promise)
|
booting.set(directory, promise)
|
||||||
promise.finally(() => {
|
void promise.finally(() => {
|
||||||
booting.delete(directory)
|
booting.delete(directory)
|
||||||
children.unpin(directory)
|
children.unpin(directory)
|
||||||
})
|
})
|
||||||
@@ -317,7 +331,7 @@ function createGlobalSync() {
|
|||||||
setSessionTodo,
|
setSessionTodo,
|
||||||
vcsCache: children.vcsCache.get(directory),
|
vcsCache: children.vcsCache.get(directory),
|
||||||
loadLsp: () => {
|
loadLsp: () => {
|
||||||
sdkFor(directory)
|
void sdkFor(directory)
|
||||||
.lsp.status()
|
.lsp.status()
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
setStore("lsp", x.data ?? [])
|
setStore("lsp", x.data ?? [])
|
||||||
@@ -346,6 +360,7 @@ function createGlobalSync() {
|
|||||||
translate: language.t,
|
translate: language.t,
|
||||||
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
||||||
setGlobalStore: setBootStore,
|
setGlobalStore: setBootStore,
|
||||||
|
queryClient,
|
||||||
})
|
})
|
||||||
bootedAt = Date.now()
|
bootedAt = Date.now()
|
||||||
} finally {
|
} finally {
|
||||||
@@ -359,13 +374,13 @@ function createGlobalSync() {
|
|||||||
eventFrame = undefined
|
eventFrame = undefined
|
||||||
eventTimer = setTimeout(() => {
|
eventTimer = setTimeout(() => {
|
||||||
eventTimer = undefined
|
eventTimer = undefined
|
||||||
globalSDK.event.start()
|
void globalSDK.event.start()
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
eventTimer = setTimeout(() => {
|
eventTimer = setTimeout(() => {
|
||||||
eventTimer = undefined
|
eventTimer = undefined
|
||||||
globalSDK.event.start()
|
void globalSDK.event.start()
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
void bootstrap()
|
void bootstrap()
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
|||||||
import type { State, VcsCache } from "./types"
|
import type { State, VcsCache } from "./types"
|
||||||
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
import { cmp, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||||
import { formatServerError } from "@/utils/server-errors"
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
import { QueryClient, queryOptions, skipToken } from "@tanstack/solid-query"
|
||||||
|
import { loadSessionsQuery } from "../global-sync"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -71,6 +73,7 @@ export async function bootstrapGlobal(input: {
|
|||||||
translate: (key: string, vars?: Record<string, string | number>) => string
|
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||||
formatMoreCount: (count: number) => string
|
formatMoreCount: (count: number) => string
|
||||||
setGlobalStore: SetStoreFunction<GlobalStore>
|
setGlobalStore: SetStoreFunction<GlobalStore>
|
||||||
|
queryClient: QueryClient
|
||||||
}) {
|
}) {
|
||||||
const fast = [
|
const fast = [
|
||||||
() =>
|
() =>
|
||||||
@@ -80,11 +83,16 @@ export async function bootstrapGlobal(input: {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
() =>
|
() =>
|
||||||
retry(() =>
|
input.queryClient.fetchQuery({
|
||||||
input.globalSDK.provider.list().then((x) => {
|
...loadProvidersQuery(null),
|
||||||
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
queryFn: () =>
|
||||||
}),
|
retry(() =>
|
||||||
),
|
input.globalSDK.provider.list().then((x) => {
|
||||||
|
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
||||||
|
return null
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
const slow = [
|
const slow = [
|
||||||
@@ -172,6 +180,12 @@ function warmSessions(input: {
|
|||||||
).then(() => undefined)
|
).then(() => undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const loadProvidersQuery = (directory: string | null) =>
|
||||||
|
queryOptions<null>({ queryKey: [directory, "providers"], queryFn: skipToken })
|
||||||
|
|
||||||
|
export const loadAgentsQuery = (directory: string | null) =>
|
||||||
|
queryOptions<null>({ queryKey: [directory, "agents"], queryFn: skipToken })
|
||||||
|
|
||||||
export async function bootstrapDirectory(input: {
|
export async function bootstrapDirectory(input: {
|
||||||
directory: string
|
directory: string
|
||||||
sdk: OpencodeClient
|
sdk: OpencodeClient
|
||||||
@@ -186,6 +200,7 @@ export async function bootstrapDirectory(input: {
|
|||||||
project: Project[]
|
project: Project[]
|
||||||
provider: ProviderListResponse
|
provider: ProviderListResponse
|
||||||
}
|
}
|
||||||
|
queryClient: QueryClient
|
||||||
}) {
|
}) {
|
||||||
const loading = input.store.status !== "complete"
|
const loading = input.store.status !== "complete"
|
||||||
const seededProject = projectID(input.directory, input.global.project)
|
const seededProject = projectID(input.directory, input.global.project)
|
||||||
@@ -207,97 +222,7 @@ export async function bootstrapDirectory(input: {
|
|||||||
input.setStore("lsp", [])
|
input.setStore("lsp", [])
|
||||||
if (loading) input.setStore("status", "partial")
|
if (loading) input.setStore("status", "partial")
|
||||||
|
|
||||||
const fast = [
|
const fast = [() => Promise.resolve(input.loadSessions(input.directory))]
|
||||||
() => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))),
|
|
||||||
() => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
|
|
||||||
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
|
||||||
]
|
|
||||||
|
|
||||||
const slow = [
|
|
||||||
() =>
|
|
||||||
seededProject
|
|
||||||
? Promise.resolve()
|
|
||||||
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
|
|
||||||
() =>
|
|
||||||
seededPath
|
|
||||||
? Promise.resolve()
|
|
||||||
: retry(() =>
|
|
||||||
input.sdk.path.get().then((x) => {
|
|
||||||
input.setStore("path", x.data!)
|
|
||||||
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
|
|
||||||
if (next) input.setStore("project", next)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.sdk.vcs.get().then((x) => {
|
|
||||||
const next = x.data ?? input.store.vcs
|
|
||||||
input.setStore("vcs", next)
|
|
||||||
if (next) input.vcsCache.setStore("value", next)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.sdk.permission.list().then((x) => {
|
|
||||||
const ids = (x.data ?? []).map((perm) => perm?.sessionID).filter((id): id is string => !!id)
|
|
||||||
const grouped = groupBySession(
|
|
||||||
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
|
|
||||||
)
|
|
||||||
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
|
|
||||||
batch(() => {
|
|
||||||
for (const sessionID of Object.keys(input.store.permission)) {
|
|
||||||
if (grouped[sessionID]) continue
|
|
||||||
input.setStore("permission", sessionID, [])
|
|
||||||
}
|
|
||||||
for (const [sessionID, permissions] of Object.entries(grouped)) {
|
|
||||||
input.setStore(
|
|
||||||
"permission",
|
|
||||||
sessionID,
|
|
||||||
reconcile(
|
|
||||||
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
|
|
||||||
{ key: "id" },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.sdk.question.list().then((x) => {
|
|
||||||
const ids = (x.data ?? []).map((question) => question?.sessionID).filter((id): id is string => !!id)
|
|
||||||
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
|
|
||||||
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
|
|
||||||
batch(() => {
|
|
||||||
for (const sessionID of Object.keys(input.store.question)) {
|
|
||||||
if (grouped[sessionID]) continue
|
|
||||||
input.setStore("question", sessionID, [])
|
|
||||||
}
|
|
||||||
for (const [sessionID, questions] of Object.entries(grouped)) {
|
|
||||||
input.setStore(
|
|
||||||
"question",
|
|
||||||
sessionID,
|
|
||||||
reconcile(
|
|
||||||
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
|
||||||
{ key: "id" },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
|
||||||
() =>
|
|
||||||
retry(() =>
|
|
||||||
input.sdk.mcp.status().then((x) => {
|
|
||||||
input.setStore("mcp", x.data!)
|
|
||||||
input.setStore("mcp_ready", true)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
const errs = errors(await runAll(fast))
|
const errs = errors(await runAll(fast))
|
||||||
if (errs.length > 0) {
|
if (errs.length > 0) {
|
||||||
@@ -310,36 +235,138 @@ export async function bootstrapDirectory(input: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await waitForPaint()
|
;(async () => {
|
||||||
const slowErrs = errors(await runAll(slow))
|
const slow = [
|
||||||
if (slowErrs.length > 0) {
|
() =>
|
||||||
console.error("Failed to finish bootstrap instance", slowErrs[0])
|
input.queryClient.ensureQueryData({
|
||||||
const project = getFilename(input.directory)
|
...loadAgentsQuery(input.directory),
|
||||||
showToast({
|
queryFn: () =>
|
||||||
variant: "error",
|
retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", normalizeAgentList(x.data)))).then(
|
||||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
() => null,
|
||||||
description: formatServerError(slowErrs[0], input.translate),
|
),
|
||||||
})
|
}),
|
||||||
}
|
() => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
|
||||||
|
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
||||||
|
() =>
|
||||||
|
seededProject
|
||||||
|
? Promise.resolve()
|
||||||
|
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
|
||||||
|
() =>
|
||||||
|
seededPath
|
||||||
|
? Promise.resolve()
|
||||||
|
: retry(() =>
|
||||||
|
input.sdk.path.get().then((x) => {
|
||||||
|
input.setStore("path", x.data!)
|
||||||
|
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
|
||||||
|
if (next) input.setStore("project", next)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
() =>
|
||||||
|
retry(() =>
|
||||||
|
input.sdk.vcs.get().then((x) => {
|
||||||
|
const next = x.data ?? input.store.vcs
|
||||||
|
input.setStore("vcs", next)
|
||||||
|
if (next) input.vcsCache.setStore("value", next)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
|
||||||
|
() =>
|
||||||
|
retry(() =>
|
||||||
|
input.sdk.permission.list().then((x) => {
|
||||||
|
const ids = (x.data ?? []).map((perm) => perm?.sessionID).filter((id): id is string => !!id)
|
||||||
|
const grouped = groupBySession(
|
||||||
|
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
|
||||||
|
)
|
||||||
|
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
|
||||||
|
batch(() => {
|
||||||
|
for (const sessionID of Object.keys(input.store.permission)) {
|
||||||
|
if (grouped[sessionID]) continue
|
||||||
|
input.setStore("permission", sessionID, [])
|
||||||
|
}
|
||||||
|
for (const [sessionID, permissions] of Object.entries(grouped)) {
|
||||||
|
input.setStore(
|
||||||
|
"permission",
|
||||||
|
sessionID,
|
||||||
|
reconcile(
|
||||||
|
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||||
|
{ key: "id" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
() =>
|
||||||
|
retry(() =>
|
||||||
|
input.sdk.question.list().then((x) => {
|
||||||
|
const ids = (x.data ?? []).map((question) => question?.sessionID).filter((id): id is string => !!id)
|
||||||
|
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
|
||||||
|
return warmSessions({ ids, store: input.store, setStore: input.setStore, sdk: input.sdk }).then(() =>
|
||||||
|
batch(() => {
|
||||||
|
for (const sessionID of Object.keys(input.store.question)) {
|
||||||
|
if (grouped[sessionID]) continue
|
||||||
|
input.setStore("question", sessionID, [])
|
||||||
|
}
|
||||||
|
for (const [sessionID, questions] of Object.entries(grouped)) {
|
||||||
|
input.setStore(
|
||||||
|
"question",
|
||||||
|
sessionID,
|
||||||
|
reconcile(
|
||||||
|
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||||
|
{ key: "id" },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||||
|
() =>
|
||||||
|
retry(() =>
|
||||||
|
input.sdk.mcp.status().then((x) => {
|
||||||
|
input.setStore("mcp", x.data!)
|
||||||
|
input.setStore("mcp_ready", true)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
|
await waitForPaint()
|
||||||
|
const slowErrs = errors(await runAll(slow))
|
||||||
const rev = (providerRev.get(input.directory) ?? 0) + 1
|
if (slowErrs.length > 0) {
|
||||||
providerRev.set(input.directory, rev)
|
console.error("Failed to finish bootstrap instance", slowErrs[0])
|
||||||
void retry(() => input.sdk.provider.list())
|
|
||||||
.then((x) => {
|
|
||||||
if (providerRev.get(input.directory) !== rev) return
|
|
||||||
input.setStore("provider", normalizeProviderList(x.data!))
|
|
||||||
input.setStore("provider_ready", true)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (providerRev.get(input.directory) !== rev) return
|
|
||||||
console.error("Failed to refresh provider list", err)
|
|
||||||
const project = getFilename(input.directory)
|
const project = getFilename(input.directory)
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||||
description: formatServerError(err, input.translate),
|
description: formatServerError(slowErrs[0], input.translate),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
|
||||||
|
|
||||||
|
const rev = (providerRev.get(input.directory) ?? 0) + 1
|
||||||
|
providerRev.set(input.directory, rev)
|
||||||
|
void input.queryClient.ensureQueryData({
|
||||||
|
...loadSessionsQuery(input.directory),
|
||||||
|
queryFn: () =>
|
||||||
|
retry(() => input.sdk.provider.list())
|
||||||
|
.then((x) => {
|
||||||
|
if (providerRev.get(input.directory) !== rev) return
|
||||||
|
input.setStore("provider", normalizeProviderList(x.data!))
|
||||||
|
input.setStore("provider_ready", true)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (providerRev.get(input.directory) !== rev) console.error("Failed to refresh provider list", err)
|
||||||
|
const project = getFilename(input.directory)
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||||
|
description: formatServerError(err, input.translate),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(() => null),
|
||||||
})
|
})
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ export function createChildStoreManager(input: {
|
|||||||
limit: 5,
|
limit: 5,
|
||||||
message: {},
|
message: {},
|
||||||
part: {},
|
part: {},
|
||||||
|
bootstrapPromise: Promise.resolve(),
|
||||||
})
|
})
|
||||||
children[directory] = child
|
children[directory] = child
|
||||||
disposers.set(directory, dispose)
|
disposers.set(directory, dispose)
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export type State = {
|
|||||||
part: {
|
part: {
|
||||||
[messageID: string]: Part[]
|
[messageID: string]: Part[]
|
||||||
}
|
}
|
||||||
|
bootstrapPromise: Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VcsCache = {
|
export type VcsCache = {
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
open(directory: string) {
|
open(directory: string) {
|
||||||
const root = rootFor(directory)
|
const root = rootFor(directory)
|
||||||
if (server.projects.list().find((x) => x.worktree === root)) return
|
if (server.projects.list().find((x) => x.worktree === root)) return
|
||||||
globalSync.project.loadSessions(root)
|
void globalSync.project.loadSessions(root)
|
||||||
server.projects.open(root)
|
server.projects.open(root)
|
||||||
},
|
},
|
||||||
close(directory: string) {
|
close(directory: string) {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
|
|||||||
entry?.value.clear()
|
entry?.value.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
removePersisted(Persist.workspace(dir, "terminal"), platform)
|
void removePersisted(Persist.workspace(dir, "terminal"), platform)
|
||||||
|
|
||||||
const legacy = new Set(getLegacyTerminalStorageKeys(dir))
|
const legacy = new Set(getLegacyTerminalStorageKeys(dir))
|
||||||
for (const id of sessionIDs ?? []) {
|
for (const id of sessionIDs ?? []) {
|
||||||
@@ -126,7 +126,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const key of legacy) {
|
for (const key of legacy) {
|
||||||
removePersisted({ key }, platform)
|
void removePersisted({ key }, platform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
@@ -3,6 +3,7 @@ import "solid-js"
|
|||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_OPENCODE_SERVER_HOST: string
|
readonly VITE_OPENCODE_SERVER_HOST: string
|
||||||
readonly VITE_OPENCODE_SERVER_PORT: string
|
readonly VITE_OPENCODE_SERVER_PORT: string
|
||||||
|
readonly OPENCODE_CHANNEL?: "dev" | "beta" | "prod"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { dict as en } from "./en"
|
|
||||||
|
|
||||||
export const dict = {
|
export const dict = {
|
||||||
"command.category.suggested": "추천",
|
"command.category.suggested": "추천",
|
||||||
"command.category.view": "보기",
|
"command.category.view": "보기",
|
||||||
|
|||||||
@@ -132,9 +132,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!slug) return { slug, dir: "" }
|
if (!slug) return { slug, dir: "" }
|
||||||
const dir = decode64(slug)
|
const dir = decode64(slug)
|
||||||
if (!dir) return { slug, dir: "" }
|
if (!dir) return { slug, dir: "" }
|
||||||
|
const store = globalSync.peek(dir, { bootstrap: false })
|
||||||
return {
|
return {
|
||||||
slug,
|
slug,
|
||||||
dir: globalSync.peek(dir, { bootstrap: false })[0].path.directory || dir,
|
store,
|
||||||
|
dir: store[0].path.directory || dir,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const availableThemeEntries = createMemo(() => theme.ids().map((id) => [id, theme.themes()[id]] as const))
|
const availableThemeEntries = createMemo(() => theme.ids().map((id) => [id, theme.themes()[id]] as const))
|
||||||
@@ -956,7 +958,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
|
|
||||||
// warm up child store to prevent flicker
|
// warm up child store to prevent flicker
|
||||||
globalSync.child(target.worktree)
|
globalSync.child(target.worktree)
|
||||||
openProject(target.worktree)
|
void openProject(target.worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateSessionByUnseen(offset: number) {
|
function navigateSessionByUnseen(offset: number) {
|
||||||
@@ -1094,7 +1096,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
disabled: !params.dir || !params.id,
|
disabled: !params.dir || !params.id,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const session = currentSessions().find((s) => s.id === params.id)
|
const session = currentSessions().find((s) => s.id === params.id)
|
||||||
if (session) archiveSession(session)
|
if (session) void archiveSession(session)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1360,11 +1362,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!server.isLocal()) return
|
if (!server.isLocal()) return
|
||||||
|
|
||||||
for (const directory of collectOpenProjectDeepLinks(urls)) {
|
for (const directory of collectOpenProjectDeepLinks(urls)) {
|
||||||
openProject(directory)
|
void openProject(directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const link of collectNewSessionDeepLinks(urls)) {
|
for (const link of collectNewSessionDeepLinks(urls)) {
|
||||||
openProject(link.directory, false)
|
void openProject(link.directory, false)
|
||||||
const slug = base64Encode(link.directory)
|
const slug = base64Encode(link.directory)
|
||||||
if (link.prompt) {
|
if (link.prompt) {
|
||||||
setSessionHandoff(slug, { prompt: link.prompt })
|
setSessionHandoff(slug, { prompt: link.prompt })
|
||||||
@@ -1453,11 +1455,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
function resolve(result: string | string[] | null) {
|
function resolve(result: string | string[] | null) {
|
||||||
if (Array.isArray(result)) {
|
if (Array.isArray(result)) {
|
||||||
for (const directory of result) {
|
for (const directory of result) {
|
||||||
openProject(directory, false)
|
void openProject(directory, false)
|
||||||
}
|
}
|
||||||
navigateToProject(result[0])
|
void navigateToProject(result[0])
|
||||||
} else if (result) {
|
} else if (result) {
|
||||||
openProject(result)
|
void openProject(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1825,7 +1827,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
const next = new Set(dirs)
|
const next = new Set(dirs)
|
||||||
for (const directory of next) {
|
for (const directory of next) {
|
||||||
if (loadedSessionDirs.has(directory)) continue
|
if (loadedSessionDirs.has(directory)) continue
|
||||||
globalSync.project.loadSessions(directory)
|
void globalSync.project.loadSessions(directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedSessionDirs.clear()
|
loadedSessionDirs.clear()
|
||||||
@@ -2110,7 +2112,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
onSave={(next) => {
|
onSave={(next) => {
|
||||||
const item = project()
|
const item = project()
|
||||||
if (!item) return
|
if (!item) return
|
||||||
renameProject(item, next)
|
void renameProject(item, next)
|
||||||
}}
|
}}
|
||||||
class="text-14-medium text-text-strong truncate"
|
class="text-14-medium text-text-strong truncate"
|
||||||
displayClass="text-14-medium text-text-strong truncate"
|
displayClass="text-14-medium text-text-strong truncate"
|
||||||
@@ -2242,7 +2244,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const item = project()
|
const item = project()
|
||||||
if (!item) return
|
if (!item) return
|
||||||
createWorkspace(item)
|
void createWorkspace(item)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{language.t("workspace.new")}
|
{language.t("workspace.new")}
|
||||||
@@ -2353,8 +2355,14 @@ export default function Layout(props: ParentProps) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [loading] = createResource(
|
||||||
|
() => route()?.store?.[0]?.bootstrapPromise,
|
||||||
|
(p) => p,
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
|
<div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
|
||||||
|
{(autoselecting(), loading()) ?? ""}
|
||||||
<Titlebar />
|
<Titlebar />
|
||||||
<div class="flex-1 min-h-0 min-w-0 flex">
|
<div class="flex-1 min-h-0 min-w-0 flex">
|
||||||
<div class="flex-1 min-h-0 relative">
|
<div class="flex-1 min-h-0 relative">
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import { Spinner } from "@opencode-ai/ui/spinner"
|
|||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { type Session } from "@opencode-ai/sdk/v2/client"
|
import { type Session } from "@opencode-ai/sdk/v2/client"
|
||||||
import { type LocalProject } from "@/context/layout"
|
import { type LocalProject } from "@/context/layout"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { loadSessionsQuery, useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
|
import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items"
|
||||||
import { sortedRootSessions, workspaceKey } from "./helpers"
|
import { sortedRootSessions, workspaceKey } from "./helpers"
|
||||||
|
import { useQuery } from "@tanstack/solid-query"
|
||||||
|
|
||||||
type InlineEditorComponent = (props: {
|
type InlineEditorComponent = (props: {
|
||||||
id: string
|
id: string
|
||||||
@@ -277,7 +278,7 @@ const WorkspaceSessionList = (props: {
|
|||||||
class="flex w-full text-left justify-start text-14-regular text-text-weak pl-2 pr-10"
|
class="flex w-full text-left justify-start text-14-regular text-text-weak pl-2 pr-10"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={(e: MouseEvent) => {
|
onClick={(e: MouseEvent) => {
|
||||||
props.loadMore()
|
void props.loadMore()
|
||||||
;(e.currentTarget as HTMLButtonElement).blur()
|
;(e.currentTarget as HTMLButtonElement).blur()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -454,7 +455,8 @@ export const LocalWorkspace = (props: {
|
|||||||
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
||||||
const booted = createMemo((prev) => prev || workspace().store.status === "complete", false)
|
const booted = createMemo((prev) => prev || workspace().store.status === "complete", false)
|
||||||
const count = createMemo(() => sessions()?.length ?? 0)
|
const count = createMemo(() => sessions()?.length ?? 0)
|
||||||
const loading = createMemo(() => !booted() && count() === 0)
|
const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) }))
|
||||||
|
const loading = createMemo(() => query.isPending && count() === 0)
|
||||||
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
||||||
@@ -471,7 +473,7 @@ export const LocalWorkspace = (props: {
|
|||||||
mobile={props.mobile}
|
mobile={props.mobile}
|
||||||
ctx={props.ctx}
|
ctx={props.ctx}
|
||||||
showNew={() => false}
|
showNew={() => false}
|
||||||
loading={loading}
|
loading={() => query.isLoading}
|
||||||
sessions={sessions}
|
sessions={sessions}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
on,
|
on,
|
||||||
onMount,
|
onMount,
|
||||||
untrack,
|
untrack,
|
||||||
|
createResource,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
@@ -432,7 +433,6 @@ export default function Page() {
|
|||||||
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
|
||||||
const isChildSession = createMemo(() => !!info()?.parentID)
|
const isChildSession = createMemo(() => !!info()?.parentID)
|
||||||
const diffs = createMemo(() => (params.id ? list(sync.data.session_diff[params.id]) : []))
|
const diffs = createMemo(() => (params.id ? list(sync.data.session_diff[params.id]) : []))
|
||||||
const sessionCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length))
|
|
||||||
const canReview = createMemo(() => !!sync.project)
|
const canReview = createMemo(() => !!sync.project)
|
||||||
const reviewTab = createMemo(() => isDesktop())
|
const reviewTab = createMemo(() => isDesktop())
|
||||||
const tabState = createSessionTabs({
|
const tabState = createSessionTabs({
|
||||||
@@ -484,7 +484,7 @@ export default function Page() {
|
|||||||
if (!tab) return
|
if (!tab) return
|
||||||
|
|
||||||
const path = file.pathFromTab(tab)
|
const path = file.pathFromTab(tab)
|
||||||
if (path) file.load(path)
|
if (path) void file.load(path)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -805,8 +805,9 @@ export default function Page() {
|
|||||||
|
|
||||||
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
|
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
|
||||||
|
|
||||||
createEffect(
|
const [sessionSync] = createResource(
|
||||||
on([() => sdk.directory, () => params.id] as const, ([, id]) => {
|
() => [sdk.directory, params.id] as const,
|
||||||
|
([directory, id]) => {
|
||||||
if (refreshFrame !== undefined) cancelAnimationFrame(refreshFrame)
|
if (refreshFrame !== undefined) cancelAnimationFrame(refreshFrame)
|
||||||
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
|
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
|
||||||
refreshFrame = undefined
|
refreshFrame = undefined
|
||||||
@@ -817,13 +818,10 @@ export default function Page() {
|
|||||||
const stale = !cached
|
const stale = !cached
|
||||||
? false
|
? false
|
||||||
: (() => {
|
: (() => {
|
||||||
const info = getSessionPrefetch(sdk.directory, id)
|
const info = getSessionPrefetch(directory, id)
|
||||||
if (!info) return true
|
if (!info) return true
|
||||||
return Date.now() - info.at > SESSION_PREFETCH_TTL
|
return Date.now() - info.at > SESSION_PREFETCH_TTL
|
||||||
})()
|
})()
|
||||||
untrack(() => {
|
|
||||||
void sync.session.sync(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
refreshFrame = requestAnimationFrame(() => {
|
refreshFrame = requestAnimationFrame(() => {
|
||||||
refreshFrame = undefined
|
refreshFrame = undefined
|
||||||
@@ -835,7 +833,9 @@ export default function Page() {
|
|||||||
})
|
})
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
}),
|
|
||||||
|
return sync.session.sync(id)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@@ -1882,6 +1882,7 @@ export default function Page() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
|
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
|
||||||
|
{sessionSync() ?? ""}
|
||||||
<SessionHeader />
|
<SessionHeader />
|
||||||
<div class="flex-1 min-h-0 flex flex-col md:flex-row">
|
<div class="flex-1 min-h-0 flex flex-col md:flex-row">
|
||||||
<Show when={!isDesktop() && !!params.id}>
|
<Show when={!isDesktop() && !!params.id}>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export const createOpenReviewFile = (input: {
|
|||||||
input.openTab(tab)
|
input.openTab(tab)
|
||||||
input.setActive(tab)
|
input.setActive(tab)
|
||||||
}
|
}
|
||||||
if (maybePromise instanceof Promise) maybePromise.then(open)
|
if (maybePromise instanceof Promise) void maybePromise.then(open)
|
||||||
else open()
|
else open()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ export function createSdkForServer({
|
|||||||
|
|
||||||
return createOpencodeClient({
|
return createOpencodeClient({
|
||||||
...config,
|
...config,
|
||||||
headers: { ...config.headers, ...auth },
|
headers: {
|
||||||
|
...(config.headers instanceof Headers ? Object.fromEntries(config.headers.entries()) : config.headers),
|
||||||
|
...auth,
|
||||||
|
},
|
||||||
baseUrl: server.url,
|
baseUrl: server.url,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -105,4 +105,4 @@ async function main() {
|
|||||||
console.log(`✓ Sitemap generated at ${outputPath}`)
|
console.log(`✓ Sitemap generated at ${outputPath}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
void main()
|
||||||
|
|||||||
@@ -766,7 +766,7 @@ export default function Spotlight(props: SpotlightProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeWebGPU()
|
void initializeWebGPU()
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (cleanupFunctionRef) {
|
if (cleanupFunctionRef) {
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ export default function BlackSubscribe() {
|
|||||||
|
|
||||||
// Resolve stripe promise once
|
// Resolve stripe promise once
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
stripePromise.then((s) => {
|
void stripePromise.then((s) => {
|
||||||
if (s) setStripe(s)
|
if (s) setStripe(s)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { APIEvent } from "@solidjs/start"
|
import type { APIEvent } from "@solidjs/start"
|
||||||
import type { DownloadPlatform } from "../types"
|
import type { DownloadPlatform } from "../types"
|
||||||
|
|
||||||
const assetNames: Record<string, string> = {
|
const prodAssetNames: Record<string, string> = {
|
||||||
"darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg",
|
"darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg",
|
||||||
"darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg",
|
"darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg",
|
||||||
"windows-x64-nsis": "opencode-desktop-windows-x64.exe",
|
"windows-x64-nsis": "opencode-desktop-windows-x64.exe",
|
||||||
@@ -10,6 +10,15 @@ const assetNames: Record<string, string> = {
|
|||||||
"linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm",
|
"linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm",
|
||||||
} satisfies Record<DownloadPlatform, string>
|
} satisfies Record<DownloadPlatform, string>
|
||||||
|
|
||||||
|
const betaAssetNames: Record<string, string> = {
|
||||||
|
"darwin-aarch64-dmg": "opencode-electron-mac-arm64.dmg",
|
||||||
|
"darwin-x64-dmg": "opencode-electron-mac-x64.dmg",
|
||||||
|
"windows-x64-nsis": "opencode-electron-win-x64.exe",
|
||||||
|
"linux-x64-deb": "opencode-electron-linux-amd64.deb",
|
||||||
|
"linux-x64-appimage": "opencode-electron-linux-x86_64.AppImage",
|
||||||
|
"linux-x64-rpm": "opencode-electron-linux-x86_64.rpm",
|
||||||
|
} satisfies Record<DownloadPlatform, string>
|
||||||
|
|
||||||
// Doing this on the server lets us preserve the original name for platforms we don't care to rename for
|
// Doing this on the server lets us preserve the original name for platforms we don't care to rename for
|
||||||
const downloadNames: Record<string, string> = {
|
const downloadNames: Record<string, string> = {
|
||||||
"darwin-aarch64-dmg": "OpenCode Desktop.dmg",
|
"darwin-aarch64-dmg": "OpenCode Desktop.dmg",
|
||||||
@@ -18,7 +27,7 @@ const downloadNames: Record<string, string> = {
|
|||||||
} satisfies { [K in DownloadPlatform]?: string }
|
} satisfies { [K in DownloadPlatform]?: string }
|
||||||
|
|
||||||
export async function GET({ params: { platform, channel } }: APIEvent) {
|
export async function GET({ params: { platform, channel } }: APIEvent) {
|
||||||
const assetName = assetNames[platform]
|
const assetName = channel === "stable" ? prodAssetNames[platform] : betaAssetNames[platform]
|
||||||
if (!assetName) return new Response(null, { status: 404 })
|
if (!assetName) return new Response(null, { status: 404 })
|
||||||
|
|
||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
@@ -37,5 +46,5 @@ export async function GET({ params: { platform, channel } }: APIEvent) {
|
|||||||
const headers = new Headers(resp.headers)
|
const headers = new Headers(resp.headers)
|
||||||
if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`)
|
if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`)
|
||||||
|
|
||||||
return new Response(resp.body, { ...resp, headers })
|
return new Response(resp.body, { status: resp.status, statusText: resp.statusText, headers })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default function Download() {
|
|||||||
|
|
||||||
const handleCopyClick = (command: string) => (event: Event) => {
|
const handleCopyClick = (command: string) => (event: Event) => {
|
||||||
const button = event.currentTarget as HTMLButtonElement
|
const button = event.currentTarget as HTMLButtonElement
|
||||||
navigator.clipboard.writeText(command)
|
void navigator.clipboard.writeText(command)
|
||||||
button.setAttribute("data-copied", "")
|
button.setAttribute("data-copied", "")
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
button.removeAttribute("data-copied")
|
button.removeAttribute("data-copied")
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Header } from "~/component/header"
|
|||||||
import { Footer } from "~/component/footer"
|
import { Footer } from "~/component/footer"
|
||||||
import { Legal } from "~/component/legal"
|
import { Legal } from "~/component/legal"
|
||||||
import { github } from "~/lib/github"
|
import { github } from "~/lib/github"
|
||||||
import { createMemo } from "solid-js"
|
|
||||||
import { config } from "~/config"
|
import { config } from "~/config"
|
||||||
import { useI18n } from "~/context/i18n"
|
import { useI18n } from "~/context/i18n"
|
||||||
import { useLanguage } from "~/context/language"
|
import { useLanguage } from "~/context/language"
|
||||||
@@ -30,12 +29,12 @@ function CopyStatus() {
|
|||||||
export default function Home() {
|
export default function Home() {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const githubData = createAsync(() => github())
|
const _githubData = createAsync(() => github())
|
||||||
const handleCopyClick = (event: Event) => {
|
const handleCopyClick = (event: Event) => {
|
||||||
const button = event.currentTarget as HTMLButtonElement
|
const button = event.currentTarget as HTMLButtonElement
|
||||||
const text = button.textContent
|
const text = button.textContent
|
||||||
if (text) {
|
if (text) {
|
||||||
navigator.clipboard.writeText(text)
|
void navigator.clipboard.writeText(text)
|
||||||
button.setAttribute("data-copied", "")
|
button.setAttribute("data-copied", "")
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
button.removeAttribute("data-copied")
|
button.removeAttribute("data-copied")
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function Home() {
|
|||||||
const callback = () => {
|
const callback = () => {
|
||||||
const text = button.textContent
|
const text = button.textContent
|
||||||
if (text) {
|
if (text) {
|
||||||
navigator.clipboard.writeText(text)
|
void navigator.clipboard.writeText(text)
|
||||||
button.setAttribute("data-copied", "")
|
button.setAttribute("data-copied", "")
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
button.removeAttribute("data-copied")
|
button.removeAttribute("data-copied")
|
||||||
|
|||||||
@@ -116,9 +116,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
|
|||||||
|
|
||||||
const setUseBalance = action(async (form: FormData) => {
|
const setUseBalance = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
const useBalance = form.get("useBalance")?.toString() === "true"
|
const useBalance = (form.get("useBalance") as string | null) === "true"
|
||||||
|
|
||||||
return json(
|
return json(
|
||||||
await withActor(async () => {
|
await withActor(async () => {
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import { formError, localizeError } from "~/lib/form-error"
|
|||||||
|
|
||||||
const setMonthlyLimit = action(async (form: FormData) => {
|
const setMonthlyLimit = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const limit = form.get("limit")?.toString()
|
const limit = form.get("limit") as string | null
|
||||||
if (!limit) return { error: formError.limitRequired }
|
if (!limit) return { error: formError.limitRequired }
|
||||||
const numericLimit = parseInt(limit)
|
const numericLimit = parseInt(limit)
|
||||||
if (numericLimit < 0) return { error: formError.monthlyLimitInvalid }
|
if (numericLimit < 0) return { error: formError.monthlyLimitInvalid }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(
|
return json(
|
||||||
await withActor(
|
await withActor(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { formError, formErrorReloadAmountMin, formErrorReloadTriggerMin, localiz
|
|||||||
|
|
||||||
const reload = action(async (form: FormData) => {
|
const reload = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(await withActor(() => Billing.reload(), workspaceID), {
|
return json(await withActor(() => Billing.reload(), workspaceID), {
|
||||||
revalidate: queryBillingInfo.key,
|
revalidate: queryBillingInfo.key,
|
||||||
@@ -21,11 +21,11 @@ const reload = action(async (form: FormData) => {
|
|||||||
|
|
||||||
const setReload = action(async (form: FormData) => {
|
const setReload = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
const reloadValue = form.get("reload")?.toString() === "true"
|
const reloadValue = (form.get("reload") as string | null) === "true"
|
||||||
const amountStr = form.get("reloadAmount")?.toString()
|
const amountStr = form.get("reloadAmount") as string | null
|
||||||
const triggerStr = form.get("reloadTrigger")?.toString()
|
const triggerStr = form.get("reloadTrigger") as string | null
|
||||||
|
|
||||||
const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
|
const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
|
||||||
const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
|
const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
|
||||||
@@ -91,8 +91,8 @@ export function ReloadSection() {
|
|||||||
const info = billingInfo()!
|
const info = billingInfo()!
|
||||||
setStore("show", true)
|
setStore("show", true)
|
||||||
setStore("reload", true)
|
setStore("reload", true)
|
||||||
setStore("reloadAmount", info.reloadAmount.toString())
|
setStore("reloadAmount", String(info.reloadAmount))
|
||||||
setStore("reloadTrigger", info.reloadTrigger.toString())
|
setStore("reloadTrigger", String(info.reloadTrigger))
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
@@ -152,11 +152,11 @@ export function ReloadSection() {
|
|||||||
data-component="input"
|
data-component="input"
|
||||||
name="reloadAmount"
|
name="reloadAmount"
|
||||||
type="number"
|
type="number"
|
||||||
min={billingInfo()?.reloadAmountMin.toString()}
|
min={String(billingInfo()?.reloadAmountMin ?? "")}
|
||||||
step="1"
|
step="1"
|
||||||
value={store.reloadAmount}
|
value={store.reloadAmount}
|
||||||
onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
|
onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
|
||||||
placeholder={billingInfo()?.reloadAmount.toString()}
|
placeholder={String(billingInfo()?.reloadAmount ?? "")}
|
||||||
disabled={!store.reload}
|
disabled={!store.reload}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,11 +166,11 @@ export function ReloadSection() {
|
|||||||
data-component="input"
|
data-component="input"
|
||||||
name="reloadTrigger"
|
name="reloadTrigger"
|
||||||
type="number"
|
type="number"
|
||||||
min={billingInfo()?.reloadTriggerMin.toString()}
|
min={String(billingInfo()?.reloadTriggerMin ?? "")}
|
||||||
step="1"
|
step="1"
|
||||||
value={store.reloadTrigger}
|
value={store.reloadTrigger}
|
||||||
onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
|
onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
|
||||||
placeholder={billingInfo()?.reloadTrigger.toString()}
|
placeholder={String(billingInfo()?.reloadTrigger ?? "")}
|
||||||
disabled={!store.reload}
|
disabled={!store.reload}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -120,9 +120,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
|
|||||||
|
|
||||||
const setLiteUseBalance = action(async (form: FormData) => {
|
const setLiteUseBalance = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
const useBalance = form.get("useBalance")?.toString() === "true"
|
const useBalance = (form.get("useBalance") as string | null) === "true"
|
||||||
|
|
||||||
return json(
|
return json(
|
||||||
await withActor(async () => {
|
await withActor(async () => {
|
||||||
|
|||||||
@@ -12,18 +12,18 @@ import { formError, localizeError } from "~/lib/form-error"
|
|||||||
|
|
||||||
const removeKey = action(async (form: FormData) => {
|
const removeKey = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const id = form.get("id")?.toString()
|
const id = form.get("id") as string | null
|
||||||
if (!id) return { error: formError.idRequired }
|
if (!id) return { error: formError.idRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
|
return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
|
||||||
}, "key.remove")
|
}, "key.remove")
|
||||||
|
|
||||||
const createKey = action(async (form: FormData) => {
|
const createKey = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const name = form.get("name")?.toString().trim()
|
const name = (form.get("name") as string | null)?.trim()
|
||||||
if (!name) return { error: formError.nameRequired }
|
if (!name) return { error: formError.nameRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(
|
return json(
|
||||||
await withActor(
|
await withActor(
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ const listMembers = query(async (workspaceID: string) => {
|
|||||||
|
|
||||||
const inviteMember = action(async (form: FormData) => {
|
const inviteMember = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const email = form.get("email")?.toString().trim()
|
const email = (form.get("email") as string | null)?.trim()
|
||||||
if (!email) return { error: formError.emailRequired }
|
if (!email) return { error: formError.emailRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
const role = form.get("role")?.toString() as (typeof UserRole)[number]
|
const role = form.get("role") as (typeof UserRole)[number] | null
|
||||||
if (!role) return { error: formError.roleRequired }
|
if (!role) return { error: formError.roleRequired }
|
||||||
const limit = form.get("limit")?.toString()
|
const limit = form.get("limit") as string | null
|
||||||
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
|
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
|
||||||
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
|
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
|
||||||
return json(
|
return json(
|
||||||
@@ -47,9 +47,9 @@ const inviteMember = action(async (form: FormData) => {
|
|||||||
|
|
||||||
const removeMember = action(async (form: FormData) => {
|
const removeMember = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const id = form.get("id")?.toString()
|
const id = form.get("id") as string | null
|
||||||
if (!id) return { error: formError.idRequired }
|
if (!id) return { error: formError.idRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(
|
return json(
|
||||||
await withActor(
|
await withActor(
|
||||||
@@ -66,13 +66,13 @@ const removeMember = action(async (form: FormData) => {
|
|||||||
const updateMember = action(async (form: FormData) => {
|
const updateMember = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
|
|
||||||
const id = form.get("id")?.toString()
|
const id = form.get("id") as string | null
|
||||||
if (!id) return { error: formError.idRequired }
|
if (!id) return { error: formError.idRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
const role = form.get("role")?.toString() as (typeof UserRole)[number]
|
const role = form.get("role") as (typeof UserRole)[number] | null
|
||||||
if (!role) return { error: formError.roleRequired }
|
if (!role) return { error: formError.roleRequired }
|
||||||
const limit = form.get("limit")?.toString()
|
const limit = form.get("limit") as string | null
|
||||||
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
|
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
|
||||||
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
|
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ function MemberRow(props: {
|
|||||||
}
|
}
|
||||||
setStore("editing", true)
|
setStore("editing", true)
|
||||||
setStore("selectedRole", props.member.role)
|
setStore("selectedRole", props.member.role)
|
||||||
setStore("limit", props.member.monthlyLimit?.toString() ?? "")
|
setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
|
|||||||
@@ -67,11 +67,11 @@ const getModelsInfo = query(async (workspaceID: string) => {
|
|||||||
|
|
||||||
const updateModel = action(async (form: FormData) => {
|
const updateModel = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const model = form.get("model")?.toString()
|
const model = form.get("model") as string | null
|
||||||
if (!model) return { error: formError.modelRequired }
|
if (!model) return { error: formError.modelRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
const enabled = form.get("enabled")?.toString() === "true"
|
const enabled = (form.get("enabled") as string | null) === "true"
|
||||||
return json(
|
return json(
|
||||||
withActor(async () => {
|
withActor(async () => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@@ -163,7 +163,7 @@ export function ModelSection() {
|
|||||||
<form action={updateModel} method="post">
|
<form action={updateModel} method="post">
|
||||||
<input type="hidden" name="model" value={id} />
|
<input type="hidden" name="model" value={id} />
|
||||||
<input type="hidden" name="workspaceID" value={params.id} />
|
<input type="hidden" name="workspaceID" value={params.id} />
|
||||||
<input type="hidden" name="enabled" value={isEnabled().toString()} />
|
<input type="hidden" name="enabled" value={String(isEnabled())} />
|
||||||
<label data-slot="model-toggle-label">
|
<label data-slot="model-toggle-label">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ function maskCredentials(credentials: string) {
|
|||||||
|
|
||||||
const removeProvider = action(async (form: FormData) => {
|
const removeProvider = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const provider = form.get("provider")?.toString()
|
const provider = form.get("provider") as string | null
|
||||||
if (!provider) return { error: formError.providerRequired }
|
if (!provider) return { error: formError.providerRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
|
return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
|
||||||
revalidate: listProviders.key,
|
revalidate: listProviders.key,
|
||||||
@@ -32,11 +32,11 @@ const removeProvider = action(async (form: FormData) => {
|
|||||||
|
|
||||||
const saveProvider = action(async (form: FormData) => {
|
const saveProvider = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const provider = form.get("provider")?.toString()
|
const provider = form.get("provider") as string | null
|
||||||
const credentials = form.get("credentials")?.toString()
|
const credentials = form.get("credentials") as string | null
|
||||||
if (!provider) return { error: formError.providerRequired }
|
if (!provider) return { error: formError.providerRequired }
|
||||||
if (!credentials) return { error: formError.apiKeyRequired }
|
if (!credentials) return { error: formError.apiKeyRequired }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(
|
return json(
|
||||||
await withActor(
|
await withActor(
|
||||||
@@ -59,10 +59,13 @@ function ProviderRow(props: { provider: Provider }) {
|
|||||||
const params = useParams()
|
const params = useParams()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const providers = createAsync(() => listProviders(params.id!))
|
const providers = createAsync(() => listProviders(params.id!))
|
||||||
const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key)
|
const saveSubmission = useSubmission(
|
||||||
|
saveProvider,
|
||||||
|
([fd]) => (fd.get("provider") as string | null) === props.provider.key,
|
||||||
|
)
|
||||||
const removeSubmission = useSubmission(
|
const removeSubmission = useSubmission(
|
||||||
removeProvider,
|
removeProvider,
|
||||||
([fd]) => fd.get("provider")?.toString() === props.provider.key,
|
([fd]) => (fd.get("provider") as string | null) === props.provider.key,
|
||||||
)
|
)
|
||||||
const [store, setStore] = createStore({ editing: false })
|
const [store, setStore] = createStore({ editing: false })
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ const getWorkspaceInfo = query(async (workspaceID: string) => {
|
|||||||
|
|
||||||
const updateWorkspace = action(async (form: FormData) => {
|
const updateWorkspace = action(async (form: FormData) => {
|
||||||
"use server"
|
"use server"
|
||||||
const name = form.get("name")?.toString().trim()
|
const name = (form.get("name") as string | null)?.trim()
|
||||||
if (!name) return { error: formError.workspaceNameRequired }
|
if (!name) return { error: formError.workspaceNameRequired }
|
||||||
if (name.length > 255) return { error: formError.nameTooLong }
|
if (name.length > 255) return { error: formError.nameTooLong }
|
||||||
const workspaceID = form.get("workspaceID")?.toString()
|
const workspaceID = form.get("workspaceID") as string | null
|
||||||
if (!workspaceID) return { error: formError.workspaceRequired }
|
if (!workspaceID) return { error: formError.workspaceRequired }
|
||||||
return json(
|
return json(
|
||||||
await withActor(
|
await withActor(
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ export function createDataDumper(sessionId: string, requestId: string, projectId
|
|||||||
const minute = timestamp.substring(10, 12)
|
const minute = timestamp.substring(10, 12)
|
||||||
const second = timestamp.substring(12, 14)
|
const second = timestamp.substring(12, 14)
|
||||||
|
|
||||||
waitUntil(
|
void waitUntil(
|
||||||
Resource.ZenDataNew.put(
|
Resource.ZenDataNew.put(
|
||||||
`data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`,
|
`data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`,
|
||||||
JSON.stringify({ timestamp, ...data }),
|
JSON.stringify({ timestamp, ...data }),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
waitUntil(
|
void waitUntil(
|
||||||
Resource.ZenDataNew.put(
|
Resource.ZenDataNew.put(
|
||||||
`meta/${data.modelName}/${sessionId}/${requestId}.json`,
|
`meta/${data.modelName}/${sessionId}/${requestId}.json`,
|
||||||
JSON.stringify({ timestamp, ...metadata }),
|
JSON.stringify({ timestamp, ...metadata }),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ export default defineConfig({
|
|||||||
plugins: [appPlugin],
|
plugins: [appPlugin],
|
||||||
publicDir: "../../../app/public",
|
publicDir: "../../../app/public",
|
||||||
root: "src/renderer",
|
root: "src/renderer",
|
||||||
|
define: {
|
||||||
|
"import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel),
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop-electron",
|
"name": "@opencode-ai/desktop-electron",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://opencode.ai",
|
"homepage": "https://opencode.ai",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
if (location.pathname === "/loading") {
|
if (location.pathname === "/loading") {
|
||||||
import("./loading")
|
void import("./loading")
|
||||||
} else {
|
} else {
|
||||||
import("./")
|
void import("./")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ const createPlatform = (): Platform => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let menuTrigger = null as null | ((id: string) => void)
|
let menuTrigger = null as null | ((id: string) => void)
|
||||||
createMenu((id) => {
|
void createMenu((id) => {
|
||||||
menuTrigger?.(id)
|
menuTrigger?.(id)
|
||||||
})
|
})
|
||||||
void listenForDeepLinks()
|
void listenForDeepLinks()
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ render(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
listener.then((cb) => cb())
|
void listener.then((cb) => cb())
|
||||||
timers.forEach(clearTimeout)
|
timers.forEach(clearTimeout)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -186,5 +186,5 @@ export async function createMenu(trigger: (id: string) => void) {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
menu.setAsAppMenu()
|
void menu.setAsAppMenu()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_Z
|
|||||||
|
|
||||||
const applyZoom = (next: number) => {
|
const applyZoom = (next: number) => {
|
||||||
setWebviewZoom(next)
|
setWebviewZoom(next)
|
||||||
invoke("plugin:webview|set_webview_zoom", {
|
void invoke("plugin:webview|set_webview_zoom", {
|
||||||
value: next,
|
value: next,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -37,4 +37,4 @@ async function test() {
|
|||||||
await Share.remove({ id: shareInfo.id, secret: shareInfo.secret })
|
await Share.remove({ id: shareInfo.id, secret: shareInfo.secret })
|
||||||
}
|
}
|
||||||
|
|
||||||
test()
|
void test()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "1.4.6"
|
version = "1.4.7"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/anomalyco/opencode"
|
repository = "https://github.com/anomalyco/opencode"
|
||||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
|||||||
icon = "./icons/opencode.svg"
|
icon = "./icons/opencode.svg"
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-aarch64]
|
[agent_servers.opencode.targets.darwin-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-darwin-arm64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-darwin-arm64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-x86_64]
|
[agent_servers.opencode.targets.darwin-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-darwin-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-darwin-x64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-aarch64]
|
[agent_servers.opencode.targets.linux-aarch64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-linux-arm64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-x86_64]
|
[agent_servers.opencode.targets.linux-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-linux-x64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.windows-x86_64]
|
[agent_servers.opencode.targets.windows-x86_64]
|
||||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-windows-x64.zip"
|
archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
research
|
research
|
||||||
dist
|
dist
|
||||||
|
dist-*
|
||||||
gen
|
gen
|
||||||
app.log
|
app.log
|
||||||
src/provider/models-snapshot.js
|
src/provider/models-snapshot.js
|
||||||
src/provider/models-snapshot.d.ts
|
src/provider/models-snapshot.d.ts
|
||||||
|
script/build-*.ts
|
||||||
|
temporary-*.md
|
||||||
|
|||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"name": ".opencode",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"@opencode-ai/plugin": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@opencode-ai/plugin": {
|
|
||||||
"version": "1.2.6",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@opencode-ai/sdk": "1.2.6",
|
|
||||||
"zod": "4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@opencode-ai/sdk": {
|
|
||||||
"version": "1.2.6",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/zod": {
|
|
||||||
"version": "4.1.8",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,6 +39,12 @@ See `specs/effect/migration.md` for the compact pattern reference and examples.
|
|||||||
- Do the work directly in the `InstanceState.make` closure — `ScopedCache` handles run-once semantics. Don't add fibers, `ensure()` callbacks, or `started` flags on top.
|
- Do the work directly in the `InstanceState.make` closure — `ScopedCache` handles run-once semantics. Don't add fibers, `ensure()` callbacks, or `started` flags on top.
|
||||||
- Use `Effect.addFinalizer` or `Effect.acquireRelease` inside the `InstanceState.make` closure for cleanup (subscriptions, process teardown, etc.).
|
- Use `Effect.addFinalizer` or `Effect.acquireRelease` inside the `InstanceState.make` closure for cleanup (subscriptions, process teardown, etc.).
|
||||||
- Use `Effect.forkScoped` inside the closure for background stream consumers — the fiber is interrupted when the instance is disposed.
|
- Use `Effect.forkScoped` inside the closure for background stream consumers — the fiber is interrupted when the instance is disposed.
|
||||||
|
- To make a service's `init()` non-blocking, fork `InstanceState.get(state)` at the `init()` call site (e.g. `Effect.forkIn(scope)`), not by forking work inside the `InstanceState.make` closure. Forking inside the closure leaves state incomplete for other methods that read it.
|
||||||
|
- `src/project/bootstrap.ts` already wraps every service `init()` in `Effect.forkDetach`, so `init()` is fire-and-forget in production. Keep `init()` methods synchronous internally; the caller controls concurrency.
|
||||||
|
|
||||||
|
## Effect v4 beta API
|
||||||
|
|
||||||
|
- `Effect.fork` and `Effect.forkDaemon` do not exist. Use `Effect.forkIn(scope)` to fork a fiber into a specific scope.
|
||||||
|
|
||||||
## Preferred Effect services
|
## Preferred Effect services
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"fix-node-pty": "bun run script/fix-node-pty.ts",
|
"fix-node-pty": "bun run script/fix-node-pty.ts",
|
||||||
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
||||||
"dev": "bun run --conditions=browser ./src/index.ts",
|
"dev": "bun run --conditions=browser ./src/index.ts",
|
||||||
|
"dev:temporary": "bun run --conditions=browser ./src/temporary.ts",
|
||||||
"db": "bun drizzle-kit"
|
"db": "bun drizzle-kit"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -78,15 +79,15 @@
|
|||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.16.1",
|
"@agentclientprotocol/sdk": "0.16.1",
|
||||||
"@ai-sdk/alibaba": "1.0.17",
|
"@ai-sdk/alibaba": "1.0.17",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.93",
|
"@ai-sdk/amazon-bedrock": "4.0.94",
|
||||||
"@ai-sdk/anthropic": "3.0.67",
|
"@ai-sdk/anthropic": "3.0.70",
|
||||||
"@ai-sdk/azure": "3.0.49",
|
"@ai-sdk/azure": "3.0.49",
|
||||||
"@ai-sdk/cerebras": "2.0.41",
|
"@ai-sdk/cerebras": "2.0.41",
|
||||||
"@ai-sdk/cohere": "3.0.27",
|
"@ai-sdk/cohere": "3.0.27",
|
||||||
"@ai-sdk/deepinfra": "2.0.41",
|
"@ai-sdk/deepinfra": "2.0.41",
|
||||||
"@ai-sdk/gateway": "3.0.97",
|
"@ai-sdk/gateway": "3.0.102",
|
||||||
"@ai-sdk/google": "3.0.63",
|
"@ai-sdk/google": "3.0.63",
|
||||||
"@ai-sdk/google-vertex": "4.0.109",
|
"@ai-sdk/google-vertex": "4.0.111",
|
||||||
"@ai-sdk/groq": "3.0.31",
|
"@ai-sdk/groq": "3.0.31",
|
||||||
"@ai-sdk/mistral": "3.0.27",
|
"@ai-sdk/mistral": "3.0.27",
|
||||||
"@ai-sdk/openai": "3.0.53",
|
"@ai-sdk/openai": "3.0.53",
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
/**
|
||||||
|
* Collapse a single-namespace barrel directory into a dir/index.ts module.
|
||||||
|
*
|
||||||
|
* Given a directory `src/foo/` that contains:
|
||||||
|
*
|
||||||
|
* - `index.ts` (exactly `export * as Foo from "./foo"`)
|
||||||
|
* - `foo.ts` (the real implementation)
|
||||||
|
* - zero or more sibling files
|
||||||
|
*
|
||||||
|
* this script:
|
||||||
|
*
|
||||||
|
* 1. Deletes the old `index.ts` barrel.
|
||||||
|
* 2. `git mv`s `foo.ts` → `index.ts` so the implementation IS the directory entry.
|
||||||
|
* 3. Appends `export * as Foo from "."` to the new `index.ts`.
|
||||||
|
* 4. Rewrites any same-directory sibling `*.ts` files that imported
|
||||||
|
* `./foo` (with or without the namespace name) to import `"."` instead.
|
||||||
|
*
|
||||||
|
* Consumer files outside the directory keep importing from the directory
|
||||||
|
* (`"@/foo"` / `"../foo"` / etc.) and continue to work, because
|
||||||
|
* `dir/index.ts` now provides the `Foo` named export directly.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* bun script/collapse-barrel.ts src/bus
|
||||||
|
* bun script/collapse-barrel.ts src/bus --dry-run
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
*
|
||||||
|
* - Only works on directories whose barrel is a single
|
||||||
|
* `export * as Name from "./file"` line. Refuses otherwise.
|
||||||
|
* - Refuses if the implementation file name already conflicts with
|
||||||
|
* `index.ts`.
|
||||||
|
* - Safe to run repeatedly: a second run on an already-collapsed dir
|
||||||
|
* will exit with a clear message.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from "node:fs"
|
||||||
|
import path from "node:path"
|
||||||
|
import { spawnSync } from "node:child_process"
|
||||||
|
|
||||||
|
const args = process.argv.slice(2)
|
||||||
|
const dryRun = args.includes("--dry-run")
|
||||||
|
const targetArg = args.find((a) => !a.startsWith("--"))
|
||||||
|
|
||||||
|
if (!targetArg) {
|
||||||
|
console.error("Usage: bun script/collapse-barrel.ts <dir> [--dry-run]")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dir = path.resolve(targetArg)
|
||||||
|
const indexPath = path.join(dir, "index.ts")
|
||||||
|
|
||||||
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
||||||
|
console.error(`Not a directory: ${dir}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
console.error(`No index.ts in ${dir}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate barrel shape.
|
||||||
|
const indexContent = fs.readFileSync(indexPath, "utf-8").trim()
|
||||||
|
const match = indexContent.match(/^export\s+\*\s+as\s+(\w+)\s+from\s+["']\.\/([^"']+)["']\s*;?\s*$/)
|
||||||
|
if (!match) {
|
||||||
|
console.error(`Not a simple single-namespace barrel:\n${indexContent}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
const namespaceName = match[1]
|
||||||
|
const implRel = match[2].replace(/\.ts$/, "")
|
||||||
|
const implPath = path.join(dir, `${implRel}.ts`)
|
||||||
|
|
||||||
|
if (!fs.existsSync(implPath)) {
|
||||||
|
console.error(`Implementation file not found: ${implPath}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (implRel === "index") {
|
||||||
|
console.error(`Nothing to do — impl file is already index.ts`)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Collapsing ${path.relative(process.cwd(), dir)}`)
|
||||||
|
console.log(` namespace: ${namespaceName}`)
|
||||||
|
console.log(` impl file: ${implRel}.ts → index.ts`)
|
||||||
|
|
||||||
|
// Figure out which sibling files need rewriting.
|
||||||
|
const siblings = fs
|
||||||
|
.readdirSync(dir)
|
||||||
|
.filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"))
|
||||||
|
.filter((f) => f !== "index.ts" && f !== `${implRel}.ts`)
|
||||||
|
.map((f) => path.join(dir, f))
|
||||||
|
|
||||||
|
type SiblingEdit = { file: string; content: string }
|
||||||
|
const siblingEdits: SiblingEdit[] = []
|
||||||
|
|
||||||
|
for (const sibling of siblings) {
|
||||||
|
const content = fs.readFileSync(sibling, "utf-8")
|
||||||
|
// Match any import or re-export referring to "./<implRel>" inside this directory.
|
||||||
|
const siblingRegex = new RegExp(`(from\\s*["'])\\.\\/${implRel.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&")}(["'])`, "g")
|
||||||
|
if (!siblingRegex.test(content)) continue
|
||||||
|
const updated = content.replace(siblingRegex, `$1.$2`)
|
||||||
|
siblingEdits.push({ file: sibling, content: updated })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (siblingEdits.length > 0) {
|
||||||
|
console.log(` sibling rewrites: ${siblingEdits.length}`)
|
||||||
|
for (const edit of siblingEdits) {
|
||||||
|
console.log(` ${path.relative(process.cwd(), edit.file)}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(` sibling rewrites: none`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dryRun) {
|
||||||
|
console.log(`\n(dry run) would:`)
|
||||||
|
console.log(` - delete ${path.relative(process.cwd(), indexPath)}`)
|
||||||
|
console.log(` - git mv ${path.relative(process.cwd(), implPath)} ${path.relative(process.cwd(), indexPath)}`)
|
||||||
|
console.log(` - append \`export * as ${namespaceName} from "."\` to the new index.ts`)
|
||||||
|
for (const edit of siblingEdits) {
|
||||||
|
console.log(` - rewrite sibling: ${path.relative(process.cwd(), edit.file)}`)
|
||||||
|
}
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply: remove the old barrel, git-mv the impl onto it, then rewrite content.
|
||||||
|
// We can't git-mv on top of an existing tracked file, so we remove the barrel first.
|
||||||
|
function runGit(...cmd: string[]) {
|
||||||
|
const res = spawnSync("git", cmd, { stdio: "inherit" })
|
||||||
|
if (res.status !== 0) {
|
||||||
|
console.error(`git ${cmd.join(" ")} failed`)
|
||||||
|
process.exit(res.status ?? 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: remove the barrel
|
||||||
|
runGit("rm", "-f", indexPath)
|
||||||
|
|
||||||
|
// Step 2: rename the impl file into index.ts
|
||||||
|
runGit("mv", implPath, indexPath)
|
||||||
|
|
||||||
|
// Step 3: append the self-reexport to the new index.ts
|
||||||
|
const newContent = fs.readFileSync(indexPath, "utf-8")
|
||||||
|
const trimmed = newContent.endsWith("\n") ? newContent : newContent + "\n"
|
||||||
|
fs.writeFileSync(indexPath, `${trimmed}\nexport * as ${namespaceName} from "."\n`)
|
||||||
|
console.log(` appended: export * as ${namespaceName} from "."`)
|
||||||
|
|
||||||
|
// Step 4: rewrite siblings
|
||||||
|
for (const edit of siblingEdits) {
|
||||||
|
fs.writeFileSync(edit.file, edit.content)
|
||||||
|
}
|
||||||
|
if (siblingEdits.length > 0) {
|
||||||
|
console.log(` rewrote ${siblingEdits.length} sibling file(s)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nDone. Verify with:`)
|
||||||
|
console.log(` cd packages/opencode`)
|
||||||
|
console.log(` bunx --bun tsgo --noEmit`)
|
||||||
|
console.log(` bun run --conditions=browser ./src/index.ts generate`)
|
||||||
|
console.log(` bun run test`)
|
||||||
@@ -68,23 +68,6 @@ function findBinary() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareBinDirectory(binaryName) {
|
|
||||||
const binDir = path.join(__dirname, "bin")
|
|
||||||
const targetPath = path.join(binDir, binaryName)
|
|
||||||
|
|
||||||
// Ensure bin directory exists
|
|
||||||
if (!fs.existsSync(binDir)) {
|
|
||||||
fs.mkdirSync(binDir, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove existing binary/symlink if it exists
|
|
||||||
if (fs.existsSync(targetPath)) {
|
|
||||||
fs.unlinkSync(targetPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { binDir, targetPath }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
if (os.platform() === "win32") {
|
if (os.platform() === "win32") {
|
||||||
@@ -112,7 +95,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
main()
|
void main()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Postinstall script error:", error.message)
|
console.error("Postinstall script error:", error.message)
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Config } from "../src/config"
|
import { Config } from "../src/config"
|
||||||
import { TuiConfig } from "../src/config/tui"
|
import { TuiConfig } from "../src/cli/cmd/tui/config/tui"
|
||||||
|
|
||||||
function generate(schema: z.ZodType) {
|
function generate(schema: z.ZodType) {
|
||||||
const result = z.toJSONSchema(schema, {
|
const result = z.toJSONSchema(schema, {
|
||||||
@@ -33,7 +33,7 @@ function generate(schema: z.ZodType) {
|
|||||||
schema.examples = [schema.default]
|
schema.examples = [schema.default]
|
||||||
}
|
}
|
||||||
|
|
||||||
schema.description = [schema.description || "", `default: \`${schema.default}\``]
|
schema.description = [schema.description || "", `default: \`${String(schema.default)}\``]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join("\n\n")
|
.join("\n\n")
|
||||||
.trim()
|
.trim()
|
||||||
|
|||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import path from "path"
|
||||||
|
const toDynamicallyImport = path.join(process.cwd(), process.argv[2])
|
||||||
|
await import(toDynamicallyImport)
|
||||||
|
console.log(performance.now())
|
||||||
Executable
+153
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import * as path from "path"
|
||||||
|
import * as ts from "typescript"
|
||||||
|
|
||||||
|
const BASE_DIR = "/home/thdxr/dev/projects/anomalyco/opencode/packages/opencode"
|
||||||
|
|
||||||
|
// Get entry file from command line arg or use default
|
||||||
|
const ENTRY_FILE = process.argv[2] || "src/cli/cmd/tui/plugin/index.ts"
|
||||||
|
|
||||||
|
const visited = new Set<string>()
|
||||||
|
|
||||||
|
function resolveImport(importPath: string, fromFile: string): string | null {
|
||||||
|
if (importPath.startsWith("@/")) {
|
||||||
|
return path.join(BASE_DIR, "src", importPath.slice(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
||||||
|
const dir = path.dirname(fromFile)
|
||||||
|
return path.resolve(dir, importPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInternalImport(importPath: string): boolean {
|
||||||
|
return importPath.startsWith("@/") || importPath.startsWith("./") || importPath.startsWith("../")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tryExtensions(filePath: string): Promise<string | null> {
|
||||||
|
const extensions = [".ts", ".tsx", ".js", ".jsx"]
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = Bun.file(filePath)
|
||||||
|
const stat = await file.stat()
|
||||||
|
|
||||||
|
if (stat?.isDirectory()) {
|
||||||
|
for (const ext of extensions) {
|
||||||
|
const indexPath = path.join(filePath, "index" + ext)
|
||||||
|
const indexFile = Bun.file(indexPath)
|
||||||
|
if (await indexFile.exists()) return indexPath
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's a file
|
||||||
|
return filePath
|
||||||
|
} catch {
|
||||||
|
// Path doesn't exist, try adding extensions
|
||||||
|
for (const ext of extensions) {
|
||||||
|
const withExt = filePath + ext
|
||||||
|
const extFile = Bun.file(withExt)
|
||||||
|
if (await extFile.exists()) return withExt
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractImports(sourceFile: ts.SourceFile): string[] {
|
||||||
|
const imports: string[] = []
|
||||||
|
|
||||||
|
function visit(node: ts.Node) {
|
||||||
|
// import x from "path" or import { x } from "path"
|
||||||
|
if (ts.isImportDeclaration(node)) {
|
||||||
|
// Skip type-only imports
|
||||||
|
if (node.importClause?.isTypeOnly) return
|
||||||
|
|
||||||
|
const moduleSpec = node.moduleSpecifier
|
||||||
|
if (ts.isStringLiteral(moduleSpec)) {
|
||||||
|
imports.push(moduleSpec.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export { x } from "path"
|
||||||
|
if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
||||||
|
if (ts.isStringLiteral(node.moduleSpecifier)) {
|
||||||
|
imports.push(node.moduleSpecifier.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic import: import("path")
|
||||||
|
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
||||||
|
const arg = node.arguments[0]
|
||||||
|
if (arg && ts.isStringLiteral(arg)) {
|
||||||
|
imports.push(arg.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.forEachChild(node, visit)
|
||||||
|
}
|
||||||
|
|
||||||
|
visit(sourceFile)
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
async function traceFile(filePath: string, depth = 0): Promise<void> {
|
||||||
|
const normalizedPath = path.relative(BASE_DIR, filePath)
|
||||||
|
|
||||||
|
if (visited.has(filePath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only trace TypeScript/JavaScript files
|
||||||
|
if (!filePath.match(/\.(ts|tsx|js|jsx)$/)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visited.add(filePath)
|
||||||
|
console.log("\t".repeat(depth) + normalizedPath)
|
||||||
|
|
||||||
|
let content: string
|
||||||
|
try {
|
||||||
|
content = await Bun.file(filePath).text()
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true)
|
||||||
|
|
||||||
|
const imports = extractImports(sourceFile)
|
||||||
|
const internalImports = imports.filter(isInternalImport)
|
||||||
|
const externalImports = imports.filter((imp) => !isInternalImport(imp))
|
||||||
|
|
||||||
|
// Print external imports
|
||||||
|
for (const imp of externalImports) {
|
||||||
|
console.log("\t".repeat(depth + 1) + `[ext] ${imp}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const imp of internalImports) {
|
||||||
|
const resolved = resolveImport(imp, filePath)
|
||||||
|
if (!resolved) continue
|
||||||
|
|
||||||
|
const actualPath = await tryExtensions(resolved)
|
||||||
|
if (!actualPath) continue
|
||||||
|
|
||||||
|
await traceFile(actualPath, depth + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const entryPath = path.join(BASE_DIR, ENTRY_FILE)
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
const file = Bun.file(entryPath)
|
||||||
|
if (!(await file.exists())) {
|
||||||
|
console.error(`File not found: ${ENTRY_FILE}`)
|
||||||
|
console.error(`Resolved to: ${entryPath}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
await traceFile(entryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
||||||
@@ -121,17 +121,46 @@ Why `question` first:
|
|||||||
|
|
||||||
Do not re-architect business logic during the HTTP migration. `HttpApi` handlers should call the same Effect services already used by the Hono handlers.
|
Do not re-architect business logic during the HTTP migration. `HttpApi` handlers should call the same Effect services already used by the Hono handlers.
|
||||||
|
|
||||||
### 4. Build in parallel, do not bridge into Hono
|
### 4. Bridge into Hono behind a feature flag
|
||||||
|
|
||||||
The `HttpApi` implementation lives under `src/server/instance/httpapi/` as a standalone Effect HTTP server. It is **not mounted into the Hono app**. There is no `toWebHandler` bridge, no Hono `Handler` export, and no `.route()` call wiring it into `experimental.ts`.
|
The `HttpApi` routes are bridged into the Hono server via `HttpRouter.toWebHandler` with a shared `memoMap`. This means:
|
||||||
|
|
||||||
The standalone server (`httpapi/server.ts`) can be started independently and proves the routes work. Tests exercise it via `HttpRouter.serve` with `NodeHttpServer.layerTest`.
|
- one process, one port — no separate server
|
||||||
|
- the Effect handler shares layer instances with `AppRuntime` (same `Question.Service`, etc.)
|
||||||
|
- Effect middleware handles auth and instance lookup independently from Hono middleware
|
||||||
|
- Hono's `.all()` catch-all intercepts matching paths before the Hono route handlers
|
||||||
|
|
||||||
The goal is to build enough route coverage in the Effect server that the Hono server can eventually be replaced entirely. Until then, the two implementations exist side by side but are completely separate processes.
|
The bridge is gated behind `OPENCODE_EXPERIMENTAL_HTTPAPI` (or `OPENCODE_EXPERIMENTAL`). When the flag is off (default), all requests go through the original Hono handlers unchanged.
|
||||||
|
|
||||||
### 5. Migrate JSON route groups gradually
|
```ts
|
||||||
|
// in instance/index.ts
|
||||||
|
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) {
|
||||||
|
const handler = ExperimentalHttpApiServer.webHandler().handler
|
||||||
|
app.all("/question", (c) => handler(c.req.raw)).all("/question/*", (c) => handler(c.req.raw))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
If the parallel slice works well, migrate additional JSON route groups one at a time. Leave streaming-style endpoints on Hono until there is a clear reason to move them.
|
The Hono route handlers are always registered (after the bridge) so `hono-openapi` generates the OpenAPI spec entries that feed SDK codegen. When the flag is on, these handlers are dead code — the `.all()` bridge matches first.
|
||||||
|
|
||||||
|
### 5. Observability
|
||||||
|
|
||||||
|
The `webHandler` provides `Observability.layer` via `Layer.provideMerge`. Since the `memoMap` is shared with `AppRuntime`, the tracing provider is deduplicated — no extra initialization cost.
|
||||||
|
|
||||||
|
This gives:
|
||||||
|
|
||||||
|
- **spans**: `Effect.fn("QuestionHttpApi.list")` etc. appear in traces alongside service-layer spans
|
||||||
|
- **HTTP logs**: `HttpMiddleware.logger` emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` annotations, flowing to motel via `OtlpLogger`
|
||||||
|
|
||||||
|
### 6. Migrate JSON route groups gradually
|
||||||
|
|
||||||
|
As each route group is ported to `HttpApi`:
|
||||||
|
|
||||||
|
1. change its `root` path from `/experimental/httpapi/<group>` to `/<group>`
|
||||||
|
2. add `.all("/<group>", handler)` / `.all("/<group>/*", handler)` to the flag block in `instance/index.ts`
|
||||||
|
3. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
|
||||||
|
4. verify SDK output is unchanged
|
||||||
|
|
||||||
|
Leave streaming-style endpoints on Hono until there is a clear reason to move them.
|
||||||
|
|
||||||
## Schema rule for HttpApi work
|
## Schema rule for HttpApi work
|
||||||
|
|
||||||
@@ -302,36 +331,43 @@ The first slice is successful if:
|
|||||||
- OpenAPI is generated from the `HttpApi` contract
|
- OpenAPI is generated from the `HttpApi` contract
|
||||||
- the tests are straightforward enough that the next slice feels mechanical
|
- the tests are straightforward enough that the next slice feels mechanical
|
||||||
|
|
||||||
## Learnings from the question slice
|
## Learnings
|
||||||
|
|
||||||
The first parallel `question` spike gave us a concrete pattern to reuse.
|
### Schema
|
||||||
|
|
||||||
- `Schema.Class` works well for route DTOs such as `Question.Request`, `Question.Info`, and `Question.Reply`.
|
- `Schema.Class` works well for route DTOs such as `Question.Request`, `Question.Info`, and `Question.Reply`.
|
||||||
- scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes.
|
- scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes.
|
||||||
- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects.
|
- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects.
|
||||||
- internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes.
|
- internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes.
|
||||||
- the experimental slice should stay as a standalone Effect server and keep calling the existing service layer unchanged.
|
- `Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes.
|
||||||
- compare generated OpenAPI semantically at the route and schema level.
|
|
||||||
|
### Integration
|
||||||
|
|
||||||
|
- `HttpRouter.toWebHandler` with the shared `memoMap` from `run-service.ts` cleanly bridges Effect routes into Hono — one process, one port, shared layer instances.
|
||||||
|
- `Observability.layer` must be explicitly provided via `Layer.provideMerge` in the routes layer for OTEL spans and HTTP logs to flow. The `memoMap` deduplicates it with `AppRuntime` — no extra cost.
|
||||||
|
- `HttpMiddleware.logger` (enabled by default when `disableLogger` is not set) emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` — these flow through `OtlpLogger` to motel.
|
||||||
|
- Hono OpenAPI stubs must remain registered for SDK codegen until the SDK pipeline reads from the Effect OpenAPI spec instead.
|
||||||
|
- the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag gates the bridge at the Hono router level — default off, no behavior change unless opted in.
|
||||||
|
|
||||||
## Route inventory
|
## Route inventory
|
||||||
|
|
||||||
Status legend:
|
Status legend:
|
||||||
|
|
||||||
- `done` - parallel `HttpApi` slice exists
|
- `bridged` - Effect HttpApi slice exists and is bridged into Hono behind the flag
|
||||||
|
- `done` - Effect HttpApi slice exists but not yet bridged
|
||||||
- `next` - good near-term candidate
|
- `next` - good near-term candidate
|
||||||
- `later` - possible, but not first wave
|
- `later` - possible, but not first wave
|
||||||
- `defer` - not a good early `HttpApi` target
|
- `defer` - not a good early `HttpApi` target
|
||||||
|
|
||||||
Current instance route inventory:
|
Current instance route inventory:
|
||||||
|
|
||||||
- `question` - `done`
|
- `question` - `bridged`
|
||||||
endpoints in slice: `GET /question`, `POST /question/:requestID/reply`
|
endpoints: `GET /question`, `POST /question/:requestID/reply`, `POST /question/:requestID/reject`
|
||||||
- `permission` - `done`
|
- `permission` - `bridged`
|
||||||
endpoints in slice: `GET /permission`, `POST /permission/:requestID/reply`
|
endpoints: `GET /permission`, `POST /permission/:requestID/reply`
|
||||||
- `provider` - `next`
|
- `provider` - `bridged` (partial)
|
||||||
best next endpoint: `GET /provider/auth`
|
bridged endpoint: `GET /provider/auth`
|
||||||
later endpoint: `GET /provider`
|
not yet ported: `GET /provider`, OAuth mutations
|
||||||
defer first-wave OAuth mutations
|
|
||||||
- `config` - `next`
|
- `config` - `next`
|
||||||
best next endpoint: `GET /config/providers`
|
best next endpoint: `GET /config/providers`
|
||||||
later endpoint: `GET /config`
|
later endpoint: `GET /config`
|
||||||
@@ -371,7 +407,13 @@ Recommended near-term sequence after the first spike:
|
|||||||
- [x] keep the underlying service calls identical to the current handlers
|
- [x] keep the underlying service calls identical to the current handlers
|
||||||
- [x] compare generated OpenAPI against the current Hono/OpenAPI setup
|
- [x] compare generated OpenAPI against the current Hono/OpenAPI setup
|
||||||
- [x] document how auth, instance lookup, and error mapping would compose in the new stack
|
- [x] document how auth, instance lookup, and error mapping would compose in the new stack
|
||||||
- [ ] decide after the spike whether `HttpApi` should stay parallel, replace only some groups, or become the long-term default
|
- [x] bridge Effect routes into Hono via `toWebHandler` with shared `memoMap`
|
||||||
|
- [x] gate behind `OPENCODE_EXPERIMENTAL_HTTPAPI` flag
|
||||||
|
- [x] verify OTEL spans and HTTP logs flow to motel
|
||||||
|
- [x] bridge question, permission, and provider auth routes
|
||||||
|
- [ ] port remaining provider endpoints (`GET /provider`, OAuth mutations)
|
||||||
|
- [ ] port `config` read endpoints
|
||||||
|
- [ ] decide when to remove the flag and make Effect routes the default
|
||||||
|
|
||||||
## Rule of thumb
|
## Rule of thumb
|
||||||
|
|
||||||
|
|||||||
@@ -1,444 +1,256 @@
|
|||||||
# Namespace → flat export migration
|
# Namespace → self-reexport migration
|
||||||
|
|
||||||
Migrate `export namespace` to the `export * as` / flat-export pattern used by
|
Migrate every `export namespace Foo { ... }` to flat top-level exports plus a
|
||||||
effect-smol. Primary goal: tree-shakeability. Secondary: consistency with Effect
|
single self-reexport line at the bottom of the same file:
|
||||||
conventions, LLM-friendliness for future migrations.
|
|
||||||
|
|
||||||
## What changes and what doesn't
|
|
||||||
|
|
||||||
The **consumer API stays the same**. You still write `Provider.ModelNotFoundError`,
|
|
||||||
`Config.JsonError`, `Bus.publish`, etc. The namespace ergonomics are preserved.
|
|
||||||
|
|
||||||
What changes is **how** the namespace is constructed — the TypeScript
|
|
||||||
`export namespace` keyword is replaced by `export * as` in a barrel file. This
|
|
||||||
is a mechanical change: unwrap the namespace body into flat exports, add a
|
|
||||||
one-line barrel. Consumers that import `{ Provider }` don't notice.
|
|
||||||
|
|
||||||
Import paths actually get **nicer**. Today most consumers import from the
|
|
||||||
explicit file (`"../provider/provider"`). After the migration, each module has a
|
|
||||||
barrel `index.ts`, so imports become `"../provider"` or `"@/provider"`:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// BEFORE — points at the file directly
|
export * as Foo from "./foo"
|
||||||
import { Provider } from "../provider/provider"
|
|
||||||
|
|
||||||
// AFTER — resolves to provider/index.ts, same Provider namespace
|
|
||||||
import { Provider } from "../provider"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Why this matters right now
|
No barrel `index.ts` files. No cross-directory indirection. Consumers keep the
|
||||||
|
exact same `import { Foo } from "../foo/foo"` ergonomics.
|
||||||
|
|
||||||
The CLI binary startup time (TOI) is too slow. Profiling shows we're loading
|
## Why this pattern
|
||||||
massive dependency graphs that are never actually used at runtime — because
|
|
||||||
bundlers cannot tree-shake TypeScript `export namespace` bodies.
|
|
||||||
|
|
||||||
### The problem in one sentence
|
We tested three options against Bun, esbuild, Rollup (what Vite uses under the
|
||||||
|
hood), Bun's runtime, and Node's native TypeScript runner.
|
||||||
`cli/error.ts` needs 6 lightweight `.isInstance()` checks on error classes, but
|
|
||||||
importing `{ Provider }` from `provider.ts` forces the bundler to include **all
|
|
||||||
20+ `@ai-sdk/*` packages**, `@aws-sdk/credential-providers`,
|
|
||||||
`google-auth-library`, and every other top-level import in that 1709-line file.
|
|
||||||
|
|
||||||
### Why `export namespace` defeats tree-shaking
|
|
||||||
|
|
||||||
TypeScript compiles `export namespace Foo { ... }` to an IIFE:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// TypeScript output
|
|
||||||
export var Provider;
|
|
||||||
(function (Provider) {
|
|
||||||
Provider.ModelNotFoundError = NamedError.create(...)
|
|
||||||
// ... 1600 more lines of assignments ...
|
|
||||||
})(Provider || (Provider = {}))
|
|
||||||
```
|
|
||||||
|
|
||||||
This is **opaque to static analysis**. The bundler sees one big function call
|
|
||||||
whose return value populates an object. It cannot determine which properties are
|
|
||||||
used downstream, so it keeps everything. Every `import` statement at the top of
|
|
||||||
`provider.ts` executes unconditionally — that's 20+ AI SDK packages loaded into
|
|
||||||
memory just so the CLI can check `Provider.ModelNotFoundError.isInstance(x)`.
|
|
||||||
|
|
||||||
### What `export * as` does differently
|
|
||||||
|
|
||||||
`export * as Provider from "./provider"` compiles to a static re-export. The
|
|
||||||
bundler knows the exact shape of `Provider` at compile time — it's the named
|
|
||||||
export list of `./provider.ts`. When it sees `Provider.ModelNotFoundError` used
|
|
||||||
but `Provider.layer` unused, it can trace that `ModelNotFoundError` doesn't
|
|
||||||
reference `createAnthropic` or any AI SDK import, and drop them. The namespace
|
|
||||||
object still exists at runtime — same API — but the bundler can see inside it.
|
|
||||||
|
|
||||||
### Concrete impact
|
|
||||||
|
|
||||||
The worst import chain in the codebase:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
src/index.ts (entry point)
|
heavy.ts loaded?
|
||||||
|
A. namespace B. barrel C. self-reexport
|
||||||
|
Bun bundler YES YES no
|
||||||
|
esbuild YES YES no
|
||||||
|
Rollup (Vite) YES YES no
|
||||||
|
Bun runtime YES YES no
|
||||||
|
Node --experimental-strip-types SYNTAX ERROR YES no
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`export namespace`** compiles to an IIFE. Bundlers see one opaque function
|
||||||
|
call and can't analyze what's used. Node's native TS runner rejects the
|
||||||
|
syntax outright: `SyntaxError: TypeScript namespace declaration is not
|
||||||
|
supported in strip-only mode`.
|
||||||
|
- **Barrel `index.ts`** files (`export * as Foo from "./foo"` in a separate
|
||||||
|
file) force every re-exported sibling to evaluate when you import one name.
|
||||||
|
Siblings with side effects (top-level imports of SDKs, etc.) always load.
|
||||||
|
- **Self-reexport** keeps the file as plain ESM. Bundlers see static named
|
||||||
|
exports. The module is only pulled in when something actually imports from
|
||||||
|
it. There is no barrel hop, so no sibling contamination and no circular
|
||||||
|
import hazard.
|
||||||
|
|
||||||
|
Bundle overhead for the self-reexport wrapper is roughly 240 bytes per module
|
||||||
|
(`Object.defineProperty` namespace proxy). At ~100 modules that's ~24KB —
|
||||||
|
negligible for a CLI binary.
|
||||||
|
|
||||||
|
## The pattern
|
||||||
|
|
||||||
|
### Before
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/permission/arity.ts
|
||||||
|
export namespace BashArity {
|
||||||
|
export function prefix(tokens: string[]) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### After
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/permission/arity.ts
|
||||||
|
export function prefix(tokens: string[]) { ... }
|
||||||
|
|
||||||
|
export * as BashArity from "./arity"
|
||||||
|
```
|
||||||
|
|
||||||
|
Consumers don't change at all:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { BashArity } from "@/permission/arity"
|
||||||
|
BashArity.prefix(...) // still works
|
||||||
|
```
|
||||||
|
|
||||||
|
Editors still auto-import `BashArity` like any named export, because the file
|
||||||
|
does have a named `BashArity` export at the module top level.
|
||||||
|
|
||||||
|
### Odd but harmless
|
||||||
|
|
||||||
|
`BashArity.BashArity.BashArity.prefix(...)` compiles and runs because the
|
||||||
|
namespace contains a re-export of itself. Nobody would write that. Not a
|
||||||
|
problem.
|
||||||
|
|
||||||
|
## Why this is different from what we tried first
|
||||||
|
|
||||||
|
An earlier pass used sibling barrel files (`index.ts` with `export * as ...`).
|
||||||
|
That turned out to be wrong for our constraints:
|
||||||
|
|
||||||
|
1. The barrel file always loads all its sibling modules when you import
|
||||||
|
through it, even if you only need one. For our CLI this is exactly the
|
||||||
|
cost we're trying to avoid.
|
||||||
|
2. Barrel + sibling imports made it very easy to accidentally create circular
|
||||||
|
imports that only surface as `ReferenceError` at runtime, not at
|
||||||
|
typecheck.
|
||||||
|
|
||||||
|
The self-reexport has none of those issues. There is no indirection. The
|
||||||
|
file and the namespace are the same unit.
|
||||||
|
|
||||||
|
## Why this matters for startup
|
||||||
|
|
||||||
|
The worst import chain in the codebase looks like:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/index.ts
|
||||||
└── FormatError from src/cli/error.ts
|
└── FormatError from src/cli/error.ts
|
||||||
├── { Provider } from provider/provider.ts (1709 lines)
|
├── { Provider } from provider/provider.ts (~1700 lines)
|
||||||
│ ├── 20+ @ai-sdk/* packages
|
│ ├── 20+ @ai-sdk/* packages
|
||||||
│ ├── @aws-sdk/credential-providers
|
│ ├── @aws-sdk/credential-providers
|
||||||
│ ├── google-auth-library
|
│ ├── google-auth-library
|
||||||
│ ├── gitlab-ai-provider, venice-ai-sdk-provider
|
│ └── more
|
||||||
│ └── fuzzysort, remeda, etc.
|
├── { Config } from config/config.ts (~1600 lines)
|
||||||
├── { Config } from config/config.ts (1663 lines)
|
└── { MCP } from mcp/mcp.ts (~900 lines)
|
||||||
│ ├── jsonc-parser
|
|
||||||
│ ├── LSPServer (all server definitions)
|
|
||||||
│ └── Plugin, Auth, Env, Account, etc.
|
|
||||||
└── { MCP } from mcp/index.ts (930 lines)
|
|
||||||
├── @modelcontextprotocol/sdk (3 transports)
|
|
||||||
└── open (browser launcher)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All of this gets pulled in to check `.isInstance()` on 6 error classes — code
|
All of that currently gets pulled in just to do `.isInstance()` on a handful
|
||||||
that needs maybe 200 bytes total. This inflates the binary, increases startup
|
of error classes. The namespace IIFE shape is the main reason bundlers cannot
|
||||||
memory, and slows down initial module evaluation.
|
strip the unused parts. Self-reexport + flat ESM fixes it.
|
||||||
|
|
||||||
### Why this also hurts memory
|
|
||||||
|
|
||||||
Every module-level import is eagerly evaluated. Even with Bun's fast module
|
|
||||||
loader, evaluating 20+ AI SDK factory functions, the AWS credential chain, and
|
|
||||||
Google's auth library allocates objects, closures, and prototype chains that
|
|
||||||
persist for the lifetime of the process. Most CLI commands never use a provider
|
|
||||||
at all.
|
|
||||||
|
|
||||||
## What effect-smol does
|
|
||||||
|
|
||||||
effect-smol achieves tree-shakeable namespaced APIs via three structural choices.
|
|
||||||
|
|
||||||
### 1. Each module is a separate file with flat named exports
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Effect.ts — no namespace wrapper, just flat exports
|
|
||||||
export const gen: { ... } = internal.gen
|
|
||||||
export const fail: <E>(error: E) => Effect<never, E> = internal.fail
|
|
||||||
export const succeed: <A>(value: A) => Effect<A> = internal.succeed
|
|
||||||
// ... 230+ individual named exports
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Barrel file uses `export * as` (not `export namespace`)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// index.ts
|
|
||||||
export * as Effect from "./Effect.ts"
|
|
||||||
export * as Schema from "./Schema.ts"
|
|
||||||
export * as Stream from "./Stream.ts"
|
|
||||||
// ~134 modules
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a namespace-like API (`Effect.gen`, `Schema.parse`) but the
|
|
||||||
bundler knows the **exact shape** at compile time — it's the static export list
|
|
||||||
of that file. It can trace property accesses (`Effect.gen` → keep `gen`,
|
|
||||||
drop `timeout` if unused). With `export namespace`, the IIFE is opaque and
|
|
||||||
nothing can be dropped.
|
|
||||||
|
|
||||||
### 3. `sideEffects: []` and deep imports
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
// package.json
|
|
||||||
{ "sideEffects": [] }
|
|
||||||
```
|
|
||||||
|
|
||||||
Plus `"./*": "./src/*.ts"` in the exports map, enabling
|
|
||||||
`import * as Effect from "effect/Effect"` to bypass the barrel entirely.
|
|
||||||
|
|
||||||
### 4. Errors as flat exports, not class declarations
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Cause.ts
|
|
||||||
export const NoSuchElementErrorTypeId = core.NoSuchElementErrorTypeId
|
|
||||||
export interface NoSuchElementError extends YieldableError { ... }
|
|
||||||
export const NoSuchElementError: new(msg?: string) => NoSuchElementError = core.NoSuchElementError
|
|
||||||
export const isNoSuchElementError: (u: unknown) => u is NoSuchElementError = core.isNoSuchElementError
|
|
||||||
```
|
|
||||||
|
|
||||||
Each error is 4 independent exports: TypeId, interface, constructor (as const),
|
|
||||||
type guard. All individually shakeable.
|
|
||||||
|
|
||||||
## The plan
|
|
||||||
|
|
||||||
The core migration is **Phase 1** — convert `export namespace` to
|
|
||||||
`export * as`. Once that's done, the bundler can tree-shake individual exports
|
|
||||||
within each module. You do NOT need to break things into subfiles for
|
|
||||||
tree-shaking to work — the bundler traces which exports you actually access on
|
|
||||||
the namespace object and drops the rest, including their transitive imports.
|
|
||||||
|
|
||||||
Splitting errors/schemas into separate files (Phase 0) is optional — it's a
|
|
||||||
lower-risk warmup step that can be done before or after the main conversion, and
|
|
||||||
it provides extra resilience against bundler edge cases. But the big win comes
|
|
||||||
from Phase 1.
|
|
||||||
|
|
||||||
### Phase 0 (optional): Pre-split errors into subfiles
|
|
||||||
|
|
||||||
This is a low-risk warmup that provides immediate benefit even before the full
|
|
||||||
`export * as` conversion. It's optional because Phase 1 alone is sufficient for
|
|
||||||
tree-shaking. But it's a good starting point if you want incremental progress:
|
|
||||||
|
|
||||||
**For each namespace that defines errors** (15 files, ~30 error classes total):
|
|
||||||
|
|
||||||
1. Create a sibling `errors.ts` file (e.g. `provider/errors.ts`) with the error
|
|
||||||
definitions as top-level named exports:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// provider/errors.ts
|
|
||||||
import z from "zod"
|
|
||||||
import { NamedError } from "@opencode-ai/shared/util/error"
|
|
||||||
import { ProviderID, ModelID } from "./schema"
|
|
||||||
|
|
||||||
export const ModelNotFoundError = NamedError.create(
|
|
||||||
"ProviderModelNotFoundError",
|
|
||||||
z.object({
|
|
||||||
providerID: ProviderID.zod,
|
|
||||||
modelID: ModelID.zod,
|
|
||||||
suggestions: z.array(z.string()).optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const InitError = NamedError.create("ProviderInitError", z.object({ providerID: ProviderID.zod }))
|
|
||||||
```
|
|
||||||
|
|
||||||
2. In the namespace file, re-export from the errors file to maintain backward
|
|
||||||
compatibility:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// provider/provider.ts — inside the namespace
|
|
||||||
export { ModelNotFoundError, InitError } from "./errors"
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Update `cli/error.ts` (and any other light consumers) to import directly:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// BEFORE
|
|
||||||
import { Provider } from "../provider/provider"
|
|
||||||
Provider.ModelNotFoundError.isInstance(input)
|
|
||||||
|
|
||||||
// AFTER
|
|
||||||
import { ModelNotFoundError as ProviderModelNotFoundError } from "../provider/errors"
|
|
||||||
ProviderModelNotFoundError.isInstance(input)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files to split (Phase 0):**
|
|
||||||
|
|
||||||
| Current file | New errors file | Errors to extract |
|
|
||||||
| ----------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `provider/provider.ts` | `provider/errors.ts` | ModelNotFoundError, InitError |
|
|
||||||
| `provider/auth.ts` | `provider/auth-errors.ts` | OauthMissing, OauthCodeMissing, OauthCallbackFailed, ValidationFailed |
|
|
||||||
| `config/config.ts` | (already has `config/paths.ts`) | ConfigDirectoryTypoError → move to paths.ts |
|
|
||||||
| `config/markdown.ts` | `config/markdown-errors.ts` | FrontmatterError |
|
|
||||||
| `mcp/index.ts` | `mcp/errors.ts` | Failed |
|
|
||||||
| `session/message-v2.ts` | `session/message-errors.ts` | OutputLengthError, AbortedError, StructuredOutputError, AuthError, APIError, ContextOverflowError |
|
|
||||||
| `session/message.ts` | (shares with message-v2) | OutputLengthError, AuthError |
|
|
||||||
| `cli/ui.ts` | `cli/ui-errors.ts` | CancelledError |
|
|
||||||
| `skill/index.ts` | `skill/errors.ts` | InvalidError, NameMismatchError |
|
|
||||||
| `worktree/index.ts` | `worktree/errors.ts` | NotGitError, NameGenerationFailedError, CreateFailedError, StartCommandFailedError, RemoveFailedError, ResetFailedError |
|
|
||||||
| `storage/storage.ts` | `storage/errors.ts` | NotFoundError |
|
|
||||||
| `npm/index.ts` | `npm/errors.ts` | InstallFailedError |
|
|
||||||
| `ide/index.ts` | `ide/errors.ts` | AlreadyInstalledError, InstallFailedError |
|
|
||||||
| `lsp/client.ts` | `lsp/errors.ts` | InitializeError |
|
|
||||||
|
|
||||||
### Phase 1: The real migration — `export namespace` → `export * as`
|
|
||||||
|
|
||||||
This is the phase that actually fixes tree-shaking. For each module:
|
|
||||||
|
|
||||||
1. **Unwrap** the `export namespace Foo { ... }` — remove the namespace wrapper,
|
|
||||||
keep all the members as top-level `export const` / `export function` / etc.
|
|
||||||
2. **Rename** the file if it's currently `index.ts` (e.g. `bus/index.ts` →
|
|
||||||
`bus/bus.ts`), so the barrel can take `index.ts`.
|
|
||||||
3. **Create the barrel** `index.ts` with one line: `export * as Foo from "./foo"`
|
|
||||||
|
|
||||||
The file structure change for a module that's currently a single file:
|
|
||||||
|
|
||||||
```
|
|
||||||
# BEFORE
|
|
||||||
provider/
|
|
||||||
provider.ts ← 1709-line file with `export namespace Provider { ... }`
|
|
||||||
|
|
||||||
# AFTER
|
|
||||||
provider/
|
|
||||||
index.ts ← NEW: `export * as Provider from "./provider"`
|
|
||||||
provider.ts ← SAME file, same name, just unwrap the namespace
|
|
||||||
```
|
|
||||||
|
|
||||||
And the code change is purely removing the wrapper:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// BEFORE: provider/provider.ts
|
|
||||||
export namespace Provider {
|
|
||||||
export class Service extends Context.Service<...>()("@opencode/Provider") {}
|
|
||||||
export const layer = Layer.effect(Service, ...)
|
|
||||||
export const ModelNotFoundError = NamedError.create(...)
|
|
||||||
export function parseModel(model: string) { ... }
|
|
||||||
}
|
|
||||||
|
|
||||||
// AFTER: provider/provider.ts — identical exports, no namespace keyword
|
|
||||||
export class Service extends Context.Service<...>()("@opencode/Provider") {}
|
|
||||||
export const layer = Layer.effect(Service, ...)
|
|
||||||
export const ModelNotFoundError = NamedError.create(...)
|
|
||||||
export function parseModel(model: string) { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// NEW: provider/index.ts
|
|
||||||
export * as Provider from "./provider"
|
|
||||||
```
|
|
||||||
|
|
||||||
Consumer code barely changes — import path gets shorter:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// BEFORE
|
|
||||||
import { Provider } from "../provider/provider"
|
|
||||||
|
|
||||||
// AFTER — resolves to provider/index.ts, same Provider object
|
|
||||||
import { Provider } from "../provider"
|
|
||||||
```
|
|
||||||
|
|
||||||
All access like `Provider.ModelNotFoundError`, `Provider.Service`,
|
|
||||||
`Provider.layer` works exactly as before. The difference is invisible to
|
|
||||||
consumers but lets the bundler see inside the namespace.
|
|
||||||
|
|
||||||
**Once this is done, you don't need to break anything into subfiles for
|
|
||||||
tree-shaking.** The bundler traces that `Provider.ModelNotFoundError` only
|
|
||||||
depends on `NamedError` + `zod` + the schema file, and drops
|
|
||||||
`Provider.layer` + all 20 AI SDK imports when they're unused. This works because
|
|
||||||
`export * as` gives the bundler a static export list it can do inner-graph
|
|
||||||
analysis on — it knows which exports reference which imports.
|
|
||||||
|
|
||||||
**Order of conversion** (by risk / size, do small modules first):
|
|
||||||
|
|
||||||
1. Tiny utilities: `Archive`, `Color`, `Token`, `Rpc`, `LocalContext` (~7-66 lines each)
|
|
||||||
2. Small services: `Auth`, `Env`, `BusEvent`, `SessionStatus`, `SessionRunState`, `Editor`, `Selection` (~25-91 lines)
|
|
||||||
3. Medium services: `Bus`, `Format`, `FileTime`, `FileWatcher`, `Command`, `Question`, `Permission`, `Vcs`, `Project`
|
|
||||||
4. Large services: `Config`, `Provider`, `MCP`, `Session`, `SessionProcessor`, `SessionPrompt`, `ACP`
|
|
||||||
|
|
||||||
### Phase 2: Build configuration
|
|
||||||
|
|
||||||
After the module structure supports tree-shaking:
|
|
||||||
|
|
||||||
1. Add `"sideEffects": []` to `packages/opencode/package.json` (or
|
|
||||||
`"sideEffects": false`) — this is safe because our services use explicit
|
|
||||||
layer composition, not import-time side effects.
|
|
||||||
2. Verify Bun's bundler respects the new structure. If Bun's tree-shaking is
|
|
||||||
insufficient, evaluate whether the compiled binary path needs an esbuild
|
|
||||||
pre-pass.
|
|
||||||
3. Consider adding `/*#__PURE__*/` annotations to `NamedError.create(...)` calls
|
|
||||||
— these are factory functions that return classes, and bundlers may not know
|
|
||||||
they're side-effect-free without the annotation.
|
|
||||||
|
|
||||||
## Automation
|
## Automation
|
||||||
|
|
||||||
The transformation is scripted. From `packages/opencode`:
|
From `packages/opencode`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun script/unwrap-namespace.ts <file> [--dry-run]
|
bun script/unwrap-namespace.ts <file> [--dry-run]
|
||||||
```
|
```
|
||||||
|
|
||||||
The script uses ast-grep for accurate AST-based namespace boundary detection
|
The script:
|
||||||
(no false matches from braces in strings/templates/comments), then:
|
|
||||||
|
|
||||||
1. Removes the `export namespace Foo {` line and its closing `}`
|
1. Uses ast-grep to locate the `export namespace Foo { ... }` block accurately.
|
||||||
2. Dedents the body by one indent level (2 spaces)
|
2. Removes the `export namespace Foo {` line and the matching closing `}`.
|
||||||
3. If the file is `index.ts`, renames it to `<name>.ts` and creates a new
|
3. Dedents the body by one indent level (2 spaces).
|
||||||
`index.ts` barrel
|
4. Rewrites `Foo.Bar` self-references inside the file to just `Bar`.
|
||||||
4. If the file is NOT `index.ts`, rewrites it in place and creates `index.ts`
|
5. Appends `export * as Foo from "./<basename>"` at the bottom of the file.
|
||||||
5. Prints the exact commands to find and rewrite import paths
|
6. Never creates a barrel `index.ts`.
|
||||||
|
|
||||||
### Walkthrough: converting a module
|
### Typical flow for one file
|
||||||
|
|
||||||
Using `Provider` as an example:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Preview what will change
|
# 1. Preview
|
||||||
bun script/unwrap-namespace.ts src/provider/provider.ts --dry-run
|
bun script/unwrap-namespace.ts src/permission/arity.ts --dry-run
|
||||||
|
|
||||||
# 2. Apply the transformation
|
# 2. Apply
|
||||||
bun script/unwrap-namespace.ts src/provider/provider.ts
|
bun script/unwrap-namespace.ts src/permission/arity.ts
|
||||||
|
|
||||||
# 3. Rewrite import paths (script prints the exact command)
|
# 3. Verify
|
||||||
rg -l 'from.*provider/provider' src/ | xargs sed -i '' 's|provider/provider"|provider"|g'
|
cd packages/opencode
|
||||||
|
bunx --bun tsgo --noEmit
|
||||||
# 4. Verify
|
bun run --conditions=browser ./src/index.ts generate
|
||||||
bun typecheck
|
bun run test <affected test files>
|
||||||
bun run test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**What changes on disk:**
|
### Consumer imports usually don't need to change
|
||||||
|
|
||||||
```
|
Most consumers already import straight from the file, e.g.:
|
||||||
# BEFORE
|
|
||||||
provider/
|
|
||||||
provider.ts ← 1709 lines, `export namespace Provider { ... }`
|
|
||||||
|
|
||||||
# AFTER
|
|
||||||
provider/
|
|
||||||
index.ts ← NEW: `export * as Provider from "./provider"`
|
|
||||||
provider.ts ← same file, namespace unwrapped to flat exports
|
|
||||||
```
|
|
||||||
|
|
||||||
**What changes in consumer code:**
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// BEFORE
|
import { BashArity } from "@/permission/arity"
|
||||||
import { Provider } from "../provider/provider"
|
import { Config } from "@/config/config"
|
||||||
|
|
||||||
// AFTER — shorter path, same Provider object
|
|
||||||
import { Provider } from "../provider"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All property access (`Provider.Service`, `Provider.ModelNotFoundError`, etc.)
|
Because the file itself now does `export * as Foo from "./foo"`, those imports
|
||||||
stays identical.
|
keep working with zero edits.
|
||||||
|
|
||||||
### Two cases the script handles
|
The only edits needed are when a consumer was importing through a previous
|
||||||
|
barrel (`"@/config"` or `"../config"` resolving to `config/index.ts`). In
|
||||||
|
that case, repoint it at the file:
|
||||||
|
|
||||||
**Case A: file is NOT `index.ts`** (e.g. `provider/provider.ts`)
|
```ts
|
||||||
|
// before
|
||||||
|
import { Config } from "@/config"
|
||||||
|
|
||||||
- Rewrites the file in place (unwrap + dedent)
|
// after
|
||||||
- Creates `provider/index.ts` as the barrel
|
import { Config } from "@/config/config"
|
||||||
- Import paths change: `"../provider/provider"` → `"../provider"`
|
```
|
||||||
|
|
||||||
**Case B: file IS `index.ts`** (e.g. `bus/index.ts`)
|
### Dynamic imports in tests
|
||||||
|
|
||||||
- Renames `index.ts` → `bus.ts` (kebab-case of namespace name)
|
If a test did `const { Foo } = await import("../../src/x/y")`, the destructure
|
||||||
- Creates new `index.ts` as the barrel
|
still works because of the self-reexport. No change required.
|
||||||
- **No import rewrites needed** — `"@/bus"` already resolves to `bus/index.ts`
|
|
||||||
|
|
||||||
## Do I need to split errors/schemas into subfiles?
|
## Verification checklist (per PR)
|
||||||
|
|
||||||
**No.** Once you do the `export * as` conversion, the bundler can tree-shake
|
Run all of these locally before pushing:
|
||||||
individual exports within the file. If `cli/error.ts` only accesses
|
|
||||||
`Provider.ModelNotFoundError`, the bundler traces that `ModelNotFoundError`
|
|
||||||
doesn't reference `createAnthropic` and drops the AI SDK imports.
|
|
||||||
|
|
||||||
Splitting into subfiles (errors.ts, schema.ts) is still a fine idea for **code
|
```bash
|
||||||
organization** — smaller files are easier to read and review. But it's not
|
cd packages/opencode
|
||||||
required for tree-shaking. The `export * as` conversion alone is sufficient.
|
bunx --bun tsgo --noEmit
|
||||||
|
bun run --conditions=browser ./src/index.ts generate
|
||||||
|
bun run test <affected test files>
|
||||||
|
```
|
||||||
|
|
||||||
The one case where subfile splitting provides extra tree-shake value is if an
|
Also do a quick grep in `src/`, `test/`, and `script/` to make sure no
|
||||||
imported package has module-level side effects that the bundler can't prove are
|
consumer is still importing the namespace from an old barrel path that no
|
||||||
unused. In practice this is rare — most npm packages are side-effect-free — and
|
longer exports it.
|
||||||
adding `"sideEffects": []` to package.json handles the common cases.
|
|
||||||
|
|
||||||
## Scope
|
The SDK build step (`bun run --conditions=browser ./src/index.ts generate`)
|
||||||
|
evaluates every module eagerly and is the most reliable way to catch circular
|
||||||
| Metric | Count |
|
import regressions at runtime — the typechecker does not catch these.
|
||||||
| ----------------------------------------------- | --------------- |
|
|
||||||
| Files with `export namespace` | 106 |
|
|
||||||
| Total namespace declarations | 118 (12 nested) |
|
|
||||||
| Files with `NamedError.create` inside namespace | 15 |
|
|
||||||
| Total error classes to extract | ~30 |
|
|
||||||
| Files using `export * as` today | 0 |
|
|
||||||
|
|
||||||
Phase 1 (the `export * as` conversion) is the main change. It's mechanical and
|
|
||||||
LLM-friendly but touches every import site, so it should be done module by
|
|
||||||
module with type-checking between each step. Each module is an independent PR.
|
|
||||||
|
|
||||||
## Rules for new code
|
## Rules for new code
|
||||||
|
|
||||||
Going forward:
|
- No new `export namespace`.
|
||||||
|
- Every module directory has a single canonical file — typically
|
||||||
|
`dir/index.ts` — with flat top-level exports and a self-reexport at the
|
||||||
|
bottom:
|
||||||
|
`export * as Foo from "."`
|
||||||
|
- Consumers import from the directory:
|
||||||
|
`import { Foo } from "@/dir"` or `import { Foo } from "../dir"`.
|
||||||
|
- No sibling barrel files. If a directory has multiple independent
|
||||||
|
namespaces, they each get their own file (e.g. `config/config.ts`,
|
||||||
|
`config/plugin.ts`) and their own self-reexport; the `index.ts` in that
|
||||||
|
directory stays minimal or does not exist.
|
||||||
|
- If a file needs a sibling, import the sibling file directly:
|
||||||
|
`import * as Sibling from "./sibling"`, not `from "."`.
|
||||||
|
|
||||||
- **No new `export namespace`**. Use a file with flat named exports and
|
### Why `dir/index.ts` + `"."` is fine for us
|
||||||
`export * as` in the barrel.
|
|
||||||
- Keep the service, layer, errors, schemas, and runtime wiring together in one
|
A single-file module (e.g. `pty/`) can live entirely in `dir/index.ts`
|
||||||
file if you want — that's fine now. The `export * as` barrel makes everything
|
with `export * as Foo from "."` at the bottom. Consumers write the
|
||||||
individually shakeable regardless of file structure.
|
short form:
|
||||||
- If a file grows large enough that it's hard to navigate, split by concern
|
|
||||||
(errors.ts, schema.ts, etc.) for readability. Not for tree-shaking — the
|
```ts
|
||||||
bundler handles that.
|
import { Pty } from "@/pty"
|
||||||
|
```
|
||||||
|
|
||||||
|
This works in Bun runtime, Bun build, esbuild, and Rollup. It does NOT
|
||||||
|
work under Node's `--experimental-strip-types` runner:
|
||||||
|
|
||||||
|
```
|
||||||
|
node --experimental-strip-types entry.ts
|
||||||
|
ERR_UNSUPPORTED_DIR_IMPORT: Directory import '/.../pty' is not supported
|
||||||
|
```
|
||||||
|
|
||||||
|
Node requires an explicit file or a `package.json#exports` map for ESM.
|
||||||
|
We don't care about that target right now because the opencode CLI is
|
||||||
|
built with Bun and the web apps are built with Vite/Rollup. If we ever
|
||||||
|
want to run raw `.ts` through Node, we'll need to either use explicit
|
||||||
|
`.ts` extensions everywhere or add per-directory `package.json` exports
|
||||||
|
maps.
|
||||||
|
|
||||||
|
### When NOT to collapse to `index.ts`
|
||||||
|
|
||||||
|
Some directories contain multiple independent namespaces where
|
||||||
|
`dir/index.ts` would be misleading. Examples:
|
||||||
|
|
||||||
|
- `config/` has `Config`, `ConfigPaths`, `ConfigMarkdown`, `ConfigPlugin`,
|
||||||
|
`ConfigKeybinds`. Each lives in its own file with its own self-reexport
|
||||||
|
(`config/config.ts`, `config/plugin.ts`, etc.). Consumers import the
|
||||||
|
specific one: `import { ConfigPlugin } from "@/config/plugin"`.
|
||||||
|
- Same shape for `session/`, `server/`, etc.
|
||||||
|
|
||||||
|
Collapsing one of those into `index.ts` would mean picking a single
|
||||||
|
"canonical" namespace for the directory, which breaks the symmetry and
|
||||||
|
hides the other files.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
There are still dozens of `export namespace` files left across the codebase.
|
||||||
|
Each one is its own small PR. Do them one at a time, verified locally, rather
|
||||||
|
than batching by directory.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { Effect, Layer, Option, Schema, Context } from "effect"
|
import { Effect, Layer, Option, Schema, Context } from "effect"
|
||||||
|
|
||||||
import { Database } from "@/storage/db"
|
import { Database } from "@/storage"
|
||||||
import { AccountStateTable, AccountTable } from "./account.sql"
|
import { AccountStateTable, AccountTable } from "./account.sql"
|
||||||
import { AccessToken, AccountID, AccountRepoError, Info, OrgID, RefreshToken } from "./schema"
|
import { AccessToken, AccountID, AccountRepoError, Info, OrgID, RefreshToken } from "./schema"
|
||||||
import { normalizeServerUrl } from "./url"
|
import { normalizeServerUrl } from "./url"
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ import {
|
|||||||
type Usage,
|
type Usage,
|
||||||
} from "@agentclientprotocol/sdk"
|
} from "@agentclientprotocol/sdk"
|
||||||
|
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util"
|
||||||
import { pathToFileURL } from "url"
|
import { pathToFileURL } from "url"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util"
|
||||||
import { Hash } from "@opencode-ai/shared/util/hash"
|
import { Hash } from "@opencode-ai/shared/util/hash"
|
||||||
import { ACPSessionManager } from "./session"
|
import { ACPSessionManager } from "./session"
|
||||||
import type { ACPConfig } from "./types"
|
import type { ACPConfig } from "./types"
|
||||||
@@ -44,11 +44,13 @@ import { AppRuntime } from "@/effect/app-runtime"
|
|||||||
import { Installation } from "@/installation"
|
import { Installation } from "@/installation"
|
||||||
import { MessageV2 } from "@/session/message-v2"
|
import { MessageV2 } from "@/session/message-v2"
|
||||||
import { Config } from "@/config"
|
import { Config } from "@/config"
|
||||||
|
import { ConfigMCP } from "@/config/mcp"
|
||||||
import { Todo } from "@/session/todo"
|
import { Todo } from "@/session/todo"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { LoadAPIKeyError } from "ai"
|
import { LoadAPIKeyError } from "ai"
|
||||||
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
|
import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
|
||||||
import { applyPatch } from "diff"
|
import { applyPatch } from "diff"
|
||||||
|
import { InstallationVersion } from "@/installation/version"
|
||||||
|
|
||||||
type ModeOption = { id: string; name: string; description?: string }
|
type ModeOption = { id: string; name: string; description?: string }
|
||||||
type ModelOption = { modelId: string; name: string }
|
type ModelOption = { modelId: string; name: string }
|
||||||
@@ -176,7 +178,7 @@ export namespace ACP {
|
|||||||
})
|
})
|
||||||
for await (const event of events.stream) {
|
for await (const event of events.stream) {
|
||||||
if (this.eventAbort.signal.aborted) return
|
if (this.eventAbort.signal.aborted) return
|
||||||
const payload = (event as any)?.payload
|
const payload = event?.payload
|
||||||
if (!payload) continue
|
if (!payload) continue
|
||||||
await this.handleEvent(payload as Event).catch((error) => {
|
await this.handleEvent(payload as Event).catch((error) => {
|
||||||
log.error("failed to handle event", { error, type: payload.type })
|
log.error("failed to handle event", { error, type: payload.type })
|
||||||
@@ -242,7 +244,7 @@ export namespace ACP {
|
|||||||
const newContent = getNewContent(content, diff)
|
const newContent = getNewContent(content, diff)
|
||||||
|
|
||||||
if (newContent) {
|
if (newContent) {
|
||||||
this.connection.writeTextFile({
|
void this.connection.writeTextFile({
|
||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
path: filepath,
|
path: filepath,
|
||||||
content: newContent,
|
content: newContent,
|
||||||
@@ -570,7 +572,7 @@ export namespace ACP {
|
|||||||
authMethods: [authMethod],
|
authMethods: [authMethod],
|
||||||
agentInfo: {
|
agentInfo: {
|
||||||
name: "OpenCode",
|
name: "OpenCode",
|
||||||
version: Installation.VERSION,
|
version: InstallationVersion,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1212,7 +1214,7 @@ export namespace ACP {
|
|||||||
description: "compact the session",
|
description: "compact the session",
|
||||||
})
|
})
|
||||||
|
|
||||||
const mcpServers: Record<string, Config.Mcp> = {}
|
const mcpServers: Record<string, ConfigMCP.Info> = {}
|
||||||
for (const server of params.mcpServers) {
|
for (const server of params.mcpServers) {
|
||||||
if ("type" in server) {
|
if ("type" in server) {
|
||||||
mcpServers[server.name] = {
|
mcpServers[server.name] = {
|
||||||
@@ -1253,7 +1255,7 @@ export namespace ACP {
|
|||||||
)
|
)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.connection.sessionUpdate({
|
void this.connection.sessionUpdate({
|
||||||
sessionId,
|
sessionId,
|
||||||
update: {
|
update: {
|
||||||
sessionUpdate: "available_commands_update",
|
sessionUpdate: "available_commands_update",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RequestError, type McpServer } from "@agentclientprotocol/sdk"
|
import { RequestError, type McpServer } from "@agentclientprotocol/sdk"
|
||||||
import type { ACPSessionState } from "./types"
|
import type { ACPSessionState } from "./types"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util"
|
||||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
|
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
|
||||||
|
|
||||||
const log = Log.create({ service: "acp-session-manager" })
|
const log = Log.create({ service: "acp-session-manager" })
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { Provider } from "../provider"
|
|||||||
import { ModelID, ProviderID } from "../provider/schema"
|
import { ModelID, ProviderID } from "../provider/schema"
|
||||||
import { generateObject, streamObject, type ModelMessage } from "ai"
|
import { generateObject, streamObject, type ModelMessage } from "ai"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Truncate } from "../tool/truncate"
|
import { Truncate } from "../tool"
|
||||||
import { Auth } from "../auth"
|
import { Auth } from "../auth"
|
||||||
import { ProviderTransform } from "../provider/transform"
|
import { ProviderTransform } from "../provider"
|
||||||
|
|
||||||
import PROMPT_GENERATE from "./generate.txt"
|
import PROMPT_GENERATE from "./generate.txt"
|
||||||
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
import PROMPT_COMPACTION from "./prompt/compaction.txt"
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ export const layer = Layer.effect(
|
|||||||
const decode = Schema.decodeUnknownOption(Info)
|
const decode = Schema.decodeUnknownOption(Info)
|
||||||
|
|
||||||
const all = Effect.fn("Auth.all")(function* () {
|
const all = Effect.fn("Auth.all")(function* () {
|
||||||
|
if (process.env.OPENCODE_AUTH_CONTENT) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(process.env.OPENCODE_AUTH_CONTENT)
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
const data = (yield* fsys.readJson(file).pipe(Effect.orElseSucceed(() => ({})))) as Record<string, unknown>
|
const data = (yield* fsys.readJson(file).pipe(Effect.orElseSucceed(() => ({})))) as Record<string, unknown>
|
||||||
return Record.filterMap(data, (value) => Result.fromOption(decode(value), () => undefined))
|
return Record.filterMap(data, (value) => Result.fromOption(decode(value), () => undefined))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
import z from "zod"
|
|
||||||
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect"
|
|
||||||
import { EffectBridge } from "@/effect"
|
|
||||||
import { Log } from "../util/log"
|
|
||||||
import { BusEvent } from "./bus-event"
|
|
||||||
import { GlobalBus } from "./global"
|
|
||||||
import { InstanceState } from "@/effect"
|
|
||||||
import { makeRuntime } from "@/effect/run-service"
|
|
||||||
|
|
||||||
const log = Log.create({ service: "bus" })
|
|
||||||
|
|
||||||
export const InstanceDisposed = BusEvent.define(
|
|
||||||
"server.instance.disposed",
|
|
||||||
z.object({
|
|
||||||
directory: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
type Payload<D extends BusEvent.Definition = BusEvent.Definition> = {
|
|
||||||
type: D["type"]
|
|
||||||
properties: z.infer<D["properties"]>
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
wildcard: PubSub.PubSub<Payload>
|
|
||||||
typed: Map<string, PubSub.PubSub<Payload>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Interface {
|
|
||||||
readonly publish: <D extends BusEvent.Definition>(
|
|
||||||
def: D,
|
|
||||||
properties: z.output<D["properties"]>,
|
|
||||||
) => Effect.Effect<void>
|
|
||||||
readonly subscribe: <D extends BusEvent.Definition>(def: D) => Stream.Stream<Payload<D>>
|
|
||||||
readonly subscribeAll: () => Stream.Stream<Payload>
|
|
||||||
readonly subscribeCallback: <D extends BusEvent.Definition>(
|
|
||||||
def: D,
|
|
||||||
callback: (event: Payload<D>) => unknown,
|
|
||||||
) => Effect.Effect<() => void>
|
|
||||||
readonly subscribeAllCallback: (callback: (event: any) => unknown) => Effect.Effect<() => void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Bus") {}
|
|
||||||
|
|
||||||
export const layer = Layer.effect(
|
|
||||||
Service,
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const state = yield* InstanceState.make<State>(
|
|
||||||
Effect.fn("Bus.state")(function* (ctx) {
|
|
||||||
const wildcard = yield* PubSub.unbounded<Payload>()
|
|
||||||
const typed = new Map<string, PubSub.PubSub<Payload>>()
|
|
||||||
|
|
||||||
yield* Effect.addFinalizer(() =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
// Publish InstanceDisposed before shutting down so subscribers see it
|
|
||||||
yield* PubSub.publish(wildcard, {
|
|
||||||
type: InstanceDisposed.type,
|
|
||||||
properties: { directory: ctx.directory },
|
|
||||||
})
|
|
||||||
yield* PubSub.shutdown(wildcard)
|
|
||||||
for (const ps of typed.values()) {
|
|
||||||
yield* PubSub.shutdown(ps)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
return { wildcard, typed }
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
function getOrCreate<D extends BusEvent.Definition>(state: State, def: D) {
|
|
||||||
return Effect.gen(function* () {
|
|
||||||
let ps = state.typed.get(def.type)
|
|
||||||
if (!ps) {
|
|
||||||
ps = yield* PubSub.unbounded<Payload>()
|
|
||||||
state.typed.set(def.type, ps)
|
|
||||||
}
|
|
||||||
return ps as unknown as PubSub.PubSub<Payload<D>>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
|
|
||||||
return Effect.gen(function* () {
|
|
||||||
const s = yield* InstanceState.get(state)
|
|
||||||
const payload: Payload = { type: def.type, properties }
|
|
||||||
log.info("publishing", { type: def.type })
|
|
||||||
|
|
||||||
const ps = s.typed.get(def.type)
|
|
||||||
if (ps) yield* PubSub.publish(ps, payload)
|
|
||||||
yield* PubSub.publish(s.wildcard, payload)
|
|
||||||
|
|
||||||
const dir = yield* InstanceState.directory
|
|
||||||
const context = yield* InstanceState.context
|
|
||||||
const workspace = yield* InstanceState.workspaceID
|
|
||||||
|
|
||||||
GlobalBus.emit("event", {
|
|
||||||
directory: dir,
|
|
||||||
project: context.project.id,
|
|
||||||
workspace,
|
|
||||||
payload,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribe<D extends BusEvent.Definition>(def: D): Stream.Stream<Payload<D>> {
|
|
||||||
log.info("subscribing", { type: def.type })
|
|
||||||
return Stream.unwrap(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const s = yield* InstanceState.get(state)
|
|
||||||
const ps = yield* getOrCreate(s, def)
|
|
||||||
return Stream.fromPubSub(ps)
|
|
||||||
}),
|
|
||||||
).pipe(Stream.ensuring(Effect.sync(() => log.info("unsubscribing", { type: def.type }))))
|
|
||||||
}
|
|
||||||
|
|
||||||
function subscribeAll(): Stream.Stream<Payload> {
|
|
||||||
log.info("subscribing", { type: "*" })
|
|
||||||
return Stream.unwrap(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const s = yield* InstanceState.get(state)
|
|
||||||
return Stream.fromPubSub(s.wildcard)
|
|
||||||
}),
|
|
||||||
).pipe(Stream.ensuring(Effect.sync(() => log.info("unsubscribing", { type: "*" }))))
|
|
||||||
}
|
|
||||||
|
|
||||||
function on<T>(pubsub: PubSub.PubSub<T>, type: string, callback: (event: T) => unknown) {
|
|
||||||
return Effect.gen(function* () {
|
|
||||||
log.info("subscribing", { type })
|
|
||||||
const bridge = yield* EffectBridge.make()
|
|
||||||
const scope = yield* Scope.make()
|
|
||||||
const subscription = yield* Scope.provide(scope)(PubSub.subscribe(pubsub))
|
|
||||||
|
|
||||||
yield* Scope.provide(scope)(
|
|
||||||
Stream.fromSubscription(subscription).pipe(
|
|
||||||
Stream.runForEach((msg) =>
|
|
||||||
Effect.tryPromise({
|
|
||||||
try: () => Promise.resolve().then(() => callback(msg)),
|
|
||||||
catch: (cause) => {
|
|
||||||
log.error("subscriber failed", { type, cause })
|
|
||||||
},
|
|
||||||
}).pipe(Effect.ignore),
|
|
||||||
),
|
|
||||||
Effect.forkScoped,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
log.info("unsubscribing", { type })
|
|
||||||
bridge.fork(Scope.close(scope, Exit.void))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscribeCallback = Effect.fn("Bus.subscribeCallback")(function* <D extends BusEvent.Definition>(
|
|
||||||
def: D,
|
|
||||||
callback: (event: Payload<D>) => unknown,
|
|
||||||
) {
|
|
||||||
const s = yield* InstanceState.get(state)
|
|
||||||
const ps = yield* getOrCreate(s, def)
|
|
||||||
return yield* on(ps, def.type, callback)
|
|
||||||
})
|
|
||||||
|
|
||||||
const subscribeAllCallback = Effect.fn("Bus.subscribeAllCallback")(function* (callback: (event: any) => unknown) {
|
|
||||||
const s = yield* InstanceState.get(state)
|
|
||||||
return yield* on(s.wildcard, "*", callback)
|
|
||||||
})
|
|
||||||
|
|
||||||
return Service.of({ publish, subscribe, subscribeAll, subscribeCallback, subscribeAllCallback })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const defaultLayer = layer
|
|
||||||
|
|
||||||
const { runPromise, runSync } = makeRuntime(Service, layer)
|
|
||||||
|
|
||||||
// runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe,
|
|
||||||
// Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw.
|
|
||||||
export async function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
|
|
||||||
return runPromise((svc) => svc.publish(def, properties))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function subscribe<D extends BusEvent.Definition>(
|
|
||||||
def: D,
|
|
||||||
callback: (event: { type: D["type"]; properties: z.infer<D["properties"]> }) => unknown,
|
|
||||||
) {
|
|
||||||
return runSync((svc) => svc.subscribeCallback(def, callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function subscribeAll(callback: (event: any) => unknown) {
|
|
||||||
return runSync((svc) => svc.subscribeAllCallback(callback))
|
|
||||||
}
|
|
||||||
@@ -1 +1,193 @@
|
|||||||
export * as Bus from "./bus"
|
import z from "zod"
|
||||||
|
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect"
|
||||||
|
import { EffectBridge } from "@/effect"
|
||||||
|
import { Log } from "../util"
|
||||||
|
import { BusEvent } from "./bus-event"
|
||||||
|
import { GlobalBus } from "./global"
|
||||||
|
import { InstanceState } from "@/effect"
|
||||||
|
import { makeRuntime } from "@/effect/run-service"
|
||||||
|
|
||||||
|
const log = Log.create({ service: "bus" })
|
||||||
|
|
||||||
|
export const InstanceDisposed = BusEvent.define(
|
||||||
|
"server.instance.disposed",
|
||||||
|
z.object({
|
||||||
|
directory: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
type Payload<D extends BusEvent.Definition = BusEvent.Definition> = {
|
||||||
|
type: D["type"]
|
||||||
|
properties: z.infer<D["properties"]>
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
wildcard: PubSub.PubSub<Payload>
|
||||||
|
typed: Map<string, PubSub.PubSub<Payload>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Interface {
|
||||||
|
readonly publish: <D extends BusEvent.Definition>(
|
||||||
|
def: D,
|
||||||
|
properties: z.output<D["properties"]>,
|
||||||
|
) => Effect.Effect<void>
|
||||||
|
readonly subscribe: <D extends BusEvent.Definition>(def: D) => Stream.Stream<Payload<D>>
|
||||||
|
readonly subscribeAll: () => Stream.Stream<Payload>
|
||||||
|
readonly subscribeCallback: <D extends BusEvent.Definition>(
|
||||||
|
def: D,
|
||||||
|
callback: (event: Payload<D>) => unknown,
|
||||||
|
) => Effect.Effect<() => void>
|
||||||
|
readonly subscribeAllCallback: (callback: (event: any) => unknown) => Effect.Effect<() => void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Bus") {}
|
||||||
|
|
||||||
|
export const layer = Layer.effect(
|
||||||
|
Service,
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const state = yield* InstanceState.make<State>(
|
||||||
|
Effect.fn("Bus.state")(function* (ctx) {
|
||||||
|
const wildcard = yield* PubSub.unbounded<Payload>()
|
||||||
|
const typed = new Map<string, PubSub.PubSub<Payload>>()
|
||||||
|
|
||||||
|
yield* Effect.addFinalizer(() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
// Publish InstanceDisposed before shutting down so subscribers see it
|
||||||
|
yield* PubSub.publish(wildcard, {
|
||||||
|
type: InstanceDisposed.type,
|
||||||
|
properties: { directory: ctx.directory },
|
||||||
|
})
|
||||||
|
yield* PubSub.shutdown(wildcard)
|
||||||
|
for (const ps of typed.values()) {
|
||||||
|
yield* PubSub.shutdown(ps)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return { wildcard, typed }
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
function getOrCreate<D extends BusEvent.Definition>(state: State, def: D) {
|
||||||
|
return Effect.gen(function* () {
|
||||||
|
let ps = state.typed.get(def.type)
|
||||||
|
if (!ps) {
|
||||||
|
ps = yield* PubSub.unbounded<Payload>()
|
||||||
|
state.typed.set(def.type, ps)
|
||||||
|
}
|
||||||
|
return ps as unknown as PubSub.PubSub<Payload<D>>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
|
||||||
|
return Effect.gen(function* () {
|
||||||
|
const s = yield* InstanceState.get(state)
|
||||||
|
const payload: Payload = { type: def.type, properties }
|
||||||
|
log.info("publishing", { type: def.type })
|
||||||
|
|
||||||
|
const ps = s.typed.get(def.type)
|
||||||
|
if (ps) yield* PubSub.publish(ps, payload)
|
||||||
|
yield* PubSub.publish(s.wildcard, payload)
|
||||||
|
|
||||||
|
const dir = yield* InstanceState.directory
|
||||||
|
const context = yield* InstanceState.context
|
||||||
|
const workspace = yield* InstanceState.workspaceID
|
||||||
|
|
||||||
|
GlobalBus.emit("event", {
|
||||||
|
directory: dir,
|
||||||
|
project: context.project.id,
|
||||||
|
workspace,
|
||||||
|
payload,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribe<D extends BusEvent.Definition>(def: D): Stream.Stream<Payload<D>> {
|
||||||
|
log.info("subscribing", { type: def.type })
|
||||||
|
return Stream.unwrap(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const s = yield* InstanceState.get(state)
|
||||||
|
const ps = yield* getOrCreate(s, def)
|
||||||
|
return Stream.fromPubSub(ps)
|
||||||
|
}),
|
||||||
|
).pipe(Stream.ensuring(Effect.sync(() => log.info("unsubscribing", { type: def.type }))))
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribeAll(): Stream.Stream<Payload> {
|
||||||
|
log.info("subscribing", { type: "*" })
|
||||||
|
return Stream.unwrap(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const s = yield* InstanceState.get(state)
|
||||||
|
return Stream.fromPubSub(s.wildcard)
|
||||||
|
}),
|
||||||
|
).pipe(Stream.ensuring(Effect.sync(() => log.info("unsubscribing", { type: "*" }))))
|
||||||
|
}
|
||||||
|
|
||||||
|
function on<T>(pubsub: PubSub.PubSub<T>, type: string, callback: (event: T) => unknown) {
|
||||||
|
return Effect.gen(function* () {
|
||||||
|
log.info("subscribing", { type })
|
||||||
|
const bridge = yield* EffectBridge.make()
|
||||||
|
const scope = yield* Scope.make()
|
||||||
|
const subscription = yield* Scope.provide(scope)(PubSub.subscribe(pubsub))
|
||||||
|
|
||||||
|
yield* Scope.provide(scope)(
|
||||||
|
Stream.fromSubscription(subscription).pipe(
|
||||||
|
Stream.runForEach((msg) =>
|
||||||
|
Effect.tryPromise({
|
||||||
|
try: () => Promise.resolve().then(() => callback(msg)),
|
||||||
|
catch: (cause) => {
|
||||||
|
log.error("subscriber failed", { type, cause })
|
||||||
|
},
|
||||||
|
}).pipe(Effect.ignore),
|
||||||
|
),
|
||||||
|
Effect.forkScoped,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
log.info("unsubscribing", { type })
|
||||||
|
bridge.fork(Scope.close(scope, Exit.void))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscribeCallback = Effect.fn("Bus.subscribeCallback")(function* <D extends BusEvent.Definition>(
|
||||||
|
def: D,
|
||||||
|
callback: (event: Payload<D>) => unknown,
|
||||||
|
) {
|
||||||
|
const s = yield* InstanceState.get(state)
|
||||||
|
const ps = yield* getOrCreate(s, def)
|
||||||
|
return yield* on(ps, def.type, callback)
|
||||||
|
})
|
||||||
|
|
||||||
|
const subscribeAllCallback = Effect.fn("Bus.subscribeAllCallback")(function* (callback: (event: any) => unknown) {
|
||||||
|
const s = yield* InstanceState.get(state)
|
||||||
|
return yield* on(s.wildcard, "*", callback)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Service.of({ publish, subscribe, subscribeAll, subscribeCallback, subscribeAllCallback })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const defaultLayer = layer
|
||||||
|
|
||||||
|
const { runPromise, runSync } = makeRuntime(Service, layer)
|
||||||
|
|
||||||
|
// runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe,
|
||||||
|
// Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw.
|
||||||
|
export async function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
|
||||||
|
return runPromise((svc) => svc.publish(def, properties))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribe<D extends BusEvent.Definition>(
|
||||||
|
def: D,
|
||||||
|
callback: (event: { type: D["type"]; properties: z.infer<D["properties"]> }) => unknown,
|
||||||
|
) {
|
||||||
|
return runSync((svc) => svc.subscribeCallback(def, callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribeAll(callback: (event: any) => unknown) {
|
||||||
|
return runSync((svc) => svc.subscribeAllCallback(callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
export * as Bus from "."
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util"
|
||||||
import { bootstrap } from "../bootstrap"
|
import { bootstrap } from "../bootstrap"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Agent } from "../../agent/agent"
|
|||||||
import { Provider } from "../../provider"
|
import { Provider } from "../../provider"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import { Filesystem } from "../../util/filesystem"
|
import { Filesystem } from "../../util"
|
||||||
import matter from "gray-matter"
|
import matter from "gray-matter"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { Argv } from "yargs"
|
import type { Argv } from "yargs"
|
||||||
import { spawn } from "child_process"
|
import { spawn } from "child_process"
|
||||||
import { Database } from "../../storage/db"
|
import { Database } from "../../storage"
|
||||||
import { drizzle } from "drizzle-orm/bun-sqlite"
|
import { drizzle } from "drizzle-orm/bun-sqlite"
|
||||||
import { Database as BunDatabase } from "bun:sqlite"
|
import { Database as BunDatabase } from "bun:sqlite"
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { JsonMigration } from "../../storage/json-migration"
|
import { JsonMigration } from "../../storage"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Provider } from "../../../provider"
|
|||||||
import { Session } from "../../../session"
|
import { Session } from "../../../session"
|
||||||
import type { MessageV2 } from "../../../session/message-v2"
|
import type { MessageV2 } from "../../../session/message-v2"
|
||||||
import { MessageID, PartID } from "../../../session/schema"
|
import { MessageID, PartID } from "../../../session/schema"
|
||||||
import { ToolRegistry } from "../../../tool/registry"
|
import { ToolRegistry } from "../../../tool"
|
||||||
import { Instance } from "../../../project/instance"
|
import { Instance } from "../../../project/instance"
|
||||||
import { Permission } from "../../../permission"
|
import { Permission } from "../../../permission"
|
||||||
import { iife } from "../../../util/iife"
|
import { iife } from "../../../util/iife"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AppRuntime } from "../../../effect/app-runtime"
|
|||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { bootstrap } from "../../bootstrap"
|
import { bootstrap } from "../../bootstrap"
|
||||||
import { cmd } from "../cmd"
|
import { cmd } from "../cmd"
|
||||||
import { Log } from "../../../util/log"
|
import { Log } from "../../../util"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
|
|
||||||
export const LSPCommand = cmd({
|
export const LSPCommand = cmd({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Project } from "../../../project/project"
|
import { Project } from "../../../project"
|
||||||
import { Log } from "../../../util/log"
|
import { Log } from "../../../util"
|
||||||
import { cmd } from "../cmd"
|
import { cmd } from "../cmd"
|
||||||
|
|
||||||
export const ScrapCommand = cmd({
|
export const ScrapCommand = cmd({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { exec } from "child_process"
|
import { exec } from "child_process"
|
||||||
import { Filesystem } from "../../util/filesystem"
|
import { Filesystem } from "../../util"
|
||||||
import * as prompts from "@clack/prompts"
|
import * as prompts from "@clack/prompts"
|
||||||
import { map, pipe, sortBy, values } from "remeda"
|
import { map, pipe, sortBy, values } from "remeda"
|
||||||
import { Octokit } from "@octokit/rest"
|
import { Octokit } from "@octokit/rest"
|
||||||
@@ -18,10 +18,10 @@ import type {
|
|||||||
} from "@octokit/webhooks-types"
|
} from "@octokit/webhooks-types"
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { ModelsDev } from "../../provider/models"
|
import { ModelsDev } from "../../provider"
|
||||||
import { Instance } from "@/project/instance"
|
import { Instance } from "@/project/instance"
|
||||||
import { bootstrap } from "../bootstrap"
|
import { bootstrap } from "../bootstrap"
|
||||||
import { SessionShare } from "@/share/session"
|
import { SessionShare } from "@/share"
|
||||||
import { Session } from "../../session"
|
import { Session } from "../../session"
|
||||||
import type { SessionID } from "../../session/schema"
|
import type { SessionID } from "../../session/schema"
|
||||||
import { MessageID, PartID } from "../../session/schema"
|
import { MessageID, PartID } from "../../session/schema"
|
||||||
@@ -32,7 +32,7 @@ import { SessionPrompt } from "@/session/prompt"
|
|||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
import { Git } from "@/git"
|
import { Git } from "@/git"
|
||||||
import { setTimeout as sleep } from "node:timers/promises"
|
import { setTimeout as sleep } from "node:timers/promises"
|
||||||
import { Process } from "@/util/process"
|
import { Process } from "@/util"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
|
|
||||||
type GitHubAuthor = {
|
type GitHubAuthor = {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { Session } from "../../session"
|
|||||||
import { MessageV2 } from "../../session/message-v2"
|
import { MessageV2 } from "../../session/message-v2"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { bootstrap } from "../bootstrap"
|
import { bootstrap } from "../bootstrap"
|
||||||
import { Database } from "../../storage/db"
|
import { Database } from "../../storage"
|
||||||
import { SessionTable, MessageTable, PartTable } from "../../session/session.sql"
|
import { SessionTable, MessageTable, PartTable } from "../../session/session.sql"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import { ShareNext } from "../../share/share-next"
|
import { ShareNext } from "../../share"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Filesystem } from "../../util/filesystem"
|
import { Filesystem } from "../../util"
|
||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
|
|
||||||
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */
|
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import { MCP } from "../../mcp"
|
|||||||
import { McpAuth } from "../../mcp/auth"
|
import { McpAuth } from "../../mcp/auth"
|
||||||
import { McpOAuthProvider } from "../../mcp/oauth-provider"
|
import { McpOAuthProvider } from "../../mcp/oauth-provider"
|
||||||
import { Config } from "../../config"
|
import { Config } from "../../config"
|
||||||
|
import { ConfigMCP } from "../../config/mcp"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import { Installation } from "../../installation"
|
import { Installation } from "../../installation"
|
||||||
|
import { InstallationVersion } from "../../installation/version"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Global } from "../../global"
|
import { Global } from "../../global"
|
||||||
import { modify, applyEdits } from "jsonc-parser"
|
import { modify, applyEdits } from "jsonc-parser"
|
||||||
import { Filesystem } from "../../util/filesystem"
|
import { Filesystem } from "../../util"
|
||||||
import { Bus } from "../../bus"
|
import { Bus } from "../../bus"
|
||||||
import { AppRuntime } from "../../effect/app-runtime"
|
import { AppRuntime } from "../../effect/app-runtime"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
@@ -42,7 +44,7 @@ function getAuthStatusText(status: MCP.AuthStatus): string {
|
|||||||
|
|
||||||
type McpEntry = NonNullable<Config.Info["mcp"]>[string]
|
type McpEntry = NonNullable<Config.Info["mcp"]>[string]
|
||||||
|
|
||||||
type McpConfigured = Config.Mcp
|
type McpConfigured = ConfigMCP.Info
|
||||||
function isMcpConfigured(config: McpEntry): config is McpConfigured {
|
function isMcpConfigured(config: McpEntry): config is McpConfigured {
|
||||||
return typeof config === "object" && config !== null && "type" in config
|
return typeof config === "object" && config !== null && "type" in config
|
||||||
}
|
}
|
||||||
@@ -425,7 +427,7 @@ async function resolveConfigPath(baseDir: string, global = false) {
|
|||||||
return candidates[0]
|
return candidates[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: string) {
|
async function addMcpToConfig(name: string, mcpConfig: ConfigMCP.Info, configPath: string) {
|
||||||
let text = "{}"
|
let text = "{}"
|
||||||
if (await Filesystem.exists(configPath)) {
|
if (await Filesystem.exists(configPath)) {
|
||||||
text = await Filesystem.readText(configPath)
|
text = await Filesystem.readText(configPath)
|
||||||
@@ -513,7 +515,7 @@ export const McpAddCommand = cmd({
|
|||||||
})
|
})
|
||||||
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
if (prompts.isCancel(command)) throw new UI.CancelledError()
|
||||||
|
|
||||||
const mcpConfig: Config.Mcp = {
|
const mcpConfig: ConfigMCP.Info = {
|
||||||
type: "local",
|
type: "local",
|
||||||
command: command.split(" "),
|
command: command.split(" "),
|
||||||
}
|
}
|
||||||
@@ -543,7 +545,7 @@ export const McpAddCommand = cmd({
|
|||||||
})
|
})
|
||||||
if (prompts.isCancel(useOAuth)) throw new UI.CancelledError()
|
if (prompts.isCancel(useOAuth)) throw new UI.CancelledError()
|
||||||
|
|
||||||
let mcpConfig: Config.Mcp
|
let mcpConfig: ConfigMCP.Info
|
||||||
|
|
||||||
if (useOAuth) {
|
if (useOAuth) {
|
||||||
const hasClientId = await prompts.confirm({
|
const hasClientId = await prompts.confirm({
|
||||||
@@ -697,7 +699,7 @@ export const McpDebugCommand = cmd({
|
|||||||
params: {
|
params: {
|
||||||
protocolVersion: "2024-11-05",
|
protocolVersion: "2024-11-05",
|
||||||
capabilities: {},
|
capabilities: {},
|
||||||
clientInfo: { name: "opencode-debug", version: Installation.VERSION },
|
clientInfo: { name: "opencode-debug", version: InstallationVersion },
|
||||||
},
|
},
|
||||||
id: 1,
|
id: 1,
|
||||||
}),
|
}),
|
||||||
@@ -746,7 +748,7 @@ export const McpDebugCommand = cmd({
|
|||||||
try {
|
try {
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
name: "opencode-debug",
|
name: "opencode-debug",
|
||||||
version: Installation.VERSION,
|
version: InstallationVersion,
|
||||||
})
|
})
|
||||||
await client.connect(transport)
|
await client.connect(transport)
|
||||||
prompts.log.success("Connection successful (already authenticated)")
|
prompts.log.success("Connection successful (already authenticated)")
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Argv } from "yargs"
|
|||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import { Provider } from "../../provider"
|
import { Provider } from "../../provider"
|
||||||
import { ProviderID } from "../../provider/schema"
|
import { ProviderID } from "../../provider/schema"
|
||||||
import { ModelsDev } from "../../provider/models"
|
import { ModelsDev } from "../../provider"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { intro, log, outro, spinner } from "@clack/prompts"
|
import { intro, log, outro, spinner } from "@clack/prompts"
|
||||||
import type { Argv } from "yargs"
|
import type { Argv } from "yargs"
|
||||||
|
|
||||||
import { ConfigPaths } from "../../config/paths"
|
import { ConfigPaths } from "../../config"
|
||||||
import { Global } from "../../global"
|
import { Global } from "../../global"
|
||||||
import { installPlugin, patchPluginConfig, readPluginManifest } from "../../plugin/install"
|
import { installPlugin, patchPluginConfig, readPluginManifest } from "../../plugin/install"
|
||||||
import { resolvePluginTarget } from "../../plugin/shared"
|
import { resolvePluginTarget } from "../../plugin/shared"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
import { Filesystem } from "../../util/filesystem"
|
import { Filesystem } from "../../util"
|
||||||
import { Process } from "../../util/process"
|
import { Process } from "../../util"
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { cmd } from "./cmd"
|
|||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
import { Git } from "@/git"
|
import { Git } from "@/git"
|
||||||
import { Instance } from "@/project/instance"
|
import { Instance } from "@/project/instance"
|
||||||
import { Process } from "@/util/process"
|
import { Process } from "@/util"
|
||||||
|
|
||||||
export const PrCommand = cmd({
|
export const PrCommand = cmd({
|
||||||
command: "pr <number>",
|
command: "pr <number>",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AppRuntime } from "../../effect/app-runtime"
|
|||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import * as prompts from "@clack/prompts"
|
import * as prompts from "@clack/prompts"
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { ModelsDev } from "../../provider/models"
|
import { ModelsDev } from "../../provider"
|
||||||
import { map, pipe, sortBy, values } from "remeda"
|
import { map, pipe, sortBy, values } from "remeda"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
@@ -12,7 +12,7 @@ import { Global } from "../../global"
|
|||||||
import { Plugin } from "../../plugin"
|
import { Plugin } from "../../plugin"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import type { Hooks } from "@opencode-ai/plugin"
|
import type { Hooks } from "@opencode-ai/plugin"
|
||||||
import { Process } from "../../util/process"
|
import { Process } from "../../util"
|
||||||
import { text } from "node:stream/consumers"
|
import { text } from "node:stream/consumers"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
|
|
||||||
@@ -297,7 +297,9 @@ export const ProvidersLoginCommand = cmd({
|
|||||||
prompts.intro("Add credential")
|
prompts.intro("Add credential")
|
||||||
if (args.url) {
|
if (args.url) {
|
||||||
const url = args.url.replace(/\/+$/, "")
|
const url = args.url.replace(/\/+$/, "")
|
||||||
const wellknown = await fetch(`${url}/.well-known/opencode`).then((x) => x.json() as any)
|
const wellknown = (await fetch(`${url}/.well-known/opencode`).then((x) => x.json())) as {
|
||||||
|
auth: { command: string[]; env: string }
|
||||||
|
}
|
||||||
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
|
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
|
||||||
const proc = Process.spawn(wellknown.auth.command, {
|
const proc = Process.spawn(wellknown.auth.command, {
|
||||||
stdout: "pipe",
|
stdout: "pipe",
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import { cmd } from "./cmd"
|
|||||||
import { Flag } from "../../flag/flag"
|
import { Flag } from "../../flag/flag"
|
||||||
import { bootstrap } from "../bootstrap"
|
import { bootstrap } from "../bootstrap"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Filesystem } from "../../util/filesystem"
|
import { Filesystem } from "../../util"
|
||||||
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
|
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
|
||||||
import { Server } from "../../server/server"
|
import { Server } from "../../server/server"
|
||||||
import { Provider } from "../../provider"
|
import { Provider } from "../../provider"
|
||||||
import { Agent } from "../../agent/agent"
|
import { Agent } from "../../agent/agent"
|
||||||
import { Permission } from "../../permission"
|
import { Permission } from "../../permission"
|
||||||
import { Tool } from "../../tool/tool"
|
import { Tool } from "../../tool"
|
||||||
import { GlobTool } from "../../tool/glob"
|
import { GlobTool } from "../../tool/glob"
|
||||||
import { GrepTool } from "../../tool/grep"
|
import { GrepTool } from "../../tool/grep"
|
||||||
import { ReadTool } from "../../tool/read"
|
import { ReadTool } from "../../tool/read"
|
||||||
@@ -25,7 +25,7 @@ import { TaskTool } from "../../tool/task"
|
|||||||
import { SkillTool } from "../../tool/skill"
|
import { SkillTool } from "../../tool/skill"
|
||||||
import { BashTool } from "../../tool/bash"
|
import { BashTool } from "../../tool/bash"
|
||||||
import { TodoWriteTool } from "../../tool/todo"
|
import { TodoWriteTool } from "../../tool/todo"
|
||||||
import { Locale } from "../../util/locale"
|
import { Locale } from "../../util"
|
||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
|
|
||||||
type ToolProps<T> = {
|
type ToolProps<T> = {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { Session } from "../../session"
|
|||||||
import { SessionID } from "../../session/schema"
|
import { SessionID } from "../../session/schema"
|
||||||
import { bootstrap } from "../bootstrap"
|
import { bootstrap } from "../bootstrap"
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { Locale } from "../../util/locale"
|
import { Locale } from "../../util"
|
||||||
import { Flag } from "../../flag/flag"
|
import { Flag } from "../../flag/flag"
|
||||||
import { Filesystem } from "../../util/filesystem"
|
import { Filesystem } from "../../util"
|
||||||
import { Process } from "../../util/process"
|
import { Process } from "../../util"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { which } from "../../util/which"
|
import { which } from "../../util/which"
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import type { Argv } from "yargs"
|
|||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { Session } from "../../session"
|
import { Session } from "../../session"
|
||||||
import { bootstrap } from "../bootstrap"
|
import { bootstrap } from "../bootstrap"
|
||||||
import { Database } from "../../storage/db"
|
import { Database } from "../../storage"
|
||||||
import { SessionTable } from "../../session/session.sql"
|
import { SessionTable } from "../../session/session.sql"
|
||||||
import { Project } from "../../project/project"
|
import { Project } from "../../project"
|
||||||
import { Instance } from "../../project/instance"
|
import { Instance } from "../../project/instance"
|
||||||
import { AppRuntime } from "@/effect/app-runtime"
|
import { AppRuntime } from "@/effect/app-runtime"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||||
import { Clipboard } from "@tui/util/clipboard"
|
import * as Clipboard from "@tui/util/clipboard"
|
||||||
import { Selection } from "@tui/util/selection"
|
import * as Selection from "@tui/util/selection"
|
||||||
import { Terminal } from "@tui/util/terminal"
|
import * as Terminal from "@tui/util/terminal"
|
||||||
import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core"
|
import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core"
|
||||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||||
import {
|
import {
|
||||||
@@ -57,7 +57,7 @@ import { ArgsProvider, useArgs, type Args } from "./context/args"
|
|||||||
import open from "open"
|
import open from "open"
|
||||||
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
||||||
import { TuiConfigProvider, useTuiConfig } from "./context/tui-config"
|
import { TuiConfigProvider, useTuiConfig } from "./context/tui-config"
|
||||||
import { TuiConfig } from "@/config/tui"
|
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||||
import { createTuiApi, TuiPluginRuntime, type RouteMap } from "./plugin"
|
import { createTuiApi, TuiPluginRuntime, type RouteMap } from "./plugin"
|
||||||
import { FormatError, FormatUnknownError } from "@/cli/error"
|
import { FormatError, FormatUnknownError } from "@/cli/error"
|
||||||
|
|
||||||
@@ -235,7 +235,10 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
|||||||
renderer,
|
renderer,
|
||||||
})
|
})
|
||||||
const [ready, setReady] = createSignal(false)
|
const [ready, setReady] = createSignal(false)
|
||||||
TuiPluginRuntime.init(api)
|
TuiPluginRuntime.init({
|
||||||
|
api,
|
||||||
|
config: tuiConfig,
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Failed to load TUI plugins", error)
|
console.error("Failed to load TUI plugins", error)
|
||||||
})
|
})
|
||||||
@@ -350,7 +353,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
|||||||
if (match) {
|
if (match) {
|
||||||
continued = true
|
continued = true
|
||||||
if (args.fork) {
|
if (args.fork) {
|
||||||
sdk.client.session.fork({ sessionID: match }).then((result) => {
|
void sdk.client.session.fork({ sessionID: match }).then((result) => {
|
||||||
if (result.data?.id) {
|
if (result.data?.id) {
|
||||||
route.navigate({ type: "session", sessionID: result.data.id })
|
route.navigate({ type: "session", sessionID: result.data.id })
|
||||||
} else {
|
} else {
|
||||||
@@ -370,7 +373,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (forked || sync.status !== "complete" || !args.sessionID || !args.fork) return
|
if (forked || sync.status !== "complete" || !args.sessionID || !args.fork) return
|
||||||
forked = true
|
forked = true
|
||||||
sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => {
|
void sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => {
|
||||||
if (result.data?.id) {
|
if (result.data?.id) {
|
||||||
route.navigate({ type: "session", sessionID: result.data.id })
|
route.navigate({ type: "session", sessionID: result.data.id })
|
||||||
} else {
|
} else {
|
||||||
@@ -818,7 +821,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
|||||||
`Successfully updated to OpenCode v${result.data.version}. Please restart the application.`,
|
`Successfully updated to OpenCode v${result.data.version}. Please restart the application.`,
|
||||||
)
|
)
|
||||||
|
|
||||||
exit()
|
void exit()
|
||||||
})
|
})
|
||||||
|
|
||||||
const plugin = createMemo(() => {
|
const plugin = createMemo(() => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user