fix(models): keep prepared auth contracts acyclic

This commit is contained in:
joshavant
2026-08-12 03:47:04 -05:00
parent 7ff6924d09
commit f8d208a0d5
14 changed files with 41 additions and 10 deletions
@@ -0,0 +1,2 @@
/** Secret-free credential modes captured by a prepared agent runtime. */
export type PreparedAgentCredentialModes = Readonly<Record<string, "api_key" | "oauth" | "token">>;
+1 -1
View File
@@ -4,6 +4,7 @@ import { asDateTimestampMs } from "@openclaw/normalization-core/number-coercion"
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { coerceSecretRef } from "../config/types.secrets.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credential-modes.js";
import { resolveAuthProfileOrder } from "./auth-profiles/order.js";
import type { AuthProfileCredential, AuthProfileStore } from "./auth-profiles/types.js";
import type { AuthStorageData } from "./sessions/auth-storage.js";
@@ -22,7 +23,6 @@ type AgentOAuthCredential = {
/** Credential value shape consumed by agent runtimes after auth-profile normalization. */
type AgentCredential = AgentApiKeyCredential | AgentOAuthCredential;
export type AgentCredentialMap = Record<string, AgentCredential>;
export type PreparedAgentCredentialModes = Readonly<Record<string, "api_key" | "oauth" | "token">>;
type ResolveAgentCredentialMapOptions = {
includeSecretRefPlaceholders?: boolean;
+1 -1
View File
@@ -5,7 +5,7 @@ import type {
ProviderModelRouteResolution,
} from "../plugin-sdk/provider-model-types.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credentials.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credential-modes.js";
import type { RuntimeAuthMaterialization } from "./auth-profiles/runtime-materializations.js";
import type { AuthProfileStore } from "./auth-profiles/types.js";
import {
+1 -1
View File
@@ -17,7 +17,7 @@ import type {
} from "../plugin-sdk/provider-model-types.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
import { isValidSecretRef } from "../secrets/ref-contract.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credentials.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credential-modes.js";
import { hasUsableOAuthCredential } from "./auth-profiles/credential-state.js";
import { resolveExternalCliAuthProfiles } from "./auth-profiles/external-cli-sync.js";
import {
@@ -202,6 +202,7 @@ describe("createOpenClawTools browser plugin integration", () => {
workspaceDir: "/tmp",
activeProjectKeys: [],
config,
authStore: { version: 1, profiles: {} },
authModes: {},
metadataSnapshot,
pluginRegistry,
+1 -1
View File
@@ -1,6 +1,6 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credentials.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credential-modes.js";
import type { AuthProfileStore } from "./auth-profiles/types.js";
import type { ModelCatalogSnapshot } from "./model-catalog.types.js";
+1 -1
View File
@@ -5,7 +5,7 @@ import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot
import type { PreparedProviderStaticCatalog } from "../plugins/provider-discovery.js";
import type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js";
import type { PluginRegistry } from "../plugins/registry-types.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credentials.js";
import type { PreparedAgentCredentialModes } from "./agent-auth-credential-modes.js";
import type { AuthProfileStore } from "./auth-profiles/types.js";
import type { InlineModelEntry } from "./embedded-agent-runner/model.inline-provider.js";
import type { AgentHarnessPluginSelection } from "./harness/runtime-plugin-load-plan.js";
@@ -60,6 +60,9 @@ describe("local gateway request context", () => {
agentDir: "/tmp/local-model-catalog-agent",
workspaceDir: "/tmp/local-model-catalog-workspace",
config: cfg,
authModes: {},
authStore: { version: 1, profiles: {} },
metadataSnapshot: { index: { plugins: [] }, plugins: [] } as never,
modelCatalog: { entries: [], routeVariants: [] },
});
@@ -21,12 +21,22 @@ function createOwner(
api?: ModelCatalogEntry["api"],
): PreparedModelRuntimeSnapshot {
const model = { id, name: id, provider, ...(api ? { api } : {}) };
const authStore: AuthProfileStore = {
version: 1,
profiles: Object.fromEntries(
Object.entries(credentials).map(([credentialProvider, credential]) => [
`${credentialProvider}:prepared`,
{ ...credential, provider: credentialProvider },
]),
),
};
return {
agentId: "main",
agentDir: `/tmp/${id}/agent`,
workspaceDir: `/tmp/${id}/workspace`,
activeProjectKeys: [],
config,
authStore,
authModes: resolveUsableAgentCredentialModes(credentials),
metadataSnapshot: { index: { plugins: [] }, plugins: [] } as never,
allowGatewaySubagentBinding: false,
@@ -1,4 +1,4 @@
import type { PreparedAgentCredentialModes } from "../../agents/agent-auth-credentials.js";
import type { PreparedAgentCredentialModes } from "../../agents/agent-auth-credential-modes.js";
import { resolveAgentDir } from "../../agents/agent-scope.js";
import type { RuntimeAuthMaterialization } from "../../agents/auth-profiles/runtime-materializations.js";
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
@@ -3,7 +3,7 @@
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { asPositiveSafeInteger as resolvePositiveSafeInteger } from "@openclaw/normalization-core/number-coercion";
import type { ModelChoice } from "../../../packages/gateway-protocol/src/schema/agents-models-skills.js";
import type { PreparedAgentCredentialModes } from "../../agents/agent-auth-credentials.js";
import type { PreparedAgentCredentialModes } from "../../agents/agent-auth-credential-modes.js";
import {
resolveAgentEffectiveModelPrimary,
resolveAgentWorkspaceDir,
+5 -1
View File
@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import type { ModelCatalogSnapshot } from "../agents/model-catalog.types.js";
import type { PublishedModelCatalogOwnerCandidate } from "../agents/prepared-model-catalog.types.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
loadGatewayModelCatalog,
@@ -33,11 +34,14 @@ function ownerSnapshot(
config: OpenClawConfig,
modelCatalog: ModelCatalogSnapshot = snapshot,
agentId?: string,
) {
): PublishedModelCatalogOwnerCandidate {
return {
...(agentId ? { agentId } : {}),
agentDir: "/tmp/gateway-agent",
config,
authModes: {},
authStore: { version: 1, profiles: {} },
metadataSnapshot: { index: { plugins: [] }, plugins: [] } as never,
modelCatalog,
};
}
@@ -1296,6 +1296,7 @@ describe("gateway server chat", () => {
compat: { supportedReasoningEfforts: ["low"] },
params: { apiKey: "private-route-token" },
};
const pluginMetadata = resolvePluginMetadataSnapshot({ config, env: process.env });
const catalogSnapshot = {
entries: [subscriptionRoute],
routeVariants: [subscriptionRoute, platformRoute],
@@ -1317,6 +1318,13 @@ describe("gateway server chat", () => {
}),
],
]);
const requirePreparedAuthStore = (agentId: string) => {
const authStore = preparedAuthStoreByAgentId.get(agentId);
if (!authStore) {
throw new Error(`expected prepared auth store for agent "${agentId}"`);
}
return authStore;
};
const responses: Array<{ ok: boolean; payload?: unknown; error?: unknown }> = [];
const { buildModelsListResult, createGatewayAgentModelCatalogProjector } =
await import("./server-methods/models-list-result.js");
@@ -1351,7 +1359,8 @@ describe("gateway server chat", () => {
cfg: config,
agentId,
snapshot: catalogSnapshot,
preparedAuthStore: preparedAuthStoreByAgentId.get(agentId),
metadataSnapshot: pluginMetadata,
preparedAuthStore: requirePreparedAuthStore(agentId),
...(profileId ? { preferredProfileId: profileId } : {}),
...(profileId && (profileSource === "user" || legacyUserProfile)
? { lockedProfileId: profileId }
@@ -1412,7 +1421,6 @@ describe("gateway server chat", () => {
const persistedConfig = getRuntimeConfig();
// Direct handlers bypass Gateway startup, so publish its process-lifecycle handoff once.
// Otherwise every route projector rediscovers the full plugin metadata graph.
const pluginMetadata = resolvePluginMetadataSnapshot({ config, env: process.env });
releasePluginMetadata = installTemporaryCurrentPluginMetadataSnapshot(pluginMetadata, {
config,
compatibleConfigs: [persistedConfig],
@@ -1427,6 +1435,8 @@ describe("gateway server chat", () => {
cfg: persistedConfig,
agentId: "work",
snapshot: catalogSnapshot,
metadataSnapshot: pluginMetadata,
preparedAuthStore: requirePreparedAuthStore("work"),
preferredProfileId: "openai:expired",
}).evaluateEntry(subscriptionRoute, catalogSnapshot.routeVariants);
expect(expiredPreferenceEvaluation).toMatchObject({
@@ -186,6 +186,7 @@ function setup(entry: SessionEntry = sessionEntry) {
allowGatewaySubagentBinding: true,
workspaceDir: WORKSPACE,
config,
authStore: { version: 1, profiles: {} },
authModes: {},
metadataSnapshot: { plugins: [] } as never,
modelCatalog: {