Bug Description
llm.maxTokens and llm.headers are read at runtime by the plugin (the LLM client resolves config.maxTokens with a 1024 fallback, and every provider spreads config.headers into outgoing requests) but they are not declared in DEFAULT_CONFIG or in LlmSchema/SkillEvolverSchema.
Root Cause
core/llm/client.ts reads opts?.maxTokens ?? config.maxTokens ?? DEFAULT_MAX_TOKENS and core/llm/providers/openai.ts (plus anthropic/gemini/bedrock) do Object.assign(headers, config.headers). However:
core/config/defaults.ts — the llm / skillEvolver / l3Llm trees have no maxTokens or headers keys.
core/config/schema.ts — LlmSchema/SkillEvolverSchema only declare reasoning.maxTokens (a different, nested key); top-level maxTokens and headers are absent.
core/config/index.ts pruneUnknown() — an unknown key is kept "as-is for forward compatibility" but logs a warning; worse, when the default slot is an empty object (headers: {} after the fix), the recursion warns for every user-supplied header key (unknown config key 'llm.headers.User-Agent').
Impact
- Every boot logs
unknown config key 'llm.maxTokens' (and 'llm.headers.*' once headers are configured) — cosmetic noise that looks like a misconfiguration.
- The keys are unvalidated: out-of-range
maxTokens values or non-string header values pass silently until the provider rejects the request.
Environment
- Plugin version: 2.0.15 (npm train) / monorepo main (b4cc9bc)
- Trigger: any config with
llm.maxTokens or llm.headers set
Suggested Fix
- Add
maxTokens: 1024 + headers: {} to the llm defaults tree (and maxTokens: 1024 to skillEvolver/l3Llm, which share SkillEvolverSchema).
- Declare
maxTokens: NumberInRange(1024, 16, 131072) and headers: Type.Optional(Type.Record(Type.String(), Type.String(), { default: {} })) in LlmSchema; declare maxTokens in SkillEvolverSchema.
- In
pruneUnknown(), treat an empty-object default slot as a free-form map (Record<string, string>) and keep the user's object as-is instead of recursing per key.
See PR (linked below) which implements exactly this with a regression test.
Bug Description
llm.maxTokensandllm.headersare read at runtime by the plugin (the LLM client resolvesconfig.maxTokenswith a 1024 fallback, and every provider spreadsconfig.headersinto outgoing requests) but they are not declared inDEFAULT_CONFIGor inLlmSchema/SkillEvolverSchema.Root Cause
core/llm/client.tsreadsopts?.maxTokens ?? config.maxTokens ?? DEFAULT_MAX_TOKENSandcore/llm/providers/openai.ts(plus anthropic/gemini/bedrock) doObject.assign(headers, config.headers). However:core/config/defaults.ts— thellm/skillEvolver/l3Llmtrees have nomaxTokensorheaderskeys.core/config/schema.ts—LlmSchema/SkillEvolverSchemaonly declarereasoning.maxTokens(a different, nested key); top-levelmaxTokensandheadersare absent.core/config/index.tspruneUnknown()— an unknown key is kept "as-is for forward compatibility" but logs a warning; worse, when the default slot is an empty object (headers: {}after the fix), the recursion warns for every user-supplied header key (unknown config key 'llm.headers.User-Agent').Impact
unknown config key 'llm.maxTokens'(and'llm.headers.*'once headers are configured) — cosmetic noise that looks like a misconfiguration.maxTokensvalues or non-string header values pass silently until the provider rejects the request.Environment
llm.maxTokensorllm.headerssetSuggested Fix
maxTokens: 1024+headers: {}to thellmdefaults tree (andmaxTokens: 1024toskillEvolver/l3Llm, which shareSkillEvolverSchema).maxTokens: NumberInRange(1024, 16, 131072)andheaders: Type.Optional(Type.Record(Type.String(), Type.String(), { default: {} }))inLlmSchema; declaremaxTokensinSkillEvolverSchema.pruneUnknown(), treat an empty-object default slot as a free-form map (Record<string, string>) and keep the user's object as-is instead of recursing per key.See PR (linked below) which implements exactly this with a regression test.