Skip to content

Commit 44afab6

Browse files
committed
perf: skip unavailable media tool factories
1 parent 1a6d891 commit 44afab6

3 files changed

Lines changed: 474 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ Docs: https://docs.openclaw.ai
159159

160160
### Fixes
161161

162+
- Agents/tools: skip unavailable media generation and PDF tool factories from the live reply path when Gateway metadata and the active auth store prove no configured provider can back them, while keeping explicit config and auth-backed providers on the normal factory path. Thanks @shakkernerd.
162163
- Agents/tools: route media and generation capability lookups through the Gateway plugin metadata snapshot during reply tool registration, avoiding repeated manifest registry reloads on the live reply path. Thanks @shakkernerd.
163164
- Agents/tools: reuse the auth profile store already loaded for the active run when deciding media and generation tool availability, avoiding repeated provider-auth runtime discovery during reply startup. Thanks @shakkernerd.
164165
- Agents/tools: keep image, video, and music generation tool registration on manifest/auth control-plane checks instead of loading runtime provider registries during reply startup, reducing live-path tool-prep blocking while leaving provider runtime resolution for execution and list actions. Thanks @shakkernerd.
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
import type { OpenClawConfig } from "../config/types.openclaw.js";
3+
import {
4+
clearCurrentPluginMetadataSnapshot,
5+
setCurrentPluginMetadataSnapshot,
6+
} from "../plugins/current-plugin-metadata-snapshot.js";
7+
import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js";
8+
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
9+
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
10+
import type { AuthProfileStore } from "./auth-profiles/types.js";
11+
import { __testing } from "./openclaw-tools.js";
12+
13+
function createAuthStore(providers: string[] = []): AuthProfileStore {
14+
return {
15+
version: 1,
16+
profiles: Object.fromEntries(
17+
providers.map((provider) => [
18+
`${provider}:default`,
19+
{
20+
provider,
21+
type: "api_key",
22+
key: "test",
23+
},
24+
]),
25+
),
26+
};
27+
}
28+
29+
function createPlugin(params: {
30+
id: string;
31+
contracts: NonNullable<PluginManifestRecord["contracts"]>;
32+
setupProviders?: Array<{ id: string; envVars?: string[] }>;
33+
}): PluginManifestRecord {
34+
return {
35+
id: params.id,
36+
origin: "bundled",
37+
rootDir: `/plugins/${params.id}`,
38+
source: `/plugins/${params.id}/index.js`,
39+
manifestPath: `/plugins/${params.id}/openclaw.plugin.json`,
40+
channels: [],
41+
providers: [],
42+
cliBackends: [],
43+
skills: [],
44+
hooks: [],
45+
contracts: params.contracts,
46+
setup: params.setupProviders ? { providers: params.setupProviders } : undefined,
47+
};
48+
}
49+
50+
function installSnapshot(config: OpenClawConfig, plugins: PluginManifestRecord[]) {
51+
const snapshot = {
52+
policyHash: resolveInstalledPluginIndexPolicyHash(config),
53+
index: { plugins: [] },
54+
registryDiagnostics: [],
55+
manifestRegistry: { plugins, diagnostics: [] },
56+
plugins,
57+
diagnostics: [],
58+
byPluginId: new Map(plugins.map((plugin) => [plugin.id, plugin])),
59+
normalizePluginId: (id: string) => id,
60+
owners: {
61+
channels: new Map(),
62+
channelConfigs: new Map(),
63+
providers: new Map(),
64+
modelCatalogProviders: new Map(),
65+
cliBackends: new Map(),
66+
setupProviders: new Map(),
67+
commandAliases: new Map(),
68+
contracts: new Map(),
69+
},
70+
metrics: {
71+
registrySnapshotMs: 0,
72+
manifestRegistryMs: 0,
73+
ownerMapsMs: 0,
74+
totalMs: 0,
75+
indexPluginCount: 0,
76+
manifestPluginCount: plugins.length,
77+
},
78+
} satisfies PluginMetadataSnapshot;
79+
setCurrentPluginMetadataSnapshot(snapshot, { config });
80+
}
81+
82+
describe("optional media tool factory planning", () => {
83+
afterEach(() => {
84+
clearCurrentPluginMetadataSnapshot();
85+
vi.unstubAllEnvs();
86+
});
87+
88+
it("skips unavailable generation and PDF factories from snapshot and run auth facts", () => {
89+
const config: OpenClawConfig = {};
90+
installSnapshot(config, [
91+
createPlugin({
92+
id: "image-owner",
93+
contracts: { imageGenerationProviders: ["image-owner"] },
94+
setupProviders: [{ id: "image-owner", envVars: ["IMAGE_OWNER_API_KEY"] }],
95+
}),
96+
createPlugin({
97+
id: "video-owner",
98+
contracts: { videoGenerationProviders: ["video-owner"] },
99+
setupProviders: [{ id: "video-owner", envVars: ["VIDEO_OWNER_API_KEY"] }],
100+
}),
101+
createPlugin({
102+
id: "music-owner",
103+
contracts: { musicGenerationProviders: ["music-owner"] },
104+
setupProviders: [{ id: "music-owner", envVars: ["MUSIC_OWNER_API_KEY"] }],
105+
}),
106+
createPlugin({
107+
id: "media-owner",
108+
contracts: { mediaUnderstandingProviders: ["media-owner"] },
109+
setupProviders: [{ id: "media-owner", envVars: ["MEDIA_OWNER_API_KEY"] }],
110+
}),
111+
]);
112+
113+
expect(
114+
__testing.resolveOptionalMediaToolFactoryPlan({
115+
config,
116+
authStore: createAuthStore(["github-copilot"]),
117+
}),
118+
).toEqual({
119+
imageGenerate: false,
120+
videoGenerate: false,
121+
musicGenerate: false,
122+
pdf: false,
123+
});
124+
});
125+
126+
it("keeps explicit model configs on the factory path", () => {
127+
const config: OpenClawConfig = {
128+
agents: {
129+
defaults: {
130+
imageGenerationModel: { primary: "image-owner/model" },
131+
videoGenerationModel: { primary: "video-owner/model" },
132+
musicGenerationModel: { primary: "music-owner/model" },
133+
pdfModel: { primary: "media-owner/model" },
134+
},
135+
},
136+
};
137+
installSnapshot(config, []);
138+
139+
expect(
140+
__testing.resolveOptionalMediaToolFactoryPlan({
141+
config,
142+
authStore: createAuthStore(),
143+
}),
144+
).toEqual({
145+
imageGenerate: true,
146+
videoGenerate: true,
147+
musicGenerate: true,
148+
pdf: true,
149+
});
150+
});
151+
152+
it("skips tools that the resolved allowlist cannot expose", () => {
153+
const config: OpenClawConfig = {};
154+
installSnapshot(config, [
155+
createPlugin({
156+
id: "image-owner",
157+
contracts: { imageGenerationProviders: ["image-owner"] },
158+
setupProviders: [{ id: "image-owner", envVars: ["IMAGE_OWNER_API_KEY"] }],
159+
}),
160+
createPlugin({
161+
id: "media-owner",
162+
contracts: { mediaUnderstandingProviders: ["anthropic"] },
163+
setupProviders: [{ id: "anthropic", envVars: ["ANTHROPIC_API_KEY"] }],
164+
}),
165+
]);
166+
167+
expect(
168+
__testing.resolveOptionalMediaToolFactoryPlan({
169+
config,
170+
authStore: createAuthStore(["image-owner", "anthropic"]),
171+
toolAllowlist: ["image_generate"],
172+
}),
173+
).toEqual({
174+
imageGenerate: true,
175+
videoGenerate: false,
176+
musicGenerate: false,
177+
pdf: false,
178+
});
179+
});
180+
181+
it("keeps auth-backed providers on the factory path", () => {
182+
const config: OpenClawConfig = {};
183+
installSnapshot(config, [
184+
createPlugin({
185+
id: "image-owner",
186+
contracts: { imageGenerationProviders: ["image-owner"] },
187+
setupProviders: [{ id: "image-owner", envVars: ["IMAGE_OWNER_API_KEY"] }],
188+
}),
189+
createPlugin({
190+
id: "video-owner",
191+
contracts: { videoGenerationProviders: ["video-owner"] },
192+
setupProviders: [{ id: "video-owner", envVars: ["VIDEO_OWNER_API_KEY"] }],
193+
}),
194+
createPlugin({
195+
id: "music-owner",
196+
contracts: { musicGenerationProviders: ["music-owner"] },
197+
setupProviders: [{ id: "music-owner", envVars: ["MUSIC_OWNER_API_KEY"] }],
198+
}),
199+
createPlugin({
200+
id: "media-owner",
201+
contracts: { mediaUnderstandingProviders: ["media-owner"] },
202+
setupProviders: [{ id: "media-owner", envVars: ["MEDIA_OWNER_API_KEY"] }],
203+
}),
204+
]);
205+
vi.stubEnv("VIDEO_OWNER_API_KEY", "video-key");
206+
207+
expect(
208+
__testing.resolveOptionalMediaToolFactoryPlan({
209+
config,
210+
authStore: createAuthStore(["image-owner", "music-owner", "media-owner"]),
211+
}),
212+
).toEqual({
213+
imageGenerate: true,
214+
videoGenerate: true,
215+
musicGenerate: true,
216+
pdf: true,
217+
});
218+
});
219+
220+
it("falls back to existing factory checks when snapshot or auth store proof is missing", () => {
221+
expect(__testing.resolveOptionalMediaToolFactoryPlan({ config: {} })).toEqual({
222+
imageGenerate: true,
223+
videoGenerate: true,
224+
musicGenerate: true,
225+
pdf: true,
226+
});
227+
228+
const config: OpenClawConfig = {};
229+
installSnapshot(config, []);
230+
231+
expect(__testing.resolveOptionalMediaToolFactoryPlan({ config })).toEqual({
232+
imageGenerate: true,
233+
videoGenerate: true,
234+
musicGenerate: true,
235+
pdf: true,
236+
});
237+
});
238+
});

0 commit comments

Comments
 (0)