allow provider / model config

This commit is contained in:
Ramnique Singh 2025-11-14 09:13:28 +05:30
parent 62caa0c8b6
commit 6251c8f007
9 changed files with 140 additions and 12 deletions

View file

@ -1,5 +1,4 @@
import { streamText, ModelMessage, tool, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import * as readline from "readline/promises";
import { stdin as input, stdout as output } from "process";
import { z } from "zod";
@ -10,8 +9,9 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { getProvider } from "../lib/models.js";
import { DefaultModel } from "../config/config.js";
const model = openai("gpt-4.1");
const rl = readline.createInterface({ input, output });
// Base directory for file operations - dynamically use user's home directory
@ -57,8 +57,9 @@ export async function startCopilot() {
process.stdout.write("\nCopilot: ");
let currentStep = 0;
const provider = getProvider();
const result = streamText({
model: model,
model: provider(DefaultModel),
messages: messages,
system: `You are an intelligent workflow assistant helping users manage their workflows in ${BASE_DIR}.

View file

@ -1,13 +1,14 @@
import path from "path";
import fs from "fs";
import { McpServerConfig } from "../entities/mcp.js";
import { ModelConfig } from "../entities/models.js";
import { z } from "zod";
import { homedir } from "os";
// Resolve app root relative to compiled file location (dist/...)
export const WorkDir = path.join(homedir(), ".rowboat");
const baseMcpConfig = {
const baseMcpConfig: z.infer<typeof McpServerConfig> = {
mcpServers: {
firecrawl: {
command: "npx",
@ -23,7 +24,19 @@ const baseMcpConfig = {
},
},
}
}
};
const baseModelConfig: z.infer<typeof ModelConfig> = {
providers: {
openai: {
flavor: "openai",
},
},
defaults: {
provider: "openai",
model: "gpt-4.1",
}
};
function ensureMcpConfig() {
const configPath = path.join(WorkDir, "config", "mcp.json");
@ -32,6 +45,13 @@ function ensureMcpConfig() {
}
}
function ensureModelConfig() {
const configPath = path.join(WorkDir, "config", "models.json");
if (!fs.existsSync(configPath)) {
fs.writeFileSync(configPath, JSON.stringify(baseModelConfig, null, 2));
}
}
function ensureDirs() {
const ensure = (p: string) => { if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); };
ensure(WorkDir);
@ -39,6 +59,7 @@ function ensureDirs() {
ensure(path.join(WorkDir, "agents"));
ensure(path.join(WorkDir, "config"));
ensureMcpConfig();
ensureModelConfig();
}
ensureDirs();
@ -50,5 +71,16 @@ function loadMcpServerConfig(): z.infer<typeof McpServerConfig> {
return McpServerConfig.parse(JSON.parse(config));
}
function loadModelConfig(): z.infer<typeof ModelConfig> {
const configPath = path.join(WorkDir, "config", "models.json");
if (!fs.existsSync(configPath)) return baseModelConfig;
const config = fs.readFileSync(configPath, "utf8");
return ModelConfig.parse(JSON.parse(config));
}
const { mcpServers } = loadMcpServerConfig();
const { providers, defaults } = loadModelConfig();
export const McpServers = mcpServers;
export const Providers = providers;
export const DefaultModel = defaults.model;
export const DefaultProvider = defaults.provider;

View file

@ -27,7 +27,8 @@ export const AgentTool = z.discriminatedUnion("type", [
export const Agent = z.object({
name: z.string(),
model: z.string(),
provider: z.string().optional(),
model: z.string().optional(),
description: z.string(),
instructions: z.string(),
tools: z.record(z.string(), AgentTool).optional(),

View file

@ -0,0 +1,15 @@
import z from "zod";
export const Provider = z.object({
flavor: z.enum(["openai", "anthropic", "google"]),
apiKey: z.string().optional(),
baseURL: z.string().optional(),
});
export const ModelConfig = z.object({
providers: z.record(z.string(), Provider),
defaults: z.object({
provider: z.string(),
model: z.string(),
}),
});

View file

@ -1,14 +1,13 @@
import { Message, MessageList } from "../entities/message.js";
import { z } from "zod";
import { Step, StepInputT, StepOutputT } from "./step.js";
import { openai } from "@ai-sdk/openai";
import { google } from "@ai-sdk/google";
import { generateText, ModelMessage, stepCountIs, streamText, tool, Tool, ToolSet, jsonSchema } from "ai";
import { ModelMessage, stepCountIs, streamText, tool, Tool, ToolSet, jsonSchema } from "ai";
import { Agent, AgentTool } from "../entities/agent.js";
import { WorkDir } from "../config/config.js";
import { DefaultModel, WorkDir } from "../config/config.js";
import fs from "fs";
import path from "path";
import { loadWorkflow } from "./utils.js";
import { getProvider } from "./models.js";
const BashTool = tool({
description: "Run a command in the shell",
@ -157,9 +156,9 @@ export class AgentNode implements Step {
// console.log("\n\n\t>>>>\t\ttools", JSON.stringify(tools, null, 2));
const provider = getProvider(this.agent.provider);
const { fullStream } = streamText({
model: openai("gpt-4.1"),
// model: google("gemini-2.5-flash"),
model: provider(this.agent.model || DefaultModel),
messages: convertFromMessages(input),
system: this.agent.instructions,
stopWhen: stepCountIs(1),

View file

@ -0,0 +1,40 @@
import { createOpenAI, OpenAIProvider } from "@ai-sdk/openai";
import { createGoogleGenerativeAI, GoogleGenerativeAIProvider } from "@ai-sdk/google";
import { AnthropicProvider, createAnthropic } from "@ai-sdk/anthropic";
import { DefaultModel, DefaultProvider, Providers } from "../config/config.js";
const providerMap: Record<string, OpenAIProvider | GoogleGenerativeAIProvider | AnthropicProvider> = {};
export function getProvider(name: string = "") {
if (!name) {
name = DefaultProvider;
}
if (providerMap[name]) {
return providerMap[name];
}
const providerConfig = Providers[name];
if (!providerConfig) {
throw new Error(`Provider ${name} not found`);
}
switch (providerConfig.flavor) {
case "openai":
providerMap[name] = createOpenAI({
apiKey: providerConfig.apiKey,
baseURL: providerConfig.baseURL,
});
break;
case "anthropic":
providerMap[name] = createAnthropic({
apiKey: providerConfig.apiKey,
baseURL: providerConfig.baseURL,
});
break;
case "google":
providerMap[name] = createGoogleGenerativeAI({
apiKey: providerConfig.apiKey,
baseURL: providerConfig.baseURL,
});
break;
}
return providerMap[name];
}

5
apps/cli/src/test.ts Normal file
View file

@ -0,0 +1,5 @@
import { RunEvent } from "./application/entities/workflow-event.js";
const obj = {"type":"tool-invocation","stepId":"test_agent","toolName":"ask-human","input":{"question":"Do you want me to run the command `date` in the terminal to show todays date?"},"ts":"2025-11-11T06:31:20.103Z"};
console.log(RunEvent.parse(obj));