feat: add computer control and managed browser

This commit is contained in:
lofyer
2026-08-05 12:55:24 +08:00
parent 2f549387a6
commit 38ac2206f2
92 changed files with 21028 additions and 766 deletions
+12 -1
View File
@@ -2,6 +2,16 @@ import { z } from 'zod'
export const assistantIdSchema = z.string().uuid()
export const workModeSchema = z.enum(['ask', 'plan', 'execute'])
export const interactiveWorkModes = ['ask', 'execute'] as const
export type WorkMode = z.infer<typeof workModeSchema>
export type InteractiveWorkMode = (typeof interactiveWorkModes)[number]
export function normalizeInteractiveWorkMode(
workMode: WorkMode | undefined
): InteractiveWorkMode {
return workMode === 'execute' ? 'execute' : 'ask'
}
export const projectCreateSchema = z
.object({
@@ -14,7 +24,6 @@ export const projectCreateSchema = z
export const projectUpdateSchema = projectCreateSchema
export type WorkMode = z.infer<typeof workModeSchema>
export type ProjectCreateInput = z.infer<typeof projectCreateSchema>
export const conversationSnapshotSchema = z
@@ -44,6 +53,7 @@ export const conversationSnapshotSchema = z
'running',
'completed',
'failed',
'recoverable',
'cancelled',
'interrupted'
]),
@@ -433,6 +443,7 @@ export const expertCreateSchema = z
.strict()
export type ExpertCreateInput = z.infer<typeof expertCreateSchema>
export type ExpertUpdateInput = ExpertCreateInput
export type AssistantExpert = ExpertCreateInput & {
id: string
+70
View File
@@ -0,0 +1,70 @@
export type BuiltinModelToolSummary = {
name: string
displayName: string
description: string
access: 'read' | 'write'
}
export const builtinModelTools = [
{
name: 'workspace_read_text',
displayName: '读取工作区文本',
description: '读取当前工作区内不超过 256KB 的 UTF-8 文本文件。',
access: 'read'
},
{
name: 'workspace_list_directory',
displayName: '列出工作区目录',
description: '列出当前工作区内目录的直属内容,最多返回 200 项。',
access: 'read'
},
{
name: 'workspace_write_text',
displayName: '写入工作区文本',
description:
'在当前工作区内新建或覆盖不超过 512KB 的 UTF-8 文本文件,父目录必须已存在。',
access: 'write'
},
{
name: 'browser_navigate',
displayName: '浏览器导航',
description: '在隔离浏览器中打开公开的 HTTP(S) 页面。',
access: 'write'
},
{
name: 'browser_snapshot',
displayName: '读取浏览器快照',
description: '读取当前页面的有界可访问性快照;可编辑值会被隐藏。',
access: 'read'
},
{
name: 'browser_click',
displayName: '点击浏览器元素',
description: '点击最近一次浏览器快照中的可见且未受保护元素。',
access: 'write'
},
{
name: 'browser_type',
displayName: '输入浏览器文本',
description: '向可编辑且未受保护的页面元素输入文本;不支持上传文件。',
access: 'write'
},
{
name: 'browser_select',
displayName: '选择浏览器选项',
description: '在最近一次快照标识的原生选择控件中选择值。',
access: 'write'
},
{
name: 'browser_back',
displayName: '浏览器返回',
description: '在隔离浏览器的历史记录中返回上一页。',
access: 'write'
},
{
name: 'browser_screenshot',
displayName: '截取浏览器页面',
description: '截取当前可见页面区域的有界 PNG 图片。',
access: 'read'
}
] as const satisfies readonly BuiltinModelToolSummary[]
+130 -1
View File
@@ -80,6 +80,130 @@ export const skillSummarySchema = z
.strict()
export type SkillSummary = z.infer<typeof skillSummarySchema>
export const computerCapabilityIdSchema = z.enum([
'host-browser-control',
'linux-desktop-control'
])
export type ComputerCapabilityId = z.infer<
typeof computerCapabilityIdSchema
>
export const browserProfileIdSchema = z.string().uuid()
export const browserProfileNameSchema = controlCharacterFreeString(80)
export const browserProfileCreateInputSchema = z
.object({
name: browserProfileNameSchema
})
.strict()
export type BrowserProfileCreateInput = z.infer<
typeof browserProfileCreateInputSchema
>
export const browserProfileRenameInputSchema = z
.object({
profileId: browserProfileIdSchema,
name: browserProfileNameSchema
})
.strict()
export type BrowserProfileRenameInput = z.infer<
typeof browserProfileRenameInputSchema
>
export const browserProfileSelectionInputSchema = z
.object({
profileId: browserProfileIdSchema
})
.strict()
export const browserProfileSummarySchema = z
.object({
id: browserProfileIdSchema,
name: browserProfileNameSchema,
mode: z.literal('managed-isolated')
})
.strict()
export type BrowserProfileSummary = z.infer<
typeof browserProfileSummarySchema
>
export const browserProfilesSummarySchema = z
.object({
profiles: z.array(browserProfileSummarySchema).max(32),
defaultProfileId: browserProfileIdSchema.nullable()
})
.strict()
export type BrowserProfilesSummary = z.infer<
typeof browserProfilesSummarySchema
>
export const computerCapabilityToggleInputSchema = z
.object({
capabilityId: computerCapabilityIdSchema,
enabled: z.boolean()
})
.strict()
export const computerCapabilityConfigInputSchema = z
.object({
capabilityId: computerCapabilityIdSchema,
browserProfileId: browserProfileIdSchema.nullable()
})
.strict()
export const computerCapabilityConfigSummarySchema = z
.object({
id: computerCapabilityIdSchema,
name: z.string().min(1).max(80),
description: z.string().min(1).max(500),
enabled: z.boolean(),
supported: z.boolean(),
browserProfileId: browserProfileIdSchema.nullable(),
riskSummary: z.string().min(1).max(500)
})
.strict()
export type ComputerCapabilityConfigSummary = z.infer<
typeof computerCapabilityConfigSummarySchema
>
export const capabilityDiagnosticStatusSchema = z.enum([
'available',
'degraded',
'unavailable',
'disabled'
])
export type CapabilityDiagnosticStatus = z.infer<
typeof capabilityDiagnosticStatusSchema
>
export const capabilityDiagnosticCheckStatusSchema =
capabilityDiagnosticStatusSchema.exclude(['disabled'])
export const capabilityDiagnosticCheckSchema = z
.object({
id: z
.string()
.min(1)
.max(80)
.regex(/^[a-z][a-z0-9-]*$/u),
status: capabilityDiagnosticCheckStatusSchema,
summary: z.string().min(1).max(240),
remedy: z.string().min(1).max(400).optional()
})
.strict()
export const capabilityDiagnosticReportSchema = z
.object({
capabilityId: computerCapabilityIdSchema,
status: capabilityDiagnosticStatusSchema,
checkedAt: z.string().datetime(),
checks: z.array(capabilityDiagnosticCheckSchema).max(16)
})
.strict()
export type CapabilityDiagnosticReport = z.infer<
typeof capabilityDiagnosticReportSchema
>
export const mcpTransportSchema = z.enum(['stdio', 'http', 'sse'])
export type McpTransport = z.infer<typeof mcpTransportSchema>
@@ -185,7 +309,12 @@ export type McpServerSummary = z.infer<typeof mcpServerSummarySchema>
export const capabilitySnapshotSchema = z
.object({
skills: z.array(skillSummarySchema).max(256),
mcpServers: z.array(mcpServerSummarySchema).max(64)
mcpServers: z.array(mcpServerSummarySchema).max(64),
computerCapabilities: z
.array(computerCapabilityConfigSummarySchema)
.max(2)
.optional(),
browserProfiles: browserProfilesSummarySchema.optional()
})
.strict()
export type CapabilitySnapshot = z.infer<typeof capabilitySnapshotSchema>
@@ -0,0 +1,110 @@
import { describe, expect, it } from 'vitest'
import {
computerControlActionSchema,
computerControlApprovalResultSchema,
computerControlObservationSchema,
computerControlRuntimeCommandSchema
} from './computer-control-contracts'
const id = 'opaque_identifier_123456'
describe('computer control contracts', () => {
it('accepts only the bounded semantic action vocabulary', () => {
expect(
computerControlActionSchema.parse({
kind: 'replace_text',
elementRef: id,
text: 'hello'
})
).toEqual({
kind: 'replace_text',
elementRef: id,
text: 'hello'
})
for (const action of [
{ kind: 'click_at', x: 10, y: 20 },
{ kind: 'key_chord', keys: ['CTRL', 'A'] },
{ kind: 'launch_process', command: 'cmd.exe' },
{ kind: 'clipboard_write', text: 'secret' },
{ kind: 'open_file_picker', path: 'C:\\private.txt' }
]) {
expect(computerControlActionSchema.safeParse(action).success).toBe(
false
)
}
})
it('rejects unbounded and unknown command fields', () => {
expect(
computerControlRuntimeCommandSchema.safeParse({
kind: 'act',
commandId: id,
leaseId: id,
observationId: id,
revision: 1,
action: {
kind: 'replace_text',
elementRef: id,
text: 'x'.repeat(4_097)
}
}).success
).toBe(false)
expect(
computerControlRuntimeCommandSchema.safeParse({
kind: 'observe',
commandId: id,
leaseId: id,
coordinates: [10, 20]
}).success
).toBe(false)
})
it('bounds observations and excludes element values', () => {
const element = {
ref: id,
role: 'textbox',
name: 'Search',
enabled: true,
focused: false,
risk: 'input',
blocked: false
}
expect(
computerControlObservationSchema.safeParse({
observationId: id,
leaseId: id,
revision: 1,
capturedAt: 1,
windowTitle: 'Window',
elements: Array.from({ length: 201 }, () => element)
}).success
).toBe(false)
expect(
computerControlObservationSchema.safeParse({
observationId: id,
leaseId: id,
revision: 1,
capturedAt: 1,
windowTitle: 'Window',
elements: [{ ...element, value: 'password' }]
}).success
).toBe(false)
})
it('allows approvals only once and never as a broad grant', () => {
expect(
computerControlApprovalResultSchema.parse({
approvalId: id,
decision: 'approve_once'
}).decision
).toBe('approve_once')
expect(
computerControlApprovalResultSchema.safeParse({
approvalId: id,
decision: 'approve_session'
}).success
).toBe(false)
})
})
+237
View File
@@ -0,0 +1,237 @@
import { z } from 'zod'
const boundedText = (maximumLength: number) =>
z
.string()
.trim()
.min(1)
.max(maximumLength)
.refine(
(value) =>
[...value].every((character) => {
const code = character.charCodeAt(0)
return code > 31 && code !== 127
}),
'值包含控制字符'
)
const opaqueIdSchema = z
.string()
.min(16)
.max(160)
.regex(/^[A-Za-z0-9_-]+$/)
export const computerControlRiskSchema = z.enum([
'observe',
'navigate',
'input',
'commit',
'forbidden'
])
export type ComputerControlRisk = z.infer<
typeof computerControlRiskSchema
>
export const computerControlElementRoleSchema = z.enum([
'button',
'link',
'textbox',
'checkbox',
'radio',
'combobox',
'option',
'menuitem',
'tab',
'listitem',
'scrollarea'
])
export type ComputerControlElementRole = z.infer<
typeof computerControlElementRoleSchema
>
export const computerControlElementSchema = z
.object({
ref: opaqueIdSchema,
role: computerControlElementRoleSchema,
name: boundedText(256),
enabled: z.boolean(),
focused: z.boolean(),
risk: computerControlRiskSchema,
blocked: z.boolean()
})
.strict()
export type ComputerControlElement = z.infer<
typeof computerControlElementSchema
>
export const computerControlObservationSchema = z
.object({
observationId: opaqueIdSchema,
leaseId: opaqueIdSchema,
revision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
capturedAt: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
windowTitle: z.string().trim().max(256),
elements: z.array(computerControlElementSchema).max(200)
})
.strict()
export type ComputerControlObservation = z.infer<
typeof computerControlObservationSchema
>
export const computerControlActionSchema = z.discriminatedUnion('kind', [
z
.object({
kind: z.literal('activate'),
elementRef: opaqueIdSchema
})
.strict(),
z
.object({
kind: z.literal('replace_text'),
elementRef: opaqueIdSchema,
text: z.string().max(4_096)
})
.strict(),
z
.object({
kind: z.literal('select_option'),
elementRef: opaqueIdSchema,
optionName: boundedText(256)
})
.strict(),
z
.object({
kind: z.literal('scroll'),
elementRef: opaqueIdSchema,
direction: z.enum(['up', 'down', 'page_up', 'page_down'])
})
.strict()
])
export type ComputerControlAction = z.infer<
typeof computerControlActionSchema
>
const commandBase = {
commandId: opaqueIdSchema,
leaseId: opaqueIdSchema
}
export const computerControlRuntimeCommandSchema = z.discriminatedUnion(
'kind',
[
z
.object({
...commandBase,
kind: z.literal('observe')
})
.strict(),
z
.object({
...commandBase,
kind: z.literal('act'),
observationId: opaqueIdSchema,
revision: z
.number()
.int()
.positive()
.max(Number.MAX_SAFE_INTEGER),
action: computerControlActionSchema
})
.strict()
]
)
export type ComputerControlRuntimeCommand = z.infer<
typeof computerControlRuntimeCommandSchema
>
export const computerControlErrorCodeSchema = z.enum([
'invalid_request',
'driver_unavailable',
'driver_timeout',
'lease_not_found',
'lease_expired',
'lease_mismatch',
'observation_not_found',
'observation_stale',
'observation_consumed',
'element_not_found',
'window_not_foreground',
'element_identity_changed',
'focus_failed',
'forbidden',
'approval_denied',
'approval_timeout',
'cancelled',
'command_id_conflict',
'outcome_unknown',
'internal_error'
])
export type ComputerControlErrorCode = z.infer<
typeof computerControlErrorCodeSchema
>
export const computerControlErrorSchema = z
.object({
code: computerControlErrorCodeSchema,
message: boundedText(256),
retryable: z.boolean()
})
.strict()
export type ComputerControlError = z.infer<
typeof computerControlErrorSchema
>
export const computerControlApprovalRequestSchema = z
.object({
approvalId: opaqueIdSchema,
leaseId: opaqueIdSchema,
commandId: opaqueIdSchema,
risk: z.enum(['input', 'commit']),
action: z.enum(['activate', 'replace_text', 'select_option']),
targetName: boundedText(256),
textLength: z.number().int().nonnegative().max(4_096).optional()
})
.strict()
export type ComputerControlApprovalRequest = z.infer<
typeof computerControlApprovalRequestSchema
>
export const computerControlApprovalResultSchema = z
.object({
approvalId: opaqueIdSchema,
decision: z.enum(['approve_once', 'deny'])
})
.strict()
export type ComputerControlApprovalResult = z.infer<
typeof computerControlApprovalResultSchema
>
export const computerControlCommandResultSchema = z.discriminatedUnion(
'status',
[
z
.object({
status: z.literal('observed'),
commandId: opaqueIdSchema,
observation: computerControlObservationSchema
})
.strict(),
z
.object({
status: z.literal('completed'),
commandId: opaqueIdSchema,
risk: computerControlRiskSchema.exclude(['forbidden'])
})
.strict(),
z
.object({
status: z.literal('error'),
commandId: opaqueIdSchema,
error: computerControlErrorSchema
})
.strict()
]
)
export type ComputerControlCommandResult = z.infer<
typeof computerControlCommandResultSchema
>
+80 -3
View File
@@ -1,7 +1,11 @@
import { z } from 'zod'
import type {
BrowserProfileCreateInput,
BrowserProfileRenameInput,
CapabilityDiagnosticReport,
CapabilityAssignments,
CapabilitySnapshot,
ComputerCapabilityId,
McpServerInput,
McpServerTestResult
} from './capability-contracts'
@@ -27,7 +31,8 @@ import {
type ScheduleCreateInput,
type HeartbeatCreateInput,
type HeartbeatUpdateInput,
type ExpertCreateInput
type ExpertCreateInput,
type ExpertUpdateInput
} from './assistant-contracts'
export const workspaceRelativePathSchema = z
@@ -61,10 +66,12 @@ export const workspaceFileRequestSchema = z
})
.strict()
export const conversationIdSchema = z.string().min(1).max(128)
export const agentRequestSchema = z
.object({
requestId: z.string().uuid(),
conversationId: z.string().min(1).max(128),
conversationId: conversationIdSchema,
projectId: z.string().uuid().optional(),
expertId: z.string().uuid().optional(),
teamMode: z.boolean().optional(),
@@ -550,7 +557,12 @@ export type AgentEvent =
type: 'tool'
callId: string
name: string
state: 'pending' | 'running' | 'completed' | 'failed'
state:
| 'pending'
| 'running'
| 'completed'
| 'failed'
| 'recoverable'
summary: string
}
| {
@@ -590,6 +602,39 @@ export type AppInfo = {
shortcut: string
}
export const browserLiveStateSchema = z
.object({
conversationId: conversationIdSchema,
status: z.enum([
'creating',
'loading',
'ready',
'acting',
'failed',
'stopped'
]),
url: z.string().max(2_048).optional(),
frameDataUrl: z
.string()
.max(7_000_000)
.refine(
(value) => value.startsWith('data:image/png;base64,'),
'浏览器画面格式无效'
)
.optional(),
error: z.string().min(1).max(240).optional(),
updatedAt: z.number().int().nonnegative()
})
.strict()
export type BrowserLiveState = z.infer<typeof browserLiveStateSchema>
export const browserStopRequestSchema = z
.object({
conversationId: conversationIdSchema
})
.strict()
export const knowledgeIdSchema = z.string().uuid()
export const knowledgeCreateSchema = z
.object({
@@ -749,6 +794,10 @@ export type DesktopApi = {
) => Promise<void>
onEvent: (listener: (event: AgentEvent) => void) => () => void
}
browser: {
stop: (conversationId: string) => Promise<void>
onState: (listener: (state: BrowserLiveState) => void) => () => void
}
settings: {
getRuntime: () => Promise<RuntimeSettings>
updateRuntime: (input: RuntimeSettingsInput) => Promise<RuntimeSettings>
@@ -836,6 +885,11 @@ export type DesktopApi = {
experts: {
list: () => Promise<AssistantExpert[]>
create: (input: ExpertCreateInput) => Promise<AssistantExpert>
update: (
expertId: string,
input: ExpertUpdateInput
) => Promise<AssistantExpert>
remove: (expertId: string) => Promise<void>
}
capabilities: {
getSnapshot: () => Promise<CapabilitySnapshot>
@@ -855,6 +909,29 @@ export type DesktopApi = {
) => Promise<CapabilitySnapshot>
removeMcpServer: (serverId: string) => Promise<CapabilitySnapshot>
testMcpServer: (serverId: string) => Promise<McpServerTestResult>
setComputerCapabilityEnabled?: (
capabilityId: ComputerCapabilityId,
enabled: boolean
) => Promise<CapabilitySnapshot>
setComputerCapabilityBrowserProfile?: (
capabilityId: ComputerCapabilityId,
browserProfileId: string | null
) => Promise<CapabilitySnapshot>
diagnoseComputerCapability?: (
capabilityId: ComputerCapabilityId
) => Promise<CapabilityDiagnosticReport>
createBrowserProfile?: (
input: BrowserProfileCreateInput
) => Promise<CapabilitySnapshot>
renameBrowserProfile?: (
input: BrowserProfileRenameInput
) => Promise<CapabilitySnapshot>
setDefaultBrowserProfile?: (
profileId: string
) => Promise<CapabilitySnapshot>
removeBrowserProfile?: (
profileId: string
) => Promise<CapabilitySnapshot>
}
context: {
selectFiles: () => Promise<ContextAttachment[]>
+11
View File
@@ -15,6 +15,8 @@ export const ipcChannels = {
agentCancel: 'agent:cancel',
agentApprovalRespond: 'agent:approval:respond',
agentEvent: 'agent:event',
browserStop: 'browser:stop',
browserState: 'browser:state',
runtimeSettingsGet: 'settings:runtime:get',
runtimeSettingsUpdate: 'settings:runtime:update',
runtimeSettingsSelectWorkspace: 'settings:runtime:select-workspace',
@@ -54,6 +56,8 @@ export const ipcChannels = {
heartbeatsHistory: 'heartbeats:history',
expertsList: 'experts:list',
expertsCreate: 'experts:create',
expertsUpdate: 'experts:update',
expertsRemove: 'experts:remove',
capabilitiesSnapshot: 'capabilities:snapshot',
capabilitiesImportSkill: 'capabilities:skill:import',
capabilitiesRemoveSkill: 'capabilities:skill:remove',
@@ -62,6 +66,13 @@ export const ipcChannels = {
capabilitiesSaveMcp: 'capabilities:mcp:save',
capabilitiesRemoveMcp: 'capabilities:mcp:remove',
capabilitiesTestMcp: 'capabilities:mcp:test',
capabilitiesToggleComputer: 'capabilities:computer:toggle',
capabilitiesConfigureComputer: 'capabilities:computer:configure',
capabilitiesDiagnoseComputer: 'capabilities:computer:diagnose',
capabilitiesCreateBrowserProfile: 'capabilities:browser-profile:create',
capabilitiesRenameBrowserProfile: 'capabilities:browser-profile:rename',
capabilitiesDefaultBrowserProfile: 'capabilities:browser-profile:default',
capabilitiesRemoveBrowserProfile: 'capabilities:browser-profile:remove',
contextSelectFiles: 'context:select-files',
contextCaptureScreen: 'context:capture-screen',
contextCaptureWindow: 'context:capture-window',
-60
View File
@@ -1,60 +0,0 @@
import { describe, expect, it } from 'vitest'
import { modelProfilePresets } from './model-presets'
describe('modelProfilePresets', () => {
it('includes domestic, local, and generic protocol presets', () => {
expect(modelProfilePresets.map((preset) => preset.id)).toEqual(
expect.arrayContaining([
'bigtoken-gpt-image-2',
'deepseek',
'qwen',
'glm',
'kimi',
'minimax',
'siliconflow',
'volcengine-ark',
'hunyuan-deployment',
'huawei-deployment',
'ollama',
'openai',
'openai-compatible',
'anthropic-compatible'
])
)
expect(
modelProfilePresets.find((preset) => preset.id === 'ollama')
).toMatchObject({
baseUrl: 'http://127.0.0.1:11434/v1',
protocol: 'openai-chat-completions',
authentication: 'none'
})
expect(
modelProfilePresets.find(
(preset) => preset.id === 'bigtoken-gpt-image-2'
)
).toMatchObject({
baseUrl: 'https://bigtoken.ai/v1',
modelName: 'gpt-image-2',
protocol: 'openai-images-generations',
authentication: 'api-key'
})
expect(
modelProfilePresets.find((preset) => preset.id === 'openai')
).toMatchObject({
baseUrl: 'https://api.openai.com/v1',
protocol: 'openai-responses',
authentication: 'api-key'
})
})
it('does not invent universal Hunyuan or Huawei endpoints', () => {
for (const id of ['hunyuan-deployment', 'huawei-deployment']) {
expect(
modelProfilePresets.find((preset) => preset.id === id)
).toMatchObject({
baseUrl: '',
requiresDeploymentUrl: true
})
}
})
})
-148
View File
@@ -1,148 +0,0 @@
import type {
ModelAuthentication,
ModelProtocol
} from './contracts'
export type ModelProfilePreset = {
id: string
name: string
description: string
baseUrl: string
modelName: string
protocol: ModelProtocol
authentication: ModelAuthentication
requiresDeploymentUrl?: boolean
}
export const modelProfilePresets = [
{
id: 'bigtoken-gpt-image-2',
name: 'BigToken GPT Image 2',
description: 'BigToken 图像生成接口,生成结果直接显示在会话中',
baseUrl: 'https://bigtoken.ai/v1',
modelName: 'gpt-image-2',
protocol: 'openai-images-generations',
authentication: 'api-key'
},
{
id: 'deepseek',
name: 'DeepSeek',
description: 'DeepSeek 官方 OpenAI 兼容接口',
baseUrl: 'https://api.deepseek.com/v1',
modelName: 'deepseek-chat',
protocol: 'openai-chat-completions',
authentication: 'api-key'
},
{
id: 'qwen',
name: 'QwenDashScope',
description: '阿里云百炼 DashScope OpenAI 兼容接口',
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
modelName: 'qwen-plus',
protocol: 'openai-chat-completions',
authentication: 'api-key'
},
{
id: 'glm',
name: 'GLM(智谱)',
description: '智谱 AI OpenAI 兼容接口',
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
modelName: 'glm-4.5',
protocol: 'openai-chat-completions',
authentication: 'api-key'
},
{
id: 'kimi',
name: 'Kimi(月之暗面)',
description: 'Moonshot OpenAI 兼容接口',
baseUrl: 'https://api.moonshot.cn/v1',
modelName: 'moonshot-v1-8k',
protocol: 'openai-chat-completions',
authentication: 'api-key'
},
{
id: 'minimax',
name: 'MiniMax',
description: 'MiniMax 国内 OpenAI 兼容接口',
baseUrl: 'https://api.minimaxi.com/v1',
modelName: 'MiniMax-M2.1',
protocol: 'openai-chat-completions',
authentication: 'api-key'
},
{
id: 'siliconflow',
name: 'SiliconFlow(硅基流动)',
description: 'SiliconFlow OpenAI 兼容接口',
baseUrl: 'https://api.siliconflow.cn/v1',
modelName: 'deepseek-ai/DeepSeek-V3.2',
protocol: 'openai-chat-completions',
authentication: 'api-key'
},
{
id: 'volcengine-ark',
name: '火山引擎方舟',
description: '方舟 OpenAI 兼容接口;模型填写推理接入点 ID',
baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
modelName: 'ep-your-endpoint-id',
protocol: 'openai-chat-completions',
authentication: 'api-key'
},
{
id: 'hunyuan-deployment',
name: '腾讯混元(自定义部署)',
description: '填写部署文档提供的专属 API Root 和模型或部署 ID',
baseUrl: '',
modelName: 'deployment-id',
protocol: 'openai-chat-completions',
authentication: 'api-key',
requiresDeploymentUrl: true
},
{
id: 'huawei-deployment',
name: '华为云模型(自定义部署)',
description: '填写部署所在区域提供的专属 API Root 和部署 ID',
baseUrl: '',
modelName: 'deployment-id',
protocol: 'openai-chat-completions',
authentication: 'api-key',
requiresDeploymentUrl: true
},
{
id: 'ollama',
name: 'Ollama(本机)',
description: '本机 Ollama OpenAI 兼容接口,无需 API Key',
baseUrl: 'http://127.0.0.1:11434/v1',
modelName: 'llama3.2',
protocol: 'openai-chat-completions',
authentication: 'none'
},
{
id: 'openai',
name: 'OpenAI',
description: 'OpenAI Responses API',
baseUrl: 'https://api.openai.com/v1',
modelName: 'gpt-4.1',
protocol: 'openai-responses',
authentication: 'api-key'
},
{
id: 'openai-compatible',
name: 'OpenAI 兼容(自定义)',
description: '填写服务商提供的 API Root 和模型名称',
baseUrl: '',
modelName: 'model-name',
protocol: 'openai-chat-completions',
authentication: 'api-key',
requiresDeploymentUrl: true
},
{
id: 'anthropic-compatible',
name: 'Anthropic Messages 兼容(自定义)',
description: '填写服务商提供的 API Root 和模型名称',
baseUrl: '',
modelName: 'model-name',
protocol: 'anthropic-messages',
authentication: 'api-key',
requiresDeploymentUrl: true
}
] as const satisfies readonly ModelProfilePreset[]