@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.
- package/README.md +15 -4
- package/dist/public/endpoint-kit.d.ts +1 -1
- package/dist/public/runtime-kit.d.ts +3 -1
- package/examples/provider-package/README.md +12 -0
- package/examples/provider-package/packages/provider-images/src/provider.ts +37 -8
- package/package.json +1 -1
- package/packages/cli/README.md +27 -0
- package/packages/cli/src/command-hint.ts +20 -0
- package/packages/cli/src/commands/environment.ts +39 -18
- package/packages/cli/src/commands/execution.ts +27 -17
- package/packages/cli/src/commands/results.ts +2 -1
- package/packages/cli/src/machine-view.ts +4 -2
- package/packages/cli/src/main.ts +23 -22
- package/packages/cli/src/observation.ts +7 -2
- package/packages/cli/src/output.ts +3 -0
- package/packages/cli/src/view.ts +4 -1
- package/packages/driver-node/README.md +5 -0
- package/packages/driver-node/src/driver.ts +20 -15
- package/packages/endpoint-kit/README.md +11 -2
- package/packages/endpoint-kit/src/index.ts +1 -1
- package/packages/generation/README.md +10 -0
- package/packages/provider-hypihub/README.md +41 -6
- package/packages/provider-hypihub/src/errors.ts +61 -0
- package/packages/provider-hypihub/src/mapping.ts +10 -25
- package/packages/provider-hypihub/src/oauth.ts +4 -1
- package/packages/provider-hypihub/src/provider.ts +104 -75
- package/packages/provider-hypihub/src/routes.ts +24 -3
- package/packages/provider-hypihub/src/upload.ts +8 -18
- package/packages/provider-whisperx-local/README.md +7 -1
- package/packages/provider-whisperx-local/src/program.ts +2 -0
- package/packages/runtime-host-node/src/index.ts +4 -0
- package/packages/runtime-kit/README.md +3 -0
- package/packages/runtime-kit/src/index.ts +2 -0
- package/packages/runtime-local/README.md +17 -0
- package/packages/runtime-local/package.json +2 -1
- package/packages/runtime-local/src/program-lock.ts +40 -0
- package/packages/runtime-local/src/programs.ts +156 -79
- package/packages/video-cli/README.md +33 -6
- package/packages/video-cli/src/cli.ts +4 -1
- package/packages/video-cli/src/creation.ts +7 -2
- package/packages/video-cli/src/index.ts +2 -0
- package/packages/video-cli/src/version.ts +90 -0
- package/services/whisperx/README.md +10 -3
- package/services/whisperx/src/hypit_whisperx_service/engine.py +19 -1
package/packages/cli/src/main.ts
CHANGED
|
@@ -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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
|
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
|
|
513
|
+
`Inspect ${commandHint(["inspect", built.id], resultScope)}`,
|
|
513
514
|
]
|
|
514
515
|
: [
|
|
515
516
|
...(completionReason === undefined ? [] : [`Reason ${completionReason}`]),
|
|
516
|
-
`Inspect
|
|
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
|
|
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
|
-
? "
|
|
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
|
-
`
|
|
558
|
-
`Finish
|
|
558
|
+
`Attention ${issue.scope}: ${issue.message}`,
|
|
559
|
+
`Finish ${commandHint(["result", "finish", built.id], commandScope)}`,
|
|
559
560
|
] : [
|
|
560
|
-
`Watch
|
|
561
|
-
`Cancel
|
|
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}
|
|
162
|
-
+ `
|
|
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];
|
package/packages/cli/src/view.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
...(
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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 `
|
|
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
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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"
|
|
42
|
-
seedance("seedance-2-fast"
|
|
43
|
-
seedance("seedance-2-mini"
|
|
44
|
-
seedance("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;
|