@strands-agents/sdk 1.18.0 → 1.19.0
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/README.md +4 -1
- package/dist/src/agent/agent.d.ts +3 -1
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +19 -9
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/background-tasks/background-tasks.d.ts.map +1 -1
- package/dist/src/background-tasks/background-tasks.js +13 -10
- package/dist/src/background-tasks/background-tasks.js.map +1 -1
- package/dist/src/context-manager/context-manager.d.ts +7 -5
- package/dist/src/context-manager/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager/context-manager.js +6 -4
- package/dist/src/context-manager/context-manager.js.map +1 -1
- package/dist/src/conversation-manager/compression/context-compression.d.ts +13 -1
- package/dist/src/conversation-manager/compression/context-compression.d.ts.map +1 -1
- package/dist/src/conversation-manager/compression/context-compression.js +32 -5
- package/dist/src/conversation-manager/compression/context-compression.js.map +1 -1
- package/dist/src/interventions/registry.d.ts.map +1 -1
- package/dist/src/interventions/registry.js +5 -0
- package/dist/src/interventions/registry.js.map +1 -1
- package/dist/src/models/anthropic.d.ts +30 -0
- package/dist/src/models/anthropic.d.ts.map +1 -1
- package/dist/src/models/anthropic.js +338 -126
- package/dist/src/models/anthropic.js.map +1 -1
- package/dist/src/models/bedrock.d.ts +10 -0
- package/dist/src/models/bedrock.d.ts.map +1 -1
- package/dist/src/models/bedrock.js +25 -15
- package/dist/src/models/bedrock.js.map +1 -1
- package/dist/src/models/model.d.ts.map +1 -1
- package/dist/src/models/model.js +6 -1
- package/dist/src/models/model.js.map +1 -1
- package/dist/src/retry/model-retry-strategy.js +16 -3
- package/dist/src/retry/model-retry-strategy.js.map +1 -1
- package/dist/src/sandbox/stream-process.d.ts.map +1 -1
- package/dist/src/sandbox/stream-process.js +5 -0
- package/dist/src/sandbox/stream-process.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/agent.d.ts +7 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js +6 -0
- package/dist/src/types/agent.js.map +1 -1
- package/dist/src/vended-tools/handoff-to-user/handoff-to-user.d.ts +28 -0
- package/dist/src/vended-tools/handoff-to-user/handoff-to-user.d.ts.map +1 -0
- package/dist/src/vended-tools/handoff-to-user/handoff-to-user.js +45 -0
- package/dist/src/vended-tools/handoff-to-user/handoff-to-user.js.map +1 -0
- package/dist/src/vended-tools/handoff-to-user/index.d.ts +8 -0
- package/dist/src/vended-tools/handoff-to-user/index.d.ts.map +1 -0
- package/dist/src/vended-tools/handoff-to-user/index.js +6 -0
- package/dist/src/vended-tools/handoff-to-user/index.js.map +1 -0
- package/dist/src/vended-tools/handoff-to-user/types.d.ts +18 -0
- package/dist/src/vended-tools/handoff-to-user/types.d.ts.map +1 -0
- package/dist/src/vended-tools/handoff-to-user/types.js +11 -0
- package/dist/src/vended-tools/handoff-to-user/types.js.map +1 -0
- package/dist/src/vended-tools/index.d.ts +10 -1
- package/dist/src/vended-tools/index.d.ts.map +1 -1
- package/dist/src/vended-tools/index.js +10 -1
- package/dist/src/vended-tools/index.js.map +1 -1
- package/package.json +8 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Anthropic, {} from '@anthropic-ai/sdk';
|
|
2
2
|
import { Model, resolveConfigMetadata, resolveCacheSection, } from '../models/model.js';
|
|
3
3
|
import { createEmptyUsage } from '../models/streaming.js';
|
|
4
|
-
import { ContextWindowOverflowError, ModelThrottledError, normalizeError } from '../errors.js';
|
|
4
|
+
import { ContextWindowOverflowError, ModelError, ModelThrottledError, normalizeError } from '../errors.js';
|
|
5
5
|
import { encodeBase64 } from '../types/media.js';
|
|
6
6
|
import { logger } from '../logging/logger.js';
|
|
7
7
|
import { warnOnce } from '../logging/warn-once.js';
|
|
@@ -17,11 +17,52 @@ const CONTEXT_WINDOW_OVERFLOW_ERRORS = [
|
|
|
17
17
|
'input length exceeds context window',
|
|
18
18
|
'input and output tokens exceed your context limit',
|
|
19
19
|
];
|
|
20
|
+
// Content blocks for tools Anthropic executes server side. They have no toolUse/toolResult equivalent
|
|
21
|
+
// (the agent never runs them), so they are not streamed.
|
|
22
|
+
const SERVER_TOOL_BLOCK_TYPES = new Set([
|
|
23
|
+
'bash_code_execution_tool_result',
|
|
24
|
+
'code_execution_tool_result',
|
|
25
|
+
'container_upload',
|
|
26
|
+
'mcp_tool_result',
|
|
27
|
+
'mcp_tool_use',
|
|
28
|
+
'server_tool_use',
|
|
29
|
+
'text_editor_code_execution_tool_result',
|
|
30
|
+
'tool_search_tool_result',
|
|
31
|
+
'web_fetch_tool_result',
|
|
32
|
+
'web_search_tool_result',
|
|
33
|
+
]);
|
|
34
|
+
// Anthropic pauses a long server-side tool turn with stop_reason=pause_turn and expects the paused
|
|
35
|
+
// assistant message to be sent back as-is to resume it. Bounds how many times stream() does so.
|
|
36
|
+
const MAX_PAUSE_TURN_CONTINUATIONS = 10;
|
|
20
37
|
/**
|
|
21
38
|
* `ephemeral` is the only cache type the Anthropic API supports.
|
|
22
39
|
*/
|
|
23
40
|
const ANTHROPIC_CACHE_TYPE = 'ephemeral';
|
|
24
41
|
const TEXT_FILE_FORMATS = ['txt', 'md', 'markdown', 'csv', 'json', 'xml', 'html', 'yml', 'yaml', 'js', 'ts', 'py'];
|
|
42
|
+
/**
|
|
43
|
+
* Validates that `anthropicTools` does not contain function tool definitions.
|
|
44
|
+
*
|
|
45
|
+
* @param anthropicTools - The configured server-side tools
|
|
46
|
+
* @throws Error - When an entry carries an `input_schema`
|
|
47
|
+
*/
|
|
48
|
+
function validateAnthropicTools(anthropicTools) {
|
|
49
|
+
for (const tool of anthropicTools) {
|
|
50
|
+
if ('input_schema' in tool) {
|
|
51
|
+
throw new Error('anthropicTools should not contain function tool definitions. Use the standard tools interface for ' +
|
|
52
|
+
'function calling tools. anthropicTools is reserved for Anthropic server-side tools like web_search, ' +
|
|
53
|
+
'web_fetch, and code_execution.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Copies the numeric fields of an Anthropic usage object; `null` fields leave the existing value in place.
|
|
59
|
+
*/
|
|
60
|
+
function mergeUsage(target, source) {
|
|
61
|
+
for (const [key, value] of Object.entries(source)) {
|
|
62
|
+
if (typeof value === 'number')
|
|
63
|
+
target[key] = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
25
66
|
export class AnthropicModel extends Model {
|
|
26
67
|
_config;
|
|
27
68
|
_client;
|
|
@@ -33,6 +74,8 @@ export class AnthropicModel extends Model {
|
|
|
33
74
|
maxTokens: MODEL_DEFAULTS.anthropic.maxTokens,
|
|
34
75
|
...modelConfig,
|
|
35
76
|
};
|
|
77
|
+
if (modelConfig.anthropicTools)
|
|
78
|
+
validateAnthropicTools(modelConfig.anthropicTools);
|
|
36
79
|
if (modelConfig.modelId === undefined) {
|
|
37
80
|
warnOnce(logger, defaultModelWarningMessage(MODEL_DEFAULTS.anthropic.modelId));
|
|
38
81
|
}
|
|
@@ -54,6 +97,8 @@ export class AnthropicModel extends Model {
|
|
|
54
97
|
}
|
|
55
98
|
}
|
|
56
99
|
updateConfig(modelConfig) {
|
|
100
|
+
if (modelConfig.anthropicTools)
|
|
101
|
+
validateAnthropicTools(modelConfig.anthropicTools);
|
|
57
102
|
this._config = { ...this._config, ...modelConfig };
|
|
58
103
|
}
|
|
59
104
|
getConfig() {
|
|
@@ -93,125 +138,181 @@ export class AnthropicModel extends Model {
|
|
|
93
138
|
return super.countTokens(messages, options);
|
|
94
139
|
}
|
|
95
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Streams a conversation with the Anthropic model.
|
|
143
|
+
*
|
|
144
|
+
* A server-side tool turn that Anthropic pauses is resumed with follow-up requests inside this call, so
|
|
145
|
+
* an error thrown by one of those requests can surface after earlier events were already yielded.
|
|
146
|
+
*
|
|
147
|
+
* @param messages - Array of conversation messages
|
|
148
|
+
* @param options - Optional streaming configuration
|
|
149
|
+
* @returns Async iterable of streaming events
|
|
150
|
+
*/
|
|
96
151
|
async *stream(messages, options) {
|
|
97
152
|
try {
|
|
98
|
-
|
|
153
|
+
let request = this._formatRequest(messages, options);
|
|
99
154
|
const requestOptions = this._buildRequestOptions();
|
|
100
|
-
const stream = requestOptions
|
|
101
|
-
? this._client.messages.stream(request, requestOptions)
|
|
102
|
-
: this._client.messages.stream(request);
|
|
103
155
|
const usage = createEmptyUsage();
|
|
104
|
-
let
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
yield {
|
|
125
|
-
type: 'modelContentBlockStartEvent',
|
|
126
|
-
start: {
|
|
127
|
-
type: 'toolUseStart',
|
|
128
|
-
name: event.content_block.name,
|
|
129
|
-
toolUseId: event.content_block.id,
|
|
130
|
-
},
|
|
131
|
-
};
|
|
156
|
+
let continuations = 0;
|
|
157
|
+
while (true) {
|
|
158
|
+
const stream = requestOptions
|
|
159
|
+
? this._client.messages.stream(request, requestOptions)
|
|
160
|
+
: this._client.messages.stream(request);
|
|
161
|
+
let stopReason = 'endTurn';
|
|
162
|
+
let messageStopped = false;
|
|
163
|
+
const responseUsage = {};
|
|
164
|
+
const serverToolBlockIndexes = new Set();
|
|
165
|
+
for await (const event of stream) {
|
|
166
|
+
switch (event.type) {
|
|
167
|
+
case 'message_start': {
|
|
168
|
+
mergeUsage(responseUsage, event.message.usage);
|
|
169
|
+
if (continuations === 0) {
|
|
170
|
+
yield {
|
|
171
|
+
type: 'modelMessageStartEvent',
|
|
172
|
+
role: event.message.role,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
132
176
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
177
|
+
case 'content_block_start':
|
|
178
|
+
if (SERVER_TOOL_BLOCK_TYPES.has(event.content_block.type)) {
|
|
179
|
+
serverToolBlockIndexes.add(event.index);
|
|
180
|
+
this._logServerToolBlock(event.content_block);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
if (event.content_block.type === 'tool_use') {
|
|
184
|
+
yield {
|
|
185
|
+
type: 'modelContentBlockStartEvent',
|
|
186
|
+
start: {
|
|
187
|
+
type: 'toolUseStart',
|
|
188
|
+
name: event.content_block.name,
|
|
189
|
+
toolUseId: event.content_block.id,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
else if (event.content_block.type === 'thinking') {
|
|
194
|
+
yield { type: 'modelContentBlockStartEvent' };
|
|
195
|
+
if (event.content_block.thinking) {
|
|
196
|
+
yield {
|
|
197
|
+
type: 'modelContentBlockDeltaEvent',
|
|
198
|
+
delta: {
|
|
199
|
+
type: 'reasoningContentDelta',
|
|
200
|
+
text: event.content_block.thinking,
|
|
201
|
+
signature: event.content_block.signature,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (event.content_block.type === 'redacted_thinking') {
|
|
207
|
+
yield { type: 'modelContentBlockStartEvent' };
|
|
136
208
|
yield {
|
|
137
209
|
type: 'modelContentBlockDeltaEvent',
|
|
138
210
|
delta: {
|
|
139
211
|
type: 'reasoningContentDelta',
|
|
140
|
-
|
|
141
|
-
signature: event.content_block.signature,
|
|
212
|
+
redactedContent: event.content_block.data,
|
|
142
213
|
},
|
|
143
214
|
};
|
|
144
215
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (event.
|
|
216
|
+
else {
|
|
217
|
+
yield { type: 'modelContentBlockStartEvent' };
|
|
218
|
+
if (event.content_block.type === 'text' && event.content_block.text) {
|
|
219
|
+
yield {
|
|
220
|
+
type: 'modelContentBlockDeltaEvent',
|
|
221
|
+
delta: { type: 'textDelta', text: event.content_block.text },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
case 'content_block_delta':
|
|
227
|
+
if (serverToolBlockIndexes.has(event.index))
|
|
228
|
+
break;
|
|
229
|
+
if (event.delta.type === 'text_delta') {
|
|
159
230
|
yield {
|
|
160
231
|
type: 'modelContentBlockDeltaEvent',
|
|
161
|
-
delta: { type: 'textDelta', text: event.
|
|
232
|
+
delta: { type: 'textDelta', text: event.delta.text },
|
|
162
233
|
};
|
|
163
234
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
235
|
+
else if (event.delta.type === 'citations_delta') {
|
|
236
|
+
const citation = this._formatCitation(event.delta.citation);
|
|
237
|
+
if (citation) {
|
|
238
|
+
yield {
|
|
239
|
+
type: 'modelContentBlockDeltaEvent',
|
|
240
|
+
delta: { type: 'citationsDelta', citations: [citation], content: [] },
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else if (event.delta.type === 'input_json_delta') {
|
|
245
|
+
yield {
|
|
246
|
+
type: 'modelContentBlockDeltaEvent',
|
|
247
|
+
delta: { type: 'toolUseInputDelta', input: event.delta.partial_json },
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
else if (event.delta.type === 'thinking_delta') {
|
|
251
|
+
yield {
|
|
252
|
+
type: 'modelContentBlockDeltaEvent',
|
|
253
|
+
delta: { type: 'reasoningContentDelta', text: event.delta.thinking },
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
else if (event.delta.type === 'signature_delta') {
|
|
257
|
+
yield {
|
|
258
|
+
type: 'modelContentBlockDeltaEvent',
|
|
259
|
+
delta: { type: 'reasoningContentDelta', signature: event.delta.signature },
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
case 'content_block_stop':
|
|
264
|
+
if (serverToolBlockIndexes.delete(event.index))
|
|
265
|
+
break;
|
|
266
|
+
yield { type: 'modelContentBlockStopEvent' };
|
|
267
|
+
break;
|
|
268
|
+
case 'message_delta':
|
|
269
|
+
if (event.usage) {
|
|
270
|
+
// Cumulative within one response; fields Anthropic omits keep their message_start value.
|
|
271
|
+
mergeUsage(responseUsage, event.usage);
|
|
272
|
+
}
|
|
273
|
+
if (event.delta.stop_reason) {
|
|
274
|
+
stopReason = this._mapStopReason(event.delta.stop_reason);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
case 'message_stop':
|
|
278
|
+
messageStopped = true;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// A stream that ends without message_stop is incomplete; emit no stop event so the caller can tell.
|
|
283
|
+
if (!messageStopped)
|
|
284
|
+
return;
|
|
285
|
+
usage.inputTokens += responseUsage.input_tokens ?? 0;
|
|
286
|
+
usage.outputTokens += responseUsage.output_tokens ?? 0;
|
|
287
|
+
if (responseUsage.cache_creation_input_tokens !== undefined) {
|
|
288
|
+
usage.cacheWriteInputTokens = (usage.cacheWriteInputTokens ?? 0) + responseUsage.cache_creation_input_tokens;
|
|
289
|
+
}
|
|
290
|
+
if (responseUsage.cache_read_input_tokens !== undefined) {
|
|
291
|
+
usage.cacheReadInputTokens = (usage.cacheReadInputTokens ?? 0) + responseUsage.cache_read_input_tokens;
|
|
292
|
+
}
|
|
293
|
+
if (stopReason === 'pauseTurn') {
|
|
294
|
+
if (continuations >= MAX_PAUSE_TURN_CONTINUATIONS) {
|
|
295
|
+
throw new ModelError(`server-side tool turn did not complete after ${continuations} continuations`);
|
|
296
|
+
}
|
|
297
|
+
continuations++;
|
|
298
|
+
const pausedMessage = await stream.finalMessage();
|
|
299
|
+
request = {
|
|
300
|
+
...request,
|
|
301
|
+
messages: [...request.messages, { role: 'assistant', content: pausedMessage.content }],
|
|
302
|
+
};
|
|
303
|
+
logger.debug(`continuation=<${continuations}> | resuming paused server-side tool turn`);
|
|
304
|
+
continue;
|
|
214
305
|
}
|
|
306
|
+
usage.totalTokens = usage.inputTokens + usage.outputTokens;
|
|
307
|
+
yield {
|
|
308
|
+
type: 'modelMetadataEvent',
|
|
309
|
+
usage,
|
|
310
|
+
};
|
|
311
|
+
yield {
|
|
312
|
+
type: 'modelMessageStopEvent',
|
|
313
|
+
stopReason,
|
|
314
|
+
};
|
|
315
|
+
return;
|
|
215
316
|
}
|
|
216
317
|
}
|
|
217
318
|
catch (unknownError) {
|
|
@@ -342,29 +443,38 @@ export class AnthropicModel extends Model {
|
|
|
342
443
|
if (system !== undefined)
|
|
343
444
|
request.system = system;
|
|
344
445
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
446
|
+
const tools = (options?.toolSpecs ?? []).map((tool) => ({
|
|
447
|
+
name: tool.name,
|
|
448
|
+
description: tool.description,
|
|
449
|
+
input_schema: tool.inputSchema,
|
|
450
|
+
}));
|
|
451
|
+
const serverTools = [
|
|
452
|
+
...(this._config.anthropicTools ?? []),
|
|
453
|
+
...(this._config.params?.tools ?? []),
|
|
454
|
+
];
|
|
455
|
+
// Forcing a tool means this turn must call a function tool, so server tools are left out.
|
|
456
|
+
if (!options?.toolChoice || 'auto' in options.toolChoice) {
|
|
457
|
+
// Copied so the cache_control below never lands on the caller's config.
|
|
458
|
+
tools.push(...serverTools.map((tool) => ({ ...tool })));
|
|
459
|
+
}
|
|
460
|
+
else if (serverTools.length > 0) {
|
|
461
|
+
logger.warn(`tool_choice=<${Object.keys(options.toolChoice)[0]}>, server_tools=<${serverTools.map((tool) => tool.name).join(',')}> | forced tool call, omitting server tools`);
|
|
462
|
+
}
|
|
463
|
+
// A cache_control on the last tool caches all of them, so one cache point suffices.
|
|
464
|
+
const toolsCache = this._cacheSection('toolsTTL');
|
|
465
|
+
const lastTool = tools[tools.length - 1];
|
|
466
|
+
if (toolsCache.enabled && lastTool) {
|
|
467
|
+
lastTool.cache_control = this._formatCacheControl(toolsCache.ttl);
|
|
468
|
+
}
|
|
469
|
+
if (tools.length > 0 && options?.toolChoice) {
|
|
470
|
+
if ('auto' in options.toolChoice) {
|
|
471
|
+
request.tool_choice = { type: 'auto' };
|
|
356
472
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
else if ('any' in options.toolChoice) {
|
|
363
|
-
request.tool_choice = { type: 'any' };
|
|
364
|
-
}
|
|
365
|
-
else if ('tool' in options.toolChoice) {
|
|
366
|
-
request.tool_choice = { type: 'tool', name: options.toolChoice.tool.name };
|
|
367
|
-
}
|
|
473
|
+
else if ('any' in options.toolChoice) {
|
|
474
|
+
request.tool_choice = { type: 'any' };
|
|
475
|
+
}
|
|
476
|
+
else if ('tool' in options.toolChoice) {
|
|
477
|
+
request.tool_choice = { type: 'tool', name: options.toolChoice.tool.name };
|
|
368
478
|
}
|
|
369
479
|
}
|
|
370
480
|
if (this._config.temperature !== undefined)
|
|
@@ -375,6 +485,11 @@ export class AnthropicModel extends Model {
|
|
|
375
485
|
request.stop_sequences = this._config.stopSequences;
|
|
376
486
|
if (this._config.params)
|
|
377
487
|
Object.assign(request, this._config.params);
|
|
488
|
+
// params.tools is already merged into tools above; never let it bypass the forced-turn rule.
|
|
489
|
+
if (tools.length > 0)
|
|
490
|
+
request.tools = tools;
|
|
491
|
+
else
|
|
492
|
+
delete request.tools;
|
|
378
493
|
return request;
|
|
379
494
|
}
|
|
380
495
|
_formatMessages(messages, messagesCache = { enabled: false }, cacheTargetMessage = -1, dynamicTrailingBlocks = 0) {
|
|
@@ -433,7 +548,8 @@ export class AnthropicModel extends Model {
|
|
|
433
548
|
logger.warn(`count=<${strippedCachePoints}> | stripped extra cache points, cacheConfig keeps the first cache ` +
|
|
434
549
|
`point in the last user message; unset cacheConfig to keep every cache point`);
|
|
435
550
|
}
|
|
436
|
-
|
|
551
|
+
// The API rejects an empty message anywhere but the trailing assistant turn.
|
|
552
|
+
return formatted.filter((msg) => msg.content.length > 0);
|
|
437
553
|
}
|
|
438
554
|
_isCacheableBlock(block) {
|
|
439
555
|
return ['text', 'image', 'tool_use', 'tool_result', 'document'].includes(block.type);
|
|
@@ -574,12 +690,108 @@ export class AnthropicModel extends Model {
|
|
|
574
690
|
};
|
|
575
691
|
}
|
|
576
692
|
return undefined;
|
|
693
|
+
case 'citationsBlock': {
|
|
694
|
+
const text = block.content.map((generated) => generated.text).join('');
|
|
695
|
+
return text ? { type: 'text', text } : undefined;
|
|
696
|
+
}
|
|
577
697
|
case 'cachePointBlock':
|
|
578
698
|
return undefined;
|
|
579
699
|
default:
|
|
580
700
|
return undefined;
|
|
581
701
|
}
|
|
582
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* Maps an Anthropic citation onto a Strands citation.
|
|
705
|
+
*
|
|
706
|
+
* @param citation - Citation from a `citations_delta` event
|
|
707
|
+
* @returns The mapped citation, or `undefined` when the location type has no Strands equivalent
|
|
708
|
+
*/
|
|
709
|
+
_formatCitation(citation) {
|
|
710
|
+
const citedText = 'cited_text' in citation && citation.cited_text ? [{ text: citation.cited_text }] : [];
|
|
711
|
+
switch (citation.type) {
|
|
712
|
+
case 'web_search_result_location': {
|
|
713
|
+
const url = citation.url ?? '';
|
|
714
|
+
let domain;
|
|
715
|
+
try {
|
|
716
|
+
domain = new URL(url).hostname;
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
domain = undefined;
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
location: { type: 'web', url, ...(domain ? { domain } : {}) },
|
|
723
|
+
source: url,
|
|
724
|
+
sourceContent: citedText,
|
|
725
|
+
title: citation.title ?? '',
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
case 'search_result_location':
|
|
729
|
+
return {
|
|
730
|
+
location: {
|
|
731
|
+
type: 'searchResult',
|
|
732
|
+
searchResultIndex: citation.search_result_index,
|
|
733
|
+
start: citation.start_block_index,
|
|
734
|
+
end: citation.end_block_index,
|
|
735
|
+
},
|
|
736
|
+
source: citation.source,
|
|
737
|
+
sourceContent: citedText,
|
|
738
|
+
title: citation.title ?? '',
|
|
739
|
+
};
|
|
740
|
+
case 'char_location':
|
|
741
|
+
return {
|
|
742
|
+
location: {
|
|
743
|
+
type: 'documentChar',
|
|
744
|
+
documentIndex: citation.document_index,
|
|
745
|
+
start: citation.start_char_index,
|
|
746
|
+
end: citation.end_char_index,
|
|
747
|
+
},
|
|
748
|
+
source: citation.file_id ?? '',
|
|
749
|
+
sourceContent: citedText,
|
|
750
|
+
title: citation.document_title ?? '',
|
|
751
|
+
};
|
|
752
|
+
case 'page_location':
|
|
753
|
+
return {
|
|
754
|
+
location: {
|
|
755
|
+
type: 'documentPage',
|
|
756
|
+
documentIndex: citation.document_index,
|
|
757
|
+
start: citation.start_page_number,
|
|
758
|
+
end: citation.end_page_number,
|
|
759
|
+
},
|
|
760
|
+
source: citation.file_id ?? '',
|
|
761
|
+
sourceContent: citedText,
|
|
762
|
+
title: citation.document_title ?? '',
|
|
763
|
+
};
|
|
764
|
+
case 'content_block_location':
|
|
765
|
+
return {
|
|
766
|
+
location: {
|
|
767
|
+
type: 'documentChunk',
|
|
768
|
+
documentIndex: citation.document_index,
|
|
769
|
+
start: citation.start_block_index,
|
|
770
|
+
end: citation.end_block_index,
|
|
771
|
+
},
|
|
772
|
+
source: citation.file_id ?? '',
|
|
773
|
+
sourceContent: citedText,
|
|
774
|
+
title: citation.document_title ?? '',
|
|
775
|
+
};
|
|
776
|
+
default:
|
|
777
|
+
logger.warn(`citation_type=<${citation.type}> | unsupported citation location | skipping`);
|
|
778
|
+
return undefined;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Logs a skipped server-side tool block, at warn level when the tool returned an error.
|
|
783
|
+
*
|
|
784
|
+
* @param contentBlock - The `content_block` of a `content_block_start` event
|
|
785
|
+
*/
|
|
786
|
+
_logServerToolBlock(contentBlock) {
|
|
787
|
+
const errorCode = contentBlock.content?.error_code;
|
|
788
|
+
if (errorCode !== undefined) {
|
|
789
|
+
logger.warn(`block_type=<${contentBlock.type}>, error_code=<${errorCode}> | server-side tool failed`);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
logger.debug(`block_type=<${contentBlock.type}> | skipping server-side tool block`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
583
795
|
_mapStopReason(anthropicReason) {
|
|
584
796
|
switch (anthropicReason) {
|
|
585
797
|
case 'end_turn':
|