This commit is contained in:
Aiden Cline 2025-11-07 13:04:46 -06:00
parent ea461b06a3
commit 42cdc30ef2
4 changed files with 424 additions and 271 deletions

129
packages/opencode/dev.log Normal file
View file

@ -0,0 +1,129 @@
INFO 2025-11-07T19:00:19 +115ms service=default version=local args=["acp"] opencode
INFO 2025-11-07T19:00:19 +0ms service=project directory=/Users/aidencline/development/opencode2/packages/opencode fromDirectory
INFO 2025-11-07T19:00:20 +218ms service=config path=/Users/aidencline/.config/opencode/config.json loading
INFO 2025-11-07T19:00:20 +1ms service=config path=/Users/aidencline/.config/opencode/opencode.json loading
INFO 2025-11-07T19:00:20 +1ms service=config path=/Users/aidencline/.config/opencode/opencode.jsonc loading
INFO 2025-11-07T19:00:20 +10ms service=config path=/Users/aidencline/development/opencode2/opencode.json loading
INFO 2025-11-07T19:00:20 +9ms service=plugin path=opencode-openai-codex-auth loading plugin
INFO 2025-11-07T19:00:20 +27ms service=plugin path=file:///Users/aidencline/.config/opencode/plugin/plugin.ts loading plugin
INFO 2025-11-07T19:00:20 +1ms service=plugin path=opencode-copilot-auth@0.0.4 loading plugin
INFO 2025-11-07T19:00:20 +3ms service=plugin path=opencode-anthropic-auth@0.0.2 loading plugin
INFO 2025-11-07T19:00:20 +1ms service=bus type=* subscribing
INFO 2025-11-07T19:00:20 +0ms service=bus type=session.updated subscribing
INFO 2025-11-07T19:00:20 +0ms service=bus type=message.updated subscribing
INFO 2025-11-07T19:00:20 +0ms service=bus type=message.part.updated subscribing
INFO 2025-11-07T19:00:20 +0ms service=format init
INFO 2025-11-07T19:00:20 +0ms service=lsp serverIds=deno, typescript, vue, eslint, gopls, ruby-lsp, pyright, elixir-ls, zls, csharp, rust, clangd, svelte, astro, jdtls, lua-ls enabled LSP servers
INFO 2025-11-07T19:00:20 +1ms service=bus type=command.executed subscribing
INFO 2025-11-07T19:00:20 +12ms service=server method=GET path=/config request
INFO 2025-11-07T19:00:20 +1ms service=server duration=1 response
INFO 2025-11-07T19:00:20 +1ms service=acp-command setup connection
INFO 2025-11-07T19:00:20 +1ms service=acp-agent protocolVersion=1 initialize
INFO 2025-11-07T19:00:20 +3ms service=server method=POST path=/session request
INFO 2025-11-07T19:00:20 +0ms service=project directory=/Users/aidencline/development/opencode2 fromDirectory
INFO 2025-11-07T19:00:20 +54ms service=config path=/Users/aidencline/development/opencode2/opencode.json loading
INFO 2025-11-07T19:00:20 +1ms service=plugin path=opencode-openai-codex-auth loading plugin
INFO 2025-11-07T19:00:20 +1ms service=plugin path=file:///Users/aidencline/.config/opencode/plugin/plugin.ts loading plugin
INFO 2025-11-07T19:00:20 +0ms service=plugin path=opencode-copilot-auth@0.0.4 loading plugin
INFO 2025-11-07T19:00:20 +0ms service=plugin path=opencode-anthropic-auth@0.0.2 loading plugin
INFO 2025-11-07T19:00:20 +0ms service=bus type=* subscribing
INFO 2025-11-07T19:00:20 +0ms service=bus type=session.updated subscribing
INFO 2025-11-07T19:00:20 +0ms service=bus type=message.updated subscribing
INFO 2025-11-07T19:00:20 +0ms service=bus type=message.part.updated subscribing
INFO 2025-11-07T19:00:20 +0ms service=format init
INFO 2025-11-07T19:00:20 +0ms service=lsp serverIds=deno, typescript, vue, eslint, gopls, ruby-lsp, pyright, elixir-ls, zls, csharp, rust, clangd, svelte, astro, jdtls, lua-ls enabled LSP servers
INFO 2025-11-07T19:00:20 +0ms service=bus type=command.executed subscribing
INFO 2025-11-07T19:00:20 +1ms service=session id=ses_5a04f19a0ffeJKEIITzQLVT2lM version=local projectID=4b0ea68d7af9a6031a7ffda7ad66e0cb83315750 directory=/Users/aidencline/development/opencode2 title=ACP Session 82ad3f07-82f6-4ae4-9e02-a27305bbbecc time={"created":1762542020191,"updated":1762542020191} created
INFO 2025-11-07T19:00:20 +1ms service=bus type=session.created publishing
INFO 2025-11-07T19:00:20 +0ms service=bus type=session.updated publishing
INFO 2025-11-07T19:00:20 +0ms service=server duration=58 response
INFO 2025-11-07T19:00:20 +1ms service=acp-session-manager state={"id":"ses_5a04f19a0ffeJKEIITzQLVT2lM","cwd":"/Users/aidencline/development/opencode2","mcpServers":[],"createdAt":"2025-11-07T19:00:20.193Z","model":{"providerID":"opencode","modelID":"big-pickle"}} creating_session
INFO 2025-11-07T19:00:20 +0ms service=acp-agent sessionId=ses_5a04f19a0ffeJKEIITzQLVT2lM mcpServers=0 creating_session
INFO 2025-11-07T19:00:20 +2ms service=server method=GET path=/config/providers request
INFO 2025-11-07T19:00:20 +1ms service=models.dev file={} refreshing
INFO 2025-11-07T19:00:20 +2ms service=provider init
INFO 2025-11-07T19:00:20 +4ms service=provider providerID=azure found
INFO 2025-11-07T19:00:20 +0ms service=provider providerID=openai found
INFO 2025-11-07T19:00:20 +0ms service=provider providerID=openrouter found
INFO 2025-11-07T19:00:20 +0ms service=provider providerID=anthropic found
INFO 2025-11-07T19:00:20 +0ms service=provider providerID=opencode found
INFO 2025-11-07T19:00:20 +1ms service=provider providerID=github-copilot found
INFO 2025-11-07T19:00:20 +2ms service=server duration=9 response
INFO 2025-11-07T19:00:20 +2ms service=server method=GET path=/agent request
INFO 2025-11-07T19:00:20 +1ms service=server duration=1 response
INFO 2025-11-07T19:00:20 +0ms service=server method=GET path=/command request
INFO 2025-11-07T19:00:20 +0ms service=server duration=0 response
INFO 2025-11-07T19:00:20 +2ms service=server method=GET path=/event request
INFO 2025-11-07T19:00:20 +0ms service=server event connected
INFO 2025-11-07T19:00:20 +1ms service=bus type=* subscribing
INFO 2025-11-07T19:00:20 +0ms service=server duration=1 response
INFO 2025-11-07T19:00:21 +1183ms service=acp-agent parts=[{"type":"text","text":"Hello"}] parts
INFO 2025-11-07T19:00:21 +1ms service=server method=POST path=/session/ses_5a04f19a0ffeJKEIITzQLVT2lM/message request
INFO 2025-11-07T19:00:21 +2ms service=session.prompt session=ses_5a04f19a0ffeJKEIITzQLVT2lM prompt
INFO 2025-11-07T19:00:21 +0ms service=server duration=2 response
INFO 2025-11-07T19:00:21 +4ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:21 +1ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:21 +1ms service=bus type=session.updated publishing
INFO 2025-11-07T19:00:21 +0ms service=provider providerID=opencode modelID=big-pickle getModel
INFO 2025-11-07T19:00:21 +1ms service=provider status=started providerID=opencode getSDK
INFO 2025-11-07T19:00:21 +57ms service=provider status=completed duration=57 providerID=opencode getSDK
INFO 2025-11-07T19:00:21 +1ms service=provider providerID=opencode modelID=big-pickle found
INFO 2025-11-07T19:00:21 +0ms service=session.lock sessionID=ses_5a04f19a0ffeJKEIITzQLVT2lM locked
INFO 2025-11-07T19:00:21 +0ms service=session.prompt session=ses_5a04f19a0ffeJKEIITzQLVT2lM sessionID=ses_5a04f19a0ffeJKEIITzQLVT2lM locking
INFO 2025-11-07T19:00:21 +34ms service=mcp key=context7 type=remote found
INFO 2025-11-07T19:00:21 +0ms service=mcp key=gh_grep type=remote found
INFO 2025-11-07T19:00:21 +0ms service=mcp key=weather type=local found
INFO 2025-11-07T19:00:21 +348ms service=mcp key=context7 transport=StreamableHTTP connected
INFO 2025-11-07T19:00:22 +160ms service=mcp key=gh_grep transport=StreamableHTTP connected
INFO 2025-11-07T19:00:23 +1265ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:23 +5ms service=session.prompt session=ses_5a04f19a0ffeJKEIITzQLVT2lM process
INFO 2025-11-07T19:00:23 +6ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:23 +0ms service=provider providerID=opencode modelID=claude-haiku-4-5 getModel
INFO 2025-11-07T19:00:23 +1ms service=provider status=started providerID=opencode getSDK
INFO 2025-11-07T19:00:23 +0ms service=bus type=session.updated publishing
INFO 2025-11-07T19:00:23 +0ms service=bus type=session.diff publishing
INFO 2025-11-07T19:00:23 +5ms service=provider status=completed duration=5 providerID=opencode getSDK
INFO 2025-11-07T19:00:23 +0ms service=provider providerID=opencode modelID=claude-haiku-4-5 found
INFO 2025-11-07T19:00:24 +1455ms service=snapshot hash=af2dd1a43d3ed11688756bc848a1803307be9925
cwd=/Users/aidencline/development/opencode2 git=/Users/aidencline/.local/share/opencode/snapshot/4b0ea68d7af9a6031a7ffda7ad66e0cb83315750 tracking
INFO 2025-11-07T19:00:24 +1ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +62ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +47ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +50ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +1ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +46ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +1ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +34ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:24 +8ms service=session.summary title=Greeting title
INFO 2025-11-07T19:00:24 +0ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:25 +615ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:25 +0ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:25 +33ms service=snapshot hash=8fe0798d0105e13db6691dddbda02aa2facc14fb
cwd=/Users/aidencline/development/opencode2 git=/Users/aidencline/.local/share/opencode/snapshot/4b0ea68d7af9a6031a7ffda7ad66e0cb83315750 tracking
INFO 2025-11-07T19:00:25 +1ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:25 +0ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:25 +29ms service=bus type=message.part.updated publishing
INFO 2025-11-07T19:00:25 +0ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:25 +1ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:25 +0ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:25 +0ms service=session.compaction pruning
INFO 2025-11-07T19:00:25 +0ms service=session.lock sessionID=ses_5a04f19a0ffeJKEIITzQLVT2lM unlocked
INFO 2025-11-07T19:00:25 +0ms service=session.prompt session=ses_5a04f19a0ffeJKEIITzQLVT2lM sessionID=ses_5a04f19a0ffeJKEIITzQLVT2lM unlocking
INFO 2025-11-07T19:00:25 +1ms service=bus type=session.idle publishing
INFO 2025-11-07T19:00:25 +0ms service=server method=POST path=/tui/show-toast request
INFO 2025-11-07T19:00:25 +0ms service=bus type=tui.toast.show publishing
INFO 2025-11-07T19:00:25 +0ms service=server duration=0 response
INFO 2025-11-07T19:00:25 +2ms service=session.compaction pruned=0 total=0 found
INFO 2025-11-07T19:00:25 +22ms service=bus type=message.updated publishing
INFO 2025-11-07T19:00:25 +1ms service=bus type=session.updated publishing
INFO 2025-11-07T19:00:25 +1ms service=bus type=session.diff publishing
INFO 2025-11-07T19:00:27 +2162ms service=session.summary body=I greeted you and offered assistance with your development tasks. I was initiating a conversation to understand how I could best help you. body
INFO 2025-11-07T19:00:27 +1ms service=bus type=message.updated publishing

View file

@ -20,13 +20,11 @@ import {
} from "@agentclientprotocol/sdk"
import { Log } from "../util/log"
import { ACPSessionManager } from "./session"
import type { ACPConfig } from "./types"
import type { ACPConfig, ACPSessionState } from "./types"
import { Provider } from "../provider/provider"
import { Installation } from "@/installation"
import { Bus } from "@/bus"
import { MessageV2 } from "@/session/message-v2"
import { Storage } from "@/storage/storage"
import { Permission } from "@/permission"
import { Config } from "@/config/config"
import { MCP } from "@/mcp"
import { Todo } from "@/session/todo"
@ -59,270 +57,284 @@ export namespace ACP {
this.connection = connection
this.config = config
this.sdk = config.sdk
this.setupEventSubscriptions()
}
private setupEventSubscriptions() {
private setupEventSubscriptions(session: ACPSessionState) {
const sessionId = session.id
const directory = session.cwd
const options: PermissionOption[] = [
{ optionId: "once", kind: "allow_once", name: "Allow once" },
{ optionId: "always", kind: "allow_always", name: "Always allow" },
{ optionId: "reject", kind: "reject_once", name: "Reject" },
]
Bus.subscribe(Permission.Event.Updated, async (event) => {
const acpSession = this.sessionManager.get(event.properties.sessionID)
const directory = acpSession.cwd
try {
const permission = event.properties
const res = await this.connection
.requestPermission({
sessionId: acpSession.id,
toolCall: {
toolCallId: permission.callID ?? permission.id,
status: "pending",
title: permission.title,
rawInput: permission.metadata,
kind: toToolKind(permission.type),
locations: toLocations(permission.type, permission.metadata),
},
options,
})
.catch(async (error) => {
log.error("failed to request permission from ACP", {
error,
permissionID: permission.id,
sessionID: permission.sessionID,
})
await this.config.sdk.postSessionIdPermissionsPermissionId({
path: { id: permission.sessionID, permissionID: permission.id },
body: {
response: "reject",
},
query: { directory },
})
return
})
if (!res) return
if (res.outcome.outcome !== "selected") {
await this.config.sdk.postSessionIdPermissionsPermissionId({
path: { id: permission.sessionID, permissionID: permission.id },
body: {
response: "reject",
},
query: { directory },
})
return
}
await this.config.sdk.postSessionIdPermissionsPermissionId({
path: { id: permission.sessionID, permissionID: permission.id },
body: {
response: res.outcome.optionId as "once" | "always" | "reject",
},
query: { directory },
})
} catch (err) {
log.error("unexpected error when handling permission", { error: err })
throw err
}
})
Bus.subscribe(MessageV2.Event.PartUpdated, async (event) => {
const props = event.properties
const { part } = props
const acpSession = this.sessionManager.get(part.sessionID)
const directory = acpSession.cwd
const message = await this.config.sdk.session
.message({
throwOnError: true,
path: {
id: part.sessionID,
messageID: part.messageID,
},
query: { directory },
})
.then((x) => x.data)
.catch(() => undefined)
if (!message || message.info.role !== "assistant") return
if (part.type === "tool") {
switch (part.state.status) {
case "pending":
await this.connection
.sessionUpdate({
sessionId: acpSession.id,
update: {
sessionUpdate: "tool_call",
toolCallId: part.callID,
title: part.tool,
kind: toToolKind(part.tool),
status: "pending",
locations: [],
rawInput: {},
},
})
.catch((err) => {
log.error("failed to send tool pending to ACP", { error: err })
})
break
case "running":
await this.connection
.sessionUpdate({
sessionId: acpSession.id,
update: {
sessionUpdate: "tool_call_update",
toolCallId: part.callID,
status: "in_progress",
locations: toLocations(part.tool, part.state.input),
rawInput: part.state.input,
},
})
.catch((err) => {
log.error("failed to send tool in_progress to ACP", { error: err })
})
break
case "completed":
const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [
{
type: "content",
content: {
type: "text",
text: part.state.output,
},
},
]
if (kind === "edit") {
const input = part.state.input
const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
const newText =
typeof input["newString"] === "string"
? input["newString"]
: typeof input["content"] === "string"
? input["content"]
: ""
content.push({
type: "diff",
path: filePath,
oldText,
newText,
})
}
if (part.tool === "todowrite") {
const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
if (parsedTodos.success) {
await this.connection
.sessionUpdate({
sessionId: acpSession.id,
update: {
sessionUpdate: "plan",
entries: parsedTodos.data.map((todo) => {
const status: PlanEntry["status"] =
todo.status === "cancelled"
? "completed"
: (todo.status as PlanEntry["status"])
return {
priority: "medium",
status,
content: todo.content,
}
}),
this.config.sdk.event.subscribe().then(async (events) => {
for await (const event of events.stream) {
switch (event.type) {
case "permission.updated":
try {
const permission = event.properties
const res = await this.connection
.requestPermission({
sessionId,
toolCall: {
toolCallId: permission.callID ?? permission.id,
status: "pending",
title: permission.title,
rawInput: permission.metadata,
kind: toToolKind(permission.type),
locations: toLocations(permission.type, permission.metadata),
},
options,
})
.catch(async (error) => {
log.error("failed to request permission from ACP", {
error,
permissionID: permission.id,
sessionID: permission.sessionID,
})
await this.config.sdk.postSessionIdPermissionsPermissionId({
path: { id: permission.sessionID, permissionID: permission.id },
body: {
response: "reject",
},
query: { directory },
})
.catch((err) => {
log.error("failed to send session update for todo", { error: err })
})
} else {
log.error("failed to parse todo output", { error: parsedTodos.error })
return
})
if (!res) return
if (res.outcome.outcome !== "selected") {
await this.config.sdk.postSessionIdPermissionsPermissionId({
path: { id: permission.sessionID, permissionID: permission.id },
body: {
response: "reject",
},
query: { directory },
})
return
}
await this.config.sdk.postSessionIdPermissionsPermissionId({
path: { id: permission.sessionID, permissionID: permission.id },
body: {
response: res.outcome.optionId as "once" | "always" | "reject",
},
query: { directory },
})
} catch (err) {
log.error("unexpected error when handling permission", { error: err })
} finally {
break
}
await this.connection
.sessionUpdate({
sessionId: acpSession.id,
update: {
sessionUpdate: "tool_call_update",
toolCallId: part.callID,
status: "completed",
kind,
content,
title: part.state.title,
rawOutput: {
output: part.state.output,
metadata: part.state.metadata,
case "message.part.updated":
log.info("message part updated", { event: event.properties })
try {
const props = event.properties
const { part } = props
const message = await this.config.sdk.session
.message({
throwOnError: true,
path: {
id: part.sessionID,
messageID: part.messageID,
},
},
})
.catch((err) => {
log.error("failed to send tool completed to ACP", { error: err })
})
break
case "error":
await this.connection
.sessionUpdate({
sessionId: acpSession.id,
update: {
sessionUpdate: "tool_call_update",
toolCallId: part.callID,
status: "failed",
content: [
{
type: "content",
content: {
type: "text",
text: part.state.error,
query: { directory },
})
.then((x) => x.data)
.catch((err) => {
log.error("unexpected error when fetching message", { error: err })
return undefined
})
if (!message || message.info.role !== "assistant") return
if (part.type === "tool") {
switch (part.state.status) {
case "pending":
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "tool_call",
toolCallId: part.callID,
title: part.tool,
kind: toToolKind(part.tool),
status: "pending",
locations: [],
rawInput: {},
},
})
.catch((err) => {
log.error("failed to send tool pending to ACP", { error: err })
})
break
case "running":
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId: part.callID,
status: "in_progress",
locations: toLocations(part.tool, part.state.input),
rawInput: part.state.input,
},
})
.catch((err) => {
log.error("failed to send tool in_progress to ACP", { error: err })
})
break
case "completed":
const kind = toToolKind(part.tool)
const content: ToolCallContent[] = [
{
type: "content",
content: {
type: "text",
text: part.state.output,
},
},
},
],
rawOutput: {
error: part.state.error,
},
},
})
.catch((err) => {
log.error("failed to send tool error to ACP", { error: err })
})
break
}
} else if (part.type === "text") {
const delta = props.delta
if (delta && part.synthetic !== true) {
await this.connection
.sessionUpdate({
sessionId: acpSession.id,
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text: delta,
},
},
})
.catch((err) => {
log.error("failed to send text to ACP", { error: err })
})
}
} else if (part.type === "reasoning") {
const delta = props.delta
if (delta) {
await this.connection
.sessionUpdate({
sessionId: acpSession.id,
update: {
sessionUpdate: "agent_thought_chunk",
content: {
type: "text",
text: delta,
},
},
})
.catch((err) => {
log.error("failed to send reasoning to ACP", { error: err })
})
]
if (kind === "edit") {
const input = part.state.input
const filePath =
typeof input["filePath"] === "string" ? input["filePath"] : ""
const oldText =
typeof input["oldString"] === "string" ? input["oldString"] : ""
const newText =
typeof input["newString"] === "string"
? input["newString"]
: typeof input["content"] === "string"
? input["content"]
: ""
content.push({
type: "diff",
path: filePath,
oldText,
newText,
})
}
if (part.tool === "todowrite") {
const parsedTodos = z
.array(Todo.Info)
.safeParse(JSON.parse(part.state.output))
if (parsedTodos.success) {
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "plan",
entries: parsedTodos.data.map((todo) => {
const status: PlanEntry["status"] =
todo.status === "cancelled"
? "completed"
: (todo.status as PlanEntry["status"])
return {
priority: "medium",
status,
content: todo.content,
}
}),
},
})
.catch((err) => {
log.error("failed to send session update for todo", { error: err })
})
} else {
log.error("failed to parse todo output", { error: parsedTodos.error })
}
}
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId: part.callID,
status: "completed",
kind,
content,
title: part.state.title,
rawOutput: {
output: part.state.output,
metadata: part.state.metadata,
},
},
})
.catch((err) => {
log.error("failed to send tool completed to ACP", { error: err })
})
break
case "error":
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId: part.callID,
status: "failed",
content: [
{
type: "content",
content: {
type: "text",
text: part.state.error,
},
},
],
rawOutput: {
error: part.state.error,
},
},
})
.catch((err) => {
log.error("failed to send tool error to ACP", { error: err })
})
break
}
} else if (part.type === "text") {
const delta = props.delta
if (delta && part.synthetic !== true) {
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text: delta,
},
},
})
.catch((err) => {
log.error("failed to send text to ACP", { error: err })
})
}
} else if (part.type === "reasoning") {
const delta = props.delta
if (delta) {
await this.connection
.sessionUpdate({
sessionId,
update: {
sessionUpdate: "agent_thought_chunk",
content: {
type: "text",
text: delta,
},
},
})
.catch((err) => {
log.error("failed to send reasoning to ACP", { error: err })
})
}
}
} finally {
break
}
}
}
})
@ -378,24 +390,27 @@ export namespace ACP {
try {
const model = await defaultModel(this.config)
// Create session via SDK
const result = await this.sdk.session.create({
body: {
title: `ACP Session ${crypto.randomUUID()}`,
},
query: {
directory,
},
})
const session = await this.sdk.session
.create({
body: {
title: `ACP Session ${crypto.randomUUID()}`,
},
query: {
directory,
},
throwOnError: true,
})
.then((x) => x.data)
if (!result.data?.id) {
throw new Error("Failed to create session")
}
const sessionId = result.data.id
const sessionId = session.id
// Store ACP session state
await this.sessionManager.create(sessionId, params.cwd, params.mcpServers, model)
const state = await this.sessionManager.create(
sessionId,
params.cwd,
params.mcpServers,
model,
)
log.info("creating_session", { sessionId, mcpServers: params.mcpServers.length })
@ -405,6 +420,8 @@ export namespace ACP {
sessionId,
})
this.setupEventSubscriptions(state)
return {
sessionId,
models: load.models,

View file

@ -1,6 +1,9 @@
import { RequestError, type McpServer } from "@agentclientprotocol/sdk"
import { Provider } from "../provider/provider"
import type { ACPSessionState } from "./types"
import { Log } from "@/util/log"
const log = Log.create({ service: "acp-session-manager" })
export class ACPSessionManager {
private sessions = new Map<string, ACPSessionState>()
@ -20,6 +23,7 @@ export class ACPSessionManager {
createdAt: new Date(),
model: resolvedModel,
}
log.info("creating_session", { state })
this.sessions.set(sessionId, state)
return state
@ -27,8 +31,10 @@ export class ACPSessionManager {
get(sessionId: string) {
const session = this.sessions.get(sessionId)
if (!session)
if (!session) {
log.error("session not found", { sessionId })
throw RequestError.invalidParams(JSON.stringify({ error: `Session not found: ${sessionId}` }))
}
return session
}

View file

@ -59,8 +59,9 @@ export namespace Log {
cleanup(Global.Path.log)
if (options.print) return
logpath = path.join(
Global.Path.log,
options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
"dev.log",
// Global.Path.log,
// options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
)
const logfile = Bun.file(logpath)
await fs.truncate(logpath).catch(() => {})