@open-mercato/core 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +13 -1
- package/dist/helpers/integration/api.js +29 -16
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/auth.js +11 -6
- package/dist/helpers/integration/auth.js.map +3 -3
- package/dist/modules/auth/commands/roles.js +9 -12
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/catalog/ai-agents-context.js +147 -0
- package/dist/modules/catalog/ai-agents-context.js.map +7 -0
- package/dist/modules/catalog/ai-agents.js +383 -0
- package/dist/modules/catalog/ai-agents.js.map +7 -0
- package/dist/modules/catalog/ai-tools/_shared.js +318 -0
- package/dist/modules/catalog/ai-tools/_shared.js.map +7 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js +391 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js +167 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js +120 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js +107 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js +429 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js +576 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js +208 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/products-pack.js +298 -0
- package/dist/modules/catalog/ai-tools/products-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js +57 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/types.js +10 -0
- package/dist/modules/catalog/ai-tools/types.js.map +7 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js +75 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools.js +28 -0
- package/dist/modules/catalog/ai-tools.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +466 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/page.js +7 -1
- package/dist/modules/catalog/backend/catalog/products/page.js.map +2 -2
- package/dist/modules/catalog/components/CatalogStatsCard.js +91 -0
- package/dist/modules/catalog/components/CatalogStatsCard.js.map +7 -0
- package/dist/modules/catalog/components/products/ProductsDataTable.js +23 -3
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/events.js +7 -4
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +59 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js +17 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
- package/dist/modules/catalog/widgets/injection-table.js +13 -1
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +94 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js +9 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/ai-agents-context.js +96 -0
- package/dist/modules/customers/ai-agents-context.js.map +7 -0
- package/dist/modules/customers/ai-agents.js +244 -0
- package/dist/modules/customers/ai-agents.js.map +7 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js +1015 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js +134 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/companies-pack.js +249 -0
- package/dist/modules/customers/ai-tools/companies-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/deals-pack.js +348 -0
- package/dist/modules/customers/ai-tools/deals-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/people-pack.js +261 -0
- package/dist/modules/customers/ai-tools/people-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/settings-pack.js +102 -0
- package/dist/modules/customers/ai-tools/settings-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/types.js +10 -0
- package/dist/modules/customers/ai-tools/types.js.map +7 -0
- package/dist/modules/customers/ai-tools.js +20 -0
- package/dist/modules/customers/ai-tools.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +469 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +117 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection-table.js +26 -0
- package/dist/modules/customers/widgets/injection-table.js.map +7 -0
- package/dist/modules/inbox_ops/ai-tools.js +4 -0
- package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
- package/dist/modules/inbox_ops/lib/llmProvider.js +52 -7
- package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
- package/dist/modules/notifications/setup.js +13 -0
- package/dist/modules/notifications/setup.js.map +7 -0
- package/jest.config.cjs +1 -0
- package/jest.setup.ts +18 -0
- package/package.json +5 -3
- package/src/helpers/integration/api.ts +38 -16
- package/src/helpers/integration/auth.ts +13 -6
- package/src/modules/auth/commands/roles.ts +10 -12
- package/src/modules/catalog/AGENTS.md +11 -0
- package/src/modules/catalog/ai-agents-context.ts +239 -0
- package/src/modules/catalog/ai-agents.ts +525 -0
- package/src/modules/catalog/ai-tools/_shared.ts +487 -0
- package/src/modules/catalog/ai-tools/authoring-pack.ts +600 -0
- package/src/modules/catalog/ai-tools/categories-pack.ts +192 -0
- package/src/modules/catalog/ai-tools/configuration-pack.ts +218 -0
- package/src/modules/catalog/ai-tools/media-tags-pack.ts +127 -0
- package/src/modules/catalog/ai-tools/merchandising-pack.ts +608 -0
- package/src/modules/catalog/ai-tools/mutation-pack.ts +761 -0
- package/src/modules/catalog/ai-tools/prices-offers-pack.ts +376 -0
- package/src/modules/catalog/ai-tools/products-pack.ts +387 -0
- package/src/modules/catalog/ai-tools/stats-pack.ts +84 -0
- package/src/modules/catalog/ai-tools/types.ts +81 -0
- package/src/modules/catalog/ai-tools/variants-pack.ts +147 -0
- package/src/modules/catalog/ai-tools.ts +78 -0
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +597 -0
- package/src/modules/catalog/backend/catalog/products/page.tsx +23 -2
- package/src/modules/catalog/components/CatalogStatsCard.tsx +118 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +54 -6
- package/src/modules/catalog/events.ts +7 -4
- package/src/modules/catalog/i18n/de.json +17 -0
- package/src/modules/catalog/i18n/en.json +17 -0
- package/src/modules/catalog/i18n/es.json +17 -0
- package/src/modules/catalog/i18n/pl.json +17 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +109 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.ts +29 -0
- package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
- package/src/modules/catalog/widgets/injection-table.ts +12 -0
- package/src/modules/customer_accounts/i18n/de.json +5 -0
- package/src/modules/customer_accounts/i18n/en.json +5 -0
- package/src/modules/customer_accounts/i18n/es.json +5 -0
- package/src/modules/customer_accounts/i18n/pl.json +5 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +136 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.ts +43 -0
- package/src/modules/customer_accounts/widgets/injection-table.ts +9 -0
- package/src/modules/customers/AGENTS.md +13 -0
- package/src/modules/customers/ai-agents-context.ts +150 -0
- package/src/modules/customers/ai-agents.ts +355 -0
- package/src/modules/customers/ai-tools/activities-tasks-pack.ts +1248 -0
- package/src/modules/customers/ai-tools/addresses-tags-pack.ts +145 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +362 -0
- package/src/modules/customers/ai-tools/deals-pack.ts +505 -0
- package/src/modules/customers/ai-tools/people-pack.ts +369 -0
- package/src/modules/customers/ai-tools/settings-pack.ts +121 -0
- package/src/modules/customers/ai-tools/types.ts +76 -0
- package/src/modules/customers/ai-tools.ts +34 -0
- package/src/modules/customers/i18n/de.json +25 -0
- package/src/modules/customers/i18n/en.json +25 -0
- package/src/modules/customers/i18n/es.json +25 -0
- package/src/modules/customers/i18n/pl.json +25 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +580 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +36 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +191 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.ts +37 -0
- package/src/modules/customers/widgets/injection-table.ts +41 -0
- package/src/modules/inbox_ops/ai-tools.ts +4 -0
- package/src/modules/inbox_ops/lib/llmProvider.ts +83 -7
- package/src/modules/notifications/setup.ts +11 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/customers/ai-tools/companies-pack.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `customers.list_companies` + `customers.get_company` (Phase 1 WS-C, Step 3.9).\n *\n * Phase 3a of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:\n * `customers.list_companies` is now an API-backed wrapper over\n * `GET /api/customers/companies`. Tool name, schema, requiredFeatures, and\n * output shape are unchanged.\n *\n * Phase 3c of the same spec migrates `customers.get_company` to a single\n * in-process call to `GET /api/customers/companies/<id>?include=...` over the\n * documented aggregate detail route. Tool name, schema, requiredFeatures, and\n * output shape are unchanged.\n */\nimport { z } from 'zod'\nimport { defineApiBackedAiTool } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool'\nimport {\n createAiApiOperationRunner,\n type AiApiOperationRequest,\n type AiToolExecutionContext,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'\nimport { assertTenantScope, type CustomersAiToolDefinition, type CustomersToolContext } from './types'\n\nconst listCompaniesInput = z\n .object({\n q: z.string().trim().optional().describe('Search text matched against display name / email / domain. Omit or leave empty to list all.'),\n limit: z.number().int().min(1).max(100).optional().describe('Maximum rows to return (default 50, max 100).'),\n offset: z.number().int().min(0).optional().describe('Number of rows to skip (default 0).'),\n tags: z.array(z.string().uuid()).optional().describe('Restrict to companies carrying at least one of these tag ids.'),\n })\n .passthrough()\n\ntype ListCompaniesInput = z.infer<typeof listCompaniesInput>\n\ntype ListCompaniesApiItem = {\n id?: string\n display_name?: string | null\n displayName?: string | null\n primary_email?: string | null\n primaryEmail?: string | null\n primary_phone?: string | null\n primaryPhone?: string | null\n status?: string | null\n lifecycle_stage?: string | null\n lifecycleStage?: string | null\n source?: string | null\n owner_user_id?: string | null\n ownerUserId?: string | null\n organization_id?: string | null\n organizationId?: string | null\n tenant_id?: string | null\n tenantId?: string | null\n domain?: string | null\n website_url?: string | null\n websiteUrl?: string | null\n industry?: string | null\n size_bucket?: string | null\n sizeBucket?: string | null\n created_at?: string | null\n createdAt?: string | null\n}\n\ntype ListCompaniesApiResponse = {\n items?: ListCompaniesApiItem[]\n total?: number\n}\n\ntype ListCompaniesOutput = {\n items: Array<Record<string, unknown>>\n total: number\n limit: number\n offset: number\n}\n\nconst listCompaniesTool = defineApiBackedAiTool<\n ListCompaniesInput,\n ListCompaniesApiResponse,\n ListCompaniesOutput\n>({\n name: 'customers.list_companies',\n displayName: 'List companies',\n description:\n 'Search / list companies for the caller tenant + organization. Returns { items, total, limit, offset }.',\n inputSchema: listCompaniesInput,\n requiredFeatures: ['customers.companies.view'],\n toOperation: (input, ctx) => {\n assertTenantScope(ctx as unknown as CustomersToolContext)\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const page = Math.floor(offset / limit) + 1\n\n const query: Record<string, string | number | boolean | null | undefined> = {\n page,\n pageSize: limit,\n }\n if (input.q?.trim()) query.search = input.q.trim()\n if (input.tags && input.tags.length > 0) query.tagIds = input.tags.join(',')\n\n const operation: AiApiOperationRequest = {\n method: 'GET',\n path: '/customers/companies',\n query,\n }\n return operation\n },\n mapResponse: (response, input) => {\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const data = (response.data ?? {}) as ListCompaniesApiResponse\n const rawItems: ListCompaniesApiItem[] = Array.isArray(data.items) ? data.items : []\n return {\n items: rawItems.map((row) => {\n const createdAtRaw = row.created_at ?? row.createdAt ?? null\n const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null\n return {\n id: row.id,\n displayName: row.display_name ?? row.displayName ?? null,\n primaryEmail: row.primary_email ?? row.primaryEmail ?? null,\n primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,\n status: row.status ?? null,\n lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,\n source: row.source ?? null,\n ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,\n organizationId: row.organization_id ?? row.organizationId ?? null,\n tenantId: row.tenant_id ?? row.tenantId ?? null,\n domain: row.domain ?? null,\n websiteUrl: row.website_url ?? row.websiteUrl ?? null,\n industry: row.industry ?? null,\n sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,\n createdAt,\n }\n }),\n total: typeof data.total === 'number' ? data.total : 0,\n limit,\n offset,\n }\n },\n}) as unknown as CustomersAiToolDefinition\n\nconst getCompanyInput = z.object({\n companyId: z.string().uuid().describe('Company entity id (UUID).'),\n includeRelated: z\n .boolean()\n .optional()\n .describe('When true, include notes, activities, deals, people, addresses, tasks, and tags (each capped at 100).'),\n})\n\ntype GetCompanyInput = z.infer<typeof getCompanyInput>\n\nfunction toIsoCompany(value: unknown): string | null {\n if (!value) return null\n const dt = value instanceof Date ? value : new Date(String(value))\n if (Number.isNaN(dt.getTime())) return null\n return dt.toISOString()\n}\n\nconst getCompanyTool: CustomersAiToolDefinition = {\n name: 'customers.get_company',\n displayName: 'Get company',\n description:\n 'Fetch a company customer record by id with profile fields and (optionally) notes, activities, deals, people, addresses, tasks, tags, and custom fields. Returns { found: false } when outside tenant/org scope.',\n inputSchema: getCompanyInput,\n requiredFeatures: ['customers.companies.view'],\n tags: ['read', 'customers'],\n handler: async (rawInput, ctx) => {\n const { tenantId: _tenantId } = assertTenantScope(ctx)\n void _tenantId\n const input: GetCompanyInput = getCompanyInput.parse(rawInput)\n const includeRelated = !!input.includeRelated\n\n const operation: AiApiOperationRequest = {\n method: 'GET',\n path: `/customers/companies/${input.companyId}`,\n }\n if (includeRelated) {\n operation.query = {\n include: 'addresses,comments,activities,interactions,deals,todos,people',\n }\n }\n\n const runner = createAiApiOperationRunner(ctx as unknown as AiToolExecutionContext)\n const response = await runner.run<Record<string, unknown>>(operation)\n if (!response.success) {\n if (response.statusCode === 404 || response.statusCode === 403) {\n return { found: false as const, companyId: input.companyId }\n }\n throw new Error(response.error ?? `Failed to fetch company ${input.companyId}`)\n }\n const data = (response.data ?? {}) as Record<string, unknown>\n const companyRow = (data.company ?? null) as Record<string, unknown> | null\n if (!companyRow) {\n return { found: false as const, companyId: input.companyId }\n }\n const profileRow = (data.profile ?? null) as Record<string, unknown> | null\n const customFields = (data.customFields ?? {}) as Record<string, unknown>\n\n let related: Record<string, unknown> | null = null\n if (includeRelated) {\n const addresses = Array.isArray(data.addresses) ? (data.addresses as Array<Record<string, unknown>>) : []\n const activities = Array.isArray(data.activities) ? (data.activities as Array<Record<string, unknown>>) : []\n const notes = Array.isArray(data.comments) ? (data.comments as Array<Record<string, unknown>>) : []\n const todos = Array.isArray(data.todos) ? (data.todos as Array<Record<string, unknown>>) : []\n const interactions = Array.isArray(data.interactions) ? (data.interactions as Array<Record<string, unknown>>) : []\n const tagsRows = Array.isArray(data.tags) ? (data.tags as Array<Record<string, unknown>>) : []\n const dealsRows = Array.isArray(data.deals) ? (data.deals as Array<Record<string, unknown>>) : []\n const peopleRows = Array.isArray(data.people) ? (data.people as Array<Record<string, unknown>>) : []\n related = {\n addresses: addresses.map((address) => ({\n id: address.id,\n name: address.name ?? null,\n purpose: address.purpose ?? null,\n addressLine1: address.addressLine1 ?? null,\n addressLine2: address.addressLine2 ?? null,\n city: address.city ?? null,\n region: address.region ?? null,\n postalCode: address.postalCode ?? null,\n country: address.country ?? null,\n isPrimary: !!address.isPrimary,\n })),\n activities: activities.map((activity) => ({\n id: activity.id,\n activityType: activity.activityType,\n subject: activity.subject ?? null,\n body: activity.body ?? null,\n occurredAt: toIsoCompany(activity.occurredAt),\n createdAt: toIsoCompany(activity.createdAt),\n })),\n notes: notes.map((comment) => ({\n id: comment.id,\n body: comment.body,\n authorUserId: comment.authorUserId ?? null,\n createdAt: toIsoCompany(comment.createdAt),\n })),\n tasks: todos.map((task) => ({\n id: task.id,\n todoId: task.todoId ?? task.id,\n todoSource: task.todoSource ?? null,\n createdAt: toIsoCompany(task.createdAt),\n })),\n interactions: interactions.map((interaction) => ({\n id: interaction.id,\n interactionType: interaction.interactionType,\n title: interaction.title ?? null,\n status: interaction.status,\n scheduledAt: toIsoCompany(interaction.scheduledAt),\n occurredAt: toIsoCompany(interaction.occurredAt),\n })),\n tags: tagsRows\n .map((tag) => {\n if (!tag || typeof tag !== 'object') return null\n const id = typeof tag.id === 'string' ? tag.id : null\n const label = typeof tag.label === 'string' ? tag.label : null\n if (!id || !label) return null\n const slug = typeof tag.slug === 'string' ? tag.slug : label\n const color = typeof tag.color === 'string' ? tag.color : null\n return { id, slug, label, color }\n })\n .filter(\n (entry): entry is { id: string; slug: string; label: string; color: string | null } =>\n entry !== null,\n ),\n deals: dealsRows\n .map((deal) => {\n if (!deal || typeof deal !== 'object') return null\n const id = typeof deal.id === 'string' ? deal.id : null\n if (!id) return null\n return {\n id,\n title: typeof deal.title === 'string' ? deal.title : '',\n status: typeof deal.status === 'string' ? deal.status : null,\n pipelineStageId:\n typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,\n valueAmount:\n typeof deal.valueAmount === 'string'\n ? deal.valueAmount\n : deal.valueAmount === null || deal.valueAmount === undefined\n ? null\n : String(deal.valueAmount),\n valueCurrency:\n typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,\n }\n })\n .filter(\n (\n value,\n ): value is {\n id: string\n title: string\n status: string | null\n pipelineStageId: string | null\n valueAmount: string | null\n valueCurrency: string | null\n } => value !== null,\n ),\n people: peopleRows\n .map((person) => {\n if (!person || typeof person !== 'object') return null\n const id = typeof person.id === 'string' ? person.id : null\n const displayName = typeof person.displayName === 'string' ? person.displayName : null\n if (!id || !displayName) return null\n return {\n id,\n displayName,\n primaryEmail:\n typeof person.primaryEmail === 'string' ? person.primaryEmail : null,\n primaryPhone:\n typeof person.primaryPhone === 'string' ? person.primaryPhone : null,\n jobTitle: typeof person.jobTitle === 'string' ? person.jobTitle : null,\n department: typeof person.department === 'string' ? person.department : null,\n }\n })\n .filter(\n (\n value,\n ): value is {\n id: string\n displayName: string\n primaryEmail: string | null\n primaryPhone: string | null\n jobTitle: string | null\n department: string | null\n } => value !== null,\n ),\n }\n }\n return {\n found: true as const,\n company: {\n id: companyRow.id,\n displayName: companyRow.displayName ?? null,\n description: companyRow.description ?? null,\n primaryEmail: companyRow.primaryEmail ?? null,\n primaryPhone: companyRow.primaryPhone ?? null,\n status: companyRow.status ?? null,\n lifecycleStage: companyRow.lifecycleStage ?? null,\n source: companyRow.source ?? null,\n ownerUserId: companyRow.ownerUserId ?? null,\n organizationId: companyRow.organizationId ?? null,\n tenantId: companyRow.tenantId ?? null,\n createdAt: toIsoCompany(companyRow.createdAt),\n updatedAt: toIsoCompany(companyRow.updatedAt),\n },\n profile: profileRow\n ? {\n id: profileRow.id,\n legalName: profileRow.legalName ?? null,\n brandName: profileRow.brandName ?? null,\n domain: profileRow.domain ?? null,\n websiteUrl: profileRow.websiteUrl ?? null,\n industry: profileRow.industry ?? null,\n sizeBucket: profileRow.sizeBucket ?? null,\n annualRevenue: profileRow.annualRevenue ?? null,\n }\n : null,\n customFields,\n related,\n }\n },\n}\n\nexport const companiesAiTools: CustomersAiToolDefinition[] = [listCompaniesTool, getCompanyTool]\n\nexport default companiesAiTools\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineApiBackedAiTool } from "@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool";
|
|
3
|
+
import {
|
|
4
|
+
createAiApiOperationRunner
|
|
5
|
+
} from "@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner";
|
|
6
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
7
|
+
import {
|
|
8
|
+
CustomerDeal,
|
|
9
|
+
CustomerPipelineStage
|
|
10
|
+
} from "../data/entities.js";
|
|
11
|
+
import {
|
|
12
|
+
assertTenantScope
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
function resolveEm(ctx) {
|
|
15
|
+
return ctx.container.resolve("em");
|
|
16
|
+
}
|
|
17
|
+
function buildScope(ctx, tenantId) {
|
|
18
|
+
return { tenantId, organizationId: ctx.organizationId };
|
|
19
|
+
}
|
|
20
|
+
const listDealsInput = z.object({
|
|
21
|
+
q: z.string().trim().optional().describe("Search text matched against deal title / description. Omit or leave empty to list all."),
|
|
22
|
+
limit: z.number().int().min(1).max(100).optional().describe("Maximum rows to return (default 50, max 100)."),
|
|
23
|
+
offset: z.number().int().min(0).optional().describe("Number of rows to skip (default 0)."),
|
|
24
|
+
personId: z.string().uuid().optional().describe("Return only deals linked to this person entity id."),
|
|
25
|
+
companyId: z.string().uuid().optional().describe("Return only deals linked to this company entity id."),
|
|
26
|
+
pipelineStageId: z.string().uuid().optional().describe("Return only deals at this pipeline stage."),
|
|
27
|
+
status: z.string().optional().describe('Filter by deal status (e.g. "open", "won", "lost").')
|
|
28
|
+
}).passthrough();
|
|
29
|
+
const listDealsTool = defineApiBackedAiTool({
|
|
30
|
+
name: "customers.list_deals",
|
|
31
|
+
displayName: "List deals",
|
|
32
|
+
description: "Search / list deals for the caller tenant + organization. Optional filters include linked person / company / pipeline stage.",
|
|
33
|
+
inputSchema: listDealsInput,
|
|
34
|
+
requiredFeatures: ["customers.deals.view"],
|
|
35
|
+
toOperation: (input, ctx) => {
|
|
36
|
+
assertTenantScope(ctx);
|
|
37
|
+
const limit = input.limit ?? 50;
|
|
38
|
+
const offset = input.offset ?? 0;
|
|
39
|
+
const page = Math.floor(offset / limit) + 1;
|
|
40
|
+
const query = {
|
|
41
|
+
page,
|
|
42
|
+
pageSize: limit
|
|
43
|
+
};
|
|
44
|
+
if (input.q?.trim()) query.search = input.q.trim();
|
|
45
|
+
if (input.personId) query.personId = input.personId;
|
|
46
|
+
if (input.companyId) query.companyId = input.companyId;
|
|
47
|
+
if (input.pipelineStageId) query.pipelineStageId = input.pipelineStageId;
|
|
48
|
+
if (input.status) query.status = input.status;
|
|
49
|
+
const operation = {
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: "/customers/deals",
|
|
52
|
+
query
|
|
53
|
+
};
|
|
54
|
+
return operation;
|
|
55
|
+
},
|
|
56
|
+
mapResponse: (response, input) => {
|
|
57
|
+
const limit = input.limit ?? 50;
|
|
58
|
+
const offset = input.offset ?? 0;
|
|
59
|
+
const data = response.data ?? {};
|
|
60
|
+
const rawItems = Array.isArray(data.items) ? data.items : [];
|
|
61
|
+
return {
|
|
62
|
+
items: rawItems.map((row) => {
|
|
63
|
+
const expectedCloseRaw = row.expected_close_at ?? row.expectedCloseAt ?? null;
|
|
64
|
+
const expectedCloseAt = expectedCloseRaw ? new Date(String(expectedCloseRaw)).toISOString() : null;
|
|
65
|
+
const createdAtRaw = row.created_at ?? row.createdAt ?? null;
|
|
66
|
+
const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null;
|
|
67
|
+
return {
|
|
68
|
+
id: row.id,
|
|
69
|
+
title: row.title ?? null,
|
|
70
|
+
description: row.description ?? null,
|
|
71
|
+
status: row.status ?? null,
|
|
72
|
+
pipelineId: row.pipeline_id ?? row.pipelineId ?? null,
|
|
73
|
+
pipelineStageId: row.pipeline_stage_id ?? row.pipelineStageId ?? null,
|
|
74
|
+
valueAmount: row.value_amount ?? row.valueAmount ?? null,
|
|
75
|
+
valueCurrency: row.value_currency ?? row.valueCurrency ?? null,
|
|
76
|
+
probability: row.probability ?? null,
|
|
77
|
+
ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
|
|
78
|
+
expectedCloseAt,
|
|
79
|
+
source: row.source ?? null,
|
|
80
|
+
organizationId: row.organization_id ?? row.organizationId ?? null,
|
|
81
|
+
tenantId: row.tenant_id ?? row.tenantId ?? null,
|
|
82
|
+
createdAt
|
|
83
|
+
};
|
|
84
|
+
}),
|
|
85
|
+
total: typeof data.total === "number" ? data.total : 0,
|
|
86
|
+
limit,
|
|
87
|
+
offset
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const getDealInput = z.object({
|
|
92
|
+
dealId: z.string().uuid().describe("Deal id (UUID)."),
|
|
93
|
+
includeRelated: z.boolean().optional().describe("When true, include notes, activities, linked people and companies (each capped at 100).")
|
|
94
|
+
});
|
|
95
|
+
function toIsoDeal(value) {
|
|
96
|
+
if (!value) return null;
|
|
97
|
+
const dt = value instanceof Date ? value : new Date(String(value));
|
|
98
|
+
if (Number.isNaN(dt.getTime())) return null;
|
|
99
|
+
return dt.toISOString();
|
|
100
|
+
}
|
|
101
|
+
const getDealTool = {
|
|
102
|
+
name: "customers.get_deal",
|
|
103
|
+
displayName: "Get deal",
|
|
104
|
+
description: "Fetch a deal by id with fields and (optionally) notes, activities, linked people, and linked companies. Returns { found: false } when outside tenant/org scope.",
|
|
105
|
+
inputSchema: getDealInput,
|
|
106
|
+
requiredFeatures: ["customers.deals.view"],
|
|
107
|
+
tags: ["read", "customers"],
|
|
108
|
+
handler: async (rawInput, ctx) => {
|
|
109
|
+
const { tenantId: _tenantId } = assertTenantScope(ctx);
|
|
110
|
+
void _tenantId;
|
|
111
|
+
const input = getDealInput.parse(rawInput);
|
|
112
|
+
const includeRelated = !!input.includeRelated;
|
|
113
|
+
const runner = createAiApiOperationRunner(ctx);
|
|
114
|
+
const detailResponse = await runner.run({
|
|
115
|
+
method: "GET",
|
|
116
|
+
path: `/customers/deals/${input.dealId}`
|
|
117
|
+
});
|
|
118
|
+
if (!detailResponse.success) {
|
|
119
|
+
if (detailResponse.statusCode === 404 || detailResponse.statusCode === 403) {
|
|
120
|
+
return { found: false, dealId: input.dealId };
|
|
121
|
+
}
|
|
122
|
+
throw new Error(detailResponse.error ?? `Failed to fetch deal ${input.dealId}`);
|
|
123
|
+
}
|
|
124
|
+
const detail = detailResponse.data ?? {};
|
|
125
|
+
const dealRow = detail.deal ?? null;
|
|
126
|
+
if (!dealRow) {
|
|
127
|
+
return { found: false, dealId: input.dealId };
|
|
128
|
+
}
|
|
129
|
+
const customFields = detail.customFields ?? {};
|
|
130
|
+
const peopleRows = Array.isArray(detail.people) ? detail.people : [];
|
|
131
|
+
const companiesRows = Array.isArray(detail.companies) ? detail.companies : [];
|
|
132
|
+
let related = null;
|
|
133
|
+
if (includeRelated) {
|
|
134
|
+
const [activitiesResponse, commentsResponse] = await Promise.all([
|
|
135
|
+
runner.run({
|
|
136
|
+
method: "GET",
|
|
137
|
+
path: "/customers/activities",
|
|
138
|
+
query: { dealId: input.dealId, page: 1, pageSize: 100, sortField: "occurredAt", sortDir: "desc" }
|
|
139
|
+
}),
|
|
140
|
+
runner.run({
|
|
141
|
+
method: "GET",
|
|
142
|
+
path: "/customers/comments",
|
|
143
|
+
query: { dealId: input.dealId, page: 1, pageSize: 100 }
|
|
144
|
+
})
|
|
145
|
+
]);
|
|
146
|
+
const activities = activitiesResponse.success && Array.isArray(activitiesResponse.data?.items) ? activitiesResponse.data.items : [];
|
|
147
|
+
const comments = commentsResponse.success && Array.isArray(commentsResponse.data?.items) ? commentsResponse.data.items : [];
|
|
148
|
+
related = {
|
|
149
|
+
activities: activities.map((activity) => ({
|
|
150
|
+
id: activity.id,
|
|
151
|
+
activityType: activity.activityType ?? activity.activity_type ?? null,
|
|
152
|
+
subject: activity.subject ?? null,
|
|
153
|
+
body: activity.body ?? null,
|
|
154
|
+
occurredAt: toIsoDeal(activity.occurredAt ?? activity.occurred_at),
|
|
155
|
+
createdAt: toIsoDeal(activity.createdAt ?? activity.created_at)
|
|
156
|
+
})),
|
|
157
|
+
notes: comments.map((comment) => ({
|
|
158
|
+
id: comment.id,
|
|
159
|
+
body: comment.body,
|
|
160
|
+
authorUserId: comment.authorUserId ?? comment.author_user_id ?? null,
|
|
161
|
+
createdAt: toIsoDeal(comment.createdAt ?? comment.created_at)
|
|
162
|
+
})),
|
|
163
|
+
people: peopleRows.map((person) => {
|
|
164
|
+
if (!person || typeof person !== "object") return null;
|
|
165
|
+
const id = typeof person.id === "string" ? person.id : null;
|
|
166
|
+
if (!id) return null;
|
|
167
|
+
const subtitle = typeof person.subtitle === "string" ? person.subtitle : null;
|
|
168
|
+
const label = typeof person.label === "string" ? person.label : "";
|
|
169
|
+
const entry = {
|
|
170
|
+
id,
|
|
171
|
+
displayName: label,
|
|
172
|
+
primaryEmail: subtitle && subtitle.includes("@") ? subtitle : null,
|
|
173
|
+
primaryPhone: subtitle && !subtitle.includes("@") ? subtitle : null,
|
|
174
|
+
participantRole: null
|
|
175
|
+
};
|
|
176
|
+
return entry;
|
|
177
|
+
}).filter(
|
|
178
|
+
(value) => value !== null
|
|
179
|
+
),
|
|
180
|
+
companies: companiesRows.map((company) => {
|
|
181
|
+
if (!company || typeof company !== "object") return null;
|
|
182
|
+
const id = typeof company.id === "string" ? company.id : null;
|
|
183
|
+
if (!id) return null;
|
|
184
|
+
const label = typeof company.label === "string" ? company.label : "";
|
|
185
|
+
const entry = {
|
|
186
|
+
id,
|
|
187
|
+
displayName: label,
|
|
188
|
+
primaryEmail: null,
|
|
189
|
+
primaryPhone: null
|
|
190
|
+
};
|
|
191
|
+
return entry;
|
|
192
|
+
}).filter(
|
|
193
|
+
(value) => value !== null
|
|
194
|
+
)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
found: true,
|
|
199
|
+
deal: {
|
|
200
|
+
id: dealRow.id,
|
|
201
|
+
title: typeof dealRow.title === "string" ? dealRow.title : "",
|
|
202
|
+
description: dealRow.description ?? null,
|
|
203
|
+
status: dealRow.status ?? null,
|
|
204
|
+
pipelineId: dealRow.pipelineId ?? null,
|
|
205
|
+
pipelineStageId: dealRow.pipelineStageId ?? null,
|
|
206
|
+
valueAmount: dealRow.valueAmount ?? null,
|
|
207
|
+
valueCurrency: dealRow.valueCurrency ?? null,
|
|
208
|
+
probability: dealRow.probability ?? null,
|
|
209
|
+
ownerUserId: dealRow.ownerUserId ?? null,
|
|
210
|
+
expectedCloseAt: toIsoDeal(dealRow.expectedCloseAt),
|
|
211
|
+
source: dealRow.source ?? null,
|
|
212
|
+
organizationId: dealRow.organizationId ?? null,
|
|
213
|
+
tenantId: dealRow.tenantId ?? null,
|
|
214
|
+
createdAt: toIsoDeal(dealRow.createdAt),
|
|
215
|
+
updatedAt: toIsoDeal(dealRow.updatedAt)
|
|
216
|
+
},
|
|
217
|
+
customFields,
|
|
218
|
+
related
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const blankToUndefined = (value) => {
|
|
223
|
+
if (typeof value !== "string") return value;
|
|
224
|
+
const trimmed = value.trim();
|
|
225
|
+
return trimmed.length === 0 ? void 0 : trimmed;
|
|
226
|
+
};
|
|
227
|
+
const updateDealStageInput = z.object({
|
|
228
|
+
dealId: z.string().uuid().describe("Deal id (UUID) to update."),
|
|
229
|
+
toPipelineStageId: z.preprocess(blankToUndefined, z.string().uuid().optional()).describe("Target pipeline stage id (UUID). Preferred \u2014 tenant-scoped stage record."),
|
|
230
|
+
toStage: z.preprocess(blankToUndefined, z.string().min(1).max(50).optional()).describe(
|
|
231
|
+
'Target status slug (e.g. "open", "won", "lost"). Used when the deal does not belong to a managed pipeline.'
|
|
232
|
+
)
|
|
233
|
+
}).refine(
|
|
234
|
+
(value) => Boolean(value.toPipelineStageId) !== Boolean(value.toStage),
|
|
235
|
+
{
|
|
236
|
+
message: "Provide exactly one of toPipelineStageId or toStage.",
|
|
237
|
+
path: ["toPipelineStageId"]
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
function recordVersionFromUpdatedAt(updatedAt) {
|
|
241
|
+
if (!updatedAt) return null;
|
|
242
|
+
const value = updatedAt instanceof Date ? updatedAt : new Date(updatedAt);
|
|
243
|
+
if (Number.isNaN(value.getTime())) return null;
|
|
244
|
+
return value.toISOString();
|
|
245
|
+
}
|
|
246
|
+
async function loadDealWithStage(em, ctx, tenantId, dealId) {
|
|
247
|
+
const where = { id: dealId, tenantId, deletedAt: null };
|
|
248
|
+
if (ctx.organizationId) where.organizationId = ctx.organizationId;
|
|
249
|
+
const deal = await findOneWithDecryption(
|
|
250
|
+
em,
|
|
251
|
+
CustomerDeal,
|
|
252
|
+
where,
|
|
253
|
+
void 0,
|
|
254
|
+
buildScope(ctx, tenantId)
|
|
255
|
+
);
|
|
256
|
+
if (!deal || deal.tenantId !== tenantId) return null;
|
|
257
|
+
if (ctx.organizationId && deal.organizationId !== ctx.organizationId) return null;
|
|
258
|
+
return deal;
|
|
259
|
+
}
|
|
260
|
+
const updateDealStageTool = {
|
|
261
|
+
name: "customers.update_deal_stage",
|
|
262
|
+
displayName: "Update deal stage",
|
|
263
|
+
description: 'Move a deal to a different pipeline stage (by stage id) or change its top-level status (e.g. "open", "won", "lost"). Mutation tool \u2014 flows through the AI pending-action approval gate.',
|
|
264
|
+
inputSchema: updateDealStageInput,
|
|
265
|
+
requiredFeatures: ["customers.deals.manage"],
|
|
266
|
+
tags: ["write", "customers"],
|
|
267
|
+
isMutation: true,
|
|
268
|
+
loadBeforeRecord: async (rawInput, ctx) => {
|
|
269
|
+
const { tenantId } = assertTenantScope(ctx);
|
|
270
|
+
const input = updateDealStageInput.parse(rawInput);
|
|
271
|
+
const em = resolveEm(ctx);
|
|
272
|
+
const deal = await loadDealWithStage(em, ctx, tenantId, input.dealId);
|
|
273
|
+
if (!deal) return null;
|
|
274
|
+
return {
|
|
275
|
+
recordId: deal.id,
|
|
276
|
+
entityType: "customers.deal",
|
|
277
|
+
recordVersion: recordVersionFromUpdatedAt(deal.updatedAt),
|
|
278
|
+
before: {
|
|
279
|
+
status: deal.status ?? null,
|
|
280
|
+
pipelineStage: deal.pipelineStage ?? null,
|
|
281
|
+
pipelineStageId: deal.pipelineStageId ?? null
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
handler: async (rawInput, ctx) => {
|
|
286
|
+
const { tenantId } = assertTenantScope(ctx);
|
|
287
|
+
const input = updateDealStageInput.parse(rawInput);
|
|
288
|
+
const em = resolveEm(ctx);
|
|
289
|
+
const deal = await loadDealWithStage(em, ctx, tenantId, input.dealId);
|
|
290
|
+
if (!deal) {
|
|
291
|
+
throw new Error(`Deal "${input.dealId}" is not accessible to the caller.`);
|
|
292
|
+
}
|
|
293
|
+
const organizationId = deal.organizationId;
|
|
294
|
+
if (!organizationId) {
|
|
295
|
+
throw new Error(`Deal "${input.dealId}" has no organization scope.`);
|
|
296
|
+
}
|
|
297
|
+
const before = {
|
|
298
|
+
status: deal.status ?? null,
|
|
299
|
+
pipelineStage: deal.pipelineStage ?? null,
|
|
300
|
+
pipelineStageId: deal.pipelineStageId ?? null
|
|
301
|
+
};
|
|
302
|
+
const body = {
|
|
303
|
+
id: deal.id,
|
|
304
|
+
tenantId,
|
|
305
|
+
organizationId
|
|
306
|
+
};
|
|
307
|
+
if (input.toPipelineStageId) {
|
|
308
|
+
const stage = await em.findOne(CustomerPipelineStage, {
|
|
309
|
+
id: input.toPipelineStageId,
|
|
310
|
+
tenantId,
|
|
311
|
+
organizationId
|
|
312
|
+
});
|
|
313
|
+
if (!stage) {
|
|
314
|
+
throw new Error("Pipeline stage not found.");
|
|
315
|
+
}
|
|
316
|
+
body.pipelineStageId = input.toPipelineStageId;
|
|
317
|
+
} else if (input.toStage) {
|
|
318
|
+
body.status = input.toStage;
|
|
319
|
+
}
|
|
320
|
+
const runner = createAiApiOperationRunner(ctx);
|
|
321
|
+
const response = await runner.run({
|
|
322
|
+
method: "PUT",
|
|
323
|
+
path: "/customers/deals",
|
|
324
|
+
body
|
|
325
|
+
});
|
|
326
|
+
if (!response.success) {
|
|
327
|
+
throw new Error(response.error ?? `Failed to update deal "${deal.id}"`);
|
|
328
|
+
}
|
|
329
|
+
const after = await loadDealWithStage(em, ctx, tenantId, deal.id);
|
|
330
|
+
return {
|
|
331
|
+
recordId: deal.id,
|
|
332
|
+
commandName: "customers.deals.update",
|
|
333
|
+
before,
|
|
334
|
+
after: after ? {
|
|
335
|
+
status: after.status ?? null,
|
|
336
|
+
pipelineStage: after.pipelineStage ?? null,
|
|
337
|
+
pipelineStageId: after.pipelineStageId ?? null
|
|
338
|
+
} : null
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const dealsAiTools = [listDealsTool, getDealTool, updateDealStageTool];
|
|
343
|
+
var deals_pack_default = dealsAiTools;
|
|
344
|
+
export {
|
|
345
|
+
dealsAiTools,
|
|
346
|
+
deals_pack_default as default
|
|
347
|
+
};
|
|
348
|
+
//# sourceMappingURL=deals-pack.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/customers/ai-tools/deals-pack.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `customers.list_deals` + `customers.get_deal` (Phase 1 WS-C, Step 3.9).\n * `customers.update_deal_stage` mutation tool (Phase 3 WS-C, Step 5.13).\n *\n * Phase 3a of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:\n * `customers.list_deals` is now an API-backed wrapper over\n * `GET /api/customers/deals`. Tool name, schema, requiredFeatures, and output\n * shape are unchanged.\n *\n * Phase 3c of the same spec migrates `customers.get_deal` to the documented\n * aggregate detail route. The handler issues 1 call without `includeRelated`\n * (`GET /customers/deals/<id>`) and 3 bounded calls with `includeRelated`\n * (deal detail + activities + comments by `dealId`). The 3-call cap matches\n * the spec's residual N+1 budget; deeper aggregation can earn a first-class\n * API later without touching the AI surface.\n */\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { defineApiBackedAiTool } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool'\nimport {\n createAiApiOperationRunner,\n type AiApiOperationRequest,\n type AiToolExecutionContext,\n} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n CustomerDeal,\n CustomerPipelineStage,\n} from '../data/entities'\nimport {\n assertTenantScope,\n type CustomersAiToolDefinition,\n type CustomersToolContext,\n type CustomersToolLoadBeforeSingleRecord,\n} from './types'\n\nfunction resolveEm(ctx: CustomersToolContext | AiToolExecutionContext): EntityManager {\n return ctx.container.resolve<EntityManager>('em')\n}\n\nfunction buildScope(ctx: CustomersToolContext | AiToolExecutionContext, tenantId: string) {\n return { tenantId, organizationId: ctx.organizationId }\n}\n\nconst listDealsInput = z\n .object({\n q: z.string().trim().optional().describe('Search text matched against deal title / description. Omit or leave empty to list all.'),\n limit: z.number().int().min(1).max(100).optional().describe('Maximum rows to return (default 50, max 100).'),\n offset: z.number().int().min(0).optional().describe('Number of rows to skip (default 0).'),\n personId: z.string().uuid().optional().describe('Return only deals linked to this person entity id.'),\n companyId: z.string().uuid().optional().describe('Return only deals linked to this company entity id.'),\n pipelineStageId: z.string().uuid().optional().describe('Return only deals at this pipeline stage.'),\n status: z.string().optional().describe('Filter by deal status (e.g. \"open\", \"won\", \"lost\").'),\n })\n .passthrough()\n\ntype ListDealsInput = z.infer<typeof listDealsInput>\n\ntype ListDealsApiItem = {\n id?: string\n title?: string | null\n description?: string | null\n status?: string | null\n pipeline_id?: string | null\n pipelineId?: string | null\n pipeline_stage_id?: string | null\n pipelineStageId?: string | null\n value_amount?: string | number | null\n valueAmount?: string | number | null\n value_currency?: string | null\n valueCurrency?: string | null\n probability?: number | null\n owner_user_id?: string | null\n ownerUserId?: string | null\n expected_close_at?: string | null\n expectedCloseAt?: string | null\n source?: string | null\n organization_id?: string | null\n organizationId?: string | null\n tenant_id?: string | null\n tenantId?: string | null\n created_at?: string | null\n createdAt?: string | null\n}\n\ntype ListDealsApiResponse = {\n items?: ListDealsApiItem[]\n total?: number\n}\n\ntype ListDealsOutput = {\n items: Array<Record<string, unknown>>\n total: number\n limit: number\n offset: number\n}\n\nconst listDealsTool = defineApiBackedAiTool<ListDealsInput, ListDealsApiResponse, ListDealsOutput>({\n name: 'customers.list_deals',\n displayName: 'List deals',\n description:\n 'Search / list deals for the caller tenant + organization. Optional filters include linked person / company / pipeline stage.',\n inputSchema: listDealsInput,\n requiredFeatures: ['customers.deals.view'],\n toOperation: (input, ctx) => {\n assertTenantScope(ctx as unknown as CustomersToolContext)\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const page = Math.floor(offset / limit) + 1\n\n const query: Record<string, string | number | boolean | null | undefined> = {\n page,\n pageSize: limit,\n }\n if (input.q?.trim()) query.search = input.q.trim()\n if (input.personId) query.personId = input.personId\n if (input.companyId) query.companyId = input.companyId\n if (input.pipelineStageId) query.pipelineStageId = input.pipelineStageId\n if (input.status) query.status = input.status\n\n const operation: AiApiOperationRequest = {\n method: 'GET',\n path: '/customers/deals',\n query,\n }\n return operation\n },\n mapResponse: (response, input) => {\n const limit = input.limit ?? 50\n const offset = input.offset ?? 0\n const data = (response.data ?? {}) as ListDealsApiResponse\n const rawItems: ListDealsApiItem[] = Array.isArray(data.items) ? data.items : []\n return {\n items: rawItems.map((row) => {\n const expectedCloseRaw = row.expected_close_at ?? row.expectedCloseAt ?? null\n const expectedCloseAt = expectedCloseRaw ? new Date(String(expectedCloseRaw)).toISOString() : null\n const createdAtRaw = row.created_at ?? row.createdAt ?? null\n const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null\n return {\n id: row.id,\n title: row.title ?? null,\n description: row.description ?? null,\n status: row.status ?? null,\n pipelineId: row.pipeline_id ?? row.pipelineId ?? null,\n pipelineStageId: row.pipeline_stage_id ?? row.pipelineStageId ?? null,\n valueAmount: row.value_amount ?? row.valueAmount ?? null,\n valueCurrency: row.value_currency ?? row.valueCurrency ?? null,\n probability: row.probability ?? null,\n ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,\n expectedCloseAt,\n source: row.source ?? null,\n organizationId: row.organization_id ?? row.organizationId ?? null,\n tenantId: row.tenant_id ?? row.tenantId ?? null,\n createdAt,\n }\n }),\n total: typeof data.total === 'number' ? data.total : 0,\n limit,\n offset,\n }\n },\n}) as unknown as CustomersAiToolDefinition\n\nconst getDealInput = z.object({\n dealId: z.string().uuid().describe('Deal id (UUID).'),\n includeRelated: z\n .boolean()\n .optional()\n .describe('When true, include notes, activities, linked people and companies (each capped at 100).'),\n})\n\ntype GetDealInput = z.infer<typeof getDealInput>\n\nfunction toIsoDeal(value: unknown): string | null {\n if (!value) return null\n const dt = value instanceof Date ? value : new Date(String(value))\n if (Number.isNaN(dt.getTime())) return null\n return dt.toISOString()\n}\n\nconst getDealTool: CustomersAiToolDefinition = {\n name: 'customers.get_deal',\n displayName: 'Get deal',\n description:\n 'Fetch a deal by id with fields and (optionally) notes, activities, linked people, and linked companies. Returns { found: false } when outside tenant/org scope.',\n inputSchema: getDealInput,\n requiredFeatures: ['customers.deals.view'],\n tags: ['read', 'customers'],\n handler: async (rawInput, ctx) => {\n const { tenantId: _tenantId } = assertTenantScope(ctx)\n void _tenantId\n const input: GetDealInput = getDealInput.parse(rawInput)\n const includeRelated = !!input.includeRelated\n const runner = createAiApiOperationRunner(ctx as unknown as AiToolExecutionContext)\n\n const detailResponse = await runner.run<Record<string, unknown>>({\n method: 'GET',\n path: `/customers/deals/${input.dealId}`,\n })\n if (!detailResponse.success) {\n if (detailResponse.statusCode === 404 || detailResponse.statusCode === 403) {\n return { found: false as const, dealId: input.dealId }\n }\n throw new Error(detailResponse.error ?? `Failed to fetch deal ${input.dealId}`)\n }\n const detail = (detailResponse.data ?? {}) as Record<string, unknown>\n const dealRow = (detail.deal ?? null) as Record<string, unknown> | null\n if (!dealRow) {\n return { found: false as const, dealId: input.dealId }\n }\n const customFields = (detail.customFields ?? {}) as Record<string, unknown>\n const peopleRows = Array.isArray(detail.people) ? (detail.people as Array<Record<string, unknown>>) : []\n const companiesRows = Array.isArray(detail.companies)\n ? (detail.companies as Array<Record<string, unknown>>)\n : []\n\n let related: Record<string, unknown> | null = null\n if (includeRelated) {\n const [activitiesResponse, commentsResponse] = await Promise.all([\n runner.run<{ items?: Array<Record<string, unknown>>; total?: number }>({\n method: 'GET',\n path: '/customers/activities',\n query: { dealId: input.dealId, page: 1, pageSize: 100, sortField: 'occurredAt', sortDir: 'desc' },\n }),\n runner.run<{ items?: Array<Record<string, unknown>>; total?: number }>({\n method: 'GET',\n path: '/customers/comments',\n query: { dealId: input.dealId, page: 1, pageSize: 100 },\n }),\n ])\n const activities =\n activitiesResponse.success && Array.isArray(activitiesResponse.data?.items)\n ? (activitiesResponse.data!.items as Array<Record<string, unknown>>)\n : []\n const comments =\n commentsResponse.success && Array.isArray(commentsResponse.data?.items)\n ? (commentsResponse.data!.items as Array<Record<string, unknown>>)\n : []\n\n related = {\n activities: activities.map((activity) => ({\n id: activity.id,\n activityType: activity.activityType ?? activity.activity_type ?? null,\n subject: activity.subject ?? null,\n body: activity.body ?? null,\n occurredAt: toIsoDeal(activity.occurredAt ?? activity.occurred_at),\n createdAt: toIsoDeal(activity.createdAt ?? activity.created_at),\n })),\n notes: comments.map((comment) => ({\n id: comment.id,\n body: comment.body,\n authorUserId: comment.authorUserId ?? comment.author_user_id ?? null,\n createdAt: toIsoDeal(comment.createdAt ?? comment.created_at),\n })),\n people: peopleRows\n .map((person) => {\n if (!person || typeof person !== 'object') return null\n const id = typeof person.id === 'string' ? person.id : null\n if (!id) return null\n const subtitle = typeof person.subtitle === 'string' ? person.subtitle : null\n const label = typeof person.label === 'string' ? person.label : ''\n const entry: {\n id: string\n displayName: string\n primaryEmail: string | null\n primaryPhone: string | null\n participantRole: string | null\n } = {\n id,\n displayName: label,\n primaryEmail: subtitle && subtitle.includes('@') ? subtitle : null,\n primaryPhone: subtitle && !subtitle.includes('@') ? subtitle : null,\n participantRole: null as string | null,\n }\n return entry\n })\n .filter(\n (value): value is {\n id: string\n displayName: string\n primaryEmail: string | null\n primaryPhone: string | null\n participantRole: string | null\n } => value !== null,\n ),\n companies: companiesRows\n .map((company) => {\n if (!company || typeof company !== 'object') return null\n const id = typeof company.id === 'string' ? company.id : null\n if (!id) return null\n const label = typeof company.label === 'string' ? company.label : ''\n const entry: {\n id: string\n displayName: string\n primaryEmail: string | null\n primaryPhone: string | null\n } = {\n id,\n displayName: label,\n primaryEmail: null as string | null,\n primaryPhone: null as string | null,\n }\n return entry\n })\n .filter(\n (value): value is {\n id: string\n displayName: string\n primaryEmail: string | null\n primaryPhone: string | null\n } => value !== null,\n ),\n }\n }\n\n return {\n found: true as const,\n deal: {\n id: dealRow.id,\n title: typeof dealRow.title === 'string' ? dealRow.title : '',\n description: dealRow.description ?? null,\n status: dealRow.status ?? null,\n pipelineId: dealRow.pipelineId ?? null,\n pipelineStageId: dealRow.pipelineStageId ?? null,\n valueAmount: dealRow.valueAmount ?? null,\n valueCurrency: dealRow.valueCurrency ?? null,\n probability: dealRow.probability ?? null,\n ownerUserId: dealRow.ownerUserId ?? null,\n expectedCloseAt: toIsoDeal(dealRow.expectedCloseAt),\n source: dealRow.source ?? null,\n organizationId: dealRow.organizationId ?? null,\n tenantId: dealRow.tenantId ?? null,\n createdAt: toIsoDeal(dealRow.createdAt),\n updatedAt: toIsoDeal(dealRow.updatedAt),\n },\n customFields,\n related,\n }\n },\n}\n\n/**\n * Mutation tool: move a deal to a different pipeline stage. Step 5.13 \u2014 first\n * mutation-capable flow on the pending-action contract.\n *\n * Accepts either `toPipelineStageId` (UUID \u2014 preferred, tenant-scoped stage\n * record) or `toStage` (free-form string that maps to `CustomerDeal.status`\n * for pipeline roots like `open`/`won`/`lost`). Exactly one must be provided.\n *\n * The handler delegates to the existing `customers.deals.update` command so\n * all side effects (audit log, `customers.deal.updated` event, query index\n * refresh, notifications) stay identical to a direct API write.\n */\n// LLMs frequently emit `\"\"` for \"not provided\" \u2014 coerce blanks (and surrounding\n// whitespace) to `undefined` BEFORE the per-field validators run so the\n// `.uuid()` check on `toPipelineStageId` does not blow up on an empty string\n// the caller actually meant as \"skip this field\".\nconst blankToUndefined = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length === 0 ? undefined : trimmed\n}\n\nconst updateDealStageInput = z\n .object({\n dealId: z.string().uuid().describe('Deal id (UUID) to update.'),\n toPipelineStageId: z\n .preprocess(blankToUndefined, z.string().uuid().optional())\n .describe('Target pipeline stage id (UUID). Preferred \u2014 tenant-scoped stage record.'),\n toStage: z\n .preprocess(blankToUndefined, z.string().min(1).max(50).optional())\n .describe(\n 'Target status slug (e.g. \"open\", \"won\", \"lost\"). Used when the deal does not belong to a managed pipeline.',\n ),\n })\n .refine(\n (value) => Boolean(value.toPipelineStageId) !== Boolean(value.toStage),\n {\n message: 'Provide exactly one of toPipelineStageId or toStage.',\n path: ['toPipelineStageId'],\n },\n )\n\ntype UpdateDealStageInput = z.infer<typeof updateDealStageInput>\n\nfunction recordVersionFromUpdatedAt(updatedAt: Date | null | undefined): string | null {\n if (!updatedAt) return null\n const value = updatedAt instanceof Date ? updatedAt : new Date(updatedAt)\n if (Number.isNaN(value.getTime())) return null\n return value.toISOString()\n}\n\nasync function loadDealWithStage(\n em: EntityManager,\n ctx: CustomersToolContext,\n tenantId: string,\n dealId: string,\n): Promise<CustomerDeal | null> {\n const where: Record<string, unknown> = { id: dealId, tenantId, deletedAt: null }\n if (ctx.organizationId) where.organizationId = ctx.organizationId\n const deal = await findOneWithDecryption<CustomerDeal>(\n em,\n CustomerDeal,\n where as any,\n undefined,\n buildScope(ctx, tenantId),\n )\n if (!deal || deal.tenantId !== tenantId) return null\n if (ctx.organizationId && deal.organizationId !== ctx.organizationId) return null\n return deal\n}\n\nconst updateDealStageTool: CustomersAiToolDefinition = {\n name: 'customers.update_deal_stage',\n displayName: 'Update deal stage',\n description:\n 'Move a deal to a different pipeline stage (by stage id) or change its top-level status (e.g. \"open\", \"won\", \"lost\"). Mutation tool \u2014 flows through the AI pending-action approval gate.',\n inputSchema: updateDealStageInput as z.ZodType<unknown>,\n requiredFeatures: ['customers.deals.manage'],\n tags: ['write', 'customers'],\n isMutation: true,\n loadBeforeRecord: async (rawInput, ctx): Promise<CustomersToolLoadBeforeSingleRecord | null> => {\n const { tenantId } = assertTenantScope(ctx)\n const input: UpdateDealStageInput = updateDealStageInput.parse(rawInput)\n const em = resolveEm(ctx)\n const deal = await loadDealWithStage(em, ctx, tenantId, input.dealId)\n if (!deal) return null\n return {\n recordId: deal.id,\n entityType: 'customers.deal',\n recordVersion: recordVersionFromUpdatedAt(deal.updatedAt),\n before: {\n status: deal.status ?? null,\n pipelineStage: deal.pipelineStage ?? null,\n pipelineStageId: deal.pipelineStageId ?? null,\n },\n }\n },\n handler: async (rawInput, ctx) => {\n const { tenantId } = assertTenantScope(ctx)\n const input: UpdateDealStageInput = updateDealStageInput.parse(rawInput)\n const em = resolveEm(ctx)\n const deal = await loadDealWithStage(em, ctx, tenantId, input.dealId)\n if (!deal) {\n throw new Error(`Deal \"${input.dealId}\" is not accessible to the caller.`)\n }\n const organizationId = deal.organizationId\n if (!organizationId) {\n throw new Error(`Deal \"${input.dealId}\" has no organization scope.`)\n }\n\n const before = {\n status: deal.status ?? null,\n pipelineStage: deal.pipelineStage ?? null,\n pipelineStageId: deal.pipelineStageId ?? null,\n }\n\n const body: Record<string, unknown> = {\n id: deal.id,\n tenantId,\n organizationId,\n }\n if (input.toPipelineStageId) {\n const stage = await em.findOne(CustomerPipelineStage, {\n id: input.toPipelineStageId,\n tenantId,\n organizationId,\n })\n if (!stage) {\n throw new Error('Pipeline stage not found.')\n }\n body.pipelineStageId = input.toPipelineStageId\n } else if (input.toStage) {\n body.status = input.toStage\n }\n\n const runner = createAiApiOperationRunner(ctx as unknown as AiToolExecutionContext)\n const response = await runner.run({\n method: 'PUT',\n path: '/customers/deals',\n body,\n })\n if (!response.success) {\n throw new Error(response.error ?? `Failed to update deal \"${deal.id}\"`)\n }\n\n const after = await loadDealWithStage(em, ctx, tenantId, deal.id)\n return {\n recordId: deal.id,\n commandName: 'customers.deals.update',\n before,\n after: after\n ? {\n status: after.status ?? null,\n pipelineStage: after.pipelineStage ?? null,\n pipelineStageId: after.pipelineStageId ?? null,\n }\n : null,\n }\n },\n}\n\nexport const dealsAiTools: CustomersAiToolDefinition[] = [listDealsTool, getDealTool, updateDealStageTool]\n\nexport default dealsAiTools\n"],
|
|
5
|
+
"mappings": "AAiBA,SAAS,SAAS;AAClB,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,OAGK;AACP,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAIK;AAEP,SAAS,UAAU,KAAmE;AACpF,SAAO,IAAI,UAAU,QAAuB,IAAI;AAClD;AAEA,SAAS,WAAW,KAAoD,UAAkB;AACxF,SAAO,EAAE,UAAU,gBAAgB,IAAI,eAAe;AACxD;AAEA,MAAM,iBAAiB,EACpB,OAAO;AAAA,EACN,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,wFAAwF;AAAA,EACjI,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,+CAA+C;AAAA,EAC3G,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,qCAAqC;AAAA,EACzF,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,oDAAoD;AAAA,EACpG,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,qDAAqD;AAAA,EACtG,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,2CAA2C;AAAA,EAClG,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qDAAqD;AAC9F,CAAC,EACA,YAAY;AA2Cf,MAAM,gBAAgB,sBAA6E;AAAA,EACjG,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,sBAAsB;AAAA,EACzC,aAAa,CAAC,OAAO,QAAQ;AAC3B,sBAAkB,GAAsC;AACxD,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAO,KAAK,MAAM,SAAS,KAAK,IAAI;AAE1C,UAAM,QAAsE;AAAA,MAC1E;AAAA,MACA,UAAU;AAAA,IACZ;AACA,QAAI,MAAM,GAAG,KAAK,EAAG,OAAM,SAAS,MAAM,EAAE,KAAK;AACjD,QAAI,MAAM,SAAU,OAAM,WAAW,MAAM;AAC3C,QAAI,MAAM,UAAW,OAAM,YAAY,MAAM;AAC7C,QAAI,MAAM,gBAAiB,OAAM,kBAAkB,MAAM;AACzD,QAAI,MAAM,OAAQ,OAAM,SAAS,MAAM;AAEvC,UAAM,YAAmC;AAAA,MACvC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,aAAa,CAAC,UAAU,UAAU;AAChC,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,OAAQ,SAAS,QAAQ,CAAC;AAChC,UAAM,WAA+B,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;AAC/E,WAAO;AAAA,MACL,OAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,cAAM,mBAAmB,IAAI,qBAAqB,IAAI,mBAAmB;AACzE,cAAM,kBAAkB,mBAAmB,IAAI,KAAK,OAAO,gBAAgB,CAAC,EAAE,YAAY,IAAI;AAC9F,cAAM,eAAe,IAAI,cAAc,IAAI,aAAa;AACxD,cAAM,YAAY,eAAe,IAAI,KAAK,OAAO,YAAY,CAAC,EAAE,YAAY,IAAI;AAChF,eAAO;AAAA,UACL,IAAI,IAAI;AAAA,UACR,OAAO,IAAI,SAAS;AAAA,UACpB,aAAa,IAAI,eAAe;AAAA,UAChC,QAAQ,IAAI,UAAU;AAAA,UACtB,YAAY,IAAI,eAAe,IAAI,cAAc;AAAA,UACjD,iBAAiB,IAAI,qBAAqB,IAAI,mBAAmB;AAAA,UACjE,aAAa,IAAI,gBAAgB,IAAI,eAAe;AAAA,UACpD,eAAe,IAAI,kBAAkB,IAAI,iBAAiB;AAAA,UAC1D,aAAa,IAAI,eAAe;AAAA,UAChC,aAAa,IAAI,iBAAiB,IAAI,eAAe;AAAA,UACrD;AAAA,UACA,QAAQ,IAAI,UAAU;AAAA,UACtB,gBAAgB,IAAI,mBAAmB,IAAI,kBAAkB;AAAA,UAC7D,UAAU,IAAI,aAAa,IAAI,YAAY;AAAA,UAC3C;AAAA,QACF;AAAA,MACF,CAAC;AAAA,MACD,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,MACrD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,iBAAiB;AAAA,EACpD,gBAAgB,EACb,QAAQ,EACR,SAAS,EACT,SAAS,yFAAyF;AACvG,CAAC;AAID,SAAS,UAAU,OAA+B;AAChD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,KAAK,iBAAiB,OAAO,QAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AACjE,MAAI,OAAO,MAAM,GAAG,QAAQ,CAAC,EAAG,QAAO;AACvC,SAAO,GAAG,YAAY;AACxB;AAEA,MAAM,cAAyC;AAAA,EAC7C,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,sBAAsB;AAAA,EACzC,MAAM,CAAC,QAAQ,WAAW;AAAA,EAC1B,SAAS,OAAO,UAAU,QAAQ;AAChC,UAAM,EAAE,UAAU,UAAU,IAAI,kBAAkB,GAAG;AACrD,SAAK;AACL,UAAM,QAAsB,aAAa,MAAM,QAAQ;AACvD,UAAM,iBAAiB,CAAC,CAAC,MAAM;AAC/B,UAAM,SAAS,2BAA2B,GAAwC;AAElF,UAAM,iBAAiB,MAAM,OAAO,IAA6B;AAAA,MAC/D,QAAQ;AAAA,MACR,MAAM,oBAAoB,MAAM,MAAM;AAAA,IACxC,CAAC;AACD,QAAI,CAAC,eAAe,SAAS;AAC3B,UAAI,eAAe,eAAe,OAAO,eAAe,eAAe,KAAK;AAC1E,eAAO,EAAE,OAAO,OAAgB,QAAQ,MAAM,OAAO;AAAA,MACvD;AACA,YAAM,IAAI,MAAM,eAAe,SAAS,wBAAwB,MAAM,MAAM,EAAE;AAAA,IAChF;AACA,UAAM,SAAU,eAAe,QAAQ,CAAC;AACxC,UAAM,UAAW,OAAO,QAAQ;AAChC,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,OAAO,OAAgB,QAAQ,MAAM,OAAO;AAAA,IACvD;AACA,UAAM,eAAgB,OAAO,gBAAgB,CAAC;AAC9C,UAAM,aAAa,MAAM,QAAQ,OAAO,MAAM,IAAK,OAAO,SAA4C,CAAC;AACvG,UAAM,gBAAgB,MAAM,QAAQ,OAAO,SAAS,IAC/C,OAAO,YACR,CAAC;AAEL,QAAI,UAA0C;AAC9C,QAAI,gBAAgB;AAClB,YAAM,CAAC,oBAAoB,gBAAgB,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC/D,OAAO,IAAgE;AAAA,UACrE,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,OAAO,EAAE,QAAQ,MAAM,QAAQ,MAAM,GAAG,UAAU,KAAK,WAAW,cAAc,SAAS,OAAO;AAAA,QAClG,CAAC;AAAA,QACD,OAAO,IAAgE;AAAA,UACrE,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,OAAO,EAAE,QAAQ,MAAM,QAAQ,MAAM,GAAG,UAAU,IAAI;AAAA,QACxD,CAAC;AAAA,MACH,CAAC;AACD,YAAM,aACJ,mBAAmB,WAAW,MAAM,QAAQ,mBAAmB,MAAM,KAAK,IACrE,mBAAmB,KAAM,QAC1B,CAAC;AACP,YAAM,WACJ,iBAAiB,WAAW,MAAM,QAAQ,iBAAiB,MAAM,KAAK,IACjE,iBAAiB,KAAM,QACxB,CAAC;AAEP,gBAAU;AAAA,QACR,YAAY,WAAW,IAAI,CAAC,cAAc;AAAA,UACxC,IAAI,SAAS;AAAA,UACb,cAAc,SAAS,gBAAgB,SAAS,iBAAiB;AAAA,UACjE,SAAS,SAAS,WAAW;AAAA,UAC7B,MAAM,SAAS,QAAQ;AAAA,UACvB,YAAY,UAAU,SAAS,cAAc,SAAS,WAAW;AAAA,UACjE,WAAW,UAAU,SAAS,aAAa,SAAS,UAAU;AAAA,QAChE,EAAE;AAAA,QACF,OAAO,SAAS,IAAI,CAAC,aAAa;AAAA,UAChC,IAAI,QAAQ;AAAA,UACZ,MAAM,QAAQ;AAAA,UACd,cAAc,QAAQ,gBAAgB,QAAQ,kBAAkB;AAAA,UAChE,WAAW,UAAU,QAAQ,aAAa,QAAQ,UAAU;AAAA,QAC9D,EAAE;AAAA,QACF,QAAQ,WACL,IAAI,CAAC,WAAW;AACf,cAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,gBAAM,KAAK,OAAO,OAAO,OAAO,WAAW,OAAO,KAAK;AACvD,cAAI,CAAC,GAAI,QAAO;AAChB,gBAAM,WAAW,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;AACzE,gBAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAChE,gBAAM,QAMF;AAAA,YACF;AAAA,YACA,aAAa;AAAA,YACb,cAAc,YAAY,SAAS,SAAS,GAAG,IAAI,WAAW;AAAA,YAC9D,cAAc,YAAY,CAAC,SAAS,SAAS,GAAG,IAAI,WAAW;AAAA,YAC/D,iBAAiB;AAAA,UACnB;AACA,iBAAO;AAAA,QACT,CAAC,EACA;AAAA,UACC,CAAC,UAMI,UAAU;AAAA,QACjB;AAAA,QACF,WAAW,cACR,IAAI,CAAC,YAAY;AAChB,cAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,gBAAM,KAAK,OAAO,QAAQ,OAAO,WAAW,QAAQ,KAAK;AACzD,cAAI,CAAC,GAAI,QAAO;AAChB,gBAAM,QAAQ,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;AAClE,gBAAM,QAKF;AAAA,YACF;AAAA,YACA,aAAa;AAAA,YACb,cAAc;AAAA,YACd,cAAc;AAAA,UAChB;AACA,iBAAO;AAAA,QACT,CAAC,EACA;AAAA,UACC,CAAC,UAKI,UAAU;AAAA,QACjB;AAAA,MACJ;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,QACJ,IAAI,QAAQ;AAAA,QACZ,OAAO,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;AAAA,QAC3D,aAAa,QAAQ,eAAe;AAAA,QACpC,QAAQ,QAAQ,UAAU;AAAA,QAC1B,YAAY,QAAQ,cAAc;AAAA,QAClC,iBAAiB,QAAQ,mBAAmB;AAAA,QAC5C,aAAa,QAAQ,eAAe;AAAA,QACpC,eAAe,QAAQ,iBAAiB;AAAA,QACxC,aAAa,QAAQ,eAAe;AAAA,QACpC,aAAa,QAAQ,eAAe;AAAA,QACpC,iBAAiB,UAAU,QAAQ,eAAe;AAAA,QAClD,QAAQ,QAAQ,UAAU;AAAA,QAC1B,gBAAgB,QAAQ,kBAAkB;AAAA,QAC1C,UAAU,QAAQ,YAAY;AAAA,QAC9B,WAAW,UAAU,QAAQ,SAAS;AAAA,QACtC,WAAW,UAAU,QAAQ,SAAS;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAkBA,MAAM,mBAAmB,CAAC,UAA4B;AACpD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,WAAW,IAAI,SAAY;AAC5C;AAEA,MAAM,uBAAuB,EAC1B,OAAO;AAAA,EACN,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,2BAA2B;AAAA,EAC9D,mBAAmB,EAChB,WAAW,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,EACzD,SAAS,+EAA0E;AAAA,EACtF,SAAS,EACN,WAAW,kBAAkB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS,CAAC,EACjE;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EACA;AAAA,EACC,CAAC,UAAU,QAAQ,MAAM,iBAAiB,MAAM,QAAQ,MAAM,OAAO;AAAA,EACrE;AAAA,IACE,SAAS;AAAA,IACT,MAAM,CAAC,mBAAmB;AAAA,EAC5B;AACF;AAIF,SAAS,2BAA2B,WAAmD;AACrF,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,QAAQ,qBAAqB,OAAO,YAAY,IAAI,KAAK,SAAS;AACxE,MAAI,OAAO,MAAM,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC1C,SAAO,MAAM,YAAY;AAC3B;AAEA,eAAe,kBACb,IACA,KACA,UACA,QAC8B;AAC9B,QAAM,QAAiC,EAAE,IAAI,QAAQ,UAAU,WAAW,KAAK;AAC/E,MAAI,IAAI,eAAgB,OAAM,iBAAiB,IAAI;AACnD,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,KAAK,QAAQ;AAAA,EAC1B;AACA,MAAI,CAAC,QAAQ,KAAK,aAAa,SAAU,QAAO;AAChD,MAAI,IAAI,kBAAkB,KAAK,mBAAmB,IAAI,eAAgB,QAAO;AAC7E,SAAO;AACT;AAEA,MAAM,sBAAiD;AAAA,EACrD,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aACE;AAAA,EACF,aAAa;AAAA,EACb,kBAAkB,CAAC,wBAAwB;AAAA,EAC3C,MAAM,CAAC,SAAS,WAAW;AAAA,EAC3B,YAAY;AAAA,EACZ,kBAAkB,OAAO,UAAU,QAA6D;AAC9F,UAAM,EAAE,SAAS,IAAI,kBAAkB,GAAG;AAC1C,UAAM,QAA8B,qBAAqB,MAAM,QAAQ;AACvE,UAAM,KAAK,UAAU,GAAG;AACxB,UAAM,OAAO,MAAM,kBAAkB,IAAI,KAAK,UAAU,MAAM,MAAM;AACpE,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO;AAAA,MACL,UAAU,KAAK;AAAA,MACf,YAAY;AAAA,MACZ,eAAe,2BAA2B,KAAK,SAAS;AAAA,MACxD,QAAQ;AAAA,QACN,QAAQ,KAAK,UAAU;AAAA,QACvB,eAAe,KAAK,iBAAiB;AAAA,QACrC,iBAAiB,KAAK,mBAAmB;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAAA,EACA,SAAS,OAAO,UAAU,QAAQ;AAChC,UAAM,EAAE,SAAS,IAAI,kBAAkB,GAAG;AAC1C,UAAM,QAA8B,qBAAqB,MAAM,QAAQ;AACvE,UAAM,KAAK,UAAU,GAAG;AACxB,UAAM,OAAO,MAAM,kBAAkB,IAAI,KAAK,UAAU,MAAM,MAAM;AACpE,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,SAAS,MAAM,MAAM,oCAAoC;AAAA,IAC3E;AACA,UAAM,iBAAiB,KAAK;AAC5B,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI,MAAM,SAAS,MAAM,MAAM,8BAA8B;AAAA,IACrE;AAEA,UAAM,SAAS;AAAA,MACb,QAAQ,KAAK,UAAU;AAAA,MACvB,eAAe,KAAK,iBAAiB;AAAA,MACrC,iBAAiB,KAAK,mBAAmB;AAAA,IAC3C;AAEA,UAAM,OAAgC;AAAA,MACpC,IAAI,KAAK;AAAA,MACT;AAAA,MACA;AAAA,IACF;AACA,QAAI,MAAM,mBAAmB;AAC3B,YAAM,QAAQ,MAAM,GAAG,QAAQ,uBAAuB;AAAA,QACpD,IAAI,MAAM;AAAA,QACV;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC7C;AACA,WAAK,kBAAkB,MAAM;AAAA,IAC/B,WAAW,MAAM,SAAS;AACxB,WAAK,SAAS,MAAM;AAAA,IACtB;AAEA,UAAM,SAAS,2BAA2B,GAAwC;AAClF,UAAM,WAAW,MAAM,OAAO,IAAI;AAAA,MAChC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AACD,QAAI,CAAC,SAAS,SAAS;AACrB,YAAM,IAAI,MAAM,SAAS,SAAS,0BAA0B,KAAK,EAAE,GAAG;AAAA,IACxE;AAEA,UAAM,QAAQ,MAAM,kBAAkB,IAAI,KAAK,UAAU,KAAK,EAAE;AAChE,WAAO;AAAA,MACL,UAAU,KAAK;AAAA,MACf,aAAa;AAAA,MACb;AAAA,MACA,OAAO,QACH;AAAA,QACE,QAAQ,MAAM,UAAU;AAAA,QACxB,eAAe,MAAM,iBAAiB;AAAA,QACtC,iBAAiB,MAAM,mBAAmB;AAAA,MAC5C,IACA;AAAA,IACN;AAAA,EACF;AACF;AAEO,MAAM,eAA4C,CAAC,eAAe,aAAa,mBAAmB;AAEzG,IAAO,qBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|