import path from "node:path"; import type { Command } from "commander"; import { resolveAgentDir } from "../../agents/agent-scope.js"; import { inspectLocalAudioSelection } from "../../media-understanding/local-audio.js"; import { buildMediaUnderstandingRegistry } from "../../media-understanding/provider-registry.js"; import { transcribeAudioFile } from "../../media-understanding/runtime.js"; import { defaultRuntime } from "../../runtime.js"; import { getProviderEnvVars } from "../../secrets/provider-env-vars.js"; import { runCommandWithRuntime } from "../cli-utils.js"; import { getModelsCommandSecretTargetIds } from "../command-secret-targets.js"; import { prepareLocalCapabilityAccountSecrets } from "./local-account-secrets.js"; import { isMissingMediaUnderstandingProvider } from "./media-understanding-result.js"; import type { CapabilityEnvelope } from "./metadata.js"; import { emitJsonOrText, formatEnvelopeForText, providerSummaryText } from "./output.js"; import { providerHasGenericConfig, registerLocalProvidersCommand, requireProviderModelOverride, resolveCapabilityAgentOption, resolveCapabilityProviderAgentId, resolveLocalCapabilityRuntimeConfig, } from "./shared.js"; async function runAudioTranscribe(params: { file: string; language?: string; model?: string; prompt?: string; agent?: string; }) { const cfg = await resolveLocalCapabilityRuntimeConfig({ commandName: "infer audio transcribe", targetIds: getModelsCommandSecretTargetIds(), }); const agentId = resolveCapabilityProviderAgentId(cfg, params.agent, "infer audio transcribe"); await prepareLocalCapabilityAccountSecrets({ cfg, agentId }); const result = await transcribeAudioFile({ agentDir: resolveAgentDir(cfg, agentId), activeModel: requireProviderModelOverride(params.model), filePath: path.resolve(params.file), cfg, agentId, language: params.language, prompt: params.prompt, }); if (!result.text) { if (isMissingMediaUnderstandingProvider(result)) { throw new Error( "No audio transcription provider is configured or ready. Configure an audio-capable tools.media.models entry, or pass --model after configuring that provider's auth/API key.", ); } throw new Error(`No transcript returned for audio: ${path.resolve(params.file)}`); } return { ok: true, capability: "audio.transcribe", transport: "local" as const, provider: result.provider, model: result.model, attempts: [], outputs: [{ path: path.resolve(params.file), text: result.text, kind: "audio.transcription" }], } satisfies CapabilityEnvelope; } export function registerAudioCapabilityCommands(capability: Command): void { const audio = capability .command("audio") .description("Audio transcription") .option("--agent ", "Agent whose model and auth state should be used"); audio .command("transcribe") .description("Transcribe one audio file") .requiredOption("--file ", "Audio file") .option("--agent ", "Agent whose model and auth state should be used") .option("--language ", "Language hint") .option("--prompt ", "Prompt hint") .option("--model ", "Model override") .option("--json", "Output JSON", false) .action(async (opts, command) => { await runCommandWithRuntime(defaultRuntime, async () => { const result = await runAudioTranscribe({ file: String(opts.file), agent: resolveCapabilityAgentOption(command, opts.agent), language: opts.language as string | undefined, model: opts.model as string | undefined, prompt: opts.prompt as string | undefined, }); emitJsonOrText(defaultRuntime, Boolean(opts.json), result, formatEnvelopeForText); }); }); registerLocalProvidersCommand( audio, "List audio transcription providers", async (cfg, agentId) => { const remoteProviders = [...buildMediaUnderstandingRegistry(undefined, cfg).values()] .filter((provider) => provider.capabilities?.includes("audio")) .map((provider) => ({ available: true, configured: providerHasGenericConfig({ cfg, providerId: provider.id, agentId, envVars: getProviderEnvVars(provider.id, { config: cfg, includeUntrustedWorkspacePlugins: false, }), }), selected: false, id: provider.id, capabilities: provider.capabilities, defaultModels: provider.defaultModels, })); const localSelection = await inspectLocalAudioSelection(); const localProviders = localSelection.candidates .filter((candidate) => candidate.available) .map((candidate) => Object.assign( { available: candidate.available, configured: candidate.ready, selected: false, localFallbackSelected: candidate.selected, id: `local/${candidate.id}`, transport: "local-cli", command: candidate.command, observedBackend: candidate.observedBackend ?? "unknown", evidence: candidate.evidence, }, candidate.capableBackend ? { capableBackend: candidate.capableBackend } : {}, candidate.requestedBackend ? { requestedBackend: candidate.requestedBackend } : {}, candidate.reason ? { reason: candidate.reason } : {}, ), ); return [...remoteProviders, ...localProviders]; }, providerSummaryText, ); }