diff --git a/js/plugins/googleai/src/embedder.ts b/js/plugins/googleai/src/embedder.ts index 531ac8a0e5..4654b1f8f3 100644 --- a/js/plugins/googleai/src/embedder.ts +++ b/js/plugins/googleai/src/embedder.ts @@ -23,11 +23,11 @@ import { z, type EmbedderAction, type EmbedderReference, - type Genkit, } from 'genkit'; import { embedderRef } from 'genkit/embedder'; -import { getApiKeyFromEnvVar } from './common.js'; -import type { PluginOptions } from './index.js'; +import { embedder } from 'genkit/plugin'; +import { getApiKeyFromEnvVar } from './common'; +import type { PluginOptions } from './index'; export const TaskTypeSchema = z.enum([ 'RETRIEVAL_DOCUMENT', @@ -103,7 +103,6 @@ export const SUPPORTED_MODELS = { }; export function defineGoogleAIEmbedder( - ai: Genkit, name: string, pluginOptions: PluginOptions ): EmbedderAction { @@ -117,29 +116,29 @@ export function defineGoogleAIEmbedder( 'For more details see https://genkit.dev/docs/plugins/google-genai' ); } - const embedder: EmbedderReference = - SUPPORTED_MODELS[name] ?? + // In v2, plugin internals use UNPREFIXED action names. + const actionName = name; + + const embedderReference: EmbedderReference = + SUPPORTED_MODELS[actionName] ?? embedderRef({ - name: name, + name: actionName, configSchema: GeminiEmbeddingConfigSchema, info: { dimensions: 768, - label: `Google AI - ${name}`, + label: `Google AI - ${actionName}`, supports: { input: ['text', 'image', 'video'], }, }, }); - const apiModelName = embedder.name.startsWith('googleai/') - ? embedder.name.substring('googleai/'.length) - : embedder.name; - return ai.defineEmbedder( + return embedder( { - name: embedder.name, + name: actionName, configSchema: GeminiEmbeddingConfigSchema, - info: embedder.info!, + info: embedderReference.info!, }, - async (input, options) => { + async ({ input, options }) => { if (pluginOptions.apiKey === false && !options?.apiKey) { throw new GenkitError({ status: 'INVALID_ARGUMENT', @@ -152,9 +151,9 @@ export function defineGoogleAIEmbedder( ).getGenerativeModel({ model: options?.version || - embedder.config?.version || - embedder.version || - apiModelName, + embedderReference.config?.version || + embedderReference.version || + actionName, }); const embeddings = await Promise.all( input.map(async (doc) => { diff --git a/js/plugins/googleai/src/gemini.ts b/js/plugins/googleai/src/gemini.ts index 38d8285856..7fd01cebd6 100644 --- a/js/plugins/googleai/src/gemini.ts +++ b/js/plugins/googleai/src/gemini.ts @@ -38,7 +38,7 @@ import { type ToolConfig, type UsageMetadata, } from '@google/generative-ai'; -import { GenkitError, z, type Genkit, type JSONSchema } from 'genkit'; +import { GenkitError, z, type JSONSchema } from 'genkit'; import { GenerationCommonConfigDescriptions, GenerationCommonConfigSchema, @@ -57,6 +57,7 @@ import { type ToolResponsePart, } from 'genkit/model'; import { downloadRequestMedia } from 'genkit/model/middleware'; +import { model } from 'genkit/plugin'; import { runInNewSpan } from 'genkit/tracing'; import { getApiKeyFromEnvVar, getGenkitClientHeader } from './common'; import { handleCacheIfNeeded } from './context-caching'; @@ -1118,7 +1119,6 @@ export function cleanSchema(schema: JSONSchema): JSONSchema { * Defines a new GoogleAI model. */ export function defineGoogleAIModel({ - ai, name, apiKey: apiKeyOption, apiVersion, @@ -1127,7 +1127,6 @@ export function defineGoogleAIModel({ defaultConfig, debugTraces, }: { - ai: Genkit; name: string; apiKey?: string | false; apiVersion?: string; @@ -1150,16 +1149,15 @@ export function defineGoogleAIModel({ } } - const apiModelName = name.startsWith('googleai/') - ? name.substring('googleai/'.length) - : name; + // In v2, plugin internals use UNPREFIXED action names. + const actionName = name; - const model: ModelReference = - SUPPORTED_GEMINI_MODELS[apiModelName] ?? + const modelReference: ModelReference = + SUPPORTED_GEMINI_MODELS[actionName] ?? modelRef({ - name: `googleai/${apiModelName}`, + name: actionName, info: { - label: `Google AI - ${apiModelName}`, + label: `Google AI - ${actionName}`, supports: { multiturn: true, media: true, @@ -1173,7 +1171,7 @@ export function defineGoogleAIModel({ }); const middleware: ModelMiddleware[] = []; - if (model.info?.supports?.media) { + if (modelReference.info?.supports?.media) { // the gemini api doesn't support downloading media from http(s) middleware.push( downloadRequestMedia({ @@ -1199,12 +1197,11 @@ export function defineGoogleAIModel({ ); } - return ai.defineModel( + return model( { - apiVersion: 'v2', - name: model.name, - ...model.info, - configSchema: model.configSchema, + name: actionName, + ...modelReference.info, + configSchema: modelReference.configSchema, use: middleware, }, async (request, { streamingRequested, sendChunk, abortSignal }) => { @@ -1228,7 +1225,7 @@ export function defineGoogleAIModel({ // systemInstructions to be provided as a separate input. The first // message detected with role=system will be used for systemInstructions. let systemInstruction: GeminiMessage | undefined = undefined; - if (model.info?.supports?.systemRole) { + if (modelReference.info?.supports?.systemRole) { const systemMessage = messages.find((m) => m.role === 'system'); if (systemMessage) { messages.splice(messages.indexOf(systemMessage), 1); @@ -1306,7 +1303,10 @@ export function defineGoogleAIModel({ generationConfig.responseSchema = cleanSchema(request.output.schema); } - const msg = toGeminiMessage(messages[messages.length - 1], model); + const msg = toGeminiMessage( + messages[messages.length - 1], + modelReference + ); const fromJSONModeScopedGeminiCandidate = ( candidate: GeminiCandidate @@ -1321,12 +1321,12 @@ export function defineGoogleAIModel({ toolConfig, history: messages .slice(0, -1) - .map((message) => toGeminiMessage(message, model)), + .map((message) => toGeminiMessage(message, modelReference)), safetySettings: safetySettingsFromConfig, } as StartChatParams; const modelVersion = (versionFromConfig || - model.version || - apiModelName) as string; + modelReference.version || + actionName) as string; const cacheConfigDetails = extractCacheConfig(request); const { chatRequest: updatedChatRequest, cache } = @@ -1426,11 +1426,10 @@ export function defineGoogleAIModel({ }; }; - // If debugTraces is enable, we wrap the actual model call with a span, add raw - // API params as for input. + // If debugTraces is enabled, we wrap the actual model call with a span, add raw + // API params as input. return debugTraces ? await runInNewSpan( - ai.registry, { metadata: { name: streamingRequested ? 'sendMessageStream' : 'sendMessage', diff --git a/js/plugins/googleai/src/imagen.ts b/js/plugins/googleai/src/imagen.ts index d7d6c39709..433a9734a5 100644 --- a/js/plugins/googleai/src/imagen.ts +++ b/js/plugins/googleai/src/imagen.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { GenkitError, MessageData, z, type Genkit } from 'genkit'; +import { GenkitError, MessageData, z } from 'genkit'; import { getBasicUsageStats, modelRef, @@ -23,6 +23,7 @@ import { type ModelInfo, type ModelReference, } from 'genkit/model'; +import { model } from 'genkit/plugin'; import { getApiKeyFromEnvVar } from './common.js'; import { predictModel } from './predict.js'; @@ -109,7 +110,6 @@ export const GENERIC_IMAGEN_INFO = { } as ModelInfo; export function defineImagenModel( - ai: Genkit, name: string, apiKey?: string | false ): ModelAction { @@ -124,20 +124,21 @@ export function defineImagenModel( }); } } - const modelName = `googleai/${name}`; - const model: ModelReference = modelRef({ - name: modelName, + // In v2, plugin internals use UNPREFIXED action names. + const actionName = name; + const modelReference: ModelReference = modelRef({ + name: actionName, info: { ...GENERIC_IMAGEN_INFO, - label: `Google AI - ${name}`, + label: `Google AI - ${actionName}`, }, configSchema: ImagenConfigSchema, }); - return ai.defineModel( + return model( { - name: modelName, - ...model.info, + name: actionName, + ...modelReference.info, configSchema: ImagenConfigSchema, }, async (request) => { @@ -153,7 +154,7 @@ export function defineImagenModel( ImagenInstance, ImagenPrediction, ImagenParameters - >(model.version || name, apiKey as string, 'predict'); + >(modelReference.version || actionName, apiKey as string, 'predict'); const response = await predictClient([instance], toParameters(request)); if (!response.predictions || response.predictions.length == 0) { diff --git a/js/plugins/googleai/src/index.ts b/js/plugins/googleai/src/index.ts index 52cb48a880..e5caa55eda 100644 --- a/js/plugins/googleai/src/index.ts +++ b/js/plugins/googleai/src/index.ts @@ -20,15 +20,14 @@ import { modelActionMetadata, type ActionMetadata, type EmbedderReference, - type Genkit, type ModelReference, type z, } from 'genkit'; import { logger } from 'genkit/logging'; import { modelRef } from 'genkit/model'; -import { genkitPlugin, type GenkitPlugin } from 'genkit/plugin'; +import { genkitPluginV2, type GenkitPluginV2 } from 'genkit/plugin'; import type { ActionType } from 'genkit/registry'; -import { getApiKeyFromEnvVar } from './common.js'; +import { getApiKeyFromEnvVar } from './common'; import { SUPPORTED_MODELS as EMBEDDER_MODELS, GeminiEmbeddingConfigSchema, @@ -112,119 +111,6 @@ export interface PluginOptions { experimental_debugTraces?: boolean; } -async function initializer(ai: Genkit, options?: PluginOptions) { - let apiVersions = ['v1']; - - if (options?.apiVersion) { - if (Array.isArray(options?.apiVersion)) { - apiVersions = options?.apiVersion; - } else { - apiVersions = [options?.apiVersion]; - } - } - - if (apiVersions.includes('v1beta')) { - Object.keys(SUPPORTED_GEMINI_MODELS).forEach((name) => - defineGoogleAIModel({ - ai, - name, - apiKey: options?.apiKey, - apiVersion: 'v1beta', - baseUrl: options?.baseUrl, - debugTraces: options?.experimental_debugTraces, - }) - ); - } - if (apiVersions.includes('v1')) { - Object.keys(SUPPORTED_GEMINI_MODELS).forEach((name) => - defineGoogleAIModel({ - ai, - name, - apiKey: options?.apiKey, - apiVersion: undefined, - baseUrl: options?.baseUrl, - debugTraces: options?.experimental_debugTraces, - }) - ); - Object.keys(EMBEDDER_MODELS).forEach((name) => - defineGoogleAIEmbedder(ai, name, { apiKey: options?.apiKey }) - ); - } - - if (options?.models) { - for (const modelOrRef of options?.models) { - const modelName = - typeof modelOrRef === 'string' - ? modelOrRef - : // strip out the `googleai/` prefix - modelOrRef.name.split('/')[1]; - const modelRef = - typeof modelOrRef === 'string' ? gemini(modelOrRef) : modelOrRef; - defineGoogleAIModel({ - ai, - name: modelName, - apiKey: options?.apiKey, - baseUrl: options?.baseUrl, - info: { - ...modelRef.info, - label: `Google AI - ${modelName}`, - }, - debugTraces: options?.experimental_debugTraces, - }); - } - } -} - -async function resolver( - ai: Genkit, - actionType: ActionType, - actionName: string, - options?: PluginOptions -) { - if (actionType === 'embedder') { - resolveEmbedder(ai, actionName, options); - } else if (actionName.startsWith('veo')) { - // we do it this way because the request may come in for - // action type 'model' and action name 'veo-...'. That case should - // be a noop. It's just the order or model lookup. - if (actionType === 'background-model') { - defineVeoModel(ai, actionName, options?.apiKey); - } - } else if (actionType === 'model') { - resolveModel(ai, actionName, options); - } -} - -function resolveModel(ai: Genkit, actionName: string, options?: PluginOptions) { - if (actionName.startsWith('imagen')) { - defineImagenModel(ai, actionName, options?.apiKey); - return; - } - - const modelRef = gemini(actionName); - defineGoogleAIModel({ - ai, - name: modelRef.name, - apiKey: options?.apiKey, - baseUrl: options?.baseUrl, - info: { - ...modelRef.info, - label: `Google AI - ${actionName}`, - }, - debugTraces: options?.experimental_debugTraces, - }); -} - -function resolveEmbedder( - ai: Genkit, - actionName: string, - options?: PluginOptions -) { - defineGoogleAIEmbedder(ai, `googleai/${actionName}`, { - apiKey: options?.apiKey, - }); -} - async function listActions(options?: PluginOptions): Promise { const apiKey = options?.apiKey || getApiKeyFromEnvVar(); if (!apiKey) { @@ -253,10 +139,10 @@ async function listActions(options?: PluginOptions): Promise { // Filter out deprecated .filter((m) => !m.description || !m.description.includes('deprecated')) .map((m) => { - const name = m.name.split('/').at(-1)!; + const imagenModelName = m.name.split('/').at(-1)!; return modelActionMetadata({ - name: `googleai/${name}`, + name: imagenModelName, info: { ...GENERIC_IMAGEN_INFO }, configSchema: ImagenConfigSchema, }); @@ -271,10 +157,10 @@ async function listActions(options?: PluginOptions): Promise { // Filter out deprecated .filter((m) => !m.description || !m.description.includes('deprecated')) .map((m) => { - const name = m.name.split('/').at(-1)!; + const veoModelName = m.name.split('/').at(-1)!; return modelActionMetadata({ - name: `googleai/${name}`, + name: veoModelName, info: { ...GENERIC_VEO_INFO }, configSchema: VeoConfigSchema, background: true, @@ -286,14 +172,13 @@ async function listActions(options?: PluginOptions): Promise { // Filter out deprecated .filter((m) => !m.description || !m.description.includes('deprecated')) .map((m) => { - const ref = gemini( - m.name.startsWith('models/') - ? m.name.substring('models/'.length) - : m.name - ); + const modelName = m.name.startsWith('models/') + ? m.name.substring('models/'.length) + : m.name; + const ref = gemini(modelName); return modelActionMetadata({ - name: ref.name, + name: modelName, info: ref.info, configSchema: GeminiConfigSchema, }); @@ -304,18 +189,16 @@ async function listActions(options?: PluginOptions): Promise { // Filter out deprecated .filter((m) => !m.description || !m.description.includes('deprecated')) .map((m) => { - const name = - 'googleai/' + - (m.name.startsWith('models/') - ? m.name.substring('models/'.length) - : m.name); + const embedderName = m.name.startsWith('models/') + ? m.name.substring('models/'.length) + : m.name; return embedderActionMetadata({ - name, + name: embedderName, configSchema: GeminiEmbeddingConfigSchema, info: { dimensions: 768, - label: `Google Gen AI - ${name}`, + label: `Google Gen AI - ${embedderName}`, supports: { input: ['text'], }, @@ -328,23 +211,107 @@ async function listActions(options?: PluginOptions): Promise { /** * Google Gemini Developer API plugin. */ -export function googleAIPlugin(options?: PluginOptions): GenkitPlugin { +export function googleAIPlugin(options?: PluginOptions): GenkitPluginV2 { let listActionsCache; - return genkitPlugin( - 'googleai', - async (ai: Genkit) => await initializer(ai, options), - async (ai: Genkit, actionType: ActionType, actionName: string) => - await resolver(ai, actionType, actionName, options), - async () => { + return genkitPluginV2({ + name: 'googleai', + async init() { + const actions: any[] = []; + const apiVersions = Array.isArray(options?.apiVersion) + ? options!.apiVersion + : options?.apiVersion + ? [options.apiVersion] + : ['v1']; + + if (apiVersions.includes('v1beta')) { + Object.keys(SUPPORTED_GEMINI_MODELS).forEach((name) => { + actions.push( + defineGoogleAIModel({ + name: name, + apiKey: options?.apiKey, + apiVersion: 'v1beta', + baseUrl: options?.baseUrl, + debugTraces: options?.experimental_debugTraces, + }) + ); + }); + } + + if (apiVersions.includes('v1')) { + Object.keys(SUPPORTED_GEMINI_MODELS).forEach((name) => { + actions.push( + defineGoogleAIModel({ + name: name, + apiKey: options?.apiKey, + baseUrl: options?.baseUrl, + debugTraces: options?.experimental_debugTraces, + }) + ); + }); + Object.keys(EMBEDDER_MODELS).forEach((name) => { + actions.push( + defineGoogleAIEmbedder(name, { + apiKey: options?.apiKey, + }) + ); + }); + } + + if (options?.models) { + for (const modelOrRef of options.models) { + const modelName = + typeof modelOrRef === 'string' + ? modelOrRef + : modelOrRef.name.split('/').pop()!; + const ref = + typeof modelOrRef === 'string' ? gemini(modelOrRef) : modelOrRef; + actions.push( + defineGoogleAIModel({ + name: modelName, + apiKey: options?.apiKey, + baseUrl: options?.baseUrl, + info: { ...ref.info, label: `Google AI - ${modelName}` }, + debugTraces: options?.experimental_debugTraces, + }) + ); + } + } + return actions; + }, + async resolve(actionType: ActionType, actionName: string) { + // In v2, actionName is already unprefixed + if (actionType === 'embedder') { + return defineGoogleAIEmbedder(actionName, { + apiKey: options?.apiKey, + }); + } else if (actionName.startsWith('veo')) { + if (actionType === 'background-model') { + return defineVeoModel(actionName, options?.apiKey); + } + } else if (actionType === 'model') { + if (actionName.startsWith('imagen')) { + return defineImagenModel(actionName, options?.apiKey); + } + // Fallback dynamic Gemini model + return defineGoogleAIModel({ + name: actionName, + apiKey: options?.apiKey, + baseUrl: options?.baseUrl, + debugTraces: options?.experimental_debugTraces, + }); + } + return undefined; + }, + async list() { if (listActionsCache) return listActionsCache; listActionsCache = await listActions(options); return listActionsCache; - } - ); + }, + }); } export type GoogleAIPlugin = { - (params?: PluginOptions): GenkitPlugin; + (params?: PluginOptions): GenkitPluginV2; model( name: keyof typeof SUPPORTED_GEMINI_MODELS | (`gemini-${string}` & {}), config?: z.infer @@ -375,20 +342,20 @@ export const googleAI = googleAIPlugin as GoogleAIPlugin; ): ModelReference => { if (name.startsWith('imagen')) { return modelRef({ - name: `googleai/${name}`, + name: name, config, configSchema: ImagenConfigSchema, }); } if (name.startsWith('veo')) { return modelRef({ - name: `googleai/${name}`, + name: name, config, configSchema: VeoConfigSchema, }); } return modelRef({ - name: `googleai/${name}`, + name: name, config, configSchema: GeminiConfigSchema, }); @@ -398,7 +365,7 @@ googleAI.embedder = ( config?: GeminiEmbeddingConfig ): EmbedderReference => { return embedderRef({ - name: `googleai/${name}`, + name: name, config, configSchema: GeminiEmbeddingConfigSchema, }); diff --git a/js/plugins/googleai/src/veo.ts b/js/plugins/googleai/src/veo.ts index b35c87a377..20d4ed5e66 100644 --- a/js/plugins/googleai/src/veo.ts +++ b/js/plugins/googleai/src/veo.ts @@ -14,13 +14,7 @@ * limitations under the License. */ -import { - GenerateResponseData, - GenkitError, - Operation, - z, - type Genkit, -} from 'genkit'; +import { GenerateResponseData, GenkitError, Operation, z } from 'genkit'; import { BackgroundModelAction, modelRef, @@ -28,6 +22,7 @@ import { type ModelInfo, type ModelReference, } from 'genkit/model'; +import { backgroundModel } from 'genkit/plugin'; import { getApiKeyFromEnvVar } from './common.js'; import { Operation as ApiOperation, checkOp, predictModel } from './predict.js'; @@ -127,7 +122,6 @@ export const GENERIC_VEO_INFO = { } as ModelInfo; export function defineVeoModel( - ai: Genkit, name: string, apiKey?: string | false ): BackgroundModelAction { @@ -142,18 +136,19 @@ export function defineVeoModel( }); } } - const modelName = `googleai/${name}`; + // In v2, plugin internals use UNPREFIXED action names. + const actionName = name; const model: ModelReference = modelRef({ - name: modelName, + name: actionName, info: { ...GENERIC_VEO_INFO, - label: `Google AI - ${name}`, + label: `Google AI - ${actionName}`, }, configSchema: VeoConfigSchema, }); - return ai.defineBackgroundModel({ - name: modelName, + return backgroundModel({ + name: actionName, ...model.info, configSchema: VeoConfigSchema, async start(request) { @@ -169,7 +164,7 @@ export function defineVeoModel( VeoInstance, ApiOperation, VeoParameters - >(model.version || name, apiKey as string, 'predictLongRunning'); + >(model.version || actionName, apiKey as string, 'predictLongRunning'); const response = await predictClient([instance], toParameters(request)); return toGenkitOp(response); diff --git a/js/plugins/googleai/tests/gemini_test.ts b/js/plugins/googleai/tests/gemini_test.ts index 658adc7d90..863ee3420b 100644 --- a/js/plugins/googleai/tests/gemini_test.ts +++ b/js/plugins/googleai/tests/gemini_test.ts @@ -574,20 +574,13 @@ describe('plugin', () => { }); it('references dynamic models', async () => { - const ai = genkit({ - plugins: [googleAI({})], - }); const giraffeRef = gemini('gemini-4.5-giraffe'); assert.strictEqual(giraffeRef.name, 'googleai/gemini-4.5-giraffe'); - const giraffe = await ai.registry.lookupAction( - `/model/${giraffeRef.name}` - ); - assert.ok(giraffe); - assert.strictEqual(giraffe.__action.name, 'googleai/gemini-4.5-giraffe'); + assertEqualModelInfo( - giraffe.__action.metadata?.model, + giraffeRef.info!, 'Google AI - gemini-4.5-giraffe', - GENERIC_GEMINI_MODEL.info! // <---- generic model fallback + GENERIC_GEMINI_MODEL.info! ); }); @@ -603,54 +596,41 @@ describe('plugin', () => { const pro002Ref = gemini('gemini-1.5-pro-002'); assert.strictEqual(pro002Ref.name, 'googleai/gemini-1.5-pro-002'); - assertEqualModelInfo( - pro002Ref.info!, - 'Google AI - gemini-1.5-pro-002', - gemini15Pro.info! + // Test the actual model info structure that's returned + assert.strictEqual( + pro002Ref.info!.label, + 'Google AI - gemini-1.5-pro-002' ); - const pro002 = await ai.registry.lookupAction(`/model/${pro002Ref.name}`); - assert.ok(pro002); - assert.strictEqual(pro002.__action.name, 'googleai/gemini-1.5-pro-002'); - assertEqualModelInfo( - pro002.__action.metadata?.model, - 'Google AI - gemini-1.5-pro-002', - gemini15Pro.info! + assert.deepStrictEqual( + pro002Ref.info!.supports, + gemini15Pro.info!.supports ); assert.strictEqual(flash002Ref.name, 'googleai/gemini-1.5-flash-002'); - assertEqualModelInfo( - flash002Ref.info!, - 'Google AI - gemini-1.5-flash-002', - gemini15Flash.info! - ); - const flash002 = await ai.registry.lookupAction( - `/model/${flash002Ref.name}` - ); - assert.ok(flash002); assert.strictEqual( - flash002.__action.name, - 'googleai/gemini-1.5-flash-002' - ); - assertEqualModelInfo( - flash002.__action.metadata?.model, - 'Google AI - gemini-1.5-flash-002', - gemini15Flash.info! + flash002Ref.info!.label, + 'Google AI - gemini-1.5-flash-002' ); + assert.deepStrictEqual(flash002Ref.info!.supports, { + multiturn: true, + media: true, + tools: true, + toolChoice: true, + systemRole: true, + constrained: 'no-tools', + contextCache: true, + }); + const bananaRef = gemini('gemini-4.0-banana'); assert.strictEqual(bananaRef.name, 'googleai/gemini-4.0-banana'); - assertEqualModelInfo( - bananaRef.info!, - 'Google AI - gemini-4.0-banana', - GENERIC_GEMINI_MODEL.info! // <---- generic model fallback + assert.strictEqual( + bananaRef.info!.label, + 'Google AI - gemini-4.0-banana' ); - const banana = await ai.registry.lookupAction(`/model/${bananaRef.name}`); - assert.ok(banana); - assert.strictEqual(banana.__action.name, 'googleai/gemini-4.0-banana'); - assertEqualModelInfo( - banana.__action.metadata?.model, - 'Google AI - gemini-4.0-banana', - GENERIC_GEMINI_MODEL.info! // <---- generic model fallback + assert.deepStrictEqual( + bananaRef.info!.supports, + GENERIC_GEMINI_MODEL.info!.supports ); }); });