@hypit/hypit 0.1.8 → 0.1.10

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 (44) hide show
  1. package/README.md +15 -4
  2. package/dist/public/endpoint-kit.d.ts +1 -1
  3. package/dist/public/runtime-kit.d.ts +3 -1
  4. package/examples/provider-package/README.md +12 -0
  5. package/examples/provider-package/packages/provider-images/src/provider.ts +37 -8
  6. package/package.json +1 -1
  7. package/packages/cli/README.md +27 -0
  8. package/packages/cli/src/command-hint.ts +20 -0
  9. package/packages/cli/src/commands/environment.ts +39 -18
  10. package/packages/cli/src/commands/execution.ts +27 -17
  11. package/packages/cli/src/commands/results.ts +2 -1
  12. package/packages/cli/src/machine-view.ts +4 -2
  13. package/packages/cli/src/main.ts +23 -22
  14. package/packages/cli/src/observation.ts +7 -2
  15. package/packages/cli/src/output.ts +3 -0
  16. package/packages/cli/src/view.ts +4 -1
  17. package/packages/driver-node/README.md +5 -0
  18. package/packages/driver-node/src/driver.ts +20 -15
  19. package/packages/endpoint-kit/README.md +11 -2
  20. package/packages/endpoint-kit/src/index.ts +1 -1
  21. package/packages/generation/README.md +10 -0
  22. package/packages/provider-hypihub/README.md +41 -6
  23. package/packages/provider-hypihub/src/errors.ts +61 -0
  24. package/packages/provider-hypihub/src/mapping.ts +10 -25
  25. package/packages/provider-hypihub/src/oauth.ts +4 -1
  26. package/packages/provider-hypihub/src/provider.ts +104 -75
  27. package/packages/provider-hypihub/src/routes.ts +24 -3
  28. package/packages/provider-hypihub/src/upload.ts +8 -18
  29. package/packages/provider-whisperx-local/README.md +7 -1
  30. package/packages/provider-whisperx-local/src/program.ts +2 -0
  31. package/packages/runtime-host-node/src/index.ts +4 -0
  32. package/packages/runtime-kit/README.md +3 -0
  33. package/packages/runtime-kit/src/index.ts +2 -0
  34. package/packages/runtime-local/README.md +17 -0
  35. package/packages/runtime-local/package.json +2 -1
  36. package/packages/runtime-local/src/program-lock.ts +40 -0
  37. package/packages/runtime-local/src/programs.ts +156 -79
  38. package/packages/video-cli/README.md +33 -6
  39. package/packages/video-cli/src/cli.ts +4 -1
  40. package/packages/video-cli/src/creation.ts +7 -2
  41. package/packages/video-cli/src/index.ts +2 -0
  42. package/packages/video-cli/src/version.ts +90 -0
  43. package/services/whisperx/README.md +10 -3
  44. package/services/whisperx/src/hypit_whisperx_service/engine.py +19 -1
@@ -17,6 +17,7 @@ import { createPlanOutput, createPricingOutput, writeCliHelp, writeCliOutput } f
17
17
  import type { CliIo, CliMachineView } from "./output.js";
18
18
  import { parseCommand } from "./arguments.js";
19
19
  import type { CliCommand, RuntimeOption } from "./command.js";
20
+ import { commandHint } from "./command-hint.js";
20
21
  import { assertPlannedRequests, assertPreflight, createCatalogDescriptor, describePlanNeeds, describePlanPricing, describePlanProviders, evaluatePlanNeeds, preflightPlan } from "./build-planning.js";
21
22
  import { buildProgressLines, observeBuild } from "./observation.js";
22
23
  import { isProjectResultCommand, runProjectResultCommand } from "./commands/results.js";
@@ -180,14 +181,11 @@ export async function runCli(
180
181
 
181
182
  let runtimeProfile = acceptsRuntimeContext(args) ? args.runtimeProfile : undefined;
182
183
  let runtimeSelectionFile: string | undefined;
183
- const runtimeWasExplicit = runtimeProfile !== undefined;
184
- let runtimeNeedsHint = runtimeWasExplicit;
185
184
  if (acceptsRuntimeContext(args) && runtimeProfile === undefined && args.command !== "logs") {
186
185
  const selected = await findRuntimeProfile(await commandProjectRoot());
187
186
  if (selected !== undefined) {
188
187
  runtimeProfile = selected.profile;
189
188
  runtimeSelectionFile = selected.selectionFile;
190
- runtimeNeedsHint = false;
191
189
  }
192
190
  }
193
191
  const runtimeController = async (profile: string): Promise<CliRuntimeController> => {
@@ -228,6 +226,7 @@ export async function runCli(
228
226
  if (isExecutionCommand(args)) {
229
227
  await runExecutionCommand({
230
228
  args,
229
+ projectRoot: await commandProjectRoot(),
231
230
  runtimeProfile,
232
231
  io,
233
232
  runtimeHost,
@@ -347,6 +346,7 @@ export async function runCli(
347
346
  if (runtimeProfile === undefined) {
348
347
  throw new Error("build requires a Runtime; run hypit runtime init, select one with runtime use, or pass --runtime <profile>");
349
348
  }
349
+ const commandScope = { projectRoot: projectResultsRoot, runtimeProfile: resolve(runtimeProfile) };
350
350
  const buildResults = await projectResults(projectResultsRoot);
351
351
  let loadedRun;
352
352
  try {
@@ -435,7 +435,7 @@ export async function runCli(
435
435
  ]),
436
436
  ].join(" · ");
437
437
  if (args.follow && "view" in built && !args.presentation.json) {
438
- const acceptedView = buildStatusView({ id: built.id, runtime: built.view });
438
+ const acceptedView = buildStatusView({ id: built.id, runtime: built.view, commandScope });
439
439
  const targets = built.view.targets.slice(0, args.limit);
440
440
  writeOperational({
441
441
  format: "hypit.cli-build@1",
@@ -452,15 +452,15 @@ export async function runCli(
452
452
  built = await observeBuild(runtime, built, {
453
453
  ...(args.maxWaitMs === undefined ? {} : { maxWaitMs: args.maxWaitMs }),
454
454
  controller,
455
+ commandScope,
455
456
  readResult: async () => await buildResults.repository.read(built.id),
456
- ...(args.presentation.json ? {} : {
457
- onProgress: (progress) => {
458
- for (const line of buildProgressLines(progress, {
459
- verbose: args.presentation.verbose,
460
- limit: args.limit,
461
- })) io.write(`${line}\n`);
462
- },
463
- }),
457
+ onProgress: (progress) => {
458
+ const report = io.writeProgress ?? (args.presentation.json ? undefined : io.write);
459
+ for (const line of buildProgressLines(progress, {
460
+ verbose: args.presentation.verbose,
461
+ limit: args.limit,
462
+ })) report?.(`${line}\n`);
463
+ },
464
464
  });
465
465
  }
466
466
  const finished = "completion" in built;
@@ -491,6 +491,7 @@ export async function runCli(
491
491
  });
492
492
  const buildView = buildStatusView({
493
493
  id: built.id,
494
+ commandScope,
494
495
  ...(activeView === undefined ? {} : { runtime: activeView }),
495
496
  ...(finishedResult === undefined ? {} : { result: finishedResult }),
496
497
  verbose: args.presentation.verbose,
@@ -501,7 +502,7 @@ export async function runCli(
501
502
  ? buildView
502
503
  : { ...buildView, title: args.title },
503
504
  };
504
- const runtimeHint = runtimeNeedsHint ? ` --runtime ${runtimeProfile}` : "";
505
+ const resultScope = { projectRoot: projectResultsRoot };
505
506
  const resultTargets = finishedResult?.targets.flatMap((name) => {
506
507
  const output = finishedResult.outputs[name];
507
508
  return output === undefined ? [] : [{ name, output }];
@@ -509,11 +510,11 @@ export async function runCli(
509
510
  const finishedLines = resultTargets.length === 0 && targetPresentations.length === 0
510
511
  ? [
511
512
  ...(completionReason === undefined ? [] : [`Reason ${completionReason}`]),
512
- `Inspect hypit inspect ${built.id}`,
513
+ `Inspect ${commandHint(["inspect", built.id], resultScope)}`,
513
514
  ]
514
515
  : [
515
516
  ...(completionReason === undefined ? [] : [`Reason ${completionReason}`]),
516
- `Inspect hypit inspect ${built.id}`,
517
+ `Inspect ${commandHint(["inspect", built.id], resultScope)}`,
517
518
  ...resultTargets
518
519
  .filter((item) => item.output.value.kind === "inline")
519
520
  .slice(0, args.limit)
@@ -524,14 +525,14 @@ export async function runCli(
524
525
  .filter((item) => item.output.value.kind !== "inline")
525
526
  .slice(0, args.limit)
526
527
  .map((item) =>
527
- `Export hypit get ${built.id} --output ${item.name} --to <path>`),
528
+ `Export ${commandHint(["get", built.id, "--output", item.name], resultScope)} --to <path>`),
528
529
  ...(resultTargets.length > 0 ? [] : targetPresentations
529
530
  .filter((item) => item.inline !== undefined)
530
531
  .slice(0, args.limit)
531
532
  .map((item) => `Result ${item.published.name} = ${item.inline}`)),
532
533
  ];
533
534
  const humanTitle = issue !== undefined
534
- ? "Result needs attention"
535
+ ? "Build needs attention"
535
536
  : finished
536
537
  ? buildOutcome === "complete"
537
538
  ? "Build complete"
@@ -551,14 +552,14 @@ export async function runCli(
551
552
  ] : []),
552
553
  ...(issue === undefined ? [] : [
553
554
  ["Execution", buildOutcome ?? machine.build.work.state] as const,
554
- ["Result", "needs attention"] as const,
555
+ [issue.scope === "cleanup" ? "Cleanup" : "Result", "needs attention"] as const,
555
556
  ]),
556
557
  ], finished ? finishedLines : issue !== undefined ? [
557
- `Result ${issue.scope}: ${issue.message}`,
558
- `Finish hypit result finish ${built.id}${runtimeHint}`,
558
+ `Attention ${issue.scope}: ${issue.message}`,
559
+ `Finish ${commandHint(["result", "finish", built.id], commandScope)}`,
559
560
  ] : [
560
- `Watch hypit status ${built.id}${runtimeHint} --watch`,
561
- `Cancel hypit cancel ${built.id}${runtimeHint}`,
561
+ `Watch ${commandHint(["status", built.id, "--watch"], commandScope)}`,
562
+ `Cancel ${commandHint(["cancel", built.id], commandScope)}`,
562
563
  ]);
563
564
  if (buildOutcome === "failed" || issue !== undefined) io.setExitCode?.(1);
564
565
  } finally {
@@ -1,6 +1,8 @@
1
1
  import type { BuildResultManifest } from "@hypit/build-result";
2
2
 
3
3
  import type { CliBuildSubmission, CliBuildView, CliRuntimeControl, CliRuntimeController } from "./runtime-port.js";
4
+ import { commandHint } from "./command-hint.js";
5
+ import type { CommandScope } from "./command-hint.js";
4
6
  import { formatOperationProgress } from "./runtime-view.js";
5
7
 
6
8
  const FIRST_HEARTBEAT_MS = 30_000;
@@ -122,6 +124,7 @@ export async function observeBuildView(
122
124
  readonly maxWaitMs?: number;
123
125
  readonly controller?: CliRuntimeController;
124
126
  readonly onProgress?: (value: BuildProgressView) => void;
127
+ readonly commandScope?: CommandScope;
125
128
  } = {},
126
129
  ): Promise<CliBuildView | undefined> {
127
130
  let view: CliBuildView | undefined = initial;
@@ -158,8 +161,9 @@ export async function observeBuildView(
158
161
  const worker = await options.controller.worker.status();
159
162
  if (worker.state !== "running") {
160
163
  throw new Error(
161
- `Runtime Worker is ${worker.state}; Build ${build} remains durable. `
162
- + `Run hypit runtime up, then run hypit status ${build} --watch again`,
164
+ `Runtime Worker is ${worker.state}; stopped watching Build ${build}. `
165
+ + `Inspect the Runtime with ${commandHint(["runtime", "status"], options.commandScope)}; `
166
+ + `read execution evidence with ${commandHint(["logs", build], options.commandScope)}`,
163
167
  );
164
168
  }
165
169
  }
@@ -184,6 +188,7 @@ export async function observeBuild(
184
188
  readonly controller?: CliRuntimeController;
185
189
  readonly readResult: () => Promise<BuildResultManifest | undefined>;
186
190
  readonly onProgress?: (value: BuildProgressView) => void;
191
+ readonly commandScope?: CommandScope;
187
192
  },
188
193
  ): Promise<CliBuildSubmission> {
189
194
  const finishFromResult = async (): Promise<CliBuildSubmission> => {
@@ -971,6 +971,9 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
971
971
  " hypit auth login <endpoint-instance> [--runtime <profile>] [--slot <name>] [--from <secret-file>]",
972
972
  " hypit auth logout <endpoint-instance> [--runtime <profile>] [--slot <name>]",
973
973
  " All auth actions accept --workspace <project> to use that project's selection.",
974
+ " status shows credential presence and the Provider's declared browser login, if any.",
975
+ " login opens that browser flow or securely prompts for the secret; --from imports a secret instead.",
976
+ " Configure the service's Endpoint first. Storing a key does not install a Provider or select bindings.",
974
977
  ],
975
978
  };
976
979
  const selected = topics[topic];
@@ -8,6 +8,8 @@ import type {
8
8
  import { buildIdCreatedAt } from "@hypit/protocol";
9
9
  import type { TypeRef } from "@hypit/protocol";
10
10
  import type { BuildView } from "@hypit/runtime-host-node";
11
+ import { commandHint } from "./command-hint.js";
12
+ import type { CommandScope } from "./command-hint.js";
11
13
 
12
14
  export type PublicOutputKind = "scalar" | "resource" | "composite";
13
15
 
@@ -159,6 +161,7 @@ export function buildStatusView(options: {
159
161
  readonly result?: BuildResultManifest;
160
162
  readonly resultReadError?: string;
161
163
  readonly verbose?: boolean;
164
+ readonly commandScope?: CommandScope;
162
165
  }): CliBuildStatusView {
163
166
  const resultState = options.resultReadError !== undefined
164
167
  ? "unavailable" as const
@@ -195,7 +198,7 @@ export function buildStatusView(options: {
195
198
  attention: issue !== undefined
196
199
  ? {
197
200
  message: issue.message,
198
- ...(issue.scope === "result" ? { action: `hypit result finish ${options.id}` } : {}),
201
+ action: commandHint(["result", "finish", options.id], options.commandScope),
199
202
  }
200
203
  : { message: options.resultReadError! },
201
204
  }),
@@ -17,6 +17,11 @@ Build. Credentials are resolved only for slots declared by the selected endpoint
17
17
  of the same credential are coalesced. Cancellation is best effort and never rolls back completed work.
18
18
  A failed action retains its receipt and error for the Result; the Driver does not retry or reconcile it.
19
19
 
20
+ Immediate calls and asynchronous submit, poll and collect actions receive the same progress and
21
+ diagnostic callbacks. Progress reaches the caller while the action is running; changes of phase also
22
+ enter the execution log. The Endpoint supplies the content, and the Driver forwards it with the
23
+ selected Endpoint identity without interpreting service-specific phases or treating progress as a receipt.
24
+
20
25
  `acceptOperation()` validates an already received value without making a remote call. Runtime can
21
26
  retain such a sibling result when another Need fails, without polling unfinished jobs to completion.
22
27
  The Driver validates returned values before offering a command result to Core. It does not load
@@ -22,6 +22,7 @@ import type {
22
22
  CredentialStore,
23
23
  CredentialValue,
24
24
  OperationSnapshot,
25
+ OperationProgress,
25
26
  OperationStore,
26
27
  RuntimeExecutionContext,
27
28
  RuntimeExecutionResult,
@@ -349,6 +350,23 @@ export class NodeDriver {
349
350
  return result;
350
351
  }
351
352
 
353
+ #endpointObservers(endpoint: string, context?: RuntimeExecutionContext) {
354
+ let phase: string | undefined;
355
+ return {
356
+ reportProgress: async (progress: OperationProgress) => {
357
+ await context?.reportProgress?.({ endpoint, progress });
358
+ if (progress.phase !== phase) {
359
+ phase = progress.phase;
360
+ await context?.recordExecution?.({ endpoint, kind: "phase", phase });
361
+ }
362
+ },
363
+ ...(context?.recordExecution === undefined ? {} : {
364
+ reportDiagnostic: (diagnostic: import("@hypit/runtime").ExecutionDiagnostic) =>
365
+ context.recordExecution!({ endpoint, kind: "diagnostic", ...diagnostic }),
366
+ }),
367
+ };
368
+ }
369
+
352
370
  async #advanceOperationStep(
353
371
  operation: OperationSnapshot,
354
372
  registration: EndpointRegistration,
@@ -374,10 +392,7 @@ export class NodeDriver {
374
392
  command: structuredClone(command), need: structuredClone(command.need),
375
393
  resources: this.#resourceStore(operation.build), operation: operation.id,
376
394
  credentials: await this.#endpointCredentials({ ...registration, credentials: operation.credentials ?? registration.credentials ?? {} }),
377
- ...(runtimeContext.recordExecution === undefined ? {} : {
378
- reportDiagnostic: (diagnostic: import("@hypit/runtime").ExecutionDiagnostic) =>
379
- runtimeContext.recordExecution!({ endpoint: operation.endpoint, kind: "diagnostic", ...diagnostic }),
380
- }),
395
+ ...this.#endpointObservers(operation.endpoint, runtimeContext),
381
396
  checkpoint: async (checkpoint: import("@hypit/endpoint-kit").EndpointCheckpoint) => {
382
397
  await operations.update(operation.id, { status: "pending", ...checkpoint,
383
398
  submission: "accepted", acknowledgedAt: operation.acknowledgedAt ?? Date.now(), wakeAt: Date.now(), progress: { phase: checkpoint.remoteEnded ? "collecting" : "submitted" } });
@@ -493,7 +508,6 @@ export class NodeDriver {
493
508
  );
494
509
  }
495
510
  }
496
- let phase: string | undefined;
497
511
  await context?.recordExecution?.({ endpoint: executable.endpointId, kind: "started" });
498
512
  try {
499
513
  const result = await executable.registration.handler({
@@ -501,16 +515,7 @@ export class NodeDriver {
501
515
  need: structuredClone(executable.command.need),
502
516
  resources: this.#resourceStore(context?.build),
503
517
  credentials: await this.#endpointCredentials(executable.registration),
504
- reportProgress: async (progress) => {
505
- await context?.reportProgress?.({ endpoint: executable.endpointId, progress });
506
- if (progress.phase !== phase) {
507
- phase = progress.phase;
508
- await context?.recordExecution?.({ endpoint: executable.endpointId, kind: "phase", phase });
509
- }
510
- },
511
- ...(context?.recordExecution === undefined ? {} : {
512
- reportDiagnostic: (diagnostic) => context.recordExecution!({ endpoint: executable.endpointId, kind: "diagnostic", ...diagnostic }),
513
- }),
518
+ ...this.#endpointObservers(executable.endpointId, context),
514
519
  });
515
520
  const event = await this.#endpointEvent(state, executable, result);
516
521
  await context?.recordExecution?.({ endpoint: executable.endpointId, kind: "completed" });
@@ -35,12 +35,21 @@ without changing Core demand. Each resource declares a `limit` and optional `uni
35
35
  A capability may add `resources` and a pure `unitsForRequest(request)` resolver for quantities of
36
36
  already declared resources. Runtime admits all claims atomically for one `fulfill-need` Command.
37
37
 
38
- A long immediate call can report its current activity with
38
+ A running immediate call or asynchronous action can report its current activity with
39
39
  `await context.reportProgress?.({ phase: "processing", completed: 12, total: 40, unit: "items" })`.
40
40
  The Provider chooses meaningful phases and quantities and reports non-secret, human-readable facts.
41
41
  The Runtime attaches them to the currently executing Command; they do not change its outcome,
42
42
  scheduling or Core facts. Completion clears the live activity. Direct callers may omit the callback.
43
- For asynchronous work, return `progress` in the existing pending outcome instead.
43
+ For asynchronous work, these callbacks describe work inside `start`, `poll` or `collect`, such as
44
+ preparing references or downloading results. A returned `pending.progress` describes the acknowledged
45
+ remote task between actions. Neither form of progress substitutes for a received task ID or receipt.
46
+
47
+ Separate facts available from the request from actions needed to fulfill it. Check known input
48
+ limits and determine the requested service operation before transferring its resources. If the
49
+ service exposes account-specific capability information, use that evidence for the selected request;
50
+ an API without such a query needs no invented discovery step. A Provider owns the meaning of its
51
+ public error codes and reasons. Preserve the failed operation and that evidence, distinguishing what
52
+ was never submitted from a submission whose remote outcome is unknown.
44
53
 
45
54
  Asynchronous execution moves forward through `start`, `poll`, and optional `collect`. `start` returns
46
55
  a task handle; `pending` means an acknowledged task is still running. `ready` records remote completion
@@ -101,7 +101,7 @@ export type EndpointInvocationContext = {
101
101
  readonly credentials: Readonly<Record<string, EndpointCredential>>;
102
102
  /** Explicit non-secret diagnostic output. The Provider owns its content, never its storage. */
103
103
  readonly reportDiagnostic?: (diagnostic: import("@hypit/runtime").ExecutionDiagnostic) => Promise<void>;
104
- /** Report non-secret activity of a long local call without creating a remote Operation. */
104
+ /** Report non-secret activity while an immediate call or asynchronous action is still running. */
105
105
  readonly reportProgress?: (progress: OperationProgress) => Promise<void>;
106
106
  };
107
107
 
@@ -9,6 +9,16 @@ implementations. `sealGenerationPortTable` describes a model's inputs; `Generati
9
9
  `selectWireModelForRequest` applies the same route selection without resolving media bytes. During
10
10
  planning, a Provider may add the model-port names already attached as future graph inputs; the
11
11
  selector does not inspect graph structure or interpret media roles.
12
+ Route selection here applies declared input-mode mappings for the already chosen exact capability;
13
+ it does not search for an alternative model, service or account.
14
+
15
+ `compileWireRequest` awaits its media URL resolver, so compilation can have external effects when
16
+ that resolver uploads files. Use `selectWireModelForRequest` for request identity and known-port
17
+ checks before those effects; pricing and execution can share the same mapping. A Provider can also
18
+ describe its API operation from the authored ports. Service-specific support queries belong to that
19
+ Provider and only use operations its API actually exposes. The generic helper neither queries a
20
+ catalogue nor switches to another route after a failure. Actual URL resolution belongs to request
21
+ execution, not to discovering a model name or making a planning placeholder.
12
22
 
13
23
  The package owns `GenerationRequest`, `GeneratedImageSet` and `GeneratedVideoSet` identities,
14
24
  schemas, validators and graph facets. Generated sets are atomic Products: a Provider persists the
@@ -16,7 +16,7 @@ admits them into the current Build's working byte area. Image references use Hyp
16
16
  `reference_image_urls`, `reference_videos`, and `reference_audios` fields. A single reference video
17
17
  remains in `reference_videos`; `ref_video_url` is reserved for a model's source-video port. First/last-frame images use `first_frame` and `last_frame`.
18
18
 
19
- Seedance 2.5 (`@hypit/seedance` model `2.5`) maps to `bytedance/seedance-2-5` and supports
19
+ Seedance 2.5 (`@hypit/seedance` model `2.5`) maps to `seedance-2.5` and supports
20
20
  `480p`, `720p` and `1080p`. The Provider passes the authored `resolution` to `POST /v1/videos`;
21
21
  omitting it in the Seedance Surface defaults to `720p`.
22
22
 
@@ -31,11 +31,44 @@ The current HypiHub GPT Image 2 route has these service-specific limits:
31
31
  HypiHub owns this support check independently: it leaves the GPT Image model package unchanged. When the service surface changes, this Provider can change without changing
32
32
  the model or another Provider.
33
33
 
34
- Model identity is preserved across input modes: Seedream 5 Lite references use the Lite image-edit
35
- route, and Grok 1.5 Preview remains the Preview model. A deployment's current catalogue may offer
34
+ Model identity and input mode are separate. The mapping uses HypiHub's canonical model names:
35
+ `gpt-image-2`, `seedream-5-lite`, `minimax-h3`, `grok-imagine-video` and the individual Seedance names.
36
+ An image request without references uses `/images/generations`; image edits use `/images/edits`
37
+ with the same model name. Video requests use `/videos`, preserving reference images, reference
38
+ videos and first/last frames in their distinct fields. Old operation-specific names are not needed
39
+ to express these modes; compatibility with previously released clients belongs to the service.
40
+
41
+ Seedream 5 Lite remains Lite across input modes, and Grok 1.5 Preview remains Preview.
42
+ A deployment's current catalogue may offer
36
43
  newer models or omit one implemented here. Availability and unsupported-input errors retain their
37
44
  service explanation; they do not imply expired credentials or authorize substituting another model.
38
45
 
46
+ Before resolving or uploading references, the Provider prepares the exact model and operation from
47
+ the authored ports and its mapping, then queries that model's authenticated directory entry. Image
48
+ editing is determined from the mapped media inputs, without manufacturing placeholder URLs or
49
+ uploading to discover the request mode. The same preparation serves images, videos and speech.
50
+ The request is then translated with real reference URLs and submitted to the selected operation.
51
+ These are internal Provider functions; Author Sources, CLI commands and Runtime scheduling are unchanged.
52
+
53
+ Progress identifies the directory query, request preparation and submission. A directory failure
54
+ retains the model, operation and service evidence, and states that this invocation uploaded no
55
+ references and submitted no generation. A missing or malformed operation list leaves support unknown;
56
+ an explicit list without the requested operation reports the actual list. No alternative model,
57
+ operation or account is attempted. Directory support alone does not establish balance, every input
58
+ combination or eventual generation success. Preparation failure also reports that generation was not
59
+ submitted; an interrupted submission retains its actual evidence without claiming no remote work exists.
60
+
61
+ The Provider interprets HypiHub's [HTTP errors](https://hypit.ai/api-reference/errors/) and
62
+ [job errors](https://hypit.ai/api-reference/jobs/) locally. Failures retain the service code,
63
+ HTTP method/route/status, requested model and `X-Request-Id` when available, plus the public reason.
64
+ `Retry-After` remains evidence and does not start another generation attempt. Job failures retain
65
+ `error_code`, `error` and their receipt even when the create response is already terminal.
66
+ Known error messages are not cut to a fixed prefix; only an unstructured non-JSON response uses
67
+ a marked excerpt. Unrelated response fields and signed asset URLs are not diagnostic content.
68
+ Runtime and Result retain ordinary failure codes/messages without interpreting HypiHub fields.
69
+ Immediate Endpoint exceptions keep the same evidence in their message. A `401` alone does not
70
+ choose OAuth over API-key configuration or establish that another login will fix the account.
71
+
39
72
  For moving portraits, [Volcengine Matting](../volcengine-matting/README.md) maps
40
73
  `@hypit/volcengine-matting@1#matte-portrait-video` to `POST /v1/videos` with
41
74
  `model: "matte-portrait-video"`, `ref_video_url` and `format` (`WEBM` by default, or `MOV`).
@@ -122,8 +155,10 @@ catalog to verify configured capabilities; ordinary preflight never makes that r
122
155
  declares HypiHub's public pricing page, `https://hypit.ai/commercial/pricing/`, as its price source.
123
156
  For each selected Need, `readPricing` resolves the corresponding HypiHub model and returns the service's
124
157
  authenticated `GET /v1/pricing?model=<model>` response unchanged together with that URL. It covers
125
- generation, alignment, Voice Design and Voice Clone through the same mechanism;
126
- the Provider does not maintain a second list of billing formulas or calculate a request total.
158
+ generation, alignment, Voice Design and Voice Clone through the same mechanism. The document's
159
+ per-operation prices are retained alongside its default price, including when references are still
160
+ pending. A model's default price is not a quote for every input mode. The Provider does not maintain
161
+ a second list of billing formulas or calculate a request total.
127
162
 
128
163
  The Provider declares its implemented speech capabilities alongside image, video and alignment.
129
164
  Voice Design produces an accepted voice-reference Resource, and Voice Clone uses that
@@ -148,7 +183,7 @@ Execution policy remains local to this Provider:
148
183
  | `requestTimeoutMs` | 300 seconds | ordinary Provider HTTP requests |
149
184
  | `oauthRequestTimeoutMs` | 30 seconds | OAuth token exchange and refresh |
150
185
  | `pricingRequestTimeoutMs` | 30 seconds | authenticated pricing requests |
151
- | `operationTimeoutMs` | 20 minutes | one remote asynchronous operation |
186
+ | `operationTimeoutMs` | 20 minutes | how long this Provider observes one asynchronous operation; expiry does not cancel the remote job |
152
187
  | `uploadConcurrency` | 8 | whole file sessions per origin/credential within this process |
153
188
  | `uploadPartTimeoutMs` | 5 minutes | one upload part |
154
189
  | `uploadPartAttempts` | 3 | attempts for one upload part |
@@ -0,0 +1,61 @@
1
+ /** HypiHub's public error envelope, 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 safeHypiHubReason(value: string): string {
12
+ return value.replace(/https?:\/\/\S+/giu, "[redacted-url]");
13
+ }
14
+
15
+ export class HypiHubServiceError extends Error {
16
+ constructor(readonly code: string, message: string) { super(message); }
17
+ }
18
+
19
+ export class HypiHubHttpError extends HypiHubServiceError {
20
+ readonly retryAfterMs?: number;
21
+ constructor(readonly status: number, response: { readonly headers: Headers }, bodyText: string,
22
+ request: { readonly method: string; readonly url: string; readonly model?: string }) {
23
+ let body: Record<string, unknown> | undefined;
24
+ try { body = record(JSON.parse(bodyText)); } catch { /* Non-JSON gateway failures still have HTTP evidence. */ }
25
+ const error = record(body?.error);
26
+ const flatError = text(body?.error);
27
+ const code = text(error?.code)
28
+ ?? (flatError !== undefined && /^[a-z][a-z0-9_]*$/iu.test(flatError) ? flatError : "HYPIHUB_HTTP_ERROR");
29
+ const reason = text(error?.message) ?? text(body?.error_description)
30
+ ?? (flatError !== code ? flatError : undefined)
31
+ ?? (body === undefined ? text(bodyText.slice(0, 2000)) : undefined);
32
+ const url = new URL(request.url);
33
+ const model = request.model ?? url.searchParams.get("model")
34
+ ?? (url.pathname.includes("/models/") ? decodeURIComponent(url.pathname.split("/models/")[1]!) : undefined);
35
+ const requestId = text(response.headers.get("x-request-id"));
36
+ const retryAfter = text(response.headers.get("retry-after"));
37
+ const facts = [
38
+ `HypiHub HTTP ${status}`, code, `${request.method} ${url.origin}${url.pathname}`,
39
+ ...(model == null ? [] : [`model=${model}`]),
40
+ ...(requestId === undefined ? [] : [`request=${requestId}`]),
41
+ ...(retryAfter === undefined ? [] : [`retry-after=${retryAfter}`]),
42
+ ];
43
+ super(code, `${facts.join("; ")}${reason === undefined ? "" : `: ${safeHypiHubReason(reason)}`}`
44
+ + (body === undefined && bodyText.length > 2000 ? " [response excerpt truncated]" : ""));
45
+ const delay = retryAfter === undefined ? NaN : /^\d+$/u.test(retryAfter)
46
+ ? Number(retryAfter) * 1000 : Date.parse(retryAfter) - Date.now();
47
+ if (Number.isFinite(delay)) this.retryAfterMs = Math.max(0, delay);
48
+ }
49
+ }
50
+
51
+ export function hypiHubJobFailure(job: Record<string, unknown>, id: string): HypiHubServiceError | undefined {
52
+ const status = job.status;
53
+ if (!["failed", "queue_expired", "canceled", "cancelled"].includes(String(status))) return undefined;
54
+ const error = record(job.error);
55
+ const code = text(job.error_code) ?? text(error?.code) ?? "HYPIHUB_JOB_FAILED";
56
+ const reason = text(job.error) ?? text(error?.message) ?? text(job.message) ?? text(job.reason) ?? text(job.detail);
57
+ const model = text(job.model);
58
+ return new HypiHubServiceError(code,
59
+ `HypiHub job ${id} ${status}; ${code}${model === undefined ? "" : `; model=${model}`}`
60
+ + (reason === undefined ? "" : `: ${safeHypiHubReason(reason)}`));
61
+ }
@@ -12,8 +12,8 @@ const MIMO_SPEECH: ModuleRef = { name: "@hypit/mimo-speech", version: "1" };
12
12
  const FISHAUDIO_SPEECH: ModuleRef = { name: "@hypit/fishaudio-speech", version: "1" };
13
13
  const ELEVENLABS_SPEECH: ModuleRef = { name: "@hypit/elevenlabs-speech", version: "1" };
14
14
 
15
- const seedance = (name: string, model: string): GenerationWireMapping => ({
16
- capability: { module: SEEDANCE, name }, result: "video", routes: [{ model }],
15
+ const seedance = (name: string): GenerationWireMapping => ({
16
+ capability: { module: SEEDANCE, name }, result: "video", routes: [{ model: name }],
17
17
  fields: {
18
18
  prompt: { as: "value", field: "prompt" },
19
19
  referenceImage: { as: "urlArray", field: "reference_image_urls", resourceFields: ["personReference"] },
@@ -38,15 +38,12 @@ export const hypiHubMappings: readonly GenerationWireMapping[] = [
38
38
  format: { as: "value", field: "format", whenAbsent: "WEBM" },
39
39
  },
40
40
  },
41
- seedance("seedance-2", "bytedance/seedance-2"),
42
- seedance("seedance-2-fast", "bytedance/seedance-2-fast"),
43
- seedance("seedance-2-mini", "bytedance/seedance-2-mini"),
44
- seedance("seedance-2.5", "bytedance/seedance-2-5"),
41
+ seedance("seedance-2"),
42
+ seedance("seedance-2-fast"),
43
+ seedance("seedance-2-mini"),
44
+ seedance("seedance-2.5"),
45
45
  {
46
- capability: { module: GPT_IMAGE, name: "gpt-image-2" }, result: "image", routes: [
47
- { model: "gpt-image-2-image-to-image", whenPresent: ["images"] },
48
- { model: "gpt-image-2-text-to-image" },
49
- ],
46
+ capability: { module: GPT_IMAGE, name: "gpt-image-2" }, result: "image", routes: [{ model: "gpt-image-2" }],
50
47
  fields: {
51
48
  prompt: { as: "value", field: "prompt" },
52
49
  aspectRatio: { as: "value", field: "aspect_ratio" },
@@ -66,10 +63,7 @@ export const hypiHubMappings: readonly GenerationWireMapping[] = [
66
63
  },
67
64
  })),
68
65
  {
69
- capability: { module: SEEDREAM, name: "seedream-5-lite" }, result: "image", routes: [
70
- { model: "seedream/5-lite-image-to-image", whenPresent: ["images"] },
71
- { model: "seedream/5-lite-text-to-image" },
72
- ],
66
+ capability: { module: SEEDREAM, name: "seedream-5-lite" }, result: "image", routes: [{ model: "seedream-5-lite" }],
73
67
  fields: {
74
68
  prompt: { as: "value", field: "prompt" },
75
69
  aspectRatio: { as: "value", field: "aspect_ratio" },
@@ -80,13 +74,7 @@ export const hypiHubMappings: readonly GenerationWireMapping[] = [
80
74
  },
81
75
  },
82
76
  {
83
- capability: { module: MINIMAX, name: "minimax-h3" }, result: "video", routes: [
84
- { model: "minimax-h3/image-to-video", whenPresent: ["lastFrame"] },
85
- { model: "minimax-h3/image-to-video", whenPresent: ["firstFrame"] },
86
- { model: "minimax-h3/reference-to-video", whenPresent: ["referenceImage"] },
87
- { model: "minimax-h3/reference-to-video", whenPresent: ["referenceVideo"] },
88
- { model: "minimax-h3/text-to-video" },
89
- ],
77
+ capability: { module: MINIMAX, name: "minimax-h3" }, result: "video", routes: [{ model: "minimax-h3" }],
90
78
  fields: {
91
79
  prompt: { as: "value", field: "prompt" }, duration: { as: "value", field: "seconds" },
92
80
  resolution: { as: "value", field: "resolution", whenAbsent: "2k" }, aspectRatio: { as: "value", field: "aspect_ratio" },
@@ -98,10 +86,7 @@ export const hypiHubMappings: readonly GenerationWireMapping[] = [
98
86
  },
99
87
  },
100
88
  {
101
- capability: { module: GROK, name: "grok-imagine-video" }, result: "video", routes: [
102
- { model: "grok-imagine/image-to-video", whenPresent: ["images"] },
103
- { model: "grok-imagine/text-to-video" },
104
- ],
89
+ capability: { module: GROK, name: "grok-imagine-video" }, result: "video", routes: [{ model: "grok-imagine-video" }],
105
90
  fields: {
106
91
  prompt: { as: "value", field: "prompt" }, duration: { as: "value", field: "seconds" },
107
92
  resolution: { as: "value", field: "resolution" }, aspectRatio: { as: "value", field: "aspect_ratio" },
@@ -1,6 +1,7 @@
1
1
  import type { EndpointCredential } from "@hypit/endpoint-kit";
2
2
  import { decodeOAuth2Credential, encodeOAuth2Credential } from "@hypit/runtime";
3
3
  import { requestDeadline } from "@hypit/runtime-kit";
4
+ import { HypiHubHttpError } from "./errors.js";
4
5
 
5
6
  const OAUTH_CLIENT_ID = "hyc_d5d5e8e7131b0c877756e66c";
6
7
  const REFRESH_SKEW_MS = 60_000;
@@ -78,10 +79,12 @@ export function createHypiHubAuth(options: {
78
79
  signal: deadline.signal,
79
80
  }));
80
81
  const text = await deadline.wait(response.text());
82
+ if (!response.ok) throw new HypiHubHttpError(response.status, response, text, {
83
+ method: "POST", url: tokenEndpoint,
84
+ });
81
85
  let body: OAuthTokenResponse;
82
86
  try { body = JSON.parse(text) as OAuthTokenResponse; }
83
87
  catch { throw new Error(`HypiHub OAuth refresh returned invalid JSON (${response.status})`); }
84
- if (!response.ok) throw new Error(`HypiHub OAuth refresh failed (${response.status}): ${text.slice(0, 200)}`);
85
88
  assert(typeof body.access_token === "string" && body.access_token.length > 0,
86
89
  "HypiHub OAuth refresh returned no access token");
87
90
  accessToken = body.access_token;