69 lines
4.3 KiB
TypeScript
69 lines
4.3 KiB
TypeScript
import path from "node:path";
|
|
|
|
import { harnessRLRuntime } from "../harnessrl/runtime.ts";
|
|
import { createApiCaseRunTransport, createLocalCaseRunTransport } from "../harnessrl/transport.ts";
|
|
import { createWorkbenchCaseRunLauncher } from "./caserun-bridge.ts";
|
|
import { createCloudWorkbenchApplication } from "./cloud-application.ts";
|
|
import { createWorkbenchDispatcher } from "./dispatcher.ts";
|
|
import { createNativeTestWorkbenchApplication } from "./native-application.ts";
|
|
import { createNativeAgentRunWorkbenchApplication } from "./native-agentrun-application.ts";
|
|
import { createNativeTestTemporalGateway } from "./native-temporal.ts";
|
|
import { createWorkbenchTemporalGateway } from "./temporal.ts";
|
|
import { createWorkbenchKafkaEventPublisher } from "./kafka-event-publisher.ts";
|
|
import type { WorkbenchEventPublisher } from "./contracts.ts";
|
|
|
|
export function workbenchRuntime(env: Record<string, string | undefined> = process.env, options: { eventPublisher?: WorkbenchEventPublisher | null; caseRunTransportMode?: "local" | "api" } = {}) {
|
|
const mode = String(env.WORKBENCH_MODE ?? "").trim();
|
|
if (mode !== "native-test" && mode !== "agentrun-native" && mode !== "temporal") throw codedError("workbench_mode_required", "WORKBENCH_MODE must be native-test, agentrun-native, or temporal");
|
|
const stateFile = path.resolve(String(env.WORKBENCH_NATIVE_STATE_FILE ?? ".state/workbench-native/state.json"));
|
|
const eventPublisher = mode === "native-test"
|
|
? null
|
|
: Object.hasOwn(options, "eventPublisher")
|
|
? options.eventPublisher ?? null
|
|
: createWorkbenchKafkaEventPublisher({ env });
|
|
const application = mode === "native-test"
|
|
? createNativeTestWorkbenchApplication({ stateFile })
|
|
: mode === "agentrun-native"
|
|
? createNativeAgentRunWorkbenchApplication({ stateFile, env, eventPublisher })
|
|
: createCloudWorkbenchApplication({ baseUrl: String(env.WORKBENCH_CLOUD_API_URL ?? ""), authorization: String(env.WORKBENCH_CLOUD_API_AUTHORIZATION ?? ""), eventPublisher });
|
|
const temporalAddress = String(env.WORKBENCH_TEMPORAL_ADDRESS ?? env.TEMPORAL_ADDRESS ?? "");
|
|
const temporalNamespace = String(env.WORKBENCH_TEMPORAL_NAMESPACE ?? "unidesk");
|
|
const taskQueue = String(env.WORKBENCH_TEMPORAL_TASK_QUEUE ?? "hwlab-v03-workbench");
|
|
const activity = mode === "native-test" ? null : {
|
|
startToCloseTimeoutMs: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_START_TO_CLOSE_TIMEOUT_MS, "WORKBENCH_ACTIVITY_START_TO_CLOSE_TIMEOUT_MS"),
|
|
retryInitialIntervalMs: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_RETRY_INITIAL_INTERVAL_MS, "WORKBENCH_ACTIVITY_RETRY_INITIAL_INTERVAL_MS"),
|
|
retryMaximumIntervalMs: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_RETRY_MAXIMUM_INTERVAL_MS, "WORKBENCH_ACTIVITY_RETRY_MAXIMUM_INTERVAL_MS"),
|
|
retryMaximumAttempts: requiredPositiveInteger(env.WORKBENCH_ACTIVITY_RETRY_MAXIMUM_ATTEMPTS, "WORKBENCH_ACTIVITY_RETRY_MAXIMUM_ATTEMPTS")
|
|
};
|
|
const temporal = mode === "native-test" ? createNativeTestTemporalGateway(application) : createWorkbenchTemporalGateway({ address: temporalAddress, namespace: temporalNamespace, taskQueue, activity: activity! });
|
|
const caseRunTransportMode = options.caseRunTransportMode ?? "local";
|
|
const caseRun = createWorkbenchCaseRunLauncher({
|
|
application,
|
|
async transport() {
|
|
if (caseRunTransportMode === "api") {
|
|
return createApiCaseRunTransport({
|
|
baseUrl: String(env.WORKBENCH_CASERUN_API_URL ?? ""),
|
|
apiKey: String(env.WORKBENCH_CASERUN_API_KEY ?? "") || undefined,
|
|
});
|
|
}
|
|
const runtime = harnessRLRuntime(env);
|
|
return createLocalCaseRunTransport({ service: runtime.service, close: () => runtime.service.close() });
|
|
},
|
|
});
|
|
return {
|
|
mode,
|
|
stateFile,
|
|
temporalAddress,
|
|
temporalNamespace,
|
|
taskQueue, activity,
|
|
eventPublisher,
|
|
application,
|
|
temporal,
|
|
dispatch: createWorkbenchDispatcher({ application, temporal, mode, caseRun }),
|
|
async close() { await temporal.close(); await application.close?.(); }
|
|
};
|
|
}
|
|
|
|
function codedError(code: string, message: string) { return Object.assign(new Error(message), { code }); }
|
|
function requiredPositiveInteger(value: string | undefined, name: string) { const parsed = Number.parseInt(String(value ?? ""), 10); if (!Number.isSafeInteger(parsed) || parsed <= 0) throw codedError("workbench_temporal_config_invalid", `${name} must be a positive integer`); return parsed; }
|