fix: scope agentrun sessions by backend profile (#683)
Co-authored-by: Codex <codex@local>
This commit is contained in:
@@ -119,7 +119,7 @@ G14 Code Agent 通过同一个 repo-owned Codex app-server stdio runner 承载
|
||||
| `codex-api` | 否 | `HWLAB_CODE_AGENT_CODEX_API_MODEL`,默认 `gpt-5.5` | `HWLAB_CODE_AGENT_CODEX_API_BASE_URL`,G14 默认应指向同 Pod `127.0.0.1` loopback forwarder,forwarder upstream 为 hyueapi | 独立 Codex/OpenAI-compatible Responses API 通道;不得依赖 DeepSeek bridge。 |
|
||||
| `minimax-m3` | 否 | `HWLAB_CODE_AGENT_MINIMAX_M3_MODEL`,默认 `MiniMax-M3` | 不由 HWLAB 配置;由 AgentRun `backendProfile=minimax-m3` 的 Secret/profile 承接 | HWLAB 只把该 profile 委托给 AgentRun v0.1,不在 HWLAB 新增 MiniMax API key、base URL、proxy 或 provider 实现。 |
|
||||
|
||||
`hwlab-cloud-api` 仍以 `HWLAB_CODE_AGENT_PROVIDER=codex-stdio` 运行。`HWLAB_CODE_AGENT_MODEL` 与 `HWLAB_CODE_AGENT_OPENAI_BASE_URL` 可作为 runtime-default 兜底,但前端默认 profile 是 `deepseek`,用户可以切到 `codex-api` 或 `minimax-m3`。Codex app-server 启动参数必须同时设置 provider base URL、provider `name`、`model` 和 `review_model`,否则 Codex/DeepSeek 链路可能在模型目录或 `/v1/responses` 阶段退化成 `model=None`。`minimax-m3` 是 AgentRun 委托 profile,HWLAB 只负责把用户选择透传为 AgentRun `backendProfile=minimax-m3`,不得为了该 profile 在 HWLAB 里重新读取 MiniMax Secret 或维护第二套 provider bridge。G14 cloud-api 的 `NO_PROXY/no_proxy` 必须包含 `hyueapi.com` 和 `.hyueapi.com`;`codex-api` 的 hyueapi upstream 由同 Pod loopback forwarder 直连,不能被 proxy 注入污染。
|
||||
`hwlab-cloud-api` 仍以 `HWLAB_CODE_AGENT_PROVIDER=codex-stdio` 运行。`HWLAB_CODE_AGENT_MODEL` 与 `HWLAB_CODE_AGENT_OPENAI_BASE_URL` 可作为 runtime-default 兜底,但前端默认 profile 是 `deepseek`,用户可以切到 `codex-api` 或 `minimax-m3`。Codex app-server 启动参数必须同时设置 provider base URL、provider `name`、`model` 和 `review_model`,否则 Codex/DeepSeek 链路可能在模型目录或 `/v1/responses` 阶段退化成 `model=None`。`minimax-m3` 是 AgentRun 委托 profile,HWLAB 只负责把用户选择透传为 AgentRun `backendProfile=minimax-m3`,不得为了该 profile 在 HWLAB 里重新读取 MiniMax Secret 或维护第二套 provider bridge。HWLAB 对外的 conversation/session/thread 合同不随 AgentRun backend 切换而改变;adapter 内部传给 AgentRun 的 `sessionRef.sessionId` 必须按 `backendProfile` 分域,`accessController` 持久化记录仍以 HWLAB 原始 session 归属为准,嵌套 `agentRun.sessionId` 保存 AgentRun scoped sessionRef。G14 cloud-api 的 `NO_PROXY/no_proxy` 必须包含 `hyueapi.com` 和 `.hyueapi.com`;`codex-api` 的 hyueapi upstream 由同 Pod loopback forwarder 直连,不能被 proxy 注入污染。
|
||||
|
||||
G14 `codex-api` 不得把 `http://172.26.26.227:17680/v1/responses` 作为默认 base URL;该地址只保留为 D601 legacy Code Queue runner 或历史 egress 对照线索。G14 上的 `codex-api` 应先进入同 Pod `127.0.0.1` loopback forwarder,再由 forwarder 直连 `hyueapi.com` / `.hyueapi.com`;这两个域名必须同时进入 `NO_PROXY` 和 `no_proxy`。DeepSeek profile 可以通过集群内 bridge/Moon Bridge 转换,`codex-api` 不能用 DeepSeek bridge 伪装通过,也不能因为默认 profile 切到 DeepSeek 而删除或覆盖 `codex-api` 的独立模型、base URL、auth 和最小闭环验证。
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
|
||||
}
|
||||
}
|
||||
|
||||
const sessionId = agentRunSessionIdForParams(params, traceId);
|
||||
const sessionId = scopedAgentRunSessionIdForParams(params, traceId, backendProfile);
|
||||
const runInput = buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, sessionId });
|
||||
const run = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs", {
|
||||
method: "POST",
|
||||
@@ -365,7 +365,7 @@ export function agentRunSessionEvidence(payload = {}) {
|
||||
lastSeq: payload.agentRun.lastSeq ?? 0,
|
||||
providerId: payload.agentRun.providerId ?? DEFAULT_PROVIDER_ID,
|
||||
reuseEligible: payload.agentRun.reuseEligible ?? false,
|
||||
sessionId: payload.sessionId ?? payload.agentRun.sessionId ?? null,
|
||||
sessionId: payload.agentRun.sessionId ?? payload.sessionId ?? null,
|
||||
conversationId: payload.conversationId ?? payload.agentRun.conversationId ?? null,
|
||||
threadId: payload.threadId ?? payload.agentRun.threadId ?? null,
|
||||
providerTrace: payload.providerTrace ?? payload.agentRun.providerTrace ?? null,
|
||||
@@ -398,7 +398,7 @@ function buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, ses
|
||||
valuesPrinted: false
|
||||
},
|
||||
sessionRef: {
|
||||
sessionId: sessionId ?? agentRunSessionIdForParams(params, traceId),
|
||||
sessionId: sessionId ?? scopedAgentRunSessionIdForParams(params, traceId, backendProfile),
|
||||
...(safeConversationId(params.conversationId) ? { conversationId: safeConversationId(params.conversationId) } : {}),
|
||||
metadata: {
|
||||
adapter: ADAPTER_ID,
|
||||
@@ -408,6 +408,8 @@ function buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, ses
|
||||
requestedThreadId: safeOpaqueId(params.threadId) || null,
|
||||
threadContinuityPolicy: THREAD_CONTINUITY_POLICY,
|
||||
sessionPolicy: SESSION_POLICY_RUN_LOCAL,
|
||||
agentRunSessionProfile: backendProfile,
|
||||
agentRunSessionPolicy: "backend-profile-scoped",
|
||||
valuesPrinted: false
|
||||
}
|
||||
},
|
||||
@@ -443,7 +445,7 @@ function buildAgentRunCommandInput({ params, traceId, backendProfile, sessionId
|
||||
message: prompt,
|
||||
traceId,
|
||||
conversationId: safeConversationId(params.conversationId) || null,
|
||||
sessionId: sessionId ?? agentRunSessionIdForParams(params, traceId),
|
||||
sessionId: sessionId ?? scopedAgentRunSessionIdForParams(params, traceId, backendProfile),
|
||||
hwlabSessionId: safeSessionId(params.sessionId) || null,
|
||||
threadId: null,
|
||||
requestedThreadId: safeOpaqueId(params.threadId) || null,
|
||||
@@ -491,18 +493,18 @@ function buildAgentRunTransientEnv(env = process.env) {
|
||||
}
|
||||
|
||||
async function resolveReusableAgentRun({ params = {}, options = {}, env = process.env, managerUrl, fetchImpl, timeoutMs, backendProfile, traceId, traceStore }) {
|
||||
const sessionId = agentRunSessionIdForParams(params, traceId);
|
||||
if (!safeSessionId(sessionId) || sessionId === agentRunSessionId(traceId) || typeof options.accessController?.getAgentSession !== "function") {
|
||||
const hwlabSessionId = hwlabSessionIdForParams(params, traceId);
|
||||
if (!safeSessionId(hwlabSessionId) || hwlabSessionId === agentRunSessionId(traceId) || typeof options.accessController?.getAgentSession !== "function") {
|
||||
return null;
|
||||
}
|
||||
const session = await options.accessController.getAgentSession(sessionId);
|
||||
const session = await options.accessController.getAgentSession(hwlabSessionId);
|
||||
if (!canReuseAgentRunSessionForOwner(session, params, options)) {
|
||||
traceStore.append(traceId, {
|
||||
type: "backend",
|
||||
status: "running",
|
||||
label: "agentrun:run:reuse-owner-skipped",
|
||||
message: "Stored AgentRun session is not visible to the current actor; a new runner Job will be created for this request.",
|
||||
sessionId,
|
||||
sessionId: hwlabSessionId,
|
||||
waitingFor: "agentrun-run-create",
|
||||
valuesPrinted: false
|
||||
});
|
||||
@@ -515,7 +517,7 @@ async function resolveReusableAgentRun({ params = {}, options = {}, env = proces
|
||||
status: "running",
|
||||
label: "agentrun:run:reuse-skipped",
|
||||
message: "No reusable AgentRun run was found for this HWLAB session; a new runner Job will be created.",
|
||||
sessionId,
|
||||
sessionId: hwlabSessionId,
|
||||
waitingFor: "agentrun-run-create",
|
||||
valuesPrinted: false
|
||||
});
|
||||
@@ -535,7 +537,7 @@ async function resolveReusableAgentRun({ params = {}, options = {}, env = proces
|
||||
message: `Stored AgentRun run ${mapping.runId} could not be read; a new runner Job will be created.`,
|
||||
errorCode: error?.code ?? "agentrun_run_lookup_failed",
|
||||
runId: mapping.runId,
|
||||
sessionId,
|
||||
sessionId: hwlabSessionId,
|
||||
waitingFor: "agentrun-run-create",
|
||||
valuesPrinted: false
|
||||
});
|
||||
@@ -549,7 +551,7 @@ async function resolveReusableAgentRun({ params = {}, options = {}, env = proces
|
||||
label: "agentrun:run:reuse-blocked",
|
||||
message: `Stored AgentRun run ${mapping.runId} cannot be reused: ${reason}; a new runner Job will be created.`,
|
||||
runId: mapping.runId,
|
||||
sessionId,
|
||||
sessionId: hwlabSessionId,
|
||||
waitingFor: "agentrun-run-create",
|
||||
valuesPrinted: false
|
||||
});
|
||||
@@ -559,17 +561,17 @@ async function resolveReusableAgentRun({ params = {}, options = {}, env = proces
|
||||
type: "backend",
|
||||
status: "running",
|
||||
label: "agentrun:run:reused",
|
||||
message: `AgentRun run ${mapping.runId} is reused for HWLAB session ${sessionId}; runner lease is active and no new bundle will be requested for this turn.`,
|
||||
message: `AgentRun run ${mapping.runId} is reused for HWLAB session ${hwlabSessionId}; runner lease is active and no new bundle will be requested for this turn.`,
|
||||
runId: mapping.runId,
|
||||
commandId: mapping.commandId ?? null,
|
||||
runnerId: mapping.runnerId ?? null,
|
||||
jobName: mapping.jobName ?? null,
|
||||
namespace: mapping.namespace ?? null,
|
||||
sessionId,
|
||||
sessionId: hwlabSessionId,
|
||||
waitingFor: "agentrun-command-create",
|
||||
valuesPrinted: false
|
||||
});
|
||||
return { sessionId, session, mapping, run };
|
||||
return { sessionId: mapping.sessionId ?? scopedAgentRunSessionIdForParams(params, traceId, backendProfile), hwlabSessionId, session, mapping, run };
|
||||
}
|
||||
|
||||
function isReusableAgentRunMapping(mapping, backendProfile) {
|
||||
@@ -1014,10 +1016,21 @@ function agentRunResultRefs(result = {}) {
|
||||
return refs;
|
||||
}
|
||||
|
||||
function agentRunSessionIdForParams(params = {}, traceId) {
|
||||
function hwlabSessionIdForParams(params = {}, traceId) {
|
||||
return safeSessionId(params.sessionId) || agentRunSessionId(traceId);
|
||||
}
|
||||
|
||||
function scopedAgentRunSessionIdForParams(params = {}, traceId, backendProfile) {
|
||||
const baseSessionId = hwlabSessionIdForParams(params, traceId);
|
||||
const profile = agentRunSessionProfileToken(backendProfile);
|
||||
const base = String(baseSessionId).replace(/^ses_/u, "").replace(/[^A-Za-z0-9_]+/gu, "_").replace(/^_+|_+$/gu, "") || "session";
|
||||
return `ses_agentrun_${profile}_${base}`;
|
||||
}
|
||||
|
||||
function agentRunSessionProfileToken(backendProfile) {
|
||||
return String(backendProfile ?? "deepseek").trim().toLowerCase().replace(/[^a-z0-9]+/gu, "_").replace(/^_+|_+$/gu, "") || "default";
|
||||
}
|
||||
|
||||
function agentRunSessionId(traceId) {
|
||||
return `ses_agentrun_${String(safeTraceId(traceId) || `trc_${randomUUID()}`).slice(4)}`;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
|
||||
leaseExpiresAt: "2999-01-01T00:00:00.000Z",
|
||||
backendProfile: "deepseek",
|
||||
sessionRef: {
|
||||
sessionId: "ses_server-test-agentrun",
|
||||
sessionId: "ses_agentrun_deepseek_server_test_agentrun",
|
||||
conversationId: "cnv_server-test-agentrun",
|
||||
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
|
||||
metadata: {}
|
||||
@@ -175,8 +175,10 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
|
||||
{ name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }
|
||||
]);
|
||||
assert.equal(Object.hasOwn(body.sessionRef, "threadId"), false);
|
||||
assert.equal(body.sessionRef.sessionId, "ses_server-test-agentrun");
|
||||
assert.equal(body.sessionRef.sessionId, "ses_agentrun_deepseek_server_test_agentrun");
|
||||
assert.equal(body.sessionRef.metadata.hwlabSessionId, "ses_server-test-agentrun");
|
||||
assert.equal(body.sessionRef.metadata.agentRunSessionProfile, "deepseek");
|
||||
assert.equal(body.sessionRef.metadata.agentRunSessionPolicy, "backend-profile-scoped");
|
||||
assert.equal(body.sessionRef.metadata.requestedThreadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
|
||||
assert.equal(body.sessionRef.metadata.threadContinuityPolicy, "hwlab-agentrun-v01-reuse-runner-thread");
|
||||
assert.equal(body.sessionRef.metadata.sessionPolicy, "hwlab-agentrun-v01-session-runner-reuse");
|
||||
@@ -185,7 +187,7 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
|
||||
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_adapter/commands") {
|
||||
assert.equal(body.type, "turn");
|
||||
assert.match(body.payload.prompt, /AgentRun adapter/u);
|
||||
assert.equal(body.payload.sessionId, "ses_server-test-agentrun");
|
||||
assert.equal(body.payload.sessionId, "ses_agentrun_deepseek_server_test_agentrun");
|
||||
assert.equal(body.payload.hwlabSessionId, "ses_server-test-agentrun");
|
||||
assert.equal(body.payload.threadId, null);
|
||||
assert.equal(body.payload.requestedThreadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
|
||||
@@ -256,7 +258,7 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
|
||||
reply: "AgentRun adapter 已接管 HWLAB Code Agent。",
|
||||
lastSeq: 5,
|
||||
eventCount: 5,
|
||||
sessionRef: { sessionId: "ses_server-test-agentrun", conversationId: "cnv_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
|
||||
sessionRef: { sessionId: "ses_agentrun_deepseek_server_test_agentrun", conversationId: "cnv_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
|
||||
});
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_adapter/commands/cmd_hwlab_adapter_second/result") {
|
||||
@@ -275,7 +277,7 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
|
||||
reply: "AgentRun adapter 复用已有 runner 完成第二轮。",
|
||||
lastSeq: 6,
|
||||
eventCount: 6,
|
||||
sessionRef: { sessionId: "ses_server-test-agentrun", conversationId: "cnv_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
|
||||
sessionRef: { sessionId: "ses_agentrun_deepseek_server_test_agentrun", conversationId: "cnv_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
|
||||
});
|
||||
}
|
||||
response.writeHead(404, { "content-type": "application/json" });
|
||||
@@ -429,6 +431,10 @@ test("cloud api AgentRun adapter maps minimax-m3 provider profile to AgentRun ba
|
||||
};
|
||||
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
|
||||
assert.equal(body.backendProfile, "minimax-m3");
|
||||
assert.equal(body.sessionRef.sessionId, "ses_agentrun_minimax_m3_server_test_minimax_m3");
|
||||
assert.equal(body.sessionRef.metadata.hwlabSessionId, "ses_server-test-minimax-m3");
|
||||
assert.equal(body.sessionRef.metadata.agentRunSessionProfile, "minimax-m3");
|
||||
assert.equal(body.sessionRef.metadata.agentRunSessionPolicy, "backend-profile-scoped");
|
||||
assert.equal(body.executionPolicy.secretScope.providerCredentials[0].profile, "minimax-m3");
|
||||
assert.equal(body.executionPolicy.secretScope.providerCredentials[0].secretRef.name, "agentrun-v01-provider-minimax-m3");
|
||||
assert.equal(body.executionPolicy.secretScope.providerCredentials[0].secretRef.namespace, "agentrun-v01");
|
||||
@@ -436,6 +442,8 @@ test("cloud api AgentRun adapter maps minimax-m3 provider profile to AgentRun ba
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_minimax_m3/commands") {
|
||||
assert.equal(body.payload.providerProfile, "minimax-m3");
|
||||
assert.equal(body.payload.sessionId, "ses_agentrun_minimax_m3_server_test_minimax_m3");
|
||||
assert.equal(body.payload.hwlabSessionId, "ses_server-test-minimax-m3");
|
||||
assert.match(body.payload.prompt, /MiniMax-M3/u);
|
||||
return send({ id: "cmd_hwlab_minimax_m3", runId: "run_hwlab_minimax_m3", state: "pending", type: "turn", seq: 1 });
|
||||
}
|
||||
@@ -476,7 +484,7 @@ test("cloud api AgentRun adapter maps minimax-m3 provider profile to AgentRun ba
|
||||
reply: "AGENTRUN_MINIMAX_M3_OK",
|
||||
lastSeq: 3,
|
||||
eventCount: 3,
|
||||
sessionRef: { sessionId: "ses_server-test-minimax-m3", conversationId: "cnv_server-test-minimax-m3", threadId: null }
|
||||
sessionRef: { sessionId: "ses_agentrun_minimax_m3_server_test_minimax_m3", conversationId: "cnv_server-test-minimax-m3", threadId: null }
|
||||
});
|
||||
}
|
||||
response.writeHead(404, { "content-type": "application/json" });
|
||||
@@ -526,6 +534,8 @@ test("cloud api AgentRun adapter maps minimax-m3 provider profile to AgentRun ba
|
||||
assert.equal(payload.status, "completed");
|
||||
assert.equal(payload.infrastructureBackend, "agentrun-v01/minimax-m3");
|
||||
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");
|
||||
assert.equal(payload.providerTrace.backendProfile, "minimax-m3");
|
||||
assert.equal(payload.agentRun.jobName, "agentrun-v01-runner-hwlab-minimax-m3");
|
||||
assert.equal(payload.reply.content, "AGENTRUN_MINIMAX_M3_OK");
|
||||
@@ -536,6 +546,172 @@ test("cloud api AgentRun adapter maps minimax-m3 provider profile to AgentRun ba
|
||||
}
|
||||
});
|
||||
|
||||
test("cloud api AgentRun adapter scopes AgentRun sessions by backend profile", async () => {
|
||||
const calls = [];
|
||||
const hwlabSessionId = "ses_server-test-profile-switch";
|
||||
const minimaxSessionId = "ses_agentrun_minimax_m3_server_test_profile_switch";
|
||||
const ownerSessions = new Map([[hwlabSessionId, {
|
||||
ownerUserId: "usr_agent_owner",
|
||||
ownerRole: "user",
|
||||
sessionId: hwlabSessionId,
|
||||
conversationId: "cnv_server-test-profile-switch",
|
||||
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
|
||||
traceId: "trc_existing_deepseek",
|
||||
status: "active",
|
||||
session: {
|
||||
agentRun: {
|
||||
adapter: "agentrun-v01",
|
||||
backendProfile: "deepseek",
|
||||
runId: "run_existing_deepseek",
|
||||
commandId: "cmd_existing_deepseek",
|
||||
jobName: "agentrun-v01-runner-existing-deepseek",
|
||||
namespace: "agentrun-v01",
|
||||
sessionId: "ses_agentrun_deepseek_server_test_profile_switch",
|
||||
reuseEligible: true,
|
||||
managerUrl: "http://127.0.0.1:1",
|
||||
status: "runner-job-created"
|
||||
}
|
||||
}
|
||||
}]]);
|
||||
|
||||
const agentRunServer = createHttpServer(async (request, response) => {
|
||||
const url = new URL(request.url || "/", "http://127.0.0.1");
|
||||
const chunks = [];
|
||||
for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||||
const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : null;
|
||||
calls.push({ method: request.method, path: url.pathname, body });
|
||||
const send = (data) => {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_profile_switch" })}\n`);
|
||||
};
|
||||
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_existing_deepseek") {
|
||||
response.writeHead(500, { "content-type": "application/json" });
|
||||
response.end(`${JSON.stringify({ ok: false, failureKind: "unexpected-reuse", message: "deepseek run must not be reused for minimax-m3" })}\n`);
|
||||
return;
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
|
||||
assert.equal(body.backendProfile, "minimax-m3");
|
||||
assert.equal(body.sessionRef.sessionId, minimaxSessionId);
|
||||
assert.equal(body.sessionRef.metadata.hwlabSessionId, hwlabSessionId);
|
||||
assert.equal(body.sessionRef.metadata.agentRunSessionProfile, "minimax-m3");
|
||||
return send({ id: "run_hwlab_profile_switch_m3", status: "pending", backendProfile: "minimax-m3", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/commands") {
|
||||
assert.equal(body.payload.providerProfile, "minimax-m3");
|
||||
assert.equal(body.payload.sessionId, minimaxSessionId);
|
||||
assert.equal(body.payload.hwlabSessionId, hwlabSessionId);
|
||||
return send({ id: "cmd_hwlab_profile_switch_m3", runId: "run_hwlab_profile_switch_m3", state: "pending", type: "turn", seq: 1 });
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/runner-jobs") {
|
||||
assert.equal(body.commandId, "cmd_hwlab_profile_switch_m3");
|
||||
return send({
|
||||
action: "create-kubernetes-job",
|
||||
runId: "run_hwlab_profile_switch_m3",
|
||||
commandId: "cmd_hwlab_profile_switch_m3",
|
||||
attemptId: "attempt_hwlab_profile_switch_m3",
|
||||
runnerId: "runner_hwlab_profile_switch_m3",
|
||||
namespace: "agentrun-v01",
|
||||
jobName: "agentrun-v01-runner-profile-switch-m3",
|
||||
jobIdentity: { namespace: "agentrun-v01", name: "agentrun-v01-runner-profile-switch-m3" },
|
||||
runner: { attemptId: "attempt_hwlab_profile_switch_m3", runnerId: "runner_hwlab_profile_switch_m3" }
|
||||
});
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/events") {
|
||||
return send({ items: [
|
||||
{ id: "evt_profile_switch_1", runId: "run_hwlab_profile_switch_m3", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_hwlab_profile_switch_m3", attemptId: "attempt_hwlab_profile_switch_m3", jobName: "agentrun-v01-runner-profile-switch-m3", namespace: "agentrun-v01" }, createdAt: "2026-06-02T00:00:00.000Z" },
|
||||
{ id: "evt_profile_switch_2", runId: "run_hwlab_profile_switch_m3", seq: 2, type: "assistant_message", payload: { commandId: "cmd_hwlab_profile_switch_m3", text: "AGENTRUN_HWLAB_MINIMAX_M3_OK" }, createdAt: "2026-06-02T00:00:01.000Z" },
|
||||
{ id: "evt_profile_switch_3", runId: "run_hwlab_profile_switch_m3", seq: 3, type: "terminal_status", payload: { commandId: "cmd_hwlab_profile_switch_m3", terminalStatus: "completed" }, createdAt: "2026-06-02T00:00:02.000Z" }
|
||||
] });
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/commands/cmd_hwlab_profile_switch_m3/result") {
|
||||
return send({
|
||||
runId: "run_hwlab_profile_switch_m3",
|
||||
commandId: "cmd_hwlab_profile_switch_m3",
|
||||
attemptId: "attempt_hwlab_profile_switch_m3",
|
||||
runnerId: "runner_hwlab_profile_switch_m3",
|
||||
jobName: "agentrun-v01-runner-profile-switch-m3",
|
||||
namespace: "agentrun-v01",
|
||||
status: "completed",
|
||||
runStatus: "completed",
|
||||
commandState: "completed",
|
||||
terminalStatus: "completed",
|
||||
completed: true,
|
||||
reply: "AGENTRUN_HWLAB_MINIMAX_M3_OK",
|
||||
lastSeq: 3,
|
||||
eventCount: 3,
|
||||
sessionRef: { sessionId: minimaxSessionId, conversationId: "cnv_server-test-profile-switch", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
|
||||
});
|
||||
}
|
||||
response.writeHead(404, { "content-type": "application/json" });
|
||||
response.end(`${JSON.stringify({ ok: false, failureKind: "schema-invalid", message: `unexpected ${request.method} ${url.pathname}`, traceId: "trc_fake_profile_switch" })}\n`);
|
||||
});
|
||||
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
||||
|
||||
const agentRunPort = agentRunServer.address().port;
|
||||
const server = createCloudApiServer({
|
||||
env: {
|
||||
HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01",
|
||||
AGENTRUN_MGR_URL: `http://127.0.0.1:${agentRunPort}`,
|
||||
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1",
|
||||
HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567",
|
||||
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek",
|
||||
HWLAB_ENVIRONMENT: "v02",
|
||||
HWLAB_GITOPS_PROFILE: "v02"
|
||||
},
|
||||
accessController: {
|
||||
required: false,
|
||||
async authenticate() {
|
||||
return { ok: true, actor: null, session: null };
|
||||
},
|
||||
async recordAgentSessionOwner(input) {
|
||||
ownerSessions.set(input.sessionId, input);
|
||||
return input;
|
||||
},
|
||||
async getAgentSession(sessionId) {
|
||||
return ownerSessions.get(sessionId) ?? null;
|
||||
}
|
||||
}
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
|
||||
try {
|
||||
const { port } = server.address();
|
||||
const traceId = "trc_server-test-agentrun-profile-switch";
|
||||
const submit = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", "x-trace-id": traceId },
|
||||
body: JSON.stringify({
|
||||
conversationId: "cnv_server-test-profile-switch",
|
||||
sessionId: hwlabSessionId,
|
||||
ownerUserId: "usr_agent_owner",
|
||||
ownerRole: "user",
|
||||
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
|
||||
providerProfile: "minimax-m3",
|
||||
message: "Switch this HWLAB session to MiniMax-M3 through AgentRun"
|
||||
})
|
||||
});
|
||||
assert.equal(submit.status, 202);
|
||||
|
||||
const payload = await pollAgentResult(port, traceId);
|
||||
validateCodeAgentChatSchema(payload);
|
||||
assert.equal(payload.status, "completed");
|
||||
assert.equal(payload.sessionId, hwlabSessionId);
|
||||
assert.equal(payload.infrastructureBackend, "agentrun-v01/minimax-m3");
|
||||
assert.equal(payload.agentRun.backendProfile, "minimax-m3");
|
||||
assert.equal(payload.agentRun.runId, "run_hwlab_profile_switch_m3");
|
||||
assert.equal(payload.agentRun.sessionId, minimaxSessionId);
|
||||
assert.equal(payload.reply.content, "AGENTRUN_HWLAB_MINIMAX_M3_OK");
|
||||
assert.equal(calls.some((call) => call.method === "GET" && call.path === "/api/v1/runs/run_existing_deepseek"), false);
|
||||
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs").length, 1);
|
||||
const stored = ownerSessions.get(hwlabSessionId);
|
||||
assert.equal(stored.session.agentRun.backendProfile, "minimax-m3");
|
||||
assert.equal(stored.session.agentRun.sessionId, minimaxSessionId);
|
||||
} 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())));
|
||||
}
|
||||
});
|
||||
|
||||
test("cloud api AgentRun adapter rejects non-internal manager URLs by default", async () => {
|
||||
const server = createCloudApiServer({
|
||||
env: {
|
||||
|
||||
Reference in New Issue
Block a user