@axiom-lattice/examples-deep_research 1.0.93 → 1.0.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +9 -0
- package/dist/index.js +205 -40
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/agents/sap_b1/tools.ts +201 -37
|
@@ -18,6 +18,28 @@ interface ApiEntry {
|
|
|
18
18
|
fields: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// ============================================================
|
|
22
|
+
// 支持 $expand 的文档类实体
|
|
23
|
+
// ============================================================
|
|
24
|
+
|
|
25
|
+
const EXPAND_FIELDS: Record<string, string[]> = {
|
|
26
|
+
"DocumentLines": [
|
|
27
|
+
"LineNum", "ItemCode", "ItemDescription", "Quantity", "UnitPrice",
|
|
28
|
+
"WarehouseCode", "Total", "Currency", "TaxCode", "PriceAfterVAT",
|
|
29
|
+
"UoMEntry", "UoMCode",
|
|
30
|
+
],
|
|
31
|
+
"DocumentAdditionalExpenses": [
|
|
32
|
+
"LineNum", "ExpenseCode", "LineTotal", "TaxCode", "VatGroup",
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const ENTITIES_WITH_LINES = new Set([
|
|
37
|
+
"Orders", "DeliveryNotes", "Invoices", "Quotations", "CreditNotes",
|
|
38
|
+
"Returns", "Drafts", "PurchaseOrders", "PurchaseDeliveryNotes",
|
|
39
|
+
"PurchaseInvoices", "PurchaseReturns", "PurchaseQuotations",
|
|
40
|
+
"DownPayments", "InventoryGenEntries", "InventoryGenExits",
|
|
41
|
+
]);
|
|
42
|
+
|
|
21
43
|
const API_LIST: ApiEntry[] = [
|
|
22
44
|
// ========== Business Partner ==========
|
|
23
45
|
{
|
|
@@ -30,9 +52,11 @@ const API_LIST: ApiEntry[] = [
|
|
|
30
52
|
fields: [
|
|
31
53
|
"CardCode", "CardName", "CardType", "GroupCode", "Currency",
|
|
32
54
|
"Phone1", "Phone2", "EmailAddress", "Address", "City", "Country",
|
|
33
|
-
"SalesPersonCode", "PriceListNum", "CreditLimit",
|
|
55
|
+
"SalesPersonCode", "PriceListNum", "CreditLimit",
|
|
56
|
+
"CurrentAccountBalance", "OpenOrdersBalance", "OpenDeliveryNotesBalance",
|
|
34
57
|
"PayTermsGrpCode", "VatGroup", "VatLiable", "FederalTaxID",
|
|
35
|
-
"Valid", "Frozen", "CompanyPrivate", "CreateDate", "UpdateDate",
|
|
58
|
+
"Valid", "Frozen", "CompanyPrivate", "CreateDate", "CreateTime", "UpdateDate", "UpdateTime",
|
|
59
|
+
"BPAddresses", "ContactEmployees",
|
|
36
60
|
],
|
|
37
61
|
},
|
|
38
62
|
{
|
|
@@ -70,6 +94,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
70
94
|
"ManageSerialNumbers", "ManageBatchNumbers",
|
|
71
95
|
"SalesVATGroup", "PurchaseVATGroup",
|
|
72
96
|
"Valid", "Frozen", "CreateDate", "UpdateDate",
|
|
97
|
+
"ItemPrices", "ItemWarehouseInfoCollection",
|
|
73
98
|
],
|
|
74
99
|
},
|
|
75
100
|
{
|
|
@@ -120,6 +145,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
120
145
|
"Comments", "Reference1", "Reference2", "NumAtCard",
|
|
121
146
|
"VatSum", "RoundDif", "DiscountPercent",
|
|
122
147
|
"PaymentGroupCode", "Project",
|
|
148
|
+
"DocumentLines",
|
|
123
149
|
],
|
|
124
150
|
},
|
|
125
151
|
{
|
|
@@ -134,6 +160,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
134
160
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
135
161
|
"SalesPersonCode", "Confirmed", "Cancelled", "DocumentStatus",
|
|
136
162
|
"Comments", "NumAtCard",
|
|
163
|
+
"DocumentLines",
|
|
137
164
|
],
|
|
138
165
|
},
|
|
139
166
|
{
|
|
@@ -148,6 +175,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
148
175
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
149
176
|
"SalesPersonCode", "Confirmed", "Cancelled", "DocumentStatus",
|
|
150
177
|
"Comments", "NumAtCard", "VatSum",
|
|
178
|
+
"DocumentLines",
|
|
151
179
|
],
|
|
152
180
|
},
|
|
153
181
|
{
|
|
@@ -161,6 +189,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
161
189
|
"DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate",
|
|
162
190
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
163
191
|
"SalesPersonCode", "Comments", "DocumentStatus",
|
|
192
|
+
"DocumentLines",
|
|
164
193
|
],
|
|
165
194
|
},
|
|
166
195
|
{
|
|
@@ -174,6 +203,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
174
203
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
175
204
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
176
205
|
"Comments",
|
|
206
|
+
"DocumentLines",
|
|
177
207
|
],
|
|
178
208
|
},
|
|
179
209
|
{
|
|
@@ -187,6 +217,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
187
217
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
188
218
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
189
219
|
"Comments",
|
|
220
|
+
"DocumentLines",
|
|
190
221
|
],
|
|
191
222
|
},
|
|
192
223
|
{
|
|
@@ -200,6 +231,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
200
231
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
201
232
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
202
233
|
"DownPaymentType", "DownPaymentAmount",
|
|
234
|
+
"DocumentLines",
|
|
203
235
|
],
|
|
204
236
|
},
|
|
205
237
|
{
|
|
@@ -231,6 +263,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
231
263
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
232
264
|
"SalesPersonCode", "Confirmed", "Cancelled", "DocumentStatus",
|
|
233
265
|
"Comments", "NumAtCard",
|
|
266
|
+
"DocumentLines",
|
|
234
267
|
],
|
|
235
268
|
},
|
|
236
269
|
{
|
|
@@ -243,6 +276,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
243
276
|
fields: [
|
|
244
277
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
245
278
|
"CardCode", "CardName", "DocTotal", "Comments",
|
|
279
|
+
"DocumentLines",
|
|
246
280
|
],
|
|
247
281
|
},
|
|
248
282
|
{
|
|
@@ -256,6 +290,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
256
290
|
"DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate",
|
|
257
291
|
"CardCode", "CardName", "DocTotal", "DocCurrency",
|
|
258
292
|
"Comments",
|
|
293
|
+
"DocumentLines",
|
|
259
294
|
],
|
|
260
295
|
},
|
|
261
296
|
{
|
|
@@ -268,6 +303,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
268
303
|
fields: [
|
|
269
304
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
270
305
|
"CardCode", "CardName", "DocTotal", "Comments",
|
|
306
|
+
"DocumentLines",
|
|
271
307
|
],
|
|
272
308
|
},
|
|
273
309
|
{
|
|
@@ -280,6 +316,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
280
316
|
fields: [
|
|
281
317
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
282
318
|
"CardCode", "CardName", "DocTotal", "Comments",
|
|
319
|
+
"DocumentLines",
|
|
283
320
|
],
|
|
284
321
|
},
|
|
285
322
|
|
|
@@ -295,6 +332,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
295
332
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
296
333
|
"Comments", "JournalMemo", "Reference1", "Reference2",
|
|
297
334
|
"WareHouseUpdateType",
|
|
335
|
+
"DocumentLines",
|
|
298
336
|
],
|
|
299
337
|
},
|
|
300
338
|
{
|
|
@@ -307,6 +345,7 @@ const API_LIST: ApiEntry[] = [
|
|
|
307
345
|
fields: [
|
|
308
346
|
"DocEntry", "DocNum", "DocType", "DocDate",
|
|
309
347
|
"Comments", "JournalMemo", "Reference1", "Reference2",
|
|
348
|
+
"DocumentLines",
|
|
310
349
|
],
|
|
311
350
|
},
|
|
312
351
|
{
|
|
@@ -573,20 +612,51 @@ const API_LIST: ApiEntry[] = [
|
|
|
573
612
|
// Tool 1: sap_api_search
|
|
574
613
|
// ============================================================
|
|
575
614
|
|
|
615
|
+
function mapResult(e: ApiEntry): Record<string, unknown> {
|
|
616
|
+
const r: Record<string, unknown> = {
|
|
617
|
+
name: e.name,
|
|
618
|
+
kind: e.kind,
|
|
619
|
+
domain: e.domain,
|
|
620
|
+
description: e.description,
|
|
621
|
+
primaryKey: e.primaryKey || null,
|
|
622
|
+
fields: e.fields,
|
|
623
|
+
readySelect: e.fields.length > 0 ? `$select=${e.fields.join(",")}` : undefined,
|
|
624
|
+
hint:
|
|
625
|
+
e.kind === "EntitySet"
|
|
626
|
+
? `${e.name} — ${e.description}。主键: ${e.primaryKey}。调用 sap_api_call 进行 CRUD 操作。`
|
|
627
|
+
: `${e.name} — ${e.description}。调用 sap_api_call 执行此方法。`,
|
|
628
|
+
};
|
|
629
|
+
if (ENTITIES_WITH_LINES.has(e.name)) {
|
|
630
|
+
r.expand = ["DocumentLines", "DocumentAdditionalExpenses"];
|
|
631
|
+
r.lineFields = ["LineNum", "ItemCode", "ItemDescription", "Quantity", "UnitPrice", "Price", "WarehouseCode", "UoMEntry", "UoMCode", "VatGroup", "Currency", "TaxCode", "PriceAfterVAT"];
|
|
632
|
+
}
|
|
633
|
+
if (e.name === "Items") {
|
|
634
|
+
r.expand = ["ItemPrices", "ItemWarehouseInfoCollection"];
|
|
635
|
+
}
|
|
636
|
+
if (e.name === "BusinessPartners") {
|
|
637
|
+
r.expand = ["BPAddresses", "ContactEmployees"];
|
|
638
|
+
}
|
|
639
|
+
return r;
|
|
640
|
+
}
|
|
641
|
+
|
|
576
642
|
registerToolLattice(
|
|
577
643
|
"sap_api_search",
|
|
578
644
|
{
|
|
579
645
|
name: "sap_api_search",
|
|
580
646
|
description:
|
|
581
|
-
"搜索 SAP B1 Service Layer API
|
|
582
|
-
"
|
|
647
|
+
"搜索 SAP B1 Service Layer API 接口元数据,覆盖 BP/物料/订单/库存四大领域。" +
|
|
648
|
+
"返回接口名、主键、字段列表、可选 expand 导航。" +
|
|
649
|
+
"可用 discover 模式发现关联接口(如搜 SalesPerson 也能找到 SalesPersons API)。" +
|
|
650
|
+
"搜索结果中的 readySelect 可直接复制到 $select。",
|
|
583
651
|
needUserApprove: false,
|
|
584
652
|
schema: z.object({
|
|
585
653
|
query: z
|
|
586
654
|
.string()
|
|
655
|
+
.optional()
|
|
587
656
|
.describe(
|
|
588
|
-
"
|
|
589
|
-
"
|
|
657
|
+
"搜索关键词(可选,不传则返回全部接口)。" +
|
|
658
|
+
"API 名称如 'BusinessPartners', 'Orders', 'Items', 'PurchaseOrders'," +
|
|
659
|
+
"或中文描述如 '客户', '订单', '物料', '库存', '采购', '仓库'"
|
|
590
660
|
),
|
|
591
661
|
domain: z
|
|
592
662
|
.string()
|
|
@@ -594,14 +664,15 @@ registerToolLattice(
|
|
|
594
664
|
.describe(
|
|
595
665
|
"按领域过滤: 'BusinessPartner'(BP), 'Item / Product'(物料), 'Document'(订单/发票), 'Inventory / Warehouse'(库存/仓库)"
|
|
596
666
|
),
|
|
597
|
-
maxResults: z.number().optional().default(
|
|
667
|
+
maxResults: z.number().optional().default(20).describe("最大返回条数"),
|
|
598
668
|
}),
|
|
599
669
|
},
|
|
600
670
|
async (input) => {
|
|
601
|
-
const q = input.query
|
|
602
|
-
const max = input.maxResults ??
|
|
671
|
+
const q = input.query?.toLowerCase();
|
|
672
|
+
const max = input.maxResults ?? 20;
|
|
673
|
+
|
|
674
|
+
const hasQuery = q && q.trim().length > 0;
|
|
603
675
|
|
|
604
|
-
// 中文关键词映射到领域
|
|
605
676
|
const domainHints: Record<string, string> = {
|
|
606
677
|
"客户": "BusinessPartner",
|
|
607
678
|
"供应商": "BusinessPartner",
|
|
@@ -617,6 +688,8 @@ registerToolLattice(
|
|
|
617
688
|
"交货": "Document",
|
|
618
689
|
"报价": "Document",
|
|
619
690
|
"草稿": "Document",
|
|
691
|
+
"draft": "Document",
|
|
692
|
+
"暂存": "Document",
|
|
620
693
|
"库存": "Inventory / Warehouse",
|
|
621
694
|
"仓库": "Inventory / Warehouse",
|
|
622
695
|
"库位": "Inventory / Warehouse",
|
|
@@ -633,13 +706,29 @@ registerToolLattice(
|
|
|
633
706
|
"到岸": "Document",
|
|
634
707
|
};
|
|
635
708
|
|
|
636
|
-
const hintedDomain = domainHints[q] || undefined;
|
|
709
|
+
const hintedDomain = q ? (domainHints[q] || undefined) : undefined;
|
|
637
710
|
const effectiveDomain = input.domain || hintedDomain;
|
|
638
711
|
|
|
639
|
-
const
|
|
712
|
+
const filtered = API_LIST.filter((e) => {
|
|
640
713
|
if (effectiveDomain && e.domain !== effectiveDomain) return false;
|
|
641
714
|
return true;
|
|
642
|
-
})
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
if (!q) {
|
|
718
|
+
const top = filtered.slice(0, max);
|
|
719
|
+
const domainCounts: Record<string, number> = {};
|
|
720
|
+
for (const e of top) domainCounts[e.domain] = (domainCounts[e.domain] || 0) + 1;
|
|
721
|
+
return {
|
|
722
|
+
query: input.query || null,
|
|
723
|
+
domainFilter: effectiveDomain || null,
|
|
724
|
+
totalMatches: filtered.length,
|
|
725
|
+
domainsFound: domainCounts,
|
|
726
|
+
results: top.map(mapResult),
|
|
727
|
+
suggestion: undefined,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const scored = filtered.map((e) => {
|
|
643
732
|
let score = 0;
|
|
644
733
|
const nameLo = e.name.toLowerCase();
|
|
645
734
|
const descLo = e.description.toLowerCase();
|
|
@@ -668,22 +757,11 @@ registerToolLattice(
|
|
|
668
757
|
for (const e of top) domainCounts[e.domain] = (domainCounts[e.domain] || 0) + 1;
|
|
669
758
|
|
|
670
759
|
return {
|
|
671
|
-
query: input.query
|
|
760
|
+
query: input.query!,
|
|
672
761
|
domainFilter: effectiveDomain || null,
|
|
673
762
|
totalMatches: scored.filter((e) => e.score > 0).length,
|
|
674
763
|
domainsFound: domainCounts,
|
|
675
|
-
results: top.map(
|
|
676
|
-
name: e.name,
|
|
677
|
-
kind: e.kind,
|
|
678
|
-
domain: e.domain,
|
|
679
|
-
description: e.description,
|
|
680
|
-
primaryKey: e.primaryKey || null,
|
|
681
|
-
fields: e.fields,
|
|
682
|
-
hint:
|
|
683
|
-
e.kind === "EntitySet"
|
|
684
|
-
? `${e.name} — ${e.description}。主键: ${e.primaryKey}。调用 sap_api_call 进行 CRUD 操作。`
|
|
685
|
-
: `${e.name} — ${e.description}。调用 sap_api_call 执行此方法。`,
|
|
686
|
-
})),
|
|
764
|
+
results: top.map(mapResult),
|
|
687
765
|
suggestion:
|
|
688
766
|
top.length === 0
|
|
689
767
|
? `未找到匹配 "${input.query}" 的接口。可用领域: BusinessPartner(客户/供应商), Item / Product(物料), Document(订单/发票), Inventory / Warehouse(库存/仓库)。尝试用英文名称搜索。`
|
|
@@ -705,9 +783,24 @@ registerToolLattice(
|
|
|
705
783
|
{
|
|
706
784
|
name: "sap_api_call",
|
|
707
785
|
description:
|
|
708
|
-
"
|
|
709
|
-
|
|
710
|
-
"
|
|
786
|
+
"执行 SAP B1 Service Layer 的 OData API 查询/创建/更新/删除。" +
|
|
787
|
+
`Base: ${BASE_URL}。` +
|
|
788
|
+
"⚠️ 先确认是否已通过 sap_api_search 查过字段列表,勿凭记忆编字段名。\n" +
|
|
789
|
+
"⚠️ 草稿/draft/暂存 → 用 Drafts 接口,不是 Orders/Invoices 等正式单据。" +
|
|
790
|
+
"Drafts 通过 DocObjectCode 区分单据类型(17=订单,13=发票,16=交货单,23=采购订单)。\n" +
|
|
791
|
+
"$filter 操作符: eq/ne/gt/lt/ge/le/contains(f,'v')/startswith(f,'v')/endswith(f,'v'),多条件用 and/or。" +
|
|
792
|
+
"字符串值必须单引号包裹。$orderby=Field desc 排序。\n\n" +
|
|
793
|
+
"⚠️ SAP B1 实战经验:\n" +
|
|
794
|
+
"1. 不要用 $expand!$expand 极易触发 400/500。改用 $select 包含嵌套字段,如 $select=DocEntry,DocNum,DocumentLines,\n" +
|
|
795
|
+
" DocumentLines 会自动作为嵌套 JSON 返回。ItemPrices、BPAddresses 等同理。\n" +
|
|
796
|
+
"2. 不要用主键路径 /Orders('1173'),易 500。用 $filter=DocEntry eq 1173 代替。\n" +
|
|
797
|
+
"3. 查单条记录时优先 $filter,而非传 id 参数。\n" +
|
|
798
|
+
"400 多为特殊字符未编码(引号用 %27);500 多为字段不存在或用错了 $expand;无结果则放宽 filter。\n" +
|
|
799
|
+
"POST 创建: sap_api_search 返回的 lineFields 是 DocumentLines 子字段。" +
|
|
800
|
+
"body 必含 DocObjectCode(DocType)、CardCode、DocDate;DocumentLines 为数组,每项必含 ItemCode、Quantity。" +
|
|
801
|
+
"PATCH 只传变更字段;DELETE 需传 id。\n" +
|
|
802
|
+
"GET 自动注入 $select+$top=20,手动传入可覆盖。嵌套集合(DocumentLines等)自动裁剪只保留常用字段,防 token 爆炸。" +
|
|
803
|
+
"认证需 SAP_B1SESSION 环境变量。",
|
|
711
804
|
needUserApprove: false,
|
|
712
805
|
schema: z.object({
|
|
713
806
|
entitySet: z
|
|
@@ -722,18 +815,18 @@ registerToolLattice(
|
|
|
722
815
|
.string()
|
|
723
816
|
.optional()
|
|
724
817
|
.describe(
|
|
725
|
-
"OData 查询参数(不含 `?`
|
|
726
|
-
"$top=10, $
|
|
727
|
-
"$
|
|
728
|
-
"$
|
|
729
|
-
"
|
|
730
|
-
"$expand=DocumentLines"
|
|
818
|
+
"OData 查询参数(不含 `?` 前缀)。" +
|
|
819
|
+
"常用: $top=10, $select=CardCode,CardName, " +
|
|
820
|
+
"$filter=CardName eq 'xxx' or contains(CardName,'xxx'), " +
|
|
821
|
+
"$orderby=DocDate desc, $expand=DocumentLines。字符串值用单引号。" +
|
|
822
|
+
"sap_api_search 返回的 readySelect 可直接复制到 $select。"
|
|
731
823
|
),
|
|
732
824
|
body: z.record(z.unknown()).optional().describe("POST/PATCH 时的 JSON 请求体"),
|
|
733
825
|
}),
|
|
734
826
|
},
|
|
735
827
|
async (input) => {
|
|
736
|
-
const
|
|
828
|
+
const queryOptions = applyDefaultSelect(input.entitySet, input.method, input.id, input.queryOptions);
|
|
829
|
+
const url = buildUrl(input.entitySet, input.method, input.id, queryOptions);
|
|
737
830
|
const method = input.method;
|
|
738
831
|
|
|
739
832
|
const headers: Record<string, string> = {
|
|
@@ -749,7 +842,8 @@ registerToolLattice(
|
|
|
749
842
|
|
|
750
843
|
try {
|
|
751
844
|
const res = await fetch(url, fetchOptions);
|
|
752
|
-
const
|
|
845
|
+
const buffer = await res.arrayBuffer();
|
|
846
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(buffer);
|
|
753
847
|
|
|
754
848
|
let data: unknown;
|
|
755
849
|
try {
|
|
@@ -758,6 +852,8 @@ registerToolLattice(
|
|
|
758
852
|
data = text;
|
|
759
853
|
}
|
|
760
854
|
|
|
855
|
+
cleanODataNoise(data);
|
|
856
|
+
|
|
761
857
|
const result: Record<string, unknown> = {
|
|
762
858
|
ok: res.ok,
|
|
763
859
|
status: res.status,
|
|
@@ -792,6 +888,74 @@ registerToolLattice(
|
|
|
792
888
|
// Helpers
|
|
793
889
|
// ============================================================
|
|
794
890
|
|
|
891
|
+
function applyDefaultSelect(entitySet: string, method: string, id?: string, queryOptions?: string): string | undefined {
|
|
892
|
+
if (method !== "GET") return queryOptions;
|
|
893
|
+
|
|
894
|
+
const parts: string[] = [];
|
|
895
|
+
|
|
896
|
+
if (!id && (!queryOptions || !/\$top\s*=/.test(queryOptions))) {
|
|
897
|
+
parts.push("$top=20");
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (!queryOptions || !/\$select\s*=/.test(queryOptions)) {
|
|
901
|
+
const entry = API_LIST.find(
|
|
902
|
+
(e) => e.kind === "EntitySet" && e.name === entitySet
|
|
903
|
+
);
|
|
904
|
+
if (entry && entry.fields.length > 0) {
|
|
905
|
+
parts.push(`$select=${entry.fields.join(",")}`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (queryOptions) {
|
|
910
|
+
parts.push(queryOptions);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return parts.length > 0 ? parts.join("&").replace(/'/g, "%27") : undefined;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function cleanODataNoise(data: unknown): void {
|
|
917
|
+
if (!data || typeof data !== "object") return;
|
|
918
|
+
if (Array.isArray(data)) {
|
|
919
|
+
for (const item of data) cleanODataNoise(item);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const obj = data as Record<string, unknown>;
|
|
923
|
+
|
|
924
|
+
delete obj["odata.metadata"];
|
|
925
|
+
delete obj["odata.etag"];
|
|
926
|
+
delete obj["odata.nextLink"];
|
|
927
|
+
|
|
928
|
+
if (Array.isArray(obj.value)) {
|
|
929
|
+
for (const record of obj.value) {
|
|
930
|
+
if (record && typeof record === "object") {
|
|
931
|
+
trimNestedCollections(record as Record<string, unknown>);
|
|
932
|
+
delete (record as Record<string, unknown>)["odata.etag"];
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
for (const v of Object.values(obj)) {
|
|
938
|
+
if (v && typeof v === "object") cleanODataNoise(v);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function trimNestedCollections(obj: Record<string, unknown>): void {
|
|
943
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
944
|
+
if (Array.isArray(val) && EXPAND_FIELDS[key]) {
|
|
945
|
+
const keep = new Set(EXPAND_FIELDS[key]);
|
|
946
|
+
for (const item of val) {
|
|
947
|
+
if (item && typeof item === "object") {
|
|
948
|
+
const record = item as Record<string, unknown>;
|
|
949
|
+
for (const k of Object.keys(record)) {
|
|
950
|
+
if (!keep.has(k)) delete record[k];
|
|
951
|
+
}
|
|
952
|
+
cleanODataNoise(item);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
795
959
|
function buildUrl(
|
|
796
960
|
entitySet: string,
|
|
797
961
|
method: string,
|