Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions extensions/ext-llm-anthropic/src/anthropic-request-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,183 @@ describe("ext-llm-anthropic/anthropic-request-builder", () => {
]);
});

it("keeps same-turn tool results when later assistant steps continue without a user turn", () => {
const prompt: RuntimePromptMessage[] = [
{ role: "user", content: [{ type: "text", text: "Create an integration agent." }] },
{
role: "assistant",
content: [
{ type: "text", text: "I will load the platform skill." },
{
type: "tool-call",
toolCallId: "toolu_load_skill",
toolName: "load_skill",
input: { skillId: "veryfront" },
},
],
},
{
role: "tool",
content: [{
type: "tool-result",
toolCallId: "toolu_load_skill",
toolName: "load_skill",
output: {
type: "json",
value: {
skillId: "veryfront",
instructions: "Create agents with create_agent after gathering context.",
},
},
}],
},
{
role: "assistant",
content: [
{ type: "text", text: "Now I will inspect the integration." },
{
type: "tool-call",
toolCallId: "toolu_get_integration",
toolName: "get_integration",
input: { integration: "harvest" },
},
],
},
{
role: "tool",
content: [{
type: "tool-result",
toolCallId: "toolu_get_integration",
toolName: "get_integration",
output: {
type: "json",
value: { slug: "harvest", name: "Harvest" },
},
}],
},
];
const warnings = createWarningCollector();

const body = buildAnthropicMessagesRequest(
"claude-sonnet-4-6",
"anthropic",
{ prompt },
false,
warnings,
);

assertEquals(body.messages, [
{ role: "user", content: [{ type: "text", text: "Create an integration agent." }] },
{
role: "assistant",
content: [
{ type: "text", text: "I will load the platform skill." },
{
type: "tool_use",
id: "toolu_load_skill",
name: "load_skill",
input: { skillId: "veryfront" },
},
],
},
{
role: "user",
content: [{
type: "tool_result",
tool_use_id: "toolu_load_skill",
content:
'{"skillId":"veryfront","instructions":"Create agents with create_agent after gathering context."}',
}],
},
{
role: "assistant",
content: [
{ type: "text", text: "Now I will inspect the integration." },
{
type: "tool_use",
id: "toolu_get_integration",
name: "get_integration",
input: { integration: "harvest" },
},
],
},
{
role: "user",
content: [{
type: "tool_result",
tool_use_id: "toolu_get_integration",
content: '{"slug":"harvest","name":"Harvest"}',
}],
},
]);
});

it("keeps historical tool-only rounds when active same-turn assistant text follows the latest user", () => {
const prompt: RuntimePromptMessage[] = [
{ role: "user", content: [{ type: "text", text: "Start with account context." }] },
{
role: "assistant",
content: [{
type: "tool-call",
toolCallId: "toolu_account",
toolName: "account__lookup",
input: { id: "acct-1" },
}],
},
{
role: "tool",
content: [{
type: "tool-result",
toolCallId: "toolu_account",
toolName: "account__lookup",
output: { type: "json", value: { plan: "pro" } },
}],
},
{ role: "user", content: [{ type: "text", text: "retry with more detail" }] },
{
role: "assistant",
content: [{ type: "text", text: "I will continue from the account context." }],
},
];
const warnings = createWarningCollector();

const body = buildAnthropicMessagesRequest(
"claude-sonnet-4-6",
"anthropic",
{ prompt },
false,
warnings,
);

assertEquals(body.messages, [
{ role: "user", content: [{ type: "text", text: "Start with account context." }] },
{
role: "assistant",
content: [{
type: "tool_use",
id: "toolu_account",
name: "account__lookup",
input: { id: "acct-1" },
}],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "toolu_account",
content: '{"plan":"pro"}',
},
{ type: "text", text: "retry with more detail" },
],
},
{
role: "assistant",
content: [{ type: "text", text: "I will continue from the account context." }],
},
]);
});

it("drops orphaned tool results when their tool use was not emitted", () => {
const prompt: RuntimePromptMessage[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ function toAnthropicMessages(
} {
const systemParts: string[] = [];
const messages: AnthropicCompatibleMessage[] = [];
const lastAssistantTextIndex = prompt.findLastIndex((message) =>
const lastUserIndex = prompt.findLastIndex((message) => message.role === "user");
const lastHistoricalAssistantTextIndex = prompt.findLastIndex((message, index) =>
index < lastUserIndex &&
message.role === "assistant" &&
message.content.some((part) => part.type === "text" && part.text.length > 0)
);
Expand All @@ -232,7 +234,7 @@ function toAnthropicMessages(
break;
case "assistant": {
skippingHistoricalToolResults = false;
const shouldCompactCompletedToolRound = index < lastAssistantTextIndex &&
const shouldCompactCompletedToolRound = index < lastHistoricalAssistantTextIndex &&
message.content.some((part) => part.type === "tool-call");
const assistantContent = shouldCompactCompletedToolRound
? message.content.filter((part) => part.type === "text" && part.text.length > 0)
Expand Down