mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-25 20:55:42 -04:00
Compare commits
11 Commits
production
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 7534d23551 | |||
| 0a6637e17a | |||
| 2b2b69d668 | |||
| 9e8b2171a5 | |||
| 5e2a6257b2 | |||
| 065dc274ec | |||
| a85d8d23aa | |||
| 2b2aacc939 | |||
| 7840562d1b | |||
| 53669cab2b | |||
| 4b19ea2a71 |
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@dnd-kit/helpers": "0.5.0",
|
||||
"@dnd-kit/solid": "0.5.0",
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13.tgz",
|
||||
"@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13-v2.tgz",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -96,7 +96,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"bin": {
|
||||
"lildax": "./bin/lildax.cjs",
|
||||
},
|
||||
@@ -144,7 +144,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -158,7 +158,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -194,7 +194,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -221,7 +221,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -243,7 +243,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -267,7 +267,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode-ai/console-core": "workspace:*",
|
||||
@@ -287,7 +287,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -381,7 +381,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"effect": "catalog:",
|
||||
@@ -435,7 +435,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -449,7 +449,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -461,7 +461,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -493,7 +493,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -509,7 +509,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||
@@ -540,7 +540,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
@@ -559,7 +559,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -690,7 +690,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -766,7 +766,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -781,7 +781,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
@@ -796,10 +796,10 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13.tgz",
|
||||
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -841,7 +841,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -854,7 +854,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -888,7 +888,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -907,7 +907,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -949,7 +949,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
@@ -976,7 +976,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -1027,7 +1027,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -6030,7 +6030,7 @@
|
||||
|
||||
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
||||
|
||||
"@opencode-ai/app/@opencode-ai/client": ["@opencode-ai/client@vendor/opencode-ai-client-1.17.13.tgz", {}, "sha512-1cYJikTCrXNhnS2qQ3P3rtdbGqhvJKksswG0amJNqgaeUfz3xDlKEDx+YoIosT7Cqk/AtO18jtip0lYL3TVdHQ=="],
|
||||
"@opencode-ai/app/@opencode-ai/client": ["@opencode-ai/client@vendor/opencode-ai-client-1.17.13-v2.tgz", {}, "sha512-332kgNifvpQOF9e3UA+pIa5xPrMhLaQkUiNiO+meS0Ba9HjSE6hfsWnEojMkD0DPSLqPP6rCF1dDoF7U0Y0OCQ=="],
|
||||
|
||||
"@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
|
||||
|
||||
@@ -6050,7 +6050,7 @@
|
||||
|
||||
"@opencode-ai/script/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"@opencode-ai/session-ui/@opencode-ai/client": ["@opencode-ai/client@../app/vendor/opencode-ai-client-1.17.13.tgz", {}, "sha512-1cYJikTCrXNhnS2qQ3P3rtdbGqhvJKksswG0amJNqgaeUfz3xDlKEDx+YoIosT7Cqk/AtO18jtip0lYL3TVdHQ=="],
|
||||
"@opencode-ai/session-ui/@opencode-ai/client": ["@opencode-ai/client@../app/vendor/opencode-ai-client-1.17.13-v2.tgz", {}, "sha512-332kgNifvpQOF9e3UA+pIa5xPrMhLaQkUiNiO+meS0Ba9HjSE6hfsWnEojMkD0DPSLqPP6rCF1dDoF7U0Y0OCQ=="],
|
||||
|
||||
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
||||
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-0kcwV34P2C3yKg2eG9W2nW+OedrSBb+1TdpuUeYtauY=",
|
||||
"aarch64-linux": "sha256-yHVygApQchAB34wrtFR4GU0CkmZOlLsl3wsp15u0xzs=",
|
||||
"aarch64-darwin": "sha256-DyalcwyK2Wn5R6249keFcNVECbgtjYNjscOFqTi88FI=",
|
||||
"x86_64-darwin": "sha256-BkGw0GWN9W9q+/g4FYR0MqxUuFP80BPoERO+ypz/arQ="
|
||||
"x86_64-linux": "sha256-2suqkFAUeT2YgppKqP14s0C+GE3TR3uNKOOD8vWEiAg=",
|
||||
"aarch64-linux": "sha256-cJ/XYTodYmgs9ZFQhHQbfZPqxjIMmEvz062OsX6/6p4=",
|
||||
"aarch64-darwin": "sha256-tKyqHeI6r1aHEk4vBS39+eA6wh9KcIwsa2ctcktKkEc=",
|
||||
"x86_64-darwin": "sha256-1u7+dNjRcqKSvUTCEarVJ9fMrRUyg9y+4seZAm6Fmus="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ async function switchSession(page: Page, title: string) {
|
||||
|
||||
async function setup(page: Page) {
|
||||
await mockOpenCodeServer(page, {
|
||||
protocol: "v2",
|
||||
protocol: "v1",
|
||||
directory,
|
||||
project: {
|
||||
id: projectID,
|
||||
@@ -93,20 +93,18 @@ async function setup(page: Page) {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ location: { directory }, data: { branch: "feature", defaultBranch: "dev" } }),
|
||||
body: JSON.stringify({ branch: "feature", default_branch: "dev" }),
|
||||
}),
|
||||
)
|
||||
await page.route("**/vcs/diff**", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
location: { directory },
|
||||
data:
|
||||
new URL(route.request().url()).searchParams.get("mode") === "branch"
|
||||
? [diff("src/alpha.ts"), diff("src/beta.ts")]
|
||||
: [diff("src/alpha.ts"), diff("src/gamma.ts")],
|
||||
}),
|
||||
body: JSON.stringify(
|
||||
new URL(route.request().url()).searchParams.get("mode") === "branch"
|
||||
? [diff("src/alpha.ts"), diff("src/beta.ts")]
|
||||
: [diff("src/alpha.ts"), diff("src/gamma.ts")],
|
||||
),
|
||||
}),
|
||||
)
|
||||
await page.addInitScript(
|
||||
|
||||
@@ -25,7 +25,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
let detailFailures = 1
|
||||
await page.setViewportSize({ width: 1400, height: 900 })
|
||||
await mockOpenCodeServer(page, {
|
||||
protocol: "v2",
|
||||
protocol: "v1",
|
||||
directory,
|
||||
project: {
|
||||
id: projectID,
|
||||
@@ -67,33 +67,31 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
location: { directory },
|
||||
data: { branch: "review-pane-performance", defaultBranch: "dev" },
|
||||
branch: "review-pane-performance",
|
||||
default_branch: "dev",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await page.route("**/api/vcs/diff**", (route) => {
|
||||
await page.route("**/vcs/diff**", (route) => {
|
||||
const url = new URL(route.request().url())
|
||||
const scope = url.searchParams.get("location[directory]")?.replaceAll("\\", "/")
|
||||
const scope = url.searchParams.get("directory")?.replaceAll("\\", "/")
|
||||
const detail = scope?.endsWith("/src/branch/d00027")
|
||||
if (detail && detailFailures-- > 0) return route.fulfill({ status: 500, body: "retry detail" })
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
location: { directory: scope ?? directory, project: { id: projectID, directory } },
|
||||
data:
|
||||
url.searchParams.get("mode") === "branch"
|
||||
? detail
|
||||
? branchDiffs
|
||||
.filter((diff) => diff.file.startsWith("src/branch/d00027/"))
|
||||
.map((diff) => fileDiff(diff.file, diff.additions, true, detailVersion))
|
||||
: branchDiffs
|
||||
: Array.from({ length: 7 }, (_, index) => fileDiff(`src/git-${index}.ts`, 1)),
|
||||
}),
|
||||
body: JSON.stringify(
|
||||
url.searchParams.get("mode") === "branch"
|
||||
? detail
|
||||
? branchDiffs
|
||||
.filter((diff) => diff.file.startsWith("src/branch/d00027/"))
|
||||
.map((diff) => fileDiff(diff.file, diff.additions, true, detailVersion))
|
||||
: branchDiffs
|
||||
: Array.from({ length: 7 }, (_, index) => fileDiff(`src/git-${index}.ts`, 1)),
|
||||
),
|
||||
})
|
||||
})
|
||||
await page.route("**/api/pty*", (route) =>
|
||||
await page.route("**/pty*", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
@@ -111,7 +109,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await page.route("**/api/pty/pty_review_terminal*", (route) =>
|
||||
await page.route("**/pty/pty_review_terminal*", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
@@ -129,7 +127,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await page.route("**/api/pty/pty_review_terminal/connect-token*", (route) =>
|
||||
await page.route("**/pty/pty_review_terminal/connect-token*", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
@@ -139,7 +137,7 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
}),
|
||||
}),
|
||||
)
|
||||
await page.routeWebSocket("**/api/pty/pty_review_terminal/connect", () => undefined)
|
||||
await page.routeWebSocket("**/pty/pty_review_terminal/connect", () => undefined)
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
||||
localStorage.setItem(
|
||||
@@ -177,8 +175,8 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
const lazyDiff = page.waitForRequest((request) => {
|
||||
const url = new URL(request.url())
|
||||
return (
|
||||
url.pathname === "/api/vcs/diff" &&
|
||||
url.searchParams.get("location[directory]")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true
|
||||
url.pathname === "/vcs/diff" &&
|
||||
url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true
|
||||
)
|
||||
})
|
||||
await lastFile.click()
|
||||
@@ -192,8 +190,8 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
||||
const refreshedDiff = page.waitForRequest((request) => {
|
||||
const url = new URL(request.url())
|
||||
return (
|
||||
url.pathname === "/api/vcs/diff" &&
|
||||
url.searchParams.get("location[directory]")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true
|
||||
url.pathname === "/vcs/diff" &&
|
||||
url.searchParams.get("directory")?.replaceAll("\\", "/").endsWith("/src/branch/d00027") === true
|
||||
)
|
||||
})
|
||||
sessionStatus[sessionID] = { type: "idle" }
|
||||
|
||||
@@ -32,7 +32,7 @@ test("keeps the terminal session alive when switching session tabs in a workspac
|
||||
const connection = new URL(connections[0]!)
|
||||
expect(connection.pathname).toBe(`/api/pty/${ptyID}/connect`)
|
||||
expect(connection.searchParams.get("location[directory]")).toBe(directory)
|
||||
expect(connection.searchParams.get("ticket")).toBe("e2e-ticket")
|
||||
expect(connection.searchParams.get("ticket")).toBeNull()
|
||||
await writeProbe(page)
|
||||
|
||||
await switchTab(page, titleB)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -53,7 +53,7 @@
|
||||
"@dnd-kit/helpers": "0.5.0",
|
||||
"@dnd-kit/solid": "0.5.0",
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13.tgz",
|
||||
"@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13-v2.tgz",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
|
||||
@@ -159,7 +159,7 @@ function ProviderPicker(props: {
|
||||
const settings = useSettings()
|
||||
if (settings.general.newLayoutDesigns())
|
||||
return <ProviderPickerV2 directory={props.directory} onSelect={props.onSelect} onPrepare={props.onPrepare} />
|
||||
const providers = useProviders(props.directory)
|
||||
const providers = useProviders(() => props.directory?.())
|
||||
const language = useLanguage()
|
||||
const popularGroup = () => language.t("dialog.provider.group.popular")
|
||||
const otherGroup = () => language.t("dialog.provider.group.other")
|
||||
@@ -231,7 +231,7 @@ function ProviderPickerV2(props: {
|
||||
onSelect: (provider: string) => void
|
||||
onPrepare?: () => void
|
||||
}) {
|
||||
const providers = useProviders(props.directory)
|
||||
const providers = useProviders(() => props.directory?.())
|
||||
const language = useLanguage()
|
||||
const [store, setStore] = createStore({
|
||||
filter: "",
|
||||
@@ -391,7 +391,7 @@ function ProviderConnection(props: {
|
||||
const language = useLanguage()
|
||||
const settings = useSettings()
|
||||
const newLayout = settings.general.newLayoutDesigns
|
||||
const providers = useProviders(props.directory)
|
||||
const providers = useProviders(() => props.directory?.())
|
||||
const directory = () => props.directory?.() ?? decode64(params.dir)
|
||||
const location = () => {
|
||||
const value = directory()
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createEffect, createMemo, createResource, createSignal, For, onCleanup,
|
||||
import { useGlobal } from "@/context/global"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { ServerConnection } from "@/context/server"
|
||||
import type { Path } from "@opencode-ai/sdk/v2/client"
|
||||
import {
|
||||
absoluteTreePath,
|
||||
activeTreeNavigation,
|
||||
@@ -68,7 +69,13 @@ export function DialogSelectDirectoryV2(props: DialogSelectDirectoryV2Props) {
|
||||
const missingBase = createMemo(() => !(sync.data.path.home || sync.data.path.directory))
|
||||
const [fallbackPath] = createResource(
|
||||
() => (missingBase() ? true : undefined),
|
||||
() => sdk.api.path.get().catch(() => undefined),
|
||||
async (): Promise<Path | undefined> => {
|
||||
if ((await sdk.protocol) !== "v1") return
|
||||
return sdk.client.path
|
||||
.get()
|
||||
.then((result) => result.data)
|
||||
.catch(() => undefined)
|
||||
},
|
||||
{ initialValue: undefined },
|
||||
)
|
||||
const home = createMemo(() => sync.data.path.home || fallbackPath()?.home || "")
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useLanguage } from "@/context/language"
|
||||
import { ServerConnection } from "@/context/server"
|
||||
import { useGlobal } from "@/context/global"
|
||||
import { cleanPickerInput, createDirectorySearch, displayPickerPath } from "./directory-picker-domain"
|
||||
import type { Path } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
interface DialogSelectDirectoryProps {
|
||||
title?: string
|
||||
@@ -59,8 +60,12 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
||||
const missingBase = createMemo(() => !(sync.data.path.home || sync.data.path.directory))
|
||||
const [fallbackPath] = createResource(
|
||||
() => (missingBase() ? true : undefined),
|
||||
async () => {
|
||||
return sdk.api.path.get().catch(() => undefined)
|
||||
async (): Promise<Path | undefined> => {
|
||||
if ((await sdk.protocol) !== "v1") return
|
||||
return sdk.client.path
|
||||
.get()
|
||||
.then((result) => result.data)
|
||||
.catch(() => undefined)
|
||||
},
|
||||
{ initialValue: undefined },
|
||||
)
|
||||
|
||||
@@ -71,12 +71,23 @@ export function createEditProjectModel(props: { project: LocalProject; server: S
|
||||
const start = store.startup.trim()
|
||||
|
||||
if (props.project.id && props.project.id !== "global") {
|
||||
const project = await serverCtx().sdk.api.project.update({
|
||||
projectID: props.project.id,
|
||||
name,
|
||||
icon: { color: store.color || "", override: store.iconOverride || "" },
|
||||
commands: { start },
|
||||
})
|
||||
if ((await serverCtx().sdk.protocol) !== "v1") return
|
||||
const project = await serverCtx()
|
||||
.sdk.client.project.update({
|
||||
projectID: props.project.id,
|
||||
directory: props.project.worktree,
|
||||
name,
|
||||
icon: { color: store.color || "", override: store.iconOverride || "" },
|
||||
commands: { start },
|
||||
})
|
||||
.then((result) => result.data)
|
||||
if (!project) return
|
||||
// const project = await serverCtx().sdk.api.project.update({
|
||||
// projectID: props.project.id,
|
||||
// name,
|
||||
// icon: { color: store.color || "", override: store.iconOverride || "" },
|
||||
// commands: { start },
|
||||
// })
|
||||
serverCtx().sync.set("project", (items) =>
|
||||
items.map((item) => (item.id === project.id ? normalizeProjectInfo(project) : item)),
|
||||
)
|
||||
|
||||
@@ -709,7 +709,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
title: cmd.name,
|
||||
description: cmd.description,
|
||||
type: "custom" as const,
|
||||
source: cmd.source,
|
||||
// source: cmd.source,
|
||||
}))
|
||||
|
||||
return [...custom, ...builtin]
|
||||
|
||||
@@ -132,7 +132,8 @@ export const SettingsGeneral: Component = () => {
|
||||
if ((await sdk.protocol) === "v1") {
|
||||
return (await sdk.client.pty.shells()).data ?? []
|
||||
}
|
||||
return (await sdk.api.pty.shells()).data
|
||||
// return (await sdk.api.pty.shells()).data
|
||||
return [] as ShellOption[]
|
||||
},
|
||||
{ initialValue: [] as ShellOption[] },
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) =>
|
||||
const serverSDK = useServerSDK()
|
||||
const protocol = useServerProtocol()
|
||||
const serverSync = useServerSync()
|
||||
const providers = useProviders()
|
||||
const providers = useProviders(() => undefined)
|
||||
const providerConnect = useProviderConnectController({ onBack: props.onBack })
|
||||
|
||||
const connect = (provider?: string) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, createSignal, startTransition } from "solid-js"
|
||||
import { Component, createMemo, createSignal, startTransition } from "solid-js"
|
||||
import { Dialog } from "@opencode-ai/ui/v2/dialog-v2"
|
||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
@@ -11,6 +11,9 @@ import { SettingsModelsV2 } from "./models"
|
||||
import "./settings-v2.css"
|
||||
import { SettingsServersV2 } from "./servers"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useTabs } from "@/context/tabs"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
|
||||
export const DialogSettings: Component<{
|
||||
sessionID?: string
|
||||
@@ -19,7 +22,20 @@ export const DialogSettings: Component<{
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
const dialog = useDialog()
|
||||
const layout = useLayout()
|
||||
const tabs = useTabs()
|
||||
const serverSync = useServerSync()
|
||||
const [tab, setTab] = createSignal(props.defaultValue ?? "general")
|
||||
const directory = createMemo(() => {
|
||||
const route = layout.route()
|
||||
if (route.type === "dir-new-sesssion") return route.dir
|
||||
if (route.type === "draft") {
|
||||
const draft = tabs.store.find((item) => item.type === "draft" && item.draftID === route.draftID)
|
||||
return draft?.type === "draft" ? draft.directory : undefined
|
||||
}
|
||||
if (route.type === "session") return serverSync().session.get(route.sessionId)?.directory
|
||||
return undefined
|
||||
})
|
||||
|
||||
const showProviders = () => {
|
||||
void dialog.show(() => <DialogSettings sessionID={props.sessionID} defaultValue="providers" />)
|
||||
@@ -87,7 +103,7 @@ export const DialogSettings: Component<{
|
||||
<SettingsServersV2 />
|
||||
</TabsV2.Content>
|
||||
<TabsV2.Content value="providers" class="settings-v2-panel">
|
||||
<SettingsProvidersV2 onBack={showProviders} />
|
||||
<SettingsProvidersV2 directory={directory} onBack={showProviders} />
|
||||
</TabsV2.Content>
|
||||
<TabsV2.Content value="models" class="settings-v2-panel">
|
||||
<SettingsModelsV2 />
|
||||
|
||||
@@ -127,7 +127,8 @@ export const SettingsGeneralV2: Component<{
|
||||
if ((await sdk.protocol) === "v1") {
|
||||
return (await sdk.client.pty.shells()).data ?? []
|
||||
}
|
||||
return (await sdk.api.pty.shells()).data
|
||||
// return (await sdk.api.pty.shells()).data
|
||||
return [] as ShellOption[]
|
||||
},
|
||||
{ initialValue: [] as ShellOption[] },
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
||||
import { createMemo, type Component, For, Show } from "solid-js"
|
||||
import { createMemo, type Accessor, type Component, For, Show } from "solid-js"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useServerProtocol, useServerSDK } from "@/context/server-sdk"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
@@ -29,18 +29,21 @@ const PROVIDER_NOTES = [
|
||||
|
||||
const PROVIDER_ICON_SIZE = 16
|
||||
|
||||
export const SettingsProvidersV2: Component<{ onBack?: () => void }> = (props) => {
|
||||
export const SettingsProvidersV2: Component<{
|
||||
directory: Accessor<string | undefined>
|
||||
onBack?: () => void
|
||||
}> = (props) => {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
const serverSdk = useServerSDK()
|
||||
const protocol = useServerProtocol()
|
||||
const serverSync = useServerSync()
|
||||
const providers = useProviders()
|
||||
const providers = useProviders(props.directory)
|
||||
const providerConnect = useProviderConnectController({ onBack: props.onBack })
|
||||
|
||||
const connect = (provider?: string) => {
|
||||
providerConnect.select(provider)
|
||||
void dialog.show(() => <DialogConnectProvider controller={providerConnect} />)
|
||||
void dialog.show(() => <DialogConnectProvider directory={props.directory} controller={providerConnect} />)
|
||||
}
|
||||
|
||||
const connected = createMemo(() => {
|
||||
|
||||
@@ -573,19 +573,13 @@ export const Terminal = (props: TerminalProps) => {
|
||||
throw new Error("PTY connect ticket rejected by origin or CSRF checks. Check the server CORS config.")
|
||||
throw new Error(`PTY connect ticket failed with ${result.response.status}`)
|
||||
}
|
||||
return sdk()
|
||||
.api.pty.connectToken({
|
||||
ptyID: id,
|
||||
location: { directory },
|
||||
"x-opencode-ticket": "1",
|
||||
})
|
||||
.then((result) => result.data.ticket)
|
||||
.catch((err: unknown) => {
|
||||
if (err && typeof err === "object" && "_tag" in err && err._tag === "ForbiddenError") {
|
||||
throw new Error("PTY connect ticket rejected by origin or CSRF checks. Check the server CORS config.")
|
||||
}
|
||||
throw err
|
||||
})
|
||||
// return sdk()
|
||||
// .api.pty.connectToken({
|
||||
// ptyID: id,
|
||||
// location: { directory },
|
||||
// "x-opencode-ticket": "1",
|
||||
// })
|
||||
// .then((result) => result.data.ticket)
|
||||
}
|
||||
|
||||
const retry = (err: unknown) => {
|
||||
@@ -616,7 +610,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
return undefined
|
||||
})
|
||||
const protocol = await sdk().protocol
|
||||
if (protocol === "v2" && !ticket) return
|
||||
// if (protocol === "v2" && !ticket) return
|
||||
if (once.value) return
|
||||
if (disposed) return
|
||||
|
||||
|
||||
@@ -134,7 +134,8 @@ export const createDirSyncContext = (
|
||||
},
|
||||
more: createMemo(() => current()[0].session.length >= current()[0].limit),
|
||||
archive: async (sessionID: string) => {
|
||||
await serverSDK.api.session.archive({ sessionID, directory })
|
||||
if ((await serverSDK.protocol) !== "v1") return
|
||||
await serverSDK.client.session.update({ sessionID, directory, time: { archived: Date.now() } })
|
||||
current()[1](
|
||||
"session",
|
||||
produce((draft) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { QueryClient } from "@tanstack/solid-query"
|
||||
import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client"
|
||||
import type { AgentApi, CatalogApi, CommandApi, ProjectApi, ReferenceApi } from "@opencode-ai/client/promise"
|
||||
import type { AgentApi, CatalogApi, CommandApi, ReferenceApi } from "@opencode-ai/client/promise"
|
||||
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
import {
|
||||
bootstrapDirectory,
|
||||
@@ -17,6 +17,8 @@ import type { State, VcsCache } from "./types"
|
||||
import { ServerScope } from "@/utils/server-scope"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type ProjectApi = ServerApi["project"]
|
||||
|
||||
const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse
|
||||
const api = {
|
||||
agent: { list: async () => ({ location: {}, data: [] }) },
|
||||
@@ -73,14 +75,14 @@ function directoryState() {
|
||||
}
|
||||
|
||||
describe("bootstrapDirectory", () => {
|
||||
test("marks a loading directory partial during bootstrap and complete after success", async () => {
|
||||
test("uses legacy MCP endpoints while refreshing a v1 directory", async () => {
|
||||
const mcpReads: string[] = []
|
||||
const [store, setStore] = directoryState()
|
||||
|
||||
await bootstrapDirectory({
|
||||
directory: "/project",
|
||||
scope: ServerScope.local,
|
||||
mcp: false,
|
||||
mcp: true,
|
||||
global: {
|
||||
config: {} satisfies Config,
|
||||
path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" },
|
||||
@@ -90,6 +92,7 @@ describe("bootstrapDirectory", () => {
|
||||
sdk: {
|
||||
app: { agents: async () => ({ data: [{ name: "build", mode: "primary" }] }) },
|
||||
config: { get: async () => ({ data: {} }) },
|
||||
session: { status: async () => ({ data: {} }) },
|
||||
vcs: { get: async () => ({ data: undefined }) },
|
||||
command: {
|
||||
list: async () => {
|
||||
@@ -106,6 +109,14 @@ describe("bootstrapDirectory", () => {
|
||||
return { data: {} }
|
||||
},
|
||||
},
|
||||
experimental: {
|
||||
resource: {
|
||||
list: async () => {
|
||||
mcpReads.push("resource")
|
||||
return { data: {} }
|
||||
},
|
||||
},
|
||||
},
|
||||
provider: { list: async () => ({ data: { all: [], connected: [], default: {} } }) },
|
||||
} as unknown as OpencodeClient,
|
||||
api,
|
||||
@@ -115,6 +126,7 @@ describe("bootstrapDirectory", () => {
|
||||
loadSessions() {},
|
||||
translate: (key) => key,
|
||||
queryClient: new QueryClient(),
|
||||
protocol: Promise.resolve("v1"),
|
||||
})
|
||||
|
||||
expect(store.status).toBe("partial")
|
||||
@@ -122,7 +134,7 @@ describe("bootstrapDirectory", () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 80))
|
||||
|
||||
expect(store.status).toBe("complete")
|
||||
expect(mcpReads).toEqual([])
|
||||
expect(mcpReads.sort()).toEqual(["command", "resource", "status"])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -190,7 +202,7 @@ describe("query keys", () => {
|
||||
calls.push(input)
|
||||
return {
|
||||
location: {},
|
||||
data: [{ name: "review", template: "Review files", source: "command" as const }],
|
||||
data: [{ name: "review", template: "Review files" /* source: "command" as const */ }],
|
||||
}
|
||||
},
|
||||
} as unknown as CommandApi
|
||||
@@ -198,7 +210,7 @@ describe("query keys", () => {
|
||||
const result = await loadCommands("/repo", api)
|
||||
|
||||
expect(calls).toEqual([{ location: { directory: "/repo" } }])
|
||||
expect(result).toEqual([{ name: "review", template: "Review files", source: "command" }])
|
||||
expect(result).toEqual([{ name: "review", template: "Review files" /* source: "command" */ }])
|
||||
})
|
||||
|
||||
test("loads projects from the current endpoint", async () => {
|
||||
|
||||
@@ -16,18 +16,12 @@ import type {
|
||||
CommandInfo,
|
||||
CommandListInput,
|
||||
CommandListOutput,
|
||||
McpApi,
|
||||
PathGetInput,
|
||||
PathGetOutput,
|
||||
PermissionApi,
|
||||
ProjectCurrentInput,
|
||||
ProjectCurrentOutput,
|
||||
ProjectListOutput,
|
||||
QuestionApi,
|
||||
ReferenceListInput,
|
||||
ReferenceListOutput,
|
||||
SessionApi,
|
||||
VcsApi,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
@@ -50,6 +44,7 @@ import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
import { ScopedKey, type ServerScope } from "@/utils/server-scope"
|
||||
import { normalizeSessionInfo } from "@/utils/session"
|
||||
import type { ServerProtocol } from "@/utils/server-protocol"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type GlobalStore = {
|
||||
ready: boolean
|
||||
@@ -121,9 +116,10 @@ type ProjectApi = {
|
||||
readonly current: (input?: ProjectCurrentInput) => Promise<ProjectCurrentOutput>
|
||||
}
|
||||
|
||||
type PathApi = {
|
||||
readonly get: (input?: PathGetInput) => Promise<PathGetOutput>
|
||||
}
|
||||
type McpApi = ServerApi["mcp"]
|
||||
type PermissionApi = ServerApi["permission"]
|
||||
type QuestionApi = ServerApi["question"]
|
||||
type VcsApi = ServerApi["vcs"]
|
||||
|
||||
export const loadProjectsQuery = (scope: ServerScope, api: ProjectApi) =>
|
||||
queryOptions({
|
||||
@@ -143,7 +139,7 @@ export const loadProjectsQuery = (scope: ServerScope, api: ProjectApi) =>
|
||||
|
||||
export async function bootstrapGlobal(input: {
|
||||
serverSDK: OpencodeClient
|
||||
serverAPI: CatalogApi & { readonly path: PathApi; readonly project: ProjectApi }
|
||||
serverAPI: CatalogApi & { readonly project: ProjectApi }
|
||||
protocol?: Promise<ServerProtocol>
|
||||
scope: ServerScope
|
||||
requestFailedTitle: string
|
||||
@@ -158,7 +154,7 @@ export async function bootstrapGlobal(input: {
|
||||
input.queryClient.fetchQuery(
|
||||
loadProvidersQuery(input.scope, null, input.serverAPI, input.serverSDK, input.protocol),
|
||||
),
|
||||
() => input.queryClient.fetchQuery(loadPathQuery(input.scope, null, input.serverAPI.path)),
|
||||
() => input.queryClient.fetchQuery(loadPathQuery(input.scope, null, input.serverSDK, input.protocol)),
|
||||
() =>
|
||||
input.queryClient
|
||||
.fetchQuery(loadProjectsQuery(input.scope, input.serverAPI.project))
|
||||
@@ -289,17 +285,26 @@ export const loadCommands = (
|
||||
agent: command.agent,
|
||||
model: providerID && id ? { providerID, id } : undefined,
|
||||
subtask: command.subtask,
|
||||
source: command.source === "skill" ? undefined : command.source,
|
||||
// source: command.source === "skill" ? undefined : command.source,
|
||||
}
|
||||
})
|
||||
}
|
||||
return api.list({ location: { directory } }).then((result) => result.data)
|
||||
})
|
||||
|
||||
export const loadPathQuery = (scope: ServerScope, directory: string | null, api: PathApi) =>
|
||||
export const loadPathQuery = (
|
||||
scope: ServerScope,
|
||||
directory: string | null,
|
||||
sdk: OpencodeClient,
|
||||
protocol?: Promise<ServerProtocol>,
|
||||
) =>
|
||||
queryOptions<Path>({
|
||||
queryKey: [scope, directory, "path"],
|
||||
queryFn: () => retry(() => api.get(directory ? { location: { directory } } : undefined)),
|
||||
queryFn: async () => {
|
||||
if ((await protocol) !== "v1")
|
||||
return { state: "", config: "", worktree: "", directory: directory ?? "", home: "" }
|
||||
return retry(() => sdk.path.get({ directory: directory ?? undefined }).then((result) => result.data!))
|
||||
},
|
||||
})
|
||||
|
||||
export const loadReferencesQuery = (
|
||||
@@ -328,7 +333,6 @@ export async function bootstrapDirectory(input: {
|
||||
readonly agent: AgentListApi
|
||||
readonly command: CommandListApi
|
||||
readonly mcp: McpApi
|
||||
readonly path: PathApi
|
||||
readonly permission: PermissionApi
|
||||
readonly project: ProjectApi
|
||||
readonly question: QuestionApi
|
||||
@@ -408,19 +412,20 @@ export async function bootstrapDirectory(input: {
|
||||
!seededPath &&
|
||||
(() =>
|
||||
input.queryClient
|
||||
.ensureQueryData(loadPathQuery(input.scope, input.directory, input.api.path))
|
||||
.ensureQueryData(loadPathQuery(input.scope, input.directory, input.sdk, input.protocol))
|
||||
.then((data) => {
|
||||
const next = projectID(data.directory ?? input.directory, input.global.project)
|
||||
if (next) input.setStore("project", next)
|
||||
})),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.api.vcs.get({ location: { directory: input.directory } }).then((result) => {
|
||||
const next = { branch: result.data.branch, default_branch: result.data.defaultBranch }
|
||||
retry(async () => {
|
||||
if ((await input.protocol) !== "v1") return
|
||||
return input.sdk.vcs.get().then((result) => {
|
||||
const next = { branch: result.data?.branch, default_branch: result.data?.default_branch }
|
||||
input.setStore("vcs", next)
|
||||
if (next) input.vcsCache.setStore("value", next)
|
||||
}),
|
||||
),
|
||||
})
|
||||
}),
|
||||
input.mcp &&
|
||||
(() =>
|
||||
loadCommands(input.directory, input.api.command, input.sdk, input.protocol).then((commands) =>
|
||||
@@ -503,9 +508,16 @@ export async function bootstrapDirectory(input: {
|
||||
}),
|
||||
),
|
||||
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||
input.mcp && (() => input.queryClient.fetchQuery(loadMcpQuery(input.scope, input.directory, input.api.mcp))),
|
||||
input.mcp &&
|
||||
(() => input.queryClient.fetchQuery(loadMcpResourcesQuery(input.scope, input.directory, input.api.mcp))),
|
||||
(() =>
|
||||
input.queryClient.fetchQuery(
|
||||
loadMcpQuery(input.scope, input.directory, input.api.mcp, input.sdk, input.protocol),
|
||||
)),
|
||||
input.mcp &&
|
||||
(() =>
|
||||
input.queryClient.fetchQuery(
|
||||
loadMcpResourcesQuery(input.scope, input.directory, input.api.mcp, input.sdk, input.protocol),
|
||||
)),
|
||||
() =>
|
||||
input.queryClient
|
||||
.fetchQuery(loadProvidersQuery(input.scope, input.directory, input.api, input.sdk, input.protocol))
|
||||
|
||||
@@ -211,19 +211,19 @@ export function applyDirectoryEvent(input: {
|
||||
}))
|
||||
break
|
||||
}
|
||||
case "session.archived": {
|
||||
const properties = event.properties as { sessionID: string }
|
||||
const result = Binary.search(input.store.session, properties.sessionID, (session) => session.id)
|
||||
if (!result.found) break
|
||||
const info = input.store.session[result.index]
|
||||
input.setStore(
|
||||
"session",
|
||||
produce((draft) => void draft.splice(result.index, 1)),
|
||||
)
|
||||
cleanupSessionCaches(input.setStore, properties.sessionID)
|
||||
if (!info?.parentID) input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
||||
break
|
||||
}
|
||||
// case "session.archived": {
|
||||
// const properties = event.properties as { sessionID: string }
|
||||
// const result = Binary.search(input.store.session, properties.sessionID, (session) => session.id)
|
||||
// if (!result.found) break
|
||||
// const info = input.store.session[result.index]
|
||||
// input.setStore(
|
||||
// "session",
|
||||
// produce((draft) => void draft.splice(result.index, 1)),
|
||||
// )
|
||||
// cleanupSessionCaches(input.setStore, properties.sessionID)
|
||||
// if (!info?.parentID) input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
||||
// break
|
||||
// }
|
||||
case "session.moved": {
|
||||
const properties = event.properties as {
|
||||
sessionID: string
|
||||
|
||||
@@ -570,16 +570,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||
continue
|
||||
}
|
||||
|
||||
void serverSdk()
|
||||
.api.project.update({ projectID: project.id, icon: { color } })
|
||||
.then((result) =>
|
||||
serverSync().set("project", (items) =>
|
||||
items.map((item) => (item.id === result.id ? normalizeProjectInfo(result) : item)),
|
||||
),
|
||||
)
|
||||
.catch(() => {
|
||||
if (colorRequested.get(worktree) === color) colorRequested.delete(worktree)
|
||||
})
|
||||
const projectID = project.id
|
||||
void (async () => {
|
||||
const sdk = serverSdk()
|
||||
if ((await sdk.protocol) !== "v1") return
|
||||
return sdk.client.project
|
||||
.update({ projectID, directory: worktree, icon: { color } })
|
||||
.then((response) => response.data)
|
||||
.then((result) => {
|
||||
if (!result) return
|
||||
serverSync().set("project", (items) =>
|
||||
items.map((item) => (item.id === result.id ? normalizeProjectInfo(result) : item)),
|
||||
)
|
||||
})
|
||||
})().catch(() => {
|
||||
if (colorRequested.get(worktree) === color) colorRequested.delete(worktree)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext(
|
||||
name: "Models",
|
||||
gate: false,
|
||||
init: (props: { directory?: Accessor<string | undefined> } = {}) => {
|
||||
const providers = useProviders(props.directory)
|
||||
const providers = useProviders(() => props.directory?.())
|
||||
|
||||
const [store, setStore, _, ready] = persisted(
|
||||
Persist.global("model", ["model.v1"]),
|
||||
|
||||
@@ -114,7 +114,7 @@ describe("v2 session reducer", () => {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
callID: "call_1",
|
||||
structured: {},
|
||||
metadata: {},
|
||||
content: [{ type: "text", text: "done" }],
|
||||
executed: true,
|
||||
},
|
||||
|
||||
@@ -269,13 +269,18 @@ export function createV2SessionReducer() {
|
||||
...tool,
|
||||
executed: event.data.executed,
|
||||
providerState: event.data.state,
|
||||
state: { status: "running", input: event.data.input, structured: {}, content: [] },
|
||||
// structured: {}, content: []
|
||||
state: { status: "running", input: event.data.input, metadata: {} },
|
||||
time: { ...tool.time, ran: event.created },
|
||||
}))
|
||||
case "session.tool.progress":
|
||||
return updateTool(source, event.data.assistantMessageID, event.data.callID, sessionID, (tool) =>
|
||||
tool.state.status === "running"
|
||||
? { ...tool, state: { ...tool.state, structured: event.data.structured, content: event.data.content } }
|
||||
? {
|
||||
...tool,
|
||||
// state: { ...tool.state, structured: event.data.structured, content: event.data.content },
|
||||
state: { ...tool.state, metadata: event.data.metadata },
|
||||
}
|
||||
: tool,
|
||||
)
|
||||
case "session.tool.success":
|
||||
@@ -288,9 +293,10 @@ export function createV2SessionReducer() {
|
||||
state: {
|
||||
status: "completed",
|
||||
input: tool.state.input,
|
||||
structured: event.data.structured,
|
||||
// structured: event.data.structured,
|
||||
metadata: event.data.metadata,
|
||||
content: event.data.content,
|
||||
result: event.data.result,
|
||||
// result: event.data.result,
|
||||
},
|
||||
time: { ...tool.time, completed: event.created },
|
||||
}
|
||||
@@ -305,10 +311,11 @@ export function createV2SessionReducer() {
|
||||
state: {
|
||||
status: "error",
|
||||
input: typeof tool.state.input === "string" ? {} : tool.state.input,
|
||||
structured: tool.state.status === "running" ? tool.state.structured : {},
|
||||
content: tool.state.status === "running" ? tool.state.content : [],
|
||||
// structured: tool.state.status === "running" ? tool.state.structured : {},
|
||||
metadata: event.data.metadata ?? (tool.state.status === "running" ? tool.state.metadata : {}),
|
||||
content: event.data.content,
|
||||
error: event.data.error,
|
||||
result: event.data.result,
|
||||
// result: event.data.result,
|
||||
},
|
||||
time: { ...tool.time, completed: event.created },
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { retry } from "@opencode-ai/core/util/retry"
|
||||
import type { MessageApi, OpenCodeEvent, SessionApi } from "@opencode-ai/client/promise"
|
||||
import type { OpenCodeEvent, SessionApi } from "@opencode-ai/client/promise"
|
||||
import type { Message, OpencodeClient, Part, Session } from "@opencode-ai/sdk/v2/client"
|
||||
import { createServerSession } from "./server-session"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type MessageApi = ServerApi["message"]
|
||||
|
||||
const session = (id: string, parentID?: string): Session => ({
|
||||
id,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Binary } from "@opencode-ai/core/util/binary"
|
||||
import { retry } from "@opencode-ai/core/util/retry"
|
||||
import type { MessageApi, OpenCodeEvent, SessionApi, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
import type { OpenCodeEvent, SessionApi, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
import type {
|
||||
Message,
|
||||
OpencodeClient,
|
||||
@@ -21,6 +21,9 @@ import { normalizeSessionInfo } from "@/utils/session"
|
||||
import { normalizeSessionMessages } from "@/utils/session-message"
|
||||
import { dropSessionCaches, pickSessionCacheEvictions, SESSION_CACHE_LIMIT } from "./global-sync/session-cache"
|
||||
import { createV2SessionReducer, type V2SessionReduction } from "./server-session-v2-reducer"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type MessageApi = ServerApi["message"]
|
||||
|
||||
const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
||||
const cmpMessage = (a: Message, b: Message) => a.time.created - b.time.created || cmp(a.id, b.id)
|
||||
@@ -954,10 +957,10 @@ export function createServerSession(
|
||||
})
|
||||
if (event.type === "session.usage.updated" && info)
|
||||
remember({ ...info, cost: event.data.cost, tokens: event.data.tokens })
|
||||
if (event.type === "session.archived") {
|
||||
if (info) remember({ ...info, time: { ...info.time, archived: event.created, updated: event.created } })
|
||||
evict([sessionID])
|
||||
}
|
||||
// if (event.type === "session.archived") {
|
||||
// if (info) remember({ ...info, time: { ...info.time, archived: event.created, updated: event.created } })
|
||||
// evict([sessionID])
|
||||
// }
|
||||
if (event.type === "session.execution.started") setData("session_status", sessionID, { type: "busy" })
|
||||
if (
|
||||
event.type === "session.execution.succeeded" ||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||
import type {
|
||||
McpApi,
|
||||
McpListInput,
|
||||
McpResourceCatalogInput,
|
||||
SessionApi,
|
||||
@@ -14,6 +13,9 @@ import { estimateRootSessionTotal, loadRootSessions } from "./global-sync/sessio
|
||||
import { loadActiveSessionsQuery, loadMcpQuery, loadMcpResourcesQuery, seedActiveSessionStatuses } from "./server-sync"
|
||||
import { ServerScope } from "@/utils/server-scope"
|
||||
import { createServerSession } from "./server-session"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type McpApi = ServerApi["mcp"]
|
||||
|
||||
describe("MCP queries", () => {
|
||||
test("loads current servers for the requested location", async () => {
|
||||
|
||||
@@ -188,7 +188,8 @@ function makeQueryOptionsApi(
|
||||
projects: () => loadProjectsQuery(scope, serverAPI.project),
|
||||
providers: (directory: PathKey | null) =>
|
||||
loadProvidersQuery(scope, directory, serverAPI, directory ? sdkFor(directory) : serverSDK(), protocol),
|
||||
path: (directory: PathKey | null) => loadPathQuery(scope, directory, serverAPI.path),
|
||||
path: (directory: PathKey | null) =>
|
||||
loadPathQuery(scope, directory, directory ? sdkFor(directory) : serverSDK(), protocol),
|
||||
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, serverAPI.agent, sdkFor(directory), protocol),
|
||||
references: (directory: PathKey) =>
|
||||
loadReferencesQuery(scope, directory, serverAPI.reference, sdkFor(directory), protocol),
|
||||
@@ -581,7 +582,7 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
||||
children.mark(key)
|
||||
if (
|
||||
event.current?.type === "session.moved" ||
|
||||
event.current?.type === "session.archived" ||
|
||||
// event.current?.type === "session.archived" ||
|
||||
event.current?.type === "session.forked" ||
|
||||
eventType === "command.updated" ||
|
||||
eventType === "config.updated" ||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useServerSync } from "@/context/server-sync"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { Iterable, pipe } from "effect"
|
||||
import type { Accessor } from "solid-js"
|
||||
import { createEffect, createMemo, type Accessor } from "solid-js"
|
||||
import { selectProviderCatalog } from "./provider-catalog"
|
||||
|
||||
export const popularProviders = [
|
||||
@@ -17,14 +17,14 @@ export const popularProviders = [
|
||||
]
|
||||
const popularProviderSet = new Set(popularProviders)
|
||||
|
||||
export function useProviders(directory?: Accessor<string | undefined>) {
|
||||
export function useProviders(directory: Accessor<string | undefined>) {
|
||||
const serverSync = useServerSync()
|
||||
const params = useParams()
|
||||
const dir = () => (directory ? directory() : decode64(params.dir))
|
||||
const providers = () => {
|
||||
const value = dir()
|
||||
const projectStore = value ? serverSync().child(value)[0] : undefined
|
||||
if (directory)
|
||||
if (value)
|
||||
return selectProviderCatalog({
|
||||
explicit: true,
|
||||
directory: value,
|
||||
@@ -37,6 +37,7 @@ export function useProviders(directory?: Accessor<string | undefined>) {
|
||||
global: serverSync().data.provider,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
all: () => providers().all,
|
||||
default: () => providers().default,
|
||||
@@ -58,7 +59,7 @@ export function useProviders(directory?: Accessor<string | undefined>) {
|
||||
},
|
||||
paid: () => {
|
||||
const connected = new Set(providers().connected)
|
||||
return [
|
||||
const paid = [
|
||||
...Iterable.filter(
|
||||
providers().all,
|
||||
([id]) =>
|
||||
@@ -66,6 +67,7 @@ export function useProviders(directory?: Accessor<string | undefined>) {
|
||||
(id !== "opencode" || Object.values(providers().all.get(id)?.models ?? {}).some((m) => m.cost?.input)),
|
||||
),
|
||||
]
|
||||
return paid
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,10 +211,16 @@ export function createHomeSessionsController(home: HomeController) {
|
||||
const ctx = home.server.focusedContext()
|
||||
if (!conn || !ctx) return
|
||||
const [, setStore] = ctx.sync.child(session.directory)
|
||||
if ((await ctx.sdk.protocol) !== "v1") return
|
||||
await archiveHomeSession({
|
||||
server: ServerConnection.key(conn),
|
||||
session,
|
||||
archive: (sessionID) => ctx.sdk.api.session.archive({ sessionID, directory: session.directory }),
|
||||
archive: (sessionID) =>
|
||||
ctx.sdk.client.session.update({
|
||||
sessionID,
|
||||
directory: session.directory,
|
||||
time: { archived: Date.now() },
|
||||
}),
|
||||
remove: () =>
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function LegacyLayout(props: ParentProps) {
|
||||
const permission = usePermission()
|
||||
const navigate = useNavigate()
|
||||
setNavigate(navigate)
|
||||
const providers = useProviders()
|
||||
const providers = useProviders(() => undefined)
|
||||
const dialog = useDialog()
|
||||
const command = useCommand()
|
||||
const theme = useTheme()
|
||||
@@ -872,12 +872,17 @@ export default function LegacyLayout(props: ParentProps) {
|
||||
}
|
||||
|
||||
async function archiveSession(session: Session) {
|
||||
if ((await serverSDK().protocol) !== "v1") return
|
||||
const [store, setStore] = serverSync().child(session.directory)
|
||||
const sessions = store.session ?? []
|
||||
const index = sessions.findIndex((s) => s.id === session.id)
|
||||
const nextSession = sessions[index + 1] ?? sessions[index - 1]
|
||||
|
||||
await serverSDK().api.session.archive({ sessionID: session.id, directory: session.directory })
|
||||
await serverSDK().client.session.update({
|
||||
sessionID: session.id,
|
||||
directory: session.directory,
|
||||
time: { archived: Date.now() },
|
||||
})
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
const match = Binary.search(draft.session, session.id, (s) => s.id)
|
||||
@@ -1296,7 +1301,13 @@ export default function LegacyLayout(props: ParentProps) {
|
||||
const name = next === getFilename(project.worktree) ? "" : next
|
||||
|
||||
if (project.id && project.id !== "global") {
|
||||
const result = await serverSDK().api.project.update({ projectID: project.id, name })
|
||||
const sdk = serverSDK()
|
||||
if ((await sdk.protocol) !== "v1") return
|
||||
const result = await sdk.client.project
|
||||
.update({ projectID: project.id, directory: project.worktree, name })
|
||||
.then((response) => response.data)
|
||||
if (!result) return
|
||||
// const result = await serverSDK().api.project.update({ projectID: project.id, name })
|
||||
serverSync().set("project", (items) =>
|
||||
items.map((item) => (item.id === result.id ? normalizeProjectInfo(result) : item)),
|
||||
)
|
||||
@@ -1479,15 +1490,20 @@ export default function LegacyLayout(props: ParentProps) {
|
||||
return
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
sessions
|
||||
.filter((session) => session.time.archived === undefined)
|
||||
.map((session) =>
|
||||
serverSDK()
|
||||
.api.session.archive({ sessionID: session.id, directory: session.directory })
|
||||
.catch(() => undefined),
|
||||
),
|
||||
)
|
||||
if ((await serverSDK().protocol) === "v1")
|
||||
await Promise.all(
|
||||
sessions
|
||||
.filter((session) => session.time.archived === undefined)
|
||||
.map((session) =>
|
||||
serverSDK()
|
||||
.client.session.update({
|
||||
sessionID: session.id,
|
||||
directory: session.directory,
|
||||
time: { archived: Date.now() },
|
||||
})
|
||||
.catch(() => undefined),
|
||||
),
|
||||
)
|
||||
|
||||
setBusy(directory, false)
|
||||
dismiss()
|
||||
|
||||
@@ -25,35 +25,39 @@ export function createPromptInputController(input: {
|
||||
}) {
|
||||
const layout = useLayout()
|
||||
const local = useLocal()
|
||||
const providers = useProviders()
|
||||
const sync = useSync()
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
const providers = useProviders(() => sdk().directory)
|
||||
const view = layout.view(input.sessionKey)
|
||||
const agentsQuery = createQuery(() => input.queryOptions.agents(pathKey(sdk().directory)))
|
||||
const globalProvidersQuery = createQuery(() => input.queryOptions.providers(null))
|
||||
const providersQuery = createQuery(() => input.queryOptions.providers(pathKey(sdk().directory)))
|
||||
|
||||
return createMemo<PromptInputControls>(() => ({
|
||||
agents: {
|
||||
available: sync().data.agent,
|
||||
options: local.agent.list().map((agent) => agent.name),
|
||||
current: local.agent.current()?.name ?? "",
|
||||
loading: agentsQuery.isLoading,
|
||||
visible: local.agent.visible(),
|
||||
select: local.agent.set,
|
||||
},
|
||||
model: {
|
||||
selection: input.model ?? local.model,
|
||||
paid: providers.paid().length > 0,
|
||||
loading:
|
||||
(local.agent.visible() && agentsQuery.isLoading) || providersQuery.isLoading || globalProvidersQuery.isLoading,
|
||||
},
|
||||
session: {
|
||||
id: input.sessionID(),
|
||||
tabs: layout.tabs(input.sessionKey),
|
||||
reviewPanel: view.reviewPanel,
|
||||
},
|
||||
}))
|
||||
return createMemo<PromptInputControls>(() => {
|
||||
return {
|
||||
agents: {
|
||||
available: sync().data.agent,
|
||||
options: local.agent.list().map((agent) => agent.name),
|
||||
current: local.agent.current()?.name ?? "",
|
||||
loading: agentsQuery.isLoading,
|
||||
visible: local.agent.visible(),
|
||||
select: local.agent.set,
|
||||
},
|
||||
model: {
|
||||
selection: input.model ?? local.model,
|
||||
paid: providers.paid().length > 0,
|
||||
loading:
|
||||
(local.agent.visible() && agentsQuery.isLoading) ||
|
||||
providersQuery.isLoading ||
|
||||
globalProvidersQuery.isLoading,
|
||||
},
|
||||
session: {
|
||||
id: input.sessionID(),
|
||||
tabs: layout.tabs(input.sessionKey),
|
||||
reviewPanel: view.reviewPanel,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createPromptProjectControls() {
|
||||
|
||||
@@ -812,13 +812,14 @@ export function MessageTimeline(props: {
|
||||
const archiveSession = async (sessionID: string) => {
|
||||
const session = sync().session.get(sessionID)
|
||||
if (!session) return
|
||||
if ((await sdk().protocol) !== "v1") return
|
||||
|
||||
const sessions = sync().data.session ?? []
|
||||
const index = sessions.findIndex((s) => s.id === sessionID)
|
||||
const nextSession = index === -1 ? undefined : (sessions[index + 1] ?? sessions[index - 1])
|
||||
|
||||
await sdk()
|
||||
.api.session.archive({ sessionID })
|
||||
.client.session.update({ sessionID, directory: sdk().directory, time: { archived: Date.now() } })
|
||||
.then(() => {
|
||||
sync().set(
|
||||
produce((draft) => {
|
||||
|
||||
@@ -53,6 +53,7 @@ function setup(
|
||||
}
|
||||
|
||||
describe("createCompatibleApi", () => {
|
||||
/*
|
||||
test("routes V1 archive through the legacy session update", async () => {
|
||||
const { api, requests } = setup("v1")
|
||||
await api.session.archive({ sessionID: "ses_1", directory: "/repo" })
|
||||
@@ -63,6 +64,7 @@ describe("createCompatibleApi", () => {
|
||||
expect(requests[0]!.method).toBe("PATCH")
|
||||
expect(await requests[0]!.json()).toMatchObject({ time: { archived: expect.any(Number) } })
|
||||
})
|
||||
*/
|
||||
|
||||
test("converts current prompts to the V1 prompt contract", async () => {
|
||||
const { api, requests } = setup("v1")
|
||||
@@ -127,14 +129,6 @@ describe("createCompatibleApi", () => {
|
||||
])
|
||||
})
|
||||
|
||||
test("keeps V2 session actions on the current API", async () => {
|
||||
const { api, requests } = setup("v2")
|
||||
await api.session.archive({ sessionID: "ses_1" })
|
||||
|
||||
expect(new URL(requests[0]!.url).pathname).toBe("/api/session/ses_1/archive")
|
||||
expect(requests[0]!.method).toBe("POST")
|
||||
})
|
||||
|
||||
test("resolves protocol detection once across implementation methods", async () => {
|
||||
let detections = 0
|
||||
const resolved = Promise.resolve<"v1" | "v2">("v2")
|
||||
@@ -147,12 +141,22 @@ describe("createCompatibleApi", () => {
|
||||
})
|
||||
const { api } = setup(protocol)
|
||||
|
||||
await api.session.archive({ sessionID: "ses_1" })
|
||||
await api.session.list()
|
||||
await api.session.list()
|
||||
|
||||
expect(detections).toBe(1)
|
||||
})
|
||||
|
||||
/*
|
||||
test("keeps V2 session actions on the current API", async () => {
|
||||
const { api, requests } = setup("v2")
|
||||
await api.session.archive({ sessionID: "ses_1" })
|
||||
|
||||
expect(new URL(requests[0]!.url).pathname).toBe("/api/session/ses_1/archive")
|
||||
expect(requests[0]!.method).toBe("POST")
|
||||
})
|
||||
*/
|
||||
|
||||
test("uses the global V1 session search endpoint", async () => {
|
||||
const { api, requests } = setup("v1")
|
||||
await api.session.list({ parentID: null, search: "session", limit: 50 })
|
||||
@@ -160,6 +164,7 @@ describe("createCompatibleApi", () => {
|
||||
expect(new URL(requests[0]!.url).pathname).toBe("/experimental/session")
|
||||
})
|
||||
|
||||
/*
|
||||
test("projects the V1 default branch", async () => {
|
||||
const { api } = setup("v1", { vcs: { branch: "feature", default_branch: "dev" } })
|
||||
|
||||
@@ -167,6 +172,7 @@ describe("createCompatibleApi", () => {
|
||||
data: { branch: "feature", defaultBranch: "dev" },
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
test("translates current file searches to the V1 dirs parameter", async () => {
|
||||
const { api, requests } = setup("v1")
|
||||
@@ -190,4 +196,41 @@ describe("createCompatibleApi", () => {
|
||||
expect(new URL(requests[0]!.url).pathname).toBe("/session/ses_1/permissions/permission_1")
|
||||
expect(new URL(requests[0]!.url).searchParams.get("directory")).toBe("/other")
|
||||
})
|
||||
|
||||
test("disposes the V1 instance after connecting a provider", async () => {
|
||||
const { api, requests } = setup("v1")
|
||||
|
||||
await api.integration.connect.key({
|
||||
integrationID: "openrouter",
|
||||
key: "secret",
|
||||
location: { directory: "/repo" },
|
||||
})
|
||||
|
||||
expect(requests.map((request) => new URL(request.url).pathname)).toEqual([
|
||||
"/auth/openrouter",
|
||||
"/instance/dispose",
|
||||
"/instance/dispose",
|
||||
])
|
||||
expect(requests[1]!.headers.get("x-opencode-directory")).toBe("%2Frepo")
|
||||
expect(requests[2]!.headers.get("x-opencode-directory")).toBeNull()
|
||||
})
|
||||
|
||||
test("disposes the V1 instance after completing provider OAuth", async () => {
|
||||
const { api, requests } = setup("v1")
|
||||
|
||||
await api.integration.oauth.complete({
|
||||
integrationID: "openrouter",
|
||||
attemptID: "openrouter:0",
|
||||
code: "code",
|
||||
location: { directory: "/repo" },
|
||||
})
|
||||
|
||||
expect(requests.map((request) => new URL(request.url).pathname)).toEqual([
|
||||
"/provider/openrouter/oauth/callback",
|
||||
"/instance/dispose",
|
||||
"/instance/dispose",
|
||||
])
|
||||
expect(requests[1]!.headers.get("x-opencode-directory")).toBe("%2Frepo")
|
||||
expect(requests[2]!.headers.get("x-opencode-directory")).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ type CompatibleSessionApi = Omit<
|
||||
shell: (input: SessionShellInput & LegacyPrompt) => Promise<SessionShellOutput>
|
||||
compact: (input: SessionCompactInput & { model?: LegacyPrompt["model"] }) => Promise<SessionCompactOutput>
|
||||
rename: (input: Parameters<SessionApi["rename"]>[0] & LegacyLocation) => ReturnType<SessionApi["rename"]>
|
||||
archive: (input: Parameters<SessionApi["archive"]>[0] & LegacyLocation) => ReturnType<SessionApi["archive"]>
|
||||
// archive: (input: Parameters<SessionApi["archive"]>[0] & LegacyLocation) => ReturnType<SessionApi["archive"]>
|
||||
remove: (input: Parameters<SessionApi["remove"]>[0] & LegacyLocation) => ReturnType<SessionApi["remove"]>
|
||||
}
|
||||
type CompatiblePermissionApi = Omit<ServerApi["permission"], "reply"> & {
|
||||
@@ -183,9 +183,9 @@ function createV1Api(input: CompatibleInput): CompatibleApi {
|
||||
async rename(value: Parameters<ServerApi["session"]["rename"]>[0] & LegacyLocation) {
|
||||
await legacy(value).session.update({ sessionID: value.sessionID, title: value.title })
|
||||
},
|
||||
async archive(value: Parameters<ServerApi["session"]["archive"]>[0] & LegacyLocation) {
|
||||
await legacy(value).session.update({ sessionID: value.sessionID, time: { archived: Date.now() } })
|
||||
},
|
||||
// async archive(value: Parameters<ServerApi["session"]["archive"]>[0] & LegacyLocation) {
|
||||
// await legacy(value).session.update({ sessionID: value.sessionID, time: { archived: Date.now() } })
|
||||
// },
|
||||
async remove(value: Parameters<ServerApi["session"]["remove"]>[0] & LegacyLocation) {
|
||||
await legacy(value).session.delete(value)
|
||||
},
|
||||
@@ -308,34 +308,34 @@ function createV1Api(input: CompatibleInput): CompatibleApi {
|
||||
if (!result.data) throw new Error("Project not found")
|
||||
return { id: result.data.id, directory: result.data.worktree } satisfies ProjectCurrent
|
||||
},
|
||||
async update(value: Parameters<ServerApi["project"]["update"]>[0]) {
|
||||
const project = (await legacy().project.list()).data?.find((item) => item.id === value.projectID)
|
||||
const result = await legacy({ directory: project?.worktree }).project.update({
|
||||
...value,
|
||||
directory: project?.worktree,
|
||||
})
|
||||
if (!result.data) throw new Error(`Project not found: ${value.projectID}`)
|
||||
return result.data as Project
|
||||
},
|
||||
// async update(value: Parameters<ServerApi["project"]["update"]>[0]) {
|
||||
// const project = (await legacy().project.list()).data?.find((item) => item.id === value.projectID)
|
||||
// const result = await legacy({ directory: project?.worktree }).project.update({
|
||||
// ...value,
|
||||
// directory: project?.worktree,
|
||||
// })
|
||||
// if (!result.data) throw new Error(`Project not found: ${value.projectID}`)
|
||||
// return result.data as Project
|
||||
// },
|
||||
async directories(value: Parameters<ServerApi["project"]["directories"]>[0]) {
|
||||
const result = await legacy(value.location).worktree.list()
|
||||
return (result.data ?? []).map((item) => ({ directory: item }))
|
||||
},
|
||||
},
|
||||
path: {
|
||||
...input.current.path,
|
||||
async get(value?: Parameters<ServerApi["path"]["get"]>[0]) {
|
||||
const result = await legacy(value?.location).path.get()
|
||||
if (!result.data) throw new Error("Path unavailable")
|
||||
return result.data
|
||||
},
|
||||
},
|
||||
// path: {
|
||||
// ...input.current.path,
|
||||
// async get(value?: Parameters<ServerApi["path"]["get"]>[0]) {
|
||||
// const result = await legacy(value?.location).path.get()
|
||||
// if (!result.data) throw new Error("Path unavailable")
|
||||
// return result.data
|
||||
// },
|
||||
// },
|
||||
vcs: {
|
||||
...input.current.vcs,
|
||||
async get(value?: Parameters<ServerApi["vcs"]["get"]>[0]) {
|
||||
const result = await legacy(value?.location).vcs.get()
|
||||
return located({ branch: result.data?.branch, defaultBranch: result.data?.default_branch }, value?.location)
|
||||
},
|
||||
// async get(value?: Parameters<ServerApi["vcs"]["get"]>[0]) {
|
||||
// const result = await legacy(value?.location).vcs.get()
|
||||
// return located({ branch: result.data?.branch, defaultBranch: result.data?.default_branch }, value?.location)
|
||||
// },
|
||||
async status(value?: Parameters<ServerApi["vcs"]["status"]>[0]) {
|
||||
const result = await legacy(value?.location).vcs.status()
|
||||
return located(result.data ?? [], value?.location)
|
||||
@@ -401,6 +401,8 @@ function createV1Api(input: CompatibleInput): CompatibleApi {
|
||||
providerID: value.integrationID,
|
||||
auth: { type: "api", key: value.key },
|
||||
})
|
||||
await legacy(value.location).instance.dispose()
|
||||
await input.legacy().instance.dispose()
|
||||
},
|
||||
},
|
||||
oauth: {
|
||||
@@ -429,6 +431,8 @@ function createV1Api(input: CompatibleInput): CompatibleApi {
|
||||
{ providerID: value.integrationID, method, code: value.code },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
await legacy(value.location).instance.dispose()
|
||||
await input.legacy().instance.dispose()
|
||||
},
|
||||
status: async (value: Parameters<ServerApi["integration"]["oauth"]["status"]>[0]) => {
|
||||
const method = Number(value.attemptID.split(":").at(-1))
|
||||
@@ -436,6 +440,8 @@ function createV1Api(input: CompatibleInput): CompatibleApi {
|
||||
{ providerID: value.integrationID, method },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
await legacy(value.location).instance.dispose()
|
||||
await input.legacy().instance.dispose()
|
||||
return located(
|
||||
{ status: "complete" as const, time: { created: Date.now(), expires: Date.now() } },
|
||||
value.location,
|
||||
@@ -445,9 +451,9 @@ function createV1Api(input: CompatibleInput): CompatibleApi {
|
||||
},
|
||||
pty: {
|
||||
...input.current.pty,
|
||||
async shells(value?: Parameters<ServerApi["pty"]["shells"]>[0]) {
|
||||
return located((await legacy(value?.location).pty.shells()).data ?? [], value?.location)
|
||||
},
|
||||
// async shells(value?: Parameters<ServerApi["pty"]["shells"]>[0]) {
|
||||
// return located((await legacy(value?.location).pty.shells()).data ?? [], value?.location)
|
||||
// },
|
||||
async list(value?: Parameters<ServerApi["pty"]["list"]>[0]) {
|
||||
return located((await legacy(value?.location).pty.list()).data ?? [], value?.location)
|
||||
},
|
||||
@@ -479,11 +485,11 @@ function createV1Api(input: CompatibleInput): CompatibleApi {
|
||||
async remove(value: Parameters<ServerApi["pty"]["remove"]>[0]) {
|
||||
await legacy(value.location).pty.remove({ ptyID: value.ptyID })
|
||||
},
|
||||
async connectToken(value: Parameters<ServerApi["pty"]["connectToken"]>[0]) {
|
||||
const result = await legacy(value.location).pty.connectToken({ ptyID: value.ptyID })
|
||||
if (!result.data) throw new Error(`Failed to connect terminal: ${value.ptyID}`)
|
||||
return located(result.data, value.location)
|
||||
},
|
||||
// async connectToken(value: Parameters<ServerApi["pty"]["connectToken"]>[0]) {
|
||||
// const result = await legacy(value.location).pty.connectToken({ ptyID: value.ptyID })
|
||||
// if (!result.data) throw new Error(`Failed to connect terminal: ${value.ptyID}`)
|
||||
// return located(result.data, value.location)
|
||||
// },
|
||||
},
|
||||
permission: {
|
||||
...input.current.permission,
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("normalizeSessionMessages", () => {
|
||||
state: {
|
||||
status: "completed",
|
||||
input: { filePath: "note.txt" },
|
||||
structured: { title: "note.txt" },
|
||||
metadata: { title: "note.txt" },
|
||||
content: [{ type: "text", text: "hello" }],
|
||||
},
|
||||
time: { created: 5, ran: 6, completed: 7 },
|
||||
@@ -170,7 +170,7 @@ describe("normalizeSessionMessages", () => {
|
||||
status: "completed",
|
||||
input: { path: "/repo/README.md", oldString: "old", newString: "new" },
|
||||
content: [{ type: "text", text: "Edited file successfully" }],
|
||||
structured: {
|
||||
metadata: {
|
||||
files: [
|
||||
{
|
||||
file: "README.md",
|
||||
|
||||
@@ -304,7 +304,8 @@ function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssi
|
||||
return {
|
||||
status: "running" as const,
|
||||
input: normalizeToolInput(tool.name, tool.state.input),
|
||||
metadata: normalizeToolMetadata(tool.name, tool.state.structured),
|
||||
// metadata: normalizeToolMetadata(tool.name, tool.state.structured),
|
||||
metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}),
|
||||
time: { start },
|
||||
}
|
||||
}
|
||||
@@ -313,7 +314,8 @@ function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssi
|
||||
status: "error" as const,
|
||||
input: normalizeToolInput(tool.name, tool.state.input),
|
||||
error: tool.state.error.message,
|
||||
metadata: normalizeToolMetadata(tool.name, tool.state.structured),
|
||||
// metadata: normalizeToolMetadata(tool.name, tool.state.structured),
|
||||
metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}),
|
||||
time: { start, end: tool.time.completed ?? start },
|
||||
}
|
||||
}
|
||||
@@ -337,7 +339,8 @@ function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssi
|
||||
input: normalizeToolInput(tool.name, tool.state.input),
|
||||
output: tool.state.content.flatMap((item) => (item.type === "text" ? [item.text] : [])).join("\n"),
|
||||
title: tool.name,
|
||||
metadata: normalizeToolMetadata(tool.name, tool.state.structured),
|
||||
// metadata: normalizeToolMetadata(tool.name, tool.state.structured),
|
||||
metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}),
|
||||
time: { start, end: tool.time.completed ?? start },
|
||||
attachments: attachments.length ? attachments : undefined,
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/cli",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-support",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -58,7 +58,6 @@ const layer = Layer.effect(
|
||||
finalize: (draft) =>
|
||||
Effect.gen(function* () {
|
||||
materialized.clear()
|
||||
const seen = new Map<string, string | undefined>()
|
||||
for (const [name, source] of draft.list()) {
|
||||
if (source.type === "local") {
|
||||
materialized.set(
|
||||
@@ -82,14 +81,11 @@ const layer = Layer.effect(
|
||||
continue
|
||||
}
|
||||
}
|
||||
const target = Repository.cachePath(global.repos, repository)
|
||||
if (seen.has(target) && seen.get(target) !== source.branch) continue
|
||||
seen.set(target, source.branch)
|
||||
materialized.set(
|
||||
name,
|
||||
new Info({
|
||||
name,
|
||||
path: AbsolutePath.make(target),
|
||||
path: AbsolutePath.make(Repository.cachePath(global.repos, repository, source.branch)),
|
||||
...(source.description === undefined ? {} : { description: source.description }),
|
||||
...(source.hidden === undefined ? {} : { hidden: source.hidden }),
|
||||
source,
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* Local tracking checkouts for remote Git references, one per remote and
|
||||
* branch. Each checkout permanently tracks a single ref: the requested branch
|
||||
* when the cache key has one, otherwise the remote default branch. Content
|
||||
* follows "newest wins": refresh fetches and hard-resets, so readers may
|
||||
* observe the checkout move underneath them.
|
||||
*/
|
||||
import path from "path"
|
||||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { FSUtil } from "./fs-util"
|
||||
@@ -135,7 +142,7 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFl
|
||||
if (input.branch) yield* validateBranch(input.branch)
|
||||
|
||||
const repository = input.reference.label
|
||||
const localPath = Repository.cachePath(global.repos, input.reference)
|
||||
const localPath = Repository.cachePath(global.repos, input.reference, input.branch)
|
||||
const cloneTarget = Repository.parse(input.reference.remote) ?? input.reference
|
||||
|
||||
return yield* flock
|
||||
@@ -143,21 +150,28 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFl
|
||||
Effect.gen(function* () {
|
||||
yield* cacheOperation(fs.ensureDir(path.dirname(localPath)), "ensure cache directory", localPath)
|
||||
|
||||
const exists = yield* fs.existsSafe(localPath)
|
||||
const existing = yield* git.repo.discover(AbsolutePath.make(localPath))
|
||||
const origin = existing ? yield* git.remote.get(existing) : undefined
|
||||
const originReference = origin ? Repository.parse(origin) : undefined
|
||||
const reuse = Boolean(existing && originReference && Repository.same(originReference, cloneTarget))
|
||||
if (exists && !reuse) {
|
||||
// Discovery walks upward, so an enclosing repository with a
|
||||
// matching origin could masquerade as the cache entry; reuse
|
||||
// requires the checkout to live exactly at the cache path.
|
||||
const worktree = existing ? yield* fs.resolve(localPath) : undefined
|
||||
const reuse = Boolean(
|
||||
existing &&
|
||||
existing.worktree === worktree &&
|
||||
originReference &&
|
||||
Repository.same(originReference, cloneTarget),
|
||||
)
|
||||
if (!reuse && (yield* fs.existsSafe(localPath))) {
|
||||
yield* cacheOperation(fs.remove(localPath, { recursive: true }), "remove stale cache", localPath)
|
||||
}
|
||||
|
||||
const currentBranch = reuse && existing ? yield* git.history.branch(existing) : undefined
|
||||
const status = statusForRepository({
|
||||
reuse,
|
||||
refresh: input.refresh,
|
||||
branchMatches: input.branch ? currentBranch === input.branch : undefined,
|
||||
})
|
||||
const status = !reuse
|
||||
? ("cloned" as const)
|
||||
: input.refresh
|
||||
? ("refreshed" as const)
|
||||
: ("cached" as const)
|
||||
|
||||
if (status === "cloned") {
|
||||
yield* git.repo
|
||||
@@ -177,25 +191,28 @@ const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFl
|
||||
.pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
|
||||
|
||||
if (input.branch) {
|
||||
const requestedBranch = input.branch
|
||||
yield* git.sync
|
||||
.fetchBranch(existing, { branch: requestedBranch })
|
||||
.fetchBranch(existing, { branch: input.branch })
|
||||
.pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
|
||||
|
||||
yield* git.sync.checkoutRemoteBranch(existing, { branch: requestedBranch }).pipe(
|
||||
Effect.mapError(
|
||||
(error) =>
|
||||
new CheckoutFailedError({
|
||||
repository,
|
||||
branch: requestedBranch,
|
||||
message: error.message,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Checking out the tracked ref before resetting keeps the
|
||||
// checkout self-healing even if it was left on another
|
||||
// branch.
|
||||
const branch = input.branch ?? (yield* git.history.defaultRemoteBranch(existing))
|
||||
if (branch) {
|
||||
yield* git.sync
|
||||
.checkoutRemoteBranch(existing, { branch })
|
||||
.pipe(
|
||||
Effect.mapError(
|
||||
(error) => new CheckoutFailedError({ repository, branch, message: error.message }),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const target = branch ?? (yield* git.history.branch(existing))
|
||||
yield* git.sync
|
||||
.resetHard(existing, yield* resetTarget(git, existing, input.branch))
|
||||
.resetHard(existing, target ? `origin/${target}` : "HEAD")
|
||||
.pipe(Effect.mapError((error) => new ResetFailedError({ repository, message: error.message })))
|
||||
}
|
||||
|
||||
@@ -229,12 +246,6 @@ export const node = makeGlobalNode({
|
||||
deps: [EffectFlock.node, FSUtil.node, Git.node, Global.node],
|
||||
})
|
||||
|
||||
function statusForRepository(input: { reuse: boolean; refresh?: boolean; branchMatches?: boolean }) {
|
||||
if (!input.reuse) return "cloned" as const
|
||||
if (input.branchMatches === false || input.refresh) return "refreshed" as const
|
||||
return "cached" as const
|
||||
}
|
||||
|
||||
function errorMessage(error: unknown) {
|
||||
return error instanceof globalThis.Error ? error.message : String(error)
|
||||
}
|
||||
@@ -245,17 +256,4 @@ function cacheOperation<A, E, R>(effect: Effect.Effect<A, E, R>, operation: stri
|
||||
)
|
||||
}
|
||||
|
||||
const resetTarget = Effect.fnUntraced(function* (
|
||||
git: Git.Interface,
|
||||
repository: Git.Repository,
|
||||
requestedBranch?: string,
|
||||
) {
|
||||
if (requestedBranch) return `origin/${requestedBranch}`
|
||||
const remoteHead = yield* git.history.defaultRemoteBranch(repository)
|
||||
if (remoteHead) return `origin/${remoteHead}`
|
||||
const currentBranch = yield* git.history.branch(repository)
|
||||
if (currentBranch) return `origin/${currentBranch}`
|
||||
return "HEAD"
|
||||
})
|
||||
|
||||
export * as RepositoryCache from "./repository-cache"
|
||||
|
||||
@@ -118,8 +118,14 @@ export function isRemote(reference: Reference): reference is RemoteReference {
|
||||
return !isFile(reference)
|
||||
}
|
||||
|
||||
export function cachePath(root: string, reference: Reference): string {
|
||||
return path.join(root, ...reference.host.split(":"), ...reference.segments)
|
||||
/**
|
||||
* Checkouts are keyed by remote and branch: a branch-specific reference gets
|
||||
* its own directory so branchless refreshes can never move it. The branch is
|
||||
* percent-encoded because valid branch names may contain `/`.
|
||||
*/
|
||||
export function cachePath(root: string, reference: Reference, branch?: string): string {
|
||||
const base = path.join(root, ...reference.host.split(":"), ...reference.segments)
|
||||
return branch ? `${base}@${encodeURIComponent(branch)}` : base
|
||||
}
|
||||
|
||||
export function cacheIdentity(reference: Reference): string {
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("Reference", () => {
|
||||
expect(yield* references.list()).toEqual([
|
||||
new Reference.Info({
|
||||
name: "sdk",
|
||||
path: AbsolutePath.make(Repository.cachePath(Global.Path.repos, repository)),
|
||||
path: AbsolutePath.make(Repository.cachePath(Global.Path.repos, repository, "main")),
|
||||
source,
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -8,7 +8,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Repository } from "@opencode-ai/core/repository"
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache"
|
||||
import { git, gitRemote } from "./fixture/git"
|
||||
import { branch, git, gitRemote } from "./fixture/git"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
@@ -66,6 +66,41 @@ describe("RepositoryCache", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("keeps branch checkouts isolated from branchless refreshes", () =>
|
||||
withRemote((fixture) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(() => branch(fixture.source, "feature", "two\n"))
|
||||
const cache = yield* RepositoryCache.Service
|
||||
|
||||
const featured = yield* cache.ensure({ reference: fixture.reference, branch: "feature" })
|
||||
expect(featured.branch).toBe("feature")
|
||||
expect(featured.localPath.endsWith("repo@feature")).toBe(true)
|
||||
expect(yield* read(path.join(featured.localPath, "README.md"))).toBe("two\n")
|
||||
|
||||
const refreshed = yield* cache.ensure({ reference: fixture.reference, refresh: true })
|
||||
expect(refreshed.localPath).not.toBe(featured.localPath)
|
||||
expect(yield* read(path.join(refreshed.localPath, "README.md"))).toBe("one\n")
|
||||
|
||||
const cached = yield* cache.ensure({ reference: fixture.reference, branch: "feature" })
|
||||
expect(cached.status).toBe("cached")
|
||||
expect(yield* read(path.join(cached.localPath, "README.md"))).toBe("two\n")
|
||||
}).pipe(Effect.provide(cacheLayer(fixture.root))),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("does not mistake an enclosing repository for the cache checkout", () =>
|
||||
withRemote((fixture) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(() => git(fixture.root, "clone", fixture.remote, path.join(fixture.root, "repos")))
|
||||
|
||||
const result = yield* (yield* RepositoryCache.Service).ensure({ reference: fixture.reference })
|
||||
|
||||
expect(result.status).toBe("cloned")
|
||||
expect(yield* read(path.join(result.localPath, "README.md"))).toBe("one\n")
|
||||
}).pipe(Effect.provide(cacheLayer(fixture.root))),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("returns typed validation and clone failures", () =>
|
||||
withRemote((fixture) =>
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -17,6 +17,12 @@ describe("Repository", () => {
|
||||
label: "owner/repo",
|
||||
})
|
||||
expect(Repository.cachePath("/cache", reference)).toBe(path.join("/cache", "github.com", "owner", "repo"))
|
||||
expect(Repository.cachePath("/cache", reference, "main")).toBe(
|
||||
path.join("/cache", "github.com", "owner", "repo@main"),
|
||||
)
|
||||
expect(Repository.cachePath("/cache", reference, "feature/x")).toBe(
|
||||
path.join("/cache", "github.com", "owner", "repo@feature%2Fx"),
|
||||
)
|
||||
expect(Repository.cacheIdentity(reference)).toBe("github.com/owner/repo")
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -635,24 +635,23 @@ function openaiCompatibleReasoningEfforts(id: string) {
|
||||
return gpt5CodexReasoningEfforts(apiId) ?? versionedGpt5ReasoningEfforts(apiId) ?? OPENAI_EFFORTS
|
||||
}
|
||||
|
||||
function anthropicOpus47OrLater(apiId: string) {
|
||||
// Matches "opus-4.7" (Anthropic/Bedrock/Vertex) and "claude-4.7-opus" (SAP AI Core inverted).
|
||||
// Greedy \d+ correctly extends to multi-digit majors (e.g. "claude-10.0-opus") for forward compatibility.
|
||||
const version = /opus-(\d+)[.-](\d+)(?:[.@-]|$)|claude-(\d+)[.-](\d+)-opus(?:[.@-]|$)/i.exec(apiId)
|
||||
if (!version) return false
|
||||
const major = Number(version[1] ?? version[3])
|
||||
const minor = Number(version[2] ?? version[4])
|
||||
function anthropicUsesModernAdaptiveThinking(apiId: string) {
|
||||
if (!apiId.toLowerCase().includes("claude-")) return false
|
||||
// Covers family-first IDs such as claude-opus-4.7 and version-first IDs such as claude-4.7-opus.
|
||||
// Limit minors to two digits so release dates in IDs such as claude-opus-4-20250514 are not versions.
|
||||
const version = /claude-(?:[a-z]+-)?(\d+)(?:[.-](\d{1,2}))?(?:[.@-]|$)/i.exec(apiId)
|
||||
if (!version) return true
|
||||
const major = Number(version[1])
|
||||
const minor = Number(version[2] ?? 0)
|
||||
return major > 4 || (major === 4 && minor >= 7)
|
||||
}
|
||||
|
||||
function anthropicSonnet5OrLater(apiId: string) {
|
||||
const version = /sonnet-(\d+)(?:[.@-]|$)|claude-(\d+)-sonnet(?:[.@-]|$)/i.exec(apiId)
|
||||
if (!version) return false
|
||||
return Number(version[1] ?? version[2]) >= 5
|
||||
function anthropicOpus45(apiId: string) {
|
||||
return ["opus-4-5", "opus-4.5"].some((value) => apiId.includes(value))
|
||||
}
|
||||
|
||||
function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
||||
if (anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")) {
|
||||
if (anthropicUsesModernAdaptiveThinking(apiId)) {
|
||||
return ["low", "medium", "high", "xhigh", "max"]
|
||||
}
|
||||
if (
|
||||
@@ -666,7 +665,7 @@ function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
||||
}
|
||||
|
||||
function anthropicOmitsThinking(apiId: string) {
|
||||
return anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")
|
||||
return anthropicUsesModernAdaptiveThinking(apiId)
|
||||
}
|
||||
|
||||
function googleThinkingLevelEfforts(apiId: string) {
|
||||
@@ -990,8 +989,10 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
)
|
||||
}
|
||||
|
||||
if (["opus-4-5", "opus-4.5"].some((v) => model.api.id.includes(v))) {
|
||||
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { effort }]))
|
||||
if (anthropicOpus45(model.api.id)) {
|
||||
return Object.fromEntries(
|
||||
WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, anthropicOpus45Effort(model, effort)]),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1103,7 +1104,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
if (id.includes("anthropic")) {
|
||||
if (adaptiveEfforts) {
|
||||
// Bedrock adaptive splits `effort` out into `output_config` (vs Anthropic
|
||||
// native which inlines it). Opus 4.7+ flipped `display` default to "omitted".
|
||||
// native which inlines it). Claude 4.7+ defaults `display` to "omitted".
|
||||
return wrapInSapModelParams(
|
||||
Object.fromEntries(
|
||||
adaptiveEfforts.map((effort) => [
|
||||
@@ -1711,6 +1712,14 @@ function reasoningEffort(model: Provider.Model, effort: string) {
|
||||
...(anthropicOmitsThinking(model.api.id) ? { display: "summarized" } : {}),
|
||||
},
|
||||
}
|
||||
if (anthropicOpus45(model.api.id))
|
||||
return {
|
||||
reasoningConfig: {
|
||||
type: "enabled",
|
||||
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
|
||||
maxReasoningEffort: effort,
|
||||
},
|
||||
}
|
||||
if (model.api.id.includes("anthropic")) return
|
||||
return { reasoningConfig: { type: "enabled", maxReasoningEffort: effort } }
|
||||
case "@ai-sdk/gateway":
|
||||
@@ -1751,7 +1760,7 @@ function reasoningEffort(model: Provider.Model, effort: string) {
|
||||
}
|
||||
|
||||
function anthropicEffort(model: Provider.Model, effort: string) {
|
||||
if (["opus-4-5", "opus-4.5"].some((value) => model.api.id.includes(value))) return { effort }
|
||||
if (anthropicOpus45(model.api.id)) return anthropicOpus45Effort(model, effort)
|
||||
// Kimi defaults to omitting adaptive thinking text unless summarized display is requested.
|
||||
if (isKimiFamily(model)) return { thinking: { type: "adaptive", display: "summarized" }, effort }
|
||||
if (!anthropicAdaptiveEfforts(model.api.id)) return
|
||||
@@ -1764,6 +1773,16 @@ function anthropicEffort(model: Provider.Model, effort: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function anthropicOpus45Effort(model: Provider.Model, effort: string) {
|
||||
return {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
|
||||
},
|
||||
effort,
|
||||
}
|
||||
}
|
||||
|
||||
function reasoningBudget(model: Provider.Model, budget: number) {
|
||||
switch (model.api.npm) {
|
||||
case "@openrouter/ai-sdk-provider":
|
||||
|
||||
@@ -3213,6 +3213,7 @@ describe("ProviderTransform.reasoningVariants", () => {
|
||||
{ thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
|
||||
"claude-opus-4-7",
|
||||
],
|
||||
["@ai-sdk/anthropic", { thinking: { type: "adaptive", display: "summarized" }, effort: "high" }, "claude-opus-5"],
|
||||
["@ai-sdk/google", { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }],
|
||||
["@ai-sdk/google-vertex", { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }],
|
||||
[
|
||||
@@ -3264,13 +3265,18 @@ describe("ProviderTransform.reasoningVariants", () => {
|
||||
)
|
||||
})
|
||||
|
||||
test("uses bare effort for Claude Opus 4.5", () => {
|
||||
test("combines effort with extended thinking for Claude Opus 4.5", () => {
|
||||
expect(
|
||||
ProviderTransform.reasoningVariants(
|
||||
model([{ type: "effort", values: ["high"] }]),
|
||||
target("@ai-sdk/anthropic", "claude-opus-4-5"),
|
||||
),
|
||||
).toEqual({ high: { effort: "high" } })
|
||||
).toEqual({
|
||||
high: {
|
||||
thinking: { type: "enabled", budgetTokens: 16_000 },
|
||||
effort: "high",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("uses explicit effort metadata for Anthropic-compatible models", () => {
|
||||
@@ -3319,6 +3325,38 @@ describe("ProviderTransform.reasoningVariants", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("uses adaptive reasoning config for Claude Opus 5 on Bedrock", () => {
|
||||
const result = ProviderTransform.reasoningVariants(
|
||||
model([{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] }]),
|
||||
target("@ai-sdk/amazon-bedrock", "us.anthropic.claude-opus-5"),
|
||||
)
|
||||
expect(Object.keys(result ?? {})).toEqual(["low", "medium", "high", "xhigh", "max"])
|
||||
expect(result?.high).toEqual({
|
||||
reasoningConfig: {
|
||||
type: "adaptive",
|
||||
maxReasoningEffort: "high",
|
||||
display: "summarized",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("combines effort with extended thinking for Claude Opus 4.5 on Bedrock", () => {
|
||||
expect(
|
||||
ProviderTransform.reasoningVariants(
|
||||
model([{ type: "effort", values: ["high"] }]),
|
||||
target("@ai-sdk/amazon-bedrock", "us.anthropic.claude-opus-4-5-20251101-v1:0"),
|
||||
),
|
||||
).toEqual({
|
||||
high: {
|
||||
reasoningConfig: {
|
||||
type: "enabled",
|
||||
budgetTokens: 16_000,
|
||||
maxReasoningEffort: "high",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("does not replace unsupported Anthropic Bedrock effort options with token budgets", () => {
|
||||
expect(
|
||||
ProviderTransform.reasoningVariants(
|
||||
@@ -4617,11 +4655,17 @@ describe("ProviderTransform.variants", () => {
|
||||
|
||||
describe("@ai-sdk/anthropic", () => {
|
||||
for (const testCase of [
|
||||
{
|
||||
name: "opus 4 dated",
|
||||
apiIds: ["claude-opus-4-20250514"],
|
||||
efforts: ["high", "max"],
|
||||
expectedHigh: { thinking: { type: "enabled", budgetTokens: 16000 } },
|
||||
},
|
||||
{
|
||||
name: "opus 4.5",
|
||||
apiIds: ["claude-opus-4-5-20251101", "claude-opus-4.5-20251101"],
|
||||
efforts: ["low", "medium", "high"],
|
||||
expectedHigh: { effort: "high" },
|
||||
expectedHigh: { thinking: { type: "enabled", budgetTokens: 16000 }, effort: "high" },
|
||||
},
|
||||
{
|
||||
name: "sonnet 4.6",
|
||||
@@ -4653,6 +4697,18 @@ describe("ProviderTransform.variants", () => {
|
||||
efforts: ["low", "medium", "high", "xhigh", "max"],
|
||||
expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
|
||||
},
|
||||
{
|
||||
name: "opus 5",
|
||||
apiIds: ["claude-opus-5", "claude-opus-5-20260724"],
|
||||
efforts: ["low", "medium", "high", "xhigh", "max"],
|
||||
expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
|
||||
},
|
||||
{
|
||||
name: "unversioned future model",
|
||||
apiIds: ["claude-future"],
|
||||
efforts: ["low", "medium", "high", "xhigh", "max"],
|
||||
expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
|
||||
},
|
||||
{
|
||||
name: "fable 5",
|
||||
apiIds: ["claude-fable-5"],
|
||||
@@ -4772,6 +4828,28 @@ describe("ProviderTransform.variants", () => {
|
||||
effort: "high",
|
||||
})
|
||||
})
|
||||
|
||||
test("opus 5 uses adaptive reasoning for Vertex model IDs", () => {
|
||||
const result = ProviderTransform.variants(
|
||||
createMockModel({
|
||||
id: "google-vertex-anthropic/claude-opus-5@default",
|
||||
providerID: "google-vertex-anthropic",
|
||||
api: {
|
||||
id: "claude-opus-5@default",
|
||||
url: "https://us-central1-aiplatform.googleapis.com",
|
||||
npm: "@ai-sdk/google-vertex/anthropic",
|
||||
},
|
||||
}),
|
||||
)
|
||||
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
|
||||
expect(result.high).toEqual({
|
||||
thinking: {
|
||||
type: "adaptive",
|
||||
display: "summarized",
|
||||
},
|
||||
effort: "high",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("@ai-sdk/amazon-bedrock", () => {
|
||||
@@ -4867,6 +4945,28 @@ describe("ProviderTransform.variants", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("anthropic opus 5 returns adaptive reasoning options with xhigh", () => {
|
||||
const result = ProviderTransform.variants(
|
||||
createMockModel({
|
||||
id: "bedrock/anthropic-claude-opus-5",
|
||||
providerID: "bedrock",
|
||||
api: {
|
||||
id: "us.anthropic.claude-opus-5-v1:0",
|
||||
url: "https://bedrock.amazonaws.com",
|
||||
npm: "@ai-sdk/amazon-bedrock",
|
||||
},
|
||||
}),
|
||||
)
|
||||
expect(Object.keys(result)).toEqual(["low", "medium", "high", "xhigh", "max"])
|
||||
expect(result.high).toEqual({
|
||||
reasoningConfig: {
|
||||
type: "adaptive",
|
||||
maxReasoningEffort: "high",
|
||||
display: "summarized",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("returns WIDELY_SUPPORTED_EFFORTS with reasoningConfig", () => {
|
||||
const model = createMockModel({
|
||||
id: "bedrock/llama-4",
|
||||
@@ -5055,6 +5155,12 @@ describe("ProviderTransform.variants", () => {
|
||||
efforts: ["low", "medium", "high", "xhigh", "max"],
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
},
|
||||
{
|
||||
name: "opus 5",
|
||||
apiIds: ["anthropic--claude-opus-5", "anthropic--claude-5-opus"],
|
||||
efforts: ["low", "medium", "high", "xhigh", "max"],
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
},
|
||||
]) {
|
||||
for (const apiId of testCase.apiIds) {
|
||||
test(`${testCase.name} ${apiId} returns adaptive thinking variants under modelParams`, () => {
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("reference HttpApi", () => {
|
||||
},
|
||||
{
|
||||
name: "effect",
|
||||
path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"),
|
||||
path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect@main"),
|
||||
source: {
|
||||
type: "git",
|
||||
repository: "Effect-TS/effect",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13.tgz",
|
||||
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-app",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-core",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/stats-server",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.18.4",
|
||||
"version": "1.18.5",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user