@agentuity/runtime 0.0.95 → 0.0.97
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 +2 -0
- package/dist/workbench.d.ts.map +1 -1
- package/dist/workbench.js +192 -39
- 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 +226 -38
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware factories for Vite-native architecture
|
|
3
|
+
* Extracted from _server.ts to be used by generated entry files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createMiddleware } from 'hono/factory';
|
|
7
|
+
import { cors } from 'hono/cors';
|
|
8
|
+
import type { Env } from './app';
|
|
9
|
+
import type { Logger } from './logger';
|
|
10
|
+
import { generateId } from './session';
|
|
11
|
+
import { runInHTTPContext } from './_context';
|
|
12
|
+
import { DURATION_HEADER, TOKENS_HEADER } from './_tokens';
|
|
13
|
+
import { extractTraceContextFromRequest } from './otel/http';
|
|
14
|
+
import {
|
|
15
|
+
context,
|
|
16
|
+
SpanKind,
|
|
17
|
+
SpanStatusCode,
|
|
18
|
+
trace,
|
|
19
|
+
propagation,
|
|
20
|
+
Meter,
|
|
21
|
+
Tracer,
|
|
22
|
+
} from '@opentelemetry/api';
|
|
23
|
+
import { TraceState } from '@opentelemetry/core';
|
|
24
|
+
import * as runtimeConfig from './_config';
|
|
25
|
+
|
|
26
|
+
const SESSION_HEADER = 'x-session-id';
|
|
27
|
+
const THREAD_HEADER = 'x-thread-id';
|
|
28
|
+
const DEPLOYMENT_HEADER = 'x-deployment';
|
|
29
|
+
|
|
30
|
+
export const AGENT_CONTEXT_PROPERTIES = [
|
|
31
|
+
'logger',
|
|
32
|
+
'tracer',
|
|
33
|
+
'sessionId',
|
|
34
|
+
'kv',
|
|
35
|
+
'stream',
|
|
36
|
+
'vector',
|
|
37
|
+
'state',
|
|
38
|
+
'thread',
|
|
39
|
+
'session',
|
|
40
|
+
'config',
|
|
41
|
+
'app',
|
|
42
|
+
'waitUntil',
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
function installContextPropertyHelpers(c: any): void {
|
|
47
|
+
for (const property of AGENT_CONTEXT_PROPERTIES) {
|
|
48
|
+
if (Object.prototype.hasOwnProperty.call(c, property)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Object.defineProperty(c, property, {
|
|
53
|
+
get() {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`In route handlers, use c.var.${property} instead of c.${property}. ` +
|
|
56
|
+
`The property '${property}' is available on AgentContext (for agent handlers) ` +
|
|
57
|
+
`but must be accessed via c.var in HonoContext (route handlers).`
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
configurable: true,
|
|
61
|
+
enumerable: false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface MiddlewareConfig {
|
|
67
|
+
logger: Logger;
|
|
68
|
+
tracer: Tracer;
|
|
69
|
+
meter: Meter;
|
|
70
|
+
corsOptions?: Parameters<typeof cors>[0];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create base middleware that sets up context variables
|
|
75
|
+
*/
|
|
76
|
+
export function createBaseMiddleware(config: MiddlewareConfig) {
|
|
77
|
+
return createMiddleware<Env>(async (c, next) => {
|
|
78
|
+
c.set('logger', config.logger);
|
|
79
|
+
c.set('tracer', config.tracer);
|
|
80
|
+
c.set('meter', config.meter);
|
|
81
|
+
|
|
82
|
+
// Import services dynamically to avoid circular deps
|
|
83
|
+
const { getServices } = await import('./_services');
|
|
84
|
+
const { getAppState } = await import('./app');
|
|
85
|
+
|
|
86
|
+
c.set('app', getAppState());
|
|
87
|
+
|
|
88
|
+
const services = getServices();
|
|
89
|
+
c.set('kv', services.kv);
|
|
90
|
+
c.set('stream', services.stream);
|
|
91
|
+
c.set('vector', services.vector);
|
|
92
|
+
|
|
93
|
+
installContextPropertyHelpers(c);
|
|
94
|
+
|
|
95
|
+
const isWebSocket = c.req.header('upgrade')?.toLowerCase() === 'websocket';
|
|
96
|
+
const skipLogging = c.req.path.startsWith('/_agentuity/');
|
|
97
|
+
const started = performance.now();
|
|
98
|
+
|
|
99
|
+
if (!skipLogging) {
|
|
100
|
+
config.logger.debug('%s %s started', c.req.method, c.req.path);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await runInHTTPContext(c, next);
|
|
104
|
+
|
|
105
|
+
if (!isWebSocket) {
|
|
106
|
+
const endTime = performance.now();
|
|
107
|
+
const duration = ((endTime - started) / 1000).toFixed(1);
|
|
108
|
+
c.header(DURATION_HEADER, `${duration}s`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!skipLogging && !isWebSocket) {
|
|
112
|
+
config.logger.debug(
|
|
113
|
+
'%s %s completed (%d) in %sms',
|
|
114
|
+
c.req.method,
|
|
115
|
+
c.req.path,
|
|
116
|
+
c.res.status,
|
|
117
|
+
Number(performance.now() - started).toFixed(2)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create CORS middleware
|
|
125
|
+
*/
|
|
126
|
+
export function createCorsMiddleware(corsOptions?: Parameters<typeof cors>[0]) {
|
|
127
|
+
return cors({
|
|
128
|
+
origin: corsOptions?.origin ?? ((origin) => origin),
|
|
129
|
+
allowHeaders: corsOptions?.allowHeaders ?? [
|
|
130
|
+
'Content-Type',
|
|
131
|
+
'Authorization',
|
|
132
|
+
'Accept',
|
|
133
|
+
'Origin',
|
|
134
|
+
'X-Requested-With',
|
|
135
|
+
THREAD_HEADER,
|
|
136
|
+
],
|
|
137
|
+
allowMethods: ['POST', 'GET', 'OPTIONS', 'HEAD', 'PUT', 'DELETE', 'PATCH'],
|
|
138
|
+
exposeHeaders: [
|
|
139
|
+
'Content-Length',
|
|
140
|
+
TOKENS_HEADER,
|
|
141
|
+
DURATION_HEADER,
|
|
142
|
+
THREAD_HEADER,
|
|
143
|
+
SESSION_HEADER,
|
|
144
|
+
DEPLOYMENT_HEADER,
|
|
145
|
+
],
|
|
146
|
+
maxAge: 600,
|
|
147
|
+
credentials: true,
|
|
148
|
+
...(corsOptions ?? {}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create OpenTelemetry middleware for session/thread tracking
|
|
154
|
+
* This is the critical middleware that creates AgentContext
|
|
155
|
+
*/
|
|
156
|
+
export function createOtelMiddleware() {
|
|
157
|
+
return createMiddleware<Env>(async (c, next) => {
|
|
158
|
+
// Import providers dynamically to avoid circular deps
|
|
159
|
+
const { getThreadProvider, getSessionProvider } = await import('./_services');
|
|
160
|
+
const WaitUntilHandler = (await import('./_waituntil')).default;
|
|
161
|
+
|
|
162
|
+
const extractedContext = extractTraceContextFromRequest(c.req.raw);
|
|
163
|
+
const method = c.req.method;
|
|
164
|
+
const url = new URL(c.req.url);
|
|
165
|
+
const threadProvider = getThreadProvider();
|
|
166
|
+
const sessionProvider = getSessionProvider();
|
|
167
|
+
|
|
168
|
+
await context.with(extractedContext, async (): Promise<void> => {
|
|
169
|
+
const tracer = trace.getTracer('http-server');
|
|
170
|
+
await tracer.startActiveSpan(
|
|
171
|
+
`HTTP ${method}`,
|
|
172
|
+
{
|
|
173
|
+
kind: SpanKind.SERVER,
|
|
174
|
+
attributes: {
|
|
175
|
+
'http.method': method,
|
|
176
|
+
'http.host': url.host,
|
|
177
|
+
'http.user_agent': c.req.header('user-agent') || '',
|
|
178
|
+
'http.path': url.pathname,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
async (span): Promise<void> => {
|
|
182
|
+
const sctx = span.spanContext();
|
|
183
|
+
const sessionId = sctx?.traceId ? `sess_${sctx.traceId}` : generateId('sess');
|
|
184
|
+
|
|
185
|
+
let traceState = sctx.traceState ?? new TraceState();
|
|
186
|
+
const projectId = runtimeConfig.getProjectId();
|
|
187
|
+
const orgId = runtimeConfig.getOrganizationId();
|
|
188
|
+
const deploymentId = runtimeConfig.getDeploymentId();
|
|
189
|
+
const isDevMode = runtimeConfig.isDevMode();
|
|
190
|
+
|
|
191
|
+
if (projectId) traceState = traceState.set('pid', projectId);
|
|
192
|
+
if (orgId) traceState = traceState.set('oid', orgId);
|
|
193
|
+
if (isDevMode) traceState = traceState.set('d', '1');
|
|
194
|
+
|
|
195
|
+
// Update the active context with the new trace state
|
|
196
|
+
// Note: SpanContext.traceState is readonly, so we update it by setting the span with a new context
|
|
197
|
+
trace.setSpan(
|
|
198
|
+
context.active(),
|
|
199
|
+
trace.wrapSpanContext({
|
|
200
|
+
...sctx,
|
|
201
|
+
traceState,
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const thread = await threadProvider.restore(c);
|
|
206
|
+
const session = await sessionProvider.restore(thread, sessionId);
|
|
207
|
+
const handler = new WaitUntilHandler(tracer);
|
|
208
|
+
|
|
209
|
+
c.set('sessionId', sessionId);
|
|
210
|
+
c.set('thread', thread);
|
|
211
|
+
c.set('session', session);
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
(c as any).set('waitUntilHandler', handler);
|
|
214
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
215
|
+
(c as any).set('agentIds', new Set<string>());
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
+
(c as any).set('trigger', 'api');
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
await next();
|
|
221
|
+
|
|
222
|
+
// Save session/thread and send events
|
|
223
|
+
await sessionProvider.save(session);
|
|
224
|
+
await threadProvider.save(thread);
|
|
225
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
226
|
+
} catch (ex) {
|
|
227
|
+
if (ex instanceof Error) {
|
|
228
|
+
span.recordException(ex);
|
|
229
|
+
}
|
|
230
|
+
span.setStatus({
|
|
231
|
+
code: SpanStatusCode.ERROR,
|
|
232
|
+
message: (ex as Error).message ?? String(ex),
|
|
233
|
+
});
|
|
234
|
+
throw ex;
|
|
235
|
+
} finally {
|
|
236
|
+
const headers: Record<string, string> = {};
|
|
237
|
+
propagation.inject(context.active(), headers);
|
|
238
|
+
for (const key of Object.keys(headers)) {
|
|
239
|
+
c.header(key, headers[key]);
|
|
240
|
+
}
|
|
241
|
+
const traceId = sctx?.traceId || sessionId.replace(/^sess_/, '');
|
|
242
|
+
c.header(SESSION_HEADER, `sess_${traceId}`);
|
|
243
|
+
if (deploymentId) {
|
|
244
|
+
c.header(DEPLOYMENT_HEADER, deploymentId);
|
|
245
|
+
}
|
|
246
|
+
span.end();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type Env as HonoEnv,
|
|
10
10
|
} from 'hono';
|
|
11
11
|
import { stream as honoStream, streamSSE as honoStreamSSE } from 'hono/streaming';
|
|
12
|
-
import { upgradeWebSocket
|
|
12
|
+
import { upgradeWebSocket } from 'hono/bun';
|
|
13
13
|
import { hash, returnResponse } from './_util';
|
|
14
14
|
import type { Env } from './app';
|
|
15
15
|
import { getAgentAsyncLocalStorage } from './_context';
|
|
@@ -487,6 +487,9 @@ export const createRouter = <E extends Env = Env, S extends Schema = Schema>():
|
|
|
487
487
|
const asyncLocalStorage = getAgentAsyncLocalStorage();
|
|
488
488
|
const capturedContext = asyncLocalStorage.getStore();
|
|
489
489
|
|
|
490
|
+
// Set Content-Type header for streaming response detection by clients
|
|
491
|
+
c.header('Content-Type', 'application/octet-stream');
|
|
492
|
+
|
|
490
493
|
return honoStream(c, async (s: any) => {
|
|
491
494
|
const runInContext = async () => {
|
|
492
495
|
try {
|
|
@@ -527,7 +530,8 @@ export const createRouter = <E extends Env = Env, S extends Schema = Schema>():
|
|
|
527
530
|
handler = args[1];
|
|
528
531
|
}
|
|
529
532
|
|
|
530
|
-
|
|
533
|
+
// Use upgradeWebSocket directly from hono/bun
|
|
534
|
+
const wrapper = upgradeWebSocket((c: Context) => {
|
|
531
535
|
let openHandler: ((event: any) => void | Promise<void>) | undefined;
|
|
532
536
|
let messageHandler: ((event: any) => void | Promise<void>) | undefined;
|
|
533
537
|
let closeHandler: ((event: any) => void | Promise<void>) | undefined;
|
|
@@ -188,12 +188,13 @@ export class LocalVectorStorage implements VectorStorage {
|
|
|
188
188
|
}>;
|
|
189
189
|
|
|
190
190
|
// If no vectors exist, return empty results
|
|
191
|
-
|
|
191
|
+
const row = rows[0];
|
|
192
|
+
if (!row) {
|
|
192
193
|
return [];
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
// Detect dimensionality from first stored vector
|
|
196
|
-
const firstEmbedding = JSON.parse(
|
|
197
|
+
const firstEmbedding = JSON.parse(row.embedding);
|
|
197
198
|
const dimensions = firstEmbedding.length;
|
|
198
199
|
|
|
199
200
|
// Generate query embedding with matching dimensions
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import type { Database } from 'bun:sqlite';
|
|
3
|
+
import type { AppState } from '../../index';
|
|
4
|
+
import type { Env } from '../../app';
|
|
5
|
+
import {
|
|
6
|
+
DefaultThread,
|
|
7
|
+
DefaultThreadIDProvider,
|
|
8
|
+
validateThreadIdOrThrow,
|
|
9
|
+
type Thread,
|
|
10
|
+
type ThreadIDProvider,
|
|
11
|
+
type ThreadProvider,
|
|
12
|
+
} from '../../session';
|
|
13
|
+
import { getLocalDB } from '../local/_db';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Local thread provider with SQLite persistence.
|
|
17
|
+
* Stores thread state in local DB for development and testing.
|
|
18
|
+
* Suitable for local development and testing with persistence across requests.
|
|
19
|
+
*/
|
|
20
|
+
export class LocalThreadProvider implements ThreadProvider {
|
|
21
|
+
private appState: AppState | null = null;
|
|
22
|
+
private threadIDProvider: ThreadIDProvider = new DefaultThreadIDProvider();
|
|
23
|
+
private db: Database | null = null;
|
|
24
|
+
|
|
25
|
+
async initialize(appState: AppState): Promise<void> {
|
|
26
|
+
this.appState = appState;
|
|
27
|
+
this.db = getLocalDB();
|
|
28
|
+
|
|
29
|
+
// Create threads table if it doesn't exist
|
|
30
|
+
this.db.run(`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS threads (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
state TEXT NOT NULL,
|
|
34
|
+
updated_at INTEGER NOT NULL
|
|
35
|
+
)
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setThreadIDProvider(provider: ThreadIDProvider): void {
|
|
40
|
+
this.threadIDProvider = provider;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async restore(ctx: Context<Env>): Promise<Thread> {
|
|
44
|
+
if (this.appState === null || this.db === null) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'LocalThreadProvider.restore called before initialize(): appState/db not set; call initialize(appState) first'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const threadId = await this.threadIDProvider.getThreadId(this.appState, ctx);
|
|
51
|
+
validateThreadIdOrThrow(threadId);
|
|
52
|
+
|
|
53
|
+
// Try to restore state from DB
|
|
54
|
+
const row = this.db
|
|
55
|
+
.query<{ state: string }, [string]>('SELECT state FROM threads WHERE id = ?')
|
|
56
|
+
.get(threadId);
|
|
57
|
+
const initialStateJson = row?.state;
|
|
58
|
+
|
|
59
|
+
// Create thread with restored state
|
|
60
|
+
const thread = new DefaultThread(this, threadId, initialStateJson);
|
|
61
|
+
|
|
62
|
+
// Populate thread state from restored data
|
|
63
|
+
if (initialStateJson) {
|
|
64
|
+
try {
|
|
65
|
+
const data = JSON.parse(initialStateJson);
|
|
66
|
+
for (const [key, value] of Object.entries(data)) {
|
|
67
|
+
thread.state.set(key, value);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Continue with empty state if parsing fails
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return thread;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async save(thread: Thread): Promise<void> {
|
|
78
|
+
if (!this.db || !(thread instanceof DefaultThread)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Only save if state was modified
|
|
83
|
+
if (!thread.isDirty()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const stateJson = thread.getSerializedState();
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
|
|
90
|
+
// Upsert thread state
|
|
91
|
+
this.db.run(
|
|
92
|
+
`INSERT INTO threads (id, state, updated_at) VALUES (?, ?, ?)
|
|
93
|
+
ON CONFLICT(id) DO UPDATE SET state = ?, updated_at = ?`,
|
|
94
|
+
[thread.id, stateJson, now, stateJson, now]
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async destroy(thread: Thread): Promise<void> {
|
|
99
|
+
if (!this.db) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Delete thread from DB
|
|
104
|
+
this.db.run('DELETE FROM threads WHERE id = ?', [thread.id]);
|
|
105
|
+
}
|
|
106
|
+
}
|