@hashgraphonline/conversational-agent 0.1.204 ā 0.1.206
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/cjs/base-agent.d.ts +6 -0
- package/dist/cjs/context/ReferenceContextManager.d.ts +84 -0
- package/dist/cjs/context/ReferenceResponseProcessor.d.ts +76 -0
- package/dist/cjs/conversational-agent.d.ts +34 -0
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/langchain-agent.d.ts +1 -0
- package/dist/cjs/mcp/ContentProcessor.d.ts +37 -0
- package/dist/cjs/mcp/MCPClientManager.d.ts +19 -1
- package/dist/cjs/memory/ContentStorage.d.ts +205 -0
- package/dist/cjs/memory/MemoryWindow.d.ts +114 -0
- package/dist/cjs/memory/ReferenceIdGenerator.d.ts +45 -0
- package/dist/cjs/memory/SmartMemoryManager.d.ts +201 -0
- package/dist/cjs/memory/TokenCounter.d.ts +61 -0
- package/dist/cjs/memory/index.d.ts +7 -0
- package/dist/cjs/plugins/hbar-transfer/TransferHbarTool.d.ts +18 -0
- package/dist/cjs/services/ContentStoreManager.d.ts +54 -0
- package/dist/cjs/types/content-reference.d.ts +213 -0
- package/dist/cjs/types/index.d.ts +4 -0
- package/dist/esm/index12.js +15 -2
- package/dist/esm/index12.js.map +1 -1
- package/dist/esm/index14.js +119 -95
- package/dist/esm/index14.js.map +1 -1
- package/dist/esm/index15.js +159 -114
- package/dist/esm/index15.js.map +1 -1
- package/dist/esm/index16.js +122 -81
- package/dist/esm/index16.js.map +1 -1
- package/dist/esm/index17.js +236 -0
- package/dist/esm/index17.js.map +1 -0
- package/dist/esm/index18.js +95 -0
- package/dist/esm/index18.js.map +1 -0
- package/dist/esm/index19.js +663 -0
- package/dist/esm/index19.js.map +1 -0
- package/dist/esm/index2.js +3 -1
- package/dist/esm/index2.js.map +1 -1
- package/dist/esm/index20.js +233 -0
- package/dist/esm/index20.js.map +1 -0
- package/dist/esm/index21.js +182 -0
- package/dist/esm/index21.js.map +1 -0
- package/dist/esm/index22.js +126 -0
- package/dist/esm/index22.js.map +1 -0
- package/dist/esm/index23.js +68 -0
- package/dist/esm/index23.js.map +1 -0
- package/dist/esm/index24.js +38 -0
- package/dist/esm/index24.js.map +1 -0
- package/dist/esm/index6.js +143 -84
- package/dist/esm/index6.js.map +1 -1
- package/dist/esm/index7.js.map +1 -1
- package/dist/esm/index8.js +69 -5
- package/dist/esm/index8.js.map +1 -1
- package/dist/types/base-agent.d.ts +6 -0
- package/dist/types/context/ReferenceContextManager.d.ts +84 -0
- package/dist/types/context/ReferenceResponseProcessor.d.ts +76 -0
- package/dist/types/conversational-agent.d.ts +34 -0
- package/dist/types/langchain-agent.d.ts +1 -0
- package/dist/types/mcp/ContentProcessor.d.ts +37 -0
- package/dist/types/mcp/MCPClientManager.d.ts +19 -1
- package/dist/types/memory/ContentStorage.d.ts +205 -0
- package/dist/types/memory/MemoryWindow.d.ts +114 -0
- package/dist/types/memory/ReferenceIdGenerator.d.ts +45 -0
- package/dist/types/memory/SmartMemoryManager.d.ts +201 -0
- package/dist/types/memory/TokenCounter.d.ts +61 -0
- package/dist/types/memory/index.d.ts +7 -0
- package/dist/types/plugins/hbar-transfer/TransferHbarTool.d.ts +18 -0
- package/dist/types/services/ContentStoreManager.d.ts +54 -0
- package/dist/types/types/content-reference.d.ts +213 -0
- package/dist/types/types/index.d.ts +4 -0
- package/package.json +30 -26
- package/src/base-agent.ts +6 -0
- package/src/context/ReferenceContextManager.ts +345 -0
- package/src/context/ReferenceResponseProcessor.ts +296 -0
- package/src/conversational-agent.ts +166 -92
- package/src/langchain-agent.ts +89 -2
- package/src/mcp/ContentProcessor.ts +317 -0
- package/src/mcp/MCPClientManager.ts +61 -1
- package/src/mcp/adapters/langchain.ts +9 -4
- package/src/memory/ContentStorage.ts +954 -0
- package/src/memory/MemoryWindow.ts +247 -0
- package/src/memory/ReferenceIdGenerator.ts +84 -0
- package/src/memory/SmartMemoryManager.ts +323 -0
- package/src/memory/TokenCounter.ts +152 -0
- package/src/memory/index.ts +8 -0
- package/src/plugins/hbar-transfer/TransferHbarTool.ts +19 -1
- package/src/plugins/hcs-10/HCS10Plugin.ts +5 -4
- package/src/services/ContentStoreManager.ts +199 -0
- package/src/types/content-reference.ts +281 -0
- package/src/types/index.ts +6 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import type { ContentReference, ReferenceId, ContentReferenceError } from '../types/content-reference';
|
|
2
|
+
import type { ContentStorage } from '../memory/ContentStorage';
|
|
3
|
+
import { Logger } from '@hashgraphonline/standards-sdk';
|
|
4
|
+
|
|
5
|
+
export interface ReferenceContext {
|
|
6
|
+
reference: ContentReference;
|
|
7
|
+
displayedAt: Date;
|
|
8
|
+
lastAccessedAt: Date;
|
|
9
|
+
contextId: string;
|
|
10
|
+
conversationTurn: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ReferenceDisplayOptions {
|
|
14
|
+
maxPreviewLength?: number;
|
|
15
|
+
showMetadata?: boolean;
|
|
16
|
+
showSize?: boolean;
|
|
17
|
+
includeActions?: boolean;
|
|
18
|
+
format?: 'inline' | 'card' | 'compact';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DisplayResult {
|
|
22
|
+
displayText: string;
|
|
23
|
+
hasValidReference: boolean;
|
|
24
|
+
contextId?: string;
|
|
25
|
+
suggestedActions?: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Manages content references within agent conversation context
|
|
30
|
+
* Tracks reference usage, provides display formatting, and handles reference validation
|
|
31
|
+
*/
|
|
32
|
+
export class ReferenceContextManager {
|
|
33
|
+
private activeReferences: Map<ReferenceId, ReferenceContext> = new Map();
|
|
34
|
+
private contentStorage: ContentStorage;
|
|
35
|
+
private logger: Logger;
|
|
36
|
+
private conversationTurn = 0;
|
|
37
|
+
|
|
38
|
+
constructor(contentStorage: ContentStorage, logger: Logger) {
|
|
39
|
+
this.contentStorage = contentStorage;
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Add a reference to the current conversation context
|
|
45
|
+
*/
|
|
46
|
+
addReference(reference: ContentReference): string {
|
|
47
|
+
this.conversationTurn++;
|
|
48
|
+
const contextId = `ctx_${Date.now()}_${reference.referenceId.substring(0, 8)}`;
|
|
49
|
+
|
|
50
|
+
const context: ReferenceContext = {
|
|
51
|
+
reference,
|
|
52
|
+
displayedAt: new Date(),
|
|
53
|
+
lastAccessedAt: new Date(),
|
|
54
|
+
contextId,
|
|
55
|
+
conversationTurn: this.conversationTurn
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.activeReferences.set(reference.referenceId, context);
|
|
59
|
+
|
|
60
|
+
this.logger.debug(`Added reference to conversation context: ${reference.referenceId} (${contextId})`);
|
|
61
|
+
|
|
62
|
+
return contextId;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate display text for a content reference
|
|
67
|
+
*/
|
|
68
|
+
async displayReference(
|
|
69
|
+
reference: ContentReference,
|
|
70
|
+
options: ReferenceDisplayOptions = {}
|
|
71
|
+
): Promise<DisplayResult> {
|
|
72
|
+
const {
|
|
73
|
+
maxPreviewLength = 150,
|
|
74
|
+
showMetadata = true,
|
|
75
|
+
showSize = true,
|
|
76
|
+
includeActions = true,
|
|
77
|
+
format = 'card'
|
|
78
|
+
} = options;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const isValid = await this.contentStorage.hasReference(reference.referenceId);
|
|
82
|
+
if (!isValid) {
|
|
83
|
+
return {
|
|
84
|
+
displayText: this.formatInvalidReference(reference, includeActions),
|
|
85
|
+
hasValidReference: false,
|
|
86
|
+
suggestedActions: ['Request fresh content', 'Use alternative content source']
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const contextId = this.addReference(reference);
|
|
91
|
+
|
|
92
|
+
let displayText = '';
|
|
93
|
+
|
|
94
|
+
switch (format) {
|
|
95
|
+
case 'inline':
|
|
96
|
+
displayText = this.formatInlineReference(reference, maxPreviewLength);
|
|
97
|
+
break;
|
|
98
|
+
case 'compact':
|
|
99
|
+
displayText = this.formatCompactReference(reference, showSize);
|
|
100
|
+
break;
|
|
101
|
+
case 'card':
|
|
102
|
+
default:
|
|
103
|
+
displayText = this.formatCardReference(reference, {
|
|
104
|
+
maxPreviewLength,
|
|
105
|
+
showMetadata,
|
|
106
|
+
showSize,
|
|
107
|
+
includeActions,
|
|
108
|
+
contextId
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
displayText,
|
|
115
|
+
hasValidReference: true,
|
|
116
|
+
contextId
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.logger.error('Error displaying reference:', error);
|
|
120
|
+
return {
|
|
121
|
+
displayText: this.formatErrorReference(reference, error),
|
|
122
|
+
hasValidReference: false,
|
|
123
|
+
suggestedActions: ['Check reference validity', 'Try again', 'Contact administrator']
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get the most recent reference for "inscribe it" commands
|
|
130
|
+
*/
|
|
131
|
+
getMostRecentReference(): ContentReference | null {
|
|
132
|
+
if (this.activeReferences.size === 0) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let mostRecent: ReferenceContext | null = null;
|
|
137
|
+
for (const context of this.activeReferences.values()) {
|
|
138
|
+
if (!mostRecent || context.displayedAt > mostRecent.displayedAt) {
|
|
139
|
+
mostRecent = context;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (mostRecent) {
|
|
144
|
+
mostRecent.lastAccessedAt = new Date();
|
|
145
|
+
return mostRecent.reference;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get reference by context ID
|
|
153
|
+
*/
|
|
154
|
+
getReferenceByContextId(contextId: string): ContentReference | null {
|
|
155
|
+
for (const context of this.activeReferences.values()) {
|
|
156
|
+
if (context.contextId === contextId) {
|
|
157
|
+
context.lastAccessedAt = new Date();
|
|
158
|
+
return context.reference;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Validate all active references and remove invalid ones
|
|
166
|
+
*/
|
|
167
|
+
async validateReferences(): Promise<{ valid: number; invalid: number; removed: ReferenceId[] }> {
|
|
168
|
+
const removed: ReferenceId[] = [];
|
|
169
|
+
let valid = 0;
|
|
170
|
+
let invalid = 0;
|
|
171
|
+
|
|
172
|
+
for (const [referenceId, context] of this.activeReferences.entries()) {
|
|
173
|
+
try {
|
|
174
|
+
const isValid = await this.contentStorage.hasReference(referenceId);
|
|
175
|
+
if (isValid) {
|
|
176
|
+
valid++;
|
|
177
|
+
} else {
|
|
178
|
+
invalid++;
|
|
179
|
+
removed.push(referenceId);
|
|
180
|
+
this.activeReferences.delete(referenceId);
|
|
181
|
+
this.logger.debug(`Removed invalid reference from context: ${referenceId}`);
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
invalid++;
|
|
185
|
+
removed.push(referenceId);
|
|
186
|
+
this.activeReferences.delete(referenceId);
|
|
187
|
+
this.logger.warn(`Error validating reference ${referenceId}:`, error);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { valid, invalid, removed };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Clear old references based on age and usage
|
|
196
|
+
*/
|
|
197
|
+
cleanupOldReferences(maxAgeMs: number = 30 * 60 * 1000): number {
|
|
198
|
+
const cutoffTime = new Date(Date.now() - maxAgeMs);
|
|
199
|
+
const toRemove: ReferenceId[] = [];
|
|
200
|
+
|
|
201
|
+
for (const [referenceId, context] of this.activeReferences.entries()) {
|
|
202
|
+
if (context.lastAccessedAt < cutoffTime) {
|
|
203
|
+
toRemove.push(referenceId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
toRemove.forEach(referenceId => {
|
|
208
|
+
this.activeReferences.delete(referenceId);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (toRemove.length > 0) {
|
|
212
|
+
this.logger.debug(`Cleaned up ${toRemove.length} old references from context`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return toRemove.length;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get current context statistics
|
|
220
|
+
*/
|
|
221
|
+
getContextStats() {
|
|
222
|
+
return {
|
|
223
|
+
activeReferences: this.activeReferences.size,
|
|
224
|
+
conversationTurn: this.conversationTurn,
|
|
225
|
+
oldestReference: this.getOldestReferenceAge(),
|
|
226
|
+
mostRecentReference: this.getMostRecentReferenceAge()
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Clear all references from context
|
|
232
|
+
*/
|
|
233
|
+
clear(): void {
|
|
234
|
+
this.activeReferences.clear();
|
|
235
|
+
this.conversationTurn = 0;
|
|
236
|
+
this.logger.debug('Cleared all references from context');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private formatCardReference(
|
|
240
|
+
reference: ContentReference,
|
|
241
|
+
options: {
|
|
242
|
+
maxPreviewLength: number;
|
|
243
|
+
showMetadata: boolean;
|
|
244
|
+
showSize: boolean;
|
|
245
|
+
includeActions: boolean;
|
|
246
|
+
contextId: string;
|
|
247
|
+
}
|
|
248
|
+
): string {
|
|
249
|
+
const { maxPreviewLength, showMetadata, showSize, includeActions, contextId } = options;
|
|
250
|
+
|
|
251
|
+
let display = `š **Large Content Reference**\n`;
|
|
252
|
+
display += `**Preview:** ${this.truncateText(reference.preview, maxPreviewLength)}\n`;
|
|
253
|
+
|
|
254
|
+
if (showSize) {
|
|
255
|
+
const sizeKB = Math.round(reference.metadata.sizeBytes / 1024);
|
|
256
|
+
display += `**Size:** ${sizeKB}KB`;
|
|
257
|
+
if (reference.metadata.contentType !== 'binary') {
|
|
258
|
+
display += ` (${reference.metadata.contentType})`;
|
|
259
|
+
}
|
|
260
|
+
display += '\n';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (showMetadata) {
|
|
264
|
+
if (reference.metadata.fileName) {
|
|
265
|
+
display += `**File:** ${reference.metadata.fileName}\n`;
|
|
266
|
+
}
|
|
267
|
+
if (reference.metadata.source) {
|
|
268
|
+
display += `**Source:** ${reference.metadata.source}\n`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (includeActions) {
|
|
273
|
+
display += `\nš” You can say "inscribe it" to inscribe this content to the Hedera network.`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
display += `\n\n*Reference ID: ${reference.referenceId.substring(0, 12)}...*`;
|
|
277
|
+
display += `\n*Context: ${contextId}*`;
|
|
278
|
+
|
|
279
|
+
return display;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private formatInlineReference(reference: ContentReference, maxPreviewLength: number): string {
|
|
283
|
+
const sizeKB = Math.round(reference.metadata.sizeBytes / 1024);
|
|
284
|
+
return `š [${sizeKB}KB ${reference.metadata.contentType}] ${this.truncateText(reference.preview, maxPreviewLength)}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private formatCompactReference(reference: ContentReference, showSize: boolean): string {
|
|
288
|
+
const sizeKB = Math.round(reference.metadata.sizeBytes / 1024);
|
|
289
|
+
const sizeText = showSize ? ` (${sizeKB}KB)` : '';
|
|
290
|
+
return `š Referenced content${sizeText}: ${reference.metadata.fileName || 'large content'}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private formatInvalidReference(reference: ContentReference, includeActions: boolean): string {
|
|
294
|
+
let display = `ā **Content Reference Expired**\n`;
|
|
295
|
+
display += `The referenced content is no longer available.\n`;
|
|
296
|
+
display += `**Original:** ${this.truncateText(reference.preview, 100)}\n`;
|
|
297
|
+
|
|
298
|
+
if (includeActions) {
|
|
299
|
+
display += `\nš” Please request fresh content from the original source.`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return display;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private formatErrorReference(reference: ContentReference, error: unknown): string {
|
|
306
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
307
|
+
let display = `ā ļø **Reference Error**\n`;
|
|
308
|
+
display += `Error accessing referenced content: ${errorMsg}\n`;
|
|
309
|
+
display += `**Reference:** ${this.truncateText(reference.preview, 100)}\n`;
|
|
310
|
+
return display;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private truncateText(text: string, maxLength: number): string {
|
|
314
|
+
if (text.length <= maxLength) {
|
|
315
|
+
return text;
|
|
316
|
+
}
|
|
317
|
+
return text.substring(0, maxLength - 3) + '...';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private getOldestReferenceAge(): number | null {
|
|
321
|
+
if (this.activeReferences.size === 0) return null;
|
|
322
|
+
|
|
323
|
+
let oldest: Date | null = null;
|
|
324
|
+
for (const context of this.activeReferences.values()) {
|
|
325
|
+
if (!oldest || context.displayedAt < oldest) {
|
|
326
|
+
oldest = context.displayedAt;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return oldest ? Date.now() - oldest.getTime() : null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private getMostRecentReferenceAge(): number | null {
|
|
334
|
+
if (this.activeReferences.size === 0) return null;
|
|
335
|
+
|
|
336
|
+
let newest: Date | null = null;
|
|
337
|
+
for (const context of this.activeReferences.values()) {
|
|
338
|
+
if (!newest || context.displayedAt > newest) {
|
|
339
|
+
newest = context.displayedAt;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return newest ? Date.now() - newest.getTime() : null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import type { ContentReference, ReferenceId, ContentMetadata } from '../types/content-reference';
|
|
2
|
+
import type { ReferenceContextManager, ReferenceDisplayOptions } from './ReferenceContextManager';
|
|
3
|
+
import { Logger } from '@hashgraphonline/standards-sdk';
|
|
4
|
+
|
|
5
|
+
export interface ResponseProcessingOptions {
|
|
6
|
+
autoDisplayReferences?: boolean;
|
|
7
|
+
displayOptions?: ReferenceDisplayOptions;
|
|
8
|
+
includeReferenceInstructions?: boolean;
|
|
9
|
+
contextualizeReferences?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ProcessedResponse {
|
|
13
|
+
content: string;
|
|
14
|
+
hasReferences: boolean;
|
|
15
|
+
referenceCount: number;
|
|
16
|
+
contextIds: string[];
|
|
17
|
+
suggestedActions: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ReferenceDetectionResult {
|
|
21
|
+
hasReferences: boolean;
|
|
22
|
+
references: Array<{
|
|
23
|
+
reference: ContentReference;
|
|
24
|
+
position: number;
|
|
25
|
+
originalText: string;
|
|
26
|
+
}>;
|
|
27
|
+
plainReferences: Array<{
|
|
28
|
+
referenceId: ReferenceId;
|
|
29
|
+
position: number;
|
|
30
|
+
originalText: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Processes agent responses to detect and display content references
|
|
36
|
+
* Handles both structured references and plain reference IDs
|
|
37
|
+
*/
|
|
38
|
+
export class ReferenceResponseProcessor {
|
|
39
|
+
private contextManager: ReferenceContextManager;
|
|
40
|
+
private logger: Logger;
|
|
41
|
+
|
|
42
|
+
constructor(contextManager: ReferenceContextManager, logger: Logger) {
|
|
43
|
+
this.contextManager = contextManager;
|
|
44
|
+
this.logger = logger;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Process an agent response to handle content references
|
|
49
|
+
*/
|
|
50
|
+
async processResponse(
|
|
51
|
+
responseContent: string,
|
|
52
|
+
options: ResponseProcessingOptions = {}
|
|
53
|
+
): Promise<ProcessedResponse> {
|
|
54
|
+
const {
|
|
55
|
+
autoDisplayReferences = true,
|
|
56
|
+
displayOptions = {},
|
|
57
|
+
includeReferenceInstructions = true,
|
|
58
|
+
contextualizeReferences = true
|
|
59
|
+
} = options;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const detection = this.detectReferences(responseContent);
|
|
63
|
+
|
|
64
|
+
if (!detection.hasReferences) {
|
|
65
|
+
return {
|
|
66
|
+
content: responseContent,
|
|
67
|
+
hasReferences: false,
|
|
68
|
+
referenceCount: 0,
|
|
69
|
+
contextIds: [],
|
|
70
|
+
suggestedActions: []
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let processedContent = responseContent;
|
|
75
|
+
const contextIds: string[] = [];
|
|
76
|
+
const suggestedActions: string[] = [];
|
|
77
|
+
|
|
78
|
+
if (autoDisplayReferences) {
|
|
79
|
+
for (const { reference, position, originalText } of detection.references) {
|
|
80
|
+
const displayResult = await this.contextManager.displayReference(reference, displayOptions);
|
|
81
|
+
|
|
82
|
+
processedContent = processedContent.replace(originalText, displayResult.displayText);
|
|
83
|
+
|
|
84
|
+
if (displayResult.contextId) {
|
|
85
|
+
contextIds.push(displayResult.contextId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (displayResult.suggestedActions) {
|
|
89
|
+
suggestedActions.push(...displayResult.suggestedActions);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const { referenceId, originalText } of detection.plainReferences) {
|
|
94
|
+
try {
|
|
95
|
+
const resolution = await this.contextManager['contentStorage'].resolveReference(referenceId);
|
|
96
|
+
if (resolution.success && resolution.metadata) {
|
|
97
|
+
const referenceMetadata: Pick<ContentMetadata, 'contentType' | 'sizeBytes' | 'source' | 'fileName' | 'mimeType'> = {
|
|
98
|
+
contentType: resolution.metadata.contentType,
|
|
99
|
+
sizeBytes: resolution.metadata.sizeBytes,
|
|
100
|
+
source: resolution.metadata.source
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (resolution.metadata.fileName !== undefined) {
|
|
104
|
+
referenceMetadata.fileName = resolution.metadata.fileName;
|
|
105
|
+
}
|
|
106
|
+
if (resolution.metadata.mimeType !== undefined) {
|
|
107
|
+
referenceMetadata.mimeType = resolution.metadata.mimeType;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const reference: ContentReference = {
|
|
111
|
+
referenceId,
|
|
112
|
+
state: 'active',
|
|
113
|
+
preview: this.createPreviewFromContent(resolution.content!, resolution.metadata.contentType),
|
|
114
|
+
metadata: referenceMetadata,
|
|
115
|
+
createdAt: resolution.metadata.createdAt,
|
|
116
|
+
format: 'ref://{id}' as const
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const displayResult = await this.contextManager.displayReference(reference, displayOptions);
|
|
120
|
+
processedContent = processedContent.replace(originalText, displayResult.displayText);
|
|
121
|
+
|
|
122
|
+
if (displayResult.contextId) {
|
|
123
|
+
contextIds.push(displayResult.contextId);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
processedContent = processedContent.replace(
|
|
127
|
+
originalText,
|
|
128
|
+
`ā Reference unavailable: ${referenceId.substring(0, 12)}...`
|
|
129
|
+
);
|
|
130
|
+
suggestedActions.push('Request fresh content');
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
this.logger.warn(`Failed to resolve plain reference ${referenceId}:`, error);
|
|
134
|
+
processedContent = processedContent.replace(
|
|
135
|
+
originalText,
|
|
136
|
+
`ā ļø Reference error: ${referenceId.substring(0, 12)}...`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (includeReferenceInstructions && (detection.references.length > 0 || detection.plainReferences.length > 0)) {
|
|
143
|
+
if (contextualizeReferences && contextIds.length === 1) {
|
|
144
|
+
processedContent += `\n\nš” To inscribe this content, say "inscribe it" or "inscribe the content".`;
|
|
145
|
+
} else if (contextualizeReferences && contextIds.length > 1) {
|
|
146
|
+
processedContent += `\n\nš” To inscribe any of this content, say "inscribe it" (uses most recent) or specify the reference.`;
|
|
147
|
+
} else {
|
|
148
|
+
processedContent += `\n\nš” Referenced content can be inscribed using the "inscribe it" command.`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
content: processedContent,
|
|
154
|
+
hasReferences: true,
|
|
155
|
+
referenceCount: detection.references.length + detection.plainReferences.length,
|
|
156
|
+
contextIds,
|
|
157
|
+
suggestedActions: [...new Set(suggestedActions)]
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.logger.error('Error processing response references:', error);
|
|
161
|
+
return {
|
|
162
|
+
content: responseContent,
|
|
163
|
+
hasReferences: false,
|
|
164
|
+
referenceCount: 0,
|
|
165
|
+
contextIds: [],
|
|
166
|
+
suggestedActions: ['Check reference system', 'Try again']
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Detect references in response content
|
|
173
|
+
*/
|
|
174
|
+
private detectReferences(content: string): ReferenceDetectionResult {
|
|
175
|
+
const references: Array<{
|
|
176
|
+
reference: ContentReference;
|
|
177
|
+
position: number;
|
|
178
|
+
originalText: string;
|
|
179
|
+
}> = [];
|
|
180
|
+
|
|
181
|
+
const plainReferences: Array<{
|
|
182
|
+
referenceId: ReferenceId;
|
|
183
|
+
position: number;
|
|
184
|
+
originalText: string;
|
|
185
|
+
}> = [];
|
|
186
|
+
|
|
187
|
+
const contentReferenceRegex = /"type":\s*"content_reference"[^}]*"referenceId":\s*"([^"]+)"[^}]*}/g;
|
|
188
|
+
let match;
|
|
189
|
+
|
|
190
|
+
while ((match = contentReferenceRegex.exec(content)) !== null) {
|
|
191
|
+
try {
|
|
192
|
+
const refObject = JSON.parse(match[0]);
|
|
193
|
+
if (refObject.type === 'content_reference' && refObject.referenceId) {
|
|
194
|
+
const reference: ContentReference = {
|
|
195
|
+
referenceId: refObject.referenceId,
|
|
196
|
+
state: refObject.state || 'active',
|
|
197
|
+
preview: refObject.preview || '',
|
|
198
|
+
metadata: refObject.metadata || {},
|
|
199
|
+
createdAt: new Date(refObject.createdAt || Date.now()),
|
|
200
|
+
format: 'ref://{id}' as const
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
references.push({
|
|
204
|
+
reference,
|
|
205
|
+
position: match.index,
|
|
206
|
+
originalText: match[0]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.logger.warn('Failed to parse content reference:', error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const plainRefRegex = /ref:\/\/([A-Za-z0-9_-]{43})|(?:^|\s)([A-Za-z0-9_-]{43})(?=\s|$)/g;
|
|
215
|
+
let plainMatch;
|
|
216
|
+
|
|
217
|
+
while ((plainMatch = plainRefRegex.exec(content)) !== null) {
|
|
218
|
+
const referenceId = plainMatch[1] || plainMatch[2];
|
|
219
|
+
if (referenceId && this.isValidReferenceId(referenceId)) {
|
|
220
|
+
plainReferences.push({
|
|
221
|
+
referenceId,
|
|
222
|
+
position: plainMatch.index,
|
|
223
|
+
originalText: plainMatch[0]
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
hasReferences: references.length > 0 || plainReferences.length > 0,
|
|
230
|
+
references,
|
|
231
|
+
plainReferences
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Create a preview from content buffer
|
|
237
|
+
*/
|
|
238
|
+
private createPreviewFromContent(content: Buffer, contentType: string): string {
|
|
239
|
+
const maxLength = 200;
|
|
240
|
+
let preview = content.toString('utf8', 0, Math.min(content.length, maxLength * 2));
|
|
241
|
+
|
|
242
|
+
if (contentType === 'html') {
|
|
243
|
+
preview = preview.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ');
|
|
244
|
+
} else if (contentType === 'json') {
|
|
245
|
+
try {
|
|
246
|
+
const parsed = JSON.parse(preview);
|
|
247
|
+
preview = JSON.stringify(parsed, null, 0);
|
|
248
|
+
} catch {
|
|
249
|
+
// Keep original if not valid JSON
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
preview = preview.trim();
|
|
254
|
+
if (preview.length > maxLength) {
|
|
255
|
+
preview = preview.substring(0, maxLength) + '...';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return preview || '[Binary content]';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Validate reference ID format
|
|
263
|
+
*/
|
|
264
|
+
private isValidReferenceId(id: string): boolean {
|
|
265
|
+
if (!id || typeof id !== 'string') {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (id.length !== 43) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get reference context statistics
|
|
278
|
+
*/
|
|
279
|
+
getContextStats() {
|
|
280
|
+
return this.contextManager.getContextStats();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Validate all references in context
|
|
285
|
+
*/
|
|
286
|
+
async validateAllReferences() {
|
|
287
|
+
return await this.contextManager.validateReferences();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Clean up old references
|
|
292
|
+
*/
|
|
293
|
+
cleanupOldReferences(maxAgeMs?: number) {
|
|
294
|
+
return this.contextManager.cleanupOldReferences(maxAgeMs);
|
|
295
|
+
}
|
|
296
|
+
}
|