From 7c885528ba25f5e7ec008045a9baf108bac44594 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Fri, 5 Jun 2026 16:15:12 +0530 Subject: [PATCH] fix(gateway): recognize env profile refs in model availability --- .../server-methods/models-list-result.ts | 11 +++- src/gateway/server-methods/models.test.ts | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/gateway/server-methods/models-list-result.ts b/src/gateway/server-methods/models-list-result.ts index f0f1da77229b..624257767223 100644 --- a/src/gateway/server-methods/models-list-result.ts +++ b/src/gateway/server-methods/models-list-result.ts @@ -84,6 +84,10 @@ function hasLiteralSecret(value: unknown): value is string { return typeof value === "string" && value.trim().length > 0; } +function hasAvailableEnvSecretRef(value: unknown): boolean { + return isSecretRef(value) && value.source === "env" && hasLiteralSecret(process.env[value.id]); +} + function profileModeAllowedForModel( provider: string, modelApi: string | undefined, @@ -107,11 +111,14 @@ function profileHasReadOnlyAvailableAuth(params: { return false; } if (params.credential.type === "api_key") { - return hasLiteralSecret(params.credential.key); + return ( + hasLiteralSecret(params.credential.key) || hasAvailableEnvSecretRef(params.credential.keyRef) + ); } if (params.credential.type === "token") { return ( - hasLiteralSecret(params.credential.token) && + (hasLiteralSecret(params.credential.token) || + hasAvailableEnvSecretRef(params.credential.tokenRef)) && (params.credential.expires === undefined || params.credential.expires > params.now) ); } diff --git a/src/gateway/server-methods/models.test.ts b/src/gateway/server-methods/models.test.ts index 178cb91f14d8..3b77c2c2bcb7 100644 --- a/src/gateway/server-methods/models.test.ts +++ b/src/gateway/server-methods/models.test.ts @@ -381,6 +381,60 @@ describe("models.list", () => { ); }); + it("marks env SecretRef-backed auth profiles available", async () => { + await withOpenClawTestState( + { + layout: "state-only", + prefix: "openclaw-models-list-env-profile-", + agentEnv: "main", + env: { + DEMO_PROVIDER_TOKEN: "test-token", + }, + }, + async (state) => { + await state.writeAuthProfiles({ + version: 1, + profiles: { + "demo-provider:env": { + type: "token", + provider: "demo-provider", + tokenRef: { + source: "env", + provider: "default", + id: "DEMO_PROVIDER_TOKEN", + }, + expires: Date.now() + 60_000, + }, + }, + }); + + const { request, respond } = requestModelsList({ + view: "all", + loadGatewayModelCatalog: vi.fn(() => + Promise.resolve([{ id: "demo-model", name: "Demo Model", provider: "demo-provider" }]), + ), + reqId: "req-models-list-env-profile", + }); + await request; + + expect(respond).toHaveBeenCalledWith( + true, + { + models: [ + { + id: "demo-model", + name: "Demo Model", + provider: "demo-provider", + available: true, + }, + ], + }, + undefined, + ); + }, + ); + }); + it("preserves catalog load errors before the timeout fallback wins", async () => { const { request, respond } = requestModelsList({ view: "configured",