@augmentcode/auggie-sdk 0.1.11 → 0.1.13

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/README.md CHANGED
@@ -312,9 +312,32 @@ const client = await Auggie.create({
312
312
  allowIndexing: true,
313
313
  apiKey: "your-api-key", // Optional
314
314
  apiUrl: "https://your-tenant.api.augmentcode.com", // Required when apiKey is provided
315
+ cliArgs: ["--verbose", "--timeout=30"], // Optional: Additional CLI arguments
315
316
  });
316
317
  ```
317
318
 
319
+ ### Advanced CLI Arguments
320
+
321
+ The `cliArgs` option allows you to pass additional command-line arguments directly to the Auggie CLI process. These arguments are appended after all SDK-generated flags.
322
+
323
+ ```typescript
324
+ // Pass custom CLI flags
325
+ const client = await Auggie.create({
326
+ model: "sonnet4.5",
327
+ cliArgs: ["--verbose", "--log-level=debug"]
328
+ });
329
+
330
+ // Arguments can use either format:
331
+ // 1. Separate flag and value: ["--timeout", "30"]
332
+ // 2. Combined with equals: ["--timeout=30"]
333
+ const client2 = await Auggie.create({
334
+ model: "sonnet4.5",
335
+ cliArgs: ["--timeout=60", "--retries", "3"]
336
+ });
337
+ ```
338
+
339
+ Use this option for advanced configurations or experimental flags not exposed through standard SDK options. Refer to `auggie --help` for available CLI flags.
340
+
318
341
  ## Context Modes
319
342
 
320
343
  The SDK provides two context modes for codebase indexing and search.
@@ -334,11 +357,27 @@ import { DirectContext } from "@augmentcode/auggie-sdk";
334
357
  // 3. Or pass credentials directly in options
335
358
  const context = await DirectContext.create();
336
359
 
337
- // Add files to index
360
+ // Add files to index (waits indefinitely by default)
338
361
  await context.addToIndex([
339
362
  { path: "src/main.ts", contents: "..." }
340
363
  ]);
341
364
 
365
+ // Optionally set a timeout
366
+ await context.addToIndex(files, {
367
+ timeout: 10 * 60 * 1000 // 10 minutes in milliseconds
368
+ });
369
+
370
+ // Track progress during indexing
371
+ await context.addToIndex(files, {
372
+ onProgress: (progress) => {
373
+ console.log(`[${progress.stage}] Uploaded: ${progress.uploaded}/${progress.total}, Indexed: ${progress.indexed}/${progress.total}`);
374
+ if (progress.stage === 'uploading') {
375
+ console.log(` Current file: ${progress.currentFile}`);
376
+ console.log(` Bytes uploaded: ${progress.bytesUploaded}`);
377
+ }
378
+ }
379
+ });
380
+
342
381
  // Search - returns results in a formatted string ready for LLM use or display
343
382
  const results = await context.search("authentication logic");
344
383
  console.log(results);
@@ -352,8 +391,47 @@ console.log(answer);
352
391
 
353
392
  // Export state to avoid re-indexing
354
393
  await context.exportToFile("/tmp/state.json");
394
+
395
+ // For search-only use cases, export lightweight state
396
+ await context.exportToFile("/tmp/search-state.json", { mode: "search-only" });
355
397
  ```
356
398
 
399
+ #### Search-Only Export (Optimized for Storage)
400
+
401
+ For applications that only need to search an existing index (without adding or removing files), you can export a lightweight search-only state that excludes the large blobs array:
402
+
403
+ ```typescript
404
+ import { DirectContext } from "@augmentcode/auggie-sdk";
405
+
406
+ // Create and index files normally
407
+ const context = await DirectContext.create();
408
+ await context.addToIndex(files);
409
+
410
+ // Export search-only state (much smaller for large codebases)
411
+ const searchState = context.export({ mode: "search-only" });
412
+ // or
413
+ await context.exportToFile("search-state.json", { mode: "search-only" });
414
+
415
+ // Later, import the search-only state
416
+ const searchContext = await DirectContext.import(searchState);
417
+ // or
418
+ const searchContext = await DirectContext.importFromFile("search-state.json");
419
+
420
+ // Search operations work normally
421
+ const results = await searchContext.search("authentication logic");
422
+
423
+ // But indexing operations will throw errors
424
+ // await searchContext.addToIndex(files); // ❌ Error: requires full state
425
+ // await searchContext.removeFromIndex(paths); // ❌ Error: requires full state
426
+ // searchContext.getIndexedPaths(); // ❌ Error: requires full state
427
+ ```
428
+
429
+ **When to use search-only export:**
430
+ - ✅ Search-only applications (e.g., documentation search, code Q&A)
431
+ - ✅ Distributed systems where indexing happens centrally
432
+ - ✅ Reducing index size for large codebases
433
+ - ❌ Applications that need to add/remove files from the index
434
+
357
435
  ### FileSystem Context
358
436
 
359
437
  **⚠️ Requires Local Auggie Installation**
@@ -403,6 +481,7 @@ Creates and initializes a new Auggie instance. This method automatically connect
403
481
  - `apiKey?: string` - API key for authentication (optional, sets AUGMENT_API_TOKEN)
404
482
  - `apiUrl?: string` - API URL for authentication (optional, sets AUGMENT_API_URL)
405
483
  - `excludedTools?: ToolIdentifier[]` - List of tool identifiers to exclude/disable (optional)
484
+ - `cliArgs?: string[]` - Additional command-line arguments to pass to the Auggie CLI process (optional, default: [])
406
485
 
407
486
  When `tools` (or `toolsMap`) is provided, an MCP server is automatically started and connected to the session.
408
487
 
@@ -39,6 +39,33 @@ type AuggieOptions = {
39
39
  * ```
40
40
  */
41
41
  excludedTools?: ToolIdentifier[];
42
+ /**
43
+ * Additional command-line arguments to pass to the Auggie CLI process.
44
+ * These arguments are appended to the CLI command when spawning the process.
45
+ *
46
+ * Use this option to pass custom or advanced CLI flags that are not
47
+ * directly exposed through other AuggieOptions properties.
48
+ *
49
+ * Arguments can be provided as:
50
+ * - Flag-only arguments: `"--verbose"`
51
+ * - Flag with value (separate strings): `"--timeout", "30"`
52
+ * - Flag with value (single string): `"--timeout=30"`
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Pass a single flag
57
+ * cliArgs: ["--verbose"]
58
+ *
59
+ * // Pass multiple arguments with values
60
+ * cliArgs: ["--timeout", "30", "--retries", "3"]
61
+ *
62
+ * // Pass flags with equals-style values
63
+ * cliArgs: ["--timeout=30", "--retries=3"]
64
+ * ```
65
+ *
66
+ * @default []
67
+ */
68
+ cliArgs?: string[];
42
69
  };
43
70
  /**
44
71
  * ACP Client for connecting to Auggie as an ACP server
@@ -65,6 +92,7 @@ declare class Auggie {
65
92
  private readonly apiUrl;
66
93
  private readonly rules;
67
94
  private readonly excludedTools;
95
+ private readonly cliArgs;
68
96
  private constructor();
69
97
  /**
70
98
  * Create and initialize a new Auggie instance
@@ -24,6 +24,7 @@ class Auggie {
24
24
  apiUrl;
25
25
  rules;
26
26
  excludedTools;
27
+ cliArgs;
27
28
  constructor({
28
29
  auggiePath = "auggie",
29
30
  workspaceRoot,
@@ -32,7 +33,8 @@ class Auggie {
32
33
  apiKey,
33
34
  apiUrl = process.env.AUGMENT_API_URL || "https://api.augmentcode.com",
34
35
  rules,
35
- excludedTools
36
+ excludedTools,
37
+ cliArgs = []
36
38
  } = {}) {
37
39
  this.auggiePath = auggiePath;
38
40
  this.workspaceRoot = workspaceRoot;
@@ -42,6 +44,7 @@ class Auggie {
42
44
  this.apiUrl = apiUrl;
43
45
  this.rules = rules;
44
46
  this.excludedTools = excludedTools;
47
+ this.cliArgs = cliArgs;
45
48
  }
46
49
  /**
47
50
  * Create and initialize a new Auggie instance
@@ -81,6 +84,9 @@ class Auggie {
81
84
  args.push("--remove-tool", tool);
82
85
  }
83
86
  }
87
+ if (this.cliArgs.length > 0) {
88
+ args.push(...this.cliArgs);
89
+ }
84
90
  const pathParts = this.auggiePath.trim().split(Auggie.PATH_SPLIT_REGEX);
85
91
  if (pathParts.length === 0 || !pathParts[0]) {
86
92
  throw new Error("Invalid auggiePath: cannot be empty");
@@ -1,4 +1,4 @@
1
- import { DirectContextOptions, DirectContextState, File, IndexingResult } from './types.js';
1
+ import { DirectContextOptions, DirectContextState, File, AddToIndexOptions, IndexingResult, IndexingProgress, ExportOptions } from './types.js';
2
2
 
3
3
  /**
4
4
  * Direct Context - API-based indexing with import/export state
@@ -43,6 +43,26 @@ import { DirectContextOptions, DirectContextState, File, IndexingResult } from '
43
43
  * await context.search("query"); // files are now indexed
44
44
  * ```
45
45
  *
46
+ * **Option 3: Set a timeout**
47
+ * ```typescript
48
+ * // By default, waits indefinitely. Set a timeout if needed:
49
+ * await context.addToIndex(files, { timeout: 10 * 60 * 1000 }); // 10 minutes
50
+ * // or
51
+ * await context.waitForIndexing(10 * 60 * 1000);
52
+ * ```
53
+ *
54
+ * **Option 4: Track progress during indexing**
55
+ * ```typescript
56
+ * await context.addToIndex(files, {
57
+ * onProgress: (progress) => {
58
+ * console.log(`[${progress.stage}] Uploaded: ${progress.uploaded}/${progress.total}, Indexed: ${progress.indexed}/${progress.total}`);
59
+ * if (progress.stage === 'uploading') {
60
+ * console.log(` Bytes uploaded: ${progress.bytesUploaded}`);
61
+ * }
62
+ * }
63
+ * });
64
+ * ```
65
+ *
46
66
  * Note: `search()` and `searchAndAsk()` do not wait for indexing automatically.
47
67
  *
48
68
  * ## State Persistence
@@ -99,6 +119,7 @@ declare class DirectContext {
99
119
  private checkpointId?;
100
120
  private pendingAdded;
101
121
  private pendingDeleted;
122
+ private isSearchOnly;
102
123
  private readonly mutex;
103
124
  private readonly pollingMutex;
104
125
  /**
@@ -140,12 +161,18 @@ declare class DirectContext {
140
161
  * Log a debug message if debug mode is enabled
141
162
  */
142
163
  private log;
164
+ /**
165
+ * Create a progress context for an operation
166
+ */
167
+ private createProgressContext;
143
168
  /**
144
169
  * Create a checkpoint if threshold is reached
145
170
  */
146
171
  private maybeCheckpoint;
147
172
  /**
148
173
  * Get the list of indexed file paths
174
+ *
175
+ * @throws Error if the context was imported from search-only state
149
176
  */
150
177
  getIndexedPaths(): string[];
151
178
  /**
@@ -158,11 +185,12 @@ declare class DirectContext {
158
185
  * @param files - Array of files to add to the index
159
186
  * @param options - Optional configuration
160
187
  * @param options.waitForIndexing - If true (default), waits for the newly added files to be indexed before returning
188
+ * @param options.timeout - Timeout in milliseconds for indexing operation (default: undefined = no timeout)
189
+ * @param options.onProgress - Optional callback to receive progress updates
161
190
  * @returns Result indicating which files were newly uploaded vs already uploaded
191
+ * @throws Error if the context was imported from search-only state
162
192
  */
163
- addToIndex(files: File[], options?: {
164
- waitForIndexing?: boolean;
165
- }): Promise<IndexingResult>;
193
+ addToIndex(files: File[], options?: AddToIndexOptions): Promise<IndexingResult>;
166
194
  /**
167
195
  * Find blobs that are missing or not yet indexed on the backend (detailed result)
168
196
  * Batches requests in chunks of 1000
@@ -195,6 +223,8 @@ declare class DirectContext {
195
223
  private doAddToIndex;
196
224
  /**
197
225
  * Remove paths from the index
226
+ *
227
+ * @throws Error if the context was imported from search-only state
198
228
  */
199
229
  removeFromIndex(paths: string[]): Promise<void>;
200
230
  /**
@@ -214,6 +244,10 @@ declare class DirectContext {
214
244
  *
215
245
  * This method is serialized via pollingMutex to prevent unbounded concurrent
216
246
  * polling requests to the backend.
247
+ *
248
+ * @param blobNames - Array of blob names to wait for
249
+ * @param timeoutMs - Timeout in milliseconds (default: undefined = no timeout)
250
+ * @param ctx - Progress context for reporting
217
251
  */
218
252
  private waitForSpecificBlobs;
219
253
  /**
@@ -222,10 +256,12 @@ declare class DirectContext {
222
256
  * This method polls the backend until all files that have been added to the index
223
257
  * are confirmed to be indexed and searchable.
224
258
  *
259
+ * @param timeout - Timeout in milliseconds (default: undefined = no timeout)
260
+ * @param onProgress - Optional callback to receive progress updates
225
261
  * @returns Promise that resolves when all files are indexed
226
- * @throws Error if indexing times out (default: 10 minutes)
262
+ * @throws Error if indexing times out (only when timeout is specified)
227
263
  */
228
- waitForIndexing(): Promise<void>;
264
+ waitForIndexing(timeout?: number, onProgress?: (progress: IndexingProgress) => void): Promise<void>;
229
265
  /**
230
266
  * Search the codebase using natural language and return formatted results.
231
267
  *
@@ -272,16 +308,36 @@ declare class DirectContext {
272
308
  searchAndAsk(searchQuery: string, prompt?: string): Promise<string>;
273
309
  /**
274
310
  * Export the current state to a JSON object
311
+ *
312
+ * @param options Export options
313
+ * @param options.mode Export mode - 'full' (default) includes all blob information,
314
+ * 'search-only' excludes blobs array for minimal storage
315
+ * @returns The exported state object
316
+ *
317
+ * @example
318
+ * ```typescript
319
+ * // Full export (default) - supports all operations
320
+ * const fullState = context.export();
321
+ * const fullState = context.export({ mode: 'full' });
322
+ *
323
+ * // Search-only export - much smaller, only supports search operations
324
+ * const searchState = context.export({ mode: 'search-only' });
325
+ * ```
275
326
  */
276
- export(): DirectContextState;
327
+ export(options?: ExportOptions): DirectContextState;
277
328
  /**
278
329
  * Internal method to import state from a JSON object
279
330
  */
280
331
  private doImport;
281
332
  /**
282
333
  * Export state to a file (Node.js only)
334
+ *
335
+ * @param filePath Path to save the state file
336
+ * @param options Export options
337
+ * @param options.mode Export mode - 'full' (default) includes all blob information,
338
+ * 'search-only' excludes blobs array for minimal storage
283
339
  */
284
- exportToFile(filePath: string): Promise<void>;
340
+ exportToFile(filePath: string, options?: ExportOptions): Promise<void>;
285
341
  }
286
342
 
287
343
  export { DirectContext };
@@ -47,6 +47,8 @@ class DirectContext {
47
47
  checkpointId;
48
48
  pendingAdded = /* @__PURE__ */ new Set();
49
49
  pendingDeleted = /* @__PURE__ */ new Set();
50
+ // Track if this instance was imported from search-only state
51
+ isSearchOnly = false;
50
52
  // Mutex for serializing state-modifying operations (upload, checkpoint, etc.)
51
53
  mutex = new Mutex();
52
54
  // Mutex for serializing polling operations to prevent unbounded concurrent requests
@@ -118,6 +120,12 @@ class DirectContext {
118
120
  console.log(`[DirectContext] ${message}`);
119
121
  }
120
122
  }
123
+ /**
124
+ * Create a progress context for an operation
125
+ */
126
+ createProgressContext(onProgress, total, startedAt) {
127
+ return { onProgress, total, startedAt, uploaded: 0, indexed: 0 };
128
+ }
121
129
  /**
122
130
  * Create a checkpoint if threshold is reached
123
131
  */
@@ -147,8 +155,15 @@ class DirectContext {
147
155
  }
148
156
  /**
149
157
  * Get the list of indexed file paths
158
+ *
159
+ * @throws Error if the context was imported from search-only state
150
160
  */
151
161
  getIndexedPaths() {
162
+ if (this.isSearchOnly) {
163
+ throw new Error(
164
+ "Cannot call getIndexedPaths() on a context imported from search-only state. This operation requires full state with blob information. Import from a full state export instead."
165
+ );
166
+ }
152
167
  return Array.from(this.blobMap.keys());
153
168
  }
154
169
  /**
@@ -161,16 +176,30 @@ class DirectContext {
161
176
  * @param files - Array of files to add to the index
162
177
  * @param options - Optional configuration
163
178
  * @param options.waitForIndexing - If true (default), waits for the newly added files to be indexed before returning
179
+ * @param options.timeout - Timeout in milliseconds for indexing operation (default: undefined = no timeout)
180
+ * @param options.onProgress - Optional callback to receive progress updates
164
181
  * @returns Result indicating which files were newly uploaded vs already uploaded
182
+ * @throws Error if the context was imported from search-only state
165
183
  */
166
184
  async addToIndex(files, options) {
185
+ if (this.isSearchOnly) {
186
+ throw new Error(
187
+ "Cannot call addToIndex() on a context imported from search-only state. This operation requires full state with blob information for deduplication. Import from a full state export instead, or create a new context with DirectContext.create()."
188
+ );
189
+ }
167
190
  const waitForIndexing = options?.waitForIndexing ?? true;
191
+ const timeout = options?.timeout;
192
+ const ctx = this.createProgressContext(
193
+ options?.onProgress,
194
+ files.length,
195
+ /* @__PURE__ */ new Date()
196
+ );
168
197
  const result = await this.mutex.runExclusive(
169
- () => this.doAddToIndex(files)
198
+ () => this.doAddToIndex(files, ctx)
170
199
  );
171
200
  if (waitForIndexing && result.newlyUploaded.length > 0) {
172
201
  const newlyUploadedBlobNames = result.newlyUploaded.map((path) => this.blobMap.get(path)).filter((blobName) => blobName !== void 0);
173
- await this.waitForSpecificBlobs(newlyUploadedBlobNames);
202
+ await this.waitForSpecificBlobs(newlyUploadedBlobNames, timeout, ctx);
174
203
  }
175
204
  return result;
176
205
  }
@@ -199,7 +228,7 @@ class DirectContext {
199
228
  * Upload files in batches respecting backend limits
200
229
  * Returns a map of client-computed blob IDs to backend-returned blob IDs
201
230
  */
202
- async batchUploadFiles(filesToUpload) {
231
+ async batchUploadFiles(filesToUpload, ctx) {
203
232
  const batches = [];
204
233
  let currentBatch = [];
205
234
  let currentBatchSize = 0;
@@ -222,6 +251,8 @@ class DirectContext {
222
251
  batches.push(currentBatch);
223
252
  }
224
253
  const blobIdMap = /* @__PURE__ */ new Map();
254
+ let filesProcessed = 0;
255
+ let bytesUploaded = 0;
225
256
  for (const batch of batches) {
226
257
  const result = await this.apiClient.batchUpload(batch);
227
258
  for (const [i, batchItem] of batch.entries()) {
@@ -231,6 +262,23 @@ class DirectContext {
231
262
  blobIdMap.set(clientBlobId, backendBlobId);
232
263
  }
233
264
  }
265
+ filesProcessed += batch.length;
266
+ ctx.uploaded = filesProcessed;
267
+ bytesUploaded += batch.reduce(
268
+ (sum, file) => sum + Buffer.byteLength(file.text, "utf-8"),
269
+ 0
270
+ );
271
+ if (ctx.onProgress) {
272
+ ctx.onProgress({
273
+ stage: "uploading",
274
+ uploaded: ctx.uploaded,
275
+ indexed: ctx.indexed,
276
+ total: ctx.total,
277
+ currentFile: batch.at(-1)?.pathName,
278
+ bytesUploaded,
279
+ startedAt: ctx.startedAt
280
+ });
281
+ }
234
282
  }
235
283
  return blobIdMap;
236
284
  }
@@ -256,7 +304,10 @@ class DirectContext {
256
304
  const newBlobEntries = [];
257
305
  const alreadyUploaded = [];
258
306
  for (const file of files) {
259
- const clientBlobId = this.blobCalculator.calculate(file.path, file.contents);
307
+ const clientBlobId = this.blobCalculator.calculate(
308
+ file.path,
309
+ file.contents
310
+ );
260
311
  if (clientBlobId) {
261
312
  const existingClientBlobId = this.clientBlobMap.get(file.path);
262
313
  if (existingClientBlobId && existingClientBlobId === clientBlobId) {
@@ -292,14 +343,16 @@ class DirectContext {
292
343
  /**
293
344
  * Internal implementation of addToIndex
294
345
  */
295
- async doAddToIndex(files) {
346
+ async doAddToIndex(files, ctx) {
296
347
  this.log(`Adding ${files.length} files to index`);
297
348
  this.validateFileSizes(files);
298
349
  const { filesToUpload, newBlobEntries, alreadyUploaded } = this.prepareBlobsForUpload(files);
299
350
  if (newBlobEntries.length === 0) {
300
351
  return { newlyUploaded: [], alreadyUploaded };
301
352
  }
302
- const blobNamesToCheck = newBlobEntries.map(([_, clientBlobId]) => clientBlobId);
353
+ const blobNamesToCheck = newBlobEntries.map(
354
+ ([_, clientBlobId]) => clientBlobId
355
+ );
303
356
  this.log(`Checking ${blobNamesToCheck.length} blobs with server`);
304
357
  const result = await this.findMissingBlobsDetailed(blobNamesToCheck);
305
358
  const missingBlobSet = /* @__PURE__ */ new Set([
@@ -313,7 +366,10 @@ class DirectContext {
313
366
  const blobIdMap = /* @__PURE__ */ new Map();
314
367
  if (filesToActuallyUpload.length > 0) {
315
368
  this.log(`Uploading ${filesToActuallyUpload.length} files to backend`);
316
- const uploadedBlobIdMap = await this.batchUploadFiles(filesToActuallyUpload);
369
+ const uploadedBlobIdMap = await this.batchUploadFiles(
370
+ filesToActuallyUpload,
371
+ ctx
372
+ );
317
373
  this.log("Upload complete");
318
374
  for (const [clientId, serverId] of uploadedBlobIdMap) {
319
375
  blobIdMap.set(clientId, serverId);
@@ -341,8 +397,15 @@ class DirectContext {
341
397
  }
342
398
  /**
343
399
  * Remove paths from the index
400
+ *
401
+ * @throws Error if the context was imported from search-only state
344
402
  */
345
403
  async removeFromIndex(paths) {
404
+ if (this.isSearchOnly) {
405
+ throw new Error(
406
+ "Cannot call removeFromIndex() on a context imported from search-only state. This operation requires full state with blob information. Import from a full state export instead."
407
+ );
408
+ }
346
409
  return await this.mutex.runExclusive(() => this.doRemoveFromIndex(paths));
347
410
  }
348
411
  /**
@@ -387,20 +450,24 @@ class DirectContext {
387
450
  *
388
451
  * This method is serialized via pollingMutex to prevent unbounded concurrent
389
452
  * polling requests to the backend.
453
+ *
454
+ * @param blobNames - Array of blob names to wait for
455
+ * @param timeoutMs - Timeout in milliseconds (default: undefined = no timeout)
456
+ * @param ctx - Progress context for reporting
390
457
  */
391
- waitForSpecificBlobs(blobNames) {
458
+ waitForSpecificBlobs(blobNames, timeoutMs, ctx) {
392
459
  return this.pollingMutex.runExclusive(async () => {
393
460
  if (blobNames.length === 0) {
394
461
  this.log("No blobs to wait for");
395
462
  return;
396
463
  }
464
+ const timeoutMsg = timeoutMs ? `timeout: ${timeoutMs / 1e3}s` : "no timeout";
397
465
  this.log(
398
- `Waiting for ${blobNames.length} blobs to be indexed on backend`
466
+ `Waiting for ${blobNames.length} blobs to be indexed on backend (${timeoutMsg})`
399
467
  );
400
468
  const initialPollIntervalMs = 3e3;
401
469
  const backoffThresholdMs = 6e4;
402
470
  const backoffPollIntervalMs = 6e4;
403
- const maxWaitTimeMs = 6e5;
404
471
  const startTime = Date.now();
405
472
  while (true) {
406
473
  const result = await this.findMissingBlobsDetailed(blobNames);
@@ -420,6 +487,16 @@ class DirectContext {
420
487
  ...result.unknownBlobNames,
421
488
  ...result.nonindexedBlobNames
422
489
  ];
490
+ ctx.indexed = blobNames.length - stillPending.length;
491
+ if (ctx.onProgress) {
492
+ ctx.onProgress({
493
+ stage: "indexing",
494
+ uploaded: ctx.uploaded,
495
+ indexed: ctx.indexed,
496
+ total: ctx.total,
497
+ startedAt: ctx.startedAt
498
+ });
499
+ }
423
500
  if (stillPending.length === 0) {
424
501
  this.log("All blobs indexed successfully");
425
502
  return;
@@ -428,9 +505,9 @@ class DirectContext {
428
505
  this.log(
429
506
  `Still waiting for ${stillPending.length} blobs to be indexed (elapsed: ${Math.round(elapsedMs / 1e3)}s)`
430
507
  );
431
- if (elapsedMs >= maxWaitTimeMs) {
508
+ if (timeoutMs !== void 0 && elapsedMs >= timeoutMs) {
432
509
  throw new Error(
433
- `Indexing timeout: Backend did not finish indexing within ${maxWaitTimeMs / 1e3} seconds`
510
+ `Indexing timeout: Backend did not finish indexing within ${timeoutMs / 1e3} seconds. You can increase the timeout by passing a 'timeout' option to addToIndex() or waitForIndexing().`
434
511
  );
435
512
  }
436
513
  const pollIntervalMs = elapsedMs < backoffThresholdMs ? initialPollIntervalMs : backoffPollIntervalMs;
@@ -444,12 +521,15 @@ class DirectContext {
444
521
  * This method polls the backend until all files that have been added to the index
445
522
  * are confirmed to be indexed and searchable.
446
523
  *
524
+ * @param timeout - Timeout in milliseconds (default: undefined = no timeout)
525
+ * @param onProgress - Optional callback to receive progress updates
447
526
  * @returns Promise that resolves when all files are indexed
448
- * @throws Error if indexing times out (default: 10 minutes)
527
+ * @throws Error if indexing times out (only when timeout is specified)
449
528
  */
450
- async waitForIndexing() {
529
+ async waitForIndexing(timeout, onProgress) {
451
530
  const blobNames = Array.from(this.blobMap.values());
452
- await this.waitForSpecificBlobs(blobNames);
531
+ const ctx = this.createProgressContext(onProgress, blobNames.length, /* @__PURE__ */ new Date());
532
+ await this.waitForSpecificBlobs(blobNames, timeout, ctx);
453
533
  }
454
534
  /**
455
535
  * Search the codebase using natural language and return formatted results.
@@ -470,11 +550,16 @@ class DirectContext {
470
550
  */
471
551
  async search(query, options) {
472
552
  this.log(`Searching for: "${query}"`);
473
- if (this.blobMap.size === 0) {
553
+ if (!this.isSearchOnly && this.blobMap.size === 0) {
474
554
  throw new Error(
475
555
  "Index not initialized. Add files to index first using addToIndex()."
476
556
  );
477
557
  }
558
+ if (this.isSearchOnly && !this.checkpointId && this.pendingAdded.size === 0) {
559
+ throw new Error(
560
+ "Index not initialized. This search-only context has no checkpoint or pending changes."
561
+ );
562
+ }
478
563
  const blobs = {
479
564
  checkpointId: this.checkpointId,
480
565
  addedBlobs: Array.from(this.pendingAdded).sort(),
@@ -521,8 +606,40 @@ class DirectContext {
521
606
  }
522
607
  /**
523
608
  * Export the current state to a JSON object
609
+ *
610
+ * @param options Export options
611
+ * @param options.mode Export mode - 'full' (default) includes all blob information,
612
+ * 'search-only' excludes blobs array for minimal storage
613
+ * @returns The exported state object
614
+ *
615
+ * @example
616
+ * ```typescript
617
+ * // Full export (default) - supports all operations
618
+ * const fullState = context.export();
619
+ * const fullState = context.export({ mode: 'full' });
620
+ *
621
+ * // Search-only export - much smaller, only supports search operations
622
+ * const searchState = context.export({ mode: 'search-only' });
623
+ * ```
524
624
  */
525
- export() {
625
+ export(options) {
626
+ const mode = options?.mode ?? "full";
627
+ if (mode === "full" && this.isSearchOnly) {
628
+ throw new Error(
629
+ "Cannot export as 'full' from a context imported from search-only state. The blob information required for full state is not available. Use export({ mode: 'search-only' }) instead."
630
+ );
631
+ }
632
+ const addedBlobs = Array.from(this.pendingAdded);
633
+ const deletedBlobs = Array.from(this.pendingDeleted);
634
+ if (mode === "search-only") {
635
+ const state2 = {
636
+ mode: "search-only",
637
+ checkpointId: this.checkpointId,
638
+ addedBlobs,
639
+ deletedBlobs
640
+ };
641
+ return state2;
642
+ }
526
643
  const blobs = [];
527
644
  for (const [path, serverBlobId] of this.blobMap.entries()) {
528
645
  const clientBlobId = this.clientBlobMap.get(path);
@@ -532,26 +649,28 @@ class DirectContext {
532
649
  blobs.push([serverBlobId, path]);
533
650
  }
534
651
  }
535
- const addedBlobs = Array.from(this.pendingAdded);
536
- const deletedBlobs = Array.from(this.pendingDeleted);
537
- return {
652
+ const state = {
653
+ mode: "full",
538
654
  checkpointId: this.checkpointId,
539
655
  addedBlobs,
540
656
  deletedBlobs,
541
657
  blobs
542
658
  };
659
+ return state;
543
660
  }
544
661
  /**
545
662
  * Internal method to import state from a JSON object
546
663
  */
547
664
  async doImport(state) {
548
665
  return await this.mutex.runExclusive(() => {
666
+ const mode = "mode" in state ? state.mode : "full";
667
+ this.isSearchOnly = mode === "search-only";
549
668
  this.checkpointId = state.checkpointId;
550
669
  this.blobMap.clear();
551
670
  this.clientBlobMap.clear();
552
671
  this.pendingAdded.clear();
553
672
  this.pendingDeleted.clear();
554
- if (state.blobs) {
673
+ if ("blobs" in state && state.blobs) {
555
674
  for (const entry of state.blobs) {
556
675
  const [serverBlobId, path, clientBlobId] = entry;
557
676
  this.blobMap.set(path, serverBlobId);
@@ -565,15 +684,20 @@ class DirectContext {
565
684
  this.pendingDeleted = new Set(state.deletedBlobs);
566
685
  }
567
686
  this.log(
568
- `State imported: checkpoint ${this.checkpointId}, ${this.blobMap.size} files, ${this.pendingAdded.size} pending added, ${this.pendingDeleted.size} pending deleted`
687
+ `State imported (mode: ${mode}): checkpoint ${this.checkpointId}, ${this.blobMap.size} files, ${this.pendingAdded.size} pending added, ${this.pendingDeleted.size} pending deleted`
569
688
  );
570
689
  });
571
690
  }
572
691
  /**
573
692
  * Export state to a file (Node.js only)
693
+ *
694
+ * @param filePath Path to save the state file
695
+ * @param options Export options
696
+ * @param options.mode Export mode - 'full' (default) includes all blob information,
697
+ * 'search-only' excludes blobs array for minimal storage
574
698
  */
575
- async exportToFile(filePath) {
576
- const state = this.export();
699
+ async exportToFile(filePath, options) {
700
+ const state = this.export(options);
577
701
  await writeFile(filePath, JSON.stringify(state, null, 2), "utf-8");
578
702
  this.log(`State saved to ${filePath}`);
579
703
  }
@@ -48,7 +48,11 @@ type BlobInfo = {
48
48
  * When present, it indicates the server computed a different blob ID than the client.
49
49
  * When absent, assume clientBlobId === serverBlobId.
50
50
  */
51
- type BlobEntry = [serverBlobId: string, path: string, clientBlobId?: string];
51
+ type BlobEntry = [
52
+ serverBlobId: string,
53
+ path: string,
54
+ clientBlobId?: string
55
+ ];
52
56
  /**
53
57
  * Blobs payload for API requests
54
58
  */
@@ -69,6 +73,75 @@ type IndexingResult = {
69
73
  /** Paths that were already uploaded (content unchanged or already on server) */
70
74
  alreadyUploaded: string[];
71
75
  };
76
+ /**
77
+ * Progress information for indexing operations
78
+ */
79
+ type IndexingProgress = {
80
+ /**
81
+ * Current stage of the indexing operation
82
+ * - 'uploading': Files are being uploaded to the backend
83
+ * - 'indexing': Backend is processing and indexing the uploaded files
84
+ */
85
+ stage: "uploading" | "indexing";
86
+ /**
87
+ * Number of files uploaded so far
88
+ */
89
+ uploaded: number;
90
+ /**
91
+ * Number of files indexed so far
92
+ */
93
+ indexed: number;
94
+ /**
95
+ * Total number of files to upload and index
96
+ */
97
+ total: number;
98
+ /**
99
+ * Path of the file currently being processed (only available during upload stage)
100
+ */
101
+ currentFile?: string;
102
+ /**
103
+ * Total bytes uploaded so far (only available during upload stage)
104
+ */
105
+ bytesUploaded?: number;
106
+ /**
107
+ * When the operation started
108
+ */
109
+ startedAt: Date;
110
+ };
111
+ /**
112
+ * Options for addToIndex operation
113
+ */
114
+ type AddToIndexOptions = {
115
+ /**
116
+ * If true (default), waits for the newly added files to be indexed before returning.
117
+ * If false, returns immediately after upload completes (indexing continues asynchronously).
118
+ * Default: true
119
+ */
120
+ waitForIndexing?: boolean;
121
+ /**
122
+ * Timeout in milliseconds for the indexing operation when waitForIndexing is true.
123
+ * If the backend does not finish indexing within this time, an error is thrown.
124
+ * Default: undefined (no timeout - waits indefinitely)
125
+ *
126
+ * Set this to a specific value (e.g., 600000 for 10 minutes) if you want to fail fast
127
+ * in case of backend issues or unexpectedly slow indexing.
128
+ */
129
+ timeout?: number;
130
+ /**
131
+ * Optional callback to receive progress updates during the indexing operation.
132
+ * Called periodically with information about the current stage, files uploaded/indexed, etc.
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * await context.addToIndex(files, {
137
+ * onProgress: (progress) => {
138
+ * console.log(`[${progress.stage}] Uploaded: ${progress.uploaded}/${progress.total}, Indexed: ${progress.indexed}/${progress.total}`);
139
+ * }
140
+ * });
141
+ * ```
142
+ */
143
+ onProgress?: (progress: IndexingProgress) => void;
144
+ };
72
145
  /**
73
146
  * Options for configuring the Direct Context
74
147
  */
@@ -91,9 +164,11 @@ type DirectContextOptions = {
91
164
  debug?: boolean;
92
165
  };
93
166
  /**
94
- * State for Direct Context that can be exported/imported
167
+ * Full state for Direct Context with all blob information
95
168
  */
96
- type DirectContextState = {
169
+ type FullContextState = {
170
+ /** Discriminator for state type */
171
+ mode: "full";
97
172
  /** Current checkpoint ID */
98
173
  checkpointId?: string;
99
174
  /** Array of blob names that have been added (pending checkpoint) */
@@ -103,6 +178,45 @@ type DirectContextState = {
103
178
  /** List of blobs as [blobName, path] tuples */
104
179
  blobs: BlobEntry[];
105
180
  };
181
+ /**
182
+ * Lightweight search-only state without blob information
183
+ *
184
+ * This state format is optimized for search-only use cases and reduces
185
+ * storage size for large codebases by excluding the blobs array.
186
+ *
187
+ * Use this when you only need to search the index and don't need to:
188
+ * - Add new files to the index
189
+ * - Remove files from the index
190
+ * - Get the list of indexed paths
191
+ */
192
+ type SearchOnlyContextState = {
193
+ /** Discriminator for state type */
194
+ mode: "search-only";
195
+ /** Current checkpoint ID */
196
+ checkpointId?: string;
197
+ /** Array of blob names that have been added (pending checkpoint) */
198
+ addedBlobs: string[];
199
+ /** Array of blob names that have been deleted (pending checkpoint) */
200
+ deletedBlobs: string[];
201
+ };
202
+ /**
203
+ * State for Direct Context that can be exported/imported
204
+ *
205
+ * Can be either full state (with blobs array) or search-only state (without blobs array).
206
+ * For backwards compatibility, states without a mode field are treated as full state.
207
+ */
208
+ type DirectContextState = FullContextState | SearchOnlyContextState;
209
+ /**
210
+ * Options for exporting Direct Context state
211
+ */
212
+ type ExportOptions = {
213
+ /**
214
+ * Export mode:
215
+ * - 'full': Include all blob information (default) - required for addToIndex/removeFromIndex
216
+ * - 'search-only': Exclude blob array for minimal storage - only supports search operations
217
+ */
218
+ mode?: "full" | "search-only";
219
+ };
106
220
  /**
107
221
  * Options for FileSystem Context (MCP mode)
108
222
  */
@@ -115,4 +229,4 @@ type FileSystemContextOptions = {
115
229
  debug?: boolean;
116
230
  };
117
231
 
118
- export type { BlobEntry, BlobInfo, Blobs, Chunk, DirectContextOptions, DirectContextState, File, FileSystemContextOptions, IndexingResult, SearchResult };
232
+ export type { AddToIndexOptions, BlobEntry, BlobInfo, Blobs, Chunk, DirectContextOptions, DirectContextState, ExportOptions, File, FileSystemContextOptions, FullContextState, IndexingProgress, IndexingResult, SearchOnlyContextState, SearchResult };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,6 @@ export { DirectContext } from './context/direct-context.js';
3
3
  export { FileSystemContext } from './context/filesystem-context.js';
4
4
  export { APIError } from './context/internal/api-client.js';
5
5
  export { BlobTooLargeError } from './context/internal/blob-name-calculator.js';
6
- export { BlobEntry, BlobInfo, Blobs, DirectContextOptions, DirectContextState, File, FileSystemContextOptions, IndexingResult } from './context/types.js';
6
+ export { AddToIndexOptions, BlobEntry, BlobInfo, Blobs, DirectContextOptions, DirectContextState, ExportOptions, File, FileSystemContextOptions, FullContextState, IndexingProgress, IndexingResult, SearchOnlyContextState } from './context/types.js';
7
7
  import '@agentclientprotocol/sdk';
8
8
  import 'ai';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augmentcode/auggie-sdk",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "TypeScript SDK for Auggie",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",