quiverai-ai-provider

QuiverAI provider for the Vercel AI SDK.
⚠️ Note: QuiverAI has released the official Vercel AI SDK provider at @ai-sdk/quiverai. Consider using that instead. This community provider is still functional but no longer actively maintained.
QuiverAI generates scalable vector graphics (SVG) from text prompts and images. SVG is an interesting edge case in AI generation: it is plain text (XML markup) that renders as a vector image. That duality means you can use it through two different AI SDK interfaces — streamText / generateText if you want a progressive streaming UX where the SVG builds up character by character, or generateImage if you prefer the more natural "give me an image" API. Both are supported.
Installation
npm install quiverai-ai-provider ai
pnpm add quiverai-ai-provider ai
Provider Instance
import { quiverai } from 'quiverai-ai-provider';
For a customized setup:
import { createQuiverAI } from 'quiverai-ai-provider';
const quiverai = createQuiverAI({
apiKey: 'your-api-key',
baseURL: 'custom-url',
headers: { },
});
Settings
apiKey | string | API key sent as Authorization: Bearer. Defaults to QUIVERAI_API_KEY. |
baseURL | string | Override the API base URL. |
headers | Record<string, string> | Extra headers included in every request. |
fetch | FetchFunction | Custom fetch implementation (useful for testing/proxying). |
Models
arrow-1 | Arrow 1.0 | 30 | 30 | 4 |
arrow-1.1 | Arrow 1.1 | 20 | 15 | 4 |
arrow-1.1-max | Arrow 1.1 Max | 25 | 20 | 16 |
All models support text-to-SVG, image-to-SVG (vectorization), and streaming.
Language Models
Use streamText or generateText to receive SVG markup as text output. Streaming is especially
useful for progressive rendering — each token is a small SVG fragment you can render live in
the browser as it arrives.
Streaming
import { quiverai } from 'quiverai-ai-provider';
import { streamText } from 'ai';
const result = streamText({
model: quiverai('arrow-1.1'),
prompt: 'A red circle with a blue border',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
Non-streaming
import { quiverai } from 'quiverai-ai-provider';
import { generateText } from 'ai';
const { text, providerMetadata } = await generateText({
model: quiverai('arrow-1.1'),
prompt: 'A red circle with a blue border',
});
console.log(text);
console.log(providerMetadata?.quiverai);
Vectorization (image → SVG)
Include an image file part in the prompt — with no accompanying text — to convert a raster
image to SVG:
import { quiverai } from 'quiverai-ai-provider';
import { generateText } from 'ai';
import { readFileSync } from 'node:fs';
const { text } = await generateText({
model: quiverai('arrow-1.1'),
messages: [
{
role: 'user',
content: [
{
type: 'file',
data: readFileSync('logo.png'),
mediaType: 'image/png',
},
],
},
],
});
console.log(text);
References (text + image → SVG)
If the prompt contains both text and image file parts, the model generates a new SVG
guided by the image(s) as visual references. You can also pass references via
providerOptions.quiverai.references:
import { quiverai } from 'quiverai-ai-provider';
import { generateText } from 'ai';
const { text } = await generateText({
model: quiverai('arrow-1.1-max'),
prompt: 'A minimalist badge in the style of these references',
providerOptions: {
quiverai: {
references: [
'https://example.com/ref1.png',
{ url: 'https://example.com/ref2.png' },
],
},
},
});
Unsupported Features
These AI SDK parameters are ignored and produce a warning:
tools, responseFormat, stopSequences, topK, seed, frequencyPenalty.
Image Models
Use generateImage for a conventional image generation workflow. The SVG is returned as a
Uint8Array containing UTF-8 encoded markup — write it directly to a .svg file or decode
it to a string.
Basic Usage
import { quiverai } from 'quiverai-ai-provider';
import { generateImage } from 'ai';
import { writeFileSync } from 'node:fs';
const { images, providerMetadata } = await generateImage({
model: quiverai.image('arrow-1.1'),
prompt: 'A red circle with a blue border',
});
const decoder = new TextDecoder();
for (const image of images) {
writeFileSync(`output-${Date.now()}.svg`, image.uint8Array);
console.log(decoder.decode(image.uint8Array));
}
console.log(providerMetadata?.quiverai.credits);
Multiple outputs
Generations (text-to-SVG) support up to 16 outputs per call via n:
const { images } = await generateImage({
model: quiverai.image('arrow-1.1'),
prompt: 'Variants of a minimalist coffee logo',
n: 4,
});
Vectorization only supports a single output.
Vectorization (image → SVG)
import { quiverai } from 'quiverai-ai-provider';
import { generateImage } from 'ai';
import { readFileSync } from 'node:fs';
const { images } = await generateImage({
model: quiverai.image('arrow-1.1'),
prompt: '',
files: [
{
type: 'file',
data: readFileSync('logo.png'),
mediaType: 'image/png',
},
],
providerOptions: {
quiverai: {
autoCrop: true,
targetSize: 512,
},
},
});
Provider Options
Pass QuiverAI-specific options via providerOptions.quiverai on any call:
import { quiverai, type QuiverAILanguageProviderOptions } from 'quiverai-ai-provider';
import { generateText } from 'ai';
const { text } = await generateText({
model: quiverai('arrow-1.1'),
prompt: 'A minimalist logo for a coffee shop',
providerOptions: {
quiverai: {
instructions: 'flat design, monochrome, geometric shapes only',
temperature: 0.7,
maxOutputTokens: 4096,
} satisfies QuiverAILanguageProviderOptions,
},
});
instructions | string | Additional style/quality instructions (text-to-SVG only). |
temperature | number | Sampling temperature (0–2). |
topP | number | Nucleus sampling threshold (0–1). |
maxOutputTokens | number | Upper bound for output token count. |
presencePenalty | number | Penalty for tokens already present (-2 to 2). |
references | Array<string | {url} | {base64}> | Reference images for text-to-SVG. Max 4 for arrow-1/arrow-1.1, 16 for arrow-1.1-max. |
autoCrop | boolean | Auto-crop source image before vectorization. |
targetSize | number | Square resize target in pixels (128–4096) for vectorization. |
Billing & Credits
QuiverAI bills per request in credits (not tokens). Each completed request returns a
credits value in providerMetadata.quiverai.credits — both for doGenerate and on the
final content event of a stream.
const { providerMetadata } = await generateText({ model: quiverai('arrow-1.1'), prompt });
console.log(providerMetadata?.quiverai.credits);
The AI SDK usage object (input/output tokens) is not populated — QuiverAI does not report
tokens.
Errors
Errors are thrown as APICallError with the following QuiverAI error codes surfaced in the
message:
| 400 | invalid_request | Malformed body or invalid parameters |
| 401 | unauthorized / invalid_api_key | Missing or invalid API key |
| 402 | insufficient_credits | Organization is out of credits |
| 403 | account_frozen | Account is frozen |
| 404 | model_not_found | Unknown model ID |
| 429 | rate_limit_exceeded / weekly_limit_exceeded | Retry after Retry-After seconds |
| 500 | internal_error | Server error |
| 502/503 | upstream_error | Upstream processing failure |
Each error includes a request_id for support. The AI SDK's APICallError.isRetryable
automatically flags 429/5xx as retryable.
Contributing
Setup
git clone https://github.com/pmontp19/quiverai-ai-sdk-provider.git
cd quiverai-ai-sdk-provider
npm install
A pre-commit hook runs biome check --write on staged files automatically.
Scripts
npm run check | Lint and format check (biome) |
npm run check:fix | Auto-fix lint and format issues |
npm run type-check | TypeScript type checking |
npm test | Run tests |
npm run build | Build the package |
Making changes
Every PR that affects the published package must include a changeset — a small file that describes what changed and the semver bump type.
npx changeset
This prompts you to select the bump type (patch, minor, or major) and write a short summary. It creates a markdown file in .changeset/ that you commit alongside your code.
If you forget, the CI changeset-check job will fail on your PR as a reminder.
Release flow
- PRs with changesets are merged into
main.
- The Release workflow automatically opens a "chore: version packages" PR that bumps
version in package.json and updates CHANGELOG.md.
- When that PR is merged, the package is published to npm automatically.
Adding a changeset retroactively
If a PR was already merged without a changeset, create one on a new branch:
git checkout -b chore/add-changeset
npx changeset
git add .changeset/
git commit -m "chore: add missing changeset for <feature>"
Then open a PR. Once merged, the release PR will pick it up.
License
MIT