mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-24 07:31:44 +00:00
feat: add implicit discovery toggles
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
20a882f9991e17310013471756ac7ec62c272e29490daeede9c0901bd51c0e69 config-baseline.json
|
||||
8ba6e5c959d5fc3eee9e6c5d1d8f764f164052f4207c0352bb39e2a7dbad64a8 config-baseline.core.json
|
||||
ca6d1fa8a3507566979ea2da2b88a6a7ae49d650f3ebd3eee14a22ed18e5be89 config-baseline.channel.json
|
||||
17fd37605bf6cb087932ec2ebcfa9dd22e669fa6b8b93081ab2deac9d24821c5 config-baseline.plugin.json
|
||||
8bbf281d0c63e38098b2132174b77ed58faf2083fb68cb88f90ebe76d7acda1b config-baseline.json
|
||||
7163170accb9a8b62455ede5437f057d5a9e9ab5da42010cf0f39cbad952071d config-baseline.core.json
|
||||
3c999707b167138de34f6255e3488b99e404c5132d3fc5879a1fa12d815c31f5 config-baseline.channel.json
|
||||
76d011c68b8bc44ec862afa826dd8ddd7c577d89ce0b822eed306f8e1e9301ab config-baseline.plugin.json
|
||||
|
||||
47
extensions/github-copilot/index.test.ts
Normal file
47
extensions/github-copilot/index.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
|
||||
|
||||
const resolveCopilotApiTokenMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./register.runtime.js", () => ({
|
||||
DEFAULT_COPILOT_API_BASE_URL: "https://api.githubcopilot.test",
|
||||
resolveCopilotApiToken: resolveCopilotApiTokenMock,
|
||||
githubCopilotLoginCommand: vi.fn(),
|
||||
fetchCopilotUsage: vi.fn(),
|
||||
}));
|
||||
|
||||
import plugin from "./index.js";
|
||||
|
||||
function registerProvider() {
|
||||
const registerProviderMock = vi.fn();
|
||||
|
||||
plugin.register(
|
||||
createTestPluginApi({
|
||||
id: "github-copilot",
|
||||
name: "GitHub Copilot",
|
||||
source: "test",
|
||||
config: {},
|
||||
runtime: {} as never,
|
||||
registerProvider: registerProviderMock,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(registerProviderMock).toHaveBeenCalledTimes(1);
|
||||
return registerProviderMock.mock.calls[0]?.[0];
|
||||
}
|
||||
|
||||
describe("github-copilot plugin", () => {
|
||||
it("skips catalog discovery when models.copilotDiscovery.enabled is false", async () => {
|
||||
const provider = registerProvider();
|
||||
|
||||
const result = await provider.catalog.run({
|
||||
config: { models: { copilotDiscovery: { enabled: false } } },
|
||||
agentDir: "/tmp/agent",
|
||||
env: { GH_TOKEN: "gh_test_token" },
|
||||
resolveProviderApiKey: () => ({ apiKey: "gh_test_token" }),
|
||||
} as never);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(resolveCopilotApiTokenMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -125,6 +125,9 @@ export default definePluginEntry({
|
||||
catalog: {
|
||||
order: "late",
|
||||
run: async (ctx) => {
|
||||
if (ctx.config?.models?.copilotDiscovery?.enabled === false) {
|
||||
return null;
|
||||
}
|
||||
const { DEFAULT_COPILOT_API_BASE_URL, resolveCopilotApiToken } =
|
||||
await loadGithubCopilotRuntime();
|
||||
const { githubToken, hasProfile } = resolveFirstGithubToken({
|
||||
|
||||
56
extensions/huggingface/index.test.ts
Normal file
56
extensions/huggingface/index.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
|
||||
|
||||
const buildHuggingfaceProviderMock = vi.hoisted(() =>
|
||||
vi.fn(async () => ({
|
||||
baseUrl: "https://router.huggingface.co/v1",
|
||||
api: "openai-completions",
|
||||
models: [],
|
||||
})),
|
||||
);
|
||||
|
||||
vi.mock("./provider-catalog.js", () => ({
|
||||
buildHuggingfaceProvider: buildHuggingfaceProviderMock,
|
||||
}));
|
||||
|
||||
vi.mock("./onboard.js", () => ({
|
||||
applyHuggingfaceConfig: vi.fn((cfg) => cfg),
|
||||
HUGGINGFACE_DEFAULT_MODEL_REF: "huggingface/deepseek-ai/DeepSeek-R1",
|
||||
}));
|
||||
|
||||
import plugin from "./index.js";
|
||||
|
||||
function registerProvider() {
|
||||
const registerProviderMock = vi.fn();
|
||||
|
||||
plugin.register(
|
||||
createTestPluginApi({
|
||||
id: "huggingface",
|
||||
name: "Hugging Face",
|
||||
source: "test",
|
||||
config: {},
|
||||
runtime: {} as never,
|
||||
registerProvider: registerProviderMock,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(registerProviderMock).toHaveBeenCalledTimes(1);
|
||||
return registerProviderMock.mock.calls[0]?.[0];
|
||||
}
|
||||
|
||||
describe("huggingface plugin", () => {
|
||||
it("skips catalog discovery when models.huggingfaceDiscovery.enabled is false", async () => {
|
||||
const provider = registerProvider();
|
||||
|
||||
const result = await provider.catalog.run({
|
||||
config: { models: { huggingfaceDiscovery: { enabled: false } } },
|
||||
resolveProviderApiKey: () => ({
|
||||
apiKey: "hf_test_token",
|
||||
discoveryApiKey: "hf_test_token",
|
||||
}),
|
||||
} as never);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(buildHuggingfaceProviderMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -41,6 +41,9 @@ export default definePluginEntry({
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async (ctx) => {
|
||||
if (ctx.config?.models?.huggingfaceDiscovery?.enabled === false) {
|
||||
return null;
|
||||
}
|
||||
const { apiKey, discoveryApiKey } = ctx.resolveProviderApiKey(PROVIDER_ID);
|
||||
if (!apiKey) {
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestPluginApi } from "../../test/helpers/plugins/plugin-api.js";
|
||||
import plugin from "./index.js";
|
||||
|
||||
@@ -18,14 +18,21 @@ const promptAndConfigureOllamaMock = vi.hoisted(() =>
|
||||
})),
|
||||
);
|
||||
const ensureOllamaModelPulledMock = vi.hoisted(() => vi.fn(async () => {}));
|
||||
const buildOllamaProviderMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./api.js", () => ({
|
||||
promptAndConfigureOllama: promptAndConfigureOllamaMock,
|
||||
ensureOllamaModelPulled: ensureOllamaModelPulledMock,
|
||||
configureOllamaNonInteractive: vi.fn(),
|
||||
buildOllamaProvider: vi.fn(),
|
||||
buildOllamaProvider: buildOllamaProviderMock,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
promptAndConfigureOllamaMock.mockClear();
|
||||
ensureOllamaModelPulledMock.mockClear();
|
||||
buildOllamaProviderMock.mockReset();
|
||||
});
|
||||
|
||||
function registerProvider() {
|
||||
const registerProviderMock = vi.fn();
|
||||
|
||||
@@ -102,6 +109,19 @@ describe("ollama plugin", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("skips ambient discovery when models.ollamaDiscovery.enabled is false", async () => {
|
||||
const provider = registerProvider();
|
||||
|
||||
const result = await provider.discovery.run({
|
||||
config: { models: { ollamaDiscovery: { enabled: false } } },
|
||||
env: {},
|
||||
resolveProviderApiKey: () => ({ apiKey: "", discoveryApiKey: "" }),
|
||||
} as never);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(buildOllamaProviderMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("wraps OpenAI-compatible payloads with num_ctx for Ollama compat routes", () => {
|
||||
const provider = registerProvider();
|
||||
let payloadSeen: Record<string, unknown> | undefined;
|
||||
|
||||
@@ -102,6 +102,9 @@ export default definePluginEntry({
|
||||
run: async (ctx: ProviderDiscoveryContext) => {
|
||||
const explicit = ctx.config.models?.providers?.ollama;
|
||||
const hasExplicitModels = Array.isArray(explicit?.models) && explicit.models.length > 0;
|
||||
if (!hasExplicitModels && ctx.config.models?.ollamaDiscovery?.enabled === false) {
|
||||
return null;
|
||||
}
|
||||
const ollamaKey = ctx.resolveProviderApiKey(PROVIDER_ID).apiKey;
|
||||
const explicitApiKey = typeof explicit?.apiKey === "string" ? explicit.apiKey : undefined;
|
||||
if (hasExplicitModels && explicit) {
|
||||
|
||||
@@ -2947,6 +2947,51 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
description:
|
||||
"Automatic AWS Bedrock model discovery settings used to synthesize provider model entries from account visibility. Keep discovery scoped and refresh intervals conservative to reduce API churn.",
|
||||
},
|
||||
copilotDiscovery: {
|
||||
type: "object",
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
title: "Copilot Discovery Enabled",
|
||||
description:
|
||||
"Set to false to prevent Copilot discovery from running even when GitHub tokens are detected. Useful when GH_TOKEN is set for other tools and you do not want Copilot provider auto-registration.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "Copilot Model Discovery",
|
||||
description:
|
||||
"GitHub Copilot implicit discovery settings. Controls whether OpenClaw probes for Copilot API access when GH_TOKEN or GITHUB_TOKEN is present.",
|
||||
},
|
||||
huggingfaceDiscovery: {
|
||||
type: "object",
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
title: "Hugging Face Discovery Enabled",
|
||||
description:
|
||||
"Set to false to prevent Hugging Face model discovery from running even when HF_TOKEN is detected. Useful when the token is set for other tools like transformers-cli.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "Hugging Face Model Discovery",
|
||||
description:
|
||||
"Hugging Face implicit discovery settings. Controls whether OpenClaw fetches the Hugging Face model catalog when HF_TOKEN is present.",
|
||||
},
|
||||
ollamaDiscovery: {
|
||||
type: "object",
|
||||
properties: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
title: "Ollama Discovery Enabled",
|
||||
description:
|
||||
"Set to false to prevent Ollama discovery from probing localhost:11434 on startup. Useful when Ollama is not intended for OpenClaw or the local probe causes startup delays.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "Ollama Model Discovery",
|
||||
description:
|
||||
"Ollama implicit discovery settings. Controls whether OpenClaw probes the local Ollama server for available models on startup.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "Models",
|
||||
@@ -24931,6 +24976,36 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: "Fallback max-token value applied to discovered models without explicit output token limits. Use conservative defaults to reduce truncation surprises and unexpected token spend.",
|
||||
tags: ["security", "auth", "performance", "models"],
|
||||
},
|
||||
"models.copilotDiscovery": {
|
||||
label: "Copilot Model Discovery",
|
||||
help: "GitHub Copilot implicit discovery settings. Controls whether OpenClaw probes for Copilot API access when GH_TOKEN or GITHUB_TOKEN is present.",
|
||||
tags: ["models"],
|
||||
},
|
||||
"models.copilotDiscovery.enabled": {
|
||||
label: "Copilot Discovery Enabled",
|
||||
help: "Set to false to prevent Copilot discovery from running even when GitHub tokens are detected. Useful when GH_TOKEN is set for other tools and you do not want Copilot provider auto-registration.",
|
||||
tags: ["models"],
|
||||
},
|
||||
"models.huggingfaceDiscovery": {
|
||||
label: "Hugging Face Model Discovery",
|
||||
help: "Hugging Face implicit discovery settings. Controls whether OpenClaw fetches the Hugging Face model catalog when HF_TOKEN is present.",
|
||||
tags: ["models"],
|
||||
},
|
||||
"models.huggingfaceDiscovery.enabled": {
|
||||
label: "Hugging Face Discovery Enabled",
|
||||
help: "Set to false to prevent Hugging Face model discovery from running even when HF_TOKEN is detected. Useful when the token is set for other tools like transformers-cli.",
|
||||
tags: ["models"],
|
||||
},
|
||||
"models.ollamaDiscovery": {
|
||||
label: "Ollama Model Discovery",
|
||||
help: "Ollama implicit discovery settings. Controls whether OpenClaw probes the local Ollama server for available models on startup.",
|
||||
tags: ["models"],
|
||||
},
|
||||
"models.ollamaDiscovery.enabled": {
|
||||
label: "Ollama Discovery Enabled",
|
||||
help: "Set to false to prevent Ollama discovery from probing localhost:11434 on startup. Useful when Ollama is not intended for OpenClaw or the local probe causes startup delays.",
|
||||
tags: ["models"],
|
||||
},
|
||||
"auth.cooldowns.billingBackoffHours": {
|
||||
label: "Billing Backoff (hours)",
|
||||
help: "Base backoff (hours) when a profile fails due to billing/insufficient credits (default: 5).",
|
||||
|
||||
@@ -373,6 +373,12 @@ const TARGET_KEYS = [
|
||||
"models.bedrockDiscovery.refreshInterval",
|
||||
"models.bedrockDiscovery.defaultContextWindow",
|
||||
"models.bedrockDiscovery.defaultMaxTokens",
|
||||
"models.copilotDiscovery",
|
||||
"models.copilotDiscovery.enabled",
|
||||
"models.huggingfaceDiscovery",
|
||||
"models.huggingfaceDiscovery.enabled",
|
||||
"models.ollamaDiscovery",
|
||||
"models.ollamaDiscovery.enabled",
|
||||
"agents",
|
||||
"agents.defaults",
|
||||
"agents.list",
|
||||
|
||||
@@ -803,6 +803,18 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Fallback context-window value applied to discovered models when provider metadata lacks explicit limits. Use realistic defaults to avoid oversized prompts that exceed true provider constraints.",
|
||||
"models.bedrockDiscovery.defaultMaxTokens":
|
||||
"Fallback max-token value applied to discovered models without explicit output token limits. Use conservative defaults to reduce truncation surprises and unexpected token spend.",
|
||||
"models.copilotDiscovery":
|
||||
"GitHub Copilot implicit discovery settings. Controls whether OpenClaw probes for Copilot API access when GH_TOKEN or GITHUB_TOKEN is present.",
|
||||
"models.copilotDiscovery.enabled":
|
||||
"Set to false to prevent Copilot discovery from running even when GitHub tokens are detected. Useful when GH_TOKEN is set for other tools and you do not want Copilot provider auto-registration.",
|
||||
"models.huggingfaceDiscovery":
|
||||
"Hugging Face implicit discovery settings. Controls whether OpenClaw fetches the Hugging Face model catalog when HF_TOKEN is present.",
|
||||
"models.huggingfaceDiscovery.enabled":
|
||||
"Set to false to prevent Hugging Face model discovery from running even when HF_TOKEN is detected. Useful when the token is set for other tools like transformers-cli.",
|
||||
"models.ollamaDiscovery":
|
||||
"Ollama implicit discovery settings. Controls whether OpenClaw probes the local Ollama server for available models on startup.",
|
||||
"models.ollamaDiscovery.enabled":
|
||||
"Set to false to prevent Ollama discovery from probing localhost:11434 on startup. Useful when Ollama is not intended for OpenClaw or the local probe causes startup delays.",
|
||||
auth: "Authentication profile root used for multi-profile provider credentials and cooldown-based failover ordering. Keep profiles minimal and explicit so automatic failover behavior stays auditable.",
|
||||
"channels.matrix.allowBots":
|
||||
'Allow messages from other configured Matrix bot accounts to trigger replies (default: false). Set "mentions" to only accept bot messages that visibly mention this bot.',
|
||||
|
||||
@@ -483,6 +483,12 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"models.bedrockDiscovery.refreshInterval": "Bedrock Discovery Refresh Interval (s)",
|
||||
"models.bedrockDiscovery.defaultContextWindow": "Bedrock Default Context Window",
|
||||
"models.bedrockDiscovery.defaultMaxTokens": "Bedrock Default Max Tokens",
|
||||
"models.copilotDiscovery": "Copilot Model Discovery",
|
||||
"models.copilotDiscovery.enabled": "Copilot Discovery Enabled",
|
||||
"models.huggingfaceDiscovery": "Hugging Face Model Discovery",
|
||||
"models.huggingfaceDiscovery.enabled": "Hugging Face Discovery Enabled",
|
||||
"models.ollamaDiscovery": "Ollama Model Discovery",
|
||||
"models.ollamaDiscovery.enabled": "Ollama Discovery Enabled",
|
||||
"auth.cooldowns.billingBackoffHours": "Billing Backoff (hours)",
|
||||
"auth.cooldowns.billingBackoffHoursByProvider": "Billing Backoff Overrides",
|
||||
"auth.cooldowns.billingMaxHours": "Billing Backoff Cap (hours)",
|
||||
|
||||
@@ -92,8 +92,15 @@ export type BedrockDiscoveryConfig = {
|
||||
defaultMaxTokens?: number;
|
||||
};
|
||||
|
||||
export type DiscoveryToggleConfig = {
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type ModelsConfig = {
|
||||
mode?: "merge" | "replace";
|
||||
providers?: Record<string, ModelProviderConfig>;
|
||||
bedrockDiscovery?: BedrockDiscoveryConfig;
|
||||
copilotDiscovery?: DiscoveryToggleConfig;
|
||||
huggingfaceDiscovery?: DiscoveryToggleConfig;
|
||||
ollamaDiscovery?: DiscoveryToggleConfig;
|
||||
};
|
||||
|
||||
@@ -342,11 +342,21 @@ export const BedrockDiscoverySchema = z
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
export const DiscoveryToggleSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
export const ModelsConfigSchema = z
|
||||
.object({
|
||||
mode: z.union([z.literal("merge"), z.literal("replace")]).optional(),
|
||||
providers: z.record(z.string(), ModelProviderSchema).optional(),
|
||||
bedrockDiscovery: BedrockDiscoverySchema,
|
||||
copilotDiscovery: DiscoveryToggleSchema,
|
||||
huggingfaceDiscovery: DiscoveryToggleSchema,
|
||||
ollamaDiscovery: DiscoveryToggleSchema,
|
||||
})
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
Reference in New Issue
Block a user