feat: add --local-source to E2E smoke test + fix input_schema test - #52
Closed
Brian Krabach (bkrabach) wants to merge 1 commit into
Closed
Conversation
Adds --local-source flag to e2e-smoke-test.sh for cross-repo validation. Allows overriding additional packages (CLI, foundation, modules) with local checkouts during E2E testing. Each path is copied into the Docker container and installed with pip install --force-reinstall --no-deps.
Also fixes test_tool_protocol_defines_input_schema to test behavioral contract (using getattr with default) instead of Protocol metaclass introspection. Python's Protocol metaclass does not expose default property implementations via hasattr/vars, so the test now validates the actual calling pattern (getattr(tool, 'input_schema', {})).
## Testing
- 150 passed, 0 failed, 1 skipped
- E2E smoke test passes with 7 local overrides
Collaborator
Author
|
Replacing with a focused PR for --local-source only. |
Ken Chau (kenotron-ms)
added a commit
that referenced
this pull request
May 8, 2026
…us.cost_usd — revives PR #73 Revives the approach from amplifier-core PR #73 (closed without merge 2026-05-07T18:15:32Z) with one critical refinement: use when_used='always' instead of default mode='json'-only. PROBLEM: M2 cost-tracking introduced cost_usd: Decimal | None on Usage and SessionStatus. Without serializer help, plain model_dump() (called by every orchestrator: loop-streaming line 472,505, loop-basic 361, loop-events 307,337) emits Decimal values into events, which crash json.dumps() at every downstream JSONL write site (hooks-logging, hooks-backup, hook-shell, context-persistent, foundation/serialization.py). A "patch every consumer" approach doesn't scale to N orchestrators or future hook modules. FIX: Move conversion to the model itself using @field_serializer("cost_usd", when_used="always"): - model_dump() and model_dump(mode='json') both return JSON-safe strings for cost_usd - Direct attribute access still returns Decimal for in-memory math - None survives as None (preserves None ≠ Decimal("0") invariant for "unknown" vs "free") - Plus _json_default(obj) in models.py + default= in ToolResult.get_serialized_output() for non-Pydantic paths where tool outputs may carry Decimal WHY "when_used='always'" MATTERS: PR #73 used default field_serializer behavior, which fires only on mode='json'. But every orchestrator calls plain model_dump(). Verification today (Pydantic 2.13.4) confirmed that without when_used='always', Decimal values still leak into events on the plain-dump path. VERIFIED BEHAVIORS (10 tests): ✓ model_dump() returns string for cost_usd ✓ json.dumps(usage.model_dump()) doesn't crash ✓ Direct usage.cost_usd access still returns Decimal ✓ Existing field_validator rejecting float still fires ✓ extra="allow" still works for provider-specific fields ✓ None survives as None (not "None") ✓ Round-trip model_validate(model_dump()) rebuilds with Decimal ✓ Precision preserved across 0.0000123, 12345.6789, 0, 1E-9 ✓ Plain model_dump() and mode='json' produce identical output ✓ int coerces to Decimal then serializes to string NEW TESTS: - tests/test_cost_usd_serialization.py: 25 tests (all pass) - 129 adjacent tests still pass (test_message_models, test_session, test_tool_result_autopop, test_interfaces, test_generated_equivalence) VERSION BUMP: 1.5.0 → 1.5.1 (PATCH, per release-mandate) - pyproject.toml - crates/amplifier-core/Cargo.toml - bindings/python/Cargo.toml - python/amplifier_core/__init__.py E2E SMOKE TEST: PASSED (run by user before push, per release-mandate Pre-Merge Gate) FILES CHANGED: - python/amplifier_core/message_models.py: +field_serializer on Usage.cost_usd - python/amplifier_core/models.py: +field_serializer on SessionStatus.cost_usd + _json_default + default= in ToolResult - tests/test_cost_usd_serialization.py: new, 234 lines - Version files: all bumped to 1.5.1 - Cargo.lock: regenerated by Rust toolchain UNBLOCKS M2/M3 milestone (microsoft-amplifier/amplifier-support#225): Once landed, these 11 PRs can drop per-consumer Decimal sanitizer code: - foundation #190: drop Decimal→str branch from serialization.py - hooks-logging #5: close as superseded (model_dump(mode='json') becomes redundant) - provider PRs: anthropic #52, openai #32, azure-openai #24, gemini #28, vllm #22, ollama #14, chat-completions #5 - consumer PRs: app-cli #171, hooks-streaming-ui #11 (unchanged) Plus 3 modules no longer need follow-up PRs: - amplifier-module-hooks-backup (automatic safety) - amplifier-module-hook-shell (automatic safety) - amplifier-module-context-persistent (automatic safety) Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Salil Das (sadlilas)
pushed a commit
that referenced
this pull request
May 8, 2026
…cost stamping (#73) * fix(core): @field_serializer(when_used='always') on Usage/SessionStatus.cost_usd — revives PR #73 Revives the approach from amplifier-core PR #73 (closed without merge 2026-05-07T18:15:32Z) with one critical refinement: use when_used='always' instead of default mode='json'-only. PROBLEM: M2 cost-tracking introduced cost_usd: Decimal | None on Usage and SessionStatus. Without serializer help, plain model_dump() (called by every orchestrator: loop-streaming line 472,505, loop-basic 361, loop-events 307,337) emits Decimal values into events, which crash json.dumps() at every downstream JSONL write site (hooks-logging, hooks-backup, hook-shell, context-persistent, foundation/serialization.py). A "patch every consumer" approach doesn't scale to N orchestrators or future hook modules. FIX: Move conversion to the model itself using @field_serializer("cost_usd", when_used="always"): - model_dump() and model_dump(mode='json') both return JSON-safe strings for cost_usd - Direct attribute access still returns Decimal for in-memory math - None survives as None (preserves None ≠ Decimal("0") invariant for "unknown" vs "free") - Plus _json_default(obj) in models.py + default= in ToolResult.get_serialized_output() for non-Pydantic paths where tool outputs may carry Decimal WHY "when_used='always'" MATTERS: PR #73 used default field_serializer behavior, which fires only on mode='json'. But every orchestrator calls plain model_dump(). Verification today (Pydantic 2.13.4) confirmed that without when_used='always', Decimal values still leak into events on the plain-dump path. VERIFIED BEHAVIORS (10 tests): ✓ model_dump() returns string for cost_usd ✓ json.dumps(usage.model_dump()) doesn't crash ✓ Direct usage.cost_usd access still returns Decimal ✓ Existing field_validator rejecting float still fires ✓ extra="allow" still works for provider-specific fields ✓ None survives as None (not "None") ✓ Round-trip model_validate(model_dump()) rebuilds with Decimal ✓ Precision preserved across 0.0000123, 12345.6789, 0, 1E-9 ✓ Plain model_dump() and mode='json' produce identical output ✓ int coerces to Decimal then serializes to string NEW TESTS: - tests/test_cost_usd_serialization.py: 25 tests (all pass) - 129 adjacent tests still pass (test_message_models, test_session, test_tool_result_autopop, test_interfaces, test_generated_equivalence) VERSION BUMP: 1.5.0 → 1.5.1 (PATCH, per release-mandate) - pyproject.toml - crates/amplifier-core/Cargo.toml - bindings/python/Cargo.toml - python/amplifier_core/__init__.py E2E SMOKE TEST: PASSED (run by user before push, per release-mandate Pre-Merge Gate) FILES CHANGED: - python/amplifier_core/message_models.py: +field_serializer on Usage.cost_usd - python/amplifier_core/models.py: +field_serializer on SessionStatus.cost_usd + _json_default + default= in ToolResult - tests/test_cost_usd_serialization.py: new, 234 lines - Version files: all bumped to 1.5.1 - Cargo.lock: regenerated by Rust toolchain UNBLOCKS M2/M3 milestone (microsoft-amplifier/amplifier-support#225): Once landed, these 11 PRs can drop per-consumer Decimal sanitizer code: - foundation #190: drop Decimal→str branch from serialization.py - hooks-logging #5: close as superseded (model_dump(mode='json') becomes redundant) - provider PRs: anthropic #52, openai #32, azure-openai #24, gemini #28, vllm #22, ollama #14, chat-completions #5 - consumer PRs: app-cli #171, hooks-streaming-ui #11 (unchanged) Plus 3 modules no longer need follow-up PRs: - amplifier-module-hooks-backup (automatic safety) - amplifier-module-hook-shell (automatic safety) - amplifier-module-context-persistent (automatic safety) Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * test: update test_model_dump_includes_cost_usd_as_decimal for new contract The test was written for the OLD contract (model_dump() returns Decimal). With @field_serializer(when_used='always') on Usage.cost_usd, plain model_dump() now returns string — same as mode='json'. Renamed and updated assertion to match. Added a "direct attribute access still returns Decimal" assertion to make the bidirectional contract explicit. Self-correction: I missed sweeping bindings/python/tests/ in the original PR's verification pass; only ran tests/. The CI catch is exactly what the release-mandate Pre-Merge Gate is for. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * test: drop bug-history references; lead docstrings with the contract Test docstrings shouldn't read like a changelog. A reader landing on these tests cares about what cost_usd actually is, not which milestone introduced it or which PR-number-of-the-week it was last fixed in. - Module docstring opens with "cost_usd is Decimal in memory ... JSON-safe string when serialized" and the None ≠ Decimal("0") invariant - Class docstrings: "Decimal in memory, string on the wire" - Per-test docstrings describe what the test verifies, not why history forced it (no @field_serializer, no _json_default, no PR #73, no M2) - Also: regenerate uv.lock for the 1.5.0 → 1.5.1 version bump that was missed when bumping the other version files manually 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --------- Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Ken Chau (kenotron-ms)
added a commit
that referenced
this pull request
May 9, 2026
Fixes microsoft-amplifier/amplifier-support#228. All four unguarded json.dumps() call sites at the Python/Rust FFI boundary now pass default=str so Decimal, datetime, and any other non-JSON-native type becomes its str() representation instead of raising TypeError and crashing the caller. Sites fixed: - helpers.rs: new json_dumps_safe() DRY helper used at all Rust sites - hooks.rs: emit(), set_default_fields(), emit_and_collect() - bridges.rs: PyHookHandlerBridge::handle() result-serialization path - loader_grpc.py: GrpcToolBridge._serialize_input() Incidental fix: provider-anthropic cost_usd crash (PR #52) is resolved automatically. str(Decimal("1.23")) == "1.23", identical to the existing @field_serializer output on the Pydantic model path. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Ken Chau (kenotron-ms)
added a commit
that referenced
this pull request
May 9, 2026
Fixes microsoft-amplifier/amplifier-support#228. All four unguarded json.dumps() call sites at the Python/Rust FFI boundary now pass default=str so Decimal, datetime, and any other non-JSON-native type becomes its str() representation instead of raising TypeError and crashing the caller. Sites fixed: - helpers.rs: new json_dumps_safe() DRY helper used at all Rust sites - hooks.rs: emit(), set_default_fields(), emit_and_collect() - bridges.rs: PyHookHandlerBridge::handle() result-serialization path - loader_grpc.py: GrpcToolBridge._serialize_input() Incidental fix: provider-anthropic cost_usd crash (PR #52) is resolved automatically. str(Decimal("1.23")) == "1.23", identical to the existing @field_serializer output on the Pydantic model path. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Ken Chau (kenotron-ms)
added a commit
that referenced
this pull request
May 9, 2026
All four unguarded json.dumps() call sites at the Python/Rust FFI boundary now pass default=str so Decimal, datetime, and any other non-JSON-native type becomes its str() representation instead of raising TypeError and crashing the caller. Sites fixed: - helpers.rs: new json_dumps_safe() DRY helper used at all Rust sites - hooks.rs: emit(), set_default_fields(), emit_and_collect() - bridges.rs: PyHookHandlerBridge::handle() result-serialization path - loader_grpc.py: GrpcToolBridge._serialize_input() Incidental fix: provider-anthropic cost_usd crash (PR #52) is resolved automatically. str(Decimal("1.23")) == "1.23", identical to the existing @field_serializer output on the Pydantic model path. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Brian Krabach (bkrabach)
pushed a commit
that referenced
this pull request
May 12, 2026
…76) * fix(bindings): sanitize non-JSON-native types at emit() FFI boundary All four unguarded json.dumps() call sites at the Python/Rust FFI boundary now pass default=str so Decimal, datetime, and any other non-JSON-native type becomes its str() representation instead of raising TypeError and crashing the caller. Sites fixed: - helpers.rs: new json_dumps_safe() DRY helper used at all Rust sites - hooks.rs: emit(), set_default_fields(), emit_and_collect() - bridges.rs: PyHookHandlerBridge::handle() result-serialization path - loader_grpc.py: GrpcToolBridge._serialize_input() Incidental fix: provider-anthropic cost_usd crash (PR #52) is resolved automatically. str(Decimal("1.23")) == "1.23", identical to the existing @field_serializer output on the Pydantic model path. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * chore: bump version to 1.5.2 - pyproject.toml: 1.5.1 → 1.5.2 - crates/amplifier-core/Cargo.toml: 1.5.1 → 1.5.2 - bindings/python/Cargo.toml: 1.5.1 → 1.5.2 - python/amplifier_core/__init__.py: 1.5.1 → 1.5.2 - scripts/bump_version.py: regex fix to handle __version__ (trailing __) - Cargo.lock: updated Verified: E2E smoke test PASSED — manylinux wheel built, pristine-import OK, Docker container test passed. Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * chore: format Python bindings with rustfmt Fix rustfmt CI check failure by applying standard formatting to: - json_dumps_safe function signature in helpers.rs - test binding in tests/mod.rs Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> * fix: extend json_dumps_safe() to all FFI json.dumps() call sites + structural guard test - Apply json_dumps_safe() to all remaining unguarded json.dumps() calls: * session.rs: session config serialization * coordinator/mod.rs: coordinator config serialization * wasm.rs: tool input, provider request/response, hook data, context message, set_messages, approval request (7 sites) - Add no_raw_json_dumps_outside_helpers structural test that scans .rs files and fails if raw json.dumps() calls appear outside the safe helper, preventing future regressions - Remove private repo reference from test docstring Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --------- Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds --local-source flag to e2e-smoke-test.sh for cross-repo validation. Also fixes test_tool_protocol_defines_input_schema to test behavioral contract instead of Protocol metaclass introspection.
--local-source
Allows overriding additional packages (CLI, foundation, modules) with local checkouts during E2E testing. Each path is copied into the Docker container and installed with pip install --force-reinstall --no-deps.
Testing