diff --git a/server/src/__tests__/company-model-service-settings-guidance.test.ts b/server/src/__tests__/company-model-service-settings-guidance.test.ts new file mode 100644 index 00000000000..20c7ce7fc03 --- /dev/null +++ b/server/src/__tests__/company-model-service-settings-guidance.test.ts @@ -0,0 +1,21 @@ +import { expect, it } from "vitest"; +import { searchRunnerApi } from "../services/native-runtime/runner-api-catalog.js"; + +// An agent asked to turn on a company model service cannot do it and must link the page that holds the control. +// The general /company/settings page has none of these sections, so the catalog names the Connections page. +it.each([ + ["fast response feature", "/api/companies/{companyId}/fast-response", "Fast response"], + ["company settings page fast responses web UI", "/api/companies/{companyId}/fast-response", "Fast response"], + ["decision model settings", "/api/companies/{companyId}/decision-model", "Decision model"], + ["prompt injection prevention settings", "/api/companies/{companyId}/prompt-injection-prevention", "Prompt Injection Prevention"], +])("search %j returns the settings page that holds the %s control", (query, path, section) => { + const operation = searchRunnerApi({ query, limit: 10 }).results.find(row => row.path === path && row.method === "PUT"); + expect(operation, query).toBeDefined(); + expect(operation!.description).toContain(`${section} section of the company-prefixed /company/settings/connections page`); + expect(operation!.description).toContain("//company/settings/connections"); +}); + +it("names the experiment that hides the Fast response section", () => { + const operation = searchRunnerApi({ query: "fast response", limit: 10 }).results.find(row => row.operationId === "GET /api/companies/{companyId}/fast-response"); + expect(operation?.description).toContain("//company/settings/instance/experimental"); +}); diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index a6ca6b275e3..08bea0a8c19 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -11515,6 +11515,11 @@ registerCurrentRoute({ // --- Tool access ------------------------------------------------------------- +/** Where a person manages a company model service; agents relay this page instead of guessing a settings route. */ +function companyModelServiceSettingsPage(section: string, condition = "") { + return `People who manage connections configure, enable and test this in the ${section} section of the company-prefixed /company/settings/connections page; link it as a Markdown link to //company/settings/connections, not the general /company/settings page.${condition} Agent credentials cannot change or test it.`; +} + for (const [method, suffix, summary] of [ ["get", "", "Get prompt injection settings and eligible shared OpenRouter connections; non-managers receive no settings or choices"], ["put", "", "Configure opt-in prompt injection model checks as a board connection manager"], @@ -11522,7 +11527,7 @@ for (const [method, suffix, summary] of [ ["post", "/test", "Classify two fixed billed samples using saved settings as a board connection manager; fallback is not a passing model test"], ] as const) registerCurrentRoute({ method, path: `/api/companies/{companyId}/prompt-injection-prevention${suffix}`, - tags: ["prompt-injection-prevention"], summary, + tags: ["prompt-injection-prevention"], summary, description: companyModelServiceSettingsPage("Prompt Injection Prevention"), ...(method === "put" ? { body: updatePromptInjectionPreventionSchema } : {}), ...(method === "post" ? { body: z.object({}).strict() } : {}), ...(suffix === "/models" ? { query: z.object({ connectionId: z.string().uuid() }) } : {}), @@ -11537,6 +11542,7 @@ for (const [method, suffix, summary] of [ ["post", "/test", "Generate a fixed billed fast response sample"], ["get", "/history", "List fast response accounting and publication outcomes"], ] as const) registerCurrentRoute({ method, path: `/api/companies/{companyId}/fast-response${suffix}`, tags: ["fast-responses"], summary, + description: companyModelServiceSettingsPage("Fast response", " The section is shown only while an instance admin has fast responses turned on in //company/settings/instance/experimental."), ...(method === "put" ? { body: updateFastResponseSchema } : {}), responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 422: r.unprocessable } }); const observabilitySignalReceiptSchema = z.object({ @@ -11576,26 +11582,31 @@ registerCurrentRoute({ registerCurrentRoute({ method: "get", path: "/api/companies/{companyId}/decision-model", tags: ["decision-models"], + description: companyModelServiceSettingsPage("Decision model"), summary: "Get decision settings, compatible connections, and manager capability", responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, }); registerCurrentRoute({ method: "put", path: "/api/companies/{companyId}/decision-model", tags: ["decision-models"], + description: companyModelServiceSettingsPage("Decision model"), summary: "Configure the company decision model as a connection manager", body: updateDecisionModelSchema, responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 422: r.unprocessable }, }); registerCurrentRoute({ method: "get", path: "/api/companies/{companyId}/decision-model/availability", tags: ["decision-models"], + description: companyModelServiceSettingsPage("Decision model"), summary: "Check local decision configuration and caller authorization without contacting a provider", responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, }); registerCurrentRoute({ method: "post", path: "/api/companies/{companyId}/decision-model/test", tags: ["decision-models"], + description: companyModelServiceSettingsPage("Decision model"), summary: "Run the fixed billed three-question setup test as a connection manager", responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, }); registerCurrentRoute({ method: "get", path: "/api/companies/{companyId}/decision-model/history", tags: ["decision-models"], + description: companyModelServiceSettingsPage("Decision model"), summary: "List decision metadata and charges with authorized task links", query: costReportQuerySchema.extend({ limit: z.coerce.number().int().min(1).max(500).optional() }), responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden },