Merge pull request #1171 from pikasTech/fix/v03-provider-profiles-agentrun-auth-1154

fix: 为 v0.3 Provider Profiles 委托补齐 AgentRun 认证
This commit is contained in:
Lyon
2026-06-14 04:26:52 +08:00
committed by GitHub
4 changed files with 92 additions and 51 deletions
+2
View File
@@ -231,6 +231,7 @@ lanes:
HWLAB_KEYCLOAK_CLIENT_SECRET: secretRef:hwlab-cloud-web-client/client-secret
HWLAB_CODE_AGENT_ADAPTER: agentrun-v01
AGENTRUN_MGR_URL: http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080
AGENTRUN_API_KEY: secretRef:hwlab-v02-master-server-admin-api-key/api-key
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: agentrun-v01
HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: agentrun-v01
HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git
@@ -411,6 +412,7 @@ lanes:
HWLAB_KEYCLOAK_CLIENT_SECRET: secretRef:hwlab-cloud-web-client/client-secret
HWLAB_CODE_AGENT_ADAPTER: agentrun-v01
AGENTRUN_MGR_URL: http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080
AGENTRUN_API_KEY: secretRef:hwlab-v03-master-server-admin-api-key/api-key
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: agentrun-v02
HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: agentrun-v02
HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git
+33 -18
View File
@@ -277,7 +277,8 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
const command = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(reusable.mapping.runId)}/commands`, {
method: "POST",
body: commandInput,
timeoutMs
timeoutMs,
env
});
const commandId = requiredString(command?.id, "command.id");
traceStore.append(traceId, {
@@ -297,7 +298,8 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
runnerJob = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(reusable.mapping.runId)}/runner-jobs`, {
method: "POST",
body: runnerJobInput,
timeoutMs
timeoutMs,
env
});
} catch (error) {
if (!isAgentRunCommandAlreadyClaimed(error)) throw error;
@@ -374,7 +376,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
throw error;
}
const runInput = buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, sessionId, toolCapabilities });
run = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs", { method: "POST", body: runInput, timeoutMs });
run = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs", { method: "POST", body: runInput, timeoutMs, env });
const runId = requiredString(run?.id, "run.id");
traceStore.append(traceId, {
type: "backend", status: "running",
@@ -383,7 +385,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
runId, backendProfile, waitingFor: "agentrun-command-create", valuesPrinted: false,
});
const commandInput = buildAgentRunCommandInput({ params, traceId, backendProfile, sessionId });
command = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs/" + encodeURIComponent(runId) + "/commands", { method: "POST", body: commandInput, timeoutMs });
command = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs/" + encodeURIComponent(runId) + "/commands", { method: "POST", body: commandInput, timeoutMs, env });
const commandId = requiredString(command?.id, "command.id");
traceStore.append(traceId, {
type: "backend", status: "running",
@@ -393,7 +395,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
});
const runnerJobInput = buildAgentRunRunnerJobInput({ env, traceId, commandId, ownerApiKey, toolCapabilities, backendProfile });
try {
runnerJob = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs/" + encodeURIComponent(runId) + "/runner-jobs", { method: "POST", body: runnerJobInput, timeoutMs });
runnerJob = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs/" + encodeURIComponent(runId) + "/runner-jobs", { method: "POST", body: runnerJobInput, timeoutMs, env });
} catch (error) {
if (attempt === 0 && await shouldResetSessionAfterEviction("session-store-evicted", error?.message)) {
sessionReset = true;
@@ -435,11 +437,12 @@ export async function syncAgentRunChatResult({ traceId, currentResult = null, op
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
const managerUrl = resolveAgentRunManagerUrl(env, mapped.agentRun.managerUrl);
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
const eventsResponse = refreshEvents ? await fetchAgentRunEventsForTrace({ fetchImpl, managerUrl, timeoutMs, mapping: { ...mapped.agentRun, traceSummary: mapped.traceSummary } }) : null;
const eventsResponse = refreshEvents ? await fetchAgentRunEventsForTrace({ fetchImpl, managerUrl, timeoutMs, env, mapping: { ...mapped.agentRun, traceSummary: mapped.traceSummary } }) : null;
if (eventsResponse) appendAgentRunEventsToTrace(traceStore, traceId, eventsResponse.events, mapped.agentRun);
const result = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapped.agentRun.runId)}/commands/${encodeURIComponent(mapped.agentRun.commandId)}/result`, {
method: "GET",
timeoutMs
timeoutMs,
env
});
const nextMapping = {
...mapped.agentRun,
@@ -472,7 +475,7 @@ async function resolveAgentRunTraceCommandMapping({ traceId, mapped, options = {
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
const managerUrl = resolveAgentRunManagerUrl(env, agentRun.managerUrl);
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
const command = await findAgentRunCommandForTrace({ fetchImpl, managerUrl, timeoutMs, runId: agentRun.runId, traceId: safeId });
const command = await findAgentRunCommandForTrace({ fetchImpl, managerUrl, timeoutMs, env, runId: agentRun.runId, traceId: safeId });
if (!command?.id) {
throw Object.assign(new Error(`AgentRun command registry has no command for ${safeId} in run ${agentRun.runId}`), {
code: "agentrun_trace_command_not_found",
@@ -522,7 +525,7 @@ export async function refreshAgentRunTrace({ traceId, result = null, options = {
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
const managerUrl = resolveAgentRunManagerUrl(env, mapped.agentRun.managerUrl);
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
const eventsResponse = await fetchAgentRunEventsForTrace({ fetchImpl, managerUrl, timeoutMs, mapping: { ...mapped.agentRun, traceSummary: mapped.traceSummary } });
const eventsResponse = await fetchAgentRunEventsForTrace({ fetchImpl, managerUrl, timeoutMs, env, mapping: { ...mapped.agentRun, traceSummary: mapped.traceSummary } });
const events = eventsResponse.events;
appendAgentRunEventsToTrace(traceStore, traceId, events, mapped.agentRun);
const lastSeq = agentRunTraceCursorSeq(eventsResponse, mapped.agentRun.lastSeq);
@@ -542,7 +545,8 @@ export async function cancelAgentRunChatTurn({ traceId, currentResult = null, op
await agentRunJson(fetchImpl, managerUrl, `/api/v1/commands/${encodeURIComponent(mapped.agentRun.commandId)}/cancel`, {
method: "POST",
body: { reason: "hwlab-user-cancel", traceId },
timeoutMs
timeoutMs,
env
});
traceStore.append(traceId, {
type: "cancel",
@@ -607,7 +611,8 @@ export async function steerAgentRunChatTurn({ traceId, currentResult = null, par
const command = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapped.agentRun.runId)}/commands`, {
method: "POST",
body: commandInput,
timeoutMs
timeoutMs,
env
});
const steerCommandId = requiredString(command?.id, "command.id");
const now = nowIso(options.now);
@@ -703,9 +708,9 @@ function agentRunSeedFromSession(session, traceId) {
return topLevelAgentRun?.runId ? topLevelAgentRun : null;
}
async function findAgentRunCommandForTrace({ fetchImpl, managerUrl, timeoutMs, runId, traceId }) {
async function findAgentRunCommandForTrace({ fetchImpl, managerUrl, timeoutMs, env, runId, traceId }) {
if (!runId || !safeTraceId(traceId)) return null;
const commands = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(runId)}/commands?afterSeq=0&limit=100`, { method: "GET", timeoutMs });
const commands = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(runId)}/commands?afterSeq=0&limit=100`, { method: "GET", timeoutMs, env });
return (Array.isArray(commands?.items) ? commands.items : []).find((item) => agentRunCommandMatchesTrace(item, traceId)) ?? null;
}
@@ -756,6 +761,7 @@ async function ensureAgentRunSessionPersistent({ fetchImpl, managerUrl, sessionI
await agentRunJson(fetchImpl, managerUrl, "/api/v1/sessions", {
method: "POST",
timeoutMs: 15_000,
env,
body: { sessionId, tenantId, projectId, backendProfile, expiresAt, codexRolloutSubdir: "sessions" },
});
} catch (error) {
@@ -1033,7 +1039,8 @@ async function resolveReusableAgentRun({ params = {}, options = {}, env = proces
try {
run = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapping.runId)}`, {
method: "GET",
timeoutMs
timeoutMs,
env
});
} catch (error) {
traceStore.append(traceId, {
@@ -1421,12 +1428,12 @@ function appendAgentRunEventsToTrace(traceStore, traceId, events, mapping = {})
}
}
async function fetchAgentRunEventsForTrace({ fetchImpl, managerUrl, timeoutMs, mapping = {} }) {
async function fetchAgentRunEventsForTrace({ fetchImpl, managerUrl, timeoutMs, env, mapping = {} }) {
const runId = requiredString(mapping.runId, "runId");
const currentCommandId = typeof mapping.commandId === "string" ? mapping.commandId : "";
const { afterSeq, endSeq } = agentRunTraceReplayWindow(mapping);
const path = `/api/v1/runs/${encodeURIComponent(runId)}/events?afterSeq=${encodeURIComponent(String(afterSeq))}&limit=500`;
const response = await agentRunJson(fetchImpl, managerUrl, path, { method: "GET", timeoutMs });
const response = await agentRunJson(fetchImpl, managerUrl, path, { method: "GET", timeoutMs, env });
const rawEvents = Array.isArray(response?.items) ? response.items : [];
const events = rawEvents.filter((event) => agentRunEventBelongsToTrace(event, { currentCommandId, afterSeq, endSeq }));
return {
@@ -1637,13 +1644,17 @@ function agentRunCommandExecutionEvent(base, payload = {}) {
};
}
async function agentRunJson(fetchImpl, managerUrl, path, { method = "GET", body, timeoutMs = 20_000 } = {}) {
async function agentRunJson(fetchImpl, managerUrl, path, { method = "GET", body, timeoutMs = 20_000, env = process.env } = {}) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const headers: Record<string, string> = {};
const apiKey = resolveAgentRunApiKey(env);
if (body !== undefined) headers["content-type"] = "application/json";
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
const response = await fetchImpl(`${managerUrl}${path}`, {
method,
headers: body === undefined ? undefined : { "content-type": "application/json" },
headers: Object.keys(headers).length === 0 ? undefined : headers,
body: body === undefined ? undefined : JSON.stringify(body),
signal: controller.signal
});
@@ -1684,6 +1695,10 @@ function resolveAgentRunManagerUrl(env = process.env, override = null) {
return url.toString().replace(/\/+$/u, "");
}
function resolveAgentRunApiKey(env = process.env) {
return firstNonEmpty(env.AGENTRUN_API_KEY, env.HWLAB_CODE_AGENT_AGENTRUN_API_KEY, env.HWLAB_API_KEY);
}
function isAgentRunManagerInternalServiceHost(host) {
return AGENTRUN_MANAGER_HOST_PATTERN.test(String(host ?? "").trim().toLowerCase());
}
@@ -31,7 +31,7 @@ test("provider profile management requires HWLAB admin auth before AgentRun dele
});
test("provider profile catalog is readable to authenticated non-admin actors and redacts management-only fields", async () => {
const calls: Array<{ method?: string; path?: string; body?: unknown }> = [];
const calls: Array<AgentRunCall> = [];
const agentRunServer = await startAgentRunServer(calls);
const { port: agentRunPort } = agentRunServer.address() as { port: number };
const server = createCloudApiServer({
@@ -59,6 +59,7 @@ test("provider profile catalog is readable to authenticated non-admin actors and
assert.equal(JSON.stringify(body).includes("auth.json"), false);
assert.equal(calls[0].method, "GET");
assert.equal(calls[0].path, "/api/v1/provider-profiles");
assert.equal(calls[0].authorization, "Bearer test-agentrun-manager-key");
} finally {
await close(server);
await close(agentRunServer);
@@ -66,7 +67,7 @@ test("provider profile catalog is readable to authenticated non-admin actors and
});
test("provider profiles collection delegates to AgentRun, rewrites codex profile, and preserves dynamic slugs", async () => {
const calls: Array<{ method?: string; path?: string; body?: unknown }> = [];
const calls: Array<AgentRunCall> = [];
const agentRunServer = await startAgentRunServer(calls);
const { port: agentRunPort } = agentRunServer.address() as { port: number };
const server = createCloudApiServer({
@@ -94,6 +95,7 @@ test("provider profiles collection delegates to AgentRun, rewrites codex profile
assert.equal(JSON.stringify(body).includes("sk-test"), false);
assert.equal(calls[0].method, "GET");
assert.equal(calls[0].path, "/api/v1/provider-profiles");
assert.equal(calls[0].authorization, "Bearer test-agentrun-manager-key");
} finally {
await close(server);
await close(agentRunServer);
@@ -101,7 +103,7 @@ test("provider profiles collection delegates to AgentRun, rewrites codex profile
});
test("provider profile set-key delegates through HWLAB without echoing API key", async () => {
const calls: Array<{ method?: string; path?: string; body?: any }> = [];
const calls: Array<AgentRunCall> = [];
const agentRunServer = await startAgentRunServer(calls);
const { port: agentRunPort } = agentRunServer.address() as { port: number };
const server = createCloudApiServer({
@@ -129,6 +131,8 @@ test("provider profile set-key delegates through HWLAB without echoing API key",
assert.equal(text.includes("Bearer hwlab-test"), false);
assert.equal(calls[0].method, "PUT");
assert.equal(calls[0].path, "/api/v1/provider-profiles/deepseek/credential");
assert.equal(calls[0].authorization, "Bearer test-agentrun-manager-key");
assert.equal(calls[0].authorization?.includes(key), false);
assert.equal(calls[0].body.apiKey, key);
assert.equal(calls[0].body.delegatedBy.system, "hwlab-v02");
assert.equal(calls[0].body.delegatedBy.userId, "usr_admin");
@@ -140,15 +144,16 @@ test("provider profile set-key delegates through HWLAB without echoing API key",
});
test("provider profile management accepts lane-specific AgentRun manager namespace", async () => {
const calls: Array<{ method?: string; path?: string; host?: string }> = [];
const calls: Array<{ method?: string; path?: string; host?: string; authorization?: string }> = [];
const server = createCloudApiServer({
env: {
AGENTRUN_MGR_URL: "http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080",
AGENTRUN_API_KEY: "test-agentrun-key",
HWLAB_ACCESS_CONTROL_REQUIRED: "1"
},
fetchImpl: async (url: string, init: any = {}) => {
const parsed = new URL(url);
calls.push({ method: init.method, path: parsed.pathname, host: parsed.hostname });
calls.push({ method: init.method, path: parsed.pathname, host: parsed.hostname, authorization: init.headers?.authorization });
return new Response(JSON.stringify({ ok: true, data: { items: [], count: 0, valuesPrinted: false }, valuesPrinted: false }), { status: 200 });
},
accessController: fakeAccessController({ actor: adminActor })
@@ -164,13 +169,15 @@ test("provider profile management accepts lane-specific AgentRun manager namespa
assert.equal(calls[0].method, "GET");
assert.equal(calls[0].host, "agentrun-mgr.agentrun-v02.svc.cluster.local");
assert.equal(calls[0].path, "/api/v1/provider-profiles");
assert.equal(calls[0].authorization, "Bearer test-agentrun-key");
assert.equal(JSON.stringify(body).includes("test-agentrun-key"), false);
} finally {
await close(server);
}
});
test("provider profile auth-json delegates through HWLAB without echoing auth json", async () => {
const calls: Array<{ method?: string; path?: string; body?: any }> = [];
const calls: Array<AgentRunCall> = [];
const agentRunServer = await startAgentRunServer(calls);
const { port: agentRunPort } = agentRunServer.address() as { port: number };
const server = createCloudApiServer({
@@ -197,6 +204,7 @@ test("provider profile auth-json delegates through HWLAB without echoing auth js
assert.equal(text.includes("sk-test-provider-secret-123456"), false);
assert.equal(calls[0].method, "PUT");
assert.equal(calls[0].path, "/api/v1/provider-profiles/hy/credential");
assert.equal(calls[0].authorization, "Bearer test-agentrun-manager-key");
assert.equal(calls[0].body.authJson, authJson);
assert.equal(calls[0].body.apiKey, undefined);
assert.equal(calls[0].body.delegatedBy.system, "hwlab-v02");
@@ -208,7 +216,7 @@ test("provider profile auth-json delegates through HWLAB without echoing auth js
});
test("provider profile config reads and writes config.toml through AgentRun", async () => {
const calls: Array<{ method?: string; path?: string; body?: any }> = [];
const calls: Array<AgentRunCall> = [];
const agentRunServer = await startAgentRunServer(calls);
const { port: agentRunPort } = agentRunServer.address() as { port: number };
const server = createCloudApiServer({
@@ -240,8 +248,10 @@ test("provider profile config reads and writes config.toml through AgentRun", as
assert.equal(JSON.stringify(writeBody).includes("# edited"), false);
assert.equal(calls[0].method, "GET");
assert.equal(calls[0].path, "/api/v1/provider-profiles/deepseek/config");
assert.equal(calls[0].authorization, "Bearer test-agentrun-manager-key");
assert.equal(calls[1].method, "PUT");
assert.equal(calls[1].path, "/api/v1/provider-profiles/deepseek/config");
assert.equal(calls[1].authorization, "Bearer test-agentrun-manager-key");
assert.equal(calls[1].body.configToml, `${configToml}# edited\n`);
assert.equal(calls[1].body.delegatedBy.system, "hwlab-v02");
} finally {
@@ -251,7 +261,7 @@ test("provider profile config reads and writes config.toml through AgentRun", as
});
test("provider profile remove delegates through HWLAB and preserves redaction", async () => {
const calls: Array<{ method?: string; path?: string; body?: any }> = [];
const calls: Array<AgentRunCall> = [];
const agentRunServer = await startAgentRunServer(calls);
const { port: agentRunPort } = agentRunServer.address() as { port: number };
const server = createCloudApiServer({
@@ -276,6 +286,7 @@ test("provider profile remove delegates through HWLAB and preserves redaction",
assert.equal(JSON.stringify(body).includes("sk-test"), false);
assert.equal(calls[0].method, "DELETE");
assert.equal(calls[0].path, "/api/v1/provider-profiles/deepseek");
assert.equal(calls[0].authorization, "Bearer test-agentrun-manager-key");
assert.equal(calls[0].body, null);
} finally {
await close(server);
@@ -286,11 +297,14 @@ test("provider profile remove delegates through HWLAB and preserves redaction",
function testEnv(port: number) {
return {
HWLAB_CODE_AGENT_AGENTRUN_MGR_URL: `http://127.0.0.1:${port}`,
AGENTRUN_API_KEY: "test-agentrun-manager-key",
HWLAB_PROVIDER_PROFILE_AGENTRUN_ALLOW_NON_K3S_URL: "1",
HWLAB_ACCESS_CONTROL_REQUIRED: "1"
};
}
type AgentRunCall = { method?: string; path?: string; body?: any; authorization?: string };
function fakeAccessController({ actor, authMethod = "web-session" }: { actor: any; authMethod?: string }) {
return {
required: true,
@@ -308,13 +322,13 @@ function fakeAccessController({ actor, authMethod = "web-session" }: { actor: an
};
}
async function startAgentRunServer(calls: Array<{ method?: string; path?: string; body?: unknown }>) {
async function startAgentRunServer(calls: Array<AgentRunCall>) {
const server = 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 });
calls.push({ method: request.method, path: url.pathname, body, authorization: request.headers.authorization });
response.writeHead(200, { "content-type": "application/json" });
if (request.method === "GET" && url.pathname === "/api/v1/provider-profiles") {
response.end(`${JSON.stringify({ ok: true, traceId: "trc_profiles", data: { items: [profile("deepseek"), profile("codex"), profile("minimax-m3"), profile("dsflash-go-cli-example")], count: 4, valuesPrinted: false } })}\n`);
+33 -23
View File
@@ -15,10 +15,12 @@ export async function handleProviderProfileCatalogHttp(request, response, option
if (!auth.ok) return sendJson(response, numericStatus(auth.status) ?? numericStatus(auth.statusCode) ?? 401, auth);
const requestId = requestIdFor(request);
const managerUrl = resolveAgentRunManagerUrl(options.env ?? process.env);
const env = options.env ?? process.env;
const managerUrl = resolveAgentRunManagerUrl(env);
const agentRunApiKey = resolveAgentRunApiKey(env);
const fetchImpl = options.fetchImpl ?? fetch;
const timeoutMs = positiveInteger((options.env ?? process.env).HWLAB_PROVIDER_PROFILE_AGENTRUN_HTTP_TIMEOUT_MS, positiveInteger((options.env ?? process.env).HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, DEFAULT_TIMEOUT_MS));
const delegated = await agentRunJson(fetchImpl, managerUrl, "/api/v1/provider-profiles", { method: "GET", requestId, timeoutMs });
const timeoutMs = positiveInteger(env.HWLAB_PROVIDER_PROFILE_AGENTRUN_HTTP_TIMEOUT_MS, positiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, DEFAULT_TIMEOUT_MS));
const delegated = await agentRunJson(fetchImpl, managerUrl, "/api/v1/provider-profiles", { method: "GET", requestId, timeoutMs, apiKey: agentRunApiKey });
const data = normalizeCatalogData(delegated.data);
const status = delegated.ok ? delegated.httpStatus || 200 : delegated.httpStatus || 502;
const base = {
@@ -48,38 +50,40 @@ export async function handleProviderProfilesHttp(request, response, url, options
const requestId = requestIdFor(request);
const route = parseProviderProfileRoute(url.pathname, request.method);
const managerUrl = resolveAgentRunManagerUrl(options.env ?? process.env);
const env = options.env ?? process.env;
const managerUrl = resolveAgentRunManagerUrl(env);
const agentRunApiKey = resolveAgentRunApiKey(env);
const fetchImpl = options.fetchImpl ?? fetch;
const timeoutMs = positiveInteger((options.env ?? process.env).HWLAB_PROVIDER_PROFILE_AGENTRUN_HTTP_TIMEOUT_MS, positiveInteger((options.env ?? process.env).HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, DEFAULT_TIMEOUT_MS));
const timeoutMs = positiveInteger(env.HWLAB_PROVIDER_PROFILE_AGENTRUN_HTTP_TIMEOUT_MS, positiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, DEFAULT_TIMEOUT_MS));
if (route.kind === "list") {
const delegated = await agentRunJson(fetchImpl, managerUrl, "/api/v1/provider-profiles", { method: "GET", requestId, timeoutMs });
const delegated = await agentRunJson(fetchImpl, managerUrl, "/api/v1/provider-profiles", { method: "GET", requestId, timeoutMs, apiKey: agentRunApiKey });
return sendDelegated(response, delegated, auth, route, managerUrl, requestId, normalizeListData(delegated.data));
}
if (route.kind === "remove") {
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}`, { method: "DELETE", requestId, timeoutMs });
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}`, { method: "DELETE", requestId, timeoutMs, apiKey: agentRunApiKey });
return sendDelegated(response, delegated, auth, route, managerUrl, requestId, normalizeProfileData(delegated.data));
}
if (route.kind === "credential") {
const body = await readJsonObject(request, options.bodyLimitBytes);
const apiKey = text(body.apiKey);
const credentialApiKey = text(body.apiKey);
const authJson = typeof body.authJson === "string" ? body.authJson : "";
if (apiKey.length < 8 && !authJson.trim()) return sendError(response, 400, "credential_required", "apiKey or authJson is required and must not be empty");
if (credentialApiKey.length < 8 && !authJson.trim()) return sendError(response, 400, "credential_required", "apiKey or authJson is required and must not be empty");
const delegatedBody = {
...(authJson.trim() ? { authJson } : { apiKey }),
...(authJson.trim() ? { authJson } : { apiKey: credentialApiKey }),
...(body.config && typeof body.config === "object" ? { config: body.config } : {}),
delegatedBy: delegatedBy(auth, requestId),
reason: text(body.reason) || "hwlab-provider-management"
};
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/credential`, { method: "PUT", body: delegatedBody, requestId, timeoutMs });
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/credential`, { method: "PUT", body: delegatedBody, requestId, timeoutMs, apiKey: agentRunApiKey });
return sendDelegated(response, delegated, auth, route, managerUrl, requestId, normalizeProfileData(delegated.data));
}
if (route.kind === "config") {
if (route.method === "GET") {
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/config`, { method: "GET", requestId, timeoutMs });
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/config`, { method: "GET", requestId, timeoutMs, apiKey: agentRunApiKey });
return sendDelegated(response, delegated, auth, route, managerUrl, requestId, normalizeConfigData(delegated.data), { allowConfigToml: true });
}
const body = await readJsonObject(request, options.bodyLimitBytes);
@@ -90,7 +94,7 @@ export async function handleProviderProfilesHttp(request, response, url, options
delegatedBy: delegatedBy(auth, requestId),
reason: text(body.reason) || "hwlab-provider-management"
};
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/config`, { method: "PUT", body: delegatedBody, requestId, timeoutMs });
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/config`, { method: "PUT", body: delegatedBody, requestId, timeoutMs, apiKey: agentRunApiKey });
return sendDelegated(response, delegated, auth, route, managerUrl, requestId, normalizeConfigData(delegated.data));
}
@@ -101,12 +105,12 @@ export async function handleProviderProfilesHttp(request, response, url, options
delegatedBy: delegatedBy(auth, requestId),
reason: text(body.reason) || "hwlab-provider-management"
};
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/validate`, { method: "POST", body: delegatedBody, requestId, timeoutMs });
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/validate`, { method: "POST", body: delegatedBody, requestId, timeoutMs, apiKey: agentRunApiKey });
return sendDelegated(response, delegated, auth, route, managerUrl, requestId, normalizeValidationData(delegated.data, route.publicProfile));
}
if (route.kind === "validation") {
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/validations/${encodeURIComponent(route.validationId)}`, { method: "GET", requestId, timeoutMs });
const delegated = await agentRunJson(fetchImpl, managerUrl, `/api/v1/provider-profiles/${encodeURIComponent(route.agentRunProfile)}/validations/${encodeURIComponent(route.validationId)}`, { method: "GET", requestId, timeoutMs, apiKey: agentRunApiKey });
return sendDelegated(response, delegated, auth, route, managerUrl, requestId, normalizeValidationData(delegated.data, route.publicProfile));
}
@@ -178,21 +182,23 @@ function normalizeProfile(value) {
return { publicProfile, agentRunProfile: publicProfile === "codex-api" ? "codex" : publicProfile };
}
async function agentRunJson(fetchImpl, managerUrl, path, { method = "GET", body, requestId, timeoutMs }) {
async function agentRunJson(fetchImpl, managerUrl, path, { method = "GET", body, requestId, timeoutMs, apiKey }) {
const startedAt = Date.now();
const targetUrl = `${managerUrl}${path}`;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const headers: Record<string, string> = {
accept: "application/json",
"content-type": "application/json",
"x-request-id": requestId,
"x-source-service-id": "hwlab-cloud-api",
"x-hwlab-delegation": "provider-profile-management-v1"
};
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
const response = await fetchImpl(targetUrl, {
method,
headers: {
accept: "application/json",
"content-type": "application/json",
"x-request-id": requestId,
"x-source-service-id": "hwlab-cloud-api",
"x-hwlab-delegation": "provider-profile-management-v1"
},
headers,
body: body === undefined ? undefined : JSON.stringify(body),
signal: controller.signal
});
@@ -406,6 +412,10 @@ function resolveAgentRunManagerUrl(env) {
return raw.replace(/\/+$/u, "");
}
function resolveAgentRunApiKey(env) {
return text(env.AGENTRUN_API_KEY) || text(env.HWLAB_CODE_AGENT_AGENTRUN_API_KEY) || text(env.HWLAB_PROVIDER_PROFILE_AGENTRUN_API_KEY) || text(env.HWLAB_API_KEY);
}
function isAgentRunManagerInternalServiceHost(host) {
return AGENTRUN_MANAGER_HOST_PATTERN.test(String(host ?? "").trim().toLowerCase());
}