feat(agent): add llm assistant chat with api tools (task 88)

OpenAI-compatible client with mock fallback, tool-calling loop, and /assistant UI.

Tools map to /api/v1/ endpoints per ADR 0006 direct-tools decision.
This commit is contained in:
ginnoir
2026-07-04 19:46:09 -05:00
parent 04ae809e07
commit 4a924a4107
19 changed files with 956 additions and 6 deletions
+94
View File
@@ -0,0 +1,94 @@
import type { ChatCompletionRequest, ChatCompletionResult, LlmClient } from "./types";
function lastUserText(messages: ChatCompletionRequest["messages"]): string {
for (let i = messages.length - 1; i >= 0; i -= 1) {
const message = messages[i];
if (message?.role === "user" && message.content) {
return message.content.toLowerCase();
}
}
return "";
}
function hadToolResults(messages: ChatCompletionRequest["messages"]): boolean {
return messages.some((message) => message.role === "tool");
}
export function createMockLlmClient(): LlmClient {
return {
async chatCompletion(request: ChatCompletionRequest): Promise<ChatCompletionResult> {
const userText = lastUserText(request.messages);
if (hadToolResults(request.messages)) {
return {
message: {
role: "assistant",
content: "Done — I updated your household data using the API.",
},
finishReason: "stop",
};
}
if (userText.includes("milk") && request.tools?.length) {
return {
message: {
role: "assistant",
content: null,
tool_calls: [
{
id: "mock_call_add_item",
type: "function",
function: {
name: "add_list_item",
arguments: JSON.stringify({
listType: "shopping",
text: "milk",
}),
},
},
],
},
finishReason: "tool_calls",
};
}
if (userText.includes("calendar") && request.tools?.length) {
const now = new Date();
const from = new Date(now);
from.setDate(from.getDate() - 1);
const to = new Date(now);
to.setDate(to.getDate() + 7);
return {
message: {
role: "assistant",
content: null,
tool_calls: [
{
id: "mock_call_list_events",
type: "function",
function: {
name: "list_events",
arguments: JSON.stringify({
from: from.toISOString(),
to: to.toISOString(),
}),
},
},
],
},
finishReason: "tool_calls",
};
}
return {
message: {
role: "assistant",
content:
"I'm the famapp assistant (mock provider). Ask me to add items or check the calendar.",
},
finishReason: "stop",
};
},
};
}