mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(gateway): avoid resolving auth during models list
This commit is contained in:
@@ -7,10 +7,15 @@ import {
|
|||||||
resolveAgentWorkspaceDir,
|
resolveAgentWorkspaceDir,
|
||||||
resolveDefaultAgentId,
|
resolveDefaultAgentId,
|
||||||
} from "../../agents/agent-scope.js";
|
} from "../../agents/agent-scope.js";
|
||||||
import { ensureAuthProfileStoreWithoutExternalProfiles } from "../../agents/auth-profiles.js";
|
import {
|
||||||
|
ensureAuthProfileStoreWithoutExternalProfiles,
|
||||||
|
resolveAuthProfileOrder,
|
||||||
|
type AuthProfileCredential,
|
||||||
|
type AuthProfileStore,
|
||||||
|
} from "../../agents/auth-profiles.js";
|
||||||
import { DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
import { DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
||||||
import { NON_ENV_SECRETREF_MARKER } from "../../agents/model-auth-markers.js";
|
import { NON_ENV_SECRETREF_MARKER } from "../../agents/model-auth-markers.js";
|
||||||
import { hasAvailableAuthForProvider } from "../../agents/model-auth.js";
|
import { hasRuntimeAvailableProviderAuth } from "../../agents/model-auth.js";
|
||||||
import {
|
import {
|
||||||
loadModelCatalogForBrowse,
|
loadModelCatalogForBrowse,
|
||||||
type ModelCatalogBrowseView,
|
type ModelCatalogBrowseView,
|
||||||
@@ -30,6 +35,7 @@ type ModelsListView = ModelCatalogBrowseView;
|
|||||||
type ModelsListEntry = ModelCatalogEntry & { available?: boolean };
|
type ModelsListEntry = ModelCatalogEntry & { available?: boolean };
|
||||||
|
|
||||||
let loggedSlowModelsListCatalog = false;
|
let loggedSlowModelsListCatalog = false;
|
||||||
|
const OAUTH_REFRESH_MARGIN_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
// Unknown views are rejected by protocol validation first; this helper keeps the
|
// Unknown views are rejected by protocol validation first; this helper keeps the
|
||||||
// handler default explicit for older clients that omit the field.
|
// handler default explicit for older clients that omit the field.
|
||||||
@@ -74,6 +80,72 @@ function createInFlightProviderAuthChecker(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasLiteralSecret(value: unknown): value is string {
|
||||||
|
return typeof value === "string" && value.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function profileModeAllowedForModel(
|
||||||
|
provider: string,
|
||||||
|
modelApi: string | undefined,
|
||||||
|
mode: AuthProfileCredential["type"],
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
normalizeProviderId(provider) !== "openai" ||
|
||||||
|
modelApi === undefined ||
|
||||||
|
modelApi === "openai-chatgpt-responses" ||
|
||||||
|
mode === "api_key"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function profileHasReadOnlyAvailableAuth(params: {
|
||||||
|
credential: AuthProfileCredential;
|
||||||
|
provider: string;
|
||||||
|
modelApi?: string;
|
||||||
|
now: number;
|
||||||
|
}): boolean {
|
||||||
|
if (!profileModeAllowedForModel(params.provider, params.modelApi, params.credential.type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (params.credential.type === "api_key") {
|
||||||
|
return hasLiteralSecret(params.credential.key);
|
||||||
|
}
|
||||||
|
if (params.credential.type === "token") {
|
||||||
|
return (
|
||||||
|
hasLiteralSecret(params.credential.token) &&
|
||||||
|
(params.credential.expires === undefined || params.credential.expires > params.now)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
hasLiteralSecret(params.credential.access) &&
|
||||||
|
params.credential.expires > params.now + OAUTH_REFRESH_MARGIN_MS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasReadOnlyAvailableProfileAuth(params: {
|
||||||
|
provider: string;
|
||||||
|
modelApi?: string;
|
||||||
|
cfg: OpenClawConfig;
|
||||||
|
store: AuthProfileStore;
|
||||||
|
}): boolean {
|
||||||
|
const now = Date.now();
|
||||||
|
return resolveAuthProfileOrder({
|
||||||
|
cfg: params.cfg,
|
||||||
|
store: params.store,
|
||||||
|
provider: params.provider,
|
||||||
|
}).some((profileId) => {
|
||||||
|
const credential = params.store.profiles[profileId];
|
||||||
|
return (
|
||||||
|
credential !== undefined &&
|
||||||
|
profileHasReadOnlyAvailableAuth({
|
||||||
|
credential,
|
||||||
|
provider: params.provider,
|
||||||
|
modelApi: params.modelApi,
|
||||||
|
now,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function createModelsListProviderAuthChecker(params: {
|
function createModelsListProviderAuthChecker(params: {
|
||||||
cfg: OpenClawConfig;
|
cfg: OpenClawConfig;
|
||||||
agentId: string;
|
agentId: string;
|
||||||
@@ -82,16 +154,24 @@ function createModelsListProviderAuthChecker(params: {
|
|||||||
const agentDir = resolveAgentDir(params.cfg, params.agentId);
|
const agentDir = resolveAgentDir(params.cfg, params.agentId);
|
||||||
const store = ensureAuthProfileStoreWithoutExternalProfiles(agentDir, {
|
const store = ensureAuthProfileStoreWithoutExternalProfiles(agentDir, {
|
||||||
allowKeychainPrompt: false,
|
allowKeychainPrompt: false,
|
||||||
|
readOnly: true,
|
||||||
|
syncExternalCli: false,
|
||||||
});
|
});
|
||||||
return createInFlightProviderAuthChecker((provider, modelApi) =>
|
return createInFlightProviderAuthChecker(
|
||||||
hasAvailableAuthForProvider({
|
(provider, modelApi) =>
|
||||||
provider,
|
hasRuntimeAvailableProviderAuth({
|
||||||
modelApi,
|
provider,
|
||||||
cfg: params.cfg,
|
modelApi,
|
||||||
agentDir,
|
cfg: params.cfg,
|
||||||
workspaceDir: params.workspaceDir,
|
workspaceDir: params.workspaceDir,
|
||||||
store,
|
allowPluginSyntheticAuth: false,
|
||||||
}),
|
}) ||
|
||||||
|
hasReadOnlyAvailableProfileAuth({
|
||||||
|
provider,
|
||||||
|
modelApi,
|
||||||
|
cfg: params.cfg,
|
||||||
|
store,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ describe("models.list", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not mark catalog rows available from expired provider profiles", async () => {
|
it("does not mark catalog rows available from expired OAuth profiles", async () => {
|
||||||
await withOpenClawTestState(
|
await withOpenClawTestState(
|
||||||
{
|
{
|
||||||
layout: "state-only",
|
layout: "state-only",
|
||||||
@@ -345,9 +345,10 @@ describe("models.list", () => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
profiles: {
|
profiles: {
|
||||||
"demo-provider:expired": {
|
"demo-provider:expired": {
|
||||||
type: "token",
|
type: "oauth",
|
||||||
provider: "demo-provider",
|
provider: "demo-provider",
|
||||||
token: "expired-token",
|
access: "expired-access",
|
||||||
|
refresh: "refresh-token",
|
||||||
expires: Date.now() - 60_000,
|
expires: Date.now() - 60_000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user