@core-ai/anthropic 0.4.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 +320 -41
  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,27 +361,145 @@ 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
  }
260
491
  const cacheReadTokens = response.usage.cache_read_input_tokens ?? 0;
261
492
  const cacheWriteTokens = response.usage.cache_creation_input_tokens ?? 0;
262
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
+ );
263
499
  return {
500
+ parts,
264
501
  content: content.length > 0 ? content : null,
502
+ reasoning: reasoning.length > 0 ? reasoning : null,
265
503
  toolCalls,
266
504
  finishReason: mapStopReason(response.stop_reason),
267
505
  usage: {
@@ -271,9 +509,7 @@ function mapGenerateResponse(response) {
271
509
  cacheReadTokens,
272
510
  cacheWriteTokens
273
511
  },
274
- outputTokenDetails: {
275
- reasoningTokens: 0
276
- }
512
+ outputTokenDetails: {}
277
513
  }
278
514
  };
279
515
  }
@@ -286,12 +522,11 @@ async function* transformStream(stream) {
286
522
  cacheReadTokens: 0,
287
523
  cacheWriteTokens: 0
288
524
  },
289
- outputTokenDetails: {
290
- reasoningTokens: 0
291
- }
525
+ outputTokenDetails: {}
292
526
  };
293
527
  const toolBuffers = /* @__PURE__ */ new Map();
294
528
  const emittedToolCalls = /* @__PURE__ */ new Set();
529
+ const contentBlockTypeByIndex = /* @__PURE__ */ new Map();
295
530
  for await (const event of stream) {
296
531
  if (event.type === "message_start") {
297
532
  const cacheReadTokens = event.message.usage.cache_read_input_tokens ?? 0;
@@ -304,13 +539,18 @@ async function* transformStream(stream) {
304
539
  cacheReadTokens,
305
540
  cacheWriteTokens
306
541
  },
307
- outputTokenDetails: {
308
- reasoningTokens: 0
309
- }
542
+ outputTokenDetails: {}
310
543
  };
311
544
  continue;
312
545
  }
313
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
+ }
314
554
  if (event.content_block.type === "tool_use") {
315
555
  const block = event.content_block;
316
556
  const initialArguments = block.input && typeof block.input === "object" ? Object.keys(block.input).length > 0 ? JSON.stringify(block.input) : "" : "";
@@ -330,11 +570,22 @@ async function* transformStream(stream) {
330
570
  if (event.type === "content_block_delta") {
331
571
  if (event.delta.type === "text_delta") {
332
572
  yield {
333
- type: "content-delta",
573
+ type: "text-delta",
334
574
  text: event.delta.text
335
575
  };
336
576
  continue;
337
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
+ }
338
589
  if (event.delta.type === "input_json_delta") {
339
590
  const current = toolBuffers.get(event.index);
340
591
  if (!current) {
@@ -350,6 +601,12 @@ async function* transformStream(stream) {
350
601
  continue;
351
602
  }
352
603
  if (event.type === "content_block_stop") {
604
+ if (contentBlockTypeByIndex.get(event.index) === "thinking") {
605
+ yield {
606
+ type: "reasoning-end"
607
+ };
608
+ continue;
609
+ }
353
610
  const current = toolBuffers.get(event.index);
354
611
  if (!current || emittedToolCalls.has(event.index)) {
355
612
  continue;
@@ -377,9 +634,7 @@ async function* transformStream(stream) {
377
634
  cacheReadTokens,
378
635
  cacheWriteTokens
379
636
  },
380
- outputTokenDetails: {
381
- reasoningTokens: 0
382
- }
637
+ outputTokenDetails: {}
383
638
  };
384
639
  continue;
385
640
  }
@@ -419,6 +674,30 @@ function asObject(value) {
419
674
  }
420
675
  return {};
421
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
+ }
422
701
  function wrapError(error) {
423
702
  if (error instanceof APIError) {
424
703
  return new ProviderError(
@@ -496,7 +775,7 @@ function extractStructuredObject(result, schema, provider) {
496
775
  async function* transformStructuredOutputStream(stream, schema, provider) {
497
776
  let contentBuffer = "";
498
777
  for await (const event of stream) {
499
- if (event.type === "content-delta") {
778
+ if (event.type === "text-delta") {
500
779
  contentBuffer += event.text;
501
780
  yield {
502
781
  type: "object-delta",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/anthropic",
3
- "version": "0.4.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.4.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
  },