302 lines
7.8 KiB
TypeScript
302 lines
7.8 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import {
|
|
ToolCallParseState,
|
|
getInitialTooLCallParseState,
|
|
handleToolCallBuffer,
|
|
} from "./parseSystemToolCall";
|
|
|
|
describe("getInitialTooLCallParseState", () => {
|
|
it("returns a properly initialized parse state", () => {
|
|
const state = getInitialTooLCallParseState();
|
|
|
|
expect(state).toEqual({
|
|
isOnArgBeginLine: false,
|
|
currentArgName: undefined,
|
|
currentArgLines: [],
|
|
currentLineIndex: 0,
|
|
processedArgNames: new Set(),
|
|
lineChunks: [],
|
|
done: false,
|
|
toolCallId: expect.any(String),
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("handleToolCallBuffer", () => {
|
|
let state: ToolCallParseState;
|
|
|
|
beforeEach(() => {
|
|
state = getInitialTooLCallParseState();
|
|
});
|
|
|
|
it("handles the ```tool\ntool_name the name", () => {
|
|
handleToolCallBuffer("```tool\ntool_name: my_name", state);
|
|
expect(state.currentLineIndex).toBe(1);
|
|
|
|
const result = handleToolCallBuffer("\n", state);
|
|
expect(result).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "my_name",
|
|
arguments: "",
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
expect(state.currentLineIndex).toBe(2);
|
|
});
|
|
|
|
it("handles the tool name line", () => {
|
|
// Line 0 (```tool) is skipped internally
|
|
state.currentLineIndex = 1;
|
|
|
|
const result = handleToolCallBuffer("TOOL_NAME: test_tool", state);
|
|
expect(result).toBeUndefined();
|
|
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "test_tool",
|
|
arguments: "",
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
expect(state.currentLineIndex).toBe(2);
|
|
});
|
|
|
|
it("handles case-insensitive tool name line", () => {
|
|
state.currentLineIndex = 1;
|
|
|
|
const result = handleToolCallBuffer("tool_name: test_tool", state);
|
|
expect(result).toBeUndefined();
|
|
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "test_tool",
|
|
arguments: "",
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
});
|
|
|
|
it("begins an argument correctly", () => {
|
|
state.currentLineIndex = 2;
|
|
|
|
const result = handleToolCallBuffer("BEGIN_ARG: test_arg", state);
|
|
expect(result).toBeUndefined();
|
|
expect(state.isOnArgBeginLine).toBe(true);
|
|
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: '{"test_arg":',
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
expect(state.currentArgName).toBe("test_arg");
|
|
expect(state.isOnArgBeginLine).toBe(false);
|
|
});
|
|
|
|
it("handles case-insensitive begin arg", () => {
|
|
state.currentLineIndex = 2;
|
|
|
|
const result = handleToolCallBuffer("begin_arg: test_arg", state);
|
|
expect(result).toBeUndefined();
|
|
expect(state.isOnArgBeginLine).toBe(true);
|
|
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: '{"test_arg":',
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
});
|
|
|
|
it("collects arg value lines", () => {
|
|
state.currentLineIndex = 3;
|
|
state.currentArgName = "test_arg";
|
|
|
|
handleToolCallBuffer("line 1", state);
|
|
const newLineResult1 = handleToolCallBuffer("\n", state);
|
|
expect(newLineResult1).toBeUndefined();
|
|
|
|
handleToolCallBuffer("line 2", state);
|
|
const newLineResult2 = handleToolCallBuffer("\n", state);
|
|
expect(newLineResult2).toBeUndefined();
|
|
|
|
expect(state.currentArgLines).toEqual(["line 1\n", "line 2\n"]);
|
|
});
|
|
|
|
it("ends an argument correctly with string value", () => {
|
|
state.currentLineIndex = 3;
|
|
state.currentArgName = "test_arg";
|
|
state.currentArgLines = ["string value"];
|
|
|
|
const result = handleToolCallBuffer("END_ARG", state);
|
|
expect(result).toBeUndefined();
|
|
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: '"string value"',
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
expect(state.currentArgName).toBeUndefined();
|
|
expect(state.processedArgNames.has("test_arg")).toBe(true);
|
|
});
|
|
|
|
it("handles case-insensitive end arg", () => {
|
|
state.currentLineIndex = 3;
|
|
state.currentArgName = "test_arg";
|
|
state.currentArgLines = ["string value"];
|
|
|
|
const result = handleToolCallBuffer("end_arg", state);
|
|
expect(result).toBeUndefined();
|
|
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: '"string value"',
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
});
|
|
|
|
it("attempts to parse JSON values", () => {
|
|
state.currentLineIndex = 3;
|
|
state.currentArgName = "test_arg";
|
|
state.currentArgLines = ["123"];
|
|
|
|
handleToolCallBuffer("END_ARG", state);
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: "123",
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
});
|
|
|
|
it("handles multiple arguments", () => {
|
|
// Setup first arg
|
|
state.currentLineIndex = 2;
|
|
state.isOnArgBeginLine = true;
|
|
state.lineChunks[2] = ["BEGIN_ARG: first_arg"];
|
|
|
|
handleToolCallBuffer("\n", state);
|
|
handleToolCallBuffer("value1", state);
|
|
handleToolCallBuffer("\n", state);
|
|
handleToolCallBuffer("END_ARG", state);
|
|
handleToolCallBuffer("\n", state);
|
|
|
|
expect(state.processedArgNames.has("first_arg")).toBe(true);
|
|
|
|
// Setup second arg
|
|
state.isOnArgBeginLine = true;
|
|
handleToolCallBuffer("BEGIN_ARG: second_arg", state);
|
|
handleToolCallBuffer("\n", state);
|
|
handleToolCallBuffer("value2", state);
|
|
handleToolCallBuffer("\n", state);
|
|
handleToolCallBuffer("END_ARG", state);
|
|
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: '"value2"',
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
expect(state.processedArgNames.has("second_arg")).toBe(true);
|
|
});
|
|
|
|
it("finalizes JSON object at end of tool call", () => {
|
|
state.currentLineIndex = 5;
|
|
state.processedArgNames.add("test_arg");
|
|
|
|
const result = handleToolCallBuffer("```", state);
|
|
|
|
expect(result).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: "}",
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
expect(state.done).toBe(true);
|
|
});
|
|
|
|
it("finalizes on newline after all args processed", () => {
|
|
state.currentLineIndex = 5;
|
|
state.processedArgNames.add("test_arg");
|
|
|
|
const result = handleToolCallBuffer("\n", state);
|
|
|
|
expect(result).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments: "}",
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
expect(state.done).toBe(true);
|
|
});
|
|
|
|
it("handles JSON array args", () => {
|
|
state.currentLineIndex = 3;
|
|
state.currentArgName = "test_arg";
|
|
state.currentArgLines = [
|
|
"[\n",
|
|
'"------- SEARCH\n',
|
|
" subtract(number) {\n",
|
|
" return this;\n",
|
|
" }\n",
|
|
"=======\n",
|
|
" subtract(number) {\n",
|
|
" this -= number\n",
|
|
" return this;\n",
|
|
" }\n",
|
|
'+++++++ REPLACE"\n',
|
|
"]\n",
|
|
];
|
|
|
|
handleToolCallBuffer("END_ARG", state);
|
|
const newLineResult = handleToolCallBuffer("\n", state);
|
|
|
|
expect(newLineResult).toEqual({
|
|
type: "function",
|
|
function: {
|
|
name: "",
|
|
arguments:
|
|
'["------- SEARCH\\n subtract(number) {\\n return this;\\n }\\n=======\\n subtract(number) {\\n this -= number\\n return this;\\n }\\n+++++++ REPLACE"]',
|
|
},
|
|
id: expect.any(String),
|
|
});
|
|
});
|
|
});
|