375 lines
9.9 KiB
TypeScript
375 lines
9.9 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 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),
|
|
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)
|
|
})
|
|
.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
|
|
>
|