import type { z } from 'zod'; import { getKeyAliases, isRecord } from '../key-alias-utils'; import type { ToolSchema } from './types'; function readAliasedValue( args: Record, key: string, ): unknown | undefined { for (const alias of getKeyAliases(key)) { if (alias in args) { return args[alias]; } } return undefined; } function readNamespacedArg( args: Record, namespace: string, key: string, ): unknown | undefined { // Lookup order: namespace object first, then flat dotted form, then bare key // fallback. Namespace-aware inputs win so multi-platform callers cannot be // cross-contaminated by a top-level bare `deviceId` leaking into the wrong // platform. const namespacedArgs = readAliasedValue(args, namespace); if (isRecord(namespacedArgs)) { const nestedValue = readAliasedValue(namespacedArgs, key); if (nestedValue !== undefined) { return nestedValue; } } const dottedValue = readAliasedValue(args, `${namespace}.${key}`); if (dottedValue !== undefined) { return dottedValue; } const directValue = readAliasedValue(args, key); if (directValue !== undefined) { return directValue; } return undefined; } export function extractNamespacedArgs< TFieldName extends string, TArgs extends Record = Record, >( args: Record, namespace: string, keys: readonly TFieldName[], ): TArgs | undefined { const extracted: Record = {}; for (const key of keys) { const value = readNamespacedArg(args, namespace, key); if (value !== undefined) { extracted[key] = value; } } return Object.keys(extracted).length > 0 ? (extracted as TArgs) : undefined; } export function sanitizeNamespacedArgs( args: Record, namespace: string, keys: readonly string[], ): Record { const excludedKeys = new Set(getKeyAliases(namespace)); for (const key of keys) { for (const alias of getKeyAliases(key)) { excludedKeys.add(alias); } for (const alias of getKeyAliases(`${namespace}.${key}`)) { excludedKeys.add(alias); } } return Object.fromEntries( Object.entries(args).filter(([key]) => !excludedKeys.has(key)), ); } /** * Build a flat tool schema whose keys are dotted `"."`. * * We intentionally stay flat (rather than `{ namespace: z.object({...}) }`) so * that CLI (`--android.device-id`) and `--help` output share the same spelling. * `readNamespacedArg` understands all three input shapes: * nested namespace object, dotted flat key, and bare key fallback. */ export function createNamespacedInitArgSchema( namespace: string, shape: Record, ): ToolSchema { return Object.fromEntries( Object.entries(shape).map(([key, value]) => [`${namespace}.${key}`, value]), ); }