mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-14 04:06:03 +00:00
fix(gateway): require resolved auth for model availability
This commit is contained in:
@@ -2,12 +2,15 @@
|
||||
// strips runtime-only provider params before sending the browse API payload.
|
||||
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
|
||||
import {
|
||||
resolveAgentDir,
|
||||
resolveAgentEffectiveModelPrimary,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
} from "../../agents/agent-scope.js";
|
||||
import { ensureAuthProfileStoreWithoutExternalProfiles } from "../../agents/auth-profiles.js";
|
||||
import { DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
||||
import { NON_ENV_SECRETREF_MARKER } from "../../agents/model-auth-markers.js";
|
||||
import { hasAvailableAuthForProvider } from "../../agents/model-auth.js";
|
||||
import {
|
||||
loadModelCatalogForBrowse,
|
||||
type ModelCatalogBrowseView,
|
||||
@@ -18,7 +21,6 @@ import {
|
||||
resolveVisibleModelCatalog,
|
||||
} from "../../agents/model-catalog-visibility.js";
|
||||
import type { ModelCatalogEntry } from "../../agents/model-catalog.types.js";
|
||||
import { createProviderAuthChecker } from "../../agents/model-provider-auth.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { isSecretRef } from "../../config/types.secrets.js";
|
||||
@@ -72,6 +74,27 @@ function createInFlightProviderAuthChecker(
|
||||
};
|
||||
}
|
||||
|
||||
function createModelsListProviderAuthChecker(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
workspaceDir: string;
|
||||
}): ProviderAuthChecker {
|
||||
const agentDir = resolveAgentDir(params.cfg, params.agentId);
|
||||
const store = ensureAuthProfileStoreWithoutExternalProfiles(agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
return createInFlightProviderAuthChecker((provider, modelApi) =>
|
||||
hasAvailableAuthForProvider({
|
||||
provider,
|
||||
modelApi,
|
||||
cfg: params.cfg,
|
||||
agentDir,
|
||||
workspaceDir: params.workspaceDir,
|
||||
store,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function buildPublicModelsListEntry(params: {
|
||||
entry: ModelCatalogEntry;
|
||||
cfg: OpenClawConfig;
|
||||
@@ -96,21 +119,13 @@ async function buildPublicModelsListEntries(params: {
|
||||
agentId: string;
|
||||
workspaceDir: string;
|
||||
}): Promise<ModelsListEntry[]> {
|
||||
const inFlightProviderAuthChecker = createInFlightProviderAuthChecker(
|
||||
createProviderAuthChecker({
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
workspaceDir: params.workspaceDir,
|
||||
allowPluginSyntheticAuth: false,
|
||||
discoverExternalCliAuth: false,
|
||||
}),
|
||||
);
|
||||
const providerAuthChecker = createModelsListProviderAuthChecker(params);
|
||||
return await Promise.all(
|
||||
params.catalog.map((entry) =>
|
||||
buildPublicModelsListEntry({
|
||||
entry,
|
||||
cfg: params.cfg,
|
||||
providerAuthChecker: inFlightProviderAuthChecker,
|
||||
providerAuthChecker,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { ErrorCodes } from "../../../packages/gateway-protocol/src/index.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { withOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
|
||||
import { createDeferred } from "../test-helpers.deferred.js";
|
||||
import { expectGatewayErrorResponse } from "./gateway-response.test-helpers.js";
|
||||
import { modelsHandlers } from "./models.js";
|
||||
@@ -332,6 +333,53 @@ describe("models.list", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not mark catalog rows available from expired provider profiles", async () => {
|
||||
await withOpenClawTestState(
|
||||
{
|
||||
layout: "state-only",
|
||||
prefix: "openclaw-models-list-expired-profile-",
|
||||
agentEnv: "main",
|
||||
},
|
||||
async (state) => {
|
||||
await state.writeAuthProfiles({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"demo-provider:expired": {
|
||||
type: "token",
|
||||
provider: "demo-provider",
|
||||
token: "expired-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-expired-profile",
|
||||
});
|
||||
await request;
|
||||
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
true,
|
||||
{
|
||||
models: [
|
||||
{
|
||||
id: "demo-model",
|
||||
name: "Demo Model",
|
||||
provider: "demo-provider",
|
||||
available: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves catalog load errors before the timeout fallback wins", async () => {
|
||||
const { request, respond } = requestModelsList({
|
||||
view: "configured",
|
||||
|
||||
Reference in New Issue
Block a user