| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- import { NativeToolCallParser } from "../NativeToolCallParser"
- describe("NativeToolCallParser", () => {
- beforeEach(() => {
- NativeToolCallParser.clearAllStreamingToolCalls()
- NativeToolCallParser.clearRawChunkState()
- })
- describe("parseToolCall", () => {
- describe("read_file tool", () => {
- it("should handle line_ranges as tuples (new format)", () => {
- const toolCall = {
- id: "toolu_123",
- name: "read_file" as const,
- arguments: JSON.stringify({
- files: [
- {
- path: "src/core/task/Task.ts",
- line_ranges: [
- [1920, 1990],
- [2060, 2120],
- ],
- },
- ],
- }),
- }
- const result = NativeToolCallParser.parseToolCall(toolCall)
- expect(result).not.toBeNull()
- expect(result?.type).toBe("tool_use")
- if (result?.type === "tool_use") {
- expect(result.nativeArgs).toBeDefined()
- const nativeArgs = result.nativeArgs as {
- files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
- }
- expect(nativeArgs.files).toHaveLength(1)
- expect(nativeArgs.files[0].path).toBe("src/core/task/Task.ts")
- expect(nativeArgs.files[0].lineRanges).toEqual([
- { start: 1920, end: 1990 },
- { start: 2060, end: 2120 },
- ])
- }
- })
- it("should handle line_ranges as strings (legacy format)", () => {
- const toolCall = {
- id: "toolu_123",
- name: "read_file" as const,
- arguments: JSON.stringify({
- files: [
- {
- path: "src/core/task/Task.ts",
- line_ranges: ["1920-1990", "2060-2120"],
- },
- ],
- }),
- }
- const result = NativeToolCallParser.parseToolCall(toolCall)
- expect(result).not.toBeNull()
- expect(result?.type).toBe("tool_use")
- if (result?.type === "tool_use") {
- expect(result.nativeArgs).toBeDefined()
- const nativeArgs = result.nativeArgs as {
- files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
- }
- expect(nativeArgs.files).toHaveLength(1)
- expect(nativeArgs.files[0].path).toBe("src/core/task/Task.ts")
- expect(nativeArgs.files[0].lineRanges).toEqual([
- { start: 1920, end: 1990 },
- { start: 2060, end: 2120 },
- ])
- }
- })
- it("should handle files without line_ranges", () => {
- const toolCall = {
- id: "toolu_123",
- name: "read_file" as const,
- arguments: JSON.stringify({
- files: [
- {
- path: "src/utils.ts",
- },
- ],
- }),
- }
- const result = NativeToolCallParser.parseToolCall(toolCall)
- expect(result).not.toBeNull()
- expect(result?.type).toBe("tool_use")
- if (result?.type === "tool_use") {
- const nativeArgs = result.nativeArgs as {
- files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
- }
- expect(nativeArgs.files).toHaveLength(1)
- expect(nativeArgs.files[0].path).toBe("src/utils.ts")
- expect(nativeArgs.files[0].lineRanges).toBeUndefined()
- }
- })
- it("should handle multiple files with different line_ranges", () => {
- const toolCall = {
- id: "toolu_123",
- name: "read_file" as const,
- arguments: JSON.stringify({
- files: [
- {
- path: "file1.ts",
- line_ranges: ["1-50"],
- },
- {
- path: "file2.ts",
- line_ranges: ["100-150", "200-250"],
- },
- {
- path: "file3.ts",
- },
- ],
- }),
- }
- const result = NativeToolCallParser.parseToolCall(toolCall)
- expect(result).not.toBeNull()
- expect(result?.type).toBe("tool_use")
- if (result?.type === "tool_use") {
- const nativeArgs = result.nativeArgs as {
- files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
- }
- expect(nativeArgs.files).toHaveLength(3)
- expect(nativeArgs.files[0].lineRanges).toEqual([{ start: 1, end: 50 }])
- expect(nativeArgs.files[1].lineRanges).toEqual([
- { start: 100, end: 150 },
- { start: 200, end: 250 },
- ])
- expect(nativeArgs.files[2].lineRanges).toBeUndefined()
- }
- })
- it("should filter out invalid line_range strings", () => {
- const toolCall = {
- id: "toolu_123",
- name: "read_file" as const,
- arguments: JSON.stringify({
- files: [
- {
- path: "file.ts",
- line_ranges: ["1-50", "invalid", "100-200", "abc-def"],
- },
- ],
- }),
- }
- const result = NativeToolCallParser.parseToolCall(toolCall)
- expect(result).not.toBeNull()
- expect(result?.type).toBe("tool_use")
- if (result?.type === "tool_use") {
- const nativeArgs = result.nativeArgs as {
- files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
- }
- expect(nativeArgs.files[0].lineRanges).toEqual([
- { start: 1, end: 50 },
- { start: 100, end: 200 },
- ])
- }
- })
- })
- })
- describe("processStreamingChunk", () => {
- describe("read_file tool", () => {
- it("should convert line_ranges strings to lineRanges objects during streaming", () => {
- const id = "toolu_streaming_123"
- NativeToolCallParser.startStreamingToolCall(id, "read_file")
- // Simulate streaming chunks
- const fullArgs = JSON.stringify({
- files: [
- {
- path: "src/test.ts",
- line_ranges: ["10-20", "30-40"],
- },
- ],
- })
- // Process the complete args as a single chunk for simplicity
- const result = NativeToolCallParser.processStreamingChunk(id, fullArgs)
- expect(result).not.toBeNull()
- expect(result?.nativeArgs).toBeDefined()
- const nativeArgs = result?.nativeArgs as {
- files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
- }
- expect(nativeArgs.files).toHaveLength(1)
- expect(nativeArgs.files[0].lineRanges).toEqual([
- { start: 10, end: 20 },
- { start: 30, end: 40 },
- ])
- })
- })
- })
- describe("finalizeStreamingToolCall", () => {
- describe("read_file tool", () => {
- it("should convert line_ranges strings to lineRanges objects on finalize", () => {
- const id = "toolu_finalize_123"
- NativeToolCallParser.startStreamingToolCall(id, "read_file")
- // Add the complete arguments
- NativeToolCallParser.processStreamingChunk(
- id,
- JSON.stringify({
- files: [
- {
- path: "finalized.ts",
- line_ranges: ["500-600"],
- },
- ],
- }),
- )
- const result = NativeToolCallParser.finalizeStreamingToolCall(id)
- expect(result).not.toBeNull()
- expect(result?.type).toBe("tool_use")
- if (result?.type === "tool_use") {
- const nativeArgs = result.nativeArgs as {
- files: Array<{ path: string; lineRanges?: Array<{ start: number; end: number }> }>
- }
- expect(nativeArgs.files[0].path).toBe("finalized.ts")
- expect(nativeArgs.files[0].lineRanges).toEqual([{ start: 500, end: 600 }])
- }
- })
- })
- })
- })
|