import type { WorkbenchApplication, WorkbenchEventPublisher } from "./contracts.ts"; import { normalizeWorkbenchCloudAuthorization } from "./cloud-authorization.ts"; export function createCloudWorkbenchApplication(options: { baseUrl: string; authorization: string; fetchImpl?: typeof fetch; eventPublisher?: WorkbenchEventPublisher | null; }): WorkbenchApplication { if (!options.baseUrl) throw codedError("workbench_cloud_api_url_required", "WORKBENCH_CLOUD_API_URL is required"); const authorization = normalizeWorkbenchCloudAuthorization(options.authorization); if (!authorization) throw codedError("workbench_cloud_api_auth_required", "WORKBENCH_CLOUD_API_AUTHORIZATION is required"); const fetchImpl = options.fetchImpl ?? fetch; const request = async (method: string, route: string, body: Record | undefined, actor: { id: string; role?: string | null }) => { const response = await fetchImpl(`${options.baseUrl.replace(/\/$/u, "")}${route}`, { method, headers: { authorization, "content-type": "application/json", "x-workbench-activity-dispatch": "1", "x-hwlab-actor-id": actor.id, "x-hwlab-actor-role": actor.role ?? "user" }, body: body ? JSON.stringify(body) : undefined }); const payload = await response.json().catch(() => null) as Record | null; if (!response.ok || !payload) { const error = payload?.error && typeof payload.error === "object" ? payload.error as Record : {}; throw codedError(String(error.code ?? "workbench_cloud_api_failed"), String(error.message ?? `Cloud API returned ${response.status}`), { status: response.status, route, layer: error.layer, retryable: error.retryable, userMessage: error.userMessage }); } return payload; }; return { async health() { return { serviceId: "hwlab-workbench", status: "ready", cloudApi: "configured" }; }, async createSession(input) { return request("POST", "/v1/agent/sessions", input.params, input.actor); }, async admitTurn(input) { await request("POST", "/v1/internal/workbench/admit", { ...input.params, traceId: input.traceId, sessionId: input.sessionId }, input.actor); return input; }, async dispatchTurn(input) { return request("POST", "/v1/agent/chat", { ...input.params, traceId: input.traceId, sessionId: input.sessionId, shortConnection: true }, input.actor); }, async cancelTurn(input) { return request("POST", "/v1/agent/chat/cancel", { ...input.params, traceId: input.traceId }, input.actor); }, async close() { await options.eventPublisher?.close?.(); } }; } function codedError(code: string, message: string, details: Record = {}) { return Object.assign(new Error(message), { code, details, status: details.status, layer: details.layer, retryable: details.retryable, userMessage: details.userMessage }); }