feat: add manager otel spans

This commit is contained in:
lyon
2026-06-19 23:05:50 +08:00
parent 731059cbf8
commit 18c3667ee0
2 changed files with 189 additions and 8 deletions
+49 -8
View File
@@ -20,6 +20,7 @@ import { getProviderProfileConfig, getProviderProfileValidation, listBackendCapa
import { listToolCredentials, setGithubSshToolCredential, showToolCredential } from "./tool-credentials.js";
import { aipodSpecFromInput, applyAipodSpec, deleteAipodSpec, listAipodSpecs, renderAipodSpecByName, showAipodSpec } from "../common/aipod-specs.js";
import { staticWorkReadyCapabilitySummary } from "../common/work-ready.js";
import { emitAgentRunOtelSpan } from "../common/otel-trace.js";
function pvcOptions(defaults: { kubectlCommand?: string } | undefined): SessionPvcOptions {
return defaults?.kubectlCommand ? { kubectlCommand: defaults.kubectlCommand } : {};
@@ -652,17 +653,36 @@ async function route({ method, url, body, store, sourceCommit, authSummary, runn
await refreshRunningQueueTasksForRead(store, queue);
return await queueCommanderForRead(store, queue, url.searchParams.get("readerId"));
}
if (method === "POST" && path === "/api/v1/runs") return await store.createRun(validateCreateRun(body)) as unknown as JsonValue;
if (method === "POST" && path === "/api/v1/runs") {
const startedAt = Date.now();
const run = await store.createRun(validateCreateRun(body));
void emitAgentRunOtelSpan("run_created", run, process.env, { startTimeMs: startedAt, kind: 2, attributes: { "http.method": "POST", "http.route": "/api/v1/runs", "http.status_code": 200, backendProfile: run.backendProfile, providerId: run.providerId } });
return run as unknown as JsonValue;
}
const runMatch = path.match(/^\/api\/v1\/runs\/([^/]+)$/u);
if (method === "GET" && runMatch) return await store.getRun(runMatch[1] ?? "") as unknown as JsonValue;
const eventMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/events$/u);
if (method === "GET" && eventMatch) {
const startedAt = Date.now();
const runId = eventMatch[1] ?? "";
const afterSeq = integerQuery(url, "afterSeq", 0);
const limit = integerQuery(url, "limit", 100);
return { items: await store.listEvents(eventMatch[1] ?? "", afterSeq, limit) as unknown as JsonValue };
const run = await store.getRun(runId);
const items = await store.listEvents(runId, afterSeq, limit);
void emitAgentRunOtelSpan("projection_sync", run, process.env, { startTimeMs: startedAt, kind: 2, attributes: { "http.method": "GET", "http.route": "/api/v1/runs/:runId/events", "http.status_code": 200, afterSeq, limit, eventCount: items.length } });
return { items: items as unknown as JsonValue };
}
const runResultMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/result$/u);
if (method === "GET" && runResultMatch) return await buildRunResult(store, runResultMatch[1] ?? "", url.searchParams.get("commandId") ?? undefined) as JsonValue;
if (method === "GET" && runResultMatch) {
const startedAt = Date.now();
const runId = runResultMatch[1] ?? "";
const run = await store.getRun(runId);
const commandId = url.searchParams.get("commandId") ?? undefined;
const command = commandId ? await store.getCommand(commandId) : null;
const result = await buildRunResult(store, runId, commandId) as JsonValue;
void emitAgentRunOtelSpan("command_result", run, process.env, { command, startTimeMs: startedAt, kind: 2, attributes: { "http.method": "GET", "http.route": "/api/v1/runs/:runId/result", "http.status_code": 200, terminalStatus: typeof result === "object" && result !== null && !Array.isArray(result) ? (result as JsonRecord).terminalStatus ?? null : null } });
return result;
}
const runCancelMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/cancel$/u);
if (method === "POST" && runCancelMatch) {
const record = body === null ? {} : asRecord(body, "cancel");
@@ -670,16 +690,29 @@ async function route({ method, url, body, store, sourceCommit, authSummary, runn
return await store.cancelRun(runCancelMatch[1] ?? "", reason) as unknown as JsonValue;
}
const commandCreateMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/commands$/u);
if (method === "POST" && commandCreateMatch) return await store.createCommand(commandCreateMatch[1] ?? "", validateCreateCommand(body)) as unknown as JsonValue;
if (method === "POST" && commandCreateMatch) {
const startedAt = Date.now();
const runId = commandCreateMatch[1] ?? "";
const command = await store.createCommand(runId, validateCreateCommand(body));
const run = await store.getRun(runId);
void emitAgentRunOtelSpan("command_created", run, process.env, { command, startTimeMs: startedAt, kind: 2, attributes: { "http.method": "POST", "http.route": "/api/v1/runs/:runId/commands", "http.status_code": 200, commandType: command.type, commandState: command.state } });
return command as unknown as JsonValue;
}
if (method === "GET" && commandCreateMatch) return { items: await store.listCommands(commandCreateMatch[1] ?? "", integerQuery(url, "afterSeq", 0), integerQuery(url, "limit", 20)) as unknown as JsonValue };
const runnerJobMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/runner-jobs$/u);
if (method === "POST" && runnerJobMatch) {
return await createKubernetesRunnerJob({
const startedAt = Date.now();
const runId = runnerJobMatch[1] ?? "";
const runnerJob = await createKubernetesRunnerJob({
store,
runId: runnerJobMatch[1] ?? "",
runId,
input: asRecord(body ?? {}, "runnerJob") as never,
defaults: runnerJobDefaultsForRequest(runnerJobDefaults, sourceCommit),
}) as unknown as JsonValue;
}) as unknown as JsonRecord;
const run = await store.getRun(runId);
const command = typeof runnerJob.commandId === "string" ? await store.getCommand(runnerJob.commandId) : null;
void emitAgentRunOtelSpan("runner_job_created", run, process.env, { command, startTimeMs: startedAt, kind: 2, attributes: { "http.method": "POST", "http.route": "/api/v1/runs/:runId/runner-jobs", "http.status_code": 200, jobName: typeof runnerJob.jobName === "string" ? runnerJob.jobName : null, namespace: typeof runnerJob.namespace === "string" ? runnerJob.namespace : null } });
return runnerJob as unknown as JsonValue;
}
if (method === "GET" && runnerJobMatch) {
const runId = runnerJobMatch[1] ?? "";
@@ -700,7 +733,15 @@ async function route({ method, url, body, store, sourceCommit, authSummary, runn
const commandShowMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/commands\/([^/]+)$/u);
if (method === "GET" && commandShowMatch) return await store.getCommand(commandShowMatch[2] ?? "") as unknown as JsonValue;
const commandResultMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/commands\/([^/]+)\/result$/u);
if (method === "GET" && commandResultMatch) return await buildRunResult(store, commandResultMatch[1] ?? "", commandResultMatch[2] ?? "") as JsonValue;
if (method === "GET" && commandResultMatch) {
const startedAt = Date.now();
const runId = commandResultMatch[1] ?? "";
const commandId = commandResultMatch[2] ?? "";
const [run, command] = await Promise.all([store.getRun(runId), store.getCommand(commandId)]);
const result = await buildRunResult(store, runId, commandId) as JsonValue;
void emitAgentRunOtelSpan("command_result", run, process.env, { command, startTimeMs: startedAt, kind: 2, attributes: { "http.method": "GET", "http.route": "/api/v1/runs/:runId/commands/:commandId/result", "http.status_code": 200, terminalStatus: typeof result === "object" && result !== null && !Array.isArray(result) ? (result as JsonRecord).terminalStatus ?? null : null } });
return result;
}
if (method === "POST" && path === "/api/v1/runners/register") return await store.registerRunner(asRecord(body ?? {}, "runner")) as unknown as JsonValue;
const claimMatch = path.match(/^\/api\/v1\/runs\/([^/]+)\/claim$/u);
if (method === "POST" && claimMatch) {