network-ai 5.2.2 → 5.3.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.
- package/README.md +9 -4
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/context-throttler.d.ts +139 -0
- package/dist/lib/context-throttler.d.ts.map +1 -0
- package/dist/lib/context-throttler.js +171 -0
- package/dist/lib/context-throttler.js.map +1 -0
- package/dist/lib/coverage-gate.d.ts +155 -0
- package/dist/lib/coverage-gate.d.ts.map +1 -0
- package/dist/lib/coverage-gate.js +213 -0
- package/dist/lib/coverage-gate.js.map +1 -0
- package/dist/lib/fsm-journey.d.ts +2 -0
- package/dist/lib/fsm-journey.d.ts.map +1 -1
- package/dist/lib/fsm-journey.js +2 -0
- package/dist/lib/fsm-journey.js.map +1 -1
- package/dist/lib/goal-decomposer.d.ts +39 -0
- package/dist/lib/goal-decomposer.d.ts.map +1 -1
- package/dist/lib/goal-decomposer.js +100 -2
- package/dist/lib/goal-decomposer.js.map +1 -1
- package/dist/lib/partition-planner.d.ts +149 -0
- package/dist/lib/partition-planner.d.ts.map +1 -0
- package/dist/lib/partition-planner.js +246 -0
- package/dist/lib/partition-planner.js.map +1 -0
- package/dist/lib/route-classifier.d.ts +133 -0
- package/dist/lib/route-classifier.d.ts.map +1 -0
- package/dist/lib/route-classifier.js +217 -0
- package/dist/lib/route-classifier.js.map +1 -0
- package/package.json +3 -2
- package/socket.json +1 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Partition Planner — Logical Work Partitioning for Network-AI
|
|
4
|
+
*
|
|
5
|
+
* Prevents parallel agents from performing redundant research or analysis by
|
|
6
|
+
* running a "meta-step" before DAG execution that generates a Scope Assignment
|
|
7
|
+
* Map (PartitionSchema). Each agent receives a boundary constraint injected
|
|
8
|
+
* into its parameters, declaring what it SHOULD focus on and what it should
|
|
9
|
+
* EXCLUDE.
|
|
10
|
+
*
|
|
11
|
+
* The planner also performs an overlap check — verifying that no two
|
|
12
|
+
* `focus_area` strings overlap semantically (or lexically, when using the
|
|
13
|
+
* built-in heuristic check).
|
|
14
|
+
*
|
|
15
|
+
* Zero external dependencies — the overlap checker and the planner function
|
|
16
|
+
* are pluggable so callers can use any LLM or rule-based strategy.
|
|
17
|
+
*
|
|
18
|
+
* @module PartitionPlanner
|
|
19
|
+
* @version 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.PartitionPlanner = void 0;
|
|
23
|
+
exports.createLexicalOverlapChecker = createLexicalOverlapChecker;
|
|
24
|
+
exports.createLLMPartitionPlanner = createLLMPartitionPlanner;
|
|
25
|
+
exports.parsePartitionJSON = parsePartitionJSON;
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// BUILT-IN OVERLAP CHECKER (lexical heuristic — zero model cost)
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Lexical overlap checker: considers two focus areas to overlap when they
|
|
31
|
+
* share significant word stems (ignoring common stop words).
|
|
32
|
+
*
|
|
33
|
+
* This is the built-in default. For true semantic overlap detection, inject
|
|
34
|
+
* an LLM-based `OverlapCheckFunction` via `PartitionPlannerOptions.overlapChecker`.
|
|
35
|
+
*/
|
|
36
|
+
function createLexicalOverlapChecker() {
|
|
37
|
+
const STOP_WORDS = new Set([
|
|
38
|
+
'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
39
|
+
'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
40
|
+
'as', 'its', 'it', 'this', 'that', 'all', 'any', 'each', 'every',
|
|
41
|
+
]);
|
|
42
|
+
function tokenize(text) {
|
|
43
|
+
return new Set(text
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
46
|
+
.split(/\s+/)
|
|
47
|
+
.filter((w) => w.length > 2 && !STOP_WORDS.has(w)));
|
|
48
|
+
}
|
|
49
|
+
return async (schema) => {
|
|
50
|
+
const overlaps = [];
|
|
51
|
+
for (let i = 0; i < schema.length; i++) {
|
|
52
|
+
for (let j = i + 1; j < schema.length; j++) {
|
|
53
|
+
const tokensA = tokenize(schema[i].focus_area);
|
|
54
|
+
const tokensB = tokenize(schema[j].focus_area);
|
|
55
|
+
const shared = [];
|
|
56
|
+
for (const token of tokensA) {
|
|
57
|
+
if (tokensB.has(token))
|
|
58
|
+
shared.push(token);
|
|
59
|
+
}
|
|
60
|
+
// Flag as overlap when ≥ 2 shared significant words or overlap ratio > 0.4
|
|
61
|
+
const unionSize = new Set([...tokensA, ...tokensB]).size;
|
|
62
|
+
const ratio = unionSize > 0 ? shared.length / unionSize : 0;
|
|
63
|
+
if (shared.length >= 2 || ratio > 0.4) {
|
|
64
|
+
overlaps.push(`Overlap between "${schema[i].agent_type}" (focus: "${schema[i].focus_area}") and "${schema[j].agent_type}" (focus: "${schema[j].focus_area}") — shared terms: ${shared.join(', ')}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return overlaps;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build a partition planner backed by an LLM.
|
|
73
|
+
*
|
|
74
|
+
* The LLM is asked to generate a PartitionSchema JSON array given the goal
|
|
75
|
+
* and the available agents. Use this for rich semantic partitioning.
|
|
76
|
+
*
|
|
77
|
+
* @param executor - Network-AI executor function
|
|
78
|
+
* @param plannerAgentId - Agent ID for the LLM that does partitioning
|
|
79
|
+
*/
|
|
80
|
+
function createLLMPartitionPlanner(executor, plannerAgentId) {
|
|
81
|
+
return async (goal, agents, context) => {
|
|
82
|
+
const agentList = agents.map((a) => `- ${a.id}: ${a.role}`).join('\n');
|
|
83
|
+
const prompt = [
|
|
84
|
+
'You are a work-partitioning planner for a multi-agent AI system.',
|
|
85
|
+
'Generate a scope assignment for each agent to prevent redundant research.',
|
|
86
|
+
'',
|
|
87
|
+
`GOAL: ${goal}`,
|
|
88
|
+
'',
|
|
89
|
+
'AGENTS:',
|
|
90
|
+
agentList,
|
|
91
|
+
'',
|
|
92
|
+
context ? `CONTEXT: ${JSON.stringify(context)}` : '',
|
|
93
|
+
'',
|
|
94
|
+
'Respond with ONLY a JSON array where each element has:',
|
|
95
|
+
'- "agent_type": agent ID from the list above',
|
|
96
|
+
'- "focus_area": a short phrase describing what ONLY this agent researches',
|
|
97
|
+
'- "excluded_topics": array of topic strings this agent must NOT cover',
|
|
98
|
+
'',
|
|
99
|
+
'Ensure no two focus_area values overlap semantically.',
|
|
100
|
+
'',
|
|
101
|
+
'Example:',
|
|
102
|
+
'[{"agent_type":"researcher","focus_area":"market trends and competitive landscape","excluded_topics":["financial projections","legal compliance"]},',
|
|
103
|
+
' {"agent_type":"analyst","focus_area":"financial projections and ROI analysis","excluded_topics":["market research","legal compliance"]}]',
|
|
104
|
+
].filter(Boolean).join('\n');
|
|
105
|
+
const result = await executor(plannerAgentId, { action: 'partition', params: { prompt } }, { agentId: plannerAgentId, taskId: `partition-${Date.now()}`, metadata: { type: 'partition-planning', ...(context ?? {}) } });
|
|
106
|
+
if (!result.success || !result.data) {
|
|
107
|
+
throw new Error(`Partition planner failed: ${result.error?.message ?? 'no data returned'}`);
|
|
108
|
+
}
|
|
109
|
+
return parsePartitionJSON(typeof result.data === 'string' ? result.data : JSON.stringify(result.data));
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Parse a PartitionSchema from an LLM response string.
|
|
114
|
+
* Handles markdown fences and leading/trailing text.
|
|
115
|
+
*/
|
|
116
|
+
function parsePartitionJSON(text) {
|
|
117
|
+
let cleaned = text.trim();
|
|
118
|
+
// Strip markdown fences
|
|
119
|
+
const fenceOpen = cleaned.indexOf('```');
|
|
120
|
+
if (fenceOpen !== -1) {
|
|
121
|
+
const afterOpen = cleaned.indexOf('\n', fenceOpen);
|
|
122
|
+
const fenceClose = cleaned.indexOf('```', afterOpen !== -1 ? afterOpen : fenceOpen + 3);
|
|
123
|
+
if (afterOpen !== -1 && fenceClose > afterOpen) {
|
|
124
|
+
cleaned = cleaned.substring(afterOpen + 1, fenceClose).trim();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Find JSON array
|
|
128
|
+
const arrayStart = cleaned.indexOf('[');
|
|
129
|
+
const arrayEnd = cleaned.lastIndexOf(']');
|
|
130
|
+
if (arrayStart !== -1 && arrayEnd > arrayStart) {
|
|
131
|
+
cleaned = cleaned.substring(arrayStart, arrayEnd + 1);
|
|
132
|
+
}
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = JSON.parse(cleaned);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
throw new Error(`Failed to parse partition schema JSON: ${err.message}`);
|
|
139
|
+
}
|
|
140
|
+
if (!Array.isArray(parsed)) {
|
|
141
|
+
throw new Error('Partition schema must be a JSON array');
|
|
142
|
+
}
|
|
143
|
+
// Validate each entry
|
|
144
|
+
const schema = [];
|
|
145
|
+
for (const entry of parsed) {
|
|
146
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
147
|
+
throw new Error('Each partition entry must be a JSON object');
|
|
148
|
+
}
|
|
149
|
+
const e = entry;
|
|
150
|
+
if (!e.agent_type || typeof e.agent_type !== 'string') {
|
|
151
|
+
throw new Error('Each partition entry must have a string "agent_type"');
|
|
152
|
+
}
|
|
153
|
+
if (!e.focus_area || typeof e.focus_area !== 'string') {
|
|
154
|
+
throw new Error(`Partition entry for "${e.agent_type}" must have a string "focus_area"`);
|
|
155
|
+
}
|
|
156
|
+
if (!Array.isArray(e.excluded_topics)) {
|
|
157
|
+
e.excluded_topics = [];
|
|
158
|
+
}
|
|
159
|
+
schema.push({
|
|
160
|
+
agent_type: e.agent_type,
|
|
161
|
+
focus_area: e.focus_area,
|
|
162
|
+
excluded_topics: e.excluded_topics.map(String),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return schema;
|
|
166
|
+
}
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// PARTITION PLANNER (OOP interface)
|
|
169
|
+
// ============================================================================
|
|
170
|
+
/**
|
|
171
|
+
* PartitionPlanner generates a PartitionSchema (scope assignment map) for a
|
|
172
|
+
* set of agents before the DAG is executed, preventing redundant research.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* const planner = new PartitionPlanner(myLLMPartitionPlannerFn);
|
|
177
|
+
* const result = await planner.plan('Analyse Q3 financial results', agents);
|
|
178
|
+
* // result.schema[0] = { agent_type: 'researcher', focus_area: '...', excluded_topics: [...] }
|
|
179
|
+
* // Inject result.schema[i] as boundary constraint into each agent's params
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
class PartitionPlanner {
|
|
183
|
+
plannerFn;
|
|
184
|
+
overlapChecker;
|
|
185
|
+
strictOverlap;
|
|
186
|
+
constructor(plannerFn, options = {}) {
|
|
187
|
+
this.plannerFn = plannerFn;
|
|
188
|
+
this.overlapChecker = options.overlapChecker ?? createLexicalOverlapChecker();
|
|
189
|
+
this.strictOverlap = options.strictOverlap ?? false;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Generate a PartitionSchema for a goal and agent list.
|
|
193
|
+
*
|
|
194
|
+
* Runs the planner then validates for overlaps. If `strictOverlap` is true
|
|
195
|
+
* and overlaps are found, throws an error. Otherwise overlaps are reported
|
|
196
|
+
* in the result.
|
|
197
|
+
*
|
|
198
|
+
* @param goal - Natural language goal
|
|
199
|
+
* @param agents - Available team agents
|
|
200
|
+
* @param context - Optional context to feed to the planner
|
|
201
|
+
*/
|
|
202
|
+
async plan(goal, agents, context) {
|
|
203
|
+
if (!goal || typeof goal !== 'string') {
|
|
204
|
+
throw new Error('Goal must be a non-empty string');
|
|
205
|
+
}
|
|
206
|
+
if (!agents || agents.length === 0) {
|
|
207
|
+
throw new Error('At least one agent is required');
|
|
208
|
+
}
|
|
209
|
+
const schema = await this.plannerFn(goal, agents, context);
|
|
210
|
+
const overlaps = await this.overlapChecker(schema);
|
|
211
|
+
const hasOverlaps = overlaps.length > 0;
|
|
212
|
+
if (hasOverlaps && this.strictOverlap) {
|
|
213
|
+
throw new Error(`Partition schema has semantic overlaps:\n${overlaps.join('\n')}`);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
schema,
|
|
217
|
+
overlaps,
|
|
218
|
+
hasOverlaps,
|
|
219
|
+
createdAt: Date.now(),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Inject partition boundary constraints into agent params.
|
|
224
|
+
*
|
|
225
|
+
* Given a PartitionSchema and an existing params object for an agent,
|
|
226
|
+
* returns a new params object with `_partitionConstraint` added.
|
|
227
|
+
*
|
|
228
|
+
* @param agentId - Agent ID to look up in the schema
|
|
229
|
+
* @param params - Existing task params
|
|
230
|
+
* @param schema - The partition schema
|
|
231
|
+
*/
|
|
232
|
+
static injectConstraint(agentId, params, schema) {
|
|
233
|
+
const entry = schema.find((e) => e.agent_type === agentId);
|
|
234
|
+
if (!entry)
|
|
235
|
+
return params;
|
|
236
|
+
return {
|
|
237
|
+
...params,
|
|
238
|
+
_partitionConstraint: {
|
|
239
|
+
focus_area: entry.focus_area,
|
|
240
|
+
excluded_topics: entry.excluded_topics,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
exports.PartitionPlanner = PartitionPlanner;
|
|
246
|
+
//# sourceMappingURL=partition-planner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition-planner.js","sourceRoot":"","sources":["../../lib/partition-planner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Classifier — Short-Circuit Routing for Network-AI
|
|
3
|
+
*
|
|
4
|
+
* Classifies an incoming goal into one of three categories before any
|
|
5
|
+
* DAG planning occurs. Simple factual lookups are routed directly to a
|
|
6
|
+
* single agent, bypassing the Blackboard/locking layer entirely to save
|
|
7
|
+
* latency and token cost. Complex synthesis uses the full DAG pipeline.
|
|
8
|
+
* System failures are surfaced immediately.
|
|
9
|
+
*
|
|
10
|
+
* Zero external dependencies — the classifier function is pluggable so
|
|
11
|
+
* callers can use any model (Haiku, Llama-3-8B, rule-based heuristic, …).
|
|
12
|
+
*
|
|
13
|
+
* @module RouteClassifier
|
|
14
|
+
* @version 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
/** The three routing categories. */
|
|
17
|
+
export type RouteCategory = 'FACTUAL_LOOKUP' | 'COMPLEX_SYNTHESIS' | 'SYSTEM_FAILURE';
|
|
18
|
+
/** Result produced by the classifier. */
|
|
19
|
+
export interface ClassificationResult {
|
|
20
|
+
/** The routing decision. */
|
|
21
|
+
category: RouteCategory;
|
|
22
|
+
/** Human-readable explanation from the classifier. */
|
|
23
|
+
rationale: string;
|
|
24
|
+
/** Confidence score 0–1 (optional — classifiers may omit this). */
|
|
25
|
+
confidence?: number;
|
|
26
|
+
/** When classification completed (epoch ms). */
|
|
27
|
+
classifiedAt: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A function that classifies a goal string.
|
|
31
|
+
*
|
|
32
|
+
* Implement this with a fast model call, a rule-based heuristic, or a
|
|
33
|
+
* combination. Must resolve — never reject — returning SYSTEM_FAILURE
|
|
34
|
+
* instead of throwing when the input is unclassifiable.
|
|
35
|
+
*/
|
|
36
|
+
export type ClassifierFunction = (goal: string) => Promise<ClassificationResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Result of a routed execution attempt (FACTUAL_LOOKUP path).
|
|
39
|
+
* For COMPLEX_SYNTHESIS the caller handles execution via the normal DAG pipeline.
|
|
40
|
+
*/
|
|
41
|
+
export interface RouteResult {
|
|
42
|
+
/** The classification that drove this route. */
|
|
43
|
+
classification: ClassificationResult;
|
|
44
|
+
/** True when the request was short-circuited (FACTUAL_LOOKUP). */
|
|
45
|
+
shortCircuited: boolean;
|
|
46
|
+
/** Agent output for the short-circuit path, undefined otherwise. */
|
|
47
|
+
answer?: unknown;
|
|
48
|
+
/** Error message for SYSTEM_FAILURE. */
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Options for {@link RouteClassifier}. */
|
|
52
|
+
export interface RouteClassifierOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Agent ID to call for FACTUAL_LOOKUP responses.
|
|
55
|
+
* Required when `executor` is provided.
|
|
56
|
+
*/
|
|
57
|
+
lookupAgentId?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A simple keyword / length heuristic classifier for when you don't want to
|
|
61
|
+
* spend tokens on a model call for every request.
|
|
62
|
+
*
|
|
63
|
+
* Treats goals of ≤ 15 words with question-like phrasing as FACTUAL_LOOKUP
|
|
64
|
+
* and everything else as COMPLEX_SYNTHESIS.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createHeuristicClassifier(): ClassifierFunction;
|
|
67
|
+
/**
|
|
68
|
+
* Build a classifier backed by an LLM via the Network-AI executor API.
|
|
69
|
+
*
|
|
70
|
+
* @param executor - The executor function from the adapter system
|
|
71
|
+
* @param classifierAgentId - Agent ID for the fast classification model
|
|
72
|
+
*/
|
|
73
|
+
export declare function createLLMClassifier(executor: (agentId: string, payload: {
|
|
74
|
+
action: string;
|
|
75
|
+
params: Record<string, unknown>;
|
|
76
|
+
}, context: {
|
|
77
|
+
agentId: string;
|
|
78
|
+
taskId: string;
|
|
79
|
+
metadata?: Record<string, unknown>;
|
|
80
|
+
}) => Promise<{
|
|
81
|
+
success: boolean;
|
|
82
|
+
data?: unknown;
|
|
83
|
+
error?: {
|
|
84
|
+
message: string;
|
|
85
|
+
};
|
|
86
|
+
}>, classifierAgentId: string): ClassifierFunction;
|
|
87
|
+
/**
|
|
88
|
+
* RouteClassifier evaluates a goal before DAG planning begins and decides
|
|
89
|
+
* whether to short-circuit to a single agent (FACTUAL_LOOKUP) or proceed
|
|
90
|
+
* with the full multi-agent pipeline (COMPLEX_SYNTHESIS).
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const classifier = new RouteClassifier(createHeuristicClassifier());
|
|
95
|
+
* const { category } = await classifier.classify('What is the capital of France?');
|
|
96
|
+
* // category === 'FACTUAL_LOOKUP'
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare class RouteClassifier {
|
|
100
|
+
private classifierFn;
|
|
101
|
+
private options;
|
|
102
|
+
constructor(classifierFn: ClassifierFunction, options?: RouteClassifierOptions);
|
|
103
|
+
/**
|
|
104
|
+
* Classify a goal.
|
|
105
|
+
*/
|
|
106
|
+
classify(goal: string): Promise<ClassificationResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Classify a goal and, if FACTUAL_LOOKUP, short-circuit to a single agent.
|
|
109
|
+
*
|
|
110
|
+
* Returns the classification result and, when short-circuited, the agent's
|
|
111
|
+
* direct answer. The caller should check `result.shortCircuited` — if false,
|
|
112
|
+
* proceed with the normal DAG pipeline.
|
|
113
|
+
*
|
|
114
|
+
* @param goal - Natural language goal
|
|
115
|
+
* @param executor - Agent executor (required for FACTUAL_LOOKUP short-circuit)
|
|
116
|
+
* @param fallbackAgentId - Agent to call on FACTUAL_LOOKUP (overrides options.lookupAgentId)
|
|
117
|
+
*/
|
|
118
|
+
route(goal: string, executor?: (agentId: string, payload: {
|
|
119
|
+
action: string;
|
|
120
|
+
params: Record<string, unknown>;
|
|
121
|
+
}, context: {
|
|
122
|
+
agentId: string;
|
|
123
|
+
taskId: string;
|
|
124
|
+
metadata?: Record<string, unknown>;
|
|
125
|
+
}) => Promise<{
|
|
126
|
+
success: boolean;
|
|
127
|
+
data?: unknown;
|
|
128
|
+
error?: {
|
|
129
|
+
message: string;
|
|
130
|
+
};
|
|
131
|
+
}>, fallbackAgentId?: string): Promise<RouteResult>;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=route-classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-classifier.d.ts","sourceRoot":"","sources":["../../lib/route-classifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,oCAAoC;AACpC,MAAM,MAAM,aAAa,GACrB,gBAAgB,GAChB,mBAAmB,GACnB,gBAAgB,CAAC;AAErB,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,4BAA4B;IAC5B,QAAQ,EAAE,aAAa,CAAC;IACxB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEjF;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,cAAc,EAAE,oBAAoB,CAAC;IACrC,kEAAkE;IAClE,cAAc,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,kBAAkB,CAwC9D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,CACR,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAC5D,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,KAC7E,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,EAC/E,iBAAiB,EAAE,MAAM,GACxB,kBAAkB,CAkEpB;AAMD;;;;;;;;;;;GAWG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,OAAO,CAAyB;gBAE5B,YAAY,EAAE,kBAAkB,EAAE,OAAO,GAAE,sBAA2B;IAKlF;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAY3D;;;;;;;;;;OAUG;IACG,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,CACT,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAC5D,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KAC7E,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,EAC/E,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,WAAW,CAAC;CA2CxB"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Route Classifier — Short-Circuit Routing for Network-AI
|
|
4
|
+
*
|
|
5
|
+
* Classifies an incoming goal into one of three categories before any
|
|
6
|
+
* DAG planning occurs. Simple factual lookups are routed directly to a
|
|
7
|
+
* single agent, bypassing the Blackboard/locking layer entirely to save
|
|
8
|
+
* latency and token cost. Complex synthesis uses the full DAG pipeline.
|
|
9
|
+
* System failures are surfaced immediately.
|
|
10
|
+
*
|
|
11
|
+
* Zero external dependencies — the classifier function is pluggable so
|
|
12
|
+
* callers can use any model (Haiku, Llama-3-8B, rule-based heuristic, …).
|
|
13
|
+
*
|
|
14
|
+
* @module RouteClassifier
|
|
15
|
+
* @version 1.0.0
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.RouteClassifier = void 0;
|
|
19
|
+
exports.createHeuristicClassifier = createHeuristicClassifier;
|
|
20
|
+
exports.createLLMClassifier = createLLMClassifier;
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// RULE-BASED HEURISTIC CLASSIFIER (built-in, zero model cost)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* A simple keyword / length heuristic classifier for when you don't want to
|
|
26
|
+
* spend tokens on a model call for every request.
|
|
27
|
+
*
|
|
28
|
+
* Treats goals of ≤ 15 words with question-like phrasing as FACTUAL_LOOKUP
|
|
29
|
+
* and everything else as COMPLEX_SYNTHESIS.
|
|
30
|
+
*/
|
|
31
|
+
function createHeuristicClassifier() {
|
|
32
|
+
const LOOKUP_STARTERS = [
|
|
33
|
+
'what is', 'what are', 'who is', 'who are', 'when is', 'when was',
|
|
34
|
+
'where is', 'where was', 'how many', 'how much', 'define ', 'list ',
|
|
35
|
+
'name ', 'tell me', 'give me', 'show me',
|
|
36
|
+
];
|
|
37
|
+
return async (goal) => {
|
|
38
|
+
const normalized = goal.trim().toLowerCase();
|
|
39
|
+
if (!normalized) {
|
|
40
|
+
return {
|
|
41
|
+
category: 'SYSTEM_FAILURE',
|
|
42
|
+
rationale: 'Goal is empty',
|
|
43
|
+
confidence: 1,
|
|
44
|
+
classifiedAt: Date.now(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const wordCount = normalized.split(/\s+/).length;
|
|
48
|
+
const isShort = wordCount <= 15;
|
|
49
|
+
const startsLikeQuestion = LOOKUP_STARTERS.some((s) => normalized.startsWith(s));
|
|
50
|
+
const endsWithQuestion = normalized.endsWith('?');
|
|
51
|
+
if (isShort && (startsLikeQuestion || endsWithQuestion)) {
|
|
52
|
+
return {
|
|
53
|
+
category: 'FACTUAL_LOOKUP',
|
|
54
|
+
rationale: `Short goal (${wordCount} words) with question-like phrasing.`,
|
|
55
|
+
confidence: 0.8,
|
|
56
|
+
classifiedAt: Date.now(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
category: 'COMPLEX_SYNTHESIS',
|
|
61
|
+
rationale: `Goal requires multi-step reasoning (${wordCount} words).`,
|
|
62
|
+
confidence: 0.75,
|
|
63
|
+
classifiedAt: Date.now(),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a classifier backed by an LLM via the Network-AI executor API.
|
|
69
|
+
*
|
|
70
|
+
* @param executor - The executor function from the adapter system
|
|
71
|
+
* @param classifierAgentId - Agent ID for the fast classification model
|
|
72
|
+
*/
|
|
73
|
+
function createLLMClassifier(executor, classifierAgentId) {
|
|
74
|
+
return async (goal) => {
|
|
75
|
+
const prompt = [
|
|
76
|
+
'Classify the following goal into exactly one category:',
|
|
77
|
+
' FACTUAL_LOOKUP — simple factual question answerable in one step',
|
|
78
|
+
' COMPLEX_SYNTHESIS — requires multi-step research/reasoning/execution',
|
|
79
|
+
' SYSTEM_FAILURE — invalid, malformed, or unclassifiable input',
|
|
80
|
+
'',
|
|
81
|
+
`GOAL: ${goal}`,
|
|
82
|
+
'',
|
|
83
|
+
'Respond with ONLY valid JSON matching this schema:',
|
|
84
|
+
'{"category":"FACTUAL_LOOKUP|COMPLEX_SYNTHESIS|SYSTEM_FAILURE","rationale":"...","confidence":0.0-1.0}',
|
|
85
|
+
].join('\n');
|
|
86
|
+
try {
|
|
87
|
+
const result = await executor(classifierAgentId, { action: 'classify', params: { prompt } }, { agentId: classifierAgentId, taskId: `classify-${Date.now()}`, metadata: { type: 'route-classification' } });
|
|
88
|
+
if (!result.success || !result.data) {
|
|
89
|
+
return {
|
|
90
|
+
category: 'COMPLEX_SYNTHESIS',
|
|
91
|
+
rationale: 'Classifier returned no data — defaulting to COMPLEX_SYNTHESIS',
|
|
92
|
+
confidence: 0.5,
|
|
93
|
+
classifiedAt: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Parse response
|
|
97
|
+
let parsed;
|
|
98
|
+
const raw = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
|
|
99
|
+
try {
|
|
100
|
+
// Strip markdown fences
|
|
101
|
+
const cleaned = raw.replace(/```[a-z]*\n?/g, '').replace(/```/g, '').trim();
|
|
102
|
+
const start = cleaned.indexOf('{');
|
|
103
|
+
const end = cleaned.lastIndexOf('}');
|
|
104
|
+
parsed = JSON.parse(cleaned.substring(start, end + 1));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return {
|
|
108
|
+
category: 'COMPLEX_SYNTHESIS',
|
|
109
|
+
rationale: 'Classifier response parse failed — defaulting to COMPLEX_SYNTHESIS',
|
|
110
|
+
confidence: 0.4,
|
|
111
|
+
classifiedAt: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const validCategories = ['FACTUAL_LOOKUP', 'COMPLEX_SYNTHESIS', 'SYSTEM_FAILURE'];
|
|
115
|
+
const category = validCategories.includes(parsed.category) ? parsed.category : 'COMPLEX_SYNTHESIS';
|
|
116
|
+
return {
|
|
117
|
+
category,
|
|
118
|
+
rationale: parsed.rationale ?? '',
|
|
119
|
+
confidence: typeof parsed.confidence === 'number' ? Math.min(1, Math.max(0, parsed.confidence)) : undefined,
|
|
120
|
+
classifiedAt: Date.now(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
return {
|
|
125
|
+
category: 'COMPLEX_SYNTHESIS',
|
|
126
|
+
rationale: `Classifier error (${err.message}) — defaulting to COMPLEX_SYNTHESIS`,
|
|
127
|
+
confidence: 0.3,
|
|
128
|
+
classifiedAt: Date.now(),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// ROUTE CLASSIFIER
|
|
135
|
+
// ============================================================================
|
|
136
|
+
/**
|
|
137
|
+
* RouteClassifier evaluates a goal before DAG planning begins and decides
|
|
138
|
+
* whether to short-circuit to a single agent (FACTUAL_LOOKUP) or proceed
|
|
139
|
+
* with the full multi-agent pipeline (COMPLEX_SYNTHESIS).
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* const classifier = new RouteClassifier(createHeuristicClassifier());
|
|
144
|
+
* const { category } = await classifier.classify('What is the capital of France?');
|
|
145
|
+
* // category === 'FACTUAL_LOOKUP'
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
class RouteClassifier {
|
|
149
|
+
classifierFn;
|
|
150
|
+
options;
|
|
151
|
+
constructor(classifierFn, options = {}) {
|
|
152
|
+
this.classifierFn = classifierFn;
|
|
153
|
+
this.options = options;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Classify a goal.
|
|
157
|
+
*/
|
|
158
|
+
async classify(goal) {
|
|
159
|
+
if (!goal || typeof goal !== 'string') {
|
|
160
|
+
return {
|
|
161
|
+
category: 'SYSTEM_FAILURE',
|
|
162
|
+
rationale: 'Goal must be a non-empty string',
|
|
163
|
+
confidence: 1,
|
|
164
|
+
classifiedAt: Date.now(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return this.classifierFn(goal);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Classify a goal and, if FACTUAL_LOOKUP, short-circuit to a single agent.
|
|
171
|
+
*
|
|
172
|
+
* Returns the classification result and, when short-circuited, the agent's
|
|
173
|
+
* direct answer. The caller should check `result.shortCircuited` — if false,
|
|
174
|
+
* proceed with the normal DAG pipeline.
|
|
175
|
+
*
|
|
176
|
+
* @param goal - Natural language goal
|
|
177
|
+
* @param executor - Agent executor (required for FACTUAL_LOOKUP short-circuit)
|
|
178
|
+
* @param fallbackAgentId - Agent to call on FACTUAL_LOOKUP (overrides options.lookupAgentId)
|
|
179
|
+
*/
|
|
180
|
+
async route(goal, executor, fallbackAgentId) {
|
|
181
|
+
const classification = await this.classify(goal);
|
|
182
|
+
if (classification.category === 'SYSTEM_FAILURE') {
|
|
183
|
+
return {
|
|
184
|
+
classification,
|
|
185
|
+
shortCircuited: true,
|
|
186
|
+
error: `System failure: ${classification.rationale}`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (classification.category === 'FACTUAL_LOOKUP') {
|
|
190
|
+
const agentId = fallbackAgentId ?? this.options.lookupAgentId;
|
|
191
|
+
if (!executor || !agentId) {
|
|
192
|
+
// No executor configured — fall through to DAG pipeline
|
|
193
|
+
return { classification, shortCircuited: false };
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const result = await executor(agentId, { action: 'answer', params: { goal } }, { agentId, taskId: `lookup-${Date.now()}`, metadata: { type: 'factual-lookup' } });
|
|
197
|
+
return {
|
|
198
|
+
classification,
|
|
199
|
+
shortCircuited: true,
|
|
200
|
+
answer: result.data,
|
|
201
|
+
error: result.success ? undefined : result.error?.message,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
return {
|
|
206
|
+
classification,
|
|
207
|
+
shortCircuited: true,
|
|
208
|
+
error: err.message,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// COMPLEX_SYNTHESIS — proceed with DAG
|
|
213
|
+
return { classification, shortCircuited: false };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.RouteClassifier = RouteClassifier;
|
|
217
|
+
//# sourceMappingURL=route-classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-classifier.js","sourceRoot":"","sources":["../../lib/route-classifier.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAoEH,8DAwCC;AAQD,kDAyEC;AApID,+EAA+E;AAC/E,8DAA8D;AAC9D,+EAA+E;AAE/E;;;;;;GAMG;AACH,SAAgB,yBAAyB;IACvC,MAAM,eAAe,GAAG;QACtB,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU;QACjE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO;QACnE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS;KACzC,CAAC;IAEF,OAAO,KAAK,EAAE,IAAY,EAAiC,EAAE;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAE7C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO;gBACL,QAAQ,EAAE,gBAAgB;gBAC1B,SAAS,EAAE,eAAe;gBAC1B,UAAU,EAAE,CAAC;gBACb,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;aACzB,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QACjD,MAAM,OAAO,GAAG,SAAS,IAAI,EAAE,CAAC;QAChC,MAAM,kBAAkB,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,MAAM,gBAAgB,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAElD,IAAI,OAAO,IAAI,CAAC,kBAAkB,IAAI,gBAAgB,CAAC,EAAE,CAAC;YACxD,OAAO;gBACL,QAAQ,EAAE,gBAAgB;gBAC1B,SAAS,EAAE,eAAe,SAAS,sCAAsC;gBACzE,UAAU,EAAE,GAAG;gBACf,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;aACzB,CAAC;QACJ,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,mBAAmB;YAC7B,SAAS,EAAE,uCAAuC,SAAS,UAAU;YACrE,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;SACzB,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CACjC,QAI+E,EAC/E,iBAAyB;IAEzB,OAAO,KAAK,EAAE,IAAY,EAAiC,EAAE;QAC3D,MAAM,MAAM,GAAG;YACb,wDAAwD;YACxD,sEAAsE;YACtE,wEAAwE;YACxE,mEAAmE;YACnE,EAAE;YACF,SAAS,IAAI,EAAE;YACf,EAAE;YACF,oDAAoD;YACpD,uGAAuG;SACxG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAC3B,iBAAiB,EACjB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,EAC1C,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,YAAY,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE,CAC7G,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACpC,OAAO;oBACL,QAAQ,EAAE,mBAAmB;oBAC7B,SAAS,EAAE,+DAA+D;oBAC1E,UAAU,EAAE,GAAG;oBACf,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;iBACzB,CAAC;YACJ,CAAC;YAED,iBAAiB;YACjB,IAAI,MAA2E,CAAC;YAChF,MAAM,GAAG,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxF,IAAI,CAAC;gBACH,wBAAwB;gBACxB,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACnC,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBACrC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACzD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;oBACL,QAAQ,EAAE,mBAAmB;oBAC7B,SAAS,EAAE,oEAAoE;oBAC/E,UAAU,EAAE,GAAG;oBACf,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;iBACzB,CAAC;YACJ,CAAC;YAED,MAAM,eAAe,GAAoB,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,CAAC,CAAC;YACnG,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC;YAEnG,OAAO;gBACL,QAAQ;gBACR,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,EAAE;gBACjC,UAAU,EAAE,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC3G,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;aACzB,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,QAAQ,EAAE,mBAAmB;gBAC7B,SAAS,EAAE,qBAAsB,GAAa,CAAC,OAAO,qCAAqC;gBAC3F,UAAU,EAAE,GAAG;gBACf,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;aACzB,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,MAAa,eAAe;IAClB,YAAY,CAAqB;IACjC,OAAO,CAAyB;IAExC,YAAY,YAAgC,EAAE,UAAkC,EAAE;QAChF,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,IAAY;QACzB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtC,OAAO;gBACL,QAAQ,EAAE,gBAAgB;gBAC1B,SAAS,EAAE,iCAAiC;gBAC5C,UAAU,EAAE,CAAC;gBACb,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;aACzB,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,KAAK,CACT,IAAY,EACZ,QAI+E,EAC/E,eAAwB;QAExB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEjD,IAAI,cAAc,CAAC,QAAQ,KAAK,gBAAgB,EAAE,CAAC;YACjD,OAAO;gBACL,cAAc;gBACd,cAAc,EAAE,IAAI;gBACpB,KAAK,EAAE,mBAAmB,cAAc,CAAC,SAAS,EAAE;aACrD,CAAC;QACJ,CAAC;QAED,IAAI,cAAc,CAAC,QAAQ,KAAK,gBAAgB,EAAE,CAAC;YACjD,MAAM,OAAO,GAAG,eAAe,IAAI,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;YAE9D,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC1B,wDAAwD;gBACxD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;YACnD,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAC3B,OAAO,EACP,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,EACtC,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAClF,CAAC;gBACF,OAAO;oBACL,cAAc;oBACd,cAAc,EAAE,IAAI;oBACpB,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO;iBAC1D,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,cAAc;oBACd,cAAc,EAAE,IAAI;oBACpB,KAAK,EAAG,GAAa,CAAC,OAAO;iBAC9B,CAAC;YACJ,CAAC;QACH,CAAC;QAED,uCAAuC;QACvC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IACnD,CAAC;CACF;AAtFD,0CAsFC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "network-ai",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "AI agent orchestration framework for TypeScript/Node.js - 29 adapters (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, A2A, Codex, MiniMax, NemoClaw, APS, Copilot, LangGraph, Anthropic Computer Use, OpenAI Agents SDK, Vertex AI, Pydantic AI, Browser Agent, Hermes, Orchestrator, RLM + streaming variants). Built-in CLI, security, swarm intelligence, real-time streaming, and agentic workflow patterns.",
|
|
5
5
|
"homepage": "https://network-ai.org",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"test:phase9": "npx ts-node test-phase9.ts",
|
|
41
41
|
"test:phase10": "npx ts-node test-phase10.ts",
|
|
42
42
|
"test:topology": "npx ts-node test-topology.ts",
|
|
43
|
+
"test:phase12": "npx ts-node test-phase12.ts",
|
|
43
44
|
"test:all": "npx ts-node run-tests.ts",
|
|
44
45
|
"setup": "npx ts-node setup.ts",
|
|
45
46
|
"setup:check": "npx ts-node setup.ts --check",
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
"devDependencies": {
|
|
104
105
|
"@types/node": "^25.6.0",
|
|
105
106
|
"dotenv": "^17.4.2",
|
|
106
|
-
"openai": "^6.
|
|
107
|
+
"openai": "^6.36.0",
|
|
107
108
|
"ts-node": "^10.9.2",
|
|
108
109
|
"typescript": "^6.0.3"
|
|
109
110
|
},
|
package/socket.json
CHANGED