@kenkaiiii/gg-ai 5.75.1 → 5.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @kenkaiiii/gg-ai
2
2
 
3
3
  <p align="center">
4
- <strong>Unified LLM streaming API. Four providers. One interface.</strong>
4
+ <strong>Unified LLM streaming API. Twelve providers plus local models. One interface.</strong>
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -40,10 +40,21 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
40
40
 
41
41
  | Provider | Models | Notes |
42
42
  |---|---|---|
43
- | `anthropic` | Claude Opus 5.5, Sonnet 5.5, Haiku 5.5 | Extended thinking, prompt caching, server-side compaction |
44
- | `openai` | GPT-4.1, o3, o4-mini | Supports OAuth (codex endpoint) and API keys |
45
- | `glm` | GLM-5.3 | Z.AI platform, OpenAI-compatible |
46
- | `moonshot` | Kimi K3, Kimi K2.8 Preview, Kimi K2.7 Code | Moonshot platform, OpenAI-compatible |
43
+ | `anthropic` | Claude Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5 | Extended thinking, prompt caching, server-side compaction |
44
+ | `openai` | GPT-6 Astra, GPT-6.1 Sol, GPT-6 Luna | OAuth (Codex endpoint) or API key |
45
+ | `gemini` | Gemini 3.1 Pro (Preview), 3.8 / 3.7 / 3.5 Flash, 3.5 / 3.1 Flash Lite | OAuth (Code Assist); native video input |
46
+ | `xai` | Grok 4.7 | OpenAI-compatible, `https://api.x.ai/v1` |
47
+ | `moonshot` | Kimi K3, K2.8 Preview, K2.7 Code, K2.7 Code HighSpeed | OpenAI-compatible; native video input |
48
+ | `glm` | GLM-5.3, GLM-5.3-Flash | Z.AI coding endpoint, OpenAI-compatible |
49
+ | `minimax` | MiniMax M3 | Anthropic-compatible endpoint |
50
+ | `xiaomi` | MiMo-V2.6-Pro, MiMo-V2.6-Flash, MiMo-V2.6-Pro-UltraSpeed | OpenAI-compatible |
51
+ | `deepseek` | DeepSeek V4 Pro, V4.1 Flash | OpenAI-compatible |
52
+ | `sakana` | Fugu, Fugu Max, Fugu Ultra | OpenAI-compatible |
53
+ | `openrouter` | Qwen3.8 Max, or any OpenRouter model id | OpenAI-compatible gateway |
54
+ | `huggingface` | Kimi K2.7 Code, DeepSeek V4.1 Flash, GPT-OSS 120B | Inference Providers router, OpenAI-compatible |
55
+ | `local` | Any model your server exposes | Ollama, LM Studio, llama.cpp, vLLM; `baseUrl` is required |
56
+
57
+ The model lists are the ones GG Coder ships in its registry (`@kenkaiiii/gg-core`). `stream()` passes `model` straight through, so any id the provider accepts works. Every provider's default endpoint can be overridden with `baseUrl`, and `providerRegistry.register()` adds your own.
47
58
 
48
59
  ---
49
60
 
@@ -52,11 +63,12 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
52
63
  | Event | Description |
53
64
  |---|---|
54
65
  | `text_delta` | Incremental text output |
55
- | `thinking_delta` | Extended thinking output (Anthropic) |
66
+ | `thinking_delta` | Reasoning output, from any provider that streams it |
56
67
  | `toolcall_delta` | Streaming tool call arguments |
57
68
  | `toolcall_done` | Completed tool call with parsed args |
58
69
  | `server_toolcall` | Server-side tool invocation |
59
70
  | `server_toolresult` | Server-side tool result |
71
+ | `keepalive` | Provider heartbeat: the stream is alive but has no new content yet |
60
72
  | `done` | Stream finished, includes stop reason |
61
73
  | `error` | Error occurred |
62
74
 
@@ -66,7 +78,7 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
66
78
 
67
79
  | Option | Type | Description |
68
80
  |---|---|---|
69
- | `provider` | `"anthropic" \| "openai" \| "glm" \| "moonshot"` | Required |
81
+ | `provider` | `Provider` (any id in the table above) | Required |
70
82
  | `model` | `string` | Required |
71
83
  | `messages` | `Message[]` | Required |
72
84
  | `tools` | `Tool[]` | Tool definitions with Zod schemas |
@@ -76,12 +88,147 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
76
88
  | `temperature` | `number` | Sampling temperature |
77
89
  | `topP` | `number` | Nucleus sampling |
78
90
  | `stop` | `string[]` | Stop sequences |
79
- | `thinking` | `"low" \| "medium" \| "high" \| "max"` | Extended thinking (Anthropic) |
91
+ | `thinking` | `"low" \| "medium" \| "high" \| "xhigh" \| "max" \| "ultra"` | Reasoning effort; each provider maps it to what the model supports |
80
92
  | `apiKey` | `string` | Provider API key |
81
93
  | `baseUrl` | `string` | Custom endpoint |
82
94
  | `signal` | `AbortSignal` | Cancellation |
83
95
  | `cacheRetention` | `"none" \| "short" \| "long"` | Prompt cache preference |
96
+ | `promptCacheKey` | `string` | Stable cache routing key (OpenAI, Moonshot, Gemini) |
97
+ | `webSearch` | `boolean` | Provider-native web search where supported |
84
98
  | `compaction` | `boolean` | Server-side compaction (Anthropic only) |
99
+ | `clearToolUses` | `boolean` | Server-side clearing of old tool results (Anthropic only) |
100
+ | `fetch` | `typeof fetch` | Custom fetch, e.g. for React Native |
101
+ | `providerOptions` | `Record<string, unknown>` | Provider-specific body fields; see below |
102
+
103
+ ### Provider-specific body fields
104
+
105
+ `providerOptions` is merged into the JSON body of `stream()`, `embed()` and `rerank()` requests. It only adds fields gg-ai hasn't set. Core fields (`model`, `messages`, `input`, `query`, `documents`, `stream`, `tools`, `system`, `contents`, `requests`, `dimensions`, `encoding_format`, `top_n`) can't be set this way, even when gg-ai left them out. The main use is OpenRouter's [provider routing](https://openrouter.ai/docs/features/provider-routing):
106
+
107
+ ```ts
108
+ import { stream, type OpenRouterProviderPreferences } from "@kenkaiiii/gg-ai";
109
+
110
+ const result = stream({
111
+ provider: "openrouter",
112
+ model: "anthropic/claude-sonnet-4.5",
113
+ messages,
114
+ apiKey,
115
+ providerOptions: {
116
+ provider: { zdr: true, data_collection: "deny" } satisfies OpenRouterProviderPreferences,
117
+ },
118
+ });
119
+ ```
120
+
121
+ `@kenkaiiii/gg-agent` passes `providerOptions` and `fetch` through to every `stream()` call it makes.
122
+
123
+ ---
124
+
125
+ ## Embeddings and reranking
126
+
127
+ `embed()` and `rerank()` use the same provider registry as `stream()`.
128
+
129
+ ```ts
130
+ import { embed, rerank } from "@kenkaiiii/gg-ai";
131
+
132
+ const { embeddings, usage } = await embed({
133
+ provider: "openrouter",
134
+ model: "google/gemini-embedding-2",
135
+ input: chunks, // any length; split into batches for you
136
+ dimensions: 1536,
137
+ normalize: true,
138
+ apiKey,
139
+ providerOptions: { provider: { zdr: true, data_collection: "deny", allow_fallbacks: true } },
140
+ });
141
+ // embeddings[i] is the vector for chunks[i]
142
+
143
+ const { results } = await rerank({
144
+ provider: "openrouter",
145
+ model: "cohere/rerank-v3.5",
146
+ query: "capital of France",
147
+ documents,
148
+ topN: 5,
149
+ apiKey,
150
+ });
151
+ // results: [{ index, score }], best first; documents[results[0].index] is the top hit
152
+ ```
153
+
154
+ **What `embed()` guarantees**
155
+ - Vectors come back in input order, even when the provider returns them out of order (gg-ai sorts by `index`).
156
+ - Long inputs are split at the provider's per-request limit (table below; override with `batchSize`). Batches run one at a time, in order.
157
+ - The response is checked: exactly one vector per input, all numbers, all the same length, and `dimensions` long when you asked for that. If any check fails it throws `ProviderError`. Nothing is padded or truncated.
158
+ - `normalize: true` L2-normalizes every vector. It's off by default. OpenAI embeddings come back unit-length already, and so does Gemini at full 3072 dimensions. `gemini-embedding-2` also normalizes shortened vectors, but `gemini-embedding-001` doesn't: at reduced `dimensions` its vectors are not unit-length, so pass `normalize: true`. Through OpenRouter or a local server, it depends on the upstream model. If you compare with cosine similarity or a dot product and aren't sure, normalize.
159
+ - `inputType: "query" | "document"` maps to the provider's native task parameter: Gemini `taskType` (`RETRIEVAL_QUERY` / `RETRIEVAL_DOCUMENT`) and OpenRouter `input_type` (`search_query` / `search_document`). It's ignored by OpenAI and local servers, which have no such parameter. It's also ignored for `gemini-embedding-2`, where Google wants the task written into the text instead (`task: search result | query: …`).
160
+ - An empty `input` returns `{ embeddings: [] }` without making a request.
161
+
162
+ **What `rerank()` guarantees**: results are sorted by score, highest first, and each `index` points into your `documents`. At most `topN` results come back, and every index is checked. An empty `documents` returns `{ results: [] }` without making a request.
163
+
164
+ ### Provider support
165
+
166
+ | Provider | `embed` | Max inputs per request | `inputType` | `rerank` |
167
+ |---|---|---|---|---|
168
+ | `openai` | ✓ `/v1/embeddings` | 2048 | ignored | — |
169
+ | `openrouter` | ✓ `/api/v1/embeddings` | 64 (gg-ai default; OpenRouter documents no limit) | `input_type` | ✓ `/api/v1/rerank` |
170
+ | `gemini` | ✓ native `batchEmbedContents` | 100 | `taskType` | — |
171
+ | `local` | ✓ `{baseUrl}/embeddings` (Ollama, LM Studio, llama.cpp, vLLM) | 64 (gg-ai default) | ignored | ✓ `{baseUrl}/rerank` (llama.cpp `--rerank`, vLLM; Ollama and LM Studio have none and return 404) |
172
+ | `palsu` | ✓ deterministic fake | all | ignored | ✓ deterministic fake |
173
+ | `anthropic`, `xiaomi`, `glm`, `moonshot`, `minimax`, `deepseek`, `sakana`, `xai`, `huggingface` | — | | | — |
174
+
175
+ Calling `embed()` or `rerank()` on a provider without that capability throws `GGAIError` with `source: "capability"`. Check first with `providerRegistry.supports(name, "embed")`.
176
+
177
+ Notes:
178
+ - **Gemini** embeddings use the Gemini API with an AI Studio key, sent as `x-goog-api-key`. The Code Assist sign-in that `stream()` uses for Gemini chat has no embeddings endpoint.
179
+ - **OpenAI** embeddings need a platform API key. ChatGPT-subscription (Codex) tokens can't call `/embeddings`.
180
+ - Anthropic has no embeddings API (it points to Voyage AI). The other providers in the last row don't document an embeddings or rerank endpoint on the API gg-ai talks to, so none is registered for them.
181
+ - Document parsing and OCR are out of scope for now. That would be a separate API (file in, pages out), and providers disagree on it much more than on embeddings.
182
+
183
+ ### Errors, retries and cancellation
184
+
185
+ Failures use the same error model as `stream()`:
186
+
187
+ | Failure | Error |
188
+ |---|---|
189
+ | HTTP 401 / 403 | `ProviderError`, `source: "auth"` |
190
+ | HTTP 402, or billing text such as `insufficient_quota` | `ProviderError` with "usage limit reached" (`isUsageLimitError()` is true) |
191
+ | HTTP 429 | `ProviderError`, `statusCode: 429`, `resetsAt` from `Retry-After` when sent |
192
+ | Other HTTP errors | `ProviderError` with `statusCode` and `requestId` |
193
+ | Connection failure | `GGAIError`, `source: "network"` |
194
+ | `signal` aborted | `AbortError` (`DOMException`); remaining batches are skipped |
195
+
196
+ Provider error bodies are trimmed to their message, then redacted and truncated, so API keys never end up in an error message.
197
+
198
+ gg-ai doesn't retry. `stream()` doesn't either: retry policy lives in the caller, which for chat is `@kenkaiiii/gg-agent`. Retry 429s and 5xx with backoff if you need to. Never retry auth errors or anything where `isUsageLimitError(err)` is true.
199
+
200
+ ### Custom providers
201
+
202
+ A provider registered at runtime can offer embeddings and reranking too. `stream` is still required, and the other two are optional. gg-ai splits input into batches of `embedBatchSize`, validates and orders the results, normalizes, and sorts. Your functions only handle one request.
203
+
204
+ ```ts
205
+ import { providerRegistry, embed, type Provider, type ProviderEntry } from "@kenkaiiii/gg-ai";
206
+
207
+ providerRegistry.register("my-search", {
208
+ stream: () => { throw new Error("chat not supported"); },
209
+ embedBatchSize: 128,
210
+ embed: async ({ model, input, dimensions, apiKey, signal, fetch = globalThis.fetch }) => {
211
+ const res = await fetch("https://example.com/embed", {
212
+ method: "POST",
213
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
214
+ body: JSON.stringify({ model, texts: input, dimensions }),
215
+ signal,
216
+ });
217
+ const json = await res.json();
218
+ return { embeddings: json.vectors, usage: { inputTokens: json.tokens } };
219
+ },
220
+ rerank: async ({ query, documents }) => ({
221
+ results: documents.map((doc, index) => ({ index, score: doc.includes(query) ? 1 : 0 })),
222
+ }),
223
+ } satisfies ProviderEntry);
224
+
225
+ // `Provider` lists the built-ins, so custom names need a cast (same as for stream()).
226
+ await embed({ provider: "my-search" as Provider, model: "v1", input: ["hello"] });
227
+ ```
228
+
229
+ ### Testing without a network
230
+
231
+ `registerPalsuProvider()` gives `palsu` deterministic `embed` and `rerank` fakes. Embeddings are hashed bag-of-words unit vectors (64 dimensions by default, or `dimensions`), so texts that share words score closer together. Rerank scores are the fraction of query words found in each document. `embedDimensions` and `embedBatchSize` in the config change the defaults, and `state.embedCallCount` / `state.rerankCallCount` count requests.
85
232
 
86
233
  ---
87
234