* feat(llm): per-provider unsupported parameter filtering (#749, #728) (#809)
Add declarative `unsupported_params` field to provider definitions in
providers.json. Parameters listed are stripped from requests before
sending, preventing 400 errors from providers that reject them (e.g.
gpt-5 family and kimi-k2.5 rejecting custom temperature values).
- Add `unsupported_params` to ProviderDefinition and RegistryProviderConfig
- Propagate from registry through config resolution
- Generic strip helpers handle temperature, max_tokens, stop_sequences
- Apply filtering in RigAdapter and AnthropicOAuthProvider
- Mark openai and tinfoil providers as unsupporting temperature
- Update openai default model to gpt-5-mini
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix(mcp): strip top-level null params before forwarding to MCP servers
LLMs frequently emit `"field": null` for optional parameters in tool
calls. Many MCP servers reject explicit nulls for fields that should
simply be absent — e.g. Notion returns 400 for `"sort": null` in a
search call, expecting the field to be omitted entirely.
Strip top-level null keys from the params object before calling
`call_tool()`. Only top-level keys are stripped; nested nulls are
preserved since they may be semantically meaningful.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Illia Polosukhin <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix(wasm): run leak scan before credential injection in tools wrapper
The tools WASM wrapper runs the LeakDetector on HTTP request headers
AFTER inject_host_credentials() has already substituted real secrets
(e.g., xoxb- Slack bot tokens). This causes the leak detector to
flag the tool's own legitimate outbound API calls as secret exfiltration.
Move the scan to run on raw_headers before any credential injection,
matching the fix already applied to the channels wrapper in #421.
Fixes the same class of bug as #421 (which only fixed channels/wasm/wrapper.rs).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* perf: inline leak scan to avoid Vec allocation on every HTTP request
Address review feedback: instead of cloning all header keys/values into
a Vec to pass to scan_http_request(), iterate over raw_headers directly
using scan_and_clean(). This also provides more specific error messages
(URL vs header vs body).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: fix cargo fmt formatting in leak scan loop
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix(mcp): JSON-RPC spec compliance — flexible id, correct notification format
- McpRequest.id is now Option<u64> with skip_serializing_if, so
notifications omit the id field as required by JSON-RPC 2.0 spec.
Previously sent id: 0 which violates the spec.
- McpResponse.id uses flexible deserialization that accepts number,
string, or null — fixes interop with non-standard MCP servers that
return string ids or missing id fields on error responses.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Fix review feedback: remove serde(default) from McpResponse.id, fix test assertions
- Remove #[serde(default)] from McpResponse.id so notifications (no id field)
don't incorrectly parse as responses — prevents DoS/spoofing via SSE
- Update test assertions to use Some(value) after id became Option<u64>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: update new transport files for Option<u64> id after rebase
Upstream #721 added stdio/unix/transport modules that use McpRequest.id
and McpResponse.id as u64. After our rebase (which changes id to
Option<u64>), these need .unwrap_or(0) for HashMap keys and Some()
wrapping in tests.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* test: add regression tests for JSON-RPC spec compliance
Tests for notification serialization without id field,
flexible id deserialization (string, null, non-numeric).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat(skills): exclude_keywords veto in skill activation scoring
Add exclude_keywords field to ActivationCriteria. If any exclude
keyword is present in the user message, the skill scores 0 regardless
of keyword or pattern matches — prevents cross-skill interference.
Behaviour: exclude_keywords is a hard veto. Even an exact skill name
match gets vetoed if an exclude keyword is also present. This is
intentional; partial exclusion (score reduction) would create
unpredictable interference behaviour.
Example use case: a writing skill with keywords ["write", "draft"]
and exclude_keywords ["route", "redirect"] will not activate on
messages like "don't route this to the writing agent".
Changes:
- ActivationCriteria: new exclude_keywords field (serde default)
- LoadedSkill: new lowercased_exclude_keywords (preprocessed at load)
- selector.rs: early-return 0 in score_skill() on veto match
- registry.rs: populate lowercased_exclude_keywords during loading
- Test helpers updated across mod.rs, selector.rs, attenuation.rs
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Fix review feedback: enforce limits on exclude_keywords, extract helper, use any()
- Add exclude_keywords to enforce_limits() with same min-length and cap
rules as keywords — prevents empty string always-match and unbounded lists
- Extract to_lowercase_vec() helper to deduplicate three identical blocks
- Use idiomatic any() iterator instead of for loop in score_skill veto check
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* test(skills): add exclude_keywords veto tests
Adds 4 tests for the exclude_keywords veto behavior as requested in review:
1. test_exclude_keyword_vetos_match — skill scores 0 when exclude keyword present
2. test_exclude_keyword_absent_does_not_block — skill activates normally without it
3. test_exclude_keyword_veto_wins_over_positive_match — veto wins even with multiple keyword hits
4. test_exclude_keyword_case_insensitive — veto fires regardless of message case
Also adds make_skill_with_excludes() test helper to avoid repeating the
LoadedSkill construction boilerplate in each test.
Note on substring matching: exclude_keywords uses message_lower.contains(excl)
(substring match), consistent with the existing positive keyword scoring path.
This means "red" would veto "redirect". This is documented behaviour — if
word-boundary semantics are needed, that's a follow-up change.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
* style: run cargo fmt on selector.rs
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
5432:5432 → 127.0.0.1:5432:5432 — the default docker-compose.yml
exposed postgres on all interfaces, making it reachable from the
local network in any docker compose deployment.
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Zaki Manian <[email protected]>