@illuma-ai/agents 1.4.0-alpha.4 → 1.4.0-alpha.6

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.
Files changed (52) hide show
  1. package/dist/cjs/content/ArtifactStore.cjs +579 -0
  2. package/dist/cjs/content/ArtifactStore.cjs.map +1 -0
  3. package/dist/cjs/content/ContentStore.cjs +638 -0
  4. package/dist/cjs/content/ContentStore.cjs.map +1 -0
  5. package/dist/cjs/content/contentAnalyzer.cjs +91 -0
  6. package/dist/cjs/content/contentAnalyzer.cjs.map +1 -0
  7. package/dist/cjs/content/index.cjs +20 -0
  8. package/dist/cjs/content/index.cjs.map +1 -0
  9. package/dist/cjs/content/mcpAutoCache.cjs +115 -0
  10. package/dist/cjs/content/mcpAutoCache.cjs.map +1 -0
  11. package/dist/cjs/main.cjs +10 -0
  12. package/dist/cjs/main.cjs.map +1 -1
  13. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs +4 -1
  14. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs.map +1 -1
  15. package/dist/cjs/tools/proxyTool.cjs +7 -5
  16. package/dist/cjs/tools/proxyTool.cjs.map +1 -1
  17. package/dist/esm/content/ArtifactStore.mjs +576 -0
  18. package/dist/esm/content/ArtifactStore.mjs.map +1 -0
  19. package/dist/esm/content/ContentStore.mjs +635 -0
  20. package/dist/esm/content/ContentStore.mjs.map +1 -0
  21. package/dist/esm/content/contentAnalyzer.mjs +87 -0
  22. package/dist/esm/content/contentAnalyzer.mjs.map +1 -0
  23. package/dist/esm/content/index.mjs +5 -0
  24. package/dist/esm/content/index.mjs.map +1 -0
  25. package/dist/esm/content/mcpAutoCache.mjs +111 -0
  26. package/dist/esm/content/mcpAutoCache.mjs.map +1 -0
  27. package/dist/esm/main.mjs +3 -0
  28. package/dist/esm/main.mjs.map +1 -1
  29. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs +4 -1
  30. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs.map +1 -1
  31. package/dist/esm/tools/proxyTool.mjs +7 -5
  32. package/dist/esm/tools/proxyTool.mjs.map +1 -1
  33. package/dist/types/content/ArtifactStore.d.ts +223 -0
  34. package/dist/types/content/ContentStore.d.ts +140 -0
  35. package/dist/types/content/contentAnalyzer.d.ts +38 -0
  36. package/dist/types/content/index.d.ts +24 -0
  37. package/dist/types/content/mcpAutoCache.d.ts +89 -0
  38. package/dist/types/content/types.d.ts +75 -0
  39. package/dist/types/index.d.ts +5 -0
  40. package/dist/types/providers/tools-server/ToolsServerCapabilityProvider.d.ts +14 -0
  41. package/dist/types/tools/proxyTool.d.ts +7 -0
  42. package/package.json +6 -1
  43. package/src/content/ArtifactStore.ts +782 -0
  44. package/src/content/ContentStore.ts +753 -0
  45. package/src/content/contentAnalyzer.ts +105 -0
  46. package/src/content/index.ts +51 -0
  47. package/src/content/mcpAutoCache.ts +185 -0
  48. package/src/content/types.ts +82 -0
  49. package/src/index.ts +19 -0
  50. package/src/providers/__tests__/ToolsServerCapabilityProvider.test.ts +65 -0
  51. package/src/providers/tools-server/ToolsServerCapabilityProvider.ts +21 -0
  52. package/src/tools/proxyTool.ts +25 -5
@@ -0,0 +1,223 @@
1
+ import type Keyv from 'keyv';
2
+ import { ContentStore } from './ContentStore';
3
+ import type { StoreEntry, StoredEntry, ContentMetadata, EditResult, ReadResult, ReadAllResult, SearchMatch } from './types';
4
+ /**
5
+ * Minimal logger surface. Callers inject any winston/pino/console-like
6
+ * logger that honors the four level methods; defaults to a no-op so the
7
+ * library runs without a configured logger.
8
+ */
9
+ export interface Logger {
10
+ debug(...args: unknown[]): void;
11
+ info(...args: unknown[]): void;
12
+ warn(...args: unknown[]): void;
13
+ error(...args: unknown[]): void;
14
+ }
15
+ /**
16
+ * Callback interface for S3 operations.
17
+ * Injected at construction so ArtifactStore stays host-agnostic — the
18
+ * consumer wires this to its own S3 strategy / presigner.
19
+ */
20
+ export interface S3Strategy {
21
+ /** Upload a buffer to S3, return the stored filepath (signed URL or key). */
22
+ saveBuffer(params: {
23
+ userId: string;
24
+ buffer: Buffer;
25
+ fileName: string;
26
+ basePath: string;
27
+ }): Promise<string>;
28
+ /** Download file content from S3 as a readable stream. */
29
+ getFileStream(filePath: string): Promise<NodeJS.ReadableStream>;
30
+ /** Delete a file from S3. Requires userId for ownership validation. */
31
+ deleteFile(userId: string, filePath: string): Promise<void>;
32
+ }
33
+ /**
34
+ * Callback interface for MongoDB File model operations.
35
+ * Injected to keep ArtifactStore decoupled from Mongoose models.
36
+ */
37
+ export interface FileModel {
38
+ /** Create or upsert a File document. */
39
+ createFile(data: Record<string, unknown>, disableTTL: boolean): Promise<Record<string, unknown>>;
40
+ /** Find a single File by filter. */
41
+ findFile(filter: Record<string, unknown>): Promise<Record<string, unknown> | null>;
42
+ /** Find multiple Files by filter. */
43
+ findFiles(filter: Record<string, unknown>): Promise<Record<string, unknown>[]>;
44
+ /** Update a File document (must include file_id). */
45
+ updateFile(data: Record<string, unknown>): Promise<Record<string, unknown> | null>;
46
+ /** Delete a File by file_id. */
47
+ deleteFile(fileId: string): Promise<Record<string, unknown> | null>;
48
+ /**
49
+ * Get the file_ids linked to a conversation via the Conversation.files array.
50
+ * Returns an empty array if the conversation is not found or has no files.
51
+ * This is the primary source of truth for "which files belong to this conversation".
52
+ */
53
+ getConversationFileIds(conversationId: string): Promise<string[]>;
54
+ /**
55
+ * Link file_ids to a conversation via $addToSet on Conversation.files.
56
+ * Idempotent — calling with already-linked file_ids is a no-op.
57
+ * Used after creating a File record to ensure it appears in "Files in Context".
58
+ */
59
+ addFilesToConversation(conversationId: string, fileIds: string[]): Promise<void>;
60
+ }
61
+ /**
62
+ * Sanitize a filename for safe S3 key usage.
63
+ * Replaces non-alphanumeric characters (except . _ - /) with underscores.
64
+ */
65
+ export declare function sanitizeName(name: string): string;
66
+ /**
67
+ * File-backed artifact store extending ContentStore with S3 persistence.
68
+ *
69
+ * Every write immediately persists to Redis (fast cache) AND S3 (permanent store).
70
+ * S3 writes are fire-and-forget async — the agent gets an instant response from Redis.
71
+ * On Redis cache miss, content is transparently restored from S3 via MongoDB lookup.
72
+ *
73
+ * Key structure (consistent across all layers):
74
+ * - Redis: `CONTENT_STORE::{conversationId}::{contentId}`
75
+ * - S3: `artifacts/{conversationId}/{userId}/{contentId}__{name}`
76
+ * - MongoDB: `file_id: "artifact-{contentId}"`, `metadata.contentId: "{contentId}"`
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * import Keyv from 'keyv';
81
+ * import { ArtifactStore, CONTENT_TTL_MS } from '@illuma-ai/agents/content';
82
+ *
83
+ * const cache = new Keyv({ namespace: `content-store::${conversationId}`, ttl: CONTENT_TTL_MS });
84
+ * const store = new ArtifactStore(cache, conversationId, userId, s3Strategy, fileModel, logger);
85
+ * const id = await store.store({ name: 'App.tsx', type: 'text/x-typescript', content: code, source: 'agent' });
86
+ * // Content is in the cache immediately; S3 + the injected FileModel persist in background.
87
+ * const result = await store.readLines(id, 1, 50);
88
+ * ```
89
+ */
90
+ export declare class ArtifactStore extends ContentStore {
91
+ protected conversationId: string;
92
+ protected userId: string;
93
+ protected s3: S3Strategy;
94
+ protected fileModel: FileModel;
95
+ protected logger: Logger;
96
+ constructor(cache: Keyv, conversationId: string, userId: string, s3: S3Strategy, fileModel: FileModel, logger?: Logger);
97
+ /** File ID prefix for MongoDB file_id. Override in subclasses. */
98
+ protected getFileIdPrefix(): string;
99
+ /** Context label stored on MongoDB File records. Override in subclasses. */
100
+ protected getContextLabel(): string;
101
+ /** S3 base path prefix. Override in subclasses. */
102
+ protected getS3BasePath(): string;
103
+ /** Build S3 file name. Override in subclasses. */
104
+ protected getS3FileName(contentId: string, name: string): string;
105
+ /** Build the canonical file_id for a content entry. */
106
+ protected buildFileId(contentId: string): string;
107
+ /**
108
+ * Store new content in Redis and persist to S3 + MongoDB in background.
109
+ * Returns immediately after Redis write — agent doesn't wait for S3.
110
+ *
111
+ * @param entry - The content to store.
112
+ * @returns The generated content ID.
113
+ */
114
+ store(entry: StoreEntry): Promise<string>;
115
+ /**
116
+ * Overwrite content for an existing entry. Updates Redis + syncs to S3.
117
+ *
118
+ * @param contentId - The content entry ID.
119
+ * @param content - New content to write.
120
+ * @throws If content ID is not found in Redis or S3.
121
+ */
122
+ write(contentId: string, content: string): Promise<void>;
123
+ /**
124
+ * Surgical string replacement. Updates Redis + syncs to S3.
125
+ *
126
+ * @param contentId - The content entry ID.
127
+ * @param oldStr - Exact string to find.
128
+ * @param newStr - Replacement string.
129
+ * @returns Edit result with diff and affected line info.
130
+ */
131
+ strReplace(contentId: string, oldStr: string, newStr: string): Promise<EditResult>;
132
+ /**
133
+ * Read lines with S3 fallback. If Redis has expired, loads from S3 first.
134
+ *
135
+ * @param contentId - The content entry ID.
136
+ * @param startLine - First line to read (1-based).
137
+ * @param endLine - Last line to read (inclusive).
138
+ * @returns Read result or null if not found in any layer.
139
+ */
140
+ readLines(contentId: string, startLine?: number, endLine?: number): Promise<ReadResult | null>;
141
+ /**
142
+ * Read full content with S3 fallback. If Redis has expired, loads from S3 first.
143
+ * No line cap — returns raw content for frontend display (e.g., CodeViz).
144
+ *
145
+ * @param contentId - The content entry ID.
146
+ * @returns Raw content with total line/char counts, or null if not found in any layer.
147
+ */
148
+ readAll(contentId: string): Promise<ReadAllResult | null>;
149
+ /**
150
+ * Search with S3 fallback. If Redis has expired, loads from S3 first.
151
+ *
152
+ * @param contentId - The content entry ID.
153
+ * @param pattern - Text or regex pattern to match.
154
+ * @param maxResults - Maximum matches to return.
155
+ * @returns Array of matches or null if not found.
156
+ */
157
+ search(contentId: string, pattern: string, maxResults?: number): Promise<SearchMatch[] | null>;
158
+ /**
159
+ * Get metadata with S3 fallback.
160
+ *
161
+ * @param contentId - The content entry ID.
162
+ * @returns Metadata or null if not found in any layer.
163
+ */
164
+ info(contentId: string): Promise<ContentMetadata | null>;
165
+ /**
166
+ * Delete an artifact from all layers: Redis + S3 + MongoDB.
167
+ *
168
+ * @param contentId - The content entry ID.
169
+ */
170
+ deleteFile(contentId: string): Promise<void>;
171
+ /**
172
+ * List all files in this conversation. Merges Redis index with MongoDB File
173
+ * records found via `Conversation.files` (the single source of truth).
174
+ *
175
+ * Query flow:
176
+ * 1. Redis index — fast cache of recently-accessed content entries (in-memory, no DB hit)
177
+ * 2. Conversation.files — canonical file_id list via `getConversationFileIds()`
178
+ * SCALE: Single indexed `findOne` on `{ conversationId }` — O(1)
179
+ * 3. Backward compat fallback — `File.find({ conversationId, user })` for pre-migration
180
+ * data not yet in `Conversation.files`. Uses index `{ user, conversationId, updatedAt }`.
181
+ * Can be removed once all File records are migrated.
182
+ * 4. Batch fetch — `File.find({ file_id: { $in: mergedIds }, user })` to hydrate full
183
+ * File documents. Uses index `{ file_id, user }`.
184
+ *
185
+ * Deduplication: Redis entries win — if a contentId is already in Redis, the MongoDB
186
+ * record is skipped. Non-artifact files are keyed by `file:{file_id}` to avoid dupes.
187
+ *
188
+ * @returns Array of metadata for all files in this conversation.
189
+ */
190
+ listFiles(): Promise<ContentMetadata[]>;
191
+ /**
192
+ * Persist a new content entry to S3 and create a MongoDB File record.
193
+ * Called in background after Redis store — agent doesn't wait for this.
194
+ *
195
+ * @param contentId - The content entry ID.
196
+ * @param entry - The original store entry with content and metadata.
197
+ */
198
+ protected persistToS3(contentId: string, entry: StoreEntry): Promise<void>;
199
+ /**
200
+ * Sync updated Redis content to S3 (overwrite same key).
201
+ * Called in background after write/edit operations.
202
+ *
203
+ * @param contentId - The content entry ID to sync.
204
+ */
205
+ protected syncToS3(contentId: string): Promise<void>;
206
+ /**
207
+ * Restore content from S3 into Redis on cache miss.
208
+ * Looks up the MongoDB File record to find the S3 path, downloads content,
209
+ * and re-populates the Redis cache with the same key structure.
210
+ *
211
+ * @param contentId - The content entry ID to restore.
212
+ * @returns The restored StoredEntry, or null if not found in S3/MongoDB.
213
+ */
214
+ protected restoreFromS3(contentId: string): Promise<StoredEntry | null>;
215
+ /**
216
+ * Ensure content is loaded into Redis. If not in Redis, attempt S3 restore.
217
+ * Used before write/edit operations that need content to be present.
218
+ *
219
+ * @param contentId - The content entry ID.
220
+ * @throws If content is not found in Redis or S3.
221
+ */
222
+ protected ensureLoaded(contentId: string): Promise<void>;
223
+ }
@@ -0,0 +1,140 @@
1
+ import type Keyv from 'keyv';
2
+ import type { StoreEntry, StoredEntry, ContentMetadata, ReadResult, ReadAllResult, SearchMatch, EditResult } from './types';
3
+ /**
4
+ * Default 3-minute TTL for ephemeral content entries. Resets on every
5
+ * access. Kept short to reduce cache-backing memory pressure — callers
6
+ * that persist to durable storage (see {@link ArtifactStore}) rely on
7
+ * lazy restore from the durable backend on cache miss.
8
+ *
9
+ * Exported so consumers can construct their injected {@link Keyv}
10
+ * instance with a matching TTL without hard-coding the number.
11
+ */
12
+ export declare const CONTENT_TTL_MS = 180000;
13
+ /**
14
+ * Per-conversation content store backed by a caller-provided {@link Keyv}
15
+ * cache (typically Keyv + @keyv/redis, with in-memory fallback).
16
+ *
17
+ * Stores large content (MCP results, artifacts, agent-generated text)
18
+ * outside the LLM context window. Entries inherit the TTL configured on
19
+ * the injected {@link Keyv} instance — {@link CONTENT_TTL_MS} is the
20
+ * recommended default.
21
+ *
22
+ * The caller is responsible for namespacing the Keyv instance per
23
+ * conversation so content_ids don't collide across threads.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import Keyv from 'keyv';
28
+ * const cache = new Keyv({ namespace: `content-store::${conversationId}`, ttl: CONTENT_TTL_MS });
29
+ * const store = new ContentStore(cache);
30
+ * const id = await store.store({ name: 'report.csv', type: 'text/plain', content: csv, source: 'mcp:sharepoint' });
31
+ * const result = await store.readLines(id, 1, 50);
32
+ * ```
33
+ */
34
+ export declare class ContentStore {
35
+ protected cache: Keyv;
36
+ protected indexKey: string;
37
+ /**
38
+ * @param cache - A pre-namespaced {@link Keyv} instance. The store
39
+ * writes both content entries and a per-store `_index` key, so
40
+ * callers MUST namespace the Keyv per conversation to avoid
41
+ * cross-thread collisions.
42
+ */
43
+ constructor(cache: Keyv);
44
+ /**
45
+ * Store new content and return a content ID.
46
+ * @param entry - The content to store with metadata.
47
+ * @returns The generated content ID.
48
+ */
49
+ store(entry: StoreEntry): Promise<string>;
50
+ /**
51
+ * Get metadata for a content entry without loading the full content.
52
+ * @param contentId - The content entry ID.
53
+ * @returns Metadata or null if not found.
54
+ */
55
+ info(contentId: string): Promise<ContentMetadata | null>;
56
+ /**
57
+ * Read lines from a content entry with optional range.
58
+ * Lines are 1-based and inclusive. Returns formatted content with line numbers.
59
+ *
60
+ * @param contentId - The content entry ID.
61
+ * @param startLine - First line to read (1-based, default 1).
62
+ * @param endLine - Last line to read (inclusive, default startLine + DEFAULT_READ_LINES - 1).
63
+ * @returns Read result with formatted content and range info, or null if not found.
64
+ */
65
+ readLines(contentId: string, startLine?: number, endLine?: number): Promise<ReadResult | null>;
66
+ /**
67
+ * Read the full content of an entry without line-number formatting or line caps.
68
+ * Used by API endpoints that serve complete content to the frontend (e.g., CodeViz).
69
+ * Unlike readLines(), this has no MAX_READ_LINES cap and returns raw content.
70
+ *
71
+ * @param contentId - The content entry ID.
72
+ * @returns Raw content with total line/char counts, or null if not found.
73
+ */
74
+ readAll(contentId: string): Promise<ReadAllResult | null>;
75
+ /**
76
+ * Search for a pattern within a content entry.
77
+ * Supports plain text matching and regex patterns.
78
+ *
79
+ * @param contentId - The content entry ID.
80
+ * @param pattern - Text or regex pattern to match.
81
+ * @param maxResults - Maximum matches to return (default MAX_SEARCH_RESULTS).
82
+ * @returns Array of matches with line numbers, or null if content not found.
83
+ */
84
+ search(contentId: string, pattern: string, maxResults?: number): Promise<SearchMatch[] | null>;
85
+ /**
86
+ * Surgical string replacement within a content entry.
87
+ * Fails if old_str is not found or appears more than once (ambiguous).
88
+ *
89
+ * Uses layered matching: exact → line-number-stripped → CRLF-normalized → trailing-whitespace-trimmed.
90
+ * On failure, returns diagnostic context showing nearby content to help the agent self-correct.
91
+ *
92
+ * @param contentId - The content entry ID.
93
+ * @param oldStr - Exact string to find.
94
+ * @param newStr - Replacement string.
95
+ * @returns Edit result with diff and affected line info.
96
+ */
97
+ strReplace(contentId: string, oldStr: string, newStr: string): Promise<EditResult>;
98
+ /**
99
+ * Overwrite content for an existing entry, preserving its name/source/type.
100
+ * @param contentId - The content entry ID.
101
+ * @param content - New content to write.
102
+ * @throws If content ID is not found.
103
+ */
104
+ write(contentId: string, content: string): Promise<void>;
105
+ /**
106
+ * List all content entries in this conversation's store.
107
+ * @returns Array of metadata for all entries.
108
+ */
109
+ list(): Promise<ContentMetadata[]>;
110
+ /**
111
+ * Get the raw content string for a content entry, without line-number formatting.
112
+ * Resets TTL on access. Used by code edit wrapper to retrieve stored code for execution.
113
+ *
114
+ * @param contentId - The content entry ID.
115
+ * @returns Raw content string, or null if not found/expired.
116
+ */
117
+ getRawContent(contentId: string): Promise<string | null>;
118
+ /**
119
+ * Delete a content entry.
120
+ * @param contentId - The content entry ID.
121
+ */
122
+ delete(contentId: string): Promise<void>;
123
+ /**
124
+ * Reset TTL on both a content entry and the index.
125
+ * Called on every access to keep active content alive.
126
+ * @param contentId - The content entry ID.
127
+ * @param stored - The stored entry to re-set (resets TTL via Keyv).
128
+ */
129
+ protected touchEntry(contentId: string, stored: StoredEntry): Promise<void>;
130
+ /**
131
+ * Retrieve the full stored entry (content + metadata) from Redis.
132
+ * Returns null if the entry has expired or doesn't exist.
133
+ */
134
+ protected getStored(contentId: string): Promise<StoredEntry | null>;
135
+ /**
136
+ * Retrieve the conversation's content index from Redis.
137
+ * The index maps content IDs to their metadata.
138
+ */
139
+ protected getIndex(): Promise<Record<string, ContentMetadata>>;
140
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Utilities for measuring, classifying, and previewing content.
3
+ * Used by the content_tool and MCP auto-caching (Phase 2) to decide
4
+ * when content is "large" and how to summarize it for the LLM.
5
+ */
6
+ /** Content size measurements. */
7
+ export interface ContentMeasurement {
8
+ totalChars: number;
9
+ totalLines: number;
10
+ /** True if content exceeds the large-content threshold. */
11
+ isLarge: boolean;
12
+ }
13
+ /** Detected content type. */
14
+ export type ContentType = 'json_array' | 'json_object' | 'text' | 'mixed';
15
+ /**
16
+ * Measure content size and determine if it exceeds the large-content threshold.
17
+ * @param text - The content to measure.
18
+ * @returns Measurement with char count, line count, and large flag.
19
+ */
20
+ export declare function measureContent(text: string): ContentMeasurement;
21
+ /**
22
+ * Detect the structural type of content.
23
+ * @param text - The content to classify.
24
+ * @returns The detected type: 'json_array', 'json_object', 'text', or 'mixed'.
25
+ */
26
+ export declare function detectContentType(text: string): ContentType;
27
+ /**
28
+ * Generate a preview/summary of content for the LLM context.
29
+ * For JSON arrays, shows the first N items. For text, truncates with an ellipsis.
30
+ *
31
+ * @param text - The full content to preview.
32
+ * @param opts - Options controlling preview size.
33
+ * @returns A truncated preview string.
34
+ */
35
+ export declare function generatePreview(text: string, opts?: {
36
+ maxItems?: number;
37
+ maxChars?: number;
38
+ }): string;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @illuma-ai/agents/content — per-conversation content + artifact stores.
3
+ *
4
+ * Host-agnostic primitives for keeping large tool / agent output out of
5
+ * the LLM context window:
6
+ *
7
+ * - {@link ContentStore} — ephemeral per-conversation cache (backed by
8
+ * any caller-provided {@link Keyv} instance; recommended with
9
+ * @keyv/redis for multi-instance deployments).
10
+ * - {@link ArtifactStore} — extends {@link ContentStore} with
11
+ * durable persistence via caller-provided {@link S3Strategy} and
12
+ * {@link FileModel} adapters.
13
+ * - {@link interceptMcpResult} — MCP tool-result auto-caching with
14
+ * gate semantics (no-op when the agent can't dereference
15
+ * `content_id`s).
16
+ * - {@link measureContent} / {@link detectContentType} /
17
+ * {@link generatePreview} — content classifiers shared by
18
+ * consumers that need to decide when to store vs inline.
19
+ */
20
+ export { ContentStore, CONTENT_TTL_MS } from './ContentStore';
21
+ export { ArtifactStore, sanitizeName, type S3Strategy, type FileModel, type Logger, } from './ArtifactStore';
22
+ export { measureContent, detectContentType, generatePreview, type ContentMeasurement, type ContentType, } from './contentAnalyzer';
23
+ export type { StoreEntry, StoredEntry, ContentMetadata, ReadResult, ReadAllResult, SearchMatch, EditResult, } from './types';
24
+ export { interceptMcpResult, extractUiMarkers, buildCachedResponse, type AutoCacheContext, type AutoCacheResult, } from './mcpAutoCache';
@@ -0,0 +1,89 @@
1
+ /**
2
+ * MCP Auto-Caching Interceptor
3
+ *
4
+ * When an MCP tool returns a large text result (>50K chars / ~12.5K tokens),
5
+ * stores it in the caller-provided {@link ContentStore} and returns a
6
+ * compact metadata reference. The LLM then uses a `content_reader` tool
7
+ * (read/search/list/info) to pull relevant pieces of the stored result
8
+ * without burning tokens on the full payload.
9
+ *
10
+ * Gate: callers MUST pass `contentReaderEnabled: true` on the context —
11
+ * otherwise the interceptor returns the original text unchanged, because
12
+ * caching without a reader tool leaves the agent with a content_id it
13
+ * cannot dereference.
14
+ *
15
+ * Design:
16
+ * - Only text content is cached. Images and UI resources pass through.
17
+ * - UI resource markers (\ui{...}) are preserved in the returned text.
18
+ * - Artifacts (second element of the tuple) are never modified.
19
+ * - Cached response is a compact one-liner (~30 tokens) — no preview blob.
20
+ * - If the store write fails, degrades gracefully — returns original text.
21
+ */
22
+ import { ContentStore } from './ContentStore';
23
+ import type { Logger } from './ArtifactStore';
24
+ import type { ContentMeasurement } from './contentAnalyzer';
25
+ /** Context for the auto-cache interceptor. */
26
+ export interface AutoCacheContext {
27
+ /**
28
+ * Pre-constructed {@link ContentStore} instance scoped to the current
29
+ * conversation. Caller owns the underlying cache lifecycle.
30
+ */
31
+ store: ContentStore;
32
+ /** MCP server name (e.g. "sharepoint", "github"). */
33
+ serverName: string;
34
+ /** MCP tool name (e.g. "read_file", "search_code"). */
35
+ toolName: string;
36
+ /**
37
+ * Whether the current agent has `content_reader` available. When false,
38
+ * the interceptor passes the large text through unchanged — caching
39
+ * without a reader tool leaves the agent with a content_id it cannot
40
+ * dereference, which is worse than returning the raw text.
41
+ */
42
+ contentReaderEnabled: boolean;
43
+ /**
44
+ * Optional diagnostic echo. Typically the conversation ID so operators
45
+ * can correlate the log line with upstream traces.
46
+ */
47
+ conversationId?: string;
48
+ /** Optional logger; defaults to silence. */
49
+ logger?: Logger;
50
+ }
51
+ /** Result of the auto-cache interception. */
52
+ export interface AutoCacheResult {
53
+ /** The (possibly modified) text content to return to the LLM. */
54
+ text: string;
55
+ /** Whether the content was cached. */
56
+ cached: boolean;
57
+ /** The content_id if cached. */
58
+ contentId?: string;
59
+ /** Content measurement data. */
60
+ measurement?: ContentMeasurement;
61
+ }
62
+ /**
63
+ * Extract all UI resource markers from text.
64
+ * @param text - The text to scan.
65
+ * @returns Array of marker strings (e.g. ['\\ui{abc123}', '\\ui{def456}'])
66
+ */
67
+ export declare function extractUiMarkers(text: string): string[];
68
+ /**
69
+ * Build a compact metadata reference for the cached content.
70
+ * Keeps token usage minimal (~30 tokens) while giving the LLM all it needs
71
+ * to access the data via content_tool.
72
+ *
73
+ * @param contentId - ContentStore entry ID.
74
+ * @param measurement - Size data.
75
+ * @param toolName - The MCP tool that produced the result.
76
+ * @param uiMarkers - UI markers extracted from the original text.
77
+ */
78
+ export declare function buildCachedResponse(contentId: string, measurement: ContentMeasurement, toolName: string, uiMarkers: string[]): string;
79
+ /**
80
+ * Core auto-cache interceptor for MCP tool results.
81
+ *
82
+ * If the text exceeds the large-content threshold (50K chars), stores it
83
+ * in ContentStore and returns a preview + content_id. Otherwise passes through.
84
+ *
85
+ * @param text - The text content from the MCP tool result.
86
+ * @param context - MCP tool and conversation context.
87
+ * @returns AutoCacheResult with possibly-modified text and caching metadata.
88
+ */
89
+ export declare function interceptMcpResult(text: string, context: AutoCacheContext): Promise<AutoCacheResult>;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Types for the per-conversation content store.
3
+ * Content entries are ephemeral (Redis-backed, 5 min TTL) and used to keep
4
+ * large tool results out of the LLM context window.
5
+ */
6
+ /** Input when storing new content. */
7
+ export interface StoreEntry {
8
+ /** Human-readable name (e.g. "Q1 Sales Report.xlsx") */
9
+ name: string;
10
+ /** MIME-like type: "text/plain", "application/json", "mcp_response", "artifact" */
11
+ type: string;
12
+ /** The raw content string */
13
+ content: string;
14
+ /** Origin identifier: "mcp:sharepoint", "artifact:msg123", "agent", etc. */
15
+ source: string;
16
+ /** Arbitrary extra data attached to the entry */
17
+ metadata?: Record<string, unknown>;
18
+ }
19
+ /** Metadata returned by info() and list() — content is NOT included. */
20
+ export interface ContentMetadata {
21
+ id: string;
22
+ name: string;
23
+ type: string;
24
+ source: string;
25
+ totalLines: number;
26
+ totalChars: number;
27
+ createdAt: number;
28
+ /** MongoDB File.file_id after persistence to S3 (set by ArtifactStore) */
29
+ fileId?: string;
30
+ /** Owner user ID (set by ArtifactStore for S3 path construction) */
31
+ userId?: string;
32
+ /** Conversation scope (set by ArtifactStore for S3 path construction) */
33
+ conversationId?: string;
34
+ /** True when the file exists in MongoDB but hasn't been ingested into ContentStore yet */
35
+ needsIngestion?: boolean;
36
+ }
37
+ /** Result of a readLines() call. */
38
+ export interface ReadResult {
39
+ /** Formatted content with line numbers */
40
+ content: string;
41
+ startLine: number;
42
+ endLine: number;
43
+ totalLines: number;
44
+ totalChars: number;
45
+ /** True if there are more lines beyond endLine */
46
+ truncated: boolean;
47
+ }
48
+ /** Result of a readAll() call — raw content without line-number formatting. */
49
+ export interface ReadAllResult {
50
+ /** Raw content string (no line-number prefixes) */
51
+ content: string;
52
+ totalLines: number;
53
+ totalChars: number;
54
+ }
55
+ /** A single search match within content. */
56
+ export interface SearchMatch {
57
+ lineNumber: number;
58
+ content: string;
59
+ }
60
+ /** Result of a strReplace() edit. */
61
+ export interface EditResult {
62
+ success: boolean;
63
+ /** Human-readable diff snippet */
64
+ diff: string;
65
+ /** Line number where the replacement occurred */
66
+ lineNumber: number;
67
+ /** Number of lines affected by the edit */
68
+ linesAffected: number;
69
+ error?: string;
70
+ }
71
+ /** Internal shape stored in Redis for each content entry. */
72
+ export interface StoredEntry {
73
+ content: string;
74
+ metadata: ContentMetadata;
75
+ }
@@ -20,6 +20,11 @@ export * from './tools/fileSearch';
20
20
  export * from './tools/artifacts';
21
21
  export * from './tools/proxyTool';
22
22
  export * from './providers';
23
+ export { ContentStore, CONTENT_TTL_MS } from './content/ContentStore';
24
+ export { ArtifactStore, sanitizeName } from './content/ArtifactStore';
25
+ export type { S3Strategy, FileModel, Logger as ContentLogger, } from './content/ArtifactStore';
26
+ export { interceptMcpResult, extractUiMarkers, buildCachedResponse, } from './content/mcpAutoCache';
27
+ export type { AutoCacheContext, AutoCacheResult } from './content/mcpAutoCache';
23
28
  export * from './memory';
24
29
  export { MEMORY_FLUSH_SYSTEM_PROMPT } from './prompts/memoryFlushPrompt';
25
30
  export { shouldFlushMemory, runMemoryFlush, } from './graphs/phases/memoryFlushPhase';
@@ -29,6 +29,19 @@ export interface ToolsServerConfig {
29
29
  client?: AxiosInstance;
30
30
  /** Optional proxy override (defaults to process.env.PROXY). */
31
31
  proxy?: string | null;
32
+ /**
33
+ * Optional per-request auth header builder — invoked on every tool
34
+ * invocation (NOT on the manifest fetch, which is service-to-service).
35
+ * When provided, the returned headers are merged into the `/execute`
36
+ * request so the host can pass user-scoped identity (e.g.,
37
+ * `Authorization: Bearer <jwt>`) that tools-server verifies for
38
+ * admin-gated tools.
39
+ *
40
+ * Typical host wiring: mint a short-lived JWT per call carrying the
41
+ * authenticated user's `{ userId, role }` claims; tools-server's
42
+ * `TOOLS_SERVER_JWT_SECRET` validates.
43
+ */
44
+ getExecuteAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
32
45
  }
33
46
  export declare class ToolsServerCapabilityProvider implements CapabilityProvider {
34
47
  private readonly config;
@@ -37,6 +50,7 @@ export declare class ToolsServerCapabilityProvider implements CapabilityProvider
37
50
  private readonly manifestPath;
38
51
  private readonly executePath;
39
52
  private readonly cache;
53
+ private readonly getExecuteAuthHeaders?;
40
54
  constructor(config: ToolsServerConfig);
41
55
  fetchManifest(filter?: CapabilityFilter): Promise<Capability[]>;
42
56
  createRunnables(capabilities: Capability[], credentials: CredentialMap): Promise<StructuredToolInterface[]>;
@@ -38,6 +38,13 @@ export interface ProxyToolOptions {
38
38
  * telemetry, debug logging. Errors in the hook are swallowed.
39
39
  */
40
40
  onExecute?: (ctx: ExecuteCallbackContext) => void;
41
+ /**
42
+ * Optional per-invocation auth header builder. Called on every tool
43
+ * invocation before POSTing; returned headers are merged into the
44
+ * request alongside the base client's headers. Typical use: pass a
45
+ * freshly minted per-user JWT for admin-gated tools.
46
+ */
47
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
41
48
  }
42
49
  export interface ExecuteCallbackContext {
43
50
  capabilityName: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illuma-ai/agents",
3
- "version": "1.4.0-alpha.4",
3
+ "version": "1.4.0-alpha.6",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -24,6 +24,11 @@
24
24
  "import": "./dist/esm/providers/a2a/A2ACapabilityProvider.mjs",
25
25
  "require": "./dist/cjs/providers/a2a/A2ACapabilityProvider.cjs",
26
26
  "types": "./dist/types/providers/a2a/A2ACapabilityProvider.d.ts"
27
+ },
28
+ "./content": {
29
+ "import": "./dist/esm/content/index.mjs",
30
+ "require": "./dist/cjs/content/index.cjs",
31
+ "types": "./dist/types/content/index.d.ts"
27
32
  }
28
33
  },
29
34
  "type": "module",