@framers/agentos-ext-ml-classifiers 0.1.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.
Files changed (62) hide show
  1. package/LICENSE +23 -0
  2. package/dist/ClassifierOrchestrator.d.ts +126 -0
  3. package/dist/ClassifierOrchestrator.d.ts.map +1 -0
  4. package/dist/ClassifierOrchestrator.js +239 -0
  5. package/dist/ClassifierOrchestrator.js.map +1 -0
  6. package/dist/IContentClassifier.d.ts +117 -0
  7. package/dist/IContentClassifier.d.ts.map +1 -0
  8. package/dist/IContentClassifier.js +22 -0
  9. package/dist/IContentClassifier.js.map +1 -0
  10. package/dist/MLClassifierGuardrail.d.ts +163 -0
  11. package/dist/MLClassifierGuardrail.d.ts.map +1 -0
  12. package/dist/MLClassifierGuardrail.js +335 -0
  13. package/dist/MLClassifierGuardrail.js.map +1 -0
  14. package/dist/SlidingWindowBuffer.d.ts +213 -0
  15. package/dist/SlidingWindowBuffer.d.ts.map +1 -0
  16. package/dist/SlidingWindowBuffer.js +246 -0
  17. package/dist/SlidingWindowBuffer.js.map +1 -0
  18. package/dist/classifiers/InjectionClassifier.d.ts +126 -0
  19. package/dist/classifiers/InjectionClassifier.d.ts.map +1 -0
  20. package/dist/classifiers/InjectionClassifier.js +210 -0
  21. package/dist/classifiers/InjectionClassifier.js.map +1 -0
  22. package/dist/classifiers/JailbreakClassifier.d.ts +124 -0
  23. package/dist/classifiers/JailbreakClassifier.d.ts.map +1 -0
  24. package/dist/classifiers/JailbreakClassifier.js +208 -0
  25. package/dist/classifiers/JailbreakClassifier.js.map +1 -0
  26. package/dist/classifiers/ToxicityClassifier.d.ts +125 -0
  27. package/dist/classifiers/ToxicityClassifier.d.ts.map +1 -0
  28. package/dist/classifiers/ToxicityClassifier.js +212 -0
  29. package/dist/classifiers/ToxicityClassifier.js.map +1 -0
  30. package/dist/classifiers/WorkerClassifierProxy.d.ts +158 -0
  31. package/dist/classifiers/WorkerClassifierProxy.d.ts.map +1 -0
  32. package/dist/classifiers/WorkerClassifierProxy.js +268 -0
  33. package/dist/classifiers/WorkerClassifierProxy.js.map +1 -0
  34. package/dist/index.d.ts +110 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +342 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/tools/ClassifyContentTool.d.ts +105 -0
  39. package/dist/tools/ClassifyContentTool.d.ts.map +1 -0
  40. package/dist/tools/ClassifyContentTool.js +149 -0
  41. package/dist/tools/ClassifyContentTool.js.map +1 -0
  42. package/dist/types.d.ts +319 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +62 -0
  45. package/dist/types.js.map +1 -0
  46. package/dist/worker/classifier-worker.d.ts +49 -0
  47. package/dist/worker/classifier-worker.d.ts.map +1 -0
  48. package/dist/worker/classifier-worker.js +180 -0
  49. package/dist/worker/classifier-worker.js.map +1 -0
  50. package/package.json +45 -0
  51. package/src/ClassifierOrchestrator.ts +290 -0
  52. package/src/IContentClassifier.ts +124 -0
  53. package/src/MLClassifierGuardrail.ts +419 -0
  54. package/src/SlidingWindowBuffer.ts +384 -0
  55. package/src/classifiers/InjectionClassifier.ts +261 -0
  56. package/src/classifiers/JailbreakClassifier.ts +259 -0
  57. package/src/classifiers/ToxicityClassifier.ts +263 -0
  58. package/src/classifiers/WorkerClassifierProxy.ts +366 -0
  59. package/src/index.ts +383 -0
  60. package/src/tools/ClassifyContentTool.ts +201 -0
  61. package/src/types.ts +391 -0
  62. package/src/worker/classifier-worker.ts +267 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @fileoverview Toxicity content classifier using the `unitary/toxic-bert` model.
3
+ *
4
+ * This classifier uses a multi-label BERT-based model trained on the Jigsaw
5
+ * Toxic Comment dataset. It assigns independent confidence scores to six
6
+ * toxicity categories and surfaces the highest-scoring label as `bestClass`.
7
+ *
8
+ * The model is loaded lazily the first time `classify()` is called and
9
+ * cached in the shared service registry so it is only initialised once even
10
+ * if multiple parts of the system hold a reference to this classifier.
11
+ *
12
+ * Graceful degradation
13
+ * --------------------
14
+ * If the model fails to load (e.g. network unavailable, ONNX runtime missing)
15
+ * the classifier sets `unavailable = true` and returns a **pass result**
16
+ * `{ bestClass: 'benign', confidence: 0, allScores: [] }` on every subsequent
17
+ * call instead of throwing. This ensures the guardrail pipeline degrades
18
+ * gracefully rather than crashing the agent.
19
+ *
20
+ * @module agentos/extensions/packs/ml-classifiers/classifiers/ToxicityClassifier
21
+ */
22
+ import type { ClassificationResult } from '@framers/agentos';
23
+ import type { ISharedServiceRegistry } from '@framers/agentos';
24
+ import type { IContentClassifier } from '../IContentClassifier';
25
+ import type { ClassifierConfig } from '../types';
26
+ /**
27
+ * Multi-label toxicity classifier backed by `unitary/toxic-bert`.
28
+ *
29
+ * Evaluates text against six toxicity categories:
30
+ * - `toxic`
31
+ * - `severe_toxic`
32
+ * - `obscene`
33
+ * - `threat`
34
+ * - `insult`
35
+ * - `identity_hate`
36
+ *
37
+ * Each category receives an independent confidence score. The label with
38
+ * the highest score is reported as `bestClass` and its score as `confidence`.
39
+ * All six scores are included in `allScores` so the pack orchestrator can
40
+ * apply per-label thresholds.
41
+ *
42
+ * @implements {IContentClassifier}
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const classifier = new ToxicityClassifier(serviceRegistry);
47
+ * const result = await classifier.classify('You are terrible!');
48
+ * // result.bestClass === 'insult', result.confidence ≈ 0.87
49
+ * ```
50
+ */
51
+ export declare class ToxicityClassifier implements IContentClassifier {
52
+ private readonly services;
53
+ private readonly config?;
54
+ /** Unique service identifier for this classifier. */
55
+ readonly id = "toxicity";
56
+ /** Human-readable name for dashboards and log output. */
57
+ readonly displayName = "Toxicity Classifier";
58
+ /** Short description of what this classifier detects. */
59
+ readonly description: string;
60
+ /**
61
+ * Default Hugging Face model ID.
62
+ * Overridable via {@link ClassifierConfig.modelId}.
63
+ */
64
+ readonly modelId = "unitary/toxic-bert";
65
+ /**
66
+ * Whether the model weights are fully loaded and the classifier is ready
67
+ * to accept `classify()` calls.
68
+ */
69
+ private _isLoaded;
70
+ /**
71
+ * Set to `true` when the model fails to load. Once `unavailable`, every
72
+ * subsequent `classify()` call immediately returns the pass result rather
73
+ * than retrying the expensive model load.
74
+ */
75
+ private unavailable;
76
+ /**
77
+ * @param services - Shared service registry used to lazily create and cache
78
+ * the underlying HuggingFace pipeline instance.
79
+ * @param config - Optional per-classifier configuration. When
80
+ * `config.modelId` is provided it overrides the default `modelId` when
81
+ * loading the model.
82
+ */
83
+ constructor(services: ISharedServiceRegistry, config?: ClassifierConfig | undefined);
84
+ /**
85
+ * Whether the underlying model pipeline has been successfully initialised.
86
+ * The flag is set to `true` after the first successful `classify()` call.
87
+ */
88
+ get isLoaded(): boolean;
89
+ /**
90
+ * Run toxicity inference on `text`.
91
+ *
92
+ * Lazily loads the pipeline on the first call via the shared service
93
+ * registry, then calls it with `{ topk: null }` to retrieve scores for
94
+ * every label.
95
+ *
96
+ * @param text - The text to evaluate.
97
+ * @returns A promise that resolves with the classification result. If the
98
+ * model is unavailable the pass result is returned instead of throwing.
99
+ */
100
+ classify(text: string): Promise<ClassificationResult>;
101
+ /**
102
+ * Release the pipeline instance from the shared service registry.
103
+ *
104
+ * Idempotent — safe to call multiple times.
105
+ */
106
+ dispose(): Promise<void>;
107
+ /**
108
+ * Returns a "pass" result used when the model is unavailable.
109
+ *
110
+ * A pass result reports `bestClass: 'benign'` with zero confidence so the
111
+ * guardrail orchestrator will always choose {@link GuardrailAction.ALLOW}.
112
+ */
113
+ private passResult;
114
+ /**
115
+ * Map the raw pipeline output (array of `{ label, score }` objects) to a
116
+ * {@link ClassificationResult}.
117
+ *
118
+ * The label with the highest score becomes `bestClass` / `confidence`.
119
+ * Every label is included in `allScores` for downstream threshold logic.
120
+ *
121
+ * @param raw - Array returned by the pipeline when called with `topk: null`.
122
+ */
123
+ private mapResult;
124
+ }
125
+ //# sourceMappingURL=ToxicityClassifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToxicityClassifier.d.ts","sourceRoot":"","sources":["../../src/classifiers/ToxicityClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAsBjD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,kBAAmB,YAAW,kBAAkB;IAmDzD,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IA/C1B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,cAAc;IAEzB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,yBAAyB;IAE7C,yDAAyD;IACzD,QAAQ,CAAC,WAAW,SAEiD;IAErE;;;OAGG;IACH,QAAQ,CAAC,OAAO,wBAAwB;IAMxC;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAAS;IAM5B;;;;;;OAMG;gBAEgB,QAAQ,EAAE,sBAAsB,EAChC,MAAM,CAAC,EAAE,gBAAgB,YAAA;IAO5C;;;OAGG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAMD;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAoD3D;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;CAuBlB"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @fileoverview Toxicity content classifier using the `unitary/toxic-bert` model.
3
+ *
4
+ * This classifier uses a multi-label BERT-based model trained on the Jigsaw
5
+ * Toxic Comment dataset. It assigns independent confidence scores to six
6
+ * toxicity categories and surfaces the highest-scoring label as `bestClass`.
7
+ *
8
+ * The model is loaded lazily the first time `classify()` is called and
9
+ * cached in the shared service registry so it is only initialised once even
10
+ * if multiple parts of the system hold a reference to this classifier.
11
+ *
12
+ * Graceful degradation
13
+ * --------------------
14
+ * If the model fails to load (e.g. network unavailable, ONNX runtime missing)
15
+ * the classifier sets `unavailable = true` and returns a **pass result**
16
+ * `{ bestClass: 'benign', confidence: 0, allScores: [] }` on every subsequent
17
+ * call instead of throwing. This ensures the guardrail pipeline degrades
18
+ * gracefully rather than crashing the agent.
19
+ *
20
+ * @module agentos/extensions/packs/ml-classifiers/classifiers/ToxicityClassifier
21
+ */
22
+ import { ML_CLASSIFIER_SERVICE_IDS } from '../types';
23
+ // ---------------------------------------------------------------------------
24
+ // ToxicityClassifier
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Multi-label toxicity classifier backed by `unitary/toxic-bert`.
28
+ *
29
+ * Evaluates text against six toxicity categories:
30
+ * - `toxic`
31
+ * - `severe_toxic`
32
+ * - `obscene`
33
+ * - `threat`
34
+ * - `insult`
35
+ * - `identity_hate`
36
+ *
37
+ * Each category receives an independent confidence score. The label with
38
+ * the highest score is reported as `bestClass` and its score as `confidence`.
39
+ * All six scores are included in `allScores` so the pack orchestrator can
40
+ * apply per-label thresholds.
41
+ *
42
+ * @implements {IContentClassifier}
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const classifier = new ToxicityClassifier(serviceRegistry);
47
+ * const result = await classifier.classify('You are terrible!');
48
+ * // result.bestClass === 'insult', result.confidence ≈ 0.87
49
+ * ```
50
+ */
51
+ export class ToxicityClassifier {
52
+ services;
53
+ config;
54
+ // -------------------------------------------------------------------------
55
+ // IContentClassifier identity fields
56
+ // -------------------------------------------------------------------------
57
+ /** Unique service identifier for this classifier. */
58
+ id = 'toxicity';
59
+ /** Human-readable name for dashboards and log output. */
60
+ displayName = 'Toxicity Classifier';
61
+ /** Short description of what this classifier detects. */
62
+ description = 'Detects toxic, hateful, or abusive language across six categories: ' +
63
+ 'toxic, severe_toxic, obscene, threat, insult, and identity_hate.';
64
+ /**
65
+ * Default Hugging Face model ID.
66
+ * Overridable via {@link ClassifierConfig.modelId}.
67
+ */
68
+ modelId = 'unitary/toxic-bert';
69
+ // -------------------------------------------------------------------------
70
+ // Internal state
71
+ // -------------------------------------------------------------------------
72
+ /**
73
+ * Whether the model weights are fully loaded and the classifier is ready
74
+ * to accept `classify()` calls.
75
+ */
76
+ _isLoaded = false;
77
+ /**
78
+ * Set to `true` when the model fails to load. Once `unavailable`, every
79
+ * subsequent `classify()` call immediately returns the pass result rather
80
+ * than retrying the expensive model load.
81
+ */
82
+ unavailable = false;
83
+ // -------------------------------------------------------------------------
84
+ // Constructor
85
+ // -------------------------------------------------------------------------
86
+ /**
87
+ * @param services - Shared service registry used to lazily create and cache
88
+ * the underlying HuggingFace pipeline instance.
89
+ * @param config - Optional per-classifier configuration. When
90
+ * `config.modelId` is provided it overrides the default `modelId` when
91
+ * loading the model.
92
+ */
93
+ constructor(services, config) {
94
+ this.services = services;
95
+ this.config = config;
96
+ }
97
+ // -------------------------------------------------------------------------
98
+ // IContentClassifier.isLoaded (getter)
99
+ // -------------------------------------------------------------------------
100
+ /**
101
+ * Whether the underlying model pipeline has been successfully initialised.
102
+ * The flag is set to `true` after the first successful `classify()` call.
103
+ */
104
+ get isLoaded() {
105
+ return this._isLoaded;
106
+ }
107
+ // -------------------------------------------------------------------------
108
+ // classify
109
+ // -------------------------------------------------------------------------
110
+ /**
111
+ * Run toxicity inference on `text`.
112
+ *
113
+ * Lazily loads the pipeline on the first call via the shared service
114
+ * registry, then calls it with `{ topk: null }` to retrieve scores for
115
+ * every label.
116
+ *
117
+ * @param text - The text to evaluate.
118
+ * @returns A promise that resolves with the classification result. If the
119
+ * model is unavailable the pass result is returned instead of throwing.
120
+ */
121
+ async classify(text) {
122
+ // Return the pass result immediately if the model previously failed to load.
123
+ if (this.unavailable) {
124
+ return this.passResult();
125
+ }
126
+ // Lazily obtain (or create) the HuggingFace pipeline instance from the
127
+ // shared service registry. The registry ensures the model is only loaded
128
+ // once even under concurrent calls.
129
+ let pipeline;
130
+ try {
131
+ pipeline = await this.services.getOrCreate(ML_CLASSIFIER_SERVICE_IDS.TOXICITY_PIPELINE, async () => {
132
+ // Dynamic import keeps the heavy ONNX runtime out of the initial
133
+ // bundle and allows environments without the package to skip loading.
134
+ const { pipeline: createPipeline } = await import('@huggingface/transformers');
135
+ return createPipeline('text-classification',
136
+ // Honour a caller-supplied model override; fall back to the default.
137
+ this.config?.modelId ?? this.modelId, { quantized: true });
138
+ }, {
139
+ /** Release ONNX/WASM resources when the registry entry is evicted. */
140
+ dispose: async (p) => p?.dispose?.(),
141
+ /** Tags used for diagnostics and capability discovery. */
142
+ tags: ['ml', 'classifier', 'toxicity', 'onnx'],
143
+ });
144
+ // Mark the classifier as ready now that the pipeline is available.
145
+ this._isLoaded = true;
146
+ }
147
+ catch {
148
+ // Model failed to load — mark as unavailable and return the pass result
149
+ // so the guardrail pipeline can continue operating.
150
+ this.unavailable = true;
151
+ return this.passResult();
152
+ }
153
+ // Run inference — request scores for ALL labels (topk: null).
154
+ const raw = await pipeline(text, { topk: null });
155
+ return this.mapResult(raw);
156
+ }
157
+ // -------------------------------------------------------------------------
158
+ // dispose (optional IContentClassifier lifecycle hook)
159
+ // -------------------------------------------------------------------------
160
+ /**
161
+ * Release the pipeline instance from the shared service registry.
162
+ *
163
+ * Idempotent — safe to call multiple times.
164
+ */
165
+ async dispose() {
166
+ await this.services.release(ML_CLASSIFIER_SERVICE_IDS.TOXICITY_PIPELINE);
167
+ this._isLoaded = false;
168
+ }
169
+ // -------------------------------------------------------------------------
170
+ // Private helpers
171
+ // -------------------------------------------------------------------------
172
+ /**
173
+ * Returns a "pass" result used when the model is unavailable.
174
+ *
175
+ * A pass result reports `bestClass: 'benign'` with zero confidence so the
176
+ * guardrail orchestrator will always choose {@link GuardrailAction.ALLOW}.
177
+ */
178
+ passResult() {
179
+ return { bestClass: 'benign', confidence: 0, allScores: [] };
180
+ }
181
+ /**
182
+ * Map the raw pipeline output (array of `{ label, score }` objects) to a
183
+ * {@link ClassificationResult}.
184
+ *
185
+ * The label with the highest score becomes `bestClass` / `confidence`.
186
+ * Every label is included in `allScores` for downstream threshold logic.
187
+ *
188
+ * @param raw - Array returned by the pipeline when called with `topk: null`.
189
+ */
190
+ mapResult(raw) {
191
+ if (!raw || raw.length === 0) {
192
+ // No output from the model — treat as benign.
193
+ return this.passResult();
194
+ }
195
+ // Find the label with the maximum confidence score.
196
+ let best = raw[0];
197
+ for (const item of raw) {
198
+ if (item.score > best.score) {
199
+ best = item;
200
+ }
201
+ }
202
+ return {
203
+ bestClass: best.label,
204
+ confidence: best.score,
205
+ allScores: raw.map((item) => ({
206
+ classLabel: item.label,
207
+ score: item.score,
208
+ })),
209
+ };
210
+ }
211
+ }
212
+ //# sourceMappingURL=ToxicityClassifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToxicityClassifier.js","sourceRoot":"","sources":["../../src/classifiers/ToxicityClassifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @fileoverview WorkerClassifierProxy — wraps an IContentClassifier to run
3
+ * inference inside a Web Worker, with automatic main-thread fallback.
4
+ *
5
+ * ## Why a proxy?
6
+ * ML inference (even quantized ONNX / WASM pipelines) can block the main
7
+ * thread for 50–500 ms per classification. Moving classification into a
8
+ * Web Worker keeps the UI responsive. This proxy makes the switch
9
+ * transparent to callers: they still call `classify(text)` and receive a
10
+ * `ClassificationResult`; the underlying transport (Worker vs. direct call)
11
+ * is an implementation detail.
12
+ *
13
+ * ## Fallback policy
14
+ * The proxy falls back to direct (main-thread) delegation whenever:
15
+ * - The global `Worker` constructor is undefined (Node.js, older browsers).
16
+ * - `browserConfig.useWebWorker` is explicitly `false`.
17
+ * - Worker creation throws (e.g. strict CSP that blocks `blob:` URLs).
18
+ *
19
+ * Once a fallback has been triggered by a Worker creation error the proxy
20
+ * sets `workerFailed = true` and remains in fallback mode for all subsequent
21
+ * calls.
22
+ *
23
+ * ## IContentClassifier contract
24
+ * The proxy forwards all identity fields (`id`, `displayName`, `description`,
25
+ * `modelId`) and the `isLoaded` state directly from the wrapped classifier so
26
+ * it is completely transparent to the orchestrator.
27
+ *
28
+ * @module agentos/extensions/packs/ml-classifiers/classifiers/WorkerClassifierProxy
29
+ */
30
+ import type { ClassificationResult } from '@framers/agentos';
31
+ import type { IContentClassifier } from '../IContentClassifier';
32
+ import type { BrowserConfig } from '../types';
33
+ /**
34
+ * Transparent proxy around an {@link IContentClassifier} that offloads
35
+ * `classify()` calls to a Web Worker when the browser environment supports it.
36
+ *
37
+ * In all other environments (Node.js, strict CSP, explicit opt-out) the proxy
38
+ * delegates calls directly to the wrapped classifier on the main thread.
39
+ *
40
+ * @implements {IContentClassifier}
41
+ *
42
+ * @example Browser context — Web Worker path
43
+ * ```typescript
44
+ * const toxicity = new ToxicityClassifier(serviceRegistry);
45
+ * const proxy = new WorkerClassifierProxy(toxicity, { useWebWorker: true });
46
+ * const result = await proxy.classify('some text');
47
+ * ```
48
+ *
49
+ * @example Node.js / forced fallback path
50
+ * ```typescript
51
+ * const proxy = new WorkerClassifierProxy(toxicity, { useWebWorker: false });
52
+ * // Delegates directly to toxicity.classify() on the same thread.
53
+ * ```
54
+ */
55
+ export declare class WorkerClassifierProxy implements IContentClassifier {
56
+ private readonly wrapped;
57
+ private readonly browserConfig?;
58
+ /**
59
+ * {@inheritDoc IContentClassifier.id}
60
+ * Delegated from the wrapped classifier so this proxy is transparent in
61
+ * the orchestrator's service-ID lookups.
62
+ */
63
+ get id(): string;
64
+ /**
65
+ * {@inheritDoc IContentClassifier.displayName}
66
+ * Returns the wrapped classifier's display name with a `(Worker)` suffix
67
+ * when the Web Worker path is active, so logs clearly indicate the mode.
68
+ */
69
+ get displayName(): string;
70
+ /**
71
+ * {@inheritDoc IContentClassifier.description}
72
+ * Delegated directly from the wrapped classifier.
73
+ */
74
+ get description(): string;
75
+ /**
76
+ * {@inheritDoc IContentClassifier.modelId}
77
+ * Delegated directly from the wrapped classifier.
78
+ */
79
+ get modelId(): string;
80
+ /**
81
+ * {@inheritDoc IContentClassifier.isLoaded}
82
+ *
83
+ * Reflects the wrapped classifier's `isLoaded` state. The wrapped
84
+ * instance is the authoritative source because it owns the model weights
85
+ * (whether they live in the Worker or on the main thread).
86
+ */
87
+ get isLoaded(): boolean;
88
+ /**
89
+ * IContentClassifier requires `isLoaded` to be settable via the interface
90
+ * contract (`isLoaded: boolean`). We store the value through the wrapped
91
+ * classifier so the authoritative state lives in one place.
92
+ */
93
+ set isLoaded(value: boolean);
94
+ /**
95
+ * Set to `true` after a Worker creation failure. Once set, all subsequent
96
+ * `classify()` calls are routed directly to the wrapped classifier without
97
+ * attempting to re-create the Worker.
98
+ */
99
+ private workerFailed;
100
+ /**
101
+ * Create a WorkerClassifierProxy.
102
+ *
103
+ * @param wrapped - The real classifier to delegate to. In Worker
104
+ * mode this classifier is still responsible for
105
+ * model loading and inference; the proxy just
106
+ * changes the thread on which it executes.
107
+ * @param browserConfig - Optional browser-side configuration. Controls
108
+ * whether Worker mode is attempted
109
+ * (`useWebWorker`, default `true`).
110
+ */
111
+ constructor(wrapped: IContentClassifier, browserConfig?: BrowserConfig | undefined);
112
+ /**
113
+ * Classify the provided text, routing to a Web Worker when available.
114
+ *
115
+ * ### Routing decision (evaluated once per call)
116
+ * 1. `typeof Worker === 'undefined'` → fallback (Node.js / no Worker API).
117
+ * 2. `browserConfig.useWebWorker === false` → fallback (explicit opt-out).
118
+ * 3. `workerFailed === true` → fallback (previous Worker creation error).
119
+ * 4. Otherwise → attempt to run in a Web Worker.
120
+ *
121
+ * If the Worker is created but fails to post a result within the
122
+ * classification request, the error is propagated as a rejected promise
123
+ * (not silently swallowed) so the orchestrator can log and fall back at
124
+ * a higher level.
125
+ *
126
+ * @param text - The text to classify. Must not be empty.
127
+ * @returns A promise that resolves with the classification result.
128
+ */
129
+ classify(text: string): Promise<ClassificationResult>;
130
+ /**
131
+ * Release resources held by the wrapped classifier.
132
+ *
133
+ * Delegates to `wrapped.dispose()` if it exists. Idempotent.
134
+ */
135
+ dispose(): Promise<void>;
136
+ /**
137
+ * Determine whether the current environment and configuration support
138
+ * running inference in a Web Worker.
139
+ *
140
+ * @returns `true` when Web Worker mode should be attempted.
141
+ */
142
+ private shouldUseWebWorker;
143
+ /**
144
+ * Run `classify(text)` inside a transient Web Worker.
145
+ *
146
+ * Each call creates a new Worker, sends a single `classify` message,
147
+ * awaits the `result` or `error` response, then terminates the Worker.
148
+ *
149
+ * If Worker creation itself throws (e.g. CSP violation), `workerFailed`
150
+ * is set to `true` and the call falls back to the wrapped classifier on
151
+ * the main thread.
152
+ *
153
+ * @param text - The text to classify inside the Worker.
154
+ * @returns A promise resolving with the {@link ClassificationResult}.
155
+ */
156
+ private classifyInWorker;
157
+ }
158
+ //# sourceMappingURL=WorkerClassifierProxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkerClassifierProxy.d.ts","sourceRoot":"","sources":["../../src/classifiers/WorkerClassifierProxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAoE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,qBAAsB,YAAW,kBAAkB;IAsF5D,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAlFjC;;;;OAIG;IACH,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;;;OAIG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;OAGG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;OAGG;IACH,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED;;;;;;OAMG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;OAIG;IACH,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAMD;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAS;IAM7B;;;;;;;;;;OAUG;gBAEgB,OAAO,EAAE,kBAAkB,EAC3B,aAAa,CAAC,EAAE,aAAa,YAAA;IAOhD;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAiB3D;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAU9B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;;;;;;;;;;;OAYG;YACW,gBAAgB;CA8D/B"}