@hypit/hypit 0.2.6 → 0.2.8

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/public/hyperframes.d.ts +19 -2
  3. package/examples/provider-package/README.md +2 -2
  4. package/package.json +2 -1
  5. package/packages/caption-fine/README.md +4 -4
  6. package/packages/credential-store-file/README.md +4 -0
  7. package/packages/credential-store-file/src/store.ts +2 -2
  8. package/packages/credential-store-platform/README.md +2 -0
  9. package/packages/hyperframes/README.md +7 -0
  10. package/packages/hyperframes/src/html-project.ts +99 -0
  11. package/packages/hyperframes/src/index.ts +2 -0
  12. package/packages/hyperframes/src/project.ts +28 -0
  13. package/packages/pixverse/README.md +35 -0
  14. package/packages/pixverse/package.json +21 -0
  15. package/packages/pixverse/src/activation.ts +11 -0
  16. package/packages/pixverse/src/index.ts +122 -0
  17. package/packages/pixverse/src/surface.ts +146 -0
  18. package/packages/provider-hiapi/README.md +1 -1
  19. package/packages/provider-hyperframes-local/README.md +19 -7
  20. package/packages/provider-hyperframes-local/src/capture.ts +18 -8
  21. package/packages/provider-hyperframes-local/src/index.ts +1 -1
  22. package/packages/provider-hyperframes-local/src/output.ts +1 -1
  23. package/packages/provider-hyperframes-local/src/process-tree.ts +8 -2
  24. package/packages/provider-hyperframes-local/src/provider.ts +29 -2
  25. package/packages/provider-hyperframes-local/src/render.ts +45 -20
  26. package/packages/provider-hyperframes-local/src/sampling.ts +3 -2
  27. package/packages/provider-hypihub/README.md +3 -4
  28. package/packages/provider-monid/README.md +1 -1
  29. package/packages/provider-tokendance/README.md +1 -1
  30. package/packages/provider-whisperx-local/README.md +8 -5
  31. package/packages/render-hyperframes/README.md +9 -0
  32. package/packages/render-hyperframes/src/index.ts +3 -0
  33. package/packages/render-hyperframes/src/manifest.ts +7 -2
  34. package/packages/render-hyperframes/src/product.ts +41 -2
  35. package/packages/runtime-local/src/process-control.ts +4 -1
  36. package/packages/runtime-local/src/programs.ts +15 -3
  37. package/packages/runtime-local/src/supervisor.ts +8 -1
  38. package/packages/script/README.md +11 -4
  39. package/packages/script/src/format.ts +11 -24
  40. package/packages/script/src/manifest.ts +2 -2
  41. package/packages/script/src/parser.ts +16 -21
  42. package/packages/script/src/surface.ts +5 -3
  43. package/packages/script/src/types.ts +1 -1
  44. package/packages/seedance/README.md +11 -18
  45. package/packages/seedance/src/index.ts +7 -7
  46. package/packages/seedance/src/surface.ts +9 -8
  47. package/packages/seedance-kits/README.md +4 -3
  48. package/packages/source/README.md +1 -1
  49. package/packages/source/src/header.ts +2 -1
  50. package/packages/studio/README.md +13 -0
  51. package/packages/studio/src/feedback-server.ts +7 -0
  52. package/packages/studio/src/mutation-origin.ts +20 -0
  53. package/packages/studio/src/parameters.ts +3 -7
  54. package/packages/studio/src/preview/render.ts +7 -1
  55. package/packages/studio/src/server.ts +39 -0
  56. package/packages/studio/src/session.ts +7 -3
  57. package/packages/studio/src/ui/syntax.ts +11 -10
  58. package/packages/temporal-markup/EDITING.md +1 -1
  59. package/packages/video-cli/README.md +47 -4
  60. package/packages/video-cli/package.json +3 -0
  61. package/packages/video-cli/src/cli.ts +9 -9
  62. package/packages/video-cli/src/creation.ts +3 -3
  63. package/packages/video-cli/src/frame-grid.ts +34 -0
  64. package/packages/video-cli/src/index.ts +4 -0
  65. package/packages/video-cli/src/media-frames.ts +25 -0
  66. package/packages/video-cli/src/media.ts +198 -46
  67. package/packages/video-cli/src/process.ts +13 -3
  68. package/packages/video-cli/src/secret-input.ts +14 -0
  69. package/packages/video-cli/src/snapshot.ts +186 -0
  70. package/packages/yt-dlp/src/download.ts +32 -18
package/README.md CHANGED
@@ -149,7 +149,7 @@ commands and the repository layout.
149
149
  </tr>
150
150
  </table>
151
151
 
152
- ## Launch Partner
152
+ ## Launch Partners
153
153
 
154
154
  <table>
155
155
  <tr>
@@ -788,5 +788,22 @@ type BrowserProgram = {
788
788
  };
789
789
  declare function browserProgram(value: BrowserProgram, artifacts?: readonly BlobRef[]): VisualProgramElement["program"];
790
790
 
791
- export { BROWSER_PROGRAM_FORMAT, assertHyperframesDocument, assertHyperframesFrameIndex, assertHyperframesFrameSpan, browserProgram, compileHyperframesDocument, hyperframesComponent, hyperframesDocumentFragment, hyperframesDocumentSchema, hyperframesManifest, hyperframesModuleRef, hyperframesProducers, hyperframesResourceUri, hyperframesTime, hyperframesTypes, materializeHyperframesHtml };
792
- export type { BrowserProgram, HyperframesCanvas, HyperframesDocument, HyperframesFrameDomain, HyperframesFrameSpan, ResourceUrlResolver };
791
+ /** An already materialized HTML programme and the local files its URLs address. */
792
+ type HyperframesHtmlProject = {
793
+ readonly html: string;
794
+ readonly assets: readonly {
795
+ readonly url: string;
796
+ readonly artifact: BlobRef;
797
+ }[];
798
+ };
799
+ /** Read the exact clock emitted by this package. Seconds never reconstruct frame identity. */
800
+ declare function hyperframesHtmlDomain(html: string): HyperframesFrameDomain & {
801
+ readonly canvas: HyperframesCanvas;
802
+ };
803
+ /** Resource URL positions in the compiler's self-contained HTML (inline JS and CSS). */
804
+ declare function mapHyperframesHtmlUrls(html: string, map: (url: string) => string): string;
805
+ declare function hyperframesHtmlAssetUrls(html: string): readonly string[];
806
+ declare function assertHyperframesHtmlProject(value: unknown): asserts value is HyperframesHtmlProject;
807
+
808
+ export { BROWSER_PROGRAM_FORMAT, assertHyperframesDocument, assertHyperframesFrameIndex, assertHyperframesFrameSpan, assertHyperframesHtmlProject, browserProgram, compileHyperframesDocument, hyperframesComponent, hyperframesDocumentFragment, hyperframesDocumentSchema, hyperframesHtmlAssetUrls, hyperframesHtmlDomain, hyperframesManifest, hyperframesModuleRef, hyperframesProducers, hyperframesResourceUri, hyperframesTime, hyperframesTypes, mapHyperframesHtmlUrls, materializeHyperframesHtml };
809
+ export type { BrowserProgram, HyperframesCanvas, HyperframesDocument, HyperframesFrameDomain, HyperframesFrameSpan, HyperframesHtmlProject, ResourceUrlResolver };
@@ -68,7 +68,7 @@ differ from the image example, and each is the reason the video example exists.
68
68
  **The reference vocabulary is wider, and each role maps to its own field.** The Model declares
69
69
  `referenceImage`, `referenceVideo` and `referenceAudio` as separate ports precisely so each can map
70
70
  to one wire field — a service that mixes them into one array cannot tell the roles apart. An image
71
- reference also carries an optional `personReference` classification, which is why those ports use
71
+ reference also requires a boolean `personReference` classification, which is why those ports use
72
72
  `itemObject` rather than `urlArray`: the item field travels with the URL it belongs to.
73
73
 
74
74
  ```json
@@ -76,7 +76,7 @@ reference also carries an optional `personReference` classification, which is wh
76
76
  ```
77
77
 
78
78
  `firstFrame` and `lastFrame` accept at most one item each, so their body fields are URLs.
79
- This illustrative service accepts their optional person classification on `POST /uploads` through
79
+ This illustrative service accepts their required visual-reference person classification on `POST /uploads` through
80
80
  `x-person-reference: true` or `false`. Their mappings declare `resourceFields: ["personReference"]`,
81
81
  and the URL resolver sends that header when supplied. Replace this with the real service’s
82
82
  documented transport; a field that the service cannot carry must be refused, never discarded.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypit/hypit",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "homepage": "https://hypit.ai",
5
5
  "repository": {
6
6
  "type": "git",
@@ -232,6 +232,7 @@
232
232
  "@hypit/narrative": "workspace:*",
233
233
  "@hypit/performance": "workspace:*",
234
234
  "@hypit/performance-studio": "workspace:*",
235
+ "@hypit/pixverse": "workspace:*",
235
236
  "@hypit/program-space": "workspace:*",
236
237
  "@hypit/project-context-node": "workspace:*",
237
238
  "@hypit/protocol": "workspace:*",
@@ -14,10 +14,10 @@ does not carry a private visual role.
14
14
  </caption-fine:Track>
15
15
  ```
16
16
 
17
- An optional Spatial Region Timeline replaces only the moving placement point. The timeline is authored
18
- numeric input, measured from the actual footage before the composition pass that consumes it;
19
- it is not a face-tracking request. A prior Build may produce that footage with ordinary fixed Caption,
20
- and a later Run can reuse the same media and alignment while changing placement:
17
+ An optional Spatial Region Timeline supplies a moving placement point when the user's reference
18
+ visibly uses head-following Caption or the user asks for that treatment. Ordinary Caption uses the
19
+ Style's placement without regions. The Region Timeline is authored numeric input mapped from available picture
20
+ evidence to the composition that consumes it; it is not a face-tracking request:
21
21
 
22
22
  ```xml
23
23
  <space:RegionTimeline id="heads" within={vertical} recipe={tracking.heads.default}/>
@@ -50,6 +50,10 @@ The default directory is `credentials` under the Host state root printed by `hyp
50
50
  `config.path` explicitly selects a directory, including an absolute directory outside that root;
51
51
  relative paths resolve against the Host root.
52
52
  Opening the adapter and reading an absent key do not create files. Keep this directory outside Git.
53
+ On WSL, the default under the Linux home uses normal Linux permissions. If the Host state root is
54
+ on a Windows-mounted drive, set this Store's `config.path` to a private directory in the WSL Linux
55
+ filesystem before login. A Windows mount's reported mode does not establish that its Windows ACL
56
+ protects the secret, so the Store does not waive its owner-private check for that path.
53
57
 
54
58
  Each key has its own JSON file containing only `secret` and optional `expiresAt`, as defined by
55
59
  CredentialValue. Filenames use reversible lower-case hex encoding of the key's UTF-16 bytes, with a
@@ -38,7 +38,7 @@ export class FileCredentialStore implements WritableCredentialStore {
38
38
  const info = await stat(this.directory);
39
39
  if (!info.isDirectory()) throw new Error(`Credential path is not a directory: ${this.directory}`);
40
40
  if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
41
- throw new Error(`Credential directory must be owner-private: ${this.directory}`);
41
+ throw new Error(`Credential directory must be owner-private: ${this.directory}. On WSL, keep credentials in the Linux filesystem rather than a Windows-mounted drive.`);
42
42
  }
43
43
  }
44
44
 
@@ -57,7 +57,7 @@ export class FileCredentialStore implements WritableCredentialStore {
57
57
  try {
58
58
  const info = await file.stat();
59
59
  if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
60
- throw new Error(`Credential file must be owner-private: ${path}`);
60
+ throw new Error(`Credential file must be owner-private: ${path}. On WSL, keep credentials in the Linux filesystem rather than a Windows-mounted drive.`);
61
61
  }
62
62
  const text = await file.readFile("utf8");
63
63
  // JSON parser messages may include secret bytes. Report the file, never its contents.
@@ -47,6 +47,8 @@ credential stored in a locker stays in that locker; a credential stored in a fil
47
47
  - `path` selects the directory for Linux file storage, resolved against the Host state root printed by
48
48
  `hypit paths`. The default is the same `credentials` directory `@hypit/credential-store-file` uses,
49
49
  so on Linux, switching between the two Stores finds the credential already stored.
50
+ - WSL uses the Linux file Store. If the Host state root is on a Windows-mounted drive, select a
51
+ private directory in the WSL Linux filesystem with `path`; the Store keeps its permission check.
50
52
  - `service` selects the locker service name. The OS Store's own default applies when it is absent.
51
53
 
52
54
  ## Storage on each platform
@@ -25,6 +25,13 @@ a Provider can verify and name staged bytes without guessing from the hash. A lo
25
25
  render Runtime calls `materializeHyperframesHtml()` with its own Artifact URL resolver before
26
26
  handing the HTML to HyperFrames. That environment-specific materialization is not a new compiled
27
27
  Record and does not change the compiled document.
28
+
29
+ For observation of already materialized HTML, `HyperframesHtmlProject` carries the HTML and the
30
+ BlobRefs addressed by its media/font URLs. `hyperframesHtmlDomain` reads the compiler's exact root
31
+ frame clock and canvas; `stageHyperframesHtmlProject` stages those resources. This input does not
32
+ reconstruct a Composition or invent typed Surface facts from HTML. A caller with the original
33
+ `HyperframesDocument`, such as Studio, should pass that document to preserve all declared dependencies,
34
+ including resources embedded in browser-program data. The document remains the complete typed input.
28
35
  Staging assigns short local filenames; opaque Resource IDs are never interpreted as filesystem
29
36
  paths or filenames, so their length and punctuation do not restrict the host filesystem.
30
37
  `stageHyperframesProject` streams each asset into its staged file. Its `validateSurface(surface, path,
@@ -0,0 +1,99 @@
1
+ import type { BlobRef } from "@hypit/protocol";
2
+ import { isResourceId } from "@hypit/protocol";
3
+ import type { HyperframesCanvas, HyperframesFrameDomain } from "./types.js";
4
+
5
+ /** An already materialized HTML programme and the local files its URLs address. */
6
+ export type HyperframesHtmlProject = {
7
+ readonly html: string;
8
+ readonly assets: readonly { readonly url: string; readonly artifact: BlobRef }[];
9
+ };
10
+
11
+ /** Read the exact clock emitted by this package. Seconds never reconstruct frame identity. */
12
+ export function hyperframesHtmlDomain(html: string): HyperframesFrameDomain & { readonly canvas: HyperframesCanvas } {
13
+ const attributeValue = (tag: string, name: string): string | undefined => {
14
+ const marker = `${name}="`;
15
+ for (let at = tag.indexOf(marker); at !== -1; at = tag.indexOf(marker, at + marker.length)) {
16
+ const previous = tag[at - 1];
17
+ if (previous === undefined || !" \t\r\n\f".includes(previous)) continue;
18
+ const start = at + marker.length;
19
+ const end = tag.indexOf('"', start);
20
+ return end === -1 ? undefined : tag.slice(start, end);
21
+ }
22
+ return undefined;
23
+ };
24
+ let root: string | undefined;
25
+ for (let at = html.indexOf("<div"); at !== -1; at = html.indexOf("<div", at)) {
26
+ const end = html.indexOf(">", at + 4);
27
+ if (end === -1) break;
28
+ const tag = html.slice(at, end + 1);
29
+ at = end + 1;
30
+ if (attributeValue(tag, "data-composition-id") === undefined) continue;
31
+ if (root !== undefined) throw new Error("Snapshot HTML needs exactly one compiled HyperFrames composition root");
32
+ root = tag;
33
+ }
34
+ if (root === undefined) throw new Error("Snapshot HTML needs exactly one compiled HyperFrames composition root");
35
+ const attribute = (name: string) => {
36
+ const value = attributeValue(root, name);
37
+ if (value === undefined) throw new Error(`Snapshot HTML lacks ${name}; use the compiled HyperFrames HTML`);
38
+ return value;
39
+ };
40
+ const positive = (value: string, name: string) => {
41
+ const number = Number(value);
42
+ if (!/^\d+$/u.test(value) || !Number.isSafeInteger(number) || number <= 0) throw new Error(`Invalid HyperFrames ${name}`);
43
+ return number;
44
+ };
45
+ const fps = attribute("data-fps").split("/");
46
+ if (fps.length > 2) throw new Error("HyperFrames data-fps must be an exact rational");
47
+ return {
48
+ frameRate: { numerator: positive(fps[0]!, "frame rate"), denominator: positive(fps[1] ?? "1", "frame rate denominator") },
49
+ frameCount: positive(attribute("data-hypit-frame-count"), "frame count"),
50
+ canvas: { width: positive(attribute("data-width"), "width"), height: positive(attribute("data-height"), "height") },
51
+ };
52
+ }
53
+
54
+ function unescape(value: string): string {
55
+ return value.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
56
+ }
57
+
58
+ /** Resource URL positions in the compiler's self-contained HTML (inline JS and CSS). */
59
+ export function mapHyperframesHtmlUrls(html: string, map: (url: string) => string): string {
60
+ const escape = (value: string) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;");
61
+ const cssUrls = (css: string) => css.replace(/url\(\s*(["']?)([^)"']+)\1\s*\)/gu,
62
+ (_all, _quote: string, url: string) => `url("${map(url.trim()).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}")`);
63
+ return html.replace(/<style\b([^>]*)>([\s\S]*?)<\/style>/gu,
64
+ (_all, attributes: string, css: string) => `<style${attributes}>${cssUrls(css)}</style>`)
65
+ .replace(/<[a-z][^>]*>/gu, tag => {
66
+ const attributes = /^(?:<(?:img|video|source|image|use|script|link)\b)/u.test(tag)
67
+ ? tag.replace(/\s(src|poster|href)="([^"]*)"/gu,
68
+ (_all, key: string, url: string) => ` ${key}="${escape(map(unescape(url)))}"`) : tag;
69
+ return attributes.replace(/\sstyle="([^"]*)"/gu,
70
+ (_all, css: string) => ` style="${escape(cssUrls(unescape(css)))}"`);
71
+ });
72
+ }
73
+
74
+ export function hyperframesHtmlAssetUrls(html: string): readonly string[] {
75
+ const urls = new Set<string>();
76
+ mapHyperframesHtmlUrls(html, url => {
77
+ if (url !== "" && !url.startsWith("#") && !url.startsWith("data:")) urls.add(url);
78
+ return url;
79
+ });
80
+ return [...urls];
81
+ }
82
+
83
+ export function assertHyperframesHtmlProject(value: unknown): asserts value is HyperframesHtmlProject {
84
+ const project = value as HyperframesHtmlProject;
85
+ if (!project || typeof project.html !== "string" || !Array.isArray(project.assets)) throw new Error("HyperFrames HTML project needs html and assets");
86
+ hyperframesHtmlDomain(project.html);
87
+ if (/hypit-resource:\/\//u.test(project.html)) throw new Error("HTML still has unresolved resources; materialize its HyperframesDocument first");
88
+ const urls = new Set<string>();
89
+ for (const asset of project.assets) {
90
+ const blob = asset?.artifact;
91
+ if (typeof asset?.url !== "string" || urls.has(asset.url) || blob?.kind !== "blob" || !isResourceId(blob.resource)
92
+ || !Number.isSafeInteger(blob.size) || blob.size < 0 || typeof blob.mediaType !== "string" || !blob.mediaType) {
93
+ throw new Error("Invalid or repeated HyperFrames HTML asset");
94
+ }
95
+ urls.add(asset.url);
96
+ }
97
+ const referenced = hyperframesHtmlAssetUrls(project.html);
98
+ if (referenced.length !== urls.size || referenced.some(url => !urls.has(url))) throw new Error("HyperFrames HTML assets must address its referenced URLs");
99
+ }
@@ -11,3 +11,5 @@ export {
11
11
  export type * from "./types.js";
12
12
  export { browserProgram, BROWSER_PROGRAM_FORMAT } from "./browser-program.js";
13
13
  export type { BrowserProgram } from "./browser-program.js";
14
+ export { assertHyperframesHtmlProject, hyperframesHtmlDomain, hyperframesHtmlAssetUrls, mapHyperframesHtmlUrls } from "./html-project.js";
15
+ export type { HyperframesHtmlProject } from "./html-project.js";
@@ -6,6 +6,8 @@ import type { BlobRef } from "@hypit/protocol";
6
6
 
7
7
  import { assertHyperframesDocument, materializeHyperframesHtml } from "./document.js";
8
8
  import type { HyperframesDocument } from "./types.js";
9
+ import { assertHyperframesHtmlProject, mapHyperframesHtmlUrls } from "./html-project.js";
10
+ import type { HyperframesHtmlProject } from "./html-project.js";
9
11
 
10
12
  /** Reads one execution resource. Whose store it comes from is the caller's business. */
11
13
  export type HyperframesArtifactReader = (
@@ -37,6 +39,32 @@ function extension(mediaType: string): string {
37
39
  return found;
38
40
  }
39
41
 
42
+ /** Stage an existing HTML programme; no reverse compilation or inferred Surface declarations. */
43
+ export async function stageHyperframesHtmlProject(options: {
44
+ readonly project: HyperframesHtmlProject;
45
+ readonly directory: string;
46
+ readonly read: HyperframesArtifactReader;
47
+ readonly signal?: AbortSignal;
48
+ }): Promise<void> {
49
+ assertHyperframesHtmlProject(options.project);
50
+ await mkdir(join(options.directory, "artifacts"), { recursive: true });
51
+ const paths = new Map<string, string>();
52
+ for (const [index, asset] of options.project.assets.entries()) {
53
+ options.signal?.throwIfAborted();
54
+ const path = `artifacts/asset-${index}${extension(asset.artifact.mediaType)}`;
55
+ const bytes = await options.read(asset.artifact, options.signal);
56
+ const file = await open(join(options.directory, path), "w");
57
+ try {
58
+ if (bytes instanceof Uint8Array) await file.writeFile(bytes);
59
+ else for await (const chunk of bytes) { options.signal?.throwIfAborted(); await file.writeFile(chunk); }
60
+ } finally { await file.close(); }
61
+ paths.set(asset.url, `./${path}`);
62
+ }
63
+ await writeFile(join(options.directory, "index.html"), mapHyperframesHtmlUrls(options.project.html, url => paths.get(url) ?? url), {
64
+ encoding: "utf8", ...(options.signal === undefined ? {} : { signal: options.signal }),
65
+ });
66
+ }
67
+
40
68
  /**
41
69
  * Lay one document out as a directory HyperFrames can render.
42
70
  *
@@ -0,0 +1,35 @@
1
+ # `@hypit/pixverse`
2
+
3
+ Exact author/compute contracts and a package-owned author Surface for PixVerse V6.
4
+
5
+ The Surface projects the primary result to an ordinary video Artifact. The package contains no
6
+ Provider selection, API key or network execution. The selected Provider implements its exact
7
+ capability.
8
+
9
+ Connect prompt and frames as ordinary graph edges:
10
+
11
+ ```xml
12
+ <pix:Video
13
+ id="opening"
14
+ prompt={line}
15
+ duration="5"
16
+ quality="720p"
17
+ aspect-ratio="9:16"
18
+ generate-audio="true"
19
+ />
20
+
21
+ <pix:Video id="bridge" prompt={motion} duration="5" quality="720p"
22
+ first-frame={hero.image} last-frame={product.image}/>
23
+ ```
24
+
25
+ The Surface only lowers this syntax into the package's exact model request. It does not select a
26
+ Provider.
27
+
28
+ The model renders 1 to 15 seconds at `360p`, `540p`, `720p` or `1080p`. A prompt-only run states its
29
+ `aspect-ratio`; a run that starts from a `first-frame` takes that frame's shape instead. A
30
+ `last-frame` bridges from the first frame into one continuous shot, so it is not combined with
31
+ `multi-clip`, which renders the prompt as several cuts.
32
+
33
+ `generate-audio` renders an audio track alongside the picture, including speech the prompt asks a
34
+ character to say. The model exposes no separate voice, language or dialogue field, so a spoken line
35
+ belongs in the prompt itself.
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@hypit/pixverse",
3
+ "version": "0.0.0-dev",
4
+ "license": "SEE LICENSE IN LICENSE",
5
+ "private": true,
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.ts"
9
+ },
10
+ "hypit": {
11
+ "activation": "./src/activation.ts"
12
+ },
13
+ "dependencies": {
14
+ "@hypit/artifact": "workspace:*",
15
+ "@hypit/generation": "workspace:*",
16
+ "@hypit/markup": "workspace:*",
17
+ "@hypit/model-kit": "workspace:*",
18
+ "@hypit/protocol": "workspace:*",
19
+ "@hypit/text": "workspace:*"
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ import { createMarkupSurfaceHostFacet } from "@hypit/markup";
2
+ import {
3
+ pixverseComponent, pixverseDefinition, pixverseManifest, pixverseModuleRef, pixverseMarkupSurfaces,
4
+ } from "./index.js";
5
+ import { decodePixverseVideoSurface } from "./surface.js";
6
+ export const hypitPackage = { format: "hypit.node-package@1" as const, modules: [{ manifest: pixverseManifest }], components: [pixverseComponent], hostFacets: [
7
+ pixverseDefinition.hostFacet,
8
+ createMarkupSurfaceHostFacet({ module: pixverseModuleRef,
9
+ declaration: pixverseMarkupSurfaces.find((item) => item.name === "video")!, handler: decodePixverseVideoSurface }),
10
+ ] };
11
+ export default hypitPackage;
@@ -0,0 +1,122 @@
1
+ import { artifactTypes } from "@hypit/artifact";
2
+ import { sealGenerationPortRequest, sealGenerationPortTable } from "@hypit/generation";
3
+ import type { GenerationPortTable, GenerationPortValue, GenerationRequest } from "@hypit/generation";
4
+ import type { SurfaceAttributeVocabulary } from "@hypit/markup";
5
+ import { defineExactModelModule } from "@hypit/model-kit";
6
+ import { textTypes } from "@hypit/text";
7
+
8
+ export const pixverseModuleRef = { name: "@hypit/pixverse", version: "1" } as const;
9
+ export const pixverseModels = ["pixverse-v6"] as const;
10
+ export type PixverseModel = typeof pixverseModels[number];
11
+
12
+ const PIXVERSE_QUALITIES = ["360p", "540p", "720p", "1080p"] as const;
13
+ const PIXVERSE_ASPECT_RATIOS = ["16:9", "4:3", "1:1", "3:4", "9:16", "2:3", "3:2", "21:9"] as const;
14
+
15
+ export const pixverseV6Ports: GenerationPortTable = sealGenerationPortTable({
16
+ model: "pixverse-v6",
17
+ result: "video",
18
+ ports: [
19
+ { name: "prompt", value: { kind: "text", maxChars: 5_000 }, minItems: 1, maxItems: 1 },
20
+ { name: "firstFrame", value: { kind: "media", accepts: ["image"] }, minItems: 0, maxItems: 1 },
21
+ { name: "lastFrame", value: { kind: "media", accepts: ["image"] }, minItems: 0, maxItems: 1 },
22
+ { name: "duration", value: { kind: "number", integer: true, minimum: 1, maximum: 15 }, minItems: 1, maxItems: 1 },
23
+ { name: "quality", value: { kind: "enum", values: [...PIXVERSE_QUALITIES] }, minItems: 1, maxItems: 1 },
24
+ { name: "aspectRatio", value: { kind: "enum", values: [...PIXVERSE_ASPECT_RATIOS] }, minItems: 0, maxItems: 1 },
25
+ { name: "generateAudio", value: { kind: "boolean" }, minItems: 0, maxItems: 1 },
26
+ { name: "multiClip", value: { kind: "boolean" }, minItems: 0, maxItems: 1 },
27
+ { name: "seed", value: { kind: "number", integer: true, minimum: 0, maximum: 2_147_483_647 }, minItems: 0, maxItems: 1 },
28
+ ],
29
+ requires: [
30
+ // A last frame states where a run that already has a first frame ends.
31
+ { kind: "requiresPresent", port: "lastFrame", needs: ["firstFrame"] },
32
+ // A run that starts from a frame inherits that frame's shape, and one that
33
+ // bridges two frames renders a single continuous shot.
34
+ { kind: "atMostOneOf", ports: ["aspectRatio", "firstFrame"] },
35
+ { kind: "atMostOneOf", ports: ["multiClip", "lastFrame"] },
36
+ ],
37
+ });
38
+
39
+ export const pixversePorts: Readonly<Record<PixverseModel, GenerationPortTable>> = {
40
+ "pixverse-v6": pixverseV6Ports,
41
+ };
42
+
43
+ export function sealPixverseRequest(
44
+ ports: Readonly<Record<string, readonly GenerationPortValue[]>>,
45
+ ): GenerationRequest {
46
+ return sealGenerationPortRequest(pixverseV6Ports, ports);
47
+ }
48
+
49
+ const pixverseBaseDefinition = defineExactModelModule({
50
+ module: pixverseModuleRef,
51
+ endpoints: [{
52
+ key: "video",
53
+ requestTypeName: "PixverseV6Request",
54
+ producerName: "request-pixverse-v6",
55
+ ports: pixverseV6Ports,
56
+ }],
57
+ });
58
+
59
+ export const pixverseEndpoints = pixverseBaseDefinition.endpoints;
60
+ export const pixverseComponent = pixverseBaseDefinition.component;
61
+ const endpoint = pixverseEndpoints.video!;
62
+
63
+ const pixverseAttributes: readonly SurfaceAttributeVocabulary[] = [
64
+ { name: "id", kind: "identifier", required: true,
65
+ summary: "Names this generation so its video Artifact can be referenced elsewhere in the Source." },
66
+ { name: "prompt", kind: "reference", required: true, accepts: [textTypes.text],
67
+ summary: "The Text edge describing the shot, including any spoken line the model should voice." },
68
+ { name: "first-frame", kind: "reference", required: false, accepts: [artifactTypes.blob],
69
+ summary: "Starts the video from one image Artifact." },
70
+ { name: "last-frame", kind: "reference", required: false, accepts: [artifactTypes.blob],
71
+ summary: "Ends the video on one image Artifact, bridging from the first frame." },
72
+ { name: "duration", kind: "literal", required: true,
73
+ summary: "How many seconds of video to render, from 1 to 15." },
74
+ { name: "quality", kind: "literal", required: true, values: [...PIXVERSE_QUALITIES],
75
+ summary: "The size band the model renders at." },
76
+ { name: "aspect-ratio", kind: "literal", required: false, values: [...PIXVERSE_ASPECT_RATIOS],
77
+ summary: "The Frame shape of the generated video." },
78
+ { name: "generate-audio", kind: "literal", required: false, values: ["true", "false"],
79
+ summary: "Renders an audio track alongside the picture." },
80
+ { name: "multi-clip", kind: "literal", required: false, values: ["true", "false"],
81
+ summary: "Renders the prompt as several cuts instead of one continuous shot." },
82
+ { name: "seed", kind: "literal", required: false,
83
+ summary: "Seeds the model's sampling so a rerun stays close to this one." },
84
+ ];
85
+
86
+ export const pixverseMarkupSurfaces = [{
87
+ name: "video",
88
+ tag: "Video",
89
+ mode: "structured" as const,
90
+ outputs: [endpoint.draftType, endpoint.mediaBindings.firstFrame!.type, endpoint.mediaBindings.lastFrame!.type],
91
+ vocabulary: {
92
+ summary: "Generates one video with the exact PixVerse V6 model from a Text prompt, optionally starting from a frame or bridging two.",
93
+ attributes: pixverseAttributes,
94
+ ports: [{
95
+ name: "video",
96
+ type: artifactTypes.blob,
97
+ summary: "The generated video, addressed as `<id>.video`.",
98
+ }],
99
+ example: `<pix:Video
100
+ id="opening"
101
+ prompt={line}
102
+ duration="5"
103
+ quality="720p"
104
+ aspect-ratio="9:16"
105
+ generate-audio="true"
106
+ />`,
107
+ notes: [
108
+ "A run that starts from a frame takes its shape from that frame, so `aspect-ratio` states the shape only for a prompt-only run.",
109
+ "A `last-frame` bridges from the `first-frame` into one continuous shot, so it is not combined with `multi-clip`.",
110
+ "A spoken line belongs in the prompt; the model exposes no separate voice, language or dialogue field.",
111
+ "The Surface lowers the element into the package's exact model request and selects no Provider.",
112
+ ],
113
+ },
114
+ }] as const;
115
+
116
+ export const pixverseManifest = {
117
+ ...pixverseBaseDefinition.manifest,
118
+ };
119
+ export const pixverseDefinition = {
120
+ ...pixverseBaseDefinition,
121
+ manifest: pixverseManifest,
122
+ };
@@ -0,0 +1,146 @@
1
+ import { artifactTypes } from "@hypit/artifact";
2
+ import { generationPort, sealGenerationMediaBinding, sealGenerationRequestDraft } from "@hypit/generation";
3
+ import type { GenerationMediaPort, GenerationPortValue } from "@hypit/generation";
4
+ import {
5
+ createExactModelPrimaryGenerationFragment,
6
+ exactModelMediaInputNames,
7
+ exactModelTextInputName,
8
+ } from "@hypit/model-kit";
9
+ import type { ExactModelEndpoint } from "@hypit/model-kit";
10
+ import type {
11
+ MarkupAttributeValue,
12
+ StructuredElement,
13
+ StructuredSurfaceHandler,
14
+ SurfaceResolvedReference,
15
+ } from "@hypit/markup";
16
+ import type { CanonicalValue, TypeRef } from "@hypit/protocol";
17
+ import { textTypes, verifyText } from "@hypit/text";
18
+
19
+ import { pixverseEndpoints } from "./index.js";
20
+
21
+ function assert(condition: unknown, message: string): asserts condition {
22
+ if (!condition) throw new Error(message);
23
+ }
24
+
25
+ function sameType(left: TypeRef, right: TypeRef): boolean {
26
+ return left.name === right.name && left.module.name === right.module.name && left.module.version === right.module.version;
27
+ }
28
+
29
+ const attributes = ["id", "prompt", "first-frame", "last-frame", "duration", "quality",
30
+ "aspect-ratio", "generate-audio", "multi-clip", "seed"] as const;
31
+
32
+ function exact(element: StructuredElement): void {
33
+ const unknown = Object.keys(element.attributes).filter((name) => !attributes.includes(name as typeof attributes[number]));
34
+ assert(unknown.length === 0, `${element.name} does not accept ${unknown[0]}`);
35
+ const missing = ["id", "prompt", "duration", "quality"].filter((name) => element.attributes[name] === undefined);
36
+ assert(missing.length === 0, `${element.name} requires ${missing.join(", ")}`);
37
+ }
38
+
39
+ function text(element: StructuredElement, name: string): string {
40
+ const value = element.attributes[name];
41
+ assert(typeof value === "string" && value.trim().length > 0, `${element.name}.${name} must be text`);
42
+ return value.trim();
43
+ }
44
+
45
+ /** An absent optional attribute leaves its port unstated, which the model reads as its own default. */
46
+ function optionalText(element: StructuredElement, name: string): readonly GenerationPortValue[] | undefined {
47
+ return element.attributes[name] === undefined ? undefined : [text(element, name)];
48
+ }
49
+
50
+ function optionalFlag(element: StructuredElement, name: string): readonly GenerationPortValue[] | undefined {
51
+ if (element.attributes[name] === undefined) return undefined;
52
+ const value = text(element, name);
53
+ assert(value === "true" || value === "false", `${element.name}.${name} must be true or false`);
54
+ return [value === "true"];
55
+ }
56
+
57
+ function integer(element: StructuredElement, name: string): readonly GenerationPortValue[] {
58
+ const value = text(element, name);
59
+ assert(/^\d+$/u.test(value), `${element.name}.${name} must be a whole number`);
60
+ return [Number(value)];
61
+ }
62
+
63
+ function ref(
64
+ element: StructuredElement,
65
+ name: string,
66
+ type: TypeRef,
67
+ resolve: (path: string) => SurfaceResolvedReference | undefined,
68
+ ): SurfaceResolvedReference {
69
+ const value: MarkupAttributeValue | undefined = element.attributes[name];
70
+ assert(typeof value === "object" && value.kind === "reference", `${element.name}.${name} must be a reference`);
71
+ const result = resolve(value.path);
72
+ assert(result !== undefined && sameType(result.type, type), `${element.name}.${name} has the wrong type`);
73
+ return result;
74
+ }
75
+
76
+ function frame(
77
+ element: StructuredElement,
78
+ name: string,
79
+ resolve: (path: string) => SurfaceResolvedReference | undefined,
80
+ ): SurfaceResolvedReference | undefined {
81
+ if (element.attributes[name] === undefined) return undefined;
82
+ const image = ref(element, name, artifactTypes.blob, resolve);
83
+ if (image.record !== undefined) {
84
+ assert(image.record.value.kind === "blob" && image.record.value.mediaType.startsWith("image/"),
85
+ `${element.name}.${name} must reference image media`);
86
+ }
87
+ return image;
88
+ }
89
+
90
+ function decoder(endpoint: ExactModelEndpoint): StructuredSurfaceHandler {
91
+ return ({ element, resolveReference }) => {
92
+ exact(element);
93
+ assert(!element.children.some((item) => item.kind === "element" || item.value.trim()),
94
+ `${element.name} accepts no children`);
95
+ const id = text(element, "id");
96
+ const prompt = ref(element, "prompt", textTypes.text, resolveReference);
97
+ if (prompt.record !== undefined) {
98
+ assert(prompt.record.value.kind === "inline", `${element.name}.prompt must reference Text`);
99
+ verifyText(prompt.record.value.value);
100
+ }
101
+ const frames = ([["firstFrame", "first-frame"], ["lastFrame", "last-frame"]] as const)
102
+ .flatMap(([port, attribute]) => {
103
+ const image = frame(element, attribute, resolveReference);
104
+ return image === undefined ? [] : [{ port, image }];
105
+ });
106
+ const stated: Record<string, readonly GenerationPortValue[] | undefined> = {
107
+ duration: integer(element, "duration"),
108
+ quality: [text(element, "quality")],
109
+ aspectRatio: optionalText(element, "aspect-ratio"),
110
+ generateAudio: optionalFlag(element, "generate-audio"),
111
+ multiClip: optionalFlag(element, "multi-clip"),
112
+ seed: element.attributes["seed"] === undefined ? undefined : integer(element, "seed"),
113
+ };
114
+ const draft = sealGenerationRequestDraft(endpoint.ports, Object.fromEntries(
115
+ Object.entries(stated).filter((entry): entry is [string, readonly GenerationPortValue[]] => entry[1] !== undefined),
116
+ ));
117
+ const records: Array<{ id: string; type: TypeRef; value: { kind: "inline"; value: CanonicalValue }; range: StructuredElement["range"] }> = [{
118
+ id: `${id}.draft`, type: endpoint.draftType,
119
+ value: { kind: "inline", value: draft as unknown as CanonicalValue }, range: element.range,
120
+ }];
121
+ const inputs: Record<string, SurfaceResolvedReference["ref"] | { kind: "record"; id: string }> = {
122
+ draft: { kind: "record", id: `${id}.draft` }, [exactModelTextInputName("prompt")]: prompt.ref,
123
+ };
124
+ const media = frames.map(({ port, image }, index) => {
125
+ const name = `media-${String(index + 1).padStart(4, "0")}`;
126
+ const bindingId = `${id}.${name}.binding`;
127
+ const mediaPort = generationPort(endpoint.ports, port);
128
+ assert(mediaPort.value.kind === "media", `PixVerse port ${port} is not media`);
129
+ records.push({
130
+ id: bindingId, type: endpoint.mediaBindings[port]!.type,
131
+ value: { kind: "inline", value: sealGenerationMediaBinding(mediaPort as GenerationMediaPort, { role: "image" }) as unknown as CanonicalValue },
132
+ range: element.range,
133
+ });
134
+ const names = exactModelMediaInputNames(name);
135
+ inputs[names.binding] = { kind: "record", id: bindingId };
136
+ inputs[names.artifact] = image.ref;
137
+ return { name, port } as const;
138
+ });
139
+ const fragment = createExactModelPrimaryGenerationFragment(endpoint, media, [{ name: "prompt", port: "prompt" }]);
140
+ return { records, fragments: [fragment], components: [{
141
+ id, fragment: fragment.id, inputs, outputs: { video: `${id}.video` }, range: element.range,
142
+ }] };
143
+ };
144
+ }
145
+
146
+ export const decodePixverseVideoSurface = decoder(pixverseEndpoints.video!);
@@ -37,7 +37,7 @@ Service limits this Provider reports as unsupported before submitting:
37
37
  `1:3`, `9:21`; 4K excludes `1:1`, `3:1`, `1:3`, `9:21`; image-to-image takes up to six references.
38
38
  - Grok Imagine renders 480p or 720p; `grok-imagine-1.5/image-to-video` animates exactly one image.
39
39
 
40
- Seedance visual references may carry `person-reference`; the Provider accepts the declaration and
40
+ Seedance visual references require `person-reference`; the Provider accepts the declaration and
41
41
  transmits nothing for it, since HiAPI has no field for it. Seedance rejects reference images and
42
42
  videos that contain a real human face; HiAPI offers no way to register authorized portrait material,
43
43
  so such a request fails with the service's moderation error.