agentfootprint 7.14.0 → 7.15.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/adapters/hosting/agentcore.js +350 -0
- package/dist/adapters/hosting/agentcore.js.map +1 -0
- package/dist/adapters/memory/agentcore.js +125 -1
- package/dist/adapters/memory/agentcore.js.map +1 -1
- package/dist/adapters/security/agentcore.js +0 -0
- package/dist/adapters/security/agentcore.js.map +1 -0
- package/dist/esm/adapters/hosting/agentcore.d.ts +199 -0
- package/dist/esm/adapters/hosting/agentcore.js +321 -0
- package/dist/esm/adapters/hosting/agentcore.js.map +1 -0
- package/dist/esm/adapters/memory/agentcore.d.ts +88 -2
- package/dist/esm/adapters/memory/agentcore.js +125 -1
- package/dist/esm/adapters/memory/agentcore.js.map +1 -1
- package/dist/esm/adapters/security/agentcore.d.ts +157 -0
- package/dist/esm/adapters/security/agentcore.js +0 -0
- package/dist/esm/adapters/security/agentcore.js.map +1 -0
- package/dist/esm/hosting/httpHost.d.ts +121 -0
- package/dist/esm/hosting/httpHost.js +248 -0
- package/dist/esm/hosting/httpHost.js.map +1 -0
- package/dist/esm/hosting/index.d.ts +8 -1
- package/dist/esm/hosting/index.js +7 -1
- package/dist/esm/hosting/index.js.map +1 -1
- package/dist/esm/hosting/nodeHost.d.ts +23 -16
- package/dist/esm/hosting/nodeHost.js +32 -199
- package/dist/esm/hosting/nodeHost.js.map +1 -1
- package/dist/esm/hosting-providers.d.ts +50 -0
- package/dist/esm/hosting-providers.js +50 -0
- package/dist/esm/hosting-providers.js.map +1 -0
- package/dist/esm/lib/mcp/gatewayTransport.d.ts +103 -0
- package/dist/esm/lib/mcp/gatewayTransport.js +123 -0
- package/dist/esm/lib/mcp/gatewayTransport.js.map +1 -0
- package/dist/esm/lib/mcp/index.d.ts +2 -1
- package/dist/esm/lib/mcp/index.js +1 -0
- package/dist/esm/lib/mcp/index.js.map +1 -1
- package/dist/esm/lib/mcp/mcpClient.js +10 -2
- package/dist/esm/lib/mcp/mcpClient.js.map +1 -1
- package/dist/esm/lib/mcp/types.d.ts +45 -1
- package/dist/esm/memory/store/types.d.ts +22 -0
- package/dist/esm/memory-providers.d.ts +1 -1
- package/dist/esm/memory-providers.js.map +1 -1
- package/dist/esm/security/index.d.ts +10 -1
- package/dist/esm/security/index.js +14 -1
- package/dist/esm/security/index.js.map +1 -1
- package/dist/esm/tool-providers/index.d.ts +6 -2
- package/dist/esm/tool-providers/index.js +5 -1
- package/dist/esm/tool-providers/index.js.map +1 -1
- package/dist/hosting/httpHost.js +276 -0
- package/dist/hosting/httpHost.js.map +1 -0
- package/dist/hosting/index.js +10 -1
- package/dist/hosting/index.js.map +1 -1
- package/dist/hosting/nodeHost.js +34 -224
- package/dist/hosting/nodeHost.js.map +1 -1
- package/dist/hosting-providers.js +57 -0
- package/dist/hosting-providers.js.map +1 -0
- package/dist/lib/mcp/gatewayTransport.js +129 -0
- package/dist/lib/mcp/gatewayTransport.js.map +1 -0
- package/dist/lib/mcp/index.js +4 -1
- package/dist/lib/mcp/index.js.map +1 -1
- package/dist/lib/mcp/mcpClient.js +10 -2
- package/dist/lib/mcp/mcpClient.js.map +1 -1
- package/dist/memory-providers.js.map +1 -1
- package/dist/security/index.js +16 -2
- package/dist/security/index.js.map +1 -1
- package/dist/tool-providers/index.js +7 -1
- package/dist/tool-providers/index.js.map +1 -1
- package/dist/types/adapters/hosting/agentcore.d.ts +200 -0
- package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -0
- package/dist/types/adapters/memory/agentcore.d.ts +88 -2
- package/dist/types/adapters/memory/agentcore.d.ts.map +1 -1
- package/dist/types/adapters/security/agentcore.d.ts +158 -0
- package/dist/types/adapters/security/agentcore.d.ts.map +1 -0
- package/dist/types/hosting/httpHost.d.ts +122 -0
- package/dist/types/hosting/httpHost.d.ts.map +1 -0
- package/dist/types/hosting/index.d.ts +8 -1
- package/dist/types/hosting/index.d.ts.map +1 -1
- package/dist/types/hosting/nodeHost.d.ts +23 -16
- package/dist/types/hosting/nodeHost.d.ts.map +1 -1
- package/dist/types/hosting-providers.d.ts +51 -0
- package/dist/types/hosting-providers.d.ts.map +1 -0
- package/dist/types/lib/mcp/gatewayTransport.d.ts +104 -0
- package/dist/types/lib/mcp/gatewayTransport.d.ts.map +1 -0
- package/dist/types/lib/mcp/index.d.ts +2 -1
- package/dist/types/lib/mcp/index.d.ts.map +1 -1
- package/dist/types/lib/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/lib/mcp/types.d.ts +45 -1
- package/dist/types/lib/mcp/types.d.ts.map +1 -1
- package/dist/types/memory/store/types.d.ts +22 -0
- package/dist/types/memory/store/types.d.ts.map +1 -1
- package/dist/types/memory-providers.d.ts +1 -1
- package/dist/types/memory-providers.d.ts.map +1 -1
- package/dist/types/security/index.d.ts +10 -1
- package/dist/types/security/index.d.ts.map +1 -1
- package/dist/types/tool-providers/index.d.ts +6 -2
- package/dist/types/tool-providers/index.d.ts.map +1 -1
- package/package.json +14 -1
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosting/httpHost — one HTTP host, parameterised by the JSON dialect it speaks.
|
|
3
|
+
*
|
|
4
|
+
* Everything hard about serving an agent over HTTP happens once, here: draining
|
|
5
|
+
* on close, aborting when the caller hangs up, failing a handler that throws,
|
|
6
|
+
* failing a handler that answers nothing, mapping refusal codes onto status
|
|
7
|
+
* codes, and choosing between one JSON body and Server-Sent Events based on
|
|
8
|
+
* what the caller asked for.
|
|
9
|
+
*
|
|
10
|
+
* Everything a deployment target gets to re-decide is an {@link HttpWire}: the
|
|
11
|
+
* two paths, and the five JSON shapes — how a request names its input and its
|
|
12
|
+
* session, and what a health probe, a completion, a failure and a streamed
|
|
13
|
+
* piece look like on the wire.
|
|
14
|
+
*
|
|
15
|
+
* ── Why this file exists ─────────────────────────────────────────────────────
|
|
16
|
+
* `nodeHost` shipped first and hard-coded its own dialect: `{ input }` in,
|
|
17
|
+
* `{ output }` out, `{ status: 'ok' }` on the health path. That was fine while
|
|
18
|
+
* it was the only HTTP adapter and wrong the moment there was a second one. A
|
|
19
|
+
* container runtime dictates its own body shape as surely as it dictates its
|
|
20
|
+
* paths, and an adapter for it should be a CONFIGURATION of the HTTP work, not
|
|
21
|
+
* a second copy of it — a copy is where the drain semantics of two adapters
|
|
22
|
+
* silently diverge.
|
|
23
|
+
*
|
|
24
|
+
* Note what did NOT have to change for that: the ports. `AgentHost`,
|
|
25
|
+
* `HostRequest` and `HostReply` say exactly what they said before. The gap was
|
|
26
|
+
* in the first adapter, which had no seam, not in the port, which needed none.
|
|
27
|
+
*
|
|
28
|
+
* Pattern: Template method via configuration (Strategy on the wire format).
|
|
29
|
+
* Everything HTTP lives here and in the wires; `types.ts` knows none of it.
|
|
30
|
+
*/
|
|
31
|
+
import { encodeSSE } from '../stream.js';
|
|
32
|
+
import { HostClosedError } from './errors.js';
|
|
33
|
+
/**
|
|
34
|
+
* Status codes mapped by refusal code. Anything else is a 500.
|
|
35
|
+
*
|
|
36
|
+
* None of the three is a 5xx: none of them is the agent breaking. A closed host
|
|
37
|
+
* is shutting down (503), a concurrent turn conflicts with the run already
|
|
38
|
+
* going (409), and a paused run conflicts with the state this reply can carry
|
|
39
|
+
* (409) — the run is unfinished, not failed, and a 500 would say otherwise to
|
|
40
|
+
* every dashboard that ever sees it.
|
|
41
|
+
*/
|
|
42
|
+
const STATUS_BY_CODE = {
|
|
43
|
+
ERR_HOST_CLOSED: 503,
|
|
44
|
+
ERR_CONCURRENT_RUN: 409,
|
|
45
|
+
ERR_PAUSE_NOT_CARRIED: 409,
|
|
46
|
+
};
|
|
47
|
+
const DEFAULT_CAPABILITIES = ['streaming'];
|
|
48
|
+
/**
|
|
49
|
+
* An HTTP host for one handler, speaking the dialect you hand it.
|
|
50
|
+
*
|
|
51
|
+
* @example The same machinery, two dialects
|
|
52
|
+
* httpHost({ name: 'nodeHost', wire: jsonWire, invokePath: '/invoke', healthPath: '/health' });
|
|
53
|
+
* httpHost({ name: 'myRuntime', wire: myWire, invokePath: '/v1/run', healthPath: '/up' });
|
|
54
|
+
*/
|
|
55
|
+
export function httpHost(options) {
|
|
56
|
+
const { name, wire, invokePath, healthPath } = options;
|
|
57
|
+
const port = options.port ?? 8080;
|
|
58
|
+
const hostname = options.hostname ?? '0.0.0.0';
|
|
59
|
+
const capabilities = options.capabilities ?? DEFAULT_CAPABILITIES;
|
|
60
|
+
return {
|
|
61
|
+
name,
|
|
62
|
+
capabilities,
|
|
63
|
+
async serve(handler) {
|
|
64
|
+
const { createServer } = await import('node:http');
|
|
65
|
+
const startedAt = Date.now();
|
|
66
|
+
const inFlight = new Set();
|
|
67
|
+
let accepting = true;
|
|
68
|
+
let closing;
|
|
69
|
+
const server = createServer((req, res) => {
|
|
70
|
+
const path = (req.url ?? '').split('?')[0];
|
|
71
|
+
if (req.method === 'GET' && path === healthPath) {
|
|
72
|
+
sendJson(res, 200, wire.health(Date.now() - startedAt));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (req.method !== 'POST' || path !== invokePath) {
|
|
76
|
+
sendJson(res, 404, wire.failure(`no route for ${req.method ?? '?'} ${path}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!accepting) {
|
|
80
|
+
const refusal = new HostClosedError(name);
|
|
81
|
+
sendJson(res, STATUS_BY_CODE[refusal.code] ?? 500, wire.failure(refusal.message, refusal.code));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const served = serveOne(req, res, handler, wire);
|
|
85
|
+
inFlight.add(served);
|
|
86
|
+
void served.finally(() => inFlight.delete(served));
|
|
87
|
+
});
|
|
88
|
+
await new Promise((resolve, reject) => {
|
|
89
|
+
server.once('error', reject);
|
|
90
|
+
server.listen(port, hostname, resolve);
|
|
91
|
+
});
|
|
92
|
+
const address = server.address();
|
|
93
|
+
const boundPort = typeof address === 'object' && address ? address.port : port;
|
|
94
|
+
const displayHost = hostname === '0.0.0.0' || hostname === '::' ? '127.0.0.1' : hostname;
|
|
95
|
+
return {
|
|
96
|
+
url: `http://${displayHost}:${boundPort}`,
|
|
97
|
+
port: boundPort,
|
|
98
|
+
close() {
|
|
99
|
+
// Idempotent: the first call owns the shutdown, later ones await it.
|
|
100
|
+
closing ??= (async () => {
|
|
101
|
+
accepting = false;
|
|
102
|
+
// Drain BEFORE touching sockets — an in-flight request is work the
|
|
103
|
+
// caller is still waiting on, and dropping it would be the exact
|
|
104
|
+
// thing close() promises not to do.
|
|
105
|
+
await Promise.allSettled([...inFlight]);
|
|
106
|
+
server.closeIdleConnections();
|
|
107
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
108
|
+
})();
|
|
109
|
+
return closing;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Run one request through the handler and write whatever it decides. */
|
|
116
|
+
async function serveOne(req, res, handler, wire) {
|
|
117
|
+
const wantsStream = (req.headers.accept ?? '').includes('text/event-stream');
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
let body;
|
|
120
|
+
try {
|
|
121
|
+
body = await readJson(req);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
sendJson(res, 400, wire.failure(`invalid JSON body: ${asError(err).message}`));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const headers = lowerCased(req.headers);
|
|
128
|
+
const query = new URLSearchParams((req.url ?? '').split('?')[1] ?? '');
|
|
129
|
+
const { input, sessionId } = wire.readRequest({ body, headers, query });
|
|
130
|
+
if (wantsStream) {
|
|
131
|
+
res.writeHead(200, {
|
|
132
|
+
'content-type': 'text/event-stream',
|
|
133
|
+
'cache-control': 'no-cache',
|
|
134
|
+
connection: 'keep-alive',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
let settled = false;
|
|
138
|
+
// The caller hung up before we answered — tell the handler so it can stop
|
|
139
|
+
// paying for work nobody is waiting for.
|
|
140
|
+
res.once('close', () => {
|
|
141
|
+
if (!settled)
|
|
142
|
+
controller.abort();
|
|
143
|
+
});
|
|
144
|
+
const reply = {
|
|
145
|
+
emit(chunk) {
|
|
146
|
+
// Not streaming? The chunk is a preview of text `complete` will deliver
|
|
147
|
+
// in full, so there is nothing to send and nothing to keep.
|
|
148
|
+
if (settled || !wantsStream)
|
|
149
|
+
return;
|
|
150
|
+
res.write(encodeSSE('chunk', wire.chunk(chunk)));
|
|
151
|
+
},
|
|
152
|
+
complete(output) {
|
|
153
|
+
if (settled)
|
|
154
|
+
return;
|
|
155
|
+
settled = true;
|
|
156
|
+
if (wantsStream) {
|
|
157
|
+
res.write(encodeSSE('complete', wire.output(output)));
|
|
158
|
+
res.end();
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
sendJson(res, 200, wire.output(output));
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
fail(error) {
|
|
165
|
+
if (settled)
|
|
166
|
+
return;
|
|
167
|
+
settled = true;
|
|
168
|
+
const code = error.code;
|
|
169
|
+
const payload = wire.failure(error.message, code);
|
|
170
|
+
if (wantsStream) {
|
|
171
|
+
res.write(encodeSSE('error', payload));
|
|
172
|
+
res.end();
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
sendJson(res, code !== undefined ? STATUS_BY_CODE[code] ?? 500 : 500, payload);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
await handler({
|
|
181
|
+
input,
|
|
182
|
+
...(sessionId !== undefined && { sessionId }),
|
|
183
|
+
headers,
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
}, reply);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
// A handler that throws is a failed request, never a hung one.
|
|
189
|
+
reply.fail(asError(err));
|
|
190
|
+
}
|
|
191
|
+
// A handler that returned without answering gets one authored answer rather
|
|
192
|
+
// than a socket the caller waits on until it times out.
|
|
193
|
+
if (!settled) {
|
|
194
|
+
reply.fail(new Error('[hosting] the handler returned without calling complete() or fail().'));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function readJson(req) {
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
const chunks = [];
|
|
200
|
+
req.on('data', (c) => chunks.push(c));
|
|
201
|
+
req.on('error', reject);
|
|
202
|
+
req.on('end', () => {
|
|
203
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
204
|
+
if (!raw)
|
|
205
|
+
return resolve({});
|
|
206
|
+
try {
|
|
207
|
+
const parsed = JSON.parse(raw);
|
|
208
|
+
resolve(parsed && typeof parsed === 'object' ? parsed : {});
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
reject(err);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function sendJson(res, status, body) {
|
|
217
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
218
|
+
res.end(JSON.stringify(body));
|
|
219
|
+
}
|
|
220
|
+
function lowerCased(headers) {
|
|
221
|
+
const out = {};
|
|
222
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
223
|
+
if (typeof value === 'string')
|
|
224
|
+
out[name.toLowerCase()] = value;
|
|
225
|
+
else if (Array.isArray(value))
|
|
226
|
+
out[name.toLowerCase()] = value.join(', ');
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
function asError(err) {
|
|
231
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Read a header case-insensitively from already-lower-cased facts.
|
|
235
|
+
*
|
|
236
|
+
* Exported because every wire that maps a header needs it and re-deriving it
|
|
237
|
+
* per adapter is how one of them ends up matching only the exact casing the
|
|
238
|
+
* author happened to test with.
|
|
239
|
+
*/
|
|
240
|
+
export function headerValue(facts, name, ...fallbacks) {
|
|
241
|
+
for (const candidate of [name, ...fallbacks]) {
|
|
242
|
+
const found = facts.headers[candidate.toLowerCase()];
|
|
243
|
+
if (typeof found === 'string' && found.length > 0)
|
|
244
|
+
return found;
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=httpHost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpHost.js","sourceRoot":"","sources":["../../../src/hosting/httpHost.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
* • `nodeHost({ port?, hostname?, invokePath?, healthPath? })` — plain
|
|
28
28
|
* `node:http`, zero dependencies. `POST /invoke`, `GET /health`, and
|
|
29
29
|
* Server-Sent Events when the caller asks for them.
|
|
30
|
+
* • `httpHost({ name, wire, invokePath, healthPath, ... })` — the HTTP work
|
|
31
|
+
* itself, parameterised by the JSON dialect it speaks. `nodeHost` is one
|
|
32
|
+
* configuration of it; an adapter for someone's container runtime is
|
|
33
|
+
* another. Two paths and five body shapes are all a second HTTP adapter
|
|
34
|
+
* re-decides.
|
|
30
35
|
* • `memorySessions()` — conversations in a Map, for tests and local dev.
|
|
31
36
|
* • `standingAgent({ agent, sessions, host })` — the composer.
|
|
32
37
|
* • `toEnvelope` / `readEnvelope` — pack a conversation, and refuse by name to
|
|
@@ -44,8 +49,10 @@
|
|
|
44
49
|
* });
|
|
45
50
|
* process.on('SIGTERM', () => void handle.close());
|
|
46
51
|
*/
|
|
47
|
-
export { nodeHost } from './nodeHost.js';
|
|
52
|
+
export { nodeHost, jsonWire } from './nodeHost.js';
|
|
48
53
|
export type { NodeHost, NodeHostHandle, NodeHostOptions } from './nodeHost.js';
|
|
54
|
+
export { httpHost, headerValue } from './httpHost.js';
|
|
55
|
+
export type { HttpHost, HttpHostHandle, HttpHostOptions, HttpRequestFacts, HttpWire, } from './httpHost.js';
|
|
49
56
|
export { memorySessions } from './memorySessions.js';
|
|
50
57
|
export { toEnvelope, readEnvelope } from './envelope.js';
|
|
51
58
|
export { standingAgent } from './standingAgent.js';
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
* • `nodeHost({ port?, hostname?, invokePath?, healthPath? })` — plain
|
|
28
28
|
* `node:http`, zero dependencies. `POST /invoke`, `GET /health`, and
|
|
29
29
|
* Server-Sent Events when the caller asks for them.
|
|
30
|
+
* • `httpHost({ name, wire, invokePath, healthPath, ... })` — the HTTP work
|
|
31
|
+
* itself, parameterised by the JSON dialect it speaks. `nodeHost` is one
|
|
32
|
+
* configuration of it; an adapter for someone's container runtime is
|
|
33
|
+
* another. Two paths and five body shapes are all a second HTTP adapter
|
|
34
|
+
* re-decides.
|
|
30
35
|
* • `memorySessions()` — conversations in a Map, for tests and local dev.
|
|
31
36
|
* • `standingAgent({ agent, sessions, host })` — the composer.
|
|
32
37
|
* • `toEnvelope` / `readEnvelope` — pack a conversation, and refuse by name to
|
|
@@ -44,7 +49,8 @@
|
|
|
44
49
|
* });
|
|
45
50
|
* process.on('SIGTERM', () => void handle.close());
|
|
46
51
|
*/
|
|
47
|
-
export { nodeHost } from './nodeHost.js';
|
|
52
|
+
export { nodeHost, jsonWire } from './nodeHost.js';
|
|
53
|
+
export { httpHost, headerValue } from './httpHost.js';
|
|
48
54
|
export { memorySessions } from './memorySessions.js';
|
|
49
55
|
export { toEnvelope, readEnvelope } from './envelope.js';
|
|
50
56
|
export { standingAgent } from './standingAgent.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hosting/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/hosting/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAStD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,aAAa,CAAC"}
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
* completion settles. The handler cannot tell the difference and does not need
|
|
22
22
|
* to, which is the property `capabilities` exists to describe.
|
|
23
23
|
*
|
|
24
|
-
* Pattern: Adapter.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Pattern: Adapter. What is specific to THIS adapter is its two paths and its
|
|
25
|
+
* JSON dialect (`jsonWire` below) — nothing else. The HTTP work itself lives in
|
|
26
|
+
* `httpHost.ts` and is shared with every other HTTP adapter, so two of them can
|
|
27
|
+
* never quietly drift apart on what `close()` drains or what a handler that
|
|
28
|
+
* throws does. `types.ts` knows none of it either way.
|
|
28
29
|
*/
|
|
29
|
-
import type
|
|
30
|
+
import { type HttpHost, type HttpHostHandle, type HttpWire } from './httpHost.js';
|
|
30
31
|
/** Options for {@link nodeHost}. */
|
|
31
32
|
export interface NodeHostOptions {
|
|
32
33
|
/** Port to bind. Default `8080`. Pass `0` for an ephemeral port. */
|
|
@@ -38,17 +39,23 @@ export interface NodeHostOptions {
|
|
|
38
39
|
/** Path that answers a health probe. Default `'/health'`. */
|
|
39
40
|
readonly healthPath?: string;
|
|
40
41
|
}
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
42
|
+
/**
|
|
43
|
+
* What {@link nodeHost}'s `serve()` resolves to — a `HostHandle` that also
|
|
44
|
+
* reports the `url` and `port` it actually bound, which is the only way to find
|
|
45
|
+
* out when you asked for port `0`.
|
|
46
|
+
*/
|
|
47
|
+
export type NodeHostHandle = HttpHostHandle;
|
|
48
|
+
/** `AgentHost` narrowed to this adapter's handle. */
|
|
49
|
+
export type NodeHost = HttpHost;
|
|
50
|
+
/**
|
|
51
|
+
* This adapter's own JSON dialect: `{ input, sessionId? }` in, `{ output }`
|
|
52
|
+
* out, `{ status: 'ok', uptimeMs }` on the health path.
|
|
53
|
+
*
|
|
54
|
+
* Exported by name so a deployment that has to keep these exact bodies while
|
|
55
|
+
* changing something else about the host reuses them rather than retyping them
|
|
56
|
+
* and getting one field subtly wrong.
|
|
57
|
+
*/
|
|
58
|
+
export declare const jsonWire: HttpWire;
|
|
52
59
|
/**
|
|
53
60
|
* An HTTP host for one handler.
|
|
54
61
|
*
|
|
@@ -21,28 +21,35 @@
|
|
|
21
21
|
* completion settles. The handler cannot tell the difference and does not need
|
|
22
22
|
* to, which is the property `capabilities` exists to describe.
|
|
23
23
|
*
|
|
24
|
-
* Pattern: Adapter.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Pattern: Adapter. What is specific to THIS adapter is its two paths and its
|
|
25
|
+
* JSON dialect (`jsonWire` below) — nothing else. The HTTP work itself lives in
|
|
26
|
+
* `httpHost.ts` and is shared with every other HTTP adapter, so two of them can
|
|
27
|
+
* never quietly drift apart on what `close()` drains or what a handler that
|
|
28
|
+
* throws does. `types.ts` knows none of it either way.
|
|
28
29
|
*/
|
|
29
|
-
import {
|
|
30
|
-
import { HostClosedError } from './errors.js';
|
|
30
|
+
import { httpHost } from './httpHost.js';
|
|
31
31
|
const HOST_NAME = 'nodeHost';
|
|
32
|
-
const CAPABILITIES = ['streaming'];
|
|
33
32
|
/**
|
|
34
|
-
*
|
|
33
|
+
* This adapter's own JSON dialect: `{ input, sessionId? }` in, `{ output }`
|
|
34
|
+
* out, `{ status: 'ok', uptimeMs }` on the health path.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* carry (409) — the run is unfinished, not failed, and a 500 would say
|
|
40
|
-
* otherwise to every dashboard that ever sees it.
|
|
36
|
+
* Exported by name so a deployment that has to keep these exact bodies while
|
|
37
|
+
* changing something else about the host reuses them rather than retyping them
|
|
38
|
+
* and getting one field subtly wrong.
|
|
41
39
|
*/
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
export const jsonWire = {
|
|
41
|
+
readRequest(facts) {
|
|
42
|
+
const input = typeof facts.body.input === 'string' ? facts.body.input : '';
|
|
43
|
+
// sessionId is caller data whichever way it arrives; the body wins so a
|
|
44
|
+
// caller that sets both is not surprised by which one the server preferred.
|
|
45
|
+
const fromBody = typeof facts.body.sessionId === 'string' ? facts.body.sessionId : undefined;
|
|
46
|
+
const sessionId = fromBody ?? facts.headers['x-session-id'];
|
|
47
|
+
return sessionId !== undefined ? { input, sessionId } : { input };
|
|
48
|
+
},
|
|
49
|
+
health: (uptimeMs) => ({ status: 'ok', uptimeMs }),
|
|
50
|
+
output: (output) => ({ output }),
|
|
51
|
+
failure: (error, code) => ({ error, ...(code !== undefined && { code }) }),
|
|
52
|
+
chunk: (text) => ({ text }),
|
|
46
53
|
};
|
|
47
54
|
/**
|
|
48
55
|
* An HTTP host for one handler.
|
|
@@ -57,189 +64,15 @@ const STATUS_BY_CODE = {
|
|
|
57
64
|
* await handle.close();
|
|
58
65
|
*/
|
|
59
66
|
export function nodeHost(options = {}) {
|
|
60
|
-
|
|
61
|
-
const hostname = options.hostname ?? '0.0.0.0';
|
|
62
|
-
const invokePath = options.invokePath ?? '/invoke';
|
|
63
|
-
const healthPath = options.healthPath ?? '/health';
|
|
64
|
-
return {
|
|
67
|
+
return httpHost({
|
|
65
68
|
name: HOST_NAME,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const server = createServer((req, res) => {
|
|
74
|
-
const path = (req.url ?? '').split('?')[0];
|
|
75
|
-
if (req.method === 'GET' && path === healthPath) {
|
|
76
|
-
sendJson(res, 200, { status: 'ok', uptimeMs: Date.now() - startedAt });
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (req.method !== 'POST' || path !== invokePath) {
|
|
80
|
-
sendJson(res, 404, { error: `no route for ${req.method ?? '?'} ${path}` });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (!accepting) {
|
|
84
|
-
const refusal = new HostClosedError(HOST_NAME);
|
|
85
|
-
sendJson(res, STATUS_BY_CODE[refusal.code] ?? 500, {
|
|
86
|
-
error: refusal.message,
|
|
87
|
-
code: refusal.code,
|
|
88
|
-
});
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const served = serveOne(req, res, handler);
|
|
92
|
-
inFlight.add(served);
|
|
93
|
-
void served.finally(() => inFlight.delete(served));
|
|
94
|
-
});
|
|
95
|
-
await new Promise((resolve, reject) => {
|
|
96
|
-
server.once('error', reject);
|
|
97
|
-
server.listen(port, hostname, resolve);
|
|
98
|
-
});
|
|
99
|
-
const address = server.address();
|
|
100
|
-
const boundPort = typeof address === 'object' && address ? address.port : port;
|
|
101
|
-
const displayHost = hostname === '0.0.0.0' || hostname === '::' ? '127.0.0.1' : hostname;
|
|
102
|
-
return {
|
|
103
|
-
url: `http://${displayHost}:${boundPort}`,
|
|
104
|
-
port: boundPort,
|
|
105
|
-
close() {
|
|
106
|
-
// Idempotent: the first call owns the shutdown, later ones await it.
|
|
107
|
-
closing ??= (async () => {
|
|
108
|
-
accepting = false;
|
|
109
|
-
// Drain BEFORE touching sockets — an in-flight request is work the
|
|
110
|
-
// caller is still waiting on, and dropping it would be the exact
|
|
111
|
-
// thing close() promises not to do.
|
|
112
|
-
await Promise.allSettled([...inFlight]);
|
|
113
|
-
server.closeIdleConnections();
|
|
114
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
115
|
-
})();
|
|
116
|
-
return closing;
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/** Run one request through the handler and write whatever it decides. */
|
|
123
|
-
async function serveOne(req, res, handler) {
|
|
124
|
-
const wantsStream = (req.headers.accept ?? '').includes('text/event-stream');
|
|
125
|
-
const controller = new AbortController();
|
|
126
|
-
let body;
|
|
127
|
-
try {
|
|
128
|
-
body = await readJson(req);
|
|
129
|
-
}
|
|
130
|
-
catch (err) {
|
|
131
|
-
sendJson(res, 400, { error: `invalid JSON body: ${asError(err).message}` });
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const input = typeof body.input === 'string' ? body.input : '';
|
|
135
|
-
// sessionId is caller data whichever way it arrives; the body wins so a
|
|
136
|
-
// caller that sets both is not surprised by which one the server preferred.
|
|
137
|
-
const sessionId = typeof body.sessionId === 'string'
|
|
138
|
-
? body.sessionId
|
|
139
|
-
: typeof req.headers['x-session-id'] === 'string'
|
|
140
|
-
? req.headers['x-session-id']
|
|
141
|
-
: undefined;
|
|
142
|
-
if (wantsStream) {
|
|
143
|
-
res.writeHead(200, {
|
|
144
|
-
'content-type': 'text/event-stream',
|
|
145
|
-
'cache-control': 'no-cache',
|
|
146
|
-
connection: 'keep-alive',
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
let settled = false;
|
|
150
|
-
// The caller hung up before we answered — tell the handler so it can stop
|
|
151
|
-
// paying for work nobody is waiting for.
|
|
152
|
-
res.once('close', () => {
|
|
153
|
-
if (!settled)
|
|
154
|
-
controller.abort();
|
|
155
|
-
});
|
|
156
|
-
const reply = {
|
|
157
|
-
emit(chunk) {
|
|
158
|
-
// Not streaming? The chunk is a preview of text `complete` will deliver
|
|
159
|
-
// in full, so there is nothing to send and nothing to keep.
|
|
160
|
-
if (settled || !wantsStream)
|
|
161
|
-
return;
|
|
162
|
-
res.write(encodeSSE('chunk', { text: chunk }));
|
|
163
|
-
},
|
|
164
|
-
complete(output) {
|
|
165
|
-
if (settled)
|
|
166
|
-
return;
|
|
167
|
-
settled = true;
|
|
168
|
-
if (wantsStream) {
|
|
169
|
-
res.write(encodeSSE('complete', { output }));
|
|
170
|
-
res.end();
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
sendJson(res, 200, { output });
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
fail(error) {
|
|
177
|
-
if (settled)
|
|
178
|
-
return;
|
|
179
|
-
settled = true;
|
|
180
|
-
const code = error.code;
|
|
181
|
-
const payload = { error: error.message, ...(code !== undefined && { code }) };
|
|
182
|
-
if (wantsStream) {
|
|
183
|
-
res.write(encodeSSE('error', payload));
|
|
184
|
-
res.end();
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
sendJson(res, code !== undefined ? STATUS_BY_CODE[code] ?? 500 : 500, payload);
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
try {
|
|
192
|
-
await handler({
|
|
193
|
-
input,
|
|
194
|
-
...(sessionId !== undefined && { sessionId }),
|
|
195
|
-
headers: lowerCased(req.headers),
|
|
196
|
-
signal: controller.signal,
|
|
197
|
-
}, reply);
|
|
198
|
-
}
|
|
199
|
-
catch (err) {
|
|
200
|
-
// A handler that throws is a failed request, never a hung one.
|
|
201
|
-
reply.fail(asError(err));
|
|
202
|
-
}
|
|
203
|
-
// A handler that returned without answering gets one authored answer rather
|
|
204
|
-
// than a socket the caller waits on until it times out.
|
|
205
|
-
if (!settled) {
|
|
206
|
-
reply.fail(new Error('[hosting] the handler returned without calling complete() or fail().'));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
function readJson(req) {
|
|
210
|
-
return new Promise((resolve, reject) => {
|
|
211
|
-
const chunks = [];
|
|
212
|
-
req.on('data', (c) => chunks.push(c));
|
|
213
|
-
req.on('error', reject);
|
|
214
|
-
req.on('end', () => {
|
|
215
|
-
const raw = Buffer.concat(chunks).toString('utf8');
|
|
216
|
-
if (!raw)
|
|
217
|
-
return resolve({});
|
|
218
|
-
try {
|
|
219
|
-
const parsed = JSON.parse(raw);
|
|
220
|
-
resolve(parsed && typeof parsed === 'object' ? parsed : {});
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
reject(err);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
69
|
+
wire: jsonWire,
|
|
70
|
+
// Chosen, not inherited — see the test in this folder that greps for one
|
|
71
|
+
// particular runtime's container-contract path literal.
|
|
72
|
+
invokePath: options.invokePath ?? '/invoke',
|
|
73
|
+
healthPath: options.healthPath ?? '/health',
|
|
74
|
+
...(options.port !== undefined && { port: options.port }),
|
|
75
|
+
...(options.hostname !== undefined && { hostname: options.hostname }),
|
|
226
76
|
});
|
|
227
77
|
}
|
|
228
|
-
function sendJson(res, status, body) {
|
|
229
|
-
res.writeHead(status, { 'content-type': 'application/json' });
|
|
230
|
-
res.end(JSON.stringify(body));
|
|
231
|
-
}
|
|
232
|
-
function lowerCased(headers) {
|
|
233
|
-
const out = {};
|
|
234
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
235
|
-
if (typeof value === 'string')
|
|
236
|
-
out[name.toLowerCase()] = value;
|
|
237
|
-
else if (Array.isArray(value))
|
|
238
|
-
out[name.toLowerCase()] = value.join(', ');
|
|
239
|
-
}
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
function asError(err) {
|
|
243
|
-
return err instanceof Error ? err : new Error(String(err));
|
|
244
|
-
}
|
|
245
78
|
//# sourceMappingURL=nodeHost.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodeHost.js","sourceRoot":"","sources":["../../../src/hosting/nodeHost.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"nodeHost.js","sourceRoot":"","sources":["../../../src/hosting/nodeHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,QAAQ,EAAqD,MAAM,eAAe,CAAC;AAwB5F,MAAM,SAAS,GAAG,UAAU,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAa;IAChC,WAAW,CAAC,KAAK;QACf,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,wEAAwE;QACxE,4EAA4E;QAC5E,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7F,MAAM,SAAS,GAAG,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC5D,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;IACpE,CAAC;IACD,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAClD,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC1E,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CAAC,UAA2B,EAAE;IACpD,OAAO,QAAQ,CAAC;QACd,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,yEAAyE;QACzE,wDAAwD;QACxD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,SAAS;QAC3C,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,SAAS;QAC3C,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;QACzD,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;KACtE,CAAC,CAAC;AACL,CAAC"}
|