mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(agents): canonicalize auth profile aliases (#119505)
Persist inherited per-agent order references, collapse equivalent auth-provider state keys onto the canonical provider, and clear every equivalent persisted key. Make auth-order validation and display alias-aware while leaving separate OAuth runtime-cache behavior unchanged. Fixes #119233 Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveOAuthDir } from "../../config/paths.js";
|
||||
import {
|
||||
closeOpenClawAgentDatabasesForTest,
|
||||
@@ -19,9 +19,12 @@ import { testing as externalAuthTesting } from "./external-auth.test-support.js"
|
||||
import { loadPersistedAuthProfileStore } from "./persisted.js";
|
||||
import {
|
||||
clearLastGoodProfileWithLock,
|
||||
markAuthProfileSuccess,
|
||||
promoteAuthProfileInOrder,
|
||||
removeAuthProfilesAcrossOwnerStores,
|
||||
removeAuthProfilesWithLock,
|
||||
removeProviderAuthProfilesWithLock,
|
||||
setAuthProfileOrder,
|
||||
upsertAuthProfileWithLock,
|
||||
} from "./profiles.js";
|
||||
import {
|
||||
@@ -46,6 +49,19 @@ import {
|
||||
import { testing as storeTesting } from "./store.test-support.js";
|
||||
import type { AuthProfileStore, RuntimeAuthProfileStore } from "./types.js";
|
||||
|
||||
vi.mock("../provider-auth-aliases.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../provider-auth-aliases.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveProviderIdForAuth: (...args: Parameters<typeof actual.resolveProviderIdForAuth>) => {
|
||||
const provider = args[0].trim().toLowerCase();
|
||||
return provider === "gmi-cloud" || provider === "gmicloud"
|
||||
? "gmi"
|
||||
: actual.resolveProviderIdForAuth(...args);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type ExpectedOAuthCredentialFields = {
|
||||
provider: string;
|
||||
access?: string;
|
||||
@@ -1398,4 +1414,184 @@ describe("promoteAuthProfileInOrder", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setAuthProfileOrder", () => {
|
||||
it("canonicalizes every alias-equivalent provider state mutation", async () => {
|
||||
await withAuthProfileTestState("openclaw-auth-alias-state-", async ({ agentDir }) => {
|
||||
fs.mkdirSync(agentDir, { recursive: true });
|
||||
const primary = "gmi:primary";
|
||||
const secondary = "gmi:secondary";
|
||||
const profiles = {
|
||||
[primary]: { type: "api_key" as const, provider: "gmi", key: "primary" },
|
||||
[secondary]: { type: "api_key" as const, provider: "gmi", key: "secondary" },
|
||||
"openai:other": { type: "api_key" as const, provider: "openai", key: "other" },
|
||||
};
|
||||
const seeded = (): AuthProfileStore => ({
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles,
|
||||
order: {
|
||||
"gmi-cloud": [primary],
|
||||
openai: ["openai:other"],
|
||||
gmicloud: [secondary],
|
||||
},
|
||||
lastGood: { gmicloud: secondary, openai: "openai:other", "gmi-cloud": primary },
|
||||
});
|
||||
|
||||
saveAuthProfileStore(seeded(), agentDir);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
await setAuthProfileOrder({ agentDir, provider: "gmi-cloud", order: [secondary] });
|
||||
expect(loadPersistedAuthProfileStore(agentDir)?.order).toEqual({
|
||||
openai: ["openai:other"],
|
||||
gmi: [secondary],
|
||||
});
|
||||
saveAuthProfileStore(
|
||||
{
|
||||
...seeded(),
|
||||
order: { ...seeded().order, gmi: [primary] },
|
||||
},
|
||||
agentDir,
|
||||
);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
await setAuthProfileOrder({ agentDir, provider: "gmi-cloud", order: null });
|
||||
expect(loadPersistedAuthProfileStore(agentDir)?.order).toEqual({
|
||||
openai: ["openai:other"],
|
||||
});
|
||||
|
||||
saveAuthProfileStore(
|
||||
{
|
||||
...seeded(),
|
||||
order: { ...seeded().order, "gmi-cloud": [secondary, primary] },
|
||||
},
|
||||
agentDir,
|
||||
);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
await promoteAuthProfileInOrder({ agentDir, provider: "gmi-cloud", profileId: secondary });
|
||||
expect(loadPersistedAuthProfileStore(agentDir)?.order).toEqual({
|
||||
openai: ["openai:other"],
|
||||
gmi: [secondary, primary],
|
||||
});
|
||||
|
||||
saveAuthProfileStore(seeded(), agentDir);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
await clearLastGoodProfileWithLock({ agentDir, provider: "gmi-cloud", profileId: secondary });
|
||||
expect(loadPersistedAuthProfileStore(agentDir)?.lastGood).toEqual({
|
||||
openai: "openai:other",
|
||||
});
|
||||
|
||||
saveAuthProfileStore(seeded(), agentDir);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
const runtimeStore = loadAuthProfileStoreForRuntime(agentDir);
|
||||
await markAuthProfileSuccess({
|
||||
agentDir,
|
||||
profileId: secondary,
|
||||
provider: "gmi-cloud",
|
||||
store: runtimeStore,
|
||||
});
|
||||
expect(loadPersistedAuthProfileStore(agentDir)?.lastGood).toEqual({
|
||||
openai: "openai:other",
|
||||
gmi: secondary,
|
||||
});
|
||||
|
||||
saveAuthProfileStore(seeded(), agentDir);
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
await removeProviderAuthProfilesWithLock({ agentDir, provider: "gmi-cloud" });
|
||||
expect(loadPersistedAuthProfileStore(agentDir)).toMatchObject({
|
||||
profiles: { "openai:other": expect.any(Object) },
|
||||
order: { openai: ["openai:other"] },
|
||||
lastGood: { openai: "openai:other" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves inherited main OAuth profile IDs in a secondary agent order without copying credentials", async () => {
|
||||
await withAuthProfileTestState(
|
||||
"openclaw-auth-order-set-inherited-",
|
||||
async ({ agentDirFor }) => {
|
||||
const mainAgentDir = agentDirFor("main");
|
||||
const customAgentDir = agentDirFor("custom");
|
||||
fs.mkdirSync(mainAgentDir, { recursive: true });
|
||||
fs.mkdirSync(customAgentDir, { recursive: true });
|
||||
// Main agent owns two OAuth profiles; the secondary agent inherits them
|
||||
// at runtime and has no local credential copies.
|
||||
const mainStore = (): AuthProfileStore => ({
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:profile-a": {
|
||||
type: "oauth",
|
||||
provider: "openai",
|
||||
access: "access-a",
|
||||
refresh: "refresh-a",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
"openai:profile-b": {
|
||||
type: "oauth",
|
||||
provider: "openai",
|
||||
access: "access-b",
|
||||
refresh: "refresh-b",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
},
|
||||
order: { openai: ["openai:profile-a"] },
|
||||
});
|
||||
saveAuthProfileStore(mainStore());
|
||||
|
||||
// The secondary agent selects the other inherited profile ID. Before the
|
||||
// fix, the local save pruned this ID because the secondary store does
|
||||
// not own the OAuth credential, so `order get` fell back to main's
|
||||
// profile-a (issue #119233).
|
||||
const updated = await setAuthProfileOrder({
|
||||
agentDir: customAgentDir,
|
||||
provider: "openai",
|
||||
order: ["openai:profile-b"],
|
||||
});
|
||||
|
||||
expect(updated?.order?.openai).toEqual(["openai:profile-b"]);
|
||||
// Reload from persistence: the inherited ID must survive, not be pruned.
|
||||
expect(loadPersistedAuthProfileStore(customAgentDir)?.order?.openai).toEqual([
|
||||
"openai:profile-b",
|
||||
]);
|
||||
// The runtime store for the secondary agent reflects the local override.
|
||||
expect(loadAuthProfileStoreForRuntime(customAgentDir).order?.openai).toEqual([
|
||||
"openai:profile-b",
|
||||
]);
|
||||
// The secondary agent must not gain a local copy of the inherited OAuth
|
||||
// credential — only the order reference is preserved.
|
||||
const persistedCustom = loadPersistedAuthProfileStore(customAgentDir);
|
||||
expect(persistedCustom?.profiles["openai:profile-b"]).toBeUndefined();
|
||||
expect(persistedCustom?.profiles["openai:profile-a"]).toBeUndefined();
|
||||
},
|
||||
{ clearOAuthDir: true },
|
||||
);
|
||||
});
|
||||
|
||||
it("clears a provider order without preserving any profile IDs", async () => {
|
||||
await withAuthProfileTestState(
|
||||
"openclaw-auth-order-set-clear-",
|
||||
async ({ agentDir }) => {
|
||||
fs.mkdirSync(agentDir, { recursive: true });
|
||||
saveAuthProfileStore({
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:local": {
|
||||
type: "api_key",
|
||||
provider: "openai",
|
||||
key: "sk-local",
|
||||
},
|
||||
},
|
||||
order: { openai: ["openai:local"] },
|
||||
});
|
||||
|
||||
const updated = await setAuthProfileOrder({
|
||||
agentDir,
|
||||
provider: "openai",
|
||||
order: null,
|
||||
});
|
||||
|
||||
expect(updated?.order?.openai ?? null).toBeNull();
|
||||
expect(loadPersistedAuthProfileStore(agentDir)?.order?.openai ?? null).toBeNull();
|
||||
},
|
||||
{ clearOAuthDir: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
* Updates profile order, last-good state, usage stats, and provider profile
|
||||
* records through locked or immediate store writes.
|
||||
*/
|
||||
import {
|
||||
findNormalizedProviderKey,
|
||||
normalizeProviderId,
|
||||
} from "@openclaw/model-catalog-core/provider-id";
|
||||
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
|
||||
import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { resolveProviderIdForAuth } from "../provider-auth-aliases.js";
|
||||
@@ -28,21 +25,44 @@ export { upsertAuthProfileWithLock, upsertAuthProfileWithLockOrThrow } from "./u
|
||||
|
||||
const authProfileProfilesLog = createSubsystemLogger("agent/embedded");
|
||||
|
||||
// Auth profile order/lastGood keys may be stored as aliases. Resolve through
|
||||
// auth provider normalization before updating per-provider state.
|
||||
function findProviderAuthStateKey(
|
||||
entries: Record<string, unknown> | undefined,
|
||||
providerKey: string,
|
||||
): string | undefined {
|
||||
if (!entries) {
|
||||
return undefined;
|
||||
}
|
||||
const normalizedProviderKey = resolveProviderIdForAuth(providerKey);
|
||||
return Object.keys(entries).find(
|
||||
(key) => resolveProviderIdForAuth(key) === normalizedProviderKey,
|
||||
function listProviderAuthStateEntries<T>(
|
||||
entries: Record<string, T> | undefined,
|
||||
provider: string,
|
||||
): Array<[string, T]> {
|
||||
const canonicalProvider = resolveProviderIdForAuth(provider);
|
||||
return Object.entries(entries ?? {})
|
||||
.filter(([key]) => resolveProviderIdForAuth(key) === canonicalProvider)
|
||||
.toSorted(([left], [right]) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function readProviderAuthState<T>(
|
||||
entries: Record<string, T> | undefined,
|
||||
provider: string,
|
||||
): T | undefined {
|
||||
const canonicalProvider = resolveProviderIdForAuth(provider);
|
||||
const matches = listProviderAuthStateEntries(entries, canonicalProvider);
|
||||
return (
|
||||
matches.find(([key]) => normalizeProviderId(key) === canonicalProvider)?.[1] ?? matches[0]?.[1]
|
||||
);
|
||||
}
|
||||
|
||||
function replaceProviderAuthState<T>(
|
||||
entries: Record<string, T> | undefined,
|
||||
provider: string,
|
||||
value?: T,
|
||||
): Record<string, T> | undefined {
|
||||
const canonicalProvider = resolveProviderIdForAuth(provider);
|
||||
const next = Object.fromEntries(
|
||||
Object.entries(entries ?? {}).filter(
|
||||
([key]) => resolveProviderIdForAuth(key) !== canonicalProvider,
|
||||
),
|
||||
) as Record<string, T>;
|
||||
if (value !== undefined) {
|
||||
next[canonicalProvider] = value;
|
||||
}
|
||||
return Object.keys(next).length > 0 ? next : undefined;
|
||||
}
|
||||
|
||||
// Successful auth clears transient failure/cooldown/disable state while keeping
|
||||
// unrelated metadata and updating lastUsed for round-robin ordering.
|
||||
function resetSuccessfulUsageStats(
|
||||
@@ -81,26 +101,31 @@ export async function setAuthProfileOrder(params: {
|
||||
provider: string;
|
||||
order?: string[] | null;
|
||||
}): Promise<AuthProfileStore | null> {
|
||||
const providerKey = normalizeProviderId(params.provider);
|
||||
const providerKey = resolveProviderIdForAuth(params.provider);
|
||||
const sanitized =
|
||||
params.order && Array.isArray(params.order) ? normalizeStringEntries(params.order) : [];
|
||||
const deduped = dedupeProfileIds(sanitized);
|
||||
|
||||
return await updateAuthProfileStoreWithLock({
|
||||
agentDir: params.agentDir,
|
||||
// Preserve requested IDs that the agent inherits (not owns) so the local
|
||||
// save path does not prune them from the order. Without this, a secondary
|
||||
// agent's `models auth order set --agent` accepts an inherited profile ID
|
||||
// (validated against the merged store) but drops it while persisting, so
|
||||
// `order get` falls back to the inherited main order — the CLI reports a
|
||||
// switch that never happened (issue #119233). Mirrors the adjacent
|
||||
// promoteAuthProfileInOrder preservation contract; the clear-order path
|
||||
// (deduped.length === 0) must not preserve anything.
|
||||
...(deduped.length > 0 ? { saveOptions: { preserveOrderProfileIds: deduped } } : {}),
|
||||
updater: (store) => {
|
||||
store.order = store.order ?? {};
|
||||
if (deduped.length === 0) {
|
||||
if (!store.order[providerKey]) {
|
||||
if (listProviderAuthStateEntries(store.order, providerKey).length === 0) {
|
||||
return false;
|
||||
}
|
||||
delete store.order[providerKey];
|
||||
if (Object.keys(store.order).length === 0) {
|
||||
store.order = undefined;
|
||||
}
|
||||
store.order = replaceProviderAuthState(store.order, providerKey);
|
||||
return true;
|
||||
}
|
||||
store.order[providerKey] = deduped;
|
||||
store.order = replaceProviderAuthState(store.order, providerKey, deduped);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
@@ -125,11 +150,8 @@ export async function promoteAuthProfileInOrder(params: {
|
||||
if (!profile || resolveProviderIdForAuth(profile.provider) !== providerKey) {
|
||||
return false;
|
||||
}
|
||||
const orderKey =
|
||||
findProviderAuthStateKey(store.order, providerKey) ??
|
||||
findNormalizedProviderKey(store.order, providerKey) ??
|
||||
normalizeProviderId(providerKey);
|
||||
const existing = store.order?.[orderKey];
|
||||
const matchingOrderEntries = listProviderAuthStateEntries(store.order, providerKey);
|
||||
const existing = readProviderAuthState(store.order, providerKey);
|
||||
if (!existing || existing.length === 0) {
|
||||
if (!params.createIfMissing) {
|
||||
return false;
|
||||
@@ -143,7 +165,7 @@ export async function promoteAuthProfileInOrder(params: {
|
||||
params.profileId,
|
||||
...providerProfiles.filter((profileId) => profileId !== params.profileId),
|
||||
]);
|
||||
store.order = { ...store.order, [orderKey]: next };
|
||||
store.order = replaceProviderAuthState(store.order, providerKey, next);
|
||||
return true;
|
||||
}
|
||||
const next = dedupeProfileIds([
|
||||
@@ -152,11 +174,13 @@ export async function promoteAuthProfileInOrder(params: {
|
||||
]);
|
||||
if (
|
||||
next.length === existing.length &&
|
||||
next.every((profileId, idx) => profileId === existing[idx])
|
||||
next.every((profileId, idx) => profileId === existing[idx]) &&
|
||||
matchingOrderEntries.length === 1 &&
|
||||
matchingOrderEntries[0]?.[0] === providerKey
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
store.order = { ...store.order, [orderKey]: next };
|
||||
store.order = replaceProviderAuthState(store.order, providerKey, next);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
@@ -183,7 +207,6 @@ export async function removeProviderAuthProfilesWithLock(params: {
|
||||
agentDir?: string;
|
||||
}): Promise<AuthProfileStore | null> {
|
||||
const providerKey = resolveProviderIdForAuth(params.provider);
|
||||
const storeOrderKey = normalizeProviderId(params.provider);
|
||||
return await updateAuthProfileStoreWithLock({
|
||||
agentDir: params.agentDir,
|
||||
updater: (store) => {
|
||||
@@ -199,19 +222,13 @@ export async function removeProviderAuthProfilesWithLock(params: {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (store.order?.[storeOrderKey]) {
|
||||
delete store.order[storeOrderKey];
|
||||
if (listProviderAuthStateEntries(store.order, providerKey).length > 0) {
|
||||
store.order = replaceProviderAuthState(store.order, providerKey);
|
||||
changed = true;
|
||||
if (Object.keys(store.order).length === 0) {
|
||||
store.order = undefined;
|
||||
}
|
||||
}
|
||||
if (store.lastGood?.[providerKey]) {
|
||||
delete store.lastGood[providerKey];
|
||||
if (listProviderAuthStateEntries(store.lastGood, providerKey).length > 0) {
|
||||
store.lastGood = replaceProviderAuthState(store.lastGood, providerKey);
|
||||
changed = true;
|
||||
if (Object.keys(store.lastGood).length === 0) {
|
||||
store.lastGood = undefined;
|
||||
}
|
||||
}
|
||||
if (store.usageStats && Object.keys(store.usageStats).length === 0) {
|
||||
store.usageStats = undefined;
|
||||
@@ -316,14 +333,11 @@ export async function clearLastGoodProfileWithLock(params: {
|
||||
return await updateAuthProfileStoreWithLock({
|
||||
agentDir: params.agentDir,
|
||||
updater: (store) => {
|
||||
const lastGoodKey = findProviderAuthStateKey(store.lastGood, providerKey);
|
||||
if (!lastGoodKey || store.lastGood?.[lastGoodKey] !== params.profileId) {
|
||||
const matches = listProviderAuthStateEntries(store.lastGood, providerKey);
|
||||
if (!matches.some(([, profileId]) => profileId === params.profileId)) {
|
||||
return false;
|
||||
}
|
||||
delete store.lastGood[lastGoodKey];
|
||||
if (Object.keys(store.lastGood).length === 0) {
|
||||
store.lastGood = undefined;
|
||||
}
|
||||
store.lastGood = replaceProviderAuthState(store.lastGood, providerKey);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
@@ -346,7 +360,7 @@ export async function markAuthProfileSuccess(params: {
|
||||
if (!profile || resolveProviderIdForAuth(profile.provider) !== providerKey) {
|
||||
return false;
|
||||
}
|
||||
freshStore.lastGood = { ...freshStore.lastGood, [providerKey]: profileId };
|
||||
freshStore.lastGood = replaceProviderAuthState(freshStore.lastGood, providerKey, profileId);
|
||||
updateSuccessfulUsageStatsEntry(freshStore, profileId, lastUsed);
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -91,6 +91,30 @@ describe("models auth order", () => {
|
||||
expect(runtime.logs).toContain("Auth profile order override: anthropic:b, anthropic:a");
|
||||
});
|
||||
|
||||
it("accepts alias-provider profiles and reports the canonical stored order", async () => {
|
||||
mocks.ensureAuthProfileStore.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"xai:a": { type: "oauth", provider: "xai", access: "tok" },
|
||||
},
|
||||
});
|
||||
mocks.setAuthProfileOrder.mockResolvedValue({
|
||||
version: 1,
|
||||
profiles: {},
|
||||
order: { xai: ["xai:a"] },
|
||||
});
|
||||
const runtime = createRuntime();
|
||||
|
||||
await modelsAuthOrderSetCommand({ provider: "x-ai", order: ["xai:a"] }, runtime);
|
||||
|
||||
expect(mocks.setAuthProfileOrder).toHaveBeenCalledWith({
|
||||
agentDir: "/tmp/agent-main",
|
||||
provider: "xai",
|
||||
order: ["xai:a"],
|
||||
});
|
||||
expect(runtime.logs).toContain("Auth profile order override: xai:a");
|
||||
});
|
||||
|
||||
it("clear removes the store order and refreshes a running gateway", async () => {
|
||||
const runtime = createRuntime();
|
||||
await modelsAuthOrderClearCommand({ provider: "anthropic" }, runtime);
|
||||
|
||||
@@ -17,10 +17,17 @@ import { refreshRunningGatewayAuthState } from "./auth-refresh.js";
|
||||
import { loadModelsConfig } from "./load-config.js";
|
||||
import { resolveModelsTargetAgent } from "./shared.js";
|
||||
|
||||
function describeOrder(store: AuthProfileStore, provider: string): string[] {
|
||||
const providerKey = normalizeProviderId(provider);
|
||||
const order = store.order?.[providerKey];
|
||||
return Array.isArray(order) ? order : [];
|
||||
function describeOrder(store: AuthProfileStore, provider: string, cfg: OpenClawConfig): string[] {
|
||||
const authProvider = resolveProviderIdForAuth(provider, { config: cfg });
|
||||
const canonical = findNormalizedProviderValue(store.order, authProvider);
|
||||
if (canonical !== undefined) {
|
||||
return canonical;
|
||||
}
|
||||
return (
|
||||
Object.entries(store.order ?? {})
|
||||
.filter(([key]) => resolveProviderIdForAuth(key, { config: cfg }) === authProvider)
|
||||
.toSorted(([left], [right]) => left.localeCompare(right))[0]?.[1] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
function describeOrderFallback(cfg: OpenClawConfig, provider: string): string {
|
||||
@@ -61,7 +68,7 @@ export async function modelsAuthOrderGetCommand(
|
||||
const store = ensureAuthProfileStore(agentDir, {
|
||||
externalCli: externalCliDiscoveryForProviderAuth({ cfg, provider }),
|
||||
});
|
||||
const order = describeOrder(store, provider);
|
||||
const order = describeOrder(store, provider, cfg);
|
||||
|
||||
if (opts.json) {
|
||||
writeRuntimeJson(runtime, {
|
||||
@@ -92,7 +99,7 @@ export async function modelsAuthOrderClearCommand(
|
||||
const { cfg, agentId, agentDir, provider } = await resolveAuthOrderContext(opts, runtime);
|
||||
const updated = await setAuthProfileOrder({
|
||||
agentDir,
|
||||
provider,
|
||||
provider: resolveProviderIdForAuth(provider, { config: cfg }),
|
||||
order: null,
|
||||
});
|
||||
if (!updated) {
|
||||
@@ -117,7 +124,7 @@ export async function modelsAuthOrderSetCommand(
|
||||
const store = ensureAuthProfileStore(agentDir, {
|
||||
externalCli: externalCliDiscoveryForProviderAuth({ cfg, provider }),
|
||||
});
|
||||
const providerKey = provider;
|
||||
const providerKey = resolveProviderIdForAuth(provider, { config: cfg });
|
||||
const requested = normalizeStringEntries(opts.order ?? []);
|
||||
if (requested.length === 0) {
|
||||
throw new Error(
|
||||
@@ -132,14 +139,14 @@ export async function modelsAuthOrderSetCommand(
|
||||
`Auth profile "${profileId}" not found in ${shortenHomePath(agentDir)}. Run ${formatCliCommand("openclaw models auth list --provider " + provider)} to see saved profiles.`,
|
||||
);
|
||||
}
|
||||
if (normalizeProviderId(cred.provider) !== providerKey) {
|
||||
if (resolveProviderIdForAuth(cred.provider, { config: cfg }) !== providerKey) {
|
||||
throw new Error(`Auth profile "${profileId}" is for ${cred.provider}, not ${provider}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await setAuthProfileOrder({
|
||||
agentDir,
|
||||
provider,
|
||||
provider: providerKey,
|
||||
order: requested,
|
||||
});
|
||||
if (!updated) {
|
||||
@@ -150,6 +157,6 @@ export async function modelsAuthOrderSetCommand(
|
||||
|
||||
runtime.log(`Agent: ${agentId}`);
|
||||
runtime.log(`Provider: ${provider}`);
|
||||
runtime.log(`Auth profile order override: ${describeOrder(updated, provider).join(", ")}`);
|
||||
runtime.log(`Auth profile order override: ${describeOrder(updated, provider, cfg).join(", ")}`);
|
||||
await refreshRunningGatewayAuthState();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user