@jini-ai/http-kit 0.3.3 → 0.3.6

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.
Files changed (86) hide show
  1. package/dist/adapter.d.ts +26 -0
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/adapter.js +36 -0
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/agents.d.ts +2 -1
  6. package/dist/agents.d.ts.map +1 -1
  7. package/dist/agents.js.map +1 -1
  8. package/dist/attachments.d.ts +219 -2
  9. package/dist/attachments.d.ts.map +1 -1
  10. package/dist/attachments.js +384 -16
  11. package/dist/attachments.js.map +1 -1
  12. package/dist/delegated-tools.d.ts +2 -2
  13. package/dist/delegated-tools.js +2 -2
  14. package/dist/express/run-stream.d.ts +15 -0
  15. package/dist/express/run-stream.d.ts.map +1 -0
  16. package/dist/express/run-stream.js +11 -0
  17. package/dist/express/run-stream.js.map +1 -0
  18. package/dist/express-index.d.ts +32 -0
  19. package/dist/express-index.d.ts.map +1 -0
  20. package/dist/express-index.js +14 -0
  21. package/dist/express-index.js.map +1 -0
  22. package/dist/fastify/adapter.d.ts +31 -0
  23. package/dist/fastify/adapter.d.ts.map +1 -0
  24. package/dist/fastify/adapter.js +64 -0
  25. package/dist/fastify/adapter.js.map +1 -0
  26. package/dist/fastify/agents.d.ts +13 -0
  27. package/dist/fastify/agents.d.ts.map +1 -0
  28. package/dist/fastify/agents.js +7 -0
  29. package/dist/fastify/agents.js.map +1 -0
  30. package/dist/fastify/api-security-middleware.d.ts +64 -0
  31. package/dist/fastify/api-security-middleware.d.ts.map +1 -0
  32. package/dist/fastify/api-security-middleware.js +139 -0
  33. package/dist/fastify/api-security-middleware.js.map +1 -0
  34. package/dist/fastify/compat.d.ts +22 -0
  35. package/dist/fastify/compat.d.ts.map +1 -0
  36. package/dist/fastify/compat.js +16 -0
  37. package/dist/fastify/compat.js.map +1 -0
  38. package/dist/fastify/daemon-status.d.ts +22 -0
  39. package/dist/fastify/daemon-status.d.ts.map +1 -0
  40. package/dist/fastify/daemon-status.js +9 -0
  41. package/dist/fastify/daemon-status.js.map +1 -0
  42. package/dist/fastify/host-tools.d.ts +13 -0
  43. package/dist/fastify/host-tools.d.ts.map +1 -0
  44. package/dist/fastify/host-tools.js +8 -0
  45. package/dist/fastify/host-tools.js.map +1 -0
  46. package/dist/fastify/index.d.ts +36 -0
  47. package/dist/fastify/index.d.ts.map +1 -0
  48. package/dist/fastify/index.js +18 -0
  49. package/dist/fastify/index.js.map +1 -0
  50. package/dist/fastify/local-daemon-request.d.ts +43 -0
  51. package/dist/fastify/local-daemon-request.d.ts.map +1 -0
  52. package/dist/fastify/local-daemon-request.js +155 -0
  53. package/dist/fastify/local-daemon-request.js.map +1 -0
  54. package/dist/fastify/origin.d.ts +21 -0
  55. package/dist/fastify/origin.d.ts.map +1 -0
  56. package/dist/fastify/origin.js +14 -0
  57. package/dist/fastify/origin.js.map +1 -0
  58. package/dist/fastify/request.d.ts +20 -0
  59. package/dist/fastify/request.d.ts.map +1 -0
  60. package/dist/fastify/request.js +25 -0
  61. package/dist/fastify/request.js.map +1 -0
  62. package/dist/fastify/response.d.ts +20 -0
  63. package/dist/fastify/response.d.ts.map +1 -0
  64. package/dist/fastify/response.js +41 -0
  65. package/dist/fastify/response.js.map +1 -0
  66. package/dist/fastify/route-registration-guard.d.ts +70 -0
  67. package/dist/fastify/route-registration-guard.d.ts.map +1 -0
  68. package/dist/fastify/route-registration-guard.js +69 -0
  69. package/dist/fastify/route-registration-guard.js.map +1 -0
  70. package/dist/fastify/run-stream.d.ts +18 -0
  71. package/dist/fastify/run-stream.d.ts.map +1 -0
  72. package/dist/fastify/run-stream.js +10 -0
  73. package/dist/fastify/run-stream.js.map +1 -0
  74. package/dist/fastify/runs.d.ts +17 -0
  75. package/dist/fastify/runs.d.ts.map +1 -0
  76. package/dist/fastify/runs.js +33 -0
  77. package/dist/fastify/runs.js.map +1 -0
  78. package/dist/index.d.ts +2 -2
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +1 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/run-stream.d.ts +60 -0
  83. package/dist/run-stream.d.ts.map +1 -0
  84. package/dist/run-stream.js +108 -0
  85. package/dist/run-stream.js.map +1 -0
  86. package/package.json +6 -6
@@ -0,0 +1,43 @@
1
+ import type { FastifyReply, FastifyRequest } from 'fastify';
2
+ interface LocalAuthority {
3
+ hostname: string;
4
+ port: string;
5
+ }
6
+ interface LocalDaemonValidation {
7
+ ok: true;
8
+ origin: string | null;
9
+ }
10
+ interface LocalDaemonValidationError {
11
+ ok: false;
12
+ message: string;
13
+ details: Record<string, string>;
14
+ }
15
+ /** Parses a `Host`-header-shaped authority string, rejecting anything with a path/userinfo/query. */
16
+ export declare function normalizeLocalAuthority(value: unknown): LocalAuthority | null;
17
+ /** True when `hostname` is `localhost`, an IPv6 loopback literal, or a `127.0.0.0/8` IPv4 literal. */
18
+ export declare function isLoopbackHostname(hostname: unknown): boolean;
19
+ /** True when `address` (typically `req.socket.remoteAddress`) is a loopback peer, unwrapping an IPv4-mapped IPv6 prefix first. */
20
+ export declare function isLoopbackPeerAddress(address: unknown): boolean;
21
+ /** Parses an `Origin` header value into its origin string, requiring a bare `scheme://loopback-host[:port]` with no path/query/userinfo. */
22
+ export declare function localOriginFromHeader(value: unknown): string | null;
23
+ /**
24
+ * Validates that `req` is a loopback request: the TCP peer address, the `Host` header, and (when
25
+ * present) the `Origin` header must all resolve to a loopback host.
26
+ *
27
+ * @param req - The incoming request.
28
+ * @returns `{ ok: true, origin }` on success (`origin` is the validated `Origin` header value, or
29
+ * `null` when the request carried none); otherwise `{ ok: false, message, details }` naming which
30
+ * check failed.
31
+ */
32
+ export declare function validateLocalDaemonRequest(req: FastifyRequest): LocalDaemonValidation | LocalDaemonValidationError;
33
+ /**
34
+ * Fastify `onRequest` hook that rejects any non-loopback request with a 403, and otherwise sets
35
+ * permissive same-machine CORS headers before calling `done()`.
36
+ *
37
+ * @param req - The incoming request.
38
+ * @param reply - The reply to reject on, or set CORS headers on before continuing.
39
+ * @param done - Called (with no error) when the request validates as loopback, continuing the hook chain.
40
+ */
41
+ export declare function requireLocalDaemonRequest(req: FastifyRequest, reply: FastifyReply, done: (err?: Error) => void): void;
42
+ export {};
43
+ //# sourceMappingURL=local-daemon-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-daemon-request.d.ts","sourceRoot":"","sources":["../../src/fastify/local-daemon-request.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG5D,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,qBAAqB;IAC7B,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,UAAU,0BAA0B;IAClC,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,qGAAqG;AACrG,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAkB7E;AAED,sGAAsG;AACtG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAS7D;AAED,kIAAkI;AAClI,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAQ/D;AAED,4IAA4I;AAC5I,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAcnE;AAOD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,GAAG,qBAAqB,GAAG,0BAA0B,CA4BlH;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAerH"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @module fastify/local-daemon-request
3
+ *
4
+ * A loopback-only request guard: validates that a request's peer address, `Host` header, and (if
5
+ * present) `Origin` header are all loopback, and sets the CORS headers a same-machine caller
6
+ * needs. Distinct from `./origin.js`'s `guardSameOrigin` (which checks a browser tab's origin
7
+ * against a configured allow-list) — this guard instead restricts an endpoint to same-machine
8
+ * callers only (e.g. a local companion process), regardless of origin allow-list configuration.
9
+ *
10
+ * The header-parsing/classification predicates (`normalizeLocalAuthority`, `isLoopbackHostname`,
11
+ * `isLoopbackPeerAddress`, `localOriginFromHeader`) are pure and operate on plain string/unknown
12
+ * values, not a framework request object — deliberately duplicated from
13
+ * `../express/local-daemon-request.ts` rather than shared; see `./response.ts`'s top-of-module
14
+ * doc for why this subtree does not import from `express/`. Only `validateLocalDaemonRequest`
15
+ * and `requireLocalDaemonRequest` below are Fastify-specific (they read `FastifyRequest`/
16
+ * `FastifyReply` instead of Express's `Request`/`Response`/`req.get(...)`).
17
+ */
18
+ import net from 'node:net';
19
+ import { sendApiError } from './compat.js';
20
+ /** Parses a `Host`-header-shaped authority string, rejecting anything with a path/userinfo/query. */
21
+ export function normalizeLocalAuthority(value) {
22
+ if (typeof value !== 'string')
23
+ return null;
24
+ const trimmed = value.trim();
25
+ if (!trimmed || /[\s/@]/.test(trimmed) || trimmed.includes(','))
26
+ return null;
27
+ try {
28
+ // The guard above already rejects any `/`, `@`, or whitespace in `trimmed`, and the WHATWG
29
+ // URL parser requires a non-empty host for an `http:` URL — so `parsed.hostname` is always
30
+ // non-empty and `parsed.pathname` is always `/` for every `trimmed` value that reaches this
31
+ // line without throwing. `hostname` below can still end up empty after stripping a trailing
32
+ // dot (e.g. `trimmed === '.'` parses to `parsed.hostname === '.'`), so that check stays.
33
+ const parsed = new URL(`http://${trimmed}`);
34
+ const hostname = parsed.hostname.toLowerCase().replace(/\.$/, '');
35
+ if (!hostname)
36
+ return null;
37
+ return { hostname, port: parsed.port };
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /** True when `hostname` is `localhost`, an IPv6 loopback literal, or a `127.0.0.0/8` IPv4 literal. */
44
+ export function isLoopbackHostname(hostname) {
45
+ const normalized = String(hostname || '')
46
+ .toLowerCase()
47
+ .replace(/^\[|\]$/g, '')
48
+ .replace(/\.$/, '');
49
+ if (normalized === 'localhost')
50
+ return true;
51
+ if (normalized === '::1' || normalized === '0:0:0:0:0:0:0:1')
52
+ return true;
53
+ if (net.isIP(normalized) === 4)
54
+ return normalized === '127.0.0.1' || normalized.startsWith('127.');
55
+ return false;
56
+ }
57
+ /** True when `address` (typically `req.socket.remoteAddress`) is a loopback peer, unwrapping an IPv4-mapped IPv6 prefix first. */
58
+ export function isLoopbackPeerAddress(address) {
59
+ if (typeof address !== 'string')
60
+ return false;
61
+ const normalized = address.trim().toLowerCase().replace(/^\[|\]$/g, '');
62
+ if (!normalized)
63
+ return false;
64
+ if (normalized.startsWith('::ffff:'))
65
+ return isLoopbackPeerAddress(normalized.slice('::ffff:'.length));
66
+ if (normalized === '::1' || normalized === '0:0:0:0:0:0:0:1')
67
+ return true;
68
+ if (net.isIP(normalized) === 4)
69
+ return normalized === '127.0.0.1' || normalized.startsWith('127.');
70
+ return false;
71
+ }
72
+ /** Parses an `Origin` header value into its origin string, requiring a bare `scheme://loopback-host[:port]` with no path/query/userinfo. */
73
+ export function localOriginFromHeader(value) {
74
+ if (typeof value !== 'string')
75
+ return null;
76
+ const trimmed = value.trim();
77
+ if (!trimmed || trimmed === 'null' || trimmed.includes(','))
78
+ return null;
79
+ try {
80
+ const parsed = new URL(trimmed);
81
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
82
+ return null;
83
+ if (parsed.pathname !== '/' || parsed.search || parsed.hash || parsed.username || parsed.password)
84
+ return null;
85
+ if (!isLoopbackHostname(parsed.hostname))
86
+ return null;
87
+ return parsed.origin;
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ /** Reads a single-value request header, collapsing Node's `string | string[] | undefined` shape (Fastify headers are Node's raw `IncomingHttpHeaders`) to the first value. */
94
+ function headerValue(value) {
95
+ return Array.isArray(value) ? value[0] : value;
96
+ }
97
+ /**
98
+ * Validates that `req` is a loopback request: the TCP peer address, the `Host` header, and (when
99
+ * present) the `Origin` header must all resolve to a loopback host.
100
+ *
101
+ * @param req - The incoming request.
102
+ * @returns `{ ok: true, origin }` on success (`origin` is the validated `Origin` header value, or
103
+ * `null` when the request carried none); otherwise `{ ok: false, message, details }` naming which
104
+ * check failed.
105
+ */
106
+ export function validateLocalDaemonRequest(req) {
107
+ if (!isLoopbackPeerAddress(req.socket?.remoteAddress)) {
108
+ return {
109
+ ok: false,
110
+ message: 'request peer must be a loopback address',
111
+ details: { peer: 'remoteAddress' },
112
+ };
113
+ }
114
+ const host = normalizeLocalAuthority(headerValue(req.headers.host));
115
+ if (!host || !isLoopbackHostname(host.hostname)) {
116
+ return {
117
+ ok: false,
118
+ message: 'request host must be a loopback daemon address',
119
+ details: { header: 'host' },
120
+ };
121
+ }
122
+ const originHeader = headerValue(req.headers.origin);
123
+ if (originHeader !== undefined && !localOriginFromHeader(originHeader)) {
124
+ return {
125
+ ok: false,
126
+ message: 'request origin must be a loopback daemon origin',
127
+ details: { header: 'origin' },
128
+ };
129
+ }
130
+ return { ok: true, origin: localOriginFromHeader(originHeader) };
131
+ }
132
+ /**
133
+ * Fastify `onRequest` hook that rejects any non-loopback request with a 403, and otherwise sets
134
+ * permissive same-machine CORS headers before calling `done()`.
135
+ *
136
+ * @param req - The incoming request.
137
+ * @param reply - The reply to reject on, or set CORS headers on before continuing.
138
+ * @param done - Called (with no error) when the request validates as loopback, continuing the hook chain.
139
+ */
140
+ export function requireLocalDaemonRequest(req, reply, done) {
141
+ const validation = validateLocalDaemonRequest(req);
142
+ if (!validation.ok) {
143
+ sendApiError(reply, 403, 'FORBIDDEN', validation.message, { details: validation.details });
144
+ return;
145
+ }
146
+ reply.header('Vary', 'Origin');
147
+ if (validation.origin) {
148
+ reply.header('Access-Control-Allow-Origin', validation.origin);
149
+ }
150
+ reply.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
151
+ reply.header('Access-Control-Allow-Headers', 'Content-Type');
152
+ reply.header('Access-Control-Max-Age', '600');
153
+ done();
154
+ }
155
+ //# sourceMappingURL=local-daemon-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-daemon-request.js","sourceRoot":"","sources":["../../src/fastify/local-daemon-request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Same-origin security guard: wraps the shared, framework-agnostic `isLocalSameOrigin` in the
3
+ * module's `Result` pipeline so the Fastify Adapter can treat an origin failure the same as a
4
+ * parse/handle failure. `isLocalSameOrigin` is imported from the shared root (`../origin-validation.js`)
5
+ * rather than duplicated, since it operates on plain header values (not a framework `Request`) and
6
+ * is already framework-agnostic — see `../pack-http.ts`'s doc for the same reasoning applied there.
7
+ */
8
+ import type { FastifyRequest } from 'fastify';
9
+ import { type Result } from '../types.js';
10
+ /** The subset of server startup state `guardSameOrigin` needs: the resolved local port. */
11
+ export interface OriginContext {
12
+ resolvedPortRef: {
13
+ current: number;
14
+ };
15
+ }
16
+ /**
17
+ * Wraps `isLocalSameOrigin` so the Fastify Adapter can fold the origin decision into the same
18
+ * error-handling pipeline as parse/handle failures.
19
+ */
20
+ export declare function guardSameOrigin(req: FastifyRequest, origin: OriginContext): Result<void>;
21
+ //# sourceMappingURL=origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/fastify/origin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAEnD,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,CAKxF"}
@@ -0,0 +1,14 @@
1
+ import { createApiError } from '@jini/protocol';
2
+ import { isLocalSameOrigin } from '../origin-validation.js';
3
+ import { err, ok } from '../types.js';
4
+ /**
5
+ * Wraps `isLocalSameOrigin` so the Fastify Adapter can fold the origin decision into the same
6
+ * error-handling pipeline as parse/handle failures.
7
+ */
8
+ export function guardSameOrigin(req, origin) {
9
+ if (isLocalSameOrigin(req, origin.resolvedPortRef.current)) {
10
+ return ok(undefined);
11
+ }
12
+ return err(createApiError('FORBIDDEN', 'cross-origin request rejected'));
13
+ }
14
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/fastify/origin.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,aAAa,CAAC;AAOnD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,GAAmB,EAAE,MAAqB;IACxE,IAAI,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,GAAG,CAAC,cAAc,CAAC,WAAW,EAAE,+BAA+B,CAAC,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Request-side normalization: turns a raw Fastify `FastifyRequest` into the framework-independent
3
+ * `RouteInputContext` the rest of the module operates on, and builds a standard `BAD_REQUEST`
4
+ * `ApiError` for validation failures. Deliberately duplicated from `../express/request.ts` — see
5
+ * `./response.ts`'s top-of-module doc for why this subtree does not import from `express/`.
6
+ */
7
+ import type { FastifyRequest } from 'fastify';
8
+ import { type ApiError, type ApiValidationIssue } from '@jini/protocol';
9
+ import type { RouteInputContext } from '../types.js';
10
+ /**
11
+ * Extracts `body`/`query`/`params` from a Fastify `FastifyRequest` into a `RouteInputContext`, so
12
+ * a route's `parse` function can be unit-tested without constructing a real `FastifyRequest`.
13
+ */
14
+ export declare function rawInput(request: FastifyRequest): RouteInputContext;
15
+ /**
16
+ * Builds a `BAD_REQUEST` `ApiError` for a failed parse. When `issues` is non-empty, attaches
17
+ * them as structured validation details so clients can render per-field errors.
18
+ */
19
+ export declare function validationError(message: string, issues?: Pick<ApiValidationIssue, 'path' | 'message'>[]): ApiError;
20
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/fastify/request.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,iBAAiB,CAMnE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC,EAAO,GAC1D,QAAQ,CAOV"}
@@ -0,0 +1,25 @@
1
+ import { createApiError } from '@jini/protocol';
2
+ /**
3
+ * Extracts `body`/`query`/`params` from a Fastify `FastifyRequest` into a `RouteInputContext`, so
4
+ * a route's `parse` function can be unit-tested without constructing a real `FastifyRequest`.
5
+ */
6
+ export function rawInput(request) {
7
+ return {
8
+ body: request.body,
9
+ query: (request.query ?? {}),
10
+ params: (request.params ?? {}),
11
+ };
12
+ }
13
+ /**
14
+ * Builds a `BAD_REQUEST` `ApiError` for a failed parse. When `issues` is non-empty, attaches
15
+ * them as structured validation details so clients can render per-field errors.
16
+ */
17
+ export function validationError(message, issues = []) {
18
+ if (issues.length === 0) {
19
+ return createApiError('BAD_REQUEST', message);
20
+ }
21
+ return createApiError('BAD_REQUEST', message, {
22
+ details: { kind: 'validation', issues },
23
+ });
24
+ }
25
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/fastify/request.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,cAAc,EAA0C,MAAM,gBAAgB,CAAC;AAGxF;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAuB;IAC9C,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAA4B;QACvD,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAA2B;KACzD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,SAAyD,EAAE;IAE3D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,cAAc,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,cAAc,CAAC,aAAa,EAAE,OAAO,EAAE;QAC5C,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAiD;KACvF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Response-side serialization: writes a plain JSON body or a standard `ApiError` envelope onto a
3
+ * Fastify `FastifyReply`, and maps an `ApiErrorCode` to its default HTTP status. No internal
4
+ * dependencies — this is the terminal step of the Fastify Adapter's error-handling pipeline.
5
+ *
6
+ * Deliberately duplicated from `../express/response.ts` rather than shared: both files implement
7
+ * the identical `ApiErrorCode` -> HTTP status mapping and error-envelope shape, but this
8
+ * subtree's whole point is to stay independent of `express/` (see `../../source-map.md`'s
9
+ * Fastify-transport section) — a Fastify-only consumer must be able to import this module without
10
+ * ever pulling in anything that even *type*-references the `express` package.
11
+ */
12
+ import type { FastifyReply } from 'fastify';
13
+ import { type ApiError } from '@jini/protocol';
14
+ /** Writes `body` as JSON with the given status code. */
15
+ export declare function sendJson(reply: FastifyReply, status: number, body: unknown): void;
16
+ /** Writes an `ApiError`, wrapped in the standard `{ error }` envelope, with the given status code. */
17
+ export declare function sendApiError(reply: FastifyReply, status: number, error: ApiError): void;
18
+ /** Resolves the HTTP status to send for an `ApiError`, defaulting to 500 for unmapped codes. */
19
+ export declare function statusForError(error: ApiError): number;
20
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/fastify/response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAA0B,KAAK,QAAQ,EAAqB,MAAM,gBAAgB,CAAC;AAE1F,wDAAwD;AACxD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAEjF;AAED,sGAAsG;AACtG,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEvF;AA8BD,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAEtD"}
@@ -0,0 +1,41 @@
1
+ import { createApiErrorResponse } from '@jini/protocol';
2
+ /** Writes `body` as JSON with the given status code. */
3
+ export function sendJson(reply, status, body) {
4
+ reply.code(status).send(body);
5
+ }
6
+ /** Writes an `ApiError`, wrapped in the standard `{ error }` envelope, with the given status code. */
7
+ export function sendApiError(reply, status, error) {
8
+ reply.code(status).send(createApiErrorResponse(error));
9
+ }
10
+ /**
11
+ * @internal
12
+ * Default HTTP status per generic `ApiErrorCode`. Codes not listed here fall back to 500 in
13
+ * `statusForError` — deliberately conservative, since an unmapped code (including any
14
+ * product-specific code a consuming pack defines) is more likely a new server-side failure mode
15
+ * than a client error. Kept identical to `../express/response.ts`'s copy — see this file's
16
+ * top-of-module doc for why it is duplicated rather than imported.
17
+ */
18
+ const ERROR_STATUS_BY_CODE = {
19
+ BAD_REQUEST: 400,
20
+ UNAUTHORIZED: 401,
21
+ FORBIDDEN: 403,
22
+ NOT_FOUND: 404,
23
+ CONFLICT: 409,
24
+ PAYLOAD_TOO_LARGE: 413,
25
+ UNSUPPORTED_MEDIA_TYPE: 415,
26
+ VALIDATION_FAILED: 422,
27
+ RATE_LIMITED: 429,
28
+ INTERNAL_ERROR: 500,
29
+ UPSTREAM_UNAVAILABLE: 502,
30
+ TOOL_TOKEN_MISSING: 401,
31
+ TOOL_TOKEN_INVALID: 401,
32
+ TOOL_TOKEN_EXPIRED: 401,
33
+ TOOL_ENDPOINT_DENIED: 403,
34
+ TOOL_OPERATION_DENIED: 403,
35
+ TOOL_NOT_AVAILABLE: 503,
36
+ };
37
+ /** Resolves the HTTP status to send for an `ApiError`, defaulting to 500 for unmapped codes. */
38
+ export function statusForError(error) {
39
+ return ERROR_STATUS_BY_CODE[error.code] ?? 500;
40
+ }
41
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/fastify/response.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,sBAAsB,EAAoC,MAAM,gBAAgB,CAAC;AAE1F,wDAAwD;AACxD,MAAM,UAAU,QAAQ,CAAC,KAAmB,EAAE,MAAc,EAAE,IAAa;IACzE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,YAAY,CAAC,KAAmB,EAAE,MAAc,EAAE,KAAe;IAC/E,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,oBAAoB,GAA0C;IAClE,WAAW,EAAE,GAAG;IAChB,YAAY,EAAE,GAAG;IACjB,SAAS,EAAE,GAAG;IACd,SAAS,EAAE,GAAG;IACd,QAAQ,EAAE,GAAG;IACb,iBAAiB,EAAE,GAAG;IACtB,sBAAsB,EAAE,GAAG;IAC3B,iBAAiB,EAAE,GAAG;IACtB,YAAY,EAAE,GAAG;IACjB,cAAc,EAAE,GAAG;IACnB,oBAAoB,EAAE,GAAG;IACzB,kBAAkB,EAAE,GAAG;IACvB,kBAAkB,EAAE,GAAG;IACvB,kBAAkB,EAAE,GAAG;IACvB,oBAAoB,EAAE,GAAG;IACzB,qBAAqB,EAAE,GAAG;IAC1B,kBAAkB,EAAE,GAAG;CACxB,CAAC;AAEF,gGAAgG;AAChG,MAAM,UAAU,cAAc,CAAC,KAAe;IAC5C,OAAO,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;AACjD,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @module fastify/route-registration-guard
3
+ *
4
+ * Builds a queryable inventory of every route registered on a Fastify instance, and guards a
5
+ * caller-supplied set of "must register at most once" route keys against accidental duplicate
6
+ * registration — e.g. two packs each mounting `POST /api/runs` would otherwise silently shadow
7
+ * one another instead of failing loudly at composition time.
8
+ *
9
+ * Unlike the Express version (`../express/route-registration-guard.ts`, which monkey-patches
10
+ * `get`/`post`/etc. because Express has no first-class "a route was just registered" signal),
11
+ * Fastify exposes a built-in `onRoute` application hook that fires exactly once per registered
12
+ * route with the fully resolved `{ method, url }` — no monkey-patching needed. `guardedRouteKey`
13
+ * is deliberately duplicated (not imported) from the Express version; see
14
+ * `./response.ts`'s top-of-module doc for why this subtree does not import from `express/`.
15
+ */
16
+ import type { FastifyInstance } from 'fastify';
17
+ /** One recorded route registration: the HTTP verb and the literal string path it was mounted on. */
18
+ export interface RouteRegistration {
19
+ method: string;
20
+ path: string;
21
+ }
22
+ /**
23
+ * Builds the `METHOD PATH` key used to check `guardedRouteKeys` membership.
24
+ *
25
+ * @param method - The HTTP method name (any case; normalized to upper-case in the key).
26
+ * @param path - The route's registered URL. Fastify's `onRoute` hook always supplies a string
27
+ * `url`, so (unlike the Express version) there is no non-string-path branch to guard against here.
28
+ * @param guardedRouteKeys - The set of `METHOD PATH` keys this call installed a duplicate-registration
29
+ * guard for.
30
+ * @returns The matching key if it is in `guardedRouteKeys`, otherwise `null`.
31
+ * @complexity O(1) — a single `Set` lookup.
32
+ * @overallScore 100/100
33
+ */
34
+ export declare function guardedRouteKey(method: string, path: string, guardedRouteKeys: ReadonlySet<string>): string | null;
35
+ export interface InstallRouteRegistrationGuardOptions {
36
+ /** `METHOD PATH` keys (e.g. `'POST /api/runs'`) that must be registered at most once on `app`. Defaults to none — installing the guard with no options only builds the inventory, it enforces nothing. */
37
+ guardedRouteKeys?: ReadonlySet<string>;
38
+ }
39
+ /**
40
+ * Registers a Fastify `onRoute` application hook that records every route registration into a
41
+ * per-app inventory (retrievable via {@link getRouteRegistrationInventory}) and throws if any
42
+ * `options.guardedRouteKeys` entry is registered a second time.
43
+ *
44
+ * A route registered for multiple HTTP methods at once (`method: ['GET', 'POST']`, or Fastify's
45
+ * own automatic `HEAD` sibling for a `GET` route when `exposeHeadRoute` is left at its default)
46
+ * produces one inventory entry and one guard check per method — mirroring how the Express version
47
+ * records one entry per distinct `app.get(...)`/`app.post(...)` call.
48
+ *
49
+ * @param app - The Fastify instance to instrument. Must not already have this guard installed
50
+ * (installing twice would double-register the `onRoute` hook and double-count the inventory — not
51
+ * guarded against here since every caller in this codebase calls this exactly once per app at
52
+ * composition time).
53
+ * @param options.guardedRouteKeys - See {@link InstallRouteRegistrationGuardOptions}.
54
+ * @returns Nothing — registers a hook on `app` and stashes its inventory under a well-known `Symbol.for` key.
55
+ * @throws If a `guardedRouteKeys` entry is registered more than once, at the moment of the second registration.
56
+ * @complexity O(1) setup; each `onRoute` firing is O(m) in the number of methods the route was registered for (typically 1).
57
+ * @overallScore 100/100
58
+ */
59
+ export declare function installRouteRegistrationGuard(app: FastifyInstance, options?: InstallRouteRegistrationGuardOptions): void;
60
+ /**
61
+ * Reads back every route {@link installRouteRegistrationGuard} recorded for `app`.
62
+ *
63
+ * @param app - An app the guard was installed on. Safe to call on an app with no guard installed
64
+ * (returns an empty array rather than throwing).
65
+ * @returns A fresh array copy each call — the caller can never mutate the guard's own internal inventory through the returned reference.
66
+ * @complexity O(n) in the number of routes registered so far.
67
+ * @overallScore 100/100
68
+ */
69
+ export declare function getRouteRegistrationInventory(app: FastifyInstance): RouteRegistration[];
70
+ //# sourceMappingURL=route-registration-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-registration-guard.d.ts","sourceRoot":"","sources":["../../src/fastify/route-registration-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAGlH;AAED,MAAM,WAAW,oCAAoC;IACnD,0MAA0M;IAC1M,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACxC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,oCAAyC,GACjD,IAAI,CAmBN;AAED;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,eAAe,GAAG,iBAAiB,EAAE,CAEvF"}
@@ -0,0 +1,69 @@
1
+ const routeInventorySymbol = Symbol.for('jini.routeRegistrationGuard.inventory');
2
+ /**
3
+ * Builds the `METHOD PATH` key used to check `guardedRouteKeys` membership.
4
+ *
5
+ * @param method - The HTTP method name (any case; normalized to upper-case in the key).
6
+ * @param path - The route's registered URL. Fastify's `onRoute` hook always supplies a string
7
+ * `url`, so (unlike the Express version) there is no non-string-path branch to guard against here.
8
+ * @param guardedRouteKeys - The set of `METHOD PATH` keys this call installed a duplicate-registration
9
+ * guard for.
10
+ * @returns The matching key if it is in `guardedRouteKeys`, otherwise `null`.
11
+ * @complexity O(1) — a single `Set` lookup.
12
+ * @overallScore 100/100
13
+ */
14
+ export function guardedRouteKey(method, path, guardedRouteKeys) {
15
+ const key = `${method.toUpperCase()} ${path}`;
16
+ return guardedRouteKeys.has(key) ? key : null;
17
+ }
18
+ /**
19
+ * Registers a Fastify `onRoute` application hook that records every route registration into a
20
+ * per-app inventory (retrievable via {@link getRouteRegistrationInventory}) and throws if any
21
+ * `options.guardedRouteKeys` entry is registered a second time.
22
+ *
23
+ * A route registered for multiple HTTP methods at once (`method: ['GET', 'POST']`, or Fastify's
24
+ * own automatic `HEAD` sibling for a `GET` route when `exposeHeadRoute` is left at its default)
25
+ * produces one inventory entry and one guard check per method — mirroring how the Express version
26
+ * records one entry per distinct `app.get(...)`/`app.post(...)` call.
27
+ *
28
+ * @param app - The Fastify instance to instrument. Must not already have this guard installed
29
+ * (installing twice would double-register the `onRoute` hook and double-count the inventory — not
30
+ * guarded against here since every caller in this codebase calls this exactly once per app at
31
+ * composition time).
32
+ * @param options.guardedRouteKeys - See {@link InstallRouteRegistrationGuardOptions}.
33
+ * @returns Nothing — registers a hook on `app` and stashes its inventory under a well-known `Symbol.for` key.
34
+ * @throws If a `guardedRouteKeys` entry is registered more than once, at the moment of the second registration.
35
+ * @complexity O(1) setup; each `onRoute` firing is O(m) in the number of methods the route was registered for (typically 1).
36
+ * @overallScore 100/100
37
+ */
38
+ export function installRouteRegistrationGuard(app, options = {}) {
39
+ const guardedRouteKeys = options.guardedRouteKeys ?? new Set();
40
+ const seen = new Set();
41
+ const inventory = [];
42
+ app[routeInventorySymbol] = inventory;
43
+ app.addHook('onRoute', (routeOptions) => {
44
+ const methods = Array.isArray(routeOptions.method) ? routeOptions.method : [routeOptions.method];
45
+ for (const method of methods) {
46
+ inventory.push({ method: method.toUpperCase(), path: routeOptions.url });
47
+ const key = guardedRouteKey(method, routeOptions.url, guardedRouteKeys);
48
+ if (key) {
49
+ if (seen.has(key)) {
50
+ throw new Error(`duplicate guarded route registration: ${key}`);
51
+ }
52
+ seen.add(key);
53
+ }
54
+ }
55
+ });
56
+ }
57
+ /**
58
+ * Reads back every route {@link installRouteRegistrationGuard} recorded for `app`.
59
+ *
60
+ * @param app - An app the guard was installed on. Safe to call on an app with no guard installed
61
+ * (returns an empty array rather than throwing).
62
+ * @returns A fresh array copy each call — the caller can never mutate the guard's own internal inventory through the returned reference.
63
+ * @complexity O(n) in the number of routes registered so far.
64
+ * @overallScore 100/100
65
+ */
66
+ export function getRouteRegistrationInventory(app) {
67
+ return [...(app[routeInventorySymbol] ?? [])];
68
+ }
69
+ //# sourceMappingURL=route-registration-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-registration-guard.js","sourceRoot":"","sources":["../../src/fastify/route-registration-guard.ts"],"names":[],"mappings":"AAuBA,MAAM,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;AAEjF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,IAAY,EAAE,gBAAqC;IACjG,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC;IAC9C,OAAO,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAOD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,6BAA6B,CAC3C,GAAoB,EACpB,UAAgD,EAAE;IAElD,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,IAAI,GAAG,EAAU,CAAC;IACvE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,SAAS,GAAwB,EAAE,CAAC;IACzC,GAAsD,CAAC,oBAAoB,CAAC,GAAG,SAAS,CAAC;IAE1F,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,YAAY,EAAE,EAAE;QACtC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACjG,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC;YACzE,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;YACxE,IAAI,GAAG,EAAE,CAAC;gBACR,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClB,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,EAAE,CAAC,CAAC;gBAClE,CAAC;gBACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAAC,GAAoB;IAChE,OAAO,CAAC,GAAG,CAAE,GAAkE,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAChH,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @module fastify/run-stream
3
+ *
4
+ * Fastify-specific mounting glue for the shared AG-UI SSE run-stream handler
5
+ * (`../run-stream.js`). `reply.raw`/`request.raw` give the exact underlying `http.ServerResponse`/
6
+ * `http.IncomingMessage` pair directly, so this file's only job is resolving the `runId` route
7
+ * param and handing those raw objects straight through — a few lines, not a reimplementation, per
8
+ * the design decision that motivated building the SSE primitive once instead of duplicating it
9
+ * per transport. `reply.hijack()` tells Fastify this handler is taking over the raw response
10
+ * itself, so Fastify's own reply lifecycle (which otherwise expects the handler to return a value
11
+ * for it to serialize, or call `reply.send(...)`) does not try to act on a response this handler
12
+ * already wrote to and ended directly.
13
+ */
14
+ import type { FastifyInstance } from 'fastify';
15
+ import { type RunStreamDeps } from '../run-stream.js';
16
+ /** Mounts the AG-UI SSE run-stream route on `app`. A pack's `http(app, services)` calls this directly. */
17
+ export declare function registerRunStreamRoute(app: FastifyInstance, deps: RunStreamDeps): void;
18
+ //# sourceMappingURL=run-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-stream.d.ts","sourceRoot":"","sources":["../../src/fastify/run-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAiD,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAErG,0GAA0G;AAC1G,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAMtF"}
@@ -0,0 +1,10 @@
1
+ import { handleRunStreamRequest, RUN_STREAM_ROUTE_PATH } from '../run-stream.js';
2
+ /** Mounts the AG-UI SSE run-stream route on `app`. A pack's `http(app, services)` calls this directly. */
3
+ export function registerRunStreamRoute(app, deps) {
4
+ app.get(RUN_STREAM_ROUTE_PATH, async (request, reply) => {
5
+ reply.hijack();
6
+ const runId = request.params.runId;
7
+ await handleRunStreamRequest(request.raw, reply.raw, runId, deps);
8
+ });
9
+ }
10
+ //# sourceMappingURL=run-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-stream.js","sourceRoot":"","sources":["../../src/fastify/run-stream.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAsB,MAAM,kBAAkB,CAAC;AAErG,0GAA0G;AAC1G,MAAM,UAAU,sBAAsB,CAAC,GAAoB,EAAE,IAAmB;IAC9E,GAAG,CAAC,GAAG,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAuB,EAAE,KAAmB,EAAE,EAAE;QACpF,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,MAAM,KAAK,GAAI,OAAO,CAAC,MAA4B,CAAC,KAAK,CAAC;QAC1D,MAAM,sBAAsB,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @module fastify/runs
3
+ *
4
+ * Fastify-mounting glue for `../runs.js`'s route logic — every `JsonRouteSpec` (`runStartRoute`,
5
+ * `runListRoute`, `runStatusRoute`, `runCancelRoute`) and the SSE event-stream core
6
+ * (`handleRunEventStreamRequest`) are the exact same functions the Express mounting in `../runs.js`
7
+ * uses; nothing here re-implements route logic, only how a request reaches it and a response
8
+ * leaves it, mirroring the rest of this `fastify/` subtree.
9
+ */
10
+ import type { FastifyInstance } from 'fastify';
11
+ import type { RunHttpDeps } from '../runs.js';
12
+ import { type AdapterContext } from './adapter.js';
13
+ /** Mounts `GET /api/runs/:runId/events` on `app` via Fastify's native `route()` (matching `mountJsonRoute`'s own registration mechanism, unlike `fastify/run-stream.ts`'s `.get()` shorthand — kept consistent within this file since `registerRunRoutes` below mounts both through the same `app`), hijacking the reply so `handleRunEventStreamRequest` can write the raw SSE stream directly. */
14
+ export declare function registerRunEventStream(app: FastifyInstance, deps: RunHttpDeps): void;
15
+ /** Mounts create/status/cancel JSON endpoints and the SSE event stream as one run transport — the Fastify-native equivalent of `../runs.js`'s `registerRunRoutes`, mounting the identical `JsonRouteSpec` objects. */
16
+ export declare function registerRunRoutes(app: FastifyInstance, deps: RunHttpDeps, adapter: AdapterContext): void;
17
+ //# sourceMappingURL=runs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/fastify/runs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAWnE,oYAAoY;AACpY,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAWpF;AAED,sNAAsN;AACtN,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAMxG"}