mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
5b1ec85f75
- Add Anthropic prompt caching (cache_control on system prompt and tools) to reduce repeated token costs by ~90% - Truncate large tool results in conversation history (2000 char cap) to prevent context bloat from ifc_tree/ifc_select responses - Add sliding window (40 messages) on conversation history, trimming at user message boundaries to avoid breaking tool-call sequences - Default "New IFC" button to IFC4X3 schema instead of IFC4 - Constrain ifc_new schema parameter with enum to prevent invalid schema strings like "IFC4X3ADD2" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
185 lines
4.7 KiB
JavaScript
185 lines
4.7 KiB
JavaScript
// This file was generated with the assistance of an AI coding tool.
|
|
|
|
function parseArguments(argumentsText) {
|
|
if (!argumentsText) return {};
|
|
try {
|
|
return JSON.parse(argumentsText);
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function toAnthropicTools(tools = []) {
|
|
return tools.map((tool) => ({
|
|
name: tool.function.name,
|
|
description: tool.function.description,
|
|
input_schema: tool.function.parameters,
|
|
}));
|
|
}
|
|
|
|
function toAnthropicAssistantContent(message) {
|
|
const content = [];
|
|
|
|
if (message.content) {
|
|
content.push({ type: "text", text: message.content });
|
|
}
|
|
|
|
for (const toolCall of message.tool_calls ?? []) {
|
|
content.push({
|
|
type: "tool_use",
|
|
id: toolCall.id,
|
|
name: toolCall.function.name,
|
|
input: parseArguments(toolCall.function.arguments),
|
|
});
|
|
}
|
|
|
|
if (content.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
return content.length === 1 && content[0].type === "text" ? content[0].text : content;
|
|
}
|
|
|
|
function toAnthropicUserContent(message) {
|
|
return typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? "");
|
|
}
|
|
|
|
function toAnthropicToolResult(message) {
|
|
return {
|
|
type: "tool_result",
|
|
tool_use_id: message.tool_call_id,
|
|
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content ?? ""),
|
|
};
|
|
}
|
|
|
|
function splitSystemAndMessages(messages = []) {
|
|
const system = [];
|
|
const anthropicMessages = [];
|
|
let pendingToolResults = [];
|
|
|
|
const flushToolResults = () => {
|
|
if (pendingToolResults.length === 0) return;
|
|
anthropicMessages.push({ role: "user", content: pendingToolResults });
|
|
pendingToolResults = [];
|
|
};
|
|
|
|
for (const message of messages) {
|
|
if (message.role === "system") {
|
|
if (message.content) {
|
|
system.push(message.content);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (message.role === "tool") {
|
|
pendingToolResults.push(toAnthropicToolResult(message));
|
|
continue;
|
|
}
|
|
|
|
flushToolResults();
|
|
|
|
if (message.role === "user") {
|
|
anthropicMessages.push({
|
|
role: "user",
|
|
content: toAnthropicUserContent(message),
|
|
});
|
|
continue;
|
|
}
|
|
|
|
if (message.role === "assistant") {
|
|
anthropicMessages.push({
|
|
role: "assistant",
|
|
content: toAnthropicAssistantContent(message),
|
|
});
|
|
}
|
|
}
|
|
|
|
flushToolResults();
|
|
|
|
return {
|
|
system: system.join("\n\n"),
|
|
messages: anthropicMessages,
|
|
};
|
|
}
|
|
|
|
function toChatCompletionResponse(response) {
|
|
const text = [];
|
|
const toolCalls = [];
|
|
|
|
for (const block of response.content ?? []) {
|
|
if (block.type === "text") {
|
|
text.push(block.text);
|
|
continue;
|
|
}
|
|
|
|
if (block.type === "tool_use") {
|
|
toolCalls.push({
|
|
id: block.id,
|
|
type: "function",
|
|
function: {
|
|
name: block.name,
|
|
arguments: JSON.stringify(block.input ?? {}),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
const message = { role: "assistant" };
|
|
const content = text.join("\n").trim();
|
|
|
|
if (content) {
|
|
message.content = content;
|
|
}
|
|
|
|
if (toolCalls.length) {
|
|
message.tool_calls = toolCalls;
|
|
}
|
|
|
|
return {
|
|
choices: [
|
|
{
|
|
message,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
export async function chat({ apiKey, model, messages, tools }) {
|
|
const request = splitSystemAndMessages(messages);
|
|
const anthropicTools = toAnthropicTools(tools);
|
|
|
|
// Mark the last tool with cache_control so the entire tool list is cached
|
|
if (anthropicTools.length > 0) {
|
|
anthropicTools[anthropicTools.length - 1].cache_control = { type: "ephemeral" };
|
|
}
|
|
|
|
const body = {
|
|
model,
|
|
max_tokens: 4096,
|
|
messages: request.messages,
|
|
tools: anthropicTools,
|
|
};
|
|
|
|
if (request.system) {
|
|
body.system = [{ type: "text", text: request.system, cache_control: { type: "ephemeral" } }];
|
|
}
|
|
|
|
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"x-api-key": apiKey,
|
|
"anthropic-version": "2023-06-01",
|
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`Anthropic error ${res.status}: ${text}`);
|
|
}
|
|
|
|
return toChatCompletionResponse(await res.json());
|
|
}
|