@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
@@ -21,6 +21,13 @@ const CUSTOMER_DICTIONARY_KINDS = [
21
21
  "renewal-quarters",
22
22
  "person-company-roles"
23
23
  ];
24
+ const CUSTOMER_DICTIONARIES_MANAGE_HREF = "/backend/config/customers";
25
+ function getCustomerDictionarySettingsSectionId(kind) {
26
+ return `customer-dictionary-${kind}`;
27
+ }
28
+ function getCustomerDictionaryManageHref(kind) {
29
+ return `${CUSTOMER_DICTIONARIES_MANAGE_HREF}#${getCustomerDictionarySettingsSectionId(kind)}`;
30
+ }
24
31
  function createEmptyCustomerDictionaryMaps() {
25
32
  return CUSTOMER_DICTIONARY_KINDS.reduce(
26
33
  (acc, kind) => {
@@ -31,12 +38,15 @@ function createEmptyCustomerDictionaryMaps() {
31
38
  );
32
39
  }
33
40
  export {
41
+ CUSTOMER_DICTIONARIES_MANAGE_HREF,
34
42
  CUSTOMER_DICTIONARY_KINDS,
35
43
  DictionaryValue,
36
44
  ICON_LIBRARY,
37
45
  ICON_SUGGESTIONS,
38
46
  createDictionaryMap,
39
47
  createEmptyCustomerDictionaryMaps,
48
+ getCustomerDictionaryManageHref,
49
+ getCustomerDictionarySettingsSectionId,
40
50
  normalizeDictionaryEntries,
41
51
  renderDictionaryColor,
42
52
  renderDictionaryIcon
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/lib/dictionaries.ts"],
4
- "sourcesContent": ["import {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n type DictionaryDisplayEntry,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n DictionaryValue,\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\nexport const CUSTOMER_DICTIONARY_KINDS = [\n 'statuses',\n 'sources',\n 'lifecycle-stages',\n 'address-types',\n 'activity-types',\n 'deal-statuses',\n 'pipeline-stages',\n 'job-titles',\n 'industries',\n 'temperature',\n 'renewal-quarters',\n 'person-company-roles',\n] as const\n\nexport type CustomerDictionaryKind = typeof CUSTOMER_DICTIONARY_KINDS[number]\nexport type CustomerDictionaryDisplayEntry = DictionaryDisplayEntry\nexport type CustomerDictionaryMap = DictionaryMap\n\nexport function createEmptyCustomerDictionaryMaps(): Record<CustomerDictionaryKind, CustomerDictionaryMap> {\n return CUSTOMER_DICTIONARY_KINDS.reduce<Record<CustomerDictionaryKind, CustomerDictionaryMap>>(\n (acc, kind) => {\n acc[kind] = {}\n return acc\n },\n {} as Record<CustomerDictionaryKind, CustomerDictionaryMap>,\n )\n}\n\nexport {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n DictionaryValue,\n createDictionaryMap,\n normalizeDictionaryEntries,\n renderDictionaryColor,\n renderDictionaryIcon,\n}\n"],
5
- "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,SAAS,oCAA2F;AACzG,SAAO,0BAA0B;AAAA,IAC/B,CAAC,KAAK,SAAS;AACb,UAAI,IAAI,IAAI,CAAC;AACb,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AACF;",
4
+ "sourcesContent": ["import {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n type DictionaryDisplayEntry,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n DictionaryValue,\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\nexport const CUSTOMER_DICTIONARY_KINDS = [\n 'statuses',\n 'sources',\n 'lifecycle-stages',\n 'address-types',\n 'activity-types',\n 'deal-statuses',\n 'pipeline-stages',\n 'job-titles',\n 'industries',\n 'temperature',\n 'renewal-quarters',\n 'person-company-roles',\n] as const\n\nexport type CustomerDictionaryKind = typeof CUSTOMER_DICTIONARY_KINDS[number]\nexport type CustomerDictionaryDisplayEntry = DictionaryDisplayEntry\nexport type CustomerDictionaryMap = DictionaryMap\n\nexport const CUSTOMER_DICTIONARIES_MANAGE_HREF = '/backend/config/customers'\n\nexport function getCustomerDictionarySettingsSectionId(kind: CustomerDictionaryKind) {\n return `customer-dictionary-${kind}`\n}\n\nexport function getCustomerDictionaryManageHref(kind: CustomerDictionaryKind) {\n return `${CUSTOMER_DICTIONARIES_MANAGE_HREF}#${getCustomerDictionarySettingsSectionId(kind)}`\n}\n\nexport function createEmptyCustomerDictionaryMaps(): Record<CustomerDictionaryKind, CustomerDictionaryMap> {\n return CUSTOMER_DICTIONARY_KINDS.reduce<Record<CustomerDictionaryKind, CustomerDictionaryMap>>(\n (acc, kind) => {\n acc[kind] = {}\n return acc\n },\n {} as Record<CustomerDictionaryKind, CustomerDictionaryMap>,\n )\n}\n\nexport {\n ICON_LIBRARY,\n ICON_SUGGESTIONS,\n type IconOption,\n DictionaryValue,\n createDictionaryMap,\n normalizeDictionaryEntries,\n renderDictionaryColor,\n renderDictionaryIcon,\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,MAAM,oCAAoC;AAE1C,SAAS,uCAAuC,MAA8B;AACnF,SAAO,uBAAuB,IAAI;AACpC;AAEO,SAAS,gCAAgC,MAA8B;AAC5E,SAAO,GAAG,iCAAiC,IAAI,uCAAuC,IAAI,CAAC;AAC7F;AAEO,SAAS,oCAA2F;AACzG,SAAO,0BAA0B;AAAA,IAC/B,CAAC,KAAK,SAAS;AACb,UAAI,IAAI,IAAI,CAAC;AACb,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6313.1.618a49a6b4",
3
+ "version": "0.6.6-develop.6317.1.b3be10ab84",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -246,16 +246,16 @@
246
246
  "zod": "^4.4.3"
247
247
  },
248
248
  "peerDependencies": {
249
- "@open-mercato/ai-assistant": "0.6.6-develop.6313.1.618a49a6b4",
250
- "@open-mercato/shared": "0.6.6-develop.6313.1.618a49a6b4",
251
- "@open-mercato/ui": "0.6.6-develop.6313.1.618a49a6b4",
249
+ "@open-mercato/ai-assistant": "0.6.6-develop.6317.1.b3be10ab84",
250
+ "@open-mercato/shared": "0.6.6-develop.6317.1.b3be10ab84",
251
+ "@open-mercato/ui": "0.6.6-develop.6317.1.b3be10ab84",
252
252
  "react": "^19.0.0",
253
253
  "react-dom": "^19.0.0"
254
254
  },
255
255
  "devDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6313.1.618a49a6b4",
257
- "@open-mercato/shared": "0.6.6-develop.6313.1.618a49a6b4",
258
- "@open-mercato/ui": "0.6.6-develop.6313.1.618a49a6b4",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6317.1.b3be10ab84",
257
+ "@open-mercato/shared": "0.6.6-develop.6317.1.b3be10ab84",
258
+ "@open-mercato/ui": "0.6.6-develop.6317.1.b3be10ab84",
259
259
  "@testing-library/dom": "^10.4.1",
260
260
  "@testing-library/jest-dom": "^6.9.1",
261
261
  "@testing-library/react": "^16.3.1",
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Shared helpers for customers AI tool packs.
3
+ *
4
+ * Mirrors the catalog precedent (`packages/core/src/modules/catalog/ai-tools/_shared.ts`):
5
+ * the company and people packs previously carried byte-for-byte-identical copies
6
+ * of the date-to-ISO helper, the `resolveEm` / `buildScope` accessors, the
7
+ * list-row → summary mapper, and the related-records builder
8
+ * (addresses / activities / notes / tasks / interactions / tags / deals, plus a
9
+ * companies-only `people` mapper). Centralizing them here gives both packs one
10
+ * source of truth so a change to the related-records output shape no longer has
11
+ * to be applied — and kept in sync — in two places.
12
+ *
13
+ * This is a pure internal refactor: tool names, input schemas, `requiredFeatures`,
14
+ * and emitted output shapes stay identical.
15
+ */
16
+ import type { EntityManager } from '@mikro-orm/postgresql'
17
+ import type { AiToolExecutionContext } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
18
+ import type { CustomersToolContext } from './types'
19
+
20
+ export function toIso(value: unknown): string | null {
21
+ if (!value) return null
22
+ const dt = value instanceof Date ? value : new Date(String(value))
23
+ if (Number.isNaN(dt.getTime())) return null
24
+ return dt.toISOString()
25
+ }
26
+
27
+ export function resolveEm(ctx: CustomersToolContext | AiToolExecutionContext): EntityManager {
28
+ return ctx.container.resolve<EntityManager>('em')
29
+ }
30
+
31
+ export function buildScope(ctx: CustomersToolContext | AiToolExecutionContext, tenantId: string) {
32
+ return {
33
+ tenantId,
34
+ organizationId: ctx.organizationId,
35
+ }
36
+ }
37
+
38
+ /* -------------------------------------------------------------------------- */
39
+ /* List-row summary mapper */
40
+ /* -------------------------------------------------------------------------- */
41
+
42
+ export type CustomerListApiItemBase = {
43
+ id?: string
44
+ display_name?: string | null
45
+ displayName?: string | null
46
+ primary_email?: string | null
47
+ primaryEmail?: string | null
48
+ primary_phone?: string | null
49
+ primaryPhone?: string | null
50
+ status?: string | null
51
+ lifecycle_stage?: string | null
52
+ lifecycleStage?: string | null
53
+ source?: string | null
54
+ owner_user_id?: string | null
55
+ ownerUserId?: string | null
56
+ organization_id?: string | null
57
+ organizationId?: string | null
58
+ tenant_id?: string | null
59
+ tenantId?: string | null
60
+ created_at?: string | null
61
+ createdAt?: string | null
62
+ }
63
+
64
+ export type CustomerListSummary = {
65
+ id: string | undefined
66
+ displayName: string | null
67
+ primaryEmail: string | null
68
+ primaryPhone: string | null
69
+ status: string | null
70
+ lifecycleStage: string | null
71
+ source: string | null
72
+ ownerUserId: string | null
73
+ organizationId: string | null
74
+ tenantId: string | null
75
+ createdAt: string | null
76
+ }
77
+
78
+ export function toCustomerListSummary(row: CustomerListApiItemBase): CustomerListSummary {
79
+ const createdAtRaw = row.created_at ?? row.createdAt ?? null
80
+ const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
81
+ return {
82
+ id: row.id,
83
+ displayName: row.display_name ?? row.displayName ?? null,
84
+ primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
85
+ primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
86
+ status: row.status ?? null,
87
+ lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
88
+ source: row.source ?? null,
89
+ ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
90
+ organizationId: row.organization_id ?? row.organizationId ?? null,
91
+ tenantId: row.tenant_id ?? row.tenantId ?? null,
92
+ createdAt,
93
+ }
94
+ }
95
+
96
+ /* -------------------------------------------------------------------------- */
97
+ /* Related-records mappers */
98
+ /* -------------------------------------------------------------------------- */
99
+
100
+ function asRows(value: unknown): Array<Record<string, unknown>> {
101
+ return Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []
102
+ }
103
+
104
+ export function mapAddresses(rows: Array<Record<string, unknown>>) {
105
+ return rows.map((address) => ({
106
+ id: address.id,
107
+ name: address.name ?? null,
108
+ purpose: address.purpose ?? null,
109
+ addressLine1: address.addressLine1 ?? null,
110
+ addressLine2: address.addressLine2 ?? null,
111
+ city: address.city ?? null,
112
+ region: address.region ?? null,
113
+ postalCode: address.postalCode ?? null,
114
+ country: address.country ?? null,
115
+ isPrimary: !!address.isPrimary,
116
+ }))
117
+ }
118
+
119
+ export function mapActivities(rows: Array<Record<string, unknown>>) {
120
+ return rows.map((activity) => ({
121
+ id: activity.id,
122
+ activityType: activity.activityType,
123
+ subject: activity.subject ?? null,
124
+ body: activity.body ?? null,
125
+ occurredAt: toIso(activity.occurredAt),
126
+ createdAt: toIso(activity.createdAt),
127
+ }))
128
+ }
129
+
130
+ export function mapNotes(rows: Array<Record<string, unknown>>) {
131
+ return rows.map((comment) => ({
132
+ id: comment.id,
133
+ body: comment.body,
134
+ authorUserId: comment.authorUserId ?? null,
135
+ createdAt: toIso(comment.createdAt),
136
+ }))
137
+ }
138
+
139
+ export function mapTasks(rows: Array<Record<string, unknown>>) {
140
+ return rows.map((task) => ({
141
+ id: task.id,
142
+ todoId: task.todoId ?? task.id,
143
+ todoSource: task.todoSource ?? null,
144
+ createdAt: toIso(task.createdAt),
145
+ }))
146
+ }
147
+
148
+ export function mapInteractions(rows: Array<Record<string, unknown>>) {
149
+ return rows.map((interaction) => ({
150
+ id: interaction.id,
151
+ interactionType: interaction.interactionType,
152
+ title: interaction.title ?? null,
153
+ status: interaction.status,
154
+ scheduledAt: toIso(interaction.scheduledAt),
155
+ occurredAt: toIso(interaction.occurredAt),
156
+ }))
157
+ }
158
+
159
+ export type RelatedTag = { id: string; slug: string; label: string; color: string | null }
160
+
161
+ export function mapTags(rows: Array<Record<string, unknown>>): RelatedTag[] {
162
+ return rows
163
+ .map((tag) => {
164
+ if (!tag || typeof tag !== 'object') return null
165
+ const id = typeof tag.id === 'string' ? tag.id : null
166
+ const label = typeof tag.label === 'string' ? tag.label : null
167
+ if (!id || !label) return null
168
+ const slug = typeof tag.slug === 'string' ? tag.slug : label
169
+ const color = typeof tag.color === 'string' ? tag.color : null
170
+ return { id, slug, label, color }
171
+ })
172
+ .filter((entry): entry is RelatedTag => entry !== null)
173
+ }
174
+
175
+ export type RelatedDeal = {
176
+ id: string
177
+ title: string
178
+ status: string | null
179
+ pipelineStageId: string | null
180
+ valueAmount: string | null
181
+ valueCurrency: string | null
182
+ }
183
+
184
+ export function mapDeals(rows: Array<Record<string, unknown>>): RelatedDeal[] {
185
+ return rows
186
+ .map((deal) => {
187
+ if (!deal || typeof deal !== 'object') return null
188
+ const id = typeof deal.id === 'string' ? deal.id : null
189
+ if (!id) return null
190
+ return {
191
+ id,
192
+ title: typeof deal.title === 'string' ? deal.title : '',
193
+ status: typeof deal.status === 'string' ? deal.status : null,
194
+ pipelineStageId: typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
195
+ valueAmount:
196
+ typeof deal.valueAmount === 'string'
197
+ ? deal.valueAmount
198
+ : deal.valueAmount === null || deal.valueAmount === undefined
199
+ ? null
200
+ : String(deal.valueAmount),
201
+ valueCurrency: typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
202
+ }
203
+ })
204
+ .filter((value): value is RelatedDeal => value !== null)
205
+ }
206
+
207
+ export type RelatedPerson = {
208
+ id: string
209
+ displayName: string
210
+ primaryEmail: string | null
211
+ primaryPhone: string | null
212
+ jobTitle: string | null
213
+ department: string | null
214
+ }
215
+
216
+ export function mapPeople(rows: Array<Record<string, unknown>>): RelatedPerson[] {
217
+ return rows
218
+ .map((person) => {
219
+ if (!person || typeof person !== 'object') return null
220
+ const id = typeof person.id === 'string' ? person.id : null
221
+ const displayName = typeof person.displayName === 'string' ? person.displayName : null
222
+ if (!id || !displayName) return null
223
+ return {
224
+ id,
225
+ displayName,
226
+ primaryEmail: typeof person.primaryEmail === 'string' ? person.primaryEmail : null,
227
+ primaryPhone: typeof person.primaryPhone === 'string' ? person.primaryPhone : null,
228
+ jobTitle: typeof person.jobTitle === 'string' ? person.jobTitle : null,
229
+ department: typeof person.department === 'string' ? person.department : null,
230
+ }
231
+ })
232
+ .filter((value): value is RelatedPerson => value !== null)
233
+ }
234
+
235
+ export type CustomerRelatedRecords = {
236
+ addresses: ReturnType<typeof mapAddresses>
237
+ activities: ReturnType<typeof mapActivities>
238
+ notes: ReturnType<typeof mapNotes>
239
+ tasks: ReturnType<typeof mapTasks>
240
+ interactions: ReturnType<typeof mapInteractions>
241
+ tags: RelatedTag[]
242
+ deals: RelatedDeal[]
243
+ people?: RelatedPerson[]
244
+ }
245
+
246
+ /**
247
+ * Builds the related-records block from a customers detail API payload
248
+ * (`addresses`, `activities`, `comments`, `todos`, `interactions`, `tags`,
249
+ * `deals`, and — for companies — `people`). The key order matches the
250
+ * pre-refactor literals so the emitted shape is unchanged. Pass
251
+ * `includePeople: true` to add the companies-only `people` collection.
252
+ */
253
+ export function buildRelatedRecords(
254
+ data: Record<string, unknown>,
255
+ options: { includePeople?: boolean } = {},
256
+ ): CustomerRelatedRecords {
257
+ const related: CustomerRelatedRecords = {
258
+ addresses: mapAddresses(asRows(data.addresses)),
259
+ activities: mapActivities(asRows(data.activities)),
260
+ notes: mapNotes(asRows(data.comments)),
261
+ tasks: mapTasks(asRows(data.todos)),
262
+ interactions: mapInteractions(asRows(data.interactions)),
263
+ tags: mapTags(asRows(data.tags)),
264
+ deals: mapDeals(asRows(data.deals)),
265
+ }
266
+ if (options.includePeople) {
267
+ related.people = mapPeople(asRows(data.people))
268
+ }
269
+ return related
270
+ }
@@ -19,6 +19,12 @@ import {
19
19
  type AiToolExecutionContext,
20
20
  } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
21
21
  import { assertTenantScope, type CustomersAiToolDefinition, type CustomersToolContext } from './types'
22
+ import {
23
+ buildRelatedRecords,
24
+ toCustomerListSummary,
25
+ toIso,
26
+ type CustomerRelatedRecords,
27
+ } from './_shared'
22
28
 
23
29
  const listCompaniesInput = z
24
30
  .object({
@@ -108,27 +114,13 @@ const listCompaniesTool = defineApiBackedAiTool<
108
114
  const data = (response.data ?? {}) as ListCompaniesApiResponse
109
115
  const rawItems: ListCompaniesApiItem[] = Array.isArray(data.items) ? data.items : []
110
116
  return {
111
- items: rawItems.map((row) => {
112
- const createdAtRaw = row.created_at ?? row.createdAt ?? null
113
- const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
114
- return {
115
- id: row.id,
116
- displayName: row.display_name ?? row.displayName ?? null,
117
- primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
118
- primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
119
- status: row.status ?? null,
120
- lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
121
- source: row.source ?? null,
122
- ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
123
- organizationId: row.organization_id ?? row.organizationId ?? null,
124
- tenantId: row.tenant_id ?? row.tenantId ?? null,
125
- domain: row.domain ?? null,
126
- websiteUrl: row.website_url ?? row.websiteUrl ?? null,
127
- industry: row.industry ?? null,
128
- sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,
129
- createdAt,
130
- }
131
- }),
117
+ items: rawItems.map((row) => ({
118
+ ...toCustomerListSummary(row),
119
+ domain: row.domain ?? null,
120
+ websiteUrl: row.website_url ?? row.websiteUrl ?? null,
121
+ industry: row.industry ?? null,
122
+ sizeBucket: row.size_bucket ?? row.sizeBucket ?? null,
123
+ })),
132
124
  total: typeof data.total === 'number' ? data.total : 0,
133
125
  limit,
134
126
  offset,
@@ -146,13 +138,6 @@ const getCompanyInput = z.object({
146
138
 
147
139
  type GetCompanyInput = z.infer<typeof getCompanyInput>
148
140
 
149
- function toIsoCompany(value: unknown): string | null {
150
- if (!value) return null
151
- const dt = value instanceof Date ? value : new Date(String(value))
152
- if (Number.isNaN(dt.getTime())) return null
153
- return dt.toISOString()
154
- }
155
-
156
141
  const getCompanyTool: CustomersAiToolDefinition = {
157
142
  name: 'customers.get_company',
158
143
  displayName: 'Get company',
@@ -193,134 +178,9 @@ const getCompanyTool: CustomersAiToolDefinition = {
193
178
  const profileRow = (data.profile ?? null) as Record<string, unknown> | null
194
179
  const customFields = (data.customFields ?? {}) as Record<string, unknown>
195
180
 
196
- let related: Record<string, unknown> | null = null
181
+ let related: CustomerRelatedRecords | null = null
197
182
  if (includeRelated) {
198
- const addresses = Array.isArray(data.addresses) ? (data.addresses as Array<Record<string, unknown>>) : []
199
- const activities = Array.isArray(data.activities) ? (data.activities as Array<Record<string, unknown>>) : []
200
- const notes = Array.isArray(data.comments) ? (data.comments as Array<Record<string, unknown>>) : []
201
- const todos = Array.isArray(data.todos) ? (data.todos as Array<Record<string, unknown>>) : []
202
- const interactions = Array.isArray(data.interactions) ? (data.interactions as Array<Record<string, unknown>>) : []
203
- const tagsRows = Array.isArray(data.tags) ? (data.tags as Array<Record<string, unknown>>) : []
204
- const dealsRows = Array.isArray(data.deals) ? (data.deals as Array<Record<string, unknown>>) : []
205
- const peopleRows = Array.isArray(data.people) ? (data.people as Array<Record<string, unknown>>) : []
206
- related = {
207
- addresses: addresses.map((address) => ({
208
- id: address.id,
209
- name: address.name ?? null,
210
- purpose: address.purpose ?? null,
211
- addressLine1: address.addressLine1 ?? null,
212
- addressLine2: address.addressLine2 ?? null,
213
- city: address.city ?? null,
214
- region: address.region ?? null,
215
- postalCode: address.postalCode ?? null,
216
- country: address.country ?? null,
217
- isPrimary: !!address.isPrimary,
218
- })),
219
- activities: activities.map((activity) => ({
220
- id: activity.id,
221
- activityType: activity.activityType,
222
- subject: activity.subject ?? null,
223
- body: activity.body ?? null,
224
- occurredAt: toIsoCompany(activity.occurredAt),
225
- createdAt: toIsoCompany(activity.createdAt),
226
- })),
227
- notes: notes.map((comment) => ({
228
- id: comment.id,
229
- body: comment.body,
230
- authorUserId: comment.authorUserId ?? null,
231
- createdAt: toIsoCompany(comment.createdAt),
232
- })),
233
- tasks: todos.map((task) => ({
234
- id: task.id,
235
- todoId: task.todoId ?? task.id,
236
- todoSource: task.todoSource ?? null,
237
- createdAt: toIsoCompany(task.createdAt),
238
- })),
239
- interactions: interactions.map((interaction) => ({
240
- id: interaction.id,
241
- interactionType: interaction.interactionType,
242
- title: interaction.title ?? null,
243
- status: interaction.status,
244
- scheduledAt: toIsoCompany(interaction.scheduledAt),
245
- occurredAt: toIsoCompany(interaction.occurredAt),
246
- })),
247
- tags: tagsRows
248
- .map((tag) => {
249
- if (!tag || typeof tag !== 'object') return null
250
- const id = typeof tag.id === 'string' ? tag.id : null
251
- const label = typeof tag.label === 'string' ? tag.label : null
252
- if (!id || !label) return null
253
- const slug = typeof tag.slug === 'string' ? tag.slug : label
254
- const color = typeof tag.color === 'string' ? tag.color : null
255
- return { id, slug, label, color }
256
- })
257
- .filter(
258
- (entry): entry is { id: string; slug: string; label: string; color: string | null } =>
259
- entry !== null,
260
- ),
261
- deals: dealsRows
262
- .map((deal) => {
263
- if (!deal || typeof deal !== 'object') return null
264
- const id = typeof deal.id === 'string' ? deal.id : null
265
- if (!id) return null
266
- return {
267
- id,
268
- title: typeof deal.title === 'string' ? deal.title : '',
269
- status: typeof deal.status === 'string' ? deal.status : null,
270
- pipelineStageId:
271
- typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
272
- valueAmount:
273
- typeof deal.valueAmount === 'string'
274
- ? deal.valueAmount
275
- : deal.valueAmount === null || deal.valueAmount === undefined
276
- ? null
277
- : String(deal.valueAmount),
278
- valueCurrency:
279
- typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
280
- }
281
- })
282
- .filter(
283
- (
284
- value,
285
- ): value is {
286
- id: string
287
- title: string
288
- status: string | null
289
- pipelineStageId: string | null
290
- valueAmount: string | null
291
- valueCurrency: string | null
292
- } => value !== null,
293
- ),
294
- people: peopleRows
295
- .map((person) => {
296
- if (!person || typeof person !== 'object') return null
297
- const id = typeof person.id === 'string' ? person.id : null
298
- const displayName = typeof person.displayName === 'string' ? person.displayName : null
299
- if (!id || !displayName) return null
300
- return {
301
- id,
302
- displayName,
303
- primaryEmail:
304
- typeof person.primaryEmail === 'string' ? person.primaryEmail : null,
305
- primaryPhone:
306
- typeof person.primaryPhone === 'string' ? person.primaryPhone : null,
307
- jobTitle: typeof person.jobTitle === 'string' ? person.jobTitle : null,
308
- department: typeof person.department === 'string' ? person.department : null,
309
- }
310
- })
311
- .filter(
312
- (
313
- value,
314
- ): value is {
315
- id: string
316
- displayName: string
317
- primaryEmail: string | null
318
- primaryPhone: string | null
319
- jobTitle: string | null
320
- department: string | null
321
- } => value !== null,
322
- ),
323
- }
183
+ related = buildRelatedRecords(data, { includePeople: true })
324
184
  }
325
185
  return {
326
186
  found: true as const,
@@ -336,8 +196,8 @@ const getCompanyTool: CustomersAiToolDefinition = {
336
196
  ownerUserId: companyRow.ownerUserId ?? null,
337
197
  organizationId: companyRow.organizationId ?? null,
338
198
  tenantId: companyRow.tenantId ?? null,
339
- createdAt: toIsoCompany(companyRow.createdAt),
340
- updatedAt: toIsoCompany(companyRow.updatedAt),
199
+ createdAt: toIso(companyRow.createdAt),
200
+ updatedAt: toIso(companyRow.updatedAt),
341
201
  },
342
202
  profile: profileRow
343
203
  ? {