feat: support opencode

This commit is contained in:
weishu
2026-01-29 10:34:57 +08:00
parent 66ab96bf38
commit 70b5c22c8f
47 changed files with 2737 additions and 28 deletions
@@ -312,7 +312,7 @@ export function HappyComposer(props: {
useEffect(() => {
const handleGlobalKeyDown = (e: globalThis.KeyboardEvent) => {
if (e.key === 'm' && (e.metaKey || e.ctrlKey) && onModelModeChange && agentFlavor !== 'codex' && agentFlavor !== 'gemini') {
if (e.key === 'm' && (e.metaKey || e.ctrlKey) && onModelModeChange && agentFlavor !== 'codex' && agentFlavor !== 'gemini' && agentFlavor !== 'opencode') {
e.preventDefault()
const currentIndex = MODEL_MODES.indexOf(modelMode as typeof MODEL_MODES[number])
const nextIndex = (currentIndex + 1) % MODEL_MODES.length
@@ -386,7 +386,7 @@ export function HappyComposer(props: {
}, [onModelModeChange, controlsDisabled, haptic])
const showPermissionSettings = Boolean(onPermissionModeChange && permissionModeOptions.length > 0)
const showModelSettings = Boolean(onModelModeChange && agentFlavor !== 'codex' && agentFlavor !== 'gemini')
const showModelSettings = Boolean(onModelModeChange && agentFlavor !== 'codex' && agentFlavor !== 'gemini' && agentFlavor !== 'opencode')
const showSettingsButton = Boolean(showPermissionSettings || showModelSettings)
const showAbortButton = true
const voiceEnabled = Boolean(onVoiceToggle)
@@ -14,7 +14,7 @@ export function AgentSelector(props: {
{t('newSession.agent')}
</label>
<div className="flex gap-3">
{(['claude', 'codex', 'gemini'] as const).map((agentType) => (
{(['claude', 'codex', 'gemini', 'opencode'] as const).map((agentType) => (
<label
key={agentType}
className="flex items-center gap-1.5 cursor-pointer"
@@ -10,6 +10,9 @@ export function ModelSelector(props: {
}) {
const { t } = useTranslation()
const options = MODEL_OPTIONS[props.agent]
if (options.length === 0) {
return null
}
return (
<div className="flex flex-col gap-1.5 px-3 py-3">
+2 -1
View File
@@ -195,11 +195,12 @@ export function NewSession(props: {
setError(null)
try {
const resolvedModel = model !== 'auto' && agent !== 'opencode' ? model : undefined
const result = await spawnSession({
machineId,
directory: directory.trim(),
agent,
model: model !== 'auto' ? model : undefined,
model: resolvedModel,
yolo: yoloMode,
sessionType,
worktreeName: sessionType === 'worktree' ? (worktreeName.trim() || undefined) : undefined
+2 -1
View File
@@ -1,4 +1,4 @@
export type AgentType = 'claude' | 'codex' | 'gemini'
export type AgentType = 'claude' | 'codex' | 'gemini' | 'opencode'
export type SessionType = 'simple' | 'worktree'
export const MODEL_OPTIONS: Record<AgentType, { value: string; label: string }[]> = {
@@ -20,4 +20,5 @@ export const MODEL_OPTIONS: Record<AgentType, { value: string; label: string }[]
{ value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
],
opencode: [],
}
@@ -33,8 +33,10 @@ function isToolAllowedForSession(toolName: string, toolInput: unknown, allowedTo
function isCodexSession(metadata: SessionMetadataSummary | null, toolName: string): boolean {
return metadata?.flavor === 'codex'
|| metadata?.flavor === 'gemini'
|| metadata?.flavor === 'opencode'
|| toolName.startsWith('Codex')
|| toolName.startsWith('Gemini')
|| toolName.startsWith('OpenCode')
}
function formatPermissionSummary(permission: ToolPermission, toolName: string, toolInput: unknown, codex: boolean, t: (key: string) => string): string {