@evomap/evolver-adapter-public 2.0.19 → 2.0.23

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.
@@ -13,8 +13,8 @@ export type NodeSecretVersionHandler = (nodeSecretVersion?: number) => void;
13
13
  */
14
14
  export type NodeSecretDivergenceHandler = () => void;
15
15
  /**
16
- * node_secret 双轨过渡(M6-5, 6 个月). 包旧 64-hex node_secret 成 AuthProvider.
17
- * **node_secret request body**(gep-a2a 契约, 实测 dev: requireNodeSecret body 非 header), 不轮换.
16
+ * LegacyAuthShim exposes node_secret as a transport-neutral credential field. HubFetch promotes it to
17
+ * Authorization: Bearer for GET and strict GEP envelope endpoints before egress.
18
18
  */
19
19
  export declare class LegacyAuthShim implements hub.AuthProvider {
20
20
  private nodeSecret;
@@ -13,8 +13,8 @@ export function parseNodeSecretVersion(value) {
13
13
  return Number.isSafeInteger(parsed) ? parsed : undefined;
14
14
  }
15
15
  /**
16
- * node_secret 双轨过渡(M6-5, 6 个月). 包旧 64-hex node_secret 成 AuthProvider.
17
- * **node_secret request body**(gep-a2a 契约, 实测 dev: requireNodeSecret body 非 header), 不轮换.
16
+ * LegacyAuthShim exposes node_secret as a transport-neutral credential field. HubFetch promotes it to
17
+ * Authorization: Bearer for GET and strict GEP envelope endpoints before egress.
18
18
  */
19
19
  export class LegacyAuthShim {
20
20
  nodeSecret;
@@ -194,6 +194,8 @@ export declare class PublicHubCapability implements hub.HubCapability {
194
194
  createRecipe(request: hub.RecipeCreateRequest): Promise<hub.RecipeReceipt>;
195
195
  publishRecipe(recipeId: string, options?: hub.RecipePublishOptions): Promise<hub.RecipeReceipt>;
196
196
  getRecipe(recipeId: string): Promise<hub.RecipeFetchReceipt>;
197
+ searchRecipes(request?: hub.RecipeSearchRequest): Promise<hub.RecipeSearchReceipt>;
198
+ listRecipes(request?: hub.RecipeSearchRequest): Promise<hub.RecipeSearchReceipt>;
197
199
  expressRecipe(recipeId: string, request?: hub.RecipeExpressRequest): Promise<hub.RecipeExpressionReceipt>;
198
200
  task: {
199
201
  claim: (taskId: string) => Promise<{
@@ -103,6 +103,8 @@ export class PublicHubCapability {
103
103
  publish: async (recipeId, options) => this.publishRecipe(recipeId, options),
104
104
  get: async (recipeId) => this.getRecipe(recipeId),
105
105
  express: async (recipeId, request = {}) => this.expressRecipe(recipeId, request),
106
+ search: async (request = {}) => this.searchRecipes(request),
107
+ list: async (request = {}) => this.listRecipes(request),
106
108
  };
107
109
  constructor(opts) {
108
110
  this.opts = opts;
@@ -633,6 +635,20 @@ export class PublicHubCapability {
633
635
  ...(recipe !== undefined ? { recipe } : {}),
634
636
  };
635
637
  }
638
+ async searchRecipes(request = {}) {
639
+ if (isHubDryRunEnabled()) {
640
+ return dryRunRecipeSearchReceipt('search_recipe', request);
641
+ }
642
+ const body = await this.http.call('GET', '/a2a/recipe/search', undefined, recipeSearchQuery(request));
643
+ return recipeSearchReceiptFromBody(body);
644
+ }
645
+ async listRecipes(request = {}) {
646
+ if (isHubDryRunEnabled()) {
647
+ return dryRunRecipeSearchReceipt('list_recipe', request);
648
+ }
649
+ const body = await this.http.call('GET', '/a2a/recipe/list', undefined, recipeSearchQuery(request));
650
+ return recipeSearchReceiptFromBody(body);
651
+ }
636
652
  async expressRecipe(recipeId, request = {}) {
637
653
  if (isHubDryRunEnabled()) {
638
654
  return dryRunRecipeReceipt('express_recipe', recipeId, { input_payload: request.inputPayload ?? {} });
@@ -878,6 +894,58 @@ function recipeOrganismIdFromPayload(payload) {
878
894
  ? stringField(organism, 'id') ?? stringField(organism, 'organism_id') ?? stringField(organism, 'organismId')
879
895
  : undefined;
880
896
  }
897
+ function recipeSearchQuery(request) {
898
+ return {
899
+ ...(request.q ? { q: request.q } : {}),
900
+ ...(request.limit !== undefined ? { limit: request.limit } : {}),
901
+ ...(request.cursor ? { cursor: request.cursor } : {}),
902
+ ...(request.sort ? { sort: request.sort } : {}),
903
+ };
904
+ }
905
+ function recipeListFromRecord(value) {
906
+ for (const key of ['recipes', 'items', 'results']) {
907
+ const found = value[key];
908
+ if (Array.isArray(found))
909
+ return found;
910
+ }
911
+ const nested = asRecord(value['data']);
912
+ if (!nested)
913
+ return undefined;
914
+ for (const key of ['recipes', 'items', 'results']) {
915
+ const found = nested[key];
916
+ if (Array.isArray(found))
917
+ return found;
918
+ }
919
+ return undefined;
920
+ }
921
+ function recipeSearchReceiptFromBody(body) {
922
+ const payload = recipePayload(body);
923
+ const recipes = recipeListFromRecord(payload) ?? recipeListFromRecord(body) ?? [];
924
+ const nextCursor = stringField(payload, 'next_cursor')
925
+ ?? stringField(payload, 'nextCursor')
926
+ ?? stringField(body, 'next_cursor')
927
+ ?? stringField(body, 'nextCursor');
928
+ const hasMore = booleanField(payload, 'has_more')
929
+ ?? booleanField(payload, 'hasMore')
930
+ ?? booleanField(body, 'has_more')
931
+ ?? booleanField(body, 'hasMore');
932
+ return {
933
+ recipes,
934
+ ...(nextCursor ? { nextCursor } : {}),
935
+ ...(hasMore !== undefined ? { hasMore } : {}),
936
+ raw: body,
937
+ };
938
+ }
939
+ function dryRunRecipeSearchReceipt(action, request) {
940
+ return {
941
+ recipes: [],
942
+ raw: {
943
+ dry_run: true,
944
+ would: action,
945
+ ...request,
946
+ },
947
+ };
948
+ }
881
949
  function recipeReceiptFromBody(body) {
882
950
  const payload = recipePayload(body);
883
951
  const recipe = asRecord(payload['recipe']);
@@ -84,9 +84,9 @@ export interface HubFetchDeps {
84
84
  deadlineScheduler?: HubDeadlineScheduler;
85
85
  }
86
86
  /**
87
- * 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证: POST 通常注入 body; GET 与 strict hello envelope
88
- * 走 **Authorization: Bearer <node_secret>** 头(hub 只从 header/body node_secret, 绝不从 query — #8);
89
- * sender_id 是标识非凭证, 留 query/body.
87
+ * 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证: legacy node_secret GET 与 strict
88
+ * GEP envelope POST 走 **Authorization: Bearer <node_secret>** 头,绝不进入 query envelope body;
89
+ * 其余兼容 REST POST 保留既有 body contract。sender_id 是标识非凭证, 留 query/body.
90
90
  * 401/403→AuthError(reauth), 4xx→HubClientError(终态), 5xx→重试.
91
91
  * 非 JSON Hub 响应(WAF/HTML/captive portal/gateway text)→HubUnreachableError, 不触发 auth recovery.
92
92
  */
package/dist/hubFetch.js CHANGED
@@ -72,6 +72,46 @@ const PROTECTED_REQUEST_HEADERS = new Set([
72
72
  'x-evomap-signature',
73
73
  'x-node-secret',
74
74
  ]);
75
+ const LEGACY_BEARER_POST_PATHS = new Set([
76
+ '/a2a/hello',
77
+ '/a2a/publish',
78
+ '/a2a/validate',
79
+ '/a2a/fetch',
80
+ '/a2a/events/poll',
81
+ '/a2a/mailbox/outbound',
82
+ ]);
83
+ function requestHeaderName(headers, name) {
84
+ const normalized = name.toLowerCase();
85
+ return Object.keys(headers).find((headerName) => headerName.toLowerCase() === normalized);
86
+ }
87
+ function setLegacyBearerFallback(headers, nodeSecret) {
88
+ const existingName = requestHeaderName(headers, 'authorization');
89
+ if (existingName !== undefined && headers[existingName]?.trim())
90
+ return false;
91
+ if (existingName !== undefined)
92
+ delete headers[existingName];
93
+ headers['authorization'] = `Bearer ${nodeSecret}`;
94
+ return true;
95
+ }
96
+ function legacyNodeSecret(value) {
97
+ return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value) ? value : undefined;
98
+ }
99
+ function isGepEnvelope(body) {
100
+ return body?.['protocol'] === 'gep-a2a'
101
+ && body['protocol_version'] === '1.0.0'
102
+ && typeof body['message_type'] === 'string'
103
+ && body['message_type'].trim().length > 0
104
+ && typeof body['message_id'] === 'string'
105
+ && body['message_id'].trim().length > 0
106
+ && typeof body['timestamp'] === 'string'
107
+ && Number.isFinite(Date.parse(body['timestamp']))
108
+ && Object.prototype.hasOwnProperty.call(body, 'payload')
109
+ && body['payload'] !== undefined;
110
+ }
111
+ function usesLegacyBearerForPost(method, path, body) {
112
+ return method.toUpperCase() === 'POST'
113
+ && (LEGACY_BEARER_POST_PATHS.has(path) || isGepEnvelope(body));
114
+ }
75
115
  function mergeRequestHeaders(requestHeaders, signedHeaders) {
76
116
  const signedNames = new Set(Object.keys(signedHeaders ?? {}).map((name) => name.toLowerCase()));
77
117
  const headers = {};
@@ -93,9 +133,9 @@ function mergeRequestHeaders(requestHeaders, signedHeaders) {
93
133
  return { ...headers, ...signedHeaders };
94
134
  }
95
135
  /**
96
- * 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证: POST 通常注入 body; GET 与 strict hello envelope
97
- * 走 **Authorization: Bearer <node_secret>** 头(hub 只从 header/body node_secret, 绝不从 query — #8);
98
- * sender_id 是标识非凭证, 留 query/body.
136
+ * 公版 hub HTTP 客户端(M6-6). 每请求经 AuthProvider 取凭证: legacy node_secret GET 与 strict
137
+ * GEP envelope POST 走 **Authorization: Bearer <node_secret>** 头,绝不进入 query envelope body;
138
+ * 其余兼容 REST POST 保留既有 body contract。sender_id 是标识非凭证, 留 query/body.
99
139
  * 401/403→AuthError(reauth), 4xx→HubClientError(终态), 5xx→重试.
100
140
  * 非 JSON Hub 响应(WAF/HTML/captive portal/gateway text)→HubUnreachableError, 不触发 auth recovery.
101
141
  */
@@ -148,26 +188,28 @@ export class HubFetch {
148
188
  qs.set(k, String(v)); // non-credential GET params (e.g. semantic-search q)
149
189
  // #8: credentials must NOT go in the query (leaks to access logs / proxies even over https).
150
190
  // node_secret travels via Authorization: Bearer; the hub reads it there, never from the query.
151
- const nodeSecret = creds['node_secret'];
152
- if (nodeSecret !== undefined && headers['authorization'] === undefined)
153
- headers['authorization'] = `Bearer ${String(nodeSecret)}`;
191
+ const nodeSecret = legacyNodeSecret(creds['node_secret']);
192
+ if (nodeSecret !== undefined)
193
+ setLegacyBearerFallback(headers, nodeSecret);
154
194
  const q = qs.toString();
155
195
  if (q)
156
196
  url += `?${q}`;
157
197
  }
158
198
  else {
159
199
  const postCreds = { ...creds };
160
- const nodeSecret = postCreds['node_secret'];
161
- if ((path === '/a2a/hello' || path === '/a2a/mailbox/outbound') && nodeSecret !== undefined) {
162
- if (headers['authorization'] === undefined)
163
- headers['authorization'] = `Bearer ${String(nodeSecret)}`;
164
- delete postCreds['node_secret'];
200
+ const postBody = { ...(bodyObj ?? {}) };
201
+ const nodeSecret = legacyNodeSecret(postCreds['node_secret']);
202
+ if (usesLegacyBearerForPost(method, path, bodyObj)) {
203
+ delete postBody['node_secret'];
204
+ if (nodeSecret !== undefined && setLegacyBearerFallback(headers, nodeSecret)) {
205
+ delete postCreds['node_secret'];
206
+ }
165
207
  }
166
208
  if (path === '/a2a/mailbox/outbound' && sender) {
167
209
  const qs = new URLSearchParams({ sender_id: sender });
168
210
  url += `?${qs.toString()}`;
169
211
  }
170
- body = JSON.stringify({ ...(sender ? { sender_id: sender } : {}), ...postCreds, ...(bodyObj ?? {}) });
212
+ body = JSON.stringify({ ...(sender ? { sender_id: sender } : {}), ...postCreds, ...postBody });
171
213
  }
172
214
  let res;
173
215
  try {
@@ -246,7 +288,7 @@ function hubOperationForRequest(path, bodyObj) {
246
288
  && payload['search_only'] === true)
247
289
  return 'search';
248
290
  }
249
- if (path === '/a2a/assets/semantic-search' || path === '/a2a/directory/search')
291
+ if (path === '/a2a/assets/semantic-search' || path === '/a2a/directory/search' || path === '/a2a/recipe/search' || path === '/a2a/recipe/list')
250
292
  return 'search';
251
293
  if (path === '/a2a/heartbeat')
252
294
  return 'heartbeat';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver-adapter-public",
3
- "version": "2.0.19",
3
+ "version": "2.0.23",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@evomap/atp-sdk": "^0.1.0",
20
- "@evomap/evolver-core": "2.0.19",
20
+ "@evomap/evolver-core": "2.0.23",
21
21
  "undici": "^6.27.0"
22
22
  },
23
23
  "optionalDependencies": {