@agentuity/runtime 0.0.107 → 0.0.109
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/_context.d.ts +2 -1
- package/dist/_context.d.ts.map +1 -1
- package/dist/_context.js +1 -0
- package/dist/_context.js.map +1 -1
- package/dist/_metadata.d.ts.map +1 -1
- package/dist/_metadata.js +7 -0
- package/dist/_metadata.js.map +1 -1
- package/dist/_standalone.d.ts +2 -1
- package/dist/_standalone.d.ts.map +1 -1
- package/dist/_standalone.js +26 -9
- package/dist/_standalone.js.map +1 -1
- package/dist/agent.d.ts +18 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -0
- package/dist/agent.js.map +1 -1
- package/dist/bun-s3-patch.d.ts +13 -2
- package/dist/bun-s3-patch.d.ts.map +1 -1
- package/dist/bun-s3-patch.js +82 -8
- package/dist/bun-s3-patch.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +7 -4
- package/dist/middleware.js.map +1 -1
- package/dist/services/thread/local.d.ts.map +1 -1
- package/dist/services/thread/local.js +106 -25
- package/dist/services/thread/local.js.map +1 -1
- package/dist/session.d.ts +206 -27
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +386 -69
- package/dist/session.js.map +1 -1
- package/dist/workbench.d.ts.map +1 -1
- package/dist/workbench.js +15 -9
- package/dist/workbench.js.map +1 -1
- package/package.json +5 -5
- package/src/_context.ts +2 -1
- package/src/_metadata.ts +11 -0
- package/src/_standalone.ts +27 -10
- package/src/agent.ts +22 -1
- package/src/bun-s3-patch.ts +138 -10
- package/src/index.ts +6 -0
- package/src/middleware.ts +8 -4
- package/src/services/thread/local.ts +119 -30
- package/src/session.ts +599 -90
- package/src/workbench.ts +19 -10
package/src/bun-s3-patch.ts
CHANGED
|
@@ -4,8 +4,19 @@
|
|
|
4
4
|
* Agentuity storage uses virtual-hosted-style URLs (e.g., ag-{id}.t3.storage.dev).
|
|
5
5
|
* Bun's default s3 export uses path-style addressing, causing bucket path mismatch.
|
|
6
6
|
*
|
|
7
|
-
* This module patches Bun.S3Client.prototype
|
|
7
|
+
* This module patches Bun.S3Client.prototype methods to automatically set
|
|
8
8
|
* virtualHostedStyle: true when S3_ENDPOINT matches *.storage.dev
|
|
9
|
+
*
|
|
10
|
+
* Patched methods:
|
|
11
|
+
* - file(path, options?) - S3Options
|
|
12
|
+
* - presign(path, options?) - S3FilePresignOptions
|
|
13
|
+
* - write(path, data, options?) - S3Options
|
|
14
|
+
* - delete(path, options?) - S3Options
|
|
15
|
+
* - exists(path, options?) - S3Options
|
|
16
|
+
* - stat(path, options?) - S3Options
|
|
17
|
+
* - size(path, options?) - S3Options
|
|
18
|
+
* - unlink(path, options?) - S3Options
|
|
19
|
+
* - list(input?, options?) - options type doesn't include virtualHostedStyle but we inject it anyway
|
|
9
20
|
*/
|
|
10
21
|
|
|
11
22
|
const PATCHED_SYMBOL = Symbol.for('agentuity.s3.patched');
|
|
@@ -27,13 +38,23 @@ export function isAgentuityStorageEndpoint(raw: string): boolean {
|
|
|
27
38
|
return host === 'storage.dev' || host.endsWith('.storage.dev');
|
|
28
39
|
}
|
|
29
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Helper to inject virtualHostedStyle into options if not already set
|
|
43
|
+
*/
|
|
44
|
+
function injectVirtualHostedStyle(options?: Record<string, unknown>): Record<string, unknown> {
|
|
45
|
+
if (!options || typeof options.virtualHostedStyle === 'undefined') {
|
|
46
|
+
return { ...options, virtualHostedStyle: true };
|
|
47
|
+
}
|
|
48
|
+
return options;
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
/**
|
|
31
52
|
* Patch Bun's S3Client to automatically use virtualHostedStyle for storage.dev endpoints
|
|
32
53
|
*
|
|
33
54
|
* This function:
|
|
34
55
|
* 1. Checks if we're running in Bun with S3 support
|
|
35
56
|
* 2. Checks if S3_ENDPOINT (or AWS_ENDPOINT) points to *.storage.dev
|
|
36
|
-
* 3. Patches S3Client.prototype
|
|
57
|
+
* 3. Patches S3Client.prototype methods to inject virtualHostedStyle: true
|
|
37
58
|
*
|
|
38
59
|
* Safe to call in non-Bun environments (will no-op).
|
|
39
60
|
* Idempotent (safe to call multiple times).
|
|
@@ -45,6 +66,21 @@ export function patchBunS3ForStorageDev(): void {
|
|
|
45
66
|
S3Client?: {
|
|
46
67
|
prototype: {
|
|
47
68
|
file?: (path: string, options?: Record<string, unknown>) => unknown;
|
|
69
|
+
presign?: (path: string, options?: Record<string, unknown>) => unknown;
|
|
70
|
+
write?: (
|
|
71
|
+
path: string,
|
|
72
|
+
data: unknown,
|
|
73
|
+
options?: Record<string, unknown>
|
|
74
|
+
) => unknown;
|
|
75
|
+
delete?: (path: string, options?: Record<string, unknown>) => unknown;
|
|
76
|
+
exists?: (path: string, options?: Record<string, unknown>) => unknown;
|
|
77
|
+
stat?: (path: string, options?: Record<string, unknown>) => unknown;
|
|
78
|
+
size?: (path: string, options?: Record<string, unknown>) => unknown;
|
|
79
|
+
unlink?: (path: string, options?: Record<string, unknown>) => unknown;
|
|
80
|
+
list?: (
|
|
81
|
+
input?: Record<string, unknown> | null,
|
|
82
|
+
options?: Record<string, unknown>
|
|
83
|
+
) => unknown;
|
|
48
84
|
[PATCHED_SYMBOL]?: boolean;
|
|
49
85
|
};
|
|
50
86
|
};
|
|
@@ -70,22 +106,114 @@ export function patchBunS3ForStorageDev(): void {
|
|
|
70
106
|
return;
|
|
71
107
|
}
|
|
72
108
|
|
|
109
|
+
// Patch file(path, options?)
|
|
73
110
|
const originalFile = S3ClientProto.file!;
|
|
74
|
-
|
|
75
111
|
S3ClientProto.file = function patchedFile(
|
|
76
112
|
this: unknown,
|
|
77
113
|
path: string,
|
|
78
114
|
options?: Record<string, unknown>
|
|
79
115
|
): unknown {
|
|
80
|
-
|
|
116
|
+
return originalFile.call(this, path, injectVirtualHostedStyle(options));
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Patch presign(path, options?)
|
|
120
|
+
if (S3ClientProto.presign) {
|
|
121
|
+
const originalPresign = S3ClientProto.presign;
|
|
122
|
+
S3ClientProto.presign = function patchedPresign(
|
|
123
|
+
this: unknown,
|
|
124
|
+
path: string,
|
|
125
|
+
options?: Record<string, unknown>
|
|
126
|
+
): unknown {
|
|
127
|
+
return originalPresign.call(this, path, injectVirtualHostedStyle(options));
|
|
128
|
+
};
|
|
129
|
+
}
|
|
81
130
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
131
|
+
// Patch write(path, data, options?)
|
|
132
|
+
if (S3ClientProto.write) {
|
|
133
|
+
const originalWrite = S3ClientProto.write;
|
|
134
|
+
S3ClientProto.write = function patchedWrite(
|
|
135
|
+
this: unknown,
|
|
136
|
+
path: string,
|
|
137
|
+
data: unknown,
|
|
138
|
+
options?: Record<string, unknown>
|
|
139
|
+
): unknown {
|
|
140
|
+
return originalWrite.call(this, path, data, injectVirtualHostedStyle(options));
|
|
141
|
+
};
|
|
142
|
+
}
|
|
86
143
|
|
|
87
|
-
|
|
88
|
-
|
|
144
|
+
// Patch delete(path, options?)
|
|
145
|
+
if (S3ClientProto.delete) {
|
|
146
|
+
const originalDelete = S3ClientProto.delete;
|
|
147
|
+
S3ClientProto.delete = function patchedDelete(
|
|
148
|
+
this: unknown,
|
|
149
|
+
path: string,
|
|
150
|
+
options?: Record<string, unknown>
|
|
151
|
+
): unknown {
|
|
152
|
+
return originalDelete.call(this, path, injectVirtualHostedStyle(options));
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Patch exists(path, options?)
|
|
157
|
+
if (S3ClientProto.exists) {
|
|
158
|
+
const originalExists = S3ClientProto.exists;
|
|
159
|
+
S3ClientProto.exists = function patchedExists(
|
|
160
|
+
this: unknown,
|
|
161
|
+
path: string,
|
|
162
|
+
options?: Record<string, unknown>
|
|
163
|
+
): unknown {
|
|
164
|
+
return originalExists.call(this, path, injectVirtualHostedStyle(options));
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Patch stat(path, options?)
|
|
169
|
+
if (S3ClientProto.stat) {
|
|
170
|
+
const originalStat = S3ClientProto.stat;
|
|
171
|
+
S3ClientProto.stat = function patchedStat(
|
|
172
|
+
this: unknown,
|
|
173
|
+
path: string,
|
|
174
|
+
options?: Record<string, unknown>
|
|
175
|
+
): unknown {
|
|
176
|
+
return originalStat.call(this, path, injectVirtualHostedStyle(options));
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Patch size(path, options?)
|
|
181
|
+
if (S3ClientProto.size) {
|
|
182
|
+
const originalSize = S3ClientProto.size;
|
|
183
|
+
S3ClientProto.size = function patchedSize(
|
|
184
|
+
this: unknown,
|
|
185
|
+
path: string,
|
|
186
|
+
options?: Record<string, unknown>
|
|
187
|
+
): unknown {
|
|
188
|
+
return originalSize.call(this, path, injectVirtualHostedStyle(options));
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Patch unlink(path, options?)
|
|
193
|
+
if (S3ClientProto.unlink) {
|
|
194
|
+
const originalUnlink = S3ClientProto.unlink;
|
|
195
|
+
S3ClientProto.unlink = function patchedUnlink(
|
|
196
|
+
this: unknown,
|
|
197
|
+
path: string,
|
|
198
|
+
options?: Record<string, unknown>
|
|
199
|
+
): unknown {
|
|
200
|
+
return originalUnlink.call(this, path, injectVirtualHostedStyle(options));
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Patch list(input?, options?)
|
|
205
|
+
// Note: The TypeScript type for list's options doesn't include virtualHostedStyle,
|
|
206
|
+
// but we inject it anyway as the underlying implementation may still use it
|
|
207
|
+
if (S3ClientProto.list) {
|
|
208
|
+
const originalList = S3ClientProto.list;
|
|
209
|
+
S3ClientProto.list = function patchedList(
|
|
210
|
+
this: unknown,
|
|
211
|
+
input?: Record<string, unknown> | null,
|
|
212
|
+
options?: Record<string, unknown>
|
|
213
|
+
): unknown {
|
|
214
|
+
return originalList.call(this, input, injectVirtualHostedStyle(options));
|
|
215
|
+
};
|
|
216
|
+
}
|
|
89
217
|
|
|
90
218
|
S3ClientProto[PATCHED_SYMBOL] = true;
|
|
91
219
|
}
|
package/src/index.ts
CHANGED
|
@@ -100,6 +100,8 @@ export {
|
|
|
100
100
|
export {
|
|
101
101
|
type ThreadEventName,
|
|
102
102
|
type SessionEventName,
|
|
103
|
+
type ThreadState,
|
|
104
|
+
type MergeOperation,
|
|
103
105
|
type Thread,
|
|
104
106
|
type Session,
|
|
105
107
|
type ThreadIDProvider,
|
|
@@ -108,6 +110,7 @@ export {
|
|
|
108
110
|
generateId,
|
|
109
111
|
DefaultThreadIDProvider,
|
|
110
112
|
DefaultThread,
|
|
113
|
+
LazyThreadState,
|
|
111
114
|
} from './session';
|
|
112
115
|
|
|
113
116
|
// services/thread/local exports
|
|
@@ -166,6 +169,9 @@ export {
|
|
|
166
169
|
CompositeEvalRunEventProvider,
|
|
167
170
|
} from './services/evalrun';
|
|
168
171
|
|
|
172
|
+
// for loading metadata
|
|
173
|
+
export { loadBuildMetadata } from './_metadata';
|
|
174
|
+
|
|
169
175
|
// _services.ts exports
|
|
170
176
|
export { getEvalRunEventProvider, getThreadProvider, getSessionProvider } from './_services';
|
|
171
177
|
|
package/src/middleware.ts
CHANGED
|
@@ -113,6 +113,12 @@ export function createBaseMiddleware(config: MiddlewareConfig) {
|
|
|
113
113
|
const endTime = performance.now();
|
|
114
114
|
const duration = ((endTime - started) / 1000).toFixed(1);
|
|
115
115
|
c.header(DURATION_HEADER, `${duration}s`);
|
|
116
|
+
|
|
117
|
+
// Set deployment header for all routes
|
|
118
|
+
const deploymentId = runtimeConfig.getDeploymentId();
|
|
119
|
+
if (deploymentId) {
|
|
120
|
+
c.header(DEPLOYMENT_HEADER, deploymentId);
|
|
121
|
+
}
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
if (!skipLogging && !isWebSocket) {
|
|
@@ -336,9 +342,10 @@ export function createOtelMiddleware() {
|
|
|
336
342
|
const agentIdsSet = (c as any).get('agentIds') as Set<string> | undefined;
|
|
337
343
|
const agentIds = agentIdsSet ? [...agentIdsSet].filter(Boolean) : undefined;
|
|
338
344
|
internal.info('[session] agentIds: %o', agentIds);
|
|
345
|
+
const isEmpty = await thread.empty();
|
|
339
346
|
await sessionEventProvider.complete({
|
|
340
347
|
id: sessionId,
|
|
341
|
-
threadId:
|
|
348
|
+
threadId: isEmpty ? null : thread.id,
|
|
342
349
|
statusCode: c.res?.status ?? 200,
|
|
343
350
|
agentIds: agentIds?.length ? agentIds : undefined,
|
|
344
351
|
userData,
|
|
@@ -357,9 +364,6 @@ export function createOtelMiddleware() {
|
|
|
357
364
|
}
|
|
358
365
|
const traceId = sctx?.traceId || sessionId.replace(/^sess_/, '');
|
|
359
366
|
c.header(SESSION_HEADER, `sess_${traceId}`);
|
|
360
|
-
if (deploymentId) {
|
|
361
|
-
c.header(DEPLOYMENT_HEADER, deploymentId);
|
|
362
|
-
}
|
|
363
367
|
span.end();
|
|
364
368
|
}
|
|
365
369
|
}
|
|
@@ -51,30 +51,37 @@ export class LocalThreadProvider implements ThreadProvider {
|
|
|
51
51
|
const threadId = await this.threadIDProvider.getThreadId(this.appState, ctx);
|
|
52
52
|
validateThreadIdOrThrow(threadId);
|
|
53
53
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
54
|
+
// Create a restore function for lazy loading
|
|
55
|
+
const restoreFn = async (): Promise<{
|
|
56
|
+
state: Map<string, unknown>;
|
|
57
|
+
metadata: Record<string, unknown>;
|
|
58
|
+
}> => {
|
|
59
|
+
if (!this.db) {
|
|
60
|
+
return { state: new Map(), metadata: {} };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const row = this.db
|
|
64
|
+
.query<{ state: string }, [string]>('SELECT state FROM threads WHERE id = ?')
|
|
65
|
+
.get(threadId);
|
|
66
|
+
|
|
67
|
+
const { flatStateJson, metadata } = parseThreadData(row?.state);
|
|
68
|
+
|
|
69
|
+
const state = new Map<string, unknown>();
|
|
70
|
+
if (flatStateJson) {
|
|
71
|
+
try {
|
|
72
|
+
const data = JSON.parse(flatStateJson);
|
|
73
|
+
for (const [key, value] of Object.entries(data)) {
|
|
74
|
+
state.set(key, value);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Continue with empty state if parsing fails
|
|
71
78
|
}
|
|
72
|
-
} catch {
|
|
73
|
-
// Continue with empty state if parsing fails
|
|
74
79
|
}
|
|
75
|
-
}
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
return { state, metadata: metadata || {} };
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return new DefaultThread(this, threadId, restoreFn);
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
async save(thread: Thread): Promise<void> {
|
|
@@ -82,20 +89,102 @@ export class LocalThreadProvider implements ThreadProvider {
|
|
|
82
89
|
return;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
if (
|
|
92
|
+
const saveMode = thread.getSaveMode();
|
|
93
|
+
if (saveMode === 'none') {
|
|
87
94
|
return;
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
const stateJson = thread.getSerializedState();
|
|
91
97
|
const now = Date.now();
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
if (saveMode === 'merge') {
|
|
100
|
+
// For merge, we need to load existing state, apply operations, then save
|
|
101
|
+
const operations = thread.getPendingOperations();
|
|
102
|
+
const metadata = thread.getMetadataForSave();
|
|
103
|
+
|
|
104
|
+
// Load existing state
|
|
105
|
+
const row = this.db
|
|
106
|
+
.query<{ state: string }, [string]>('SELECT state FROM threads WHERE id = ?')
|
|
107
|
+
.get(thread.id);
|
|
108
|
+
|
|
109
|
+
const { flatStateJson, metadata: existingMetadata } = parseThreadData(row?.state);
|
|
110
|
+
|
|
111
|
+
const state: Record<string, unknown> = {};
|
|
112
|
+
if (flatStateJson) {
|
|
113
|
+
try {
|
|
114
|
+
Object.assign(state, JSON.parse(flatStateJson));
|
|
115
|
+
} catch {
|
|
116
|
+
// Continue with empty state if parsing fails
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Apply operations
|
|
121
|
+
for (const op of operations) {
|
|
122
|
+
switch (op.op) {
|
|
123
|
+
case 'clear':
|
|
124
|
+
for (const key of Object.keys(state)) {
|
|
125
|
+
delete state[key];
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
case 'set':
|
|
129
|
+
if (op.key !== undefined) {
|
|
130
|
+
state[op.key] = op.value;
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case 'delete':
|
|
134
|
+
if (op.key !== undefined) {
|
|
135
|
+
delete state[op.key];
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case 'push':
|
|
139
|
+
if (op.key !== undefined) {
|
|
140
|
+
const existing = state[op.key];
|
|
141
|
+
let arr: unknown[];
|
|
142
|
+
if (Array.isArray(existing)) {
|
|
143
|
+
existing.push(op.value);
|
|
144
|
+
arr = existing;
|
|
145
|
+
} else if (existing === undefined) {
|
|
146
|
+
arr = [op.value];
|
|
147
|
+
state[op.key] = arr;
|
|
148
|
+
} else {
|
|
149
|
+
// If non-array, silently skip
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// Apply maxRecords limit
|
|
153
|
+
if (op.maxRecords !== undefined && arr.length > op.maxRecords) {
|
|
154
|
+
state[op.key] = arr.slice(arr.length - op.maxRecords);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Build final data
|
|
162
|
+
const finalMetadata = metadata || existingMetadata || {};
|
|
163
|
+
const hasState = Object.keys(state).length > 0;
|
|
164
|
+
const hasMetadata = Object.keys(finalMetadata).length > 0;
|
|
165
|
+
|
|
166
|
+
let stateJson = '';
|
|
167
|
+
if (hasState || hasMetadata) {
|
|
168
|
+
const data: { state?: Record<string, unknown>; metadata?: Record<string, unknown> } = {};
|
|
169
|
+
if (hasState) data.state = state;
|
|
170
|
+
if (hasMetadata) data.metadata = finalMetadata;
|
|
171
|
+
stateJson = JSON.stringify(data);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.db.run(
|
|
175
|
+
`INSERT INTO threads (id, state, updated_at) VALUES (?, ?, ?)
|
|
176
|
+
ON CONFLICT(id) DO UPDATE SET state = ?, updated_at = ?`,
|
|
177
|
+
[thread.id, stateJson, now, stateJson, now]
|
|
178
|
+
);
|
|
179
|
+
} else {
|
|
180
|
+
// Full save
|
|
181
|
+
const stateJson = await thread.getSerializedState();
|
|
182
|
+
this.db.run(
|
|
183
|
+
`INSERT INTO threads (id, state, updated_at) VALUES (?, ?, ?)
|
|
184
|
+
ON CONFLICT(id) DO UPDATE SET state = ?, updated_at = ?`,
|
|
185
|
+
[thread.id, stateJson, now, stateJson, now]
|
|
186
|
+
);
|
|
187
|
+
}
|
|
99
188
|
}
|
|
100
189
|
|
|
101
190
|
async destroy(thread: Thread): Promise<void> {
|