66 lines
2.9 KiB
TypeScript
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
|
|
});
|
|
}
|