The built-in browser was limited to direct-model tools, and its Agent switch also blocked manual Browser workbar actions. It is now a request-scoped GoodBuddy capability assignable to direct models, managed OpenCode, and Continue, while manual browser controls remain available when Agent access is off. Existing browser enablement migrates to capability settings version 6. DeepSeek Harness remains unsupported. Release note: 内置浏览器现在可分配给直连模型、OpenCode 和 Continue;关闭 Agent 浏览器能力不会影响用户在浏览器工作栏中的手动操作。
459 lines
12 KiB
TypeScript
459 lines
12 KiB
TypeScript
import { z } from 'zod'
|
|
import { settingsWarningsSchema } from './settings-warning-contracts'
|
|
|
|
const controlCharacterFreeString = (maximumLength: number) =>
|
|
z
|
|
.string()
|
|
.trim()
|
|
.min(1)
|
|
.max(maximumLength)
|
|
.refine(
|
|
(value) =>
|
|
[...value].every((character) => {
|
|
const code = character.charCodeAt(0)
|
|
return code > 31 && code !== 127
|
|
}),
|
|
'值包含控制字符'
|
|
)
|
|
|
|
export const runtimeTargetSchema = z.enum([
|
|
'model',
|
|
'opencode',
|
|
'continue',
|
|
'deepseek-harness'
|
|
])
|
|
export type RuntimeTarget = z.infer<typeof runtimeTargetSchema>
|
|
|
|
export const capabilityAssignmentsSchema = z
|
|
.array(runtimeTargetSchema)
|
|
.max(4)
|
|
.refine(
|
|
(assignments) => new Set(assignments).size === assignments.length,
|
|
'Runtime 分配不能重复'
|
|
)
|
|
export type CapabilityAssignments = z.infer<
|
|
typeof capabilityAssignmentsSchema
|
|
>
|
|
|
|
export const builtinMcpServerIdSchema = z.enum([
|
|
'knowledge-base',
|
|
'magic-notes',
|
|
'goodbuddy-config',
|
|
'builtin-browser'
|
|
])
|
|
export type BuiltinMcpServerId = z.infer<
|
|
typeof builtinMcpServerIdSchema
|
|
>
|
|
|
|
export const builtinMcpAssignmentsSchema =
|
|
capabilityAssignmentsSchema.refine(
|
|
(assignments) => !assignments.includes('deepseek-harness'),
|
|
'DeepSeek Harness 当前不支持内置 MCP'
|
|
)
|
|
|
|
export const builtinMcpServerToggleInputSchema = z
|
|
.object({
|
|
serverId: builtinMcpServerIdSchema,
|
|
enabled: z.boolean()
|
|
})
|
|
.strict()
|
|
|
|
export const builtinMcpServerAssignmentsInputSchema = z
|
|
.object({
|
|
serverId: builtinMcpServerIdSchema,
|
|
assignments: builtinMcpAssignmentsSchema
|
|
})
|
|
.strict()
|
|
|
|
export const builtinMcpServerStateSummarySchema = z
|
|
.object({
|
|
id: builtinMcpServerIdSchema,
|
|
enabled: z.boolean(),
|
|
assignments: builtinMcpAssignmentsSchema
|
|
})
|
|
.strict()
|
|
export type BuiltinMcpServerStateSummary = z.infer<
|
|
typeof builtinMcpServerStateSummarySchema
|
|
>
|
|
|
|
export const secretActionSchema = z.discriminatedUnion('action', [
|
|
z.object({ action: z.literal('keep') }).strict(),
|
|
z
|
|
.object({
|
|
action: z.literal('replace'),
|
|
value: controlCharacterFreeString(8_192)
|
|
})
|
|
.strict(),
|
|
z.object({ action: z.literal('clear') }).strict()
|
|
])
|
|
export type SecretAction = z.infer<typeof secretActionSchema>
|
|
|
|
export const skillIdSchema = z
|
|
.string()
|
|
.min(1)
|
|
.max(128)
|
|
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
|
|
|
|
export const skillImportKindSchema = z.enum(['directory', 'zip'])
|
|
export type SkillImportKind = z.infer<typeof skillImportKindSchema>
|
|
|
|
export const skillToggleInputSchema = z
|
|
.object({
|
|
skillId: skillIdSchema,
|
|
enabled: z.boolean()
|
|
})
|
|
.strict()
|
|
|
|
export const skillAssignmentsInputSchema = z
|
|
.object({
|
|
skillId: skillIdSchema,
|
|
assignments: capabilityAssignmentsSchema
|
|
})
|
|
.strict()
|
|
|
|
export const skillSummarySchema = z
|
|
.object({
|
|
id: skillIdSchema,
|
|
name: z.string().min(1).max(80),
|
|
description: z.string().min(1).max(500),
|
|
version: z.string().max(32).optional(),
|
|
tags: z.array(z.string().min(1).max(32)).max(12),
|
|
source: z.enum(['builtin', 'imported']),
|
|
digest: z.string().regex(/^[a-f0-9]{64}$/),
|
|
enabled: z.boolean(),
|
|
assignments: capabilityAssignmentsSchema
|
|
})
|
|
.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>
|
|
|
|
export const mcpServerIdSchema = z.string().uuid()
|
|
const mcpServerNameSchema = controlCharacterFreeString(80)
|
|
const mcpServerDescriptionSchema = z.string().trim().max(500)
|
|
const mcpCommandSchema = controlCharacterFreeString(4_096)
|
|
const mcpArgumentSchema = controlCharacterFreeString(4_096)
|
|
const mcpRemoteUrlSchema = z
|
|
.string()
|
|
.url()
|
|
.max(2_048)
|
|
.superRefine((value, context) => {
|
|
if (!['http:', 'https:'].includes(new URL(value).protocol)) {
|
|
context.addIssue({
|
|
code: 'custom',
|
|
message: 'MCP URL 必须使用 HTTP 或 HTTPS'
|
|
})
|
|
}
|
|
})
|
|
|
|
const mcpCommonInputShape = {
|
|
name: mcpServerNameSchema,
|
|
description: mcpServerDescriptionSchema,
|
|
enabled: z.boolean(),
|
|
allowDynamicTools: z.boolean(),
|
|
assignments: capabilityAssignmentsSchema,
|
|
secret: secretActionSchema
|
|
}
|
|
|
|
export const mcpServerInputSchema = z.discriminatedUnion('transport', [
|
|
z
|
|
.object({
|
|
...mcpCommonInputShape,
|
|
transport: z.literal('stdio'),
|
|
command: mcpCommandSchema,
|
|
args: z.array(mcpArgumentSchema).max(64)
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
...mcpCommonInputShape,
|
|
transport: z.literal('http'),
|
|
url: mcpRemoteUrlSchema
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
...mcpCommonInputShape,
|
|
transport: z.literal('sse'),
|
|
url: mcpRemoteUrlSchema
|
|
})
|
|
.strict()
|
|
])
|
|
export type McpServerInput = z.infer<typeof mcpServerInputSchema>
|
|
|
|
export const mcpServerSummarySchema = z.discriminatedUnion('transport', [
|
|
z
|
|
.object({
|
|
id: mcpServerIdSchema,
|
|
name: mcpServerNameSchema,
|
|
description: mcpServerDescriptionSchema,
|
|
enabled: z.boolean(),
|
|
allowDynamicTools: z.boolean(),
|
|
assignments: capabilityAssignmentsSchema,
|
|
secretConfigured: z.boolean(),
|
|
transport: z.literal('stdio'),
|
|
command: mcpCommandSchema,
|
|
args: z.array(mcpArgumentSchema).max(64)
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
id: mcpServerIdSchema,
|
|
name: mcpServerNameSchema,
|
|
description: mcpServerDescriptionSchema,
|
|
enabled: z.boolean(),
|
|
allowDynamicTools: z.boolean(),
|
|
assignments: capabilityAssignmentsSchema,
|
|
secretConfigured: z.boolean(),
|
|
transport: z.literal('http'),
|
|
url: mcpRemoteUrlSchema
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
id: mcpServerIdSchema,
|
|
name: mcpServerNameSchema,
|
|
description: mcpServerDescriptionSchema,
|
|
enabled: z.boolean(),
|
|
allowDynamicTools: z.boolean(),
|
|
assignments: capabilityAssignmentsSchema,
|
|
secretConfigured: z.boolean(),
|
|
transport: z.literal('sse'),
|
|
url: mcpRemoteUrlSchema
|
|
})
|
|
.strict()
|
|
])
|
|
export type McpServerSummary = z.infer<typeof mcpServerSummarySchema>
|
|
|
|
export const webSearchCapabilitySchema = z
|
|
.object({
|
|
provider: z.literal('exa'),
|
|
enabled: z.boolean(),
|
|
availableIn: z.tuple([z.literal('ask'), z.literal('execute')]),
|
|
tools: z.tuple([
|
|
z.literal('web_search'),
|
|
z.literal('web_fetch')
|
|
])
|
|
})
|
|
.strict()
|
|
export type WebSearchCapability = z.infer<
|
|
typeof webSearchCapabilitySchema
|
|
>
|
|
|
|
export const capabilitySnapshotSchema = z
|
|
.object({
|
|
skills: z.array(skillSummarySchema).max(256),
|
|
builtinMcpServers: z
|
|
.array(builtinMcpServerStateSummarySchema)
|
|
.max(4)
|
|
.optional(),
|
|
mcpServers: z.array(mcpServerSummarySchema).max(64),
|
|
webSearch: webSearchCapabilitySchema.optional(),
|
|
computerCapabilities: z
|
|
.array(computerCapabilityConfigSummarySchema)
|
|
.max(2)
|
|
.optional(),
|
|
browserProfiles: browserProfilesSummarySchema.optional(),
|
|
warnings: settingsWarningsSchema.optional()
|
|
})
|
|
.strict()
|
|
export type CapabilitySnapshot = z.infer<typeof capabilitySnapshotSchema>
|
|
|
|
export const mcpServerTestResultSchema = z
|
|
.object({
|
|
serverName: z.string().min(1).max(120).optional(),
|
|
serverVersion: z.string().min(1).max(64).optional(),
|
|
dynamicToolsSupported: z.boolean(),
|
|
toolCount: z.number().int().min(0).max(10_000),
|
|
tools: z
|
|
.array(
|
|
z
|
|
.object({
|
|
name: z.string().min(1).max(128),
|
|
description: z.string().max(500).optional()
|
|
})
|
|
.strict()
|
|
)
|
|
.max(100),
|
|
promptCount: z.number().int().min(0).max(10_000).optional(),
|
|
prompts: z
|
|
.array(
|
|
z
|
|
.object({
|
|
name: z.string().min(1).max(128),
|
|
description: z.string().max(500).optional(),
|
|
arguments: z
|
|
.array(
|
|
z
|
|
.object({
|
|
name: z.string().min(1).max(128),
|
|
description: z.string().max(500).optional(),
|
|
required: z.boolean()
|
|
})
|
|
.strict()
|
|
)
|
|
.max(32)
|
|
})
|
|
.strict()
|
|
)
|
|
.max(100)
|
|
.optional(),
|
|
resourceCount: z.number().int().min(0).max(10_000).optional(),
|
|
resources: z
|
|
.array(
|
|
z
|
|
.object({
|
|
name: z.string().min(1).max(200),
|
|
uri: z.string().min(1).max(2_048),
|
|
description: z.string().max(500).optional(),
|
|
mimeType: z.string().min(1).max(200).optional()
|
|
})
|
|
.strict()
|
|
)
|
|
.max(100)
|
|
.optional(),
|
|
promptsSupported: z.boolean().optional(),
|
|
resourcesSupported: z.boolean().optional()
|
|
})
|
|
.strict()
|
|
export type McpServerTestResult = z.infer<
|
|
typeof mcpServerTestResultSchema
|
|
>
|
|
|
|
export const webSearchTestResultSchema = z
|
|
.object({
|
|
provider: z.literal('exa'),
|
|
query: z.string().min(1).max(120),
|
|
durationMs: z.number().int().min(0),
|
|
preview: z.string().min(1).max(500)
|
|
})
|
|
.strict()
|
|
export type WebSearchTestResult = z.infer<
|
|
typeof webSearchTestResultSchema
|
|
>
|