@thinwrap/llm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/cjs/base/base.connector.d.ts +44 -0
  4. package/dist/cjs/base/base.connector.js +216 -0
  5. package/dist/cjs/facades/chat.facade.d.ts +17 -0
  6. package/dist/cjs/facades/chat.facade.js +63 -0
  7. package/dist/cjs/facades/embeddings.facade.d.ts +23 -0
  8. package/dist/cjs/facades/embeddings.facade.js +63 -0
  9. package/dist/cjs/index.d.ts +15 -0
  10. package/dist/cjs/index.js +35 -0
  11. package/dist/cjs/package.json +1 -0
  12. package/dist/cjs/providers/_shared/openai-compat.connector.d.ts +23 -0
  13. package/dist/cjs/providers/_shared/openai-compat.connector.js +320 -0
  14. package/dist/cjs/providers/_shared/openai-compat.embeddings.connector.d.ts +18 -0
  15. package/dist/cjs/providers/_shared/openai-compat.embeddings.connector.js +105 -0
  16. package/dist/cjs/providers/_shared/spec.d.ts +99 -0
  17. package/dist/cjs/providers/_shared/spec.js +84 -0
  18. package/dist/cjs/providers/anthropic/anthropic.config.d.ts +17 -0
  19. package/dist/cjs/providers/anthropic/anthropic.config.js +2 -0
  20. package/dist/cjs/providers/anthropic/anthropic.connector.d.ts +30 -0
  21. package/dist/cjs/providers/anthropic/anthropic.connector.js +427 -0
  22. package/dist/cjs/providers/anthropic/index.d.ts +2 -0
  23. package/dist/cjs/providers/anthropic/index.js +5 -0
  24. package/dist/cjs/providers/bedrock/bedrock.config.d.ts +18 -0
  25. package/dist/cjs/providers/bedrock/bedrock.config.js +2 -0
  26. package/dist/cjs/providers/bedrock/bedrock.connector.d.ts +61 -0
  27. package/dist/cjs/providers/bedrock/bedrock.connector.js +479 -0
  28. package/dist/cjs/providers/bedrock/index.d.ts +2 -0
  29. package/dist/cjs/providers/bedrock/index.js +5 -0
  30. package/dist/cjs/providers/gemini/gemini.config.d.ts +12 -0
  31. package/dist/cjs/providers/gemini/gemini.config.js +2 -0
  32. package/dist/cjs/providers/gemini/gemini.connector.d.ts +28 -0
  33. package/dist/cjs/providers/gemini/gemini.connector.js +434 -0
  34. package/dist/cjs/providers/gemini/index.d.ts +2 -0
  35. package/dist/cjs/providers/gemini/index.js +5 -0
  36. package/dist/cjs/types/chat.types.d.ts +115 -0
  37. package/dist/cjs/types/chat.types.js +2 -0
  38. package/dist/cjs/types/config-map.type.d.ts +29 -0
  39. package/dist/cjs/types/config-map.type.js +2 -0
  40. package/dist/cjs/types/embeddings.types.d.ts +29 -0
  41. package/dist/cjs/types/embeddings.types.js +2 -0
  42. package/dist/cjs/types/error.types.d.ts +20 -0
  43. package/dist/cjs/types/error.types.js +22 -0
  44. package/dist/cjs/types/index.d.ts +7 -0
  45. package/dist/cjs/types/index.js +23 -0
  46. package/dist/cjs/types/passthrough.type.d.ts +15 -0
  47. package/dist/cjs/types/passthrough.type.js +2 -0
  48. package/dist/cjs/types/provider-id.enum.d.ts +8 -0
  49. package/dist/cjs/types/provider-id.enum.js +29 -0
  50. package/dist/cjs/types/provider.interface.d.ts +7 -0
  51. package/dist/cjs/types/provider.interface.js +2 -0
  52. package/dist/esm/base/base.connector.d.ts +44 -0
  53. package/dist/esm/base/base.connector.js +209 -0
  54. package/dist/esm/facades/chat.facade.d.ts +17 -0
  55. package/dist/esm/facades/chat.facade.js +59 -0
  56. package/dist/esm/facades/embeddings.facade.d.ts +23 -0
  57. package/dist/esm/facades/embeddings.facade.js +59 -0
  58. package/dist/esm/index.d.ts +15 -0
  59. package/dist/esm/index.js +9 -0
  60. package/dist/esm/package.json +1 -0
  61. package/dist/esm/providers/_shared/openai-compat.connector.d.ts +23 -0
  62. package/dist/esm/providers/_shared/openai-compat.connector.js +316 -0
  63. package/dist/esm/providers/_shared/openai-compat.embeddings.connector.d.ts +18 -0
  64. package/dist/esm/providers/_shared/openai-compat.embeddings.connector.js +101 -0
  65. package/dist/esm/providers/_shared/spec.d.ts +99 -0
  66. package/dist/esm/providers/_shared/spec.js +81 -0
  67. package/dist/esm/providers/anthropic/anthropic.config.d.ts +17 -0
  68. package/dist/esm/providers/anthropic/anthropic.config.js +1 -0
  69. package/dist/esm/providers/anthropic/anthropic.connector.d.ts +30 -0
  70. package/dist/esm/providers/anthropic/anthropic.connector.js +423 -0
  71. package/dist/esm/providers/anthropic/index.d.ts +2 -0
  72. package/dist/esm/providers/anthropic/index.js +1 -0
  73. package/dist/esm/providers/bedrock/bedrock.config.d.ts +18 -0
  74. package/dist/esm/providers/bedrock/bedrock.config.js +1 -0
  75. package/dist/esm/providers/bedrock/bedrock.connector.d.ts +61 -0
  76. package/dist/esm/providers/bedrock/bedrock.connector.js +474 -0
  77. package/dist/esm/providers/bedrock/index.d.ts +2 -0
  78. package/dist/esm/providers/bedrock/index.js +1 -0
  79. package/dist/esm/providers/gemini/gemini.config.d.ts +12 -0
  80. package/dist/esm/providers/gemini/gemini.config.js +1 -0
  81. package/dist/esm/providers/gemini/gemini.connector.d.ts +28 -0
  82. package/dist/esm/providers/gemini/gemini.connector.js +430 -0
  83. package/dist/esm/providers/gemini/index.d.ts +2 -0
  84. package/dist/esm/providers/gemini/index.js +1 -0
  85. package/dist/esm/types/chat.types.d.ts +115 -0
  86. package/dist/esm/types/chat.types.js +1 -0
  87. package/dist/esm/types/config-map.type.d.ts +29 -0
  88. package/dist/esm/types/config-map.type.js +1 -0
  89. package/dist/esm/types/embeddings.types.d.ts +29 -0
  90. package/dist/esm/types/embeddings.types.js +1 -0
  91. package/dist/esm/types/error.types.d.ts +20 -0
  92. package/dist/esm/types/error.types.js +18 -0
  93. package/dist/esm/types/index.d.ts +7 -0
  94. package/dist/esm/types/index.js +7 -0
  95. package/dist/esm/types/passthrough.type.d.ts +15 -0
  96. package/dist/esm/types/passthrough.type.js +1 -0
  97. package/dist/esm/types/provider-id.enum.d.ts +8 -0
  98. package/dist/esm/types/provider-id.enum.js +26 -0
  99. package/dist/esm/types/provider.interface.d.ts +7 -0
  100. package/dist/esm/types/provider.interface.js +1 -0
  101. package/package.json +73 -0
  102. package/src/providers/anthropic/README.md +64 -0
  103. package/src/providers/azure-openai/README.md +62 -0
  104. package/src/providers/bedrock/README.md +71 -0
  105. package/src/providers/cloudflare/README.md +62 -0
  106. package/src/providers/deepinfra/README.md +61 -0
  107. package/src/providers/deepseek/README.md +50 -0
  108. package/src/providers/fireworks/README.md +62 -0
  109. package/src/providers/gemini/README.md +67 -0
  110. package/src/providers/groq/README.md +50 -0
  111. package/src/providers/lmstudio/README.md +62 -0
  112. package/src/providers/mistral/README.md +61 -0
  113. package/src/providers/ollama/README.md +62 -0
  114. package/src/providers/openai/README.md +62 -0
  115. package/src/providers/openrouter/README.md +62 -0
  116. package/src/providers/perplexity/README.md +50 -0
  117. package/src/providers/together/README.md +61 -0
  118. package/src/providers/vllm/README.md +62 -0
  119. package/src/providers/xai/README.md +50 -0
@@ -0,0 +1,15 @@
1
+ export interface Passthrough {
2
+ /**
3
+ * Merged into the outbound request JSON body; overrides connector-built fields
4
+ * on key collision. Never transformed or validated — if you populate this from
5
+ * untrusted input, sanitize it yourself (you own the keys that reach the wire).
6
+ */
7
+ body?: Record<string, unknown>;
8
+ /** Merged into request headers. */
9
+ headers?: Record<string, string>;
10
+ /** Appended to the request URL query string. */
11
+ query?: Record<string, string>;
12
+ }
13
+ export type WithPassthrough<T> = T & {
14
+ _passthrough?: Passthrough;
15
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Provider ids implemented so far — the 15 first-class OpenAI-compatible
3
+ * providers (one shared connector) plus native-adapter providers as they land
4
+ * (`anthropic`). Remaining natives (bedrock, gemini, vertex, cohere) are added
5
+ * when their adapters ship.
6
+ */
7
+ export declare const LLM_PROVIDER_IDS: readonly ["openai", "azure-openai", "openrouter", "groq", "together", "fireworks", "deepseek", "xai", "mistral", "perplexity", "deepinfra", "cloudflare", "vllm", "ollama", "lmstudio", "anthropic", "bedrock", "gemini"];
8
+ export type LlmProviderId = (typeof LLM_PROVIDER_IDS)[number];
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Provider ids implemented so far — the 15 first-class OpenAI-compatible
3
+ * providers (one shared connector) plus native-adapter providers as they land
4
+ * (`anthropic`). Remaining natives (bedrock, gemini, vertex, cohere) are added
5
+ * when their adapters ship.
6
+ */
7
+ export const LLM_PROVIDER_IDS = [
8
+ 'openai',
9
+ 'azure-openai',
10
+ 'openrouter',
11
+ 'groq',
12
+ 'together',
13
+ 'fireworks',
14
+ 'deepseek',
15
+ 'xai',
16
+ 'mistral',
17
+ 'perplexity',
18
+ 'deepinfra',
19
+ 'cloudflare',
20
+ 'vllm',
21
+ 'ollama',
22
+ 'lmstudio',
23
+ 'anthropic',
24
+ 'bedrock',
25
+ 'gemini',
26
+ ];
@@ -0,0 +1,7 @@
1
+ import type { ChatInput, ChatResult, ChatStreamDelta } from './chat.types.js';
2
+ /** The contract every chat connector satisfies — shared-compat or native adapter. */
3
+ export interface IChatConnector {
4
+ readonly id: string;
5
+ complete(input: ChatInput): Promise<ChatResult>;
6
+ stream(input: ChatInput): AsyncIterable<ChatStreamDelta>;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@thinwrap/llm",
3
+ "version": "1.0.0",
4
+ "description": "Unified, zero-dependency TypeScript facade over LLM chat-completion (and embeddings) providers. Stateless, BYO fetch, no vendor SDKs.",
5
+ "main": "./dist/cjs/index.js",
6
+ "module": "./dist/esm/index.js",
7
+ "types": "./dist/cjs/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/esm/index.d.ts",
12
+ "default": "./dist/esm/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/cjs/index.d.ts",
16
+ "default": "./dist/cjs/index.js"
17
+ }
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src/providers/*/README.md"
23
+ ],
24
+ "sideEffects": false,
25
+ "publishConfig": {
26
+ "provenance": true,
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "prebuild": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
31
+ "build": "npm run build:cjs && npm run build:esm && node scripts/fix-esm-imports.mjs",
32
+ "build:cjs": "tsc -p tsconfig.build.json",
33
+ "build:esm": "tsc -p tsconfig.esm.json",
34
+ "check:dist": "node scripts/check-dist.mjs",
35
+ "typecheck": "tsc -p tsconfig.json --noEmit",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest"
38
+ },
39
+ "keywords": [
40
+ "llm",
41
+ "ai",
42
+ "openai",
43
+ "anthropic",
44
+ "claude",
45
+ "gpt",
46
+ "chat-completions",
47
+ "embeddings",
48
+ "gateway",
49
+ "zero-dependency",
50
+ "lightweight",
51
+ "bring-your-own-fetch",
52
+ "stateless"
53
+ ],
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/thinwrap/llm-ts"
57
+ },
58
+ "homepage": "https://thinwrap.dev",
59
+ "bugs": {
60
+ "url": "https://github.com/thinwrap/llm-ts/issues"
61
+ },
62
+ "author": "Dmitry Polyanovsky",
63
+ "engines": {
64
+ "node": ">=18"
65
+ },
66
+ "license": "MIT",
67
+ "devDependencies": {
68
+ "@types/node": "^20.19.41",
69
+ "typescript": "^5.9.3",
70
+ "vite": "^6.4.3",
71
+ "vitest": "^3.2.4"
72
+ }
73
+ }
@@ -0,0 +1,64 @@
1
+ # Anthropic (Claude) — `@thinwrap/llm`
2
+
3
+ Native adapter over the Anthropic **Messages API** (`POST /v1/messages`). Anthropic does not speak OpenAI's Chat Completions shape, so this connector is a full translation layer — it produces the identical normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other connector, so switching to/from `anthropic` is still just the provider id + `model`.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { Chat } from '@thinwrap/llm';
9
+
10
+ const chat = new Chat('anthropic', { apiKey: process.env.ANTHROPIC_API_KEY! });
11
+ const res = await chat.complete({
12
+ model: 'claude-sonnet-4-5',
13
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
14
+ });
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Field | Required | Default | Notes |
20
+ |---|---|---|---|
21
+ | `apiKey` | yes | — | Sent as `x-api-key` (not `Authorization: Bearer`). |
22
+ | `baseUrl` | no | `https://api.anthropic.com/v1` | Override for proxies / gateways. |
23
+ | `anthropicVersion` | no | `2023-06-01` | `anthropic-version` header. |
24
+ | `defaultMaxTokens` | no | `4096` | Anthropic **requires** `max_tokens`; used when `maxOutputTokens` is omitted. |
25
+ | `fetch` | no | `globalThis.fetch` | BYO HTTP client. |
26
+
27
+ ## Auth setup
28
+
29
+ Create an API key in the Anthropic Console. It is sent as the `x-api-key` header along with `anthropic-version`. No environment inference — pass `apiKey` explicitly (or read it from your own env as above).
30
+
31
+ ## Chat
32
+
33
+ ### Endpoint
34
+
35
+ `POST <baseUrl>/messages` (default `https://api.anthropic.com/v1/messages`).
36
+
37
+ ### Notes / quirks
38
+
39
+ How the normalized shape maps:
40
+
41
+ - **System message** → hoisted to the top-level `system` field (multiple system messages are concatenated).
42
+ - **`role: 'tool'` messages** → carried inside a `user` turn as `tool_result` blocks; consecutive tool messages coalesce into one turn (native Anthropic requirement).
43
+ - **Assistant `toolCalls`** → `tool_use` content blocks; **`tools`** → `{ name, description, input_schema }` (JSON Schema in `input_schema`, not nested under `function`).
44
+ - **`toolChoice`** → `auto`→`{type:'auto'}`, `required`→`{type:'any'}`, `none`→`{type:'none'}`, `{function:{name}}`→`{type:'tool', name}`.
45
+ - **Images** → `{ type:'image', source:{ type:'base64', media_type, data } }`.
46
+ - **`stop`** → `stop_sequences`. **`reasoning.effort`** → `thinking:{ type:'enabled', budget_tokens }` (low/medium/high ≈ 1024/4096/12288; sampling params are dropped while thinking is enabled, per Anthropic's constraint).
47
+ - **Usage** → `{ inputTokens, outputTokens, totalTokens }` (Anthropic reports no total; it is summed).
48
+ - **`stop_reason`** → `finishReason` (`end_turn`/`stop_sequence`→`stop`, `max_tokens`→`length`, `tool_use`→`tool_calls`, `refusal`→`content_filter`).
49
+
50
+ Not normalized (passthrough / raw — never emulated):
51
+
52
+ - **`responseFormat`** — Anthropic has no `response_format`; use tool-based structured output via `_passthrough`. This connector does **not** apply `responseFormat`.
53
+ - **Reasoning CoT output** — `thinking` blocks are surfaced only in `ChatResult.raw` (D2: reasoning output is not normalized).
54
+ - **Prompt caching** — pass `cache_control` markers via `_passthrough.body`; cached-token counts appear in `raw`.
55
+
56
+ ## Embeddings
57
+
58
+ Anthropic exposes no OpenAI-float embeddings surface (its embeddings partner is Voyage), so there is no `Embeddings` support — `new Embeddings('anthropic', …)` throws `ConnectorError` (`invalid_request`).
59
+
60
+ ## Error mapping & passthrough
61
+
62
+ - `ConnectorError.providerCode`: `401/403`→`auth_failed`, `429`→`rate_limited`, `500/502/503/529`→`provider_unavailable`, `400/413/422`→`invalid_request` (or `context_length_exceeded` when the message indicates a context overflow). The raw error body is on `cause.raw`; `Retry-After` (when present) on `cause.retryAfter` / `cause.retryAfterSeconds`.
63
+ - Provider-specific request fields go through `_passthrough.body` / `.headers` / `.query`; vendor response extras stay on `ChatResult.raw`.
64
+ - Rate-limit guidance: <https://docs.anthropic.com/en/api/rate-limits>.
@@ -0,0 +1,62 @@
1
+ # Azure OpenAI — `@thinwrap/llm`
2
+
3
+ First-class OpenAI-compatible provider — served by the shared `OpenAICompatConnector` via a row in `src/providers/_shared/spec.ts` (no per-provider connector class). It emits the identical normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other connector, so switching to/from `azure-openai` is just the provider id + `model`.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { Chat } from '@thinwrap/llm';
9
+
10
+ const chat = new Chat('azure-openai', { apiKey: process.env.AZURE_OPENAI_API_KEY!, baseUrl: '<resource URL — see quirks>' });
11
+ const res = await chat.complete({
12
+ model: '<your-deployment-name>',
13
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
14
+ });
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Field | Required | Default | Notes |
20
+ |---|---|---|---|
21
+ | `apiKey` | yes | — | Sent as the `api-key: <apiKey>` header (Microsoft Entra OAuth is also accepted on the v1 GA API). |
22
+ | `baseUrl` | yes | — | Required — no public default (see Chat notes). |
23
+ | `fetch` | no | `globalThis.fetch` | Bring-your-own fetch. |
24
+ | `headers` | no | — | Extra headers merged onto every request. |
25
+
26
+ ## Auth setup
27
+
28
+ Get the key and resource endpoint from the Azure portal. The connector sends it as the `api-key` header; Microsoft Entra OAuth bearer tokens are also accepted on the v1 GA API — supply that token via `headers` instead.
29
+
30
+ ## Chat
31
+
32
+ ### Endpoint
33
+
34
+ `POST https://<resource>.openai.azure.com/openai/v1/chat/completions`
35
+
36
+ ### Notes / quirks
37
+
38
+ - **`baseUrl` is required**: `https://<resource>.openai.azure.com/openai/v1` (v1 GA API, opt-in Aug 2025 — no `api-version` needed).
39
+ - The `model` field is the per-resource **deployment name**, not a portable global model id.
40
+ - Auth is dual-mode: `api-key` header or an Entra bearer token (supply the latter via `headers`).
41
+
42
+ ## Embeddings
43
+
44
+ Supported (served by the shared `OpenAICompatEmbeddingsConnector`).
45
+
46
+ ```ts
47
+ import { Embeddings } from '@thinwrap/llm';
48
+
49
+ const emb = new Embeddings('azure-openai', { apiKey: process.env.AZURE_OPENAI_API_KEY!, baseUrl: '<resource URL — see quirks>' });
50
+ const out = await emb.create({
51
+ model: '<embedding-model>',
52
+ input: ['hello', 'world'],
53
+ });
54
+ ```
55
+
56
+ `POST https://<resource>.openai.azure.com/openai/v1/embeddings` with `encoding_format: 'float'`. `dimensions` maps to `dimensions` when set. Vectors are returned in input order on `out.embeddings`.
57
+
58
+ ## Error mapping & passthrough
59
+
60
+ - Non-2xx responses throw `ConnectorError` with a 7-value `providerCode` (`auth_failed` / `rate_limited` / `provider_unavailable` / `invalid_request` / `context_length_exceeded` / `content_filtered` / `unknown`). The raw vendor body is on `cause.raw`; retry hints (when present) on `cause.retryAfter` / `cause.retryAfterSeconds`.
61
+ - Sub-baseline features — `topK`, `seed`, `frequency`/`presence_penalty`, `logitBias`, `n`, reasoning control beyond `reasoning.effort`, prompt caching — ride through `_passthrough` (request) / `raw` (result). They are never emulated.
62
+ - Rate-limit guidance: <https://learn.microsoft.com/azure/ai-services/openai/quotas-limits>.
@@ -0,0 +1,71 @@
1
+ # AWS Bedrock — `@thinwrap/llm`
2
+
3
+ Native adapter over Amazon Bedrock's unified **Converse API** (`POST /model/{modelId}/converse`). Requests are signed with **hand-rolled AWS SigV4** on `node:crypto` — no `@aws-sdk/*`, no third-party crypto (same zero-dep approach as the notifications SES/SNS connectors). Emits the identical normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other connector.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { Chat } from '@thinwrap/llm';
9
+
10
+ const chat = new Chat('bedrock', {
11
+ region: 'us-east-1',
12
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
13
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
14
+ // sessionToken: process.env.AWS_SESSION_TOKEN, // for temporary creds
15
+ });
16
+ const res = await chat.complete({
17
+ model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
18
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
19
+ });
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ | Field | Required | Default | Notes |
25
+ |---|---|---|---|
26
+ | `region` | yes | — | e.g. `us-east-1`. No environment inference. |
27
+ | `accessKeyId` / `secretAccessKey` | yes | — | SigV4 credentials. |
28
+ | `sessionToken` | no | — | STS temporary-credential token → `X-Amz-Security-Token`. |
29
+ | `baseUrl` | no | `https://bedrock-runtime.<region>.amazonaws.com` | Override origin (VPC endpoint / proxy). |
30
+ | `defaultMaxTokens` | no | `4096` | Converse `inferenceConfig.maxTokens` when `maxOutputTokens` is omitted. |
31
+ | `fetch` | no | `globalThis.fetch` | BYO HTTP client. |
32
+
33
+ ## Auth setup
34
+
35
+ Uses standard AWS SigV4 (service `bedrock`) hand-rolled on `node:crypto` — no AWS SDK. Supply `accessKeyId` / `secretAccessKey` (and `sessionToken` for temporary credentials). The body is serialized once and those exact bytes are signed together with the canonical path so URL, body, and signature all agree.
36
+
37
+ ## Chat
38
+
39
+ ### Endpoint
40
+
41
+ `POST <origin>/model/{modelId}/converse` where `origin` defaults to `https://bedrock-runtime.<region>.amazonaws.com`. The model id is percent-encoded for the path (e.g. `...-v2:0` → `...-v2%3A0`) so the SigV4 canonical URI matches the wire URL exactly.
42
+
43
+ ### Notes / quirks
44
+
45
+ How the normalized shape maps:
46
+
47
+ - **System message** → top-level `system: [{ text }]`.
48
+ - **`role: 'tool'` messages** → `toolResult` blocks inside a `user` turn; consecutive tool messages coalesce into one turn.
49
+ - **Assistant `toolCalls`** → `toolUse` blocks; **`tools`** → `toolConfig.tools[].toolSpec` with the JSON Schema under `inputSchema.json`.
50
+ - **`toolChoice`** → `auto`→`{auto:{}}`, `required`→`{any:{}}`, `{function:{name}}`→`{tool:{name}}` (`none` → `toolConfig` omitted).
51
+ - **Images** → `{ image: { format, source: { bytes } } }` (`format` derived from `mediaType`).
52
+ - **Sampling** → `inferenceConfig` `{ maxTokens, temperature, topP, stopSequences }`.
53
+ - **Usage** → Converse `usage` maps directly to `{ inputTokens, outputTokens, totalTokens }`.
54
+ - **`stopReason`** → `finishReason` (`end_turn`/`stop_sequence`→`stop`, `max_tokens`→`length`, `tool_use`→`tool_calls`, `content_filtered`/`guardrail_intervened`→`content_filter`).
55
+
56
+ v1 limitations (documented, not silent):
57
+
58
+ - **Streaming is non-incremental in v1.** Bedrock's `converse-stream` uses AWS's binary event-stream framing (`application/vnd.amazon.eventstream`), not SSE. v1 does not parse that binary protocol — `stream()` issues one Converse call and yields the full result as deltas. A true incremental binary-event-stream parser is a planned follow-up.
59
+ - **Reasoning is not auto-mapped.** Reasoning control on Bedrock is model-dependent; pass `additionalModelRequestFields` via `_passthrough.body`.
60
+ - **`responseFormat`** is not mapped (use tool-based structured output via `_passthrough`); **prompt caching** via `_passthrough` (`cachePoint` blocks).
61
+ - **SigV4 signature is not yet pinned to an external cross-verified vector** (structural tests only); pinning a known AWS test vector is a follow-up, matching the notifications SES/SNS specs.
62
+
63
+ ## Embeddings
64
+
65
+ Bedrock has no OpenAI-float embeddings surface (Amazon Titan embeddings are not a Converse/OpenAI-float shape), so there is no `Embeddings` support — `new Embeddings('bedrock', …)` throws `ConnectorError` (`invalid_request`).
66
+
67
+ ## Error mapping & passthrough
68
+
69
+ - `ConnectorError.providerCode`: `401/403`→`auth_failed`, `429`→`rate_limited`, `5xx`→`provider_unavailable`, `400/404/422`→`invalid_request` (or `rate_limited` for throttling, `context_length_exceeded` on context overflow). Raw body on `cause.raw`; `Retry-After` (when present) on `cause.retryAfter` / `cause.retryAfterSeconds`.
70
+ - Provider-specific request fields go through `_passthrough.body` / `.headers` / `.query`; vendor response extras stay on `ChatResult.raw`.
71
+ - Rate-limit / quota guidance: <https://docs.aws.amazon.com/bedrock/latest/userguide/quotas.html>.
@@ -0,0 +1,62 @@
1
+ # Cloudflare Workers AI — `@thinwrap/llm`
2
+
3
+ First-class OpenAI-compatible provider — served by the shared `OpenAICompatConnector` via a row in `src/providers/_shared/spec.ts` (no per-provider connector class). It emits the identical normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other connector, so switching to/from `cloudflare` is just the provider id + `model`.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { Chat } from '@thinwrap/llm';
9
+
10
+ const chat = new Chat('cloudflare', { apiKey: process.env.CLOUDFLARE_API_TOKEN!, baseUrl: '<resource URL — see quirks>' });
11
+ const res = await chat.complete({
12
+ model: '@cf/meta/llama-3.1-8b-instruct',
13
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
14
+ });
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Field | Required | Default | Notes |
20
+ |---|---|---|---|
21
+ | `apiKey` | yes | — | Sent as `Authorization: Bearer <apiKey>`. |
22
+ | `baseUrl` | yes | — | Required — no public default (embeds your account id; see Chat notes). |
23
+ | `fetch` | no | `globalThis.fetch` | Bring-your-own fetch. |
24
+ | `headers` | no | — | Extra headers merged onto every request. |
25
+
26
+ ## Auth setup
27
+
28
+ Create an API token (Workers AI scope) in the Cloudflare dashboard. It is sent as `Authorization: Bearer <apiKey>`.
29
+
30
+ ## Chat
31
+
32
+ ### Endpoint
33
+
34
+ `POST https://api.cloudflare.com/client/v4/accounts/<accountId>/ai/v1/chat/completions`
35
+
36
+ ### Notes / quirks
37
+
38
+ - **`baseUrl` is required**: `https://api.cloudflare.com/client/v4/accounts/<accountId>/ai/v1`.
39
+ - Model ids are `@cf/{provider}/{model}` (some `@hf/…`), required verbatim.
40
+ - JSON mode is **not strict** (no schema-adherence guarantee) and can't stream.
41
+
42
+ ## Embeddings
43
+
44
+ Supported (served by the shared `OpenAICompatEmbeddingsConnector`).
45
+
46
+ ```ts
47
+ import { Embeddings } from '@thinwrap/llm';
48
+
49
+ const emb = new Embeddings('cloudflare', { apiKey: process.env.CLOUDFLARE_API_TOKEN!, baseUrl: '<resource URL — see quirks>' });
50
+ const out = await emb.create({
51
+ model: '@cf/baai/bge-large-en-v1.5',
52
+ input: ['hello', 'world'],
53
+ });
54
+ ```
55
+
56
+ `POST https://api.cloudflare.com/client/v4/accounts/<accountId>/ai/v1/embeddings` with `encoding_format: 'float'`. `dimensions` maps to `dimensions` when set. Vectors are returned in input order on `out.embeddings`.
57
+
58
+ ## Error mapping & passthrough
59
+
60
+ - Non-2xx responses throw `ConnectorError` with a 7-value `providerCode` (`auth_failed` / `rate_limited` / `provider_unavailable` / `invalid_request` / `context_length_exceeded` / `content_filtered` / `unknown`). The raw vendor body is on `cause.raw`; retry hints (when present) on `cause.retryAfter` / `cause.retryAfterSeconds`.
61
+ - Sub-baseline features — `topK`, `seed`, `frequency`/`presence_penalty`, `logitBias`, `n`, reasoning control beyond `reasoning.effort`, prompt caching — ride through `_passthrough` (request) / `raw` (result). They are never emulated.
62
+ - Rate-limit guidance: <https://developers.cloudflare.com/workers-ai/platform/limits/>.
@@ -0,0 +1,61 @@
1
+ # DeepInfra — `@thinwrap/llm`
2
+
3
+ First-class OpenAI-compatible provider — served by the shared `OpenAICompatConnector` via a row in `src/providers/_shared/spec.ts` (no per-provider connector class). It emits the identical normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other connector, so switching to/from `deepinfra` is just the provider id + `model`.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { Chat } from '@thinwrap/llm';
9
+
10
+ const chat = new Chat('deepinfra', { apiKey: process.env.DEEPINFRA_API_KEY! });
11
+ const res = await chat.complete({
12
+ model: 'meta-llama/Meta-Llama-3.1-70B-Instruct',
13
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
14
+ });
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Field | Required | Default | Notes |
20
+ |---|---|---|---|
21
+ | `apiKey` | yes | — | Sent as `Authorization: Bearer <apiKey>`. |
22
+ | `baseUrl` | no | `https://api.deepinfra.com/v1/openai` | Override for proxies / gateways. |
23
+ | `fetch` | no | `globalThis.fetch` | Bring-your-own fetch. |
24
+ | `headers` | no | — | Extra headers merged onto every request. |
25
+
26
+ ## Auth setup
27
+
28
+ Create a token at <https://deepinfra.com/dash/api_keys>. It is sent as `Authorization: Bearer <apiKey>`.
29
+
30
+ ## Chat
31
+
32
+ ### Endpoint
33
+
34
+ `POST https://api.deepinfra.com/v1/openai/chat/completions`
35
+
36
+ ### Notes / quirks
37
+
38
+ - Model ids are HuggingFace-style `org/model` slugs (case-sensitive).
39
+ - Runs on a vLLM backend (superset of OpenAI); `usage` adds a non-standard `estimated_cost`; vision `detail` is ignored.
40
+
41
+ ## Embeddings
42
+
43
+ Supported (served by the shared `OpenAICompatEmbeddingsConnector`).
44
+
45
+ ```ts
46
+ import { Embeddings } from '@thinwrap/llm';
47
+
48
+ const emb = new Embeddings('deepinfra', { apiKey: process.env.DEEPINFRA_API_KEY! });
49
+ const out = await emb.create({
50
+ model: 'BAAI/bge-large-en-v1.5',
51
+ input: ['hello', 'world'],
52
+ });
53
+ ```
54
+
55
+ `POST https://api.deepinfra.com/v1/openai/embeddings` with `encoding_format: 'float'`. `dimensions` maps to `dimensions` when set. Vectors are returned in input order on `out.embeddings`.
56
+
57
+ ## Error mapping & passthrough
58
+
59
+ - Non-2xx responses throw `ConnectorError` with a 7-value `providerCode` (`auth_failed` / `rate_limited` / `provider_unavailable` / `invalid_request` / `context_length_exceeded` / `content_filtered` / `unknown`). The raw vendor body is on `cause.raw`; retry hints (when present) on `cause.retryAfter` / `cause.retryAfterSeconds`.
60
+ - Sub-baseline features — `topK`, `seed`, `frequency`/`presence_penalty`, `logitBias`, `n`, reasoning control beyond `reasoning.effort`, prompt caching — ride through `_passthrough` (request) / `raw` (result). They are never emulated.
61
+ - Rate-limit guidance: <https://docs.deepinfra.com/account/rate-limits>.
@@ -0,0 +1,50 @@
1
+ # DeepSeek — `@thinwrap/llm`
2
+
3
+ First-class OpenAI-compatible provider — served by the shared `OpenAICompatConnector` via a row in `src/providers/_shared/spec.ts` (no per-provider connector class). It emits the identical normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other connector, so switching to/from `deepseek` is just the provider id + `model`.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { Chat } from '@thinwrap/llm';
9
+
10
+ const chat = new Chat('deepseek', { apiKey: process.env.DEEPSEEK_API_KEY! });
11
+ const res = await chat.complete({
12
+ model: 'deepseek-chat',
13
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
14
+ });
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Field | Required | Default | Notes |
20
+ |---|---|---|---|
21
+ | `apiKey` | yes | — | Sent as `Authorization: Bearer <apiKey>`. |
22
+ | `baseUrl` | no | `https://api.deepseek.com/v1` | Override for proxies / gateways. |
23
+ | `fetch` | no | `globalThis.fetch` | Bring-your-own fetch. |
24
+ | `headers` | no | — | Extra headers merged onto every request. |
25
+
26
+ ## Auth setup
27
+
28
+ Create a key at <https://platform.deepseek.com/api_keys>. It is sent as `Authorization: Bearer <apiKey>`.
29
+
30
+ ## Chat
31
+
32
+ ### Endpoint
33
+
34
+ `POST https://api.deepseek.com/v1/chat/completions`
35
+
36
+ ### Notes / quirks
37
+
38
+ - The OpenAI-compatible endpoint is first-class (the native API).
39
+ - **No strict `json_schema`** — `response_format` supports only `text`/`json_object`; a `json_schema` request degrades to best-effort JSON.
40
+ - `frequency_penalty` / `presence_penalty` are silently ignored; reasoning is exposed via `reasoning_content` (in `raw`).
41
+
42
+ ## Embeddings
43
+
44
+ DeepSeek exposes no OpenAI-float embeddings surface, so there is no `Embeddings` support — `new Embeddings('deepseek', …)` is a type error and throws `ConnectorError` (`invalid_request`).
45
+
46
+ ## Error mapping & passthrough
47
+
48
+ - Non-2xx responses throw `ConnectorError` with a 7-value `providerCode` (`auth_failed` / `rate_limited` / `provider_unavailable` / `invalid_request` / `context_length_exceeded` / `content_filtered` / `unknown`). The raw vendor body is on `cause.raw`; retry hints (when present) on `cause.retryAfter` / `cause.retryAfterSeconds`.
49
+ - Sub-baseline features — `topK`, `seed`, `frequency`/`presence_penalty`, `logitBias`, `n`, reasoning control beyond `reasoning.effort`, prompt caching — ride through `_passthrough` (request) / `raw` (result). They are never emulated.
50
+ - Rate-limit guidance: <https://api-docs.deepseek.com/quick_start/rate_limit>.
@@ -0,0 +1,62 @@
1
+ # Fireworks AI — `@thinwrap/llm`
2
+
3
+ First-class OpenAI-compatible provider — served by the shared `OpenAICompatConnector` via a row in `src/providers/_shared/spec.ts` (no per-provider connector class). It emits the identical normalized `ChatResult` / `ChatStreamDelta` / `ConnectorError` as every other connector, so switching to/from `fireworks` is just the provider id + `model`.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { Chat } from '@thinwrap/llm';
9
+
10
+ const chat = new Chat('fireworks', { apiKey: process.env.FIREWORKS_API_KEY! });
11
+ const res = await chat.complete({
12
+ model: 'accounts/fireworks/models/llama-v3p3-70b-instruct',
13
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
14
+ });
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ | Field | Required | Default | Notes |
20
+ |---|---|---|---|
21
+ | `apiKey` | yes | — | Sent as `Authorization: Bearer <apiKey>`. |
22
+ | `baseUrl` | no | `https://api.fireworks.ai/inference/v1` | Override for proxies / gateways. |
23
+ | `fetch` | no | `globalThis.fetch` | Bring-your-own fetch. |
24
+ | `headers` | no | — | Extra headers merged onto every request. |
25
+
26
+ ## Auth setup
27
+
28
+ Create a key at <https://fireworks.ai/account/api-keys>. It is sent as `Authorization: Bearer <apiKey>`.
29
+
30
+ ## Chat
31
+
32
+ ### Endpoint
33
+
34
+ `POST https://api.fireworks.ai/inference/v1/chat/completions`
35
+
36
+ ### Notes / quirks
37
+
38
+ - The OpenAI-compatible endpoint **is** the native API (a superset).
39
+ - Model ids are account-namespaced (`accounts/fireworks/models/…`); bare OpenAI names won't resolve.
40
+ - Reasoning chain-of-thought is exposed via `reasoning_content` (in `raw`).
41
+
42
+ ## Embeddings
43
+
44
+ Supported (served by the shared `OpenAICompatEmbeddingsConnector`).
45
+
46
+ ```ts
47
+ import { Embeddings } from '@thinwrap/llm';
48
+
49
+ const emb = new Embeddings('fireworks', { apiKey: process.env.FIREWORKS_API_KEY! });
50
+ const out = await emb.create({
51
+ model: 'nomic-ai/nomic-embed-text-v1.5',
52
+ input: ['hello', 'world'],
53
+ });
54
+ ```
55
+
56
+ `POST https://api.fireworks.ai/inference/v1/embeddings` with `encoding_format: 'float'`. `dimensions` maps to `dimensions` when set. Vectors are returned in input order on `out.embeddings`.
57
+
58
+ ## Error mapping & passthrough
59
+
60
+ - Non-2xx responses throw `ConnectorError` with a 7-value `providerCode` (`auth_failed` / `rate_limited` / `provider_unavailable` / `invalid_request` / `context_length_exceeded` / `content_filtered` / `unknown`). The raw vendor body is on `cause.raw`; retry hints (when present) on `cause.retryAfter` / `cause.retryAfterSeconds`.
61
+ - Sub-baseline features — `topK`, `seed`, `frequency`/`presence_penalty`, `logitBias`, `n`, reasoning control beyond `reasoning.effort`, prompt caching — ride through `_passthrough` (request) / `raw` (result). They are never emulated.
62
+ - Rate-limit guidance: <https://docs.fireworks.ai/guides/quotas_usage/rate-limits>.
@@ -0,0 +1,67 @@
1
+ # Google Gemini (AI Studio) — `@thinwrap/llm`
2
+
3
+ Native adapter for the Gemini `generateContent` API (`generativelanguage.googleapis.com/v1beta`). Gemini uses `contents[]`/`parts[]` (roles `user`/`model`), a top-level `systemInstruction`, and `functionDeclarations`, so this is a full translation layer emitting the identical normalized `ChatResult`/`ChatStreamDelta`/`ConnectorError`. **Streaming is real SSE** (`:streamGenerateContent?alt=sse`) — genuinely incremental, unlike the Bedrock v1 fallback.
4
+
5
+ > This is the **AI Studio** Gemini API (`gemini` provider id). Vertex AI Gemini is a separate provider (`vertex`, a future connector) with different auth (GCP OAuth/ADC) and endpoints.
6
+
7
+ ## Quick start
8
+
9
+ ```ts
10
+ import { Chat } from '@thinwrap/llm';
11
+
12
+ const chat = new Chat('gemini', { apiKey: process.env.GEMINI_API_KEY! });
13
+ const res = await chat.complete({
14
+ model: 'gemini-2.5-flash',
15
+ messages: [{ role: 'user', content: 'Say hi in one word.' }],
16
+ });
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ | Field | Required | Default | Notes |
22
+ |---|---|---|---|
23
+ | `apiKey` | yes | — | Sent as `x-goog-api-key`. |
24
+ | `baseUrl` | no | `https://generativelanguage.googleapis.com/v1beta` | Override for proxies. |
25
+ | `defaultMaxTokens` | no | — | `generationConfig.maxOutputTokens` when `maxOutputTokens` is omitted. |
26
+ | `fetch` | no | `globalThis.fetch` | BYO HTTP client. |
27
+
28
+ ## Auth setup
29
+
30
+ Create an API key in Google AI Studio. It is sent as the `x-goog-api-key` header (the key is not placed in the query string). No environment inference — pass `apiKey` explicitly.
31
+
32
+ ## Chat
33
+
34
+ ### Endpoint
35
+
36
+ `POST <baseUrl>/models/{model}:generateContent` (non-streaming) and `POST <baseUrl>/models/{model}:streamGenerateContent?alt=sse` (streaming). The model is path-escaped. Default base URL `https://generativelanguage.googleapis.com/v1beta`.
37
+
38
+ ### Notes / quirks
39
+
40
+ How the normalized shape maps:
41
+
42
+ - **System message** → top-level `systemInstruction`.
43
+ - **Roles** → `assistant`→`model`, `user`→`user`.
44
+ - **`role: 'tool'` messages** → `functionResponse` parts inside a `user` turn, keyed by function **NAME** (Gemini has no tool-call ids). The name is resolved from the matching prior assistant tool call (`toolCallId`→name), or `message.name`, falling back to `toolCallId`. Consecutive tool messages coalesce into one turn.
45
+ - **Assistant `toolCalls`** → `functionCall` parts; **`tools`** → `[{ functionDeclarations: [...] }]`.
46
+ - **`toolChoice`** → `functionCallingConfig.mode`: `auto`→`AUTO`, `required`→`ANY`, `none`→`NONE`, `{function:{name}}`→`ANY` + `allowedFunctionNames`.
47
+ - **Images** → `{ inlineData: { mimeType, data } }`.
48
+ - **Sampling** → `generationConfig` `{ maxOutputTokens, temperature, topP, stopSequences }`.
49
+ - **`responseFormat`** → **mapped** (unlike Anthropic): `json_object`→`responseMimeType: application/json`; `json_schema`→ that plus `responseSchema` (Gemini honors a JSON-Schema subset).
50
+ - **`reasoning.effort`** → `generationConfig.thinkingConfig.thinkingBudget` (low/medium/high ≈ 1024/8192/24576).
51
+ - **Usage** → `usageMetadata` `{ promptTokenCount, candidatesTokenCount, totalTokenCount }` → `{ inputTokens, outputTokens, totalTokens }`.
52
+ - **`finishReason`** → `STOP`→`stop`, `MAX_TOKENS`→`length`, `SAFETY`/`RECITATION`/etc.→`content_filter`; a `functionCall` part maps to `tool_calls`.
53
+
54
+ Not normalized (passthrough / raw):
55
+
56
+ - **Gemini-3 `thought_signature`** round-trip and reasoning CoT output → `raw` / `_passthrough` (the wrapper does not persist or re-send signatures automatically).
57
+ - **Prompt caching** (`cachedContent`), safety settings, and video params → `_passthrough.body`.
58
+
59
+ ## Embeddings
60
+
61
+ Gemini's native embeddings use a different (non-OpenAI-float) shape, so there is no `Embeddings` support here — `new Embeddings('gemini', …)` throws `ConnectorError` (`invalid_request`). A dedicated embeddings surface is a future connector.
62
+
63
+ ## Error mapping & passthrough
64
+
65
+ - `ConnectorError.providerCode`: `401/403`→`auth_failed`, `429`→`rate_limited`, `5xx`→`provider_unavailable`, `400/404/422`→`invalid_request` (or `context_length_exceeded`). Gemini emits **no rate-limit headers** — retry timing is parsed from the 429 body's `RetryInfo.retryDelay` into `cause.retryAfterSeconds`. Raw body on `cause.raw`.
66
+ - Provider-specific request fields go through `_passthrough.body` / `.headers` / `.query`; vendor response extras stay on `ChatResult.raw`.
67
+ - Rate-limit guidance: <https://ai.google.dev/gemini-api/docs/rate-limits>.