@zenera/neo 1.1.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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/agent.d.ts +66 -0
- package/dist/agent.js +97 -0
- package/dist/architecture.d.ts +74 -0
- package/dist/architecture.js +105 -0
- package/dist/embedding.d.ts +60 -0
- package/dist/embedding.js +21 -0
- package/dist/embeddings/gemini.d.ts +24 -0
- package/dist/embeddings/gemini.js +71 -0
- package/dist/embeddings/index.d.ts +4 -0
- package/dist/embeddings/index.js +4 -0
- package/dist/embeddings/openai.d.ts +18 -0
- package/dist/embeddings/openai.js +27 -0
- package/dist/embeddings/openrouter.d.ts +21 -0
- package/dist/embeddings/openrouter.js +54 -0
- package/dist/events.d.ts +144 -0
- package/dist/events.js +110 -0
- package/dist/ids.d.ts +14 -0
- package/dist/ids.js +33 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/inspect/client.d.ts +2 -0
- package/dist/inspect/client.js +1326 -0
- package/dist/inspect/css.d.ts +2 -0
- package/dist/inspect/css.js +164 -0
- package/dist/inspect/index.d.ts +9 -0
- package/dist/inspect/index.js +12 -0
- package/dist/inspect/page.d.ts +3 -0
- package/dist/inspect/page.js +93 -0
- package/dist/inspect/report.d.ts +54 -0
- package/dist/inspect/report.js +62 -0
- package/dist/kernel.d.ts +178 -0
- package/dist/kernel.js +929 -0
- package/dist/memory-stores/factory.d.ts +15 -0
- package/dist/memory-stores/factory.js +29 -0
- package/dist/memory-stores/file.d.ts +37 -0
- package/dist/memory-stores/file.js +174 -0
- package/dist/memory-stores/in-memory.d.ts +17 -0
- package/dist/memory-stores/in-memory.js +80 -0
- package/dist/memory-stores/index.d.ts +4 -0
- package/dist/memory-stores/index.js +5 -0
- package/dist/memory.d.ts +115 -0
- package/dist/memory.js +213 -0
- package/dist/model.d.ts +25 -0
- package/dist/model.js +2 -0
- package/dist/models/anthropic.d.ts +36 -0
- package/dist/models/anthropic.js +293 -0
- package/dist/models/factory.d.ts +202 -0
- package/dist/models/factory.js +533 -0
- package/dist/models/gemini.d.ts +45 -0
- package/dist/models/gemini.js +368 -0
- package/dist/models/index.d.ts +7 -0
- package/dist/models/index.js +7 -0
- package/dist/models/openai-chat.d.ts +19 -0
- package/dist/models/openai-chat.js +203 -0
- package/dist/models/openai-responses.d.ts +31 -0
- package/dist/models/openai-responses.js +258 -0
- package/dist/models/openrouter.d.ts +44 -0
- package/dist/models/openrouter.js +230 -0
- package/dist/payload-stores/factory.d.ts +15 -0
- package/dist/payload-stores/factory.js +30 -0
- package/dist/payload-stores/file.d.ts +29 -0
- package/dist/payload-stores/file.js +112 -0
- package/dist/payload-stores/in-memory.d.ts +17 -0
- package/dist/payload-stores/in-memory.js +40 -0
- package/dist/payload-stores/index.d.ts +4 -0
- package/dist/payload-stores/index.js +5 -0
- package/dist/payload.d.ts +89 -0
- package/dist/payload.js +181 -0
- package/dist/project/config.d.ts +302 -0
- package/dist/project/config.js +290 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +4 -0
- package/dist/project/load.d.ts +142 -0
- package/dist/project/load.js +430 -0
- package/dist/project/refs.d.ts +23 -0
- package/dist/project/refs.js +70 -0
- package/dist/prompt.d.ts +66 -0
- package/dist/prompt.js +62 -0
- package/dist/runner.d.ts +103 -0
- package/dist/runner.js +537 -0
- package/dist/services.d.ts +23 -0
- package/dist/services.js +44 -0
- package/dist/skill-providers/factory.d.ts +16 -0
- package/dist/skill-providers/factory.js +26 -0
- package/dist/skill-providers/file.d.ts +44 -0
- package/dist/skill-providers/file.js +209 -0
- package/dist/skill-providers/index.d.ts +4 -0
- package/dist/skill-providers/index.js +5 -0
- package/dist/skill-providers/static.d.ts +13 -0
- package/dist/skill-providers/static.js +47 -0
- package/dist/skills.d.ts +102 -0
- package/dist/skills.js +232 -0
- package/dist/state.d.ts +86 -0
- package/dist/state.js +49 -0
- package/dist/tools/exa.d.ts +23 -0
- package/dist/tools/exa.js +389 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/sandbox.d.ts +172 -0
- package/dist/tools/sandbox.js +602 -0
- package/dist/tools/workspace.d.ts +53 -0
- package/dist/tools/workspace.js +1034 -0
- package/dist/trajectory.d.ts +213 -0
- package/dist/trajectory.js +232 -0
- package/dist/types.d.ts +195 -0
- package/dist/types.js +134 -0
- package/package.json +98 -0
package/dist/memory.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { hash } from "./payload.js";
|
|
2
|
+
import { MEMORY_DELETE_TOOL, MEMORY_SEARCH_TOOL, MEMORY_UPDATE_TOOL, MEMORY_WRITE_TOOL, tool, withEffects, } from "./types.js";
|
|
3
|
+
export function memoryOpId(runId, callId) {
|
|
4
|
+
return hash(`${runId}\u0000${callId}`);
|
|
5
|
+
}
|
|
6
|
+
export function renderMemories(hits) {
|
|
7
|
+
if (!hits.length) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
const lines = hits.map((h) => `- [${h.record.id}] (${h.record.kind}, score ${h.score.toFixed(2)}) ${h.record.text}`);
|
|
11
|
+
return `Relevant memories:\n${lines.join('\n')}`;
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Shared ranking
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Filtering plus token-overlap scoring, shared by the backends that have no
|
|
18
|
+
* embeddings of their own. A vector store ignores this and ranks in the
|
|
19
|
+
* database instead — the kernel only ever sees `MemoryHit[]`.
|
|
20
|
+
*/
|
|
21
|
+
export function rankMemories(records, q) {
|
|
22
|
+
const limit = q.limit ?? 8;
|
|
23
|
+
const terms = tokenize(q.text ?? '');
|
|
24
|
+
const hits = [];
|
|
25
|
+
for (const rec of records) {
|
|
26
|
+
if (q.kind && rec.kind !== q.kind) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (q.filter && !matches(rec.metadata, q.filter)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const score = terms.length ? overlap(terms, tokenize(rec.text)) : 1;
|
|
33
|
+
if (score <= 0 || (q.minScore !== undefined && score < q.minScore)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
hits.push({ record: rec, score });
|
|
37
|
+
}
|
|
38
|
+
return hits.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
39
|
+
}
|
|
40
|
+
function tokenize(v) {
|
|
41
|
+
return v
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.split(/[^a-z0-9]+/)
|
|
44
|
+
.filter((t) => t.length > 2);
|
|
45
|
+
}
|
|
46
|
+
function overlap(a, b) {
|
|
47
|
+
const set = new Set(b);
|
|
48
|
+
const shared = a.filter((t) => set.has(t)).length;
|
|
49
|
+
return a.length ? shared / a.length : 0;
|
|
50
|
+
}
|
|
51
|
+
function matches(meta, filter) {
|
|
52
|
+
return Object.entries(filter).every(([k, v]) => meta?.[k] === v);
|
|
53
|
+
}
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Built-in memory tools
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
function spaceParam(bindings) {
|
|
58
|
+
return bindings.length > 1
|
|
59
|
+
? { type: 'string', enum: bindings.map((b) => b.scope), description: 'memory space' }
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
62
|
+
function pick(bindings, space) {
|
|
63
|
+
const b = space ? bindings.find((x) => x.scope === space) : bindings[0];
|
|
64
|
+
if (!b) {
|
|
65
|
+
throw new Error(`unknown memory space: ${space ?? '(default)'}`);
|
|
66
|
+
}
|
|
67
|
+
return b;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Memory is reachable both explicitly (these tools) and implicitly (auto-recall
|
|
71
|
+
* in the runner). Both land in the trajectory; only the explicit path is
|
|
72
|
+
* visible to the model as a tool.
|
|
73
|
+
*/
|
|
74
|
+
export function memoryTools(bindings) {
|
|
75
|
+
if (!bindings.length) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
const writable = bindings.filter((b) => b.access === 'read-write');
|
|
79
|
+
const space = spaceParam(bindings);
|
|
80
|
+
const withSpace = (props) => (space ? { ...props, space } : props);
|
|
81
|
+
const tools = [
|
|
82
|
+
tool({
|
|
83
|
+
name: MEMORY_SEARCH_TOOL,
|
|
84
|
+
description: 'Search long-lived memories.',
|
|
85
|
+
parameters: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: withSpace({
|
|
88
|
+
query: { type: 'string' },
|
|
89
|
+
kind: { type: 'string' },
|
|
90
|
+
limit: { type: 'integer' },
|
|
91
|
+
}),
|
|
92
|
+
required: ['query'],
|
|
93
|
+
additionalProperties: false,
|
|
94
|
+
},
|
|
95
|
+
execute: async (args, tc) => {
|
|
96
|
+
const b = pick(bindings, args.space);
|
|
97
|
+
const store = tc.services.memoryStore(b.store);
|
|
98
|
+
const hits = await store.search(b.scope, {
|
|
99
|
+
text: args.query,
|
|
100
|
+
kind: args.kind,
|
|
101
|
+
limit: args.limit,
|
|
102
|
+
});
|
|
103
|
+
return hits.map((h) => ({
|
|
104
|
+
id: h.record.id,
|
|
105
|
+
kind: h.record.kind,
|
|
106
|
+
text: h.record.text,
|
|
107
|
+
revision: h.record.revision,
|
|
108
|
+
score: Number(h.score.toFixed(3)),
|
|
109
|
+
}));
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
];
|
|
113
|
+
if (!writable.length) {
|
|
114
|
+
return tools;
|
|
115
|
+
}
|
|
116
|
+
const wSpace = spaceParam(writable);
|
|
117
|
+
const withWSpace = (props) => wSpace ? { ...props, space: wSpace } : props;
|
|
118
|
+
tools.push(tool({
|
|
119
|
+
name: MEMORY_WRITE_TOOL,
|
|
120
|
+
description: 'Store a new long-lived memory.',
|
|
121
|
+
parameters: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: withWSpace({ text: { type: 'string' }, kind: { type: 'string' } }),
|
|
124
|
+
required: ['text'],
|
|
125
|
+
additionalProperties: false,
|
|
126
|
+
},
|
|
127
|
+
execute: async (args, tc) => {
|
|
128
|
+
const b = pick(writable, args.space);
|
|
129
|
+
const opId = memoryOpId(tc.state.runId, tc.callId);
|
|
130
|
+
const rec = await tc.services
|
|
131
|
+
.memoryStore(b.store)
|
|
132
|
+
.write(b.scope, { text: args.text, kind: args.kind }, opId);
|
|
133
|
+
return withEffects(`stored memory ${rec.id}`, {
|
|
134
|
+
kind: 'memory_op',
|
|
135
|
+
spec: {
|
|
136
|
+
kind: 'op',
|
|
137
|
+
op: 'write',
|
|
138
|
+
store: b.store,
|
|
139
|
+
scope: b.scope,
|
|
140
|
+
opId,
|
|
141
|
+
recordId: rec.id,
|
|
142
|
+
revision: rec.revision,
|
|
143
|
+
after: rec.text,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
}), tool({
|
|
148
|
+
name: MEMORY_UPDATE_TOOL,
|
|
149
|
+
description: 'Update an existing memory by id.',
|
|
150
|
+
parameters: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: withWSpace({
|
|
153
|
+
id: { type: 'string' },
|
|
154
|
+
text: { type: 'string' },
|
|
155
|
+
kind: { type: 'string' },
|
|
156
|
+
}),
|
|
157
|
+
required: ['id'],
|
|
158
|
+
additionalProperties: false,
|
|
159
|
+
},
|
|
160
|
+
execute: async (args, tc) => {
|
|
161
|
+
const b = pick(writable, args.space);
|
|
162
|
+
const store = tc.services.memoryStore(b.store);
|
|
163
|
+
const before = await store.get(b.scope, args.id);
|
|
164
|
+
const opId = memoryOpId(tc.state.runId, tc.callId);
|
|
165
|
+
const rec = await store.update(b.scope, args.id, { text: args.text, kind: args.kind }, opId);
|
|
166
|
+
return withEffects(`updated memory ${rec.id}`, {
|
|
167
|
+
kind: 'memory_op',
|
|
168
|
+
spec: {
|
|
169
|
+
kind: 'op',
|
|
170
|
+
op: 'update',
|
|
171
|
+
store: b.store,
|
|
172
|
+
scope: b.scope,
|
|
173
|
+
opId,
|
|
174
|
+
recordId: rec.id,
|
|
175
|
+
revision: rec.revision,
|
|
176
|
+
before: before?.text,
|
|
177
|
+
after: rec.text,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
}), tool({
|
|
182
|
+
name: MEMORY_DELETE_TOOL,
|
|
183
|
+
description: 'Delete a memory by id.',
|
|
184
|
+
parameters: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: withWSpace({ id: { type: 'string' } }),
|
|
187
|
+
required: ['id'],
|
|
188
|
+
additionalProperties: false,
|
|
189
|
+
},
|
|
190
|
+
execute: async (args, tc) => {
|
|
191
|
+
const b = pick(writable, args.space);
|
|
192
|
+
const store = tc.services.memoryStore(b.store);
|
|
193
|
+
const before = await store.get(b.scope, args.id);
|
|
194
|
+
const opId = memoryOpId(tc.state.runId, tc.callId);
|
|
195
|
+
await store.delete(b.scope, args.id, opId);
|
|
196
|
+
return withEffects(`deleted memory ${args.id}`, {
|
|
197
|
+
kind: 'memory_op',
|
|
198
|
+
spec: {
|
|
199
|
+
kind: 'op',
|
|
200
|
+
op: 'delete',
|
|
201
|
+
store: b.store,
|
|
202
|
+
scope: b.scope,
|
|
203
|
+
opId,
|
|
204
|
+
recordId: args.id,
|
|
205
|
+
revision: before?.revision ?? 0,
|
|
206
|
+
before: before?.text,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
return tools;
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=memory.js.map
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { StreamDelta } from './events.ts';
|
|
2
|
+
import type { Message, TokenUsage, ToolCall, ToolSchema } from './types.ts';
|
|
3
|
+
export type StopReason = 'stop' | 'tool_calls' | 'length' | 'content_filter';
|
|
4
|
+
export interface ModelRequest {
|
|
5
|
+
system?: string;
|
|
6
|
+
messages: Message[];
|
|
7
|
+
tools: ToolSchema[];
|
|
8
|
+
toolChoice?: 'auto' | 'required' | 'none';
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
}
|
|
11
|
+
export interface ModelResponse {
|
|
12
|
+
text: string;
|
|
13
|
+
/** reasoning chain, when the provider returns one */
|
|
14
|
+
thinking?: string;
|
|
15
|
+
toolCalls: ToolCall[];
|
|
16
|
+
usage?: TokenUsage;
|
|
17
|
+
stopReason?: StopReason;
|
|
18
|
+
}
|
|
19
|
+
export interface Model {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
generate(req: ModelRequest): Promise<ModelResponse>;
|
|
22
|
+
/** optional: non-streaming models simply never produce deltas */
|
|
23
|
+
stream?(req: ModelRequest, onDelta: (d: StreamDelta) => void): Promise<ModelResponse>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=model.d.ts.map
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import type { StreamDelta } from '../events.ts';
|
|
3
|
+
import type { Model, ModelRequest, ModelResponse } from '../model.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Provider-specific knobs.
|
|
6
|
+
*
|
|
7
|
+
* `maxTokens` has no counterpart in the OpenAI adapters because there it is
|
|
8
|
+
* optional; Anthropic *requires* an output cap on every request, so one has to
|
|
9
|
+
* exist. 8192 is the largest value every current Claude model accepts, which
|
|
10
|
+
* makes it the only safe default — raise it per model when you know better.
|
|
11
|
+
*/
|
|
12
|
+
export interface AnthropicModelOptions {
|
|
13
|
+
/** hard cap on output tokens; required by the API, so it always has a value */
|
|
14
|
+
maxTokens?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Extended thinking budget, in tokens (≥1024 enables it). Off by default,
|
|
17
|
+
* and deliberately so — see the note on `#params` about tool use.
|
|
18
|
+
*/
|
|
19
|
+
thinkingBudgetTokens?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The `Model` contract over `/v1/messages`.
|
|
23
|
+
*
|
|
24
|
+
* Unlike Google, Anthropic is not worth reaching through an OpenAI-compatible
|
|
25
|
+
* shim: the compatibility layer they publish is explicitly a porting aid, and
|
|
26
|
+
* it drops the two things this runtime cares most about — accurate cache
|
|
27
|
+
* accounting and extended thinking. A real adapter is ~200 lines and gets both.
|
|
28
|
+
*/
|
|
29
|
+
export declare class AnthropicModel implements Model {
|
|
30
|
+
#private;
|
|
31
|
+
readonly id: string;
|
|
32
|
+
constructor(id: string, client: Anthropic, options?: AnthropicModelOptions);
|
|
33
|
+
generate(req: ModelRequest): Promise<ModelResponse>;
|
|
34
|
+
stream(req: ModelRequest, onDelta: (d: StreamDelta) => void): Promise<ModelResponse>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { zeroUsage, } from "../types.js";
|
|
2
|
+
/** Every current Claude model accepts at least this many output tokens. */
|
|
3
|
+
const DEFAULT_MAX_TOKENS = 8192;
|
|
4
|
+
/** The API's floor for a thinking budget; below it the request is rejected. */
|
|
5
|
+
const MIN_THINKING_BUDGET = 1024;
|
|
6
|
+
/**
|
|
7
|
+
* The `Model` contract over `/v1/messages`.
|
|
8
|
+
*
|
|
9
|
+
* Unlike Google, Anthropic is not worth reaching through an OpenAI-compatible
|
|
10
|
+
* shim: the compatibility layer they publish is explicitly a porting aid, and
|
|
11
|
+
* it drops the two things this runtime cares most about — accurate cache
|
|
12
|
+
* accounting and extended thinking. A real adapter is ~200 lines and gets both.
|
|
13
|
+
*/
|
|
14
|
+
export class AnthropicModel {
|
|
15
|
+
id;
|
|
16
|
+
#client;
|
|
17
|
+
#maxTokens;
|
|
18
|
+
#thinkingBudget;
|
|
19
|
+
constructor(id, client, options = {}) {
|
|
20
|
+
this.id = id;
|
|
21
|
+
this.#client = client;
|
|
22
|
+
this.#thinkingBudget =
|
|
23
|
+
options.thinkingBudgetTokens && options.thinkingBudgetTokens >= MIN_THINKING_BUDGET
|
|
24
|
+
? options.thinkingBudgetTokens
|
|
25
|
+
: undefined;
|
|
26
|
+
// The budget is spent *out of* the output cap, so a cap at or below it
|
|
27
|
+
// leaves no room to answer in.
|
|
28
|
+
this.#maxTokens = Math.max(options.maxTokens ?? DEFAULT_MAX_TOKENS, (this.#thinkingBudget ?? 0) + MIN_THINKING_BUDGET);
|
|
29
|
+
}
|
|
30
|
+
async generate(req) {
|
|
31
|
+
const res = await this.#client.messages.create(this.#params(req), { signal: req.signal });
|
|
32
|
+
return {
|
|
33
|
+
...readBlocks(res.content),
|
|
34
|
+
usage: toUsage(res.usage),
|
|
35
|
+
stopReason: toStopReason(res.stop_reason),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async stream(req, onDelta) {
|
|
39
|
+
const stream = this.#client.messages.stream(this.#params(req), { signal: req.signal });
|
|
40
|
+
// Blocks are addressed by index, and a tool call's id and name arrive in
|
|
41
|
+
// its `content_block_start` while the arguments trickle in afterwards.
|
|
42
|
+
const calls = new Map();
|
|
43
|
+
for await (const event of stream) {
|
|
44
|
+
if (event.type === 'content_block_start') {
|
|
45
|
+
const block = event.content_block;
|
|
46
|
+
if (block.type === 'tool_use') {
|
|
47
|
+
calls.set(event.index, { id: block.id, name: block.name, args: '' });
|
|
48
|
+
onDelta({ type: 'tool_call_detected', callId: block.id, name: block.name });
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (event.type !== 'content_block_delta') {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const delta = event.delta;
|
|
56
|
+
switch (delta.type) {
|
|
57
|
+
case 'text_delta':
|
|
58
|
+
onDelta({ type: 'text_delta', delta: delta.text });
|
|
59
|
+
break;
|
|
60
|
+
case 'thinking_delta':
|
|
61
|
+
onDelta({ type: 'thinking_delta', delta: delta.thinking });
|
|
62
|
+
break;
|
|
63
|
+
case 'input_json_delta': {
|
|
64
|
+
const call = calls.get(event.index);
|
|
65
|
+
if (call && delta.partial_json) {
|
|
66
|
+
call.args += delta.partial_json;
|
|
67
|
+
onDelta({
|
|
68
|
+
type: 'tool_args_delta',
|
|
69
|
+
callId: call.id,
|
|
70
|
+
name: call.name,
|
|
71
|
+
delta: delta.partial_json,
|
|
72
|
+
argsSoFar: call.args,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// The accumulated message is authoritative: it has the parsed tool
|
|
80
|
+
// inputs and the final usage, so the deltas above are for the UI only
|
|
81
|
+
// and nothing here depends on having reassembled them correctly.
|
|
82
|
+
const final = await stream.finalMessage();
|
|
83
|
+
return {
|
|
84
|
+
...readBlocks(final.content),
|
|
85
|
+
usage: toUsage(final.usage),
|
|
86
|
+
stopReason: toStopReason(final.stop_reason),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Note what is *not* here: prior thinking blocks are never replayed. The
|
|
91
|
+
* API requires the original block, signature included, when extended
|
|
92
|
+
* thinking is on and the turn being replayed made tool calls — and a
|
|
93
|
+
* signature is not something the trajectory carries, since it is meaningless
|
|
94
|
+
* to every other provider. So `thinkingBudgetTokens` is safe for plain
|
|
95
|
+
* conversation and will be rejected by the API on a tool-using multi-turn
|
|
96
|
+
* run. Preserving signatures would mean a provider-specific field on
|
|
97
|
+
* `AssistantMessage`, which is a trajectory change, not an adapter change.
|
|
98
|
+
*/
|
|
99
|
+
#params(req) {
|
|
100
|
+
return {
|
|
101
|
+
model: this.id,
|
|
102
|
+
max_tokens: this.#maxTokens,
|
|
103
|
+
system: req.system,
|
|
104
|
+
messages: toMessages(req.messages),
|
|
105
|
+
thinking: this.#thinkingBudget
|
|
106
|
+
? { type: 'enabled', budget_tokens: this.#thinkingBudget }
|
|
107
|
+
: undefined,
|
|
108
|
+
tools: req.tools.length
|
|
109
|
+
? req.tools.map((t) => ({
|
|
110
|
+
name: t.name,
|
|
111
|
+
description: t.description,
|
|
112
|
+
input_schema: t.parameters,
|
|
113
|
+
}))
|
|
114
|
+
: undefined,
|
|
115
|
+
tool_choice: req.tools.length ? toToolChoice(req.toolChoice) : undefined,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Wire translation
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Internal messages → Anthropic turns.
|
|
124
|
+
*
|
|
125
|
+
* Two things differ from the OpenAI shape and both are handled by merging
|
|
126
|
+
* adjacent same-role turns: a tool result is a *user* turn rather than a role
|
|
127
|
+
* of its own, and parallel tool results have to arrive together in one turn
|
|
128
|
+
* rather than as one message each.
|
|
129
|
+
*/
|
|
130
|
+
function toMessages(messages) {
|
|
131
|
+
const out = [];
|
|
132
|
+
const push = (role, blocks) => {
|
|
133
|
+
if (!blocks.length) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const last = out.at(-1);
|
|
137
|
+
if (last?.role === role) {
|
|
138
|
+
last.content.push(...blocks);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
out.push({ role, content: blocks });
|
|
142
|
+
};
|
|
143
|
+
for (const m of messages) {
|
|
144
|
+
switch (m.role) {
|
|
145
|
+
case 'system':
|
|
146
|
+
// There is no mid-conversation system turn in this API, and
|
|
147
|
+
// hoisting it to the top-level `system` would move it away from
|
|
148
|
+
// the point it was injected at — which is the whole value of a
|
|
149
|
+
// memory recall. A user turn keeps the position.
|
|
150
|
+
push('user', [{ type: 'text', text: m.content }]);
|
|
151
|
+
break;
|
|
152
|
+
case 'user':
|
|
153
|
+
push('user', m.content.map(toBlock));
|
|
154
|
+
break;
|
|
155
|
+
case 'assistant': {
|
|
156
|
+
const blocks = [];
|
|
157
|
+
if (m.content) {
|
|
158
|
+
blocks.push({ type: 'text', text: m.content });
|
|
159
|
+
}
|
|
160
|
+
for (const c of m.toolCalls ?? []) {
|
|
161
|
+
blocks.push({
|
|
162
|
+
type: 'tool_use',
|
|
163
|
+
id: c.id,
|
|
164
|
+
name: c.name,
|
|
165
|
+
input: parseArgs(c.args),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
push('assistant', blocks);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'tool':
|
|
172
|
+
push('user', [
|
|
173
|
+
{
|
|
174
|
+
type: 'tool_result',
|
|
175
|
+
tool_use_id: m.callId,
|
|
176
|
+
content: m.content,
|
|
177
|
+
is_error: m.isError,
|
|
178
|
+
},
|
|
179
|
+
]);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
/** Only images have a first-class representation; other media degrade to text. */
|
|
186
|
+
function toBlock(part) {
|
|
187
|
+
if (part.type === 'text') {
|
|
188
|
+
return { type: 'text', text: part.text };
|
|
189
|
+
}
|
|
190
|
+
if (part.type !== 'image') {
|
|
191
|
+
return { type: 'text', text: `[${part.type}] ${part.url}` };
|
|
192
|
+
}
|
|
193
|
+
const inline = readDataUri(part.url);
|
|
194
|
+
return inline
|
|
195
|
+
? { type: 'image', source: { type: 'base64', ...inline } }
|
|
196
|
+
: { type: 'image', source: { type: 'url', url: part.url } };
|
|
197
|
+
}
|
|
198
|
+
const DATA_URI = /^data:([^;,]+);base64,(.*)$/s;
|
|
199
|
+
function readDataUri(url) {
|
|
200
|
+
const match = DATA_URI.exec(url);
|
|
201
|
+
if (!match) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
media_type: match[1],
|
|
206
|
+
data: match[2],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Tool arguments travel as the raw JSON string the model produced, because that
|
|
211
|
+
* is what every other provider wants back verbatim. Anthropic wants the parsed
|
|
212
|
+
* object, and a malformed one is the model's error to see, not a crash here.
|
|
213
|
+
*/
|
|
214
|
+
function parseArgs(args) {
|
|
215
|
+
if (!args.trim()) {
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
return JSON.parse(args);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return { _raw: args };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function toToolChoice(choice) {
|
|
226
|
+
switch (choice) {
|
|
227
|
+
case 'required':
|
|
228
|
+
return { type: 'any' };
|
|
229
|
+
case 'none':
|
|
230
|
+
return { type: 'none' };
|
|
231
|
+
default:
|
|
232
|
+
return { type: 'auto' };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function readBlocks(content) {
|
|
236
|
+
let text = '';
|
|
237
|
+
let thinking = '';
|
|
238
|
+
const toolCalls = [];
|
|
239
|
+
for (const block of content) {
|
|
240
|
+
switch (block.type) {
|
|
241
|
+
case 'text':
|
|
242
|
+
text += block.text;
|
|
243
|
+
break;
|
|
244
|
+
case 'thinking':
|
|
245
|
+
thinking += block.thinking;
|
|
246
|
+
break;
|
|
247
|
+
case 'tool_use':
|
|
248
|
+
toolCalls.push({
|
|
249
|
+
id: block.id,
|
|
250
|
+
name: block.name,
|
|
251
|
+
args: JSON.stringify(block.input ?? {}),
|
|
252
|
+
});
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { text, thinking: thinking || undefined, toolCalls };
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Anthropic reports `input_tokens` *excluding* what the cache served, while
|
|
260
|
+
* `TokenUsage.cachedInputTokens` is defined as a subset of `inputTokens`. So
|
|
261
|
+
* the cache figures are added back in rather than passed through, and a run
|
|
262
|
+
* that mixes providers still sums to a comparable number.
|
|
263
|
+
*/
|
|
264
|
+
function toUsage(u) {
|
|
265
|
+
if (!u) {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
const read = u.cache_read_input_tokens ?? 0;
|
|
269
|
+
const written = u.cache_creation_input_tokens ?? 0;
|
|
270
|
+
return {
|
|
271
|
+
...zeroUsage(),
|
|
272
|
+
inputTokens: u.input_tokens + read + written,
|
|
273
|
+
cachedInputTokens: read,
|
|
274
|
+
outputTokens: u.output_tokens,
|
|
275
|
+
// Thinking is billed as ordinary output and is not broken out, so
|
|
276
|
+
// claiming a number here would be inventing one.
|
|
277
|
+
reasoningTokens: 0,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function toStopReason(reason) {
|
|
281
|
+
switch (reason) {
|
|
282
|
+
case 'tool_use':
|
|
283
|
+
return 'tool_calls';
|
|
284
|
+
case 'max_tokens':
|
|
285
|
+
case 'model_context_window_exceeded':
|
|
286
|
+
return 'length';
|
|
287
|
+
case 'refusal':
|
|
288
|
+
return 'content_filter';
|
|
289
|
+
default:
|
|
290
|
+
return 'stop';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=anthropic.js.map
|