Files
goodbuddy/src/main/agent/create-runtime.ts
T
mesalogo b36d688900
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build Linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build Linux x64 Agent (push) Canceled after 0s
feat: add direct model programming agents
Direct model conversations can now execute bounded platform-native processes and delegate one level of programming work without duplicating tools in Agent Runtimes. Process and Subagent activity follows existing workspace, mode, approval, cancellation, and lifecycle boundaries, with chat attribution and complete feature documentation.

Remove the static Renderer bundle baseline gate and its build-only manifests; production builds continue to compile all processes and verify the control-plane installer. Also include the pending documentation link normalization.

Release note: 直连模型现在可在 Execute 模式运行本机命令,并可委派编程 Subagent 完成修改、测试和复核;构建流程不再因静态 Renderer 包体基线阻塞。
2026-09-02 00:50:54 +08:00

357 lines
12 KiB
TypeScript

import {
ModelAgentRuntime,
type ModelRuntimeOptions
} from './model-runtime'
import { ContinueAgentRuntime } from './continue-runtime'
import { OpenCodeRuntime } from './opencode-runtime'
import {
DeepSeekHarnessRuntime,
type DeepSeekHarnessRuntimeOptions
} from './deepseek-harness-runtime'
import type { AgentRuntime } from './runtime'
import { UnconfiguredAgentRuntime } from './unconfigured-runtime'
import type {
ResolvedModelProfile,
ResolvedRuntimeSettings
} from '../runtime-settings-store'
import {
defaultRuntimeSettings,
isDeepSeekHarnessModelProfile,
isAgentRuntimeModelProtocol
} from '../../shared/contracts'
import type {
ResolvedMcpServer,
RuntimeSkillPackage
} from '../capabilities/capability-service'
import type { BundledRuntimePaths } from './bundled-runtimes'
import type { ContinueHostLauncher } from './continue-host-adapter'
import type { BrowserToolService } from '../browser/browser-model-tools'
import type { ModelToolProviderLike } from './model-tool-provider'
import type { SubagentScheduler } from '../assistant/subagent-scheduler'
import type { KnowledgeMcpGateway } from './knowledge-mcp-gateway'
import { ModelToolProvider } from './model-tool-provider'
import type { ControlledHarnessExtensionPackage } from './deepseek-harness-extension-loader'
import {
LocalWorkspaceAccess,
type WorkspaceAccess
} from '../workspace'
import {
REMOTE_EXECUTION_SPACE_UNAVAILABLE,
type ExecutionSpaceDescriptor
} from '../execution-space'
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
skillPackages?: RuntimeSkillPackage[]
mcpServers?: ResolvedMcpServer[]
continueHostCacheRoot?: string
bundledRuntimePaths?: BundledRuntimePaths
continueHostLauncher?: ContinueHostLauncher
deepseekHarnessLauncher?: DeepSeekHarnessRuntimeOptions['launch']
deepseekHarnessExtensions?: ControlledHarnessExtensionPackage[]
browserService?: BrowserToolService
knowledgeGateway?: KnowledgeMcpGateway
webSearchEnabled?: boolean
executionSpace?: ExecutionSpaceDescriptor
workspaceAccess?: WorkspaceAccess
directModelSubagentScheduler?: SubagentScheduler
}
function resolveContextCompression(
settings: ResolvedRuntimeSettings,
currentProfile: ResolvedModelProfile | undefined
): ModelRuntimeOptions['contextCompression'] {
const compression =
settings.contextCompression ?? defaultRuntimeSettings.contextCompression
const source = compression.modelSource
const summaryProfile =
source.kind === 'profile'
? settings.modelProfiles.find(
(profile) =>
profile.id === source.profileId &&
isAgentRuntimeModelProtocol(profile.protocol)
)
: undefined
return {
settings: compression,
contextWindowTokens: currentProfile?.contextWindowTokens,
...(summaryProfile
? {
summaryModel: {
apiKey: summaryProfile.apiKey,
baseUrl: summaryProfile.baseUrl,
model: summaryProfile.modelName,
protocol: summaryProfile.protocol as Exclude<
typeof summaryProfile.protocol,
'openai-images-generations'
>,
authentication: summaryProfile.authentication,
contextWindowTokens: summaryProfile.contextWindowTokens,
maximumOutputTokens: summaryProfile.maximumOutputTokens
}
}
: {})
}
}
export function createDefaultModelRuntime(
defaultWorkspace: string,
settings: ResolvedRuntimeSettings
): AgentRuntime {
if (settings.modelProtocol === 'openai-images-generations') {
return new UnconfiguredAgentRuntime()
}
const currentProfile = settings.modelProfiles.find(
(profile) => profile.id === settings.defaultModelProfileId
)
return new ModelAgentRuntime({
apiKey: settings.apiKey,
baseUrl: settings.modelBaseUrl,
model: settings.modelName,
protocol: settings.modelProtocol,
authentication: settings.modelAuthentication,
supportsImageInput: settings.supportsImageInput,
maximumOutputTokens: currentProfile?.maximumOutputTokens,
defaultWorkspace: settings.workspacePath || defaultWorkspace,
contextCompression: resolveContextCompression(
settings,
currentProfile
),
toolProvider: noSubagentTools
})
}
export function createModelProfileRuntime(
defaultWorkspace: string,
settings: ResolvedRuntimeSettings,
profile: ResolvedModelProfile
): ModelAgentRuntime {
return new ModelAgentRuntime({
apiKey: profile.apiKey,
baseUrl: profile.baseUrl,
model: profile.modelName,
protocol: profile.protocol,
authentication: profile.authentication,
supportsImageInput: profile.supportsImageInput,
maximumOutputTokens: profile.maximumOutputTokens,
imageGenerationQuality:
profile.imageGenerationQuality ??
defaultRuntimeSettings.imageGenerationQuality,
contextCompression: resolveContextCompression(settings, profile),
defaultWorkspace: settings.workspacePath || defaultWorkspace,
toolProvider: noSubagentTools
})
}
export function createAgentRuntime(
defaultWorkspace: string,
settings?: ResolvedRuntimeSettings,
capabilities: AgentCapabilityContext = {}
): AgentRuntime {
const executionSpace = capabilities.executionSpace
const remote = executionSpace?.kind === 'ssh'
const baseUrl = (
settings?.opencodeBaseUrl ||
process.env.GOODBUDDY_OPENCODE_URL ||
''
).trim()
const embedded = !baseUrl
const workspace =
executionSpace?.kind === 'local'
? executionSpace.rootPath
: settings?.workspacePath || defaultWorkspace
const getWorkspaceAccess = (): WorkspaceAccess =>
capabilities.workspaceAccess ??
executionSpace?.workspaceAccess ??
new LocalWorkspaceAccess(workspace)
const provider = settings?.provider ?? defaultRuntimeSettings.provider
if (
remote &&
(provider === 'continue' ||
provider === 'opencode' ||
provider === 'deepseek-harness' ||
provider === 'auto')
) {
throw new Error(REMOTE_EXECUTION_SPACE_UNAVAILABLE)
}
if (provider === 'deepseek-harness') {
const profile = settings?.deepseekHarnessModelProfile
if (!profile || !isDeepSeekHarnessModelProfile(profile)) {
throw new Error(
'DeepSeek Harness 需要使用 API Key 的 OpenAI 兼容 Chat Completions 模型连接'
)
}
if (!profile.apiKey) {
throw new Error('DeepSeek Harness 模型连接未配置 API Key')
}
if (!capabilities.deepseekHarnessLauncher) {
throw new Error('DeepSeek Harness 受控 Host 启动器不可用')
}
return new DeepSeekHarnessRuntime({
defaultWorkspace: workspace,
baseUrl: profile.baseUrl,
model: profile.modelName,
supportsImageInput: profile.supportsImageInput === true,
launch: capabilities.deepseekHarnessLauncher,
credentialRefs: {
GOODBUDDY_HARNESS_MODEL_API_KEY: profile.apiKey
},
skillPackages: capabilities.skillPackages,
extensionPackages: capabilities.deepseekHarnessExtensions,
toolProvider: new ModelToolProvider(
getWorkspaceAccess(),
capabilities.mcpServers,
undefined,
capabilities.knowledgeGateway,
capabilities.webSearchEnabled === true
)
})
}
if (provider === 'continue') {
if (
settings?.continueModelProfile &&
!isAgentRuntimeModelProtocol(
settings.continueModelProfile.protocol
)
) {
throw new Error(
'Continue 独立模型连接仅支持文本对话协议,不支持图像生成协议'
)
}
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() ??
'',
modelProfile: settings?.continueModelProfile,
skillInstructions: capabilities.skillInstructions,
skillPackages: capabilities.skillPackages,
defaultWorkspace: workspace,
hostCacheRoot:
capabilities.continueHostCacheRoot ??
process.env.GOODBUDDY_CONTINUE_HOST_CACHE?.trim() ??
'',
launchHost: capabilities.continueHostLauncher,
knowledgeGateway: capabilities.knowledgeGateway,
mcpServers: capabilities.mcpServers,
customization: settings?.runtimeCustomization.continue
})
}
if (provider === 'opencode' || (provider === 'auto' && (baseUrl || embedded))) {
if (
settings?.opencodeModelProfile &&
!isAgentRuntimeModelProtocol(
settings.opencodeModelProfile.protocol
)
) {
throw new Error(
'OpenCode 独立模型连接仅支持文本对话协议,不支持图像生成协议'
)
}
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,
skillPackages: capabilities.skillPackages,
defaultWorkspace: workspace,
knowledgeGateway: capabilities.knowledgeGateway,
mcpServers: capabilities.mcpServers,
customization: settings?.runtimeCustomization.opencode
})
}
const defaultModelProfile =
settings?.modelProfiles.find(
(profile) => profile.id === settings.defaultModelProfileId
) ?? settings?.modelProfiles[0]
const modelApiKey =
defaultModelProfile?.apiKey ||
settings?.apiKey ||
process.env.GOODBUDDY_MODEL_API_KEY?.trim() ||
process.env.GOODBUDDY_BIGTOKEN_API_KEY?.trim()
const modelAuthentication =
defaultModelProfile?.authentication ??
settings?.modelAuthentication ??
defaultRuntimeSettings.modelAuthentication
if (
provider === 'model' ||
(provider === 'auto' &&
(modelAuthentication === 'none' || modelApiKey))
) {
return new ModelAgentRuntime({
apiKey: modelApiKey ?? '',
baseUrl:
defaultModelProfile?.baseUrl ||
settings?.modelBaseUrl ||
process.env.GOODBUDDY_MODEL_BASE_URL?.trim() ||
process.env.GOODBUDDY_BIGTOKEN_BASE_URL?.trim() ||
defaultRuntimeSettings.modelBaseUrl,
model:
defaultModelProfile?.modelName ||
settings?.modelName ||
process.env.GOODBUDDY_MODEL_NAME?.trim() ||
process.env.GOODBUDDY_BIGTOKEN_MODEL?.trim() ||
defaultRuntimeSettings.modelName,
protocol:
defaultModelProfile?.protocol ??
settings?.modelProtocol ??
defaultRuntimeSettings.modelProtocol,
authentication: modelAuthentication,
supportsImageInput:
defaultModelProfile?.supportsImageInput ??
settings?.supportsImageInput ??
defaultRuntimeSettings.supportsImageInput,
imageGenerationQuality:
defaultModelProfile?.imageGenerationQuality ??
settings?.imageGenerationQuality ??
defaultRuntimeSettings.imageGenerationQuality,
skillInstructions: capabilities.skillInstructions,
defaultWorkspace: workspace,
mcpServers: capabilities.mcpServers,
browserService: capabilities.browserService,
knowledgeGateway: capabilities.knowledgeGateway,
webSearchEnabled: capabilities.webSearchEnabled,
contextCompression: settings
? resolveContextCompression(settings, defaultModelProfile)
: undefined,
workspaceAccess: getWorkspaceAccess(),
directModelSubagentScheduler:
capabilities.directModelSubagentScheduler
})
}
return new UnconfiguredAgentRuntime()
}