feat: add natural language configuration tools

This commit is contained in:
lofyer
2026-08-14 01:23:32 +08:00
parent 445ebbc002
commit 7a86c96623
19 changed files with 3284 additions and 119 deletions
+15
View File
@@ -4,6 +4,7 @@ import {
knowledgeScopedDataTools,
magicNoteScopedDataTools
} from './scoped-data-tools'
import { goodbuddyConfigTools } from './goodbuddy-config-tools'
export type BuiltinMcpServerSummary = {
id: string
@@ -49,5 +50,19 @@ export const builtinMcpServers = [
access: 'mixed',
authorization: 'conversation-scoped',
requiresFeature: 'magic-notes'
},
{
id: 'goodbuddy-config',
name: 'GoodBuddy 配置',
description:
'发现常见配置示例,读取脱敏配置,并通过计划和原生确认管理应用偏好、Skills 与 MCP。',
tools: goodbuddyConfigTools.map(({ name, summary, access }) => ({
name,
description: summary,
access
})),
assignments: ['model', 'opencode', 'continue'],
access: 'mixed',
authorization: 'conversation-scoped'
}
] as const satisfies readonly BuiltinMcpServerSummary[]
@@ -0,0 +1,227 @@
import { describe, expect, it } from 'vitest'
import {
goodbuddyConfigApplyInputSchema,
goodbuddyConfigCapabilities,
goodbuddyConfigCapabilitiesOutputSchema,
goodbuddyConfigCommonExamples,
goodbuddyConfigGetOutputSchema,
goodbuddyConfigOperationNameSchema,
goodbuddyConfigOperationSchema,
goodbuddyConfigPlanInputSchema,
goodbuddyConfigPlanOutputSchema
} from './goodbuddy-config-contracts'
import {
goodbuddyConfigToolByName,
goodbuddyConfigTools
} from './goodbuddy-config-tools'
describe('GoodBuddy configuration contracts', () => {
it('publishes one valid generated example for every operation', () => {
const operationNames = goodbuddyConfigOperationNameSchema.options
expect(goodbuddyConfigCommonExamples).toHaveLength(
operationNames.length
)
expect(
goodbuddyConfigCommonExamples.map(({ operation }) => operation)
).toEqual(operationNames)
for (const example of goodbuddyConfigCommonExamples) {
expect(goodbuddyConfigOperationSchema.parse(example)).toEqual(
example
)
}
expect(
goodbuddyConfigCapabilitiesOutputSchema.parse(
goodbuddyConfigCapabilities
)
).toEqual(goodbuddyConfigCapabilities)
})
it('accepts a bounded sequence of strongly typed operations', () => {
const input = {
operations: [
{
operation: 'application.update',
updates: {
magicNotesEnabled: true,
magicNoteCommentMode: 'after-save-manual'
}
},
{
operation: 'skill.setAssignments',
skillId: 'document-writing',
assignments: ['model', 'continue']
},
{
operation: 'mcp.add',
connection: {
name: 'Project MCP',
description: '',
allowDynamicTools: false,
transport: 'http',
url: 'https://mcp.example.com/tools'
},
enabled: false,
assignments: ['model']
}
]
}
expect(goodbuddyConfigPlanInputSchema.parse(input)).toEqual(input)
})
it('keeps custom MCP assignments limited to the direct model', () => {
expect(() =>
goodbuddyConfigOperationSchema.parse({
operation: 'mcp.add',
connection: {
name: 'Project MCP',
description: '',
allowDynamicTools: false,
transport: 'http',
url: 'https://mcp.example.com/tools'
},
enabled: true,
assignments: ['continue']
})
).toThrow()
})
it('never accepts MCP secrets or credential-bearing remote URLs', () => {
const baseConnection = {
name: 'Project MCP',
description: '',
allowDynamicTools: false,
transport: 'http',
url: 'https://mcp.example.com/tools'
}
expect(() =>
goodbuddyConfigOperationSchema.parse({
operation: 'mcp.add',
connection: {
...baseConnection,
secret: { action: 'replace', value: 'do-not-accept' }
},
enabled: true,
assignments: ['model']
})
).toThrow()
expect(() =>
goodbuddyConfigOperationSchema.parse({
operation: 'mcp.update',
serverId: '00000000-0000-4000-8000-000000000001',
connection: {
...baseConnection,
url: 'https://user:password@mcp.example.com/tools'
}
})
).toThrow()
expect(() =>
goodbuddyConfigOperationSchema.parse({
operation: 'mcp.update',
serverId: '00000000-0000-4000-8000-000000000001',
connection: {
...baseConnection,
url: 'https://mcp.example.com/tools?token=secret'
}
})
).toThrow()
})
it('only exposes redacted MCP summaries from get', () => {
const snapshot = {
application: {
checkUpdatesOnStartup: true,
magicNotesEnabled: true,
magicNoteCommentMode: 'immediate',
magicNoteCommentFormat: 'combined'
},
skills: [],
mcpServers: [
{
id: '00000000-0000-4000-8000-000000000001',
name: 'Project MCP',
description: '',
enabled: true,
allowDynamicTools: false,
assignments: ['model'],
secretConfigured: true,
transport: 'http'
}
]
}
expect(goodbuddyConfigGetOutputSchema.parse(snapshot)).toEqual(
snapshot
)
expect(() =>
goodbuddyConfigGetOutputSchema.parse({
...snapshot,
mcpServers: [
{
...snapshot.mcpServers[0],
url: 'https://mcp.example.com?token=secret',
secret: 'secret'
}
]
})
).toThrow()
})
it('requires an expiring, approved plan and applies only its ID', () => {
const operation = goodbuddyConfigCommonExamples[0]!
expect(
goodbuddyConfigPlanOutputSchema.parse({
planId: '00000000-0000-4000-8000-000000000002',
expiresAt: '2030-01-01T00:00:00.000Z',
operations: [operation],
steps: [
{
index: 0,
operation: operation.operation,
summary: 'Update startup checks.',
risk: 'low',
reload: 'none',
destructive: false
}
],
overallRisk: 'low',
reload: 'none',
requiresApproval: true
}).requiresApproval
).toBe(true)
expect(
goodbuddyConfigApplyInputSchema.parse({
planId: '00000000-0000-4000-8000-000000000002'
})
).toEqual({
planId: '00000000-0000-4000-8000-000000000002'
})
expect(() =>
goodbuddyConfigApplyInputSchema.parse({
planId: '00000000-0000-4000-8000-000000000002',
operations: [operation]
})
).toThrow()
})
it('catalogs the four request-scoped MCP tools', () => {
expect(goodbuddyConfigTools.map(({ name }) => name)).toEqual([
'goodbuddy_config_capabilities',
'goodbuddy_config_get',
'goodbuddy_config_plan',
'goodbuddy_config_apply'
])
expect(
goodbuddyConfigToolByName.get('goodbuddy_config_apply')?.access
).toBe(
'write'
)
expect(
goodbuddyConfigTools
.filter(({ name }) => name !== 'goodbuddy_config_apply')
.every(({ access }) => access === 'read')
).toBe(true)
})
})
+554
View File
@@ -0,0 +1,554 @@
import { z } from 'zod'
import {
applicationSettingsSchema,
applicationSettingsUpdateSchema
} from './application-settings-contracts'
import {
capabilityAssignmentsSchema,
mcpServerIdSchema,
mcpTransportSchema,
skillIdSchema,
skillSummarySchema
} from './capability-contracts'
export const GOODBUDDY_CONFIG_MAX_OPERATIONS = 32
const boundedPathSchema = z
.string()
.trim()
.min(1)
.max(4_096)
.refine(
(value) =>
[...value].every((character) => {
const code = character.charCodeAt(0)
return code > 31 && code !== 127
}),
'Path contains control characters'
)
const mcpNameSchema = z.string().trim().min(1).max(80)
const mcpDescriptionSchema = z.string().trim().max(500)
const mcpCommandSchema = z.string().trim().min(1).max(4_096)
const mcpArgumentSchema = z.string().trim().min(1).max(4_096)
const publicMcpUrlSchema = z
.string()
.url()
.max(2_048)
.superRefine((value, context) => {
const url = new URL(value)
if (!['http:', 'https:'].includes(url.protocol)) {
context.addIssue({
code: 'custom',
message: 'MCP URL must use HTTP or HTTPS'
})
}
if (url.username || url.password || url.search || url.hash) {
context.addIssue({
code: 'custom',
message:
'MCP URL must not contain credentials, query parameters, or fragments'
})
}
})
const mcpConnectionShape = {
name: mcpNameSchema,
description: mcpDescriptionSchema,
allowDynamicTools: z.boolean()
}
const directModelAssignmentsSchema = z
.array(z.literal('model'))
.max(1)
/**
* Secret-free MCP connection settings accepted from a model. Authentication
* material is intentionally absent and must be managed through the trusted UI.
*/
export const goodbuddyConfigMcpConnectionSchema = z.discriminatedUnion(
'transport',
[
z
.object({
...mcpConnectionShape,
transport: z.literal('stdio'),
command: mcpCommandSchema,
args: z.array(mcpArgumentSchema).max(64)
})
.strict(),
z
.object({
...mcpConnectionShape,
transport: z.literal('http'),
url: publicMcpUrlSchema
})
.strict(),
z
.object({
...mcpConnectionShape,
transport: z.literal('sse'),
url: publicMcpUrlSchema
})
.strict()
]
)
export type GoodBuddyConfigMcpConnection = z.infer<
typeof goodbuddyConfigMcpConnectionSchema
>
const applicationUpdateOperationSchema = z
.object({
operation: z.literal('application.update'),
updates: applicationSettingsUpdateSchema
})
.strict()
const skillImportOperationSchema = z
.object({
operation: z.literal('skill.import'),
sourcePath: boundedPathSchema,
enabled: z.boolean(),
assignments: capabilityAssignmentsSchema
})
.strict()
const skillSetEnabledOperationSchema = z
.object({
operation: z.literal('skill.setEnabled'),
skillId: skillIdSchema,
enabled: z.boolean()
})
.strict()
const skillSetAssignmentsOperationSchema = z
.object({
operation: z.literal('skill.setAssignments'),
skillId: skillIdSchema,
assignments: capabilityAssignmentsSchema
})
.strict()
const skillRemoveOperationSchema = z
.object({
operation: z.literal('skill.remove'),
skillId: skillIdSchema
})
.strict()
const mcpAddOperationSchema = z
.object({
operation: z.literal('mcp.add'),
connection: goodbuddyConfigMcpConnectionSchema,
enabled: z.boolean(),
assignments: directModelAssignmentsSchema
})
.strict()
const mcpUpdateOperationSchema = z
.object({
operation: z.literal('mcp.update'),
serverId: mcpServerIdSchema,
connection: goodbuddyConfigMcpConnectionSchema
})
.strict()
const mcpSetEnabledOperationSchema = z
.object({
operation: z.literal('mcp.setEnabled'),
serverId: mcpServerIdSchema,
enabled: z.boolean()
})
.strict()
const mcpSetAssignmentsOperationSchema = z
.object({
operation: z.literal('mcp.setAssignments'),
serverId: mcpServerIdSchema,
assignments: directModelAssignmentsSchema
})
.strict()
const mcpRemoveOperationSchema = z
.object({
operation: z.literal('mcp.remove'),
serverId: mcpServerIdSchema
})
.strict()
export const goodbuddyConfigOperationSchema = z.discriminatedUnion(
'operation',
[
applicationUpdateOperationSchema,
skillImportOperationSchema,
skillSetEnabledOperationSchema,
skillSetAssignmentsOperationSchema,
skillRemoveOperationSchema,
mcpAddOperationSchema,
mcpUpdateOperationSchema,
mcpSetEnabledOperationSchema,
mcpSetAssignmentsOperationSchema,
mcpRemoveOperationSchema
]
)
export type GoodBuddyConfigOperation = z.infer<
typeof goodbuddyConfigOperationSchema
>
export const goodbuddyConfigOperationNameSchema = z.enum([
'application.update',
'skill.import',
'skill.setEnabled',
'skill.setAssignments',
'skill.remove',
'mcp.add',
'mcp.update',
'mcp.setEnabled',
'mcp.setAssignments',
'mcp.remove'
])
export type GoodBuddyConfigOperationName = z.infer<
typeof goodbuddyConfigOperationNameSchema
>
export const goodbuddyConfigRiskSchema = z.enum([
'low',
'medium',
'high'
])
export type GoodBuddyConfigRisk = z.infer<
typeof goodbuddyConfigRiskSchema
>
export const goodbuddyConfigReloadSchema = z.enum([
'none',
'after-current-request'
])
export type GoodBuddyConfigReload = z.infer<
typeof goodbuddyConfigReloadSchema
>
type OperationRegistry = {
[Name in GoodBuddyConfigOperationName]: {
summary: string
risk: GoodBuddyConfigRisk
reload: GoodBuddyConfigReload
destructive: boolean
exampleRequest: string
example: Extract<GoodBuddyConfigOperation, { operation: Name }>
}
}
export const goodbuddyConfigOperationRegistry = {
'application.update': {
summary: 'Update one or more public application preferences.',
risk: 'low',
reload: 'none',
destructive: false,
exampleRequest: '关闭启动时自动检查更新。',
example: {
operation: 'application.update',
updates: { checkUpdatesOnStartup: false }
}
},
'skill.import': {
summary: 'Import one Skill directory or ZIP from a local path.',
risk: 'high',
reload: 'after-current-request',
destructive: false,
exampleRequest: '导入当前工作区的 meeting-helper Skill。',
example: {
operation: 'skill.import',
sourcePath: './meeting-helper',
enabled: true,
assignments: ['model']
}
},
'skill.setEnabled': {
summary: 'Enable or disable an installed Skill.',
risk: 'medium',
reload: 'after-current-request',
destructive: false,
exampleRequest: '启用 meeting-helper Skill。',
example: {
operation: 'skill.setEnabled',
skillId: 'meeting-helper',
enabled: true
}
},
'skill.setAssignments': {
summary: 'Choose the runtimes that can use an installed Skill.',
risk: 'medium',
reload: 'after-current-request',
destructive: false,
exampleRequest: '让 meeting-helper 可用于直连模型和 OpenCode。',
example: {
operation: 'skill.setAssignments',
skillId: 'meeting-helper',
assignments: ['model', 'opencode']
}
},
'skill.remove': {
summary: 'Permanently remove an imported Skill.',
risk: 'high',
reload: 'after-current-request',
destructive: true,
exampleRequest: '删除已导入的 meeting-helper Skill。',
example: {
operation: 'skill.remove',
skillId: 'meeting-helper'
}
},
'mcp.add': {
summary: 'Add a secret-free MCP server configuration.',
risk: 'high',
reload: 'after-current-request',
destructive: false,
exampleRequest:
'添加一个使用 npx 启动的本地 MCP,先保持禁用,只分配给直连模型。',
example: {
operation: 'mcp.add',
connection: {
name: 'Local tools',
description: 'Local project tools',
allowDynamicTools: false,
transport: 'stdio',
command: 'npx',
args: ['-y', '@example/mcp-server']
},
enabled: false,
assignments: ['model']
}
},
'mcp.update': {
summary: 'Replace the public connection settings for an MCP server.',
risk: 'high',
reload: 'after-current-request',
destructive: false,
exampleRequest: '把 Project tools MCP 的地址改为新的 HTTPS 地址。',
example: {
operation: 'mcp.update',
serverId: '00000000-0000-4000-8000-000000000001',
connection: {
name: 'Project tools',
description: 'Tools served on the local network',
allowDynamicTools: true,
transport: 'http',
url: 'https://mcp.example.com/tools'
}
}
},
'mcp.setEnabled': {
summary: 'Enable or disable a configured MCP server.',
risk: 'high',
reload: 'after-current-request',
destructive: false,
exampleRequest: '启用指定的 MCP Server。',
example: {
operation: 'mcp.setEnabled',
serverId: '00000000-0000-4000-8000-000000000001',
enabled: true
}
},
'mcp.setAssignments': {
summary: 'Choose the runtimes that can use an MCP server.',
risk: 'high',
reload: 'after-current-request',
destructive: false,
exampleRequest: '只把指定 MCP Server 分配给直连模型。',
example: {
operation: 'mcp.setAssignments',
serverId: '00000000-0000-4000-8000-000000000001',
assignments: ['model']
}
},
'mcp.remove': {
summary: 'Permanently remove an MCP server configuration.',
risk: 'high',
reload: 'after-current-request',
destructive: true,
exampleRequest: '删除指定的 MCP Server 配置。',
example: {
operation: 'mcp.remove',
serverId: '00000000-0000-4000-8000-000000000001'
}
}
} as const satisfies OperationRegistry
export const goodbuddyConfigCommonExamples =
goodbuddyConfigOperationNameSchema.options.map(
(operation) => goodbuddyConfigOperationRegistry[operation].example
)
export const goodbuddyConfigOperationDescriptorSchema = z
.object({
operation: goodbuddyConfigOperationNameSchema,
summary: z.string().min(1).max(240),
risk: goodbuddyConfigRiskSchema,
reload: goodbuddyConfigReloadSchema,
destructive: z.boolean(),
exampleRequest: z.string().min(1).max(240),
example: goodbuddyConfigOperationSchema
})
.strict()
export type GoodBuddyConfigOperationDescriptor = z.infer<
typeof goodbuddyConfigOperationDescriptorSchema
>
export const goodbuddyConfigOperationDescriptors =
goodbuddyConfigOperationNameSchema.options.map((operation) => ({
operation,
...goodbuddyConfigOperationRegistry[operation]
}))
export const goodbuddyConfigCapabilitiesInputSchema = z
.object({})
.strict()
export type GoodBuddyConfigCapabilitiesInput = z.infer<
typeof goodbuddyConfigCapabilitiesInputSchema
>
export const goodbuddyConfigCapabilitiesOutputSchema = z
.object({
server: z.literal('goodbuddy_config'),
version: z.literal(1),
authorization: z.literal('request-scoped'),
secretPolicy: z.literal('never-exposed-or-accepted'),
applyRequiresApproval: z.literal(true),
operations: z.array(goodbuddyConfigOperationDescriptorSchema).length(10)
})
.strict()
export type GoodBuddyConfigCapabilitiesOutput = z.infer<
typeof goodbuddyConfigCapabilitiesOutputSchema
>
export const goodbuddyConfigCapabilities = {
server: 'goodbuddy_config',
version: 1,
authorization: 'request-scoped',
secretPolicy: 'never-exposed-or-accepted',
applyRequiresApproval: true,
operations: goodbuddyConfigOperationDescriptors
} as const satisfies GoodBuddyConfigCapabilitiesOutput
/**
* Deliberately omits commands, arguments, URLs, and credential values.
*/
export const goodbuddyConfigMcpSummarySchema = z
.object({
id: mcpServerIdSchema,
name: mcpNameSchema,
description: mcpDescriptionSchema,
enabled: z.boolean(),
allowDynamicTools: z.boolean(),
assignments: capabilityAssignmentsSchema,
secretConfigured: z.boolean(),
transport: mcpTransportSchema
})
.strict()
export type GoodBuddyConfigMcpSummary = z.infer<
typeof goodbuddyConfigMcpSummarySchema
>
export const goodbuddyConfigSnapshotSchema = z
.object({
application: applicationSettingsSchema,
skills: z.array(skillSummarySchema).max(256),
mcpServers: z.array(goodbuddyConfigMcpSummarySchema).max(64)
})
.strict()
export type GoodBuddyConfigSnapshot = z.infer<
typeof goodbuddyConfigSnapshotSchema
>
export const goodbuddyConfigGetInputSchema = z.object({}).strict()
export type GoodBuddyConfigGetInput = z.infer<
typeof goodbuddyConfigGetInputSchema
>
export const goodbuddyConfigGetOutputSchema =
goodbuddyConfigSnapshotSchema
export type GoodBuddyConfigGetOutput = GoodBuddyConfigSnapshot
export const goodbuddyConfigPlanInputSchema = z
.object({
operations: z
.array(goodbuddyConfigOperationSchema)
.min(1)
.max(GOODBUDDY_CONFIG_MAX_OPERATIONS)
})
.strict()
export type GoodBuddyConfigPlanInput = z.infer<
typeof goodbuddyConfigPlanInputSchema
>
export const goodbuddyConfigPlanStepSchema = z
.object({
index: z.number().int().nonnegative(),
operation: goodbuddyConfigOperationNameSchema,
summary: z.string().min(1).max(12_000),
risk: goodbuddyConfigRiskSchema,
reload: goodbuddyConfigReloadSchema,
destructive: z.boolean()
})
.strict()
export type GoodBuddyConfigPlanStep = z.infer<
typeof goodbuddyConfigPlanStepSchema
>
export const goodbuddyConfigPlanOutputSchema = z
.object({
planId: z.string().uuid(),
expiresAt: z.string().datetime(),
operations: z
.array(goodbuddyConfigOperationSchema)
.min(1)
.max(GOODBUDDY_CONFIG_MAX_OPERATIONS),
steps: z
.array(goodbuddyConfigPlanStepSchema)
.min(1)
.max(GOODBUDDY_CONFIG_MAX_OPERATIONS),
overallRisk: goodbuddyConfigRiskSchema,
reload: goodbuddyConfigReloadSchema,
requiresApproval: z.literal(true)
})
.strict()
.superRefine((value, context) => {
if (value.operations.length !== value.steps.length) {
context.addIssue({
code: 'custom',
message: 'Plan operations and steps must have the same length'
})
}
})
export type GoodBuddyConfigPlanOutput = z.infer<
typeof goodbuddyConfigPlanOutputSchema
>
export const goodbuddyConfigApplyInputSchema = z
.object({
planId: z.string().uuid()
})
.strict()
export type GoodBuddyConfigApplyInput = z.infer<
typeof goodbuddyConfigApplyInputSchema
>
export const goodbuddyConfigApplyOutputSchema = z
.object({
planId: z.string().uuid(),
status: z.enum(['applied', 'partially-applied']),
appliedOperations: z
.number()
.int()
.min(1)
.max(GOODBUDDY_CONFIG_MAX_OPERATIONS),
reload: goodbuddyConfigReloadSchema,
snapshot: goodbuddyConfigSnapshotSchema,
error: z.string().min(1).max(2_000).optional()
})
.strict()
export type GoodBuddyConfigApplyOutput = z.infer<
typeof goodbuddyConfigApplyOutputSchema
>
+91
View File
@@ -0,0 +1,91 @@
import { z } from 'zod'
import {
goodbuddyConfigApplyInputSchema,
goodbuddyConfigApplyOutputSchema,
goodbuddyConfigCapabilitiesInputSchema,
goodbuddyConfigCapabilitiesOutputSchema,
goodbuddyConfigGetInputSchema,
goodbuddyConfigGetOutputSchema,
goodbuddyConfigPlanInputSchema,
goodbuddyConfigPlanOutputSchema
} from './goodbuddy-config-contracts'
export type GoodBuddyConfigToolDefinition = {
name: string
title: string
description: string
summary: string
access: 'read' | 'write'
inputSchema: z.ZodType
outputSchema: z.ZodType
}
export const goodbuddyConfigToolCatalog = {
capabilities: {
name: 'goodbuddy_config_capabilities',
title: 'Discover GoodBuddy configuration capabilities',
description:
'List supported secret-free configuration operations, examples, risk levels, and reload effects. This tool does not read or change settings.',
summary: 'Discover supported configuration operations and examples.',
access: 'read',
inputSchema: goodbuddyConfigCapabilitiesInputSchema,
outputSchema: goodbuddyConfigCapabilitiesOutputSchema
},
get: {
name: 'goodbuddy_config_get',
title: 'Read sanitized GoodBuddy settings',
description:
'Read public application preferences, Skill summaries, and redacted MCP summaries for this request. Credentials and connection details are never returned.',
summary: 'Read sanitized application, Skill, and MCP settings.',
access: 'read',
inputSchema: goodbuddyConfigGetInputSchema,
outputSchema: goodbuddyConfigGetOutputSchema
},
plan: {
name: 'goodbuddy_config_plan',
title: 'Plan GoodBuddy configuration changes',
description:
'Validate and normalize a bounded sequence of strongly typed changes without applying it. The returned plan is scoped to this request and expires.',
summary: 'Validate configuration changes and inspect their effects.',
access: 'read',
inputSchema: goodbuddyConfigPlanInputSchema,
outputSchema: goodbuddyConfigPlanOutputSchema
},
apply: {
name: 'goodbuddy_config_apply',
title: 'Apply an approved GoodBuddy configuration plan',
description:
'Apply a previously planned request-scoped change after GoodBuddy approval controls authorize it. Raw operations and secrets are not accepted. If a later operation fails, the result reports partial application and the remaining operations are not attempted.',
summary: 'Apply one approved request-scoped configuration plan.',
access: 'write',
inputSchema: goodbuddyConfigApplyInputSchema,
outputSchema: goodbuddyConfigApplyOutputSchema
}
} as const satisfies Record<
'capabilities' | 'get' | 'plan' | 'apply',
GoodBuddyConfigToolDefinition
>
export const goodbuddyConfigToolKeys = [
'capabilities',
'get',
'plan',
'apply'
] as const
export const goodbuddyConfigTools = [
goodbuddyConfigToolCatalog.capabilities,
goodbuddyConfigToolCatalog.get,
goodbuddyConfigToolCatalog.plan,
goodbuddyConfigToolCatalog.apply
] as const satisfies readonly GoodBuddyConfigToolDefinition[]
export type GoodBuddyConfigToolName =
(typeof goodbuddyConfigTools)[number]['name']
export const goodbuddyConfigToolByName = new Map<
GoodBuddyConfigToolName,
(typeof goodbuddyConfigTools)[number]
>(
goodbuddyConfigTools.map((tool) => [tool.name, tool])
)
+15 -1
View File
@@ -1,4 +1,8 @@
import { z } from 'zod'
import {
goodbuddyConfigTools,
type GoodBuddyConfigToolName
} from './goodbuddy-config-tools'
export type ScopedDataToolAccess = 'read' | 'write'
@@ -230,10 +234,12 @@ export const magicNoteScopedDataTools = [
export const scopedDataTools = [
...knowledgeScopedDataTools,
...magicNoteScopedDataTools
...magicNoteScopedDataTools,
...goodbuddyConfigTools
] as const
export type ScopedDataToolName = (typeof scopedDataTools)[number]['name']
export type { GoodBuddyConfigToolName }
export const scopedDataToolByName = new Map<
ScopedDataToolName,
@@ -254,6 +260,14 @@ export const magicNoteWriteToolNames = magicNoteScopedDataTools
.filter((tool) => tool.access === 'write')
.map((tool) => tool.name)
export const goodbuddyConfigReadToolNames = goodbuddyConfigTools
.filter((tool) => tool.access === 'read')
.map((tool) => tool.name)
export const goodbuddyConfigWriteToolNames = goodbuddyConfigTools
.filter((tool) => tool.access === 'write')
.map((tool) => tool.name)
export const scopedReadToolNames = scopedDataTools
.filter((tool) => tool.access === 'read')
.map((tool) => tool.name)