Files
growqr-backend/src/actors/conversation/agent.ts

77 lines
2.4 KiB
TypeScript

import { createOpenAI } from "@ai-sdk/openai";
import { streamText, tool } from "ai";
import { z } from "zod";
import type { ConversationMessage } from "./types.js";
import { config } from "../../config.js";
const SYSTEM_PROMPT = `You are the GrowQR conversation agent.
Keep answers concise, practical, and focused on the user's goals.
When you learn durable information, call the memory tools. For now these tools
are intentionally stubbed so this actor can stay isolated and unwired.`;
function normalizeModel(model: string): string {
if (config.llmProvider === "opencode" && model.startsWith("opencode/")) {
return model.slice("opencode/".length);
}
return model;
}
const conversationProvider = createOpenAI({
apiKey: config.llmApiKey,
baseURL: config.llmBaseUrl.replace(/\/$/, ""),
name: config.llmProvider,
});
export function getConversationModel() {
const modelId = process.env.CONVERSATION_ACTOR_MODEL ?? config.agentModel;
if (!config.llmApiKey) {
throw new Error("Missing LLM_API_KEY/OPENCODE_API_KEY for conversation streaming.");
}
return conversationProvider.chat(normalizeModel(modelId));
}
export function buildModelMessages(messages: ConversationMessage[]) {
return messages.map((message) => ({
role: message.role,
content: message.content,
}));
}
export function streamConversationResponse(messages: ConversationMessage[]) {
return streamText({
model: getConversationModel(),
system: SYSTEM_PROMPT,
messages: buildModelMessages(messages),
tools: {
readMemory: tool({
description: "Read a markdown memory file. Stubbed until memoryActor is wired.",
inputSchema: z.object({
path: z.string().describe("Memory path, e.g. /profile.md"),
}),
execute: async ({ path }) => ({
path,
found: false,
content: "",
note: "memoryActor is not wired yet",
}),
}),
writeMemory: tool({
description: "Write a markdown memory file. Stubbed until memoryActor is wired.",
inputSchema: z.object({
path: z.string(),
contentMd: z.string(),
reason: z.string().optional(),
}),
execute: async ({ path, contentMd, reason }) => ({
path,
bytes: contentMd.length,
reason,
saved: false,
note: "memoryActor is not wired yet",
}),
}),
},
});
}