feat(core): add mcp and compaction config schema
This commit is contained in:
parent
d9b4f7b84d
commit
864f54cc46
5 changed files with 167 additions and 9 deletions
|
|
@ -11,9 +11,11 @@ import { Policy } from "./policy"
|
|||
import { AbsolutePath } from "./schema"
|
||||
import { ConfigAgent } from "./config/agent"
|
||||
import { ConfigAttachments } from "./config/attachments"
|
||||
import { ConfigCompaction } from "./config/compaction"
|
||||
import { ConfigExperimental } from "./config/experimental"
|
||||
import { ConfigFormatter } from "./config/formatter"
|
||||
import { ConfigLSP } from "./config/lsp"
|
||||
import { ConfigMCP } from "./config/mcp"
|
||||
import { ConfigPlugin } from "./config/plugin"
|
||||
import { ConfigProvider } from "./config/provider"
|
||||
import { ConfigReference } from "./config/reference"
|
||||
|
|
@ -68,6 +70,12 @@ export class Info extends Schema.Class<Info>("Config.Info")({
|
|||
tool_output: ConfigToolOutput.Info.pipe(Schema.optional).annotate({
|
||||
description: "Tool output truncation thresholds",
|
||||
}),
|
||||
mcp: ConfigMCP.Info.pipe(Schema.optional).annotate({
|
||||
description: "MCP server configuration",
|
||||
}),
|
||||
compaction: ConfigCompaction.Info.pipe(Schema.optional).annotate({
|
||||
description: "Conversation compaction behavior",
|
||||
}),
|
||||
skills: Schema.String.pipe(Schema.Array, Schema.optional).annotate({
|
||||
description: "Additional paths or URLs to discover skills from",
|
||||
}),
|
||||
|
|
|
|||
16
packages/core/src/config/compaction.ts
Normal file
16
packages/core/src/config/compaction.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export * as ConfigCompaction from "./compaction"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { NonNegativeInt } from "../schema"
|
||||
|
||||
export class Keep extends Schema.Class<Keep>("ConfigV2.Compaction.Keep")({
|
||||
turns: NonNegativeInt.pipe(Schema.optional),
|
||||
tokens: NonNegativeInt.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class Info extends Schema.Class<Info>("ConfigV2.Compaction")({
|
||||
auto: Schema.Boolean.pipe(Schema.optional),
|
||||
prune: Schema.Boolean.pipe(Schema.optional),
|
||||
keep: Keep.pipe(Schema.optional),
|
||||
buffer: NonNegativeInt.pipe(Schema.optional),
|
||||
}) {}
|
||||
36
packages/core/src/config/mcp.ts
Normal file
36
packages/core/src/config/mcp.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export * as ConfigMCP from "./mcp"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { PositiveInt } from "../schema"
|
||||
|
||||
export class Local extends Schema.Class<Local>("ConfigV2.MCP.Local")({
|
||||
type: Schema.Literal("local"),
|
||||
command: Schema.String.pipe(Schema.Array),
|
||||
environment: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
timeout: PositiveInt.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class OAuth extends Schema.Class<OAuth>("ConfigV2.MCP.OAuth")({
|
||||
client_id: Schema.String.pipe(Schema.optional),
|
||||
client_secret: Schema.String.pipe(Schema.optional),
|
||||
scope: Schema.String.pipe(Schema.optional),
|
||||
callback_port: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })).pipe(Schema.optional),
|
||||
redirect_uri: Schema.String.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class Remote extends Schema.Class<Remote>("ConfigV2.MCP.Remote")({
|
||||
type: Schema.Literal("remote"),
|
||||
url: Schema.String,
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
|
||||
oauth: Schema.Union([OAuth, Schema.Literal(false)]).pipe(Schema.optional),
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
timeout: PositiveInt.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export const Server = Schema.Union([Local, Remote]).pipe(Schema.toTaggedUnion("type"))
|
||||
|
||||
export class Info extends Schema.Class<Info>("ConfigV2.MCP")({
|
||||
timeout: PositiveInt.pipe(Schema.optional),
|
||||
servers: Schema.Record(Schema.String, Server).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
|
@ -200,6 +200,31 @@ describe("Config", () => {
|
|||
lsp: { typescript: { disabled: true }, custom: { command: ["custom-lsp"], extensions: [".foo"] } },
|
||||
attachments: { image: { auto_resize: false, max_width: 1200, max_height: 900, max_base64_bytes: 1048576 } },
|
||||
tool_output: { max_lines: 1000, max_bytes: 32768 },
|
||||
mcp: {
|
||||
timeout: 5000,
|
||||
servers: {
|
||||
local: {
|
||||
type: "local",
|
||||
command: ["node", "./mcp/server.js"],
|
||||
environment: { API_KEY: "secret" },
|
||||
disabled: false,
|
||||
timeout: 10000,
|
||||
},
|
||||
remote: {
|
||||
type: "remote",
|
||||
url: "https://mcp.example.com/mcp",
|
||||
headers: { Authorization: "Bearer token" },
|
||||
oauth: { client_id: "client", scope: "read write", callback_port: 19876 },
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
compaction: {
|
||||
auto: true,
|
||||
prune: false,
|
||||
keep: { turns: 3, tokens: 2000 },
|
||||
buffer: 10000,
|
||||
},
|
||||
skills: ["./skills", "~/shared-skills", "https://example.com/.well-known/skills/"],
|
||||
instructions: ["CONTRIBUTING.md", ".cursor/rules/*.md", "https://example.com/shared-rules.md"],
|
||||
references: {
|
||||
|
|
@ -261,6 +286,31 @@ describe("Config", () => {
|
|||
image: { auto_resize: false, max_width: 1200, max_height: 900, max_base64_bytes: 1048576 },
|
||||
})
|
||||
expect(documents[0]?.info.tool_output).toEqual({ max_lines: 1000, max_bytes: 32768 })
|
||||
expect(documents[0]?.info.mcp).toEqual({
|
||||
timeout: 5000,
|
||||
servers: {
|
||||
local: {
|
||||
type: "local",
|
||||
command: ["node", "./mcp/server.js"],
|
||||
environment: { API_KEY: "secret" },
|
||||
disabled: false,
|
||||
timeout: 10000,
|
||||
},
|
||||
remote: {
|
||||
type: "remote",
|
||||
url: "https://mcp.example.com/mcp",
|
||||
headers: { Authorization: "Bearer token" },
|
||||
oauth: { client_id: "client", scope: "read write", callback_port: 19876 },
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(documents[0]?.info.compaction).toEqual({
|
||||
auto: true,
|
||||
prune: false,
|
||||
keep: { turns: 3, tokens: 2000 },
|
||||
buffer: 10000,
|
||||
})
|
||||
expect(documents[0]?.info.skills).toEqual([
|
||||
"./skills",
|
||||
"~/shared-skills",
|
||||
|
|
|
|||
|
|
@ -276,7 +276,39 @@ External protocol and server integration configuration.
|
|||
|
||||
| Field | Current Purpose | Status | Notes |
|
||||
| ----- | ------------------------------------- | ------- | ----- |
|
||||
| `mcp` | MCP server definitions and enablement | pending | |
|
||||
| `mcp` | MCP server definitions and enablement | redesign | Keep opencode's explicit local/remote server entry format, nested under `mcp.servers`; use `disabled` for inactive entries and move timeout here. |
|
||||
|
||||
Keep the opencode MCP server entry format instead of adopting the common `mcpServers` copy/paste shape. Local servers remain explicit `type: "local"` entries with command arrays and `environment`; remote servers remain explicit `type: "remote"` entries with `url`, `headers`, and optional `oauth`. Nest the server map under `mcp.servers` so protocol-wide settings such as default timeout can live under the same subsystem.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"mcp": {
|
||||
"timeout": 5000,
|
||||
"servers": {
|
||||
"github": {
|
||||
"type": "local",
|
||||
"command": ["npx", "-y", "@github/github-mcp-server"],
|
||||
"environment": { "GITHUB_TOKEN": "{env:GITHUB_TOKEN}" },
|
||||
"disabled": false,
|
||||
"timeout": 10000,
|
||||
},
|
||||
"docs": {
|
||||
"type": "remote",
|
||||
"url": "https://docs.example.com/mcp",
|
||||
"headers": { "Authorization": "Bearer {env:DOCS_TOKEN}" },
|
||||
"oauth": {
|
||||
"client_id": "{env:MCP_CLIENT_ID}",
|
||||
"client_secret": "{env:MCP_CLIENT_SECRET}",
|
||||
"scope": "read write",
|
||||
"callback_port": 19876,
|
||||
"redirect_uri": "http://127.0.0.1:19876/mcp/oauth/callback",
|
||||
},
|
||||
"disabled": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Group 10: Conversation Lifecycle
|
||||
|
||||
|
|
@ -284,7 +316,23 @@ Behavior affecting long-running conversations and context management.
|
|||
|
||||
| Field | Current Purpose | Status | Notes |
|
||||
| ------------ | ----------------------------------------------------------- | ------- | ----- |
|
||||
| `compaction` | Automatic compaction, pruning, and context reserve settings | pending | |
|
||||
| `compaction` | Automatic compaction, pruning, and context reserve settings | redesign | Group retained verbatim history under `keep` and rename context headroom to `buffer`. |
|
||||
|
||||
Retain the compaction capability but redesign the less clear limits. `keep.turns` is the maximum number of recent user turns to preserve verbatim after compaction, and `keep.tokens` is the token budget for those retained turns. `buffer` is the token headroom reserved so automatic compaction triggers before the input window is exhausted.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"compaction": {
|
||||
"auto": true,
|
||||
"prune": true,
|
||||
"keep": {
|
||||
"turns": 2,
|
||||
"tokens": 2000,
|
||||
},
|
||||
"buffer": 10000,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Group 11: Deprecated And Experimental Settings
|
||||
|
||||
|
|
@ -292,13 +340,13 @@ Fields that should not be ported by inertia; each needs an explicit justificatio
|
|||
|
||||
| Field | Current Purpose | Status | Notes |
|
||||
| ------------------------------------ | --------------------------------------- | ------- | ------------------------------------------------------------------- |
|
||||
| `layout` | Legacy layout selection | pending | Deprecated; current description says stretch layout is always used. |
|
||||
| `experimental.disable_paste_summary` | Disable pasted-content summary behavior | pending | |
|
||||
| `experimental.batch_tool` | Enable batch tool | pending | |
|
||||
| `experimental.openTelemetry` | Enable AI SDK telemetry spans | pending | |
|
||||
| `experimental.primary_tools` | Restrict tools to primary agents | pending | |
|
||||
| `experimental.continue_loop_on_deny` | Continue loop after denied tool call | pending | |
|
||||
| `experimental.mcp_timeout` | MCP request timeout | pending | May belong with MCP rather than experiments. |
|
||||
| `layout` | Legacy layout selection | remove | Do not port deprecated option; stretch layout is always used. |
|
||||
| `experimental.disable_paste_summary` | Disable pasted-content summary behavior | remove | Do not port; pasted-input presentation behavior belongs to the client/UI surface. |
|
||||
| `experimental.batch_tool` | Enable batch tool | remove | Do not port; batch tool is no longer a supported feature. |
|
||||
| `experimental.openTelemetry` | Enable AI SDK telemetry spans | remove | Do not port; observability is process-level and should use standard OpenTelemetry environment or declarative configuration. |
|
||||
| `experimental.primary_tools` | Restrict tools to primary agents | remove | Do not port obsolete gating; agent tool access is configured through permissions. |
|
||||
| `experimental.continue_loop_on_deny` | Continue loop after denied tool call | remove | Do not port legacy denied-tool loop behavior. |
|
||||
| `experimental.mcp_timeout` | MCP request timeout | redesign | Move to `mcp.timeout` for the default and `mcp.servers.<name>.timeout` for per-server overrides. |
|
||||
|
||||
## Review Order
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue