@harrylabsj/kiwi 0.8.0 → 0.10.0
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/README.md +1 -1
- package/dist/a2a/negotiate.d.ts +6 -2
- package/dist/a2a/negotiate.js +10 -3
- package/dist/a2a/negotiate.js.map +1 -1
- package/dist/a2a/node.d.ts +4 -1
- package/dist/a2a/node.js +56 -3
- package/dist/a2a/node.js.map +1 -1
- package/dist/a2a/server/merchant-handler.d.ts +11 -3
- package/dist/a2a/server/merchant-handler.js +84 -26
- package/dist/a2a/server/merchant-handler.js.map +1 -1
- package/dist/agent/buyer/buyer-tools.js +2 -2
- package/dist/agent/buyer/buyer-tools.js.map +1 -1
- package/dist/agent/kernel.js +5 -1
- package/dist/agent/kernel.js.map +1 -1
- package/dist/agent/memory/schema.d.ts +1 -1
- package/dist/agent/memory/schema.js +9 -1
- package/dist/agent/memory/schema.js.map +1 -1
- package/dist/agent/merchant/action-candidate.d.ts +18 -0
- package/dist/agent/merchant/action-candidate.js +51 -0
- package/dist/agent/merchant/action-candidate.js.map +1 -1
- package/dist/agent/merchant/fake-merchant-client.js +6 -1
- package/dist/agent/merchant/fake-merchant-client.js.map +1 -1
- package/dist/agent/merchant/intelligence/default-backend.js +25 -2
- package/dist/agent/merchant/intelligence/default-backend.js.map +1 -1
- package/dist/agent/merchant/merchant-client.d.ts +50 -1
- package/dist/agent/merchant/merchant-client.js +157 -8
- package/dist/agent/merchant/merchant-client.js.map +1 -1
- package/dist/agent/merchant/merchant-presentations.js +25 -2
- package/dist/agent/merchant/merchant-presentations.js.map +1 -1
- package/dist/agent/merchant/merchant-tools.js +102 -188
- package/dist/agent/merchant/merchant-tools.js.map +1 -1
- package/dist/agent/merchant/types.d.ts +6 -2
- package/dist/agent/merchant/types.js +45 -0
- package/dist/agent/merchant/types.js.map +1 -1
- package/dist/auth/merchant-authorization.d.ts +49 -0
- package/dist/auth/merchant-authorization.js +61 -0
- package/dist/auth/merchant-authorization.js.map +1 -0
- package/dist/auth/merchant-oauth.d.ts +255 -0
- package/dist/auth/merchant-oauth.js +764 -0
- package/dist/auth/merchant-oauth.js.map +1 -0
- package/dist/auth/merchant-pairing.d.ts +62 -0
- package/dist/auth/merchant-pairing.js +199 -0
- package/dist/auth/merchant-pairing.js.map +1 -0
- package/dist/auth/merchant-sessions.d.ts +71 -0
- package/dist/auth/merchant-sessions.js +168 -0
- package/dist/auth/merchant-sessions.js.map +1 -0
- package/dist/buyer-core/build-service.d.ts +5 -0
- package/dist/buyer-core/build-service.js +12 -0
- package/dist/buyer-core/build-service.js.map +1 -1
- package/dist/buyer-core/errors.d.ts +1 -1
- package/dist/buyer-core/errors.js +2 -0
- package/dist/buyer-core/errors.js.map +1 -1
- package/dist/buyer-core/merchant-index.d.ts +19 -4
- package/dist/buyer-core/merchant-index.js +137 -16
- package/dist/buyer-core/merchant-index.js.map +1 -1
- package/dist/buyer-core/negotiator.d.ts +0 -10
- package/dist/buyer-core/negotiator.js +12 -1
- package/dist/buyer-core/negotiator.js.map +1 -1
- package/dist/buyer-core/quote-fetcher.js +2 -1
- package/dist/buyer-core/quote-fetcher.js.map +1 -1
- package/dist/buyer-core/service.d.ts +104 -0
- package/dist/buyer-core/service.js +105 -1
- package/dist/buyer-core/service.js.map +1 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +612 -20
- package/dist/cli.js.map +1 -1
- package/dist/config/profile.d.ts +30 -0
- package/dist/config/profile.js +126 -61
- package/dist/config/profile.js.map +1 -1
- package/dist/demo/demo-runner.js +12 -9
- package/dist/demo/demo-runner.js.map +1 -1
- package/dist/discovery/catalog-source/buyer-follows.d.ts +82 -0
- package/dist/discovery/catalog-source/buyer-follows.js +228 -0
- package/dist/discovery/catalog-source/buyer-follows.js.map +1 -0
- package/dist/discovery/catalog-source/connector-identity.d.ts +75 -0
- package/dist/discovery/catalog-source/connector-identity.js +175 -0
- package/dist/discovery/catalog-source/connector-identity.js.map +1 -0
- package/dist/discovery/catalog-source/errors.d.ts +1 -1
- package/dist/discovery/catalog-source/errors.js +2 -0
- package/dist/discovery/catalog-source/errors.js.map +1 -1
- package/dist/discovery/catalog-source/merchant-publications.d.ts +65 -0
- package/dist/discovery/catalog-source/merchant-publications.js +207 -0
- package/dist/discovery/catalog-source/merchant-publications.js.map +1 -0
- package/dist/discovery/catalog-source/register.d.ts +24 -0
- package/dist/discovery/catalog-source/register.js +46 -0
- package/dist/discovery/catalog-source/register.js.map +1 -1
- package/dist/discovery/catalog-source/source.js +2 -1
- package/dist/discovery/catalog-source/source.js.map +1 -1
- package/dist/fs/atomic-write.d.ts +18 -0
- package/dist/fs/atomic-write.js +29 -0
- package/dist/fs/atomic-write.js.map +1 -0
- package/dist/http/server.js +4 -1
- package/dist/http/server.js.map +1 -1
- package/dist/mcp/cli.d.ts +2 -0
- package/dist/mcp/cli.js +7 -6
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/merchant-auth.d.ts +78 -0
- package/dist/mcp/merchant-auth.js +146 -0
- package/dist/mcp/merchant-auth.js.map +1 -0
- package/dist/mcp/merchant-dirs.d.ts +26 -0
- package/dist/mcp/merchant-dirs.js +44 -0
- package/dist/mcp/merchant-dirs.js.map +1 -0
- package/dist/mcp/merchant-resources.d.ts +38 -0
- package/dist/mcp/merchant-resources.js +146 -0
- package/dist/mcp/merchant-resources.js.map +1 -0
- package/dist/mcp/merchant-server.d.ts +106 -0
- package/dist/mcp/merchant-server.js +615 -0
- package/dist/mcp/merchant-server.js.map +1 -0
- package/dist/mcp/merchant-tools.d.ts +103 -0
- package/dist/mcp/merchant-tools.js +517 -0
- package/dist/mcp/merchant-tools.js.map +1 -0
- package/dist/mcp/server.js +4 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +5 -3
- package/dist/mcp/tools.js +102 -5
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts +1 -1
- package/dist/mcp/types.js +4 -0
- package/dist/mcp/types.js.map +1 -1
- package/dist/merchant/ops.d.ts +0 -11
- package/dist/merchant/ops.js +13 -1
- package/dist/merchant/ops.js.map +1 -1
- package/dist/merchant/workbench-service.d.ts +189 -0
- package/dist/merchant/workbench-service.js +417 -0
- package/dist/merchant/workbench-service.js.map +1 -0
- package/dist/merchant-admin/pending-page.d.ts +35 -0
- package/dist/merchant-admin/pending-page.js +69 -0
- package/dist/merchant-admin/pending-page.js.map +1 -0
- package/dist/merchant-core/commands.d.ts +99 -0
- package/dist/merchant-core/commands.js +179 -0
- package/dist/merchant-core/commands.js.map +1 -0
- package/dist/merchant-core/executor.d.ts +69 -0
- package/dist/merchant-core/executor.js +218 -0
- package/dist/merchant-core/executor.js.map +1 -0
- package/dist/merchant-core/negotiation-adapters.d.ts +55 -0
- package/dist/merchant-core/negotiation-adapters.js +55 -0
- package/dist/merchant-core/negotiation-adapters.js.map +1 -0
- package/dist/merchant-core/network-checks.d.ts +65 -0
- package/dist/merchant-core/network-checks.js +111 -0
- package/dist/merchant-core/network-checks.js.map +1 -0
- package/dist/merchant-core/operations.d.ts +64 -0
- package/dist/merchant-core/operations.js +136 -0
- package/dist/merchant-core/operations.js.map +1 -0
- package/dist/merchant-core/policy-runtime.d.ts +70 -0
- package/dist/merchant-core/policy-runtime.js +181 -0
- package/dist/merchant-core/policy-runtime.js.map +1 -0
- package/dist/merchant-core/product-import.d.ts +67 -0
- package/dist/merchant-core/product-import.js +160 -0
- package/dist/merchant-core/product-import.js.map +1 -0
- package/dist/merchant-core/service.d.ts +203 -0
- package/dist/merchant-core/service.js +402 -0
- package/dist/merchant-core/service.js.map +1 -0
- package/dist/merchant-gateway/catalog-publications.d.ts +96 -0
- package/dist/merchant-gateway/catalog-publications.js +212 -0
- package/dist/merchant-gateway/catalog-publications.js.map +1 -0
- package/dist/merchant-gateway/catalog-tools.d.ts +46 -0
- package/dist/merchant-gateway/catalog-tools.js +315 -0
- package/dist/merchant-gateway/catalog-tools.js.map +1 -0
- package/dist/merchant-gateway/cli.d.ts +75 -0
- package/dist/merchant-gateway/cli.js +461 -0
- package/dist/merchant-gateway/cli.js.map +1 -0
- package/dist/merchant-gateway/credential-vault.d.ts +44 -0
- package/dist/merchant-gateway/credential-vault.js +111 -0
- package/dist/merchant-gateway/credential-vault.js.map +1 -0
- package/dist/merchant-gateway/entry-server.d.ts +117 -0
- package/dist/merchant-gateway/entry-server.js +747 -0
- package/dist/merchant-gateway/entry-server.js.map +1 -0
- package/dist/merchant-gateway/instance-registration.d.ts +144 -0
- package/dist/merchant-gateway/instance-registration.js +391 -0
- package/dist/merchant-gateway/instance-registration.js.map +1 -0
- package/dist/merchant-gateway/instance-tools.d.ts +58 -0
- package/dist/merchant-gateway/instance-tools.js +186 -0
- package/dist/merchant-gateway/instance-tools.js.map +1 -0
- package/dist/merchant-gateway/mcp-proxy.d.ts +25 -0
- package/dist/merchant-gateway/mcp-proxy.js +192 -0
- package/dist/merchant-gateway/mcp-proxy.js.map +1 -0
- package/dist/merchant-gateway/pinned-fetch.d.ts +54 -0
- package/dist/merchant-gateway/pinned-fetch.js +231 -0
- package/dist/merchant-gateway/pinned-fetch.js.map +1 -0
- package/dist/merchant-gateway/tenant-registry.d.ts +82 -0
- package/dist/merchant-gateway/tenant-registry.js +190 -0
- package/dist/merchant-gateway/tenant-registry.js.map +1 -0
- package/dist/merchant-gateway/tool-bundle.d.ts +26 -0
- package/dist/merchant-gateway/tool-bundle.js +56 -0
- package/dist/merchant-gateway/tool-bundle.js.map +1 -0
- package/dist/merchant-runtime/backup.d.ts +48 -0
- package/dist/merchant-runtime/backup.js +226 -0
- package/dist/merchant-runtime/backup.js.map +1 -0
- package/dist/merchant-runtime/health.d.ts +99 -0
- package/dist/merchant-runtime/health.js +206 -0
- package/dist/merchant-runtime/health.js.map +1 -0
- package/dist/merchant-runtime/jobs.d.ts +64 -0
- package/dist/merchant-runtime/jobs.js +107 -0
- package/dist/merchant-runtime/jobs.js.map +1 -0
- package/dist/merchant-runtime/manager.d.ts +94 -0
- package/dist/merchant-runtime/manager.js +305 -0
- package/dist/merchant-runtime/manager.js.map +1 -0
- package/dist/merchant-runtime/services.d.ts +30 -0
- package/dist/merchant-runtime/services.js +51 -0
- package/dist/merchant-runtime/services.js.map +1 -0
- package/dist/net/url.d.ts +26 -0
- package/dist/net/url.js +32 -0
- package/dist/net/url.js.map +1 -0
- package/dist/product-cli.js +22 -3
- package/dist/product-cli.js.map +1 -1
- package/dist/product-compat.d.ts +9 -1
- package/dist/product-compat.js +12 -1
- package/dist/product-compat.js.map +1 -1
- package/dist/product-init.js +17 -5
- package/dist/product-init.js.map +1 -1
- package/dist/product-publish.js +2 -1
- package/dist/product-publish.js.map +1 -1
- package/package.json +3 -2
- package/skills/kiwi-buyer/SKILL.md +2 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/** 授权请求(入口持有;login_url 是给商家浏览器打开的地址)。 */
|
|
17
|
+
export interface ConnectorIdentityRequest {
|
|
18
|
+
readonly requestId: string;
|
|
19
|
+
readonly loginUrl: string;
|
|
20
|
+
readonly expiresAt: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 身份兑换时同时签发的商家作用域凭据(目录侧 schema v32)。
|
|
24
|
+
*
|
|
25
|
+
* 入口用它代表**该商家**调用目录的商家接口(公开资料草稿/状态/撤回);发布
|
|
26
|
+
* 仍须商家在目录门户确认页批准,凭据本身不能把 draft 变成 published。
|
|
27
|
+
*/
|
|
28
|
+
export interface ConnectorMerchantCredential {
|
|
29
|
+
readonly accessToken: string;
|
|
30
|
+
readonly scope: string;
|
|
31
|
+
readonly expiresAt: string;
|
|
32
|
+
}
|
|
33
|
+
/** 兑换结果:已验证的商家身份(唯一可信来源)+ 该商家的作用域凭据。 */
|
|
34
|
+
export interface ConnectorIdentity {
|
|
35
|
+
readonly merchantId: string;
|
|
36
|
+
readonly merchantName: string;
|
|
37
|
+
readonly credential: ConnectorMerchantCredential;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 入口需要的身份能力。测试与部署可注入不同实现;生产实现是本文件的
|
|
41
|
+
* HTTP 客户端。
|
|
42
|
+
*/
|
|
43
|
+
export interface CatalogIdentityProvider {
|
|
44
|
+
createRequest(input: {
|
|
45
|
+
returnUrl: string;
|
|
46
|
+
clientLabel?: string;
|
|
47
|
+
}): Promise<ConnectorIdentityRequest>;
|
|
48
|
+
exchange(input: {
|
|
49
|
+
requestId: string;
|
|
50
|
+
code: string;
|
|
51
|
+
}): Promise<ConnectorIdentity>;
|
|
52
|
+
}
|
|
53
|
+
export interface CatalogConnectorIdentityDeps {
|
|
54
|
+
/** Catalog 服务根 URL(http/https)。 */
|
|
55
|
+
baseUrl: string;
|
|
56
|
+
/** 连接器机器凭据(catalog 侧 KIWI_CATALOG_CONNECTOR_TOKEN)。 */
|
|
57
|
+
connectorToken: string;
|
|
58
|
+
fetchImpl?: typeof fetch;
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
}
|
|
61
|
+
export declare class CatalogConnectorIdentityClient implements CatalogIdentityProvider {
|
|
62
|
+
private readonly baseUrl;
|
|
63
|
+
private readonly connectorToken;
|
|
64
|
+
private readonly deps;
|
|
65
|
+
constructor(deps: CatalogConnectorIdentityDeps);
|
|
66
|
+
createRequest(input: {
|
|
67
|
+
returnUrl: string;
|
|
68
|
+
clientLabel?: string;
|
|
69
|
+
}): Promise<ConnectorIdentityRequest>;
|
|
70
|
+
exchange(input: {
|
|
71
|
+
requestId: string;
|
|
72
|
+
code: string;
|
|
73
|
+
}): Promise<ConnectorIdentity>;
|
|
74
|
+
private postJson;
|
|
75
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* CatalogConnectorIdentityClient —— kiwi-catalog 一次性身份授权(连接器侧)。
|
|
18
|
+
*
|
|
19
|
+
* POST {baseUrl}/v1/connector-identity/requests
|
|
20
|
+
* {return_url, client_label?} → {ok, request:{request_id, expires_at}, login_url}
|
|
21
|
+
* POST {baseUrl}/v1/connector-identity/exchange
|
|
22
|
+
* {request_id, code} → {ok, identity:{merchant_id, merchant_name}}
|
|
23
|
+
*
|
|
24
|
+
* 契约来源:kiwi-catalog 仓 kiwi_catalog/api/handlers/connector_identity.py
|
|
25
|
+
* (schema v31;merchant-buddy 第 1 版设计 §3.2 / 商家连接器发布计划 §3.1)。
|
|
26
|
+
* 调用方是**商家连接器**的远程入口——买方连接器(`kiwi-sourcing`,本地
|
|
27
|
+
* stdio)不使用本接口。
|
|
28
|
+
*
|
|
29
|
+
* 硬边界(fail-closed,调用方不得吞掉错误):
|
|
30
|
+
* - merchant_id 只能来自 exchange 的返回值——绝不从工具参数、URL 或
|
|
31
|
+
* 客户端自述值推断(商家连接器入口的租户判定唯一来源);
|
|
32
|
+
* - 出站加固与既有 catalog 客户端一致:不跟随重定向(3xx 可能转发凭据)、
|
|
33
|
+
* 超时、响应大小上限、响应结构逐字段校验。
|
|
34
|
+
*
|
|
35
|
+
* connector token(`KIWI_CATALOG_CONNECTOR_TOKEN`)是入口与目录之间的机器
|
|
36
|
+
* 凭据:它只够创建/兑换一次性授权请求,不能读写任何商家数据(目录侧在
|
|
37
|
+
* 商家确认前不会为该请求绑定 merchant_id)。
|
|
38
|
+
*/
|
|
39
|
+
import { PRODUCT_VERSION } from "../../product-cli.js";
|
|
40
|
+
import { isRedirectResponse, readJsonBody } from "../../net/safe-http.js";
|
|
41
|
+
import { CatalogSourceError } from "./errors.js";
|
|
42
|
+
import { validateBaseUrl } from "./source.js";
|
|
43
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
44
|
+
function nonEmptyString(value, field) {
|
|
45
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
46
|
+
throw new CatalogSourceError("response_invalid", `connector-identity response is missing non-empty string field "${field}"`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
export class CatalogConnectorIdentityClient {
|
|
51
|
+
baseUrl;
|
|
52
|
+
connectorToken;
|
|
53
|
+
deps;
|
|
54
|
+
constructor(deps) {
|
|
55
|
+
this.baseUrl = validateBaseUrl(deps.baseUrl);
|
|
56
|
+
if (typeof deps.connectorToken !== "string" || deps.connectorToken.trim() === "") {
|
|
57
|
+
throw new CatalogSourceError("invalid_input", "connector-identity client requires a non-empty connector token");
|
|
58
|
+
}
|
|
59
|
+
this.connectorToken = deps.connectorToken;
|
|
60
|
+
this.deps = deps;
|
|
61
|
+
}
|
|
62
|
+
async createRequest(input) {
|
|
63
|
+
const body = await this.postJson("/v1/connector-identity/requests", {
|
|
64
|
+
return_url: input.returnUrl,
|
|
65
|
+
...(input.clientLabel !== undefined ? { client_label: input.clientLabel } : {}),
|
|
66
|
+
});
|
|
67
|
+
const request = body.request;
|
|
68
|
+
if (request === null || typeof request !== "object" || Array.isArray(request)) {
|
|
69
|
+
throw new CatalogSourceError("response_invalid", 'connector-identity response is missing object field "request"');
|
|
70
|
+
}
|
|
71
|
+
const record = request;
|
|
72
|
+
const loginUrl = nonEmptyString(body.login_url, "login_url");
|
|
73
|
+
let parsedLoginUrl;
|
|
74
|
+
try {
|
|
75
|
+
parsedLoginUrl = new URL(loginUrl);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new CatalogSourceError("response_invalid", "connector-identity login_url is not an absolute URL");
|
|
79
|
+
}
|
|
80
|
+
if (parsedLoginUrl.protocol !== "https:" && parsedLoginUrl.protocol !== "http:") {
|
|
81
|
+
throw new CatalogSourceError("response_invalid", `connector-identity login_url must use http or https (got ${parsedLoginUrl.protocol})`);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
requestId: nonEmptyString(record.request_id, "request.request_id"),
|
|
85
|
+
loginUrl,
|
|
86
|
+
expiresAt: nonEmptyString(record.expires_at, "request.expires_at"),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async exchange(input) {
|
|
90
|
+
const body = await this.postJson("/v1/connector-identity/exchange", {
|
|
91
|
+
request_id: input.requestId,
|
|
92
|
+
code: input.code,
|
|
93
|
+
});
|
|
94
|
+
const identity = body.identity;
|
|
95
|
+
if (identity === null || typeof identity !== "object" || Array.isArray(identity)) {
|
|
96
|
+
throw new CatalogSourceError("response_invalid", 'connector-identity response is missing object field "identity"');
|
|
97
|
+
}
|
|
98
|
+
const record = identity;
|
|
99
|
+
const credential = body.credential;
|
|
100
|
+
if (credential === null || typeof credential !== "object" || Array.isArray(credential)) {
|
|
101
|
+
throw new CatalogSourceError("response_invalid", 'connector-identity response is missing object field "credential"');
|
|
102
|
+
}
|
|
103
|
+
const credentialRecord = credential;
|
|
104
|
+
return {
|
|
105
|
+
merchantId: nonEmptyString(record.merchant_id, "identity.merchant_id"),
|
|
106
|
+
// 商家名允许为空字符串(未填名称的账号),不做非空校验。
|
|
107
|
+
merchantName: typeof record.merchant_name === "string" ? record.merchant_name : "",
|
|
108
|
+
credential: {
|
|
109
|
+
accessToken: nonEmptyString(credentialRecord.access_token, "credential.access_token"),
|
|
110
|
+
scope: typeof credentialRecord.scope === "string" ? credentialRecord.scope : "",
|
|
111
|
+
expiresAt: nonEmptyString(credentialRecord.expires_at, "credential.expires_at"),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async postJson(requestPath, body) {
|
|
116
|
+
const fetchImpl = this.deps.fetchImpl ?? globalThis.fetch;
|
|
117
|
+
const timeoutMs = this.deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
118
|
+
const url = `${this.baseUrl}${requestPath}`;
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
121
|
+
let response;
|
|
122
|
+
let raw;
|
|
123
|
+
try {
|
|
124
|
+
try {
|
|
125
|
+
response = await fetchImpl(url, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
// 出站加固:绝不跟随重定向(3xx 目标不经过校验,且可能转发凭据)。
|
|
128
|
+
redirect: "manual",
|
|
129
|
+
signal: controller.signal,
|
|
130
|
+
headers: {
|
|
131
|
+
accept: "application/json",
|
|
132
|
+
"content-type": "application/json",
|
|
133
|
+
authorization: `Bearer ${this.connectorToken}`,
|
|
134
|
+
"user-agent": `kiwi-connector/${PRODUCT_VERSION}`,
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify(body),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const name = err?.name;
|
|
141
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
142
|
+
throw new CatalogSourceError("request_failed", name === "AbortError"
|
|
143
|
+
? `kiwi-catalog connector request timed out after ${timeoutMs}ms: ${url}`
|
|
144
|
+
: `kiwi-catalog connector request failed: ${url} (${detail})`);
|
|
145
|
+
}
|
|
146
|
+
if (isRedirectResponse(response)) {
|
|
147
|
+
throw new CatalogSourceError("request_failed", `kiwi-catalog connector request must not follow redirects (HTTP ${response.status} from ${url})`);
|
|
148
|
+
}
|
|
149
|
+
if (response.status === 401 || response.status === 403) {
|
|
150
|
+
throw new CatalogSourceError("session_rejected", `kiwi-catalog rejected the connector credential (HTTP ${response.status} from ${url})`);
|
|
151
|
+
}
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new CatalogSourceError("request_failed", `kiwi-catalog connector request returned HTTP ${response.status} from ${url}`);
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
raw = await readJsonBody(response, { signal: controller.signal });
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
throw new CatalogSourceError("response_invalid", `kiwi-catalog connector response from ${url} is not valid JSON`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
}
|
|
165
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
166
|
+
throw new CatalogSourceError("response_invalid", "connector-identity response must be a JSON object");
|
|
167
|
+
}
|
|
168
|
+
const envelope = raw;
|
|
169
|
+
if (envelope.ok !== true) {
|
|
170
|
+
throw new CatalogSourceError("response_invalid", `connector-identity response is not ok: ${typeof envelope.error === "string" ? envelope.error : "unknown error"}`);
|
|
171
|
+
}
|
|
172
|
+
return envelope;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=connector-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector-identity.js","sourceRoot":"","sources":["../../../src/discovery/catalog-source/connector-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* - contract_violation 候选元素未通过 CandidateAgent DTO schema 校验,或 contract
|
|
24
24
|
* 注解非 candidate-agent / 非 1.x(视为协议级违规)。
|
|
25
25
|
*/
|
|
26
|
-
export declare const CATALOG_SOURCE_ERROR_CODES: readonly ["invalid_input", "request_failed", "response_invalid", "contract_violation"];
|
|
26
|
+
export declare const CATALOG_SOURCE_ERROR_CODES: readonly ["invalid_input", "request_failed", "response_invalid", "contract_violation", "session_rejected"];
|
|
27
27
|
export type CatalogSourceErrorCode = (typeof CATALOG_SOURCE_ERROR_CODES)[number];
|
|
28
28
|
/** Catalog 读取失败。带错误码;fail-closed,调用方不得吞掉。 */
|
|
29
29
|
export declare class CatalogSourceError extends Error {
|
|
@@ -28,6 +28,8 @@ export const CATALOG_SOURCE_ERROR_CODES = [
|
|
|
28
28
|
"request_failed",
|
|
29
29
|
"response_invalid",
|
|
30
30
|
"contract_violation",
|
|
31
|
+
/** 会话认证端点拒绝了买家会话(HTTP 401/403):登录态缺失或已过期。 */
|
|
32
|
+
"session_rejected",
|
|
31
33
|
];
|
|
32
34
|
/** Catalog 读取失败。带错误码;fail-closed,调用方不得吞掉。 */
|
|
33
35
|
export class CatalogSourceError extends Error {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/discovery/catalog-source/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,eAAe;IACf,gBAAgB;IAChB,kBAAkB;IAClB,oBAAoB;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/discovery/catalog-source/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,eAAe;IACf,gBAAgB;IAChB,kBAAkB;IAClB,oBAAoB;IACpB,6CAA6C;IAC7C,kBAAkB;CACV,CAAC;AAIX,6CAA6C;AAC7C,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAyB;IAEtC,YAAY,IAA4B,EAAE,OAAe;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import type { CatalogSourceDeps } from "./source.js";
|
|
17
|
+
/**
|
|
18
|
+
* kiwi-catalog 商家公开资料(M0)wire 投影。仅收纳买方发现需要的公开字段;
|
|
19
|
+
* faq/summary 等大字段不进搜索投影(工具返回大小控制)。
|
|
20
|
+
*/
|
|
21
|
+
export interface MerchantPublicationRecord {
|
|
22
|
+
readonly publication_id: string;
|
|
23
|
+
readonly merchant_id: string;
|
|
24
|
+
readonly merchant_display_name: string;
|
|
25
|
+
/** 命中查询的商品名(商家声明)。 */
|
|
26
|
+
readonly title: string;
|
|
27
|
+
readonly category?: string;
|
|
28
|
+
readonly shop_platform?: string;
|
|
29
|
+
readonly shop_url?: string;
|
|
30
|
+
readonly source_kind: "merchant_declared";
|
|
31
|
+
readonly status: "draft" | "published" | "withdrawn";
|
|
32
|
+
readonly published_at?: string;
|
|
33
|
+
readonly updated_at: string;
|
|
34
|
+
/** M0 恒 false(schema 层不变量);为 true 的响应视为协议级违规。 */
|
|
35
|
+
readonly inquiry_available: false;
|
|
36
|
+
}
|
|
37
|
+
/** 公开资料搜索查询(上游键集:q/category/merchant_id/limit/cursor)。 */
|
|
38
|
+
export interface MerchantPublicationSearchQuery {
|
|
39
|
+
q?: string;
|
|
40
|
+
category?: string;
|
|
41
|
+
/** 精确限定商家(RFQ 硬门按 merchant_id 解析用)。 */
|
|
42
|
+
merchant_id?: string;
|
|
43
|
+
limit?: number;
|
|
44
|
+
/** 分页游标(searchPublications 内部翻页用;调用方无需直接设置)。 */
|
|
45
|
+
cursor?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 手写校验(上游无 vendored schema 时的 fail-closed 兜底):必需公开字段 +
|
|
49
|
+
* 两个 M0 不变量。失败抛 contract_violation,绝不把未校验对象当合法结果。
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateMerchantPublication(value: unknown): MerchantPublicationRecord;
|
|
52
|
+
export declare class MerchantPublicationsSource {
|
|
53
|
+
private readonly baseUrl;
|
|
54
|
+
private readonly deps;
|
|
55
|
+
constructor(deps: CatalogSourceDeps);
|
|
56
|
+
/** 核心请求:超时 / HTTP / 网络失败统一映射为 CatalogSourceError。 */
|
|
57
|
+
private getJson;
|
|
58
|
+
/**
|
|
59
|
+
* 公开检索商家公开资料(仅 published 未过期由上游保证)。分页完整拉取,
|
|
60
|
+
* 跨页按 publication_id 去重;limit 是总返回上限而非页大小(同 searchRecords
|
|
61
|
+
* 的历史教训)。旧 catalog 无此端点时抛 request_failed(HTTP 404),由
|
|
62
|
+
* 调用方(KiwiCatalogMerchantIndex)按单侧失败容忍处理。
|
|
63
|
+
*/
|
|
64
|
+
searchPublications(query?: MerchantPublicationSearchQuery): Promise<MerchantPublicationRecord[]>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* MerchantPublicationsSource —— kiwi-catalog 商家公开资料(M0)只读客户端。
|
|
18
|
+
*
|
|
19
|
+
* GET {baseUrl}/v1/merchant-publications/search → {ok, results: […], next_cursor}
|
|
20
|
+
*
|
|
21
|
+
* 契约来源:kiwi-catalog 仓 kiwi_catalog/services/merchant_publications.py 的
|
|
22
|
+
* public_projection(schema v29,merchant-buddy 第 0 版设计 §4 M0 表)。上游暂无
|
|
23
|
+
* vendored JSON schema,本文件用手写校验锁定关键不变量(fail-closed):
|
|
24
|
+
* - source_kind 恒为 merchant_declared(商家声明,不是 Kiwi 背书);
|
|
25
|
+
* - inquiry_available 恒为 false(M0 不产生 Agent Card / A2A 端点 / 实时报价)。
|
|
26
|
+
*
|
|
27
|
+
* 与 KiwiCatalogSource 的关系:同一 catalog 服务的第三个搜索面;出站加固
|
|
28
|
+
* (不跟随重定向、超时、响应大小上限)与错误语义(CatalogSourceError)保持一致。
|
|
29
|
+
*/
|
|
30
|
+
import { CatalogSourceError } from "./errors.js";
|
|
31
|
+
import { PRODUCT_VERSION } from "../../product-cli.js";
|
|
32
|
+
import { isRedirectResponse, readJsonBody, SafeHttpError } from "../../net/safe-http.js";
|
|
33
|
+
import { validateBaseUrl } from "./source.js";
|
|
34
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
35
|
+
/** 分页拉取上限(与 KiwiCatalogSource 同一防御:游标不前进/循环保护)。 */
|
|
36
|
+
const MAX_SEARCH_PAGES = 100;
|
|
37
|
+
const PUBLICATION_SEARCH_QUERY_KEYS = [
|
|
38
|
+
"q",
|
|
39
|
+
"category",
|
|
40
|
+
"merchant_id",
|
|
41
|
+
"limit",
|
|
42
|
+
"cursor",
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* 手写校验(上游无 vendored schema 时的 fail-closed 兜底):必需公开字段 +
|
|
46
|
+
* 两个 M0 不变量。失败抛 contract_violation,绝不把未校验对象当合法结果。
|
|
47
|
+
*/
|
|
48
|
+
export function validateMerchantPublication(value) {
|
|
49
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
50
|
+
throw new CatalogSourceError("contract_violation", "MerchantPublication must be a JSON object");
|
|
51
|
+
}
|
|
52
|
+
const record = value;
|
|
53
|
+
for (const field of [
|
|
54
|
+
"publication_id",
|
|
55
|
+
"merchant_id",
|
|
56
|
+
"merchant_display_name",
|
|
57
|
+
"title",
|
|
58
|
+
"updated_at",
|
|
59
|
+
]) {
|
|
60
|
+
if (typeof record[field] !== "string" || record[field] === "") {
|
|
61
|
+
throw new CatalogSourceError("contract_violation", `MerchantPublication missing non-empty string field "${field}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (record.source_kind !== "merchant_declared") {
|
|
65
|
+
throw new CatalogSourceError("contract_violation", `MerchantPublication source_kind must be "merchant_declared" (got ${String(record.source_kind)})`);
|
|
66
|
+
}
|
|
67
|
+
if (record.inquiry_available !== false) {
|
|
68
|
+
// M0 不变量:公开资料绝不声称可实时询价(不产生虚假 Agent/实时报价标记)。
|
|
69
|
+
throw new CatalogSourceError("contract_violation", "MerchantPublication inquiry_available must be false (M0 invariant)");
|
|
70
|
+
}
|
|
71
|
+
for (const field of ["category", "shop_platform", "shop_url", "published_at"]) {
|
|
72
|
+
const v = record[field];
|
|
73
|
+
if (v !== undefined && v !== null && typeof v !== "string") {
|
|
74
|
+
throw new CatalogSourceError("contract_violation", `MerchantPublication field "${field}" must be a string when present`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return record;
|
|
78
|
+
}
|
|
79
|
+
/** 序列化公开资料搜索查询。未知键 → invalid_input(fail-closed)。 */
|
|
80
|
+
function buildPublicationSearchQuery(query) {
|
|
81
|
+
for (const key of Object.keys(query)) {
|
|
82
|
+
if (!PUBLICATION_SEARCH_QUERY_KEYS.includes(key)) {
|
|
83
|
+
throw new CatalogSourceError("invalid_input", `unknown merchant-publications search query key: "${key}"`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (query.limit !== undefined && (!Number.isInteger(query.limit) || query.limit <= 0)) {
|
|
87
|
+
throw new CatalogSourceError("invalid_input", `merchant-publications search limit must be a positive integer (got ${query.limit})`);
|
|
88
|
+
}
|
|
89
|
+
const entries = [];
|
|
90
|
+
const push = (key, value) => {
|
|
91
|
+
if (value !== undefined)
|
|
92
|
+
entries.push([key, value]);
|
|
93
|
+
};
|
|
94
|
+
push("q", query.q);
|
|
95
|
+
push("category", query.category);
|
|
96
|
+
push("merchant_id", query.merchant_id);
|
|
97
|
+
if (query.limit !== undefined)
|
|
98
|
+
push("limit", String(query.limit));
|
|
99
|
+
push("cursor", query.cursor);
|
|
100
|
+
return new URLSearchParams(entries).toString();
|
|
101
|
+
}
|
|
102
|
+
export class MerchantPublicationsSource {
|
|
103
|
+
baseUrl;
|
|
104
|
+
deps;
|
|
105
|
+
constructor(deps) {
|
|
106
|
+
this.baseUrl = validateBaseUrl(deps.baseUrl);
|
|
107
|
+
this.deps = deps;
|
|
108
|
+
}
|
|
109
|
+
/** 核心请求:超时 / HTTP / 网络失败统一映射为 CatalogSourceError。 */
|
|
110
|
+
async getJson(requestPath) {
|
|
111
|
+
const fetchImpl = this.deps.fetchImpl ?? globalThis.fetch;
|
|
112
|
+
const timeoutMs = this.deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
113
|
+
const url = `${this.baseUrl}${requestPath}`;
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
116
|
+
let response;
|
|
117
|
+
let raw;
|
|
118
|
+
try {
|
|
119
|
+
try {
|
|
120
|
+
response = await fetchImpl(url, {
|
|
121
|
+
// 出站加固:绝不跟随重定向(3xx 目标不经过校验,且可能转发 Bearer 头)。
|
|
122
|
+
redirect: "manual",
|
|
123
|
+
signal: controller.signal,
|
|
124
|
+
headers: {
|
|
125
|
+
accept: "application/json",
|
|
126
|
+
"user-agent": `kiwi-buyer/${PRODUCT_VERSION}`,
|
|
127
|
+
...(this.deps.authToken !== undefined
|
|
128
|
+
? { authorization: `Bearer ${this.deps.authToken}` }
|
|
129
|
+
: {}),
|
|
130
|
+
...(this.deps.buyerId !== undefined ? { "x-buyer-id": this.deps.buyerId } : {}),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
const name = err?.name;
|
|
136
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
137
|
+
throw new CatalogSourceError("request_failed", name === "AbortError"
|
|
138
|
+
? `kiwi-catalog request timed out after ${timeoutMs}ms: ${url}`
|
|
139
|
+
: `kiwi-catalog request failed: ${url} (${detail})`);
|
|
140
|
+
}
|
|
141
|
+
if (isRedirectResponse(response)) {
|
|
142
|
+
throw new CatalogSourceError("request_failed", `kiwi-catalog request must not follow redirects (HTTP ${response.status} from ${url})`);
|
|
143
|
+
}
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new CatalogSourceError("request_failed", `kiwi-catalog request returned HTTP ${response.status} from ${url}`);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
raw = await readJsonBody(response, { signal: controller.signal });
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (controller.signal.aborted) {
|
|
152
|
+
throw new CatalogSourceError("request_failed", `kiwi-catalog request timed out after ${timeoutMs}ms while reading response: ${url}`);
|
|
153
|
+
}
|
|
154
|
+
throw new CatalogSourceError("response_invalid", err instanceof SafeHttpError && err.code === "response_too_large"
|
|
155
|
+
? `kiwi-catalog response from ${url}: ${err.message}`
|
|
156
|
+
: `kiwi-catalog response from ${url} is not valid JSON`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
}
|
|
162
|
+
return raw;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 公开检索商家公开资料(仅 published 未过期由上游保证)。分页完整拉取,
|
|
166
|
+
* 跨页按 publication_id 去重;limit 是总返回上限而非页大小(同 searchRecords
|
|
167
|
+
* 的历史教训)。旧 catalog 无此端点时抛 request_failed(HTTP 404),由
|
|
168
|
+
* 调用方(KiwiCatalogMerchantIndex)按单侧失败容忍处理。
|
|
169
|
+
*/
|
|
170
|
+
async searchPublications(query = {}) {
|
|
171
|
+
const records = [];
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const limit = query.limit;
|
|
174
|
+
let cursor;
|
|
175
|
+
for (let page = 0; page < MAX_SEARCH_PAGES; page++) {
|
|
176
|
+
const qs = buildPublicationSearchQuery({
|
|
177
|
+
...query,
|
|
178
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
179
|
+
});
|
|
180
|
+
if (limit !== undefined && records.length >= limit)
|
|
181
|
+
break;
|
|
182
|
+
const raw = await this.getJson(`/v1/merchant-publications/search${qs.length > 0 ? `?${qs}` : ""}`);
|
|
183
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
184
|
+
throw new CatalogSourceError("response_invalid", "merchant-publications search response must be a JSON object");
|
|
185
|
+
}
|
|
186
|
+
const body = raw;
|
|
187
|
+
if (!Array.isArray(body.results)) {
|
|
188
|
+
throw new CatalogSourceError("response_invalid", 'merchant-publications search response is missing array field "results"');
|
|
189
|
+
}
|
|
190
|
+
for (const element of body.results) {
|
|
191
|
+
if (limit !== undefined && records.length >= limit)
|
|
192
|
+
break;
|
|
193
|
+
const record = validateMerchantPublication(element);
|
|
194
|
+
if (seen.has(record.publication_id))
|
|
195
|
+
continue;
|
|
196
|
+
seen.add(record.publication_id);
|
|
197
|
+
records.push(record);
|
|
198
|
+
}
|
|
199
|
+
const next = body.next_cursor;
|
|
200
|
+
if (typeof next !== "string" || next === "")
|
|
201
|
+
break;
|
|
202
|
+
cursor = next;
|
|
203
|
+
}
|
|
204
|
+
return limit !== undefined ? records.slice(0, limit) : records;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=merchant-publications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merchant-publications.js","sourceRoot":"","sources":["../../../src/discovery/catalog-source/merchant-publications.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,3 +56,27 @@ export declare function normalizeCatalogBaseUrl(url: string): string;
|
|
|
56
56
|
* 幂等:同 domain + agent_card_url 重复注册由 catalog 侧幂等处理,安全可重试。
|
|
57
57
|
*/
|
|
58
58
|
export declare function registerCatalogAgent(input: CatalogRegisterInput): Promise<CatalogRegisterResult>;
|
|
59
|
+
/** 心跳结果(catalog 侧 last_seen_at 与生效 TTL)。 */
|
|
60
|
+
export interface CatalogHeartbeatResult {
|
|
61
|
+
ok: true;
|
|
62
|
+
lastSeenAt: string;
|
|
63
|
+
freshTtlSeconds: number;
|
|
64
|
+
}
|
|
65
|
+
export interface CatalogHeartbeatInput {
|
|
66
|
+
catalogBaseUrl: string;
|
|
67
|
+
catalogAgentId: string;
|
|
68
|
+
/** owner 凭据:随机 token(优先)或 HMAC 派生 secret(legacy)。 */
|
|
69
|
+
ownerToken?: string;
|
|
70
|
+
ownerTokenSecret?: string;
|
|
71
|
+
merchantId?: string;
|
|
72
|
+
fetchImpl?: typeof fetch;
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Agent 心跳(WP6 / 发布计划 §3.6):只刷新 last_seen_at,不重新抓取资料。
|
|
77
|
+
*
|
|
78
|
+
* 商家 A2A 节点周期性调用;catalog 读侧按 last_seen_at + TTL 派生 freshness,
|
|
79
|
+
* 离线商家因此不再被采购专家标成"可实时询价"(公开资料仍可查)。
|
|
80
|
+
* 心跳间隔应显著小于 catalog 的 TTL(缺省 900s)。
|
|
81
|
+
*/
|
|
82
|
+
export declare function sendCatalogAgentHeartbeat(input: CatalogHeartbeatInput): Promise<CatalogHeartbeatResult>;
|
|
@@ -110,4 +110,50 @@ export async function registerCatalogAgent(input) {
|
|
|
110
110
|
verificationEnqueued: payload.verification_enqueued === true,
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Agent 心跳(WP6 / 发布计划 §3.6):只刷新 last_seen_at,不重新抓取资料。
|
|
115
|
+
*
|
|
116
|
+
* 商家 A2A 节点周期性调用;catalog 读侧按 last_seen_at + TTL 派生 freshness,
|
|
117
|
+
* 离线商家因此不再被采购专家标成"可实时询价"(公开资料仍可查)。
|
|
118
|
+
* 心跳间隔应显著小于 catalog 的 TTL(缺省 900s)。
|
|
119
|
+
*/
|
|
120
|
+
export async function sendCatalogAgentHeartbeat(input) {
|
|
121
|
+
const fetchImpl = input.fetchImpl ?? globalThis.fetch;
|
|
122
|
+
const base = normalizeCatalogBaseUrl(input.catalogBaseUrl);
|
|
123
|
+
const body = {};
|
|
124
|
+
if (input.ownerToken !== undefined && input.ownerToken !== "") {
|
|
125
|
+
body.owner_token = input.ownerToken;
|
|
126
|
+
}
|
|
127
|
+
else if (input.ownerTokenSecret !== undefined && input.merchantId !== undefined) {
|
|
128
|
+
body.owner_token = createHmac("sha256", input.ownerTokenSecret)
|
|
129
|
+
.update(`kiwi-catalog-owner:${input.merchantId}`)
|
|
130
|
+
.digest("hex");
|
|
131
|
+
}
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const timer = setTimeout(() => controller.abort(), input.timeoutMs ?? 15_000);
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetchImpl(`${base}/v1/agent-catalog/agents/${encodeURIComponent(input.catalogAgentId)}/heartbeat`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
// 出站纪律同 register:不跟随重定向(体里带 owner 凭据)。
|
|
138
|
+
redirect: "manual",
|
|
139
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
140
|
+
body: JSON.stringify(body),
|
|
141
|
+
signal: controller.signal,
|
|
142
|
+
});
|
|
143
|
+
const text = await res.text();
|
|
144
|
+
const payload = text === "" ? {} : JSON.parse(text);
|
|
145
|
+
if (!res.ok || payload.ok === false) {
|
|
146
|
+
const detail = typeof payload.error === "string" ? payload.error : `HTTP ${res.status}`;
|
|
147
|
+
throw new CatalogSourceError("request_failed", `catalog heartbeat failed: ${detail}`);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
lastSeenAt: typeof payload.last_seen_at === "string" ? payload.last_seen_at : "",
|
|
152
|
+
freshTtlSeconds: typeof payload.fresh_ttl_seconds === "number" ? payload.fresh_ttl_seconds : 0,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
113
159
|
//# sourceMappingURL=register.js.map
|