Skip to content

feat: align provider and agent behavior with Pi - #106

Merged
prabirshrestha merged 27 commits into
mainfrom
feat/constrained-sampling
Jul 27, 2026
Merged

feat: align provider and agent behavior with Pi#106
prabirshrestha merged 27 commits into
mainfrom
feat/constrained-sampling

Conversation

@prabirshrestha

@prabirshrestha prabirshrestha commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Why

ai.rs is the Rust port of Pi's AI/provider layer, but the Rust implementation had fallen behind Pi in several behaviors that matter to tool-using agents and downstream consumers such as aiproxy. The largest gaps were native constrained sampling, OpenAI Responses history fidelity, streamed output routing, deferred tool loading, provider-specific reasoning replay, and agent-loop handling around tool execution.

This PR brings the providers and agent loop already supported by ai.rs back in line with Pi. Pi is treated as the behavioral source of truth: the Rust code follows Pi's wire shapes, control flow, compatibility flags, and regression cases as closely as Rust and the existing public API permit. AGENTS.md now records that rule for future ports.

Scope

The port covers the provider surfaces and agent loop that already exist in ai.rs:

  • OpenAI Responses
  • OpenAI Chat Completions and compatible providers
  • Anthropic Messages
  • GitHub Copilot OAuth and runtime behavior
  • the lightweight ai.rs agent loop

It does not add unrelated Pi providers or downstream aiproxy-specific behavior.

Constrained sampling and tool definitions

  • adds Pi-compatible constrainedSampling support to Tool
  • supports explicit false, strict JSON-schema sampling, and native grammar sampling
  • supports prefer versus require behavior when strict schemas are unavailable
  • supports OpenAI Lark and regex grammar variants
  • validates that native grammar tools expose exactly one required string input property
  • falls back to an ordinary function tool when native grammar tools are unavailable and the configuration allows fallback
  • adds provider compatibility metadata for strict tools and OpenAI grammar tools
  • documents the Rust API and native grammar-tool history behavior in crates/ai/README.md

This is based on the behavior introduced by Pi PR #6341.

OpenAI Responses

  • emits native custom tool definitions for supported grammar tools
  • streams native custom_tool_call input and replays it as custom_tool_call / custom_tool_call_output history
  • keeps the public ToolCall.arguments representation consistent between native grammar tools and function-tool fallback
  • routes reasoning, text, and tool events by Responses output_index, including interleaved output items
  • uses terminal response.output_item.done data as authoritative and backfills terminal reasoning encrypted_content when necessary
  • preserves assistant message phase across turns with versioned text signatures, matching Pi commit 87d7138
  • preserves distinct Responses call IDs and item IDs while normalizing cross-provider handoffs
  • handles null completed-message content as empty text
  • validates replayed grammar arguments instead of silently constructing invalid custom-tool history
  • maps terminal failed/incomplete events and treats an early stream EOF as an error
  • forwards tool_choice, service_tier, prompt-cache controls, and reasoning controls using Pi-compatible wire shapes
  • supports message-anchored deferred tools through Responses tool search

OpenAI Chat Completions and compatible providers

  • supports constrained schemas and native grammar/custom-tool streaming where the provider advertises support
  • accumulates interleaved text, reasoning, and parallel tool-call deltas independently
  • buffers encrypted reasoning details that arrive before their matching tool call
  • preserves xAI reasoning details during replay
  • preserves the distinct Z.AI and Qwen thinking request shapes instead of treating them as interchangeable
  • preserves Z.AI reasoning content across turns and aligns its maximum thinking-level metadata with Pi
  • supports Kimi-style message-anchored deferred tool loading
  • keeps tool-call IDs unique across APIs and maintains tool-result cache anchors
  • uses Pi-compatible placeholders for empty tool results and preserves bridge state when empty assistant messages are skipped
  • maps provider finish_reason failures and reports streams that end without a terminal finish reason
  • forwards explicit OpenRouter-compatible routing for custom providers without relying on URL detection

Anthropic Messages

  • supports strict tool schemas and message-anchored tool_reference loading
  • honors an explicit null off entry in the model thinking-level map
  • emits the correct disabled-thinking payload for supported budget/adaptive reasoning models
  • preserves sparse usage from message_start when a later message_delta omits usage
  • aligns cache-control placement, long-cache compatibility, and session-affinity behavior with Pi
  • supports request-scoped Anthropic auth-token and API-key resolution
  • accepts header-only authentication for gateway/proxy configurations

Agent loop and shared types

  • does not execute tool calls from a response truncated by the output-token limit
  • ignores progress updates emitted after a tool execution has settled, including while another parallel tool is still running
  • records tool-provided usage in ToolResultMessage, exposes it to after_tool_call, and allows the hook to replace it
  • serializes tool execution usage in transcripts without adding it to the main model-context accounting
  • records addedToolNames on tool results so tool definitions become available from the correct transcript point onward
  • estimates context usage from the latest reliable provider usage plus trailing messages
  • clamps requested output tokens to the remaining context window with Pi's safety margin
  • accepts Pi's lax null/missing message content and normalizes it to empty content
  • aligns context-overflow detection with Pi's positive and negative patterns

Provider-scoped configuration and authentication

  • adds request-scoped provider environment overrides, with request values taking precedence over process environment variables
  • applies PI_CACHE_RETENTION consistently across Responses, Chat Completions, and Anthropic
  • accepts non-empty Authorization, x-api-key, or cf-aig-authorization headers where the matching Pi provider allows header-only authentication
  • keeps normal provider API-key discovery as the fallback

GitHub Copilot parity

  • ports Pi's device-code polling behavior, including authorization_pending, slow_down, expiry, and cancellation handling
  • persists GitHub refresh tokens and access-token expiry, refreshes proactively, and retries after authentication failures
  • validates device verification URLs before presenting them to the user
  • discovers available Copilot models with GET /models, stores their IDs with the credentials, and filters the configured model catalog accordingly
  • derives the Copilot API base URL from the token or enterprise domain
  • synchronizes Pi's fixed client headers:
    • User-Agent: GitHubCopilotChat/0.35.0
    • Editor-Version: vscode/1.107.0
    • Editor-Plugin-Version: copilot-chat/0.35.0
    • Copilot-Integration-Id: vscode-chat
    • X-GitHub-Api-Version: 2026-06-01 for model discovery
  • synchronizes dynamic inference headers: X-Initiator, Openai-Intent: conversation-edits, and Copilot-Vision-Request: true when appropriate

Copilot model policy enablement is intentionally not included. ai.rs does not maintain a hard-coded model-ID policy list or call POST /models/{id}/policy; consumers can add that policy if they need it.

Compatibility and public API additions

The main data-model additions are:

  • ConstrainedSampling, ConstrainedSamplingConfig, ConstrainedSamplingStrict, and grammar variants
  • provider capability flags for grammar, strict tools, deferred tools, tool search, and tool references
  • request-scoped ProviderEnv
  • ToolResultMessage.usage and ToolResultMessage.added_tool_names

Existing ordinary function tools continue to work without opting into constrained sampling. Provider-specific features remain capability-gated.

Verification

  • mise run fmt
  • mise run check
  • strict mise run clippy
  • mise run test-ai — 517 tests passed
  • GitHub CI Build and Test passed
  • the simple-coding-agent example successfully completed a real coding interaction against this branch
  • aiproxy was temporarily pointed at this exact ai.rs checkout:
    • cargo check --workspace passed
    • aiproxy-http: 77 tests passed
    • aiproxy-core: 49 tests passed
    • aiproxy-cli: 55 tests passed
  • a real codex exec through aiproxy exercised a native grammar/custom tool, replayed its output into the next Responses request, and continued inference successfully
  • a real Codex collaboration run successfully spawned a child, delivered its encrypted task, called collaboration.wait_agent, and returned the child result; the namespace/encryption compatibility fix for that final wire boundary is in aiproxy PR #57, not in ai.rs

Port the constrained-sampling behavior from Pi PR #6341, including strict schemas, native grammar tools, custom tool streaming and replay, and provider capability metadata.
Lock in the capture, versioned text-signature encoding, and replay behavior from Pi commit 87d71380.
Port Pi output_index slot routing and terminal reasoning signature backfill from 8c9dbffa and 1f0dbc00. Preserve Pi terminal item precedence and add interleaving, replay, and abort regression coverage.
Port Pi d9f7f814 and bc41f612 for unique cross-API tool IDs and tool-result cache anchors. Port Pi 351efc82 and daab056a so length-truncated calls are not executed and settled tools ignore late progress updates. Mirror the upstream regression scenarios, including parallel late updates.
Ports the applicable agent-loop portion of Pi commit 2fd3868401f0fe79496f3df731b20348ba57538a, including usage observation and replacement in after_tool_call plus transcript serialization.
Ports Pi commit 9ccfcd7cfcacdf593c0b24929d1d847e6cdf6711 by honoring an explicit null off entry in the model thinking-level map.
Ports Pi commit b9bfa7ed46ef19f48991473eae202be768623702 so explicit OpenRouter-compatible routing is forwarded by custom providers without URL detection.
Ports Pi commit 7d0497fdb752a05a9327864951ae7d6fcc4a9617 by buffering encrypted reasoning details until their matching streamed tool call arrives.
Ports the supported-provider portion of Pi commit 7f29e7a3697dc77416818faea4f4af29a13ce079, including request-scoped PI_CACHE_RETENTION precedence for Responses, Chat Completions, and Anthropic.
Ports Pi commit 2d597f0212145c9e4b8d6f66d85349e5e2cb86a4 by normalizing completed message items with null content to empty text.
Ports Pi commit 64b51efb6ef6f1e42676e866da906a5f900e592d so Z.AI uses thinking.type while Qwen retains enable_thinking.
@prabirshrestha prabirshrestha changed the title feat: port Pi provider and agent behavior feat: align provider and agent behavior with Pi Jul 27, 2026
@prabirshrestha
prabirshrestha merged commit 49f9bb9 into main Jul 27, 2026
1 check passed
@prabirshrestha
prabirshrestha deleted the feat/constrained-sampling branch July 27, 2026 03:11
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