anthropic-gateway 1.0.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/LICENSE +25 -0
- package/README.md +75 -0
- package/cli.js +261 -0
- package/config.example.json +20 -0
- package/lib/server.js +132 -0
- package/package.json +39 -0
- package/vendor/LICENSE.claude-adapter +21 -0
- package/vendor/dist/cli.js +288 -0
- package/vendor/dist/converters/index.js +22 -0
- package/vendor/dist/converters/request.js +349 -0
- package/vendor/dist/converters/response.js +125 -0
- package/vendor/dist/converters/streaming.js +308 -0
- package/vendor/dist/converters/tools.js +51 -0
- package/vendor/dist/converters/xmlPrompt.js +87 -0
- package/vendor/dist/converters/xmlStreaming.js +258 -0
- package/vendor/dist/index.js +32 -0
- package/vendor/dist/server/handlers.js +184 -0
- package/vendor/dist/server/index.js +116 -0
- package/vendor/dist/types/anthropic.js +4 -0
- package/vendor/dist/types/config.js +4 -0
- package/vendor/dist/types/index.js +21 -0
- package/vendor/dist/types/openai.js +4 -0
- package/vendor/dist/utils/config.js +169 -0
- package/vendor/dist/utils/errorLog.js +61 -0
- package/vendor/dist/utils/fileStorage.js +114 -0
- package/vendor/dist/utils/index.js +19 -0
- package/vendor/dist/utils/logger.js +138 -0
- package/vendor/dist/utils/metadata.js +128 -0
- package/vendor/dist/utils/provider.js +14 -0
- package/vendor/dist/utils/tokenUsage.js +32 -0
- package/vendor/dist/utils/ui.js +107 -0
- package/vendor/dist/utils/update.js +112 -0
- package/vendor/dist/utils/validation.js +130 -0
- package/vendor/package.json +6 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UpstreamResponseError = void 0;
|
|
4
|
+
exports.convertResponseToAnthropic = convertResponseToAnthropic;
|
|
5
|
+
exports.createErrorResponse = createErrorResponse;
|
|
6
|
+
/** An upstream response was structurally unusable for Anthropic conversion. */
|
|
7
|
+
class UpstreamResponseError extends Error {
|
|
8
|
+
status = 502;
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'UpstreamResponseError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.UpstreamResponseError = UpstreamResponseError;
|
|
15
|
+
/**
|
|
16
|
+
* Convert OpenAI Chat Completion response to Anthropic Messages format
|
|
17
|
+
*/
|
|
18
|
+
function convertResponseToAnthropic(openaiResponse, originalModelRequested) {
|
|
19
|
+
const choice = openaiResponse.choices?.[0];
|
|
20
|
+
if (!choice?.message) {
|
|
21
|
+
throw new UpstreamResponseError('Upstream response did not include a completion choice');
|
|
22
|
+
}
|
|
23
|
+
const message = choice.message;
|
|
24
|
+
// Build content blocks
|
|
25
|
+
const content = [];
|
|
26
|
+
// Add text content if present
|
|
27
|
+
if (message.content) {
|
|
28
|
+
content.push({
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: message.content,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Add tool use blocks if present
|
|
34
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
35
|
+
for (const toolCall of message.tool_calls) {
|
|
36
|
+
content.push(convertToolCallToToolUse(toolCall));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Map finish reason
|
|
40
|
+
const stopReason = mapFinishReason(choice.finish_reason);
|
|
41
|
+
// Build usage
|
|
42
|
+
const upstreamUsage = openaiResponse.usage;
|
|
43
|
+
const usage = {
|
|
44
|
+
input_tokens: upstreamUsage?.prompt_tokens ?? 0,
|
|
45
|
+
output_tokens: upstreamUsage?.completion_tokens ?? 0,
|
|
46
|
+
cache_read_input_tokens: upstreamUsage?.prompt_tokens_details?.cached_tokens,
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
id: `msg_${openaiResponse.id}`,
|
|
50
|
+
type: 'message',
|
|
51
|
+
role: 'assistant',
|
|
52
|
+
content,
|
|
53
|
+
model: originalModelRequested,
|
|
54
|
+
stop_reason: stopReason,
|
|
55
|
+
stop_sequence: null,
|
|
56
|
+
usage,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Convert OpenAI tool call to Anthropic tool_use block
|
|
61
|
+
*/
|
|
62
|
+
function convertToolCallToToolUse(toolCall) {
|
|
63
|
+
let input;
|
|
64
|
+
try {
|
|
65
|
+
input = JSON.parse(toolCall.function.arguments);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
input = { raw: toolCall.function.arguments };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
type: 'tool_use',
|
|
72
|
+
id: toolCall.id,
|
|
73
|
+
name: toolCall.function.name,
|
|
74
|
+
input,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Map OpenAI finish_reason to Anthropic stop_reason
|
|
79
|
+
*/
|
|
80
|
+
function mapFinishReason(finishReason) {
|
|
81
|
+
if (!finishReason)
|
|
82
|
+
return null;
|
|
83
|
+
switch (finishReason) {
|
|
84
|
+
case 'stop':
|
|
85
|
+
return 'end_turn';
|
|
86
|
+
case 'length':
|
|
87
|
+
return 'max_tokens';
|
|
88
|
+
case 'tool_calls':
|
|
89
|
+
return 'tool_use';
|
|
90
|
+
case 'content_filter':
|
|
91
|
+
return 'end_turn'; // Map to end_turn as closest equivalent
|
|
92
|
+
default:
|
|
93
|
+
return 'end_turn';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create an error response in Anthropic format
|
|
98
|
+
*/
|
|
99
|
+
function createErrorResponse(error, statusCode = 500) {
|
|
100
|
+
return {
|
|
101
|
+
error: {
|
|
102
|
+
type: mapErrorType(statusCode),
|
|
103
|
+
message: error.message,
|
|
104
|
+
},
|
|
105
|
+
status: statusCode,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function mapErrorType(statusCode) {
|
|
109
|
+
switch (statusCode) {
|
|
110
|
+
case 400:
|
|
111
|
+
return 'invalid_request_error';
|
|
112
|
+
case 401:
|
|
113
|
+
return 'authentication_error';
|
|
114
|
+
case 403:
|
|
115
|
+
return 'permission_error';
|
|
116
|
+
case 404:
|
|
117
|
+
return 'not_found_error';
|
|
118
|
+
case 429:
|
|
119
|
+
return 'rate_limit_error';
|
|
120
|
+
case 500:
|
|
121
|
+
default:
|
|
122
|
+
return 'api_error';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=response.js.map
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BoundedToolIdRegistry = void 0;
|
|
4
|
+
exports.streamOpenAIToAnthropic = streamOpenAIToAnthropic;
|
|
5
|
+
const tools_1 = require("./tools");
|
|
6
|
+
const tokenUsage_1 = require("../utils/tokenUsage");
|
|
7
|
+
const errorLog_1 = require("../utils/errorLog");
|
|
8
|
+
// Global counter and bounded registry for unique tool IDs within this process.
|
|
9
|
+
let toolIdCounter = 0;
|
|
10
|
+
class BoundedToolIdRegistry {
|
|
11
|
+
maxEntries;
|
|
12
|
+
pruneCount;
|
|
13
|
+
ids = new Set();
|
|
14
|
+
constructor(maxEntries = 10000, pruneCount = 5000) {
|
|
15
|
+
this.maxEntries = maxEntries;
|
|
16
|
+
this.pruneCount = pruneCount;
|
|
17
|
+
}
|
|
18
|
+
has(id) {
|
|
19
|
+
return this.ids.has(id);
|
|
20
|
+
}
|
|
21
|
+
/** Reserve an ID and return whether it had not been seen before. */
|
|
22
|
+
claim(id) {
|
|
23
|
+
if (this.ids.has(id)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
this.ids.add(id);
|
|
27
|
+
if (this.ids.size > this.maxEntries) {
|
|
28
|
+
const idsToRemove = Math.min(this.pruneCount, this.ids.size);
|
|
29
|
+
const iterator = this.ids.values();
|
|
30
|
+
for (let i = 0; i < idsToRemove; i++) {
|
|
31
|
+
const oldest = iterator.next().value;
|
|
32
|
+
if (oldest === undefined)
|
|
33
|
+
break;
|
|
34
|
+
this.ids.delete(oldest);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
get size() {
|
|
40
|
+
return this.ids.size;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.BoundedToolIdRegistry = BoundedToolIdRegistry;
|
|
44
|
+
const usedToolIds = new BoundedToolIdRegistry();
|
|
45
|
+
function generateUniqueToolId() {
|
|
46
|
+
let id;
|
|
47
|
+
do {
|
|
48
|
+
toolIdCounter++;
|
|
49
|
+
const timestamp = Date.now().toString(36);
|
|
50
|
+
const counter = toolIdCounter.toString(36).padStart(4, '0');
|
|
51
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
52
|
+
id = `call_${timestamp}_${counter}_${random}`;
|
|
53
|
+
} while (usedToolIds.has(id));
|
|
54
|
+
usedToolIds.claim(id);
|
|
55
|
+
return id;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Transform OpenAI streaming response to Anthropic SSE format
|
|
59
|
+
*/
|
|
60
|
+
async function streamOpenAIToAnthropic(openaiStream, reply, originalModel, provider = '') {
|
|
61
|
+
const state = {
|
|
62
|
+
messageId: `msg_${Date.now().toString(36)}`,
|
|
63
|
+
model: originalModel,
|
|
64
|
+
responseModel: '',
|
|
65
|
+
provider,
|
|
66
|
+
contentBlockIndex: 0,
|
|
67
|
+
currentToolCalls: new Map(),
|
|
68
|
+
inputTokens: 0,
|
|
69
|
+
outputTokens: 0,
|
|
70
|
+
cachedInputTokens: 0,
|
|
71
|
+
hasStarted: false,
|
|
72
|
+
textContent: '',
|
|
73
|
+
textBlockOpen: false,
|
|
74
|
+
};
|
|
75
|
+
// Access the underlying Node.js response for SSE streaming
|
|
76
|
+
const raw = reply.raw;
|
|
77
|
+
// Set SSE headers
|
|
78
|
+
raw.setHeader('Content-Type', 'text/event-stream');
|
|
79
|
+
raw.setHeader('Cache-Control', 'no-cache');
|
|
80
|
+
raw.setHeader('Connection', 'keep-alive');
|
|
81
|
+
raw.setHeader('X-Accel-Buffering', 'no');
|
|
82
|
+
try {
|
|
83
|
+
for await (const chunk of openaiStream) {
|
|
84
|
+
processChunk(chunk, state, raw);
|
|
85
|
+
}
|
|
86
|
+
// Send final events
|
|
87
|
+
finishStream(state, raw);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
sendErrorEvent(normalizeStreamingError(error), state, raw);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function normalizeStreamingError(error) {
|
|
94
|
+
return error instanceof Error
|
|
95
|
+
? error
|
|
96
|
+
: new Error(typeof error === 'string' ? error : 'Unknown streaming failure');
|
|
97
|
+
}
|
|
98
|
+
function processChunk(chunk, state, raw) {
|
|
99
|
+
// Update usage if present
|
|
100
|
+
if (chunk.usage) {
|
|
101
|
+
state.inputTokens = chunk.usage.prompt_tokens;
|
|
102
|
+
state.outputTokens = chunk.usage.completion_tokens;
|
|
103
|
+
state.cachedInputTokens = chunk.usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
104
|
+
}
|
|
105
|
+
// Capture response model from chunk
|
|
106
|
+
if (chunk.model && !state.responseModel) {
|
|
107
|
+
state.responseModel = chunk.model;
|
|
108
|
+
}
|
|
109
|
+
const choice = chunk.choices[0];
|
|
110
|
+
if (!choice)
|
|
111
|
+
return;
|
|
112
|
+
// Send message_start on first chunk
|
|
113
|
+
if (!state.hasStarted) {
|
|
114
|
+
sendMessageStart(state, raw);
|
|
115
|
+
state.hasStarted = true;
|
|
116
|
+
}
|
|
117
|
+
const delta = choice.delta;
|
|
118
|
+
// Handle text content
|
|
119
|
+
if (delta.content) {
|
|
120
|
+
if (!state.textBlockOpen) {
|
|
121
|
+
sendContentBlockStart(state.contentBlockIndex, 'text', '', raw);
|
|
122
|
+
state.textBlockOpen = true;
|
|
123
|
+
}
|
|
124
|
+
state.textContent += delta.content;
|
|
125
|
+
sendTextDelta(state.contentBlockIndex, delta.content, raw);
|
|
126
|
+
}
|
|
127
|
+
// Handle tool calls
|
|
128
|
+
if (delta.tool_calls) {
|
|
129
|
+
for (const toolCall of delta.tool_calls) {
|
|
130
|
+
processToolCallDelta(toolCall, state, raw);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Handle finish reason
|
|
134
|
+
if (choice.finish_reason) {
|
|
135
|
+
if (state.textBlockOpen) {
|
|
136
|
+
sendContentBlockStop(state.contentBlockIndex, raw);
|
|
137
|
+
state.textBlockOpen = false;
|
|
138
|
+
state.textContent = '';
|
|
139
|
+
state.contentBlockIndex++;
|
|
140
|
+
}
|
|
141
|
+
for (const toolCall of state.currentToolCalls.values()) {
|
|
142
|
+
sendContentBlockStop(toolCall.blockIndex, raw);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function processToolCallDelta(toolCall, state, raw) {
|
|
147
|
+
const index = toolCall.index;
|
|
148
|
+
// Check if this is a new tool call
|
|
149
|
+
if (!state.currentToolCalls.has(index)) {
|
|
150
|
+
if (state.textBlockOpen) {
|
|
151
|
+
sendContentBlockStop(state.contentBlockIndex, raw);
|
|
152
|
+
state.textBlockOpen = false;
|
|
153
|
+
state.textContent = '';
|
|
154
|
+
state.contentBlockIndex++;
|
|
155
|
+
}
|
|
156
|
+
// IMPORTANT: Use the original OpenAI tool ID to maintain consistency
|
|
157
|
+
// This ID must match when tool results are sent back
|
|
158
|
+
// If OpenAI doesn't provide an ID, generate a guaranteed unique one
|
|
159
|
+
let toolId;
|
|
160
|
+
if (toolCall.id && usedToolIds.claim(toolCall.id)) {
|
|
161
|
+
toolId = toolCall.id;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
toolId = generateUniqueToolId();
|
|
165
|
+
}
|
|
166
|
+
const blockIndex = state.contentBlockIndex + index;
|
|
167
|
+
const newToolCall = {
|
|
168
|
+
id: toolId,
|
|
169
|
+
name: toolCall.function?.name || '',
|
|
170
|
+
arguments: '',
|
|
171
|
+
blockIndex,
|
|
172
|
+
};
|
|
173
|
+
state.currentToolCalls.set(index, newToolCall);
|
|
174
|
+
sendContentBlockStart(blockIndex, 'tool_use', newToolCall.name, raw, newToolCall.id);
|
|
175
|
+
}
|
|
176
|
+
// Update tool call data
|
|
177
|
+
const currentCall = state.currentToolCalls.get(index);
|
|
178
|
+
if (toolCall.function?.name) {
|
|
179
|
+
currentCall.name = toolCall.function.name;
|
|
180
|
+
}
|
|
181
|
+
if (toolCall.function?.arguments) {
|
|
182
|
+
currentCall.arguments += toolCall.function.arguments;
|
|
183
|
+
sendInputJsonDelta(currentCall.blockIndex, toolCall.function.arguments, raw);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function sendMessageStart(state, raw) {
|
|
187
|
+
const event = {
|
|
188
|
+
type: 'message_start',
|
|
189
|
+
message: {
|
|
190
|
+
id: state.messageId,
|
|
191
|
+
type: 'message',
|
|
192
|
+
role: 'assistant',
|
|
193
|
+
content: [],
|
|
194
|
+
model: state.model,
|
|
195
|
+
stop_reason: null,
|
|
196
|
+
stop_sequence: null,
|
|
197
|
+
usage: {
|
|
198
|
+
input_tokens: state.inputTokens,
|
|
199
|
+
output_tokens: state.outputTokens,
|
|
200
|
+
cache_read_input_tokens: state.cachedInputTokens,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
sendSSE(event, raw);
|
|
205
|
+
}
|
|
206
|
+
function sendContentBlockStart(index, type, textOrName, raw, id) {
|
|
207
|
+
let contentBlock;
|
|
208
|
+
if (type === 'text') {
|
|
209
|
+
contentBlock = { type: 'text', text: '' };
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
contentBlock = {
|
|
213
|
+
type: 'tool_use',
|
|
214
|
+
id: id || (0, tools_1.generateToolUseId)(),
|
|
215
|
+
name: textOrName,
|
|
216
|
+
input: {},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const event = {
|
|
220
|
+
type: 'content_block_start',
|
|
221
|
+
index,
|
|
222
|
+
content_block: contentBlock,
|
|
223
|
+
};
|
|
224
|
+
sendSSE(event, raw);
|
|
225
|
+
}
|
|
226
|
+
function sendTextDelta(index, text, raw) {
|
|
227
|
+
const event = {
|
|
228
|
+
type: 'content_block_delta',
|
|
229
|
+
index,
|
|
230
|
+
delta: {
|
|
231
|
+
type: 'text_delta',
|
|
232
|
+
text,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
sendSSE(event, raw);
|
|
236
|
+
}
|
|
237
|
+
function sendInputJsonDelta(index, partialJson, raw) {
|
|
238
|
+
const event = {
|
|
239
|
+
type: 'content_block_delta',
|
|
240
|
+
index,
|
|
241
|
+
delta: {
|
|
242
|
+
type: 'input_json_delta',
|
|
243
|
+
partial_json: partialJson,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
sendSSE(event, raw);
|
|
247
|
+
}
|
|
248
|
+
function sendContentBlockStop(index, raw) {
|
|
249
|
+
const event = {
|
|
250
|
+
type: 'content_block_stop',
|
|
251
|
+
index,
|
|
252
|
+
};
|
|
253
|
+
sendSSE(event, raw);
|
|
254
|
+
}
|
|
255
|
+
function finishStream(state, raw) {
|
|
256
|
+
// Determine stop reason
|
|
257
|
+
const hasToolCalls = state.currentToolCalls.size > 0;
|
|
258
|
+
const stopReason = hasToolCalls ? 'tool_use' : 'end_turn';
|
|
259
|
+
// Record token usage
|
|
260
|
+
(0, tokenUsage_1.recordUsage)({
|
|
261
|
+
provider: state.provider,
|
|
262
|
+
modelName: state.model,
|
|
263
|
+
model: state.responseModel || undefined,
|
|
264
|
+
inputTokens: state.inputTokens,
|
|
265
|
+
outputTokens: state.outputTokens,
|
|
266
|
+
cachedInputTokens: state.cachedInputTokens || undefined,
|
|
267
|
+
streaming: true,
|
|
268
|
+
});
|
|
269
|
+
// Send message_delta
|
|
270
|
+
const deltaEvent = {
|
|
271
|
+
type: 'message_delta',
|
|
272
|
+
delta: {
|
|
273
|
+
stop_reason: stopReason,
|
|
274
|
+
stop_sequence: null,
|
|
275
|
+
},
|
|
276
|
+
usage: {
|
|
277
|
+
output_tokens: state.outputTokens,
|
|
278
|
+
cache_read_input_tokens: state.cachedInputTokens,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
sendSSE(deltaEvent, raw);
|
|
282
|
+
// Send message_stop
|
|
283
|
+
sendSSE({ type: 'message_stop' }, raw);
|
|
284
|
+
raw.end();
|
|
285
|
+
}
|
|
286
|
+
function sendErrorEvent(error, state, raw) {
|
|
287
|
+
// Record error to file
|
|
288
|
+
(0, errorLog_1.recordError)(error, {
|
|
289
|
+
requestId: state.messageId,
|
|
290
|
+
provider: state.provider,
|
|
291
|
+
modelName: state.model,
|
|
292
|
+
streaming: true,
|
|
293
|
+
});
|
|
294
|
+
const event = {
|
|
295
|
+
type: 'error',
|
|
296
|
+
error: {
|
|
297
|
+
type: 'api_error',
|
|
298
|
+
message: error.message,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
sendSSE(event, raw);
|
|
302
|
+
raw.end();
|
|
303
|
+
}
|
|
304
|
+
function sendSSE(data, raw) {
|
|
305
|
+
raw.write(`event: ${data.type}\n`);
|
|
306
|
+
raw.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=streaming.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertToolsToOpenAI = convertToolsToOpenAI;
|
|
4
|
+
exports.convertToolChoiceToOpenAI = convertToolChoiceToOpenAI;
|
|
5
|
+
exports.generateToolUseId = generateToolUseId;
|
|
6
|
+
/**
|
|
7
|
+
* Convert Anthropic tool definitions to OpenAI function format
|
|
8
|
+
*/
|
|
9
|
+
function convertToolsToOpenAI(tools) {
|
|
10
|
+
return tools.map(tool => ({
|
|
11
|
+
type: 'function',
|
|
12
|
+
function: {
|
|
13
|
+
name: tool.name,
|
|
14
|
+
description: tool.description,
|
|
15
|
+
parameters: tool.input_schema,
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Convert Anthropic tool choice to OpenAI format
|
|
21
|
+
*/
|
|
22
|
+
function convertToolChoiceToOpenAI(toolChoice) {
|
|
23
|
+
switch (toolChoice.type) {
|
|
24
|
+
case 'auto':
|
|
25
|
+
return 'auto';
|
|
26
|
+
case 'any':
|
|
27
|
+
return 'required'; // OpenAI's equivalent - forces tool use
|
|
28
|
+
case 'tool':
|
|
29
|
+
if (toolChoice.name) {
|
|
30
|
+
return {
|
|
31
|
+
type: 'function',
|
|
32
|
+
function: { name: toolChoice.name },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return 'auto';
|
|
36
|
+
default:
|
|
37
|
+
return 'auto';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate a unique tool use ID in Anthropic format
|
|
42
|
+
*/
|
|
43
|
+
function generateToolUseId() {
|
|
44
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
45
|
+
let result = 'toolu_';
|
|
46
|
+
for (let i = 0; i < 24; i++) {
|
|
47
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateXmlToolInstructions = generateXmlToolInstructions;
|
|
4
|
+
exports.hasXmlToolInstructions = hasXmlToolInstructions;
|
|
5
|
+
/**
|
|
6
|
+
* Escape special XML characters in a string
|
|
7
|
+
*/
|
|
8
|
+
function escapeXml(str) {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/&/g, '&')
|
|
11
|
+
.replace(/</g, '<')
|
|
12
|
+
.replace(/>/g, '>')
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/'/g, ''');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generate XML tool instructions to inject into system prompt.
|
|
18
|
+
* This enables models without native function calling to use tools via XML output.
|
|
19
|
+
*/
|
|
20
|
+
function generateXmlToolInstructions(tools) {
|
|
21
|
+
if (!tools || tools.length === 0) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
const toolDefinitions = tools.map(t => {
|
|
25
|
+
const schemaJson = JSON.stringify(t.input_schema, null, 2);
|
|
26
|
+
return `- **${t.name}**: ${escapeXml(t.description)}
|
|
27
|
+
Parameters: ${schemaJson}`;
|
|
28
|
+
}).join('\n\n');
|
|
29
|
+
return `
|
|
30
|
+
# TOOL CALLING FORMAT
|
|
31
|
+
|
|
32
|
+
You are required to use tools to fetch information or perform actions.
|
|
33
|
+
To invoke a tool, you MUST use the following EXACT XML format.
|
|
34
|
+
ANY deviation from this format will cause the tool call to fail.
|
|
35
|
+
|
|
36
|
+
<tool_code name="TOOL_NAME">
|
|
37
|
+
{"argument_name": "value"}
|
|
38
|
+
</tool_code>
|
|
39
|
+
|
|
40
|
+
## CRITICAL EXECUTION RULES:
|
|
41
|
+
1. **NO Markdown**: Do NOT wrap the XML in \`\`\`xml or \`\`\` code blocks. Output the raw XML tags directly.
|
|
42
|
+
2. **Valid JSON**: The content between the tags MUST be valid, parseable JSON.
|
|
43
|
+
- Use double quotes for keys and string values.
|
|
44
|
+
- No trailing commas.
|
|
45
|
+
- No comments using // or /*.
|
|
46
|
+
3. **Exact Name Match**: The \`name\` attribute MUST match a tool name from the "Available Tools" list exactly (case-sensitive).
|
|
47
|
+
4. **No Nested Content**: The JSON parameters must be the direct child of \`tool_code\`. Do not nest another \`tool\` or \`function\` tag inside.
|
|
48
|
+
5. **Thinking**: If you need to think or explain your reasoning, do so in text BEFORE the \`<tool_code>\` block. Do NOT put thoughts inside the tool code.
|
|
49
|
+
6. **Multiple Tools**: You may call multiple tools in sequence by outputting multiple \`<tool_code>\` blocks.
|
|
50
|
+
7. **Tool Outputs**: Tool results will be provided to you in the following format:
|
|
51
|
+
<tool_output>
|
|
52
|
+
{result_json_or_text}
|
|
53
|
+
</tool_output>
|
|
54
|
+
|
|
55
|
+
## EXAMPLE (Correct):
|
|
56
|
+
Thinking: I need to read the file.
|
|
57
|
+
<tool_code name="Read">
|
|
58
|
+
{"file_path": "src/utils.ts"}
|
|
59
|
+
</tool_code>
|
|
60
|
+
|
|
61
|
+
## EXAMPLES (Incorrect - DO NOT USE):
|
|
62
|
+
Wrapped in code blocks:
|
|
63
|
+
\`\`\`xml
|
|
64
|
+
<tool_code name="Read">...</tool_code>
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
Nested tags:
|
|
68
|
+
<tool_code><tool name="Read">...</tool></tool_code>
|
|
69
|
+
|
|
70
|
+
Invalid JSON (keys not quoted):
|
|
71
|
+
<tool_code name="Read">
|
|
72
|
+
{file_path: "src/utils.ts"}
|
|
73
|
+
</tool_code>
|
|
74
|
+
|
|
75
|
+
## Available Tools:
|
|
76
|
+
|
|
77
|
+
${toolDefinitions}
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if a system prompt already contains XML tool instructions
|
|
82
|
+
*/
|
|
83
|
+
function hasXmlToolInstructions(systemPrompt) {
|
|
84
|
+
return systemPrompt.includes('# TOOL CALLING FORMAT') &&
|
|
85
|
+
systemPrompt.includes('<tool_code');
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=xmlPrompt.js.map
|