Skip to content

Commit 5a98f31

Browse files
authored
Merge branch 'main' into feat/langgraph-app-mode-itinerary
2 parents eee067b + 26a517c commit 5a98f31

88 files changed

Lines changed: 3727 additions & 299 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/website/content/AGENTS.md.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Production-ready chat, durable threads, interrupts, subagents, planning, memory, and generative UI for Angular agent apps.
44

55
## Install
6-
npm install @threadplane/chat @threadplane/langgraph
6+
npm install @threadplane/chat @threadplane/langgraph @langchain/core @langchain/langgraph-sdk marked
77

88
## Key requirement
99
`injectAgent()` MUST be called within an Angular injection context (component constructor or field initializer). Calling it in ngOnInit or any async context throws "NG0203: inject() must be called from an injection context".

apps/website/content/CLAUDE.md.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Production-ready chat, durable threads, interrupts, subagents, planning, memory, and generative UI for Angular agent apps.
44

55
## Install
6-
npm install @threadplane/chat @threadplane/langgraph
6+
npm install @threadplane/chat @threadplane/langgraph @langchain/core @langchain/langgraph-sdk marked
77

88
## Key requirement
99
`injectAgent()` MUST be called within an Angular injection context (component constructor or field initializer). Calling it in ngOnInit or any async context throws "NG0203: inject() must be called from an injection context".

apps/website/content/docs/a2ui/api/api-docs.json

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@
460460
{
461461
"name": "push",
462462
"signature": "push(chunk: string): A2uiMessage[]",
463-
"description": "",
463+
"description": "Push a JSONL stream chunk and return every complete A2UI envelope parsed from it.",
464464
"params": [
465465
{
466466
"name": "chunk",
@@ -881,19 +881,21 @@
881881
{
882882
"name": "createA2uiMessageParser",
883883
"kind": "function",
884-
"description": "",
884+
"description": "Creates a stateful parser for newline-delimited A2UI message streams.\n\nThe parser buffers incomplete lines, skips malformed JSON, and returns only\nrecognized A2UI envelopes: `surfaceUpdate`, `dataModelUpdate`,\n`beginRendering`, and `deleteSurface`.",
885885
"signature": "createA2uiMessageParser(): A2uiMessageParser",
886886
"params": [],
887887
"returns": {
888888
"type": "A2uiMessageParser",
889889
"description": ""
890890
},
891-
"examples": []
891+
"examples": [
892+
"```ts\nconst parser = createA2uiMessageParser();\nconst messages = parser.push('{\"beginRendering\":{\"surfaceId\":\"s1\",\"root\":\"root\"}}\\n');\n```"
893+
]
892894
},
893895
{
894896
"name": "deleteByPointer",
895897
"kind": "function",
896-
"description": "",
898+
"description": "Returns a new model with the key at `pointer` removed.\n\nEmpty string and `/` clear the whole model. Missing parent paths leave the\ninput model unchanged.",
897899
"signature": "deleteByPointer(model: Record<string, unknown>, pointer: string): Record<string, unknown>",
898900
"params": [
899901
{
@@ -913,12 +915,14 @@
913915
"type": "Record<string, unknown>",
914916
"description": ""
915917
},
916-
"examples": []
918+
"examples": [
919+
"```ts\nconst next = deleteByPointer({ user: { name: 'Ada' } }, '/user/name');\n```"
920+
]
917921
},
918922
{
919923
"name": "getByPointer",
920924
"kind": "function",
921-
"description": "",
925+
"description": "Reads a value from a model using the package's simple slash-separated pointer syntax.\n\nEmpty string and `/` target the root. Segments are not RFC 6901-unescaped;\nnormalize keys before using pointers that would require `~0` or `~1`.",
922926
"signature": "getByPointer(model: Record<string, unknown>, pointer: string): unknown",
923927
"params": [
924928
{
@@ -938,12 +942,14 @@
938942
"type": "unknown",
939943
"description": ""
940944
},
941-
"examples": []
945+
"examples": [
946+
"```ts\ngetByPointer({ user: { name: 'Ada' } }, '/user/name'); // 'Ada'\n```"
947+
]
942948
},
943949
{
944950
"name": "isLiteralBoolean",
945951
"kind": "function",
946-
"description": "",
952+
"description": "Returns true when `value` is an A2UI boolean literal wrapper.",
947953
"signature": "isLiteralBoolean(value: unknown): value is { literalBoolean: boolean }",
948954
"params": [
949955
{
@@ -962,7 +968,7 @@
962968
{
963969
"name": "isLiteralNumber",
964970
"kind": "function",
965-
"description": "",
971+
"description": "Returns true when `value` is an A2UI number literal wrapper.",
966972
"signature": "isLiteralNumber(value: unknown): value is { literalNumber: number }",
967973
"params": [
968974
{
@@ -981,7 +987,7 @@
981987
{
982988
"name": "isLiteralString",
983989
"kind": "function",
984-
"description": "",
990+
"description": "Returns true when `value` is an A2UI string literal wrapper.",
985991
"signature": "isLiteralString(value: unknown): value is { literalString: string }",
986992
"params": [
987993
{
@@ -1000,7 +1006,7 @@
10001006
{
10011007
"name": "isPathRef",
10021008
"kind": "function",
1003-
"description": "",
1009+
"description": "Returns true when `value` is an A2UI dynamic path reference.",
10041010
"signature": "isPathRef(value: unknown): value is { path: string }",
10051011
"params": [
10061012
{
@@ -1019,7 +1025,7 @@
10191025
{
10201026
"name": "resolveDynamic",
10211027
"kind": "function",
1022-
"description": "",
1028+
"description": "Resolves an A2UI dynamic value against a client data model.\n\nLiteral wrappers unwrap to their inner values, `{ path }` references read\nfrom the model by JSON-pointer path, arrays resolve recursively, and\nunrecognized plain values pass through unchanged.",
10231029
"signature": "resolveDynamic(value: unknown, model: Record<string, unknown>, scope: A2uiScope): unknown",
10241030
"params": [
10251031
{
@@ -1045,12 +1051,14 @@
10451051
"type": "unknown",
10461052
"description": ""
10471053
},
1048-
"examples": []
1054+
"examples": [
1055+
"```ts\nconst model = { customer: { name: 'Ada' } };\nresolveDynamic({ path: '/customer/name' }, model); // 'Ada'\nresolveDynamic({ literalString: 'Checkout' }, model); // 'Checkout'\n```"
1056+
]
10491057
},
10501058
{
10511059
"name": "setByPointer",
10521060
"kind": "function",
1053-
"description": "",
1061+
"description": "Returns a new model with `value` written at `pointer`.\n\nThe input model is not mutated. Missing intermediate objects are created.",
10541062
"signature": "setByPointer(model: Record<string, unknown>, pointer: string, value: unknown): Record<string, unknown>",
10551063
"params": [
10561064
{
@@ -1076,6 +1084,8 @@
10761084
"type": "Record<string, unknown>",
10771085
"description": ""
10781086
},
1079-
"examples": []
1087+
"examples": [
1088+
"```ts\nconst next = setByPointer({}, '/user/name', 'Ada');\n```"
1089+
]
10801090
}
10811091
]

apps/website/content/docs/a2ui/getting-started/quickstart.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Let's start with a real stream. An agent emits A2UI as newline-delimited JSON
2828
```text
2929
---a2ui_JSON---
3030
{"dataModelUpdate":{"surfaceId":"booking","contents":[{"key":"origin","valueString":"LAX"},{"key":"dest","valueString":"JFK"},{"key":"passengers","valueNumber":1}]}}
31-
{"surfaceUpdate":{"surfaceId":"booking","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","origin","submit"]}}}},{"id":"title","component":{"Text":{"text":"Book a flight","usageHint":"h2"}}},{"id":"origin","component":{"MultipleChoice":{"label":"Origin","options":[{"label":"LAX","value":"LAX"},{"label":"JFK","value":"JFK"}],"selections":{"path":"/origin"},"maxAllowedSelections":1}}},{"id":"submit_label","component":{"Text":{"text":"Search flights"}}},{"id":"submit","component":{"Button":{"child":"submit_label","primary":true,"action":{"name":"bookingSubmit","context":[{"key":"origin","value":{"path":"/origin"}},{"key":"dest","value":{"path":"/dest"}}]}}}}]}}
31+
{"surfaceUpdate":{"surfaceId":"booking","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","origin","submit"]}}}},{"id":"title","component":{"Text":{"text":{"literalString":"Book a flight"},"usageHint":"h2"}}},{"id":"origin","component":{"MultipleChoice":{"label":{"literalString":"Origin"},"options":[{"label":{"literalString":"LAX"},"value":"LAX"},{"label":{"literalString":"JFK"},"value":"JFK"}],"selections":{"path":"/origin"},"maxAllowedSelections":1}}},{"id":"submit_label","component":{"Text":{"text":{"literalString":"Search flights"}}}},{"id":"submit","component":{"Button":{"child":"submit_label","primary":true,"action":{"name":"bookingSubmit","context":[{"key":"origin","value":{"path":"/origin"}},{"key":"dest","value":{"path":"/dest"}}]}}}}]}}
3232
{"beginRendering":{"surfaceId":"booking","root":"root"}}
3333
```
3434

apps/website/content/docs/a2ui/guides/message-protocol.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Every envelope carries that `surfaceId`. A `dataModelUpdate` for `"booking"` onl
1515
As an **id-keyed adjacency list**. A `surfaceUpdate` carries a flat `components` array. Each entry has an `id` and a single `component` definition. Parent-child links are by id reference, not by nesting.
1616

1717
```json
18-
{"surfaceUpdate":{"surfaceId":"booking","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","origin","submit"]}}}},{"id":"title","component":{"Text":{"text":"Book a flight","usageHint":"h2"}}}]}}
18+
{"surfaceUpdate":{"surfaceId":"booking","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","origin","submit"]}}}},{"id":"title","component":{"Text":{"text":{"literalString":"Book a flight"},"usageHint":"h2"}}}]}}
1919
```
2020

2121
The `component` value is a **keyed union**: a single-key object where the key names the component type and the value holds its props — `{ "<Name>": { props } }`. `{ "Text": { ... } }` is a Text, `{ "Column": { ... } }` is a Column. There's no separate `type` field; the key *is* the type.
@@ -69,7 +69,7 @@ The stream is a sequence of single-key envelope objects. The parser recognizes e
6969
Defines (or replaces) the components for a surface.
7070

7171
```json
72-
{"surfaceUpdate":{"surfaceId":"booking","components":[{"id":"submit_label","component":{"Text":{"text":"Search flights"}}},{"id":"submit","component":{"Button":{"child":"submit_label","primary":true,"action":{"name":"bookingSubmit","context":[{"key":"origin","value":{"path":"/origin"}},{"key":"dest","value":{"path":"/dest"}}]}}}}]}}
72+
{"surfaceUpdate":{"surfaceId":"booking","components":[{"id":"submit_label","component":{"Text":{"text":{"literalString":"Search flights"}}}},{"id":"submit","component":{"Button":{"child":"submit_label","primary":true,"action":{"name":"bookingSubmit","context":[{"key":"origin","value":{"path":"/origin"}},{"key":"dest","value":{"path":"/dest"}}]}}}}]}}
7373
```
7474

7575
### `dataModelUpdate`

apps/website/content/docs/ag-ui/api/api-docs.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@
405405
{
406406
"name": "AgUiAgent",
407407
"kind": "interface",
408-
"description": "The neutral Agent contract, widened with the AG-UI adapter's optional\n`customEvents` signal (the chat composition feature-detects it to enable\nlive a2ui streaming) and the optional `clientTools` capability.\nMirrors langgraph's LangGraphAgent extension.",
408+
"description": "The neutral Agent contract, widened with the AG-UI adapter's\n`customEvents` signal (the chat composition feature-detects it to enable\nlive a2ui streaming), the browser client-tools capability, and concrete\nACTIVITY-backed `subagents`.\nMirrors langgraph's LangGraphAgent extension where the protocol surfaces\noverlap.",
409409
"properties": [
410410
{
411411
"name": "clientTools",
@@ -620,7 +620,7 @@
620620
"description": ""
621621
},
622622
"examples": [
623-
"```ts\nTestBed.configureTestingModule({\n providers: [provideFakeAgent({ responses: ['Hello from the fake agent'] })],\n});\n```"
623+
"```ts\nTestBed.configureTestingModule({\n providers: [provideFakeAgent({ tokens: ['Hello from the fake agent'] })],\n});\n```"
624624
]
625625
},
626626
{
@@ -646,6 +646,8 @@
646646
"type": "AgUiAgent<>",
647647
"description": ""
648648
},
649-
"examples": []
649+
"examples": [
650+
"```ts\nimport { HttpAgent } from '@ag-ui/client';\nimport { toAgent } from '@threadplane/ag-ui';\n\nconst agent = toAgent(new HttpAgent({ url: '/api/agent' }));\n```"
651+
]
650652
}
651653
]

apps/website/content/docs/ag-ui/api/inject-agent.mdx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,22 @@ These fields are stable across runtime adapters and are what chat components con
6565

6666
## AG-UI-specific surface
6767

68-
The AG-UI adapter extends the neutral `Agent` contract with one additional signal:
68+
The AG-UI adapter extends the neutral `Agent` contract with AG-UI-specific protocol surfaces:
6969

7070
| Field | Type | Description |
7171
|-------|------|-------------|
7272
| `customEvents()` | `CustomStreamEvent[]` | Custom events emitted by the backend during a run. Accumulates per run; resets on each new `submit()`. |
73+
| `clientTools` | `ClientToolsCapability` | Browser client-tool catalog, pending calls, and result resolution used by `<chat [clientTools]>`. |
74+
| `subagents()` | `Map<string, Subagent>` | `ACTIVITY_*` entries with `activityType: 'subagent'`, projected to the neutral subagent contract and keyed by `messageId`. |
7375

74-
`injectAgent()` returns the `AgUiAgent` type — the neutral `Agent` contract plus `customEvents` — so the signal is reachable directly, no cast required:
76+
`injectAgent()` returns the `AgUiAgent` type — the neutral `Agent` contract plus these AG-UI-specific fields — so they are reachable directly, no cast required:
7577

7678
```ts
7779
import { injectAgent } from '@threadplane/ag-ui';
7880

7981
const chat = injectAgent();
80-
chat.customEvents(); // Signal<CustomStreamEvent[]>
82+
chat.customEvents(); // CustomStreamEvent[]
83+
chat.subagents(); // Map<string, Subagent>
8184
```
8285

8386
The chat a2ui bridge reads `customEvents` to light up live generative-UI streaming when your backend emits `a2ui-partial` events. The consuming side is documented in chat's [A2UI overview](/docs/chat/a2ui/overview). See the [Custom Events guide](/docs/ag-ui/guides/custom-events) for backend wiring details.

apps/website/content/docs/ag-ui/api/to-agent.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ const agent = toAgent(source, { telemetry: myTelemetrySink });
2424

2525
## AgUiAgent
2626

27-
`toAgent()` returns an `AgUiAgent`, which extends the neutral `Agent` contract with one additional signal:
27+
`toAgent()` returns an `AgUiAgent`, which extends the neutral `Agent` contract with AG-UI-specific protocol surfaces:
2828

2929
| Field | Type | Description |
3030
|-------|------|-------------|
3131
| `customEvents()` | `Signal<CustomStreamEvent[]>` | Custom events accumulated during a run. Resets at the start of each new run. |
32+
| `clientTools` | `ClientToolsCapability` | Browser client-tool catalog, pending calls, and result resolution. The chat composition uses this when you pass `<chat [clientTools]>`. |
33+
| `subagents()` | `Signal<Map<string, Subagent>>` | `ACTIVITY_*` events with `activityType: 'subagent'`, projected to the neutral subagent contract and keyed by `messageId`. |
3234

3335
The standard `Agent` signals (`messages`, `status`, `isLoading`, `error`, `toolCalls`, `state`, `interrupt`) and actions (`submit`, `stop`, `regenerate`) are all present.
3436

apps/website/content/docs/ag-ui/concepts/architecture.mdx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ Every AG-UI event is passed through the reducer. The reducer updates Angular sig
8888
- `toolCalls` for tool call starts, arguments, results, and completion.
8989
- `state` for AG-UI state snapshots and JSON Patch deltas.
9090
- `interrupt` cleared on `RUN_STARTED` and set by the `CUSTOM` `on_interrupt` event.
91-
- `events$` for custom events.
91+
- `events$` for runtime-neutral custom-event side effects.
92+
- `customEvents` for accumulated non-`on_interrupt` `CUSTOM` events used by live a2ui and app-specific reactive UI.
93+
- `subagents` for `ACTIVITY_*` events with `activityType: 'subagent'`.
9294

9395
When the user submits input, the adapter builds a user message, appends it locally, adds it to the AG-UI source with `source.addMessage()`, then calls `source.runAgent()`.
9496

@@ -106,6 +108,12 @@ the final tool-call surface; with it, surfaces build up live.
106108

107109
The consuming side — how the chat composition turns these events into rendered surfaces — lives in chat's [A2UI overview](/docs/chat/a2ui/overview).
108110

111+
## Browser client tools
112+
113+
`AgUiAgent.clientTools` is the browser-tool bridge consumed by `<chat [clientTools]>`. The chat composition registers the browser catalog on the agent; the adapter threads that catalog into every `source.runAgent({ tools })` call, exposes unresolved browser-owned tool calls through `clientTools.pending()`, and sends the browser result back as a tool message before continuing the run.
114+
115+
See chat's [Client Tools guide](/docs/chat/guides/client-tools) for declaring `action()`, `view()`, and `ask()` tools. With AG-UI, the backend must bind the supplied AG-UI tool specs and finish the turn without emitting `TOOL_CALL_RESULT` for browser-owned tools so the client can resolve them.
116+
109117
## Provider choices
110118

111119
Use `provideAgent()` when you have a real AG-UI HTTP endpoint.
@@ -188,9 +196,10 @@ The AG-UI adapter currently covers:
188196
- Tool calls from `TOOL_CALL_*`.
189197
- Shared state from `STATE_SNAPSHOT` and `STATE_DELTA`.
190198
- Message replacement from `MESSAGES_SNAPSHOT`.
191-
- Custom events from `CUSTOM`.
199+
- Custom events from non-`on_interrupt` `CUSTOM` events, surfaced through both `events$` and `customEvents`.
192200
- Interrupts from `CUSTOM` events named `on_interrupt`.
193-
- Subagent/activity progress from `ACTIVITY_SNAPSHOT` and `ACTIVITY_DELTA`.
201+
- Browser client tools via `AgUiAgent.clientTools`.
202+
- Subagent progress from `ACTIVITY_SNAPSHOT` and `ACTIVITY_DELTA` events whose `activityType` is `subagent`.
194203
- Citations stored under `state.citations`.
195204

196205
These features are intentionally out of scope for the AG-UI adapter today:

apps/website/content/docs/ag-ui/getting-started/installation.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
## Install packages
1010

1111
```bash
12-
npm install @threadplane/chat @threadplane/ag-ui @ag-ui/client @ag-ui/core
12+
npm install @threadplane/chat @threadplane/ag-ui @ag-ui/client @ag-ui/core marked
1313
```
1414

1515
`@threadplane/chat` provides the chat UI primitives. `@threadplane/ag-ui` provides the adapter that wires an AG-UI backend into the `Agent` contract those primitives consume.
16+
`marked` is the required markdown parser peer used by `@threadplane/chat` when assistant messages render through `<chat>`.
1617

1718
## Peer Dependencies
1819

0 commit comments

Comments
 (0)