Ctrl K
ComunidadGuías…

packages/ai/test/tool-history.test.ts

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { describe, expect, test } from "bun:test"
import { Message, ToolCallPart, ToolResultPart } from "../src/schema/messages.js"
import { normalizeToolHistory } from "../src/tool-history.js"

const toolCall = (id: string, name = id) => ToolCallPart.make({ id, name, input: {} })
const toolResult = (id: string, value: unknown, name = id, resultType?: "text" | "content" | "error") =>
  Message.tool(ToolResultPart.make({ id, name, result: value, resultType }))

describe("tool history normalization", () => {
  test("fills missing local results before the next step", () => {
    const normalized = normalizeToolHistory([
      Message.assistant([toolCall("first"), toolCall("second")]),
      toolResult("first", "done", "wrong", "text"),
      Message.user("Continue."),
      Message.assistant(toolCall("trailing")),
    ])

    expect(normalized.map((message) => message.role)).toEqual([
      "assistant",
      "tool",
      "tool",
      "user",
      "assistant",
      "tool",
    ])
    expect(normalized[1]?.content[0]).toMatchObject({ type: "tool-result", id: "first", name: "first" })
    expect(normalized[2]?.content).toEqual([
      { type: "tool-result", id: "second", name: "second", result: { type: "error", value: "Tool result missing" } },
    ])
    expect(normalized[4]?.content).toEqual([toolCall("trailing")])
    expect(normalized[5]?.content).toEqual([
      {
        type: "tool-result",
        id: "trailing",
        name: "trailing",
        result: { type: "error", value: "Tool result missing" },
      },
    ])
  })

  test("fills missing results for trailing calls without replacing available results", () => {
    const calls = Message.assistant([toolCall("answered"), toolCall("unanswered")])
    const answered = toolResult("answered", "done", "answered", "text")
    const hosted = ToolCallPart.make({ id: "hosted", name: "web_search", input: {}, providerExecuted: true })

    expect(normalizeToolHistory([calls, answered])).toEqual([
      calls,
      answered,
      Message.tool(
        ToolResultPart.make({
          id: "unanswered",
          name: "unanswered",
          result: "Tool result missing",
          resultType: "error",
        }),
      ),
    ])
    expect(normalizeToolHistory([Message.assistant(hosted)])).toEqual([Message.assistant(hosted)])
  })

  test("normalizes empty results without changing whitespace or media", () => {
    const media = { type: "file" as const, uri: "data:image/png;base64,AQID", mime: "image/png" }
    const normalized = normalizeToolHistory([
      Message.assistant([
        toolCall("text"),
        toolCall("content"),
        toolCall("error"),
        toolCall("mixed"),
        toolCall("whitespace"),
      ]),
      toolResult("text", "", "text", "text"),
      toolResult("content", [], "content", "content"),
      toolResult("error", "", "error", "error"),
      toolResult("mixed", [{ type: "text", text: "" }, media], "mixed", "content"),
      toolResult("whitespace", "   ", "whitespace", "text"),
    ])

    expect(normalized.slice(1).map((message) => message.content[0])).toEqual([
      { type: "tool-result", id: "text", name: "text", result: { type: "text", value: "(no tool output)" } },
      { type: "tool-result", id: "content", name: "content", result: { type: "text", value: "(no tool output)" } },
      { type: "tool-result", id: "error", name: "error", result: { type: "error", value: "(no tool output)" } },
      { type: "tool-result", id: "mixed", name: "mixed", result: { type: "content", value: [media] } },
      { type: "tool-result", id: "whitespace", name: "whitespace", result: { type: "text", value: "   " } },
    ])
  })

  test("leaves unmatched and provider-executed history unchanged", () => {
    const hostedCall = ToolCallPart.make({
      id: "hosted",
      name: "web_search",
      input: {},
      providerExecuted: true,
    })
    const hostedResult = ToolResultPart.make({
      id: "hosted",
      name: "web_search",
      result: "",
      resultType: "text",
      providerExecuted: true,
    })
    const hosted = Message.assistant([hostedCall, hostedResult])
    const orphan = toolResult("orphan", "ignored", "orphan", "text")

    expect(normalizeToolHistory([orphan, hosted])).toEqual([orphan, hosted])
  })

  test("uses a matching call as the complete tool identity", () => {
    const normalized = normalizeToolHistory([
      Message.assistant(ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })),
      Message.tool(ToolResultPart.make({ id: "call_1", name: "wrong", namespace: "stale", result: "done" })),
    ])

    expect(normalized[1]?.content[0]).toMatchObject({ name: "lookup", namespace: undefined })
  })

  test("moves system updates after the results of pending calls", () => {
    const calls = Message.assistant([toolCall("first"), toolCall("second")])
    const first = toolResult("first", "one", "first", "text")
    const second = toolResult("second", "two", "second", "text")
    const update = Message.system("First update.")
    const later = Message.system("Second update.")
    const user = Message.user("Continue.")
    const missing = (id: string) =>
      Message.tool(ToolResultPart.make({ id, name: id, result: "Tool result missing", resultType: "error" }))

    expect(normalizeToolHistory([calls, first, update, later, second, user])).toEqual([
      calls,
      first,
      second,
      update,
      later,
      user,
    ])
    expect(normalizeToolHistory([Message.assistant(toolCall("first")), update, user])).toEqual([
      Message.assistant(toolCall("first")),
      missing("first"),
      update,
      user,
    ])
    expect(normalizeToolHistory([Message.assistant(toolCall("first")), update])).toEqual([
      Message.assistant(toolCall("first")),
      missing("first"),
      update,
    ])
  })

  test("moves effort updates after the results of pending calls", () => {
    const call = Message.assistant(toolCall("first"))
    const result = toolResult("first", "one", "first", "text")
    const effort = Message.effort({ effort: "low", previous: "high" })

    expect(normalizeToolHistory([call, effort, result])).toEqual([call, result, effort])
  })

  test("keeps system updates in place when no call is pending", () => {
    const history = [Message.assistant(toolCall("first")), toolResult("first", "one", "first", "text")]
    const update = Message.system("Update.")
    const input = [Message.user("Start."), update, ...history, update, Message.user("Continue.")]

    expect(normalizeToolHistory(input)).toBe(input)
  })
})