@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
279 lines (278 loc) • 7.73 kB
JavaScript
import { resolveDebugOption } from "../../logger/resolve.js";
import { streamGenerationResult } from "../stream-generation-result.js";
import { applyGenerationResultTransforms, createGenerationContext, runGenerationAbort, runGenerationError, runGenerationFinish, runGenerationStart, runGenerationUsage } from "../middleware/run.js";
import "./adapter.js";
import "./chat-stream-summarize.js";
import { aiEventClient } from "@tanstack/ai-event-client";
//#region src/activities/summarize/index.ts
/**
* Summarize Activity
*
* Generates summaries from text input.
* This is a self-contained module with implementation, types, and JSDoc.
*/
/** The adapter kind this activity handles */
var kind = "summarize";
function createId(prefix) {
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
/**
* Summarize activity - generates summaries from text.
*
* Supports both streaming and non-streaming modes.
*
* @example Basic summarization
* ```ts
* import { summarize } from '@tanstack/ai'
* import { openaiSummarize } from '@tanstack/ai-openai'
*
* const result = await summarize({
* adapter: openaiSummarize('gpt-4o-mini'),
* text: 'Long article text here...'
* })
*
* console.log(result.summary)
* ```
*
* @example Summarization with style
* ```ts
* const result = await summarize({
* adapter: openaiSummarize('gpt-4o-mini'),
* text: 'Long article text here...',
* style: 'bullet-points',
* maxLength: 100
* })
* ```
*
* @example Focused summarization
* ```ts
* const result = await summarize({
* adapter: openaiSummarize('gpt-4o-mini'),
* text: 'Long technical document...',
* focus: ['key findings', 'methodology']
* })
* ```
*
* @example Streaming summarization
* ```ts
* for await (const chunk of summarize({
* adapter: openaiSummarize('gpt-4o-mini'),
* text: 'Long article text here...',
* stream: true
* })) {
* if (chunk.type === 'content') {
* process.stdout.write(chunk.delta)
* }
* }
* ```
*/
function summarize(options) {
const { stream } = options;
if (stream) return runStreamingSummarize(options);
return runSummarize(options);
}
/**
* Run non-streaming summarization
*/
async function runSummarize(options) {
const { adapter, text, maxLength, style, focus, modelOptions, middleware } = options;
const model = adapter.model;
const requestId = createId("summarize");
const inputLength = text.length;
const startTime = Date.now();
const logger = resolveDebugOption(options.debug);
const mwCtx = createGenerationContext({
requestId,
activity: "summarize",
provider: adapter.name,
model,
modelOptions,
threadId: options.threadId,
runId: options.runId,
createId
});
await runGenerationStart(middleware, mwCtx);
aiEventClient.emit("summarize:request:started", {
requestId,
provider: adapter.name,
model,
inputLength,
timestamp: startTime
});
logger.request(`activity=summarize provider=${adapter.name}`, {
provider: adapter.name,
model,
inputLength
});
const summarizeOptions = {
model,
text,
maxLength,
style,
focus,
modelOptions,
logger
};
try {
const result = await applyGenerationResultTransforms(mwCtx, await adapter.summarize(summarizeOptions));
const duration = Date.now() - startTime;
const outputLength = result.summary.length;
aiEventClient.emit("summarize:request:completed", {
requestId,
provider: adapter.name,
model,
inputLength,
outputLength,
duration,
timestamp: Date.now()
});
logger.output(`activity=summarize length=${outputLength}`, {
hasSummary: !!result.summary,
outputLength
});
if (result.usage) await runGenerationUsage(middleware, mwCtx, result.usage);
await runGenerationFinish(middleware, mwCtx, {
duration,
usage: result.usage
});
return result;
} catch (error) {
await runGenerationError(middleware, mwCtx, {
error,
duration: Date.now() - startTime
});
logger.errors("summarize activity failed", {
error,
source: "summarize"
});
throw error;
}
}
/** Read a `usage` off a transformed result without asserting its shape. */
function usageOf(result) {
if (typeof result !== "object" || result === null) return void 0;
const usage = result.usage;
return typeof usage === "object" && usage !== null ? usage : void 0;
}
/**
* Run streaming summarization
* Uses the adapter's native streaming if available, otherwise falls back
* to non-streaming and yields the result as a single chunk.
*/
async function* runStreamingSummarize(options) {
const { adapter, text, maxLength, style, focus, modelOptions, runId, threadId } = options;
const model = adapter.model;
const logger = resolveDebugOption(options.debug);
logger.request(`activity=summarize provider=${adapter.name}`, {
provider: adapter.name,
model,
stream: true
});
const summarizeOptions = {
model,
text,
maxLength,
style,
focus,
modelOptions,
logger,
...runId !== void 0 ? { runId } : {},
...threadId !== void 0 ? { threadId } : {}
};
if (adapter.summarizeStream) {
yield* runNativeSummarizeStream(options, adapter.summarizeStream(summarizeOptions), logger);
return;
}
try {
yield* streamGenerationResult((resolved) => runSummarize({
...options,
stream: false,
runId: resolved.runId
}), {
...runId !== void 0 ? { runId } : {},
...threadId !== void 0 ? { threadId } : {}
});
} catch (error) {
logger.errors("summarize activity failed", {
error,
source: "summarize"
});
throw error;
}
}
/**
* Drive an adapter's native `summarizeStream`, wiring the generation middleware
* around it.
*
* The adapter emits a terminal `generation:result` CUSTOM chunk carrying the
* assembled {@link SummarizationResult}; that is the one point where a result
* exists, so the transforms run there and the REWRITTEN result is what gets
* yielded — the client and the persisted run record then hold the same object.
* An adapter whose stream never emits one still finishes the run, just with no
* result recorded.
*/
async function* runNativeSummarizeStream(options, stream, logger) {
const { adapter, middleware, modelOptions } = options;
const mwCtx = createGenerationContext({
requestId: createId("summarize"),
activity: "summarize",
provider: adapter.name,
model: adapter.model,
modelOptions,
threadId: options.threadId,
runId: options.runId,
createId
});
await runGenerationStart(middleware, mwCtx);
const startTime = Date.now();
let settled = false;
try {
for await (const chunk of stream) {
if (chunk.type === "CUSTOM" && chunk.name === "generation:result") {
const result = await applyGenerationResultTransforms(mwCtx, chunk.value);
const usage = usageOf(result);
if (usage) await runGenerationUsage(middleware, mwCtx, usage);
await runGenerationFinish(middleware, mwCtx, {
duration: Date.now() - startTime,
usage
});
settled = true;
yield {
...chunk,
value: result
};
continue;
}
yield chunk;
}
if (!settled) {
await runGenerationFinish(middleware, mwCtx, { duration: Date.now() - startTime });
settled = true;
}
} catch (error) {
settled = true;
await runGenerationError(middleware, mwCtx, {
error,
duration: Date.now() - startTime
});
logger.errors("summarize activity failed", {
error,
source: "summarize"
});
throw error;
} finally {
if (!settled) await runGenerationAbort(middleware, mwCtx, {
reason: "Summarize stream abandoned before completion",
duration: Date.now() - startTime
});
}
}
/**
* Create typed options for the summarize() function without executing.
*/
function createSummarizeOptions(options) {
return options;
}
//#endregion
export { createSummarizeOptions, kind, summarize };
//# sourceMappingURL=index.js.map