@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
package/src/index.ts ADDED
@@ -0,0 +1,383 @@
1
+ /**
2
+ * @fileoverview Pack factory for the ML Classifier Guardrail Extension Pack.
3
+ *
4
+ * Exports the main `createMLClassifierPack()` factory that assembles the
5
+ * ML classifier guardrail and the `classify_content` tool into a single
6
+ * {@link ExtensionPack} ready for registration with the AgentOS extension
7
+ * manager.
8
+ *
9
+ * Also exports a `createExtensionPack()` bridge function that conforms to
10
+ * the AgentOS manifest factory convention, delegating to
11
+ * `createMLClassifierPack()` with options extracted from the
12
+ * {@link ExtensionPackContext}.
13
+ *
14
+ * ### Default behaviour (zero-config)
15
+ * When called without arguments, all three built-in classifiers (toxicity,
16
+ * prompt-injection, jailbreak) are active using their default model IDs and
17
+ * the default threshold set:
18
+ * - block at 0.90 confidence
19
+ * - flag at 0.70 confidence
20
+ * - warn (sanitize) at 0.40 confidence
21
+ *
22
+ * ### Activation lifecycle
23
+ * Components are built eagerly at pack creation time for direct programmatic
24
+ * use. When the extension manager activates the pack, `onActivate` rebuilds
25
+ * all components with the manager's shared service registry so heavyweight
26
+ * resources (ONNX/WASM model pipelines) are shared across the agent.
27
+ *
28
+ * ### Disabling classifiers
29
+ * Individual classifiers can be disabled by omitting them from the
30
+ * `options.classifiers` array. An empty array or `undefined` activates all
31
+ * three built-in classifiers.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { createMLClassifierPack } from './ml-classifiers';
36
+ *
37
+ * // All built-in classifiers at default thresholds:
38
+ * const pack = createMLClassifierPack();
39
+ *
40
+ * // Toxicity only with custom block threshold:
41
+ * const strictPack = createMLClassifierPack({
42
+ * classifiers: ['toxicity'],
43
+ * thresholds: { blockThreshold: 0.85 },
44
+ * streamingMode: true,
45
+ * guardrailScope: 'both',
46
+ * });
47
+ * ```
48
+ *
49
+ * @module agentos/extensions/packs/ml-classifiers
50
+ */
51
+
52
+ import type { ISharedServiceRegistry } from '@framers/agentos';
53
+ import { SharedServiceRegistry } from '@framers/agentos';
54
+ import type { ExtensionPack, ExtensionPackContext } from '@framers/agentos';
55
+ import type { ExtensionDescriptor, ExtensionLifecycleContext } from '@framers/agentos';
56
+ import { EXTENSION_KIND_GUARDRAIL, EXTENSION_KIND_TOOL } from '@framers/agentos';
57
+ import type { MLClassifierPackOptions } from './types';
58
+ import { DEFAULT_THRESHOLDS } from './types';
59
+ import { MLClassifierGuardrail } from './MLClassifierGuardrail';
60
+ import { ClassifierOrchestrator } from './ClassifierOrchestrator';
61
+ import { SlidingWindowBuffer } from './SlidingWindowBuffer';
62
+ import { ClassifyContentTool } from './tools/ClassifyContentTool';
63
+ import { ToxicityClassifier } from './classifiers/ToxicityClassifier';
64
+ import { InjectionClassifier } from './classifiers/InjectionClassifier';
65
+ import { JailbreakClassifier } from './classifiers/JailbreakClassifier';
66
+ import type { IContentClassifier } from './IContentClassifier';
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Re-exports — allow single-import for consumers
70
+ // ---------------------------------------------------------------------------
71
+
72
+ /**
73
+ * Re-export all types from the ML classifier type definitions so consumers
74
+ * can import everything from a single entry point:
75
+ * ```ts
76
+ * import { createMLClassifierPack, DEFAULT_THRESHOLDS } from './ml-classifiers';
77
+ * ```
78
+ */
79
+ export * from './types';
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Pack factory
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * Create an {@link ExtensionPack} that bundles:
87
+ * - The {@link MLClassifierGuardrail} guardrail (evaluates input & output).
88
+ * - The {@link ClassifyContentTool} `classify_content` tool (on-demand analysis).
89
+ *
90
+ * The built-in classifiers that are instantiated depend on `options.classifiers`:
91
+ * - `'toxicity'` → {@link ToxicityClassifier} (`unitary/toxic-bert`)
92
+ * - `'injection'` → {@link InjectionClassifier} (`protectai/deberta-v3-small-prompt-injection-v2`)
93
+ * - `'jailbreak'` → {@link JailbreakClassifier} (`meta-llama/PromptGuard-86M`)
94
+ *
95
+ * When `options.classifiers` is `undefined` or empty, **all three** are active.
96
+ *
97
+ * Additional classifiers supplied via `options.customClassifiers` are appended
98
+ * to the active list and run in parallel alongside the built-in ones.
99
+ *
100
+ * @param options - Optional pack-level configuration. All properties have
101
+ * sensible defaults; see {@link MLClassifierPackOptions}.
102
+ * @returns A fully-configured {@link ExtensionPack} with one guardrail
103
+ * descriptor and one tool descriptor.
104
+ */
105
+ export function createMLClassifierPack(options?: MLClassifierPackOptions): ExtensionPack {
106
+ /**
107
+ * Resolved options — default to empty object so every sub-check can
108
+ * safely use `opts.foo` without null-guarding the whole `options` reference.
109
+ */
110
+ const opts: MLClassifierPackOptions = options ?? {};
111
+
112
+ // -------------------------------------------------------------------------
113
+ // Mutable state — upgraded by onActivate with the extension manager's
114
+ // shared service registry.
115
+ // -------------------------------------------------------------------------
116
+
117
+ const state = {
118
+ /**
119
+ * Service registry — starts as a standalone instance so the pack can be
120
+ * used directly (without activation) in unit tests and scripts.
121
+ * Replaced with the shared registry when `onActivate` is called by the
122
+ * extension manager.
123
+ */
124
+ services: new SharedServiceRegistry() as ISharedServiceRegistry,
125
+ };
126
+
127
+ // -------------------------------------------------------------------------
128
+ // Component instances — rebuilt by buildComponents()
129
+ // -------------------------------------------------------------------------
130
+
131
+ /**
132
+ * The guardrail that evaluates user input and/or agent output streams
133
+ * against all active ML classifiers.
134
+ */
135
+ let guardrail: MLClassifierGuardrail;
136
+
137
+ /**
138
+ * The on-demand classification tool exposed to agents and workflows.
139
+ */
140
+ let tool: ClassifyContentTool;
141
+
142
+ /**
143
+ * The orchestrator that runs all active classifiers in parallel and folds
144
+ * their results into a single {@link ChunkEvaluation} via worst-wins
145
+ * aggregation.
146
+ */
147
+ let orchestrator: ClassifierOrchestrator;
148
+
149
+ /**
150
+ * The sliding-window buffer used internally by the guardrail to evaluate
151
+ * streamed output tokens incrementally.
152
+ */
153
+ let buffer: SlidingWindowBuffer;
154
+
155
+ // -------------------------------------------------------------------------
156
+ // buildComponents
157
+ // -------------------------------------------------------------------------
158
+
159
+ /**
160
+ * (Re)construct all pack components using the current `state.services`.
161
+ *
162
+ * Called once at pack creation for direct programmatic use, and again
163
+ * during `onActivate` to upgrade to the extension manager's shared
164
+ * service registry (so ONNX/WASM pipelines are shared across the agent).
165
+ *
166
+ * ### Classifier selection
167
+ * The active classifiers are determined by `opts.classifiers`:
168
+ * - `undefined` or empty → all three built-in classifiers are created.
169
+ * - Non-empty array → only the named classifiers are created.
170
+ *
171
+ * Any `opts.customClassifiers` are always appended to the list.
172
+ */
173
+ function buildComponents(): void {
174
+ // ------------------------------------------------------------------
175
+ // 1. Determine which built-in classifiers to instantiate.
176
+ // ------------------------------------------------------------------
177
+
178
+ /**
179
+ * Determine whether a given built-in classifier name is enabled.
180
+ *
181
+ * When `opts.classifiers` is undefined or an empty array every built-in
182
+ * classifier is considered enabled (zero-config default).
183
+ *
184
+ * @param name - One of `'toxicity'`, `'injection'`, or `'jailbreak'`.
185
+ * @returns `true` when the classifier should be included.
186
+ */
187
+ function isBuiltInEnabled(name: 'toxicity' | 'injection' | 'jailbreak'): boolean {
188
+ // No explicit list — enable all built-in classifiers.
189
+ if (!opts.classifiers || opts.classifiers.length === 0) {
190
+ return true;
191
+ }
192
+ return opts.classifiers.includes(name);
193
+ }
194
+
195
+ /** Array that will be populated with every active IContentClassifier. */
196
+ const activeClassifiers: IContentClassifier[] = [];
197
+
198
+ // Toxicity classifier — detects hateful, abusive, and toxic language.
199
+ if (isBuiltInEnabled('toxicity')) {
200
+ activeClassifiers.push(new ToxicityClassifier(state.services));
201
+ }
202
+
203
+ // Injection classifier — detects prompt-injection payloads.
204
+ if (isBuiltInEnabled('injection')) {
205
+ activeClassifiers.push(new InjectionClassifier(state.services));
206
+ }
207
+
208
+ // Jailbreak classifier — detects system-prompt override attempts.
209
+ if (isBuiltInEnabled('jailbreak')) {
210
+ activeClassifiers.push(new JailbreakClassifier(state.services));
211
+ }
212
+
213
+ // Append any caller-supplied custom classifiers.
214
+ if (opts.customClassifiers && opts.customClassifiers.length > 0) {
215
+ activeClassifiers.push(...opts.customClassifiers);
216
+ }
217
+
218
+ // ------------------------------------------------------------------
219
+ // 2. Resolve pack-level thresholds (merge caller overrides on top of
220
+ // the library defaults).
221
+ // ------------------------------------------------------------------
222
+
223
+ const thresholds = {
224
+ ...DEFAULT_THRESHOLDS,
225
+ ...opts.thresholds,
226
+ };
227
+
228
+ // ------------------------------------------------------------------
229
+ // 3. Build the orchestrator with the resolved classifier list and
230
+ // thresholds.
231
+ // ------------------------------------------------------------------
232
+ orchestrator = new ClassifierOrchestrator(activeClassifiers, thresholds);
233
+
234
+ // ------------------------------------------------------------------
235
+ // 4. Build the sliding-window buffer for streaming evaluation.
236
+ // ------------------------------------------------------------------
237
+ buffer = new SlidingWindowBuffer({
238
+ chunkSize: opts.chunkSize,
239
+ contextSize: opts.contextSize,
240
+ maxEvaluations: opts.maxEvaluations,
241
+ });
242
+
243
+ // ------------------------------------------------------------------
244
+ // 5. Build the guardrail, passing the shared registry and options.
245
+ // The guardrail creates its own orchestrator internally from the
246
+ // `classifiers` option — we pass the pre-built classifier instances
247
+ // via the third constructor argument.
248
+ // ------------------------------------------------------------------
249
+ guardrail = new MLClassifierGuardrail(state.services, opts, activeClassifiers);
250
+
251
+ // ------------------------------------------------------------------
252
+ // 6. Build the on-demand classification tool backed by the orchestrator.
253
+ // ------------------------------------------------------------------
254
+ tool = new ClassifyContentTool(orchestrator);
255
+ }
256
+
257
+ // Initial build — makes the pack usable immediately without activation.
258
+ buildComponents();
259
+
260
+ // -------------------------------------------------------------------------
261
+ // ExtensionPack shape
262
+ // -------------------------------------------------------------------------
263
+
264
+ return {
265
+ /** Canonical pack name used in manifests and logs. */
266
+ name: 'ml-classifiers',
267
+
268
+ /** Semantic version of this pack implementation. */
269
+ version: '1.0.0',
270
+
271
+ /**
272
+ * Descriptor getter — always returns the latest (possibly rebuilt)
273
+ * component instances. Using a getter ensures that after `onActivate`
274
+ * rebuilds the components, the descriptors array reflects the new
275
+ * references rather than stale closures from the initial build.
276
+ */
277
+ get descriptors(): ExtensionDescriptor[] {
278
+ return [
279
+ {
280
+ /**
281
+ * Guardrail descriptor.
282
+ *
283
+ * Priority 5 places this guardrail after the PII redaction guardrail
284
+ * (priority 10) so PII is stripped before ML classification.
285
+ */
286
+ id: 'ml-classifier-guardrail',
287
+ kind: EXTENSION_KIND_GUARDRAIL,
288
+ priority: 5,
289
+ payload: guardrail,
290
+ },
291
+ {
292
+ /**
293
+ * On-demand classification tool descriptor.
294
+ *
295
+ * Priority 0 uses the default ordering — tools are typically
296
+ * ordered by name rather than priority.
297
+ */
298
+ id: 'classify_content',
299
+ kind: EXTENSION_KIND_TOOL,
300
+ priority: 0,
301
+ payload: tool,
302
+ },
303
+ ];
304
+ },
305
+
306
+ /**
307
+ * Lifecycle hook called by the extension manager when the pack is
308
+ * activated.
309
+ *
310
+ * Upgrades the internal service registry to the extension manager's
311
+ * shared instance (so ONNX/WASM model weights are shared across all
312
+ * extensions) then rebuilds all components to use the new registry.
313
+ *
314
+ * @param context - Activation context provided by the extension manager.
315
+ */
316
+ onActivate: (context: ExtensionLifecycleContext): void => {
317
+ // Upgrade to the shared registry when the manager provides one.
318
+ if (context.services) {
319
+ state.services = context.services;
320
+ }
321
+
322
+ // Rebuild all components with the upgraded registry.
323
+ buildComponents();
324
+ },
325
+
326
+ /**
327
+ * Lifecycle hook called when the pack is deactivated or the agent shuts
328
+ * down.
329
+ *
330
+ * Disposes the classifier orchestrator (which releases ONNX/WASM
331
+ * resources for every registered classifier) and clears the sliding
332
+ * window buffer to release per-stream state.
333
+ */
334
+ onDeactivate: async (): Promise<void> => {
335
+ // Dispose all classifiers managed by the orchestrator.
336
+ // orchestrator may be undefined if buildComponents() was never called
337
+ // successfully (defensive guard).
338
+ if (orchestrator) {
339
+ await orchestrator.dispose();
340
+ }
341
+
342
+ // Clear any in-progress stream buffers.
343
+ if (buffer) {
344
+ buffer.clear();
345
+ }
346
+ },
347
+ };
348
+ }
349
+
350
+ // ---------------------------------------------------------------------------
351
+ // Manifest factory bridge
352
+ // ---------------------------------------------------------------------------
353
+
354
+ /**
355
+ * AgentOS manifest factory function.
356
+ *
357
+ * Conforms to the convention expected by the extension loader when resolving
358
+ * packs from manifests. Extracts `options` from the {@link ExtensionPackContext}
359
+ * and delegates to {@link createMLClassifierPack}.
360
+ *
361
+ * @param context - Manifest context containing optional pack options, secret
362
+ * resolver, and shared service registry.
363
+ * @returns A fully-configured {@link ExtensionPack}.
364
+ *
365
+ * @example Manifest entry:
366
+ * ```json
367
+ * {
368
+ * "packs": [
369
+ * {
370
+ * "module": "./ml-classifiers",
371
+ * "options": {
372
+ * "classifiers": ["toxicity", "jailbreak"],
373
+ * "thresholds": { "blockThreshold": 0.95 },
374
+ * "streamingMode": true
375
+ * }
376
+ * }
377
+ * ]
378
+ * }
379
+ * ```
380
+ */
381
+ export function createExtensionPack(context: ExtensionPackContext): ExtensionPack {
382
+ return createMLClassifierPack(context.options as MLClassifierPackOptions);
383
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * @fileoverview On-demand content classification tool for AgentOS.
3
+ *
4
+ * `ClassifyContentTool` exposes the ML classifier pipeline as an invocable
5
+ * {@link ITool}, enabling agents and workflows to explicitly classify text
6
+ * for safety signals (toxicity, prompt injection, jailbreak) on demand,
7
+ * rather than relying solely on the implicit guardrail pipeline.
8
+ *
9
+ * Use cases:
10
+ * - An agent that needs to evaluate user-generated content before storing
11
+ * it in a knowledge base.
12
+ * - A moderation workflow that classifies a batch of flagged messages.
13
+ * - A debugging tool for inspecting classifier behaviour on specific inputs.
14
+ *
15
+ * The tool delegates to a {@link ClassifierOrchestrator} instance and returns
16
+ * the full {@link ChunkEvaluation} (including per-classifier scores and the
17
+ * aggregated recommended action).
18
+ *
19
+ * @module agentos/extensions/packs/ml-classifiers/tools/ClassifyContentTool
20
+ */
21
+
22
+ import type {
23
+ ITool,
24
+ JSONSchemaObject,
25
+ ToolExecutionContext,
26
+ ToolExecutionResult,
27
+ } from '@framers/agentos';
28
+ import type { ChunkEvaluation } from '../types';
29
+ import type { ClassifierOrchestrator } from '../ClassifierOrchestrator';
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Input shape
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Input arguments for the `classify_content` tool.
37
+ */
38
+ export interface ClassifyInput {
39
+ /**
40
+ * The text to classify for safety signals.
41
+ * Must not be empty.
42
+ */
43
+ text: string;
44
+
45
+ /**
46
+ * Optional subset of classifier IDs to run.
47
+ * When omitted, all registered classifiers are invoked.
48
+ */
49
+ classifiers?: string[];
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // ClassifyContentTool
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * ITool implementation that runs ML content classifiers on demand.
58
+ *
59
+ * The tool is read-only (`hasSideEffects: false`) — it inspects text and
60
+ * returns structured classification results without modifying any state.
61
+ *
62
+ * @implements {ITool<ClassifyInput, ChunkEvaluation>}
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const tool = new ClassifyContentTool(orchestrator);
67
+ * const result = await tool.execute(
68
+ * { text: 'some potentially harmful text' },
69
+ * executionContext,
70
+ * );
71
+ *
72
+ * if (result.success) {
73
+ * console.log(result.output.recommendedAction); // 'allow' | 'flag' | 'block' | …
74
+ * }
75
+ * ```
76
+ */
77
+ export class ClassifyContentTool implements ITool<ClassifyInput, ChunkEvaluation> {
78
+ // -------------------------------------------------------------------------
79
+ // ITool identity & metadata
80
+ // -------------------------------------------------------------------------
81
+
82
+ /** Unique tool identifier used for registration and lookup. */
83
+ readonly id = 'classify_content';
84
+
85
+ /** Functional name exposed to LLMs for tool-call invocation. */
86
+ readonly name = 'classify_content';
87
+
88
+ /** Human-readable display name for dashboards and UI. */
89
+ readonly displayName = 'Content Safety Classifier';
90
+
91
+ /** Natural-language description of the tool's purpose and behaviour. */
92
+ readonly description =
93
+ 'Classify text for toxicity, prompt injection, and jailbreak attempts ' +
94
+ 'using ML models. Returns per-classifier scores and an aggregated ' +
95
+ 'recommended guardrail action.';
96
+
97
+ /** Logical grouping for tool discovery and filtering. */
98
+ readonly category = 'security';
99
+
100
+ /** SemVer version of this tool implementation. */
101
+ readonly version = '1.0.0';
102
+
103
+ /** This tool only reads text — it performs no mutations. */
104
+ readonly hasSideEffects = false;
105
+
106
+ // -------------------------------------------------------------------------
107
+ // JSON Schema for input validation
108
+ // -------------------------------------------------------------------------
109
+
110
+ /**
111
+ * JSON Schema describing the expected input arguments.
112
+ *
113
+ * - `text` (required): The string to classify.
114
+ * - `classifiers` (optional): Array of classifier IDs to restrict evaluation.
115
+ */
116
+ readonly inputSchema: JSONSchemaObject = {
117
+ type: 'object',
118
+ properties: {
119
+ text: {
120
+ type: 'string',
121
+ description: 'Text to classify for safety signals.',
122
+ },
123
+ classifiers: {
124
+ type: 'array',
125
+ items: { type: 'string' },
126
+ description:
127
+ 'Optional: only run these classifier IDs. When omitted all registered classifiers are used.',
128
+ },
129
+ },
130
+ required: ['text'],
131
+ };
132
+
133
+ // -------------------------------------------------------------------------
134
+ // Internal state
135
+ // -------------------------------------------------------------------------
136
+
137
+ /** The orchestrator that drives the underlying ML classifiers. */
138
+ private readonly orchestrator: ClassifierOrchestrator;
139
+
140
+ // -------------------------------------------------------------------------
141
+ // Constructor
142
+ // -------------------------------------------------------------------------
143
+
144
+ /**
145
+ * Create a new ClassifyContentTool.
146
+ *
147
+ * @param orchestrator - The classifier orchestrator that will handle
148
+ * parallel classification and result aggregation.
149
+ */
150
+ constructor(orchestrator: ClassifierOrchestrator) {
151
+ this.orchestrator = orchestrator;
152
+ }
153
+
154
+ // -------------------------------------------------------------------------
155
+ // execute
156
+ // -------------------------------------------------------------------------
157
+
158
+ /**
159
+ * Run all (or a subset of) ML classifiers against the provided text and
160
+ * return the aggregated evaluation.
161
+ *
162
+ * @param args - Tool input containing the text to classify and an
163
+ * optional list of classifier IDs to restrict execution.
164
+ * @param _context - Execution context (unused — classification is
165
+ * stateless and user-agnostic).
166
+ * @returns A successful result containing the {@link ChunkEvaluation},
167
+ * or a failure result if the text is missing or classification
168
+ * throws an unexpected error.
169
+ */
170
+ async execute(
171
+ args: ClassifyInput,
172
+ _context: ToolExecutionContext,
173
+ ): Promise<ToolExecutionResult<ChunkEvaluation>> {
174
+ // Validate that text is provided and non-empty.
175
+ if (!args.text || args.text.trim().length === 0) {
176
+ return {
177
+ success: false,
178
+ error: 'The "text" argument is required and must not be empty.',
179
+ };
180
+ }
181
+
182
+ try {
183
+ // Delegate to the orchestrator for parallel classification.
184
+ // NOTE: The `args.classifiers` filter is not yet implemented in the
185
+ // orchestrator — it would require a filtering layer. For now, all
186
+ // registered classifiers are invoked regardless.
187
+ const evaluation = await this.orchestrator.classifyAll(args.text);
188
+
189
+ return {
190
+ success: true,
191
+ output: evaluation,
192
+ };
193
+ } catch (err: unknown) {
194
+ const message = err instanceof Error ? err.message : String(err);
195
+ return {
196
+ success: false,
197
+ error: `Classification failed: ${message}`,
198
+ };
199
+ }
200
+ }
201
+ }