@forgeax/engine-plugin 0.0.0-dev.8d955ade1c79

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/src/loader.ts ADDED
@@ -0,0 +1,212 @@
1
+ import type { Context, Fiber, Plugin } from '@deepseek-ai/cordis';
2
+ import { type EntryOptions, Group, Loader } from '@deepseek-ai/cordis-plugin-loader';
3
+ import type { ToolRealm } from '@forgeax/engine-tool-runtime';
4
+
5
+ import { isToolPlugin } from './tool-plugin.js';
6
+
7
+ export type PluginRealm = ToolRealm;
8
+
9
+ export interface PluginCatalogRecord {
10
+ readonly realm: PluginRealm;
11
+ readonly load: () => Promise<unknown>;
12
+ }
13
+
14
+ export type PluginCatalog = ReadonlyMap<string, PluginCatalogRecord>;
15
+
16
+ export { createContextCapabilityResolver } from './capability.js';
17
+ export { defineToolPlugin, isToolPlugin, type ToolPlugin } from './tool-plugin.js';
18
+
19
+ export interface GamePluginEntry extends EntryOptions {
20
+ readonly realm?: PluginRealm;
21
+ }
22
+
23
+ export type CatalogLoaderErrorCode =
24
+ | 'plugin-catalog-missing'
25
+ | 'plugin-realm-mismatch'
26
+ | 'plugin-entry-realm-mixed'
27
+ | 'plugin-realm-unsupported'
28
+ | 'plugin-catalog-digest-mismatch';
29
+
30
+ export class CatalogLoaderError extends Error {
31
+ readonly code: CatalogLoaderErrorCode;
32
+ readonly expected: string;
33
+ readonly hint: string;
34
+ readonly detail: Readonly<Record<string, unknown>>;
35
+
36
+ constructor(
37
+ code: CatalogLoaderErrorCode,
38
+ expected: string,
39
+ hint: string,
40
+ detail: Readonly<Record<string, unknown>>,
41
+ ) {
42
+ super(`${code}: ${expected}`);
43
+ this.name = 'CatalogLoaderError';
44
+ this.code = code;
45
+ this.expected = expected;
46
+ this.hint = hint;
47
+ this.detail = detail;
48
+ }
49
+ }
50
+
51
+ interface CatalogLoaderConfig {
52
+ readonly catalog: PluginCatalog;
53
+ readonly realm: PluginRealm;
54
+ readonly baseUrl?: string;
55
+ }
56
+
57
+ export interface CatalogLoaderBootstrapOptions {
58
+ readonly catalogDigest: string;
59
+ readonly supportedRealms: readonly PluginRealm[];
60
+ }
61
+
62
+ export interface CatalogLoaderBootstrapValue extends CatalogLoaderHandle {
63
+ readonly catalogDigest: string;
64
+ readonly realm: PluginRealm;
65
+ }
66
+
67
+ export type CatalogLoaderBootstrapResult =
68
+ | { readonly ok: true; readonly value: CatalogLoaderBootstrapValue }
69
+ | { readonly ok: false; readonly error: CatalogLoaderError };
70
+
71
+ /** DeepSeek Harness Loader with its import boundary resolved by a static build catalog. */
72
+ export class CatalogLoader extends Loader {
73
+ readonly catalog: PluginCatalog;
74
+ readonly realm: PluginRealm;
75
+
76
+ constructor(ctx: Context, config: CatalogLoaderConfig) {
77
+ super(ctx, config.baseUrl === undefined ? {} : { baseUrl: config.baseUrl });
78
+ this.catalog = config.catalog;
79
+ this.realm = config.realm;
80
+ this.internal = undefined;
81
+ this.builtins.group = Group;
82
+ }
83
+
84
+ override import(name: string, getOuterStack?: () => string[]): Promise<unknown> | unknown {
85
+ if (name.startsWith('cordis:')) return super.import(name, getOuterStack);
86
+ const record = this.catalog.get(name);
87
+ if (record === undefined) {
88
+ throw new CatalogLoaderError(
89
+ 'plugin-catalog-missing',
90
+ `plugin ${name} to exist in the generated catalog`,
91
+ 'Install the package, add the Entry to forge.json, and rebuild the generated catalog.',
92
+ { name, realm: this.realm },
93
+ );
94
+ }
95
+ if (record.realm !== this.realm) {
96
+ throw new CatalogLoaderError(
97
+ 'plugin-realm-mismatch',
98
+ `plugin ${name} to target the ${this.realm} realm`,
99
+ 'Use a realm-specific plugin export and Entry.',
100
+ { actual: record.realm, expected: this.realm, name },
101
+ );
102
+ }
103
+ return record.load();
104
+ }
105
+
106
+ override unwrapExports(exports: unknown): unknown {
107
+ const value = super.unwrapExports(exports);
108
+ return isToolPlugin(value) ? value.plugin : value;
109
+ }
110
+ }
111
+
112
+ export interface CatalogLoaderHandle {
113
+ readonly loader: CatalogLoader;
114
+ readonly fiber: Fiber;
115
+ }
116
+
117
+ /** Bridges one already-active Cordis realm into the generic typed capability seam. */
118
+ /** Install the native DSH Loader service into an existing Cordis realm. */
119
+ export async function installCatalogLoader(
120
+ ctx: Context,
121
+ catalog: PluginCatalog,
122
+ realm: PluginRealm,
123
+ ): Promise<CatalogLoaderHandle> {
124
+ const fiber = await ctx.plugin(CatalogLoader as unknown as Plugin, { catalog, realm });
125
+ return { fiber, loader: ctx.loader as CatalogLoader };
126
+ }
127
+
128
+ /** Validate the physical realm before installing the one native CatalogLoader. */
129
+ export async function bootstrapCatalogLoader(
130
+ ctx: Context,
131
+ catalog: PluginCatalog,
132
+ realm: PluginRealm,
133
+ options: CatalogLoaderBootstrapOptions,
134
+ ): Promise<CatalogLoaderBootstrapResult> {
135
+ if (!options.supportedRealms.includes(realm)) {
136
+ return {
137
+ ok: false,
138
+ error: new CatalogLoaderError(
139
+ 'plugin-realm-unsupported',
140
+ `the ${realm} realm to be supported by this host`,
141
+ 'Select a realm advertised by the capability matrix before module evaluation.',
142
+ { realm, supportedRealms: options.supportedRealms },
143
+ ),
144
+ };
145
+ }
146
+ const handle = await installCatalogLoader(ctx, catalog, realm);
147
+ return {
148
+ ok: true,
149
+ value: { ...handle, catalogDigest: options.catalogDigest, realm },
150
+ };
151
+ }
152
+
153
+ function effectiveRealm(entry: GamePluginEntry, inherited: PluginRealm): PluginRealm {
154
+ return entry.realm ?? inherited;
155
+ }
156
+
157
+ function assertSingleRealmGroup(entry: GamePluginEntry, inheritedRealm: PluginRealm): void {
158
+ const realm = effectiveRealm(entry, inheritedRealm);
159
+ if (!entry.group) return;
160
+ const children = (entry.config ?? []) as readonly GamePluginEntry[];
161
+ for (const child of children) {
162
+ const childRealm = effectiveRealm(child, realm);
163
+ if (childRealm !== realm) {
164
+ throw new CatalogLoaderError(
165
+ 'plugin-entry-realm-mixed',
166
+ `group ${entry.id} to contain entries for only the ${realm} physical realm`,
167
+ 'Split Host and Engine capabilities into separate top-level groups.',
168
+ { actual: childRealm, expected: realm, group: entry.id },
169
+ );
170
+ }
171
+ assertSingleRealmGroup(child, realm);
172
+ }
173
+ }
174
+
175
+ function projectEntry(entry: GamePluginEntry, inherited: PluginRealm): EntryOptions {
176
+ const realm = effectiveRealm(entry, inherited);
177
+ const config = entry.group
178
+ ? projectPluginEntries((entry.config ?? []) as readonly GamePluginEntry[], realm, realm)
179
+ : entry.config;
180
+ return {
181
+ id: entry.id,
182
+ name: entry.name,
183
+ ...(config === undefined ? {} : { config }),
184
+ ...(entry.group == null ? {} : { group: entry.group }),
185
+ ...(entry.disabled == null ? {} : { disabled: entry.disabled }),
186
+ ...(entry.inject == null ? {} : { inject: entry.inject }),
187
+ };
188
+ }
189
+
190
+ /** Select one physical realm and strip ForgeaX-only deployment metadata before DSH reconciliation. */
191
+ export function projectPluginEntries(
192
+ entries: readonly GamePluginEntry[],
193
+ realm: PluginRealm,
194
+ inheritedRealm: PluginRealm = 'engine',
195
+ ): EntryOptions[] {
196
+ const projected: EntryOptions[] = [];
197
+ for (const entry of entries) {
198
+ const current = effectiveRealm(entry, inheritedRealm);
199
+ assertSingleRealmGroup(entry, inheritedRealm);
200
+ if (current === realm) projected.push(projectEntry(entry, current));
201
+ }
202
+ return projected;
203
+ }
204
+
205
+ export {
206
+ Entry,
207
+ EntryGroup,
208
+ type EntryOptions,
209
+ EntryTree,
210
+ Group,
211
+ Loader,
212
+ } from '@deepseek-ai/cordis-plugin-loader';
@@ -0,0 +1,21 @@
1
+ import type { Plugin } from '@deepseek-ai/cordis';
2
+ import type { ToolContribution } from '@forgeax/engine-tool-runtime';
3
+
4
+ /** A native Cordis plugin plus the tool contributions it exposes. */
5
+ export interface ToolPlugin {
6
+ readonly plugin: Plugin;
7
+ readonly tools: readonly ToolContribution<unknown, unknown>[];
8
+ }
9
+
10
+ export function defineToolPlugin(
11
+ plugin: Plugin,
12
+ tools: readonly ToolContribution<unknown, unknown>[],
13
+ ): ToolPlugin {
14
+ return { plugin, tools: [...tools] };
15
+ }
16
+
17
+ export function isToolPlugin(value: unknown): value is ToolPlugin {
18
+ if (typeof value !== 'object' || value === null) return false;
19
+ const candidate = value as Partial<ToolPlugin>;
20
+ return candidate.plugin !== undefined && Array.isArray(candidate.tools);
21
+ }