@hiper2d/ai-agents 0.15.0 → 0.17.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
@@ -839,9 +839,14 @@ var SupportedAiModels = {
839
839
  reasoningEffort: "high",
840
840
  tags: ["expensive"]
841
841
  },
842
+ // Sonnet moved 5 → 5.5 on 2026-09-28 (`claude-sonnet-5-5`, verified live with the platform key).
843
+ // Same price, 1M context / 128K output. Adaptive thinking, API default effort `high` (effort
844
+ // levels were recalibrated vs Sonnet 5 — same pin, not necessarily the same thinking depth).
845
+ // It rejects `thinking: {type: "disabled"}`; ClaudeAgent sends `between_tools` instead. It
846
+ // reads Sonnet 5 thinking blocks, so games moving onto it keep the bots' earlier reasoning.
842
847
  [LLM_CONSTANTS.CLAUDE_SONNET]: {
843
- displayName: "Claude 5 Sonnet",
844
- modelApiName: "claude-sonnet-5",
848
+ displayName: "Claude 5.5 Sonnet",
849
+ modelApiName: "claude-sonnet-5-5",
845
850
  apiKeyName: API_KEY_CONSTANTS.ANTHROPIC,
846
851
  hasThinking: true,
847
852
  reasoningEffort: "high",
@@ -1345,6 +1350,7 @@ var MODEL_PRICING = {
1345
1350
  cacheHitPrice: 0.2
1346
1351
  },
1347
1352
  [SupportedAiModels[LLM_CONSTANTS.CLAUDE_SONNET].modelApiName]: {
1353
+ // Sonnet 5.5 (2026-09-28): same $2/$10 and $0.20 cache reads as Sonnet 5.
1348
1354
  inputPrice: 2,
1349
1355
  outputPrice: 10,
1350
1356
  cacheHitPrice: 0.2
@@ -1756,8 +1762,9 @@ function extractTokenUsageFromResponse8(response) {
1756
1762
  var VOICE_MODEL_CONSTANTS = {
1757
1763
  OPENAI_TTS: "gpt-4o-mini-tts",
1758
1764
  OPENAI_STT: "whisper-1",
1759
- // ai.google.dev/gemini-api/docs/speech-generation
1760
- GOOGLE_TTS: "gemini-3.1-flash-tts-preview",
1765
+ // ai.google.dev/gemini-api/docs/speech-generation — Google's named replacement
1766
+ // for gemini-3.1-flash-tts-preview (2026-09-24: ~6 s vs ~15 s per line, ~4x cheaper)
1767
+ GOOGLE_TTS: "gemini-3.8-flash-lite-tts",
1761
1768
  // ai.google.dev/gemini-api/docs/transcribe — Interactions API only (see google-stt.ts)
1762
1769
  GOOGLE_STT: "gemini-3.5-transcribe"
1763
1770
  };
@@ -1769,9 +1776,9 @@ var VOICE_PROVIDER_API_KEY = {
1769
1776
  var VOICE_MODEL_PRICING = {
1770
1777
  [VOICE_MODEL_CONSTANTS.OPENAI_TTS]: { pricePerMillionCharacters: 15 },
1771
1778
  [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 },
1779
+ // ~32 audio tokens per second of speech, so a 15-second line is ~$0.003.
1780
+ // Google doubles both rates to $1 / $12 on 2027-01-01 — update then.
1781
+ [VOICE_MODEL_CONSTANTS.GOOGLE_TTS]: { textInputPricePerM: 0.5, audioOutputPricePerM: 6 },
1775
1782
  // ~25 audio tokens per second in, ~175 text tokens per minute out: ≈ $0.005/min.
1776
1783
  [VOICE_MODEL_CONSTANTS.GOOGLE_STT]: { audioInputPricePerM: 2, textOutputPricePerM: 12 }
1777
1784
  };
@@ -1878,12 +1885,10 @@ import { GoogleGenAI } from "@google/genai";
1878
1885
  var AUDIO_TOKENS_PER_SECOND = 32;
1879
1886
  var SAMPLE_RATE = 24e3;
1880
1887
  var PCM_BYTES_PER_SECOND = SAMPLE_RATE * 2;
1881
- function buildGoogleTtsPrompt(text, voiceStyle) {
1888
+ var WAV_HEADER_BYTES = 44;
1889
+ function normalizeTtsStyle(voiceStyle) {
1882
1890
  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}`;
1891
+ return style || void 0;
1887
1892
  }
1888
1893
  function pcmToWav(pcmData, sampleRate = SAMPLE_RATE) {
1889
1894
  const numChannels = 1;
@@ -1927,28 +1932,51 @@ function describeEmptyTtsResponse(response) {
1927
1932
  if (!parts.length) bits.push("no parts");
1928
1933
  return bits.join(", ");
1929
1934
  }
1935
+ function isSafetyBlocked(response) {
1936
+ const finish = response?.candidates?.[0]?.finishReason;
1937
+ return finish === "SAFETY" || finish === "PROHIBITED_CONTENT" || !!response?.promptFeedback?.blockReason;
1938
+ }
1939
+ function findAudioPart(response) {
1940
+ const parts = response?.candidates?.[0]?.content?.parts ?? [];
1941
+ const inline = parts.find((part) => part.inlineData?.mimeType?.startsWith("audio/") && part.inlineData?.data)?.inlineData;
1942
+ return inline ? { data: inline.data, mimeType: inline.mimeType } : void 0;
1943
+ }
1930
1944
  async function generateGoogleTtsAudio(text, apiKey, options) {
1931
1945
  const client = new GoogleGenAI({ apiKey });
1932
- const response = await client.models.generateContent({
1946
+ const request = (style2) => client.models.generateContent({
1933
1947
  model: VOICE_MODEL_CONSTANTS.GOOGLE_TTS,
1934
- contents: [{ parts: [{ text: buildGoogleTtsPrompt(text, options.voiceStyle) }] }],
1948
+ contents: [{ parts: [style2 ? { text, speechMetadata: { style: style2 } } : { text }] }],
1935
1949
  config: {
1936
1950
  responseModalities: ["AUDIO"],
1937
1951
  speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: options.voiceName } } }
1938
1952
  }
1939
1953
  });
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"));
1954
+ const style = normalizeTtsStyle(options.voiceStyle);
1955
+ let response = await request(style);
1956
+ let styleDropped = false;
1957
+ let blockedInputTokens = 0;
1958
+ if (!findAudioPart(response) && style && isSafetyBlocked(response)) {
1959
+ blockedInputTokens = response?.usageMetadata?.promptTokenCount ?? 0;
1960
+ response = await request(void 0);
1961
+ styleDropped = true;
1962
+ }
1963
+ const audioPart = findAudioPart(response);
1964
+ if (!audioPart) {
1965
+ throw new Error(`No audio data in Google TTS response (${describeEmptyTtsResponse(response)}${styleDropped ? ", also without the style" : ""})`);
1966
+ }
1967
+ const bytes = new Uint8Array(Buffer.from(audioPart.data, "base64"));
1968
+ const isWav = /^audio\/(x-)?wav/i.test(audioPart.mimeType);
1969
+ const audio = isWav ? bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) : pcmToWav(bytes);
1970
+ const pcmBytes = isWav ? Math.max(0, bytes.length - WAV_HEADER_BYTES) : bytes.length;
1947
1971
  const usageMetadata = response.usageMetadata ?? {};
1948
- const inputTokens = usageMetadata.promptTokenCount ?? 0;
1972
+ const inputTokens = (usageMetadata.promptTokenCount ?? 0) + blockedInputTokens;
1949
1973
  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 } };
1974
+ const outputTokens = reportedOutput && reportedOutput > 0 ? reportedOutput : Math.ceil(pcmBytes / PCM_BYTES_PER_SECOND * AUDIO_TOKENS_PER_SECOND);
1975
+ return {
1976
+ audio,
1977
+ usage: { inputTokens, outputTokens },
1978
+ ...styleDropped ? { styleDropped } : {}
1979
+ };
1952
1980
  }
1953
1981
 
1954
1982
  // src/voice/google-stt.ts
@@ -1979,11 +2007,11 @@ var GoogleVoiceAgent = class {
1979
2007
  ttsModel = VOICE_MODEL_CONSTANTS.GOOGLE_TTS;
1980
2008
  sttModel = VOICE_MODEL_CONSTANTS.GOOGLE_STT;
1981
2009
  async speak(request) {
1982
- const { audio, usage } = await generateGoogleTtsAudio(request.text, this.apiKey, {
2010
+ const { audio, usage, styleDropped } = await generateGoogleTtsAudio(request.text, this.apiKey, {
1983
2011
  voiceName: request.voice,
1984
2012
  voiceStyle: request.voiceStyle
1985
2013
  });
1986
- return { audio, costUSD: calculateGeminiTtsCost(usage), usage };
2014
+ return { audio, costUSD: calculateGeminiTtsCost(usage), usage, ...styleDropped ? { styleDropped } : {} };
1987
2015
  }
1988
2016
  async transcribe(request) {
1989
2017
  const { text, durationSeconds, usage } = await transcribeWithGemini(request.audio, this.apiKey, { mimeType: request.mimeType });
@@ -2572,6 +2600,7 @@ ${instruction}`);
2572
2600
  // src/agents/anthropic-agent.ts
2573
2601
  import { Anthropic } from "@anthropic-ai/sdk";
2574
2602
  var rejectsDisabledThinking = (model) => model.includes("fable") || model.includes("opus-5-5");
2603
+ var usesBetweenToolsForOff = (model) => model.includes("sonnet-5-5");
2575
2604
  var ClaudeAgent = class extends AbstractAgent {
2576
2605
  client;
2577
2606
  /**
@@ -2788,7 +2817,9 @@ ${schemaDescription}`;
2788
2817
  params.temperature = 1;
2789
2818
  }
2790
2819
  } else if (usesAdaptiveThinking) {
2791
- if (!rejectsDisabledThinking(this.model)) {
2820
+ if (usesBetweenToolsForOff(this.model)) {
2821
+ params.thinking = { type: "between_tools" };
2822
+ } else if (!rejectsDisabledThinking(this.model)) {
2792
2823
  params.thinking = { type: "disabled" };
2793
2824
  }
2794
2825
  } else {
@@ -2884,7 +2915,9 @@ ${schemaDescription}`;
2884
2915
  params.temperature = 1;
2885
2916
  }
2886
2917
  } else if (usesAdaptiveThinking) {
2887
- if (!rejectsDisabledThinking(this.model)) {
2918
+ if (usesBetweenToolsForOff(this.model)) {
2919
+ params.thinking = { type: "between_tools" };
2920
+ } else if (!rejectsDisabledThinking(this.model)) {
2888
2921
  params.thinking = { type: "disabled" };
2889
2922
  }
2890
2923
  } else {
@@ -5351,7 +5384,6 @@ export {
5351
5384
  XAI_REASONING_EFFORTS,
5352
5385
  ZodSchemaConverter,
5353
5386
  applySpend,
5354
- buildGoogleTtsPrompt,
5355
5387
  calculateAnthropicCost,
5356
5388
  calculateCost,
5357
5389
  calculateDeepSeekCost,
@@ -5404,6 +5436,7 @@ export {
5404
5436
  isHybridThinkingModel,
5405
5437
  isInPeakWindow,
5406
5438
  isPeakBilling,
5439
+ isSafetyBlocked,
5407
5440
  isWeekendAt,
5408
5441
  ledgerSpend,
5409
5442
  logger,
@@ -5411,6 +5444,7 @@ export {
5411
5444
  modelHasTag,
5412
5445
  modelIsFast,
5413
5446
  needsPromptBasedSchema,
5447
+ normalizeTtsStyle,
5414
5448
  parseAndValidateLlmJson,
5415
5449
  pcmToWav,
5416
5450
  periodEnd,