@vgai/sdk 0.4.1 → 0.5.0-canary.20260719.0

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.4.1",
5
+ "version": "0.5.0-canary.20260719.0",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
@@ -17,11 +17,17 @@
17
17
  ],
18
18
  "exports": {
19
19
  ".": "./src/index.ts",
20
+ "./account": "./src/account.ts",
21
+ "./generations": "./src/generations.ts",
22
+ "./mcp-stdio": "./src/mcp/mcp-stdio-server.ts",
23
+ "./project-tool-catalog": "./src/project-tool-catalog.ts",
24
+ "./project-inspection-node": "./src/project/inspection-node.ts",
20
25
  "./registry": "./src/registry.ts",
21
26
  "./tools": "./src/tools.ts"
22
27
  },
23
28
  "dependencies": {
24
- "@vgai/engine": "0.4.1",
29
+ "@modelcontextprotocol/sdk": "^1.29.0",
30
+ "@vgai/engine": "0.5.0-canary.20260719.0",
25
31
  "playwright": "^1.58.2",
26
32
  "zod": "^4.3.6"
27
33
  }
package/src/account.ts ADDED
@@ -0,0 +1,133 @@
1
+ import { z } from 'zod';
2
+
3
+ export const AccountModeSchema = z.enum(['mock', 'live']);
4
+ export const AccountPlanSchema = z.object({
5
+ id: z.string().min(1),
6
+ name: z.string().min(1),
7
+ status: z.enum(['free', 'trialing', 'active', 'past_due', 'cancelling']),
8
+ interval: z.enum(['month', 'year']).optional(),
9
+ renewsAt: z.string().datetime().optional(),
10
+ endsAt: z.string().datetime().optional(),
11
+ });
12
+ export const AccountCreditsSchema = z.object({
13
+ included: z.number().nonnegative(),
14
+ purchased: z.number().nonnegative(),
15
+ used: z.number().nonnegative(),
16
+ reserved: z.number().nonnegative(),
17
+ remaining: z.number().nonnegative(),
18
+ resetsAt: z.string().datetime().optional(),
19
+ purchasedExpiresAt: z.string().datetime().optional(),
20
+ });
21
+ export const AccountSpendPolicySchema = z.object({
22
+ overageEnabled: z.boolean(),
23
+ monthlyLimitCredits: z.number().nonnegative(),
24
+ alertThresholds: z.array(z.number().min(1).max(100)).max(8),
25
+ autoReload: z
26
+ .object({
27
+ enabled: z.boolean(),
28
+ whenRemainingBelow: z.number().nonnegative(),
29
+ reloadTo: z.number().positive(),
30
+ monthlyLimitCredits: z.number().nonnegative(),
31
+ })
32
+ .optional(),
33
+ });
34
+ export const AccountUserSchema = z.object({
35
+ id: z.string().min(1),
36
+ email: z.string().email(),
37
+ name: z.string().min(1).optional(),
38
+ });
39
+ export const AccountUsageEntrySchema = z.object({
40
+ id: z.string().min(1),
41
+ occurredAt: z.string().datetime(),
42
+ kind: z.literal('generation'),
43
+ provider: z.string().min(1),
44
+ operation: z.string().optional(),
45
+ credits: z.number().nonnegative(),
46
+ state: z.enum(['reserved', 'settled', 'released']),
47
+ externalId: z.string().optional(),
48
+ });
49
+ export const AccountSnapshotSchema = z.discriminatedUnion('authenticated', [
50
+ z.object({ authenticated: z.literal(false), mode: AccountModeSchema }),
51
+ z.object({
52
+ authenticated: z.literal(true),
53
+ mode: AccountModeSchema,
54
+ user: AccountUserSchema,
55
+ plan: AccountPlanSchema,
56
+ credits: AccountCreditsSchema,
57
+ spendPolicy: AccountSpendPolicySchema,
58
+ entitlements: z.object({
59
+ generation: z.boolean(),
60
+ dailyJobs: z.number().int().positive().optional(),
61
+ }),
62
+ }),
63
+ ]);
64
+ /** Product-facing routes. Provider tools may translate BYOK to their native
65
+ * transport name (`direct`) internally. */
66
+ export const GenerationExecutionRouteSchema = z.enum(['mock', 'managed', 'byok']);
67
+ export const ProviderCredentialIdSchema = z.enum(['fal', 'tripo', 'worldlabs']);
68
+ export const ProviderCredentialSourceSchema = z.enum(['environment', 'system', 'session', 'none']);
69
+ export const ProviderCredentialStatusSchema = z.object({
70
+ provider: ProviderCredentialIdSchema,
71
+ label: z.string().min(1),
72
+ environmentVariable: z.string().min(1),
73
+ configured: z.boolean(),
74
+ source: ProviderCredentialSourceSchema,
75
+ editable: z.boolean(),
76
+ persistence: z.enum(['system', 'session']),
77
+ maskedKey: z.string().min(1).optional(),
78
+ problem: z.string().min(1).optional(),
79
+ });
80
+ export const ProviderCredentialTestResultSchema = z.object({
81
+ provider: ProviderCredentialIdSchema,
82
+ ok: z.literal(true),
83
+ testedAt: z.string().datetime(),
84
+ });
85
+
86
+ export type AccountMode = z.infer<typeof AccountModeSchema>;
87
+ export type AccountPlan = z.infer<typeof AccountPlanSchema>;
88
+ export type AccountCredits = z.infer<typeof AccountCreditsSchema>;
89
+ export type AccountSpendPolicy = z.infer<typeof AccountSpendPolicySchema>;
90
+ export type AccountUser = z.infer<typeof AccountUserSchema>;
91
+ export type AccountUsageEntry = z.infer<typeof AccountUsageEntrySchema>;
92
+ export type AccountSnapshot = z.infer<typeof AccountSnapshotSchema>;
93
+ export type GenerationExecutionRoute = z.infer<typeof GenerationExecutionRouteSchema>;
94
+ export type ProviderCredentialId = z.infer<typeof ProviderCredentialIdSchema>;
95
+ export type ProviderCredentialSource = z.infer<typeof ProviderCredentialSourceSchema>;
96
+ export type ProviderCredentialStatus = z.infer<typeof ProviderCredentialStatusSchema>;
97
+ export type ProviderCredentialTestResult = z.infer<typeof ProviderCredentialTestResultSchema>;
98
+ export type EditorAccountSnapshot = AccountSnapshot & {
99
+ routes: { mock: true; managed: boolean; byok: boolean; byokProviders: string[] };
100
+ preferredRoute: GenerationExecutionRoute;
101
+ /** Editor-only metadata. Credential values never cross the server boundary. */
102
+ providerCredentials: ProviderCredentialStatus[];
103
+ };
104
+ export interface GenerationAccountProjection {
105
+ routes: EditorAccountSnapshot['routes'];
106
+ preferredRoute: GenerationExecutionRoute;
107
+ }
108
+ export function generationAccountProjection(
109
+ account: EditorAccountSnapshot,
110
+ ): GenerationAccountProjection {
111
+ return {
112
+ routes: {
113
+ mock: true,
114
+ managed: account.routes.managed,
115
+ byok: account.routes.byok,
116
+ byokProviders: [...account.routes.byokProviders],
117
+ },
118
+ preferredRoute: account.preferredRoute,
119
+ };
120
+ }
121
+ export const EditorAccountSnapshotSchema: z.ZodType<EditorAccountSnapshot> = z.intersection(
122
+ AccountSnapshotSchema,
123
+ z.object({
124
+ routes: z.object({
125
+ mock: z.literal(true),
126
+ managed: z.boolean(),
127
+ byok: z.boolean(),
128
+ byokProviders: z.array(z.string().min(1)),
129
+ }),
130
+ preferredRoute: GenerationExecutionRouteSchema,
131
+ providerCredentials: z.array(ProviderCredentialStatusSchema),
132
+ }),
133
+ );
@@ -105,7 +105,7 @@
105
105
  * deliberately never exercised in the first place.)
106
106
  */
107
107
 
108
- import { existsSync, readFileSync } from 'node:fs';
108
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
109
109
  import { homedir } from 'node:os';
110
110
  import { join, resolve } from 'node:path';
111
111
  import { z } from 'zod';
@@ -172,6 +172,8 @@ export interface EditorSessionInfo {
172
172
  project: string | null;
173
173
  /** Dev-server process id, when known from the local session registry (null for an unregistered/legacy server the caller only probed by port). */
174
174
  pid: number | null;
175
+ /** Exact explicitly targeted editor origin/base URL, including protocol and host. */
176
+ url?: string;
175
177
  }
176
178
 
177
179
  export interface EditorCommandResult {
@@ -241,6 +243,8 @@ export interface ConsoleSubscriptionMetadata {
241
243
  * `getTransport` below.
242
244
  */
243
245
  export interface EditorTransport {
246
+ /** Probe an explicit --url/VGAI_EDITOR_URL without consulting the local session registry. */
247
+ probeSessionUrl?(editorUrl: string, timeoutMs: number): Promise<EditorSessionInfo | undefined>;
244
248
  listSessions(timeoutMs: number): Promise<EditorSessionInfo[]>;
245
249
  getSelection(
246
250
  session: EditorSessionInfo,
@@ -361,11 +365,30 @@ async function postJson(
361
365
  }
362
366
 
363
367
  function baseUrl(session: EditorSessionInfo): string {
364
- return `http://localhost:${session.port}`;
368
+ return session.url ?? `http://localhost:${session.port}`;
365
369
  }
366
370
 
367
371
  /** The real, production transport — HTTP against a live `vgai edit` dev server. */
368
372
  export class HttpEditorTransport implements EditorTransport {
373
+ async probeSessionUrl(
374
+ editorUrl: string,
375
+ timeoutMs: number,
376
+ ): Promise<EditorSessionInfo | undefined> {
377
+ let url: URL;
378
+ try {
379
+ url = new URL(editorUrl);
380
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return undefined;
381
+ } catch {
382
+ return undefined;
383
+ }
384
+ const base = editorUrl.replace(/\/+$/, '');
385
+ const body = await fetchJson(`${base}/__editor/project`, timeoutMs);
386
+ if (body === undefined) return undefined;
387
+ const project = (body as { project?: { path?: string } | null }).project?.path ?? null;
388
+ const port = url.port ? Number(url.port) : url.protocol === 'https:' ? 443 : 80;
389
+ return { port, project, pid: null, url: base };
390
+ }
391
+
369
392
  async listSessions(timeoutMs: number): Promise<EditorSessionInfo[]> {
370
393
  const registered = readRegisteredSessions();
371
394
  const perProbeTimeout = Math.min(EDITOR_PROBE_TIMEOUT_MS, Math.max(200, timeoutMs));
@@ -572,7 +595,12 @@ export const EDITOR_NOT_RUNNING_ERROR: ErrorDefinition = {
572
595
  };
573
596
 
574
597
  function canonicalize(p: string): string {
575
- return resolve(p);
598
+ const absolute = resolve(p);
599
+ try {
600
+ return realpathSync(absolute);
601
+ } catch {
602
+ return absolute;
603
+ }
576
604
  }
577
605
 
578
606
  /** Extract the port from an `http(s)://host:port` URL, or undefined if unparseable. */
@@ -589,15 +617,16 @@ function portOf(url: string): number | undefined {
589
617
  * Deterministic session selection (§8 B3 AC: "Session selection is
590
618
  * deterministic when multiple editors are running"). Precedence, in order:
591
619
  *
592
- * 1. `ctx.editorUrl` given -> the live session on THAT exact port, or
593
- * EDITOR_NOT_RUNNING if no live session answers there. (Explicit
594
- * targeting always wins — mirrors `vgai-cli`'s own `--url` override.)
620
+ * 1. `ctx.editorUrl` given -> probe THAT exact URL, preserving its protocol
621
+ * and host, or EDITOR_NOT_RUNNING if no live session answers there.
622
+ * (Explicit targeting always wins — mirrors `vgai-cli`'s own `--url`
623
+ * override and supports non-local/HTTPS editor endpoints.)
595
624
  * 2. `ctx.projectRoot` given (no editorUrl) -> the live session whose
596
625
  * canonical project path matches `ctx.projectRoot`'s canonical path.
597
626
  * (Mirrors `packages/vgai-cli/src/index.ts`'s `getClient()` cwd rule —
598
627
  * "run `vgai play` from inside a project and it targets THAT project's
599
628
  * editor, whichever port it landed on".)
600
- * 3. Neither given, or neither matched -> the LOWEST-numbered port among
629
+ * 3. Neither hint given -> the LOWEST-numbered port among
601
630
  * all live sessions (a total order over the only session field that is
602
631
  * always present and stable, so the rule never depends on registration
603
632
  * order or wall-clock time).
@@ -618,6 +647,18 @@ export async function resolveEditorSession(
618
647
  });
619
648
  };
620
649
 
650
+ if (editorUrl !== undefined && transport.probeSessionUrl) {
651
+ try {
652
+ const session = await withTimeout(
653
+ transport.probeSessionUrl(editorUrl, EDITOR_PROBE_TIMEOUT_MS),
654
+ EDITOR_PROBE_TIMEOUT_MS,
655
+ 'explicit editor URL probe',
656
+ );
657
+ if (session) return session;
658
+ } catch {}
659
+ return notRunning();
660
+ }
661
+
621
662
  let sessions: EditorSessionInfo[];
622
663
  try {
623
664
  sessions = await withTimeout(
@@ -634,13 +675,14 @@ export async function resolveEditorSession(
634
675
  const port = portOf(editorUrl);
635
676
  const match = sessions.find((s) => s.port === port);
636
677
  if (!match) return notRunning();
637
- return match;
678
+ return { ...match, url: editorUrl.replace(/\/+$/, '') };
638
679
  }
639
680
 
640
681
  if (ctx.projectRoot !== undefined) {
641
682
  const canon = canonicalize(ctx.projectRoot);
642
683
  const match = sessions.find((s) => s.project !== null && canonicalize(s.project) === canon);
643
- if (match) return match;
684
+ if (!match) return notRunning();
685
+ return match;
644
686
  }
645
687
 
646
688
  return [...sessions].sort((a, b) => a.port - b.port)[0]!;
@@ -0,0 +1,112 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * First-party generation JOB vocabulary.
5
+ *
6
+ * Providers keep their native request and result schemas. This deliberately
7
+ * normalizes only the lifecycle that is genuinely shared by Fal, Tripo,
8
+ * World Labs, and future generators: durable identity, state, resumption,
9
+ * acceptance, and the charge shown to the user.
10
+ */
11
+
12
+ export const GenerationJobStatusSchema = z.enum([
13
+ 'queued',
14
+ 'running',
15
+ 'succeeded',
16
+ 'failed',
17
+ 'cancelled',
18
+ ]);
19
+
20
+ export const GenerationBillingSchema = z.discriminatedUnion('route', [
21
+ z.object({ route: z.literal('mock') }),
22
+ z.object({
23
+ route: z.literal('managed'),
24
+ estimatedCredits: z.number().nonnegative().optional(),
25
+ settledCredits: z.number().nonnegative().optional(),
26
+ }),
27
+ z.object({
28
+ route: z.literal('byok'),
29
+ currency: z.literal('USD'),
30
+ estimatedAmount: z.number().nonnegative().optional(),
31
+ settledAmount: z.number().nonnegative().optional(),
32
+ }),
33
+ ]);
34
+
35
+ const GenerationOperationReferenceSchema = z.object({
36
+ tool: z.string().min(1),
37
+ input: z.unknown(),
38
+ });
39
+
40
+ export const GenerationJobSchema = z.object({
41
+ id: z.string().min(1),
42
+ provider: z.string().min(1),
43
+ externalId: z.string().min(1),
44
+ label: z.string().min(1),
45
+ operation: z.string().min(1),
46
+ mode: z.enum(['mock', 'direct', 'managed']),
47
+ status: GenerationJobStatusSchema,
48
+ createdAt: z.string().datetime(),
49
+ updatedAt: z.string().datetime(),
50
+ progress: z.number().min(0).max(1).optional(),
51
+ message: z.string().optional(),
52
+ pollError: z.string().optional(),
53
+ pollErrorAt: z.string().datetime().optional(),
54
+ pollFailureCount: z.number().int().nonnegative().optional(),
55
+ nextPollAt: z.string().datetime().optional(),
56
+ billing: GenerationBillingSchema,
57
+ poll: GenerationOperationReferenceSchema,
58
+ accept: GenerationOperationReferenceSchema.optional(),
59
+ acceptedAt: z.string().datetime().optional(),
60
+ provenanceOperationId: z.string().optional(),
61
+ outputPaths: z.array(z.string()).optional(),
62
+ });
63
+
64
+ export const GenerationJobsDocumentSchema = z.object({
65
+ version: z.literal(1),
66
+ jobs: z.array(GenerationJobSchema),
67
+ });
68
+
69
+ export type GenerationJobStatus = z.infer<typeof GenerationJobStatusSchema>;
70
+ export type GenerationBilling = z.infer<typeof GenerationBillingSchema>;
71
+ export type GenerationJob = z.infer<typeof GenerationJobSchema>;
72
+ export type GenerationJobsDocument = z.infer<typeof GenerationJobsDocumentSchema>;
73
+
74
+ export interface GenerationJobDraft {
75
+ provider: string;
76
+ externalId: string;
77
+ label: string;
78
+ operation: string;
79
+ mode: 'mock' | 'direct' | 'managed';
80
+ status: GenerationJobStatus;
81
+ progress?: number;
82
+ message?: string;
83
+ billing: GenerationBilling;
84
+ poll: { tool: string; input: unknown };
85
+ accept?: { tool: string; input: unknown };
86
+ }
87
+
88
+ export interface GenerationJobUpdate {
89
+ provider: string;
90
+ externalId: string;
91
+ status?: GenerationJobStatus;
92
+ progress?: number;
93
+ message?: string;
94
+ billing?: GenerationBilling;
95
+ accepted?: {
96
+ provenanceOperationId: string;
97
+ outputPaths: string[];
98
+ };
99
+ }
100
+
101
+ /** Provider-owned mapping attached to a registered native operation module. */
102
+ export type GenerationToolContribution =
103
+ | {
104
+ role: 'submit';
105
+ provider: string;
106
+ toJob(input: unknown, result: unknown): GenerationJobDraft;
107
+ }
108
+ | {
109
+ role: 'poll' | 'accept';
110
+ provider: string;
111
+ toUpdate(input: unknown, result: unknown): GenerationJobUpdate;
112
+ };
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './account.js';
1
2
  export * from './cinematic/index.js';
2
3
  export * from './editor/index.js';
3
4
  export {
@@ -16,8 +17,10 @@ export {
16
17
  projectStatus,
17
18
  registerBuiltinOperations,
18
19
  } from './operations.js';
20
+ export * from './perf/index.js';
19
21
  export * from './play/index.js';
20
22
  export * from './project/index.js';
23
+ export * from './project-tool-catalog.js';
21
24
  export {
22
25
  defineOperation,
23
26
  type ErrorDefinition,
package/src/mcp/index.ts CHANGED
@@ -4,7 +4,9 @@ export {
4
4
  isResourceEligible,
5
5
  type JsonRpcRequest,
6
6
  type JsonRpcResponse,
7
+ MAX_INLINE_MCP_SCHEMA_BYTES,
7
8
  MCP_RESOURCE_PREFIX,
9
+ MCP_SCHEMA_RESOURCE_PREFIX,
8
10
  type McpProjection,
9
11
  type McpProjectionOptions,
10
12
  type McpResource,
@@ -14,3 +16,15 @@ export {
14
16
  outcomeToToolResult,
15
17
  toolInputSchema,
16
18
  } from './mcp-projection.js';
19
+ export {
20
+ type CreateMcpServerOptions,
21
+ createMcpServer,
22
+ DEFAULT_MCP_INSTRUCTIONS,
23
+ DEFAULT_MCP_SERVER_INFO,
24
+ type VgaiMcpServer,
25
+ } from './mcp-server.js';
26
+ export {
27
+ type ConnectedMcpStdioServer,
28
+ type ConnectMcpStdioOptions,
29
+ connectMcpStdioServer,
30
+ } from './mcp-stdio.js';