@agentuity/runtime 1.0.47 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_globals.d.ts +58 -0
- package/dist/_globals.d.ts.map +1 -0
- package/dist/_globals.js +71 -0
- package/dist/_globals.js.map +1 -0
- package/dist/_metadata.d.ts.map +1 -1
- package/dist/_metadata.js +14 -0
- package/dist/_metadata.js.map +1 -1
- package/dist/_process-protection.d.ts +2 -0
- package/dist/_process-protection.d.ts.map +1 -1
- package/dist/_process-protection.js +14 -23
- package/dist/_process-protection.js.map +1 -1
- package/dist/_server.d.ts +4 -0
- package/dist/_server.d.ts.map +1 -1
- package/dist/_server.js +4 -0
- package/dist/_server.js.map +1 -1
- package/dist/_services.d.ts +1 -1
- package/dist/_services.d.ts.map +1 -1
- package/dist/_services.js +5 -1
- package/dist/_services.js.map +1 -1
- package/dist/_standalone.d.ts.map +1 -1
- package/dist/_standalone.js +3 -9
- package/dist/_standalone.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +1 -0
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts +149 -71
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +121 -156
- package/dist/app.js.map +1 -1
- package/dist/bootstrap.d.ts +44 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +256 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/dev-patches/aisdk.d.ts.map +1 -1
- package/dist/dev-patches/aisdk.js +6 -8
- package/dist/dev-patches/aisdk.js.map +1 -1
- package/dist/dev-patches/gateway.d.ts.map +1 -1
- package/dist/dev-patches/gateway.js +7 -8
- package/dist/dev-patches/gateway.js.map +1 -1
- package/dist/handlers/_route-meta.d.ts +20 -0
- package/dist/handlers/_route-meta.d.ts.map +1 -0
- package/dist/handlers/_route-meta.js +25 -0
- package/dist/handlers/_route-meta.js.map +1 -0
- package/dist/handlers/cron.d.ts.map +1 -1
- package/dist/handlers/cron.js +3 -1
- package/dist/handlers/cron.js.map +1 -1
- package/dist/handlers/sse.d.ts +3 -3
- package/dist/handlers/sse.d.ts.map +1 -1
- package/dist/handlers/sse.js +4 -16
- package/dist/handlers/sse.js.map +1 -1
- package/dist/handlers/stream.d.ts.map +1 -1
- package/dist/handlers/stream.js +4 -12
- package/dist/handlers/stream.js.map +1 -1
- package/dist/handlers/websocket.d.ts +3 -1
- package/dist/handlers/websocket.d.ts.map +1 -1
- package/dist/handlers/websocket.js +6 -37
- package/dist/handlers/websocket.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +1 -8
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +29 -71
- package/dist/middleware.js.map +1 -1
- package/dist/otel/logger.d.ts.map +1 -1
- package/dist/otel/logger.js +4 -7
- package/dist/otel/logger.js.map +1 -1
- package/dist/otel/otel.d.ts +4 -1
- package/dist/otel/otel.d.ts.map +1 -1
- package/dist/otel/otel.js +13 -2
- package/dist/otel/otel.js.map +1 -1
- package/dist/router.d.ts +10 -62
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +9 -146
- package/dist/router.js.map +1 -1
- package/dist/workbench.d.ts +1 -1
- package/dist/workbench.d.ts.map +1 -1
- package/dist/workbench.js +120 -12
- package/dist/workbench.js.map +1 -1
- package/package.json +7 -7
- package/src/_globals.ts +92 -0
- package/src/_metadata.ts +14 -0
- package/src/_process-protection.ts +17 -28
- package/src/_server.ts +4 -0
- package/src/_services.ts +6 -2
- package/src/_standalone.ts +4 -9
- package/src/agent.ts +1 -0
- package/src/app.ts +294 -195
- package/src/bootstrap.ts +316 -0
- package/src/dev-patches/aisdk.ts +8 -11
- package/src/dev-patches/gateway.ts +9 -11
- package/src/globals.d.ts +28 -0
- package/src/handlers/_route-meta.ts +31 -0
- package/src/handlers/cron.ts +4 -1
- package/src/handlers/sse.ts +8 -19
- package/src/handlers/stream.ts +5 -12
- package/src/handlers/websocket.ts +11 -37
- package/src/index.ts +2 -3
- package/src/middleware.ts +40 -99
- package/src/otel/logger.ts +5 -8
- package/src/otel/otel.ts +14 -2
- package/src/router.ts +12 -216
- package/src/workbench.ts +135 -12
package/src/bootstrap.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server lifecycle helpers.
|
|
3
|
+
*
|
|
4
|
+
* These functions are called by createApp() to set up routes, middleware,
|
|
5
|
+
* and the Bun HTTP server. They're kept separate to keep createApp() focused
|
|
6
|
+
* on orchestration while these handle the details.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Context } from 'hono';
|
|
10
|
+
import { websocket, serveStatic } from 'hono/bun';
|
|
11
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { mimeTypes } from '@agentuity/server';
|
|
14
|
+
|
|
15
|
+
import { runShutdown } from './app';
|
|
16
|
+
import type { AnalyticsOptions, WorkbenchOptions } from './app';
|
|
17
|
+
import { createRouter } from './router';
|
|
18
|
+
import { createWebSessionMiddleware } from './middleware';
|
|
19
|
+
import { enableProcessExitProtection } from './_process-protection';
|
|
20
|
+
import { hasWaitUntilPending } from './_waituntil';
|
|
21
|
+
import { getOrganizationId, getProjectId, isDevMode as runtimeIsDevMode } from './_config';
|
|
22
|
+
import { BEACON_SCRIPT } from '@agentuity/frontend';
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Mode detection
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Runtime mode detection.
|
|
30
|
+
* Dynamic string concatenation prevents Bun.build from inlining NODE_ENV.
|
|
31
|
+
* @see https://github.com/oven-sh/bun/issues/20183
|
|
32
|
+
*/
|
|
33
|
+
const getEnv = (key: string) => process.env[key];
|
|
34
|
+
/**
|
|
35
|
+
* Check if running in development mode.
|
|
36
|
+
*
|
|
37
|
+
* The CLI dev server explicitly sets NODE_ENV='development'. In production
|
|
38
|
+
* (cloud deployment, CI integration test running a built app.js), NODE_ENV
|
|
39
|
+
* may be 'production' or unset entirely. When unset, we assume production
|
|
40
|
+
* — the dev server always sets it, so absence means production.
|
|
41
|
+
*/
|
|
42
|
+
export const isDevelopment = () => getEnv('NODE' + '_' + 'ENV') === 'development';
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Analytics helpers
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/** Resolve analytics config with defaults */
|
|
49
|
+
export function resolveAnalyticsConfig(
|
|
50
|
+
analytics: boolean | AnalyticsOptions | undefined
|
|
51
|
+
): AnalyticsOptions & { enabled: boolean } {
|
|
52
|
+
if (analytics === false) {
|
|
53
|
+
return { enabled: false };
|
|
54
|
+
}
|
|
55
|
+
const opts = typeof analytics === 'object' ? analytics : {};
|
|
56
|
+
return {
|
|
57
|
+
enabled: opts.enabled !== false,
|
|
58
|
+
requireConsent: opts.requireConsent ?? false,
|
|
59
|
+
trackClicks: opts.trackClicks ?? true,
|
|
60
|
+
trackScroll: opts.trackScroll ?? true,
|
|
61
|
+
trackOutboundLinks: opts.trackOutboundLinks ?? true,
|
|
62
|
+
trackForms: opts.trackForms ?? false,
|
|
63
|
+
trackWebVitals: opts.trackWebVitals ?? true,
|
|
64
|
+
trackErrors: opts.trackErrors ?? true,
|
|
65
|
+
trackSPANavigation: opts.trackSPANavigation ?? true,
|
|
66
|
+
sampleRate: opts.sampleRate ?? 1,
|
|
67
|
+
excludePatterns: opts.excludePatterns ?? [],
|
|
68
|
+
globalProperties: opts.globalProperties ?? {},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Resolve workbench config */
|
|
73
|
+
export function resolveWorkbenchConfig(
|
|
74
|
+
workbench: boolean | string | WorkbenchOptions | undefined
|
|
75
|
+
): { enabled: boolean; route: string; headers: Record<string, string> } {
|
|
76
|
+
if (!workbench) {
|
|
77
|
+
return { enabled: false, route: '/workbench', headers: {} };
|
|
78
|
+
}
|
|
79
|
+
if (workbench === true) {
|
|
80
|
+
return { enabled: true, route: '/workbench', headers: {} };
|
|
81
|
+
}
|
|
82
|
+
if (typeof workbench === 'string') {
|
|
83
|
+
return { enabled: true, route: workbench, headers: {} };
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
enabled: true,
|
|
87
|
+
route: workbench.route ?? '/workbench',
|
|
88
|
+
headers: workbench.headers ?? {},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Inject analytics scripts into HTML */
|
|
93
|
+
function injectAnalytics(
|
|
94
|
+
html: string,
|
|
95
|
+
analyticsConfig: AnalyticsOptions & { enabled: boolean }
|
|
96
|
+
): string {
|
|
97
|
+
if (!analyticsConfig.enabled) return html;
|
|
98
|
+
|
|
99
|
+
const orgId = getOrganizationId() || '';
|
|
100
|
+
const projectId = getProjectId() || '';
|
|
101
|
+
const isDevmode = runtimeIsDevMode();
|
|
102
|
+
|
|
103
|
+
const pageConfig = {
|
|
104
|
+
...analyticsConfig,
|
|
105
|
+
orgId,
|
|
106
|
+
projectId,
|
|
107
|
+
isDevmode,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const configScript = `<script>window.__AGENTUITY_ANALYTICS__=${JSON.stringify(pageConfig)};</script>`;
|
|
111
|
+
const sessionScript = '<script src="/_agentuity/webanalytics/session.js" async></script>';
|
|
112
|
+
|
|
113
|
+
// In production, the beacon is already in HTML as a CDN asset (data-agentuity-beacon marker)
|
|
114
|
+
const beaconMarker = '<script data-agentuity-beacon';
|
|
115
|
+
if (html.includes(beaconMarker)) {
|
|
116
|
+
const injection = configScript + sessionScript;
|
|
117
|
+
return html.replace(beaconMarker, injection + beaconMarker);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Development: beacon served from local route
|
|
121
|
+
const beaconScript = '<script src="/_agentuity/webanalytics/analytics.js"></script>';
|
|
122
|
+
const injection = configScript + sessionScript + beaconScript;
|
|
123
|
+
|
|
124
|
+
if (html.includes('</head>')) {
|
|
125
|
+
return html.replace('</head>', injection + '</head>');
|
|
126
|
+
}
|
|
127
|
+
if (html.includes('<body')) {
|
|
128
|
+
return html.replace(/<body([^>]*)>/, `<body$1>${injection}`);
|
|
129
|
+
}
|
|
130
|
+
return injection + html;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Register analytics routes on the app */
|
|
134
|
+
export function registerAnalyticsRoutes(
|
|
135
|
+
app: ReturnType<typeof createRouter>,
|
|
136
|
+
_analyticsConfig: AnalyticsOptions & { enabled: boolean }
|
|
137
|
+
): void {
|
|
138
|
+
app.get(
|
|
139
|
+
'/_agentuity/webanalytics/session.js',
|
|
140
|
+
createWebSessionMiddleware(),
|
|
141
|
+
async (c: Context) => {
|
|
142
|
+
const threadId = c.get('_webThreadId') || '';
|
|
143
|
+
const sessionData = JSON.stringify({ threadId });
|
|
144
|
+
const sessionScript = `window.__AGENTUITY_SESSION__=${sessionData};`;
|
|
145
|
+
return new Response(sessionScript, {
|
|
146
|
+
headers: {
|
|
147
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
148
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (isDevelopment()) {
|
|
155
|
+
app.get('/_agentuity/webanalytics/analytics.js', async () => {
|
|
156
|
+
return new Response(BEACON_SCRIPT, {
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
159
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Health routes
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
export function registerHealthRoutes(app: ReturnType<typeof createRouter>): void {
|
|
171
|
+
if (!isDevelopment()) {
|
|
172
|
+
const healthHandler = (c: Context) => {
|
|
173
|
+
return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
174
|
+
};
|
|
175
|
+
const idleHandler = (c: Context) => {
|
|
176
|
+
const server = globalThis.__AGENTUITY_SERVER__;
|
|
177
|
+
if (!server) return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
178
|
+
if (hasWaitUntilPending())
|
|
179
|
+
return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
180
|
+
if (server.pendingRequests > 1)
|
|
181
|
+
return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
182
|
+
if (server.pendingWebSockets > 0)
|
|
183
|
+
return c.text('NO', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
184
|
+
return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
185
|
+
};
|
|
186
|
+
app.get('/_agentuity/health', healthHandler);
|
|
187
|
+
app.get('/_health', healthHandler);
|
|
188
|
+
app.get('/_agentuity/idle', idleHandler);
|
|
189
|
+
app.get('/_idle', idleHandler);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isDevelopment()) {
|
|
193
|
+
app.get('/_agentuity/ready', (c: Context) => {
|
|
194
|
+
return c.text('OK', 200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// Web routes (production static serving)
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
export function registerWebRoutes(
|
|
204
|
+
app: ReturnType<typeof createRouter>,
|
|
205
|
+
analyticsConfig: AnalyticsOptions & { enabled: boolean }
|
|
206
|
+
): void {
|
|
207
|
+
if (isDevelopment()) return;
|
|
208
|
+
|
|
209
|
+
// Resolve client dir relative to the built app.js location (import.meta.dir)
|
|
210
|
+
// rather than process.cwd(), since the app may be run from inside .agentuity/
|
|
211
|
+
const appDir = typeof import.meta.dir === 'string' ? import.meta.dir : process.cwd();
|
|
212
|
+
const clientDir = join(appDir, 'client');
|
|
213
|
+
// Fallback: try process.cwd()/.agentuity/client if appDir/client doesn't exist
|
|
214
|
+
const resolvedClientDir = existsSync(join(clientDir, 'index.html'))
|
|
215
|
+
? clientDir
|
|
216
|
+
: join(process.cwd(), '.agentuity', 'client');
|
|
217
|
+
const indexHtmlPath = join(resolvedClientDir, 'index.html');
|
|
218
|
+
const baseIndexHtml = existsSync(indexHtmlPath) ? readFileSync(indexHtmlPath, 'utf-8') : '';
|
|
219
|
+
|
|
220
|
+
if (!baseIndexHtml) return;
|
|
221
|
+
|
|
222
|
+
const prodHtmlHandler = (c: Context) => {
|
|
223
|
+
if (analyticsConfig.enabled) {
|
|
224
|
+
const html = injectAnalytics(baseIndexHtml, analyticsConfig);
|
|
225
|
+
return c.html(html);
|
|
226
|
+
}
|
|
227
|
+
return c.html(baseIndexHtml);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
app.get('/', prodHtmlHandler);
|
|
231
|
+
app.use('/assets/*', serveStatic({ root: resolvedClientDir, mimes: mimeTypes }));
|
|
232
|
+
app.use(
|
|
233
|
+
'/*',
|
|
234
|
+
serveStatic({
|
|
235
|
+
root: resolvedClientDir,
|
|
236
|
+
rewriteRequestPath: (path: string) => path,
|
|
237
|
+
mimes: mimeTypes,
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
app.all('/_agentuity/*', (c: Context) => c.notFound());
|
|
242
|
+
app.all('/api/*', (c: Context) => c.notFound());
|
|
243
|
+
|
|
244
|
+
app.get('*', (c: Context) => {
|
|
245
|
+
const path = c.req.path;
|
|
246
|
+
if (/\.[a-zA-Z0-9]+$/.test(path)) {
|
|
247
|
+
return c.notFound();
|
|
248
|
+
}
|
|
249
|
+
return prodHtmlHandler(c);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Workbench UI route (dev only)
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
257
|
+
export function registerWorkbenchUI(
|
|
258
|
+
app: ReturnType<typeof createRouter>,
|
|
259
|
+
workbenchConfig: { enabled: boolean; route: string }
|
|
260
|
+
): void {
|
|
261
|
+
if (!workbenchConfig.enabled || !isDevelopment()) return;
|
|
262
|
+
|
|
263
|
+
const workbenchSrcDir = join(process.cwd(), '.agentuity', 'workbench-src');
|
|
264
|
+
const workbenchIndexPath = join(workbenchSrcDir, 'index.html');
|
|
265
|
+
|
|
266
|
+
app.get(workbenchConfig.route, async (c: Context) => {
|
|
267
|
+
const html = await Bun.file(workbenchIndexPath).text();
|
|
268
|
+
const withVite = html
|
|
269
|
+
.replace('src="./main.tsx"', `src="/@fs${workbenchSrcDir}/main.tsx"`)
|
|
270
|
+
.replace('href="./styles.css"', `href="/@fs${workbenchSrcDir}/styles.css"`);
|
|
271
|
+
return c.html(withVite);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// Server startup
|
|
277
|
+
// ============================================================================
|
|
278
|
+
|
|
279
|
+
export function startServer(
|
|
280
|
+
app: ReturnType<typeof createRouter>,
|
|
281
|
+
options?: { requestTimeout?: number }
|
|
282
|
+
): void {
|
|
283
|
+
if (typeof Bun === 'undefined') return;
|
|
284
|
+
|
|
285
|
+
enableProcessExitProtection();
|
|
286
|
+
|
|
287
|
+
const port = parseInt(process.env.PORT || '3500', 10);
|
|
288
|
+
const requestTimeout = options?.requestTimeout ?? 0;
|
|
289
|
+
|
|
290
|
+
const server = Bun.serve({
|
|
291
|
+
fetch: (req, server) => {
|
|
292
|
+
server.timeout(req, requestTimeout);
|
|
293
|
+
return app.fetch(req, server);
|
|
294
|
+
},
|
|
295
|
+
websocket,
|
|
296
|
+
port,
|
|
297
|
+
hostname: '127.0.0.1',
|
|
298
|
+
development: isDevelopment(),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
globalThis.__AGENTUITY_SERVER__ = server;
|
|
302
|
+
|
|
303
|
+
if (!isDevelopment()) {
|
|
304
|
+
const handleShutdown = async (_signal: string) => {
|
|
305
|
+
try {
|
|
306
|
+
await runShutdown();
|
|
307
|
+
} catch {
|
|
308
|
+
// Ignore shutdown errors
|
|
309
|
+
}
|
|
310
|
+
process.exit(0);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
process.once('SIGTERM', () => handleShutdown('SIGTERM'));
|
|
314
|
+
process.once('SIGINT', () => handleShutdown('SIGINT'));
|
|
315
|
+
}
|
|
316
|
+
}
|
package/src/dev-patches/aisdk.ts
CHANGED
|
@@ -12,19 +12,16 @@
|
|
|
12
12
|
function warnMissingKey(envKey: string): void {
|
|
13
13
|
const isDev =
|
|
14
14
|
process.env.AGENTUITY_ENVIRONMENT === 'development' || process.env.NODE_ENV !== 'production';
|
|
15
|
+
|
|
16
|
+
// Align no-bundle runtime behavior with historical build-time expectations:
|
|
17
|
+
// in development, don't emit eager startup errors for every provider.
|
|
15
18
|
if (isDev) {
|
|
16
|
-
|
|
17
|
-
console.error(
|
|
18
|
-
' 1. Login to Agentuity Cloud (agentuity auth login) to use the AI Gateway (recommended)'
|
|
19
|
-
);
|
|
20
|
-
console.error(` 2. Set ${envKey} in your .env file to use the provider directly`);
|
|
21
|
-
} else {
|
|
22
|
-
console.error(`[ERROR] The environment variable ${envKey} is required. Either:`);
|
|
23
|
-
console.error(
|
|
24
|
-
' 1. Use Agentuity Cloud AI Gateway by ensuring AGENTUITY_SDK_KEY is configured'
|
|
25
|
-
);
|
|
26
|
-
console.error(` 2. Set ${envKey} using "agentuity env set ${envKey}" and redeploy`);
|
|
19
|
+
return;
|
|
27
20
|
}
|
|
21
|
+
|
|
22
|
+
console.error(`[ERROR] The environment variable ${envKey} is required. Either:`);
|
|
23
|
+
console.error(' 1. Use Agentuity Cloud AI Gateway by ensuring AGENTUITY_SDK_KEY is configured');
|
|
24
|
+
console.error(` 2. Set ${envKey} using "agentuity env set ${envKey}" and redeploy`);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
/**
|
|
@@ -21,19 +21,17 @@ const GATEWAY_CONFIGS: GatewayConfig[] = [
|
|
|
21
21
|
function warnMissingKey(envKey: string): void {
|
|
22
22
|
const isDev =
|
|
23
23
|
process.env.AGENTUITY_ENVIRONMENT === 'development' || process.env.NODE_ENV !== 'production';
|
|
24
|
+
|
|
25
|
+
// Align no-bundle runtime behavior with historical build-time expectations:
|
|
26
|
+
// in development, don't emit eager startup errors for every provider.
|
|
27
|
+
// Missing credentials should surface when/if that provider is actually used.
|
|
24
28
|
if (isDev) {
|
|
25
|
-
|
|
26
|
-
console.error(
|
|
27
|
-
' 1. Login to Agentuity Cloud (agentuity auth login) to use the AI Gateway (recommended)'
|
|
28
|
-
);
|
|
29
|
-
console.error(` 2. Set ${envKey} in your .env file to use the provider directly`);
|
|
30
|
-
} else {
|
|
31
|
-
console.error(`[ERROR] The environment variable ${envKey} is required. Either:`);
|
|
32
|
-
console.error(
|
|
33
|
-
' 1. Use Agentuity Cloud AI Gateway by ensuring AGENTUITY_SDK_KEY is configured'
|
|
34
|
-
);
|
|
35
|
-
console.error(` 2. Set ${envKey} using "agentuity env set ${envKey}" and redeploy`);
|
|
29
|
+
return;
|
|
36
30
|
}
|
|
31
|
+
|
|
32
|
+
console.error(`[ERROR] The environment variable ${envKey} is required. Either:`);
|
|
33
|
+
console.error(' 1. Use Agentuity Cloud AI Gateway by ensuring AGENTUITY_SDK_KEY is configured');
|
|
34
|
+
console.error(` 2. Set ${envKey} using "agentuity env set ${envKey}" and redeploy`);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
/**
|
package/src/globals.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe globalThis declarations for Agentuity runtime.
|
|
3
|
+
*
|
|
4
|
+
* String-keyed globals that persist across bun --hot reloads.
|
|
5
|
+
* Symbol-keyed globals are accessed via the typed helpers in _globals.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
// eslint-disable-next-line no-var
|
|
10
|
+
var __AGENTUITY_SERVER__:
|
|
11
|
+
| {
|
|
12
|
+
stop: (closeActiveConnections?: boolean) => void;
|
|
13
|
+
port: number | undefined;
|
|
14
|
+
pendingRequests: number;
|
|
15
|
+
pendingWebSockets: number;
|
|
16
|
+
}
|
|
17
|
+
| undefined;
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line no-var
|
|
20
|
+
var __AGENTUITY_BUN_SUBPROCESS__:
|
|
21
|
+
| {
|
|
22
|
+
kill: (signal?: number | NodeJS.Signals) => void;
|
|
23
|
+
exitCode: number | null;
|
|
24
|
+
}
|
|
25
|
+
| undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route metadata tagging for build-time route type discovery.
|
|
3
|
+
*
|
|
4
|
+
* Handler wrappers (websocket, sse, stream, cron) stamp this symbol on
|
|
5
|
+
* the returned middleware so the build tool can detect the route type
|
|
6
|
+
* from `router.routes` without AST parsing.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const ROUTE_META = Symbol.for('agentuity:route-meta');
|
|
10
|
+
|
|
11
|
+
export interface RouteMeta {
|
|
12
|
+
type: 'websocket' | 'sse' | 'stream' | 'cron';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tag a handler/middleware with route metadata.
|
|
17
|
+
*/
|
|
18
|
+
export function tagRoute<T extends (...args: any[]) => any>(handler: T, meta: RouteMeta): T {
|
|
19
|
+
(handler as any)[ROUTE_META] = meta;
|
|
20
|
+
return handler;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read route metadata from a handler/middleware.
|
|
25
|
+
*/
|
|
26
|
+
export function getRouteMeta(handler: unknown): RouteMeta | undefined {
|
|
27
|
+
if (typeof handler === 'function') {
|
|
28
|
+
return (handler as any)[ROUTE_META];
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
package/src/handlers/cron.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Context, Handler } from 'hono';
|
|
|
2
2
|
import { returnResponse } from '../_util';
|
|
3
3
|
import type { Env } from '../app';
|
|
4
4
|
import { verifySignature } from '../signature';
|
|
5
|
+
import { tagRoute } from './_route-meta';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Handler function for cron jobs.
|
|
@@ -92,7 +93,7 @@ export function cron<E extends Env = Env>(
|
|
|
92
93
|
handler = maybeHandler!;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
const cronHandler: Handler<E> = async (c: Context<E>) => {
|
|
96
97
|
if (c.req.method !== 'POST') {
|
|
97
98
|
throw new Error(
|
|
98
99
|
`Cron endpoint must use POST method, but received ${c.req.method}. ` +
|
|
@@ -127,6 +128,8 @@ export function cron<E extends Env = Env>(
|
|
|
127
128
|
|
|
128
129
|
return returnResponse(c, result);
|
|
129
130
|
};
|
|
131
|
+
|
|
132
|
+
return tagRoute(cronHandler, { type: 'cron' });
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
/**
|
package/src/handlers/sse.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { stream as honoStream } from 'hono/streaming';
|
|
|
3
3
|
import { context as otelContext, ROOT_CONTEXT } from '@opentelemetry/api';
|
|
4
4
|
import { StructuredError } from '@agentuity/core';
|
|
5
5
|
import type { Schema } from '@agentuity/schema';
|
|
6
|
-
import { getAgentAsyncLocalStorage } from '../_context';
|
|
7
6
|
import type { Env } from '../app';
|
|
7
|
+
import { tagRoute } from './_route-meta';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Error thrown when sse() is called without a handler function.
|
|
@@ -83,7 +83,7 @@ export interface SSEOptions<TOutput = unknown> {
|
|
|
83
83
|
*
|
|
84
84
|
* This schema is used for:
|
|
85
85
|
* - Type inference in generated `routes.ts` registry
|
|
86
|
-
* - Automatic typing of `
|
|
86
|
+
* - Automatic typing of `EventSource/EventStreamManager` hook's `data` property
|
|
87
87
|
*
|
|
88
88
|
* The schema is NOT used for runtime validation - SSE messages are sent
|
|
89
89
|
* as-is through the stream. Use this for TypeScript type safety only.
|
|
@@ -187,8 +187,8 @@ function formatSSEMessage(message: SSEMessage): string {
|
|
|
187
187
|
* stream.close();
|
|
188
188
|
* }));
|
|
189
189
|
*
|
|
190
|
-
* // On the frontend,
|
|
191
|
-
* // const { data } =
|
|
190
|
+
* // On the frontend, EventSource/EventStreamManager will now have typed data:
|
|
191
|
+
* // const { data } = EventSource/EventStreamManager('/api/stream');
|
|
192
192
|
* // data.type is 'token' | 'complete' | 'error'
|
|
193
193
|
* ```
|
|
194
194
|
*
|
|
@@ -225,10 +225,7 @@ export function sse<E extends Env = Env, TOutput = unknown>(
|
|
|
225
225
|
|
|
226
226
|
// Note: options.output is captured for type inference but not used at runtime
|
|
227
227
|
// The CLI extracts this during build to generate typed route registries
|
|
228
|
-
|
|
229
|
-
const asyncLocalStorage = getAgentAsyncLocalStorage();
|
|
230
|
-
const capturedContext = asyncLocalStorage.getStore();
|
|
231
|
-
|
|
228
|
+
const sseHandler: Handler<E> = (c: Context<E>) => {
|
|
232
229
|
// Track stream completion for deferred session/thread saving
|
|
233
230
|
// This promise resolves when the stream closes (normally or via abort)
|
|
234
231
|
let resolveDone: (() => void) | undefined;
|
|
@@ -345,10 +342,6 @@ export function sse<E extends Env = Env, TOutput = unknown>(
|
|
|
345
342
|
}
|
|
346
343
|
};
|
|
347
344
|
|
|
348
|
-
// Run handler with AsyncLocalStorage context propagation.
|
|
349
|
-
// honoStream already uses a fire-and-forget pattern internally,
|
|
350
|
-
// so we can safely await here - the response is already being sent.
|
|
351
|
-
//
|
|
352
345
|
// IMPORTANT: We run in ROOT_CONTEXT (no active OTEL span) to avoid a Bun bug
|
|
353
346
|
// where OTEL-instrumented fetch conflicts with streaming responses.
|
|
354
347
|
// This causes "ReadableStream has already been used" errors when AI SDK's
|
|
@@ -357,13 +350,9 @@ export function sse<E extends Env = Env, TOutput = unknown>(
|
|
|
357
350
|
// our OTEL fetch wrapper use the original unpatched fetch.
|
|
358
351
|
// See: https://github.com/agentuity/sdk/issues/471
|
|
359
352
|
// See: https://github.com/oven-sh/bun/issues/24766
|
|
360
|
-
await otelContext.with(ROOT_CONTEXT,
|
|
361
|
-
if (capturedContext) {
|
|
362
|
-
await asyncLocalStorage.run(capturedContext, runInContext);
|
|
363
|
-
} else {
|
|
364
|
-
await runInContext();
|
|
365
|
-
}
|
|
366
|
-
});
|
|
353
|
+
await otelContext.with(ROOT_CONTEXT, runInContext);
|
|
367
354
|
});
|
|
368
355
|
};
|
|
356
|
+
|
|
357
|
+
return tagRoute(sseHandler, { type: 'sse' });
|
|
369
358
|
}
|
package/src/handlers/stream.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Context, Handler } from 'hono';
|
|
2
2
|
import { stream as honoStream } from 'hono/streaming';
|
|
3
3
|
import { context as otelContext, ROOT_CONTEXT } from '@opentelemetry/api';
|
|
4
|
-
import { getAgentAsyncLocalStorage } from '../_context';
|
|
5
4
|
import type { Env } from '../app';
|
|
6
5
|
import { STREAM_DONE_PROMISE_KEY, IS_STREAMING_RESPONSE_KEY } from './sse';
|
|
6
|
+
import { tagRoute } from './_route-meta';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Handler function for streaming responses.
|
|
@@ -57,10 +57,7 @@ export type StreamHandler<E extends Env = Env> = (
|
|
|
57
57
|
* @returns Hono handler for streaming response
|
|
58
58
|
*/
|
|
59
59
|
export function stream<E extends Env = Env>(handler: StreamHandler<E>): Handler<E> {
|
|
60
|
-
|
|
61
|
-
const asyncLocalStorage = getAgentAsyncLocalStorage();
|
|
62
|
-
const capturedContext = asyncLocalStorage.getStore();
|
|
63
|
-
|
|
60
|
+
const streamHandler: Handler<E> = (c: Context<E>) => {
|
|
64
61
|
// Track stream completion for deferred session/thread saving
|
|
65
62
|
// This promise resolves when the stream completes (pipe finishes or errors)
|
|
66
63
|
let resolveDone: (() => void) | undefined;
|
|
@@ -116,13 +113,9 @@ export function stream<E extends Env = Env>(handler: StreamHandler<E>): Handler<
|
|
|
116
113
|
// our OTEL fetch wrapper use the original unpatched fetch.
|
|
117
114
|
// See: https://github.com/agentuity/sdk/issues/471
|
|
118
115
|
// See: https://github.com/oven-sh/bun/issues/24766
|
|
119
|
-
await otelContext.with(ROOT_CONTEXT,
|
|
120
|
-
if (capturedContext) {
|
|
121
|
-
await asyncLocalStorage.run(capturedContext, runInContext);
|
|
122
|
-
} else {
|
|
123
|
-
await runInContext();
|
|
124
|
-
}
|
|
125
|
-
});
|
|
116
|
+
await otelContext.with(ROOT_CONTEXT, runInContext);
|
|
126
117
|
});
|
|
127
118
|
};
|
|
119
|
+
|
|
120
|
+
return tagRoute(streamHandler, { type: 'stream' });
|
|
128
121
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Context, MiddlewareHandler } from 'hono';
|
|
2
2
|
import { upgradeWebSocket } from 'hono/bun';
|
|
3
3
|
import { context as otelContext, ROOT_CONTEXT } from '@opentelemetry/api';
|
|
4
|
-
import { getAgentAsyncLocalStorage } from '../_context';
|
|
5
4
|
import type { Env } from '../app';
|
|
5
|
+
import { tagRoute } from './_route-meta';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Context key for WebSocket close promise.
|
|
@@ -92,16 +92,15 @@ export type WebSocketHandler<E extends Env = Env> = (
|
|
|
92
92
|
* @param handler - Synchronous handler function receiving context and WebSocket connection
|
|
93
93
|
* @returns Hono middleware handler for WebSocket upgrade
|
|
94
94
|
*/
|
|
95
|
-
export function websocket<E extends Env = Env>(
|
|
95
|
+
export function websocket<E extends Env = Env>(
|
|
96
|
+
handler: WebSocketHandler<E>
|
|
97
|
+
): MiddlewareHandler<E, string, { outputFormat: 'ws' }> {
|
|
96
98
|
const wsHandler = upgradeWebSocket((c: Context<E>) => {
|
|
97
99
|
let openHandler: ((event: Event) => void | Promise<void>) | undefined;
|
|
98
100
|
let messageHandler: ((event: MessageEvent) => void | Promise<void>) | undefined;
|
|
99
101
|
let closeHandler: ((event: CloseEvent) => void | Promise<void>) | undefined;
|
|
100
102
|
let initialized = false;
|
|
101
103
|
|
|
102
|
-
const asyncLocalStorage = getAgentAsyncLocalStorage();
|
|
103
|
-
const capturedContext = asyncLocalStorage.getStore();
|
|
104
|
-
|
|
105
104
|
// Create done promise for session lifecycle deferral, but ONLY for actual
|
|
106
105
|
// WebSocket upgrade requests. The factory runs unconditionally for every
|
|
107
106
|
// request hitting this route (Hono calls createEvents before attempting
|
|
@@ -144,11 +143,7 @@ export function websocket<E extends Env = Env>(handler: WebSocketHandler<E>): Mi
|
|
|
144
143
|
// See: https://github.com/oven-sh/bun/issues/24766
|
|
145
144
|
const runHandler = () => {
|
|
146
145
|
otelContext.with(ROOT_CONTEXT, () => {
|
|
147
|
-
|
|
148
|
-
asyncLocalStorage.run(capturedContext, () => handler(c, wsConnection));
|
|
149
|
-
} else {
|
|
150
|
-
handler(c, wsConnection);
|
|
151
|
-
}
|
|
146
|
+
handler(c, wsConnection);
|
|
152
147
|
});
|
|
153
148
|
initialized = true;
|
|
154
149
|
};
|
|
@@ -162,14 +157,7 @@ export function websocket<E extends Env = Env>(handler: WebSocketHandler<E>): Mi
|
|
|
162
157
|
wsConnection.send = (data) => ws.send(data);
|
|
163
158
|
|
|
164
159
|
if (openHandler) {
|
|
165
|
-
|
|
166
|
-
await otelContext.with(ROOT_CONTEXT, async () => {
|
|
167
|
-
if (capturedContext) {
|
|
168
|
-
await asyncLocalStorage.run(capturedContext, () => h(event));
|
|
169
|
-
} else {
|
|
170
|
-
await h(event);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
160
|
+
await otelContext.with(ROOT_CONTEXT, () => openHandler!(event));
|
|
173
161
|
}
|
|
174
162
|
} catch (err) {
|
|
175
163
|
c.var.logger?.error('WebSocket onOpen error:', err);
|
|
@@ -184,14 +172,7 @@ export function websocket<E extends Env = Env>(handler: WebSocketHandler<E>): Mi
|
|
|
184
172
|
runHandler();
|
|
185
173
|
}
|
|
186
174
|
if (messageHandler) {
|
|
187
|
-
|
|
188
|
-
await otelContext.with(ROOT_CONTEXT, async () => {
|
|
189
|
-
if (capturedContext) {
|
|
190
|
-
await asyncLocalStorage.run(capturedContext, () => h(event));
|
|
191
|
-
} else {
|
|
192
|
-
await h(event);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
175
|
+
await otelContext.with(ROOT_CONTEXT, () => messageHandler!(event));
|
|
195
176
|
}
|
|
196
177
|
} catch (err) {
|
|
197
178
|
c.var.logger?.error('WebSocket onMessage error:', err);
|
|
@@ -202,14 +183,7 @@ export function websocket<E extends Env = Env>(handler: WebSocketHandler<E>): Mi
|
|
|
202
183
|
onClose: async (event: CloseEvent, _ws: any) => {
|
|
203
184
|
try {
|
|
204
185
|
if (closeHandler) {
|
|
205
|
-
|
|
206
|
-
await otelContext.with(ROOT_CONTEXT, async () => {
|
|
207
|
-
if (capturedContext) {
|
|
208
|
-
await asyncLocalStorage.run(capturedContext, () => h(event));
|
|
209
|
-
} else {
|
|
210
|
-
await h(event);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
186
|
+
await otelContext.with(ROOT_CONTEXT, () => closeHandler!(event));
|
|
213
187
|
}
|
|
214
188
|
} catch (err) {
|
|
215
189
|
c.var.logger?.error('WebSocket onClose error:', err);
|
|
@@ -222,8 +196,8 @@ export function websocket<E extends Env = Env>(handler: WebSocketHandler<E>): Mi
|
|
|
222
196
|
};
|
|
223
197
|
});
|
|
224
198
|
|
|
225
|
-
const middleware: MiddlewareHandler<E> = (c, next) =>
|
|
226
|
-
(wsHandler as unknown as MiddlewareHandler<E>)(c, next);
|
|
199
|
+
const middleware: MiddlewareHandler<E, string, { outputFormat: 'ws' }> = (c, next) =>
|
|
200
|
+
(wsHandler as unknown as MiddlewareHandler<E, string, { outputFormat: 'ws' }>)(c, next);
|
|
227
201
|
|
|
228
|
-
return middleware;
|
|
202
|
+
return tagRoute(middleware, { type: 'websocket' });
|
|
229
203
|
}
|