Skip to content

feat(dart): first-party Dart SDK generated from the schema (V0) - #3073

Closed
mme wants to merge 9 commits into
mainfrom
mme/dart-sdk-v0
Closed

mme wants to merge 9 commits into
mainfrom
mme/dart-sdk-v0

Conversation

@mme

@mme mme commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

What

A first-party Dart client for AG-UI 1.0, package agui at sdks/dart, generated from spec/1.0/schema.json. It is a V0: a working skeleton for the Dart team to complete, with an explicit ledger of what is not implemented in sdks/dart/AGENTS.md. The community package at sdks/community/dart is untouched.

How

  • Generator. A new emitter spec/generator/dart.ts renders the protocol model into sdks/dart/lib/src/generated/ (models.dart, shapes.dart, version.dart): immutable classes with fromJson/toJson, sealed unions, enums with wire names, and a shape table for enforcement. Wired into generate.ts and the byte-for-byte drift gate. The emitter lays the code out the way dart format 3.13 does, since the generator cannot run the formatter; the Dart CI checks it with a pinned SDK.
  • Client. AbstractAgent and HttpAgent over server-sent events, pure Dart, package:http as the only dependency.
  • Pipeline. The TypeScript client's run pipeline ported stage for stage, with error and warning text kept verbatim: era shims for 0.0.39/0.0.45/0.0.57 peers, the compatibility boundary, enforcement (drop unknown types, strip unknown material with a warning per path, decode), chunk expansion, verification, and application of every event onto messages, state and pendingInterrupts.
  • Tests. The codec round-trips every document in spec/1.0/fixtures; the shared null-omission and agent-capabilities fixtures now list dart in producedBy; and the conformance corpus in spec/1.0/conformance/streams runs as a third lane (one expectOverrides.dart, for the decoder's spelling of a wrong-role rejection). spec/harness/conformance.test.ts and fixture.ts know the lane.
  • CI. .github/workflows/unit-dart.yml (analyze, format check, test; Dart pinned to 3.13.5). The TypeScript workflow's path filter includes the generated Dart so the drift gate runs on hand edits there.

Not in this PR (by design, see the ledger)

Subscriber API, middleware abstraction, resume validation, protobuf transport, legacy event conversion/compaction/activity history, copyWith/equality on generated types, docs pages, pub.dev publishing, a Flutter companion.

Verification

  • cd sdks/dart && dart analyze --fatal-infos && dart format --set-exit-if-changed . && dart test — 645 tests, all 72 conformance streams included
  • cd spec && pnpm test && pnpm lint && pnpm typecheck — 2577 tests
  • pnpm --filter @ag-ui/client typecheck and its conformance lane (69) after the fixture-type change

mme added 9 commits October 15, 2026 12:19
A new emitter renders the protocol model into sdks/dart/lib/src/generated:
immutable final classes with fromJson/toJson, sealed classes for the unions,
enums with wire names, typedefs for the aliases, and a shape table for the
enforcement stage. Output is laid out the way dart format 3.13 would lay it
out, since the generator cannot run the formatter; the Dart CI checks it.
…ated codec

Optional literal fields are stored rather than computed so an absent one
stays absent; the open RFC 6902 operations keep undefined members in an
extras map; omitOptionalNulls walks the generated shapes to drop whole
optional nulls from raw documents, the producer-side contract the shared
null-omission fixture pins. Dart joins producedBy in both shared fixtures.
The agent holds the conversation and state, builds the request (declaring
the protocol version unless the peer ceiling is pinned below it), and
runs one pipeline at a time; the HTTP agent posts JSON and reads
server-sent events through an injectable http.Client.
The boundary reads the tabled optional nulls as absent, translates the
retired THINKING_* events and legacy binary parts, and warns once per
field per run. Enforcement drops unknown event types, deep-strips unknown
material against the generated shapes with a warning per path, and
decodes; a malformed known field stays fatal. The 0.0.39, 0.0.45 and
0.0.57 downgrades are plain functions selected by the peer ceiling.
The remaining three stages of the TypeScript run pipeline, ported rule
for rule with the error and warning text kept verbatim: chunk events
expand into their triads, the lifecycle and attribution rules are
verified, and every event is applied onto messages, state and pending
interrupts, with a hand-written RFC 6902 over the generated operation
types. The pipeline now runs all stages in the TypeScript order.
Every stream in spec/1.0/conformance/streams is replayed as SSE bytes
through a mock http.Client into an HttpAgent and held to the fixture's
expectation; the spec harness and the TypeScript fixture type know the
dart lane. One override, for the decoder's spelling of a wrong-role
rejection. Also the example, README, changelog and the AGENTS.md
handoff with the ledger of what is not implemented.
A run now starts on listen, so a stream nobody listens to cannot lock the
agent; the peer ceiling is validated at construction; HttpAgent.close()
releases the client it created; a frame that is not JSON is a decode
error; the SSE parser delivers an event still open at end of stream, as
TypeScript does, and caps event size; agent.messages is a fresh read-only
view per applied event; the reducer's copy helpers are pinned by a test
that sets every field; the emitter resolves aliases on optional fields
and refuses an optional literal that narrows an enum; the drift gate runs
on hand edits to generated Dart; the Dart CI check has its own name;
AGENTS.md and README corrected accordingly.
@mme
mme requested a review from a team as a code owner October 15, 2026 11:09
Comment on lines +3 to +17
on:
push:
branches: [main]
paths:
- "sdks/dart/**"
- "spec/**"
- "sdks/fixtures/**"
- ".github/workflows/unit-dart.yml"
pull_request:
branches: [main]
paths:
- "sdks/dart/**"
- "spec/**"
- "sdks/fixtures/**"
- ".github/workflows/unit-dart.yml"
@github-actions

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1792062588 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1792062588' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1792062588' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1792062588' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1792062588' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1792062588' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1792062588' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1792062588

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: b841707

@pkg-pr-new

pkg-pr-new Bot commented Oct 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@3073

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@3073

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@3073

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@3073

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@3073

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@3073

@ag-ui/adk-js

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk-js@3073

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@3073

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@3073

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@3073

@ag-ui/antigravity

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/antigravity@3073

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@3073

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@3073

@ag-ui/claude-managed-agents

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-managed-agents@3073

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@3073

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@3073

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@3073

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@3073

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@3073

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@3073

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@3073

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@3073

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@3073

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@3073

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@3073

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@3073

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@3073

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@3073

commit: 4314a77

@contextablemark contextablemark 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.

I’d fix these two before merging: stopping a run can leave the agent stuck, and the generated message constructor can send invalid content. Both reproduced locally despite the 645 tests passing.

..headers['content-type'] = 'application/json'
..headers['accept'] = 'text/event-stream'
..body = jsonEncode(input.toJson());
final response = await client.send(request);

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.

P1 — cancelling a run can leave it stuck.

I reproduced this while waiting for response headers and again after RUN_STARTED with an idle SSE stream: subscription.cancel() stayed pending and agent.isRunning stayed true until the transport progressed or closed. That also blocks the next run.

Could we make cancellation unwind the transport and pipeline without waiting for the server, and add tests for both cases? Cancelling the subscription is the documented way to stop a run, so it needs to work when the server goes quiet.


/// What the person sent: either plain text, or an ordered list of parts for a
/// multimodal message.
final Object content;

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.

P2 — this lets invalid content go out on the wire.

UserMessage(id: 'u', content: 42) compiles and serializes as {"role":"user","id":"u","content":42}, but UserMessage.fromJson() rejects that same output. The constructor accepts anything because this field is Object.

Could we generate a typed text-or-parts API, or typed constructors with validation, so unsupported values cannot be sent? Please fix this in the emitter and cover valid text/multimodal round-trips plus invalid outbound content.

@mme mme closed this Oct 15, 2026
@macedondev

Copy link
Copy Markdown

Can I use the following registerCopilotKit configuration with the ag_ui Flutter package?

registerCopilotKit({
  path: "/ag-ui/app",
  resourceId: "copilotkit",
  a2ui: {
    enabled: true,
    injectA2UITool: true,
  },
});

How can I connect AG-UI to Flutter GenUI? Why is there no documentation explaining how to integrate them?

I haven't implemented the Flutter side yet. For now, I just want to research how to connect AG-UI with GenUI and determine the correct integration approach.

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.

4 participants