Merge pull request #1171 from pikasTech/fix/v03-provider-profiles-agentrun-auth-1154
fix: 为 v0.3 Provider Profiles 委托补齐 AgentRun 认证
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user