Files
2026-07-20 21:41:59 +02:00

66 lines
2.9 KiB
TypeScript

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<string, unknown> | 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<string, unknown> | null;
if (!response.ok || !payload) {
const error = payload?.error && typeof payload.error === "object" ? payload.error as Record<string, unknown> : {};
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<string, unknown> = {}) {
return Object.assign(new Error(message), {
code,
details,
status: details.status,
layer: details.layer,
retryable: details.retryable,
userMessage: details.userMessage
});
}