refactor(cli): inline acp one-off helpers

This commit is contained in:
Shoubhit Dash
2026-07-20 18:24:09 +05:30
parent 1669eac226
commit 7761ea2e4a
6 changed files with 84 additions and 122 deletions
+4 -8
View File
@@ -32,7 +32,10 @@ export function buildConfigOptions(input: {
modes?: readonly ConfigOptionMode[]
currentModeId?: string
}): SessionConfigOption[] {
const variants = variantsForModel(input.providers, input.currentModel)
const variants =
input.providers
.find((provider) => provider.id === input.currentModel.providerID)
?.models.find((model) => model.id === input.currentModel.modelID)?.variants ?? []
const effort =
variants.length > 0 ? buildEffortSelectOption({ variants, currentVariant: input.currentVariant }) : undefined
return [
@@ -121,13 +124,6 @@ export function formatVariantName(variant: string) {
.join(" ")
}
function variantsForModel(providers: readonly ConfigOptionProvider[], model: ModelSelection["model"]) {
return (
providers.find((provider) => provider.id === model.providerID)?.models.find((item) => item.id === model.modelID)
?.variants ?? []
)
}
function selectVariant(variant: string | undefined, variants: readonly string[]) {
if (variant && variants.includes(variant)) return variant
if (variants.includes(DEFAULT_VARIANT_VALUE)) return DEFAULT_VARIANT_VALUE
+25 -32
View File
@@ -1,4 +1,4 @@
import type { ContentBlock, ContentChunk, ResourceLink, Role } from "@agentclientprotocol/sdk"
import type { ContentBlock, ContentChunk, ResourceLink } from "@agentclientprotocol/sdk"
import path from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"
@@ -14,8 +14,16 @@ export function promptContentToParts(content: readonly ContentBlock[]): PromptPa
export function contentBlockToParts(block: ContentBlock): PromptPart[] {
switch (block.type) {
case "text":
return [{ type: "text", text: block.text, ...audienceFlags(block.annotations?.audience ?? undefined) }]
case "text": {
const audience = block.annotations?.audience
if (audience?.length === 1 && audience[0] === "assistant") {
return [{ type: "text", text: block.text, synthetic: true }]
}
if (audience?.length === 1 && audience[0] === "user") {
return [{ type: "text", text: block.text, ignored: true }]
}
return [{ type: "text", text: block.text }]
}
case "image":
if (block.data) {
return [
@@ -84,7 +92,8 @@ export function partsToContentChunks(parts: readonly ReplayPart[]): ContentChunk
content: {
type: "text",
text: part.text,
...replayAudience(part),
...(part.synthetic ? { annotations: { audience: ["assistant" as const] } } : {}),
...(!part.synthetic && part.ignored ? { annotations: { audience: ["user" as const] } } : {}),
},
},
]
@@ -105,15 +114,17 @@ export function partsToContentChunks(parts: readonly ReplayPart[]): ContentChunk
]
}
if (!part.url.startsWith("data:")) return []
const data = decodeDataUrl(part.url)
if (!data) return []
if (data.mime.startsWith("image/")) {
const match = /^data:([^;]+);base64,(.*)$/.exec(part.url)
if (!match?.[1] || match[2] === undefined) return []
const mime = match[1]
const data = match[2]
if (mime.startsWith("image/")) {
return [
{
content: {
type: "image",
mimeType: data.mime,
data: data.base64,
mimeType: mime,
data,
uri: pathToFileURL(part.filename ?? "image").href,
},
},
@@ -124,16 +135,16 @@ export function partsToContentChunks(parts: readonly ReplayPart[]): ContentChunk
content: {
type: "resource",
resource:
data.mime.startsWith("text/") || data.mime === "application/json"
mime.startsWith("text/") || mime === "application/json"
? {
uri: pathToFileURL(part.filename ?? "file").href,
mimeType: data.mime,
text: Buffer.from(data.base64, "base64").toString("utf8"),
mimeType: mime,
text: Buffer.from(data, "base64").toString("utf8"),
}
: {
uri: pathToFileURL(part.filename ?? "file").href,
mimeType: data.mime,
blob: data.base64,
mimeType: mime,
blob: data,
},
},
},
@@ -163,24 +174,6 @@ function resourceLinkToPart(link: ResourceLink): PromptPart {
return { type: "text", text: link.uri }
}
function decodeDataUrl(url: string): { readonly mime: string; readonly base64: string } | undefined {
const match = /^data:([^;]+);base64,(.*)$/.exec(url)
if (!match?.[1] || match[2] === undefined) return undefined
return { mime: match[1], base64: match[2] }
}
function audienceFlags(audience: readonly Role[] | null | undefined) {
if (audience?.length === 1 && audience[0] === "assistant") return { synthetic: true as const }
if (audience?.length === 1 && audience[0] === "user") return { ignored: true as const }
return {}
}
function replayAudience(part: Extract<PromptPart, { type: "text" }>) {
if (part.synthetic) return { annotations: { audience: ["assistant" as const] } }
if (part.ignored) return { annotations: { audience: ["user" as const] } }
return {}
}
function filenameFromUri(uri: string | undefined): string | undefined {
if (!uri || uri.startsWith("data:")) return undefined
if (URL.canParse(uri)) return path.basename(new URL(uri).pathname) || undefined
+6 -5
View File
@@ -311,7 +311,12 @@ async function replayMessage(
sessionId: sessionID,
update: {
sessionUpdate: "tool_call",
...pendingToolCall({ toolCallId: part.id, toolName: part.name, state: { input: toolInput(part) }, cwd }),
...pendingToolCall({
toolCallId: part.id,
toolName: part.name,
state: { input: part.state.status === "streaming" ? {} : part.state.input },
cwd,
}),
},
})
switch (part.state.status) {
@@ -376,10 +381,6 @@ function matchesStart(event: EventSubscribeOutput, start: TurnStart) {
return event.type === "session.skill.activated" && event.id === start.id.replace(/^msg_/, "evt_")
}
function toolInput(tool: Extract<SessionMessageAssistant["content"][number], { type: "tool" }>) {
return tool.state.status === "streaming" ? {} : tool.state.input
}
function response(
assistant: SessionMessageAssistant | undefined,
executionError: { readonly type: string; readonly message: string } | undefined,
+13 -27
View File
@@ -1,10 +1,4 @@
import type {
AgentSideConnection,
PermissionOption,
RequestPermissionResponse,
ToolCallContent,
ToolCallLocation,
} from "@agentclientprotocol/sdk"
import type { AgentSideConnection, PermissionOption, ToolCallContent, ToolCallLocation } from "@agentclientprotocol/sdk"
import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise"
import { Patch } from "@opencode-ai/core/patch"
import { isAbsolute, resolve } from "node:path"
@@ -47,10 +41,12 @@ export async function replyPermission(input: {
options,
})
.catch(() => undefined)
const selected = result?.outcome.outcome === "selected" ? result.outcome.optionId : undefined
const reply = selected === "once" || selected === "always" ? selected : "reject"
await input.client.permission.reply({
sessionID: input.sessionID,
requestID: input.event.data.id,
reply: selectedReply(result),
reply,
})
}
@@ -63,7 +59,15 @@ export async function syncEditedFiles(input: {
readonly structured: Readonly<Record<string, unknown>>
}) {
if (!input.connection.writeTextFile || toToolKind(input.toolName) !== "edit") return
const paths = editedPaths(input.toolInput, input.structured)
const files = Array.isArray(input.structured.files)
? input.structured.files.flatMap((file): string[] => {
if (!file || typeof file !== "object") return []
const path = Reflect.get(file, "file")
return typeof path === "string" ? [path] : []
})
: []
const path = filePath(input.toolInput)
const paths = [...new Set([...files, ...(path ? [path] : [])])]
await Promise.all(
paths.map(async (path) => {
const target = resolvePath(path, input.cwd)
@@ -161,18 +165,6 @@ function readText(path: string, cwd: string) {
.catch(() => "")
}
function editedPaths(input: ToolInput, structured: Readonly<Record<string, unknown>>) {
const files = Array.isArray(structured.files)
? structured.files.flatMap((file): string[] => {
if (!file || typeof file !== "object") return []
const path = Reflect.get(file, "file")
return typeof path === "string" ? [path] : []
})
: []
const path = filePath(input)
return [...new Set([...files, ...(path ? [path] : [])])]
}
function filePath(input: ToolInput) {
return stringValue(input.path) ?? stringValue(input.filePath) ?? stringValue(input.filepath)
}
@@ -181,12 +173,6 @@ function resolvePath(path: string, cwd: string) {
return isAbsolute(path) ? path : resolve(cwd, path)
}
function selectedReply(result: RequestPermissionResponse | undefined): "once" | "always" | "reject" {
if (result?.outcome.outcome !== "selected") return "reject"
if (result.outcome.optionId === "once" || result.outcome.optionId === "always") return result.outcome.optionId
return "reject"
}
function stringValue(value: unknown) {
return typeof value === "string" ? value : undefined
}
+12 -16
View File
@@ -127,7 +127,18 @@ export function make(input: { readonly client: OpenCodeClient; readonly connecti
}
sessions.set(session.id, state)
await registerMcpServers(input.client, registeredMcp, state, mcpServers)
await sendAvailableCommands(input.connection, state)
await input.connection.sessionUpdate({
sessionId: state.id,
update: {
sessionUpdate: "available_commands_update",
availableCommands: [
...state.catalog.commands,
...state.catalog.skills.filter(
(skill) => !state.catalog.commands.some((command) => command.name === skill.name),
),
].map((command) => ({ name: command.name, description: command.description ?? "" })),
},
})
return state
}
@@ -491,21 +502,6 @@ function stableStringify(value: unknown): string {
.join(",")}}`
}
async function sendAvailableCommands(connection: Connection, session: Attached) {
await connection.sessionUpdate({
sessionId: session.id,
update: {
sessionUpdate: "available_commands_update",
availableCommands: [
...session.catalog.commands,
...session.catalog.skills.filter(
(skill) => !session.catalog.commands.some((command) => command.name === skill.name),
),
].map((command) => ({ name: command.name, description: command.description ?? "" })),
},
})
}
async function sendUsageUpdate(client: OpenCodeClient, connection: Connection, session: Attached) {
const info = await client.session.get({ sessionID: session.id })
const model = session.catalog.models.find(
+24 -34
View File
@@ -103,31 +103,35 @@ export function completedToolUpdate(input: {
readonly structured: Readonly<Record<string, unknown>>
readonly result?: unknown
}): ToolCallUpdate {
return {
toolCallId: input.toolCallId,
status: "completed",
content: completedToolContent(input.toolName, input.input, input.content, input.structured),
rawOutput: {
structured: input.structured,
...(input.result === undefined ? {} : { result: input.result }),
},
}
}
function completedToolContent(
toolName: string,
input: ToolInput,
content: ToolContent,
structured: Readonly<Record<string, unknown>>,
): ToolCallContent[] {
const normalized = toolContent(content)
const read = toolName.toLocaleLowerCase() === "read" ? readDisplayText(structured) : undefined
const normalized = toolContent(input.content)
const read = input.toolName.toLocaleLowerCase() === "read" ? readDisplayText(input.structured) : undefined
const images = normalized.filter((part) => part.type === "content" && part.content.type === "image")
const primary =
read === undefined
? normalized.filter((part) => !images.includes(part))
: [{ type: "content" as const, content: { type: "text" as const, text: read } }]
return [...primary, ...diffContent(input), ...images]
const oldText = stringValue(input.input.oldString)
const newText = stringValue(input.input.newString)
const diff: ToolCallContent[] =
oldText === undefined || newText === undefined
? []
: [
{
type: "diff",
path: stringValue(input.input.path) ?? stringValue(input.input.filePath) ?? "",
oldText,
newText,
},
]
return {
toolCallId: input.toolCallId,
status: "completed",
content: [...primary, ...diff, ...images],
rawOutput: {
structured: input.structured,
...(input.result === undefined ? {} : { result: input.result }),
},
}
}
export function errorToolUpdate(input: {
@@ -160,20 +164,6 @@ function toolContent(content: ToolContent): ToolCallContent[] {
})
}
function diffContent(input: ToolInput): ToolCallContent[] {
const oldText = stringValue(input.oldString)
const newText = stringValue(input.newString)
if (oldText === undefined || newText === undefined) return []
return [
{
type: "diff",
path: stringValue(input.path) ?? stringValue(input.filePath) ?? "",
oldText,
newText,
},
]
}
function readDisplayText(structured: Readonly<Record<string, unknown>>) {
if (typeof structured.content === "string") {
if (structured.type === "text-page" || structured.encoding === "utf8") return structured.content