@heddleagent/execution-host-client 6.0.0-next.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/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/authority/index.d.ts +3 -0
- package/dist/authority/index.d.ts.map +1 -0
- package/dist/authority/index.js +2 -0
- package/dist/authority/index.js.map +1 -0
- package/dist/authority/jose-execution-authority.d.ts +14 -0
- package/dist/authority/jose-execution-authority.d.ts.map +1 -0
- package/dist/authority/jose-execution-authority.js +266 -0
- package/dist/authority/jose-execution-authority.js.map +1 -0
- package/dist/authority/types.d.ts +63 -0
- package/dist/authority/types.d.ts.map +1 -0
- package/dist/authority/types.js +2 -0
- package/dist/authority/types.js.map +1 -0
- package/dist/contracts/index.d.ts +163 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +149 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/conversation/durable-hosted-conversation-turn-service.d.ts +23 -0
- package/dist/conversation/durable-hosted-conversation-turn-service.d.ts.map +1 -0
- package/dist/conversation/durable-hosted-conversation-turn-service.js +273 -0
- package/dist/conversation/durable-hosted-conversation-turn-service.js.map +1 -0
- package/dist/conversation/hosted-conversation-turn-service.d.ts +17 -0
- package/dist/conversation/hosted-conversation-turn-service.d.ts.map +1 -0
- package/dist/conversation/hosted-conversation-turn-service.js +60 -0
- package/dist/conversation/hosted-conversation-turn-service.js.map +1 -0
- package/dist/conversation/index.d.ts +7 -0
- package/dist/conversation/index.d.ts.map +1 -0
- package/dist/conversation/index.js +5 -0
- package/dist/conversation/index.js.map +1 -0
- package/dist/conversation/lifecycle-types.d.ts +208 -0
- package/dist/conversation/lifecycle-types.d.ts.map +1 -0
- package/dist/conversation/lifecycle-types.js +92 -0
- package/dist/conversation/lifecycle-types.js.map +1 -0
- package/dist/conversation/types.d.ts +35 -0
- package/dist/conversation/types.d.ts.map +1 -0
- package/dist/conversation/types.js +11 -0
- package/dist/conversation/types.js.map +1 -0
- package/dist/http-sse/direct-http-execution-host.d.ts +12 -0
- package/dist/http-sse/direct-http-execution-host.d.ts.map +1 -0
- package/dist/http-sse/direct-http-execution-host.js +279 -0
- package/dist/http-sse/direct-http-execution-host.js.map +1 -0
- package/dist/http-sse/errors.d.ts +19 -0
- package/dist/http-sse/errors.d.ts.map +1 -0
- package/dist/http-sse/errors.js +29 -0
- package/dist/http-sse/errors.js.map +1 -0
- package/dist/http-sse/index.d.ts +4 -0
- package/dist/http-sse/index.d.ts.map +1 -0
- package/dist/http-sse/index.js +3 -0
- package/dist/http-sse/index.js.map +1 -0
- package/dist/http-sse/types.d.ts +20 -0
- package/dist/http-sse/types.d.ts.map +1 -0
- package/dist/http-sse/types.js +2 -0
- package/dist/http-sse/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/errors.d.ts +11 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +15 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/jwt-capability-verifier.d.ts +12 -0
- package/dist/mcp/jwt-capability-verifier.d.ts.map +1 -0
- package/dist/mcp/jwt-capability-verifier.js +136 -0
- package/dist/mcp/jwt-capability-verifier.js.map +1 -0
- package/dist/mcp/node/index.d.ts +4 -0
- package/dist/mcp/node/index.d.ts.map +1 -0
- package/dist/mcp/node/index.js +3 -0
- package/dist/mcp/node/index.js.map +1 -0
- package/dist/mcp/node/json-toolset.d.ts +23 -0
- package/dist/mcp/node/json-toolset.d.ts.map +1 -0
- package/dist/mcp/node/json-toolset.js +94 -0
- package/dist/mcp/node/json-toolset.js.map +1 -0
- package/dist/mcp/node/streamable-http-mcp-service.d.ts +18 -0
- package/dist/mcp/node/streamable-http-mcp-service.d.ts.map +1 -0
- package/dist/mcp/node/streamable-http-mcp-service.js +229 -0
- package/dist/mcp/node/streamable-http-mcp-service.js.map +1 -0
- package/dist/mcp/node/types.d.ts +53 -0
- package/dist/mcp/node/types.d.ts.map +1 -0
- package/dist/mcp/node/types.js +2 -0
- package/dist/mcp/node/types.js.map +1 -0
- package/dist/mcp/types.d.ts +37 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/node/authority-key.d.ts +14 -0
- package/dist/node/authority-key.d.ts.map +1 -0
- package/dist/node/authority-key.js +89 -0
- package/dist/node/authority-key.js.map +1 -0
- package/dist/node/direct-credentials.d.ts +21 -0
- package/dist/node/direct-credentials.d.ts.map +1 -0
- package/dist/node/direct-credentials.js +47 -0
- package/dist/node/direct-credentials.js.map +1 -0
- package/dist/node/http-service.d.ts +21 -0
- package/dist/node/http-service.d.ts.map +1 -0
- package/dist/node/http-service.js +389 -0
- package/dist/node/http-service.js.map +1 -0
- package/dist/node/index.d.ts +6 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +4 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/types.d.ts +52 -0
- package/dist/node/types.d.ts.map +1 -0
- package/dist/node/types.js +2 -0
- package/dist/node/types.js.map +1 -0
- package/dist/testing/event-stream.d.ts +17 -0
- package/dist/testing/event-stream.d.ts.map +1 -0
- package/dist/testing/event-stream.js +83 -0
- package/dist/testing/event-stream.js.map +1 -0
- package/dist/testing/hosted-conversation-turn-store-conformance.d.ts +17 -0
- package/dist/testing/hosted-conversation-turn-store-conformance.d.ts.map +1 -0
- package/dist/testing/hosted-conversation-turn-store-conformance.js +137 -0
- package/dist/testing/hosted-conversation-turn-store-conformance.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/invocation.d.ts +20 -0
- package/dist/testing/invocation.d.ts.map +1 -0
- package/dist/testing/invocation.js +39 -0
- package/dist/testing/invocation.js.map +1 -0
- package/dist/testing/local-execution-host-contract-fixture.d.ts +21 -0
- package/dist/testing/local-execution-host-contract-fixture.d.ts.map +1 -0
- package/dist/testing/local-execution-host-contract-fixture.js +197 -0
- package/dist/testing/local-execution-host-contract-fixture.js.map +1 -0
- package/dist/testing/request.d.ts +10 -0
- package/dist/testing/request.d.ts.map +1 -0
- package/dist/testing/request.js +140 -0
- package/dist/testing/request.js.map +1 -0
- package/dist/testing/types.d.ts +42 -0
- package/dist/testing/types.d.ts.map +1 -0
- package/dist/testing/types.js +2 -0
- package/dist/testing/types.js.map +1 -0
- package/package.json +92 -0
- package/spec/v1/README.md +101 -0
- package/spec/v1/durable-hosted-conversation-lifecycle.md +139 -0
- package/spec/v1/fixtures/ambiguous-eof.sse +9 -0
- package/spec/v1/fixtures/authority.json +70 -0
- package/spec/v1/fixtures/cancelled.sse +9 -0
- package/spec/v1/fixtures/durable-conversation-lifecycle.json +890 -0
- package/spec/v1/fixtures/invalid-request-extra-field.json +8 -0
- package/spec/v1/fixtures/invalid-sequence-gap.sse +9 -0
- package/spec/v1/fixtures/manifest.json +53 -0
- package/spec/v1/fixtures/valid-request.json +7 -0
- package/spec/v1/fixtures/valid-result.sse +13 -0
- package/spec/v1/openapi.json +896 -0
- package/spec/v1/schema-bundle.json +1286 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { once } from 'node:events';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ExecutionHostStreamEventSchema } from '../contracts/index.js';
|
|
4
|
+
import { ExecutionHostInvocationCancelledError } from '../http-sse/index.js';
|
|
5
|
+
export const DEFAULT_ADOPTER_JWKS_PATH = '/.well-known/jwks.json';
|
|
6
|
+
export const DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH = '/hosted-execution/conversation-turns';
|
|
7
|
+
const DEFAULT_MAX_BODY_BYTES = 64 * 1_024;
|
|
8
|
+
const DEFAULT_MAX_PROMPT_CHARACTERS = 20_000;
|
|
9
|
+
const REDACTED_HEADER_VALUE = '[REDACTED]';
|
|
10
|
+
const MAX_AUTHORIZATION_CHARACTERS = 8_192;
|
|
11
|
+
const PathSchema = z.string().min(1).max(256).regex(/^\/(?:[^?#\s]*)$/, 'must be an absolute path without query, fragment, or whitespace');
|
|
12
|
+
const PublicErrorSchema = z.object({
|
|
13
|
+
statusCode: z.number().int().min(400).max(599),
|
|
14
|
+
message: z.string().min(1).max(1_600),
|
|
15
|
+
}).strict();
|
|
16
|
+
class AdopterHttpRequestError extends Error {
|
|
17
|
+
statusCode;
|
|
18
|
+
constructor(statusCode) {
|
|
19
|
+
super('Invalid adopter HTTP request.');
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Standard Node HTTP edge for adopter-side JWKS and hosted conversations.
|
|
25
|
+
*
|
|
26
|
+
* The service owns bounded parsing, credential-header scrubbing, SSE framing,
|
|
27
|
+
* disconnect cancellation, safe failures, and graceful shutdown. Product
|
|
28
|
+
* authentication, authorization, identity selection, and persistence stay in
|
|
29
|
+
* adopter callbacks.
|
|
30
|
+
*/
|
|
31
|
+
export class NodeExecutionAdopterHttpService {
|
|
32
|
+
#authority;
|
|
33
|
+
#authenticator;
|
|
34
|
+
#conversations;
|
|
35
|
+
#projectError;
|
|
36
|
+
#reportFailure;
|
|
37
|
+
#paths;
|
|
38
|
+
#maxBodyBytes;
|
|
39
|
+
#maxPromptCharacters;
|
|
40
|
+
#jwksMaxAgeSeconds;
|
|
41
|
+
#activeConversations = new Set();
|
|
42
|
+
#closed = false;
|
|
43
|
+
#closePromise;
|
|
44
|
+
constructor(config) {
|
|
45
|
+
this.#authority = config.authority;
|
|
46
|
+
this.#authenticator = config.authenticator;
|
|
47
|
+
this.#conversations = config.conversations;
|
|
48
|
+
this.#projectError = config.projectError;
|
|
49
|
+
this.#reportFailure = config.reportFailure;
|
|
50
|
+
this.#paths = parsePaths(config.paths);
|
|
51
|
+
this.#maxBodyBytes = z.number().int().min(1).max(1_048_576).parse(config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
|
|
52
|
+
this.#maxPromptCharacters = z.number().int().min(1).max(200_000).parse(config.maxPromptCharacters ?? DEFAULT_MAX_PROMPT_CHARACTERS);
|
|
53
|
+
this.#jwksMaxAgeSeconds = z.number().int().min(0).max(86_400).parse(config.jwksMaxAgeSeconds ?? 60);
|
|
54
|
+
}
|
|
55
|
+
handle(request, response) {
|
|
56
|
+
const pathname = readPathname(request.url);
|
|
57
|
+
if (pathname === this.#paths.jwks) {
|
|
58
|
+
this.handleJwks(request, response);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (pathname === this.#paths.conversationTurns) {
|
|
62
|
+
this.handleConversationTurn(request, response);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
handleJwks(request, response) {
|
|
68
|
+
request.resume();
|
|
69
|
+
if (this.#closed) {
|
|
70
|
+
writeJsonError(response, 503, 'Hosted execution is unavailable.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (request.method !== 'GET') {
|
|
74
|
+
writeJsonError(response, 405, 'Method not allowed.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
writeJson(response, 200, this.#authority.publicJwks(), {
|
|
79
|
+
'Cache-Control': `public, max-age=${this.#jwksMaxAgeSeconds}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.#report({
|
|
84
|
+
phase: 'jwks',
|
|
85
|
+
errorType: errorType(error),
|
|
86
|
+
streamAccepted: false,
|
|
87
|
+
});
|
|
88
|
+
writeJsonError(response, 500, 'Verification keys are unavailable.');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
handleConversationTurn(request, response) {
|
|
92
|
+
if (this.#closed) {
|
|
93
|
+
request.resume();
|
|
94
|
+
writeJsonError(response, 503, 'Hosted execution is unavailable.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const abortController = new AbortController();
|
|
98
|
+
const abort = () => abortController.abort(new Error('The owning adopter HTTP request closed.'));
|
|
99
|
+
request.once('aborted', abort);
|
|
100
|
+
response.once('close', abort);
|
|
101
|
+
const active = {
|
|
102
|
+
abortController,
|
|
103
|
+
request,
|
|
104
|
+
response,
|
|
105
|
+
pending: Promise.resolve(),
|
|
106
|
+
};
|
|
107
|
+
active.pending = this.#serveConversation(request, response, abortController.signal).catch((error) => {
|
|
108
|
+
this.#report({
|
|
109
|
+
phase: 'conversation',
|
|
110
|
+
errorType: errorType(error),
|
|
111
|
+
streamAccepted: response.headersSent,
|
|
112
|
+
});
|
|
113
|
+
if (!response.headersSent) {
|
|
114
|
+
writeJsonError(response, 500, 'Hosted execution request failed.');
|
|
115
|
+
}
|
|
116
|
+
else if (!response.destroyed && !response.writableEnded) {
|
|
117
|
+
response.end();
|
|
118
|
+
}
|
|
119
|
+
}).finally(() => {
|
|
120
|
+
request.removeListener('aborted', abort);
|
|
121
|
+
response.removeListener('close', abort);
|
|
122
|
+
this.#activeConversations.delete(active);
|
|
123
|
+
});
|
|
124
|
+
this.#activeConversations.add(active);
|
|
125
|
+
}
|
|
126
|
+
close() {
|
|
127
|
+
if (!this.#closePromise) {
|
|
128
|
+
this.#closed = true;
|
|
129
|
+
this.#closePromise = this.#closeActiveConversations();
|
|
130
|
+
}
|
|
131
|
+
return this.#closePromise;
|
|
132
|
+
}
|
|
133
|
+
async #closeActiveConversations() {
|
|
134
|
+
const active = [...this.#activeConversations];
|
|
135
|
+
active.forEach(({ abortController, request, response }) => {
|
|
136
|
+
abortController.abort(new Error('The adopter HTTP service is stopping.'));
|
|
137
|
+
request.destroy();
|
|
138
|
+
response.destroy();
|
|
139
|
+
});
|
|
140
|
+
await Promise.allSettled(active.map(({ pending }) => pending));
|
|
141
|
+
}
|
|
142
|
+
async #serveConversation(request, response, signal) {
|
|
143
|
+
let authorization;
|
|
144
|
+
try {
|
|
145
|
+
authorization = takeAuthorization(request);
|
|
146
|
+
if (request.method !== 'POST') {
|
|
147
|
+
request.resume();
|
|
148
|
+
writeJsonError(response, 405, 'Method not allowed.');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
request.resume();
|
|
154
|
+
writeJsonError(response, error instanceof AdopterHttpRequestError ? error.statusCode : 400, 'Invalid conversation request.');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let principal;
|
|
158
|
+
try {
|
|
159
|
+
principal = await this.#authenticator.authenticate({
|
|
160
|
+
authorization,
|
|
161
|
+
remoteAddress: request.socket.remoteAddress,
|
|
162
|
+
signal,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
this.#report({
|
|
167
|
+
phase: 'authentication',
|
|
168
|
+
errorType: errorType(error),
|
|
169
|
+
streamAccepted: false,
|
|
170
|
+
});
|
|
171
|
+
request.resume();
|
|
172
|
+
writeJsonError(response, 500, 'Authentication could not be completed.');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!principal) {
|
|
176
|
+
request.resume();
|
|
177
|
+
writeJsonError(response, 401, 'Authentication is required.', {
|
|
178
|
+
'WWW-Authenticate': 'Bearer',
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let prompt;
|
|
183
|
+
try {
|
|
184
|
+
const body = await readJsonBody(request, this.#maxBodyBytes, signal);
|
|
185
|
+
prompt = z.object({
|
|
186
|
+
prompt: z.string().trim().min(1).max(this.#maxPromptCharacters),
|
|
187
|
+
}).strict().parse(body).prompt;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const statusCode = error instanceof AdopterHttpRequestError
|
|
191
|
+
? error.statusCode
|
|
192
|
+
: 400;
|
|
193
|
+
writeJsonError(response, statusCode, 'Invalid conversation request.');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
let streamed = false;
|
|
197
|
+
try {
|
|
198
|
+
for await (const rawEvent of this.#conversations.streamTurn({
|
|
199
|
+
principal,
|
|
200
|
+
prompt,
|
|
201
|
+
signal,
|
|
202
|
+
})) {
|
|
203
|
+
const event = ExecutionHostStreamEventSchema.parse(rawEvent);
|
|
204
|
+
if (!streamed) {
|
|
205
|
+
writeEventStreamHeaders(response);
|
|
206
|
+
streamed = true;
|
|
207
|
+
}
|
|
208
|
+
await writeSseEvent(response, event, signal);
|
|
209
|
+
}
|
|
210
|
+
if (!streamed) {
|
|
211
|
+
writeJsonError(response, 502, 'Execution Host returned no events.');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
response.end();
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const publicError = !streamed ? this.#project(error) : undefined;
|
|
218
|
+
if (publicError) {
|
|
219
|
+
writeJsonError(response, publicError.statusCode, publicError.message);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (error instanceof ExecutionHostInvocationCancelledError
|
|
223
|
+
|| signal.aborted) {
|
|
224
|
+
if (!response.headersSent && !response.destroyed) {
|
|
225
|
+
writeJsonError(response, 499, 'Hosted conversation was cancelled.');
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this.#report({
|
|
230
|
+
phase: 'conversation',
|
|
231
|
+
errorType: errorType(error),
|
|
232
|
+
streamAccepted: streamed,
|
|
233
|
+
});
|
|
234
|
+
if (!response.headersSent) {
|
|
235
|
+
writeJsonError(response, 502, 'Execution Host conversation failed.');
|
|
236
|
+
}
|
|
237
|
+
else if (!response.destroyed && !response.writableEnded) {
|
|
238
|
+
// A post-acceptance failure deliberately ends without a terminal event.
|
|
239
|
+
response.end();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
#project(error) {
|
|
244
|
+
if (!this.#projectError) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const projected = this.#projectError(error);
|
|
249
|
+
return projected ? PublicErrorSchema.parse(projected) : undefined;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
#report(failure) {
|
|
256
|
+
try {
|
|
257
|
+
this.#reportFailure?.(Object.freeze({ ...failure }));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Observability must not change request settlement.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function parsePaths(paths) {
|
|
265
|
+
const parsed = {
|
|
266
|
+
jwks: PathSchema.parse(paths?.jwks ?? DEFAULT_ADOPTER_JWKS_PATH),
|
|
267
|
+
conversationTurns: PathSchema.parse(paths?.conversationTurns ?? DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH),
|
|
268
|
+
};
|
|
269
|
+
if (parsed.jwks === parsed.conversationTurns) {
|
|
270
|
+
throw new Error('Adopter HTTP paths must be distinct.');
|
|
271
|
+
}
|
|
272
|
+
return Object.freeze(parsed);
|
|
273
|
+
}
|
|
274
|
+
function readPathname(rawUrl) {
|
|
275
|
+
try {
|
|
276
|
+
return new URL(rawUrl ?? '/', 'http://localhost').pathname;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function takeAuthorization(request) {
|
|
283
|
+
const occurrences = request.rawHeaders.reduce((count, headerName, index) => count
|
|
284
|
+
+ (index % 2 === 0 && headerName.toLowerCase() === 'authorization' ? 1 : 0), 0);
|
|
285
|
+
const raw = request.headers.authorization;
|
|
286
|
+
request.headers.authorization = raw === undefined
|
|
287
|
+
? undefined
|
|
288
|
+
: REDACTED_HEADER_VALUE;
|
|
289
|
+
for (let index = 0; index < request.rawHeaders.length; index += 2) {
|
|
290
|
+
if (request.rawHeaders[index]?.toLowerCase() === 'authorization') {
|
|
291
|
+
request.rawHeaders[index + 1] = REDACTED_HEADER_VALUE;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (occurrences > 1 || Array.isArray(raw)) {
|
|
295
|
+
throw new AdopterHttpRequestError(400);
|
|
296
|
+
}
|
|
297
|
+
if (raw && raw.length > MAX_AUTHORIZATION_CHARACTERS) {
|
|
298
|
+
throw new AdopterHttpRequestError(400);
|
|
299
|
+
}
|
|
300
|
+
return raw;
|
|
301
|
+
}
|
|
302
|
+
async function readJsonBody(request, maxBodyBytes, signal) {
|
|
303
|
+
const contentType = request.headers['content-type']
|
|
304
|
+
?.split(';', 1)[0]
|
|
305
|
+
?.trim()
|
|
306
|
+
.toLowerCase();
|
|
307
|
+
if (contentType !== 'application/json') {
|
|
308
|
+
request.resume();
|
|
309
|
+
throw new AdopterHttpRequestError(415);
|
|
310
|
+
}
|
|
311
|
+
const contentLength = request.headers['content-length'];
|
|
312
|
+
if (contentLength !== undefined) {
|
|
313
|
+
const declaredLength = Number(contentLength);
|
|
314
|
+
if (!Number.isSafeInteger(declaredLength) || declaredLength < 0) {
|
|
315
|
+
request.resume();
|
|
316
|
+
throw new AdopterHttpRequestError(400);
|
|
317
|
+
}
|
|
318
|
+
if (declaredLength > maxBodyBytes) {
|
|
319
|
+
request.resume();
|
|
320
|
+
throw new AdopterHttpRequestError(413);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const chunks = [];
|
|
324
|
+
let receivedBytes = 0;
|
|
325
|
+
for await (const chunk of request) {
|
|
326
|
+
signal.throwIfAborted();
|
|
327
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
328
|
+
receivedBytes += buffer.byteLength;
|
|
329
|
+
if (receivedBytes > maxBodyBytes) {
|
|
330
|
+
request.resume();
|
|
331
|
+
throw new AdopterHttpRequestError(413);
|
|
332
|
+
}
|
|
333
|
+
chunks.push(buffer);
|
|
334
|
+
}
|
|
335
|
+
if (!request.complete || receivedBytes === 0) {
|
|
336
|
+
throw new AdopterHttpRequestError(400);
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
return JSON.parse(Buffer.concat(chunks, receivedBytes).toString('utf8'));
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
throw new AdopterHttpRequestError(400);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function writeEventStreamHeaders(response) {
|
|
346
|
+
response.writeHead(200, {
|
|
347
|
+
'Cache-Control': 'no-store',
|
|
348
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
349
|
+
Connection: 'keep-alive',
|
|
350
|
+
'X-Accel-Buffering': 'no',
|
|
351
|
+
'X-Content-Type-Options': 'nosniff',
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
async function writeSseEvent(response, event, signal) {
|
|
355
|
+
const frame = [
|
|
356
|
+
`event: ${event.kind}`,
|
|
357
|
+
`id: ${event.sequence}`,
|
|
358
|
+
`data: ${JSON.stringify(event)}`,
|
|
359
|
+
'',
|
|
360
|
+
'',
|
|
361
|
+
].join('\n');
|
|
362
|
+
if (!response.write(frame)) {
|
|
363
|
+
await once(response, 'drain', { signal });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function writeJsonError(response, statusCode, message, headers = {}) {
|
|
367
|
+
writeJson(response, statusCode, { error: { message } }, {
|
|
368
|
+
'Cache-Control': 'no-store',
|
|
369
|
+
...headers,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function writeJson(response, statusCode, value, headers = {}) {
|
|
373
|
+
if (response.headersSent || response.destroyed) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const body = JSON.stringify(value);
|
|
377
|
+
response.writeHead(statusCode, {
|
|
378
|
+
'Cache-Control': 'no-store',
|
|
379
|
+
'Content-Length': Buffer.byteLength(body),
|
|
380
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
381
|
+
'X-Content-Type-Options': 'nosniff',
|
|
382
|
+
...headers,
|
|
383
|
+
});
|
|
384
|
+
response.end(body);
|
|
385
|
+
}
|
|
386
|
+
function errorType(error) {
|
|
387
|
+
return error instanceof Error ? error.name : 'unknown';
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=http-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-service.js","sourceRoot":"","sources":["../../src/node/http-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { once } from 'node:events';\nimport type {\n IncomingMessage,\n ServerResponse,\n} from 'node:http';\nimport { z } from 'zod';\nimport { ExecutionHostStreamEventSchema } from '../contracts/index.js';\nimport { ExecutionHostInvocationCancelledError } from '../http-sse/index.js';\nimport type {\n NodeExecutionAdopterFailure,\n NodeExecutionAdopterHttpHandler,\n NodeExecutionAdopterHttpPaths,\n NodeExecutionAdopterHttpServiceConfig,\n NodeExecutionAdopterPublicError,\n} from './types.js';\n\nexport const DEFAULT_ADOPTER_JWKS_PATH = '/.well-known/jwks.json';\nexport const DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH =\n '/hosted-execution/conversation-turns';\n\nconst DEFAULT_MAX_BODY_BYTES = 64 * 1_024;\nconst DEFAULT_MAX_PROMPT_CHARACTERS = 20_000;\nconst REDACTED_HEADER_VALUE = '[REDACTED]';\nconst MAX_AUTHORIZATION_CHARACTERS = 8_192;\nconst PathSchema = z.string().min(1).max(256).regex(\n /^\\/(?:[^?#\\s]*)$/,\n 'must be an absolute path without query, fragment, or whitespace',\n);\nconst PublicErrorSchema = z.object({\n statusCode: z.number().int().min(400).max(599),\n message: z.string().min(1).max(1_600),\n}).strict();\n\ntype ActiveConversation = {\n abortController: AbortController;\n request: IncomingMessage;\n response: ServerResponse;\n pending: Promise<void>;\n};\n\nclass AdopterHttpRequestError extends Error {\n constructor(readonly statusCode: 400 | 413 | 415) {\n super('Invalid adopter HTTP request.');\n }\n}\n\n/**\n * Standard Node HTTP edge for adopter-side JWKS and hosted conversations.\n *\n * The service owns bounded parsing, credential-header scrubbing, SSE framing,\n * disconnect cancellation, safe failures, and graceful shutdown. Product\n * authentication, authorization, identity selection, and persistence stay in\n * adopter callbacks.\n */\nexport class NodeExecutionAdopterHttpService<Principal>\nimplements NodeExecutionAdopterHttpHandler {\n readonly #authority: NodeExecutionAdopterHttpServiceConfig<Principal>[\n 'authority'\n ];\n readonly #authenticator: NodeExecutionAdopterHttpServiceConfig<Principal>[\n 'authenticator'\n ];\n readonly #conversations: NodeExecutionAdopterHttpServiceConfig<\n Principal\n >['conversations'];\n readonly #projectError: NodeExecutionAdopterHttpServiceConfig<Principal>[\n 'projectError'\n ];\n readonly #reportFailure: NodeExecutionAdopterHttpServiceConfig<Principal>[\n 'reportFailure'\n ];\n readonly #paths: Readonly<NodeExecutionAdopterHttpPaths>;\n readonly #maxBodyBytes: number;\n readonly #maxPromptCharacters: number;\n readonly #jwksMaxAgeSeconds: number;\n readonly #activeConversations = new Set<ActiveConversation>();\n #closed = false;\n #closePromise: Promise<void> | undefined;\n\n constructor(config: NodeExecutionAdopterHttpServiceConfig<Principal>) {\n this.#authority = config.authority;\n this.#authenticator = config.authenticator;\n this.#conversations = config.conversations;\n this.#projectError = config.projectError;\n this.#reportFailure = config.reportFailure;\n this.#paths = parsePaths(config.paths);\n this.#maxBodyBytes = z.number().int().min(1).max(1_048_576).parse(\n config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n );\n this.#maxPromptCharacters = z.number().int().min(1).max(200_000).parse(\n config.maxPromptCharacters ?? DEFAULT_MAX_PROMPT_CHARACTERS,\n );\n this.#jwksMaxAgeSeconds = z.number().int().min(0).max(86_400).parse(\n config.jwksMaxAgeSeconds ?? 60,\n );\n }\n\n handle(request: IncomingMessage, response: ServerResponse): boolean {\n const pathname = readPathname(request.url);\n if (pathname === this.#paths.jwks) {\n this.handleJwks(request, response);\n return true;\n }\n if (pathname === this.#paths.conversationTurns) {\n this.handleConversationTurn(request, response);\n return true;\n }\n return false;\n }\n\n handleJwks(request: IncomingMessage, response: ServerResponse): void {\n request.resume();\n if (this.#closed) {\n writeJsonError(response, 503, 'Hosted execution is unavailable.');\n return;\n }\n if (request.method !== 'GET') {\n writeJsonError(response, 405, 'Method not allowed.');\n return;\n }\n try {\n writeJson(response, 200, this.#authority.publicJwks(), {\n 'Cache-Control': `public, max-age=${this.#jwksMaxAgeSeconds}`,\n });\n } catch (error) {\n this.#report({\n phase: 'jwks',\n errorType: errorType(error),\n streamAccepted: false,\n });\n writeJsonError(response, 500, 'Verification keys are unavailable.');\n }\n }\n\n handleConversationTurn(\n request: IncomingMessage,\n response: ServerResponse,\n ): void {\n if (this.#closed) {\n request.resume();\n writeJsonError(response, 503, 'Hosted execution is unavailable.');\n return;\n }\n\n const abortController = new AbortController();\n const abort = () => abortController.abort(\n new Error('The owning adopter HTTP request closed.'),\n );\n request.once('aborted', abort);\n response.once('close', abort);\n const active: ActiveConversation = {\n abortController,\n request,\n response,\n pending: Promise.resolve(),\n };\n active.pending = this.#serveConversation(\n request,\n response,\n abortController.signal,\n ).catch((error) => {\n this.#report({\n phase: 'conversation',\n errorType: errorType(error),\n streamAccepted: response.headersSent,\n });\n if (!response.headersSent) {\n writeJsonError(response, 500, 'Hosted execution request failed.');\n } else if (!response.destroyed && !response.writableEnded) {\n response.end();\n }\n }).finally(() => {\n request.removeListener('aborted', abort);\n response.removeListener('close', abort);\n this.#activeConversations.delete(active);\n });\n this.#activeConversations.add(active);\n }\n\n close(): Promise<void> {\n if (!this.#closePromise) {\n this.#closed = true;\n this.#closePromise = this.#closeActiveConversations();\n }\n return this.#closePromise;\n }\n\n async #closeActiveConversations(): Promise<void> {\n const active = [...this.#activeConversations];\n active.forEach(({ abortController, request, response }) => {\n abortController.abort(new Error('The adopter HTTP service is stopping.'));\n request.destroy();\n response.destroy();\n });\n await Promise.allSettled(active.map(({ pending }) => pending));\n }\n\n async #serveConversation(\n request: IncomingMessage,\n response: ServerResponse,\n signal: AbortSignal,\n ): Promise<void> {\n let authorization: string | undefined;\n try {\n authorization = takeAuthorization(request);\n if (request.method !== 'POST') {\n request.resume();\n writeJsonError(response, 405, 'Method not allowed.');\n return;\n }\n } catch (error) {\n request.resume();\n writeJsonError(\n response,\n error instanceof AdopterHttpRequestError ? error.statusCode : 400,\n 'Invalid conversation request.',\n );\n return;\n }\n\n let principal: Principal | undefined;\n try {\n principal = await this.#authenticator.authenticate({\n authorization,\n remoteAddress: request.socket.remoteAddress,\n signal,\n });\n } catch (error) {\n this.#report({\n phase: 'authentication',\n errorType: errorType(error),\n streamAccepted: false,\n });\n request.resume();\n writeJsonError(response, 500, 'Authentication could not be completed.');\n return;\n }\n if (!principal) {\n request.resume();\n writeJsonError(response, 401, 'Authentication is required.', {\n 'WWW-Authenticate': 'Bearer',\n });\n return;\n }\n\n let prompt: string;\n try {\n const body = await readJsonBody(request, this.#maxBodyBytes, signal);\n prompt = z.object({\n prompt: z.string().trim().min(1).max(this.#maxPromptCharacters),\n }).strict().parse(body).prompt;\n } catch (error) {\n const statusCode = error instanceof AdopterHttpRequestError\n ? error.statusCode\n : 400;\n writeJsonError(response, statusCode, 'Invalid conversation request.');\n return;\n }\n\n let streamed = false;\n try {\n for await (const rawEvent of this.#conversations.streamTurn({\n principal,\n prompt,\n signal,\n })) {\n const event = ExecutionHostStreamEventSchema.parse(rawEvent);\n if (!streamed) {\n writeEventStreamHeaders(response);\n streamed = true;\n }\n await writeSseEvent(response, event, signal);\n }\n if (!streamed) {\n writeJsonError(response, 502, 'Execution Host returned no events.');\n return;\n }\n response.end();\n } catch (error) {\n const publicError = !streamed ? this.#project(error) : undefined;\n if (publicError) {\n writeJsonError(response, publicError.statusCode, publicError.message);\n return;\n }\n if (\n error instanceof ExecutionHostInvocationCancelledError\n || signal.aborted\n ) {\n if (!response.headersSent && !response.destroyed) {\n writeJsonError(response, 499, 'Hosted conversation was cancelled.');\n }\n return;\n }\n this.#report({\n phase: 'conversation',\n errorType: errorType(error),\n streamAccepted: streamed,\n });\n if (!response.headersSent) {\n writeJsonError(response, 502, 'Execution Host conversation failed.');\n } else if (!response.destroyed && !response.writableEnded) {\n // A post-acceptance failure deliberately ends without a terminal event.\n response.end();\n }\n }\n }\n\n #project(error: unknown): NodeExecutionAdopterPublicError | undefined {\n if (!this.#projectError) {\n return undefined;\n }\n try {\n const projected = this.#projectError(error);\n return projected ? PublicErrorSchema.parse(projected) : undefined;\n } catch {\n return undefined;\n }\n }\n\n #report(failure: NodeExecutionAdopterFailure): void {\n try {\n this.#reportFailure?.(Object.freeze({ ...failure }));\n } catch {\n // Observability must not change request settlement.\n }\n }\n}\n\nfunction parsePaths(\n paths: Partial<NodeExecutionAdopterHttpPaths> | undefined,\n): Readonly<NodeExecutionAdopterHttpPaths> {\n const parsed = {\n jwks: PathSchema.parse(paths?.jwks ?? DEFAULT_ADOPTER_JWKS_PATH),\n conversationTurns: PathSchema.parse(\n paths?.conversationTurns ?? DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH,\n ),\n };\n if (parsed.jwks === parsed.conversationTurns) {\n throw new Error('Adopter HTTP paths must be distinct.');\n }\n return Object.freeze(parsed);\n}\n\nfunction readPathname(rawUrl: string | undefined): string | undefined {\n try {\n return new URL(rawUrl ?? '/', 'http://localhost').pathname;\n } catch {\n return undefined;\n }\n}\n\nfunction takeAuthorization(request: IncomingMessage): string | undefined {\n const occurrences = request.rawHeaders.reduce(\n (count, headerName, index) => count\n + (index % 2 === 0 && headerName.toLowerCase() === 'authorization' ? 1 : 0),\n 0,\n );\n const raw = request.headers.authorization;\n request.headers.authorization = raw === undefined\n ? undefined\n : REDACTED_HEADER_VALUE;\n for (let index = 0; index < request.rawHeaders.length; index += 2) {\n if (request.rawHeaders[index]?.toLowerCase() === 'authorization') {\n request.rawHeaders[index + 1] = REDACTED_HEADER_VALUE;\n }\n }\n if (occurrences > 1 || Array.isArray(raw)) {\n throw new AdopterHttpRequestError(400);\n }\n if (raw && raw.length > MAX_AUTHORIZATION_CHARACTERS) {\n throw new AdopterHttpRequestError(400);\n }\n return raw;\n}\n\nasync function readJsonBody(\n request: IncomingMessage,\n maxBodyBytes: number,\n signal: AbortSignal,\n): Promise<unknown> {\n const contentType = request.headers['content-type']\n ?.split(';', 1)[0]\n ?.trim()\n .toLowerCase();\n if (contentType !== 'application/json') {\n request.resume();\n throw new AdopterHttpRequestError(415);\n }\n const contentLength = request.headers['content-length'];\n if (contentLength !== undefined) {\n const declaredLength = Number(contentLength);\n if (!Number.isSafeInteger(declaredLength) || declaredLength < 0) {\n request.resume();\n throw new AdopterHttpRequestError(400);\n }\n if (declaredLength > maxBodyBytes) {\n request.resume();\n throw new AdopterHttpRequestError(413);\n }\n }\n\n const chunks: Buffer[] = [];\n let receivedBytes = 0;\n for await (const chunk of request) {\n signal.throwIfAborted();\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n receivedBytes += buffer.byteLength;\n if (receivedBytes > maxBodyBytes) {\n request.resume();\n throw new AdopterHttpRequestError(413);\n }\n chunks.push(buffer);\n }\n if (!request.complete || receivedBytes === 0) {\n throw new AdopterHttpRequestError(400);\n }\n try {\n return JSON.parse(\n Buffer.concat(chunks, receivedBytes).toString('utf8'),\n ) as unknown;\n } catch {\n throw new AdopterHttpRequestError(400);\n }\n}\n\nfunction writeEventStreamHeaders(response: ServerResponse): void {\n response.writeHead(200, {\n 'Cache-Control': 'no-store',\n 'Content-Type': 'text/event-stream; charset=utf-8',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n 'X-Content-Type-Options': 'nosniff',\n });\n}\n\nasync function writeSseEvent(\n response: ServerResponse,\n event: z.infer<typeof ExecutionHostStreamEventSchema>,\n signal: AbortSignal,\n): Promise<void> {\n const frame = [\n `event: ${event.kind}`,\n `id: ${event.sequence}`,\n `data: ${JSON.stringify(event)}`,\n '',\n '',\n ].join('\\n');\n if (!response.write(frame)) {\n await once(response, 'drain', { signal });\n }\n}\n\nfunction writeJsonError(\n response: ServerResponse,\n statusCode: number,\n message: string,\n headers: Record<string, string> = {},\n): void {\n writeJson(response, statusCode, { error: { message } }, {\n 'Cache-Control': 'no-store',\n ...headers,\n });\n}\n\nfunction writeJson(\n response: ServerResponse,\n statusCode: number,\n value: unknown,\n headers: Record<string, string> = {},\n): void {\n if (response.headersSent || response.destroyed) {\n return;\n }\n const body = JSON.stringify(value);\n response.writeHead(statusCode, {\n 'Cache-Control': 'no-store',\n 'Content-Length': Buffer.byteLength(body),\n 'Content-Type': 'application/json; charset=utf-8',\n 'X-Content-Type-Options': 'nosniff',\n ...headers,\n });\n response.end(body);\n}\n\nfunction errorType(error: unknown): string {\n return error instanceof Error ? error.name : 'unknown';\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ExecutionAuthorityKeyFileError, generateEphemeralExecutionAuthorityKeyPair, generateExecutionAuthorityKeyFile, loadExecutionAuthorityKeyPairFromFile, } from './authority-key.js';
|
|
2
|
+
export { DirectExecutionHostCredentials } from './direct-credentials.js';
|
|
3
|
+
export type { DirectExecutionHostCredentialEnvironmentNames, } from './direct-credentials.js';
|
|
4
|
+
export { DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH, DEFAULT_ADOPTER_JWKS_PATH, NodeExecutionAdopterHttpService, } from './http-service.js';
|
|
5
|
+
export type { NodeExecutionAdopterAuthenticationInput, NodeExecutionAdopterAuthenticator, NodeExecutionAdopterConversationInput, NodeExecutionAdopterConversationService, NodeExecutionAdopterFailure, NodeExecutionAdopterHttpHandler, NodeExecutionAdopterHttpPaths, NodeExecutionAdopterHttpServiceConfig, NodeExecutionAdopterPublicError, } from './types.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,0CAA0C,EAC1C,iCAAiC,EACjC,qCAAqC,GACtC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AACzE,YAAY,EACV,6CAA6C,GAC9C,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,EACvC,yBAAyB,EACzB,+BAA+B,GAChC,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,uCAAuC,EACvC,iCAAiC,EACjC,qCAAqC,EACrC,uCAAuC,EACvC,2BAA2B,EAC3B,+BAA+B,EAC/B,6BAA6B,EAC7B,qCAAqC,EACrC,+BAA+B,GAChC,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ExecutionAuthorityKeyFileError, generateEphemeralExecutionAuthorityKeyPair, generateExecutionAuthorityKeyFile, loadExecutionAuthorityKeyPairFromFile, } from './authority-key.js';
|
|
2
|
+
export { DirectExecutionHostCredentials } from './direct-credentials.js';
|
|
3
|
+
export { DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH, DEFAULT_ADOPTER_JWKS_PATH, NodeExecutionAdopterHttpService, } from './http-service.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,0CAA0C,EAC1C,iCAAiC,EACjC,qCAAqC,GACtC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AAIzE,OAAO,EACL,uCAAuC,EACvC,yBAAyB,EACzB,+BAA+B,GAChC,MAAM,mBAAmB,CAAC","sourcesContent":["export {\n ExecutionAuthorityKeyFileError,\n generateEphemeralExecutionAuthorityKeyPair,\n generateExecutionAuthorityKeyFile,\n loadExecutionAuthorityKeyPairFromFile,\n} from './authority-key.js';\nexport { DirectExecutionHostCredentials } from './direct-credentials.js';\nexport type {\n DirectExecutionHostCredentialEnvironmentNames,\n} from './direct-credentials.js';\nexport {\n DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH,\n DEFAULT_ADOPTER_JWKS_PATH,\n NodeExecutionAdopterHttpService,\n} from './http-service.js';\nexport type {\n NodeExecutionAdopterAuthenticationInput,\n NodeExecutionAdopterAuthenticator,\n NodeExecutionAdopterConversationInput,\n NodeExecutionAdopterConversationService,\n NodeExecutionAdopterFailure,\n NodeExecutionAdopterHttpHandler,\n NodeExecutionAdopterHttpPaths,\n NodeExecutionAdopterHttpServiceConfig,\n NodeExecutionAdopterPublicError,\n} from './types.js';\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { ExecutionAuthority } from '../authority/index.js';
|
|
3
|
+
import type { ExecutionHostStreamEvent } from '../contracts/index.js';
|
|
4
|
+
export type NodeExecutionAdopterAuthenticationInput = {
|
|
5
|
+
authorization?: string;
|
|
6
|
+
remoteAddress?: string;
|
|
7
|
+
signal: AbortSignal;
|
|
8
|
+
};
|
|
9
|
+
export type NodeExecutionAdopterConversationInput<Principal> = {
|
|
10
|
+
principal: Principal;
|
|
11
|
+
prompt: string;
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
};
|
|
14
|
+
export interface NodeExecutionAdopterAuthenticator<Principal> {
|
|
15
|
+
authenticate(input: NodeExecutionAdopterAuthenticationInput): Principal | undefined | Promise<Principal | undefined>;
|
|
16
|
+
}
|
|
17
|
+
export interface NodeExecutionAdopterConversationService<Principal> {
|
|
18
|
+
streamTurn(input: NodeExecutionAdopterConversationInput<Principal>): AsyncIterable<ExecutionHostStreamEvent>;
|
|
19
|
+
}
|
|
20
|
+
export type NodeExecutionAdopterPublicError = {
|
|
21
|
+
statusCode: number;
|
|
22
|
+
message: string;
|
|
23
|
+
};
|
|
24
|
+
export type NodeExecutionAdopterFailure = {
|
|
25
|
+
phase: 'jwks' | 'authentication' | 'conversation';
|
|
26
|
+
errorType: string;
|
|
27
|
+
streamAccepted: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type NodeExecutionAdopterHttpPaths = {
|
|
30
|
+
jwks: string;
|
|
31
|
+
conversationTurns: string;
|
|
32
|
+
};
|
|
33
|
+
export type NodeExecutionAdopterHttpServiceConfig<Principal> = {
|
|
34
|
+
authority: Pick<ExecutionAuthority, 'publicJwks'>;
|
|
35
|
+
authenticator: NodeExecutionAdopterAuthenticator<Principal>;
|
|
36
|
+
conversations: NodeExecutionAdopterConversationService<Principal>;
|
|
37
|
+
/** Maps adopter-domain failures to intentionally public HTTP errors. */
|
|
38
|
+
projectError?: (error: unknown) => NodeExecutionAdopterPublicError | undefined;
|
|
39
|
+
/** Receives credential-free operational metadata, never the raw error. */
|
|
40
|
+
reportFailure?: (failure: NodeExecutionAdopterFailure) => void;
|
|
41
|
+
paths?: Partial<NodeExecutionAdopterHttpPaths>;
|
|
42
|
+
maxBodyBytes?: number;
|
|
43
|
+
maxPromptCharacters?: number;
|
|
44
|
+
jwksMaxAgeSeconds?: number;
|
|
45
|
+
};
|
|
46
|
+
export interface NodeExecutionAdopterHttpHandler {
|
|
47
|
+
handle(request: IncomingMessage, response: ServerResponse): boolean;
|
|
48
|
+
handleJwks(request: IncomingMessage, response: ServerResponse): void;
|
|
49
|
+
handleConversationTurn(request: IncomingMessage, response: ServerResponse): void;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/node/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEtE,MAAM,MAAM,uCAAuC,GAAG;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qCAAqC,CAAC,SAAS,IAAI;IAC7D,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,iCAAiC,CAAC,SAAS;IAC1D,YAAY,CACV,KAAK,EAAE,uCAAuC,GAC7C,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,uCAAuC,CAAC,SAAS;IAChE,UAAU,CACR,KAAK,EAAE,qCAAqC,CAAC,SAAS,CAAC,GACtD,aAAa,CAAC,wBAAwB,CAAC,CAAC;CAC5C;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,cAAc,CAAC;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,qCAAqC,CAAC,SAAS,IAAI;IAC7D,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;IAClD,aAAa,EAAE,iCAAiC,CAAC,SAAS,CAAC,CAAC;IAC5D,aAAa,EAAE,uCAAuC,CAAC,SAAS,CAAC,CAAC;IAClE,wEAAwE;IACxE,YAAY,CAAC,EAAE,CACb,KAAK,EAAE,OAAO,KACX,+BAA+B,GAAG,SAAS,CAAC;IACjD,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,IAAI,CAAC;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,WAAW,+BAA+B;IAC9C,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC;IACpE,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACrE,sBAAsB,CACpB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,GACvB,IAAI,CAAC;IACR,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/node/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n IncomingMessage,\n ServerResponse,\n} from 'node:http';\nimport type { ExecutionAuthority } from '../authority/index.js';\nimport type { ExecutionHostStreamEvent } from '../contracts/index.js';\n\nexport type NodeExecutionAdopterAuthenticationInput = {\n authorization?: string;\n remoteAddress?: string;\n signal: AbortSignal;\n};\n\nexport type NodeExecutionAdopterConversationInput<Principal> = {\n principal: Principal;\n prompt: string;\n signal: AbortSignal;\n};\n\nexport interface NodeExecutionAdopterAuthenticator<Principal> {\n authenticate(\n input: NodeExecutionAdopterAuthenticationInput,\n ): Principal | undefined | Promise<Principal | undefined>;\n}\n\nexport interface NodeExecutionAdopterConversationService<Principal> {\n streamTurn(\n input: NodeExecutionAdopterConversationInput<Principal>,\n ): AsyncIterable<ExecutionHostStreamEvent>;\n}\n\nexport type NodeExecutionAdopterPublicError = {\n statusCode: number;\n message: string;\n};\n\nexport type NodeExecutionAdopterFailure = {\n phase: 'jwks' | 'authentication' | 'conversation';\n errorType: string;\n streamAccepted: boolean;\n};\n\nexport type NodeExecutionAdopterHttpPaths = {\n jwks: string;\n conversationTurns: string;\n};\n\nexport type NodeExecutionAdopterHttpServiceConfig<Principal> = {\n authority: Pick<ExecutionAuthority, 'publicJwks'>;\n authenticator: NodeExecutionAdopterAuthenticator<Principal>;\n conversations: NodeExecutionAdopterConversationService<Principal>;\n /** Maps adopter-domain failures to intentionally public HTTP errors. */\n projectError?: (\n error: unknown,\n ) => NodeExecutionAdopterPublicError | undefined;\n /** Receives credential-free operational metadata, never the raw error. */\n reportFailure?: (failure: NodeExecutionAdopterFailure) => void;\n paths?: Partial<NodeExecutionAdopterHttpPaths>;\n maxBodyBytes?: number;\n maxPromptCharacters?: number;\n jwksMaxAgeSeconds?: number;\n};\n\nexport interface NodeExecutionAdopterHttpHandler {\n handle(request: IncomingMessage, response: ServerResponse): boolean;\n handleJwks(request: IncomingMessage, response: ServerResponse): void;\n handleConversationTurn(\n request: IncomingMessage,\n response: ServerResponse,\n ): void;\n close(): Promise<void>;\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ServerResponse } from 'node:http';
|
|
2
|
+
import type { LocalExecutionHostTerminal } from './types.js';
|
|
3
|
+
export declare class LocalExecutionHostEventStream {
|
|
4
|
+
#private;
|
|
5
|
+
private constructor();
|
|
6
|
+
static open(input: {
|
|
7
|
+
response: ServerResponse;
|
|
8
|
+
signal: AbortSignal;
|
|
9
|
+
invocationId: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
now: () => Date;
|
|
12
|
+
}): Promise<LocalExecutionHostEventStream>;
|
|
13
|
+
publishActivity(activity: unknown): Promise<void>;
|
|
14
|
+
settle(terminal: LocalExecutionHostTerminal): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare function parseLocalExecutionHostTerminal(value: unknown): LocalExecutionHostTerminal;
|
|
17
|
+
//# sourceMappingURL=event-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-stream.d.ts","sourceRoot":"","sources":["../../src/testing/event-stream.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAQhD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AA2B7D,qBAAa,6BAA6B;;IAQxC,OAAO;WAcM,IAAI,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,cAAc,CAAC;QACzB,MAAM,EAAE,WAAW,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,IAAI,CAAC;KACjB,GAAG,OAAO,CAAC,6BAA6B,CAAC;IAW1C,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,MAAM,CAAC,QAAQ,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;CA8BlE;AAED,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,OAAO,GACb,0BAA0B,CAE5B"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { once } from 'node:events';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EXECUTION_CONTRACT_VERSION, ExecutionHostStreamEventSchema, OpaqueIdSchema, RuntimePublicResultSchema, } from '../contracts/index.js';
|
|
4
|
+
const ErrorCodeSchema = z.string().min(1).max(128).regex(/^[a-z0-9_]+$/);
|
|
5
|
+
const TerminalSchema = z.discriminatedUnion('kind', [
|
|
6
|
+
z.object({
|
|
7
|
+
kind: z.literal('result'),
|
|
8
|
+
result: RuntimePublicResultSchema,
|
|
9
|
+
}).strict(),
|
|
10
|
+
z.object({
|
|
11
|
+
kind: z.literal('cancelled'),
|
|
12
|
+
reason: z.string().min(1).max(1_600),
|
|
13
|
+
}).strict(),
|
|
14
|
+
z.object({
|
|
15
|
+
kind: z.literal('error'),
|
|
16
|
+
error: z.object({
|
|
17
|
+
code: ErrorCodeSchema,
|
|
18
|
+
message: z.string().min(1).max(1_600),
|
|
19
|
+
}).strict(),
|
|
20
|
+
}).strict(),
|
|
21
|
+
z.object({ kind: z.literal('interrupted') }).strict(),
|
|
22
|
+
]);
|
|
23
|
+
export class LocalExecutionHostEventStream {
|
|
24
|
+
#response;
|
|
25
|
+
#signal;
|
|
26
|
+
#invocationId;
|
|
27
|
+
#runId;
|
|
28
|
+
#now;
|
|
29
|
+
#sequence = 0;
|
|
30
|
+
constructor(input) {
|
|
31
|
+
this.#response = input.response;
|
|
32
|
+
this.#signal = input.signal;
|
|
33
|
+
this.#invocationId = input.invocationId;
|
|
34
|
+
this.#runId = OpaqueIdSchema.parse(input.runId);
|
|
35
|
+
this.#now = input.now;
|
|
36
|
+
}
|
|
37
|
+
static async open(input) {
|
|
38
|
+
const stream = new LocalExecutionHostEventStream(input);
|
|
39
|
+
input.response.writeHead(200, {
|
|
40
|
+
'Cache-Control': 'no-store',
|
|
41
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
42
|
+
'X-Content-Type-Options': 'nosniff',
|
|
43
|
+
});
|
|
44
|
+
await stream.#write({ kind: 'accepted' });
|
|
45
|
+
return stream;
|
|
46
|
+
}
|
|
47
|
+
publishActivity(activity) {
|
|
48
|
+
return this.#write({ kind: 'activity', activity });
|
|
49
|
+
}
|
|
50
|
+
async settle(terminal) {
|
|
51
|
+
this.#signal.throwIfAborted();
|
|
52
|
+
if (terminal.kind !== 'interrupted') {
|
|
53
|
+
await this.#write(terminal);
|
|
54
|
+
}
|
|
55
|
+
this.#response.end();
|
|
56
|
+
}
|
|
57
|
+
async #write(body) {
|
|
58
|
+
this.#signal.throwIfAborted();
|
|
59
|
+
const event = ExecutionHostStreamEventSchema.parse({
|
|
60
|
+
schemaVersion: EXECUTION_CONTRACT_VERSION,
|
|
61
|
+
invocationId: this.#invocationId,
|
|
62
|
+
runId: this.#runId,
|
|
63
|
+
sequence: this.#sequence,
|
|
64
|
+
timestamp: this.#now().toISOString(),
|
|
65
|
+
...body,
|
|
66
|
+
});
|
|
67
|
+
this.#sequence += 1;
|
|
68
|
+
const frame = [
|
|
69
|
+
`id: ${event.sequence}`,
|
|
70
|
+
`event: ${event.kind}`,
|
|
71
|
+
`data: ${JSON.stringify(event)}`,
|
|
72
|
+
'',
|
|
73
|
+
'',
|
|
74
|
+
].join('\n');
|
|
75
|
+
if (!this.#response.write(frame)) {
|
|
76
|
+
await once(this.#response, 'drain', { signal: this.#signal });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function parseLocalExecutionHostTerminal(value) {
|
|
81
|
+
return TerminalSchema.parse(value);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=event-stream.js.map
|