| import type { | |
| EditableSettingSource, | |
| SettingSource, | |
| } from '../settings/constants.js' | |
| import { | |
| ALLOWED_OFFICIAL_MARKETPLACE_NAMES, | |
| type PluginScope, | |
| } from './schemas.js' | |
| /** | |
| * Extended scope type that includes 'flag' for session-only plugins. | |
| * 'flag' scope is NOT persisted to installed_plugins.json. | |
| */ | |
| export type ExtendedPluginScope = PluginScope | 'flag' | |
| /** | |
| * Scopes that are persisted to installed_plugins.json. | |
| * Excludes 'flag' which is session-only. | |
| */ | |
| export type PersistablePluginScope = Exclude<ExtendedPluginScope, 'flag'> | |
| /** | |
| * Map from SettingSource to plugin scope. | |
| * Note: flagSettings maps to 'flag' which is session-only and not persisted. | |
| */ | |
| export const SETTING_SOURCE_TO_SCOPE = { | |
| policySettings: 'managed', | |
| userSettings: 'user', | |
| projectSettings: 'project', | |
| localSettings: 'local', | |
| flagSettings: 'flag', | |
| } as const satisfies Record<SettingSource, ExtendedPluginScope> | |
| /** | |
| * Parsed plugin identifier with name and optional marketplace | |
| */ | |
| export type ParsedPluginIdentifier = { | |
| name: string | |
| marketplace?: string | |
| } | |
| /** | |
| * Parse a plugin identifier string into name and marketplace components | |
| * @param plugin The plugin identifier (name or name@marketplace) | |
| * @returns Parsed plugin name and optional marketplace | |
| * | |
| * Note: Only the first '@' is used as separator. If the input contains multiple '@' symbols | |
| * (e.g., "plugin@market@place"), everything after the second '@' is ignored. | |
| * This is intentional as marketplace names should not contain '@'. | |
| */ | |
| export function parsePluginIdentifier(plugin: string): ParsedPluginIdentifier { | |
| if (plugin.includes('@')) { | |
| const parts = plugin.split('@') | |
| return { name: parts[0] || '', marketplace: parts[1] } | |
| } | |
| return { name: plugin } | |
| } | |
| /** | |
| * Build a plugin ID from name and marketplace | |
| * @param name The plugin name | |
| * @param marketplace Optional marketplace name | |
| * @returns Plugin ID in format "name" or "name@marketplace" | |
| */ | |
| export function buildPluginId(name: string, marketplace?: string): string { | |
| return marketplace ? `${name}@${marketplace}` : name | |
| } | |
| /** | |
| * Check if a marketplace name is an official (Anthropic-controlled) marketplace. | |
| * Used for telemetry redaction — official plugin identifiers are safe to log to | |
| * general-access additional_metadata; third-party identifiers go only to the | |
| * PII-tagged _PROTO_* BQ columns. | |
| */ | |
| export function isOfficialMarketplaceName( | |
| marketplace: string | undefined, | |
| ): boolean { | |
| return ( | |
| marketplace !== undefined && | |
| ALLOWED_OFFICIAL_MARKETPLACE_NAMES.has(marketplace.toLowerCase()) | |
| ) | |
| } | |
| /** | |
| * Map from installable plugin scope to editable setting source. | |
| * This is the inverse of SETTING_SOURCE_TO_SCOPE for editable scopes only. | |
| * Note: 'managed' scope cannot be installed to, so it's not included here. | |
| */ | |
| const SCOPE_TO_EDITABLE_SOURCE: Record< | |
| Exclude<PluginScope, 'managed'>, | |
| EditableSettingSource | |
| > = { | |
| user: 'userSettings', | |
| project: 'projectSettings', | |
| local: 'localSettings', | |
| } | |
| /** | |
| * Convert a plugin scope to its corresponding editable setting source | |
| * @param scope The plugin installation scope | |
| * @returns The corresponding setting source for reading/writing settings | |
| * @throws Error if scope is 'managed' (cannot install plugins to managed scope) | |
| */ | |
| export function scopeToSettingSource( | |
| scope: PluginScope, | |
| ): EditableSettingSource { | |
| if (scope === 'managed') { | |
| throw new Error('Cannot install plugins to managed scope') | |
| } | |
| return SCOPE_TO_EDITABLE_SOURCE[scope] | |
| } | |
| /** | |
| * Convert an editable setting source to its corresponding plugin scope. | |
| * Derived from SETTING_SOURCE_TO_SCOPE to maintain a single source of truth. | |
| * @param source The setting source | |
| * @returns The corresponding plugin scope | |
| */ | |
| export function settingSourceToScope( | |
| source: EditableSettingSource, | |
| ): Exclude<PluginScope, 'managed'> { | |
| return SETTING_SOURCE_TO_SCOPE[source] as Exclude<PluginScope, 'managed'> | |
| } | |