@aigne/anthropic 0.14.16-beta.9 → 0.14.16

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,274 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.16](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.27...anthropic-v0.14.16) (2026-01-16)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @aigne/core bumped to 1.72.0
11
+ * @aigne/platform-helpers bumped to 0.6.7
12
+ * devDependencies
13
+ * @aigne/test-utils bumped to 0.5.69
14
+
15
+ ## [0.14.16-beta.27](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.26...anthropic-v0.14.16-beta.27) (2026-01-16)
16
+
17
+
18
+ ### Features
19
+
20
+ * add dynamic model options resolution with getter pattern ([#708](https://github.com/AIGNE-io/aigne-framework/issues/708)) ([5ed5085](https://github.com/AIGNE-io/aigne-framework/commit/5ed5085203763c70194853c56edc13acf56d81c6))
21
+ * add modalities support for chat model ([#454](https://github.com/AIGNE-io/aigne-framework/issues/454)) ([70d1bf6](https://github.com/AIGNE-io/aigne-framework/commit/70d1bf631f4e711235d89c6df8ee210a19179b30))
22
+ * add prompt caching for OpenAI/Gemini/Anthropic and cache token display ([#838](https://github.com/AIGNE-io/aigne-framework/issues/838)) ([46c628f](https://github.com/AIGNE-io/aigne-framework/commit/46c628f180572ea1b955d1a9888aad6145204842))
23
+ * add session compact support for AIAgent ([#863](https://github.com/AIGNE-io/aigne-framework/issues/863)) ([9010918](https://github.com/AIGNE-io/aigne-framework/commit/9010918cd3f18b02b5c60ddc9ed5c34b568d0b28))
24
+ * **core:** add automatic JSON parsing and validation for structured outputs ([#548](https://github.com/AIGNE-io/aigne-framework/issues/548)) ([9077f93](https://github.com/AIGNE-io/aigne-framework/commit/9077f93856865915aaf5e8caa5638ef0b7f05b1e))
25
+ * **core:** add nested getter pattern support for model options ([#796](https://github.com/AIGNE-io/aigne-framework/issues/796)) ([824b2fe](https://github.com/AIGNE-io/aigne-framework/commit/824b2fe55cb2a24620e2bb73b470532918fa2996))
26
+ * support custom prefer input file type ([#469](https://github.com/AIGNE-io/aigne-framework/issues/469)) ([db0161b](https://github.com/AIGNE-io/aigne-framework/commit/db0161bbac52542c771ee2f40f361636b0668075))
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * **anthropic:** handle null content blocks in streaming responses ([9fefd6f](https://github.com/AIGNE-io/aigne-framework/commit/9fefd6fcca58bb8a59616560f86a04a0015f6aca))
32
+ * **anthropic:** simplify structured output with output tool pattern ([#899](https://github.com/AIGNE-io/aigne-framework/issues/899)) ([a6033c8](https://github.com/AIGNE-io/aigne-framework/commit/a6033c8a9ccf5e8ff6bcb14bc68c43a990ce2fa2))
33
+ * **anthropic:** update structured output tool name to generate_json ([897e94d](https://github.com/AIGNE-io/aigne-framework/commit/897e94d82a1bbfa46ae13038a58a65cba6a3b259))
34
+ * bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
35
+ * bump version ([70d217c](https://github.com/AIGNE-io/aigne-framework/commit/70d217c8360dd0dda7f5f17011c4e92ec836e801))
36
+ * bump version ([af04b69](https://github.com/AIGNE-io/aigne-framework/commit/af04b6931951afa35d52065430acc7fef4b10087))
37
+ * bump version ([ba7ad18](https://github.com/AIGNE-io/aigne-framework/commit/ba7ad184fcf32b49bf0507a3cb638d20fb00690d))
38
+ * bump version ([93a1c10](https://github.com/AIGNE-io/aigne-framework/commit/93a1c10cf35f88eaafe91092481f5d087bd5b3a9))
39
+ * **core:** default enable auto breakpoints for chat model ([d4a6b83](https://github.com/AIGNE-io/aigne-framework/commit/d4a6b8323d6c83be45669885b32febb545bdf797))
40
+ * **core:** preserve Agent Skill in session compact and support complex tool result content ([#876](https://github.com/AIGNE-io/aigne-framework/issues/876)) ([edb86ae](https://github.com/AIGNE-io/aigne-framework/commit/edb86ae2b9cfe56a8f08b276f843606e310566cf))
41
+ * **core:** simplify token-estimator logic for remaining characters ([45d43cc](https://github.com/AIGNE-io/aigne-framework/commit/45d43ccd3afd636cfb459eea2e6551e8f9c53765))
42
+ * improve test coverage tracking and reporting ([#903](https://github.com/AIGNE-io/aigne-framework/issues/903)) ([031144e](https://github.com/AIGNE-io/aigne-framework/commit/031144e74f29e882cffe52ffda8f7a18c76ace7f))
43
+ * **models:** support cache the last message for anthropic chat model ([#853](https://github.com/AIGNE-io/aigne-framework/issues/853)) ([bd08e44](https://github.com/AIGNE-io/aigne-framework/commit/bd08e44b28c46ac9a85234f0100d6dd144703c9d))
44
+ * **model:** transform local file to base64 before request llm ([#462](https://github.com/AIGNE-io/aigne-framework/issues/462)) ([58ef5d7](https://github.com/AIGNE-io/aigne-framework/commit/58ef5d77046c49f3c4eed15b7f0cc283cbbcd74a))
45
+ * update deps compatibility in CommonJS environment ([#580](https://github.com/AIGNE-io/aigne-framework/issues/580)) ([a1e35d0](https://github.com/AIGNE-io/aigne-framework/commit/a1e35d016405accb51c1aeb6a544503a1c78e912))
46
+
47
+
48
+ ### Dependencies
49
+
50
+ * The following workspace dependencies were updated
51
+ * dependencies
52
+ * @aigne/core bumped to 1.72.0-beta.25
53
+ * devDependencies
54
+ * @aigne/test-utils bumped to 0.5.69-beta.25
55
+
56
+ ## [0.14.16-beta.26](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.25...anthropic-v0.14.16-beta.26) (2026-01-16)
57
+
58
+
59
+ ### Dependencies
60
+
61
+ * The following workspace dependencies were updated
62
+ * dependencies
63
+ * @aigne/core bumped to 1.72.0-beta.24
64
+ * devDependencies
65
+ * @aigne/test-utils bumped to 0.5.69-beta.24
66
+
67
+ ## [0.14.16-beta.25](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.24...anthropic-v0.14.16-beta.25) (2026-01-15)
68
+
69
+
70
+ ### Dependencies
71
+
72
+ * The following workspace dependencies were updated
73
+ * dependencies
74
+ * @aigne/core bumped to 1.72.0-beta.23
75
+ * devDependencies
76
+ * @aigne/test-utils bumped to 0.5.69-beta.23
77
+
78
+ ## [0.14.16-beta.24](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.23...anthropic-v0.14.16-beta.24) (2026-01-15)
79
+
80
+
81
+ ### Dependencies
82
+
83
+ * The following workspace dependencies were updated
84
+ * dependencies
85
+ * @aigne/core bumped to 1.72.0-beta.22
86
+ * devDependencies
87
+ * @aigne/test-utils bumped to 0.5.69-beta.22
88
+
89
+ ## [0.14.16-beta.23](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.22...anthropic-v0.14.16-beta.23) (2026-01-15)
90
+
91
+
92
+ ### Dependencies
93
+
94
+ * The following workspace dependencies were updated
95
+ * dependencies
96
+ * @aigne/core bumped to 1.72.0-beta.21
97
+ * devDependencies
98
+ * @aigne/test-utils bumped to 0.5.69-beta.21
99
+
100
+ ## [0.14.16-beta.22](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.21...anthropic-v0.14.16-beta.22) (2026-01-15)
101
+
102
+
103
+ ### Dependencies
104
+
105
+ * The following workspace dependencies were updated
106
+ * dependencies
107
+ * @aigne/core bumped to 1.72.0-beta.20
108
+ * devDependencies
109
+ * @aigne/test-utils bumped to 0.5.69-beta.20
110
+
111
+ ## [0.14.16-beta.21](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.20...anthropic-v0.14.16-beta.21) (2026-01-14)
112
+
113
+
114
+ ### Bug Fixes
115
+
116
+ * improve test coverage tracking and reporting ([#903](https://github.com/AIGNE-io/aigne-framework/issues/903)) ([031144e](https://github.com/AIGNE-io/aigne-framework/commit/031144e74f29e882cffe52ffda8f7a18c76ace7f))
117
+
118
+
119
+ ### Dependencies
120
+
121
+ * The following workspace dependencies were updated
122
+ * dependencies
123
+ * @aigne/core bumped to 1.72.0-beta.19
124
+ * @aigne/platform-helpers bumped to 0.6.7-beta.2
125
+ * devDependencies
126
+ * @aigne/test-utils bumped to 0.5.69-beta.19
127
+
128
+ ## [0.14.16-beta.20](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.19...anthropic-v0.14.16-beta.20) (2026-01-13)
129
+
130
+
131
+ ### Bug Fixes
132
+
133
+ * **anthropic:** handle null content blocks in streaming responses ([9fefd6f](https://github.com/AIGNE-io/aigne-framework/commit/9fefd6fcca58bb8a59616560f86a04a0015f6aca))
134
+
135
+ ## [0.14.16-beta.19](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.18...anthropic-v0.14.16-beta.19) (2026-01-13)
136
+
137
+
138
+ ### Bug Fixes
139
+
140
+ * **anthropic:** simplify structured output with output tool pattern ([#899](https://github.com/AIGNE-io/aigne-framework/issues/899)) ([a6033c8](https://github.com/AIGNE-io/aigne-framework/commit/a6033c8a9ccf5e8ff6bcb14bc68c43a990ce2fa2))
141
+ * **anthropic:** update structured output tool name to generate_json ([897e94d](https://github.com/AIGNE-io/aigne-framework/commit/897e94d82a1bbfa46ae13038a58a65cba6a3b259))
142
+
143
+
144
+ ### Dependencies
145
+
146
+ * The following workspace dependencies were updated
147
+ * dependencies
148
+ * @aigne/core bumped to 1.72.0-beta.18
149
+ * devDependencies
150
+ * @aigne/test-utils bumped to 0.5.69-beta.18
151
+
152
+ ## [0.14.16-beta.18](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.17...anthropic-v0.14.16-beta.18) (2026-01-12)
153
+
154
+
155
+ ### Dependencies
156
+
157
+ * The following workspace dependencies were updated
158
+ * dependencies
159
+ * @aigne/core bumped to 1.72.0-beta.17
160
+ * devDependencies
161
+ * @aigne/test-utils bumped to 0.5.69-beta.17
162
+
163
+ ## [0.14.16-beta.17](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.16...anthropic-v0.14.16-beta.17) (2026-01-12)
164
+
165
+
166
+ ### Dependencies
167
+
168
+ * The following workspace dependencies were updated
169
+ * dependencies
170
+ * @aigne/core bumped to 1.72.0-beta.16
171
+ * devDependencies
172
+ * @aigne/test-utils bumped to 0.5.69-beta.16
173
+
174
+ ## [0.14.16-beta.16](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.15...anthropic-v0.14.16-beta.16) (2026-01-10)
175
+
176
+
177
+ ### Bug Fixes
178
+
179
+ * **core:** simplify token-estimator logic for remaining characters ([45d43cc](https://github.com/AIGNE-io/aigne-framework/commit/45d43ccd3afd636cfb459eea2e6551e8f9c53765))
180
+
181
+
182
+ ### Dependencies
183
+
184
+ * The following workspace dependencies were updated
185
+ * dependencies
186
+ * @aigne/core bumped to 1.72.0-beta.15
187
+ * devDependencies
188
+ * @aigne/test-utils bumped to 0.5.69-beta.15
189
+
190
+ ## [0.14.16-beta.15](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.14...anthropic-v0.14.16-beta.15) (2026-01-09)
191
+
192
+
193
+ ### Bug Fixes
194
+
195
+ * **core:** default enable auto breakpoints for chat model ([d4a6b83](https://github.com/AIGNE-io/aigne-framework/commit/d4a6b8323d6c83be45669885b32febb545bdf797))
196
+
197
+
198
+ ### Dependencies
199
+
200
+ * The following workspace dependencies were updated
201
+ * dependencies
202
+ * @aigne/core bumped to 1.72.0-beta.14
203
+ * devDependencies
204
+ * @aigne/test-utils bumped to 0.5.69-beta.14
205
+
206
+ ## [0.14.16-beta.14](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.13...anthropic-v0.14.16-beta.14) (2026-01-08)
207
+
208
+
209
+ ### Bug Fixes
210
+
211
+ * bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
212
+
213
+
214
+ ### Dependencies
215
+
216
+ * The following workspace dependencies were updated
217
+ * dependencies
218
+ * @aigne/core bumped to 1.72.0-beta.13
219
+ * @aigne/platform-helpers bumped to 0.6.7-beta.1
220
+ * devDependencies
221
+ * @aigne/test-utils bumped to 0.5.69-beta.13
222
+
223
+ ## [0.14.16-beta.13](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.12...anthropic-v0.14.16-beta.13) (2026-01-07)
224
+
225
+
226
+ ### Dependencies
227
+
228
+ * The following workspace dependencies were updated
229
+ * dependencies
230
+ * @aigne/core bumped to 1.72.0-beta.12
231
+ * devDependencies
232
+ * @aigne/test-utils bumped to 0.5.69-beta.12
233
+
234
+ ## [0.14.16-beta.12](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.11...anthropic-v0.14.16-beta.12) (2026-01-06)
235
+
236
+
237
+ ### Bug Fixes
238
+
239
+ * **core:** preserve Agent Skill in session compact and support complex tool result content ([#876](https://github.com/AIGNE-io/aigne-framework/issues/876)) ([edb86ae](https://github.com/AIGNE-io/aigne-framework/commit/edb86ae2b9cfe56a8f08b276f843606e310566cf))
240
+
241
+
242
+ ### Dependencies
243
+
244
+ * The following workspace dependencies were updated
245
+ * dependencies
246
+ * @aigne/core bumped to 1.72.0-beta.11
247
+ * devDependencies
248
+ * @aigne/test-utils bumped to 0.5.69-beta.11
249
+
250
+ ## [0.14.16-beta.11](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.10...anthropic-v0.14.16-beta.11) (2026-01-06)
251
+
252
+
253
+ ### Dependencies
254
+
255
+ * The following workspace dependencies were updated
256
+ * dependencies
257
+ * @aigne/core bumped to 1.72.0-beta.10
258
+ * devDependencies
259
+ * @aigne/test-utils bumped to 0.5.69-beta.10
260
+
261
+ ## [0.14.16-beta.10](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.9...anthropic-v0.14.16-beta.10) (2026-01-02)
262
+
263
+
264
+ ### Dependencies
265
+
266
+ * The following workspace dependencies were updated
267
+ * dependencies
268
+ * @aigne/core bumped to 1.72.0-beta.9
269
+ * devDependencies
270
+ * @aigne/test-utils bumped to 0.5.69-beta.9
271
+
3
272
  ## [0.14.16-beta.9](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.8...anthropic-v0.14.16-beta.9) (2025-12-31)
4
273
 
5
274
 
@@ -132,14 +132,14 @@ export declare class AnthropicChatModel extends ChatModel {
132
132
  apiKey: string | undefined;
133
133
  model: string;
134
134
  };
135
+ countTokens(input: ChatModelInput): Promise<number>;
136
+ private getMessageCreateParams;
135
137
  private getMaxTokens;
136
138
  /**
137
139
  * Process the input using Claude's chat model
138
140
  * @param input - The input to process
139
141
  * @returns The processed output from the model
140
142
  */
141
- process(input: ChatModelInput, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
142
- private _process;
143
- private extractResultFromAnthropicStream;
144
- private requestStructuredOutput;
143
+ process(input: ChatModelInput, _options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
144
+ private processInput;
145
145
  }
@@ -6,13 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AnthropicChatModel = exports.claudeChatModelOptionsSchema = void 0;
7
7
  const core_1 = require("@aigne/core");
8
8
  const json_schema_js_1 = require("@aigne/core/utils/json-schema.js");
9
- const logger_js_1 = require("@aigne/core/utils/logger.js");
10
- const model_utils_js_1 = require("@aigne/core/utils/model-utils.js");
11
- const stream_utils_js_1 = require("@aigne/core/utils/stream-utils.js");
12
9
  const type_utils_js_1 = require("@aigne/core/utils/type-utils.js");
13
10
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
14
11
  const zod_1 = require("zod");
15
12
  const CHAT_MODEL_CLAUDE_DEFAULT_MODEL = "claude-3-7-sonnet-latest";
13
+ const OUTPUT_FUNCTION_NAME = "generate_json";
16
14
  /**
17
15
  * @hidden
18
16
  */
@@ -82,6 +80,23 @@ class AnthropicChatModel extends core_1.ChatModel {
82
80
  model: this.options?.model || CHAT_MODEL_CLAUDE_DEFAULT_MODEL,
83
81
  };
84
82
  }
83
+ async countTokens(input) {
84
+ const request = await this.getMessageCreateParams(input);
85
+ return (await this.client.messages.countTokens((0, type_utils_js_1.omit)(request, "max_tokens"))).input_tokens;
86
+ }
87
+ async getMessageCreateParams(input) {
88
+ const { modelOptions = {} } = input;
89
+ const model = modelOptions.model || this.credential.model;
90
+ const disableParallelToolUse = modelOptions.parallelToolCalls === false;
91
+ return {
92
+ model,
93
+ temperature: modelOptions.temperature,
94
+ top_p: modelOptions.topP,
95
+ max_tokens: this.getMaxTokens(model),
96
+ ...(await convertMessages(input)),
97
+ ...convertTools({ ...input, disableParallelToolUse }),
98
+ };
99
+ }
85
100
  getMaxTokens(model) {
86
101
  const matchers = [
87
102
  [/claude-opus-4-/, 32000],
@@ -102,164 +117,70 @@ class AnthropicChatModel extends core_1.ChatModel {
102
117
  * @param input - The input to process
103
118
  * @returns The processed output from the model
104
119
  */
105
- process(input, options) {
106
- return this._process(input, options);
120
+ process(input, _options) {
121
+ return this.processInput(input);
107
122
  }
108
- async _process(input, _options) {
109
- const { modelOptions = {} } = input;
110
- const model = modelOptions.model || this.credential.model;
111
- const disableParallelToolUse = modelOptions.parallelToolCalls === false;
112
- const body = {
113
- model,
114
- temperature: modelOptions.temperature,
115
- top_p: modelOptions.topP,
116
- // TODO: make dynamic based on model https://docs.anthropic.com/en/docs/about-claude/models/all-models
117
- max_tokens: this.getMaxTokens(model),
118
- ...(await convertMessages(input)),
119
- ...convertTools({ ...input, disableParallelToolUse }),
120
- };
121
- // Claude does not support json_schema response and tool calls in the same request,
122
- // so we need to handle the case where tools are not used and responseFormat is json
123
- if (!input.tools?.length && input.responseFormat?.type === "json_schema") {
124
- return this.requestStructuredOutput(body, input.responseFormat);
123
+ async *processInput(input) {
124
+ const body = await this.getMessageCreateParams(input);
125
+ const stream = this.client.messages.stream({ ...body, stream: true });
126
+ const blocks = [];
127
+ let usage;
128
+ let json;
129
+ for await (const chunk of stream) {
130
+ if (chunk.type === "message_start") {
131
+ yield { delta: { json: { model: chunk.message.model } } };
132
+ const { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, } = chunk.message.usage;
133
+ usage = {
134
+ inputTokens: input_tokens,
135
+ outputTokens: output_tokens,
136
+ cacheCreationInputTokens: cache_creation_input_tokens ?? undefined,
137
+ cacheReadInputTokens: cache_read_input_tokens ?? undefined,
138
+ };
139
+ }
140
+ if (chunk.type === "message_delta" && usage) {
141
+ usage.outputTokens = chunk.usage.output_tokens;
142
+ }
143
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
144
+ yield { delta: { text: { text: chunk.delta.text } } };
145
+ }
146
+ if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
147
+ blocks[chunk.index] = {
148
+ type: "function",
149
+ id: chunk.content_block.id,
150
+ function: { name: chunk.content_block.name, arguments: {} },
151
+ args: "",
152
+ };
153
+ }
154
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
155
+ const call = blocks[chunk.index];
156
+ if (!call)
157
+ throw new Error("Tool call not found");
158
+ call.args += chunk.delta.partial_json;
159
+ }
125
160
  }
126
- const stream = this.client.messages.stream({
127
- ...body,
128
- stream: true,
129
- });
130
- if (input.responseFormat?.type !== "json_schema") {
131
- return this.extractResultFromAnthropicStream(stream, true);
161
+ const toolCalls = blocks.filter(type_utils_js_1.isNonNullable);
162
+ // Separate output tool from business tool calls
163
+ const outputToolCall = toolCalls.find((c) => c.function.name === OUTPUT_FUNCTION_NAME);
164
+ const businessToolCalls = toolCalls
165
+ .filter((c) => c.function.name !== OUTPUT_FUNCTION_NAME)
166
+ .map(({ args, ...c }) => ({
167
+ ...c,
168
+ function: {
169
+ ...c.function,
170
+ arguments: args.trim() ? (0, json_schema_js_1.parseJSON)(args) : {},
171
+ },
172
+ }))
173
+ .filter(type_utils_js_1.isNonNullable);
174
+ if (outputToolCall) {
175
+ json = outputToolCall.args.trim() ? (0, json_schema_js_1.parseJSON)(outputToolCall.args) : {};
132
176
  }
133
- const result = await this.extractResultFromAnthropicStream(stream);
134
- // Just return the result if it has tool calls
135
- if (result.toolCalls?.length)
136
- return result;
137
- // Try to parse the text response as JSON
138
- // If it matches the json_schema, return it as json
139
- const json = (0, core_1.safeParseJSON)(result.text || "");
140
- const validated = this.validateJsonSchema(input.responseFormat.jsonSchema.schema, json, {
141
- safe: true,
142
- });
143
- if (validated.success) {
144
- return { ...result, json: validated.data, text: undefined };
177
+ if (businessToolCalls.length) {
178
+ yield { delta: { json: { toolCalls: businessToolCalls } } };
145
179
  }
146
- logger_js_1.logger.warn(`AnthropicChatModel: Text response does not match JSON schema, trying to use tool to extract json `, { text: result.text });
147
- // Claude doesn't support json_schema response and tool calls in the same request,
148
- // so we need to make a separate request for json_schema response when the tool calls is empty
149
- const output = await this.requestStructuredOutput(body, input.responseFormat);
150
- return {
151
- ...output,
152
- // merge usage from both requests
153
- usage: (0, model_utils_js_1.mergeUsage)(result.usage, output.usage),
154
- };
155
- }
156
- async extractResultFromAnthropicStream(stream, streaming) {
157
- const result = new ReadableStream({
158
- async start(controller) {
159
- try {
160
- const toolCalls = [];
161
- let usage;
162
- let model;
163
- for await (const chunk of stream) {
164
- if (chunk.type === "message_start") {
165
- if (!model) {
166
- model = chunk.message.model;
167
- controller.enqueue({ delta: { json: { model } } });
168
- }
169
- const { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, } = chunk.message.usage;
170
- usage = {
171
- inputTokens: input_tokens,
172
- outputTokens: output_tokens,
173
- cacheCreationInputTokens: cache_creation_input_tokens ?? undefined,
174
- cacheReadInputTokens: cache_read_input_tokens ?? undefined,
175
- };
176
- }
177
- if (chunk.type === "message_delta" && usage) {
178
- usage.outputTokens = chunk.usage.output_tokens;
179
- }
180
- // handle streaming text
181
- if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
182
- controller.enqueue({
183
- delta: { text: { text: chunk.delta.text } },
184
- });
185
- }
186
- if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
187
- toolCalls[chunk.index] = {
188
- type: "function",
189
- id: chunk.content_block.id,
190
- function: {
191
- name: chunk.content_block.name,
192
- arguments: {},
193
- },
194
- args: "",
195
- };
196
- }
197
- if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
198
- const call = toolCalls[chunk.index];
199
- if (!call)
200
- throw new Error("Tool call not found");
201
- call.args += chunk.delta.partial_json;
202
- }
203
- }
204
- controller.enqueue({ delta: { json: { usage } } });
205
- if (toolCalls.length) {
206
- controller.enqueue({
207
- delta: {
208
- json: {
209
- toolCalls: toolCalls
210
- .map(({ args, ...c }) => ({
211
- ...c,
212
- function: {
213
- ...c.function,
214
- // NOTE: claude may return a blank string for empty object (the tool's input schema is a empty object)
215
- arguments: args.trim() ? (0, json_schema_js_1.parseJSON)(args) : {},
216
- },
217
- }))
218
- .filter(type_utils_js_1.isNonNullable),
219
- },
220
- },
221
- });
222
- }
223
- controller.close();
224
- }
225
- catch (error) {
226
- controller.error(error);
227
- }
228
- },
229
- });
230
- return streaming ? result : await (0, stream_utils_js_1.agentResponseStreamToObject)(result);
231
- }
232
- async requestStructuredOutput(body, responseFormat) {
233
- if (responseFormat?.type !== "json_schema") {
234
- throw new Error("Expected json_schema response format");
180
+ if (json !== undefined) {
181
+ yield { delta: { json: { json: json } } };
235
182
  }
236
- const result = await this.client.messages.create({
237
- ...body,
238
- tools: [
239
- {
240
- name: "generate_json",
241
- description: "Generate a json result by given context",
242
- input_schema: responseFormat.jsonSchema.schema,
243
- },
244
- ],
245
- tool_choice: {
246
- type: "tool",
247
- name: "generate_json",
248
- disable_parallel_tool_use: true,
249
- },
250
- stream: false,
251
- });
252
- const jsonTool = result.content.find((i) => i.type === "tool_use" && i.name === "generate_json");
253
- if (!jsonTool)
254
- throw new Error("Json tool not found");
255
- return {
256
- json: jsonTool.input,
257
- model: result.model,
258
- usage: {
259
- inputTokens: result.usage.input_tokens,
260
- outputTokens: result.usage.output_tokens,
261
- },
262
- };
183
+ yield { delta: { json: { usage } } };
263
184
  }
264
185
  }
265
186
  exports.AnthropicChatModel = AnthropicChatModel;
@@ -283,7 +204,7 @@ function parseCacheConfig(modelOptions) {
283
204
  autoBreakpoints,
284
205
  };
285
206
  }
286
- async function convertMessages({ messages, responseFormat, tools, modelOptions, }) {
207
+ async function convertMessages({ messages, modelOptions }) {
287
208
  const systemBlocks = [];
288
209
  const msgs = [];
289
210
  // Extract cache configuration with defaults
@@ -312,15 +233,15 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
312
233
  else if (msg.role === "tool") {
313
234
  if (!msg.toolCallId)
314
235
  throw new Error("Tool message must have toolCallId");
315
- if (typeof msg.content !== "string")
316
- throw new Error("Tool message must have string content");
236
+ if (!msg.content)
237
+ throw new Error("Tool message must have content");
317
238
  msgs.push({
318
239
  role: "user",
319
240
  content: [
320
241
  {
321
242
  type: "tool_result",
322
243
  tool_use_id: msg.toolCallId,
323
- content: msg.content,
244
+ content: await convertContent(msg.content),
324
245
  },
325
246
  ],
326
247
  });
@@ -350,14 +271,6 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
350
271
  }
351
272
  }
352
273
  }
353
- // If there are tools and responseFormat is json_schema, we need to add a system message
354
- // to inform the model about the expected json schema, then trying to parse the response as json
355
- if (tools?.length && responseFormat?.type === "json_schema") {
356
- systemBlocks.push({
357
- type: "text",
358
- text: `You should provide a json response with schema: ${JSON.stringify(responseFormat.jsonSchema.schema)}`,
359
- });
360
- }
361
274
  // Apply cache_control to the last system block if auto strategy is enabled
362
275
  if (shouldCache && strategy === "auto") {
363
276
  if (autoBreakpoints.system && systemBlocks.length > 0) {
@@ -436,61 +349,64 @@ async function convertContent(content) {
436
349
  }
437
350
  throw new Error("Invalid chat message content");
438
351
  }
439
- function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, }) {
440
- let choice;
441
- if (typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function") {
442
- choice = {
443
- type: "tool",
444
- name: toolChoice.function.name,
445
- disable_parallel_tool_use: disableParallelToolUse,
446
- };
447
- }
448
- else if (toolChoice === "required") {
449
- choice = { type: "any", disable_parallel_tool_use: disableParallelToolUse };
450
- }
451
- else if (toolChoice === "auto") {
452
- choice = {
453
- type: "auto",
454
- disable_parallel_tool_use: disableParallelToolUse,
455
- };
456
- }
457
- else if (toolChoice === "none") {
458
- choice = { type: "none" };
459
- }
352
+ function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, responseFormat, }) {
460
353
  // Extract cache configuration with defaults
461
354
  const { shouldCache, ttl, strategy, autoBreakpoints } = parseCacheConfig(modelOptions);
462
355
  const shouldCacheTools = shouldCache && strategy === "auto" && autoBreakpoints.tools;
356
+ // Convert business tools
357
+ const convertedTools = (tools ?? []).map((i) => {
358
+ const tool = {
359
+ name: i.function.name,
360
+ description: i.function.description,
361
+ input_schema: (0, type_utils_js_1.isEmpty)(i.function.parameters)
362
+ ? { type: "object" }
363
+ : i.function.parameters,
364
+ };
365
+ // Manual cache mode: apply tool-specific cacheControl
366
+ if (shouldCache && strategy === "manual" && i.cacheControl) {
367
+ tool.cache_control = {
368
+ type: i.cacheControl.type,
369
+ ...(i.cacheControl.ttl && { ttl: i.cacheControl.ttl }),
370
+ };
371
+ }
372
+ return tool;
373
+ });
374
+ // Add output tool for structured output
375
+ if (responseFormat?.type === "json_schema") {
376
+ convertedTools.push({
377
+ name: OUTPUT_FUNCTION_NAME,
378
+ description: "Generate a json result by given context",
379
+ input_schema: responseFormat.jsonSchema.schema,
380
+ });
381
+ }
382
+ // Auto cache mode: add cache_control to the last tool
383
+ if (shouldCacheTools && convertedTools.length) {
384
+ const lastTool = convertedTools[convertedTools.length - 1];
385
+ if (lastTool) {
386
+ lastTool.cache_control = { type: "ephemeral", ...(ttl === "1h" && { ttl: "1h" }) };
387
+ }
388
+ }
389
+ // Determine tool choice
390
+ const choice = responseFormat?.type === "json_schema"
391
+ ? // For structured output: force output tool if no business tools, otherwise let model choose
392
+ tools?.length
393
+ ? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
394
+ : { type: "tool", name: OUTPUT_FUNCTION_NAME, disable_parallel_tool_use: true }
395
+ : typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function"
396
+ ? {
397
+ type: "tool",
398
+ name: toolChoice.function.name,
399
+ disable_parallel_tool_use: disableParallelToolUse,
400
+ }
401
+ : toolChoice === "required"
402
+ ? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
403
+ : toolChoice === "auto"
404
+ ? { type: "auto", disable_parallel_tool_use: disableParallelToolUse }
405
+ : toolChoice === "none"
406
+ ? { type: "none" }
407
+ : undefined;
463
408
  return {
464
- tools: tools?.length
465
- ? tools.map((i, index, arr) => {
466
- const tool = {
467
- name: i.function.name,
468
- description: i.function.description,
469
- input_schema: (0, type_utils_js_1.isEmpty)(i.function.parameters)
470
- ? { type: "object" }
471
- : i.function.parameters,
472
- };
473
- // Auto mode: add cache_control to the last tool
474
- if (shouldCacheTools && index === arr.length - 1) {
475
- tool.cache_control = { type: "ephemeral" };
476
- if (ttl === "1h") {
477
- tool.cache_control = { type: "ephemeral", ttl: "1h" };
478
- }
479
- }
480
- // Manual mode: use tool-specific cacheControl if provided
481
- else if (shouldCache && strategy === "manual") {
482
- const toolWithCache = i;
483
- if (toolWithCache.cacheControl) {
484
- tool.cache_control = {
485
- type: toolWithCache.cacheControl.type,
486
- ...(toolWithCache.cacheControl.ttl && { ttl: toolWithCache.cacheControl.ttl }),
487
- };
488
- }
489
- }
490
- return tool;
491
- })
492
- : undefined,
409
+ tools: convertedTools.length ? convertedTools : undefined,
493
410
  tool_choice: choice,
494
411
  };
495
412
  }
496
- // safeParseJSON is now imported from @aigne/core
@@ -132,14 +132,14 @@ export declare class AnthropicChatModel extends ChatModel {
132
132
  apiKey: string | undefined;
133
133
  model: string;
134
134
  };
135
+ countTokens(input: ChatModelInput): Promise<number>;
136
+ private getMessageCreateParams;
135
137
  private getMaxTokens;
136
138
  /**
137
139
  * Process the input using Claude's chat model
138
140
  * @param input - The input to process
139
141
  * @returns The processed output from the model
140
142
  */
141
- process(input: ChatModelInput, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
142
- private _process;
143
- private extractResultFromAnthropicStream;
144
- private requestStructuredOutput;
143
+ process(input: ChatModelInput, _options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
144
+ private processInput;
145
145
  }
@@ -132,14 +132,14 @@ export declare class AnthropicChatModel extends ChatModel {
132
132
  apiKey: string | undefined;
133
133
  model: string;
134
134
  };
135
+ countTokens(input: ChatModelInput): Promise<number>;
136
+ private getMessageCreateParams;
135
137
  private getMaxTokens;
136
138
  /**
137
139
  * Process the input using Claude's chat model
138
140
  * @param input - The input to process
139
141
  * @returns The processed output from the model
140
142
  */
141
- process(input: ChatModelInput, options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
142
- private _process;
143
- private extractResultFromAnthropicStream;
144
- private requestStructuredOutput;
143
+ process(input: ChatModelInput, _options: AgentInvokeOptions): PromiseOrValue<AgentProcessResult<ChatModelOutput>>;
144
+ private processInput;
145
145
  }
@@ -1,12 +1,10 @@
1
- import { ChatModel, safeParseJSON, } from "@aigne/core";
1
+ import { ChatModel, } from "@aigne/core";
2
2
  import { parseJSON } from "@aigne/core/utils/json-schema.js";
3
- import { logger } from "@aigne/core/utils/logger.js";
4
- import { mergeUsage } from "@aigne/core/utils/model-utils.js";
5
- import { agentResponseStreamToObject } from "@aigne/core/utils/stream-utils.js";
6
- import { checkArguments, isEmpty, isNonNullable, } from "@aigne/core/utils/type-utils.js";
3
+ import { checkArguments, isEmpty, isNonNullable, omit, } from "@aigne/core/utils/type-utils.js";
7
4
  import Anthropic from "@anthropic-ai/sdk";
8
5
  import { z } from "zod";
9
6
  const CHAT_MODEL_CLAUDE_DEFAULT_MODEL = "claude-3-7-sonnet-latest";
7
+ const OUTPUT_FUNCTION_NAME = "generate_json";
10
8
  /**
11
9
  * @hidden
12
10
  */
@@ -76,6 +74,23 @@ export class AnthropicChatModel extends ChatModel {
76
74
  model: this.options?.model || CHAT_MODEL_CLAUDE_DEFAULT_MODEL,
77
75
  };
78
76
  }
77
+ async countTokens(input) {
78
+ const request = await this.getMessageCreateParams(input);
79
+ return (await this.client.messages.countTokens(omit(request, "max_tokens"))).input_tokens;
80
+ }
81
+ async getMessageCreateParams(input) {
82
+ const { modelOptions = {} } = input;
83
+ const model = modelOptions.model || this.credential.model;
84
+ const disableParallelToolUse = modelOptions.parallelToolCalls === false;
85
+ return {
86
+ model,
87
+ temperature: modelOptions.temperature,
88
+ top_p: modelOptions.topP,
89
+ max_tokens: this.getMaxTokens(model),
90
+ ...(await convertMessages(input)),
91
+ ...convertTools({ ...input, disableParallelToolUse }),
92
+ };
93
+ }
79
94
  getMaxTokens(model) {
80
95
  const matchers = [
81
96
  [/claude-opus-4-/, 32000],
@@ -96,164 +111,70 @@ export class AnthropicChatModel extends ChatModel {
96
111
  * @param input - The input to process
97
112
  * @returns The processed output from the model
98
113
  */
99
- process(input, options) {
100
- return this._process(input, options);
114
+ process(input, _options) {
115
+ return this.processInput(input);
101
116
  }
102
- async _process(input, _options) {
103
- const { modelOptions = {} } = input;
104
- const model = modelOptions.model || this.credential.model;
105
- const disableParallelToolUse = modelOptions.parallelToolCalls === false;
106
- const body = {
107
- model,
108
- temperature: modelOptions.temperature,
109
- top_p: modelOptions.topP,
110
- // TODO: make dynamic based on model https://docs.anthropic.com/en/docs/about-claude/models/all-models
111
- max_tokens: this.getMaxTokens(model),
112
- ...(await convertMessages(input)),
113
- ...convertTools({ ...input, disableParallelToolUse }),
114
- };
115
- // Claude does not support json_schema response and tool calls in the same request,
116
- // so we need to handle the case where tools are not used and responseFormat is json
117
- if (!input.tools?.length && input.responseFormat?.type === "json_schema") {
118
- return this.requestStructuredOutput(body, input.responseFormat);
117
+ async *processInput(input) {
118
+ const body = await this.getMessageCreateParams(input);
119
+ const stream = this.client.messages.stream({ ...body, stream: true });
120
+ const blocks = [];
121
+ let usage;
122
+ let json;
123
+ for await (const chunk of stream) {
124
+ if (chunk.type === "message_start") {
125
+ yield { delta: { json: { model: chunk.message.model } } };
126
+ const { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, } = chunk.message.usage;
127
+ usage = {
128
+ inputTokens: input_tokens,
129
+ outputTokens: output_tokens,
130
+ cacheCreationInputTokens: cache_creation_input_tokens ?? undefined,
131
+ cacheReadInputTokens: cache_read_input_tokens ?? undefined,
132
+ };
133
+ }
134
+ if (chunk.type === "message_delta" && usage) {
135
+ usage.outputTokens = chunk.usage.output_tokens;
136
+ }
137
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
138
+ yield { delta: { text: { text: chunk.delta.text } } };
139
+ }
140
+ if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
141
+ blocks[chunk.index] = {
142
+ type: "function",
143
+ id: chunk.content_block.id,
144
+ function: { name: chunk.content_block.name, arguments: {} },
145
+ args: "",
146
+ };
147
+ }
148
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
149
+ const call = blocks[chunk.index];
150
+ if (!call)
151
+ throw new Error("Tool call not found");
152
+ call.args += chunk.delta.partial_json;
153
+ }
119
154
  }
120
- const stream = this.client.messages.stream({
121
- ...body,
122
- stream: true,
123
- });
124
- if (input.responseFormat?.type !== "json_schema") {
125
- return this.extractResultFromAnthropicStream(stream, true);
155
+ const toolCalls = blocks.filter(isNonNullable);
156
+ // Separate output tool from business tool calls
157
+ const outputToolCall = toolCalls.find((c) => c.function.name === OUTPUT_FUNCTION_NAME);
158
+ const businessToolCalls = toolCalls
159
+ .filter((c) => c.function.name !== OUTPUT_FUNCTION_NAME)
160
+ .map(({ args, ...c }) => ({
161
+ ...c,
162
+ function: {
163
+ ...c.function,
164
+ arguments: args.trim() ? parseJSON(args) : {},
165
+ },
166
+ }))
167
+ .filter(isNonNullable);
168
+ if (outputToolCall) {
169
+ json = outputToolCall.args.trim() ? parseJSON(outputToolCall.args) : {};
126
170
  }
127
- const result = await this.extractResultFromAnthropicStream(stream);
128
- // Just return the result if it has tool calls
129
- if (result.toolCalls?.length)
130
- return result;
131
- // Try to parse the text response as JSON
132
- // If it matches the json_schema, return it as json
133
- const json = safeParseJSON(result.text || "");
134
- const validated = this.validateJsonSchema(input.responseFormat.jsonSchema.schema, json, {
135
- safe: true,
136
- });
137
- if (validated.success) {
138
- return { ...result, json: validated.data, text: undefined };
171
+ if (businessToolCalls.length) {
172
+ yield { delta: { json: { toolCalls: businessToolCalls } } };
139
173
  }
140
- logger.warn(`AnthropicChatModel: Text response does not match JSON schema, trying to use tool to extract json `, { text: result.text });
141
- // Claude doesn't support json_schema response and tool calls in the same request,
142
- // so we need to make a separate request for json_schema response when the tool calls is empty
143
- const output = await this.requestStructuredOutput(body, input.responseFormat);
144
- return {
145
- ...output,
146
- // merge usage from both requests
147
- usage: mergeUsage(result.usage, output.usage),
148
- };
149
- }
150
- async extractResultFromAnthropicStream(stream, streaming) {
151
- const result = new ReadableStream({
152
- async start(controller) {
153
- try {
154
- const toolCalls = [];
155
- let usage;
156
- let model;
157
- for await (const chunk of stream) {
158
- if (chunk.type === "message_start") {
159
- if (!model) {
160
- model = chunk.message.model;
161
- controller.enqueue({ delta: { json: { model } } });
162
- }
163
- const { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, } = chunk.message.usage;
164
- usage = {
165
- inputTokens: input_tokens,
166
- outputTokens: output_tokens,
167
- cacheCreationInputTokens: cache_creation_input_tokens ?? undefined,
168
- cacheReadInputTokens: cache_read_input_tokens ?? undefined,
169
- };
170
- }
171
- if (chunk.type === "message_delta" && usage) {
172
- usage.outputTokens = chunk.usage.output_tokens;
173
- }
174
- // handle streaming text
175
- if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
176
- controller.enqueue({
177
- delta: { text: { text: chunk.delta.text } },
178
- });
179
- }
180
- if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
181
- toolCalls[chunk.index] = {
182
- type: "function",
183
- id: chunk.content_block.id,
184
- function: {
185
- name: chunk.content_block.name,
186
- arguments: {},
187
- },
188
- args: "",
189
- };
190
- }
191
- if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
192
- const call = toolCalls[chunk.index];
193
- if (!call)
194
- throw new Error("Tool call not found");
195
- call.args += chunk.delta.partial_json;
196
- }
197
- }
198
- controller.enqueue({ delta: { json: { usage } } });
199
- if (toolCalls.length) {
200
- controller.enqueue({
201
- delta: {
202
- json: {
203
- toolCalls: toolCalls
204
- .map(({ args, ...c }) => ({
205
- ...c,
206
- function: {
207
- ...c.function,
208
- // NOTE: claude may return a blank string for empty object (the tool's input schema is a empty object)
209
- arguments: args.trim() ? parseJSON(args) : {},
210
- },
211
- }))
212
- .filter(isNonNullable),
213
- },
214
- },
215
- });
216
- }
217
- controller.close();
218
- }
219
- catch (error) {
220
- controller.error(error);
221
- }
222
- },
223
- });
224
- return streaming ? result : await agentResponseStreamToObject(result);
225
- }
226
- async requestStructuredOutput(body, responseFormat) {
227
- if (responseFormat?.type !== "json_schema") {
228
- throw new Error("Expected json_schema response format");
174
+ if (json !== undefined) {
175
+ yield { delta: { json: { json: json } } };
229
176
  }
230
- const result = await this.client.messages.create({
231
- ...body,
232
- tools: [
233
- {
234
- name: "generate_json",
235
- description: "Generate a json result by given context",
236
- input_schema: responseFormat.jsonSchema.schema,
237
- },
238
- ],
239
- tool_choice: {
240
- type: "tool",
241
- name: "generate_json",
242
- disable_parallel_tool_use: true,
243
- },
244
- stream: false,
245
- });
246
- const jsonTool = result.content.find((i) => i.type === "tool_use" && i.name === "generate_json");
247
- if (!jsonTool)
248
- throw new Error("Json tool not found");
249
- return {
250
- json: jsonTool.input,
251
- model: result.model,
252
- usage: {
253
- inputTokens: result.usage.input_tokens,
254
- outputTokens: result.usage.output_tokens,
255
- },
256
- };
177
+ yield { delta: { json: { usage } } };
257
178
  }
258
179
  }
259
180
  /**
@@ -276,7 +197,7 @@ function parseCacheConfig(modelOptions) {
276
197
  autoBreakpoints,
277
198
  };
278
199
  }
279
- async function convertMessages({ messages, responseFormat, tools, modelOptions, }) {
200
+ async function convertMessages({ messages, modelOptions }) {
280
201
  const systemBlocks = [];
281
202
  const msgs = [];
282
203
  // Extract cache configuration with defaults
@@ -305,15 +226,15 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
305
226
  else if (msg.role === "tool") {
306
227
  if (!msg.toolCallId)
307
228
  throw new Error("Tool message must have toolCallId");
308
- if (typeof msg.content !== "string")
309
- throw new Error("Tool message must have string content");
229
+ if (!msg.content)
230
+ throw new Error("Tool message must have content");
310
231
  msgs.push({
311
232
  role: "user",
312
233
  content: [
313
234
  {
314
235
  type: "tool_result",
315
236
  tool_use_id: msg.toolCallId,
316
- content: msg.content,
237
+ content: await convertContent(msg.content),
317
238
  },
318
239
  ],
319
240
  });
@@ -343,14 +264,6 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
343
264
  }
344
265
  }
345
266
  }
346
- // If there are tools and responseFormat is json_schema, we need to add a system message
347
- // to inform the model about the expected json schema, then trying to parse the response as json
348
- if (tools?.length && responseFormat?.type === "json_schema") {
349
- systemBlocks.push({
350
- type: "text",
351
- text: `You should provide a json response with schema: ${JSON.stringify(responseFormat.jsonSchema.schema)}`,
352
- });
353
- }
354
267
  // Apply cache_control to the last system block if auto strategy is enabled
355
268
  if (shouldCache && strategy === "auto") {
356
269
  if (autoBreakpoints.system && systemBlocks.length > 0) {
@@ -429,61 +342,64 @@ async function convertContent(content) {
429
342
  }
430
343
  throw new Error("Invalid chat message content");
431
344
  }
432
- function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, }) {
433
- let choice;
434
- if (typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function") {
435
- choice = {
436
- type: "tool",
437
- name: toolChoice.function.name,
438
- disable_parallel_tool_use: disableParallelToolUse,
439
- };
440
- }
441
- else if (toolChoice === "required") {
442
- choice = { type: "any", disable_parallel_tool_use: disableParallelToolUse };
443
- }
444
- else if (toolChoice === "auto") {
445
- choice = {
446
- type: "auto",
447
- disable_parallel_tool_use: disableParallelToolUse,
448
- };
449
- }
450
- else if (toolChoice === "none") {
451
- choice = { type: "none" };
452
- }
345
+ function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, responseFormat, }) {
453
346
  // Extract cache configuration with defaults
454
347
  const { shouldCache, ttl, strategy, autoBreakpoints } = parseCacheConfig(modelOptions);
455
348
  const shouldCacheTools = shouldCache && strategy === "auto" && autoBreakpoints.tools;
349
+ // Convert business tools
350
+ const convertedTools = (tools ?? []).map((i) => {
351
+ const tool = {
352
+ name: i.function.name,
353
+ description: i.function.description,
354
+ input_schema: isEmpty(i.function.parameters)
355
+ ? { type: "object" }
356
+ : i.function.parameters,
357
+ };
358
+ // Manual cache mode: apply tool-specific cacheControl
359
+ if (shouldCache && strategy === "manual" && i.cacheControl) {
360
+ tool.cache_control = {
361
+ type: i.cacheControl.type,
362
+ ...(i.cacheControl.ttl && { ttl: i.cacheControl.ttl }),
363
+ };
364
+ }
365
+ return tool;
366
+ });
367
+ // Add output tool for structured output
368
+ if (responseFormat?.type === "json_schema") {
369
+ convertedTools.push({
370
+ name: OUTPUT_FUNCTION_NAME,
371
+ description: "Generate a json result by given context",
372
+ input_schema: responseFormat.jsonSchema.schema,
373
+ });
374
+ }
375
+ // Auto cache mode: add cache_control to the last tool
376
+ if (shouldCacheTools && convertedTools.length) {
377
+ const lastTool = convertedTools[convertedTools.length - 1];
378
+ if (lastTool) {
379
+ lastTool.cache_control = { type: "ephemeral", ...(ttl === "1h" && { ttl: "1h" }) };
380
+ }
381
+ }
382
+ // Determine tool choice
383
+ const choice = responseFormat?.type === "json_schema"
384
+ ? // For structured output: force output tool if no business tools, otherwise let model choose
385
+ tools?.length
386
+ ? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
387
+ : { type: "tool", name: OUTPUT_FUNCTION_NAME, disable_parallel_tool_use: true }
388
+ : typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function"
389
+ ? {
390
+ type: "tool",
391
+ name: toolChoice.function.name,
392
+ disable_parallel_tool_use: disableParallelToolUse,
393
+ }
394
+ : toolChoice === "required"
395
+ ? { type: "any", disable_parallel_tool_use: disableParallelToolUse }
396
+ : toolChoice === "auto"
397
+ ? { type: "auto", disable_parallel_tool_use: disableParallelToolUse }
398
+ : toolChoice === "none"
399
+ ? { type: "none" }
400
+ : undefined;
456
401
  return {
457
- tools: tools?.length
458
- ? tools.map((i, index, arr) => {
459
- const tool = {
460
- name: i.function.name,
461
- description: i.function.description,
462
- input_schema: isEmpty(i.function.parameters)
463
- ? { type: "object" }
464
- : i.function.parameters,
465
- };
466
- // Auto mode: add cache_control to the last tool
467
- if (shouldCacheTools && index === arr.length - 1) {
468
- tool.cache_control = { type: "ephemeral" };
469
- if (ttl === "1h") {
470
- tool.cache_control = { type: "ephemeral", ttl: "1h" };
471
- }
472
- }
473
- // Manual mode: use tool-specific cacheControl if provided
474
- else if (shouldCache && strategy === "manual") {
475
- const toolWithCache = i;
476
- if (toolWithCache.cacheControl) {
477
- tool.cache_control = {
478
- type: toolWithCache.cacheControl.type,
479
- ...(toolWithCache.cacheControl.ttl && { ttl: toolWithCache.cacheControl.ttl }),
480
- };
481
- }
482
- }
483
- return tool;
484
- })
485
- : undefined,
402
+ tools: convertedTools.length ? convertedTools : undefined,
486
403
  tool_choice: choice,
487
404
  };
488
405
  }
489
- // safeParseJSON is now imported from @aigne/core
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/anthropic",
3
- "version": "0.14.16-beta.9",
3
+ "version": "0.14.16",
4
4
  "description": "AIGNE Anthropic SDK for integrating with Claude AI models",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -37,8 +37,8 @@
37
37
  "dependencies": {
38
38
  "@anthropic-ai/sdk": "^0.63.0",
39
39
  "zod": "^3.25.67",
40
- "@aigne/core": "^1.72.0-beta.8",
41
- "@aigne/platform-helpers": "^0.6.7-beta"
40
+ "@aigne/core": "^1.72.0",
41
+ "@aigne/platform-helpers": "^0.6.7"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/bun": "^1.2.22",
@@ -46,7 +46,7 @@
46
46
  "npm-run-all": "^4.1.5",
47
47
  "rimraf": "^6.0.1",
48
48
  "typescript": "^5.9.2",
49
- "@aigne/test-utils": "^0.5.69-beta.8"
49
+ "@aigne/test-utils": "^0.5.69"
50
50
  },
51
51
  "scripts": {
52
52
  "lint": "tsc --noEmit",