genmagic-tanstack-ai
v0.1.0
Published
GenMagic adapters for TanStack AI: one API key for text, image, speech and video models (Veo, Kling, Seedance, Wan, FLUX, Nano Banana, GPT Image and more) with chat, generateImage, generateSpeech and generateVideo.
Maintainers
Readme
genmagic-tanstack-ai
GenMagic adapters for TanStack AI. One API key and one pay-as-you-go balance for more than 450 models: text, images, speech and video, including Veo, Kling, Seedance, Wan, FLUX, Nano Banana and GPT Image. Each model's price is public before you run it.
It adds four adapters: genmagicText for chat(), genmagicImage for generateImage(), genmagicSpeech for generateSpeech() and genmagicVideo for generateVideo().
Install
npm install genmagic-tanstack-ai @tanstack/aiNeeds @tanstack/ai 1.0.1 or newer and Node 22 or newer. The package is ESM.
Get a key (a free key with starter credit takes a minute): https://genmagic.co/get-api-key?utm_source=tanstack-ai&utm_medium=package
export GENMAGIC_API_KEY=...Or pass it yourself. The key is read when a call is made, so creating an adapter never fails without one.
import { genmagicImage, createGenMagicImage } from 'genmagic-tanstack-ai'
genmagicImage('google/gemini-nano-banana-2.1', { apiKey: process.env.MY_KEY })
createGenMagicImage('google/gemini-nano-banana-2.1', process.env.MY_KEY!)It is paid per generation
GenMagic bills every text, image, speech and video call against a prepaid balance, at the price listed for the model (see "Find models and prices"). A video is charged when the clip completes, not when it starts. This package never calls GenMagic by itself: you pay only for the generations your code asks for.
Text
import { chat } from '@tanstack/ai'
import { genmagicText } from 'genmagic-tanstack-ai'
const stream = chat({
adapter: genmagicText('anthropic/claude-haiku-5.5'),
threadId: 'thread-1',
messages: [{ role: 'user', content: 'Write a one-line tagline for a coffee subscription.' }],
})
for await (const chunk of stream) {
if (chunk.type === 'TEXT_MESSAGE_CONTENT') process.stdout.write(chunk.delta)
}Text goes through GenMagic's OpenAI-compatible /chat/completions, so streaming, tools, outputSchema and usage work as they do with any OpenAI-style model. Set sampling and thinking with modelOptions (temperature, max_tokens, reasoning_effort, or any parameter in the model's supported_parameters). The chat({ reasoning }) shortcut is not mapped.
Images
import { generateImage } from '@tanstack/ai'
import { genmagicImage } from 'genmagic-tanstack-ai'
const result = await generateImage({
adapter: genmagicImage('google/gemini-nano-banana-2.1'),
threadId: 'thread-1',
prompt: 'A red fox in snow, 35mm photo',
size: '1792x1024',
})
console.log(result.images[0]?.url)GenMagic images come in 1024x1024, 1792x1024 and 1024x1792, up to four per call (numberOfImages). The answer is a hosted url; set modelOptions: { response_format: 'b64_json' } to receive base64 instead. To transform a picture, add one image to the prompt:
prompt: [
{ type: 'text', content: 'Make it a watercolor' },
{ type: 'image', source: { type: 'url', value: 'https://example.com/fox.png' } },
]GenMagic transforms one reference image per call and takes no mask, so more images or a mask role are refused with a clear error before anything is sent. modelOptions: { type: 'logo' } steers the model toward a brand mark.
Speech
import { writeFile } from 'node:fs/promises'
import { generateSpeech } from '@tanstack/ai'
import { genmagicSpeech } from 'genmagic-tanstack-ai'
const result = await generateSpeech({
adapter: genmagicSpeech('openai/gpt-audio-mini'),
threadId: 'thread-1',
text: 'Welcome to the show.',
voice: 'alloy',
})
await writeFile(`welcome.${result.format}`, Buffer.from(result.audio, 'base64'))result.audio is base64, and result.format and result.contentType say what GenMagic returned. GenMagic voices the text exactly as written in the format its model produces: format and speed are ignored with a warning.
Video
import { generateVideo } from '@tanstack/ai'
import { genmagicVideo } from 'genmagic-tanstack-ai'
const stream = generateVideo({
adapter: genmagicVideo('alibaba/wan-3.0'),
threadId: 'thread-1',
prompt: 'Waves at sunset, slow drone shot',
size: '16:9',
duration: 5,
pollingInterval: 5000,
stream: true,
})
for await (const chunk of stream) {
if (chunk.type === 'CUSTOM' && chunk.name === 'generation:result') {
console.log(chunk.value.url)
}
}A video is a job. The adapter starts it (GenMagic answers at once), then TanStack AI polls it until it is done. To start now and collect later, even from another process, keep the job id:
import { generateVideo, getVideoJobStatus } from '@tanstack/ai'
const adapter = genmagicVideo('alibaba/wan-3.0')
const { jobId } = await generateVideo({ adapter, threadId: 'thread-1', prompt: 'Waves at sunset' })
const status = await getVideoJobStatus({ adapter, jobId })
// status.status is 'pending', 'processing', 'completed' or 'failed'; status.url when completedsize is an aspect ratio (16:9, 9:16, 1:1), a resolution (720p) or width and height (1280x720, read as 16:9 at 720p). duration is whole seconds. Which durations and resolutions a model has is in its catalog entry. Put the rest in modelOptions, for example { generate_audio: true, resolution: '720p' }.
Prompt images route by metadata.role: no role or start_frame is the opening image, end_frame is the closing image, reference or character adds a reference (up to nine; GenMagic does not combine references with an opening or closing image). Which models take which is in capabilities.frame_images and capabilities.references.
Find models and prices
Browse https://genmagic.co/models, or ask the API. The catalog needs no key.
curl -s "https://genmagic.co/api/v1/models?category=video" \
| jq -r '.data[] | "\(.id) $\(.pricing.est_per_generation_usd)"'
curl -s "https://genmagic.co/api/v1/models?category=image" \
| jq -r '.data[] | "\(.id) $\(.pricing.est_per_generation_usd)"'category is text, image, audio or video. Speech models are the audio ones whose audio_kind is speech. est_per_generation_usd is the estimated price of one default generation; a model's pricing has the exact rates (per token, per image, per second at each resolution). The full API is described at https://genmagic.co/openapi.json.
What a call cost
GenMagic returns the cost of each image, speech and video call, and the balance left. They arrive as result.usage:
result.usage?.cost // US dollars, for example 0.058
result.usage?.providerUsageDetails // { costCents: 5.8, creditsRemainingCents: 194.2 }For a video the usage is on the completed result (getVideoJobStatus(...).usage, or value.usage in the streamed generation:result). usage is left out when GenMagic did not send a cost. Text reports token counts as usual; GET https://genmagic.co/api/v1/usage returns your balance and spend.
Errors and retries
When GenMagic refuses a call you get its own message: a wrong key, too little balance (it says how to top up), a model's content refusal, a rate limit. Media calls throw a GenMagicApiError with status, code and type; chat() ends the run with a RUN_ERROR event carrying the same message and code. Without a key, the call fails before any request is sent and the message says where to get one.
A paid call is sent once and never retried by this package: GenMagic does not deduplicate, so a retry after a lost connection could bill twice. That covers every text, image, speech and video start, including after a 429 or 5xx. If you want TanStack AI to wait out a rate limit, opt in with chat({ retry }); the wait GenMagic asks for is on the RUN_ERROR.
The one thing that is retried is the video status lookup, which is read-only and free: after a network error, a 429 or a 5xx it is tried up to three more times (waits of 0.5, 1.5 and 3 seconds, or the longer wait GenMagic asks for). Change the waits with statusRetryDelaysMs on genmagicVideo, or pass [] to turn it off.
Settings
Each factory takes (model, config); each createGenMagic* factory takes (model, apiKey, config).
apiKey: defaults toGENMAGIC_API_KEY.baseURL: defaults tohttps://genmagic.co/api/v1.headers: extra headers for every call.fetch: your own fetch, for logging, proxies or tests.statusRetryDelaysMs(video only): waits between retries of a status lookup.
Every call carries X-GenMagic-Client: tanstack-ai/<version> so GenMagic can tell traffic through this package apart from your other API use.
Bring your own key
For apps where each user pastes their own key, genmagic-tanstack-ai/byok exports genmagicByok, a defineByokProvider descriptor (id: 'genmagic', env GENMAGIC_API_KEY). Import it from that subpath, never from the main entry.
Not covered
GenMagic's music and sound models, transcription, embeddings, voice design and live video have no adapter in this version.
Links
- Developer docs: https://genmagic.co/developers?utm_source=tanstack-ai&utm_medium=package
- Models and prices: https://genmagic.co/models
- API reference: https://genmagic.co/openapi.json
- Support: [email protected]
MIT
