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

108 lines
4.5 KiB
TypeScript

import { ApplicationFailure, Context } from "@temporalio/activity";
import type { WorkbenchActivityConfig, WorkbenchApplication, WorkbenchEventPublisher, WorkbenchTurnInput } from "./contracts.ts";
export function createWorkbenchActivities(application: WorkbenchApplication, options: {
activity: WorkbenchActivityConfig;
eventPublisher?: WorkbenchEventPublisher | null;
attempt?: () => number;
}) {
return {
async dispatchWorkbenchTurn(input: WorkbenchTurnInput) {
try {
return await application.dispatchTurn(input);
} catch (error) {
const attempt = positiveInteger(options.attempt?.() ?? currentActivityAttempt(), 1);
const retryable = (error as any)?.retryable !== false;
const exhausted = !retryable || attempt >= options.activity.retryMaximumAttempts;
await publishDispatchFailure(options.eventPublisher, input, error, {
attempt,
maxAttempts: options.activity.retryMaximumAttempts,
retryable,
exhausted,
backoffMs: exhausted ? null : retryDelayMs(options.activity, attempt)
});
throw ApplicationFailure.create({
message: errorMessage(error),
type: errorCode(error),
nonRetryable: !retryable,
details: [{
code: errorCode(error),
layer: (error as any)?.layer ?? null,
status: (error as any)?.status ?? null,
retryable,
attempt,
maxAttempts: options.activity.retryMaximumAttempts,
valuesPrinted: false
}]
});
}
},
async cancelWorkbenchTurn(input: { actor: WorkbenchTurnInput["actor"]; traceId: string; params: Record<string, unknown> }) {
return application.cancelTurn(input);
}
};
}
async function publishDispatchFailure(publisher: WorkbenchEventPublisher | null | undefined, input: WorkbenchTurnInput, error: unknown, state: { attempt: number; maxAttempts: number; retryable: boolean; exhausted: boolean; backoffMs: number | null }) {
if (!publisher) return;
const code = errorCode(error);
const message = userErrorMessage(error);
const createdAt = new Date().toISOString();
await publisher.publish({
traceId: input.traceId,
sessionId: input.sessionId,
event: {
sourceEventId: `workbench:${input.traceId}:dispatch-failure:${state.attempt}`,
type: state.exhausted ? "error" : "backend_status",
phase: state.exhausted ? "dispatch-failed" : "dispatch-retry-scheduled",
failureDomain: (error as any)?.layer === "billing" ? "billing" : "infrastructure",
component: "hwlab-cloud-api",
code,
failureKind: code,
summary: message,
message,
retryable: state.retryable,
willRetry: !state.exhausted,
retryPhase: state.exhausted ? "retryExhausted" : "retryScheduled",
attempt: state.attempt,
maxAttempts: state.maxAttempts,
backoffMs: state.backoffMs,
createdAt,
terminal: false,
valuesPrinted: false
}
});
if (!state.exhausted) return;
await publisher.publish({
traceId: input.traceId,
sessionId: input.sessionId,
event: {
sourceEventId: `workbench:${input.traceId}:terminal-failed`,
type: "terminal_status",
terminalStatus: "failed",
status: "failed",
failureDomain: (error as any)?.layer === "billing" ? "billing" : "infrastructure",
code,
failureKind: code,
errorCode: code,
message,
retryable: state.retryable,
attempt: state.attempt,
maxAttempts: state.maxAttempts,
createdAt,
terminal: true,
valuesPrinted: false
}
});
}
function currentActivityAttempt() {
try { return Context.current().info.attempt; } catch { return 1; }
}
function retryDelayMs(activity: WorkbenchActivityConfig, attempt: number) { return Math.min(activity.retryMaximumIntervalMs, activity.retryInitialIntervalMs * (2 ** Math.max(0, attempt - 1))); }
function positiveInteger(value: unknown, fallback: number) { const parsed = Number(value); return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback; }
function errorCode(error: unknown) { return String((error as any)?.code ?? "workbench_activity_failed").trim() || "workbench_activity_failed"; }
function errorMessage(error: unknown) { return String((error as any)?.message ?? "Workbench activity failed").trim() || "Workbench activity failed"; }
function userErrorMessage(error: unknown) { return String((error as any)?.userMessage ?? (error as any)?.message ?? "Workbench activity failed").trim() || "Workbench activity failed"; }