@tylerl0706/ahpx 0.2.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/index.js ADDED
@@ -0,0 +1,229 @@
1
+ import {
2
+ ActionType,
3
+ ActiveClientManager,
4
+ AhpClient,
5
+ AuthHandler,
6
+ ForwardingFormatter,
7
+ HealthChecker,
8
+ PendingMessageKind,
9
+ ProtocolLayer,
10
+ ReconnectManager,
11
+ ResponsePartKind,
12
+ RpcError,
13
+ RpcTimeoutError,
14
+ SessionHandle,
15
+ SessionLifecycle,
16
+ SessionPersistence,
17
+ SessionStatus,
18
+ SessionStore,
19
+ StateMirror,
20
+ ToolCallStatus,
21
+ Transport,
22
+ WebSocketForwarder,
23
+ WebhookForwarder,
24
+ buildTurnSummary,
25
+ ensureFileUri,
26
+ fileUriToDisplayPath,
27
+ truncatePreview
28
+ } from "./chunk-B6RV43UP.js";
29
+
30
+ // src/client/connection-pool.ts
31
+ var ConnectionPool = class {
32
+ _clients = /* @__PURE__ */ new Map();
33
+ _maxPerServer;
34
+ constructor(options) {
35
+ this._maxPerServer = options?.maxConnectionsPerServer ?? 1;
36
+ }
37
+ /**
38
+ * Get or create a connected client for the given server URL.
39
+ *
40
+ * If a connected client already exists for this URL, it is returned.
41
+ * Otherwise a new client is created, connected, and cached.
42
+ */
43
+ async getClient(url, options) {
44
+ const normalizedUrl = this.normalizeUrl(url);
45
+ const existing = this._clients.get(normalizedUrl);
46
+ if (existing?.connected) {
47
+ return existing;
48
+ }
49
+ if (existing) {
50
+ this._clients.delete(normalizedUrl);
51
+ }
52
+ const client = new AhpClient(options);
53
+ await client.connect(url);
54
+ client.on("disconnected", () => {
55
+ this._clients.delete(normalizedUrl);
56
+ });
57
+ this._clients.set(normalizedUrl, client);
58
+ return client;
59
+ }
60
+ /**
61
+ * Close all connections in the pool.
62
+ */
63
+ async closeAll() {
64
+ const clients = [...this._clients.values()];
65
+ this._clients.clear();
66
+ await Promise.all(
67
+ clients.map(
68
+ (client) => client.disconnect().catch(() => {
69
+ })
70
+ )
71
+ );
72
+ }
73
+ /** Number of active connections in the pool. */
74
+ get activeConnections() {
75
+ return this._clients.size;
76
+ }
77
+ /** Total number of active sessions across all connections. */
78
+ get activeSessions() {
79
+ let total = 0;
80
+ for (const client of this._clients.values()) {
81
+ total += client.sessions.size;
82
+ }
83
+ return total;
84
+ }
85
+ normalizeUrl(url) {
86
+ return url.replace(/\/+$/, "");
87
+ }
88
+ };
89
+
90
+ // src/fleet/manager.ts
91
+ var FleetManager = class {
92
+ _connections;
93
+ _strategy;
94
+ _preferredServer;
95
+ _tags;
96
+ _checker;
97
+ _healthCache = [];
98
+ _roundRobinIndex = 0;
99
+ constructor(options) {
100
+ this._connections = options.connections;
101
+ this._strategy = options.strategy ?? "least-sessions";
102
+ this._preferredServer = options.preferredServer;
103
+ this._checker = new HealthChecker({ timeout: options.healthCheckTimeout });
104
+ const tags = {};
105
+ if (options.tags) {
106
+ for (const [tag, names] of Object.entries(options.tags)) {
107
+ tags[tag] = [...names];
108
+ }
109
+ }
110
+ for (const conn of options.connections) {
111
+ if (conn.tags) {
112
+ for (const tag of conn.tags) {
113
+ if (!tags[tag]) {
114
+ tags[tag] = [];
115
+ }
116
+ if (!tags[tag].includes(conn.name)) {
117
+ tags[tag].push(conn.name);
118
+ }
119
+ }
120
+ }
121
+ }
122
+ this._tags = tags;
123
+ }
124
+ /** Pick the best server for a dispatch based on strategy and requirements. */
125
+ async selectServer(requirements) {
126
+ if (this._healthCache.length === 0) {
127
+ await this.refresh();
128
+ }
129
+ let candidates = this._healthCache.filter((h) => h.status === "healthy");
130
+ if (requirements?.provider) {
131
+ const provider = requirements.provider;
132
+ candidates = candidates.filter((h) => h.agents.some((a) => a.provider === provider));
133
+ }
134
+ if (requirements?.model) {
135
+ const model = requirements.model;
136
+ candidates = candidates.filter((h) => h.agents.some((a) => a.models.includes(model)));
137
+ }
138
+ if (requirements?.tag) {
139
+ const tag = requirements.tag;
140
+ const serversWithTag = this._tags[tag] ?? [];
141
+ candidates = candidates.filter((h) => serversWithTag.includes(h.name));
142
+ }
143
+ if (candidates.length === 0) {
144
+ throw new Error("No healthy server matches the requirements");
145
+ }
146
+ const selected = this._applyStrategy(candidates);
147
+ return { name: selected.name, url: selected.url };
148
+ }
149
+ /** Get health for all servers (cached). */
150
+ async getHealth() {
151
+ if (this._healthCache.length === 0) {
152
+ await this.refresh();
153
+ }
154
+ return [...this._healthCache];
155
+ }
156
+ /** Refresh health cache by checking all servers. */
157
+ async refresh() {
158
+ this._healthCache = await this._checker.checkAll(this._connections);
159
+ }
160
+ _applyStrategy(candidates) {
161
+ switch (this._strategy) {
162
+ case "least-sessions":
163
+ return this._leastSessions(candidates);
164
+ case "round-robin": {
165
+ const index = this._roundRobinIndex % candidates.length;
166
+ this._roundRobinIndex = (this._roundRobinIndex + 1) % candidates.length;
167
+ return candidates[index];
168
+ }
169
+ case "random":
170
+ return candidates[Math.floor(Math.random() * candidates.length)];
171
+ case "preferred": {
172
+ if (this._preferredServer) {
173
+ const preferred = candidates.find((h) => h.name === this._preferredServer);
174
+ if (preferred) {
175
+ return preferred;
176
+ }
177
+ }
178
+ return this._leastSessions(candidates);
179
+ }
180
+ }
181
+ }
182
+ _leastSessions(candidates) {
183
+ let best = candidates[0];
184
+ for (let i = 1; i < candidates.length; i++) {
185
+ if (candidates[i].activeSessions < best.activeSessions) {
186
+ best = candidates[i];
187
+ }
188
+ }
189
+ return best;
190
+ }
191
+ };
192
+
193
+ // src/protocol/commands.ts
194
+ var ContentEncoding = /* @__PURE__ */ ((ContentEncoding2) => {
195
+ ContentEncoding2["Base64"] = "base64";
196
+ ContentEncoding2["Utf8"] = "utf-8";
197
+ return ContentEncoding2;
198
+ })(ContentEncoding || {});
199
+ export {
200
+ ActionType,
201
+ ActiveClientManager,
202
+ AhpClient,
203
+ AuthHandler,
204
+ ConnectionPool,
205
+ ContentEncoding,
206
+ FleetManager,
207
+ ForwardingFormatter,
208
+ HealthChecker,
209
+ PendingMessageKind,
210
+ ProtocolLayer,
211
+ ReconnectManager,
212
+ ResponsePartKind,
213
+ RpcError,
214
+ RpcTimeoutError,
215
+ SessionHandle,
216
+ SessionLifecycle,
217
+ SessionPersistence,
218
+ SessionStatus,
219
+ SessionStore,
220
+ StateMirror,
221
+ ToolCallStatus,
222
+ Transport,
223
+ WebSocketForwarder,
224
+ WebhookForwarder,
225
+ buildTurnSummary,
226
+ ensureFileUri,
227
+ fileUriToDisplayPath,
228
+ truncatePreview
229
+ };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@tylerl0706/ahpx",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "Agent Host Protocol client — use as a library or CLI to manage AHP server connections, sessions, and agent interactions",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./cli": {
14
+ "import": "./dist/bin.js"
15
+ }
16
+ },
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "bin": {
20
+ "ahpx": "./dist/bin.js"
21
+ },
22
+ "files": ["dist", "LICENSE"],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/TylerLeonhardt/ahpx.git"
26
+ },
27
+ "homepage": "https://github.com/TylerLeonhardt/ahpx#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/TylerLeonhardt/ahpx/issues"
30
+ },
31
+ "keywords": ["ahp", "agent-host-protocol", "cli", "agent", "ai", "websocket", "json-rpc"],
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "lint": "biome check .",
44
+ "lint:fix": "biome check --write .",
45
+ "typecheck": "tsc --noEmit"
46
+ },
47
+ "dependencies": {
48
+ "commander": "^13.1.0",
49
+ "picocolors": "^1.1.1",
50
+ "ws": "^8.18.0"
51
+ },
52
+ "devDependencies": {
53
+ "@biomejs/biome": "^1.9.4",
54
+ "@types/node": "^22.13.10",
55
+ "@types/ws": "^8.5.14",
56
+ "tsup": "^8.4.0",
57
+ "typescript": "^5.8.2",
58
+ "vitest": "^3.0.9"
59
+ }
60
+ }