DeepSeek Harness: Plugin Configuration

Last updated: 2026-08-31

Configuration is a plugin's "control knob" — API keys, timeouts, feature toggles, these environment-specific parameters shouldn't be hardcoded but externalized through a configuration system. Schemastery is Cordis's declarative configuration framework: define once, automatically get type safety, UI forms, validation, and documentation.

💡 Tip: Schemastery's core philosophy is "declaration as documentation" — you define the configuration structure with Schema, and the framework auto-generates Web UI forms and validation logic. Changing configuration doesn't require changing code.

📋 Prerequisites: Completed 15-define-tool.md, able to write basic tools

1. What You'll Learn


Config HMR

2. Schemastery Declarative Configuration

(1) Basic Concept

Schemastery is Cordis's built-in Schema definition library, inspired by Zod and JSON Schema, but designed specifically for interactive configuration.

TYPESCRIPT
import { Schema } from '@deepseek-ai/cordis'

export const Config = Schema.object({
  apiKey: Schema.string().required().description('API key for the service'),
  maxRetries: Schema.number().default(3).description('Maximum retry attempts'),
  debug: Schema.boolean().default(false).description('Enable debug logging')
})

(2) Comparison with JSON Schema

Dimension Schemastery JSON Schema
Syntax Chainable API JSON object
Type inference ✅ Automatic ❌ Manual
UI forms ✅ Auto-generated ❌ Extra tooling needed
Default values .default() default field
Descriptions .description() description field
Validation Chainable validators pattern/min/max etc.

(3) ▶ Example 3

TYPESCRIPT
export const Config = Schema.object({
  apiKey: Schema.string().required(),
  maxRetries: Schema.number().default(3)
})

export const name = 'my-plugin'

export function apply(ctx: Context) {
  // ctx.config type is automatically inferred
  ctx.logger.info(`API key: ${ctx.config.apiKey}`)
  ctx.logger.info(`Max retries: ${ctx.config.maxRetries}`)
}

The framework validates user-provided configuration values and injects them into ctx.config.


3. Schema Types

(1) ▶ Example 1

TYPESCRIPT
Schema.string()                           // Any string
Schema.string().required()                // Required
Schema.string().default('hello')          // Default value
Schema.string().description('Your name')  // Description
Schema.string().pattern(/^[a-z]+$/)       // Regex validation
Schema.string().min(1).max(100)           // Length limits

(2) ▶ Example 2

TYPESCRIPT
Schema.number()                    // Any number
Schema.number().default(0)         // Default value
Schema.number().min(0).max(100)    // Range limits
Schema.number().step(1)            // Step (for UI sliders)
Schema.integer()                   // Integer

(3) Schema.boolean

TYPESCRIPT
Schema.boolean()                   // Boolean
Schema.boolean().default(false)    // Default false

(4) Schema.object

TYPESCRIPT
Schema.object({
  host: Schema.string().default('localhost'),
  port: Schema.number().default(5432),
  ssl: Schema.boolean().default(false)
})

(5) Schema.array

TYPESCRIPT
Schema.array(Schema.string())                          // String array
Schema.array(Schema.string()).default([])              // Default empty array
Schema.array(Schema.object({                           // Object array
  name: Schema.string().required(),
  url: Schema.string().required()
}))

(6) Schema.union / Schema.const

TYPESCRIPT
// Enum values
Schema.union(['read', 'write', 'execute'])

// Constant
Schema.const('fixed-value')

// Enum with descriptions
Schema.union([
  Schema.const('read').description('Read-only access'),
  Schema.const('write').description('Read and write access'),
  Schema.const('execute').description('Full access')
])

(7) Schema.dict

TYPESCRIPT
// Dictionary type
Schema.dict(
  Schema.string(),           // Value type
  Schema.string()            // Key type (optional)
)

4. Chainable Modifiers

(1) .required()

Marks as required. When the user doesn't provide it, the framework reports an error and blocks loading.

TYPESCRIPT
apiKey: Schema.string().required()

(2) .default(value)

Sets a default value. When the user doesn't provide it, the default is used without error.

TYPESCRIPT
maxRetries: Schema.number().default(3)
timeout: Schema.number().default(30000)

(3) .description(text)

Adds a description to a configuration item, displayed in the Web UI as hint text for form labels.

TYPESCRIPT
apiKey: Schema.string().required()
  .description('API key obtained from the service dashboard')

(4) .min() / .max()

Numeric range or string length limits:

TYPESCRIPT
port: Schema.number().min(1).max(65535).default(8080)
name: Schema.string().min(1).max(50)

(5) .pattern()

Regex validation:

TYPESCRIPT
email: Schema.string().pattern(/^[^@]+@[^@]+\.[^@]+$/)
  .description('Valid email address')

(6) .step()

Numeric step, affects UI slider precision:

TYPESCRIPT
timeout: Schema.number().min(1000).max(300000).step(1000).default(30000)

(7) Combined Usage

TYPESCRIPT
const Config = Schema.object({
  apiKey: Schema.string()
    .required()
    .pattern(/^sk-[a-zA-Z0-9]+$/)
    .description('DeepSeek API key (starts with sk-)'),
  
  maxTokens: Schema.number()
    .min(1).max(32768)
    .default(4096)
    .description('Maximum tokens per request'),
  
  model: Schema.union(['deepseek-chat', 'deepseek-reasoner'])
    .default('deepseek-chat')
    .description('Model to use'),
  
  temperature: Schema.number()
    .min(0).max(2).step(0.1)
    .default(0.7)
    .description('Sampling temperature')
})

5. Nested Configuration

(1) Nested Objects

TYPESCRIPT
const Config = Schema.object({
  database: Schema.object({
    host: Schema.string().default('localhost'),
    port: Schema.number().default(5432),
    name: Schema.string().required(),
    ssl: Schema.boolean().default(false)
  }).default({ host: 'localhost', port: 5432, ssl: false }),

  cache: Schema.object({
    enabled: Schema.boolean().default(true),
    ttl: Schema.number().default(3600).description('Cache TTL in seconds')
  }).default({ enabled: true, ttl: 3600 })
})

(2) Deep Nesting

TYPESCRIPT
const Config = Schema.object({
  llm: Schema.object({
    provider: Schema.union(['deepseek', 'openai']).default('deepseek'),
    deepseek: Schema.object({
      apiKey: Schema.string().required(),
      model: Schema.string().default('deepseek-chat')
    }),
    openai: Schema.object({
      apiKey: Schema.string(),
      endpoint: Schema.string().default('https://api.openai.com/v1')
    })
  })
})

(3) Reusable Schema

TYPESCRIPT
const ConnectionSchema = Schema.object({
  host: Schema.string().default('localhost'),
  port: Schema.number().default(5432),
  timeout: Schema.number().default(5000)
})

const Config = Schema.object({
  primary: ConnectionSchema.description('Primary connection'),
  replica: ConnectionSchema.description('Replica connection')
})

6. Configuration Display in the Web UI Settings Page

(1) Auto Form Generation

Config defined with Schemastery automatically generates forms in the Web UI settings page:

Schema Type UI Control
Schema.string() Text input
Schema.number() Number input / slider
Schema.boolean() Toggle switch
Schema.union() Dropdown select
Schema.array() List editor
Schema.object() Grouped panel
Schema.dict() Key-value editor

(2) Description Display

Each field's .description() appears as hint text below the input:

TEXT 📖 Display only
┌─────────────────────────────────────────┐
│ API Key                                 │
│ [sk-xxxxxxxxxxxxxxx                   ] │
│ DeepSeek API key (starts with sk-)      │
├─────────────────────────────────────────┤
│ Max Tokens                              │
│ [4096                                 ] │
│ Maximum tokens per request              │
└─────────────────────────────────────────┘

(3) Validation Feedback

When user input doesn't match the Schema, the UI shows immediate feedback:

TEXT 📖 Display only
┌─────────────────────────────────────────┐
│ API Key                                 │
│ [invalid-key                          ] │
│ ❌ Must match pattern: /^sk-[a-zA-Z0-9]+$/ │
└─────────────────────────────────────────┘

7. Configuration Validation and Error Messages

(1) Startup Validation

When a plugin loads, the framework automatically validates its configuration:

TYPESCRIPT
const Config = Schema.object({
  port: Schema.number().min(1).max(65535)
})

// User config: port: -1
// → Error: Invalid config for plugin my-plugin:
//   port: must be >= 1

When validation fails, the plugin isn't loaded and the error is output to the terminal.

(2) Type Safety

TypeScript automatically infers ctx.config's type from the Config definition:

TYPESCRIPT
const Config = Schema.object({
  apiKey: Schema.string().required(),
  maxRetries: Schema.number().default(3)
})

export function apply(ctx: Context) {
  ctx.config.apiKey     // string ✅
  ctx.config.maxRetries // number ✅
  ctx.config.unknown    // Type error ❌
}

(3) Custom Validation

TYPESCRIPT
const Config = Schema.object({
  startDate: Schema.string().required(),
  endDate: Schema.string().required()
}).validate((value) => {
  if (new Date(value.startDate) > new Date(value.endDate)) {
    throw new Error('startDate must be before endDate')
  }
  return value
})

8. Dynamic Configuration Updates

(1) Configuration Change Listening

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.on('config/updated', (newConfig) => {
    ctx.logger.info('config updated:', newConfig)
    // Adjust behavior based on new config
  })
}

(2) Hot Update Flow

100%
graph LR
    UI[User modifies config] --> VALID[Schema validation]
    VALID --> APPLY[Apply new config]
    APPLY --> EMIT[Emit config/updated]
    EMIT --> RELOAD[Plugin responds to update]

(3) Configs Requiring Restart

Some configuration changes (like port numbers or dependency injection) require a restart:

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.on('config/updated', (config) => {
    if (config.port !== ctx.config.port) {
      ctx.logger.warn('port change requires restart')
    }
  })
}

❓ FAQ

Q Must Config be exported with the name Config?
A Yes, the framework convention looks for export const Config. Other names won't be recognized.
Q Can I dynamically add configuration items at runtime?
A Not recommended. Config should be defined before plugin loading. If you need dynamic behavior, use ctx.on('config/updated') to respond to configuration changes.
Q How to organize many configuration items?
A Use nested Schema.object for grouping, each group with a clear description. The Web UI renders nested objects as collapsible panels.
Q How to protect sensitive information like API keys?
A Schemastery supports the .hidden() modifier, which displays a password field in the UI: typescript apiKey: Schema.string().required().hidden()
Q Can configuration values be functions?
A No. Configuration must be JSON-serializable values (string/number/boolean/object/array). Function behavior should be implemented in apply.
Q Will multiple plugins' configurations conflict?
A No. Each plugin has an independent configuration namespace plugins.plugin-name.config, isolated from each other.

📖 Summary


📝 Exercises

1. ⭐ Basic: Add Config to the file_count tool with defaultPath (string, default current directory) and maxDepth (number, default 10). Verify form generation in the Web UI settings page.

2. ⭐⭐ Intermediate: Write a multi-connection configuration plugin. Config contains connections (array of objects, each with host/port/username/password), defined with nested Schema.object. Verify: errors when required fields are missing, default values fill correctly.

3. ⭐⭐⭐ Challenge: Create a configuration with custom validation: startDate and endDate must satisfy startDate < endDate; maxRetries must be a positive integer. Test: input invalid values and confirm validation error messages display correctly in the Web UI.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏