huskies: merge 950

This commit is contained in:
dave
2026-05-13 08:41:57 +00:00
parent 7491eec257
commit 4a8ed4348b
38 changed files with 354 additions and 4329 deletions
+9 -87
View File
@@ -1,8 +1,7 @@
/**
* HTTP transport layer for the Huskies API client.
* Provides the low-level `requestJson` helper, the `callMcpTool` function
* for MCP JSON-RPC calls, the `resolveWsHost` utility, and the `api`
* object exposing all REST endpoints.
* Provides the `callMcpTool` function for MCP JSON-RPC calls, the
* `resolveWsHost` utility, and the `api` object exposing all endpoints.
*/
import { rpcCall } from "../rpc";
@@ -15,18 +14,13 @@ import type {
import type {
AllTokenUsageResponse,
AnthropicModelInfo,
CommandOutput,
FileEntry,
OAuthStatus,
SearchResult,
TestResultsResponse,
TokenCostResponse,
WorkItemContent,
} from "./types";
/** Base URL prefix for all REST API requests in production. */
export const DEFAULT_API_BASE = "/api";
/**
* Resolve the WebSocket host to connect to.
* In development, uses the injected port (or 3001); in production, uses the
@@ -40,31 +34,6 @@ export function resolveWsHost(
return isDev ? `127.0.0.1:${envPort || "3001"}` : locationHost;
}
function buildApiUrl(path: string, baseUrl = DEFAULT_API_BASE): string {
return `${baseUrl}${path}`;
}
async function requestJson<T>(
path: string,
options: RequestInit = {},
baseUrl = DEFAULT_API_BASE,
): Promise<T> {
const res = await fetch(buildApiUrl(path, baseUrl), {
headers: {
"Content-Type": "application/json",
...(options.headers ?? {}),
},
...options,
});
if (!res.ok) {
const text = await res.text();
throw new Error(text || `Request failed (${res.status})`);
}
return res.json() as Promise<T>;
}
/**
* Invoke an MCP tool via the server's JSON-RPC `/mcp` endpoint.
* Returns the first text content block from the tool result, or an empty
@@ -92,7 +61,7 @@ export async function callMcpTool(
return text;
}
/** Typed REST and MCP wrappers for all Huskies server endpoints. */
/** Typed wrappers for all Huskies server endpoints. */
export const api = {
getCurrentProject(_baseUrl?: string) {
return rpcCall<string | null>("project.current");
@@ -137,40 +106,11 @@ export const api = {
const r = await rpcCall<OkResult>("anthropic.set_api_key", params);
return r.ok;
},
readFile(path: string, baseUrl?: string) {
return requestJson<string>(
"/fs/read",
{ method: "POST", body: JSON.stringify({ path }) },
baseUrl,
);
readFile(path: string) {
return rpcCall<string>("io.read_file", { path });
},
writeFile(path: string, content: string, baseUrl?: string) {
return requestJson<boolean>(
"/fs/write",
{ method: "POST", body: JSON.stringify({ path, content }) },
baseUrl,
);
},
listDirectory(path: string, baseUrl?: string) {
return requestJson<FileEntry[]>(
"/fs/list",
{ method: "POST", body: JSON.stringify({ path }) },
baseUrl,
);
},
listDirectoryAbsolute(path: string, baseUrl?: string) {
return requestJson<FileEntry[]>(
"/io/fs/list/absolute",
{ method: "POST", body: JSON.stringify({ path }) },
baseUrl,
);
},
createDirectoryAbsolute(path: string, baseUrl?: string) {
return requestJson<boolean>(
"/io/fs/create/absolute",
{ method: "POST", body: JSON.stringify({ path }) },
baseUrl,
);
listDirectoryAbsolute(path: string) {
return rpcCall<FileEntry[]>("io.list_directory_absolute", { path });
},
getHomeDirectory(_baseUrl?: string) {
return rpcCall<string>("io.home_directory");
@@ -178,20 +118,6 @@ export const api = {
listProjectFiles(_baseUrl?: string) {
return rpcCall<string[]>("io.list_project_files");
},
searchFiles(query: string, baseUrl?: string) {
return requestJson<SearchResult[]>(
"/fs/search",
{ method: "POST", body: JSON.stringify({ query }) },
baseUrl,
);
},
execShell(command: string, args: string[], baseUrl?: string) {
return requestJson<CommandOutput>(
"/shell/exec",
{ method: "POST", body: JSON.stringify({ command, args }) },
baseUrl,
);
},
async cancelChat(_baseUrl?: string) {
const r = await rpcCall<OkResult>("chat.cancel");
return r.ok;
@@ -237,11 +163,7 @@ export const api = {
return rpcCall<OAuthStatus>("oauth.status");
},
/** Execute a bot slash command without LLM invocation. Returns markdown response text. */
botCommand(command: string, args: string, baseUrl?: string) {
return requestJson<{ response: string }>(
"/bot/command",
{ method: "POST", body: JSON.stringify({ command, args }) },
baseUrl,
);
botCommand(command: string, args: string) {
return rpcCall<{ response: string }>("bot.command", { command, args });
},
};
+1 -1
View File
@@ -33,6 +33,6 @@ export type {
WsResponse,
} from "./types";
export { api, callMcpTool, DEFAULT_API_BASE, resolveWsHost } from "./http";
export { api, callMcpTool, resolveWsHost } from "./http";
export { ChatWebSocket } from "./websocket";