fix(v02): report AgentRun result contract
This commit is contained in:
@@ -2,17 +2,6 @@ import { randomUUID } from "node:crypto";
|
||||
|
||||
import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts";
|
||||
import { decorateCodeAgentSession } from "./code-agent-session-lifecycle.ts";
|
||||
import {
|
||||
CODEX_APP_SERVER_PROTOCOL,
|
||||
CODEX_APP_SERVER_WIRE_API,
|
||||
CODEX_STDIO_BACKEND,
|
||||
CODEX_STDIO_CAPABILITY_LEVEL,
|
||||
CODEX_STDIO_IMPLEMENTATION_TYPE,
|
||||
CODEX_STDIO_PROVIDER,
|
||||
CODEX_STDIO_RUNNER_KIND,
|
||||
CODEX_STDIO_SESSION_MODE,
|
||||
longLivedSessionGate
|
||||
} from "./codex-stdio-session.ts";
|
||||
import {
|
||||
parsePositiveInteger,
|
||||
safeConversationId,
|
||||
@@ -31,9 +20,13 @@ const DEFAULT_PROVIDER_ID = "G14";
|
||||
const DEFAULT_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
||||
const DEFAULT_RUNNER_NAMESPACE = "agentrun-v01";
|
||||
const DEFAULT_TIMEOUT_MS = 1_200_000;
|
||||
const AGENTRUN_BACKEND_PREFIX = ADAPTER_ID;
|
||||
const AGENTRUN_RUNNER_KIND = "agentrun-v01-shared-runner";
|
||||
const AGENTRUN_SESSION_MODE = "agentrun-v01-durable-session";
|
||||
const AGENTRUN_IMPLEMENTATION_TYPE = "agentrun-v01-shared-execution-infra";
|
||||
const AGENTRUN_CAPABILITY_LEVEL = "agentrun-v01-shared-code-agent-session";
|
||||
const AGENTRUN_PROVIDER_TRACE_PROTOCOL = "agentrun-v01-jsonrpc";
|
||||
const AGENTRUN_PROVIDER_TRACE_WIRE_API = "agentrun-v01-command-result";
|
||||
const AGENTRUN_BACKENDS = Object.freeze(["codex", "deepseek", "minimax-m3"]);
|
||||
const THREAD_CONTINUITY_POLICY = "hwlab-agentrun-v01-reuse-runner-thread";
|
||||
const SESSION_POLICY_RUN_LOCAL = "hwlab-agentrun-v01-session-runner-reuse";
|
||||
@@ -82,7 +75,7 @@ export function describeAgentRunAdapterAvailability(env = process.env, options =
|
||||
const managerHost = hostForUrl(managerUrl);
|
||||
const repoHost = hostForUrl(repoUrl);
|
||||
const status = ready ? "agentrun-ready" : "blocked";
|
||||
const capabilityLevel = ready ? CODEX_STDIO_CAPABILITY_LEVEL : "blocked";
|
||||
const capabilityLevel = ready ? AGENTRUN_CAPABILITY_LEVEL : "blocked";
|
||||
|
||||
return {
|
||||
endpoint: "POST /v1/agent/chat",
|
||||
@@ -224,13 +217,13 @@ export function initialAgentRunChatResult({ params = {}, options = {}, traceId }
|
||||
messageId: `msg_${safeTraceId(traceId)?.slice(4) || randomUUID()}`,
|
||||
createdAt: timestamp,
|
||||
updatedAt: timestamp,
|
||||
provider: CODEX_STDIO_PROVIDER,
|
||||
provider: providerForBackendProfile(backendProfile),
|
||||
model: modelForBackendProfile(backendProfile, env),
|
||||
backend: CODEX_STDIO_BACKEND,
|
||||
backend: backendForBackendProfile(backendProfile),
|
||||
infrastructureBackend: `agentrun-v01/${backendProfile}`,
|
||||
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
|
||||
sessionMode: CODEX_STDIO_SESSION_MODE,
|
||||
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
||||
capabilityLevel: AGENTRUN_CAPABILITY_LEVEL,
|
||||
sessionMode: AGENTRUN_SESSION_MODE,
|
||||
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||
threadContinuityPolicy: THREAD_CONTINUITY_POLICY,
|
||||
agentRun: {
|
||||
adapter: ADAPTER_ID,
|
||||
@@ -623,13 +616,13 @@ export async function loadPersistedAgentRunResult(traceId, options = {}) {
|
||||
createdAt: session.startedAt ?? session.updatedAt ?? nowIso(options.now),
|
||||
updatedAt: session.updatedAt ?? nowIso(options.now),
|
||||
ownerUserId: session.ownerUserId,
|
||||
provider: CODEX_STDIO_PROVIDER,
|
||||
provider: providerForBackendProfile(agentRun.backendProfile ?? "deepseek"),
|
||||
model: modelForBackendProfile(agentRun.backendProfile, options.env ?? process.env),
|
||||
backend: CODEX_STDIO_BACKEND,
|
||||
backend: backendForBackendProfile(agentRun.backendProfile ?? "deepseek"),
|
||||
infrastructureBackend: `agentrun-v01/${agentRun.backendProfile ?? "deepseek"}`,
|
||||
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
|
||||
sessionMode: CODEX_STDIO_SESSION_MODE,
|
||||
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
||||
capabilityLevel: AGENTRUN_CAPABILITY_LEVEL,
|
||||
sessionMode: AGENTRUN_SESSION_MODE,
|
||||
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||
finalResponse: session.session?.finalResponse ?? null,
|
||||
traceSummary: session.session?.traceSummary ?? null,
|
||||
agentRun: { ...agentRun, adapter: ADAPTER_ID, valuesPrinted: false },
|
||||
@@ -1025,12 +1018,12 @@ function providerCredentialSecretRef(profile, env) {
|
||||
|
||||
function agentRunProviderTrace({ base = {}, result = {}, terminalStatus = null } = {}) {
|
||||
return {
|
||||
transport: "stdio",
|
||||
protocol: CODEX_APP_SERVER_PROTOCOL,
|
||||
wireApi: CODEX_APP_SERVER_WIRE_API,
|
||||
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
||||
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
||||
command: "codex app-server --listen stdio://",
|
||||
transport: "agentrun-v01",
|
||||
protocol: AGENTRUN_PROVIDER_TRACE_PROTOCOL,
|
||||
wireApi: AGENTRUN_PROVIDER_TRACE_WIRE_API,
|
||||
runnerKind: AGENTRUN_RUNNER_KIND,
|
||||
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||
command: "agentrun.v01.command.turn",
|
||||
toolName: "agentrun.v01.command.turn",
|
||||
source: ADAPTER_ID,
|
||||
backendProfile: base.agentRun?.backendProfile ?? null,
|
||||
@@ -1062,15 +1055,15 @@ function agentRunFailureAttribution({ code, message, canceled = false } = {}) {
|
||||
return {
|
||||
category: "provider_invalid_tool_call",
|
||||
retryable: true,
|
||||
userMessage: "Codex app-server/provider 返回了无效 tool-call arguments JSON;这不是 HWLAB device-pod 或 Cloud API 端点失败,请查看 providerTrace.failureKind 和 runnerTrace 定位上游 tool_call_id。",
|
||||
summary: message || "Codex app-server/provider returned invalid tool-call arguments JSON."
|
||||
userMessage: "AgentRun/provider 返回了无效 tool-call arguments JSON;这不是 HWLAB device-pod 或 Cloud API 端点失败,请查看 providerTrace.failureKind 和 runnerTrace 定位上游 tool_call_id。",
|
||||
summary: message || "AgentRun/provider returned invalid tool-call arguments JSON."
|
||||
};
|
||||
}
|
||||
if (code === "thread-resume-failed") {
|
||||
return {
|
||||
category: "thread_resume_failed",
|
||||
retryable: true,
|
||||
userMessage: "AgentRun 复用的 Codex thread 已失效;当前 turn 会按失败终止,Workbench 会清理 stale continuation 指针,下一轮自动从新 session/thread 启动。",
|
||||
userMessage: "AgentRun 复用的 thread 已失效;当前 turn 会按失败终止,Workbench 会清理 stale continuation 指针,下一轮自动从新 session/thread 启动。",
|
||||
summary: message || "AgentRun thread/resume failed for an existing thread."
|
||||
};
|
||||
}
|
||||
@@ -1083,24 +1076,29 @@ function agentRunFailureAttribution({ code, message, canceled = false } = {}) {
|
||||
}
|
||||
|
||||
function agentRunLongLivedSessionGate(base = {}) {
|
||||
return longLivedSessionGate({
|
||||
provider: CODEX_STDIO_PROVIDER,
|
||||
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
||||
session: agentRunSessionSummary(base, "idle"),
|
||||
sessionMode: CODEX_STDIO_SESSION_MODE,
|
||||
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
||||
codexStdioFeasibility: {
|
||||
ready: true,
|
||||
canStartLongLivedCodexStdio: true,
|
||||
source: ADAPTER_ID,
|
||||
valuesPrinted: false
|
||||
}
|
||||
});
|
||||
const session = agentRunSessionSummary(base, "idle");
|
||||
return {
|
||||
status: "pass",
|
||||
pass: true,
|
||||
requiredCapability: AGENTRUN_CAPABILITY_LEVEL,
|
||||
currentCapability: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||
sessionId: session.sessionId ?? null,
|
||||
sessionStatus: session.status ?? null,
|
||||
runnerKind: AGENTRUN_RUNNER_KIND,
|
||||
provider: providerForBackendProfile(base.agentRun?.backendProfile ?? "deepseek"),
|
||||
sessionMode: AGENTRUN_SESSION_MODE,
|
||||
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||
adapter: ADAPTER_ID,
|
||||
delegatedToAgentRun: true,
|
||||
codexStdio: false,
|
||||
blockers: [],
|
||||
valuesPrinted: false
|
||||
};
|
||||
}
|
||||
|
||||
function agentRunToolCalls(result = {}, status = "completed") {
|
||||
return [{
|
||||
name: "codex-app-server.turn",
|
||||
name: "agentrun.v01.command.turn",
|
||||
status,
|
||||
runId: result?.runId ?? null,
|
||||
commandId: result?.commandId ?? null,
|
||||
@@ -1584,11 +1582,12 @@ function agentRunSessionSummary(base, status) {
|
||||
conversationId: base.conversationId ?? base.agentRun?.conversationId ?? null,
|
||||
threadId: base.threadId ?? base.agentRun?.threadId ?? null,
|
||||
status,
|
||||
sessionMode: CODEX_STDIO_SESSION_MODE,
|
||||
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
||||
sessionMode: AGENTRUN_SESSION_MODE,
|
||||
runnerKind: AGENTRUN_RUNNER_KIND,
|
||||
lastTraceId: base.traceId ?? base.agentRun?.traceId ?? null,
|
||||
longLivedSession: true,
|
||||
codexStdio: true,
|
||||
codexStdio: false,
|
||||
delegatedToAgentRun: true,
|
||||
writeCapable: true,
|
||||
durable: true,
|
||||
durableSession: true,
|
||||
@@ -1624,7 +1623,7 @@ function agentRunSessionReuseSummary(base, reused, options = {}) {
|
||||
|
||||
function agentRunRunnerSummary(mapping = {}) {
|
||||
return {
|
||||
kind: CODEX_STDIO_RUNNER_KIND,
|
||||
kind: AGENTRUN_RUNNER_KIND,
|
||||
adapter: ADAPTER_ID,
|
||||
runId: mapping.runId ?? null,
|
||||
commandId: mapping.commandId ?? null,
|
||||
@@ -1633,11 +1632,12 @@ function agentRunRunnerSummary(mapping = {}) {
|
||||
jobName: mapping.jobName ?? null,
|
||||
namespace: mapping.namespace ?? null,
|
||||
backendProfile: mapping.backendProfile ?? null,
|
||||
provider: CODEX_STDIO_PROVIDER,
|
||||
sessionMode: CODEX_STDIO_SESSION_MODE,
|
||||
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
||||
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
|
||||
codexStdio: true,
|
||||
provider: providerForBackendProfile(mapping.backendProfile ?? "deepseek"),
|
||||
sessionMode: AGENTRUN_SESSION_MODE,
|
||||
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||
capabilityLevel: AGENTRUN_CAPABILITY_LEVEL,
|
||||
codexStdio: false,
|
||||
delegatedToAgentRun: true,
|
||||
writeCapable: true,
|
||||
durableSession: true,
|
||||
longLivedSession: true,
|
||||
@@ -1648,15 +1648,19 @@ function agentRunRunnerSummary(mapping = {}) {
|
||||
|
||||
function agentRunTraceMeta(env = process.env) {
|
||||
return {
|
||||
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
||||
runnerKind: AGENTRUN_RUNNER_KIND,
|
||||
workspace: resolveAgentRunRepoUrl(env),
|
||||
sandbox: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SANDBOX, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, "danger-full-access"),
|
||||
protocol: CODEX_APP_SERVER_PROTOCOL,
|
||||
sessionMode: CODEX_STDIO_SESSION_MODE,
|
||||
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE
|
||||
protocol: AGENTRUN_PROVIDER_TRACE_PROTOCOL,
|
||||
sessionMode: AGENTRUN_SESSION_MODE,
|
||||
implementationType: AGENTRUN_IMPLEMENTATION_TYPE
|
||||
};
|
||||
}
|
||||
|
||||
function backendForBackendProfile(profile) {
|
||||
return `${AGENTRUN_BACKEND_PREFIX}/${resolveAgentRunBackendProfile({}, { providerProfile: profile })}`;
|
||||
}
|
||||
|
||||
function resolveAgentRunRepoUrl(env = process.env) {
|
||||
const raw = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_REPO_URL, env.HWLAB_BOOT_READ_URL, DEFAULT_REPO_URL);
|
||||
const url = new URL(raw);
|
||||
|
||||
@@ -437,15 +437,32 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
|
||||
const payload = await pollAgentResult(port, traceId);
|
||||
validateCodeAgentChatSchema(payload);
|
||||
assert.equal(payload.status, "completed");
|
||||
assert.equal(payload.provider, "codex-stdio");
|
||||
assert.equal(payload.provider, "deepseek");
|
||||
assert.equal(payload.backend, "agentrun-v01/deepseek");
|
||||
assert.equal(payload.infrastructureBackend, "agentrun-v01/deepseek");
|
||||
assert.equal(payload.providerTrace.protocol, "codex-app-server-jsonrpc-stdio");
|
||||
assert.equal(payload.providerTrace.command, "codex app-server --listen stdio://");
|
||||
assert.equal(payload.capabilityLevel, "agentrun-v01-shared-code-agent-session");
|
||||
assert.equal(payload.sessionMode, "agentrun-v01-durable-session");
|
||||
assert.equal(payload.implementationType, "agentrun-v01-shared-execution-infra");
|
||||
assert.equal(payload.runner.kind, "agentrun-v01-shared-runner");
|
||||
assert.equal(payload.runner.provider, "deepseek");
|
||||
assert.equal(payload.runner.codexStdio, false);
|
||||
assert.equal(payload.runner.delegatedToAgentRun, true);
|
||||
assert.equal(payload.providerTrace.protocol, "agentrun-v01-jsonrpc");
|
||||
assert.equal(payload.providerTrace.command, "agentrun.v01.command.turn");
|
||||
assert.equal(payload.providerTrace.runnerKind, "agentrun-v01-shared-runner");
|
||||
assert.equal(payload.providerTrace.terminalStatus, "completed");
|
||||
assert.equal(payload.longLivedSessionGate.status, "pass");
|
||||
assert.equal(payload.longLivedSessionGate.provider, "deepseek");
|
||||
assert.equal(payload.longLivedSessionGate.codexStdio, false);
|
||||
assert.equal(payload.longLivedSessionGate.delegatedToAgentRun, true);
|
||||
assert.equal(payload.agentRun.runId, "run_hwlab_adapter");
|
||||
assert.equal(payload.agentRun.commandId, "cmd_hwlab_adapter");
|
||||
assert.equal(payload.agentRun.jobName, "agentrun-v01-runner-hwlab-adapter");
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
assert.equal(serializedPayload.includes("repo-owned-codex"), false);
|
||||
assert.equal(serializedPayload.includes("codex-app-server-stdio"), false);
|
||||
assert.equal(serializedPayload.includes("\"provider\":\"codex-stdio\""), false);
|
||||
assert.equal(serializedPayload.includes("\"codexStdio\":true"), false);
|
||||
assert.match(payload.reply.content, /AgentRun adapter/u);
|
||||
assert.ok(payload.runnerTrace.events.some((event) => event.label === "agentrun:backend:runner-job-created"));
|
||||
const bundleEvent = payload.runnerTrace.events.find((event) => event.label === "agentrun:backend:resource-bundle-materialized");
|
||||
@@ -560,6 +577,10 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
|
||||
const secondPayload = await pollAgentResult(port, secondTraceId);
|
||||
validateCodeAgentChatSchema(secondPayload);
|
||||
assert.equal(secondPayload.status, "completed");
|
||||
assert.equal(secondPayload.provider, "deepseek");
|
||||
assert.equal(secondPayload.backend, "agentrun-v01/deepseek");
|
||||
assert.equal(secondPayload.runner.kind, "agentrun-v01-shared-runner");
|
||||
assert.equal(secondPayload.runner.codexStdio, false);
|
||||
assert.equal(secondPayload.agentRun.runId, "run_hwlab_adapter");
|
||||
assert.equal(secondPayload.agentRun.commandId, "cmd_hwlab_adapter_second");
|
||||
assert.equal(secondPayload.providerTrace.commandId, "cmd_hwlab_adapter_second");
|
||||
@@ -889,6 +910,7 @@ test("cloud api AgentRun adapter exposes invalid tool-call attribution in result
|
||||
assert.equal(payload.error.code, "provider-invalid-tool-call");
|
||||
assert.equal(payload.error.category, "provider_invalid_tool_call");
|
||||
assert.match(payload.error.userMessage, /无效 tool-call arguments JSON/u);
|
||||
assert.match(payload.error.userMessage, /AgentRun\/provider/u);
|
||||
assert.match(payload.error.userMessage, /不是 HWLAB device-pod/u);
|
||||
assert.equal(payload.blocker.category, "provider_invalid_tool_call");
|
||||
assert.match(payload.blocker.summary, /invalid function arguments json string/u);
|
||||
@@ -897,6 +919,10 @@ test("cloud api AgentRun adapter exposes invalid tool-call attribution in result
|
||||
assert.equal(payload.sessionReuse.threadId, "thread_invalid_tool");
|
||||
assert.equal(payload.agentRun.reuseEligible, false);
|
||||
assert.equal(payload.reuseEligible, false);
|
||||
const serializedPayload = JSON.stringify(payload);
|
||||
assert.equal(serializedPayload.includes("repo-owned-codex"), false);
|
||||
assert.equal(serializedPayload.includes("codex-app-server-stdio"), false);
|
||||
assert.equal(serializedPayload.includes("\"provider\":\"codex-stdio\""), false);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
||||
await new Promise((resolve, reject) => agentRunServer.close((error) => (error ? reject(error) : resolve())));
|
||||
@@ -1031,7 +1057,12 @@ test("cloud api AgentRun adapter maps minimax-m3 provider profile to AgentRun ba
|
||||
const payload = await pollAgentResult(port, traceId);
|
||||
validateCodeAgentChatSchema(payload);
|
||||
assert.equal(payload.status, "completed");
|
||||
assert.equal(payload.provider, "minimax-m3");
|
||||
assert.equal(payload.backend, "agentrun-v01/minimax-m3");
|
||||
assert.equal(payload.infrastructureBackend, "agentrun-v01/minimax-m3");
|
||||
assert.equal(payload.runner.kind, "agentrun-v01-shared-runner");
|
||||
assert.equal(payload.runner.provider, "minimax-m3");
|
||||
assert.equal(payload.runner.codexStdio, false);
|
||||
assert.equal(payload.agentRun.backendProfile, "minimax-m3");
|
||||
assert.equal(payload.sessionId, "ses_server-test-minimax-m3");
|
||||
assert.equal(payload.agentRun.sessionId, "ses_agentrun_minimax_m3_server_test_minimax_m3");
|
||||
|
||||
Reference in New Issue
Block a user