deepline 0.3.16 → 0.3.17

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.
@@ -88,6 +88,10 @@ import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-mani
88
88
  import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
89
89
  import { RUNTIME_ENVIRONMENT_TOKEN_HEADER } from '../../shared_libs/play-runtime/coordinator-headers.js';
90
90
  import { resolveTheirstackClientTimeoutMs } from '../../shared_libs/integrations/theirstack-execution-policy.js';
91
+ import {
92
+ BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS,
93
+ usesExtendedBetterContactLauncherBudget,
94
+ } from '../../shared_libs/integrations/bettercontact-execution-policy.js';
91
95
  import {
92
96
  normalizePlayRuntimeEnvironment,
93
97
  normalizePlayRuntimeNamespace,
@@ -460,6 +464,9 @@ function resolveToolExecuteTimeoutMs(
460
464
  input,
461
465
  );
462
466
  if (theirstackTimeoutMs !== null) return theirstackTimeoutMs;
467
+ if (usesExtendedBetterContactLauncherBudget(normalized)) {
468
+ return BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS;
469
+ }
463
470
  return normalized === 'deeplineagent' ||
464
471
  normalized === 'deeplineagent_deeplineagent' ||
465
472
  normalized === 'ai_inference' ||
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
192
192
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
193
193
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
194
194
  // getters keep their established compatibility behavior.
195
- version: '0.3.16',
195
+ version: '0.3.17',
196
196
  updateSummary:
197
197
  'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
198
198
  contracts: {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * BetterContact launchers are upstream-async but Deepline gives callers a
3
+ * bounded synchronous convenience wait before returning a pollable request
4
+ * id. Keep the provider and SDK budgets together so the client cannot abandon
5
+ * a launched job while the server is still inside that documented path.
6
+ */
7
+
8
+ /** Per-request upstream timeout used by BetterContact launch and poll calls. */
9
+ export const BETTERCONTACT_UPSTREAM_REQUEST_TIMEOUT_MS = 30_000;
10
+
11
+ /**
12
+ * Client budget for a BetterContact launcher response.
13
+ *
14
+ * Covers route cold start, the bounded synchronous wait, one in-flight
15
+ * upstream request, and response serialization without widening unrelated
16
+ * tool requests.
17
+ */
18
+ export const BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS = 120_000;
19
+
20
+ const BETTERCONTACT_LAUNCHER_TOOL_IDS = new Set([
21
+ 'bettercontact_enrich',
22
+ 'bettercontact_bulk_enrich',
23
+ ]);
24
+
25
+ export function usesExtendedBetterContactLauncherBudget(
26
+ toolId: string,
27
+ ): boolean {
28
+ return BETTERCONTACT_LAUNCHER_TOOL_IDS.has(toolId.trim().toLowerCase());
29
+ }
@@ -102,16 +102,31 @@ export async function executeChunkedRequests<TRequest, TResult>(input: {
102
102
  input.weightOf,
103
103
  );
104
104
  for (const chunk of chunks) {
105
+ // notifyChain serializes onChunkComplete calls (the caller's callback can
106
+ // touch shared, non-concurrency-safe state) without letting one entry's
107
+ // callback failure skip a sibling's callback. A naive `.then()` chain
108
+ // would propagate a rejection forward and silently drop every queued
109
+ // notify() behind it -- for BetterContact/native-batch callers that
110
+ // means a correlation-mismatch throw on one row would leak the tool
111
+ // slots of every other row in the same chunk, since their
112
+ // onChunkComplete (and its `finally { releaseToolSlot() }`) would never
113
+ // run. Each callback failure is caught and re-thrown only after every
114
+ // queued entry in the chunk has had its callback invoked.
105
115
  let notifyChain: Promise<void> = Promise.resolve();
116
+ let firstCallbackFailure: unknown;
106
117
  const notify = async (
107
118
  entry: ChunkExecutionResult<TRequest, TResult>,
108
119
  ): Promise<void> => {
109
120
  if (input.retainResults !== false) {
110
121
  results.push(entry);
111
122
  }
112
- notifyChain = notifyChain.then(
113
- async () => await input.onChunkComplete?.([entry]),
114
- );
123
+ notifyChain = notifyChain.then(async () => {
124
+ try {
125
+ await input.onChunkComplete?.([entry]);
126
+ } catch (error) {
127
+ firstCallbackFailure ??= error;
128
+ }
129
+ });
115
130
  await notifyChain;
116
131
  };
117
132
 
@@ -135,6 +150,7 @@ export async function executeChunkedRequests<TRequest, TResult>(input: {
135
150
  }),
136
151
  );
137
152
  await notifyChain;
153
+ if (firstCallbackFailure !== undefined) throw firstCallbackFailure;
138
154
  }
139
155
 
140
156
  return results;
@@ -0,0 +1,726 @@
1
+ import {
2
+ defineBatchStrategyMap,
3
+ type BatchOperationStrategy,
4
+ } from './batching-types';
5
+
6
+ const BETTERCONTACT_BATCH_ITEM_KEY = 'deepline_batch_item_key';
7
+ const BETTERCONTACT_BATCH_SIZE = 100;
8
+ const BETTERCONTACT_SCOPED_REQUEST_PREFIX = 'deepline-bc-batch-v1:';
9
+ const BETTERCONTACT_DEFAULT_POLL_INTERVAL_MS = 2_000;
10
+ const BETTERCONTACT_MAX_SYNC_WAIT_MS = 30_000;
11
+ const BETTERCONTACT_SINGLE_PAYLOAD_FIELDS = new Set([
12
+ 'first_name',
13
+ 'last_name',
14
+ 'company_domain',
15
+ 'company',
16
+ 'linkedin_url',
17
+ 'custom_fields',
18
+ 'enrich_email_address',
19
+ 'enrich_phone_number',
20
+ 'wait_for_completion',
21
+ 'poll_interval_ms',
22
+ 'max_wait_ms',
23
+ ]);
24
+
25
+ type BetterContactCustomFields = Record<string, unknown>;
26
+
27
+ type BetterContactContact = {
28
+ first_name: string;
29
+ last_name: string;
30
+ company_domain?: string;
31
+ company?: string;
32
+ linkedin_url?: string;
33
+ custom_fields?: BetterContactCustomFields;
34
+ };
35
+
36
+ type BetterContactControls = {
37
+ enrich_email_address?: boolean;
38
+ enrich_phone_number?: boolean;
39
+ wait_for_completion?: boolean;
40
+ poll_interval_ms?: number;
41
+ max_wait_ms?: number;
42
+ };
43
+
44
+ type BetterContactSinglePayload = BetterContactContact & BetterContactControls;
45
+
46
+ type BetterContactBulkPayload = BetterContactControls & {
47
+ contacts: BetterContactContact[];
48
+ };
49
+
50
+ type BetterContactResult = Record<string, unknown> & {
51
+ data?: unknown[];
52
+ };
53
+
54
+ type BetterContactBatchResult = BetterContactResult | unknown[];
55
+
56
+ export type BetterContactScopedRequest = {
57
+ requestId: string;
58
+ correlationField: string;
59
+ correlationValue: string;
60
+ };
61
+
62
+ // Fields BetterContact's AsyncGetResponse reports once for the whole batch
63
+ // request, never per contact: `summary` (a batch-wide breakdown),
64
+ // `credits_consumed`/`credits_left` (the request's total spend/remaining
65
+ // balance). Billing settlement reads these from BetterContact's own async
66
+ // status endpoint independently (see billing.ts's fetchTruth), never from
67
+ // this per-row public shape, so stripping them here only affects what a
68
+ // caller sees on each row -- it cannot desync actual charging. Left
69
+ // unstripped, a 20-contact batch that spent 20 credits total would report
70
+ // `credits_consumed: 20` on every one of the 20 rows, misrepresenting a
71
+ // per-request total as a per-row cost.
72
+ const BETTERCONTACT_BATCH_LEVEL_ONLY_FIELDS = [
73
+ 'summary',
74
+ 'credits_consumed',
75
+ 'credits_left',
76
+ ] as const;
77
+
78
+ function withoutBatchLevelSummary(
79
+ result: BetterContactResult,
80
+ logicalItemCount: number,
81
+ ): BetterContactResult {
82
+ if (logicalItemCount <= 1) {
83
+ return result;
84
+ }
85
+ const perItemResult = { ...result };
86
+ for (const field of BETTERCONTACT_BATCH_LEVEL_ONLY_FIELDS) {
87
+ delete perItemResult[field];
88
+ }
89
+ return perItemResult;
90
+ }
91
+
92
+ function stableValue(value: unknown): unknown {
93
+ if (Array.isArray(value)) {
94
+ return value.map(stableValue);
95
+ }
96
+ if (value && typeof value === 'object') {
97
+ return Object.fromEntries(
98
+ Object.entries(value as Record<string, unknown>)
99
+ .filter(([, entry]) => entry !== undefined)
100
+ .sort(([left], [right]) => left.localeCompare(right))
101
+ .map(([key, entry]) => [key, stableValue(entry)]),
102
+ );
103
+ }
104
+ return value;
105
+ }
106
+
107
+ function stableStringify(value: unknown): string {
108
+ return JSON.stringify(stableValue(value));
109
+ }
110
+
111
+ function shortStableHash(value: string): string {
112
+ let hash = 0x811c9dc5;
113
+ for (let index = 0; index < value.length; index += 1) {
114
+ hash ^= value.charCodeAt(index);
115
+ hash = Math.imul(hash, 0x01000193) >>> 0;
116
+ }
117
+ return hash.toString(36);
118
+ }
119
+
120
+ function normalizedControls(payload: BetterContactControls) {
121
+ const waitForCompletion = payload.wait_for_completion !== false;
122
+ return {
123
+ enrich_phone_number: payload.enrich_phone_number === true,
124
+ wait_for_completion: waitForCompletion,
125
+ ...(waitForCompletion
126
+ ? {
127
+ poll_interval_ms:
128
+ payload.poll_interval_ms ?? BETTERCONTACT_DEFAULT_POLL_INTERVAL_MS,
129
+ max_wait_ms: Math.min(
130
+ payload.max_wait_ms ?? BETTERCONTACT_MAX_SYNC_WAIT_MS,
131
+ BETTERCONTACT_MAX_SYNC_WAIT_MS,
132
+ ),
133
+ }
134
+ : {}),
135
+ };
136
+ }
137
+
138
+ function controlsKey(payload: BetterContactControls): string {
139
+ return stableStringify(normalizedControls(payload));
140
+ }
141
+
142
+ function copyControls(payload: BetterContactControls): BetterContactControls {
143
+ return {
144
+ ...(payload.enrich_email_address !== undefined
145
+ ? { enrich_email_address: payload.enrich_email_address }
146
+ : {}),
147
+ ...(payload.enrich_phone_number !== undefined
148
+ ? { enrich_phone_number: payload.enrich_phone_number }
149
+ : {}),
150
+ ...(payload.wait_for_completion !== undefined
151
+ ? { wait_for_completion: payload.wait_for_completion }
152
+ : {}),
153
+ ...(payload.poll_interval_ms !== undefined
154
+ ? { poll_interval_ms: payload.poll_interval_ms }
155
+ : {}),
156
+ ...(payload.max_wait_ms !== undefined
157
+ ? { max_wait_ms: payload.max_wait_ms }
158
+ : {}),
159
+ };
160
+ }
161
+
162
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
163
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
164
+ }
165
+
166
+ function hasValidOptionalBoolean(
167
+ payload: Record<string, unknown>,
168
+ field: string,
169
+ ): boolean {
170
+ return payload[field] === undefined || typeof payload[field] === 'boolean';
171
+ }
172
+
173
+ function hasValidOptionalInteger(
174
+ payload: Record<string, unknown>,
175
+ field: string,
176
+ minimum: number,
177
+ maximum: number,
178
+ ): boolean {
179
+ const value = payload[field];
180
+ return (
181
+ value === undefined ||
182
+ (typeof value === 'number' &&
183
+ Number.isInteger(value) &&
184
+ value >= minimum &&
185
+ value <= maximum)
186
+ );
187
+ }
188
+
189
+ function hasValidControls(payload: BetterContactControls): boolean {
190
+ const record = payload as Record<string, unknown>;
191
+ return (
192
+ hasValidOptionalBoolean(record, 'enrich_email_address') &&
193
+ hasValidOptionalBoolean(record, 'enrich_phone_number') &&
194
+ hasValidOptionalBoolean(record, 'wait_for_completion') &&
195
+ hasValidOptionalInteger(record, 'poll_interval_ms', 250, 10_000) &&
196
+ hasValidOptionalInteger(record, 'max_wait_ms', 1_000, 120_000)
197
+ );
198
+ }
199
+
200
+ function hasValidCustomFields(contact: BetterContactContact): boolean {
201
+ const customFields = (contact as Record<string, unknown>).custom_fields;
202
+ return customFields === undefined || isPlainRecord(customFields);
203
+ }
204
+
205
+ function isBatchableContact(contact: BetterContactContact): boolean {
206
+ return hasRequiredContactFields(contact) && hasValidCustomFields(contact);
207
+ }
208
+
209
+ function hasOnlyKnownSinglePayloadFields(
210
+ payload: BetterContactSinglePayload,
211
+ ): boolean {
212
+ return Object.keys(payload).every((field) =>
213
+ BETTERCONTACT_SINGLE_PAYLOAD_FIELDS.has(field),
214
+ );
215
+ }
216
+
217
+ function isBatchableSinglePayload(
218
+ payload: BetterContactSinglePayload,
219
+ ): boolean {
220
+ return (
221
+ isBatchableContact(payload) &&
222
+ hasValidControls(payload) &&
223
+ hasOnlyKnownSinglePayloadFields(payload)
224
+ );
225
+ }
226
+
227
+ function contactIdentity(contact: BetterContactContact): string {
228
+ return stableStringify({
229
+ first_name: contact.first_name,
230
+ last_name: contact.last_name,
231
+ company_domain: contact.company_domain ?? null,
232
+ company: contact.company ?? null,
233
+ linkedin_url: contact.linkedin_url ?? null,
234
+ custom_fields: contact.custom_fields ?? null,
235
+ });
236
+ }
237
+
238
+ function buildItemKey(contact: BetterContactContact, index = 0): string {
239
+ return `dl_bc_${index}_${shortStableHash(contactIdentity(contact))}`;
240
+ }
241
+
242
+ function withItemKey(
243
+ contact: BetterContactContact,
244
+ itemKey: string,
245
+ ): BetterContactContact {
246
+ const customFields = contact.custom_fields ?? {};
247
+ const correlationFieldBase = `${BETTERCONTACT_BATCH_ITEM_KEY}_${shortStableHash(
248
+ itemKey,
249
+ )}`;
250
+ let correlationField = correlationFieldBase;
251
+ let suffix = 1;
252
+ while (Object.prototype.hasOwnProperty.call(customFields, correlationField)) {
253
+ correlationField = `${correlationFieldBase}_${suffix}`;
254
+ suffix += 1;
255
+ }
256
+ return {
257
+ ...contact,
258
+ custom_fields: {
259
+ ...customFields,
260
+ [correlationField]: itemKey,
261
+ },
262
+ };
263
+ }
264
+
265
+ function resultCustomFieldRecords(
266
+ row: unknown,
267
+ ): Array<Record<string, unknown>> {
268
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
269
+ return [];
270
+ }
271
+ const customFields = (row as Record<string, unknown>).custom_fields;
272
+ return (Array.isArray(customFields) ? customFields : [customFields]).filter(
273
+ (candidate): candidate is Record<string, unknown> =>
274
+ Boolean(candidate) &&
275
+ typeof candidate === 'object' &&
276
+ !Array.isArray(candidate),
277
+ );
278
+ }
279
+
280
+ function encodeScopedRequest(scope: BetterContactScopedRequest): string {
281
+ return `${BETTERCONTACT_SCOPED_REQUEST_PREFIX}${encodeURIComponent(
282
+ JSON.stringify([
283
+ scope.requestId,
284
+ scope.correlationField,
285
+ scope.correlationValue,
286
+ ]),
287
+ )}`;
288
+ }
289
+
290
+ export function parseBetterContactScopedRequest(
291
+ requestId: string,
292
+ ): BetterContactScopedRequest | null {
293
+ if (!requestId.startsWith(BETTERCONTACT_SCOPED_REQUEST_PREFIX)) {
294
+ return null;
295
+ }
296
+ try {
297
+ const parsed = JSON.parse(
298
+ decodeURIComponent(
299
+ requestId.slice(BETTERCONTACT_SCOPED_REQUEST_PREFIX.length),
300
+ ),
301
+ ) as unknown;
302
+ if (
303
+ !Array.isArray(parsed) ||
304
+ parsed.length !== 3 ||
305
+ parsed.some((value) => typeof value !== 'string' || value.length === 0)
306
+ ) {
307
+ return null;
308
+ }
309
+ return {
310
+ requestId: parsed[0] as string,
311
+ correlationField: parsed[1] as string,
312
+ correlationValue: parsed[2] as string,
313
+ };
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
318
+
319
+ function rowMatchesCorrelation(
320
+ row: unknown,
321
+ correlationField: string,
322
+ correlationValue: string,
323
+ ): boolean {
324
+ return resultCustomFieldRecords(row).some(
325
+ (fields) => fields[correlationField] === correlationValue,
326
+ );
327
+ }
328
+
329
+ function withoutCorrelationSelector(
330
+ row: unknown,
331
+ correlationField: string,
332
+ ): unknown {
333
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
334
+ return row;
335
+ }
336
+ const record = row as Record<string, unknown>;
337
+ const customFields = record.custom_fields;
338
+ const cleanRecord = (fields: Record<string, unknown>) => {
339
+ const cleaned = { ...fields };
340
+ delete cleaned[correlationField];
341
+ return cleaned;
342
+ };
343
+ return {
344
+ ...record,
345
+ custom_fields: Array.isArray(customFields)
346
+ ? customFields
347
+ .filter(isPlainRecord)
348
+ .map(cleanRecord)
349
+ .filter((fields) => Object.keys(fields).length > 0)
350
+ : isPlainRecord(customFields)
351
+ ? cleanRecord(customFields)
352
+ : customFields,
353
+ };
354
+ }
355
+
356
+ export function isolateBetterContactScopedResult(
357
+ result: unknown,
358
+ scopedRequestId: string,
359
+ ): unknown {
360
+ const scope = parseBetterContactScopedRequest(scopedRequestId);
361
+ if (!scope || !isPlainRecord(result)) {
362
+ return result;
363
+ }
364
+ const scopedResult: BetterContactResult = { ...result, id: scopedRequestId };
365
+ delete scopedResult.summary;
366
+ if (!Array.isArray(result.data)) {
367
+ return scopedResult;
368
+ }
369
+ if (result.data.length === 0) {
370
+ return { ...scopedResult, data: [] };
371
+ }
372
+ const matches = result.data.filter((row) =>
373
+ rowMatchesCorrelation(row, scope.correlationField, scope.correlationValue),
374
+ );
375
+ if (matches.length !== 1) {
376
+ throw new Error(
377
+ matches.length === 0
378
+ ? 'BetterContact scoped result has missing or unmatched correlation identity.'
379
+ : 'BetterContact scoped result has ambiguous correlation identity.',
380
+ );
381
+ }
382
+ return {
383
+ ...scopedResult,
384
+ data: [withoutCorrelationSelector(matches[0], scope.correlationField)],
385
+ };
386
+ }
387
+
388
+ function correlationEntryForItem(item: {
389
+ itemKey: string;
390
+ payload: BetterContactContact;
391
+ }): [string, unknown] | null {
392
+ const keyedContact = withItemKey(item.payload, item.itemKey);
393
+ const originalFields = item.payload.custom_fields ?? {};
394
+ return (
395
+ Object.entries(keyedContact.custom_fields ?? {}).find(
396
+ ([field]) => !Object.prototype.hasOwnProperty.call(originalFields, field),
397
+ ) ?? null
398
+ );
399
+ }
400
+
401
+ function resultMatchesItem(
402
+ row: unknown,
403
+ item: { itemKey: string; payload: BetterContactContact },
404
+ ): boolean {
405
+ const correlationEntry = correlationEntryForItem(item);
406
+ if (!correlationEntry) {
407
+ return false;
408
+ }
409
+ const [correlationField, correlationValue] = correlationEntry;
410
+ return rowMatchesCorrelation(row, correlationField, String(correlationValue));
411
+ }
412
+
413
+ function withoutCorrelationField(
414
+ row: unknown,
415
+ item: { itemKey: string; payload: BetterContactContact },
416
+ ): unknown {
417
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
418
+ return row;
419
+ }
420
+ const correlationField = correlationEntryForItem(item)?.[0];
421
+ if (!correlationField) {
422
+ return row;
423
+ }
424
+ return withoutCorrelationSelector(row, correlationField);
425
+ }
426
+
427
+ function scopedRequestIdForItem(
428
+ requestId: string,
429
+ item: { itemKey: string; payload: BetterContactContact },
430
+ ): string {
431
+ const correlationEntry = correlationEntryForItem(item);
432
+ if (!correlationEntry) {
433
+ throw new Error(
434
+ 'BetterContact batch item is missing correlation identity.',
435
+ );
436
+ }
437
+ return encodeScopedRequest({
438
+ requestId,
439
+ correlationField: correlationEntry[0],
440
+ correlationValue: String(correlationEntry[1]),
441
+ });
442
+ }
443
+
444
+ function withScopedRequestId<TPayload extends Record<string, unknown>>(
445
+ result: BetterContactResult,
446
+ item: { itemKey: string; payload: TPayload },
447
+ logicalItemCount: number,
448
+ ): BetterContactResult {
449
+ const requestId = typeof result.id === 'string' ? result.id.trim() : '';
450
+ if (!requestId || logicalItemCount <= 1) {
451
+ return result;
452
+ }
453
+ return {
454
+ ...result,
455
+ id: scopedRequestIdForItem(requestId, {
456
+ itemKey: item.itemKey,
457
+ payload: item.payload as unknown as BetterContactContact,
458
+ }),
459
+ };
460
+ }
461
+
462
+ function hasRequiredContactFields(contact: BetterContactContact): boolean {
463
+ const companyDomain =
464
+ typeof contact.company_domain === 'string' &&
465
+ contact.company_domain.trim().length > 0
466
+ ? contact.company_domain
467
+ : contact.company;
468
+ return (
469
+ typeof contact.first_name === 'string' &&
470
+ contact.first_name.trim().length > 0 &&
471
+ typeof contact.last_name === 'string' &&
472
+ contact.last_name.trim().length > 0 &&
473
+ typeof companyDomain === 'string' &&
474
+ companyDomain.trim().length > 0
475
+ );
476
+ }
477
+
478
+ function splitBetterContactResult<TPayload extends Record<string, unknown>>(
479
+ fullResult: BetterContactBatchResult,
480
+ compiled: {
481
+ items: Array<{ itemKey: string; payload: TPayload }>;
482
+ },
483
+ ) {
484
+ const resultRows = Array.isArray(fullResult)
485
+ ? fullResult
486
+ : Array.isArray(fullResult.data)
487
+ ? fullResult.data
488
+ : null;
489
+ if (!resultRows) {
490
+ return compiled.items.map((item) => ({
491
+ itemKey: item.itemKey,
492
+ result: {
493
+ data: Array.isArray(fullResult)
494
+ ? fullResult
495
+ : withScopedRequestId(
496
+ withoutBatchLevelSummary(fullResult, compiled.items.length),
497
+ item,
498
+ compiled.items.length,
499
+ ),
500
+ },
501
+ rawResult: fullResult,
502
+ }));
503
+ }
504
+ if (resultRows.length === 0) {
505
+ return compiled.items.map((item) => ({
506
+ itemKey: item.itemKey,
507
+ result: {
508
+ data: Array.isArray(fullResult)
509
+ ? []
510
+ : withScopedRequestId(
511
+ {
512
+ ...withoutBatchLevelSummary(fullResult, compiled.items.length),
513
+ data: [],
514
+ },
515
+ item,
516
+ compiled.items.length,
517
+ ),
518
+ },
519
+ rawResult: null,
520
+ }));
521
+ }
522
+
523
+ const remainingItems = new Map(
524
+ compiled.items.map((item) => [item.itemKey, item] as const),
525
+ );
526
+ const rowsByItemKey = new Map<string, unknown>();
527
+ for (const row of resultRows) {
528
+ const matches = [...remainingItems.values()].filter((item) =>
529
+ resultMatchesItem(row, {
530
+ itemKey: item.itemKey,
531
+ payload: item.payload as unknown as BetterContactContact,
532
+ }),
533
+ );
534
+ if (matches.length !== 1) {
535
+ throw new Error(
536
+ matches.length === 0
537
+ ? 'BetterContact bulk result has missing or unmatched correlation identity.'
538
+ : 'BetterContact bulk result has ambiguous correlation identity.',
539
+ );
540
+ }
541
+ const matchedItem = matches[0]!;
542
+ remainingItems.delete(matchedItem.itemKey);
543
+ rowsByItemKey.set(matchedItem.itemKey, row);
544
+ }
545
+
546
+ return compiled.items.map((item) => {
547
+ const matchedRow = rowsByItemKey.get(item.itemKey);
548
+ if (!matchedRow) {
549
+ throw new Error(
550
+ `BetterContact bulk result is missing result identity ${item.itemKey}.`,
551
+ );
552
+ }
553
+ const publicRow = withoutCorrelationField(matchedRow, {
554
+ itemKey: item.itemKey,
555
+ payload: item.payload as unknown as BetterContactContact,
556
+ });
557
+ const resultData = Array.isArray(fullResult)
558
+ ? [publicRow]
559
+ : withScopedRequestId(
560
+ {
561
+ ...withoutBatchLevelSummary(fullResult, compiled.items.length),
562
+ data: [publicRow],
563
+ },
564
+ item,
565
+ compiled.items.length,
566
+ );
567
+ return {
568
+ itemKey: item.itemKey,
569
+ result: { data: resultData },
570
+ rawResult: publicRow,
571
+ };
572
+ });
573
+ }
574
+
575
+ const sharedStrategyFields = {
576
+ batchOperation: 'bettercontact_bulk_enrich' as const,
577
+ kind: 'identifier_batch' as const,
578
+ maxBatchSize: BETTERCONTACT_BATCH_SIZE,
579
+ bucketKeyPayloadFields: ['enrich_phone_number'],
580
+ };
581
+
582
+ const bettercontactSingleBatchStrategy: BatchOperationStrategy<
583
+ BetterContactSinglePayload,
584
+ BetterContactBulkPayload,
585
+ BetterContactBatchResult,
586
+ { data: BetterContactBatchResult },
587
+ unknown
588
+ > = {
589
+ ...sharedStrategyFields,
590
+ sourceOperation: 'bettercontact_enrich',
591
+ canBatchWith(left, right) {
592
+ return (
593
+ isBatchableSinglePayload(left) &&
594
+ isBatchableSinglePayload(right) &&
595
+ controlsKey(left) === controlsKey(right)
596
+ );
597
+ },
598
+ toBucketKey(payload) {
599
+ if (!isBatchableSinglePayload(payload)) {
600
+ return `bettercontact_enrich:invalid:${stableStringify(payload)}`;
601
+ }
602
+ return `bettercontact_bulk_enrich:${controlsKey(payload)}`;
603
+ },
604
+ toItemKey(payload) {
605
+ return buildItemKey(payload);
606
+ },
607
+ compile(payloads) {
608
+ const first = payloads[0] ?? ({} as BetterContactSinglePayload);
609
+ const items = payloads.map((payload, index) => ({
610
+ itemKey: buildItemKey(payload, index),
611
+ payload,
612
+ }));
613
+ return {
614
+ batchOperation: 'bettercontact_bulk_enrich',
615
+ batchPayload: {
616
+ ...copyControls(first),
617
+ contacts: items.map((item) =>
618
+ isBatchableSinglePayload(item.payload)
619
+ ? withItemKey(item.payload, item.itemKey)
620
+ : item.payload,
621
+ ),
622
+ },
623
+ items,
624
+ };
625
+ },
626
+ splitResult: splitBetterContactResult,
627
+ };
628
+
629
+ function readBulkContacts(
630
+ payload: BetterContactBulkPayload,
631
+ ): BetterContactContact[] {
632
+ return Array.isArray(payload.contacts) ? payload.contacts : [];
633
+ }
634
+
635
+ function isOneContactBulkPayload(payload: BetterContactBulkPayload): boolean {
636
+ return readBulkContacts(payload).length === 1;
637
+ }
638
+
639
+ const bettercontactBulkSelfBatchStrategy: BatchOperationStrategy<
640
+ BetterContactBulkPayload,
641
+ BetterContactBulkPayload,
642
+ BetterContactBatchResult,
643
+ { data: BetterContactBatchResult },
644
+ unknown
645
+ > = {
646
+ ...sharedStrategyFields,
647
+ sourceOperation: 'bettercontact_bulk_enrich',
648
+ canBatchWith(left, right) {
649
+ return (
650
+ isOneContactBulkPayload(left) &&
651
+ isOneContactBulkPayload(right) &&
652
+ isBatchableContact(readBulkContacts(left)[0]!) &&
653
+ isBatchableContact(readBulkContacts(right)[0]!) &&
654
+ hasValidControls(left) &&
655
+ hasValidControls(right) &&
656
+ controlsKey(left) === controlsKey(right)
657
+ );
658
+ },
659
+ toBucketKey(payload) {
660
+ if (!isOneContactBulkPayload(payload)) {
661
+ return `bettercontact_bulk_enrich:passthrough:${stableStringify(payload)}`;
662
+ }
663
+ if (
664
+ !isBatchableContact(readBulkContacts(payload)[0]!) ||
665
+ !hasValidControls(payload)
666
+ ) {
667
+ return `bettercontact_bulk_enrich:invalid:${stableStringify(payload)}`;
668
+ }
669
+ return `bettercontact_bulk_enrich:${controlsKey(payload)}`;
670
+ },
671
+ toItemKey(payload) {
672
+ return buildItemKey(
673
+ readBulkContacts(payload)[0] ?? ({} as BetterContactContact),
674
+ );
675
+ },
676
+ compile(payloads) {
677
+ const first = payloads[0] ?? ({ contacts: [] } as BetterContactBulkPayload);
678
+ if (payloads.length === 1 && !isOneContactBulkPayload(first)) {
679
+ return {
680
+ batchOperation: 'bettercontact_bulk_enrich',
681
+ batchPayload: first,
682
+ items: [{ itemKey: this.toItemKey(first), payload: first }],
683
+ };
684
+ }
685
+
686
+ const items = payloads.map((payload, index) => ({
687
+ itemKey: buildItemKey(readBulkContacts(payload)[0]!, index),
688
+ payload,
689
+ }));
690
+ return {
691
+ batchOperation: 'bettercontact_bulk_enrich',
692
+ batchPayload: {
693
+ ...copyControls(first),
694
+ contacts: items.map((item) => {
695
+ const contact = readBulkContacts(item.payload)[0]!;
696
+ return isBatchableContact(contact) && hasValidControls(item.payload)
697
+ ? withItemKey(contact, item.itemKey)
698
+ : contact;
699
+ }),
700
+ },
701
+ items,
702
+ };
703
+ },
704
+ splitResult(fullResult, compiled) {
705
+ const first = compiled.items[0];
706
+ if (
707
+ compiled.items.length === 1 &&
708
+ first &&
709
+ !isOneContactBulkPayload(first.payload)
710
+ ) {
711
+ return [
712
+ {
713
+ itemKey: first.itemKey,
714
+ result: { data: fullResult },
715
+ rawResult: fullResult,
716
+ },
717
+ ];
718
+ }
719
+ return splitBetterContactResult(fullResult, compiled);
720
+ },
721
+ };
722
+
723
+ export const bettercontactBatchStrategies = defineBatchStrategyMap({
724
+ bettercontact_enrich: bettercontactSingleBatchStrategy,
725
+ bettercontact_bulk_enrich: bettercontactBulkSelfBatchStrategy,
726
+ });
@@ -5369,6 +5369,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
5369
5369
  entries: Array<{
5370
5370
  request: ToolCallRequest;
5371
5371
  result: unknown | null;
5372
+ status?: string;
5372
5373
  metadata?: ToolResultMetadataInput | null;
5373
5374
  jobId?: string;
5374
5375
  meta?: Record<string, unknown>;
@@ -5380,7 +5381,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
5380
5381
  ...entry,
5381
5382
  wrapped: await this.wrapToolExecutionResult({
5382
5383
  toolId,
5383
- status: entry.result == null ? 'no_result' : 'completed',
5384
+ status:
5385
+ entry.status ?? (entry.result == null ? 'no_result' : 'completed'),
5384
5386
  jobId: entry.jobId,
5385
5387
  result: entry.result,
5386
5388
  metadata: entry.metadata,
@@ -11282,12 +11284,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11282
11284
  }
11283
11285
  continue;
11284
11286
  }
11287
+ const batchExecution = entry.result;
11285
11288
  try {
11286
11289
  const splitResults =
11287
- entry.result != null
11290
+ batchExecution != null
11288
11291
  ? entry.request.splitResults(
11289
11292
  legacyResultForBatchSplitter(
11290
- entry.result.execution,
11293
+ batchExecution.execution,
11291
11294
  ),
11292
11295
  )
11293
11296
  : entry.request.memberRequests.map(() => null);
@@ -11298,11 +11301,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11298
11301
  (request, index) => ({
11299
11302
  request,
11300
11303
  result: splitResults[index] ?? null,
11304
+ status: batchExecution?.execution.status,
11301
11305
  toolResponse:
11302
- entry.result == null
11306
+ batchExecution == null
11303
11307
  ? undefined
11304
11308
  : publicToolResponseForBatchedItem(
11305
- entry.result.execution,
11309
+ batchExecution.execution,
11306
11310
  splitResults[index] ?? null,
11307
11311
  this.currentToolResponseContract ===
11308
11312
  RAW_V2_TOOL_RESPONSE_CONTRACT,
@@ -11323,7 +11327,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11323
11327
  resolveLiveFollowers(request, resolvedResults[index]);
11324
11328
  }
11325
11329
  } finally {
11326
- entry.result?.releaseToolSlot();
11330
+ batchExecution?.releaseToolSlot();
11327
11331
  }
11328
11332
  }
11329
11333
 
@@ -11340,6 +11344,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11340
11344
  const completionBuffer: Array<{
11341
11345
  request: ToolCallRequest;
11342
11346
  result: unknown | null;
11347
+ status?: string;
11343
11348
  metadata?: ToolResultMetadataInput | null;
11344
11349
  jobId?: string;
11345
11350
  meta?: Record<string, unknown>;
@@ -11359,6 +11364,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11359
11364
  entries.map((entry) => ({
11360
11365
  request: entry.request,
11361
11366
  result: entry.result,
11367
+ status: entry.status,
11362
11368
  metadata: entry.metadata,
11363
11369
  jobId: entry.jobId,
11364
11370
  meta: entry.meta,
@@ -11392,6 +11398,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11392
11398
  completionBuffer.push({
11393
11399
  request,
11394
11400
  result: execution.result ?? null,
11401
+ status: execution.status,
11395
11402
  metadata: execution.metadata ?? null,
11396
11403
  jobId: execution.jobId,
11397
11404
  meta: execution.meta,
@@ -1,4 +1,5 @@
1
1
  import type { AnyBatchOperationStrategy } from './batching-types';
2
+ import { bettercontactBatchStrategies } from './bettercontact-batching';
2
3
  import { DEFAULT_PLAY_RUNTIME_BATCH_STRATEGIES } from './default-batch-strategies';
3
4
  import { fullenrichBatchStrategies } from './fullenrich-batching';
4
5
  import { opensosdataBatchStrategies } from './opensosdata-batching';
@@ -8,6 +9,7 @@ export const PLAY_RUNTIME_BATCH_OPERATION_REGISTRY: Record<
8
9
  AnyBatchOperationStrategy
9
10
  > = {
10
11
  ...DEFAULT_PLAY_RUNTIME_BATCH_STRATEGIES,
12
+ ...bettercontactBatchStrategies,
11
13
  ...fullenrichBatchStrategies,
12
14
  ...opensosdataBatchStrategies,
13
15
  };
package/dist/cli/index.js CHANGED
@@ -1047,7 +1047,7 @@ var SDK_RELEASE = {
1047
1047
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1048
1048
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1049
1049
  // getters keep their established compatibility behavior.
1050
- version: "0.3.16",
1050
+ version: "0.3.17",
1051
1051
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1052
1052
  contracts: {
1053
1053
  api: {
@@ -3772,6 +3772,16 @@ function resolveTheirstackClientTimeoutMs(endpointId, payload) {
3772
3772
  return usesExtendedTheirstackJobSearchBudget(endpointId, payload) ? THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS : null;
3773
3773
  }
3774
3774
 
3775
+ // ../shared_libs/integrations/bettercontact-execution-policy.ts
3776
+ var BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS = 12e4;
3777
+ var BETTERCONTACT_LAUNCHER_TOOL_IDS = /* @__PURE__ */ new Set([
3778
+ "bettercontact_enrich",
3779
+ "bettercontact_bulk_enrich"
3780
+ ]);
3781
+ function usesExtendedBetterContactLauncherBudget(toolId) {
3782
+ return BETTERCONTACT_LAUNCHER_TOOL_IDS.has(toolId.trim().toLowerCase());
3783
+ }
3784
+
3775
3785
  // ../shared_libs/play-runtime/backend.ts
3776
3786
  var PLAY_RUNTIME_BACKENDS = {
3777
3787
  localProcess: "local_process",
@@ -4052,6 +4062,9 @@ function resolveToolExecuteTimeoutMs(toolId, input2) {
4052
4062
  input2
4053
4063
  );
4054
4064
  if (theirstackTimeoutMs !== null) return theirstackTimeoutMs;
4065
+ if (usesExtendedBetterContactLauncherBudget(normalized)) {
4066
+ return BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS;
4067
+ }
4055
4068
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
4056
4069
  }
4057
4070
  var RUNS_FAILED_LOG_LIMIT = 20;
@@ -1033,7 +1033,7 @@ var SDK_RELEASE = {
1033
1033
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1034
1034
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1035
1035
  // getters keep their established compatibility behavior.
1036
- version: "0.3.16",
1036
+ version: "0.3.17",
1037
1037
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1038
1038
  contracts: {
1039
1039
  api: {
@@ -3758,6 +3758,16 @@ function resolveTheirstackClientTimeoutMs(endpointId, payload) {
3758
3758
  return usesExtendedTheirstackJobSearchBudget(endpointId, payload) ? THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS : null;
3759
3759
  }
3760
3760
 
3761
+ // ../shared_libs/integrations/bettercontact-execution-policy.ts
3762
+ var BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS = 12e4;
3763
+ var BETTERCONTACT_LAUNCHER_TOOL_IDS = /* @__PURE__ */ new Set([
3764
+ "bettercontact_enrich",
3765
+ "bettercontact_bulk_enrich"
3766
+ ]);
3767
+ function usesExtendedBetterContactLauncherBudget(toolId) {
3768
+ return BETTERCONTACT_LAUNCHER_TOOL_IDS.has(toolId.trim().toLowerCase());
3769
+ }
3770
+
3761
3771
  // ../shared_libs/play-runtime/backend.ts
3762
3772
  var PLAY_RUNTIME_BACKENDS = {
3763
3773
  localProcess: "local_process",
@@ -4038,6 +4048,9 @@ function resolveToolExecuteTimeoutMs(toolId, input2) {
4038
4048
  input2
4039
4049
  );
4040
4050
  if (theirstackTimeoutMs !== null) return theirstackTimeoutMs;
4051
+ if (usesExtendedBetterContactLauncherBudget(normalized)) {
4052
+ return BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS;
4053
+ }
4041
4054
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
4042
4055
  }
4043
4056
  var RUNS_FAILED_LOG_LIMIT = 20;
package/dist/index.js CHANGED
@@ -783,7 +783,7 @@ var SDK_RELEASE = {
783
783
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
784
784
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
785
785
  // getters keep their established compatibility behavior.
786
- version: "0.3.16",
786
+ version: "0.3.17",
787
787
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
788
788
  contracts: {
789
789
  api: {
@@ -3483,6 +3483,16 @@ function resolveTheirstackClientTimeoutMs(endpointId, payload) {
3483
3483
  return usesExtendedTheirstackJobSearchBudget(endpointId, payload) ? THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS : null;
3484
3484
  }
3485
3485
 
3486
+ // ../shared_libs/integrations/bettercontact-execution-policy.ts
3487
+ var BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS = 12e4;
3488
+ var BETTERCONTACT_LAUNCHER_TOOL_IDS = /* @__PURE__ */ new Set([
3489
+ "bettercontact_enrich",
3490
+ "bettercontact_bulk_enrich"
3491
+ ]);
3492
+ function usesExtendedBetterContactLauncherBudget(toolId) {
3493
+ return BETTERCONTACT_LAUNCHER_TOOL_IDS.has(toolId.trim().toLowerCase());
3494
+ }
3495
+
3486
3496
  // ../shared_libs/play-runtime/backend.ts
3487
3497
  var PLAY_RUNTIME_BACKENDS = {
3488
3498
  localProcess: "local_process",
@@ -3763,6 +3773,9 @@ function resolveToolExecuteTimeoutMs(toolId, input) {
3763
3773
  input
3764
3774
  );
3765
3775
  if (theirstackTimeoutMs !== null) return theirstackTimeoutMs;
3776
+ if (usesExtendedBetterContactLauncherBudget(normalized)) {
3777
+ return BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS;
3778
+ }
3766
3779
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
3767
3780
  }
3768
3781
  var RUNS_FAILED_LOG_LIMIT = 20;
package/dist/index.mjs CHANGED
@@ -706,7 +706,7 @@ var SDK_RELEASE = {
706
706
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
707
707
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
708
708
  // getters keep their established compatibility behavior.
709
- version: "0.3.16",
709
+ version: "0.3.17",
710
710
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
711
711
  contracts: {
712
712
  api: {
@@ -3406,6 +3406,16 @@ function resolveTheirstackClientTimeoutMs(endpointId, payload) {
3406
3406
  return usesExtendedTheirstackJobSearchBudget(endpointId, payload) ? THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS : null;
3407
3407
  }
3408
3408
 
3409
+ // ../shared_libs/integrations/bettercontact-execution-policy.ts
3410
+ var BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS = 12e4;
3411
+ var BETTERCONTACT_LAUNCHER_TOOL_IDS = /* @__PURE__ */ new Set([
3412
+ "bettercontact_enrich",
3413
+ "bettercontact_bulk_enrich"
3414
+ ]);
3415
+ function usesExtendedBetterContactLauncherBudget(toolId) {
3416
+ return BETTERCONTACT_LAUNCHER_TOOL_IDS.has(toolId.trim().toLowerCase());
3417
+ }
3418
+
3409
3419
  // ../shared_libs/play-runtime/backend.ts
3410
3420
  var PLAY_RUNTIME_BACKENDS = {
3411
3421
  localProcess: "local_process",
@@ -3686,6 +3696,9 @@ function resolveToolExecuteTimeoutMs(toolId, input) {
3686
3696
  input
3687
3697
  );
3688
3698
  if (theirstackTimeoutMs !== null) return theirstackTimeoutMs;
3699
+ if (usesExtendedBetterContactLauncherBudget(normalized)) {
3700
+ return BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS;
3701
+ }
3689
3702
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
3690
3703
  }
3691
3704
  var RUNS_FAILED_LOG_LIMIT = 20;
@@ -20,6 +20,7 @@
20
20
  "dist/bundling-sources/sdk/src/tool-output.ts",
21
21
  "dist/bundling-sources/sdk/src/types.ts",
22
22
  "dist/bundling-sources/sdk/src/version.ts",
23
+ "dist/bundling-sources/shared_libs/integrations/bettercontact-execution-policy.ts",
23
24
  "dist/bundling-sources/shared_libs/integrations/theirstack-execution-policy.ts",
24
25
  "dist/bundling-sources/shared_libs/observability/node-tracing.ts",
25
26
  "dist/bundling-sources/shared_libs/observability/redaction.ts",
@@ -36,6 +37,7 @@
36
37
  "dist/bundling-sources/shared_libs/play-runtime/backend.ts",
37
38
  "dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts",
38
39
  "dist/bundling-sources/shared_libs/play-runtime/batching-types.ts",
40
+ "dist/bundling-sources/shared_libs/play-runtime/bettercontact-batching.ts",
39
41
  "dist/bundling-sources/shared_libs/play-runtime/bounded-dispatch.ts",
40
42
  "dist/bundling-sources/shared_libs/play-runtime/builtin-pacing.ts",
41
43
  "dist/bundling-sources/shared_libs/play-runtime/cell-provenance.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",