Files
2026-03-03 23:49:13 +01:00

208 lines
6.6 KiB
JavaScript

"use strict";
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Anthropic = void 0;
const fetchWithTimeout_1 = require("../fetchWithTimeout");
const types_1 = require("../types");
class Anthropic {
name = 'anthropic';
async complete(conversation, options) {
const maxTokens = Math.min(options.maxTokens ?? 32_768, 32_768);
const { response, error } = await create({
model: options.model,
max_tokens: maxTokens,
temperature: options.temperature,
system: systemPrompt(conversation.systemPrompt),
messages: conversation.messages.map(toAnthropicMessageParts).flat(),
tools: conversation.tools.map(toAnthropicTool),
thinking: options.reasoning && options.reasoning !== 'none' ? {
type: 'enabled',
budget_tokens: options.maxTokens ? Math.round(maxTokens / 10) : 1024,
} : undefined,
}, options);
if (error || !response)
return { result: (0, types_1.assistantMessageFromError)(error ?? 'No response from Anthropic API'), usage: (0, types_1.emptyUsage)() };
const result = toAssistantMessage(response);
const usage = {
input: response.usage.input_tokens,
output: response.usage.output_tokens,
};
return { result, usage };
}
}
exports.Anthropic = Anthropic;
async function create(createParams, options) {
const headers = {
'Content-Type': 'application/json',
'x-api-key': options.apiKey,
'anthropic-version': '2023-06-01',
};
const debugBody = { ...createParams, tools: `${createParams.tools?.length ?? 0} tools` };
options.debug?.('lowire:anthropic')('Request:', JSON.stringify(debugBody, null, 2));
const response = await (0, fetchWithTimeout_1.fetchWithTimeout)(options.apiEndpoint ?? `https://api.anthropic.com/v1/messages`, {
method: 'POST',
headers,
body: JSON.stringify(createParams),
signal: options.signal,
timeout: options.apiTimeout
});
const responseText = await response.text();
const responseBody = JSON.parse(responseText);
options.debug?.('lowire:anthropic')('Response:', responseText);
if (!response.ok) {
options.debug?.('lowire:anthropic')('Response:', response.status);
return { error: `API error: ${response.status} ${response.statusText} ${responseText}` };
}
return { response: responseBody };
}
function toContentPart(block) {
if (block.type === 'text') {
return {
type: 'text',
text: block.text,
};
}
if (block.type === 'tool_use') {
return {
type: 'tool_call',
name: block.name,
arguments: block.input,
id: block.id,
};
}
if (block.type === 'thinking') {
return {
type: 'thinking',
thinking: block.thinking,
signature: block.signature,
};
}
return null;
}
function toAnthropicResultParam(part) {
if (part.type === 'text') {
return {
type: 'text',
text: part.text,
};
}
if (part.type === 'image') {
return {
type: 'image',
source: {
type: 'base64',
data: part.data,
media_type: part.mimeType
},
};
}
throw new Error(`Unsupported content part type: ${part.type}`);
}
function toAssistantMessage(message) {
const stopReason = { code: 'ok' };
if (message.stop_reason === 'max_tokens')
stopReason.code = 'max_tokens';
return {
role: 'assistant',
content: message.content.map(toContentPart).filter(Boolean),
stopReason,
};
}
function toAnthropicTool(tool) {
return {
name: tool.name,
description: tool.description,
input_schema: tool.inputSchema,
};
}
function toAnthropicAssistantMessageParam(message) {
const content = [];
const toolResults = [];
for (const part of message.content) {
if (part.type === 'text') {
content.push({ ...part, citations: [] });
continue;
}
if (part.type === 'tool_call') {
content.push({
type: 'tool_use',
id: part.id,
name: part.name,
input: part.arguments
});
if (part.result)
toolResults.push(toAnthropicToolResultMessage(part, part.result));
continue;
}
if (part.type === 'thinking') {
content.push({
type: 'thinking',
thinking: part.thinking,
signature: part.signature,
});
continue;
}
}
if (message.toolError) {
toolResults.push({
role: 'user',
content: [{
type: 'text',
text: message.toolError,
}]
});
}
return [{
role: 'assistant',
content
}, ...toolResults];
}
function toAnthropicToolResultMessage(call, result) {
const toolResult = {
type: 'tool_result',
tool_use_id: call.id,
content: result.content.map(toAnthropicResultParam),
is_error: result.isError,
};
return {
role: 'user',
content: [toolResult]
};
}
function toAnthropicMessageParts(message) {
if (message.role === 'user') {
return [{
role: 'user',
content: message.content
}];
}
if (message.role === 'assistant')
return toAnthropicAssistantMessageParam(message);
throw new Error(`Unsupported message role: ${message.role}`);
}
const systemPrompt = (prompt) => `
### System instructions
${prompt}
### Tool calling instructions
- Make sure every message contains a tool call.
- When you use a tool, you may provide a brief thought or explanation in the content field
immediately before the tool_call. Do not split this into separate messages.
- Every reply must include a tool call.
`;