@vgai/sdk 0.5.48 → 0.5.50
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/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@vgai/sdk",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.50",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -21,17 +21,19 @@
|
|
|
21
21
|
"./build-discipline": "./src/project/build-discipline.ts",
|
|
22
22
|
"./generations": "./src/generations.ts",
|
|
23
23
|
"./mcp-stdio": "./src/mcp/mcp-stdio-server.ts",
|
|
24
|
+
"./output-roots": "./src/project/output-roots.ts",
|
|
24
25
|
"./project-tool-catalog": "./src/project-tool-catalog.ts",
|
|
25
26
|
"./project-inspection-node": "./src/project/inspection-node.ts",
|
|
26
27
|
"./registry": "./src/registry.ts",
|
|
27
28
|
"./run-name": "./src/project/run-name.ts",
|
|
28
29
|
"./session-journal": "./src/project/session-journal.ts",
|
|
29
30
|
"./tab-census": "./src/project/tab-census.ts",
|
|
30
|
-
"./tools": "./src/tools.ts"
|
|
31
|
+
"./tools": "./src/tools.ts",
|
|
32
|
+
"./provider-execution": "./src/provider-execution.ts"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
35
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
34
|
-
"@vgai/engine": "0.5.
|
|
36
|
+
"@vgai/engine": "0.5.50",
|
|
35
37
|
"playwright": "^1.58.2",
|
|
36
38
|
"zod": "^4.3.6"
|
|
37
39
|
}
|
package/src/account.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// Account identity and billing preference are host configuration, never generation-call inputs.
|
|
4
4
|
export const AccountBackendSchema = z.enum(['mock', 'live']);
|
|
5
5
|
export const AccountPlanSchema = z.object({
|
|
6
6
|
id: z.string().min(1),
|
|
@@ -123,7 +123,7 @@ export const AccountSnapshotSchema = z.discriminatedUnion('authenticated', [
|
|
|
123
123
|
]);
|
|
124
124
|
/** Product-facing routes. Provider tools may translate BYOK to their native
|
|
125
125
|
* transport name (`direct`) internally. */
|
|
126
|
-
export const GenerationExecutionRouteSchema = z.enum(['mock', 'managed', 'byok']);
|
|
126
|
+
export const GenerationExecutionRouteSchema = z.enum(['auto', 'mock', 'managed', 'byok']);
|
|
127
127
|
export const ProviderCredentialIdSchema = z.enum(['fal', 'tripo', 'worldlabs', 'openrouter']);
|
|
128
128
|
export const CodingInferenceSettingsSchema = z.object({
|
|
129
129
|
enabled: z.boolean(),
|
package/src/perf/perf-run.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { join as joinPath } from 'node:path';
|
|
27
|
-
import {
|
|
27
|
+
import type { Browser } from 'playwright';
|
|
28
28
|
import {
|
|
29
29
|
DEFAULT_ENGINE_ROOT,
|
|
30
30
|
findFreeRenderPort,
|
|
@@ -138,7 +138,7 @@ export async function runPerfCapture(request: PerfRunRequest): Promise<PerfRunRe
|
|
|
138
138
|
const server = await launchEntryServer(entry, port, engineRoot, undefined);
|
|
139
139
|
let browser: Browser | undefined;
|
|
140
140
|
try {
|
|
141
|
-
browser = await chromium.launch({
|
|
141
|
+
browser = await (await import('playwright')).chromium.launch({
|
|
142
142
|
headless: true,
|
|
143
143
|
args: ['--use-gl=angle', '--use-angle=swiftshader'],
|
|
144
144
|
// Cancellation/exit is owned by the caller, never Playwright's own
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project directories a tool may commit generated bytes into, and the one
|
|
3
|
+
* normalizer every writer validates a path with.
|
|
4
|
+
*
|
|
5
|
+
* A project has two of them, and the difference is what SHIPS:
|
|
6
|
+
*
|
|
7
|
+
* - `public/` — game assets. Exported, served at a root-relative URL,
|
|
8
|
+
* loaded by the game's own source.
|
|
9
|
+
* - `references/` — reference material: moodboards, style plates, generated
|
|
10
|
+
* stills and clips an author or an agent looks at while
|
|
11
|
+
* building. Ordinary files, indexed by the editor's Content
|
|
12
|
+
* panel like any other media, and NOT exported — the
|
|
13
|
+
* project's own build copies `publicDir` and nothing else.
|
|
14
|
+
*
|
|
15
|
+
* This lives in the SDK rather than beside one writer because the rule has more
|
|
16
|
+
* than one enforcer: the editor host's atomic project-output writer, and every
|
|
17
|
+
* registered provider boundary that takes an `outputDirectory` from a caller.
|
|
18
|
+
* Two copies of "must be under public/" is how a reference output becomes
|
|
19
|
+
* writable through one door and refused at the next.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The writable output roots, in the order a chooser should offer them. */
|
|
23
|
+
export const PROJECT_OUTPUT_ROOTS = ['public', 'references'] as const;
|
|
24
|
+
|
|
25
|
+
export type ProjectOutputRoot = (typeof PROJECT_OUTPUT_ROOTS)[number];
|
|
26
|
+
|
|
27
|
+
/** Human-readable list for an error message: `public/` or `references/`. */
|
|
28
|
+
function rootList(): string {
|
|
29
|
+
return PROJECT_OUTPUT_ROOTS.map((root) => `${root}/`).join(' or ');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** POSIX-normalized, `./`-stripped, trailing-slash-stripped. */
|
|
33
|
+
function normalizeSlashes(path: string): string {
|
|
34
|
+
const slashed = path.replaceAll('\\', '/');
|
|
35
|
+
const segments: string[] = [];
|
|
36
|
+
for (const segment of slashed.split('/')) {
|
|
37
|
+
if (segment === '' || segment === '.') continue;
|
|
38
|
+
segments.push(segment);
|
|
39
|
+
}
|
|
40
|
+
return segments.join('/');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The root `path` sits under, or `undefined` when it sits under none. */
|
|
44
|
+
export function projectOutputRootOf(path: string): ProjectOutputRoot | undefined {
|
|
45
|
+
const normalized = normalizeSlashes(path);
|
|
46
|
+
return PROJECT_OUTPUT_ROOTS.find(
|
|
47
|
+
(root) => normalized === root || normalized.startsWith(`${root}/`),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate and normalize a project-relative output path.
|
|
53
|
+
*
|
|
54
|
+
* Throws — with a message naming BOTH roots, because the common mistake is
|
|
55
|
+
* knowing about one of them — for an absolute path, an escape, a bare root
|
|
56
|
+
* with no file under it, or anything outside the roots above. `subject` names
|
|
57
|
+
* what is being validated in that message (`Generated output path`, `Fal
|
|
58
|
+
* outputDirectory`, …).
|
|
59
|
+
*/
|
|
60
|
+
export function normalizeProjectOutputPath(
|
|
61
|
+
path: string,
|
|
62
|
+
subject = 'Generated output path',
|
|
63
|
+
): string {
|
|
64
|
+
const normalized = normalizeSlashes(path);
|
|
65
|
+
const escapes = normalized.split('/').includes('..');
|
|
66
|
+
const root = projectOutputRootOf(normalized);
|
|
67
|
+
if (path.startsWith('/') || escapes || root === undefined || normalized === root) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`${subject} ${JSON.stringify(path)} is invalid; it must be a project-relative path under ${rootList()}.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
@@ -237,6 +237,12 @@ export type SessionJournalEvent =
|
|
|
237
237
|
}
|
|
238
238
|
/** The tab said its command listener PICKED THE COMMAND UP. */
|
|
239
239
|
| { readonly kind: 'command-receipt'; readonly requestId8: string }
|
|
240
|
+
/**
|
|
241
|
+
* The last tab's connection went away while UNRECEIPTED commands were still
|
|
242
|
+
* queued, and this many were swept. Receipted ones are deliberately not
|
|
243
|
+
* counted here — they keep their own work budget.
|
|
244
|
+
*/
|
|
245
|
+
| { readonly kind: 'command-swept-on-last-tab-gone'; readonly settled: number }
|
|
240
246
|
/** The command settled — by the tab's own answer or by the relay refusing. */
|
|
241
247
|
| {
|
|
242
248
|
readonly kind: 'command-result';
|
|
@@ -640,6 +646,8 @@ export function formatJournalLine(line: SessionJournalLine): string {
|
|
|
640
646
|
return `journal: ${at} command-relayed ${line.command} ${line.requestId8} -> tab ${line.tabId8 ?? 'none'}`;
|
|
641
647
|
case 'command-receipt':
|
|
642
648
|
return `journal: ${at} command-receipt ${line.requestId8}`;
|
|
649
|
+
case 'command-swept-on-last-tab-gone':
|
|
650
|
+
return `journal: ${at} command-swept-on-last-tab-gone ${line.settled} unreceipted`;
|
|
643
651
|
case 'command-result':
|
|
644
652
|
return `journal: ${at} command-result ${line.requestId8} ${line.ok ? 'ok' : `FAILED ${line.error ?? ''}`}`;
|
|
645
653
|
case 'command-held':
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import type { GenerationJobDraft } from './generations.js';
|
|
3
|
+
|
|
4
|
+
export type ProviderExecutionMode = 'direct' | 'managed' | 'mock';
|
|
5
|
+
export type ProviderModeResolver = (
|
|
6
|
+
provider: string,
|
|
7
|
+
requestId?: string,
|
|
8
|
+
) => Promise<ProviderExecutionMode>;
|
|
9
|
+
|
|
10
|
+
const credentialNames: Record<string, string> = {
|
|
11
|
+
fal: 'FAL_KEY',
|
|
12
|
+
tripo: 'TRIPO_API_KEY',
|
|
13
|
+
worldlabs: 'WORLDLABS_API_KEY',
|
|
14
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Project SSR and installed packages can load separate module instances.
|
|
18
|
+
// The host's async context must still be shared, without sharing requests.
|
|
19
|
+
const key = Symbol.for('vgai.provider-execution-context');
|
|
20
|
+
const globals = globalThis as typeof globalThis & {
|
|
21
|
+
[key]?: AsyncLocalStorage<{
|
|
22
|
+
resolve: ProviderModeResolver;
|
|
23
|
+
record?: ((job: GenerationJobDraft) => Promise<void>) | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
const context = globals[key] ?? new AsyncLocalStorage();
|
|
27
|
+
globals[key] = context;
|
|
28
|
+
|
|
29
|
+
/** Trusted host configuration. Provider choice and billing never enter tool input. */
|
|
30
|
+
export function withProviderExecution<T>(
|
|
31
|
+
resolve: ProviderModeResolver,
|
|
32
|
+
work: () => Promise<T>,
|
|
33
|
+
record?: (job: GenerationJobDraft) => Promise<void>,
|
|
34
|
+
) {
|
|
35
|
+
const pending = new Map<string, Promise<ProviderExecutionMode>>();
|
|
36
|
+
return context.run(
|
|
37
|
+
{
|
|
38
|
+
record,
|
|
39
|
+
resolve: (provider, requestId) => {
|
|
40
|
+
const key = JSON.stringify([provider, requestId]);
|
|
41
|
+
let result = pending.get(key);
|
|
42
|
+
if (!result) {
|
|
43
|
+
result = resolve(provider, requestId);
|
|
44
|
+
pending.set(key, result);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
work,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Native provider clients use the same project ledger as registered tools. */
|
|
54
|
+
export async function recordProviderGeneration(job: GenerationJobDraft): Promise<void> {
|
|
55
|
+
await context.getStore()?.record?.(job);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Library-side connection resolution; callers supply only native provider input. */
|
|
59
|
+
export async function resolveProviderMode(
|
|
60
|
+
provider: string,
|
|
61
|
+
requestId?: string,
|
|
62
|
+
): Promise<ProviderExecutionMode> {
|
|
63
|
+
const resolve = context.getStore()?.resolve;
|
|
64
|
+
if (resolve) return resolve(provider, requestId);
|
|
65
|
+
const name = credentialNames[provider];
|
|
66
|
+
if (!name) throw new Error(`Unknown generation provider ${JSON.stringify(provider)}.`);
|
|
67
|
+
if (process.env[name]?.trim()) return 'direct';
|
|
68
|
+
if (process.env['VGAI_GENERATION_GATEWAY'] && process.env['VGAI_ACCESS_TOKEN']) return 'managed';
|
|
69
|
+
throw new Error(`Connect ${provider} or sign in to VGAI in Account before generating.`);
|
|
70
|
+
}
|
|
@@ -34,7 +34,6 @@ import { tmpdir } from 'node:os';
|
|
|
34
34
|
import { dirname, join, resolve as resolvePath } from 'node:path';
|
|
35
35
|
import { fileURLToPath } from 'node:url';
|
|
36
36
|
import type { Browser, Page } from 'playwright';
|
|
37
|
-
import { chromium } from 'playwright';
|
|
38
37
|
import { checkFfmpegCapability, type FfmpegCapabilityError } from './capabilities/ffmpeg';
|
|
39
38
|
|
|
40
39
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1539,7 +1538,7 @@ export async function openRenderCinematicPage(
|
|
|
1539
1538
|
);
|
|
1540
1539
|
let browser: Browser | undefined;
|
|
1541
1540
|
try {
|
|
1542
|
-
browser = await chromium.launch({
|
|
1541
|
+
browser = await (await import('playwright')).chromium.launch({
|
|
1543
1542
|
headless: true,
|
|
1544
1543
|
args: ['--use-gl=angle', '--use-angle=swiftshader'],
|
|
1545
1544
|
// B7 review fold-in (SIGINT race): Playwright's DEFAULT is to install
|
|
@@ -1836,7 +1835,7 @@ export async function renderCinematic(
|
|
|
1836
1835
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
1837
1836
|
try {
|
|
1838
1837
|
onProgress?.({ phase: 'browser-launch' });
|
|
1839
|
-
browser = await chromium.launch({
|
|
1838
|
+
browser = await (await import('playwright')).chromium.launch({
|
|
1840
1839
|
headless: true,
|
|
1841
1840
|
args: ['--use-gl=angle', '--use-angle=swiftshader'],
|
|
1842
1841
|
// B7 review fold-in (SIGINT race, #10): without this, Playwright
|