208 lines
6.6 KiB
JavaScript
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.
|
|
`;
|