Skip to content

Rust runtime parity fixes + make authenticationMode optional#333

Merged
sethjuarez merged 8 commits into
mainfrom
sethjuarez/rust-runtime-parity-fixes
Apr 10, 2026
Merged

Rust runtime parity fixes + make authenticationMode optional#333
sethjuarez merged 8 commits into
mainfrom
sethjuarez/rust-runtime-parity-fixes

Conversation

@sethjuarez

Copy link
Copy Markdown
Member

Summary

This PR brings the Rust Prompty runtime to full parity with TypeScript/Python/C# and fixes 23 audit gaps, plus a schema-level change making Connection.authenticationMode optional.

Rust Runtime Parity Fixes (commits 1-3)

🔴 Critical fixes (5)

  • trace_span_async() now properly ends spans via Arc<SpanEmitter>
  • invoke()/turn() no longer unwrap StructuredResult early — callers use cast()
  • Nonce storage moved from global Mutex to thread-through-pipeline pattern
  • Parser no longer accepts developer: role (spec: system/user/assistant only)
  • AgentEvent payloads now carry data (messages, results) for event consumers

❌ Feature gaps closed (6)

  • Tool bindings injected from tool.bindings spec field
  • PromptyTool dispatch with circular reference detection + MCP/OpenAPI/Custom stubs
  • ReferenceConnection resolved from registry in all 3 executors
  • Entra ID auth for Azure behind entra_id feature flag
  • Anthropic structured output wire format (output_config)
  • turn_from_path() convenience function

🟡 Spec correctness (7)

  • validate_inputs() called before rendering
  • load()/load_async() traced
  • Body whitespace: trim trailing newlines only (not full .trim())
  • Context summary format: [Context summary: ...]
  • Cookie/authorization added to redaction regex
  • Usage hoisting from child frames to parent
  • Lenient undefined variable mode confirmed correct per spec vectors

🔧 Rust quality (4)

  • Edition 2024 for prompty-foundry and prompty-anthropic
  • Static LazyLock<reqwest::Client> — no new client per request
  • Box<dyn Trait>Arc<dyn Trait> — no RwLock held across .await
  • spawn_blocking for sync file I/O in async paths

Schema Change (commit 4)

Connection.authenticationMode → optional

authenticationMode is defined on the base Connection type but no runtime reads or branches on it. It is purely metadata for orchestrators. Making it optional avoids forcing every .prompty file to carry a field nobody consumes.

  • Regenerated all 6 emitter targets (TS, Python, C#, Go, Rust, VS Code schema)

Test Results

  • 483 Rust tests pass, 0 failures, 15 ignored
  • All model tests pass across runtimes
  • Cross-runtime parity audit: ~95% spec compliance, full parity with TS/Python/C#

Commits (unsigned — will be signed before merge)

  1. de1afe0 — Critical fixes + spec correctness
  2. 279f3d1 — Quality fixes + feature gaps
  3. 9e4e697 — Targeted confidence tests
  4. 189e800 — Schema: authenticationMode optional

sethjuarez and others added 8 commits April 9, 2026 23:28
…unwrap, events, roles

- trace_span_async: use Arc<SpanEmitter>, properly end() spans in both paths
- Nonce global state: remove global Mutex, thread nonces through pipeline
- Structured unwrap: return processed directly from invoke(), let callers cast()
- AgentEvent: add data payloads to MessagesUpdated and Done variants
- Parser: remove developer role (spec: system/user/assistant only)
- Body trimming: trim only trailing newlines, preserve leading whitespace
- Context summary: use [Context summary: ...] format
- Redaction: add cookie/authorization to sensitive key regex
- Load tracing: add trace spans to load()/load_async()
- Render validation: call validate_inputs() before rendering
- turn_from_path: convenience function matching TS API
- InvokerError::Load variant for load failures

448 tests passing, 0 failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Quality fixes:
- fix-edition-mismatch: prompty-foundry and prompty-anthropic now edition 2024
- fix-client-reuse: static LazyLock<reqwest::Client> in all 3 executor crates
- fix-rwlock-await: Box<dyn Trait> → Arc<dyn Trait> in registry and tool_dispatch,
  clone Arc before dropping guard to avoid holding lock across .await
- fix-file-resolve-async: load_async() uses spawn_blocking for build_agent()

Feature gap fixes:
- add-tool-bindings: resolve_bindings() injects parent inputs per tool.bindings
- add-prompty-tool-dispatch: PromptyToolHandler (recursive .prompty loading with
  circular reference detection), McpToolHandler, OpenApiToolHandler, CustomToolHandler
- add-reference-connection: all 3 executor crates resolve kind=reference connections
  from the connection registry via Cow<Value> pattern
- add-entra-id-auth: optional entra_id feature on prompty-foundry, async
  get_auth_header with DefaultAzureCredential token acquisition
- add-anthropic-structured-wire: outputSchema → output_config with json_schema format
- fix-usage-hoisting: hoist __usage from result.usage, array results, and child
  frames in PromptyTracer

462 tests pass, 0 failures, 15 ignored.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PromptyToolHandler (4 tests):
- Missing __source_path metadata → clear error
- Missing path field in tool def → clear error
- Circular reference detection → error not hang
- Nonexistent child .prompty → load failure

Reference connection resolution (14 tests across 3 executors):
- Passthrough for kind=key connections (no-op path)
- Missing name field on reference connection → error
- Successful reference lookup via connection registry
- End-to-end flow: reference → build_url / get_api_key / get_auth_header

Kind handler dispatch via dispatch_tool (2 tests):
- Layer 3 kind-based dispatch routes to MCP handler
- Wildcard (*) handler catches unknown kinds

Entra ID auth (1 test):
- Foundry connection without API key or entra_id feature → error

483 tests pass, 0 failures, 15 ignored.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
authenticationMode is defined on the base Connection type but no runtime
ever reads or branches on it. It is purely metadata for tooling and
orchestrators. Making it optional (rather than required-with-default)
avoids forcing every .prompty file to carry a field nobody consumes.

- Changed authenticationMode from required (default 'system') to optional
  in schema/model/connection.tsp
- Regenerated all 6 emitter targets: TypeScript, Python, C#, Go, Rust,
  VS Code JSON Schema, and markdown reference docs
- All runtimes updated: String -> Option<String> (Rust), str -> Optional
  (Python), string -> string? (C#/TS), etc.
- All existing tests pass (483 Rust tests, model tests across runtimes)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sethjuarez
sethjuarez marked this pull request as ready for review April 10, 2026 21:59
@sethjuarez
sethjuarez merged commit 80aa0ff into main Apr 10, 2026
19 checks passed
@sethjuarez
sethjuarez deleted the sethjuarez/rust-runtime-parity-fixes branch April 10, 2026 21:59
sethjuarez added a commit that referenced this pull request Apr 10, 2026
Bump Python (2.0.0a8), TypeScript (2.0.0-alpha.8), C# (2.0.0-alpha.8),
and VS Code extension packages for release including:
- fix: recursive array/object JSON Schema in tool params (#332)
- feat: Rust runtime parity + optional authenticationMode (#333)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sethjuarez added a commit that referenced this pull request Apr 11, 2026
* chore: bump all runtimes to alpha.8

Bump Python (2.0.0a8), TypeScript (2.0.0-alpha.8), C# (2.0.0-alpha.8),
and VS Code extension packages for release including:
- fix: recursive array/object JSON Schema in tool params (#332)
- feat: Rust runtime parity + optional authenticationMode (#333)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: nonce type coercion bug in parser strict mode across all runtimes

When randomBytes/token_hex generates an all-digit hex nonce (e.g.
'1234567890123456'), parseAttrs type coercion converts it to a number.
The subsequent strict comparison (number !== string) always fails,
causing a spurious 'Nonce mismatch' error.

Fix: compare nonces as strings in buildMessage/build_message across
Python, TypeScript, and Rust parsers.

Probability of all-digit 16-char hex: (10/16)^16 ≈ 0.01% — matches
the observed flaky CI failure rate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: add conceptual documentation for thread inputs (conversation history)

Adds a new core-concepts page explaining how thread inputs work in Prompty:
- What threads are and why they use nonce-based expansion
- How to declare kind: thread inputs and place them in templates
- Data format with multi-language examples (Python, TypeScript, C#, Rust)
- Internal pipeline mechanics (render → parse → expand)
- Best practices: token budgets, sliding windows, stateless design
- Integration with agent mode

Also adds cross-references from the file-format doc, core-concepts index,
and the chat-assistant tutorial.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant