terse-cli 0.2.9 → 0.2.11

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.
@@ -107,24 +107,84 @@ function attioAttributeBaseType(attr) {
107
107
  }
108
108
  return "unknown";
109
109
  }
110
- function attioValueTypeLines() {
111
- return ["export type AttioSelectOption = {", " id: string", " title: string", " is_archived: boolean", "}"];
110
+ function attioValueTypeLines(objects = [], lists = []) {
111
+ const lines = [
112
+ "export type AttioSelectOption = {",
113
+ " id: string",
114
+ " title: string",
115
+ " is_archived: boolean",
116
+ "}",
117
+ "",
118
+ 'export type AttioActorReferenceInput = string | { workspace_member_email_address: string } | { referenced_actor_type: "workspace-member"; referenced_actor_id: string }',
119
+ "export type AttioRecordReferenceInput = { target_object: string; target_record_id: string } | Record<string, unknown>",
120
+ "export type AttioActorReference = { referenced_actor_type: string; referenced_actor_id: string | null }",
121
+ "export type AttioRecordReferenceValue = { target_object: string; target_record_id: string }",
122
+ "export type AttioCurrencyValue = { currency_value: number; currency_code: string | null }"
123
+ ];
124
+ const usedConstNames = new Set();
125
+ for (const object of [...objects, ...lists]) {
126
+ for (const attr of object.attributes) {
127
+ if (!attr.options || attr.options.length === 0)
128
+ continue;
129
+ let attrName = toGeneratedIdentifier(attr.title || attr.api_slug, "Attribute");
130
+ if (attrName.startsWith(object.staticName) && attrName.length > object.staticName.length) {
131
+ attrName = attrName.slice(object.staticName.length);
132
+ }
133
+ let constName = `Attio${object.staticName}${attrName}`;
134
+ while (usedConstNames.has(constName))
135
+ constName += "_";
136
+ usedConstNames.add(constName);
137
+ lines.push("", `export const ${constName} = {`);
138
+ const usedKeys = new Set();
139
+ for (const title of attr.options) {
140
+ let key = toGeneratedIdentifier(title, "Option");
141
+ while (usedKeys.has(key))
142
+ key += "_";
143
+ usedKeys.add(key);
144
+ lines.push(` ${key}: "${escapeString(title)}",`);
145
+ }
146
+ lines.push("} as const");
147
+ }
148
+ }
149
+ return lines;
150
+ }
151
+ function attioIsMultiValue(attr) {
152
+ if (typeof attr.is_multiselect === "boolean")
153
+ return attr.is_multiselect;
154
+ return isProbablyAttioMultiValue(attr);
155
+ }
156
+ function attioAttributeInputBaseType(attr) {
157
+ const type = (attr.type || "").toLowerCase();
158
+ if (type.includes("actor"))
159
+ return "AttioActorReferenceInput";
160
+ if (type.includes("record") && type.includes("reference"))
161
+ return "AttioRecordReferenceInput";
162
+ if ((type.includes("select") || type.includes("status")) && attr.options && attr.options.length > 0) {
163
+ return renderStringLiteralUnion(attr.options);
164
+ }
165
+ return attioAttributeBaseType(attr);
112
166
  }
113
167
  function attioAttributeRecordBaseType(attr) {
114
168
  const type = (attr.type || "").toLowerCase();
115
169
  if (type.includes("select") || type.includes("status"))
116
170
  return "AttioSelectOption";
171
+ if (type.includes("actor"))
172
+ return "AttioActorReference";
173
+ if (type.includes("record") && type.includes("reference"))
174
+ return "AttioRecordReferenceValue";
175
+ if (type.includes("currency"))
176
+ return "AttioCurrencyValue";
117
177
  return attioAttributeBaseType(attr);
118
178
  }
119
179
  function attioAttributeInputTsType(attr) {
120
- const baseType = attioAttributeBaseType(attr);
121
- if (!isProbablyAttioMultiValue(attr))
180
+ const baseType = attioAttributeInputBaseType(attr);
181
+ if (!attioIsMultiValue(attr))
122
182
  return baseType;
123
183
  return baseType.includes("|") ? `(${baseType})[]` : `${baseType}[]`;
124
184
  }
125
185
  function attioAttributeRecordTsType(attr) {
126
186
  const baseType = attioAttributeRecordBaseType(attr);
127
- if (!isProbablyAttioMultiValue(attr))
187
+ if (!attioIsMultiValue(attr))
128
188
  return baseType;
129
189
  return baseType.includes("|") ? `(${baseType})[]` : `${baseType}[]`;
130
190
  }
@@ -363,35 +423,60 @@ function buildGeneratedAttioObjects(instances) {
363
423
  staticName += "_";
364
424
  usedNames.add(staticName);
365
425
  const attributes = (object.attributes || []).filter((attr) => !!attr.api_slug);
366
- const attributeSource = attributes.length === 0
367
- ? "[]"
368
- : `[\n${attributes
369
- .map(attr => {
370
- const fields = [
371
- `apiSlug: "${escapeString(attr.api_slug)}"`,
372
- attr.title ? `title: "${escapeString(attr.title)}"` : undefined,
373
- attr.type ? `type: "${escapeString(attr.type)}"` : undefined,
374
- attr.is_required !== undefined ? `isRequired: ${attr.is_required ? "true" : "false"}` : undefined,
375
- attr.is_unique !== undefined ? `isUnique: ${attr.is_unique ? "true" : "false"}` : undefined
376
- ]
377
- .filter(Boolean)
378
- .join(", ");
379
- return ` { ${fields} }`;
380
- })
381
- .join(",\n")}\n ]`;
382
426
  return {
383
427
  staticName,
384
428
  apiSlug: object.api_slug,
385
429
  objectId: object.id.object_id,
386
430
  singularNoun: object.singular_noun,
387
- attributeSource,
431
+ attributeSource: renderAttioAttributeSource(attributes),
388
432
  recordValuesType: renderAttioObjectValueShape(attributes, "record"),
389
433
  inputValuesType: renderAttioObjectValueShape(attributes, "input"),
390
434
  attributes
391
435
  };
392
436
  });
393
437
  }
394
- function buildAttioRuntimeLines(objects) {
438
+ function buildGeneratedAttioLists(instances) {
439
+ const lists = instances[0]?.lists ?? [];
440
+ const usedNames = new Set();
441
+ return lists.map(list => {
442
+ let staticName = toGeneratedIdentifier(list.name || list.api_slug || "List", "AttioList");
443
+ while (usedNames.has(staticName))
444
+ staticName += "_";
445
+ usedNames.add(staticName);
446
+ const attributes = (list.attributes || []).filter((attr) => !!attr.api_slug);
447
+ const parentObject = Array.isArray(list.parent_object) ? list.parent_object[0] || "" : list.parent_object || "";
448
+ return {
449
+ staticName,
450
+ apiSlug: list.api_slug,
451
+ name: list.name,
452
+ listId: list.id.list_id,
453
+ parentObject,
454
+ attributeSource: renderAttioAttributeSource(attributes),
455
+ entryValuesType: renderAttioObjectValueShape(attributes, "input"),
456
+ entryRecordValuesType: renderAttioObjectValueShape(attributes, "record"),
457
+ attributes
458
+ };
459
+ });
460
+ }
461
+ function renderAttioAttributeSource(attributes) {
462
+ if (attributes.length === 0)
463
+ return "[]";
464
+ return `[\n${attributes
465
+ .map(attr => {
466
+ const fields = [
467
+ `apiSlug: "${escapeString(attr.api_slug)}"`,
468
+ attr.title ? `title: "${escapeString(attr.title)}"` : undefined,
469
+ attr.type ? `type: "${escapeString(attr.type)}"` : undefined,
470
+ attr.is_required !== undefined ? `isRequired: ${attr.is_required ? "true" : "false"}` : undefined,
471
+ attr.is_unique !== undefined ? `isUnique: ${attr.is_unique ? "true" : "false"}` : undefined
472
+ ]
473
+ .filter(Boolean)
474
+ .join(", ");
475
+ return ` { ${fields} }`;
476
+ })
477
+ .join(",\n")}\n ]`;
478
+ }
479
+ function buildAttioRuntimeLines(objects, lists) {
395
480
  const lines = [];
396
481
  lines.push("const __attioMetadataKeys = new Set([");
397
482
  lines.push(' "active_from",');
@@ -403,7 +488,7 @@ function buildAttioRuntimeLines(objects) {
403
488
  lines.push("const __attioMultiValueAttributeSlugsByObject: Record<string, readonly string[]> = {");
404
489
  for (const object of objects) {
405
490
  const multiValueSlugs = object.attributes
406
- .filter(isProbablyAttioMultiValue)
491
+ .filter(attioIsMultiValue)
407
492
  .map(attr => `"${escapeString(attr.api_slug)}"`)
408
493
  .join(", ");
409
494
  lines.push(` "${escapeString(object.apiSlug)}": [${multiValueSlugs}],`);
@@ -466,6 +551,56 @@ function buildAttioRuntimeLines(objects) {
466
551
  lines.push(" return flattenedValues as TValues");
467
552
  lines.push("}");
468
553
  lines.push("");
554
+ lines.push("const __attioMultiValueEntrySlugsByList: Record<string, readonly string[]> = {");
555
+ for (const list of lists) {
556
+ const multiValueSlugs = list.attributes
557
+ .filter(attioIsMultiValue)
558
+ .map(attr => `"${escapeString(attr.api_slug)}"`)
559
+ .join(", ");
560
+ lines.push(` "${escapeString(list.apiSlug)}": [${multiValueSlugs}],`);
561
+ lines.push(` "${escapeString(list.listId)}": [${multiValueSlugs}],`);
562
+ }
563
+ lines.push("}");
564
+ lines.push("");
565
+ lines.push("function __getAttioEntryValues<TValues extends Record<string, unknown>>(listKey: string, rawEntryValues: unknown): TValues {");
566
+ lines.push(' if (!rawEntryValues || typeof rawEntryValues !== "object" || Array.isArray(rawEntryValues)) return {} as TValues');
567
+ lines.push(" const flattenedValues: Record<string, unknown> = {}");
568
+ lines.push(" const multiValueSlugs = __attioMultiValueEntrySlugsByList[listKey] || []");
569
+ lines.push(" for (const [attributeSlug, rawValue] of Object.entries(rawEntryValues)) {");
570
+ lines.push(" flattenedValues[attributeSlug] = __flattenAttioAttributeValue(rawValue, multiValueSlugs.includes(attributeSlug))");
571
+ lines.push(" }");
572
+ lines.push(" return flattenedValues as TValues");
573
+ lines.push("}");
574
+ lines.push("");
575
+ lines.push("function __requireAttioListParentObject(list: unknown, parentObjectSlug: string | null | undefined): string {");
576
+ lines.push(" if (parentObjectSlug) return parentObjectSlug");
577
+ lines.push(' if (list && typeof list === "object" && "parentObject" in (list as Record<string, unknown>)) {');
578
+ lines.push(" const parentObject = (list as { parentObject?: unknown }).parentObject");
579
+ lines.push(' if (typeof parentObject === "string" && parentObject.length > 0) return parentObject');
580
+ lines.push(" }");
581
+ lines.push(' throw new Error("parentObjectSlug is required when the list is passed as a plain ID/slug rather than a generated AttioList constant.")');
582
+ lines.push("}");
583
+ lines.push("");
584
+ lines.push("function __enhanceAttioListEntry<TList>(list: TList, entry: unknown): AttioListEntryFor<TList> {");
585
+ lines.push(' const raw = (entry && typeof entry === "object" ? entry : {}) as { entry_values?: unknown }');
586
+ lines.push(" return { ...(raw as object), entry_values: __getAttioEntryValues(__normalizeAttioObjectSlug(list), raw.entry_values) } as AttioListEntryFor<TList>");
587
+ lines.push("}");
588
+ lines.push("");
589
+ lines.push('function __enhanceAttioListEntriesResult<TList>(list: TList, result: ToolOutputByName["attio_lists"]): AttioListEntriesResult<TList> {');
590
+ lines.push(" const entries = (result.entries || []).map(entry => __enhanceAttioListEntry(list, entry))");
591
+ lines.push(" return { entries, count: entries.length, offset: result.offset ?? 0 }");
592
+ lines.push("}");
593
+ lines.push("");
594
+ lines.push('function __enhanceAttioListEntryResult<TList>(list: TList, result: ToolOutputByName["attio_lists"]): AttioListEntryResult<TList> {');
595
+ lines.push(' if (!result.entry) throw new Error("Attio returned a response without the expected list entry payload.")');
596
+ lines.push(" return __enhanceAttioListEntry(list, result.entry)");
597
+ lines.push("}");
598
+ lines.push("");
599
+ lines.push('function __enhanceAttioHistoryResult<TValue>(result: ToolOutputByName["attio_records"]): AttioAttributeHistoryResult<TValue> {');
600
+ lines.push(" const history = (result.history || []).map(entry => ({ active_from: entry.active_from, active_until: entry.active_until, value: __flattenAttioLeafValue(entry) as TValue }))");
601
+ lines.push(" return { history, count: history.length }");
602
+ lines.push("}");
603
+ lines.push("");
469
604
  lines.push('registerEventTransform("attio", (event) => {');
470
605
  lines.push(" const e = event as { record?: { values?: unknown }; resourceIds?: { object_id?: string } }");
471
606
  lines.push(" if (!e?.record?.values) return event");
@@ -480,6 +615,7 @@ function prepareAttioSection(instances, tools) {
480
615
  if (instances.length === 0)
481
616
  return sectionData([]);
482
617
  const objects = buildGeneratedAttioObjects(instances);
618
+ const lists = buildGeneratedAttioLists(instances);
483
619
  return sectionData([
484
620
  "registerEventTransform",
485
621
  "AttioOutputConfig",
@@ -519,8 +655,9 @@ function prepareAttioSection(instances, tools) {
519
655
  id: instances[0].id,
520
656
  skillToolType: buildSkillToolTypeForIntegration(tools, "attio"),
521
657
  objects,
522
- valueTypeLines: attioValueTypeLines(),
523
- runtimeLines: buildAttioRuntimeLines(objects)
658
+ lists,
659
+ valueTypeLines: attioValueTypeLines(objects, lists),
660
+ runtimeLines: buildAttioRuntimeLines(objects, lists)
524
661
  });
525
662
  }
526
663
  function prepareSnowflakeSection(instances, tools) {
@@ -609,12 +746,12 @@ function prepareToolsSection(tools, input) {
609
746
  attioPreludeLines.push("type __AttioPrimitive = string | number | boolean | null");
610
747
  attioPreludeLines.push("type __AttioStructuredValue = Record<string, unknown>");
611
748
  attioPreludeLines.push("type __AttioValue = __AttioPrimitive | __AttioStructuredValue | (__AttioPrimitive | __AttioStructuredValue)[]");
612
- attioPreludeLines.push("type __AttioFilterAtom<T> = T extends AttioSelectOption ? string : T");
749
+ attioPreludeLines.push("type __AttioFilterAtom<T> = T extends AttioSelectOption ? string : T extends AttioActorReference ? string : T extends AttioRecordReferenceValue ? string : T extends AttioCurrencyValue ? number : T");
613
750
  attioPreludeLines.push("type __AttioFilterShorthand<T> = T extends (infer U)[] ? __AttioFilterAtom<U> | __AttioFilterAtom<U>[] : __AttioFilterAtom<T>");
614
751
  attioPreludeLines.push("type __AttioFilterValue<T> = __AttioFilterShorthand<T> | { $eq?: __AttioFilterShorthand<T>; $contains?: string; $starts_with?: string; $ends_with?: string } | Record<string, unknown>");
615
752
  attioPreludeLines.push("type __AttioFilterExpression<TValues extends Record<string, unknown>> = Partial<{ [K in keyof TValues]: __AttioFilterValue<TValues[K]> }> & { $and?: Array<__AttioFilterExpression<TValues>>; $or?: Array<__AttioFilterExpression<TValues>> }");
616
- attioPreludeLines.push('type __AttioRecordBase = NonNullable<NonNullable<ToolOutputByName["attio_upsert_record"]["records"]>[number]>');
617
- attioPreludeLines.push('type __AttioRecordWithValues<TValues extends Record<string, unknown>> = Omit<__AttioRecordBase, "values"> & TValues & { values: TValues; attributes: TValues }');
753
+ attioPreludeLines.push("type __AttioRecordBase = { id?: { workspace_id?: string; object_id?: string; record_id?: string }; record_id?: string; web_url?: string; created_at?: string }");
754
+ attioPreludeLines.push("type __AttioRecordWithValues<TValues extends Record<string, unknown>> = __AttioRecordBase & TValues & { values: TValues; attributes: TValues }");
618
755
  attioPreludeLines.push("");
619
756
  if (attioGeneratedObjects.length > 0) {
620
757
  attioPreludeLines.push("export type AttioInputValuesByObject = {");
@@ -655,11 +792,32 @@ function prepareToolsSection(tools, input) {
655
792
  attioPreludeLines.push("export type AttioAttributeSlug<TObject extends GeneratedAttioObject = GeneratedAttioObject> = Extract<keyof AttioValuesFor<TObject>, string>");
656
793
  attioPreludeLines.push("export type AttioFilterFor<TObject extends GeneratedAttioObject = GeneratedAttioObject> = __AttioFilterExpression<AttioRecordValuesFor<TObject>>");
657
794
  attioPreludeLines.push("export type AttioRecordFor<TObject extends GeneratedAttioObject = GeneratedAttioObject> = __AttioRecordWithValues<AttioRecordValuesFor<TObject>>");
658
- attioPreludeLines.push("export type AttioQueryRecordsParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; filter?: AttioFilterFor<TObject> | null; limit?: number | null }");
795
+ attioPreludeLines.push("export type AttioQueryRecordsParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; filter?: AttioFilterFor<TObject> | null; limit?: number | null; offset?: number | null }");
796
+ attioPreludeLines.push("export type AttioSearchRecordsParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; query: string; limit?: number | null }");
797
+ attioPreludeLines.push("export type AttioGetRecordParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; recordId: string }");
798
+ attioPreludeLines.push("export type AttioCreateRecordParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; values: AttioValuesFor<TObject> }");
799
+ attioPreludeLines.push('export type AttioUpdateRecordParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; recordId: string; values: Partial<AttioValuesFor<TObject>>; multiselectMode?: "overwrite" | "append" | null }');
659
800
  attioPreludeLines.push("export type AttioUpsertRecordParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; matchingAttribute: AttioAttributeSlug<TObject>; records: AttioValuesFor<TObject>[] }");
660
- attioPreludeLines.push("export type AttioListObjectsParams = Record<string, never>");
661
- attioPreludeLines.push('export type AttioQueryRecordsResult<TObject extends GeneratedAttioObject = GeneratedAttioObject> = Omit<ToolOutputByName["attio_query_records"], "records"> & { records: Array<AttioRecordFor<TObject>> }');
662
- attioPreludeLines.push('export type AttioUpsertRecordResult<TObject extends GeneratedAttioObject = GeneratedAttioObject> = Omit<ToolOutputByName["attio_upsert_record"], "records"> & { records?: Array<AttioRecordFor<TObject>> }');
801
+ attioPreludeLines.push("export type AttioDeleteRecordParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; recordId: string }");
802
+ attioPreludeLines.push("export type AttioGetAttributeHistoryParams<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { object: TObject; recordId: string; attribute: AttioAttributeSlug<TObject>; limit?: number | null; offset?: number | null }");
803
+ attioPreludeLines.push("export type AttioListWorkspaceMembersParams = Record<string, never>");
804
+ attioPreludeLines.push("export type AttioGetWorkspaceMemberParams = { workspaceMemberId: string }");
805
+ attioPreludeLines.push('export type AttioWorkspaceMembersResult = ToolOutputByName["attio_workspace_members"]');
806
+ attioPreludeLines.push('export type AttioRecordsResult = ToolOutputByName["attio_records"]');
807
+ attioPreludeLines.push("export type AttioQueryRecordsResult<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { records: Array<AttioRecordFor<TObject>>; count: number; offset: number }");
808
+ attioPreludeLines.push('export type AttioSearchRecordsResult = { matches: NonNullable<AttioRecordsResult["matches"]>; count: number }');
809
+ attioPreludeLines.push("export type AttioUpsertRecordResult<TObject extends GeneratedAttioObject = GeneratedAttioObject> = { records: Array<AttioRecordFor<TObject>>; count: number }");
810
+ attioPreludeLines.push("export type AttioSingleRecordResult<TObject extends GeneratedAttioObject = GeneratedAttioObject> = AttioRecordFor<TObject>");
811
+ attioPreludeLines.push('export type AttioListsResult = ToolOutputByName["attio_lists"]');
812
+ attioPreludeLines.push("export type AttioEntryValuesFor<TList> = TList extends { __entryValues: infer TValues } ? TValues : Record<string, unknown>");
813
+ attioPreludeLines.push("export type AttioEntryRecordValuesFor<TList> = TList extends { __entryRecordValues: infer TValues extends Record<string, unknown> } ? TValues : Record<string, unknown>");
814
+ attioPreludeLines.push("export type AttioEntryFilterFor<TList> = __AttioFilterExpression<AttioEntryRecordValuesFor<TList>>");
815
+ attioPreludeLines.push("export type AttioListEntryFor<TList> = { id: { workspace_id: string; list_id: string; entry_id: string }; parent_record_id: string; parent_object: string; created_at: string; entry_values: AttioEntryRecordValuesFor<TList> }");
816
+ attioPreludeLines.push("export type AttioListEntriesResult<TList> = { entries: Array<AttioListEntryFor<TList>>; count: number; offset: number }");
817
+ attioPreludeLines.push("export type AttioListEntryResult<TList> = AttioListEntryFor<TList>");
818
+ attioPreludeLines.push("type __AttioSingleValue<T> = T extends (infer U)[] ? U : T");
819
+ attioPreludeLines.push("export type AttioAttributeHistoryEntryFor<TValue> = { active_from: string; active_until: string | null; value: TValue }");
820
+ attioPreludeLines.push("export type AttioAttributeHistoryResult<TValue = unknown> = { history: Array<AttioAttributeHistoryEntryFor<TValue>>; count: number }");
663
821
  attioPreludeLines.push("");
664
822
  attioPreludeLines.push("function __normalizeAttioObjectSlug(object: unknown): string {");
665
823
  attioPreludeLines.push(' if (object && typeof object === "object" && "apiSlug" in (object as Record<string, unknown>)) {');
@@ -674,6 +832,10 @@ function prepareToolsSection(tools, input) {
674
832
  attioPreludeLines.push(" return JSON.stringify(filter)");
675
833
  attioPreludeLines.push("}");
676
834
  attioPreludeLines.push("");
835
+ attioPreludeLines.push("function __serializeAttioValues(values: unknown): string {");
836
+ attioPreludeLines.push(" return JSON.stringify(values ?? {})");
837
+ attioPreludeLines.push("}");
838
+ attioPreludeLines.push("");
677
839
  attioPreludeLines.push("function __serializeAttioRecords(records: unknown): string {");
678
840
  attioPreludeLines.push(" if (!Array.isArray(records)) return JSON.stringify([])");
679
841
  attioPreludeLines.push(' return JSON.stringify(records.filter((record): record is Record<string, unknown> => typeof record === "object" && record !== null && !Array.isArray(record)))');
@@ -685,18 +847,28 @@ function prepareToolsSection(tools, input) {
685
847
  attioPreludeLines.push(" return { ...values, ...(record as __AttioRecordBase), values, attributes: values } as __AttioRecordWithValues<AttioRecordValuesFor<TObject>>");
686
848
  attioPreludeLines.push("}");
687
849
  attioPreludeLines.push("");
688
- attioPreludeLines.push('function __enhanceAttioQueryResult<TObject extends GeneratedAttioObject>(object: TObject, result: ToolOutputByName["attio_query_records"]): AttioQueryRecordsResult<TObject> {');
689
- attioPreludeLines.push(" return {");
690
- attioPreludeLines.push(" ...result,");
691
- attioPreludeLines.push(" records: (result.records || []).map(record => __enhanceAttioRecord(object, record)).filter(Boolean) as Array<AttioRecordFor<TObject>>,");
692
- attioPreludeLines.push(" }");
850
+ attioPreludeLines.push("function __requireAttioPayload<T>(value: T | null | undefined, what: string): T {");
851
+ attioPreludeLines.push(' if (value === null || value === undefined) throw new Error("Attio returned a response without the expected " + what + " payload.")');
852
+ attioPreludeLines.push(" return value");
693
853
  attioPreludeLines.push("}");
694
854
  attioPreludeLines.push("");
695
- attioPreludeLines.push('function __enhanceAttioUpsertResult<TObject extends GeneratedAttioObject>(object: TObject, result: ToolOutputByName["attio_upsert_record"]): AttioUpsertRecordResult<TObject> {');
696
- attioPreludeLines.push(" return {");
697
- attioPreludeLines.push(" ...result,");
698
- attioPreludeLines.push(" records: (result.records || []).map(record => __enhanceAttioRecord(object, record)).filter(Boolean) as Array<AttioRecordFor<TObject>>,");
699
- attioPreludeLines.push(" }");
855
+ attioPreludeLines.push('function __enhanceAttioQueryResult<TObject extends GeneratedAttioObject>(object: TObject, result: ToolOutputByName["attio_records"]): AttioQueryRecordsResult<TObject> {');
856
+ attioPreludeLines.push(" const records = (result.records || []).map(record => __enhanceAttioRecord(object, record)).filter(Boolean) as Array<AttioRecordFor<TObject>>");
857
+ attioPreludeLines.push(" return { records, count: records.length, offset: result.offset ?? 0 }");
858
+ attioPreludeLines.push("}");
859
+ attioPreludeLines.push("");
860
+ attioPreludeLines.push('function __enhanceAttioSearchResult(result: ToolOutputByName["attio_records"]): AttioSearchRecordsResult {');
861
+ attioPreludeLines.push(" const matches = result.matches ?? []");
862
+ attioPreludeLines.push(" return { matches, count: matches.length }");
863
+ attioPreludeLines.push("}");
864
+ attioPreludeLines.push("");
865
+ attioPreludeLines.push('function __enhanceAttioUpsertResult<TObject extends GeneratedAttioObject>(object: TObject, result: ToolOutputByName["attio_records"]): AttioUpsertRecordResult<TObject> {');
866
+ attioPreludeLines.push(" const records = (result.records || []).map(record => __enhanceAttioRecord(object, record)).filter(Boolean) as Array<AttioRecordFor<TObject>>");
867
+ attioPreludeLines.push(" return { records, count: records.length }");
868
+ attioPreludeLines.push("}");
869
+ attioPreludeLines.push("");
870
+ attioPreludeLines.push('function __enhanceAttioSingleRecordResult<TObject extends GeneratedAttioObject>(object: TObject, result: ToolOutputByName["attio_records"]): AttioSingleRecordResult<TObject> {');
871
+ attioPreludeLines.push(' return __requireAttioPayload(__enhanceAttioRecord(object, result.record), "record")');
700
872
  attioPreludeLines.push("}");
701
873
  attioPreludeLines.push("");
702
874
  }
@@ -753,43 +925,18 @@ function prepareToolsSection(tools, input) {
753
925
  const groups = rawGroups.map(group => ({
754
926
  key: group.key,
755
927
  integrationType: toolIntegrationToIntegrationType(group.integration),
756
- methods: group.tools.map(tool => {
928
+ methods: group.tools.flatMap(tool => {
757
929
  const methodName = toCamelCase(tool.displayName);
758
930
  const paramsType = toolNameToInterfaceName(tool.name);
759
931
  const normalizedParamsExpr = group.integration === "github" ? normalizeGitHubReposParams(tool.name) : "params";
760
- let generatedSignature;
761
- if (group.integration === "attio" && tool.name === "attio_query_records") {
762
- generatedSignature = `${methodName}<TObject extends GeneratedAttioObject>(params: AttioQueryRecordsParams<TObject>): Promise<AttioQueryRecordsResult<TObject>>`;
763
- }
764
- else if (group.integration === "attio" && tool.name === "attio_upsert_record") {
765
- generatedSignature = `${methodName}<TObject extends GeneratedAttioObject>(params: AttioUpsertRecordParams<TObject>): Promise<AttioUpsertRecordResult<TObject>>`;
766
- }
767
- else if (group.integration === "attio" && tool.name === "attio_list_objects") {
768
- generatedSignature = `${methodName}(params?: AttioListObjectsParams): Promise<ToolOutputByName["${escapeString(tool.name)}"]>`;
769
- }
770
- else {
771
- generatedSignature = `${methodName}(params: ${paramsType}): Promise<ToolOutputByName["${escapeString(tool.name)}"]>`;
932
+ if (group.integration === "attio" && group.integrationId) {
933
+ const attioMethods = buildAttioToolMethods(group.integrationId, tool.name);
934
+ if (attioMethods)
935
+ return attioMethods;
772
936
  }
937
+ const generatedSignature = `${methodName}(params: ${paramsType}): Promise<ToolOutputByName["${escapeString(tool.name)}"]>`;
773
938
  let runtimeLines;
774
- if (group.integration === "attio" && tool.name === "attio_query_records" && group.integrationId) {
775
- runtimeLines = [
776
- `${methodName}: <TObject extends GeneratedAttioObject>(params: AttioQueryRecordsParams<TObject>) =>`,
777
- ` TerseAgent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { objectSlug: __normalizeAttioObjectSlug(params.object), filter: __serializeAttioFilter(params.filter), limit: params.limit ?? null, integrationId: "${escapeString(group.integrationId)}" }).then(result => __enhanceAttioQueryResult(params.object, result)),`
778
- ];
779
- }
780
- else if (group.integration === "attio" && tool.name === "attio_upsert_record" && group.integrationId) {
781
- runtimeLines = [
782
- `${methodName}: <TObject extends GeneratedAttioObject>(params: AttioUpsertRecordParams<TObject>) =>`,
783
- ` TerseAgent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { objectSlug: __normalizeAttioObjectSlug(params.object), matchingAttribute: params.matchingAttribute, records: __serializeAttioRecords(params.records), integrationId: "${escapeString(group.integrationId)}" }).then(result => __enhanceAttioUpsertResult(params.object, result)),`
784
- ];
785
- }
786
- else if (group.integration === "attio" && tool.name === "attio_list_objects" && group.integrationId) {
787
- runtimeLines = [
788
- `${methodName}: (params: AttioListObjectsParams = {}) =>`,
789
- ` TerseAgent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { ...params, integrationId: "${escapeString(group.integrationId)}" }),`
790
- ];
791
- }
792
- else if (group.integrationId && hasAutoFillId(tool)) {
939
+ if (group.integrationId && hasAutoFillId(tool)) {
793
940
  runtimeLines = [
794
941
  `${methodName}: (params: ${paramsType}) =>`,
795
942
  ` TerseAgent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", { ...(${normalizedParamsExpr}), integrationId: "${escapeString(group.integrationId)}" }),`
@@ -801,11 +948,13 @@ function prepareToolsSection(tools, input) {
801
948
  ` TerseAgent.executeTool<ToolOutputByName["${escapeString(tool.name)}"]>("${escapeString(tool.name)}", ${normalizedParamsExpr}),`
802
949
  ];
803
950
  }
804
- return {
805
- description: tool.description || undefined,
806
- generatedSignature,
807
- runtimeLines
808
- };
951
+ return [
952
+ {
953
+ description: tool.description || undefined,
954
+ generatedSignature,
955
+ runtimeLines
956
+ }
957
+ ];
809
958
  })
810
959
  }));
811
960
  return {
@@ -818,6 +967,363 @@ function prepareToolsSection(tools, input) {
818
967
  }
819
968
  };
820
969
  }
970
+ function buildAttioRecordsMethods(integrationId) {
971
+ const id = escapeString(integrationId);
972
+ const call = (requestExpr) => `TerseAgent.executeTool<ToolOutputByName["attio_records"]>("attio_records", { integrationId: "${id}", request: ${requestExpr} })`;
973
+ const objectSlug = "objectSlug: __normalizeAttioObjectSlug(params.object)";
974
+ return [
975
+ {
976
+ description: "Query records of an Attio object, with optional filtering and limit/offset pagination (limit defaults to 20, max 500).",
977
+ generatedSignature: "queryRecords<TObject extends GeneratedAttioObject>(params: AttioQueryRecordsParams<TObject>): Promise<AttioQueryRecordsResult<TObject>>",
978
+ runtimeLines: [
979
+ "queryRecords: <TObject extends GeneratedAttioObject>(params: AttioQueryRecordsParams<TObject>) =>",
980
+ ` ${call(`{ action: "query", ${objectSlug}, filter: __serializeAttioFilter(params.filter), limit: params.limit ?? null, offset: params.offset ?? null }`)}.then(result => __enhanceAttioQueryResult(params.object, result)),`
981
+ ]
982
+ },
983
+ {
984
+ description: "Fuzzy-search records (max 25 matches) following Attio's in-product matching: names/domains/emails/phones on people and companies, the record label on other objects. Query with a distinctive substring of the target name; extra tokens absent from it can prevent matches. Results are eventually consistent (a just-created record may not be indexed) — use queryRecords for read-after-write.",
985
+ generatedSignature: "searchRecords<TObject extends GeneratedAttioObject>(params: AttioSearchRecordsParams<TObject>): Promise<AttioSearchRecordsResult>",
986
+ runtimeLines: [
987
+ "searchRecords: <TObject extends GeneratedAttioObject>(params: AttioSearchRecordsParams<TObject>) =>",
988
+ ` ${call(`{ action: "search", ${objectSlug}, query: params.query, limit: params.limit ?? null }`)}.then(result => __enhanceAttioSearchResult(result)),`
989
+ ]
990
+ },
991
+ {
992
+ description: "Fetch a single record by its ID.",
993
+ generatedSignature: "getRecord<TObject extends GeneratedAttioObject>(params: AttioGetRecordParams<TObject>): Promise<AttioSingleRecordResult<TObject>>",
994
+ runtimeLines: [
995
+ "getRecord: <TObject extends GeneratedAttioObject>(params: AttioGetRecordParams<TObject>) =>",
996
+ ` ${call(`{ action: "get", ${objectSlug}, recordId: params.recordId }`)}.then(result => __enhanceAttioSingleRecordResult(params.object, result)),`
997
+ ]
998
+ },
999
+ {
1000
+ description: "Create a new record. Unlike upsertRecord, no matching attribute is needed, so this works for objects without a unique writable attribute (e.g. deals).",
1001
+ generatedSignature: "createRecord<TObject extends GeneratedAttioObject>(params: AttioCreateRecordParams<TObject>): Promise<AttioSingleRecordResult<TObject>>",
1002
+ runtimeLines: [
1003
+ "createRecord: <TObject extends GeneratedAttioObject>(params: AttioCreateRecordParams<TObject>) =>",
1004
+ ` ${call(`{ action: "create", ${objectSlug}, values: JSON.stringify(params.values) }`)}.then(result => __enhanceAttioSingleRecordResult(params.object, result)),`
1005
+ ]
1006
+ },
1007
+ {
1008
+ description: "Update an existing record by its ID. Only the attributes present in values are touched; multiselectMode 'append' adds to multi-value attributes instead of overwriting them.",
1009
+ generatedSignature: "updateRecord<TObject extends GeneratedAttioObject>(params: AttioUpdateRecordParams<TObject>): Promise<AttioSingleRecordResult<TObject>>",
1010
+ runtimeLines: [
1011
+ "updateRecord: <TObject extends GeneratedAttioObject>(params: AttioUpdateRecordParams<TObject>) =>",
1012
+ ` ${call(`{ action: "update", ${objectSlug}, recordId: params.recordId, values: JSON.stringify(params.values), multiselectMode: params.multiselectMode ?? null }`)}.then(result => __enhanceAttioSingleRecordResult(params.object, result)),`
1013
+ ]
1014
+ },
1015
+ {
1016
+ description: "Create or update one or more records, matched on a unique writable attribute (e.g. email_addresses for people, domains for companies). Throws if ANY record fails, naming each failure; records earlier in the batch may already be written (upserts are safe to retry).",
1017
+ generatedSignature: "upsertRecord<TObject extends GeneratedAttioObject>(params: AttioUpsertRecordParams<TObject>): Promise<AttioUpsertRecordResult<TObject>>",
1018
+ runtimeLines: [
1019
+ "upsertRecord: <TObject extends GeneratedAttioObject>(params: AttioUpsertRecordParams<TObject>) =>",
1020
+ ` ${call(`{ action: "upsert", ${objectSlug}, matchingAttribute: params.matchingAttribute, records: __serializeAttioRecords(params.records) }`)}.then(result => __enhanceAttioUpsertResult(params.object, result)),`
1021
+ ]
1022
+ },
1023
+ {
1024
+ description: "Permanently delete a record by its ID. This cannot be undone.",
1025
+ generatedSignature: "deleteRecord<TObject extends GeneratedAttioObject>(params: AttioDeleteRecordParams<TObject>): Promise<void>",
1026
+ runtimeLines: [
1027
+ "deleteRecord: <TObject extends GeneratedAttioObject>(params: AttioDeleteRecordParams<TObject>) =>",
1028
+ ` ${call(`{ action: "delete", ${objectSlug}, recordId: params.recordId }`)}.then(() => undefined),`
1029
+ ]
1030
+ },
1031
+ {
1032
+ description: "Fetch the historic values of one attribute on a record (e.g. every stage a deal has been in), with limit/offset pagination. Entries come back as { active_from, active_until, value } with the value flattened and typed by the attribute.",
1033
+ generatedSignature: "getAttributeHistory<TObject extends GeneratedAttioObject, TAttr extends AttioAttributeSlug<TObject>>(params: AttioGetAttributeHistoryParams<TObject> & { attribute: TAttr }): Promise<AttioAttributeHistoryResult<__AttioSingleValue<NonNullable<AttioRecordValuesFor<TObject>[TAttr & keyof AttioRecordValuesFor<TObject>]>>>>",
1034
+ runtimeLines: [
1035
+ "getAttributeHistory: <TObject extends GeneratedAttioObject, TAttr extends AttioAttributeSlug<TObject>>(params: AttioGetAttributeHistoryParams<TObject> & { attribute: TAttr }) =>",
1036
+ ` ${call(`{ action: "get_attribute_history", ${objectSlug}, recordId: params.recordId, attributeSlug: params.attribute, limit: params.limit ?? null, offset: params.offset ?? null }`)}.then(result => __enhanceAttioHistoryResult<__AttioSingleValue<NonNullable<AttioRecordValuesFor<TObject>[TAttr & keyof AttioRecordValuesFor<TObject>]>>>(result)),`
1037
+ ]
1038
+ }
1039
+ ];
1040
+ }
1041
+ function buildAttioToolMethods(integrationId, toolName) {
1042
+ switch (toolName) {
1043
+ case "attio_records":
1044
+ return buildAttioRecordsMethods(integrationId);
1045
+ case "attio_workspace_members":
1046
+ return buildAttioWorkspaceMembersMethods(integrationId);
1047
+ case "attio_lists":
1048
+ return buildAttioListsMethods(integrationId);
1049
+ case "attio_tasks":
1050
+ case "attio_notes":
1051
+ case "attio_comments":
1052
+ case "attio_meetings":
1053
+ case "attio_files":
1054
+ case "attio_schema":
1055
+ return buildAttioResourceMethods(integrationId, toolName, ATTIO_RESOURCE_METHOD_SPECS[toolName]);
1056
+ default:
1057
+ return null;
1058
+ }
1059
+ }
1060
+ const ATTIO_RESOURCE_METHOD_SPECS = {
1061
+ attio_tasks: [
1062
+ {
1063
+ action: "list",
1064
+ methodName: "listTasks",
1065
+ description: "List Attio tasks, optionally filtered by linked record or completion state (limit/offset pagination).",
1066
+ emptyParams: true,
1067
+ result: { kind: "list", key: "tasks" }
1068
+ },
1069
+ { action: "get", methodName: "getTask", description: "Fetch a single Attio task by ID.", result: { kind: "single", key: "task" } },
1070
+ {
1071
+ action: "create",
1072
+ methodName: "createTask",
1073
+ description: "Create an Attio task with optional deadline, assignees (workspace-member emails or IDs) and linked records.",
1074
+ result: { kind: "single", key: "task" }
1075
+ },
1076
+ {
1077
+ action: "update",
1078
+ methodName: "updateTask",
1079
+ description: "Update an Attio task's deadline, completion state, assignees or linked records (content is immutable).",
1080
+ result: { kind: "single", key: "task" }
1081
+ },
1082
+ { action: "delete", methodName: "deleteTask", description: "Permanently delete an Attio task.", result: { kind: "void" } }
1083
+ ],
1084
+ attio_notes: [
1085
+ {
1086
+ action: "list",
1087
+ methodName: "listNotes",
1088
+ description: "List Attio notes, optionally scoped to one record (limit/offset pagination).",
1089
+ emptyParams: true,
1090
+ result: { kind: "list", key: "notes" }
1091
+ },
1092
+ { action: "get", methodName: "getNote", description: "Fetch a single Attio note by ID.", result: { kind: "single", key: "note" } },
1093
+ { action: "create", methodName: "createNote", description: "Create a note on a record (markdown by default).", result: { kind: "single", key: "note" } },
1094
+ { action: "delete", methodName: "deleteNote", description: "Permanently delete a note.", result: { kind: "void" } }
1095
+ ],
1096
+ attio_comments: [
1097
+ {
1098
+ action: "create",
1099
+ methodName: "createComment",
1100
+ description: "Create a comment: reply to a thread via threadId, or start a thread on a record via objectSlug + recordId. Requires authorWorkspaceMemberId.",
1101
+ result: { kind: "single", key: "comment" }
1102
+ },
1103
+ { action: "get", methodName: "getComment", description: "Fetch a single comment by ID.", result: { kind: "single", key: "comment" } },
1104
+ { action: "delete", methodName: "deleteComment", description: "Permanently delete a comment.", result: { kind: "void" } },
1105
+ { action: "list_threads", methodName: "listThreads", description: "List comment threads on a record.", emptyParams: true, result: { kind: "list", key: "threads" } },
1106
+ { action: "get_thread", methodName: "getThread", description: "Fetch a thread with all of its comments.", result: { kind: "single", key: "thread" } }
1107
+ ],
1108
+ attio_meetings: [
1109
+ {
1110
+ action: "list",
1111
+ methodName: "listMeetings",
1112
+ description: "List meetings, filterable by linked record, participants or time range (cursor pagination via nextCursor).",
1113
+ emptyParams: true,
1114
+ result: { kind: "list", key: "meetings", cursor: true }
1115
+ },
1116
+ { action: "get", methodName: "getMeeting", description: "Fetch a single meeting by ID.", result: { kind: "single", key: "meeting" } },
1117
+ {
1118
+ action: "list_recordings",
1119
+ methodName: "listCallRecordings",
1120
+ description: "List call recordings for a meeting.",
1121
+ result: { kind: "list", key: "recordings", cursor: true }
1122
+ },
1123
+ {
1124
+ action: "get_transcript",
1125
+ methodName: "getCallTranscript",
1126
+ description: "Fetch the transcript of a call recording.",
1127
+ result: { kind: "singleWithCursor", key: "transcript" }
1128
+ }
1129
+ ],
1130
+ attio_files: [
1131
+ { action: "list", methodName: "listFiles", description: "List files attached to a record (cursor pagination).", result: { kind: "list", key: "files", cursor: true } },
1132
+ { action: "get", methodName: "getFile", description: "Fetch a file's metadata by ID.", result: { kind: "single", key: "file" } },
1133
+ { action: "upload", methodName: "uploadFile", description: "Upload a file to a record from base64 content (max 50 MB).", result: { kind: "single", key: "file" } },
1134
+ {
1135
+ action: "get_download_url",
1136
+ methodName: "getFileDownloadUrl",
1137
+ description: "Get a signed download URL for a file.",
1138
+ result: { kind: "single", key: "downloadUrl", what: "download URL" }
1139
+ },
1140
+ { action: "delete", methodName: "deleteFile", description: "Permanently delete a file.", result: { kind: "void" } }
1141
+ ],
1142
+ attio_schema: [
1143
+ {
1144
+ action: "list_objects",
1145
+ methodName: "listObjects",
1146
+ description: "List all object types in the workspace with their attributes. Call before creating or updating records.",
1147
+ emptyParams: true,
1148
+ result: { kind: "list", key: "objects" }
1149
+ },
1150
+ { action: "get_object", methodName: "getObject", description: "Fetch one object's configuration.", result: { kind: "single", key: "object" } },
1151
+ { action: "create_object", methodName: "createObject", description: "Create a custom object type (changes the workspace schema).", result: { kind: "single", key: "object" } },
1152
+ { action: "update_object", methodName: "updateObject", description: "Update an object's slug or display names.", result: { kind: "single", key: "object" } },
1153
+ { action: "list_attributes", methodName: "listAttributes", description: "List the attributes on an object or list.", result: { kind: "list", key: "attributes" } },
1154
+ {
1155
+ action: "create_attribute",
1156
+ methodName: "createAttribute",
1157
+ description: "Create an attribute on an object or list (changes the workspace schema).",
1158
+ result: { kind: "single", key: "attribute" }
1159
+ },
1160
+ { action: "update_attribute", methodName: "updateAttribute", description: "Update an attribute's title or constraints.", result: { kind: "single", key: "attribute" } },
1161
+ { action: "list_statuses", methodName: "listStatuses", description: "List the statuses of a status attribute (e.g. deal stages).", result: { kind: "list", key: "statuses" } },
1162
+ {
1163
+ action: "create_status",
1164
+ methodName: "createStatus",
1165
+ description: "Add a status to a status attribute. Rerun terse generate to refresh constants.",
1166
+ result: { kind: "single", key: "status" }
1167
+ },
1168
+ { action: "update_status", methodName: "updateStatus", description: "Rename or archive a status.", result: { kind: "single", key: "status" } },
1169
+ { action: "list_select_options", methodName: "listSelectOptions", description: "List the options of a select attribute.", result: { kind: "list", key: "selectOptions" } },
1170
+ {
1171
+ action: "create_select_option",
1172
+ methodName: "createSelectOption",
1173
+ description: "Add an option to a select attribute. Rerun terse generate to refresh constants.",
1174
+ result: { kind: "single", key: "selectOption" }
1175
+ },
1176
+ { action: "update_select_option", methodName: "updateSelectOption", description: "Rename or archive a select option.", result: { kind: "single", key: "selectOption" } }
1177
+ ]
1178
+ };
1179
+ // Each method narrows the wire result to its action's payload at runtime: singles unwrap to the bare
1180
+ // entity (throwing if absent), lists rebuild a { items, count, nextCursor? } wrapper, deletes resolve void.
1181
+ function attioResultParts(toolName, result) {
1182
+ const base = `ToolOutputByName["${toolName}"]`;
1183
+ switch (result.kind) {
1184
+ case "single":
1185
+ return {
1186
+ resultType: `NonNullable<${base}["${result.key}"]>`,
1187
+ thenExpr: `.then(result => __requireAttioPayload(result.${result.key}, "${result.what ?? result.key}"))`
1188
+ };
1189
+ case "singleWithCursor":
1190
+ return {
1191
+ resultType: `{ ${result.key}: NonNullable<${base}["${result.key}"]>; nextCursor: string | null }`,
1192
+ thenExpr: `.then(result => ({ ${result.key}: __requireAttioPayload(result.${result.key}, "${result.what ?? result.key}"), nextCursor: result.nextCursor ?? null }))`
1193
+ };
1194
+ case "list": {
1195
+ const cursorType = result.cursor ? "; nextCursor: string | null" : "";
1196
+ const cursorValue = result.cursor ? ", nextCursor: result.nextCursor ?? null" : "";
1197
+ return {
1198
+ resultType: `{ ${result.key}: NonNullable<${base}["${result.key}"]>; count: number${cursorType} }`,
1199
+ thenExpr: `.then(result => ({ ${result.key}: result.${result.key} ?? [], count: (result.${result.key} ?? []).length${cursorValue} }))`
1200
+ };
1201
+ }
1202
+ case "void":
1203
+ return { resultType: "void", thenExpr: ".then(() => undefined)" };
1204
+ default:
1205
+ throw result;
1206
+ }
1207
+ }
1208
+ function buildAttioResourceMethods(integrationId, toolName, specs) {
1209
+ const id = escapeString(integrationId);
1210
+ return specs.map(spec => {
1211
+ const paramsType = `Omit<Extract<ToolInputByName["${toolName}"]["request"], { action: "${spec.action}" }>, "action">`;
1212
+ const { resultType, thenExpr } = attioResultParts(toolName, spec.result);
1213
+ return {
1214
+ description: spec.description,
1215
+ generatedSignature: `${spec.methodName}(${spec.emptyParams ? `params?: ${paramsType}` : `params: ${paramsType}`}): Promise<${resultType}>`,
1216
+ runtimeLines: [
1217
+ `${spec.methodName}: (params: ${paramsType}${spec.emptyParams ? " = {}" : ""}) =>`,
1218
+ ` TerseAgent.executeTool<ToolOutputByName["${toolName}"]>("${toolName}", { integrationId: "${id}", request: { action: "${spec.action}", ...params } })${thenExpr},`
1219
+ ]
1220
+ };
1221
+ });
1222
+ }
1223
+ function buildAttioListsMethods(integrationId) {
1224
+ const id = escapeString(integrationId);
1225
+ const call = (requestExpr) => `TerseAgent.executeTool<ToolOutputByName["attio_lists"]>("attio_lists", { integrationId: "${id}", request: ${requestExpr} })`;
1226
+ const listKey = "listIdOrSlug: __normalizeAttioObjectSlug(params.list)";
1227
+ const listParam = "list: GeneratedAttioList | string";
1228
+ return [
1229
+ {
1230
+ description: "List all Attio lists in the workspace.",
1231
+ generatedSignature: 'listLists(): Promise<{ lists: NonNullable<AttioListsResult["lists"]>; count: number }>',
1232
+ runtimeLines: ["listLists: () =>", ` ${call('{ action: "list" }')}.then(result => ({ lists: result.lists ?? [], count: (result.lists ?? []).length })),`]
1233
+ },
1234
+ {
1235
+ description: "Fetch a list's configuration. Accepts a generated AttioList constant or a plain list ID/slug.",
1236
+ generatedSignature: `getList(params: { ${listParam} }): Promise<NonNullable<AttioListsResult["list"]>>`,
1237
+ runtimeLines: [`getList: (params: { ${listParam} }) =>`, ` ${call(`{ action: "get", ${listKey} }`)}.then(result => __requireAttioPayload(result.list, "list")),`]
1238
+ },
1239
+ {
1240
+ description: "Create a new list over an object. This changes the workspace for every user.",
1241
+ generatedSignature: 'createList(params: Omit<Extract<ToolInputByName["attio_lists"]["request"], { action: "create" }>, "action">): Promise<NonNullable<AttioListsResult["list"]>>',
1242
+ runtimeLines: [
1243
+ 'createList: (params: Omit<Extract<ToolInputByName["attio_lists"]["request"], { action: "create" }>, "action">) =>',
1244
+ ` ${call('{ action: "create", ...params }')}.then(result => __requireAttioPayload(result.list, "list")),`
1245
+ ]
1246
+ },
1247
+ {
1248
+ description: "Rename a list.",
1249
+ generatedSignature: `updateList(params: { ${listParam}; name: string }): Promise<NonNullable<AttioListsResult["list"]>>`,
1250
+ runtimeLines: [
1251
+ `updateList: (params: { ${listParam}; name: string }) =>`,
1252
+ ` ${call(`{ action: "update", ${listKey}, name: params.name }`)}.then(result => __requireAttioPayload(result.list, "list")),`
1253
+ ]
1254
+ },
1255
+ {
1256
+ description: "List entries in a list with a typed filter, an optional parent-record lookup, and limit/offset pagination. Entry values come back flattened (statuses/selects as AttioSelectOption).",
1257
+ generatedSignature: "queryListEntries<TList extends GeneratedAttioList | string>(params: { list: TList; filter?: AttioEntryFilterFor<TList> | null; parentRecordId?: string | null; parentObjectSlug?: string; limit?: number | null; offset?: number | null }): Promise<AttioListEntriesResult<TList>>",
1258
+ runtimeLines: [
1259
+ "queryListEntries: <TList extends GeneratedAttioList | string>(params: { list: TList; filter?: AttioEntryFilterFor<TList> | null; parentRecordId?: string | null; parentObjectSlug?: string; limit?: number | null; offset?: number | null }) =>",
1260
+ ` ${call('{ action: "query_entries", listIdOrSlug: __normalizeAttioObjectSlug(params.list), filter: __serializeAttioFilter(params.filter), parentRecordId: params.parentRecordId ?? null, parentObjectSlug: params.parentRecordId ? __requireAttioListParentObject(params.list, params.parentObjectSlug) : null, limit: params.limit ?? null, offset: params.offset ?? null }')}.then(result => __enhanceAttioListEntriesResult(params.list, result)),`
1261
+ ]
1262
+ },
1263
+ {
1264
+ description: "Add a record to a list as a new entry, with typed entry values. parentObjectSlug is inferred from a generated AttioList constant.",
1265
+ generatedSignature: "addListEntry<TList extends GeneratedAttioList | string>(params: { list: TList; parentRecordId: string; parentObjectSlug?: string; entryValues?: AttioEntryValuesFor<TList> | null }): Promise<AttioListEntryResult<TList>>",
1266
+ runtimeLines: [
1267
+ "addListEntry: <TList extends GeneratedAttioList | string>(params: { list: TList; parentRecordId: string; parentObjectSlug?: string; entryValues?: AttioEntryValuesFor<TList> | null }) =>",
1268
+ ` ${call('{ action: "add_entry", listIdOrSlug: __normalizeAttioObjectSlug(params.list), parentObjectSlug: __requireAttioListParentObject(params.list, params.parentObjectSlug), parentRecordId: params.parentRecordId, entryValues: __serializeAttioValues(params.entryValues) }')}.then(result => __enhanceAttioListEntryResult(params.list, result)),`
1269
+ ]
1270
+ },
1271
+ {
1272
+ description: "Create or update a list entry keyed by its parent record (idempotent list membership), with typed entry values.",
1273
+ generatedSignature: "upsertListEntry<TList extends GeneratedAttioList | string>(params: { list: TList; parentRecordId: string; parentObjectSlug?: string; entryValues?: AttioEntryValuesFor<TList> | null }): Promise<AttioListEntryResult<TList>>",
1274
+ runtimeLines: [
1275
+ "upsertListEntry: <TList extends GeneratedAttioList | string>(params: { list: TList; parentRecordId: string; parentObjectSlug?: string; entryValues?: AttioEntryValuesFor<TList> | null }) =>",
1276
+ ` ${call('{ action: "upsert_entry", listIdOrSlug: __normalizeAttioObjectSlug(params.list), parentObjectSlug: __requireAttioListParentObject(params.list, params.parentObjectSlug), parentRecordId: params.parentRecordId, entryValues: __serializeAttioValues(params.entryValues) }')}.then(result => __enhanceAttioListEntryResult(params.list, result)),`
1277
+ ]
1278
+ },
1279
+ {
1280
+ description: "Fetch a single list entry with flattened, typed entry values.",
1281
+ generatedSignature: "getListEntry<TList extends GeneratedAttioList | string>(params: { list: TList; entryId: string }): Promise<AttioListEntryResult<TList>>",
1282
+ runtimeLines: [
1283
+ "getListEntry: <TList extends GeneratedAttioList | string>(params: { list: TList; entryId: string }) =>",
1284
+ ` ${call('{ action: "get_entry", listIdOrSlug: __normalizeAttioObjectSlug(params.list), entryId: params.entryId }')}.then(result => __enhanceAttioListEntryResult(params.list, result)),`
1285
+ ]
1286
+ },
1287
+ {
1288
+ description: "Update a list entry's attribute values (e.g. move its stage), typed against the list's entry attributes.",
1289
+ generatedSignature: 'updateListEntry<TList extends GeneratedAttioList | string>(params: { list: TList; entryId: string; entryValues: Partial<AttioEntryValuesFor<TList>>; multiselectMode?: "overwrite" | "append" | null }): Promise<AttioListEntryResult<TList>>',
1290
+ runtimeLines: [
1291
+ 'updateListEntry: <TList extends GeneratedAttioList | string>(params: { list: TList; entryId: string; entryValues: Partial<AttioEntryValuesFor<TList>>; multiselectMode?: "overwrite" | "append" | null }) =>',
1292
+ ` ${call('{ action: "update_entry", listIdOrSlug: __normalizeAttioObjectSlug(params.list), entryId: params.entryId, entryValues: __serializeAttioValues(params.entryValues), multiselectMode: params.multiselectMode ?? null }')}.then(result => __enhanceAttioListEntryResult(params.list, result)),`
1293
+ ]
1294
+ },
1295
+ {
1296
+ description: "Remove an entry from a list; the parent record is untouched.",
1297
+ generatedSignature: `removeListEntry(params: { ${listParam}; entryId: string }): Promise<void>`,
1298
+ runtimeLines: [
1299
+ `removeListEntry: (params: { ${listParam}; entryId: string }) =>`,
1300
+ ` ${call('{ action: "remove_entry", listIdOrSlug: __normalizeAttioObjectSlug(params.list), entryId: params.entryId }')}.then(() => undefined),`
1301
+ ]
1302
+ }
1303
+ ];
1304
+ }
1305
+ function buildAttioWorkspaceMembersMethods(integrationId) {
1306
+ const id = escapeString(integrationId);
1307
+ const call = (requestExpr) => `TerseAgent.executeTool<ToolOutputByName["attio_workspace_members"]>("attio_workspace_members", { integrationId: "${id}", request: ${requestExpr} })`;
1308
+ return [
1309
+ {
1310
+ description: "List every Attio workspace member (name, email address, access level). Use to resolve a record's owner to a person, e.g. for a Slack DM by email.",
1311
+ generatedSignature: 'listWorkspaceMembers(params?: AttioListWorkspaceMembersParams): Promise<{ members: NonNullable<AttioWorkspaceMembersResult["members"]>; count: number }>',
1312
+ runtimeLines: [
1313
+ "listWorkspaceMembers: (_params: AttioListWorkspaceMembersParams = {}) =>",
1314
+ ` ${call('{ action: "list" }')}.then(result => ({ members: result.members ?? [], count: (result.members ?? []).length })),`
1315
+ ]
1316
+ },
1317
+ {
1318
+ description: "Fetch a single workspace member by ID (e.g. the referenced_actor_id of a record's owner value).",
1319
+ generatedSignature: 'getWorkspaceMember(params: AttioGetWorkspaceMemberParams): Promise<NonNullable<AttioWorkspaceMembersResult["member"]>>',
1320
+ runtimeLines: [
1321
+ "getWorkspaceMember: (params: AttioGetWorkspaceMemberParams) =>",
1322
+ ` ${call('{ action: "get", workspaceMemberId: params.workspaceMemberId }')}.then(result => __requireAttioPayload(result.member, "workspace member")),`
1323
+ ]
1324
+ }
1325
+ ];
1326
+ }
821
1327
  function prepareSystemSection() {
822
1328
  return sectionData([
823
1329
  "TimeTriggerConfig",