feat: TUI client of goose-acp (#7362)

This commit is contained in:
Alex Hancock
2026-02-19 15:54:07 -05:00
committed by GitHub
parent 398bf8be8c
commit d4dfa5d311
15 changed files with 2343 additions and 87 deletions
+1 -21
View File
@@ -28,7 +28,6 @@ main().catch((err) => {
async function main() {
const schemaSrc = await fs.readFile(SCHEMA_PATH, "utf8");
const jsonSchema = JSON.parse(
// Convert JSON Schema $defs refs to OpenAPI component refs
schemaSrc.replaceAll("#/$defs/", "#/components/schemas/"),
);
@@ -63,7 +62,6 @@ async function main() {
async function postProcessTypes() {
const tsPath = resolve(OUTPUT_DIR, "types.gen.ts");
let src = await fs.readFile(tsPath, "utf8");
// Remove the ClientOptions type block injected by @hey-api (not part of our schema)
src = src.replace(/\nexport type ClientOptions =[\s\S]*?^};\n/m, "\n");
await fs.writeFile(tsPath, src);
}
@@ -72,17 +70,14 @@ async function postProcessIndex(meta: { methods: unknown[] }) {
const indexPath = resolve(OUTPUT_DIR, "index.ts");
let src = await fs.readFile(indexPath, "utf8");
// Strip ClientOptions from re-exports
src = src.replace(/,?\s*ClientOptions\s*,?/g, (match) => {
if (match.startsWith(",") && match.endsWith(",")) return ",";
if (match.startsWith(",")) return "";
return "";
});
// Fix bare relative imports to use .js extensions (required by nodenext consumers)
src = fixRelativeImports(src);
// Append method constants
const methodConstants = await prettier.format(
`
export const GOOSE_EXT_METHODS = ${JSON.stringify(meta.methods, null, 2)} as const;
@@ -94,7 +89,6 @@ export type GooseExtMethod = (typeof GOOSE_EXT_METHODS)[number];
await fs.writeFile(indexPath, `${src}\n${methodConstants}`);
// Also fix imports in zod.gen.ts (it may import from types.gen)
for (const file of ["zod.gen.ts", "types.gen.ts"]) {
const filePath = resolve(OUTPUT_DIR, file);
try {
@@ -127,9 +121,6 @@ interface MethodMeta {
responseType: string | null;
}
/**
* Convert a method path like "session/list" or "working_dir/update" to camelCase "sessionList", "workingDirUpdate".
*/
function methodToCamelCase(method: string): string {
return method
.split(/[/_]/)
@@ -139,10 +130,6 @@ function methodToCamelCase(method: string): string {
.join("");
}
/**
* Generate a typed GooseClient class that wraps ClientSideConnection.extMethod()
* with proper TypeScript types and Zod runtime validation.
*/
async function generateClient(meta: { methods: MethodMeta[] }) {
const typeImports = new Set<string>();
const zodImports = new Set<string>();
@@ -153,7 +140,6 @@ async function generateClient(meta: { methods: MethodMeta[] }) {
const fnName = methodToCamelCase(m.method);
const fullMethod = `_goose/${m.method}`;
// Build param type and arg
let paramType = "";
let paramArg = "";
let callParams = "{}";
@@ -164,7 +150,6 @@ async function generateClient(meta: { methods: MethodMeta[] }) {
callParams = "params";
}
// Build return type and validation
let returnType: string;
let bodyLines: string[];
@@ -183,7 +168,6 @@ async function generateClient(meta: { methods: MethodMeta[] }) {
`await this.conn.extMethod("${fullMethod}", ${callParams});`,
];
} else {
// Both request and response are untyped (serde_json::Value)
returnType = "Record<string, unknown>";
bodyLines = [
`return await this.conn.extMethod("${fullMethod}", ${callParams ? callParams : "{}"});`,
@@ -212,11 +196,7 @@ export interface ExtMethodProvider {
${typeImportLine}
${zodImportLine}
/**
* Typed client for Goose custom extension methods.
* Wraps an ExtMethodProvider (e.g. ClientSideConnection) with proper types and Zod validation.
*/
export class GooseClient {
export class GooseExtClient {
constructor(private conn: ExtMethodProvider) {}
${methodDefs.join("\n")}
}
+1 -29
View File
@@ -39,7 +39,7 @@ import {
* Typed client for Goose custom extension methods.
* Wraps an ExtMethodProvider (e.g. ClientSideConnection) with proper types and Zod validation.
*/
export class GooseClient {
export class GooseExtClient {
constructor(private conn: ExtMethodProvider) {}
async extensionsAdd(params: AddExtensionRequest): Promise<void> {
@@ -98,32 +98,4 @@ export class GooseClient {
const raw = await this.conn.extMethod("_goose/config/extensions", {});
return zGetExtensionsResponse.parse(raw) as GetExtensionsResponse;
}
async toolCall(): Promise<Record<string, unknown>> {
return await this.conn.extMethod("_goose/tool/call", {});
}
async providerUpdate(): Promise<Record<string, unknown>> {
return await this.conn.extMethod("_goose/provider/update", {});
}
async containerSet(): Promise<Record<string, unknown>> {
return await this.conn.extMethod("_goose/container/set", {});
}
async appsList(): Promise<Record<string, unknown>> {
return await this.conn.extMethod("_goose/apps/list", {});
}
async appsExport(): Promise<Record<string, unknown>> {
return await this.conn.extMethod("_goose/apps/export", {});
}
async appsImport(): Promise<Record<string, unknown>> {
return await this.conn.extMethod("_goose/apps/import", {});
}
async configProviders(): Promise<Record<string, unknown>> {
return await this.conn.extMethod("_goose/config/providers", {});
}
}
-35
View File
@@ -58,41 +58,6 @@ export const GOOSE_EXT_METHODS = [
requestType: null,
responseType: "GetExtensionsResponse",
},
{
method: "tool/call",
requestType: null,
responseType: null,
},
{
method: "provider/update",
requestType: null,
responseType: null,
},
{
method: "container/set",
requestType: null,
responseType: null,
},
{
method: "apps/list",
requestType: null,
responseType: null,
},
{
method: "apps/export",
requestType: null,
responseType: null,
},
{
method: "apps/import",
requestType: null,
responseType: null,
},
{
method: "config/providers",
requestType: null,
responseType: null,
},
] as const;
export type GooseExtMethod = (typeof GOOSE_EXT_METHODS)[number];
+118
View File
@@ -0,0 +1,118 @@
import {
ClientSideConnection,
type Client,
type Stream,
type InitializeRequest,
type InitializeResponse,
type NewSessionRequest,
type NewSessionResponse,
type LoadSessionRequest,
type LoadSessionResponse,
type PromptRequest,
type PromptResponse,
type CancelNotification,
type AuthenticateRequest,
type AuthenticateResponse,
type SetSessionModeRequest,
type SetSessionModeResponse,
type SetSessionConfigOptionRequest,
type SetSessionConfigOptionResponse,
type ForkSessionRequest,
type ForkSessionResponse,
type ListSessionsRequest,
type ListSessionsResponse,
type ResumeSessionRequest,
type ResumeSessionResponse,
type SetSessionModelRequest,
type SetSessionModelResponse,
} from "@agentclientprotocol/sdk";
import { GooseExtClient } from "./generated/client.gen.js";
export class GooseClient {
private conn: ClientSideConnection;
private ext: GooseExtClient;
constructor(toClient: () => Client, stream: Stream) {
this.conn = new ClientSideConnection(toClient, stream);
this.ext = new GooseExtClient(this.conn);
}
get signal(): AbortSignal {
return this.conn.signal;
}
get closed(): Promise<void> {
return this.conn.closed;
}
initialize(params: InitializeRequest): Promise<InitializeResponse> {
return this.conn.initialize(params);
}
newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
return this.conn.newSession(params);
}
loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
return this.conn.loadSession(params);
}
prompt(params: PromptRequest): Promise<PromptResponse> {
return this.conn.prompt(params);
}
cancel(params: CancelNotification): Promise<void> {
return this.conn.cancel(params);
}
authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse> {
return this.conn.authenticate(params);
}
setSessionMode(
params: SetSessionModeRequest,
): Promise<SetSessionModeResponse> {
return this.conn.setSessionMode(params);
}
setSessionConfigOption(
params: SetSessionConfigOptionRequest,
): Promise<SetSessionConfigOptionResponse> {
return this.conn.setSessionConfigOption(params);
}
unstable_forkSession(
params: ForkSessionRequest,
): Promise<ForkSessionResponse> {
return this.conn.unstable_forkSession(params);
}
unstable_listSessions(
params: ListSessionsRequest,
): Promise<ListSessionsResponse> {
return this.conn.unstable_listSessions(params);
}
unstable_resumeSession(
params: ResumeSessionRequest,
): Promise<ResumeSessionResponse> {
return this.conn.unstable_resumeSession(params);
}
unstable_setSessionModel(
params: SetSessionModelRequest,
): Promise<SetSessionModelResponse> {
return this.conn.unstable_setSessionModel(params);
}
extMethod(
method: string,
params: Record<string, unknown>,
): Promise<Record<string, unknown>> {
return this.conn.extMethod(method, params);
}
get goose(): GooseExtClient {
return this.ext;
}
}
+8 -2
View File
@@ -1,3 +1,9 @@
export * from "./generated/index.js";
export * from "./generated/types.gen.js";
export * from "./generated/zod.gen.js";
export { GooseClient } from "./generated/client.gen.js";
export { GooseClient } from "./goose-client.js";
export {
ClientSideConnection,
type Client,
type Stream,
} from "@agentclientprotocol/sdk";