diff --git a/.changeset/openai-models.md b/.changeset/openai-models.md new file mode 100644 index 0000000..3133fdb --- /dev/null +++ b/.changeset/openai-models.md @@ -0,0 +1,6 @@ +--- +'mycoder': minor +'mycoder-agent': minor +--- + +Add support for OpenAI models (o3 mini and GPT-4o) via Vercel AI SDK diff --git a/packages/agent/src/core/toolAgent/config.ts b/packages/agent/src/core/toolAgent/config.ts index 596d878..6bb7785 100644 --- a/packages/agent/src/core/toolAgent/config.ts +++ b/packages/agent/src/core/toolAgent/config.ts @@ -1,6 +1,34 @@ import { execSync } from 'child_process'; import { anthropic } from '@ai-sdk/anthropic'; +import { openai } from '@ai-sdk/openai'; + +/** + * Available model providers + */ +export type ModelProvider = 'anthropic' | 'openai'; + +/** + * Available models by provider + */ +export const AVAILABLE_MODELS = { + anthropic: ['claude-3-7-sonnet-20250219', 'claude-3-opus-20240229'], + openai: ['gpt-4o-2024-05-13', 'o3-mini-2024-07-18'], +}; + +/** + * Get the model instance based on provider and model name + */ +export function getModel(provider: ModelProvider, modelName: string) { + switch (provider) { + case 'anthropic': + return anthropic(modelName); + case 'openai': + return openai(modelName); + default: + throw new Error(`Unknown model provider: ${provider}`); + } +} /** * Default configuration for the tool agent diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 8447faa..28ee7e0 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -26,6 +26,7 @@ export * from './tools/interaction/userPrompt.js'; export * from './core/executeToolCall.js'; export * from './core/types.js'; export * from './core/toolAgent.js'; +export * from './core/toolAgent/config.js'; // Utils export * from './tools/getTools.js'; diff --git a/packages/agent/src/tools/interaction/subAgent.ts b/packages/agent/src/tools/interaction/subAgent.ts index 8d8de08..d808e0f 100644 --- a/packages/agent/src/tools/interaction/subAgent.ts +++ b/packages/agent/src/tools/interaction/subAgent.ts @@ -1,7 +1,7 @@ -import { anthropic } from '@ai-sdk/anthropic'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; +import { getModel } from '../../core/toolAgent/config.js'; import { toolAgent } from '../../core/toolAgent.js'; import { Tool } from '../../core/types.js'; import { getTools } from '../getTools.js'; @@ -50,7 +50,7 @@ type ReturnType = z.infer; // Sub-agent specific configuration const subAgentConfig = { maxIterations: 50, - model: anthropic('claude-3-7-sonnet-20250219'), + model: getModel('anthropic', 'claude-3-7-sonnet-20250219'), maxTokens: 4096, temperature: 0.7, getSystemPrompt: () => { diff --git a/packages/cli/src/commands/$default.ts b/packages/cli/src/commands/$default.ts index 8b06b2d..1113ace 100644 --- a/packages/cli/src/commands/$default.ts +++ b/packages/cli/src/commands/$default.ts @@ -11,11 +11,15 @@ import { LogLevel, subAgentTool, errorToString, + getModel, + AVAILABLE_MODELS, + DEFAULT_CONFIG, } from 'mycoder-agent'; import { TokenTracker } from 'mycoder-agent/dist/core/tokens.js'; import { SharedOptions } from '../options.js'; import { initSentry, captureException } from '../sentry/index.js'; +import { getConfig } from '../settings/config.js'; import { hasUserConsented, saveUserConsent } from '../settings/settings.js'; import { nameToLogIndex } from '../utils/nameToLogIndex.js'; import { checkForUpdates, getPackageInfo } from '../utils/versionCheck.js'; @@ -86,10 +90,33 @@ export const command: CommandModule = { ); try { - // Early API key check - if (!process.env.ANTHROPIC_API_KEY) { + // Get configuration for model provider and name + const userConfig = getConfig(); + const userModelProvider = argv.modelProvider || userConfig.modelProvider; + const userModelName = argv.modelName || userConfig.modelName; + + // Early API key check based on model provider + if (userModelProvider === 'anthropic' && !process.env.ANTHROPIC_API_KEY) { logger.error(getAnthropicApiKeyError()); throw new Error('Anthropic API key not found'); + } else if ( + userModelProvider === 'openai' && + !process.env.OPENAI_API_KEY + ) { + logger.error( + 'No OpenAI API key found. Please set the OPENAI_API_KEY environment variable.', + 'You can get an API key from https://platform.openai.com/api-keys', + ); + throw new Error('OpenAI API key not found'); + } + + // Validate model name + if (!AVAILABLE_MODELS[userModelProvider].includes(userModelName)) { + logger.error( + `Invalid model name: ${userModelName} for provider ${userModelProvider}`, + `Available models for ${userModelProvider}: ${AVAILABLE_MODELS[userModelProvider].join(', ')}`, + ); + throw new Error(`Invalid model name: ${userModelName}`); } let prompt: string | undefined; @@ -134,7 +161,16 @@ export const command: CommandModule = { process.exit(0); }); - const result = await toolAgent(prompt, tools, undefined, { + // Create a config with the selected model + const agentConfig = { + ...DEFAULT_CONFIG, + model: getModel( + userModelProvider as 'anthropic' | 'openai', + userModelName, + ), + }; + + const result = await toolAgent(prompt, tools, agentConfig, { logger, headless: argv.headless ?? true, userSession: argv.userSession ?? false, diff --git a/packages/cli/src/options.ts b/packages/cli/src/options.ts index 7d802a8..b5a12a5 100644 --- a/packages/cli/src/options.ts +++ b/packages/cli/src/options.ts @@ -7,6 +7,8 @@ export type SharedOptions = { readonly userSession?: boolean; readonly pageFilter?: 'simple' | 'none' | 'readability'; readonly sentryDsn?: string; + readonly modelProvider?: string; + readonly modelName?: string; }; export const sharedOptions = { @@ -17,6 +19,15 @@ export const sharedOptions = { default: 'info', choices: ['debug', 'verbose', 'info', 'warn', 'error'], } as const, + modelProvider: { + type: 'string', + description: 'AI model provider to use', + choices: ['anthropic', 'openai'], + } as const, + modelName: { + type: 'string', + description: 'AI model name to use', + } as const, interactive: { type: 'boolean', alias: 'i', diff --git a/packages/cli/src/settings/config.ts b/packages/cli/src/settings/config.ts index df55fd5..e950125 100644 --- a/packages/cli/src/settings/config.ts +++ b/packages/cli/src/settings/config.ts @@ -9,6 +9,8 @@ const configFile = path.join(getSettingsDir(), 'config.json'); const defaultConfig = { // Add default configuration values here githubMode: false, + modelProvider: 'anthropic', + modelName: 'claude-3-7-sonnet-20250219', }; export type Config = typeof defaultConfig; diff --git a/packages/cli/tests/settings/config.test.ts b/packages/cli/tests/settings/config.test.ts index f056918..f079271 100644 --- a/packages/cli/tests/settings/config.test.ts +++ b/packages/cli/tests/settings/config.test.ts @@ -36,7 +36,11 @@ describe('Config', () => { const config = getConfig(); - expect(config).toEqual({ githubMode: false }); + expect(config).toEqual({ + githubMode: false, + modelProvider: 'anthropic', + modelName: 'claude-3-7-sonnet-20250219', + }); expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFile); }); @@ -60,7 +64,11 @@ describe('Config', () => { const config = getConfig(); - expect(config).toEqual({ githubMode: false }); + expect(config).toEqual({ + githubMode: false, + modelProvider: 'anthropic', + modelName: 'claude-3-7-sonnet-20250219', + }); }); });