From 7884f3c9ab8d6776bd0f21b52541d38425bcb9f6 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Fri, 13 Feb 2026 19:59:09 +0200 Subject: [PATCH 01/14] Added embed/embedMany activites & Gemini embeddings adapter --- .../ai-gemini/src/adapters/embedding.ts | 84 +++++++++++++++++ .../embedding/embedding-provider-options.ts | 74 +++++++++++++++ .../typescript/ai-gemini/src/model-meta.ts | 30 +++++- .../ai/src/activities/embed/adapter.ts | 91 +++++++++++++++++++ .../ai/src/activities/embed/embed-many.ts | 66 ++++++++++++++ .../ai/src/activities/embed/embed.ts | 66 ++++++++++++++ .../ai/src/activities/embed/index.ts | 10 ++ .../typescript/ai/src/activities/index.ts | 17 ++++ packages/typescript/ai/src/event-client.ts | 68 ++++++++++++++ packages/typescript/ai/src/types.ts | 55 ++++++++++- 10 files changed, 557 insertions(+), 4 deletions(-) create mode 100644 packages/typescript/ai-gemini/src/adapters/embedding.ts create mode 100644 packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts create mode 100644 packages/typescript/ai/src/activities/embed/adapter.ts create mode 100644 packages/typescript/ai/src/activities/embed/embed-many.ts create mode 100644 packages/typescript/ai/src/activities/embed/embed.ts create mode 100644 packages/typescript/ai/src/activities/embed/index.ts diff --git a/packages/typescript/ai-gemini/src/adapters/embedding.ts b/packages/typescript/ai-gemini/src/adapters/embedding.ts new file mode 100644 index 000000000..3e7bfa766 --- /dev/null +++ b/packages/typescript/ai-gemini/src/adapters/embedding.ts @@ -0,0 +1,84 @@ +import { BaseEmbeddingAdapter } from "@tanstack/ai/adapters"; +import { createGeminiClient, generateId } from "../utils"; +import { validateTaskType, validateValue } from "../embedding/embedding-provider-options"; +import type { GoogleGenAI } from "@google/genai"; +import type { EmbedManyOptions, EmbedManyResult, EmbedOptions, EmbedResult } from "@tanstack/ai"; +import type { GEMINI_EMBEDDING_MODELS } from "../model-meta"; +import type { GeminiClientConfig } from "../utils"; +import type { GeminiEmbeddingModelProviderOptionsByName, GeminiEmbeddingProviderOptions } from "../embedding/embedding-provider-options"; + +/** + * Configuration for Gemini embedding adapter + */ +export interface GeminiEmbeddingConfig extends GeminiClientConfig { } + +export type GeminiEmbeddingModel = (typeof GEMINI_EMBEDDING_MODELS)[number] + +export class GeminiEmbeddingAdapter< + TModel extends GeminiEmbeddingModel, +> extends BaseEmbeddingAdapter { + readonly kind = 'embedding' as const + readonly name = 'gemini' as const + + // Type-only property - never assigned at runtime + declare '~types': { + providerOptions: GeminiEmbeddingProviderOptions + modelProviderOptionsByName: GeminiEmbeddingModelProviderOptionsByName + } + + private client: GoogleGenAI + + constructor(config: GeminiEmbeddingConfig, model: TModel) { + super({}, model) + this.client = createGeminiClient(config) + } + + async embed( + options: EmbedOptions + ): Promise { + const { model, value, modelOptions } = options + + validateValue({ value, model }) + validateTaskType({ taskType: modelOptions?.taskType, model }) + + const { embeddings } = await this.client.models.embedContent({ + model, + contents: value, + config: { + ...modelOptions, + } + }) + + return { + embedding: embeddings?.[0]?.values || [], + id: generateId(this.name), + model, + usage: undefined, + } + } + + async embedMany( + options: EmbedManyOptions + ): Promise { + const { model, values, modelOptions } = options + + validateValue({ value: values, model }) + validateTaskType({ taskType: modelOptions?.taskType, model }) + + const { embeddings } = await this.client.models.embedContent({ + model, + contents: values, + config: { + ...modelOptions + } + }) + + return { + embeddings: embeddings?.map((embedding) => embedding.values || []) || [], + id: generateId(this.name), + model, + usage: undefined, + } + } +} + diff --git a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts new file mode 100644 index 000000000..fe2a0ff0f --- /dev/null +++ b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts @@ -0,0 +1,74 @@ +import type { HttpOptions } from "@google/genai"; +import type { GeminiEmbeddingModel } from "../adapters/embedding"; + +export interface GeminiEmbeddingProviderOptions { + /** Used to override HTTP request options. */ + httpOptions?: HttpOptions; + /** + * Type of task for which the embedding will be used. + */ + taskType?: string; + /** + * Title for the text. Only applicable when TaskType is `RETRIEVAL_DOCUMENT`. + */ + title?: string; + /** + * Reduced dimension for the output embedding. If set, + * excessive values in the output embedding are truncated from the end. + * Supported by newer models since 2024 only. You cannot set this value if + * using the earlier model (`models/embedding-001`). + */ + outputDimensionality?: number +} + +export type GeminiEmbeddingModelProviderOptionsByName = { + [K in GeminiEmbeddingModel]: GeminiEmbeddingProviderOptions +} + +/** + * Validates the task type + */ +export function validateTaskType(options: { + taskType: string | undefined + model: string +}) { + const { taskType, model } = options + if (!taskType) return; + + if ( + taskType !== 'SEMANTIC_SIMILARITY' && + taskType !== 'CLASSIFICATION' && + taskType !== 'CLUSTERING' && + taskType !== 'RETRIEVAL_DOCUMENT' && + taskType !== 'RETRIEVAL_QUERY' && + taskType !== 'CODE_RETRIEVAL_QUERY' && + taskType !== 'QUESTION_ANSWERING' && + taskType !== 'FACT_VERIFICATION' + ) { + throw new Error(`Invalid task type "${taskType}" for model "${model}".`) + } +} + +/** + * Validates the value to embed is not empty + */ +export function validateValue(options: { + value: string | Array + model: string +}): void { + const { value, model } = options + if (Array.isArray(value)) { + if (value.length === 0) { + throw new Error(`Value array cannot be empty for model "${model}".`) + } + for (const v of value) { + if (!v || v.trim().length === 0) { + throw new Error(`Value array cannot contain empty values for model "${model}".`) + } + } + } else { + if (!value || value.trim().length === 0) { + throw new Error(`Value cannot be empty for model "${model}".`) + } + } +} diff --git a/packages/typescript/ai-gemini/src/model-meta.ts b/packages/typescript/ai-gemini/src/model-meta.ts index 54761a026..7da85403e 100644 --- a/packages/typescript/ai-gemini/src/model-meta.ts +++ b/packages/typescript/ai-gemini/src/model-meta.ts @@ -12,7 +12,7 @@ interface ModelMeta { name: string supports: { input: Array<'text' | 'image' | 'audio' | 'video' | 'document'> - output: Array<'text' | 'image' | 'audio' | 'video'> + output: Array<'text' | 'image' | 'audio' | 'video' | 'embedding'> capabilities?: Array< | 'audio_generation' | 'batch_api' @@ -678,6 +678,28 @@ const IMAGEN_3 = { GeminiCommonConfigOptions & GeminiCachedContentOptions > + +const GEMINI_EMBEDDING_001 = { + name: 'embedding-001', + max_input_tokens: 2048, + supports: { + input: ['text'], + output: ['embedding'], + }, + pricing: { + input: { + normal: 0, + }, + output: { + normal: 0.15, + }, + }, +} as const satisfies ModelMeta< + GeminiToolConfigOptions & + GeminiSafetyOptions & + GeminiCommonConfigOptions & + GeminiCachedContentOptions +> /** const VEO_3_1_PREVIEW = { name: 'veo-3.1-generate-preview', @@ -833,6 +855,12 @@ export const GEMINI_MODELS = [ export type GeminiModels = (typeof GEMINI_MODELS)[number] +export const GEMINI_EMBEDDING_MODELS = [ + GEMINI_EMBEDDING_001.name, +] as const + +export type GeminiEmbeddingModels = (typeof GEMINI_EMBEDDING_MODELS)[number] + export type GeminiImageModels = (typeof GEMINI_IMAGE_MODELS)[number] export const GEMINI_IMAGE_MODELS = [ diff --git a/packages/typescript/ai/src/activities/embed/adapter.ts b/packages/typescript/ai/src/activities/embed/adapter.ts new file mode 100644 index 000000000..e6ee78de6 --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/adapter.ts @@ -0,0 +1,91 @@ +import type { EmbedManyOptions, EmbedManyResult, EmbedOptions, EmbedResult } from "../../types" + +// =========================== +// Activity Kind +// =========================== + +/** The adapter kind this activity handles */ +export const kind = 'embedding' as const + +export interface EmbeddingAdapterConfig { + apiKey?: string + baseUrl?: string + timeout?: number + maxRetries?: number + headers?: Record +} + +export interface EmbeddingAdapter< + TModel extends string = string, + TProviderOptions extends object = Record +> { + /** Discriminator for adapter kind - used to determine API shape */ + readonly kind: 'embedding' + /** Adapter name identifier */ + readonly name: string + /** The model this adapter is configured for */ + readonly model: TModel + + + /** + * @internal Type-only properties for inference. Not assigned at runtime. + */ + '~types': { + providerOptions: TProviderOptions + } + + /** + * Generate embeddings for text + */ + embed: (options: EmbedOptions) => Promise + + /** + * Generate embeddings for multiple texts + */ + embedMany: (options: EmbedManyOptions) => Promise +} + +/** + * A EmbeddingAdapter with any/unknown type parameters. + * Useful as a constraint in generic functions and interfaces. + */ +export type AnyEmbeddingAdapter = EmbeddingAdapter + +/** + * Abstract base class for embed adapters. + * Extend this class to implement an embed adapter for a specific provider. + * + * Generic parameters match EmbedAdapter - all pre-resolved by the provider function. + */ +export abstract class BaseEmbeddingAdapter< + TModel extends string = string, + TProviderOptions extends object = Record, +> implements EmbeddingAdapter { + readonly kind = 'embedding' as const + abstract readonly name: string + readonly model: TModel + + // Type-only property - never assigned at runtime + declare '~types': { + providerOptions: TProviderOptions + } + + protected config: EmbeddingAdapterConfig + + constructor(config: EmbeddingAdapterConfig = {}, model: TModel) { + this.config = config + this.model = model + } + + abstract embed( + options: EmbedOptions, + ): Promise + + abstract embedMany( + options: EmbedManyOptions, + ): Promise + + protected generateId(): string { + return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}` + } +} \ No newline at end of file diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts new file mode 100644 index 000000000..c7d7a6e40 --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -0,0 +1,66 @@ +import { aiEventClient } from "../../event-client" +import type { EmbeddingAdapter, kind } from "./adapter" +import type { EmbedManyResult } from "../../types" + +export interface EmbedManyActivityOptions< + TAdapter extends EmbeddingAdapter +> { + /** The embedding adapter to use (must be created with a model) */ + adapter: TAdapter & { kind: typeof kind } + /** The value to convert to embedding */ + values: Array + /** Model-specific options for embedding */ + modelOptions?: TAdapter['~types']['providerOptions'] +} + +/** Result type for the TTS activity */ +export type EmbedManyActivityResult = Promise + +function createId(prefix: string): string { + return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` +} + +export async function embedMany< + TAdapter extends EmbeddingAdapter +>( + options: EmbedManyActivityOptions +): EmbedManyActivityResult { + const { adapter, ...rest } = options + const model = adapter.model + const requestId = createId('embed-many') + const startTime = Date.now() + + aiEventClient.emit('embed-many:request:started', { + requestId, + provider: adapter.name, + model, + values: rest.values, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: startTime, + }) + + const result = await adapter.embedMany({ ...rest, model }) + const duration = Date.now() - startTime + + aiEventClient.emit('embed-many:request:completed', { + requestId, + provider: adapter.name, + model, + embeddings: result.embeddings, + duration, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: Date.now(), + }) + + if (result.usage) { + aiEventClient.emit('embed:usage', { + requestId, + provider: adapter.name, + model, + usage: result.usage, + timestamp: Date.now(), + }) + } + + return result +} \ No newline at end of file diff --git a/packages/typescript/ai/src/activities/embed/embed.ts b/packages/typescript/ai/src/activities/embed/embed.ts new file mode 100644 index 000000000..8469ec574 --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/embed.ts @@ -0,0 +1,66 @@ +import { aiEventClient } from "../../event-client" +import type { EmbeddingAdapter, kind } from "./adapter" +import type { EmbedResult } from "../../types" + +export interface EmbedActivityOptions< + TAdapter extends EmbeddingAdapter +> { + /** The embedding adapter to use (must be created with a model) */ + adapter: TAdapter & { kind: typeof kind } + /** The value to convert to embedding */ + value: string + /** Model-specific options for embedding */ + modelOptions?: TAdapter['~types']['providerOptions'] +} + +/** Result type for the embed activity */ +export type EmbedActivityResult = Promise + +function createId(prefix: string): string { + return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` +} + +export async function embed< + TAdapter extends EmbeddingAdapter +>( + options: EmbedActivityOptions +): EmbedActivityResult { + const { adapter, ...rest } = options + const model = adapter.model + const requestId = createId('embed') + const startTime = Date.now() + + aiEventClient.emit('embed:request:started', { + requestId, + provider: adapter.name, + model, + value: rest.value, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: startTime, + }) + + const result = await adapter.embed({ ...rest, model }) + const duration = Date.now() - startTime + + aiEventClient.emit('embed:request:completed', { + requestId, + provider: adapter.name, + model, + embedding: result.embedding, + duration, + modelOptions: rest.modelOptions as Record | undefined, + timestamp: Date.now(), + }) + + if (result.usage) { + aiEventClient.emit('embed:usage', { + requestId, + provider: adapter.name, + model, + usage: result.usage, + timestamp: Date.now(), + }) + } + + return result +} \ No newline at end of file diff --git a/packages/typescript/ai/src/activities/embed/index.ts b/packages/typescript/ai/src/activities/embed/index.ts new file mode 100644 index 000000000..9b7979e27 --- /dev/null +++ b/packages/typescript/ai/src/activities/embed/index.ts @@ -0,0 +1,10 @@ +export { embed } from "./embed" +export { embedMany } from "./embed-many" +export { kind } from "./adapter" + +// Re-export adapter types +export { BaseEmbeddingAdapter } from './adapter' +export type { + AnyEmbeddingAdapter, + EmbeddingAdapterConfig, +} from './adapter' \ No newline at end of file diff --git a/packages/typescript/ai/src/activities/index.ts b/packages/typescript/ai/src/activities/index.ts index 521675a71..004a59270 100644 --- a/packages/typescript/ai/src/activities/index.ts +++ b/packages/typescript/ai/src/activities/index.ts @@ -141,6 +141,23 @@ export { type AnyTranscriptionAdapter, } from './generateTranscription/adapter' +// =========================== +// Embedding Activity +// =========================== + +export { + kind as embeddingKind, + embed, + embedMany, +} from './embed/index' + +export { + BaseEmbeddingAdapter, + type EmbeddingAdapter, + type EmbeddingAdapterConfig, + type AnyEmbeddingAdapter, +} from './embed/adapter' + // =========================== // Adapter Union Types // =========================== diff --git a/packages/typescript/ai/src/event-client.ts b/packages/typescript/ai/src/event-client.ts index b76d67055..f0f5bccad 100644 --- a/packages/typescript/ai/src/event-client.ts +++ b/packages/typescript/ai/src/event-client.ts @@ -33,6 +33,10 @@ export interface ImageUsage { totalTokens?: number } +export interface EmbeddingsUsage { + totalTokens: number +} + interface BaseEventContext { timestamp: number requestId?: string @@ -384,6 +388,62 @@ export interface VideoUsageEvent extends BaseEventContext { usage: TokenUsage } +// =========================== +// Embed Events +// =========================== + +/** Emitted when an embed request starts. */ +export interface EmbedRequestStartedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + value: string +} + +/** Emitted when an embed request completes. */ +export interface EmbedRequestCompletedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + embedding: Array + duration: number +} + +/** Emitted when embed usage metrics are available. */ +export interface EmbedUsageEvent extends BaseEventContext { + requestId: string + model: string + usage: EmbeddingsUsage +} + +// =========================== +// EmbedMany Events +// =========================== + +/** Emitted when an embed many request starts. */ +export interface EmbedManyRequestStartedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + values: Array +} + +/** Emitted when an embed many request completes. */ +export interface EmbedManyRequestCompletedEvent extends BaseEventContext { + requestId: string + provider: string + model: string + embeddings: Array> + duration: number +} + +/** Emitted when embed many usage metrics are available. */ +export interface EmbedManyUsageEvent extends BaseEventContext { + requestId: string + model: string + usage: EmbeddingsUsage +} + // =========================== // Client Events // =========================== @@ -475,6 +535,14 @@ export interface AIDevtoolsEventMap { 'tanstack-ai-devtools:video:request:completed': VideoRequestCompletedEvent 'tanstack-ai-devtools:video:usage': VideoUsageEvent + // Embed events + 'tanstack-ai-devtools:embed:request:started': EmbedRequestStartedEvent + 'tanstack-ai-devtools:embed:request:completed': EmbedRequestCompletedEvent + 'tanstack-ai-devtools:embed:usage': EmbedUsageEvent + 'tanstack-ai-devtools:embed-many:request:started': EmbedManyRequestStartedEvent + 'tanstack-ai-devtools:embed-many:request:completed': EmbedManyRequestCompletedEvent + 'tanstack-ai-devtools:embed-many:usage': EmbedManyUsageEvent + // Client events 'tanstack-ai-devtools:client:created': ClientCreatedEvent 'tanstack-ai-devtools:client:loading:changed': ClientLoadingChangedEvent diff --git a/packages/typescript/ai/src/types.ts b/packages/typescript/ai/src/types.ts index 7bd3c52d9..4303e735e 100644 --- a/packages/typescript/ai/src/types.ts +++ b/packages/typescript/ai/src/types.ts @@ -1,4 +1,5 @@ import type { StandardJSONSchemaV1 } from '@standard-schema/spec' +import type { EmbeddingsUsage } from './event-client' /** * Tool call states - track the lifecycle of a tool call @@ -231,9 +232,9 @@ export type ConstrainedContent< export interface ModelMessage< TContent extends string | null | Array = - | string - | null - | Array, + | string + | null + | Array, > { role: 'user' | 'assistant' | 'tool' content: TContent @@ -1172,3 +1173,51 @@ export interface DefaultMessageMetadataByModality { video: unknown document: unknown } + +// ============================================================================ +// Embed Types +// ============================================================================ + +export interface EmbedOptions< + TProviderOptions extends object = object +> { + /** The model to use for transcription */ + model: string + /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */ + value: string + /** Model-specific options for embedding */ + modelOptions?: TProviderOptions +} + +export interface EmbedManyOptions< + TProviderOptions extends object = object +> { + /** The model to use for transcription */ + model: string + /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */ + values: Array + /** Model-specific options for embedding */ + modelOptions?: TProviderOptions +} + +export interface EmbedResult { + /** Unique identifier for the embedding */ + id: string + /** Model used for embedding */ + model: string + /** The embedding vector */ + embedding: Array + /** Usage metadata */ + usage?: EmbeddingsUsage +} + +export interface EmbedManyResult { + /** Unique identifier for the embedding */ + id: string + /** Model used for embedding */ + model: string + /** The embedding vectors */ + embeddings: Array> + /** Usage metadata */ + usage?: EmbeddingsUsage +} \ No newline at end of file From e7b40878c8dc71e201a1e134a7f4cdaf1df22f45 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Fri, 13 Feb 2026 20:07:06 +0200 Subject: [PATCH 02/14] added changeset --- .changeset/tired-spoons-nail.md | 29 +++++++++++++++++++ .../embedding/embedding-provider-options.ts | 6 ++-- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 .changeset/tired-spoons-nail.md diff --git a/.changeset/tired-spoons-nail.md b/.changeset/tired-spoons-nail.md new file mode 100644 index 000000000..ae30d2f6e --- /dev/null +++ b/.changeset/tired-spoons-nail.md @@ -0,0 +1,29 @@ +--- +'@tanstack/tests-adapters': minor +'@tanstack/preact-ai-devtools': minor +'@tanstack/react-ai-devtools': minor +'@tanstack/solid-ai-devtools': minor +'@tanstack/smoke-tests-e2e': minor +'@tanstack/ai-openrouter': minor +'@tanstack/ai-anthropic': minor +'@tanstack/ai-devtools-core': minor +'@tanstack/ai-react-ui': minor +'@tanstack/ai-solid-ui': minor +'@tanstack/ai-client': minor +'@tanstack/ai-gemini': minor +'@tanstack/ai-ollama': minor +'@tanstack/ai-openai': minor +'@tanstack/ai-preact': minor +'@tanstack/ai-svelte': minor +'@tanstack/ai-vue-ui': minor +'@tanstack/ai-react': minor +'@tanstack/ai-solid': minor +'@tanstack/ai-grok': minor +'@tanstack/ai-vue': minor +'ts-svelte-chat': minor +'@tanstack/ai': minor +'vanilla-chat': minor +'ts-vue-chat': minor +--- + +Added embed/embedMany activity and Gemini embeddings adapter diff --git a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts index fe2a0ff0f..7782115d4 100644 --- a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts +++ b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts @@ -1,5 +1,5 @@ import type { HttpOptions } from "@google/genai"; -import type { GeminiEmbeddingModel } from "../adapters/embedding"; +import type { GeminiEmbeddingModels } from "../model-meta"; export interface GeminiEmbeddingProviderOptions { /** Used to override HTTP request options. */ @@ -22,7 +22,7 @@ export interface GeminiEmbeddingProviderOptions { } export type GeminiEmbeddingModelProviderOptionsByName = { - [K in GeminiEmbeddingModel]: GeminiEmbeddingProviderOptions + [K in GeminiEmbeddingModels]: GeminiEmbeddingProviderOptions } /** @@ -45,7 +45,7 @@ export function validateTaskType(options: { taskType !== 'QUESTION_ANSWERING' && taskType !== 'FACT_VERIFICATION' ) { - throw new Error(`Invalid task type "${taskType}" for model "${model}".`) + throw new Error(`Invalid task type "${taskType}" for model "${model}".`) } } From 8317f552ae0c66adf6926ffd1ad4805d110253cc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:25:26 +0000 Subject: [PATCH 03/14] ci: apply automated fixes --- .../ai-gemini/src/adapters/embedding.ts | 40 ++++++++++++------- .../embedding/embedding-provider-options.ts | 16 ++++---- .../typescript/ai-gemini/src/model-meta.ts | 4 +- .../ai/src/activities/embed/adapter.ts | 20 ++++++---- .../ai/src/activities/embed/embed-many.ts | 16 ++++---- .../ai/src/activities/embed/embed.ts | 16 ++++---- .../ai/src/activities/embed/index.ts | 11 ++--- .../typescript/ai/src/activities/index.ts | 6 +-- packages/typescript/ai/src/types.ts | 16 +++----- 9 files changed, 72 insertions(+), 73 deletions(-) diff --git a/packages/typescript/ai-gemini/src/adapters/embedding.ts b/packages/typescript/ai-gemini/src/adapters/embedding.ts index 3e7bfa766..cf26f1f60 100644 --- a/packages/typescript/ai-gemini/src/adapters/embedding.ts +++ b/packages/typescript/ai-gemini/src/adapters/embedding.ts @@ -1,16 +1,27 @@ -import { BaseEmbeddingAdapter } from "@tanstack/ai/adapters"; -import { createGeminiClient, generateId } from "../utils"; -import { validateTaskType, validateValue } from "../embedding/embedding-provider-options"; -import type { GoogleGenAI } from "@google/genai"; -import type { EmbedManyOptions, EmbedManyResult, EmbedOptions, EmbedResult } from "@tanstack/ai"; -import type { GEMINI_EMBEDDING_MODELS } from "../model-meta"; -import type { GeminiClientConfig } from "../utils"; -import type { GeminiEmbeddingModelProviderOptionsByName, GeminiEmbeddingProviderOptions } from "../embedding/embedding-provider-options"; +import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters' +import { createGeminiClient, generateId } from '../utils' +import { + validateTaskType, + validateValue, +} from '../embedding/embedding-provider-options' +import type { GoogleGenAI } from '@google/genai' +import type { + EmbedManyOptions, + EmbedManyResult, + EmbedOptions, + EmbedResult, +} from '@tanstack/ai' +import type { GEMINI_EMBEDDING_MODELS } from '../model-meta' +import type { GeminiClientConfig } from '../utils' +import type { + GeminiEmbeddingModelProviderOptionsByName, + GeminiEmbeddingProviderOptions, +} from '../embedding/embedding-provider-options' /** * Configuration for Gemini embedding adapter */ -export interface GeminiEmbeddingConfig extends GeminiClientConfig { } +export interface GeminiEmbeddingConfig extends GeminiClientConfig {} export type GeminiEmbeddingModel = (typeof GEMINI_EMBEDDING_MODELS)[number] @@ -34,7 +45,7 @@ export class GeminiEmbeddingAdapter< } async embed( - options: EmbedOptions + options: EmbedOptions, ): Promise { const { model, value, modelOptions } = options @@ -46,7 +57,7 @@ export class GeminiEmbeddingAdapter< contents: value, config: { ...modelOptions, - } + }, }) return { @@ -58,7 +69,7 @@ export class GeminiEmbeddingAdapter< } async embedMany( - options: EmbedManyOptions + options: EmbedManyOptions, ): Promise { const { model, values, modelOptions } = options @@ -69,8 +80,8 @@ export class GeminiEmbeddingAdapter< model, contents: values, config: { - ...modelOptions - } + ...modelOptions, + }, }) return { @@ -81,4 +92,3 @@ export class GeminiEmbeddingAdapter< } } } - diff --git a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts index 7782115d4..cf0782219 100644 --- a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts +++ b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts @@ -1,17 +1,17 @@ -import type { HttpOptions } from "@google/genai"; -import type { GeminiEmbeddingModels } from "../model-meta"; +import type { HttpOptions } from '@google/genai' +import type { GeminiEmbeddingModels } from '../model-meta' export interface GeminiEmbeddingProviderOptions { /** Used to override HTTP request options. */ - httpOptions?: HttpOptions; + httpOptions?: HttpOptions /** * Type of task for which the embedding will be used. */ - taskType?: string; + taskType?: string /** * Title for the text. Only applicable when TaskType is `RETRIEVAL_DOCUMENT`. */ - title?: string; + title?: string /** * Reduced dimension for the output embedding. If set, * excessive values in the output embedding are truncated from the end. @@ -33,7 +33,7 @@ export function validateTaskType(options: { model: string }) { const { taskType, model } = options - if (!taskType) return; + if (!taskType) return if ( taskType !== 'SEMANTIC_SIMILARITY' && @@ -63,7 +63,9 @@ export function validateValue(options: { } for (const v of value) { if (!v || v.trim().length === 0) { - throw new Error(`Value array cannot contain empty values for model "${model}".`) + throw new Error( + `Value array cannot contain empty values for model "${model}".`, + ) } } } else { diff --git a/packages/typescript/ai-gemini/src/model-meta.ts b/packages/typescript/ai-gemini/src/model-meta.ts index 7da85403e..a9069326e 100644 --- a/packages/typescript/ai-gemini/src/model-meta.ts +++ b/packages/typescript/ai-gemini/src/model-meta.ts @@ -855,9 +855,7 @@ export const GEMINI_MODELS = [ export type GeminiModels = (typeof GEMINI_MODELS)[number] -export const GEMINI_EMBEDDING_MODELS = [ - GEMINI_EMBEDDING_001.name, -] as const +export const GEMINI_EMBEDDING_MODELS = [GEMINI_EMBEDDING_001.name] as const export type GeminiEmbeddingModels = (typeof GEMINI_EMBEDDING_MODELS)[number] diff --git a/packages/typescript/ai/src/activities/embed/adapter.ts b/packages/typescript/ai/src/activities/embed/adapter.ts index e6ee78de6..0f9c7d7f4 100644 --- a/packages/typescript/ai/src/activities/embed/adapter.ts +++ b/packages/typescript/ai/src/activities/embed/adapter.ts @@ -1,4 +1,9 @@ -import type { EmbedManyOptions, EmbedManyResult, EmbedOptions, EmbedResult } from "../../types" +import type { + EmbedManyOptions, + EmbedManyResult, + EmbedOptions, + EmbedResult, +} from '../../types' // =========================== // Activity Kind @@ -17,7 +22,7 @@ export interface EmbeddingAdapterConfig { export interface EmbeddingAdapter< TModel extends string = string, - TProviderOptions extends object = Record + TProviderOptions extends object = Record, > { /** Discriminator for adapter kind - used to determine API shape */ readonly kind: 'embedding' @@ -26,7 +31,6 @@ export interface EmbeddingAdapter< /** The model this adapter is configured for */ readonly model: TModel - /** * @internal Type-only properties for inference. Not assigned at runtime. */ @@ -42,7 +46,9 @@ export interface EmbeddingAdapter< /** * Generate embeddings for multiple texts */ - embedMany: (options: EmbedManyOptions) => Promise + embedMany: ( + options: EmbedManyOptions, + ) => Promise } /** @@ -77,9 +83,7 @@ export abstract class BaseEmbeddingAdapter< this.model = model } - abstract embed( - options: EmbedOptions, - ): Promise + abstract embed(options: EmbedOptions): Promise abstract embedMany( options: EmbedManyOptions, @@ -88,4 +92,4 @@ export abstract class BaseEmbeddingAdapter< protected generateId(): string { return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}` } -} \ No newline at end of file +} diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts index c7d7a6e40..b71adbd0e 100644 --- a/packages/typescript/ai/src/activities/embed/embed-many.ts +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -1,9 +1,9 @@ -import { aiEventClient } from "../../event-client" -import type { EmbeddingAdapter, kind } from "./adapter" -import type { EmbedManyResult } from "../../types" +import { aiEventClient } from '../../event-client' +import type { EmbeddingAdapter, kind } from './adapter' +import type { EmbedManyResult } from '../../types' export interface EmbedManyActivityOptions< - TAdapter extends EmbeddingAdapter + TAdapter extends EmbeddingAdapter, > { /** The embedding adapter to use (must be created with a model) */ adapter: TAdapter & { kind: typeof kind } @@ -21,10 +21,8 @@ function createId(prefix: string): string { } export async function embedMany< - TAdapter extends EmbeddingAdapter ->( - options: EmbedManyActivityOptions -): EmbedManyActivityResult { + TAdapter extends EmbeddingAdapter, +>(options: EmbedManyActivityOptions): EmbedManyActivityResult { const { adapter, ...rest } = options const model = adapter.model const requestId = createId('embed-many') @@ -63,4 +61,4 @@ export async function embedMany< } return result -} \ No newline at end of file +} diff --git a/packages/typescript/ai/src/activities/embed/embed.ts b/packages/typescript/ai/src/activities/embed/embed.ts index 8469ec574..f85efc5e0 100644 --- a/packages/typescript/ai/src/activities/embed/embed.ts +++ b/packages/typescript/ai/src/activities/embed/embed.ts @@ -1,9 +1,9 @@ -import { aiEventClient } from "../../event-client" -import type { EmbeddingAdapter, kind } from "./adapter" -import type { EmbedResult } from "../../types" +import { aiEventClient } from '../../event-client' +import type { EmbeddingAdapter, kind } from './adapter' +import type { EmbedResult } from '../../types' export interface EmbedActivityOptions< - TAdapter extends EmbeddingAdapter + TAdapter extends EmbeddingAdapter, > { /** The embedding adapter to use (must be created with a model) */ adapter: TAdapter & { kind: typeof kind } @@ -20,10 +20,8 @@ function createId(prefix: string): string { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` } -export async function embed< - TAdapter extends EmbeddingAdapter ->( - options: EmbedActivityOptions +export async function embed>( + options: EmbedActivityOptions, ): EmbedActivityResult { const { adapter, ...rest } = options const model = adapter.model @@ -63,4 +61,4 @@ export async function embed< } return result -} \ No newline at end of file +} diff --git a/packages/typescript/ai/src/activities/embed/index.ts b/packages/typescript/ai/src/activities/embed/index.ts index 9b7979e27..9df865328 100644 --- a/packages/typescript/ai/src/activities/embed/index.ts +++ b/packages/typescript/ai/src/activities/embed/index.ts @@ -1,10 +1,7 @@ -export { embed } from "./embed" -export { embedMany } from "./embed-many" -export { kind } from "./adapter" +export { embed } from './embed' +export { embedMany } from './embed-many' +export { kind } from './adapter' // Re-export adapter types export { BaseEmbeddingAdapter } from './adapter' -export type { - AnyEmbeddingAdapter, - EmbeddingAdapterConfig, -} from './adapter' \ No newline at end of file +export type { AnyEmbeddingAdapter, EmbeddingAdapterConfig } from './adapter' diff --git a/packages/typescript/ai/src/activities/index.ts b/packages/typescript/ai/src/activities/index.ts index 004a59270..219661eb0 100644 --- a/packages/typescript/ai/src/activities/index.ts +++ b/packages/typescript/ai/src/activities/index.ts @@ -145,11 +145,7 @@ export { // Embedding Activity // =========================== -export { - kind as embeddingKind, - embed, - embedMany, -} from './embed/index' +export { kind as embeddingKind, embed, embedMany } from './embed/index' export { BaseEmbeddingAdapter, diff --git a/packages/typescript/ai/src/types.ts b/packages/typescript/ai/src/types.ts index 4303e735e..70f476c16 100644 --- a/packages/typescript/ai/src/types.ts +++ b/packages/typescript/ai/src/types.ts @@ -232,9 +232,9 @@ export type ConstrainedContent< export interface ModelMessage< TContent extends string | null | Array = - | string - | null - | Array, + | string + | null + | Array, > { role: 'user' | 'assistant' | 'tool' content: TContent @@ -1178,9 +1178,7 @@ export interface DefaultMessageMetadataByModality { // Embed Types // ============================================================================ -export interface EmbedOptions< - TProviderOptions extends object = object -> { +export interface EmbedOptions { /** The model to use for transcription */ model: string /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */ @@ -1189,9 +1187,7 @@ export interface EmbedOptions< modelOptions?: TProviderOptions } -export interface EmbedManyOptions< - TProviderOptions extends object = object -> { +export interface EmbedManyOptions { /** The model to use for transcription */ model: string /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */ @@ -1220,4 +1216,4 @@ export interface EmbedManyResult { embeddings: Array> /** Usage metadata */ usage?: EmbeddingsUsage -} \ No newline at end of file +} From bca129c319924cfadfd3e102bf0e4f7a6a1ccaad Mon Sep 17 00:00:00 2001 From: Nikas Belogolov <30692665+nikas-belogolov@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:01:27 +0200 Subject: [PATCH 04/14] Update packages/typescript/ai/src/types.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/typescript/ai/src/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/typescript/ai/src/types.ts b/packages/typescript/ai/src/types.ts index 70f476c16..cb0174ad3 100644 --- a/packages/typescript/ai/src/types.ts +++ b/packages/typescript/ai/src/types.ts @@ -1179,18 +1179,18 @@ export interface DefaultMessageMetadataByModality { // ============================================================================ export interface EmbedOptions { - /** The model to use for transcription */ + /** The model to use for embedding */ model: string - /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */ + /** The text value to embed */ value: string /** Model-specific options for embedding */ modelOptions?: TProviderOptions } export interface EmbedManyOptions { - /** The model to use for transcription */ + /** The model to use for embedding */ model: string - /** The audio data to transcribe - can be base64 string, File, Blob, or Buffer */ + /** The text values to embed */ values: Array /** Model-specific options for embedding */ modelOptions?: TProviderOptions From c5d6b927583c57baf92a42848dc0ae12f26ca538 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov <30692665+nikas-belogolov@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:20:53 +0200 Subject: [PATCH 05/14] Update packages/typescript/ai/src/activities/embed/embed-many.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/typescript/ai/src/activities/embed/embed-many.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts index b71adbd0e..9edf3e8af 100644 --- a/packages/typescript/ai/src/activities/embed/embed-many.ts +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -13,7 +13,7 @@ export interface EmbedManyActivityOptions< modelOptions?: TAdapter['~types']['providerOptions'] } -/** Result type for the TTS activity */ +/** Result type for the embed-many activity */ export type EmbedManyActivityResult = Promise function createId(prefix: string): string { From 36cc319175fbf6500ec078362cffbe45c5a75485 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov <30692665+nikas-belogolov@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:21:22 +0200 Subject: [PATCH 06/14] Update packages/typescript/ai/src/activities/embed/embed-many.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/typescript/ai/src/activities/embed/embed-many.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts index 9edf3e8af..04a5e6100 100644 --- a/packages/typescript/ai/src/activities/embed/embed-many.ts +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -7,7 +7,7 @@ export interface EmbedManyActivityOptions< > { /** The embedding adapter to use (must be created with a model) */ adapter: TAdapter & { kind: typeof kind } - /** The value to convert to embedding */ + /** The values to convert to embeddings */ values: Array /** Model-specific options for embedding */ modelOptions?: TAdapter['~types']['providerOptions'] From 8a00a484215dbda37778632db2b2ca68e6a62878 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sat, 14 Feb 2026 10:28:01 +0200 Subject: [PATCH 07/14] moved createId into a utility module, added typing --- .../embedding/embedding-provider-options.ts | 32 +++++++++++-------- .../ai/src/activities/embed/adapter.ts | 4 --- .../ai/src/activities/embed/embed-many.ts | 10 +++--- .../ai/src/activities/embed/embed.ts | 17 +++++----- .../ai/src/activities/generateImage/index.ts | 7 ++-- .../ai/src/activities/generateSpeech/index.ts | 7 ++-- .../activities/generateTranscription/index.ts | 7 ++-- .../ai/src/activities/generateVideo/index.ts | 11 ++----- .../ai/src/activities/summarize/index.ts | 11 ++----- .../typescript/ai/src/activities/utils/id.ts | 3 ++ 10 files changed, 43 insertions(+), 66 deletions(-) create mode 100644 packages/typescript/ai/src/activities/utils/id.ts diff --git a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts index cf0782219..363bf58b8 100644 --- a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts +++ b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts @@ -1,5 +1,18 @@ -import type { HttpOptions } from '@google/genai' -import type { GeminiEmbeddingModels } from '../model-meta' +import type { HttpOptions } from "@google/genai"; +import type { GeminiEmbeddingModels } from "../model-meta"; + +const VALID_TASK_TYPES = new Set([ + 'SEMANTIC_SIMILARITY', + 'CLASSIFICATION', + 'CLUSTERING', + 'RETRIEVAL_DOCUMENT', + 'RETRIEVAL_QUERY', + 'CODE_RETRIEVAL_QUERY', + 'QUESTION_ANSWERING', + 'FACT_VERIFICATION' +]) + +type TaskType = 'SEMANTIC_SIMILARITY' | 'CLASSIFICATION' | 'CLUSTERING' | 'RETRIEVAL_DOCUMENT' | 'RETRIEVAL_QUERY' | 'CODE_RETRIEVAL_QUERY' | 'QUESTION_ANSWERING' | 'FACT_VERIFICATION' export interface GeminiEmbeddingProviderOptions { /** Used to override HTTP request options. */ @@ -7,7 +20,7 @@ export interface GeminiEmbeddingProviderOptions { /** * Type of task for which the embedding will be used. */ - taskType?: string + taskType?: TaskType; /** * Title for the text. Only applicable when TaskType is `RETRIEVAL_DOCUMENT`. */ @@ -29,22 +42,13 @@ export type GeminiEmbeddingModelProviderOptionsByName = { * Validates the task type */ export function validateTaskType(options: { - taskType: string | undefined + taskType: TaskType | undefined model: string }) { const { taskType, model } = options if (!taskType) return - if ( - taskType !== 'SEMANTIC_SIMILARITY' && - taskType !== 'CLASSIFICATION' && - taskType !== 'CLUSTERING' && - taskType !== 'RETRIEVAL_DOCUMENT' && - taskType !== 'RETRIEVAL_QUERY' && - taskType !== 'CODE_RETRIEVAL_QUERY' && - taskType !== 'QUESTION_ANSWERING' && - taskType !== 'FACT_VERIFICATION' - ) { + if (!VALID_TASK_TYPES.has(taskType)) { throw new Error(`Invalid task type "${taskType}" for model "${model}".`) } } diff --git a/packages/typescript/ai/src/activities/embed/adapter.ts b/packages/typescript/ai/src/activities/embed/adapter.ts index 0f9c7d7f4..72610e95b 100644 --- a/packages/typescript/ai/src/activities/embed/adapter.ts +++ b/packages/typescript/ai/src/activities/embed/adapter.ts @@ -88,8 +88,4 @@ export abstract class BaseEmbeddingAdapter< abstract embedMany( options: EmbedManyOptions, ): Promise - - protected generateId(): string { - return `${this.name}-${Date.now()}-${Math.random().toString(36).substring(7)}` - } } diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts index 04a5e6100..0b3fa37dc 100644 --- a/packages/typescript/ai/src/activities/embed/embed-many.ts +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -1,6 +1,7 @@ -import { aiEventClient } from '../../event-client' -import type { EmbeddingAdapter, kind } from './adapter' -import type { EmbedManyResult } from '../../types' +import { aiEventClient } from "../../event-client" +import { createId } from '../utils/id' +import type { EmbedManyResult } from "../../types" +import type { EmbeddingAdapter, kind } from "./adapter" export interface EmbedManyActivityOptions< TAdapter extends EmbeddingAdapter, @@ -16,9 +17,6 @@ export interface EmbedManyActivityOptions< /** Result type for the embed-many activity */ export type EmbedManyActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} export async function embedMany< TAdapter extends EmbeddingAdapter, diff --git a/packages/typescript/ai/src/activities/embed/embed.ts b/packages/typescript/ai/src/activities/embed/embed.ts index f85efc5e0..7cf83ee41 100644 --- a/packages/typescript/ai/src/activities/embed/embed.ts +++ b/packages/typescript/ai/src/activities/embed/embed.ts @@ -1,6 +1,7 @@ -import { aiEventClient } from '../../event-client' -import type { EmbeddingAdapter, kind } from './adapter' -import type { EmbedResult } from '../../types' +import { aiEventClient } from "../../event-client" +import { createId } from '../utils/id' +import type { EmbedResult } from "../../types" +import type { EmbeddingAdapter, kind } from "./adapter" export interface EmbedActivityOptions< TAdapter extends EmbeddingAdapter, @@ -16,12 +17,10 @@ export interface EmbedActivityOptions< /** Result type for the embed activity */ export type EmbedActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - -export async function embed>( - options: EmbedActivityOptions, +export async function embed< + TAdapter extends EmbeddingAdapter +>( + options: EmbedActivityOptions ): EmbedActivityResult { const { adapter, ...rest } = options const model = adapter.model diff --git a/packages/typescript/ai/src/activities/generateImage/index.ts b/packages/typescript/ai/src/activities/generateImage/index.ts index d573b32f1..bcee9d7b5 100644 --- a/packages/typescript/ai/src/activities/generateImage/index.ts +++ b/packages/typescript/ai/src/activities/generateImage/index.ts @@ -6,8 +6,9 @@ */ import { aiEventClient } from '../../event-client.js' -import type { ImageAdapter } from './adapter' +import { createId } from '../utils/id' import type { ImageGenerationResult } from '../../types' +import type { ImageAdapter } from './adapter' // =========================== // Activity Kind @@ -83,10 +84,6 @@ export interface ImageActivityOptions< /** Result type for the image activity */ export type ImageActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/generateSpeech/index.ts b/packages/typescript/ai/src/activities/generateSpeech/index.ts index 39645ebab..b54fda578 100644 --- a/packages/typescript/ai/src/activities/generateSpeech/index.ts +++ b/packages/typescript/ai/src/activities/generateSpeech/index.ts @@ -6,8 +6,9 @@ */ import { aiEventClient } from '../../event-client.js' -import type { TTSAdapter } from './adapter' +import { createId } from '../utils/id' import type { TTSResult } from '../../types' +import type { TTSAdapter } from './adapter' // =========================== // Activity Kind @@ -62,10 +63,6 @@ export interface TTSActivityOptions< /** Result type for the TTS activity */ export type TTSActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/generateTranscription/index.ts b/packages/typescript/ai/src/activities/generateTranscription/index.ts index dff9a477b..5cc67c0eb 100644 --- a/packages/typescript/ai/src/activities/generateTranscription/index.ts +++ b/packages/typescript/ai/src/activities/generateTranscription/index.ts @@ -6,8 +6,9 @@ */ import { aiEventClient } from '../../event-client.js' -import type { TranscriptionAdapter } from './adapter' +import { createId } from '../utils/id' import type { TranscriptionResult } from '../../types' +import type { TranscriptionAdapter } from './adapter' // =========================== // Activity Kind @@ -62,10 +63,6 @@ export interface TranscriptionActivityOptions< /** Result type for the transcription activity */ export type TranscriptionActivityResult = Promise -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/generateVideo/index.ts b/packages/typescript/ai/src/activities/generateVideo/index.ts index 9a7a46aa4..01ffe0167 100644 --- a/packages/typescript/ai/src/activities/generateVideo/index.ts +++ b/packages/typescript/ai/src/activities/generateVideo/index.ts @@ -8,12 +8,13 @@ */ import { aiEventClient } from '../../event-client.js' -import type { VideoAdapter } from './adapter' +import { createId } from '../utils/id' import type { VideoJobResult, VideoStatusResult, VideoUrlResult, } from '../../types' +import type { VideoAdapter } from './adapter' // =========================== // Activity Kind @@ -34,14 +35,6 @@ export type VideoProviderOptions = ? TAdapter['~types']['providerOptions'] : object -// =========================== -// Activity Options Types - -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} -// =========================== - /** * Base options shared by all video activity operations. * The model is extracted from the adapter's model property. diff --git a/packages/typescript/ai/src/activities/summarize/index.ts b/packages/typescript/ai/src/activities/summarize/index.ts index ddee4280a..6ffce87e0 100644 --- a/packages/typescript/ai/src/activities/summarize/index.ts +++ b/packages/typescript/ai/src/activities/summarize/index.ts @@ -6,12 +6,13 @@ */ import { aiEventClient } from '../../event-client.js' -import type { SummarizeAdapter } from './adapter' +import { createId } from '../utils/id' import type { StreamChunk, SummarizationOptions, SummarizationResult, } from '../../types' +import type { SummarizeAdapter } from './adapter' // =========================== // Activity Kind @@ -81,14 +82,6 @@ export type SummarizeActivityResult = ? AsyncIterable : Promise -// =========================== -// Helper Functions -// =========================== - -function createId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` -} - // =========================== // Activity Implementation // =========================== diff --git a/packages/typescript/ai/src/activities/utils/id.ts b/packages/typescript/ai/src/activities/utils/id.ts new file mode 100644 index 000000000..7ec803573 --- /dev/null +++ b/packages/typescript/ai/src/activities/utils/id.ts @@ -0,0 +1,3 @@ +export function createId(prefix: string): string { + return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}` +} From 74d3d9c3764c8841f1d39aa055cd900af60c81ee Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 08:33:39 +0000 Subject: [PATCH 08/14] ci: apply automated fixes --- .../embedding/embedding-provider-options.ts | 18 +++++++++++++----- .../ai/src/activities/embed/embed-many.ts | 7 +++---- .../ai/src/activities/embed/embed.ts | 12 +++++------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts index 363bf58b8..83a442bda 100644 --- a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts +++ b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts @@ -1,5 +1,5 @@ -import type { HttpOptions } from "@google/genai"; -import type { GeminiEmbeddingModels } from "../model-meta"; +import type { HttpOptions } from '@google/genai' +import type { GeminiEmbeddingModels } from '../model-meta' const VALID_TASK_TYPES = new Set([ 'SEMANTIC_SIMILARITY', @@ -9,10 +9,18 @@ const VALID_TASK_TYPES = new Set([ 'RETRIEVAL_QUERY', 'CODE_RETRIEVAL_QUERY', 'QUESTION_ANSWERING', - 'FACT_VERIFICATION' + 'FACT_VERIFICATION', ]) -type TaskType = 'SEMANTIC_SIMILARITY' | 'CLASSIFICATION' | 'CLUSTERING' | 'RETRIEVAL_DOCUMENT' | 'RETRIEVAL_QUERY' | 'CODE_RETRIEVAL_QUERY' | 'QUESTION_ANSWERING' | 'FACT_VERIFICATION' +type TaskType = + | 'SEMANTIC_SIMILARITY' + | 'CLASSIFICATION' + | 'CLUSTERING' + | 'RETRIEVAL_DOCUMENT' + | 'RETRIEVAL_QUERY' + | 'CODE_RETRIEVAL_QUERY' + | 'QUESTION_ANSWERING' + | 'FACT_VERIFICATION' export interface GeminiEmbeddingProviderOptions { /** Used to override HTTP request options. */ @@ -20,7 +28,7 @@ export interface GeminiEmbeddingProviderOptions { /** * Type of task for which the embedding will be used. */ - taskType?: TaskType; + taskType?: TaskType /** * Title for the text. Only applicable when TaskType is `RETRIEVAL_DOCUMENT`. */ diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts index 0b3fa37dc..0429081d3 100644 --- a/packages/typescript/ai/src/activities/embed/embed-many.ts +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -1,7 +1,7 @@ -import { aiEventClient } from "../../event-client" +import { aiEventClient } from '../../event-client' import { createId } from '../utils/id' -import type { EmbedManyResult } from "../../types" -import type { EmbeddingAdapter, kind } from "./adapter" +import type { EmbedManyResult } from '../../types' +import type { EmbeddingAdapter, kind } from './adapter' export interface EmbedManyActivityOptions< TAdapter extends EmbeddingAdapter, @@ -17,7 +17,6 @@ export interface EmbedManyActivityOptions< /** Result type for the embed-many activity */ export type EmbedManyActivityResult = Promise - export async function embedMany< TAdapter extends EmbeddingAdapter, >(options: EmbedManyActivityOptions): EmbedManyActivityResult { diff --git a/packages/typescript/ai/src/activities/embed/embed.ts b/packages/typescript/ai/src/activities/embed/embed.ts index 7cf83ee41..5a8d4bb4d 100644 --- a/packages/typescript/ai/src/activities/embed/embed.ts +++ b/packages/typescript/ai/src/activities/embed/embed.ts @@ -1,7 +1,7 @@ -import { aiEventClient } from "../../event-client" +import { aiEventClient } from '../../event-client' import { createId } from '../utils/id' -import type { EmbedResult } from "../../types" -import type { EmbeddingAdapter, kind } from "./adapter" +import type { EmbedResult } from '../../types' +import type { EmbeddingAdapter, kind } from './adapter' export interface EmbedActivityOptions< TAdapter extends EmbeddingAdapter, @@ -17,10 +17,8 @@ export interface EmbedActivityOptions< /** Result type for the embed activity */ export type EmbedActivityResult = Promise -export async function embed< - TAdapter extends EmbeddingAdapter ->( - options: EmbedActivityOptions +export async function embed>( + options: EmbedActivityOptions, ): EmbedActivityResult { const { adapter, ...rest } = options const model = adapter.model From 704a4c6868456043d03d1cafa5457612646d9ee6 Mon Sep 17 00:00:00 2001 From: Nikas Belogolov <30692665+nikas-belogolov@users.noreply.github.com> Date: Sat, 14 Feb 2026 11:51:06 +0200 Subject: [PATCH 09/14] Update packages/typescript/ai/src/activities/generateSpeech/index.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/typescript/ai/src/activities/generateSpeech/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript/ai/src/activities/generateSpeech/index.ts b/packages/typescript/ai/src/activities/generateSpeech/index.ts index b54fda578..af7daad91 100644 --- a/packages/typescript/ai/src/activities/generateSpeech/index.ts +++ b/packages/typescript/ai/src/activities/generateSpeech/index.ts @@ -6,7 +6,7 @@ */ import { aiEventClient } from '../../event-client.js' -import { createId } from '../utils/id' +import { createId } from '../utils/id.js' import type { TTSResult } from '../../types' import type { TTSAdapter } from './adapter' From 55cb0a8e352dbc88801b296558989201c114241e Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sat, 14 Feb 2026 11:52:12 +0200 Subject: [PATCH 10/14] fixed event name --- packages/typescript/ai/src/activities/embed/embed-many.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript/ai/src/activities/embed/embed-many.ts b/packages/typescript/ai/src/activities/embed/embed-many.ts index 0429081d3..2b0fb5e29 100644 --- a/packages/typescript/ai/src/activities/embed/embed-many.ts +++ b/packages/typescript/ai/src/activities/embed/embed-many.ts @@ -48,7 +48,7 @@ export async function embedMany< }) if (result.usage) { - aiEventClient.emit('embed:usage', { + aiEventClient.emit('embed-many:usage', { requestId, provider: adapter.name, model, From c3f49f4c85f2b0a390bbbfc7901f160949d6fbac Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sat, 14 Feb 2026 11:55:31 +0200 Subject: [PATCH 11/14] derive TaskType type from VALID_TASK_TYPES --- .../src/embedding/embedding-provider-options.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts index 83a442bda..a62b7d3ce 100644 --- a/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts +++ b/packages/typescript/ai-gemini/src/embedding/embedding-provider-options.ts @@ -1,7 +1,7 @@ import type { HttpOptions } from '@google/genai' import type { GeminiEmbeddingModels } from '../model-meta' -const VALID_TASK_TYPES = new Set([ +const VALID_TASK_TYPES = [ 'SEMANTIC_SIMILARITY', 'CLASSIFICATION', 'CLUSTERING', @@ -10,17 +10,9 @@ const VALID_TASK_TYPES = new Set([ 'CODE_RETRIEVAL_QUERY', 'QUESTION_ANSWERING', 'FACT_VERIFICATION', -]) +] as const -type TaskType = - | 'SEMANTIC_SIMILARITY' - | 'CLASSIFICATION' - | 'CLUSTERING' - | 'RETRIEVAL_DOCUMENT' - | 'RETRIEVAL_QUERY' - | 'CODE_RETRIEVAL_QUERY' - | 'QUESTION_ANSWERING' - | 'FACT_VERIFICATION' +type TaskType = (typeof VALID_TASK_TYPES)[number] export interface GeminiEmbeddingProviderOptions { /** Used to override HTTP request options. */ @@ -56,7 +48,7 @@ export function validateTaskType(options: { const { taskType, model } = options if (!taskType) return - if (!VALID_TASK_TYPES.has(taskType)) { + if (!VALID_TASK_TYPES.includes(taskType)) { throw new Error(`Invalid task type "${taskType}" for model "${model}".`) } } From 3d6e10445748a37deef0da94abfba9ce5f16b24b Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sat, 14 Feb 2026 12:06:42 +0200 Subject: [PATCH 12/14] Added gemini embedding adpter totalTokens count --- .../typescript/ai-gemini/src/adapters/embedding.ts | 14 ++++++++++++-- .../typescript/ai/src/activities/embed/adapter.ts | 2 +- packages/typescript/ai/src/event-client.ts | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/typescript/ai-gemini/src/adapters/embedding.ts b/packages/typescript/ai-gemini/src/adapters/embedding.ts index cf26f1f60..e0d853dd2 100644 --- a/packages/typescript/ai-gemini/src/adapters/embedding.ts +++ b/packages/typescript/ai-gemini/src/adapters/embedding.ts @@ -52,6 +52,11 @@ export class GeminiEmbeddingAdapter< validateValue({ value, model }) validateTaskType({ taskType: modelOptions?.taskType, model }) + const { totalTokens } = await this.client.models.countTokens({ + model, + contents: value, + }) + const { embeddings } = await this.client.models.embedContent({ model, contents: value, @@ -64,7 +69,7 @@ export class GeminiEmbeddingAdapter< embedding: embeddings?.[0]?.values || [], id: generateId(this.name), model, - usage: undefined, + usage: totalTokens ? { totalTokens } : undefined, } } @@ -76,6 +81,11 @@ export class GeminiEmbeddingAdapter< validateValue({ value: values, model }) validateTaskType({ taskType: modelOptions?.taskType, model }) + const { totalTokens } = await this.client.models.countTokens({ + model, + contents: values, + }) + const { embeddings } = await this.client.models.embedContent({ model, contents: values, @@ -88,7 +98,7 @@ export class GeminiEmbeddingAdapter< embeddings: embeddings?.map((embedding) => embedding.values || []) || [], id: generateId(this.name), model, - usage: undefined, + usage: totalTokens ? { totalTokens } : undefined, } } } diff --git a/packages/typescript/ai/src/activities/embed/adapter.ts b/packages/typescript/ai/src/activities/embed/adapter.ts index 72610e95b..36e58fffa 100644 --- a/packages/typescript/ai/src/activities/embed/adapter.ts +++ b/packages/typescript/ai/src/activities/embed/adapter.ts @@ -52,7 +52,7 @@ export interface EmbeddingAdapter< } /** - * A EmbeddingAdapter with any/unknown type parameters. + * An EmbeddingAdapter with any/unknown type parameters. * Useful as a constraint in generic functions and interfaces. */ export type AnyEmbeddingAdapter = EmbeddingAdapter diff --git a/packages/typescript/ai/src/event-client.ts b/packages/typescript/ai/src/event-client.ts index f0f5bccad..0870a7008 100644 --- a/packages/typescript/ai/src/event-client.ts +++ b/packages/typescript/ai/src/event-client.ts @@ -34,7 +34,7 @@ export interface ImageUsage { } export interface EmbeddingsUsage { - totalTokens: number + totalTokens?: number } interface BaseEventContext { From f389f0195c6f1e4b4b5b431e7628ad7348bec8da Mon Sep 17 00:00:00 2001 From: Nikas Belogolov Date: Sat, 14 Feb 2026 18:15:54 +0200 Subject: [PATCH 13/14] fixed gemini embedding adapter --- .../ai-gemini/src/adapters/embedding.ts | 67 +++++++++++++++++-- packages/typescript/ai-gemini/src/index.ts | 10 +++ .../typescript/ai-gemini/src/model-meta.ts | 4 +- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/packages/typescript/ai-gemini/src/adapters/embedding.ts b/packages/typescript/ai-gemini/src/adapters/embedding.ts index e0d853dd2..362119884 100644 --- a/packages/typescript/ai-gemini/src/adapters/embedding.ts +++ b/packages/typescript/ai-gemini/src/adapters/embedding.ts @@ -1,5 +1,5 @@ import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters' -import { createGeminiClient, generateId } from '../utils' +import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from '../utils' import { validateTaskType, validateValue, @@ -11,7 +11,7 @@ import type { EmbedOptions, EmbedResult, } from '@tanstack/ai' -import type { GEMINI_EMBEDDING_MODELS } from '../model-meta' +import type { GeminiEmbeddingModels } from '../model-meta' import type { GeminiClientConfig } from '../utils' import type { GeminiEmbeddingModelProviderOptionsByName, @@ -23,10 +23,8 @@ import type { */ export interface GeminiEmbeddingConfig extends GeminiClientConfig {} -export type GeminiEmbeddingModel = (typeof GEMINI_EMBEDDING_MODELS)[number] - export class GeminiEmbeddingAdapter< - TModel extends GeminiEmbeddingModel, + TModel extends GeminiEmbeddingModels, > extends BaseEmbeddingAdapter { readonly kind = 'embedding' as const readonly name = 'gemini' as const @@ -102,3 +100,62 @@ export class GeminiEmbeddingAdapter< } } } + +/** + * Creates a Gemini embedding adapter with explicit API key. + * Type resolution happens here at the call site. + * + * @param model - The model name (e.g., 'embedding-001') + * @param apiKey - Your Google API key + * @param config - Optional additional configuration + * @returns Configured Gemini embedding adapter instance with resolved types + * + * @example + * ```typescript + * const adapter = createGeminiEmbedding('embedding-001', "your-api-key"); + * + * const result = await embed({ + * adapter, + * value: 'Hello, world!' + * }); + * ``` + */ +export function createGeminiEmbedding( + model: TModel, + apiKey: string, + config?: Omit, +): GeminiEmbeddingAdapter { + return new GeminiEmbeddingAdapter({ apiKey, ...config }, model) +} + +/** + * Creates a Gemini embedding adapter with automatic API key detection from environment variables. + * Type resolution happens here at the call site. + * + * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in: + * - `process.env` (Node.js) + * - `window.env` (Browser with injected env) + * + * @param model - The model name (e.g., 'embedding-001') + * @param config - Optional configuration (excluding apiKey which is auto-detected) + * @returns Configured Gemini embedding adapter instance with resolved types + * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment + * + * @example + * ```typescript + * // Automatically uses GOOGLE_API_KEY from environment + * const adapter = geminiEmbedding('embedding-001'); + * + * const result = await embed({ + * adapter, + * value: 'Hello, world!' + * }); + * ``` + */ +export function geminiEmbedding( + model: TModel, + config?: Omit, +): GeminiEmbeddingAdapter { + const apiKey = getGeminiApiKeyFromEnv() + return createGeminiEmbedding(model, apiKey, config) +} diff --git a/packages/typescript/ai-gemini/src/index.ts b/packages/typescript/ai-gemini/src/index.ts index c60ce0756..6a191ad71 100644 --- a/packages/typescript/ai-gemini/src/index.ts +++ b/packages/typescript/ai-gemini/src/index.ts @@ -51,12 +51,22 @@ export { type GeminiTTSProviderOptions, } from './adapters/tts' +// Embedding adapter +export { + GeminiEmbeddingAdapter, + createGeminiEmbedding, + geminiEmbedding, + type GeminiEmbeddingConfig, +} from './adapters/embedding' + // Re-export models from model-meta for convenience export { GEMINI_MODELS as GeminiTextModels } from './model-meta' +export { GEMINI_EMBEDDING_MODELS as GeminiEmbeddingModels } from './model-meta' export { GEMINI_IMAGE_MODELS as GeminiImageModels } from './model-meta' export { GEMINI_TTS_MODELS as GeminiTTSModels } from './model-meta' export { GEMINI_TTS_VOICES as GeminiTTSVoices } from './model-meta' export type { GeminiModels as GeminiTextModel } from './model-meta' +export type { GeminiEmbeddingModels as GeminiEmbeddingModel } from './model-meta' export type { GeminiImageModels as GeminiImageModel } from './model-meta' export type { GeminiTTSVoice } from './model-meta' diff --git a/packages/typescript/ai-gemini/src/model-meta.ts b/packages/typescript/ai-gemini/src/model-meta.ts index a9069326e..7da85403e 100644 --- a/packages/typescript/ai-gemini/src/model-meta.ts +++ b/packages/typescript/ai-gemini/src/model-meta.ts @@ -855,7 +855,9 @@ export const GEMINI_MODELS = [ export type GeminiModels = (typeof GEMINI_MODELS)[number] -export const GEMINI_EMBEDDING_MODELS = [GEMINI_EMBEDDING_001.name] as const +export const GEMINI_EMBEDDING_MODELS = [ + GEMINI_EMBEDDING_001.name, +] as const export type GeminiEmbeddingModels = (typeof GEMINI_EMBEDDING_MODELS)[number] From 4d1ad0a58b3f686641a5d0cf72ee0724ddb382f7 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:17:13 +0000 Subject: [PATCH 14/14] ci: apply automated fixes --- packages/typescript/ai-gemini/src/adapters/embedding.ts | 6 +++++- packages/typescript/ai-gemini/src/model-meta.ts | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/typescript/ai-gemini/src/adapters/embedding.ts b/packages/typescript/ai-gemini/src/adapters/embedding.ts index 362119884..851c15997 100644 --- a/packages/typescript/ai-gemini/src/adapters/embedding.ts +++ b/packages/typescript/ai-gemini/src/adapters/embedding.ts @@ -1,5 +1,9 @@ import { BaseEmbeddingAdapter } from '@tanstack/ai/adapters' -import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from '../utils' +import { + createGeminiClient, + generateId, + getGeminiApiKeyFromEnv, +} from '../utils' import { validateTaskType, validateValue, diff --git a/packages/typescript/ai-gemini/src/model-meta.ts b/packages/typescript/ai-gemini/src/model-meta.ts index 7da85403e..a9069326e 100644 --- a/packages/typescript/ai-gemini/src/model-meta.ts +++ b/packages/typescript/ai-gemini/src/model-meta.ts @@ -855,9 +855,7 @@ export const GEMINI_MODELS = [ export type GeminiModels = (typeof GEMINI_MODELS)[number] -export const GEMINI_EMBEDDING_MODELS = [ - GEMINI_EMBEDDING_001.name, -] as const +export const GEMINI_EMBEDDING_MODELS = [GEMINI_EMBEDDING_001.name] as const export type GeminiEmbeddingModels = (typeof GEMINI_EMBEDDING_MODELS)[number]