refactor example import

This commit is contained in:
Ramnique Singh 2025-11-21 11:42:57 +05:30
parent ea4159a94b
commit 97e47faca8
7 changed files with 46 additions and 78 deletions

View file

@ -10,8 +10,7 @@
},
"files": [
"dist",
"bin",
"examples"
"bin"
],
"bin": {
"rowboatx": "bin/app.js"

View file

@ -4,7 +4,6 @@ import { stdin as input, stdout as output } from "node:process";
import fs from "fs";
import { promises as fsp } from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { WorkDir, getModelConfig, updateModelConfig } from "./application/config/config.js";
import { RunEvent } from "./application/entities/run-events.js";
import { createInterface, Interface } from "node:readline/promises";
@ -12,12 +11,8 @@ import { ToolCallPart } from "./application/entities/message.js";
import { Agent } from "./application/entities/agent.js";
import { McpServerConfig, McpServerDefinition } from "./application/entities/mcp.js";
import { z } from "zod";
import { Flavor, ModelConfig } from "./application/entities/models.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PackageRoot = path.resolve(__dirname, "..");
const ExamplesDir = path.join(PackageRoot, "examples");
import { Flavor } from "./application/entities/models.js";
import { examples } from "./examples/index.js";
export async function updateState(agent: string, runId: string) {
const state = new AgentState(agent, runId);
@ -413,51 +408,14 @@ function renderCurrentModel(provider: string, flavor: string, model: string) {
console.log("");
}
const ExampleSchema = z.object({
id: z.string().min(1),
"post-install-instructions": z.string().optional(),
description: z.string().optional(),
entryAgent: z.string().optional(),
agents: z.array(Agent).min(1),
mcpServers: z.record(z.string(), McpServerDefinition).optional(),
}).refine(
(data) => !data.entryAgent || data.agents.some((agent) => agent.name === data.entryAgent),
{
message: "entryAgent must reference one of the defined agents",
path: ["entryAgent"],
},
);
async function readExampleFile(exampleName: string): Promise<string> {
const examplePath = path.join(ExamplesDir, `${exampleName}.json`);
try {
return await fsp.readFile(examplePath, "utf8");
} catch (error: any) {
if (error?.code === "ENOENT") {
const availableExamples = await listAvailableExamples();
const listMessage = availableExamples.length
? `Available examples: ${availableExamples.join(", ")}`
: "No packaged examples were found.";
throw new Error(`Unknown example '${exampleName}'. ${listMessage}`);
}
// Re-throw other errors (permission issues, etc.)
throw error;
}
}
async function listAvailableExamples(): Promise<string[]> {
try {
const entries = await fsp.readdir(ExamplesDir);
return entries
.filter((entry) => entry.endsWith(".json"))
.map((entry) => entry.replace(/\.json$/, ""))
.sort();
} catch {
return [];
}
return Object.keys(examples);
}
async function writeAgents(agents: z.infer<typeof Agent>[]) {
async function writeAgents(agents: z.infer<typeof Agent>[] | undefined) {
if (!agents) {
return;
}
await fsp.mkdir(path.join(WorkDir, "agents"), { recursive: true });
await Promise.all(
agents.map(async (agent) => {
@ -509,22 +467,17 @@ async function mergeMcpServers(servers: Record<string, z.infer<typeof McpServerD
}
export async function importExample(exampleName: string) {
const raw = await readExampleFile(exampleName);
const parsed = ExampleSchema.parse(JSON.parse(raw));
const entryAgentName = parsed.entryAgent ?? parsed.agents[0]?.name;
if (!entryAgentName) {
throw new Error(`Example '${exampleName}' does not define any agents to run.`);
}
const postInstallInstructions = parsed["post-install-instructions"];
await writeAgents(parsed.agents);
const example = examples[exampleName];
const postInstallInstructions = example.instructions;
await writeAgents(example.agents);
let serverMerge = { added: [] as string[], skipped: [] as string[] };
if (parsed.mcpServers) {
serverMerge = await mergeMcpServers(parsed.mcpServers);
if (example.mcpServers) {
serverMerge = await mergeMcpServers(example.mcpServers);
}
return {
id: parsed.id,
entryAgent: entryAgentName,
importedAgents: parsed.agents.map((agent) => agent.name),
id: example.id,
entryAgent: example.entryAgent,
importedAgents: example.agents?.map((agent) => agent.name) ?? [],
addedServers: serverMerge.added,
skippedServers: serverMerge.skipped,
postInstallInstructions,

View file

@ -12,19 +12,6 @@ let modelConfig: z.infer<typeof ModelConfig> | null = null;
const baseMcpConfig: z.infer<typeof McpServerConfig> = {
mcpServers: {
firecrawl: {
command: "npx",
args: ["-y", "supergateway", "--stdio", "npx -y firecrawl-mcp"],
env: {
FIRECRAWL_API_KEY: "fc-aaacee4bdd164100a4d83af85bef6fdc",
},
},
test: {
url: "http://localhost:3000",
headers: {
"Authorization": "Bearer test",
},
},
}
};

View file

@ -0,0 +1,12 @@
import z from "zod"
import { Agent } from "./agent.js"
import { McpServerDefinition } from "./mcp.js"
export const Example = z.object({
id: z.string(),
instructions: z.string().optional(),
description: z.string().optional(),
entryAgent: z.string().optional(),
agents: z.array(Agent).optional(),
mcpServers: z.record(z.string(), McpServerDefinition).optional(),
});

View file

@ -0,0 +1,8 @@
{
"id": "gemini3_svg_pelican",
"provider": "google",
"model": "gemini-3.0-pro",
"description": "Outputs a single valid SVG depicting a pelican riding a bicycle.",
"instructions": "You must output only a single, valid, self-contained SVG XML depicting a pelican riding a bicycle. Requirements: 1) Output must be ONLY raw SVG XML (no markdown fences, no explanations). 2) Use viewBox=\"0 0 512 512\" and set width/height to 512. 3) Include clear, recognizable pelican and bicycle using basic shapes/paths. 4) No external refs, images, scripts, or styles; use inline attributes only. 5) Keep IDs minimal; keep total file size reasonable.",
"tools": {}
}

View file

@ -0,0 +1,9 @@
import twitterPodcast from './twitter-podcast.json' with { type: 'json' };
import gemini3Test from './gemini3-test.json' with { type: 'json' };
import { Example } from '../application/entities/example.js';
import z from 'zod';
export const examples: Record<string, z.infer<typeof Example>> = {
"twitter-podcast": Example.parse(twitterPodcast),
"gemini3-test": Example.parse(gemini3Test),
};

View file

@ -1,6 +1,6 @@
{
"id": "twitter-podcast",
"post-import-instructions": "This example workflow generates a narrated podcast episode from recent AI-related tweets using multiple agents.",
"instructions": "This example workflow generates a narrated podcast episode from recent AI-related tweets using multiple agents.",
"description": "Generates a narrated podcast episode from recent AI-related tweets using multiple agents.",
"entryAgent": "tweet-podcast",
"agents": [