First version copilot:

- basic llm call that can perform CRUD actions over dummy workflow json files
This commit is contained in:
tusharmagar 2025-10-30 16:09:19 +08:00 committed by Ramnique Singh
parent 055dda35b9
commit 4310b1d45d
11 changed files with 327 additions and 4 deletions

View file

@ -0,0 +1,105 @@
import readline from "readline";
import { z } from "zod";
import { openai } from "@ai-sdk/openai";
import { generateObject } from "ai";
import { Workflow } from "../../application/entities/workflow.js";
import { listWorkflows, getWorkflow, upsertWorkflow, deleteWorkflow } from "./workflows/service.js";
const ChatCommand = z.object({
action: z.enum([
"help",
"list_workflows",
"get_workflow",
"create_workflow",
"update_workflow",
"delete_workflow",
"unknown",
]),
id: z.string().optional(),
updates: Workflow.partial().optional(),
});
type ChatCommandT = z.infer<typeof ChatCommand>;
const systemPrompt = `
You are a CLI assistant that converts the user's natural language into a JSON command for managing workflows.
Rules:
- Only output JSON matching the provided schema. No extra commentary.
- Choose the most appropriate action from: help, list_workflows, get_workflow, create_workflow, update_workflow, delete_workflow, unknown.
- For actions that need an id (get/update/delete/create), set "id" to the workflow identifier (e.g. "example_workflow").
- For create/update, include only provided fields in "updates". If not provided, omit.
- Workflow shape reminder: { name: string, description: string, steps: Step[] } where Step is either { type: "function", id: string } or { type: "agent", id: string }.
- If the request is ambiguous, set action to "unknown".
`;
async function interpret(input: string): Promise<ChatCommandT> {
const { object } = await generateObject({
model: openai("gpt-4.1"),
system: systemPrompt,
prompt: input,
schema: ChatCommand,
});
return object;
}
async function execute(cmd: ChatCommandT): Promise<unknown> {
switch (cmd.action) {
case "help":
return {
usage: [
"Examples:",
"- list workflows",
"- show workflow example_workflow",
"- create workflow demo with one step calling function get_date",
"- update workflow demo: add agent step default_assistant",
"- delete workflow demo",
],
};
case "list_workflows":
return { items: listWorkflows() };
case "get_workflow":
if (!cmd.id) return { error: "id required" };
return getWorkflow(cmd.id) ?? null;
case "create_workflow":
if (!cmd.id) return { error: "id required" };
return upsertWorkflow(cmd.id, { ...(cmd.updates ?? {}) });
case "update_workflow":
if (!cmd.id) return { error: "id required" };
return upsertWorkflow(cmd.id, { ...(cmd.updates ?? {}) });
case "delete_workflow":
if (!cmd.id) return { error: "id required" };
return { deleted: deleteWorkflow(cmd.id) };
case "unknown":
return { error: "Could not determine intent. Try again or ask for help." };
}
}
export async function startCopilot(): Promise<void> {
if (!process.env.OPENAI_API_KEY) {
console.error("OPENAI_API_KEY is not set. Please export it to use chat.");
process.exitCode = 1;
return;
}
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
console.log("Rowboat Copilot (type 'exit' to quit)");
const ask = () => rl.question("> ", async (line) => {
if (!line || line.trim().toLowerCase() === "exit") {
rl.close();
return;
}
try {
const cmd = await interpret(line);
console.log("\n=== Parsed Command ===\n" + JSON.stringify(cmd, null, 2));
const result = await execute(cmd);
console.log("\n=== Result ===\n" + JSON.stringify(result, null, 2) + "\n");
} catch (err) {
console.error("Error:", (err as Error).message);
}
ask();
});
ask();
}