@agentuity/runtime 0.0.94 → 0.0.96
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/AGENTS.md +3 -1
- package/dist/_events.d.ts +64 -0
- package/dist/_events.d.ts.map +1 -0
- package/dist/_events.js +92 -0
- package/dist/_events.js.map +1 -0
- package/dist/_idle.d.ts +1 -1
- package/dist/_idle.d.ts.map +1 -1
- package/dist/_idle.js +2 -16
- package/dist/_idle.js.map +1 -1
- package/dist/_server.d.ts +30 -13
- package/dist/_server.d.ts.map +1 -1
- package/dist/_server.js +39 -572
- package/dist/_server.js.map +1 -1
- package/dist/_services.d.ts.map +1 -1
- package/dist/_services.js +4 -2
- package/dist/_services.js.map +1 -1
- package/dist/_standalone.d.ts.map +1 -1
- package/dist/_standalone.js +2 -1
- package/dist/_standalone.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +13 -17
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts +58 -171
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +119 -218
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +29 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +200 -0
- package/dist/middleware.js.map +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +5 -2
- package/dist/router.js.map +1 -1
- package/dist/services/local/vector.d.ts.map +1 -1
- package/dist/services/local/vector.js +3 -2
- package/dist/services/local/vector.js.map +1 -1
- package/dist/services/thread/local.d.ts +20 -0
- package/dist/services/thread/local.d.ts.map +1 -0
- package/dist/services/thread/local.js +76 -0
- package/dist/services/thread/local.js.map +1 -0
- package/dist/session.d.ts +60 -8
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +186 -54
- package/dist/session.js.map +1 -1
- package/dist/web.d.ts +8 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +66 -0
- package/dist/web.js.map +1 -0
- package/dist/workbench.d.ts +3 -0
- package/dist/workbench.d.ts.map +1 -1
- package/dist/workbench.js +300 -31
- package/dist/workbench.js.map +1 -1
- package/package.json +10 -10
- package/src/_events.ts +142 -0
- package/src/_idle.ts +2 -18
- package/src/_server.ts +48 -681
- package/src/_services.ts +4 -2
- package/src/_standalone.ts +2 -1
- package/src/agent.ts +11 -14
- package/src/app.ts +164 -246
- package/src/index.ts +42 -4
- package/src/middleware.ts +252 -0
- package/src/router.ts +6 -2
- package/src/services/local/vector.ts +3 -2
- package/src/services/thread/local.ts +106 -0
- package/src/session.ts +238 -59
- package/src/web.ts +75 -0
- package/src/workbench.ts +367 -30
package/src/_server.ts
CHANGED
|
@@ -1,59 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type Context,
|
|
6
|
-
type Tracer,
|
|
7
|
-
trace,
|
|
8
|
-
type Attributes,
|
|
9
|
-
propagation,
|
|
10
|
-
} from '@opentelemetry/api';
|
|
11
|
-
import { TraceState } from '@opentelemetry/core';
|
|
12
|
-
import type { Span } from '@opentelemetry/sdk-trace-base';
|
|
13
|
-
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
14
|
-
import { type LogLevel, ServiceException } from '@agentuity/core';
|
|
15
|
-
import { cors } from 'hono/cors';
|
|
16
|
-
import { createMiddleware } from 'hono/factory';
|
|
17
|
-
import { Hono, type Context as HonoContext } from 'hono';
|
|
18
|
-
import { HTTPException } from 'hono/http-exception';
|
|
19
|
-
import type { BunWebSocketData } from 'hono/bun';
|
|
20
|
-
import { matchedRoutes } from 'hono/route';
|
|
21
|
-
import { websocket } from 'hono/bun';
|
|
22
|
-
import { join } from 'node:path';
|
|
23
|
-
import type { AppConfig, Env, PrivateVariables } from './app';
|
|
24
|
-
import { extractTraceContextFromRequest } from './otel/http';
|
|
25
|
-
import { register } from './otel/config';
|
|
26
|
-
import type { Logger } from './logger';
|
|
27
|
-
import { internal } from './logger/internal';
|
|
28
|
-
import { isIdle } from './_idle';
|
|
29
|
-
import * as runtimeConfig from './_config';
|
|
30
|
-
import { runInHTTPContext } from './_context';
|
|
31
|
-
import { runAgentShutdowns, createAgentMiddleware } from './agent';
|
|
32
|
-
import { enableProcessExitProtection, internalExit } from './_process-protection';
|
|
33
|
-
import {
|
|
34
|
-
createServices,
|
|
35
|
-
getThreadProvider,
|
|
36
|
-
getSessionProvider,
|
|
37
|
-
getSessionEventProvider,
|
|
38
|
-
getServices,
|
|
39
|
-
} from './_services';
|
|
40
|
-
import { generateId } from './session';
|
|
41
|
-
import WaitUntilHandler from './_waituntil';
|
|
42
|
-
import registerTokenProcessor, { TOKENS_HEADER, DURATION_HEADER } from './_tokens';
|
|
43
|
-
|
|
44
|
-
const SESSION_HEADER = 'x-session-id';
|
|
45
|
-
|
|
46
|
-
let globalServerInstance: Bun.Server<BunWebSocketData> | null = null;
|
|
1
|
+
/**
|
|
2
|
+
* Minimal server globals for Vite-native architecture
|
|
3
|
+
* The server is managed by Vite (dev) or Bun.serve in the generated entry file (prod)
|
|
4
|
+
*/
|
|
47
5
|
|
|
48
|
-
|
|
6
|
+
import type { Logger } from './logger';
|
|
7
|
+
import type { Hono, Context as HonoContext } from 'hono';
|
|
8
|
+
import type { Env, PrivateVariables } from './app';
|
|
9
|
+
import type { Tracer } from '@opentelemetry/api';
|
|
10
|
+
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
49
11
|
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
let globalRouterInstance: Hono<Env<any>> | null = null;
|
|
50
14
|
let globalLogger: Logger | null = null;
|
|
15
|
+
let globalTracer: Tracer | null = null;
|
|
16
|
+
|
|
17
|
+
const spanProcessors: SpanProcessor[] = [];
|
|
51
18
|
|
|
52
19
|
/**
|
|
53
20
|
* List of AgentContext properties that should trigger helpful error messages
|
|
54
21
|
* when accessed directly on HonoContext in route handlers.
|
|
55
|
-
*
|
|
56
|
-
* Users should access these via c.var.propertyName instead of c.propertyName.
|
|
57
22
|
*/
|
|
58
23
|
export const AGENT_CONTEXT_PROPERTIES = [
|
|
59
24
|
'logger',
|
|
@@ -70,666 +35,68 @@ export const AGENT_CONTEXT_PROPERTIES = [
|
|
|
70
35
|
'waitUntil',
|
|
71
36
|
] as const;
|
|
72
37
|
|
|
73
|
-
/**
|
|
74
|
-
* Install helpful error messages on HonoContext for AgentContext properties.
|
|
75
|
-
* When users try to access c.logger instead of c.var.logger in route handlers,
|
|
76
|
-
* they'll get a clear error message explaining the correct usage.
|
|
77
|
-
*/
|
|
78
|
-
function installContextPropertyHelpers(c: HonoContext): void {
|
|
79
|
-
for (const property of AGENT_CONTEXT_PROPERTIES) {
|
|
80
|
-
// Skip if property already exists (e.g., native Hono properties)
|
|
81
|
-
if (Object.prototype.hasOwnProperty.call(c, property)) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
Object.defineProperty(c, property, {
|
|
86
|
-
get() {
|
|
87
|
-
throw new Error(
|
|
88
|
-
`In route handlers, use c.var.${property} instead of c.${property}. ` +
|
|
89
|
-
`The property '${property}' is available on AgentContext (for agent handlers) ` +
|
|
90
|
-
`but must be accessed via c.var in HonoContext (route handlers).`
|
|
91
|
-
);
|
|
92
|
-
},
|
|
93
|
-
set() {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`In route handlers, use c.var.${property} instead of c.${property}. ` +
|
|
96
|
-
`The property '${property}' is available on AgentContext (for agent handlers) ` +
|
|
97
|
-
`but must be accessed via c.var in HonoContext (route handlers).`
|
|
98
|
-
);
|
|
99
|
-
},
|
|
100
|
-
configurable: true,
|
|
101
|
-
enumerable: false,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
let globalTracer: Tracer | null = null;
|
|
106
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
-
let globalAppState: any = null;
|
|
108
|
-
|
|
109
|
-
export function getServer() {
|
|
110
|
-
return globalServerInstance;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
38
|
export function getRouter() {
|
|
114
39
|
return globalRouterInstance;
|
|
115
40
|
}
|
|
116
41
|
|
|
117
|
-
//
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
export function setGlobalRouter(router: Hono<Env<any>>) {
|
|
44
|
+
globalRouterInstance = router;
|
|
45
|
+
}
|
|
118
46
|
|
|
119
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Returns the global logger instance.
|
|
49
|
+
* This is a singleton created during application initialization.
|
|
50
|
+
*/
|
|
51
|
+
export function createLogger() {
|
|
120
52
|
return globalLogger;
|
|
121
53
|
}
|
|
122
54
|
|
|
123
|
-
export function
|
|
124
|
-
return
|
|
55
|
+
export function getLogger() {
|
|
56
|
+
return globalLogger;
|
|
125
57
|
}
|
|
126
58
|
|
|
127
|
-
export function
|
|
128
|
-
|
|
59
|
+
export function setGlobalLogger(logger: Logger) {
|
|
60
|
+
globalLogger = logger;
|
|
129
61
|
}
|
|
130
62
|
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
const environment = runtimeConfig.getEnvironment();
|
|
134
|
-
return devmode || environment === 'development';
|
|
63
|
+
export function getTracer() {
|
|
64
|
+
return globalTracer;
|
|
135
65
|
}
|
|
136
66
|
|
|
137
|
-
function
|
|
138
|
-
|
|
67
|
+
export function setGlobalTracer(tracer: Tracer) {
|
|
68
|
+
globalTracer = tracer;
|
|
139
69
|
}
|
|
140
70
|
|
|
141
|
-
const spanProcessors: SpanProcessor[] = [];
|
|
142
|
-
|
|
143
71
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
72
|
+
* Add a custom span processor that will be added to the otel configuration.
|
|
73
|
+
* This method must be called before the server is initialized.
|
|
146
74
|
*/
|
|
147
75
|
export function addSpanProcessor(processor: SpanProcessor) {
|
|
148
76
|
spanProcessors.push(processor);
|
|
149
77
|
}
|
|
150
78
|
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
const projectId = runtimeConfig.getProjectId();
|
|
154
|
-
const deploymentId = runtimeConfig.getDeploymentId();
|
|
155
|
-
const devmode = runtimeConfig.isDevMode();
|
|
156
|
-
const environment = runtimeConfig.getEnvironment();
|
|
157
|
-
|
|
158
|
-
class RegisterAgentSpanProcessor implements SpanProcessor {
|
|
159
|
-
onStart(span: Span, _context: Context) {
|
|
160
|
-
const attrs: Attributes = {
|
|
161
|
-
'@agentuity/orgId': orgId,
|
|
162
|
-
'@agentuity/projectId': projectId,
|
|
163
|
-
'@agentuity/deploymentId': deploymentId,
|
|
164
|
-
'@agentuity/devmode': devmode,
|
|
165
|
-
'@agentuity/environment': environment,
|
|
166
|
-
};
|
|
167
|
-
span.setAttributes(attrs);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
onEnd(_span: Span) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
forceFlush() {
|
|
175
|
-
return Promise.resolve();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
shutdown() {
|
|
179
|
-
return Promise.resolve();
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
addSpanProcessor(new RegisterAgentSpanProcessor());
|
|
79
|
+
export function getSpanProcessors(): SpanProcessor[] {
|
|
80
|
+
return spanProcessors;
|
|
183
81
|
}
|
|
184
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Helper to cast HonoContext to include private variables
|
|
85
|
+
*/
|
|
185
86
|
export function privateContext<E extends Env>(c: HonoContext<E>) {
|
|
186
87
|
return c as unknown as HonoContext<{ Variables: PrivateVariables }>;
|
|
187
88
|
}
|
|
188
89
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
90
|
+
/**
|
|
91
|
+
* No-op for Vite-native architecture (Vite manages server lifecycle)
|
|
92
|
+
*/
|
|
193
93
|
export const notifyReady = () => {
|
|
194
|
-
|
|
94
|
+
// No-op: Vite handles server readiness
|
|
195
95
|
};
|
|
196
96
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return [globalServerInstance, globalAppState as TAppState];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const { promise, resolve } = Promise.withResolvers<void>();
|
|
207
|
-
startupPromise = promise;
|
|
208
|
-
startupPromiseResolver = resolve;
|
|
209
|
-
|
|
210
|
-
runtimeConfig.init();
|
|
211
|
-
|
|
212
|
-
const logLevel = process.env.AGENTUITY_LOG_LEVEL || 'info';
|
|
213
|
-
const port = getPort();
|
|
214
|
-
const hostname = '127.0.0.1';
|
|
215
|
-
const serverUrl = `http://${hostname}:${port}`;
|
|
216
|
-
|
|
217
|
-
// Enable process.exit protection before any user code can run
|
|
218
|
-
enableProcessExitProtection();
|
|
219
|
-
|
|
220
|
-
// this must come before registering any otel stuff
|
|
221
|
-
registerAgentuitySpanProcessor();
|
|
222
|
-
registerTokenProcessor();
|
|
223
|
-
|
|
224
|
-
// Create the telemetry and logger
|
|
225
|
-
const otel = register({ processors: spanProcessors, logLevel: logLevel as LogLevel });
|
|
226
|
-
|
|
227
|
-
// Create services (may return local router)
|
|
228
|
-
const servicesResult = createServices(otel.logger, config, serverUrl);
|
|
229
|
-
|
|
230
|
-
// Create the App State
|
|
231
|
-
globalAppState = await appStateInitializer();
|
|
232
|
-
|
|
233
|
-
globalRouterInstance = router as unknown as Hono<Env>;
|
|
234
|
-
globalLogger = otel.logger;
|
|
235
|
-
globalTracer = otel.tracer;
|
|
236
|
-
|
|
237
|
-
router.onError((error, _c) => {
|
|
238
|
-
if (error instanceof HTTPException) {
|
|
239
|
-
otel.logger.error('HTTP Error: %s (%d)', error.cause, error.status);
|
|
240
|
-
return error.getResponse();
|
|
241
|
-
}
|
|
242
|
-
if (error.name === 'UnauthenticatedError') {
|
|
243
|
-
otel.logger.error('Unauthenticated Error: %s', error.message);
|
|
244
|
-
return new Response(error.message, { status: 501 });
|
|
245
|
-
}
|
|
246
|
-
if (error instanceof ServiceException) {
|
|
247
|
-
const serviceError = error as InstanceType<typeof ServiceException>;
|
|
248
|
-
otel.logger.error(
|
|
249
|
-
'Service Exception: %s (%s returned HTTP status code: %d%s)',
|
|
250
|
-
error.message,
|
|
251
|
-
serviceError.url,
|
|
252
|
-
serviceError.statusCode,
|
|
253
|
-
serviceError.sessionId ? `, session: ${serviceError.sessionId}` : ''
|
|
254
|
-
);
|
|
255
|
-
return new Response(error.message, {
|
|
256
|
-
status: serviceError.statusCode ?? 500,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
otel.logger.error('Unhandled Server Error: %s', error);
|
|
260
|
-
return new Response('Internal Server Error', { status: 500 });
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const blockOnStartup = async () => {
|
|
264
|
-
// block until completing the setup if still running
|
|
265
|
-
if (startupPromise) {
|
|
266
|
-
await startupPromise;
|
|
267
|
-
startupPromise = undefined;
|
|
268
|
-
startupPromiseResolver = undefined;
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
router.get('/_health', async (c) => {
|
|
273
|
-
await blockOnStartup();
|
|
274
|
-
return c.text('OK');
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
router.use(async (c, next) => {
|
|
278
|
-
await blockOnStartup();
|
|
279
|
-
|
|
280
|
-
c.set('logger', otel.logger);
|
|
281
|
-
c.set('tracer', otel.tracer);
|
|
282
|
-
c.set('meter', otel.meter);
|
|
283
|
-
c.set('app', globalAppState);
|
|
284
|
-
|
|
285
|
-
// Set storage services so they're available in c.var
|
|
286
|
-
const services = getServices();
|
|
287
|
-
c.set('kv', services.kv);
|
|
288
|
-
c.set('stream', services.stream);
|
|
289
|
-
c.set('vector', services.vector);
|
|
290
|
-
|
|
291
|
-
// Add helpful error messages for common mistakes
|
|
292
|
-
// Users should use c.var.XYZ in route handlers, not c.XYZ
|
|
293
|
-
installContextPropertyHelpers(c);
|
|
294
|
-
|
|
295
|
-
const isWebSocket = c.req.header('upgrade')?.toLowerCase() === 'websocket';
|
|
296
|
-
const skipLogging = c.req.path.startsWith('/_agentuity/');
|
|
297
|
-
const started = performance.now();
|
|
298
|
-
if (!skipLogging) {
|
|
299
|
-
otel.logger.debug('%s %s started', c.req.method, c.req.path);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
await runInHTTPContext(c, next);
|
|
303
|
-
|
|
304
|
-
// Calculate and add duration header for all HTTP requests (not WebSocket)
|
|
305
|
-
if (!isWebSocket) {
|
|
306
|
-
const endTime = performance.now();
|
|
307
|
-
const duration = ((endTime - started) / 1000).toFixed(1); // Duration in seconds
|
|
308
|
-
c.header(DURATION_HEADER, `${duration}s`);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Don't log completion for websocket upgrades - they stay open
|
|
312
|
-
if (!skipLogging && !isWebSocket) {
|
|
313
|
-
otel.logger.debug(
|
|
314
|
-
'%s %s completed (%d) in %sms',
|
|
315
|
-
c.req.method,
|
|
316
|
-
c.req.path,
|
|
317
|
-
c.res.status,
|
|
318
|
-
Number(performance.now() - started).toFixed(2)
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// setup the cors middleware
|
|
324
|
-
router.use(
|
|
325
|
-
'*',
|
|
326
|
-
cors({
|
|
327
|
-
origin: config?.cors?.origin ?? ((origin) => origin),
|
|
328
|
-
allowHeaders: config?.cors?.allowHeaders ?? [
|
|
329
|
-
'Content-Type',
|
|
330
|
-
'Authorization',
|
|
331
|
-
'Accept',
|
|
332
|
-
'Origin',
|
|
333
|
-
'X-Requested-With',
|
|
334
|
-
],
|
|
335
|
-
allowMethods: ['POST', 'GET', 'OPTIONS', 'HEAD', 'PUT', 'DELETE', 'PATCH'],
|
|
336
|
-
exposeHeaders: [
|
|
337
|
-
'Content-Length',
|
|
338
|
-
TOKENS_HEADER,
|
|
339
|
-
DURATION_HEADER,
|
|
340
|
-
SESSION_HEADER,
|
|
341
|
-
'x-deployment',
|
|
342
|
-
],
|
|
343
|
-
maxAge: 600,
|
|
344
|
-
credentials: true,
|
|
345
|
-
...(config?.cors ?? {}), // allow the app config to override
|
|
346
|
-
})
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
router.route('/_agentuity', createAgentuityAPIs());
|
|
350
|
-
|
|
351
|
-
// Mount local storage router if using local services
|
|
352
|
-
if (servicesResult?.localRouter) {
|
|
353
|
-
router.route('/', servicesResult.localRouter);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// we create a middleware that attempts to match our routeid to the incoming route
|
|
357
|
-
let routeMapping: Record<string, string>;
|
|
358
|
-
const routePathMapper = createMiddleware<Env>(async (c, next) => {
|
|
359
|
-
if (!routeMapping) {
|
|
360
|
-
// Look for .routemapping.json in the project's directory
|
|
361
|
-
// This is where the build plugin writes it (build.config.outdir)
|
|
362
|
-
const projectRoot = process.cwd();
|
|
363
|
-
let routeMappingPath: string;
|
|
364
|
-
if (projectRoot === '/home/agentuity/app') {
|
|
365
|
-
// in production there is no .agentuity folder
|
|
366
|
-
routeMappingPath = join(projectRoot, '.routemapping.json');
|
|
367
|
-
} else {
|
|
368
|
-
// in dev mode, look in .agentuity folder (where build writes it)
|
|
369
|
-
routeMappingPath = join(projectRoot, '.agentuity', '.routemapping.json');
|
|
370
|
-
}
|
|
371
|
-
const file = Bun.file(routeMappingPath);
|
|
372
|
-
if (!(await file.exists())) {
|
|
373
|
-
internal.warn(
|
|
374
|
-
'Route mapping file not found at %s. Route tracking will be disabled.',
|
|
375
|
-
routeMappingPath
|
|
376
|
-
);
|
|
377
|
-
routeMapping = {}; // Empty mapping, no route tracking
|
|
378
|
-
} else {
|
|
379
|
-
routeMapping = (await file.json()) as Record<string, string>;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const matches = matchedRoutes(c).filter(
|
|
383
|
-
(m) => m.method !== 'ALL' && (m.path.startsWith('/api') || m.path.startsWith('/agent/'))
|
|
384
|
-
);
|
|
385
|
-
const _c = privateContext(c);
|
|
386
|
-
if (matches.length > 0) {
|
|
387
|
-
const method = c.req.method.toLowerCase();
|
|
388
|
-
for (const m of matches) {
|
|
389
|
-
const found = routeMapping[`${method} ${m.path}`];
|
|
390
|
-
if (found) {
|
|
391
|
-
_c.set('routeId', found);
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
_c.set('trigger', 'api'); // will get overwritten below if another trigger
|
|
397
|
-
return next();
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
router.use('/api/*', routePathMapper);
|
|
401
|
-
|
|
402
|
-
// set the trigger for specific types
|
|
403
|
-
for (const trigger of ['sms', 'email', 'cron'] as const) {
|
|
404
|
-
const middleware = createMiddleware(async (c, next) => {
|
|
405
|
-
const _c = privateContext(c);
|
|
406
|
-
_c.set('trigger', trigger);
|
|
407
|
-
await next();
|
|
408
|
-
});
|
|
409
|
-
router.use(`/api/${trigger}/*`, middleware);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// otelMiddleware must run before createAgentMiddleware to set session/thread
|
|
413
|
-
router.use('/api/*', otelMiddleware);
|
|
414
|
-
|
|
415
|
-
// Attach services and agent registry to context for API routes
|
|
416
|
-
router.use('/api/*', async (c, next) => {
|
|
417
|
-
// Use a null agent name to just populate the agent registry without setting current agent
|
|
418
|
-
return createAgentMiddleware('')(c, next);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// Apply otelMiddleware to workbench routes for full telemetry and session tracking
|
|
422
|
-
if (config?.services?.workbench) {
|
|
423
|
-
// otelMiddleware must run before createAgentMiddleware to set session/thread
|
|
424
|
-
router.use('/_agentuity/workbench/*', otelMiddleware);
|
|
425
|
-
router.use('/_agentuity/workbench/*', async (c, next) => {
|
|
426
|
-
// Use a null agent name to just populate the agent registry without setting current agent
|
|
427
|
-
return createAgentMiddleware('')(c, next);
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const shutdown = async () => {
|
|
432
|
-
if (isShutdown) {
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
otel.logger.debug('shutdown started');
|
|
436
|
-
isShutdown = true;
|
|
437
|
-
// Force exit after timeout if cleanup hangs
|
|
438
|
-
const forceExitTimer = setTimeout(() => {
|
|
439
|
-
otel.logger.warn('shutdown timed out after 5s, forcing exit');
|
|
440
|
-
internalExit(1);
|
|
441
|
-
}, 5_000);
|
|
442
|
-
try {
|
|
443
|
-
// stop accepting new connections
|
|
444
|
-
if (globalServerInstance) {
|
|
445
|
-
await globalServerInstance.stop();
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// wait for idle
|
|
449
|
-
const shutdownStarted = Date.now();
|
|
450
|
-
otel.logger.debug('waiting for pending connections to complete');
|
|
451
|
-
while (Date.now() - shutdownStarted < 60_000 * 2) {
|
|
452
|
-
if ((globalServerInstance?.pendingRequests ?? 0) > 0) {
|
|
453
|
-
await Bun.sleep(1_000);
|
|
454
|
-
} else {
|
|
455
|
-
break;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
otel.logger.debug('no more pending connections');
|
|
459
|
-
|
|
460
|
-
// Run agent shutdowns first
|
|
461
|
-
await runAgentShutdowns(globalAppState);
|
|
462
|
-
|
|
463
|
-
// Run app shutdown if provided
|
|
464
|
-
if (config?.shutdown && globalAppState) {
|
|
465
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
466
|
-
await config.shutdown(globalAppState as any);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
await otel.shutdown();
|
|
470
|
-
otel.logger.debug('shutdown completed');
|
|
471
|
-
} finally {
|
|
472
|
-
clearTimeout(forceExitTimer);
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
process.on('beforeExit', async () => await shutdown());
|
|
477
|
-
|
|
478
|
-
// Handle synchronous exit event - can't do async work here
|
|
479
|
-
process.on('exit', (code) => {
|
|
480
|
-
if (!isShutdown) {
|
|
481
|
-
otel.logger.debug('process exiting with code %d before shutdown completed', code);
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
process.once('SIGINT', async () => {
|
|
486
|
-
await shutdown();
|
|
487
|
-
internalExit(0);
|
|
488
|
-
});
|
|
489
|
-
process.once('SIGTERM', async () => {
|
|
490
|
-
await shutdown();
|
|
491
|
-
internalExit(0);
|
|
492
|
-
});
|
|
493
|
-
process.once('uncaughtException', async (err) => {
|
|
494
|
-
otel.logger.error('An uncaught exception was received: %s', err);
|
|
495
|
-
await shutdown();
|
|
496
|
-
internalExit(1);
|
|
497
|
-
});
|
|
498
|
-
process.once('unhandledRejection', async (reason) => {
|
|
499
|
-
otel.logger.error('An unhandled promise rejection was received: %s', reason);
|
|
500
|
-
await shutdown();
|
|
501
|
-
internalExit(1);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
const server = Bun.serve({
|
|
505
|
-
hostname,
|
|
506
|
-
development: isDevelopment(),
|
|
507
|
-
fetch: router.fetch,
|
|
508
|
-
idleTimeout: 0,
|
|
509
|
-
port,
|
|
510
|
-
websocket,
|
|
511
|
-
id: null,
|
|
512
|
-
});
|
|
513
|
-
globalServerInstance = server;
|
|
514
|
-
|
|
515
|
-
return [server, globalAppState];
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
const createAgentuityAPIs = () => {
|
|
519
|
-
const router = new Hono<Env>();
|
|
520
|
-
router.get('idle', (c) => {
|
|
521
|
-
if (isIdle() && !isShutdown) {
|
|
522
|
-
return c.text('OK', { status: 200 });
|
|
523
|
-
}
|
|
524
|
-
return c.text('NO', { status: 200 });
|
|
525
|
-
});
|
|
526
|
-
router.get('health', (c) => c.text('OK'));
|
|
527
|
-
return router;
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
const otelMiddleware = createMiddleware<Env>(async (c, next) => {
|
|
531
|
-
// Extract trace context from headers
|
|
532
|
-
const extractedContext = extractTraceContextFromRequest(c.req.raw);
|
|
533
|
-
|
|
534
|
-
const method = c.req.method;
|
|
535
|
-
const url = new URL(c.req.url);
|
|
536
|
-
const threadProvider = getThreadProvider();
|
|
537
|
-
const sessionProvider = getSessionProvider();
|
|
538
|
-
const sessionEventProvider = getSessionEventProvider();
|
|
539
|
-
|
|
540
|
-
// Execute the request handler within the extracted context
|
|
541
|
-
await context.with(extractedContext, async (): Promise<void> => {
|
|
542
|
-
// Create a span for this incoming request
|
|
543
|
-
const tracer = trace.getTracer('http-server');
|
|
544
|
-
await tracer.startActiveSpan(
|
|
545
|
-
`HTTP ${method}`,
|
|
546
|
-
{
|
|
547
|
-
kind: SpanKind.SERVER,
|
|
548
|
-
attributes: {
|
|
549
|
-
'http.method': method,
|
|
550
|
-
'http.host': url.host,
|
|
551
|
-
'http.user_agent': c.req.header('user-agent') || '',
|
|
552
|
-
'http.path': url.pathname,
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
async (span): Promise<void> => {
|
|
556
|
-
const sctx = span.spanContext();
|
|
557
|
-
const sessionId = sctx?.traceId ? `sess_${sctx.traceId}` : generateId('sess');
|
|
558
|
-
|
|
559
|
-
// Add to tracestate
|
|
560
|
-
let traceState = sctx.traceState ?? new TraceState();
|
|
561
|
-
const projectId = runtimeConfig.getProjectId();
|
|
562
|
-
const orgId = runtimeConfig.getOrganizationId();
|
|
563
|
-
const deploymentId = runtimeConfig.getDeploymentId();
|
|
564
|
-
const isDevMode = runtimeConfig.isDevMode();
|
|
565
|
-
if (projectId) {
|
|
566
|
-
traceState = traceState.set('pid', projectId);
|
|
567
|
-
}
|
|
568
|
-
if (orgId) {
|
|
569
|
-
traceState = traceState.set('oid', orgId);
|
|
570
|
-
}
|
|
571
|
-
if (isDevMode) {
|
|
572
|
-
traceState = traceState.set('d', '1');
|
|
573
|
-
}
|
|
574
|
-
sctx.traceState = traceState;
|
|
575
|
-
|
|
576
|
-
const thread = await threadProvider.restore(c);
|
|
577
|
-
const session = await sessionProvider.restore(thread, sessionId);
|
|
578
|
-
const handler = new WaitUntilHandler(tracer);
|
|
579
|
-
|
|
580
|
-
const _c = privateContext(c);
|
|
581
|
-
const agentIds = new Set<string>();
|
|
582
|
-
_c.set('agentIds', agentIds);
|
|
583
|
-
|
|
584
|
-
const shouldSendSession = !!(orgId && projectId && _c.var.routeId);
|
|
585
|
-
let canSendSessionEvents = true;
|
|
586
|
-
|
|
587
|
-
if (shouldSendSession) {
|
|
588
|
-
await sessionEventProvider
|
|
589
|
-
.start({
|
|
590
|
-
id: sessionId,
|
|
591
|
-
orgId,
|
|
592
|
-
projectId,
|
|
593
|
-
threadId: thread.id,
|
|
594
|
-
routeId: _c.var.routeId,
|
|
595
|
-
deploymentId,
|
|
596
|
-
devmode: isDevMode,
|
|
597
|
-
environment: runtimeConfig.getEnvironment(),
|
|
598
|
-
method: c.req.method,
|
|
599
|
-
url: c.req.url,
|
|
600
|
-
trigger: _c.var.trigger,
|
|
601
|
-
})
|
|
602
|
-
.catch((ex) => {
|
|
603
|
-
canSendSessionEvents = false;
|
|
604
|
-
c.var.logger.error('error sending session start event: %s', ex);
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
c.set('sessionId', sessionId);
|
|
609
|
-
c.set('thread', thread);
|
|
610
|
-
c.set('session', session);
|
|
611
|
-
_c.set('waitUntilHandler', handler);
|
|
612
|
-
|
|
613
|
-
let hasPendingWaits = false;
|
|
614
|
-
|
|
615
|
-
try {
|
|
616
|
-
await next();
|
|
617
|
-
if (handler?.hasPending()) {
|
|
618
|
-
hasPendingWaits = true;
|
|
619
|
-
handler
|
|
620
|
-
.waitUntilAll(c.var.logger, sessionId)
|
|
621
|
-
.then(async () => {
|
|
622
|
-
c.var.logger.debug('wait until finished for session %s', sessionId);
|
|
623
|
-
await sessionProvider.save(session);
|
|
624
|
-
await threadProvider.save(thread);
|
|
625
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
626
|
-
if (shouldSendSession && canSendSessionEvents) {
|
|
627
|
-
const userData = session.serializeUserData();
|
|
628
|
-
sessionEventProvider
|
|
629
|
-
.complete({
|
|
630
|
-
id: sessionId,
|
|
631
|
-
threadId: thread.empty() ? null : thread.id,
|
|
632
|
-
statusCode: c.res.status,
|
|
633
|
-
agentIds: Array.from(agentIds),
|
|
634
|
-
userData,
|
|
635
|
-
})
|
|
636
|
-
.then(() => {})
|
|
637
|
-
.catch((ex) => c.var.logger.error(ex));
|
|
638
|
-
}
|
|
639
|
-
})
|
|
640
|
-
.catch((ex) => {
|
|
641
|
-
c.var.logger.error('wait until errored for session %s. %s', sessionId, ex);
|
|
642
|
-
if (ex instanceof Error) {
|
|
643
|
-
span.recordException(ex);
|
|
644
|
-
}
|
|
645
|
-
const message = (ex as Error).message ?? String(ex);
|
|
646
|
-
span.setStatus({
|
|
647
|
-
code: SpanStatusCode.ERROR,
|
|
648
|
-
message,
|
|
649
|
-
});
|
|
650
|
-
c.var.logger.error(message);
|
|
651
|
-
if (shouldSendSession && canSendSessionEvents) {
|
|
652
|
-
const userData = session.serializeUserData();
|
|
653
|
-
sessionEventProvider
|
|
654
|
-
.complete({
|
|
655
|
-
id: sessionId,
|
|
656
|
-
threadId: thread.empty() ? null : thread.id,
|
|
657
|
-
statusCode: c.res.status,
|
|
658
|
-
error: message,
|
|
659
|
-
agentIds: Array.from(agentIds),
|
|
660
|
-
userData,
|
|
661
|
-
})
|
|
662
|
-
.then(() => {})
|
|
663
|
-
.catch((ex) => c.var.logger.error(ex));
|
|
664
|
-
}
|
|
665
|
-
})
|
|
666
|
-
.finally(() => {
|
|
667
|
-
span.end();
|
|
668
|
-
});
|
|
669
|
-
} else {
|
|
670
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
671
|
-
if (shouldSendSession && canSendSessionEvents) {
|
|
672
|
-
const userData = session.serializeUserData();
|
|
673
|
-
sessionEventProvider
|
|
674
|
-
.complete({
|
|
675
|
-
id: sessionId,
|
|
676
|
-
threadId: thread.empty() ? null : thread.id,
|
|
677
|
-
statusCode: c.res.status,
|
|
678
|
-
agentIds: Array.from(agentIds),
|
|
679
|
-
userData,
|
|
680
|
-
})
|
|
681
|
-
.then(() => {})
|
|
682
|
-
.catch((ex) => c.var.logger.error(ex));
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
} catch (ex) {
|
|
686
|
-
if (ex instanceof Error) {
|
|
687
|
-
span.recordException(ex);
|
|
688
|
-
}
|
|
689
|
-
const message = (ex as Error).message ?? String(ex);
|
|
690
|
-
span.setStatus({
|
|
691
|
-
code: SpanStatusCode.ERROR,
|
|
692
|
-
message,
|
|
693
|
-
});
|
|
694
|
-
c.var.logger.error(message);
|
|
695
|
-
if (shouldSendSession && canSendSessionEvents) {
|
|
696
|
-
const userData = session.serializeUserData();
|
|
697
|
-
sessionEventProvider
|
|
698
|
-
.complete({
|
|
699
|
-
id: sessionId,
|
|
700
|
-
threadId: thread.empty() ? null : thread.id,
|
|
701
|
-
statusCode: c.res.status,
|
|
702
|
-
error: message,
|
|
703
|
-
agentIds: Array.from(agentIds),
|
|
704
|
-
userData,
|
|
705
|
-
})
|
|
706
|
-
.then(() => {})
|
|
707
|
-
.catch((ex) => c.var.logger.error(ex));
|
|
708
|
-
}
|
|
709
|
-
throw ex;
|
|
710
|
-
} finally {
|
|
711
|
-
// add otel headers into HTTP response
|
|
712
|
-
const headers: Record<string, string> = {};
|
|
713
|
-
propagation.inject(context.active(), headers);
|
|
714
|
-
for (const key of Object.keys(headers)) {
|
|
715
|
-
c.header(key, headers[key]);
|
|
716
|
-
}
|
|
717
|
-
// add session and deployment headers
|
|
718
|
-
const traceId = sctx?.traceId || sessionId.replace(/^sess_/, '');
|
|
719
|
-
c.header(SESSION_HEADER, `sess_${traceId}`);
|
|
720
|
-
if (deploymentId) {
|
|
721
|
-
c.header('x-deployment', deploymentId);
|
|
722
|
-
}
|
|
723
|
-
if (!hasPendingWaits) {
|
|
724
|
-
try {
|
|
725
|
-
await sessionProvider.save(session);
|
|
726
|
-
await threadProvider.save(thread);
|
|
727
|
-
} finally {
|
|
728
|
-
span.end();
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
);
|
|
734
|
-
});
|
|
735
|
-
});
|
|
97
|
+
/**
|
|
98
|
+
* No-op for Vite-native architecture (returns null)
|
|
99
|
+
*/
|
|
100
|
+
export function getServer() {
|
|
101
|
+
return null;
|
|
102
|
+
}
|