@agentuity/runtime 0.0.100 → 0.0.102
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 +34 -212
- package/dist/_metadata.d.ts +107 -0
- package/dist/_metadata.d.ts.map +1 -0
- package/dist/_metadata.js +179 -0
- package/dist/_metadata.js.map +1 -0
- package/dist/_process-protection.d.ts.map +1 -1
- package/dist/_process-protection.js +4 -0
- package/dist/_process-protection.js.map +1 -1
- package/dist/_services.d.ts.map +1 -1
- package/dist/_services.js +18 -17
- package/dist/_services.js.map +1 -1
- package/dist/_standalone.d.ts.map +1 -1
- package/dist/_standalone.js +17 -0
- package/dist/_standalone.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +53 -12
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts +61 -10
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js.map +1 -1
- package/dist/devmode.d.ts.map +1 -1
- package/dist/devmode.js +13 -5
- package/dist/devmode.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +61 -5
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +192 -25
- package/dist/middleware.js.map +1 -1
- package/dist/services/evalrun/http.d.ts.map +1 -1
- package/dist/services/evalrun/http.js +14 -4
- package/dist/services/evalrun/http.js.map +1 -1
- package/dist/services/local/vector.d.ts +5 -1
- package/dist/services/local/vector.d.ts.map +1 -1
- package/dist/services/local/vector.js +112 -0
- package/dist/services/local/vector.js.map +1 -1
- package/dist/services/session/http.d.ts.map +1 -1
- package/dist/services/session/http.js +7 -0
- package/dist/services/session/http.js.map +1 -1
- package/dist/services/session/local.d.ts +2 -2
- package/dist/services/session/local.d.ts.map +1 -1
- package/dist/services/session/local.js +5 -4
- package/dist/services/session/local.js.map +1 -1
- package/dist/session.d.ts +30 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +90 -13
- package/dist/session.js.map +1 -1
- package/dist/workbench.d.ts.map +1 -1
- package/dist/workbench.js +13 -20
- package/dist/workbench.js.map +1 -1
- package/package.json +5 -5
- package/src/_metadata.ts +307 -0
- package/src/_process-protection.ts +6 -0
- package/src/_services.ts +23 -21
- package/src/_standalone.ts +22 -0
- package/src/agent.ts +63 -12
- package/src/app.ts +65 -9
- package/src/devmode.ts +16 -5
- package/src/index.ts +12 -2
- package/src/middleware.ts +221 -29
- package/src/services/evalrun/http.ts +15 -4
- package/src/services/local/vector.ts +160 -0
- package/src/services/session/http.ts +11 -0
- package/src/services/session/local.ts +9 -4
- package/src/session.ts +142 -13
- package/src/workbench.ts +13 -26
package/src/app.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { type Env as HonoEnv } from 'hono';
|
|
3
3
|
import type { cors } from 'hono/cors';
|
|
4
|
+
import type { compress } from 'hono/compress';
|
|
4
5
|
import type { Logger } from './logger';
|
|
5
6
|
import type { Meter, Tracer } from '@opentelemetry/api';
|
|
6
7
|
import type {
|
|
@@ -14,19 +15,78 @@ import type {
|
|
|
14
15
|
import type { Email } from './io/email';
|
|
15
16
|
import type { ThreadProvider, SessionProvider, Session, Thread } from './session';
|
|
16
17
|
import type WaitUntilHandler from './_waituntil';
|
|
17
|
-
|
|
18
|
-
// TODO: This should be imported from workbench package, but causes circular dependency
|
|
19
|
-
export interface WorkbenchInstance {
|
|
20
|
-
config: { route?: string; headers?: Record<string, string> };
|
|
21
|
-
}
|
|
18
|
+
import type { Context } from 'hono';
|
|
22
19
|
|
|
23
20
|
type CorsOptions = Parameters<typeof cors>[0];
|
|
21
|
+
type HonoCompressOptions = Parameters<typeof compress>[0];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration options for response compression middleware.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const app = await createApp({
|
|
29
|
+
* compression: {
|
|
30
|
+
* enabled: true,
|
|
31
|
+
* threshold: 1024,
|
|
32
|
+
* }
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface CompressionConfig {
|
|
37
|
+
/**
|
|
38
|
+
* Enable or disable compression globally.
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Minimum response body size in bytes before compression is attempted.
|
|
45
|
+
* Responses smaller than this threshold will not be compressed.
|
|
46
|
+
* @default 1024
|
|
47
|
+
*/
|
|
48
|
+
threshold?: number;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Optional filter function to skip compression for specific requests.
|
|
52
|
+
* Return false to skip compression for the request.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* filter: (c) => !c.req.path.startsWith('/internal')
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
filter?: (c: Context) => boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Raw options passed through to Hono's compress middleware.
|
|
63
|
+
* These are merged with Agentuity's defaults.
|
|
64
|
+
*/
|
|
65
|
+
honoOptions?: HonoCompressOptions;
|
|
66
|
+
}
|
|
24
67
|
|
|
25
68
|
export interface AppConfig<TAppState = Record<string, never>> {
|
|
26
69
|
/**
|
|
27
70
|
* Override the default cors settings
|
|
28
71
|
*/
|
|
29
72
|
cors?: CorsOptions;
|
|
73
|
+
/**
|
|
74
|
+
* Configure response compression.
|
|
75
|
+
* Set to `false` to disable compression entirely.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const app = await createApp({
|
|
80
|
+
* compression: {
|
|
81
|
+
* threshold: 2048,
|
|
82
|
+
* }
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* // Or disable compression:
|
|
86
|
+
* const app = await createApp({ compression: false });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
compression?: CompressionConfig | false;
|
|
30
90
|
/**
|
|
31
91
|
* Override the default services
|
|
32
92
|
*/
|
|
@@ -63,10 +123,6 @@ export interface AppConfig<TAppState = Record<string, never>> {
|
|
|
63
123
|
* the EvalRunEventProvider to override instead of the default
|
|
64
124
|
*/
|
|
65
125
|
evalRunEvent?: EvalRunEventProvider;
|
|
66
|
-
/**
|
|
67
|
-
* the Workbench to override instead of the default
|
|
68
|
-
*/
|
|
69
|
-
workbench?: WorkbenchInstance;
|
|
70
126
|
};
|
|
71
127
|
/**
|
|
72
128
|
* Optional setup function called before server starts
|
package/src/devmode.ts
CHANGED
|
@@ -78,12 +78,23 @@ const overlay = `
|
|
|
78
78
|
</style>
|
|
79
79
|
`;
|
|
80
80
|
|
|
81
|
+
// Global controller to avoid registering multiple SIGINT listeners
|
|
82
|
+
let globalController: AbortController | undefined;
|
|
83
|
+
let globalSigintHandler: (() => void) | undefined;
|
|
84
|
+
|
|
81
85
|
export function registerDevModeRoutes(router: Hono) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
// Reuse existing controller or create new one
|
|
87
|
+
if (!globalController) {
|
|
88
|
+
globalController = new AbortController();
|
|
89
|
+
|
|
90
|
+
// Only register SIGINT handler once
|
|
91
|
+
globalSigintHandler = () => {
|
|
92
|
+
globalController?.abort();
|
|
93
|
+
};
|
|
94
|
+
process.on('SIGINT', globalSigintHandler);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const signal = globalController.signal;
|
|
87
98
|
router.get('/__dev__/reload', () => {
|
|
88
99
|
const stream = new ReadableStream({
|
|
89
100
|
start(controller): void {
|
package/src/index.ts
CHANGED
|
@@ -27,8 +27,8 @@ export {
|
|
|
27
27
|
|
|
28
28
|
// app.ts exports (all app-related functionality)
|
|
29
29
|
export {
|
|
30
|
-
type WorkbenchInstance,
|
|
31
30
|
type AppConfig,
|
|
31
|
+
type CompressionConfig,
|
|
32
32
|
type Variables,
|
|
33
33
|
type TriggerType,
|
|
34
34
|
type PrivateVariables,
|
|
@@ -44,7 +44,12 @@ export {
|
|
|
44
44
|
export { addEventListener, removeEventListener } from './_events';
|
|
45
45
|
|
|
46
46
|
// middleware.ts exports (Vite-native)
|
|
47
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
createBaseMiddleware,
|
|
49
|
+
createCorsMiddleware,
|
|
50
|
+
createOtelMiddleware,
|
|
51
|
+
createCompressionMiddleware,
|
|
52
|
+
} from './middleware';
|
|
48
53
|
|
|
49
54
|
// Internal exports needed by generated entry files
|
|
50
55
|
export { register } from './otel/config';
|
|
@@ -193,3 +198,8 @@ export type { RouteSchema, GetRouteSchema } from './_validation';
|
|
|
193
198
|
*/
|
|
194
199
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
195
200
|
export interface AppState {}
|
|
201
|
+
|
|
202
|
+
// Re-export bootstrapRuntimeEnv from @agentuity/server for convenience
|
|
203
|
+
// This allows generated code to import from @agentuity/runtime instead of having
|
|
204
|
+
// a direct dependency on @agentuity/server
|
|
205
|
+
export { bootstrapRuntimeEnv, type RuntimeBootstrapOptions } from '@agentuity/server';
|
package/src/middleware.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { createMiddleware } from 'hono/factory';
|
|
7
7
|
import { cors } from 'hono/cors';
|
|
8
|
-
import
|
|
8
|
+
import { compress } from 'hono/compress';
|
|
9
|
+
import type { Env, CompressionConfig } from './app';
|
|
9
10
|
import type { Logger } from './logger';
|
|
11
|
+
import { getAppConfig } from './app';
|
|
10
12
|
import { generateId } from './session';
|
|
11
13
|
import { runInHTTPContext } from './_context';
|
|
12
14
|
import { DURATION_HEADER, TOKENS_HEADER } from './_tokens';
|
|
@@ -22,6 +24,8 @@ import {
|
|
|
22
24
|
} from '@opentelemetry/api';
|
|
23
25
|
import { TraceState } from '@opentelemetry/core';
|
|
24
26
|
import * as runtimeConfig from './_config';
|
|
27
|
+
import { getSessionEventProvider } from './_services';
|
|
28
|
+
import { internal } from './logger/internal';
|
|
25
29
|
|
|
26
30
|
const SESSION_HEADER = 'x-session-id';
|
|
27
31
|
const THREAD_HEADER = 'x-thread-id';
|
|
@@ -74,7 +78,8 @@ export interface MiddlewareConfig {
|
|
|
74
78
|
* Create base middleware that sets up context variables
|
|
75
79
|
*/
|
|
76
80
|
export function createBaseMiddleware(config: MiddlewareConfig) {
|
|
77
|
-
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
return createMiddleware<Env<any>>(async (c, next) => {
|
|
78
83
|
c.set('logger', config.logger);
|
|
79
84
|
c.set('tracer', config.tracer);
|
|
80
85
|
c.set('meter', config.meter);
|
|
@@ -121,31 +126,77 @@ export function createBaseMiddleware(config: MiddlewareConfig) {
|
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
/**
|
|
124
|
-
* Create CORS middleware
|
|
129
|
+
* Create CORS middleware with lazy config resolution.
|
|
130
|
+
*
|
|
131
|
+
* Handles Cross-Origin Resource Sharing (CORS) headers for API routes.
|
|
132
|
+
* Config is resolved at request time, allowing it to be set via createApp().
|
|
133
|
+
* Static options passed here take precedence over app config.
|
|
134
|
+
*
|
|
135
|
+
* Default behavior:
|
|
136
|
+
* - Reflects the request origin (allows any origin)
|
|
137
|
+
* - Allows common headers: Content-Type, Authorization, Accept, Origin, X-Requested-With
|
|
138
|
+
* - Allows all standard HTTP methods
|
|
139
|
+
* - Enables credentials
|
|
140
|
+
* - Sets max-age to 600 seconds (10 minutes)
|
|
141
|
+
*
|
|
142
|
+
* @param staticOptions - Optional static CORS options that override app config
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* // Use with default settings
|
|
147
|
+
* app.use('/api/*', createCorsMiddleware());
|
|
148
|
+
*
|
|
149
|
+
* // Or configure via createApp
|
|
150
|
+
* const app = await createApp({
|
|
151
|
+
* cors: {
|
|
152
|
+
* origin: 'https://example.com',
|
|
153
|
+
* allowHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header'],
|
|
154
|
+
* maxAge: 3600,
|
|
155
|
+
* }
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* // Or pass static options directly (overrides app config)
|
|
159
|
+
* app.use('/api/*', createCorsMiddleware({
|
|
160
|
+
* origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
161
|
+
* credentials: true,
|
|
162
|
+
* }));
|
|
163
|
+
* ```
|
|
125
164
|
*/
|
|
126
|
-
export function createCorsMiddleware(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
export function createCorsMiddleware(staticOptions?: Parameters<typeof cors>[0]) {
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
167
|
+
return createMiddleware<Env<any>>(async (c, next) => {
|
|
168
|
+
// Lazy resolve: merge app config with static options
|
|
169
|
+
const appConfig = getAppConfig();
|
|
170
|
+
const corsOptions = {
|
|
171
|
+
...appConfig?.cors,
|
|
172
|
+
...staticOptions,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const corsMiddleware = cors({
|
|
176
|
+
origin: corsOptions?.origin ?? ((origin: string) => origin),
|
|
177
|
+
allowHeaders: corsOptions?.allowHeaders ?? [
|
|
178
|
+
'Content-Type',
|
|
179
|
+
'Authorization',
|
|
180
|
+
'Accept',
|
|
181
|
+
'Origin',
|
|
182
|
+
'X-Requested-With',
|
|
183
|
+
THREAD_HEADER,
|
|
184
|
+
],
|
|
185
|
+
allowMethods: ['POST', 'GET', 'OPTIONS', 'HEAD', 'PUT', 'DELETE', 'PATCH'],
|
|
186
|
+
exposeHeaders: [
|
|
187
|
+
'Content-Length',
|
|
188
|
+
TOKENS_HEADER,
|
|
189
|
+
DURATION_HEADER,
|
|
190
|
+
THREAD_HEADER,
|
|
191
|
+
SESSION_HEADER,
|
|
192
|
+
DEPLOYMENT_HEADER,
|
|
193
|
+
],
|
|
194
|
+
maxAge: 600,
|
|
195
|
+
credentials: true,
|
|
196
|
+
...(corsOptions ?? {}),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return corsMiddleware(c, next);
|
|
149
200
|
});
|
|
150
201
|
}
|
|
151
202
|
|
|
@@ -154,7 +205,8 @@ export function createCorsMiddleware(corsOptions?: Parameters<typeof cors>[0]) {
|
|
|
154
205
|
* This is the critical middleware that creates AgentContext
|
|
155
206
|
*/
|
|
156
207
|
export function createOtelMiddleware() {
|
|
157
|
-
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
|
+
return createMiddleware<Env<any>>(async (c, next) => {
|
|
158
210
|
// Import providers dynamically to avoid circular deps
|
|
159
211
|
const { getThreadProvider, getSessionProvider } = await import('./_services');
|
|
160
212
|
const WaitUntilHandler = (await import('./_waituntil')).default;
|
|
@@ -188,6 +240,14 @@ export function createOtelMiddleware() {
|
|
|
188
240
|
const deploymentId = runtimeConfig.getDeploymentId();
|
|
189
241
|
const isDevMode = runtimeConfig.isDevMode();
|
|
190
242
|
|
|
243
|
+
internal.info(
|
|
244
|
+
'[session] config: orgId=%s, projectId=%s, deploymentId=%s, isDevMode=%s',
|
|
245
|
+
orgId ?? 'NOT SET (AGENTUITY_CLOUD_ORG_ID)',
|
|
246
|
+
projectId ?? 'NOT SET (AGENTUITY_CLOUD_PROJECT_ID)',
|
|
247
|
+
deploymentId ?? 'none',
|
|
248
|
+
isDevMode
|
|
249
|
+
);
|
|
250
|
+
|
|
191
251
|
if (projectId) traceState = traceState.set('pid', projectId);
|
|
192
252
|
if (orgId) traceState = traceState.set('oid', orgId);
|
|
193
253
|
if (isDevMode) traceState = traceState.set('d', '1');
|
|
@@ -211,17 +271,45 @@ export function createOtelMiddleware() {
|
|
|
211
271
|
c.set('session', session);
|
|
212
272
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
273
|
(c as any).set('waitUntilHandler', handler);
|
|
274
|
+
const agentIds = new Set<string>();
|
|
214
275
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
215
|
-
(c as any).set('agentIds',
|
|
276
|
+
(c as any).set('agentIds', agentIds);
|
|
216
277
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
278
|
(c as any).set('trigger', 'api');
|
|
218
279
|
|
|
280
|
+
// Send session start event (so evalruns can reference this session)
|
|
281
|
+
const sessionEventProvider = getSessionEventProvider();
|
|
282
|
+
const shouldSendSession = !!(orgId && projectId);
|
|
283
|
+
if (shouldSendSession && sessionEventProvider) {
|
|
284
|
+
try {
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
286
|
+
const routeId = (c as any).var?.routeId || '';
|
|
287
|
+
await sessionEventProvider.start({
|
|
288
|
+
id: sessionId,
|
|
289
|
+
threadId: thread.id,
|
|
290
|
+
orgId,
|
|
291
|
+
projectId,
|
|
292
|
+
deploymentId: deploymentId || undefined,
|
|
293
|
+
devmode: isDevMode,
|
|
294
|
+
trigger: 'api',
|
|
295
|
+
routeId,
|
|
296
|
+
environment: runtimeConfig.getEnvironment(),
|
|
297
|
+
url: c.req.path,
|
|
298
|
+
method: c.req.method,
|
|
299
|
+
});
|
|
300
|
+
} catch (_ex) {
|
|
301
|
+
// Silently ignore session start errors - don't block request
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
219
305
|
try {
|
|
220
306
|
await next();
|
|
221
|
-
|
|
222
307
|
// Save session/thread and send events
|
|
308
|
+
internal.info('[session] saving session %s (thread: %s)', sessionId, thread.id);
|
|
223
309
|
await sessionProvider.save(session);
|
|
310
|
+
internal.info('[session] session saved, now saving thread');
|
|
224
311
|
await threadProvider.save(thread);
|
|
312
|
+
internal.info('[session] thread saved');
|
|
225
313
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
226
314
|
} catch (ex) {
|
|
227
315
|
if (ex instanceof Error) {
|
|
@@ -233,6 +321,37 @@ export function createOtelMiddleware() {
|
|
|
233
321
|
});
|
|
234
322
|
throw ex;
|
|
235
323
|
} finally {
|
|
324
|
+
// Send session complete event
|
|
325
|
+
internal.info(
|
|
326
|
+
'[session] shouldSendSession: %s, hasSessionEventProvider: %s',
|
|
327
|
+
shouldSendSession,
|
|
328
|
+
!!sessionEventProvider
|
|
329
|
+
);
|
|
330
|
+
if (shouldSendSession && sessionEventProvider) {
|
|
331
|
+
try {
|
|
332
|
+
const userData = session.serializeUserData();
|
|
333
|
+
internal.info(
|
|
334
|
+
'[session] sending session complete event, userData: %s',
|
|
335
|
+
userData ? `${userData.length} bytes` : 'none'
|
|
336
|
+
);
|
|
337
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
338
|
+
const agentIdsSet = (c as any).get('agentIds') as Set<string> | undefined;
|
|
339
|
+
const agentIds = agentIdsSet ? [...agentIdsSet].filter(Boolean) : undefined;
|
|
340
|
+
internal.info('[session] agentIds: %o', agentIds);
|
|
341
|
+
await sessionEventProvider.complete({
|
|
342
|
+
id: sessionId,
|
|
343
|
+
threadId: thread.empty() ? null : thread.id,
|
|
344
|
+
statusCode: c.res?.status ?? 200,
|
|
345
|
+
agentIds: agentIds?.length ? agentIds : undefined,
|
|
346
|
+
userData,
|
|
347
|
+
});
|
|
348
|
+
internal.info('[session] session complete event sent');
|
|
349
|
+
} catch (ex) {
|
|
350
|
+
internal.info('[session] session complete event failed: %s', ex);
|
|
351
|
+
// Silently ignore session complete errors - don't block response
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
236
355
|
const headers: Record<string, string> = {};
|
|
237
356
|
propagation.inject(context.active(), headers);
|
|
238
357
|
for (const key of Object.keys(headers)) {
|
|
@@ -250,3 +369,76 @@ export function createOtelMiddleware() {
|
|
|
250
369
|
});
|
|
251
370
|
});
|
|
252
371
|
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Create compression middleware with lazy config resolution.
|
|
375
|
+
*
|
|
376
|
+
* Compresses response bodies using gzip or deflate based on the Accept-Encoding header.
|
|
377
|
+
* Config is resolved at request time, allowing it to be set via createApp().
|
|
378
|
+
*
|
|
379
|
+
* @param staticConfig - Optional static config that overrides app config
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```typescript
|
|
383
|
+
* // Use with default settings
|
|
384
|
+
* app.use('*', createCompressionMiddleware());
|
|
385
|
+
*
|
|
386
|
+
* // Or configure via createApp
|
|
387
|
+
* const app = await createApp({
|
|
388
|
+
* compression: {
|
|
389
|
+
* threshold: 2048,
|
|
390
|
+
* }
|
|
391
|
+
* });
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
export function createCompressionMiddleware(staticConfig?: CompressionConfig) {
|
|
395
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
396
|
+
return createMiddleware<Env<any>>(async (c, next) => {
|
|
397
|
+
// Lazy resolve: merge app config with static config
|
|
398
|
+
const appConfig = getAppConfig();
|
|
399
|
+
const appCompressionConfig = appConfig?.compression;
|
|
400
|
+
|
|
401
|
+
// Check if compression is explicitly disabled
|
|
402
|
+
if (appCompressionConfig === false || staticConfig?.enabled === false) {
|
|
403
|
+
return next();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Merge configs: static config takes precedence over app config
|
|
407
|
+
const config: CompressionConfig = {
|
|
408
|
+
...(typeof appCompressionConfig === 'object' ? appCompressionConfig : {}),
|
|
409
|
+
...staticConfig,
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const { enabled = true, threshold = 1024, filter, honoOptions } = config;
|
|
413
|
+
|
|
414
|
+
// Skip if explicitly disabled
|
|
415
|
+
if (!enabled) {
|
|
416
|
+
return next();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Skip WebSocket upgrade requests
|
|
420
|
+
const upgrade = c.req.header('upgrade');
|
|
421
|
+
if (upgrade && upgrade.toLowerCase() === 'websocket') {
|
|
422
|
+
return next();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Skip if no Accept-Encoding header
|
|
426
|
+
const acceptEncoding = c.req.header('accept-encoding');
|
|
427
|
+
if (!acceptEncoding) {
|
|
428
|
+
return next();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check custom filter
|
|
432
|
+
if (filter && !filter(c)) {
|
|
433
|
+
return next();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Create and run the Hono compress middleware
|
|
437
|
+
const compressMiddleware = compress({
|
|
438
|
+
threshold,
|
|
439
|
+
...honoOptions,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await compressMiddleware(c, next);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type Logger,
|
|
14
14
|
StructuredError,
|
|
15
15
|
} from '@agentuity/core';
|
|
16
|
+
import { internal } from '../../logger/internal';
|
|
16
17
|
|
|
17
18
|
const EvalRunResponseError = StructuredError('EvalRunResponseError');
|
|
18
19
|
|
|
@@ -38,12 +39,22 @@ export class HTTPEvalRunEventProvider implements EvalRunEventProvider {
|
|
|
38
39
|
async start(event: EvalRunStartEvent): Promise<void> {
|
|
39
40
|
const endpoint = '/evalrun/2025-03-17';
|
|
40
41
|
const fullUrl = `${this.baseUrl}${endpoint}`;
|
|
41
|
-
this.logger.debug('[EVALRUN HTTP] Sending eval run start event: %s', event.id);
|
|
42
|
-
this.logger.debug('[EVALRUN HTTP] URL: %s %s', 'POST', fullUrl);
|
|
43
|
-
this.logger.debug('[EVALRUN HTTP] Base URL: %s', this.baseUrl);
|
|
44
42
|
|
|
45
43
|
const payload = { ...event, timestamp: Date.now() };
|
|
46
|
-
|
|
44
|
+
|
|
45
|
+
// Log full payload using internal logger
|
|
46
|
+
internal.info('[EVALRUN HTTP] ========== START PAYLOAD ==========');
|
|
47
|
+
internal.info('[EVALRUN HTTP] id: %s', payload.id);
|
|
48
|
+
internal.info('[EVALRUN HTTP] evalId: %s', payload.evalId);
|
|
49
|
+
internal.info('[EVALRUN HTTP] evalIdentifier: %s', payload.evalIdentifier);
|
|
50
|
+
internal.info('[EVALRUN HTTP] sessionId: %s', payload.sessionId);
|
|
51
|
+
internal.info('[EVALRUN HTTP] orgId: %s', payload.orgId);
|
|
52
|
+
internal.info('[EVALRUN HTTP] projectId: %s', payload.projectId);
|
|
53
|
+
internal.info('[EVALRUN HTTP] devmode: %s', payload.devmode);
|
|
54
|
+
internal.info('[EVALRUN HTTP] deploymentId: %s', payload.deploymentId);
|
|
55
|
+
internal.info('[EVALRUN HTTP] spanId: %s', payload.spanId);
|
|
56
|
+
internal.info('[EVALRUN HTTP] URL: POST %s', fullUrl);
|
|
57
|
+
internal.info('[EVALRUN HTTP] ============================================');
|
|
47
58
|
|
|
48
59
|
try {
|
|
49
60
|
const resp = await this.apiClient.post(
|
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
VectorSearchResultWithDocument,
|
|
9
9
|
VectorSearchParams,
|
|
10
10
|
VectorSearchResult,
|
|
11
|
+
VectorNamespaceStats,
|
|
12
|
+
VectorNamespaceStatsWithSamples,
|
|
11
13
|
} from '@agentuity/core';
|
|
12
14
|
import { randomUUID } from 'node:crypto';
|
|
13
15
|
import { simpleEmbedding, cosineSimilarity, now } from './_util';
|
|
@@ -271,4 +273,162 @@ export class LocalVectorStorage implements VectorStorage {
|
|
|
271
273
|
const { count } = query.get(this.#projectPath, name) as { count: number };
|
|
272
274
|
return count > 0;
|
|
273
275
|
}
|
|
276
|
+
|
|
277
|
+
async getStats(name: string): Promise<VectorNamespaceStatsWithSamples> {
|
|
278
|
+
if (!name?.trim()) {
|
|
279
|
+
throw new Error('Vector storage name is required');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const countQuery = this.#db.query(`
|
|
283
|
+
SELECT COUNT(*) as count,
|
|
284
|
+
MIN(created_at) as created_at, MAX(updated_at) as last_used
|
|
285
|
+
FROM vector_storage
|
|
286
|
+
WHERE project_path = ? AND name = ?
|
|
287
|
+
`);
|
|
288
|
+
|
|
289
|
+
const stats = countQuery.get(this.#projectPath, name) as {
|
|
290
|
+
count: number;
|
|
291
|
+
created_at: number | null;
|
|
292
|
+
last_used: number | null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (stats.count === 0) {
|
|
296
|
+
return { sum: 0, count: 0 };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const sampleQuery = this.#db.query(`
|
|
300
|
+
SELECT key, embedding, document, metadata, created_at, updated_at
|
|
301
|
+
FROM vector_storage
|
|
302
|
+
WHERE project_path = ? AND name = ?
|
|
303
|
+
LIMIT 20
|
|
304
|
+
`);
|
|
305
|
+
|
|
306
|
+
const samples = sampleQuery.all(this.#projectPath, name) as Array<{
|
|
307
|
+
key: string;
|
|
308
|
+
embedding: string;
|
|
309
|
+
document: string | null;
|
|
310
|
+
metadata: string | null;
|
|
311
|
+
created_at: number;
|
|
312
|
+
updated_at: number;
|
|
313
|
+
}>;
|
|
314
|
+
|
|
315
|
+
const encoder = new TextEncoder();
|
|
316
|
+
let totalSum = 0;
|
|
317
|
+
const sampledResults: VectorNamespaceStatsWithSamples['sampledResults'] = {};
|
|
318
|
+
for (const sample of samples) {
|
|
319
|
+
const embeddingBytes = encoder.encode(sample.embedding).length;
|
|
320
|
+
const documentBytes = sample.document ? encoder.encode(sample.document).length : 0;
|
|
321
|
+
const size = embeddingBytes + documentBytes;
|
|
322
|
+
totalSum += size;
|
|
323
|
+
sampledResults![sample.key] = {
|
|
324
|
+
embedding: JSON.parse(sample.embedding),
|
|
325
|
+
document: sample.document || undefined,
|
|
326
|
+
size,
|
|
327
|
+
metadata: sample.metadata ? JSON.parse(sample.metadata) : undefined,
|
|
328
|
+
firstUsed: sample.created_at,
|
|
329
|
+
lastUsed: sample.updated_at,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Estimate total size based on sampled average if we have more records than samples
|
|
334
|
+
const estimatedSum =
|
|
335
|
+
stats.count <= samples.length
|
|
336
|
+
? totalSum
|
|
337
|
+
: Math.round((totalSum / samples.length) * stats.count);
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
sum: estimatedSum,
|
|
341
|
+
count: stats.count,
|
|
342
|
+
createdAt: stats.created_at || undefined,
|
|
343
|
+
lastUsed: stats.last_used || undefined,
|
|
344
|
+
sampledResults,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async getAllStats(): Promise<Record<string, VectorNamespaceStats>> {
|
|
349
|
+
const query = this.#db.query(`
|
|
350
|
+
SELECT name, embedding, document
|
|
351
|
+
FROM vector_storage
|
|
352
|
+
WHERE project_path = ?
|
|
353
|
+
`);
|
|
354
|
+
|
|
355
|
+
const rows = query.all(this.#projectPath) as Array<{
|
|
356
|
+
name: string;
|
|
357
|
+
embedding: string;
|
|
358
|
+
document: string | null;
|
|
359
|
+
}>;
|
|
360
|
+
|
|
361
|
+
const encoder = new TextEncoder();
|
|
362
|
+
const namespaceStats = new Map<
|
|
363
|
+
string,
|
|
364
|
+
{ sum: number; count: number; createdAt?: number; lastUsed?: number }
|
|
365
|
+
>();
|
|
366
|
+
|
|
367
|
+
for (const row of rows) {
|
|
368
|
+
const embeddingBytes = encoder.encode(row.embedding).length;
|
|
369
|
+
const documentBytes = row.document ? encoder.encode(row.document).length : 0;
|
|
370
|
+
const size = embeddingBytes + documentBytes;
|
|
371
|
+
|
|
372
|
+
const existing = namespaceStats.get(row.name);
|
|
373
|
+
if (existing) {
|
|
374
|
+
existing.sum += size;
|
|
375
|
+
existing.count += 1;
|
|
376
|
+
} else {
|
|
377
|
+
namespaceStats.set(row.name, { sum: size, count: 1 });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Get timestamps in a separate query
|
|
382
|
+
const timestampQuery = this.#db.query(`
|
|
383
|
+
SELECT name, MIN(created_at) as created_at, MAX(updated_at) as last_used
|
|
384
|
+
FROM vector_storage
|
|
385
|
+
WHERE project_path = ?
|
|
386
|
+
GROUP BY name
|
|
387
|
+
`);
|
|
388
|
+
|
|
389
|
+
const timestamps = timestampQuery.all(this.#projectPath) as Array<{
|
|
390
|
+
name: string;
|
|
391
|
+
created_at: number | null;
|
|
392
|
+
last_used: number | null;
|
|
393
|
+
}>;
|
|
394
|
+
|
|
395
|
+
for (const ts of timestamps) {
|
|
396
|
+
const stats = namespaceStats.get(ts.name);
|
|
397
|
+
if (stats) {
|
|
398
|
+
stats.createdAt = ts.created_at || undefined;
|
|
399
|
+
stats.lastUsed = ts.last_used || undefined;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const results: Record<string, VectorNamespaceStats> = {};
|
|
404
|
+
for (const [name, stats] of namespaceStats) {
|
|
405
|
+
results[name] = stats;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return results;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async getNamespaces(): Promise<string[]> {
|
|
412
|
+
const query = this.#db.query(`
|
|
413
|
+
SELECT DISTINCT name
|
|
414
|
+
FROM vector_storage
|
|
415
|
+
WHERE project_path = ?
|
|
416
|
+
`);
|
|
417
|
+
|
|
418
|
+
const rows = query.all(this.#projectPath) as Array<{ name: string }>;
|
|
419
|
+
return rows.map((row) => row.name);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async deleteNamespace(name: string): Promise<void> {
|
|
423
|
+
if (!name?.trim()) {
|
|
424
|
+
throw new Error('Vector storage name is required');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const stmt = this.#db.prepare(`
|
|
428
|
+
DELETE FROM vector_storage
|
|
429
|
+
WHERE project_path = ? AND name = ?
|
|
430
|
+
`);
|
|
431
|
+
|
|
432
|
+
stmt.run(this.#projectPath, name);
|
|
433
|
+
}
|
|
274
434
|
}
|