Allow dynamic AgentRun provider profiles

This commit is contained in:
Codex Agent
2026-06-08 03:22:26 +08:00
parent 97998ffec7
commit 34788702dc
15 changed files with 154 additions and 83 deletions
+20 -14
View File
@@ -81,7 +81,8 @@ test("hwlab-cli client access uses API key auth on Cloud Web admin access routes
test("hwlab-cli client provider-profiles uses Cloud Web admin routes", async () => {
const calls: any[] = [];
const result = await runHwlabCli(["client", "provider-profiles", "list", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-admin"], {
const result = await runHwlabCli(["client", "provider-profiles", "list", "--base-url", "http://web.test"], {
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({
@@ -96,8 +97,10 @@ test("hwlab-cli client provider-profiles uses Cloud Web admin routes", async ()
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/admin/provider-profiles");
assert.equal(calls[0].init.method, "GET");
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-admin");
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_admin_test_key");
assert.equal(calls[0].init.headers.cookie, undefined);
assert.equal(result.payload.route.path, "/v1/admin/provider-profiles");
assert.equal(result.payload.auth.authMethod, "api-key");
assert.equal(result.payload.body.items[0].profile, "deepseek");
assert.equal(result.payload.body.items[0].bridge.route, "HWLAB Moon Bridge -> DeepSeek official upstream");
assert.equal(JSON.stringify(result.payload).includes("sk-"), false);
@@ -1812,14 +1815,16 @@ test("hwlab-cli client agent result ignores stored Web session by default after
});
test("hwlab-cli client protected agent commands return structured auth diagnosis for forbidden trace", async () => {
const result = await runHwlabCli(["client", "agent", "trace", "trc_forbidden", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], {
const result = await runHwlabCli(["client", "agent", "trace", "trc_forbidden", "--base-url", "http://web.test"], {
env: { HWLAB_API_KEY: "hwl_live_owner_test_key" },
fetchImpl: async () => new Response(JSON.stringify({ error: { code: "agent_session_owner_required" } }), { status: 403 })
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.httpStatus, 403);
assert.equal(result.payload.authDiagnosis.code, "auth_forbidden");
assert.equal(result.payload.authDiagnosis.cookieSource, "explicit");
assert.equal(result.payload.auth.authMethod, "api-key");
assert.equal(result.payload.authDiagnosis.cookieSource, null);
assert.match(result.payload.authDiagnosis.message, /访/u);
assert.equal(result.payload.request.url, "http://web.test/v1/agent/chat/trace/trc_forbidden");
assert.match(result.payload.authDiagnosis.nextCommands[1], /client auth whoami/u);
@@ -1829,6 +1834,7 @@ test("hwlab-cli client protected agent command reports missing credentials with
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-auth-missing-"));
const result = await runHwlabCli(["client", "agent", "result", "trc_missing_auth", "--base-url", "http://web.test"], {
cwd,
env: {},
fetchImpl: async () => new Response(JSON.stringify({ ok: false, error: { code: "auth_required" } }), { status: 401 })
});
@@ -1858,10 +1864,9 @@ test("hwlab-cli client agent inspect builds protected inspect query", async () =
"--thread-id",
"thread-1",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a"
"http://web.test"
], {
env: { HWLAB_API_KEY: "hwl_live_owner_test_key" },
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({ ok: true, status: "found", latestTraceId: "trc_inspect" }), { status: 200 });
@@ -1870,7 +1875,8 @@ test("hwlab-cli client agent inspect builds protected inspect query", async () =
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/agent/chat/inspect?traceId=trc_inspect&conversationId=cnv_inspect&sessionId=ses_inspect&threadId=thread-1");
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-a");
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_owner_test_key");
assert.equal(calls[0].init.headers.cookie, undefined);
assert.equal(result.payload.action, "client.agent.inspect");
assert.equal(result.payload.body.status, "found");
});
@@ -2014,11 +2020,10 @@ test("hwlab-cli client request covers arbitrary Cloud Web same-origin API routes
"/v1/access/status",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--trace-id",
"trc_request"
], {
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({ ok: true, authenticated: true, actor: { username: "admin" }, roles: ["admin"] }), { status: 200 });
@@ -2027,7 +2032,8 @@ test("hwlab-cli client request covers arbitrary Cloud Web same-origin API routes
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/access/status");
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-a");
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_admin_test_key");
assert.equal(calls[0].init.headers.cookie, undefined);
assert.equal(calls[0].init.headers["x-trace-id"], "trc_request");
assert.equal(result.payload.action, "client.request");
assert.equal(result.payload.route.path, "/v1/access/status");
@@ -2063,8 +2069,6 @@ test("hwlab-cli client rpc mirrors Cloud Web JSON-RPC envelope metadata", async
"system.health",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--trace-id",
"trc_rpc",
"--id",
@@ -2072,6 +2076,7 @@ test("hwlab-cli client rpc mirrors Cloud Web JSON-RPC envelope metadata", async
"--params-json",
"{}"
], {
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
return new Response(JSON.stringify({ jsonrpc: "2.0", id: "req_rpc", result: { status: "ok" }, meta: { traceId: "trc_rpc", serviceId: "hwlab-cloud-api", environment: "dev" } }), { status: 200 });
@@ -2080,7 +2085,8 @@ test("hwlab-cli client rpc mirrors Cloud Web JSON-RPC envelope metadata", async
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/json-rpc");
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-a");
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_admin_test_key");
assert.equal(calls[0].init.headers.cookie, undefined);
assert.equal(calls[0].body.method, "system.health");
assert.deepEqual(calls[0].body.params, {});
assert.deepEqual(calls[0].body.meta, { traceId: "trc_rpc", serviceId: "hwlab-cloud-web", environment: "dev" });
+1 -1
View File
@@ -250,7 +250,7 @@ export function caseHelp() {
`case prepare CASE_ID --case-repo ${STANDARD_CASE_REPO_PATH} [--run-id RUN_ID]`,
`case build CASE_ID --case-repo ${STANDARD_CASE_REPO_PATH} [--run-dir DIR]`,
`case collect CASE_ID --case-repo ${STANDARD_CASE_REPO_PATH} --run-dir DIR`,
`case run CASE_ID --case-repo ${STANDARD_CASE_REPO_PATH} [--provider-profile deepseek|codex-api|minimax-m3]`,
`case run CASE_ID --case-repo ${STANDARD_CASE_REPO_PATH} [--provider-profile PROFILE]`,
`case run start CASE_ID --case-repo ${STANDARD_CASE_REPO_PATH} [--run-id RUN_ID]`,
`case run status RUN_ID [--state-dir .state/hwlab-cli/caserun]`,
`case run result RUN_ID [--state-dir .state/hwlab-cli/caserun]`,
+19 -15
View File
@@ -149,16 +149,16 @@ function help() {
"hwlab-cli client request GET /v1/access/status [--full]",
"hwlab-cli client request GET /v1/web-performance/summary [--full]",
"hwlab-cli client rpc system.health [--full]",
"hwlab-cli client agent session create --provider-profile minimax-m3",
"hwlab-cli client agent session create --provider-profile PROFILE",
"hwlab-cli client agent session select SESSION_ID",
"hwlab-cli client agent send --message TEXT|--message-file PATH [--from-trace TRACE] [--conversation-id ID] [--session-id ID] [--thread-id ID] [--retry-of TRACE] --provider-profile deepseek|codex-api|minimax-m3",
"hwlab-cli client agent send --message TEXT|--message-file PATH [--from-trace TRACE] [--conversation-id ID] [--session-id ID] [--thread-id ID] [--retry-of TRACE] --provider-profile PROFILE",
"hwlab-cli client agent send --message TEXT --wait --timeout-ms 50000",
"hwlab-cli client agent result TRACE_ID",
"hwlab-cli client agent trace TRACE_ID [--render web]",
"hwlab-cli client agent inspect --trace-id TRACE_ID",
"hwlab-cli client agent cancel TRACE_ID",
"hwlab-cli client agent steer TRACE_ID --message TEXT|--message-file PATH",
"hwlab-cli client harness submit --message TEXT --provider-profile deepseek|codex-api|minimax-m3",
"hwlab-cli client harness submit --message TEXT --provider-profile PROFILE",
"hwlab-cli client harness wait TRACE_ID --timeout-ms 50000",
"hwlab-cli client harness audit TRACE_ID",
"hwlab-cli case run CASE_ID --case-repo /root/hwlab-case-registry"
@@ -428,8 +428,12 @@ async function providerProfileConfigFromStdin(context: any) {
function normalizeProviderProfile(value: unknown) {
const profile = text(value).toLowerCase();
const normalized = profile === "codex" ? "codex-api" : profile;
if (["deepseek", "codex-api", "minimax-m3"].includes(normalized)) return normalized;
throw cliError("invalid_provider_profile", "provider profile must be deepseek, codex-api, or minimax-m3", { profile });
if (/^[a-z0-9][a-z0-9-]{0,63}$/u.test(normalized) && normalized !== "runtime-default") return normalized;
throw cliError("invalid_provider_profile", "provider profile must be a lowercase slug such as deepseek, minimax-m3, dsflash-go, or codex-api", {
profile,
alias: "codex -> codex-api",
pattern: "^[a-z0-9][a-z0-9-]{0,63}$"
});
}
function validationTerminalStatus(body: any) {
@@ -874,11 +878,11 @@ function agentHelp() {
serviceRuntime: false,
imagePublished: false,
commands: [
"session create [--provider-profile deepseek|codex-api|minimax-m3] [--conversation-id ID]",
"session create [--provider-profile PROFILE] [--conversation-id ID]",
"session select SESSION_ID [--workspace-id ID]",
"session status SESSION_ID",
"session list [--limit N]",
"send --message TEXT|--message-file PATH [--from-trace TRACE] [--conversation-id ID] [--session-id ID] [--thread-id ID] [--retry-of TRACE] [--provider-profile deepseek|codex-api|minimax-m3] [default: short return]",
"send --message TEXT|--message-file PATH [--from-trace TRACE] [--conversation-id ID] [--session-id ID] [--thread-id ID] [--retry-of TRACE] [--provider-profile PROFILE] [default: short return]",
"composer status [--profile NAME] [--full]",
"composer submit --message TEXT|--message-file PATH [--profile NAME] [default: auto turn/steer]",
"send --message TEXT --wait [--timeout-ms N] [--poll-interval-ms N]",
@@ -1177,7 +1181,7 @@ function agentSessionHelp() {
serviceRuntime: false,
imagePublished: false,
commands: [
"create [--provider-profile deepseek|codex-api|minimax-m3] [--conversation-id ID]",
"create [--provider-profile PROFILE] [--conversation-id ID]",
"select SESSION_ID [--workspace-id ID]",
"status SESSION_ID",
"list [--limit N]"
@@ -1190,7 +1194,7 @@ async function agentSessionCreate(context: any) {
const body = clean({
conversationId: text(context.parsed.conversationId),
sessionId: text(context.parsed.sessionId),
providerProfile: text(context.parsed.providerProfile) || "minimax-m3",
providerProfile: text(context.parsed.providerProfile) || text(workspaceState?.providerProfile) || "deepseek",
projectId: text(context.parsed.projectId) || DEFAULT_WORKBENCH_PROJECT_ID,
workspaceId: text(context.parsed.workspaceId) || text(workspaceState?.workspaceId),
updatedByClient: "hwlab-cli"
@@ -1444,7 +1448,7 @@ async function resolveAgentReplayContext(context: any) {
sessionId: sessionId || null,
threadId: threadId || null,
sessionStatus,
nextCommands: ["hwlab-cli client agent session create --provider-profile minimax-m3"]
nextCommands: ["hwlab-cli client agent session create --provider-profile PROFILE"]
});
}
return {
@@ -1503,7 +1507,7 @@ function harnessHelp() {
aliases: ["harness", "harness-ops", "harness-opt"],
commands: [
"health [--base-url URL]",
"submit --message TEXT|--message-file PATH [--conversation-id ID] [--trace-id ID] [--provider-profile deepseek|codex-api|minimax-m3]",
"submit --message TEXT|--message-file PATH [--conversation-id ID] [--trace-id ID] [--provider-profile PROFILE]",
"result TRACE_ID [--full]",
"trace TRACE_ID [--limit N] [--full]",
"wait TRACE_ID [--timeout-ms N] [--poll-interval-ms N]",
@@ -1707,8 +1711,8 @@ async function agentSend(context: any) {
traceId,
conversationId: conversationId || null,
nextCommands: [
"hwlab-cli client agent session create --provider-profile minimax-m3",
"hwlab-cli client agent send --session-id <SESSION_ID> --message TEXT --provider-profile minimax-m3"
"hwlab-cli client agent session create --provider-profile PROFILE",
"hwlab-cli client agent send --session-id <SESSION_ID> --message TEXT --provider-profile PROFILE"
]
});
}
@@ -1719,14 +1723,14 @@ async function agentSend(context: any) {
conversationId: conversationId || null,
sessionId,
sessionStatus,
nextCommands: ["hwlab-cli client agent session create --provider-profile minimax-m3"]
nextCommands: ["hwlab-cli client agent session create --provider-profile PROFILE"]
});
}
if (!conversationId) {
throw cliError("session_conversation_required", "selected Code Agent session does not expose conversationId; create a new session before sending", {
traceId,
sessionId,
nextCommands: ["hwlab-cli client agent session create --provider-profile minimax-m3"]
nextCommands: ["hwlab-cli client agent session create --provider-profile PROFILE"]
});
}
const threadId = text(parsed.threadId) || replay.threadId || (explicitSessionRecord ? text(explicitSessionRecord.threadId) : "") || (workspaceMatchesSelectedSession ? text(workspaceState?.threadId) : "");