@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.
- package/LICENSE +201 -0
- package/README.md +51 -0
- package/dist/composio/bounded-data.d.ts +36 -0
- package/dist/composio/bounded-data.d.ts.map +1 -0
- package/dist/composio/bounded-data.js +181 -0
- package/dist/composio/bounded-data.js.map +1 -0
- package/dist/composio/catalog.d.ts +147 -0
- package/dist/composio/catalog.d.ts.map +1 -0
- package/dist/composio/catalog.js +126 -0
- package/dist/composio/catalog.js.map +1 -0
- package/dist/composio/composio-config.d.ts +42 -0
- package/dist/composio/composio-config.d.ts.map +1 -0
- package/dist/composio/composio-config.js +145 -0
- package/dist/composio/composio-config.js.map +1 -0
- package/dist/composio/composio-descriptions.d.ts +16 -0
- package/dist/composio/composio-descriptions.d.ts.map +1 -0
- package/dist/composio/composio-descriptions.js +766 -0
- package/dist/composio/composio-descriptions.js.map +1 -0
- package/dist/composio/composio.d.ts +364 -0
- package/dist/composio/composio.d.ts.map +1 -0
- package/dist/composio/composio.js +1902 -0
- package/dist/composio/composio.js.map +1 -0
- package/dist/composio/errors.d.ts +15 -0
- package/dist/composio/errors.d.ts.map +1 -0
- package/dist/composio/errors.js +16 -0
- package/dist/composio/errors.js.map +1 -0
- package/dist/composio/file-lock.d.ts +13 -0
- package/dist/composio/file-lock.d.ts.map +1 -0
- package/dist/composio/file-lock.js +92 -0
- package/dist/composio/file-lock.js.map +1 -0
- package/dist/composio/index.d.ts +18 -0
- package/dist/composio/index.d.ts.map +1 -0
- package/dist/composio/index.js +7 -0
- package/dist/composio/index.js.map +1 -0
- package/dist/composio/json-schema.d.ts +93 -0
- package/dist/composio/json-schema.d.ts.map +1 -0
- package/dist/composio/json-schema.js +498 -0
- package/dist/composio/json-schema.js.map +1 -0
- package/dist/composio/output-protection.d.ts +32 -0
- package/dist/composio/output-protection.d.ts.map +1 -0
- package/dist/composio/output-protection.js +105 -0
- package/dist/composio/output-protection.js.map +1 -0
- package/dist/composio/service.d.ts +181 -0
- package/dist/composio/service.d.ts.map +1 -0
- package/dist/composio/service.js +758 -0
- package/dist/composio/service.js.map +1 -0
- package/dist/media-providers/capability-registry.d.ts +28 -0
- package/dist/media-providers/capability-registry.d.ts.map +1 -0
- package/dist/media-providers/capability-registry.js +27 -0
- package/dist/media-providers/capability-registry.js.map +1 -0
- package/dist/media-providers/catalog.d.ts +20 -0
- package/dist/media-providers/catalog.d.ts.map +1 -0
- package/dist/media-providers/catalog.js +20 -0
- package/dist/media-providers/catalog.js.map +1 -0
- package/dist/media-providers/dispatch/context.d.ts +21 -0
- package/dist/media-providers/dispatch/context.d.ts.map +1 -0
- package/dist/media-providers/dispatch/context.js +36 -0
- package/dist/media-providers/dispatch/context.js.map +1 -0
- package/dist/media-providers/dispatch/credentials.d.ts +4 -0
- package/dist/media-providers/dispatch/credentials.d.ts.map +1 -0
- package/dist/media-providers/dispatch/credentials.js +25 -0
- package/dist/media-providers/dispatch/credentials.js.map +1 -0
- package/dist/media-providers/dispatch/engine.d.ts +30 -0
- package/dist/media-providers/dispatch/engine.d.ts.map +1 -0
- package/dist/media-providers/dispatch/engine.js +208 -0
- package/dist/media-providers/dispatch/engine.js.map +1 -0
- package/dist/media-providers/dispatch/index.d.ts +25 -0
- package/dist/media-providers/dispatch/index.d.ts.map +1 -0
- package/dist/media-providers/dispatch/index.js +24 -0
- package/dist/media-providers/dispatch/index.js.map +1 -0
- package/dist/media-providers/dispatch/openai-compatible.d.ts +44 -0
- package/dist/media-providers/dispatch/openai-compatible.d.ts.map +1 -0
- package/dist/media-providers/dispatch/openai-compatible.js +214 -0
- package/dist/media-providers/dispatch/openai-compatible.js.map +1 -0
- package/dist/media-providers/dispatch/providers/aihubmix-shared.d.ts +72 -0
- package/dist/media-providers/dispatch/providers/aihubmix-shared.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/aihubmix-shared.js +131 -0
- package/dist/media-providers/dispatch/providers/aihubmix-shared.js.map +1 -0
- package/dist/media-providers/dispatch/providers/aihubmix.d.ts +4 -0
- package/dist/media-providers/dispatch/providers/aihubmix.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/aihubmix.js +205 -0
- package/dist/media-providers/dispatch/providers/aihubmix.js.map +1 -0
- package/dist/media-providers/dispatch/providers/custom-image.d.ts +6 -0
- package/dist/media-providers/dispatch/providers/custom-image.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/custom-image.js +94 -0
- package/dist/media-providers/dispatch/providers/custom-image.js.map +1 -0
- package/dist/media-providers/dispatch/providers/elevenlabs.d.ts +4 -0
- package/dist/media-providers/dispatch/providers/elevenlabs.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/elevenlabs.js +163 -0
- package/dist/media-providers/dispatch/providers/elevenlabs.js.map +1 -0
- package/dist/media-providers/dispatch/providers/fishaudio.d.ts +3 -0
- package/dist/media-providers/dispatch/providers/fishaudio.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/fishaudio.js +83 -0
- package/dist/media-providers/dispatch/providers/fishaudio.js.map +1 -0
- package/dist/media-providers/dispatch/providers/grok.d.ts +15 -0
- package/dist/media-providers/dispatch/providers/grok.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/grok.js +150 -0
- package/dist/media-providers/dispatch/providers/grok.js.map +1 -0
- package/dist/media-providers/dispatch/providers/imagerouter.d.ts +6 -0
- package/dist/media-providers/dispatch/providers/imagerouter.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/imagerouter.js +131 -0
- package/dist/media-providers/dispatch/providers/imagerouter.js.map +1 -0
- package/dist/media-providers/dispatch/providers/minimax.d.ts +3 -0
- package/dist/media-providers/dispatch/providers/minimax.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/minimax.js +96 -0
- package/dist/media-providers/dispatch/providers/minimax.js.map +1 -0
- package/dist/media-providers/dispatch/providers/nanobanana.d.ts +3 -0
- package/dist/media-providers/dispatch/providers/nanobanana.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/nanobanana.js +142 -0
- package/dist/media-providers/dispatch/providers/nanobanana.js.map +1 -0
- package/dist/media-providers/dispatch/providers/openai.d.ts +4 -0
- package/dist/media-providers/dispatch/providers/openai.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/openai.js +231 -0
- package/dist/media-providers/dispatch/providers/openai.js.map +1 -0
- package/dist/media-providers/dispatch/providers/openrouter.d.ts +13 -0
- package/dist/media-providers/dispatch/providers/openrouter.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/openrouter.js +152 -0
- package/dist/media-providers/dispatch/providers/openrouter.js.map +1 -0
- package/dist/media-providers/dispatch/providers/senseaudio.d.ts +4 -0
- package/dist/media-providers/dispatch/providers/senseaudio.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/senseaudio.js +190 -0
- package/dist/media-providers/dispatch/providers/senseaudio.js.map +1 -0
- package/dist/media-providers/dispatch/providers/volcengine.d.ts +3 -0
- package/dist/media-providers/dispatch/providers/volcengine.d.ts.map +1 -0
- package/dist/media-providers/dispatch/providers/volcengine.js +95 -0
- package/dist/media-providers/dispatch/providers/volcengine.js.map +1 -0
- package/dist/media-providers/dispatch/response-parsers.d.ts +44 -0
- package/dist/media-providers/dispatch/response-parsers.d.ts.map +1 -0
- package/dist/media-providers/dispatch/response-parsers.js +103 -0
- package/dist/media-providers/dispatch/response-parsers.js.map +1 -0
- package/dist/media-providers/dispatch/ssrf-guard.d.ts +51 -0
- package/dist/media-providers/dispatch/ssrf-guard.d.ts.map +1 -0
- package/dist/media-providers/dispatch/ssrf-guard.js +215 -0
- package/dist/media-providers/dispatch/ssrf-guard.js.map +1 -0
- package/dist/media-providers/dispatch/stub.d.ts +5 -0
- package/dist/media-providers/dispatch/stub.d.ts.map +1 -0
- package/dist/media-providers/dispatch/stub.js +98 -0
- package/dist/media-providers/dispatch/stub.js.map +1 -0
- package/dist/media-providers/dispatch/types.d.ts +133 -0
- package/dist/media-providers/dispatch/types.d.ts.map +1 -0
- package/dist/media-providers/dispatch/types.js +2 -0
- package/dist/media-providers/dispatch/types.js.map +1 -0
- package/dist/media-providers/dispatch/vendor-adapter.d.ts +54 -0
- package/dist/media-providers/dispatch/vendor-adapter.d.ts.map +1 -0
- package/dist/media-providers/dispatch/vendor-adapter.js +68 -0
- package/dist/media-providers/dispatch/vendor-adapter.js.map +1 -0
- package/dist/media-providers/dispatch/vendor-registry.d.ts +36 -0
- package/dist/media-providers/dispatch/vendor-registry.d.ts.map +1 -0
- package/dist/media-providers/dispatch/vendor-registry.js +46 -0
- package/dist/media-providers/dispatch/vendor-registry.js.map +1 -0
- package/dist/media-providers/index.d.ts +22 -0
- package/dist/media-providers/index.d.ts.map +1 -0
- package/dist/media-providers/index.js +22 -0
- package/dist/media-providers/index.js.map +1 -0
- package/dist/media-providers/policy.d.ts +49 -0
- package/dist/media-providers/policy.d.ts.map +1 -0
- package/dist/media-providers/policy.js +39 -0
- package/dist/media-providers/policy.js.map +1 -0
- package/dist/media-providers/providers.d.ts +33 -0
- package/dist/media-providers/providers.d.ts.map +1 -0
- package/dist/media-providers/providers.js +200 -0
- package/dist/media-providers/providers.js.map +1 -0
- package/dist/media-providers/seed.d.ts +14 -0
- package/dist/media-providers/seed.d.ts.map +1 -0
- package/dist/media-providers/seed.js +109 -0
- package/dist/media-providers/seed.js.map +1 -0
- package/dist/media-providers/sqlite-task-store.d.ts +29 -0
- package/dist/media-providers/sqlite-task-store.d.ts.map +1 -0
- package/dist/media-providers/sqlite-task-store.js +234 -0
- package/dist/media-providers/sqlite-task-store.js.map +1 -0
- package/dist/media-providers/staging.d.ts +29 -0
- package/dist/media-providers/staging.d.ts.map +1 -0
- package/dist/media-providers/staging.js +141 -0
- package/dist/media-providers/staging.js.map +1 -0
- package/dist/media-providers/task-store.d.ts +94 -0
- package/dist/media-providers/task-store.d.ts.map +1 -0
- package/dist/media-providers/task-store.js +160 -0
- package/dist/media-providers/task-store.js.map +1 -0
- package/dist/media-providers/tokens.d.ts +7 -0
- package/dist/media-providers/tokens.d.ts.map +1 -0
- package/dist/media-providers/tokens.js +13 -0
- package/dist/media-providers/tokens.js.map +1 -0
- package/dist/media-providers/types.d.ts +123 -0
- package/dist/media-providers/types.d.ts.map +1 -0
- package/dist/media-providers/types.js +12 -0
- package/dist/media-providers/types.js.map +1 -0
- package/dist/media-providers/video-request.d.ts +62 -0
- package/dist/media-providers/video-request.d.ts.map +1 -0
- package/dist/media-providers/video-request.js +219 -0
- package/dist/media-providers/video-request.js.map +1 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAKlD,SAAS,OAAO,CAAqC,KAA0B,EAAE,IAAU;IACzF,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAE,KAA8B,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACxF,CAAC;AAaD;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAmB,OAAoC;IACzF,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QACnC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACtD,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACpD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrE,CAAC;QACD,OAAO;YACL,KAAK;YACL,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC;YAC/C,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;SACnE,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAqBD;;;;;;;;GAQG;AACH,MAAM,UAAU,4BAA4B,CAC1C,OAAsC;IAEtC,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QACnC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,IAA8B,CAAC;QACnC,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,cAAc,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,WAAW,KAAK,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,cAAc,IAAI,CAAC,SAAS,CAAC,WAAW,KAAK,IAAI,CAAC,SAAS,CAAC,UAAU,IAAI,SAAS,EAAE,CAAC,CAAC;QAC5H,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;QAC7B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,8BAA8B,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,qBAAqB,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC;QAClD,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACvE,OAAO;YACL,KAAK;YACL,YAAY,EAAE,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,OAAO,OAAO,KAAK,CAAC,MAAM,QAAQ;YAC/H,YAAY,EAAE,MAAM;SACrB,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -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"}
|