Skip to content

Add a Swift runtime for Prompty - #439

Merged
Seth Juarez (sethjuarez) merged 11 commits into
mainfrom
sejuare-microsoft/swift-runtime
Aug 19, 2026
Merged

Seth Juarez (sethjuarez) merged 11 commits into
mainfrom
sejuare-microsoft/swift-runtime

Conversation

@sethjuarez

@sethjuarez Seth Juarez (sethjuarez) commented Aug 4, 2026 •

Copy link
Copy Markdown
Member

Adds a Swift implementation of the Prompty runtime. There was no Swift runtime in this repo before; nothing existing changes behaviour.

The Rust runtime is the behavioural reference throughout, and both are held to the same cross-runtime vectors emitted into schema/tsp-output/.typra-generated/vectors.json.

Note

Status: complete, green, and mergeable. No shim, no emitter pin gymnastics.

This PR was rebuilt from scratch on the published @typra/emitter@0.9.1. The earlier version of this branch stood up a post-generation repair shim against an old emitter and was gated on an upstream release; that entire approach is gone. The one real Swift-only emitter defect it was working around (absent required scalar/dict fields on discriminated-union subtypes threw instead of defaulting, where Rust uses unwrap_or_default()) was fixed at source in Typra (sethjuarez/typra#231), released as @typra/emitter@0.9.1, and consumed here. The generated Swift now compiles and round-trips with no hand edits to generated code and no shim.

What's here

Two SwiftPM packages, and the split is the point:

Package Module Contents Hand-written?
prompty-model PromptyModel Domain types + pipeline protocols + generated model tests No — Typra-generated
prompty Prompty Loader, renderers, parser, registry, harness Yes
prompty PromptyOpenAI OpenAI executor and processor Yes

The runtime defines no domain types of its own. Prompty, Model, Message, ContentPart, Tool and the four pipeline protocols (Renderer, Parser, Executor, Processor) all come from the generated model, and the hand-written code conforms to them.

Covered: loading (including ${env:} / ${file:}), rendering, parsing, preparation, protocols and registries, provider execution and processing, tools and turns, structured output, streaming, and harness durability/replay.

Model contract (verified against the generated Swift)

The rebuild pins down the cross-runtime contract exactly as Rust implements it:

  • Connection is a closed union (reference, remote, key, anonymous, oauth, foundry). An unknown kind is rejected (unknownDiscriminator), matching the Rust panic!. Inherited base fields (authenticationMode, usageDescription) are emitted on every subtype and round-trip.
  • Tool is an open union. An unknown kind is preserved as a CustomTool carrying its raw payload, and re-serialises losslessly.
  • Absent required complex fields throw (e.g. connection on McpTool / OpenApiTool / CustomTool) — the strict validate_input_at contract shared by all runtimes.
  • Absent required scalar/dict fields on subtypes default silently — the 0.9.1 fix, matching Rust's unwrap_or_default().
  • @coerce normalises a bare scalar shorthand into {kind, example} instead of throwing.
  • ContentPart is closed; empty/missing kind surfaces as invalidField.

Tests

  • prompty-model (generated tests): 273 tests, 0 failures.
  • prompty runtime: 177 tests, 0 failures, 10 skipped (live-provider E2E that need OPENAI_API_KEY, plus self-guarding vector tests that auto-activate when the property_scalar_coercion vectors land from Define canonical engine port and connection contracts #447).

Two hand-written test files (ConnectionRoundTripTests, WildcardPreservationTests) were deleted: both encoded a now-invalid open-union Connection with a .unknown case the closed union does not have. Real Connection coverage lives in GeneratedModelRoundTripTests.

CI: .github/workflows/prompty-swift-check.yml builds and tests both packages on ubuntu + macOS with Swift 6.0, on any PR touching runtime/swift/** or schema/**.

Commits

cd79a457 Add Swift emit-target and generated PromptyModel (emitter 0.9.0, no shim)
f3e3a75d Adopt @typra/emitter 0.9.1 and regenerate PromptyModel
603ddf02 Reconcile hand-written runtime tests to the 0.9.1 model contract
2feec8f6 Add Swift build and test CI workflow

Emitter changes upstreamed

fix(swift): default absent required scalar/dict on union subtypes — sethjuarez/typra#231, merged and released as @typra/emitter@0.9.1. No generated code in this repo was hand-edited to compensate.

Copilot AI lite review requested due to automatic review settings August 4, 2026 08:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 08:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 09:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 11:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 11:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 12:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 12:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 12:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 13:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 13:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 14:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 14:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 15:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 4, 2026 15:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 5, 2026 02:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 5, 2026 03:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 5, 2026 03:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 5, 2026 03:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Copilot AI review requested due to automatic review settings August 5, 2026 03:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

@sethjuarez

Copy link
Copy Markdown
Member Author

Stale against current main — needs re-authoring, not a mechanical rebase. This PR predates two shifts it can't survive a merge across: the @typra/emitter 0.4.x -> 0.9.0 jump and the schema/model/ source reorg (contracts/ conformance/ operations/ wire/) with generated-model relocation across the runtimes. The concept here is still wanted and is not yet on main, so leaving this open as a tracker rather than closing. Re-author fresh on the 0.9.0 contracts/ layout. (Context: #481 assessed all 11 pre-0.9.0 in-flight PRs.)

…0, no shim)

The Typra Swift emitter at 0.9.0 emits a library that compiles cleanly
without the source-defect shim that earlier versions (0.4.x) required, so
the generated PromptyModel is committed as-emitted with no post-processing.
This keeps Typra-emitted files unmodified, per project principle.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bump schema dep to ^0.9.1 (published from Typra PR #231) which fixes the
Swift-only BUG 1: absent required scalar/dict fields on discriminated-union
subtypes now default silently (matching Rust unwrap_or_default) instead of
throwing. Re-enable the Swift test-dir in tspconfig and regenerate.

Regenerated PromptyModel now:
- drops the absent-required-scalar throws across every Connection subtype
- emits inherited Connection base fields (authenticationMode, usageDescription)
  on each subtype so they round-trip
- emits @Coerce for bare-scalar Property shorthand
- ships generated per-model + conformance tests (273 tests, 0 failures)

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

Align the hand-written Prompty runtime's tests with the corrected generated
model contract (closed Connection union, open Tool union, scalar/dict leniency,
inherited base fields, @Coerce). No runtime code changed — all edits are
test-side, and no generated code was touched.

- GeneratedModelRoundTripTests: supply required connection to mcp/openapi/custom
  fixtures; assert Connection base fields round-trip; assert unknown Connection
  kind is rejected (closed union) rather than preserved
- ContentPartDiscriminatorTests: empty/missing kind now surface as invalidField;
  give the shared CustomTool fixture its required connection
- PropertyKindDispatchTests: assert bare-scalar @Coerce shorthand coerces to
  {kind, example} instead of throwing
- ToolBindingTests: drop the canonicalizeParameters shim and its tripwire now
  that vectors.json declares canonical parameters; load tools verbatim
- delete ConnectionRoundTripTests and WildcardPreservationTests: both encoded a
  now-invalid open-union Connection with a .unknown case that the closed union
  does not have; closed-union coverage lives in GeneratedModelRoundTripTests

prompty-model: 273 tests, 0 failures. prompty runtime: 177 tests, 0 failures,
10 skipped (live-API + not-yet-landed vector guards).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add prompty-swift-check.yml mirroring the other per-runtime -check workflows:
runs on PRs touching runtime/swift/** or schema/**, and is reusable via
workflow_call. Builds and tests both the generated prompty-model package and
the hand-written prompty runtime on ubuntu and macOS with Swift 6.0.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@sethjuarez
Seth Juarez (sethjuarez) force-pushed the sejuare-microsoft/swift-runtime branch from 039ad07 to 2feec8f Compare August 18, 2026 18:25
The committed Swift model tree was generated with @typra/emitter 0.9.2
(the caret ^0.9.1 range resolved 0.9.2 in node_modules), but the
lockfile pinned 0.9.1. CI's `npm ci` therefore regenerated Swift with
0.9.1, producing output that differed from the committed 0.9.2 tree
(Package.swift, Contracts/Connectivity/*.swift, etc.) and failing the
"regeneration produces no diff" check.

Bump the pin to the latest published release (0.9.2) and refresh the
lockfile so CI regenerates with the same emitter that produced the
committed tree. A local regen on 0.9.2 yields no Swift diff, confirming
the committed tree already matches 0.9.2 output.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The macOS job installed a standalone Swift 6.0.3 toolchain via
swift-actions/setup-swift, but that compiler is then used against
macos-latest's newer Xcode SDK (built with a later Swift), failing with
"this SDK is not supported by the compiler. Please select a toolchain
which matches the SDK." The Linux job is unaffected (no Xcode) and keeps
passing.

Split the toolchain setup by OS: Linux keeps the pinned standalone Swift
6.0 toolchain, while macOS selects Xcode via maxim-lobanov/setup-xcode
and uses its bundled Swift, so the compiler and SDK are always matched.
Aligns with issue #474 (toolchain pinning/reproducibility).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The Swift emit target's driver shells out to `swift-format` after
emitting, but wraps the call in a catch that silently falls back to the
emitter's own deterministic formatting when swift-format is absent (no
warning is printed). The schema-repro-check regen runner has no Swift
toolchain, so it regenerated the compact deterministic output while the
committed tree had been formatted by swift-format on a machine that had
it. The two never matched, so "regeneration produces no diff" failed
across the whole Swift tree with no way for the gate's missing-formatter
guard to attribute it (the driver emits no "Warning:" line).

Set `format: false` on the Swift emit target (a sanctioned per-target
emitter option) so the emitter's deterministic output is the single
source of truth on every platform, with or without swift-format, and
regenerate. The change is pure line-wrapping (long signatures inline
instead of wrapped); the Swift is semantically identical and still
compiles and passes both the ubuntu and macOS test jobs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pin to the latest published emitter (0.9.3) to keep every runtime on the
same version. 0.9.3 only changes relative Python cancellation-token
import depth handling, so the Swift emit target is unaffected and
regeneration produces no Swift diff (format is disabled for Swift, so
its deterministic output is unchanged regardless).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Regenerate the Swift model tree against the canonical engine port and
connection contracts introduced in #447. Adds the Swift EngineToolPort,
EnginePermissionPort, EnginePostCommitPort, and EngineDurabilityPort
protocols (native async throws cancellation) and propagates the
Connection forward-compat `unknown` fallback into tool connection
defaults.

Deterministic emitter output (format: false); no hand edits.

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

#447 redefined Connection as an open union: an unknown discriminator now
survives as the wildcard `unknown` case instead of throwing (matching Rust's
ConnectionKind::Unknown). Update the hand-written round-trip test to assert
preservation rather than rejection, mirroring the Tool open-union test above.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@sethjuarez
Seth Juarez (sethjuarez) merged commit 55c5c2c into main Aug 19, 2026
16 checks passed
Seth Juarez (sethjuarez) added a commit that referenced this pull request Aug 19, 2026
Swift runtime landed on main (#439) after this branch was authored, so
regenerating #438's canonicalized StreamFailure contract against current
main emits the Swift model files (FailureChunk case on StreamChunk, the
StreamFailure struct, StreamFailureOutcome enum) plus the generated
conformance test. Pure typra output; no hand edits.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Seth Juarez (sethjuarez) added a commit that referenced this pull request Aug 19, 2026
* feat(schema): classify stream failures

Add a canonical StreamFailure contract and FailureChunk stream chunk kind so provider stream errors can be classified as determinate or indeterminate. Regenerated all runtime models, vscode schemas, and reference docs from the pinned emitter.

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

* feat(runtime): classify stream failures across rust and typescript

Port the classified stream-failure behavior onto the regenerated
FailureChunk/StreamFailure model. Rust (prompty pipeline + prompty-openai
processor) and TypeScript (core pipeline, openai/anthropic processors)
now surface determinate vs indeterminate stream failures with matching
process vectors. Python, Go, and C# receive the feature via the
regenerated model classes.

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

* feat(swift): regenerate stream-failure contract for Swift runtime

Swift runtime landed on main (#439) after this branch was authored, so
regenerating #438's canonicalized StreamFailure contract against current
main emits the Swift model files (FailureChunk case on StreamChunk, the
StreamFailure struct, StreamFailureOutcome enum) plus the generated
conformance test. Pure typra output; no hand edits.

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

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Seth Juarez <sethjuarez@users.noreply.github.com>
Seth Juarez (sethjuarez) added a commit that referenced this pull request Aug 19, 2026
…-port + open-union contracts

Brings the Java runtime up to parity with main after PRs #447 (engine
ports, open-union Connection, stream-failure) and #439 (Swift) landed.

- Resolve schema conflicts: union tspconfig.yaml (Java + Swift targets),
  pin @typra/emitter ^0.9.3, take main's package-lock + export-surfaces.
- Regenerate: adds Engine{Durability,Permission,PostCommit,Tool}Port,
  FailureChunk, StreamFailure(Outcome), UnknownConnection (+ generated
  round-trip tests); Connection becomes an open union, StreamChunk gains
  the failure case.
- Rewrite the hand-written unknown-connection-kind test to assert the new
  open-union round-trip (UnknownConnection preserves kind + extra keys)
  instead of expecting a throw.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@sethjuarez
Seth Juarez (sethjuarez) deleted the sejuare-microsoft/swift-runtime branch August 19, 2026 03:21
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.

2 participants