@ai-sdk/langchain 3.0.0-beta.17 → 3.0.0-beta.177

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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@ai-sdk/langchain",
3
- "version": "3.0.0-beta.17",
3
+ "version": "3.0.0-beta.177",
4
+ "type": "module",
4
5
  "license": "Apache-2.0",
5
6
  "sideEffects": false,
6
7
  "main": "./dist/index.js",
7
- "module": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
10
  "dist/**/*",
@@ -20,20 +20,20 @@
20
20
  "./package.json": "./package.json",
21
21
  ".": {
22
22
  "types": "./dist/index.d.ts",
23
- "import": "./dist/index.mjs",
24
- "require": "./dist/index.js"
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "ai": "7.0.0-beta.17"
28
+ "ai": "7.0.0-beta.177"
29
29
  },
30
30
  "devDependencies": {
31
- "@langchain/core": "^1.1.5",
32
- "@langchain/langgraph": "^1.0.5",
33
- "@types/node": "20.17.24",
34
- "tsup": "^8",
31
+ "@langchain/core": "^1.1.46",
32
+ "@langchain/langgraph": "^1.3.0",
33
+ "@types/node": "22.19.19",
34
+ "tsup": "^8.5.1",
35
35
  "typescript": "5.8.3",
36
- "@ai-sdk/provider-utils": "5.0.0-beta.1",
36
+ "@ai-sdk/provider-utils": "5.0.0-beta.49",
37
37
  "@vercel/ai-tsconfig": "0.0.0"
38
38
  },
39
39
  "peerDependencies": {
@@ -46,15 +46,17 @@
46
46
  }
47
47
  },
48
48
  "engines": {
49
- "node": ">=18"
49
+ "node": ">=22"
50
50
  },
51
51
  "publishConfig": {
52
- "access": "public"
52
+ "access": "public",
53
+ "provenance": true
53
54
  },
54
55
  "homepage": "https://ai-sdk.dev/docs",
55
56
  "repository": {
56
57
  "type": "git",
57
- "url": "git+https://github.com/vercel/ai.git"
58
+ "url": "https://github.com/vercel/ai",
59
+ "directory": "packages/langchain"
58
60
  },
59
61
  "bugs": {
60
62
  "url": "https://github.com/vercel/ai/issues"
@@ -66,9 +68,7 @@
66
68
  "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
67
69
  "build:watch": "pnpm clean && tsup --watch",
68
70
  "clean": "del-cli dist *.tsbuildinfo",
69
- "lint": "eslint \"./**/*.ts*\"",
70
71
  "type-check": "tsc --build",
71
- "prettier-check": "prettier --check \"./**/*.ts*\"",
72
72
  "test": "pnpm test:node && pnpm test:edge",
73
73
  "test:update": "pnpm test:node -u",
74
74
  "test:watch": "vitest --config vitest.node.config.js",
package/src/adapter.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  SystemMessage,
3
- BaseMessage,
4
- AIMessageChunk,
3
+ type BaseMessage,
4
+ type AIMessageChunk,
5
5
  } from '@langchain/core/messages';
6
6
  import {
7
+ convertToModelMessages,
7
8
  type UIMessage,
8
9
  type UIMessageChunk,
9
- convertToModelMessages,
10
10
  type ModelMessage,
11
11
  } from 'ai';
12
12
  import {
@@ -18,9 +18,11 @@ import {
18
18
  parseLangGraphEvent,
19
19
  isToolResultPart,
20
20
  extractReasoningFromContentBlocks,
21
+ extractCitationsFromContentBlocks,
22
+ emitSourceChunks,
21
23
  } from './utils';
22
- import { type LangGraphEventState } from './types';
23
- import { type StreamCallbacks } from './stream-callbacks';
24
+ import type { LangGraphEventState } from './types';
25
+ import type { StreamCallbacks } from './stream-callbacks';
24
26
 
25
27
  /**
26
28
  * Converts AI SDK UIMessages to LangChain BaseMessage objects.
@@ -132,6 +134,7 @@ function processStreamEventsEvent(
132
134
  textStarted: boolean;
133
135
  textMessageId: string | null;
134
136
  reasoningMessageId: string | null;
137
+ emittedSourceIds: Set<string>;
135
138
  },
136
139
  controller: ReadableStreamDefaultController<UIMessageChunk>,
137
140
  ): void {
@@ -150,8 +153,41 @@ function processStreamEventsEvent(
150
153
  switch (event.event) {
151
154
  case 'on_chat_model_start': {
152
155
  /**
153
- * Handle model start - capture message metadata if available
154
- * run_id is at event level in v2, but check data for backwards compatibility
156
+ * End the previous model turn's reasoning stream before a new LLM invocation.
157
+ * Without this, reasoning-delta chunks from the next turn could attach to the
158
+ * wrong reasoning part in the UI message stream.
159
+ */
160
+ if (state.reasoningStarted) {
161
+ controller.enqueue({
162
+ type: 'reasoning-end',
163
+ id:
164
+ state.reasoningMessageId != null
165
+ ? state.reasoningMessageId
166
+ : state.messageId,
167
+ });
168
+ state.reasoningStarted = false;
169
+ state.reasoningMessageId = null;
170
+ }
171
+
172
+ /**
173
+ * End the previous model turn's text stream before a new LLM invocation.
174
+ * The streamEvents adapter otherwise leaves `textStarted` true, so all later
175
+ * text-delta chunks append to the first text part — tools still grow the parts
176
+ * array, which breaks chronological order in the assistant message.
177
+ */
178
+ if (state.textStarted) {
179
+ controller.enqueue({
180
+ type: 'text-end',
181
+ id:
182
+ state.textMessageId != null ? state.textMessageId : state.messageId,
183
+ });
184
+ state.textStarted = false;
185
+ state.textMessageId = null;
186
+ }
187
+
188
+ /**
189
+ * Handle model start — capture message metadata if available.
190
+ * `run_id` is on the event in streamEvents v2; fall back to `data` for compatibility.
155
191
  */
156
192
  const runId = event.run_id || (event.data.run_id as string | undefined);
157
193
  if (runId) {
@@ -241,6 +277,16 @@ function processStreamEventsEvent(
241
277
  id: state.textMessageId ?? state.messageId,
242
278
  });
243
279
  }
280
+
281
+ const citations = extractCitationsFromContentBlocks(chunk);
282
+ if (citations.length > 0) {
283
+ emitSourceChunks(
284
+ citations,
285
+ state.messageId,
286
+ state.emittedSourceIds,
287
+ controller,
288
+ );
289
+ }
244
290
  }
245
291
  break;
246
292
  }
@@ -378,27 +424,24 @@ export function toUIMessageStream<TState = unknown>(
378
424
  textMessageId: null as string | null,
379
425
  /** Track the ID used for reasoning-start to ensure reasoning-end uses the same ID */
380
426
  reasoningMessageId: null as string | null,
427
+ emittedSourceIds: new Set<string>(),
381
428
  };
382
429
 
383
430
  /**
384
431
  * State for LangGraph stream handling
385
432
  */
386
433
  const langGraphState: LangGraphEventState = {
387
- messageSeen: {} as Record<
388
- string,
389
- { text?: boolean; reasoning?: boolean; tool?: Record<string, boolean> }
390
- >,
391
- messageConcat: {} as Record<string, AIMessageChunk>,
434
+ messageSeen: new Map(),
435
+ messageConcat: new Map(),
392
436
  emittedToolCalls: new Set<string>(),
437
+ emittedToolInputs: new Set<string>(),
393
438
  emittedImages: new Set<string>(),
394
439
  emittedReasoningIds: new Set<string>(),
395
- messageReasoningIds: {} as Record<string, string>,
396
- toolCallInfoByIndex: {} as Record<
397
- string,
398
- Record<number, { id: string; name: string }>
399
- >,
440
+ messageReasoningIds: new Map(),
441
+ toolCallInfoByIndex: new Map(),
400
442
  currentStep: null as number | null,
401
443
  emittedToolCallsByKey: new Map<string, string>(),
444
+ emittedSourceIds: new Set<string>(),
402
445
  };
403
446
 
404
447
  /**
@@ -535,6 +578,20 @@ export function toUIMessageStream<TState = unknown>(
535
578
  }
536
579
  controller.enqueue({ type: 'finish' });
537
580
  } else if (streamType === 'langgraph') {
581
+ /**
582
+ * Close any open text/reasoning parts before finishing.
583
+ * This handles streams without values events (e.g. streamMode: 'messages')
584
+ * where the values handler never ran to emit *-end events.
585
+ */
586
+ for (const [id, seen] of langGraphState.messageSeen) {
587
+ if (seen.text) {
588
+ controller.enqueue({ type: 'text-end', id });
589
+ }
590
+ if (seen.reasoning) {
591
+ controller.enqueue({ type: 'reasoning-end', id });
592
+ }
593
+ }
594
+
538
595
  /**
539
596
  * Emit finish-step if a step was started
540
597
  */
package/src/transport.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { AIMessageChunk } from '@langchain/core/messages';
2
- import {
3
- type UIMessage,
4
- type UIMessageChunk,
5
- type ChatTransport,
6
- type ChatRequestOptions,
1
+ import type { AIMessageChunk } from '@langchain/core/messages';
2
+ import type {
3
+ UIMessage,
4
+ UIMessageChunk,
5
+ ChatTransport,
6
+ ChatRequestOptions,
7
7
  } from 'ai';
8
8
  import {
9
9
  RemoteGraph,
@@ -45,9 +45,9 @@ export type LangSmithDeploymentTransportOptions = Omit<
45
45
  * });
46
46
  * ```
47
47
  */
48
- export class LangSmithDeploymentTransport<UI_MESSAGE extends UIMessage>
49
- implements ChatTransport<UI_MESSAGE>
50
- {
48
+ export class LangSmithDeploymentTransport<
49
+ UI_MESSAGE extends UIMessage,
50
+ > implements ChatTransport<UI_MESSAGE> {
51
51
  protected graph: RemoteGraph;
52
52
 
53
53
  constructor(options: LangSmithDeploymentTransportOptions) {
package/src/types.ts CHANGED
@@ -1,33 +1,49 @@
1
- import { type AIMessageChunk } from '@langchain/core/messages';
1
+ import type { AIMessageChunk } from '@langchain/core/messages';
2
+
3
+ export interface LangGraphMessageSeen {
4
+ text?: boolean;
5
+ reasoning?: boolean;
6
+ tool?: Set<string>;
7
+ }
2
8
 
3
9
  /**
4
10
  * State for LangGraph event processing
5
11
  */
6
12
  export interface LangGraphEventState {
7
13
  /** Tracks which message IDs have been seen */
8
- messageSeen: Record<
9
- string,
10
- { text?: boolean; reasoning?: boolean; tool?: Record<string, boolean> }
11
- >;
14
+ messageSeen: Map<string, LangGraphMessageSeen>;
12
15
  /** Accumulates message chunks for later reference */
13
- messageConcat: Record<string, AIMessageChunk>;
14
- /** Maps tool call IDs to their message IDs (for chunks that don't include the ID) */
16
+ messageConcat: Map<string, AIMessageChunk>;
17
+ /** Tracks which tool call IDs have emitted tool-input-start */
15
18
  emittedToolCalls: Set<string>;
19
+ /** Tracks which tool call IDs have emitted complete tool inputs */
20
+ emittedToolInputs: Set<string>;
16
21
  /** Maps image IDs to their message IDs (for chunks that don't include the ID) */
17
22
  emittedImages: Set<string>;
18
23
  /** Maps reasoning block IDs to their message IDs (for chunks that don't include the ID) */
19
24
  emittedReasoningIds: Set<string>;
20
25
  /** Maps message IDs to their reasoning block IDs (for chunks that don't include the ID) */
21
- messageReasoningIds: Record<string, string>;
26
+ messageReasoningIds: Map<string, string>;
22
27
  /** Maps message ID + tool call index to tool call info (for streaming chunks without ID) */
23
- toolCallInfoByIndex: Record<
24
- string,
25
- Record<number, { id: string; name: string }>
26
- >;
28
+ toolCallInfoByIndex: Map<string, Map<number, { id: string; name: string }>>;
27
29
  /** Tracks the current LangGraph step for start-step/finish-step events */
28
30
  currentStep: number | null;
29
31
  /** Maps tool call key (name:argsJson) to tool call ID for HITL interrupt handling */
30
32
  emittedToolCallsByKey: Map<string, string>;
33
+ /** Tracks source IDs already emitted to avoid duplicates across messages/values events */
34
+ emittedSourceIds: Set<string>;
35
+ }
36
+
37
+ /**
38
+ * A LangChain citation projected to the fields the AI SDK source parts can carry.
39
+ */
40
+ export interface NormalizedCitation {
41
+ url?: string;
42
+ title?: string;
43
+ source?: string;
44
+ citedText?: string;
45
+ startIndex?: number;
46
+ endIndex?: number;
31
47
  }
32
48
 
33
49
  /**