Compare commits

..
Author SHA1 Message Date
opencode d35cdffd2e release: v1.14.39 2026-05-05 10:57:40 +00:00
23 changed files with 50 additions and 196 deletions
+15 -15
View File
@@ -29,7 +29,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -85,7 +85,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -119,7 +119,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -146,7 +146,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@ai-sdk/anthropic": "3.0.64",
"@ai-sdk/openai": "3.0.48",
@@ -170,7 +170,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -194,7 +194,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "1.14.38",
"version": "1.14.39",
"bin": {
"opencode": "./bin/opencode",
},
@@ -228,7 +228,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -274,7 +274,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -303,7 +303,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -319,7 +319,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.14.38",
"version": "1.14.39",
"bin": {
"opencode": "./bin/opencode",
},
@@ -461,7 +461,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"effect": "catalog:",
@@ -496,7 +496,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -511,7 +511,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -546,7 +546,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -595,7 +595,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.14.38",
"version": "1.14.39",
"description": "",
"type": "module",
"exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.14.38",
"version": "1.14.39",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.14.38",
"version": "1.14.39",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.14.38",
"version": "1.14.39",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.14.38",
"version": "1.14.39",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.38",
"version": "1.14.39",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.14.38",
"version": "1.14.39",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.14.38",
"version": "1.14.39",
"private": true,
"type": "module",
"license": "MIT",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.14.38"
version = "1.14.39"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.38/opencode-darwin-arm64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.39/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.38/opencode-darwin-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.39/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.38/opencode-linux-arm64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.39/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.38/opencode-linux-x64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.39/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.38/opencode-windows-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.39/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.14.38",
"version": "1.14.39",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.38",
"version": "1.14.39",
"name": "opencode",
"type": "module",
"license": "MIT",
+8 -15
View File
@@ -122,7 +122,7 @@ function normalizeMessages(
})
}
if (["@ai-sdk/anthropic", "@ai-sdk/google-vertex/anthropic"].includes(model.api.npm)) {
// Anthropic rejects assistant turns where client tool_use blocks are followed by non-tool
// Anthropic rejects assistant turns where tool_use blocks are followed by non-tool
// content, e.g. [tool_use, tool_use, text], with:
// `tool_use` ids were found without `tool_result` blocks immediately after...
//
@@ -130,26 +130,19 @@ function normalizeMessages(
// assistant messages are later merged by the provider/SDK, so preserving the
// original [tool_use...] then [text] order still produces the invalid payload.
//
// Provider-executed tools are different: the AI SDK intentionally represents
// their tool-call and tool-result together in assistant content.
// The root cause appears to be somewhere upstream where the stream is originally
// processed. We were unable to locate an exact narrower reproduction elsewhere,
// so we keep this transform in place for the time being.
msgs = msgs.flatMap((msg) => {
if (msg.role !== "assistant" || !Array.isArray(msg.content)) return [msg]
const parts = msg.content
const providerExecuted = new Set(
parts.flatMap((part) => (part.type === "tool-call" && part.providerExecuted === true ? [part.toolCallId] : [])),
)
const isClientToolPart = (part: (typeof parts)[number]) => {
if (part.type === "tool-call") return part.providerExecuted !== true
if (part.type === "tool-result") return !providerExecuted.has(part.toolCallId)
return false
}
const first = parts.findIndex((part) => part.type === "tool-call" && part.providerExecuted !== true)
const first = parts.findIndex((part) => part.type === "tool-call")
if (first === -1) return [msg]
if (!parts.slice(first).some((part) => !isClientToolPart(part))) return [msg]
if (!parts.slice(first).some((part) => part.type !== "tool-call")) return [msg]
return [
{ ...msg, content: parts.filter((part) => !isClientToolPart(part)) },
{ ...msg, content: parts.filter(isClientToolPart) },
{ ...msg, content: parts.filter((part) => part.type !== "tool-call") },
{ ...msg, content: parts.filter((part) => part.type === "tool-call") },
]
})
}
@@ -1104,32 +1104,6 @@ export function filterCompacted(msgs: Iterable<WithParts>) {
completed.add(msg.info.parentID)
}
result.reverse()
const compactionIndex = result.findLastIndex(
(msg) =>
msg.info.role === "user" &&
msg.parts.some((item): item is CompactionPart => item.type === "compaction" && item.tail_start_id !== undefined),
)
const compaction = result[compactionIndex]
const part = compaction?.parts.find(
(item): item is CompactionPart => item.type === "compaction" && item.tail_start_id !== undefined,
)
const summaryIndex = compaction
? result.findIndex(
(msg, index) =>
index > compactionIndex &&
msg.info.role === "assistant" &&
msg.info.summary &&
msg.info.parentID === compaction.info.id,
)
: -1
const tailIndex = part?.tail_start_id ? result.findIndex((msg) => msg.info.id === part.tail_start_id) : -1
if (tailIndex >= 0 && tailIndex < compactionIndex && summaryIndex > compactionIndex) {
return [
...result.slice(compactionIndex, summaryIndex + 1),
...result.slice(tailIndex, compactionIndex),
...result.slice(summaryIndex + 1),
]
}
return result
}
@@ -1498,117 +1498,6 @@ describe("ProviderTransform.message - anthropic empty content filtering", () =>
])
})
test("keeps tool-call and tool-result paired when splitting anthropic messages", () => {
const msgs = [
{
role: "assistant",
content: [
{ type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
{ type: "tool-result", toolCallId: "toolu_1", toolName: "read", output: { type: "text", value: "ok" } },
{ type: "text", text: "I checked your home directory." },
],
},
] as any[]
const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[]
expect(result).toHaveLength(2)
expect(result[0]).toMatchObject({
role: "assistant",
content: [{ type: "text", text: "I checked your home directory." }],
})
expect(result[1]).toMatchObject({
role: "assistant",
content: [
{ type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
{ type: "tool-result", toolCallId: "toolu_1", toolName: "read", output: { type: "text", value: "ok" } },
],
})
})
test("leaves provider-executed anthropic tool results with trailing text unchanged", () => {
const msgs = [
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "srvtoolu_1",
toolName: "code_execution",
input: { code: "print('ok')", type: "programmatic-tool-call" },
providerExecuted: true,
},
{
type: "tool-result",
toolCallId: "srvtoolu_1",
toolName: "code_execution",
output: {
type: "json",
value: { type: "code_execution_result", stdout: "ok", stderr: "", return_code: 0 },
},
},
{ type: "text", text: "The code ran successfully." },
],
},
] as any[]
const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[]
expect(result).toHaveLength(1)
expect(result[0].content).toMatchObject(msgs[0].content)
})
test("keeps provider-executed pairs together when splitting mixed anthropic tool parts", () => {
const msgs = [
{
role: "assistant",
content: [
{ type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
{
type: "tool-call",
toolCallId: "srvtoolu_1",
toolName: "code_execution",
input: { code: "print('ok')", type: "programmatic-tool-call" },
providerExecuted: true,
},
{
type: "tool-result",
toolCallId: "srvtoolu_1",
toolName: "code_execution",
output: {
type: "json",
value: { type: "code_execution_result", stdout: "ok", stderr: "", return_code: 0 },
},
},
{ type: "text", text: "The server-side tool ran successfully." },
],
},
] as any[]
const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[]
expect(result).toHaveLength(2)
expect(result[0].content).toMatchObject([
{
type: "tool-call",
toolCallId: "srvtoolu_1",
toolName: "code_execution",
input: { code: "print('ok')", type: "programmatic-tool-call" },
providerExecuted: true,
},
{
type: "tool-result",
toolCallId: "srvtoolu_1",
toolName: "code_execution",
output: { type: "json", value: { type: "code_execution_result", stdout: "ok", stderr: "", return_code: 0 } },
},
{ type: "text", text: "The server-side tool ran successfully." },
])
expect(result[1].content).toMatchObject([
{ type: "tool-call", toolCallId: "toolu_1", toolName: "read", input: { filePath: "/root" } },
])
})
test("splits vertex anthropic assistant messages when text trails tool calls", () => {
const model = {
...anthropicModel,
@@ -1218,9 +1218,7 @@ describe("session.compaction.process", () => {
expect(captured).not.toContain("keep tail")
const filtered = MessageV2.filterCompacted(MessageV2.stream(session.id))
expect(filtered.map((msg) => msg.info.id).slice(0, 3)).toEqual([parent!, expect.any(String), keep.id])
expect(filtered[1]?.info.role).toBe("assistant")
expect(filtered[1]?.info.role === "assistant" ? filtered[1].info.summary : false).toBe(true)
expect(filtered[0]?.info.id).toBe(keep.id)
expect(filtered.map((msg) => msg.info.id)).not.toContain(large.id)
} finally {
await rt.dispose()
@@ -834,7 +834,7 @@ describe("MessageV2.filterCompacted", () => {
const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
expect(result.map((item) => item.info.id)).toEqual([c1, s1, u2, a2, u3, a3])
expect(result.map((item) => item.info.id)).toEqual([u2, a2, c1, s1, u3, a3])
await svc.remove(session.id)
},
@@ -889,7 +889,7 @@ describe("MessageV2.filterCompacted", () => {
})
const parentFiltered = MessageV2.filterCompacted(MessageV2.stream(session.id))
expect(parentFiltered.map((item) => item.info.id)).toEqual([c1, s1, u2, a2, u3, a3])
expect(parentFiltered.map((item) => item.info.id)).toEqual([u2, a2, c1, s1, u3, a3])
const forked = await svc.fork({ sessionID: session.id })
const childFiltered = MessageV2.filterCompacted(MessageV2.stream(forked.id))
@@ -964,7 +964,7 @@ describe("MessageV2.filterCompacted", () => {
const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
expect(result.map((item) => item.info.id)).toEqual([c1, s1, a3, u3, a4])
expect(result.map((item) => item.info.id)).toEqual([a3, c1, s1, u3, a4])
await svc.remove(session.id)
},
@@ -1041,7 +1041,7 @@ describe("MessageV2.filterCompacted", () => {
const result = MessageV2.filterCompacted(MessageV2.stream(session.id))
expect(result.map((item) => item.info.id)).toEqual([c2, s2, u3, a3, u4, a4])
expect(result.map((item) => item.info.id)).toEqual([u3, a3, c2, s2, u4, a4])
await svc.remove(session.id)
},
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.14.38",
"version": "1.14.39",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.14.38",
"version": "1.14.39",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.14.38",
"version": "1.14.39",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.14.38",
"version": "1.14.39",
"type": "module",
"license": "MIT",
"exports": {
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
"version": "1.14.38",
"version": "1.14.39",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.14.38",
"version": "1.14.39",
"publisher": "sst-dev",
"repository": {
"type": "git",