@agentier/anthropic 0.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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Anthropic provider for the agenti framework.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export { anthropic } from './provider';
7
+ export type { AnthropicProviderConfig } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,250 @@
1
+ // src/mapper.ts
2
+ function toAnthropicMessages(messages) {
3
+ let system;
4
+ const result = [];
5
+ for (const msg of messages) {
6
+ if (msg.role === "system") {
7
+ system = msg.content ?? undefined;
8
+ continue;
9
+ }
10
+ if (msg.role === "user") {
11
+ result.push({ role: "user", content: msg.content ?? "" });
12
+ continue;
13
+ }
14
+ if (msg.role === "assistant") {
15
+ const content = [];
16
+ if (msg.content) {
17
+ content.push({ type: "text", text: msg.content });
18
+ }
19
+ if (msg.toolCalls) {
20
+ for (const tc of msg.toolCalls) {
21
+ content.push({
22
+ type: "tool_use",
23
+ id: tc.id,
24
+ name: tc.name,
25
+ input: tc.arguments
26
+ });
27
+ }
28
+ }
29
+ result.push({
30
+ role: "assistant",
31
+ content: content.length === 1 && content[0].type === "text" ? content[0].text : content
32
+ });
33
+ continue;
34
+ }
35
+ if (msg.role === "tool") {
36
+ const lastMsg = result[result.length - 1];
37
+ const toolResultBlock = {
38
+ type: "tool_result",
39
+ tool_use_id: msg.toolCallId ?? "",
40
+ content: msg.content ?? ""
41
+ };
42
+ if (lastMsg && lastMsg.role === "user" && Array.isArray(lastMsg.content)) {
43
+ lastMsg.content.push(toolResultBlock);
44
+ } else {
45
+ result.push({ role: "user", content: [toolResultBlock] });
46
+ }
47
+ }
48
+ }
49
+ return { system, messages: result };
50
+ }
51
+ function toAnthropicTools(tools) {
52
+ return tools.map((t) => ({
53
+ name: t.name,
54
+ description: t.description,
55
+ input_schema: t.parameters
56
+ }));
57
+ }
58
+ function fromAnthropicResponse(content) {
59
+ let text = "";
60
+ const toolCalls = [];
61
+ for (const block of content) {
62
+ if (block.type === "text") {
63
+ text += block.text;
64
+ } else if (block.type === "tool_use") {
65
+ toolCalls.push({
66
+ id: block.id,
67
+ name: block.name,
68
+ arguments: block.input
69
+ });
70
+ }
71
+ }
72
+ return {
73
+ text: text || null,
74
+ toolCalls
75
+ };
76
+ }
77
+
78
+ // src/provider.ts
79
+ function anthropic(config) {
80
+ const baseUrl = (config.baseUrl ?? "https://api.anthropic.com").replace(/\/$/, "");
81
+ const apiVersion = config.apiVersion ?? "2023-06-01";
82
+ const fetchFn = config.fetch ?? globalThis.fetch;
83
+ function buildHeaders() {
84
+ return {
85
+ "Content-Type": "application/json",
86
+ "x-api-key": config.apiKey,
87
+ "anthropic-version": apiVersion,
88
+ ...config.defaultHeaders
89
+ };
90
+ }
91
+ function buildBody(params, stream = false) {
92
+ const { system, messages } = toAnthropicMessages(params.messages);
93
+ const body = {
94
+ model: params.model,
95
+ messages,
96
+ max_tokens: params.maxOutputTokens ?? 4096,
97
+ stream
98
+ };
99
+ if (system)
100
+ body.system = system;
101
+ if (params.tools && params.tools.length > 0) {
102
+ body.tools = toAnthropicTools(params.tools);
103
+ }
104
+ if (params.temperature !== undefined)
105
+ body.temperature = params.temperature;
106
+ if (params.topP !== undefined)
107
+ body.top_p = params.topP;
108
+ return body;
109
+ }
110
+ const provider = {
111
+ name: "anthropic",
112
+ async chat(params) {
113
+ const response = await fetchFn(`${baseUrl}/v1/messages`, {
114
+ method: "POST",
115
+ headers: buildHeaders(),
116
+ body: JSON.stringify(buildBody(params, false)),
117
+ signal: params.signal
118
+ });
119
+ if (!response.ok) {
120
+ const errorBody = await response.text();
121
+ throw new Error(`Anthropic API error (${response.status}): ${errorBody}`);
122
+ }
123
+ const data = await response.json();
124
+ const { text, toolCalls } = fromAnthropicResponse(data.content);
125
+ return {
126
+ content: text,
127
+ toolCalls,
128
+ usage: {
129
+ inputTokens: data.usage?.input_tokens ?? 0,
130
+ outputTokens: data.usage?.output_tokens ?? 0
131
+ },
132
+ raw: data
133
+ };
134
+ },
135
+ async* stream(params) {
136
+ const response = await fetchFn(`${baseUrl}/v1/messages`, {
137
+ method: "POST",
138
+ headers: buildHeaders(),
139
+ body: JSON.stringify(buildBody(params, true)),
140
+ signal: params.signal
141
+ });
142
+ if (!response.ok) {
143
+ const errorBody = await response.text();
144
+ throw new Error(`Anthropic API error (${response.status}): ${errorBody}`);
145
+ }
146
+ const reader = response.body?.getReader();
147
+ if (!reader)
148
+ throw new Error("No response body");
149
+ const decoder = new TextDecoder;
150
+ let buffer = "";
151
+ let fullContent = "";
152
+ const toolCalls = [];
153
+ let currentToolUse = null;
154
+ let inputTokens = 0;
155
+ let outputTokens = 0;
156
+ try {
157
+ while (true) {
158
+ const { done, value } = await reader.read();
159
+ if (done)
160
+ break;
161
+ buffer += decoder.decode(value, { stream: true });
162
+ const lines = buffer.split(`
163
+ `);
164
+ buffer = lines.pop() ?? "";
165
+ for (const line of lines) {
166
+ const trimmed = line.trim();
167
+ if (!trimmed.startsWith("data: "))
168
+ continue;
169
+ const data = trimmed.slice(6);
170
+ try {
171
+ const event = JSON.parse(data);
172
+ switch (event.type) {
173
+ case "content_block_start": {
174
+ const block = event.content_block;
175
+ if (block.type === "tool_use") {
176
+ currentToolUse = {
177
+ id: block.id,
178
+ name: block.name,
179
+ inputJson: ""
180
+ };
181
+ yield {
182
+ type: "tool_call_start",
183
+ id: block.id,
184
+ name: block.name
185
+ };
186
+ }
187
+ break;
188
+ }
189
+ case "content_block_delta": {
190
+ const delta = event.delta;
191
+ if (delta.type === "text_delta") {
192
+ fullContent += delta.text;
193
+ yield { type: "token", text: delta.text };
194
+ } else if (delta.type === "input_json_delta" && currentToolUse) {
195
+ currentToolUse.inputJson += delta.partial_json;
196
+ yield {
197
+ type: "tool_call_delta",
198
+ id: currentToolUse.id,
199
+ argumentsDelta: delta.partial_json
200
+ };
201
+ }
202
+ break;
203
+ }
204
+ case "content_block_stop": {
205
+ if (currentToolUse) {
206
+ const call = {
207
+ id: currentToolUse.id,
208
+ name: currentToolUse.name,
209
+ arguments: currentToolUse.inputJson ? JSON.parse(currentToolUse.inputJson) : {}
210
+ };
211
+ toolCalls.push(call);
212
+ yield { type: "tool_call_end", id: currentToolUse.id, call };
213
+ currentToolUse = null;
214
+ }
215
+ break;
216
+ }
217
+ case "message_delta": {
218
+ if (event.usage) {
219
+ outputTokens = event.usage.output_tokens ?? outputTokens;
220
+ }
221
+ break;
222
+ }
223
+ case "message_start": {
224
+ if (event.message?.usage) {
225
+ inputTokens = event.message.usage.input_tokens ?? 0;
226
+ }
227
+ break;
228
+ }
229
+ }
230
+ } catch {}
231
+ }
232
+ }
233
+ } finally {
234
+ reader.releaseLock();
235
+ }
236
+ yield {
237
+ type: "done",
238
+ response: {
239
+ content: fullContent || null,
240
+ toolCalls,
241
+ usage: { inputTokens, outputTokens }
242
+ }
243
+ };
244
+ }
245
+ };
246
+ return provider;
247
+ }
248
+ export {
249
+ anthropic
250
+ };
@@ -0,0 +1,66 @@
1
+ import type { Message, ToolCall, ToolJsonSchema } from '@agentier/core';
2
+ /** Valid roles in the Anthropic Messages API (system is handled separately). */
3
+ export type AnthropicRole = 'user' | 'assistant';
4
+ /** A plain text content block. */
5
+ export interface AnthropicTextBlock {
6
+ type: 'text';
7
+ text: string;
8
+ }
9
+ /** A tool-use content block representing an assistant's request to invoke a tool. */
10
+ export interface AnthropicToolUseBlock {
11
+ type: 'tool_use';
12
+ id: string;
13
+ name: string;
14
+ input: Record<string, unknown>;
15
+ }
16
+ /** A tool-result content block carrying the output of a previously invoked tool. */
17
+ export interface AnthropicToolResultBlock {
18
+ type: 'tool_result';
19
+ tool_use_id: string;
20
+ content: string;
21
+ }
22
+ /** Union of all content block types used in Anthropic messages. */
23
+ export type AnthropicContentBlock = AnthropicTextBlock | AnthropicToolUseBlock | AnthropicToolResultBlock;
24
+ /** Wire format for an Anthropic message (role + mixed content blocks). */
25
+ export interface AnthropicMessage {
26
+ role: AnthropicRole;
27
+ content: string | AnthropicContentBlock[];
28
+ }
29
+ /** Wire format for an Anthropic tool definition. */
30
+ export interface AnthropicTool {
31
+ name: string;
32
+ description: string;
33
+ input_schema: Record<string, unknown>;
34
+ }
35
+ /**
36
+ * Converts agenti `Message` objects into the Anthropic Messages API format.
37
+ *
38
+ * System messages are extracted into a separate `system` string (Anthropic
39
+ * treats system prompts as a top-level parameter, not a message). Tool result
40
+ * messages are folded into the preceding `user` turn when possible, matching
41
+ * the Anthropic expectation that tool results appear inside user messages.
42
+ *
43
+ * @param messages - Array of provider-agnostic messages.
44
+ * @returns An object with an optional `system` prompt and an array of Anthropic messages.
45
+ */
46
+ export declare function toAnthropicMessages(messages: Message[]): {
47
+ system?: string;
48
+ messages: AnthropicMessage[];
49
+ };
50
+ /**
51
+ * Converts agenti tool schemas into the Anthropic tool definition format.
52
+ *
53
+ * @param tools - Array of provider-agnostic tool JSON schemas.
54
+ * @returns Array of Anthropic tool definitions with `input_schema`.
55
+ */
56
+ export declare function toAnthropicTools(tools: ToolJsonSchema[]): AnthropicTool[];
57
+ /**
58
+ * Extracts text and tool calls from Anthropic response content blocks.
59
+ *
60
+ * @param content - Array of content blocks from the Anthropic API response.
61
+ * @returns Parsed text (or `null` if empty) and an array of agenti `ToolCall` objects.
62
+ */
63
+ export declare function fromAnthropicResponse(content: AnthropicContentBlock[]): {
64
+ text: string | null;
65
+ toolCalls: ToolCall[];
66
+ };
@@ -0,0 +1,24 @@
1
+ import type { ModelProvider } from '@agentier/core';
2
+ import type { AnthropicProviderConfig } from './types';
3
+ /**
4
+ * Creates an Anthropic {@link ModelProvider}.
5
+ *
6
+ * Supports both blocking `chat` and streaming `stream` completions via the
7
+ * Anthropic Messages API (`/v1/messages`). Handles the Anthropic-specific
8
+ * SSE event protocol (`content_block_start`, `content_block_delta`, etc.).
9
+ *
10
+ * @param config - Provider configuration including API key and optional overrides.
11
+ * @returns A `ModelProvider` wired to the Anthropic Messages API.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { anthropic } from '@agentier/provider-anthropic'
16
+ *
17
+ * const provider = anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! })
18
+ * const response = await provider.chat({
19
+ * model: 'claude-sonnet-4-20250514',
20
+ * messages: [{ role: 'user', content: 'Hello!' }],
21
+ * })
22
+ * ```
23
+ */
24
+ export declare function anthropic(config: AnthropicProviderConfig): ModelProvider;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Configuration for the Anthropic provider.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * const config: AnthropicProviderConfig = {
7
+ * apiKey: process.env.ANTHROPIC_API_KEY!,
8
+ * apiVersion: '2023-06-01',
9
+ * }
10
+ * ```
11
+ */
12
+ export interface AnthropicProviderConfig {
13
+ /** Anthropic API key sent via the `x-api-key` header. */
14
+ apiKey: string;
15
+ /** Base URL for API requests. Defaults to `https://api.anthropic.com`. */
16
+ baseUrl?: string;
17
+ /** Anthropic API version sent via the `anthropic-version` header. Defaults to `'2023-06-01'`. */
18
+ apiVersion?: string;
19
+ /** Additional headers merged into every request. */
20
+ defaultHeaders?: Record<string, string>;
21
+ /** Custom `fetch` implementation, useful for proxies or test doubles. */
22
+ fetch?: typeof globalThis.fetch;
23
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@agentier/anthropic",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "bun build ./src/index.ts --outdir ./dist --target node",
18
+ "test": "bun test",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "peerDependencies": {
22
+ "@agentier/core": "^0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@agentier/core": "workspace:*",
26
+ "typescript": "^5.5.0",
27
+ "@types/bun": "latest"
28
+ }
29
+ }