Agent Skillsag-ui-protocol/ag-ui › agui-dotnet-wire-types

agui-dotnet-wire-types

GitHub

用于在AG-UI .NET SDK中新增或修改协议类型,确保与TypeScript参考实现兼容,处理序列化上下文、转换器及公共API更新。

.github/skills/agui-dotnet-wire-types/SKILL.md ag-ui-protocol/ag-ui

Trigger Scenarios

添加新的AG-UI事件类型 修改消息角色或输入内容类型 编辑JSON序列化上下文或转换器 修复协议类型的PublicAPI分析器构建失败

Install

npx skills add ag-ui-protocol/ag-ui --skill agui-dotnet-wire-types -g -y
More Options

Non-standard path

npx skills add https://github.com/ag-ui-protocol/ag-ui/tree/main/.github/skills/agui-dotnet-wire-types -g -y

Use without installing

npx skills use ag-ui-protocol/ag-ui@agui-dotnet-wire-types

指定 Agent (Claude Code)

npx skills add ag-ui-protocol/ag-ui --skill agui-dotnet-wire-types -a claude-code -g -y

安装 repo 全部 skill

npx skills add ag-ui-protocol/ag-ui --all -g -y

预览 repo 内 skill

npx skills add ag-ui-protocol/ag-ui --list

SKILL.md

Frontmatter
{
    "name": "agui-dotnet-wire-types",
    "description": "Add or modify a wire\/protocol type in the AG-UI .NET SDK AGUI.Abstractions package — a new event, message, or content-part type, the AOT source-gen serializer context, or a polymorphic JSON converter, keeping it AOT-safe, JSON-wire-compatible with the TypeScript reference, and PublicAPI-clean. USE FOR: adding an AG-UI event type, adding a message role or input-content type, editing AGUIJsonSerializerContext, editing BaseEventJsonConverter \/ AGUIMessageJsonConverter \/ AGUIInputContentJsonConverter, fixing PublicAPI.Unshipped analyzer build failures on protocol types, wire-format round-trip serialization. DO NOT USE FOR: writing integration\/SSE tests (use agui-dotnet-integration-tests), server hosting\/endpoint code, or non-Abstractions packages."
}

AG-UI .NET Wire/Protocol Types

Add or change a serialized protocol type in sdks/dotnet/src/AGUI.Abstractions/. These types are the JSON wire format. The TypeScript SDK is the reference — every type must round-trip against its JSON. Add // Keep in sync with sdks/typescript/packages/core/src/{events,types}.ts to each new type.

The 5 source packages are AGUI.Abstractions, AGUI.Formatting, AGUI.Protobuf, AGUI.Client, AGUI.Server. Wire types live in AGUI.Abstractions only. (sdks/dotnet/AGENTS.md is the canonical reference for the serialization rules and conventions.)

Run all commands from sdks/dotnet/.

Hard rules (forgetting any breaks the build or wire compat)

Every change to a polymorphic type touches four coordinated spots. Miss one and either the build fails or the type silently won't deserialize:

  1. The concrete class (Events/, Messages/).
  2. The discriminator constant (AGUIEventTypes / AGUIRoles / AGUIInputContentTypes).
  3. The source-gen context registration (Serialization/AGUIJsonSerializerContext.cs).
  4. The hand-written converter Read and Write cases (BaseEventJsonConverter / AGUIMessageJsonConverter / AGUIInputContentJsonConverter).

Plus: PublicAPI.Unshipped.txt (analyzer fails the build) and a round-trip test.

Recipe: new EVENT type

  1. Class in Events/{Name}Event.cs, sealed, deriving BaseEvent. Override Type => AGUIEventTypes.{Name}.
  2. Constant in Events/AGUIEventTypes.cs: public const string {Name} = "SCREAMING_SNAKE_CASE";.
  3. Register [JsonSerializable(typeof({Name}Event))] in AGUIJsonSerializerContext.
  4. Converter in BaseEventJsonConverter: add a Read switch arm (AGUIEventTypes.{Name} => jsonElement.Deserialize(options.GetTypeInfo(typeof({Name}Event))) as {Name}Event) and a Write case {Name}Event x: JsonSerializer.Serialize(writer, x, options.GetTypeInfo(typeof({Name}Event))); break;.
  5. PublicAPI — add every new public member to src/AGUI.Abstractions/PublicAPI.Unshipped.txt (format below).
  6. Test in tests/AGUI.Abstractions.UnitTests/{Name}EventTest.cs (JsonDocument assertions).
[JsonPropertyName("type")]
public override string Type => AGUIEventTypes.RunStarted;

[JsonPropertyName("threadId")]                          // required: init to string.Empty
public string ThreadId { get; set; } = string.Empty;

[JsonPropertyName("parentRunId")]                       // optional: WhenWritingNull
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? ParentRunId { get; set; }

[JsonPropertyName("value")]                             // dynamic payload: JsonElement, never object
public JsonElement? Value { get; set; }

Variation: MESSAGE type

Same shape, but: folder Messages/, derive AGUIMessage, discriminator is role keyed on AGUIRoles constants (lowercase, e.g. "assistant"), and the converter is AGUIMessageJsonConverter (Read + Write switch arms). Sync against types.ts. Most roles serialize directly; AGUIUserMessage is hand-serialized because its content is string | array — follow the existing WriteUserMessage/DeserializeUserMessage pattern if a role needs union content.

Variation: INPUT-CONTENT part

Folder Messages/, derive AGUIInputContent, discriminator is type keyed on AGUIInputContentTypes constants (lowercase, e.g. "image"), converter is AGUIInputContentJsonConverter. Also add the same arm to AGUIMessageJsonConverter.DeserializeUserMessage's inner contentType switch, since user-message arrays inline-dispatch content parts.

Serialization / AOT rules

  • [JsonSerializable(typeof(T))] for every new type — non-negotiable for AOT (no runtime reflection serializer).
  • [JsonPropertyName("camelCase")] on every serialized property (explicit even though the context sets CamelCase).
  • [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] on every optional/nullable property.
  • Required strings init to string.Empty; collections init to [].
  • Serialize via the generated context: AGUIJsonSerializerContext.Default.{Type} (or options.GetTypeInfo(typeof(T)) inside converters).
  • Dynamic/arbitrary payloads are JsonElement / JsonElement?.
  • Never JsonSerializer.Serialize<object>(...), never pass raw strings through unparsed, never reflection-based serialization.

Polymorphic converter pattern

Hand-written JsonConverter<TBase>, keyed on a discriminator property. BaseEventJsonConverter is the exemplar: Read deserializes to JsonElement, reads the discriminator, switches to the concrete GetTypeInfo(...), and throws JsonException on unknown/null; Write switches on the runtime type. Both directions must list every concrete type. A new abstract family needs the converter referenced via [JsonConverter(typeof(...))] on the base class.

PublicAPI.Unshipped.txt

The analyzer fails the build if a public member is missing here. Add one line per member, e.g. for an event:

AGUI.Abstractions.RunStartedEvent
AGUI.Abstractions.RunStartedEvent.RunStartedEvent() -> void
AGUI.Abstractions.RunStartedEvent.ThreadId.get -> string!
AGUI.Abstractions.RunStartedEvent.ThreadId.set -> void
override AGUI.Abstractions.RunStartedEvent.Type.get -> string!
AGUI.Abstractions.AGUIJsonSerializerContext.RunStartedEvent.get -> System.Text.Json.Serialization.Metadata.JsonTypeInfo<AGUI.Abstractions.RunStartedEvent>

! = non-null reference, ? = nullable. Don't forget the source-gen context's generated JsonTypeInfo<T> getter line.

Round-trip test (mandatory)

Assert JSON property names by parsing with JsonDocument — asserting the deserialized object alone misses camelCase/discriminator bugs. Don't compare full JSON strings; no reflection.

var json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.CustomEvent);
using var doc = JsonDocument.Parse(json);
Assert.Equal("CUSTOM", doc.RootElement.GetProperty("type").GetString());
Assert.Equal("user_preference_updated", doc.RootElement.GetProperty("name").GetString());

Also test dispatch through the base: JsonSerializer.Deserialize(json, AGUIJsonSerializerContext.Default.BaseEvent) then Assert.IsType<CustomEvent>(evt). For wire-format drift, add a fixture under tests/AGUI.Abstractions.UnitTests/Compatibility/ produced by the TypeScript SDK and load it via FixtureLoader ({Category}CompatibilityTest naming).

Code style / naming

  • sealed class (no record); one type per file named for the type.
  • No tuples in public APIs — define a named type.
  • Event classes {Name}Event; event discriminators SCREAMING_SNAKE_CASE (member PascalCase); role/type/outcome discriminators are lowercase string constants in static classes — never enums.
  • Braces always; ArgumentNullException.ThrowIfNull(...) for public validation; no /// docs on internal/private.

Validate

dotnet build
dotnet test tests/AGUI.Abstractions.UnitTests/

Version History

  • 1a78c27 Current 2026-07-24 20:27

Same Skill Collection

skills/ag-ui-a2ui-integration/SKILL.md
.github/skills/agui-cross-sdk-parity/SKILL.md
.github/skills/agui-dojo/SKILL.md
.github/skills/agui-dotnet-agents-sync/SKILL.md
.github/skills/agui-dotnet-code-review/SKILL.md
.github/skills/agui-dotnet-cross-language-tests/SKILL.md
.github/skills/agui-dotnet-feature-workflow/SKILL.md
.github/skills/agui-dotnet-integration-tests/SKILL.md
.github/skills/agui-dotnet-sample-step/SKILL.md
.github/skills/agui-dotnet-sdk-docs/SKILL.md
.github/skills/agui-dotnet-transport/SKILL.md
.github/skills/agui-dotnet-unit-tests/SKILL.md
.github/skills/agui-playwright-validate/SKILL.md

Metadata

Files
0
Version
3bba136
Hash
c27d58d8
Indexed
2026-07-24 20:27

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-20 23:53
浙ICP备14020137号-1 $방문자$