@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
|
@@ -0,0 +1,120 @@
|
|
|
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 { polloMappings } from "./mapping.js";
|
|
13
|
+
|
|
14
|
+
export type PolloPreparedRequest = {
|
|
15
|
+
/** Request path under the Pollo platform base URL. */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
readonly compile: (resolve: GenerationArtifactUrlResolver) => Promise<Record<string, unknown>>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type PolloRoute = GenerationWireMapping & {
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly returns: TypeRef;
|
|
23
|
+
readonly supports: (request: EndpointRequest) => EndpointSupport;
|
|
24
|
+
readonly prepare: (constraints: CanonicalValue) => PolloPreparedRequest;
|
|
25
|
+
readonly packageResult: (artifacts: readonly BlobRef[]) => StoredValue;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function scalar(request: GenerationRequest, port: string): string | number | boolean | undefined {
|
|
29
|
+
const value = request.ports[port]?.[0];
|
|
30
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
function count(request: GenerationRequest, port: string): number {
|
|
33
|
+
return request.ports[port]?.length ?? 0;
|
|
34
|
+
}
|
|
35
|
+
function strings(value: unknown): readonly string[] {
|
|
36
|
+
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const GPT_IMAGE_RATIOS = ["1:1", "3:2", "2:3", "16:9", "9:16", "4:3", "3:4", "21:9", "auto"];
|
|
40
|
+
|
|
41
|
+
/** Pollo's documented input ranges for each mapped model, checked before any reference is resolved. */
|
|
42
|
+
function rejection(mapping: GenerationWireMapping, request: GenerationRequest): string | undefined {
|
|
43
|
+
const { name } = mapping.capability;
|
|
44
|
+
const ratio = scalar(request, "aspectRatio");
|
|
45
|
+
if (name === "grok-imagine-video-1.5-preview") {
|
|
46
|
+
if (count(request, "images") !== 1) return "Pollo Grok Imagine 1.5 animates exactly one image";
|
|
47
|
+
if (ratio !== "auto") return `Pollo Grok Imagine 1.5 takes no aspect ratio; use auto, not ${String(ratio)}`;
|
|
48
|
+
}
|
|
49
|
+
if (name === "gpt-image-2" && !GPT_IMAGE_RATIOS.includes(String(ratio))) {
|
|
50
|
+
return `Pollo GPT Image 2 does not render ${String(ratio)}`;
|
|
51
|
+
}
|
|
52
|
+
if (mapping.capability.module.name === "@hypit/nano-banana" && ratio === "auto") {
|
|
53
|
+
return `Pollo ${name} takes an explicit aspect ratio, not auto`;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ref(type: "image" | "video" | "audio") {
|
|
59
|
+
return (url: string) => ({ url, type });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Fields the model package requires that Pollo names differently or does not take. */
|
|
63
|
+
function normalize(mapping: GenerationWireMapping, input: Record<string, unknown>): Record<string, unknown> {
|
|
64
|
+
const { name } = mapping.capability;
|
|
65
|
+
if (name === "minimax-h3") {
|
|
66
|
+
const refs = [
|
|
67
|
+
...strings(input.refs_image).map(ref("image")),
|
|
68
|
+
...strings(input.refs_video).map(ref("video")),
|
|
69
|
+
...strings(input.refs_audio).map(ref("audio")),
|
|
70
|
+
];
|
|
71
|
+
delete input.refs_image;
|
|
72
|
+
delete input.refs_video;
|
|
73
|
+
delete input.refs_audio;
|
|
74
|
+
if (refs.length > 0) input.refs = refs;
|
|
75
|
+
}
|
|
76
|
+
if (name === "grok-imagine-video-1.5-preview") {
|
|
77
|
+
input.image = strings(input.images)[0];
|
|
78
|
+
delete input.images;
|
|
79
|
+
delete input.aspect_ratio;
|
|
80
|
+
}
|
|
81
|
+
if (mapping.capability.module.name === "@hypit/nano-banana") delete input.output_format;
|
|
82
|
+
return input;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function capabilityKey(capability: CapabilityRef): string {
|
|
86
|
+
return `${capability.module.name}@${capability.module.version}#${capability.name}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const polloRoutes: readonly PolloRoute[] = polloMappings.map((mapping) => ({
|
|
90
|
+
...mapping,
|
|
91
|
+
key: capabilityKey(mapping.capability),
|
|
92
|
+
returns: mapping.result === "image" ? generationTypes.imageSet : generationTypes.videoSet,
|
|
93
|
+
supports: (request) => {
|
|
94
|
+
const reason = rejection(mapping, request.constraints as unknown as GenerationRequest);
|
|
95
|
+
return reason === undefined ? { status: "supported" } : { status: "unsupported", reason };
|
|
96
|
+
},
|
|
97
|
+
prepare: (constraints) => {
|
|
98
|
+
const request = constraints as unknown as GenerationRequest;
|
|
99
|
+
const reason = rejection(mapping, request);
|
|
100
|
+
if (reason !== undefined) throw new Error(reason);
|
|
101
|
+
return {
|
|
102
|
+
path: selectWireModelForRequest(mapping, request),
|
|
103
|
+
compile: async (resolve) => ({
|
|
104
|
+
input: normalize(mapping, (await compileWireRequest(mapping, request, resolve)).input as Record<string, unknown>),
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
packageResult: (artifacts) => ({
|
|
109
|
+
kind: "inline",
|
|
110
|
+
value: canonicalize(mapping.result === "image"
|
|
111
|
+
? sealGeneratedImageSet({ images: artifacts })
|
|
112
|
+
: sealGeneratedVideoSet({ videos: artifacts })),
|
|
113
|
+
}),
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
const byCapability = new Map(polloRoutes.map((route) => [route.key, route]));
|
|
117
|
+
|
|
118
|
+
export function polloRouteForCapability(capability: CapabilityRef): PolloRoute | undefined {
|
|
119
|
+
return byCapability.get(capabilityKey(capability));
|
|
120
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# `@hypit/provider-tokendance`
|
|
2
|
+
|
|
3
|
+
Hypit Runtime Provider for a [TokenDance](https://tokendance.space) account. It submits generation
|
|
4
|
+
requests with a TokenDance API key through the gateway protocols TokenDance documents for each model
|
|
5
|
+
and stores the returned files in the current Build.
|
|
6
|
+
|
|
7
|
+
| Capability | TokenDance model | Protocol |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `@hypit/seedance@1#seedance-2` | `seedance-2.0` | Ark `POST /ark/v3/generations/tasks`, polled at `GET /ark/v3/generations/tasks/{id}` |
|
|
10
|
+
| `@hypit/seedance@1#seedance-2-fast` | `seedance-2.0-fast` | same |
|
|
11
|
+
| `@hypit/seedance@1#seedance-2-mini` | `seedance-2.0-mini` | same |
|
|
12
|
+
| `@hypit/seedance@1#seedance-2.5` | `seedance-2.5` | same |
|
|
13
|
+
| `@hypit/seedream@1#seedream-5-lite` | `seedream-5.0-lite` | Ark `POST /ark/v3/images/generations`, synchronous |
|
|
14
|
+
| `@hypit/minimax-h3@1#minimax-h3` | `minimax-h3` | MiniMax `POST /minimax/v2/video_generation`, polled at `GET /minimax/v2/query/video_generation/{id}` |
|
|
15
|
+
|
|
16
|
+
The TokenDance catalogue at `GET /gateway/v1/models` lists further models; this Provider maps only
|
|
17
|
+
the models the Distribution already describes.
|
|
18
|
+
|
|
19
|
+
Video requests write the prompt and each media input as one item of the protocol's `content`
|
|
20
|
+
array with its `role` (`first_frame`, `last_frame`, `reference_image`, `reference_video`,
|
|
21
|
+
`reference_audio`), then `resolution`, `ratio`, `duration` and, for Seedance, `generate_audio`;
|
|
22
|
+
`web-search="true"` adds `tools: [{ "type": "web_search" }]`. Seedance visual references may carry
|
|
23
|
+
`person-reference`; the Provider accepts the declaration and transmits nothing for it, since the Ark
|
|
24
|
+
protocol has no such field. Seedance 2.0 and 2.5 reject reference images and videos that contain a
|
|
25
|
+
real human face; TokenDance offers no way to register authorized portrait material, so such a request
|
|
26
|
+
fails with the service's moderation error.
|
|
27
|
+
|
|
28
|
+
Seedream requests send the Ark `size` in pixels: the authored `quality` selects the 2K, 3K or 4K
|
|
29
|
+
tier and `aspect-ratio` the entry from the Ark reference table for Seedream 5.0 lite. Output uses
|
|
30
|
+
`response_format: "url"` and `watermark: false`. `nsfw-check` has no Ark field and is not sent.
|
|
31
|
+
|
|
32
|
+
Service limits this Provider reports as unsupported before submitting:
|
|
33
|
+
|
|
34
|
+
- Seedance 2.5 frame mode (`first-frame` present) requires `aspect-ratio="adaptive"`.
|
|
35
|
+
- MiniMax H3 text-to-video requires an explicit `aspect-ratio`; frame and reference modes may omit it.
|
|
36
|
+
|
|
37
|
+
Reference media reach each protocol the way its documentation provides. Ark takes images under
|
|
38
|
+
30 MB and audio up to 15 MB inline as `data:` URLs within a 64 MB request body; the Provider checks
|
|
39
|
+
both before submitting. An Ark reference video takes a URL only; configure `publicAssetUrl` when
|
|
40
|
+
embedding the Provider, otherwise such a request fails before submission. MiniMax inputs are
|
|
41
|
+
uploaded through the gateway's `POST /minimax/v1/files/upload` with `purpose:
|
|
42
|
+
video_generation_input` (images up to 30 MB, videos up to 50 MB, audio up to 15 MB) and referenced
|
|
43
|
+
as `mm_file://{file_id}`; MiniMax keeps such files for seven days.
|
|
44
|
+
|
|
45
|
+
Runtime Profile example:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"endpoints": {
|
|
50
|
+
"tokendance.default": {
|
|
51
|
+
"use": "@hypit/provider-tokendance",
|
|
52
|
+
"pool": "tokendance.default",
|
|
53
|
+
"config": {
|
|
54
|
+
"apiKey": { "store": "platform", "key": "tokendance.api-key" },
|
|
55
|
+
"defaultConcurrency": 3,
|
|
56
|
+
"pollIntervalMs": 10000
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`baseUrl` defaults to `https://tokendance.space/gateway`. Store the API key with
|
|
64
|
+
`hypit auth login tokendance.default --runtime hypit.runtime.json`. Optional `requestTimeoutMs`,
|
|
65
|
+
`operationTimeoutMs` and `actionLimits` bound single HTTP calls, the whole remote task and action
|
|
66
|
+
concurrency. Task and HTTP failures keep TokenDance's `error.code` and message, with any signed URL
|
|
67
|
+
in the message redacted.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hypit/provider-tokendance",
|
|
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/minimax-h3": "workspace:*",
|
|
18
|
+
"@hypit/seedance": "workspace:*",
|
|
19
|
+
"@hypit/seedream": "workspace:*"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -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 { createTokenDanceProvider } from "./provider.js";
|
|
12
|
+
|
|
13
|
+
const adapter = createRuntimeEndpointAdapterFacet({
|
|
14
|
+
use: "@hypit/provider-tokendance",
|
|
15
|
+
activate(context) {
|
|
16
|
+
if (context.pool === undefined) throw new Error("TokenDance Provider Pool is required");
|
|
17
|
+
const config = runtimeConfigObject(context.config, "TokenDance");
|
|
18
|
+
runtimeConfigExact(config, [
|
|
19
|
+
"baseUrl",
|
|
20
|
+
"apiKey",
|
|
21
|
+
"defaultConcurrency",
|
|
22
|
+
"actionLimits",
|
|
23
|
+
"pollIntervalMs",
|
|
24
|
+
"requestTimeoutMs",
|
|
25
|
+
"operationTimeoutMs",
|
|
26
|
+
], "TokenDance");
|
|
27
|
+
const baseUrl = runtimeConfigString(config.baseUrl, "TokenDance 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("TokenDance baseUrl must use HTTPS or loopback");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const apiKey = runtimeConfigCredentialRef(config.apiKey, "TokenDance apiKey");
|
|
35
|
+
if (apiKey === undefined) throw new Error("TokenDance apiKey CredentialRef is required");
|
|
36
|
+
const actionLimits = runtimeConfigActionLimits(config.actionLimits);
|
|
37
|
+
const defaultConcurrency = runtimeConfigPositiveInteger(config.defaultConcurrency, "TokenDance defaultConcurrency");
|
|
38
|
+
const pollIntervalMs = runtimeConfigPositiveInteger(config.pollIntervalMs, "TokenDance pollIntervalMs");
|
|
39
|
+
const requestTimeoutMs = runtimeConfigPositiveInteger(config.requestTimeoutMs, "TokenDance requestTimeoutMs");
|
|
40
|
+
const operationTimeoutMs = runtimeConfigPositiveInteger(config.operationTimeoutMs, "TokenDance operationTimeoutMs");
|
|
41
|
+
return {
|
|
42
|
+
endpoint: createTokenDanceProvider({
|
|
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,47 @@
|
|
|
1
|
+
/** TokenDance relays each protocol's own error body; keep the code, the message and the HTTP facts. */
|
|
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 safeTokenDanceReason(value: string): string {
|
|
12
|
+
return value.replace(/https?:\/\/\S+/giu, "[redacted-url]");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class TokenDanceServiceError extends Error {
|
|
16
|
+
constructor(readonly code: string, message: string) { super(message); }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class TokenDanceHttpError extends TokenDanceServiceError {
|
|
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 error = record(body?.error);
|
|
25
|
+
const code = text(error?.code) ?? text(error?.type) ?? text(body?.code) ?? "TOKENDANCE_HTTP_ERROR";
|
|
26
|
+
const reason = text(error?.message) ?? text(body?.message)
|
|
27
|
+
?? (body === undefined ? text(bodyText.slice(0, 2000)) : undefined);
|
|
28
|
+
const requestId = text(response.headers.get("x-request-id"));
|
|
29
|
+
const facts = [
|
|
30
|
+
`TokenDance HTTP ${status}`, code, `${request.method} ${request.path}`,
|
|
31
|
+
...(request.model === undefined ? [] : [`model=${request.model}`]),
|
|
32
|
+
...(requestId === undefined ? [] : [`request=${requestId}`]),
|
|
33
|
+
];
|
|
34
|
+
super(code, `${facts.join("; ")}${reason === undefined ? "" : `: ${safeTokenDanceReason(reason)}`}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A terminal task body from either protocol; `undefined` when the task did not fail. */
|
|
39
|
+
export function tokenDanceTaskFailure(task: Record<string, unknown>, id: string): TokenDanceServiceError | undefined {
|
|
40
|
+
const status = String(task.status);
|
|
41
|
+
if (!["failed", "cancelled", "canceled", "expired"].includes(status)) return undefined;
|
|
42
|
+
const error = record(task.error);
|
|
43
|
+
const code = text(error?.code) ?? "TOKENDANCE_TASK_FAILED";
|
|
44
|
+
const reason = text(error?.message) ?? text(task.error);
|
|
45
|
+
return new TokenDanceServiceError(code,
|
|
46
|
+
`TokenDance task ${id} ${status}; ${code}${reason === undefined ? "" : `: ${safeTokenDanceReason(reason)}`}`);
|
|
47
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TokenDance model IDs and the request fields they accept. Media fields name the `role` of an Ark
|
|
10
|
+
* or MiniMax `content` item; routes.ts folds them into that array. `personReference` is accepted
|
|
11
|
+
* on visual references and not transmitted: neither protocol has a field for it.
|
|
12
|
+
*/
|
|
13
|
+
const seedance = (name: string, model: string): GenerationWireMapping => ({
|
|
14
|
+
capability: { module: SEEDANCE, name }, result: "video", routes: [{ model }],
|
|
15
|
+
fields: {
|
|
16
|
+
prompt: { as: "value", field: "text" },
|
|
17
|
+
referenceImage: { as: "urlArray", field: "reference_image", resourceFields: ["personReference"] },
|
|
18
|
+
referenceVideo: { as: "urlArray", field: "reference_video", resourceFields: ["personReference"] },
|
|
19
|
+
referenceAudio: { as: "urlArray", field: "reference_audio" },
|
|
20
|
+
firstFrame: { as: "url", field: "first_frame", resourceFields: ["personReference"] },
|
|
21
|
+
lastFrame: { as: "url", field: "last_frame", resourceFields: ["personReference"] },
|
|
22
|
+
resolution: { as: "value", field: "resolution" },
|
|
23
|
+
aspectRatio: { as: "value", field: "ratio" },
|
|
24
|
+
duration: { as: "value", field: "duration" },
|
|
25
|
+
generateAudio: { as: "value", field: "generate_audio" },
|
|
26
|
+
webSearch: { as: "value", field: "web_search" },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const tokenDanceMappings: readonly GenerationWireMapping[] = [
|
|
31
|
+
seedance("seedance-2", "seedance-2.0"),
|
|
32
|
+
seedance("seedance-2-fast", "seedance-2.0-fast"),
|
|
33
|
+
seedance("seedance-2-mini", "seedance-2.0-mini"),
|
|
34
|
+
seedance("seedance-2.5", "seedance-2.5"),
|
|
35
|
+
{
|
|
36
|
+
capability: { module: SEEDREAM, name: "seedream-5-lite" }, result: "image", routes: [{ model: "seedream-5.0-lite" }],
|
|
37
|
+
fields: {
|
|
38
|
+
prompt: { as: "value", field: "prompt" },
|
|
39
|
+
aspectRatio: { as: "value", field: "aspect_ratio" },
|
|
40
|
+
quality: { as: "value", field: "quality" },
|
|
41
|
+
outputFormat: { as: "value", field: "output_format" },
|
|
42
|
+
nsfwCheck: { as: "value", field: "nsfw_check" },
|
|
43
|
+
images: { as: "urlArray", field: "image" },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
capability: { module: MINIMAX, name: "minimax-h3" }, result: "video", routes: [{ model: "minimax-h3" }],
|
|
48
|
+
fields: {
|
|
49
|
+
prompt: { as: "value", field: "text" },
|
|
50
|
+
duration: { as: "value", field: "duration" },
|
|
51
|
+
resolution: { as: "value", field: "resolution", whenAbsent: "2K" },
|
|
52
|
+
aspectRatio: { as: "value", field: "ratio" },
|
|
53
|
+
referenceImage: { as: "urlArray", field: "reference_image" },
|
|
54
|
+
referenceVideo: { as: "urlArray", field: "reference_video" },
|
|
55
|
+
referenceAudio: { as: "urlArray", field: "reference_audio" },
|
|
56
|
+
firstFrame: { as: "url", field: "first_frame" },
|
|
57
|
+
lastFrame: { as: "url", field: "last_frame" },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { requestDeadline } from "@hypit/runtime-kit";
|
|
2
|
+
import type { AsyncEndpoint, EndpointCredential, EndpointInvocationContext, EndpointOutcome, ImmediateEndpointHandler } 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 { tokenDanceRouteForCapability, tokenDanceRoutes } from "./routes.js";
|
|
10
|
+
import type { TokenDanceRoute } from "./routes.js";
|
|
11
|
+
import { TokenDanceHttpError, TokenDanceServiceError, tokenDanceTaskFailure } from "./errors.js";
|
|
12
|
+
|
|
13
|
+
export const tokenDanceProviderModuleRef = { name: "@hypit/provider-tokendance", version: "1" } as const;
|
|
14
|
+
|
|
15
|
+
export type CreateTokenDanceProviderOptions = {
|
|
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.tokendance-operation@1";
|
|
32
|
+
readonly taskId: string;
|
|
33
|
+
readonly route: string;
|
|
34
|
+
readonly startedAt: number;
|
|
35
|
+
readonly url?: 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, "TokenDance 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, "TokenDance apiKey credential is unavailable; store a TokenDance 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 TokenDanceServiceError ? error.code : "TOKENDANCE_ERROR", message: failureMessage(error) } };
|
|
60
|
+
}
|
|
61
|
+
function httpsUrl(value: unknown, subject: string): string {
|
|
62
|
+
assert(typeof value === "string" && /^https?:\/\//u.test(value), `${subject} has no download URL`);
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class TokenDanceClient {
|
|
67
|
+
constructor(readonly baseUrl: string, readonly timeout: number, readonly fetcher: typeof globalThis.fetch) {}
|
|
68
|
+
async json(path: string, key: string, init: RequestInit = {}): Promise<Record<string, unknown>> {
|
|
69
|
+
const deadline = requestDeadline(this.timeout);
|
|
70
|
+
try {
|
|
71
|
+
const response = await deadline.wait(this.fetcher(`${this.baseUrl}${path}`, {
|
|
72
|
+
...init, signal: deadline.signal, headers: { authorization: `Bearer ${key}`, ...(init.headers ?? {}) },
|
|
73
|
+
}));
|
|
74
|
+
const text = await deadline.wait(response.text());
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const input = typeof init.body === "string" ? JSON.parse(init.body) as Record<string, unknown> : undefined;
|
|
77
|
+
throw new TokenDanceHttpError(response.status, response, text, {
|
|
78
|
+
method: init.method ?? "GET", path, ...(typeof input?.model === "string" ? { model: input.model } : {}),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
let body: unknown;
|
|
82
|
+
try { body = text.length === 0 ? {} : JSON.parse(text); } catch { throw new Error(`TokenDance returned invalid JSON (${response.status})`); }
|
|
83
|
+
return object(body, "TokenDance response");
|
|
84
|
+
} finally { deadline.finish(); }
|
|
85
|
+
}
|
|
86
|
+
/** MiniMax's file API through the gateway; the returned id is referenced as `mm_file://{file_id}`. */
|
|
87
|
+
async uploadMiniMaxInput(bytes: Uint8Array, artifact: BlobRef, key: string): Promise<string> {
|
|
88
|
+
const form = new FormData();
|
|
89
|
+
form.set("purpose", "video_generation_input");
|
|
90
|
+
form.set("file", new Blob([new Uint8Array(bytes)], { type: artifact.mediaType }), `${artifact.resource}.${artifact.mediaType.split("/")[1] ?? "bin"}`);
|
|
91
|
+
const response = await this.json("/minimax/v1/files/upload", key, { method: "POST", body: form });
|
|
92
|
+
const status = object(response.base_resp ?? {}, "TokenDance upload base_resp").status_code;
|
|
93
|
+
assert(status === undefined || status === 0, `TokenDance MiniMax upload rejected: ${String(object(response.base_resp, "TokenDance upload base_resp").status_msg ?? status)}`);
|
|
94
|
+
const id = object(response.file, "TokenDance upload file").file_id;
|
|
95
|
+
assert((typeof id === "string" && id.length > 0) || typeof id === "number", "TokenDance MiniMax upload returned no file_id");
|
|
96
|
+
return `mm_file://${String(id)}`;
|
|
97
|
+
}
|
|
98
|
+
async download(url: string): Promise<{ readonly bytes: Uint8Array; readonly mediaType: string }> {
|
|
99
|
+
const deadline = requestDeadline(this.timeout);
|
|
100
|
+
try {
|
|
101
|
+
const response = await deadline.wait(this.fetcher(url, { signal: deadline.signal }));
|
|
102
|
+
if (!response.ok) throw new Error(`TokenDance asset returned HTTP ${response.status}`);
|
|
103
|
+
return { bytes: new Uint8Array(await deadline.wait(response.arrayBuffer())), mediaType: response.headers.get("content-type")?.split(";", 1)[0] ?? "application/octet-stream" };
|
|
104
|
+
} finally { deadline.finish(); }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function mediaKind(mediaType: string): "image" | "video" | "audio" | undefined {
|
|
109
|
+
const kind = mediaType.split("/", 1)[0];
|
|
110
|
+
return kind === "image" || kind === "video" || kind === "audio" ? kind : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolverFor(client: TokenDanceClient, route: TokenDanceRoute, context: EndpointInvocationContext, publicAssetUrl: CreateTokenDanceProviderOptions["publicAssetUrl"]): GenerationArtifactUrlResolver {
|
|
114
|
+
const resolved = new Map<string, Promise<string>>();
|
|
115
|
+
return (artifact, fields) => {
|
|
116
|
+
const existing = resolved.get(artifact.resource);
|
|
117
|
+
if (existing !== undefined) return existing;
|
|
118
|
+
const promise = (async () => {
|
|
119
|
+
if (publicAssetUrl !== undefined) return await publicAssetUrl(artifact, context.resources, fields);
|
|
120
|
+
const kind = mediaKind(artifact.mediaType);
|
|
121
|
+
const limit = kind === undefined ? undefined : route.mediaLimits[kind];
|
|
122
|
+
assert(limit !== undefined,
|
|
123
|
+
`TokenDance ${route.protocol} accepts ${artifact.mediaType} references only by public URL; configure publicAssetUrl for this Endpoint`);
|
|
124
|
+
assert(artifact.size <= limit,
|
|
125
|
+
`TokenDance ${route.protocol} accepts ${kind} references up to ${limit / 1_000_000} MB; ${artifact.resource} is ${artifact.size} bytes`);
|
|
126
|
+
const bytes = await context.resources.get(artifact.resource);
|
|
127
|
+
assert(bytes !== undefined && bytes.byteLength === artifact.size, `Reference Resource ${artifact.resource} is unavailable or has changed`);
|
|
128
|
+
if (route.protocol === "minimax-video") return await client.uploadMiniMaxInput(bytes, artifact, apiKey(context.credentials));
|
|
129
|
+
return `data:${artifact.mediaType};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
130
|
+
})();
|
|
131
|
+
resolved.set(artifact.resource, promise);
|
|
132
|
+
return promise;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function prepare(client: TokenDanceClient, context: EndpointInvocationContext, publicAssetUrl: CreateTokenDanceProviderOptions["publicAssetUrl"]) {
|
|
137
|
+
const route = tokenDanceRouteForCapability(context.need.capability);
|
|
138
|
+
assert(route !== undefined, "TokenDance does not implement this exact capability");
|
|
139
|
+
const request = route.prepare(context.need.constraints);
|
|
140
|
+
await context.reportProgress?.({ phase: `Preparing TokenDance request: ${request.model}` });
|
|
141
|
+
let body: string;
|
|
142
|
+
try {
|
|
143
|
+
body = JSON.stringify(await request.compile(resolverFor(client, route, context, publicAssetUrl)));
|
|
144
|
+
const cap = route.mediaLimits.body;
|
|
145
|
+
assert(cap === undefined || Buffer.byteLength(body) <= cap,
|
|
146
|
+
`TokenDance ${route.protocol} accepts request bodies up to ${(cap ?? 0) / 1_000_000} MB; inline references make this one ${Buffer.byteLength(body)} bytes`);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new TokenDanceServiceError(error instanceof TokenDanceServiceError ? error.code : "TOKENDANCE_ERROR",
|
|
149
|
+
`TokenDance request preparation failed; model=${request.model}; generation not submitted: ${failureMessage(error)}`);
|
|
150
|
+
}
|
|
151
|
+
return { route, model: request.model, body };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function store(client: TokenDanceClient, urls: readonly string[], resources: ResourceStore): Promise<BlobRef[]> {
|
|
155
|
+
const blobs: BlobRef[] = [];
|
|
156
|
+
for (const url of urls) {
|
|
157
|
+
const downloaded = await client.download(url);
|
|
158
|
+
blobs.push(await resources.put(downloaded.bytes, downloaded.mediaType));
|
|
159
|
+
}
|
|
160
|
+
return blobs;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const paths = {
|
|
164
|
+
"ark-video": { submit: "/ark/v3/generations/tasks", task: (id: string) => `/ark/v3/generations/tasks/${encodeURIComponent(id)}` },
|
|
165
|
+
"minimax-video": { submit: "/minimax/v2/video_generation", task: (id: string) => `/minimax/v2/query/video_generation/${encodeURIComponent(id)}` },
|
|
166
|
+
} as const;
|
|
167
|
+
|
|
168
|
+
function taskId(protocol: keyof typeof paths, response: Record<string, unknown>): string {
|
|
169
|
+
const id = protocol === "ark-video" ? response.id : response.task_id;
|
|
170
|
+
assert(typeof id === "string" && id.length > 0, "TokenDance response has no task id");
|
|
171
|
+
return id;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Ark answers the task itself; MiniMax wraps it in `task`. */
|
|
175
|
+
function taskBody(protocol: keyof typeof paths, response: Record<string, unknown>): Record<string, unknown> {
|
|
176
|
+
return protocol === "ark-video" ? response : object(response.task, "TokenDance task");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function taskVideoUrl(protocol: keyof typeof paths, task: Record<string, unknown>): string {
|
|
180
|
+
const content = object(task.content, "TokenDance task content");
|
|
181
|
+
return httpsUrl(protocol === "ark-video" ? content.video_url : content.url, "TokenDance task");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function endpoint(client: TokenDanceClient, pollIntervalMs: number, maxOperationMs: number, publicAssetUrl: CreateTokenDanceProviderOptions["publicAssetUrl"]): AsyncEndpoint {
|
|
185
|
+
return {
|
|
186
|
+
async start(context) {
|
|
187
|
+
try {
|
|
188
|
+
const { route, model, body } = await prepare(client, context, publicAssetUrl);
|
|
189
|
+
assert(route.protocol !== "ark-image", "TokenDance image capabilities use an immediate endpoint");
|
|
190
|
+
await context.reportProgress?.({ phase: `Submitting TokenDance request: ${model}` });
|
|
191
|
+
const response = await client.json(paths[route.protocol].submit, apiKey(context.credentials), {
|
|
192
|
+
method: "POST", headers: { "content-type": "application/json" }, body,
|
|
193
|
+
});
|
|
194
|
+
const handle: Handle = { contract: "hypit.tokendance-operation@1", taskId: taskId(route.protocol, response), route: route.key, startedAt: Date.now() };
|
|
195
|
+
const receipt = { id: handle.taskId };
|
|
196
|
+
await context.checkpoint?.({ handle: canonicalize(handle), receipt });
|
|
197
|
+
return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: "submitted" }), receipt };
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return failure(error);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
async poll(context) {
|
|
203
|
+
try {
|
|
204
|
+
const handle = object(context.handle, "TokenDance handle") as unknown as Handle;
|
|
205
|
+
const route = tokenDanceRouteForCapability(context.need.capability);
|
|
206
|
+
assert(route !== undefined && handle.contract === "hypit.tokendance-operation@1" && handle.route === route.key && route.protocol !== "ark-image", "TokenDance handle is invalid");
|
|
207
|
+
const receipt = { id: handle.taskId };
|
|
208
|
+
if (Date.now() - handle.startedAt > maxOperationMs) {
|
|
209
|
+
return { status: "failed", receipt, failure: { code: "TOKENDANCE_OPERATION_TIMEOUT", message: `TokenDance task ${handle.taskId} exceeded this Provider's operationTimeoutMs (${maxOperationMs}); remote outcome is unknown` } };
|
|
210
|
+
}
|
|
211
|
+
const task = taskBody(route.protocol, await client.json(paths[route.protocol].task(handle.taskId), apiKey(context.credentials)));
|
|
212
|
+
const status = String(task.status);
|
|
213
|
+
if (status === "queued" || status === "running") return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: status }), receipt };
|
|
214
|
+
const rejected = tokenDanceTaskFailure(task, handle.taskId);
|
|
215
|
+
if (rejected !== undefined) return { ...failure(rejected), receipt };
|
|
216
|
+
assert(status === "succeeded", `TokenDance returned unknown task status ${status}`);
|
|
217
|
+
return { status: "ready", handle: canonicalize({ ...handle, url: taskVideoUrl(route.protocol, task) }), receipt };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return failure(error);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
async collect(context) {
|
|
223
|
+
try {
|
|
224
|
+
const handle = object(context.handle, "TokenDance handle") as unknown as Handle;
|
|
225
|
+
const route = tokenDanceRouteForCapability(context.need.capability);
|
|
226
|
+
assert(route !== undefined && handle.route === route.key, "TokenDance collection route differs");
|
|
227
|
+
await context.reportProgress?.({ phase: "Receiving generated video" });
|
|
228
|
+
const blobs = await store(client, [httpsUrl(handle.url, "TokenDance handle")], context.resources);
|
|
229
|
+
return { status: "completed", result: { value: route.packageResult(blobs) }, receipt: { id: handle.taskId } };
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return failure(error);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function createTokenDanceProvider(options: CreateTokenDanceProviderOptions = {}) {
|
|
238
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 300_000;
|
|
239
|
+
const operationTimeoutMs = options.operationTimeoutMs ?? 30 * 60_000;
|
|
240
|
+
for (const [name, value] of Object.entries({ requestTimeoutMs, operationTimeoutMs })) {
|
|
241
|
+
assert(Number.isSafeInteger(value) && value > 0, `TokenDance ${name} must be a positive integer`);
|
|
242
|
+
}
|
|
243
|
+
const client = new TokenDanceClient(apiBaseUrl(options.baseUrl ?? "https://tokendance.space/gateway"), requestTimeoutMs, options.fetch ?? globalThis.fetch);
|
|
244
|
+
const asyncEndpoint = endpoint(client, options.pollIntervalMs ?? 10_000, operationTimeoutMs, options.publicAssetUrl);
|
|
245
|
+
const imageEndpoint: ImmediateEndpointHandler = async (context) => {
|
|
246
|
+
const { route, model, body } = await prepare(client, context, options.publicAssetUrl);
|
|
247
|
+
assert(route.protocol === "ark-image", "TokenDance video capabilities use an asynchronous endpoint");
|
|
248
|
+
await context.reportProgress?.({ phase: `Submitting TokenDance request: ${model}` });
|
|
249
|
+
const response = await client.json("/ark/v3/images/generations", apiKey(context.credentials), {
|
|
250
|
+
method: "POST", headers: { "content-type": "application/json" }, body,
|
|
251
|
+
});
|
|
252
|
+
assert(Array.isArray(response.data) && response.data.length > 0, "TokenDance image response has no data");
|
|
253
|
+
const urls = response.data.map((item, index) => httpsUrl(object(item, `TokenDance image ${index + 1}`).url, `TokenDance image ${index + 1}`));
|
|
254
|
+
await context.reportProgress?.({ phase: "Receiving generated images" });
|
|
255
|
+
return { value: route.packageResult(await store(client, urls, context.resources)) };
|
|
256
|
+
};
|
|
257
|
+
return defineEndpointPackage({
|
|
258
|
+
module: tokenDanceProviderModuleRef, facet: "gateway", instance: options.instance ?? "tokendance.default", pool: options.pool ?? options.instance ?? "tokendance.default",
|
|
259
|
+
pricing: { kind: "page", url: "https://tokendance.space/models" },
|
|
260
|
+
credentials: { apiKey: options.apiKey ?? credentialRef("os", "tokendance.api-key") },
|
|
261
|
+
credentialInputs: { apiKey: { label: "TokenDance API key" } },
|
|
262
|
+
defaultConcurrency: options.defaultConcurrency ?? 4,
|
|
263
|
+
...(options.actionLimits === undefined ? {} : { actionLimits: options.actionLimits }),
|
|
264
|
+
capabilities: tokenDanceRoutes.map((route) => route.protocol === "ark-image"
|
|
265
|
+
? { capability: route.capability, returns: route.returns, lifecycle: "immediate" as const, handler: imageEndpoint, capacity: route.capability.name, supports: route.supports }
|
|
266
|
+
: { capability: route.capability, returns: route.returns, lifecycle: "asynchronous" as const, endpoint: asyncEndpoint, capacity: route.capability.name, supports: route.supports }),
|
|
267
|
+
});
|
|
268
|
+
}
|