@ashx-j/lunr-ai 0.2.26 → 0.2.27

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mario Zechner
4
+ Copyright (c) 2026 ashx-j
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @earendil-works/pi-ai
1
+ # @ashx-j/lunr-ai
2
2
 
3
3
  Unified LLM API with provider collections, automatic auth resolution, token and cost tracking, and simple context persistence and hand-off to other models mid-session.
4
4
 
@@ -47,6 +47,7 @@ Unified LLM API with provider collections, automatic auth resolution, token and
47
47
  - [Browser Usage](#browser-usage)
48
48
  - [Bundling and Tree Shaking](#bundling-and-tree-shaking)
49
49
  - [OAuth Providers](#oauth-providers)
50
+ - [Anthropic subscriptions](#anthropic-subscriptions)
50
51
  - [Vertex AI](#vertex-ai)
51
52
  - [CLI Login](#cli-login)
52
53
  - [Programmatic OAuth](#programmatic-oauth)
@@ -90,18 +91,18 @@ Unified LLM API with provider collections, automatic auth resolution, token and
90
91
  ## Installation
91
92
 
92
93
  ```bash
93
- npm install @earendil-works/pi-ai
94
+ npm install @ashx-j/lunr-ai
94
95
  ```
95
96
 
96
- TypeBox exports are re-exported from `@earendil-works/pi-ai`: `Type`, `Static`, and `TSchema`.
97
+ TypeBox exports are re-exported from `@ashx-j/lunr-ai`: `Type`, `Static`, and `TSchema`.
97
98
 
98
99
  ## Quick Start
99
100
 
100
101
  You build a `Models` collection of providers and stream through it. The quickest start registers every built-in provider; apps that care about bundle size register individual providers instead (see [Provider Factories](#provider-factories) and [Bundling and Tree Shaking](#bundling-and-tree-shaking)).
101
102
 
102
103
  ```typescript
103
- import { Type, type Context, type Tool } from '@earendil-works/pi-ai';
104
- import { builtinModels } from '@earendil-works/pi-ai/providers/all';
104
+ import { Type, type Context, type Tool } from '@ashx-j/lunr-ai';
105
+ import { builtinModels } from '@ashx-j/lunr-ai/providers/all';
105
106
 
106
107
  // A Models collection with every built-in provider registered
107
108
  const models = builtinModels();
@@ -236,10 +237,11 @@ Providers internally share **API implementations** (the wire protocols): Anthrop
236
237
  For apps that only need specific providers, there is one factory per built-in provider, each a subpath import that pulls only that provider's catalog:
237
238
 
238
239
  ```typescript
239
- import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';
240
- import { openaiProvider } from '@earendil-works/pi-ai/providers/openai';
241
- import { openrouterProvider } from '@earendil-works/pi-ai/providers/openrouter';
242
- import { amazonBedrockProvider } from '@earendil-works/pi-ai/providers/amazon-bedrock';
240
+ import { createModels } from '@ashx-j/lunr-ai';
241
+ import { anthropicProvider } from '@ashx-j/lunr-ai/providers/anthropic';
242
+ import { openaiProvider } from '@ashx-j/lunr-ai/providers/openai';
243
+ import { openrouterProvider } from '@ashx-j/lunr-ai/providers/openrouter';
244
+ import { amazonBedrockProvider } from '@ashx-j/lunr-ai/providers/amazon-bedrock';
243
245
  // ...one module per provider in the Supported Providers list
244
246
 
245
247
  const models = createModels();
@@ -254,7 +256,7 @@ Provider factories import their model catalog and a lazy API wrapper. They do no
254
256
  For apps that want everything (as in Quick Start):
255
257
 
256
258
  ```typescript
257
- import { builtinModels } from '@earendil-works/pi-ai/providers/all';
259
+ import { builtinModels } from '@ashx-j/lunr-ai/providers/all';
258
260
 
259
261
  const models = builtinModels(); // a Models collection with every built-in provider registered
260
262
  ```
@@ -285,7 +287,7 @@ for (const m of anthropicModels) {
285
287
  Dynamically listed models are typed `Model<Api>`. Narrow with the `hasApi()` guard when you need API-specific option typing:
286
288
 
287
289
  ```typescript
288
- import { hasApi } from '@earendil-works/pi-ai';
290
+ import { hasApi } from '@ashx-j/lunr-ai';
289
291
 
290
292
  const m = models.getModel('anthropic', 'claude-sonnet-4-5');
291
293
  if (m && hasApi(m, 'anthropic-messages')) {
@@ -299,7 +301,7 @@ if (m && hasApi(m, 'anthropic-messages')) {
299
301
  For tooling that wants the generated built-in catalog with full literal typing (provider and model IDs auto-complete), independent of any collection:
300
302
 
301
303
  ```typescript
302
- import { getBuiltinModel, getBuiltinModels, getBuiltinProviders } from '@earendil-works/pi-ai/providers/all';
304
+ import { getBuiltinModel, getBuiltinModels, getBuiltinProviders } from '@ashx-j/lunr-ai/providers/all';
303
305
 
304
306
  const model = getBuiltinModel('openai', 'gpt-4o-mini'); // typed Model<'openai-responses'>
305
307
  const providers = getBuiltinProviders();
@@ -312,12 +314,12 @@ Providers may have dynamic model lists (a llama.cpp server, a live OpenRouter li
312
314
 
313
315
  ```typescript
314
316
  // getModels() returns the last-known list (empty before the first refresh)
315
- await models.refresh('llamacpp'); // fetch one provider's list; rejects on failure
316
- await models.refresh(); // refresh all providers concurrently, best-effort
317
+ const result = await models.refresh(); // refresh configured dynamic providers concurrently
318
+ for (const [provider, error] of result.errors) console.error(provider, error);
317
319
  const fresh = models.getModel('llamacpp', 'qwen3-30b');
318
320
  ```
319
321
 
320
- Static built-in providers are no-ops for `refresh()`. See [createProvider()](#createprovider) for building a dynamic provider.
322
+ `refresh()` takes an options object with `allowNetwork`, `force`, and `signal`. It returns per-provider errors and an `aborted` flag. Static providers are no-ops. See [createProvider()](#createprovider) for building a dynamic provider.
321
323
 
322
324
  ## Auth
323
325
 
@@ -377,10 +379,10 @@ Header names are merged case-insensitively. Explicit headers override auth/model
377
379
 
378
380
  ### Credential Store
379
381
 
380
- Stored credentials (API keys entered interactively, OAuth tokens) live in a `CredentialStore` — one type-tagged credential per provider. pi-ai ships an in-memory default; apps inject persistent storage:
382
+ Stored credentials (API keys entered interactively, OAuth tokens) live in a `CredentialStore` — one type-tagged credential per provider. lunr-ai ships an in-memory default; apps inject persistent storage:
381
383
 
382
384
  ```typescript
383
- import { createModels, type CredentialStore } from '@earendil-works/pi-ai';
385
+ import { createModels, type CredentialStore } from '@ashx-j/lunr-ai';
384
386
 
385
387
  const models = createModels({ credentials: myFileBackedStore });
386
388
  // builtinModels() takes the same options:
@@ -389,7 +391,7 @@ const models = createModels({ credentials: myFileBackedStore });
389
391
 
390
392
  The contract is small: `read(providerId)`, `list()` for non-secret `{ providerId, type }` metadata, `modify(providerId, fn)` (the only write path — a serialized read-modify-write), and `delete(providerId)`. Enumeration must not resolve secrets or execute configured key commands. OAuth token refresh runs inside `modify`, so concurrent requests and processes cannot double-refresh a rotated token. A stored credential *owns* its provider: environment variables are only consulted when nothing is stored, and a failed refresh never silently falls back to an env key.
391
393
 
392
- API-key credentials use the same discriminator as pi's `auth.json` and can carry provider-scoped env/config values:
394
+ API-key credentials use the same discriminator as lunR's `auth.json` and can carry provider-scoped env/config values:
393
395
 
394
396
  ```typescript
395
397
  const credential = {
@@ -411,7 +413,7 @@ Built-in providers resolve these env vars (Node.js; in browsers pass `apiKey` ex
411
413
  | OpenAI | `OPENAI_API_KEY` |
412
414
  | Ant Ling | `ANT_LING_API_KEY` |
413
415
  | Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` (e.g. `https://{resource}.ai.azure.com`) or `AZURE_OPENAI_RESOURCE_NAME`. Supports `*.openai.azure.com`, `*.cognitiveservices.azure.com` and `*.ai.azure.com`; root endpoints auto-normalize to `/openai/v1`. Optional: `AZURE_OPENAI_API_VERSION` (default `v1`), `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`. |
414
- | Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` |
416
+ | Anthropic | `ANTHROPIC_API_KEY` for API billing. Claude subscriptions require the Claude Code setup below; `ANTHROPIC_OAUTH_TOKEN` is unsupported. |
415
417
  | DeepSeek | `DEEPSEEK_API_KEY` |
416
418
  | NVIDIA NIM | `NVIDIA_API_KEY` |
417
419
  | Google | `GEMINI_API_KEY` |
@@ -449,7 +451,7 @@ Tools enable LLMs to interact with external systems. This library uses TypeBox s
449
451
  ### Defining Tools
450
452
 
451
453
  ```typescript
452
- import { Type, type Tool, StringEnum } from '@earendil-works/pi-ai';
454
+ import { Type, type Tool, StringEnum } from '@ashx-j/lunr-ai';
453
455
 
454
456
  // Define tool parameters with TypeBox
455
457
  const weatherTool: Tool = {
@@ -573,7 +575,7 @@ for await (const event of s) {
573
575
  When implementing your own tool execution loop, use `validateToolCall` to validate arguments before passing them to your tools:
574
576
 
575
577
  ```typescript
576
- import { validateToolCall, type Tool } from '@earendil-works/pi-ai';
578
+ import { validateToolCall, type Tool } from '@ashx-j/lunr-ai';
577
579
 
578
580
  const tools: Tool[] = [weatherTool, calculatorTool];
579
581
  const s = models.stream(model, { messages, tools });
@@ -666,7 +668,7 @@ Image generation uses a separate API surface from text/chat generation, mirrorin
666
668
  ### Basic Image Generation
667
669
 
668
670
  ```typescript
669
- import { builtinImagesModels } from '@earendil-works/pi-ai/providers/all';
671
+ import { builtinImagesModels } from '@ashx-j/lunr-ai/providers/all';
670
672
 
671
673
  // Every built-in image-generation provider; accepts the same options as createModels()
672
674
  const imagesModels = builtinImagesModels();
@@ -688,12 +690,12 @@ for (const block of result.output) {
688
690
  }
689
691
  ```
690
692
 
691
- Like the chat side, you can build the collection from parts: `createImagesModels({ credentials?, authContext? })`, the `openrouterImagesProvider()` factory from `@earendil-works/pi-ai/providers/openrouter-images`, and `createImagesProvider({ id, auth, models, refreshModels?, api })` for custom image providers (with `imagesModels.refresh(provider?)` for dynamic lists). Failures never reject — they return an `AssistantImages` with `stopReason: "error"`. The collection's provider-scoped `getAuth(providerId)` works exactly like the chat-side one.
693
+ Like the chat side, you can build the collection from parts: `createImagesModels({ credentials?, authContext? })`, the `openrouterImagesProvider()` factory from `@ashx-j/lunr-ai/providers/openrouter-images`, and `createImagesProvider({ id, auth, models, refreshModels?, api })` for custom image providers (with `imagesModels.refresh(provider?)` for dynamic lists). Failures never reject — they return an `AssistantImages` with `stopReason: "error"`. The collection's provider-scoped `getAuth(providerId)` works exactly like the chat-side one.
692
694
 
693
695
  The old global API (`getImageModel()` / `getImageModels()` / `getImageProviders()` / `generateImages()`) remains available on the [compat entrypoint](#migrating-from-the-old-global-api):
694
696
 
695
697
  ```typescript
696
- import { getImageModel, generateImages } from '@earendil-works/pi-ai/compat';
698
+ import { getImageModel, generateImages } from '@ashx-j/lunr-ai/compat';
697
699
 
698
700
  const model = getImageModel('openrouter', 'google/gemini-2.5-flash-image');
699
701
  const result = await generateImages(model, {
@@ -778,7 +780,7 @@ for (const block of response.content) {
778
780
  `models.stream()`/`complete()` accept the owning API's full option set. Use `hasApi()` to narrow a dynamically looked-up model to its API for full option typing:
779
781
 
780
782
  ```typescript
781
- import { hasApi } from '@earendil-works/pi-ai';
783
+ import { hasApi } from '@ashx-j/lunr-ai';
782
784
 
783
785
  // OpenAI Reasoning (o1, o3, gpt-5)
784
786
  const openaiModel = models.getModel('openai', 'gpt-5-mini')!;
@@ -950,8 +952,8 @@ The callback is supported by `stream`, `complete`, `streamSimple`, and `complete
950
952
  `createProvider()` builds a provider from parts: identity, auth, a model list, and an API implementation. Use it for local inference servers, proxies, or any OpenAI/Anthropic-compatible endpoint:
951
953
 
952
954
  ```typescript
953
- import { createModels, createProvider, envApiKeyAuth, type Model } from '@earendil-works/pi-ai';
954
- import { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';
955
+ import { createModels, createProvider, envApiKeyAuth, type Model } from '@ashx-j/lunr-ai';
956
+ import { openAICompletionsApi } from '@ashx-j/lunr-ai/api/openai-completions.lazy';
955
957
 
956
958
  const ollamaModel: Model<'openai-completions'> = {
957
959
  id: 'llama-3.1-8b',
@@ -996,8 +998,8 @@ const proxy = createProvider({
996
998
  Mixed-API providers pass a map keyed by `model.api`; each model dispatches to its API's implementation:
997
999
 
998
1000
  ```typescript
999
- import { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';
1000
- import { openAIResponsesApi } from '@earendil-works/pi-ai/api/openai-responses.lazy';
1001
+ import { anthropicMessagesApi } from '@ashx-j/lunr-ai/api/anthropic-messages.lazy';
1002
+ import { openAIResponsesApi } from '@ashx-j/lunr-ai/api/openai-responses.lazy';
1001
1003
 
1002
1004
  const gateway = createProvider({
1003
1005
  id: 'my-gateway',
@@ -1086,7 +1088,7 @@ const ollamaReasoningModel: Model<'openai-completions'> = {
1086
1088
  The API implementations are importable on their own. Each module exports exactly `stream` and `streamSimple` with that API's full option typing. Direct calls bypass provider auth — pass `apiKey` explicitly:
1087
1089
 
1088
1090
  ```typescript
1089
- import { stream } from '@earendil-works/pi-ai/api/anthropic-messages';
1091
+ import { stream } from '@ashx-j/lunr-ai/api/anthropic-messages';
1090
1092
 
1091
1093
  const s = stream(claudeModel, context, {
1092
1094
  apiKey: process.env.ANTHROPIC_API_KEY,
@@ -1109,7 +1111,7 @@ Built-in API implementations live under `./api/<api-id>`:
1109
1111
  | `mistral-conversations` | `MistralOptions` |
1110
1112
  | `bedrock-converse-stream` | `BedrockOptions` |
1111
1113
 
1112
- Importing an implementation module loads its SDK. The `./api/<id>.lazy` wrappers (used by the provider factories) defer that load to the first request when the runtime or bundler supports dynamic import chunking. Legacy raw API subpaths from older releases (`./anthropic`, `./google`, `./mistral`, `./openai-completions`, ...) were removed; use `@earendil-works/pi-ai/api/<api-id>`.
1114
+ Importing an implementation module loads its SDK. The `./api/<id>.lazy` wrappers (used by the provider factories) defer that load to the first request when the runtime or bundler supports dynamic import chunking. Legacy raw API subpaths from older releases (`./anthropic`, `./google`, `./mistral`, `./openai-completions`, ...) were removed; use `@ashx-j/lunr-ai/api/<api-id>`.
1113
1115
 
1114
1116
  ### OpenAI Compatibility Settings
1115
1117
 
@@ -1161,7 +1163,7 @@ import {
1161
1163
  fauxText,
1162
1164
  fauxThinking,
1163
1165
  fauxToolCall,
1164
- } from '@earendil-works/pi-ai';
1166
+ } from '@ashx-j/lunr-ai';
1165
1167
 
1166
1168
  const faux = fauxProvider({
1167
1169
  tokensPerSecond: 50 // optional
@@ -1248,10 +1250,10 @@ When messages from one provider are sent to a different provider, the library au
1248
1250
  - **Tool calls and regular text** are preserved unchanged
1249
1251
 
1250
1252
  ```typescript
1251
- import { createModels, type Context } from '@earendil-works/pi-ai';
1252
- import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';
1253
- import { openaiProvider } from '@earendil-works/pi-ai/providers/openai';
1254
- import { googleProvider } from '@earendil-works/pi-ai/providers/google';
1253
+ import { createModels, type Context } from '@ashx-j/lunr-ai';
1254
+ import { anthropicProvider } from '@ashx-j/lunr-ai/providers/anthropic';
1255
+ import { openaiProvider } from '@ashx-j/lunr-ai/providers/openai';
1256
+ import { googleProvider } from '@ashx-j/lunr-ai/providers/google';
1255
1257
 
1256
1258
  const models = createModels();
1257
1259
  models.setProvider(anthropicProvider());
@@ -1318,8 +1320,8 @@ Models are plain serializable data too — no functions or implementations attac
1318
1320
  The library supports browser environments. The core entrypoint and provider factories are side-effect free and bundle cleanly. Environment variables are not available in browsers, so pass API keys explicitly — or inject a `CredentialStore` (e.g. localStorage-backed) and let provider auth resolve from stored credentials:
1319
1321
 
1320
1322
  ```typescript
1321
- import { createModels } from '@earendil-works/pi-ai';
1322
- import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';
1323
+ import { createModels } from '@ashx-j/lunr-ai';
1324
+ import { anthropicProvider } from '@ashx-j/lunr-ai/providers/anthropic';
1323
1325
 
1324
1326
  const models = createModels();
1325
1327
  models.setProvider(anthropicProvider());
@@ -1345,8 +1347,8 @@ Browser compatibility notes:
1345
1347
  For small bundles, import only the providers you need:
1346
1348
 
1347
1349
  ```typescript
1348
- import { createModels } from '@earendil-works/pi-ai';
1349
- import { openaiProvider } from '@earendil-works/pi-ai/providers/openai';
1350
+ import { createModels } from '@ashx-j/lunr-ai';
1351
+ import { openaiProvider } from '@ashx-j/lunr-ai/providers/openai';
1350
1352
 
1351
1353
  const models = createModels();
1352
1354
  models.setProvider(openaiProvider());
@@ -1354,14 +1356,14 @@ models.setProvider(openaiProvider());
1354
1356
 
1355
1357
  Rules:
1356
1358
 
1357
- - `@earendil-works/pi-ai` is the core entrypoint and does not import built-in catalogs, provider factories, or SDK implementations.
1358
- - `@earendil-works/pi-ai/providers/<provider>` imports that provider's catalog and lazy API wrapper only.
1359
- - `@earendil-works/pi-ai/providers/all` imports every built-in provider factory and all catalogs. Use it only when you want the full built-in set.
1359
+ - `@ashx-j/lunr-ai` is the core entrypoint and does not import built-in catalogs, provider factories, or SDK implementations.
1360
+ - `@ashx-j/lunr-ai/providers/<provider>` imports that provider's catalog and lazy API wrapper only.
1361
+ - `@ashx-j/lunr-ai/providers/all` imports every built-in provider factory and all catalogs. Use it only when you want the full built-in set.
1360
1362
  - With code splitting, provider SDKs stay in lazy chunks and load on first request.
1361
1363
  - Without code splitting, bundlers fold reachable lazy API implementations into the single bundle. A single-provider bundle then includes that provider's SDK; `providers/all` includes all statically visible SDKs. Bedrock is the exception: its AWS SDK implementation is loaded through a bundler-opaque Node-only import.
1362
- - Importing `@earendil-works/pi-ai/api/<api-id>` directly loads that API implementation and its SDK immediately.
1364
+ - Importing `@ashx-j/lunr-ai/api/<api-id>` directly loads that API implementation and its SDK immediately.
1363
1365
 
1364
- Avoid `@earendil-works/pi-ai/compat` in new bundled apps; it preserves the old global API and imports the full built-in catalog surface.
1366
+ Avoid `@ashx-j/lunr-ai/compat` in new bundled apps; it preserves the old global API and imports the full built-in catalog surface.
1365
1367
 
1366
1368
  For single-file Node ESM bundles, some SDK dependencies may still use dynamic CommonJS `require()` internally. If you see errors such as `Dynamic require of "child_process" is not supported`, add a Node `require` shim to the bundle. With esbuild:
1367
1369
 
@@ -1376,8 +1378,8 @@ This is only for Node bundles; it is not a browser or Cloudflare Workers workaro
1376
1378
  Bedrock is Node-only. Add it like any other provider:
1377
1379
 
1378
1380
  ```typescript
1379
- import { createModels } from '@earendil-works/pi-ai';
1380
- import { amazonBedrockProvider } from '@earendil-works/pi-ai/providers/amazon-bedrock';
1381
+ import { createModels } from '@ashx-j/lunr-ai';
1382
+ import { amazonBedrockProvider } from '@ashx-j/lunr-ai/providers/amazon-bedrock';
1381
1383
 
1382
1384
  const models = createModels();
1383
1385
  models.setProvider(amazonBedrockProvider());
@@ -1386,8 +1388,8 @@ models.setProvider(amazonBedrockProvider());
1386
1388
  In normal Node package usage and code-split bundles, Bedrock loads its AWS SDK implementation lazily. For a standalone single-file bundle that must include Bedrock support, register the implementation module explicitly:
1387
1389
 
1388
1390
  ```typescript
1389
- import { setBedrockProviderModule } from '@earendil-works/pi-ai/api/bedrock-converse-stream.lazy';
1390
- import { bedrockProviderModule } from '@earendil-works/pi-ai/bedrock-provider';
1391
+ import { setBedrockProviderModule } from '@ashx-j/lunr-ai/api/bedrock-converse-stream.lazy';
1392
+ import { bedrockProviderModule } from '@ashx-j/lunr-ai/bedrock-provider';
1391
1393
 
1392
1394
  setBedrockProviderModule(bedrockProviderModule);
1393
1395
  ```
@@ -1417,46 +1419,50 @@ Use this when one process needs different provider settings per request, or when
1417
1419
 
1418
1420
  Several providers support OAuth authentication instead of static API keys:
1419
1421
 
1420
- - **Anthropic** (Claude Pro/Max subscription)
1422
+ - **Anthropic** subscriptions use a separate Claude Code bridge, described below.
1421
1423
  - **OpenAI Codex** (ChatGPT Plus/Pro subscription, access to GPT-5.x Codex models)
1422
1424
  - **GitHub Copilot** (Copilot subscription)
1423
1425
 
1424
- Each of these providers carries an `OAuthAuth` on `provider.auth.oauth` with three operations: `login(interaction)` uses the provider-neutral `AuthInteraction.prompt()`/`notify()` protocol and returns a credential, `refresh(credential)` exchanges the refresh token, and `toAuth(credential)` derives request auth (GitHub Copilot's per-account base URL comes from here). Refresh is automatic: `models.getAuth(providerId)` and request paths refresh expired tokens under a credential-store lock, so concurrent requests and processes cannot double-refresh.
1426
+ OpenAI Codex and GitHub Copilot expose `OAuthAuth` on `provider.auth.oauth`. `login(interaction)` returns a credential, `refresh(credential)` refreshes it, and `toAuth(credential)` derives request auth. `Models` persists login credentials and refreshes expired OAuth tokens under a credential-store lock.
1425
1427
 
1426
1428
  ```typescript
1427
- import { createModels } from '@earendil-works/pi-ai';
1428
- import { anthropicProvider } from '@earendil-works/pi-ai/providers/anthropic';
1429
+ import { createModels } from '@ashx-j/lunr-ai';
1430
+ import { githubCopilotProvider } from '@ashx-j/lunr-ai/providers/github-copilot';
1429
1431
 
1430
- const models = createModels({ credentials: myStore }); // persistent CredentialStore
1431
- models.setProvider(anthropicProvider());
1432
+ const models = createModels({ credentials: myStore });
1433
+ models.setProvider(githubCopilotProvider());
1432
1434
 
1433
- // Login: Models drives the flow and persists the credential
1434
- await models.login('anthropic', 'oauth', {
1435
- prompt: async (p) => {
1436
- // p.type: 'text' | 'secret' | 'select' | 'manual_code'
1437
- // manual_code prompts race a local callback server; p.signal aborts them when the server wins
1438
- return await askUser(p.message);
1435
+ await models.login('github-copilot', 'oauth', {
1436
+ prompt: async (prompt) => {
1437
+ if (prompt.type === 'select') {
1438
+ // Return the selected option's id, not its display label.
1439
+ return await selectOption(prompt.message, prompt.options);
1440
+ }
1441
+ return await askUser(prompt.message, prompt.signal);
1439
1442
  },
1440
1443
  notify: (event) => {
1441
- // event.type: 'info' | 'auth_url' | 'device_code' | 'progress'
1442
- if (event.type === 'info') {
1443
- console.log(event.message);
1444
- for (const link of event.links ?? []) console.log(`${link.label ?? 'More information'}: ${link.url}`);
1445
- }
1446
- if (event.type === 'auth_url') console.log(`Open: ${event.url}`);
1447
- if (event.type === 'device_code') console.log(`Code: ${event.userCode} at ${event.verificationUri}`);
1448
- if (event.type === 'progress') console.log(event.message);
1444
+ if (event.type === 'auth_url') console.log(event.url);
1445
+ if (event.type === 'device_code') console.log(event.userCode, event.verificationUri);
1446
+ if (event.type === 'info' || event.type === 'progress') console.log(event.message);
1449
1447
  },
1450
1448
  });
1451
1449
 
1452
- // From here on, requests resolve and refresh the token automatically
1453
- const model = models.getModel('anthropic', 'claude-sonnet-4-5')!;
1454
- await models.complete(model, context);
1455
-
1456
- // Logout
1457
- await models.logout('anthropic');
1450
+ // Requests resolve and refresh stored OAuth credentials automatically.
1451
+ const model = (await models.getAvailable('github-copilot'))[0];
1452
+ if (model) await models.complete(model, context);
1453
+ await models.logout('github-copilot');
1458
1454
  ```
1459
1455
 
1456
+ `myStore`, `selectOption`, and `askUser` are application-provided storage and UI functions. Handle prompt cancellation through the supplied signal.
1457
+
1458
+ ### Anthropic subscriptions
1459
+
1460
+ Anthropic API keys use the Messages API. Claude subscription setup instead returns an `external_claude_code` credential. It requires Python 3.10+ and Claude Code 2.1.263, and checks the existing Claude Code subscription login and supported model routes. Missing dependency installers require explicit confirmation. Claude Code owns authentication; lunR stores the executable paths and route metadata, without copying subscription tokens.
1461
+
1462
+ Use `/login anthropic` in lunR for the terminal setup flow. SDK applications can call `models.login('anthropic', 'oauth', interaction)` after registering `anthropicProvider()`. Their `AuthInteraction` must handle selection prompts and can supply `handoff` for Claude Code login. Without a handoff, setup asks the user to log in to Claude Code separately. Select a model through `models.getAvailable('anthropic')` after setup so its route matches the connected account.
1463
+
1464
+ Legacy Anthropic OAuth credentials fail closed. `ANTHROPIC_OAUTH_TOKEN` does not configure this provider. Run setup again rather than attempting to refresh those credentials. Subscription requests use the vendored Python bridge and Claude Code CLI; they need a local Node environment and those external executables. Live subscription and cross-platform operation remain qualification limits. See the [provider guide](https://github.com/ashx-j/lunR/blob/master/packages/coding-agent/docs/providers.md) for the integration limits.
1465
+
1460
1466
  ### Vertex AI
1461
1467
 
1462
1468
  Vertex AI models support either a Google Cloud API key or Application Default Credentials (ADC). Its provider-owned API-key login flow can configure either method:
@@ -1484,16 +1490,16 @@ Official docs: [Application Default Credentials](https://cloud.google.com/docs/a
1484
1490
  The quickest way to authenticate:
1485
1491
 
1486
1492
  ```bash
1487
- npx @earendil-works/pi-ai login # interactive provider selection
1488
- npx @earendil-works/pi-ai login anthropic # login to specific provider
1489
- npx @earendil-works/pi-ai list # list available providers
1493
+ npx @ashx-j/lunr-ai login # interactive provider selection
1494
+ npx @ashx-j/lunr-ai login github-copilot # login to a supported OAuth provider
1495
+ npx @ashx-j/lunr-ai list # list available providers
1490
1496
  ```
1491
1497
 
1492
- Credentials are saved to `auth.json` in the current directory.
1498
+ The AI CLI saves OAuth credentials to `auth.json` in the current directory. It does not connect an Anthropic subscription; use `/login anthropic` inside lunR for Claude Code setup.
1493
1499
 
1494
1500
  ### Programmatic OAuth
1495
1501
 
1496
- Built-in login and refresh flows are private provider implementations. Use provider-owned `OAuthAuth`, which composes with `CredentialStore` and gets locked auto-refresh through `Models`. The `@earendil-works/pi-ai/oauth` entry point retains only type declarations required by coding-agent extension OAuth compatibility.
1502
+ Built-in login and refresh flows are private provider implementations. Use `Models.login()` with a `CredentialStore` for persistence and supported OAuth refresh. Anthropic subscription setup follows the separate Claude Code route above. The `@ashx-j/lunr-ai/oauth` entry point retains only type declarations required by coding-agent extension OAuth compatibility.
1497
1503
 
1498
1504
  Provider notes:
1499
1505
 
@@ -1509,10 +1515,10 @@ Older versions exposed a global API: `stream()`/`complete()` dispatching on `mod
1509
1515
 
1510
1516
  ```typescript
1511
1517
  // Before
1512
- import { getModel, complete } from '@earendil-works/pi-ai';
1518
+ import { getModel, complete } from '@ashx-j/lunr-ai';
1513
1519
 
1514
1520
  // After (verbatim behavior, one import-path change)
1515
- import { getModel, complete } from '@earendil-works/pi-ai/compat';
1521
+ import { getModel, complete } from '@ashx-j/lunr-ai/compat';
1516
1522
  ```
1517
1523
 
1518
1524
  Compat is a strict superset of the root entrypoint, so a file can switch its import path wholesale. It will be removed in a future release; migrate to `createModels()` + provider factories:
@@ -1524,7 +1530,7 @@ Compat is a strict superset of the root entrypoint, so a file can switch its imp
1524
1530
  | `stream(model, ctx, opts)` (env-key injection) | `models.stream(model, ctx, opts)` (provider auth resolution) |
1525
1531
  | `registerApiProvider({ api, stream, streamSimple })` | `createProvider({ id, auth, models, api })` + `models.setProvider()` |
1526
1532
  | `getEnvApiKey('openai')` | `await models.getAuth(model.provider)` |
1527
- | `streamAnthropic(model, ctx, opts)` | `stream` from `@earendil-works/pi-ai/api/anthropic-messages`, or a provider in a collection |
1533
+ | `streamAnthropic(model, ctx, opts)` | `stream` from `@ashx-j/lunr-ai/api/anthropic-messages`, or a provider in a collection |
1528
1534
  | `registerFauxProvider()` | `fauxProvider()` + `models.setProvider()` |
1529
1535
 
1530
1536
  ## Development
@@ -1548,7 +1554,7 @@ Create a new API implementation file (for example `bedrock-converse-stream.ts`)
1548
1554
  - Tool conversion if the provider supports tools
1549
1555
  - Response parsing to emit standardized events (`text`, `tool_call`, `thinking`, `usage`, `stop`)
1550
1556
 
1551
- Add a lazy wrapper `src/api/<api-id>.lazy.ts` (`<name>Api()` via `lazyApi()`) so providers can reference the implementation without importing its SDK. Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@earendil-works/pi-ai`.
1557
+ Add a lazy wrapper `src/api/<api-id>.lazy.ts` (`<name>Api()` via `lazyApi()`) so providers can reference the implementation without importing its SDK. Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@ashx-j/lunr-ai`.
1552
1558
 
1553
1559
  #### 3. Model Generation (`scripts/generate-models.ts`, `scripts/generate-image-models.ts`)
1554
1560
 
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-claude-code-bridge.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-claude-code-bridge.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,GAAG,EAEH,OAAO,EACP,KAAK,EACL,mBAAmB,EACnB,aAAa,EAGb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAwCvE,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,UAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoB/F;AAmJD,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3D;AAgGD,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,aAAa,GAAG,mBAAwB,GAC/C,2BAA2B,CA2R7B","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ExternalClaudeCodeCredential } from \"../auth/types.ts\";\nimport { calculateCost } from \"../models.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamOptions,\n\tToolCall,\n\tUsage,\n} from \"../types.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\n\nconst WORKER = fileURLToPath(\n\tnew URL(\"../../vendor/hermes-claude-subscription-directsdk/lunr_bridge.py\", import.meta.url),\n);\nconst MAX_RECORD = 64 * 1024 * 1024;\nconst CONFLICTS = [\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANTHROPIC_BASE_URL\",\n\t\"ANTHROPIC_FOUNDRY_API_KEY\",\n\t\"CLAUDE_CODE_USE_BEDROCK\",\n\t\"CLAUDE_CODE_USE_VERTEX\",\n\t\"CLAUDE_CODE_USE_FOUNDRY\",\n\t\"CLAUDE_CODE_EXTRA_BODY\",\n] as const;\nconst ENV_ALLOWLIST = [\n\t\"PATH\",\n\t\"HOME\",\n\t\"USERPROFILE\",\n\t\"APPDATA\",\n\t\"LOCALAPPDATA\",\n\t\"PROGRAMDATA\",\n\t\"SYSTEMROOT\",\n\t\"SYSTEMDRIVE\",\n\t\"COMSPEC\",\n\t\"PATHEXT\",\n\t\"TEMP\",\n\t\"TMP\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"HTTP_PROXY\",\n\t\"HTTPS_PROXY\",\n\t\"NO_PROXY\",\n\t\"http_proxy\",\n\t\"https_proxy\",\n\t\"no_proxy\",\n] as const;\n\nexport function claudeCodeEnvironment(configDir?: string, strict = true): Record<string, string> {\n\tconst conflicting = CONFLICTS.filter((name) => {\n\t\tconst value = process.env[name];\n\t\treturn (\n\t\t\tvalue &&\n\t\t\t((name !== \"CLAUDE_CODE_USE_BEDROCK\" &&\n\t\t\t\tname !== \"CLAUDE_CODE_USE_VERTEX\" &&\n\t\t\t\tname !== \"CLAUDE_CODE_USE_FOUNDRY\") ||\n\t\t\t\t![\"0\", \"false\", \"no\", \"off\"].includes(value.toLowerCase()))\n\t\t);\n\t});\n\tif (strict && conflicting.length)\n\t\tthrow new Error(`Claude Code subscription refuses conflicting environment variables: ${conflicting.join(\", \")}`);\n\tif (process.env.NODE_TLS_REJECT_UNAUTHORIZED === \"0\")\n\t\tthrow new Error(\"Claude Code subscription requires TLS certificate verification\");\n\tconst env = Object.fromEntries(\n\t\tENV_ALLOWLIST.flatMap((name) => (process.env[name] ? [[name, process.env[name]]] : [])),\n\t);\n\tif (configDir) env.CLAUDE_SUBSCRIPTION_DIRECTSDK_CONFIG_DIR = configDir;\n\treturn env;\n}\n\nfunction contentHash(content: AssistantMessage[\"content\"]): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(content)).digest(\"hex\");\n}\n\nfunction history(\n\tcontext: Context,\n\tmodel: Model<Api>,\n\tconnection: ExternalClaudeCodeCredential,\n): { messages: unknown[]; tools: unknown[] } {\n\tconst messages: Record<string, unknown>[] = context.systemPrompt\n\t\t? [{ role: \"system\", content: context.systemPrompt }]\n\t\t: [];\n\tfor (const message of context.messages) {\n\t\tif (message.role === \"user\") {\n\t\t\tmessages.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent:\n\t\t\t\t\ttypeof message.content === \"string\"\n\t\t\t\t\t\t? message.content\n\t\t\t\t\t\t: message.content.map((block) =>\n\t\t\t\t\t\t\t\tblock.type === \"text\"\n\t\t\t\t\t\t\t\t\t? { type: \"text\", text: block.text }\n\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"image_url\",\n\t\t\t\t\t\t\t\t\t\t\timage_url: { url: `data:${block.mimeType};base64,${block.data}` },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t),\n\t\t\t});\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst text = message.content\n\t\t\t\t.filter((block) => block.type === \"text\")\n\t\t\t\t.map((block) => block.text)\n\t\t\t\t.join(\"\");\n\t\t\tconst calls = message.content\n\t\t\t\t.filter((block): block is ToolCall => block.type === \"toolCall\")\n\t\t\t\t.map((block) => ({\n\t\t\t\t\tid: block.id,\n\t\t\t\t\ttype: \"function\",\n\t\t\t\t\tfunction: { name: block.name, arguments: JSON.stringify(block.arguments) },\n\t\t\t\t}));\n\t\t\tconst projection = {\n\t\t\t\tcontent: text.trim(),\n\t\t\t\ttool_calls: calls.map((call) => ({\n\t\t\t\t\tid: call.id,\n\t\t\t\t\tname: call.function.name,\n\t\t\t\t\tinput: JSON.parse(call.function.arguments),\n\t\t\t\t})),\n\t\t\t};\n\t\t\tconst carrier =\n\t\t\t\tmessage.provider === \"anthropic\" &&\n\t\t\t\tmessage.model === model.id &&\n\t\t\t\tmessage.claudeCodeCarrier?.version === 1 &&\n\t\t\t\tconnection.accountFingerprint &&\n\t\t\t\tmessage.claudeCodeCarrier.accountFingerprint === connection.accountFingerprint &&\n\t\t\t\tmessage.claudeCodeCarrier.contentHash === contentHash(message.content) &&\n\t\t\t\tJSON.stringify(message.claudeCodeCarrier.projection) === JSON.stringify(projection)\n\t\t\t\t\t? [{ type: \"claude-subscription-directsdk-experimental.native_assistant\", ...message.claudeCodeCarrier }]\n\t\t\t\t\t: [];\n\t\t\tmessages.push({ role: \"assistant\", content: text, tool_calls: calls, reasoning_details: carrier });\n\t\t} else {\n\t\t\tif (message.content.some((block) => block.type !== \"text\"))\n\t\t\t\tthrow new Error(\"Claude Code subscription supports text tool results only\");\n\t\t\tmessages.push({\n\t\t\t\trole: \"tool\",\n\t\t\t\ttool_call_id: message.toolCallId,\n\t\t\t\tcontent: message.content.map((block) => (block.type === \"text\" ? block.text : \"\")).join(\"\"),\n\t\t\t\tis_error: message.isError,\n\t\t\t});\n\t\t}\n\t}\n\tconst tools = (context.tools ?? []).map((tool) => ({\n\t\ttype: \"function\",\n\t\tfunction: {\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: tool.parameters,\n\t\t},\n\t}));\n\treturn { messages, tools };\n}\n\nfunction validateOptions(model: Model<Api>, options: StreamOptions | SimpleStreamOptions): void {\n\tif (!options.externalClaudeCode)\n\t\tthrow new Error(\"Claude Code subscription setup is required. Run /login anthropic.\");\n\tif (\n\t\toptions.apiKey ||\n\t\tObject.keys(options.headers ?? {}).length ||\n\t\toptions.onPayload ||\n\t\toptions.onResponse ||\n\t\toptions.env ||\n\t\toptions.metadata ||\n\t\t(options.transport && options.transport !== \"auto\") ||\n\t\toptions.maxRetries ||\n\t\t(options.maxRetryDelayMs !== undefined && options.maxRetryDelayMs !== 60_000) ||\n\t\toptions.websocketConnectTimeoutMs ||\n\t\tmodel.baseUrl !== \"https://api.anthropic.com\"\n\t) {\n\t\tthrow new Error(\"Claude Code subscription does not support API keys, alternate endpoints, or request mutations\");\n\t}\n\tconst anthro = options as StreamOptions & {\n\t\ttoolChoice?: unknown;\n\t\tclient?: unknown;\n\t\textraBody?: unknown;\n\t\tthinkingBudgetTokens?: unknown;\n\t\tstopSequences?: unknown;\n\t\tthinkingEnabled?: boolean;\n\t\teffort?: string;\n\t\tthinkingDisplay?: unknown;\n\t\tinterleavedThinking?: unknown;\n\t};\n\tif (\n\t\t(anthro.toolChoice && anthro.toolChoice !== \"auto\") ||\n\t\tanthro.client ||\n\t\tanthro.extraBody ||\n\t\tanthro.thinkingBudgetTokens ||\n\t\tanthro.stopSequences ||\n\t\tanthro.thinkingDisplay !== undefined ||\n\t\tanthro.interleavedThinking !== undefined\n\t) {\n\t\tthrow new Error(\"Claude Code subscription does not support forced tools or native request overrides\");\n\t}\n\tif (options.temperature !== undefined)\n\t\tthrow new Error(\"Claude Code subscription does not support sampling overrides\");\n}\n\ntype WorkerEvent = { v: 1; requestId: string; type: string; text?: unknown; pid?: unknown; response?: unknown };\n\nfunction parseWorkerRecord(line: string, requestId: string): WorkerEvent {\n\tif (Buffer.byteLength(line) > MAX_RECORD) throw new Error(\"Claude Code bridge record exceeds size limit\");\n\tconst value: unknown = JSON.parse(line);\n\tif (\n\t\t!value ||\n\t\ttypeof value !== \"object\" ||\n\t\t!(\"v\" in value) ||\n\t\tvalue.v !== 1 ||\n\t\t!(\"requestId\" in value) ||\n\t\tvalue.requestId !== requestId ||\n\t\t!(\"type\" in value) ||\n\t\ttypeof value.type !== \"string\"\n\t) {\n\t\tthrow new Error(\"Invalid Claude Code bridge record\");\n\t}\n\treturn value as WorkerEvent;\n}\n\nexport async function stopNative(pid: number): Promise<void> {\n\tif (!Number.isSafeInteger(pid) || pid < 1) return;\n\tif (process.platform === \"win32\") {\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst killer = spawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], { stdio: \"ignore\", windowsHide: true });\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tkiller.kill();\n\t\t\t\tresolve();\n\t\t\t}, 3000);\n\t\t\tkiller.once(\"error\", () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t\tkiller.once(\"close\", () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t} else {\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t/* Already exited. */\n\t\t}\n\t}\n}\n\nfunction validatedCompletion(\n\tvalue: unknown,\n\tmodel: Model<Api>,\n\ttext: string,\n\tthinking: string,\n): {\n\tcontent: AssistantMessage[\"content\"];\n\tcarrier: AssistantMessage[\"claudeCodeCarrier\"];\n\tusage: Usage;\n\tresponseId: string;\n\treason: \"stop\" | \"length\" | \"toolUse\";\n} {\n\tif (!value || typeof value !== \"object\" || !(\"choices\" in value) || !Array.isArray(value.choices))\n\t\tthrow new Error(\"Invalid native completion\");\n\tconst response = value as Record<string, unknown> & {\n\t\tchoices: { finish_reason: unknown; message: Record<string, unknown> }[];\n\t\tusage: Record<string, unknown>;\n\t};\n\tconst choice = response.choices[0];\n\tif (\n\t\t!choice ||\n\t\t![\"stop\", \"length\", \"tool_calls\"].includes(String(choice.finish_reason)) ||\n\t\t(typeof choice.message?.content !== \"string\" && choice.message?.content !== null) ||\n\t\t(choice.message.content ?? \"\") !== text ||\n\t\t(choice.message.reasoning_content ?? \"\") !== thinking\n\t)\n\t\tthrow new Error(\"Native completion differs from streamed content\");\n\tconst rawCalls = choice.message.tool_calls ?? [];\n\tif (!Array.isArray(rawCalls)) throw new Error(\"Invalid native tool calls\");\n\tconst calls: ToolCall[] = rawCalls.map((call) => {\n\t\tif (\n\t\t\t!call ||\n\t\t\ttypeof call !== \"object\" ||\n\t\t\ttypeof call.id !== \"string\" ||\n\t\t\ttypeof call.function?.name !== \"string\" ||\n\t\t\ttypeof call.function?.arguments !== \"string\"\n\t\t)\n\t\t\tthrow new Error(\"Invalid native tool call\");\n\t\tconst args: unknown = JSON.parse(call.function.arguments);\n\t\tif (!args || typeof args !== \"object\" || Array.isArray(args)) throw new Error(\"Invalid native tool arguments\");\n\t\treturn { type: \"toolCall\", id: call.id, name: call.function.name, arguments: args as Record<string, unknown> };\n\t});\n\tconst native = response.usage;\n\tif (\n\t\t!native ||\n\t\ttypeof native.native_admission !== \"object\" ||\n\t\tnative.native_admission === null ||\n\t\t(native.native_admission as { upstream_requests?: unknown }).upstream_requests !== 1\n\t)\n\t\tthrow new Error(\"Native one-request admission not verified\");\n\tif (\n\t\t!native ||\n\t\t!Number.isFinite(native.prompt_tokens) ||\n\t\t!Number.isFinite(native.completion_tokens) ||\n\t\t!Number.isFinite(native.cache_creation_input_tokens) ||\n\t\t!(\"prompt_tokens_details\" in native)\n\t)\n\t\tthrow new Error(\"Missing native usage\");\n\tconst cached = (native.prompt_tokens_details as { cached_tokens?: unknown }).cached_tokens;\n\tif (!Number.isFinite(cached)) throw new Error(\"Missing native cache usage\");\n\tconst usage: Usage = {\n\t\tinput: Number(native.prompt_tokens) - Number(cached) - Number(native.cache_creation_input_tokens),\n\t\toutput: Number(native.completion_tokens),\n\t\tcacheRead: Number(cached),\n\t\tcacheWrite: Number(native.cache_creation_input_tokens),\n\t\ttotalTokens: Number(native.total_tokens),\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t};\n\tif (usage.input < 0 || usage.output < 0) throw new Error(\"Invalid native usage\");\n\tcalculateCost(model, usage);\n\tconst details = choice.message.reasoning_details;\n\tconst carrier = Array.isArray(details)\n\t\t? details.find((entry) => entry?.type === \"claude-subscription-directsdk-experimental.native_assistant\")\n\t\t: undefined;\n\tconst projection = {\n\t\tcontent: text.trim(),\n\t\ttool_calls: calls.map((call) => ({ id: call.id, name: call.name, input: call.arguments })),\n\t};\n\tif (\n\t\t!carrier ||\n\t\tcarrier.version !== 1 ||\n\t\t!Array.isArray(carrier.messages) ||\n\t\tJSON.stringify(carrier.projection) !== JSON.stringify(projection)\n\t)\n\t\tthrow new Error(\"Invalid native replay carrier\");\n\treturn {\n\t\tcontent: [...(text ? [{ type: \"text\" as const, text }] : []), ...calls],\n\t\tcarrier: { version: 1, messages: carrier.messages, projection },\n\t\tusage,\n\t\tresponseId: String(response.id ?? \"\"),\n\t\treason: choice.finish_reason === \"tool_calls\" ? \"toolUse\" : choice.finish_reason === \"length\" ? \"length\" : \"stop\",\n\t};\n}\n\nexport function streamClaudeCode(\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions: StreamOptions | SimpleStreamOptions = {},\n): AssistantMessageEventStream {\n\tconst stream = new AssistantMessageEventStream();\n\tconst output: AssistantMessage = {\n\t\trole: \"assistant\",\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tcontent: [],\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\tvoid (async () => {\n\t\tlet child: ChildProcessWithoutNullStreams | undefined;\n\t\tlet nativePid: number | undefined;\n\t\tlet nativeCleanup: Promise<void> | undefined;\n\t\tconst stopNativeOnce = () => {\n\t\t\tif (nativePid === undefined) return Promise.resolve();\n\t\t\tnativeCleanup ??= stopNative(nativePid);\n\t\t\treturn nativeCleanup;\n\t\t};\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tlet abortTimer: NodeJS.Timeout | undefined;\n\t\tlet started = false;\n\t\tlet complete = false;\n\t\tlet buffer = \"\";\n\t\tconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\n\t\tlet text = \"\";\n\t\tlet thinking = \"\";\n\t\tlet activeIndex = -1;\n\t\tlet activeType: \"text\" | \"thinking\" | undefined;\n\t\ttry {\n\t\t\tvalidateOptions(model, options);\n\t\t\tif (options.signal?.aborted) throw new Error(\"Claude Code request cancelled\");\n\t\t\tconst connection = options.externalClaudeCode!;\n\t\t\tconst env = claudeCodeEnvironment(connection.configDir);\n\t\t\tconst input = history(context, model, connection);\n\t\t\tconst anthropicOptions = options as StreamOptions & { thinkingEnabled?: boolean; effort?: string };\n\t\t\tconst reasoning =\n\t\t\t\t\"reasoning\" in options && options.reasoning\n\t\t\t\t\t? { enabled: true, effort: options.reasoning }\n\t\t\t\t\t: anthropicOptions.thinkingEnabled !== undefined || anthropicOptions.effort\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tenabled: anthropicOptions.thinkingEnabled ?? true,\n\t\t\t\t\t\t\t\t...(anthropicOptions.effort ? { effort: anthropicOptions.effort } : {}),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined;\n\t\t\tconst requestId = randomUUID();\n\t\t\tchild = spawn(connection.python, [\"-s\", \"-B\", \"-u\", WORKER], {\n\t\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t\tenv,\n\t\t\t});\n\t\t\tconst worker = child;\n\t\t\tworker.stderr.resume();\n\t\t\tworker.stdin.on(\"error\", () => {});\n\t\t\tconst idleMs = Math.min(300_000, Math.max(1000, options.timeoutMs ?? 180_000));\n\t\t\tconst record = {\n\t\t\t\tv: 1,\n\t\t\t\trequestId,\n\t\t\t\ttype: \"start\",\n\t\t\t\tcommand: connection.command,\n\t\t\t\tenv,\n\t\t\t\tmodel: model.id,\n\t\t\t\t...input,\n\t\t\t\textraBody: {\n\t\t\t\t\t...(options.maxTokens ? { max_tokens: options.maxTokens } : {}),\n\t\t\t\t\t...(reasoning ? { reasoning } : {}),\n\t\t\t\t},\n\t\t\t\ttimeout: Math.round(idleMs / 1000),\n\t\t\t};\n\t\t\tconst payload = JSON.stringify(record);\n\t\t\tif (Buffer.byteLength(payload) > MAX_RECORD) throw new Error(\"Claude Code request exceeds size limit\");\n\t\t\tworker.stdin.write(`${payload}\\n`);\n\t\t\tlet cancelSent = false;\n\t\t\tlet timedOut = false;\n\t\t\tconst abort = () => {\n\t\t\t\tif (cancelSent) return;\n\t\t\t\tcancelSent = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tworker.stdin.write(`${JSON.stringify({ v: 1, requestId, type: \"cancel\" })}\\n`);\n\t\t\t\tabortTimer = setTimeout(() => {\n\t\t\t\t\tvoid stopNativeOnce().then(() => worker.kill());\n\t\t\t\t}, 2500);\n\t\t\t};\n\t\t\tconst resetDeadline = () => {\n\t\t\t\tif (cancelSent) return;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tabort();\n\t\t\t\t}, idleMs);\n\t\t\t};\n\t\t\ttimer = setTimeout(\n\t\t\t\t() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tabort();\n\t\t\t\t},\n\t\t\t\tMath.min(30_000, idleMs),\n\t\t\t);\n\t\t\toptions.signal?.addEventListener(\"abort\", abort, { once: true });\n\t\t\tif (options.signal?.aborted) abort();\n\t\t\ttry {\n\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\tworker.on(\"error\", reject);\n\t\t\t\t\tworker.stdout.on(\"data\", (data: Buffer) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tbuffer += decoder.decode(data, { stream: true });\n\t\t\t\t\t\t\tif (Buffer.byteLength(buffer) > MAX_RECORD)\n\t\t\t\t\t\t\t\tthrow new Error(\"Claude Code bridge record exceeds size limit\");\n\t\t\t\t\t\t\tfor (let newline = buffer.indexOf(\"\\n\"); newline >= 0; newline = buffer.indexOf(\"\\n\")) {\n\t\t\t\t\t\t\t\tconst event = parseWorkerRecord(buffer.slice(0, newline), requestId);\n\t\t\t\t\t\t\t\tbuffer = buffer.slice(newline + 1);\n\t\t\t\t\t\t\t\tif (event.type === \"ready\" || event.type === \"replay_progress\") resetDeadline();\n\t\t\t\t\t\t\t\telse if (\n\t\t\t\t\t\t\t\t\tevent.type === \"native_started\" &&\n\t\t\t\t\t\t\t\t\tNumber.isSafeInteger(event.pid) &&\n\t\t\t\t\t\t\t\t\tNumber(event.pid) > 0\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnativePid = Number(event.pid);\n\t\t\t\t\t\t\t\t\tresetDeadline();\n\t\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\t\tcancelSent &&\n\t\t\t\t\t\t\t\t\t[\"start\", \"text_delta\", \"thinking_delta\", \"complete\"].includes(event.type)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\telse if (event.type === \"start\") {\n\t\t\t\t\t\t\t\t\tif (started) throw new Error(\"Duplicate Claude Code stream start\");\n\t\t\t\t\t\t\t\t\tstarted = true;\n\t\t\t\t\t\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t\t\t\t\t\t} else if (event.type === \"text_delta\" || event.type === \"thinking_delta\") {\n\t\t\t\t\t\t\t\t\tif (!started || complete || typeof event.text !== \"string\")\n\t\t\t\t\t\t\t\t\t\tthrow new Error(\"Invalid Claude Code stream delta\");\n\t\t\t\t\t\t\t\t\tresetDeadline();\n\t\t\t\t\t\t\t\t\tconst kind = event.type === \"text_delta\" ? \"text\" : \"thinking\";\n\t\t\t\t\t\t\t\t\tif (activeType !== kind) {\n\t\t\t\t\t\t\t\t\t\tif (activeType === \"text\")\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { text: string }).text,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tif (activeType === \"thinking\")\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { thinking: string }).thinking,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tactiveIndex = output.content.length;\n\t\t\t\t\t\t\t\t\t\tactiveType = kind;\n\t\t\t\t\t\t\t\t\t\tif (kind === \"text\") {\n\t\t\t\t\t\t\t\t\t\t\toutput.content.push({ type: \"text\", text: \"\" });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"text_start\", contentIndex: activeIndex, partial: output });\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\toutput.content.push({ type: \"thinking\", thinking: \"\" });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: activeIndex, partial: output });\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconst block = output.content[activeIndex];\n\t\t\t\t\t\t\t\t\tif (kind === \"text\" && block?.type === \"text\") {\n\t\t\t\t\t\t\t\t\t\ttext += event.text;\n\t\t\t\t\t\t\t\t\t\tblock.text += event.text;\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (kind === \"thinking\" && block?.type === \"thinking\") {\n\t\t\t\t\t\t\t\t\t\tthinking += event.text;\n\t\t\t\t\t\t\t\t\t\tblock.thinking += event.text;\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (event.type === \"complete\") {\n\t\t\t\t\t\t\t\t\tif (!started || complete) throw new Error(\"Invalid Claude Code completion order\");\n\t\t\t\t\t\t\t\t\tconst result = validatedCompletion(event.response, model, text, thinking);\n\t\t\t\t\t\t\t\t\tcomplete = true;\n\t\t\t\t\t\t\t\t\tif (activeType === \"text\")\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { text: string }).text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tif (activeType === \"thinking\")\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { thinking: string }).thinking,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\toutput.content = [\n\t\t\t\t\t\t\t\t\t\t...output.content.filter((block) => block.type !== \"toolCall\"),\n\t\t\t\t\t\t\t\t\t\t...result.content.filter((block) => block.type === \"toolCall\"),\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t\toutput.claudeCodeCarrier = result.carrier && {\n\t\t\t\t\t\t\t\t\t\t...result.carrier,\n\t\t\t\t\t\t\t\t\t\tcontentHash: contentHash(output.content),\n\t\t\t\t\t\t\t\t\t\taccountFingerprint: connection.accountFingerprint,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\toutput.responseId = result.responseId;\n\t\t\t\t\t\t\t\t\toutput.usage = result.usage;\n\t\t\t\t\t\t\t\t\toutput.stopReason = result.reason;\n\t\t\t\t\t\t\t\t\tfor (let index = 0; index < output.content.length; index++) {\n\t\t\t\t\t\t\t\t\t\tconst block = output.content[index];\n\t\t\t\t\t\t\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: index, partial: output });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"toolcall_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\t\t\t\t\ttoolCall: block,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (event.type === \"cancelled\" && cancelSent) {\n\t\t\t\t\t\t\t\t} else if (event.type === \"error\" || event.type === \"cancelled\")\n\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t\"Claude Code request failed. Check subscription setup and native CLI compatibility.\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\telse throw new Error(\"Unknown Claude Code bridge event\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tworker.on(\"close\", () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tdecoder.decode();\n\t\t\t\t\t\t\tif (complete || cancelSent) resolve();\n\t\t\t\t\t\t\telse reject(new Error(\"Claude Code bridge exited without a validated completion\"));\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\toptions.signal?.removeEventListener(\"abort\", abort);\n\t\t\t}\n\t\t\tif (options.signal?.aborted || timedOut) throw new Error(\"Claude Code request cancelled or timed out\");\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason as \"stop\" | \"length\" | \"toolUse\", message: output });\n\t\t} catch (error) {\n\t\t\toutput.content = [];\n\t\t\toutput.claudeCodeCarrier = undefined;\n\t\t\toutput.stopReason = options.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\tconst detail =\n\t\t\t\terror instanceof Error &&\n\t\t\t\t(error.message.startsWith(\"Claude Code subscription setup is required\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription does not support\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription refuses conflicting\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription requires TLS\"))\n\t\t\t\t\t? error.message\n\t\t\t\t\t: undefined;\n\t\t\toutput.errorMessage = options.signal?.aborted\n\t\t\t\t? \"Claude Code request cancelled\"\n\t\t\t\t: (detail ??\n\t\t\t\t\t\"Claude Code request failed. Run /login anthropic to check setup and Claude Code version 2.1.263.\");\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t} finally {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tif (abortTimer) clearTimeout(abortTimer);\n\t\t\tif (child) {\n\t\t\t\tif (!complete || child.exitCode === null) await stopNativeOnce();\n\t\t\t\tif (child.exitCode === null) child.kill();\n\t\t\t}\n\t\t}\n\t})();\n\treturn stream;\n}\n"]}
1
+ {"version":3,"file":"anthropic-claude-code-bridge.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-claude-code-bridge.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,GAAG,EAEH,OAAO,EACP,KAAK,EACL,mBAAmB,EACnB,aAAa,EAGb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAwDvE,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,UAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoB/F;AA2JD,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3D;AAgGD,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,aAAa,GAAG,mBAAwB,GAC/C,2BAA2B,CA8R7B","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport type { ExternalClaudeCodeCredential } from \"../auth/types.ts\";\nimport { calculateCost } from \"../models.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamOptions,\n\tToolCall,\n\tUsage,\n} from \"../types.ts\";\nimport { getClaudeCodeWorkerPath } from \"../utils/claude-code-assets.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\n\nconst SAFE_ERRORS = {\n\tcontext_overflow: \"Claude Code prompt is too long for the context window.\",\n\ttimeout: \"Claude Code request timed out.\",\n\trate_limit: \"Claude Code rate limit reached. Please retry your request.\",\n\toverloaded: \"Claude Code service overloaded. Please retry your request.\",\n\ttransient: \"Claude Code service unavailable. Please retry your request.\",\n\tincomplete: \"Claude Code stream ended before message_stop. Please retry your request.\",\n} as const;\n\nclass ClaudeCodeRequestError extends Error {\n\tconstructor(category: unknown) {\n\t\tsuper(\n\t\t\ttypeof category === \"string\" && Object.hasOwn(SAFE_ERRORS, category)\n\t\t\t\t? SAFE_ERRORS[category as keyof typeof SAFE_ERRORS]\n\t\t\t\t: \"Claude Code request failed. Run /login anthropic to check setup and Claude Code version 2.1.263.\",\n\t\t);\n\t}\n}\n\nconst MAX_RECORD = 64 * 1024 * 1024;\nconst CONFLICTS = [\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANTHROPIC_BASE_URL\",\n\t\"ANTHROPIC_FOUNDRY_API_KEY\",\n\t\"CLAUDE_CODE_USE_BEDROCK\",\n\t\"CLAUDE_CODE_USE_VERTEX\",\n\t\"CLAUDE_CODE_USE_FOUNDRY\",\n\t\"CLAUDE_CODE_EXTRA_BODY\",\n] as const;\nconst ENV_ALLOWLIST = [\n\t\"PATH\",\n\t\"HOME\",\n\t\"USERPROFILE\",\n\t\"APPDATA\",\n\t\"LOCALAPPDATA\",\n\t\"PROGRAMDATA\",\n\t\"SYSTEMROOT\",\n\t\"SYSTEMDRIVE\",\n\t\"COMSPEC\",\n\t\"PATHEXT\",\n\t\"TEMP\",\n\t\"TMP\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"HTTP_PROXY\",\n\t\"HTTPS_PROXY\",\n\t\"NO_PROXY\",\n\t\"http_proxy\",\n\t\"https_proxy\",\n\t\"no_proxy\",\n] as const;\n\nexport function claudeCodeEnvironment(configDir?: string, strict = true): Record<string, string> {\n\tconst conflicting = CONFLICTS.filter((name) => {\n\t\tconst value = process.env[name];\n\t\treturn (\n\t\t\tvalue &&\n\t\t\t((name !== \"CLAUDE_CODE_USE_BEDROCK\" &&\n\t\t\t\tname !== \"CLAUDE_CODE_USE_VERTEX\" &&\n\t\t\t\tname !== \"CLAUDE_CODE_USE_FOUNDRY\") ||\n\t\t\t\t![\"0\", \"false\", \"no\", \"off\"].includes(value.toLowerCase()))\n\t\t);\n\t});\n\tif (strict && conflicting.length)\n\t\tthrow new Error(`Claude Code subscription refuses conflicting environment variables: ${conflicting.join(\", \")}`);\n\tif (process.env.NODE_TLS_REJECT_UNAUTHORIZED === \"0\")\n\t\tthrow new Error(\"Claude Code subscription requires TLS certificate verification\");\n\tconst env = Object.fromEntries(\n\t\tENV_ALLOWLIST.flatMap((name) => (process.env[name] ? [[name, process.env[name]]] : [])),\n\t);\n\tif (configDir) env.CLAUDE_SUBSCRIPTION_DIRECTSDK_CONFIG_DIR = configDir;\n\treturn env;\n}\n\nfunction contentHash(content: AssistantMessage[\"content\"]): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(content)).digest(\"hex\");\n}\n\nfunction history(\n\tcontext: Context,\n\tmodel: Model<Api>,\n\tconnection: ExternalClaudeCodeCredential,\n): { messages: unknown[]; tools: unknown[] } {\n\tconst messages: Record<string, unknown>[] = context.systemPrompt\n\t\t? [{ role: \"system\", content: context.systemPrompt }]\n\t\t: [];\n\tfor (const message of context.messages) {\n\t\tif (message.role === \"user\") {\n\t\t\tmessages.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent:\n\t\t\t\t\ttypeof message.content === \"string\"\n\t\t\t\t\t\t? message.content\n\t\t\t\t\t\t: message.content.map((block) =>\n\t\t\t\t\t\t\t\tblock.type === \"text\"\n\t\t\t\t\t\t\t\t\t? { type: \"text\", text: block.text }\n\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"image_url\",\n\t\t\t\t\t\t\t\t\t\t\timage_url: { url: `data:${block.mimeType};base64,${block.data}` },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t),\n\t\t\t});\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst text = message.content\n\t\t\t\t.filter((block) => block.type === \"text\")\n\t\t\t\t.map((block) => block.text)\n\t\t\t\t.join(\"\");\n\t\t\tconst calls = message.content\n\t\t\t\t.filter((block): block is ToolCall => block.type === \"toolCall\")\n\t\t\t\t.map((block) => ({\n\t\t\t\t\tid: block.id,\n\t\t\t\t\ttype: \"function\",\n\t\t\t\t\tfunction: { name: block.name, arguments: JSON.stringify(block.arguments) },\n\t\t\t\t}));\n\t\t\tconst projection = {\n\t\t\t\tcontent: text.trim(),\n\t\t\t\ttool_calls: calls.map((call) => ({\n\t\t\t\t\tid: call.id,\n\t\t\t\t\tname: call.function.name,\n\t\t\t\t\tinput: JSON.parse(call.function.arguments),\n\t\t\t\t})),\n\t\t\t};\n\t\t\tconst carrier =\n\t\t\t\tmessage.provider === \"anthropic\" &&\n\t\t\t\tmessage.model === model.id &&\n\t\t\t\tmessage.claudeCodeCarrier?.version === 1 &&\n\t\t\t\tconnection.accountFingerprint &&\n\t\t\t\tmessage.claudeCodeCarrier.accountFingerprint === connection.accountFingerprint &&\n\t\t\t\tmessage.claudeCodeCarrier.contentHash === contentHash(message.content) &&\n\t\t\t\tJSON.stringify(message.claudeCodeCarrier.projection) === JSON.stringify(projection)\n\t\t\t\t\t? [{ type: \"claude-subscription-directsdk-experimental.native_assistant\", ...message.claudeCodeCarrier }]\n\t\t\t\t\t: [];\n\t\t\tmessages.push({ role: \"assistant\", content: text, tool_calls: calls, reasoning_details: carrier });\n\t\t} else {\n\t\t\tif (message.content.some((block) => block.type !== \"text\"))\n\t\t\t\tthrow new Error(\"Claude Code subscription supports text tool results only\");\n\t\t\tmessages.push({\n\t\t\t\trole: \"tool\",\n\t\t\t\ttool_call_id: message.toolCallId,\n\t\t\t\tcontent: message.content.map((block) => (block.type === \"text\" ? block.text : \"\")).join(\"\"),\n\t\t\t\tis_error: message.isError,\n\t\t\t});\n\t\t}\n\t}\n\tconst tools = (context.tools ?? []).map((tool) => ({\n\t\ttype: \"function\",\n\t\tfunction: {\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: tool.parameters,\n\t\t},\n\t}));\n\treturn { messages, tools };\n}\n\nfunction validateOptions(model: Model<Api>, options: StreamOptions | SimpleStreamOptions): void {\n\tif (!options.externalClaudeCode)\n\t\tthrow new Error(\"Claude Code subscription setup is required. Run /login anthropic.\");\n\tif (\n\t\toptions.apiKey ||\n\t\tObject.keys(options.headers ?? {}).length ||\n\t\toptions.onPayload ||\n\t\toptions.onResponse ||\n\t\toptions.env ||\n\t\toptions.metadata ||\n\t\t(options.transport && options.transport !== \"auto\") ||\n\t\toptions.maxRetries ||\n\t\t(options.maxRetryDelayMs !== undefined && options.maxRetryDelayMs !== 60_000) ||\n\t\toptions.websocketConnectTimeoutMs ||\n\t\tmodel.baseUrl !== \"https://api.anthropic.com\"\n\t) {\n\t\tthrow new Error(\"Claude Code subscription does not support API keys, alternate endpoints, or request mutations\");\n\t}\n\tconst anthro = options as StreamOptions & {\n\t\ttoolChoice?: unknown;\n\t\tclient?: unknown;\n\t\textraBody?: unknown;\n\t\tthinkingBudgetTokens?: unknown;\n\t\tstopSequences?: unknown;\n\t\tthinkingEnabled?: boolean;\n\t\teffort?: string;\n\t\tthinkingDisplay?: unknown;\n\t\tinterleavedThinking?: unknown;\n\t};\n\tif (\n\t\t(anthro.toolChoice && anthro.toolChoice !== \"auto\") ||\n\t\tanthro.client ||\n\t\tanthro.extraBody ||\n\t\tanthro.thinkingBudgetTokens ||\n\t\tanthro.stopSequences ||\n\t\tanthro.thinkingDisplay !== undefined ||\n\t\tanthro.interleavedThinking !== undefined\n\t) {\n\t\tthrow new Error(\"Claude Code subscription does not support forced tools or native request overrides\");\n\t}\n\tif (options.temperature !== undefined)\n\t\tthrow new Error(\"Claude Code subscription does not support sampling overrides\");\n}\n\ntype WorkerEvent = {\n\tv: 1;\n\trequestId: string;\n\ttype: string;\n\ttext?: unknown;\n\tpid?: unknown;\n\tresponse?: unknown;\n\tcategory?: unknown;\n};\n\nfunction parseWorkerRecord(line: string, requestId: string): WorkerEvent {\n\tif (Buffer.byteLength(line) > MAX_RECORD) throw new Error(\"Claude Code bridge record exceeds size limit\");\n\tconst value: unknown = JSON.parse(line);\n\tif (\n\t\t!value ||\n\t\ttypeof value !== \"object\" ||\n\t\t!(\"v\" in value) ||\n\t\tvalue.v !== 1 ||\n\t\t!(\"requestId\" in value) ||\n\t\tvalue.requestId !== requestId ||\n\t\t!(\"type\" in value) ||\n\t\ttypeof value.type !== \"string\"\n\t) {\n\t\tthrow new Error(\"Invalid Claude Code bridge record\");\n\t}\n\treturn value as WorkerEvent;\n}\n\nexport async function stopNative(pid: number): Promise<void> {\n\tif (!Number.isSafeInteger(pid) || pid < 1) return;\n\tif (process.platform === \"win32\") {\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst killer = spawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], { stdio: \"ignore\", windowsHide: true });\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tkiller.kill();\n\t\t\t\tresolve();\n\t\t\t}, 3000);\n\t\t\tkiller.once(\"error\", () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t\tkiller.once(\"close\", () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t} else {\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t/* Already exited. */\n\t\t}\n\t}\n}\n\nfunction validatedCompletion(\n\tvalue: unknown,\n\tmodel: Model<Api>,\n\ttext: string,\n\tthinking: string,\n): {\n\tcontent: AssistantMessage[\"content\"];\n\tcarrier: AssistantMessage[\"claudeCodeCarrier\"];\n\tusage: Usage;\n\tresponseId: string;\n\treason: \"stop\" | \"length\" | \"toolUse\";\n} {\n\tif (!value || typeof value !== \"object\" || !(\"choices\" in value) || !Array.isArray(value.choices))\n\t\tthrow new Error(\"Invalid native completion\");\n\tconst response = value as Record<string, unknown> & {\n\t\tchoices: { finish_reason: unknown; message: Record<string, unknown> }[];\n\t\tusage: Record<string, unknown>;\n\t};\n\tconst choice = response.choices[0];\n\tif (\n\t\t!choice ||\n\t\t![\"stop\", \"length\", \"tool_calls\"].includes(String(choice.finish_reason)) ||\n\t\t(typeof choice.message?.content !== \"string\" && choice.message?.content !== null) ||\n\t\t(choice.message.content ?? \"\") !== text ||\n\t\t(choice.message.reasoning_content ?? \"\") !== thinking\n\t)\n\t\tthrow new Error(\"Native completion differs from streamed content\");\n\tconst rawCalls = choice.message.tool_calls ?? [];\n\tif (!Array.isArray(rawCalls)) throw new Error(\"Invalid native tool calls\");\n\tconst calls: ToolCall[] = rawCalls.map((call) => {\n\t\tif (\n\t\t\t!call ||\n\t\t\ttypeof call !== \"object\" ||\n\t\t\ttypeof call.id !== \"string\" ||\n\t\t\ttypeof call.function?.name !== \"string\" ||\n\t\t\ttypeof call.function?.arguments !== \"string\"\n\t\t)\n\t\t\tthrow new Error(\"Invalid native tool call\");\n\t\tconst args: unknown = JSON.parse(call.function.arguments);\n\t\tif (!args || typeof args !== \"object\" || Array.isArray(args)) throw new Error(\"Invalid native tool arguments\");\n\t\treturn { type: \"toolCall\", id: call.id, name: call.function.name, arguments: args as Record<string, unknown> };\n\t});\n\tconst native = response.usage;\n\tif (\n\t\t!native ||\n\t\ttypeof native.native_admission !== \"object\" ||\n\t\tnative.native_admission === null ||\n\t\t(native.native_admission as { upstream_requests?: unknown }).upstream_requests !== 1\n\t)\n\t\tthrow new Error(\"Native one-request admission not verified\");\n\tif (\n\t\t!native ||\n\t\t!Number.isFinite(native.prompt_tokens) ||\n\t\t!Number.isFinite(native.completion_tokens) ||\n\t\t!Number.isFinite(native.cache_creation_input_tokens) ||\n\t\t!(\"prompt_tokens_details\" in native)\n\t)\n\t\tthrow new Error(\"Missing native usage\");\n\tconst cached = (native.prompt_tokens_details as { cached_tokens?: unknown }).cached_tokens;\n\tif (!Number.isFinite(cached)) throw new Error(\"Missing native cache usage\");\n\tconst usage: Usage = {\n\t\tinput: Number(native.prompt_tokens) - Number(cached) - Number(native.cache_creation_input_tokens),\n\t\toutput: Number(native.completion_tokens),\n\t\tcacheRead: Number(cached),\n\t\tcacheWrite: Number(native.cache_creation_input_tokens),\n\t\ttotalTokens: Number(native.total_tokens),\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t};\n\tif (usage.input < 0 || usage.output < 0) throw new Error(\"Invalid native usage\");\n\tcalculateCost(model, usage);\n\tconst details = choice.message.reasoning_details;\n\tconst carrier = Array.isArray(details)\n\t\t? details.find((entry) => entry?.type === \"claude-subscription-directsdk-experimental.native_assistant\")\n\t\t: undefined;\n\tconst projection = {\n\t\tcontent: text.trim(),\n\t\ttool_calls: calls.map((call) => ({ id: call.id, name: call.name, input: call.arguments })),\n\t};\n\tif (\n\t\t!carrier ||\n\t\tcarrier.version !== 1 ||\n\t\t!Array.isArray(carrier.messages) ||\n\t\tJSON.stringify(carrier.projection) !== JSON.stringify(projection)\n\t)\n\t\tthrow new Error(\"Invalid native replay carrier\");\n\treturn {\n\t\tcontent: [...(text ? [{ type: \"text\" as const, text }] : []), ...calls],\n\t\tcarrier: { version: 1, messages: carrier.messages, projection },\n\t\tusage,\n\t\tresponseId: String(response.id ?? \"\"),\n\t\treason: choice.finish_reason === \"tool_calls\" ? \"toolUse\" : choice.finish_reason === \"length\" ? \"length\" : \"stop\",\n\t};\n}\n\nexport function streamClaudeCode(\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions: StreamOptions | SimpleStreamOptions = {},\n): AssistantMessageEventStream {\n\tconst stream = new AssistantMessageEventStream();\n\tconst output: AssistantMessage = {\n\t\trole: \"assistant\",\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tcontent: [],\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\tvoid (async () => {\n\t\tlet child: ChildProcessWithoutNullStreams | undefined;\n\t\tlet nativePid: number | undefined;\n\t\tlet nativeCleanup: Promise<void> | undefined;\n\t\tconst stopNativeOnce = () => {\n\t\t\tif (nativePid === undefined) return Promise.resolve();\n\t\t\tnativeCleanup ??= stopNative(nativePid);\n\t\t\treturn nativeCleanup;\n\t\t};\n\t\tlet timedOut = false;\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tlet abortTimer: NodeJS.Timeout | undefined;\n\t\tlet started = false;\n\t\tlet complete = false;\n\t\tlet buffer = \"\";\n\t\tconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\n\t\tlet text = \"\";\n\t\tlet thinking = \"\";\n\t\tlet activeIndex = -1;\n\t\tlet activeType: \"text\" | \"thinking\" | undefined;\n\t\ttry {\n\t\t\tvalidateOptions(model, options);\n\t\t\tif (options.signal?.aborted) throw new Error(\"Claude Code request cancelled\");\n\t\t\tconst connection = options.externalClaudeCode!;\n\t\t\tconst env = claudeCodeEnvironment(connection.configDir);\n\t\t\tconst input = history(context, model, connection);\n\t\t\tconst anthropicOptions = options as StreamOptions & { thinkingEnabled?: boolean; effort?: string };\n\t\t\tconst reasoning =\n\t\t\t\t\"reasoning\" in options && options.reasoning\n\t\t\t\t\t? { enabled: true, effort: options.reasoning }\n\t\t\t\t\t: anthropicOptions.thinkingEnabled !== undefined || anthropicOptions.effort\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tenabled: anthropicOptions.thinkingEnabled ?? true,\n\t\t\t\t\t\t\t\t...(anthropicOptions.effort ? { effort: anthropicOptions.effort } : {}),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined;\n\t\t\tconst requestId = randomUUID();\n\t\t\tchild = spawn(connection.python, [\"-s\", \"-B\", \"-u\", getClaudeCodeWorkerPath(\"lunr_bridge.py\")], {\n\t\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t\tenv,\n\t\t\t});\n\t\t\tconst worker = child;\n\t\t\tworker.stderr.resume();\n\t\t\tworker.stdin.on(\"error\", () => {});\n\t\t\tconst idleMs = Math.min(300_000, Math.max(1000, options.timeoutMs ?? 180_000));\n\t\t\tconst record = {\n\t\t\t\tv: 1,\n\t\t\t\trequestId,\n\t\t\t\ttype: \"start\",\n\t\t\t\tcommand: connection.command,\n\t\t\t\tenv,\n\t\t\t\tmodel: model.id,\n\t\t\t\t...input,\n\t\t\t\textraBody: {\n\t\t\t\t\t...(options.maxTokens ? { max_tokens: options.maxTokens } : {}),\n\t\t\t\t\t...(reasoning ? { reasoning } : {}),\n\t\t\t\t},\n\t\t\t\ttimeout: Math.round(idleMs / 1000),\n\t\t\t};\n\t\t\tconst payload = JSON.stringify(record);\n\t\t\tif (Buffer.byteLength(payload) > MAX_RECORD) throw new Error(\"Claude Code request exceeds size limit\");\n\t\t\tworker.stdin.write(`${payload}\\n`);\n\t\t\tlet cancelSent = false;\n\t\t\tconst abort = () => {\n\t\t\t\tif (cancelSent) return;\n\t\t\t\tcancelSent = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tworker.stdin.write(`${JSON.stringify({ v: 1, requestId, type: \"cancel\" })}\\n`);\n\t\t\t\tabortTimer = setTimeout(() => {\n\t\t\t\t\tvoid stopNativeOnce().then(() => worker.kill());\n\t\t\t\t}, 2500);\n\t\t\t};\n\t\t\tconst resetDeadline = () => {\n\t\t\t\tif (cancelSent) return;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tabort();\n\t\t\t\t}, idleMs);\n\t\t\t};\n\t\t\ttimer = setTimeout(\n\t\t\t\t() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tabort();\n\t\t\t\t},\n\t\t\t\tMath.min(30_000, idleMs),\n\t\t\t);\n\t\t\toptions.signal?.addEventListener(\"abort\", abort, { once: true });\n\t\t\tif (options.signal?.aborted) abort();\n\t\t\ttry {\n\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\tworker.on(\"error\", reject);\n\t\t\t\t\tworker.stdout.on(\"data\", (data: Buffer) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tbuffer += decoder.decode(data, { stream: true });\n\t\t\t\t\t\t\tif (Buffer.byteLength(buffer) > MAX_RECORD)\n\t\t\t\t\t\t\t\tthrow new Error(\"Claude Code bridge record exceeds size limit\");\n\t\t\t\t\t\t\tfor (let newline = buffer.indexOf(\"\\n\"); newline >= 0; newline = buffer.indexOf(\"\\n\")) {\n\t\t\t\t\t\t\t\tconst event = parseWorkerRecord(buffer.slice(0, newline), requestId);\n\t\t\t\t\t\t\t\tbuffer = buffer.slice(newline + 1);\n\t\t\t\t\t\t\t\tif (event.type === \"ready\" || event.type === \"replay_progress\") resetDeadline();\n\t\t\t\t\t\t\t\telse if (\n\t\t\t\t\t\t\t\t\tevent.type === \"native_started\" &&\n\t\t\t\t\t\t\t\t\tNumber.isSafeInteger(event.pid) &&\n\t\t\t\t\t\t\t\t\tNumber(event.pid) > 0\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnativePid = Number(event.pid);\n\t\t\t\t\t\t\t\t\tresetDeadline();\n\t\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\t\tcancelSent &&\n\t\t\t\t\t\t\t\t\t[\"start\", \"text_delta\", \"thinking_delta\", \"complete\"].includes(event.type)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\telse if (event.type === \"start\") {\n\t\t\t\t\t\t\t\t\tif (started) throw new Error(\"Duplicate Claude Code stream start\");\n\t\t\t\t\t\t\t\t\tstarted = true;\n\t\t\t\t\t\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t\t\t\t\t\t} else if (event.type === \"text_delta\" || event.type === \"thinking_delta\") {\n\t\t\t\t\t\t\t\t\tif (!started || complete || typeof event.text !== \"string\")\n\t\t\t\t\t\t\t\t\t\tthrow new Error(\"Invalid Claude Code stream delta\");\n\t\t\t\t\t\t\t\t\tresetDeadline();\n\t\t\t\t\t\t\t\t\tconst kind = event.type === \"text_delta\" ? \"text\" : \"thinking\";\n\t\t\t\t\t\t\t\t\tif (activeType !== kind) {\n\t\t\t\t\t\t\t\t\t\tif (activeType === \"text\")\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { text: string }).text,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tif (activeType === \"thinking\")\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { thinking: string }).thinking,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tactiveIndex = output.content.length;\n\t\t\t\t\t\t\t\t\t\tactiveType = kind;\n\t\t\t\t\t\t\t\t\t\tif (kind === \"text\") {\n\t\t\t\t\t\t\t\t\t\t\toutput.content.push({ type: \"text\", text: \"\" });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"text_start\", contentIndex: activeIndex, partial: output });\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\toutput.content.push({ type: \"thinking\", thinking: \"\" });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: activeIndex, partial: output });\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconst block = output.content[activeIndex];\n\t\t\t\t\t\t\t\t\tif (kind === \"text\" && block?.type === \"text\") {\n\t\t\t\t\t\t\t\t\t\ttext += event.text;\n\t\t\t\t\t\t\t\t\t\tblock.text += event.text;\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (kind === \"thinking\" && block?.type === \"thinking\") {\n\t\t\t\t\t\t\t\t\t\tthinking += event.text;\n\t\t\t\t\t\t\t\t\t\tblock.thinking += event.text;\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (event.type === \"complete\") {\n\t\t\t\t\t\t\t\t\tif (!started || complete) throw new Error(\"Invalid Claude Code completion order\");\n\t\t\t\t\t\t\t\t\tconst result = validatedCompletion(event.response, model, text, thinking);\n\t\t\t\t\t\t\t\t\tcomplete = true;\n\t\t\t\t\t\t\t\t\tif (activeType === \"text\")\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { text: string }).text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tif (activeType === \"thinking\")\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { thinking: string }).thinking,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\toutput.content = [\n\t\t\t\t\t\t\t\t\t\t...output.content.filter((block) => block.type !== \"toolCall\"),\n\t\t\t\t\t\t\t\t\t\t...result.content.filter((block) => block.type === \"toolCall\"),\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t\toutput.claudeCodeCarrier = result.carrier && {\n\t\t\t\t\t\t\t\t\t\t...result.carrier,\n\t\t\t\t\t\t\t\t\t\tcontentHash: contentHash(output.content),\n\t\t\t\t\t\t\t\t\t\taccountFingerprint: connection.accountFingerprint,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\toutput.responseId = result.responseId;\n\t\t\t\t\t\t\t\t\toutput.usage = result.usage;\n\t\t\t\t\t\t\t\t\toutput.stopReason = result.reason;\n\t\t\t\t\t\t\t\t\tfor (let index = 0; index < output.content.length; index++) {\n\t\t\t\t\t\t\t\t\t\tconst block = output.content[index];\n\t\t\t\t\t\t\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: index, partial: output });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"toolcall_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\t\t\t\t\ttoolCall: block,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (event.type === \"cancelled\" && cancelSent) {\n\t\t\t\t\t\t\t\t} else if (event.type === \"error\" || event.type === \"cancelled\")\n\t\t\t\t\t\t\t\t\tthrow new ClaudeCodeRequestError(event.type === \"error\" ? event.category : undefined);\n\t\t\t\t\t\t\t\telse throw new Error(\"Unknown Claude Code bridge event\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tworker.on(\"close\", () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tdecoder.decode();\n\t\t\t\t\t\t\tif (complete || cancelSent) resolve();\n\t\t\t\t\t\t\telse reject(new Error(\"Claude Code bridge exited without a validated completion\"));\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\toptions.signal?.removeEventListener(\"abort\", abort);\n\t\t\t}\n\t\t\tif (timedOut) throw new ClaudeCodeRequestError(\"timeout\");\n\t\t\tif (options.signal?.aborted) throw new Error(\"Claude Code request cancelled\");\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason as \"stop\" | \"length\" | \"toolUse\", message: output });\n\t\t} catch (error) {\n\t\t\toutput.content = [];\n\t\t\toutput.claudeCodeCarrier = undefined;\n\t\t\toutput.stopReason = options.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\tconst detail =\n\t\t\t\terror instanceof Error &&\n\t\t\t\t(error.message.startsWith(\"Claude Code subscription setup is required\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription does not support\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription refuses conflicting\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription requires TLS\"))\n\t\t\t\t\t? error.message\n\t\t\t\t\t: undefined;\n\t\t\toutput.errorMessage = options.signal?.aborted\n\t\t\t\t? \"Claude Code request cancelled\"\n\t\t\t\t: timedOut\n\t\t\t\t\t? SAFE_ERRORS.timeout\n\t\t\t\t\t: error instanceof ClaudeCodeRequestError\n\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t: (detail ??\n\t\t\t\t\t\t\t\"Claude Code request failed. Run /login anthropic to check setup and Claude Code version 2.1.263.\");\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t} finally {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tif (abortTimer) clearTimeout(abortTimer);\n\t\t\tif (child) {\n\t\t\t\tif (!complete || child.exitCode === null) await stopNativeOnce();\n\t\t\t\tif (child.exitCode === null) child.kill();\n\t\t\t}\n\t\t}\n\t})();\n\treturn stream;\n}\n"]}