@aigne/anthropic 0.14.16-beta.9 → 0.14.17-beta

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,285 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.17-beta](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16...anthropic-v0.14.17-beta) (2026-01-20)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @aigne/core bumped to 1.73.0-beta
11
+ * devDependencies
12
+ * @aigne/test-utils bumped to 0.5.70-beta
13
+
14
+ ## [0.14.16](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.27...anthropic-v0.14.16) (2026-01-16)
15
+
16
+
17
+ ### Dependencies
18
+
19
+ * The following workspace dependencies were updated
20
+ * dependencies
21
+ * @aigne/core bumped to 1.72.0
22
+ * @aigne/platform-helpers bumped to 0.6.7
23
+ * devDependencies
24
+ * @aigne/test-utils bumped to 0.5.69
25
+
26
+ ## [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)
27
+
28
+
29
+ ### Features
30
+
31
+ * 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))
32
+ * 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))
33
+ * 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))
34
+ * 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))
35
+ * **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))
36
+ * **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))
37
+ * 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))
38
+
39
+
40
+ ### Bug Fixes
41
+
42
+ * **anthropic:** handle null content blocks in streaming responses ([9fefd6f](https://github.com/AIGNE-io/aigne-framework/commit/9fefd6fcca58bb8a59616560f86a04a0015f6aca))
43
+ * **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))
44
+ * **anthropic:** update structured output tool name to generate_json ([897e94d](https://github.com/AIGNE-io/aigne-framework/commit/897e94d82a1bbfa46ae13038a58a65cba6a3b259))
45
+ * bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
46
+ * bump version ([70d217c](https://github.com/AIGNE-io/aigne-framework/commit/70d217c8360dd0dda7f5f17011c4e92ec836e801))
47
+ * bump version ([af04b69](https://github.com/AIGNE-io/aigne-framework/commit/af04b6931951afa35d52065430acc7fef4b10087))
48
+ * bump version ([ba7ad18](https://github.com/AIGNE-io/aigne-framework/commit/ba7ad184fcf32b49bf0507a3cb638d20fb00690d))
49
+ * bump version ([93a1c10](https://github.com/AIGNE-io/aigne-framework/commit/93a1c10cf35f88eaafe91092481f5d087bd5b3a9))
50
+ * **core:** default enable auto breakpoints for chat model ([d4a6b83](https://github.com/AIGNE-io/aigne-framework/commit/d4a6b8323d6c83be45669885b32febb545bdf797))
51
+ * **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))
52
+ * **core:** simplify token-estimator logic for remaining characters ([45d43cc](https://github.com/AIGNE-io/aigne-framework/commit/45d43ccd3afd636cfb459eea2e6551e8f9c53765))
53
+ * 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))
54
+ * **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))
55
+ * **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))
56
+ * 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))
57
+
58
+
59
+ ### Dependencies
60
+
61
+ * The following workspace dependencies were updated
62
+ * dependencies
63
+ * @aigne/core bumped to 1.72.0-beta.25
64
+ * devDependencies
65
+ * @aigne/test-utils bumped to 0.5.69-beta.25
66
+
67
+ ## [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)
68
+
69
+
70
+ ### Dependencies
71
+
72
+ * The following workspace dependencies were updated
73
+ * dependencies
74
+ * @aigne/core bumped to 1.72.0-beta.24
75
+ * devDependencies
76
+ * @aigne/test-utils bumped to 0.5.69-beta.24
77
+
78
+ ## [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)
79
+
80
+
81
+ ### Dependencies
82
+
83
+ * The following workspace dependencies were updated
84
+ * dependencies
85
+ * @aigne/core bumped to 1.72.0-beta.23
86
+ * devDependencies
87
+ * @aigne/test-utils bumped to 0.5.69-beta.23
88
+
89
+ ## [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)
90
+
91
+
92
+ ### Dependencies
93
+
94
+ * The following workspace dependencies were updated
95
+ * dependencies
96
+ * @aigne/core bumped to 1.72.0-beta.22
97
+ * devDependencies
98
+ * @aigne/test-utils bumped to 0.5.69-beta.22
99
+
100
+ ## [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)
101
+
102
+
103
+ ### Dependencies
104
+
105
+ * The following workspace dependencies were updated
106
+ * dependencies
107
+ * @aigne/core bumped to 1.72.0-beta.21
108
+ * devDependencies
109
+ * @aigne/test-utils bumped to 0.5.69-beta.21
110
+
111
+ ## [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)
112
+
113
+
114
+ ### Dependencies
115
+
116
+ * The following workspace dependencies were updated
117
+ * dependencies
118
+ * @aigne/core bumped to 1.72.0-beta.20
119
+ * devDependencies
120
+ * @aigne/test-utils bumped to 0.5.69-beta.20
121
+
122
+ ## [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)
123
+
124
+
125
+ ### Bug Fixes
126
+
127
+ * 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))
128
+
129
+
130
+ ### Dependencies
131
+
132
+ * The following workspace dependencies were updated
133
+ * dependencies
134
+ * @aigne/core bumped to 1.72.0-beta.19
135
+ * @aigne/platform-helpers bumped to 0.6.7-beta.2
136
+ * devDependencies
137
+ * @aigne/test-utils bumped to 0.5.69-beta.19
138
+
139
+ ## [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)
140
+
141
+
142
+ ### Bug Fixes
143
+
144
+ * **anthropic:** handle null content blocks in streaming responses ([9fefd6f](https://github.com/AIGNE-io/aigne-framework/commit/9fefd6fcca58bb8a59616560f86a04a0015f6aca))
145
+
146
+ ## [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)
147
+
148
+
149
+ ### Bug Fixes
150
+
151
+ * **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))
152
+ * **anthropic:** update structured output tool name to generate_json ([897e94d](https://github.com/AIGNE-io/aigne-framework/commit/897e94d82a1bbfa46ae13038a58a65cba6a3b259))
153
+
154
+
155
+ ### Dependencies
156
+
157
+ * The following workspace dependencies were updated
158
+ * dependencies
159
+ * @aigne/core bumped to 1.72.0-beta.18
160
+ * devDependencies
161
+ * @aigne/test-utils bumped to 0.5.69-beta.18
162
+
163
+ ## [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)
164
+
165
+
166
+ ### Dependencies
167
+
168
+ * The following workspace dependencies were updated
169
+ * dependencies
170
+ * @aigne/core bumped to 1.72.0-beta.17
171
+ * devDependencies
172
+ * @aigne/test-utils bumped to 0.5.69-beta.17
173
+
174
+ ## [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)
175
+
176
+
177
+ ### Dependencies
178
+
179
+ * The following workspace dependencies were updated
180
+ * dependencies
181
+ * @aigne/core bumped to 1.72.0-beta.16
182
+ * devDependencies
183
+ * @aigne/test-utils bumped to 0.5.69-beta.16
184
+
185
+ ## [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)
186
+
187
+
188
+ ### Bug Fixes
189
+
190
+ * **core:** simplify token-estimator logic for remaining characters ([45d43cc](https://github.com/AIGNE-io/aigne-framework/commit/45d43ccd3afd636cfb459eea2e6551e8f9c53765))
191
+
192
+
193
+ ### Dependencies
194
+
195
+ * The following workspace dependencies were updated
196
+ * dependencies
197
+ * @aigne/core bumped to 1.72.0-beta.15
198
+ * devDependencies
199
+ * @aigne/test-utils bumped to 0.5.69-beta.15
200
+
201
+ ## [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)
202
+
203
+
204
+ ### Bug Fixes
205
+
206
+ * **core:** default enable auto breakpoints for chat model ([d4a6b83](https://github.com/AIGNE-io/aigne-framework/commit/d4a6b8323d6c83be45669885b32febb545bdf797))
207
+
208
+
209
+ ### Dependencies
210
+
211
+ * The following workspace dependencies were updated
212
+ * dependencies
213
+ * @aigne/core bumped to 1.72.0-beta.14
214
+ * devDependencies
215
+ * @aigne/test-utils bumped to 0.5.69-beta.14
216
+
217
+ ## [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)
218
+
219
+
220
+ ### Bug Fixes
221
+
222
+ * bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
223
+
224
+
225
+ ### Dependencies
226
+
227
+ * The following workspace dependencies were updated
228
+ * dependencies
229
+ * @aigne/core bumped to 1.72.0-beta.13
230
+ * @aigne/platform-helpers bumped to 0.6.7-beta.1
231
+ * devDependencies
232
+ * @aigne/test-utils bumped to 0.5.69-beta.13
233
+
234
+ ## [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)
235
+
236
+
237
+ ### Dependencies
238
+
239
+ * The following workspace dependencies were updated
240
+ * dependencies
241
+ * @aigne/core bumped to 1.72.0-beta.12
242
+ * devDependencies
243
+ * @aigne/test-utils bumped to 0.5.69-beta.12
244
+
245
+ ## [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)
246
+
247
+
248
+ ### Bug Fixes
249
+
250
+ * **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))
251
+
252
+
253
+ ### Dependencies
254
+
255
+ * The following workspace dependencies were updated
256
+ * dependencies
257
+ * @aigne/core bumped to 1.72.0-beta.11
258
+ * devDependencies
259
+ * @aigne/test-utils bumped to 0.5.69-beta.11
260
+
261
+ ## [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)
262
+
263
+
264
+ ### Dependencies
265
+
266
+ * The following workspace dependencies were updated
267
+ * dependencies
268
+ * @aigne/core bumped to 1.72.0-beta.10
269
+ * devDependencies
270
+ * @aigne/test-utils bumped to 0.5.69-beta.10
271
+
272
+ ## [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)
273
+
274
+
275
+ ### Dependencies
276
+
277
+ * The following workspace dependencies were updated
278
+ * dependencies
279
+ * @aigne/core bumped to 1.72.0-beta.9
280
+ * devDependencies
281
+ * @aigne/test-utils bumped to 0.5.69-beta.9
282
+
3
283
  ## [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
284
 
5
285
 
@@ -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.17-beta",
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.73.0-beta",
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.70-beta"
50
50
  },
51
51
  "scripts": {
52
52
  "lint": "tsc --noEmit",