@hypit/hypit 0.2.2 → 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.
Files changed (68) hide show
  1. package/README.md +18 -6
  2. package/bin/hypit.mjs +12 -9
  3. package/dist/public/generation.d.ts +4 -5
  4. package/examples/provider-package/README.md +92 -16
  5. package/examples/provider-package/hypit.runtime.json +8 -1
  6. package/examples/provider-package/packages/provider-videos/package.json +11 -0
  7. package/examples/provider-package/packages/provider-videos/src/activation.ts +24 -0
  8. package/examples/provider-package/packages/provider-videos/src/provider.ts +181 -0
  9. package/examples/provider-package/packages/provider-videos/tsconfig.json +9 -0
  10. package/package.json +5 -1
  11. package/packages/build-result/package.json +1 -1
  12. package/packages/build-result/src/store.ts +1 -1
  13. package/packages/credential-store-file/README.md +9 -4
  14. package/packages/credential-store-file/package.json +2 -1
  15. package/packages/credential-store-file/src/activation.ts +2 -2
  16. package/packages/credential-store-file/src/index.ts +0 -1
  17. package/packages/credential-store-file/src/store.ts +13 -40
  18. package/packages/credential-store-os/runtime/windows-credential.ps1 +1 -2
  19. package/packages/credential-store-os/src/store.ts +2 -68
  20. package/packages/credential-store-os/src/windows.ts +47 -0
  21. package/packages/credential-store-platform/README.md +2 -1
  22. package/packages/credential-store-platform/src/activation.ts +2 -2
  23. package/packages/file-io-node/README.md +20 -0
  24. package/packages/file-io-node/package.json +13 -0
  25. package/packages/file-io-node/src/index.ts +1 -0
  26. package/packages/{build-result → file-io-node}/src/replace-file-windows.ts +3 -3
  27. package/packages/generation/README.md +6 -0
  28. package/packages/generation/src/mapping.ts +30 -10
  29. package/packages/media-execution/src/execute.ts +3 -2
  30. package/packages/media-execution/src/toolchain.ts +10 -36
  31. package/packages/provider-hiapi/README.md +75 -0
  32. package/packages/provider-hiapi/package.json +24 -0
  33. package/packages/provider-hiapi/src/activation.ts +62 -0
  34. package/packages/provider-hiapi/src/errors.ts +44 -0
  35. package/packages/provider-hiapi/src/index.ts +2 -0
  36. package/packages/provider-hiapi/src/mapping.ts +129 -0
  37. package/packages/provider-hiapi/src/provider.ts +215 -0
  38. package/packages/provider-hiapi/src/routes.ts +154 -0
  39. package/packages/provider-hyperframes-local/src/program.ts +2 -1
  40. package/packages/provider-media-local/README.md +2 -1
  41. package/packages/provider-monid/README.md +60 -0
  42. package/packages/provider-monid/package.json +19 -0
  43. package/packages/provider-monid/src/activation.ts +62 -0
  44. package/packages/provider-monid/src/errors.ts +55 -0
  45. package/packages/provider-monid/src/index.ts +2 -0
  46. package/packages/provider-monid/src/mapping.ts +33 -0
  47. package/packages/provider-monid/src/provider.ts +242 -0
  48. package/packages/provider-monid/src/routes.ts +103 -0
  49. package/packages/provider-pollo/README.md +59 -0
  50. package/packages/provider-pollo/package.json +22 -0
  51. package/packages/provider-pollo/src/activation.ts +62 -0
  52. package/packages/provider-pollo/src/errors.ts +41 -0
  53. package/packages/provider-pollo/src/index.ts +2 -0
  54. package/packages/provider-pollo/src/mapping.ts +60 -0
  55. package/packages/provider-pollo/src/provider.ts +194 -0
  56. package/packages/provider-pollo/src/routes.ts +120 -0
  57. package/packages/provider-tokendance/README.md +67 -0
  58. package/packages/provider-tokendance/package.json +21 -0
  59. package/packages/provider-tokendance/src/activation.ts +62 -0
  60. package/packages/provider-tokendance/src/errors.ts +47 -0
  61. package/packages/provider-tokendance/src/index.ts +2 -0
  62. package/packages/provider-tokendance/src/mapping.ts +60 -0
  63. package/packages/provider-tokendance/src/provider.ts +268 -0
  64. package/packages/provider-tokendance/src/routes.ts +192 -0
  65. package/packages/runtime-local/src/programs.ts +2 -0
  66. package/packages/video-cli/package.json +4 -0
  67. package/packages/credential-store-file/src/paths.ts +0 -16
  68. /package/packages/{build-result → file-io-node}/src/replace-file.ts +0 -0
@@ -0,0 +1,60 @@
1
+ # `@hypit/provider-monid`
2
+
3
+ Hypit Runtime Provider for a [Monid](https://monid.ai) workspace. It runs Monid's `bytedance`
4
+ Seedance endpoints through `POST /v1/run`, polls `GET /v1/runs/{runId}` until the run is terminal,
5
+ downloads the returned `video_url` and stores the video 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
+
14
+ Monid's catalogue also lists MiniMax H3 and other generation endpoints; their input schemas are
15
+ published only through the authenticated `inspect` operation, so this Provider maps the Seedance
16
+ endpoints whose request body Monid documents publicly.
17
+
18
+ The run `input` is the BytePlus ModelArk request the endpoint relays: one `content` array holding
19
+ the prompt and each media input as a typed item with its `role` (`first_frame`, `last_frame`,
20
+ `reference_image`, `reference_video`, `reference_audio`), then `resolution`, `ratio`, `duration` and
21
+ `generate_audio`. Monid documents no web search field for these endpoints, so `web-search="true"`
22
+ is unsupported. Seedance 2.5 frame mode (`first-frame` present) requires `aspect-ratio="adaptive"`.
23
+ Seedance visual references may carry `person-reference`; the Provider accepts the declaration and
24
+ transmits nothing for it, since the endpoint has no field for it. Seedance rejects reference images
25
+ and videos that contain a real human face; Monid offers no way to register authorized portrait
26
+ material, so such a request fails with the upstream moderation error.
27
+
28
+ Reference media are uploaded through the workspace file system Monid provides for this purpose
29
+ (`sfs`): `/put` signs an upload for `hypit/<resource>.<ext>`, the bytes are `PUT` to that URL, and
30
+ `/cat` mints a one-day download URL that the generation endpoint fetches. Uploaded files stay in the
31
+ workspace until removed with `/rm`. Embedded callers may replace this transport with
32
+ `publicAssetUrl`.
33
+
34
+ A run ends with Monid's own status (`COMPLETED`, `FAILED`, `BLOCKED`, `STOPPED`, `TIMED_OUT`) and,
35
+ when completed, the provider's HTTP status in `providerResponse`. A completed run whose provider
36
+ answered 4xx or 5xx fails with that status and the provider's error message; a `BLOCKED` run keeps
37
+ Monid's `reason`. Signed URLs in messages are redacted.
38
+
39
+ Runtime Profile example:
40
+
41
+ ```json
42
+ {
43
+ "endpoints": {
44
+ "monid.default": {
45
+ "use": "@hypit/provider-monid",
46
+ "pool": "monid.default",
47
+ "config": {
48
+ "apiKey": { "store": "platform", "key": "monid.api-key" },
49
+ "defaultConcurrency": 3,
50
+ "pollIntervalMs": 10000
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ `baseUrl` defaults to `https://api.monid.ai`. Store the API key with
58
+ `hypit auth login monid.default --runtime hypit.runtime.json`. Optional `requestTimeoutMs`,
59
+ `operationTimeoutMs` and `actionLimits` bound single HTTP calls, the whole remote run and action
60
+ concurrency; file-system runs poll at most every five seconds within `requestTimeoutMs`.
@@ -0,0 +1,19 @@
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/protocol": "workspace:*",
13
+ "@hypit/runtime": "workspace:*",
14
+ "@hypit/runtime-kit": "workspace:*"
15
+ },
16
+ "devDependencies": {
17
+ "@hypit/seedance": "workspace:*"
18
+ }
19
+ }
@@ -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,2 @@
1
+ export { createMonidProvider, monidProviderModuleRef } from "./provider.js";
2
+ export type { CreateMonidProviderOptions } from "./provider.js";
@@ -0,0 +1,33 @@
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
+
6
+ /**
7
+ * Monid `bytedance` endpoints and the fields their ModelArk request body takes. Media fields name
8
+ * the `role` of a `content` item; routes.ts folds them into that array. `personReference` is
9
+ * accepted on visual references and not transmitted: the endpoint has no field for it.
10
+ */
11
+ const seedance = (name: string, endpoint: string): GenerationWireMapping => ({
12
+ capability: { module: SEEDANCE, name }, result: "video", routes: [{ model: endpoint }],
13
+ fields: {
14
+ prompt: { as: "value", field: "text" },
15
+ referenceImage: { as: "urlArray", field: "reference_image", resourceFields: ["personReference"] },
16
+ referenceVideo: { as: "urlArray", field: "reference_video", resourceFields: ["personReference"] },
17
+ referenceAudio: { as: "urlArray", field: "reference_audio" },
18
+ firstFrame: { as: "url", field: "first_frame", resourceFields: ["personReference"] },
19
+ lastFrame: { as: "url", field: "last_frame", resourceFields: ["personReference"] },
20
+ resolution: { as: "value", field: "resolution" },
21
+ aspectRatio: { as: "value", field: "ratio" },
22
+ duration: { as: "value", field: "duration" },
23
+ generateAudio: { as: "value", field: "generate_audio" },
24
+ webSearch: { as: "value", field: "web_search" },
25
+ },
26
+ });
27
+
28
+ export const monidMappings: readonly GenerationWireMapping[] = [
29
+ seedance("seedance-2", "/v1/video/seedance-2.0"),
30
+ seedance("seedance-2-fast", "/v1/video/seedance-2.0-fast"),
31
+ seedance("seedance-2-mini", "/v1/video/seedance-2.0-mini"),
32
+ seedance("seedance-2.5", "/v1/video/seedance-2.5"),
33
+ ];
@@ -0,0 +1,242 @@
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 { monidRouteForCapability, monidRoutes } from "./routes.js";
10
+ import { MonidHttpError, MonidServiceError, monidRunFailure, monidTerminalStatuses } from "./errors.js";
11
+
12
+ export const monidProviderModuleRef = { name: "@hypit/provider-monid", version: "1" } as const;
13
+
14
+ export type CreateMonidProviderOptions = {
15
+ readonly instance?: string;
16
+ readonly pool?: string;
17
+ readonly baseUrl?: string;
18
+ readonly apiKey?: CredentialRef;
19
+ readonly defaultConcurrency?: number;
20
+ readonly actionLimits?: import("@hypit/endpoint-kit").EndpointActionLimits;
21
+ readonly pollIntervalMs?: number;
22
+ readonly requestTimeoutMs?: number;
23
+ readonly operationTimeoutMs?: number;
24
+ readonly fetch?: typeof globalThis.fetch;
25
+ /** Publish a referenced Resource at a URL the service can fetch; replaces the workspace file system upload. */
26
+ readonly publicAssetUrl?: (artifact: BlobRef, artifacts: ResourceStore, fields?: Readonly<Record<string, string | number | boolean>>) => Promise<string>;
27
+ };
28
+
29
+ type Handle = {
30
+ readonly contract: "hypit.monid-operation@1";
31
+ readonly runId: string;
32
+ readonly route: string;
33
+ readonly startedAt: number;
34
+ readonly url?: string;
35
+ };
36
+
37
+ function assert(condition: unknown, message: string): asserts condition { if (!condition) throw new Error(message); }
38
+ function object(value: unknown, subject: string): Record<string, unknown> {
39
+ assert(value !== null && typeof value === "object" && !Array.isArray(value), `${subject} must be an object`);
40
+ return value as Record<string, unknown>;
41
+ }
42
+ function capabilityKey(capability: CapabilityRef): string { return `${capability.module.name}@${capability.module.version}#${capability.name}`; }
43
+ function apiBaseUrl(value: string): string {
44
+ let trimmed = value.trim();
45
+ while (trimmed.endsWith("/")) trimmed = trimmed.slice(0, -1);
46
+ assert(trimmed.length > 0, "Monid base URL is empty");
47
+ return trimmed;
48
+ }
49
+ function apiKey(credentials: Readonly<Record<string, EndpointCredential>>): string {
50
+ const value = credentials.apiKey?.secret;
51
+ assert(typeof value === "string" && value.length > 0, "Monid apiKey credential is unavailable; store a Monid API key for this Endpoint");
52
+ return value;
53
+ }
54
+ function failureMessage(error: unknown): string {
55
+ return error instanceof Error ? error.message : String(error);
56
+ }
57
+ function failure(error: unknown): EndpointOutcome {
58
+ return { status: "failed", failure: { code: error instanceof MonidServiceError ? error.code : "MONID_ERROR", message: failureMessage(error) } };
59
+ }
60
+ function runId(run: Record<string, unknown>): string {
61
+ assert(typeof run.runId === "string" && run.runId.length > 0, "Monid response has no runId");
62
+ return run.runId;
63
+ }
64
+ function httpsUrl(value: unknown, subject: string): string {
65
+ assert(typeof value === "string" && /^https?:\/\//u.test(value), `${subject} has no URL`);
66
+ return value;
67
+ }
68
+ /** The relayed ModelArk result; Monid returns the task's `video_url` in the run output. */
69
+ function outputVideoUrl(run: Record<string, unknown>): string {
70
+ const output = object(run.output, "Monid run output");
71
+ const nested = output.content !== undefined ? object(output.content, "Monid run output content").video_url : undefined;
72
+ return httpsUrl(output.video_url ?? nested, "Monid run output");
73
+ }
74
+ const extensions: Readonly<Record<string, string>> = {
75
+ "image/jpeg": "jpg", "image/png": "png", "image/webp": "webp", "video/mp4": "mp4", "video/quicktime": "mov",
76
+ "audio/wav": "wav", "audio/x-wav": "wav", "audio/mpeg": "mp3",
77
+ };
78
+
79
+ class MonidClient {
80
+ constructor(readonly baseUrl: string, readonly timeout: number, readonly pollIntervalMs: number, readonly fetcher: typeof globalThis.fetch) {}
81
+ async json(path: string, key: string, init: RequestInit = {}): Promise<{ readonly status: number; readonly body: Record<string, unknown> }> {
82
+ const deadline = requestDeadline(this.timeout);
83
+ try {
84
+ const response = await deadline.wait(this.fetcher(`${this.baseUrl}${path}`, {
85
+ ...init, signal: deadline.signal, headers: { authorization: `Bearer ${key}`, ...(init.headers ?? {}) },
86
+ }));
87
+ const text = await deadline.wait(response.text());
88
+ let body: unknown;
89
+ try { body = text.length === 0 ? {} : JSON.parse(text); } catch { body = undefined; }
90
+ // A synchronous run mirrors the provider's HTTP status while still returning the run itself.
91
+ const run = body !== null && typeof body === "object" && !Array.isArray(body) && typeof (body as Record<string, unknown>).runId === "string";
92
+ if (!response.ok && !run) throw new MonidHttpError(response.status, response, text, { method: init.method ?? "GET", path });
93
+ assert(body !== undefined, `Monid returned invalid JSON (${response.status})`);
94
+ return { status: response.status, body: object(body, "Monid response") };
95
+ } finally { deadline.finish(); }
96
+ }
97
+ async run(request: { readonly provider: string; readonly endpoint: string; readonly input: Record<string, unknown> }, key: string) {
98
+ return await this.json("/v1/run", key, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(request) });
99
+ }
100
+ async getRun(id: string, key: string): Promise<Record<string, unknown>> {
101
+ return (await this.json(`/v1/runs/${encodeURIComponent(id)}`, key)).body;
102
+ }
103
+ /** Run a short workspace operation to completion and return its provider output. */
104
+ async completeRun(request: { readonly provider: string; readonly endpoint: string; readonly input: Record<string, unknown> }, key: string): Promise<Record<string, unknown>> {
105
+ let run = (await this.run(request, key)).body;
106
+ const id = runId(run);
107
+ const deadline = requestDeadline(this.timeout);
108
+ try {
109
+ while (!monidTerminalStatuses.includes(String(run.status) as typeof monidTerminalStatuses[number])) {
110
+ await deadline.wait(new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs)));
111
+ run = await this.getRun(id, key);
112
+ }
113
+ } finally { deadline.finish(); }
114
+ const rejected = monidRunFailure(run, id);
115
+ if (rejected !== undefined) throw rejected;
116
+ return object(run.output, `Monid ${request.provider}${request.endpoint} output`);
117
+ }
118
+ /** Upload through the workspace file system (`sfs`) and mint a URL third parties can fetch. */
119
+ async publish(artifact: BlobRef, resources: ResourceStore, key: string): Promise<string> {
120
+ const bytes = await resources.get(artifact.resource);
121
+ assert(bytes !== undefined && bytes.byteLength === artifact.size, `Reference Resource ${artifact.resource} is unavailable or has changed`);
122
+ const path = `hypit/${artifact.resource}.${extensions[artifact.mediaType] ?? artifact.mediaType.split("/")[1] ?? "bin"}`;
123
+ const put = await this.completeRun({ provider: "sfs", endpoint: "/put", input: { path, sizeBytes: bytes.byteLength } }, key);
124
+ const uploadUrl = httpsUrl(put.uploadUrl, "Monid sfs /put output");
125
+ const deadline = requestDeadline(this.timeout);
126
+ try {
127
+ const response = await deadline.wait(this.fetcher(uploadUrl, { method: "PUT", body: new Blob([new Uint8Array(bytes)]), signal: deadline.signal }));
128
+ assert(response.ok, `Monid file upload returned HTTP ${response.status}`);
129
+ } finally { deadline.finish(); }
130
+ const cat = await this.completeRun({ provider: "sfs", endpoint: "/cat", input: { path, ttl: "1d" } }, key);
131
+ return httpsUrl(cat.url, "Monid sfs /cat output");
132
+ }
133
+ async download(url: string): Promise<{ readonly bytes: Uint8Array; readonly mediaType: string }> {
134
+ const deadline = requestDeadline(this.timeout);
135
+ try {
136
+ const response = await deadline.wait(this.fetcher(url, { signal: deadline.signal }));
137
+ if (!response.ok) throw new Error(`Monid asset returned HTTP ${response.status}`);
138
+ return { bytes: new Uint8Array(await deadline.wait(response.arrayBuffer())), mediaType: response.headers.get("content-type")?.split(";", 1)[0] ?? "application/octet-stream" };
139
+ } finally { deadline.finish(); }
140
+ }
141
+ }
142
+
143
+ function resolverFor(client: MonidClient, context: EndpointInvocationContext, publicAssetUrl: CreateMonidProviderOptions["publicAssetUrl"]): GenerationArtifactUrlResolver {
144
+ const resolved = new Map<string, Promise<string>>();
145
+ return (artifact, fields) => {
146
+ const existing = resolved.get(artifact.resource);
147
+ if (existing !== undefined) return existing;
148
+ const promise = publicAssetUrl === undefined
149
+ ? client.publish(artifact, context.resources, apiKey(context.credentials))
150
+ : publicAssetUrl(artifact, context.resources, fields);
151
+ resolved.set(artifact.resource, promise);
152
+ return promise;
153
+ };
154
+ }
155
+
156
+ function endpoint(client: MonidClient, pollIntervalMs: number, maxOperationMs: number, publicAssetUrl: CreateMonidProviderOptions["publicAssetUrl"]): AsyncEndpoint {
157
+ return {
158
+ async start(context) {
159
+ try {
160
+ const route = monidRouteForCapability(context.need.capability);
161
+ assert(route !== undefined, "Monid does not implement this exact capability");
162
+ const request = route.prepare(context.need.constraints);
163
+ await context.reportProgress?.({ phase: `Preparing Monid request: bytedance ${request.endpoint}` });
164
+ let input: Record<string, unknown>;
165
+ try {
166
+ input = await request.compile(resolverFor(client, context, publicAssetUrl));
167
+ } catch (error) {
168
+ throw new MonidServiceError(error instanceof MonidServiceError ? error.code : "MONID_ERROR",
169
+ `Monid request preparation failed; endpoint=${request.endpoint}; generation not submitted: ${failureMessage(error)}`);
170
+ }
171
+ await context.reportProgress?.({ phase: `Submitting Monid request: bytedance ${request.endpoint}` });
172
+ const { body: run } = await client.run({ provider: "bytedance", endpoint: request.endpoint, input }, apiKey(context.credentials));
173
+ const handle: Handle = { contract: "hypit.monid-operation@1", runId: runId(run), route: route.key, startedAt: Date.now() };
174
+ const receipt = { id: handle.runId };
175
+ const ended = monidTerminalStatuses.includes(String(run.status) as typeof monidTerminalStatuses[number]);
176
+ await context.checkpoint?.({ handle: canonicalize(handle), receipt, ...(ended ? { remoteEnded: true as const } : {}) });
177
+ if (!ended) return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: String(run.status) }), receipt };
178
+ const rejected = monidRunFailure(run, handle.runId);
179
+ if (rejected !== undefined) return { ...failure(rejected), receipt };
180
+ return { status: "ready", handle: canonicalize({ ...handle, url: outputVideoUrl(run) }), receipt };
181
+ } catch (error) {
182
+ return failure(error);
183
+ }
184
+ },
185
+ async poll(context) {
186
+ try {
187
+ const handle = object(context.handle, "Monid handle") as unknown as Handle;
188
+ const route = monidRouteForCapability(context.need.capability);
189
+ assert(route !== undefined && handle.contract === "hypit.monid-operation@1" && handle.route === route.key, "Monid handle is invalid");
190
+ const receipt = { id: handle.runId };
191
+ if (Date.now() - handle.startedAt > maxOperationMs) {
192
+ return { status: "failed", receipt, failure: { code: "MONID_OPERATION_TIMEOUT", message: `Monid run ${handle.runId} exceeded this Provider's operationTimeoutMs (${maxOperationMs}); remote outcome is unknown` } };
193
+ }
194
+ const run = await client.getRun(handle.runId, apiKey(context.credentials));
195
+ const status = String(run.status);
196
+ if (!monidTerminalStatuses.includes(status as typeof monidTerminalStatuses[number])) {
197
+ return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: status }), receipt };
198
+ }
199
+ const rejected = monidRunFailure(run, handle.runId);
200
+ if (rejected !== undefined) return { ...failure(rejected), receipt };
201
+ return { status: "ready", handle: canonicalize({ ...handle, url: outputVideoUrl(run) }), receipt };
202
+ } catch (error) {
203
+ return failure(error);
204
+ }
205
+ },
206
+ async collect(context) {
207
+ try {
208
+ const handle = object(context.handle, "Monid handle") as unknown as Handle;
209
+ const route = monidRouteForCapability(context.need.capability);
210
+ assert(route !== undefined && handle.route === route.key, "Monid collection route differs");
211
+ await context.reportProgress?.({ phase: "Receiving generated video" });
212
+ const downloaded = await client.download(httpsUrl(handle.url, "Monid handle"));
213
+ const blob = await context.resources.put(downloaded.bytes, downloaded.mediaType);
214
+ return { status: "completed", result: { value: route.packageResult([blob]) }, receipt: { id: handle.runId } };
215
+ } catch (error) {
216
+ return failure(error);
217
+ }
218
+ },
219
+ };
220
+ }
221
+
222
+ export function createMonidProvider(options: CreateMonidProviderOptions = {}) {
223
+ const requestTimeoutMs = options.requestTimeoutMs ?? 300_000;
224
+ const operationTimeoutMs = options.operationTimeoutMs ?? 30 * 60_000;
225
+ const pollIntervalMs = options.pollIntervalMs ?? 10_000;
226
+ for (const [name, value] of Object.entries({ requestTimeoutMs, operationTimeoutMs, pollIntervalMs })) {
227
+ assert(Number.isSafeInteger(value) && value > 0, `Monid ${name} must be a positive integer`);
228
+ }
229
+ const client = new MonidClient(apiBaseUrl(options.baseUrl ?? "https://api.monid.ai"), requestTimeoutMs, Math.min(pollIntervalMs, 5_000), options.fetch ?? globalThis.fetch);
230
+ const asyncEndpoint = endpoint(client, pollIntervalMs, operationTimeoutMs, options.publicAssetUrl);
231
+ return defineEndpointPackage({
232
+ module: monidProviderModuleRef, facet: "gateway", instance: options.instance ?? "monid.default", pool: options.pool ?? options.instance ?? "monid.default",
233
+ pricing: { kind: "page", url: "https://monid.ai/tools/bytedance" },
234
+ credentials: { apiKey: options.apiKey ?? credentialRef("os", "monid.api-key") },
235
+ credentialInputs: { apiKey: { label: "Monid API key" } },
236
+ defaultConcurrency: options.defaultConcurrency ?? 4,
237
+ ...(options.actionLimits === undefined ? {} : { actionLimits: options.actionLimits }),
238
+ capabilities: monidRoutes.map((route) => ({
239
+ capability: route.capability, returns: route.returns, lifecycle: "asynchronous" as const, endpoint: asyncEndpoint, capacity: route.capability.name, supports: route.supports,
240
+ })),
241
+ });
242
+ }
@@ -0,0 +1,103 @@
1
+ import {
2
+ compileWireRequest,
3
+ selectWireModelForRequest,
4
+ generationTypes,
5
+ sealGeneratedVideoSet,
6
+ } from "@hypit/generation";
7
+ import type { GenerationArtifactUrlResolver, GenerationRequest, GenerationWireMapping } from "@hypit/generation";
8
+ import { canonicalize } from "@hypit/protocol";
9
+ import type { BlobRef, CapabilityRef, CanonicalValue, StoredValue, TypeRef } from "@hypit/protocol";
10
+ import type { EndpointRequest, EndpointSupport } from "@hypit/endpoint-kit";
11
+ import { monidMappings } from "./mapping.js";
12
+
13
+ export type MonidPreparedRequest = {
14
+ /** Monid endpoint path under the `bytedance` provider. */
15
+ readonly endpoint: string;
16
+ readonly compile: (resolve: GenerationArtifactUrlResolver) => Promise<Record<string, unknown>>;
17
+ };
18
+
19
+ export type MonidRoute = GenerationWireMapping & {
20
+ readonly key: string;
21
+ readonly returns: TypeRef;
22
+ readonly supports: (request: EndpointRequest) => EndpointSupport;
23
+ readonly prepare: (constraints: CanonicalValue) => MonidPreparedRequest;
24
+ readonly packageResult: (artifacts: readonly BlobRef[]) => StoredValue;
25
+ };
26
+
27
+ function scalar(request: GenerationRequest, port: string): string | number | boolean | undefined {
28
+ const value = request.ports[port]?.[0];
29
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : undefined;
30
+ }
31
+ function present(request: GenerationRequest, port: string): boolean {
32
+ return (request.ports[port]?.length ?? 0) > 0;
33
+ }
34
+ function strings(value: unknown): readonly string[] {
35
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : [];
36
+ }
37
+
38
+ function rejection(mapping: GenerationWireMapping, request: GenerationRequest): string | undefined {
39
+ if (scalar(request, "webSearch") === true) {
40
+ return "Monid's Seedance endpoints document no web search field";
41
+ }
42
+ if (mapping.capability.name === "seedance-2.5" && present(request, "firstFrame") && scalar(request, "aspectRatio") !== "adaptive") {
43
+ return `Seedance 2.5 frame mode takes only aspect-ratio adaptive, not ${String(scalar(request, "aspectRatio"))}`;
44
+ }
45
+ return undefined;
46
+ }
47
+
48
+ function contentItem(type: "image_url" | "video_url" | "audio_url", url: string, role: string) {
49
+ return { type, [type]: { url }, role };
50
+ }
51
+
52
+ /** The ModelArk request body Monid relays: one `content` array of typed, role-tagged items plus the output settings. */
53
+ function arkInput(input: Record<string, unknown>): Record<string, unknown> {
54
+ const first = input.first_frame;
55
+ const last = input.last_frame;
56
+ return {
57
+ content: [
58
+ { type: "text", text: input.text },
59
+ ...(typeof first === "string" ? [contentItem("image_url", first, "first_frame")] : []),
60
+ ...(typeof last === "string" ? [contentItem("image_url", last, "last_frame")] : []),
61
+ ...strings(input.reference_image).map((url) => contentItem("image_url", url, "reference_image")),
62
+ ...strings(input.reference_video).map((url) => contentItem("video_url", url, "reference_video")),
63
+ ...strings(input.reference_audio).map((url) => contentItem("audio_url", url, "reference_audio")),
64
+ ],
65
+ resolution: input.resolution,
66
+ ratio: input.ratio,
67
+ duration: input.duration,
68
+ generate_audio: input.generate_audio,
69
+ };
70
+ }
71
+
72
+ function capabilityKey(capability: CapabilityRef): string {
73
+ return `${capability.module.name}@${capability.module.version}#${capability.name}`;
74
+ }
75
+
76
+ export const monidRoutes: readonly MonidRoute[] = monidMappings.map((mapping) => ({
77
+ ...mapping,
78
+ key: capabilityKey(mapping.capability),
79
+ returns: generationTypes.videoSet,
80
+ supports: (request) => {
81
+ const reason = rejection(mapping, request.constraints as unknown as GenerationRequest);
82
+ return reason === undefined ? { status: "supported" } : { status: "unsupported", reason };
83
+ },
84
+ prepare: (constraints) => {
85
+ const request = constraints as unknown as GenerationRequest;
86
+ const reason = rejection(mapping, request);
87
+ if (reason !== undefined) throw new Error(reason);
88
+ return {
89
+ endpoint: selectWireModelForRequest(mapping, request),
90
+ compile: async (resolve) => arkInput((await compileWireRequest(mapping, request, resolve)).input as Record<string, unknown>),
91
+ };
92
+ },
93
+ packageResult: (artifacts) => ({
94
+ kind: "inline",
95
+ value: canonicalize(sealGeneratedVideoSet({ videos: artifacts })),
96
+ }),
97
+ }));
98
+
99
+ const byCapability = new Map(monidRoutes.map((route) => [route.key, route]));
100
+
101
+ export function monidRouteForCapability(capability: CapabilityRef): MonidRoute | undefined {
102
+ return byCapability.get(capabilityKey(capability));
103
+ }
@@ -0,0 +1,59 @@
1
+ # `@hypit/provider-pollo`
2
+
3
+ Hypit Runtime Provider for a [Pollo AI](https://docs.pollo.ai) API platform account. Each capability
4
+ posts `{ "input": … }` to the model's generation path with the `x-api-key` header, polls
5
+ `GET /v1/generation/{taskId}/status` until every generation reaches `succeed`, downloads each
6
+ generation's `url` and stores the files in the current Build.
7
+
8
+ | Capability | Pollo generation path |
9
+ | --- | --- |
10
+ | `@hypit/minimax-h3@1#minimax-h3` | `/v1/generation/minimax/minimax-h3/video` |
11
+ | `@hypit/grok-imagine@1#grok-imagine-video-1.5-preview` | `/v1/generation/xai/grok-imagine-video-1-5/video` |
12
+ | `@hypit/gpt-image@1#gpt-image-2` | `/v1/generation/openai/gpt-image-2/image` |
13
+ | `@hypit/nano-banana@1#nano-banana-2` | `/v1/generation/google/nano-banana-2/image` |
14
+ | `@hypit/nano-banana@1#nano-banana-pro` | `/v1/generation/google/nano-banana-pro/image` |
15
+
16
+ Pollo documents further models; this Provider maps only the models the Distribution already
17
+ describes. Seedance and Seedream are not offered by Pollo.
18
+
19
+ MiniMax H3 requests send `prompt`, `duration`, `resolution` and `aspectRatio`; a first frame is
20
+ `image`, a last frame `imageTail`, and reference images, videos and audio become typed entries of
21
+ `refs`. Pollo renders 480p when `resolution` is omitted.
22
+
23
+ Service limits this Provider reports as unsupported before submitting:
24
+
25
+ - Grok Imagine 1.5 animates exactly one image and has no aspect-ratio field; author
26
+ `aspect-ratio="auto"`.
27
+ - GPT Image 2 renders `1:1`, `3:2`, `2:3`, `16:9`, `9:16`, `4:3`, `3:4`, `21:9` and `auto`;
28
+ other ratios are unsupported. Pollo's `quality` field is left at its default.
29
+ - Nano Banana takes an explicit aspect ratio, not `auto`. `output-format` has no Pollo field and is
30
+ not sent.
31
+
32
+ Pollo accepts reference media only by HTTP(S) URL. Configure `publicAssetUrl` when embedding the
33
+ Provider; without it, a request with reference media fails before submission, while text-only
34
+ requests proceed.
35
+
36
+ Runtime Profile example:
37
+
38
+ ```json
39
+ {
40
+ "endpoints": {
41
+ "pollo.default": {
42
+ "use": "@hypit/provider-pollo",
43
+ "pool": "pollo.default",
44
+ "config": {
45
+ "apiKey": { "store": "platform", "key": "pollo.api-key" },
46
+ "defaultConcurrency": 3,
47
+ "pollIntervalMs": 10000
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ `baseUrl` defaults to `https://pollo.ai/api/platform`. Store the API key with
55
+ `hypit auth login pollo.default --runtime hypit.runtime.json`. Optional `requestTimeoutMs`,
56
+ `operationTimeoutMs` and `actionLimits` bound single HTTP calls, the whole remote task and action
57
+ concurrency. HTTP failures keep Pollo's `errorCode`, message and `requestId`; a failed generation
58
+ keeps its `failMsg`, with any signed URL in the message redacted. Pollo stores generated files for
59
+ 14 days; the Provider downloads them when the task completes.