@aigne/anthropic 0.14.16-beta.8 → 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,290 @@
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
+
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)
273
+
274
+
275
+ ### Features
276
+
277
+ * 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))
278
+
279
+
280
+ ### Dependencies
281
+
282
+ * The following workspace dependencies were updated
283
+ * dependencies
284
+ * @aigne/core bumped to 1.72.0-beta.8
285
+ * devDependencies
286
+ * @aigne/test-utils bumped to 0.5.69-beta.8
287
+
3
288
  ## [0.14.16-beta.8](https://github.com/AIGNE-io/aigne-framework/compare/anthropic-v0.14.16-beta.7...anthropic-v0.14.16-beta.8) (2025-12-26)
4
289
 
5
290
 
@@ -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
@@ -291,26 +212,36 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
291
212
  const ttl = cacheConfig.ttl === "1h" ? "1h" : undefined;
292
213
  for (const msg of messages) {
293
214
  if (msg.role === "system") {
294
- if (typeof msg.content !== "string")
295
- throw new Error("System message must have content");
296
- const block = {
297
- type: "text",
298
- text: msg.content,
299
- };
300
- systemBlocks.push(block);
215
+ if (typeof msg.content === "string") {
216
+ const block = {
217
+ type: "text",
218
+ text: msg.content,
219
+ };
220
+ systemBlocks.push(block);
221
+ }
222
+ else if (Array.isArray(msg.content)) {
223
+ systemBlocks.push(...msg.content.map((item) => {
224
+ if (item.type !== "text")
225
+ throw new Error("System message only supports text content blocks");
226
+ return { type: "text", text: item.text };
227
+ }));
228
+ }
229
+ else {
230
+ throw new Error("System message must have string or array content");
231
+ }
301
232
  }
302
233
  else if (msg.role === "tool") {
303
234
  if (!msg.toolCallId)
304
235
  throw new Error("Tool message must have toolCallId");
305
- if (typeof msg.content !== "string")
306
- throw new Error("Tool message must have string content");
236
+ if (!msg.content)
237
+ throw new Error("Tool message must have content");
307
238
  msgs.push({
308
239
  role: "user",
309
240
  content: [
310
241
  {
311
242
  type: "tool_result",
312
243
  tool_use_id: msg.toolCallId,
313
- content: msg.content,
244
+ content: await convertContent(msg.content),
314
245
  },
315
246
  ],
316
247
  });
@@ -340,14 +271,6 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
340
271
  }
341
272
  }
342
273
  }
343
- // If there are tools and responseFormat is json_schema, we need to add a system message
344
- // to inform the model about the expected json schema, then trying to parse the response as json
345
- if (tools?.length && responseFormat?.type === "json_schema") {
346
- systemBlocks.push({
347
- type: "text",
348
- text: `You should provide a json response with schema: ${JSON.stringify(responseFormat.jsonSchema.schema)}`,
349
- });
350
- }
351
274
  // Apply cache_control to the last system block if auto strategy is enabled
352
275
  if (shouldCache && strategy === "auto") {
353
276
  if (autoBreakpoints.system && systemBlocks.length > 0) {
@@ -426,61 +349,64 @@ async function convertContent(content) {
426
349
  }
427
350
  throw new Error("Invalid chat message content");
428
351
  }
429
- function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, }) {
430
- let choice;
431
- if (typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function") {
432
- choice = {
433
- type: "tool",
434
- name: toolChoice.function.name,
435
- disable_parallel_tool_use: disableParallelToolUse,
436
- };
437
- }
438
- else if (toolChoice === "required") {
439
- choice = { type: "any", disable_parallel_tool_use: disableParallelToolUse };
440
- }
441
- else if (toolChoice === "auto") {
442
- choice = {
443
- type: "auto",
444
- disable_parallel_tool_use: disableParallelToolUse,
445
- };
446
- }
447
- else if (toolChoice === "none") {
448
- choice = { type: "none" };
449
- }
352
+ function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, responseFormat, }) {
450
353
  // Extract cache configuration with defaults
451
354
  const { shouldCache, ttl, strategy, autoBreakpoints } = parseCacheConfig(modelOptions);
452
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;
453
408
  return {
454
- tools: tools?.length
455
- ? tools.map((i, index, arr) => {
456
- const tool = {
457
- name: i.function.name,
458
- description: i.function.description,
459
- input_schema: (0, type_utils_js_1.isEmpty)(i.function.parameters)
460
- ? { type: "object" }
461
- : i.function.parameters,
462
- };
463
- // Auto mode: add cache_control to the last tool
464
- if (shouldCacheTools && index === arr.length - 1) {
465
- tool.cache_control = { type: "ephemeral" };
466
- if (ttl === "1h") {
467
- tool.cache_control = { type: "ephemeral", ttl: "1h" };
468
- }
469
- }
470
- // Manual mode: use tool-specific cacheControl if provided
471
- else if (shouldCache && strategy === "manual") {
472
- const toolWithCache = i;
473
- if (toolWithCache.cacheControl) {
474
- tool.cache_control = {
475
- type: toolWithCache.cacheControl.type,
476
- ...(toolWithCache.cacheControl.ttl && { ttl: toolWithCache.cacheControl.ttl }),
477
- };
478
- }
479
- }
480
- return tool;
481
- })
482
- : undefined,
409
+ tools: convertedTools.length ? convertedTools : undefined,
483
410
  tool_choice: choice,
484
411
  };
485
412
  }
486
- // 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
@@ -284,26 +205,36 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
284
205
  const ttl = cacheConfig.ttl === "1h" ? "1h" : undefined;
285
206
  for (const msg of messages) {
286
207
  if (msg.role === "system") {
287
- if (typeof msg.content !== "string")
288
- throw new Error("System message must have content");
289
- const block = {
290
- type: "text",
291
- text: msg.content,
292
- };
293
- systemBlocks.push(block);
208
+ if (typeof msg.content === "string") {
209
+ const block = {
210
+ type: "text",
211
+ text: msg.content,
212
+ };
213
+ systemBlocks.push(block);
214
+ }
215
+ else if (Array.isArray(msg.content)) {
216
+ systemBlocks.push(...msg.content.map((item) => {
217
+ if (item.type !== "text")
218
+ throw new Error("System message only supports text content blocks");
219
+ return { type: "text", text: item.text };
220
+ }));
221
+ }
222
+ else {
223
+ throw new Error("System message must have string or array content");
224
+ }
294
225
  }
295
226
  else if (msg.role === "tool") {
296
227
  if (!msg.toolCallId)
297
228
  throw new Error("Tool message must have toolCallId");
298
- if (typeof msg.content !== "string")
299
- throw new Error("Tool message must have string content");
229
+ if (!msg.content)
230
+ throw new Error("Tool message must have content");
300
231
  msgs.push({
301
232
  role: "user",
302
233
  content: [
303
234
  {
304
235
  type: "tool_result",
305
236
  tool_use_id: msg.toolCallId,
306
- content: msg.content,
237
+ content: await convertContent(msg.content),
307
238
  },
308
239
  ],
309
240
  });
@@ -333,14 +264,6 @@ async function convertMessages({ messages, responseFormat, tools, modelOptions,
333
264
  }
334
265
  }
335
266
  }
336
- // If there are tools and responseFormat is json_schema, we need to add a system message
337
- // to inform the model about the expected json schema, then trying to parse the response as json
338
- if (tools?.length && responseFormat?.type === "json_schema") {
339
- systemBlocks.push({
340
- type: "text",
341
- text: `You should provide a json response with schema: ${JSON.stringify(responseFormat.jsonSchema.schema)}`,
342
- });
343
- }
344
267
  // Apply cache_control to the last system block if auto strategy is enabled
345
268
  if (shouldCache && strategy === "auto") {
346
269
  if (autoBreakpoints.system && systemBlocks.length > 0) {
@@ -419,61 +342,64 @@ async function convertContent(content) {
419
342
  }
420
343
  throw new Error("Invalid chat message content");
421
344
  }
422
- function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, }) {
423
- let choice;
424
- if (typeof toolChoice === "object" && "type" in toolChoice && toolChoice.type === "function") {
425
- choice = {
426
- type: "tool",
427
- name: toolChoice.function.name,
428
- disable_parallel_tool_use: disableParallelToolUse,
429
- };
430
- }
431
- else if (toolChoice === "required") {
432
- choice = { type: "any", disable_parallel_tool_use: disableParallelToolUse };
433
- }
434
- else if (toolChoice === "auto") {
435
- choice = {
436
- type: "auto",
437
- disable_parallel_tool_use: disableParallelToolUse,
438
- };
439
- }
440
- else if (toolChoice === "none") {
441
- choice = { type: "none" };
442
- }
345
+ function convertTools({ tools, toolChoice, disableParallelToolUse, modelOptions, responseFormat, }) {
443
346
  // Extract cache configuration with defaults
444
347
  const { shouldCache, ttl, strategy, autoBreakpoints } = parseCacheConfig(modelOptions);
445
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;
446
401
  return {
447
- tools: tools?.length
448
- ? tools.map((i, index, arr) => {
449
- const tool = {
450
- name: i.function.name,
451
- description: i.function.description,
452
- input_schema: isEmpty(i.function.parameters)
453
- ? { type: "object" }
454
- : i.function.parameters,
455
- };
456
- // Auto mode: add cache_control to the last tool
457
- if (shouldCacheTools && index === arr.length - 1) {
458
- tool.cache_control = { type: "ephemeral" };
459
- if (ttl === "1h") {
460
- tool.cache_control = { type: "ephemeral", ttl: "1h" };
461
- }
462
- }
463
- // Manual mode: use tool-specific cacheControl if provided
464
- else if (shouldCache && strategy === "manual") {
465
- const toolWithCache = i;
466
- if (toolWithCache.cacheControl) {
467
- tool.cache_control = {
468
- type: toolWithCache.cacheControl.type,
469
- ...(toolWithCache.cacheControl.ttl && { ttl: toolWithCache.cacheControl.ttl }),
470
- };
471
- }
472
- }
473
- return tool;
474
- })
475
- : undefined,
402
+ tools: convertedTools.length ? convertedTools : undefined,
476
403
  tool_choice: choice,
477
404
  };
478
405
  }
479
- // 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.8",
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.7",
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.7"
49
+ "@aigne/test-utils": "^0.5.69"
50
50
  },
51
51
  "scripts": {
52
52
  "lint": "tsc --noEmit",