mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
wip
This commit is contained in:
parent
ea461b06a3
commit
42cdc30ef2
4 changed files with 424 additions and 271 deletions
129
packages/opencode/dev.log
Normal file
129
packages/opencode/dev.log
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(() => {})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue