import { createHash, randomUUID } from "node:crypto"; import { spawn } from "node:child_process"; import { AgentRunError } from "../common/errors.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"; import { asRecord, validateBackendProfile } from "../common/validation.js"; import { redactJson, redactText } from "../common/redaction.js"; import { createKubernetesRunnerJob, type RunnerJobDefaults } from "./kubernetes-runner-job.js"; import { buildRunResult } from "./result.js"; 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; kubectlCommand?: string; } export interface ProviderProfileValidationOptions extends ProviderProfileOptions { store: AgentRunStore; sourceCommit: string; runnerJobDefaults?: Partial; } interface ProfileConfig { model: string; providerName: string; baseUrl: string; envKey: string; wireApi: "responses"; displayName: string; modelCatalogPath?: string; } interface RenderedConfig { configToml: string; configSummary: JsonRecord; } export async function listProviderProfiles(options: ProviderProfileOptions = {}): Promise { const profiles = await listProviderProfileIds(options); const items = await Promise.all(profiles.map((profile) => providerProfileStatus(profile, options))); return { items, count: items.length, valuesPrinted: false }; } export async function listBackendCapabilities(options: ProviderProfileOptions = {}): Promise { const profiles = new Set(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 { return providerProfileStatus(validateBackendProfile(profile), options); } export async function getProviderProfileConfig(profileValue: string, options: ProviderProfileOptions = {}): Promise { const profile = validateBackendProfile(profileValue); const spec = requiredSpec(profile); const namespace = profileNamespace(options); const secret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl"); if (!secret) throw new AgentRunError("secret-unavailable", `provider profile ${profile} Secret is not configured`, { httpStatus: 404, details: { profile, secretRef: secretRefSummary(profile, namespace) } }); const data = asOptionalRecord(secret.data); const annotations = asOptionalRecord(asOptionalRecord(secret.metadata)?.annotations); const configToml = configTomlFromData(data, profile); return { action: "provider-profile-config-read", profile, configured: hasRequiredKeys(data, spec.requiredSecretKeys), secretRef: secretRefSummary(profile, namespace), resourceVersion: stringPath(secret, ["metadata", "resourceVersion"]), credentialHashSuffix: hashDataKey(data, "auth.json") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-credential-hash-suffix`]), configHashSuffix: shortHash(configToml), updatedAt: stringPath(annotations, [`${credentialAnnotationPrefix}-updated-at`]) ?? stringPath(secret, ["metadata", "creationTimestamp"]), configToml, configTomlPrinted: true, credentialValuesPrinted: false, valuesPrinted: false, }; } export async function removeProviderProfile(profileValue: string, options: ProviderProfileOptions = {}): Promise { const profile = validateBackendProfile(profileValue); const spec = requiredSpec(profile); const namespace = profileNamespace(options); const secret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl"); const data = asOptionalRecord(secret?.data); const annotations = asOptionalRecord(asOptionalRecord(secret?.metadata)?.annotations); if (secret) await kubectlDeleteSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl"); return { action: "provider-profile-removed", mutation: true, profile, configured: false, removed: Boolean(secret), ...(secret ? {} : { alreadyAbsent: true }), ...(isBuiltinProviderProfile(profile) ? { builtinCapabilityRetained: true } : {}), secretRef: secretRefSummary(profile, namespace), deletedResourceVersion: stringPath(secret, ["metadata", "resourceVersion"]), credentialHashSuffix: hashDataKey(data, "auth.json") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-credential-hash-suffix`]), configHashSuffix: hashDataKey(data, "config.toml") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-config-hash-suffix`]), updatedAt: new Date().toISOString(), valuesPrinted: false, pollActions: [ providerActionDescriptor({ action: "list-provider-profiles", operation: "list", resourceKind: "provider-profile", resourceName: "*" }), providerActionDescriptor({ action: "inspect-provider-profile", operation: "describe", resourceKind: "provider-profile", resourceName: profile, profile }), providerActionDescriptor({ action: "set-provider-key", operation: "set-key", resourceKind: "provider-profile", resourceName: profile, profile, inputKind: "credential" }), providerActionDescriptor({ action: "set-provider-config", operation: "set-config", resourceKind: "provider-profile", resourceName: profile, profile, inputKind: "config" }), ], }; } export async function setProviderProfileConfig(profileValue: string, body: unknown, options: ProviderProfileOptions = {}): Promise { const profile = validateBackendProfile(profileValue); const spec = requiredSpec(profile); const record = asRecord(body ?? {}, "providerProfileConfig"); const configToml = configTomlField(record, profile); const delegatedBy = delegatedBySummary(record.delegatedBy); const namespace = profileNamespace(options); const existingSecret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl"); const existingData = asOptionalRecord(existingSecret?.data); const authJsonData = dataKey(existingData, "auth.json"); const credentialHashSuffix = hashDataKey(existingData, "auth.json"); const secretData = providerProfileSecretData({ profile, authJsonData, configToml, existingData }); const secretManifest: JsonRecord = { apiVersion: "v1", kind: "Secret", metadata: { name: spec.defaultSecretName, namespace, labels: { "app.kubernetes.io/part-of": "agentrun", "agentrun.pikastech.local/profile": profile, }, annotations: { [`${credentialAnnotationPrefix}-profile`]: profile, ...(credentialHashSuffix ? { [`${credentialAnnotationPrefix}-credential-hash-suffix`]: credentialHashSuffix } : {}), [`${credentialAnnotationPrefix}-config-hash-suffix`]: shortHash(configToml), [`${credentialAnnotationPrefix}-updated-at`]: new Date().toISOString(), ...(delegatedBy ? { [`${credentialAnnotationPrefix}-delegated-system`]: delegatedBy.system, [`${credentialAnnotationPrefix}-delegated-request-id`]: delegatedBy.requestId ?? "" } : {}), }, }, type: "Opaque", data: secretData, }; const applied = await kubectlUpsertSecret(secretManifest, options.kubectlCommand ?? "kubectl"); return { action: "provider-profile-config-updated", mutation: true, profile, configured: hasRequiredKeys(secretData, spec.requiredSecretKeys), secretRef: secretRefSummary(profile, namespace), resourceVersion: objectPath(applied, ["metadata", "resourceVersion"]), credentialHashSuffix: credentialHashSuffix ?? null, configHashSuffix: shortHash(configToml), updatedAt: objectPath(applied, ["metadata", "annotations", `${credentialAnnotationPrefix}-updated-at`]) ?? new Date().toISOString(), delegatedBy, requiresExternalBridgeUpdate: profileUsesMoonBridge(profile, configToml), configTomlPrinted: false, credentialValuesPrinted: false, valuesPrinted: false, pollActions: [ providerActionDescriptor({ action: "inspect-provider-config", operation: "config", resourceKind: "provider-profile", resourceName: profile, profile }), providerActionDescriptor({ action: "inspect-provider-profile", operation: "describe", resourceKind: "provider-profile", resourceName: profile, profile }), providerActionDescriptor({ action: "set-provider-key", operation: "set-key", resourceKind: "provider-profile", resourceName: profile, profile, inputKind: "credential" }), providerActionDescriptor({ action: "validate-provider-profile", operation: "validate", resourceKind: "provider-profile", resourceName: profile, profile, wait: true, timeoutMs: 120_000 }), ], }; } export async function setProviderProfileCredential(profileValue: string, body: unknown, options: ProviderProfileOptions = {}): Promise { const profile = validateBackendProfile(profileValue); const spec = requiredSpec(profile); const record = asRecord(body ?? {}, "providerProfileCredential"); const credential = credentialAuthJson(record); const delegatedBy = delegatedBySummary(record.delegatedBy); const namespace = profileNamespace(options); const existingSecret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl"); const existingData = asOptionalRecord(existingSecret?.data); const renderedConfig = await renderedConfigForWrite(profile, record, options); const secretData = providerProfileSecretData({ profile, authJsonData: base64Data(credential.authJson), configToml: renderedConfig.configToml, existingData }); const secretManifest: JsonRecord = { apiVersion: "v1", kind: "Secret", metadata: { name: spec.defaultSecretName, namespace, labels: { "app.kubernetes.io/part-of": "agentrun", "agentrun.pikastech.local/profile": profile, }, annotations: { [`${credentialAnnotationPrefix}-profile`]: profile, [`${credentialAnnotationPrefix}-credential-hash-suffix`]: shortHash(credential.authJson), [`${credentialAnnotationPrefix}-config-hash-suffix`]: shortHash(renderedConfig.configToml), [`${credentialAnnotationPrefix}-updated-at`]: new Date().toISOString(), ...(delegatedBy ? { [`${credentialAnnotationPrefix}-delegated-system`]: delegatedBy.system, [`${credentialAnnotationPrefix}-delegated-request-id`]: delegatedBy.requestId ?? "" } : {}), }, }, type: "Opaque", data: secretData, }; const applied = await kubectlUpsertSecret(secretManifest, options.kubectlCommand ?? "kubectl"); return { action: "provider-profile-credential-updated", mutation: true, profile, configured: hasRequiredKeys(secretData, spec.requiredSecretKeys), secretRef: secretRefSummary(profile, namespace), resourceVersion: objectPath(applied, ["metadata", "resourceVersion"]), credentialHashSuffix: shortHash(credential.authJson), configHashSuffix: shortHash(renderedConfig.configToml), updatedAt: objectPath(applied, ["metadata", "annotations", `${credentialAnnotationPrefix}-updated-at`]) ?? new Date().toISOString(), credentialSource: credential.source, configSummary: renderedConfig.configSummary, delegatedBy, requiresExternalBridgeUpdate: profileUsesMoonBridge(profile, renderedConfig.configToml), valuesPrinted: false, pollActions: [ providerActionDescriptor({ action: "inspect-provider-profile", operation: "describe", resourceKind: "provider-profile", resourceName: profile, profile }), providerActionDescriptor({ action: "validate-provider-profile", operation: "validate", resourceKind: "provider-profile", resourceName: profile, profile, wait: true, timeoutMs: 120_000 }), ], }; } function providerActionDescriptor(input: { action: string; operation: string; resourceKind: string; resourceName: string; profile?: string | null; inputKind?: string | null; wait?: boolean; timeoutMs?: number | null }): JsonRecord { return { action: input.action, operation: input.operation, resourceKind: input.resourceKind, resourceName: input.resourceName, profile: input.profile ?? null, ...(input.inputKind ? { inputKind: input.inputKind } : {}), ...(input.wait === true ? { wait: true } : {}), ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}), valuesPrinted: false, }; } export async function validateProviderProfile(profileValue: string, body: unknown, options: ProviderProfileValidationOptions): Promise { const profile = validateBackendProfile(profileValue); const namespace = profileNamespace(options); const runnerDefaults = runnerDefaultsForValidation(options, namespace); const record = body === null ? {} : asRecord(body ?? {}, "providerProfileValidation"); const validationId = `val_${randomUUID().replace(/-/gu, "")}`; const prompt = typeof record.prompt === "string" && record.prompt.trim().length > 0 ? record.prompt.trim() : `只回复 AGENTRUN_PROVIDER_PROFILE_OK_${profile.replace(/[^a-z0-9]/gu, "_").toUpperCase()}`; const run = await options.store.createRun({ tenantId: "hwlab", projectId: "pikasTech/HWLAB", workspaceRef: { kind: "opaque", path: "/home/agentrun/agentrun-source" }, providerId: "G14", backendProfile: profile, executionPolicy: validationExecutionPolicy(profile, namespace), traceSink: null, sessionRef: { sessionId: `sess_${validationId}`, metadata: { purpose: "provider-profile-validation", validationId, profile } }, resourceBundleRef: null, }); const command = await options.store.createCommand(run.id, { type: "turn", payload: { prompt }, idempotencyKey: validationId }); const runnerJob = await createKubernetesRunnerJob({ store: options.store, runId: run.id, input: { commandId: command.id, idempotencyKey: validationId, attemptId: `attempt_${validationId.slice(4, 16)}`, }, defaults: runnerDefaults, }); return validationResponse({ profile, validationId, runId: run.id, commandId: command.id, runnerJob, status: "running" }); } export async function getProviderProfileValidation(profileValue: string, validationId: string, options: { store: AgentRunStore }): Promise { const profile = validateBackendProfile(profileValue); if (!/^val_[a-f0-9]{32}$/u.test(validationId)) throw new AgentRunError("schema-invalid", `validationId ${validationId} is not valid`, { httpStatus: 400 }); const session = await options.store.getSession(`sess_${validationId}`); const runId = session?.lastRunId ?? session?.activeRunId; const commandId = session?.lastCommandId ?? session?.activeCommandId; if (!runId || !commandId) throw new AgentRunError("schema-invalid", `validation ${validationId} was not found`, { httpStatus: 404 }); const [result, jobs, events] = await Promise.all([ buildRunResult(options.store, runId, commandId), options.store.listRunnerJobs(runId, commandId), options.store.listEvents(runId, 0, 500), ]); const status = validationStatus(result as JsonRecord); return { validationId, profile, runId, commandId, status, result: result as JsonRecord, runnerJobs: jobs.map((job) => runnerJobStatusSummary(job, events)), lastSeq: events.at(-1)?.seq ?? 0, valuesPrinted: false, }; } async function providerProfileStatus(profile: BackendProfile, options: ProviderProfileOptions): Promise { const spec = requiredSpec(profile); const namespace = profileNamespace(options); const secretRef = secretRefSummary(profile, namespace); const secret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl"); if (!secret) { return { profile, backendKind: spec.backendKind, configured: false, failureKind: "secret-unavailable", secretRef, resourceVersion: null, credentialHashSuffix: null, configHashSuffix: null, updatedAt: null, valuesPrinted: false, }; } const data = asOptionalRecord(secret.data); const annotations = asOptionalRecord(asOptionalRecord(secret.metadata)?.annotations); return { profile, backendKind: spec.backendKind, configured: hasRequiredKeys(data, spec.requiredSecretKeys), failureKind: hasRequiredKeys(data, spec.requiredSecretKeys) ? null : "secret-unavailable", secretRef, resourceVersion: stringPath(secret, ["metadata", "resourceVersion"]), credentialHashSuffix: hashDataKey(data, "auth.json") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-credential-hash-suffix`]), configHashSuffix: hashDataKey(data, "config.toml") ?? stringPath(annotations, [`${credentialAnnotationPrefix}-config-hash-suffix`]), updatedAt: stringPath(annotations, [`${credentialAnnotationPrefix}-updated-at`]) ?? stringPath(secret, ["metadata", "creationTimestamp"]), keyPresence: Object.fromEntries(spec.requiredSecretKeys.map((key) => [key, typeof data?.[key] === "string"])) as JsonRecord, lastValidation: null, valuesPrinted: false, }; } async function listProviderProfileIds(options: ProviderProfileOptions): Promise { const profiles = new Set(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 { return compareBackendProfiles(left, right); } function isBuiltinProviderProfile(profile: BackendProfile): boolean { return backendProfileSpecs.some((item) => item.profile === profile); } 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 providerProfileSecretData(input: { profile: BackendProfile; authJsonData?: string | null; configToml: string; existingData?: JsonRecord | null }): JsonRecord { const data: JsonRecord = { "config.toml": base64Data(input.configToml) }; if (input.authJsonData) data["auth.json"] = input.authJsonData; const modelCatalogData = input.profile === "dsflash-go" ? dataKey(input.existingData ?? null, dsflashGoModelCatalogFile) ?? defaultModelCatalogData(input.profile) : null; if (modelCatalogData) data[dsflashGoModelCatalogFile] = modelCatalogData; return data; } function defaultModelCatalogData(profile: BackendProfile): string | null { return profile === "dsflash-go" ? base64Data(dsflashGoModelCatalogJson()) : null; } function credentialAuthJson(record: JsonRecord): { authJson: string; source: "auth-json" | "api-key" } { const authJson = record.authJson; if (typeof authJson === "string" && authJson.trim().length > 0) return { authJson: authJsonField(authJson), source: "auth-json" }; const apiKey = stringField(record, "apiKey"); if (apiKey.length < 8) throw new AgentRunError("secret-unavailable", "apiKey is too short", { httpStatus: 400 }); return { authJson: `${JSON.stringify(authPayload(apiKey))}\n`, source: "api-key" }; } function authJsonField(value: string): string { if (!value.trim()) throw new AgentRunError("schema-invalid", "authJson is required", { httpStatus: 400 }); let parsed: unknown; try { parsed = JSON.parse(value); } catch { throw new AgentRunError("schema-invalid", "authJson must be valid JSON", { httpStatus: 400 }); } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new AgentRunError("schema-invalid", "authJson must be a JSON object", { httpStatus: 400 }); return value; } function authPayload(apiKey: string): JsonRecord { return { OPENAI_API_KEY: apiKey }; } function renderConfigToml(config: ProfileConfig): string { const { contextWindow, autoCompactTokenLimit } = contextWindowSettings(config.model); return [ `model_provider = ${tomlString(config.providerName)}`, `model = ${tomlString(config.model)}`, `review_model = ${tomlString(config.model)}`, "disable_response_storage = true", "network_access = \"enabled\"", `model_context_window = ${contextWindow}`, `model_auto_compact_token_limit = ${autoCompactTokenLimit}`, ...(config.modelCatalogPath ? [`model_catalog_json = ${tomlString(config.modelCatalogPath)}`] : []), "approvals_reviewer = \"user\"", "", `[model_providers.${config.providerName}]`, `name = ${tomlString(config.displayName)}`, `base_url = ${tomlString(config.baseUrl)}`, `wire_api = ${tomlString(config.wireApi)}`, "requires_openai_auth = true", "", "[projects.\"/root\"]", "trust_level = \"trusted\"", "", "[projects.\"/home/agentrun/workspaces\"]", "trust_level = \"trusted\"", "", "[tui.model_availability_nux]", `${tomlString(config.model)} = 4`, "", "[notice]", "hide_full_access_warning = true", "", ].join("\n"); } async function renderedConfigForWrite(profile: BackendProfile, record: JsonRecord, options: ProviderProfileOptions): Promise { if (record.config !== undefined && record.config !== null) return renderedConfigFromProfileConfig(configField(record.config, profile), "request-config"); const existing = await existingConfigToml(profile, options); if (existing && existingConfigAllowed(profile, existing)) { return { configToml: existing, configSummary: { source: "existing-secret", preserved: true, valuesPrinted: false } }; } return renderedConfigFromProfileConfig(defaultConfig(profile), "profile-default"); } function renderedConfigFromProfileConfig(config: ProfileConfig, source: string): RenderedConfig { return { configToml: renderConfigToml(config), configSummary: { source, model: config.model, providerName: config.providerName, baseUrl: config.baseUrl, authField: config.envKey, wireApi: config.wireApi, modelCatalogJson: config.modelCatalogPath ?? null, valuesPrinted: false, }, }; } async function existingConfigToml(profile: BackendProfile, options: ProviderProfileOptions): Promise { const spec = requiredSpec(profile); const namespace = profileNamespace(options); const secret = await kubectlGetSecret(spec.defaultSecretName, namespace, options.kubectlCommand ?? "kubectl"); const data = asOptionalRecord(secret?.data); const value = data?.["config.toml"]; if (typeof value !== "string" || value.length === 0) return null; try { return Buffer.from(value, "base64").toString("utf8"); } catch { return null; } } function existingConfigAllowed(profile: BackendProfile, configToml: string): boolean { if (configToml.trim().length === 0) return false; if ((profile === "deepseek" || profile === "dsflash-go") && configToml.includes("hyueapi.com")) return false; if (profile === "deepseek" && !configToml.includes("hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local")) return false; if (profile === "dsflash-go") { if (!configToml.includes(dsflashGoModelSlug)) return false; if (modelCatalogPathFromConfigToml(configToml) !== dsflashGoRuntimeModelCatalogPath) return false; const baseUrl = baseUrlFromConfigToml(configToml); if (!baseUrl) return false; try { if (!isAllowedDsflashBridgeUrl(new URL(baseUrl))) return false; } catch { return false; } } return true; } function configTomlFromData(data: JsonRecord | null, profile: BackendProfile): string { const encoded = dataKey(data, "config.toml"); if (!encoded) throw new AgentRunError("secret-unavailable", `provider profile ${profile} config.toml is not configured`, { httpStatus: 404, details: { profile, key: "config.toml" } }); try { return Buffer.from(encoded, "base64").toString("utf8"); } catch { throw new AgentRunError("schema-invalid", `provider profile ${profile} config.toml is not valid base64`, { httpStatus: 400, details: { profile, key: "config.toml" } }); } } function configTomlField(record: JsonRecord, profile: BackendProfile): string { const value = record.configToml; if (typeof value !== "string" || value.trim().length === 0) throw new AgentRunError("schema-invalid", "configToml is required", { httpStatus: 400 }); validateConfigTomlForProfile(profile, value); return value; } function configField(value: unknown, profile: BackendProfile): ProfileConfig { const defaults = defaultConfig(profile); if (value === undefined || value === null) return defaults; const record = asRecord(value, "config"); const model = optionalString(record.model) ?? defaults.model; const baseUrl = optionalString(record.baseUrl) ?? defaults.baseUrl; const providerName = optionalString(record.providerName) ?? defaults.providerName; const envKey = optionalString(record.envKey) ?? defaults.envKey; if (profile === "dsflash-go" && model !== dsflashGoModelSlug) throw new AgentRunError("schema-invalid", `dsflash-go model must be ${dsflashGoModelSlug}`, { httpStatus: 400 }); validateBaseUrl(profile, baseUrl); if (!/^[A-Z_][A-Z0-9_]{0,63}$/u.test(envKey)) throw new AgentRunError("schema-invalid", "config.envKey must be an uppercase env name", { httpStatus: 400 }); if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/u.test(providerName)) throw new AgentRunError("schema-invalid", "config.providerName must be a provider identifier", { httpStatus: 400 }); return { ...defaults, model, baseUrl, providerName, envKey }; } function defaultConfig(profile: BackendProfile): ProfileConfig { if (profile === "deepseek") { return { model: "deepseek-chat", providerName: "OpenAI", baseUrl: "http://hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local:4000/v1", envKey: "OPENAI_API_KEY", wireApi: "responses", displayName: "OpenAI", }; } if (profile === "dsflash-go") { return { model: "deepseek-v4-flash", providerName: "opencode", baseUrl: "http://hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local:4000/v1", envKey: "OPENAI_API_KEY", wireApi: "responses", displayName: "Moon Bridge OpenCode Zen Go Flash", modelCatalogPath: dsflashGoRuntimeModelCatalogPath, }; } if (profile === "minimax-m3") { return { model: "MiniMax-M3", providerName: "minimax", baseUrl: "https://api.minimaxi.com/v1", envKey: "OPENAI_API_KEY", wireApi: "responses", displayName: "MiniMax", }; } return { model: "gpt-5-codex", providerName: "openai", baseUrl: "https://api.openai.com/v1", envKey: "OPENAI_API_KEY", wireApi: "responses", displayName: "OpenAI", }; } function profileUsesMoonBridge(profile: BackendProfile, configToml: string): boolean { return profile === "deepseek" || profile === "dsflash-go" || /hwlab-deepseek-proxy\.hwlab-v02\.svc\.cluster\.local|moon|bridge/iu.test(configToml); } function validateBaseUrl(profile: BackendProfile, value: string): void { let url: URL; try { url = new URL(value); } catch { throw new AgentRunError("schema-invalid", "config.baseUrl must be a valid URL", { httpStatus: 400 }); } if ((profile === "deepseek" || profile === "dsflash-go") && isHyueHost(url.hostname)) { throw new AgentRunError("tenant-policy-denied", `${profile} profile must use HWLAB Moon Bridge, not hyueapi.com`, { httpStatus: 403 }); } if (profile === "deepseek" && url.hostname !== "hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local") { throw new AgentRunError("tenant-policy-denied", "deepseek profile baseUrl must point to HWLAB v0.2 DeepSeek bridge", { httpStatus: 403 }); } if (profile === "dsflash-go" && !isAllowedDsflashBridgeUrl(url)) { throw new AgentRunError("tenant-policy-denied", "dsflash-go profile baseUrl must point to a Moon Bridge service or wrapper-local bridge", { httpStatus: 403 }); } } function validateConfigTomlForProfile(profile: BackendProfile, configToml: string): void { if ((profile === "deepseek" || profile === "dsflash-go") && /hyueapi\.com/iu.test(configToml)) { throw new AgentRunError("tenant-policy-denied", `${profile} profile must use HWLAB Moon Bridge, not hyueapi.com`, { httpStatus: 403 }); } if (profile === "deepseek" && !configToml.includes("hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local")) { throw new AgentRunError("tenant-policy-denied", "deepseek profile config.toml must point to HWLAB v0.2 DeepSeek bridge", { httpStatus: 403 }); } if (profile !== "dsflash-go") return; if (!configToml.includes(dsflashGoModelSlug)) throw new AgentRunError("schema-invalid", `dsflash-go config.toml must use model ${dsflashGoModelSlug}`, { httpStatus: 400 }); if (modelCatalogPathFromConfigToml(configToml) !== dsflashGoRuntimeModelCatalogPath) throw new AgentRunError("schema-invalid", `dsflash-go config.toml must set model_catalog_json to ${dsflashGoRuntimeModelCatalogPath}`, { httpStatus: 400 }); const baseUrl = baseUrlFromConfigToml(configToml); if (!baseUrl) throw new AgentRunError("schema-invalid", "dsflash-go config.toml must include model_providers base_url", { httpStatus: 400 }); validateBaseUrl(profile, baseUrl); } function isHyueHost(hostname: string): boolean { return hostname === "hyueapi.com" || hostname.endsWith(".hyueapi.com"); } function isAllowedDsflashBridgeUrl(url: URL): boolean { if (url.hostname === "127.0.0.1" || url.hostname === "localhost") return true; if (url.hostname === "hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local") return true; return url.hostname.endsWith(".svc.cluster.local") && /moon|bridge|deepseek|opencode/iu.test(url.hostname); } function baseUrlFromConfigToml(configToml: string): string | null { const match = configToml.match(/^\s*base_url\s*=\s*"([^"]+)"\s*$/mu); return match?.[1] ?? null; } function modelCatalogPathFromConfigToml(configToml: string): string | null { const match = configToml.match(/^\s*model_catalog_json\s*=\s*"([^"]+)"\s*$/mu); return match?.[1] ?? null; } function contextWindowSettings(model: string): { contextWindow: number; autoCompactTokenLimit: number } { if (model === "deepseek-v4-pro" || model === "deepseek-v4-flash") { return { contextWindow: 1_000_000, autoCompactTokenLimit: 900_000 }; } return { contextWindow: 200_000, autoCompactTokenLimit: 180_000 }; } function validationExecutionPolicy(profile: BackendProfile, namespace: string): ExecutionPolicy { const spec = requiredSpec(profile); return { sandbox: "workspace-write", approval: "never", timeoutMs: 120_000, network: "enabled", secretScope: { allowCredentialEcho: false, providerCredentials: [{ profile, secretRef: { namespace, name: spec.defaultSecretName, keys: [...spec.requiredSecretKeys] } }], }, }; } function runnerDefaultsForValidation(options: ProviderProfileValidationOptions, namespace: string): RunnerJobDefaults { const bootRepoUrl = optionalString(options.runnerJobDefaults?.bootRepoUrl ?? process.env.AGENTRUN_BOOT_REPO_URL); return { namespace, managerUrl: options.runnerJobDefaults?.managerUrl ?? process.env.AGENTRUN_INTERNAL_MGR_URL ?? `http://agentrun-mgr.${namespace}.svc.cluster.local:8080`, image: options.runnerJobDefaults?.image ?? process.env.AGENTRUN_RUNNER_IMAGE ?? "", ...(bootRepoUrl ? { bootRepoUrl } : {}), sourceCommit: options.runnerJobDefaults?.sourceCommit ?? options.sourceCommit, serviceAccountName: options.runnerJobDefaults?.serviceAccountName ?? process.env.AGENTRUN_RUNNER_SERVICE_ACCOUNT ?? "agentrun-v01-runner", kubectlCommand: options.runnerJobDefaults?.kubectlCommand ?? options.kubectlCommand ?? "kubectl", }; } function validationResponse(input: { profile: BackendProfile; validationId: string; runId: string; commandId: string; runnerJob: JsonRecord; status: string }): JsonRecord { const jobIdentity = asOptionalRecord(input.runnerJob.jobIdentity); const jobName = typeof jobIdentity?.name === "string" ? jobIdentity.name : typeof input.runnerJob.jobName === "string" ? input.runnerJob.jobName : null; return { action: "provider-profile-validation-started", validationId: input.validationId, profile: input.profile, runId: input.runId, commandId: input.commandId, jobName, status: input.status, pollUrl: `/api/v1/provider-profiles/${encodeURIComponent(input.profile)}/validations/${encodeURIComponent(input.validationId)}`, runnerJob: input.runnerJob, valuesPrinted: false, }; } function validationStatus(result: JsonRecord): string { const status = typeof result.status === "string" ? result.status : null; const terminalStatus = typeof result.terminalStatus === "string" ? result.terminalStatus : null; if (terminalStatus === "completed" || status === "completed") return "completed"; if (terminalStatus === "cancelled" || status === "cancelled") return "cancelled"; if (terminalStatus === "failed" || terminalStatus === "blocked" || status === "failed" || status === "blocked") return "failed"; return "running"; } function secretRefSummary(profile: BackendProfile, namespace: string): JsonRecord { const spec = requiredSpec(profile); return { namespace, name: spec.defaultSecretName, keys: [...spec.requiredSecretKeys], valuesPrinted: false }; } function requiredSpec(profile: BackendProfile) { const spec = backendProfileSpec(profile); if (!spec) throw new AgentRunError("schema-invalid", `backendProfile ${profile} must be a lowercase slug`, { httpStatus: 400 }); return spec; } function profileNamespace(options: ProviderProfileOptions): string { return options.namespace ?? process.env.AGENTRUN_RUNTIME_NAMESPACE ?? defaultNamespace; } async function kubectlGetSecret(name: string, namespace: string, kubectlCommand: string): Promise { const result = await runKubectl(kubectlCommand, ["get", "secret", name, "-n", namespace, "-o", "json"]); if (result.code !== 0) { const failureText = `${result.stderr}\n${result.stdout}`; if (/notfound|not found|not-found/iu.test(failureText)) return null; throw new AgentRunError("infra-failed", `kubectl get secret ${namespace}/${name} failed with code ${result.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(result.stderr.slice(-2000)), stdout: redactText(result.stdout.slice(-1000)) }) }); } return parseKubectlObject(result.stdout, "secret"); } async function kubectlListSecrets(namespace: string, kubectlCommand: string): Promise { 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 { const name = stringPath(manifest, ["metadata", "name"]) ?? ""; const namespace = stringPath(manifest, ["metadata", "namespace"]) ?? defaultNamespace; const stdin = `${JSON.stringify(manifest)}\n`; const replace = await runKubectl(kubectlCommand, ["replace", "-f", "-", "-o", "json"], stdin); if (replace.code === 0) return await kubectlRemoveLastAppliedAnnotation(kubectlCommand, name, namespace); if (isKubectlNotFoundFailure(replace)) { const created = await runKubectl(kubectlCommand, ["create", "-f", "-", "-o", "json"], stdin); if (created.code === 0) return parseKubectlObject(created.stdout, "provider profile secret create", { redactSecretData: true }); throw new AgentRunError("infra-failed", `kubectl create provider profile secret ${namespace}/${name} failed with code ${created.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(created.stderr.slice(-2000)), stdout: redactText(created.stdout.slice(-1000)) }) }); } throw new AgentRunError("infra-failed", `kubectl replace provider profile secret ${namespace}/${name} failed with code ${replace.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(replace.stderr.slice(-2000)), stdout: redactText(replace.stdout.slice(-1000)) }) }); } async function kubectlDeleteSecret(name: string, namespace: string, kubectlCommand: string): Promise { const result = await runKubectl(kubectlCommand, ["delete", "secret", name, "-n", namespace, "--ignore-not-found=true"]); if (result.code !== 0) { throw new AgentRunError("infra-failed", `kubectl delete provider profile secret ${namespace}/${name} failed with code ${result.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(result.stderr.slice(-2000)), stdout: redactText(result.stdout.slice(-1000)) }) }); } } function isKubectlNotFoundFailure(result: { stdout: string; stderr: string }): boolean { return /notfound|not found|not-found/iu.test(`${result.stderr}\n${result.stdout}`); } async function kubectlRemoveLastAppliedAnnotation(kubectlCommand: string, name: string, namespace: string): Promise { const patch = { metadata: { annotations: { [kubectlLastAppliedAnnotation]: null } } }; const result = await runKubectl(kubectlCommand, ["patch", "secret", name, "-n", namespace, "--type", "merge", "-p", JSON.stringify(patch), "-o", "json"]); if (result.code !== 0) { throw new AgentRunError("infra-failed", `kubectl remove provider profile secret last-applied annotation ${namespace}/${name} failed with code ${result.code}`, { httpStatus: 502, details: redactJson({ stderr: redactText(result.stderr.slice(-2000)), stdout: redactText(result.stdout.slice(-1000)) }) }); } return parseKubectlObject(result.stdout, "provider profile secret annotation cleanup", { redactSecretData: true }); } async function runKubectl(kubectlCommand: string, args: string[], stdin?: string): Promise<{ code: number | null; signal: NodeJS.Signals | null; stdout: string; stderr: string }> { const child = spawn(kubectlCommand, args, { stdio: ["pipe", "pipe", "pipe"] }); let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { stdout += String(chunk); }); child.stderr.on("data", (chunk) => { stderr += String(chunk); }); child.stdin.end(stdin ?? ""); const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => { child.on("error", reject); child.on("close", (code, signal) => resolve({ code, signal })); }).catch((error: unknown) => { throw new AgentRunError("infra-failed", `failed to start kubectl: ${error instanceof Error ? error.message : String(error)}`, { httpStatus: 503 }); }); return { ...result, stdout, stderr }; } function parseKubectlObject(stdout: string, label: string, options: { redactSecretData?: boolean } = {}): JsonRecord { try { const parsed = JSON.parse(stdout) as unknown; if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return options.redactSecretData ? redactSecretObject(parsed as JsonRecord) : parsed as JsonRecord; } catch (error) { throw new AgentRunError("infra-failed", `kubectl returned invalid JSON for ${label}: ${error instanceof Error ? error.message : String(error)}`, { httpStatus: 502, details: { stdoutPreview: label.includes("secret") ? "REDACTED" : redactText(stdout.slice(0, 1000)) } }); } throw new AgentRunError("infra-failed", `kubectl returned non-object JSON for ${label}`, { httpStatus: 502 }); } function redactSecretObject(object: JsonRecord): JsonRecord { const copy = JSON.parse(JSON.stringify(object)) as JsonRecord; if (copy.data) copy.data = "REDACTED"; if (copy.stringData) copy.stringData = "REDACTED"; return copy; } function hasRequiredKeys(data: JsonRecord | null, keys: readonly string[]): boolean { return keys.every((key) => typeof data?.[key] === "string" && String(data[key]).length > 0); } function hashDataKey(data: JsonRecord | null, key: string): string | null { const value = data?.[key]; if (typeof value !== "string" || value.length === 0) return null; try { return shortHash(Buffer.from(value, "base64").toString("utf8")); } catch { return shortHash(value); } } function base64Data(value: string): string { return Buffer.from(value, "utf8").toString("base64"); } function dataKey(data: JsonRecord | null, key: string): string | null { const value = data?.[key]; return typeof value === "string" && value.length > 0 ? value : null; } function shortHash(value: string): string { return createHash("sha256").update(value).digest("hex").slice(0, 12); } function stringField(record: JsonRecord, key: string): string { const value = record[key]; if (typeof value !== "string" || value.trim().length === 0) throw new AgentRunError("schema-invalid", `${key} is required`, { httpStatus: 400 }); return value.trim(); } function optionalString(value: unknown): string | undefined { return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined; } function objectPath(record: JsonRecord, path: string[]): JsonValue | null { let current: unknown = record; for (const key of path) { if (typeof current !== "object" || current === null || Array.isArray(current)) return null; current = (current as JsonRecord)[key]; } return current as JsonValue; } function stringPath(record: JsonRecord | null | undefined, path: string[]): string | null { if (!record) return null; const value = objectPath(record, path); return typeof value === "string" ? value : null; } function asOptionalRecord(value: unknown): JsonRecord | null { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as JsonRecord : null; } function delegatedBySummary(value: unknown): JsonRecord | null { if (value === undefined || value === null) return null; const record = asRecord(value, "delegatedBy"); const system = optionalString(record.system) ?? "operator-cli"; return { system, userId: optionalString(record.userId) ?? null, username: optionalString(record.username) ?? null, requestId: optionalString(record.requestId) ?? null, valuesPrinted: false, }; } function tomlString(value: string): string { return JSON.stringify(value); }