Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
d6859af
chore(deps): bump the linting group with 3 updates
dependabot[bot] Jun 29, 2026
e1f9065
chore(deps): bump @ai-sdk/openai from 3.0.73 to 4.0.2
dependabot[bot] Jun 29, 2026
b23cebc
chore(deps): bump @ai-sdk/provider from 3.0.10 to 4.0.0
dependabot[bot] Jun 29, 2026
7f1c0cf
chore(deps): bump the linting group with 3 updates (#114)
algolj Jun 30, 2026
994314d
Merge branch 'develop' into dependabot/npm_and_yarn/ai-sdk/openai-4.0.2
algolj Jun 30, 2026
2989265
chore(deps): bump @ai-sdk/openai from 3.0.73 to 4.0.2 (#115)
algolj Jun 30, 2026
20fb1ab
Merge branch 'develop' into dependabot/npm_and_yarn/ai-sdk/provider-4…
algolj Jun 30, 2026
988f8b3
chore(deps): bump @types/node from 25.9.3 to 26.0.1
dependabot[bot] Jun 30, 2026
102ecd0
chore(deps): bump @ai-sdk/provider from 3.0.10 to 4.0.0 (#116)
algolj Jun 30, 2026
2e09e6d
Merge branch 'develop' into dependabot/npm_and_yarn/types/node-26.0.1
algolj Jun 30, 2026
eb3d60b
chore(deps): bump @types/node from 25.9.3 to 26.0.1 (#117)
algolj Jun 30, 2026
0ac2c4e
chore(deps): bump the linting group with 2 updates
dependabot[bot] Jul 6, 2026
a7ce3b6
chore(deps): bump @ai-sdk/openai from 4.0.2 to 4.0.8
dependabot[bot] Jul 6, 2026
f4cae31
chore(deps): bump the linting group with 2 updates (#118)
algolj Jul 6, 2026
82cbd0a
Merge branch 'develop' into dependabot/npm_and_yarn/ai-sdk/openai-4.0.8
algolj Jul 6, 2026
92af320
chore: update package versions in pnpm-lock.yaml
algolj Jul 6, 2026
89c0e04
chore(deps): bump @ai-sdk/openai from 4.0.2 to 4.0.8 (#119)
algolj Jul 6, 2026
8cf8d32
chore(deps): bump typedoc from 0.28.19 to 0.28.20
dependabot[bot] Jul 6, 2026
87e35b8
chore(deps): bump @ai-sdk/provider from 4.0.0 to 4.0.2
dependabot[bot] Jul 6, 2026
e583308
chore(deps): bump @ai-sdk/provider from 4.0.0 to 4.0.2 (#121)
algolj Jul 6, 2026
216101b
Merge branch 'develop' into dependabot/npm_and_yarn/typedoc-0.28.20
algolj Jul 6, 2026
cb042e3
chore(deps): bump typedoc from 0.28.19 to 0.28.20 (#122)
algolj Jul 6, 2026
f105501
feat: remove deprecated info
oparshyna Jul 8, 2026
3067cc0
feat: remove deprecated info (#123)
algolj Jul 10, 2026
616e592
chore(deps): bump the testing group with 2 updates
dependabot[bot] Jul 13, 2026
1b91b74
chore(deps): bump the linting group with 3 updates
dependabot[bot] Jul 13, 2026
8831b62
chore(deps): bump @ai-sdk/provider from 4.0.2 to 4.0.3
dependabot[bot] Jul 13, 2026
dac9aac
ci: pin npx CLI versions
art-tykh Jul 17, 2026
0b43f72
ci(deps): bump actions/setup-node from 6 to 7
dependabot[bot] Jul 20, 2026
3adf1cb
chore(deps): bump the testing group with 2 updates (#125)
algolj Jul 31, 2026
96c29b1
Merge branch 'develop' into dependabot/npm_and_yarn/linting-7d28c5bccc
algolj Jul 31, 2026
18564d0
chore(deps): bump the linting group with 3 updates (#126)
algolj Jul 31, 2026
cc054d5
Merge branch 'develop' into dependabot/npm_and_yarn/ai-sdk/provider-4…
algolj Jul 31, 2026
c9ec031
chore(deps): bump @types/node from 26.1.0 to 26.1.2
dependabot[bot] Jul 31, 2026
e4d5ac4
chore(deps): bump @ai-sdk/provider from 4.0.2 to 4.0.3 (#128)
algolj Jul 31, 2026
f3a7f47
Merge branch 'develop' into dependabot/npm_and_yarn/types/node-26.1.1
algolj Jul 31, 2026
7bec08c
chore(deps): bump @nestjs/common from 11.1.20 to 11.1.28
dependabot[bot] Jul 31, 2026
48ff15b
chore(deps): bump @types/node from 26.1.0 to 26.1.2 (#130)
algolj Jul 31, 2026
b7117fa
Merge branch 'develop' into dependabot/npm_and_yarn/nestjs/common-11.…
algolj Jul 31, 2026
a482a57
chore(deps): bump @ai-sdk/openai from 4.0.8 to 4.0.22
dependabot[bot] Jul 31, 2026
0c1abaf
chore(deps): bump @nestjs/common from 11.1.20 to 11.1.28 (#131)
algolj Jul 31, 2026
d1fa551
Merge branch 'develop' into ci/pin-npx
algolj Jul 31, 2026
5f936aa
chore(deps): bump the semantic-release group across 1 directory with …
dependabot[bot] Jul 31, 2026
ac835aa
ci: pin npx CLI versions (#132)
algolj Jul 31, 2026
a2432e8
Merge branch 'develop' into dependabot/github_actions/actions/setup-n…
algolj Jul 31, 2026
0cc05ef
ci(deps): bump actions/setup-node from 6 to 7 (#134)
algolj Jul 31, 2026
5f29fcd
Merge branch 'develop' into dependabot/npm_and_yarn/ai-sdk/openai-4.0.20
algolj Jul 31, 2026
93fef5d
chore(deps): bump @ai-sdk/openai from 4.0.8 to 4.0.22 (#135)
algolj Jul 31, 2026
44c2b61
Merge branch 'develop' into dependabot/npm_and_yarn/semantic-release-…
algolj Jul 31, 2026
0738e3f
chore(deps): update integrity hash for @babel/code-frame@7.29.7 in pn…
algolj Jul 31, 2026
6b76f48
chore(deps): bump the semantic-release group across 1 directory with …
algolj Jul 31, 2026
af1f0ab
feat: add AI voices endpoint to SDK
ci-macpaw Sep 17, 2026
cb837f4
fix(voices): align types and query params with OpenAPI spec
ci-macpaw Sep 17, 2026
e271873
feat: add AI credit balance endpoint to SDK
tkachenko Sep 18, 2026
108a18a
feat: add AI credit balance endpoint to SDK
tkachenko Sep 18, 2026
f20321c
feat: add multiprovider support
oparshyna Sep 18, 2026
9735cf8
test: disable retries for list() to surface RateLimitError immediately
algolj Sep 21, 2026
012aa4a
feat: update compatibility and README to include createVoiceClient
algolj Sep 21, 2026
8a9534a
docs: refine compatibility and README formatting for clarity
algolj Sep 21, 2026
87f0026
feat: update tests and documentation for createCreditBalanceClient in…
algolj Sep 21, 2026
7d77e81
Potential fix for pull request finding
tkachenko Sep 21, 2026
54d5e88
feat: add AI credit balance endpoint to SDK
tkachenko Sep 21, 2026
51802b7
feat: add AI credit balance endpoint to SDK (#163)
tkachenko Sep 21, 2026
c12048b
Merge remote-tracking branch 'origin/develop' into feature/ai-sdk-add…
oparshyna Sep 21, 2026
414ebb0
feat: add AI speech (text-to-speech) endpoint to SDK
oparshyna Sep 21, 2026
5d4c9ad
feat: add AI speech (text-to-speech) endpoint to SDK (#169)
oparshyna Sep 21, 2026
0d6fc8f
feat: voices and speech integration
oparshyna Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
Expand Down Expand Up @@ -85,7 +85,7 @@ jobs:

- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: '22.14.0' # npm trusted publishing requires Node 22.14+
cache: pnpm
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
id: semantic
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
run: pnpm exec semantic-release

- name: Release summary
if: steps.semantic.outputs.new_release_published == 'true'
Expand Down
53 changes: 35 additions & 18 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
# SDK ↔ AI Gateway API — Compatibility Check

This document relates **AI Gateway HTTP routes** to how **`@macpaw/ai-sdk`** talks to the gateway today. The SDK does not ship a typed HTTP client; it uses an OpenAI-compatible **provider** plus **`createGatewayFetch`** for arbitrary paths.
This document relates **AI Gateway HTTP routes** to how **`@macpaw/ai-sdk`** talks to the gateway today.

The SDK uses:

- an OpenAI-compatible **provider** (`createAIGatewayProvider` / `createGatewayProvider`) for Vercel `ai` generation APIs
- **`createGatewayFetch`** for arbitrary / multipart paths
- typed clients **`createVideoClient`**, **`createVoiceClient`**, and **`createSpeechClient`** for the video, voices, and speech Gateway routes

## Paths

Gateway routes are versioned under `/ai/v1/...` (from the host root). With `createGatewayFetch` the base already includes `/ai`, so pass the path relative to it (`/v1/...`). The Vercel/OpenAI stack issues requests under the same prefix automatically.
Gateway routes are versioned under `/ai/v1/...` (from the host root). With `createGatewayFetch` the base already includes `/ai`, so pass the path relative to it (`/v1/...`). The Vercel/OpenAI stack issues requests under the same prefix automatically. Typed clients use the same relative paths on a resolved `baseURL`.

| API | Full route (from host) | Relative path (base `…/ai`) | SDK surface |
| -------------------- | ---------------------------------- | ------------------------------- | -------------------------------- |
| Chat completions | `/ai/v1/chat/completions` | `/v1/chat/completions` | provider |
| Responses | `/ai/v1/responses` | `/v1/responses` | provider |
| Embeddings | `/ai/v1/embeddings` | `/v1/embeddings` | provider |
| Model info | `/ai/v1/model/info` | `/v1/model/info` | provider / `createGatewayFetch` |
| Images generations | `/ai/v1/images/generations` | `/v1/images/generations` | provider / `createGatewayFetch` |
| Images edits | `/ai/v1/images/edits` | `/v1/images/edits` | `createGatewayFetch` (multipart) |
| Audio transcriptions | `/ai/v1/audio/transcriptions` | `/v1/audio/transcriptions` | provider / `createGatewayFetch` |
| Videos create | `/ai/v1/videos` | `/v1/videos` | `createVideoClient.create` |
| Videos get | `/ai/v1/videos/{video_id}` | `/v1/videos/{video_id}` | `createVideoClient.get` |
| Videos content | `/ai/v1/videos/{video_id}/content` | `/v1/videos/{video_id}/content` | `createVideoClient.getContent` |
| Voices list | `/ai/v1/voices` | `/v1/voices` | `createVoiceClient.list` |
| Audio speech create | `/ai/v1/audio/speech` | `/v1/audio/speech` | `createSpeechClient.create` |

`createVoiceClient.list()` sends `provider` (required) and optional `next_page_token` as query params.

| API | Full route (from host) | Relative path (base `…/ai`) |
| -------------------- | ----------------------------- | --------------------------- |
| Chat completions | `/ai/v1/chat/completions` | `/v1/chat/completions` |
| Responses | `/ai/v1/responses` | `/v1/responses` |
| Embeddings | `/ai/v1/embeddings` | `/v1/embeddings` |
| Model info | `/ai/v1/model/info` | `/v1/model/info` |
| Images generations | `/ai/v1/images/generations` | `/v1/images/generations` |
| Images edits | `/ai/v1/images/edits` | `/v1/images/edits` |
| Audio transcriptions | `/ai/v1/audio/transcriptions` | `/v1/audio/transcriptions` |
| Audio translations | `/ai/v1/audio/translations` | `/v1/audio/translations` |
`createSpeechClient.create()` requires `model`, `input`, and `voice`; the response is always streaming (SSE, or the provider's own raw stream when `raw_provider_response: true`), so `create()` returns the raw `Response` rather than parsed JSON.

## Base URL

- **SDK** `DEFAULT_BASE_URLS.production`: `https://api.macpaw.com/ai`
- Other environments: pass `baseURL` explicitly on `GatewayProviderSettings` / `createGatewayFetch`.
- Other environments: pass `baseURL` explicitly on `GatewayProviderSettings` / `createGatewayFetch` / the typed clients.

## Authorization

Expand All @@ -33,21 +47,24 @@ Gateway expects `Authorization: Bearer <token>`. The SDK sets it from `getAuthTo
1. Gateway JSON: `statusCode`, `message`, `timestamp`, `code`, `path`, optional `errors[]`
2. OpenAI proxy shape: `{ error: { message, type, code } }`

Codes map to `ErrorCode` and typed errors (`AuthError`, `CreditsError`, …).
Codes map to `ErrorCode` and typed errors (`AuthError`, `CreditsError`, …). `PAYMENT_REQUIRED` (used on **402** by the audio speech endpoint) and `INSUFFICIENT_CREDITS` both normalize to `CreditsError` / `ErrorCode.InsufficientCredits`.

## Multipart and streaming

- **Multipart** (`multipart/form-data`): build `FormData` in app code and `POST` via `createGatewayFetch`.
- **SSE**: consumed by upstream **`ai`** / provider streaming, not by a separate SSE helper in this package’s public API.
- **SSE**: consumed by upstream **`ai`** / provider streaming, not by a separate SSE helper in this package’s public API — `createSpeechClient.create()` follows the same rule and returns the raw `Response` for callers to read `.body` as a stream, the same way `createVideoClient.getContent()` returns raw binary content.

## Provider vs custom fetch
## Provider vs custom fetch vs typed clients

| Style | Mechanism |
| ---------------------------------------------------------- | --------------------------------------------------- |
| `generateText`, `streamText`, tools, embeddings via Vercel | `createAIGatewayProvider` / `createGatewayProvider` |
| Custom HTTP (multipart, bespoke routes) | `createGatewayFetch` + `fetch` API |
| Same gateway, same auth/retry/middleware | Both use `GatewayProviderSettings` semantics |
| Video generation (create / poll / content) | `createVideoClient` |
| Voice listing (paginated) | `createVoiceClient` |
| Speech generation (streaming text-to-speech) | `createSpeechClient` |
| Same gateway, same auth/retry/middleware | All use `GatewayProviderSettings` semantics |

---

**Conclusion:** Wire format, base URL, auth, and error shapes stay aligned with AI Gateway. The SDK surface is intentionally thin: provider + fetch bridge + NestJS helpers.
**Conclusion:** Wire format, base URL, auth, and error shapes stay aligned with AI Gateway. The SDK surface is provider + fetch bridge + typed video/voices/speech clients + NestJS helpers.
94 changes: 87 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
[![npm version](https://img.shields.io/npm/v/%40macpaw%2Fai-sdk)](https://www.npmjs.com/package/@macpaw/ai-sdk)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)

Thin **Vercel AI SDK** extension for **MacPaw AI Gateway**: OpenAI-compatible providers (`createAIGatewayProvider`, `createGatewayProvider`), a **`createGatewayFetch`** bridge for any HTTP client, a **`createVideoClient`** for video generation, shared **auth / retry / middleware / errors**, and optional **NestJS** wiring.
Thin **Vercel AI SDK** extension for **MacPaw AI Gateway**: OpenAI-compatible providers (`createAIGatewayProvider`, `createGatewayProvider`), a **`createGatewayFetch`** bridge for any HTTP client, **`createVideoClient`** / **`createVoiceClient`** / **`createSpeechClient`** for video generation, voice listing, and text-to-speech, a **`createCreditBalanceClient`** for querying AI credit balances, shared **auth / retry / middleware / errors**, and optional **NestJS** wiring.

Core generation APIs stay on upstream **`ai`** and **`@ai-sdk/*`**. This package only adds Gateway-specific construction and the fetch pipeline.

## Package entry points

| Import | Use for |
| ------------------------- | ------------------------------------------------------------------------------------------ |
| `@macpaw/ai-sdk` | **Canonical** — providers, `createGatewayFetch`, `createVideoClient`, errors, config types |
| `@macpaw/ai-sdk/provider` | **Alias** of the root entry (same `dist`; for older snippets) |
| `@macpaw/ai-sdk/nestjs` | `AIGatewayModule`, `@InjectAIGateway()`, `AIGatewayExceptionFilter` |
| Import | Use for |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `@macpaw/ai-sdk` | **Canonical** — providers, `createGatewayFetch`, `createVideoClient`, `createVoiceClient`, `createSpeechClient`, `createCreditBalanceClient`, errors, config types |
| `@macpaw/ai-sdk/provider` | **Alias** of the root entry (same `dist`; for older snippets) |
| `@macpaw/ai-sdk/nestjs` | `AIGatewayModule`, `@InjectAIGateway()`, `AIGatewayExceptionFilter` |

Upstream **`ai`**, **`@ai-sdk/openai`**, **`@ai-sdk/react`** (or **`ai/react`**) remain the home for Vercel primitives and React hooks.

Expand Down Expand Up @@ -69,11 +69,14 @@ for await (const delta of result.textStream) {
- **Request ID** — `X-Request-ID` on Gateway requests when missing
- **Timeout** — per attempt, combined with caller `AbortSignal`
- **Video generation** — `createVideoClient` wraps the Gateway video endpoints (create job, poll status, fetch content)
- **Voices** — `createVoiceClient` lists voices from Gateway providers (paginated `GET /v1/voices`)
- **Speech (text-to-speech)** — `createSpeechClient` generates streaming speech audio (`POST /v1/audio/speech`)
- **Credit balances** — `createCreditBalanceClient` fetches active AI credit balances for the authenticated user
- **Tree-shakeable** — ESM + CJS

## Configuration (`GatewayProviderSettings`)

Used by `createAIGatewayProvider`, `createGatewayProvider`, `createGatewayFetch`, `createVideoClient`, and Nest `AIGatewayModule`.
Used by `createAIGatewayProvider`, `createGatewayProvider`, `createGatewayFetch`, `createVideoClient`, `createVoiceClient`, `createSpeechClient`, `createCreditBalanceClient`, and Nest `AIGatewayModule`.

| Field | Purpose |
| -------------- | ---------------------------------------------------------------- |
Expand Down Expand Up @@ -189,6 +192,83 @@ const buffer = await res.arrayBuffer();

`create` does **not** retry on 5xx — POST video jobs are non-idempotent. Auth retry (401 → fresh token) still applies.

## `createVoiceClient` — list voices

Wraps `GET /v1/voices`. Same auth, retry, and error normalization as the other clients. `provider` is required; `"elevenlabs"` is the only Gateway provider today, but any future provider id can be passed as a string.

```ts
import { createVoiceClient } from '@macpaw/ai-sdk';

const voices = createVoiceClient({
env: 'production',
getAuthToken: async () => (await getSetappSession()).accessToken,
});

const first = await voices.list({ provider: 'elevenlabs' });
for (const voice of first.voices) {
console.log(voice.voice_id);
}

if (first.has_more) {
const next = await voices.list({
provider: 'elevenlabs',
next_page_token: first.next_page_token,
});
console.log(next.voices.length);
}
```

Each `Voice` always has `voice_id`; other fields are provider-specific and passed through as-is. `list()` is a GET, so config-level retries on **429** / **5xx** still apply. Auth retry (401 → fresh token) also applies.

## `createSpeechClient` — text-to-speech

Wraps `POST /v1/audio/speech`. **Streaming only** — the response is always Server-Sent Events (or the provider's own raw stream when `raw_provider_response: true`), never a single buffered file, so `create()` returns the raw `Response` for callers to consume as a stream. `model` (`provider/model`), `input`, and `voice` are required; every other parameter is validated per model by the Gateway, so `response_format` and any additional model-specific fields are passed straight through.

```ts
import { createSpeechClient } from '@macpaw/ai-sdk';

const speech = createSpeechClient({
env: 'production',
getAuthToken: async () => (await getSetappSession()).accessToken,
});

const response = await speech.create({
model: 'openai/gpt-4o-mini-tts',
input: 'Today is a wonderful day to build something people love!',
voice: 'alloy',
response_format: 'mp3',
});

// response.body is a ReadableStream of `text/event-stream` bytes by default —
// parse it as SSE to read `speech.audio.delta` / `speech.audio.done` events.
```

`create()` disables config-level retries (POST is non-idempotent — retrying on 5xx risks duplicate provider-side generation and credit charges). Auth retry (401 → fresh token) still applies. Set `raw_provider_response: true` to receive the selected provider's own stream unmodified (for ElevenLabs, its native JSON stream including alignment data the normalized events cannot carry).

## `createCreditBalanceClient` — AI credit balances

Fetches all active AI credit balances for the user identified by the Bearer token. Balances are ordered FEFO (First Expiring, First Out) and include optional membership metadata.

```ts
import { createCreditBalanceClient } from '@macpaw/ai-sdk';

const balance = createCreditBalanceClient({
env: 'production',
getAuthToken: async () => (await getSetappSession()).accessToken,
});

const { data } = await balance.getBalances();

console.log(data.totalAvailable.amount); // e.g. "1000000"
console.log(data.totalAvailable.currency); // "MACPAW_CREDITS"

for (const b of data.balances) {
console.log(b.id, b.currentValue.amount, b.expiresAt);
}
```

`getBalances` calls `GET /entitlement/v1/ai/credits/balances` on the gateway's root domain (derived from your `baseURL` origin). The response shape mirrors the API spec exactly — `data.balances`, `data.totalAvailable`, and `data.totalInitialAmount`.

## Middleware

```ts
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@
"url": "https://github.com/MacPaw/ai-sdk-typescript/issues"
},
"devDependencies": {
"@ai-sdk/openai": "3.0.73",
"@ai-sdk/provider": "3.0.10",
"@ai-sdk/openai": "4.0.22",
"@ai-sdk/provider": "4.0.3",
"@eslint/js": "^10.0.1",
"@nestjs/common": "^11.1.17",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/node": "^25.5.0",
"@semantic-release/changelog": "^7.0.0",
"@semantic-release/git": "^11.0.1",
"@types/node": "^26.0.1",
"eslint": "^10.1.0",
"prettier": "^3.8.1",
"rxjs": "^7.8.2",
Expand Down
Loading
Loading