Skip to content

feat(providers): provider-neutral normalization for cross-model hook runs#29

Merged
waitdeadai merged 1 commit into
mainfrom
feature/providers-shim
May 26, 2026
Merged

feat(providers): provider-neutral normalization for cross-model hook runs#29
waitdeadai merged 1 commit into
mainfrom
feature/providers-shim

Conversation

@waitdeadai

Copy link
Copy Markdown
Owner

providers/ — provider-neutral normalization for cross-model hook runs

The Claude-side substrate for the provider-invariance question from anthropics/claude-code#61167 (with @yurukusa): does operator-side discipline generalize across models, or lean on Claude-specific guard behavior? To answer it, the detectors must run against non-Claude transcripts — which needs one neutral shape.

What it adds (all under providers/, additive, pure stdlib, no network)

  • normalize.pyNormalizedTurn {assistant_message, tool_calls[]} + adapters: claude_hook, openai_chat, openai_responses, kimi.
  • fixtures.py + conformance_test.py — the same logical turn in all four envelopes proves the substrate property: identical assistant_message, identical tool_calls, and an IDENTICAL lib/count_drift.py verdict across every provider envelope. A dispatch claimed in text but never emitted as a calltool_calls == [] on every envelope (the dispatch-fabrication signal). 16/16 conformance checks pass.
  • CONTRACT.md + README.md — schema, per-provider mapping table, and the conformance bar a new adapter must pass.

Why

This is the prerequisite that makes a cross-model F1 pass runnable. The division of labor (per #61167): the contract + Claude reference adapter live here; @yurukusa's live Kimi K2 / OpenAI tool-format adapter conforms to it, and the falsifiable provider-invariance pass runs once a labeled cross-model corpus exists.

Honesty notes

  • Tool-call/message formats verified live 2026-05-26 (OpenAI Chat/Responses, Kimi/Moonshot, Claude Code hooks). Two items flagged and routed around: Claude last_assistant_message is version-gated (transcript-parse fallback), and Task tool_input key names are opaque (treated as opaque; detectors use tool name + count).
  • Fixtures are documented-format synthetic, not live-captured; live-capture validation is the adapter author's side.
  • Out of scope (separate follow-up): live API calls, a labeled cross-model corpus, actual per-model F1 numbers.

Verified: python3 providers/conformance_test.py 16/16; tests/test-count-drift.sh 9/9 (no regression); stdlib-only; offline.

🤖 Generated with Claude Code

…runs

Adds the Claude-side substrate for the provider-invariance question
(anthropics/claude-code#61167): normalize any provider's model turn into one
NormalizedTurn {assistant_message, tool_calls} so the dark-pattern detectors run
unchanged against OpenAI / Kimi K2 / etc.

- providers/normalize.py: NormalizedTurn + adapters (claude_hook, openai_chat,
  openai_responses, kimi). Pure stdlib, no network.
- providers/fixtures.py + conformance_test.py: same logical turn in 4 envelopes ->
  identical assistant_message, identical tool_calls, IDENTICAL count_drift verdict
  (16/16). Claimed-but-uncalled dispatch -> tool_calls==[] on every envelope.
- providers/CONTRACT.md + README.md: schema, mapping table, conformance bar so
  @yurukusa's live Kimi/OpenAI adapter has a spec.

Formats verified live 2026-05-26; two items flagged (Claude last_assistant_message
version-gating, Task tool_input opacity) and routed around. Additive; no existing
file changed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@waitdeadai waitdeadai merged commit 2ff5d7a into main May 26, 2026
2 checks passed
@waitdeadai waitdeadai deleted the feature/providers-shim branch May 26, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants