mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix: cover comfy manifest availability contracts
This commit is contained in:
@@ -166,6 +166,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/runtime: validate agent model allowlists against manifest model catalog metadata during reply startup, avoiding broad provider runtime catalog loading before the agent run lane starts. Thanks @shakkernerd.
|
||||
- Agents/runtime: keep allowlisted configured model thinking metadata available when manifest catalog rows are absent, so explicit high-reasoning levels remain valid for custom configured models. Thanks @shakkernerd.
|
||||
- Agents/tools: preserve plugin-declared config-only generation providers such as local Comfy workflows during reply tool pre-gating, and share manifest auth/config availability checks between the planner and final tool factories. Thanks @shakkernerd.
|
||||
- Agents/tools: keep Comfy generation tools visible from legacy local workflow config and cloud API-key config when no Gateway metadata snapshot is active, using plugin-declared manifest signals instead of loading provider runtimes. Thanks @shakkernerd.
|
||||
- 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.
|
||||
- Agents/tools: let plugins declare media generation auth aliases and base-url guards in manifests, preserving OpenAI Codex OAuth image generation availability without core-owned provider special cases. Thanks @shakkernerd.
|
||||
- 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.
|
||||
|
||||
@@ -43,6 +43,37 @@
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId"]
|
||||
},
|
||||
{
|
||||
"rootPath": "models.providers.comfy",
|
||||
"overlayPath": "image",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"default": "local",
|
||||
"allowed": ["local"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId"]
|
||||
},
|
||||
{
|
||||
"rootPath": "plugins.entries.comfy.config",
|
||||
"overlayPath": "image",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"allowed": ["cloud"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId", "apiKey"]
|
||||
},
|
||||
{
|
||||
"rootPath": "models.providers.comfy",
|
||||
"overlayPath": "image",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"allowed": ["cloud"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId", "apiKey"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -60,6 +91,37 @@
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId"]
|
||||
},
|
||||
{
|
||||
"rootPath": "models.providers.comfy",
|
||||
"overlayPath": "music",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"default": "local",
|
||||
"allowed": ["local"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId"]
|
||||
},
|
||||
{
|
||||
"rootPath": "plugins.entries.comfy.config",
|
||||
"overlayPath": "music",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"allowed": ["cloud"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId", "apiKey"]
|
||||
},
|
||||
{
|
||||
"rootPath": "models.providers.comfy",
|
||||
"overlayPath": "music",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"allowed": ["cloud"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId", "apiKey"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -77,6 +139,37 @@
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId"]
|
||||
},
|
||||
{
|
||||
"rootPath": "models.providers.comfy",
|
||||
"overlayPath": "video",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"default": "local",
|
||||
"allowed": ["local"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId"]
|
||||
},
|
||||
{
|
||||
"rootPath": "plugins.entries.comfy.config",
|
||||
"overlayPath": "video",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"allowed": ["cloud"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId", "apiKey"]
|
||||
},
|
||||
{
|
||||
"rootPath": "models.providers.comfy",
|
||||
"overlayPath": "video",
|
||||
"mode": {
|
||||
"path": "mode",
|
||||
"allowed": ["cloud"]
|
||||
},
|
||||
"requiredAny": ["workflow", "workflowPath"],
|
||||
"required": ["promptNodeId", "apiKey"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { setBundledPluginsDirOverrideForTest } from "../plugins/bundled-dir.js";
|
||||
import {
|
||||
clearCurrentPluginMetadataSnapshot,
|
||||
setCurrentPluginMetadataSnapshot,
|
||||
@@ -103,6 +105,7 @@ function installSnapshot(
|
||||
describe("optional media tool factory planning", () => {
|
||||
afterEach(() => {
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
setBundledPluginsDirOverrideForTest(undefined);
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
@@ -390,6 +393,69 @@ describe("optional media tool factory planning", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "legacy local provider config",
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
comfy: {
|
||||
workflow: { "1": { inputs: {} } },
|
||||
promptNodeId: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig,
|
||||
},
|
||||
{
|
||||
name: "plugin cloud API key config",
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
comfy: {
|
||||
config: {
|
||||
mode: "cloud",
|
||||
apiKey: "cloud-key",
|
||||
workflow: { "1": { inputs: {} } },
|
||||
promptNodeId: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig,
|
||||
},
|
||||
{
|
||||
name: "legacy cloud API key config",
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
comfy: {
|
||||
mode: "cloud",
|
||||
apiKey: "cloud-key",
|
||||
workflow: { "1": { inputs: {} } },
|
||||
promptNodeId: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig,
|
||||
},
|
||||
])(
|
||||
"registers generation tools from Comfy $name without a current metadata snapshot",
|
||||
({ config }) => {
|
||||
setBundledPluginsDirOverrideForTest(path.join(process.cwd(), "extensions"));
|
||||
|
||||
const toolNames = createOpenClawTools({
|
||||
config,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
}).map((tool) => tool.name);
|
||||
|
||||
expect(toolNames).toContain("image_generate");
|
||||
expect(toolNames).toContain("video_generate");
|
||||
expect(toolNames).toContain("music_generate");
|
||||
},
|
||||
);
|
||||
|
||||
it("honors manifest-declared image provider auth alias base-url guards", () => {
|
||||
const config: OpenClawConfig = {
|
||||
models: {
|
||||
|
||||
@@ -4,7 +4,8 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { SsrFPolicy } from "../../infra/net/ssrf.js";
|
||||
import { getDefaultLocalRoots } from "../../media/web-media.js";
|
||||
import { readSnakeCaseParamRaw } from "../../param-key.js";
|
||||
import { resolveManifestCapabilityProviderIds } from "../../plugins/capability-provider-runtime.js";
|
||||
import { loadCapabilityManifestSnapshot } from "../../plugins/capability-provider-runtime.js";
|
||||
import { listAvailableManifestContractValues } from "../../plugins/manifest-contract-eligibility.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
@@ -321,12 +322,16 @@ export function hasGenerationToolAvailability(params: {
|
||||
}),
|
||||
);
|
||||
}
|
||||
const snapshot = getCurrentCapabilityMetadataSnapshot({
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
});
|
||||
const snapshot =
|
||||
getCurrentCapabilityMetadataSnapshot({
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
}) ??
|
||||
loadCapabilityManifestSnapshot({
|
||||
cfg: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
});
|
||||
if (
|
||||
snapshot &&
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
key: params.providerKey,
|
||||
@@ -336,10 +341,10 @@ export function hasGenerationToolAvailability(params: {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return resolveManifestCapabilityProviderIds({
|
||||
key: params.providerKey,
|
||||
cfg: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
return listAvailableManifestContractValues({
|
||||
snapshot,
|
||||
contract: params.providerKey,
|
||||
config: params.cfg,
|
||||
}).some((providerId) =>
|
||||
hasAuthForProvider({
|
||||
provider: providerId,
|
||||
|
||||
@@ -85,7 +85,7 @@ function uniqueSorted(values: Iterable<string>): string[] {
|
||||
return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function loadCapabilityManifestSnapshot(params: {
|
||||
export function loadCapabilityManifestSnapshot(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
}): Pick<PluginMetadataSnapshot, "index" | "plugins"> {
|
||||
|
||||
Reference in New Issue
Block a user