@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 +163 -42
- package/fesm2022/threadplane-langgraph.mjs +214 -45
- package/fesm2022/threadplane-langgraph.mjs.map +1 -1
- package/package.json +10 -1
- package/types/threadplane-langgraph.d.ts +115 -53
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,
|
|
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
|
-
|
|
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:**
|
|
14
|
-
|
|
15
|
-
## What it does
|
|
33
|
+
**Peer dependencies:**
|
|
16
34
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
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 {
|
|
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 =
|
|
50
|
-
apiUrl: 'https://your-langgraph-platform.com',
|
|
51
|
-
assistantId: 'my-agent',
|
|
52
|
-
});
|
|
74
|
+
protected readonly chat = injectAgent();
|
|
53
75
|
}
|
|
54
76
|
```
|
|
55
77
|
|
|
56
|
-
> `
|
|
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
|
-
|
|
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
|
-
//
|
|
62
|
-
const
|
|
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/
|
|
85
|
-
- [`
|
|
86
|
-
- [
|
|
87
|
-
- [
|
|
88
|
-
- [
|
|
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
|
|
213
|
+
MIT. See [LICENSE](../../LICENSE).
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
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
|
|
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
|
-
* //
|
|
1887
|
-
*
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1891
|
-
*
|
|
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
|
|
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
|
-
|
|
2630
|
-
|
|
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
|
-
//
|
|
3090
|
+
// Provider
|
|
2922
3091
|
|
|
2923
3092
|
/**
|
|
2924
3093
|
* Generated bundle index. Do not edit.
|
|
2925
3094
|
*/
|
|
2926
3095
|
|
|
2927
|
-
export {
|
|
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
|