|
@@ -30,7 +30,7 @@ import {
|
|
|
type SetSessionModeResponse,
|
|
type SetSessionModeResponse,
|
|
|
} from "@agentclientprotocol/sdk"
|
|
} from "@agentclientprotocol/sdk"
|
|
|
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|
|
-import type { Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
|
|
|
|
|
|
+import type { AssistantMessage, Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
|
|
import { Context, Effect, Layer, ManagedRuntime } from "effect"
|
|
import { Context, Effect, Layer, ManagedRuntime } from "effect"
|
|
|
import * as ACPError from "./error"
|
|
import * as ACPError from "./error"
|
|
|
import { buildConfigOptions, parseModelSelection } from "./config-option"
|
|
import { buildConfigOptions, parseModelSelection } from "./config-option"
|
|
@@ -521,7 +521,7 @@ export function make(input: {
|
|
|
"session",
|
|
"session",
|
|
|
)
|
|
)
|
|
|
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
|
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
|
|
- return promptResponse(response.info, params.messageId)
|
|
|
|
|
|
|
+ return yield* promptResponse(response.info, params.messageId)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const known = snapshot.availableCommands.find((item) => item.name === command.name)
|
|
const known = snapshot.availableCommands.find((item) => item.name === command.name)
|
|
@@ -543,7 +543,7 @@ export function make(input: {
|
|
|
"session",
|
|
"session",
|
|
|
)
|
|
)
|
|
|
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
|
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
|
|
- return promptResponse(response.info, params.messageId)
|
|
|
|
|
|
|
+ return yield* promptResponse(response.info, params.messageId)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (command.name === "compact") {
|
|
if (command.name === "compact") {
|
|
@@ -563,7 +563,7 @@ export function make(input: {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
|
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
|
|
- return promptResponse(undefined, params.messageId)
|
|
|
|
|
|
|
+ return yield* promptResponse(undefined, params.messageId)
|
|
|
}),
|
|
}),
|
|
|
cancel,
|
|
cancel,
|
|
|
}
|
|
}
|
|
@@ -695,7 +695,8 @@ type MessageInfo = {
|
|
|
readonly agent?: Message["agent"]
|
|
readonly agent?: Message["agent"]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-type AssistantInfo = UsageService.AssistantTokenCost | undefined
|
|
|
|
|
|
|
+type AssistantError = NonNullable<AssistantMessage["error"]>
|
|
|
|
|
+type AssistantInfo = (UsageService.AssistantTokenCost & Pick<AssistantMessage, "error">) | undefined
|
|
|
|
|
|
|
|
function request<T>(fn: () => Promise<T | SdkResponse<T>>, service?: string) {
|
|
function request<T>(fn: () => Promise<T | SdkResponse<T>>, service?: string) {
|
|
|
return Effect.tryPromise({
|
|
return Effect.tryPromise({
|
|
@@ -811,13 +812,60 @@ function detectSlashCommand(parts: ReturnType<typeof promptContentToParts>) {
|
|
|
return { name, args: rest.join(" ").trim() }
|
|
return { name, args: rest.join(" ").trim() }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function promptResponse(info: AssistantInfo, messageId: string | null | undefined): PromptResponse {
|
|
|
|
|
- return {
|
|
|
|
|
- stopReason: "end_turn",
|
|
|
|
|
- ...(info ? { usage: UsageService.buildUsage(info) } : {}),
|
|
|
|
|
|
|
+const promptResponse = Effect.fn("ACP.promptResponse")(function* (
|
|
|
|
|
+ info: AssistantInfo,
|
|
|
|
|
+ messageId: string | null | undefined,
|
|
|
|
|
+) {
|
|
|
|
|
+ if (!info?.error) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ stopReason: "end_turn" as const,
|
|
|
|
|
+ ...(info ? { usage: UsageService.buildUsage(info) } : {}),
|
|
|
|
|
+ ...(messageId ? { userMessageId: messageId } : {}),
|
|
|
|
|
+ _meta: {},
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const base = {
|
|
|
|
|
+ usage: UsageService.buildUsage(info),
|
|
|
...(messageId ? { userMessageId: messageId } : {}),
|
|
...(messageId ? { userMessageId: messageId } : {}),
|
|
|
_meta: {},
|
|
_meta: {},
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if (info.error.name === "MessageAbortedError") {
|
|
|
|
|
+ return {
|
|
|
|
|
+ stopReason: "cancelled" as const,
|
|
|
|
|
+ ...base,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (info.error.name === "MessageOutputLengthError") {
|
|
|
|
|
+ return {
|
|
|
|
|
+ stopReason: "max_tokens" as const,
|
|
|
|
|
+ ...base,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (info.error.name === "ContentFilterError") {
|
|
|
|
|
+ return {
|
|
|
|
|
+ stopReason: "refusal" as const,
|
|
|
|
|
+ ...base,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (info.error.name === "ProviderAuthError") {
|
|
|
|
|
+ return yield* new ACPError.AuthRequiredError({ providerId: info.error.data.providerID })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return yield* new ACPError.ServiceFailureError({
|
|
|
|
|
+ service: "session",
|
|
|
|
|
+ safeMessage: promptErrorMessage(info.error),
|
|
|
|
|
+ errorName: info.error.name,
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function promptErrorMessage(error: AssistantError) {
|
|
|
|
|
+ if ("message" in error.data && typeof error.data.message === "string") return error.data.message
|
|
|
|
|
+ return "OpenCode prompt failed"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function sendUsageUpdate(
|
|
function sendUsageUpdate(
|