refactor: share remote agent command parsing

This commit is contained in:
weishu
2026-05-21 14:42:40 +08:00
parent b0a3397601
commit 1284385d38
4 changed files with 114 additions and 90 deletions
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest'
import { GEMINI_PERMISSION_MODES, OPENCODE_PERMISSION_MODES } from '@hapi/protocol/modes'
import { parseRemoteAgentCommandOptions } from './agentCommandOptions'
describe('parseRemoteAgentCommandOptions', () => {
it('parses common remote agent flags', () => {
expect(parseRemoteAgentCommandOptions([
'--started-by', 'runner',
'--hapi-starting-mode', 'remote',
'--permission-mode', 'yolo',
'--resume', 'session-1',
'--model', 'model-a'
], GEMINI_PERMISSION_MODES)).toEqual({
startedBy: 'runner',
startingMode: 'remote',
permissionMode: 'yolo',
resumeSessionId: 'session-1',
model: 'model-a'
})
})
it('does not let --yolo override an explicit permission mode that appeared first', () => {
expect(parseRemoteAgentCommandOptions([
'--permission-mode', 'default',
'--yolo'
], OPENCODE_PERMISSION_MODES).permissionMode).toBe('default')
})
it('keeps current unknown-arg behavior by ignoring unrecognized flags', () => {
expect(parseRemoteAgentCommandOptions([
'--unknown',
'value',
'--model',
'model-a'
], GEMINI_PERMISSION_MODES)).toEqual({
model: 'model-a'
})
})
it('rejects invalid constrained values', () => {
expect(() => parseRemoteAgentCommandOptions([
'--hapi-starting-mode',
'sideways'
], GEMINI_PERMISSION_MODES)).toThrow('Invalid --hapi-starting-mode')
expect(() => parseRemoteAgentCommandOptions([
'--permission-mode',
'bypassPermissions'
], GEMINI_PERMISSION_MODES)).toThrow('Invalid --permission-mode value')
})
it('requires values for resume and model flags', () => {
expect(() => parseRemoteAgentCommandOptions(['--resume'], OPENCODE_PERMISSION_MODES)).toThrow('Missing --resume value')
expect(() => parseRemoteAgentCommandOptions(['--model'], OPENCODE_PERMISSION_MODES)).toThrow('Missing --model value')
})
})
+54
View File
@@ -0,0 +1,54 @@
import type { PermissionMode } from '@hapi/protocol/types'
export type RemoteAgentCommandOptions<TPermissionMode extends PermissionMode> = {
startedBy?: 'runner' | 'terminal'
startingMode?: 'local' | 'remote'
permissionMode?: TPermissionMode
model?: string
resumeSessionId?: string
}
export function parseRemoteAgentCommandOptions<TPermissionMode extends PermissionMode>(
args: string[],
allowedPermissionModes: readonly TPermissionMode[]
): RemoteAgentCommandOptions<TPermissionMode> {
const options: RemoteAgentCommandOptions<TPermissionMode> = {}
let hasExplicitPermissionMode = false
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg === '--started-by') {
options.startedBy = args[++i] as 'runner' | 'terminal'
} else if (arg === '--hapi-starting-mode') {
const value = args[++i]
if (value === 'local' || value === 'remote') {
options.startingMode = value
} else {
throw new Error('Invalid --hapi-starting-mode (expected local or remote)')
}
} else if (arg === '--permission-mode') {
const mode = args[++i]
if (!mode || !(allowedPermissionModes as readonly string[]).includes(mode)) {
throw new Error(`Invalid --permission-mode value: ${mode ?? '(missing)'}`)
}
options.permissionMode = mode as TPermissionMode
hasExplicitPermissionMode = true
} else if (arg === '--yolo' && !hasExplicitPermissionMode) {
options.permissionMode = 'yolo' as TPermissionMode
} else if (arg === '--resume') {
const sessionId = args[++i]
if (!sessionId) {
throw new Error('Missing --resume value')
}
options.resumeSessionId = sessionId
} else if (arg === '--model') {
const model = args[++i]
if (!model) {
throw new Error('Missing --model value')
}
options.model = model
}
}
return options
}
+2 -45
View File
@@ -4,57 +4,14 @@ import { initializeToken } from '@/ui/tokenInit'
import { maybeAutoStartServer } from '@/utils/autoStartServer'
import type { CommandDefinition } from './types'
import { GEMINI_PERMISSION_MODES } from '@hapi/protocol/modes'
import type { GeminiPermissionMode } from '@hapi/protocol/types'
import { parseRemoteAgentCommandOptions } from './agentCommandOptions'
export const geminiCommand: CommandDefinition = {
name: 'gemini',
requiresRuntimeAssets: true,
run: async ({ commandArgs }) => {
try {
const options: {
startedBy?: 'runner' | 'terminal'
startingMode?: 'local' | 'remote'
permissionMode?: GeminiPermissionMode
model?: string
resumeSessionId?: string
} = {}
let hasExplicitPermissionMode = false
for (let i = 0; i < commandArgs.length; i++) {
const arg = commandArgs[i]
if (arg === '--started-by') {
options.startedBy = commandArgs[++i] as 'runner' | 'terminal'
} else if (arg === '--hapi-starting-mode') {
const value = commandArgs[++i]
if (value === 'local' || value === 'remote') {
options.startingMode = value
} else {
throw new Error('Invalid --hapi-starting-mode (expected local or remote)')
}
} else if (arg === '--permission-mode') {
const mode = commandArgs[++i]
if (!mode || !(GEMINI_PERMISSION_MODES as readonly string[]).includes(mode)) {
throw new Error(`Invalid --permission-mode value: ${mode ?? '(missing)'}`)
}
options.permissionMode = mode as GeminiPermissionMode
hasExplicitPermissionMode = true
} else if (arg === '--yolo' && !hasExplicitPermissionMode) {
options.permissionMode = 'yolo'
} else if (arg === '--resume') {
const sessionId = commandArgs[++i]
if (!sessionId) {
throw new Error('Missing --resume value')
}
options.resumeSessionId = sessionId
} else if (arg === '--model') {
const model = commandArgs[++i]
if (!model) {
throw new Error('Missing --model value')
}
options.model = model
}
}
const options = parseRemoteAgentCommandOptions(commandArgs, GEMINI_PERMISSION_MODES)
await initializeToken()
await maybeAutoStartServer()
+2 -45
View File
@@ -4,57 +4,14 @@ import { initializeToken } from '@/ui/tokenInit'
import { maybeAutoStartServer } from '@/utils/autoStartServer'
import type { CommandDefinition } from './types'
import { OPENCODE_PERMISSION_MODES } from '@hapi/protocol/modes'
import type { OpencodePermissionMode } from '@hapi/protocol/types'
import { parseRemoteAgentCommandOptions } from './agentCommandOptions'
export const opencodeCommand: CommandDefinition = {
name: 'opencode',
requiresRuntimeAssets: true,
run: async ({ commandArgs }) => {
try {
const options: {
startedBy?: 'runner' | 'terminal'
startingMode?: 'local' | 'remote'
permissionMode?: OpencodePermissionMode
model?: string
resumeSessionId?: string
} = {}
let hasExplicitPermissionMode = false
for (let i = 0; i < commandArgs.length; i++) {
const arg = commandArgs[i]
if (arg === '--started-by') {
options.startedBy = commandArgs[++i] as 'runner' | 'terminal'
} else if (arg === '--hapi-starting-mode') {
const value = commandArgs[++i]
if (value === 'local' || value === 'remote') {
options.startingMode = value
} else {
throw new Error('Invalid --hapi-starting-mode (expected local or remote)')
}
} else if (arg === '--permission-mode') {
const mode = commandArgs[++i]
if (!mode || !(OPENCODE_PERMISSION_MODES as readonly string[]).includes(mode)) {
throw new Error(`Invalid --permission-mode value: ${mode ?? '(missing)'}`)
}
options.permissionMode = mode as OpencodePermissionMode
hasExplicitPermissionMode = true
} else if (arg === '--yolo' && !hasExplicitPermissionMode) {
options.permissionMode = 'yolo'
} else if (arg === '--resume') {
const sessionId = commandArgs[++i]
if (!sessionId) {
throw new Error('Missing --resume value')
}
options.resumeSessionId = sessionId
} else if (arg === '--model') {
const model = commandArgs[++i]
if (!model) {
throw new Error('Missing --model value')
}
options.model = model
}
}
const options = parseRemoteAgentCommandOptions(commandArgs, OPENCODE_PERMISSION_MODES)
await initializeToken()
await maybeAutoStartServer()