openpetswithchatandmcp/apps/desktop/tests/openapi-chat-tool-loop.test.ts
2026-06-19 05:34:35 +00:00

154 lines
4.9 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
buildOpenApiInstructionsWithTools,
runToolEnabledOpenApiChatLoop,
} from "../src/openapi-chat-tool-loop.js";
const desktopRoot = process.env.FAMILIAROS_DESKTOP_ROOT ?? resolve(dirname(fileURLToPath(import.meta.url)), "..");
const openApiChatSource = readFileSync(resolve(desktopRoot, "src/openapi-chat.ts"), "utf8");
const openApiChatPromptFlowsSource = readFileSync(resolve(desktopRoot, "src/openapi-chat-prompt-flows.ts"), "utf8");
const openApiChatToolLoopSource = readFileSync(resolve(desktopRoot, "src/openapi-chat-tool-loop.ts"), "utf8");
assert.match(openApiChatSource, /from "\.\/openapi-chat-prompt-flows(?:\.js)?"/, "openapi-chat must import the extracted prompt-flow seam.");
assert.match(openApiChatPromptFlowsSource, /from "\.\/openapi-chat-tool-loop(?:\.js)?"/, "openapi-chat prompt flows must import the extracted tool loop seam.");
assert.match(openApiChatToolLoopSource, /export async function runToolEnabledOpenApiChatLoop/, "openapi-chat tool loop seam must export the async tool runner.");
assert.match(openApiChatToolLoopSource, /export function buildOpenApiInstructionsWithTools/, "openapi-chat tool loop seam must export tool-aware instruction shaping.");
assert.equal(
buildOpenApiInstructionsWithTools("Base instructions", [
{
type: "function",
function: {
name: "memory.save",
description: "Persist a note",
parameters: { type: "object" },
},
},
]),
"Base instructions\n\nYou have access to the following tools: memory.save. When you need to use a tool, respond with a tool call. The system will execute it and return the result. Do not make up tool results.",
"tool-aware instructions must append the available tool names and execution rule.",
);
const requestBodies: Array<Record<string, unknown>> = [];
const toolCalls: Array<{ name: string; args: unknown }> = [];
const responses = [
new Response(JSON.stringify({
id: "chatcmpl_tool",
choices: [
{
message: {
content: "Checking memory",
tool_calls: [
{
id: "tool_1",
type: "function",
function: {
name: "memory.save",
arguments: "{\"note\":\"Remember the milk\"}",
},
},
],
},
},
],
})),
new Response(JSON.stringify({
id: "chatcmpl_final",
choices: [
{
message: {
content: "Saved it for later.",
},
},
],
})),
];
const result = await runToolEnabledOpenApiChatLoop({
endpoint: "https://provider.example/v1/responses",
credential: "sk-test",
model: "gpt-test",
prompt: "Remember the milk",
systemContent: "You are FamiliarOS.",
history: [],
maxHistoryEntries: 24,
maxToolCallIterations: 5,
maxResponseOutputTokens: 256,
tools: [
{
type: "function",
function: {
name: "memory.save",
description: "Persist a note",
parameters: { type: "object" },
},
},
],
manager: {
async callTool(name: string, args: unknown) {
toolCalls.push({ name, args });
return "stored";
},
},
async fetchImpl(_input, init) {
assert.ok(init?.body, "tool loop must send a request body.");
requestBodies.push(JSON.parse(String(init.body)) as Record<string, unknown>);
const next = responses.shift();
assert.ok(next, "tool loop test ran out of stubbed responses.");
return next;
},
});
assert.equal(result.finalText, "Saved it for later.");
assert.equal(result.responseId, "chatcmpl_final");
assert.equal(result.iterationCount, 2);
assert.equal(result.endpoint, "https://provider.example/v1/chat/completions");
assert.equal(toolCalls.length, 1);
assert.deepEqual(toolCalls[0], {
name: "memory.save",
args: { note: "Remember the milk" },
});
assert.equal(requestBodies.length, 2);
assert.equal(requestBodies[0].tool_choice, "auto");
assert.equal(
(requestBodies[0].tools as Array<{ function: { name: string } }>)[0]?.function.name,
"memory.save",
"tool loop must forward the OpenAI tool definition payload.",
);
assert.deepEqual(
result.history,
[
{ role: "system", content: "You are FamiliarOS." },
{ role: "user", content: "Remember the milk" },
{
role: "assistant",
content: "Checking memory",
tool_calls: [
{
id: "tool_1",
type: "function",
function: {
name: "memory.save",
arguments: "{\"note\":\"Remember the milk\"}",
},
},
],
},
{
role: "tool",
tool_call_id: "tool_1",
content: "stored",
},
{
role: "assistant",
content: "Saved it for later.",
},
],
"tool loop must preserve the system/user/tool-call/tool-result/final-assistant history sequence.",
);
console.error("OpenAPI chat tool loop validation passed.");