modelmix 5.1.1 → 5.1.2
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 +2 -2
- package/demo/free.js +2 -3
- package/effort.js +0 -1
- package/index.js +513 -2847
- package/lib/content-cache.js +31 -0
- package/lib/model-chain.js +51 -0
- package/lib/object-utils.js +7 -0
- package/lib/provider-debug.js +23 -0
- package/lib/providers/anthropic.js +337 -0
- package/lib/providers/base.js +409 -0
- package/lib/providers/google.js +293 -0
- package/lib/providers/openai-compatible.js +338 -0
- package/lib/providers/openai.js +622 -0
- package/lib/providers.js +26 -0
- package/lib/template-engine.js +168 -0
- package/lib/token-usage.js +299 -0
- package/package.json +1 -1
- package/skills/modelmix/SKILL.md +2 -2
- package/test/effort.test.js +20 -0
- package/test/model-chain.test.js +12 -0
- package/test/public-api.test.js +54 -0
- package/test/tokens.test.js +20 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { isPlainObject } = require('./object-utils');
|
|
2
|
+
|
|
3
|
+
function normalizeContentCache(cache) {
|
|
4
|
+
if (cache !== undefined) {
|
|
5
|
+
if (!isPlainObject(cache) || cache.breakpoint !== true) {
|
|
6
|
+
throw new TypeError('cache must be { breakpoint: true }.');
|
|
7
|
+
}
|
|
8
|
+
return { breakpoint: true };
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function stripContentCacheMetadata(content) {
|
|
14
|
+
if (!content || typeof content !== 'object') return content;
|
|
15
|
+
const sanitized = { ...content };
|
|
16
|
+
delete sanitized.cache;
|
|
17
|
+
delete sanitized.cache_control;
|
|
18
|
+
delete sanitized.prompt_cache_breakpoint;
|
|
19
|
+
return sanitized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function hasNeutralCacheBreakpoint(messages = []) {
|
|
23
|
+
return messages.some(message => Array.isArray(message?.content)
|
|
24
|
+
&& message.content.some(block => block?.cache?.breakpoint === true));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
normalizeContentCache,
|
|
29
|
+
stripContentCacheMetadata,
|
|
30
|
+
hasNeutralCacheBreakpoint
|
|
31
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { normalizeEffort } = require('../effort');
|
|
2
|
+
|
|
3
|
+
const CHAIN_MODEL_SHORTCUTS = new Set([
|
|
4
|
+
'gpt5', 'gpt5mini', 'gpt5nano',
|
|
5
|
+
'gpt51', 'gpt52', 'gpt54', 'gpt54mini', 'gpt54nano', 'gpt54pro',
|
|
6
|
+
'gpt55', 'gpt55pro', 'gpt56sol', 'gpt56terra', 'gpt56luna',
|
|
7
|
+
'gptRealtime', 'gptRealtimeMini', 'gpt53codex', 'gpt53chat', 'gptOss',
|
|
8
|
+
'fable50', 'fable5', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
|
|
9
|
+
'sonnet50', 'sonnet5', 'sonnet46', 'sonnet45', 'haiku45',
|
|
10
|
+
'gemini31pro', 'gemini37flash', 'gemini36flash', 'gemini35flash',
|
|
11
|
+
'gemini35flashLite', 'gemini31flashLite', 'sonarPro', 'sonar',
|
|
12
|
+
'grok46', 'grok45', 'grok43', 'grok420multiAgent', 'grok420',
|
|
13
|
+
'qwen3', 'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max',
|
|
14
|
+
'hermes470b', 'hermes4405b', 'hermes3',
|
|
15
|
+
'kimiK26', 'kimiK27Code', 'kimiK3', 'kimiK25',
|
|
16
|
+
'minimaxM27', 'minimaxM3', 'mimo25', 'mimo25pro',
|
|
17
|
+
'deepseekV4Pro', 'deepseekV4Flash', 'GLM51', 'GLM52'
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function parseChainModels(modelSpecs) {
|
|
21
|
+
if (modelSpecs.length === 0) {
|
|
22
|
+
throw new TypeError('chain() requires at least one model shortcut string.');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return modelSpecs.map((modelSpec, index) => {
|
|
26
|
+
if (typeof modelSpec !== 'string') {
|
|
27
|
+
throw new TypeError(`Invalid chain model at index ${index}: expected a model shortcut string.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const match = /^([A-Za-z_$][A-Za-z0-9_$]*)(?:@(-?\d+))?$/.exec(modelSpec);
|
|
31
|
+
if (!match) {
|
|
32
|
+
throw new TypeError(`Invalid chain model "${modelSpec}": expected "shortcut" or "shortcut@effort".`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const shortcut = match[1];
|
|
36
|
+
if (!CHAIN_MODEL_SHORTCUTS.has(shortcut)) {
|
|
37
|
+
throw new Error(`Unknown model shortcut "${shortcut}" in chain().`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
shortcut,
|
|
42
|
+
effort: match[2] === undefined ? undefined : normalizeEffort(Number(match[2]))
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function listChainModelShortcuts() {
|
|
48
|
+
return [...CHAIN_MODEL_SHORTCUTS];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { listChainModelShortcuts, parseChainModels };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
3
|
+
const prototype = Object.getPrototypeOf(value);
|
|
4
|
+
return prototype === Object.prototype || prototype === null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
module.exports = { isPlainObject };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function configForDebug(config) {
|
|
2
|
+
const safeConfig = { ...config };
|
|
3
|
+
delete safeConfig.apiKey;
|
|
4
|
+
delete safeConfig.debug;
|
|
5
|
+
return safeConfig;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function redactSecret(value, secret, seen = new WeakSet()) {
|
|
9
|
+
if (!secret) return value;
|
|
10
|
+
if (typeof value === 'string') return value.split(secret).join('[REDACTED]');
|
|
11
|
+
if (!value || typeof value !== 'object') return value;
|
|
12
|
+
if (seen.has(value)) return '[Circular]';
|
|
13
|
+
|
|
14
|
+
seen.add(value);
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value.map(item => redactSecret(item, secret, seen));
|
|
17
|
+
}
|
|
18
|
+
return Object.fromEntries(
|
|
19
|
+
Object.entries(value).map(([key, item]) => [key, redactSecret(item, secret, seen)])
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { configForDebug, redactSecret };
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
const { isPlainObject } = require('../object-utils');
|
|
2
|
+
const {
|
|
3
|
+
normalizeContentCache,
|
|
4
|
+
stripContentCacheMetadata,
|
|
5
|
+
hasNeutralCacheBreakpoint
|
|
6
|
+
} = require('../content-cache');
|
|
7
|
+
|
|
8
|
+
function createAnthropicProviders({ ModelMix, MixCustom, log }) {
|
|
9
|
+
class MixAnthropic extends MixCustom {
|
|
10
|
+
|
|
11
|
+
sanitizeCacheOptions(options) {
|
|
12
|
+
delete options.prompt_cache_key;
|
|
13
|
+
delete options.prompt_cache_options;
|
|
14
|
+
delete options.prompt_cache_retention;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static validateCacheControl(cacheControl) {
|
|
18
|
+
if (!isPlainObject(cacheControl) || cacheControl.type !== 'ephemeral') {
|
|
19
|
+
throw new TypeError('Anthropic cache_control must have type "ephemeral".');
|
|
20
|
+
}
|
|
21
|
+
if (cacheControl.ttl !== undefined
|
|
22
|
+
&& cacheControl.ttl !== '5m'
|
|
23
|
+
&& cacheControl.ttl !== '1h') {
|
|
24
|
+
throw new TypeError('Anthropic cache_control.ttl must be "5m" or "1h".');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Opus 4.7+ and Claude 5 family reject sampling params (temperature/top_p/top_k).
|
|
30
|
+
* See: https://platform.claude.com/docs/en/about-claude/models/migration-guide
|
|
31
|
+
*/
|
|
32
|
+
static rejectsSamplingParams(model = '') {
|
|
33
|
+
const id = String(model).toLowerCase();
|
|
34
|
+
if (!id.includes('claude')) return false;
|
|
35
|
+
if (id.includes('mythos') || id.includes('fable')) return true;
|
|
36
|
+
|
|
37
|
+
const opus = id.match(/claude-opus-(\d+)(?:-(\d+))?/);
|
|
38
|
+
if (opus) {
|
|
39
|
+
const major = Number(opus[1]);
|
|
40
|
+
const minor = opus[2] !== undefined ? Number(opus[2]) : 0;
|
|
41
|
+
return major > 4 || (major === 4 && minor >= 7);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sonnet = id.match(/claude-sonnet-(\d+)/);
|
|
45
|
+
if (sonnet) return Number(sonnet[1]) >= 5;
|
|
46
|
+
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getDefaultConfig(customConfig) {
|
|
51
|
+
|
|
52
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
53
|
+
throw new Error('Anthropic API key not found. Please provide it in config or set ANTHROPIC_API_KEY environment variable.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return super.getDefaultConfig({
|
|
57
|
+
url: 'https://api.anthropic.com/v1/messages',
|
|
58
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
59
|
+
...customConfig
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async create({ config = {}, options = {} } = {}) {
|
|
64
|
+
|
|
65
|
+
delete options.response_format;
|
|
66
|
+
|
|
67
|
+
if (MixAnthropic.rejectsSamplingParams(options.model)) {
|
|
68
|
+
delete options.temperature;
|
|
69
|
+
delete options.top_p;
|
|
70
|
+
delete options.top_k;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const requestConfig = { ...config };
|
|
74
|
+
if (hasNeutralCacheBreakpoint(options.messages)) {
|
|
75
|
+
const contentCacheControl = options.cache_control ?? { type: 'ephemeral' };
|
|
76
|
+
MixAnthropic.validateCacheControl(contentCacheControl);
|
|
77
|
+
requestConfig._contentCacheControl = { ...contentCacheControl };
|
|
78
|
+
delete options.cache_control;
|
|
79
|
+
} else if (options.cache_control !== undefined) {
|
|
80
|
+
MixAnthropic.validateCacheControl(options.cache_control);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
options.system = config.system;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
return await super.create({ config: requestConfig, options });
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// Log the error details for debugging
|
|
89
|
+
if (error.response && error.response.data) {
|
|
90
|
+
log.error('Anthropic API Error:\n', error.response.data);
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
convertMessages(messages, config) {
|
|
97
|
+
return MixAnthropic.convertMessages(messages, config);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static convertMessages(messages, config) {
|
|
101
|
+
// Filter out orphaned tool results for Anthropic
|
|
102
|
+
const filteredMessages = [];
|
|
103
|
+
for (let i = 0; i < messages.length; i++) {
|
|
104
|
+
if (messages[i].role === 'tool') {
|
|
105
|
+
// Preceding assistant may use OpenAI tool_calls or Anthropic tool_use blocks.
|
|
106
|
+
let foundToolCall = false;
|
|
107
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
108
|
+
if (ModelMix.hasToolInteraction(messages[j]) && messages[j].role === 'assistant') {
|
|
109
|
+
foundToolCall = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!foundToolCall) {
|
|
114
|
+
// Skip orphaned tool results
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
filteredMessages.push(messages[i]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return filteredMessages.map(message => {
|
|
122
|
+
if (message.role === 'tool') {
|
|
123
|
+
// Handle new format: tool_call_id directly on message
|
|
124
|
+
if (message.tool_call_id) {
|
|
125
|
+
return {
|
|
126
|
+
role: "user",
|
|
127
|
+
content: [{
|
|
128
|
+
type: "tool_result",
|
|
129
|
+
tool_use_id: message.tool_call_id,
|
|
130
|
+
content: message.content
|
|
131
|
+
}]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Handle old format: content is an array
|
|
135
|
+
return {
|
|
136
|
+
role: "user",
|
|
137
|
+
content: message.content.map(content => ({
|
|
138
|
+
type: "tool_result",
|
|
139
|
+
tool_use_id: content.tool_call_id,
|
|
140
|
+
content: content.content
|
|
141
|
+
}))
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Handle messages with tool_calls (assistant messages that call tools)
|
|
146
|
+
if (message.tool_calls) {
|
|
147
|
+
const content = message.tool_calls.map(call => ({
|
|
148
|
+
type: 'tool_use',
|
|
149
|
+
id: call.id,
|
|
150
|
+
name: call.function.name,
|
|
151
|
+
input: JSON.parse(call.function.arguments)
|
|
152
|
+
}));
|
|
153
|
+
return { role: 'assistant', content };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle content conversion for other messages
|
|
157
|
+
if (message.content && Array.isArray(message.content)) {
|
|
158
|
+
const content = message.content.filter(content => content !== null && content !== undefined).map(content => {
|
|
159
|
+
const neutralCache = content?.cache !== undefined
|
|
160
|
+
? normalizeContentCache(content.cache)
|
|
161
|
+
: undefined;
|
|
162
|
+
if (neutralCache && content.cache_control !== undefined) {
|
|
163
|
+
throw new TypeError('Use either cache or cache_control on an Anthropic content block, not both.');
|
|
164
|
+
}
|
|
165
|
+
let converted = content;
|
|
166
|
+
if (content && content.type === 'function') {
|
|
167
|
+
converted = {
|
|
168
|
+
type: 'tool_use',
|
|
169
|
+
id: content.id,
|
|
170
|
+
name: content.function.name,
|
|
171
|
+
input: JSON.parse(content.function.arguments)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const sanitized = stripContentCacheMetadata(converted);
|
|
175
|
+
if (content.cache_control !== undefined) {
|
|
176
|
+
MixAnthropic.validateCacheControl(content.cache_control);
|
|
177
|
+
sanitized.cache_control = { ...content.cache_control };
|
|
178
|
+
} else if (neutralCache?.breakpoint) {
|
|
179
|
+
sanitized.cache_control = {
|
|
180
|
+
...(config?._contentCacheControl || { type: 'ephemeral' })
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return sanitized;
|
|
184
|
+
});
|
|
185
|
+
return { ...message, content };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { ...message };
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getDefaultHeaders(customHeaders) {
|
|
193
|
+
return super.getDefaultHeaders({
|
|
194
|
+
'x-api-key': this.config.apiKey,
|
|
195
|
+
'anthropic-version': '2023-06-01',
|
|
196
|
+
...customHeaders
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
extractDelta(data) {
|
|
201
|
+
if (data.delta && data.delta.text) return data.delta.text;
|
|
202
|
+
return '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static extractToolCalls(data) {
|
|
206
|
+
|
|
207
|
+
return data.content.map(item => {
|
|
208
|
+
if (item.type === 'tool_use') {
|
|
209
|
+
return {
|
|
210
|
+
id: item.id,
|
|
211
|
+
type: 'function',
|
|
212
|
+
function: {
|
|
213
|
+
name: item.name,
|
|
214
|
+
arguments: JSON.stringify(item.input)
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}).filter(item => item !== null);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static extractMessage(data) {
|
|
223
|
+
const content = Array.isArray(data?.content) ? data.content : [];
|
|
224
|
+
const stopReason = data?.stop_reason;
|
|
225
|
+
|
|
226
|
+
// Anthropic can return text in different positions depending on thinking/tool blocks.
|
|
227
|
+
const textBlock = content.find(block => typeof block?.text === 'string' && block.text.trim().length > 0);
|
|
228
|
+
if (textBlock) {
|
|
229
|
+
return textBlock.text;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// A tool_use turn can legitimately contain no text blocks.
|
|
233
|
+
if (stopReason === 'tool_use') {
|
|
234
|
+
return '';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Empty/non-text content is often due to safety refusal or token limits.
|
|
238
|
+
const contentTypes = content.map(block => block?.type || 'unknown').join(', ') || 'none';
|
|
239
|
+
|
|
240
|
+
if (stopReason === 'refusal') {
|
|
241
|
+
throw new Error('Anthropic refused to process this request (content policy). Try different wording or a fallback model.');
|
|
242
|
+
}
|
|
243
|
+
if (!content.length) {
|
|
244
|
+
throw new Error(`Anthropic returned empty content (stop_reason: ${stopReason ?? 'unknown'}).`);
|
|
245
|
+
}
|
|
246
|
+
throw new Error(`Anthropic content blocks are missing .text (stop_reason: ${stopReason ?? 'unknown'}, content_types: ${contentTypes}).`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static extractThinkingBlock(data) {
|
|
250
|
+
const content = Array.isArray(data?.content) ? data.content : [];
|
|
251
|
+
return content.find(block => block?.type === 'thinking') || null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static extractThink(data) {
|
|
255
|
+
const block = MixAnthropic.extractThinkingBlock(data);
|
|
256
|
+
// Preserve empty string: display "omitted" returns thinking: "" with a signature.
|
|
257
|
+
return typeof block?.thinking === 'string' ? block.thinking : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static extractSignature(data) {
|
|
261
|
+
const block = MixAnthropic.extractThinkingBlock(data);
|
|
262
|
+
return typeof block?.signature === 'string' && block.signature
|
|
263
|
+
? block.signature
|
|
264
|
+
: null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
static extractTokens(data) {
|
|
268
|
+
// Anthropic format
|
|
269
|
+
if (data.usage) {
|
|
270
|
+
const cached = ModelMix.extractCacheTokens(data.usage);
|
|
271
|
+
const cacheWrite5m = data.usage.cache_creation?.ephemeral_5m_input_tokens ?? 0;
|
|
272
|
+
const cacheWrite1h = data.usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
|
|
273
|
+
const cacheWrite = Math.max(
|
|
274
|
+
ModelMix.extractCacheWriteTokens(data.usage),
|
|
275
|
+
cacheWrite5m + cacheWrite1h
|
|
276
|
+
);
|
|
277
|
+
const input = (data.usage.input_tokens || 0) + cached + cacheWrite;
|
|
278
|
+
const output = data.usage.output_tokens || 0;
|
|
279
|
+
return ModelMix.normalizeTokenUsage({
|
|
280
|
+
input,
|
|
281
|
+
output,
|
|
282
|
+
total: input + output,
|
|
283
|
+
cached,
|
|
284
|
+
cacheWrite,
|
|
285
|
+
cacheWrite5m,
|
|
286
|
+
cacheWrite1h
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return ModelMix.normalizeTokenUsage();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
processResponse(response) {
|
|
293
|
+
const data = response.data;
|
|
294
|
+
return {
|
|
295
|
+
message: MixAnthropic.extractMessage(data),
|
|
296
|
+
think: MixAnthropic.extractThink(data),
|
|
297
|
+
toolCalls: MixAnthropic.extractToolCalls(data),
|
|
298
|
+
tokens: MixAnthropic.extractTokens(data),
|
|
299
|
+
response: data,
|
|
300
|
+
signature: MixAnthropic.extractSignature(data),
|
|
301
|
+
// Replay Anthropic content blocks verbatim (including empty thinking).
|
|
302
|
+
assistantMessage: Array.isArray(data?.content)
|
|
303
|
+
? { role: 'assistant', content: data.content }
|
|
304
|
+
: undefined
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
getOptionsTools(tools) {
|
|
309
|
+
return MixAnthropic.getOptionsTools(tools);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
static getOptionsTools(tools) {
|
|
313
|
+
const options = {};
|
|
314
|
+
const toolsArray = [];
|
|
315
|
+
for (const tool in tools) {
|
|
316
|
+
for (const item of tools[tool]) {
|
|
317
|
+
toolsArray.push({
|
|
318
|
+
name: item.name,
|
|
319
|
+
description: item.description,
|
|
320
|
+
input_schema: item.inputSchema
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Solo incluir tools si el array no está vacío
|
|
326
|
+
if (toolsArray.length > 0) {
|
|
327
|
+
options.tools = toolsArray;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return options;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return { MixAnthropic };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
module.exports = createAnthropicProviders;
|