@mastra/core
Version:
1 lines • 36.6 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":["getRegisteredProviders","parseModelString","isProviderRegistered"],"sources":["../../../src/agent-builder/ee/types.ts","../../../src/agent-builder/ee/errors.ts","../../../src/agent-builder/ee/normalize-candidate.ts","../../../src/agent-builder/ee/allowlist.ts","../../../src/agent-builder/ee/policy.ts","../../../src/agent-builder/ee/picker.ts"],"sourcesContent":["import type { Provider, ModelForProvider } from '../../llm/model';\nimport type { SerializedMemoryConfig } from '../../memory/types';\nimport type { StorageBrowserRef, StorageWorkspaceRef } from '../../storage/types';\n\n/**\n * Allowlist entry for a known provider (one of the generated `Provider` strings).\n * `modelId` narrows to the union of model ids declared for that provider.\n * Omitting `modelId` is the **provider wildcard** — every model under the provider is allowed.\n */\nexport type KnownProviderEntry = {\n [P in Provider]: { provider: P; modelId?: ModelForProvider<P> };\n}[Provider];\n\n/**\n * Allowlist entry for a custom / gateway provider that isn't in the generated registry.\n *\n * The `kind: 'custom'` discriminant is **required** — without it, an arbitrary string\n * provider would silently bypass the typo protection that `KnownProviderEntry` gives.\n * `modelId` is `string` (with the `& {}` escape hatch keeping autocomplete usable).\n */\nexport type CustomProviderEntry = {\n kind: 'custom';\n provider: string;\n // string & {} preserves IDE autocomplete on call sites that still pass known model\n // strings, while making the type accept arbitrary gateway model ids.\n modelId?: string & {};\n};\n\n/**\n * Allowlist entry. Either a typed known-provider entry, or a tagged custom-provider entry.\n */\nexport type ProviderModelEntry = KnownProviderEntry | CustomProviderEntry;\n\n/**\n * Default model entry. Same shape as {@link ProviderModelEntry} but `modelId` is required —\n * a default needs to point at a specific model, not a whole provider.\n */\nexport type DefaultModelEntry =\n | { [P in Provider]: { provider: P; modelId: ModelForProvider<P> } }[Provider]\n | { kind: 'custom'; provider: string; modelId: string & {} };\n\n/**\n * Admin-controlled model policy for the Agent Builder.\n * Owned here; re-exported from `@mastra/core/agent-builder/ee` and the SDK.\n *\n * Invariants (enforced in Phase 4):\n * - `active: false` → all other fields ignored.\n * - `active: true` + `pickerVisible: false` (locked) → `default` MUST be set.\n * - When `allowed` is non-empty, `default` (if set) MUST satisfy `isModelAllowed(allowed, default)`.\n */\nexport interface BuilderModelPolicy {\n active: boolean;\n pickerVisible?: boolean;\n allowed?: ProviderModelEntry[];\n default?: DefaultModelEntry;\n}\n\n/**\n * Default values for agents created via the builder.\n * Used as fallbacks when the user doesn't specify a value.\n */\nexport interface BuilderAgentDefaults extends Record<string, unknown> {\n /** Default memory configuration for new agents */\n memory?: SerializedMemoryConfig;\n /** Default workspace reference for new agents */\n workspace?: StorageWorkspaceRef;\n /** Default browser configuration for new agents */\n browser?: StorageBrowserRef;\n /**\n * Admin-controlled model allowlist + default applied to new agents.\n * `allowed` empty/undefined ⇒ no restriction. `default` (if set) is preselected on create.\n * See parent RFC for full semantics (wildcards, custom gateways, deny-by-default).\n */\n models?: {\n allowed?: ProviderModelEntry[];\n default?: DefaultModelEntry;\n };\n /**\n * Admin-controlled allowlist of tool IDs visible in the builder tools picker.\n *\n * Semantics:\n * - omitted (`undefined`) ⇒ unrestricted; show all registered tools.\n * - `allowed: []` ⇒ empty picker (explicit lockdown).\n * - `allowed: [...ids]` ⇒ show only the listed tool IDs.\n *\n * IDs are `tool.id` (preferred — what you see in the UI, URLs and traces)\n * but the registration key (the property name under `Mastra({ tools: {…} })`)\n * is also accepted as an alias. Matched against the registered tools at\n * request time. Unknown IDs are dropped and surfaced as warnings.\n */\n tools?: {\n allowed?: string[];\n };\n /**\n * Admin-controlled allowlist of agent IDs visible in the builder sub-agents picker.\n *\n * Semantics:\n * - omitted (`undefined`) ⇒ unrestricted; show all registered agents.\n * - `allowed: []` ⇒ empty picker (explicit lockdown).\n * - `allowed: [...ids]` ⇒ show only the listed agent IDs.\n *\n * IDs are `Agent.id` (preferred — what you see in the UI, URLs and traces)\n * but the registration key (the property name under `Mastra({ agents: {…} })`)\n * is also accepted as an alias. Matched against the registered agents at\n * request time. Unknown IDs are dropped and surfaced as warnings.\n */\n agents?: {\n allowed?: string[];\n };\n /**\n * Admin-controlled allowlist of workflow IDs visible in the builder workflows picker.\n *\n * Semantics:\n * - omitted (`undefined`) ⇒ unrestricted; show all registered workflows.\n * - `allowed: []` ⇒ empty picker (explicit lockdown).\n * - `allowed: [...ids]` ⇒ show only the listed workflow IDs.\n *\n * IDs are `workflow.id` (preferred — what you see in the UI, URLs and traces)\n * but the registration key (the property name under `Mastra({ workflows: {…} })`)\n * is also accepted as an alias. Matched against the registered workflows at\n * request time. Unknown IDs are dropped and surfaced as warnings.\n */\n workflows?: {\n allowed?: string[];\n };\n}\n\n/**\n * Feature toggles for the agent editor surface.\n * Each key controls visibility of that section in the builder UI.\n *\n * **Semantic: omitted = true (allowlist model — features default ON)**\n * - omitted or `true` — feature is visible to users\n * - `false` — feature is hidden\n *\n * Admins opt OUT of features by setting them to `false`. The raw\n * `AgentFeatures` shape carries `undefined`/`true`/`false`; the resolved\n * shape produced by {@link resolveAgentFeatures} is fully populated and is\n * what consumers (server handlers, UI hooks) actually read via\n * `IAgentBuilder.getFeatures()`.\n *\n * Consumer code should use strict equality on the resolved shape:\n * ```ts\n * const showTools = builder.getFeatures()?.agent?.tools === true;\n * ```\n *\n * Special case — `browser`: defaults to `true` ONLY when a valid\n * `configuration.agent.browser` (with `config.provider`) is provided.\n * Without a provider, the toggle has no backend, so the resolved value is\n * `false` regardless of the omitted-default. An explicit `browser: true`\n * with missing/invalid config is downgraded to `false` and surfaced as a\n * warning by `EditorAgentBuilder` (admin error feedback).\n */\nexport interface AgentFeatures {\n tools?: boolean;\n agents?: boolean;\n workflows?: boolean;\n scorers?: boolean;\n skills?: boolean;\n memory?: boolean;\n variables?: boolean;\n /** Favorite agents and skills with per-user state and aggregate counts. */\n favorites?: boolean;\n avatarUpload?: boolean;\n /**\n * Allow end-users to enable browser access for their agents.\n * Defaults to `true` only when a valid browser provider is configured;\n * otherwise resolves to `false`. See doc above for the full rule.\n */\n browser?: boolean;\n /**\n * Whether the model picker is visible to end-users in the Agent Builder.\n * Omitted ⇒ picker visible (default-on). `false` ⇒ picker hidden (locked\n * mode); admin's `models.default` is applied.\n * When visible, choices are filtered by `models.allowed` if set.\n */\n model?: boolean;\n}\n\n/**\n * Default-on values for {@link AgentFeatures}. `browser` is defaulted\n * dynamically by {@link resolveAgentFeatures} based on configuration; it is\n * intentionally absent here so the shape mirrors what `resolveAgentFeatures`\n * unconditionally fills in.\n */\nexport const BUILDER_FEATURE_DEFAULTS: Required<Omit<AgentFeatures, 'browser'>> = {\n tools: true,\n agents: true,\n workflows: true,\n scorers: true,\n skills: true,\n memory: true,\n variables: true,\n favorites: true,\n avatarUpload: true,\n model: true,\n};\n\n/**\n * Context required to resolve {@link AgentFeatures} defaults. Lives separately\n * from the raw config so this helper stays pure and synchronous.\n */\nexport interface ResolveAgentFeaturesContext {\n /**\n * Whether `configuration.agent.browser` declares a valid provider.\n * Drives the default-on/off decision for `features.agent.browser`.\n */\n hasBrowserConfig: boolean;\n}\n\n/**\n * Pure normalization of the raw {@link AgentFeatures} into a fully-populated\n * shape with default-on semantics applied.\n *\n * Rules:\n * - Explicit `false` always wins (admin opt-out).\n * - Explicit `true` wins for non-`browser` keys.\n * - Omitted keys resolve to `true` (except `browser`, see below).\n * - `browser`:\n * - explicit `false` ⇒ `false`.\n * - explicit `true` + `hasBrowserConfig: false` ⇒ `false` (caller is\n * responsible for emitting a warning; this helper does not throw).\n * - explicit `true` + `hasBrowserConfig: true` ⇒ `true`.\n * - omitted ⇒ `hasBrowserConfig` (default-on only when prerequisite met).\n */\nexport function resolveAgentFeatures(\n raw: AgentFeatures | undefined,\n ctx: ResolveAgentFeaturesContext,\n): Required<AgentFeatures> {\n const pick = <K extends keyof typeof BUILDER_FEATURE_DEFAULTS>(key: K): boolean => {\n const explicit = raw?.[key];\n return explicit === undefined ? BUILDER_FEATURE_DEFAULTS[key] : explicit;\n };\n\n const resolveBrowser = (): boolean => {\n const explicit = raw?.browser;\n if (explicit === false) return false;\n if (explicit === true) return ctx.hasBrowserConfig;\n return ctx.hasBrowserConfig;\n };\n\n return {\n tools: pick('tools'),\n agents: pick('agents'),\n workflows: pick('workflows'),\n scorers: pick('scorers'),\n skills: pick('skills'),\n memory: pick('memory'),\n variables: pick('variables'),\n favorites: pick('favorites'),\n avatarUpload: pick('avatarUpload'),\n model: pick('model'),\n browser: resolveBrowser(),\n };\n}\n\n/**\n * Configuration for the Agent Builder EE feature.\n * Passed to `MastraEditorConfig.builder`.\n *\n * All fields are optional. JSON-safe (no functions, no class instances).\n */\nexport interface AgentBuilderOptions {\n /**\n * Whether the builder is enabled. Default: true.\n * Set to false to disable the builder without removing the config.\n */\n enabled?: boolean;\n\n /**\n * Deployment-level feature toggles.\n * Key presence means \"this surface exists for this deployment.\"\n */\n features?: {\n agent?: AgentFeatures;\n };\n\n /**\n * Admin-pinned values applied to every artifact the builder produces.\n * Not overridable by end-users.\n *\n * Known fields are typed explicitly; additional fields allowed for extensibility.\n */\n configuration?: {\n agent?: BuilderAgentDefaults;\n };\n\n /**\n * Skill registries the Agent Builder is allowed to browse and install from.\n *\n * Each registry is opt-in. When no registries are enabled, the Builder hides\n * registry browse UI entirely. When at least one is enabled, the Builder\n * shows a \"Browse registries\" entry alongside \"Create skill\".\n */\n registries?: {\n /**\n * The public skills.sh registry (https://skills.sh).\n * Off by default — admins must explicitly opt in.\n */\n skillsSh?: {\n /** When true, the Builder may browse and install from skills.sh. */\n enabled: boolean;\n };\n };\n}\n\n/**\n * Public interface for the Agent Builder.\n * Implemented by EditorAgentBuilder in @mastra/editor/ee.\n */\nexport interface IAgentBuilder {\n readonly enabled: boolean;\n getFeatures(): AgentBuilderOptions['features'];\n getConfiguration(): AgentBuilderOptions['configuration'];\n /**\n * The opt-in skill registries this Builder is allowed to browse and install\n * from. Returns `undefined` when the admin has not configured any registries.\n */\n getRegistries?(): AgentBuilderOptions['registries'];\n /**\n * Optional warnings produced during construction-time validation\n * (e.g. allowlist entries with unknown providers that lack `kind: 'custom'`).\n * Surfaced via `GET /editor/builder/settings.modelPolicyWarnings` for admin UI display.\n */\n getModelPolicyWarnings?(): string[];\n}\n","import type { ModelCandidate } from './normalize-candidate';\nimport type { ProviderModelEntry } from './types';\n\nexport const MODEL_NOT_ALLOWED_CODE = 'MODEL_NOT_ALLOWED' as const;\n\n/**\n * Thrown by `enforceModelAllowlist` call sites when a write attempts to persist\n * a model that the active builder allowlist does not permit.\n *\n * Lives in `@mastra/core` so editor and server layers can both throw it\n * without crossing package boundaries. The server adapter\n * (`packages/server/src/server/handlers/error.ts`) maps this to HTTP 422 with\n * a structured JSON body of the same shape.\n */\nexport class ModelNotAllowedError extends Error {\n readonly code = MODEL_NOT_ALLOWED_CODE;\n readonly allowed: ProviderModelEntry[] | undefined;\n readonly attempted: ModelCandidate;\n readonly offendingLabel: string;\n\n constructor(args: {\n allowed: ProviderModelEntry[] | undefined;\n attempted: ModelCandidate;\n offendingLabel: string;\n message?: string;\n }) {\n const message =\n args.message ??\n `Model \"${args.attempted.provider}/${args.attempted.modelId}\" (${args.offendingLabel}) is not in the configured allowlist.`;\n super(message);\n this.name = 'ModelNotAllowedError';\n this.allowed = args.allowed;\n this.attempted = args.attempted;\n this.offendingLabel = args.offendingLabel;\n }\n}\n\nexport function isModelNotAllowedError(error: unknown): error is ModelNotAllowedError {\n return error instanceof Error && (error as { code?: unknown }).code === MODEL_NOT_ALLOWED_CODE;\n}\n","import { getRegisteredProviders, parseModelString } from '../../llm/model/provider-registry.js';\nimport type { MastraModelConfig } from '../../llm/model/shared.types.js';\nimport type { StorageConditionalField, StorageConditionalVariant, StorageModelConfig } from '../../storage/types.js';\n\nexport type ModelCandidateOrigin =\n | 'static'\n | 'conditional-variant'\n | 'conditional-default'\n | 'runtime'\n | 'list'\n | 'sdk-instance'\n | 'openai-compatible';\n\n/**\n * A single normalized provider/model candidate extracted from one of the many\n * shapes a model can be expressed in across the codebase.\n *\n * `origin` records which dispatch branch produced the candidate, mainly for\n * error messages on conditional variants.\n *\n * `label` is a short human-friendly description (variant index / SDK provider id\n * / etc.) used by `enforceModelAllowlist` when reporting the offending entry.\n */\nexport interface ModelCandidate {\n provider: string;\n modelId: string;\n origin: ModelCandidateOrigin;\n label?: string;\n}\n\n/**\n * Anything we accept as input to {@link toModelCandidates}. Kept open so call\n * sites can pass arbitrary stored or runtime model values without manual coercion.\n */\nexport type ModelCandidateInput =\n | string\n | MastraModelConfig\n | StorageModelConfig\n | StorageConditionalField<StorageModelConfig>\n | StorageConditionalVariant<string>[]\n | { provider?: unknown; modelId?: unknown; name?: unknown; id?: unknown; providerId?: unknown }\n | ((...args: unknown[]) => unknown)\n | null\n | undefined;\n\n/**\n * Gateway-aware split of a runtime model string. `parseModelString` only splits\n * on the first slash, which fails for gateway provider IDs that themselves\n * contain a slash (e.g. `acme/custom/foo-1`). We try the longest registered\n * provider prefix first and fall back to the first-slash split when no match\n * is found in the registry.\n */\nfunction splitRuntimeModelString(input: string): { provider: string; modelId: string } | undefined {\n const providers = getRegisteredProviders().sort((a, b) => b.length - a.length);\n for (const providerId of providers) {\n const prefix = `${providerId}/`;\n if (input.startsWith(prefix)) {\n const modelId = input.slice(prefix.length);\n if (modelId.length > 0) return { provider: providerId, modelId };\n }\n }\n const parsed = parseModelString(input);\n if (parsed.provider && parsed.modelId) {\n return { provider: parsed.provider, modelId: parsed.modelId };\n }\n return undefined;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction fromObject(value: Record<string, unknown>, origin: ModelCandidateOrigin, label?: string): ModelCandidate[] {\n // SDK instance: AI SDK language models expose `provider` + `modelId`.\n const providerField = value.provider;\n const modelIdField = value.modelId;\n const nameField = value.name;\n const idField = value.id;\n const providerIdField = value.providerId;\n\n // OpenAICompatibleConfig `{ id: 'provider/model' }` — must be checked before\n // `{ provider, modelId }` so AI SDK instances (which also have `provider`)\n // don't get pre-empted by a stale `id` lookup.\n if (typeof idField === 'string' && idField.includes('/') && providerField === undefined) {\n const split = splitRuntimeModelString(idField);\n if (split) {\n return [{ ...split, origin: 'openai-compatible', label: label ?? idField }];\n }\n }\n\n // OpenAICompatibleConfig `{ providerId, modelId }`\n if (typeof providerIdField === 'string' && typeof modelIdField === 'string') {\n return [\n {\n provider: providerIdField,\n modelId: modelIdField,\n origin: 'openai-compatible',\n label: label ?? `${providerIdField}/${modelIdField}`,\n },\n ];\n }\n\n // AI SDK language model instance: `{ provider, modelId, ... doGenerate }`\n if (typeof providerField === 'string' && typeof modelIdField === 'string') {\n const isSdkInstance = typeof (value as { doGenerate?: unknown }).doGenerate === 'function';\n return [\n {\n provider: providerField,\n modelId: modelIdField,\n origin: isSdkInstance ? 'sdk-instance' : origin,\n label: label ?? `${providerField}/${modelIdField}`,\n },\n ];\n }\n\n // Stored static `{ provider, name }`\n if (typeof providerField === 'string' && typeof nameField === 'string') {\n return [\n {\n provider: providerField,\n modelId: nameField,\n origin,\n label: label ?? `${providerField}/${nameField}`,\n },\n ];\n }\n\n return [];\n}\n\n/**\n * Convert any supported model expression into a flat list of `{ provider, modelId }`\n * candidates. Empty array means \"could not statically determine\" — callers\n * should treat that as unenforced at this level (runtime defense in Phase 7\n * picks it up).\n *\n * Dispatch order:\n * 1. `null` / `undefined` / `function` → `[]` (dynamic, defer to runtime)\n * 2. `string` → gateway-aware split\n * 3. Conditional variants array → walk each variant\n * 4. Object → openai-compatible / SDK instance / stored static, see {@link fromObject}\n */\nexport function toModelCandidates(input: ModelCandidateInput): ModelCandidate[] {\n if (input === null || input === undefined) return [];\n\n if (typeof input === 'function') return [];\n\n if (typeof input === 'string') {\n const split = splitRuntimeModelString(input);\n if (!split) return [];\n return [{ ...split, origin: 'runtime', label: input }];\n }\n\n if (Array.isArray(input)) {\n const candidates: ModelCandidate[] = [];\n input.forEach((variant, index) => {\n if (!isPlainObject(variant)) return;\n const value = (variant as { value?: unknown }).value ?? variant;\n const hasRules = isPlainObject(variant) && 'rules' in variant && (variant as { rules?: unknown }).rules != null;\n const origin: ModelCandidateOrigin = hasRules ? 'conditional-variant' : 'conditional-default';\n const label = hasRules ? `variant[${index}]` : `variant[${index}] (default)`;\n if (typeof value === 'string') {\n const split = splitRuntimeModelString(value);\n if (split) {\n candidates.push({ ...split, origin, label });\n }\n return;\n }\n if (isPlainObject(value)) {\n candidates.push(...fromObject(value, origin, label));\n }\n });\n return candidates;\n }\n\n if (isPlainObject(input)) {\n return fromObject(input, 'static');\n }\n\n return [];\n}\n","import { isProviderRegistered } from '../../llm/model/provider-registry.js';\nimport { ModelNotAllowedError } from './errors.js';\nimport { toModelCandidates } from './normalize-candidate.js';\nimport type { ModelCandidate, ModelCandidateInput } from './normalize-candidate.js';\nimport type { ProviderModelEntry } from './types.js';\n\n/**\n * Candidate model to check against the allowlist.\n * Caller is responsible for normalizing the source shape via {@link toModelCandidates}\n * (in `./normalize-candidate.ts`) before reaching this matcher.\n */\nexport interface ModelMatchCandidate {\n provider: string;\n modelId: string;\n}\n\n/**\n * Single-entry match: provider equality (case-sensitive). When the entry omits\n * `modelId` it matches every model under that provider (provider wildcard).\n *\n * Custom (`kind: 'custom'`) entries match by exact provider string. Known-provider\n * entries match by exact provider string too — the typed surface is purely a\n * compile-time guard.\n */\nexport function matchesProvider(entry: ProviderModelEntry, candidate: ModelMatchCandidate): boolean {\n if (entry.provider !== candidate.provider) return false;\n if (!entry.modelId) return true; // wildcard\n return entry.modelId === candidate.modelId;\n}\n\n/**\n * Returns `true` if the candidate is allowed under the given allowlist.\n *\n * Rules:\n * - `undefined` allowlist ⇒ unrestricted (always `true`).\n * - `[]` empty allowlist ⇒ unrestricted (always `true`).\n * - Non-empty allowlist where **every** entry's provider is unknown to the\n * runtime registry AND not tagged `kind: 'custom'` ⇒ deny everything. This\n * prevents typos (e.g. `openaii`) from acting as an unintended deny-all that\n * silently allows anything else; it is the documented \"deny vs ignore\" rule.\n */\nexport function isModelAllowed(allowed: ProviderModelEntry[] | undefined, candidate: ModelMatchCandidate): boolean {\n if (allowed === undefined) return true;\n if (allowed.length === 0) return true;\n\n const activeEntries = allowed.filter(entry => {\n if ('kind' in entry && entry.kind === 'custom') return true;\n return isProviderRegistered(entry.provider);\n });\n\n if (activeEntries.length === 0) return false;\n\n return activeEntries.some(entry => matchesProvider(entry, candidate));\n}\n\n/**\n * Result of an allowlist enforcement check.\n *\n * `attempted` is the candidate (or list of candidates) that triggered the\n * decision; `offendingLabel` (when set) names the specific failing entry so\n * callers can surface it in error messages — particularly useful for\n * conditional model variants.\n */\nexport type EnforceModelAllowlistResult =\n | { ok: true }\n | {\n ok: false;\n attempted: ModelCandidate;\n offendingLabel: string;\n };\n\n/**\n * Apply an allowlist to any supported model expression. Normalizes via\n * `toModelCandidates`, then runs `isModelAllowed` per candidate. Returns the\n * **first** failing candidate so error messages can pinpoint which variant of\n * a conditional / fallback list violated the policy.\n *\n * If `toModelCandidates` returns no candidates (dynamic function, unparsable\n * shape) this passes — runtime defense (Phase 7) handles those cases.\n */\nexport function enforceModelAllowlist(\n allowed: ProviderModelEntry[] | undefined,\n input: ModelCandidateInput,\n): EnforceModelAllowlistResult {\n const candidates = toModelCandidates(input);\n for (const candidate of candidates) {\n if (!isModelAllowed(allowed, candidate)) {\n return {\n ok: false,\n attempted: candidate,\n offendingLabel: candidate.label ?? candidate.origin,\n };\n }\n }\n return { ok: true };\n}\n\n/**\n * Convenience wrapper around `enforceModelAllowlist` that throws\n * `ModelNotAllowedError` on rejection. Use at write call sites so the server\n * adapter can translate into HTTP 422 + structured body.\n */\nexport function assertModelAllowed(allowed: ProviderModelEntry[] | undefined, input: ModelCandidateInput): void {\n const result = enforceModelAllowlist(allowed, input);\n if (result.ok) return;\n throw new ModelNotAllowedError({\n allowed,\n attempted: result.attempted,\n offendingLabel: result.offendingLabel,\n });\n}\n","import type { IAgentBuilder, BuilderModelPolicy, DefaultModelEntry, ProviderModelEntry } from './types';\n\n/**\n * Inputs for the shared {@link isBuilderModelPolicyActive} predicate.\n *\n * Lives separately from {@link BuilderModelPolicy} because we need to ask the\n * \"is the model slice active?\" question at config-validation time, *before*\n * a `BuilderModelPolicy` has been built.\n */\nexport interface BuilderModelPolicyInputs {\n /** `AgentBuilderOptions.enabled` (defaulted: missing = `true`). */\n enabled: boolean;\n /** `features.agent.model` — `true` means picker visible. */\n pickerVisible: boolean;\n /** `configuration.agent.models.allowed`. */\n allowed?: ProviderModelEntry[];\n /** `configuration.agent.models.default`. */\n default?: DefaultModelEntry;\n}\n\n/**\n * Single source of truth for whether the admin has actually configured a model\n * policy. Reused by:\n * - {@link builderToModelPolicy} (UI / runtime derivation)\n * - `EditorAgentBuilder` config validation (Phase 4)\n * - Server-side enforcement gate (Phase 6)\n *\n * \"Active\" means the admin opted into the model slice in some way:\n * - the picker is visible (open-mode), OR\n * - an allowlist was set, OR\n * - a default model was set.\n *\n * If the builder is `enabled: false`, the slice is never active.\n */\nexport function isBuilderModelPolicyActive(inputs: BuilderModelPolicyInputs): boolean {\n if (!inputs.enabled) return false;\n if (inputs.pickerVisible) return true;\n if (inputs.allowed !== undefined) return true;\n if (inputs.default !== undefined) return true;\n return false;\n}\n\n/**\n * Pure derivation of the {@link BuilderModelPolicy} from an `IAgentBuilder`.\n * No `Mastra` / `IEditor` dependency — server and editor wrappers feed it\n * a builder instance through their own resolution paths.\n *\n * Returns `{ active: false }` when:\n * - the builder is missing,\n * - the builder is disabled, or\n * - none of the model-slice signals are present.\n *\n * In every active case, `allowed` and `default` are passed through verbatim\n * so locked-mode UI still has the data it needs to render the chosen model.\n */\nexport function builderToModelPolicy(builder: IAgentBuilder | undefined): BuilderModelPolicy {\n if (!builder || !builder.enabled) {\n return { active: false };\n }\n\n const features = builder.getFeatures();\n const configuration = builder.getConfiguration();\n const pickerVisible = features?.agent?.model === true;\n const models = configuration?.agent?.models;\n const allowed = models?.allowed;\n const defaultModel = models?.default;\n\n const active = isBuilderModelPolicyActive({\n enabled: builder.enabled,\n pickerVisible,\n allowed,\n default: defaultModel,\n });\n\n if (!active) {\n return { active: false };\n }\n\n return {\n active: true,\n pickerVisible,\n ...(allowed !== undefined ? { allowed } : {}),\n ...(defaultModel !== undefined ? { default: defaultModel } : {}),\n };\n}\n","import type { BuilderAgentDefaults } from './types';\n\n/**\n * Resolved picker visibility for the Agent Builder configure panel.\n *\n * One field per kind (tools / agents / workflows).\n * - `null` ⇒ unrestricted (show all registered entries).\n * - `string[]` ⇒ explicit allowlist (may be empty to show none).\n */\nexport interface ResolvedPickerVisibility {\n visibleTools: string[] | null;\n visibleAgents: string[] | null;\n visibleWorkflows: string[] | null;\n /** Non-fatal warnings (e.g. unknown IDs in any allowlist). */\n warnings: string[];\n}\n\nexport interface ResolvePickerVisibilityInputs {\n /** The `agent` slice of `AgentBuilderOptions['configuration']`. */\n config: BuilderAgentDefaults | undefined;\n /** All tool IDs currently registered with the Mastra instance. */\n registeredToolIds: readonly string[];\n /** All agent IDs currently registered with the Mastra instance. */\n registeredAgentIds: readonly string[];\n /** All workflow IDs currently registered with the Mastra instance. */\n registeredWorkflowIds: readonly string[];\n}\n\ninterface ResolveOneResult {\n visible: string[] | null;\n warnings: string[];\n}\n\nfunction resolveOne(\n allowlist: string[] | undefined,\n registered: readonly string[],\n kindLabel: string,\n configPath: string,\n): ResolveOneResult {\n if (allowlist === undefined) {\n return { visible: null, warnings: [] };\n }\n\n const known = new Set(registered);\n const seen = new Set<string>();\n const visible: string[] = [];\n const warnings: string[] = [];\n\n for (const id of allowlist) {\n if (seen.has(id)) continue;\n seen.add(id);\n if (known.has(id)) {\n visible.push(id);\n } else {\n warnings.push(\n `${configPath} references unknown ${kindLabel} \"${id}\" — no ${kindLabel} with this ID is registered. It will be hidden from the builder picker.`,\n );\n }\n }\n\n return { visible, warnings };\n}\n\n/**\n * Pure derivation of {@link ResolvedPickerVisibility} from admin config and\n * the registered tool/agent/workflow sets.\n *\n * Per kind:\n * - allowlist undefined ⇒ `null` (unrestricted), no warnings.\n * - allowlist provided ⇒ filter to known IDs; emit one warning per unknown ID.\n *\n * Stable order: each visible list preserves admin-provided order with unknowns\n * dropped. Duplicates are de-duplicated.\n */\nexport function resolvePickerVisibility({\n config,\n registeredToolIds,\n registeredAgentIds,\n registeredWorkflowIds,\n}: ResolvePickerVisibilityInputs): ResolvedPickerVisibility {\n const tools = resolveOne(config?.tools?.allowed, registeredToolIds, 'tool', 'configuration.agent.tools.allowed');\n const agents = resolveOne(config?.agents?.allowed, registeredAgentIds, 'agent', 'configuration.agent.agents.allowed');\n const workflows = resolveOne(\n config?.workflows?.allowed,\n registeredWorkflowIds,\n 'workflow',\n 'configuration.agent.workflows.allowed',\n );\n\n return {\n visibleTools: tools.visible,\n visibleAgents: agents.visible,\n visibleWorkflows: workflows.visible,\n warnings: [...tools.warnings, ...agents.warnings, ...workflows.warnings],\n };\n}\n"],"mappings":"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"}