@genkit-ai/core
Version:
Genkit AI framework core libraries.
1 lines • 11 kB
Source Map (JSON)
{"version":3,"sources":["../src/dynamic-action-provider.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type * as z from 'zod';\nimport { Action, ActionMetadata, defineAction } from './action.js';\nimport { GenkitError } from './error.js';\nimport { ActionMetadataRecord, ActionType, Registry } from './registry.js';\n\ntype DapValue = {\n [K in ActionType]?: Action<z.ZodTypeAny, z.ZodTypeAny, z.ZodTypeAny>[];\n};\n\nclass SimpleCache {\n private value: DapValue | undefined;\n private expiresAt: number | undefined;\n private ttlMillis: number;\n private dap: DynamicActionProviderAction;\n private dapFn: DapFn;\n private fetchPromise: Promise<DapValue> | null = null;\n\n constructor(\n dap: DynamicActionProviderAction,\n config: DapConfig,\n dapFn: DapFn\n ) {\n this.dap = dap;\n this.dapFn = dapFn;\n this.ttlMillis = !config.cacheConfig?.ttlMillis\n ? 3 * 1000\n : config.cacheConfig?.ttlMillis;\n }\n\n /**\n * Gets or fetches the DAP data.\n * @param skipTrace Don't run the action. i.e. don't create a trace log.\n * @returns The DAP data\n */\n async getOrFetch(params?: { skipTrace?: boolean }): Promise<DapValue> {\n const isStale =\n !this.value ||\n !this.expiresAt ||\n this.ttlMillis < 0 ||\n Date.now() > this.expiresAt;\n if (!isStale) {\n return this.value!;\n }\n\n if (!this.fetchPromise) {\n this.fetchPromise = (async () => {\n try {\n // Get a new value\n this.value = await this.dapFn(); // this returns the actual actions\n this.expiresAt = Date.now() + this.ttlMillis;\n\n if (!params?.skipTrace) {\n // Also run the action\n // This action actually does nothing, with the important side\n // effect of logging its input and output (which are the same).\n // It does not change what we return, it just makes\n // the content of the DAP visible in the DevUI and logging trace.\n await this.dap.run(transformDapValue(this.value));\n }\n return this.value;\n } catch (error) {\n console.error('Error fetching Dynamic Action Provider value:', error);\n this.invalidate();\n throw error; // Rethrow to reject the fetchPromise\n } finally {\n // Allow new fetches after this one completes or fails.\n this.fetchPromise = null;\n }\n })();\n }\n return await this.fetchPromise;\n }\n\n invalidate() {\n this.value = undefined;\n }\n}\n\nexport interface DynamicRegistry {\n __cache: SimpleCache;\n invalidateCache(): void;\n getAction(\n actionType: string,\n actionName: string\n ): Promise<Action<z.ZodTypeAny, z.ZodTypeAny, z.ZodTypeAny> | undefined>;\n listActionMetadata(\n actionType: string,\n actionName: string\n ): Promise<ActionMetadata[]>;\n getActionMetadataRecord(dapPrefix: string): Promise<ActionMetadataRecord>;\n}\n\nexport type DynamicActionProviderAction = Action<\n z.ZodTypeAny,\n z.ZodTypeAny,\n z.ZodTypeAny\n> &\n DynamicRegistry & {\n __action: {\n metadata: {\n type: 'dynamic-action-provider';\n };\n };\n };\n\nexport function isDynamicActionProvider(\n obj: Action<z.ZodTypeAny, z.ZodTypeAny>\n): obj is DynamicActionProviderAction {\n return obj.__action?.metadata?.type == 'dynamic-action-provider';\n}\n\nexport interface DapConfig {\n name: string;\n description?: string;\n cacheConfig?: {\n // Negative = no caching\n // Zero or undefined = default (3000 milliseconds)\n // Positive number = how many milliseconds the cache is valid for\n ttlMillis: number | undefined;\n };\n metadata?: Record<string, any>;\n}\n\nexport type DapFn = () => Promise<DapValue>;\nexport type DapMetadata = {\n [K in ActionType]?: ActionMetadata[];\n};\n\nfunction transformDapValue(value: DapValue): DapMetadata {\n const metadata: DapMetadata = {};\n for (const key of Object.keys(value)) {\n metadata[key] = value[key].map((a) => {\n return a.__action;\n });\n }\n return metadata;\n}\n\nexport function defineDynamicActionProvider(\n registry: Registry,\n config: DapConfig | string,\n fn: DapFn\n): DynamicActionProviderAction {\n let cfg: DapConfig;\n if (typeof config == 'string') {\n cfg = { name: config };\n } else {\n cfg = { ...config };\n }\n const a = defineAction(\n registry,\n {\n ...cfg,\n actionType: 'dynamic-action-provider',\n metadata: { ...(cfg.metadata || {}), type: 'dynamic-action-provider' },\n },\n async (i, _options) => {\n // The actions are retrieved, saved in a cache, formatted nicely and\n // then passed in here so they can be automatically logged by the action\n // call. This action is for logging only. We cannot run the actual\n // 'getting the data from the DAP' here because the DAP data is required\n // to resolve tools/resources etc. And there can be a LOT of tools etc.\n // for a single generate. Which would log one DAP action per resolve,\n // and unnecessarily overwhelm the Dev UI with DAP actions that all have\n // the same information. So we only run this action (for the logging) when\n // we go get new data from the DAP (so we can see what it returned).\n return i;\n }\n );\n implementDap(a as DynamicActionProviderAction, cfg, fn);\n return a as DynamicActionProviderAction;\n}\n\nfunction implementDap(\n dap: DynamicActionProviderAction,\n config: DapConfig,\n dapFn: DapFn\n) {\n dap.__cache = new SimpleCache(dap, config, dapFn);\n dap.invalidateCache = () => {\n dap.__cache.invalidate();\n };\n\n dap.getAction = async (actionType: string, actionName: string) => {\n const result = await dap.__cache.getOrFetch();\n if (result[actionType]) {\n return result[actionType].find((t) => t.__action.name == actionName);\n }\n return undefined;\n };\n\n dap.listActionMetadata = async (actionType: string, actionName: string) => {\n const result = await dap.__cache.getOrFetch();\n if (!result[actionType]) {\n return [];\n }\n\n // Match everything in the actionType\n const metadata = result[actionType].map((a) => a.__action);\n if (actionName == '*') {\n return metadata;\n }\n\n // Prefix matching\n if (actionName.endsWith('*')) {\n const prefix = actionName.slice(0, -1);\n return metadata.filter((m) => m.name.startsWith(prefix));\n }\n\n // Single match or empty array\n return metadata.filter((m) => m.name == actionName);\n };\n\n // This is called by listResolvableActions which is used by the\n // reflection API.\n dap.getActionMetadataRecord = async (dapPrefix: string) => {\n const dapActions = {} as ActionMetadataRecord;\n // We want to skip traces so we don't get a new action trace\n // every time the DevUI requests the list of actions.\n // This is ok, because the DevUI will show the actions, so\n // not having them in the trace is fine.\n const result = await dap.__cache.getOrFetch({ skipTrace: true });\n for (const [actionType, actions] of Object.entries(result)) {\n const metadataList = actions.map((a) => a.__action);\n for (const metadata of metadataList) {\n if (!metadata.name) {\n throw new GenkitError({\n status: 'INVALID_ARGUMENT',\n message: `Invalid metadata when listing dynamic actions from ${dapPrefix} - name required`,\n });\n }\n const key = `${dapPrefix}:${actionType}/${metadata.name}`;\n dapActions[key] = metadata;\n }\n }\n return dapActions;\n };\n}\n"],"mappings":"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","names":[]}