From 4cb1c9d6d27026debff4b4c60d4c228a8e90b337 Mon Sep 17 00:00:00 2001 From: Andy Bonventre Date: Fri, 9 Jan 2026 19:00:04 -0500 Subject: [PATCH] fix(ai-sdk): make smartQuoteTransform generic for tool type compatibility The function previously returned StreamTextTransform, which caused type incompatibility when used with streamText() calls that have specific tool types. Consumers were forced to use `as any` casts. Changed the function to be generic over TOOLS extends ToolSet, matching the pattern used by smoothStream and other AI SDK transforms. This allows TypeScript to properly infer and propagate tool types through the transform. Added regression tests to verify type compatibility with custom tool types. --- packages/smartquote/src/ai-sdk/index.test.ts | 29 +++++++++++++++++++- packages/smartquote/src/ai-sdk/index.ts | 15 ++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/smartquote/src/ai-sdk/index.test.ts b/packages/smartquote/src/ai-sdk/index.test.ts index 8fb2f1f..1e0b293 100644 --- a/packages/smartquote/src/ai-sdk/index.test.ts +++ b/packages/smartquote/src/ai-sdk/index.test.ts @@ -5,7 +5,7 @@ * to test our transform with realistic AI streaming patterns. */ import { describe, expect, it } from 'vitest'; -import { streamText } from 'ai'; +import { streamText, type StreamTextTransform, type ToolSet } from 'ai'; import { MockLanguageModelV3, simulateReadableStream } from 'ai/test'; import { smartQuoteTransform, SmartQuote } from './index.js'; @@ -294,4 +294,31 @@ describe('smartQuoteTransform with AI SDK', () => { expect(text).toContain(RightDouble); }); }); + + describe('type compatibility', () => { + it('is compatible with streamText tools parameter', () => { + // Define a typed tools object - we intersect with ToolSet to satisfy + // the constraint since our simplified type is missing required Tool properties + type MyToolSet = { + myTool: { description: string; parameters: unknown }; + }; + // Create a transform with the specific tool type + const transform = smartQuoteTransform(); + // This should compile without type errors - the transform should be assignable + // to experimental_transform when tools are specified + const config: { experimental_transform: StreamTextTransform } = { + experimental_transform: transform, + }; + expect(config.experimental_transform).toBe(transform); + }); + + it('works with default ToolSet when no type parameter is specified', () => { + // When calling without type params, it defaults to ToolSet + const transform = smartQuoteTransform(); + const config: { experimental_transform: StreamTextTransform } = { + experimental_transform: transform, + }; + expect(config.experimental_transform).toBe(transform); + }); + }); }); diff --git a/packages/smartquote/src/ai-sdk/index.ts b/packages/smartquote/src/ai-sdk/index.ts index cdd2ed9..96b1752 100644 --- a/packages/smartquote/src/ai-sdk/index.ts +++ b/packages/smartquote/src/ai-sdk/index.ts @@ -45,16 +45,25 @@ export { SmartQuote, smartQuotes } from '../index.js'; * * Converts straight quotes to typographically correct smart quotes * in streaming AI responses. See module documentation for examples. + * + * @remarks + * The function is generic over the tools type parameter, allowing it to be + * compatible with any tool configuration in `streamText()`. The transform + * only processes `text-delta` stream parts and passes through all other + * parts unchanged, including tool-related stream parts. + * + * @typeParam TOOLS - The tools type from the streamText configuration. + * Defaults to ToolSet for compatibility when no tools are specified. */ -export function smartQuoteTransform( +export function smartQuoteTransform( options?: SmartQuoteTransformOptions, -): StreamTextTransform { +): StreamTextTransform { return () => { const transform = options?.disableMarkdown ? createTextTransform() : createMarkdownTextTransform(); - return new TransformStream, TextStreamPart>({ + return new TransformStream, TextStreamPart>({ transform(part, controller) { if (part.type === 'text-delta') { const converted = transform(part.text);