@agent-native/core 0.51.5 → 0.51.7

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 (38) hide show
  1. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  2. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  3. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  4. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  5. package/dist/cli/templates-meta.js +1 -1
  6. package/dist/cli/templates-meta.js.map +1 -1
  7. package/dist/coding-tools/run-code.d.ts.map +1 -1
  8. package/dist/coding-tools/run-code.js +435 -3
  9. package/dist/coding-tools/run-code.js.map +1 -1
  10. package/dist/provider-api/corpus-jobs-store.d.ts +95 -0
  11. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -0
  12. package/dist/provider-api/corpus-jobs-store.js +394 -0
  13. package/dist/provider-api/corpus-jobs-store.js.map +1 -0
  14. package/dist/provider-api/corpus-jobs.d.ts +146 -0
  15. package/dist/provider-api/corpus-jobs.d.ts.map +1 -0
  16. package/dist/provider-api/corpus-jobs.js +1192 -0
  17. package/dist/provider-api/corpus-jobs.js.map +1 -0
  18. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  19. package/dist/server/agent-chat-plugin.js +9 -2
  20. package/dist/server/agent-chat-plugin.js.map +1 -1
  21. package/dist/server/auth-marketing.js +4 -4
  22. package/dist/server/auth-marketing.js.map +1 -1
  23. package/docs/content/cloneable-saas.md +1 -1
  24. package/docs/content/getting-started.md +1 -1
  25. package/docs/content/local-file-mode.md +6 -1
  26. package/docs/content/template-analytics.md +0 -8
  27. package/docs/content/template-assets.md +0 -6
  28. package/docs/content/template-brain.md +0 -8
  29. package/docs/content/template-calendar.md +0 -8
  30. package/docs/content/template-clips.md +0 -8
  31. package/docs/content/template-content.md +27 -23
  32. package/docs/content/template-design.md +0 -6
  33. package/docs/content/template-forms.md +0 -10
  34. package/docs/content/template-mail.md +0 -8
  35. package/docs/content/template-plan.md +180 -0
  36. package/docs/content/template-slides.md +0 -8
  37. package/docs/content/template-videos.md +0 -8
  38. package/package.json +3 -1
@@ -0,0 +1,1192 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { defineAction } from "../action.js";
4
+ import { getCredentialContext } from "../server/request-context.js";
5
+ import { getStagedDatasetRows } from "./staged-datasets-store.js";
6
+ import { appendProviderCorpusJobHits, createProviderCorpusJob, deleteProviderCorpusJob, getProviderCorpusJob, getProviderCorpusJobHits, listProviderCorpusJobs, updateProviderCorpusJob, } from "./corpus-jobs-store.js";
7
+ const MethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
8
+ const ProviderRequestSchema = z.object({
9
+ provider: z.string().min(1).describe("Configured provider API id."),
10
+ method: MethodSchema.default("GET").describe("HTTP method to use."),
11
+ path: z.string().min(1).describe("Provider API path or allowed full URL."),
12
+ query: z.unknown().optional().describe("Optional query parameters."),
13
+ headers: z.record(z.string(), z.unknown()).optional(),
14
+ body: z.unknown().optional(),
15
+ auth: z.enum(["default", "none"]).default("default"),
16
+ connectionId: z.string().trim().min(1).optional(),
17
+ accountId: z.string().optional(),
18
+ timeoutMs: z.coerce.number().int().min(1_000).max(120_000).optional(),
19
+ maxBytes: z.coerce
20
+ .number()
21
+ .int()
22
+ .min(1_000)
23
+ .max(4 * 1024 * 1024)
24
+ .optional(),
25
+ });
26
+ const PaginationSchema = z.object({
27
+ itemsPath: z
28
+ .string()
29
+ .optional()
30
+ .describe("Dot-path to records in each page, e.g. records.calls."),
31
+ nextCursorPath: z
32
+ .string()
33
+ .optional()
34
+ .describe("Dot-path to the next cursor in each response."),
35
+ cursorPath: z.string().optional().describe("Alias for nextCursorPath."),
36
+ cursorParam: z
37
+ .string()
38
+ .optional()
39
+ .describe("Query parameter that receives the next cursor."),
40
+ cursorBodyPath: z
41
+ .string()
42
+ .optional()
43
+ .describe("Request body path that receives the next cursor."),
44
+ pageParam: z
45
+ .string()
46
+ .optional()
47
+ .describe("Query parameter for page-number pagination."),
48
+ startPage: z.coerce.number().int().min(0).optional(),
49
+ offsetParam: z
50
+ .string()
51
+ .optional()
52
+ .describe("Query parameter for offset pagination."),
53
+ startOffset: z.coerce.number().int().min(0).optional(),
54
+ pageSize: z.coerce.number().int().min(1).optional(),
55
+ maxPages: z.coerce
56
+ .number()
57
+ .int()
58
+ .min(1)
59
+ .max(2_000)
60
+ .optional()
61
+ .describe("Overall page cap for the job. Default 500."),
62
+ });
63
+ const BatchSchema = z.object({
64
+ items: z
65
+ .array(z.unknown())
66
+ .max(10_000)
67
+ .optional()
68
+ .describe("Input records or ids to process in batches."),
69
+ inputDatasetId: z
70
+ .string()
71
+ .optional()
72
+ .describe("Optional staged dataset id to read input records from."),
73
+ inputValuePath: z
74
+ .string()
75
+ .optional()
76
+ .describe("Dot-path to extract the provider id/value from each input row."),
77
+ batchSize: z.coerce.number().int().min(1).max(100).optional(),
78
+ itemBodyPath: z
79
+ .string()
80
+ .optional()
81
+ .describe("Dot-path in request body where the current batch array goes."),
82
+ itemQueryParam: z
83
+ .string()
84
+ .optional()
85
+ .describe("Query parameter where the current batch goes."),
86
+ responseItemsPath: z
87
+ .string()
88
+ .optional()
89
+ .describe("Dot-path to records in each batch response."),
90
+ });
91
+ const SearchSchema = z.object({
92
+ query: z.string().optional().describe("Phrase to search for."),
93
+ queries: z.array(z.string()).max(50).optional(),
94
+ terms: z
95
+ .array(z.string())
96
+ .max(50)
97
+ .optional()
98
+ .describe("Terms for allTerms/anyTerm search."),
99
+ regex: z.string().optional(),
100
+ regexFlags: z.string().optional(),
101
+ matchMode: z.enum(["query", "allTerms", "anyTerm"]).optional(),
102
+ caseSensitive: z.boolean().optional(),
103
+ textPaths: z
104
+ .array(z.string())
105
+ .max(100)
106
+ .optional()
107
+ .describe("Only search these dot-paths. Omit to search text recursively."),
108
+ idPaths: z.array(z.string()).max(100).optional(),
109
+ metadataPaths: z.array(z.string()).max(100).optional(),
110
+ contextChars: z.coerce.number().int().min(20).max(1_000).optional(),
111
+ maxHits: z.coerce.number().int().min(1).max(10_000).optional(),
112
+ maxHitsPerItem: z.coerce.number().int().min(1).max(100).optional(),
113
+ maxFieldsPerItem: z.coerce.number().int().min(1).max(50_000).optional(),
114
+ });
115
+ const LimitsSchema = z.object({
116
+ pageBudget: z.coerce
117
+ .number()
118
+ .int()
119
+ .min(1)
120
+ .max(200)
121
+ .optional()
122
+ .describe("Pages to process in this action call. Default 25."),
123
+ batchBudget: z.coerce
124
+ .number()
125
+ .int()
126
+ .min(1)
127
+ .max(500)
128
+ .optional()
129
+ .describe("Batches to process in this action call. Default 25."),
130
+ runtimeMs: z.coerce
131
+ .number()
132
+ .int()
133
+ .min(1_000)
134
+ .max(110_000)
135
+ .optional()
136
+ .describe("Wall-clock budget for this action call. Default 90000."),
137
+ itemBudget: z.coerce
138
+ .number()
139
+ .int()
140
+ .min(1)
141
+ .max(200_000)
142
+ .optional()
143
+ .describe("Response records to search in this action call."),
144
+ maxHits: z.coerce
145
+ .number()
146
+ .int()
147
+ .min(1)
148
+ .max(10_000)
149
+ .optional()
150
+ .describe("Stored hit cap for the whole job. Default comes from search."),
151
+ });
152
+ const ProviderCorpusJobSchema = z.object({
153
+ operation: z
154
+ .enum(["start", "continue", "status", "results", "list", "delete"])
155
+ .default("start"),
156
+ jobId: z
157
+ .string()
158
+ .optional()
159
+ .describe("Existing job id for continue/status/results/delete."),
160
+ name: z.string().optional().describe("Human-readable job name."),
161
+ mode: z.enum(["paginated-search", "batch-search"]).optional(),
162
+ request: ProviderRequestSchema.optional(),
163
+ pagination: PaginationSchema.optional(),
164
+ batch: BatchSchema.optional(),
165
+ search: SearchSchema.optional(),
166
+ limits: LimitsSchema.optional(),
167
+ offset: z.coerce
168
+ .number()
169
+ .int()
170
+ .min(0)
171
+ .optional()
172
+ .describe("Results/list offset."),
173
+ limit: z.coerce
174
+ .number()
175
+ .int()
176
+ .min(1)
177
+ .max(1_000)
178
+ .optional()
179
+ .describe("Results/list limit."),
180
+ });
181
+ const DEFAULT_PAGE_BUDGET = 25;
182
+ const DEFAULT_BATCH_BUDGET = 25;
183
+ const DEFAULT_RUNTIME_MS = 90_000;
184
+ const DEFAULT_BATCH_SIZE = 25;
185
+ const DEFAULT_OVERALL_MAX_PAGES = 500;
186
+ const DEFAULT_MAX_HITS = 500;
187
+ export function createProviderCorpusJobAction(options) {
188
+ return defineAction({
189
+ description: "Create, continue, inspect, list, read results from, or delete a durable provider corpus search job. " +
190
+ "Use this for broad provider searches, cross-source joins, transcript/message/ticket/issue/document scans, batch endpoint walks, and absence-sensitive questions that may exceed one tool call. " +
191
+ "The job is generic: paginated-search walks any paginated provider endpoint; batch-search walks a supplied id/record list or staged dataset through any provider endpoint by injecting each batch into a query param or request body path. " +
192
+ "Every provider request still goes through provider-api-request credentials, host allow-listing, SSRF blocking, secret redaction, and provider quota cooldown. " +
193
+ "When status is paused or quota_wait, call operation=continue with the jobId after the indicated time/budget. Report coverage counts, pagination status, and any remaining gaps.",
194
+ schema: ProviderCorpusJobSchema,
195
+ http: false,
196
+ run: async (args) => runProviderCorpusJobAction(args, options),
197
+ });
198
+ }
199
+ async function runProviderCorpusJobAction(args, options) {
200
+ const ctx = getCredentialContext();
201
+ if (!ctx)
202
+ throw new Error("No authenticated context for provider-corpus-job.");
203
+ if (args.operation === "list") {
204
+ const jobs = await listProviderCorpusJobs({
205
+ appId: options.appId,
206
+ ownerEmail: ctx.userEmail,
207
+ limit: args.limit,
208
+ });
209
+ return { jobs: jobs.map(jobSummary), total: jobs.length };
210
+ }
211
+ if (args.operation === "start") {
212
+ const job = await startJob(args, options.appId, ctx.userEmail);
213
+ return continueJob(job, args.limits, options.getRuntime());
214
+ }
215
+ const jobId = args.jobId?.trim();
216
+ if (!jobId)
217
+ throw new Error(`${args.operation} requires jobId.`);
218
+ if (args.operation === "delete") {
219
+ const deleted = await deleteProviderCorpusJob({
220
+ id: jobId,
221
+ appId: options.appId,
222
+ ownerEmail: ctx.userEmail,
223
+ });
224
+ if (!deleted) {
225
+ throw new Error(`Provider corpus job ${jobId} not found or belongs to another owner/app.`);
226
+ }
227
+ return { deleted: true, jobId };
228
+ }
229
+ const job = await getProviderCorpusJob({
230
+ id: jobId,
231
+ appId: options.appId,
232
+ ownerEmail: ctx.userEmail,
233
+ });
234
+ if (!job) {
235
+ throw new Error(`Provider corpus job ${jobId} not found or belongs to another owner/app.`);
236
+ }
237
+ if (args.operation === "status")
238
+ return jobStatus(job);
239
+ if (args.operation === "results") {
240
+ const hits = await getProviderCorpusJobHits({
241
+ jobId,
242
+ appId: options.appId,
243
+ ownerEmail: ctx.userEmail,
244
+ offset: args.offset,
245
+ limit: args.limit,
246
+ });
247
+ return {
248
+ ...jobStatus(job),
249
+ hits,
250
+ offset: args.offset ?? 0,
251
+ limit: args.limit ?? 100,
252
+ };
253
+ }
254
+ if (args.operation === "continue") {
255
+ return continueJob(job, args.limits, options.getRuntime());
256
+ }
257
+ throw new Error(`Unsupported provider corpus operation ${args.operation}.`);
258
+ }
259
+ async function startJob(args, appId, ownerEmail) {
260
+ if (!args.mode)
261
+ throw new Error("start requires mode.");
262
+ if (!args.request)
263
+ throw new Error("start requires request.");
264
+ const search = args.search ?? {};
265
+ if (!hasSearchNeedle(search)) {
266
+ throw new Error("start requires search.query, search.queries, search.terms, or search.regex.");
267
+ }
268
+ if (args.mode === "paginated-search" && !args.pagination) {
269
+ throw new Error("paginated-search requires pagination.");
270
+ }
271
+ if (args.mode === "batch-search") {
272
+ if (!args.batch)
273
+ throw new Error("batch-search requires batch config.");
274
+ if (!args.batch.itemBodyPath && !args.batch.itemQueryParam) {
275
+ throw new Error("batch-search requires batch.itemBodyPath or batch.itemQueryParam.");
276
+ }
277
+ if (!args.batch.items?.length && !args.batch.inputDatasetId) {
278
+ throw new Error("batch-search requires batch.items or inputDatasetId.");
279
+ }
280
+ }
281
+ const id = args.jobId?.trim() || `pcj_${randomUUID().replace(/-/g, "")}`;
282
+ const request = cleanRequest(args.request);
283
+ const checkpoint = args.mode === "batch-search"
284
+ ? { nextIndex: 0, sourceItemCount: null }
285
+ : {
286
+ pageIndex: 0,
287
+ cursor: null,
288
+ pageNumber: args.pagination?.startPage ?? 1,
289
+ offset: args.pagination?.startOffset ?? 0,
290
+ lastCursor: null,
291
+ stoppedReason: null,
292
+ };
293
+ return createProviderCorpusJob({
294
+ id,
295
+ appId,
296
+ ownerEmail,
297
+ name: args.name?.trim() || id,
298
+ mode: args.mode,
299
+ status: "paused",
300
+ provider: request.provider,
301
+ request: request,
302
+ pagination: args.pagination ?? null,
303
+ batch: args.batch ?? null,
304
+ search: search,
305
+ limits: args.limits ?? {},
306
+ checkpoint,
307
+ });
308
+ }
309
+ async function continueJob(job, overrideLimits, runtime) {
310
+ if (job.status === "completed")
311
+ return jobStatus(job);
312
+ const limits = normalizeLimits({
313
+ ...job.limits,
314
+ ...(overrideLimits ?? {}),
315
+ });
316
+ await updateProviderCorpusJob({
317
+ id: job.id,
318
+ appId: job.appId,
319
+ ownerEmail: job.ownerEmail,
320
+ status: "running",
321
+ error: null,
322
+ nextResumeAt: null,
323
+ });
324
+ try {
325
+ const refreshed = await getProviderCorpusJob({
326
+ id: job.id,
327
+ appId: job.appId,
328
+ ownerEmail: job.ownerEmail,
329
+ });
330
+ if (!refreshed)
331
+ throw new Error(`Provider corpus job ${job.id} disappeared.`);
332
+ const next = refreshed.mode === "batch-search"
333
+ ? await runBatchSearch(refreshed, limits, runtime)
334
+ : await runPaginatedSearch(refreshed, limits, runtime);
335
+ const hits = await getProviderCorpusJobHits({
336
+ jobId: next.id,
337
+ appId: next.appId,
338
+ ownerEmail: next.ownerEmail,
339
+ limit: 25,
340
+ });
341
+ return {
342
+ ...jobStatus(next),
343
+ sampleHits: hits,
344
+ nextAction: nextAction(next),
345
+ };
346
+ }
347
+ catch (err) {
348
+ const message = err instanceof Error ? err.message : String(err);
349
+ const failed = (await updateProviderCorpusJob({
350
+ id: job.id,
351
+ appId: job.appId,
352
+ ownerEmail: job.ownerEmail,
353
+ status: "failed",
354
+ error: message,
355
+ })) ?? job;
356
+ return { ...jobStatus(failed), nextAction: nextAction(failed) };
357
+ }
358
+ }
359
+ async function runPaginatedSearch(job, limits, runtime) {
360
+ const startedAt = Date.now();
361
+ const pagination = normalizePagination(job.pagination);
362
+ const request = job.request;
363
+ const search = job.search;
364
+ const checkpoint = { ...job.checkpoint };
365
+ const maxPages = pagination.maxPages ?? DEFAULT_OVERALL_MAX_PAGES;
366
+ let pageIndex = numberValue(checkpoint.pageIndex, 0);
367
+ let cursor = valueOrNull(checkpoint.cursor);
368
+ let pageNumber = numberValue(checkpoint.pageNumber, pagination.startPage ?? 1);
369
+ let offset = numberValue(checkpoint.offset, pagination.startOffset ?? 0);
370
+ let pagesThisCall = 0;
371
+ let itemsThisCall = 0;
372
+ let current = job;
373
+ while (pagesThisCall < limits.pageBudget &&
374
+ pageIndex < maxPages &&
375
+ itemsThisCall < limits.itemBudget &&
376
+ Date.now() - startedAt < limits.runtimeMs) {
377
+ const pageRequest = buildPaginatedRequest(request, pagination, {
378
+ pageIndex,
379
+ cursor,
380
+ pageNumber,
381
+ offset,
382
+ });
383
+ const provider = await callProvider(runtime, pageRequest);
384
+ if (provider.quota) {
385
+ return pauseForQuota(current, {
386
+ ...checkpoint,
387
+ pageIndex,
388
+ cursor,
389
+ pageNumber,
390
+ offset,
391
+ }, provider.quota);
392
+ }
393
+ const items = extractItemsArray(provider.body, pagination.itemsPath, false);
394
+ const nextCursorPath = pagination.nextCursorPath || pagination.cursorPath;
395
+ const nextCursor = nextCursorPath
396
+ ? valueOrNull(getAtPath(provider.body, nextCursorPath))
397
+ : null;
398
+ const searchResult = searchItems(items, search, {
399
+ baseItemIndex: current.itemsProcessed,
400
+ maxStoredHits: Math.max(0, limits.maxHits - current.storedHits),
401
+ pageIndex,
402
+ });
403
+ const storedHits = searchResult.storedHits.map((hit) => hitToRecord(hit));
404
+ await appendProviderCorpusJobHits({
405
+ jobId: job.id,
406
+ startIndex: current.storedHits,
407
+ hits: storedHits,
408
+ });
409
+ pagesThisCall++;
410
+ itemsThisCall += searchResult.searchedItems;
411
+ const nextCheckpoint = {
412
+ ...checkpoint,
413
+ pageIndex: pageIndex + 1,
414
+ cursor: nextCursor,
415
+ pageNumber,
416
+ offset,
417
+ lastCursor: nextCursor,
418
+ stoppedReason: null,
419
+ };
420
+ let status = "paused";
421
+ let stoppedReason = null;
422
+ if (items.length === 0) {
423
+ status = "completed";
424
+ stoppedReason = "empty-page";
425
+ }
426
+ else if (nextCursorPath) {
427
+ if (!nextCursor) {
428
+ status = "completed";
429
+ stoppedReason = "no-next-cursor";
430
+ }
431
+ else if (cursor !== null && String(nextCursor) === String(cursor)) {
432
+ status = "completed";
433
+ stoppedReason = "repeated-cursor";
434
+ }
435
+ else {
436
+ cursor = nextCursor;
437
+ }
438
+ }
439
+ else if (pagination.pageParam) {
440
+ pageNumber += 1;
441
+ nextCheckpoint.pageNumber = pageNumber;
442
+ }
443
+ else if (pagination.offsetParam) {
444
+ const step = pagination.pageSize || items.length;
445
+ offset += step;
446
+ nextCheckpoint.offset = offset;
447
+ if (pagination.pageSize && items.length < pagination.pageSize) {
448
+ status = "completed";
449
+ stoppedReason = "short-page";
450
+ }
451
+ }
452
+ else {
453
+ status = "completed";
454
+ stoppedReason = "single-page";
455
+ }
456
+ if (pageIndex + 1 >= maxPages && status !== "completed") {
457
+ status = "completed";
458
+ stoppedReason = "max-pages";
459
+ }
460
+ nextCheckpoint.stoppedReason = stoppedReason;
461
+ current =
462
+ (await updateProviderCorpusJob({
463
+ id: job.id,
464
+ appId: job.appId,
465
+ ownerEmail: job.ownerEmail,
466
+ status,
467
+ checkpoint: nextCheckpoint,
468
+ pagesProcessed: current.pagesProcessed + 1,
469
+ itemsProcessed: current.itemsProcessed + searchResult.searchedItems,
470
+ matchedItems: current.matchedItems + searchResult.matchedItems,
471
+ totalHits: current.totalHits + searchResult.totalHits,
472
+ storedHits: current.storedHits + storedHits.length,
473
+ error: null,
474
+ nextResumeAt: null,
475
+ })) ?? current;
476
+ pageIndex += 1;
477
+ if (status === "completed")
478
+ return current;
479
+ }
480
+ return pauseForBudget(current, {
481
+ ...checkpoint,
482
+ pageIndex,
483
+ cursor,
484
+ pageNumber,
485
+ offset,
486
+ stoppedReason: "budget",
487
+ });
488
+ }
489
+ async function runBatchSearch(job, limits, runtime) {
490
+ const startedAt = Date.now();
491
+ const batch = normalizeBatch(job.batch);
492
+ const request = job.request;
493
+ const search = job.search;
494
+ const sourceItems = await loadBatchSourceItems(job, batch);
495
+ const totalSourceItems = sourceItems.length;
496
+ let nextIndex = numberValue(job.checkpoint.nextIndex, 0);
497
+ let batchesThisCall = 0;
498
+ let itemsThisCall = 0;
499
+ let current = job;
500
+ while (nextIndex < totalSourceItems &&
501
+ batchesThisCall < limits.batchBudget &&
502
+ itemsThisCall < limits.itemBudget &&
503
+ Date.now() - startedAt < limits.runtimeMs) {
504
+ const slice = sourceItems.slice(nextIndex, nextIndex + batch.batchSize);
505
+ const values = slice
506
+ .map((item) => batch.inputValuePath ? getAtPath(item, batch.inputValuePath) : item)
507
+ .filter((value) => value !== undefined && value !== null);
508
+ if (values.length === 0) {
509
+ nextIndex += slice.length || batch.batchSize;
510
+ continue;
511
+ }
512
+ const batchRequest = buildBatchRequest(request, batch, values);
513
+ const provider = await callProvider(runtime, batchRequest);
514
+ if (provider.quota) {
515
+ return pauseForQuota(current, {
516
+ ...job.checkpoint,
517
+ nextIndex,
518
+ sourceItemCount: totalSourceItems,
519
+ }, provider.quota);
520
+ }
521
+ const items = extractItemsArray(provider.body, batch.responseItemsPath, true);
522
+ const searchResult = searchItems(items, search, {
523
+ baseItemIndex: current.itemsProcessed,
524
+ maxStoredHits: Math.max(0, limits.maxHits - current.storedHits),
525
+ batchIndex: current.batchesProcessed,
526
+ });
527
+ const storedHits = searchResult.storedHits.map((hit) => hitToRecord(hit));
528
+ await appendProviderCorpusJobHits({
529
+ jobId: job.id,
530
+ startIndex: current.storedHits,
531
+ hits: storedHits,
532
+ });
533
+ batchesThisCall++;
534
+ itemsThisCall += searchResult.searchedItems;
535
+ nextIndex += slice.length;
536
+ const status = nextIndex >= totalSourceItems ? "completed" : "paused";
537
+ current =
538
+ (await updateProviderCorpusJob({
539
+ id: job.id,
540
+ appId: job.appId,
541
+ ownerEmail: job.ownerEmail,
542
+ status,
543
+ checkpoint: {
544
+ ...job.checkpoint,
545
+ nextIndex,
546
+ sourceItemCount: totalSourceItems,
547
+ stoppedReason: status === "completed" ? "completed" : "budget",
548
+ },
549
+ batchesProcessed: current.batchesProcessed + 1,
550
+ itemsProcessed: current.itemsProcessed + searchResult.searchedItems,
551
+ matchedItems: current.matchedItems + searchResult.matchedItems,
552
+ totalHits: current.totalHits + searchResult.totalHits,
553
+ storedHits: current.storedHits + storedHits.length,
554
+ error: null,
555
+ nextResumeAt: null,
556
+ })) ?? current;
557
+ if (status === "completed")
558
+ return current;
559
+ }
560
+ return pauseForBudget(current, {
561
+ ...job.checkpoint,
562
+ nextIndex,
563
+ sourceItemCount: totalSourceItems,
564
+ stoppedReason: "budget",
565
+ });
566
+ }
567
+ async function pauseForQuota(job, checkpoint, quota) {
568
+ return ((await updateProviderCorpusJob({
569
+ id: job.id,
570
+ appId: job.appId,
571
+ ownerEmail: job.ownerEmail,
572
+ status: "quota_wait",
573
+ checkpoint,
574
+ error: `Provider quota exhausted. Retry after ${quota.retryAt}.`,
575
+ nextResumeAt: parseRetryAt(quota.retryAt, quota.retryAfterMs),
576
+ })) ?? job);
577
+ }
578
+ async function pauseForBudget(job, checkpoint) {
579
+ return ((await updateProviderCorpusJob({
580
+ id: job.id,
581
+ appId: job.appId,
582
+ ownerEmail: job.ownerEmail,
583
+ status: "paused",
584
+ checkpoint,
585
+ error: null,
586
+ nextResumeAt: null,
587
+ })) ?? job);
588
+ }
589
+ async function callProvider(runtime, request) {
590
+ const raw = (await runtime.executeRequest(cleanRequest(request)));
591
+ const response = raw.response;
592
+ if (!response) {
593
+ return { body: raw };
594
+ }
595
+ const quota = readQuota(response);
596
+ if (quota)
597
+ return { body: null, quota };
598
+ if (response.ok !== true) {
599
+ const status = response.status ?? "unknown";
600
+ const detail = response.text ?? response.json ?? response.statusText ?? "";
601
+ throw new Error(`Provider request failed (${status}): ${stringifyBrief(detail, 500)}`);
602
+ }
603
+ return {
604
+ body: response.json ??
605
+ (typeof response.text === "string" ? tryParseJson(response.text) : null),
606
+ };
607
+ }
608
+ async function loadBatchSourceItems(job, batch) {
609
+ if (batch.items.length > 0)
610
+ return batch.items;
611
+ if (!batch.inputDatasetId)
612
+ return [];
613
+ return getStagedDatasetRows({
614
+ id: batch.inputDatasetId,
615
+ appId: job.appId,
616
+ ownerEmail: job.ownerEmail,
617
+ });
618
+ }
619
+ function cleanRequest(request) {
620
+ return {
621
+ provider: request.provider,
622
+ method: request.method,
623
+ path: request.path,
624
+ query: request.query,
625
+ headers: request.headers,
626
+ body: request.body,
627
+ auth: request.auth,
628
+ connectionId: request.connectionId,
629
+ accountId: request.accountId,
630
+ timeoutMs: request.timeoutMs,
631
+ maxBytes: request.maxBytes,
632
+ };
633
+ }
634
+ function buildPaginatedRequest(request, pagination, state) {
635
+ let query = cloneRecord(request.query);
636
+ let body = request.body;
637
+ if (pagination.pageParam) {
638
+ query = { ...query, [pagination.pageParam]: state.pageNumber };
639
+ }
640
+ if (pagination.offsetParam) {
641
+ query = { ...query, [pagination.offsetParam]: state.offset };
642
+ }
643
+ if (state.pageIndex > 0 && state.cursor != null) {
644
+ if (pagination.cursorParam) {
645
+ query = { ...query, [pagination.cursorParam]: state.cursor };
646
+ }
647
+ if (pagination.cursorBodyPath) {
648
+ body = setAtPath(body, pagination.cursorBodyPath, state.cursor);
649
+ }
650
+ }
651
+ return { ...request, query, body };
652
+ }
653
+ function buildBatchRequest(request, batch, values) {
654
+ let query = cloneRecord(request.query);
655
+ let body = request.body;
656
+ const payload = values.length === 1 ? values[0] : values;
657
+ if (batch.itemQueryParam) {
658
+ query = { ...query, [batch.itemQueryParam]: payload };
659
+ }
660
+ if (batch.itemBodyPath) {
661
+ body = setAtPath(body, batch.itemBodyPath, values);
662
+ }
663
+ return { ...request, query, body };
664
+ }
665
+ function searchItems(items, search, options) {
666
+ const maxStoredHits = Math.max(0, options.maxStoredHits);
667
+ const maxHitsPerItem = boundedNumber(search.maxHitsPerItem, 3, 1, 100);
668
+ const maxFieldsPerItem = boundedNumber(search.maxFieldsPerItem, 5_000, 1, 50_000);
669
+ let matchedItems = 0;
670
+ let totalHits = 0;
671
+ const storedHits = [];
672
+ for (let itemOffset = 0; itemOffset < items.length; itemOffset++) {
673
+ const item = items[itemOffset];
674
+ const fields = collectSearchStrings(item, search.textPaths, maxFieldsPerItem);
675
+ const identity = extractItemIdentity(item, search.idPaths);
676
+ const metadata = extractMetadata(item, search.metadataPaths);
677
+ let itemMatched = false;
678
+ let storedForItem = 0;
679
+ const itemWideTermMatch = findItemWideTermMatch(fields, search);
680
+ const addHit = (field, match) => {
681
+ totalHits++;
682
+ if (!itemMatched) {
683
+ matchedItems++;
684
+ itemMatched = true;
685
+ }
686
+ if (storedHits.length < maxStoredHits && storedForItem < maxHitsPerItem) {
687
+ storedForItem++;
688
+ storedHits.push({
689
+ id: identity.id,
690
+ idPath: identity.idPath,
691
+ itemIndex: options.baseItemIndex + itemOffset,
692
+ pageIndex: options.pageIndex,
693
+ pageItemIndex: options.pageIndex === undefined ? undefined : itemOffset,
694
+ batchIndex: options.batchIndex,
695
+ batchItemIndex: options.batchIndex === undefined ? undefined : itemOffset,
696
+ path: field.path,
697
+ kind: match.kind,
698
+ query: match.query,
699
+ match: match.match,
700
+ snippet: makeSnippet(field.text, match.index, search.contextChars),
701
+ ...(Object.keys(metadata).length ? { metadata } : {}),
702
+ });
703
+ }
704
+ };
705
+ if (itemWideTermMatch) {
706
+ addHit(itemWideTermMatch.field, itemWideTermMatch.match);
707
+ }
708
+ for (const field of fields) {
709
+ const matches = findSearchMatches(field.text, search, !itemWideTermMatch);
710
+ for (const match of matches)
711
+ addHit(field, match);
712
+ }
713
+ }
714
+ return {
715
+ searchedItems: items.length,
716
+ matchedItems,
717
+ totalHits,
718
+ storedHits,
719
+ };
720
+ }
721
+ function normalizeLimits(input) {
722
+ const maxHits = input.maxHits ?? DEFAULT_MAX_HITS;
723
+ return {
724
+ pageBudget: input.pageBudget ?? DEFAULT_PAGE_BUDGET,
725
+ batchBudget: input.batchBudget ?? DEFAULT_BATCH_BUDGET,
726
+ runtimeMs: input.runtimeMs ?? DEFAULT_RUNTIME_MS,
727
+ itemBudget: input.itemBudget ?? 200_000,
728
+ maxHits,
729
+ };
730
+ }
731
+ function normalizePagination(input) {
732
+ const parsed = PaginationSchema.parse(input ?? {});
733
+ return {
734
+ itemsPath: parsed.itemsPath ?? "",
735
+ nextCursorPath: parsed.nextCursorPath ?? "",
736
+ cursorPath: parsed.cursorPath ?? "",
737
+ cursorParam: parsed.cursorParam ?? "",
738
+ cursorBodyPath: parsed.cursorBodyPath ?? "",
739
+ pageParam: parsed.pageParam ?? "",
740
+ startPage: parsed.startPage ?? 1,
741
+ offsetParam: parsed.offsetParam ?? "",
742
+ startOffset: parsed.startOffset ?? 0,
743
+ pageSize: parsed.pageSize ?? 0,
744
+ maxPages: parsed.maxPages ?? DEFAULT_OVERALL_MAX_PAGES,
745
+ };
746
+ }
747
+ function normalizeBatch(input) {
748
+ const parsed = BatchSchema.parse(input ?? {});
749
+ return {
750
+ items: parsed.items ?? [],
751
+ inputDatasetId: parsed.inputDatasetId ?? "",
752
+ inputValuePath: parsed.inputValuePath ?? "",
753
+ batchSize: parsed.batchSize ?? DEFAULT_BATCH_SIZE,
754
+ itemBodyPath: parsed.itemBodyPath ?? "",
755
+ itemQueryParam: parsed.itemQueryParam ?? "",
756
+ responseItemsPath: parsed.responseItemsPath ?? "",
757
+ };
758
+ }
759
+ function jobStatus(job) {
760
+ const nextResumeAtIso = job.nextResumeAt
761
+ ? new Date(job.nextResumeAt).toISOString()
762
+ : null;
763
+ return {
764
+ job: jobSummary(job),
765
+ coverage: {
766
+ pagesProcessed: job.pagesProcessed,
767
+ batchesProcessed: job.batchesProcessed,
768
+ itemsProcessed: job.itemsProcessed,
769
+ matchedItems: job.matchedItems,
770
+ totalHits: job.totalHits,
771
+ storedHits: job.storedHits,
772
+ truncatedHits: job.totalHits > job.storedHits,
773
+ },
774
+ checkpoint: job.checkpoint,
775
+ error: job.error,
776
+ nextResumeAt: nextResumeAtIso,
777
+ };
778
+ }
779
+ function jobSummary(job) {
780
+ return {
781
+ id: job.id,
782
+ name: job.name,
783
+ mode: job.mode,
784
+ status: job.status,
785
+ provider: job.provider,
786
+ createdAt: new Date(job.createdAt).toISOString(),
787
+ updatedAt: new Date(job.updatedAt).toISOString(),
788
+ };
789
+ }
790
+ function nextAction(job) {
791
+ if (job.status === "completed") {
792
+ return `Read all stored hits with operation="results", jobId="${job.id}".`;
793
+ }
794
+ if (job.status === "quota_wait") {
795
+ const at = job.nextResumeAt
796
+ ? new Date(job.nextResumeAt).toISOString()
797
+ : "later";
798
+ return `Provider quota is cooling down. Continue this job after ${at} with operation="continue", jobId="${job.id}".`;
799
+ }
800
+ if (job.status === "paused") {
801
+ return `Continue this job with operation="continue", jobId="${job.id}" until completed or quota_wait.`;
802
+ }
803
+ if (job.status === "failed") {
804
+ return 'Fix the request/configuration or start a new job; progress and stored hits are still inspectable with operation="results".';
805
+ }
806
+ return `Check status with operation="status", jobId="${job.id}".`;
807
+ }
808
+ function extractItemsArray(body, itemsPath, wrapObjectFallback) {
809
+ if (Array.isArray(body))
810
+ return body;
811
+ if (!body || typeof body !== "object")
812
+ return [];
813
+ if (itemsPath) {
814
+ const found = getAtPath(body, itemsPath);
815
+ if (Array.isArray(found))
816
+ return found;
817
+ if (found !== undefined && wrapObjectFallback)
818
+ return [found];
819
+ return [];
820
+ }
821
+ const obj = body;
822
+ for (const key of [
823
+ "data",
824
+ "results",
825
+ "items",
826
+ "records",
827
+ "rows",
828
+ "calls",
829
+ "callTranscripts",
830
+ "transcripts",
831
+ "messages",
832
+ "tickets",
833
+ "issues",
834
+ "deals",
835
+ "events",
836
+ "notes",
837
+ "documents",
838
+ "entries",
839
+ "objects",
840
+ ]) {
841
+ if (Array.isArray(obj[key]))
842
+ return obj[key];
843
+ }
844
+ const arrayFields = Object.values(obj).filter(Array.isArray);
845
+ if (arrayFields.length === 1)
846
+ return arrayFields[0];
847
+ return wrapObjectFallback ? [body] : [];
848
+ }
849
+ function hasSearchNeedle(search) {
850
+ return Boolean(search.query ||
851
+ search.regex ||
852
+ search.queries?.length ||
853
+ search.terms?.length);
854
+ }
855
+ function readQuota(response) {
856
+ const quota = response.quota;
857
+ if (quota?.exhausted === true && typeof quota.retryAt === "string") {
858
+ return {
859
+ retryAt: quota.retryAt,
860
+ retryAfterMs: numberValue(quota.retryAfterMs, 0),
861
+ reason: String(quota.reason ?? "quota"),
862
+ };
863
+ }
864
+ const json = response.json;
865
+ if (json?.error === "provider_quota_exhausted" &&
866
+ typeof json.retryAt === "string") {
867
+ return {
868
+ retryAt: json.retryAt,
869
+ retryAfterMs: numberValue(json.retryAfterMs, 0),
870
+ reason: String(json.reason ?? "quota"),
871
+ };
872
+ }
873
+ return null;
874
+ }
875
+ function hitToRecord(hit) {
876
+ return Object.fromEntries(Object.entries(hit).filter(([, value]) => value !== undefined));
877
+ }
878
+ function tryParseJson(text) {
879
+ try {
880
+ return JSON.parse(text);
881
+ }
882
+ catch {
883
+ return text;
884
+ }
885
+ }
886
+ function stringifyBrief(value, maxChars) {
887
+ const text = typeof value === "string" ? value : JSON.stringify(value);
888
+ return text.slice(0, maxChars);
889
+ }
890
+ function cloneRecord(value) {
891
+ if (!value || typeof value !== "object" || Array.isArray(value))
892
+ return {};
893
+ return JSON.parse(JSON.stringify(value));
894
+ }
895
+ function valueOrNull(value) {
896
+ if (value === undefined ||
897
+ value === null ||
898
+ value === "" ||
899
+ value === false) {
900
+ return null;
901
+ }
902
+ return value;
903
+ }
904
+ function numberValue(value, fallback) {
905
+ const parsed = Number(value);
906
+ return Number.isFinite(parsed) ? parsed : fallback;
907
+ }
908
+ function parseRetryAt(retryAt, retryAfterMs) {
909
+ const parsed = Date.parse(retryAt);
910
+ if (Number.isFinite(parsed))
911
+ return parsed;
912
+ return Date.now() + Math.max(0, retryAfterMs);
913
+ }
914
+ function boundedNumber(value, fallback, min, max) {
915
+ const parsed = Number(value);
916
+ const finite = Number.isFinite(parsed) ? parsed : fallback;
917
+ return Math.max(min, Math.min(max, finite));
918
+ }
919
+ function pathParts(path) {
920
+ if (!path)
921
+ return [];
922
+ return path
923
+ .replace(/\[(\d+)\]/g, ".$1")
924
+ .split(".")
925
+ .map((part) => part.trim())
926
+ .filter(Boolean);
927
+ }
928
+ function getAtPath(value, path) {
929
+ let current = value;
930
+ for (const part of pathParts(path)) {
931
+ if (current === undefined || current === null)
932
+ return undefined;
933
+ if (typeof current !== "object")
934
+ return undefined;
935
+ current = current[part];
936
+ }
937
+ return current;
938
+ }
939
+ function setAtPath(base, path, value) {
940
+ const parts = pathParts(path);
941
+ if (!parts.length)
942
+ return value;
943
+ const root = base && typeof base === "object" && !Array.isArray(base)
944
+ ? JSON.parse(JSON.stringify(base))
945
+ : {};
946
+ let current = root;
947
+ for (const part of parts.slice(0, -1)) {
948
+ const existing = current[part];
949
+ const next = existing && typeof existing === "object" && !Array.isArray(existing)
950
+ ? { ...existing }
951
+ : {};
952
+ current[part] = next;
953
+ current = next;
954
+ }
955
+ current[parts[parts.length - 1]] = value;
956
+ return root;
957
+ }
958
+ function collectSearchStrings(item, textPaths, maxFields) {
959
+ const paths = (textPaths ?? []).filter((path) => path.trim());
960
+ if (!paths.length)
961
+ return collectStrings(item, "", [], maxFields);
962
+ const fields = [];
963
+ for (const path of paths) {
964
+ const value = getAtPath(item, path);
965
+ if (value !== undefined)
966
+ collectStrings(value, path, fields, maxFields);
967
+ if (fields.length >= maxFields)
968
+ break;
969
+ }
970
+ return fields;
971
+ }
972
+ function collectStrings(value, basePath, out, limit) {
973
+ if (out.length >= limit || value === undefined || value === null)
974
+ return out;
975
+ if (typeof value === "string" ||
976
+ typeof value === "number" ||
977
+ typeof value === "boolean" ||
978
+ typeof value === "bigint") {
979
+ out.push({ path: basePath || "$", text: String(value) });
980
+ return out;
981
+ }
982
+ if (Array.isArray(value)) {
983
+ for (let i = 0; i < value.length && out.length < limit; i++) {
984
+ collectStrings(value[i], basePath ? `${basePath}[${i}]` : `[${i}]`, out, limit);
985
+ }
986
+ return out;
987
+ }
988
+ if (typeof value === "object") {
989
+ for (const key of Object.keys(value)) {
990
+ if (out.length >= limit)
991
+ break;
992
+ collectStrings(value[key], basePath ? `${basePath}.${key}` : key, out, limit);
993
+ }
994
+ }
995
+ return out;
996
+ }
997
+ const DEFAULT_ID_PATHS = [
998
+ "id",
999
+ "callId",
1000
+ "callID",
1001
+ "call_id",
1002
+ "call.id",
1003
+ "call.metaData.id",
1004
+ "metaData.id",
1005
+ "metadata.id",
1006
+ "recordId",
1007
+ "record_id",
1008
+ "objectId",
1009
+ "object_id",
1010
+ "ticketId",
1011
+ "ticket_id",
1012
+ "issueId",
1013
+ "issue_id",
1014
+ "messageId",
1015
+ "message_id",
1016
+ "conversationId",
1017
+ "conversation_id",
1018
+ "eventId",
1019
+ "event_id",
1020
+ "documentId",
1021
+ "document_id",
1022
+ "url",
1023
+ "webUrl",
1024
+ "permalink",
1025
+ ];
1026
+ function extractItemIdentity(item, idPaths) {
1027
+ for (const path of [...(idPaths ?? []), ...DEFAULT_ID_PATHS]) {
1028
+ const value = getAtPath(item, path);
1029
+ if (value !== undefined && value !== null && String(value) !== "") {
1030
+ return { id: stringifySearchValue(value), idPath: path };
1031
+ }
1032
+ }
1033
+ return { id: null, idPath: null };
1034
+ }
1035
+ function extractMetadata(item, metadataPaths) {
1036
+ const metadata = {};
1037
+ for (const path of metadataPaths ?? []) {
1038
+ const value = getAtPath(item, path);
1039
+ if (value !== undefined)
1040
+ metadata[path] = value;
1041
+ }
1042
+ return metadata;
1043
+ }
1044
+ function stringifySearchValue(value) {
1045
+ if (typeof value === "string")
1046
+ return value;
1047
+ if (value === undefined || value === null)
1048
+ return "";
1049
+ if (typeof value === "number" ||
1050
+ typeof value === "boolean" ||
1051
+ typeof value === "bigint") {
1052
+ return String(value);
1053
+ }
1054
+ return stringifyBrief(value, 500);
1055
+ }
1056
+ function normalizedTerms(search) {
1057
+ const explicit = (search.terms ?? [])
1058
+ .map((term) => term.trim())
1059
+ .filter(Boolean);
1060
+ if (explicit.length)
1061
+ return explicit;
1062
+ if (search.matchMode === "allTerms" && search.query) {
1063
+ return search.query
1064
+ .split(/\s+/)
1065
+ .map((term) => term.trim())
1066
+ .filter(Boolean);
1067
+ }
1068
+ return [];
1069
+ }
1070
+ function findItemWideTermMatch(fields, search) {
1071
+ const terms = normalizedTerms(search);
1072
+ if (!terms.length || search.matchMode === "anyTerm")
1073
+ return null;
1074
+ const caseSensitive = Boolean(search.caseSensitive);
1075
+ const termHits = terms.map((term) => {
1076
+ const needle = caseSensitive ? term : term.toLowerCase();
1077
+ for (const field of fields) {
1078
+ const haystack = caseSensitive ? field.text : field.text.toLowerCase();
1079
+ const index = haystack.indexOf(needle);
1080
+ if (index >= 0)
1081
+ return { term, field, index };
1082
+ }
1083
+ return { term, field: null, index: -1 };
1084
+ });
1085
+ if (termHits.some((hit) => !hit.field || hit.index < 0))
1086
+ return null;
1087
+ const first = termHits
1088
+ .filter((hit) => Boolean(hit.field))
1089
+ .sort((a, b) => a.index - b.index)[0];
1090
+ if (!first)
1091
+ return null;
1092
+ return {
1093
+ field: first.field,
1094
+ match: {
1095
+ kind: "allTerms",
1096
+ query: terms.join(" "),
1097
+ index: first.index,
1098
+ match: first.term,
1099
+ },
1100
+ };
1101
+ }
1102
+ function findSearchMatches(text, search, includeTerms) {
1103
+ const source = String(text);
1104
+ const caseSensitive = Boolean(search.caseSensitive);
1105
+ const haystack = caseSensitive ? source : source.toLowerCase();
1106
+ const maxMatchesPerField = 1_000;
1107
+ const matches = [];
1108
+ const addSubstring = (needle, label, kind) => {
1109
+ if (needle === undefined || needle === null)
1110
+ return;
1111
+ const rawNeedle = String(needle);
1112
+ if (!rawNeedle)
1113
+ return;
1114
+ const searchNeedle = caseSensitive ? rawNeedle : rawNeedle.toLowerCase();
1115
+ let from = 0;
1116
+ while (from <= haystack.length) {
1117
+ const index = haystack.indexOf(searchNeedle, from);
1118
+ if (index < 0)
1119
+ break;
1120
+ matches.push({
1121
+ kind,
1122
+ query: label,
1123
+ index,
1124
+ match: source.slice(index, index + rawNeedle.length),
1125
+ });
1126
+ from = index + Math.max(1, searchNeedle.length);
1127
+ if (matches.length >= maxMatchesPerField)
1128
+ break;
1129
+ }
1130
+ };
1131
+ if (search.regex) {
1132
+ const regex = new RegExp(search.regex, normalizeRegexFlags(search.regexFlags, caseSensitive));
1133
+ let match;
1134
+ while ((match = regex.exec(source)) && typeof match.index === "number") {
1135
+ matches.push({
1136
+ kind: "regex",
1137
+ query: search.regex,
1138
+ index: match.index,
1139
+ match: match[0],
1140
+ });
1141
+ if (matches.length >= maxMatchesPerField)
1142
+ break;
1143
+ if (match[0] === "")
1144
+ regex.lastIndex += 1;
1145
+ }
1146
+ }
1147
+ if (search.query)
1148
+ addSubstring(search.query, search.query, "query");
1149
+ for (const query of search.queries ?? [])
1150
+ addSubstring(query, query, "query");
1151
+ const terms = includeTerms ? normalizedTerms(search) : [];
1152
+ if (terms.length) {
1153
+ const hits = terms
1154
+ .map((term) => {
1155
+ const needle = caseSensitive ? term : term.toLowerCase();
1156
+ return { term, index: haystack.indexOf(needle) };
1157
+ })
1158
+ .filter((hit) => hit.index >= 0);
1159
+ const mode = search.matchMode === "anyTerm" ? "anyTerm" : "allTerms";
1160
+ if ((mode === "allTerms" && hits.length === terms.length) ||
1161
+ (mode === "anyTerm" && hits.length > 0)) {
1162
+ const first = hits.sort((a, b) => a.index - b.index)[0];
1163
+ if (first) {
1164
+ matches.push({
1165
+ kind: mode,
1166
+ query: terms.join(" "),
1167
+ index: first.index,
1168
+ match: first.term,
1169
+ });
1170
+ }
1171
+ }
1172
+ }
1173
+ return matches.sort((a, b) => a.index - b.index);
1174
+ }
1175
+ function normalizeRegexFlags(flags, caseSensitive) {
1176
+ const allowed = (flags ?? "")
1177
+ .replace(/[^dgimsuvy]/g, "")
1178
+ .replace(/[gy]/g, "");
1179
+ const withCase = caseSensitive || /i/.test(allowed) ? allowed : `${allowed}i`;
1180
+ return `${withCase}g`;
1181
+ }
1182
+ function makeSnippet(text, index, contextChars) {
1183
+ const context = boundedNumber(contextChars, 180, 20, 1_000);
1184
+ const start = Math.max(0, index - context);
1185
+ const end = Math.min(text.length, index + context);
1186
+ const prefix = start > 0 ? "..." : "";
1187
+ const suffix = end < text.length ? "..." : "";
1188
+ return `${prefix}${text.slice(start, end)}${suffix}`
1189
+ .replace(/\s+/g, " ")
1190
+ .trim();
1191
+ }
1192
+ //# sourceMappingURL=corpus-jobs.js.map