@jini-ai/integrations 0.1.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 (191) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +51 -0
  3. package/dist/composio/bounded-data.d.ts +36 -0
  4. package/dist/composio/bounded-data.d.ts.map +1 -0
  5. package/dist/composio/bounded-data.js +181 -0
  6. package/dist/composio/bounded-data.js.map +1 -0
  7. package/dist/composio/catalog.d.ts +147 -0
  8. package/dist/composio/catalog.d.ts.map +1 -0
  9. package/dist/composio/catalog.js +126 -0
  10. package/dist/composio/catalog.js.map +1 -0
  11. package/dist/composio/composio-config.d.ts +42 -0
  12. package/dist/composio/composio-config.d.ts.map +1 -0
  13. package/dist/composio/composio-config.js +145 -0
  14. package/dist/composio/composio-config.js.map +1 -0
  15. package/dist/composio/composio-descriptions.d.ts +16 -0
  16. package/dist/composio/composio-descriptions.d.ts.map +1 -0
  17. package/dist/composio/composio-descriptions.js +766 -0
  18. package/dist/composio/composio-descriptions.js.map +1 -0
  19. package/dist/composio/composio.d.ts +364 -0
  20. package/dist/composio/composio.d.ts.map +1 -0
  21. package/dist/composio/composio.js +1902 -0
  22. package/dist/composio/composio.js.map +1 -0
  23. package/dist/composio/errors.d.ts +15 -0
  24. package/dist/composio/errors.d.ts.map +1 -0
  25. package/dist/composio/errors.js +16 -0
  26. package/dist/composio/errors.js.map +1 -0
  27. package/dist/composio/file-lock.d.ts +13 -0
  28. package/dist/composio/file-lock.d.ts.map +1 -0
  29. package/dist/composio/file-lock.js +92 -0
  30. package/dist/composio/file-lock.js.map +1 -0
  31. package/dist/composio/index.d.ts +18 -0
  32. package/dist/composio/index.d.ts.map +1 -0
  33. package/dist/composio/index.js +7 -0
  34. package/dist/composio/index.js.map +1 -0
  35. package/dist/composio/json-schema.d.ts +93 -0
  36. package/dist/composio/json-schema.d.ts.map +1 -0
  37. package/dist/composio/json-schema.js +498 -0
  38. package/dist/composio/json-schema.js.map +1 -0
  39. package/dist/composio/output-protection.d.ts +32 -0
  40. package/dist/composio/output-protection.d.ts.map +1 -0
  41. package/dist/composio/output-protection.js +105 -0
  42. package/dist/composio/output-protection.js.map +1 -0
  43. package/dist/composio/service.d.ts +181 -0
  44. package/dist/composio/service.d.ts.map +1 -0
  45. package/dist/composio/service.js +758 -0
  46. package/dist/composio/service.js.map +1 -0
  47. package/dist/media-providers/capability-registry.d.ts +28 -0
  48. package/dist/media-providers/capability-registry.d.ts.map +1 -0
  49. package/dist/media-providers/capability-registry.js +27 -0
  50. package/dist/media-providers/capability-registry.js.map +1 -0
  51. package/dist/media-providers/catalog.d.ts +20 -0
  52. package/dist/media-providers/catalog.d.ts.map +1 -0
  53. package/dist/media-providers/catalog.js +20 -0
  54. package/dist/media-providers/catalog.js.map +1 -0
  55. package/dist/media-providers/dispatch/context.d.ts +21 -0
  56. package/dist/media-providers/dispatch/context.d.ts.map +1 -0
  57. package/dist/media-providers/dispatch/context.js +36 -0
  58. package/dist/media-providers/dispatch/context.js.map +1 -0
  59. package/dist/media-providers/dispatch/credentials.d.ts +4 -0
  60. package/dist/media-providers/dispatch/credentials.d.ts.map +1 -0
  61. package/dist/media-providers/dispatch/credentials.js +25 -0
  62. package/dist/media-providers/dispatch/credentials.js.map +1 -0
  63. package/dist/media-providers/dispatch/engine.d.ts +30 -0
  64. package/dist/media-providers/dispatch/engine.d.ts.map +1 -0
  65. package/dist/media-providers/dispatch/engine.js +208 -0
  66. package/dist/media-providers/dispatch/engine.js.map +1 -0
  67. package/dist/media-providers/dispatch/index.d.ts +25 -0
  68. package/dist/media-providers/dispatch/index.d.ts.map +1 -0
  69. package/dist/media-providers/dispatch/index.js +24 -0
  70. package/dist/media-providers/dispatch/index.js.map +1 -0
  71. package/dist/media-providers/dispatch/openai-compatible.d.ts +44 -0
  72. package/dist/media-providers/dispatch/openai-compatible.d.ts.map +1 -0
  73. package/dist/media-providers/dispatch/openai-compatible.js +214 -0
  74. package/dist/media-providers/dispatch/openai-compatible.js.map +1 -0
  75. package/dist/media-providers/dispatch/providers/aihubmix-shared.d.ts +72 -0
  76. package/dist/media-providers/dispatch/providers/aihubmix-shared.d.ts.map +1 -0
  77. package/dist/media-providers/dispatch/providers/aihubmix-shared.js +131 -0
  78. package/dist/media-providers/dispatch/providers/aihubmix-shared.js.map +1 -0
  79. package/dist/media-providers/dispatch/providers/aihubmix.d.ts +4 -0
  80. package/dist/media-providers/dispatch/providers/aihubmix.d.ts.map +1 -0
  81. package/dist/media-providers/dispatch/providers/aihubmix.js +205 -0
  82. package/dist/media-providers/dispatch/providers/aihubmix.js.map +1 -0
  83. package/dist/media-providers/dispatch/providers/custom-image.d.ts +6 -0
  84. package/dist/media-providers/dispatch/providers/custom-image.d.ts.map +1 -0
  85. package/dist/media-providers/dispatch/providers/custom-image.js +94 -0
  86. package/dist/media-providers/dispatch/providers/custom-image.js.map +1 -0
  87. package/dist/media-providers/dispatch/providers/elevenlabs.d.ts +4 -0
  88. package/dist/media-providers/dispatch/providers/elevenlabs.d.ts.map +1 -0
  89. package/dist/media-providers/dispatch/providers/elevenlabs.js +163 -0
  90. package/dist/media-providers/dispatch/providers/elevenlabs.js.map +1 -0
  91. package/dist/media-providers/dispatch/providers/fishaudio.d.ts +3 -0
  92. package/dist/media-providers/dispatch/providers/fishaudio.d.ts.map +1 -0
  93. package/dist/media-providers/dispatch/providers/fishaudio.js +83 -0
  94. package/dist/media-providers/dispatch/providers/fishaudio.js.map +1 -0
  95. package/dist/media-providers/dispatch/providers/grok.d.ts +15 -0
  96. package/dist/media-providers/dispatch/providers/grok.d.ts.map +1 -0
  97. package/dist/media-providers/dispatch/providers/grok.js +150 -0
  98. package/dist/media-providers/dispatch/providers/grok.js.map +1 -0
  99. package/dist/media-providers/dispatch/providers/imagerouter.d.ts +6 -0
  100. package/dist/media-providers/dispatch/providers/imagerouter.d.ts.map +1 -0
  101. package/dist/media-providers/dispatch/providers/imagerouter.js +131 -0
  102. package/dist/media-providers/dispatch/providers/imagerouter.js.map +1 -0
  103. package/dist/media-providers/dispatch/providers/minimax.d.ts +3 -0
  104. package/dist/media-providers/dispatch/providers/minimax.d.ts.map +1 -0
  105. package/dist/media-providers/dispatch/providers/minimax.js +96 -0
  106. package/dist/media-providers/dispatch/providers/minimax.js.map +1 -0
  107. package/dist/media-providers/dispatch/providers/nanobanana.d.ts +3 -0
  108. package/dist/media-providers/dispatch/providers/nanobanana.d.ts.map +1 -0
  109. package/dist/media-providers/dispatch/providers/nanobanana.js +142 -0
  110. package/dist/media-providers/dispatch/providers/nanobanana.js.map +1 -0
  111. package/dist/media-providers/dispatch/providers/openai.d.ts +4 -0
  112. package/dist/media-providers/dispatch/providers/openai.d.ts.map +1 -0
  113. package/dist/media-providers/dispatch/providers/openai.js +231 -0
  114. package/dist/media-providers/dispatch/providers/openai.js.map +1 -0
  115. package/dist/media-providers/dispatch/providers/openrouter.d.ts +13 -0
  116. package/dist/media-providers/dispatch/providers/openrouter.d.ts.map +1 -0
  117. package/dist/media-providers/dispatch/providers/openrouter.js +152 -0
  118. package/dist/media-providers/dispatch/providers/openrouter.js.map +1 -0
  119. package/dist/media-providers/dispatch/providers/senseaudio.d.ts +4 -0
  120. package/dist/media-providers/dispatch/providers/senseaudio.d.ts.map +1 -0
  121. package/dist/media-providers/dispatch/providers/senseaudio.js +190 -0
  122. package/dist/media-providers/dispatch/providers/senseaudio.js.map +1 -0
  123. package/dist/media-providers/dispatch/providers/volcengine.d.ts +3 -0
  124. package/dist/media-providers/dispatch/providers/volcengine.d.ts.map +1 -0
  125. package/dist/media-providers/dispatch/providers/volcengine.js +95 -0
  126. package/dist/media-providers/dispatch/providers/volcengine.js.map +1 -0
  127. package/dist/media-providers/dispatch/response-parsers.d.ts +44 -0
  128. package/dist/media-providers/dispatch/response-parsers.d.ts.map +1 -0
  129. package/dist/media-providers/dispatch/response-parsers.js +103 -0
  130. package/dist/media-providers/dispatch/response-parsers.js.map +1 -0
  131. package/dist/media-providers/dispatch/ssrf-guard.d.ts +51 -0
  132. package/dist/media-providers/dispatch/ssrf-guard.d.ts.map +1 -0
  133. package/dist/media-providers/dispatch/ssrf-guard.js +215 -0
  134. package/dist/media-providers/dispatch/ssrf-guard.js.map +1 -0
  135. package/dist/media-providers/dispatch/stub.d.ts +5 -0
  136. package/dist/media-providers/dispatch/stub.d.ts.map +1 -0
  137. package/dist/media-providers/dispatch/stub.js +98 -0
  138. package/dist/media-providers/dispatch/stub.js.map +1 -0
  139. package/dist/media-providers/dispatch/types.d.ts +133 -0
  140. package/dist/media-providers/dispatch/types.d.ts.map +1 -0
  141. package/dist/media-providers/dispatch/types.js +2 -0
  142. package/dist/media-providers/dispatch/types.js.map +1 -0
  143. package/dist/media-providers/dispatch/vendor-adapter.d.ts +54 -0
  144. package/dist/media-providers/dispatch/vendor-adapter.d.ts.map +1 -0
  145. package/dist/media-providers/dispatch/vendor-adapter.js +68 -0
  146. package/dist/media-providers/dispatch/vendor-adapter.js.map +1 -0
  147. package/dist/media-providers/dispatch/vendor-registry.d.ts +36 -0
  148. package/dist/media-providers/dispatch/vendor-registry.d.ts.map +1 -0
  149. package/dist/media-providers/dispatch/vendor-registry.js +46 -0
  150. package/dist/media-providers/dispatch/vendor-registry.js.map +1 -0
  151. package/dist/media-providers/index.d.ts +22 -0
  152. package/dist/media-providers/index.d.ts.map +1 -0
  153. package/dist/media-providers/index.js +22 -0
  154. package/dist/media-providers/index.js.map +1 -0
  155. package/dist/media-providers/policy.d.ts +49 -0
  156. package/dist/media-providers/policy.d.ts.map +1 -0
  157. package/dist/media-providers/policy.js +39 -0
  158. package/dist/media-providers/policy.js.map +1 -0
  159. package/dist/media-providers/providers.d.ts +33 -0
  160. package/dist/media-providers/providers.d.ts.map +1 -0
  161. package/dist/media-providers/providers.js +200 -0
  162. package/dist/media-providers/providers.js.map +1 -0
  163. package/dist/media-providers/seed.d.ts +14 -0
  164. package/dist/media-providers/seed.d.ts.map +1 -0
  165. package/dist/media-providers/seed.js +109 -0
  166. package/dist/media-providers/seed.js.map +1 -0
  167. package/dist/media-providers/sqlite-task-store.d.ts +29 -0
  168. package/dist/media-providers/sqlite-task-store.d.ts.map +1 -0
  169. package/dist/media-providers/sqlite-task-store.js +234 -0
  170. package/dist/media-providers/sqlite-task-store.js.map +1 -0
  171. package/dist/media-providers/staging.d.ts +29 -0
  172. package/dist/media-providers/staging.d.ts.map +1 -0
  173. package/dist/media-providers/staging.js +141 -0
  174. package/dist/media-providers/staging.js.map +1 -0
  175. package/dist/media-providers/task-store.d.ts +94 -0
  176. package/dist/media-providers/task-store.d.ts.map +1 -0
  177. package/dist/media-providers/task-store.js +160 -0
  178. package/dist/media-providers/task-store.js.map +1 -0
  179. package/dist/media-providers/tokens.d.ts +7 -0
  180. package/dist/media-providers/tokens.d.ts.map +1 -0
  181. package/dist/media-providers/tokens.js +13 -0
  182. package/dist/media-providers/tokens.js.map +1 -0
  183. package/dist/media-providers/types.d.ts +123 -0
  184. package/dist/media-providers/types.d.ts.map +1 -0
  185. package/dist/media-providers/types.js +12 -0
  186. package/dist/media-providers/types.js.map +1 -0
  187. package/dist/media-providers/video-request.d.ts +62 -0
  188. package/dist/media-providers/video-request.d.ts.map +1 -0
  189. package/dist/media-providers/video-request.js +219 -0
  190. package/dist/media-providers/video-request.js.map +1 -0
  191. package/package.json +93 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Provider: Volcengine Ark (Doubao) — Seedream / Seededit image generation
3
+ * via an OpenAI-images-compatible `/images/generations` endpoint. Ported
4
+ * near-verbatim from Open Design's `apps/daemon/src/media/index.ts`
5
+ * `renderVolcengineImage` — see `source-map.md`.
6
+ *
7
+ * The response shape (`{ data: [{ b64_json | url }] }`) matches OpenAI's
8
+ * images API, so this routes through `openai-compatible.ts`'s already-
9
+ * ported URL-building/JSON-parsing/byte-extraction helpers rather than
10
+ * re-implementing them — same reasoning `grok.ts` already documents for
11
+ * the identical shape match.
12
+ *
13
+ * Unlike `grok.ts`, the origin does not sniff the downloaded bytes' magic
14
+ * number for `suggestedExt` here — Seedream/Seededit are documented to
15
+ * always return PNG, so the origin hardcodes `.png` and this port
16
+ * preserves that exactly rather than silently "upgrading" it to
17
+ * `sniffImageExt`.
18
+ *
19
+ * Also unlike `senseaudio.ts`/`aihubmix.ts` (ported alongside this batch),
20
+ * the origin's `entry.url` fallback download here is a plain `fetch`, not
21
+ * routed through `ssrf-guard.ts`'s `assertAndFetchExternalAsset` — this
22
+ * mirrors the real origin exactly (verified: `assertAndFetchExternalAsset`
23
+ * is only called from `renderSenseAudioImage`/`renderAIHubMixImage`/
24
+ * `renderAIHubMixVideo` in the actual source, never from
25
+ * `renderVolcengineImage`), not a gap this port introduced. Flagged here
26
+ * as a real asymmetry present in the origin itself, not silently smoothed
27
+ * over — the same asymmetry already exists for `openai.ts`/`grok.ts`'s own
28
+ * `entry.url` fallbacks, which are equally unguarded in the origin.
29
+ *
30
+ * A genuine origin quirk, preserved rather than "fixed": `openaiSizeFor`
31
+ * only special-cases `gpt-image-*`/`dall-e-3` catalog ids; every Volcengine
32
+ * catalog id (`doubao-seedream-*`/`doubao-seededit-*`) falls through to its
33
+ * default branch, so the request `size` is always `1024x1024` regardless of
34
+ * the requested aspect ratio. The origin's own inline comment flags this
35
+ * ("lefarcen + codex P2 on PR #1309") as a known review note rather than a
36
+ * silent bug, and calls `openaiSizeFor(ctx.model, ctx.aspect)` unchanged —
37
+ * this port keeps that exact call and behavior.
38
+ *
39
+ * 2026-07-21: migrated onto the generic vendor-adapter dispatch engine
40
+ * (`vendor-adapter.ts`/`vendor-registry.ts`). External behavior is
41
+ * unchanged (verified against `volcengine.test.ts`, which asserts
42
+ * URL/body/error-message/providerNote shape and passes unmodified) — only
43
+ * the internal implementation moved into a registered `VendorAdapter`. Not
44
+ * routed through `response-parsers.ts`'s factories — reuses
45
+ * `openai-compatible.ts`'s `parseOpenAICompatibleJson`/
46
+ * `bytesFromOpenAICompatibleData` helpers directly, same as before this
47
+ * pass (see `imagerouter.ts`'s module doc for the identical reasoning).
48
+ */
49
+ import { buildOpenAIImageUrl, bytesFromOpenAICompatibleData, openaiSizeFor, parseOpenAICompatibleJson, withRequestInit } from '../openai-compatible.js';
50
+ import { dispatchVendorRequest, requireApiKey } from '../vendor-adapter.js';
51
+ import { mediaVendorRegistry } from '../vendor-registry.js';
52
+ const NO_CREDENTIAL_MESSAGE = 'no Volcengine Ark credential — configure an API key or set ARK_API_KEY.';
53
+ const volcengineImageAdapter = {
54
+ requireCredential: requireApiKey(NO_CREDENTIAL_MESSAGE),
55
+ buildRequest(ctx, credentials) {
56
+ const apiKey = credentials.apiKey; // requireCredential already validated this.
57
+ const baseUrl = (credentials.baseUrl || 'https://ark.cn-beijing.volces.com/api/v3').replace(/\/$/, '');
58
+ const body = {
59
+ model: ctx.wireModel,
60
+ prompt: ctx.prompt || 'A high-quality reference image.',
61
+ response_format: 'b64_json',
62
+ // openaiSizeFor branches on the catalog id (gpt-image-* vs dall-e-*
63
+ // accept different size enums), so it must see the pre-alias catalog
64
+ // model, not the post-alias wire name — matching `openai.ts`'s own
65
+ // `renderOpenAIImage` (which carries the same origin-code comment).
66
+ size: openaiSizeFor(ctx.model, ctx.aspect),
67
+ };
68
+ return {
69
+ url: buildOpenAIImageUrl(baseUrl, false),
70
+ init: withRequestInit(ctx, {
71
+ method: 'POST',
72
+ headers: {
73
+ authorization: `Bearer ${apiKey}`,
74
+ 'content-type': 'application/json',
75
+ },
76
+ body: JSON.stringify(body),
77
+ }),
78
+ meta: undefined,
79
+ };
80
+ },
81
+ async parseResponse(resp, ctx) {
82
+ const data = await parseOpenAICompatibleJson(resp, 'volcengine image');
83
+ const bytes = await bytesFromOpenAICompatibleData(data, 'volcengine image', ctx.requestInit);
84
+ return {
85
+ bytes,
86
+ providerNote: `volcengine/${ctx.wireModel} · ${ctx.aspect} · ${bytes.length} bytes`,
87
+ suggestedExt: '.png',
88
+ };
89
+ },
90
+ };
91
+ mediaVendorRegistry.register('volcengine', 'image', volcengineImageAdapter);
92
+ export async function renderVolcengineImage(ctx, credentials) {
93
+ return dispatchVendorRequest(volcengineImageAdapter, ctx, credentials);
94
+ }
95
+ //# sourceMappingURL=volcengine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volcengine.js","sourceRoot":"","sources":["../../../../src/media-providers/dispatch/providers/volcengine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,aAAa,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAExJ,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,qBAAqB,GAAG,yEAAyE,CAAC;AAExG,MAAM,sBAAsB,GAAkB;IAC5C,iBAAiB,EAAE,aAAa,CAAC,qBAAqB,CAAC;IAEvD,YAAY,CAAC,GAAkB,EAAE,WAAgC;QAC/D,MAAM,MAAM,GAAG,WAAW,CAAC,MAAO,CAAC,CAAC,4CAA4C;QAChF,MAAM,OAAO,GAAG,CAAC,WAAW,CAAC,OAAO,IAAI,0CAA0C,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACvG,MAAM,IAAI,GAA4B;YACpC,KAAK,EAAE,GAAG,CAAC,SAAS;YACpB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,iCAAiC;YACvD,eAAe,EAAE,UAAU;YAC3B,oEAAoE;YACpE,qEAAqE;YACrE,mEAAmE;YACnE,oEAAoE;YACpE,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;SAC3C,CAAC;QAEF,OAAO;YACL,GAAG,EAAE,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC;YACxC,IAAI,EAAE,eAAe,CAAC,GAAG,EAAE;gBACzB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,MAAM,EAAE;oBACjC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC;YACF,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,IAAc,EAAE,GAAkB;QACpD,MAAM,IAAI,GAAG,MAAM,yBAAyB,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QACvE,MAAM,KAAK,GAAG,MAAM,6BAA6B,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;QAC7F,OAAO;YACL,KAAK;YACL,YAAY,EAAE,cAAc,GAAG,CAAC,SAAS,MAAM,GAAG,CAAC,MAAM,MAAM,KAAK,CAAC,MAAM,QAAQ;YACnF,YAAY,EAAE,MAAM;SACrB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,mBAAmB,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC;AAE5E,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAkB,EAAE,WAAgC;IAC9F,OAAO,qBAAqB,CAAC,sBAAsB,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { VendorResponseParser } from './vendor-adapter.js';
2
+ type Resolvable<T, Args extends readonly unknown[]> = T | ((...args: Args) => T);
3
+ export interface RawBytesParserOptions<Meta> {
4
+ /** Used in the `${tag} ${status}: ${truncated body}` message thrown on a non-OK response. A function receives the request's `meta` (e.g. so a dynamically-computed azure/non-azure tag survives the port — see `providers/openai.ts`). */
5
+ readonly errorTag: Resolvable<string, [meta: Meta]>;
6
+ /** Thrown verbatim when the response body decodes to zero bytes. */
7
+ readonly zeroBytesMessage: Resolvable<string, [meta: Meta]>;
8
+ /** Builds the successful `RenderResult.providerNote`. */
9
+ readonly note: (bytes: Buffer, meta: Meta) => string;
10
+ /** The successful `RenderResult.suggestedExt`. */
11
+ readonly suggestedExt: Resolvable<string, [bytes: Buffer, meta: Meta]>;
12
+ }
13
+ /**
14
+ * A `VendorResponseParser` for vendors that return raw bytes directly (no
15
+ * JSON envelope): non-OK -> read text, throw a tagged/truncated error;
16
+ * otherwise read the full body as bytes, throw on zero bytes, else build a
17
+ * `RenderResult` via `note`/`suggestedExt`. This is the exact control flow
18
+ * `fishaudio.ts`'s `renderFishAudioTTS` and `openai.ts`'s
19
+ * `renderOpenAISpeech` each independently hand-wrote before this pass.
20
+ */
21
+ export declare function createRawBytesParser<Meta = undefined>(options: RawBytesParserOptions<Meta>): VendorResponseParser<Meta>;
22
+ /** The `meta` shape `createHexEnvelopeAudioParser` needs to build a `providerNote` — the resolved wire model id and voice id, both already computed by the adapter's `buildRequest`. */
23
+ export interface HexEnvelopeAudioMeta {
24
+ readonly wireModel: string;
25
+ readonly voiceId: string;
26
+ }
27
+ export interface HexEnvelopeAudioParserOptions {
28
+ /** Used verbatim as the tag in every error this parser can throw (`${errorTag} ${status}: ...`, `${errorTag} non-JSON: ...`, `${errorTag} api error ...: ...`, `${errorTag} response missing data.audio`, `${errorTag} decoded zero bytes`) — e.g. `'minimax tts'` / `'senseaudio tts'`. */
29
+ readonly errorTag: string;
30
+ /** Used as the `providerNote`'s `<providerId>/<wireModel> · <voiceId> · <seconds>s · <bytes> bytes` prefix — e.g. `'minimax'` / `'senseaudio'`. */
31
+ readonly providerId: string;
32
+ }
33
+ /**
34
+ * A `VendorResponseParser` for vendors whose TTS response wraps hex-encoded
35
+ * audio inside a `base_resp`-enveloped JSON body (an HTTP 200 can still be
36
+ * a *logical* failure) — the exact shape MiniMax's and SenseAudio's TTS
37
+ * endpoints both independently return, verified identical (down to the
38
+ * literal error-message templates) by reading both origin renderers in
39
+ * full. `extra_info.audio_length` is centiseconds; `seconds` is `'?'` when
40
+ * absent, matching both vendors' own formatting.
41
+ */
42
+ export declare function createHexEnvelopeAudioParser<Meta extends HexEnvelopeAudioMeta>(options: HexEnvelopeAudioParserOptions): VendorResponseParser<Meta>;
43
+ export {};
44
+ //# sourceMappingURL=response-parsers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-parsers.d.ts","sourceRoot":"","sources":["../../../src/media-providers/dispatch/response-parsers.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,KAAK,UAAU,CAAC,CAAC,EAAE,IAAI,SAAS,SAAS,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC;AAMjF,MAAM,WAAW,qBAAqB,CAAC,IAAI;IACzC,0OAA0O;IAC1O,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACpD,oEAAoE;IACpE,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC5D,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC;IACrD,kDAAkD;IAClD,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;CACxE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAG,SAAS,EAAE,OAAO,EAAE,qBAAqB,CAAC,IAAI,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAiBvH;AAED,wLAAwL;AACxL,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAQD,MAAM,WAAW,6BAA6B;IAC5C,4RAA4R;IAC5R,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mJAAmJ;IACnJ,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,SAAS,oBAAoB,EAC5E,OAAO,EAAE,6BAA6B,GACrC,oBAAoB,CAAC,IAAI,CAAC,CA+B5B"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Reusable `VendorResponseParser` factories for the two response shapes
3
+ * that genuinely repeat, byte-for-byte, across multiple already-ported
4
+ * vendors (verified by reading their real bodies — see `source-map.md`'s
5
+ * 2026-07-21 dispatch-engine-generalization section, not assumed from
6
+ * their doc comments alone):
7
+ *
8
+ * - `createRawBytesParser` — "POST JSON, get raw audio/image bytes back
9
+ * directly (no envelope)" — used today by `providers/fishaudio.ts`'s
10
+ * `renderFishAudioTTS` and `providers/openai.ts`'s `renderOpenAISpeech`.
11
+ * xAI TTS (`grok.ts`'s `renderXAITTS`) and ElevenLabs's two renderers
12
+ * have the identical shape and are natural future migrations onto this
13
+ * same parser, not yet done this pass (see source-map.md for why).
14
+ * - `createHexEnvelopeAudioParser` — "POST JSON, get hex-encoded audio back
15
+ * inside a `base_resp`-enveloped JSON body" — used today by
16
+ * `providers/minimax.ts` and `providers/senseaudio.ts`'s TTS renderers,
17
+ * which were verified (by reading both files in full) to share this
18
+ * exact structure down to the literal error-message templates, modulo a
19
+ * provider-tag string.
20
+ *
21
+ * Not every vendor's response shape fits either factory (e.g. OpenAI's own
22
+ * image response, whose non-JSON error message is NOT azure-tag-aware
23
+ * while its non-ok error message IS — see `providers/openai.ts`'s
24
+ * `parseOpenAIImageResponse` — or SenseAudio's image response, which needs
25
+ * a second SSRF-guarded fetch mid-parse). Those stay custom
26
+ * `VendorResponseParser` functions, which is the expected, legitimate
27
+ * outcome for a vendor whose wire shape doesn't actually match another
28
+ * vendor's — see this package's `vendor-adapter.ts` module doc for why
29
+ * `parseResponse` is a function, not a further declarative shape.
30
+ */
31
+ import { truncate } from './openai-compatible.js';
32
+ function resolve(value, args) {
33
+ return typeof value === 'function' ? value(...args) : value;
34
+ }
35
+ /**
36
+ * A `VendorResponseParser` for vendors that return raw bytes directly (no
37
+ * JSON envelope): non-OK -> read text, throw a tagged/truncated error;
38
+ * otherwise read the full body as bytes, throw on zero bytes, else build a
39
+ * `RenderResult` via `note`/`suggestedExt`. This is the exact control flow
40
+ * `fishaudio.ts`'s `renderFishAudioTTS` and `openai.ts`'s
41
+ * `renderOpenAISpeech` each independently hand-wrote before this pass.
42
+ */
43
+ export function createRawBytesParser(options) {
44
+ return async (resp, _ctx, request) => {
45
+ if (!resp.ok) {
46
+ const text = await resp.text();
47
+ const tag = resolve(options.errorTag, [request.meta]);
48
+ throw new Error(`${tag} ${resp.status}: ${truncate(text, 240)}`);
49
+ }
50
+ const bytes = Buffer.from(await resp.arrayBuffer());
51
+ if (bytes.length === 0) {
52
+ throw new Error(resolve(options.zeroBytesMessage, [request.meta]));
53
+ }
54
+ return {
55
+ bytes,
56
+ providerNote: options.note(bytes, request.meta),
57
+ suggestedExt: resolve(options.suggestedExt, [bytes, request.meta]),
58
+ };
59
+ };
60
+ }
61
+ /**
62
+ * A `VendorResponseParser` for vendors whose TTS response wraps hex-encoded
63
+ * audio inside a `base_resp`-enveloped JSON body (an HTTP 200 can still be
64
+ * a *logical* failure) — the exact shape MiniMax's and SenseAudio's TTS
65
+ * endpoints both independently return, verified identical (down to the
66
+ * literal error-message templates) by reading both origin renderers in
67
+ * full. `extra_info.audio_length` is centiseconds; `seconds` is `'?'` when
68
+ * absent, matching both vendors' own formatting.
69
+ */
70
+ export function createHexEnvelopeAudioParser(options) {
71
+ return async (resp, _ctx, request) => {
72
+ const respText = await resp.text();
73
+ if (!resp.ok) {
74
+ throw new Error(`${options.errorTag} ${resp.status}: ${truncate(respText, 240)}`);
75
+ }
76
+ let data;
77
+ try {
78
+ data = JSON.parse(respText);
79
+ }
80
+ catch {
81
+ throw new Error(`${options.errorTag} non-JSON: ${truncate(respText, 200)}`);
82
+ }
83
+ if (data.base_resp && data.base_resp.status_code !== 0) {
84
+ throw new Error(`${options.errorTag} api error ${data.base_resp.status_code}: ${data.base_resp.status_msg || 'unknown'}`);
85
+ }
86
+ const hex = data.data?.audio;
87
+ if (typeof hex !== 'string' || !hex) {
88
+ throw new Error(`${options.errorTag} response missing data.audio`);
89
+ }
90
+ const bytes = Buffer.from(hex, 'hex');
91
+ if (bytes.length === 0) {
92
+ throw new Error(`${options.errorTag} decoded zero bytes`);
93
+ }
94
+ const audioLength = data.extra_info?.audio_length;
95
+ const seconds = audioLength ? Math.round(audioLength / 100) / 10 : '?';
96
+ return {
97
+ bytes,
98
+ providerNote: `${options.providerId}/${request.meta.wireModel} · ${request.meta.voiceId} · ${seconds}s · ${bytes.length} bytes`,
99
+ suggestedExt: '.mp3',
100
+ };
101
+ };
102
+ }
103
+ //# sourceMappingURL=response-parsers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-parsers.js","sourceRoot":"","sources":["../../../src/media-providers/dispatch/response-parsers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ export interface DnsLookupAddress {
2
+ readonly address: string;
3
+ readonly family: number;
4
+ }
5
+ export type DnsLookupFn = (hostname: string) => Promise<readonly DnsLookupAddress[]>;
6
+ /** `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 equivalents — the carve-out for local providers (e.g. Ollama), consulted before the blocklist so loopback is never rejected as "internal". */
7
+ export declare function isLoopbackApiHost(hostname: string): boolean;
8
+ /** The unspecified address, RFC1918/CGNAT/link-local/multicast IPv4 (via `isBlockedIpv4`), unique-local (`fc00::/7`) and link-local (`fe80::/10`) IPv6, and their IPv4-mapped-IPv6 equivalents. */
9
+ export declare function isBlockedExternalApiHostname(hostname: string): boolean;
10
+ /**
11
+ * DNS-aware companion to the sync hostname check: resolves the hostname and
12
+ * re-runs the block-list against every address it actually resolves to, so
13
+ * a public DNS name pointing at internal infrastructure
14
+ * (`internal.example.com` -> `10.0.0.5`) can't slip through a hostname-only
15
+ * check. DNS lookup failures are not treated as a security signal — a
16
+ * transient resolver hiccup shouldn't turn into a false "blocked" result
17
+ * when `fetch` is about to surface its own connection error anyway; the
18
+ * sync check above already rejected the obvious literal-IP cases.
19
+ */
20
+ export declare function validateBaseUrlResolved(baseUrl: string, lookup?: DnsLookupFn): Promise<{
21
+ ok: true;
22
+ } | {
23
+ ok: false;
24
+ error: string;
25
+ forbidden: boolean;
26
+ }>;
27
+ /**
28
+ * SSRF guard for asset URLs handed back inside a successful API response —
29
+ * typically a `data.url`/`data.video_url` pointing at the gateway's CDN,
30
+ * attacker-controllable when the upstream gateway is compromised or
31
+ * misconfigured. Returns a discriminated union so callers don't have to
32
+ * repeat the resolved-validation plumbing.
33
+ */
34
+ export declare function assertExternalAssetUrl(rawUrl: string, lookup?: DnsLookupFn): Promise<{
35
+ ok: true;
36
+ } | {
37
+ ok: false;
38
+ error: string;
39
+ }>;
40
+ /**
41
+ * Validates an upstream-controlled asset URL and fetches it with the SSRF
42
+ * guard pinned through redirects: runs `assertExternalAssetUrl` on the
43
+ * literal URL, then forces `redirect: 'error'` so a validated public URL
44
+ * that 302s into loopback/RFC1918/metadata space is rejected before any
45
+ * bytes are read. Throws on a blocked host (so the redirect-bypass can't be
46
+ * forgotten at a call site); the forced `redirect` is spread last so it
47
+ * overrides any value the caller passed in `init`. Callers keep their own
48
+ * `!resp.ok` HTTP-status handling.
49
+ */
50
+ export declare function assertAndFetchExternalAsset(url: string, init?: RequestInit, lookup?: DnsLookupFn): Promise<Response>;
51
+ //# sourceMappingURL=ssrf-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssrf-guard.d.ts","sourceRoot":"","sources":["../../../src/media-providers/dispatch/ssrf-guard.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAAC;AA4DrF,+MAA+M;AAC/M,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED,mMAAmM;AACnM,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQtE;AA4BD;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,WAA8B,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAyB/K;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,WAA8B,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAYzJ;AAED;;;;;;;;;GASG;AACH,wBAAsB,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,EAAE,MAAM,GAAE,WAA8B,GAAG,OAAO,CAAC,QAAQ,CAAC,CAKhJ"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * SSRF guard for asset URLs returned *inside* a provider's successful
3
+ * response body (e.g. SenseAudio image's `url`, AIHubMix image's
4
+ * `data[].url` fallback) — these are attacker-controllable whenever the
5
+ * upstream gateway is compromised or misconfigured, unlike a caller-
6
+ * configured `baseUrl`, which a host operator chose deliberately. Ported
7
+ * near-verbatim from Open Design's `apps/daemon/src/connectionTest.ts`
8
+ * (`assertAndFetchExternalAsset`/`assertExternalAssetUrl`/
9
+ * `validateBaseUrlResolved`) plus the sync hostname classifiers from
10
+ * `packages/contracts/src/api/connectionTest.ts` (`isLoopbackApiHost`/
11
+ * `isBlockedExternalApiHostname`/`validateBaseUrl`) — see `source-map.md`.
12
+ *
13
+ * Scope note: the origin's `validateBaseUrlResolved` also accepts an
14
+ * operator-declared `allowedInternalHosts` allowlist
15
+ * (`ValidateBaseUrlOptions`/`isAllowlistedInternalHost`, fed from an
16
+ * `OD_ALLOWED_INTERNAL_HOSTS`-style env var) — but that allowlist is used
17
+ * ONLY by a *different* exported function, `validateUserProviderBaseUrl`,
18
+ * for base URLs a host operator deliberately configured. The origin's own
19
+ * doc comment on `validateUserProviderBaseUrl` states the allowlist is for
20
+ * "user-configured endpoints" and warns it must never reach "the
21
+ * attacker-controllable asset-download SSRF guard" — i.e. never this
22
+ * module's call path. Proof, not assumption: `assertExternalAssetUrl` calls
23
+ * `validateBaseUrlResolved(rawUrl)` with no options argument, so
24
+ * `allowedInternalHosts` is always `undefined` here, and
25
+ * `isAllowlistedInternalHost` returns `false` unconditionally whenever its
26
+ * `allowedInternalHosts` argument is empty/absent (its own first line is
27
+ * `if (!allowedInternalHosts || allowedInternalHosts.length === 0) return
28
+ * false;`) — so the allowlist branch is provably dead code on this call
29
+ * path and is not ported. This module implements only the strict,
30
+ * non-configurable variant the asset-download path actually uses; a host
31
+ * that wants an operator-configurable internal-host allowlist for its own
32
+ * *user-supplied* base URLs (a different feature, not exercised by any
33
+ * vendor ported into this package) would need to build that separately.
34
+ */
35
+ import { promises as dnsPromises } from 'node:dns';
36
+ import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform';
37
+ const defaultDnsLookup = async (hostname) => {
38
+ const result = await dnsPromises.lookup(hostname, { all: true, family: 0 });
39
+ return result.map(({ address, family }) => ({ address, family }));
40
+ };
41
+ /** Strips IPv6 brackets, lowercases, and drops a trailing-dot FQDN suffix (RFC 1034) so `Localhost.`/`[::1]` compare equal to their canonical forms. */
42
+ function normalizeBracketedIpv6(hostname) {
43
+ const stripped = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
44
+ return stripped.toLowerCase().replace(/\.+$/, '');
45
+ }
46
+ function parseIpv4(hostname) {
47
+ const parts = hostname.split('.');
48
+ if (parts.length !== 4)
49
+ return null;
50
+ const parsed = parts.map((part) => {
51
+ if (!/^\d{1,3}$/.test(part))
52
+ return null;
53
+ const value = Number(part);
54
+ return value >= 0 && value <= 255 ? value : null;
55
+ });
56
+ if (parsed.some((part) => part === null))
57
+ return null;
58
+ return parsed;
59
+ }
60
+ function isLoopbackIpv4(hostname) {
61
+ const parts = parseIpv4(hostname);
62
+ return Boolean(parts && parts[0] === 127);
63
+ }
64
+ /** `0.0.0.0`, CGNAT (100.64/10), link-local (169.254/16), RFC1918 private ranges, and multicast/reserved (>= 224.0.0.0). */
65
+ function isBlockedIpv4(hostname) {
66
+ const parts = parseIpv4(hostname);
67
+ if (!parts)
68
+ return false;
69
+ const [a, b] = parts;
70
+ return a === 0 || (a === 100 && b >= 64 && b <= 127) || (a === 169 && b === 254) || a === 10 || (a === 192 && b === 168) || (a === 172 && b >= 16 && b <= 31) || a >= 224;
71
+ }
72
+ /** Recovers the dotted-quad IPv4 address from an IPv4-mapped IPv6 literal (`::ffff:a.b.c.d` or `::ffff:HHHH:HHHH`), or `null` if `hostname` isn't one. */
73
+ function ipv4MappedToDotted(hostname) {
74
+ const host = normalizeBracketedIpv6(hostname);
75
+ const mapped = /^::ffff:(.+)$/i.exec(host)?.[1];
76
+ if (!mapped)
77
+ return null;
78
+ if (parseIpv4(mapped.toLowerCase()))
79
+ return mapped.toLowerCase();
80
+ const hexParts = mapped.split(':');
81
+ if (hexParts.length !== 2 || !hexParts.every((part) => /^[0-9a-f]{1,4}$/i.test(part))) {
82
+ return null;
83
+ }
84
+ // hexParts.length === 2 is already proven above, and the `.every(...)`
85
+ // regex above already requires each element to be a non-empty 1-4-digit
86
+ // hex string — so hexParts[0]/[1] can never be `undefined` here despite
87
+ // noUncheckedIndexedAccess's static `string | undefined` type. Asserted
88
+ // rather than re-guarded with a dead runtime check, matching this
89
+ // package's established convention (see `engine.ts`'s `allowed[0]!`).
90
+ const hi = hexParts[0];
91
+ const lo = hexParts[1];
92
+ const value = (Number.parseInt(hi, 16) << 16) | Number.parseInt(lo, 16);
93
+ return [(value >>> 24) & 255, (value >>> 16) & 255, (value >>> 8) & 255, value & 255].join('.');
94
+ }
95
+ /** `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 equivalents — the carve-out for local providers (e.g. Ollama), consulted before the blocklist so loopback is never rejected as "internal". */
96
+ export function isLoopbackApiHost(hostname) {
97
+ const host = normalizeBracketedIpv6(hostname);
98
+ if (host === 'localhost' || host === '::1')
99
+ return true;
100
+ if (isLoopbackIpv4(host))
101
+ return true;
102
+ const mapped = ipv4MappedToDotted(host);
103
+ return Boolean(mapped && isLoopbackIpv4(mapped));
104
+ }
105
+ /** The unspecified address, RFC1918/CGNAT/link-local/multicast IPv4 (via `isBlockedIpv4`), unique-local (`fc00::/7`) and link-local (`fe80::/10`) IPv6, and their IPv4-mapped-IPv6 equivalents. */
106
+ export function isBlockedExternalApiHostname(hostname) {
107
+ const host = normalizeBracketedIpv6(hostname);
108
+ if (host === '::')
109
+ return true;
110
+ if (isBlockedIpv4(host))
111
+ return true;
112
+ if (/^f[cd][0-9a-f]{2}:/i.test(host))
113
+ return true;
114
+ if (/^fe[89ab][0-9a-f]:/i.test(host))
115
+ return true;
116
+ const mapped = ipv4MappedToDotted(host);
117
+ return Boolean(mapped && isBlockedIpv4(mapped));
118
+ }
119
+ /** Whether `hostname` is already a literal IPv4/IPv6 address (as opposed to a DNS name) — skips the DNS-resolve step in `validateBaseUrlResolved` since there's nothing further to resolve. */
120
+ function looksLikeIpLiteral(hostname) {
121
+ const host = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
122
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host))
123
+ return true;
124
+ return host.includes(':');
125
+ }
126
+ function validateBaseUrlSync(baseUrl) {
127
+ let parsed;
128
+ try {
129
+ parsed = new URL(String(baseUrl).replace(/\/+$/, ''));
130
+ }
131
+ catch {
132
+ return { ok: false, error: 'Invalid baseUrl', forbidden: false };
133
+ }
134
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
135
+ return { ok: false, error: 'Only http/https allowed', forbidden: false };
136
+ }
137
+ const hostname = parsed.hostname.toLowerCase();
138
+ if (!isLoopbackApiHost(hostname) && isBlockedExternalApiHostname(hostname)) {
139
+ return { ok: false, error: 'Internal IPs blocked', forbidden: true };
140
+ }
141
+ return { ok: true, parsed };
142
+ }
143
+ /**
144
+ * DNS-aware companion to the sync hostname check: resolves the hostname and
145
+ * re-runs the block-list against every address it actually resolves to, so
146
+ * a public DNS name pointing at internal infrastructure
147
+ * (`internal.example.com` -> `10.0.0.5`) can't slip through a hostname-only
148
+ * check. DNS lookup failures are not treated as a security signal — a
149
+ * transient resolver hiccup shouldn't turn into a false "blocked" result
150
+ * when `fetch` is about to surface its own connection error anyway; the
151
+ * sync check above already rejected the obvious literal-IP cases.
152
+ */
153
+ export async function validateBaseUrlResolved(baseUrl, lookup = defaultDnsLookup) {
154
+ const sync = validateBaseUrlSync(baseUrl);
155
+ if (!sync.ok)
156
+ return sync;
157
+ const hostname = sync.parsed.hostname.toLowerCase();
158
+ if (isLoopbackApiHost(hostname) || looksLikeIpLiteral(hostname)) {
159
+ return { ok: true };
160
+ }
161
+ let addresses;
162
+ try {
163
+ addresses = await lookup(hostname);
164
+ }
165
+ catch {
166
+ return { ok: true };
167
+ }
168
+ for (const addr of addresses) {
169
+ const ip = String(addr.address).toLowerCase();
170
+ if (isLoopbackApiHost(ip))
171
+ continue;
172
+ if (isBlockedExternalApiHostname(ip)) {
173
+ return { ok: false, error: 'Internal IPs blocked', forbidden: true };
174
+ }
175
+ }
176
+ return { ok: true };
177
+ }
178
+ /**
179
+ * SSRF guard for asset URLs handed back inside a successful API response —
180
+ * typically a `data.url`/`data.video_url` pointing at the gateway's CDN,
181
+ * attacker-controllable when the upstream gateway is compromised or
182
+ * misconfigured. Returns a discriminated union so callers don't have to
183
+ * repeat the resolved-validation plumbing.
184
+ */
185
+ export async function assertExternalAssetUrl(rawUrl, lookup = defaultDnsLookup) {
186
+ if (typeof rawUrl !== 'string' || !rawUrl) {
187
+ return { ok: false, error: 'empty download url' };
188
+ }
189
+ const validated = await validateBaseUrlResolved(rawUrl, lookup);
190
+ if (!validated.ok) {
191
+ return {
192
+ ok: false,
193
+ error: validated.forbidden ? `blocked download url (${validated.error})` : `invalid download url: ${validated.error}`,
194
+ };
195
+ }
196
+ return { ok: true };
197
+ }
198
+ /**
199
+ * Validates an upstream-controlled asset URL and fetches it with the SSRF
200
+ * guard pinned through redirects: runs `assertExternalAssetUrl` on the
201
+ * literal URL, then forces `redirect: 'error'` so a validated public URL
202
+ * that 302s into loopback/RFC1918/metadata space is rejected before any
203
+ * bytes are read. Throws on a blocked host (so the redirect-bypass can't be
204
+ * forgotten at a call site); the forced `redirect` is spread last so it
205
+ * overrides any value the caller passed in `init`. Callers keep their own
206
+ * `!resp.ok` HTTP-status handling.
207
+ */
208
+ export async function assertAndFetchExternalAsset(url, init = {}, lookup = defaultDnsLookup) {
209
+ const check = await assertExternalAssetUrl(url, lookup);
210
+ if (!check.ok)
211
+ throw new Error(check.error);
212
+ // Downloading an already-generated asset, not waiting on generation — UPLOAD-class (2min).
213
+ return fetchWithTimeout(url, { ...init, redirect: 'error' }, { timeoutMs: FETCH_TIMEOUT_MS.UPLOAD });
214
+ }
215
+ //# sourceMappingURL=ssrf-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssrf-guard.js","sourceRoot":"","sources":["../../../src/media-providers/dispatch/ssrf-guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { RenderContext, RenderResult } from './types.js';
2
+ export declare function renderStub(ctx: RenderContext, providerId: string, integrated: boolean): Promise<RenderResult>;
3
+ /** Renders a labelled placeholder SVG for the image surface (an explicit opt-in alternative to the PNG placeholder). */
4
+ export declare function svgPlaceholder(ctx: RenderContext): string;
5
+ //# sourceMappingURL=stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../../src/media-providers/dispatch/stub.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9D,wBAAsB,UAAU,CAAC,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAyCnH;AAED,wHAAwH;AACxH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAazD"}