Lifei/delete tauri backend acp (#8582)
Co-authored-by: Jack Amadeo <jackamadeo@squareup.com>
This commit is contained in:
+49
-122
@@ -1,5 +1,4 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { USE_DIRECT_ACP } from "./acpFeatureFlag";
|
||||
import type { ContentBlock } from "@agentclientprotocol/sdk";
|
||||
import * as directAcp from "./acpApi";
|
||||
import * as sessionTracker from "./acpSessionTracker";
|
||||
import {
|
||||
@@ -15,7 +14,6 @@ export interface AcpProvider {
|
||||
|
||||
export interface AcpSendMessageOptions {
|
||||
systemPrompt?: string;
|
||||
workingDir?: string;
|
||||
personaId?: string;
|
||||
personaName?: string;
|
||||
/** Image attachments as [base64Data, mimeType] pairs. */
|
||||
@@ -29,63 +27,40 @@ export interface AcpPrepareSessionOptions {
|
||||
|
||||
/** Discover ACP providers installed on the system. */
|
||||
export async function discoverAcpProviders(): Promise<AcpProvider[]> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
return directAcp.listProviders();
|
||||
}
|
||||
return invoke("discover_acp_providers");
|
||||
return directAcp.listProviders();
|
||||
}
|
||||
|
||||
/** Send a message to an ACP agent. Response streams via Tauri events. */
|
||||
export async function acpSendMessage(
|
||||
sessionId: string,
|
||||
providerId: string,
|
||||
prompt: string,
|
||||
options: AcpSendMessageOptions = {},
|
||||
): Promise<void> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
const { systemPrompt, personaId, images } = options;
|
||||
const { systemPrompt, personaId, images } = options;
|
||||
|
||||
const gooseSessionId = sessionTracker.getGooseSessionId(
|
||||
sessionId,
|
||||
personaId,
|
||||
);
|
||||
if (!gooseSessionId) {
|
||||
throw new Error("Session not prepared. Call acpPrepareSession first.");
|
||||
}
|
||||
|
||||
const hasSystem = systemPrompt && systemPrompt.trim().length > 0;
|
||||
const effectivePrompt = hasSystem
|
||||
? `<persona-instructions>\n${systemPrompt}\n</persona-instructions>\n\n<user-message>\n${prompt}\n</user-message>`
|
||||
: prompt;
|
||||
|
||||
const content: import("@agentclientprotocol/sdk").ContentBlock[] = [
|
||||
{ type: "text", text: effectivePrompt },
|
||||
];
|
||||
if (images) {
|
||||
for (const [data, mimeType] of images) {
|
||||
content.push({ type: "image", data, mimeType } as any);
|
||||
}
|
||||
}
|
||||
|
||||
const messageId = crypto.randomUUID();
|
||||
setActiveMessageId(gooseSessionId, messageId);
|
||||
|
||||
await directAcp.prompt(gooseSessionId, content);
|
||||
|
||||
clearActiveMessageId(gooseSessionId);
|
||||
return;
|
||||
const gooseSessionId = sessionTracker.getGooseSessionId(sessionId, personaId);
|
||||
if (!gooseSessionId) {
|
||||
throw new Error("Session not prepared. Call acpPrepareSession first.");
|
||||
}
|
||||
const { systemPrompt, workingDir, personaId, personaName, images } = options;
|
||||
return invoke("acp_send_message", {
|
||||
sessionId,
|
||||
providerId,
|
||||
prompt,
|
||||
systemPrompt: systemPrompt ?? null,
|
||||
workingDir: workingDir ?? null,
|
||||
personaId: personaId ?? null,
|
||||
personaName: personaName ?? null,
|
||||
images: images ?? [],
|
||||
});
|
||||
|
||||
const hasSystem = systemPrompt && systemPrompt.trim().length > 0;
|
||||
const effectivePrompt = hasSystem
|
||||
? `<persona-instructions>\n${systemPrompt}\n</persona-instructions>\n\n<user-message>\n${prompt}\n</user-message>`
|
||||
: prompt;
|
||||
|
||||
const content: ContentBlock[] = [{ type: "text", text: effectivePrompt }];
|
||||
if (images) {
|
||||
for (const [data, mimeType] of images) {
|
||||
content.push({ type: "image", data, mimeType } as ContentBlock);
|
||||
}
|
||||
}
|
||||
|
||||
const messageId = crypto.randomUUID();
|
||||
setActiveMessageId(gooseSessionId, messageId);
|
||||
|
||||
await directAcp.prompt(gooseSessionId, content);
|
||||
|
||||
clearActiveMessageId(gooseSessionId);
|
||||
}
|
||||
|
||||
/** Prepare or warm an ACP session ahead of the first prompt. */
|
||||
@@ -94,37 +69,21 @@ export async function acpPrepareSession(
|
||||
providerId: string,
|
||||
options: AcpPrepareSessionOptions = {},
|
||||
): Promise<void> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
const workingDir = options.workingDir ?? "~/.goose/artifacts";
|
||||
await sessionTracker.prepareSession(
|
||||
sessionId,
|
||||
providerId,
|
||||
workingDir,
|
||||
options.personaId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { workingDir, personaId } = options;
|
||||
return invoke("acp_prepare_session", {
|
||||
const workingDir = options.workingDir ?? "~/.goose/artifacts";
|
||||
await sessionTracker.prepareSession(
|
||||
sessionId,
|
||||
providerId,
|
||||
workingDir: workingDir ?? null,
|
||||
personaId: personaId ?? null,
|
||||
});
|
||||
workingDir,
|
||||
options.personaId,
|
||||
);
|
||||
}
|
||||
|
||||
export async function acpSetModel(
|
||||
sessionId: string,
|
||||
modelId: string,
|
||||
): Promise<void> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
const gooseSessionId = sessionTracker.getGooseSessionId(sessionId);
|
||||
return directAcp.setModel(gooseSessionId ?? sessionId, modelId);
|
||||
}
|
||||
return invoke("acp_set_model", {
|
||||
sessionId,
|
||||
modelId,
|
||||
});
|
||||
const gooseSessionId = sessionTracker.getGooseSessionId(sessionId);
|
||||
return directAcp.setModel(gooseSessionId ?? sessionId, modelId);
|
||||
}
|
||||
|
||||
/** Session info returned by the goose binary's list_sessions. */
|
||||
@@ -145,77 +104,54 @@ export interface AcpSessionSearchResult {
|
||||
|
||||
/** List all sessions known to the goose binary. */
|
||||
export async function acpListSessions(): Promise<AcpSessionInfo[]> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
return directAcp.listSessions();
|
||||
}
|
||||
return invoke("acp_list_sessions");
|
||||
return directAcp.listSessions();
|
||||
}
|
||||
|
||||
export async function acpSearchSessions(
|
||||
query: string,
|
||||
sessionIds: string[],
|
||||
): Promise<AcpSessionSearchResult[]> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
return searchSessionsViaExports(query, sessionIds);
|
||||
}
|
||||
return invoke("acp_search_sessions", { query, sessionIds });
|
||||
return searchSessionsViaExports(query, sessionIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an existing session from the goose binary.
|
||||
*
|
||||
* This triggers message replay via SessionNotification events that the
|
||||
* frontend's useAcpStream hook picks up automatically.
|
||||
* notification handler picks up automatically.
|
||||
*/
|
||||
export async function acpLoadSession(
|
||||
sessionId: string,
|
||||
gooseSessionId: string,
|
||||
workingDir?: string,
|
||||
): Promise<void> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
const effectiveWorkingDir = workingDir ?? "~/.goose/artifacts";
|
||||
await directAcp.loadSession(gooseSessionId, effectiveWorkingDir);
|
||||
sessionTracker.registerSession(
|
||||
sessionId,
|
||||
gooseSessionId,
|
||||
"goose",
|
||||
effectiveWorkingDir,
|
||||
);
|
||||
return;
|
||||
}
|
||||
return invoke("acp_load_session", {
|
||||
const effectiveWorkingDir = workingDir ?? "~/.goose/artifacts";
|
||||
await directAcp.loadSession(gooseSessionId, effectiveWorkingDir);
|
||||
sessionTracker.registerSession(
|
||||
sessionId,
|
||||
gooseSessionId,
|
||||
workingDir: workingDir ?? null,
|
||||
});
|
||||
"goose",
|
||||
effectiveWorkingDir,
|
||||
);
|
||||
}
|
||||
|
||||
/** Export a session as JSON via the goose binary. */
|
||||
export async function acpExportSession(sessionId: string): Promise<string> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
return directAcp.exportSession(sessionId);
|
||||
}
|
||||
return invoke("acp_export_session", { sessionId });
|
||||
return directAcp.exportSession(sessionId);
|
||||
}
|
||||
|
||||
/** Import a session from JSON via the goose binary. Returns new session metadata. */
|
||||
export async function acpImportSession(json: string): Promise<AcpSessionInfo> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
return directAcp.importSession(json);
|
||||
}
|
||||
return invoke("acp_import_session", { json });
|
||||
return directAcp.importSession(json);
|
||||
}
|
||||
|
||||
/** Duplicate (fork) a session via the goose binary. Returns new session metadata. */
|
||||
export async function acpDuplicateSession(
|
||||
sessionId: string,
|
||||
): Promise<AcpSessionInfo> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
const gooseSessionId =
|
||||
sessionTracker.getGooseSessionId(sessionId) ?? sessionId;
|
||||
return directAcp.forkSession(gooseSessionId);
|
||||
}
|
||||
return invoke("acp_duplicate_session", { sessionId });
|
||||
const gooseSessionId =
|
||||
sessionTracker.getGooseSessionId(sessionId) ?? sessionId;
|
||||
return directAcp.forkSession(gooseSessionId);
|
||||
}
|
||||
|
||||
/** Cancel an in-progress ACP session so the backend stops streaming. */
|
||||
@@ -223,16 +159,7 @@ export async function acpCancelSession(
|
||||
sessionId: string,
|
||||
personaId?: string,
|
||||
): Promise<boolean> {
|
||||
if (USE_DIRECT_ACP) {
|
||||
const gooseSessionId = sessionTracker.getGooseSessionId(
|
||||
sessionId,
|
||||
personaId,
|
||||
);
|
||||
await directAcp.cancelSession(gooseSessionId ?? sessionId);
|
||||
return true;
|
||||
}
|
||||
return invoke("acp_cancel_session", {
|
||||
sessionId,
|
||||
personaId: personaId ?? null,
|
||||
});
|
||||
const gooseSessionId = sessionTracker.getGooseSessionId(sessionId, personaId);
|
||||
await directAcp.cancelSession(gooseSessionId ?? sessionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,28 +23,40 @@ const DEPRECATED_PROVIDER_IDS = new Set(["claude-code", "codex", "gemini-cli"]);
|
||||
export async function listProviders(): Promise<AcpProvider[]> {
|
||||
const client = await getClient();
|
||||
const result = await client.goose.GooseProvidersList({});
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ACP SDK types don't expose providers field
|
||||
return (result as any).providers
|
||||
.filter((p: any) => !DEPRECATED_PROVIDER_IDS.has(p.id))
|
||||
.map((p: any) => ({ id: p.id, label: p.label }));
|
||||
.filter(
|
||||
(p: { id: string; label: string }) => !DEPRECATED_PROVIDER_IDS.has(p.id),
|
||||
)
|
||||
.map((p: { id: string; label: string }) => ({ id: p.id, label: p.label }));
|
||||
}
|
||||
|
||||
export async function listSessions(): Promise<AcpSessionInfo[]> {
|
||||
const client = await getClient();
|
||||
// GooseClient.unstable_listSessions doesn't work with SDK 0.19 (renamed to listSessions).
|
||||
// Bypass GooseClient and call the connection directly. Fix when ui/acp is updated.
|
||||
// biome-ignore lint/suspicious/noExplicitAny: SDK doesn't expose conn property
|
||||
const conn = (client as any).conn;
|
||||
const response = await conn.listSessions({});
|
||||
return response.sessions.map((info: any) => ({
|
||||
sessionId: info.sessionId,
|
||||
title: info.title ?? null,
|
||||
updatedAt: info.updatedAt ?? null,
|
||||
messageCount: (info._meta?.messageCount as number) ?? 0,
|
||||
}));
|
||||
return response.sessions.map(
|
||||
(info: {
|
||||
sessionId: string;
|
||||
title?: string;
|
||||
updatedAt?: string;
|
||||
_meta?: Record<string, unknown>;
|
||||
}) => ({
|
||||
sessionId: info.sessionId,
|
||||
title: info.title ?? null,
|
||||
updatedAt: info.updatedAt ?? null,
|
||||
messageCount: (info._meta?.messageCount as number) ?? 0,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function exportSession(sessionId: string): Promise<string> {
|
||||
const client = await getClient();
|
||||
const result = await client.goose.GooseSessionExport({ sessionId });
|
||||
// biome-ignore lint/suspicious/noExplicitAny: SDK doesn't expose data field on export result
|
||||
return (result as any).data;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { GooseClient } from "@aaif/goose-acp";
|
||||
import { GooseClient } from "@aaif/goose-sdk";
|
||||
import {
|
||||
PROTOCOL_VERSION,
|
||||
type Client,
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const USE_DIRECT_ACP = true;
|
||||
@@ -235,7 +235,7 @@ function handleLive(
|
||||
...msg,
|
||||
content: msg.content.map((c) =>
|
||||
c.type === "toolRequest" && c.id === update.toolCallId
|
||||
? { ...c, name: update.title! }
|
||||
? { ...c, name: update.title ?? "" }
|
||||
: c,
|
||||
),
|
||||
}));
|
||||
@@ -310,7 +310,8 @@ function handleShared(sessionId: string, update: SessionUpdate): void {
|
||||
};
|
||||
if ("options" in configUpdate && Array.isArray(configUpdate.options)) {
|
||||
const modelOption = configUpdate.options.find(
|
||||
(opt: any) => opt.category === "model",
|
||||
(opt: { category?: string; kind?: Record<string, unknown> }) =>
|
||||
opt.category === "model",
|
||||
);
|
||||
if (modelOption?.kind?.type === "select") {
|
||||
const select = modelOption.kind;
|
||||
@@ -391,6 +392,7 @@ function findMessageWithToolCall(
|
||||
}
|
||||
|
||||
function extractToolResultText(update: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ACP SDK ToolCallContent type is complex
|
||||
content?: Array<any> | null;
|
||||
rawOutput?: unknown;
|
||||
}): string {
|
||||
|
||||
@@ -55,6 +55,7 @@ export function createWebSocketStream(wsUrl: string): Stream {
|
||||
async pull(controller) {
|
||||
await waitForMessage();
|
||||
while (incoming.length > 0) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: length checked in while condition
|
||||
controller.enqueue(incoming.shift()!);
|
||||
}
|
||||
if (closed && incoming.length === 0) {
|
||||
|
||||
@@ -106,7 +106,7 @@ function searchSession(
|
||||
};
|
||||
}
|
||||
|
||||
function safeParse(json: string): Record<string, any> | null {
|
||||
function safeParse(json: string): Record<string, unknown> | null {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Provider types — these map to goose serve provider names.
|
||||
// All sessions run through goose serve; the provider ID selects which
|
||||
// backend provider goose uses for inference. The list is dynamic
|
||||
// (fetched from the backend via discover_acp_providers) so this is a
|
||||
// plain string rather than a narrow union.
|
||||
// Provider types map to goose serve provider names.
|
||||
// The provider list is dynamic, so this remains a plain string rather than
|
||||
// a narrow union.
|
||||
export type ProviderType = string;
|
||||
|
||||
export interface ProviderConfig {
|
||||
|
||||
Reference in New Issue
Block a user