@jxburros/ai-nugget 0.3.1 → 0.6.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 +134 -271
- package/UPGRADING.md +158 -0
- package/dist/adapters/engines/anthropic.d.ts +7 -1
- package/dist/adapters/engines/anthropic.js +64 -37
- package/dist/adapters/engines/base.d.ts +29 -2
- package/dist/adapters/engines/base.js +52 -8
- package/dist/adapters/engines/google.d.ts +17 -1
- package/dist/adapters/engines/google.js +93 -29
- package/dist/adapters/engines/ollama.d.ts +2 -1
- package/dist/adapters/engines/ollama.js +51 -15
- package/dist/adapters/engines/openaiChat.d.ts +2 -1
- package/dist/adapters/engines/openaiChat.js +60 -30
- package/dist/adapters/profiles.d.ts +21 -1
- package/dist/adapters/profiles.js +12 -2
- package/dist/agent/loop.d.ts +43 -1
- package/dist/agent/loop.js +120 -13
- package/dist/cjs/adapters/engines/anthropic.js +308 -0
- package/dist/cjs/adapters/engines/base.js +147 -0
- package/dist/cjs/adapters/engines/google.js +284 -0
- package/dist/cjs/adapters/engines/ollama.js +199 -0
- package/dist/cjs/adapters/engines/openaiChat.js +261 -0
- package/dist/cjs/adapters/index.js +22 -0
- package/dist/cjs/adapters/profiles.js +125 -0
- package/dist/cjs/agent/index.js +18 -0
- package/dist/cjs/agent/loop.js +372 -0
- package/dist/cjs/agent/tools.js +51 -0
- package/dist/cjs/connect.js +27 -0
- package/dist/cjs/errors.js +110 -0
- package/dist/cjs/handler.js +561 -0
- package/dist/cjs/index.js +27 -0
- package/dist/cjs/json.js +121 -0
- package/dist/cjs/keys.js +81 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/policy.js +42 -0
- package/dist/cjs/redact.js +78 -0
- package/dist/cjs/tokens.js +22 -0
- package/dist/cjs/transport.js +172 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/util.js +112 -0
- package/dist/connect.d.ts +31 -0
- package/dist/connect.js +25 -0
- package/dist/errors.js +25 -1
- package/dist/handler.d.ts +61 -1
- package/dist/handler.js +247 -61
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/keys.js +1 -4
- package/dist/redact.d.ts +18 -0
- package/dist/redact.js +29 -1
- package/dist/transport.d.ts +3 -1
- package/dist/transport.js +21 -1
- package/dist/types.d.ts +96 -2
- package/dist/util.d.ts +16 -0
- package/dist/util.js +50 -0
- package/nugget/VERSION.txt +2 -2
- package/nugget/dist/adapters/engines/anthropic.d.ts +14 -0
- package/nugget/dist/adapters/engines/anthropic.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/anthropic.js +305 -0
- package/nugget/dist/adapters/engines/anthropic.js.map +1 -0
- package/nugget/dist/adapters/engines/base.d.ts +50 -0
- package/nugget/dist/adapters/engines/base.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/base.js +137 -0
- package/nugget/dist/adapters/engines/base.js.map +1 -0
- package/nugget/dist/adapters/engines/google.d.ts +24 -0
- package/nugget/dist/adapters/engines/google.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/google.js +281 -0
- package/nugget/dist/adapters/engines/google.js.map +1 -0
- package/nugget/dist/adapters/engines/ollama.d.ts +11 -0
- package/nugget/dist/adapters/engines/ollama.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/ollama.js +196 -0
- package/nugget/dist/adapters/engines/ollama.js.map +1 -0
- package/nugget/dist/adapters/engines/openaiChat.d.ts +16 -0
- package/nugget/dist/adapters/engines/openaiChat.d.ts.map +1 -0
- package/nugget/dist/adapters/engines/openaiChat.js +258 -0
- package/nugget/dist/adapters/engines/openaiChat.js.map +1 -0
- package/nugget/dist/adapters/index.d.ts +5 -0
- package/nugget/dist/adapters/index.d.ts.map +1 -0
- package/nugget/dist/adapters/index.js +17 -0
- package/nugget/dist/adapters/index.js.map +1 -0
- package/nugget/dist/adapters/profiles.d.ts +62 -0
- package/nugget/dist/adapters/profiles.d.ts.map +1 -0
- package/nugget/dist/adapters/profiles.js +121 -0
- package/nugget/dist/adapters/profiles.js.map +1 -0
- package/nugget/dist/agent/index.d.ts +3 -0
- package/nugget/dist/agent/index.d.ts.map +1 -0
- package/nugget/dist/agent/index.js +3 -0
- package/nugget/dist/agent/index.js.map +1 -0
- package/nugget/dist/agent/loop.d.ts +112 -0
- package/nugget/dist/agent/loop.d.ts.map +1 -0
- package/nugget/dist/agent/loop.js +370 -0
- package/nugget/dist/agent/loop.js.map +1 -0
- package/nugget/dist/agent/tools.d.ts +29 -0
- package/nugget/dist/agent/tools.d.ts.map +1 -0
- package/nugget/dist/agent/tools.js +48 -0
- package/nugget/dist/agent/tools.js.map +1 -0
- package/nugget/dist/cjs/adapters/engines/anthropic.js +308 -0
- package/nugget/dist/cjs/adapters/engines/base.js +147 -0
- package/nugget/dist/cjs/adapters/engines/google.js +284 -0
- package/nugget/dist/cjs/adapters/engines/ollama.js +199 -0
- package/nugget/dist/cjs/adapters/engines/openaiChat.js +261 -0
- package/nugget/dist/cjs/adapters/index.js +22 -0
- package/nugget/dist/cjs/adapters/profiles.js +125 -0
- package/nugget/dist/cjs/agent/index.js +18 -0
- package/nugget/dist/cjs/agent/loop.js +372 -0
- package/nugget/dist/cjs/agent/tools.js +51 -0
- package/nugget/dist/cjs/connect.js +27 -0
- package/nugget/dist/cjs/errors.js +110 -0
- package/nugget/dist/cjs/handler.js +561 -0
- package/nugget/dist/cjs/index.js +27 -0
- package/nugget/dist/cjs/json.js +121 -0
- package/nugget/dist/cjs/keys.js +81 -0
- package/nugget/dist/cjs/package.json +3 -0
- package/nugget/dist/cjs/policy.js +42 -0
- package/nugget/dist/cjs/redact.js +78 -0
- package/nugget/dist/cjs/tokens.js +22 -0
- package/nugget/dist/cjs/transport.js +172 -0
- package/nugget/dist/cjs/types.js +6 -0
- package/nugget/dist/cjs/util.js +112 -0
- package/nugget/dist/connect.d.ts +31 -0
- package/nugget/dist/connect.d.ts.map +1 -0
- package/nugget/dist/connect.js +25 -0
- package/nugget/dist/connect.js.map +1 -0
- package/nugget/dist/errors.d.ts +22 -0
- package/nugget/dist/errors.d.ts.map +1 -0
- package/nugget/dist/errors.js +104 -0
- package/nugget/dist/errors.js.map +1 -0
- package/nugget/dist/handler.d.ts +108 -0
- package/nugget/dist/handler.d.ts.map +1 -0
- package/nugget/dist/handler.js +557 -0
- package/nugget/dist/handler.js.map +1 -0
- package/nugget/dist/index.d.ts +12 -0
- package/nugget/dist/index.d.ts.map +1 -0
- package/nugget/dist/index.js +12 -0
- package/nugget/dist/index.js.map +1 -0
- package/nugget/dist/json.d.ts +7 -0
- package/nugget/dist/json.d.ts.map +1 -0
- package/nugget/dist/json.js +114 -0
- package/nugget/dist/json.js.map +1 -0
- package/nugget/dist/keys.d.ts +7 -0
- package/nugget/dist/keys.d.ts.map +1 -0
- package/nugget/dist/keys.js +75 -0
- package/nugget/dist/keys.js.map +1 -0
- package/nugget/dist/policy.d.ts +6 -0
- package/nugget/dist/policy.d.ts.map +1 -0
- package/nugget/dist/policy.js +37 -0
- package/nugget/dist/policy.js.map +1 -0
- package/nugget/dist/redact.d.ts +26 -0
- package/nugget/dist/redact.d.ts.map +1 -0
- package/nugget/dist/redact.js +74 -0
- package/nugget/dist/redact.js.map +1 -0
- package/nugget/dist/tokens.d.ts +5 -0
- package/nugget/dist/tokens.d.ts.map +1 -0
- package/nugget/dist/tokens.js +18 -0
- package/nugget/dist/tokens.js.map +1 -0
- package/nugget/dist/transport.d.ts +30 -0
- package/nugget/dist/transport.d.ts.map +1 -0
- package/nugget/dist/transport.js +165 -0
- package/nugget/dist/transport.js.map +1 -0
- package/nugget/dist/types.d.ts +285 -0
- package/nugget/dist/types.d.ts.map +1 -0
- package/nugget/dist/types.js +4 -0
- package/nugget/dist/types.js.map +1 -0
- package/nugget/dist/util.d.ts +24 -0
- package/nugget/dist/util.d.ts.map +1 -0
- package/nugget/dist/util.js +101 -0
- package/nugget/dist/util.js.map +1 -0
- package/nugget/src/adapters/engines/anthropic.ts +62 -37
- package/nugget/src/adapters/engines/base.ts +50 -7
- package/nugget/src/adapters/engines/google.ts +92 -29
- package/nugget/src/adapters/engines/ollama.ts +51 -17
- package/nugget/src/adapters/engines/openaiChat.ts +61 -32
- package/nugget/src/adapters/profiles.ts +53 -3
- package/nugget/src/agent/loop.ts +158 -14
- package/nugget/src/connect.ts +48 -0
- package/nugget/src/errors.ts +25 -1
- package/nugget/src/handler.ts +286 -59
- package/nugget/src/index.ts +1 -0
- package/nugget/src/keys.ts +1 -5
- package/nugget/src/redact.ts +32 -1
- package/nugget/src/transport.ts +20 -1
- package/nugget/src/types.ts +96 -1
- package/nugget/src/util.ts +55 -0
- package/package.json +20 -6
package/README.md
CHANGED
|
@@ -1,316 +1,179 @@
|
|
|
1
1
|
# AI Nugget
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
**Version 0.6.0** · [What's changed since 0.5.0](#whats-changed-since-050) · [Changelog](./CHANGELOG.md) · [Upgrading](./UPGRADING.md)
|
|
4
|
+
|
|
5
|
+
A small, zero-dependency, isomorphic TypeScript nugget for talking to AI model
|
|
6
|
+
providers through one pipeline:
|
|
5
7
|
|
|
6
8
|
```
|
|
7
9
|
policy → key resolution → beforeCall hook → concurrency/retry → provider adapter → redacted telemetry
|
|
8
10
|
```
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
only `fetch`, `ReadableStream`,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
19 provider profiles over 4 protocol engines. Runs identically in Node, the
|
|
13
|
+
browser main thread, and Web Workers using only `fetch`, `ReadableStream`,
|
|
14
|
+
`AbortController`, and `TextDecoder`. MIT licensed.
|
|
15
|
+
|
|
16
|
+
It is intentionally **not** a router, prompt library, memory system, or secrets
|
|
17
|
+
vault: apps choose models, own prompts, store secrets, and decide policy.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
build/adoption plan, and dev handoff are archived under `docs/archive/`.
|
|
21
|
+
Requires Node `^20.19.0` or `>=22.12.0` ([why](./docs/distribution.md#requirements)).
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
```bash
|
|
24
|
+
npm install @jxburros/ai-nugget
|
|
25
|
+
```
|
|
20
26
|
|
|
21
27
|
```ts
|
|
22
28
|
import { AIHandler, envKeySource } from '@jxburros/ai-nugget';
|
|
23
29
|
|
|
24
30
|
const handler = new AIHandler({ keySource: envKeySource() });
|
|
25
31
|
|
|
32
|
+
const conn = { id: 'main', provider: 'openai', keyRef: { kind: 'env', name: 'OPENAI_API_KEY' } };
|
|
33
|
+
const req = { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Hello!' }] };
|
|
34
|
+
|
|
26
35
|
// Non-streaming
|
|
27
|
-
const result = await handler.chat(
|
|
28
|
-
{ id: 'main', provider: 'openai', keyRef: { kind: 'env', name: 'OPENAI_API_KEY' } },
|
|
29
|
-
{ model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Hello!' }] },
|
|
30
|
-
);
|
|
36
|
+
const result = await handler.chat(conn, req);
|
|
31
37
|
console.log(result.text, result.usage, result.finishReason);
|
|
32
38
|
|
|
33
|
-
// Streaming (
|
|
39
|
+
// Streaming (Node route handlers, React, Web Workers — same code)
|
|
34
40
|
for await (const event of handler.stream(conn, req)) {
|
|
35
41
|
if (event.type === 'delta') process.stdout.write(event.text);
|
|
36
42
|
if (event.type === 'done') console.log('\n', event.result.usage);
|
|
43
|
+
if (event.type === 'error') console.error(event.error.kind, event.error.message);
|
|
37
44
|
}
|
|
38
45
|
```
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
folder (see below).
|
|
47
|
+
No key? Point at a local model instead — same code, no `keyRef`:
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
turns each named provider into defaults + quirks on top of an engine, so adding
|
|
47
|
-
an OpenAI-compatible provider is a table row, not another copy of the SSE loop.
|
|
49
|
+
```ts
|
|
50
|
+
const conn = { id: 'local', provider: 'ollama', baseUrl: 'http://127.0.0.1:11434' };
|
|
51
|
+
```
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|---|---|---|---|---|
|
|
51
|
-
| `openaiChat` | `openai`, `azure-openai`, `openrouter`, `groq`, `deepseek`, `mistral`, `together`, `fireworks`, `lmstudio`, `llamacpp`, `vllm`, `openai-compat` | SSE + `stream_options.include_usage` | `tools`/`tool_calls` deltas | `response_format` (`json_schema` where supported, otherwise `json_object`) |
|
|
52
|
-
| `anthropic` | `anthropic` | SSE (`content_block_delta`) | `tool_use` blocks (streamed) | forced-tool JSON mode |
|
|
53
|
-
| `google` | `google` | SSE (`streamGenerateContent`) | `functionDeclarations` | `responseMimeType` |
|
|
54
|
-
| `ollama` | `ollama` | NDJSON | native `tools` | `format: json` |
|
|
53
|
+
CommonJS works too: `const { AIHandler } = require('@jxburros/ai-nugget')`.
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
endpoint if you must; that is your configuration, not a supported profile.
|
|
55
|
+
For a running server, copy a starter from
|
|
56
|
+
[`examples/integrations/`](./examples/integrations/) — Express, Next.js,
|
|
57
|
+
Cloudflare Workers, or local Ollama.
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
canonical `provider/model` key so the same weights served by different hosts stay
|
|
63
|
-
distinct and comparable.
|
|
59
|
+
## Two things to configure before production
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
Both are seams the library leaves open on purpose, and both are easy to miss:
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
1. **Never take `provider` or `baseUrl` from client input.** They decide where
|
|
64
|
+
your resolved API key gets sent. Keep connections on a server-side allowlist
|
|
65
|
+
and let clients pick an id. → [security.md](./docs/security.md#ssrf-caller-controlled-baseurl)
|
|
66
|
+
2. **Configure a `GovernancePolicy`.** With none, every provider and model is
|
|
67
|
+
allowed; the handler logs a startup notice so this is visible at runtime.
|
|
68
|
+
→ [security.md](./docs/security.md#governance-is-neutral-by-default)
|
|
70
69
|
|
|
71
70
|
```ts
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
local: boolean; // runs on localhost / user-controlled infra
|
|
77
|
-
embeddable: boolean; // meant to run as a local sidecar, not a hosted service
|
|
78
|
-
}
|
|
71
|
+
new AIHandler({
|
|
72
|
+
keySource: envKeySource(),
|
|
73
|
+
policy: allowlistPolicy({ openai: ['gpt-4o', 'gpt-4o-mini'], ollama: ['*'] }),
|
|
74
|
+
});
|
|
79
75
|
```
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
`
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
`
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
→ jittered retry (honoring `Retry-After`) → redacted `CallRecord` telemetry →
|
|
160
|
-
`afterCall`, with `chat`, `stream`, `listModels`, `testConnection`. Every call —
|
|
161
|
-
success, failure, or consumer-abandoned stream — produces one redacted telemetry
|
|
162
|
-
record. Retries stop once user-visible output has been emitted, and telemetry
|
|
163
|
-
or `afterCall` failures never re-execute an already-successful provider call.
|
|
164
|
-
`listModels()` and `testConnection()` use explicit operation IDs
|
|
165
|
-
(`__listModels__`, `__testConnection__`) so policy and telemetry can see those
|
|
166
|
-
key-bearing probes too.
|
|
167
|
-
- **Seams:** `KeySource` (`env`/`literal`/`memory`/chain + ref parsing),
|
|
168
|
-
`Redactor` (default secret patterns + session-resolved key redaction),
|
|
169
|
-
neutral `GovernancePolicy` (`blocklistPolicy`/`allowlistPolicy`/`composePolicies`).
|
|
170
|
-
`allowlistPolicy` fails closed for providers omitted from the configured map;
|
|
171
|
-
use `'*'` in a provider's prefix list to allow non-chat operation IDs.
|
|
172
|
-
The default redactor covers common provider token formats and generic bearer
|
|
173
|
-
tokens, with session-resolved keys always added exactly.
|
|
174
|
-
- **Agent layer (`@jxburros/ai-nugget/agent`):** `defineTool` + light JSON-schema
|
|
175
|
-
arg validation (object-ness, `required`, top-level `properties[key].type` —
|
|
176
|
-
not full JSON Schema: no `enum`, nested schemas, `oneOf`, bounds, `pattern`,
|
|
177
|
-
array `items`, or `additionalProperties`; validate again inside
|
|
178
|
-
`tool.execute` if you need stricter guarantees), `runAgent()` model↔tool loop
|
|
179
|
-
over the full handler pipeline,
|
|
180
|
-
streamed `AgentEvent`s, budgets (`maxSteps`/`maxTokens`/`deadlineMs`) with honest
|
|
181
|
-
`stopReason`s, `ApprovalGate` for side-effecting tools (deny is fed back to the
|
|
182
|
-
model as data), and `native` / `promptJson` / `auto` tool modes. `auto` (the
|
|
183
|
-
default) resolves per call from the connection's provider capability profile
|
|
184
|
-
(`profileFor(provider).capabilities.nativeTools`) — hosted providers get
|
|
185
|
-
`native`, local runtimes and the `openai-compat` escape hatch get
|
|
186
|
-
`promptJson`, and an explicit `toolMode` always overrides the default.
|
|
187
|
-
`promptJson` mode accepts a single `{"tool","input"}` directive or a batched
|
|
188
|
-
`{"tools":[…]}` (a bare array works too), so the model can request several
|
|
189
|
-
tools in one turn just like native tool-calling. Prompt-JSON history is
|
|
190
|
-
serialized as plain text turns, not provider-native tool-call wire format.
|
|
191
|
-
|
|
192
|
-
```ts
|
|
193
|
-
import { runAgent, defineTool } from '@jxburros/ai-nugget/agent';
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Commands
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
| Doc | Covers |
|
|
80
|
+
|---|---|
|
|
81
|
+
| [providers.md](./docs/providers.md) | The 19 profiles, which are **live-verified vs mock-verified vs config-only**, capabilities, JSON-mode/tools limits, `openai-compat`, model discovery |
|
|
82
|
+
| [reliability.md](./docs/reliability.md) | `timeoutMs` vs `idleTimeoutMs`, why retries stop after output is emitted, double-billing and idempotency, why limits are per-instance |
|
|
83
|
+
| [security.md](./docs/security.md) | Key handling, redaction coverage, SSRF, opt-in injection defenses, the secure-tool recipe |
|
|
84
|
+
| [agent-loop.md](./docs/agent-loop.md) | `runAgent`, tool modes, budgets and what happens mid-step, the approval gate |
|
|
85
|
+
| [recipes.md](./docs/recipes.md) | Env-based connections, JSON output + validation, the error-handling matrix, telemetry |
|
|
86
|
+
| [integrations.md](./docs/integrations.md) | The host matrix CI runs (Node, Next.js, Workers, local runtime, packaging) |
|
|
87
|
+
| [distribution.md](./docs/distribution.md) | Commands, live smoke tests, CI workflows, npm/GitHub Packages/vendored `nugget/`, bundler notes |
|
|
88
|
+
| [design.md](./design.md) | The full contract |
|
|
89
|
+
| [state-of-the-nugget.md](./docs/state-of-the-nugget.md) | Current health snapshot: architecture, safety and reliability posture, coverage gaps, what's next |
|
|
90
|
+
| [UPGRADING.md](./UPGRADING.md) | Version-by-version changes |
|
|
91
|
+
|
|
92
|
+
## What's in the box
|
|
93
|
+
|
|
94
|
+
- **Core** — message-based contracts (`types.ts`), typed `AIError` + `classify()`
|
|
95
|
+
with wire-boundary redaction, timeout/idle-timeout/abort merging and SSE/NDJSON
|
|
96
|
+
readers (`transport.ts`), defensive JSON extraction with schema guards
|
|
97
|
+
(`json.ts`), token estimation with an explicit `estimated` flag.
|
|
98
|
+
- **Adapters** — four engines (`openaiChat`, `anthropic`, `google`, `ollama`)
|
|
99
|
+
with streaming, native tool-calling, JSON modes, `finishReason` mapping,
|
|
100
|
+
buffered-`stream:true` fallback, reasoning-token channel, first-token latency,
|
|
101
|
+
usage normalization, and truncation detection (`stream_anomaly`). Provider
|
|
102
|
+
differences live in a data-driven profile table, not in copies of the SSE loop.
|
|
103
|
+
- **Pipeline (`AIHandler`)** — `chat`, `stream`, `chatParsed`, `embed`,
|
|
104
|
+
`listModels`, `testConnection`, `prewarm`. Every call — success, failure, or
|
|
105
|
+
abandoned stream — produces exactly one redacted `CallRecord`. Retries are
|
|
106
|
+
jittered and honor `Retry-After`; telemetry or `afterCall` failures never
|
|
107
|
+
re-run a completed provider call.
|
|
108
|
+
- **Seams** — `KeySource` (env/literal/memory/chain + ref parsing), `Redactor`,
|
|
109
|
+
neutral `GovernancePolicy` (`blocklistPolicy`/`allowlistPolicy`/`composePolicies`),
|
|
110
|
+
`TelemetrySink`, and an optional `pricing` hook. `providerOptions` passes
|
|
111
|
+
provider-native fields through without waiting for a library release.
|
|
112
|
+
- **Agent layer** (`@jxburros/ai-nugget/agent`) — `defineTool`, `runAgent()`
|
|
113
|
+
model↔tool loop over the full pipeline, streamed `AgentEvent`s, budgets with
|
|
114
|
+
honest `stopReason`s, an `ApprovalGate` for side-effecting tools, and
|
|
115
|
+
`native`/`promptJson`/`auto` tool modes.
|
|
116
|
+
|
|
117
|
+
## What's changed since 0.5.0
|
|
118
|
+
|
|
119
|
+
**0.6.0 is backward-compatible** — every change below is additive. The one thing
|
|
120
|
+
to check is the new `not_found` error kind if you `switch` exhaustively over
|
|
121
|
+
`AIErrorKind`; see [UPGRADING.md](./UPGRADING.md#05x--060). Full detail lives in
|
|
122
|
+
the [changelog](./CHANGELOG.md).
|
|
123
|
+
|
|
124
|
+
- **Security & correctness** — `beforeCall` hooks now receive a scrubbed
|
|
125
|
+
connection (masked literal `keyRef` and auth headers, safe to log);
|
|
126
|
+
`classify()` redacts provider response excerpts at the wire boundary so an
|
|
127
|
+
`AIError` can never carry an unredacted secret; label-anchored redaction
|
|
128
|
+
covers unprefixed secrets (Azure `api-key`, AWS keys, `client_secret`, session
|
|
129
|
+
tokens); a new `not_found` error kind distinguishes 404/410 from a malformed
|
|
130
|
+
request; and Ollama tool-call arguments coerce through the shared parser so a
|
|
131
|
+
llama.cpp-style backend sending JSON-as-string no longer fails.
|
|
132
|
+
- **Observability** — `stream_anomaly` detection now fires on all four engines
|
|
133
|
+
(not just `openaiChat`); Google and Anthropic emit `json_mode_downgraded` when
|
|
134
|
+
a JSON mode is dropped for tools; `AIHandler` logs a startup notice when no
|
|
135
|
+
`GovernancePolicy` is configured; and OpenAI requests carry a stable
|
|
136
|
+
`Idempotency-Key` across retries to mitigate double-billing.
|
|
137
|
+
- **Providers & types** — `openai-compat` is now `keyOptional`, matching the
|
|
138
|
+
other local-runtime profiles, so keyless servers (including JX Runtime) work
|
|
139
|
+
without a `keyRef`; `Connection.provider` is typed `KnownProvider | (string &
|
|
140
|
+
{})` for autocomplete and typo-catching with the escape hatch still open.
|
|
141
|
+
- **Docs & examples** — `README.md` was trimmed to a front door with reference
|
|
142
|
+
material moved under [`docs/`](./docs/) (`providers`, `reliability`,
|
|
143
|
+
`security`, `agent-loop`, `recipes`, `integrations`, `distribution`), and
|
|
144
|
+
[`examples/integrations/`](./examples/integrations/) added five CI-verified
|
|
145
|
+
starters (Express, Next.js, Cloudflare Workers, local Ollama, ESM/CJS
|
|
146
|
+
packaging check).
|
|
147
|
+
|
|
148
|
+
## Contributing
|
|
149
|
+
|
|
150
|
+
`AGENTS.md` is the always-on baseline — invariants, required reading, and the
|
|
151
|
+
[admission test](./AGENTS.md#feature-admission-test) a new feature has to pass.
|
|
152
|
+
`.claude/skills/` holds task-specific guides (`use-ai-nugget`,
|
|
153
|
+
`build-agent-loop`, `add-provider`, `develop-nugget`) that Claude Code loads
|
|
154
|
+
automatically and any agent or human can read as plain Markdown.
|
|
197
155
|
|
|
198
156
|
```bash
|
|
199
|
-
npm
|
|
200
|
-
npm test
|
|
201
|
-
npm run
|
|
202
|
-
npm run build # tsc → dist/ (also the typecheck)
|
|
203
|
-
npm run build:nugget # writes a vendorable nugget/ stamped with version + content hash
|
|
157
|
+
npm test # Node contract suite
|
|
158
|
+
npm run test:browser # same suite in headless Chromium (isomorphism)
|
|
159
|
+
npm run build && npm run build:nugget
|
|
204
160
|
```
|
|
205
161
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`tests/live-smoke.test.ts` exercises the real wire path against a live provider.
|
|
209
|
-
It is **skipped unless `AI_HANDLER_LIVE=1`**, so it never runs in the default
|
|
210
|
-
suite or in CI:
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
# Local Ollama (defaults: provider=ollama, model=llama3.2)
|
|
214
|
-
AI_HANDLER_LIVE=1 npm run test:live
|
|
215
|
-
|
|
216
|
-
# A cloud provider, with JSON-mode and agent tool-loop checks enabled
|
|
217
|
-
AI_HANDLER_LIVE=1 AI_HANDLER_LIVE_PROVIDER=openai AI_HANDLER_LIVE_MODEL=gpt-4o-mini \
|
|
218
|
-
AI_HANDLER_LIVE_KEY_ENV=OPENAI_API_KEY AI_HANDLER_LIVE_JSON=1 AI_HANDLER_LIVE_TOOLS=1 \
|
|
219
|
-
npm run test:live
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Config env vars: `AI_HANDLER_LIVE_PROVIDER`, `AI_HANDLER_LIVE_MODEL`,
|
|
223
|
-
`AI_HANDLER_LIVE_BASE_URL`, `AI_HANDLER_LIVE_KEY` (literal) or
|
|
224
|
-
`AI_HANDLER_LIVE_KEY_ENV` (key from an env var), `AI_HANDLER_LIVE_JSON`,
|
|
225
|
-
`AI_HANDLER_LIVE_TOOLS`.
|
|
226
|
-
|
|
227
|
-
**Live provider matrix (`.github/workflows/live-matrix.yml`).** The default
|
|
228
|
-
CI workflow only exercises mocked fetch responses, so subtle live-wire
|
|
229
|
-
behavior differences (OpenAI, Anthropic, Google, OpenRouter, and local
|
|
230
|
-
runtimes each drift independently) can't surface there. `live-matrix.yml`
|
|
231
|
-
runs the same `tests/live-smoke.test.ts` suite against real endpoints —
|
|
232
|
-
manually (`workflow_dispatch`) or on a weekly schedule — never on push/PR, so
|
|
233
|
-
it can't block or flake a normal CI run. Each matrix entry reads its API key
|
|
234
|
-
from a same-named repository secret (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,
|
|
235
|
-
`GOOGLE_API_KEY`, `OPENROUTER_API_KEY`) and skips itself with a notice if that
|
|
236
|
-
secret isn't configured, so you only need to add secrets for the providers
|
|
237
|
-
you actually use. Local runtimes (Ollama, llama.cpp, LM Studio, vLLM) aren't
|
|
238
|
-
in the matrix since they need a reachable server; smoke-test those manually
|
|
239
|
-
with the local `test:live` invocation above.
|
|
240
|
-
|
|
241
|
-
## Distribution
|
|
242
|
-
|
|
243
|
-
Three supported paths, in order of preference:
|
|
244
|
-
|
|
245
|
-
1. **npm registry (primary).** `@jxburros/ai-nugget` publishes to
|
|
246
|
-
[npmjs.org](https://www.npmjs.com/package/@jxburros/ai-nugget) on every
|
|
247
|
-
published GitHub release (`.github/workflows/publish.yml`). Consumers install
|
|
248
|
-
from the normal npm registry:
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
npm install @jxburros/ai-nugget@^0.3.1
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
This is the default dependency path for apps: fix once here, bump the
|
|
255
|
-
dependency in each app, and let npm resolve it normally.
|
|
256
|
-
2. **GitHub Packages (kept).** The same release workflow also publishes
|
|
257
|
-
`@jxburros/ai-nugget` to GitHub Packages for GitHub-native workflows and
|
|
258
|
-
existing portfolio consumers that already point at that registry. Those apps
|
|
259
|
-
add a project `.npmrc`:
|
|
260
|
-
|
|
261
|
-
```
|
|
262
|
-
@jxburros:registry=https://npm.pkg.github.com
|
|
263
|
-
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
(GitHub Packages requires an authenticated token even for public-repo
|
|
267
|
-
packages.) Then `npm install @jxburros/ai-nugget@^0.3.1`.
|
|
268
|
-
3. **Vendored `nugget/` (fallback).** `nugget/` is a generated single-folder
|
|
269
|
-
build (`src/` + `VERSION.txt` with a version + content-hash stamp) for repos
|
|
270
|
-
that cannot take a package dependency. Copy it in; `VERSION.txt` makes drift
|
|
271
|
-
from the source of truth detectable. (Bundlers that don't resolve
|
|
272
|
-
`.ts`+`.js`-suffixed imports the way `tsc` does — e.g. Turbopack — should
|
|
273
|
-
vendor `dist/` instead; see below.)
|
|
274
|
-
|
|
275
|
-
`dist/` (ESM + `.d.ts`) is the package's own build output. `dist/` and
|
|
276
|
-
`nugget/` are both committed and regenerated from `src/`; `prepublishOnly`
|
|
277
|
-
rebuilds both, and CI fails if either is stale.
|
|
278
|
-
|
|
279
|
-
**Bundler compatibility for the vendored `nugget/` path.** `nugget/src/*.ts`
|
|
280
|
-
uses NodeNext-style relative imports with explicit `.js` extensions (e.g.
|
|
281
|
-
`export * from './types.js'`), which `tsc` needs to resolve `.ts` files under
|
|
282
|
-
`moduleResolution: bundler`/`nodenext`. Not every bundler's runtime module
|
|
283
|
-
graph treats `.ts`/`.js` as interchangeable the way `tsc` does — confirmed
|
|
284
|
-
with Next.js 16's Turbopack: aliasing straight at `nugget/src/index.ts`
|
|
285
|
-
type-checks but fails to resolve at build/dev time (`Module not found: Can't
|
|
286
|
-
resolve './types.js'`), and aliasing at the compiled `dist/index.d.ts` builds
|
|
287
|
-
but silently resolves some named exports to `undefined` at runtime. If your
|
|
288
|
-
bundler hits this, vendor `dist/` (real `.js` + `.d.ts` pairs) instead of
|
|
289
|
-
`nugget/`, and point path aliases at its `.js` entry points (e.g.
|
|
290
|
-
`dist/index.js`, `dist/agent/index.js`) — TypeScript picks up the sibling
|
|
291
|
-
`.d.ts` automatically.
|
|
162
|
+
Full command list and CI layout: [distribution.md](./docs/distribution.md).
|
|
292
163
|
|
|
293
164
|
## Examples
|
|
294
165
|
|
|
295
|
-
`examples/` has runnable scripts
|
|
296
|
-
|
|
166
|
+
`examples/` has runnable scripts (local Ollama, llama.cpp, `promptJson` vs
|
|
167
|
+
native tools, approval gates, telemetry, a model picker), a small chat demo
|
|
168
|
+
(`npm run demo:steel-chat`), three mini-apps consuming the published package,
|
|
169
|
+
and the CI-verified [integration starters](./examples/integrations/). See
|
|
297
170
|
`examples/README.md`.
|
|
298
171
|
|
|
299
|
-
## Agent skills
|
|
300
|
-
|
|
301
|
-
`.claude/skills/` contains Agent Skills — task-specific guides that Claude Code
|
|
302
|
-
picks up automatically and that any AI agent (or human) can read as plain
|
|
303
|
-
Markdown: `use-ai-nugget` (integrating the nugget into an app),
|
|
304
|
-
`build-agent-loop` (the agent/tool-calling layer), `add-provider` (extending
|
|
305
|
-
the provider profile table), and `develop-nugget` (invariants and validation
|
|
306
|
-
for changes to this repo). `AGENTS.md` remains the always-on baseline; the
|
|
307
|
-
skills carry the per-task detail.
|
|
308
|
-
|
|
309
172
|
## Non-goals
|
|
310
173
|
|
|
311
|
-
Not a full agent framework (no planning/memory/RAG/multi-agent
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
174
|
+
Not a full agent framework (no planning/memory/RAG/multi-agent), not a
|
|
175
|
+
router/recommender (apps choose models), not a secrets vault (`KeySource` is a
|
|
176
|
+
seam), and not a UI. The governance seam ships neutral: it is where an app *can*
|
|
177
|
+
enforce rules, not a place the library imposes its own.
|
|
178
|
+
|
|
179
|
+
<!-- GitHub Pages deployment is configured in .github/workflows/pages.yml. -->
|
package/UPGRADING.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Upgrading
|
|
2
|
+
|
|
3
|
+
## 0.5.x → 0.6.0
|
|
4
|
+
|
|
5
|
+
Behavior-compatible for ordinary consumers, with **two things worth checking**.
|
|
6
|
+
|
|
7
|
+
### 1. `not_found` is a new `AIErrorKind`
|
|
8
|
+
|
|
9
|
+
404 and 410 responses now classify as `not_found` instead of
|
|
10
|
+
`invalid_request`, so a wrong `baseUrl`/model or a misrouted edge is
|
|
11
|
+
distinguishable from a malformed request body. It is non-retryable by default.
|
|
12
|
+
|
|
13
|
+
- **If you `switch` exhaustively over `AIErrorKind`** (with a `never` default),
|
|
14
|
+
TypeScript will require a new branch. Map it like `auth`/`key_unavailable` —
|
|
15
|
+
it's a *server-side configuration* problem, not the caller's fault:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
case 'not_found': return { status: 502, message: 'The AI service is not configured correctly.' };
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **If you switch non-exhaustively** with a fallback, nothing breaks: a 404 that
|
|
22
|
+
used to produce "that request was invalid" now falls into your default branch
|
|
23
|
+
instead. That is the intended improvement, but check the resulting message
|
|
24
|
+
still reads correctly.
|
|
25
|
+
|
|
26
|
+
### 2. A startup notice when no policy is configured
|
|
27
|
+
|
|
28
|
+
`new AIHandler({ keySource })` with no `policy` now logs one line at
|
|
29
|
+
construction: every provider and model is allowed. `allowAllPolicy()` is still
|
|
30
|
+
the default — nothing is blocked. To silence it, say what you mean:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
new AIHandler({ keySource, policy: allowAllPolicy() }); // deliberate, no warning
|
|
34
|
+
new AIHandler({ keySource, silencePolicyWarning: true }); // same, if you prefer
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Also new, no action required
|
|
38
|
+
|
|
39
|
+
- **`Connection.provider` is now typed** `KnownProvider | (string & {})`. You get
|
|
40
|
+
autocomplete and typo-catching; arbitrary strings still compile and still
|
|
41
|
+
resolve through `openai-compat`.
|
|
42
|
+
- **`beforeCall` hooks get a scrubbed connection.** `info.connection.keyRef` is
|
|
43
|
+
masked for literal refs, and `info.resolved.headers` masks auth headers. If a
|
|
44
|
+
hook was reading `info.connection.keyRef.value` to get the key — don't; resolve
|
|
45
|
+
it through your `KeySource` instead.
|
|
46
|
+
- **`Idempotency-Key` on OpenAI requests.** One key per logical call, reused
|
|
47
|
+
across retries. A header you set yourself on `Connection.headers` wins.
|
|
48
|
+
- **New `context` stream events**: `json_mode_downgraded` (Google/Anthropic drop
|
|
49
|
+
JSON mode when tools are present) and `stream_anomaly` on all four engines
|
|
50
|
+
(previously `openaiChat` only). Both are informational; ignoring them keeps
|
|
51
|
+
the old behavior.
|
|
52
|
+
- **Documentation moved.** `README.md` is now a front door; reference material
|
|
53
|
+
lives under `docs/` (`providers`, `reliability`, `security`, `agent-loop`,
|
|
54
|
+
`recipes`, `integrations`, `distribution`). Deep links into the old README
|
|
55
|
+
anchors will need updating.
|
|
56
|
+
|
|
57
|
+
## 0.4.x → 0.5.0
|
|
58
|
+
|
|
59
|
+
**0.5.0 is backward-compatible.** Every change below is additive — new optional
|
|
60
|
+
fields, new methods, new exports, and a new `require` entry point. No existing
|
|
61
|
+
signature changed and no default behavior changed, so an app on 0.4.x can bump
|
|
62
|
+
without code edits and adopt the new capabilities as needed.
|
|
63
|
+
|
|
64
|
+
The numbers in brackets are the suggestion numbers from the AI Server Studio
|
|
65
|
+
production friction report (`docs/reviews/2026-08-09-ai-server-studio-friction-report.md`),
|
|
66
|
+
so you can trace each change back to the friction it resolves.
|
|
67
|
+
|
|
68
|
+
### Packaging
|
|
69
|
+
|
|
70
|
+
- **[1] Dual ESM + CommonJS build.** The package now exposes a `require`
|
|
71
|
+
condition (`dist/cjs/`) in addition to ESM. CommonJS hosts can
|
|
72
|
+
`const { AIHandler } = require('@jxburros/ai-nugget')` with no dynamic-import
|
|
73
|
+
shim. ESM consumers are unaffected.
|
|
74
|
+
- **[2] Vendorable compiled output.** `npm run build:nugget` now emits both
|
|
75
|
+
`nugget/src` (TypeScript) and `nugget/dist` (compiled ESM `.js` + `.d.ts`), so
|
|
76
|
+
bundlers that can't resolve `.ts` via `.js`-suffixed imports (e.g. Turbopack)
|
|
77
|
+
can vendor `nugget/dist`.
|
|
78
|
+
|
|
79
|
+
### Requests & providers
|
|
80
|
+
|
|
81
|
+
- **[4] `ChatRequest.providerOptions`.** A provider-native passthrough merged
|
|
82
|
+
into the outgoing request body (shallow top-level; one level deep into Ollama
|
|
83
|
+
`options` and Google `generationConfig`). Reach fields the nugget doesn't
|
|
84
|
+
model — Ollama `num_ctx`/`keep_alive`, OpenAI `reasoning_effort`, Anthropic
|
|
85
|
+
`thinking`, Google `safetySettings` — without waiting for a release.
|
|
86
|
+
- **[16] `Connection.idleTimeoutMs`.** An idle timeout for streams, distinct from
|
|
87
|
+
the total `timeoutMs`: it aborts only when no chunk arrives for that long, and
|
|
88
|
+
resets on every chunk — so a slow-but-healthy long local generation isn't
|
|
89
|
+
killed mid-stream.
|
|
90
|
+
- **[17] `reasoning` stream events.** Reasoning/thinking tokens (Anthropic
|
|
91
|
+
`thinking_delta`, OpenAI `reasoning_content`, Gemini `thought` parts, Ollama
|
|
92
|
+
`thinking`) surface as `{ type: 'reasoning', text }` instead of being dropped
|
|
93
|
+
or blended into `delta`.
|
|
94
|
+
- **[18] `listModels()` for Anthropic & Google.** Both now query the real
|
|
95
|
+
endpoints (`/v1/models`, `/v1beta/models`) instead of returning `[]`.
|
|
96
|
+
- **[19] Faster Ollama discovery.** `/api/show` probes run with bounded
|
|
97
|
+
concurrency instead of serially.
|
|
98
|
+
- **[20] New provider profiles + Azure api-version override.** Added `cerebras`,
|
|
99
|
+
`moonshot`, `cohere`, and `perplexity`. The Azure `api-version` is now
|
|
100
|
+
overridable per call via `providerOptions.apiVersion` (default unchanged).
|
|
101
|
+
- **[21] Safer URL joining** in the `openai-compat` engine (no accidental double
|
|
102
|
+
slash even if `baseUrl` ends in `/`).
|
|
103
|
+
- **[23] Embeddings.** `AIHandler.embed(conn, req)` plus `EmbedRequest` /
|
|
104
|
+
`EmbedResult`, implemented for Ollama (`/api/embed`) and OpenAI-compatible
|
|
105
|
+
providers (`/embeddings`) — through the same policy/key/telemetry pipeline as
|
|
106
|
+
chat. Providers without embeddings throw a typed `invalid_request` error.
|
|
107
|
+
|
|
108
|
+
### Structured output & typed results
|
|
109
|
+
|
|
110
|
+
- **[15] `AIHandler.chatParsed(conn, req, schema)`.** Requests JSON, extracts it,
|
|
111
|
+
and validates against any [Standard Schema](https://standardschema.dev)
|
|
112
|
+
validator (Zod / Valibot / ArkType) — with one automatic corrective retry on a
|
|
113
|
+
validation miss. No schema library is bundled; you pass your own.
|
|
114
|
+
|
|
115
|
+
### Agent loop (`@jxburros/ai-nugget/agent`)
|
|
116
|
+
|
|
117
|
+
- **[5] Option passthrough.** `AgentOptions` now forwards `temperature`,
|
|
118
|
+
`maxTokens`, `topP`, `stopSequences`, and `providerOptions` to every turn.
|
|
119
|
+
- **[6] Capability-aware `toolMode`.** `AgentOptions.modelCapabilities` — when
|
|
120
|
+
`toolMode: 'auto'`, a model advertising `"tools"` uses native tool-calling even
|
|
121
|
+
on a local runtime whose profile default is `promptJson`.
|
|
122
|
+
- **[7] No leaked directives + `tool_mode` event.** In `promptJson` mode, tool
|
|
123
|
+
directives are withheld from the visible `delta` stream, and a `tool_mode`
|
|
124
|
+
event discloses the resolved protocol.
|
|
125
|
+
- **[8] Parameter schemas in the `promptJson` prompt**, so the model isn't
|
|
126
|
+
guessing argument shape.
|
|
127
|
+
- **[9] `undefined` tool results no longer crash a run.**
|
|
128
|
+
- **[10] `AgentResult.error`** is populated when `stopReason === 'error'`.
|
|
129
|
+
- **[11] Structured tool errors.** A tool returning `{ ok: false, ... }` is
|
|
130
|
+
surfaced with `isError: true` so the model can recover in-turn.
|
|
131
|
+
- **[12] `AgentOptions.toolResult`.** Opt-in `maxChars` cap and `wrapUntrusted`
|
|
132
|
+
framing for tool results re-entering context.
|
|
133
|
+
- **[14] `AgentOptions.approvalMode: 'all'`.** Gate every tool (not just
|
|
134
|
+
`sideEffects` ones) so `tool_denied` is reachable without per-tool flags.
|
|
135
|
+
|
|
136
|
+
### Handler, telemetry & security
|
|
137
|
+
|
|
138
|
+
- **[3] `AIHandler.prewarm(conn)`.** A no-throw warm-up (health probe) to move
|
|
139
|
+
cold DNS/TLS/daemon-spin-up cost off the first real call.
|
|
140
|
+
- **[22] `CallInfo.resolved`.** The `beforeCall` hook now receives the resolved
|
|
141
|
+
connection (base URL + headers, key omitted) so it can validate the effective
|
|
142
|
+
endpoint — e.g. a request-time SSRF re-check — without hijacking `keySource`.
|
|
143
|
+
- **[24] `HandlerOptions.pricing`.** An optional cost estimator whose result is
|
|
144
|
+
recorded on `CallRecord.costUsd`.
|
|
145
|
+
|
|
146
|
+
### Tooling & hygiene
|
|
147
|
+
|
|
148
|
+
- **[25]** Assistant messages with both content parts and tool calls no longer
|
|
149
|
+
drop their text/image parts on replay (Anthropic & Google); dead `role:'tool'`
|
|
150
|
+
branches removed; the `403 → non-retryable` policy is documented; ESLint and
|
|
151
|
+
Vitest coverage are wired up (`npm run lint`, `npm run test:coverage`).
|
|
152
|
+
|
|
153
|
+
### Not changed (deliberately)
|
|
154
|
+
|
|
155
|
+
- No provider policy ships as a library default (blocklists/allowlists remain the
|
|
156
|
+
app's configuration at the seam).
|
|
157
|
+
- Zero runtime dependencies — everything above is implemented with the isomorphic
|
|
158
|
+
core (`fetch`/`ReadableStream`/`AbortController`/`TextDecoder`) only.
|