@tanstack/ai 0.51.0 → 0.52.1

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
@@ -61,12 +61,8 @@ framework bindings as your app needs them.
61
61
 
62
62
  - [Overview](https://tanstack.com/ai/latest/docs/getting-started/overview) -
63
63
  what TanStack AI is and how the packages fit together.
64
- - [Quick Start: React](https://tanstack.com/ai/latest/docs/getting-started/quick-start) -
65
- add streaming chat to a React app.
66
- - [Quick Start: Vue](https://tanstack.com/ai/latest/docs/getting-started/quick-start-vue) -
67
- build with Vue composables.
68
- - [Quick Start: Svelte](https://tanstack.com/ai/latest/docs/getting-started/quick-start-svelte) -
69
- build with Svelte 5 runes.
64
+ - [Quick Start](https://tanstack.com/ai/latest/docs/getting-started/quick-start) -
65
+ add streaming chat. Pick your framework at the top of the page.
70
66
  - [Quick Start: Server Only](https://tanstack.com/ai/latest/docs/getting-started/quick-start-server) -
71
67
  use TanStack AI from a server endpoint, script, or backend service.
72
68
  - [TanStack AI vs Vercel AI SDK](https://tanstack.com/ai/latest/docs/comparison/vercel-ai-sdk) -
@@ -115,6 +115,7 @@ var TextEngine = class {
115
115
  effectiveRequest;
116
116
  effectiveSignal;
117
117
  messages;
118
+ providerMessages;
118
119
  iterationCount = 0;
119
120
  /** Cumulative tool calls counted in this run (emitted + pending resume). */
120
121
  toolCallCount = 0;
@@ -156,6 +157,9 @@ var TextEngine = class {
156
157
  middlewareRunner;
157
158
  middlewareCtx;
158
159
  sandboxFileQueue = [];
160
+ middlewareCustomQueue = [];
161
+ middlewareCustomWaiters = [];
162
+ drainingMiddlewareCustom = false;
159
163
  deferredPromises = [];
160
164
  abortReason;
161
165
  middlewareAbortController;
@@ -193,6 +197,7 @@ var TextEngine = class {
193
197
  this.initialApprovals = approvals;
194
198
  this.initialClientToolResults = clientToolResults;
195
199
  this.messages = convertMessagesToModelMessages(config.params.messages);
200
+ this.providerMessages = this.messages;
196
201
  assertUniqueToolNames(config.params.tools || []);
197
202
  this.lazyToolManager = new LazyToolManager(config.params.tools || [], this.messages, config.params.lazyToolsConfig);
198
203
  this.tools = this.lazyToolManager.getActiveTools();
@@ -223,6 +228,12 @@ var TextEngine = class {
223
228
  this.abortReason = reason;
224
229
  this.middlewareAbortController?.abort(reason);
225
230
  },
231
+ emitCustomEvent: (name, value) => {
232
+ this.middlewareCustomQueue.push(this.createCustomEventChunk(name, value));
233
+ const waiters = this.middlewareCustomWaiters;
234
+ this.middlewareCustomWaiters = [];
235
+ for (const waiter of waiters) waiter();
236
+ },
226
237
  context: config.context,
227
238
  defer: (promise) => {
228
239
  this.deferredPromises.push(promise);
@@ -304,14 +315,14 @@ var TextEngine = class {
304
315
  this.beforeRun();
305
316
  this.logger.agentLoop("run started", { threadId: this.middlewareCtx.threadId });
306
317
  try {
307
- await this.middlewareRunner.runSetup(this.middlewareCtx);
318
+ yield* this.runWhileYielding(this.middlewareRunner.runSetup(this.middlewareCtx));
308
319
  this.middlewareCtx.phase = "init";
309
320
  const initialConfig = this.buildMiddlewareConfig();
310
- const transformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, initialConfig);
321
+ const transformedConfig = yield* this.runWhileYielding(this.middlewareRunner.runOnConfig(this.middlewareCtx, initialConfig));
311
322
  this.applyMiddlewareConfig(transformedConfig);
312
323
  await this.applyEphemeralInterruptResume(transformedConfig);
313
324
  await this.applyDurableGenericInterruptResolution();
314
- await this.middlewareRunner.runOnStart(this.middlewareCtx);
325
+ yield* this.runWhileYielding(this.middlewareRunner.runOnStart(this.middlewareCtx));
315
326
  if (this.earlyTermination) {
316
327
  yield* this.emitSuccessfulEarlyTermination();
317
328
  if (!this.terminalHookCalled) {
@@ -330,12 +341,12 @@ var TextEngine = class {
330
341
  if (this.earlyTermination) break;
331
342
  if (this.isCancelled()) return;
332
343
  this.logger.agentLoop(`iteration=${this.middlewareCtx.iteration}`, { iteration: this.middlewareCtx.iteration });
333
- await this.beginCycle();
344
+ yield* this.runWhileYielding(this.beginCycle());
334
345
  if (this.cyclePhase === "processText") {
335
346
  this.middlewareCtx.phase = "beforeModel";
336
347
  this.middlewareCtx.iteration = this.iterationCount;
337
348
  const iterConfig = this.buildMiddlewareConfig();
338
- const iterTransformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, iterConfig);
349
+ const iterTransformedConfig = yield* this.runWhileYielding(this.middlewareRunner.runOnConfig(this.middlewareCtx, iterConfig));
339
350
  this.applyMiddlewareConfig(iterTransformedConfig);
340
351
  if (yield* this.emitBoundaryInterrupts("beforeModel", this.createSyntheticFinishedEvent())) {
341
352
  this.setToolPhase("wait");
@@ -353,7 +364,7 @@ var TextEngine = class {
353
364
  } else yield* this.flushDeferredModelRunFinishedChunks();
354
365
  } else yield* this.processToolCalls();
355
366
  this.endCycle();
356
- } while (await this.shouldContinue());
367
+ } while (yield* this.runWhileYielding(this.shouldContinue()));
357
368
  this.logger.agentLoop("run finished", { finishReason: this.lastFinishReason });
358
369
  if (this.finalStructuredOutput && this.toolPhase !== "wait" && !this.isCancelled() && !this.finalizationError && !this.earlyTermination) {
359
370
  if (this.finalStructuredOutput.nativeCombined === true) yield* this.harvestCombinedStructuredOutput();
@@ -505,7 +516,7 @@ var TextEngine = class {
505
516
  for (const [approvalId, resolution] of approvals) adapterApprovals.set(approvalId, typeof resolution === "boolean" ? resolution : resolution.approved);
506
517
  for await (const raw of this.adapter.chatStream({
507
518
  model: this.params.model,
508
- messages: this.messages,
519
+ messages: this.providerMessages,
509
520
  tools: toolsWithJsonSchemas,
510
521
  metadata,
511
522
  request: this.effectiveRequest,
@@ -581,16 +592,21 @@ var TextEngine = class {
581
592
  this.deferredToolCallRunFinishedChunks.push(spec);
582
593
  continue;
583
594
  }
584
- if (spec.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
595
+ if (spec.type === EventType.RUN_STARTED) {
596
+ if (this.hasPublicRunStarted) continue;
597
+ this.hasPublicRunStarted = true;
598
+ }
585
599
  this.logger.output(`type=${spec.type}`, { chunk: spec });
586
600
  yield spec;
587
601
  this.middlewareCtx.chunkIndex++;
588
602
  }
589
603
  if (raw.type === EventType.RUN_FINISHED) await this.runOnUsageFromChunk(raw);
590
604
  yield* this.drainSandboxFileQueue();
605
+ yield* this.drainMiddlewareCustomQueue();
591
606
  if (this.earlyTermination) break;
592
607
  }
593
608
  yield* this.drainSandboxFileQueue();
609
+ yield* this.drainMiddlewareCustomQueue();
594
610
  }
595
611
  handleStreamChunk(chunk) {
596
612
  switch (chunk.type) {
@@ -810,12 +826,12 @@ var TextEngine = class {
810
826
  return "stop";
811
827
  }
812
828
  const allResults = [...executionResult.results, ...deferredErrorResults];
813
- await this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, {
829
+ yield* this.runWhileYielding(this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, {
814
830
  toolCalls: pendingToolCalls,
815
831
  results: allResults,
816
832
  needsApproval: executionResult.needsApproval,
817
833
  needsClientExecution: executionResult.needsClientExecution
818
- });
834
+ }));
819
835
  if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
820
836
  this.discardDeferredToolCallRunFinishedChunks();
821
837
  if (allResults.length > 0) for (const chunk of this.buildToolResultChunks(allResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
@@ -903,14 +919,14 @@ var TextEngine = class {
903
919
  return;
904
920
  }
905
921
  const allResults = [...executionResult.results, ...deferredErrorResults];
906
- await this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, {
922
+ yield* this.runWhileYielding(this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, {
907
923
  toolCalls,
908
924
  results: allResults,
909
925
  needsApproval: executionResult.needsApproval,
910
926
  needsClientExecution: executionResult.needsClientExecution
911
- });
927
+ }));
912
928
  const afterToolBoundaryChunks = this.buildToolResultChunks(allResults, finishEvent);
913
- const afterToolRequests = await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
929
+ const afterToolRequests = yield* this.runWhileYielding(this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx));
914
930
  if (afterToolRequests.length > 0) {
915
931
  for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
916
932
  yield* this.emitBoundaryInterrupts("afterTools", finishEvent, toolCalls, afterToolRequests);
@@ -1286,7 +1302,7 @@ var TextEngine = class {
1286
1302
  }
1287
1303
  async *emitBoundaryInterrupts(phase, finishEvent, toolCalls = [], requests) {
1288
1304
  this.middlewareCtx.phase = phase;
1289
- const boundaryRequests = requests ?? await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
1305
+ const boundaryRequests = requests ?? (yield* this.runWhileYielding(this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx)));
1290
1306
  if (boundaryRequests.length === 0) return false;
1291
1307
  for (const request of boundaryRequests) if (this.interruptDefinitions.get(request.definition.id) !== request.definition) throw new Error(`Generic interrupt definition ${request.definition.id} is not registered on this chat.`);
1292
1308
  if (phase === "afterModel") {
@@ -1625,17 +1641,17 @@ var TextEngine = class {
1625
1641
  ...baseWithoutTools,
1626
1642
  outputSchema: this.finalStructuredOutput.jsonSchema
1627
1643
  };
1628
- structuredConfig = await this.middlewareRunner.runOnStructuredOutputConfig(this.middlewareCtx, structuredConfig);
1644
+ structuredConfig = yield* this.runWhileYielding(this.middlewareRunner.runOnStructuredOutputConfig(this.middlewareCtx, structuredConfig));
1629
1645
  const { outputSchema: pinnedSchema, ...chatConfigSlice } = structuredConfig;
1630
- const postOnConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, {
1646
+ const postOnConfig = yield* this.runWhileYielding(this.middlewareRunner.runOnConfig(this.middlewareCtx, {
1631
1647
  ...chatConfigSlice,
1632
1648
  tools: baseConfig.tools
1633
- });
1649
+ }));
1634
1650
  this.applyMiddlewareConfig(postOnConfig);
1635
1651
  const structuredCallOptions = {
1636
1652
  chatOptions: {
1637
1653
  model: this.params.model,
1638
- messages: this.messages,
1654
+ messages: this.providerMessages,
1639
1655
  metadata: postOnConfig.metadata,
1640
1656
  modelOptions: postOnConfig.modelOptions,
1641
1657
  systemPrompts: postOnConfig.systemPrompts,
@@ -1859,6 +1875,7 @@ var TextEngine = class {
1859
1875
  buildMiddlewareConfig() {
1860
1876
  return {
1861
1877
  messages: this.messages,
1878
+ providerMessages: this.messages,
1862
1879
  systemPrompts: [...this.systemPrompts],
1863
1880
  tools: [...this.tools],
1864
1881
  resume: this.params.resume,
@@ -2072,6 +2089,7 @@ var TextEngine = class {
2072
2089
  applyMiddlewareConfig(config) {
2073
2090
  this.applyResumeToolState(config.resumeToolState);
2074
2091
  this.messages = config.messages;
2092
+ this.providerMessages = config.providerMessages ?? config.messages;
2075
2093
  this.systemPrompts = config.systemPrompts;
2076
2094
  assertUniqueToolNames(config.tools);
2077
2095
  this.tools = config.tools;
@@ -2096,7 +2114,10 @@ var TextEngine = class {
2096
2114
  *emitPublicChunks(outputs) {
2097
2115
  for (const output of outputs) for (const spec of normalizeStreamChunk(output)) {
2098
2116
  restorePublicUsage(spec);
2099
- if (spec.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
2117
+ if (spec.type === EventType.RUN_STARTED) {
2118
+ if (this.hasPublicRunStarted) continue;
2119
+ this.hasPublicRunStarted = true;
2120
+ }
2100
2121
  yield spec;
2101
2122
  this.middlewareCtx.chunkIndex++;
2102
2123
  }
@@ -2108,6 +2129,55 @@ var TextEngine = class {
2108
2129
  async *pipeThroughMiddleware(chunk) {
2109
2130
  const afterMw = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
2110
2131
  yield* this.emitPublicChunks(afterMw);
2132
+ if (!this.drainingMiddlewareCustom) yield* this.drainMiddlewareCustomQueue();
2133
+ }
2134
+ /**
2135
+ * Drain CUSTOM chunks pushed by `ctx.emitCustomEvent` through middleware
2136
+ * and into the public stream. If the run has not yet sent `RUN_STARTED`,
2137
+ * emit that first so CUSTOM events are not the first wire event.
2138
+ */
2139
+ async *drainMiddlewareCustomQueue() {
2140
+ if (this.drainingMiddlewareCustom) return;
2141
+ if (this.middlewareCustomQueue.length === 0) return;
2142
+ this.drainingMiddlewareCustom = true;
2143
+ try {
2144
+ yield* this.emitSyntheticRunStarted(this.createSyntheticFinishedEvent());
2145
+ while (this.middlewareCustomQueue.length > 0) {
2146
+ const chunk = this.middlewareCustomQueue.shift();
2147
+ if (chunk) yield* this.pipeThroughMiddleware(chunk);
2148
+ }
2149
+ } finally {
2150
+ this.drainingMiddlewareCustom = false;
2151
+ }
2152
+ }
2153
+ /**
2154
+ * Await `work` while yielding any `emitCustomEvent` chunks as they arrive.
2155
+ */
2156
+ async *runWhileYielding(work) {
2157
+ let settled = false;
2158
+ let result;
2159
+ let error;
2160
+ const done = work.then((value) => {
2161
+ settled = true;
2162
+ result = value;
2163
+ }, (err) => {
2164
+ settled = true;
2165
+ error = err;
2166
+ });
2167
+ while (!settled) {
2168
+ yield* this.drainMiddlewareCustomQueue();
2169
+ if (settled) break;
2170
+ await Promise.race([done, new Promise((resolve) => {
2171
+ if (this.middlewareCustomQueue.length > 0) {
2172
+ resolve();
2173
+ return;
2174
+ }
2175
+ this.middlewareCustomWaiters.push(resolve);
2176
+ })]);
2177
+ }
2178
+ yield* this.drainMiddlewareCustomQueue();
2179
+ if (error !== void 0) throw error;
2180
+ return result;
2111
2181
  }
2112
2182
  /**
2113
2183
  * Drain queued `sandbox.file` chunks (emitted via the SandboxRuntime sink)
@@ -2124,12 +2194,13 @@ var TextEngine = class {
2124
2194
  * through the middleware pipeline and returning the final ExecuteToolCallsResult.
2125
2195
  */
2126
2196
  async *drainToolCallGenerator(generator) {
2127
- let next = await generator.next();
2128
- while (!next.done) {
2197
+ let pending = generator.next();
2198
+ while (true) {
2199
+ const next = yield* this.runWhileYielding(pending);
2200
+ if (next.done) return next.value;
2129
2201
  yield* this.pipeThroughMiddleware(next.value);
2130
- next = await generator.next();
2202
+ pending = generator.next();
2131
2203
  }
2132
- return next.value;
2133
2204
  }
2134
2205
  createCustomEventChunk(eventName, value) {
2135
2206
  return {