Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  tool-call-id.test.ts

  Sprache: JAVA
 

import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { describe, expect, it } from "vitest";
import { castAgentMessages } from "./test-helpers/agent-message-fixtures.js";
import {
  isValidCloudCodeAssistToolId,
  sanitizeToolCallId,
  sanitizeToolCallIdsForCloudCodeAssist,
} from "./tool-call-id.js";

const buildDuplicateIdCollisionInput = () =>
  castAgentMessages([
    {
      role: "assistant",
      content: [
        { type: "toolCall", id: "call_a|b", name: "read", arguments: {} },
        { type: "toolCall", id: "call_a:b", name: "read", arguments: {} },
      ],
    },
    {
      role: "toolResult",
      toolCallId: "call_a|b",
      toolName: "read",
      content: [{ type: "text", text: "one" }],
    },
    {
      role: "toolResult",
      toolCallId: "call_a:b",
      toolName: "read",
      content: [{ type: "text", text: "two" }],
    },
  ]);

const readToolCall = (id: string) => ({
  type: "toolCall" as const,
  id,
  name: "read",
  arguments: {},
});

const buildToolResult = (params: {
  toolCallId: string;
  text: string;
  toolName?: string;
  toolUseId?: string;
}) => ({
  role: "toolResult" as const,
  toolCallId: params.toolCallId,
  ...(params.toolUseId ? { toolUseId: params.toolUseId } : {}),
  toolName: params.toolName ?? "read",
  content: [{ type: "text" as const, text: params.text }],
});

const signedReadAssistant = (signature: string, id: string) => ({
  role: "assistant" as const,
  content: [
    { type: "thinking" as const, thinking: "internal", thinkingSignature: signature },
    readToolCall(id),
  ],
});

const buildRepeatedEditIdInput = (params: { includeToolUseId?: boolean } = {}) =>
  castAgentMessages([
    {
      role: "assistant",
      content: [
        { type: "toolCall", id: "edit:22", name: "edit", arguments: {} },
        { type: "toolCall", id: "edit:22", name: "edit", arguments: {} },
      ],
    },
    buildToolResult({
      toolCallId: "edit:22",
      toolName: "edit",
      ...(params.includeToolUseId ? { toolUseId: "edit:22" } : {}),
      text: "one",
    }),
    buildToolResult({
      toolCallId: "edit:22",
      toolName: "edit",
      ...(params.includeToolUseId ? { toolUseId: "edit:22" } : {}),
      text: "two",
    }),
  ]);

const buildRepeatedRawIdInput = () => buildRepeatedEditIdInput();

const buildRepeatedSharedToolResultIdInput = () =>
  buildRepeatedEditIdInput({ includeToolUseId: true });

function expectCollisionIdsRemainDistinct(
  out: AgentMessage[],
  mode: "strict" | "strict9",
): { aId: string; bId: string } {
  const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
  const a = assistant.content?.[0] as { id?: string };
  const b = assistant.content?.[1] as { id?: string };
  expect(typeof a.id).toBe("string");
  expect(typeof b.id).toBe("string");
  expect(a.id).not.toBe(b.id);
  expect(isValidCloudCodeAssistToolId(a.id as string, mode)).toBe(true);
  expect(isValidCloudCodeAssistToolId(b.id as string, mode)).toBe(true);

  const r1 = out[1] as Extract<AgentMessage, { role: "toolResult" }>;
  const r2 = out[2] as Extract<AgentMessage, { role: "toolResult" }>;
  expect(r1.toolCallId).toBe(a.id);
  expect(r2.toolCallId).toBe(b.id);
  return { aId: a.id as string, bId: b.id as string };
}

function expectSingleToolCallRewrite(
  out: AgentMessage[],
  expectedId: string,
  mode: "strict" | "strict9",
): void {
  const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
  const toolCall = assistant.content?.[0] as { id?: string };
  expect(toolCall.id).toBe(expectedId);
  expect(isValidCloudCodeAssistToolId(toolCall.id as string, mode)).toBe(true);

  const result = out[1] as Extract<AgentMessage, { role: "toolResult" }>;
  expect(result.toolCallId).toBe(toolCall.id);
}

function expectToolUseIdsFollowDistinctToolCallIds(
  out: AgentMessage[],
  mode: "strict" | "strict9",
): { aId: string; bId: string } {
  const ids = expectCollisionIdsRemainDistinct(out, mode);
  const r1 = out[1] as Extract<AgentMessage, { role: "toolResult" }> & { toolUseId?: string };
  const r2 = out[2] as Extract<AgentMessage, { role: "toolResult" }> & { toolUseId?: string };
  expect(r1.toolUseId).toBe(ids.aId);
  expect(r2.toolUseId).toBe(ids.bId);
  return ids;
}

function expectStrict9IdLengths(ids: { aId: string; bId: string }) {
  expect(ids.aId.length).toBe(9);
  expect(ids.bId.length).toBe(9);
}

function expectDistinctStrict9Ids(out: AgentMessage[], input: AgentMessage[]) {
  expect(out).not.toBe(input);
  const ids = expectCollisionIdsRemainDistinct(out, "strict9");
  expectStrict9IdLengths(ids);
}

function expectReplaySafeSignedTurnOwnership(params: {
  input: AgentMessage[];
  preservedTurn: "first" | "second";
  firstToolCallIndex: number;
}) {
  const out = sanitizeToolCallIdsForCloudCodeAssist(params.input, "strict", {
    preserveReplaySafeThinkingToolCallIds: true,
    allowedToolNames: ["read"],
  });

  expect(out).not.toBe(params.input);
  const firstAssistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
  const secondAssistant = out[2] as Extract<AgentMessage, { role: "assistant" }>;
  const firstToolCall = firstAssistant.content?.[params.firstToolCallIndex] as { id?: string };
  const secondToolCall = secondAssistant.content?.[1] as { id?: string };

  if (params.preservedTurn === "first") {
    expect(firstToolCall.id).toBe("call1");
    expect(secondToolCall.id).not.toBe("call1");
    expect((out[1] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe("call1");
    expect((out[3] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe(
      secondToolCall.id,
    );
  } else {
    expect(firstToolCall.id).not.toBe("call1");
    expect(secondToolCall.id).toBe("call1");
    expect((out[1] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe(
      firstToolCall.id,
    );
    expect((out[3] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe("call1");
  }

  expect(firstToolCall.id).not.toBe(secondToolCall.id);
}

describe("sanitizeToolCallIdsForCloudCodeAssist", () => {
  describe("strict mode (default)", () => {
    it("is a no-op for already-valid non-colliding IDs", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [{ type: "toolCall", id: "call1", name: "read", arguments: {} }],
        },
        {
          role: "toolResult",
          toolCallId: "call1",
          toolName: "read",
          content: [{ type: "text", text: "ok" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input);
      expect(out).toBe(input);
    });

    it("strips non-alphanumeric characters from tool call IDs", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [{ type: "toolCall", id: "call|item:123", name: "read", arguments: {} }],
        },
        {
          role: "toolResult",
          toolCallId: "call|item:123",
          toolName: "read",
          content: [{ type: "text", text: "ok" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input);
      expect(out).not.toBe(input);
      // Strict mode strips all non-alphanumeric characters
      expectSingleToolCallRewrite(out, "callitem123""strict");
    });

    it("avoids collisions when sanitization would produce duplicate IDs", () => {
      const input = buildDuplicateIdCollisionInput();

      const out = sanitizeToolCallIdsForCloudCodeAssist(input);
      expect(out).not.toBe(input);
      expectCollisionIdsRemainDistinct(out, "strict");
    });

    it("reuses one rewritten id when a tool result carries matching toolCallId and toolUseId", () => {
      const input = buildRepeatedSharedToolResultIdInput();

      const out = sanitizeToolCallIdsForCloudCodeAssist(input);
      expect(out).not.toBe(input);
      expectToolUseIdsFollowDistinctToolCallIds(out, "strict");
    });

    it("assigns distinct IDs when identical raw tool call ids repeat", () => {
      const input = buildRepeatedRawIdInput();

      const out = sanitizeToolCallIdsForCloudCodeAssist(input);
      expect(out).not.toBe(input);
      expectCollisionIdsRemainDistinct(out, "strict");
    });

    it("caps tool call IDs at 40 chars while preserving uniqueness", () => {
      const longA = `call_${"a".repeat(60)}`;
      const longB = `call_${"a".repeat(59)}b`;
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [
            { type: "toolCall", id: longA, name: "read", arguments: {} },
            { type: "toolCall", id: longB, name: "read", arguments: {} },
          ],
        },
        {
          role: "toolResult",
          toolCallId: longA,
          toolName: "read",
          content: [{ type: "text", text: "one" }],
        },
        {
          role: "toolResult",
          toolCallId: longB,
          toolName: "read",
          content: [{ type: "text", text: "two" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input);
      const { aId, bId } = expectCollisionIdsRemainDistinct(out, "strict");
      expect(aId.length).toBeLessThanOrEqual(40);
      expect(bId.length).toBeLessThanOrEqual(40);
    });
  });

  describe("strict mode (alphanumeric only)", () => {
    it("strips underscores and hyphens from tool call IDs", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [
            {
              type: "toolCall",
              id: "plugin_login_1768799841527_1",
              name: "login",
              arguments: {},
            },
          ],
        },
        {
          role: "toolResult",
          toolCallId: "plugin_login_1768799841527_1",
          toolName: "login",
          content: [{ type: "text", text: "ok" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict");
      expect(out).not.toBe(input);
      // Strict mode strips all non-alphanumeric characters
      expectSingleToolCallRewrite(out, "pluginlogin17687998415271""strict");
    });

    it("preserves native anthropic ids while sanitizing mixed-provider ids when requested", () => {
      const nativeId = "toolu_01ABCDEF1234567890";
      const nonNativeId = "call_123|fc_123";
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [
            { type: "toolUse", id: nativeId, name: "read", input: { path: "IDENTITY.md" } },
            { type: "toolUse", id: nonNativeId, name: "read", input: { path: "README.md" } },
          ],
        },
        {
          role: "toolResult",
          toolCallId: nativeId,
          toolUseId: nativeId,
          toolName: "read",
          content: [{ type: "text", text: "identity" }],
        },
        {
          role: "toolResult",
          toolCallId: nonNativeId,
          toolUseId: nonNativeId,
          toolName: "read",
          content: [{ type: "text", text: "readme" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict", {
        preserveNativeAnthropicToolUseIds: true,
      });

      expect(out).not.toBe(input);
      expect((out[0] as Extract<AgentMessage, { role: "assistant" }>).content).toEqual([
        { type: "toolUse", id: nativeId, name: "read", input: { path: "IDENTITY.md" } },
        { type: "toolUse", id: "call123fc123", name: "read", input: { path: "README.md" } },
      ]);
      expect(
        (out[1] as Extract<AgentMessage, { role: "toolResult" }> & { toolUseId?: string })
          .toolCallId,
      ).toBe(nativeId);
      expect(
        (out[1] as Extract<AgentMessage, { role: "toolResult" }> & { toolUseId?: string })
          .toolUseId,
      ).toBe(nativeId);
      expect(
        (out[2] as Extract<AgentMessage, { role: "toolResult" }> & { toolUseId?: string })
          .toolCallId,
      ).toBe("call123fc123");
      expect(
        (out[2] as Extract<AgentMessage, { role: "toolResult" }> & { toolUseId?: string })
          .toolUseId,
      ).toBe("call123fc123");
    });

    it("preserves replay-safe signed-thinking tool ids when requested", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [
            { type: "thinking", thinking: "internal", thinkingSignature: "sig_1" },
            { type: "toolCall", id: "call_1", name: "read", arguments: {} },
          ],
        },
        {
          role: "toolResult",
          toolCallId: "call_1",
          toolName: "read",
          content: [{ type: "text", text: "ok" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict", {
        preserveReplaySafeThinkingToolCallIds: true,
        allowedToolNames: ["read"],
      });

      expect(out).toBe(input);
      expect(
        ((out[0] as Extract<AgentMessage, { role: "assistant" }>).content?.[1] as { id?: string })
          .id,
      ).toBe("call_1");
      expect((out[1] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe("call_1");
    });

    it("rewrites earlier mutable ids away from later preserved signed ids", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [readToolCall("call_1")],
        },
        buildToolResult({ toolCallId: "call_1", text: "first" }),
        signedReadAssistant("sig_1""call1"),
        buildToolResult({ toolCallId: "call1", text: "second" }),
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict", {
        preserveReplaySafeThinkingToolCallIds: true,
        allowedToolNames: ["read"],
      });

      expect(out).not.toBe(input);
      const firstAssistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
      const firstToolCall = firstAssistant.content?.[0] as { id?: string };
      expect(firstToolCall.id).not.toBe("call1");

      expectReplaySafeSignedTurnOwnership({
        input,
        preservedTurn: "second",
        firstToolCallIndex: 0,
      });
    });

    it("rewrites later signed turns when an earlier signed turn already owns the raw id", () => {
      const input = castAgentMessages([
        signedReadAssistant("sig_1""call1"),
        buildToolResult({ toolCallId: "call1", text: "first" }),
        signedReadAssistant("sig_2""call1"),
        buildToolResult({ toolCallId: "call1", text: "second" }),
      ]);

      expectReplaySafeSignedTurnOwnership({
        input,
        preservedTurn: "first",
        firstToolCallIndex: 1,
      });
    });

    it("avoids collisions with alphanumeric-only suffixes", () => {
      const input = buildDuplicateIdCollisionInput();

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict");
      expect(out).not.toBe(input);
      const { aId, bId } = expectCollisionIdsRemainDistinct(out, "strict");
      // Should not contain underscores or hyphens
      expect(aId).not.toMatch(/[_-]/);
      expect(bId).not.toMatch(/[_-]/);
    });

    it("assigns distinct strict IDs when identical raw tool call ids repeat", () => {
      const input = buildRepeatedRawIdInput();

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict");
      expect(out).not.toBe(input);
      const { aId, bId } = expectCollisionIdsRemainDistinct(out, "strict");
      expect(aId).not.toMatch(/[_-]/);
      expect(bId).not.toMatch(/[_-]/);
    });

    it("preserves native Kimi function ids in direct strict sanitization", () => {
      expect(sanitizeToolCallId("functions.read:0""strict")).toBe("functions.read:0");
      expect(sanitizeToolCallId("functions.bash_tool:12""strict")).toBe("functions.bash_tool:12");
      expect(sanitizeToolCallId("functions.edit-file:3""strict")).toBe("functions.edit-file:3");
      expect(isValidCloudCodeAssistToolId("functions.read:0""strict")).toBe(true);
      expect(isValidCloudCodeAssistToolId("functions.read:0""strict9")).toBe(false);
    });

    it("preserves native Kimi function ids across assistant/toolResult pairs", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [{ type: "toolCall", id: "functions.read:0", name: "read", arguments: {} }],
        },
        {
          role: "toolResult",
          toolCallId: "functions.read:0",
          toolName: "read",
          content: [{ type: "text", text: "ok" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict");
      expect(out).toBe(input);
    });

    it("preserves native Kimi ids while sanitizing non-Kimi siblings", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [
            { type: "toolCall", id: "functions.read:0", name: "read", arguments: {} },
            { type: "toolCall", id: "call_a|b", name: "read", arguments: {} },
          ],
        },
        buildToolResult({ toolCallId: "functions.read:0", text: "native" }),
        buildToolResult({ toolCallId: "call_a|b", text: "sanitized" }),
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict");
      expect(out).not.toBe(input);
      const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
      const native = assistant.content?.[0] as { id?: string };
      const sibling = assistant.content?.[1] as { id?: string };
      expect(native.id).toBe("functions.read:0");
      expect(sibling.id).toBe("callab");
      expect((out[1] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe(
        "functions.read:0",
      );
      expect((out[2] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe("callab");
    });

    it("disambiguates repeated native Kimi ids after preserving the first occurrence", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [{ type: "toolCall", id: "functions.read:0", name: "read", arguments: {} }],
        },
        buildToolResult({ toolCallId: "functions.read:0", text: "one" }),
        {
          role: "assistant",
          content: [{ type: "toolCall", id: "functions.read:0", name: "read", arguments: {} }],
        },
        buildToolResult({ toolCallId: "functions.read:0", text: "two" }),
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict");
      expect(out).not.toBe(input);
      const first = (out[0] as Extract<AgentMessage, { role: "assistant" }>).content?.[0] as {
        id?: string;
      };
      const second = (out[2] as Extract<AgentMessage, { role: "assistant" }>).content?.[0] as {
        id?: string;
      };
      expect(first.id).toBe("functions.read:0");
      expect(second.id).not.toBe("functions.read:0");
      expect(isValidCloudCodeAssistToolId(second.id as string, "strict")).toBe(true);
      expect((out[1] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe(
        "functions.read:0",
      );
      expect((out[3] as Extract<AgentMessage, { role: "toolResult" }>).toolCallId).toBe(second.id);
    });

    it("does not preserve malformed Kimi-like ids", () => {
      for (const bad of [
        "functions.read",
        "functions.:0",
        "functions.read:",
        "functions.read:x",
        "functions.read:0:extra",
        "xfunctions.read:0",
      ]) {
        expect(sanitizeToolCallId(bad, "strict")).not.toBe(bad);
        expect(isValidCloudCodeAssistToolId(bad, "strict")).toBe(false);
      }
    });
  });

  describe("strict9 mode (Mistral tool call IDs)", () => {
    it("is a no-op for already-valid 9-char alphanumeric IDs", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [{ type: "toolCall", id: "abc123XYZ", name: "read", arguments: {} }],
        },
        {
          role: "toolResult",
          toolCallId: "abc123XYZ",
          toolName: "read",
          content: [{ type: "text", text: "ok" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict9");
      expect(out).toBe(input);
    });

    it("enforces alphanumeric IDs with length 9", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [
            { type: "toolCall", id: "call_abc|item:123", name: "read", arguments: {} },
            { type: "toolCall", id: "call_abc|item:456", name: "read", arguments: {} },
          ],
        },
        {
          role: "toolResult",
          toolCallId: "call_abc|item:123",
          toolName: "read",
          content: [{ type: "text", text: "one" }],
        },
        {
          role: "toolResult",
          toolCallId: "call_abc|item:456",
          toolName: "read",
          content: [{ type: "text", text: "two" }],
        },
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict9");
      expectDistinctStrict9Ids(out, input);
    });

    it("assigns distinct strict9 IDs when identical raw tool call ids repeat", () => {
      const input = buildRepeatedRawIdInput();

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict9");
      expectDistinctStrict9Ids(out, input);
    });

    it("reuses one rewritten strict9 id when a tool result carries matching toolCallId and toolUseId", () => {
      const input = buildRepeatedSharedToolResultIdInput();

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict9");
      expect(out).not.toBe(input);
      expectStrict9IdLengths(expectToolUseIdsFollowDistinctToolCallIds(out, "strict9"));
    });

    it("rewrites native Kimi function ids in strict9 mode", () => {
      const input = castAgentMessages([
        {
          role: "assistant",
          content: [{ type: "toolCall", id: "functions.read:0", name: "read", arguments: {} }],
        },
        buildToolResult({ toolCallId: "functions.read:0", text: "ok" }),
      ]);

      const out = sanitizeToolCallIdsForCloudCodeAssist(input, "strict9");
      expect(out).not.toBe(input);
      expectSingleToolCallRewrite(out, "functions""strict9");
    });
  });
});

Messung V0.5 in Prozent
C=99 H=100 G=99

¤ Dauer der Verarbeitung: 0.10 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge