@axiom-lattice/examples-deep_research 1.0.61 → 1.0.63

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.
@@ -0,0 +1,809 @@
1
+ import z from "zod";
2
+ import { registerToolLattice } from "@axiom-lattice/core";
3
+
4
+ const BASE_URL =
5
+ process.env.SAP_SERVICE_LAYER_URL || "https://b1s.alphafina.cn/b1s/v1";
6
+
7
+ // ============================================================
8
+ // API 元数据
9
+ // ============================================================
10
+
11
+ interface ApiEntry {
12
+ name: string;
13
+ kind: "EntitySet" | "FunctionImport";
14
+ entityType?: string;
15
+ primaryKey?: string;
16
+ domain: string;
17
+ description: string;
18
+ fields: string[];
19
+ }
20
+
21
+ const API_LIST: ApiEntry[] = [
22
+ // ========== Business Partner ==========
23
+ {
24
+ name: "BusinessPartners",
25
+ kind: "EntitySet",
26
+ entityType: "SAPB1.BusinessPartner",
27
+ primaryKey: "CardCode",
28
+ domain: "BusinessPartner",
29
+ description: "客户/供应商主数据",
30
+ fields: [
31
+ "CardCode", "CardName", "CardType", "GroupCode", "Currency",
32
+ "Phone1", "Phone2", "EmailAddress", "Address", "City", "Country",
33
+ "SalesPersonCode", "PriceListNum", "CreditLimit", "Balance",
34
+ "PayTermsGrpCode", "VatGroup", "VatLiable", "FederalTaxID",
35
+ "Valid", "Frozen", "CompanyPrivate", "CreateDate", "UpdateDate",
36
+ ],
37
+ },
38
+ {
39
+ name: "BusinessPartnerGroups",
40
+ kind: "EntitySet",
41
+ entityType: "SAPB1.BusinessPartnerGroup",
42
+ primaryKey: "Code",
43
+ domain: "BusinessPartner",
44
+ description: "BP 分组",
45
+ fields: ["Code", "Name", "Type"],
46
+ },
47
+ {
48
+ name: "SalesPersons",
49
+ kind: "EntitySet",
50
+ entityType: "SAPB1.SalesPerson",
51
+ primaryKey: "SalesEmployeeCode",
52
+ domain: "BusinessPartner",
53
+ description: "销售雇员",
54
+ fields: ["SalesEmployeeCode", "SalesEmployeeName", "CommissionGroup", "Valid"],
55
+ },
56
+
57
+ // ========== Item / Product ==========
58
+ {
59
+ name: "Items",
60
+ kind: "EntitySet",
61
+ entityType: "SAPB1.Item",
62
+ primaryKey: "ItemCode",
63
+ domain: "Item / Product",
64
+ description: "物料主数据",
65
+ fields: [
66
+ "ItemCode", "ItemName", "ForeignName", "ItemsGroupCode",
67
+ "BarCode", "SalesItem", "PurchaseItem", "InventoryItem",
68
+ "SalesUnit", "PurchaseUnit", "InventoryUOM",
69
+ "QuantityOnStock", "AvgStdPrice", "DefaultWarehouse",
70
+ "ManageSerialNumbers", "ManageBatchNumbers",
71
+ "SalesVATGroup", "PurchaseVATGroup",
72
+ "Valid", "Frozen", "CreateDate", "UpdateDate",
73
+ ],
74
+ },
75
+ {
76
+ name: "ItemGroups",
77
+ kind: "EntitySet",
78
+ entityType: "SAPB1.ItemGroups",
79
+ primaryKey: "Number",
80
+ domain: "Item / Product",
81
+ description: "物料组",
82
+ fields: ["Number", "GroupName", "CommissionGroup"],
83
+ },
84
+ {
85
+ name: "PriceLists",
86
+ kind: "EntitySet",
87
+ entityType: "SAPB1.PriceList",
88
+ primaryKey: "PriceListNo",
89
+ domain: "Item / Product",
90
+ description: "价格清单",
91
+ fields: [
92
+ "PriceListNo", "PriceListName", "DefaultPrimeCurrency",
93
+ "DefaultAdditionalCurrency1", "DefaultAdditionalCurrency2",
94
+ "BasePriceList", "Factor", "Active", "IsGrossPrice",
95
+ "RoundingMethod", "GroupNum",
96
+ ],
97
+ },
98
+ {
99
+ name: "BarCodes",
100
+ kind: "EntitySet",
101
+ entityType: "SAPB1.BarCode",
102
+ primaryKey: "AbsEntry",
103
+ domain: "Item / Product",
104
+ description: "条码",
105
+ fields: ["AbsEntry", "ItemCode", "UoMEntry", "BarCode", "FreeText"],
106
+ },
107
+
108
+ // ========== Orders — Sales ==========
109
+ {
110
+ name: "Orders",
111
+ kind: "EntitySet",
112
+ entityType: "SAPB1.Document",
113
+ primaryKey: "DocEntry",
114
+ domain: "Document",
115
+ description: "销售订单",
116
+ fields: [
117
+ "DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate", "TaxDate",
118
+ "CardCode", "CardName", "Address", "DocTotal", "DocCurrency",
119
+ "SalesPersonCode", "Confirmed", "Cancelled", "DocumentStatus",
120
+ "Comments", "Reference1", "Reference2", "NumAtCard",
121
+ "VatSum", "RoundDif", "DiscountPercent",
122
+ "PaymentGroupCode", "Project",
123
+ ],
124
+ },
125
+ {
126
+ name: "DeliveryNotes",
127
+ kind: "EntitySet",
128
+ entityType: "SAPB1.Document",
129
+ primaryKey: "DocEntry",
130
+ domain: "Document",
131
+ description: "交货单",
132
+ fields: [
133
+ "DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate", "TaxDate",
134
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
135
+ "SalesPersonCode", "Confirmed", "Cancelled", "DocumentStatus",
136
+ "Comments", "NumAtCard",
137
+ ],
138
+ },
139
+ {
140
+ name: "Invoices",
141
+ kind: "EntitySet",
142
+ entityType: "SAPB1.Document",
143
+ primaryKey: "DocEntry",
144
+ domain: "Document",
145
+ description: "销售发票 (应收)",
146
+ fields: [
147
+ "DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate", "TaxDate",
148
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
149
+ "SalesPersonCode", "Confirmed", "Cancelled", "DocumentStatus",
150
+ "Comments", "NumAtCard", "VatSum",
151
+ ],
152
+ },
153
+ {
154
+ name: "Quotations",
155
+ kind: "EntitySet",
156
+ entityType: "SAPB1.Document",
157
+ primaryKey: "DocEntry",
158
+ domain: "Document",
159
+ description: "销售报价单",
160
+ fields: [
161
+ "DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate",
162
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
163
+ "SalesPersonCode", "Comments", "DocumentStatus",
164
+ ],
165
+ },
166
+ {
167
+ name: "CreditNotes",
168
+ kind: "EntitySet",
169
+ entityType: "SAPB1.Document",
170
+ primaryKey: "DocEntry",
171
+ domain: "Document",
172
+ description: "销售贷项凭证",
173
+ fields: [
174
+ "DocEntry", "DocNum", "DocType", "DocDate",
175
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
176
+ "Comments",
177
+ ],
178
+ },
179
+ {
180
+ name: "Returns",
181
+ kind: "EntitySet",
182
+ entityType: "SAPB1.Document",
183
+ primaryKey: "DocEntry",
184
+ domain: "Document",
185
+ description: "销售退货",
186
+ fields: [
187
+ "DocEntry", "DocNum", "DocType", "DocDate",
188
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
189
+ "Comments",
190
+ ],
191
+ },
192
+ {
193
+ name: "DownPayments",
194
+ kind: "EntitySet",
195
+ entityType: "SAPB1.Document",
196
+ primaryKey: "DocEntry",
197
+ domain: "Document",
198
+ description: "预收款",
199
+ fields: [
200
+ "DocEntry", "DocNum", "DocType", "DocDate",
201
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
202
+ "DownPaymentType", "DownPaymentAmount",
203
+ ],
204
+ },
205
+ {
206
+ name: "Drafts",
207
+ kind: "EntitySet",
208
+ entityType: "SAPB1.Document",
209
+ primaryKey: "DocEntry",
210
+ domain: "Document",
211
+ description: "销售草稿",
212
+ fields: [
213
+ "DocEntry", "DocNum", "DocType", "DocDate",
214
+ "CardCode", "CardName", "DocTotal", "Comments",
215
+ ],
216
+ },
217
+
218
+ // ========== Orders — Purchase ==========
219
+ {
220
+ name: "PurchaseOrders",
221
+ kind: "EntitySet",
222
+ entityType: "SAPB1.Document",
223
+ primaryKey: "DocEntry",
224
+ domain: "Document",
225
+ description: "采购订单",
226
+ fields: [
227
+ "DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate", "TaxDate",
228
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
229
+ "SalesPersonCode", "Confirmed", "Cancelled", "DocumentStatus",
230
+ "Comments", "NumAtCard",
231
+ ],
232
+ },
233
+ {
234
+ name: "PurchaseDeliveryNotes",
235
+ kind: "EntitySet",
236
+ entityType: "SAPB1.Document",
237
+ primaryKey: "DocEntry",
238
+ domain: "Document",
239
+ description: "采购收货单",
240
+ fields: [
241
+ "DocEntry", "DocNum", "DocType", "DocDate",
242
+ "CardCode", "CardName", "DocTotal", "Comments",
243
+ ],
244
+ },
245
+ {
246
+ name: "PurchaseInvoices",
247
+ kind: "EntitySet",
248
+ entityType: "SAPB1.Document",
249
+ primaryKey: "DocEntry",
250
+ domain: "Document",
251
+ description: "采购发票 (应付)",
252
+ fields: [
253
+ "DocEntry", "DocNum", "DocType", "DocDate", "DocDueDate",
254
+ "CardCode", "CardName", "DocTotal", "DocCurrency",
255
+ "Comments",
256
+ ],
257
+ },
258
+ {
259
+ name: "PurchaseReturns",
260
+ kind: "EntitySet",
261
+ entityType: "SAPB1.Document",
262
+ primaryKey: "DocEntry",
263
+ domain: "Document",
264
+ description: "采购退货",
265
+ fields: [
266
+ "DocEntry", "DocNum", "DocType", "DocDate",
267
+ "CardCode", "CardName", "DocTotal", "Comments",
268
+ ],
269
+ },
270
+ {
271
+ name: "PurchaseQuotations",
272
+ kind: "EntitySet",
273
+ entityType: "SAPB1.Document",
274
+ primaryKey: "DocEntry",
275
+ domain: "Document",
276
+ description: "采购报价",
277
+ fields: [
278
+ "DocEntry", "DocNum", "DocType", "DocDate",
279
+ "CardCode", "CardName", "DocTotal", "Comments",
280
+ ],
281
+ },
282
+
283
+ // ========== Inventory / Warehouse ==========
284
+ {
285
+ name: "InventoryGenEntries",
286
+ kind: "EntitySet",
287
+ entityType: "SAPB1.Document",
288
+ primaryKey: "DocEntry",
289
+ domain: "Inventory / Warehouse",
290
+ description: "库存收货",
291
+ fields: [
292
+ "DocEntry", "DocNum", "DocType", "DocDate",
293
+ "Comments", "JournalMemo", "Reference1", "Reference2",
294
+ "WareHouseUpdateType",
295
+ ],
296
+ },
297
+ {
298
+ name: "InventoryGenExits",
299
+ kind: "EntitySet",
300
+ entityType: "SAPB1.Document",
301
+ primaryKey: "DocEntry",
302
+ domain: "Inventory / Warehouse",
303
+ description: "库存发货",
304
+ fields: [
305
+ "DocEntry", "DocNum", "DocType", "DocDate",
306
+ "Comments", "JournalMemo", "Reference1", "Reference2",
307
+ ],
308
+ },
309
+ {
310
+ name: "StockTransfers",
311
+ kind: "EntitySet",
312
+ entityType: "SAPB1.StockTransfer",
313
+ primaryKey: "DocEntry",
314
+ domain: "Inventory / Warehouse",
315
+ description: "库存转储",
316
+ fields: [
317
+ "DocEntry", "DocNum", "DocDate", "DueDate",
318
+ "FromWarehouse", "ToWarehouse", "Comments",
319
+ "Reference1", "Reference2",
320
+ ],
321
+ },
322
+ {
323
+ name: "InventoryPostings",
324
+ kind: "EntitySet",
325
+ entityType: "SAPB1.InventoryPosting",
326
+ primaryKey: "DocumentEntry",
327
+ domain: "Inventory / Warehouse",
328
+ description: "库存过账",
329
+ fields: [
330
+ "DocumentEntry", "DocumentNumber", "PostingDate", "CountDate",
331
+ "Remarks", "Reference2", "PriceList",
332
+ ],
333
+ },
334
+ {
335
+ name: "InventoryCountings",
336
+ kind: "EntitySet",
337
+ entityType: "SAPB1.InventoryCounting",
338
+ primaryKey: "DocumentEntry",
339
+ domain: "Inventory / Warehouse",
340
+ description: "库存盘点",
341
+ fields: [
342
+ "DocumentEntry", "DocumentNumber", "CountDate",
343
+ "SingleCounterType", "SingleCounterID", "Remarks",
344
+ ],
345
+ },
346
+ {
347
+ name: "Warehouses",
348
+ kind: "EntitySet",
349
+ entityType: "SAPB1.Warehouse",
350
+ primaryKey: "WarehouseCode",
351
+ domain: "Inventory / Warehouse",
352
+ description: "仓库定义",
353
+ fields: [
354
+ "WarehouseCode", "WarehouseName",
355
+ "Street", "City", "Country", "Location",
356
+ "DropShip", "Nettable", "Inactive",
357
+ "EnableBinLocations", "ManageSerialAndBatchNumbers",
358
+ ],
359
+ },
360
+ {
361
+ name: "BinLocations",
362
+ kind: "EntitySet",
363
+ entityType: "SAPB1.BinLocation",
364
+ primaryKey: "AbsEntry",
365
+ domain: "Inventory / Warehouse",
366
+ description: "库位",
367
+ fields: [
368
+ "AbsEntry", "Warehouse", "BinCode", "Description",
369
+ "Sublevel1", "Sublevel2", "Sublevel3", "Sublevel4",
370
+ "Inactive", "MinimumQty", "MaximumQty",
371
+ ],
372
+ },
373
+ {
374
+ name: "BatchNumberDetails",
375
+ kind: "EntitySet",
376
+ entityType: "SAPB1.BatchNumberDetail",
377
+ primaryKey: "DocEntry",
378
+ domain: "Inventory / Warehouse",
379
+ description: "批次号明细",
380
+ fields: [
381
+ "DocEntry", "ItemCode", "ItemDescription",
382
+ "Batch", "BatchAttribute1", "BatchAttribute2",
383
+ "AdmissionDate", "ExpirationDate", "ManufacturingDate",
384
+ ],
385
+ },
386
+ {
387
+ name: "SerialNumberDetails",
388
+ kind: "EntitySet",
389
+ entityType: "SAPB1.SerialNumberDetail",
390
+ primaryKey: "DocEntry",
391
+ domain: "Inventory / Warehouse",
392
+ description: "序列号明细",
393
+ fields: [
394
+ "DocEntry", "ItemCode", "ItemDescription",
395
+ "SerialNumber", "MfrSerialNo",
396
+ "AdmissionDate", "ExpirationDate",
397
+ ],
398
+ },
399
+
400
+ // ========== Function Imports (常用) ==========
401
+ {
402
+ name: "ItemsService_InitData",
403
+ kind: "FunctionImport",
404
+ entityType: "SAPB1.Item",
405
+ domain: "Item / Product",
406
+ description: "初始化物料数据",
407
+ fields: [],
408
+ },
409
+ {
410
+ name: "BusinessPartnersService_InitData",
411
+ kind: "FunctionImport",
412
+ entityType: "SAPB1.BusinessPartner",
413
+ domain: "BusinessPartner",
414
+ description: "初始化 BP 数据",
415
+ fields: [],
416
+ },
417
+ {
418
+ name: "OrdersService_InitData",
419
+ kind: "FunctionImport",
420
+ entityType: "SAPB1.Document",
421
+ domain: "Document",
422
+ description: "初始化订单数据",
423
+ fields: [],
424
+ },
425
+ {
426
+ name: "InvoicesService_InitData",
427
+ kind: "FunctionImport",
428
+ entityType: "SAPB1.Document",
429
+ domain: "Document",
430
+ description: "初始化发票数据",
431
+ fields: [],
432
+ },
433
+
434
+ // ========== Pricing / Cost / Rate ==========
435
+ {
436
+ name: "SpecialPrices",
437
+ kind: "EntitySet",
438
+ entityType: "SAPB1.SpecialPrice",
439
+ primaryKey: "ItemCode+CardCode",
440
+ domain: "Item / Product",
441
+ description: "特殊价格(BP/客户特定价格)",
442
+ fields: [
443
+ "ItemCode", "CardCode", "Price", "Currency",
444
+ "DiscountPercent", "PriceListNum", "AutoUpdate", "SourcePrice",
445
+ ],
446
+ },
447
+ {
448
+ name: "LandedCosts",
449
+ kind: "EntitySet",
450
+ entityType: "SAPB1.LandedCost",
451
+ primaryKey: "DocEntry",
452
+ domain: "Document",
453
+ description: "到岸成本/附加成本凭证",
454
+ fields: ["DocEntry", "DocNum", "DocDate", "CardCode", "Comments"],
455
+ },
456
+ {
457
+ name: "LandedCostsCodes",
458
+ kind: "EntitySet",
459
+ entityType: "SAPB1.LandedCostsCode",
460
+ primaryKey: "Code",
461
+ domain: "Document",
462
+ description: "到岸成本代码定义",
463
+ fields: ["Code", "Name"],
464
+ },
465
+ {
466
+ name: "CompanyService_GetItemPrice",
467
+ kind: "FunctionImport",
468
+ entityType: "SAPB1.ItemPriceReturnParams",
469
+ domain: "Item / Product",
470
+ description: "根据参数查询物料价格(含历史明细)",
471
+ fields: ["ItemPrices", "ItemUnitOfMeasurementCollection"],
472
+ },
473
+ {
474
+ name: "SBOBobService_GetCurrencyRate",
475
+ kind: "FunctionImport",
476
+ domain: "Finance / Accounting",
477
+ description: "获取货币汇率",
478
+ fields: [],
479
+ },
480
+ {
481
+ name: "SBOBobService_GetIndexRate",
482
+ kind: "FunctionImport",
483
+ domain: "Finance / Accounting",
484
+ description: "获取指数汇率",
485
+ fields: [],
486
+ },
487
+ {
488
+ name: "SBOBobService_SetCurrencyRate",
489
+ kind: "FunctionImport",
490
+ domain: "Finance / Accounting",
491
+ description: "设置货币汇率",
492
+ fields: [],
493
+ },
494
+ {
495
+ name: "Z20_COST",
496
+ kind: "EntitySet",
497
+ entityType: "SAPB1.Z20_COST",
498
+ primaryKey: "DocEntry",
499
+ domain: "Item / Product",
500
+ description: "自定义: 成本记录表(含周期/实例维度的成本历史)",
501
+ fields: [
502
+ "DocEntry", "DocNum", "Period", "Instance", "Series",
503
+ "Status", "RequestStatus", "Creator", "Remark",
504
+ "Canceled", "Object", "CreateDate", "CreateTime",
505
+ ],
506
+ },
507
+ {
508
+ name: "Z20_CPAT",
509
+ kind: "EntitySet",
510
+ entityType: "SAPB1.Z20_CPAT",
511
+ primaryKey: "DocEntry",
512
+ domain: "Item / Product",
513
+ description: "自定义: 成本/价格分摊记录",
514
+ fields: [
515
+ "DocEntry", "DocNum", "Period", "Instance",
516
+ "Status", "Creator", "Remark", "CreateDate", "CreateTime",
517
+ ],
518
+ },
519
+ {
520
+ name: "Z20_OINP",
521
+ kind: "EntitySet",
522
+ entityType: "SAPB1.Z20_OINP",
523
+ primaryKey: "DocEntry",
524
+ domain: "Document",
525
+ description: "自定义: 采购订单输入价格记录",
526
+ fields: [
527
+ "DocEntry", "DocNum", "Period", "Instance",
528
+ "Status", "Creator", "Remark", "CreateDate", "CreateTime",
529
+ ],
530
+ },
531
+ {
532
+ name: "Z20_PWAG",
533
+ kind: "EntitySet",
534
+ entityType: "SAPB1.Z20_PWAG",
535
+ primaryKey: "DocEntry",
536
+ domain: "Item / Product",
537
+ description: "自定义: 工价/工序价格记录",
538
+ fields: [
539
+ "DocEntry", "DocNum", "Period", "Instance",
540
+ "Status", "Creator", "Remark", "CreateDate", "CreateTime",
541
+ ],
542
+ },
543
+ {
544
+ name: "Z20_HOLD",
545
+ kind: "EntitySet",
546
+ entityType: "SAPB1.Z20_HOLD",
547
+ primaryKey: "DocEntry",
548
+ domain: "Inventory / Warehouse",
549
+ description: "自定义: 暂存/冻结库存记录",
550
+ fields: [
551
+ "DocEntry", "DocNum", "Period", "Instance",
552
+ "Status", "Creator", "Remark", "CreateDate", "CreateTime",
553
+ ],
554
+ },
555
+ {
556
+ name: "Z20_IMIT",
557
+ kind: "EntitySet",
558
+ entityType: "SAPB1.Z20_IMIT",
559
+ primaryKey: "DocEntry",
560
+ domain: "Inventory / Warehouse",
561
+ description: "自定义: 库存初始化记录",
562
+ fields: [
563
+ "DocEntry", "DocNum", "Period", "Instance",
564
+ "Status", "Creator", "Remark", "CreateDate", "CreateTime",
565
+ ],
566
+ },
567
+ ];
568
+
569
+ // ============================================================
570
+ // Tool 1: sap_api_search
571
+ // ============================================================
572
+
573
+ registerToolLattice(
574
+ "sap_api_search",
575
+ {
576
+ name: "sap_api_search",
577
+ description:
578
+ "搜索 SAP B1 Service Layer API 接口。覆盖业务伙伴(BP)、物料(Item)、销售/采购订单(Document/Order)、" +
579
+ "库存(Inventory/Warehouse) 四大领域。返回接口名称、主键、常用字段列表及描述。",
580
+ needUserApprove: false,
581
+ schema: z.object({
582
+ query: z
583
+ .string()
584
+ .describe(
585
+ "搜索关键词。可以是 API 名称(如 'BusinessPartners', 'Orders', 'Items', 'PurchaseOrders')" +
586
+ "或业务描述(如 '客户', '订单', '物料', '库存', '采购', '仓库')"
587
+ ),
588
+ domain: z
589
+ .string()
590
+ .optional()
591
+ .describe(
592
+ "按领域过滤: 'BusinessPartner'(BP), 'Item / Product'(物料), 'Document'(订单/发票), 'Inventory / Warehouse'(库存/仓库)"
593
+ ),
594
+ maxResults: z.number().optional().default(10).describe("最大返回条数"),
595
+ }),
596
+ },
597
+ async (input) => {
598
+ const q = input.query.toLowerCase();
599
+ const max = input.maxResults ?? 10;
600
+
601
+ // 中文关键词映射到领域
602
+ const domainHints: Record<string, string> = {
603
+ "客户": "BusinessPartner",
604
+ "供应商": "BusinessPartner",
605
+ "bp": "BusinessPartner",
606
+ "物料": "Item / Product",
607
+ "产品": "Item / Product",
608
+ "商品": "Item / Product",
609
+ "货品": "Item / Product",
610
+ "订单": "Document",
611
+ "销售": "Document",
612
+ "采购": "Document",
613
+ "发票": "Document",
614
+ "交货": "Document",
615
+ "报价": "Document",
616
+ "草稿": "Document",
617
+ "库存": "Inventory / Warehouse",
618
+ "仓库": "Inventory / Warehouse",
619
+ "库位": "Inventory / Warehouse",
620
+ "批次": "Inventory / Warehouse",
621
+ "序列号": "Inventory / Warehouse",
622
+ "收货": "Inventory / Warehouse",
623
+ "发货": "Inventory / Warehouse",
624
+ "转储": "Inventory / Warehouse",
625
+ "盘点": "Inventory / Warehouse",
626
+ "过账": "Inventory / Warehouse",
627
+ "价格": "Item / Product",
628
+ "成本": "Item / Product",
629
+ "汇率": "Finance / Accounting",
630
+ "到岸": "Document",
631
+ };
632
+
633
+ const hintedDomain = domainHints[q] || undefined;
634
+ const effectiveDomain = input.domain || hintedDomain;
635
+
636
+ const scored = API_LIST.filter((e) => {
637
+ if (effectiveDomain && e.domain !== effectiveDomain) return false;
638
+ return true;
639
+ }).map((e) => {
640
+ let score = 0;
641
+ const nameLo = e.name.toLowerCase();
642
+ const descLo = e.description.toLowerCase();
643
+ const typeLo = (e.entityType || "").toLowerCase();
644
+
645
+ if (nameLo === q) score += 100;
646
+ else if (nameLo.startsWith(q)) score += 60;
647
+ else if (nameLo.includes(q)) score += 30;
648
+ if (descLo.includes(q)) score += 20;
649
+ if (typeLo.includes(q)) score += 10;
650
+
651
+ for (const word of q.split(/[\s_\-/]+/).filter((w: string) => w.length >= 2)) {
652
+ if (nameLo.includes(word)) score += 10;
653
+ if (descLo.includes(word)) score += 5;
654
+ }
655
+
656
+ return { ...e, score };
657
+ });
658
+
659
+ const top = scored
660
+ .filter((e) => e.score > 0)
661
+ .sort((a, b) => b.score - a.score)
662
+ .slice(0, max);
663
+
664
+ const domainCounts: Record<string, number> = {};
665
+ for (const e of top) domainCounts[e.domain] = (domainCounts[e.domain] || 0) + 1;
666
+
667
+ return {
668
+ query: input.query,
669
+ domainFilter: effectiveDomain || null,
670
+ totalMatches: scored.filter((e) => e.score > 0).length,
671
+ domainsFound: domainCounts,
672
+ results: top.map((e) => ({
673
+ name: e.name,
674
+ kind: e.kind,
675
+ domain: e.domain,
676
+ description: e.description,
677
+ primaryKey: e.primaryKey || null,
678
+ fields: e.fields,
679
+ hint:
680
+ e.kind === "EntitySet"
681
+ ? `${e.name} — ${e.description}。主键: ${e.primaryKey}。调用 sap_api_call 进行 CRUD 操作。`
682
+ : `${e.name} — ${e.description}。调用 sap_api_call 执行此方法。`,
683
+ })),
684
+ suggestion:
685
+ top.length === 0
686
+ ? `未找到匹配 "${input.query}" 的接口。可用领域: BusinessPartner(客户/供应商), Item / Product(物料), Document(订单/发票), Inventory / Warehouse(库存/仓库)。尝试用英文名称搜索。`
687
+ : undefined,
688
+ };
689
+ }
690
+ );
691
+
692
+ // ============================================================
693
+ // Tool 2: sap_api_call
694
+ // ============================================================
695
+
696
+ const SAP_COOKIE = process.env.SAP_B1SESSION
697
+ ? `B1SESSION=${process.env.SAP_B1SESSION}; ROUTEID=.node0`
698
+ : "";
699
+
700
+ registerToolLattice(
701
+ "sap_api_call",
702
+ {
703
+ name: "sap_api_call",
704
+ description:
705
+ "执行对 SAP B1 Service Layer 的 OData API 调用。直接发起 HTTP 请求并返回响应数据。" +
706
+ `当前 Base URL: ${BASE_URL}。` +
707
+ "GET 请求通常无需认证,POST/PATCH/DELETE 需设置环境变量 SAP_B1SESSION。",
708
+ needUserApprove: false,
709
+ schema: z.object({
710
+ entitySet: z
711
+ .string()
712
+ .describe("EntitySet 名称,如 'BusinessPartners', 'Orders', 'Items', 'PurchaseOrders'"),
713
+ method: z.enum(["GET", "POST", "PATCH", "DELETE"]).describe("HTTP 方法"),
714
+ id: z
715
+ .string()
716
+ .optional()
717
+ .describe("主键值,GET/PATCH/DELETE 单个实体时使用。如 /BusinessPartners('C001')"),
718
+ queryOptions: z
719
+ .string()
720
+ .optional()
721
+ .describe(
722
+ "OData 查询参数(不含 `?` 前缀)。常用: " +
723
+ "$top=10, $skip=20, " +
724
+ "$select=CardCode,CardName, " +
725
+ "$filter=contains(CardName,'清华'), " +
726
+ "$orderby=DocDate desc, " +
727
+ "$expand=DocumentLines"
728
+ ),
729
+ body: z.record(z.unknown()).optional().describe("POST/PATCH 时的 JSON 请求体"),
730
+ }),
731
+ },
732
+ async (input) => {
733
+ const url = buildUrl(input.entitySet, input.method, input.id, input.queryOptions);
734
+ const method = input.method;
735
+
736
+ const headers: Record<string, string> = {
737
+ "Content-Type": "application/json",
738
+ Accept: "application/json",
739
+ };
740
+ if (SAP_COOKIE) headers.Cookie = SAP_COOKIE;
741
+
742
+ const fetchOptions: RequestInit = { method, headers };
743
+ if ((method === "POST" || method === "PATCH") && input.body) {
744
+ fetchOptions.body = JSON.stringify(input.body);
745
+ }
746
+
747
+ try {
748
+ const res = await fetch(url, fetchOptions);
749
+ const text = await res.text();
750
+
751
+ let data: unknown;
752
+ try {
753
+ data = JSON.parse(text);
754
+ } catch {
755
+ data = text;
756
+ }
757
+
758
+ const result: Record<string, unknown> = {
759
+ ok: res.ok,
760
+ status: res.status,
761
+ statusText: res.statusText,
762
+ data,
763
+ };
764
+
765
+ if (!res.ok) {
766
+ result.error = `HTTP ${res.status} ${res.statusText}`;
767
+ result.hint =
768
+ res.status === 401
769
+ ? "需要有效的 B1SESSION Cookie。请先通过 Login 端点获取,或设置 SAP_B1SESSION 环境变量。"
770
+ : res.status === 404
771
+ ? "接口或实体不存在,请检查 entitySet 名称和 id。"
772
+ : undefined;
773
+ }
774
+
775
+ return result;
776
+ } catch (err: unknown) {
777
+ const message = err instanceof Error ? err.message : String(err);
778
+ return {
779
+ ok: false,
780
+ error: `请求失败: ${message}`,
781
+ url,
782
+ hint: "网络连接失败,请检查 SAP_SERVICE_LAYER_URL 是否正确。",
783
+ };
784
+ }
785
+ }
786
+ );
787
+
788
+ // ============================================================
789
+ // Helpers
790
+ // ============================================================
791
+
792
+ function buildUrl(
793
+ entitySet: string,
794
+ method: string,
795
+ id?: string,
796
+ queryOptions?: string
797
+ ): string {
798
+ let url = `${BASE_URL}/${entitySet}`;
799
+
800
+ if (id && method !== "POST") {
801
+ url += `('${encodeURIComponent(id)}')`;
802
+ }
803
+
804
+ if (queryOptions) {
805
+ url += `?${queryOptions}`;
806
+ }
807
+
808
+ return url;
809
+ }