diff --git a/deploy/deploy.yaml b/deploy/deploy.yaml index ed591c84..4f19f5ef 100644 --- a/deploy/deploy.yaml +++ b/deploy/deploy.yaml @@ -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 diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 8209db41..cb1db5ca 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -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 = {}; + 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()); } diff --git a/internal/cloud/provider-profile-management.test.ts b/internal/cloud/provider-profile-management.test.ts index 7cc41094..2f118039 100644 --- a/internal/cloud/provider-profile-management.test.ts +++ b/internal/cloud/provider-profile-management.test.ts @@ -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 = []; 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 = []; 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 = []; 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 = []; 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 = []; 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 = []; 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) { 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`); diff --git a/internal/cloud/provider-profile-management.ts b/internal/cloud/provider-profile-management.ts index 51338461..61e22583 100644 --- a/internal/cloud/provider-profile-management.ts +++ b/internal/cloud/provider-profile-management.ts @@ -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 = { + 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()); }