fix: resolve tts secret refs for local infer (#72549)

This commit is contained in:
Josh Avant
2026-04-26 22:31:39 -05:00
committed by GitHub
parent 6a05b9eec5
commit 4878d3e059
5 changed files with 97 additions and 2 deletions

View File

@@ -97,6 +97,13 @@ const mocks = vi.hoisted(() => ({
: {}),
}),
),
resolveCommandSecretRefsViaGateway: vi.fn(async ({ config }: { config: unknown }) => ({
resolvedConfig: config,
diagnostics: [],
targetStatesByPath: {},
hadUnresolvedTargets: false,
})),
getTtsCommandSecretTargetIds: vi.fn(() => new Set(["messages.tts.providers.*.apiKey"])),
createEmbeddingProvider: vi.fn(async () => ({
provider: {
id: "openai",
@@ -188,6 +195,14 @@ vi.mock("../gateway/connection-details.js", () => ({
})),
}));
vi.mock("./command-secret-gateway.js", () => ({
resolveCommandSecretRefsViaGateway: mocks.resolveCommandSecretRefsViaGateway,
}));
vi.mock("./command-secret-targets.js", () => ({
getTtsCommandSecretTargetIds: mocks.getTtsCommandSecretTargetIds,
}));
vi.mock("../media-understanding/runtime.js", () => ({
describeImageFile:
mocks.describeImageFile as typeof import("../media-understanding/runtime.js").describeImageFile,
@@ -311,6 +326,15 @@ describe("capability cli", () => {
mocks.generateVideo.mockReset();
mocks.transcribeAudioFile.mockClear();
mocks.textToSpeech.mockClear();
mocks.resolveCommandSecretRefsViaGateway
.mockReset()
.mockImplementation(async ({ config }: { config: unknown }) => ({
resolvedConfig: config,
diagnostics: [],
targetStatesByPath: {},
hadUnresolvedTargets: false,
}));
mocks.getTtsCommandSecretTargetIds.mockClear();
mocks.setTtsProvider.mockClear();
mocks.resolveExplicitTtsOverrides.mockClear();
mocks.buildMediaUnderstandingRegistry.mockReset().mockReturnValue(new Map());
@@ -1057,6 +1081,58 @@ describe("capability cli", () => {
expect(mocks.setTtsProvider).not.toHaveBeenCalled();
});
it("resolves static TTS SecretRefs before local conversion", async () => {
const sourceConfig = {
messages: {
tts: {
providers: {
minimax: {
apiKey: { source: "exec", provider: "mockexec", id: "minimax/tts/apiKey" },
},
},
},
},
};
const resolvedConfig = {
messages: {
tts: {
providers: {
minimax: {
apiKey: "resolved-minimax-key",
},
},
},
},
};
mocks.loadConfig.mockReturnValueOnce(sourceConfig);
mocks.resolveCommandSecretRefsViaGateway.mockResolvedValueOnce({
resolvedConfig,
diagnostics: [],
targetStatesByPath: {
"messages.tts.providers.minimax.apiKey": "resolved_local",
},
hadUnresolvedTargets: false,
});
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,
argv: ["capability", "tts", "convert", "--text", "hello", "--json"],
});
expect(mocks.resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith({
config: sourceConfig,
commandName: "infer tts convert",
targetIds: new Set(["messages.tts.providers.*.apiKey"]),
mode: "enforce_resolved",
});
expect(mocks.resolveExplicitTtsOverrides).toHaveBeenCalledWith(
expect.objectContaining({ cfg: resolvedConfig }),
);
expect(mocks.textToSpeech).toHaveBeenCalledWith(
expect.objectContaining({ cfg: resolvedConfig }),
);
});
it("disables TTS fallback when explicit provider or voice/model selection is requested", async () => {
await runRegisteredCli({
register: registerCapabilityCli as (program: Command) => void,

View File

@@ -79,6 +79,8 @@ import {
runWebSearch,
} from "../web-search/runtime.js";
import { runCommandWithRuntime } from "./cli-utils.js";
import { resolveCommandSecretRefsViaGateway } from "./command-secret-gateway.js";
import { getTtsCommandSecretTargetIds } from "./command-secret-targets.js";
import { createDefaultDeps } from "./deps.js";
import { removeCommandByName } from "./program/command-tree.js";
import { collectOption } from "./program/helpers.js";
@@ -1111,7 +1113,12 @@ async function runTtsConvert(params: {
} satisfies CapabilityEnvelope;
}
const cfg = loadConfig();
const { resolvedConfig: cfg } = await resolveCommandSecretRefsViaGateway({
config: loadConfig(),
commandName: "infer tts convert",
targetIds: getTtsCommandSecretTargetIds(),
mode: "enforce_resolved",
});
const overrides = resolveExplicitTtsOverrides({
cfg,
provider: params.provider,

View File

@@ -4,6 +4,7 @@ import { readCommandSource } from "./command-source.test-helpers.js";
const SECRET_TARGET_CALLSITES = [
bundledPluginFile("memory-core", "src/cli.runtime.ts"),
"src/cli/capability-cli.ts",
"src/cli/qr-cli.ts",
"src/agents/agent-runtime-config.ts",
"src/commands/agent.ts",

View File

@@ -58,6 +58,7 @@ import {
getQrRemoteCommandSecretTargetIds,
getScopedChannelsCommandSecretTargets,
getSecurityAuditCommandSecretTargetIds,
getTtsCommandSecretTargetIds,
} from "./command-secret-targets.js";
describe("command secret target ids", () => {
@@ -73,6 +74,11 @@ describe("command secret target ids", () => {
expect(ids.has("channels.discord.token")).toBe(false);
});
it("keeps static TTS targets out of the registry path", () => {
const ids = getTtsCommandSecretTargetIds();
expect(ids).toEqual(new Set(["messages.tts.providers.*.apiKey"]));
});
it("includes memorySearch remote targets for agent runtime commands", () => {
const ids = getAgentRuntimeCommandSecretTargetIds();
expect(ids.has("agents.defaults.memorySearch.remote.apiKey")).toBe(true);

View File

@@ -23,12 +23,13 @@ const STATIC_MODEL_TARGET_IDS = [
"models.providers.*.request.tls.key",
"models.providers.*.request.tls.passphrase",
] as const;
const STATIC_TTS_TARGET_IDS = ["messages.tts.providers.*.apiKey"] as const;
const STATIC_AGENT_RUNTIME_BASE_TARGET_IDS = [
...STATIC_MODEL_TARGET_IDS,
"agents.defaults.memorySearch.remote.apiKey",
"agents.list[].memorySearch.remote.apiKey",
"agents.list[].tts.providers.*.apiKey",
"messages.tts.providers.*.apiKey",
...STATIC_TTS_TARGET_IDS,
"skills.entries.*.apiKey",
"tools.web.search.apiKey",
] as const;
@@ -221,6 +222,10 @@ export function getModelsCommandSecretTargetIds(): Set<string> {
return toTargetIdSet(STATIC_MODEL_TARGET_IDS);
}
export function getTtsCommandSecretTargetIds(): Set<string> {
return toTargetIdSet(STATIC_TTS_TARGET_IDS);
}
export function getAgentRuntimeCommandSecretTargetIds(params?: {
includeChannelTargets?: boolean;
}): Set<string> {