fix(cli): scope web command secret refs

This commit is contained in:
Peter Steinberger
2026-05-17 05:58:49 +01:00
parent 230806eaf2
commit fe680e47ce
32 changed files with 2383 additions and 2182 deletions

View File

@@ -20,8 +20,6 @@ const mocks = vi.hoisted(() => ({
writeStdout: vi.fn(),
},
loadConfig: vi.fn(() => ({})),
getRuntimeConfigSourceSnapshot: vi.fn(() => null),
setRuntimeConfigSnapshot: vi.fn(),
loadAuthProfileStoreForRuntime: vi.fn(() => ({ profiles: {}, order: {} })),
listProfilesForProvider: vi.fn(() => []),
updateAuthProfileStoreWithLock: vi.fn(
@@ -135,23 +133,13 @@ const mocks = vi.hoisted(() => ({
convertHeicToJpeg: vi.fn(async () => Buffer.from("jpeg-normalized")),
isWebSearchProviderConfigured: vi.fn(() => false),
isWebFetchProviderConfigured: vi.fn(() => false),
resolveCommandConfigWithSecrets: vi.fn(async ({ config }: { config: unknown }) => ({
resolvedConfig: config,
effectiveConfig: config,
diagnostics: [],
})),
getAgentRuntimeCommandSecretTargetIds: vi.fn(() => new Set(["agent-runtime-target"])),
getMemoryEmbeddingCommandSecretTargetIds: vi.fn(() => new Set(["memory-target"])),
getModelsCommandSecretTargetIds: vi.fn(() => new Set(["model-target"])),
getTtsCommandSecretTargetIds: vi.fn(() => new Set(["tts-target"])),
getWebFetchCommandSecretTargets: vi.fn(() => ({
targetIds: new Set(["web-fetch-target"]),
allowedPaths: new Set(["plugins.entries.firecrawl.config.webFetch.apiKey"]),
})),
getWebSearchCommandSecretTargets: vi.fn(() => ({
targetIds: new Set(["web-search-target"]),
allowedPaths: new Set(["plugins.entries.tavily.config.webSearch.apiKey"]),
})),
resolveCommandConfigWithSecrets: vi.fn(
async ({ config }: { config: Record<string, unknown> }) => ({
resolvedConfig: config,
effectiveConfig: config,
diagnostics: [],
}),
),
modelsStatusCommand: vi.fn(
async (_opts: unknown, runtime: { log: (...args: unknown[]) => void }) => {
runtime.log(JSON.stringify({ ok: true, providers: [{ id: "openai" }] }));
@@ -166,12 +154,8 @@ vi.mock("../runtime.js", () => ({
}));
vi.mock("../config/config.js", () => ({
getRuntimeConfigSourceSnapshot:
mocks.getRuntimeConfigSourceSnapshot as typeof import("../config/config.js").getRuntimeConfigSourceSnapshot,
getRuntimeConfig: mocks.loadConfig as typeof import("../config/config.js").getRuntimeConfig,
loadConfig: mocks.loadConfig as typeof import("../config/config.js").loadConfig,
setRuntimeConfigSnapshot:
mocks.setRuntimeConfigSnapshot as typeof import("../config/config.js").setRuntimeConfigSnapshot,
}));
vi.mock("./command-config-resolution.js", () => ({
@@ -319,24 +303,81 @@ vi.mock("../web-fetch/runtime.js", () => ({
resolveWebFetchDefinition: vi.fn(),
}));
vi.mock("./command-config-resolution.js", () => ({
resolveCommandConfigWithSecrets:
mocks.resolveCommandConfigWithSecrets as typeof import("./command-config-resolution.js").resolveCommandConfigWithSecrets,
vi.mock("../plugins/web-fetch-providers.runtime.js", () => ({
resolvePluginWebFetchProviders: vi.fn((params: { config?: Record<string, unknown> }) => [
{
pluginId: "firecrawl",
id: "firecrawl",
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
getConfiguredCredentialValue: (config?: {
plugins?: {
entries?: {
firecrawl?: { config?: { webFetch?: { apiKey?: unknown } } };
};
};
}) => config?.plugins?.entries?.firecrawl?.config?.webFetch?.apiKey,
getConfiguredCredentialFallback: () => ({
path: "plugins.entries.firecrawl.config.webSearch.apiKey",
value: (
params.config as {
plugins?: {
entries?: {
firecrawl?: { config?: { webSearch?: { apiKey?: unknown } } };
};
};
}
)?.plugins?.entries?.firecrawl?.config?.webSearch?.apiKey,
}),
getCredentialValue: (): undefined => undefined,
},
]),
}));
vi.mock("./command-secret-targets.js", () => ({
getAgentRuntimeCommandSecretTargetIds:
mocks.getAgentRuntimeCommandSecretTargetIds as typeof import("./command-secret-targets.js").getAgentRuntimeCommandSecretTargetIds,
getMemoryEmbeddingCommandSecretTargetIds:
mocks.getMemoryEmbeddingCommandSecretTargetIds as typeof import("./command-secret-targets.js").getMemoryEmbeddingCommandSecretTargetIds,
getModelsCommandSecretTargetIds:
mocks.getModelsCommandSecretTargetIds as typeof import("./command-secret-targets.js").getModelsCommandSecretTargetIds,
getTtsCommandSecretTargetIds:
mocks.getTtsCommandSecretTargetIds as typeof import("./command-secret-targets.js").getTtsCommandSecretTargetIds,
getWebFetchCommandSecretTargets:
mocks.getWebFetchCommandSecretTargets as typeof import("./command-secret-targets.js").getWebFetchCommandSecretTargets,
getWebSearchCommandSecretTargets:
mocks.getWebSearchCommandSecretTargets as typeof import("./command-secret-targets.js").getWebSearchCommandSecretTargets,
vi.mock("../plugins/web-search-providers.runtime.js", () => ({
resolvePluginWebSearchProviders: vi.fn(() => [
{
pluginId: "tavily",
id: "tavily",
credentialPath: "plugins.entries.tavily.config.webSearch.apiKey",
getConfiguredCredentialValue: (config?: {
plugins?: {
entries?: {
tavily?: { config?: { webSearch?: { apiKey?: unknown } } };
};
};
}) => config?.plugins?.entries?.tavily?.config?.webSearch?.apiKey,
getConfiguredCredentialFallback: (): undefined => undefined,
getCredentialValue: (): undefined => undefined,
},
{
pluginId: "firecrawl",
id: "firecrawl",
credentialPath: "plugins.entries.firecrawl.config.webSearch.apiKey",
getConfiguredCredentialValue: (config?: {
plugins?: {
entries?: {
firecrawl?: { config?: { webSearch?: { apiKey?: unknown } } };
};
};
}) => config?.plugins?.entries?.firecrawl?.config?.webSearch?.apiKey,
getConfiguredCredentialFallback: (): undefined => undefined,
getCredentialValue: (): undefined => undefined,
},
{
pluginId: "exa",
id: "exa",
credentialPath: "plugins.entries.exa.config.webSearch.apiKey",
getConfiguredCredentialValue: (config?: {
plugins?: {
entries?: {
exa?: { config?: { webSearch?: { apiKey?: unknown } } };
};
};
}) => config?.plugins?.entries?.exa?.config?.webSearch?.apiKey,
getConfiguredCredentialFallback: (): undefined => undefined,
getCredentialValue: (): undefined => undefined,
},
]),
}));
describe("capability cli", () => {
@@ -349,8 +390,6 @@ describe("capability cli", () => {
mocks.runtime.log.mockClear();
mocks.runtime.error.mockClear();
mocks.runtime.writeJson.mockClear();
mocks.getRuntimeConfigSourceSnapshot.mockReset().mockReturnValue(null);
mocks.setRuntimeConfigSnapshot.mockClear();
mocks.loadModelCatalog
.mockReset()
.mockResolvedValue([{ id: "gpt-5.4", provider: "openai", name: "GPT-5.4" }] as never);
@@ -401,29 +440,6 @@ describe("capability cli", () => {
mocks.registerBuiltInMemoryEmbeddingProviders.mockClear();
mocks.isWebSearchProviderConfigured.mockReset().mockReturnValue(false);
mocks.isWebFetchProviderConfigured.mockReset().mockReturnValue(false);
mocks.resolveCommandConfigWithSecrets
.mockReset()
.mockImplementation(async ({ config }: { config: unknown }) => ({
resolvedConfig: config,
effectiveConfig: config,
diagnostics: [],
}));
mocks.getAgentRuntimeCommandSecretTargetIds
.mockReset()
.mockReturnValue(new Set(["agent-runtime-target"]));
mocks.getMemoryEmbeddingCommandSecretTargetIds
.mockReset()
.mockReturnValue(new Set(["memory-target"]));
mocks.getModelsCommandSecretTargetIds.mockReset().mockReturnValue(new Set(["model-target"]));
mocks.getTtsCommandSecretTargetIds.mockReset().mockReturnValue(new Set(["tts-target"]));
mocks.getWebFetchCommandSecretTargets.mockReset().mockReturnValue({
targetIds: new Set(["web-fetch-target"]),
allowedPaths: new Set(["plugins.entries.firecrawl.config.webFetch.apiKey"]),
});
mocks.getWebSearchCommandSecretTargets.mockReset().mockReturnValue({
targetIds: new Set(["web-search-target"]),
allowedPaths: new Set(["plugins.entries.tavily.config.webSearch.apiKey"]),
});
mocks.modelsStatusCommand.mockClear();
mocks.callGateway.mockImplementation((async ({ method }: { method: string }) => {
if (method === "tts.status") {
@@ -482,7 +498,6 @@ describe("capability cli", () => {
};
type ImageDescribeParams = {
filePath?: string;
mediaUrl?: string;
model?: unknown;
prompt?: unknown;
provider?: unknown;
@@ -561,13 +576,6 @@ describe("capability cli", () => {
return calls[0]?.[0];
}
function firstCommandConfigResolutionCall() {
const calls = mocks.resolveCommandConfigWithSecrets.mock.calls as unknown as Array<
[Record<string, unknown>]
>;
return calls[0]?.[0];
}
function expectModelRunDispatch(transport: "local" | "gateway", modelRef: string) {
if (transport === "gateway") {
const slash = modelRef.indexOf("/");
@@ -1196,26 +1204,6 @@ describe("capability cli", () => {
expect(describeCall?.timeoutMs).toBe(90000);
});
it("keeps image describe URL files as remote media references", async () => {
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: [
"capability",
"image",
"describe",
"--file",
"https://example.com/photo.png",
"--json",
],
});
const describeCall = imageDescribeCall();
expect(describeCall?.filePath).toBe("https://example.com/photo.png");
expect(describeCall?.mediaUrl).toBe("https://example.com/photo.png");
const outputs = firstJsonOutput()?.outputs as Array<Record<string, unknown>>;
expect(outputs[0]?.path).toBe("https://example.com/photo.png");
});
it("uses the explicit media-understanding provider for image describe model overrides", async () => {
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
@@ -2003,35 +1991,6 @@ describe("capability cli", () => {
expect(firstJsonOutput()?.model).toBe("text-embedding-3-small");
});
it("resolves command SecretRefs before local model capability execution", async () => {
const rawConfig = { agents: { defaults: { model: "openai/gpt-5.4" } } };
const resolvedConfig = { agents: { defaults: { model: "openai/gpt-5.4" } }, resolved: true };
const targetIds = new Set(["models.providers.*.apiKey"]);
mocks.loadConfig.mockReturnValue(rawConfig);
mocks.getModelsCommandSecretTargetIds.mockReturnValue(targetIds);
mocks.resolveCommandConfigWithSecrets.mockResolvedValueOnce({
resolvedConfig,
effectiveConfig: resolvedConfig,
diagnostics: [],
} as never);
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: ["capability", "model", "run", "--prompt", "hello", "--json"],
});
expect(firstCommandConfigResolutionCall()).toEqual(
expect.objectContaining({
config: rawConfig,
commandName: "infer model run",
targetIds,
runtime: mocks.runtime,
}),
);
expect(firstPreparedModelParams()?.cfg).toBe(resolvedConfig);
expect(mocks.setRuntimeConfigSnapshot).toHaveBeenCalledWith(resolvedConfig);
});
it("derives the embedding provider from a provider/model override", async () => {
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
@@ -2307,9 +2266,13 @@ describe("capability cli", () => {
argv: ["infer", "web", "search", "--query", "ping", "--json"],
});
const { getCapabilityWebSearchCommandSecretTargets } =
await import("./command-secret-targets.js");
const scopedTargets = getCapabilityWebSearchCommandSecretTargets(unresolvedConfig as never);
expect(mocks.resolveCommandConfigWithSecrets).toHaveBeenCalledWith(
expect.objectContaining({
commandName: "infer web search",
targetIds: scopedTargets.targetIds,
}),
);
expect(webSearchRuntime.runWebSearch).toHaveBeenCalledWith(
@@ -2319,6 +2282,229 @@ describe("capability cli", () => {
);
});
it("uses the infer web search provider override when resolving SecretRefs", async () => {
const unresolvedConfig = {
tools: { web: { search: { provider: "exa", enabled: true } } },
plugins: {
entries: {
firecrawl: {
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" },
},
},
},
exa: {
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: "EXA_API_KEY" },
},
},
},
},
},
};
const resolvedConfig = {
...unresolvedConfig,
plugins: {
entries: {
...unresolvedConfig.plugins.entries,
firecrawl: {
config: {
webSearch: {
apiKey: "resolved-firecrawl-key",
},
},
},
},
},
};
mocks.loadConfig.mockReturnValue(unresolvedConfig);
mocks.resolveCommandConfigWithSecrets.mockResolvedValueOnce({
resolvedConfig,
effectiveConfig: resolvedConfig,
diagnostics: [],
});
const webSearchRuntime = await import("../web-search/runtime.js");
vi.mocked(webSearchRuntime.runWebSearch).mockResolvedValueOnce({
provider: "firecrawl",
result: { results: [] },
} as never);
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: ["infer", "web", "search", "--query", "ping", "--provider", "firecrawl", "--json"],
});
const { getCapabilityWebSearchCommandSecretTargets } =
await import("./command-secret-targets.js");
const scopedTargets = getCapabilityWebSearchCommandSecretTargets(unresolvedConfig as never, {
providerId: "firecrawl",
});
const configResolutionCall = mocks.resolveCommandConfigWithSecrets.mock.calls.at(-1)?.[0];
expect(configResolutionCall).toEqual(
expect.objectContaining({
commandName: "infer web search",
targetIds: scopedTargets.targetIds,
forcedActivePaths: scopedTargets.forcedActivePaths,
}),
);
expect(configResolutionCall).not.toHaveProperty("allowedPaths");
expect(webSearchRuntime.runWebSearch).toHaveBeenCalledWith(
expect.objectContaining({
config: resolvedConfig,
providerId: "firecrawl",
}),
);
});
it("resolves only plugin web fetch SecretRefs before running infer web fetch", async () => {
const unresolvedConfig = {
tools: { web: { fetch: { provider: "firecrawl", enabled: true } } },
plugins: {
entries: {
exa: {
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: "EXA_API_KEY" },
},
},
},
firecrawl: {
config: {
webFetch: {
apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" },
},
},
},
},
},
};
const resolvedConfig = {
...unresolvedConfig,
plugins: {
entries: {
...unresolvedConfig.plugins.entries,
firecrawl: {
config: {
webFetch: {
apiKey: "resolved-firecrawl-key",
},
},
},
},
},
};
mocks.loadConfig.mockReturnValue(unresolvedConfig);
mocks.resolveCommandConfigWithSecrets.mockResolvedValueOnce({
resolvedConfig,
effectiveConfig: resolvedConfig,
diagnostics: [],
});
const webFetchRuntime = await import("../web-fetch/runtime.js");
vi.mocked(webFetchRuntime.resolveWebFetchDefinition).mockReturnValueOnce({
provider: { id: "firecrawl" },
definition: { execute: vi.fn(async () => ({ content: "ok" })) },
} as never);
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: ["infer", "web", "fetch", "--url", "https://example.com", "--json"],
});
const { getCapabilityWebFetchCommandSecretTargets } =
await import("./command-secret-targets.js");
expect(mocks.resolveCommandConfigWithSecrets).toHaveBeenCalledWith(
expect.objectContaining({
commandName: "infer web fetch",
targetIds: getCapabilityWebFetchCommandSecretTargets(unresolvedConfig as never).targetIds,
}),
);
expect(webFetchRuntime.resolveWebFetchDefinition).toHaveBeenCalledWith(
expect.objectContaining({
config: resolvedConfig,
}),
);
});
it("uses the infer web fetch provider override when resolving fallback SecretRefs", async () => {
const fallbackRef = { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" };
const unresolvedConfig = {
tools: { web: { fetch: { enabled: true } } },
plugins: {
entries: {
firecrawl: {
config: {
webSearch: {
apiKey: fallbackRef,
},
},
},
},
},
};
const resolvedConfig = {
...unresolvedConfig,
plugins: {
entries: {
firecrawl: {
config: {
webSearch: {
apiKey: "resolved-firecrawl-key",
},
},
},
},
},
};
mocks.loadConfig.mockReturnValue(unresolvedConfig);
mocks.resolveCommandConfigWithSecrets.mockResolvedValueOnce({
resolvedConfig,
effectiveConfig: resolvedConfig,
diagnostics: [],
});
const webFetchRuntime = await import("../web-fetch/runtime.js");
vi.mocked(webFetchRuntime.resolveWebFetchDefinition).mockReturnValueOnce({
provider: { id: "firecrawl" },
definition: { execute: vi.fn(async () => ({ content: "ok" })) },
} as never);
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: [
"infer",
"web",
"fetch",
"--url",
"https://example.com",
"--provider",
"firecrawl",
"--json",
],
});
const { getCapabilityWebFetchCommandSecretTargets } =
await import("./command-secret-targets.js");
const scopedTargets = getCapabilityWebFetchCommandSecretTargets(unresolvedConfig as never, {
providerId: "firecrawl",
});
const configResolutionCall = mocks.resolveCommandConfigWithSecrets.mock.calls.at(-1)?.[0];
expect(configResolutionCall).toEqual(
expect.objectContaining({
commandName: "infer web fetch",
targetIds: scopedTargets.targetIds,
forcedActivePaths: scopedTargets.forcedActivePaths,
}),
);
expect(configResolutionCall).not.toHaveProperty("allowedPaths");
expect(webFetchRuntime.resolveWebFetchDefinition).toHaveBeenCalledWith(
expect.objectContaining({
config: resolvedConfig,
providerId: "firecrawl",
}),
);
});
it("surfaces available, configured, and selected for web providers", async () => {
mocks.loadConfig.mockReturnValue({
tools: {
@@ -2374,161 +2560,6 @@ describe("capability cli", () => {
});
});
it("resolves command SecretRefs before local web search execution", async () => {
const rawConfig = {
tools: { web: { search: { provider: "brave" } } },
plugins: {
entries: {
tavily: {
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: "TAVILY_API_KEY" },
},
},
},
},
},
};
const resolvedConfig = {
...rawConfig,
tools: { web: { search: { provider: "tavily" } } },
plugins: {
entries: {
tavily: { config: { webSearch: { apiKey: "resolved-tavily-key" } } },
},
},
};
const targetIds = new Set(["plugins.entries.tavily.config.webSearch.apiKey"]);
const allowedPaths = new Set(["plugins.entries.tavily.config.webSearch.apiKey"]);
mocks.loadConfig.mockReturnValue(rawConfig);
mocks.getWebSearchCommandSecretTargets.mockReturnValue({
targetIds,
allowedPaths,
});
mocks.resolveCommandConfigWithSecrets.mockResolvedValueOnce({
resolvedConfig,
effectiveConfig: resolvedConfig,
diagnostics: [],
} as never);
const webSearchRuntime = await import("../web-search/runtime.js");
vi.mocked(webSearchRuntime.runWebSearch).mockResolvedValueOnce({
provider: "tavily",
result: { results: [] },
} as never);
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: ["capability", "web", "search", "--provider", "tavily", "--query", "ping", "--json"],
});
expect(firstCommandConfigResolutionCall()).toEqual(
expect.objectContaining({
commandName: "infer web search",
targetIds,
allowedPaths,
providerOverrides: { webSearch: "tavily" },
runtime: mocks.runtime,
}),
);
expect(firstCommandConfigResolutionCall()?.config).toEqual(
expect.objectContaining({
tools: { web: { search: { provider: "tavily" } } },
}),
);
expect(rawConfig.tools.web.search.provider).toBe("brave");
expect(vi.mocked(webSearchRuntime.runWebSearch).mock.calls[0]?.[0]).toEqual(
expect.objectContaining({
config: resolvedConfig,
preferInputConfig: true,
providerId: "tavily",
}),
);
});
it("resolves command SecretRefs before local web fetch execution", async () => {
const rawConfig = {
tools: { web: { fetch: { provider: "browser" } } },
plugins: {
entries: {
firecrawl: {
config: {
webFetch: {
apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" },
},
},
},
},
},
};
const resolvedConfig = {
...rawConfig,
tools: { web: { fetch: { provider: "firecrawl" } } },
plugins: {
entries: {
firecrawl: { config: { webFetch: { apiKey: "resolved-firecrawl-key" } } },
},
},
};
const targetIds = new Set(["plugins.entries.firecrawl.config.webFetch.apiKey"]);
const allowedPaths = new Set(["plugins.entries.firecrawl.config.webFetch.apiKey"]);
mocks.loadConfig.mockReturnValue(rawConfig);
mocks.getWebFetchCommandSecretTargets.mockReturnValue({
targetIds,
allowedPaths,
});
mocks.resolveCommandConfigWithSecrets.mockResolvedValueOnce({
resolvedConfig,
effectiveConfig: resolvedConfig,
diagnostics: [],
} as never);
const webFetchRuntime = await import("../web-fetch/runtime.js");
const execute = vi.fn(async () => ({ text: "ok" }));
vi.mocked(webFetchRuntime.resolveWebFetchDefinition).mockReturnValueOnce({
provider: { id: "firecrawl" },
definition: { execute },
} as never);
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: [
"capability",
"web",
"fetch",
"--provider",
"firecrawl",
"--url",
"https://example.com",
"--json",
],
});
expect(firstCommandConfigResolutionCall()).toEqual(
expect.objectContaining({
commandName: "infer web fetch",
targetIds,
allowedPaths,
providerOverrides: { webFetch: "firecrawl" },
runtime: mocks.runtime,
}),
);
expect(firstCommandConfigResolutionCall()?.config).toEqual(
expect.objectContaining({
tools: { web: { fetch: { provider: "firecrawl" } } },
}),
);
expect(rawConfig.tools.web.fetch.provider).toBe("browser");
expect(vi.mocked(webFetchRuntime.resolveWebFetchDefinition).mock.calls[0]?.[0]).toEqual(
expect.objectContaining({
config: resolvedConfig,
providerId: "firecrawl",
}),
);
expect(execute).toHaveBeenCalledWith({
url: "https://example.com",
format: undefined,
});
});
it("surfaces selected and configured embedding provider state", async () => {
mocks.loadConfig.mockReturnValue({});
mocks.resolveMemorySearchConfig.mockReturnValue({