Conversation
…ds (closes #559) Wrap was hard-coded for the (self, params, ctx) calling convention, so framework-dispatched arg-projected tools like update_media_buy — called as method(self, media_buy_id=..., patch=..., ctx=ctx) by dispatch.py's arg_projector path — raised TypeError before the wrap body ran. Salesagent shipped a workaround dropping @Wrap from update_media_buy entirely, losing idempotency on retries. Wrap now resolves the calling convention via _resolve_call_args() and supports three shapes: 1. Positional (self, params, ctx) — original. 2. Keyword (self, params=..., context=...). 3. Arg-projected (self, **arg_projector_kwargs, ctx=...) — searches kwargs for a Pydantic model (the framework's contract for the request model, e.g. patch on update_media_buy) to pull idempotency_key from. Falls back to the kwargs dict (excluding ctx/context) when no Pydantic model is present, so projections exposing idempotency_key at the top level still work. Tests: 6 new in TestWrapArgProjectionCalling, 154 across the broader idempotency surface green. Lint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ydantic, BaseModel strictness) - _resolve_call_args: 'kwargs.get(context) or kwargs.get(ctx)' silently fell through to ctx when context was explicitly None. Switched to 'in'-based check so explicit None wins. - Multi-Pydantic-kwarg: prefer 'params' / 'request' / 'patch' by name before falling back to first-by-iteration. Eliminates dict-order fragility when a tool has two model kwargs. - isinstance(BaseModel) instead of hasattr(model_dump). A duck-typed object with model_dump no longer accidentally matches. 4 new tests cover: explicit None-context wins, multi-Pydantic preference order, no-preferred-name first-by-iteration fallback, duck-typed non-Pydantic exclusion. 59 tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Updated with expert-review fixes (code-reviewer + ad-tech-protocol-expert): Code-reviewer must-fix:
Tests added: 4 new in Protocol-expert open question (#1) — should target IDs (e.g. CI running on the new commit. 59 tests green locally; lint clean. |
|
Fixes look solid — the On the target-ID scoping question: your read of AdCP #2315 is correct. The principal-scope model places the key-uniqueness burden on the buyer, and key reuse across different targets is buyer error per spec. That said, Generated by Claude Code |
|
Thanks for the thorough review, @bokelley. Glad the Agreed on the target-ID scoping follow-up — the spec-alignment reasoning (buyer owns key uniqueness) is sound for this PR, and the HTTP-level resource-identity angle is worth its own issue to track cleanly. Won't create it here; leaving that to the team. Ready for merge when Salesagent's re-application of Generated by Claude Code |
Closes #559.
The bug
`@IdempotencyStore.wrap` had signature `(handler_self, params, context=None, *args, **kwargs)`. The framework's arg-projector path in `dispatch.py:1081` calls methods as `method(**arg_projector, ctx=ctx)` — for `update_media_buy`, that's `method(self, media_buy_id=..., patch=..., ctx=...)`. Python can't bind `params` (positional, no default) → `TypeError: missing 1 required positional argument: 'params'` before the wrap body runs.
Salesagent's kill-nginx spike shipped a workaround dropping `@_IDEMPOTENCY.wrap` from `update_media_buy` entirely. Buyer retries re-execute side effects.
Fix
Wrap signature is now `async def _wrapped(*args, **kwargs)`. A new `_resolve_call_args` helper detects which of three calling conventions the framework dispatched with:
The inner handler is invoked via `*args/**kwargs` verbatim. The wrap is signature-transparent — no matter which convention the framework used, the inner handler sees its expected arguments unchanged.
Why a wrap-side fix (not dispatch-side)?
Three reasons:
Edge cases handled
Tests
6 new in `TestWrapArgProjectionCalling`:
154 tests green across the broader idempotency surface (validate-wiring, PG backend, MCP/A2A integration). Lint clean.
Test plan
🤖 Generated with Claude Code