diff --git a/.agents/skills/fish-audio-tts/SKILL.md b/.agents/skills/fish-audio-tts/SKILL.md index 7c30a18e..b5fe28ae 100644 --- a/.agents/skills/fish-audio-tts/SKILL.md +++ b/.agents/skills/fish-audio-tts/SKILL.md @@ -26,11 +26,11 @@ The backend model is chosen with the `model` **HTTP header**, not a body field. `model` is **required — there is no default**. Pass one of: - `s2.1-pro` — latest generation. Best quality: inline emotion tags, 80+ languages, multi-speaker. Hero narration. -- `s2.1-pro-free` — **promotional** free access to s2.1-pro. Drafts, samples, and validation runs at $0 during the promo window only. Per the [fish.audio announcement](https://fish.audio/ar/blog/s2-1-pro-free-api/?articleLocale=en): free through end of July 2026, subject to Fair Use, no SLA/latency guarantee, requests may be retained, and commercial use is restricted. Never route production or client narration through it. +- `s2.1-pro-free` — **promotional** free access to s2.1-pro. Drafts, samples, and validation runs at $0 during the promo window only. Per the [fish.audio announcement](https://fish.audio/ko/blog/s2-1-pro-free-api/?articleLocale=en): free through August 31, 2026, subject to Fair Use, no SLA/latency guarantee, requests may be retained, and commercial use is restricted. Never route production or client narration through it. - `s2-pro` — first S2 generation. Stable high quality with emotion-tag support. - `s1` — previous flagship. Kept for compatibility with existing integrations. -Billing is **per UTF-8 byte of input text** (not per character). CJK text and emoji cost 3-4x an ASCII character of the same visible length. Approximate: `s1` / `s2-pro` / `s2.1-pro` ≈ $15 per 1M bytes, `s2.1-pro-free` = $0 during the promo window only (the tool's `estimate_cost()` switches to the paid `s2.1-pro` rate after end of July 2026). Verify current pricing at https://fish.audio before large batches. +Billing is **per UTF-8 byte of input text** (not per character). CJK text and emoji cost 3-4x an ASCII character of the same visible length. Current list pricing: `s1` / `s2-pro` / `s2.1-pro` = $15 per 1M bytes, `s2.1-pro-free` = $0 during the promo window only (the tool's `estimate_cost()` switches to the paid `s2.1-pro` rate after August 31, 2026). Verify current pricing at https://docs.fish.audio/developer-guide/models-pricing/pricing-and-rate-limits before large batches. ## Inline emotion tags (S2 models only) diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index d9d0f763..8a5f0358 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -323,13 +323,13 @@ No subscription — pure pay-as-you-go, no minimum spend. | `s2-pro` | First S2 generation — stable high quality with emotion-tag support | | `s1` | Previous flagship, kept for compatibility (no emotion tags) | -**`s2.1-pro-free` caveats — promotional, not a durable free tier.** Per the [fish.audio announcement](https://fish.audio/ar/blog/s2-1-pro-free-api/?articleLocale=en), free API access runs **through the end of July 2026** and is subject to Fair Use limits, carries **no SLA or latency guarantee**, requests **may be retained** by fish.audio, and **commercial use is restricted**. Don't route client work or production narration through it, and don't plan long-term costs at $0 — `fish_audio_tts.estimate_cost()` falls back to the paid `s2.1-pro` rate after the promotional window ends. +**`s2.1-pro-free` caveats — promotional, not a durable free tier.** Per the [fish.audio announcement](https://fish.audio/ko/blog/s2-1-pro-free-api/?articleLocale=en), free API access runs **through August 31, 2026** and is subject to Fair Use limits, carries **no SLA or latency guarantee**, requests **may be retained** by fish.audio, and **commercial use is restricted**. Don't route client work or production narration through it, and don't plan long-term costs at $0 — `fish_audio_tts.estimate_cost()` falls back to the paid `s2.1-pro` rate after the promotional window ends. The legacy `speech-1.x` tier and `s1-mini` have been removed from the fish.audio API and are not supported. #### Pricing -Billing is **per UTF-8 byte of input text** (not per character) — CJK text and emoji cost 3-4x an ASCII character of the same visible length. Approximate: `s1` / `s2-pro` / `s2.1-pro` ≈ $15 per 1M bytes; `s2.1-pro-free` is $0 only during the promotional window (through end of July 2026 — see caveats above). Verify current pricing at [fish.audio](https://fish.audio) before large batches. +Billing is **per UTF-8 byte of input text** (not per character) — CJK text and emoji cost 3-4x an ASCII character of the same visible length. Current list pricing: `s1` / `s2-pro` / `s2.1-pro` = $15 per 1M bytes; `s2.1-pro-free` is $0 only during the promotional window (through August 31, 2026 — see caveats above). Verify current pricing in the [official pricing guide](https://docs.fish.audio/developer-guide/models-pricing/pricing-and-rate-limits) before large batches. --- diff --git a/tests/tools/test_fish_audio_tts.py b/tests/tools/test_fish_audio_tts.py index 4c3985c5..d7f21baa 100644 --- a/tests/tools/test_fish_audio_tts.py +++ b/tests/tools/test_fish_audio_tts.py @@ -80,9 +80,13 @@ class TestModelContract: assert result.success is False assert "Unknown fish.audio model" in result.error - def test_schema_declares_model_required(self): + def test_schema_requires_model_or_selector_alias(self): schema = FishAudioTTS.input_schema - assert "model" in schema["required"] + assert schema["required"] == ["text"] + assert {tuple(option["required"]) for option in schema["anyOf"]} == { + ("model",), + ("model_id",), + } assert "model_id" in schema["properties"] def test_model_id_alias_accepted(self, api_key, tmp_path): @@ -252,12 +256,12 @@ class TestCost: def test_s2_1_pro_free_costs_zero_during_promo_window(self): tool = FishAudioTTS() - with patch.object(FishAudioTTS, "_today", return_value=date(2026, 7, 1)): + with patch.object(FishAudioTTS, "_today", return_value=date(2026, 8, 31)): assert tool.estimate_cost({"text": "same text here", "model": "s2.1-pro-free"}) == 0.0 def test_s2_1_pro_free_charged_at_paid_rate_after_promo_window(self): tool = FishAudioTTS() - with patch.object(FishAudioTTS, "_today", return_value=date(2026, 8, 1)): + with patch.object(FishAudioTTS, "_today", return_value=date(2026, 9, 1)): free_after = tool.estimate_cost({"text": "same text here", "model": "s2.1-pro-free"}) paid = tool.estimate_cost({"text": "same text here", "model": "s2.1-pro"}) assert free_after == paid diff --git a/tools/audio/fish_audio_tts.py b/tools/audio/fish_audio_tts.py index 632d8c2c..50ee8107 100644 --- a/tools/audio/fish_audio_tts.py +++ b/tools/audio/fish_audio_tts.py @@ -77,7 +77,11 @@ class FishAudioTTS(BaseTool): input_schema = { "type": "object", - "required": ["text", "model"], + "required": ["text"], + "anyOf": [ + {"required": ["model"]}, + {"required": ["model_id"]}, + ], "properties": { "text": {"type": "string", "description": "Text to convert to speech"}, "model": { @@ -229,11 +233,11 @@ class FishAudioTTS(BaseTool): _DEFAULT_RATE = 0.000015 # s2.1-pro-free is a promotion, not a durable free tier: free API access runs - # through the end of July 2026, subject to Fair Use, with no SLA/latency + # through August 31, 2026, subject to Fair Use, with no SLA/latency # guarantee, possible request retention, and commercial-use restrictions # (https://fish.audio/ar/blog/s2-1-pro-free-api/?articleLocale=en). After the # window, cost planning falls back to the paid s2.1-pro rate. - _S21_PRO_FREE_PROMO_END = date(2026, 7, 31) + _S21_PRO_FREE_PROMO_END = date(2026, 8, 31) # Effective API defaults for output-affecting inputs. Applied when computing # the idempotency key so an omitted field and its explicit default hash the