@hypit/hypit 0.2.3 → 0.2.4
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/package.json +5 -1
- package/packages/media-execution/src/execute.ts +3 -2
- package/packages/provider-hiapi/README.md +75 -0
- package/packages/provider-hiapi/package.json +24 -0
- package/packages/provider-hiapi/src/activation.ts +62 -0
- package/packages/provider-hiapi/src/errors.ts +44 -0
- package/packages/provider-hiapi/src/index.ts +2 -0
- package/packages/provider-hiapi/src/mapping.ts +129 -0
- package/packages/provider-hiapi/src/provider.ts +215 -0
- package/packages/provider-hiapi/src/routes.ts +154 -0
- package/packages/provider-monid/README.md +60 -0
- package/packages/provider-monid/package.json +19 -0
- package/packages/provider-monid/src/activation.ts +62 -0
- package/packages/provider-monid/src/errors.ts +55 -0
- package/packages/provider-monid/src/index.ts +2 -0
- package/packages/provider-monid/src/mapping.ts +33 -0
- package/packages/provider-monid/src/provider.ts +242 -0
- package/packages/provider-monid/src/routes.ts +103 -0
- package/packages/provider-pollo/README.md +59 -0
- package/packages/provider-pollo/package.json +22 -0
- package/packages/provider-pollo/src/activation.ts +62 -0
- package/packages/provider-pollo/src/errors.ts +41 -0
- package/packages/provider-pollo/src/index.ts +2 -0
- package/packages/provider-pollo/src/mapping.ts +60 -0
- package/packages/provider-pollo/src/provider.ts +194 -0
- package/packages/provider-pollo/src/routes.ts +120 -0
- package/packages/provider-tokendance/README.md +67 -0
- package/packages/provider-tokendance/package.json +21 -0
- package/packages/provider-tokendance/src/activation.ts +62 -0
- package/packages/provider-tokendance/src/errors.ts +47 -0
- package/packages/provider-tokendance/src/index.ts +2 -0
- package/packages/provider-tokendance/src/mapping.ts +60 -0
- package/packages/provider-tokendance/src/provider.ts +268 -0
- package/packages/provider-tokendance/src/routes.ts +192 -0
- package/packages/video-cli/package.json +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypit/hypit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"homepage": "https://hypit.ai",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -236,9 +236,13 @@
|
|
|
236
236
|
"@hypit/project-context-node": "workspace:*",
|
|
237
237
|
"@hypit/protocol": "workspace:*",
|
|
238
238
|
"@hypit/provider-hyperframes-local": "workspace:*",
|
|
239
|
+
"@hypit/provider-hiapi": "workspace:*",
|
|
239
240
|
"@hypit/provider-hypihub": "workspace:*",
|
|
240
241
|
"@hypit/provider-image-opencv-local": "workspace:*",
|
|
241
242
|
"@hypit/provider-media-local": "workspace:*",
|
|
243
|
+
"@hypit/provider-monid": "workspace:*",
|
|
244
|
+
"@hypit/provider-pollo": "workspace:*",
|
|
245
|
+
"@hypit/provider-tokendance": "workspace:*",
|
|
242
246
|
"@hypit/provider-whisperx-local": "workspace:*",
|
|
243
247
|
"@hypit/ranking": "workspace:*",
|
|
244
248
|
"@hypit/ranking-studio": "workspace:*",
|
|
@@ -689,7 +689,7 @@ export async function executeNormalizeMedia(
|
|
|
689
689
|
await runProcess({
|
|
690
690
|
executable: env.ffmpegPath,
|
|
691
691
|
argv: ["-y", ...visualInput, "-an", "-vf", filter,
|
|
692
|
-
"-frames:v", String(plan.frameCount), "-fps_mode", "cfr", ...encoderArgs, output],
|
|
692
|
+
"-frames:v", String(plan.frameCount), "-r", fps, "-fps_mode", "cfr", ...encoderArgs, output],
|
|
693
693
|
timeoutMs: env.processTimeoutMs,
|
|
694
694
|
maxStdoutBytes: 64 * 1024,
|
|
695
695
|
...(env.sharedLibraryPath === undefined ? {} : { sharedLibraryPath: env.sharedLibraryPath }),
|
|
@@ -951,7 +951,8 @@ export async function executeTransformMedia(
|
|
|
951
951
|
argv.push("-map", "0:v:0", "-an", "-vf", plan.videoFilters.join(","));
|
|
952
952
|
}
|
|
953
953
|
argv.push(
|
|
954
|
-
"-frames:v", String(plan.frameCount),
|
|
954
|
+
"-frames:v", String(plan.frameCount),
|
|
955
|
+
"-r", `${media.timeline.frameRate.numerator}/${media.timeline.frameRate.denominator}`, "-fps_mode", "cfr",
|
|
955
956
|
"-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p",
|
|
956
957
|
"-movflags", "+faststart", output,
|
|
957
958
|
);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# `@hypit/provider-hiapi`
|
|
2
|
+
|
|
3
|
+
Hypit Runtime Provider for a [HiAPI](https://www.hiapi.ai) account. Every capability submits one
|
|
4
|
+
task to `POST /v1/tasks` with the model's documented `input` fields, polls
|
|
5
|
+
`GET /v1/tasks/{taskId}` until the task is terminal, downloads `data.output[].url` and stores the
|
|
6
|
+
files in the current Build. Submission carries an `Idempotency-Key`, so a retried submission returns
|
|
7
|
+
the original task.
|
|
8
|
+
|
|
9
|
+
| Capability | HiAPI model |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `@hypit/seedance@1#seedance-2` | `seedance-2.0` |
|
|
12
|
+
| `@hypit/seedance@1#seedance-2-fast` | `seedance-2.0-fast` |
|
|
13
|
+
| `@hypit/seedance@1#seedance-2-mini` | `seedance-2.0-mini` |
|
|
14
|
+
| `@hypit/seedance@1#seedance-2.5` | `seedance-2.5/reference-to-video` with a reference video, `seedance-2.5/image-to-video` with frames, reference images or reference audio, otherwise `seedance-2.5/text-to-video` |
|
|
15
|
+
| `@hypit/seedream@1#seedream-5-lite` | `seedream-5.0-lite/image-to-image` with references, otherwise `seedream-5.0-lite/text-to-image` |
|
|
16
|
+
| `@hypit/minimax-h3@1#minimax-h3` | `minimax-h3` |
|
|
17
|
+
| `@hypit/gpt-image@1#gpt-image-2` | `gpt-image-2/image-to-image` with references, otherwise `gpt-image-2/text-to-image` |
|
|
18
|
+
| `@hypit/nano-banana@1#nano-banana-2` | `Nano-Banana-2` |
|
|
19
|
+
| `@hypit/nano-banana@1#nano-banana-pro` | `Nano-Banana-Pro` |
|
|
20
|
+
| `@hypit/grok-imagine@1#grok-imagine-video` | `grok-imagine/image-to-video` with images, otherwise `grok-imagine/text-to-video` |
|
|
21
|
+
| `@hypit/grok-imagine@1#grok-imagine-video-1.5-preview` | `grok-imagine-1.5/image-to-video` |
|
|
22
|
+
|
|
23
|
+
HiAPI's model index at `https://www.hiapi.ai/docs/models.json` lists further models and routes;
|
|
24
|
+
this Provider maps only the models the Distribution already describes, on each model's standard
|
|
25
|
+
route.
|
|
26
|
+
|
|
27
|
+
Service limits this Provider reports as unsupported before submitting:
|
|
28
|
+
|
|
29
|
+
- `seedance-2.0-mini` has no `web_search` field; `web-search="true"` is unsupported there.
|
|
30
|
+
- `seedance-2.5/image-to-video` takes only `aspect-ratio="adaptive"`; `text-to-video` and
|
|
31
|
+
`image-to-video` render 720p or 1080p, `reference-to-video` also 480p.
|
|
32
|
+
- Seedream 5.0 lite renders 2K (`quality="basic"`) or 4K (`quality="ultra"`); `high` (3K) is
|
|
33
|
+
unsupported. `output-format` and `nsfw-check` have no HiAPI field and are not sent.
|
|
34
|
+
- `minimax-h3` renders 2K only and accepts up to five reference images; requests send
|
|
35
|
+
`watermark: false`.
|
|
36
|
+
- GPT Image 2: `background` only at 1K; `auto` ratio only at 1K; 2K excludes `5:4`, `4:5`, `3:1`,
|
|
37
|
+
`1:3`, `9:21`; 4K excludes `1:1`, `3:1`, `1:3`, `9:21`; image-to-image takes up to six references.
|
|
38
|
+
- Grok Imagine renders 480p or 720p; `grok-imagine-1.5/image-to-video` animates exactly one image.
|
|
39
|
+
|
|
40
|
+
Seedance visual references may carry `person-reference`; the Provider accepts the declaration and
|
|
41
|
+
transmits nothing for it, since HiAPI has no field for it. Seedance rejects reference images and
|
|
42
|
+
videos that contain a real human face; HiAPI offers no way to register authorized portrait material,
|
|
43
|
+
so such a request fails with the service's moderation error.
|
|
44
|
+
|
|
45
|
+
Reference images and audio travel inline as `data:` URLs, which HiAPI documents for its Seedance
|
|
46
|
+
inputs, within the per-file sizes each model page states: 30 MB images and 15 MB audio for
|
|
47
|
+
`seedance-2.0-mini` and the Seedance 2.5 models (2.5 also caps combined images at 120 MB), 10 MB
|
|
48
|
+
images for `seedream-5.0-lite/image-to-image` and `grok-imagine/image-to-video`, 20 MB for
|
|
49
|
+
`grok-imagine-1.5/image-to-video`, 30 MB for `Nano-Banana-Pro`. The Provider checks these before
|
|
50
|
+
submitting. A reference video must be a public HTTPS URL; configure `publicAssetUrl` when embedding
|
|
51
|
+
the Provider, otherwise such a request fails before submission.
|
|
52
|
+
|
|
53
|
+
Runtime Profile example:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"endpoints": {
|
|
58
|
+
"hiapi.default": {
|
|
59
|
+
"use": "@hypit/provider-hiapi",
|
|
60
|
+
"pool": "hiapi.default",
|
|
61
|
+
"config": {
|
|
62
|
+
"apiKey": { "store": "platform", "key": "hiapi.api-key" },
|
|
63
|
+
"defaultConcurrency": 3,
|
|
64
|
+
"pollIntervalMs": 10000
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`baseUrl` defaults to `https://api.hiapi.ai`. Store the API key with
|
|
72
|
+
`hypit auth login hiapi.default --runtime hypit.runtime.json`. Optional `requestTimeoutMs`,
|
|
73
|
+
`operationTimeoutMs` and `actionLimits` bound single HTTP calls, the whole remote task and action
|
|
74
|
+
concurrency. HTTP failures keep HiAPI's `error_code` and message; failed tasks keep
|
|
75
|
+
`data.error.code` and message, with any signed URL in the message redacted.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hypit/provider-hiapi",
|
|
3
|
+
"version": "0.0.0-dev",
|
|
4
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": { ".": "./src/index.ts" },
|
|
8
|
+
"hypit": { "activation": "./src/activation.ts" },
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@hypit/endpoint-kit": "workspace:*",
|
|
11
|
+
"@hypit/generation": "workspace:*",
|
|
12
|
+
"@hypit/protocol": "workspace:*",
|
|
13
|
+
"@hypit/runtime": "workspace:*",
|
|
14
|
+
"@hypit/runtime-kit": "workspace:*"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@hypit/gpt-image": "workspace:*",
|
|
18
|
+
"@hypit/grok-imagine": "workspace:*",
|
|
19
|
+
"@hypit/minimax-h3": "workspace:*",
|
|
20
|
+
"@hypit/nano-banana": "workspace:*",
|
|
21
|
+
"@hypit/seedance": "workspace:*",
|
|
22
|
+
"@hypit/seedream": "workspace:*"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRuntimeEndpointAdapterFacet,
|
|
3
|
+
runtimeConfigCredentialRef,
|
|
4
|
+
runtimeConfigActionLimits,
|
|
5
|
+
runtimeConfigExact,
|
|
6
|
+
runtimeConfigObject,
|
|
7
|
+
runtimeConfigPositiveInteger,
|
|
8
|
+
runtimeConfigString,
|
|
9
|
+
} from "@hypit/runtime-kit";
|
|
10
|
+
|
|
11
|
+
import { createHiApiProvider } from "./provider.js";
|
|
12
|
+
|
|
13
|
+
const adapter = createRuntimeEndpointAdapterFacet({
|
|
14
|
+
use: "@hypit/provider-hiapi",
|
|
15
|
+
activate(context) {
|
|
16
|
+
if (context.pool === undefined) throw new Error("HiAPI Provider Pool is required");
|
|
17
|
+
const config = runtimeConfigObject(context.config, "HiAPI");
|
|
18
|
+
runtimeConfigExact(config, [
|
|
19
|
+
"baseUrl",
|
|
20
|
+
"apiKey",
|
|
21
|
+
"defaultConcurrency",
|
|
22
|
+
"actionLimits",
|
|
23
|
+
"pollIntervalMs",
|
|
24
|
+
"requestTimeoutMs",
|
|
25
|
+
"operationTimeoutMs",
|
|
26
|
+
], "HiAPI");
|
|
27
|
+
const baseUrl = runtimeConfigString(config.baseUrl, "HiAPI baseUrl");
|
|
28
|
+
if (baseUrl !== undefined) {
|
|
29
|
+
const url = new URL(baseUrl);
|
|
30
|
+
if (url.protocol !== "https:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1") {
|
|
31
|
+
throw new Error("HiAPI baseUrl must use HTTPS or loopback");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const apiKey = runtimeConfigCredentialRef(config.apiKey, "HiAPI apiKey");
|
|
35
|
+
if (apiKey === undefined) throw new Error("HiAPI apiKey CredentialRef is required");
|
|
36
|
+
const actionLimits = runtimeConfigActionLimits(config.actionLimits);
|
|
37
|
+
const defaultConcurrency = runtimeConfigPositiveInteger(config.defaultConcurrency, "HiAPI defaultConcurrency");
|
|
38
|
+
const pollIntervalMs = runtimeConfigPositiveInteger(config.pollIntervalMs, "HiAPI pollIntervalMs");
|
|
39
|
+
const requestTimeoutMs = runtimeConfigPositiveInteger(config.requestTimeoutMs, "HiAPI requestTimeoutMs");
|
|
40
|
+
const operationTimeoutMs = runtimeConfigPositiveInteger(config.operationTimeoutMs, "HiAPI operationTimeoutMs");
|
|
41
|
+
return {
|
|
42
|
+
endpoint: createHiApiProvider({
|
|
43
|
+
instance: context.instance,
|
|
44
|
+
pool: context.pool,
|
|
45
|
+
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
46
|
+
apiKey,
|
|
47
|
+
...(defaultConcurrency === undefined ? {} : { defaultConcurrency }),
|
|
48
|
+
...(actionLimits === undefined ? {} : { actionLimits }),
|
|
49
|
+
...(pollIntervalMs === undefined ? {} : { pollIntervalMs }),
|
|
50
|
+
...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
|
|
51
|
+
...(operationTimeoutMs === undefined ? {} : { operationTimeoutMs }),
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const hypitPackage = {
|
|
58
|
+
format: "hypit.node-package@1" as const,
|
|
59
|
+
hostFacets: [adapter],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default hypitPackage;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** HiAPI's `{ code, message, error_code }` envelope and task `error` object, kept at the service boundary. */
|
|
2
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
3
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
4
|
+
? value as Record<string, unknown> : undefined;
|
|
5
|
+
}
|
|
6
|
+
function text(value: unknown): string | undefined {
|
|
7
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Responses may mention a signed asset URL. Keep the reason, not its access capability.
|
|
11
|
+
export function safeHiApiReason(value: string): string {
|
|
12
|
+
return value.replace(/https?:\/\/\S+/giu, "[redacted-url]");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class HiApiServiceError extends Error {
|
|
16
|
+
constructor(readonly code: string, message: string) { super(message); }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class HiApiHttpError extends HiApiServiceError {
|
|
20
|
+
constructor(readonly status: number, response: { readonly headers: Headers }, bodyText: string,
|
|
21
|
+
request: { readonly method: string; readonly path: string; readonly model?: string }) {
|
|
22
|
+
let body: Record<string, unknown> | undefined;
|
|
23
|
+
try { body = record(JSON.parse(bodyText)); } catch { /* Non-JSON gateway failures still have HTTP evidence. */ }
|
|
24
|
+
const code = text(body?.error_code) ?? "HIAPI_HTTP_ERROR";
|
|
25
|
+
const reason = text(body?.message) ?? (body === undefined ? text(bodyText.slice(0, 2000)) : undefined);
|
|
26
|
+
const requestId = text(response.headers.get("x-request-id"));
|
|
27
|
+
const facts = [
|
|
28
|
+
`HiAPI HTTP ${status}`, code, `${request.method} ${request.path}`,
|
|
29
|
+
...(request.model === undefined ? [] : [`model=${request.model}`]),
|
|
30
|
+
...(requestId === undefined ? [] : [`request=${requestId}`]),
|
|
31
|
+
];
|
|
32
|
+
super(code, `${facts.join("; ")}${reason === undefined ? "" : `: ${safeHiApiReason(reason)}`}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A terminal `fail` task; `undefined` otherwise. */
|
|
37
|
+
export function hiApiTaskFailure(task: Record<string, unknown>, id: string): HiApiServiceError | undefined {
|
|
38
|
+
if (task.status !== "fail") return undefined;
|
|
39
|
+
const error = record(task.error);
|
|
40
|
+
const code = text(error?.code) ?? "HIAPI_TASK_FAILED";
|
|
41
|
+
const reason = text(error?.message);
|
|
42
|
+
return new HiApiServiceError(code,
|
|
43
|
+
`HiAPI task ${id} failed; ${code}${reason === undefined ? "" : `: ${safeHiApiReason(reason)}`}`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ModuleRef } from "@hypit/protocol";
|
|
2
|
+
import type { GenerationWireMapping } from "@hypit/generation";
|
|
3
|
+
|
|
4
|
+
const SEEDANCE: ModuleRef = { name: "@hypit/seedance", version: "1" };
|
|
5
|
+
const SEEDREAM: ModuleRef = { name: "@hypit/seedream", version: "1" };
|
|
6
|
+
const MINIMAX: ModuleRef = { name: "@hypit/minimax-h3", version: "1" };
|
|
7
|
+
const GPT_IMAGE: ModuleRef = { name: "@hypit/gpt-image", version: "1" };
|
|
8
|
+
const NANO_BANANA: ModuleRef = { name: "@hypit/nano-banana", version: "1" };
|
|
9
|
+
const GROK: ModuleRef = { name: "@hypit/grok-imagine", version: "1" };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* HiAPI model IDs and the `input` fields each documents. A model whose HiAPI ID depends on the
|
|
13
|
+
* request shape (text, frames or references) lists one route per shape; the first route whose
|
|
14
|
+
* ports are present wins. `personReference` is accepted on Seedance visual references and not
|
|
15
|
+
* transmitted: HiAPI has no field for it.
|
|
16
|
+
*/
|
|
17
|
+
const seedanceFields = {
|
|
18
|
+
prompt: { as: "value", field: "prompt" },
|
|
19
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
20
|
+
duration: { as: "value", field: "duration" },
|
|
21
|
+
resolution: { as: "value", field: "resolution" },
|
|
22
|
+
firstFrame: { as: "url", field: "first_frame_url", resourceFields: ["personReference"] },
|
|
23
|
+
lastFrame: { as: "url", field: "last_frame_url", resourceFields: ["personReference"] },
|
|
24
|
+
referenceImage: { as: "urlArray", field: "reference_image_urls", resourceFields: ["personReference"] },
|
|
25
|
+
referenceVideo: { as: "urlArray", field: "reference_video_urls", resourceFields: ["personReference"] },
|
|
26
|
+
referenceAudio: { as: "urlArray", field: "reference_audio_urls" },
|
|
27
|
+
generateAudio: { as: "value", field: "generate_audio" },
|
|
28
|
+
webSearch: { as: "value", field: "web_search" },
|
|
29
|
+
} as const satisfies GenerationWireMapping["fields"];
|
|
30
|
+
|
|
31
|
+
const seedance2 = (name: string, model: string): GenerationWireMapping => ({
|
|
32
|
+
capability: { module: SEEDANCE, name }, result: "video", routes: [{ model }], fields: seedanceFields,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const hiApiMappings: readonly GenerationWireMapping[] = [
|
|
36
|
+
seedance2("seedance-2", "seedance-2.0"),
|
|
37
|
+
seedance2("seedance-2-fast", "seedance-2.0-fast"),
|
|
38
|
+
seedance2("seedance-2-mini", "seedance-2.0-mini"),
|
|
39
|
+
{
|
|
40
|
+
capability: { module: SEEDANCE, name: "seedance-2.5" }, result: "video",
|
|
41
|
+
routes: [
|
|
42
|
+
{ model: "seedance-2.5/reference-to-video", whenPresent: ["referenceVideo"] },
|
|
43
|
+
{ model: "seedance-2.5/image-to-video", whenPresent: ["firstFrame"] },
|
|
44
|
+
{ model: "seedance-2.5/image-to-video", whenPresent: ["referenceImage"] },
|
|
45
|
+
{ model: "seedance-2.5/image-to-video", whenPresent: ["referenceAudio"] },
|
|
46
|
+
{ model: "seedance-2.5/text-to-video" },
|
|
47
|
+
],
|
|
48
|
+
fields: seedanceFields,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
capability: { module: SEEDREAM, name: "seedream-5-lite" }, result: "image",
|
|
52
|
+
routes: [
|
|
53
|
+
{ model: "seedream-5.0-lite/image-to-image", whenPresent: ["images"] },
|
|
54
|
+
{ model: "seedream-5.0-lite/text-to-image" },
|
|
55
|
+
],
|
|
56
|
+
fields: {
|
|
57
|
+
prompt: { as: "value", field: "prompt" },
|
|
58
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
59
|
+
quality: { as: "value", field: "quality" },
|
|
60
|
+
outputFormat: { as: "value", field: "output_format" },
|
|
61
|
+
nsfwCheck: { as: "value", field: "nsfw_check" },
|
|
62
|
+
images: { as: "urlArray", field: "image_urls" },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
capability: { module: MINIMAX, name: "minimax-h3" }, result: "video", routes: [{ model: "minimax-h3" }],
|
|
67
|
+
constants: { watermark: false },
|
|
68
|
+
fields: {
|
|
69
|
+
prompt: { as: "value", field: "prompt" },
|
|
70
|
+
duration: { as: "value", field: "duration" },
|
|
71
|
+
resolution: { as: "value", field: "resolution" },
|
|
72
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
73
|
+
firstFrame: { as: "url", field: "first_frame_image" },
|
|
74
|
+
lastFrame: { as: "url", field: "last_frame_image" },
|
|
75
|
+
referenceImage: { as: "urlArray", field: "image_urls" },
|
|
76
|
+
referenceVideo: { as: "urlArray", field: "video_urls" },
|
|
77
|
+
referenceAudio: { as: "urlArray", field: "audio_urls" },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
capability: { module: GPT_IMAGE, name: "gpt-image-2" }, result: "image",
|
|
82
|
+
routes: [
|
|
83
|
+
{ model: "gpt-image-2/image-to-image", whenPresent: ["images"] },
|
|
84
|
+
{ model: "gpt-image-2/text-to-image" },
|
|
85
|
+
],
|
|
86
|
+
fields: {
|
|
87
|
+
prompt: { as: "value", field: "prompt" },
|
|
88
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
89
|
+
resolution: { as: "value", field: "resolution" },
|
|
90
|
+
background: { as: "value", field: "background" },
|
|
91
|
+
images: { as: "urlArray", field: "input_urls" },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
...([["nano-banana-2", "Nano-Banana-2"], ["nano-banana-pro", "Nano-Banana-Pro"]] as const).map(([name, model]): GenerationWireMapping => ({
|
|
95
|
+
capability: { module: NANO_BANANA, name }, result: "image", routes: [{ model }],
|
|
96
|
+
fields: {
|
|
97
|
+
prompt: { as: "value", field: "prompt" },
|
|
98
|
+
images: { as: "urlArray", field: "image_input" },
|
|
99
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
100
|
+
resolution: { as: "value", field: "resolution" },
|
|
101
|
+
outputFormat: { as: "value", field: "output_format" },
|
|
102
|
+
},
|
|
103
|
+
})),
|
|
104
|
+
{
|
|
105
|
+
capability: { module: GROK, name: "grok-imagine-video" }, result: "video",
|
|
106
|
+
routes: [
|
|
107
|
+
{ model: "grok-imagine/image-to-video", whenPresent: ["images"] },
|
|
108
|
+
{ model: "grok-imagine/text-to-video" },
|
|
109
|
+
],
|
|
110
|
+
fields: {
|
|
111
|
+
prompt: { as: "value", field: "prompt" },
|
|
112
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
113
|
+
resolution: { as: "value", field: "resolution" },
|
|
114
|
+
duration: { as: "value", field: "duration" },
|
|
115
|
+
images: { as: "urlArray", field: "image_urls" },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
capability: { module: GROK, name: "grok-imagine-video-1.5-preview" }, result: "video",
|
|
120
|
+
routes: [{ model: "grok-imagine-1.5/image-to-video" }],
|
|
121
|
+
fields: {
|
|
122
|
+
prompt: { as: "value", field: "prompt" },
|
|
123
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
124
|
+
resolution: { as: "value", field: "resolution" },
|
|
125
|
+
duration: { as: "value", field: "duration" },
|
|
126
|
+
images: { as: "urlArray", field: "image_urls" },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
];
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { requestDeadline } from "@hypit/runtime-kit";
|
|
2
|
+
import type { AsyncEndpoint, EndpointCredential, EndpointInvocationContext, EndpointOutcome } from "@hypit/endpoint-kit";
|
|
3
|
+
import { defineEndpointPackage, wakeAfter } from "@hypit/endpoint-kit";
|
|
4
|
+
import type { GenerationArtifactUrlResolver } from "@hypit/generation";
|
|
5
|
+
import { canonicalize } from "@hypit/protocol";
|
|
6
|
+
import type { BlobRef, CapabilityRef } from "@hypit/protocol";
|
|
7
|
+
import { credentialRef } from "@hypit/runtime";
|
|
8
|
+
import type { CredentialRef, ResourceStore } from "@hypit/runtime";
|
|
9
|
+
import { hiApiRouteForCapability, hiApiRoutes } from "./routes.js";
|
|
10
|
+
import type { HiApiMediaLimits } from "./routes.js";
|
|
11
|
+
import { HiApiHttpError, HiApiServiceError, hiApiTaskFailure } from "./errors.js";
|
|
12
|
+
|
|
13
|
+
export const hiApiProviderModuleRef = { name: "@hypit/provider-hiapi", version: "1" } as const;
|
|
14
|
+
|
|
15
|
+
export type CreateHiApiProviderOptions = {
|
|
16
|
+
readonly instance?: string;
|
|
17
|
+
readonly pool?: string;
|
|
18
|
+
readonly baseUrl?: string;
|
|
19
|
+
readonly apiKey?: CredentialRef;
|
|
20
|
+
readonly defaultConcurrency?: number;
|
|
21
|
+
readonly actionLimits?: import("@hypit/endpoint-kit").EndpointActionLimits;
|
|
22
|
+
readonly pollIntervalMs?: number;
|
|
23
|
+
readonly requestTimeoutMs?: number;
|
|
24
|
+
readonly operationTimeoutMs?: number;
|
|
25
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
26
|
+
/** Publish a referenced Resource at a URL the service can fetch; replaces inline data URLs. */
|
|
27
|
+
readonly publicAssetUrl?: (artifact: BlobRef, artifacts: ResourceStore, fields?: Readonly<Record<string, string | number | boolean>>) => Promise<string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type Handle = {
|
|
31
|
+
readonly contract: "hypit.hiapi-operation@1";
|
|
32
|
+
readonly taskId: string;
|
|
33
|
+
readonly route: string;
|
|
34
|
+
readonly startedAt: number;
|
|
35
|
+
readonly urls?: readonly string[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function assert(condition: unknown, message: string): asserts condition { if (!condition) throw new Error(message); }
|
|
39
|
+
function object(value: unknown, subject: string): Record<string, unknown> {
|
|
40
|
+
assert(value !== null && typeof value === "object" && !Array.isArray(value), `${subject} must be an object`);
|
|
41
|
+
return value as Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
function capabilityKey(capability: CapabilityRef): string { return `${capability.module.name}@${capability.module.version}#${capability.name}`; }
|
|
44
|
+
function apiBaseUrl(value: string): string {
|
|
45
|
+
let trimmed = value.trim();
|
|
46
|
+
while (trimmed.endsWith("/")) trimmed = trimmed.slice(0, -1);
|
|
47
|
+
assert(trimmed.length > 0, "HiAPI base URL is empty");
|
|
48
|
+
return trimmed;
|
|
49
|
+
}
|
|
50
|
+
function apiKey(credentials: Readonly<Record<string, EndpointCredential>>): string {
|
|
51
|
+
const value = credentials.apiKey?.secret;
|
|
52
|
+
assert(typeof value === "string" && value.length > 0, "HiAPI apiKey credential is unavailable; store a HiAPI API key for this Endpoint");
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function failureMessage(error: unknown): string {
|
|
56
|
+
return error instanceof Error ? error.message : String(error);
|
|
57
|
+
}
|
|
58
|
+
function failure(error: unknown): EndpointOutcome {
|
|
59
|
+
return { status: "failed", failure: { code: error instanceof HiApiServiceError ? error.code : "HIAPI_ERROR", message: failureMessage(error) } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class HiApiClient {
|
|
63
|
+
constructor(readonly baseUrl: string, readonly timeout: number, readonly fetcher: typeof globalThis.fetch) {}
|
|
64
|
+
async json(path: string, key: string, init: RequestInit = {}): Promise<Record<string, unknown>> {
|
|
65
|
+
const deadline = requestDeadline(this.timeout);
|
|
66
|
+
try {
|
|
67
|
+
const response = await deadline.wait(this.fetcher(`${this.baseUrl}${path}`, {
|
|
68
|
+
...init, signal: deadline.signal, headers: { authorization: `Bearer ${key}`, ...(init.headers ?? {}) },
|
|
69
|
+
}));
|
|
70
|
+
const text = await deadline.wait(response.text());
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const input = typeof init.body === "string" ? JSON.parse(init.body) as Record<string, unknown> : undefined;
|
|
73
|
+
throw new HiApiHttpError(response.status, response, text, {
|
|
74
|
+
method: init.method ?? "GET", path, ...(typeof input?.model === "string" ? { model: input.model } : {}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
let body: unknown;
|
|
78
|
+
try { body = text.length === 0 ? {} : JSON.parse(text); } catch { throw new Error(`HiAPI returned invalid JSON (${response.status})`); }
|
|
79
|
+
return object(object(body, "HiAPI response").data, "HiAPI response data");
|
|
80
|
+
} finally { deadline.finish(); }
|
|
81
|
+
}
|
|
82
|
+
async download(url: string): Promise<{ readonly bytes: Uint8Array; readonly mediaType: string }> {
|
|
83
|
+
const deadline = requestDeadline(this.timeout);
|
|
84
|
+
try {
|
|
85
|
+
const response = await deadline.wait(this.fetcher(url, { signal: deadline.signal }));
|
|
86
|
+
if (!response.ok) throw new Error(`HiAPI asset returned HTTP ${response.status}`);
|
|
87
|
+
return { bytes: new Uint8Array(await deadline.wait(response.arrayBuffer())), mediaType: response.headers.get("content-type")?.split(";", 1)[0] ?? "application/octet-stream" };
|
|
88
|
+
} finally { deadline.finish(); }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolverFor(limits: HiApiMediaLimits, context: EndpointInvocationContext, publicAssetUrl: CreateHiApiProviderOptions["publicAssetUrl"]): GenerationArtifactUrlResolver {
|
|
93
|
+
const resolved = new Map<string, Promise<string>>();
|
|
94
|
+
let imageBytes = 0;
|
|
95
|
+
return (artifact, fields) => {
|
|
96
|
+
const existing = resolved.get(artifact.resource);
|
|
97
|
+
if (existing !== undefined) return existing;
|
|
98
|
+
const promise = (async () => {
|
|
99
|
+
if (publicAssetUrl !== undefined) return await publicAssetUrl(artifact, context.resources, fields);
|
|
100
|
+
// HiAPI documents data URLs for image and audio inputs; reference videos must be public HTTPS URLs.
|
|
101
|
+
const kind = artifact.mediaType.split("/", 1)[0];
|
|
102
|
+
assert(kind === "image" || kind === "audio",
|
|
103
|
+
`HiAPI accepts ${artifact.mediaType} references only by public URL; configure publicAssetUrl for this Endpoint`);
|
|
104
|
+
const limit = limits[kind];
|
|
105
|
+
assert(limit === undefined || artifact.size <= limit,
|
|
106
|
+
`HiAPI accepts ${kind} references up to ${(limit ?? 0) / 1_000_000} MB for this model; ${artifact.resource} is ${artifact.size} bytes`);
|
|
107
|
+
if (kind === "image") {
|
|
108
|
+
imageBytes += artifact.size;
|
|
109
|
+
assert(limits.imagesTotal === undefined || imageBytes <= limits.imagesTotal,
|
|
110
|
+
`HiAPI accepts reference images up to ${(limits.imagesTotal ?? 0) / 1_000_000} MB combined for this model`);
|
|
111
|
+
}
|
|
112
|
+
const bytes = await context.resources.get(artifact.resource);
|
|
113
|
+
assert(bytes !== undefined && bytes.byteLength === artifact.size, `Reference Resource ${artifact.resource} is unavailable or has changed`);
|
|
114
|
+
return `data:${artifact.mediaType};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
115
|
+
})();
|
|
116
|
+
resolved.set(artifact.resource, promise);
|
|
117
|
+
return promise;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function endpoint(client: HiApiClient, pollIntervalMs: number, maxOperationMs: number, publicAssetUrl: CreateHiApiProviderOptions["publicAssetUrl"]): AsyncEndpoint {
|
|
122
|
+
return {
|
|
123
|
+
async start(context) {
|
|
124
|
+
try {
|
|
125
|
+
const route = hiApiRouteForCapability(context.need.capability);
|
|
126
|
+
assert(route !== undefined, "HiAPI does not implement this exact capability");
|
|
127
|
+
const request = route.prepare(context.need.constraints);
|
|
128
|
+
await context.reportProgress?.({ phase: `Preparing HiAPI request: ${request.model}` });
|
|
129
|
+
let body: Record<string, unknown>;
|
|
130
|
+
try {
|
|
131
|
+
body = await request.compile(resolverFor(request.mediaLimits, context, publicAssetUrl));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new HiApiServiceError(error instanceof HiApiServiceError ? error.code : "HIAPI_ERROR",
|
|
134
|
+
`HiAPI request preparation failed; model=${request.model}; generation not submitted: ${failureMessage(error)}`);
|
|
135
|
+
}
|
|
136
|
+
await context.reportProgress?.({ phase: `Submitting HiAPI request: ${request.model}` });
|
|
137
|
+
const response = await client.json("/v1/tasks", apiKey(context.credentials), {
|
|
138
|
+
method: "POST", headers: { "content-type": "application/json", "idempotency-key": context.operation }, body: JSON.stringify(body),
|
|
139
|
+
});
|
|
140
|
+
assert(typeof response.taskId === "string" && response.taskId.length > 0, "HiAPI response has no taskId");
|
|
141
|
+
const handle: Handle = { contract: "hypit.hiapi-operation@1", taskId: response.taskId, route: route.key, startedAt: Date.now() };
|
|
142
|
+
const receipt = { id: handle.taskId };
|
|
143
|
+
await context.checkpoint?.({ handle: canonicalize(handle), receipt });
|
|
144
|
+
return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: "submitted" }), receipt };
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return failure(error);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
async poll(context) {
|
|
150
|
+
try {
|
|
151
|
+
const handle = object(context.handle, "HiAPI handle") as unknown as Handle;
|
|
152
|
+
const route = hiApiRouteForCapability(context.need.capability);
|
|
153
|
+
assert(route !== undefined && handle.contract === "hypit.hiapi-operation@1" && handle.route === route.key, "HiAPI handle is invalid");
|
|
154
|
+
const receipt = { id: handle.taskId };
|
|
155
|
+
if (Date.now() - handle.startedAt > maxOperationMs) {
|
|
156
|
+
return { status: "failed", receipt, failure: { code: "HIAPI_OPERATION_TIMEOUT", message: `HiAPI task ${handle.taskId} exceeded this Provider's operationTimeoutMs (${maxOperationMs}); remote outcome is unknown` } };
|
|
157
|
+
}
|
|
158
|
+
const task = await client.json(`/v1/tasks/${encodeURIComponent(handle.taskId)}`, apiKey(context.credentials));
|
|
159
|
+
const status = String(task.status);
|
|
160
|
+
if (status === "queued" || status === "handling" || status === "archiving") {
|
|
161
|
+
return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: status }), receipt };
|
|
162
|
+
}
|
|
163
|
+
const rejected = hiApiTaskFailure(task, handle.taskId);
|
|
164
|
+
if (rejected !== undefined) return { ...failure(rejected), receipt };
|
|
165
|
+
assert(status === "success", `HiAPI returned unknown task status ${status}`);
|
|
166
|
+
assert(Array.isArray(task.output) && task.output.length > 0, "HiAPI task succeeded without output");
|
|
167
|
+
const urls = task.output.map((item, index) => {
|
|
168
|
+
const url = object(item, `HiAPI output ${index + 1}`).url;
|
|
169
|
+
assert(typeof url === "string" && /^https?:\/\//u.test(url), `HiAPI output ${index + 1} has no URL`);
|
|
170
|
+
return url;
|
|
171
|
+
});
|
|
172
|
+
return { status: "ready", handle: canonicalize({ ...handle, urls }), receipt };
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return failure(error);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
async collect(context) {
|
|
178
|
+
try {
|
|
179
|
+
const handle = object(context.handle, "HiAPI handle") as unknown as Handle;
|
|
180
|
+
const route = hiApiRouteForCapability(context.need.capability);
|
|
181
|
+
assert(route !== undefined && handle.route === route.key && Array.isArray(handle.urls), "HiAPI collection route differs");
|
|
182
|
+
await context.reportProgress?.({ phase: "Receiving generated files" });
|
|
183
|
+
const blobs: BlobRef[] = [];
|
|
184
|
+
for (const url of handle.urls) {
|
|
185
|
+
const downloaded = await client.download(url);
|
|
186
|
+
blobs.push(await context.resources.put(downloaded.bytes, downloaded.mediaType));
|
|
187
|
+
}
|
|
188
|
+
return { status: "completed", result: { value: route.packageResult(blobs) }, receipt: { id: handle.taskId } };
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return failure(error);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createHiApiProvider(options: CreateHiApiProviderOptions = {}) {
|
|
197
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 300_000;
|
|
198
|
+
const operationTimeoutMs = options.operationTimeoutMs ?? 30 * 60_000;
|
|
199
|
+
for (const [name, value] of Object.entries({ requestTimeoutMs, operationTimeoutMs })) {
|
|
200
|
+
assert(Number.isSafeInteger(value) && value > 0, `HiAPI ${name} must be a positive integer`);
|
|
201
|
+
}
|
|
202
|
+
const client = new HiApiClient(apiBaseUrl(options.baseUrl ?? "https://api.hiapi.ai"), requestTimeoutMs, options.fetch ?? globalThis.fetch);
|
|
203
|
+
const asyncEndpoint = endpoint(client, options.pollIntervalMs ?? 10_000, operationTimeoutMs, options.publicAssetUrl);
|
|
204
|
+
return defineEndpointPackage({
|
|
205
|
+
module: hiApiProviderModuleRef, facet: "gateway", instance: options.instance ?? "hiapi.default", pool: options.pool ?? options.instance ?? "hiapi.default",
|
|
206
|
+
pricing: { kind: "page", url: "https://www.hiapi.ai/en/pricing" },
|
|
207
|
+
credentials: { apiKey: options.apiKey ?? credentialRef("os", "hiapi.api-key") },
|
|
208
|
+
credentialInputs: { apiKey: { label: "HiAPI API key" } },
|
|
209
|
+
defaultConcurrency: options.defaultConcurrency ?? 4,
|
|
210
|
+
...(options.actionLimits === undefined ? {} : { actionLimits: options.actionLimits }),
|
|
211
|
+
capabilities: hiApiRoutes.map((route) => ({
|
|
212
|
+
capability: route.capability, returns: route.returns, lifecycle: "asynchronous" as const, endpoint: asyncEndpoint, capacity: route.capability.name, supports: route.supports,
|
|
213
|
+
})),
|
|
214
|
+
});
|
|
215
|
+
}
|