@framers/agentos-ext-ml-classifiers 0.1.0 → 0.3.1

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 (83) hide show
  1. package/.github/workflows/ci.yml +20 -0
  2. package/.github/workflows/release.yml +37 -0
  3. package/.releaserc.json +9 -0
  4. package/LICENSE +96 -21
  5. package/README.md +72 -0
  6. package/dist/MLClassifierGuardrail.d.ts +88 -117
  7. package/dist/MLClassifierGuardrail.d.ts.map +1 -1
  8. package/dist/MLClassifierGuardrail.js +263 -264
  9. package/dist/MLClassifierGuardrail.js.map +1 -1
  10. package/dist/index.d.ts +16 -90
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +36 -309
  13. package/dist/index.js.map +1 -1
  14. package/dist/keyword-classifier.d.ts +26 -0
  15. package/dist/keyword-classifier.d.ts.map +1 -0
  16. package/dist/keyword-classifier.js +113 -0
  17. package/dist/keyword-classifier.js.map +1 -0
  18. package/dist/llm-classifier.d.ts +27 -0
  19. package/dist/llm-classifier.d.ts.map +1 -0
  20. package/dist/llm-classifier.js +129 -0
  21. package/dist/llm-classifier.js.map +1 -0
  22. package/dist/tools/ClassifyContentTool.d.ts +53 -80
  23. package/dist/tools/ClassifyContentTool.d.ts.map +1 -1
  24. package/dist/tools/ClassifyContentTool.js +52 -103
  25. package/dist/tools/ClassifyContentTool.js.map +1 -1
  26. package/dist/types.d.ts +77 -277
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/types.js +9 -55
  29. package/dist/types.js.map +1 -1
  30. package/package.json +10 -24
  31. package/scripts/fix-esm-imports.mjs +181 -0
  32. package/src/MLClassifierGuardrail.ts +306 -310
  33. package/src/index.ts +35 -339
  34. package/src/keyword-classifier.ts +130 -0
  35. package/src/llm-classifier.ts +163 -0
  36. package/src/tools/ClassifyContentTool.ts +75 -132
  37. package/src/types.ts +78 -325
  38. package/test/llm-tier.spec.ts +267 -0
  39. package/test/ml-classifiers.spec.ts +57 -0
  40. package/test/onnx-tier.spec.ts +255 -0
  41. package/test/tier-fallthrough.spec.ts +185 -0
  42. package/tsconfig.json +20 -0
  43. package/vitest.config.ts +35 -0
  44. package/dist/ClassifierOrchestrator.d.ts +0 -126
  45. package/dist/ClassifierOrchestrator.d.ts.map +0 -1
  46. package/dist/ClassifierOrchestrator.js +0 -239
  47. package/dist/ClassifierOrchestrator.js.map +0 -1
  48. package/dist/IContentClassifier.d.ts +0 -117
  49. package/dist/IContentClassifier.d.ts.map +0 -1
  50. package/dist/IContentClassifier.js +0 -22
  51. package/dist/IContentClassifier.js.map +0 -1
  52. package/dist/SlidingWindowBuffer.d.ts +0 -213
  53. package/dist/SlidingWindowBuffer.d.ts.map +0 -1
  54. package/dist/SlidingWindowBuffer.js +0 -246
  55. package/dist/SlidingWindowBuffer.js.map +0 -1
  56. package/dist/classifiers/InjectionClassifier.d.ts +0 -126
  57. package/dist/classifiers/InjectionClassifier.d.ts.map +0 -1
  58. package/dist/classifiers/InjectionClassifier.js +0 -210
  59. package/dist/classifiers/InjectionClassifier.js.map +0 -1
  60. package/dist/classifiers/JailbreakClassifier.d.ts +0 -124
  61. package/dist/classifiers/JailbreakClassifier.d.ts.map +0 -1
  62. package/dist/classifiers/JailbreakClassifier.js +0 -208
  63. package/dist/classifiers/JailbreakClassifier.js.map +0 -1
  64. package/dist/classifiers/ToxicityClassifier.d.ts +0 -125
  65. package/dist/classifiers/ToxicityClassifier.d.ts.map +0 -1
  66. package/dist/classifiers/ToxicityClassifier.js +0 -212
  67. package/dist/classifiers/ToxicityClassifier.js.map +0 -1
  68. package/dist/classifiers/WorkerClassifierProxy.d.ts +0 -158
  69. package/dist/classifiers/WorkerClassifierProxy.d.ts.map +0 -1
  70. package/dist/classifiers/WorkerClassifierProxy.js +0 -268
  71. package/dist/classifiers/WorkerClassifierProxy.js.map +0 -1
  72. package/dist/worker/classifier-worker.d.ts +0 -49
  73. package/dist/worker/classifier-worker.d.ts.map +0 -1
  74. package/dist/worker/classifier-worker.js +0 -180
  75. package/dist/worker/classifier-worker.js.map +0 -1
  76. package/src/ClassifierOrchestrator.ts +0 -290
  77. package/src/IContentClassifier.ts +0 -124
  78. package/src/SlidingWindowBuffer.ts +0 -384
  79. package/src/classifiers/InjectionClassifier.ts +0 -261
  80. package/src/classifiers/JailbreakClassifier.ts +0 -259
  81. package/src/classifiers/ToxicityClassifier.ts +0 -263
  82. package/src/classifiers/WorkerClassifierProxy.ts +0 -366
  83. package/src/worker/classifier-worker.ts +0 -267
package/dist/types.js CHANGED
@@ -1,62 +1,16 @@
1
1
  /**
2
- * @fileoverview Core type definitions for the ML Classifier Guardrail Extension Pack.
2
+ * @file types.ts
3
+ * @description Core type definitions for the ML Classifiers extension pack.
3
4
  *
4
- * This file defines all configuration shapes, runtime result types, and
5
- * service-identifier constants used by the ML classifier pipeline. All
6
- * classifiers in this pack evaluate text content against learned models
7
- * (toxicity, prompt-injection, jailbreak) and emit structured results that
8
- * feed into the AgentOS guardrail decision tree.
5
+ * Defines the shared interfaces used across the ML classification system:
6
+ * classifier categories, confidence results, option shapes, and the LLM
7
+ * invoker callback signature.
9
8
  *
10
- * Import hierarchy
11
- * ----------------
12
- * ```
13
- * IUtilityAI ──── ClassificationResult, ClassificationScore
14
- * IGuardrailService ── GuardrailAction
15
- * │
16
- * ▼
17
- * types.ts (this file)
18
- * │
19
- * ▼
20
- * IContentClassifier.ts / SlidingWindowBuffer.ts / …
21
- * ```
22
- *
23
- * @module agentos/extensions/packs/ml-classifiers/types
24
- */
25
- /**
26
- * Sensible defaults for {@link ClassifierThresholds}.
27
- *
28
- * These values reflect a conservative-but-pragmatic policy:
29
- * - block at 90 % confidence → very high bar, minimises false positives
30
- * - flag at 70 % → surfaced for human review, not blocked
31
- * - warn at 40 % → low-confidence signal, handled with a light touch
9
+ * @module ml-classifiers/types
32
10
  */
33
- export const DEFAULT_THRESHOLDS = {
34
- blockThreshold: 0.9,
35
- flagThreshold: 0.7,
36
- warnThreshold: 0.4,
37
- };
38
- // ---------------------------------------------------------------------------
39
- // Service identifiers
40
- // ---------------------------------------------------------------------------
41
11
  /**
42
- * Well-known service identifier strings for the three built-in ML classifier
43
- * pipelines.
44
- *
45
- * These IDs follow the `agentos:<domain>:<name>` naming convention used
46
- * throughout the AgentOS extension ecosystem. Use them to retrieve specific
47
- * classifier services from the shared service registry.
48
- *
49
- * @example
50
- * ```typescript
51
- * const toxicity = serviceRegistry.get(ML_CLASSIFIER_SERVICE_IDS.TOXICITY_PIPELINE);
52
- * ```
12
+ * All supported classifier categories as a constant array, used for
13
+ * iteration and default configuration.
53
14
  */
54
- export const ML_CLASSIFIER_SERVICE_IDS = {
55
- /** Classifier that detects toxic, hateful, or abusive language. */
56
- TOXICITY_PIPELINE: 'agentos:ml-classifiers:toxicity-pipeline',
57
- /** Classifier that detects prompt-injection attempts. */
58
- INJECTION_PIPELINE: 'agentos:ml-classifiers:injection-pipeline',
59
- /** Classifier that detects jailbreak / system-override attempts. */
60
- JAILBREAK_PIPELINE: 'agentos:ml-classifiers:jailbreak-pipeline',
61
- };
15
+ export const ALL_CATEGORIES = ['toxic', 'injection', 'nsfw', 'threat'];
62
16
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AA2CH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAyB;IACtD,cAAc,EAAE,GAAG;IACnB,aAAa,EAAE,GAAG;IAClB,aAAa,EAAE,GAAG;CACV,CAAC;AA6NX,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,mEAAmE;IACnE,iBAAiB,EAAE,0CAA0C;IAE7D,yDAAyD;IACzD,kBAAkB,EAAE,2CAA2C;IAE/D,oEAAoE;IACpE,kBAAkB,EAAE,2CAA2C;CACvD,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgBH;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@framers/agentos-ext-ml-classifiers",
3
- "version": "0.1.0",
4
- "description": "ML-based content classification guardrail (toxicity, injection, jailbreak) for AgentOS",
3
+ "version": "0.3.1",
4
+ "description": "ML-based content classifiers for AgentOS — toxicity, prompt injection, and NSFW detection via ONNX models or LLM fallback",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -11,35 +11,21 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
- "files": [
15
- "dist",
16
- "src",
17
- "SKILL.md",
18
- "manifest.json"
19
- ],
20
14
  "peerDependencies": {
21
- "@framers/agentos": "^0.1.0"
15
+ "@framers/agentos": ">=0.7.0"
22
16
  },
23
17
  "optionalDependencies": {
24
18
  "@huggingface/transformers": "^3.0.0"
25
19
  },
20
+ "license": "Apache-2.0",
26
21
  "devDependencies": {
27
- "typescript": "^5.5.0",
28
- "vitest": "^1.6.0",
29
- "@framers/agentos": "0.1.47"
30
- },
31
- "license": "MIT",
32
- "author": "Frame.dev",
33
- "repository": {
34
- "type": "git",
35
- "url": "https://github.com/framersai/agentos-extensions.git",
36
- "directory": "registry/curated/safety/ml-classifiers"
37
- },
38
- "publishConfig": {
39
- "access": "public"
22
+ "semantic-release": "^24.0.0",
23
+ "@semantic-release/github": "^11.0.0"
40
24
  },
25
+ "homepage": "https://agentos.sh",
41
26
  "scripts": {
42
- "build": "tsc -p tsconfig.json",
43
- "test": "vitest run"
27
+ "build": "tsc && node scripts/fix-esm-imports.mjs",
28
+ "clean": "rm -rf dist",
29
+ "typecheck": "tsc --noEmit"
44
30
  }
45
31
  }
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const distDir = path.resolve(__dirname, '..', 'dist');
10
+ const SELF_PACKAGE_NAME = '@framers/agentos-ext-ml-classifiers';
11
+
12
+ function collectJsFiles(dirPath) {
13
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
14
+ const files = [];
15
+ for (const entry of entries) {
16
+ const fullPath = path.join(dirPath, entry.name);
17
+ if (entry.isDirectory()) {
18
+ files.push(...collectJsFiles(fullPath));
19
+ } else if (entry.isFile() && fullPath.endsWith('.js')) {
20
+ files.push(fullPath);
21
+ }
22
+ }
23
+ return files;
24
+ }
25
+
26
+ function resolveSpecifier(filePath, specifier) {
27
+ if (specifier === SELF_PACKAGE_NAME || specifier.startsWith(`${SELF_PACKAGE_NAME}/`)) {
28
+ const relativeExportPath =
29
+ specifier === SELF_PACKAGE_NAME ? 'index' : specifier.slice(SELF_PACKAGE_NAME.length + 1);
30
+ const targetCandidates = [
31
+ path.resolve(distDir, `${relativeExportPath}.js`),
32
+ path.resolve(distDir, relativeExportPath, 'index.js'),
33
+ ];
34
+ const targetPath = targetCandidates.find((candidate) => fs.existsSync(candidate));
35
+ if (!targetPath) {
36
+ return specifier;
37
+ }
38
+
39
+ let relativePath = path.relative(path.dirname(filePath), targetPath).replace(/\\/g, '/');
40
+ if (!relativePath.startsWith('.')) {
41
+ relativePath = `./${relativePath}`;
42
+ }
43
+ return relativePath;
44
+ }
45
+
46
+ if (!specifier.startsWith('.')) {
47
+ return specifier;
48
+ }
49
+
50
+ const hasKnownExtension = /\.(?:[cm]?js|json|node)$/i.test(specifier);
51
+ if (hasKnownExtension) {
52
+ return specifier;
53
+ }
54
+
55
+ const baseDir = path.dirname(filePath);
56
+ const asJs = path.resolve(baseDir, `${specifier}.js`);
57
+ if (fs.existsSync(asJs)) {
58
+ return `${specifier}.js`;
59
+ }
60
+
61
+ const asIndex = path.resolve(baseDir, specifier, 'index.js');
62
+ if (fs.existsSync(asIndex)) {
63
+ return `${specifier}/index.js`;
64
+ }
65
+
66
+ return specifier;
67
+ }
68
+
69
+ function rewriteSpecifiers(filePath) {
70
+ const original = fs.readFileSync(filePath, 'utf8');
71
+ let modified = original;
72
+ let changed = false;
73
+
74
+ const patterns = [
75
+ // Multi-line aware: `[\s\S]*?` matches anything including newlines, non-greedy, so
76
+ // imports with destructuring across multiple lines (e.g. `import { A,\n B\n} from './X'`)
77
+ // are caught. Anchored to start-of-line by `^` + `m` flag and terminated by the `from`
78
+ // keyword before a quote.
79
+ /(^\s*(?:import|export)\s[\s\S]*?from\s+['"])([^'"]+)(['"])/gm,
80
+ /(import\(\s*['"])([^'"]+)(['"]\s*\))/g
81
+ ];
82
+
83
+ for (const pattern of patterns) {
84
+ modified = modified.replace(pattern, (match, prefix, specifier, suffix) => {
85
+ const rewritten = resolveSpecifier(filePath, specifier);
86
+ if (rewritten !== specifier) {
87
+ changed = true;
88
+ return `${prefix}${rewritten}${suffix}`;
89
+ }
90
+ return match;
91
+ });
92
+ }
93
+
94
+ if (changed) {
95
+ fs.writeFileSync(filePath, modified, 'utf8');
96
+ }
97
+ }
98
+
99
+ function copyIfPresent(sourcePath, targetPath) {
100
+ if (!fs.existsSync(sourcePath)) {
101
+ return false;
102
+ }
103
+
104
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
105
+ fs.copyFileSync(sourcePath, targetPath);
106
+ return true;
107
+ }
108
+
109
+ /**
110
+ * Walk the dist tree after rewriting and verify every static/dynamic relative
111
+ * import has a resolvable extension. Catches the case where a previous build
112
+ * left partial dist state (e.g. rimraf failed because another process held
113
+ * files open) and the rewriter's regex didn't reach a stale file.
114
+ *
115
+ * Returns an array of {file, specifier, line} for each unfixed import.
116
+ */
117
+ export function findUnfixedRelativeImports(distDir) {
118
+ const issues = [];
119
+ const jsFiles = collectJsFiles(distDir);
120
+ const patterns = [
121
+ /(^\s*(?:import|export)\s[\s\S]*?from\s+['"])([^'"]+)(['"])/gm,
122
+ /(import\(\s*['"])([^'"]+)(['"]\s*\))/g,
123
+ ];
124
+ for (const file of jsFiles) {
125
+ const contents = fs.readFileSync(file, 'utf8');
126
+ for (const pattern of patterns) {
127
+ pattern.lastIndex = 0;
128
+ let m;
129
+ while ((m = pattern.exec(contents)) !== null) {
130
+ const specifier = m[2];
131
+ if (!specifier.startsWith('.')) continue;
132
+ if (/\.(?:[cm]?js|json|node)$/i.test(specifier)) continue;
133
+ // Look up the file's line number for a useful error message.
134
+ const lineNumber = contents.slice(0, m.index).split('\n').length;
135
+ issues.push({ file, specifier, line: lineNumber });
136
+ }
137
+ }
138
+ }
139
+ return issues;
140
+ }
141
+
142
+ // Only run the main pipeline when invoked directly, not when this module is
143
+ // imported from a test or another script.
144
+ if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
145
+ if (!fs.existsSync(distDir)) {
146
+ console.log('[agentos fix-esm-imports] dist directory missing; nothing to do.');
147
+ process.exit(0);
148
+ }
149
+
150
+ const jsFiles = collectJsFiles(distDir);
151
+ for (const file of jsFiles) {
152
+ rewriteSpecifiers(file);
153
+ }
154
+
155
+ const copiedExtensionSecrets = copyIfPresent(
156
+ path.resolve(distDir, 'core', 'config', 'extension-secrets.json'),
157
+ path.resolve(distDir, 'config', 'extension-secrets.json'),
158
+ );
159
+
160
+ console.log(`[agentos fix-esm-imports] Processed ${jsFiles.length} files under ${distDir}.`);
161
+ if (copiedExtensionSecrets) {
162
+ console.log('[agentos fix-esm-imports] Mirrored extension-secrets.json into dist/config for public package exports.');
163
+ }
164
+
165
+ const unfixed = findUnfixedRelativeImports(distDir);
166
+ if (unfixed.length > 0) {
167
+ console.error(
168
+ `[agentos fix-esm-imports] FAIL: ${unfixed.length} relative import${unfixed.length === 1 ? '' : 's'} ` +
169
+ `in dist still missing a file extension. This usually means a previous build ` +
170
+ `left partial state — try \`rm -rf dist && pnpm build\`.`,
171
+ );
172
+ for (const issue of unfixed.slice(0, 20)) {
173
+ const rel = path.relative(distDir, issue.file);
174
+ console.error(` ${rel}:${issue.line} -> ${issue.specifier}`);
175
+ }
176
+ if (unfixed.length > 20) {
177
+ console.error(` …and ${unfixed.length - 20} more.`);
178
+ }
179
+ process.exit(1);
180
+ }
181
+ }