@hypit/hypit 0.2.3 → 0.2.5
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 +72 -0
- package/packages/provider-monid/package.json +20 -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 +66 -0
- package/packages/provider-monid/src/provider.ts +245 -0
- package/packages/provider-monid/src/routes.ts +132 -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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileWireRequest,
|
|
3
|
+
selectWireModelForRequest,
|
|
4
|
+
generationTypes,
|
|
5
|
+
sealGeneratedImageSet,
|
|
6
|
+
sealGeneratedVideoSet,
|
|
7
|
+
} from "@hypit/generation";
|
|
8
|
+
import type { GenerationArtifactUrlResolver, GenerationRequest, GenerationWireMapping } from "@hypit/generation";
|
|
9
|
+
import { canonicalize } from "@hypit/protocol";
|
|
10
|
+
import type { BlobRef, CapabilityRef, CanonicalValue, StoredValue, TypeRef } from "@hypit/protocol";
|
|
11
|
+
import type { EndpointRequest, EndpointSupport } from "@hypit/endpoint-kit";
|
|
12
|
+
import { hiApiMappings } from "./mapping.js";
|
|
13
|
+
|
|
14
|
+
/** Documented byte limits for inline media on one HiAPI model; an absent kind carries no documented cap. */
|
|
15
|
+
export type HiApiMediaLimits = {
|
|
16
|
+
readonly image?: number;
|
|
17
|
+
readonly audio?: number;
|
|
18
|
+
/** Documented cap on the combined size of all reference images. */
|
|
19
|
+
readonly imagesTotal?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const MB = 1_000_000;
|
|
23
|
+
|
|
24
|
+
/** Per-file sizes HiAPI's model pages state for images and audio; videos travel by public URL. */
|
|
25
|
+
const hiApiMediaLimits: Readonly<Record<string, HiApiMediaLimits>> = {
|
|
26
|
+
"seedance-2.0-mini": { image: 30 * MB, audio: 15 * MB },
|
|
27
|
+
"seedance-2.5/image-to-video": { image: 30 * MB, audio: 15 * MB, imagesTotal: 120 * MB },
|
|
28
|
+
"seedance-2.5/reference-to-video": { image: 30 * MB, audio: 15 * MB, imagesTotal: 120 * MB },
|
|
29
|
+
"seedream-5.0-lite/image-to-image": { image: 10 * MB },
|
|
30
|
+
"Nano-Banana-Pro": { image: 30 * MB },
|
|
31
|
+
"grok-imagine/image-to-video": { image: 10 * MB },
|
|
32
|
+
"grok-imagine-1.5/image-to-video": { image: 20 * MB },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type HiApiPreparedRequest = {
|
|
36
|
+
readonly model: string;
|
|
37
|
+
readonly mediaLimits: HiApiMediaLimits;
|
|
38
|
+
readonly compile: (resolve: GenerationArtifactUrlResolver) => Promise<Record<string, unknown>>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type HiApiRoute = GenerationWireMapping & {
|
|
42
|
+
readonly key: string;
|
|
43
|
+
readonly returns: TypeRef;
|
|
44
|
+
readonly supports: (request: EndpointRequest) => EndpointSupport;
|
|
45
|
+
readonly prepare: (constraints: CanonicalValue) => HiApiPreparedRequest;
|
|
46
|
+
readonly packageResult: (artifacts: readonly BlobRef[]) => StoredValue;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function scalar(request: GenerationRequest, port: string): string | number | boolean | undefined {
|
|
50
|
+
const value = request.ports[port]?.[0];
|
|
51
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : undefined;
|
|
52
|
+
}
|
|
53
|
+
function count(request: GenerationRequest, port: string): number {
|
|
54
|
+
return request.ports[port]?.length ?? 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const SEEDREAM_RESOLUTION: Readonly<Record<string, string>> = { basic: "2K", ultra: "4K" };
|
|
58
|
+
const GPT_IMAGE_UNAVAILABLE: Readonly<Record<string, readonly string[]>> = {
|
|
59
|
+
"2K": ["5:4", "4:5", "3:1", "1:3", "9:21"],
|
|
60
|
+
"4K": ["1:1", "3:1", "1:3", "9:21"],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** HiAPI's documented input ranges for each mapped model, checked before any reference is resolved. */
|
|
64
|
+
function rejection(mapping: GenerationWireMapping, request: GenerationRequest): string | undefined {
|
|
65
|
+
const { name } = mapping.capability;
|
|
66
|
+
const ratio = scalar(request, "aspectRatio");
|
|
67
|
+
const resolution = scalar(request, "resolution");
|
|
68
|
+
let model: string;
|
|
69
|
+
try { model = selectWireModelForRequest(mapping, request); } catch (error) { return error instanceof Error ? error.message : String(error); }
|
|
70
|
+
if (name === "seedance-2-mini" && scalar(request, "webSearch") === true) {
|
|
71
|
+
return "HiAPI seedance-2.0-mini has no web_search field";
|
|
72
|
+
}
|
|
73
|
+
if (name === "seedance-2.5") {
|
|
74
|
+
if (model === "seedance-2.5/image-to-video" && ratio !== "adaptive") {
|
|
75
|
+
return `HiAPI ${model} takes only aspect-ratio adaptive, not ${String(ratio)}`;
|
|
76
|
+
}
|
|
77
|
+
if (model !== "seedance-2.5/reference-to-video" && resolution === "480p") {
|
|
78
|
+
return `HiAPI ${model} renders 720p or 1080p, not 480p`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (name === "seedream-5-lite" && SEEDREAM_RESOLUTION[String(scalar(request, "quality"))] === undefined) {
|
|
82
|
+
return `HiAPI Seedream 5.0 lite renders 2K (basic) or 4K (ultra), not quality ${String(scalar(request, "quality"))}`;
|
|
83
|
+
}
|
|
84
|
+
if (name === "minimax-h3") {
|
|
85
|
+
if (resolution !== undefined && resolution !== "2K") return `HiAPI minimax-h3 renders 2K only, not ${String(resolution)}`;
|
|
86
|
+
if (count(request, "referenceImage") > 5) return "HiAPI minimax-h3 accepts up to five reference images";
|
|
87
|
+
}
|
|
88
|
+
if (name === "gpt-image-2") {
|
|
89
|
+
if (count(request, "images") > 6) return "HiAPI gpt-image-2/image-to-image accepts up to six reference images";
|
|
90
|
+
if (scalar(request, "background") !== undefined && resolution !== "1K") return `HiAPI GPT Image 2 accepts background only at 1K; omit it at ${String(resolution)}`;
|
|
91
|
+
if (ratio === "auto" && resolution !== "1K") return "HiAPI GPT Image 2 renders aspect-ratio auto only at 1K";
|
|
92
|
+
if (GPT_IMAGE_UNAVAILABLE[String(resolution)]?.includes(String(ratio))) return `HiAPI GPT Image 2 does not render ${String(ratio)} at ${String(resolution)}`;
|
|
93
|
+
}
|
|
94
|
+
if (mapping.capability.module.name === "@hypit/grok-imagine") {
|
|
95
|
+
if (resolution === "1080p") return `HiAPI ${model} renders 480p or 720p, not 1080p`;
|
|
96
|
+
if (name === "grok-imagine-video-1.5-preview" && count(request, "images") !== 1) {
|
|
97
|
+
return "HiAPI grok-imagine-1.5/image-to-video animates exactly one image";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Fields the model package requires that HiAPI names differently or does not take. */
|
|
104
|
+
function normalize(mapping: GenerationWireMapping, input: Record<string, unknown>): Record<string, unknown> {
|
|
105
|
+
const { name } = mapping.capability;
|
|
106
|
+
if (name === "seedance-2-mini" && input.web_search === false) delete input.web_search;
|
|
107
|
+
if (name === "seedream-5-lite") {
|
|
108
|
+
input.resolution = SEEDREAM_RESOLUTION[String(input.quality)];
|
|
109
|
+
delete input.quality;
|
|
110
|
+
delete input.output_format;
|
|
111
|
+
delete input.nsfw_check;
|
|
112
|
+
}
|
|
113
|
+
return input;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function capabilityKey(capability: CapabilityRef): string {
|
|
117
|
+
return `${capability.module.name}@${capability.module.version}#${capability.name}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const hiApiRoutes: readonly HiApiRoute[] = hiApiMappings.map((mapping) => ({
|
|
121
|
+
...mapping,
|
|
122
|
+
key: capabilityKey(mapping.capability),
|
|
123
|
+
returns: mapping.result === "image" ? generationTypes.imageSet : generationTypes.videoSet,
|
|
124
|
+
supports: (request) => {
|
|
125
|
+
const reason = rejection(mapping, request.constraints as unknown as GenerationRequest);
|
|
126
|
+
return reason === undefined ? { status: "supported" } : { status: "unsupported", reason };
|
|
127
|
+
},
|
|
128
|
+
prepare: (constraints) => {
|
|
129
|
+
const request = constraints as unknown as GenerationRequest;
|
|
130
|
+
const reason = rejection(mapping, request);
|
|
131
|
+
if (reason !== undefined) throw new Error(reason);
|
|
132
|
+
const model = selectWireModelForRequest(mapping, request);
|
|
133
|
+
return {
|
|
134
|
+
model,
|
|
135
|
+
mediaLimits: hiApiMediaLimits[model] ?? {},
|
|
136
|
+
compile: async (resolve) => ({
|
|
137
|
+
model,
|
|
138
|
+
input: normalize(mapping, (await compileWireRequest(mapping, request, resolve)).input as Record<string, unknown>),
|
|
139
|
+
}),
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
packageResult: (artifacts) => ({
|
|
143
|
+
kind: "inline",
|
|
144
|
+
value: canonicalize(mapping.result === "image"
|
|
145
|
+
? sealGeneratedImageSet({ images: artifacts })
|
|
146
|
+
: sealGeneratedVideoSet({ videos: artifacts })),
|
|
147
|
+
}),
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
const byCapability = new Map(hiApiRoutes.map((route) => [route.key, route]));
|
|
151
|
+
|
|
152
|
+
export function hiApiRouteForCapability(capability: CapabilityRef): HiApiRoute | undefined {
|
|
153
|
+
return byCapability.get(capabilityKey(capability));
|
|
154
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# `@hypit/provider-monid`
|
|
2
|
+
|
|
3
|
+
Hypit Runtime Provider for a [Monid](https://monid.ai) workspace. It runs Monid's generation
|
|
4
|
+
endpoints through `POST /v1/run`, polls `GET /v1/runs/{runId}` until the run is terminal, downloads
|
|
5
|
+
the returned video and stores it in the current Build.
|
|
6
|
+
|
|
7
|
+
| Capability | Monid endpoint |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `@hypit/seedance@1#seedance-2` | `bytedance` `/v1/video/seedance-2.0` |
|
|
10
|
+
| `@hypit/seedance@1#seedance-2-fast` | `bytedance` `/v1/video/seedance-2.0-fast` |
|
|
11
|
+
| `@hypit/seedance@1#seedance-2-mini` | `bytedance` `/v1/video/seedance-2.0-mini` |
|
|
12
|
+
| `@hypit/seedance@1#seedance-2.5` | `bytedance` `/v1/video/seedance-2.5` |
|
|
13
|
+
| `@hypit/minimax-h3@1#minimax-h3` | `minimax` `/v1/video/minimax-h3` |
|
|
14
|
+
|
|
15
|
+
Monid's catalogue lists further generation endpoints, including the H3 Fast, Max and Max Turbo
|
|
16
|
+
variants and Hailuo 2.3; their models are not the ones the Distribution describes. Input schemas
|
|
17
|
+
are published through the authenticated `inspect` operation, which is where the request bodies
|
|
18
|
+
above come from.
|
|
19
|
+
|
|
20
|
+
Every mapped endpoint relays a BytePlus ModelArk request: one `content` array holding the prompt
|
|
21
|
+
and each media input as a typed item with its `role` (`first_frame`, `last_frame`,
|
|
22
|
+
`reference_image`, `reference_video`, `reference_audio`), then `resolution`, `ratio` and
|
|
23
|
+
`duration`.
|
|
24
|
+
|
|
25
|
+
The Seedance endpoints add `generate_audio`. Monid documents no web search field for them, so
|
|
26
|
+
`web-search="true"` is unsupported, and Seedance 2.5 frame mode (`first-frame` present) requires
|
|
27
|
+
`aspect-ratio="adaptive"`. Seedance visual references may carry `person-reference`; the Provider
|
|
28
|
+
accepts the declaration and transmits nothing for it, since the endpoint has no field for it.
|
|
29
|
+
Seedance rejects reference images and videos that contain a real human face; Monid offers no way to
|
|
30
|
+
register authorized portrait material, so such a request fails with the upstream moderation error.
|
|
31
|
+
|
|
32
|
+
MiniMax H3 names its model in the body and takes neither of those two fields. The endpoint requires
|
|
33
|
+
a resolution, so a request that states none is sent at `2K`, the resolution the HypiHub Provider
|
|
34
|
+
also selects. It also requires a ratio that is not `adaptive` for text-to-video, while frame mode
|
|
35
|
+
resolves the framing from the uploaded image and reference-to-video defaults to adaptive: a
|
|
36
|
+
text-to-video request carrying no `aspect-ratio` is reported unsupported before any reference is
|
|
37
|
+
resolved, and the other two modes are sent as `adaptive`. H3 returns its video at `content.url`
|
|
38
|
+
rather than the ModelArk `video_url`.
|
|
39
|
+
|
|
40
|
+
Reference media are uploaded through the workspace file system Monid provides for this purpose
|
|
41
|
+
(`sfs`): `/put` signs an upload for `hypit/<resource>.<ext>`, the bytes are `PUT` to that URL, and
|
|
42
|
+
`/cat` mints a one-day download URL that the generation endpoint fetches. Uploaded files stay in the
|
|
43
|
+
workspace until removed with `/rm`. Embedded callers may replace this transport with
|
|
44
|
+
`publicAssetUrl`.
|
|
45
|
+
|
|
46
|
+
A run ends with Monid's own status (`COMPLETED`, `FAILED`, `BLOCKED`, `STOPPED`, `TIMED_OUT`) and,
|
|
47
|
+
when completed, the provider's HTTP status in `providerResponse`. A completed run whose provider
|
|
48
|
+
answered 4xx or 5xx fails with that status and the provider's error message; a `BLOCKED` run keeps
|
|
49
|
+
Monid's `reason`. Signed URLs in messages are redacted.
|
|
50
|
+
|
|
51
|
+
Runtime Profile example:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"endpoints": {
|
|
56
|
+
"monid.default": {
|
|
57
|
+
"use": "@hypit/provider-monid",
|
|
58
|
+
"pool": "monid.default",
|
|
59
|
+
"config": {
|
|
60
|
+
"apiKey": { "store": "platform", "key": "monid.api-key" },
|
|
61
|
+
"defaultConcurrency": 3,
|
|
62
|
+
"pollIntervalMs": 10000
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`baseUrl` defaults to `https://api.monid.ai`. Store the API key with
|
|
70
|
+
`hypit auth login monid.default --runtime hypit.runtime.json`. Optional `requestTimeoutMs`,
|
|
71
|
+
`operationTimeoutMs` and `actionLimits` bound single HTTP calls, the whole remote run and action
|
|
72
|
+
concurrency; file-system runs poll at most every five seconds within `requestTimeoutMs`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hypit/provider-monid",
|
|
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/minimax-h3": "workspace:*",
|
|
13
|
+
"@hypit/protocol": "workspace:*",
|
|
14
|
+
"@hypit/runtime": "workspace:*",
|
|
15
|
+
"@hypit/runtime-kit": "workspace:*"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@hypit/seedance": "workspace:*"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -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 { createMonidProvider } from "./provider.js";
|
|
12
|
+
|
|
13
|
+
const adapter = createRuntimeEndpointAdapterFacet({
|
|
14
|
+
use: "@hypit/provider-monid",
|
|
15
|
+
activate(context) {
|
|
16
|
+
if (context.pool === undefined) throw new Error("Monid Provider Pool is required");
|
|
17
|
+
const config = runtimeConfigObject(context.config, "Monid");
|
|
18
|
+
runtimeConfigExact(config, [
|
|
19
|
+
"baseUrl",
|
|
20
|
+
"apiKey",
|
|
21
|
+
"defaultConcurrency",
|
|
22
|
+
"actionLimits",
|
|
23
|
+
"pollIntervalMs",
|
|
24
|
+
"requestTimeoutMs",
|
|
25
|
+
"operationTimeoutMs",
|
|
26
|
+
], "Monid");
|
|
27
|
+
const baseUrl = runtimeConfigString(config.baseUrl, "Monid 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("Monid baseUrl must use HTTPS or loopback");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const apiKey = runtimeConfigCredentialRef(config.apiKey, "Monid apiKey");
|
|
35
|
+
if (apiKey === undefined) throw new Error("Monid apiKey CredentialRef is required");
|
|
36
|
+
const actionLimits = runtimeConfigActionLimits(config.actionLimits);
|
|
37
|
+
const defaultConcurrency = runtimeConfigPositiveInteger(config.defaultConcurrency, "Monid defaultConcurrency");
|
|
38
|
+
const pollIntervalMs = runtimeConfigPositiveInteger(config.pollIntervalMs, "Monid pollIntervalMs");
|
|
39
|
+
const requestTimeoutMs = runtimeConfigPositiveInteger(config.requestTimeoutMs, "Monid requestTimeoutMs");
|
|
40
|
+
const operationTimeoutMs = runtimeConfigPositiveInteger(config.operationTimeoutMs, "Monid operationTimeoutMs");
|
|
41
|
+
return {
|
|
42
|
+
endpoint: createMonidProvider({
|
|
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,55 @@
|
|
|
1
|
+
/** Monid's `{ code, message }` error envelope and run outcome fields, 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 safeMonidReason(value: string): string {
|
|
12
|
+
return value.replace(/https?:\/\/\S+/giu, "[redacted-url]");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class MonidServiceError extends Error {
|
|
16
|
+
constructor(readonly code: string, message: string) { super(message); }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class MonidHttpError extends MonidServiceError {
|
|
20
|
+
constructor(readonly status: number, response: { readonly headers: Headers }, bodyText: string,
|
|
21
|
+
request: { readonly method: string; readonly path: 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 reason = text(body?.message) ?? (body === undefined ? text(bodyText.slice(0, 2000)) : undefined);
|
|
25
|
+
const requestId = text(response.headers.get("x-request-id"));
|
|
26
|
+
const facts = [
|
|
27
|
+
`Monid HTTP ${status}`, `${request.method} ${request.path}`,
|
|
28
|
+
...(requestId === undefined ? [] : [`request=${requestId}`]),
|
|
29
|
+
];
|
|
30
|
+
super("MONID_HTTP_ERROR", `${facts.join("; ")}${reason === undefined ? "" : `: ${safeMonidReason(reason)}`}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const monidTerminalStatuses = ["COMPLETED", "FAILED", "BLOCKED", "STOPPED", "TIMED_OUT"] as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A run that ended without provider output: a non-COMPLETED terminal status, or COMPLETED with a
|
|
38
|
+
* provider HTTP error. `undefined` while the run is pending or when the provider answered 2xx.
|
|
39
|
+
*/
|
|
40
|
+
export function monidRunFailure(run: Record<string, unknown>, id: string): MonidServiceError | undefined {
|
|
41
|
+
const status = String(run.status);
|
|
42
|
+
if (status !== "COMPLETED") {
|
|
43
|
+
if (!monidTerminalStatuses.includes(status as typeof monidTerminalStatuses[number])) return undefined;
|
|
44
|
+
const reason = text(run.reason);
|
|
45
|
+
return new MonidServiceError(`MONID_RUN_${status}`,
|
|
46
|
+
`Monid run ${id} ${status}${reason === undefined ? "" : `: ${safeMonidReason(reason)}`}`);
|
|
47
|
+
}
|
|
48
|
+
const provider = record(run.providerResponse);
|
|
49
|
+
const httpStatus = typeof provider?.httpStatus === "number" ? provider.httpStatus : 200;
|
|
50
|
+
if (httpStatus < 400) return undefined;
|
|
51
|
+
const error = record(provider?.error);
|
|
52
|
+
const detail = text(error?.message) ?? text(record(error?.error)?.message) ?? text(provider?.error);
|
|
53
|
+
return new MonidServiceError("MONID_PROVIDER_ERROR",
|
|
54
|
+
`Monid run ${id} completed with provider HTTP ${httpStatus}${detail === undefined ? "" : `: ${safeMonidReason(detail)}`}`);
|
|
55
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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 MINIMAX_H3: ModuleRef = { name: "@hypit/minimax-h3", version: "1" };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One mapping plus the Monid provider that relays the endpoint. Monid addresses an endpoint by
|
|
9
|
+
* provider and path, so the provider travels with the mapping rather than being assumed.
|
|
10
|
+
*/
|
|
11
|
+
export type MonidMapping = GenerationWireMapping & { readonly service: string };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Monid `bytedance` endpoints and the fields their ModelArk request body takes. Media fields name
|
|
15
|
+
* the `role` of a `content` item; routes.ts folds them into that array. `personReference` is
|
|
16
|
+
* accepted on visual references and not transmitted: the endpoint has no field for it.
|
|
17
|
+
*/
|
|
18
|
+
const seedance = (name: string, endpoint: string): MonidMapping => ({
|
|
19
|
+
service: "bytedance",
|
|
20
|
+
capability: { module: SEEDANCE, name }, result: "video", routes: [{ model: endpoint }],
|
|
21
|
+
fields: {
|
|
22
|
+
prompt: { as: "value", field: "text" },
|
|
23
|
+
referenceImage: { as: "urlArray", field: "reference_image", resourceFields: ["personReference"] },
|
|
24
|
+
referenceVideo: { as: "urlArray", field: "reference_video", resourceFields: ["personReference"] },
|
|
25
|
+
referenceAudio: { as: "urlArray", field: "reference_audio" },
|
|
26
|
+
firstFrame: { as: "url", field: "first_frame", resourceFields: ["personReference"] },
|
|
27
|
+
lastFrame: { as: "url", field: "last_frame", resourceFields: ["personReference"] },
|
|
28
|
+
resolution: { as: "value", field: "resolution" },
|
|
29
|
+
aspectRatio: { as: "value", field: "ratio" },
|
|
30
|
+
duration: { as: "value", field: "duration" },
|
|
31
|
+
generateAudio: { as: "value", field: "generate_audio" },
|
|
32
|
+
webSearch: { as: "value", field: "web_search" },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Monid's `minimax` MiniMax-H3 endpoint. It takes the same role-tagged `content` array as the
|
|
38
|
+
* ModelArk endpoints above, names the model in the body, and carries neither a generated-audio nor
|
|
39
|
+
* a web-search field. `resolution` is required by the endpoint while the model's port is optional,
|
|
40
|
+
* so an unstated resolution is sent as the 2K the HypiHub Provider also selects.
|
|
41
|
+
*/
|
|
42
|
+
const minimaxH3: MonidMapping = {
|
|
43
|
+
service: "minimax",
|
|
44
|
+
capability: { module: MINIMAX_H3, name: "minimax-h3" }, result: "video",
|
|
45
|
+
routes: [{ model: "/v1/video/minimax-h3" }],
|
|
46
|
+
constants: { model: "MiniMax-H3" },
|
|
47
|
+
fields: {
|
|
48
|
+
prompt: { as: "value", field: "text" },
|
|
49
|
+
referenceImage: { as: "urlArray", field: "reference_image" },
|
|
50
|
+
referenceVideo: { as: "urlArray", field: "reference_video" },
|
|
51
|
+
referenceAudio: { as: "urlArray", field: "reference_audio" },
|
|
52
|
+
firstFrame: { as: "url", field: "first_frame" },
|
|
53
|
+
lastFrame: { as: "url", field: "last_frame" },
|
|
54
|
+
resolution: { as: "value", field: "resolution", whenAbsent: "2K" },
|
|
55
|
+
aspectRatio: { as: "value", field: "ratio" },
|
|
56
|
+
duration: { as: "value", field: "duration" },
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const monidMappings: readonly MonidMapping[] = [
|
|
61
|
+
seedance("seedance-2", "/v1/video/seedance-2.0"),
|
|
62
|
+
seedance("seedance-2-fast", "/v1/video/seedance-2.0-fast"),
|
|
63
|
+
seedance("seedance-2-mini", "/v1/video/seedance-2.0-mini"),
|
|
64
|
+
seedance("seedance-2.5", "/v1/video/seedance-2.5"),
|
|
65
|
+
minimaxH3,
|
|
66
|
+
];
|