Export the provider registry through harness WASM and remove TS OpenAI
Problem
TypeScript maintained a separate OpenAI-only provider instead of using the Rust provider registry. Its conversation writer also lost provider metadata and ordered reasoning when saving tool exchanges.
Design
Export the registry and immutable resolved providers from the harness WASM artifact. ModelRegistry.resolve("provider/model", options) implements the TypeScript provider contract through generated bindings. resolveAlloy(AlloySpec, ModelFeatures) exports native capability admission and minimum capacities; select(operationId, ordinal: bigint, role) uses the same deterministic policy on resume. Rust performs protocol projection, streaming, usage and verified counting over injected browser fetch. The harness supplies dispatch identity; cancellation and iterator return abort pending headers and bodies.
An owner content reader resolves bounded text and verifies pinned bytes, digest and UTF-8 before HTTP. Reference markers contain the canonical FileRef, preserving volume, path and revision. A single explicit provider.prepare(request) handle admits canonical input, reads and verifies pinned content, projects the provider input once, and retains immutable HTTP bytes for countTokens() and generate(dispatch). Both HTTP serializations borrow the original tool arguments; counters receive the actual resolved file projection. Native preparation retains allocation custody and reuses encoded bytes during permitted authentication recovery.
The TypeScript loop retains canonical reasoning/call order and complete parallel batches. Rust requests projection artifacts through the shared owner reader; delete the TypeScript preloading loop that missed saved reasoning. MemoryConversation writes existing canonical reasoning events and RecordedInvocation artifacts into its volume, then selects that history through Rust. Shared event admission validates the complete saved invocation before any tool handler runs.
Qualification executes provider WASM tests through the existing wasm-bindgen runner beside the harness WASM step. The Node wrapper caches the runner's generated artifacts by module digest, resolved runner path and executable content hash, Node version, arguments, environment and wrapper source; unchanged test binaries execute the same generated runner JS directly. Build the provider crate as the shared harness WASM artifact; generated outputs remain ignored.
Deletions
Delete openai.ts and openai.test.ts (536 lines against current main), remove their re-export and repoint callers to the generated registry facade. Remove owned argument/count-body copies through the inherited #756 API, the lossy reference marker and the obsolete empty-file estimation path. Current delta over main: +3634/-1019, net +2615 lines; most additions are fixtures, properties and models.
Tests run
All Linux builds/tests and the Windows compile proof ran sequentially on bb1 through bs.ps1. No live keys, real login files, network endpoints or sleeps.
- Native providers: 214 passed in 0.61 s, including the unchanged stock workflow gate, shrinking preparation equivalence, independent counter-wire expectations and generation immutability and negative controls.
- Production-lock Loom models and negative controls: 15 passed in 0.18 s. Synchronization code is unchanged in this review delta.
- Actual WASM: 15 passed in 0.11 s, including random native/binding alloy parity across all policies and full-width turn indices, minimum capacities, resume stability, lost-ordinal control, verified file reads, HTTP/cancellation and portable JSON boundaries.
- Paired preparation measurements: canonical-input copies/decodes/re-encodes and verified file reads fall from 2 to 1 for count plus generation. Independent old paths are the negative control; HTTP bodies and verified counts agree across all adapters.
- Paired WASM runner measurements: cold generation/execution 21.293 s; unchanged binary through the input-digest cache 0.306 s. This executes the upstream generated runner; it does not skip tests. Cache-key properties include omitted module/runner controls, same-version wrapper separation, and CJS/MJS discovery.
- Strict harness production and all test type checking passed, including the direct public FileRef callback and wrong-owner type control. Strict Pi production/type checks and focused cache-script checkJs passed.
- Provider TS: 107 tests across 11 files in 3.99 s. Separate runtime: 35 in 2.03 s. Pi: 7 in 0.789 s. Cache properties and same-version wrapper regression: 3 in 0.17 s. Public-export consumer fixture: 2 in 0.755 s, against the qualified linked package exports.
- Pinned-nightly provider_stream fuzz: 256 runs in 1 s, using the checked-in corpus.
- Windows GNU: cargo check --locked for harness/providers --lib --tests, filesystem/test-support/memory feature closure, passed in 2m04s. No local builds.
- git diff --check, caller searches, LF/NUL and file-size checks passed. Generated artifacts remain ignored.
Decisions
- #753 and #756 are merged. Published history uses signed merges and ordinary pushes; this PR carries only browser/provider/TS migration work over main.
- Browser credentials are explicit API keys; Codex login remains native-only. Provider catalogs are config-volume data and the registry is a request-time snapshot. Fetch/content callbacks are transient I/O; reasoning, calls and results remain files in the existing transcript, with no second durable store.
- One literal JSON decoder preserves private serde keys in model/options/completion values. The existing reserved-key rejection for executable tool artifacts remains enforced before handlers. Arbitrary JSON maps cross JavaScript through safe Map conversion and Object.fromEntries, preserving own
__proto__keys. - Typed public numeric fields use exact JS Numbers. Arbitrary model JSON uses BigInt for 64-bit integers; the existing portable tool-number admission limit still rejects unsafe tool arguments. Counting returns the Rust-owned bigint contract.
- The existing RecordedInvocation format replaces bare invocation artifacts under the no-compatibility-shims direction. Previously discarded signatures cannot be reconstructed; there is no legacy reader.
- Validators and argument normalization inherit #753/#756. Total media/schema repair remains a follow-up. Warning-free wasm32 cfg gating and CI deny-warnings follow immediately after this PR; no allow attributes are added.
Additional decisions for this review:
- Preparation is an explicit request handle, rather than mutable registry/global caching. Repoint the TS runtime, scripted fixtures, Pi and installed consumer; delete the separate count/generation preparation entry points. Canonical task/transcript files remain authoritative; the handle only owns transient I/O materialization.
- The public TS facade and providers import the same generated model mappings. Generated discriminated FileRef/VolumeRef types preserve owner/class correlation and the public agent identity brand; readers require no FileRef cast.
- Counter and generation endpoints have different wire envelopes, so their two HTTP byte strings remain distinct. Projection and source verification happen once, and each dispatch borrows the retained bytes.
- Prepared handles implement standard disposal. The TS loop disposes the materialization before invoking tools, including stream errors; completion/failure lifetime tests cover both paths. Pi declares the same ESNext.Disposable library as harness. There is no fallback provider interface.
- The latest review delta is +175/-93, net +82 across 8 files. Signed head: 8a9a6818846cf8b614f0ffca1836274a7e990e9a.
Final re-review decisions and verification:
- Resolve the actual runner executable and hash its contents, retaining its path in the cache identity. Do not execute --version on warm runs. Two wrappers reporting the same upstream version each generate once and reuse their own bindings; the version-only and omitted-runner controls fail the same oracle. The explicit process launcher keeps these fixtures portable to Windows without executable shebang support.
- Delete BorrowedCountBody. Native admission and browser preparation both use counting::wire::body, which borrows the projected generation and shares its field serializer. Gemini's count envelope overrides the model during serialization; the original generation remains immutable. The shrinking property checks independent expected wire shapes for all four adapters, with an unfiltered-request negative control through the same checker.
- Current focused Linux results: native providers 214 in 0.61 s; WASM 15 in 0.12 s, cached runner total 0.306 s; script tests 3 in 0.17 s; strict script checkJs passed. Unchanged TS, Loom and fuzz paths retain their prior evidence above. Windows GNU providers --lib --tests passed in 1m28s through bs.ps1. No local builds.
README split: restored typescript/packages/pi/README.md exactly to main in signed commit 8a9a6818846cf8b614f0ffca1836274a7e990e9a. The one-sentence correction is now README-only PR #796, explaining the prepared-request projection behavior. No human-authorship checkbox was ticked. Code is byte-identical to the validated d21b4fb4e head; no rebuild for this documentation-only revert.