Three follow-ups to ef3b579 (OpenAI SDK migration) and ebe39ef (canvas frame):
- .env.example / config.ts / AGENTS.md: anthropic & google native protocols
were removed with the Vercel AI SDK, but .env.example and AGENTS.md still
advertised them. Rewrite the docs to point Claude/Gemini at their
OpenAI-compatible endpoints (api.anthropic.com/v1,
generativelanguage.googleapis.com/v1beta/openai), drop the dead Gemini
"Nano Banana" image example, sync AGENTS.md (text/vision protocol list,
image protocol list, the "OpenAI/Gemini via AI SDK" reference note), and
append a short hint in readProvider() error message guiding
anthropic/google users to openai_compatible instead of a bare rejection.
- chat.ts: drop the unsafe `as { prompt_tokens_details?: ... }` cast; read
cached_tokens straight off the SDK's CompletionUsage type. Add a comment
noting the OpenAI usage object reports cache reads only (no cache-write
count), so the create cost the old AI SDK path logged is unrecoverable.
- PlayCanvas.tsx: revert <img key={imageUrl}> to key={imageUrl.slice(-48)}.
The gpt-image/mock paths emit multi-MB data URIs; using the full string as
React's reconciliation key adds avoidable diff overhead during the frequent
re-renders. Matches the existing <audio> element's key convention.
Validation: pnpm typecheck passes. (pnpm lint fails on a pre-existing Next 16
`next lint` CLI issue, identical on staging — unrelated to this change.)
IMAGE_TIMEOUT_MS sets a per-attempt hard deadline (AbortSignal.timeout);
IMAGE_HEDGE_MS races a second identical scene-paint request when the
first is still pending past the threshold. Both default to OFF when
unset, preserving historical behavior for self-hosted deploys.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add StepFun step-tts-mini / step-tts-2 / stepaudio-2.5-tts as an alternate
TTS provider alongside Xiaomi MiMo. Auto-detected from TTS_BASE_URL host
(contains `stepfun.com` → StepFun; otherwise → MiMo), mirroring how the
image client infers Runware from `*.runware.ai`.
CharacterVoice becomes a discriminated union on `provider`:
- xiaomi: { referenceAudioBase64, mimeType } — unchanged
- stepfun: { voiceId, model, mimeType } — preset voice ID + chosen model
Provision dispatches on the current cfg's base URL; synthesis dispatches
on the voice's own `provider` tag so a session with mixed voices (e.g. a
provider switch mid-development) routes each beat through the correct
protocol. xiaomiSynthesize now guards against being called with a non-
xiaomi voice, surfacing the bug as a clear runtime error instead of a
TypeScript narrow violation at the access site.
StepFun has no voicedesign equivalent — only preset voices + voice
cloning from a reference audio upload. Cloning would require an extra
asset per character, so v1 maps the LLM's Chinese voiceDescription to one
of the 32 published preset IDs via gender + age + tone keyword scoring,
with a deterministic hash spread across the top-3 candidates so multiple
characters with similar descriptions don't collapse onto the identical
preset. lineDelivery is accepted but not yet propagated to StepFun's
voice_label.emotion / .style fields — left as a follow-up.
beat-audio route validation relaxed from `voice.referenceAudioBase64`
(xiaomi-shaped) to `voice.provider` (shape-agnostic), so stepfun voices
pass the gate; provider-specific shape errors still surface from the
synth function.
Observed latency on InfiPlot's dev loop: StepFun step-tts-mini median
~2.3s per beat with 0% timeouts across the test session, vs MiMo's
median ~8s with the long tail tripping the existing 15s synth budget
on roughly 2 of 3 beats. Pricing: step-tts-mini ¥0.9/万字符 (~¥0.14
per typical 50-beat session) vs MiMo TTS currently free under the
Token Plan creator incentive.
AGENTS.md provider matrix updated to describe both providers and the
discriminated-union dispatch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>