mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-02 10:33:00 +08:00
feat: add opencode support
This commit is contained in:
@@ -9,8 +9,9 @@ import { queryClaudeSDK } from '../claude-sdk.js';
|
||||
import { spawnCursor } from '../cursor-cli.js';
|
||||
import { queryCodex } from '../openai-codex.js';
|
||||
import { spawnGemini } from '../gemini-cli.js';
|
||||
import { spawnOpenCode } from '../opencode-cli.js';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
||||
import { providerModelsService } from '../modules/providers/services/provider-models.service.js';
|
||||
import { IS_PLATFORM } from '../constants/config.js';
|
||||
import { normalizeProjectPath } from '../shared/utils.js';
|
||||
|
||||
@@ -608,7 +609,7 @@ class ResponseCollector {
|
||||
/**
|
||||
* POST /api/agent
|
||||
*
|
||||
* Trigger an AI agent (Claude or Cursor) to work on a project.
|
||||
* Trigger an AI agent to work on a project.
|
||||
* Supports automatic GitHub branch and pull request creation after successful completion.
|
||||
*
|
||||
* ================================================================================================
|
||||
@@ -633,7 +634,7 @@ class ResponseCollector {
|
||||
* - Source for auto-generated branch names (if createBranch=true and no branchName)
|
||||
* - Fallback for PR title if no commits are made
|
||||
*
|
||||
* @param {string} provider - (Optional) AI provider to use. Options: 'claude' | 'cursor' | 'codex' | 'gemini'
|
||||
* @param {string} provider - (Optional) AI provider to use. Options: 'claude' | 'cursor' | 'codex' | 'gemini' | 'opencode'
|
||||
* Default: 'claude'
|
||||
*
|
||||
* @param {boolean} stream - (Optional) Enable Server-Sent Events (SSE) streaming for real-time updates.
|
||||
@@ -751,7 +752,7 @@ class ResponseCollector {
|
||||
* Input Validations (400 Bad Request):
|
||||
* - Either githubUrl OR projectPath must be provided (not neither)
|
||||
* - message must be non-empty string
|
||||
* - provider must be 'claude', 'cursor', 'codex', or 'gemini'
|
||||
* - provider must be 'claude', 'cursor', 'codex', 'gemini', or 'opencode'
|
||||
* - createBranch/createPR requires githubUrl OR projectPath (not neither)
|
||||
* - branchName must pass Git naming rules (if provided)
|
||||
*
|
||||
@@ -859,8 +860,8 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
||||
return res.status(400).json({ error: 'message is required' });
|
||||
}
|
||||
|
||||
if (!['claude', 'cursor', 'codex', 'gemini'].includes(provider)) {
|
||||
return res.status(400).json({ error: 'provider must be "claude", "cursor", "codex", or "gemini"' });
|
||||
if (!['claude', 'cursor', 'codex', 'gemini', 'opencode'].includes(provider)) {
|
||||
return res.status(400).json({ error: 'provider must be "claude", "cursor", "codex", "gemini", or "opencode"' });
|
||||
}
|
||||
|
||||
// Validate GitHub branch/PR creation requirements
|
||||
@@ -938,6 +939,10 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const codexModels = await providerModelsService.getProviderModels('codex');
|
||||
const geminiModels = await providerModelsService.getProviderModels('gemini');
|
||||
const opencodeModels = await providerModelsService.getProviderModels('opencode', { cwd: finalProjectPath });
|
||||
|
||||
// Start the appropriate session
|
||||
if (provider === 'claude') {
|
||||
console.log('🤖 Starting Claude SDK session');
|
||||
@@ -967,7 +972,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
||||
projectPath: finalProjectPath,
|
||||
cwd: finalProjectPath,
|
||||
sessionId: sessionId || null,
|
||||
model: model || CODEX_MODELS.DEFAULT,
|
||||
model: model || codexModels.DEFAULT,
|
||||
permissionMode: 'bypassPermissions'
|
||||
}, writer);
|
||||
} else if (provider === 'gemini') {
|
||||
@@ -977,9 +982,18 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
||||
projectPath: finalProjectPath,
|
||||
cwd: finalProjectPath,
|
||||
sessionId: sessionId || null,
|
||||
model: model,
|
||||
model: model || geminiModels.DEFAULT,
|
||||
skipPermissions: true // CLI mode bypasses permissions
|
||||
}, writer);
|
||||
} else if (provider === 'opencode') {
|
||||
console.log('Starting OpenCode CLI session');
|
||||
|
||||
await spawnOpenCode(message.trim(), {
|
||||
projectPath: finalProjectPath,
|
||||
cwd: finalProjectPath,
|
||||
sessionId: sessionId || null,
|
||||
model: model || opencodeModels.DEFAULT
|
||||
}, writer);
|
||||
}
|
||||
|
||||
// Handle GitHub branch and PR creation after successful agent completion
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'path';
|
||||
|
||||
import express from 'express';
|
||||
|
||||
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
||||
import { providerModelsService } from '../modules/providers/services/provider-models.service.js';
|
||||
import { parseFrontMatter } from '../shared/frontmatter.js';
|
||||
import { findAppRoot, getModuleDir } from '../utils/runtime-paths.js';
|
||||
|
||||
@@ -187,15 +187,31 @@ Custom commands can be created in:
|
||||
},
|
||||
|
||||
'/model': async (args, context) => {
|
||||
// Read available models from centralized constants
|
||||
const [claude, cursor, codex, gemini, opencode] = await Promise.all([
|
||||
providerModelsService.getProviderModels('claude'),
|
||||
providerModelsService.getProviderModels('cursor'),
|
||||
providerModelsService.getProviderModels('codex'),
|
||||
providerModelsService.getProviderModels('gemini'),
|
||||
providerModelsService.getProviderModels('opencode'),
|
||||
]);
|
||||
|
||||
const availableModels = {
|
||||
claude: CLAUDE_MODELS.OPTIONS.map(o => o.value),
|
||||
cursor: CURSOR_MODELS.OPTIONS.map(o => o.value),
|
||||
codex: CODEX_MODELS.OPTIONS.map(o => o.value)
|
||||
claude: claude.OPTIONS.map(o => o.value),
|
||||
cursor: cursor.OPTIONS.map(o => o.value),
|
||||
codex: codex.OPTIONS.map(o => o.value),
|
||||
gemini: gemini.OPTIONS.map(o => o.value),
|
||||
opencode: opencode.OPTIONS.map(o => o.value),
|
||||
};
|
||||
|
||||
const currentProvider = context?.provider || 'claude';
|
||||
const currentModel = context?.model || CLAUDE_MODELS.DEFAULT;
|
||||
const defaults = {
|
||||
claude: claude.DEFAULT,
|
||||
cursor: cursor.DEFAULT,
|
||||
codex: codex.DEFAULT,
|
||||
gemini: gemini.DEFAULT,
|
||||
opencode: opencode.DEFAULT,
|
||||
};
|
||||
const currentModel = context?.model || defaults[currentProvider] || claude.DEFAULT;
|
||||
|
||||
return {
|
||||
type: 'builtin',
|
||||
@@ -216,13 +232,10 @@ Custom commands can be created in:
|
||||
'/cost': async (args, context) => {
|
||||
const tokenUsage = context?.tokenUsage || {};
|
||||
const provider = context?.provider || 'claude';
|
||||
const catalog = await providerModelsService.getProviderModels(provider);
|
||||
const model =
|
||||
context?.model ||
|
||||
(provider === 'cursor'
|
||||
? CURSOR_MODELS.DEFAULT
|
||||
: provider === 'codex'
|
||||
? CODEX_MODELS.DEFAULT
|
||||
: CLAUDE_MODELS.DEFAULT);
|
||||
catalog.DEFAULT;
|
||||
|
||||
const used = Number(tokenUsage.used ?? tokenUsage.totalUsed ?? tokenUsage.total_tokens ?? 0) || 0;
|
||||
const total =
|
||||
@@ -314,6 +327,9 @@ Custom commands can be created in:
|
||||
? `${uptimeHours}h ${uptimeMinutes % 60}m`
|
||||
: `${uptimeMinutes}m`;
|
||||
|
||||
const statusProvider = context?.provider || 'claude';
|
||||
const statusCatalog = await providerModelsService.getProviderModels(statusProvider);
|
||||
|
||||
return {
|
||||
type: 'builtin',
|
||||
action: 'status',
|
||||
@@ -322,8 +338,8 @@ Custom commands can be created in:
|
||||
packageName,
|
||||
uptime: uptimeFormatted,
|
||||
uptimeSeconds: Math.floor(uptime),
|
||||
model: context?.model || CLAUDE_MODELS.DEFAULT,
|
||||
provider: context?.provider || 'claude',
|
||||
model: context?.model || statusCatalog.DEFAULT,
|
||||
provider: statusProvider,
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import express from 'express';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { CURSOR_MODELS } from '../../shared/modelConstants.js';
|
||||
import { CURSOR_MODELS } from '../modules/providers/services/provider-models.service.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user