@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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/agent.d.ts +66 -0
  4. package/dist/agent.js +97 -0
  5. package/dist/architecture.d.ts +74 -0
  6. package/dist/architecture.js +105 -0
  7. package/dist/embedding.d.ts +60 -0
  8. package/dist/embedding.js +21 -0
  9. package/dist/embeddings/gemini.d.ts +24 -0
  10. package/dist/embeddings/gemini.js +71 -0
  11. package/dist/embeddings/index.d.ts +4 -0
  12. package/dist/embeddings/index.js +4 -0
  13. package/dist/embeddings/openai.d.ts +18 -0
  14. package/dist/embeddings/openai.js +27 -0
  15. package/dist/embeddings/openrouter.d.ts +21 -0
  16. package/dist/embeddings/openrouter.js +54 -0
  17. package/dist/events.d.ts +144 -0
  18. package/dist/events.js +110 -0
  19. package/dist/ids.d.ts +14 -0
  20. package/dist/ids.js +33 -0
  21. package/dist/index.d.ts +25 -0
  22. package/dist/index.js +25 -0
  23. package/dist/inspect/client.d.ts +2 -0
  24. package/dist/inspect/client.js +1326 -0
  25. package/dist/inspect/css.d.ts +2 -0
  26. package/dist/inspect/css.js +164 -0
  27. package/dist/inspect/index.d.ts +9 -0
  28. package/dist/inspect/index.js +12 -0
  29. package/dist/inspect/page.d.ts +3 -0
  30. package/dist/inspect/page.js +93 -0
  31. package/dist/inspect/report.d.ts +54 -0
  32. package/dist/inspect/report.js +62 -0
  33. package/dist/kernel.d.ts +178 -0
  34. package/dist/kernel.js +929 -0
  35. package/dist/memory-stores/factory.d.ts +15 -0
  36. package/dist/memory-stores/factory.js +29 -0
  37. package/dist/memory-stores/file.d.ts +37 -0
  38. package/dist/memory-stores/file.js +174 -0
  39. package/dist/memory-stores/in-memory.d.ts +17 -0
  40. package/dist/memory-stores/in-memory.js +80 -0
  41. package/dist/memory-stores/index.d.ts +4 -0
  42. package/dist/memory-stores/index.js +5 -0
  43. package/dist/memory.d.ts +115 -0
  44. package/dist/memory.js +213 -0
  45. package/dist/model.d.ts +25 -0
  46. package/dist/model.js +2 -0
  47. package/dist/models/anthropic.d.ts +36 -0
  48. package/dist/models/anthropic.js +293 -0
  49. package/dist/models/factory.d.ts +202 -0
  50. package/dist/models/factory.js +533 -0
  51. package/dist/models/gemini.d.ts +45 -0
  52. package/dist/models/gemini.js +368 -0
  53. package/dist/models/index.d.ts +7 -0
  54. package/dist/models/index.js +7 -0
  55. package/dist/models/openai-chat.d.ts +19 -0
  56. package/dist/models/openai-chat.js +203 -0
  57. package/dist/models/openai-responses.d.ts +31 -0
  58. package/dist/models/openai-responses.js +258 -0
  59. package/dist/models/openrouter.d.ts +44 -0
  60. package/dist/models/openrouter.js +230 -0
  61. package/dist/payload-stores/factory.d.ts +15 -0
  62. package/dist/payload-stores/factory.js +30 -0
  63. package/dist/payload-stores/file.d.ts +29 -0
  64. package/dist/payload-stores/file.js +112 -0
  65. package/dist/payload-stores/in-memory.d.ts +17 -0
  66. package/dist/payload-stores/in-memory.js +40 -0
  67. package/dist/payload-stores/index.d.ts +4 -0
  68. package/dist/payload-stores/index.js +5 -0
  69. package/dist/payload.d.ts +89 -0
  70. package/dist/payload.js +181 -0
  71. package/dist/project/config.d.ts +302 -0
  72. package/dist/project/config.js +290 -0
  73. package/dist/project/index.d.ts +4 -0
  74. package/dist/project/index.js +4 -0
  75. package/dist/project/load.d.ts +142 -0
  76. package/dist/project/load.js +430 -0
  77. package/dist/project/refs.d.ts +23 -0
  78. package/dist/project/refs.js +70 -0
  79. package/dist/prompt.d.ts +66 -0
  80. package/dist/prompt.js +62 -0
  81. package/dist/runner.d.ts +103 -0
  82. package/dist/runner.js +537 -0
  83. package/dist/services.d.ts +23 -0
  84. package/dist/services.js +44 -0
  85. package/dist/skill-providers/factory.d.ts +16 -0
  86. package/dist/skill-providers/factory.js +26 -0
  87. package/dist/skill-providers/file.d.ts +44 -0
  88. package/dist/skill-providers/file.js +209 -0
  89. package/dist/skill-providers/index.d.ts +4 -0
  90. package/dist/skill-providers/index.js +5 -0
  91. package/dist/skill-providers/static.d.ts +13 -0
  92. package/dist/skill-providers/static.js +47 -0
  93. package/dist/skills.d.ts +102 -0
  94. package/dist/skills.js +232 -0
  95. package/dist/state.d.ts +86 -0
  96. package/dist/state.js +49 -0
  97. package/dist/tools/exa.d.ts +23 -0
  98. package/dist/tools/exa.js +389 -0
  99. package/dist/tools/index.d.ts +4 -0
  100. package/dist/tools/index.js +6 -0
  101. package/dist/tools/sandbox.d.ts +172 -0
  102. package/dist/tools/sandbox.js +602 -0
  103. package/dist/tools/workspace.d.ts +53 -0
  104. package/dist/tools/workspace.js +1034 -0
  105. package/dist/trajectory.d.ts +213 -0
  106. package/dist/trajectory.js +232 -0
  107. package/dist/types.d.ts +195 -0
  108. package/dist/types.js +134 -0
  109. 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
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=model.js.map
@@ -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