fix: 支持动态 provider profile slug

This commit is contained in:
Codex
2026-06-08 04:19:58 +08:00
parent bda8e3bb1e
commit 509c2aa6fd
6 changed files with 181 additions and 47 deletions
+34 -6
View File
@@ -1,5 +1,7 @@
import type { BackendProfile, JsonRecord } from "./types.js";
export const backendProfileIdPattern = /^[a-z0-9][a-z0-9-]{0,63}$/u;
export interface BackendProfileSpec {
profile: BackendProfile;
backendKind: "codex-app-server-stdio";
@@ -13,7 +15,7 @@ export interface BackendProfileSpec {
description: string;
}
export const backendProfileSpecs: readonly BackendProfileSpec[] = [
const builtinBackendProfileSpecs: readonly BackendProfileSpec[] = [
{
profile: "codex",
backendKind: "codex-app-server-stdio",
@@ -64,14 +66,40 @@ export const backendProfileSpecs: readonly BackendProfileSpec[] = [
},
];
export const backendProfiles = backendProfileSpecs.map((item) => item.profile) as readonly BackendProfile[];
export const backendProfileSpecs = builtinBackendProfileSpecs;
export const backendProfiles = builtinBackendProfileSpecs.map((item) => item.profile) as readonly BackendProfile[];
export function defaultSecretNameForProfile(profile: string): string {
return `agentrun-v01-provider-${profile}`;
}
function dynamicBackendProfileSpec(profile: string): BackendProfileSpec {
return {
profile,
backendKind: "codex-app-server-stdio",
protocol: "codex-app-server-jsonrpc-stdio",
transport: "stdio",
command: "codex app-server --listen stdio://",
status: "registered",
requiredSecretKeys: ["auth.json", "config.toml"],
defaultSecretName: defaultSecretNameForProfile(profile),
profileIsolation: "profile-scoped-codex-home",
description: `Dynamic Codex-compatible profile ${profile}`,
};
}
export function backendProfileSpec(profile: string): BackendProfileSpec | null {
return backendProfileSpecs.find((item) => item.profile === profile) ?? null;
if (!isBackendProfileSlug(profile)) return null;
return builtinBackendProfileSpecs.find((item) => item.profile === profile) ?? dynamicBackendProfileSpec(profile);
}
export function isBackendProfileSlug(value: string): boolean {
return backendProfileIdPattern.test(value);
}
export function isBackendProfile(value: string): value is BackendProfile {
return backendProfileSpec(value) !== null;
return isBackendProfileSlug(value);
}
export function backendCapability(spec: BackendProfileSpec): JsonRecord {
@@ -111,7 +139,7 @@ export function mergeBackendCapability(profile: string, storedCapabilities: Json
}
export function backendCapabilities(): JsonRecord[] {
return backendProfileSpecs.map(backendCapability);
return builtinBackendProfileSpecs.map(backendCapability);
}
export function backendCapabilitiesSqlValues(profiles?: readonly BackendProfile[]): string {
@@ -121,7 +149,7 @@ export function backendCapabilitiesSqlValues(profiles?: readonly BackendProfile[
if (!spec) throw new Error(`unknown backend profile for SQL seed: ${profile}`);
return spec;
})
: backendProfileSpecs;
: builtinBackendProfileSpecs;
return specs.map((spec) => {
const capabilities = JSON.stringify({
backendKind: spec.backendKind,
+1 -1
View File
@@ -28,7 +28,7 @@ export type FailureKind =
export type RunStatus = "pending" | "claimed" | "running" | "completed" | "failed" | "blocked" | "cancelled";
export type CommandState = "pending" | "acknowledged" | "completed" | "failed" | "cancelled";
export type TerminalStatus = "completed" | "failed" | "blocked" | "cancelled";
export type BackendProfile = "codex" | "deepseek" | "minimax-m3" | "dsflash-go";
export type BackendProfile = string;
export type QueueTaskState = "pending" | "running" | "completed" | "failed" | "blocked" | "cancelled";
export type SessionExecutionState = "idle" | "running" | "terminal";
export type SessionAttentionState = "active" | "unread" | "read";
+7 -5
View File
@@ -1,7 +1,9 @@
import { createHash, randomUUID } from "node:crypto";
import type { BackendProfile, CreateCommandInput, CreateQueueTaskInput, CreateRunInput, ExecutionPolicy, JsonRecord, JsonValue, QueueTaskState, ResourceBundleRef, SecretRef, SessionListState, SessionRef } from "./types.js";
import { AgentRunError } from "./errors.js";
import { backendProfileSpec, backendProfiles, isBackendProfile } from "./backend-profiles.js";
import { backendProfileIdPattern, backendProfileSpec, isBackendProfile } from "./backend-profiles.js";
const backendProfilePatternText = String(backendProfileIdPattern);
const allowedTenants = new Set(["unidesk", "hwlab"]);
const allowedToolCredentials = ["github", "unidesk-ssh"] as const;
@@ -44,7 +46,7 @@ export function validateCreateRun(input: unknown): CreateRunInput {
const tenantId = requiredString(record, "tenantId");
if (!allowedTenants.has(tenantId)) throw new AgentRunError("tenant-policy-denied", `tenantId ${tenantId} is not allowed`, { httpStatus: 403 });
const backendProfileValue = requiredString(record, "backendProfile");
if (!isBackendProfile(backendProfileValue)) throw new AgentRunError("schema-invalid", `backendProfile ${backendProfileValue} is not supported in v0.1`, { httpStatus: 400, details: { allowedBackends: [...backendProfiles] } });
if (!isBackendProfile(backendProfileValue)) throw new AgentRunError("schema-invalid", `backendProfile ${backendProfileValue} must be a lowercase slug`, { httpStatus: 400, details: { pattern: backendProfilePatternText } });
const backendProfile = backendProfileValue as BackendProfile;
const executionPolicy = validateExecutionPolicy(requiredRecord(record, "executionPolicy"));
validateBackendSecretScope(backendProfile, executionPolicy);
@@ -214,7 +216,7 @@ export function validateExecutionPolicy(record: JsonRecord): ExecutionPolicy {
const item = asRecord(credential, "providerCredential");
const profile = typeof item.profile === "string" ? item.profile.trim() : "";
if (profile.length === 0) throw new AgentRunError("schema-invalid", "provider credential profile is required", { httpStatus: 400 });
if (!isBackendProfile(profile)) throw new AgentRunError("schema-invalid", `provider credential profile ${profile} is not supported in v0.1`, { httpStatus: 400, details: { allowedBackends: [...backendProfiles] } });
if (!isBackendProfile(profile)) throw new AgentRunError("schema-invalid", `provider credential profile ${profile} must be a lowercase slug`, { httpStatus: 400, details: { pattern: backendProfilePatternText } });
const secretRef = asRecord(item.secretRef, "providerCredential.secretRef");
if (typeof secretRef.name !== "string" || secretRef.name.length === 0) throw new AgentRunError("schema-invalid", "provider credential secretRef.name is required", { httpStatus: 400 });
const keys = Array.isArray(secretRef.keys) ? secretRef.keys : [];
@@ -327,7 +329,7 @@ export function validateCreateQueueTask(input: unknown): CreateQueueTaskInput {
const tenantId = requiredString(record, "tenantId");
if (!allowedTenants.has(tenantId)) throw new AgentRunError("tenant-policy-denied", `tenantId ${tenantId} is not allowed`, { httpStatus: 403 });
const backendProfileValue = optionalString(record.backendProfile) ?? "codex";
if (!isBackendProfile(backendProfileValue)) throw new AgentRunError("schema-invalid", `backendProfile ${backendProfileValue} is not supported in v0.1`, { httpStatus: 400, details: { allowedBackends: [...backendProfiles] } });
if (!isBackendProfile(backendProfileValue)) throw new AgentRunError("schema-invalid", `backendProfile ${backendProfileValue} must be a lowercase slug`, { httpStatus: 400, details: { pattern: backendProfilePatternText } });
const queue = optionalString(record.queue) ?? "default";
const lane = optionalString(record.lane) ?? "default";
const priorityValue = record.priority ?? 0;
@@ -367,5 +369,5 @@ export function validateSessionListState(value: string): SessionListState {
export function validateBackendProfile(value: string): BackendProfile {
if (isBackendProfile(value)) return value;
throw new AgentRunError("schema-invalid", `backendProfile ${value} is not supported in v0.1`, { httpStatus: 400, details: { allowedBackends: [...backendProfiles] } });
throw new AgentRunError("schema-invalid", `backendProfile ${value} must be a lowercase slug`, { httpStatus: 400, details: { pattern: backendProfilePatternText } });
}
+72 -14
View File
@@ -1,7 +1,7 @@
import { createHash, randomUUID } from "node:crypto";
import { spawn } from "node:child_process";
import { AgentRunError } from "../common/errors.js";
import { backendProfileSpec, backendProfileSpecs } from "../common/backend-profiles.js";
import { backendProfileSpec, backendProfileSpecs, isBackendProfileSlug } from "../common/backend-profiles.js";
import type { AgentRunStore } from "./store.js";
import type { BackendProfile, ExecutionPolicy, JsonRecord, JsonValue } from "../common/types.js";
import { asRecord, validateBackendProfile } from "../common/validation.js";
@@ -13,6 +13,7 @@ import { runnerJobStatusSummary } from "./runner-job-status.js";
const defaultNamespace = "agentrun-v01";
const credentialAnnotationPrefix = "agentrun.pikastech.local/provider-profile";
const kubectlLastAppliedAnnotation = "kubectl.kubernetes.io/last-applied-configuration";
const providerSecretNamePrefix = "agentrun-v01-provider-";
export interface ProviderProfileOptions {
namespace?: string;
@@ -40,7 +41,8 @@ interface RenderedConfig {
}
export async function listProviderProfiles(options: ProviderProfileOptions = {}): Promise<JsonRecord> {
const items = await Promise.all(backendProfileSpecs.map((spec) => providerProfileStatus(spec.profile, options)));
const profiles = await listProviderProfileIds(options);
const items = await Promise.all(profiles.map((profile) => providerProfileStatus(profile, options)));
return { items, count: items.length, valuesPrinted: false };
}
@@ -81,10 +83,8 @@ export async function setProviderProfileConfig(profileValue: string, body: unkno
const delegatedBy = delegatedBySummary(record.delegatedBy);
const namespace = profileNamespace(options);
const existingSecret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl");
if (!existingSecret) throw new AgentRunError("secret-unavailable", `provider profile ${profile} Secret is not configured`, { httpStatus: 404, details: { profile, secretRef: secretRefSummary(profile, namespace) } });
const existingData = asOptionalRecord(existingSecret.data);
const existingData = asOptionalRecord(existingSecret?.data);
const authJsonData = dataKey(existingData, "auth.json");
if (!authJsonData) throw new AgentRunError("secret-unavailable", `provider profile ${profile} auth.json is not configured`, { httpStatus: 400, details: { profile, secretRef: secretRefSummary(profile, namespace), key: "auth.json" } });
const credentialHashSuffix = hashDataKey(existingData, "auth.json");
const secretManifest: JsonRecord = {
apiVersion: "v1",
@@ -105,30 +105,28 @@ export async function setProviderProfileConfig(profileValue: string, body: unkno
},
},
type: "Opaque",
data: {
"auth.json": authJsonData,
"config.toml": base64Data(configToml),
},
data: providerProfileSecretData({ authJsonData, configToml }),
};
const applied = await kubectlUpsertSecret(secretManifest, options.kubectlCommand ?? "kubectl");
return {
action: "provider-profile-config-updated",
mutation: true,
profile,
configured: true,
configured: Boolean(authJsonData),
secretRef: secretRefSummary(profile, namespace),
resourceVersion: objectPath(applied, ["metadata", "resourceVersion"]),
credentialHashSuffix,
credentialHashSuffix: credentialHashSuffix ?? null,
configHashSuffix: shortHash(configToml),
updatedAt: objectPath(applied, ["metadata", "annotations", `${credentialAnnotationPrefix}-updated-at`]) ?? new Date().toISOString(),
delegatedBy,
requiresExternalBridgeUpdate: profile === "deepseek" || profile === "dsflash-go",
requiresExternalBridgeUpdate: profileUsesMoonBridge(profile, configToml),
configTomlPrinted: false,
credentialValuesPrinted: false,
valuesPrinted: false,
pollCommands: {
config: `./scripts/agentrun provider-profiles config ${profile}`,
show: `./scripts/agentrun provider-profiles show ${profile}`,
setKey: `./scripts/agentrun provider-profiles set-key ${profile} --key-stdin`,
validate: `./scripts/agentrun provider-profiles validate ${profile} --wait --timeout-ms 120000`,
},
};
@@ -180,7 +178,7 @@ export async function setProviderProfileCredential(profileValue: string, body: u
updatedAt: objectPath(applied, ["metadata", "annotations", `${credentialAnnotationPrefix}-updated-at`]) ?? new Date().toISOString(),
configSummary: rendered.config.configSummary,
delegatedBy,
requiresExternalBridgeUpdate: profile === "deepseek" || profile === "dsflash-go",
requiresExternalBridgeUpdate: profileUsesMoonBridge(profile, rendered.config.configToml),
valuesPrinted: false,
pollCommands: {
show: `./scripts/agentrun provider-profiles show ${profile}`,
@@ -284,6 +282,46 @@ async function providerProfileStatus(profile: BackendProfile, options: ProviderP
};
}
async function listProviderProfileIds(options: ProviderProfileOptions): Promise<BackendProfile[]> {
const profiles = new Set<BackendProfile>(backendProfileSpecs.map((spec) => spec.profile));
const namespace = profileNamespace(options);
const secrets = await kubectlListSecrets(namespace, options.kubectlCommand ?? "kubectl");
for (const item of secrets) {
const secretProfile = providerProfileFromSecret(item);
if (secretProfile) profiles.add(secretProfile);
}
return [...profiles].sort(compareProviderProfiles);
}
function compareProviderProfiles(left: string, right: string): number {
const leftIndex = backendProfileSpecs.findIndex((item) => item.profile === left);
const rightIndex = backendProfileSpecs.findIndex((item) => item.profile === right);
if (leftIndex >= 0 || rightIndex >= 0) {
if (leftIndex < 0) return 1;
if (rightIndex < 0) return -1;
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
}
return left.localeCompare(right);
}
function providerProfileFromSecret(secret: JsonRecord): BackendProfile | null {
const metadata = asOptionalRecord(secret.metadata);
const labels = asOptionalRecord(metadata?.labels);
const annotatedProfile = stringPath(asOptionalRecord(metadata?.annotations), [`${credentialAnnotationPrefix}-profile`]);
const labeledProfile = stringPath(labels, ["agentrun.pikastech.local/profile"]);
const namedProfile = profileFromSecretName(stringPath(metadata, ["name"]));
for (const candidate of [annotatedProfile, labeledProfile, namedProfile]) {
if (candidate && isBackendProfileSlug(candidate)) return candidate;
}
return null;
}
function profileFromSecretName(name: string | null): string | null {
if (!name || !name.startsWith(providerSecretNamePrefix)) return null;
const profile = name.slice(providerSecretNamePrefix.length);
return profile.length > 0 ? profile : null;
}
function renderCredential(apiKey: string, config: RenderedConfig): { authJson: string; config: RenderedConfig } {
const authJson = `${JSON.stringify(authPayload(apiKey))}\n`;
return {
@@ -292,6 +330,12 @@ function renderCredential(apiKey: string, config: RenderedConfig): { authJson: s
};
}
function providerProfileSecretData(input: { authJsonData?: string | null; configToml: string }): JsonRecord {
const data: JsonRecord = { "config.toml": base64Data(input.configToml) };
if (input.authJsonData) data["auth.json"] = input.authJsonData;
return data;
}
function authPayload(apiKey: string): JsonRecord {
return { OPENAI_API_KEY: apiKey };
}
@@ -445,6 +489,10 @@ function defaultConfig(profile: BackendProfile): ProfileConfig {
};
}
function profileUsesMoonBridge(profile: BackendProfile, configToml: string): boolean {
return profile === "deepseek" || profile === "dsflash-go" || configToml.includes("hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local");
}
function validateBaseUrl(profile: BackendProfile, value: string): void {
let url: URL;
try {
@@ -525,7 +573,7 @@ function secretRefSummary(profile: BackendProfile, namespace: string): JsonRecor
function requiredSpec(profile: BackendProfile) {
const spec = backendProfileSpec(profile);
if (!spec) throw new AgentRunError("schema-invalid", `backendProfile ${profile} is not supported in v0.1`, { httpStatus: 400 });
if (!spec) throw new AgentRunError("schema-invalid", `backendProfile ${profile} must be a lowercase slug`, { httpStatus: 400 });
return spec;
}
@@ -543,6 +591,16 @@ async function kubectlGetSecret(name: string, namespace: string, kubectlCommand:
return parseKubectlObject(result.stdout, "secret");
}
async function kubectlListSecrets(namespace: string, kubectlCommand: string): Promise<JsonRecord[]> {
const result = await runKubectl(kubectlCommand, ["get", "secrets", "-n", namespace, "-o", "json"]);
if (result.code !== 0) {
throw new AgentRunError("infra-failed", `kubectl get secrets ${namespace} failed with code ${result.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(result.stderr.slice(-2000)), stdout: redactText(result.stdout.slice(-1000)) }) });
}
const parsed = parseKubectlObject(result.stdout, "secret-list");
const items = Array.isArray(parsed.items) ? parsed.items : [];
return items.filter((item): item is JsonRecord => typeof item === "object" && item !== null && !Array.isArray(item));
}
async function kubectlUpsertSecret(manifest: JsonRecord, kubectlCommand: string): Promise<JsonRecord> {
const name = stringPath(manifest, ["metadata", "name"]) ?? "<unknown>";
const namespace = stringPath(manifest, ["metadata", "namespace"]) ?? defaultNamespace;
@@ -12,12 +12,9 @@ const secretText = "sk-selftest-provider-profile-secret";
const selfTest: SelfTestCase = async (context) => {
const gitopsRenderer = await readFile(path.join(context.root, "scripts/src/gitops-render.ts"), "utf8");
assert.equal(gitopsRenderer.includes("agentrun-v01-mgr-provider-secret-manager"), true);
assert.equal(gitopsRenderer.includes('verbs: ["create"]'), true);
assert.equal(gitopsRenderer.includes('verbs: ["get", "patch", "update"]'), true);
assert.equal(gitopsRenderer.includes('resourceNames: ["agentrun-v01-provider-codex", "agentrun-v01-provider-deepseek", "agentrun-v01-provider-minimax-m3", "agentrun-v01-provider-dsflash-go"]'), true);
for (const profile of ["codex", "deepseek", "minimax-m3", "dsflash-go"]) {
assert.equal(gitopsRenderer.includes(`agentrun-v01-provider-${profile}`), true);
}
assert.equal(gitopsRenderer.includes('verbs: ["create", "get", "list", "patch", "update"]'), true);
assert.equal(gitopsRenderer.includes('resourceNames: ["agentrun-v01-provider-codex", "agentrun-v01-provider-deepseek", "agentrun-v01-provider-minimax-m3", "agentrun-v01-provider-dsflash-go"]'), false);
assert.equal(gitopsRenderer.includes('resources: ["secrets"]'), true);
const fakeKubectl = path.join(context.tmp, "fake-provider-kubectl.js");
const replacedSecretPath = path.join(context.tmp, "provider-secret-replace.json");
@@ -42,11 +39,22 @@ if (args[0] === "get" && args[1] === "secret") {
console.log(await stateFile.text());
process.exit(0);
}
if (name === "agentrun-v01-provider-dsflash-go") {
if (name === "agentrun-v01-provider-dsflash-go" || name === "agentrun-v01-provider-dsflash-go-cli-selftest") {
console.error('Error from server (NotFound): secrets "' + name + '" not found');
process.exit(1);
}
console.log(JSON.stringify(fixtureSecret(name)));
const fixture = fixtureSecret(name);
await Bun.write(secretStatePath(name), JSON.stringify(fixture));
console.log(JSON.stringify(fixture));
process.exit(0);
}
if (args[0] === "get" && args[1] === "secrets") {
const items = [fixtureSecret("agentrun-v01-provider-codex"), fixtureSecret("agentrun-v01-provider-deepseek"), fixtureSecret("agentrun-v01-provider-minimax-m3")];
const dsflashState = Bun.file(secretStatePath("agentrun-v01-provider-dsflash-go"));
if (await dsflashState.exists()) items.push(JSON.parse(await dsflashState.text()));
const dynamicState = Bun.file(secretStatePath("agentrun-v01-provider-dsflash-go-cli-selftest"));
if (await dynamicState.exists()) items.push(JSON.parse(await dynamicState.text()));
console.log(JSON.stringify({ apiVersion: "v1", kind: "SecretList", items }));
process.exit(0);
}
if (args[0] === "apply") {
@@ -71,12 +79,10 @@ if (args[0] === "patch" && args[1] === "secret") {
if (args[0] === "replace") {
const text = await readStdin();
const manifest = JSON.parse(text);
if (manifest.metadata?.name === "agentrun-v01-provider-dsflash-go") {
const stateFile = Bun.file(secretStatePath(manifest.metadata.name));
if (!(await stateFile.exists())) {
console.error('Error from server (NotFound): secrets "' + manifest.metadata.name + '" not found');
process.exit(1);
}
const stateFile = Bun.file(secretStatePath(manifest.metadata.name));
if (!(await stateFile.exists())) {
console.error('Error from server (NotFound): secrets "' + manifest.metadata.name + '" not found');
process.exit(1);
}
await Bun.write(${JSON.stringify(replacedSecretPath)}, JSON.stringify({ args, manifest }, null, 2));
const annotations = manifest.metadata?.annotations ?? {};
@@ -148,6 +154,27 @@ process.exit(1);
assert.equal(Buffer.from(String(configData["auth.json"]), "base64").toString("utf8").includes("redacted-fixture"), true);
assert.equal(Buffer.from(String(configData["config.toml"]), "base64").toString("utf8"), updatedConfigToml);
const dynamicProfile = "dsflash-go-cli-selftest";
const dynamicConfigToml = "model_provider = \"opencode\"\nmodel = \"deepseek-v4-flash\"\nreview_model = \"deepseek-v4-flash\"\nbase_url = \"http://hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local:4000/v1\"\n";
const dynamicConfig = await client.put(`/api/v1/provider-profiles/${encodeURIComponent(dynamicProfile)}/config`, {
configToml: dynamicConfigToml,
delegatedBy: { system: "hwlab-v02", userId: "u-dynamic", username: "dynamic", requestId: "req-config-dynamic-selftest" },
reason: "self-test-dynamic-config",
}) as JsonRecord;
assert.equal(dynamicConfig.profile, dynamicProfile);
assert.equal(dynamicConfig.configured, false);
assert.equal(dynamicConfig.credentialHashSuffix, null);
const dynamicCreateRecord = JSON.parse(await readFile(createdSecretPath, "utf8")) as JsonRecord;
const dynamicCreateManifest = dynamicCreateRecord.manifest as JsonRecord;
assert.equal(((dynamicCreateManifest.metadata as JsonRecord).name), `agentrun-v01-provider-${dynamicProfile}`);
const dynamicCreateData = dynamicCreateManifest.data as JsonRecord;
assert.equal(Object.hasOwn(dynamicCreateData, "auth.json"), false);
assert.equal(Buffer.from(String(dynamicCreateData["config.toml"]), "base64").toString("utf8"), dynamicConfigToml);
const dynamicShownAfterConfig = await client.get(`/api/v1/provider-profiles/${encodeURIComponent(dynamicProfile)}`) as JsonRecord;
assert.equal(dynamicShownAfterConfig.configured, false);
assert.equal(dynamicShownAfterConfig.failureKind, "secret-unavailable");
const updated = await client.put("/api/v1/provider-profiles/deepseek/credential", {
apiKey: secretText,
delegatedBy: { system: "hwlab-v02", userId: "u1", username: "tester", requestId: "req-selftest" },
@@ -205,6 +232,30 @@ process.exit(1);
assert.equal(dsflashShown.failureKind, null);
assertNoSecretLeak(dsflashCreated);
const dynamicCredential = await client.put(`/api/v1/provider-profiles/${encodeURIComponent(dynamicProfile)}/credential`, {
apiKey: secretText,
delegatedBy: { system: "hwlab-v02", userId: "u3", username: "tester3", requestId: "req-dynamic-create-selftest" },
reason: "self-test-dynamic-create",
}) as JsonRecord;
assert.equal(dynamicCredential.profile, dynamicProfile);
assert.equal(dynamicCredential.resourceVersion, "rv-cleaned");
assert.equal(dynamicCredential.requiresExternalBridgeUpdate, true);
const dynamicReplaceRecord = JSON.parse(await readFile(replacedSecretPath, "utf8")) as JsonRecord;
const dynamicReplaceManifest = dynamicReplaceRecord.manifest as JsonRecord;
assert.equal(((dynamicReplaceManifest.metadata as JsonRecord).name), `agentrun-v01-provider-${dynamicProfile}`);
const dynamicReplaceData = dynamicReplaceManifest.data as JsonRecord;
assert.equal(Buffer.from(String(dynamicReplaceData["auth.json"]), "base64").toString("utf8").includes(secretText), true);
assert.equal(Buffer.from(String(dynamicReplaceData["config.toml"]), "base64").toString("utf8"), dynamicConfigToml);
const dynamicShown = await client.get(`/api/v1/provider-profiles/${encodeURIComponent(dynamicProfile)}`) as JsonRecord;
assert.equal(dynamicShown.configured, true);
assert.equal(dynamicShown.failureKind, null);
const listAfterDynamic = await client.get("/api/v1/provider-profiles") as JsonRecord;
assert.equal(listAfterDynamic.count, 5);
const dynamicListItems = (listAfterDynamic.items as JsonRecord[]) ?? [];
assert.equal(dynamicListItems.some((item) => item.profile === dynamicProfile), true);
assertNoSecretLeak(dynamicCredential);
await assert.rejects(
() => client.put("/api/v1/provider-profiles/deepseek/credential", { apiKey: secretText, config: { baseUrl: "https://hyueapi.com/v1" } }),
(error) => error instanceof Error && error.message.includes("not hyueapi.com"),
@@ -228,7 +279,7 @@ process.exit(1);
assert.equal(finalValidation.status, "completed");
assert.equal(JSON.stringify(finalValidation).includes(secretText), false);
assertNoSecretLeak(finalValidation);
return { name: "provider-profile-management", tests: ["provider-profiles-list-redacted", "provider-profile-config", "provider-profile-set-key-redacted", "provider-profile-secret-replace-annotation-cleanup", "provider-profile-secret-create-upsert", "provider-profile-deepseek-moon-bridge", "provider-profile-manager-secret-rbac", "provider-profile-validation-runner-job"] };
return { name: "provider-profile-management", tests: ["provider-profiles-list-redacted", "provider-profile-config", "provider-profile-set-key-redacted", "provider-profile-secret-replace-annotation-cleanup", "provider-profile-secret-create-upsert", "provider-profile-config-only-create", "provider-profile-dynamic-slug-roundtrip", "provider-profile-deepseek-moon-bridge", "provider-profile-manager-secret-rbac", "provider-profile-validation-runner-job"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}