@hiper2d/ai-agents 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1756,8 +1756,9 @@ function extractTokenUsageFromResponse8(response) {
1756
1756
  var VOICE_MODEL_CONSTANTS = {
1757
1757
  OPENAI_TTS: "gpt-4o-mini-tts",
1758
1758
  OPENAI_STT: "whisper-1",
1759
- // ai.google.dev/gemini-api/docs/speech-generation
1760
- GOOGLE_TTS: "gemini-3.1-flash-tts-preview",
1759
+ // ai.google.dev/gemini-api/docs/speech-generation — Google's named replacement
1760
+ // for gemini-3.1-flash-tts-preview (2026-09-24: ~6 s vs ~15 s per line, ~4x cheaper)
1761
+ GOOGLE_TTS: "gemini-3.8-flash-lite-tts",
1761
1762
  // ai.google.dev/gemini-api/docs/transcribe — Interactions API only (see google-stt.ts)
1762
1763
  GOOGLE_STT: "gemini-3.5-transcribe"
1763
1764
  };
@@ -1769,9 +1770,9 @@ var VOICE_PROVIDER_API_KEY = {
1769
1770
  var VOICE_MODEL_PRICING = {
1770
1771
  [VOICE_MODEL_CONSTANTS.OPENAI_TTS]: { pricePerMillionCharacters: 15 },
1771
1772
  [VOICE_MODEL_CONSTANTS.OPENAI_STT]: { pricePerMinute: 6e-3 },
1772
- // Measured 2026-09-05: ~32 audio tokens per second of speech, so a 15-second
1773
- // line is ~$0.01 — about 3-5x an OpenAI line of the same length.
1774
- [VOICE_MODEL_CONSTANTS.GOOGLE_TTS]: { textInputPricePerM: 1, audioOutputPricePerM: 20 },
1773
+ // ~32 audio tokens per second of speech, so a 15-second line is ~$0.003.
1774
+ // Google doubles both rates to $1 / $12 on 2027-01-01 — update then.
1775
+ [VOICE_MODEL_CONSTANTS.GOOGLE_TTS]: { textInputPricePerM: 0.5, audioOutputPricePerM: 6 },
1775
1776
  // ~25 audio tokens per second in, ~175 text tokens per minute out: ≈ $0.005/min.
1776
1777
  [VOICE_MODEL_CONSTANTS.GOOGLE_STT]: { audioInputPricePerM: 2, textOutputPricePerM: 12 }
1777
1778
  };
@@ -1878,12 +1879,10 @@ import { GoogleGenAI } from "@google/genai";
1878
1879
  var AUDIO_TOKENS_PER_SECOND = 32;
1879
1880
  var SAMPLE_RATE = 24e3;
1880
1881
  var PCM_BYTES_PER_SECOND = SAMPLE_RATE * 2;
1881
- function buildGoogleTtsPrompt(text, voiceStyle) {
1882
+ var WAV_HEADER_BYTES = 44;
1883
+ function normalizeTtsStyle(voiceStyle) {
1882
1884
  const style = voiceStyle?.trim().replace(/[:.!,;\s]+$/, "");
1883
- if (!style) return text;
1884
- const isShort = style.split(/\s+/).length <= 3 && !/[.!?,;]/.test(style);
1885
- return isShort ? `Say ${style}: ${text}` : `${style}:
1886
- ${text}`;
1885
+ return style || void 0;
1887
1886
  }
1888
1887
  function pcmToWav(pcmData, sampleRate = SAMPLE_RATE) {
1889
1888
  const numChannels = 1;
@@ -1927,28 +1926,51 @@ function describeEmptyTtsResponse(response) {
1927
1926
  if (!parts.length) bits.push("no parts");
1928
1927
  return bits.join(", ");
1929
1928
  }
1929
+ function isSafetyBlocked(response) {
1930
+ const finish = response?.candidates?.[0]?.finishReason;
1931
+ return finish === "SAFETY" || finish === "PROHIBITED_CONTENT" || !!response?.promptFeedback?.blockReason;
1932
+ }
1933
+ function findAudioPart(response) {
1934
+ const parts = response?.candidates?.[0]?.content?.parts ?? [];
1935
+ const inline = parts.find((part) => part.inlineData?.mimeType?.startsWith("audio/") && part.inlineData?.data)?.inlineData;
1936
+ return inline ? { data: inline.data, mimeType: inline.mimeType } : void 0;
1937
+ }
1930
1938
  async function generateGoogleTtsAudio(text, apiKey, options) {
1931
1939
  const client = new GoogleGenAI({ apiKey });
1932
- const response = await client.models.generateContent({
1940
+ const request = (style2) => client.models.generateContent({
1933
1941
  model: VOICE_MODEL_CONSTANTS.GOOGLE_TTS,
1934
- contents: [{ parts: [{ text: buildGoogleTtsPrompt(text, options.voiceStyle) }] }],
1942
+ contents: [{ parts: [style2 ? { text, speechMetadata: { style: style2 } } : { text }] }],
1935
1943
  config: {
1936
1944
  responseModalities: ["AUDIO"],
1937
1945
  speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: options.voiceName } } }
1938
1946
  }
1939
1947
  });
1940
- const candidate = response.candidates?.[0];
1941
- const parts = candidate?.content?.parts ?? [];
1942
- const audioPart = parts.find((part) => part.inlineData?.mimeType?.startsWith("audio/"));
1943
- if (!audioPart?.inlineData?.data) {
1944
- throw new Error(`No audio data in Google TTS response (${describeEmptyTtsResponse(response)})`);
1945
- }
1946
- const pcmData = new Uint8Array(Buffer.from(audioPart.inlineData.data, "base64"));
1948
+ const style = normalizeTtsStyle(options.voiceStyle);
1949
+ let response = await request(style);
1950
+ let styleDropped = false;
1951
+ let blockedInputTokens = 0;
1952
+ if (!findAudioPart(response) && style && isSafetyBlocked(response)) {
1953
+ blockedInputTokens = response?.usageMetadata?.promptTokenCount ?? 0;
1954
+ response = await request(void 0);
1955
+ styleDropped = true;
1956
+ }
1957
+ const audioPart = findAudioPart(response);
1958
+ if (!audioPart) {
1959
+ throw new Error(`No audio data in Google TTS response (${describeEmptyTtsResponse(response)}${styleDropped ? ", also without the style" : ""})`);
1960
+ }
1961
+ const bytes = new Uint8Array(Buffer.from(audioPart.data, "base64"));
1962
+ const isWav = /^audio\/(x-)?wav/i.test(audioPart.mimeType);
1963
+ const audio = isWav ? bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) : pcmToWav(bytes);
1964
+ const pcmBytes = isWav ? Math.max(0, bytes.length - WAV_HEADER_BYTES) : bytes.length;
1947
1965
  const usageMetadata = response.usageMetadata ?? {};
1948
- const inputTokens = usageMetadata.promptTokenCount ?? 0;
1966
+ const inputTokens = (usageMetadata.promptTokenCount ?? 0) + blockedInputTokens;
1949
1967
  const reportedOutput = usageMetadata.candidatesTokenCount;
1950
- const outputTokens = reportedOutput && reportedOutput > 0 ? reportedOutput : Math.ceil(pcmData.length / PCM_BYTES_PER_SECOND * AUDIO_TOKENS_PER_SECOND);
1951
- return { audio: pcmToWav(pcmData), usage: { inputTokens, outputTokens } };
1968
+ const outputTokens = reportedOutput && reportedOutput > 0 ? reportedOutput : Math.ceil(pcmBytes / PCM_BYTES_PER_SECOND * AUDIO_TOKENS_PER_SECOND);
1969
+ return {
1970
+ audio,
1971
+ usage: { inputTokens, outputTokens },
1972
+ ...styleDropped ? { styleDropped } : {}
1973
+ };
1952
1974
  }
1953
1975
 
1954
1976
  // src/voice/google-stt.ts
@@ -1979,11 +2001,11 @@ var GoogleVoiceAgent = class {
1979
2001
  ttsModel = VOICE_MODEL_CONSTANTS.GOOGLE_TTS;
1980
2002
  sttModel = VOICE_MODEL_CONSTANTS.GOOGLE_STT;
1981
2003
  async speak(request) {
1982
- const { audio, usage } = await generateGoogleTtsAudio(request.text, this.apiKey, {
2004
+ const { audio, usage, styleDropped } = await generateGoogleTtsAudio(request.text, this.apiKey, {
1983
2005
  voiceName: request.voice,
1984
2006
  voiceStyle: request.voiceStyle
1985
2007
  });
1986
- return { audio, costUSD: calculateGeminiTtsCost(usage), usage };
2008
+ return { audio, costUSD: calculateGeminiTtsCost(usage), usage, ...styleDropped ? { styleDropped } : {} };
1987
2009
  }
1988
2010
  async transcribe(request) {
1989
2011
  const { text, durationSeconds, usage } = await transcribeWithGemini(request.audio, this.apiKey, { mimeType: request.mimeType });
@@ -5351,7 +5373,6 @@ export {
5351
5373
  XAI_REASONING_EFFORTS,
5352
5374
  ZodSchemaConverter,
5353
5375
  applySpend,
5354
- buildGoogleTtsPrompt,
5355
5376
  calculateAnthropicCost,
5356
5377
  calculateCost,
5357
5378
  calculateDeepSeekCost,
@@ -5404,6 +5425,7 @@ export {
5404
5425
  isHybridThinkingModel,
5405
5426
  isInPeakWindow,
5406
5427
  isPeakBilling,
5428
+ isSafetyBlocked,
5407
5429
  isWeekendAt,
5408
5430
  ledgerSpend,
5409
5431
  logger,
@@ -5411,6 +5433,7 @@ export {
5411
5433
  modelHasTag,
5412
5434
  modelIsFast,
5413
5435
  needsPromptBasedSchema,
5436
+ normalizeTtsStyle,
5414
5437
  parseAndValidateLlmJson,
5415
5438
  pcmToWav,
5416
5439
  periodEnd,