@staticn0va/wigolo 0.1.1 → 0.1.2

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 (67) hide show
  1. package/README.md +142 -345
  2. package/dist/agent/pipeline.d.ts.map +1 -1
  3. package/dist/agent/pipeline.js +35 -5
  4. package/dist/agent/pipeline.js.map +1 -1
  5. package/dist/cache/store.d.ts +1 -0
  6. package/dist/cache/store.d.ts.map +1 -1
  7. package/dist/cache/store.js +4 -2
  8. package/dist/cache/store.js.map +1 -1
  9. package/dist/cli/doctor.d.ts.map +1 -1
  10. package/dist/cli/doctor.js +43 -17
  11. package/dist/cli/doctor.js.map +1 -1
  12. package/dist/cli/shutdown.d.ts +2 -0
  13. package/dist/cli/shutdown.d.ts.map +1 -0
  14. package/dist/cli/shutdown.js +26 -0
  15. package/dist/cli/shutdown.js.map +1 -0
  16. package/dist/extraction/v1/local-llm.d.ts.map +1 -1
  17. package/dist/extraction/v1/local-llm.js +13 -37
  18. package/dist/extraction/v1/local-llm.js.map +1 -1
  19. package/dist/fetch/error-describe.d.ts +7 -0
  20. package/dist/fetch/error-describe.d.ts.map +1 -0
  21. package/dist/fetch/error-describe.js +37 -0
  22. package/dist/fetch/error-describe.js.map +1 -0
  23. package/dist/fetch/router.d.ts.map +1 -1
  24. package/dist/fetch/router.js +4 -2
  25. package/dist/fetch/router.js.map +1 -1
  26. package/dist/index.js +17 -12
  27. package/dist/index.js.map +1 -1
  28. package/dist/integrations/cloud/llm/model-select.d.ts +5 -0
  29. package/dist/integrations/cloud/llm/model-select.d.ts.map +1 -0
  30. package/dist/integrations/cloud/llm/model-select.js +32 -0
  31. package/dist/integrations/cloud/llm/model-select.js.map +1 -0
  32. package/dist/integrations/cloud/llm/run.d.ts +27 -0
  33. package/dist/integrations/cloud/llm/run.d.ts.map +1 -0
  34. package/dist/integrations/cloud/llm/run.js +99 -0
  35. package/dist/integrations/cloud/llm/run.js.map +1 -0
  36. package/dist/integrations/cloud/llm/text-adapters.d.ts +19 -0
  37. package/dist/integrations/cloud/llm/text-adapters.d.ts.map +1 -0
  38. package/dist/integrations/cloud/llm/text-adapters.js +103 -0
  39. package/dist/integrations/cloud/llm/text-adapters.js.map +1 -0
  40. package/dist/providers/rerank-provider.d.ts +1 -0
  41. package/dist/providers/rerank-provider.d.ts.map +1 -1
  42. package/dist/providers/rerank-provider.js +13 -0
  43. package/dist/providers/rerank-provider.js.map +1 -1
  44. package/dist/research/brief.d.ts +1 -0
  45. package/dist/research/brief.d.ts.map +1 -1
  46. package/dist/research/brief.js +8 -4
  47. package/dist/research/brief.js.map +1 -1
  48. package/dist/research/pipeline.js +1 -1
  49. package/dist/research/pipeline.js.map +1 -1
  50. package/dist/research/synthesis-local.d.ts +3 -0
  51. package/dist/research/synthesis-local.d.ts.map +1 -1
  52. package/dist/research/synthesis-local.js +18 -29
  53. package/dist/research/synthesis-local.js.map +1 -1
  54. package/dist/search/filters.d.ts.map +1 -1
  55. package/dist/search/filters.js +11 -1
  56. package/dist/search/filters.js.map +1 -1
  57. package/dist/search/reranker/transformers-rerank-provider.d.ts +1 -0
  58. package/dist/search/reranker/transformers-rerank-provider.d.ts.map +1 -1
  59. package/dist/search/reranker/transformers-rerank-provider.js +16 -0
  60. package/dist/search/reranker/transformers-rerank-provider.js.map +1 -1
  61. package/dist/tools/cache.d.ts.map +1 -1
  62. package/dist/tools/cache.js +4 -2
  63. package/dist/tools/cache.js.map +1 -1
  64. package/dist/tools/fetch.d.ts.map +1 -1
  65. package/dist/tools/fetch.js +17 -4
  66. package/dist/tools/fetch.js.map +1 -1
  67. package/package.json +1 -1
@@ -0,0 +1,7 @@
1
+ interface DescribedError {
2
+ reason: string;
3
+ hint?: string;
4
+ }
5
+ export declare function describeFetchError(err: unknown): DescribedError;
6
+ export {};
7
+ //# sourceMappingURL=error-describe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-describe.d.ts","sourceRoot":"","sources":["../../src/fetch/error-describe.ts"],"names":[],"mappings":"AAKA,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAwBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAa/D"}
@@ -0,0 +1,37 @@
1
+ const CODE_DESCRIPTIONS = {
2
+ ENOTFOUND: { reason: "DNS resolution failed (ENOTFOUND)", hint: "Check the domain name; the host could not be resolved" },
3
+ ECONNREFUSED: { reason: "Connection refused (ECONNREFUSED)", hint: "Target host rejected the connection \u2014 server may be down or port closed" },
4
+ ECONNRESET: { reason: "Connection reset (ECONNRESET)", hint: "Remote peer closed the connection mid-request \u2014 retry may succeed" },
5
+ ETIMEDOUT: { reason: "Connection timed out (ETIMEDOUT)", hint: "Increase timeoutMs or check network reachability" },
6
+ EAI_AGAIN: { reason: "DNS lookup temporarily failed (EAI_AGAIN)", hint: "DNS resolver issue \u2014 retry shortly" },
7
+ EHOSTUNREACH: { reason: "Host unreachable (EHOSTUNREACH)" },
8
+ ENETUNREACH: { reason: "Network unreachable (ENETUNREACH)" },
9
+ CERT_HAS_EXPIRED: { reason: "TLS certificate expired" },
10
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: { reason: "TLS chain verification failed" },
11
+ SELF_SIGNED_CERT_IN_CHAIN: { reason: "Self-signed TLS certificate in chain" }
12
+ };
13
+ function extractCode(err, depth = 0) {
14
+ if (!err || depth > 5) return null;
15
+ if (typeof err !== "object") return null;
16
+ const e = err;
17
+ if (typeof e.code === "string" && e.code) return e.code;
18
+ if (e.cause) return extractCode(e.cause, depth + 1);
19
+ return null;
20
+ }
21
+ function describeFetchError(err) {
22
+ const code = extractCode(err);
23
+ if (code && CODE_DESCRIPTIONS[code]) return CODE_DESCRIPTIONS[code];
24
+ if (err instanceof Error) {
25
+ if (err.name === "TimeoutError" || err.name === "AbortError") {
26
+ return { reason: "Request timed out", hint: "Increase timeoutMs or check network reachability" };
27
+ }
28
+ const msg = err.message;
29
+ if (code) return { reason: `${msg} (${code})` };
30
+ return { reason: msg || "fetch failed" };
31
+ }
32
+ return { reason: String(err) };
33
+ }
34
+ export {
35
+ describeFetchError
36
+ };
37
+ //# sourceMappingURL=error-describe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fetch/error-describe.ts"],"sourcesContent":["// Translate a thrown fetch error into a stable, specific reason string.\n// Node's undici surfaces \"fetch failed\" on the outer TypeError while the\n// actual code (ENOTFOUND/ECONNREFUSED/etc.) hides on err.cause. Drill the\n// chain so callers see what actually broke instead of a generic phrase.\n\ninterface DescribedError {\n reason: string;\n hint?: string;\n}\n\nconst CODE_DESCRIPTIONS: Record<string, { reason: string; hint?: string }> = {\n ENOTFOUND: { reason: 'DNS resolution failed (ENOTFOUND)', hint: 'Check the domain name; the host could not be resolved' },\n ECONNREFUSED: { reason: 'Connection refused (ECONNREFUSED)', hint: 'Target host rejected the connection — server may be down or port closed' },\n ECONNRESET: { reason: 'Connection reset (ECONNRESET)', hint: 'Remote peer closed the connection mid-request — retry may succeed' },\n ETIMEDOUT: { reason: 'Connection timed out (ETIMEDOUT)', hint: 'Increase timeoutMs or check network reachability' },\n EAI_AGAIN: { reason: 'DNS lookup temporarily failed (EAI_AGAIN)', hint: 'DNS resolver issue — retry shortly' },\n EHOSTUNREACH: { reason: 'Host unreachable (EHOSTUNREACH)' },\n ENETUNREACH: { reason: 'Network unreachable (ENETUNREACH)' },\n CERT_HAS_EXPIRED: { reason: 'TLS certificate expired' },\n UNABLE_TO_VERIFY_LEAF_SIGNATURE: { reason: 'TLS chain verification failed' },\n SELF_SIGNED_CERT_IN_CHAIN: { reason: 'Self-signed TLS certificate in chain' },\n};\n\nfunction extractCode(err: unknown, depth = 0): string | null {\n if (!err || depth > 5) return null;\n if (typeof err !== 'object') return null;\n const e = err as { code?: unknown; cause?: unknown };\n if (typeof e.code === 'string' && e.code) return e.code;\n if (e.cause) return extractCode(e.cause, depth + 1);\n return null;\n}\n\nexport function describeFetchError(err: unknown): DescribedError {\n const code = extractCode(err);\n if (code && CODE_DESCRIPTIONS[code]) return CODE_DESCRIPTIONS[code];\n\n if (err instanceof Error) {\n if (err.name === 'TimeoutError' || err.name === 'AbortError') {\n return { reason: 'Request timed out', hint: 'Increase timeoutMs or check network reachability' };\n }\n const msg = err.message;\n if (code) return { reason: `${msg} (${code})` };\n return { reason: msg || 'fetch failed' };\n }\n return { reason: String(err) };\n}\n"],"mappings":"AAUA,MAAM,oBAAuE;AAAA,EAC3E,WAAW,EAAE,QAAQ,qCAAqC,MAAM,wDAAwD;AAAA,EACxH,cAAc,EAAE,QAAQ,qCAAqC,MAAM,+EAA0E;AAAA,EAC7I,YAAY,EAAE,QAAQ,iCAAiC,MAAM,yEAAoE;AAAA,EACjI,WAAW,EAAE,QAAQ,oCAAoC,MAAM,mDAAmD;AAAA,EAClH,WAAW,EAAE,QAAQ,6CAA6C,MAAM,0CAAqC;AAAA,EAC7G,cAAc,EAAE,QAAQ,kCAAkC;AAAA,EAC1D,aAAa,EAAE,QAAQ,oCAAoC;AAAA,EAC3D,kBAAkB,EAAE,QAAQ,0BAA0B;AAAA,EACtD,iCAAiC,EAAE,QAAQ,gCAAgC;AAAA,EAC3E,2BAA2B,EAAE,QAAQ,uCAAuC;AAC9E;AAEA,SAAS,YAAY,KAAc,QAAQ,GAAkB;AAC3D,MAAI,CAAC,OAAO,QAAQ,EAAG,QAAO;AAC9B,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAM,QAAO,EAAE;AACnD,MAAI,EAAE,MAAO,QAAO,YAAY,EAAE,OAAO,QAAQ,CAAC;AAClD,SAAO;AACT;AAEO,SAAS,mBAAmB,KAA8B;AAC/D,QAAM,OAAO,YAAY,GAAG;AAC5B,MAAI,QAAQ,kBAAkB,IAAI,EAAG,QAAO,kBAAkB,IAAI;AAElE,MAAI,eAAe,OAAO;AACxB,QAAI,IAAI,SAAS,kBAAkB,IAAI,SAAS,cAAc;AAC5D,aAAO,EAAE,QAAQ,qBAAqB,MAAM,mDAAmD;AAAA,IACjG;AACA,UAAM,MAAM,IAAI;AAChB,QAAI,KAAM,QAAO,EAAE,QAAQ,GAAG,GAAG,KAAK,IAAI,IAAI;AAC9C,WAAO,EAAE,QAAQ,OAAO,eAAe;AAAA,EACzC;AACA,SAAO,EAAE,QAAQ,OAAO,GAAG,EAAE;AAC/B;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/fetch/router.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEnF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CACH,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE;YACnB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,eAAe,CAAC,EAAE,MAAM,CAAC;SAC1B,CAAC;KACH,GACA,OAAO,CAAC;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,CACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAChK,OAAO,CAAC,cAAc,CAAC,CAAC;CAC5B;AAED,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAC/D,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAC7B,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,oBAAoB,CAAC;IACnC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED,UAAU,WAAW;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAuB;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;gBAE1C,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB;gBACzD,OAAO,EAAE,kBAAkB;IAiCvC,OAAO,CAAC,sBAAsB;IAUxB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,GAAG,UAAU,CAAC;IAC3G,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;IAyJ/E,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIvD,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,gBAAgB;CAczB"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/fetch/router.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import { createLogger } from "../logger.js";
3
3
  import { contentAppearsEmpty } from "./content-check.js";
4
4
  import { getAuthOptions } from "./auth.js";
5
5
  import { fetchWithPlaywright, shouldEscalate } from "./playwright-tier.js";
6
+ import { describeFetchError } from "./error-describe.js";
6
7
  class SmartRouter {
7
8
  domainMap = /* @__PURE__ */ new Map();
8
9
  httpClient;
@@ -81,11 +82,12 @@ class SmartRouter {
81
82
  hint
82
83
  };
83
84
  }
85
+ const described = describeFetchError(err);
84
86
  return {
85
87
  error: "playwright_fetch_failed",
86
- error_reason: err instanceof Error ? err.message : String(err),
88
+ error_reason: described.reason,
87
89
  stage: "fetch",
88
- hint: "Stealth fetch failed; check network or retry"
90
+ hint: described.hint ?? "Stealth fetch failed; check network or retry"
89
91
  };
90
92
  }
91
93
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fetch/router.ts"],"sourcesContent":["import { getConfig } from '../config.js';\nimport { createLogger } from '../logger.js';\nimport { contentAppearsEmpty } from './content-check.js';\nimport { getAuthOptions } from './auth.js';\nimport { fetchWithPlaywright, shouldEscalate } from './playwright-tier.js';\nimport type { RawFetchResult, BrowserAction, Mode, StageError } from '../types.js';\n\nexport interface RouterFetchOptions {\n renderJs?: 'auto' | 'always' | 'never';\n useAuth?: boolean;\n headers?: Record<string, string>;\n screenshot?: boolean;\n actions?: BrowserAction[];\n force_refresh?: boolean;\n mode?: Mode;\n /**\n * Conditional-GET headers. When set, the HTTP path sends them with the\n * request and a 304 response is returned as RawFetchResult with\n * statusCode=304 + html=''. Routes that always escalate to Playwright\n * (renderJs=always, useAuth, actions) ignore these headers.\n */\n conditionalHeaders?: {\n ifNoneMatch?: string;\n ifModifiedSince?: string;\n };\n}\n\nexport interface HttpClient {\n fetch(\n url: string,\n options?: {\n headers?: Record<string, string>;\n timeoutMs?: number;\n conditionalHeaders?: {\n ifNoneMatch?: string;\n ifModifiedSince?: string;\n };\n },\n ): Promise<{\n url: string;\n finalUrl: string;\n html: string;\n contentType: string;\n statusCode: number;\n headers: Record<string, string>;\n rawBuffer?: Buffer;\n }>;\n}\n\nexport interface BrowserPoolInterface {\n fetchWithBrowser(\n url: string,\n options?: { headers?: Record<string, string>; storageStatePath?: string; userDataDir?: string; screenshot?: boolean; actions?: BrowserAction[]; cdpUrl?: string },\n ): Promise<RawFetchResult>;\n}\n\nexport type HttpFetcher = (\n url: string,\n options?: { headers?: Record<string, string>; timeoutMs?: number },\n) => Promise<{ url: string; html: string; text: string }>;\n\nexport type PlaywrightFetcher = (\n url: string,\n options?: { timeoutMs?: number },\n) => Promise<{ html: string; text: string }>;\n\nexport interface SmartRouterOptions {\n httpClient?: HttpClient;\n browserPool?: BrowserPoolInterface;\n httpFetcher?: HttpFetcher;\n playwrightFetcher?: PlaywrightFetcher;\n}\n\ninterface DomainStats {\n failureCount: number;\n preferPlaywright: boolean;\n}\n\nexport class SmartRouter {\n private readonly domainMap = new Map<string, DomainStats>();\n private readonly httpClient?: HttpClient;\n private readonly browserPool?: BrowserPoolInterface;\n private readonly httpFetcher: HttpFetcher;\n private readonly playwrightFetcher: PlaywrightFetcher;\n\n constructor(httpClient: HttpClient, browserPool: BrowserPoolInterface);\n constructor(options: SmartRouterOptions);\n constructor(\n httpClientOrOptions: HttpClient | SmartRouterOptions,\n browserPool?: BrowserPoolInterface,\n ) {\n if (browserPool !== undefined) {\n this.httpClient = httpClientOrOptions as HttpClient;\n this.browserPool = browserPool;\n } else if (\n httpClientOrOptions &&\n typeof httpClientOrOptions === 'object' &&\n ('httpClient' in httpClientOrOptions ||\n 'browserPool' in httpClientOrOptions ||\n 'httpFetcher' in httpClientOrOptions ||\n 'playwrightFetcher' in httpClientOrOptions)\n ) {\n const opts = httpClientOrOptions as SmartRouterOptions;\n if (!opts.httpFetcher && !opts.httpClient) {\n throw new Error('SmartRouter: must provide either httpFetcher or httpClient in options');\n }\n this.httpClient = opts.httpClient;\n this.browserPool = opts.browserPool;\n this.httpFetcher = opts.httpFetcher ?? this.makeDefaultHttpFetcher();\n this.playwrightFetcher = opts.playwrightFetcher ?? fetchWithPlaywright;\n return;\n } else {\n // Backwards-compat: single HttpClient positional (unusual but safe)\n this.httpClient = httpClientOrOptions as HttpClient;\n }\n this.httpFetcher = this.makeDefaultHttpFetcher();\n this.playwrightFetcher = fetchWithPlaywright;\n }\n\n private makeDefaultHttpFetcher(): HttpFetcher {\n return async (url, opts) => {\n if (!this.httpClient) {\n throw new Error('SmartRouter: httpClient not configured');\n }\n const r = await this.httpClient.fetch(url, opts);\n return { url: r.url, html: r.html, text: '' };\n };\n }\n\n async fetch(url: string, options: RouterFetchOptions & { mode: 'stealth' }): Promise<RawFetchResult | StageError>;\n async fetch(url: string, options?: RouterFetchOptions): Promise<RawFetchResult>;\n async fetch(\n url: string,\n options: RouterFetchOptions = {},\n ): Promise<RawFetchResult | StageError> {\n const { renderJs = 'auto', useAuth = false, headers, screenshot, actions, mode, conditionalHeaders } = options;\n const config = getConfig();\n const logger = createLogger('fetch');\n const threshold = config.browserFallbackThreshold;\n const domain = new URL(url).hostname;\n\n // Stealth mode: static fetch first, escalate to Playwright when content is thin.\n if (mode === 'stealth') {\n logger.debug('routing to stealth (static then escalate)', { url });\n const staticResult = await this.httpFetcher(url, { headers });\n this.ensureStats(domain);\n if (!shouldEscalate(staticResult.text)) {\n return {\n url: staticResult.url,\n finalUrl: staticResult.url,\n html: staticResult.html,\n contentType: 'text/html',\n statusCode: 200,\n method: 'http',\n headers: {},\n };\n }\n try {\n const pw = await this.playwrightFetcher(url);\n return {\n url: staticResult.url,\n finalUrl: staticResult.url,\n html: pw.html,\n contentType: 'text/html',\n statusCode: 200,\n method: 'playwright',\n headers: {},\n escalated: true,\n };\n } catch (err) {\n if (err instanceof Error && err.message === 'playwright_not_installed') {\n const hint = (err as Error & { hint?: string }).hint ?? 'npx playwright install chromium';\n return {\n error: 'playwright_not_installed',\n error_reason: 'Stealth mode requested but Playwright chromium is not installed',\n stage: 'fetch',\n hint,\n };\n }\n return {\n error: 'playwright_fetch_failed',\n error_reason: err instanceof Error ? err.message : String(err),\n stage: 'fetch',\n hint: 'Stealth fetch failed; check network or retry',\n };\n }\n }\n\n // Cache mode: HTTP-only with tight timeout, never escalates to a browser.\n if (mode === 'cache') {\n if (actions && actions.length > 0) {\n logger.warn('mode=cache ignores browser actions; switch to default/stealth to execute them', {\n url,\n actionCount: actions.length,\n });\n }\n logger.debug('routing to http (cache)', { url });\n if (!this.httpClient) throw new Error('SmartRouter: httpClient not configured');\n const result = await this.httpClient.fetch(url, {\n headers,\n timeoutMs: config.fastTimeoutMs,\n conditionalHeaders,\n });\n this.ensureStats(domain);\n const raw = this.toRawFetchResult(result);\n // Don't probe content of a 304 — body is empty by spec, not a SPA shell.\n raw.jsRequired = result.statusCode === 304 ? false : contentAppearsEmpty(result.html);\n return raw;\n }\n\n // Actions always force Playwright --- actions need a live browser page\n if (actions && actions.length > 0) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n const authOptions = useAuth ? (await getAuthOptions() ?? {}) : {};\n logger.debug('routing to playwright', { url, reason: 'actions present' });\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot, actions, ...authOptions });\n }\n\n // Always Playwright for auth or explicit override\n if (renderJs === 'always' || useAuth) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n const authOptions = useAuth ? (await getAuthOptions() ?? {}) : {};\n logger.debug('routing to playwright', { url, reason: useAuth ? 'auth' : 'render_js=always' });\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot, ...authOptions });\n }\n\n // HTTP only, no fallback\n if (renderJs === 'never') {\n if (!this.httpClient) throw new Error('SmartRouter: httpClient not configured');\n logger.debug('routing to http (never)', { url });\n const result = await this.httpClient.fetch(url, { headers, conditionalHeaders });\n this.ensureStats(domain);\n return this.toRawFetchResult(result);\n }\n\n // auto: check if domain is already marked for Playwright\n const stats = this.ensureStats(domain);\n\n if (stats.preferPlaywright) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n logger.debug('routing to playwright (domain marked)', { url, domain });\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot });\n }\n\n // Try HTTP first\n try {\n if (!this.httpClient) throw new Error('SmartRouter: httpClient not configured');\n const result = await this.httpClient.fetch(url, { headers, conditionalHeaders });\n\n // 304 = unchanged: pass through; never escalate to a browser.\n if (result.statusCode === 304) {\n return this.toRawFetchResult(result);\n }\n\n // Check for SPA shell / empty content\n if (contentAppearsEmpty(result.html)) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n logger.info('SPA shell detected, marking domain for playwright', { url, domain });\n stats.preferPlaywright = true;\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot });\n }\n\n return this.toRawFetchResult(result);\n } catch (err) {\n stats.failureCount++;\n logger.warn('http fetch failed', {\n url,\n domain,\n failureCount: stats.failureCount,\n error: err instanceof Error ? err.message : String(err),\n });\n\n if (stats.failureCount >= threshold) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n logger.info('failure threshold reached, marking domain for playwright', { url, domain, threshold });\n stats.preferPlaywright = true;\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot });\n }\n\n throw err;\n }\n }\n\n getDomainStats(domain: string): DomainStats | undefined {\n return this.domainMap.get(domain);\n }\n\n private ensureStats(domain: string): DomainStats {\n let stats = this.domainMap.get(domain);\n if (!stats) {\n stats = { failureCount: 0, preferPlaywright: false };\n this.domainMap.set(domain, stats);\n }\n return stats;\n }\n\n private toRawFetchResult(\n result: Awaited<ReturnType<HttpClient['fetch']>>,\n ): RawFetchResult {\n return {\n url: result.url,\n finalUrl: result.finalUrl,\n html: result.html,\n contentType: result.contentType,\n statusCode: result.statusCode,\n method: 'http',\n headers: result.headers,\n rawBuffer: result.rawBuffer,\n };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/fetch/router.ts"],"sourcesContent":["import { getConfig } from '../config.js';\nimport { createLogger } from '../logger.js';\nimport { contentAppearsEmpty } from './content-check.js';\nimport { getAuthOptions } from './auth.js';\nimport { fetchWithPlaywright, shouldEscalate } from './playwright-tier.js';\nimport { describeFetchError } from './error-describe.js';\nimport type { RawFetchResult, BrowserAction, Mode, StageError } from '../types.js';\n\nexport interface RouterFetchOptions {\n renderJs?: 'auto' | 'always' | 'never';\n useAuth?: boolean;\n headers?: Record<string, string>;\n screenshot?: boolean;\n actions?: BrowserAction[];\n force_refresh?: boolean;\n mode?: Mode;\n /**\n * Conditional-GET headers. When set, the HTTP path sends them with the\n * request and a 304 response is returned as RawFetchResult with\n * statusCode=304 + html=''. Routes that always escalate to Playwright\n * (renderJs=always, useAuth, actions) ignore these headers.\n */\n conditionalHeaders?: {\n ifNoneMatch?: string;\n ifModifiedSince?: string;\n };\n}\n\nexport interface HttpClient {\n fetch(\n url: string,\n options?: {\n headers?: Record<string, string>;\n timeoutMs?: number;\n conditionalHeaders?: {\n ifNoneMatch?: string;\n ifModifiedSince?: string;\n };\n },\n ): Promise<{\n url: string;\n finalUrl: string;\n html: string;\n contentType: string;\n statusCode: number;\n headers: Record<string, string>;\n rawBuffer?: Buffer;\n }>;\n}\n\nexport interface BrowserPoolInterface {\n fetchWithBrowser(\n url: string,\n options?: { headers?: Record<string, string>; storageStatePath?: string; userDataDir?: string; screenshot?: boolean; actions?: BrowserAction[]; cdpUrl?: string },\n ): Promise<RawFetchResult>;\n}\n\nexport type HttpFetcher = (\n url: string,\n options?: { headers?: Record<string, string>; timeoutMs?: number },\n) => Promise<{ url: string; html: string; text: string }>;\n\nexport type PlaywrightFetcher = (\n url: string,\n options?: { timeoutMs?: number },\n) => Promise<{ html: string; text: string }>;\n\nexport interface SmartRouterOptions {\n httpClient?: HttpClient;\n browserPool?: BrowserPoolInterface;\n httpFetcher?: HttpFetcher;\n playwrightFetcher?: PlaywrightFetcher;\n}\n\ninterface DomainStats {\n failureCount: number;\n preferPlaywright: boolean;\n}\n\nexport class SmartRouter {\n private readonly domainMap = new Map<string, DomainStats>();\n private readonly httpClient?: HttpClient;\n private readonly browserPool?: BrowserPoolInterface;\n private readonly httpFetcher: HttpFetcher;\n private readonly playwrightFetcher: PlaywrightFetcher;\n\n constructor(httpClient: HttpClient, browserPool: BrowserPoolInterface);\n constructor(options: SmartRouterOptions);\n constructor(\n httpClientOrOptions: HttpClient | SmartRouterOptions,\n browserPool?: BrowserPoolInterface,\n ) {\n if (browserPool !== undefined) {\n this.httpClient = httpClientOrOptions as HttpClient;\n this.browserPool = browserPool;\n } else if (\n httpClientOrOptions &&\n typeof httpClientOrOptions === 'object' &&\n ('httpClient' in httpClientOrOptions ||\n 'browserPool' in httpClientOrOptions ||\n 'httpFetcher' in httpClientOrOptions ||\n 'playwrightFetcher' in httpClientOrOptions)\n ) {\n const opts = httpClientOrOptions as SmartRouterOptions;\n if (!opts.httpFetcher && !opts.httpClient) {\n throw new Error('SmartRouter: must provide either httpFetcher or httpClient in options');\n }\n this.httpClient = opts.httpClient;\n this.browserPool = opts.browserPool;\n this.httpFetcher = opts.httpFetcher ?? this.makeDefaultHttpFetcher();\n this.playwrightFetcher = opts.playwrightFetcher ?? fetchWithPlaywright;\n return;\n } else {\n // Backwards-compat: single HttpClient positional (unusual but safe)\n this.httpClient = httpClientOrOptions as HttpClient;\n }\n this.httpFetcher = this.makeDefaultHttpFetcher();\n this.playwrightFetcher = fetchWithPlaywright;\n }\n\n private makeDefaultHttpFetcher(): HttpFetcher {\n return async (url, opts) => {\n if (!this.httpClient) {\n throw new Error('SmartRouter: httpClient not configured');\n }\n const r = await this.httpClient.fetch(url, opts);\n return { url: r.url, html: r.html, text: '' };\n };\n }\n\n async fetch(url: string, options: RouterFetchOptions & { mode: 'stealth' }): Promise<RawFetchResult | StageError>;\n async fetch(url: string, options?: RouterFetchOptions): Promise<RawFetchResult>;\n async fetch(\n url: string,\n options: RouterFetchOptions = {},\n ): Promise<RawFetchResult | StageError> {\n const { renderJs = 'auto', useAuth = false, headers, screenshot, actions, mode, conditionalHeaders } = options;\n const config = getConfig();\n const logger = createLogger('fetch');\n const threshold = config.browserFallbackThreshold;\n const domain = new URL(url).hostname;\n\n // Stealth mode: static fetch first, escalate to Playwright when content is thin.\n if (mode === 'stealth') {\n logger.debug('routing to stealth (static then escalate)', { url });\n const staticResult = await this.httpFetcher(url, { headers });\n this.ensureStats(domain);\n if (!shouldEscalate(staticResult.text)) {\n return {\n url: staticResult.url,\n finalUrl: staticResult.url,\n html: staticResult.html,\n contentType: 'text/html',\n statusCode: 200,\n method: 'http',\n headers: {},\n };\n }\n try {\n const pw = await this.playwrightFetcher(url);\n return {\n url: staticResult.url,\n finalUrl: staticResult.url,\n html: pw.html,\n contentType: 'text/html',\n statusCode: 200,\n method: 'playwright',\n headers: {},\n escalated: true,\n };\n } catch (err) {\n if (err instanceof Error && err.message === 'playwright_not_installed') {\n const hint = (err as Error & { hint?: string }).hint ?? 'npx playwright install chromium';\n return {\n error: 'playwright_not_installed',\n error_reason: 'Stealth mode requested but Playwright chromium is not installed',\n stage: 'fetch',\n hint,\n };\n }\n const described = describeFetchError(err);\n return {\n error: 'playwright_fetch_failed',\n error_reason: described.reason,\n stage: 'fetch',\n hint: described.hint ?? 'Stealth fetch failed; check network or retry',\n };\n }\n }\n\n // Cache mode: HTTP-only with tight timeout, never escalates to a browser.\n if (mode === 'cache') {\n if (actions && actions.length > 0) {\n logger.warn('mode=cache ignores browser actions; switch to default/stealth to execute them', {\n url,\n actionCount: actions.length,\n });\n }\n logger.debug('routing to http (cache)', { url });\n if (!this.httpClient) throw new Error('SmartRouter: httpClient not configured');\n const result = await this.httpClient.fetch(url, {\n headers,\n timeoutMs: config.fastTimeoutMs,\n conditionalHeaders,\n });\n this.ensureStats(domain);\n const raw = this.toRawFetchResult(result);\n // Don't probe content of a 304 — body is empty by spec, not a SPA shell.\n raw.jsRequired = result.statusCode === 304 ? false : contentAppearsEmpty(result.html);\n return raw;\n }\n\n // Actions always force Playwright --- actions need a live browser page\n if (actions && actions.length > 0) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n const authOptions = useAuth ? (await getAuthOptions() ?? {}) : {};\n logger.debug('routing to playwright', { url, reason: 'actions present' });\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot, actions, ...authOptions });\n }\n\n // Always Playwright for auth or explicit override\n if (renderJs === 'always' || useAuth) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n const authOptions = useAuth ? (await getAuthOptions() ?? {}) : {};\n logger.debug('routing to playwright', { url, reason: useAuth ? 'auth' : 'render_js=always' });\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot, ...authOptions });\n }\n\n // HTTP only, no fallback\n if (renderJs === 'never') {\n if (!this.httpClient) throw new Error('SmartRouter: httpClient not configured');\n logger.debug('routing to http (never)', { url });\n const result = await this.httpClient.fetch(url, { headers, conditionalHeaders });\n this.ensureStats(domain);\n return this.toRawFetchResult(result);\n }\n\n // auto: check if domain is already marked for Playwright\n const stats = this.ensureStats(domain);\n\n if (stats.preferPlaywright) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n logger.debug('routing to playwright (domain marked)', { url, domain });\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot });\n }\n\n // Try HTTP first\n try {\n if (!this.httpClient) throw new Error('SmartRouter: httpClient not configured');\n const result = await this.httpClient.fetch(url, { headers, conditionalHeaders });\n\n // 304 = unchanged: pass through; never escalate to a browser.\n if (result.statusCode === 304) {\n return this.toRawFetchResult(result);\n }\n\n // Check for SPA shell / empty content\n if (contentAppearsEmpty(result.html)) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n logger.info('SPA shell detected, marking domain for playwright', { url, domain });\n stats.preferPlaywright = true;\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot });\n }\n\n return this.toRawFetchResult(result);\n } catch (err) {\n stats.failureCount++;\n logger.warn('http fetch failed', {\n url,\n domain,\n failureCount: stats.failureCount,\n error: err instanceof Error ? err.message : String(err),\n });\n\n if (stats.failureCount >= threshold) {\n if (!this.browserPool) throw new Error('SmartRouter: browserPool not configured');\n logger.info('failure threshold reached, marking domain for playwright', { url, domain, threshold });\n stats.preferPlaywright = true;\n return this.browserPool.fetchWithBrowser(url, { headers, screenshot });\n }\n\n throw err;\n }\n }\n\n getDomainStats(domain: string): DomainStats | undefined {\n return this.domainMap.get(domain);\n }\n\n private ensureStats(domain: string): DomainStats {\n let stats = this.domainMap.get(domain);\n if (!stats) {\n stats = { failureCount: 0, preferPlaywright: false };\n this.domainMap.set(domain, stats);\n }\n return stats;\n }\n\n private toRawFetchResult(\n result: Awaited<ReturnType<HttpClient['fetch']>>,\n ): RawFetchResult {\n return {\n url: result.url,\n finalUrl: result.finalUrl,\n html: result.html,\n contentType: result.contentType,\n statusCode: result.statusCode,\n method: 'http',\n headers: result.headers,\n rawBuffer: result.rawBuffer,\n };\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -15,28 +15,33 @@ import { runBackfill } from "./cli/backfill.js";
15
15
  import { printHelp, printVersion, printUnknownCommand } from "./cli/help.js";
16
16
  import { getConfig } from "./config.js";
17
17
  import { startServer } from "./server.js";
18
+ import { shutdownCli } from "./cli/shutdown.js";
19
+ async function exitCli(code) {
20
+ await shutdownCli();
21
+ process.exit(code);
22
+ }
18
23
  const { command, args } = parseCommand(process.argv.slice(2));
19
24
  switch (command) {
20
25
  case "warmup":
21
26
  await runWarmup(args);
22
- process.exit(0);
27
+ await exitCli(0);
23
28
  break;
24
29
  case "serve":
25
30
  runDaemon(args);
26
31
  break;
27
32
  case "health": {
28
33
  const exitCode = await runHealthCheck();
29
- process.exit(exitCode);
34
+ await exitCli(exitCode);
30
35
  break;
31
36
  }
32
37
  case "doctor": {
33
38
  const code = await runDoctor(getConfig().dataDir);
34
- process.exit(code);
39
+ await exitCli(code);
35
40
  break;
36
41
  }
37
42
  case "auth": {
38
43
  const authCode = await runAuth(args);
39
- process.exit(authCode);
44
+ await exitCli(authCode);
40
45
  break;
41
46
  }
42
47
  case "shell":
@@ -47,40 +52,40 @@ switch (command) {
47
52
  break;
48
53
  case "init": {
49
54
  const initCode = await runInit(args);
50
- process.exit(initCode);
55
+ await exitCli(initCode);
51
56
  break;
52
57
  }
53
58
  case "uninstall": {
54
59
  const uninstallCode = await runUninstall(args);
55
- process.exit(uninstallCode);
60
+ await exitCli(uninstallCode);
56
61
  break;
57
62
  }
58
63
  case "setup": {
59
64
  const code = await runSetupMcp(args);
60
- process.exit(code);
65
+ await exitCli(code);
61
66
  break;
62
67
  }
63
68
  case "status": {
64
69
  const code = await runStatus(args);
65
- process.exit(code);
70
+ await exitCli(code);
66
71
  break;
67
72
  }
68
73
  case "backfill": {
69
74
  const code = await runBackfill(args);
70
- process.exit(code);
75
+ await exitCli(code);
71
76
  break;
72
77
  }
73
78
  case "help":
74
79
  printHelp();
75
- process.exit(0);
80
+ await exitCli(0);
76
81
  break;
77
82
  case "version":
78
83
  printVersion();
79
- process.exit(0);
84
+ await exitCli(0);
80
85
  break;
81
86
  case "unknown":
82
87
  printUnknownCommand(args[0] ?? "");
83
- process.exit(1);
88
+ await exitCli(1);
84
89
  break;
85
90
  case "mcp": {
86
91
  const config = getConfig();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { parseCommand } from './cli/index.js';\nimport { runWarmup } from './cli/warmup.js';\nimport { runDaemon } from './cli/daemon.js';\nimport { runHealthCheck } from './cli/health.js';\nimport { runDoctor } from './cli/doctor.js';\nimport { runShell } from './cli/shell.js';\nimport { runAuth } from './cli/auth.js';\nimport { runPluginCommand } from './cli/plugin.js';\nimport { runInit } from './cli/init.js';\nimport { runUninstall } from './cli/uninstall.js';\nimport { runSetupMcp } from './cli/setup-mcp.js';\nimport { runStatus } from './cli/status.js';\nimport { runBackfill } from './cli/backfill.js';\nimport { printHelp, printVersion, printUnknownCommand } from './cli/help.js';\nimport { getConfig } from './config.js';\nimport { startServer } from './server.js';\n\nconst { command, args } = parseCommand(process.argv.slice(2));\n\nswitch (command) {\n case 'warmup':\n await runWarmup(args);\n // Explicit exit for clean teardown — ensures all child subprocesses\n // (reranker, embedding) are reaped before Node returns.\n process.exit(0);\n break;\n\n case 'serve':\n runDaemon(args);\n break;\n\n case 'health': {\n const exitCode = await runHealthCheck();\n process.exit(exitCode);\n break;\n }\n\n case 'doctor': {\n const code = await runDoctor(getConfig().dataDir);\n process.exit(code);\n break;\n }\n\n case 'auth': {\n const authCode = await runAuth(args);\n process.exit(authCode);\n break;\n }\n\n case 'shell':\n await runShell(args);\n break;\n\n case 'plugin':\n runPluginCommand(args);\n break;\n\n case 'init': {\n const initCode = await runInit(args);\n process.exit(initCode);\n break;\n }\n\n case 'uninstall': {\n const uninstallCode = await runUninstall(args);\n process.exit(uninstallCode);\n break;\n }\n\n case 'setup': {\n const code = await runSetupMcp(args);\n process.exit(code);\n break;\n }\n\n case 'status': {\n const code = await runStatus(args);\n process.exit(code);\n break;\n }\n\n case 'backfill': {\n const code = await runBackfill(args);\n process.exit(code);\n break;\n }\n\n case 'help':\n printHelp();\n process.exit(0);\n break;\n\n case 'version':\n printVersion();\n process.exit(0);\n break;\n\n case 'unknown':\n printUnknownCommand(args[0] ?? '');\n process.exit(1);\n break;\n\n case 'mcp': {\n const config = getConfig();\n\n try {\n const { tryConnectDaemon } = await import('./daemon/proxy.js');\n const report = await tryConnectDaemon(config.daemonPort, config.daemonHost);\n if (report) {\n process.stderr.write(\n `[wigolo] Daemon detected at ${config.daemonHost}:${config.daemonPort} ` +\n `(status: ${report.status}). Full proxy deferred to v2.1; starting local server.\\n`,\n );\n }\n } catch {\n // Daemon proxy module may not be available -- fall through to local server\n }\n\n await startServer();\n break;\n }\n}\n"],"mappings":";AAEA,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,wBAAwB;AACjC,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,WAAW,cAAc,2BAA2B;AAC7D,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAE5B,MAAM,EAAE,SAAS,KAAK,IAAI,aAAa,QAAQ,KAAK,MAAM,CAAC,CAAC;AAE5D,QAAQ,SAAS;AAAA,EACf,KAAK;AACH,UAAM,UAAU,IAAI;AAGpB,YAAQ,KAAK,CAAC;AACd;AAAA,EAEF,KAAK;AACH,cAAU,IAAI;AACd;AAAA,EAEF,KAAK,UAAU;AACb,UAAM,WAAW,MAAM,eAAe;AACtC,YAAQ,KAAK,QAAQ;AACrB;AAAA,EACF;AAAA,EAEA,KAAK,UAAU;AACb,UAAM,OAAO,MAAM,UAAU,UAAU,EAAE,OAAO;AAChD,YAAQ,KAAK,IAAI;AACjB;AAAA,EACF;AAAA,EAEA,KAAK,QAAQ;AACX,UAAM,WAAW,MAAM,QAAQ,IAAI;AACnC,YAAQ,KAAK,QAAQ;AACrB;AAAA,EACF;AAAA,EAEA,KAAK;AACH,UAAM,SAAS,IAAI;AACnB;AAAA,EAEF,KAAK;AACH,qBAAiB,IAAI;AACrB;AAAA,EAEF,KAAK,QAAQ;AACX,UAAM,WAAW,MAAM,QAAQ,IAAI;AACnC,YAAQ,KAAK,QAAQ;AACrB;AAAA,EACF;AAAA,EAEA,KAAK,aAAa;AAChB,UAAM,gBAAgB,MAAM,aAAa,IAAI;AAC7C,YAAQ,KAAK,aAAa;AAC1B;AAAA,EACF;AAAA,EAEA,KAAK,SAAS;AACZ,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,YAAQ,KAAK,IAAI;AACjB;AAAA,EACF;AAAA,EAEA,KAAK,UAAU;AACb,UAAM,OAAO,MAAM,UAAU,IAAI;AACjC,YAAQ,KAAK,IAAI;AACjB;AAAA,EACF;AAAA,EAEA,KAAK,YAAY;AACf,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,YAAQ,KAAK,IAAI;AACjB;AAAA,EACF;AAAA,EAEA,KAAK;AACH,cAAU;AACV,YAAQ,KAAK,CAAC;AACd;AAAA,EAEF,KAAK;AACH,iBAAa;AACb,YAAQ,KAAK,CAAC;AACd;AAAA,EAEF,KAAK;AACH,wBAAoB,KAAK,CAAC,KAAK,EAAE;AACjC,YAAQ,KAAK,CAAC;AACd;AAAA,EAEF,KAAK,OAAO;AACV,UAAM,SAAS,UAAU;AAEzB,QAAI;AACF,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,mBAAmB;AAC7D,YAAM,SAAS,MAAM,iBAAiB,OAAO,YAAY,OAAO,UAAU;AAC1E,UAAI,QAAQ;AACV,gBAAQ,OAAO;AAAA,UACb,+BAA+B,OAAO,UAAU,IAAI,OAAO,UAAU,aACzD,OAAO,MAAM;AAAA;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,UAAM,YAAY;AAClB;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { parseCommand } from './cli/index.js';\nimport { runWarmup } from './cli/warmup.js';\nimport { runDaemon } from './cli/daemon.js';\nimport { runHealthCheck } from './cli/health.js';\nimport { runDoctor } from './cli/doctor.js';\nimport { runShell } from './cli/shell.js';\nimport { runAuth } from './cli/auth.js';\nimport { runPluginCommand } from './cli/plugin.js';\nimport { runInit } from './cli/init.js';\nimport { runUninstall } from './cli/uninstall.js';\nimport { runSetupMcp } from './cli/setup-mcp.js';\nimport { runStatus } from './cli/status.js';\nimport { runBackfill } from './cli/backfill.js';\nimport { printHelp, printVersion, printUnknownCommand } from './cli/help.js';\nimport { getConfig } from './config.js';\nimport { startServer } from './server.js';\nimport { shutdownCli } from './cli/shutdown.js';\n\nasync function exitCli(code: number): Promise<never> {\n await shutdownCli();\n process.exit(code);\n}\n\nconst { command, args } = parseCommand(process.argv.slice(2));\n\nswitch (command) {\n case 'warmup':\n await runWarmup(args);\n await exitCli(0);\n break;\n\n case 'serve':\n runDaemon(args);\n break;\n\n case 'health': {\n const exitCode = await runHealthCheck();\n await exitCli(exitCode);\n break;\n }\n\n case 'doctor': {\n const code = await runDoctor(getConfig().dataDir);\n await exitCli(code);\n break;\n }\n\n case 'auth': {\n const authCode = await runAuth(args);\n await exitCli(authCode);\n break;\n }\n\n case 'shell':\n await runShell(args);\n break;\n\n case 'plugin':\n runPluginCommand(args);\n break;\n\n case 'init': {\n const initCode = await runInit(args);\n await exitCli(initCode);\n break;\n }\n\n case 'uninstall': {\n const uninstallCode = await runUninstall(args);\n await exitCli(uninstallCode);\n break;\n }\n\n case 'setup': {\n const code = await runSetupMcp(args);\n await exitCli(code);\n break;\n }\n\n case 'status': {\n const code = await runStatus(args);\n await exitCli(code);\n break;\n }\n\n case 'backfill': {\n const code = await runBackfill(args);\n await exitCli(code);\n break;\n }\n\n case 'help':\n printHelp();\n await exitCli(0);\n break;\n\n case 'version':\n printVersion();\n await exitCli(0);\n break;\n\n case 'unknown':\n printUnknownCommand(args[0] ?? '');\n await exitCli(1);\n break;\n\n case 'mcp': {\n const config = getConfig();\n\n try {\n const { tryConnectDaemon } = await import('./daemon/proxy.js');\n const report = await tryConnectDaemon(config.daemonPort, config.daemonHost);\n if (report) {\n process.stderr.write(\n `[wigolo] Daemon detected at ${config.daemonHost}:${config.daemonPort} ` +\n `(status: ${report.status}). Full proxy deferred to v2.1; starting local server.\\n`,\n );\n }\n } catch {\n // Daemon proxy module may not be available -- fall through to local server\n }\n\n await startServer();\n break;\n }\n}\n"],"mappings":";AAEA,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,wBAAwB;AACjC,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,WAAW,cAAc,2BAA2B;AAC7D,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAE5B,eAAe,QAAQ,MAA8B;AACnD,QAAM,YAAY;AAClB,UAAQ,KAAK,IAAI;AACnB;AAEA,MAAM,EAAE,SAAS,KAAK,IAAI,aAAa,QAAQ,KAAK,MAAM,CAAC,CAAC;AAE5D,QAAQ,SAAS;AAAA,EACf,KAAK;AACH,UAAM,UAAU,IAAI;AACpB,UAAM,QAAQ,CAAC;AACf;AAAA,EAEF,KAAK;AACH,cAAU,IAAI;AACd;AAAA,EAEF,KAAK,UAAU;AACb,UAAM,WAAW,MAAM,eAAe;AACtC,UAAM,QAAQ,QAAQ;AACtB;AAAA,EACF;AAAA,EAEA,KAAK,UAAU;AACb,UAAM,OAAO,MAAM,UAAU,UAAU,EAAE,OAAO;AAChD,UAAM,QAAQ,IAAI;AAClB;AAAA,EACF;AAAA,EAEA,KAAK,QAAQ;AACX,UAAM,WAAW,MAAM,QAAQ,IAAI;AACnC,UAAM,QAAQ,QAAQ;AACtB;AAAA,EACF;AAAA,EAEA,KAAK;AACH,UAAM,SAAS,IAAI;AACnB;AAAA,EAEF,KAAK;AACH,qBAAiB,IAAI;AACrB;AAAA,EAEF,KAAK,QAAQ;AACX,UAAM,WAAW,MAAM,QAAQ,IAAI;AACnC,UAAM,QAAQ,QAAQ;AACtB;AAAA,EACF;AAAA,EAEA,KAAK,aAAa;AAChB,UAAM,gBAAgB,MAAM,aAAa,IAAI;AAC7C,UAAM,QAAQ,aAAa;AAC3B;AAAA,EACF;AAAA,EAEA,KAAK,SAAS;AACZ,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,UAAM,QAAQ,IAAI;AAClB;AAAA,EACF;AAAA,EAEA,KAAK,UAAU;AACb,UAAM,OAAO,MAAM,UAAU,IAAI;AACjC,UAAM,QAAQ,IAAI;AAClB;AAAA,EACF;AAAA,EAEA,KAAK,YAAY;AACf,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,UAAM,QAAQ,IAAI;AAClB;AAAA,EACF;AAAA,EAEA,KAAK;AACH,cAAU;AACV,UAAM,QAAQ,CAAC;AACf;AAAA,EAEF,KAAK;AACH,iBAAa;AACb,UAAM,QAAQ,CAAC;AACf;AAAA,EAEF,KAAK;AACH,wBAAoB,KAAK,CAAC,KAAK,EAAE;AACjC,UAAM,QAAQ,CAAC;AACf;AAAA,EAEF,KAAK,OAAO;AACV,UAAM,SAAS,UAAU;AAEzB,QAAI;AACF,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,mBAAmB;AAC7D,YAAM,SAAS,MAAM,iBAAiB,OAAO,YAAY,OAAO,UAAU;AAC1E,UAAI,QAAQ;AACV,gBAAQ,OAAO;AAAA,UACb,+BAA+B,OAAO,UAAU,IAAI,OAAO,UAAU,aACzD,OAAO,MAAM;AAAA;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,UAAM,YAAY;AAClB;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,5 @@
1
+ import type { LLMProvider } from './types.js';
2
+ export declare function resolveModel(provider: LLMProvider, callerOverride?: string, env?: Record<string, string | undefined>): string;
3
+ export declare function providerModelEnvVar(provider: LLMProvider): string;
4
+ export declare function providerDefaultModel(provider: LLMProvider): string;
5
+ //# sourceMappingURL=model-select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-select.d.ts","sourceRoot":"","sources":["../../../../src/integrations/cloud/llm/model-select.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgB9C,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,WAAW,EACrB,cAAc,CAAC,EAAE,MAAM,EACvB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,MAAM,CAOR;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAEjE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAElE"}
@@ -0,0 +1,32 @@
1
+ const PROVIDER_DEFAULTS = {
2
+ anthropic: "claude-haiku-4-5",
3
+ openai: "gpt-4o-mini",
4
+ gemini: "gemini-2.5-flash-lite",
5
+ groq: "llama-3.3-70b-versatile"
6
+ };
7
+ const PROVIDER_ENV = {
8
+ anthropic: "WIGOLO_LLM_MODEL_ANTHROPIC",
9
+ openai: "WIGOLO_LLM_MODEL_OPENAI",
10
+ gemini: "WIGOLO_LLM_MODEL_GEMINI",
11
+ groq: "WIGOLO_LLM_MODEL_GROQ"
12
+ };
13
+ function resolveModel(provider, callerOverride, env = process.env) {
14
+ if (callerOverride) return callerOverride;
15
+ const providerSpecific = env[PROVIDER_ENV[provider]];
16
+ if (providerSpecific) return providerSpecific;
17
+ const universal = env.WIGOLO_LLM_MODEL;
18
+ if (universal) return universal;
19
+ return PROVIDER_DEFAULTS[provider];
20
+ }
21
+ function providerModelEnvVar(provider) {
22
+ return PROVIDER_ENV[provider];
23
+ }
24
+ function providerDefaultModel(provider) {
25
+ return PROVIDER_DEFAULTS[provider];
26
+ }
27
+ export {
28
+ providerDefaultModel,
29
+ providerModelEnvVar,
30
+ resolveModel
31
+ };
32
+ //# sourceMappingURL=model-select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/integrations/cloud/llm/model-select.ts"],"sourcesContent":["// Resolve the model name to use for a given provider. Precedence:\n// 1. caller-supplied override (synthesis vs extract may differ)\n// 2. provider-specific env (e.g. WIGOLO_LLM_MODEL_GEMINI)\n// 3. universal env (WIGOLO_LLM_MODEL) — applies to whichever provider is active\n// 4. provider default (per adapter)\n\nimport type { LLMProvider } from './types.js';\n\nconst PROVIDER_DEFAULTS: Record<LLMProvider, string> = {\n anthropic: 'claude-haiku-4-5',\n openai: 'gpt-4o-mini',\n gemini: 'gemini-2.5-flash-lite',\n groq: 'llama-3.3-70b-versatile',\n};\n\nconst PROVIDER_ENV: Record<LLMProvider, string> = {\n anthropic: 'WIGOLO_LLM_MODEL_ANTHROPIC',\n openai: 'WIGOLO_LLM_MODEL_OPENAI',\n gemini: 'WIGOLO_LLM_MODEL_GEMINI',\n groq: 'WIGOLO_LLM_MODEL_GROQ',\n};\n\nexport function resolveModel(\n provider: LLMProvider,\n callerOverride?: string,\n env: Record<string, string | undefined> = process.env,\n): string {\n if (callerOverride) return callerOverride;\n const providerSpecific = env[PROVIDER_ENV[provider]];\n if (providerSpecific) return providerSpecific;\n const universal = env.WIGOLO_LLM_MODEL;\n if (universal) return universal;\n return PROVIDER_DEFAULTS[provider];\n}\n\nexport function providerModelEnvVar(provider: LLMProvider): string {\n return PROVIDER_ENV[provider];\n}\n\nexport function providerDefaultModel(provider: LLMProvider): string {\n return PROVIDER_DEFAULTS[provider];\n}\n"],"mappings":"AAQA,MAAM,oBAAiD;AAAA,EACrD,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AACR;AAEA,MAAM,eAA4C;AAAA,EAChD,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AACR;AAEO,SAAS,aACd,UACA,gBACA,MAA0C,QAAQ,KAC1C;AACR,MAAI,eAAgB,QAAO;AAC3B,QAAM,mBAAmB,IAAI,aAAa,QAAQ,CAAC;AACnD,MAAI,iBAAkB,QAAO;AAC7B,QAAM,YAAY,IAAI;AACtB,MAAI,UAAW,QAAO;AACtB,SAAO,kBAAkB,QAAQ;AACnC;AAEO,SAAS,oBAAoB,UAA+B;AACjE,SAAO,aAAa,QAAQ;AAC9B;AAEO,SAAS,qBAAqB,UAA+B;AAClE,SAAO,kBAAkB,QAAQ;AACnC;","names":[]}
@@ -0,0 +1,27 @@
1
+ import type { LLMProvider } from './types.js';
2
+ export interface RunLlmTextOpts {
3
+ prompt: string;
4
+ maxTokens?: number;
5
+ modelOverride?: string;
6
+ timeoutMs?: number;
7
+ signal?: AbortSignal;
8
+ }
9
+ export interface RunLlmTextResult {
10
+ text: string;
11
+ provider: LLMProvider | 'custom';
12
+ model: string;
13
+ latencyMs: number;
14
+ }
15
+ export interface RunLlmJsonOpts extends RunLlmTextOpts {
16
+ jsonSchema?: Record<string, unknown>;
17
+ }
18
+ export interface RunLlmJsonResult {
19
+ values: Record<string, unknown>;
20
+ provider: LLMProvider | 'custom';
21
+ model: string;
22
+ latencyMs: number;
23
+ }
24
+ export declare function isLlmConfigured(env?: Record<string, string | undefined>): boolean;
25
+ export declare function runLlmText(opts: RunLlmTextOpts): Promise<RunLlmTextResult>;
26
+ export declare function runLlmJson(opts: RunLlmJsonOpts): Promise<RunLlmJsonResult>;
27
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/integrations/cloud/llm/run.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAO9C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,OAAO,CAI9F;AAkBD,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0ChF;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmBhF"}
@@ -0,0 +1,99 @@
1
+ import { TEXT_ADAPTERS } from "./text-adapters.js";
2
+ import { selectProvider, providerEnvVar } from "./select.js";
3
+ import { resolveModel } from "./model-select.js";
4
+ import { createLogger } from "../../../logger.js";
5
+ const log = createLogger("providers");
6
+ const DEFAULT_TIMEOUT_MS = 6e4;
7
+ function isLlmConfigured(env = process.env) {
8
+ const raw = env.WIGOLO_LLM_PROVIDER;
9
+ if (raw && (raw.startsWith("http://") || raw.startsWith("https://"))) return true;
10
+ return selectProvider(env) !== null;
11
+ }
12
+ function pickBackend(env) {
13
+ const raw = env.WIGOLO_LLM_PROVIDER;
14
+ if (raw && (raw.startsWith("http://") || raw.startsWith("https://"))) {
15
+ return { type: "custom", url: raw };
16
+ }
17
+ const provider = selectProvider(env);
18
+ if (provider) return { type: "cloud", provider };
19
+ return null;
20
+ }
21
+ function buildSignal(opts) {
22
+ if (opts.signal) return opts.signal;
23
+ if (opts.timeoutMs) return AbortSignal.timeout(opts.timeoutMs);
24
+ return AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
25
+ }
26
+ async function runLlmText(opts) {
27
+ const backend = pickBackend(process.env);
28
+ if (!backend) {
29
+ throw new Error("No LLM configured \u2014 set WIGOLO_LLM_PROVIDER or a provider API key");
30
+ }
31
+ const signal = buildSignal(opts);
32
+ if (backend.type === "cloud") {
33
+ const apiKey = process.env[providerEnvVar(backend.provider)];
34
+ const model2 = resolveModel(backend.provider, opts.modelOverride);
35
+ log.debug("runLlmText cloud", { provider: backend.provider, model: model2 });
36
+ const r = await TEXT_ADAPTERS[backend.provider](
37
+ { prompt: opts.prompt, model: model2, maxTokens: opts.maxTokens, signal },
38
+ apiKey
39
+ );
40
+ return { text: r.text, provider: r.provider, model: r.model, latencyMs: r.latencyMs };
41
+ }
42
+ const endpoint = backend.url.includes("/chat/completions") ? backend.url : backend.url.replace(/\/+$/, "") + "/v1/chat/completions";
43
+ const model = opts.modelOverride ?? process.env.WIGOLO_LLM_MODEL ?? "local";
44
+ log.debug("runLlmText custom", { url: endpoint, model });
45
+ const start = Date.now();
46
+ const response = await fetch(endpoint, {
47
+ method: "POST",
48
+ headers: { "content-type": "application/json" },
49
+ body: JSON.stringify({
50
+ model,
51
+ messages: [{ role: "user", content: opts.prompt }],
52
+ max_tokens: opts.maxTokens
53
+ }),
54
+ signal
55
+ });
56
+ if (!response.ok) throw new Error(`Local LLM endpoint returned ${response.status}`);
57
+ const payload = await response.json();
58
+ const text = payload.choices?.[0]?.message?.content;
59
+ if (typeof text !== "string" || text.trim().length === 0) {
60
+ throw new Error("Local LLM response missing message content");
61
+ }
62
+ return { text, provider: "custom", model, latencyMs: Date.now() - start };
63
+ }
64
+ async function runLlmJson(opts) {
65
+ const schemaText = opts.jsonSchema ? `
66
+ Return JSON matching this schema:
67
+ ${JSON.stringify(opts.jsonSchema)}` : "";
68
+ const wrapped = `${opts.prompt}
69
+
70
+ Return ONLY valid JSON, no prose.${schemaText}`;
71
+ const r = await runLlmText({ ...opts, prompt: wrapped });
72
+ let values;
73
+ try {
74
+ values = JSON.parse(stripJsonFences(r.text));
75
+ } catch (e) {
76
+ throw new Error(`LLM returned invalid JSON: ${e.message}`);
77
+ }
78
+ if (!values || typeof values !== "object" || Array.isArray(values)) {
79
+ throw new Error("LLM response is not a JSON object");
80
+ }
81
+ return {
82
+ values,
83
+ provider: r.provider,
84
+ model: r.model,
85
+ latencyMs: r.latencyMs
86
+ };
87
+ }
88
+ function stripJsonFences(text) {
89
+ const trimmed = text.trim();
90
+ const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]+?)\s*```$/);
91
+ if (fenced) return fenced[1];
92
+ return trimmed;
93
+ }
94
+ export {
95
+ isLlmConfigured,
96
+ runLlmJson,
97
+ runLlmText
98
+ };
99
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/integrations/cloud/llm/run.ts"],"sourcesContent":["// Unified entry point for LLM calls across wigolo. Selects a backend from\n// env and delegates:\n// - cloud provider name (anthropic/openai/gemini/groq) → SDK adapter\n// - OpenAI-compatible URL (http://...) → POST /v1/chat/completions\n//\n// Used by research synthesis, agent synthesis, and v1 extract LLM fallback\n// so a single WIGOLO_LLM_PROVIDER configuration drives every code path.\n\nimport { TEXT_ADAPTERS, type TextCallResult } from './text-adapters.js';\nimport { selectProvider, providerEnvVar } from './select.js';\nimport { resolveModel } from './model-select.js';\nimport type { LLMProvider } from './types.js';\nimport { createLogger } from '../../../logger.js';\n\nconst log = createLogger('providers');\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\nexport interface RunLlmTextOpts {\n prompt: string;\n maxTokens?: number;\n modelOverride?: string;\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\nexport interface RunLlmTextResult {\n text: string;\n provider: LLMProvider | 'custom';\n model: string;\n latencyMs: number;\n}\n\nexport interface RunLlmJsonOpts extends RunLlmTextOpts {\n jsonSchema?: Record<string, unknown>;\n}\n\nexport interface RunLlmJsonResult {\n values: Record<string, unknown>;\n provider: LLMProvider | 'custom';\n model: string;\n latencyMs: number;\n}\n\nexport function isLlmConfigured(env: Record<string, string | undefined> = process.env): boolean {\n const raw = env.WIGOLO_LLM_PROVIDER;\n if (raw && (raw.startsWith('http://') || raw.startsWith('https://'))) return true;\n return selectProvider(env) !== null;\n}\n\nfunction pickBackend(env: Record<string, string | undefined>): { type: 'cloud'; provider: LLMProvider } | { type: 'custom'; url: string } | null {\n const raw = env.WIGOLO_LLM_PROVIDER;\n if (raw && (raw.startsWith('http://') || raw.startsWith('https://'))) {\n return { type: 'custom', url: raw };\n }\n const provider = selectProvider(env);\n if (provider) return { type: 'cloud', provider };\n return null;\n}\n\nfunction buildSignal(opts: { timeoutMs?: number; signal?: AbortSignal }): AbortSignal | undefined {\n if (opts.signal) return opts.signal;\n if (opts.timeoutMs) return AbortSignal.timeout(opts.timeoutMs);\n return AbortSignal.timeout(DEFAULT_TIMEOUT_MS);\n}\n\nexport async function runLlmText(opts: RunLlmTextOpts): Promise<RunLlmTextResult> {\n const backend = pickBackend(process.env);\n if (!backend) {\n throw new Error('No LLM configured — set WIGOLO_LLM_PROVIDER or a provider API key');\n }\n const signal = buildSignal(opts);\n\n if (backend.type === 'cloud') {\n const apiKey = process.env[providerEnvVar(backend.provider)] as string;\n const model = resolveModel(backend.provider, opts.modelOverride);\n log.debug('runLlmText cloud', { provider: backend.provider, model });\n const r: TextCallResult = await TEXT_ADAPTERS[backend.provider](\n { prompt: opts.prompt, model, maxTokens: opts.maxTokens, signal },\n apiKey,\n );\n return { text: r.text, provider: r.provider, model: r.model, latencyMs: r.latencyMs };\n }\n\n // Custom OpenAI-compatible URL backend (e.g. Ollama, vLLM, LM Studio).\n const endpoint = backend.url.includes('/chat/completions')\n ? backend.url\n : backend.url.replace(/\\/+$/, '') + '/v1/chat/completions';\n const model = opts.modelOverride ?? process.env.WIGOLO_LLM_MODEL ?? 'local';\n log.debug('runLlmText custom', { url: endpoint, model });\n const start = Date.now();\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n model,\n messages: [{ role: 'user', content: opts.prompt }],\n max_tokens: opts.maxTokens,\n }),\n signal,\n });\n if (!response.ok) throw new Error(`Local LLM endpoint returned ${response.status}`);\n const payload = (await response.json()) as { choices?: Array<{ message?: { content?: string } }> };\n const text = payload.choices?.[0]?.message?.content;\n if (typeof text !== 'string' || text.trim().length === 0) {\n throw new Error('Local LLM response missing message content');\n }\n return { text, provider: 'custom', model, latencyMs: Date.now() - start };\n}\n\nexport async function runLlmJson(opts: RunLlmJsonOpts): Promise<RunLlmJsonResult> {\n const schemaText = opts.jsonSchema ? `\\nReturn JSON matching this schema:\\n${JSON.stringify(opts.jsonSchema)}` : '';\n const wrapped = `${opts.prompt}\\n\\nReturn ONLY valid JSON, no prose.${schemaText}`;\n const r = await runLlmText({ ...opts, prompt: wrapped });\n let values: unknown;\n try {\n values = JSON.parse(stripJsonFences(r.text));\n } catch (e) {\n throw new Error(`LLM returned invalid JSON: ${(e as Error).message}`);\n }\n if (!values || typeof values !== 'object' || Array.isArray(values)) {\n throw new Error('LLM response is not a JSON object');\n }\n return {\n values: values as Record<string, unknown>,\n provider: r.provider,\n model: r.model,\n latencyMs: r.latencyMs,\n };\n}\n\nfunction stripJsonFences(text: string): string {\n const trimmed = text.trim();\n const fenced = trimmed.match(/^```(?:json)?\\s*([\\s\\S]+?)\\s*```$/);\n if (fenced) return fenced[1];\n return trimmed;\n}\n"],"mappings":"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","names":["model"]}
@@ -0,0 +1,19 @@
1
+ import type { LLMProvider } from './types.js';
2
+ export interface TextCallOpts {
3
+ prompt: string;
4
+ model: string;
5
+ maxTokens?: number;
6
+ signal?: AbortSignal;
7
+ }
8
+ export interface TextCallResult {
9
+ text: string;
10
+ provider: LLMProvider;
11
+ model: string;
12
+ latencyMs: number;
13
+ }
14
+ export declare function callAnthropicText(opts: TextCallOpts, apiKey: string): Promise<TextCallResult>;
15
+ export declare function callOpenAIText(opts: TextCallOpts, apiKey: string): Promise<TextCallResult>;
16
+ export declare function callGeminiText(opts: TextCallOpts, apiKey: string): Promise<TextCallResult>;
17
+ export declare function callGroqText(opts: TextCallOpts, apiKey: string): Promise<TextCallResult>;
18
+ export declare const TEXT_ADAPTERS: Record<LLMProvider, (opts: TextCallOpts, apiKey: string) => Promise<TextCallResult>>;
19
+ //# sourceMappingURL=text-adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-adapters.d.ts","sourceRoot":"","sources":["../../../../src/integrations/cloud/llm/text-adapters.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAsBnG;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAsBhG;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAoBhG;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAsB9F;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAChC,WAAW,EACX,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAMhE,CAAC"}
@@ -0,0 +1,103 @@
1
+ const DEFAULT_MAX_TOKENS = 2e3;
2
+ async function callAnthropicText(opts, apiKey) {
3
+ const { default: Anthropic } = await import("@anthropic-ai/sdk");
4
+ const client = new Anthropic({ apiKey });
5
+ const start = Date.now();
6
+ const response = await client.messages.create(
7
+ {
8
+ model: opts.model,
9
+ max_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
10
+ messages: [{ role: "user", content: opts.prompt }]
11
+ },
12
+ { signal: opts.signal }
13
+ );
14
+ const block = (response.content ?? []).find((b) => b.type === "text");
15
+ if (!block) throw new Error("anthropic: no text block in response");
16
+ return {
17
+ text: block.text,
18
+ provider: "anthropic",
19
+ model: response.model ?? opts.model,
20
+ latencyMs: Date.now() - start
21
+ };
22
+ }
23
+ async function callOpenAIText(opts, apiKey) {
24
+ const { default: OpenAI } = await import("openai");
25
+ const client = new OpenAI({ apiKey });
26
+ const start = Date.now();
27
+ const response = await client.chat.completions.create(
28
+ {
29
+ model: opts.model,
30
+ max_completion_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
31
+ messages: [{ role: "user", content: opts.prompt }]
32
+ },
33
+ { signal: opts.signal }
34
+ );
35
+ const text = response.choices?.[0]?.message?.content;
36
+ if (typeof text !== "string" || text.trim().length === 0) {
37
+ throw new Error("openai: empty content in response");
38
+ }
39
+ return {
40
+ text,
41
+ provider: "openai",
42
+ model: response.model ?? opts.model,
43
+ latencyMs: Date.now() - start
44
+ };
45
+ }
46
+ async function callGeminiText(opts, apiKey) {
47
+ const { GoogleGenAI } = await import("@google/genai");
48
+ const client = new GoogleGenAI({ apiKey });
49
+ const start = Date.now();
50
+ const response = await client.models.generateContent({
51
+ model: opts.model,
52
+ contents: opts.prompt,
53
+ config: {
54
+ maxOutputTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
55
+ abortSignal: opts.signal
56
+ }
57
+ });
58
+ const text = response.text;
59
+ if (!text || text.trim().length === 0) throw new Error("gemini: empty text in response");
60
+ return {
61
+ text,
62
+ provider: "gemini",
63
+ model: opts.model,
64
+ latencyMs: Date.now() - start
65
+ };
66
+ }
67
+ async function callGroqText(opts, apiKey) {
68
+ const { default: Groq } = await import("groq-sdk");
69
+ const client = new Groq({ apiKey });
70
+ const start = Date.now();
71
+ const response = await client.chat.completions.create(
72
+ {
73
+ model: opts.model,
74
+ max_completion_tokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
75
+ messages: [{ role: "user", content: opts.prompt }]
76
+ },
77
+ { signal: opts.signal }
78
+ );
79
+ const text = response.choices?.[0]?.message?.content;
80
+ if (typeof text !== "string" || text.trim().length === 0) {
81
+ throw new Error("groq: empty content in response");
82
+ }
83
+ return {
84
+ text,
85
+ provider: "groq",
86
+ model: response.model ?? opts.model,
87
+ latencyMs: Date.now() - start
88
+ };
89
+ }
90
+ const TEXT_ADAPTERS = {
91
+ anthropic: callAnthropicText,
92
+ openai: callOpenAIText,
93
+ gemini: callGeminiText,
94
+ groq: callGroqText
95
+ };
96
+ export {
97
+ TEXT_ADAPTERS,
98
+ callAnthropicText,
99
+ callGeminiText,
100
+ callGroqText,
101
+ callOpenAIText
102
+ };
103
+ //# sourceMappingURL=text-adapters.js.map