@threadplane/langgraph 0.0.47 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,28 @@
1
1
  # @threadplane/langgraph
2
2
 
3
- Adapter that wraps a LangGraph agent into the runtime-neutral `Agent` contract from `@threadplane/chat`. The Angular equivalent of LangGraph's React `useStream()` hook — signal-driven access to messages, status, tool calls, interrupts, subagents, regenerate, and thread history.
3
+ Adapter that wraps a LangGraph agent into the runtime-neutral `Agent` contract from `@threadplane/chat`. The Angular equivalent of LangGraph's React `useStream()` hook — signal-driven access to messages, status, tool calls, interrupts, subagents, branch history, and thread persistence.
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/@threadplane/langgraph">
7
+ <img alt="npm version" src="https://img.shields.io/npm/v/@threadplane%2Flanggraph?color=6C8EFF&labelColor=080B14&style=flat-square" />
8
+ </a>
9
+ <a href="https://angular.dev">
10
+ <img alt="Angular 20+" src="https://img.shields.io/badge/Angular-20%2B%20%7C%2021-6C8EFF?labelColor=080B14&style=flat-square" />
11
+ </a>
12
+ <a href="https://opensource.org/licenses/MIT">
13
+ <img alt="MIT" src="https://img.shields.io/badge/License-MIT-6C8EFF?labelColor=080B14&style=flat-square" />
14
+ </a>
15
+ </p>
16
+
17
+ > Talking to a non-LangGraph backend? See [`@threadplane/ag-ui`](https://www.npmjs.com/package/@threadplane/ag-ui) — same API shape, AG-UI protocol underneath.
4
18
 
5
- Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). MIT licensed.
19
+ ## What it does
20
+
21
+ - **`provideAgent()`** — wire the LangGraph adapter into Angular DI. Provided at the root injector or at any component subtree (multi-thread UIs work via Angular's hierarchical DI).
22
+ - **`injectAgent()`** — retrieve the configured `LangGraphAgent` in any component. Returns a `LangGraphAgent` whose entire state surface (`messages`, `status`, `isLoading`, `error`, `interrupt`, `toolCalls`, `subagents`, `queue`, `branch`, `history`, and more) is exposed as Angular Signals. No subscriptions, no `async` pipe, no zone.js required.
23
+ - **Human-in-the-loop** — `interrupt()` delivers a runtime-neutral interrupt value; `langGraphInterrupts()` exposes the raw LangGraph interrupt list when you need it.
24
+ - **Subagent streaming** — `subagents()` + `getSubagent(toolCallId)`, `getSubagentsByType(type)`, `getSubagentsByMessage(msg)`, and `activeSubagents()` surface streaming subgraph state without extra bookkeeping.
25
+ - **Time-travel and thread persistence** — `branch()` / `history()` / `experimentalBranchTree()` enable checkpoint navigation; `LangGraphThreadsAdapter` provides SDK-backed thread CRUD so you never have to hand-roll thread management.
6
26
 
7
27
  ## Install
8
28
 
@@ -10,18 +30,20 @@ Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). MI
10
30
  npm install @threadplane/langgraph @threadplane/chat
11
31
  ```
12
32
 
13
- **Peer dependencies:** `@angular/core ^20.0.0 || ^21.0.0`, `@langchain/core ^1.1.0`, `@langchain/langgraph-sdk ^1.7.0`, `rxjs ~7.8.0`
14
-
15
- ## What it does
33
+ **Peer dependencies:**
16
34
 
17
- - **`agent()`** — Angular Signal-based handle to a LangGraph streaming run. Returns `messages()`, `status()`, `isLoading()`, `error()`, `interrupt()`, `toolCalls()`, plus actions (`submit`, `stop`, `regenerate`, `reload`).
18
- - **`provideAgent()`** — configure the LangGraph endpoint once in `app.config.ts`. Per-call overrides are accepted by `agent()` itself.
19
- - **Thread persistence** — pass `threadId: signal(...)` + `onThreadId` to round-trip thread IDs through your own storage (localStorage, URL, etc.).
20
- - **`MockAgentTransport`** — deterministic in-memory transport for tests. Never mock `agent()` itself; swap the transport instead.
21
- - **`extractCitations()`** — populates `Message.citations` from LangGraph message metadata. Reads from `additional_kwargs.citations` (preferred) or `additional_kwargs.sources` (fallback).
35
+ ```
36
+ @threadplane/chat *
37
+ @angular/core ^20.0.0 || ^21.0.0
38
+ @langchain/core ^1.1.33
39
+ @langchain/langgraph-sdk ^1.7.4
40
+ rxjs ~7.8.0
41
+ ```
22
42
 
23
43
  ## Quick start
24
44
 
45
+ Configure the LangGraph endpoint once in `app.config.ts`:
46
+
25
47
  ```ts
26
48
  // app.config.ts
27
49
  import { provideAgent } from '@threadplane/langgraph';
@@ -30,15 +52,18 @@ export const appConfig: ApplicationConfig = {
30
52
  providers: [
31
53
  provideAgent({
32
54
  apiUrl: 'https://your-langgraph-platform.com',
55
+ assistantId: 'my-agent',
33
56
  }),
34
57
  ],
35
58
  };
36
59
  ```
37
60
 
61
+ Then call `injectAgent()` in any component and pass the result to `<chat />`:
62
+
38
63
  ```ts
39
64
  // chat.component.ts
40
65
  import { Component } from '@angular/core';
41
- import { agent } from '@threadplane/langgraph';
66
+ import { injectAgent } from '@threadplane/langgraph';
42
67
  import { ChatComponent } from '@threadplane/chat';
43
68
 
44
69
  @Component({
@@ -46,47 +71,143 @@ import { ChatComponent } from '@threadplane/chat';
46
71
  template: `<chat [agent]="chat" />`,
47
72
  })
48
73
  export class ChatComponentHost {
49
- chat = agent({
50
- apiUrl: 'https://your-langgraph-platform.com',
51
- assistantId: 'my-agent',
52
- });
74
+ protected readonly chat = injectAgent();
53
75
  }
54
76
  ```
55
77
 
56
- > `agent()` must be called within an Angular injection context (component field initializer or constructor). Calling it in `ngOnInit` or any async context throws `NG0203: inject() must be called from an injection context`.
78
+ > `injectAgent()` must be called within an Angular injection context — a component field initializer or constructor. Calling it in `ngOnInit` or any async context throws `NG0203: inject() must be called from an injection context`.
57
79
 
58
- ## Citations example
80
+ > Need a different agent for a specific component subtree (e.g., a sidebar showing a separate conversation)? Re-provide `provideAgent({...})` in that component's `providers: []` array — Angular's hierarchical DI takes care of the rest.
81
+
82
+ ## Capabilities
83
+
84
+ ### Messages, status, and errors
85
+
86
+ | Signal | Type | Description |
87
+ |---|---|---|
88
+ | `messages()` | `Message[]` | Accumulated chat messages from the stream |
89
+ | `status()` | `'idle' \| 'running' \| 'error'` | Runtime-neutral run status |
90
+ | `isLoading()` | `boolean` | `true` while a run is streaming |
91
+ | `error()` | `unknown \| null` | Last error, if any |
92
+
93
+ ### Human-in-the-loop (interrupts)
59
94
 
60
95
  ```ts
61
- // In your LangGraph node:
62
- const response = await llm.invoke([...]);
63
-
64
- return new AIMessage({
65
- content: response.content,
66
- additional_kwargs: {
67
- citations: [
68
- {
69
- id: 'doc-1',
70
- index: 1,
71
- title: 'Example Article',
72
- url: 'https://example.com/article',
73
- snippet: 'Relevant excerpt...',
74
- },
75
- ],
76
- },
77
- });
78
-
79
- // Message.citations auto-populates in @threadplane/chat via extractCitations()
96
+ const pending = chat.interrupt(); // runtime-neutral interrupt value
97
+ const raw = chat.langGraphInterrupts(); // raw LangGraph Interrupt[]
80
98
  ```
81
99
 
100
+ Resume by calling `chat.submit(response)`.
101
+
102
+ ### Tool calls
103
+
104
+ `toolCalls()` is a Signal of all tool call entries observed in the current run, updated incrementally as the stream progresses.
105
+
106
+ ### Subagents
107
+
108
+ ```ts
109
+ chat.subagents() // Signal<Map<string, Subagent>> of all subagents
110
+ chat.activeSubagents() // currently streaming subagents (SubagentStreamRef[])
111
+ chat.getSubagent(toolCallId) // look up by tool call ID
112
+ chat.getSubagentsByType(type) // filter by subagent type
113
+ chat.getSubagentsByMessage(msg) // filter by parent message
114
+ ```
115
+
116
+ ### Queue
117
+
118
+ `queue()` exposes pending run entries when the agent is configured with a multitask strategy that queues concurrent submissions.
119
+
120
+ ### Branch, history, and time-travel
121
+
122
+ ```ts
123
+ chat.branch() // current branch identifier Signal
124
+ chat.setBranch(b) // switch to a checkpoint branch
125
+ chat.history() // runtime-neutral history entries
126
+ chat.langGraphHistory() // raw LangGraph ThreadState[]
127
+ chat.experimentalBranchTree() // full branching tree for time-travel UI
128
+ ```
129
+
130
+ ### Actions
131
+
132
+ ```ts
133
+ chat.submit(input, opts?) // send a new message
134
+ chat.stop() // cancel the active run
135
+ chat.regenerate(assistantMessageIndex) // re-run from a prior assistant turn
136
+ chat.reload() // re-run the last submission
137
+ chat.switchThread(threadId) // load a different thread
138
+ chat.joinStream(runId, lastEventId?) // reconnect to an in-flight run
139
+ ```
140
+
141
+ ### Thread persistence
142
+
143
+ `LangGraphThreadsAdapter` is a drop-in, SDK-backed thread store. Provide it alongside the agent config:
144
+
145
+ ```ts
146
+ import { provideAgent, LangGraphThreadsAdapter, LANGGRAPH_THREADS_CONFIG } from '@threadplane/langgraph';
147
+
148
+ export const appConfig: ApplicationConfig = {
149
+ providers: [
150
+ provideAgent({ apiUrl: 'https://your-langgraph-platform.com' }),
151
+ { provide: LANGGRAPH_THREADS_CONFIG, useValue: { apiUrl: 'https://your-langgraph-platform.com' } },
152
+ LangGraphThreadsAdapter,
153
+ ],
154
+ };
155
+ ```
156
+
157
+ Pair it with the lifecycle helpers to keep your thread list fresh:
158
+
159
+ ```ts
160
+ import { refreshOnRunEnd, refreshOnTransition } from '@threadplane/langgraph';
161
+
162
+ refreshOnRunEnd(chat, () => threadsAdapter.loadThreads());
163
+ ```
164
+
165
+ ### Citations
166
+
167
+ `extractCitations(msg)` reads citation metadata from a LangGraph message's `additional_kwargs`, returning `Citation[] | undefined` (`undefined` when no citation metadata is present). It checks `additional_kwargs.citations` first, falling back to `additional_kwargs.sources`.
168
+
169
+ ```ts
170
+ import { extractCitations } from '@threadplane/langgraph';
171
+
172
+ const citations = extractCitations(message);
173
+ ```
174
+
175
+ `Citation` is a type from `@threadplane/chat`; `CitationsResolverService` and `provideChat` also live there.
176
+
177
+ ## Testing
178
+
179
+ ```ts
180
+ // Fake backend — streams canned tokens, no server:
181
+ import { provideFakeAgent } from '@threadplane/langgraph';
182
+ providers: [provideFakeAgent({ tokens: ['Hello', ' world'] })];
183
+ ```
184
+
185
+ For component/unit tests, use the writable-signal mock `mockLangGraphAgent()`
186
+ (it extends the neutral `mockAgent` from `@threadplane/chat`). See
187
+ [Choosing an adapter → Testing](https://threadplane.ai/docs/choosing-an-adapter#testing).
188
+
189
+ Need to hand-script exact wire events (tool calls, interrupts, multi-batch
190
+ lifecycles)? `MockAgentTransport` is the advanced escape hatch — swap the
191
+ transport, never mock `injectAgent()` itself.
192
+
193
+ ## Reliability
194
+
195
+ **Runtime-neutral contract.** `LangGraphAgent` implements the `Agent` contract from `@threadplane/chat`. Components that depend only on that contract are portable across adapters (`@threadplane/ag-ui`, future adapters) without modification.
196
+
197
+ **Release policy.** Patch-only `0.0.x` releases — every change, including breaking ones, increments the patch version until the library reaches `1.0.0`.
198
+
199
+ **CI.** The "Library — lint / test / build" job runs lint, tests, and build on every pull request.
200
+
82
201
  ## Documentation
83
202
 
84
- - [Quickstart](https://threadplane.ai/docs/agent/getting-started/quickstart)
85
- - [`agent()` API reference](https://threadplane.ai/docs/agent/api/agent)
86
- - [Human-in-the-loop / interrupts](https://threadplane.ai/docs/agent/guides/interrupts)
87
- - [Thread persistence](https://threadplane.ai/docs/agent/guides/persistence)
88
- - [Testing with `MockAgentTransport`](https://threadplane.ai/docs/agent/guides/testing)
203
+ - [Quickstart](https://threadplane.ai/docs/langgraph/getting-started/quickstart)
204
+ - [`injectAgent()` API reference](https://threadplane.ai/docs/langgraph/api/inject-agent)
205
+ - [`provideAgent()` API reference](https://threadplane.ai/docs/langgraph/api/provide-agent)
206
+ - [Human-in-the-loop / interrupts](https://threadplane.ai/docs/langgraph/guides/interrupts)
207
+ - [Thread persistence](https://threadplane.ai/docs/langgraph/guides/persistence)
208
+ - [Testing with `MockAgentTransport`](https://threadplane.ai/docs/langgraph/guides/testing)
209
+ - [Choosing an adapter (LangGraph vs AG-UI)](https://threadplane.ai/docs/choosing-an-adapter)
89
210
 
90
211
  ## License
91
212
 
92
- MIT — free for any use. See [LICENSE](../../LICENSE).
213
+ MIT. See [LICENSE](../../LICENSE).
@@ -1,20 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, Injectable, inject, DestroyRef, isSignal, computed, effect } from '@angular/core';
2
+ import { signal, Injectable, inject, DestroyRef, isSignal, computed, effect, InjectionToken } from '@angular/core';
3
3
  import { toObservable, toSignal } from '@angular/core/rxjs-interop';
4
4
  import { takeUntil, Subject, BehaviorSubject, of, throttleTime, asyncScheduler } from 'rxjs';
5
5
  import { takeUntil as takeUntil$1 } from 'rxjs/operators';
6
6
  import { Client } from '@langchain/langgraph-sdk';
7
7
  import { getToolCallsWithResults } from '@langchain/langgraph-sdk/utils';
8
-
9
- // SPDX-License-Identifier: MIT
10
- const AGENT_CONFIG = new InjectionToken('AGENT_CONFIG');
11
- /**
12
- * Angular provider factory that registers global defaults for all
13
- * agent instances in the application.
14
- */
15
- function provideAgent(config) {
16
- return { provide: AGENT_CONFIG, useValue: config };
17
- }
8
+ import { mockAgent } from '@threadplane/chat';
18
9
 
19
10
  // SPDX-License-Identifier: MIT
20
11
  /**
@@ -1868,10 +1859,14 @@ function computeMessageCheckpoints(history) {
1868
1859
  return out;
1869
1860
  }
1870
1861
  /**
1871
- * Creates a LangGraph-backed Angular agent.
1862
+ * Internal factory that constructs a LangGraph-backed Angular agent.
1863
+ *
1864
+ * @internal Consumers do not call this directly. Configure the adapter with
1865
+ * `provideAgent({...})` in `app.config.ts` (or a component's `providers`), then
1866
+ * retrieve the agent with `injectAgent()`. This factory is the construction
1867
+ * logic invoked by `provideAgent`'s DI factory.
1872
1868
  *
1873
- * Must be called within an Angular injection context (component constructor,
1874
- * field initializer, or `runInInjectionContext`). Returns a unified
1869
+ * Must run within an Angular injection context. Returns a unified
1875
1870
  * {@link LangGraphAgent} whose properties are Angular Signals that update
1876
1871
  * in real time as LangGraph streams messages, values, tool calls, interrupts,
1877
1872
  * subagent state, and checkpoint history.
@@ -1883,13 +1878,18 @@ function computeMessageCheckpoints(history) {
1883
1878
  *
1884
1879
  * @example
1885
1880
  * ```typescript
1886
- * // In a component field initializer
1887
- * const chat = agent({
1888
- * assistantId: 'chat_agent',
1889
- * apiUrl: 'http://localhost:2024',
1890
- * threadId: signal(this.savedThreadId),
1891
- * onThreadId: (id) => localStorage.setItem('threadId', id),
1892
- * });
1881
+ * // app.config.ts configure once
1882
+ * providers: [
1883
+ * provideAgent({
1884
+ * assistantId: 'chat',
1885
+ * apiUrl: 'http://localhost:2024',
1886
+ * threadId: signal(savedThreadId),
1887
+ * onThreadId: (id) => localStorage.setItem('threadId', id),
1888
+ * }),
1889
+ * ];
1890
+ *
1891
+ * // component — retrieve from DI
1892
+ * const chat = injectAgent();
1893
1893
  *
1894
1894
  * // Access signals in template
1895
1895
  * // chat.messages(), chat.status(), chat.error()
@@ -2457,6 +2457,93 @@ function isRecord(v) {
2457
2457
  return typeof v === 'object' && v !== null && !Array.isArray(v);
2458
2458
  }
2459
2459
 
2460
+ // SPDX-License-Identifier: MIT
2461
+ /**
2462
+ * @internal — exported only so the legacy in-tree `agent({...})` factory (and
2463
+ * its tests) can read provider-supplied defaults. Not part of the public API;
2464
+ * consumers should construct config inline at `provideAgent({...})`.
2465
+ */
2466
+ const AGENT_CONFIG = new InjectionToken('AGENT_CONFIG');
2467
+ /**
2468
+ * @internal — exported for spec access only. Consumers must use `injectAgent()`.
2469
+ */
2470
+ const AGENT = new InjectionToken('AGENT');
2471
+ /**
2472
+ * Wire the LangGraph adapter into Angular's dependency injection.
2473
+ *
2474
+ * Registers a singleton `LangGraphAgent` constructed from `config`. Retrieve it
2475
+ * in any component with `injectAgent()`. Provide this at the application root
2476
+ * (`app.config.ts`) for an app-wide agent.
2477
+ *
2478
+ * To use a different agent in a component subtree, re-provide
2479
+ * `provideAgent({...})` in that component's `providers: []` array —
2480
+ * Angular's hierarchical DI scopes the singleton accordingly.
2481
+ *
2482
+ * **Static vs factory config.** Pass a plain `AgentConfig` object when the
2483
+ * config is known up front. Pass a `() => AgentConfig` factory when the config
2484
+ * depends on runtime/DI state — the factory runs inside an Angular injection
2485
+ * context, so it may call `inject()` to read services, route params, or
2486
+ * component-scoped signals:
2487
+ *
2488
+ * ```ts
2489
+ * providers: [
2490
+ * provideAgent(() => {
2491
+ * const route = inject(ActivatedRoute);
2492
+ * return { assistantId: 'chat', threadId: toSignal(route.paramMap) };
2493
+ * }),
2494
+ * ];
2495
+ * ```
2496
+ */
2497
+ function provideAgent(configOrFactory) {
2498
+ // Resolve the factory (if any) lazily, inside the injection context of the
2499
+ // AGENT_CONFIG useFactory below — never at decoration time.
2500
+ const resolveConfig = () => typeof configOrFactory === 'function' ? configOrFactory() : configOrFactory;
2501
+ return [
2502
+ // AGENT_CONFIG resolves the config once (running the factory in an
2503
+ // injection context if a factory was passed). AGENT reads the resolved
2504
+ // config from here, so the factory is invoked exactly once.
2505
+ { provide: AGENT_CONFIG, useFactory: resolveConfig },
2506
+ {
2507
+ provide: AGENT,
2508
+ useFactory: () => {
2509
+ // useFactory runs in an injection context, so the legacy `agent()`
2510
+ // factory's `inject(DestroyRef)` calls work.
2511
+ const config = inject(AGENT_CONFIG);
2512
+ if (config.assistantId === undefined) {
2513
+ throw new Error('provideAgent: `assistantId` is required to construct the AGENT singleton.');
2514
+ }
2515
+ return agent({
2516
+ assistantId: config.assistantId,
2517
+ ...(config.apiUrl !== undefined ? { apiUrl: config.apiUrl } : {}),
2518
+ ...(config.threadId !== undefined ? { threadId: config.threadId } : {}),
2519
+ ...(config.onThreadId !== undefined ? { onThreadId: config.onThreadId } : {}),
2520
+ ...(config.initialValues !== undefined ? { initialValues: config.initialValues } : {}),
2521
+ ...(config.throttle !== undefined ? { throttle: config.throttle } : {}),
2522
+ ...(config.toMessage !== undefined ? { toMessage: config.toMessage } : {}),
2523
+ ...(config.transport !== undefined ? { transport: config.transport } : {}),
2524
+ ...(config.telemetry !== undefined ? { telemetry: config.telemetry } : {}),
2525
+ ...(config.filterSubagentMessages !== undefined ? { filterSubagentMessages: config.filterSubagentMessages } : {}),
2526
+ ...(config.subagentToolNames !== undefined ? { subagentToolNames: config.subagentToolNames } : {}),
2527
+ });
2528
+ },
2529
+ },
2530
+ ];
2531
+ }
2532
+
2533
+ // SPDX-License-Identifier: MIT
2534
+ /**
2535
+ * Retrieve the LangGraph-backed Agent from the current Angular injection context.
2536
+ *
2537
+ * Mirrors `@threadplane/ag-ui`'s `injectAgent()` so consumer code is identical
2538
+ * regardless of which adapter is wired in `app.config.ts`. The agent is a
2539
+ * singleton scoped to the injector that called `provideAgent()` — re-provide
2540
+ * in a child component's `providers: []` to scope a different agent to that
2541
+ * subtree (Angular's hierarchical DI handles the rest).
2542
+ */
2543
+ function injectAgent() {
2544
+ return inject(AGENT);
2545
+ }
2546
+
2460
2547
  // SPDX-License-Identifier: MIT
2461
2548
  const AGENT_LIFECYCLE = new InjectionToken('AGENT_LIFECYCLE');
2462
2549
 
@@ -2592,18 +2679,22 @@ class MockAgentTransport {
2592
2679
  /**
2593
2680
  * Creates a mock LangGraphAgent with writable signals for testing.
2594
2681
  * Control state by writing to the returned writable signals directly.
2682
+ *
2683
+ * Neutral `Agent`-contract signals come from {@link mockAgent}; LangGraph-specific
2684
+ * signals are declared here and layered on top.
2595
2685
  */
2596
2686
  function mockLangGraphAgent(initial = {}) {
2597
- const messages$ = signal(initial.messages ?? [], ...(ngDevMode ? [{ debugName: "messages$" }] : []));
2687
+ const base = mockAgent({
2688
+ ...initial,
2689
+ withInterrupt: true,
2690
+ withSubagents: true,
2691
+ history: initial.history ?? [],
2692
+ });
2693
+ // ── LangGraph-specific writable signals (defaults copied verbatim) ────────
2598
2694
  const langGraphMessages$ = signal(initial.langGraphMessages ?? [], ...(ngDevMode ? [{ debugName: "langGraphMessages$" }] : []));
2599
- const status$ = signal(initial.status ?? 'idle', ...(ngDevMode ? [{ debugName: "status$" }] : []));
2600
- const isLoading$ = signal(initial.isLoading ?? false, ...(ngDevMode ? [{ debugName: "isLoading$" }] : []));
2601
- const error$ = signal(initial.error ?? null, ...(ngDevMode ? [{ debugName: "error$" }] : []));
2602
2695
  const hasValue$ = signal(initial.hasValue ?? false, ...(ngDevMode ? [{ debugName: "hasValue$" }] : []));
2603
2696
  const value$ = signal(null, ...(ngDevMode ? [{ debugName: "value$" }] : []));
2604
- const interrupt$ = signal(undefined, ...(ngDevMode ? [{ debugName: "interrupt$" }] : []));
2605
2697
  const langGraphInterrupts$ = signal([], ...(ngDevMode ? [{ debugName: "langGraphInterrupts$" }] : []));
2606
- const toolCalls$ = signal([], ...(ngDevMode ? [{ debugName: "toolCalls$" }] : []));
2607
2698
  const langGraphToolCalls$ = signal([], ...(ngDevMode ? [{ debugName: "langGraphToolCalls$" }] : []));
2608
2699
  const toolProgress$ = signal([], ...(ngDevMode ? [{ debugName: "toolProgress$" }] : []));
2609
2700
  const queue$ = signal({
@@ -2613,33 +2704,20 @@ function mockLangGraphAgent(initial = {}) {
2613
2704
  clear: async () => undefined,
2614
2705
  }, ...(ngDevMode ? [{ debugName: "queue$" }] : []));
2615
2706
  const branch$ = signal('', ...(ngDevMode ? [{ debugName: "branch$" }] : []));
2616
- const history$ = signal([], ...(ngDevMode ? [{ debugName: "history$" }] : []));
2617
2707
  const langGraphHistory$ = signal([], ...(ngDevMode ? [{ debugName: "langGraphHistory$" }] : []));
2618
2708
  const experimentalBranchTree$ = signal({ type: 'sequence', items: [] }, ...(ngDevMode ? [{ debugName: "experimentalBranchTree$" }] : []));
2619
2709
  const isThreadLoading$ = signal(initial.isThreadLoading ?? false, ...(ngDevMode ? [{ debugName: "isThreadLoading$" }] : []));
2620
- const subagents$ = signal(new Map(), ...(ngDevMode ? [{ debugName: "subagents$" }] : []));
2621
2710
  const activeSubagents$ = signal([], ...(ngDevMode ? [{ debugName: "activeSubagents$" }] : []));
2622
2711
  const customEvents$ = signal([], ...(ngDevMode ? [{ debugName: "customEvents$" }] : []));
2712
+ // `state` derives from the raw LangGraph value (preserves current behavior).
2623
2713
  const state$ = computed(() => {
2624
2714
  const v = value$();
2625
2715
  return v && typeof v === 'object' ? v : {};
2626
2716
  }, ...(ngDevMode ? [{ debugName: "state$" }] : []));
2627
- const eventsSubject = new Subject();
2628
2717
  const mock = {
2629
- // ── AgentWithHistory (runtime-neutral surface) ────────────────────────
2630
- messages: messages$,
2631
- status: status$,
2632
- isLoading: isLoading$,
2633
- error: error$,
2634
- toolCalls: toolCalls$,
2718
+ ...base,
2719
+ // ── Neutral surface: override `state` to derive from the LangGraph value ─
2635
2720
  state: state$,
2636
- interrupt: interrupt$,
2637
- subagents: subagents$,
2638
- events$: eventsSubject.asObservable(),
2639
- history: history$,
2640
- submit: (_input, _opts) => Promise.resolve(),
2641
- stop: () => Promise.resolve(),
2642
- regenerate: (_assistantMessageIndex) => Promise.resolve(),
2643
2721
  // ── Raw LangGraph signals ─────────────────────────────────────────────
2644
2722
  langGraphMessages: langGraphMessages$,
2645
2723
  langGraphInterrupts: langGraphInterrupts$,
@@ -2694,6 +2772,97 @@ function mockLangGraphAgent(initial = {}) {
2694
2772
  return mock;
2695
2773
  }
2696
2774
 
2775
+ const DEFAULT_TOKENS = ['Hello', ' from', ' the', ' fake', ' LangGraph', ' agent.'];
2776
+ /**
2777
+ * In-process AgentTransport that auto-streams a canned assistant reply.
2778
+ *
2779
+ * Backs `provideFakeAgent()`. Unlike `MockAgentTransport` (passive, driven
2780
+ * manually from specs), this transport emits its tokens automatically on
2781
+ * `stream()`, then completes — suitable for offline demos and integration tests.
2782
+ *
2783
+ * NOT for production use.
2784
+ */
2785
+ class FakeStreamTransport {
2786
+ tokens;
2787
+ reasoningTokens;
2788
+ delayMs;
2789
+ constructor(config = {}) {
2790
+ this.tokens = config.tokens ?? DEFAULT_TOKENS;
2791
+ this.reasoningTokens = config.reasoningTokens ?? [];
2792
+ // Default 60ms matches @threadplane/ag-ui's FakeAgent so both adapters'
2793
+ // provideFakeAgent() stream at the same cadence for the same config.
2794
+ this.delayMs = config.delayMs ?? 60;
2795
+ }
2796
+ async *stream(_assistantId, _threadId, _payload, signal, _options) {
2797
+ const id = 'fake-ai-1';
2798
+ let reasoning = '';
2799
+ for (const chunk of this.reasoningTokens) {
2800
+ if (signal.aborted)
2801
+ return;
2802
+ reasoning += chunk;
2803
+ yield {
2804
+ type: 'messages',
2805
+ messages: [
2806
+ { id, type: 'ai', content: '', additional_kwargs: { reasoning_content: reasoning } },
2807
+ ],
2808
+ };
2809
+ if (this.delayMs > 0)
2810
+ await delay(this.delayMs);
2811
+ }
2812
+ let content = '';
2813
+ for (const tok of this.tokens) {
2814
+ if (signal.aborted)
2815
+ return;
2816
+ content += tok;
2817
+ yield {
2818
+ type: 'messages',
2819
+ messages: [{ id, type: 'ai', content }],
2820
+ };
2821
+ if (this.delayMs > 0)
2822
+ await delay(this.delayMs);
2823
+ }
2824
+ }
2825
+ async createQueuedRun(_assistantId, threadId, payload, _signal, options) {
2826
+ return {
2827
+ id: 'fake-queued-run',
2828
+ threadId,
2829
+ values: payload,
2830
+ options: { ...options, multitaskStrategy: 'enqueue' },
2831
+ createdAt: new Date(),
2832
+ };
2833
+ }
2834
+ async cancelRun(_threadId, _runId, _signal) {
2835
+ // No-op: the fake has no real runs to cancel.
2836
+ return;
2837
+ }
2838
+ async getHistory(_threadId, _signal) {
2839
+ return [];
2840
+ }
2841
+ async *joinStream() {
2842
+ // No queued-run replay in the fake; yields nothing.
2843
+ yield* [];
2844
+ }
2845
+ }
2846
+ function delay(ms) {
2847
+ return new Promise((resolve) => setTimeout(resolve, ms));
2848
+ }
2849
+
2850
+ /**
2851
+ * Wire an in-process fake LangGraph agent into Angular DI.
2852
+ *
2853
+ * Streams a canned assistant reply (see FakeAgentConfig) with no backend —
2854
+ * the symmetric counterpart to @threadplane/ag-ui's provideFakeAgent(). For
2855
+ * advanced manual scripting (tool calls, interrupts, multi-batch), provide
2856
+ * the agent yourself with
2857
+ * `provideAgent({ assistantId, transport: new MockAgentTransport(...) })`.
2858
+ */
2859
+ function provideFakeAgent(config = {}) {
2860
+ return provideAgent({
2861
+ assistantId: 'fake',
2862
+ transport: new FakeStreamTransport(config),
2863
+ });
2864
+ }
2865
+
2697
2866
  // SPDX-License-Identifier: MIT
2698
2867
  const LANGGRAPH_THREADS_CONFIG = new InjectionToken('LANGGRAPH_THREADS_CONFIG');
2699
2868
  /** Optional adapter clients can pass an explicit Client (e.g. for
@@ -2918,11 +3087,11 @@ function refreshOnTransition(watch, isActive, fn) {
2918
3087
  }
2919
3088
 
2920
3089
  // SPDX-License-Identifier: MIT
2921
- // Primary function
3090
+ // Provider
2922
3091
 
2923
3092
  /**
2924
3093
  * Generated bundle index. Do not edit.
2925
3094
  */
2926
3095
 
2927
- export { AGENT_CONFIG, AGENT_LIFECYCLE, AgentLifecycleRegistry, FetchStreamTransport, LANGGRAPH_CLIENT, LANGGRAPH_THREADS_CONFIG, LangGraphThreadsAdapter, MockAgentTransport, ResourceStatus, agent, createLangGraphClient, extractCitations, mockLangGraphAgent, provideAgent, refreshOnRunEnd, refreshOnTransition, toAbsoluteApiUrl };
3096
+ export { AGENT_LIFECYCLE, AgentLifecycleRegistry, FakeStreamTransport, FetchStreamTransport, LANGGRAPH_CLIENT, LANGGRAPH_THREADS_CONFIG, LangGraphThreadsAdapter, MockAgentTransport, ResourceStatus, createLangGraphClient, extractCitations, injectAgent, mockLangGraphAgent, provideAgent, provideFakeAgent, refreshOnRunEnd, refreshOnTransition, toAbsoluteApiUrl };
2928
3097
  //# sourceMappingURL=threadplane-langgraph.mjs.map