@core-ai/anthropic 0.3.0 → 0.5.1

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 (2) hide show
  1. package/dist/index.js +348 -40
  2. package/package.json +2 -3
package/dist/index.js CHANGED
@@ -14,6 +14,103 @@ import {
14
14
  import { APIError } from "@anthropic-ai/sdk";
15
15
  import { zodToJsonSchema } from "zod-to-json-schema";
16
16
  import { ProviderError } from "@core-ai/core-ai";
17
+
18
+ // src/model-capabilities.ts
19
+ var DEFAULT_CAPABILITIES = {
20
+ reasoning: {
21
+ thinkingMode: "adaptive",
22
+ supportsMaxEffort: false
23
+ }
24
+ };
25
+ var MODEL_CAPABILITIES = {
26
+ "claude-opus-4-6": {
27
+ reasoning: {
28
+ thinkingMode: "adaptive",
29
+ supportsMaxEffort: true
30
+ }
31
+ },
32
+ "claude-sonnet-4-6": {
33
+ reasoning: {
34
+ thinkingMode: "adaptive",
35
+ supportsMaxEffort: false
36
+ }
37
+ },
38
+ "claude-opus-4-5": {
39
+ reasoning: {
40
+ thinkingMode: "manual",
41
+ supportsMaxEffort: false
42
+ }
43
+ },
44
+ "claude-sonnet-4-5": {
45
+ reasoning: {
46
+ thinkingMode: "manual",
47
+ supportsMaxEffort: false
48
+ }
49
+ },
50
+ "claude-opus-4-1": {
51
+ reasoning: {
52
+ thinkingMode: "manual",
53
+ supportsMaxEffort: false
54
+ }
55
+ },
56
+ "claude-opus-4": {
57
+ reasoning: {
58
+ thinkingMode: "manual",
59
+ supportsMaxEffort: false
60
+ }
61
+ },
62
+ "claude-sonnet-4": {
63
+ reasoning: {
64
+ thinkingMode: "manual",
65
+ supportsMaxEffort: false
66
+ }
67
+ },
68
+ "claude-haiku-4-5": {
69
+ reasoning: {
70
+ thinkingMode: "manual",
71
+ supportsMaxEffort: false
72
+ }
73
+ },
74
+ "claude-sonnet-3-7": {
75
+ reasoning: {
76
+ thinkingMode: "manual",
77
+ supportsMaxEffort: false
78
+ }
79
+ }
80
+ };
81
+ function getAnthropicModelCapabilities(modelId) {
82
+ const normalizedModelId = normalizeModelId(modelId);
83
+ return MODEL_CAPABILITIES[normalizedModelId] ?? DEFAULT_CAPABILITIES;
84
+ }
85
+ function normalizeModelId(modelId) {
86
+ return modelId.replace(/-\d{8}$/, "");
87
+ }
88
+ function toAnthropicAdaptiveEffort(effort, supportsMaxEffort) {
89
+ if (effort === "minimal") {
90
+ return "low";
91
+ }
92
+ if (effort === "max") {
93
+ return supportsMaxEffort ? "max" : "high";
94
+ }
95
+ return effort;
96
+ }
97
+ function toAnthropicManualBudget(effort) {
98
+ if (effort === "minimal") {
99
+ return 1024;
100
+ }
101
+ if (effort === "low") {
102
+ return 2048;
103
+ }
104
+ if (effort === "medium") {
105
+ return 8192;
106
+ }
107
+ if (effort === "high") {
108
+ return 32768;
109
+ }
110
+ return 65536;
111
+ }
112
+
113
+ // src/chat-adapter.ts
17
114
  var UNSUPPORTED_ANTHROPIC_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
18
115
  "minimum",
19
116
  "maximum",
@@ -44,19 +141,40 @@ function convertMessages(messages) {
44
141
  }
45
142
  if (message.role === "assistant") {
46
143
  const contentBlocks = [];
47
- if (message.content) {
48
- contentBlocks.push({
49
- type: "text",
50
- text: message.content
51
- });
52
- }
53
- for (const toolCall of message.toolCalls ?? []) {
54
- contentBlocks.push({
55
- type: "tool_use",
56
- id: toolCall.id,
57
- name: toolCall.name,
58
- input: toolCall.arguments
59
- });
144
+ for (const part of message.parts) {
145
+ if (part.type === "text") {
146
+ contentBlocks.push({
147
+ type: "text",
148
+ text: part.text
149
+ });
150
+ continue;
151
+ }
152
+ if (part.type === "tool-call") {
153
+ contentBlocks.push({
154
+ type: "tool_use",
155
+ id: part.toolCall.id,
156
+ name: part.toolCall.name,
157
+ input: part.toolCall.arguments
158
+ });
159
+ continue;
160
+ }
161
+ const signature = part.providerMetadata?.["signature"];
162
+ const redactedData = part.providerMetadata?.["redactedData"];
163
+ if (typeof redactedData === "string") {
164
+ contentBlocks.push({
165
+ type: "redacted_thinking",
166
+ data: redactedData
167
+ });
168
+ continue;
169
+ }
170
+ const thinkingBlock = {
171
+ type: "thinking",
172
+ thinking: part.text
173
+ };
174
+ if (typeof signature === "string") {
175
+ thinkingBlock["signature"] = signature;
176
+ }
177
+ contentBlocks.push(thinkingBlock);
60
178
  }
61
179
  convertedMessages.push({
62
180
  role: "assistant",
@@ -163,6 +281,7 @@ function createStructuredOutputOptions(options) {
163
281
  }
164
282
  return {
165
283
  messages: options.messages,
284
+ reasoning: options.reasoning,
166
285
  config: options.config,
167
286
  providerOptions: {
168
287
  ...options.providerOptions ?? {},
@@ -210,20 +329,20 @@ function isJsonObject(value) {
210
329
  return value !== null && typeof value === "object" && !Array.isArray(value);
211
330
  }
212
331
  function createGenerateRequest(modelId, defaultMaxTokens, options) {
213
- return {
214
- ...createRequestBase(modelId, defaultMaxTokens, options),
215
- ...options.providerOptions
216
- };
332
+ const baseRequest = createRequestBase(modelId, defaultMaxTokens, options);
333
+ return mergeProviderOptions(baseRequest, options.providerOptions);
217
334
  }
218
335
  function createStreamRequest(modelId, defaultMaxTokens, options) {
219
- return {
336
+ const baseRequest = {
220
337
  ...createRequestBase(modelId, defaultMaxTokens, options),
221
- stream: true,
222
- ...options.providerOptions
338
+ stream: true
223
339
  };
340
+ return mergeProviderOptions(baseRequest, options.providerOptions);
224
341
  }
225
342
  function createRequestBase(modelId, defaultMaxTokens, options) {
343
+ validateAnthropicReasoningConfig(modelId, options);
226
344
  const converted = convertMessages(options.messages);
345
+ const reasoningFields = mapReasoningToRequestFields(modelId, options);
227
346
  return {
228
347
  model: modelId,
229
348
  messages: converted.messages,
@@ -231,6 +350,7 @@ function createRequestBase(modelId, defaultMaxTokens, options) {
231
350
  ...converted.system ? { system: converted.system } : {},
232
351
  ...options.tools && Object.keys(options.tools).length > 0 ? { tools: convertTools(options.tools) } : {},
233
352
  ...options.toolChoice ? { tool_choice: convertToolChoice(options.toolChoice) } : {},
353
+ ...reasoningFields,
234
354
  ...mapConfigToRequestFields(options.config)
235
355
  };
236
356
  }
@@ -241,31 +361,155 @@ function mapConfigToRequestFields(config) {
241
361
  ...config?.stopSequences ? { stop_sequences: config.stopSequences } : {}
242
362
  };
243
363
  }
364
+ function validateAnthropicReasoningConfig(modelId, options) {
365
+ if (!options.reasoning) {
366
+ return;
367
+ }
368
+ if (options.config?.temperature !== void 0) {
369
+ throw new ProviderError(
370
+ `Anthropic model "${modelId}" does not support temperature when reasoning is enabled`,
371
+ "anthropic"
372
+ );
373
+ }
374
+ if (options.config?.topP !== void 0 && (options.config.topP < 0.95 || options.config.topP > 1)) {
375
+ throw new ProviderError(
376
+ `Anthropic model "${modelId}" requires topP between 0.95 and 1 when reasoning is enabled`,
377
+ "anthropic"
378
+ );
379
+ }
380
+ if (options.toolChoice && options.toolChoice !== "auto" && options.toolChoice !== "none") {
381
+ throw new ProviderError(
382
+ `Anthropic model "${modelId}" only supports toolChoice "auto" or "none" when reasoning is enabled`,
383
+ "anthropic"
384
+ );
385
+ }
386
+ const providerOptions = asObject(options.providerOptions);
387
+ if (providerOptions["top_k"] !== void 0) {
388
+ throw new ProviderError(
389
+ `Anthropic model "${modelId}" does not support top_k when reasoning is enabled`,
390
+ "anthropic"
391
+ );
392
+ }
393
+ }
394
+ function mapReasoningToRequestFields(modelId, options) {
395
+ if (!options.reasoning) {
396
+ return {};
397
+ }
398
+ const capabilities = getAnthropicModelCapabilities(modelId);
399
+ const baseFields = {};
400
+ if (options.tools && Object.keys(options.tools).length > 0) {
401
+ baseFields["betas"] = ["interleaved-thinking-2025-05-14"];
402
+ }
403
+ if (capabilities.reasoning.thinkingMode === "adaptive") {
404
+ baseFields["thinking"] = { type: "adaptive" };
405
+ baseFields["output_config"] = {
406
+ effort: toAnthropicAdaptiveEffort(
407
+ options.reasoning.effort,
408
+ capabilities.reasoning.supportsMaxEffort
409
+ )
410
+ };
411
+ return baseFields;
412
+ }
413
+ baseFields["thinking"] = {
414
+ type: "enabled",
415
+ budget_tokens: toAnthropicManualBudget(options.reasoning.effort)
416
+ };
417
+ return baseFields;
418
+ }
419
+ function mergeProviderOptions(baseRequest, providerOptions) {
420
+ if (!providerOptions) {
421
+ return baseRequest;
422
+ }
423
+ const baseOutputConfig = asObject(
424
+ baseRequest.output_config
425
+ );
426
+ const providerOutputConfig = asObject(providerOptions["output_config"]);
427
+ const mergedOutputConfig = {
428
+ ...baseOutputConfig,
429
+ ...providerOutputConfig
430
+ };
431
+ const mergedBetas = [
432
+ ...asStringArray(baseRequest.betas),
433
+ ...asStringArray(providerOptions["betas"])
434
+ ];
435
+ const mergedRequest = {
436
+ ...baseRequest,
437
+ ...providerOptions,
438
+ ...Object.keys(mergedOutputConfig).length > 0 ? { output_config: mergedOutputConfig } : {},
439
+ ...mergedBetas.length > 0 ? { betas: uniqueStrings(mergedBetas) } : {}
440
+ };
441
+ return mergedRequest;
442
+ }
244
443
  function mapGenerateResponse(response) {
245
- const toolCalls = [];
246
- let content = "";
444
+ const parts = [];
247
445
  for (const block of response.content) {
248
446
  if (block.type === "text") {
249
- content += block.text;
447
+ parts.push({
448
+ type: "text",
449
+ text: block.text
450
+ });
250
451
  continue;
251
452
  }
252
453
  if (block.type === "tool_use") {
253
- toolCalls.push({
254
- id: block.id,
255
- name: block.name,
256
- arguments: asObject(block.input)
454
+ parts.push({
455
+ type: "tool-call",
456
+ toolCall: {
457
+ id: block.id,
458
+ name: block.name,
459
+ arguments: asObject(block.input)
460
+ }
461
+ });
462
+ continue;
463
+ }
464
+ if (block.type === "thinking") {
465
+ const thinkingText = typeof block.thinking === "string" ? block.thinking : extractThinkingText(block.thinking);
466
+ const signature = typeof block.signature === "string" ? block.signature : void 0;
467
+ parts.push({
468
+ type: "reasoning",
469
+ text: thinkingText,
470
+ ...signature ? {
471
+ providerMetadata: {
472
+ signature
473
+ }
474
+ } : {}
475
+ });
476
+ continue;
477
+ }
478
+ if (block.type === "redacted_thinking") {
479
+ const redactedData = typeof block.data === "string" ? block.data : void 0;
480
+ parts.push({
481
+ type: "reasoning",
482
+ text: "",
483
+ ...redactedData ? {
484
+ providerMetadata: {
485
+ redactedData
486
+ }
487
+ } : {}
257
488
  });
258
489
  }
259
490
  }
491
+ const cacheReadTokens = response.usage.cache_read_input_tokens ?? 0;
492
+ const cacheWriteTokens = response.usage.cache_creation_input_tokens ?? 0;
493
+ const inputTokens = response.usage.input_tokens + cacheReadTokens + cacheWriteTokens;
494
+ const content = parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
495
+ const reasoning = parts.flatMap((part) => part.type === "reasoning" ? [part.text] : []).join("");
496
+ const toolCalls = parts.flatMap(
497
+ (part) => part.type === "tool-call" ? [part.toolCall] : []
498
+ );
260
499
  return {
500
+ parts,
261
501
  content: content.length > 0 ? content : null,
502
+ reasoning: reasoning.length > 0 ? reasoning : null,
262
503
  toolCalls,
263
504
  finishReason: mapStopReason(response.stop_reason),
264
505
  usage: {
265
- inputTokens: response.usage.input_tokens,
506
+ inputTokens,
266
507
  outputTokens: response.usage.output_tokens,
267
- reasoningTokens: 0,
268
- totalTokens: response.usage.input_tokens + response.usage.output_tokens
508
+ inputTokenDetails: {
509
+ cacheReadTokens,
510
+ cacheWriteTokens
511
+ },
512
+ outputTokenDetails: {}
269
513
  }
270
514
  };
271
515
  }
@@ -274,22 +518,39 @@ async function* transformStream(stream) {
274
518
  let usage = {
275
519
  inputTokens: 0,
276
520
  outputTokens: 0,
277
- reasoningTokens: 0,
278
- totalTokens: 0
521
+ inputTokenDetails: {
522
+ cacheReadTokens: 0,
523
+ cacheWriteTokens: 0
524
+ },
525
+ outputTokenDetails: {}
279
526
  };
280
527
  const toolBuffers = /* @__PURE__ */ new Map();
281
528
  const emittedToolCalls = /* @__PURE__ */ new Set();
529
+ const contentBlockTypeByIndex = /* @__PURE__ */ new Map();
282
530
  for await (const event of stream) {
283
531
  if (event.type === "message_start") {
532
+ const cacheReadTokens = event.message.usage.cache_read_input_tokens ?? 0;
533
+ const cacheWriteTokens = event.message.usage.cache_creation_input_tokens ?? 0;
534
+ const inputTokens = event.message.usage.input_tokens + cacheReadTokens + cacheWriteTokens;
284
535
  usage = {
285
- inputTokens: event.message.usage.input_tokens,
536
+ inputTokens,
286
537
  outputTokens: event.message.usage.output_tokens,
287
- reasoningTokens: 0,
288
- totalTokens: event.message.usage.input_tokens + event.message.usage.output_tokens
538
+ inputTokenDetails: {
539
+ cacheReadTokens,
540
+ cacheWriteTokens
541
+ },
542
+ outputTokenDetails: {}
289
543
  };
290
544
  continue;
291
545
  }
292
546
  if (event.type === "content_block_start") {
547
+ contentBlockTypeByIndex.set(event.index, event.content_block.type);
548
+ if (event.content_block.type === "thinking") {
549
+ yield {
550
+ type: "reasoning-start"
551
+ };
552
+ continue;
553
+ }
293
554
  if (event.content_block.type === "tool_use") {
294
555
  const block = event.content_block;
295
556
  const initialArguments = block.input && typeof block.input === "object" ? Object.keys(block.input).length > 0 ? JSON.stringify(block.input) : "" : "";
@@ -309,11 +570,22 @@ async function* transformStream(stream) {
309
570
  if (event.type === "content_block_delta") {
310
571
  if (event.delta.type === "text_delta") {
311
572
  yield {
312
- type: "content-delta",
573
+ type: "text-delta",
313
574
  text: event.delta.text
314
575
  };
315
576
  continue;
316
577
  }
578
+ if (event.delta.type === "thinking_delta") {
579
+ const thinkingDelta = event.delta;
580
+ const thinkingText = typeof thinkingDelta.thinking === "string" ? thinkingDelta.thinking : typeof thinkingDelta.text === "string" ? thinkingDelta.text : "";
581
+ if (thinkingText.length > 0) {
582
+ yield {
583
+ type: "reasoning-delta",
584
+ text: thinkingText
585
+ };
586
+ }
587
+ continue;
588
+ }
317
589
  if (event.delta.type === "input_json_delta") {
318
590
  const current = toolBuffers.get(event.index);
319
591
  if (!current) {
@@ -329,6 +601,12 @@ async function* transformStream(stream) {
329
601
  continue;
330
602
  }
331
603
  if (event.type === "content_block_stop") {
604
+ if (contentBlockTypeByIndex.get(event.index) === "thinking") {
605
+ yield {
606
+ type: "reasoning-end"
607
+ };
608
+ continue;
609
+ }
332
610
  const current = toolBuffers.get(event.index);
333
611
  if (!current || emittedToolCalls.has(event.index)) {
334
612
  continue;
@@ -346,11 +624,17 @@ async function* transformStream(stream) {
346
624
  }
347
625
  if (event.type === "message_delta") {
348
626
  finishReason = mapStopReason(event.delta.stop_reason);
627
+ const nonCachedInputTokens = event.usage.input_tokens ?? usage.inputTokens - usage.inputTokenDetails.cacheReadTokens - usage.inputTokenDetails.cacheWriteTokens;
628
+ const cacheReadTokens = event.usage.cache_read_input_tokens ?? usage.inputTokenDetails.cacheReadTokens;
629
+ const cacheWriteTokens = event.usage.cache_creation_input_tokens ?? usage.inputTokenDetails.cacheWriteTokens;
349
630
  usage = {
350
- inputTokens: event.usage.input_tokens ?? usage.inputTokens,
631
+ inputTokens: nonCachedInputTokens + cacheReadTokens + cacheWriteTokens,
351
632
  outputTokens: event.usage.output_tokens,
352
- reasoningTokens: 0,
353
- totalTokens: (event.usage.input_tokens ?? usage.inputTokens) + event.usage.output_tokens
633
+ inputTokenDetails: {
634
+ cacheReadTokens,
635
+ cacheWriteTokens
636
+ },
637
+ outputTokenDetails: {}
354
638
  };
355
639
  continue;
356
640
  }
@@ -390,6 +674,30 @@ function asObject(value) {
390
674
  }
391
675
  return {};
392
676
  }
677
+ function asStringArray(value) {
678
+ if (!Array.isArray(value)) {
679
+ return [];
680
+ }
681
+ return value.filter((item) => typeof item === "string");
682
+ }
683
+ function uniqueStrings(values) {
684
+ return [...new Set(values)];
685
+ }
686
+ function extractThinkingText(value) {
687
+ if (typeof value === "string") {
688
+ return value;
689
+ }
690
+ if (!Array.isArray(value)) {
691
+ return "";
692
+ }
693
+ return value.flatMap((item) => {
694
+ if (!item || typeof item !== "object") {
695
+ return [];
696
+ }
697
+ const text = item.text;
698
+ return typeof text === "string" ? [text] : [];
699
+ }).join("");
700
+ }
393
701
  function wrapError(error) {
394
702
  if (error instanceof APIError) {
395
703
  return new ProviderError(
@@ -467,7 +775,7 @@ function extractStructuredObject(result, schema, provider) {
467
775
  async function* transformStructuredOutputStream(stream, schema, provider) {
468
776
  let contentBuffer = "";
469
777
  for await (const event of stream) {
470
- if (event.type === "content-delta") {
778
+ if (event.type === "text-delta") {
471
779
  contentBuffer += event.text;
472
780
  yield {
473
781
  type: "object-delta",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/anthropic",
3
- "version": "0.3.0",
3
+ "version": "0.5.1",
4
4
  "description": "Anthropic provider package for @core-ai/core-ai",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",
@@ -38,12 +38,11 @@
38
38
  "build": "tsup",
39
39
  "lint": "eslint src/ --max-warnings 0",
40
40
  "check-types": "tsc --noEmit",
41
- "prepublishOnly": "npm run build",
42
41
  "test": "vitest run",
43
42
  "test:watch": "vitest"
44
43
  },
45
44
  "dependencies": {
46
- "@core-ai/core-ai": "^0.3.0",
45
+ "@core-ai/core-ai": "^0.5.1",
47
46
  "@anthropic-ai/sdk": "^0.78.0",
48
47
  "zod-to-json-schema": "^3.25.1"
49
48
  },