@tjamescouch/agentchat 0.22.1 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +1 -1
- package/dist/bin/agentchat.d.ts +7 -0
- package/dist/bin/agentchat.d.ts.map +1 -0
- package/dist/bin/agentchat.js +1511 -0
- package/dist/bin/agentchat.js.map +1 -0
- package/dist/lib/allowlist.d.ts +77 -0
- package/dist/lib/allowlist.d.ts.map +1 -0
- package/dist/lib/allowlist.js +151 -0
- package/dist/lib/allowlist.js.map +1 -0
- package/dist/lib/client.d.ts +147 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +704 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/daemon.d.ts +122 -0
- package/dist/lib/daemon.d.ts.map +1 -0
- package/dist/lib/daemon.js +523 -0
- package/dist/lib/daemon.js.map +1 -0
- package/dist/lib/deploy/akash.d.ts +271 -0
- package/dist/lib/deploy/akash.d.ts.map +1 -0
- package/dist/lib/deploy/akash.js +671 -0
- package/dist/lib/deploy/akash.js.map +1 -0
- package/dist/lib/deploy/config.d.ts +62 -0
- package/dist/lib/deploy/config.d.ts.map +1 -0
- package/dist/lib/deploy/config.js +116 -0
- package/dist/lib/deploy/config.js.map +1 -0
- package/dist/lib/deploy/docker.d.ts +37 -0
- package/dist/lib/deploy/docker.d.ts.map +1 -0
- package/dist/lib/deploy/docker.js +122 -0
- package/dist/lib/deploy/docker.js.map +1 -0
- package/dist/lib/deploy/index.d.ts +11 -0
- package/dist/lib/deploy/index.d.ts.map +1 -0
- package/dist/lib/deploy/index.js +11 -0
- package/dist/lib/deploy/index.js.map +1 -0
- package/dist/lib/escrow-hooks.d.ts +199 -0
- package/dist/lib/escrow-hooks.d.ts.map +1 -0
- package/dist/lib/escrow-hooks.js +221 -0
- package/dist/lib/escrow-hooks.js.map +1 -0
- package/dist/lib/identity.d.ts +134 -0
- package/dist/lib/identity.d.ts.map +1 -0
- package/dist/lib/identity.js +334 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/jitter.d.ts +42 -0
- package/dist/lib/jitter.d.ts.map +1 -0
- package/{lib/jitter.ts → dist/lib/jitter.js} +10 -18
- package/dist/lib/jitter.js.map +1 -0
- package/dist/lib/proposals.d.ts +223 -0
- package/dist/lib/proposals.d.ts.map +1 -0
- package/dist/lib/proposals.js +379 -0
- package/dist/lib/proposals.js.map +1 -0
- package/dist/lib/protocol.d.ts +220 -0
- package/dist/lib/protocol.d.ts.map +1 -0
- package/dist/lib/protocol.js +507 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/receipts.d.ts +134 -0
- package/dist/lib/receipts.d.ts.map +1 -0
- package/dist/lib/receipts.js +270 -0
- package/dist/lib/receipts.js.map +1 -0
- package/dist/lib/reputation.d.ts +250 -0
- package/dist/lib/reputation.d.ts.map +1 -0
- package/dist/lib/reputation.js +586 -0
- package/dist/lib/reputation.js.map +1 -0
- package/dist/lib/security.d.ts +27 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/security.js +150 -0
- package/dist/lib/security.js.map +1 -0
- package/dist/lib/server/handlers/admin.d.ts +26 -0
- package/dist/lib/server/handlers/admin.d.ts.map +1 -0
- package/dist/lib/server/handlers/admin.js +76 -0
- package/dist/lib/server/handlers/admin.js.map +1 -0
- package/dist/lib/server/handlers/identity.d.ts +36 -0
- package/dist/lib/server/handlers/identity.d.ts.map +1 -0
- package/dist/lib/server/handlers/identity.js +330 -0
- package/dist/lib/server/handlers/identity.js.map +1 -0
- package/dist/lib/server/handlers/index.d.ts +10 -0
- package/dist/lib/server/handlers/index.d.ts.map +1 -0
- package/dist/lib/server/handlers/index.js +15 -0
- package/dist/lib/server/handlers/index.js.map +1 -0
- package/dist/lib/server/handlers/message.d.ts +47 -0
- package/dist/lib/server/handlers/message.d.ts.map +1 -0
- package/dist/lib/server/handlers/message.js +265 -0
- package/dist/lib/server/handlers/message.js.map +1 -0
- package/dist/lib/server/handlers/presence.d.ts +18 -0
- package/dist/lib/server/handlers/presence.d.ts.map +1 -0
- package/dist/lib/server/handlers/presence.js +35 -0
- package/dist/lib/server/handlers/presence.js.map +1 -0
- package/dist/lib/server/handlers/proposal.d.ts +38 -0
- package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
- package/dist/lib/server/handlers/proposal.js +273 -0
- package/dist/lib/server/handlers/proposal.js.map +1 -0
- package/dist/lib/server/handlers/skills.d.ts +22 -0
- package/dist/lib/server/handlers/skills.d.ts.map +1 -0
- package/dist/lib/server/handlers/skills.js +119 -0
- package/dist/lib/server/handlers/skills.js.map +1 -0
- package/dist/lib/server-directory.d.ts +85 -0
- package/dist/lib/server-directory.d.ts.map +1 -0
- package/dist/lib/server-directory.js +177 -0
- package/dist/lib/server-directory.js.map +1 -0
- package/dist/lib/server.d.ts +162 -0
- package/dist/lib/server.d.ts.map +1 -0
- package/dist/lib/server.js +602 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/lib/types.d.ts +461 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +98 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +22 -13
- package/bin/agentchat.js +0 -1617
- package/bin/agentchat.ts +0 -1812
- package/lib/allowlist.js +0 -162
- package/lib/chat.py +0 -241
- package/lib/client.js +0 -821
- package/lib/client.ts +0 -877
- package/lib/daemon.js +0 -562
- package/lib/daemon.ts +0 -662
- package/lib/deploy/akash.js +0 -811
- package/lib/deploy/config.js +0 -128
- package/lib/deploy/docker.js +0 -132
- package/lib/deploy/index.js +0 -24
- package/lib/elo_swarm.py +0 -569
- package/lib/escrow-hooks.js +0 -237
- package/lib/escrow-hooks.ts +0 -391
- package/lib/identity.js +0 -376
- package/lib/identity.ts +0 -412
- package/lib/jitter.js +0 -54
- package/lib/proposals.js +0 -426
- package/lib/proposals.ts +0 -612
- package/lib/protocol.js +0 -516
- package/lib/receipts.js +0 -294
- package/lib/receipts.ts +0 -359
- package/lib/reputation.js +0 -664
- package/lib/reputation.ts +0 -790
- package/lib/security.js +0 -183
- package/lib/server/handlers/admin.js +0 -94
- package/lib/server/handlers/identity.js +0 -258
- package/lib/server/handlers/index.js +0 -42
- package/lib/server/handlers/message.js +0 -319
- package/lib/server/handlers/presence.js +0 -45
- package/lib/server/handlers/proposal.js +0 -358
- package/lib/server/handlers/skills.js +0 -141
- package/lib/server-directory.js +0 -190
- package/lib/server-directory.ts +0 -232
- package/lib/server.js +0 -633
- package/lib/server.ts +0 -698
- package/lib/supervisor/USAGE.md +0 -110
- package/lib/supervisor/agent-health.sh +0 -107
- package/lib/supervisor/agent-monitor.sh +0 -123
- package/lib/supervisor/agent-supervisor.sh +0 -135
- package/lib/supervisor/agentctl.sh +0 -266
- package/lib/supervisor/god-backup.sh +0 -126
- package/lib/supervisor/god-watchdog.sh +0 -107
- package/lib/supervisor/killswitch.sh +0 -43
- package/lib/supervisor/notify.sh +0 -19
- package/lib/types.ts +0 -433
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skills Handlers
|
|
3
|
-
* Handles skill registration and search
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
ServerMessageType,
|
|
8
|
-
ErrorCode,
|
|
9
|
-
createMessage,
|
|
10
|
-
createError,
|
|
11
|
-
} from '../../protocol.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Handle REGISTER_SKILLS command
|
|
15
|
-
*/
|
|
16
|
-
export function handleRegisterSkills(server, ws, msg) {
|
|
17
|
-
const agent = server.agents.get(ws);
|
|
18
|
-
if (!agent) {
|
|
19
|
-
server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (!agent.pubkey) {
|
|
24
|
-
server._send(ws, createError(ErrorCode.SIGNATURE_REQUIRED, 'Skill registration requires persistent identity'));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Store skills for this agent
|
|
29
|
-
const registration = {
|
|
30
|
-
agent_id: `@${agent.id}`,
|
|
31
|
-
skills: msg.skills,
|
|
32
|
-
registered_at: Date.now(),
|
|
33
|
-
sig: msg.sig
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
server.skillsRegistry.set(agent.id, registration);
|
|
37
|
-
|
|
38
|
-
server._log('skills_registered', { agent: agent.id, count: msg.skills.length });
|
|
39
|
-
|
|
40
|
-
// Notify the registering agent
|
|
41
|
-
server._send(ws, createMessage(ServerMessageType.SKILLS_REGISTERED, {
|
|
42
|
-
agent_id: `@${agent.id}`,
|
|
43
|
-
skills_count: msg.skills.length,
|
|
44
|
-
registered_at: registration.registered_at
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
// Optionally broadcast to #discovery channel if it exists
|
|
48
|
-
if (server.channels.has('#discovery')) {
|
|
49
|
-
server._broadcast('#discovery', createMessage(ServerMessageType.MSG, {
|
|
50
|
-
from: '@server',
|
|
51
|
-
to: '#discovery',
|
|
52
|
-
content: `Agent @${agent.id} registered ${msg.skills.length} skill(s): ${msg.skills.map(s => s.capability).join(', ')}`
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Handle SEARCH_SKILLS command
|
|
59
|
-
*/
|
|
60
|
-
export async function handleSearchSkills(server, ws, msg) {
|
|
61
|
-
const agent = server.agents.get(ws);
|
|
62
|
-
if (!agent) {
|
|
63
|
-
server._send(ws, createError(ErrorCode.AUTH_REQUIRED, 'Must IDENTIFY first'));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const query = msg.query || {};
|
|
68
|
-
const results = [];
|
|
69
|
-
|
|
70
|
-
// Search through all registered skills
|
|
71
|
-
for (const [agentId, registration] of server.skillsRegistry) {
|
|
72
|
-
for (const skill of registration.skills) {
|
|
73
|
-
let matches = true;
|
|
74
|
-
|
|
75
|
-
// Filter by capability (substring match, case-insensitive)
|
|
76
|
-
if (query.capability) {
|
|
77
|
-
const cap = skill.capability.toLowerCase();
|
|
78
|
-
const search = query.capability.toLowerCase();
|
|
79
|
-
if (!cap.includes(search)) {
|
|
80
|
-
matches = false;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Filter by max_rate
|
|
85
|
-
if (query.max_rate !== undefined && skill.rate !== undefined) {
|
|
86
|
-
if (skill.rate > query.max_rate) {
|
|
87
|
-
matches = false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Filter by currency
|
|
92
|
-
if (query.currency && skill.currency) {
|
|
93
|
-
if (skill.currency.toLowerCase() !== query.currency.toLowerCase()) {
|
|
94
|
-
matches = false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (matches) {
|
|
99
|
-
results.push({
|
|
100
|
-
agent_id: registration.agent_id,
|
|
101
|
-
...skill,
|
|
102
|
-
registered_at: registration.registered_at
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Enrich results with reputation data
|
|
109
|
-
const uniqueAgentIds = [...new Set(results.map(r => r.agent_id))];
|
|
110
|
-
const ratingCache = new Map();
|
|
111
|
-
for (const agentId of uniqueAgentIds) {
|
|
112
|
-
const ratingInfo = await server.reputationStore.getRating(agentId);
|
|
113
|
-
ratingCache.set(agentId, ratingInfo);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Add rating info to each result
|
|
117
|
-
for (const result of results) {
|
|
118
|
-
const ratingInfo = ratingCache.get(result.agent_id);
|
|
119
|
-
result.rating = ratingInfo.rating;
|
|
120
|
-
result.transactions = ratingInfo.transactions;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Sort by rating (highest first), then by registration time
|
|
124
|
-
results.sort((a, b) => {
|
|
125
|
-
if (b.rating !== a.rating) return b.rating - a.rating;
|
|
126
|
-
return b.registered_at - a.registered_at;
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Limit results
|
|
130
|
-
const limit = query.limit || 50;
|
|
131
|
-
const limitedResults = results.slice(0, limit);
|
|
132
|
-
|
|
133
|
-
server._log('skills_search', { agent: agent.id, query, results_count: limitedResults.length });
|
|
134
|
-
|
|
135
|
-
server._send(ws, createMessage(ServerMessageType.SEARCH_RESULTS, {
|
|
136
|
-
query_id: msg.query_id || null,
|
|
137
|
-
query,
|
|
138
|
-
results: limitedResults,
|
|
139
|
-
total: results.length
|
|
140
|
-
}));
|
|
141
|
-
}
|
package/lib/server-directory.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server Directory
|
|
3
|
-
* Registry of known AgentChat servers for discovery
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import http from 'http';
|
|
7
|
-
import https from 'https';
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
|
|
11
|
-
// Default public servers (can be extended)
|
|
12
|
-
export const DEFAULT_SERVERS = process.env.AGENTCHAT_PUBLIC === 'true'
|
|
13
|
-
? [
|
|
14
|
-
{
|
|
15
|
-
name: 'AgentChat Public',
|
|
16
|
-
url: 'wss://agentchat-server.fly.dev',
|
|
17
|
-
description: 'Official public AgentChat server',
|
|
18
|
-
region: 'global'
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
: [
|
|
22
|
-
{
|
|
23
|
-
name: 'AgentChat Local',
|
|
24
|
-
url: 'ws://localhost:6667',
|
|
25
|
-
description: 'Local AgentChat server',
|
|
26
|
-
region: 'local'
|
|
27
|
-
}
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
// Default directory file path
|
|
31
|
-
export const DEFAULT_DIRECTORY_PATH = path.join(
|
|
32
|
-
process.env.HOME || process.env.USERPROFILE || '.',
|
|
33
|
-
'.agentchat',
|
|
34
|
-
'servers.json'
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Server Directory for discovering AgentChat servers
|
|
39
|
-
*/
|
|
40
|
-
export class ServerDirectory {
|
|
41
|
-
constructor(options = {}) {
|
|
42
|
-
this.directoryPath = options.directoryPath || DEFAULT_DIRECTORY_PATH;
|
|
43
|
-
this.servers = [...DEFAULT_SERVERS];
|
|
44
|
-
this.timeout = options.timeout || 5000;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Load servers from directory file
|
|
49
|
-
*/
|
|
50
|
-
async load() {
|
|
51
|
-
try {
|
|
52
|
-
const data = await fs.readFile(this.directoryPath, 'utf8');
|
|
53
|
-
const loaded = JSON.parse(data);
|
|
54
|
-
if (Array.isArray(loaded.servers)) {
|
|
55
|
-
// Merge with defaults, avoiding duplicates by URL
|
|
56
|
-
const urls = new Set(this.servers.map(s => s.url));
|
|
57
|
-
for (const server of loaded.servers) {
|
|
58
|
-
if (!urls.has(server.url)) {
|
|
59
|
-
this.servers.push(server);
|
|
60
|
-
urls.add(server.url);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
} catch (err) {
|
|
65
|
-
// File doesn't exist or is invalid, use defaults
|
|
66
|
-
}
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Save servers to directory file
|
|
72
|
-
*/
|
|
73
|
-
async save() {
|
|
74
|
-
const dir = path.dirname(this.directoryPath);
|
|
75
|
-
await fs.mkdir(dir, { recursive: true });
|
|
76
|
-
await fs.writeFile(this.directoryPath, JSON.stringify({
|
|
77
|
-
version: 1,
|
|
78
|
-
updated_at: new Date().toISOString(),
|
|
79
|
-
servers: this.servers
|
|
80
|
-
}, null, 2));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Add a server to the directory
|
|
85
|
-
*/
|
|
86
|
-
async addServer(server) {
|
|
87
|
-
const existing = this.servers.find(s => s.url === server.url);
|
|
88
|
-
if (existing) {
|
|
89
|
-
Object.assign(existing, server);
|
|
90
|
-
} else {
|
|
91
|
-
this.servers.push(server);
|
|
92
|
-
}
|
|
93
|
-
await this.save();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Remove a server from the directory
|
|
98
|
-
*/
|
|
99
|
-
async removeServer(url) {
|
|
100
|
-
this.servers = this.servers.filter(s => s.url !== url);
|
|
101
|
-
await this.save();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Check health of a single server
|
|
106
|
-
* @param {Object} server - Server object with url
|
|
107
|
-
* @returns {Object} Server with health status
|
|
108
|
-
*/
|
|
109
|
-
async checkHealth(server) {
|
|
110
|
-
const wsUrl = server.url;
|
|
111
|
-
// Convert ws:// or wss:// to http:// or https://
|
|
112
|
-
const httpUrl = wsUrl
|
|
113
|
-
.replace('wss://', 'https://')
|
|
114
|
-
.replace('ws://', 'http://');
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
const health = await this._fetchHealth(httpUrl + '/health');
|
|
118
|
-
return {
|
|
119
|
-
...server,
|
|
120
|
-
status: 'online',
|
|
121
|
-
health,
|
|
122
|
-
checked_at: new Date().toISOString()
|
|
123
|
-
};
|
|
124
|
-
} catch (err) {
|
|
125
|
-
return {
|
|
126
|
-
...server,
|
|
127
|
-
status: 'offline',
|
|
128
|
-
error: err.message || err.code || 'Unknown error',
|
|
129
|
-
checked_at: new Date().toISOString()
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Fetch health endpoint
|
|
136
|
-
*/
|
|
137
|
-
_fetchHealth(url) {
|
|
138
|
-
return new Promise((resolve, reject) => {
|
|
139
|
-
const protocol = url.startsWith('https') ? https : http;
|
|
140
|
-
const req = protocol.get(url, { timeout: this.timeout }, (res) => {
|
|
141
|
-
let data = '';
|
|
142
|
-
res.on('data', chunk => data += chunk);
|
|
143
|
-
res.on('end', () => {
|
|
144
|
-
if (res.statusCode === 200) {
|
|
145
|
-
try {
|
|
146
|
-
resolve(JSON.parse(data));
|
|
147
|
-
} catch {
|
|
148
|
-
reject(new Error('Invalid health response'));
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
req.on('error', reject);
|
|
157
|
-
req.on('timeout', () => {
|
|
158
|
-
req.destroy();
|
|
159
|
-
reject(new Error('Timeout'));
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Discover available servers (check health of all known servers)
|
|
166
|
-
* @param {Object} options
|
|
167
|
-
* @param {boolean} options.onlineOnly - Only return online servers
|
|
168
|
-
* @returns {Array} List of servers with status
|
|
169
|
-
*/
|
|
170
|
-
async discover(options = {}) {
|
|
171
|
-
const results = await Promise.all(
|
|
172
|
-
this.servers.map(server => this.checkHealth(server))
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
if (options.onlineOnly) {
|
|
176
|
-
return results.filter(s => s.status === 'online');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return results;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get list of known servers without health check
|
|
184
|
-
*/
|
|
185
|
-
list() {
|
|
186
|
-
return [...this.servers];
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default ServerDirectory;
|
package/lib/server-directory.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server Directory
|
|
3
|
-
* Registry of known AgentChat servers for discovery
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import http from 'http';
|
|
7
|
-
import https from 'https';
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
|
|
11
|
-
export interface ServerEntry {
|
|
12
|
-
name: string;
|
|
13
|
-
url: string;
|
|
14
|
-
description?: string;
|
|
15
|
-
region?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface HealthData {
|
|
19
|
-
agents?: {
|
|
20
|
-
connected?: number;
|
|
21
|
-
};
|
|
22
|
-
uptime_seconds?: number;
|
|
23
|
-
[key: string]: unknown;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ServerWithStatus extends ServerEntry {
|
|
27
|
-
status: 'online' | 'offline' | 'unknown';
|
|
28
|
-
health?: HealthData;
|
|
29
|
-
error?: string;
|
|
30
|
-
checked_at?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ServerDirectoryFile {
|
|
34
|
-
version: number;
|
|
35
|
-
updated_at: string;
|
|
36
|
-
servers: ServerEntry[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface ServerDirectoryOptions {
|
|
40
|
-
directoryPath?: string;
|
|
41
|
-
timeout?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface DiscoverOptions {
|
|
45
|
-
onlineOnly?: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Default public servers (can be extended)
|
|
49
|
-
export const DEFAULT_SERVERS: ServerEntry[] = process.env.AGENTCHAT_PUBLIC === 'true'
|
|
50
|
-
? [
|
|
51
|
-
{
|
|
52
|
-
name: 'AgentChat Public',
|
|
53
|
-
url: 'wss://agentchat-server.fly.dev',
|
|
54
|
-
description: 'Official public AgentChat server',
|
|
55
|
-
region: 'global'
|
|
56
|
-
}
|
|
57
|
-
]
|
|
58
|
-
: [
|
|
59
|
-
{
|
|
60
|
-
name: 'AgentChat Local',
|
|
61
|
-
url: 'ws://localhost:6667',
|
|
62
|
-
description: 'Local AgentChat server',
|
|
63
|
-
region: 'local'
|
|
64
|
-
}
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
// Default directory file path
|
|
68
|
-
export const DEFAULT_DIRECTORY_PATH: string = path.join(
|
|
69
|
-
process.env.HOME || process.env.USERPROFILE || '.',
|
|
70
|
-
'.agentchat',
|
|
71
|
-
'servers.json'
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Server Directory for discovering AgentChat servers
|
|
76
|
-
*/
|
|
77
|
-
export class ServerDirectory {
|
|
78
|
-
private directoryPath: string;
|
|
79
|
-
private servers: ServerEntry[];
|
|
80
|
-
private timeout: number;
|
|
81
|
-
|
|
82
|
-
constructor(options: ServerDirectoryOptions = {}) {
|
|
83
|
-
this.directoryPath = options.directoryPath || DEFAULT_DIRECTORY_PATH;
|
|
84
|
-
this.servers = [...DEFAULT_SERVERS];
|
|
85
|
-
this.timeout = options.timeout || 5000;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Load servers from directory file
|
|
90
|
-
*/
|
|
91
|
-
async load(): Promise<this> {
|
|
92
|
-
try {
|
|
93
|
-
const data = await fs.readFile(this.directoryPath, 'utf8');
|
|
94
|
-
const loaded = JSON.parse(data) as ServerDirectoryFile;
|
|
95
|
-
if (Array.isArray(loaded.servers)) {
|
|
96
|
-
// Merge with defaults, avoiding duplicates by URL
|
|
97
|
-
const urls = new Set(this.servers.map(s => s.url));
|
|
98
|
-
for (const server of loaded.servers) {
|
|
99
|
-
if (!urls.has(server.url)) {
|
|
100
|
-
this.servers.push(server);
|
|
101
|
-
urls.add(server.url);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
// File doesn't exist or is invalid, use defaults
|
|
107
|
-
}
|
|
108
|
-
return this;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Save servers to directory file
|
|
113
|
-
*/
|
|
114
|
-
async save(): Promise<void> {
|
|
115
|
-
const dir = path.dirname(this.directoryPath);
|
|
116
|
-
await fs.mkdir(dir, { recursive: true });
|
|
117
|
-
await fs.writeFile(this.directoryPath, JSON.stringify({
|
|
118
|
-
version: 1,
|
|
119
|
-
updated_at: new Date().toISOString(),
|
|
120
|
-
servers: this.servers
|
|
121
|
-
} as ServerDirectoryFile, null, 2));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Add a server to the directory
|
|
126
|
-
*/
|
|
127
|
-
async addServer(server: ServerEntry): Promise<void> {
|
|
128
|
-
const existing = this.servers.find(s => s.url === server.url);
|
|
129
|
-
if (existing) {
|
|
130
|
-
Object.assign(existing, server);
|
|
131
|
-
} else {
|
|
132
|
-
this.servers.push(server);
|
|
133
|
-
}
|
|
134
|
-
await this.save();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Remove a server from the directory
|
|
139
|
-
*/
|
|
140
|
-
async removeServer(url: string): Promise<void> {
|
|
141
|
-
this.servers = this.servers.filter(s => s.url !== url);
|
|
142
|
-
await this.save();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Check health of a single server
|
|
147
|
-
* @param server - Server object with url
|
|
148
|
-
* @returns Server with health status
|
|
149
|
-
*/
|
|
150
|
-
async checkHealth(server: ServerEntry): Promise<ServerWithStatus> {
|
|
151
|
-
const wsUrl = server.url;
|
|
152
|
-
// Convert ws:// or wss:// to http:// or https://
|
|
153
|
-
const httpUrl = wsUrl
|
|
154
|
-
.replace('wss://', 'https://')
|
|
155
|
-
.replace('ws://', 'http://');
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
const health = await this._fetchHealth(httpUrl + '/health');
|
|
159
|
-
return {
|
|
160
|
-
...server,
|
|
161
|
-
status: 'online',
|
|
162
|
-
health,
|
|
163
|
-
checked_at: new Date().toISOString()
|
|
164
|
-
};
|
|
165
|
-
} catch (err) {
|
|
166
|
-
const error = err as Error & { code?: string };
|
|
167
|
-
return {
|
|
168
|
-
...server,
|
|
169
|
-
status: 'offline',
|
|
170
|
-
error: error.message || error.code || 'Unknown error',
|
|
171
|
-
checked_at: new Date().toISOString()
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Fetch health endpoint
|
|
178
|
-
*/
|
|
179
|
-
private _fetchHealth(url: string): Promise<HealthData> {
|
|
180
|
-
return new Promise((resolve, reject) => {
|
|
181
|
-
const protocol = url.startsWith('https') ? https : http;
|
|
182
|
-
const req = protocol.get(url, { timeout: this.timeout }, (res) => {
|
|
183
|
-
let data = '';
|
|
184
|
-
res.on('data', (chunk: Buffer) => data += chunk);
|
|
185
|
-
res.on('end', () => {
|
|
186
|
-
if (res.statusCode === 200) {
|
|
187
|
-
try {
|
|
188
|
-
resolve(JSON.parse(data) as HealthData);
|
|
189
|
-
} catch {
|
|
190
|
-
reject(new Error('Invalid health response'));
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
reject(new Error(`HTTP ${res.statusCode}`));
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
req.on('error', reject);
|
|
199
|
-
req.on('timeout', () => {
|
|
200
|
-
req.destroy();
|
|
201
|
-
reject(new Error('Timeout'));
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Discover available servers (check health of all known servers)
|
|
208
|
-
* @param options
|
|
209
|
-
* @param options.onlineOnly - Only return online servers
|
|
210
|
-
* @returns List of servers with status
|
|
211
|
-
*/
|
|
212
|
-
async discover(options: DiscoverOptions = {}): Promise<ServerWithStatus[]> {
|
|
213
|
-
const results = await Promise.all(
|
|
214
|
-
this.servers.map(server => this.checkHealth(server))
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
if (options.onlineOnly) {
|
|
218
|
-
return results.filter(s => s.status === 'online');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return results;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Get list of known servers without health check
|
|
226
|
-
*/
|
|
227
|
-
list(): ServerEntry[] {
|
|
228
|
-
return [...this.servers];
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export default ServerDirectory;
|