{"version":3,"file":"speech.mjs","names":[],"sources":["../../../../../../../ai/src/speech/speech.ts"],"sourcesContent":["import type {\n  GeneratedAudio,\n  SpeechModelContract,\n  SpeechModelPricing,\n} from \"../contracts/speech-model.contract\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport { REPORT_SCHEMA_VERSION } from \"../contracts/result/base-report.type\";\nimport type { ExecuteResult } from \"../contracts/result/execute-result.type\";\nimport type { ModelPricing } from \"../contracts/result/model-pricing.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport { AIError } from \"../errors/ai-error\";\nimport { ProviderError } from \"../errors/provider-error\";\nimport type { FlowObserveOption } from \"../observe/resolve-observers\";\nimport { notifyObservers } from \"../observe/resolve-observers\";\nimport { computeCost } from \"../utils/compute-cost\";\nimport { generateRunId } from \"../utils/generate-run-id\";\nimport { stampReportLineage } from \"../utils/stamp-report-lineage\";\n\n/** Parameters for {@link speech}. `model` comes from `sdk.speech({ name })`. */\nexport type SpeechParams = {\n  /** The TTS model to synthesize with. */\n  model: SpeechModelContract;\n  /** The text to speak. */\n  text: string;\n  /** Voice id/name; overrides the model's default voice. */\n  voice?: string;\n  /** Output container (`\"mp3\"` / `\"opus\"` / `\"aac\"` / `\"flac\"` / `\"wav\"` / `\"pcm\"`). */\n  format?: string;\n  /** Playback speed multiplier. */\n  speed?: number;\n  /** Extra tone/delivery steering (model-dependent). */\n  instructions?: string;\n  /** Cancellation handle. */\n  signal?: AbortSignal;\n  /** Observability routing — same `observe` seam as agents. */\n  observe?: FlowObserveOption;\n  /** Groups this call into a session for flat cost/trace queries. */\n  sessionId?: string;\n  /** Report node name (defaults to `\"speech\"`). */\n  name?: string;\n  /** Provider-specific options forwarded verbatim to the adapter. */\n  options?: Record<string, unknown>;\n};\n\n/** Success payload of a {@link speech} run. */\nexport type SpeechData = {\n  /** The synthesized audio, normalized to the discriminated shape. */\n  audio: GeneratedAudio;\n};\n\n/** The report node a {@link speech} run produces (`type: \"speech\"`). */\nexport type SpeechReport = BaseReport & {\n  type: \"speech\";\n  /** Identity of the TTS model this run used. */\n  model: { name: string; provider: string };\n  /** Number of input characters synthesized (0 on failure). */\n  characters: number;\n};\n\n/** Result envelope of {@link speech} — the uniform `{ data, error, usage, report }`. */\nexport type SpeechResult = ExecuteResult<SpeechData> & {\n  type: \"speech\";\n  report: SpeechReport;\n};\n\n/**\n * Synthesize speech from text — the text-to-speech verb of the\n * output-modality track (Theme I), sibling to `ai.image()`. Wraps a\n * {@link SpeechModelContract} (from `openai.speech(...)`) in the\n * framework's uniform result contract:\n *\n * - **Never throws.** Provider failures surface as a typed `AIError` on\n *   `result.error`; `result.data` is then `undefined`.\n * - **Cost-truth.** `result.usage.cost` is filled per-character\n *   (`tts-1`) or per-token (`gpt-4o-mini-tts`), folding into the same\n *   `Usage.cost` rollup as text.\n * - **Observable.** The completed {@link SpeechReport} routes to any\n *   registered `Observer` (panoptic, OTel, …) via the `observe` seam.\n *\n * @example\n * const openai = new OpenAISDK({ apiKey });\n * const { data, error } = await ai.speech({\n *   model: openai.speech({ name: \"tts-1\", voice: \"alloy\" }),\n *   text: \"Your order has shipped.\",\n *   format: \"mp3\",\n * });\n * if (!error) await fs.writeFile(\"ship.mp3\", Buffer.from(data.audio.base64, \"base64\"));\n */\nexport async function speech(params: SpeechParams): Promise<SpeechResult> {\n  const { model, text } = params;\n\n  const runId = generateRunId(\"speech\");\n  const startedAt = new Date().toISOString();\n  const startPerf = performance.now();\n\n  const usage: Usage = { input: 0, output: 0, total: 0 };\n  let data: SpeechData | undefined;\n  let error: AIError | undefined;\n  let status: SpeechReport[\"status\"] = \"completed\";\n  let characters = 0;\n\n  try {\n    const response = await model.generate(text, {\n      voice: params.voice,\n      format: params.format,\n      speed: params.speed,\n      instructions: params.instructions,\n      signal: params.signal,\n      ...params.options,\n    });\n\n    Object.assign(usage, response.usage);\n    characters = response.characters;\n\n    if (usage.cost === undefined) {\n      const cost = computeSpeechCost(usage, characters, model.pricing);\n      if (cost !== undefined) {\n        usage.cost = cost;\n      }\n    }\n\n    data = { audio: response.audio };\n  } catch (thrown) {\n    error =\n      thrown instanceof AIError ? thrown : new ProviderError(toMessage(thrown), { cause: thrown });\n    status = params.signal?.aborted ? \"cancelled\" : \"failed\";\n  }\n\n  const report: SpeechReport = {\n    runId,\n    rootRunId: runId,\n    name: params.name ?? \"speech\",\n    type: \"speech\",\n    status,\n    error,\n    startedAt,\n    endedAt: new Date().toISOString(),\n    duration: performance.now() - startPerf,\n    usage,\n    children: [],\n    model: { name: model.name, provider: model.provider },\n    characters,\n    reportSchemaVersion: REPORT_SCHEMA_VERSION,\n  };\n\n  stampReportLineage(report, { rootRunId: runId, sessionId: params.sessionId });\n\n  await notifyObservers(params.observe, report);\n\n  return { type: \"speech\", data, error, usage, report };\n}\n\n/**\n * Price a TTS run: `perMillionCharacters × characters` (per-character\n * metering, attributed to `cost.input`) wins when configured, otherwise\n * the standard token math. Returns `undefined` when no usable pricing\n * is present.\n */\nfunction computeSpeechCost(\n  usage: Usage,\n  characters: number,\n  pricing: SpeechModelPricing | undefined,\n): ModelPricing | undefined {\n  if (!pricing) {\n    return undefined;\n  }\n\n  if (pricing.perMillionCharacters !== undefined) {\n    return { input: (characters * pricing.perMillionCharacters) / 1_000_000, output: 0 };\n  }\n\n  if (pricing.input !== undefined && pricing.output !== undefined) {\n    return computeCost(usage, { input: pricing.input, output: pricing.output });\n  }\n\n  return undefined;\n}\n\n/** Best-effort message for a non-`AIError` thrown value. */\nfunction toMessage(thrown: unknown): string {\n  return thrown instanceof Error ? thrown.message : String(thrown);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,eAAsB,OAAO,QAA6C;CACxE,MAAM,EAAE,OAAO,SAAS;CAExB,MAAM,QAAQ,cAAc,QAAQ;CACpC,MAAM,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY;CACzC,MAAM,YAAY,YAAY,IAAI;CAElC,MAAM,QAAe;EAAE,OAAO;EAAG,QAAQ;EAAG,OAAO;CAAE;CACrD,IAAI;CACJ,IAAI;CACJ,IAAI,SAAiC;CACrC,IAAI,aAAa;CAEjB,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,SAAS,MAAM;GAC1C,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,OAAO,OAAO;GACd,cAAc,OAAO;GACrB,QAAQ,OAAO;GACf,GAAG,OAAO;EACZ,CAAC;EAED,OAAO,OAAO,OAAO,SAAS,KAAK;EACnC,aAAa,SAAS;EAEtB,IAAI,MAAM,SAAS,QAAW;GAC5B,MAAM,OAAO,kBAAkB,OAAO,YAAY,MAAM,OAAO;GAC/D,IAAI,SAAS,QACX,MAAM,OAAO;EAEjB;EAEA,OAAO,EAAE,OAAO,SAAS,MAAM;CACjC,SAAS,QAAQ;EACf,QACE,kBAAkB,UAAU,SAAS,IAAI,cAAc,UAAU,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;EAC7F,SAAS,OAAO,QAAQ,UAAU,cAAc;CAClD;CAEA,MAAM,SAAuB;EAC3B;EACA,WAAW;EACX,MAAM,OAAO,QAAQ;EACrB,MAAM;EACN;EACA;EACA;EACA,0BAAS,IAAI,KAAK,EAAC,CAAC,YAAY;EAChC,UAAU,YAAY,IAAI,IAAI;EAC9B;EACA,UAAU,CAAC;EACX,OAAO;GAAE,MAAM,MAAM;GAAM,UAAU,MAAM;EAAS;EACpD;EACA;CACF;CAEA,mBAAmB,QAAQ;EAAE,WAAW;EAAO,WAAW,OAAO;CAAU,CAAC;CAE5E,MAAM,gBAAgB,OAAO,SAAS,MAAM;CAE5C,OAAO;EAAE,MAAM;EAAU;EAAM;EAAO;EAAO;CAAO;AACtD;;;;;;;AAQA,SAAS,kBACP,OACA,YACA,SAC0B;CAC1B,IAAI,CAAC,SACH;CAGF,IAAI,QAAQ,yBAAyB,QACnC,OAAO;EAAE,OAAQ,aAAa,QAAQ,uBAAwB;EAAW,QAAQ;CAAE;CAGrF,IAAI,QAAQ,UAAU,UAAa,QAAQ,WAAW,QACpD,OAAO,YAAY,OAAO;EAAE,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO,CAAC;AAI9E;;AAGA,SAAS,UAAU,QAAyB;CAC1C,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AACjE"}