@core-ai/anthropic 0.2.1 → 0.4.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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +291 -59
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -17,7 +17,7 @@ import { generate } from '@core-ai/core-ai';
17
17
  import { createAnthropic } from '@core-ai/anthropic';
18
18
 
19
19
  const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
20
- const model = anthropic.chatModel('claude-sonnet-4-20250514');
20
+ const model = anthropic.chatModel('claude-haiku-4-5');
21
21
 
22
22
  const result = await generate({
23
23
  model,
package/dist/index.js CHANGED
@@ -2,12 +2,28 @@
2
2
  import Anthropic from "@anthropic-ai/sdk";
3
3
 
4
4
  // src/chat-model.ts
5
- import { createStreamResult } from "@core-ai/core-ai";
5
+ import {
6
+ StructuredOutputNoObjectGeneratedError,
7
+ StructuredOutputParseError,
8
+ StructuredOutputValidationError,
9
+ createObjectStreamResult,
10
+ createStreamResult
11
+ } from "@core-ai/core-ai";
6
12
 
7
13
  // src/chat-adapter.ts
8
14
  import { APIError } from "@anthropic-ai/sdk";
9
15
  import { zodToJsonSchema } from "zod-to-json-schema";
10
16
  import { ProviderError } from "@core-ai/core-ai";
17
+ var UNSUPPORTED_ANTHROPIC_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
18
+ "minimum",
19
+ "maximum",
20
+ "exclusiveMinimum",
21
+ "exclusiveMaximum",
22
+ "multipleOf",
23
+ "minLength",
24
+ "maxLength",
25
+ "maxItems"
26
+ ]);
11
27
  function convertMessages(messages) {
12
28
  const systemParts = [];
13
29
  const convertedMessages = [];
@@ -115,12 +131,12 @@ function convertUserContentPart(part) {
115
131
  }
116
132
  function convertTools(tools) {
117
133
  return Object.values(tools).map((tool) => {
118
- const schema = zodToJsonSchema(tool.parameters);
119
- const { $schema: _schema, ...inputSchema } = schema;
134
+ const schema = toAnthropicJsonSchema(tool.parameters);
120
135
  return {
121
136
  name: tool.name,
122
137
  description: tool.description,
123
- input_schema: inputSchema
138
+ input_schema: schema,
139
+ strict: true
124
140
  };
125
141
  });
126
142
  }
@@ -139,35 +155,90 @@ function convertToolChoice(choice) {
139
155
  name: choice.toolName
140
156
  };
141
157
  }
158
+ function createStructuredOutputOptions(options) {
159
+ const schema = toAnthropicJsonSchema(options.schema);
160
+ const schemaDescription = options.schemaDescription?.trim();
161
+ if (schemaDescription && schemaDescription.length > 0) {
162
+ schema.description = schemaDescription;
163
+ }
164
+ return {
165
+ messages: options.messages,
166
+ config: options.config,
167
+ providerOptions: {
168
+ ...options.providerOptions ?? {},
169
+ output_config: {
170
+ format: {
171
+ type: "json_schema",
172
+ schema
173
+ }
174
+ }
175
+ },
176
+ signal: options.signal
177
+ };
178
+ }
179
+ function toAnthropicJsonSchema(schema) {
180
+ const rawSchema = zodToJsonSchema(schema);
181
+ return normalizeAnthropicJsonSchema(rawSchema);
182
+ }
183
+ function normalizeAnthropicJsonSchema(value) {
184
+ const normalized = normalizeAnthropicJsonValue(value);
185
+ return isJsonObject(normalized) ? normalized : {};
186
+ }
187
+ function normalizeAnthropicJsonValue(value) {
188
+ if (Array.isArray(value)) {
189
+ return value.map(normalizeAnthropicJsonValue);
190
+ }
191
+ if (!isJsonObject(value)) {
192
+ return value;
193
+ }
194
+ const normalized = {};
195
+ for (const [key, child] of Object.entries(value)) {
196
+ if (key === "$schema" || UNSUPPORTED_ANTHROPIC_SCHEMA_KEYWORDS.has(key) || key === "minItems" && typeof child === "number" && child > 1) {
197
+ continue;
198
+ }
199
+ normalized[key] = normalizeAnthropicJsonValue(child);
200
+ }
201
+ if (isObjectSchema(normalized)) {
202
+ normalized.additionalProperties = false;
203
+ }
204
+ return normalized;
205
+ }
206
+ function isObjectSchema(value) {
207
+ return value.type === "object" || Object.hasOwn(value, "properties") || Object.hasOwn(value, "required");
208
+ }
209
+ function isJsonObject(value) {
210
+ return value !== null && typeof value === "object" && !Array.isArray(value);
211
+ }
142
212
  function createGenerateRequest(modelId, defaultMaxTokens, options) {
143
- const converted = convertMessages(options.messages);
144
213
  return {
145
- model: modelId,
146
- messages: converted.messages,
147
- max_tokens: options.config?.maxTokens ?? defaultMaxTokens,
148
- ...converted.system ? { system: converted.system } : {},
149
- ...options.tools && Object.keys(options.tools).length > 0 ? { tools: convertTools(options.tools) } : {},
150
- ...options.toolChoice ? { tool_choice: convertToolChoice(options.toolChoice) } : {},
151
- ...options.config?.temperature !== void 0 ? { temperature: options.config.temperature } : {},
152
- ...options.config?.topP !== void 0 ? { top_p: options.config.topP } : {},
153
- ...options.config?.stopSequences ? { stop_sequences: options.config.stopSequences } : {},
214
+ ...createRequestBase(modelId, defaultMaxTokens, options),
154
215
  ...options.providerOptions
155
216
  };
156
217
  }
157
218
  function createStreamRequest(modelId, defaultMaxTokens, options) {
219
+ return {
220
+ ...createRequestBase(modelId, defaultMaxTokens, options),
221
+ stream: true,
222
+ ...options.providerOptions
223
+ };
224
+ }
225
+ function createRequestBase(modelId, defaultMaxTokens, options) {
158
226
  const converted = convertMessages(options.messages);
159
227
  return {
160
228
  model: modelId,
161
229
  messages: converted.messages,
162
- stream: true,
163
230
  max_tokens: options.config?.maxTokens ?? defaultMaxTokens,
164
231
  ...converted.system ? { system: converted.system } : {},
165
232
  ...options.tools && Object.keys(options.tools).length > 0 ? { tools: convertTools(options.tools) } : {},
166
233
  ...options.toolChoice ? { tool_choice: convertToolChoice(options.toolChoice) } : {},
167
- ...options.config?.temperature !== void 0 ? { temperature: options.config.temperature } : {},
168
- ...options.config?.topP !== void 0 ? { top_p: options.config.topP } : {},
169
- ...options.config?.stopSequences ? { stop_sequences: options.config.stopSequences } : {},
170
- ...options.providerOptions
234
+ ...mapConfigToRequestFields(options.config)
235
+ };
236
+ }
237
+ function mapConfigToRequestFields(config) {
238
+ return {
239
+ ...config?.temperature !== void 0 ? { temperature: config.temperature } : {},
240
+ ...config?.topP !== void 0 ? { top_p: config.topP } : {},
241
+ ...config?.stopSequences ? { stop_sequences: config.stopSequences } : {}
171
242
  };
172
243
  }
173
244
  function mapGenerateResponse(response) {
@@ -186,15 +257,23 @@ function mapGenerateResponse(response) {
186
257
  });
187
258
  }
188
259
  }
260
+ const cacheReadTokens = response.usage.cache_read_input_tokens ?? 0;
261
+ const cacheWriteTokens = response.usage.cache_creation_input_tokens ?? 0;
262
+ const inputTokens = response.usage.input_tokens + cacheReadTokens + cacheWriteTokens;
189
263
  return {
190
264
  content: content.length > 0 ? content : null,
191
265
  toolCalls,
192
266
  finishReason: mapStopReason(response.stop_reason),
193
267
  usage: {
194
- inputTokens: response.usage.input_tokens,
268
+ inputTokens,
195
269
  outputTokens: response.usage.output_tokens,
196
- reasoningTokens: 0,
197
- totalTokens: response.usage.input_tokens + response.usage.output_tokens
270
+ inputTokenDetails: {
271
+ cacheReadTokens,
272
+ cacheWriteTokens
273
+ },
274
+ outputTokenDetails: {
275
+ reasoningTokens: 0
276
+ }
198
277
  }
199
278
  };
200
279
  }
@@ -203,25 +282,38 @@ async function* transformStream(stream) {
203
282
  let usage = {
204
283
  inputTokens: 0,
205
284
  outputTokens: 0,
206
- reasoningTokens: 0,
207
- totalTokens: 0
285
+ inputTokenDetails: {
286
+ cacheReadTokens: 0,
287
+ cacheWriteTokens: 0
288
+ },
289
+ outputTokenDetails: {
290
+ reasoningTokens: 0
291
+ }
208
292
  };
209
293
  const toolBuffers = /* @__PURE__ */ new Map();
210
294
  const emittedToolCalls = /* @__PURE__ */ new Set();
211
295
  for await (const event of stream) {
212
296
  if (event.type === "message_start") {
297
+ const cacheReadTokens = event.message.usage.cache_read_input_tokens ?? 0;
298
+ const cacheWriteTokens = event.message.usage.cache_creation_input_tokens ?? 0;
299
+ const inputTokens = event.message.usage.input_tokens + cacheReadTokens + cacheWriteTokens;
213
300
  usage = {
214
- inputTokens: event.message.usage.input_tokens,
301
+ inputTokens,
215
302
  outputTokens: event.message.usage.output_tokens,
216
- reasoningTokens: 0,
217
- totalTokens: event.message.usage.input_tokens + event.message.usage.output_tokens
303
+ inputTokenDetails: {
304
+ cacheReadTokens,
305
+ cacheWriteTokens
306
+ },
307
+ outputTokenDetails: {
308
+ reasoningTokens: 0
309
+ }
218
310
  };
219
311
  continue;
220
312
  }
221
313
  if (event.type === "content_block_start") {
222
314
  if (event.content_block.type === "tool_use") {
223
315
  const block = event.content_block;
224
- const initialArguments = block.input && typeof block.input === "object" ? JSON.stringify(block.input) : "";
316
+ const initialArguments = block.input && typeof block.input === "object" ? Object.keys(block.input).length > 0 ? JSON.stringify(block.input) : "" : "";
225
317
  toolBuffers.set(event.index, {
226
318
  id: block.id,
227
319
  name: block.name,
@@ -275,11 +367,19 @@ async function* transformStream(stream) {
275
367
  }
276
368
  if (event.type === "message_delta") {
277
369
  finishReason = mapStopReason(event.delta.stop_reason);
370
+ const nonCachedInputTokens = event.usage.input_tokens ?? usage.inputTokens - usage.inputTokenDetails.cacheReadTokens - usage.inputTokenDetails.cacheWriteTokens;
371
+ const cacheReadTokens = event.usage.cache_read_input_tokens ?? usage.inputTokenDetails.cacheReadTokens;
372
+ const cacheWriteTokens = event.usage.cache_creation_input_tokens ?? usage.inputTokenDetails.cacheWriteTokens;
278
373
  usage = {
279
- inputTokens: event.usage.input_tokens ?? usage.inputTokens,
374
+ inputTokens: nonCachedInputTokens + cacheReadTokens + cacheWriteTokens,
280
375
  outputTokens: event.usage.output_tokens,
281
- reasoningTokens: 0,
282
- totalTokens: (event.usage.input_tokens ?? usage.inputTokens) + event.usage.output_tokens
376
+ inputTokenDetails: {
377
+ cacheReadTokens,
378
+ cacheWriteTokens
379
+ },
380
+ outputTokenDetails: {
381
+ reasoningTokens: 0
382
+ }
283
383
  };
284
384
  continue;
285
385
  }
@@ -321,7 +421,12 @@ function asObject(value) {
321
421
  }
322
422
  function wrapError(error) {
323
423
  if (error instanceof APIError) {
324
- return new ProviderError(error.message, "anthropic", error.status, error);
424
+ return new ProviderError(
425
+ error.message,
426
+ "anthropic",
427
+ error.status,
428
+ error
429
+ );
325
430
  }
326
431
  return new ProviderError(
327
432
  error instanceof Error ? error.message : String(error),
@@ -333,39 +438,166 @@ function wrapError(error) {
333
438
 
334
439
  // src/chat-model.ts
335
440
  function createAnthropicChatModel(client, modelId, defaultMaxTokens) {
441
+ const provider = "anthropic";
442
+ async function callAnthropicMessagesApi(request) {
443
+ try {
444
+ return await client.messages.create(request);
445
+ } catch (error) {
446
+ throw wrapError(error);
447
+ }
448
+ }
449
+ async function generateChat(options) {
450
+ const request = createGenerateRequest(modelId, defaultMaxTokens, options);
451
+ const response = await callAnthropicMessagesApi(request);
452
+ return mapGenerateResponse(response);
453
+ }
454
+ async function streamChat(options) {
455
+ const request = createStreamRequest(modelId, defaultMaxTokens, options);
456
+ const stream = await callAnthropicMessagesApi(request);
457
+ return createStreamResult(transformStream(stream));
458
+ }
336
459
  return {
337
- provider: "anthropic",
460
+ provider,
338
461
  modelId,
339
- async generate(options) {
340
- try {
341
- const request = createGenerateRequest(
342
- modelId,
343
- defaultMaxTokens,
344
- options
345
- );
346
- const response = await client.messages.create(request);
347
- return mapGenerateResponse(response);
348
- } catch (error) {
349
- throw wrapError(error);
350
- }
462
+ generate: generateChat,
463
+ stream: streamChat,
464
+ async generateObject(options) {
465
+ const structuredOptions = createStructuredOutputOptions(options);
466
+ const result = await generateChat(structuredOptions);
467
+ const object = extractStructuredObject(result, options.schema, provider);
468
+ return {
469
+ object,
470
+ finishReason: result.finishReason,
471
+ usage: result.usage
472
+ };
351
473
  },
352
- async stream(options) {
353
- try {
354
- const request = createStreamRequest(
355
- modelId,
356
- defaultMaxTokens,
357
- options
358
- );
359
- const stream = await client.messages.create(
360
- request
361
- );
362
- return createStreamResult(transformStream(stream));
363
- } catch (error) {
364
- throw wrapError(error);
365
- }
474
+ async streamObject(options) {
475
+ const structuredOptions = createStructuredOutputOptions(options);
476
+ const stream = await streamChat(structuredOptions);
477
+ return createObjectStreamResult(
478
+ transformStructuredOutputStream(
479
+ stream,
480
+ options.schema,
481
+ provider
482
+ )
483
+ );
366
484
  }
367
485
  };
368
486
  }
487
+ function extractStructuredObject(result, schema, provider) {
488
+ const rawOutput = requireStructuredOutputPayload(
489
+ result.finishReason,
490
+ result.content?.trim(),
491
+ provider,
492
+ "model did not emit a structured object payload"
493
+ );
494
+ return parseAndValidateStructuredObject(schema, rawOutput, provider);
495
+ }
496
+ async function* transformStructuredOutputStream(stream, schema, provider) {
497
+ let contentBuffer = "";
498
+ for await (const event of stream) {
499
+ if (event.type === "content-delta") {
500
+ contentBuffer += event.text;
501
+ yield {
502
+ type: "object-delta",
503
+ text: event.text
504
+ };
505
+ continue;
506
+ }
507
+ if (event.type === "tool-call-delta") {
508
+ contentBuffer += event.argumentsDelta;
509
+ yield {
510
+ type: "object-delta",
511
+ text: event.argumentsDelta
512
+ };
513
+ continue;
514
+ }
515
+ if (event.type === "finish") {
516
+ const rawOutput = requireStructuredOutputPayload(
517
+ event.finishReason,
518
+ contentBuffer.trim(),
519
+ provider,
520
+ "structured output stream ended without an object payload"
521
+ );
522
+ const validatedObject = parseAndValidateStructuredObject(
523
+ schema,
524
+ rawOutput,
525
+ provider
526
+ );
527
+ yield {
528
+ type: "object",
529
+ object: validatedObject
530
+ };
531
+ yield {
532
+ type: "finish",
533
+ finishReason: event.finishReason,
534
+ usage: event.usage
535
+ };
536
+ }
537
+ }
538
+ }
539
+ function requireStructuredOutputPayload(finishReason, rawOutput, provider, noPayloadMessage) {
540
+ if (finishReason === "content-filter") {
541
+ throw new StructuredOutputNoObjectGeneratedError(
542
+ "model refused to produce a structured output",
543
+ provider,
544
+ {
545
+ rawOutput
546
+ }
547
+ );
548
+ }
549
+ if (finishReason === "length") {
550
+ throw new StructuredOutputNoObjectGeneratedError(
551
+ "structured output was truncated because max tokens were reached",
552
+ provider,
553
+ {
554
+ rawOutput
555
+ }
556
+ );
557
+ }
558
+ if (!rawOutput || rawOutput.length === 0) {
559
+ throw new StructuredOutputNoObjectGeneratedError(noPayloadMessage, provider);
560
+ }
561
+ return rawOutput;
562
+ }
563
+ function parseAndValidateStructuredObject(schema, rawOutput, provider) {
564
+ const parsedOutput = parseJson(rawOutput, provider);
565
+ return validateStructuredObject(schema, parsedOutput, provider, rawOutput);
566
+ }
567
+ function parseJson(rawOutput, provider) {
568
+ try {
569
+ return JSON.parse(rawOutput);
570
+ } catch (error) {
571
+ throw new StructuredOutputParseError(
572
+ "failed to parse structured output as JSON",
573
+ provider,
574
+ {
575
+ rawOutput,
576
+ cause: error
577
+ }
578
+ );
579
+ }
580
+ }
581
+ function validateStructuredObject(schema, value, provider, rawOutput) {
582
+ const parsed = schema.safeParse(value);
583
+ if (parsed.success) {
584
+ return parsed.data;
585
+ }
586
+ throw new StructuredOutputValidationError(
587
+ "structured output does not match schema",
588
+ provider,
589
+ formatZodIssues(parsed.error.issues),
590
+ {
591
+ rawOutput
592
+ }
593
+ );
594
+ }
595
+ function formatZodIssues(issues) {
596
+ return issues.map((issue) => {
597
+ const path = issue.path.length > 0 ? issue.path.map((segment) => String(segment)).join(".") : "<root>";
598
+ return `${path}: ${issue.message}`;
599
+ });
600
+ }
369
601
 
370
602
  // src/provider.ts
371
603
  function createAnthropic(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/anthropic",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Anthropic provider package for @core-ai/core-ai",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",
@@ -43,7 +43,7 @@
43
43
  "test:watch": "vitest"
44
44
  },
45
45
  "dependencies": {
46
- "@core-ai/core-ai": "^0.2.1",
46
+ "@core-ai/core-ai": "^0.4.0",
47
47
  "@anthropic-ai/sdk": "^0.78.0",
48
48
  "zod-to-json-schema": "^3.25.1"
49
49
  },