File size: 4,745 Bytes
5c2a829 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | import { beforeEach, describe, expect, expectTypeOf, it, vi } from "vitest";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
const mocks = vi.hoisted(() => ({
getSnapshot: vi.fn(),
loadCatalog: vi.fn(),
}));
vi.mock("../agents/prepared-model-catalog.js", () => ({
loadProviderScopedThinkingCatalog: vi.fn(async () => []),
getPreparedModelCatalogSnapshot: (...args: unknown[]) => mocks.getSnapshot(...args),
readPreparedModelCatalog: (...args: unknown[]) => mocks.loadCatalog(...args),
}));
import {
loadModelCatalog,
loadPreparedModelCatalog,
resolveThinkingDefaultWithRuntimeCatalog,
} from "openclaw/plugin-sdk/agent-runtime";
describe("agent-runtime model catalog compatibility", () => {
beforeEach(() => {
mocks.getSnapshot.mockReset();
mocks.loadCatalog.mockReset();
});
it("uses the shipped thinking catalog callback", async () => {
const readCatalog = vi.fn(async () => []);
await expect(
resolveThinkingDefaultWithRuntimeCatalog({
cfg: { agents: { defaults: { thinkingDefault: "low" } } },
provider: "example",
model: "example-model",
loadModelCatalog: readCatalog,
}),
).resolves.toBe("low");
expect(readCatalog).toHaveBeenCalledOnce();
});
it("propagates failures from the shipped thinking catalog callback", async () => {
const failure = new Error("catalog unavailable");
await expect(
resolveThinkingDefaultWithRuntimeCatalog({
cfg: {},
provider: "example",
model: "example-model",
loadModelCatalog: async () => {
throw failure;
},
}),
).rejects.toBe(failure);
});
it.each([
["prepared", loadPreparedModelCatalog],
["legacy", loadModelCatalog],
] as const)("preserves the writable default of the %s SDK loader", async (_name, load) => {
const entries = [{ provider: "test", id: "discovered", name: "Discovered" }];
mocks.loadCatalog.mockResolvedValue(entries);
await expect(load()).resolves.toBe(entries);
expect(mocks.loadCatalog).toHaveBeenCalledExactlyOnceWith({ readOnly: false });
expect(mocks.getSnapshot).not.toHaveBeenCalled();
});
it.each([true, false])("preserves explicit readOnly:%s in the SDK loader", async (readOnly) => {
const config = {};
const entries = [{ provider: "test", id: "selected", name: "Selected" }];
mocks.loadCatalog.mockResolvedValue(entries);
await expect(loadPreparedModelCatalog({ config, readOnly })).resolves.toBe(entries);
expect(mocks.loadCatalog).toHaveBeenCalledExactlyOnceWith({ config, readOnly });
});
it("keeps legacy cache-only reads nonblocking", async () => {
mocks.getSnapshot.mockReturnValue({
entries: [{ provider: "test", id: "cached", name: "Cached" }],
routeVariants: [],
});
await expect(
loadModelCatalog({ cacheOnly: true, useCache: true, refreshFullCatalog: true }),
).resolves.toEqual([{ provider: "test", id: "cached", name: "Cached" }]);
expect(mocks.loadCatalog).not.toHaveBeenCalled();
});
it("preserves explicit refresh intent through the legacy loader", async () => {
const entries = [{ provider: "test", id: "refreshed", name: "Refreshed" }];
mocks.loadCatalog.mockResolvedValue(entries);
await expect(loadModelCatalog({ refreshFullCatalog: true })).resolves.toBe(entries);
expect(mocks.loadCatalog).toHaveBeenCalledExactlyOnceWith({
readOnly: false,
refreshFullCatalog: true,
});
expect(mocks.getSnapshot).not.toHaveBeenCalled();
});
it("accepts legacy options without overriding lifecycle metadata", async () => {
type LegacyMetadataSnapshot = Omit<
PluginMetadataSnapshot,
"owners" | "declaredProviderOwners"
> & {
owners: Omit<PluginMetadataSnapshot["owners"], "modelIdNormalizationPolicies">;
};
type AcceptedMetadataSnapshot = NonNullable<
NonNullable<Parameters<typeof loadModelCatalog>[0]>["metadataSnapshot"]
>;
expectTypeOf<LegacyMetadataSnapshot>().toMatchTypeOf<AcceptedMetadataSnapshot>();
expectTypeOf<PluginMetadataSnapshot>().toMatchTypeOf<AcceptedMetadataSnapshot>();
mocks.loadCatalog.mockResolvedValue([]);
const config = {};
const env = { OPENCLAW_STATE_DIR: "/tmp/plugin-state" };
await loadModelCatalog({
agentDir: "/tmp/plugin-agent",
config,
env,
metadataSnapshot: {} as never,
readOnly: true,
useCache: false,
workspaceDir: "/tmp/plugin-workspace",
});
expect(mocks.loadCatalog).toHaveBeenCalledWith({
agentDir: "/tmp/plugin-agent",
config,
env,
readOnly: true,
workspaceDir: "/tmp/plugin-workspace",
});
});
});
|