@kernel.chat/kbot 3.69.1 → 3.71.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/dist/a2a.d.ts +50 -5
- package/dist/a2a.js +305 -44
- package/dist/auth.d.ts +4 -0
- package/dist/auth.js +43 -4
- package/dist/doctor.js +53 -7
- package/dist/integrations/ableton-bridge.d.ts +158 -0
- package/dist/integrations/ableton-bridge.js +486 -0
- package/dist/integrations/ableton-m4l.d.ts +94 -0
- package/dist/integrations/ableton-m4l.js +252 -1
- package/dist/integrations/install-remote-script.d.ts +23 -0
- package/dist/integrations/install-remote-script.js +121 -0
- package/dist/machine.d.ts +1 -0
- package/dist/machine.js +17 -1
- package/dist/serve.js +3 -2
- package/dist/tools/a2a.d.ts +2 -0
- package/dist/tools/a2a.js +233 -0
- package/dist/tools/ableton-bridge-tools.d.ts +14 -0
- package/dist/tools/ableton-bridge-tools.js +327 -0
- package/dist/tools/ai-analysis.d.ts +2 -0
- package/dist/tools/ai-analysis.js +677 -0
- package/dist/tools/financial-analysis.d.ts +2 -0
- package/dist/tools/financial-analysis.js +945 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/music-gen.d.ts +2 -0
- package/dist/tools/music-gen.js +1006 -0
- package/dist/tools/threat-intel.d.ts +2 -0
- package/dist/tools/threat-intel.js +1619 -0
- package/package.json +2 -2
package/dist/a2a.d.ts
CHANGED
|
@@ -69,7 +69,13 @@ export interface RemoteAgent {
|
|
|
69
69
|
discoveredAt: string;
|
|
70
70
|
lastContactedAt?: string;
|
|
71
71
|
}
|
|
72
|
-
/** Build the Agent Card for this kbot instance
|
|
72
|
+
/** Build the Agent Card for this kbot instance.
|
|
73
|
+
*
|
|
74
|
+
* Exposes all 35 kbot agents as A2A skills — 26 specialists from
|
|
75
|
+
* the SPECIALISTS registry plus 9 preset agents from the matrix system.
|
|
76
|
+
* Each skill includes tags for capability-based discovery and optional
|
|
77
|
+
* example prompts.
|
|
78
|
+
*/
|
|
73
79
|
export declare function buildAgentCard(endpointUrl?: string): AgentCard;
|
|
74
80
|
export interface A2ARouteOptions {
|
|
75
81
|
/** Port the server is running on (for Agent Card URL) */
|
|
@@ -79,12 +85,51 @@ export interface A2ARouteOptions {
|
|
|
79
85
|
/** Bearer token for authentication (optional) */
|
|
80
86
|
token?: string;
|
|
81
87
|
}
|
|
88
|
+
/** Get a snapshot of the A2A server status for the a2a_status tool */
|
|
89
|
+
export declare function getA2AStatus(): {
|
|
90
|
+
server: {
|
|
91
|
+
running: boolean;
|
|
92
|
+
startedAt: string | null;
|
|
93
|
+
endpointUrl: string | null;
|
|
94
|
+
uptime: string | null;
|
|
95
|
+
};
|
|
96
|
+
tasks: {
|
|
97
|
+
received: number;
|
|
98
|
+
completed: number;
|
|
99
|
+
failed: number;
|
|
100
|
+
active: number;
|
|
101
|
+
stored: number;
|
|
102
|
+
};
|
|
103
|
+
capabilities: {
|
|
104
|
+
totalSkills: number;
|
|
105
|
+
skills: Array<{
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
tags: string[];
|
|
109
|
+
}>;
|
|
110
|
+
};
|
|
111
|
+
connections: {
|
|
112
|
+
uniqueClients: number;
|
|
113
|
+
clients: string[];
|
|
114
|
+
};
|
|
115
|
+
registry: {
|
|
116
|
+
remoteAgents: number;
|
|
117
|
+
agents: Array<{
|
|
118
|
+
url: string;
|
|
119
|
+
name: string;
|
|
120
|
+
skills: number;
|
|
121
|
+
lastContact: string | null;
|
|
122
|
+
}>;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
82
125
|
/**
|
|
83
|
-
*
|
|
126
|
+
* Create the A2A request handler.
|
|
127
|
+
*
|
|
128
|
+
* Supports two interfaces:
|
|
129
|
+
* 1. **JSON-RPC** (A2A spec) — POST to `/a2a` with JSON-RPC envelope
|
|
130
|
+
* 2. **REST** (backward-compatible) — GET/POST to `/a2a/tasks`, `/a2a/tasks/:id`, etc.
|
|
84
131
|
*
|
|
85
|
-
*
|
|
86
|
-
* request listener. Returns `true` if the request was handled by A2A routes,
|
|
87
|
-
* `false` if it should be passed to the next handler.
|
|
132
|
+
* Agent Card discovery is always at `GET /.well-known/agent.json`.
|
|
88
133
|
*/
|
|
89
134
|
export declare function createA2AHandler(options?: A2ARouteOptions): (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
|
|
90
135
|
/**
|
package/dist/a2a.js
CHANGED
|
@@ -45,74 +45,141 @@ function pruneTasks() {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
// ── Skill mapping ──
|
|
48
|
+
/** Tag map for all agents — specialists, preset agents, and domain experts.
|
|
49
|
+
* Used to populate A2A skill tags for discovery by external agents. */
|
|
50
|
+
const AGENT_TAG_MAP = {
|
|
51
|
+
// Core specialists
|
|
52
|
+
kernel: ['general', 'assistant', 'coordination'],
|
|
53
|
+
researcher: ['research', 'fact-checking', 'synthesis'],
|
|
54
|
+
coder: ['programming', 'code-generation', 'debugging', 'refactoring'],
|
|
55
|
+
writer: ['writing', 'documentation', 'content-creation'],
|
|
56
|
+
analyst: ['analysis', 'strategy', 'evaluation'],
|
|
57
|
+
// Extended specialists
|
|
58
|
+
aesthete: ['design', 'ui-ux', 'css', 'accessibility'],
|
|
59
|
+
guardian: ['security', 'vulnerability-scanning', 'owasp'],
|
|
60
|
+
curator: ['knowledge-management', 'documentation', 'indexing'],
|
|
61
|
+
strategist: ['business-strategy', 'roadmapping', 'competitive-analysis'],
|
|
62
|
+
// Domain specialists
|
|
63
|
+
infrastructure: ['devops', 'ci-cd', 'containers', 'cloud'],
|
|
64
|
+
quant: ['data-science', 'statistics', 'quantitative-analysis'],
|
|
65
|
+
investigator: ['deep-research', 'root-cause-analysis', 'forensics'],
|
|
66
|
+
oracle: ['predictions', 'trend-analysis', 'forecasting'],
|
|
67
|
+
chronist: ['history', 'timelines', 'changelog'],
|
|
68
|
+
sage: ['philosophy', 'wisdom', 'mental-models'],
|
|
69
|
+
communicator: ['communication', 'messaging', 'presentations'],
|
|
70
|
+
adapter: ['translation', 'format-conversion', 'migration'],
|
|
71
|
+
producer: ['music-production', 'ableton', 'daw', 'audio', 'midi'],
|
|
72
|
+
scientist: ['science', 'biology', 'chemistry', 'physics', 'earth-science', 'mathematics'],
|
|
73
|
+
immune: ['self-audit', 'bug-finding', 'security-hardening', 'code-quality'],
|
|
74
|
+
neuroscientist: ['neuroscience', 'brain', 'eeg', 'cognitive-science', 'neuroimaging'],
|
|
75
|
+
social_scientist: ['social-science', 'psychometrics', 'game-theory', 'econometrics', 'survey-design'],
|
|
76
|
+
philosopher: ['philosophy', 'logic', 'ethics', 'argumentation'],
|
|
77
|
+
epidemiologist: ['epidemiology', 'public-health', 'disease-modeling', 'surveillance'],
|
|
78
|
+
linguist: ['linguistics', 'phonetics', 'typology', 'corpus-analysis', 'nlp'],
|
|
79
|
+
historian: ['history', 'archival-research', 'digital-humanities', 'timelines'],
|
|
80
|
+
// Preset agents
|
|
81
|
+
hacker: ['offensive-security', 'penetration-testing', 'ctf', 'red-team'],
|
|
82
|
+
operator: ['automation', 'orchestration', 'task-execution', 'planning'],
|
|
83
|
+
dreamer: ['creative', 'worldbuilding', 'imagination', 'dream-interpretation'],
|
|
84
|
+
creative: ['generative-art', 'creative-coding', 'shaders', 'music', 'procedural-generation'],
|
|
85
|
+
developer: ['kbot', 'self-improvement', 'typescript', 'tooling'],
|
|
86
|
+
replit: ['replit', 'deployment', 'ai-integration', 'rapid-prototyping'],
|
|
87
|
+
gamedev: ['game-development', 'game-design', 'godot', 'unity', 'game-feel'],
|
|
88
|
+
playtester: ['game-testing', 'qa', 'game-feel', 'ux-testing', 'difficulty-analysis'],
|
|
89
|
+
trader: ['trading', 'markets', 'technical-analysis', 'portfolio', 'defi'],
|
|
90
|
+
};
|
|
48
91
|
/** Map specialist definitions to A2A skills */
|
|
49
92
|
function specialistToSkill(id, def) {
|
|
50
|
-
const tagMap = {
|
|
51
|
-
kernel: ['general', 'assistant', 'coordination'],
|
|
52
|
-
researcher: ['research', 'fact-checking', 'synthesis'],
|
|
53
|
-
coder: ['programming', 'code-generation', 'debugging', 'refactoring'],
|
|
54
|
-
writer: ['writing', 'documentation', 'content-creation'],
|
|
55
|
-
analyst: ['analysis', 'strategy', 'evaluation'],
|
|
56
|
-
aesthete: ['design', 'ui-ux', 'css', 'accessibility'],
|
|
57
|
-
guardian: ['security', 'vulnerability-scanning', 'owasp'],
|
|
58
|
-
curator: ['knowledge-management', 'documentation', 'indexing'],
|
|
59
|
-
strategist: ['business-strategy', 'roadmapping', 'competitive-analysis'],
|
|
60
|
-
infrastructure: ['devops', 'ci-cd', 'containers', 'cloud'],
|
|
61
|
-
quant: ['data-science', 'statistics', 'quantitative-analysis'],
|
|
62
|
-
investigator: ['deep-research', 'root-cause-analysis', 'forensics'],
|
|
63
|
-
oracle: ['predictions', 'trend-analysis', 'forecasting'],
|
|
64
|
-
chronist: ['history', 'timelines', 'changelog'],
|
|
65
|
-
sage: ['philosophy', 'wisdom', 'mental-models'],
|
|
66
|
-
communicator: ['communication', 'messaging', 'presentations'],
|
|
67
|
-
adapter: ['translation', 'format-conversion', 'migration'],
|
|
68
|
-
};
|
|
69
93
|
return {
|
|
70
94
|
id,
|
|
71
95
|
name: def.name,
|
|
72
96
|
description: def.prompt.split('\n')[0].replace(/^You are (?:an? )?/, ''),
|
|
73
|
-
tags:
|
|
97
|
+
tags: AGENT_TAG_MAP[id] || [id],
|
|
74
98
|
};
|
|
75
99
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
100
|
+
/** Skill descriptions for preset agents not in SPECIALISTS.
|
|
101
|
+
* These come from matrix.ts BUILTIN_AGENTS and PRESETS. */
|
|
102
|
+
const PRESET_AGENT_SKILLS = [
|
|
78
103
|
{
|
|
79
104
|
id: 'hacker',
|
|
80
105
|
name: 'Hacker',
|
|
81
|
-
description: 'Offensive security specialist and CTF solver — red team analysis',
|
|
82
|
-
tags: ['
|
|
106
|
+
description: 'Offensive security specialist and CTF solver — red team analysis, exploit chains, and remediation',
|
|
107
|
+
tags: AGENT_TAG_MAP['hacker'],
|
|
108
|
+
examples: ['Find auth bypass in this API', 'Solve this CTF challenge', 'Red team our deployment'],
|
|
83
109
|
},
|
|
84
110
|
{
|
|
85
111
|
id: 'operator',
|
|
86
112
|
name: 'Operator',
|
|
87
|
-
description: 'Autonomous executor — plans, executes, verifies, and reports',
|
|
88
|
-
tags: ['
|
|
113
|
+
description: 'Autonomous executor — plans, decomposes, executes, verifies, and reports multi-step tasks',
|
|
114
|
+
tags: AGENT_TAG_MAP['operator'],
|
|
115
|
+
examples: ['Refactor this module and update all tests', 'Set up CI/CD for this repo'],
|
|
89
116
|
},
|
|
90
117
|
{
|
|
91
118
|
id: 'dreamer',
|
|
92
119
|
name: 'Dreamer',
|
|
93
|
-
description: 'Liminal space explorer — dream interpretation, worldbuilding, vision engineering',
|
|
94
|
-
tags: ['
|
|
120
|
+
description: 'Liminal space explorer — dream interpretation, worldbuilding, and vision engineering',
|
|
121
|
+
tags: AGENT_TAG_MAP['dreamer'],
|
|
122
|
+
examples: ['Interpret this dream', 'Build a world with these constraints'],
|
|
95
123
|
},
|
|
96
124
|
{
|
|
97
125
|
id: 'creative',
|
|
98
126
|
name: 'Creative',
|
|
99
|
-
description: 'Generative art, creative coding, shaders, procedural generation',
|
|
100
|
-
tags: ['
|
|
127
|
+
description: 'Generative art, creative coding, shaders, procedural generation, and computational aesthetics',
|
|
128
|
+
tags: AGENT_TAG_MAP['creative'],
|
|
129
|
+
examples: ['Generate a p5.js particle system', 'Write a GLSL shader for this effect'],
|
|
101
130
|
},
|
|
102
131
|
{
|
|
103
132
|
id: 'developer',
|
|
104
133
|
name: 'Developer',
|
|
105
|
-
description: 'kbot self-improvement specialist — builds and
|
|
106
|
-
tags: ['
|
|
134
|
+
description: 'kbot self-improvement specialist — builds, extends, and optimizes kbot itself',
|
|
135
|
+
tags: AGENT_TAG_MAP['developer'],
|
|
136
|
+
examples: ['Add a new tool to kbot', 'Optimize the agent routing system'],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'replit',
|
|
140
|
+
name: 'Replit',
|
|
141
|
+
description: 'Replit specialist — builds, integrates, and deploys AI-powered systems on Replit',
|
|
142
|
+
tags: AGENT_TAG_MAP['replit'],
|
|
143
|
+
examples: ['Deploy this app on Replit', 'Set up Replit AI integration'],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'gamedev',
|
|
147
|
+
name: 'Game Developer',
|
|
148
|
+
description: 'Game development specialist — game design, Godot/Unity, game feel, systems design, and rapid prototyping',
|
|
149
|
+
tags: AGENT_TAG_MAP['gamedev'],
|
|
150
|
+
examples: ['Design a combat system', 'Build a Godot platformer prototype'],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'playtester',
|
|
154
|
+
name: 'Playtester',
|
|
155
|
+
description: 'Game QA and playtesting specialist — difficulty analysis, UX evaluation, game feel critique',
|
|
156
|
+
tags: AGENT_TAG_MAP['playtester'],
|
|
157
|
+
examples: ['Evaluate this game design', 'Test the difficulty curve'],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'trader',
|
|
161
|
+
name: 'Trader',
|
|
162
|
+
description: 'Trading and market analysis specialist — technical analysis, portfolio management, DeFi, and quantitative strategies',
|
|
163
|
+
tags: AGENT_TAG_MAP['trader'],
|
|
164
|
+
examples: ['Analyze this stock chart', 'Build a portfolio rebalancing strategy'],
|
|
107
165
|
},
|
|
108
166
|
];
|
|
109
167
|
// ── Agent Card ──
|
|
110
|
-
/** Build the Agent Card for this kbot instance
|
|
168
|
+
/** Build the Agent Card for this kbot instance.
|
|
169
|
+
*
|
|
170
|
+
* Exposes all 35 kbot agents as A2A skills — 26 specialists from
|
|
171
|
+
* the SPECIALISTS registry plus 9 preset agents from the matrix system.
|
|
172
|
+
* Each skill includes tags for capability-based discovery and optional
|
|
173
|
+
* example prompts.
|
|
174
|
+
*/
|
|
111
175
|
export function buildAgentCard(endpointUrl) {
|
|
112
176
|
const url = endpointUrl || `http://localhost:${DEFAULT_PORT}`;
|
|
177
|
+
// Deduplicate: PRESET_AGENT_SKILLS may overlap with SPECIALISTS keys
|
|
178
|
+
const specialistIds = new Set(Object.keys(SPECIALISTS));
|
|
179
|
+
const deduplicatedPresets = PRESET_AGENT_SKILLS.filter(s => !specialistIds.has(s.id));
|
|
113
180
|
const skills = [
|
|
114
181
|
...Object.entries(SPECIALISTS).map(([id, def]) => specialistToSkill(id, def)),
|
|
115
|
-
...
|
|
182
|
+
...deduplicatedPresets,
|
|
116
183
|
];
|
|
117
184
|
return {
|
|
118
185
|
name: 'kbot',
|
|
@@ -226,21 +293,180 @@ function readBody(req) {
|
|
|
226
293
|
req.on('error', reject);
|
|
227
294
|
});
|
|
228
295
|
}
|
|
296
|
+
const serverState = {
|
|
297
|
+
running: false,
|
|
298
|
+
startedAt: null,
|
|
299
|
+
endpointUrl: null,
|
|
300
|
+
tasksReceived: 0,
|
|
301
|
+
tasksCompleted: 0,
|
|
302
|
+
tasksFailed: 0,
|
|
303
|
+
activeConnections: new Set(),
|
|
304
|
+
};
|
|
305
|
+
/** Record an incoming connection (caller agent URL or IP) */
|
|
306
|
+
function trackConnection(req) {
|
|
307
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
308
|
+
const ip = typeof forwarded === 'string' ? forwarded.split(',')[0].trim() : req.socket.remoteAddress || 'unknown';
|
|
309
|
+
const userAgent = req.headers['user-agent'] || 'unknown';
|
|
310
|
+
serverState.activeConnections.add(`${ip} (${userAgent})`);
|
|
311
|
+
}
|
|
312
|
+
/** Get a snapshot of the A2A server status for the a2a_status tool */
|
|
313
|
+
export function getA2AStatus() {
|
|
314
|
+
const card = buildAgentCard(serverState.endpointUrl || undefined);
|
|
315
|
+
const registry = loadRegistry();
|
|
316
|
+
const activeTasks = [...tasks.values()].filter(t => t.status.state === 'working' || t.status.state === 'submitted');
|
|
317
|
+
let uptime = null;
|
|
318
|
+
if (serverState.startedAt) {
|
|
319
|
+
const ms = Date.now() - new Date(serverState.startedAt).getTime();
|
|
320
|
+
const hours = Math.floor(ms / 3_600_000);
|
|
321
|
+
const minutes = Math.floor((ms % 3_600_000) / 60_000);
|
|
322
|
+
const seconds = Math.floor((ms % 60_000) / 1_000);
|
|
323
|
+
uptime = `${hours}h ${minutes}m ${seconds}s`;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
server: {
|
|
327
|
+
running: serverState.running,
|
|
328
|
+
startedAt: serverState.startedAt,
|
|
329
|
+
endpointUrl: serverState.endpointUrl,
|
|
330
|
+
uptime,
|
|
331
|
+
},
|
|
332
|
+
tasks: {
|
|
333
|
+
received: serverState.tasksReceived,
|
|
334
|
+
completed: serverState.tasksCompleted,
|
|
335
|
+
failed: serverState.tasksFailed,
|
|
336
|
+
active: activeTasks.length,
|
|
337
|
+
stored: tasks.size,
|
|
338
|
+
},
|
|
339
|
+
capabilities: {
|
|
340
|
+
totalSkills: card.skills.length,
|
|
341
|
+
skills: card.skills.map(s => ({ id: s.id, name: s.name, tags: s.tags })),
|
|
342
|
+
},
|
|
343
|
+
connections: {
|
|
344
|
+
uniqueClients: serverState.activeConnections.size,
|
|
345
|
+
clients: [...serverState.activeConnections],
|
|
346
|
+
},
|
|
347
|
+
registry: {
|
|
348
|
+
remoteAgents: Object.keys(registry).length,
|
|
349
|
+
agents: Object.values(registry).map(r => ({
|
|
350
|
+
url: r.url,
|
|
351
|
+
name: r.card.name,
|
|
352
|
+
skills: r.card.skills.length,
|
|
353
|
+
lastContact: r.lastContactedAt || null,
|
|
354
|
+
})),
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** JSON-RPC error codes per the spec */
|
|
359
|
+
const JSONRPC_PARSE_ERROR = -32700;
|
|
360
|
+
const JSONRPC_INVALID_REQUEST = -32600;
|
|
361
|
+
const JSONRPC_METHOD_NOT_FOUND = -32601;
|
|
362
|
+
const JSONRPC_INVALID_PARAMS = -32602;
|
|
363
|
+
const JSONRPC_INTERNAL_ERROR = -32603;
|
|
364
|
+
const JSONRPC_TASK_NOT_FOUND = -32001;
|
|
365
|
+
const JSONRPC_TASK_NOT_CANCELABLE = -32002;
|
|
366
|
+
function jsonRpcSuccess(id, result) {
|
|
367
|
+
return { jsonrpc: '2.0', id, result };
|
|
368
|
+
}
|
|
369
|
+
function jsonRpcError(id, code, message, data) {
|
|
370
|
+
return { jsonrpc: '2.0', id, error: { code, message, ...(data !== undefined ? { data } : {}) } };
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Handle a JSON-RPC request per the A2A protocol spec.
|
|
374
|
+
*
|
|
375
|
+
* Supported methods:
|
|
376
|
+
* - tasks/send — submit a task (sync execution)
|
|
377
|
+
* - tasks/get — get task status and result
|
|
378
|
+
* - tasks/cancel — cancel a running task
|
|
379
|
+
* - tasks/sendSubscribe — submit a task (async, returns immediately)
|
|
380
|
+
*/
|
|
381
|
+
async function handleJsonRpc(rpcReq) {
|
|
382
|
+
const { id, method, params } = rpcReq;
|
|
383
|
+
switch (method) {
|
|
384
|
+
case 'tasks/send': {
|
|
385
|
+
const message = params?.message;
|
|
386
|
+
if (!message?.parts || !message?.role) {
|
|
387
|
+
return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { message: { role, parts } }');
|
|
388
|
+
}
|
|
389
|
+
const metadata = params?.metadata;
|
|
390
|
+
const task = createTask(message, metadata);
|
|
391
|
+
serverState.tasksReceived++;
|
|
392
|
+
const completed = await executeTask(task);
|
|
393
|
+
if (completed.status.state === 'completed')
|
|
394
|
+
serverState.tasksCompleted++;
|
|
395
|
+
if (completed.status.state === 'failed')
|
|
396
|
+
serverState.tasksFailed++;
|
|
397
|
+
return jsonRpcSuccess(id, taskToResponse(completed));
|
|
398
|
+
}
|
|
399
|
+
case 'tasks/sendSubscribe': {
|
|
400
|
+
const message = params?.message;
|
|
401
|
+
if (!message?.parts || !message?.role) {
|
|
402
|
+
return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { message: { role, parts } }');
|
|
403
|
+
}
|
|
404
|
+
const metadata = params?.metadata;
|
|
405
|
+
const task = createTask(message, metadata);
|
|
406
|
+
serverState.tasksReceived++;
|
|
407
|
+
// Async: return immediately, execute in background
|
|
408
|
+
executeTask(task).then(() => {
|
|
409
|
+
if (task.status.state === 'completed')
|
|
410
|
+
serverState.tasksCompleted++;
|
|
411
|
+
if (task.status.state === 'failed')
|
|
412
|
+
serverState.tasksFailed++;
|
|
413
|
+
}).catch(() => {
|
|
414
|
+
task.status = { state: 'failed', message: 'Background execution failed', timestamp: new Date().toISOString() };
|
|
415
|
+
serverState.tasksFailed++;
|
|
416
|
+
});
|
|
417
|
+
return jsonRpcSuccess(id, taskToResponse(task));
|
|
418
|
+
}
|
|
419
|
+
case 'tasks/get': {
|
|
420
|
+
const taskId = params?.id;
|
|
421
|
+
if (!taskId) {
|
|
422
|
+
return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { id: string }');
|
|
423
|
+
}
|
|
424
|
+
const task = tasks.get(taskId);
|
|
425
|
+
if (!task) {
|
|
426
|
+
return jsonRpcError(id, JSONRPC_TASK_NOT_FOUND, `Task ${taskId} not found`);
|
|
427
|
+
}
|
|
428
|
+
return jsonRpcSuccess(id, taskToResponse(task));
|
|
429
|
+
}
|
|
430
|
+
case 'tasks/cancel': {
|
|
431
|
+
const taskId = params?.id;
|
|
432
|
+
if (!taskId) {
|
|
433
|
+
return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { id: string }');
|
|
434
|
+
}
|
|
435
|
+
const task = tasks.get(taskId);
|
|
436
|
+
if (!task) {
|
|
437
|
+
return jsonRpcError(id, JSONRPC_TASK_NOT_FOUND, `Task ${taskId} not found`);
|
|
438
|
+
}
|
|
439
|
+
if (task.status.state === 'completed' || task.status.state === 'failed') {
|
|
440
|
+
return jsonRpcError(id, JSONRPC_TASK_NOT_CANCELABLE, `Cannot cancel task in '${task.status.state}' state`);
|
|
441
|
+
}
|
|
442
|
+
task.status = { state: 'canceled', timestamp: new Date().toISOString() };
|
|
443
|
+
return jsonRpcSuccess(id, taskToResponse(task));
|
|
444
|
+
}
|
|
445
|
+
default:
|
|
446
|
+
return jsonRpcError(id, JSONRPC_METHOD_NOT_FOUND, `Unknown method: ${method}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
229
449
|
/**
|
|
230
|
-
*
|
|
450
|
+
* Create the A2A request handler.
|
|
451
|
+
*
|
|
452
|
+
* Supports two interfaces:
|
|
453
|
+
* 1. **JSON-RPC** (A2A spec) — POST to `/a2a` with JSON-RPC envelope
|
|
454
|
+
* 2. **REST** (backward-compatible) — GET/POST to `/a2a/tasks`, `/a2a/tasks/:id`, etc.
|
|
231
455
|
*
|
|
232
|
-
*
|
|
233
|
-
* request listener. Returns `true` if the request was handled by A2A routes,
|
|
234
|
-
* `false` if it should be passed to the next handler.
|
|
456
|
+
* Agent Card discovery is always at `GET /.well-known/agent.json`.
|
|
235
457
|
*/
|
|
236
458
|
export function createA2AHandler(options = {}) {
|
|
237
459
|
const baseUrl = options.endpointUrl || `http://localhost:${options.port || DEFAULT_PORT}`;
|
|
238
460
|
const card = buildAgentCard(baseUrl);
|
|
461
|
+
// Mark server as running
|
|
462
|
+
serverState.running = true;
|
|
463
|
+
serverState.startedAt = new Date().toISOString();
|
|
464
|
+
serverState.endpointUrl = baseUrl;
|
|
239
465
|
return async (req, res) => {
|
|
240
466
|
const url = new URL(req.url || '/', `http://localhost`);
|
|
241
467
|
const path = url.pathname;
|
|
242
468
|
// CORS preflight for A2A routes
|
|
243
|
-
if (req.method === 'OPTIONS' && (path === '/.well-known/agent.json' || path.startsWith('/a2a
|
|
469
|
+
if (req.method === 'OPTIONS' && (path === '/.well-known/agent.json' || path.startsWith('/a2a'))) {
|
|
244
470
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
245
471
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
246
472
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
@@ -249,7 +475,7 @@ export function createA2AHandler(options = {}) {
|
|
|
249
475
|
return true;
|
|
250
476
|
}
|
|
251
477
|
// Auth check for task endpoints
|
|
252
|
-
if (options.token && path.startsWith('/a2a
|
|
478
|
+
if (options.token && path.startsWith('/a2a')) {
|
|
253
479
|
const auth = req.headers.authorization;
|
|
254
480
|
const bearerToken = auth?.startsWith('Bearer ') ? auth.slice(7) : null;
|
|
255
481
|
const tokenBuf = Buffer.from(options.token);
|
|
@@ -261,11 +487,35 @@ export function createA2AHandler(options = {}) {
|
|
|
261
487
|
}
|
|
262
488
|
// GET /.well-known/agent.json — Agent Card discovery
|
|
263
489
|
if (path === '/.well-known/agent.json' && req.method === 'GET') {
|
|
490
|
+
trackConnection(req);
|
|
264
491
|
sendJson(res, 200, card);
|
|
265
492
|
return true;
|
|
266
493
|
}
|
|
267
|
-
// POST /a2a
|
|
494
|
+
// POST /a2a — JSON-RPC endpoint (A2A protocol spec)
|
|
495
|
+
if (path === '/a2a' && req.method === 'POST') {
|
|
496
|
+
trackConnection(req);
|
|
497
|
+
try {
|
|
498
|
+
const rawBody = await readBody(req);
|
|
499
|
+
const parsed = JSON.parse(rawBody);
|
|
500
|
+
// Validate JSON-RPC envelope
|
|
501
|
+
if (typeof parsed !== 'object' || parsed === null ||
|
|
502
|
+
parsed.jsonrpc !== '2.0' ||
|
|
503
|
+
typeof parsed.method !== 'string') {
|
|
504
|
+
sendJson(res, 200, jsonRpcError(parsed?.id ?? null, JSONRPC_INVALID_REQUEST, 'Invalid JSON-RPC request: expected { jsonrpc: "2.0", method: string, id: string|number }'));
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
const rpcReq = parsed;
|
|
508
|
+
const rpcRes = await handleJsonRpc(rpcReq);
|
|
509
|
+
sendJson(res, 200, rpcRes);
|
|
510
|
+
}
|
|
511
|
+
catch (err) {
|
|
512
|
+
sendJson(res, 200, jsonRpcError(null, JSONRPC_PARSE_ERROR, 'Parse error: ' + (err instanceof Error ? err.message : 'invalid JSON')));
|
|
513
|
+
}
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
// POST /a2a/tasks — Submit a new task (REST, backward-compatible)
|
|
268
517
|
if (path === '/a2a/tasks' && req.method === 'POST') {
|
|
518
|
+
trackConnection(req);
|
|
269
519
|
try {
|
|
270
520
|
const body = JSON.parse(await readBody(req));
|
|
271
521
|
if (!body.message || !body.message.parts || !body.message.role) {
|
|
@@ -273,19 +523,30 @@ export function createA2AHandler(options = {}) {
|
|
|
273
523
|
return true;
|
|
274
524
|
}
|
|
275
525
|
const task = createTask(body.message, body.metadata);
|
|
526
|
+
serverState.tasksReceived++;
|
|
276
527
|
// Check if caller wants synchronous execution
|
|
277
528
|
const sync = url.searchParams.get('sync') === 'true';
|
|
278
529
|
if (sync) {
|
|
279
530
|
// Synchronous: execute and return completed task
|
|
280
531
|
const completed = await executeTask(task);
|
|
532
|
+
if (completed.status.state === 'completed')
|
|
533
|
+
serverState.tasksCompleted++;
|
|
534
|
+
if (completed.status.state === 'failed')
|
|
535
|
+
serverState.tasksFailed++;
|
|
281
536
|
sendJson(res, 200, taskToResponse(completed));
|
|
282
537
|
}
|
|
283
538
|
else {
|
|
284
539
|
// Asynchronous: return immediately with submitted status, execute in background
|
|
285
540
|
sendJson(res, 202, taskToResponse(task));
|
|
286
541
|
// Fire and forget — task will be updated in the store
|
|
287
|
-
executeTask(task).
|
|
542
|
+
executeTask(task).then(() => {
|
|
543
|
+
if (task.status.state === 'completed')
|
|
544
|
+
serverState.tasksCompleted++;
|
|
545
|
+
if (task.status.state === 'failed')
|
|
546
|
+
serverState.tasksFailed++;
|
|
547
|
+
}).catch(() => {
|
|
288
548
|
task.status = { state: 'failed', message: 'Background execution failed', timestamp: new Date().toISOString() };
|
|
549
|
+
serverState.tasksFailed++;
|
|
289
550
|
});
|
|
290
551
|
}
|
|
291
552
|
}
|
|
@@ -294,7 +555,7 @@ export function createA2AHandler(options = {}) {
|
|
|
294
555
|
}
|
|
295
556
|
return true;
|
|
296
557
|
}
|
|
297
|
-
// GET /a2a/tasks/:id — Get task status and result
|
|
558
|
+
// GET /a2a/tasks/:id — Get task status and result (REST)
|
|
298
559
|
const taskStatusMatch = path.match(/^\/a2a\/tasks\/([a-f0-9-]+)$/);
|
|
299
560
|
if (taskStatusMatch && req.method === 'GET') {
|
|
300
561
|
const taskId = taskStatusMatch[1];
|
|
@@ -306,7 +567,7 @@ export function createA2AHandler(options = {}) {
|
|
|
306
567
|
sendJson(res, 200, taskToResponse(task));
|
|
307
568
|
return true;
|
|
308
569
|
}
|
|
309
|
-
// POST /a2a/tasks/:id/cancel — Cancel a task
|
|
570
|
+
// POST /a2a/tasks/:id/cancel — Cancel a task (REST)
|
|
310
571
|
const taskCancelMatch = path.match(/^\/a2a\/tasks\/([a-f0-9-]+)\/cancel$/);
|
|
311
572
|
if (taskCancelMatch && req.method === 'POST') {
|
|
312
573
|
const taskId = taskCancelMatch[1];
|
package/dist/auth.d.ts
CHANGED
|
@@ -64,6 +64,10 @@ export declare function disableByok(): void;
|
|
|
64
64
|
export declare function isOllamaRunning(): Promise<boolean>;
|
|
65
65
|
/** List available Ollama models */
|
|
66
66
|
export declare function listOllamaModels(): Promise<string[]>;
|
|
67
|
+
/** Fetch the Ollama server version string (e.g. "0.19.0"), or null if unreachable */
|
|
68
|
+
export declare function getOllamaVersion(): Promise<string | null>;
|
|
69
|
+
/** Detect whether Ollama is using the MLX backend (Ollama 0.19+ on Apple Silicon) */
|
|
70
|
+
export declare function isOllamaMLXBackend(): Promise<boolean>;
|
|
67
71
|
/** Set up Ollama as the active provider */
|
|
68
72
|
export declare function setupOllama(model?: string): Promise<boolean>;
|
|
69
73
|
/** Check if LM Studio is running locally (default port 1234) */
|
package/dist/auth.js
CHANGED
|
@@ -78,12 +78,12 @@ export const PROVIDERS = {
|
|
|
78
78
|
name: 'DeepSeek',
|
|
79
79
|
apiUrl: 'https://api.deepseek.com/chat/completions',
|
|
80
80
|
apiStyle: 'openai',
|
|
81
|
-
defaultModel: 'deepseek-v4',
|
|
81
|
+
defaultModel: 'deepseek-v4', // ~1T MoE (37B active), 1M context
|
|
82
82
|
fastModel: 'deepseek-chat',
|
|
83
|
-
inputCost: 0.
|
|
84
|
-
outputCost:
|
|
83
|
+
inputCost: 0.50, // V4 pricing (cache hit: $0.10)
|
|
84
|
+
outputCost: 2.0,
|
|
85
85
|
authHeader: 'bearer',
|
|
86
|
-
models: ['deepseek-v4', 'deepseek-chat', 'deepseek-reasoner'],
|
|
86
|
+
models: ['deepseek-v4', 'deepseek-v4-reasoner', 'deepseek-chat', 'deepseek-reasoner'],
|
|
87
87
|
},
|
|
88
88
|
groq: {
|
|
89
89
|
name: 'Groq',
|
|
@@ -661,6 +661,45 @@ export async function listOllamaModels() {
|
|
|
661
661
|
return [];
|
|
662
662
|
}
|
|
663
663
|
}
|
|
664
|
+
/** Fetch the Ollama server version string (e.g. "0.19.0"), or null if unreachable */
|
|
665
|
+
export async function getOllamaVersion() {
|
|
666
|
+
try {
|
|
667
|
+
const res = await fetch(`${OLLAMA_HOST}/api/version`, { signal: AbortSignal.timeout(2000) });
|
|
668
|
+
if (!res.ok)
|
|
669
|
+
return null;
|
|
670
|
+
const data = await res.json();
|
|
671
|
+
return data.version ?? null;
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/** Detect whether Ollama is using the MLX backend (Ollama 0.19+ on Apple Silicon) */
|
|
678
|
+
export async function isOllamaMLXBackend() {
|
|
679
|
+
try {
|
|
680
|
+
// Ollama 0.19+ exposes backend info in /api/ps (running models list)
|
|
681
|
+
// If the response contains "mlx" in the backend or accelerator field, MLX is active.
|
|
682
|
+
// Fallback: check Ollama version >= 0.19 on Apple Silicon (MLX is the default).
|
|
683
|
+
const res = await fetch(`${OLLAMA_HOST}/api/ps`, { signal: AbortSignal.timeout(2000) });
|
|
684
|
+
if (res.ok) {
|
|
685
|
+
const text = await res.text();
|
|
686
|
+
if (/mlx/i.test(text))
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
// Heuristic: Ollama 0.19+ on arm64 darwin defaults to MLX
|
|
690
|
+
const version = await getOllamaVersion();
|
|
691
|
+
if (version && process.arch === 'arm64' && process.platform === 'darwin') {
|
|
692
|
+
const parts = version.split('.').map(Number);
|
|
693
|
+
const [major = 0, minor = 0] = parts;
|
|
694
|
+
if (major > 0 || minor >= 19)
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
catch {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
664
703
|
/** Set up Ollama as the active provider */
|
|
665
704
|
export async function setupOllama(model) {
|
|
666
705
|
const running = await isOllamaRunning();
|