mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-26 17:06:23 +02:00
allow provider / model config
This commit is contained in:
parent
62caa0c8b6
commit
6251c8f007
9 changed files with 140 additions and 12 deletions
34
apps/cli/package-lock.json
generated
34
apps/cli/package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.44",
|
||||
"@ai-sdk/google": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.53",
|
||||
"@modelcontextprotocol/sdk": "^1.20.2",
|
||||
|
|
@ -26,6 +27,39 @@
|
|||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/anthropic": {
|
||||
"version": "2.0.44",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.44.tgz",
|
||||
"integrity": "sha512-o8TfNXRzO/KZkBrcx+CL9LQsPhx7PHyqzUGjza3TJaF9WxfH1S5UQLAmEw8F7lQoHNLU0IX03WT8o8R/4JbUxQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "2.0.0",
|
||||
"@ai-sdk/provider-utils": "3.0.17"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "3.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz",
|
||||
"integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "2.0.0",
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"eventsource-parser": "^3.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/gateway": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.44",
|
||||
"@ai-sdk/google": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.53",
|
||||
"@modelcontextprotocol/sdk": "^1.20.2",
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
15
apps/cli/src/application/entities/models.ts
Normal file
15
apps/cli/src/application/entities/models.ts
Normal 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(),
|
||||
}),
|
||||
});
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
40
apps/cli/src/application/lib/models.ts
Normal file
40
apps/cli/src/application/lib/models.ts
Normal 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
5
apps/cli/src/test.ts
Normal 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 today’s date?"},"ts":"2025-11-11T06:31:20.103Z"};
|
||||
|
||||
console.log(RunEvent.parse(obj));
|
||||
Loading…
Add table
Add a link
Reference in a new issue