215 lines
8.6 KiB
TypeScript
215 lines
8.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "bun:test";
|
|
|
|
import { submitAgentRunChatTurn } from "./code-agent-agentrun-adapter.ts";
|
|
import { createCodeAgentTraceStore } from "./code-agent-trace-store.ts";
|
|
|
|
const sourceCommit = "0123456789abcdef0123456789abcdef01234567";
|
|
|
|
test("fresh gpt.pika turn returns running only after durable dispatch intent is confirmed", async () => {
|
|
const traceId = "trc_gpt_pika_durable_admission";
|
|
const calls = [];
|
|
const traceStore = createCodeAgentTraceStore();
|
|
const result = await submitAgentRunChatTurn({
|
|
traceId,
|
|
traceStore,
|
|
params: {
|
|
ownerUserId: "usr_gpt_pika",
|
|
projectId: "pikasTech/HWLAB",
|
|
conversationId: "cnv_gpt_pika_durable",
|
|
sessionId: "ses_gpt_pika_durable",
|
|
message: "verify durable admission",
|
|
providerProfile: "gpt.pika"
|
|
},
|
|
options: {
|
|
env: agentRunTestEnv(),
|
|
accessController: {
|
|
async codeAgentToolCapabilitiesForOwner() {
|
|
return { tools: { hwpod: { allowed: true } } };
|
|
},
|
|
store: {
|
|
async findActiveDefaultApiKeyForUser() {
|
|
return { displaySecret: "hwl_live_must_not_be_persisted" };
|
|
}
|
|
}
|
|
},
|
|
fetchImpl: createAgentRunFetch(calls, {
|
|
dispatchIntent: {
|
|
id: "dispatch_gpt_pika_durable",
|
|
state: "pending",
|
|
runnerJobId: "rjob_gpt_pika_durable",
|
|
attemptCount: 0,
|
|
durable: true
|
|
}
|
|
})
|
|
}
|
|
});
|
|
|
|
assert.equal(result.status, "running");
|
|
assert.equal(result.agentRun.runId, "run_gpt_pika_durable");
|
|
assert.equal(result.agentRun.commandId, "cmd_gpt_pika_durable");
|
|
assert.equal(result.agentRun.dispatchIntentId, "dispatch_gpt_pika_durable");
|
|
assert.equal(result.agentRun.dispatchIntentState, "pending");
|
|
assert.equal(result.agentRun.runnerJobId, "rjob_gpt_pika_durable");
|
|
assert.equal(result.agentRun.durableDispatch, true);
|
|
assert.equal(result.agentRun.status, "dispatch-intent-pending");
|
|
|
|
const runCall = calls.find((call) => call.path === "/api/v1/runs");
|
|
assert.ok(runCall);
|
|
assert.deepEqual(runCall.body.workspaceRef, { kind: "opaque", path: "." });
|
|
const commandCall = calls.find((call) => call.path.endsWith("/commands"));
|
|
assert.ok(commandCall);
|
|
assert.equal(commandCall.body.idempotencyKey, traceId);
|
|
assert.equal(commandCall.body.payload.traceId, traceId);
|
|
assert.equal(commandCall.body.payload.sessionId, "ses_agentrun_gpt_pika_durable");
|
|
assert.equal(commandCall.body.payload.hwlabSessionId, "ses_gpt_pika_durable");
|
|
assert.equal(commandCall.body.payload.conversationId, "cnv_gpt_pika_durable");
|
|
assert.equal(commandCall.body.dispatch.kind, "kubernetes-job");
|
|
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "commandId"), false);
|
|
assert.equal(commandCall.body.dispatch.input.idempotencyKey, traceId);
|
|
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "namespace"), false);
|
|
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "image"), false);
|
|
assert.equal(Object.hasOwn(commandCall.body.dispatch.input, "serviceAccountName"), false);
|
|
assert.equal(commandCall.body.dispatch.input.transientEnv.every((entry) => entry.sensitive === false), true);
|
|
assert.equal(commandCall.body.dispatch.input.transientEnv.every((entry) => !entry.name.startsWith("AGENTRUN_") && !entry.name.startsWith("KUBERNETES_")), true);
|
|
assert.equal(JSON.stringify(commandCall.body).includes("hwl_live_must_not_be_persisted"), false);
|
|
assert.equal(calls.some((call) => call.path.endsWith("/runner-jobs")), false);
|
|
assert.ok(traceStore.snapshot(traceId).events.some((event) => event.label === "agentrun:dispatch-intent:admitted"));
|
|
});
|
|
|
|
test("fresh turn rejects an unconfirmed dispatch intent instead of returning false running", async () => {
|
|
const calls = [];
|
|
await assert.rejects(
|
|
submitAgentRunChatTurn({
|
|
traceId: "trc_dispatch_intent_missing",
|
|
traceStore: createCodeAgentTraceStore(),
|
|
params: {
|
|
sessionId: "ses_dispatch_intent_missing",
|
|
conversationId: "cnv_dispatch_intent_missing",
|
|
message: "must fail admission",
|
|
providerProfile: "gpt.pika"
|
|
},
|
|
options: {
|
|
env: agentRunTestEnv(),
|
|
fetchImpl: createAgentRunFetch(calls, { dispatchIntent: null })
|
|
}
|
|
}),
|
|
(error) => error?.code === "agentrun_dispatch_intent_unconfirmed" && error?.statusCode === 502
|
|
);
|
|
assert.equal(calls.some((call) => call.path.endsWith("/runner-jobs")), false);
|
|
});
|
|
|
|
test("fresh turn rejects a durable dispatch intent with missing contract fields", async () => {
|
|
await assert.rejects(
|
|
submitAgentRunChatTurn({
|
|
traceId: "trc_dispatch_intent_attempt_missing",
|
|
traceStore: createCodeAgentTraceStore(),
|
|
params: {
|
|
sessionId: "ses_dispatch_intent_attempt_missing",
|
|
message: "must require explicit attempt count",
|
|
providerProfile: "gpt.pika"
|
|
},
|
|
options: {
|
|
env: agentRunTestEnv(),
|
|
fetchImpl: createAgentRunFetch([], {
|
|
dispatchIntent: {
|
|
id: "dispatch_attempt_missing",
|
|
state: "pending",
|
|
runnerJobId: "rjob_attempt_missing",
|
|
durable: true
|
|
}
|
|
})
|
|
}
|
|
}),
|
|
(error) => error?.code === "agentrun_dispatch_intent_unconfirmed" && error?.statusCode === 502
|
|
);
|
|
});
|
|
|
|
for (const terminal of [
|
|
{ state: "failed", code: "agentrun_dispatch_intent_failed", statusCode: 503 },
|
|
{ state: "cancelled", code: "agentrun_dispatch_intent_cancelled", statusCode: 409 }
|
|
]) {
|
|
test(`fresh turn exposes ${terminal.state} durable dispatch replay instead of returning running`, async () => {
|
|
await assert.rejects(
|
|
submitAgentRunChatTurn({
|
|
traceId: `trc_dispatch_intent_${terminal.state}`,
|
|
traceStore: createCodeAgentTraceStore(),
|
|
params: {
|
|
sessionId: `ses_dispatch_intent_${terminal.state}`,
|
|
message: `surface ${terminal.state} admission`,
|
|
providerProfile: "gpt.pika"
|
|
},
|
|
options: {
|
|
env: agentRunTestEnv(),
|
|
fetchImpl: createAgentRunFetch([], {
|
|
dispatchIntent: {
|
|
id: `dispatch_terminal_${terminal.state}`,
|
|
state: terminal.state,
|
|
runnerJobId: `rjob_terminal_${terminal.state}`,
|
|
attemptCount: 2,
|
|
durable: true
|
|
}
|
|
})
|
|
}
|
|
}),
|
|
(error) => error?.code === terminal.code && error?.statusCode === terminal.statusCode
|
|
);
|
|
});
|
|
}
|
|
|
|
function agentRunTestEnv() {
|
|
return {
|
|
AGENTRUN_MGR_URL: "http://127.0.0.1:65535",
|
|
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_DISPATCH_RETRY_MAX: "0",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "NC01",
|
|
HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: sourceCommit,
|
|
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "gpt.pika",
|
|
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: "caller-controlled-namespace-must-be-ignored",
|
|
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_IMAGE: "caller-controlled-image-must-be-ignored",
|
|
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_SERVICE_ACCOUNT: "caller-controlled-service-account-must-be-ignored",
|
|
HWLAB_RUNTIME_API_URL: "http://hwlab-cloud-api.hwlab-v03.svc.cluster.local:6667",
|
|
HWLAB_RUNTIME_WEB_URL: "http://hwlab-cloud-web.hwlab-v03.svc.cluster.local:8080",
|
|
HWLAB_RUNTIME_NAMESPACE: "hwlab-v03",
|
|
HWLAB_RUNTIME_LANE: "v03",
|
|
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
|
|
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1",
|
|
UNIDESK_MAIN_SERVER_IP: "http://127.0.0.1:18081"
|
|
};
|
|
}
|
|
|
|
function createAgentRunFetch(calls, { dispatchIntent }) {
|
|
return async (url, init = {}) => {
|
|
const parsed = new URL(url);
|
|
const method = init.method ?? "GET";
|
|
const body = init.body ? JSON.parse(String(init.body)) : null;
|
|
calls.push({ method, path: parsed.pathname, body });
|
|
const send = (data) => new Response(JSON.stringify({ ok: true, data }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" }
|
|
});
|
|
if (method === "POST" && parsed.pathname === "/api/v1/sessions") {
|
|
return send({ session: { id: "ses_agentrun_gpt_pika_durable", metadata: {} } });
|
|
}
|
|
if (method === "POST" && parsed.pathname === "/api/v1/runs") {
|
|
return send({
|
|
id: "run_gpt_pika_durable",
|
|
status: "pending",
|
|
sessionRef: body.sessionRef,
|
|
backendProfile: body.backendProfile
|
|
});
|
|
}
|
|
if (method === "POST" && parsed.pathname === "/api/v1/runs/run_gpt_pika_durable/commands") {
|
|
return send({
|
|
id: "cmd_gpt_pika_durable",
|
|
runId: "run_gpt_pika_durable",
|
|
state: "pending",
|
|
type: "turn",
|
|
seq: 1,
|
|
dispatchIntent
|
|
});
|
|
}
|
|
throw new Error(`unexpected AgentRun call ${method} ${parsed.pathname}`);
|
|
};
|
|
}
|