@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.
Files changed (35) hide show
  1. package/package.json +5 -1
  2. package/packages/media-execution/src/execute.ts +3 -2
  3. package/packages/provider-hiapi/README.md +75 -0
  4. package/packages/provider-hiapi/package.json +24 -0
  5. package/packages/provider-hiapi/src/activation.ts +62 -0
  6. package/packages/provider-hiapi/src/errors.ts +44 -0
  7. package/packages/provider-hiapi/src/index.ts +2 -0
  8. package/packages/provider-hiapi/src/mapping.ts +129 -0
  9. package/packages/provider-hiapi/src/provider.ts +215 -0
  10. package/packages/provider-hiapi/src/routes.ts +154 -0
  11. package/packages/provider-monid/README.md +60 -0
  12. package/packages/provider-monid/package.json +19 -0
  13. package/packages/provider-monid/src/activation.ts +62 -0
  14. package/packages/provider-monid/src/errors.ts +55 -0
  15. package/packages/provider-monid/src/index.ts +2 -0
  16. package/packages/provider-monid/src/mapping.ts +33 -0
  17. package/packages/provider-monid/src/provider.ts +242 -0
  18. package/packages/provider-monid/src/routes.ts +103 -0
  19. package/packages/provider-pollo/README.md +59 -0
  20. package/packages/provider-pollo/package.json +22 -0
  21. package/packages/provider-pollo/src/activation.ts +62 -0
  22. package/packages/provider-pollo/src/errors.ts +41 -0
  23. package/packages/provider-pollo/src/index.ts +2 -0
  24. package/packages/provider-pollo/src/mapping.ts +60 -0
  25. package/packages/provider-pollo/src/provider.ts +194 -0
  26. package/packages/provider-pollo/src/routes.ts +120 -0
  27. package/packages/provider-tokendance/README.md +67 -0
  28. package/packages/provider-tokendance/package.json +21 -0
  29. package/packages/provider-tokendance/src/activation.ts +62 -0
  30. package/packages/provider-tokendance/src/errors.ts +47 -0
  31. package/packages/provider-tokendance/src/index.ts +2 -0
  32. package/packages/provider-tokendance/src/mapping.ts +60 -0
  33. package/packages/provider-tokendance/src/provider.ts +268 -0
  34. package/packages/provider-tokendance/src/routes.ts +192 -0
  35. package/packages/video-cli/package.json +4 -0
@@ -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.
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@hypit/provider-pollo",
3
+ "version": "0.0.0-dev",
4
+ "license": "SEE LICENSE IN LICENSE",
5
+ "private": true,
6
+ "type": "module",
7
+ "exports": { ".": "./src/index.ts" },
8
+ "hypit": { "activation": "./src/activation.ts" },
9
+ "dependencies": {
10
+ "@hypit/endpoint-kit": "workspace:*",
11
+ "@hypit/generation": "workspace:*",
12
+ "@hypit/protocol": "workspace:*",
13
+ "@hypit/runtime": "workspace:*",
14
+ "@hypit/runtime-kit": "workspace:*"
15
+ },
16
+ "devDependencies": {
17
+ "@hypit/gpt-image": "workspace:*",
18
+ "@hypit/grok-imagine": "workspace:*",
19
+ "@hypit/minimax-h3": "workspace:*",
20
+ "@hypit/nano-banana": "workspace:*"
21
+ }
22
+ }
@@ -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 { createPolloProvider } from "./provider.js";
12
+
13
+ const adapter = createRuntimeEndpointAdapterFacet({
14
+ use: "@hypit/provider-pollo",
15
+ activate(context) {
16
+ if (context.pool === undefined) throw new Error("Pollo Provider Pool is required");
17
+ const config = runtimeConfigObject(context.config, "Pollo");
18
+ runtimeConfigExact(config, [
19
+ "baseUrl",
20
+ "apiKey",
21
+ "defaultConcurrency",
22
+ "actionLimits",
23
+ "pollIntervalMs",
24
+ "requestTimeoutMs",
25
+ "operationTimeoutMs",
26
+ ], "Pollo");
27
+ const baseUrl = runtimeConfigString(config.baseUrl, "Pollo 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("Pollo baseUrl must use HTTPS or loopback");
32
+ }
33
+ }
34
+ const apiKey = runtimeConfigCredentialRef(config.apiKey, "Pollo apiKey");
35
+ if (apiKey === undefined) throw new Error("Pollo apiKey CredentialRef is required");
36
+ const actionLimits = runtimeConfigActionLimits(config.actionLimits);
37
+ const defaultConcurrency = runtimeConfigPositiveInteger(config.defaultConcurrency, "Pollo defaultConcurrency");
38
+ const pollIntervalMs = runtimeConfigPositiveInteger(config.pollIntervalMs, "Pollo pollIntervalMs");
39
+ const requestTimeoutMs = runtimeConfigPositiveInteger(config.requestTimeoutMs, "Pollo requestTimeoutMs");
40
+ const operationTimeoutMs = runtimeConfigPositiveInteger(config.operationTimeoutMs, "Pollo operationTimeoutMs");
41
+ return {
42
+ endpoint: createPolloProvider({
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,41 @@
1
+ /** Pollo's `{ errorCode, message, code, requestId }` envelope and generation `failMsg`, 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 safePolloReason(value: string): string {
12
+ return value.replace(/https?:\/\/\S+/giu, "[redacted-url]");
13
+ }
14
+
15
+ export class PolloServiceError extends Error {
16
+ constructor(readonly code: string, message: string) { super(message); }
17
+ }
18
+
19
+ export class PolloHttpError extends PolloServiceError {
20
+ constructor(readonly status: number, bodyText: string, request: { readonly method: string; readonly path: string }) {
21
+ let body: Record<string, unknown> | undefined;
22
+ try { body = record(JSON.parse(bodyText)); } catch { /* Non-JSON gateway failures still have HTTP evidence. */ }
23
+ const code = text(body?.errorCode) ?? "POLLO_HTTP_ERROR";
24
+ const reason = text(body?.message) ?? (body === undefined ? text(bodyText.slice(0, 2000)) : undefined);
25
+ const requestId = text(body?.requestId);
26
+ const facts = [
27
+ `Pollo HTTP ${status}`, code, `${request.method} ${request.path}`,
28
+ ...(requestId === undefined ? [] : [`request=${requestId}`]),
29
+ ];
30
+ super(code, `${facts.join("; ")}${reason === undefined ? "" : `: ${safePolloReason(reason)}`}`);
31
+ }
32
+ }
33
+
34
+ /** The first failed generation of a task; `undefined` when none failed. */
35
+ export function polloTaskFailure(generations: readonly Record<string, unknown>[], id: string): PolloServiceError | undefined {
36
+ const failed = generations.find((generation) => generation.status === "failed");
37
+ if (failed === undefined) return undefined;
38
+ const reason = text(failed.failMsg);
39
+ return new PolloServiceError("POLLO_TASK_FAILED",
40
+ `Pollo task ${id} failed${reason === undefined ? "" : `: ${safePolloReason(reason)}`}`);
41
+ }
@@ -0,0 +1,2 @@
1
+ export { createPolloProvider, polloProviderModuleRef } from "./provider.js";
2
+ export type { CreatePolloProviderOptions } from "./provider.js";
@@ -0,0 +1,60 @@
1
+ import type { ModuleRef } from "@hypit/protocol";
2
+ import type { GenerationWireMapping } from "@hypit/generation";
3
+
4
+ const MINIMAX: ModuleRef = { name: "@hypit/minimax-h3", version: "1" };
5
+ const GROK: ModuleRef = { name: "@hypit/grok-imagine", version: "1" };
6
+ const GPT_IMAGE: ModuleRef = { name: "@hypit/gpt-image", version: "1" };
7
+ const NANO_BANANA: ModuleRef = { name: "@hypit/nano-banana", version: "1" };
8
+
9
+ /**
10
+ * Pollo generation paths and the `input` fields each documents. The route model is the request
11
+ * path under the Pollo platform base URL. Reference fields prefixed `refs_` are folded into MiniMax's
12
+ * typed `refs` array by routes.ts.
13
+ */
14
+ export const polloMappings: readonly GenerationWireMapping[] = [
15
+ {
16
+ capability: { module: MINIMAX, name: "minimax-h3" }, result: "video", routes: [{ model: "/v1/generation/minimax/minimax-h3/video" }],
17
+ fields: {
18
+ prompt: { as: "value", field: "prompt" },
19
+ duration: { as: "value", field: "duration" },
20
+ resolution: { as: "value", field: "resolution" },
21
+ aspectRatio: { as: "value", field: "aspectRatio" },
22
+ firstFrame: { as: "url", field: "image" },
23
+ lastFrame: { as: "url", field: "imageTail" },
24
+ referenceImage: { as: "urlArray", field: "refs_image" },
25
+ referenceVideo: { as: "urlArray", field: "refs_video" },
26
+ referenceAudio: { as: "urlArray", field: "refs_audio" },
27
+ },
28
+ },
29
+ {
30
+ capability: { module: GROK, name: "grok-imagine-video-1.5-preview" }, result: "video",
31
+ routes: [{ model: "/v1/generation/xai/grok-imagine-video-1-5/video" }],
32
+ fields: {
33
+ prompt: { as: "value", field: "prompt" },
34
+ aspectRatio: { as: "value", field: "aspect_ratio" },
35
+ resolution: { as: "value", field: "resolution" },
36
+ duration: { as: "value", field: "duration" },
37
+ images: { as: "urlArray", field: "images" },
38
+ },
39
+ },
40
+ {
41
+ capability: { module: GPT_IMAGE, name: "gpt-image-2" }, result: "image", routes: [{ model: "/v1/generation/openai/gpt-image-2/image" }],
42
+ fields: {
43
+ prompt: { as: "value", field: "prompt" },
44
+ aspectRatio: { as: "value", field: "aspectRatio" },
45
+ resolution: { as: "value", field: "resolution" },
46
+ background: { as: "value", field: "background" },
47
+ images: { as: "urlArray", field: "images" },
48
+ },
49
+ },
50
+ ...(["nano-banana-2", "nano-banana-pro"] as const).map((name): GenerationWireMapping => ({
51
+ capability: { module: NANO_BANANA, name }, result: "image", routes: [{ model: `/v1/generation/google/${name}/image` }],
52
+ fields: {
53
+ prompt: { as: "value", field: "prompt" },
54
+ images: { as: "urlArray", field: "images" },
55
+ aspectRatio: { as: "value", field: "aspectRatio" },
56
+ resolution: { as: "value", field: "resolution" },
57
+ outputFormat: { as: "value", field: "output_format" },
58
+ },
59
+ })),
60
+ ];
@@ -0,0 +1,194 @@
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 { polloRouteForCapability, polloRoutes } from "./routes.js";
10
+ import { PolloHttpError, PolloServiceError, polloTaskFailure } from "./errors.js";
11
+
12
+ export const polloProviderModuleRef = { name: "@hypit/provider-pollo", version: "1" } as const;
13
+
14
+ export type CreatePolloProviderOptions = {
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 an HTTP(S) URL Pollo can fetch. Pollo accepts no inline media. */
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.pollo-operation@1";
31
+ readonly taskId: string;
32
+ readonly route: string;
33
+ readonly startedAt: number;
34
+ readonly urls?: readonly 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, "Pollo 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, "Pollo apiKey credential is unavailable; store a Pollo 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 PolloServiceError ? error.code : "POLLO_ERROR", message: failureMessage(error) } };
59
+ }
60
+
61
+ class PolloClient {
62
+ constructor(readonly baseUrl: string, readonly timeout: number, readonly fetcher: typeof globalThis.fetch) {}
63
+ async json(path: string, key: string, init: RequestInit = {}): Promise<Record<string, unknown>> {
64
+ const deadline = requestDeadline(this.timeout);
65
+ try {
66
+ const response = await deadline.wait(this.fetcher(`${this.baseUrl}${path}`, {
67
+ ...init, signal: deadline.signal, headers: { "x-api-key": key, ...(init.headers ?? {}) },
68
+ }));
69
+ const text = await deadline.wait(response.text());
70
+ if (!response.ok) throw new PolloHttpError(response.status, text, { method: init.method ?? "GET", path });
71
+ let body: unknown;
72
+ try { body = text.length === 0 ? {} : JSON.parse(text); } catch { throw new Error(`Pollo returned invalid JSON (${response.status})`); }
73
+ return object(body, "Pollo response");
74
+ } finally { deadline.finish(); }
75
+ }
76
+ async download(url: string): Promise<{ readonly bytes: Uint8Array; readonly mediaType: string }> {
77
+ const deadline = requestDeadline(this.timeout);
78
+ try {
79
+ const response = await deadline.wait(this.fetcher(url, { signal: deadline.signal }));
80
+ if (!response.ok) throw new Error(`Pollo asset returned HTTP ${response.status}`);
81
+ return { bytes: new Uint8Array(await deadline.wait(response.arrayBuffer())), mediaType: response.headers.get("content-type")?.split(";", 1)[0] ?? "application/octet-stream" };
82
+ } finally { deadline.finish(); }
83
+ }
84
+ }
85
+
86
+ function resolverFor(context: EndpointInvocationContext, publicAssetUrl: CreatePolloProviderOptions["publicAssetUrl"]): GenerationArtifactUrlResolver {
87
+ const resolved = new Map<string, Promise<string>>();
88
+ return (artifact, fields) => {
89
+ const existing = resolved.get(artifact.resource);
90
+ if (existing !== undefined) return existing;
91
+ assert(publicAssetUrl !== undefined,
92
+ "Pollo accepts reference media only by HTTP(S) URL; configure publicAssetUrl for this Endpoint");
93
+ const promise = publicAssetUrl(artifact, context.resources, fields);
94
+ resolved.set(artifact.resource, promise);
95
+ return promise;
96
+ };
97
+ }
98
+
99
+ function endpoint(client: PolloClient, pollIntervalMs: number, maxOperationMs: number, publicAssetUrl: CreatePolloProviderOptions["publicAssetUrl"]): AsyncEndpoint {
100
+ return {
101
+ async start(context) {
102
+ try {
103
+ const route = polloRouteForCapability(context.need.capability);
104
+ assert(route !== undefined, "Pollo does not implement this exact capability");
105
+ const request = route.prepare(context.need.constraints);
106
+ await context.reportProgress?.({ phase: `Preparing Pollo request: ${request.path}` });
107
+ let body: Record<string, unknown>;
108
+ try {
109
+ body = await request.compile(resolverFor(context, publicAssetUrl));
110
+ } catch (error) {
111
+ throw new PolloServiceError(error instanceof PolloServiceError ? error.code : "POLLO_ERROR",
112
+ `Pollo request preparation failed; path=${request.path}; generation not submitted: ${failureMessage(error)}`);
113
+ }
114
+ await context.reportProgress?.({ phase: `Submitting Pollo request: ${request.path}` });
115
+ const response = await client.json(request.path, apiKey(context.credentials), {
116
+ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
117
+ });
118
+ assert(typeof response.taskId === "string" && response.taskId.length > 0, "Pollo response has no taskId");
119
+ const handle: Handle = { contract: "hypit.pollo-operation@1", taskId: response.taskId, route: route.key, startedAt: Date.now() };
120
+ const receipt = { id: handle.taskId };
121
+ await context.checkpoint?.({ handle: canonicalize(handle), receipt });
122
+ return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: String(response.status ?? "submitted") }), receipt };
123
+ } catch (error) {
124
+ return failure(error);
125
+ }
126
+ },
127
+ async poll(context) {
128
+ try {
129
+ const handle = object(context.handle, "Pollo handle") as unknown as Handle;
130
+ const route = polloRouteForCapability(context.need.capability);
131
+ assert(route !== undefined && handle.contract === "hypit.pollo-operation@1" && handle.route === route.key, "Pollo handle is invalid");
132
+ const receipt = { id: handle.taskId };
133
+ if (Date.now() - handle.startedAt > maxOperationMs) {
134
+ return { status: "failed", receipt, failure: { code: "POLLO_OPERATION_TIMEOUT", message: `Pollo task ${handle.taskId} exceeded this Provider's operationTimeoutMs (${maxOperationMs}); remote outcome is unknown` } };
135
+ }
136
+ const task = await client.json(`/v1/generation/${encodeURIComponent(handle.taskId)}/status`, apiKey(context.credentials));
137
+ assert(Array.isArray(task.generations) && task.generations.length > 0, "Pollo task has no generations");
138
+ const generations = task.generations.map((item, index) => object(item, `Pollo generation ${index + 1}`));
139
+ const rejected = polloTaskFailure(generations, handle.taskId);
140
+ if (rejected !== undefined) return { ...failure(rejected), receipt };
141
+ const pending = generations.find((generation) => generation.status !== "succeed");
142
+ if (pending !== undefined) {
143
+ const status = String(pending.status);
144
+ assert(status === "waiting" || status === "processing", `Pollo returned unknown generation status ${status}`);
145
+ return { ...wakeAfter(canonicalize(handle), pollIntervalMs, Date.now(), { phase: status }), receipt };
146
+ }
147
+ const urls = generations.map((generation, index) => {
148
+ assert(typeof generation.url === "string" && /^https?:\/\//u.test(generation.url), `Pollo generation ${index + 1} has no URL`);
149
+ return generation.url;
150
+ });
151
+ return { status: "ready", handle: canonicalize({ ...handle, urls }), receipt };
152
+ } catch (error) {
153
+ return failure(error);
154
+ }
155
+ },
156
+ async collect(context) {
157
+ try {
158
+ const handle = object(context.handle, "Pollo handle") as unknown as Handle;
159
+ const route = polloRouteForCapability(context.need.capability);
160
+ assert(route !== undefined && handle.route === route.key && Array.isArray(handle.urls), "Pollo collection route differs");
161
+ await context.reportProgress?.({ phase: "Receiving generated files" });
162
+ const blobs: BlobRef[] = [];
163
+ for (const url of handle.urls) {
164
+ const downloaded = await client.download(url);
165
+ blobs.push(await context.resources.put(downloaded.bytes, downloaded.mediaType));
166
+ }
167
+ return { status: "completed", result: { value: route.packageResult(blobs) }, receipt: { id: handle.taskId } };
168
+ } catch (error) {
169
+ return failure(error);
170
+ }
171
+ },
172
+ };
173
+ }
174
+
175
+ export function createPolloProvider(options: CreatePolloProviderOptions = {}) {
176
+ const requestTimeoutMs = options.requestTimeoutMs ?? 300_000;
177
+ const operationTimeoutMs = options.operationTimeoutMs ?? 30 * 60_000;
178
+ for (const [name, value] of Object.entries({ requestTimeoutMs, operationTimeoutMs })) {
179
+ assert(Number.isSafeInteger(value) && value > 0, `Pollo ${name} must be a positive integer`);
180
+ }
181
+ const client = new PolloClient(apiBaseUrl(options.baseUrl ?? "https://pollo.ai/api/platform"), requestTimeoutMs, options.fetch ?? globalThis.fetch);
182
+ const asyncEndpoint = endpoint(client, options.pollIntervalMs ?? 10_000, operationTimeoutMs, options.publicAssetUrl);
183
+ return defineEndpointPackage({
184
+ module: polloProviderModuleRef, facet: "gateway", instance: options.instance ?? "pollo.default", pool: options.pool ?? options.instance ?? "pollo.default",
185
+ pricing: { kind: "page", url: "https://api.pollo.ai/pricing" },
186
+ credentials: { apiKey: options.apiKey ?? credentialRef("os", "pollo.api-key") },
187
+ credentialInputs: { apiKey: { label: "Pollo API key" } },
188
+ defaultConcurrency: options.defaultConcurrency ?? 4,
189
+ ...(options.actionLimits === undefined ? {} : { actionLimits: options.actionLimits }),
190
+ capabilities: polloRoutes.map((route) => ({
191
+ capability: route.capability, returns: route.returns, lifecycle: "asynchronous" as const, endpoint: asyncEndpoint, capacity: route.capability.name, supports: route.supports,
192
+ })),
193
+ });
194
+ }