chore: sync workspace changes

This commit is contained in:
Nathan Flurry 2026-01-25 01:57:16 -08:00
parent 30d3aca1ee
commit f92ecd9b9a
38 changed files with 4829 additions and 1219 deletions

View file

@ -0,0 +1,17 @@
{
"name": "sandbox-daemon-typescript",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"generate:openapi": "cargo run -p sandbox-daemon-openapi-gen -- --out src/generated/openapi.json",
"generate:types": "openapi-typescript src/generated/openapi.json -o src/generated/openapi.ts",
"generate": "npm run generate:openapi && npm run generate:types",
"build": "tsc -p tsconfig.json"
},
"devDependencies": {
"@types/node": "^22.0.0",
"openapi-typescript": "^6.7.0",
"typescript": "^5.7.0"
}
}

View file

@ -0,0 +1,254 @@
import type { components } from "./generated/openapi";
export type AgentInstallRequest = components["schemas"]["AgentInstallRequest"];
export type AgentModeInfo = components["schemas"]["AgentModeInfo"];
export type AgentModesResponse = components["schemas"]["AgentModesResponse"];
export type AgentInfo = components["schemas"]["AgentInfo"];
export type AgentListResponse = components["schemas"]["AgentListResponse"];
export type CreateSessionRequest = components["schemas"]["CreateSessionRequest"];
export type CreateSessionResponse = components["schemas"]["CreateSessionResponse"];
export type MessageRequest = components["schemas"]["MessageRequest"];
export type EventsQuery = components["schemas"]["EventsQuery"];
export type EventsResponse = components["schemas"]["EventsResponse"];
export type QuestionReplyRequest = components["schemas"]["QuestionReplyRequest"];
export type PermissionReplyRequest = components["schemas"]["PermissionReplyRequest"];
export type PermissionReply = components["schemas"]["PermissionReply"];
export type ProblemDetails = components["schemas"]["ProblemDetails"];
export type UniversalEvent = components["schemas"]["UniversalEvent"];
const API_PREFIX = "/v1";
export interface SandboxDaemonClientOptions {
baseUrl: string;
token?: string;
fetch?: typeof fetch;
headers?: HeadersInit;
}
export class SandboxDaemonError extends Error {
readonly status: number;
readonly problem?: ProblemDetails;
readonly response: Response;
constructor(status: number, problem: ProblemDetails | undefined, response: Response) {
super(problem?.title ?? `Request failed with status ${status}`);
this.name = "SandboxDaemonError";
this.status = status;
this.problem = problem;
this.response = response;
}
}
type QueryValue = string | number | boolean | null | undefined;
type RequestOptions = {
query?: Record<string, QueryValue>;
body?: unknown;
headers?: HeadersInit;
accept?: string;
};
export class SandboxDaemonClient {
private readonly baseUrl: string;
private readonly token?: string;
private readonly fetcher: typeof fetch;
private readonly defaultHeaders?: HeadersInit;
constructor(options: SandboxDaemonClientOptions) {
this.baseUrl = options.baseUrl.replace(/\/$/, "");
this.token = options.token;
this.fetcher = options.fetch ?? globalThis.fetch;
this.defaultHeaders = options.headers;
if (!this.fetcher) {
throw new Error("Fetch API is not available; provide a fetch implementation.");
}
}
async listAgents(): Promise<AgentListResponse> {
return this.requestJson("GET", `${API_PREFIX}/agents`);
}
async installAgent(agent: string, request: AgentInstallRequest = {}): Promise<void> {
await this.requestJson("POST", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/install`, {
body: request,
});
}
async getAgentModes(agent: string): Promise<AgentModesResponse> {
return this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/modes`);
}
async createSession(sessionId: string, request: CreateSessionRequest): Promise<CreateSessionResponse> {
return this.requestJson("POST", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}`, {
body: request,
});
}
async postMessage(sessionId: string, request: MessageRequest): Promise<void> {
await this.requestJson("POST", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/messages`, {
body: request,
});
}
async getEvents(sessionId: string, query?: EventsQuery): Promise<EventsResponse> {
return this.requestJson("GET", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/events`, {
query,
});
}
async getEventsSse(sessionId: string, query?: EventsQuery): Promise<Response> {
return this.requestRaw("GET", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/events/sse`, {
query,
accept: "text/event-stream",
});
}
async *streamEvents(sessionId: string, query?: EventsQuery): AsyncGenerator<UniversalEvent, void, void> {
const response = await this.getEventsSse(sessionId, query);
if (!response.body) {
throw new Error("SSE stream is not readable in this environment.");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
let index = buffer.indexOf("\n\n");
while (index !== -1) {
const chunk = buffer.slice(0, index);
buffer = buffer.slice(index + 2);
const dataLines = chunk
.split(/\r?\n/)
.filter((line) => line.startsWith("data:"));
if (dataLines.length > 0) {
const payload = dataLines
.map((line) => line.slice(5).trim())
.join("\n");
if (payload) {
yield JSON.parse(payload) as UniversalEvent;
}
}
index = buffer.indexOf("\n\n");
}
}
}
async replyQuestion(
sessionId: string,
questionId: string,
request: QuestionReplyRequest,
): Promise<void> {
await this.requestJson(
"POST",
`${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/questions/${encodeURIComponent(questionId)}/reply`,
{ body: request },
);
}
async rejectQuestion(sessionId: string, questionId: string): Promise<void> {
await this.requestJson(
"POST",
`${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/questions/${encodeURIComponent(questionId)}/reject`,
);
}
async replyPermission(
sessionId: string,
permissionId: string,
request: PermissionReplyRequest,
): Promise<void> {
await this.requestJson(
"POST",
`${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/permissions/${encodeURIComponent(permissionId)}/reply`,
{ body: request },
);
}
private async requestJson<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {
const response = await this.requestRaw(method, path, {
query: options.query,
body: options.body,
headers: options.headers,
accept: options.accept ?? "application/json",
});
if (response.status === 204) {
return undefined as T;
}
const text = await response.text();
if (!text) {
return undefined as T;
}
return JSON.parse(text) as T;
}
private async requestRaw(method: string, path: string, options: RequestOptions = {}): Promise<Response> {
const url = this.buildUrl(path, options.query);
const headers = new Headers(this.defaultHeaders ?? undefined);
if (this.token) {
headers.set("Authorization", `Bearer ${this.token}`);
}
if (options.accept) {
headers.set("Accept", options.accept);
}
const init: RequestInit = { method, headers };
if (options.body !== undefined) {
headers.set("Content-Type", "application/json");
init.body = JSON.stringify(options.body);
}
if (options.headers) {
const extra = new Headers(options.headers);
extra.forEach((value, key) => headers.set(key, value));
}
const response = await this.fetcher(url, init);
if (!response.ok) {
const problem = await this.readProblem(response);
throw new SandboxDaemonError(response.status, problem, response);
}
return response;
}
private buildUrl(path: string, query?: Record<string, QueryValue>): string {
const url = new URL(`${this.baseUrl}${path}`);
if (query) {
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
url.searchParams.set(key, String(value));
});
}
return url.toString();
}
private async readProblem(response: Response): Promise<ProblemDetails | undefined> {
try {
const text = await response.clone().text();
if (!text) {
return undefined;
}
return JSON.parse(text) as ProblemDetails;
} catch {
return undefined;
}
}
}
export const createSandboxDaemonClient = (options: SandboxDaemonClientOptions): SandboxDaemonClient => {
return new SandboxDaemonClient(options);
};

View file

@ -0,0 +1,470 @@
/* eslint-disable */
// This file is generated by openapi-typescript. Do not edit by hand.
export interface paths {
"/v1/agents": {
get: {
responses: {
200: {
content: {
"application/json": components["schemas"]["AgentListResponse"];
};
};
};
};
};
"/v1/agents/{agent}/install": {
post: {
parameters: {
path: {
agent: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["AgentInstallRequest"];
};
};
responses: {
204: {
description: string;
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
500: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/agents/{agent}/modes": {
get: {
parameters: {
path: {
agent: string;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["AgentModesResponse"];
};
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}": {
post: {
parameters: {
path: {
session_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateSessionRequest"];
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["CreateSessionResponse"];
};
};
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
409: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}/messages": {
post: {
parameters: {
path: {
session_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["MessageRequest"];
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}/events": {
get: {
parameters: {
path: {
session_id: string;
};
query?: {
offset?: number | null;
limit?: number | null;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["EventsResponse"];
};
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}/events/sse": {
get: {
parameters: {
path: {
session_id: string;
};
query?: {
offset?: number | null;
};
};
responses: {
200: {
description: string;
};
};
};
};
"/v1/sessions/{session_id}/questions/{question_id}/reply": {
post: {
parameters: {
path: {
session_id: string;
question_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["QuestionReplyRequest"];
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}/questions/{question_id}/reject": {
post: {
parameters: {
path: {
session_id: string;
question_id: string;
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
"/v1/sessions/{session_id}/permissions/{permission_id}/reply": {
post: {
parameters: {
path: {
session_id: string;
permission_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["PermissionReplyRequest"];
};
};
responses: {
204: {
description: string;
};
404: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
};
}
export interface components {
schemas: {
AgentError: {
type: components["schemas"]["ErrorType"];
message: string;
agent?: string | null;
session_id?: string | null;
details?: unknown | null;
};
AgentInfo: {
id: string;
installed: boolean;
path?: string | null;
version?: string | null;
};
AgentInstallRequest: {
reinstall?: boolean | null;
};
AgentListResponse: {
agents: components["schemas"]["AgentInfo"][];
};
AgentModeInfo: {
id: string;
name: string;
description: string;
};
AgentModesResponse: {
modes: components["schemas"]["AgentModeInfo"][];
};
AttachmentSource:
| {
type: "path";
path: string;
}
| {
type: "url";
url: string;
}
| {
type: "data";
data: string;
encoding?: string | null;
};
CrashInfo: {
message: string;
kind?: string | null;
details?: unknown | null;
};
CreateSessionRequest: {
agent: string;
agentMode?: string | null;
agentVersion?: string | null;
model?: string | null;
permissionMode?: string | null;
token?: string | null;
validateToken?: boolean | null;
variant?: string | null;
};
CreateSessionResponse: {
healthy: boolean;
error?: components["schemas"]["AgentError"] | null;
agentSessionId?: string | null;
};
ErrorType:
| "invalid_request"
| "unsupported_agent"
| "agent_not_installed"
| "install_failed"
| "agent_process_exited"
| "token_invalid"
| "permission_denied"
| "session_not_found"
| "session_already_exists"
| "mode_not_supported"
| "stream_error"
| "timeout";
EventsQuery: {
offset?: number | null;
limit?: number | null;
};
EventsResponse: {
events: components["schemas"]["UniversalEvent"][];
hasMore: boolean;
};
MessageRequest: {
message: string;
};
PermissionReply: "once" | "always" | "reject";
PermissionReplyRequest: {
reply: components["schemas"]["PermissionReply"];
};
PermissionRequest: {
id: string;
sessionId: string;
permission: string;
patterns: string[];
always: string[];
metadata?: Record<string, unknown>;
tool?: components["schemas"]["PermissionToolRef"] | null;
};
PermissionToolRef: {
messageId: string;
callId: string;
};
ProblemDetails: {
type: string;
title: string;
status: number;
detail?: string | null;
instance?: string | null;
[key: string]: unknown;
};
QuestionInfo: {
question: string;
options: components["schemas"]["QuestionOption"][];
header?: string | null;
multiSelect?: boolean | null;
custom?: boolean | null;
};
QuestionOption: {
label: string;
description?: string | null;
};
QuestionReplyRequest: {
answers: string[][];
};
QuestionRequest: {
id: string;
sessionId: string;
questions: components["schemas"]["QuestionInfo"][];
tool?: components["schemas"]["QuestionToolRef"] | null;
};
QuestionToolRef: {
messageId: string;
callId: string;
};
Started: {
message?: string | null;
details?: unknown | null;
};
UniversalEvent: {
id: number;
timestamp: string;
sessionId: string;
agent: string;
agentSessionId?: string | null;
data: components["schemas"]["UniversalEventData"];
};
UniversalEventData:
| { message: components["schemas"]["UniversalMessage"] }
| { started: components["schemas"]["Started"] }
| { error: components["schemas"]["CrashInfo"] }
| { questionAsked: components["schemas"]["QuestionRequest"] }
| { permissionAsked: components["schemas"]["PermissionRequest"] }
| { raw: unknown };
UniversalMessage:
| components["schemas"]["UniversalMessageParsed"]
| {
raw: unknown;
error?: string | null;
};
UniversalMessageParsed: {
role: string;
parts: components["schemas"]["UniversalMessagePart"][];
id?: string | null;
metadata?: Record<string, unknown>;
};
UniversalMessagePart:
| {
type: "text";
text: string;
}
| {
type: "tool_call";
name: string;
input: unknown;
id?: string | null;
}
| {
type: "tool_result";
output: unknown;
id?: string | null;
name?: string | null;
is_error?: boolean | null;
}
| {
type: "function_call";
arguments: unknown;
id?: string | null;
name?: string | null;
raw?: unknown | null;
}
| {
type: "function_result";
result: unknown;
id?: string | null;
name?: string | null;
is_error?: boolean | null;
raw?: unknown | null;
}
| {
type: "file";
source: components["schemas"]["AttachmentSource"];
mime_type?: string | null;
filename?: string | null;
raw?: unknown | null;
}
| {
type: "image";
source: components["schemas"]["AttachmentSource"];
mime_type?: string | null;
alt?: string | null;
raw?: unknown | null;
}
| {
type: "error";
message: string;
}
| {
type: "unknown";
raw: unknown;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type webhooks = never;

View file

@ -0,0 +1,19 @@
export { SandboxDaemonClient, SandboxDaemonError, createSandboxDaemonClient } from "./client";
export type {
AgentInfo,
AgentInstallRequest,
AgentListResponse,
AgentModeInfo,
AgentModesResponse,
CreateSessionRequest,
CreateSessionResponse,
EventsQuery,
EventsResponse,
MessageRequest,
PermissionReply,
PermissionReplyRequest,
ProblemDetails,
QuestionReplyRequest,
UniversalEvent,
} from "./client";
export type { components, paths } from "./generated/openapi";

View file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"resolveJsonModule": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}