From 690cd394fc68aa5842d1ac07a690b5a4d0e7ee06 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jul 2026 13:57:35 +0200 Subject: [PATCH] provider profiles validation accepts model --- src/mgr/provider-profiles.ts | 19 ++++++++++++++++--- .../cases/45-provider-profile-management.ts | 6 +++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/mgr/provider-profiles.ts b/src/mgr/provider-profiles.ts index 108d18a..c2f9e27 100644 --- a/src/mgr/provider-profiles.ts +++ b/src/mgr/provider-profiles.ts @@ -269,6 +269,7 @@ export async function validateProviderProfile(profileValue: string, body: unknow 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 model = optionalModel(record.model); const run = await options.store.createRun({ tenantId: "hwlab", projectId: "pikasTech/HWLAB", @@ -280,7 +281,7 @@ export async function validateProviderProfile(profileValue: string, body: unknow 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 command = await options.store.createCommand(run.id, { type: "turn", payload: { prompt, ...(model ? { model } : {}) }, idempotencyKey: validationId }); const runnerJob = await createKubernetesRunnerJob({ store: options.store, runId: run.id, @@ -291,7 +292,7 @@ export async function validateProviderProfile(profileValue: string, body: unknow }, defaults: runnerDefaults, }); - return validationResponse({ profile, validationId, runId: run.id, commandId: command.id, runnerJob, status: "running" }); + return validationResponse({ profile, validationId, runId: run.id, commandId: command.id, runnerJob, status: "running", model }); } export async function getProviderProfileValidation(profileValue: string, validationId: string, options: { store: AgentRunStore }): Promise { @@ -686,13 +687,14 @@ function runnerDefaultsForValidation(options: ProviderProfileValidationOptions, }; } -function validationResponse(input: { profile: BackendProfile; validationId: string; runId: string; commandId: string; runnerJob: JsonRecord; status: string }): JsonRecord { +function validationResponse(input: { profile: BackendProfile; validationId: string; runId: string; commandId: string; runnerJob: JsonRecord; status: string; model?: string | null }): 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, + model: input.model ?? null, runId: input.runId, commandId: input.commandId, jobName, @@ -703,6 +705,17 @@ function validationResponse(input: { profile: BackendProfile; validationId: stri }; } +function optionalModel(value: unknown): string | null { + if (value === undefined || value === null || value === "") return null; + if (typeof value !== "string") throw new AgentRunError("schema-invalid", "provider profile validation model must be a string", { httpStatus: 400 }); + const model = value.trim(); + if (model.length === 0) return null; + if (model.length > 128 || !/^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,127}$/u.test(model)) { + throw new AgentRunError("schema-invalid", "provider profile validation model must be a safe model id", { httpStatus: 400 }); + } + return model; +} + function validationStatus(result: JsonRecord): string { const status = typeof result.status === "string" ? result.status : null; const terminalStatus = typeof result.terminalStatus === "string" ? result.terminalStatus : null; diff --git a/src/selftest/cases/45-provider-profile-management.ts b/src/selftest/cases/45-provider-profile-management.ts index 6ea2b3f..ecd2d05 100644 --- a/src/selftest/cases/45-provider-profile-management.ts +++ b/src/selftest/cases/45-provider-profile-management.ts @@ -343,13 +343,17 @@ process.exit(1); (error) => error instanceof Error && error.message.includes("not hyueapi.com"), ); - const validation = await client.post("/api/v1/provider-profiles/deepseek/validate", {}) as JsonRecord; + const validation = await client.post("/api/v1/provider-profiles/deepseek/validate", { model: "deepseek-chat", prompt: "只回复 AGENTRUN_PROVIDER_PROFILE_OK_DEEPSEEK" }) as JsonRecord; assert.equal(validation.profile, "deepseek"); + assert.equal(validation.model, "deepseek-chat"); assert.equal(validation.status, "running"); assert.equal(typeof validation.validationId, "string"); const validationId = String(validation.validationId); const validationRun = await store.getRun(String(validation.runId)) as JsonRecord; + const validationCommand = await store.getCommand(String(validation.commandId)) as JsonRecord; assert.equal((validationRun.workspaceRef as JsonRecord).path, "/home/agentrun/agentrun-source"); + assert.equal((validationCommand.payload as JsonRecord).model, "deepseek-chat"); + assert.equal((validationCommand.payload as JsonRecord).prompt, "只回复 AGENTRUN_PROVIDER_PROFILE_OK_DEEPSEEK"); const jobManifest = JSON.parse(await readFile(createdJobPath, "utf8")) as JsonRecord; assert.equal(JSON.stringify(jobManifest).includes("agentrun-v01-provider-deepseek"), true); assert.equal(JSON.stringify(jobManifest).includes(secretText), false);