Files
goodbuddy/src/main/agent/create-runtime.ts
T
2026-08-05 19:40:40 +08:00

173 lines
6.0 KiB
TypeScript

import { ModelAgentRuntime } from './model-runtime'
import { ContinueAgentRuntime } from './continue-runtime'
import { OpenCodeRuntime } from './opencode-runtime'
import type { AgentRuntime } from './runtime'
import { UnconfiguredAgentRuntime } from './unconfigured-runtime'
import type { ResolvedRuntimeSettings } from '../runtime-settings-store'
import { defaultRuntimeSettings } from '../../shared/contracts'
import type { ResolvedMcpServer } from '../capabilities/capability-service'
import type { BundledRuntimePaths } from './bundled-runtimes'
import type { ContinueHostLauncher } from './continue-host-adapter'
import { resolveRuntimeSandbox } from './runtime-sandbox'
import type { BrowserToolService } from '../browser/browser-model-tools'
import type { ModelToolProviderLike } from './model-tool-provider'
const noSubagentTools: ModelToolProviderLike = {
listTools: async () => [],
getApproval: () => {
throw new Error('子专家不允许工具调用')
},
callTool: async () => {
throw new Error('子专家不允许工具调用')
},
releaseConversation: async () => undefined,
dispose: async () => undefined
}
export type AgentCapabilityContext = {
skillInstructions?: string
mcpServers?: ResolvedMcpServer[]
continueHostCacheRoot?: string
bundledRuntimePaths?: BundledRuntimePaths
continueHostLauncher?: ContinueHostLauncher
browserService?: BrowserToolService
}
export function createDefaultModelRuntime(
defaultWorkspace: string,
settings: ResolvedRuntimeSettings
): AgentRuntime {
if (settings.modelProtocol === 'openai-images-generations') {
return new UnconfiguredAgentRuntime()
}
return new ModelAgentRuntime({
apiKey: settings.apiKey,
baseUrl: settings.modelBaseUrl,
model: settings.modelName,
protocol: settings.modelProtocol,
authentication: settings.modelAuthentication,
defaultWorkspace: settings.workspacePath || defaultWorkspace,
toolProvider: noSubagentTools
})
}
export function createAgentRuntime(
defaultWorkspace: string,
settings?: ResolvedRuntimeSettings,
capabilities: AgentCapabilityContext = {}
): AgentRuntime {
const baseUrl =
settings?.opencodeBaseUrl || process.env.GOODBUDDY_OPENCODE_URL
const embedded =
settings?.opencodeEmbedded ??
process.env.GOODBUDDY_OPENCODE_EMBEDDED === 'true'
const workspace = settings?.workspacePath || defaultWorkspace
const provider = settings?.provider ?? 'auto'
const sandboxMode =
settings?.runtimeSandboxMode ??
defaultRuntimeSettings.runtimeSandboxMode
if (provider === 'continue') {
if (
settings?.continueModelProfile &&
settings.continueModelProfile.protocol !== 'anthropic-messages' &&
settings.continueModelProfile.protocol !==
'openai-chat-completions'
) {
throw new Error(
'Continue 独立模型连接仅支持 Anthropic Messages 或 OpenAI 兼容 Chat Completions'
)
}
return new ContinueAgentRuntime({
binaryPath:
settings?.continueBinaryPath ??
process.env.GOODBUDDY_CONTINUE_BINARY?.trim() ??
process.env.GOODBUDDY_CONTINUE_COMMAND?.trim() ??
'',
bundledBinaryPath: capabilities.bundledRuntimePaths?.continue,
configPath:
settings?.continueConfigPath ??
process.env.GOODBUDDY_CONTINUE_CONFIG?.trim() ??
'',
runtimeSandboxMode: sandboxMode,
modelProfile: settings?.continueModelProfile,
skillInstructions: capabilities.skillInstructions,
defaultWorkspace: workspace,
hostCacheRoot:
capabilities.continueHostCacheRoot ??
process.env.GOODBUDDY_CONTINUE_HOST_CACHE?.trim() ??
'',
launchHost: capabilities.continueHostLauncher
})
}
if (provider === 'opencode' || (provider === 'auto' && (baseUrl || embedded))) {
if (
settings?.opencodeModelProfile &&
(settings.opencodeModelProfile.protocol !== 'anthropic-messages' ||
settings.opencodeModelProfile.authentication !== 'api-key')
) {
throw new Error(
'OpenCode 独立模型连接仅支持需要 API Key 的 Anthropic Messages 协议'
)
}
return new OpenCodeRuntime({
baseUrl,
embedded,
binaryPath:
settings?.opencodeBinaryPath ??
process.env.GOODBUDDY_OPENCODE_BINARY?.trim() ??
'',
bundledBinaryPath: capabilities.bundledRuntimePaths?.opencode,
configPath:
settings?.opencodeConfigPath ??
process.env.GOODBUDDY_OPENCODE_CONFIG?.trim() ??
'',
modelProfile: settings?.opencodeModelProfile,
skillInstructions: capabilities.skillInstructions,
sandbox: resolveRuntimeSandbox(sandboxMode),
defaultWorkspace: workspace
})
}
const modelApiKey =
settings?.apiKey ||
process.env.GOODBUDDY_MODEL_API_KEY?.trim() ||
process.env.GOODBUDDY_BIGTOKEN_API_KEY?.trim()
const modelAuthentication =
settings?.modelAuthentication ??
defaultRuntimeSettings.modelAuthentication
if (
provider === 'model' ||
(provider === 'auto' &&
(modelAuthentication === 'none' || modelApiKey))
) {
return new ModelAgentRuntime({
apiKey: modelApiKey ?? '',
baseUrl:
settings?.modelBaseUrl ||
process.env.GOODBUDDY_MODEL_BASE_URL?.trim() ||
process.env.GOODBUDDY_BIGTOKEN_BASE_URL?.trim() ||
defaultRuntimeSettings.modelBaseUrl,
model:
settings?.modelName ||
process.env.GOODBUDDY_MODEL_NAME?.trim() ||
process.env.GOODBUDDY_BIGTOKEN_MODEL?.trim() ||
defaultRuntimeSettings.modelName,
protocol:
settings?.modelProtocol ??
defaultRuntimeSettings.modelProtocol,
authentication: modelAuthentication,
imageGenerationQuality:
settings?.imageGenerationQuality ??
defaultRuntimeSettings.imageGenerationQuality,
skillInstructions: capabilities.skillInstructions,
defaultWorkspace: workspace,
mcpServers: capabilities.mcpServers,
browserService: capabilities.browserService
})
}
return new UnconfiguredAgentRuntime()
}