@x12i/sdk 0.4.0 → 1.0.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.
Files changed (48) hide show
  1. package/README.md +47 -0
  2. package/dist/admin-import.d.ts +6 -0
  3. package/dist/admin-import.js +127 -0
  4. package/dist/admin-import.js.map +1 -0
  5. package/dist/admin-mcp.d.ts +3 -0
  6. package/dist/admin-mcp.js +60 -0
  7. package/dist/admin-mcp.js.map +1 -0
  8. package/dist/admin.d.ts +63 -0
  9. package/dist/admin.js +259 -0
  10. package/dist/admin.js.map +1 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +68 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/client.d.ts +74 -195
  15. package/dist/client.js +187 -695
  16. package/dist/client.js.map +1 -1
  17. package/dist/identity.d.ts +8 -0
  18. package/dist/identity.js +34 -0
  19. package/dist/identity.js.map +1 -0
  20. package/dist/index.d.ts +6 -22
  21. package/dist/index.js +6 -12
  22. package/dist/index.js.map +1 -1
  23. package/dist/init.d.ts +18 -0
  24. package/dist/init.js +147 -0
  25. package/dist/init.js.map +1 -0
  26. package/dist/maturity.d.ts +65 -0
  27. package/dist/maturity.js +138 -0
  28. package/dist/maturity.js.map +1 -0
  29. package/dist/mcp.d.ts +27 -0
  30. package/dist/mcp.js +168 -0
  31. package/dist/mcp.js.map +1 -0
  32. package/dist/protocol.d.ts +38 -0
  33. package/dist/protocol.js +168 -0
  34. package/dist/protocol.js.map +1 -0
  35. package/dist/rank.d.ts +19 -0
  36. package/dist/rank.js +82 -0
  37. package/dist/rank.js.map +1 -0
  38. package/dist/runner.d.ts +22 -0
  39. package/dist/runner.js +99 -0
  40. package/dist/runner.js.map +1 -0
  41. package/package.json +66 -61
  42. package/LICENSE +0 -21
  43. package/dist/client.d.ts.map +0 -1
  44. package/dist/index.d.ts.map +0 -1
  45. package/dist/registry.d.ts +0 -8
  46. package/dist/registry.d.ts.map +0 -1
  47. package/dist/registry.js +0 -21
  48. package/dist/registry.js.map +0 -1
package/dist/client.js CHANGED
@@ -1,728 +1,220 @@
1
- import { fetchUrl, launchBrowser, retrieve, retrieveBatch, retrieveWithCache, checkChanges, InMemoryFetchCache, InMemorySnapshotStore, } from "@x12i/fetcher";
2
- import { ingestRepository } from "@x12i/ingest";
3
- import { createSerperProvider, loadSearchConfig, search as runSearch, } from "@x12i/search";
4
- import { crawl } from "@x12i/crawl";
5
- import { research } from "@x12i/research";
6
- import { extract, } from "@x12i/extract";
7
- import { InMemoryRunStore, } from "@x12i/runs";
8
- import { InMemoryFunctionCheckpointStore, analyzeWebChanges, analyzeWebSource, answerFromWeb, auditWebResearch, compareWebSubjects, createExecutorFromPolicy, evaluateCandidate, listBindings, listWebAIProfiles, loadBindingsFromPath, loadHostPolicyFromEnv, planQueries, previewWebAIProfile, rankEvidenceBlocks, validateBindingCompatibility, } from "@x12i/web-functions";
9
- import { createHash } from "node:crypto";
10
- import { createDefaultRegistry } from "./registry.js";
11
- function requestHash(value) {
12
- return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 24);
13
- }
14
- /**
15
- * Per-site fetch defaults so client-rendered pages actually populate before
16
- * we snapshot HTML. Caller-supplied options still win (spread after this).
17
- */
18
- function siteFetchDefaults(url) {
19
- try {
20
- const parsed = new URL(url);
21
- const host = parsed.hostname;
22
- const path = parsed.pathname;
23
- if ((host === "openrouter.ai" || host.endsWith(".openrouter.ai")) &&
24
- (path === "/rankings" || path.startsWith("/rankings/"))) {
25
- return {
26
- waitForSelector: "div.grid.grid-cols-12.items-center",
27
- settleMs: 500,
28
- };
29
- }
30
- if ((host === "linkedin.com" || host === "www.linkedin.com" || host.endsWith(".linkedin.com")) &&
31
- path.startsWith("/company/")) {
32
- return { waitForSelector: "h1", settleMs: 1000 };
33
- }
34
- if (host === "medium.com" ||
35
- host.endsWith(".medium.com") ||
36
- host === "towardsdatascience.com") {
37
- return { waitForSelector: "article h1, h1", settleMs: 800 };
38
- }
39
- if (host === "g2.com" || host === "www.g2.com" || host.endsWith(".g2.com")) {
40
- return { settleMs: 1500 };
41
- }
1
+ import { createRequire } from "node:module";
2
+ import { getReadme, searchPackages } from "@x12i/npm-core";
3
+ import { loadOrCreateIdentity, saveIdentity, defaultIdentityPath } from "./identity.js";
4
+ import { rankPackages } from "./rank.js";
5
+ import { canonicalRequest, randomNonce, reviewRating, sign, solveWork, usageFields, } from "./protocol.js";
6
+ const require = createRequire(import.meta.url);
7
+ export const SDK_VERSION = require("../package.json").version;
8
+ /** Catalog service. Override with X12I_SDK_URL. */
9
+ export const DEFAULT_SERVICE_URL = "https://x12i-sdk.x12i.com";
10
+ export class SdkError extends Error {
11
+ status;
12
+ code;
13
+ constructor(message, status = 0, code = "REQUEST_FAILED") {
14
+ super(message);
15
+ this.status = status;
16
+ this.code = code;
17
+ this.name = "SdkError";
42
18
  }
43
- catch {
44
- // ignore unparseable URLs
45
- }
46
- return {};
47
19
  }
48
- /**
49
- * Canonical programmatic surface: fetch/retrieve/research/crawl/extract/runs.
50
- * CLI, REST, and MCP are thin adapters around this client.
51
- */
52
- export class WebAgentClient {
53
- registry;
54
- defaultFetchOptions;
55
- reuseBrowser;
56
- searchProvider;
57
- browserPromise;
58
- fetchCache;
59
- snapshotStore;
60
- runStore;
61
- extractionAdapter;
62
- ownsCache;
63
- ownsSnapshots;
64
- ownsRuns;
65
- funcxExecutor;
66
- ownsFuncxExecutor;
67
- webFunctionBindings;
68
- hostPolicy;
69
- aiProfilesSource;
70
- functionCheckpoints = new InMemoryFunctionCheckpointStore();
71
- activeRunEventSink;
20
+ export class X12iClient {
21
+ baseUrl;
22
+ identityPath;
23
+ fetchImpl;
72
24
  constructor(options = {}) {
73
- this.registry = options.registry ?? createDefaultRegistry();
74
- this.defaultFetchOptions = options.fetchOptions ?? {};
75
- this.reuseBrowser = options.reuseBrowser ?? false;
76
- this.searchProvider = resolveSearchProvider(options.search);
77
- this.ownsCache = !options.fetchCache;
78
- this.ownsSnapshots = !options.snapshotStore;
79
- this.ownsRuns = !options.runStore;
80
- this.fetchCache = options.fetchCache ?? new InMemoryFetchCache();
81
- this.snapshotStore = options.snapshotStore ?? new InMemorySnapshotStore();
82
- this.runStore = options.runStore ?? new InMemoryRunStore();
83
- this.extractionAdapter = options.extractionAdapter;
84
- this.hostPolicy = loadHostPolicyFromEnv();
85
- if (options.aiProfiles?.source)
86
- this.hostPolicy.aiProfilesSource = options.aiProfiles.source;
87
- this.aiProfilesSource = options.aiProfiles?.source ?? this.hostPolicy.aiProfilesSource ?? "auto";
88
- this.ownsFuncxExecutor = options.ownsFuncxExecutor ?? !options.funcxExecutor;
89
- this.funcxExecutor = options.funcxExecutor ?? createExecutorFromPolicy(this.hostPolicy);
90
- this.webFunctionBindings = options.webFunctionBindings ?? [];
91
- void this.loadBindingsFromEnv();
25
+ this.baseUrl = (options.baseUrl ?? process.env.X12I_SDK_URL ?? DEFAULT_SERVICE_URL).replace(/\/$/, "");
26
+ this.identityPath = options.identityPath ?? defaultIdentityPath();
27
+ this.fetchImpl = options.fetch ?? fetch;
92
28
  }
93
- async loadBindingsFromEnv() {
94
- const path = process.env.WEB_AGENTIC_FUNCTION_BINDINGS;
95
- if (!path || this.webFunctionBindings.length)
96
- return;
29
+ async search(task) {
30
+ try {
31
+ const body = await this.send("POST", "/v1/recommend", JSON.stringify({ task }), { "content-type": "application/json" });
32
+ const parsed = body;
33
+ return { source: "service", ranked: parsed.ranked === true, task, results: parsed.results ?? [] };
34
+ }
35
+ catch (error) {
36
+ if (error instanceof SdkError && error.status >= 400 && error.status < 500)
37
+ throw error;
38
+ const found = await searchPackages(task, { scope: "@x12i/", size: 20, fetch: this.fetchImpl });
39
+ const results = rankPackages(task, found.map((pkg) => ({ name: pkg.name, description: pkg.description, keywords: pkg.keywords })));
40
+ return { source: "registry", ranked: results.some((item) => item.confidence === "high"), task, results };
41
+ }
42
+ }
43
+ catalog(cursor, limit = 50) {
44
+ const query = new URLSearchParams();
45
+ if (cursor)
46
+ query.set("cursor", cursor);
47
+ query.set("limit", String(limit));
48
+ return this.send("GET", `/v1/catalog?${query}`);
49
+ }
50
+ package(name) {
51
+ return this.readThrough(packagePath(name), name);
52
+ }
53
+ readme(name) {
54
+ return this.readThrough(`${packagePath(name)}/readme`, name);
55
+ }
56
+ reviews(name) {
57
+ return this.send("GET", `${packagePath(name)}/reviews`);
58
+ }
59
+ async report(input) {
60
+ const fields = usageFields(input);
61
+ const identity = await this.ensureEnrolled();
62
+ return this.signedPost("/v1/reports", { ...fields, sdkVersion: SDK_VERSION }, identity);
63
+ }
64
+ async review(input) {
65
+ const fields = usageFields(input);
66
+ const rating = reviewRating(input.rating);
67
+ const identity = await this.ensureEnrolled();
68
+ return this.signedPost("/v1/reviews", { ...fields, rating, sdkVersion: SDK_VERSION }, identity);
69
+ }
70
+ async status() {
71
+ let identity;
97
72
  try {
98
- this.webFunctionBindings = await loadBindingsFromPath(path);
73
+ identity = await loadOrCreateIdentity(this.identityPath);
99
74
  }
100
75
  catch {
101
- // Bindings file is optional; function routes report not configured / incompatible.
102
- }
103
- }
104
- webFunctionsDeps() {
105
- return {
106
- bindings: this.webFunctionBindings,
107
- executor: this.funcxExecutor,
108
- hostPolicy: this.hostPolicy,
109
- checkpoints: this.functionCheckpoints,
110
- onEvent: (type, data) => {
111
- this.activeRunEventSink?.(type, data);
112
- },
113
- resolvePacket: async (runId) => {
114
- for (const kind of ["research_packet", "packet", "grounded_answer"]) {
115
- const items = await this.runStore.listItems(runId, { kind, limit: 1 });
116
- const data = items.items[0]?.data;
117
- if (kind === "grounded_answer")
118
- continue;
119
- if (data)
120
- return data;
121
- }
122
- // Prefer packet item explicitly
123
- const packetItems = await this.runStore.listItems(runId, { limit: 20 });
124
- const packet = packetItems.items.find((i) => i.kind === "packet" || i.kind === "research_packet");
125
- return packet?.data;
126
- },
127
- resolveRunSource: async (runId, sourceId) => {
128
- const packet = await this.webFunctionsDeps().resolvePacket?.(runId);
129
- return packet?.sources.find((s) => s.sourceId === sourceId);
130
- },
131
- retrieveUrl: async (url, opts) => {
132
- const fetch = await this.retrieve(url, { strategy: "auto", signal: opts?.signal });
133
- return {
134
- sourceId: `src_${createHash("sha256").update(url).digest("hex").slice(0, 10)}`,
135
- title: fetch.title,
136
- fetch,
137
- };
138
- },
139
- research: (req, opts) => this.research(req, opts),
140
- checkChanges: async ({ url, againstSnapshotId, signal }) => {
141
- void signal;
142
- const previousSnapshot = await this.snapshotStore.get(againstSnapshotId);
143
- return this.checkChanges({ url, previousSnapshot });
144
- },
145
- };
146
- }
147
- buildResearchFunctionHooks(request) {
148
- if (!request.functions || this.funcxExecutor.mode === "disabled")
149
- return undefined;
150
- const fn = request.functions;
151
- const deps = this.webFunctionsDeps();
152
- return {
153
- planQueries: fn.queryPlanBindingId
154
- ? async (input) => planQueries(deps, {
155
- bindingId: fn.queryPlanBindingId,
156
- question: input.question,
157
- constraints: input.constraints,
158
- profileAlternativeId: input.profileAlternativeId ?? fn.profileAlternativeId,
159
- signal: input.signal,
160
- })
161
- : undefined,
162
- evaluateCandidate: fn.candidateEvaluateBindingId
163
- ? async (input) => evaluateCandidate(deps, {
164
- bindingId: fn.candidateEvaluateBindingId,
165
- candidate: input.candidate,
166
- profileAlternativeId: input.profileAlternativeId ?? fn.profileAlternativeId,
167
- signal: input.signal,
168
- })
169
- : undefined,
170
- analyzeSource: fn.sourceAnalyzeBindingId
171
- ? async (input) => {
172
- const result = await analyzeWebSource(deps, {
173
- bindingId: fn.sourceAnalyzeBindingId,
174
- profileAlternativeId: input.profileAlternativeId ?? fn.profileAlternativeId,
175
- source: { researchSource: input.source },
176
- includeOriginal: false,
177
- }, { signal: input.signal });
178
- return {
179
- analysis: result.analysis,
180
- warnings: result.warnings,
181
- errors: result.errors,
182
- };
183
- }
184
- : undefined,
185
- enrichRecord: fn.recordEnrichBindingId
186
- ? async (input) => {
187
- const result = await analyzeWebSource(deps, {
188
- bindingId: fn.recordEnrichBindingId,
189
- profileAlternativeId: input.profileAlternativeId ?? fn.profileAlternativeId,
190
- source: { researchSource: input.source },
191
- enrichBindingId: fn.recordEnrichBindingId,
192
- includeOriginal: true,
193
- }, { signal: input.signal });
194
- return {
195
- original: result.original,
196
- enriched: result.enriched,
197
- conflicts: result.conflicts,
198
- warnings: result.warnings,
199
- errors: result.errors,
200
- };
201
- }
202
- : undefined,
203
- rankEvidence: fn.evidenceRankBindingId
204
- ? async (input) => rankEvidenceBlocks(deps, {
205
- bindingId: fn.evidenceRankBindingId,
206
- blocks: input.blocks,
207
- question: input.question,
208
- profileAlternativeId: input.profileAlternativeId ?? fn.profileAlternativeId,
209
- signal: input.signal,
210
- })
211
- : undefined,
212
- };
213
- }
214
- use(parser) {
215
- this.registry.register(parser);
216
- return this;
217
- }
218
- /** Download a URL via Playwright (compatibility default: browser strategy). */
219
- async fetch(url, options = {}) {
220
- const browser = this.reuseBrowser ? await this.getBrowser() : undefined;
221
- return fetchUrl(url, {
222
- ...this.defaultFetchOptions,
223
- ...siteFetchDefaults(url),
224
- ...options,
225
- browser: browser ?? options.browser,
226
- });
227
- }
228
- /** Strategy-aware multi-format retrieval (default strategy: auto). */
229
- async retrieve(url, options = {}) {
230
- const browser = this.reuseBrowser ? await this.getBrowser() : undefined;
231
- return retrieve(url, {
232
- ...this.defaultFetchOptions,
233
- ...siteFetchDefaults(url),
234
- strategy: "auto",
235
- ...options,
236
- browser: browser ?? options.browser,
237
- });
238
- }
239
- async fetchBatch(request) {
240
- const browser = this.reuseBrowser ? await this.getBrowser() : undefined;
241
- return retrieveBatch({
242
- ...request,
243
- retrieveOptions: {
244
- ...this.defaultFetchOptions,
245
- strategy: "auto",
246
- ...request.retrieveOptions,
247
- browser: browser ?? request.retrieveOptions?.browser,
248
- },
249
- });
250
- }
251
- async retrieveCached(url, options = {}) {
252
- const browser = this.reuseBrowser ? await this.getBrowser() : undefined;
253
- return retrieveWithCache(url, {
254
- ...this.defaultFetchOptions,
255
- ...siteFetchDefaults(url),
256
- strategy: "auto",
257
- cache: this.fetchCache,
258
- ...options,
259
- browser: browser ?? options.browser,
260
- });
261
- }
262
- async classify(url, options = {}) {
263
- const fetched = await this.fetch(url, options);
264
- return this.registry.classify(fetched);
265
- }
266
- async parse(url, options = {}) {
267
- const fetched = await this.fetch(url, options);
268
- return this.registry.parse(fetched);
269
- }
270
- async parseAs(type, url, options = {}) {
271
- const fetched = await this.fetch(url, options);
272
- return this.registry.parseAs(type, fetched);
273
- }
274
- listParsers() {
275
- return this.registry.list();
276
- }
277
- ingest(rootUrl, options = {}) {
278
- return ingestRepository(rootUrl, options);
279
- }
280
- search(request, options = {}) {
281
- return runSearch(request, {
282
- provider: this.searchProvider ?? null,
283
- signal: options.signal,
284
- });
285
- }
286
- searchConfigured() {
287
- return this.searchProvider !== undefined;
288
- }
289
- async fetchSearchResults(searchResult, options = {}) {
290
- const targets = resolveFetchTargets(searchResult, options);
291
- const concurrency = Math.max(1, options.concurrency ?? 2);
292
- const continueOnError = options.continueOnError !== false;
293
- const pages = [];
294
- for (let i = 0; i < targets.length; i += concurrency) {
295
- if (options.signal?.aborted)
296
- break;
297
- const batch = targets.slice(i, i + concurrency);
298
- const settled = await Promise.all(batch.map(async (t) => {
299
- try {
300
- const fetched = await this.fetch(t.url, {
301
- ...options.fetchOptions,
302
- });
303
- return {
304
- position: t.position,
305
- url: t.url,
306
- title: fetched.title ?? t.title,
307
- fetch: fetched,
308
- };
309
- }
310
- catch (err) {
311
- const error = err instanceof Error ? err.message : String(err);
312
- if (!continueOnError)
313
- throw err;
314
- return { position: t.position, url: t.url, title: t.title, error };
315
- }
316
- }));
317
- pages.push(...settled);
76
+ identity = undefined;
318
77
  }
319
- return pages;
320
- }
321
- async searchAndFetch(request) {
322
- const { top, positions, urls, concurrency, continueOnError, fetchOptions, signal, ...searchRequest } = request;
323
- const searchResult = await this.search(searchRequest, { signal });
324
- const pages = await this.fetchSearchResults(searchResult, {
325
- positions: positions ?? (urls ? undefined : range(1, top ?? 3)),
326
- urls,
327
- concurrency,
328
- continueOnError,
329
- fetchOptions,
330
- signal,
331
- });
332
- return { search: searchResult, pages };
333
- }
334
- async research(request, options = {}) {
335
- await this.loadBindingsFromEnv();
336
- const browser = this.reuseBrowser ? await this.getBrowser() : undefined;
337
- const deps = {
338
- searchFn: (req, opts) => this.search(req, opts),
339
- retrieveFn: (url, ropts) => retrieve(url, {
340
- ...this.defaultFetchOptions,
341
- strategy: "auto",
342
- ...ropts,
343
- browser,
344
- }),
345
- registry: this.registry,
346
- signal: options.signal,
347
- functionHooks: this.buildResearchFunctionHooks(request),
348
- };
349
- return research(request, deps);
350
- }
351
- async startResearch(request, opts) {
352
- const run = await this.runStore.create({
353
- type: "research",
354
- request,
355
- requestHash: requestHash(request),
356
- idempotencyKey: opts?.idempotencyKey,
357
- });
358
- void this.runResearchJob(run.id, request);
359
- return run;
360
- }
361
- async runResearchJob(runId, request) {
362
- await this.runStore.update(runId, { status: "planning" });
363
- await this.runStore.update(runId, { status: "running", phase: "research" });
78
+ let service;
364
79
  try {
365
- const packet = await this.research(request);
366
- const status = packet.status === "completed"
367
- ? "completed"
368
- : packet.status === "failed"
369
- ? "failed"
370
- : packet.status === "cancelled"
371
- ? "cancelled"
372
- : packet.status === "partial"
373
- ? "partial"
374
- : "completed_with_errors";
375
- await this.runStore.update(runId, { status });
376
- await this.runStore.appendItem?.(runId, {
377
- kind: "packet",
378
- data: packet,
379
- });
80
+ service = await this.send("GET", "/v1/status");
380
81
  }
381
- catch (err) {
382
- await this.runStore.update(runId, {
383
- status: "failed",
384
- errors: [
385
- {
386
- code: "PARSER_FAILED",
387
- message: err instanceof Error ? err.message : String(err),
388
- phase: "export",
389
- retryable: false,
390
- },
391
- ],
392
- });
82
+ catch (error) {
83
+ service = { ok: false, error: error instanceof Error ? error.message : String(error) };
393
84
  }
394
- }
395
- async crawl(request, options = {}) {
396
- const browser = this.reuseBrowser ? await this.getBrowser() : undefined;
397
- return crawl(request, {
398
- retrieve: (url, ropts) => retrieve(url, {
399
- ...this.defaultFetchOptions,
400
- strategy: "auto",
401
- includeLinks: true,
402
- ...ropts,
403
- browser,
404
- }),
405
- signal: options.signal,
406
- });
407
- }
408
- async startCrawl(request, opts) {
409
- const run = await this.runStore.create({
410
- type: "crawl",
411
- request,
412
- requestHash: requestHash(request),
413
- idempotencyKey: opts?.idempotencyKey,
414
- });
415
- void this.runCrawlJob(run.id, request);
416
- return run;
417
- }
418
- async runCrawlJob(runId, request) {
419
- await this.runStore.update(runId, { status: "running", phase: "crawl" });
420
- try {
421
- const result = await this.crawl(request);
422
- await this.runStore.update(runId, {
423
- status: result.terminationReason === "cancelled" ? "cancelled" : "completed",
424
- usage: {
425
- pagesFetched: result.usage.pagesFetched,
426
- bytesFetched: result.usage.bytesFetched,
427
- },
428
- });
429
- await this.runStore.appendItem?.(runId, {
430
- kind: "crawl_result",
431
- data: result,
432
- });
433
- }
434
- catch (err) {
435
- await this.runStore.update(runId, {
436
- status: "failed",
437
- errors: [
438
- {
439
- code: "PARSER_FAILED",
440
- message: err instanceof Error ? err.message : String(err),
441
- phase: "crawl",
442
- retryable: false,
443
- },
444
- ],
445
- });
446
- }
447
- }
448
- async extract(request) {
449
- const browser = this.reuseBrowser ? await this.getBrowser() : undefined;
450
- return extract(request, {
451
- registry: this.registry,
452
- adapter: this.extractionAdapter,
453
- retrieveFn: (url, opts) => retrieve(url, {
454
- ...this.defaultFetchOptions,
455
- strategy: "auto",
456
- ...opts,
457
- browser,
458
- }),
459
- });
460
- }
461
- async checkChanges(request) {
462
- let current = request.current;
463
- if (!current && request.url) {
464
- current = await this.retrieve(request.url, { strategy: "auto" });
465
- if (current.canonicalUrl) {
466
- await this.snapshotStore.put({
467
- canonicalUrl: current.canonicalUrl,
468
- markdown: current.markdown,
469
- rawContentHash: current.rawContentHash,
470
- normalizedContentHash: current.normalizedContentHash,
471
- });
472
- }
473
- }
474
- let previousSnapshot = request.previousSnapshot;
475
- if (!previousSnapshot && !request.previous && current?.canonicalUrl) {
476
- previousSnapshot = await this.snapshotStore.latestForUrl(current.canonicalUrl);
477
- }
478
- return checkChanges({ ...request, current, previousSnapshot });
479
- }
480
- async analyze(request, opts) {
481
- await this.loadBindingsFromEnv();
482
- return analyzeWebSource(this.webFunctionsDeps(), request, opts);
483
- }
484
- async answer(request, opts) {
485
- await this.loadBindingsFromEnv();
486
- return answerFromWeb(this.webFunctionsDeps(), request, opts);
487
- }
488
- async compare(request, opts) {
489
- await this.loadBindingsFromEnv();
490
- return compareWebSubjects(this.webFunctionsDeps(), request, opts);
491
- }
492
- async analyzeChanges(request, opts) {
493
- await this.loadBindingsFromEnv();
494
- return analyzeWebChanges(this.webFunctionsDeps(), request, opts);
495
- }
496
- async auditResearch(request, opts) {
497
- await this.loadBindingsFromEnv();
498
- return auditWebResearch(this.webFunctionsDeps(), request, opts);
499
- }
500
- async listWebFunctions(options) {
501
- await this.loadBindingsFromEnv();
502
- const listed = listBindings(this.webFunctionBindings, { role: options?.role });
503
- const statuses = await Promise.all(listed.map(async (binding) => {
504
- const compat = await validateBindingCompatibility(binding, this.funcxExecutor, this.hostPolicy);
505
- return { binding, ...compat };
506
- }));
507
85
  return {
508
- mode: this.funcxExecutor.mode,
509
- bindings: statuses,
86
+ sdkVersion: SDK_VERSION,
87
+ serviceUrl: this.baseUrl,
88
+ identity: identity ? { fingerprint: identity.fingerprint, enrolled: identity.enrolledService === this.baseUrl } : null,
89
+ service,
510
90
  };
511
91
  }
512
- async listWebAIProfiles(options) {
513
- return listWebAIProfiles({
514
- role: options?.role,
515
- source: this.aiProfilesSource,
516
- catalogLane: "text",
517
- });
518
- }
519
- async previewWebAIProfile(request) {
520
- return previewWebAIProfile({
521
- profileChoice: request.profileChoice,
522
- bindingId: request.bindingId,
523
- source: this.aiProfilesSource,
524
- hostPolicy: this.hostPolicy,
525
- });
526
- }
527
- async startAnswer(request, opts) {
528
- const run = await this.runStore.create({
529
- type: "answer",
530
- request,
531
- requestHash: requestHash(request),
532
- idempotencyKey: opts?.idempotencyKey,
533
- });
534
- void this.runAnswerJob(run.id, request);
535
- return run;
536
- }
537
- async runAnswerJob(runId, request) {
538
- await this.runStore.update(runId, { status: "running", phase: "function" });
539
- this.activeRunEventSink = (type, data) => {
540
- void this.runStore.appendEvent(runId, { type: type, data });
541
- };
542
- await this.runStore.appendEvent(runId, { type: "function_started", data: { bindingId: request.synthesisBindingId } });
92
+ async ensureEnrolled() {
93
+ const identity = await loadOrCreateIdentity(this.identityPath);
94
+ if (identity.enrolledService === this.baseUrl)
95
+ return identity;
96
+ const challenge = await this.send("POST", "/v1/enrollment/challenges", JSON.stringify({ publicKey: identity.publicKey }), {
97
+ "content-type": "application/json",
98
+ });
99
+ const work = await solveWork(challenge.challenge, challenge.workBits ?? 0);
100
+ await this.signedPost("/v1/installs/enroll", { challenge: challenge.challenge, work }, identity);
101
+ identity.enrolledService = this.baseUrl;
102
+ await saveIdentity(this.identityPath, identity);
103
+ return identity;
104
+ }
105
+ async signedPost(path, payload, identity) {
106
+ const body = JSON.stringify(payload);
107
+ const timestamp = String(Date.now());
108
+ const nonce = randomNonce();
109
+ const message = await canonicalRequest("POST", path, timestamp, nonce, body);
110
+ const signature = await sign(identity.privateKey, message);
111
+ return this.send("POST", path, body, {
112
+ "content-type": "application/json",
113
+ "x-x12i-key": identity.publicKey,
114
+ "x-x12i-timestamp": timestamp,
115
+ "x-x12i-nonce": nonce,
116
+ "x-x12i-signature": signature,
117
+ });
118
+ }
119
+ async readThrough(path, name) {
120
+ let cachedBody;
543
121
  try {
544
- // Resume reuse: if a completed grounded_answer already exists, return it.
545
- const existing = await this.runStore.listItems(runId, { kind: "grounded_answer", limit: 1 });
546
- if (existing.items[0]?.data) {
547
- const result = existing.items[0].data;
548
- await this.runStore.update(runId, {
549
- status: result.status === "failed"
550
- ? "failed"
551
- : result.status === "completed_with_errors"
552
- ? "completed_with_errors"
553
- : "completed",
554
- });
555
- return;
556
- }
557
- const result = await this.answer(request);
558
- await this.runStore.appendItem?.(runId, { kind: "grounded_answer", data: result });
559
- await this.runStore.appendEvent(runId, {
560
- type: result.audit ? "audit_completed" : "function_completed",
561
- data: { status: result.status },
562
- });
563
- await this.runStore.update(runId, {
564
- status: result.status === "failed" ? "failed" : result.status === "completed_with_errors" ? "completed_with_errors" : "completed",
565
- });
122
+ cachedBody = await this.send("GET", path);
566
123
  }
567
- catch (err) {
568
- await this.runStore.update(runId, {
569
- status: "failed",
570
- errors: [
571
- {
572
- code: "FUNCTION_INVOCATION_FAILED",
573
- message: err instanceof Error ? err.message : String(err),
574
- phase: "function",
575
- retryable: false,
576
- },
577
- ],
578
- });
124
+ catch (error) {
125
+ if (error instanceof SdkError && error.status >= 400 && error.status < 500)
126
+ throw error;
127
+ return this.readmeFromRegistry(name);
579
128
  }
580
- finally {
581
- this.activeRunEventSink = undefined;
582
- }
583
- }
584
- async startCompare(request, opts) {
585
- const run = await this.runStore.create({
586
- type: "compare",
587
- request,
588
- requestHash: requestHash(request),
589
- idempotencyKey: opts?.idempotencyKey,
590
- });
591
- void this.runCompareJob(run.id, request);
592
- return run;
593
- }
594
- async runCompareJob(runId, request) {
595
- await this.runStore.update(runId, { status: "running", phase: "function" });
596
- this.activeRunEventSink = (type, data) => {
597
- void this.runStore.appendEvent(runId, { type: type, data });
598
- };
599
- await this.runStore.appendEvent(runId, {
600
- type: "function_started",
601
- data: { bindingId: request.comparisonBindingId },
602
- });
129
+ const cached = snapshotPackage(cachedBody);
130
+ if (cached.readmeStatus === "fresh")
131
+ return packageView(cachedBody, cached, false, "service");
603
132
  try {
604
- const existing = await this.runStore.listItems(runId, { kind: "web_comparison", limit: 1 });
605
- if (existing.items[0]?.data) {
606
- const result = existing.items[0].data;
607
- await this.runStore.update(runId, {
608
- status: result.status === "failed"
609
- ? "failed"
610
- : result.status === "completed_with_errors"
611
- ? "completed_with_errors"
612
- : "completed",
613
- });
614
- return;
615
- }
616
- const result = await this.compare(request);
617
- await this.runStore.appendItem?.(runId, { kind: "web_comparison", data: result });
618
- await this.runStore.appendEvent(runId, {
619
- type: result.audit ? "audit_completed" : "function_completed",
620
- data: { status: result.status },
621
- });
622
- await this.runStore.update(runId, {
623
- status: result.status === "failed"
624
- ? "failed"
625
- : result.status === "completed_with_errors"
626
- ? "completed_with_errors"
627
- : "completed",
628
- });
629
- }
630
- catch (err) {
631
- await this.runStore.update(runId, {
632
- status: "failed",
633
- errors: [
634
- {
635
- code: "FUNCTION_INVOCATION_FAILED",
636
- message: err instanceof Error ? err.message : String(err),
637
- phase: "function",
638
- retryable: false,
639
- },
640
- ],
641
- });
133
+ const freshBody = await this.send("GET", `${path}?update=1`);
134
+ const fresh = snapshotPackage(freshBody);
135
+ const updated = fresh.readme !== cached.readme || fresh.version !== cached.version || fresh.description !== cached.description;
136
+ return packageView(freshBody, cached, updated, "service");
642
137
  }
643
- finally {
644
- this.activeRunEventSink = undefined;
138
+ catch (error) {
139
+ return { ...packageView(cachedBody, cached, false, "service"), refreshError: error instanceof Error ? error.message : String(error) };
645
140
  }
646
141
  }
647
- getRun(runId) {
648
- return this.runStore.get(runId);
649
- }
650
- listRunItems(runId, options) {
651
- return this.runStore.listItems(runId, options);
652
- }
653
- cancelRun(runId) {
654
- return this.runStore.requestCancel(runId);
142
+ async readmeFromRegistry(name) {
143
+ const info = await getReadme(name, { fetch: this.fetchImpl });
144
+ const cached = { readme: "", readmeStatus: "missing", version: null, description: "", modifiedAt: null, refreshedAt: null };
145
+ return {
146
+ name: info.name,
147
+ readme: info.readme,
148
+ readmeStatus: "fresh",
149
+ version: info.version,
150
+ description: info.description,
151
+ modifiedAt: info.modifiedAt,
152
+ refreshedAt: null,
153
+ updated: Boolean(info.readme),
154
+ source: "registry",
155
+ cached,
156
+ detail: { ...info },
157
+ };
655
158
  }
656
- async resumeRun(runId) {
657
- const run = await this.runStore.get(runId);
658
- if (!run)
659
- throw new Error("RUN_NOT_FOUND");
660
- if (!["partial", "cancelled", "failed"].includes(run.status)) {
661
- throw new Error("RUN_NOT_RESUMABLE");
662
- }
663
- await this.runStore.update(runId, { status: "queued", cancellationRequested: false });
664
- if (run.type === "research") {
665
- void this.runResearchJob(runId, run.request);
666
- }
667
- else if (run.type === "crawl") {
668
- void this.runCrawlJob(runId, run.request);
669
- }
670
- else if (run.type === "answer") {
671
- void this.runAnswerJob(runId, run.request);
159
+ async send(method, path, body, headers = {}) {
160
+ let response;
161
+ try {
162
+ response = await this.fetchImpl(`${this.baseUrl}${path}`, { method, headers, body });
672
163
  }
673
- else if (run.type === "compare") {
674
- void this.runCompareJob(runId, run.request);
164
+ catch (error) {
165
+ throw new SdkError(error instanceof Error ? error.message : "Service unreachable");
675
166
  }
676
- return (await this.runStore.get(runId));
677
- }
678
- async getBrowser() {
679
- this.browserPromise ??= launchBrowser(this.defaultFetchOptions.executablePath);
680
- return this.browserPromise;
681
- }
682
- async close() {
683
- if (this.browserPromise) {
684
- const browser = await this.browserPromise;
685
- await browser.close();
686
- this.browserPromise = undefined;
167
+ const text = await response.text();
168
+ const parsed = text ? safeJson(text) : {};
169
+ if (!response.ok) {
170
+ const record = parsed;
171
+ throw new SdkError(record.error?.message ?? `Service returned ${response.status}`, response.status, record.error?.code ?? "HTTP");
687
172
  }
688
- void this.ownsCache;
689
- void this.ownsSnapshots;
690
- void this.ownsRuns;
173
+ return parsed;
691
174
  }
692
175
  }
693
- function resolveSearchProvider(search) {
694
- if (search === null)
695
- return undefined;
696
- if (!search) {
697
- return createSerperProvider(loadSearchConfig());
698
- }
699
- if ("search" in search && typeof search.search === "function") {
700
- return search;
176
+ function snapshotPackage(body) {
177
+ const status = body.readmeStatus;
178
+ return {
179
+ readme: typeof body.readme === "string" ? body.readme : "",
180
+ readmeStatus: status === "fresh" || status === "stale" || status === "missing" ? status : "missing",
181
+ version: typeof body.version === "string" ? body.version : null,
182
+ description: typeof body.description === "string" ? body.description : "",
183
+ modifiedAt: typeof body.modifiedAt === "string" ? body.modifiedAt : null,
184
+ refreshedAt: typeof body.refreshedAt === "string" ? body.refreshedAt : null,
185
+ };
186
+ }
187
+ function packageView(body, cached, updated, source) {
188
+ const current = updated || cached.readmeStatus !== "fresh" ? snapshotPackage(body) : cached;
189
+ const name = typeof body.name === "string" ? body.name : "";
190
+ return {
191
+ name,
192
+ readme: current.readme,
193
+ readmeStatus: current.readmeStatus,
194
+ version: current.version,
195
+ description: current.description,
196
+ modifiedAt: current.modifiedAt,
197
+ refreshedAt: current.refreshedAt,
198
+ updated,
199
+ source,
200
+ cached,
201
+ detail: body,
202
+ };
203
+ }
204
+ function packagePath(name) {
205
+ if (name.startsWith("@")) {
206
+ const slash = name.indexOf("/");
207
+ if (slash > 1)
208
+ return `/v1/packages/${encodeURIComponent(name.slice(0, slash))}/${encodeURIComponent(name.slice(slash + 1))}`;
701
209
  }
702
- return createSerperProvider(search);
210
+ return `/v1/packages/${encodeURIComponent(name)}`;
703
211
  }
704
- function resolveFetchTargets(searchResult, options) {
705
- if (options.urls?.length) {
706
- return options.urls.map((url) => {
707
- const match = searchResult.organic.find((o) => o.url === url);
708
- return { url, position: match?.position, title: match?.title };
709
- });
212
+ function safeJson(text) {
213
+ try {
214
+ return JSON.parse(text);
710
215
  }
711
- const positions = options.positions?.length
712
- ? options.positions
713
- : searchResult.organic.map((o) => o.position);
714
- const out = [];
715
- for (const pos of positions) {
716
- const hit = searchResult.organic.find((o) => o.position === pos);
717
- if (hit)
718
- out.push({ url: hit.url, position: hit.position, title: hit.title });
216
+ catch {
217
+ return { text };
719
218
  }
720
- return out;
721
- }
722
- function range(start, endInclusive) {
723
- const out = [];
724
- for (let i = start; i <= endInclusive; i++)
725
- out.push(i);
726
- return out;
727
219
  }
728
220
  //# sourceMappingURL=client.js.map