/**
 * OpenAI-compatible function schemas for the CLI's tool catalog.
 *
 * Purpose:
 * Reasoning models on the Responses API (codex, o-series, gpt-5-pro) are
 * RLHFed against the CLI's inline JSON-block protocol and need native
 * function declarations to emit tool calls. The CLI owns these schemas
 * (tools + their rendering are a CLI concern, never a backend concern) and
 * passes them through the chat payload's `options.tools` field. The Azure
 * backend forwards them verbatim to the Responses API and converts incoming
 * function_call SSE events back into the CLI's inline JSON-block format.
 *
 * Shape convention:
 * Each schema mirrors the `parsedData` shape expected by (a) the tool's
 * `execute()` / `parseParameters()` in src/tools/*.js, and (b) the matching
 * renderer in web-ui/src/components/toolRenderers/*.jsx. Keeping all three
 * in sync here means specific renderers (TaskManagerRenderer, TerminalRenderer,
 * etc.) render their native visualizations without the backend needing to
 * know anything about UI.
 *
 * When you add a new tool to the CLI, add its schema here. The backend
 * remains tool-agnostic.
 */

export const OPENAI_FUNCTION_SCHEMAS = [
  // TaskManager — actions-array with flat task fields per action.
  // Field names mirror the tool's system-prompt protocol (src/tools/taskManagerTool.js
  // doc block) and the tool's runtime read-sites, so Responses-API models and
  // chat-completion models produce identical payloads.
  //   create:            {type, title, description?, priority?}
  //   update:            {type, taskId, title?, description?, priority?, status?}
  //   complete|cancel:   {type, taskId}
  //   list|clear:        {type}
  // One call = one task action. Parallel tool calls cover multi-task cases.
  {
    type: 'function',
    name: 'taskmanager',
    description: 'Create, update, complete, cancel, list, clear, or SEARCH tasks. `search` ranks tasks by semantic similarity (with substring fallback when embeddings are disabled). `backfill-search-index` rebuilds the vector index — run after enabling embeddings or after switching provider.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          description: 'Array with a single action object.',
          items: {
            type: 'object',
            properties: {
              type:        { type: 'string', enum: ['create', 'update', 'complete', 'cancel', 'list', 'clear', 'search', 'backfill-search-index'] },
              taskId:      { type: 'string', description: 'Task id — required for update/complete/cancel. Omit for create/list/clear.' },
              query:       { type: 'string', description: 'Search query — required for search.' },
              topK:        { type: 'integer', description: 'Max ranked results for search. Default 10.' },
              hybrid:      { type: 'boolean', description: 'Mix substring scoring via RRF for search. Default true.' },
              title:       { type: 'string', description: 'Task title — required for create; optional for update.' },
              description: { type: 'string' },
              priority:    { type: 'string', enum: ['high', 'medium', 'low'], description: 'Only set for create/update. Omit for list — do NOT use as a filter.' },
              status:      { type: 'string', enum: ['pending', 'in_progress', 'completed'], description: 'Only set for update. Omit for create (defaults to pending) and for list (returns all tasks).' },
            },
            required: ['type'],
          },
          minItems: 1,
          maxItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // JobDone — actions-array with action+summary.
  {
    type: 'function',
    name: 'jobdone',
    description: 'Signal that all tasks are complete and the agent should stop.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              action:  { type: 'string', enum: ['complete'] },
              summary: { type: 'string', description: 'Brief summary of what was accomplished' },
            },
            required: ['action', 'summary'],
          },
        },
      },
      required: ['actions'],
    },
  },
  // Filesystem — actions-array with per-action-typed field names (filePath /
  // outputPath / sourcePath + destPath / directory). Aligned with the tool's
  // system-prompt protocol (src/tools/fileSystemTool.js doc block) and the
  // tool's validate/execute read-sites.
  //   read|delete|exists|stats:   {type, filePath}
  //   write:                      {type, outputPath, content}
  //   append:                     {type, filePath, content}
  //   copy|move:                  {type, sourcePath, destPath}
  //   create-dir:                 {type, directory}
  //   list:                       {type, directory}
  {
    type: 'function',
    name: 'filesystem',
    description: 'Read, write, create, delete, copy/move, append, or list files and directories. Emit one action object per file operation (multiple allowed in the actions array).',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              type:        { type: 'string', enum: ['read', 'write', 'append', 'delete', 'copy', 'move', 'exists', 'stats', 'create-dir', 'list'], description: 'The filesystem operation to perform.' },
              filePath:    { type: 'string', description: 'Target file path. Required for: read, append, delete, exists, stats.' },
              outputPath:  { type: 'string', description: 'Destination file path. Required for: write.' },
              sourcePath:  { type: 'string', description: 'Source file path. Required for: copy, move.' },
              destPath:    { type: 'string', description: 'Destination file path. Required for: copy, move.' },
              directory:   { type: 'string', description: 'Directory path. Required for: create-dir, list.' },
              content:     { type: 'string', description: 'File content. Required for: write, append.' },
            },
            required: ['type'],
          },
        },
      },
      required: ['actions'],
    },
  },
  // Terminal — actions-array. Tool supports run-command, change-directory,
  // list-directory, create-directory, get-working-directory (see
  // src/tools/terminalTool.js switch on action.type).
  {
    type: 'function',
    name: 'terminal',
    description: 'Run shell commands, change/list/create working directories, or report the current working directory.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              type:      { type: 'string', enum: ['run-command', 'change-directory', 'list-directory', 'create-directory', 'get-working-directory'], description: 'The terminal operation to perform.' },
              command:   { type: 'string', description: 'Shell command to run. Required for: run-command.' },
              directory: { type: 'string', description: 'Directory path. Required for: change-directory, list-directory, create-directory.' },
              timeout:   { type: 'number', description: 'Timeout in ms for run-command. Default 30000.' },
            },
            required: ['type'],
          },
        },
      },
      required: ['actions'],
    },
  },
  // Seek — uses filePaths + searchTerms (matches tool's and renderer's flat shape).
  {
    type: 'function',
    name: 'seek',
    description: 'Search for text/patterns across project files (grep-like).',
    parameters: {
      type: 'object',
      properties: {
        searchTerms: {
          type: 'array',
          items: { type: 'string' },
          description: 'Patterns to search for (regex supported)',
        },
        filePaths: {
          type: 'array',
          items: { type: 'string' },
          description: 'Directories or files to search in (defaults to project root)',
        },
      },
      required: ['searchTerms'],
    },
  },
  // File-content-replace — files array with nested replacements.
  // Field names mirror src/tools/fileContentReplaceTool.js runtime (file.path,
  // replacement.oldContent/newContent) so the same payload shape works for
  // both native function calls and the system-prompt inline protocol.
  {
    type: 'function',
    name: 'file-content-replace',
    description: 'Find and replace exact text spans within one or more files.',
    parameters: {
      type: 'object',
      properties: {
        files: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              path: { type: 'string', description: 'File path relative to the project root.' },
              replacements: {
                type: 'array',
                items: {
                  type: 'object',
                  properties: {
                    oldContent: { type: 'string', description: 'Exact text to find (whitespace-sensitive).' },
                    newContent: { type: 'string', description: 'Replacement text.' },
                    all:        { type: 'boolean', description: 'Replace every occurrence. Default false (replace first only).' },
                  },
                  required: ['oldContent', 'newContent'],
                },
              },
            },
            required: ['path', 'replacements'],
          },
        },
      },
      required: ['files'],
    },
  },
  // File-tree — flat params using `directory` + `maxDepth`.
  {
    type: 'function',
    name: 'file-tree',
    description: 'Get a tree-view of the project directory structure.',
    parameters: {
      type: 'object',
      properties: {
        directory:  { type: 'string', description: 'Root directory (defaults to project root)' },
        maxDepth:   { type: 'number', description: 'Max depth (default 3)' },
        showHidden: { type: 'boolean' },
      },
    },
  },
  // Memory — flat params (no actions array). Field names mirror the tool's
  // validate/execute sites in src/tools/memoryTool.js.
  //
  // Memory-entry actions (user-authored notes):
  //   add:             {action, title, content, description?}
  //   update:          {action, id, title?, content?, description?}
  //   delete|read:     {action, id}
  //   list:            {action, level?}
  //   search:          {action, query, level?}
  //   stats:           {action}
  //
  // Conversation-archive action (read-only browse of the agent's own past
  // conversation — survives compaction; see src/services/conversationQuery.js):
  //   reminisce:       {action, mode, ...mode-specific params}
  //     overview       — {mode}
  //     range          — {mode, from?, to?, offset?, limit?, detail?}
  //     search         — {mode, query, role?, maxResults?, cursor?, detail?}
  //     around         — {mode, messageId, before?, after?, detail?}
  //     byTool         — {mode, toolId?, limit?, cursor?}
  //     read           — {mode, messageId, lineFrom?/lineTo? OR contentFrom?/contentTo?, detail?}
  // Web — operation-router envelope. Top-level `operation` picks one of
  // four sub-routines (search/fetch/interactive/authenticate). Interactive
  // takes a nested `actions[]` array with its own per-action discriminator,
  // but that surface is huge (~30 action types) — the schema captures the
  // top-level shape and lets the model emit free-form objects inside
  // `actions`. The tool's parseParameters is permissive about unknown
  // keys, so this remains safe.
  //   search:       {operation, query, engine?, maxResults?, stealthLevel?}
  //   fetch:        {operation, url, formats?[], stealthLevel?}
  //   interactive:  {operation, actions: [{action, ...args}], stealthLevel?,
  //                  humanMode?, headless?}
  //   authenticate: {operation, siteId, loginUrl?, tabName?, stealthLevel?,
  //                  usernameSelector?, passwordSelector?, submitSelector?,
  //                  keepTabOpen?}
  {
    type: 'function',
    name: 'web',
    description: 'Search the web, fetch URL content, drive a real browser, or perform credentialed login. Pick the right operation: search→Google-like, fetch→static page text, interactive→stateful browser session, authenticate→OAuth/form login.',
    parameters: {
      type: 'object',
      properties: {
        operation: {
          type: 'string',
          enum: ['search', 'fetch', 'interactive', 'authenticate'],
          description: 'Top-level routine. Pick exactly one.',
        },
        // search params
        query:        { type: 'string', description: 'Search query (operation=search).' },
        engine:       { type: 'string', description: 'Search engine — "duckduckgo" (default) or "google".' },
        maxResults:   { type: 'integer', description: 'Max search results (operation=search). Default 10.' },
        // fetch params
        url:          { type: 'string', description: 'URL to fetch (operation=fetch).' },
        formats:      {
          type: 'array',
          items: { type: 'string', enum: ['title', 'text', 'links', 'html', 'console'] },
          description: 'Output formats to extract (operation=fetch). Default [title,text].',
        },
        // interactive params
        actions: {
          type: 'array',
          description: 'Browser action chain (operation=interactive). Each item: {action, ...args} — see tool description for the full action vocabulary (navigate, click, type, screenshot, wait-for, …).',
          items: { type: 'object', additionalProperties: true },
        },
        tabName:      { type: 'string', description: 'Tab identifier for stateful sessions (operation=interactive|authenticate).' },
        humanMode:    { type: 'boolean', description: 'Human-like timing for anti-detection (operation=interactive). Default true.' },
        headless:     { type: 'boolean', description: 'Run browser headless (operation=interactive). Default true unless stealthLevel="maximum".' },
        // authenticate params
        siteId:           { type: 'string', description: 'Credential-vault site key (operation=authenticate).' },
        loginUrl:         { type: 'string', description: 'Custom login URL (operation=authenticate).' },
        usernameSelector: { type: 'string', description: 'CSS selector for username field (operation=authenticate).' },
        passwordSelector: { type: 'string', description: 'CSS selector for password field (operation=authenticate).' },
        submitSelector:   { type: 'string', description: 'CSS selector for submit button (operation=authenticate).' },
        keepTabOpen:      { type: 'boolean', description: 'Leave the authenticated tab open for follow-up browsing (operation=authenticate).' },
        // shared
        stealthLevel: {
          type: 'string',
          enum: ['standard', 'maximum'],
          description: 'Anti-detection level. "standard" is headless; "maximum" opens a visible browser window with full stealth (slower).',
        },
      },
      required: ['operation'],
    },
  },
  // AgentCommunication — actions-array, 5 typed actions.
  //   get-available-agents:    {type}
  //   send-message:            {type, recipient, subject?, message, priority?, requiresReply?}
  //   reply-to-message:        {type, messageId, message}
  //   get-unreplied-messages:  {type}
  //   mark-conversation-ended: {type, conversationId, reason?}
  {
    type: 'function',
    name: 'agentcommunication',
    description: 'Send/receive messages between live agents, and find the right agent for a task. Prefer `find-agent` (semantic routing) when you know what kind of work you need done but not which agent; use `get-available-agents` for the full enumeration. `backfill-agents-index` rebuilds the routing index (run after agent CRUD activity or after switching embedding provider).',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              type: {
                type: 'string',
                enum: ['get-available-agents', 'send-message', 'reply-to-message', 'get-unreplied-messages', 'mark-conversation-ended', 'find-agent', 'backfill-agents-index'],
              },
              recipient:      { type: 'string', description: 'Full agent id (from get-available-agents or find-agent). Required for send-message.' },
              subject:        { type: 'string', description: 'Optional short subject for send-message.' },
              message:        { type: 'string', description: 'Message body. Required for send-message and reply-to-message.' },
              priority:       { type: 'string', enum: ['low', 'normal', 'high'], description: 'Send-message priority. Default normal.' },
              requiresReply:  { type: 'boolean', description: 'Send-message expects a reply. Default true.' },
              messageId:      { type: 'string', description: 'Message id to reply to (reply-to-message).' },
              conversationId: { type: 'string', description: 'Conversation id to close (mark-conversation-ended).' },
              query:          { type: 'string', description: 'Natural-language description of the task / role for find-agent. e.g. "good at SQL migrations".' },
              topK:           { type: 'integer', description: 'Max ranked results for find-agent. Default 5.' },
              hybrid:         { type: 'boolean', description: 'For find-agent: mix substring scoring via RRF. Default true.' },
              includeSelf:    { type: 'boolean', description: 'For find-agent: include the requesting agent in results. Default false.' },
              reason:         { type: 'string', description: 'Optional close reason (mark-conversation-ended).' },
            },
            required: ['type'],
          },
          minItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // AgentDelay — flat params. One pause per call; chain via parallel tool calls.
  //   {duration, reason?}
  {
    type: 'function',
    name: 'agentdelay',
    description: 'Pause this agent for `duration` seconds. Use after long-running shell commands or while waiting on external processes — never to "think harder".',
    parameters: {
      type: 'object',
      properties: {
        duration: { type: 'integer', description: 'Seconds to pause. Bounded by the tool’s configured min/max range.' },
        reason:   { type: 'string',  description: 'Optional human-readable reason (logged with the pause).' },
      },
      required: ['duration'],
    },
  },
  // Help — flat params. List all tools OR fetch full docs for a specific one.
  //   list-all:    {list: true}
  //   describe:    {tool: "<toolId>"}
  {
    type: 'function',
    name: 'help',
    description: 'Fetch full documentation for a tool, or list every tool you have access to. Call this before invoking a tool for the first time if you’re unsure of its parameters.',
    parameters: {
      type: 'object',
      properties: {
        tool: { type: 'string', description: 'Tool id whose full docs you want — e.g. "filesystem", "terminal", "web".' },
        list: { type: 'boolean', description: 'Set true to list every tool id with a one-line summary.' },
      },
    },
  },
  // UserPrompt — flat params: an optional context message and an array
  // of question objects. Each option can be a plain string or a richer
  // {id, label, description}. Agent execution PAUSES until the user
  // responds — use sparingly and only for irreversible/ambiguous decisions.
  //   {message?, questions: [{id?, message, options?, allowFreeText?, allowWebSearch?, multiSelect?, required?}]}
  {
    type: 'function',
    name: 'userprompt',
    description: 'Ask the user a question and pause until they answer. Use ONLY when you genuinely cannot proceed without their input — never to confirm reversible work.',
    parameters: {
      type: 'object',
      properties: {
        message:   { type: 'string', description: 'Optional context message shown above the questions.' },
        questions: {
          type: 'array',
          minItems: 1,
          items: {
            type: 'object',
            properties: {
              id:             { type: 'string', description: 'Stable question id. Auto-generated if omitted.' },
              message:        { type: 'string', description: 'The question text.' },
              options: {
                type: 'array',
                description: 'Suggested answers. Each entry is a plain string OR an object {id, label, description}.',
                items: {
                  oneOf: [
                    { type: 'string' },
                    {
                      type: 'object',
                      properties: {
                        id:          { type: 'string' },
                        label:       { type: 'string' },
                        description: { type: 'string' },
                      },
                      required: ['label'],
                    },
                  ],
                },
              },
              allowFreeText:  { type: 'boolean', description: 'Allow free-text response. Default true.' },
              allowWebSearch: { type: 'boolean', description: 'Show a web-search hint icon. Default true.' },
              multiSelect:    { type: 'boolean', description: 'Allow multiple option selection. Default false.' },
              required:       { type: 'boolean', description: 'Answer required to continue. Default true.' },
            },
            required: ['message'],
          },
        },
      },
      required: ['questions'],
    },
  },
  // PDF — actions-array; per-tool the inner discriminator is `action`
  // (legacy shape; the runtime accepts both `type` and `action`).
  //   get-info:    {action, filePath}
  //   read-pages:  {action, filePath, startPage?, endPage?}   1-indexed; endPage exclusive; max 10 pages/call
  //   create-pdf:  {action, outputPath, htmlContent, pageSize?, orientation?, margins?, ...}
  {
    type: 'function',
    name: 'pdf',
    description: 'Inspect, read, or create PDF files. Read max 10 pages per call to stay within token limits.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              action:               { type: 'string', enum: ['get-info', 'read-pages', 'create-pdf'] },
              filePath:             { type: 'string', description: 'Source PDF path. Required for get-info and read-pages.' },
              startPage:            { type: 'integer', description: '1-indexed first page (read-pages). Default 1.' },
              endPage:              { type: 'integer', description: 'Exclusive last page (read-pages). Default startPage+10.' },
              outputPath:           { type: 'string', description: 'Output file path (create-pdf). Relative to project dir or absolute.' },
              htmlContent:          { type: 'string', description: 'Full HTML to render into the PDF (create-pdf).' },
              pageSize:             { type: 'string', enum: ['A3', 'A4', 'A5', 'Letter', 'Legal', 'Tabloid'], description: 'Page size for create-pdf. Default A4.' },
              orientation:          { type: 'string', enum: ['portrait', 'landscape'], description: 'Orientation for create-pdf. Default portrait.' },
              margins:              { type: 'object', description: 'CSS-unit margins {top, right, bottom, left}.', additionalProperties: { type: 'string' } },
              printBackground:      { type: 'boolean', description: 'Whether to print background colors/images (create-pdf). Default true.' },
              displayHeaderFooter:  { type: 'boolean', description: 'Show header/footer (create-pdf). Default false.' },
              headerTemplate:       { type: 'string', description: 'HTML template for the header (create-pdf).' },
              footerTemplate:       { type: 'string', description: 'HTML template for the footer (create-pdf).' },
            },
            required: ['action'],
          },
          minItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // Vision — flat params; one analyze/ask per call. Maintains sessions
  // so follow-up questions reuse the uploaded image without re-sending.
  //   analyze:         {action, imagePath, question, model?}
  //   ask:             {action, sessionId, question}
  //   list-sessions:   {action}
  //   close-session:   {action, sessionId}
  {
    type: 'function',
    name: 'vision',
    description: 'Analyze an image with a natural-language question, then ask follow-ups in the same session without re-uploading. Session-based.',
    parameters: {
      type: 'object',
      properties: {
        action:    { type: 'string', enum: ['analyze', 'ask', 'list-sessions', 'close-session'] },
        imagePath: { type: 'string', description: 'Local path to the image (analyze).' },
        question:  { type: 'string', description: 'Natural-language question (analyze, ask).' },
        sessionId: { type: 'string', description: 'Vision session id from a previous analyze (ask, close-session).' },
        model:     { type: 'string', description: 'Optional model override for analyze.' },
      },
      required: ['action'],
    },
  },
  // Image-gen — flat params; one image per call.
  //   {prompt, outputPath?, outputType?, size?, model?, sourceImage?, transparency?}
  {
    type: 'function',
    name: 'image-gen',
    description: 'Generate or edit an image. WebP by default; force PNG/JPEG via outputPath extension or outputType. sourceImage lets you edit an existing file (background removal, style transfer, etc.).',
    parameters: {
      type: 'object',
      properties: {
        prompt:       { type: 'string', description: 'Detailed description of the image OR edit instruction when sourceImage is provided.' },
        outputPath:   { type: 'string', description: 'Where to save the image. Extension picks the format (.webp/.png/.jpg).' },
        outputType:   { type: 'string', enum: ['webp', 'png', 'jpg', 'jpeg'], description: 'Explicit format. Used when outputPath has no extension.' },
        size:         { type: 'string', description: 'Image size, e.g. "1024x1024" or "1792x1024".' },
        model:        { type: 'string', description: 'Optional model override.' },
        sourceImage:  { type: 'string', description: 'Path to an existing image to edit instead of generating from scratch.' },
        transparency: { type: 'boolean', description: 'Request a transparent background (WebP/PNG output).' },
      },
      required: ['prompt'],
    },
  },
  // Video-gen — flat params; one video per call.
  //   {prompt, outputPath?, model?, duration?}
  {
    type: 'function',
    name: 'video-gen',
    description: 'Generate a short video clip from a text prompt. Provider-side files expire in 24h — set outputPath to keep a local copy.',
    parameters: {
      type: 'object',
      properties: {
        prompt:     { type: 'string', description: 'Detailed scene description.' },
        outputPath: { type: 'string', description: 'Local .mp4 path. Omit for a temp file that expires.' },
        model:      { type: 'string', description: 'Optional model override.' },
        duration:   { type: 'integer', description: 'Clip duration in seconds (model-dependent maximum).' },
      },
      required: ['prompt'],
    },
  },
  // Skills — flat params with an `action` enum + per-action fields.
  // Shape mirrors src/tools/skillsTool.js getParameterSchema().
  {
    type: 'function',
    name: 'skills',
    description: 'Browse and load reusable skill recipes from the agent\'s skill library. Use describe → read-section to load only the parts you need. `search` ranks skills by semantic similarity (falls back to substring when embeddings are off). `backfill-search-index` rebuilds the vector index — run after enabling embeddings.',
    parameters: {
      type: 'object',
      properties: {
        action: {
          type: 'string',
          enum: ['list', 'describe', 'read', 'read-section', 'read-file', 'create', 'update', 'delete', 'import', 'search', 'backfill-search-index'],
        },
        name:    { type: 'string', description: 'Skill name in kebab-case. Required for describe/read/read-section/read-file/create/update/delete.' },
        content: { type: 'string', description: 'Markdown body. Required for create.' },
        section: { type: 'string', description: 'Section heading to read (read-section).' },
        file:    { type: 'string', description: 'Relative file path within the skill directory (read-file).' },
        query:   { type: 'string', description: 'Natural-language query for search.' },
        topK:    { type: 'integer', description: 'Max ranked results for search. Default 5.' },
        hybrid:  { type: 'boolean', description: 'Mix substring scoring via RRF for search. Default true.' },
        source:  { type: 'string', description: 'Source file or directory path (import).' },
        files: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              path:    { type: 'string' },
              content: { type: 'string' },
            },
            required: ['path', 'content'],
          },
          description: 'Optional supporting files (create/update).',
        },
      },
      required: ['action'],
    },
  },
  // Code-map — actions-array with `type` discriminator.
  //   skeleton:    {type, path, level?, includeImports?}
  //   read-range:  {type, filePath, startLine, endLine}
  {
    type: 'function',
    name: 'code-map',
    description: 'Explore code structure without reading whole files. Two-step: skeleton gives signatures with line numbers; read-range pulls the exact span. Batches both actions in one call.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              type:           { type: 'string', enum: ['skeleton', 'read-range'] },
              path:           { type: 'string', description: 'File or directory to scan (skeleton).' },
              level:          { type: 'string', enum: ['A.0', 'A.1', 'B.0', 'B.1'], description: 'Skeleton detail level. A=public only, B=all; .0=signatures only, .1=+docstrings. Default B.0.' },
              includeImports: { type: 'boolean', description: 'Include import statements in skeleton output. Default false.' },
              filePath:       { type: 'string', description: 'File to read a range from (read-range).' },
              startLine:      { type: 'integer', description: '1-indexed first line (read-range).' },
              endLine:        { type: 'integer', description: '1-indexed last line, inclusive (read-range).' },
            },
            required: ['type'],
          },
          minItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // Office — actions-array with dot-namespaced `type` ({family.verb}).
  // Family is one of doc / sheet / pres. Verb is create / read / info.
  // Content shape varies per type; see src/tools/officeTool.js getDescription().
  {
    type: 'function',
    name: 'office',
    description: 'Create or read Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) files. Type is "family.verb" — e.g. "doc.create", "sheet.read", "pres.info".',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              type: {
                type: 'string',
                enum: ['doc.create', 'doc.read', 'doc.info', 'sheet.create', 'sheet.read', 'sheet.info', 'pres.create', 'pres.info'],
              },
              outputPath: { type: 'string', description: 'Destination path for *.create actions. Extension is auto-appended if missing.' },
              filePath:   { type: 'string', description: 'Source path for *.read and *.info actions.' },
              content:    { type: 'object', description: 'Family-specific document model — see the office tool description for doc/sheet/pres content shapes.', additionalProperties: true },
              format:     { type: 'string', enum: ['text', 'html'], description: 'Output format for doc.read. Default text.' },
              sheetName:  { type: 'string', description: 'Sheet name to read (sheet.read). Defaults to first sheet.' },
              startRow:   { type: 'integer', description: 'First row, 1-indexed (sheet.read). Default 1.' },
              endRow:     { type: 'integer', description: 'Last row, 1-indexed inclusive (sheet.read).' },
              includeFormulas: { type: 'boolean', description: 'Return formulas instead of values (sheet.read).' },
            },
            required: ['type'],
          },
          minItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // Visual-editor — flat params; one action per call. Drives the live
  // visual editor UI (element selection, app navigation, server mgmt).
  //   {action, url?, selector?, mode?, projectRoot?}
  {
    type: 'function',
    name: 'visual-editor',
    description: 'Drive the visual editor: pick elements on a live page, navigate the embedded preview, start/stop a dev server, switch select vs preview mode.',
    parameters: {
      type: 'object',
      properties: {
        action: {
          type: 'string',
          enum: [
            'get-context', 'set-app-url', 'get-status', 'clear-context',
            'start-server', 'serve-static', 'detect-project', 'open-editor',
            'highlight', 'scroll-to', 'get-source', 'reload', 'set-mode',
          ],
        },
        url:         { type: 'string', description: 'Target URL (set-app-url, open-editor).' },
        selector:    { type: 'string', description: 'CSS selector (highlight, scroll-to, get-source).' },
        mode:        { type: 'string', enum: ['select', 'preview'], description: 'Interaction mode (set-mode).' },
        projectRoot: { type: 'string', description: 'Project root for start-server / serve-static / detect-project.' },
      },
      required: ['action'],
    },
  },
  // WhatsApp — flat params, mirrors the tool's getParameterSchema() exactly.
  // READ-ONLY surface: no message sending.
  //   {action, chatId?, messageId?, searchTerm?, chatIds?, filter?, type?, page?, pageSize?, unreadOnly?, limit?, before?, after?}
  {
    type: 'function',
    name: 'whatsapp',
    description: 'Read-only access to the user\'s linked WhatsApp account (list chats, fetch messages, search, download media). No send capability.',
    parameters: {
      type: 'object',
      properties: {
        action:     { type: 'string', enum: ['list-chats', 'get-messages', 'search', 'chat-info', 'get-media', 'status'] },
        chatId:     { type: 'string', description: 'WhatsApp chat id (get-messages, chat-info, get-media).' },
        messageId:  { type: 'string', description: 'Message id with attached media (get-media).' },
        searchTerm: { type: 'string', description: 'Full-text search query (search).' },
        chatIds:    { type: 'array', items: { type: 'string' }, description: 'Restrict search to specific chats.' },
        filter:     { type: 'string', description: 'List-chats free-text filter (matches chat name).' },
        type:       { type: 'string', enum: ['all', 'group', 'individual'], description: 'Filter list-chats by chat type.' },
        page:       { type: 'integer', description: 'List-chats pagination (1-indexed).' },
        pageSize:   { type: 'integer', description: 'List-chats page size.' },
        unreadOnly: { type: 'boolean', description: 'List-chats: only chats with unread messages.' },
        limit:      { type: 'integer', description: 'Get-messages max count.' },
        before:     { type: 'string', description: 'Get-messages cursor: messages before this id.' },
        after:      { type: 'string', description: 'Get-messages cursor: messages after this id.' },
      },
      required: ['action'],
    },
  },
  // Memory — actions-array (canonical plural shape, matches filesystem /
  // terminal / taskmanager). Supports batching multiple ops in one
  // tool call: e.g. read three memories at once, or list+search in
  // the same turn. Singular {action, ...} is still accepted by the
  // runtime as backward-compat but the schema teaches plural.
  {
    type: 'function',
    name: 'memory',
    description: 'Store and retrieve persistent memories across sessions, semantic-search them (when embeddings are enabled — gracefully falls back to substring otherwise), and browse your own pre-compaction conversation archive via the `reminisce` action. Pack multiple operations into one call.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              type:        { type: 'string', enum: ['add', 'update', 'append', 'delete', 'read', 'list', 'search', 'semantic-search', 'backfill-embeddings', 'reminisce-backfill-embeddings', 'forget-stale', 'publish-to-team', 'search-team', 'unpublish-from-team', 'stats', 'reminisce'], description: 'The memory operation to perform. `semantic-search` uses embeddings over declared memories. `backfill-embeddings` rebuilds the memory index. `reminisce-backfill-embeddings` rebuilds the conversation-archive index. `forget-stale` deletes memories older than ageDays. `publish-to-team` copies an existing memory into a team\'s shared pool (requires team membership). `search-team` semantic-searches a team pool. `unpublish-from-team` removes from a team pool.' },
              id:          { type: 'string', description: 'Memory id. Required for: update, append, delete, read (of memory entries).' },
              title:       { type: 'string', description: 'Short title. Required for: add. Optional for: update.' },
              content:     { type: 'string', description: 'Full memory content. Required for: add, append. Optional for: update.' },
              description: { type: 'string', description: 'Short summary. Optional for: add, update.' },
              query:       { type: 'string', description: 'Search query. Required for: search, semantic-search, reminisce search.' },
              topK:        { type: 'integer', description: 'Max ranked results for semantic-search. Default 10. Positive integer.' },
              hybrid:      { type: 'boolean', description: 'For semantic-search: mix substring scoring via Reciprocal Rank Fusion. Default true — preserves exact-match precision while adding paraphrase recall.' },
              ageDays:     { type: 'number', description: 'Age threshold for forget-stale. Default 90. Memories older than this become candidates for deletion.' },
              dryRun:      { type: 'boolean', description: 'For forget-stale: preview candidates without deleting. Default false.' },
              includePlans: { type: 'boolean', description: 'For forget-stale: include plan/* memories. Default false (they\'re the active working plan).' },
              teamId:      { type: 'string', description: 'Team identifier — required for publish-to-team, search-team, unpublish-from-team. Caller must be a member of the team.' },
              level:       { type: 'string', enum: ['titles', 'descriptions', 'full'], description: 'Detail level for memory list/search output.' },

              // ── reminisce-specific params ─────────────────────────
              mode:        { type: 'string', enum: ['overview', 'range', 'search', 'semantic-search', 'around', 'byTool', 'read'], description: 'Reminisce sub-mode. Required when type="reminisce". `semantic-search` ranks archived messages by embedding similarity to `query` (hybrid: semantic + substring via RRF when embeddings are enabled; falls back to substring `search` when disabled).' },
              messageId:   { type: 'string', description: 'Stable pointer into the conversation archive (reminisce around/read).' },
              from:        { type: 'string', description: 'ISO timestamp lower bound (reminisce range).' },
              to:          { type: 'string', description: 'ISO timestamp upper bound (reminisce range).' },
              offset:      { type: 'integer', description: 'Pagination offset (reminisce range).' },
              limit:       { type: 'integer', description: 'Max entries returned (reminisce range/byTool). 1–100.' },
              maxResults:  { type: 'integer', description: 'Max search matches returned (reminisce search). 1–50.' },
              role:        { type: 'string', enum: ['user', 'assistant', 'system'], description: 'Filter by message role (reminisce search).' },
              cursor:      { type: 'string', description: 'Opaque pagination cursor from a previous reminisce search/byTool response.' },
              toolId:      { type: 'string', description: 'Filter by toolId (reminisce byTool).' },
              before:      { type: 'integer', description: 'Number of MESSAGES before the target to include (reminisce around). 0–20.' },
              after:       { type: 'integer', description: 'Number of MESSAGES after the target to include (reminisce around). 0–20.' },
              lineFrom:    { type: 'integer', description: 'First line to read (reminisce read).' },
              lineTo:      { type: 'integer', description: 'Last line to read (reminisce read). Window capped at 500 lines.' },
              contentFrom: { type: 'integer', description: 'First character offset (reminisce read).' },
              contentTo:   { type: 'integer', description: 'Last character offset (reminisce read). Window capped at 16000 chars.' },
              detail:      { type: 'string', enum: ['default', 'full'], description: 'Return shape for reminisce results.' },
            },
            required: ['type'],
          },
        },
      },
      required: ['actions'],
    },
  },
  // Composio — third-party integration hub (~250 apps). actions-array with
  // type discriminator. One operation per call to keep the OAuth + execute
  // round-trips traceable per tool invocation.
  {
    type: 'function',
    name: 'composio',
    description: 'Third-party integration hub: connect to apps like Gmail, Slack, GitHub, Linear, Notion, Drive, Sheets, Calendar via Composio. Prefer `find-action` (semantic search across the whole catalog) when you don\'t already know the slug — it returns ranked candidates with their inputSchemas attached. Use `list-toolkits` / `list-tools` only when you need the full enumeration. `backfill-actions-index` builds the search index (run it once after enabling embeddings or after Composio refreshes its catalog).',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          description: 'Array with a single action object.',
          items: {
            type: 'object',
            properties: {
              type:        { type: 'string', enum: ['list-toolkits', 'list-tools', 'connect', 'connection-status', 'execute', 'find-action', 'backfill-actions-index'] },
              toolkitSlug: { type: 'string', description: 'Toolkit slug — required for list-tools, connect, connection-status, execute. Optional for find-action (narrows the search to one toolkit). e.g. "github", "slack".' },
              actionSlug:  { type: 'string', description: 'Composio action slug — required for execute. e.g. "GITHUB_CREATE_AN_ISSUE".' },
              arguments:   { type: 'object', additionalProperties: true, description: 'Arguments for execute, matching the action\'s inputSchema (discover via list-tools or find-action).' },
              query:       { type: 'string', description: 'Natural-language query for find-action. e.g. "send a DM in Slack", "open a GitHub issue".' },
              topK:        { type: 'integer', description: 'Max ranked results for find-action. Default 10. Positive integer.' },
              hybrid:      { type: 'boolean', description: 'For find-action: mix substring scoring via Reciprocal Rank Fusion. Default true.' },
            },
            required: ['type'],
          },
          minItems: 1,
          maxItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // PlatformControl — control Loxia platform features (schedules, agents,
  // teams, flows). Wide param union; per-action requirements are validated
  // server-side. Call list-capabilities first to discover this agent's
  // permission level for each feature.
  {
    type: 'function',
    name: 'platformcontrol',
    description: 'Control Loxia platform features: scheduled tasks, agents, teams, and flows (CRUD + execute). Per-feature permission applies. Call list-capabilities to discover what this agent can do.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          description: 'Array with a single action object.',
          items: {
            type: 'object',
            properties: {
              type: {
                type: 'string',
                enum: [
                  'list-capabilities',
                  // Scheduled tasks
                  'list-schedules', 'get-schedule', 'create-schedule', 'update-schedule',
                  'delete-schedule', 'toggle-schedule', 'trigger-schedule', 'list-presets',
                  'schedule-self-resume',
                  // Agent CRUD
                  'list-agents', 'create-agent', 'update-agent', 'delete-agent',
                  // Team CRUD + membership
                  'list-teams', 'create-team', 'update-team', 'delete-team',
                  'add-team-member', 'remove-team-member',
                  // Flow CRUD + execution
                  'list-flows', 'get-flow', 'create-flow', 'update-flow',
                  'delete-flow', 'execute-flow', 'dry-run-flow',
                ],
              },
              // ── Schedule fields ──
              scheduleId:      { type: 'string', description: 'Required for get/update/delete/toggle/trigger-schedule.' },
              name:            { type: 'string', description: 'Name — required for create-schedule, create-agent, create-team, create-flow. Optional for updates and schedule-self-resume.' },
              prompt:          { type: 'string', description: 'Required for create-schedule and schedule-self-resume.' },
              cronExpression:  { type: 'string', description: 'Required for create-schedule. 5-field cron OR a preset name (call list-presets).' },
              targetAgentId:   { type: 'string', description: 'create-schedule target. "self" = this agent. Defaults to caller in "own" mode.' },
              enabled:         { type: 'boolean', description: 'Defaults to true on create. Used by toggle-schedule.' },
              runOnce:         { type: 'boolean' },
              maxRuns:         { type: 'integer' },
              startDate:       { type: 'string', description: 'ISO datetime.' },
              endDate:         { type: 'string', description: 'ISO datetime.' },
              description:     { type: 'string' },
              runAt:           { type: 'string', description: 'ISO datetime — one-shot wake time for schedule-self-resume (future, minute-resolution).' },
              // ── Agent fields ──
              agentId:         { type: 'string', description: 'Required for update-agent, delete-agent, add/remove-team-member. "self" sugar accepted by team ops.' },
              systemPrompt:    { type: 'string', description: 'Required for create-agent.' },
              model:           { type: 'string' },
              capabilities:    { type: 'array', items: { type: 'string' } },
              skills:          { type: 'array', items: { type: 'string' } },
              toolConfig:      { type: 'object', additionalProperties: true, description: 'Per-tool config object. Child platformcontrol.* is CLAMPED to caller\'s level (no privilege escalation).' },
              directoryAccess: { type: 'object', additionalProperties: true },
              // ── Team fields ──
              teamId:          { type: 'string', description: 'Required for update-team, delete-team, add/remove-team-member.' },
              color:           { type: 'string' },
              // ── Flow fields ──
              flowId:          { type: 'string', description: 'Required for get/update/delete/execute/dry-run-flow.' },
              nodes:           { type: 'array', items: { type: 'object', additionalProperties: true } },
              edges:           { type: 'array', items: { type: 'object', additionalProperties: true } },
              variables:       { type: 'object', additionalProperties: true },
              flow:            { type: 'object', additionalProperties: true, description: 'Full flow definition — alternative to flowId for dry-run-flow (lint without saving).' },
              input:           { type: 'object', additionalProperties: true, description: 'Execution input for execute-flow.' },
            },
            required: ['type'],
          },
          minItems: 1,
          maxItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // Desktop — flat params per call (desktop's parseParameters() returns
  // parsed.parameters || parsed). Coord-or-intent unions: click/scroll/drag
  // accept either raw (x, y) OR a natural-language `intent` the grounding
  // model resolves to coords from a fresh screenshot.
  {
    type: 'function',
    name: 'desktop',
    description: 'Drive the user\'s OS: screenshot, click, type, key_press, scroll, drag, list/focus windows. Prefer intent-driven actions (e.g. {"action":"click","intent":"orange Save button"}) over raw coords — they screenshot + ground + act in one step.',
    parameters: {
      type: 'object',
      properties: {
        action: {
          type: 'string',
          enum: ['screenshot', 'click', 'type', 'key_press', 'scroll', 'drag', 'list_windows', 'focus_window', 'describe_capabilities'],
        },
        // ── Coord-or-intent (click, scroll, drag) ──
        x:           { type: 'number', description: 'X coord. Use when not passing intent.' },
        y:           { type: 'number', description: 'Y coord. Use when not passing intent.' },
        intent:      { type: 'string', description: 'Natural-language description ("the orange Save button"). Tool screenshots, grounds via vision LLM, then clicks/scrolls/drags.' },
        // ── Click ──
        button:      { type: 'string', enum: ['left', 'right', 'middle'], description: 'Click/drag button. Defaults to left.' },
        count:       { type: 'integer', description: '1 = single click (default), 2 = double click.' },
        // ── Type ──
        text:        { type: 'string', description: 'Required for action="type".' },
        delayMs:     { type: 'integer', description: 'Optional per-keystroke delay for type.' },
        // ── KeyPress ──
        keys:        { type: 'array', items: { type: 'string' }, description: 'Required for action="key_press". Chord like ["Control","S"].' },
        // ── Scroll ──
        dy:          { type: 'number', description: 'Required for action="scroll". Positive = down.' },
        // ── Drag ──
        from:        { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Drag start coords (alternative to fromIntent).' },
        to:          { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Drag end coords (alternative to toIntent).' },
        fromIntent:  { type: 'string', description: 'Drag start intent.' },
        toIntent:    { type: 'string', description: 'Drag end intent.' },
        // ── FocusWindow ──
        titleMatch:  { type: 'string', description: 'Required for action="focus_window". Substring match against window titles.' },
        // ── Screenshot ──
        region:      { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' }, width: { type: 'number' }, height: { type: 'number' } }, description: 'Optional capture region; defaults to full screen.' },
      },
      required: ['action'],
    },
  },
  // Import Analyzer — flat params. Single-operation tool; parseParameters
  // accepts the object directly.
  {
    type: 'function',
    name: 'import-analyzer',
    description: 'Analyze JS/TS imports: detect missing files, missing exports, circular dependencies, and unused exports. Use mode="quick" for fast scans.',
    parameters: {
      type: 'object',
      properties: {
        path:       { type: 'string', description: 'Directory to analyze. Default ".".' },
        mode:       { type: 'string', enum: ['full', 'quick', 'fix'], description: 'Default "full".' },
        output:     { type: 'string', enum: ['summary', 'detailed', 'json'], description: 'Default "summary".' },
        ignoreFile: { type: 'string', description: 'Ignore file name. Default ".gitignore".' },
      },
    },
  },
  // Dependency Resolver — flat params. Single-operation tool. Always creates
  // a backup before modifying package.json.
  {
    type: 'function',
    name: 'dependency-resolver',
    description: 'Check or update npm dependencies to latest compatible versions. Creates a .backup.json before modifying package.json. mode="check" is read-only.',
    parameters: {
      type: 'object',
      properties: {
        path:       { type: 'string', description: 'Project directory with package.json. Default ".".' },
        mode:       { type: 'string', enum: ['check', 'fix', 'auto'], description: 'Default "check" (read-only).' },
        includeDev: { type: 'boolean', description: 'Include devDependencies. Default true.' },
      },
    },
  },
  // Static Analysis — actions-array with type discriminator. Heterogeneous
  // surface across JS/TS/Python/CSS analysis, ESLint, Prettier, Sparrow SAST,
  // and structured-file validation.
  {
    type: 'function',
    name: 'staticanalysis',
    description: 'Static code analysis: syntax/type/import errors, ESLint, Prettier formatting, Sparrow SAST (12 languages, no external deps), structured-file validation (JSON/YAML/XML/TOML/INI/ENV). One action per call.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          description: 'Array with a single action object.',
          items: {
            type: 'object',
            properties: {
              type: {
                type: 'string',
                enum: [
                  'analyze', 'analyze-project', 'fix', 'format',
                  'security-scan', 'security-scan-project',
                  'sparrow-scan', 'sparrow-scan-project',
                  'validate-config', 'validate-config-directory',
                  'validate-structured',
                ],
              },
              filePath:        { type: 'string', description: 'Required for single-file actions (analyze, fix, format, security-scan, sparrow-scan, validate-config, validate-structured by path).' },
              directory:       { type: 'string', description: 'Required for project-wide actions (analyze-project, security-scan-project, sparrow-scan-project, validate-config-directory).' },
              pattern:         { type: 'string', description: 'Optional glob pattern, e.g. "**/*.js".' },
              includeWarnings: { type: 'boolean', description: 'Include warnings in results. Default true.' },
              maxErrors:       { type: 'integer', description: 'Cap number of errors returned.' },
              writeFile:       { type: 'boolean', description: 'For fix/format: write the result back to disk.' },
              content:         { type: 'string', description: 'Inline content for validate-structured (requires format).' },
              format:          { type: 'string', enum: ['json', 'yaml', 'xml', 'toml', 'ini', 'env'], description: 'Format override for validate-structured (auto-detected from filePath if omitted).' },
            },
            required: ['type'],
          },
          minItems: 1,
          maxItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // Clone Detection — actions-array, currently a single type. Future
  // detection modes will land alongside detect-clones in this enum.
  {
    type: 'function',
    name: 'clonedetection',
    description: 'Detect exact and similar code clones in JS/TS/Vue projects. Returns refactoring recommendations with priorities. One action per call.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          description: 'Array with a single action object.',
          items: {
            type: 'object',
            properties: {
              type:                { type: 'string', enum: ['detect-clones'] },
              directory:           { type: 'string', description: 'Directory to analyze. Required.' },
              minTokens:           { type: 'integer', description: 'Minimum token count per clone. Default 50; lower = more sensitive.' },
              minLines:            { type: 'integer', description: 'Minimum line count per clone. Default 5.' },
              similarityThreshold: { type: 'number', description: '0-1 similarity threshold. Default 0.85.' },
              priorityFilter:      { type: 'string', enum: ['high', 'medium', 'low'], description: 'Filter detected clones by priority.' },
              maxResults:          { type: 'integer', description: 'Cap number of clones returned.' },
              outputMode:          { type: 'string', enum: ['summary', 'detailed', 'recommendations'], description: 'Default "detailed".' },
            },
            required: ['type', 'directory'],
          },
          minItems: 1,
          maxItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // Foundry Web Search — flat params. Single-operation tool with one
  // required field; orchestrates server-side via Azure AI Foundry + Bing.
  {
    type: 'function',
    name: 'foundry_web_search',
    description: 'Grounded web search via Azure AI Foundry + Bing. Returns a synthesized answer with CITED URLs. Use for time-sensitive lookups requiring sources. NOT for raw-link lists — use the "web" tool for that.',
    parameters: {
      type: 'object',
      properties: {
        query:        { type: 'string', description: 'The search query.' },
        instructions: { type: 'string', description: 'Optional override of the default tool-use nudge — only when the model is ignoring search.' },
        model:        { type: 'string', description: 'Optional chat model to orchestrate (default gpt-4o; must have Bing grounding bound in Foundry).' },
      },
      required: ['query'],
    },
  },
  // Doc (legacy) — Word document tool. SUPERSEDED by the "office" tool's
  // doc.* actions; kept registered for back-compat. New callers should
  // prefer office.
  {
    type: 'function',
    name: 'doc',
    description: 'LEGACY Word (.docx) tool — prefer the "office" tool with doc.* actions instead. Get info, read text/HTML, or create DOCX files.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          description: 'Array with a single action object.',
          items: {
            type: 'object',
            properties: {
              action:     { type: 'string', enum: ['get-info', 'read', 'create'] },
              filePath:   { type: 'string', description: 'Required for get-info and read.' },
              format:     { type: 'string', enum: ['text', 'html'], description: 'read output format. Default "text".' },
              outputPath: { type: 'string', description: 'Required for create.' },
              content:    { type: 'object', additionalProperties: true, description: 'Required for create. { title?, author?, sections: [{ children: [{ type, ... }] }] }.' },
            },
            required: ['action'],
          },
          minItems: 1,
          maxItems: 1,
        },
      },
      required: ['actions'],
    },
  },
  // Spreadsheet (legacy) — Excel tool. SUPERSEDED by the "office" tool's
  // sheet.* actions; kept registered for back-compat.
  {
    type: 'function',
    name: 'spreadsheet',
    description: 'LEGACY Excel (.xlsx) tool — prefer the "office" tool with sheet.* actions instead. Get info, read sheet data, or create XLSX files.',
    parameters: {
      type: 'object',
      properties: {
        actions: {
          type: 'array',
          description: 'Array with a single action object.',
          items: {
            type: 'object',
            properties: {
              action:          { type: 'string', enum: ['get-info', 'read', 'create'] },
              filePath:        { type: 'string', description: 'Required for get-info and read.' },
              sheetName:       { type: 'string', description: 'Sheet to read (defaults to first sheet).' },
              startRow:        { type: 'integer', description: '1-indexed start row for read.' },
              endRow:          { type: 'integer', description: 'Inclusive end row for read.' },
              includeFormulas: { type: 'boolean', description: 'Return formulas instead of values for read.' },
              outputPath:      { type: 'string', description: 'Required for create.' },
              content:         { type: 'object', additionalProperties: true, description: 'Required for create. { sheets: [{ name, columns, rows, headerStyle?, freezeRow?, autoFilter?, formulas? }] }.' },
            },
            required: ['action'],
          },
          minItems: 1,
          maxItems: 1,
        },
      },
      required: ['actions'],
    },
  },
];

/**
 * Build the tools array for a specific agent based on its enabled capabilities.
 * Keeps the model's tool list minimal — it sees only what the agent actually
 * has access to, which also reduces prompt size and ambiguity.
 *
 * @param {string[]} capabilities - Agent capabilities (matches tool ids)
 * @returns {Array} Function schemas the agent is allowed to invoke
 */
export function getToolSchemasForAgent(capabilities = []) {
  if (!Array.isArray(capabilities) || capabilities.length === 0) {
    // Agent has no capability list — give it the full catalog rather than
    // leaving it tool-less. Matches the CLI's existing "include all" fallback.
    return OPENAI_FUNCTION_SCHEMAS;
  }
  const allowed = new Set(capabilities.map(c => String(c).toLowerCase()));
  return OPENAI_FUNCTION_SCHEMAS.filter(s => allowed.has(s.name.toLowerCase()));
}

/**
 * Names of every tool that has a native function schema in this file.
 * Importable as a Set so other modules (notably baseTool's system-prompt
 * builder) can decide "is the structured schema the canonical source of
 * truth for this tool, or do we still need to bake a text description
 * into the system prompt?". When a model uses the Responses API (which
 * is RLHFed for native function-calling), the structured schema in
 * `tools:` is the canonical source — emitting the text description as
 * well doubles the same information in the context window.
 */
export const NATIVE_SCHEMA_TOOL_NAMES = new Set(
  OPENAI_FUNCTION_SCHEMAS.map(s => s.name.toLowerCase())
);

export default OPENAI_FUNCTION_SCHEMAS;
