@open-mercato/core 0.6.6-develop.6313.1.618a49a6b4 → 0.6.6-develop.6317.1.b3be10ab84

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.
Files changed (35) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/ai-tools/_shared.js +158 -0
  3. package/dist/modules/customers/ai-tools/_shared.js.map +7 -0
  4. package/dist/modules/customers/ai-tools/companies-pack.js +15 -121
  5. package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
  6. package/dist/modules/customers/ai-tools/people-pack.js +9 -107
  7. package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
  8. package/dist/modules/customers/components/DictionarySettings.js +92 -37
  9. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  10. package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -0
  11. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  12. package/dist/modules/customers/components/calendar/EventPeekPopover.js +12 -1
  13. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
  14. package/dist/modules/customers/components/calendar/TimeGrid.js +3 -0
  15. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  16. package/dist/modules/customers/components/calendar/types.js.map +1 -1
  17. package/dist/modules/customers/components/formConfig.js +7 -1
  18. package/dist/modules/customers/components/formConfig.js.map +2 -2
  19. package/dist/modules/customers/lib/dictionaries.js +10 -0
  20. package/dist/modules/customers/lib/dictionaries.js.map +2 -2
  21. package/package.json +7 -7
  22. package/src/modules/customers/ai-tools/_shared.ts +270 -0
  23. package/src/modules/customers/ai-tools/companies-pack.ts +17 -157
  24. package/src/modules/customers/ai-tools/people-pack.ts +11 -133
  25. package/src/modules/customers/components/DictionarySettings.tsx +60 -2
  26. package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -0
  27. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +29 -11
  28. package/src/modules/customers/components/calendar/TimeGrid.tsx +3 -0
  29. package/src/modules/customers/components/calendar/types.ts +1 -0
  30. package/src/modules/customers/components/formConfig.tsx +8 -2
  31. package/src/modules/customers/i18n/de.json +1 -0
  32. package/src/modules/customers/i18n/en.json +1 -0
  33. package/src/modules/customers/i18n/es.json +1 -0
  34. package/src/modules/customers/i18n/pl.json +1 -0
  35. package/src/modules/customers/lib/dictionaries.ts +10 -0
@@ -1,4 +1,4 @@
1
- [build:core] found 3424 entry points
1
+ [build:core] found 3425 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 186 entry points
4
4
  [build:core:generated] built successfully
@@ -0,0 +1,158 @@
1
+ function toIso(value) {
2
+ if (!value) return null;
3
+ const dt = value instanceof Date ? value : new Date(String(value));
4
+ if (Number.isNaN(dt.getTime())) return null;
5
+ return dt.toISOString();
6
+ }
7
+ function resolveEm(ctx) {
8
+ return ctx.container.resolve("em");
9
+ }
10
+ function buildScope(ctx, tenantId) {
11
+ return {
12
+ tenantId,
13
+ organizationId: ctx.organizationId
14
+ };
15
+ }
16
+ function toCustomerListSummary(row) {
17
+ const createdAtRaw = row.created_at ?? row.createdAt ?? null;
18
+ const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null;
19
+ return {
20
+ id: row.id,
21
+ displayName: row.display_name ?? row.displayName ?? null,
22
+ primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
23
+ primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
24
+ status: row.status ?? null,
25
+ lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
26
+ source: row.source ?? null,
27
+ ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
28
+ organizationId: row.organization_id ?? row.organizationId ?? null,
29
+ tenantId: row.tenant_id ?? row.tenantId ?? null,
30
+ createdAt
31
+ };
32
+ }
33
+ function asRows(value) {
34
+ return Array.isArray(value) ? value : [];
35
+ }
36
+ function mapAddresses(rows) {
37
+ return rows.map((address) => ({
38
+ id: address.id,
39
+ name: address.name ?? null,
40
+ purpose: address.purpose ?? null,
41
+ addressLine1: address.addressLine1 ?? null,
42
+ addressLine2: address.addressLine2 ?? null,
43
+ city: address.city ?? null,
44
+ region: address.region ?? null,
45
+ postalCode: address.postalCode ?? null,
46
+ country: address.country ?? null,
47
+ isPrimary: !!address.isPrimary
48
+ }));
49
+ }
50
+ function mapActivities(rows) {
51
+ return rows.map((activity) => ({
52
+ id: activity.id,
53
+ activityType: activity.activityType,
54
+ subject: activity.subject ?? null,
55
+ body: activity.body ?? null,
56
+ occurredAt: toIso(activity.occurredAt),
57
+ createdAt: toIso(activity.createdAt)
58
+ }));
59
+ }
60
+ function mapNotes(rows) {
61
+ return rows.map((comment) => ({
62
+ id: comment.id,
63
+ body: comment.body,
64
+ authorUserId: comment.authorUserId ?? null,
65
+ createdAt: toIso(comment.createdAt)
66
+ }));
67
+ }
68
+ function mapTasks(rows) {
69
+ return rows.map((task) => ({
70
+ id: task.id,
71
+ todoId: task.todoId ?? task.id,
72
+ todoSource: task.todoSource ?? null,
73
+ createdAt: toIso(task.createdAt)
74
+ }));
75
+ }
76
+ function mapInteractions(rows) {
77
+ return rows.map((interaction) => ({
78
+ id: interaction.id,
79
+ interactionType: interaction.interactionType,
80
+ title: interaction.title ?? null,
81
+ status: interaction.status,
82
+ scheduledAt: toIso(interaction.scheduledAt),
83
+ occurredAt: toIso(interaction.occurredAt)
84
+ }));
85
+ }
86
+ function mapTags(rows) {
87
+ return rows.map((tag) => {
88
+ if (!tag || typeof tag !== "object") return null;
89
+ const id = typeof tag.id === "string" ? tag.id : null;
90
+ const label = typeof tag.label === "string" ? tag.label : null;
91
+ if (!id || !label) return null;
92
+ const slug = typeof tag.slug === "string" ? tag.slug : label;
93
+ const color = typeof tag.color === "string" ? tag.color : null;
94
+ return { id, slug, label, color };
95
+ }).filter((entry) => entry !== null);
96
+ }
97
+ function mapDeals(rows) {
98
+ return rows.map((deal) => {
99
+ if (!deal || typeof deal !== "object") return null;
100
+ const id = typeof deal.id === "string" ? deal.id : null;
101
+ if (!id) return null;
102
+ return {
103
+ id,
104
+ title: typeof deal.title === "string" ? deal.title : "",
105
+ status: typeof deal.status === "string" ? deal.status : null,
106
+ pipelineStageId: typeof deal.pipelineStageId === "string" ? deal.pipelineStageId : null,
107
+ valueAmount: typeof deal.valueAmount === "string" ? deal.valueAmount : deal.valueAmount === null || deal.valueAmount === void 0 ? null : String(deal.valueAmount),
108
+ valueCurrency: typeof deal.valueCurrency === "string" ? deal.valueCurrency : null
109
+ };
110
+ }).filter((value) => value !== null);
111
+ }
112
+ function mapPeople(rows) {
113
+ return rows.map((person) => {
114
+ if (!person || typeof person !== "object") return null;
115
+ const id = typeof person.id === "string" ? person.id : null;
116
+ const displayName = typeof person.displayName === "string" ? person.displayName : null;
117
+ if (!id || !displayName) return null;
118
+ return {
119
+ id,
120
+ displayName,
121
+ primaryEmail: typeof person.primaryEmail === "string" ? person.primaryEmail : null,
122
+ primaryPhone: typeof person.primaryPhone === "string" ? person.primaryPhone : null,
123
+ jobTitle: typeof person.jobTitle === "string" ? person.jobTitle : null,
124
+ department: typeof person.department === "string" ? person.department : null
125
+ };
126
+ }).filter((value) => value !== null);
127
+ }
128
+ function buildRelatedRecords(data, options = {}) {
129
+ const related = {
130
+ addresses: mapAddresses(asRows(data.addresses)),
131
+ activities: mapActivities(asRows(data.activities)),
132
+ notes: mapNotes(asRows(data.comments)),
133
+ tasks: mapTasks(asRows(data.todos)),
134
+ interactions: mapInteractions(asRows(data.interactions)),
135
+ tags: mapTags(asRows(data.tags)),
136
+ deals: mapDeals(asRows(data.deals))
137
+ };
138
+ if (options.includePeople) {
139
+ related.people = mapPeople(asRows(data.people));
140
+ }
141
+ return related;
142
+ }
143
+ export {
144
+ buildRelatedRecords,
145
+ buildScope,
146
+ mapActivities,
147
+ mapAddresses,
148
+ mapDeals,
149
+ mapInteractions,
150
+ mapNotes,
151
+ mapPeople,
152
+ mapTags,
153
+ mapTasks,
154
+ resolveEm,
155
+ toCustomerListSummary,
156
+ toIso
157
+ };
158
+ //# sourceMappingURL=_shared.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/ai-tools/_shared.ts"],
4
+ "sourcesContent": ["/**\n * Shared helpers for customers AI tool packs.\n *\n * Mirrors the catalog precedent (`packages/core/src/modules/catalog/ai-tools/_shared.ts`):\n * the company and people packs previously carried byte-for-byte-identical copies\n * of the date-to-ISO helper, the `resolveEm` / `buildScope` accessors, the\n * list-row \u2192 summary mapper, and the related-records builder\n * (addresses / activities / notes / tasks / interactions / tags / deals, plus a\n * companies-only `people` mapper). Centralizing them here gives both packs one\n * source of truth so a change to the related-records output shape no longer has\n * to be applied \u2014 and kept in sync \u2014 in two places.\n *\n * This is a pure internal refactor: tool names, input schemas, `requiredFeatures`,\n * and emitted output shapes stay identical.\n */\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AiToolExecutionContext } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'\nimport type { CustomersToolContext } from './types'\n\nexport function toIso(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\nexport function resolveEm(ctx: CustomersToolContext | AiToolExecutionContext): EntityManager {\n return ctx.container.resolve<EntityManager>('em')\n}\n\nexport function buildScope(ctx: CustomersToolContext | AiToolExecutionContext, tenantId: string) {\n return {\n tenantId,\n organizationId: ctx.organizationId,\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* List-row summary mapper */\n/* -------------------------------------------------------------------------- */\n\nexport type CustomerListApiItemBase = {\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 created_at?: string | null\n createdAt?: string | null\n}\n\nexport type CustomerListSummary = {\n id: string | undefined\n displayName: string | null\n primaryEmail: string | null\n primaryPhone: string | null\n status: string | null\n lifecycleStage: string | null\n source: string | null\n ownerUserId: string | null\n organizationId: string | null\n tenantId: string | null\n createdAt: string | null\n}\n\nexport function toCustomerListSummary(row: CustomerListApiItemBase): CustomerListSummary {\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 createdAt,\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Related-records mappers */\n/* -------------------------------------------------------------------------- */\n\nfunction asRows(value: unknown): Array<Record<string, unknown>> {\n return Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []\n}\n\nexport function mapAddresses(rows: Array<Record<string, unknown>>) {\n return rows.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}\n\nexport function mapActivities(rows: Array<Record<string, unknown>>) {\n return rows.map((activity) => ({\n id: activity.id,\n activityType: activity.activityType,\n subject: activity.subject ?? null,\n body: activity.body ?? null,\n occurredAt: toIso(activity.occurredAt),\n createdAt: toIso(activity.createdAt),\n }))\n}\n\nexport function mapNotes(rows: Array<Record<string, unknown>>) {\n return rows.map((comment) => ({\n id: comment.id,\n body: comment.body,\n authorUserId: comment.authorUserId ?? null,\n createdAt: toIso(comment.createdAt),\n }))\n}\n\nexport function mapTasks(rows: Array<Record<string, unknown>>) {\n return rows.map((task) => ({\n id: task.id,\n todoId: task.todoId ?? task.id,\n todoSource: task.todoSource ?? null,\n createdAt: toIso(task.createdAt),\n }))\n}\n\nexport function mapInteractions(rows: Array<Record<string, unknown>>) {\n return rows.map((interaction) => ({\n id: interaction.id,\n interactionType: interaction.interactionType,\n title: interaction.title ?? null,\n status: interaction.status,\n scheduledAt: toIso(interaction.scheduledAt),\n occurredAt: toIso(interaction.occurredAt),\n }))\n}\n\nexport type RelatedTag = { id: string; slug: string; label: string; color: string | null }\n\nexport function mapTags(rows: Array<Record<string, unknown>>): RelatedTag[] {\n return rows\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((entry): entry is RelatedTag => entry !== null)\n}\n\nexport type RelatedDeal = {\n id: string\n title: string\n status: string | null\n pipelineStageId: string | null\n valueAmount: string | null\n valueCurrency: string | null\n}\n\nexport function mapDeals(rows: Array<Record<string, unknown>>): RelatedDeal[] {\n return rows\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: 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: typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,\n }\n })\n .filter((value): value is RelatedDeal => value !== null)\n}\n\nexport type RelatedPerson = {\n id: string\n displayName: string\n primaryEmail: string | null\n primaryPhone: string | null\n jobTitle: string | null\n department: string | null\n}\n\nexport function mapPeople(rows: Array<Record<string, unknown>>): RelatedPerson[] {\n return rows\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: typeof person.primaryEmail === 'string' ? person.primaryEmail : null,\n primaryPhone: 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((value): value is RelatedPerson => value !== null)\n}\n\nexport type CustomerRelatedRecords = {\n addresses: ReturnType<typeof mapAddresses>\n activities: ReturnType<typeof mapActivities>\n notes: ReturnType<typeof mapNotes>\n tasks: ReturnType<typeof mapTasks>\n interactions: ReturnType<typeof mapInteractions>\n tags: RelatedTag[]\n deals: RelatedDeal[]\n people?: RelatedPerson[]\n}\n\n/**\n * Builds the related-records block from a customers detail API payload\n * (`addresses`, `activities`, `comments`, `todos`, `interactions`, `tags`,\n * `deals`, and \u2014 for companies \u2014 `people`). The key order matches the\n * pre-refactor literals so the emitted shape is unchanged. Pass\n * `includePeople: true` to add the companies-only `people` collection.\n */\nexport function buildRelatedRecords(\n data: Record<string, unknown>,\n options: { includePeople?: boolean } = {},\n): CustomerRelatedRecords {\n const related: CustomerRelatedRecords = {\n addresses: mapAddresses(asRows(data.addresses)),\n activities: mapActivities(asRows(data.activities)),\n notes: mapNotes(asRows(data.comments)),\n tasks: mapTasks(asRows(data.todos)),\n interactions: mapInteractions(asRows(data.interactions)),\n tags: mapTags(asRows(data.tags)),\n deals: mapDeals(asRows(data.deals)),\n }\n if (options.includePeople) {\n related.people = mapPeople(asRows(data.people))\n }\n return related\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -4,6 +4,11 @@ import {
4
4
  createAiApiOperationRunner
5
5
  } from "@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner";
6
6
  import { assertTenantScope } from "./types.js";
7
+ import {
8
+ buildRelatedRecords,
9
+ toCustomerListSummary,
10
+ toIso
11
+ } from "./_shared.js";
7
12
  const listCompaniesInput = z.object({
8
13
  q: z.string().trim().optional().describe("Search text matched against display name / email / domain. Omit or leave empty to list all."),
9
14
  limit: z.number().int().min(1).max(100).optional().describe("Maximum rows to return (default 50, max 100)."),
@@ -40,27 +45,13 @@ const listCompaniesTool = defineApiBackedAiTool({
40
45
  const data = response.data ?? {};
41
46
  const rawItems = Array.isArray(data.items) ? data.items : [];
42
47
  return {
43
- items: rawItems.map((row) => {
44
- const createdAtRaw = row.created_at ?? row.createdAt ?? null;
45
- const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null;
46
- return {
47
- id: row.id,
48
- displayName: row.display_name ?? row.displayName ?? null,
49
- primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
50
- primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
51
- status: row.status ?? null,
52
- lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
53
- source: row.source ?? null,
54
- ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
55
- organizationId: row.organization_id ?? row.organizationId ?? null,
56
- tenantId: row.tenant_id ?? row.tenantId ?? null,
57
- domain: row.domain ?? null,
58
- websiteUrl: row.website_url ?? row.websiteUrl ?? null,
59
- industry: row.industry ?? null,
60
- sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,
61
- createdAt
62
- };
63
- }),
48
+ items: rawItems.map((row) => ({
49
+ ...toCustomerListSummary(row),
50
+ domain: row.domain ?? null,
51
+ websiteUrl: row.website_url ?? row.websiteUrl ?? null,
52
+ industry: row.industry ?? null,
53
+ sizeBucket: row.size_bucket ?? row.sizeBucket ?? null
54
+ })),
64
55
  total: typeof data.total === "number" ? data.total : 0,
65
56
  limit,
66
57
  offset
@@ -71,12 +62,6 @@ const getCompanyInput = z.object({
71
62
  companyId: z.string().uuid().describe("Company entity id (UUID)."),
72
63
  includeRelated: z.boolean().optional().describe("When true, include notes, activities, deals, people, addresses, tasks, and tags (each capped at 100).")
73
64
  });
74
- function toIsoCompany(value) {
75
- if (!value) return null;
76
- const dt = value instanceof Date ? value : new Date(String(value));
77
- if (Number.isNaN(dt.getTime())) return null;
78
- return dt.toISOString();
79
- }
80
65
  const getCompanyTool = {
81
66
  name: "customers.get_company",
82
67
  displayName: "Get company",
@@ -115,98 +100,7 @@ const getCompanyTool = {
115
100
  const customFields = data.customFields ?? {};
116
101
  let related = null;
117
102
  if (includeRelated) {
118
- const addresses = Array.isArray(data.addresses) ? data.addresses : [];
119
- const activities = Array.isArray(data.activities) ? data.activities : [];
120
- const notes = Array.isArray(data.comments) ? data.comments : [];
121
- const todos = Array.isArray(data.todos) ? data.todos : [];
122
- const interactions = Array.isArray(data.interactions) ? data.interactions : [];
123
- const tagsRows = Array.isArray(data.tags) ? data.tags : [];
124
- const dealsRows = Array.isArray(data.deals) ? data.deals : [];
125
- const peopleRows = Array.isArray(data.people) ? data.people : [];
126
- related = {
127
- addresses: addresses.map((address) => ({
128
- id: address.id,
129
- name: address.name ?? null,
130
- purpose: address.purpose ?? null,
131
- addressLine1: address.addressLine1 ?? null,
132
- addressLine2: address.addressLine2 ?? null,
133
- city: address.city ?? null,
134
- region: address.region ?? null,
135
- postalCode: address.postalCode ?? null,
136
- country: address.country ?? null,
137
- isPrimary: !!address.isPrimary
138
- })),
139
- activities: activities.map((activity) => ({
140
- id: activity.id,
141
- activityType: activity.activityType,
142
- subject: activity.subject ?? null,
143
- body: activity.body ?? null,
144
- occurredAt: toIsoCompany(activity.occurredAt),
145
- createdAt: toIsoCompany(activity.createdAt)
146
- })),
147
- notes: notes.map((comment) => ({
148
- id: comment.id,
149
- body: comment.body,
150
- authorUserId: comment.authorUserId ?? null,
151
- createdAt: toIsoCompany(comment.createdAt)
152
- })),
153
- tasks: todos.map((task) => ({
154
- id: task.id,
155
- todoId: task.todoId ?? task.id,
156
- todoSource: task.todoSource ?? null,
157
- createdAt: toIsoCompany(task.createdAt)
158
- })),
159
- interactions: interactions.map((interaction) => ({
160
- id: interaction.id,
161
- interactionType: interaction.interactionType,
162
- title: interaction.title ?? null,
163
- status: interaction.status,
164
- scheduledAt: toIsoCompany(interaction.scheduledAt),
165
- occurredAt: toIsoCompany(interaction.occurredAt)
166
- })),
167
- tags: tagsRows.map((tag) => {
168
- if (!tag || typeof tag !== "object") return null;
169
- const id = typeof tag.id === "string" ? tag.id : null;
170
- const label = typeof tag.label === "string" ? tag.label : null;
171
- if (!id || !label) return null;
172
- const slug = typeof tag.slug === "string" ? tag.slug : label;
173
- const color = typeof tag.color === "string" ? tag.color : null;
174
- return { id, slug, label, color };
175
- }).filter(
176
- (entry) => entry !== null
177
- ),
178
- deals: dealsRows.map((deal) => {
179
- if (!deal || typeof deal !== "object") return null;
180
- const id = typeof deal.id === "string" ? deal.id : null;
181
- if (!id) return null;
182
- return {
183
- id,
184
- title: typeof deal.title === "string" ? deal.title : "",
185
- status: typeof deal.status === "string" ? deal.status : null,
186
- pipelineStageId: typeof deal.pipelineStageId === "string" ? deal.pipelineStageId : null,
187
- valueAmount: typeof deal.valueAmount === "string" ? deal.valueAmount : deal.valueAmount === null || deal.valueAmount === void 0 ? null : String(deal.valueAmount),
188
- valueCurrency: typeof deal.valueCurrency === "string" ? deal.valueCurrency : null
189
- };
190
- }).filter(
191
- (value) => value !== null
192
- ),
193
- people: peopleRows.map((person) => {
194
- if (!person || typeof person !== "object") return null;
195
- const id = typeof person.id === "string" ? person.id : null;
196
- const displayName = typeof person.displayName === "string" ? person.displayName : null;
197
- if (!id || !displayName) return null;
198
- return {
199
- id,
200
- displayName,
201
- primaryEmail: typeof person.primaryEmail === "string" ? person.primaryEmail : null,
202
- primaryPhone: typeof person.primaryPhone === "string" ? person.primaryPhone : null,
203
- jobTitle: typeof person.jobTitle === "string" ? person.jobTitle : null,
204
- department: typeof person.department === "string" ? person.department : null
205
- };
206
- }).filter(
207
- (value) => value !== null
208
- )
209
- };
103
+ related = buildRelatedRecords(data, { includePeople: true });
210
104
  }
211
105
  return {
212
106
  found: true,
@@ -222,8 +116,8 @@ const getCompanyTool = {
222
116
  ownerUserId: companyRow.ownerUserId ?? null,
223
117
  organizationId: companyRow.organizationId ?? null,
224
118
  tenantId: companyRow.tenantId ?? null,
225
- createdAt: toIsoCompany(companyRow.createdAt),
226
- updatedAt: toIsoCompany(companyRow.updatedAt)
119
+ createdAt: toIso(companyRow.createdAt),
120
+ updatedAt: toIso(companyRow.updatedAt)
227
121
  },
228
122
  profile: profileRow ? {
229
123
  id: profileRow.id,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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/implemented/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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",
4
+ "sourcesContent": ["/**\n * `customers.list_companies` + `customers.get_company` (Phase 1 WS-C, Step 3.9).\n *\n * Phase 3a of `.ai/specs/implemented/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'\nimport {\n buildRelatedRecords,\n toCustomerListSummary,\n toIso,\n type CustomerRelatedRecords,\n} from './_shared'\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 ...toCustomerListSummary(row),\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 })),\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\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: CustomerRelatedRecords | null = null\n if (includeRelated) {\n related = buildRelatedRecords(data, { includePeople: true })\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: toIso(companyRow.createdAt),\n updatedAt: toIso(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
6
  "names": []
7
7
  }
@@ -8,16 +8,14 @@ import {
8
8
  CustomerPersonProfile
9
9
  } from "../data/entities.js";
10
10
  import { assertTenantScope } from "./types.js";
11
+ import {
12
+ buildRelatedRecords,
13
+ buildScope,
14
+ resolveEm,
15
+ toCustomerListSummary,
16
+ toIso
17
+ } from "./_shared.js";
11
18
  const NIL_UUID = "00000000-0000-0000-0000-000000000000";
12
- function resolveEm(ctx) {
13
- return ctx.container.resolve("em");
14
- }
15
- function buildScope(ctx, tenantId) {
16
- return {
17
- tenantId,
18
- organizationId: ctx.organizationId
19
- };
20
- }
21
19
  const listPeopleInput = z.object({
22
20
  q: z.string().trim().optional().describe("Optional search text matched against display name / email / phone. Omit or leave empty to list all."),
23
21
  limit: z.number().int().min(1).max(100).optional().describe("Maximum rows to return (default 50, max 100)."),
@@ -73,23 +71,7 @@ const listPeopleTool = defineApiBackedAiTool({
73
71
  const data = response.data ?? {};
74
72
  const rawItems = Array.isArray(data.items) ? data.items : [];
75
73
  return {
76
- items: rawItems.map((row) => {
77
- const createdAtRaw = row.created_at ?? row.createdAt ?? null;
78
- const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null;
79
- return {
80
- id: row.id,
81
- displayName: row.display_name ?? row.displayName ?? null,
82
- primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
83
- primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
84
- status: row.status ?? null,
85
- lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
86
- source: row.source ?? null,
87
- ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
88
- organizationId: row.organization_id ?? row.organizationId ?? null,
89
- tenantId: row.tenant_id ?? row.tenantId ?? null,
90
- createdAt
91
- };
92
- }),
74
+ items: rawItems.map((row) => toCustomerListSummary(row)),
93
75
  total: typeof data.total === "number" ? data.total : 0,
94
76
  limit,
95
77
  offset
@@ -100,12 +82,6 @@ const getPersonInput = z.object({
100
82
  personId: z.string().uuid().describe("Person entity id (UUID)."),
101
83
  includeRelated: z.boolean().optional().describe("When true, include notes, activities, deals, addresses, tasks, and tags (each capped at 100).")
102
84
  });
103
- function toIso(value) {
104
- if (!value) return null;
105
- const dt = value instanceof Date ? value : new Date(String(value));
106
- if (Number.isNaN(dt.getTime())) return null;
107
- return dt.toISOString();
108
- }
109
85
  const getPersonTool = {
110
86
  name: "customers.get_person",
111
87
  displayName: "Get person",
@@ -141,81 +117,7 @@ const getPersonTool = {
141
117
  const customFields = data.customFields ?? {};
142
118
  let related = null;
143
119
  if (includeRelated) {
144
- const addresses = Array.isArray(data.addresses) ? data.addresses : [];
145
- const activities = Array.isArray(data.activities) ? data.activities : [];
146
- const notes = Array.isArray(data.comments) ? data.comments : [];
147
- const todos = Array.isArray(data.todos) ? data.todos : [];
148
- const interactions = Array.isArray(data.interactions) ? data.interactions : [];
149
- const tagsRows = Array.isArray(data.tags) ? data.tags : [];
150
- const dealsRows = Array.isArray(data.deals) ? data.deals : [];
151
- related = {
152
- addresses: addresses.map((address) => ({
153
- id: address.id,
154
- name: address.name ?? null,
155
- purpose: address.purpose ?? null,
156
- addressLine1: address.addressLine1 ?? null,
157
- addressLine2: address.addressLine2 ?? null,
158
- city: address.city ?? null,
159
- region: address.region ?? null,
160
- postalCode: address.postalCode ?? null,
161
- country: address.country ?? null,
162
- isPrimary: !!address.isPrimary
163
- })),
164
- activities: activities.map((activity) => ({
165
- id: activity.id,
166
- activityType: activity.activityType,
167
- subject: activity.subject ?? null,
168
- body: activity.body ?? null,
169
- occurredAt: toIso(activity.occurredAt),
170
- createdAt: toIso(activity.createdAt)
171
- })),
172
- notes: notes.map((comment) => ({
173
- id: comment.id,
174
- body: comment.body,
175
- authorUserId: comment.authorUserId ?? null,
176
- createdAt: toIso(comment.createdAt)
177
- })),
178
- tasks: todos.map((task) => ({
179
- id: task.id,
180
- todoId: task.todoId ?? task.id,
181
- todoSource: task.todoSource ?? null,
182
- createdAt: toIso(task.createdAt)
183
- })),
184
- interactions: interactions.map((interaction) => ({
185
- id: interaction.id,
186
- interactionType: interaction.interactionType,
187
- title: interaction.title ?? null,
188
- status: interaction.status,
189
- scheduledAt: toIso(interaction.scheduledAt),
190
- occurredAt: toIso(interaction.occurredAt)
191
- })),
192
- tags: tagsRows.map((tag) => {
193
- if (!tag || typeof tag !== "object") return null;
194
- const id = typeof tag.id === "string" ? tag.id : null;
195
- const label = typeof tag.label === "string" ? tag.label : null;
196
- if (!id || !label) return null;
197
- const slug = typeof tag.slug === "string" ? tag.slug : label;
198
- const color = typeof tag.color === "string" ? tag.color : null;
199
- return { id, slug, label, color };
200
- }).filter(
201
- (entry) => entry !== null
202
- ),
203
- deals: dealsRows.map((deal) => {
204
- if (!deal || typeof deal !== "object") return null;
205
- const id = typeof deal.id === "string" ? deal.id : null;
206
- if (!id) return null;
207
- return {
208
- id,
209
- title: typeof deal.title === "string" ? deal.title : "",
210
- status: typeof deal.status === "string" ? deal.status : null,
211
- pipelineStageId: typeof deal.pipelineStageId === "string" ? deal.pipelineStageId : null,
212
- valueAmount: typeof deal.valueAmount === "string" ? deal.valueAmount : deal.valueAmount === null || deal.valueAmount === void 0 ? null : String(deal.valueAmount),
213
- valueCurrency: typeof deal.valueCurrency === "string" ? deal.valueCurrency : null
214
- };
215
- }).filter(
216
- (value) => value !== null
217
- )
218
- };
120
+ related = buildRelatedRecords(data);
219
121
  }
220
122
  return {
221
123
  found: true,