@jxburros/ai-nugget 0.7.0 → 0.7.3
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 +63 -3
- package/UPGRADING.md +19 -1
- package/dist/adapters/engines/anthropic.js +80 -10
- package/dist/agent/loop.js +3 -1
- package/dist/cjs/adapters/engines/anthropic.js +79 -9
- package/dist/cjs/agent/loop.js +3 -1
- package/dist/cjs/handler.js +1 -1
- package/dist/handler.js +1 -1
- package/dist/types.d.ts +9 -0
- package/nugget/VERSION.txt +2 -2
- package/nugget/dist/adapters/engines/anthropic.d.ts.map +1 -1
- package/nugget/dist/adapters/engines/anthropic.js +80 -10
- package/nugget/dist/adapters/engines/anthropic.js.map +1 -1
- package/nugget/dist/agent/loop.d.ts.map +1 -1
- package/nugget/dist/agent/loop.js +3 -1
- package/nugget/dist/agent/loop.js.map +1 -1
- package/nugget/dist/cjs/adapters/engines/anthropic.js +79 -9
- package/nugget/dist/cjs/agent/loop.js +3 -1
- package/nugget/dist/cjs/handler.js +1 -1
- package/nugget/dist/handler.js +1 -1
- package/nugget/dist/handler.js.map +1 -1
- package/nugget/dist/types.d.ts +9 -0
- package/nugget/dist/types.d.ts.map +1 -1
- package/nugget/dist/types.js.map +1 -1
- package/nugget/src/adapters/engines/anthropic.ts +71 -10
- package/nugget/src/agent/loop.ts +4 -2
- package/nugget/src/handler.ts +1 -1
- package/nugget/src/types.ts +10 -0
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# AI Nugget
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="assets/ai-nugget-logo.png" alt="AI Nugget official logo" width="180" />
|
|
4
|
+
|
|
5
|
+
**Version 0.7.3** · [What's changed in 0.7.x](#whats-changed-in-07x) · [Changelog](./CHANGELOG.md) · [Upgrading](./UPGRADING.md)
|
|
4
6
|
|
|
5
7
|
A small, zero-dependency, isomorphic TypeScript nugget for talking to AI model
|
|
6
8
|
providers through one pipeline:
|
|
@@ -24,6 +26,13 @@ Requires Node `^20.19.0` or `>=22.12.0` ([why](./docs/distribution.md#requiremen
|
|
|
24
26
|
npm install @jxburros/ai-nugget
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
> **0.7.1–0.7.3 are not on npm yet.** The registry's `latest` is still 0.7.0
|
|
30
|
+
> until the 0.7.3 Release run publishes, so the command above may install 0.7.0
|
|
31
|
+
> (without the 0.7.1 Anthropic fixes or the 0.7.2 `/agent` types fix). Until
|
|
32
|
+
> then, pin a commit from this repository
|
|
33
|
+
> (`npm install github:jxburros/AI-Nugget#<commit>`) or vendor `nugget/` — see
|
|
34
|
+
> [distribution paths](./docs/distribution.md#distribution-paths).
|
|
35
|
+
|
|
27
36
|
```ts
|
|
28
37
|
import { AIHandler, envKeySource } from '@jxburros/ai-nugget';
|
|
29
38
|
|
|
@@ -50,7 +59,9 @@ No key? Point at a local model instead — same code, no `keyRef`:
|
|
|
50
59
|
const conn = { id: 'local', provider: 'ollama', baseUrl: 'http://127.0.0.1:11434' };
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
CommonJS works too: `const { AIHandler } = require('@jxburros/ai-nugget')`.
|
|
62
|
+
CommonJS works too: `const { AIHandler } = require('@jxburros/ai-nugget')`. TypeScript
|
|
63
|
+
finds the root and `/agent` types under `node10` (classic `module: commonjs`)
|
|
64
|
+
and `bundler` resolution, and under `node16`/`nodenext` from ESM files.
|
|
54
65
|
|
|
55
66
|
For a running server, copy a starter from
|
|
56
67
|
[`examples/integrations/`](./examples/integrations/) — Express, Next.js,
|
|
@@ -116,7 +127,48 @@ new AIHandler({
|
|
|
116
127
|
honest `stopReason`s, an `ApprovalGate` for side-effecting tools, and
|
|
117
128
|
`native`/`promptJson`/`auto` tool modes.
|
|
118
129
|
|
|
119
|
-
##
|
|
130
|
+
## Anthropic compatibility
|
|
131
|
+
|
|
132
|
+
Claude Opus/Sonnet/Haiku 5.5 requests omit unsupported sampling parameters.
|
|
133
|
+
Modern Claude JSON schemas use `output_config.format`; requests without a schema
|
|
134
|
+
use a JSON instruction and emit `json_mode_downgraded`. Older models retain the
|
|
135
|
+
forced-tool fallback. Explicit forced-tool choices on models that reject them
|
|
136
|
+
fail with `invalid_request` rather than silently changing the choice.
|
|
137
|
+
|
|
138
|
+
Anthropic results include opaque `providerState`. Custom tool loops must copy it
|
|
139
|
+
onto the corresponding assistant `ChatMessage`; `runAgent` does this automatically.
|
|
140
|
+
Keep it unchanged and private: it contains signed thinking blocks needed for replay,
|
|
141
|
+
not display or telemetry data. Replay is limited to the same provider and model.
|
|
142
|
+
See [provider details](./docs/providers.md#anthropic-compatibility).
|
|
143
|
+
|
|
144
|
+
## What's changed in 0.7.x
|
|
145
|
+
|
|
146
|
+
### 0.7.3
|
|
147
|
+
|
|
148
|
+
**Release-pipeline fix.** Package contents are identical to 0.7.2; 0.7.3 exists
|
|
149
|
+
because the 0.7.1 and 0.7.2 Release runs failed before publishing (see the
|
|
150
|
+
[changelog](./CHANGELOG.md)).
|
|
151
|
+
|
|
152
|
+
### 0.7.2
|
|
153
|
+
|
|
154
|
+
**`/agent` types under classic `node10` resolution.** A `typesVersions` entry
|
|
155
|
+
lets TypeScript projects on `module: commonjs` (default `node10` resolution)
|
|
156
|
+
resolve `@jxburros/ai-nugget/agent`. Otherwise documentation only — notably the
|
|
157
|
+
[JX Runtime guidance](./docs/providers.md#pointing-at-a-jx-runtime-server) for
|
|
158
|
+
JX 1.5, including the `stream_options` opt-in for real streamed token counts.
|
|
159
|
+
No runtime behavior changed.
|
|
160
|
+
|
|
161
|
+
### 0.7.1
|
|
162
|
+
|
|
163
|
+
**Anthropic correctness for modern Claude models.** Unsupported sampling
|
|
164
|
+
parameters are omitted, JSON schemas use native structured output, and signed
|
|
165
|
+
thinking is preserved for tool-loop replay through the new opaque
|
|
166
|
+
`providerState` on results (`runAgent` copies it for you; custom tool loops must
|
|
167
|
+
copy it onto the assistant message). SSE API errors become typed failures.
|
|
168
|
+
Details in [Anthropic compatibility](#anthropic-compatibility),
|
|
169
|
+
[UPGRADING.md](./UPGRADING.md#070--071), and the [changelog](./CHANGELOG.md).
|
|
170
|
+
|
|
171
|
+
### 0.7.0
|
|
120
172
|
|
|
121
173
|
**0.7.0 is backward-compatible for ordinary consumers** — the first two changes are additive; the third changes what `delta` carries for reasoning models (see UPGRADING). Full detail in the
|
|
122
174
|
[changelog](./CHANGELOG.md) and [UPGRADING.md](./UPGRADING.md#06x--070).
|
|
@@ -206,3 +258,11 @@ enforce rules, not a place the library imposes its own.
|
|
|
206
258
|
## Project Stewardship
|
|
207
259
|
|
|
208
260
|
This project is developed by **Jeffrey** through **JX Holdings, LLC**. GitHub: [`jxburros`](https://github.com/jxburros).
|
|
261
|
+
|
|
262
|
+
## Unified release procedure
|
|
263
|
+
|
|
264
|
+
`release.json` is the authoritative release manifest. Keep configured embedded versions synchronized and advance affected apps (patch by default) before merging changes into main. The first adoption establishes a baseline; later PRs must pass **Release version validation**.
|
|
265
|
+
|
|
266
|
+
Use **Actions → Release → Run workflow** from **main**, choose stable or preview, and leave **dry_run** enabled first. Only apps ahead of their published version in their own namespace are selected. Build/test/sign all required packages before publishing. Tag pushes and merges no longer publish migrated apps automatically. After inspecting the dry run, dispatch with dry_run disabled and satisfy configured approvals. Published versions are never overwritten; use a new version for a correction.
|
|
267
|
+
|
|
268
|
+
See [.release/README.md](.release/README.md) for this repository's inventory, credentials, compatibility, and validation limits. Shared implementation: [JX Holdings release infrastructure](https://github.com/JX-Holdings-LLC/release-infrastructure).
|
package/UPGRADING.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Upgrading
|
|
2
2
|
|
|
3
|
+
## 0.7.2 → 0.7.3
|
|
4
|
+
|
|
5
|
+
No changes: 0.7.3 republishes the 0.7.2 package contents after a release-pipeline fix.
|
|
6
|
+
|
|
7
|
+
## 0.7.1 → 0.7.2
|
|
8
|
+
|
|
9
|
+
No code changes required. `package.json` gains a `typesVersions` entry so
|
|
10
|
+
TypeScript on classic `node10` resolution can find the `/agent` types; consumers
|
|
11
|
+
on `node16`/`nodenext`/`bundler` resolve exactly as before.
|
|
12
|
+
|
|
13
|
+
## 0.7.0 → 0.7.1
|
|
14
|
+
|
|
15
|
+
Custom Anthropic tool loops should copy `result.providerState` onto their assistant
|
|
16
|
+
messages to preserve signed thinking; `runAgent` already does this. Sampling fields
|
|
17
|
+
are omitted on known modern Claude models. Native JSON schemas replace forced JSON
|
|
18
|
+
tools there; schema-less requests disclose an instruction-only downgrade. Explicit
|
|
19
|
+
unsupported forced-tool and thinking-off choices fail with `invalid_request`.
|
|
20
|
+
|
|
3
21
|
## 0.6.x → 0.7.0
|
|
4
22
|
|
|
5
23
|
**Backward-compatible for ordinary consumers.** Two additive changes and one
|
|
@@ -86,7 +104,7 @@ signature changed and no default behavior changed, so an app on 0.4.x can bump
|
|
|
86
104
|
without code edits and adopt the new capabilities as needed.
|
|
87
105
|
|
|
88
106
|
The numbers in brackets are the suggestion numbers from the AI Server Studio
|
|
89
|
-
production friction report (`docs/
|
|
107
|
+
production friction report ([`docs/archive/2026-08-09-ai-server-studio-friction-report.md`](./docs/archive/2026-08-09-ai-server-studio-friction-report.md)),
|
|
90
108
|
so you can trace each change back to the friction it resolves.
|
|
91
109
|
|
|
92
110
|
### Packaging
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AIError } from '../../errors.js';
|
|
1
|
+
import { AIError, classify } from '../../errors.js';
|
|
2
2
|
import { estimatedUsage } from '../../tokens.js';
|
|
3
3
|
import { fetchJson, postResponse, sseLines } from '../../transport.js';
|
|
4
4
|
import { applyProviderOptions, REASONING_BUDGET_TOKENS, asNumber, asRecord, asString, joinUrl, textFromMessages } from '../../util.js';
|
|
@@ -23,7 +23,8 @@ export class AnthropicAdapter {
|
|
|
23
23
|
}
|
|
24
24
|
async *stream(conn, req) {
|
|
25
25
|
const started = Date.now();
|
|
26
|
-
const
|
|
26
|
+
const nativeJson = modernModel(req.model);
|
|
27
|
+
const jsonMode = req.responseFormat?.type === 'json' && !nativeJson && !req.tools?.length;
|
|
27
28
|
let firstTokenMs = null;
|
|
28
29
|
let text = '';
|
|
29
30
|
let inputTokens;
|
|
@@ -31,6 +32,7 @@ export class AnthropicAdapter {
|
|
|
31
32
|
let stopReason;
|
|
32
33
|
let sawTerminal = false;
|
|
33
34
|
const emittedTools = [];
|
|
35
|
+
const responseBlocks = new Map();
|
|
34
36
|
// Partial tool_use blocks keyed by content-block index (input_json_delta accumulation).
|
|
35
37
|
const blocks = new Map();
|
|
36
38
|
const timeout = streamTimeout(conn, req.signal);
|
|
@@ -38,19 +40,29 @@ export class AnthropicAdapter {
|
|
|
38
40
|
// Anthropic JSON mode is implemented as a forced `json_output` tool, which
|
|
39
41
|
// cannot coexist with the caller's own tools — same silent-downgrade shape
|
|
40
42
|
// as Gemini, so it gets the same signal rather than quietly dropping.
|
|
41
|
-
if (req.responseFormat?.type === 'json' && req.tools?.length) {
|
|
43
|
+
if (req.responseFormat?.type === 'json' && !nativeJson && req.tools?.length) {
|
|
42
44
|
yield {
|
|
43
45
|
type: 'context',
|
|
44
46
|
kind: 'json_mode_downgraded',
|
|
45
47
|
data: { reason: 'Anthropic JSON mode uses a forced tool and cannot be combined with caller tools; the request was sent without JSON mode', provider: conn.provider },
|
|
46
48
|
};
|
|
47
49
|
}
|
|
50
|
+
if (req.responseFormat?.type === 'json' && nativeJson && !req.responseFormat.schema) {
|
|
51
|
+
yield { type: 'context', kind: 'json_mode_downgraded', data: { provider: conn.provider, reason: 'Claude native JSON outputs require a schema; using a JSON instruction without constrained decoding.' } };
|
|
52
|
+
}
|
|
48
53
|
try {
|
|
49
54
|
const res = await postResponse(`${conn.baseUrl}/v1/messages`, body(req, jsonMode), conn.headers, timeout.signal, conn.provider);
|
|
50
55
|
const contentType = res.headers.get('content-type') ?? '';
|
|
51
56
|
if (!contentType.includes('text/event-stream')) {
|
|
52
57
|
const data = await res.json();
|
|
53
58
|
const parsed = parseResponse(data);
|
|
59
|
+
const content = asRecord(data)?.content;
|
|
60
|
+
if (Array.isArray(content))
|
|
61
|
+
content.forEach((block, i) => {
|
|
62
|
+
const record = asRecord(block);
|
|
63
|
+
if (record)
|
|
64
|
+
responseBlocks.set(i, record);
|
|
65
|
+
});
|
|
54
66
|
inputTokens = parsed.inputTokens;
|
|
55
67
|
outputTokens = parsed.outputTokens;
|
|
56
68
|
stopReason = parsed.stopReason;
|
|
@@ -77,7 +89,12 @@ export class AnthropicAdapter {
|
|
|
77
89
|
if (!record)
|
|
78
90
|
continue;
|
|
79
91
|
const type = asString(record.type);
|
|
80
|
-
if (type === '
|
|
92
|
+
if (type === 'error') {
|
|
93
|
+
const error = asRecord(record.error);
|
|
94
|
+
const statuses = { overloaded_error: 529, rate_limit_error: 429, authentication_error: 401, permission_error: 403, invalid_request_error: 400, not_found_error: 404, api_error: 500, billing_error: 402, timeout_error: 504, request_too_large: 413 };
|
|
95
|
+
throw classify(statuses[asString(error?.type) ?? ''] ?? 500, JSON.stringify(record), conn.provider);
|
|
96
|
+
}
|
|
97
|
+
else if (type === 'message_start') {
|
|
81
98
|
const usage = asRecord(asRecord(record.message)?.usage);
|
|
82
99
|
inputTokens = asNumber(usage?.input_tokens) ?? inputTokens;
|
|
83
100
|
outputTokens = asNumber(usage?.output_tokens) ?? outputTokens;
|
|
@@ -85,6 +102,8 @@ export class AnthropicAdapter {
|
|
|
85
102
|
else if (type === 'content_block_start') {
|
|
86
103
|
const index = asNumber(record.index) ?? 0;
|
|
87
104
|
const block = asRecord(record.content_block);
|
|
105
|
+
if (block)
|
|
106
|
+
responseBlocks.set(index, { ...block });
|
|
88
107
|
if (block?.type === 'tool_use') {
|
|
89
108
|
blocks.set(index, { id: asString(block.id) ?? randomId(), name: asString(block.name) ?? 'unknown', raw: '' });
|
|
90
109
|
}
|
|
@@ -92,6 +111,14 @@ export class AnthropicAdapter {
|
|
|
92
111
|
else if (type === 'content_block_delta') {
|
|
93
112
|
const index = asNumber(record.index) ?? 0;
|
|
94
113
|
const delta = asRecord(record.delta);
|
|
114
|
+
const block = responseBlocks.get(index);
|
|
115
|
+
if (block && delta) {
|
|
116
|
+
const field = delta.type === 'text_delta' ? 'text' : delta.type === 'thinking_delta' ? 'thinking' : delta.type === 'signature_delta' ? 'signature' : undefined;
|
|
117
|
+
if (field)
|
|
118
|
+
block[field] = String(block[field] ?? '') + String(delta[field] ?? '');
|
|
119
|
+
if (delta.type === 'citations_delta')
|
|
120
|
+
block.citations = [...(Array.isArray(block.citations) ? block.citations : []), delta.citation];
|
|
121
|
+
}
|
|
95
122
|
if (delta?.type === 'text_delta') {
|
|
96
123
|
const piece = asString(delta.text) ?? '';
|
|
97
124
|
if (piece) {
|
|
@@ -118,6 +145,9 @@ export class AnthropicAdapter {
|
|
|
118
145
|
const partial = blocks.get(index);
|
|
119
146
|
if (partial) {
|
|
120
147
|
const call = { id: partial.id, name: partial.name, raw: partial.raw, arguments: parseArgs(partial.raw) };
|
|
148
|
+
const block = responseBlocks.get(index);
|
|
149
|
+
if (block)
|
|
150
|
+
block.input = call.arguments;
|
|
121
151
|
blocks.delete(index);
|
|
122
152
|
if (jsonMode && call.name === JSON_MODE_TOOL) {
|
|
123
153
|
text = jsonTextFrom([call], text);
|
|
@@ -149,6 +179,7 @@ export class AnthropicAdapter {
|
|
|
149
179
|
const hasTools = emittedTools.length > 0;
|
|
150
180
|
yield { type: 'done', result: {
|
|
151
181
|
text,
|
|
182
|
+
providerState: { provider: conn.provider, model: req.model, content: [...responseBlocks.entries()].sort(([a], [b]) => a - b).map(([, block]) => block) },
|
|
152
183
|
toolCalls: hasTools ? emittedTools : undefined,
|
|
153
184
|
finishReason: mapStop(stopReason, hasTools),
|
|
154
185
|
usage: inputTokens !== undefined || outputTokens !== undefined ? { inputTokens, outputTokens, estimated: false } : estimatedUsage(textFromMessages(req.messages), text),
|
|
@@ -192,9 +223,15 @@ function body(req, jsonMode) {
|
|
|
192
223
|
top_p: req.topP,
|
|
193
224
|
stop_sequences: req.stopSequences,
|
|
194
225
|
system,
|
|
195
|
-
messages: toAnthropicMessages(req.messages.filter((m) => m.role !== 'system')),
|
|
226
|
+
messages: toAnthropicMessages(req.messages.filter((m) => m.role !== 'system'), req.model),
|
|
196
227
|
stream: true,
|
|
197
228
|
};
|
|
229
|
+
if (req.responseFormat?.type === 'json' && modernModel(req.model) && req.responseFormat.schema) {
|
|
230
|
+
base.output_config = { format: { type: 'json_schema', schema: schemaFor(req) } };
|
|
231
|
+
}
|
|
232
|
+
if (req.responseFormat?.type === 'json' && modernModel(req.model) && !req.responseFormat.schema) {
|
|
233
|
+
base.system = [system, 'Return only valid JSON, without markdown fences or a preamble.'].filter(Boolean).join('\n\n');
|
|
234
|
+
}
|
|
198
235
|
if (jsonMode) {
|
|
199
236
|
// Forced-tool JSON mode: Anthropic has no response_format, so we make the
|
|
200
237
|
// model answer through a single required tool whose input is the JSON we want.
|
|
@@ -215,7 +252,14 @@ function body(req, jsonMode) {
|
|
|
215
252
|
// `budget_tokens < max_tokens` and rejects temperature/top_p alongside
|
|
216
253
|
// thinking, so the budget is fitted under max_tokens (raising max_tokens
|
|
217
254
|
// when the caller's value leaves no room) and the samplers are dropped.
|
|
218
|
-
if (req.
|
|
255
|
+
if (modernModel(req.model)) {
|
|
256
|
+
const none = req.reasoningEffort === 'none';
|
|
257
|
+
if (none && alwaysThinking(req.model))
|
|
258
|
+
throw new AIError('This Claude model cannot disable thinking; choose a reasoning effort instead.', { kind: 'invalid_request', provider: 'anthropic' });
|
|
259
|
+
base.thinking = { type: none ? (/^claude-sonnet-5-5(?:-|$)/.test(req.model) ? 'between_tools' : 'disabled') : 'adaptive' };
|
|
260
|
+
base.output_config = { ...asRecord(base.output_config), effort: none ? 'low' : req.reasoningEffort === 'minimal' ? 'low' : req.reasoningEffort };
|
|
261
|
+
}
|
|
262
|
+
else if (req.reasoningEffort === 'none') {
|
|
219
263
|
base.thinking = { type: 'disabled' };
|
|
220
264
|
}
|
|
221
265
|
else {
|
|
@@ -230,14 +274,24 @@ function body(req, jsonMode) {
|
|
|
230
274
|
}
|
|
231
275
|
// providerOptions carries Anthropic-native fields (`thinking`, `metadata`,
|
|
232
276
|
// top-level `cache_control` extras, `service_tier`, …) without a release.
|
|
233
|
-
|
|
277
|
+
const result = applyProviderOptions(base, req.providerOptions, ['output_config']);
|
|
278
|
+
if (modernModel(req.model)) {
|
|
279
|
+
delete result.temperature;
|
|
280
|
+
delete result.top_p;
|
|
281
|
+
delete result.top_k;
|
|
282
|
+
}
|
|
283
|
+
const choice = asRecord(result.tool_choice);
|
|
284
|
+
if (noForcedTools(req.model) && (choice?.type === 'tool' || choice?.type === 'any')) {
|
|
285
|
+
throw new AIError('This Claude model does not support forced tool choice; use auto or structured outputs.', { kind: 'invalid_request', provider: 'anthropic' });
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
234
288
|
}
|
|
235
|
-
function toAnthropicMessages(messages) {
|
|
289
|
+
function toAnthropicMessages(messages, model) {
|
|
236
290
|
const out = [];
|
|
237
291
|
for (let index = 0; index < messages.length; index += 1) {
|
|
238
292
|
const message = messages[index];
|
|
239
293
|
if (message.role !== 'tool') {
|
|
240
|
-
out.push(toAnthropicMessage(message));
|
|
294
|
+
out.push(toAnthropicMessage(message, model));
|
|
241
295
|
continue;
|
|
242
296
|
}
|
|
243
297
|
const content = [];
|
|
@@ -251,7 +305,10 @@ function toAnthropicMessages(messages) {
|
|
|
251
305
|
}
|
|
252
306
|
return out;
|
|
253
307
|
}
|
|
254
|
-
function toAnthropicMessage(m) {
|
|
308
|
+
function toAnthropicMessage(m, model) {
|
|
309
|
+
if (m.role === 'assistant' && m.providerState?.provider === 'anthropic' && m.providerState.model === model) {
|
|
310
|
+
return { role: 'assistant', content: m.providerState.content };
|
|
311
|
+
}
|
|
255
312
|
// Tool-role messages are handled by toAnthropicMessages' batching loop and
|
|
256
313
|
// never reach here, so no tool branch is needed.
|
|
257
314
|
// assistant turns that carried tool calls replay them as tool_use blocks,
|
|
@@ -316,8 +373,21 @@ function jsonTextFrom(toolCalls, fallback) {
|
|
|
316
373
|
function mapStop(stop, hasTools) {
|
|
317
374
|
if (stop === 'tool_use' && hasTools)
|
|
318
375
|
return 'tool';
|
|
376
|
+
if (stop === 'refusal')
|
|
377
|
+
return 'content_filter';
|
|
319
378
|
if (stop === 'max_tokens')
|
|
320
379
|
return 'length';
|
|
321
380
|
return 'stop';
|
|
322
381
|
}
|
|
382
|
+
/** Models released after Opus 4.6 use adaptive effort and reject sampling knobs.
|
|
383
|
+
* Unknown/custom IDs retain legacy behavior rather than guessing capabilities. */
|
|
384
|
+
function modernModel(model) {
|
|
385
|
+
return /^claude-(?:opus-(?:4-[78]|5(?:-5)?)|sonnet-5(?:-5)?|haiku-5-5|(?:fable|mythos)-5(?:-1)?|mythos-preview)(?:-\d{8}|-latest)?$/.test(model);
|
|
386
|
+
}
|
|
387
|
+
function alwaysThinking(model) {
|
|
388
|
+
return /^claude-(?:fable-|mythos-|opus-5-5(?:-|$))/.test(model);
|
|
389
|
+
}
|
|
390
|
+
function noForcedTools(model) {
|
|
391
|
+
return /^claude-(?:(?:opus|sonnet)-5-5(?:-|$)|(?:fable|mythos)-5-1(?:-|$))/.test(model);
|
|
392
|
+
}
|
|
323
393
|
//# sourceMappingURL=anthropic.js.map
|
package/dist/agent/loop.js
CHANGED
|
@@ -32,6 +32,7 @@ async function* run(opts, resolveResult) {
|
|
|
32
32
|
yield emit(opts, { type: 'step_start', step });
|
|
33
33
|
const calls = [];
|
|
34
34
|
let stepText = '';
|
|
35
|
+
let providerState;
|
|
35
36
|
let streamFailure;
|
|
36
37
|
// In promptJson mode a tool directive must not leak into the visible
|
|
37
38
|
// stream, so directive-looking text is withheld from `delta` events (still
|
|
@@ -65,6 +66,7 @@ async function* run(opts, resolveResult) {
|
|
|
65
66
|
if (event.type === 'error')
|
|
66
67
|
streamFailure = event.error;
|
|
67
68
|
if (event.type === 'done') {
|
|
69
|
+
providerState = event.result.providerState;
|
|
68
70
|
usage = usage ? mergeUsage(usage, event.result.usage) : event.result.usage;
|
|
69
71
|
if (toolMode === 'promptJson' && calls.length === 0)
|
|
70
72
|
calls.push(...callsFromPromptJson(stepText));
|
|
@@ -85,7 +87,7 @@ async function* run(opts, resolveResult) {
|
|
|
85
87
|
finalText = stepText;
|
|
86
88
|
messages.push(toolMode === 'promptJson'
|
|
87
89
|
? { role: 'assistant', content: stepText }
|
|
88
|
-
: { role: 'assistant', content: stepText, toolCalls: calls.length ? calls : undefined });
|
|
90
|
+
: { role: 'assistant', content: stepText, providerState, toolCalls: calls.length ? calls : undefined });
|
|
89
91
|
if (calls.length === 0)
|
|
90
92
|
return yield* yieldDone('finished');
|
|
91
93
|
for (const call of calls) {
|
|
@@ -26,7 +26,8 @@ class AnthropicAdapter {
|
|
|
26
26
|
}
|
|
27
27
|
async *stream(conn, req) {
|
|
28
28
|
const started = Date.now();
|
|
29
|
-
const
|
|
29
|
+
const nativeJson = modernModel(req.model);
|
|
30
|
+
const jsonMode = req.responseFormat?.type === 'json' && !nativeJson && !req.tools?.length;
|
|
30
31
|
let firstTokenMs = null;
|
|
31
32
|
let text = '';
|
|
32
33
|
let inputTokens;
|
|
@@ -34,6 +35,7 @@ class AnthropicAdapter {
|
|
|
34
35
|
let stopReason;
|
|
35
36
|
let sawTerminal = false;
|
|
36
37
|
const emittedTools = [];
|
|
38
|
+
const responseBlocks = new Map();
|
|
37
39
|
// Partial tool_use blocks keyed by content-block index (input_json_delta accumulation).
|
|
38
40
|
const blocks = new Map();
|
|
39
41
|
const timeout = (0, base_js_1.streamTimeout)(conn, req.signal);
|
|
@@ -41,19 +43,29 @@ class AnthropicAdapter {
|
|
|
41
43
|
// Anthropic JSON mode is implemented as a forced `json_output` tool, which
|
|
42
44
|
// cannot coexist with the caller's own tools — same silent-downgrade shape
|
|
43
45
|
// as Gemini, so it gets the same signal rather than quietly dropping.
|
|
44
|
-
if (req.responseFormat?.type === 'json' && req.tools?.length) {
|
|
46
|
+
if (req.responseFormat?.type === 'json' && !nativeJson && req.tools?.length) {
|
|
45
47
|
yield {
|
|
46
48
|
type: 'context',
|
|
47
49
|
kind: 'json_mode_downgraded',
|
|
48
50
|
data: { reason: 'Anthropic JSON mode uses a forced tool and cannot be combined with caller tools; the request was sent without JSON mode', provider: conn.provider },
|
|
49
51
|
};
|
|
50
52
|
}
|
|
53
|
+
if (req.responseFormat?.type === 'json' && nativeJson && !req.responseFormat.schema) {
|
|
54
|
+
yield { type: 'context', kind: 'json_mode_downgraded', data: { provider: conn.provider, reason: 'Claude native JSON outputs require a schema; using a JSON instruction without constrained decoding.' } };
|
|
55
|
+
}
|
|
51
56
|
try {
|
|
52
57
|
const res = await (0, transport_js_1.postResponse)(`${conn.baseUrl}/v1/messages`, body(req, jsonMode), conn.headers, timeout.signal, conn.provider);
|
|
53
58
|
const contentType = res.headers.get('content-type') ?? '';
|
|
54
59
|
if (!contentType.includes('text/event-stream')) {
|
|
55
60
|
const data = await res.json();
|
|
56
61
|
const parsed = parseResponse(data);
|
|
62
|
+
const content = (0, util_js_1.asRecord)(data)?.content;
|
|
63
|
+
if (Array.isArray(content))
|
|
64
|
+
content.forEach((block, i) => {
|
|
65
|
+
const record = (0, util_js_1.asRecord)(block);
|
|
66
|
+
if (record)
|
|
67
|
+
responseBlocks.set(i, record);
|
|
68
|
+
});
|
|
57
69
|
inputTokens = parsed.inputTokens;
|
|
58
70
|
outputTokens = parsed.outputTokens;
|
|
59
71
|
stopReason = parsed.stopReason;
|
|
@@ -80,7 +92,12 @@ class AnthropicAdapter {
|
|
|
80
92
|
if (!record)
|
|
81
93
|
continue;
|
|
82
94
|
const type = (0, util_js_1.asString)(record.type);
|
|
83
|
-
if (type === '
|
|
95
|
+
if (type === 'error') {
|
|
96
|
+
const error = (0, util_js_1.asRecord)(record.error);
|
|
97
|
+
const statuses = { overloaded_error: 529, rate_limit_error: 429, authentication_error: 401, permission_error: 403, invalid_request_error: 400, not_found_error: 404, api_error: 500, billing_error: 402, timeout_error: 504, request_too_large: 413 };
|
|
98
|
+
throw (0, errors_js_1.classify)(statuses[(0, util_js_1.asString)(error?.type) ?? ''] ?? 500, JSON.stringify(record), conn.provider);
|
|
99
|
+
}
|
|
100
|
+
else if (type === 'message_start') {
|
|
84
101
|
const usage = (0, util_js_1.asRecord)((0, util_js_1.asRecord)(record.message)?.usage);
|
|
85
102
|
inputTokens = (0, util_js_1.asNumber)(usage?.input_tokens) ?? inputTokens;
|
|
86
103
|
outputTokens = (0, util_js_1.asNumber)(usage?.output_tokens) ?? outputTokens;
|
|
@@ -88,6 +105,8 @@ class AnthropicAdapter {
|
|
|
88
105
|
else if (type === 'content_block_start') {
|
|
89
106
|
const index = (0, util_js_1.asNumber)(record.index) ?? 0;
|
|
90
107
|
const block = (0, util_js_1.asRecord)(record.content_block);
|
|
108
|
+
if (block)
|
|
109
|
+
responseBlocks.set(index, { ...block });
|
|
91
110
|
if (block?.type === 'tool_use') {
|
|
92
111
|
blocks.set(index, { id: (0, util_js_1.asString)(block.id) ?? (0, base_js_1.randomId)(), name: (0, util_js_1.asString)(block.name) ?? 'unknown', raw: '' });
|
|
93
112
|
}
|
|
@@ -95,6 +114,14 @@ class AnthropicAdapter {
|
|
|
95
114
|
else if (type === 'content_block_delta') {
|
|
96
115
|
const index = (0, util_js_1.asNumber)(record.index) ?? 0;
|
|
97
116
|
const delta = (0, util_js_1.asRecord)(record.delta);
|
|
117
|
+
const block = responseBlocks.get(index);
|
|
118
|
+
if (block && delta) {
|
|
119
|
+
const field = delta.type === 'text_delta' ? 'text' : delta.type === 'thinking_delta' ? 'thinking' : delta.type === 'signature_delta' ? 'signature' : undefined;
|
|
120
|
+
if (field)
|
|
121
|
+
block[field] = String(block[field] ?? '') + String(delta[field] ?? '');
|
|
122
|
+
if (delta.type === 'citations_delta')
|
|
123
|
+
block.citations = [...(Array.isArray(block.citations) ? block.citations : []), delta.citation];
|
|
124
|
+
}
|
|
98
125
|
if (delta?.type === 'text_delta') {
|
|
99
126
|
const piece = (0, util_js_1.asString)(delta.text) ?? '';
|
|
100
127
|
if (piece) {
|
|
@@ -121,6 +148,9 @@ class AnthropicAdapter {
|
|
|
121
148
|
const partial = blocks.get(index);
|
|
122
149
|
if (partial) {
|
|
123
150
|
const call = { id: partial.id, name: partial.name, raw: partial.raw, arguments: (0, base_js_1.parseArgs)(partial.raw) };
|
|
151
|
+
const block = responseBlocks.get(index);
|
|
152
|
+
if (block)
|
|
153
|
+
block.input = call.arguments;
|
|
124
154
|
blocks.delete(index);
|
|
125
155
|
if (jsonMode && call.name === JSON_MODE_TOOL) {
|
|
126
156
|
text = jsonTextFrom([call], text);
|
|
@@ -152,6 +182,7 @@ class AnthropicAdapter {
|
|
|
152
182
|
const hasTools = emittedTools.length > 0;
|
|
153
183
|
yield { type: 'done', result: {
|
|
154
184
|
text,
|
|
185
|
+
providerState: { provider: conn.provider, model: req.model, content: [...responseBlocks.entries()].sort(([a], [b]) => a - b).map(([, block]) => block) },
|
|
155
186
|
toolCalls: hasTools ? emittedTools : undefined,
|
|
156
187
|
finishReason: mapStop(stopReason, hasTools),
|
|
157
188
|
usage: inputTokens !== undefined || outputTokens !== undefined ? { inputTokens, outputTokens, estimated: false } : (0, tokens_js_1.estimatedUsage)((0, util_js_1.textFromMessages)(req.messages), text),
|
|
@@ -196,9 +227,15 @@ function body(req, jsonMode) {
|
|
|
196
227
|
top_p: req.topP,
|
|
197
228
|
stop_sequences: req.stopSequences,
|
|
198
229
|
system,
|
|
199
|
-
messages: toAnthropicMessages(req.messages.filter((m) => m.role !== 'system')),
|
|
230
|
+
messages: toAnthropicMessages(req.messages.filter((m) => m.role !== 'system'), req.model),
|
|
200
231
|
stream: true,
|
|
201
232
|
};
|
|
233
|
+
if (req.responseFormat?.type === 'json' && modernModel(req.model) && req.responseFormat.schema) {
|
|
234
|
+
base.output_config = { format: { type: 'json_schema', schema: schemaFor(req) } };
|
|
235
|
+
}
|
|
236
|
+
if (req.responseFormat?.type === 'json' && modernModel(req.model) && !req.responseFormat.schema) {
|
|
237
|
+
base.system = [system, 'Return only valid JSON, without markdown fences or a preamble.'].filter(Boolean).join('\n\n');
|
|
238
|
+
}
|
|
202
239
|
if (jsonMode) {
|
|
203
240
|
// Forced-tool JSON mode: Anthropic has no response_format, so we make the
|
|
204
241
|
// model answer through a single required tool whose input is the JSON we want.
|
|
@@ -219,7 +256,14 @@ function body(req, jsonMode) {
|
|
|
219
256
|
// `budget_tokens < max_tokens` and rejects temperature/top_p alongside
|
|
220
257
|
// thinking, so the budget is fitted under max_tokens (raising max_tokens
|
|
221
258
|
// when the caller's value leaves no room) and the samplers are dropped.
|
|
222
|
-
if (req.
|
|
259
|
+
if (modernModel(req.model)) {
|
|
260
|
+
const none = req.reasoningEffort === 'none';
|
|
261
|
+
if (none && alwaysThinking(req.model))
|
|
262
|
+
throw new errors_js_1.AIError('This Claude model cannot disable thinking; choose a reasoning effort instead.', { kind: 'invalid_request', provider: 'anthropic' });
|
|
263
|
+
base.thinking = { type: none ? (/^claude-sonnet-5-5(?:-|$)/.test(req.model) ? 'between_tools' : 'disabled') : 'adaptive' };
|
|
264
|
+
base.output_config = { ...(0, util_js_1.asRecord)(base.output_config), effort: none ? 'low' : req.reasoningEffort === 'minimal' ? 'low' : req.reasoningEffort };
|
|
265
|
+
}
|
|
266
|
+
else if (req.reasoningEffort === 'none') {
|
|
223
267
|
base.thinking = { type: 'disabled' };
|
|
224
268
|
}
|
|
225
269
|
else {
|
|
@@ -234,14 +278,24 @@ function body(req, jsonMode) {
|
|
|
234
278
|
}
|
|
235
279
|
// providerOptions carries Anthropic-native fields (`thinking`, `metadata`,
|
|
236
280
|
// top-level `cache_control` extras, `service_tier`, …) without a release.
|
|
237
|
-
|
|
281
|
+
const result = (0, util_js_1.applyProviderOptions)(base, req.providerOptions, ['output_config']);
|
|
282
|
+
if (modernModel(req.model)) {
|
|
283
|
+
delete result.temperature;
|
|
284
|
+
delete result.top_p;
|
|
285
|
+
delete result.top_k;
|
|
286
|
+
}
|
|
287
|
+
const choice = (0, util_js_1.asRecord)(result.tool_choice);
|
|
288
|
+
if (noForcedTools(req.model) && (choice?.type === 'tool' || choice?.type === 'any')) {
|
|
289
|
+
throw new errors_js_1.AIError('This Claude model does not support forced tool choice; use auto or structured outputs.', { kind: 'invalid_request', provider: 'anthropic' });
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
238
292
|
}
|
|
239
|
-
function toAnthropicMessages(messages) {
|
|
293
|
+
function toAnthropicMessages(messages, model) {
|
|
240
294
|
const out = [];
|
|
241
295
|
for (let index = 0; index < messages.length; index += 1) {
|
|
242
296
|
const message = messages[index];
|
|
243
297
|
if (message.role !== 'tool') {
|
|
244
|
-
out.push(toAnthropicMessage(message));
|
|
298
|
+
out.push(toAnthropicMessage(message, model));
|
|
245
299
|
continue;
|
|
246
300
|
}
|
|
247
301
|
const content = [];
|
|
@@ -255,7 +309,10 @@ function toAnthropicMessages(messages) {
|
|
|
255
309
|
}
|
|
256
310
|
return out;
|
|
257
311
|
}
|
|
258
|
-
function toAnthropicMessage(m) {
|
|
312
|
+
function toAnthropicMessage(m, model) {
|
|
313
|
+
if (m.role === 'assistant' && m.providerState?.provider === 'anthropic' && m.providerState.model === model) {
|
|
314
|
+
return { role: 'assistant', content: m.providerState.content };
|
|
315
|
+
}
|
|
259
316
|
// Tool-role messages are handled by toAnthropicMessages' batching loop and
|
|
260
317
|
// never reach here, so no tool branch is needed.
|
|
261
318
|
// assistant turns that carried tool calls replay them as tool_use blocks,
|
|
@@ -320,7 +377,20 @@ function jsonTextFrom(toolCalls, fallback) {
|
|
|
320
377
|
function mapStop(stop, hasTools) {
|
|
321
378
|
if (stop === 'tool_use' && hasTools)
|
|
322
379
|
return 'tool';
|
|
380
|
+
if (stop === 'refusal')
|
|
381
|
+
return 'content_filter';
|
|
323
382
|
if (stop === 'max_tokens')
|
|
324
383
|
return 'length';
|
|
325
384
|
return 'stop';
|
|
326
385
|
}
|
|
386
|
+
/** Models released after Opus 4.6 use adaptive effort and reject sampling knobs.
|
|
387
|
+
* Unknown/custom IDs retain legacy behavior rather than guessing capabilities. */
|
|
388
|
+
function modernModel(model) {
|
|
389
|
+
return /^claude-(?:opus-(?:4-[78]|5(?:-5)?)|sonnet-5(?:-5)?|haiku-5-5|(?:fable|mythos)-5(?:-1)?|mythos-preview)(?:-\d{8}|-latest)?$/.test(model);
|
|
390
|
+
}
|
|
391
|
+
function alwaysThinking(model) {
|
|
392
|
+
return /^claude-(?:fable-|mythos-|opus-5-5(?:-|$))/.test(model);
|
|
393
|
+
}
|
|
394
|
+
function noForcedTools(model) {
|
|
395
|
+
return /^claude-(?:(?:opus|sonnet)-5-5(?:-|$)|(?:fable|mythos)-5-1(?:-|$))/.test(model);
|
|
396
|
+
}
|
package/dist/cjs/agent/loop.js
CHANGED
|
@@ -35,6 +35,7 @@ async function* run(opts, resolveResult) {
|
|
|
35
35
|
yield emit(opts, { type: 'step_start', step });
|
|
36
36
|
const calls = [];
|
|
37
37
|
let stepText = '';
|
|
38
|
+
let providerState;
|
|
38
39
|
let streamFailure;
|
|
39
40
|
// In promptJson mode a tool directive must not leak into the visible
|
|
40
41
|
// stream, so directive-looking text is withheld from `delta` events (still
|
|
@@ -68,6 +69,7 @@ async function* run(opts, resolveResult) {
|
|
|
68
69
|
if (event.type === 'error')
|
|
69
70
|
streamFailure = event.error;
|
|
70
71
|
if (event.type === 'done') {
|
|
72
|
+
providerState = event.result.providerState;
|
|
71
73
|
usage = usage ? (0, tokens_js_1.mergeUsage)(usage, event.result.usage) : event.result.usage;
|
|
72
74
|
if (toolMode === 'promptJson' && calls.length === 0)
|
|
73
75
|
calls.push(...callsFromPromptJson(stepText));
|
|
@@ -88,7 +90,7 @@ async function* run(opts, resolveResult) {
|
|
|
88
90
|
finalText = stepText;
|
|
89
91
|
messages.push(toolMode === 'promptJson'
|
|
90
92
|
? { role: 'assistant', content: stepText }
|
|
91
|
-
: { role: 'assistant', content: stepText, toolCalls: calls.length ? calls : undefined });
|
|
93
|
+
: { role: 'assistant', content: stepText, providerState, toolCalls: calls.length ? calls : undefined });
|
|
92
94
|
if (calls.length === 0)
|
|
93
95
|
return yield* yieldDone('finished');
|
|
94
96
|
for (const call of calls) {
|
package/dist/cjs/handler.js
CHANGED
|
@@ -86,7 +86,7 @@ class AIHandler {
|
|
|
86
86
|
}
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
|
-
if (event.type === 'delta' || event.type === 'tool_call')
|
|
89
|
+
if (event.type === 'delta' || event.type === 'tool_call' || event.type === 'reasoning')
|
|
90
90
|
emittedOutput = true;
|
|
91
91
|
if (event.type === 'delta' && stripper) {
|
|
92
92
|
// Inline reasoning never reaches `delta`; it rides the reasoning channel like a
|
package/dist/handler.js
CHANGED
|
@@ -82,7 +82,7 @@ export class AIHandler {
|
|
|
82
82
|
}
|
|
83
83
|
continue;
|
|
84
84
|
}
|
|
85
|
-
if (event.type === 'delta' || event.type === 'tool_call')
|
|
85
|
+
if (event.type === 'delta' || event.type === 'tool_call' || event.type === 'reasoning')
|
|
86
86
|
emittedOutput = true;
|
|
87
87
|
if (event.type === 'delta' && stripper) {
|
|
88
88
|
// Inline reasoning never reaches `delta`; it rides the reasoning channel like a
|
package/dist/types.d.ts
CHANGED
|
@@ -7,9 +7,17 @@ export interface ContentPart {
|
|
|
7
7
|
imageBase64?: string;
|
|
8
8
|
mimeType?: string;
|
|
9
9
|
}
|
|
10
|
+
/** Opaque provider response content, for replay within the same model/tool workflow.
|
|
11
|
+
* Keep it unmodified; never include it in telemetry or display it as answer text. */
|
|
12
|
+
export interface ProviderState {
|
|
13
|
+
provider: string;
|
|
14
|
+
model: string;
|
|
15
|
+
content: Record<string, unknown>[];
|
|
16
|
+
}
|
|
10
17
|
export interface ChatMessage {
|
|
11
18
|
role: Role;
|
|
12
19
|
content: string | ContentPart[];
|
|
20
|
+
providerState?: ProviderState;
|
|
13
21
|
toolCalls?: ToolCall[];
|
|
14
22
|
toolCallId?: string;
|
|
15
23
|
name?: string;
|
|
@@ -130,6 +138,7 @@ export interface Usage {
|
|
|
130
138
|
}
|
|
131
139
|
export interface ChatResult {
|
|
132
140
|
text: string;
|
|
141
|
+
providerState?: ProviderState;
|
|
133
142
|
toolCalls?: ToolCall[];
|
|
134
143
|
finishReason: 'stop' | 'length' | 'tool' | 'content_filter' | 'error' | 'canceled';
|
|
135
144
|
usage: Usage;
|
package/nugget/VERSION.txt
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
0.7.
|
|
2
|
-
|
|
1
|
+
0.7.3
|
|
2
|
+
f6bc628594f887826274e9019dff75be686d5043e9823ccdbb0a4d9765552752
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/adapters/engines/anthropic.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAe,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,kBAAkB,EAAE,WAAW,EAAY,MAAM,gBAAgB,CAAC;AAMlJ,qBAAa,gBAAiB,YAAW,eAAe;IACtD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,MAAM;IAItB,IAAI,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAUpE,MAAM,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/adapters/engines/anthropic.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAe,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,kBAAkB,EAAE,WAAW,EAAY,MAAM,gBAAgB,CAAC;AAMlJ,qBAAa,gBAAiB,YAAW,eAAe;IACtD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBACd,QAAQ,EAAE,MAAM;IAItB,IAAI,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAUpE,MAAM,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC;IAmJrF;;;;OAIG;IACG,UAAU,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAajE"}
|