feat: 补充 runner Job dry-run 骨架
This commit is contained in:
+41
-19
@@ -1,35 +1,57 @@
|
||||
import { ManagerClient } from "../mgr/client.js";
|
||||
import { RunnerManagerApi, failureKindFromError, terminalStatusForFailure, errorMessage } from "./manager-api.js";
|
||||
import { runBackendTurn, type BackendAdapterOptions } from "../backend/adapter.js";
|
||||
import type { CommandRecord, JsonRecord, RunRecord, RunnerRecord } from "../common/types.js";
|
||||
import type { BackendProfile, JsonRecord, RunRecord, RunnerRecord } from "../common/types.js";
|
||||
|
||||
export interface RunnerOnceOptions extends BackendAdapterOptions {
|
||||
managerUrl: string;
|
||||
runId: string;
|
||||
commandId?: string;
|
||||
runnerId?: string;
|
||||
attemptId?: string;
|
||||
leaseMs?: number;
|
||||
backendProfile?: BackendProfile;
|
||||
placement?: "host-process" | "kubernetes-job";
|
||||
sourceCommit?: string;
|
||||
jobName?: string;
|
||||
podName?: string;
|
||||
logPath?: string;
|
||||
}
|
||||
|
||||
export async function runOnce(options: RunnerOnceOptions): Promise<JsonRecord> {
|
||||
const client = new ManagerClient(options.managerUrl);
|
||||
const runner = await client.post("/api/v1/runners/register", {
|
||||
id: options.runnerId ?? undefined,
|
||||
const api = new RunnerManagerApi(options.managerUrl);
|
||||
const leaseMs = options.leaseMs ?? 60_000;
|
||||
const attemptId = options.attemptId ?? `attempt_${Date.now().toString(36)}`;
|
||||
const runner = await api.register({
|
||||
runId: options.runId,
|
||||
attemptId: options.attemptId ?? `attempt_${Date.now().toString(36)}`,
|
||||
backendProfile: "codex",
|
||||
placement: "host-process",
|
||||
sourceCommit: process.env.AGENTRUN_SOURCE_COMMIT ?? "unknown",
|
||||
} as JsonRecord) as unknown as RunnerRecord;
|
||||
const claimed = await client.post(`/api/v1/runs/${options.runId}/claim`, { runnerId: runner.id, leaseMs: options.leaseMs ?? 60_000 }) as unknown as RunRecord;
|
||||
const commandsResponse = await client.get(`/api/v1/runs/${options.runId}/commands?afterSeq=0&limit=20`) as { items?: CommandRecord[] };
|
||||
const command = commandsResponse.items?.find((item) => item.state === "pending" && item.type === "turn");
|
||||
if (!command) {
|
||||
await client.patch(`/api/v1/runs/${options.runId}/status`, { terminalStatus: "blocked", failureKind: "schema-invalid", failureMessage: "no pending turn command" });
|
||||
return { runner, claimed, terminalStatus: "blocked", failureKind: "schema-invalid" };
|
||||
attemptId,
|
||||
backendProfile: options.backendProfile ?? "codex",
|
||||
placement: options.placement ?? "host-process",
|
||||
sourceCommit: options.sourceCommit ?? process.env.AGENTRUN_SOURCE_COMMIT ?? "unknown",
|
||||
...(options.runnerId ? { runnerId: options.runnerId } : {}),
|
||||
...(options.jobName ? { jobName: options.jobName } : {}),
|
||||
...(options.podName ? { podName: options.podName } : {}),
|
||||
...(options.logPath ? { logPath: options.logPath } : {}),
|
||||
}) as RunnerRecord;
|
||||
let claimed: RunRecord;
|
||||
try {
|
||||
claimed = await api.claim(options.runId, runner.id, leaseMs);
|
||||
await api.heartbeat(options.runId, runner.id, leaseMs);
|
||||
} catch (error) {
|
||||
const failureKind = failureKindFromError(error);
|
||||
if (failureKind !== "runner-lease-conflict") {
|
||||
await api.reportFailure(options.runId, { terminalStatus: terminalStatusForFailure(failureKind), failureKind, failureMessage: errorMessage(error) });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
await client.post(`/api/v1/commands/${command.id}/ack`, {});
|
||||
const commandsResponse = await api.pollCommands(options.runId, { afterSeq: 0, limit: 20, ...(options.commandId ? { commandId: options.commandId } : {}) });
|
||||
const command = commandsResponse.selected;
|
||||
if (!command) {
|
||||
await api.reportStatus(options.runId, { terminalStatus: "blocked", failureKind: "schema-invalid", failureMessage: "no pending turn command" });
|
||||
return { runner, claimed, terminalStatus: "blocked", failureKind: "schema-invalid", polledCommands: commandsResponse.items.length };
|
||||
}
|
||||
await api.ackCommand(command.id);
|
||||
const result = await runBackendTurn(claimed, command, options);
|
||||
for (const event of result.events) await client.post(`/api/v1/runs/${options.runId}/events`, event as unknown as JsonRecord);
|
||||
const finalRun = await client.patch(`/api/v1/runs/${options.runId}/status`, { terminalStatus: result.terminalStatus, failureKind: result.failureKind, failureMessage: result.failureMessage }) as unknown as RunRecord;
|
||||
for (const event of result.events) await api.appendEvent(options.runId, event);
|
||||
const finalRun = await api.reportStatus(options.runId, { terminalStatus: result.terminalStatus, failureKind: result.failureKind, failureMessage: result.failureMessage }) as RunRecord;
|
||||
return { runner, commandId: command.id, terminalStatus: result.terminalStatus, failureKind: result.failureKind, run: finalRun } as JsonRecord;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user