aisubs 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 - 2026-08-16
4
+
5
+ - Add a universal OpenAI-compatible Chat Completions surface with translation
6
+ to supported native Responses, Anthropic Messages, and Google
7
+ `generateContent` endpoints.
8
+ - Add native OpenAI Realtime WebSocket tunnelling and account-scoped model
9
+ discovery with capability metadata.
10
+ - Add Claude, OpenCode Go, and OpenCode Zen to the dashboard and examples;
11
+ improve provider model metadata and usage reporting.
12
+ - Document desktop-client and SDK setup, local-key handling, compatibility
13
+ behavior, and cross-protocol streaming limits.
14
+
15
+ ## 0.2.0 - 2026-08-15
16
+
17
+ - Previous release.
package/README.md CHANGED
@@ -6,13 +6,27 @@
6
6
  </table>
7
7
 
8
8
  AISubs connects provider accounts once and exposes each account through a local,
9
- account-scoped API. Use it from the official OpenAI or Anthropic SDK, Vercel AI SDK,
10
- TanStack AI, Python, cURL, or an app that accepts a custom API base URL.
9
+ account-scoped API. Every generative account has an OpenAI-compatible surface,
10
+ while native Responses, Anthropic Messages, Google `generateContent`, Realtime,
11
+ and provider-specific endpoints remain available. Use it from an SDK, cURL, or
12
+ an app that accepts a custom API base URL.
11
13
 
12
14
  <p align="center">
13
15
  <img src="./public/aisubs-dashboard.png" alt="AI Subs dashboard with provider connections and local API access" width="100%" />
14
16
  </p>
15
17
 
18
+ <p align="center">
19
+ <img src="./public/aisubs-chatgpt-account.png" alt="AISubs ChatGPT demo account details" width="100%" />
20
+ </p>
21
+
22
+ <p align="center">
23
+ <img src="./public/aisubs-copilot-account.png" alt="AISubs GitHub Copilot demo account details" width="100%" />
24
+ </p>
25
+
26
+ <p align="center">
27
+ <img src="./public/aisubs-grok-account.png" alt="AISubs Grok demo account details" width="100%" />
28
+ </p>
29
+
16
30
  > Credentials, API keys, and requests remain on your computer. AISubs collects no
17
31
  > telemetry or analytics. The dashboard keeps up to 200 redacted account request logs in memory for debugging.
18
32
 
@@ -35,6 +49,11 @@ key: reveal it, copy it, or deliberately regenerate it there.
35
49
  The default URL is `http://127.0.0.1:4319`. Credentials and the API key are stored
36
50
  under `~/.aisubs` and reused on later starts.
37
51
 
52
+ Supported subscription/account providers are ChatGPT, Claude, GitHub Copilot,
53
+ Grok, OpenCode Go, and OpenCode Zen. Google `generateContent` is a supported
54
+ wire protocol only for Google models surfaced by one of those connected
55
+ providers; AISubs does not connect or claim a Google subscription account.
56
+
38
57
  ## Use an account from any compatible app
39
58
 
40
59
  Open an account in the dashboard and copy its base URL:
@@ -61,37 +80,98 @@ export OPENAI_API_KEY="aisubs_..."
61
80
  AISubs removes its local key before forwarding a request and adds only the
62
81
  selected account's provider credential.
63
82
 
64
- ### Compatibility contract
83
+ ## Desktop app examples
84
+
85
+ Keep AISubs running, then give the app the three values from the connected
86
+ account: its base URL, the AISubs API key, and an exact model ID.
87
+
88
+ ### [Handy](https://handy.computer/download) (macOS)
89
+
90
+ Open **Post Process** and set:
91
+
92
+ | Handy field | Value |
93
+ | ----------- | -------------------------------------------------------- |
94
+ | Provider | `Custom` |
95
+ | Base URL | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1` |
96
+ | API Key | Your `aisubs_...` key from the AISubs dashboard |
97
+ | Model | An exact Dashboard model ID, for example `gpt-5.6-terra` |
98
+
99
+ Click Handy’s refresh button beside **Model**, select the model, then choose a
100
+ prompt and test it. Replace `chatgpt/default` with the provider and account
101
+ segment copied from your AISubs dashboard when using a different account.
102
+
103
+ ### [Raycast](https://www.raycast.com/download) AI custom provider
104
+
105
+ Create or edit `~/.config/raycast/ai/providers.yaml` and add a provider like
106
+ this. Keep the API key private—use the value from your AISubs dashboard, never
107
+ one copied from an example or screenshot.
108
+
109
+ ```yaml
110
+ providers:
111
+ - id: ai-subs
112
+ name: AI Subs
113
+ base_url: http://127.0.0.1:4319/aisubs/chatgpt/default/v1
114
+ api_keys:
115
+ default: aisubs_REPLACE_WITH_YOUR_DASHBOARD_KEY
116
+ models:
117
+ - id: gpt-5.6-terra
118
+ name: AISubs GPT-5.6 Terra
119
+ context: 272000
120
+ description: ChatGPT subscription via AISubs
121
+ abilities:
122
+ vision:
123
+ supported: true
124
+ system_message:
125
+ supported: true
126
+ tools:
127
+ supported: true
128
+ reasoning_effort:
129
+ supported: true
130
+ ```
131
+
132
+ Restart Raycast after saving. Replace the base URL, model ID, context, and
133
+ abilities with the values shown for your connected account. Do not enable an
134
+ ability that the selected model does not report.
135
+
136
+ [Cline](https://cline.bot/) also accepts a custom OpenAI-compatible endpoint;
137
+ use the same base URL, local AISubs key, and model ID. Exact settings labels can
138
+ change between client releases, so the account dashboard remains the source of
139
+ truth for the three values.
65
140
 
66
- AISubs preserves the provider's native request and response protocol. It also
67
- provides a focused, non-streaming text Chat Completions adapter for ChatGPT
68
- accounts so apps such as [Handy](https://github.com/cjpais/Handy) can use them.
69
-
70
- | Provider/model protocol | Base URL | Supported request path |
71
- | ---------------------------------- | --------------------------- | --------------------------------------- |
72
- | OpenAI Responses | Account URL ending in `/v1` | `POST /responses` |
73
- | OpenAI-compatible Chat Completions | Account URL ending in `/v1` | `POST /chat/completions` |
74
- | Anthropic Messages | Account URL ending in `/v1` | `POST /messages` |
75
- | Google generateContent | Account URL ending in `/v1` | `POST /models/MODEL_ID:generateContent` |
76
- | Model discovery | Account URL ending in `/v1` | `GET /models` |
77
-
78
- Embeddings are intentionally not exposed. The ChatGPT compatibility adapter
79
- supports text messages and JSON-schema response formats; tools, images, audio,
80
- and streaming Chat Completions remain native-Responses-only features.
81
-
82
- ### Handy
83
-
84
- Choose **Custom** in Handy and copy these values from the ChatGPT account page:
85
-
86
- ```text
87
- Base URL: http://127.0.0.1:4319/aisubs/chatgpt/ACCOUNT/v1
88
- API key: the persistent key shown on the AISubs dashboard
89
- Model: an exact model ID shown for the account
90
- ```
141
+ ### Compatibility contract
91
142
 
92
- Handy sends `POST /chat/completions` with streaming disabled. AISubs translates
93
- that request to ChatGPT Responses and returns the `choices[0].message.content`
94
- shape Handy reads.
143
+ Use Chat Completions when an app offers only an “OpenAI-compatible” provider.
144
+ AISubs discovers the selected model's native protocol and translates the
145
+ request and response when necessary. Calling a model's native protocol remains
146
+ a pass-through, which preserves provider-specific fields and streaming events.
147
+
148
+ | Integration path | Example full URL | Request example |
149
+ | ---------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------- |
150
+ | OpenAI Responses | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/responses` | `POST /responses` |
151
+ | OpenAI-compatible Chat Completions | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/chat/completions` | `POST /chat/completions` |
152
+ | Anthropic Messages | `http://127.0.0.1:4319/aisubs/claude/default/v1/messages` | `POST /messages` |
153
+ | Google generateContent | `http://127.0.0.1:4319/aisubs/opencode-zen/default/v1/models/MODEL_ID:generateContent` | `POST /models/MODEL_ID:generateContent` |
154
+ | Model discovery | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/models` | `GET /models` |
155
+ | Model details | `http://127.0.0.1:4319/aisubs/chatgpt/default/v1/models/MODEL_ID` | `GET /models/MODEL_ID` |
156
+ | OpenAI Realtime | `ws://127.0.0.1:4319/aisubs/PROVIDER/ACCOUNT/v1/realtime?model=MODEL_ID` | WebSocket |
157
+
158
+ Replace the provider, account (`default`), and model ID with the values shown
159
+ for your connected account in the AISubs dashboard.
160
+
161
+ Cross-protocol translation covers text and system messages, streaming, function
162
+ tools and tool results, image/file/audio input where both protocols support it,
163
+ JSON-schema output, reasoning effort, stop conditions, and usage including
164
+ cached and reasoning tokens. If a feature has no safe equivalent, AISubs returns
165
+ an explicit `unsupported_feature` error instead of silently dropping it.
166
+
167
+ Provider-native routes—including embeddings, image/audio generation, batches,
168
+ files, and Realtime—are forwarded when that connected provider exposes them.
169
+ AISubs cannot add a capability that the provider, subscription, or selected
170
+ model does not have. Native requests preserve provider-specific cache controls;
171
+ translated requests preserve shared fields and cache-usage counters. Other than
172
+ Responses-to-Chat streaming, cross-protocol streams may be emitted after the
173
+ native response completes; use the native endpoint when event-level streaming
174
+ or provider-specific fields matter.
95
175
 
96
176
  ## SDK examples
97
177
 
@@ -102,7 +182,7 @@ export AISUBS_API_KEY="aisubs_..."
102
182
  ```
103
183
 
104
184
  <details>
105
- <summary><strong>Official OpenAI JavaScript SDK Responses and Chat Completions</strong></summary>
185
+ <summary><strong>Official OpenAI JavaScript SDK: Responses and Chat Completions</strong></summary>
106
186
 
107
187
  Install:
108
188
 
@@ -131,8 +211,8 @@ const stream = await client.responses.create({
131
211
  for await (const event of stream) console.log(event);
132
212
  ```
133
213
 
134
- Chat Completions uses the same client with an account/model that reports that
135
- endpoint:
214
+ Chat Completions uses the same client with any generative model. AISubs
215
+ translates to the selected model's native protocol when needed:
136
216
 
137
217
  ```js
138
218
  const response = await client.chat.completions.create({
@@ -206,16 +286,17 @@ const result = streamText({
206
286
  for await (const text of result.textStream) process.stdout.write(text);
207
287
  ```
208
288
 
209
- For Chat Completions, install `@ai-sdk/openai-compatible`, create the provider
210
- with the same account base URL, and select the model with `provider("MODEL_ID")`.
289
+ For the universal Chat Completions surface, install
290
+ `@ai-sdk/openai-compatible`, create the provider with the same account base URL,
291
+ and select the model with `provider("MODEL_ID")`.
211
292
 
212
293
  </details>
213
294
 
214
295
  <details>
215
- <summary><strong>TanStack AI Chat Completions</strong></summary>
296
+ <summary><strong>TanStack AI: Chat Completions</strong></summary>
216
297
 
217
- TanStack's generic compatibility adapter targets Chat Completions. Use it only
218
- for a model that lists `chat/completions`.
298
+ TanStack's generic compatibility adapter targets Chat Completions. AISubs
299
+ translates it to the selected model's native protocol.
219
300
 
220
301
  ```bash
221
302
  nub install @tanstack/ai @tanstack/ai-openai
@@ -271,7 +352,7 @@ print(response.choices[0].message.content)
271
352
  </details>
272
353
 
273
354
  <details>
274
- <summary><strong>cURL Responses, Chat Completions, Anthropic, and Google</strong></summary>
355
+ <summary><strong>cURL: Responses, Chat Completions, Anthropic, and Google</strong></summary>
275
356
 
276
357
  ```bash
277
358
  curl "http://127.0.0.1:4319/aisubs/chatgpt/personal/v1/responses" \
@@ -313,7 +394,9 @@ curl "http://127.0.0.1:4319/aisubs/opencode-zen/lab/v1/models/MODEL_ID_FROM_DASH
313
394
  ## Direct AISubs SDK
314
395
 
315
396
  Use the in-process SDK when AISubs is part of your trusted Node.js backend. It
316
- needs no local server or AISubs API key.
397
+ needs no local server, AISubs API key, or app-specific integration. Direct SDK
398
+ requests use the provider-native endpoint; the local server is the surface that
399
+ adds cross-protocol compatibility for third-party clients.
317
400
 
318
401
  Install the package in a project:
319
402
 
@@ -337,8 +420,12 @@ if (!(await account.status()).authenticated) {
337
420
  }
338
421
 
339
422
  const catalog = await account.getModels();
340
- const model = catalog?.models.find((item) => item.selectable !== false)?.id;
341
- if (!model) throw new Error("No selectable model is available");
423
+ const selected = catalog?.models.find((item) => item.selectable !== false);
424
+ if (!selected) throw new Error("No selectable model is available");
425
+
426
+ // ChatGPT models are native Responses models. Other providers report their
427
+ // native endpoint in selected.endpoints; use the matching native SDK adapter.
428
+ const model = selected.id;
342
429
 
343
430
  const response = await account.proxy("responses", {
344
431
  method: "POST",
@@ -354,6 +441,11 @@ Available provider factories are `chatGptProvider()`, `claudeProvider()`,
354
441
  `copilotProvider()`, `grokProvider()`, `openCodeGoProvider()`, and
355
442
  `openCodeZenProvider()`.
356
443
 
444
+ `getModels()` is cached for five minutes and `getUsage()` for 15 seconds by
445
+ default, with concurrent calls deduplicated. Account changes invalidate both.
446
+ Pass `modelsCacheTtlMs` or `usageCacheTtlMs` to `createSubscriptionAuth()` when
447
+ an in-process application needs different metadata freshness.
448
+
357
449
  Useful account methods:
358
450
 
359
451
  | Method | Purpose |
@@ -539,9 +631,12 @@ POST /v1/api-key/regenerate # dashboard session only
539
631
  * /aisubs/:provider/:account/v1/*
540
632
  ```
541
633
 
542
- The provider and account routes require `Authorization: Bearer AISUBS_API_KEY`
543
- or `x-api-key: AISUBS_API_KEY`. Regenerating the key immediately invalidates the
544
- old key.
634
+ The provider and account routes accept `Authorization: Bearer AISUBS_API_KEY`
635
+ or `x-api-key: AISUBS_API_KEY`. A Google-protocol client can instead put that
636
+ same local AISubs key in `x-goog-api-key` or `?key=`; this does not represent a
637
+ Google credential or subscription. Local credentials are removed from headers
638
+ and query strings before proxying. Regenerating the key immediately invalidates
639
+ the old key.
545
640
 
546
641
  ## Storage and security
547
642
 
package/dist/auth.d.ts CHANGED
@@ -66,6 +66,8 @@ export declare class SubscriptionAuth {
66
66
  details(provider: ProviderId, account?: string, signal?: AbortSignal): Promise<SubscriptionAccountDetails>;
67
67
  fetch(provider: ProviderId, input: string | URL | Request, init?: RequestInit, account?: string): Promise<Response>;
68
68
  proxy(provider: ProviderId, account: string, path: string, init?: RequestInit): Promise<Response>;
69
+ /** Build an authorized direct-provider request for transports such as WebSocket. */
70
+ authorizeProxyRequest(provider: ProviderId, account: string, path: string, init?: RequestInit): Promise<Request>;
69
71
  getUsage(provider: ProviderId, account?: string, callerSignal?: AbortSignal): Promise<ProviderUsage | null>;
70
72
  getModels(provider: ProviderId, account?: string, callerSignal?: AbortSignal): Promise<ProviderModels | null>;
71
73
  account(provider: ProviderId, account: string): SubscriptionAccount;
package/dist/auth.js CHANGED
@@ -421,6 +421,22 @@ export class SubscriptionAuth {
421
421
  const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
422
422
  return this.fetch(provider, new URL(path, base), init, accountKey);
423
423
  }
424
+ /** Build an authorized direct-provider request for transports such as WebSocket. */
425
+ async authorizeProxyRequest(provider, account, path, init) {
426
+ const adapter = this.adapter(provider);
427
+ const accountKey = normalizeAccountKey(account);
428
+ if (adapter.proxy) {
429
+ throw new Error(`${provider} does not expose a direct transport endpoint`);
430
+ }
431
+ const credential = await this.credential(provider, accountKey);
432
+ const baseUrl = typeof adapter.proxyBaseUrl === "function"
433
+ ? adapter.proxyBaseUrl(credential)
434
+ : adapter.proxyBaseUrl;
435
+ if (!baseUrl)
436
+ throw new Error(`${provider} does not expose a direct API endpoint`);
437
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
438
+ return adapter.authorize(createRequest(new URL(path, base), init), credential);
439
+ }
424
440
  async getUsage(provider, account = DEFAULT_ACCOUNT, callerSignal) {
425
441
  const adapter = this.adapter(provider);
426
442
  if (!adapter.getUsage)
@@ -0,0 +1,14 @@
1
+ import type { SubscriptionAuth } from "./auth.js";
2
+ import type { ProviderId } from "./types.js";
3
+ export type WireProtocol = "responses" | "chat/completions" | "messages" | "google";
4
+ export declare class CompatibilityError extends Error {
5
+ readonly code: string;
6
+ readonly status: number;
7
+ constructor(message: string, code?: string, status?: number);
8
+ }
9
+ export declare function requestProtocol(path: string): {
10
+ protocol: WireProtocol;
11
+ model?: string;
12
+ stream?: boolean;
13
+ } | null;
14
+ export declare function proxyCompatible(auth: SubscriptionAuth, provider: ProviderId, account: string, path: string, body: Buffer, headers: Headers, signal?: AbortSignal): Promise<Response | null>;