@volter/editor-sdk 0.5.57
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +19 -0
- package/package.json +90 -0
- package/src/account.ts +210 -0
- package/src/chrome.ts +83 -0
- package/src/client.ts +1547 -0
- package/src/commands.ts +66 -0
- package/src/contributions.ts +985 -0
- package/src/document-probe.ts +237 -0
- package/src/editor-view.ts +220 -0
- package/src/extension.ts +40 -0
- package/src/generations.ts +178 -0
- package/src/host.ts +1167 -0
- package/src/http-transport.browser.ts +14 -0
- package/src/http-transport.node.ts +19 -0
- package/src/index.ts +128 -0
- package/src/layout-arrangements.ts +5 -0
- package/src/layouts.tsx +108 -0
- package/src/looks.ts +14 -0
- package/src/project/output-roots.ts +73 -0
- package/src/project/tab-census.ts +149 -0
- package/src/project-tool-catalog.ts +96 -0
- package/src/selection.tsx +108 -0
- package/src/services.ts +18 -0
- package/src/session/build-report.ts +19 -0
- package/src/session/collaboration-types.ts +262 -0
- package/src/session/command-table.ts +333 -0
- package/src/session/discovery.ts +90 -0
- package/src/session/editor-brand.ts +73 -0
- package/src/session/editor-compatibility.ts +248 -0
- package/src/session/editor-control-lifecycle.ts +68 -0
- package/src/session/editor-control-protocol.ts +5 -0
- package/src/session/entrypoint-selection-readers.ts +66 -0
- package/src/session/entrypoint-selection-source.ts +120 -0
- package/src/session/game-css-scope.ts +30 -0
- package/src/session/product-create.ts +24 -0
- package/src/session/product-locator.ts +389 -0
- package/src/session/project-module-url.ts +245 -0
- package/src/session/registry-format.ts +203 -0
- package/src/session/relative-path-guard.ts +56 -0
- package/src/session/source-glob.ts +15 -0
- package/src/session/tool-contribution-convention.ts +116 -0
- package/src/session/workbench-locator.ts +650 -0
- package/src/session.ts +41 -0
- package/src/share.ts +160 -0
- package/src/tools/errors.ts +91 -0
- package/src/tools/provider-execution.ts +70 -0
- package/src/tools/registry.ts +341 -0
- package/src/tools/types.ts +159 -0
- package/src/transport.ts +97 -0
- package/src/types.ts +1581 -0
- package/src/views.ts +164 -0
- package/src/widgets/design-system.ts +93 -0
- package/src/widgets/editor-appearance.ts +149 -0
- package/src/widgets/editor-material.ts +83 -0
- package/src/widgets/icon-set-registry.ts +105 -0
- package/src/widgets/index.ts +71 -0
- package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
- package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
- package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
- package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
- package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
- package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
- package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
- package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
- package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
- package/src/widgets/inspector-widgets/color-utils.ts +201 -0
- package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
- package/src/widgets/inspector-widgets/index.ts +24 -0
- package/src/widgets/inspector-widgets/shared.tsx +140 -0
- package/src/widgets/interactive-edit-scope.ts +33 -0
- package/src/widgets/patterns/Dialog.tsx +129 -0
- package/src/widgets/patterns/Fields.tsx +44 -0
- package/src/widgets/patterns/List.tsx +25 -0
- package/src/widgets/patterns/StateSurface.tsx +40 -0
- package/src/widgets/patterns/Surfaces.tsx +122 -0
- package/src/widgets/patterns/Tabs.tsx +80 -0
- package/src/widgets/patterns/Toolbar.tsx +72 -0
- package/src/widgets/patterns/Tree.tsx +72 -0
- package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
- package/src/widgets/primitives/Button.tsx +62 -0
- package/src/widgets/primitives/ColorInput.tsx +78 -0
- package/src/widgets/primitives/DraftTextInput.tsx +63 -0
- package/src/widgets/primitives/EditorIcon.tsx +157 -0
- package/src/widgets/primitives/FormControls.tsx +88 -0
- package/src/widgets/primitives/HoverPreview.tsx +96 -0
- package/src/widgets/primitives/JsonInput.tsx +113 -0
- package/src/widgets/primitives/Layout.tsx +100 -0
- package/src/widgets/primitives/Menu.tsx +140 -0
- package/src/widgets/primitives/NumberInput.tsx +169 -0
- package/src/widgets/primitives/Panel.tsx +80 -0
- package/src/widgets/primitives/SectionHeader.tsx +77 -0
- package/src/widgets/primitives/Text.tsx +54 -0
- package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
- package/src/widgets/primitives/Tooltip.tsx +204 -0
- package/src/widgets/primitives/Vec3Input.tsx +70 -0
- package/src/widgets/primitives/banner-tones.ts +32 -0
- package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
- package/src/widgets/primitives/editor-icons.ts +245 -0
- package/src/widgets/primitives/panel-header-styles.ts +42 -0
- package/src/widgets/theme.ts +2633 -0
- package/src/widgets/z-index.ts +25 -0
package/src/session.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE SESSION FACE — what the session SERVER and the editor PAGE both agree on.
|
|
3
|
+
*
|
|
4
|
+
* `@volter/editor-sdk` is the API with four faces (ARCHITECTURE-CORE §The target
|
|
5
|
+
* shape): protocol, host doors + layouts, widgets, session client. This is the
|
|
6
|
+
* protocol face's home, and it exists because of a LICENCE fact, not tidiness:
|
|
7
|
+
* the target shape's editor-kit row says the session server and the CLI are
|
|
8
|
+
* Apache "ONLY once the session imports nothing of the editor". Every module
|
|
9
|
+
* re-exported here used to live in `packages/editor/src/` (AGPL-3.0-only) and
|
|
10
|
+
* was value-imported by `packages/editor/server/**`, so the Node session's
|
|
11
|
+
* import graph reached AGPL application code on every boot.
|
|
12
|
+
*
|
|
13
|
+
* What qualifies for this face: a shape TWO programs must agree on, that imports
|
|
14
|
+
* neither React, nor the editor shell store, nor `node:` — measured, not
|
|
15
|
+
* asserted. All twelve below import nothing but `../share`, `@volter/editor-project`'s
|
|
16
|
+
* manifest schema, and each other. A module the editor app merely happens to
|
|
17
|
+
* share with the server does NOT qualify; it stays where it is and shows up in
|
|
18
|
+
* `scripts/validate-import-bans.mjs`'s `server-imports-editor-app` ratchet with
|
|
19
|
+
* a reason, until someone makes it a contract or deletes it.
|
|
20
|
+
*
|
|
21
|
+
* IMPORT THE MODULE, NOT THIS BARREL, from anything the closure meter watches:
|
|
22
|
+
* `@volter/editor-sdk/session/relative-path-guard`. The subpath is a wildcard
|
|
23
|
+
* export (`"./session/*"`), the same shape the runtime packages use, because
|
|
24
|
+
* `export *` here would put all twelve modules in every consumer's static
|
|
25
|
+
* value-import closure — measured at +4 files on the `full` build and +5 on
|
|
26
|
+
* `models` the one time this file was the door. This barrel is documentation
|
|
27
|
+
* and a convenience for callers no meter watches.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export * from './session/build-report';
|
|
31
|
+
export * from './session/collaboration-types';
|
|
32
|
+
export * from './session/command-table';
|
|
33
|
+
export * from './session/editor-brand';
|
|
34
|
+
export * from './session/editor-compatibility';
|
|
35
|
+
export * from './session/editor-control-lifecycle';
|
|
36
|
+
export * from './session/editor-control-protocol';
|
|
37
|
+
export * from './session/game-css-scope';
|
|
38
|
+
export * from './session/project-module-url';
|
|
39
|
+
export * from './session/relative-path-guard';
|
|
40
|
+
export * from './session/source-glob';
|
|
41
|
+
export * from './session/tool-contribution-convention';
|
package/src/share.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE share-session wire — `/__editor/share-control`'s payloads, declared once.
|
|
3
|
+
*
|
|
4
|
+
* This route has three clients living in three different compilation units:
|
|
5
|
+
* the editor SERVER that serves it (`packages/editor/server/`), the browser
|
|
6
|
+
* PANEL that polls it (`packages/editor/src/`), and the `vgai share` CLI
|
|
7
|
+
* (`packages/vgai-cli/src/`). Each of them used to declare its own copy of the
|
|
8
|
+
* payload, and the copies had already drifted — silently, in both directions,
|
|
9
|
+
* because an under-declared read of a JSON superset is a type error nowhere:
|
|
10
|
+
*
|
|
11
|
+
* - the CLI's invitation had no `delivery`, so `vgai share` could not report
|
|
12
|
+
* that the invitation email had FAILED to send — the one state the field
|
|
13
|
+
* exists for;
|
|
14
|
+
* - the CLI's audit rows typed `type` and `capability` as bare `string`,
|
|
15
|
+
* which accepts any spelling of an event the server never emits;
|
|
16
|
+
* - the browser panel's invitation had no `inviterAccountId`;
|
|
17
|
+
* - the browser panel had no `audit` at all, and re-declared the tunnel
|
|
18
|
+
* health shape inline instead of naming it.
|
|
19
|
+
*
|
|
20
|
+
* This package is the right home for all three: `@vgai/editor` and `@vgai/cli`
|
|
21
|
+
* both already depend on it, it depends on neither, and the CLI's esbuild
|
|
22
|
+
* bundle inlines it. (The CLI's standing "no editor-package dependency" rule is
|
|
23
|
+
* about `@vgai/editor` — the server+UI package whose express/vite/chokidar
|
|
24
|
+
* graph the standalone bundle must not drag in. This is a types-only module.)
|
|
25
|
+
*
|
|
26
|
+
* Two things deliberately stay OFF this wire, and stay off it structurally
|
|
27
|
+
* rather than by memory:
|
|
28
|
+
*
|
|
29
|
+
* - the invitation TOKEN HASH, which is a local extension on the server
|
|
30
|
+
* (`share-host.ts`'s `StoredShareInvitation extends ShareInvitationRecord`);
|
|
31
|
+
* - a participant's ORGANIZATION list, which the gateway holds on its own
|
|
32
|
+
* `ShareAccountProjection` to resolve team invitations. {@link ShareAccount}
|
|
33
|
+
* is what a participant projects to, so `status()` narrows to it.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A share role, least authority first.
|
|
38
|
+
*
|
|
39
|
+
* `maintainer` is deliberately absent: it is local-only and has no share role,
|
|
40
|
+
* which is why the editor's `CollaborationRole` is this union PLUS `maintainer`
|
|
41
|
+
* rather than the other way around.
|
|
42
|
+
*/
|
|
43
|
+
export type ShareRole = 'viewer' | 'commenter' | 'tester' | 'editor' | 'terminal';
|
|
44
|
+
|
|
45
|
+
export type ShareTunnelProvider = 'cloudflared' | 'ngrok';
|
|
46
|
+
|
|
47
|
+
export type ShareTunnelState = 'stopped' | 'starting' | 'healthy' | 'failed';
|
|
48
|
+
|
|
49
|
+
export interface ShareTunnelHealth {
|
|
50
|
+
checkedAt: string;
|
|
51
|
+
latencyMs: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The capability vocabulary a grant is checked against.
|
|
56
|
+
*
|
|
57
|
+
* Wider than the roles on purpose: `public` and `never-share` are DECLARATIONS
|
|
58
|
+
* a route makes about itself, and `maintain` is the local-only authority no
|
|
59
|
+
* share role reaches — so this union is not derivable from {@link ShareRole}.
|
|
60
|
+
*/
|
|
61
|
+
export type ShareCapability =
|
|
62
|
+
| 'public'
|
|
63
|
+
| 'view'
|
|
64
|
+
| 'comment'
|
|
65
|
+
| 'test'
|
|
66
|
+
| 'edit'
|
|
67
|
+
| 'terminal'
|
|
68
|
+
| 'maintain'
|
|
69
|
+
| 'never-share';
|
|
70
|
+
|
|
71
|
+
/** A verified VGAI account, as a share participant projects onto the wire. */
|
|
72
|
+
export interface ShareAccount {
|
|
73
|
+
id: string;
|
|
74
|
+
email: string;
|
|
75
|
+
name?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* How the invitation was DELIVERED, when we delivered it.
|
|
80
|
+
*
|
|
81
|
+
* Present-and-`failed` is the state this field exists for: an invitation
|
|
82
|
+
* created whose email never left is otherwise indistinguishable, on every
|
|
83
|
+
* surface, from one that arrived.
|
|
84
|
+
*/
|
|
85
|
+
export interface ShareInvitationDelivery {
|
|
86
|
+
channel: 'clerk-email';
|
|
87
|
+
status: 'sent' | 'failed';
|
|
88
|
+
providerInvitationId?: string;
|
|
89
|
+
sentAt?: string;
|
|
90
|
+
problem?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ShareInvitationRecord {
|
|
94
|
+
id: string;
|
|
95
|
+
inviterAccountId: string;
|
|
96
|
+
recipientAccountId?: string;
|
|
97
|
+
recipientEmail?: string;
|
|
98
|
+
recipientOrganizationId?: string;
|
|
99
|
+
recipientOrganizationName?: string;
|
|
100
|
+
recipientOrganizationDomain?: string;
|
|
101
|
+
/** The highest role this invitation may ever be raised to. */
|
|
102
|
+
roleCeiling: ShareRole;
|
|
103
|
+
usePolicy: 'person' | 'team';
|
|
104
|
+
createdAt: string;
|
|
105
|
+
expiresAt: string;
|
|
106
|
+
revokedAt?: string;
|
|
107
|
+
delivery?: ShareInvitationDelivery;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ShareParticipant {
|
|
111
|
+
participantId: string;
|
|
112
|
+
account: ShareAccount;
|
|
113
|
+
invitationId: string;
|
|
114
|
+
role: ShareRole;
|
|
115
|
+
roleCeiling: ShareRole;
|
|
116
|
+
credentials: number;
|
|
117
|
+
joined: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type ShareAuditEventType =
|
|
121
|
+
| 'auth-failed'
|
|
122
|
+
| 'auth-started'
|
|
123
|
+
| 'invite-redeemed'
|
|
124
|
+
| 'capability-denied'
|
|
125
|
+
| 'role-changed'
|
|
126
|
+
| 'participant-kicked'
|
|
127
|
+
| 'invitation-revoked'
|
|
128
|
+
| 'terminal-invoked';
|
|
129
|
+
|
|
130
|
+
export interface ShareAuditEvent {
|
|
131
|
+
at: string;
|
|
132
|
+
type: ShareAuditEventType;
|
|
133
|
+
invitationId?: string;
|
|
134
|
+
participantId?: string;
|
|
135
|
+
accountId?: string;
|
|
136
|
+
capability?: ShareCapability;
|
|
137
|
+
/** The role a `role-changed` event settled on. */
|
|
138
|
+
role?: ShareRole;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The `/__editor/share-control/status` payload. */
|
|
142
|
+
export interface ShareStatus {
|
|
143
|
+
active: boolean;
|
|
144
|
+
sessionId: string;
|
|
145
|
+
projectRoot: string;
|
|
146
|
+
publicUrl?: string;
|
|
147
|
+
tunnelProvider?: ShareTunnelProvider;
|
|
148
|
+
tunnel: ShareTunnelState;
|
|
149
|
+
health?: ShareTunnelHealth;
|
|
150
|
+
invitations: readonly ShareInvitationRecord[];
|
|
151
|
+
participants: readonly ShareParticipant[];
|
|
152
|
+
audit: readonly ShareAuditEvent[];
|
|
153
|
+
problem?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The `/__editor/share-control/invitations` reply. */
|
|
157
|
+
export interface CreatedShareInvitation {
|
|
158
|
+
invitation: ShareInvitationRecord;
|
|
159
|
+
url: string;
|
|
160
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core, registry-level error codes — always available regardless of which
|
|
5
|
+
* operation was dispatched. Operation-specific codes are declared per
|
|
6
|
+
* operation on `ToolDefinition.errors` (see `registry.ts`).
|
|
7
|
+
*/
|
|
8
|
+
export const CORE_ERROR_CODES = {
|
|
9
|
+
/** `dispatch()` was called with a name no operation is registered under. */
|
|
10
|
+
OPERATION_NOT_FOUND: 'OPERATION_NOT_FOUND',
|
|
11
|
+
/** Input failed the operation's Zod input schema. */
|
|
12
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
13
|
+
/** The impl's return value failed the operation's Zod result schema, or a
|
|
14
|
+
* thrown ToolError's `data` failed its declared error-code schema —
|
|
15
|
+
* either way, an implementation bug, not a caller bug. */
|
|
16
|
+
INVALID_OUTPUT: 'INVALID_OUTPUT',
|
|
17
|
+
/** The impl threw something that isn't a declared, structured error. */
|
|
18
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export type CoreErrorCode = (typeof CORE_ERROR_CODES)[keyof typeof CORE_ERROR_CODES];
|
|
22
|
+
|
|
23
|
+
/** A single Zod validation failure, reduced to a machine-readable shape. */
|
|
24
|
+
export interface StructuredIssue {
|
|
25
|
+
path: (string | number | symbol)[];
|
|
26
|
+
message: string;
|
|
27
|
+
code: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The one error shape every `dispatch()` failure normalizes into. `code` is
|
|
32
|
+
* always machine-readable and always present — nothing in this codebase may
|
|
33
|
+
* identify an operation failure by parsing `message` prose (§8 B1 AC).
|
|
34
|
+
*/
|
|
35
|
+
export interface StructuredOperationError {
|
|
36
|
+
code: string;
|
|
37
|
+
message: string;
|
|
38
|
+
/** Present for INVALID_INPUT / INVALID_OUTPUT — the exact failing path(s). */
|
|
39
|
+
issues?: StructuredIssue[];
|
|
40
|
+
/** Present when the failing code declares a data schema (or the impl attached data). */
|
|
41
|
+
data?: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A global brand keeps expected errors recognizable across separately loaded
|
|
46
|
+
* copies of @vgai/sdk (for example, an editor host dispatching a standalone
|
|
47
|
+
* project's tool). `instanceof` alone cannot cross that package boundary.
|
|
48
|
+
*/
|
|
49
|
+
export const OPERATION_ERROR_BRAND = Symbol.for('@vgai/sdk.ToolError');
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The only way an operation `impl` should signal an EXPECTED, contractual
|
|
53
|
+
* failure (as opposed to an unexpected bug/exception). `code` must be one
|
|
54
|
+
* the operation's `ToolDefinition.errors` declares — `dispatch()`
|
|
55
|
+
* cross-checks it and validates `data` against that code's schema, so an
|
|
56
|
+
* undeclared code or mismatched data is itself normalized into a structured
|
|
57
|
+
* error rather than silently forwarded (see `registry.ts`'s
|
|
58
|
+
* `normalizeThrown`).
|
|
59
|
+
*/
|
|
60
|
+
export class ToolError extends Error {
|
|
61
|
+
readonly [OPERATION_ERROR_BRAND] = true;
|
|
62
|
+
readonly code: string;
|
|
63
|
+
readonly data: unknown;
|
|
64
|
+
|
|
65
|
+
constructor(code: string, message: string, data?: unknown) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = 'ToolError';
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.data = data;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isOperationError(value: unknown): value is ToolError {
|
|
74
|
+
if (value instanceof ToolError) return true;
|
|
75
|
+
if (value === null || typeof value !== 'object') return false;
|
|
76
|
+
const candidate = value as Record<PropertyKey, unknown>;
|
|
77
|
+
return (
|
|
78
|
+
candidate[OPERATION_ERROR_BRAND] === true &&
|
|
79
|
+
typeof candidate['code'] === 'string' &&
|
|
80
|
+
typeof candidate['message'] === 'string'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Reduce a `ZodError`'s issues to the structured, serializable shape above. */
|
|
85
|
+
export function toStructuredIssues(issues: readonly z.ZodIssue[]): StructuredIssue[] {
|
|
86
|
+
return issues.map((issue) => ({
|
|
87
|
+
path: [...issue.path],
|
|
88
|
+
message: issue.message,
|
|
89
|
+
code: issue.code,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
@@ -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 Volter Editor in Account before generating.`);
|
|
70
|
+
}
|