fix: expose dynamic provider profiles in backends list
This commit is contained in:
@@ -70,6 +70,17 @@ export const backendProfileSpecs = builtinBackendProfileSpecs;
|
||||
|
||||
export const backendProfiles = builtinBackendProfileSpecs.map((item) => item.profile) as readonly BackendProfile[];
|
||||
|
||||
export function compareBackendProfiles(left: string, right: string): number {
|
||||
const leftIndex = builtinBackendProfileSpecs.findIndex((item) => item.profile === left);
|
||||
const rightIndex = builtinBackendProfileSpecs.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);
|
||||
}
|
||||
|
||||
export function defaultSecretNameForProfile(profile: string): string {
|
||||
return `agentrun-v01-provider-${profile}`;
|
||||
}
|
||||
@@ -138,8 +149,14 @@ export function mergeBackendCapability(profile: string, storedCapabilities: Json
|
||||
};
|
||||
}
|
||||
|
||||
export function backendCapabilities(): JsonRecord[] {
|
||||
return builtinBackendProfileSpecs.map(backendCapability);
|
||||
export function backendCapabilities(profiles: readonly string[] = backendProfiles): JsonRecord[] {
|
||||
const profileIds = new Set<BackendProfile>(backendProfiles);
|
||||
for (const profile of profiles) {
|
||||
if (isBackendProfileSlug(profile)) profileIds.add(profile as BackendProfile);
|
||||
}
|
||||
return [...profileIds]
|
||||
.sort(compareBackendProfiles)
|
||||
.map((profile) => backendCapability(backendProfileSpec(profile) as BackendProfileSpec));
|
||||
}
|
||||
|
||||
export function backendCapabilitiesSqlValues(profiles?: readonly BackendProfile[], options: { requiredSecretKeysByProfile?: Record<string, readonly string[]> } = {}): string {
|
||||
|
||||
@@ -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, isBackendProfileSlug } from "../common/backend-profiles.js";
|
||||
import { backendCapabilities, backendProfileSpec, backendProfileSpecs, compareBackendProfiles, isBackendProfileSlug } from "../common/backend-profiles.js";
|
||||
import { dsflashGoModelCatalogFile, dsflashGoModelCatalogJson, dsflashGoModelSlug, dsflashGoRuntimeModelCatalogPath } from "../common/model-catalogs.js";
|
||||
import type { AgentRunStore } from "./store.js";
|
||||
import type { BackendProfile, ExecutionPolicy, JsonRecord, JsonValue } from "../common/types.js";
|
||||
@@ -48,6 +48,33 @@ export async function listProviderProfiles(options: ProviderProfileOptions = {})
|
||||
return { items, count: items.length, valuesPrinted: false };
|
||||
}
|
||||
|
||||
export async function listBackendCapabilities(options: ProviderProfileOptions = {}): Promise<JsonRecord> {
|
||||
const profiles = new Set<BackendProfile>(backendProfileSpecs.map((spec) => spec.profile));
|
||||
try {
|
||||
for (const profile of await listProviderProfileIds(options)) profiles.add(profile);
|
||||
const dynamicProfiles = [...profiles].filter((profile) => !isBuiltinProviderProfile(profile)).sort(compareBackendProfiles);
|
||||
return {
|
||||
items: backendCapabilities([...profiles]),
|
||||
dynamicProfileDiscovery: {
|
||||
status: "succeeded",
|
||||
dynamicProfiles,
|
||||
dynamicProfileCount: dynamicProfiles.length,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
items: backendCapabilities([...profiles]),
|
||||
dynamicProfileDiscovery: {
|
||||
status: "failed",
|
||||
failureKind: error instanceof AgentRunError ? error.failureKind : "infra-failed",
|
||||
message: redactText(error instanceof Error ? error.message : String(error)),
|
||||
valuesPrinted: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function showProviderProfile(profile: string, options: ProviderProfileOptions = {}): Promise<JsonRecord> {
|
||||
return providerProfileStatus(validateBackendProfile(profile), options);
|
||||
}
|
||||
@@ -328,14 +355,7 @@ async function listProviderProfileIds(options: ProviderProfileOptions): Promise<
|
||||
}
|
||||
|
||||
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);
|
||||
return compareBackendProfiles(left, right);
|
||||
}
|
||||
|
||||
function isBuiltinProviderProfile(profile: BackendProfile): boolean {
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import { runnerJobStatusSummary } from "./runner-job-status.js";
|
||||
import { createSessionPvc, deleteSessionPvc, getSessionPvcSummary, refreshSessionPvcSummary, runSessionStorageGc } from "./session-pvc.js";
|
||||
import type { SessionPvcSummary } from "./session-pvc.js";
|
||||
import type { SessionPvcOptions } from "./session-pvc.js";
|
||||
import { getProviderProfileConfig, getProviderProfileValidation, listProviderProfiles, removeProviderProfile, setProviderProfileConfig, setProviderProfileCredential, showProviderProfile, validateProviderProfile } from "./provider-profiles.js";
|
||||
import { getProviderProfileConfig, getProviderProfileValidation, listBackendCapabilities, listProviderProfiles, removeProviderProfile, setProviderProfileConfig, setProviderProfileCredential, showProviderProfile, validateProviderProfile } from "./provider-profiles.js";
|
||||
|
||||
function pvcOptions(defaults: { kubectlCommand?: string } | undefined): SessionPvcOptions {
|
||||
return defaults?.kubectlCommand ? { kubectlCommand: defaults.kubectlCommand } : {};
|
||||
@@ -92,7 +92,7 @@ async function route({ method, url, body, store, sourceCommit, runnerJobDefaults
|
||||
const ready = path === "/health/live" ? true : database.ready;
|
||||
return { serviceId: "agentrun-mgr", live: true, ready, database, sourceCommit, secretRefs: { databaseUrl: database.adapter === "postgres" ? "redacted" : "not-used", valuesPrinted: false } };
|
||||
}
|
||||
if (method === "GET" && path === "/api/v1/backends") return { items: await store.backends() as unknown as JsonValue };
|
||||
if (method === "GET" && path === "/api/v1/backends") return await listBackendCapabilities(providerProfileDefaults) as JsonValue;
|
||||
if (method === "GET" && path === "/api/v1/provider-profiles") return await listProviderProfiles(providerProfileDefaults) as JsonValue;
|
||||
const providerProfileMatch = path.match(/^\/api\/v1\/provider-profiles\/([^/]+)$/u);
|
||||
if (method === "GET" && providerProfileMatch) return await showProviderProfile(providerProfileMatch[1] ?? "", providerProfileDefaults) as JsonValue;
|
||||
|
||||
@@ -296,6 +296,14 @@ process.exit(1);
|
||||
assert.equal(listAfterDynamic.count, 5);
|
||||
const dynamicListItems = (listAfterDynamic.items as JsonRecord[]) ?? [];
|
||||
assert.equal(dynamicListItems.some((item) => item.profile === dynamicProfile), true);
|
||||
const backendsAfterDynamic = await client.get("/api/v1/backends") as JsonRecord;
|
||||
const backendItems = (backendsAfterDynamic.items as JsonRecord[]) ?? [];
|
||||
const dynamicBackend = backendItems.find((item) => item.profile === dynamicProfile) as JsonRecord | undefined;
|
||||
assert.equal(dynamicBackend?.backendKind, "codex-app-server-stdio");
|
||||
assert.equal(((dynamicBackend?.defaultSecretRef as JsonRecord).name), `agentrun-v01-provider-${dynamicProfile}`);
|
||||
assert.equal(((backendsAfterDynamic.dynamicProfileDiscovery as JsonRecord).status), "succeeded");
|
||||
assert.equal(((backendsAfterDynamic.dynamicProfileDiscovery as JsonRecord).dynamicProfiles as string[]).includes(dynamicProfile), true);
|
||||
assertNoSecretLeak(backendsAfterDynamic);
|
||||
assertNoSecretLeak(dynamicCredential);
|
||||
|
||||
const removedDeepseek = await client.delete("/api/v1/provider-profiles/deepseek") as JsonRecord;
|
||||
@@ -345,7 +353,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-set-auth-json-redacted", "provider-profile-secret-replace-annotation-cleanup", "provider-profile-secret-create-upsert", "provider-profile-config-only-create", "provider-profile-dsflash-go-model-catalog", "provider-profile-dynamic-slug-roundtrip", "provider-profile-remove-builtin", "provider-profile-remove-dynamic-slug", "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-set-auth-json-redacted", "provider-profile-secret-replace-annotation-cleanup", "provider-profile-secret-create-upsert", "provider-profile-config-only-create", "provider-profile-dsflash-go-model-catalog", "provider-profile-dynamic-slug-roundtrip", "backends-list-dynamic-profile", "provider-profile-remove-builtin", "provider-profile-remove-dynamic-slug", "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()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user