{"version":3,"file":"prompt.mjs","names":["LogicalSearchOperator","FilterElement","Search","MULTI_VALUE_OPERATORS","NO_VALUE_OPERATORS","OPERATOR_FROM_BACKEND","OPERATORS_BY_TYPE","AiSearchSchemaContext","formatTodayForPrompt","today","Date","iso","toISOString","slice","weekday","toLocaleDateString","renderFilterFieldLines","ctx","maxListedSegments","filterFields","filter","f","path","split","length","map","type","join","renderColumnLines","columns","c","caps","orderable","push","aggregatable","visible","name","renderOperatorLines","Object","entries","ops","toIntentOp","logicalSearchOperator","Or","toIntentCondition","element","operator","searchOperator","stored","searchValue","values","has","value","field","searchField","aggregateFunction","describeCurrentSearchState","searchBody","parts","filterGroups","rootGroup","find","g","parentGroupId","opByGroupId","Map","groupId","rootOp","conditions","ReturnType","grouped","filterElements","bucket","get","set","groups","groupConditions","op","JSON","stringify","orderElements","o","sortColumn","direction","sortDirection","groupBy","groupProperty","aggregates","groupOperationList","property","operation","hidden","pageSize","STANDARD_INTENT_RULES"],"sources":["../../src/searchIntent/prompt.ts"],"sourcesContent":["import { LogicalSearchOperator } from '@univerus/udp-stencil-component-library';\nimport type {\n  FilterElement,\n  Search\n} from '@univerus/udp-stencil-component-library';\nimport {\n  MULTI_VALUE_OPERATORS,\n  NO_VALUE_OPERATORS,\n  OPERATOR_FROM_BACKEND,\n  OPERATORS_BY_TYPE\n} from './operators';\nimport type { AiSearchSchemaContext } from './types';\n\n// Prompt building blocks for search-intent copilots. Hosts own their full\n// system prompt (the preamble and domain rules are surface-specific copy);\n// these renderers cover the mechanical, schema-driven sections so every host\n// describes fields, operators, and live query state identically.\n\n/** \"2026-07-06 (Monday)\" — for resolving relative dates in user requests. */\nexport const formatTodayForPrompt = (): string => {\n  const today = new Date();\n  const iso = today.toISOString().slice(0, 10);\n  const weekday = today.toLocaleDateString('en-US', { weekday: 'long' });\n  return `${iso} (${weekday})`;\n};\n\n/**\n * One \"path (type)\" line per filterable field. Paths deeper than\n * maxListedSegments dot segments are omitted — hosts with deep association\n * paths describe those by construction rule instead, because the full list\n * is too large for the prompt.\n */\nexport const renderFilterFieldLines = (\n  ctx: AiSearchSchemaContext,\n  maxListedSegments: number = 2\n): string =>\n  (ctx.filterFields ?? [])\n    // A field with no path cannot be named in a filter, so it is not listed —\n    // hosts derive paths from their own schema source and can produce one.\n    .filter((f) => f?.path && f.path.split('.').length <= maxListedSegments)\n    .map((f) => `${f.path} (${f.type ?? 'default'})`)\n    .join('\\n');\n\n/** One \"name (type) [capabilities]\" line per column. */\nexport const renderColumnLines = (ctx: AiSearchSchemaContext): string =>\n  (ctx.columns ?? [])\n    .map((c) => {\n      const caps: string[] = [];\n      if (c.orderable) caps.push('sortable');\n      caps.push('groupable');\n      if (c.aggregatable) caps.push('aggregatable');\n      if (!c.visible) caps.push('currently hidden');\n      return `${c.name} (${c.type}) [${caps.join(', ')}]`;\n    })\n    .join('\\n');\n\n/** One \"type: op, op, ...\" line per normalized field type. */\nexport const renderOperatorLines = (): string =>\n  Object.entries(OPERATORS_BY_TYPE)\n    .filter(([type]) => type !== 'default')\n    .map(([type, ops]) => `${type}: ${ops.join(', ')}`)\n    .join('\\n');\n\nconst toIntentOp = (logicalSearchOperator: LogicalSearchOperator | undefined): 'and' | 'or' =>\n  logicalSearchOperator === LogicalSearchOperator.Or ? 'or' : 'and';\n\n/**\n * One stored filter element read back as an intent condition. Operators are\n * un-mapped from their backend constants (\"LIKE\" → \"contains\") and multi-value\n * operators get their comma-joined storage split back into `values`, so the\n * state the model is shown uses the exact vocabulary the propose tool expects\n * — it can echo any part of it back verbatim as a full-state replacement.\n */\nconst toIntentCondition = (element: FilterElement) => {\n  const operator =\n    OPERATOR_FROM_BACKEND[element.searchOperator] ??\n    element.searchOperator ??\n    null;\n  const stored = element.searchValue ?? '';\n  const values =\n    operator && MULTI_VALUE_OPERATORS.has(operator)\n      ? { values: stored === '' ? [] : stored.split(',') }\n      : operator && NO_VALUE_OPERATORS.has(operator)\n        ? {}\n        : { value: stored };\n  return {\n    field: element.searchField ?? null,\n    operator,\n    ...values,\n    ...(element.aggregateFunction\n      ? { aggregateFunction: element.aggregateFunction }\n      : {})\n  };\n};\n\n/**\n * Compact summary of the live query state, injected fresh on every request.\n * Rendered in the intent vocabulary (not raw Search field names) and shaped\n * like the propose tool's payload: filters are reassembled into root\n * conditions plus one entry per filter group, so nothing about the current\n * query — the AND/OR that joins it included — is missing from what the model\n * has to reproduce when it replaces a section.\n */\nexport const describeCurrentSearchState = (\n  searchBody: Search,\n  ctx: AiSearchSchemaContext\n): string => {\n  const parts: string[] = [];\n\n  // The root group is the parentless one; older/manually built states have no\n  // groups at all and are joined by the top-level logicalSearchOperator.\n  const filterGroups = searchBody.filterGroups ?? [];\n  const rootGroup = filterGroups.find((g) => g.parentGroupId == null);\n  const opByGroupId = new Map(\n    filterGroups.map((g) => [g.groupId, toIntentOp(g.logicalSearchOperator)])\n  );\n  const rootOp = rootGroup\n    ? toIntentOp(rootGroup.logicalSearchOperator)\n    : toIntentOp(searchBody.logicalSearchOperator);\n\n  const conditions: ReturnType<typeof toIntentCondition>[] = [];\n  const grouped = new Map<number, ReturnType<typeof toIntentCondition>[]>();\n  for (const element of searchBody.filterElements ?? []) {\n    const groupId = element.groupId;\n    if (groupId == null || groupId === rootGroup?.groupId) {\n      conditions.push(toIntentCondition(element));\n      continue;\n    }\n    const bucket = grouped.get(groupId) ?? [];\n    bucket.push(toIntentCondition(element));\n    grouped.set(groupId, bucket);\n  }\n  const groups = [...grouped].map(([groupId, groupConditions]) => ({\n    op: opByGroupId.get(groupId) ?? 'and',\n    conditions: groupConditions\n  }));\n  parts.push(`filters: ${JSON.stringify({ op: rootOp, conditions, groups })}`);\n\n  parts.push(\n    `sorts: ${JSON.stringify(\n      (searchBody.orderElements ?? []).map((o) => ({\n        field: o.sortColumn ?? null,\n        direction: o.sortDirection ?? 'ASC',\n        ...(o.aggregateFunction\n          ? { aggregateFunction: o.aggregateFunction }\n          : {})\n      }))\n    )}`\n  );\n\n  parts.push(\n    `grouping: ${JSON.stringify({\n      groupBy: searchBody.groupProperty ?? [],\n      aggregates: (searchBody.groupOperationList ?? []).map((g) => ({\n        field: g.property ?? null,\n        operation: g.operation ?? null\n      }))\n    })}`\n  );\n\n  const hidden = (ctx.columns ?? [])\n    .filter((c) => !c.visible)\n    .map((c) => c.name);\n  parts.push(`hiddenColumns: ${JSON.stringify(hidden)}`);\n  parts.push(`pageSize: ${searchBody.pageSize ?? '(host default)'}`);\n  return parts.join('\\n');\n};\n\n/**\n * The surface-agnostic rules every search-intent copilot needs (declarative\n * tri-state sections, capability gates, value formats, clarification). New\n * hosts start from these and append their own domain rules; hosts with\n * bespoke prompts may keep their own copy instead.\n */\nexport const STANDARD_INTENT_RULES: string[] = [\n  'Each section (filters, sorts, grouping, columns, pageSize) is declarative: pass null (or omit) to leave it unchanged, or pass the complete new state to replace it. When the user asks to add/remove one filter, return the FULL new filter state including everything that should be kept from the current state.',\n  'Only sort by columns marked [sortable]. Only aggregate columns marked [aggregatable]. Aggregates require at least one groupBy column.',\n  'When grouping is active, results can only be sorted by grouped columns — do not propose sorts on other columns alongside grouping.',\n  'Value formats: booleans \"true\"/\"false\"; dates \"YYYY-MM-DD\"; datetimes \"YYYY-MM-DDTHH:mm:ss\"; numbers as plain strings (\"42\"). between takes exactly 2 values (low, high); in/not_in take 1-500 values.',\n  '\"contains\" does substring matching — prefer it over equals for free-text name/description searches.',\n  \"Do not invent field paths, GUID values, or lookup IDs. If the user references a value that needs an ID you don't have (e.g. a person or status by name for a guid field), use ask_clarification.\",\n  \"If the user's request is ambiguous, use ask_clarification rather than guessing.\"\n];\n"],"mappings":"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"}