@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 +22 -0
- package/README.md +90 -84
- package/dist/api/anthropic-claude-code-bridge.d.ts.map +1 -1
- package/dist/api/anthropic-claude-code-bridge.js +29 -9
- package/dist/api/anthropic-claude-code-bridge.js.map +1 -1
- package/dist/auth/helpers.d.ts.map +1 -1
- package/dist/auth/helpers.js +2 -2
- package/dist/auth/helpers.js.map +1 -1
- package/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/dist/auth/oauth/anthropic.js +3 -4
- package/dist/auth/oauth/anthropic.js.map +1 -1
- package/dist/utils/claude-code-assets.d.ts +8 -0
- package/dist/utils/claude-code-assets.d.ts.map +1 -0
- package/dist/utils/claude-code-assets.js +16 -0
- package/dist/utils/claude-code-assets.js.map +1 -0
- package/package.json +1 -1
- package/vendor/hermes-claude-subscription-directsdk/lunr_bridge.py +26 -2
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
|
-
# @
|
|
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 @
|
|
94
|
+
npm install @ashx-j/lunr-ai
|
|
94
95
|
```
|
|
95
96
|
|
|
96
|
-
TypeBox exports are re-exported from `@
|
|
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 '@
|
|
104
|
-
import { builtinModels } from '@
|
|
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 {
|
|
240
|
-
import {
|
|
241
|
-
import {
|
|
242
|
-
import {
|
|
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 '@
|
|
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 '@
|
|
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 '@
|
|
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(
|
|
316
|
-
|
|
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
|
-
|
|
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.
|
|
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 '@
|
|
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
|
|
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`
|
|
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 '@
|
|
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 '@
|
|
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 '@
|
|
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 `@
|
|
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 '@
|
|
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 '@
|
|
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 '@
|
|
954
|
-
import { openAICompletionsApi } from '@
|
|
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 '@
|
|
1000
|
-
import { openAIResponsesApi } from '@
|
|
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 '@
|
|
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 `@
|
|
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 '@
|
|
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 '@
|
|
1252
|
-
import { anthropicProvider } from '@
|
|
1253
|
-
import { openaiProvider } from '@
|
|
1254
|
-
import { googleProvider } from '@
|
|
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 '@
|
|
1322
|
-
import { anthropicProvider } from '@
|
|
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 '@
|
|
1349
|
-
import { openaiProvider } from '@
|
|
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
|
-
- `@
|
|
1358
|
-
- `@
|
|
1359
|
-
- `@
|
|
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 `@
|
|
1364
|
+
- Importing `@ashx-j/lunr-ai/api/<api-id>` directly loads that API implementation and its SDK immediately.
|
|
1363
1365
|
|
|
1364
|
-
Avoid `@
|
|
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 '@
|
|
1380
|
-
import { amazonBedrockProvider } from '@
|
|
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 '@
|
|
1390
|
-
import { bedrockProviderModule } from '@
|
|
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**
|
|
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
|
-
|
|
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 '@
|
|
1428
|
-
import {
|
|
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 });
|
|
1431
|
-
models.setProvider(
|
|
1432
|
+
const models = createModels({ credentials: myStore });
|
|
1433
|
+
models.setProvider(githubCopilotProvider());
|
|
1432
1434
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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
|
-
|
|
1442
|
-
if (event.type === '
|
|
1443
|
-
|
|
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
|
-
//
|
|
1453
|
-
const model = models.
|
|
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 @
|
|
1488
|
-
npx @
|
|
1489
|
-
npx @
|
|
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
|
-
|
|
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
|
|
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 '@
|
|
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 '@
|
|
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 `@
|
|
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 `@
|
|
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"]}
|