@pie-players/pie-assessment-toolkit 0.3.68 → 0.3.69

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.
Files changed (33) hide show
  1. package/README.md +80 -8
  2. package/dist/components/ItemToolBar.custom-element.js +1 -1
  3. package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
  4. package/dist/components/SectionToolBar.custom-element.js +1 -1
  5. package/dist/components/chunks/ItemToolBar-pe5szfyx.js +46 -0
  6. package/dist/components/chunks/ItemToolBar-rd7te9r0.js +51 -0
  7. package/dist/policy/core/compose-decision.js +14 -0
  8. package/dist/policy/core/decision-types.d.ts +1 -1
  9. package/dist/policy/sources/PnpPolicySource.d.ts +8 -0
  10. package/dist/policy/sources/PnpPolicySource.js +19 -10
  11. package/dist/services/ToolRegistry.d.ts +34 -0
  12. package/dist/services/ToolRegistry.js +34 -0
  13. package/dist/services/tool-context.js +8 -3
  14. package/dist/services/tool-providers/CortexToolProvider.d.ts +18 -0
  15. package/dist/services/tool-providers/CortexToolProvider.js +32 -0
  16. package/dist/services/tool-providers/DesmosToolProvider.d.ts +13 -102
  17. package/dist/services/tool-providers/DesmosToolProvider.js +14 -145
  18. package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +21 -0
  19. package/dist/services/tool-providers/GeoGebraToolProvider.js +32 -0
  20. package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +35 -0
  21. package/dist/services/tool-providers/LazyCalculatorToolProvider.js +95 -0
  22. package/dist/services/tool-providers/index.d.ts +4 -0
  23. package/dist/services/tool-providers/index.js +2 -0
  24. package/dist/tools/client.d.ts +0 -1
  25. package/dist/tools/client.js +0 -2
  26. package/dist/tools/internal.d.ts +1 -1
  27. package/dist/tools/internal.js +1 -1
  28. package/dist/tools/types.d.ts +1 -66
  29. package/package.json +22 -12
  30. package/dist/components/chunks/ItemToolBar-38mhtjsq.js +0 -51
  31. package/dist/components/chunks/ItemToolBar-9ymm7pd1.js +0 -46
  32. package/dist/tools/library-loader.d.ts +0 -62
  33. package/dist/tools/library-loader.js +0 -261
@@ -483,6 +483,26 @@ export interface ToolRegistration {
483
483
  * @returns true if tool should be visible, false to hide
484
484
  */
485
485
  isVisibleInContext?(context: ToolContext): boolean;
486
+ /**
487
+ * Whether this tool can act on this content at all — a capability question,
488
+ * not a relevance heuristic. Answering `false` withdraws the tool even where
489
+ * a PNP grant would otherwise keep it, which {@link
490
+ * ToolRegistration.isVisibleInContext} deliberately cannot do.
491
+ *
492
+ * The two gates answer different questions, and most tools declare only the
493
+ * first. A calculator is *applicable* to every item — a learner granted one
494
+ * keeps it on an item that does not look mathematical — while its relevance
495
+ * is a guess about usefulness. An answer eliminator on an item with no choice
496
+ * interaction has nothing to strike through, so no grant can make it work.
497
+ *
498
+ * Declare this only where the tool's own controls provably do nothing:
499
+ * withdrawing a granted accommodation on a false negative is the more
500
+ * expensive failure. Omitting it means "applicable".
501
+ *
502
+ * @param context - The item or element context the tool would act on
503
+ * @returns false to withdraw the tool from this context
504
+ */
505
+ isApplicableToContent?(context: ToolContext): boolean;
486
506
  /**
487
507
  * Toolbar render contract. Required for `toolbar-toggle` and
488
508
  * `selection-gateway`; a region capability renders through
@@ -625,6 +645,20 @@ export declare class ToolRegistry {
625
645
  * @returns Array of visible tool registrations
626
646
  */
627
647
  filterVisibleInContext(allowedToolIds: string[], context: ToolContext): ToolRegistration[];
648
+ /**
649
+ * Whether a tool can act on any of the contexts it would be placed against.
650
+ * Unlike the relevance pass this is a veto: a `false` here removes the tool
651
+ * from a toolbar even when a grant protects it, so a tool answers `false`
652
+ * only where its controls provably do nothing.
653
+ *
654
+ * A tool that declares no applicability gate is applicable. So is one
655
+ * evaluated against no contexts — content that has not resolved yet cannot
656
+ * establish that a tool is useless.
657
+ *
658
+ * @param toolId - Tool to ask
659
+ * @param contexts - Every context the tool could act on at this placement
660
+ */
661
+ isApplicableToAnyContext(toolId: string, contexts: readonly ToolContext[]): boolean;
628
662
  /**
629
663
  * Get tool metadata for building UIs
630
664
  * Useful for building PNP configuration interfaces
@@ -164,6 +164,10 @@ function assertToolRegistrationShape(registration) {
164
164
  typeof registration.isVisibleInContext !== "function") {
165
165
  throw new Error(`Invalid tool registration "${registration.toolId}": "isVisibleInContext" must be a function when present.`);
166
166
  }
167
+ if (registration.isApplicableToContent !== undefined &&
168
+ typeof registration.isApplicableToContent !== "function") {
169
+ throw new Error(`Invalid tool registration "${registration.toolId}": "isApplicableToContent" must be a function when present.`);
170
+ }
167
171
  if (registration.requiresAuthoredContent !== undefined) {
168
172
  if (typeof registration.requiresAuthoredContent !== "object" ||
169
173
  registration.requiresAuthoredContent === null ||
@@ -442,6 +446,36 @@ export class ToolRegistry {
442
446
  }
443
447
  return visible;
444
448
  }
449
+ /**
450
+ * Whether a tool can act on any of the contexts it would be placed against.
451
+ * Unlike the relevance pass this is a veto: a `false` here removes the tool
452
+ * from a toolbar even when a grant protects it, so a tool answers `false`
453
+ * only where its controls provably do nothing.
454
+ *
455
+ * A tool that declares no applicability gate is applicable. So is one
456
+ * evaluated against no contexts — content that has not resolved yet cannot
457
+ * establish that a tool is useless.
458
+ *
459
+ * @param toolId - Tool to ask
460
+ * @param contexts - Every context the tool could act on at this placement
461
+ */
462
+ isApplicableToAnyContext(toolId, contexts) {
463
+ const tool = this.get(toolId);
464
+ if (!tool?.isApplicableToContent)
465
+ return true;
466
+ if (contexts.length === 0)
467
+ return true;
468
+ return contexts.some((context) => {
469
+ try {
470
+ return tool.isApplicableToContent?.(context) ?? true;
471
+ }
472
+ catch (error) {
473
+ console.error(`Error evaluating applicability for tool '${toolId}':`, error);
474
+ // A gate that throws has not established that the tool is useless.
475
+ return true;
476
+ }
477
+ });
478
+ }
445
479
  /**
446
480
  * Get tool metadata for building UIs
447
481
  * Useful for building PNP configuration interfaces
@@ -300,9 +300,14 @@ export function hasChoiceInteraction(context) {
300
300
  if (!m || typeof m !== "object")
301
301
  return false;
302
302
  const type = m.element || "";
303
- if (interactionTypes.includes(type))
304
- return true;
305
- // Fallback for configs that don't provide canonical element names.
303
+ // A model that names its element has answered the question, whichever way
304
+ // the answer falls. `choices` is carried by interactions that are not
305
+ // choice interactions at all `placement-ordering`, `categorize` and
306
+ // `drag-in-the-blank` each hold their draggables there — so reading it on
307
+ // a named model showed the answer eliminator on items where the tool does
308
+ // nothing. The heuristic remains for configs that name no element.
309
+ if (type)
310
+ return interactionTypes.includes(type);
306
311
  return Array.isArray(m.choices) && m.choices.length > 0;
307
312
  });
308
313
  }
@@ -0,0 +1,18 @@
1
+ /** Fully bundled open-source calculator adapter for the tool-provider registry. */
2
+ import type { CalculatorToolProviderInitConfig } from "./LazyCalculatorToolProvider.js";
3
+ import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
4
+ import type { ToolProviderCapabilities } from "./ToolProviderApi.js";
5
+ export type CortexToolProviderConfig = CalculatorToolProviderInitConfig;
6
+ export declare class CortexToolProvider extends LazyCalculatorToolProvider<CortexToolProviderConfig> {
7
+ readonly providerId = "cortex-calculator";
8
+ readonly providerName = "PIE Open-Source Calculator";
9
+ readonly version = "1";
10
+ readonly requiresAuth = false;
11
+ protected getDefinition(): {
12
+ backend: string;
13
+ moduleImportOperation: string;
14
+ loadProvider: () => Promise<typeof import("@pie-players/pie-calculator-cortex").CortexCalculatorProvider>;
15
+ initializationErrorMessage: string;
16
+ };
17
+ getCapabilities(): ToolProviderCapabilities;
18
+ }
@@ -0,0 +1,32 @@
1
+ /** Fully bundled open-source calculator adapter for the tool-provider registry. */
2
+ import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
3
+ export class CortexToolProvider extends LazyCalculatorToolProvider {
4
+ providerId = "cortex-calculator";
5
+ providerName = "PIE Open-Source Calculator";
6
+ version = "1";
7
+ requiresAuth = false;
8
+ getDefinition() {
9
+ return {
10
+ backend: "cortex",
11
+ moduleImportOperation: "cortex-provider-module-import",
12
+ loadProvider: async () => {
13
+ const module = await import("@pie-players/pie-calculator-cortex");
14
+ return module.CortexCalculatorProvider;
15
+ },
16
+ initializationErrorMessage: "Failed to initialize the PIE open-source calculator provider. Confirm that this browser supports module workers and that the bundled worker assets are available.",
17
+ };
18
+ }
19
+ getCapabilities() {
20
+ return {
21
+ supportsOffline: true,
22
+ requiresAuth: false,
23
+ maxInstances: null,
24
+ features: {
25
+ basic: true,
26
+ scientific: true,
27
+ graphing: true,
28
+ fourFunction: true,
29
+ },
30
+ };
31
+ }
32
+ }
@@ -1,108 +1,19 @@
1
- /**
2
- * Desmos Calculator Tool Provider
3
- *
4
- * Provides Desmos calculators (basic, scientific, graphing)
5
- * with authentication and proxy support.
6
- *
7
- * SECURITY BEST PRACTICE:
8
- * - Development: Pass apiKey directly for local testing
9
- * - Production: Use proxyEndpoint or authFetcher to keep API key server-side
10
- *
11
- * Part of PIE Assessment Toolkit.
12
- */
13
- import type { CalculatorProvider } from "@pie-players/pie-calculator";
14
- import type { ToolProviderApi, ToolProviderCapabilities } from "./ToolProviderApi.js";
15
- /**
16
- * Desmos tool provider configuration
17
- *
18
- * Auth and telemetry only. Per-calculator Desmos options are owned by the
19
- * calculator component, which derives them from the calculator type and passes
20
- * them to `createCalculator()` directly — this provider never sees that config,
21
- * so a defaults field here would silently do nothing.
22
- */
23
- export interface DesmosToolProviderConfig {
24
- /**
25
- * Desmos API key (DEVELOPMENT ONLY)
26
- * Never expose in production client code!
27
- *
28
- * Obtain from: https://www.desmos.com/api
29
- */
30
- apiKey?: string;
31
- /**
32
- * Server proxy endpoint (PRODUCTION RECOMMENDED)
33
- * Backend handles API key securely
34
- *
35
- * @example '/api/desmos/token'
36
- * @example 'https://api.myapp.com/tools/desmos/auth'
37
- */
38
- proxyEndpoint?: string;
39
- /**
40
- * Optional telemetry callback for tool/backend instrumentation.
41
- */
42
- onTelemetry?: (eventName: string, payload?: Record<string, unknown>) => void | Promise<void>;
43
- }
44
- /**
45
- * Desmos Calculator Tool Provider
46
- *
47
- * Wraps DesmosCalculatorProvider with the ToolProviderApi interface
48
- * for use in the ToolProviderRegistry.
49
- *
50
- * @example
51
- * ```typescript
52
- * const provider = new DesmosToolProvider();
53
- *
54
- * await provider.initialize({
55
- * apiKey: 'your-api-key', // Development only
56
- * proxyEndpoint: '/api/desmos/token', // Production
57
- * });
58
- *
59
- * const calculatorProvider = await provider.createInstance();
60
- * ```
61
- */
62
- export declare class DesmosToolProvider implements ToolProviderApi<DesmosToolProviderConfig, CalculatorProvider> {
1
+ /** Desmos calculator adapter for the generic tool-provider registry. */
2
+ import type { CalculatorProviderInit } from "@pie-players/pie-calculator";
3
+ import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
4
+ import type { ToolProviderCapabilities } from "./ToolProviderApi.js";
5
+ /** Provider initialization is the provider-neutral calculator contract. */
6
+ export type DesmosToolProviderConfig = CalculatorProviderInit;
7
+ export declare class DesmosToolProvider extends LazyCalculatorToolProvider<DesmosToolProviderConfig> {
63
8
  readonly providerId = "desmos-calculator";
64
9
  readonly providerName = "Desmos Calculator";
65
- readonly category: "calculator";
66
10
  readonly version = "1.12";
67
11
  readonly requiresAuth = true;
68
- private desmosProvider;
69
- private config;
70
- private emitTelemetry;
71
- /**
72
- * Initialize Desmos calculator provider
73
- *
74
- * Loads the Desmos API library and authenticates with provided credentials.
75
- *
76
- * @param config Configuration with API key or proxy endpoint
77
- * @throws Error if initialization fails
78
- */
79
- initialize(config: DesmosToolProviderConfig): Promise<void>;
80
- /**
81
- * Create a calculator provider instance
82
- *
83
- * Returns the initialized Desmos calculator provider.
84
- *
85
- * @param config Optional instance-specific configuration (currently unused)
86
- * @returns Desmos calculator provider
87
- * @throws Error if provider not initialized
88
- */
89
- createInstance(config?: Partial<DesmosToolProviderConfig>): Promise<CalculatorProvider>;
90
- /**
91
- * Get provider capabilities
92
- *
93
- * @returns Desmos calculator capabilities
94
- */
12
+ protected getDefinition(): {
13
+ backend: string;
14
+ moduleImportOperation: string;
15
+ loadProvider: () => Promise<typeof import("@pie-players/pie-calculator-desmos").DesmosCalculatorProvider>;
16
+ initializationErrorMessage: string;
17
+ };
95
18
  getCapabilities(): ToolProviderCapabilities;
96
- /**
97
- * Check if provider is ready
98
- *
99
- * @returns true if provider is initialized
100
- */
101
- isReady(): boolean;
102
- /**
103
- * Clean up provider resources
104
- *
105
- * Destroys the Desmos calculator provider and releases resources.
106
- */
107
- destroy(): void;
108
19
  }
@@ -1,136 +1,26 @@
1
- /**
2
- * Desmos Calculator Tool Provider
3
- *
4
- * Provides Desmos calculators (basic, scientific, graphing)
5
- * with authentication and proxy support.
6
- *
7
- * SECURITY BEST PRACTICE:
8
- * - Development: Pass apiKey directly for local testing
9
- * - Production: Use proxyEndpoint or authFetcher to keep API key server-side
10
- *
11
- * Part of PIE Assessment Toolkit.
12
- */
13
- /**
14
- * Desmos Calculator Tool Provider
15
- *
16
- * Wraps DesmosCalculatorProvider with the ToolProviderApi interface
17
- * for use in the ToolProviderRegistry.
18
- *
19
- * @example
20
- * ```typescript
21
- * const provider = new DesmosToolProvider();
22
- *
23
- * await provider.initialize({
24
- * apiKey: 'your-api-key', // Development only
25
- * proxyEndpoint: '/api/desmos/token', // Production
26
- * });
27
- *
28
- * const calculatorProvider = await provider.createInstance();
29
- * ```
30
- */
31
- export class DesmosToolProvider {
1
+ /** Desmos calculator adapter for the generic tool-provider registry. */
2
+ import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
3
+ export class DesmosToolProvider extends LazyCalculatorToolProvider {
32
4
  providerId = "desmos-calculator";
33
5
  providerName = "Desmos Calculator";
34
- category = "calculator";
35
6
  version = "1.12";
36
7
  requiresAuth = true;
37
- desmosProvider = null;
38
- config = null;
39
- async emitTelemetry(eventName, payload) {
40
- try {
41
- await this.config?.onTelemetry?.(eventName, payload);
42
- }
43
- catch (error) {
44
- console.warn("[DesmosToolProvider] telemetry callback failed:", error);
45
- }
46
- }
47
- /**
48
- * Initialize Desmos calculator provider
49
- *
50
- * Loads the Desmos API library and authenticates with provided credentials.
51
- *
52
- * @param config Configuration with API key or proxy endpoint
53
- * @throws Error if initialization fails
54
- */
55
- async initialize(config) {
56
- if (this.desmosProvider) {
57
- console.warn("[DesmosToolProvider] Already initialized, skipping reinitialization");
58
- return;
59
- }
60
- this.config = config;
61
- const moduleLoadStartedAt = Date.now();
62
- await this.emitTelemetry("pie-tool-library-load-start", {
63
- toolId: "calculator",
64
- operation: "desmos-provider-module-import",
8
+ getDefinition() {
9
+ return {
65
10
  backend: "desmos",
66
- });
67
- const desmosModule = await (async () => {
68
- try {
69
- const loaded = (await import("@pie-players/pie-calculator-desmos"));
70
- await this.emitTelemetry("pie-tool-library-load-success", {
71
- toolId: "calculator",
72
- operation: "desmos-provider-module-import",
73
- backend: "desmos",
74
- duration: Date.now() - moduleLoadStartedAt,
75
- });
76
- return loaded;
77
- }
78
- catch (error) {
79
- await this.emitTelemetry("pie-tool-library-load-error", {
80
- toolId: "calculator",
81
- operation: "desmos-provider-module-import",
82
- backend: "desmos",
83
- duration: Date.now() - moduleLoadStartedAt,
84
- errorType: "ToolLibraryLoadError",
85
- message: error instanceof Error ? error.message : String(error),
86
- });
87
- throw error;
88
- }
89
- })();
90
- this.desmosProvider = new desmosModule.DesmosCalculatorProvider();
91
- // Initialize with API key or proxy
92
- try {
93
- await this.desmosProvider.initialize({
94
- apiKey: config.apiKey,
95
- proxyEndpoint: config.proxyEndpoint,
96
- onTelemetry: config.onTelemetry,
97
- });
98
- console.log(`[DesmosToolProvider] Initialized successfully ${config.proxyEndpoint
99
- ? "(using proxy)"
100
- : config.apiKey
101
- ? "(direct API key)"
102
- : "(no auth)"}`);
103
- }
104
- catch (error) {
105
- console.error("[DesmosToolProvider] Initialization failed:", error);
106
- throw new Error("Failed to initialize Desmos calculator provider. Check API key or proxy endpoint.");
107
- }
108
- }
109
- /**
110
- * Create a calculator provider instance
111
- *
112
- * Returns the initialized Desmos calculator provider.
113
- *
114
- * @param config Optional instance-specific configuration (currently unused)
115
- * @returns Desmos calculator provider
116
- * @throws Error if provider not initialized
117
- */
118
- async createInstance(config) {
119
- if (!this.desmosProvider) {
120
- throw new Error("[DesmosToolProvider] Provider not initialized. Call initialize() first.");
121
- }
122
- return this.desmosProvider;
11
+ moduleImportOperation: "desmos-provider-module-import",
12
+ loadProvider: async () => {
13
+ const module = await import("@pie-players/pie-calculator-desmos");
14
+ return module.DesmosCalculatorProvider;
15
+ },
16
+ initializationErrorMessage: "Failed to initialize Desmos calculator provider. Check the application key, preloaded API, runtime endpoint, or network access.",
17
+ };
123
18
  }
124
- /**
125
- * Get provider capabilities
126
- *
127
- * @returns Desmos calculator capabilities
128
- */
129
19
  getCapabilities() {
130
20
  return {
131
- supportsOffline: false, // Requires Desmos CDN
21
+ supportsOffline: false,
132
22
  requiresAuth: true,
133
- maxInstances: null, // Unlimited calculator instances
23
+ maxInstances: null,
134
24
  features: {
135
25
  basic: true,
136
26
  scientific: true,
@@ -139,25 +29,4 @@ export class DesmosToolProvider {
139
29
  },
140
30
  };
141
31
  }
142
- /**
143
- * Check if provider is ready
144
- *
145
- * @returns true if provider is initialized
146
- */
147
- isReady() {
148
- return this.desmosProvider !== null;
149
- }
150
- /**
151
- * Clean up provider resources
152
- *
153
- * Destroys the Desmos calculator provider and releases resources.
154
- */
155
- destroy() {
156
- if (this.desmosProvider) {
157
- this.desmosProvider.destroy();
158
- this.desmosProvider = null;
159
- }
160
- this.config = null;
161
- console.log("[DesmosToolProvider] Destroyed");
162
- }
163
32
  }
@@ -0,0 +1,21 @@
1
+ /** GeoGebra calculator adapter for the generic tool-provider registry. */
2
+ import type { CalculatorToolProviderInitConfig } from "./LazyCalculatorToolProvider.js";
3
+ import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
4
+ import type { ToolProviderCapabilities } from "./ToolProviderApi.js";
5
+ export interface GeoGebraToolProviderConfig extends CalculatorToolProviderInitConfig {
6
+ scriptUrl?: string;
7
+ appletTimeoutMs?: number;
8
+ }
9
+ export declare class GeoGebraToolProvider extends LazyCalculatorToolProvider<GeoGebraToolProviderConfig> {
10
+ readonly providerId = "geogebra-calculator";
11
+ readonly providerName = "GeoGebra Calculator";
12
+ readonly version = "6";
13
+ readonly requiresAuth = false;
14
+ protected getDefinition(): {
15
+ backend: string;
16
+ moduleImportOperation: string;
17
+ loadProvider: () => Promise<typeof import("@pie-players/pie-calculator-geogebra").GeoGebraCalculatorProvider>;
18
+ initializationErrorMessage: string;
19
+ };
20
+ getCapabilities(): ToolProviderCapabilities;
21
+ }
@@ -0,0 +1,32 @@
1
+ /** GeoGebra calculator adapter for the generic tool-provider registry. */
2
+ import { LazyCalculatorToolProvider } from "./LazyCalculatorToolProvider.js";
3
+ export class GeoGebraToolProvider extends LazyCalculatorToolProvider {
4
+ providerId = "geogebra-calculator";
5
+ providerName = "GeoGebra Calculator";
6
+ version = "6";
7
+ requiresAuth = false;
8
+ getDefinition() {
9
+ return {
10
+ backend: "geogebra",
11
+ moduleImportOperation: "geogebra-provider-module-import",
12
+ loadProvider: async () => {
13
+ const module = await import("@pie-players/pie-calculator-geogebra");
14
+ return module.GeoGebraCalculatorProvider;
15
+ },
16
+ initializationErrorMessage: "Failed to initialize GeoGebra calculator provider. Confirm that the deployment may load GeoGebra and that its script URL is reachable.",
17
+ };
18
+ }
19
+ getCapabilities() {
20
+ return {
21
+ supportsOffline: false,
22
+ requiresAuth: false,
23
+ maxInstances: null,
24
+ features: {
25
+ basic: true,
26
+ scientific: true,
27
+ graphing: true,
28
+ fourFunction: false,
29
+ },
30
+ };
31
+ }
32
+ }
@@ -0,0 +1,35 @@
1
+ import type { CalculatorProvider, CalculatorProviderInit } from "@pie-players/pie-calculator";
2
+ import type { ToolProviderApi, ToolProviderCapabilities } from "./ToolProviderApi.js";
3
+ export type CalculatorToolProviderInitConfig = Pick<CalculatorProviderInit, "onTelemetry">;
4
+ type InitializableCalculatorProvider<TConfig> = CalculatorProvider & {
5
+ initialize(config: TConfig): Promise<void>;
6
+ };
7
+ interface LazyCalculatorProviderDefinition<TConfig> {
8
+ backend: string;
9
+ moduleImportOperation: string;
10
+ loadProvider: () => Promise<new () => InitializableCalculatorProvider<TConfig>>;
11
+ initializationErrorMessage: string;
12
+ }
13
+ /**
14
+ * Shared lazy-module and lifecycle implementation for calculator tool adapters.
15
+ * Concrete adapters own only their metadata, capabilities and provider import.
16
+ */
17
+ export declare abstract class LazyCalculatorToolProvider<TConfig extends CalculatorToolProviderInitConfig> implements ToolProviderApi<TConfig, CalculatorProvider> {
18
+ abstract readonly providerId: string;
19
+ abstract readonly providerName: string;
20
+ readonly category: "calculator";
21
+ abstract readonly version: string;
22
+ abstract readonly requiresAuth: boolean;
23
+ protected abstract getDefinition(): LazyCalculatorProviderDefinition<TConfig>;
24
+ abstract getCapabilities(): ToolProviderCapabilities;
25
+ private calculatorProvider;
26
+ private initializationPromise;
27
+ private lifecycleGeneration;
28
+ private emitTelemetry;
29
+ initialize(config?: TConfig): Promise<void>;
30
+ private initializeProvider;
31
+ createInstance(): Promise<CalculatorProvider>;
32
+ isReady(): boolean;
33
+ destroy(): void;
34
+ }
35
+ export {};
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared lazy-module and lifecycle implementation for calculator tool adapters.
3
+ * Concrete adapters own only their metadata, capabilities and provider import.
4
+ */
5
+ export class LazyCalculatorToolProvider {
6
+ category = "calculator";
7
+ calculatorProvider = null;
8
+ initializationPromise = null;
9
+ lifecycleGeneration = 0;
10
+ async emitTelemetry(config, eventName, payload) {
11
+ try {
12
+ await config.onTelemetry?.(eventName, payload);
13
+ }
14
+ catch (error) {
15
+ console.warn(`[${this.providerName}] telemetry callback failed:`, error);
16
+ }
17
+ }
18
+ async initialize(config = {}) {
19
+ if (this.calculatorProvider)
20
+ return;
21
+ if (this.initializationPromise)
22
+ return this.initializationPromise;
23
+ const generation = ++this.lifecycleGeneration;
24
+ const initializationPromise = this.initializeProvider(config, generation);
25
+ this.initializationPromise = initializationPromise;
26
+ try {
27
+ await initializationPromise;
28
+ }
29
+ finally {
30
+ if (this.initializationPromise === initializationPromise) {
31
+ this.initializationPromise = null;
32
+ }
33
+ }
34
+ }
35
+ async initializeProvider(config, generation) {
36
+ const definition = this.getDefinition();
37
+ const moduleLoadStartedAt = Date.now();
38
+ await this.emitTelemetry(config, "pie-tool-library-load-start", {
39
+ toolId: "calculator",
40
+ operation: definition.moduleImportOperation,
41
+ backend: definition.backend,
42
+ });
43
+ let ProviderConstructor;
44
+ try {
45
+ ProviderConstructor = await definition.loadProvider();
46
+ await this.emitTelemetry(config, "pie-tool-library-load-success", {
47
+ toolId: "calculator",
48
+ operation: definition.moduleImportOperation,
49
+ backend: definition.backend,
50
+ duration: Date.now() - moduleLoadStartedAt,
51
+ });
52
+ }
53
+ catch (error) {
54
+ await this.emitTelemetry(config, "pie-tool-library-load-error", {
55
+ toolId: "calculator",
56
+ operation: definition.moduleImportOperation,
57
+ backend: definition.backend,
58
+ duration: Date.now() - moduleLoadStartedAt,
59
+ errorType: "ToolLibraryLoadError",
60
+ message: error instanceof Error ? error.message : String(error),
61
+ });
62
+ throw new Error(definition.initializationErrorMessage, { cause: error });
63
+ }
64
+ if (generation !== this.lifecycleGeneration) {
65
+ throw new Error(`${this.providerName} initialization was cancelled`);
66
+ }
67
+ const candidate = new ProviderConstructor();
68
+ try {
69
+ await candidate.initialize(config);
70
+ if (generation !== this.lifecycleGeneration) {
71
+ throw new Error(`${this.providerName} initialization was cancelled`);
72
+ }
73
+ this.calculatorProvider = candidate;
74
+ }
75
+ catch (error) {
76
+ candidate.destroy();
77
+ throw new Error(definition.initializationErrorMessage, { cause: error });
78
+ }
79
+ }
80
+ async createInstance() {
81
+ if (!this.calculatorProvider) {
82
+ throw new Error(`[${this.providerName}] Provider not initialized. Call initialize() first.`);
83
+ }
84
+ return this.calculatorProvider;
85
+ }
86
+ isReady() {
87
+ return this.calculatorProvider !== null;
88
+ }
89
+ destroy() {
90
+ this.lifecycleGeneration += 1;
91
+ this.initializationPromise = null;
92
+ this.calculatorProvider?.destroy();
93
+ this.calculatorProvider = null;
94
+ }
95
+ }
@@ -11,5 +11,9 @@ export { ToolProviderRegistry } from "./ToolProviderRegistry.js";
11
11
  export type { ToolProviderConfig } from "./ToolProviderRegistry.js";
12
12
  export { DesmosToolProvider } from "./DesmosToolProvider.js";
13
13
  export type { DesmosToolProviderConfig } from "./DesmosToolProvider.js";
14
+ export { CortexToolProvider } from "./CortexToolProvider.js";
15
+ export type { CortexToolProviderConfig } from "./CortexToolProvider.js";
16
+ export { GeoGebraToolProvider } from "./GeoGebraToolProvider.js";
17
+ export type { GeoGebraToolProviderConfig } from "./GeoGebraToolProvider.js";
14
18
  export { TTSToolProvider } from "./TTSToolProvider.js";
15
19
  export type { TTSToolProviderConfig, TTSBackend, } from "./TTSToolProvider.js";
@@ -10,4 +10,6 @@
10
10
  export { ToolProviderRegistry } from "./ToolProviderRegistry.js";
11
11
  // Concrete providers
12
12
  export { DesmosToolProvider } from "./DesmosToolProvider.js";
13
+ export { CortexToolProvider } from "./CortexToolProvider.js";
14
+ export { GeoGebraToolProvider } from "./GeoGebraToolProvider.js";
13
15
  export { TTSToolProvider } from "./TTSToolProvider.js";