mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
wip
This commit is contained in:
parent
afb831c93c
commit
d4b7c4a024
4 changed files with 95 additions and 47 deletions
|
|
@ -22,7 +22,6 @@ import { Log } from "../util/log"
|
|||
import { ACPSessionManager } from "./session"
|
||||
import type { ACPConfig } from "./types"
|
||||
import { Provider } from "../provider/provider"
|
||||
import { SessionPrompt } from "../session/prompt"
|
||||
import { Installation } from "@/installation"
|
||||
import { SessionLock } from "@/session/lock"
|
||||
import { Bus } from "@/bus"
|
||||
|
|
@ -37,18 +36,19 @@ import { MCP } from "@/mcp"
|
|||
import { Todo } from "@/session/todo"
|
||||
import { z } from "zod"
|
||||
import { LoadAPIKeyError } from "ai"
|
||||
import type { OpencodeClient } from "@opencode-ai/sdk"
|
||||
|
||||
export namespace ACP {
|
||||
const log = Log.create({ service: "acp-agent" })
|
||||
|
||||
export async function init() {
|
||||
const model = await defaultModel({})
|
||||
export async function init({ sdk }: { sdk: OpencodeClient }) {
|
||||
const model = await defaultModel({ sdk })
|
||||
return {
|
||||
create: (connection: AgentSideConnection, config: ACPConfig) => {
|
||||
if (!config.defaultModel) {
|
||||
config.defaultModel = model
|
||||
create: (connection: AgentSideConnection, fullConfig: ACPConfig) => {
|
||||
if (!fullConfig.defaultModel) {
|
||||
fullConfig.defaultModel = model
|
||||
}
|
||||
return new Agent(connection, config)
|
||||
return new Agent(connection, fullConfig)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -57,10 +57,12 @@ export namespace ACP {
|
|||
private sessionManager = new ACPSessionManager()
|
||||
private connection: AgentSideConnection
|
||||
private config: ACPConfig
|
||||
private sdk: ACPConfig["sdk"]
|
||||
|
||||
constructor(connection: AgentSideConnection, config: ACPConfig = {}) {
|
||||
constructor(connection: AgentSideConnection, config: ACPConfig) {
|
||||
this.connection = connection
|
||||
this.config = config
|
||||
this.sdk = config.sdk
|
||||
this.setupEventSubscriptions()
|
||||
}
|
||||
|
||||
|
|
@ -366,17 +368,36 @@ export namespace ACP {
|
|||
async newSession(params: NewSessionRequest) {
|
||||
try {
|
||||
const model = await defaultModel(this.config)
|
||||
const session = await this.sessionManager.create(params.cwd, params.mcpServers, model)
|
||||
|
||||
log.info("creating_session", { mcpServers: params.mcpServers.length })
|
||||
// Create session via SDK
|
||||
const result = await this.sdk.session.create({
|
||||
body: {
|
||||
title: `ACP Session ${crypto.randomUUID()}`,
|
||||
},
|
||||
query: {
|
||||
directory: params.cwd,
|
||||
},
|
||||
})
|
||||
|
||||
if (!result.data?.id) {
|
||||
throw new Error("Failed to create session")
|
||||
}
|
||||
|
||||
const sessionId = result.data.id
|
||||
|
||||
// Store ACP session state
|
||||
await this.sessionManager.create(sessionId, params.cwd, params.mcpServers, model)
|
||||
|
||||
log.info("creating_session", { sessionId, mcpServers: params.mcpServers.length })
|
||||
|
||||
const load = await this.loadSession({
|
||||
cwd: params.cwd,
|
||||
mcpServers: params.mcpServers,
|
||||
sessionId: session.id,
|
||||
sessionId,
|
||||
})
|
||||
|
||||
return {
|
||||
sessionId: session.id,
|
||||
sessionId,
|
||||
models: load.models,
|
||||
modes: load.modes,
|
||||
_meta: {},
|
||||
|
|
@ -531,7 +552,10 @@ export namespace ACP {
|
|||
}
|
||||
const agent = acpSession.modeId ?? "build"
|
||||
|
||||
const parts: SessionPrompt.PromptInput["parts"] = []
|
||||
const parts: Array<
|
||||
| { type: "text"; text: string }
|
||||
| { type: "file"; url: string; filename: string; mime: string }
|
||||
> = []
|
||||
for (const part of params.prompt) {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
|
|
@ -545,12 +569,14 @@ export namespace ACP {
|
|||
parts.push({
|
||||
type: "file",
|
||||
url: `data:${part.mimeType};base64,${part.data}`,
|
||||
filename: "image",
|
||||
mime: part.mimeType,
|
||||
})
|
||||
} else if (part.uri && part.uri.startsWith("http:")) {
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: part.uri,
|
||||
filename: "image",
|
||||
mime: part.mimeType,
|
||||
})
|
||||
}
|
||||
|
|
@ -581,7 +607,7 @@ export namespace ACP {
|
|||
|
||||
const cmd = (() => {
|
||||
const text = parts
|
||||
.filter((p) => p.type === "text")
|
||||
.filter((p): p is { type: "text"; text: string } => p.type === "text")
|
||||
.map((p) => p.text)
|
||||
.join("")
|
||||
.trim()
|
||||
|
|
@ -598,26 +624,30 @@ export namespace ACP {
|
|||
}
|
||||
|
||||
if (!cmd) {
|
||||
await SessionPrompt.prompt({
|
||||
sessionID,
|
||||
model: {
|
||||
providerID: model.providerID,
|
||||
modelID: model.modelID,
|
||||
await this.sdk.session.prompt({
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
model: {
|
||||
providerID: model.providerID,
|
||||
modelID: model.modelID,
|
||||
},
|
||||
parts,
|
||||
agent,
|
||||
},
|
||||
parts,
|
||||
agent,
|
||||
})
|
||||
return done
|
||||
}
|
||||
|
||||
const command = await Command.get(cmd.name)
|
||||
if (command) {
|
||||
await SessionPrompt.command({
|
||||
sessionID,
|
||||
command: command.name,
|
||||
arguments: cmd.args,
|
||||
model: model.providerID + "/" + model.modelID,
|
||||
agent,
|
||||
await this.sdk.session.command({
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
command: command.name,
|
||||
arguments: cmd.args,
|
||||
model: model.providerID + "/" + model.modelID,
|
||||
agent,
|
||||
},
|
||||
})
|
||||
return done
|
||||
}
|
||||
|
|
@ -688,11 +718,14 @@ export namespace ACP {
|
|||
}
|
||||
|
||||
async function defaultModel(config: ACPConfig) {
|
||||
const sdk = config.sdk
|
||||
const configured = config.defaultModel
|
||||
if (configured) return configured
|
||||
|
||||
const model = await Config.get()
|
||||
.then((cfg) => {
|
||||
const model = await sdk.config
|
||||
.get({ throwOnError: true })
|
||||
.then((resp) => {
|
||||
const cfg = resp.data
|
||||
if (!cfg.model) return undefined
|
||||
const parsed = Provider.parseModel(cfg.model)
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import type { McpServer } from "@agentclientprotocol/sdk"
|
||||
import { Session } from "../session"
|
||||
import { Provider } from "../provider/provider"
|
||||
import type { ACPSessionState } from "./types"
|
||||
|
||||
|
|
@ -7,17 +6,15 @@ export class ACPSessionManager {
|
|||
private sessions = new Map<string, ACPSessionState>()
|
||||
|
||||
async create(
|
||||
sessionId: string,
|
||||
cwd: string,
|
||||
mcpServers: McpServer[],
|
||||
model?: ACPSessionState["model"],
|
||||
): Promise<ACPSessionState> {
|
||||
const session = await Session.create({ title: `ACP Session ${crypto.randomUUID()}` })
|
||||
const sessionId = session.id
|
||||
const resolvedModel = model ?? (await Provider.defaultModel())
|
||||
|
||||
const state: ACPSessionState = {
|
||||
id: sessionId,
|
||||
parentId: session.parentID,
|
||||
cwd,
|
||||
mcpServers,
|
||||
createdAt: new Date(),
|
||||
|
|
@ -33,10 +30,6 @@ export class ACPSessionManager {
|
|||
}
|
||||
|
||||
async remove(sessionId: string) {
|
||||
const state = this.sessions.get(sessionId)
|
||||
if (!state) return
|
||||
|
||||
await Session.remove(sessionId).catch(() => {})
|
||||
this.sessions.delete(sessionId)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { McpServer } from "@agentclientprotocol/sdk"
|
||||
import type { OpencodeClient } from "@opencode-ai/sdk"
|
||||
|
||||
export interface ACPSessionState {
|
||||
id: string
|
||||
parentId?: string
|
||||
cwd: string
|
||||
mcpServers: McpServer[]
|
||||
createdAt: Date
|
||||
|
|
@ -14,6 +14,7 @@ export interface ACPSessionState {
|
|||
}
|
||||
|
||||
export interface ACPConfig {
|
||||
sdk: OpencodeClient
|
||||
defaultModel?: {
|
||||
providerID: string
|
||||
modelID: string
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { bootstrap } from "../bootstrap"
|
|||
import { cmd } from "./cmd"
|
||||
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
|
||||
import { ACP } from "@/acp/agent"
|
||||
import { Server } from "@/server/server"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
|
||||
const log = Log.create({ service: "acp-command" })
|
||||
|
||||
|
|
@ -17,15 +19,34 @@ export const AcpCommand = cmd({
|
|||
command: "acp",
|
||||
describe: "Start ACP (Agent Client Protocol) server",
|
||||
builder: (yargs) => {
|
||||
return yargs.option("cwd", {
|
||||
describe: "working directory",
|
||||
type: "string",
|
||||
default: process.cwd(),
|
||||
})
|
||||
return yargs
|
||||
.option("cwd", {
|
||||
describe: "working directory",
|
||||
type: "string",
|
||||
default: process.cwd(),
|
||||
})
|
||||
.option("port", {
|
||||
type: "number",
|
||||
describe: "port to listen on",
|
||||
default: 0,
|
||||
})
|
||||
.option("hostname", {
|
||||
type: "string",
|
||||
describe: "hostname to listen on",
|
||||
default: "127.0.0.1",
|
||||
})
|
||||
},
|
||||
handler: async (opts) => {
|
||||
if (opts.cwd) process.chdir(opts["cwd"])
|
||||
handler: async (args) => {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
const server = Server.listen({
|
||||
port: args.port,
|
||||
hostname: args.hostname,
|
||||
})
|
||||
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: `http://${server.hostname}:${server.port}`,
|
||||
})
|
||||
|
||||
const input = new WritableStream<Uint8Array>({
|
||||
write(chunk) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
|
@ -50,10 +71,10 @@ export const AcpCommand = cmd({
|
|||
})
|
||||
|
||||
const stream = ndJsonStream(input, output)
|
||||
const agent = await ACP.init()
|
||||
const agent = await ACP.init({ sdk })
|
||||
|
||||
new AgentSideConnection((conn) => {
|
||||
return agent.create(conn, {})
|
||||
return agent.create(conn, { sdk })
|
||||
}, stream)
|
||||
|
||||
log.info("setup connection")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue