open-classify 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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -0
  3. package/dist/src/aggregator.d.ts +18 -0
  4. package/dist/src/aggregator.js +267 -0
  5. package/dist/src/catalog.d.ts +7 -0
  6. package/dist/src/catalog.js +189 -0
  7. package/dist/src/classifiers/custom/conversation_diegest/manifest.json +28 -0
  8. package/dist/src/classifiers/custom/conversation_diegest/prompt.md +7 -0
  9. package/dist/src/classifiers/custom/memory_retrieval_queries/manifest.json +29 -0
  10. package/dist/src/classifiers/custom/memory_retrieval_queries/prompt.md +5 -0
  11. package/dist/src/classifiers/stock/model_specialization/manifest.json +8 -0
  12. package/dist/src/classifiers/stock/preflight/manifest.json +8 -0
  13. package/dist/src/classifiers/stock/prompts/base.md +1 -0
  14. package/dist/src/classifiers/stock/prompts/classifier-header.md +4 -0
  15. package/dist/src/classifiers/stock/prompts/confidence.md +3 -0
  16. package/dist/src/classifiers/stock/prompts/custom-output.md +1 -0
  17. package/dist/src/classifiers/stock/prompts/model_specialization.md +7 -0
  18. package/dist/src/classifiers/stock/prompts/preflight-output.md +10 -0
  19. package/dist/src/classifiers/stock/prompts/preflight.md +47 -0
  20. package/dist/src/classifiers/stock/prompts/reason.md +3 -0
  21. package/dist/src/classifiers/stock/prompts/routing-output.md +5 -0
  22. package/dist/src/classifiers/stock/prompts/routing.md +9 -0
  23. package/dist/src/classifiers/stock/prompts/security-output.md +8 -0
  24. package/dist/src/classifiers/stock/prompts/security.md +26 -0
  25. package/dist/src/classifiers/stock/prompts/specialty.md +10 -0
  26. package/dist/src/classifiers/stock/prompts/tier.md +7 -0
  27. package/dist/src/classifiers/stock/prompts/tools-output.md +7 -0
  28. package/dist/src/classifiers/stock/prompts/tools.md +10 -0
  29. package/dist/src/classifiers/stock/routing/manifest.json +8 -0
  30. package/dist/src/classifiers/stock/security/manifest.json +12 -0
  31. package/dist/src/classifiers/stock/tools/manifest.json +19 -0
  32. package/dist/src/classifiers.d.ts +14 -0
  33. package/dist/src/classifiers.js +87 -0
  34. package/dist/src/config.d.ts +29 -0
  35. package/dist/src/config.js +144 -0
  36. package/dist/src/enums.d.ts +10 -0
  37. package/dist/src/enums.js +62 -0
  38. package/dist/src/index.d.ts +13 -0
  39. package/dist/src/index.js +18 -0
  40. package/dist/src/input.d.ts +4 -0
  41. package/dist/src/input.js +192 -0
  42. package/dist/src/manifest.d.ts +115 -0
  43. package/dist/src/manifest.js +1 -0
  44. package/dist/src/ollama.d.ts +54 -0
  45. package/dist/src/ollama.js +293 -0
  46. package/dist/src/pipeline.d.ts +17 -0
  47. package/dist/src/pipeline.js +274 -0
  48. package/dist/src/stock-prompt.d.ts +2 -0
  49. package/dist/src/stock-prompt.js +63 -0
  50. package/dist/src/stock-validation.d.ts +22 -0
  51. package/dist/src/stock-validation.js +329 -0
  52. package/dist/src/stock.d.ts +101 -0
  53. package/dist/src/stock.js +14 -0
  54. package/dist/src/types.d.ts +34 -0
  55. package/dist/src/types.js +6 -0
  56. package/dist/src/ui-server.d.ts +1 -0
  57. package/dist/src/ui-server.js +250 -0
  58. package/dist/src/validation.d.ts +17 -0
  59. package/dist/src/validation.js +127 -0
  60. package/open-classify.config.example.json +24 -0
  61. package/package.json +56 -0
@@ -0,0 +1,329 @@
1
+ import { DOWNSTREAM_MODEL_TIER_VALUES, MODEL_SPECIALIZATION_VALUES, SECURITY_DECISION_VALUES, } from "./enums.js";
2
+ import { Ajv } from "ajv/dist/ajv.js";
3
+ import { STOCK_CLASSIFIER_NAMES } from "./stock.js";
4
+ import { ensureNoDuplicates, isRecord, requireConfidence, requireEnum, requireNonEmptyStringMaxLength, requireNonNegativeSafeInteger, requireString, requireStringArray, throwInvalid, } from "./validation.js";
5
+ export const STOCK_REASON_MAX_CHARS = 120;
6
+ export const STOCK_REPLY_MAX_CHARS = 200;
7
+ export const STOCK_TOOL_ID_MAX_CHARS = 64;
8
+ export const STOCK_TOOL_DESCRIPTION_MAX_CHARS = 240;
9
+ export const STOCK_MANIFEST_NAME_MAX_CHARS = 80;
10
+ export const STOCK_MANIFEST_VERSION_MAX_CHARS = 40;
11
+ export const STOCK_MANIFEST_PURPOSE_MAX_CHARS = 400;
12
+ const STOCK_SAFETY_RISK_LEVEL_VALUES = [
13
+ "normal",
14
+ "suspicious",
15
+ "high_risk",
16
+ "unknown",
17
+ ];
18
+ const MANIFEST_KIND_VALUES = ["stock", "custom"];
19
+ const ajv = new Ajv({ allErrors: true, strict: false });
20
+ const COMMON_MANIFEST_KEYS = [
21
+ "kind",
22
+ "name",
23
+ "version",
24
+ "purpose",
25
+ "order",
26
+ "fallback",
27
+ "backend",
28
+ ];
29
+ const STOCK_MANIFEST_KEYS = [
30
+ ...COMMON_MANIFEST_KEYS,
31
+ "tools",
32
+ ];
33
+ const CUSTOM_MANIFEST_KEYS = [
34
+ ...COMMON_MANIFEST_KEYS,
35
+ "output_schema",
36
+ ];
37
+ export function validateJsonClassifierManifest(value, model = "manifest") {
38
+ if (!isRecord(value)) {
39
+ throwInvalid("manifest", model, "manifest must be a JSON object");
40
+ }
41
+ const kind = requireEnum(value.kind, MANIFEST_KIND_VALUES, "manifest", model, "kind");
42
+ return kind === "stock"
43
+ ? validateStockManifest(value, model)
44
+ : validateCustomManifest(value, model);
45
+ }
46
+ function validateStockManifest(value, model) {
47
+ ensureAllowedObjectKeys(value, STOCK_MANIFEST_KEYS, "manifest", model, "manifest");
48
+ const name = requireEnum(value.name, STOCK_CLASSIFIER_NAMES, "manifest", model, "name");
49
+ const base = validateManifestCommon(value, model);
50
+ const tools = value.tools === undefined
51
+ ? undefined
52
+ : validateTools(value.tools, model);
53
+ if (name !== "tools" && tools !== undefined) {
54
+ throwInvalid("manifest", model, "tools is only supported on the tools classifier");
55
+ }
56
+ const fallback = validateStockOutputForName(name, value.fallback, model, tools);
57
+ return {
58
+ kind: "stock",
59
+ name,
60
+ ...base,
61
+ fallback,
62
+ ...(tools === undefined ? {} : { tools }),
63
+ };
64
+ }
65
+ function validateCustomManifest(value, model) {
66
+ ensureAllowedObjectKeys(value, CUSTOM_MANIFEST_KEYS, "manifest", model, "manifest");
67
+ const name = requireNonEmptyStringMaxLength(value.name, "manifest", model, "name", STOCK_MANIFEST_NAME_MAX_CHARS);
68
+ if (STOCK_CLASSIFIER_NAMES.includes(name)) {
69
+ throwInvalid("manifest", model, `custom classifier name "${name}" collides with a stock classifier`);
70
+ }
71
+ const base = validateManifestCommon(value, model);
72
+ if (value.output_schema === undefined) {
73
+ throwInvalid("manifest", model, "output_schema is required for custom classifiers");
74
+ }
75
+ const outputSchema = value.output_schema;
76
+ compileOutputSchema(outputSchema, "manifest", model);
77
+ const fallback = validateCustomOutput(value.fallback, name, model, outputSchema);
78
+ return {
79
+ kind: "custom",
80
+ name,
81
+ ...base,
82
+ fallback,
83
+ output_schema: outputSchema,
84
+ };
85
+ }
86
+ function validateManifestCommon(value, model) {
87
+ const version = requireNonEmptyStringMaxLength(value.version, "manifest", model, "version", STOCK_MANIFEST_VERSION_MAX_CHARS);
88
+ const purpose = requireNonEmptyStringMaxLength(value.purpose, "manifest", model, "purpose", STOCK_MANIFEST_PURPOSE_MAX_CHARS);
89
+ const order = requireNonNegativeSafeInteger(value.order, "manifest", model, "order");
90
+ return {
91
+ version,
92
+ purpose,
93
+ order,
94
+ ...(value.backend === undefined ? {} : { backend: validateBackend(value.backend, model) }),
95
+ };
96
+ }
97
+ export function validateOutputForManifest(manifest, value, context) {
98
+ if (manifest.kind === "stock") {
99
+ return validateStockOutputForName(manifest.name, value, context.model, manifest.tools);
100
+ }
101
+ return validateCustomOutput(value, context.classifier, context.model, manifest.output_schema);
102
+ }
103
+ function validateStockOutputForName(name, value, model, tools) {
104
+ if (!isRecord(value)) {
105
+ throwInvalid(name, model, "output must be a JSON object");
106
+ }
107
+ switch (name) {
108
+ case "preflight":
109
+ return validatePreflightOutput(value, model);
110
+ case "routing":
111
+ return validateTierRoutingOutput(value, model);
112
+ case "model_specialization":
113
+ return validateModelSpecializationOutput(value, model);
114
+ case "tools":
115
+ return validateToolsOutput(value, model, tools?.map((tool) => tool.id));
116
+ case "security":
117
+ return validateSecurityOutput(value, model);
118
+ default: {
119
+ const _exhaustive = name;
120
+ void _exhaustive;
121
+ throwInvalid("manifest", model, `unknown stock classifier name`);
122
+ }
123
+ }
124
+ }
125
+ function validateMetadata(value, classifier, model) {
126
+ return {
127
+ ...(value.reason === undefined
128
+ ? {}
129
+ : { reason: truncateText(requireString(value.reason, classifier, model, "reason"), STOCK_REASON_MAX_CHARS) }),
130
+ ...(value.confidence === undefined
131
+ ? {}
132
+ : { confidence: requireConfidence(value.confidence, classifier, model) }),
133
+ };
134
+ }
135
+ function validatePreflightOutput(value, model) {
136
+ ensureAllowedObjectKeys(value, ["reason", "confidence", "final_reply", "ack_reply"], "preflight", model, "output");
137
+ if (value.final_reply !== undefined && value.ack_reply !== undefined) {
138
+ throwInvalid("preflight", model, "final_reply and ack_reply are mutually exclusive");
139
+ }
140
+ const meta = validateMetadata(value, "preflight", model);
141
+ return {
142
+ ...meta,
143
+ ...(value.final_reply === undefined
144
+ ? {}
145
+ : { final_reply: validateReplySignal(value.final_reply, "preflight", model, "final_reply") }),
146
+ ...(value.ack_reply === undefined
147
+ ? {}
148
+ : { ack_reply: validateReplySignal(value.ack_reply, "preflight", model, "ack_reply") }),
149
+ };
150
+ }
151
+ function validateReplySignal(value, classifier, model, field) {
152
+ if (!isRecord(value)) {
153
+ throwInvalid(classifier, model, `${field} must be an object`);
154
+ }
155
+ ensureAllowedObjectKeys(value, ["reply"], classifier, model, field);
156
+ const reply = requireString(value.reply, classifier, model, `${field}.reply`);
157
+ if (reply.trim().length === 0) {
158
+ throwInvalid(classifier, model, `${field}.reply must not be empty`);
159
+ }
160
+ if (reply.length > STOCK_REPLY_MAX_CHARS) {
161
+ throwInvalid(classifier, model, `${field}.reply must be ${STOCK_REPLY_MAX_CHARS} characters or fewer`);
162
+ }
163
+ return { reply };
164
+ }
165
+ function validateTierRoutingOutput(value, model) {
166
+ ensureAllowedObjectKeys(value, ["reason", "confidence", "model_tier"], "routing", model, "output");
167
+ const meta = validateMetadata(value, "routing", model);
168
+ const modelTier = normalizeOptionalEnumValue(value.model_tier);
169
+ return {
170
+ ...meta,
171
+ ...(modelTier === undefined
172
+ ? {}
173
+ : { model_tier: requireEnum(modelTier, DOWNSTREAM_MODEL_TIER_VALUES, "routing", model, "model_tier") }),
174
+ };
175
+ }
176
+ function validateModelSpecializationOutput(value, model) {
177
+ ensureAllowedObjectKeys(value, ["reason", "confidence", "specialization"], "model_specialization", model, "output");
178
+ const meta = validateMetadata(value, "model_specialization", model);
179
+ const specialization = normalizeOptionalEnumValue(value.specialization);
180
+ return {
181
+ ...meta,
182
+ ...(specialization === undefined
183
+ ? {}
184
+ : { specialization: requireEnum(specialization, MODEL_SPECIALIZATION_VALUES, "model_specialization", model, "specialization") }),
185
+ };
186
+ }
187
+ function normalizeOptionalEnumValue(value) {
188
+ if (value === undefined || value === null) {
189
+ return undefined;
190
+ }
191
+ if (typeof value === "string" && value.trim().length === 0) {
192
+ return undefined;
193
+ }
194
+ return value;
195
+ }
196
+ function validateToolsOutput(value, model, configuredTools) {
197
+ ensureAllowedObjectKeys(value, ["reason", "confidence", "tools"], "tools", model, "output");
198
+ const meta = validateMetadata(value, "tools", model);
199
+ const tools = requireStringArray(value.tools, "tools", model, "tools").map(normalizeTool);
200
+ ensureNoDuplicates(tools, "tools", model, "tools");
201
+ if (configuredTools) {
202
+ const allowed = new Set(configuredTools);
203
+ for (const tool of tools) {
204
+ if (!allowed.has(tool)) {
205
+ throwInvalid("tools", model, `tools includes unsupported tool ${tool}`);
206
+ }
207
+ }
208
+ }
209
+ return { ...meta, tools };
210
+ }
211
+ function validateSecurityOutput(value, model) {
212
+ ensureAllowedObjectKeys(value, ["reason", "confidence", "decision", "risk_level", "signals"], "security", model, "output");
213
+ const meta = validateMetadata(value, "security", model);
214
+ const decision = value.decision === undefined
215
+ ? undefined
216
+ : requireEnum(value.decision, SECURITY_DECISION_VALUES, "security", model, "decision");
217
+ const riskLevel = requireEnum(value.risk_level, STOCK_SAFETY_RISK_LEVEL_VALUES, "security", model, "risk_level");
218
+ const signals = requireStringArray(value.signals, "security", model, "signals");
219
+ ensureNoDuplicates(signals, "security", model, "signals");
220
+ if ((riskLevel === "normal" || riskLevel === "unknown") && signals.length > 0) {
221
+ throwInvalid("security", model, `${riskLevel} risk_level must not include signals`);
222
+ }
223
+ if (riskLevel !== "normal" && riskLevel !== "unknown" && signals.length === 0) {
224
+ throwInvalid("security", model, "elevated risk_level must include at least one signal");
225
+ }
226
+ if (decision === "block" && riskLevel !== "high_risk") {
227
+ throwInvalid("security", model, "decision block requires high_risk risk_level");
228
+ }
229
+ if (decision === "allow" && riskLevel === "high_risk") {
230
+ throwInvalid("security", model, "decision allow must not use high_risk risk_level");
231
+ }
232
+ return {
233
+ ...meta,
234
+ ...(decision === undefined ? {} : { decision }),
235
+ risk_level: riskLevel,
236
+ signals,
237
+ };
238
+ }
239
+ function validateCustomOutput(value, classifier, model, schema) {
240
+ if (!isRecord(value)) {
241
+ throwInvalid(classifier, model, "output must be a JSON object");
242
+ }
243
+ ensureAllowedObjectKeys(value, ["reason", "confidence", "output"], classifier, model, "output");
244
+ if (value.output === undefined) {
245
+ throwInvalid(classifier, model, "output is required for custom classifiers");
246
+ }
247
+ const meta = validateMetadata(value, classifier, model);
248
+ validateWithSchema(value.output, schema, classifier, model, "output");
249
+ return { ...meta, output: value.output };
250
+ }
251
+ function validateTools(value, model) {
252
+ if (!Array.isArray(value)) {
253
+ throwInvalid("manifest", model, "tools must be an array");
254
+ }
255
+ const out = value.map((item, index) => {
256
+ if (!isRecord(item)) {
257
+ throwInvalid("manifest", model, `tools[${index}] must be an object`);
258
+ }
259
+ return {
260
+ id: requireNonEmptyStringMaxLength(item.id, "manifest", model, `tools[${index}].id`, STOCK_TOOL_ID_MAX_CHARS),
261
+ description: requireNonEmptyStringMaxLength(item.description, "manifest", model, `tools[${index}].description`, STOCK_TOOL_DESCRIPTION_MAX_CHARS),
262
+ };
263
+ });
264
+ ensureNoDuplicates(out.map((item) => item.id), "manifest", model, "tools[].id");
265
+ return out;
266
+ }
267
+ function validateBackend(value, model) {
268
+ if (!isRecord(value))
269
+ throwInvalid("manifest", model, "backend must be an object");
270
+ ensureAllowedObjectKeys(value, ["ollama"], "manifest", model, "backend");
271
+ if (value.ollama === undefined)
272
+ return {};
273
+ if (!isRecord(value.ollama)) {
274
+ throwInvalid("manifest", model, "backend.ollama must be an object");
275
+ }
276
+ ensureAllowedObjectKeys(value.ollama, ["base_model"], "manifest", model, "backend.ollama");
277
+ return {
278
+ ollama: {
279
+ ...(value.ollama.base_model === undefined
280
+ ? {}
281
+ : { base_model: requireString(value.ollama.base_model, "manifest", model, "backend.ollama.base_model") }),
282
+ },
283
+ };
284
+ }
285
+ function ensureAllowedObjectKeys(value, allowedKeys, classifier, model, path) {
286
+ const allowed = new Set(allowedKeys);
287
+ for (const key of Object.keys(value)) {
288
+ if (!allowed.has(key)) {
289
+ throwInvalid(classifier, model, `${path}.${key} is not a supported field`);
290
+ }
291
+ }
292
+ }
293
+ function compileOutputSchema(schema, classifier, model) {
294
+ try {
295
+ ajv.compile(schema);
296
+ }
297
+ catch (error) {
298
+ throwInvalid(classifier, model, `output_schema is invalid JSON Schema: ${errorMessage(error)}`);
299
+ }
300
+ }
301
+ export function validateWithSchema(value, schema, classifier, model, path) {
302
+ const validate = ajv.compile(schema);
303
+ if (!validate(value)) {
304
+ const message = ajv.errorsText(validate.errors, { dataVar: path });
305
+ throwInvalid(classifier, model, message);
306
+ }
307
+ }
308
+ function normalizeTool(tool) {
309
+ const aliases = {
310
+ browser: "web",
311
+ browsing: "web",
312
+ internet: "web",
313
+ web_browsing: "web",
314
+ web_search: "web",
315
+ };
316
+ return aliases[tool] ?? tool;
317
+ }
318
+ function truncateText(text, maxChars) {
319
+ return text.length <= maxChars ? text : text.slice(0, maxChars).trimEnd();
320
+ }
321
+ function errorMessage(error) {
322
+ return error instanceof Error ? error.message : String(error);
323
+ }
324
+ export function validateClassifierOutputWithManifest(value, options) {
325
+ return validateOutputForManifest(options.manifest, value, {
326
+ classifier: options.classifier,
327
+ model: options.model,
328
+ });
329
+ }
@@ -0,0 +1,101 @@
1
+ import type { DownstreamModelTier, ModelSpecialization, SecurityDecision } from "./enums.js";
2
+ export interface StockClassifierMessageInput {
3
+ readonly role: "user" | "assistant";
4
+ readonly text: string;
5
+ }
6
+ export interface StockClassifierInput {
7
+ readonly messages: ReadonlyArray<StockClassifierMessageInput>;
8
+ }
9
+ export interface FinalReplySignal {
10
+ readonly reply: string;
11
+ }
12
+ export interface AckReplySignal {
13
+ readonly reply: string;
14
+ }
15
+ export interface RoutingSignal {
16
+ readonly model_tier?: DownstreamModelTier;
17
+ readonly specialization?: ModelSpecialization;
18
+ }
19
+ export interface TierSignal {
20
+ readonly model_tier?: DownstreamModelTier;
21
+ }
22
+ export interface SpecializationSignal {
23
+ readonly specialization?: ModelSpecialization;
24
+ }
25
+ export interface ToolsSignal {
26
+ readonly tools: ReadonlyArray<string>;
27
+ }
28
+ export interface SafetySignal {
29
+ readonly decision?: SecurityDecision;
30
+ readonly risk_level: "normal" | "suspicious" | "high_risk" | "unknown";
31
+ readonly signals: ReadonlyArray<string>;
32
+ }
33
+ export interface ClassifierOutputMetadata {
34
+ readonly reason?: string;
35
+ readonly confidence?: number;
36
+ }
37
+ export interface PreflightClassifierOutput extends ClassifierOutputMetadata {
38
+ readonly final_reply?: FinalReplySignal;
39
+ readonly ack_reply?: AckReplySignal;
40
+ }
41
+ export type RoutingClassifierOutput = TierSignal & ClassifierOutputMetadata;
42
+ export type ModelSpecializationClassifierOutput = SpecializationSignal & ClassifierOutputMetadata;
43
+ export type ToolsClassifierOutput = ToolsSignal & ClassifierOutputMetadata;
44
+ export type SecurityClassifierOutput = SafetySignal & ClassifierOutputMetadata;
45
+ export interface CustomClassifierOutputValue extends ClassifierOutputMetadata {
46
+ readonly output: unknown;
47
+ }
48
+ export interface StockClassifierOutputs {
49
+ readonly preflight: PreflightClassifierOutput;
50
+ readonly routing: RoutingClassifierOutput;
51
+ readonly model_specialization: ModelSpecializationClassifierOutput;
52
+ readonly tools: ToolsClassifierOutput;
53
+ readonly security: SecurityClassifierOutput;
54
+ }
55
+ export declare const STOCK_CLASSIFIER_NAMES: readonly ["preflight", "routing", "model_specialization", "tools", "security"];
56
+ export type StockClassifierName = (typeof STOCK_CLASSIFIER_NAMES)[number];
57
+ export type StockClassifierOutput = StockClassifierOutputs[StockClassifierName];
58
+ export type ClassifierOutput = StockClassifierOutput | CustomClassifierOutputValue;
59
+ export interface ToolDefinition {
60
+ readonly id: string;
61
+ readonly description: string;
62
+ }
63
+ interface ManifestCommon {
64
+ readonly version: string;
65
+ readonly purpose: string;
66
+ readonly order: number;
67
+ readonly backend?: {
68
+ readonly ollama?: {
69
+ readonly base_model?: string;
70
+ };
71
+ };
72
+ }
73
+ export interface StockJsonManifest<Name extends StockClassifierName = StockClassifierName> extends ManifestCommon {
74
+ readonly kind: "stock";
75
+ readonly name: Name;
76
+ readonly fallback: StockClassifierOutputs[Name];
77
+ readonly tools?: ReadonlyArray<ToolDefinition>;
78
+ }
79
+ export interface CustomJsonManifest extends ManifestCommon {
80
+ readonly kind: "custom";
81
+ readonly name: string;
82
+ readonly fallback: CustomClassifierOutputValue;
83
+ readonly output_schema: unknown;
84
+ }
85
+ export type JsonClassifierManifest = StockJsonManifest | CustomJsonManifest;
86
+ export interface RuntimeStockManifest<Name extends StockClassifierName = StockClassifierName> extends StockJsonManifest<Name> {
87
+ readonly systemPrompt: string;
88
+ }
89
+ export interface RuntimeCustomManifest extends CustomJsonManifest {
90
+ readonly systemPrompt: string;
91
+ }
92
+ export type RuntimeClassifierManifest = RuntimeStockManifest | RuntimeCustomManifest;
93
+ export declare function isStockManifest(manifest: RuntimeClassifierManifest): manifest is RuntimeStockManifest;
94
+ export declare function isCustomManifest(manifest: RuntimeClassifierManifest): manifest is RuntimeCustomManifest;
95
+ export interface CustomClassifierOutput {
96
+ readonly classifier: string;
97
+ readonly reason?: string;
98
+ readonly confidence?: number;
99
+ readonly output: unknown;
100
+ }
101
+ export {};
@@ -0,0 +1,14 @@
1
+ export const STOCK_CLASSIFIER_NAMES = [
2
+ "preflight",
3
+ "routing",
4
+ "model_specialization",
5
+ "tools",
6
+ "security",
7
+ ];
8
+ // Helper: narrow a manifest to its stock kind for callers that know the name.
9
+ export function isStockManifest(manifest) {
10
+ return manifest.kind === "stock";
11
+ }
12
+ export function isCustomManifest(manifest) {
13
+ return manifest.kind === "custom";
14
+ }
@@ -0,0 +1,34 @@
1
+ export type ConversationMessageRole = "user" | "assistant";
2
+ export interface ConversationMessageInput {
3
+ role?: ConversationMessageRole;
4
+ text: string;
5
+ }
6
+ export interface OpenClassifyInput {
7
+ /**
8
+ * Chronological message history ending with the message to classify.
9
+ *
10
+ * Callers should send the context they already have. Open Classify classifies
11
+ * only the final message and uses earlier messages as context. Normalization
12
+ * walks backward from the final message, keeps newest whole context messages
13
+ * while the classifier payload budget allows, and caps the retained history to
14
+ * 20 messages. It never slices message text.
15
+ */
16
+ messages: ConversationMessageInput[];
17
+ }
18
+ export interface NormalizedOpenClassifyInput {
19
+ messages: ConversationMessageInput[];
20
+ text: string;
21
+ target_message_hash: string;
22
+ }
23
+ export interface ClassifierInput {
24
+ text: string;
25
+ messages: ConversationMessageInput[];
26
+ target_message_hash: string;
27
+ }
28
+ export type ClassifierFallbackReason = "error" | "timeout";
29
+ export interface ClassifierRunStatus {
30
+ ok: boolean;
31
+ source: "model" | "fallback";
32
+ reason?: ClassifierFallbackReason;
33
+ error?: string;
34
+ }
@@ -0,0 +1,6 @@
1
+ // Shared input + run-status types. Classifier manifests and stock output
2
+ // validation define classifier-specific runtime contracts; pipeline result
3
+ // types live in `src/manifest.ts`. This file is intentionally small: it should
4
+ // only hold the contract for what callers send in and the operational metadata
5
+ // every classifier carries alongside its verdict.
6
+ export {};
@@ -0,0 +1 @@
1
+ export {};