@alvera-ai/platform-sdk 0.10.0-rc.20 → 0.10.0-rc.22

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.
@@ -28,7 +28,7 @@ const { data: created } = await api.aiAgents.create(
28
28
  name: 'Contact Us Triage Categorizer',
29
29
  description: 'Triages inbound submissions into appointment / job / spam',
30
30
 
31
- tool_id: chatCompletionToolId, // see §2 — must be a tool with intent: 'chat_completion'
31
+ tool_id: chatCompletionToolId, // see §2 — must be a tool with intent: 'llm_enrichment'
32
32
 
33
33
  model: 'llama3.2:3b', // provider-specific model identifier
34
34
  data_access: 'unregulated', // 'unregulated' | 'regulated' (compliance gate)
@@ -76,7 +76,7 @@ Respond with JSON: {"category": "..."}`,
76
76
 
77
77
  ### `tool_id` must point to a chat-completion tool
78
78
 
79
- The bound tool's `intent` MUST be `chat_completion`. Tools with
79
+ The bound tool's `intent` MUST be `llm_enrichment`. Tools with
80
80
  any other intent (`sms`, `data_exchange`, `status_poller`, …)
81
81
  are rejected at create time as a 422 on `/tool_id`.
82
82
 
@@ -256,7 +256,7 @@ reference via `additional_context.<slug>`.
256
256
  ```
257
257
  name required string
258
258
  description optional string
259
- tool_id required UUID — must be intent: chat_completion
259
+ tool_id required UUID — must be intent: llm_enrichment
260
260
  model required string — provider-specific identifier
261
261
  data_access required enum — 'unregulated' | 'regulated'
262
262
  temperature required number
@@ -275,7 +275,7 @@ Standard JSON:API envelopes per `errors.md`. Common rejections:
275
275
 
276
276
  | `source.pointer` | Cause |
277
277
  |---------------------------------|----------------------------------------------------|
278
- | `/tool_id` | tool's intent isn't `chat_completion` |
278
+ | `/tool_id` | tool's intent isn't `llm_enrichment` |
279
279
  | `/llm_response_schema` | missing or not a valid JSON Schema object |
280
280
  | `/input_schema` | missing or not a valid JSON Schema object |
281
281
  | `/prompt_config/body` | references a field absent from `input_schema` |
@@ -390,7 +390,7 @@ with `{ status: 'skipped' }` and no per-agent entries.
390
390
  the schema means no structured output and downstream decision
391
391
  templates can't reference the agent's fields.
392
392
 
393
- 2. **`tool_id` must reference an `intent: 'chat_completion'`
393
+ 2. **`tool_id` must reference an `intent: 'llm_enrichment'`
394
394
  tool.** Other intents are rejected at create. The chat
395
395
  completion tool is typically `tool_body_type: 'rest_api'`
396
396
  against an OpenAI-compatible endpoint; the platform appends
@@ -229,17 +229,22 @@ and can be retried via `.syncRoutes`.
229
229
  ### Sync routes
230
230
 
231
231
  ```typescript
232
- const { data: app } = await api.connectedApps.syncRoutes(
232
+ const { data: ack } = await api.connectedApps.syncRoutes(
233
233
  tenantSlug, datalakeSlug, appId,
234
234
  )
235
- // app.status — 'synced' or 'error'
236
- // app.routes — refreshed from the primary URL's manifest
237
- // app.last_synced_at — refreshed when status is 'synced'
235
+ // 202 Accepted — the sync runs in a background worker:
236
+ // ack.status — 'enqueued'
237
+ // ack.job_id — background job id
238
+ // ack.connected_app_id — echo of the app id
239
+ // ack.enqueued_at — enqueue timestamp
238
240
  ```
239
241
 
240
- This is the explicit "re-fetch and save" verb. Use it after
241
- deploying a new manifest, fixing a primary URL outage, or
242
- recovering from `status: 'error'`.
242
+ This is the explicit "re-fetch and save" verb — asynchronous. The
243
+ 202 acknowledgement proves the app id resolved and a sync job was
244
+ accepted; the worker then refreshes `routes` + `last_synced_at` and
245
+ lands `status: 'synced'` (or `'error'`) on the app row. Poll
246
+ `.get()` for the outcome. Use it after deploying a new manifest,
247
+ fixing a primary URL outage, or recovering from `status: 'error'`.
243
248
 
244
249
  Discovery failure modes the platform handles explicitly — each
245
250
  leaves the row at `status: 'error'` with `error` populated:
@@ -482,7 +482,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
482
482
  one row passed and one was filtered.
483
483
 
484
484
  ```typescript
485
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
485
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
486
486
  sql_where_clause: `ra.batch_id IN ('${ctx.batchFulfilled}', '${ctx.batchCancelled}')`,
487
487
  mode: 'live',
488
488
  manual_override: false,
@@ -493,7 +493,7 @@ ctx.runBatchId = runResp.data.batch_id!
493
493
  const deadline = Date.now() + 120_000
494
494
  let status: string | null = null
495
495
  while (Date.now() < deadline) {
496
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
496
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
497
497
  status = log.status ?? null
498
498
  if (status && status !== 'pending') break
499
499
  await new Promise((r) => setTimeout(r, 2_000))
@@ -514,7 +514,7 @@ filtered — would mean the filter is not actually evaluating the
514
514
  appointment status.
515
515
 
516
516
  ```typescript
517
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
517
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
518
518
  const ourWels = (wfLogs.data ?? []).filter(
519
519
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
520
520
  )
@@ -604,7 +604,7 @@ if (!fulfilledAppointmentId) {
604
604
  throw new Error('fulfilled appointment not found via unregulated search')
605
605
  }
606
606
 
607
- const { data: execResp } = await api.workflows.execute(tenantSlug, ctx.workflowSlug, {
607
+ const { data: execResp } = await api.workflows.execute(tenantSlug, datalakeSlug, ctx.workflowSlug, {
608
608
  dataset_id: fulfilledAppointmentId,
609
609
  decision_key: 'send_appointment_review_sms',
610
610
  manual_override: true,
@@ -614,7 +614,7 @@ ctx.execWelId = execResp.workflow_execution_log_id!
614
614
  const deadline = Date.now() + 60_000
615
615
  let welStatus: string | null = null
616
616
  while (Date.now() < deadline) {
617
- const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, ctx.workflowSlug, ctx.execWelId)
617
+ const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.execWelId)
618
618
  welStatus = (wel as { status?: string }).status ?? null
619
619
  if (welStatus && welStatus !== 'pending' && welStatus !== 'executing') break
620
620
  await new Promise((r) => setTimeout(r, 2_000))
@@ -639,6 +639,7 @@ closing the SMS → reply loop end-to-end.
639
639
  ```typescript
640
640
  const { data: regulatedWel } = await api.workflows.workflowLogs.get(
641
641
  tenantSlug,
642
+ datalakeSlug,
642
643
  ctx.workflowSlug,
643
644
  ctx.execWelId,
644
645
  { dataAccessMode: 'regulated' },
@@ -655,7 +656,7 @@ if (!tokenMatch) {
655
656
  }
656
657
  const shortPath = tokenMatch[1]!
657
658
 
658
- const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
659
+ const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
659
660
  short_path: shortPath,
660
661
  user_agent: 'cookbook-doctest/appointment-review',
661
662
  })
@@ -664,7 +665,7 @@ if (resolved.route_path !== '/forms/review') {
664
665
  }
665
666
 
666
667
  const now = new Date().toISOString()
667
- const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
668
+ const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
668
669
  short_path: shortPath,
669
670
  opened_at: now,
670
671
  form_submitted_at: now,
@@ -393,7 +393,7 @@ life of the run. So this step polls on `total_wels` (every per-row job
393
393
  has produced a WEL) rather than waiting for the run to conclude.
394
394
 
395
395
  ```typescript
396
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
396
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
397
397
  sql_where_clause: `rle.batch_id IN ('${ctx.batchBirthday}', '${ctx.batchNoDob}')`,
398
398
  mode: 'live',
399
399
  manual_override: false,
@@ -404,7 +404,7 @@ ctx.runBatchId = runResp.data.batch_id!
404
404
  const deadline = Date.now() + 60_000
405
405
  let totalWels = 0
406
406
  while (Date.now() < deadline) {
407
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
407
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
408
408
  if (log.status === 'failed') throw new Error('workflow run reached :failed')
409
409
  totalWels = typeof log.total_wels === 'number' ? log.total_wels : 0
410
410
  if (totalWels >= 2) break
@@ -424,7 +424,7 @@ old shallow version of this cookbook could never catch that because
424
424
  it never ran the workflow at all.
425
425
 
426
426
  ```typescript
427
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
427
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
428
428
  const ourWels = (wfLogs.data ?? []).filter(
429
429
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
430
430
  )
@@ -475,7 +475,7 @@ const { data: search } = await api.datasets.search(tenantSlug, datalakeSlug, 'le
475
475
  const birthdayLegalEntityId = (search.data?.[0] as { id?: string } | undefined)?.id
476
476
  if (!birthdayLegalEntityId) throw new Error('birthday row legal_entity not found via search')
477
477
 
478
- const { data: execResp } = await api.workflows.execute(tenantSlug, ctx.workflowSlug, {
478
+ const { data: execResp } = await api.workflows.execute(tenantSlug, datalakeSlug, ctx.workflowSlug, {
479
479
  dataset_id: birthdayLegalEntityId,
480
480
  decision_key: 'send_happy_birthday_sms',
481
481
  manual_override: true,
@@ -486,7 +486,7 @@ ctx.forceExecWelId = execResp.workflow_execution_log_id!
486
486
  const deadline = Date.now() + 60_000
487
487
  let welStatus: string | null = null
488
488
  while (Date.now() < deadline) {
489
- const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, ctx.workflowSlug, ctx.forceExecWelId)
489
+ const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.forceExecWelId)
490
490
  welStatus = (wel as { status?: string }).status ?? null
491
491
  if (welStatus && welStatus !== 'pending' && welStatus !== 'executing') break
492
492
  await new Promise((r) => setTimeout(r, 2_000))
@@ -541,6 +541,7 @@ the page, closing the SMS → reply loop end-to-end.
541
541
  ```typescript
542
542
  const { data: regulatedWel } = await api.workflows.workflowLogs.get(
543
543
  tenantSlug,
544
+ datalakeSlug,
544
545
  ctx.workflowSlug,
545
546
  ctx.forceExecWelId,
546
547
  { dataAccessMode: 'regulated' },
@@ -557,7 +558,7 @@ if (!tokenMatch) {
557
558
  }
558
559
  const shortPath = tokenMatch[1]!
559
560
 
560
- const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
561
+ const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
561
562
  short_path: shortPath,
562
563
  user_agent: 'cookbook-doctest/birthday-greeting',
563
564
  })
@@ -566,7 +567,7 @@ if (resolved.route_path !== '/forms/birthday-greeting') {
566
567
  }
567
568
 
568
569
  const now = new Date().toISOString()
569
- const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
570
+ const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
570
571
  short_path: shortPath,
571
572
  opened_at: now,
572
573
  form_submitted_at: now,
@@ -159,7 +159,7 @@ toolId = smsToolResp.data.id!
159
159
  ## 003 — create the LLM tool
160
160
 
161
161
  The Triage agent calls a chat-completion endpoint to classify each
162
- row. The tool's `intent: 'chat_completion'` distinguishes it from
162
+ row. The tool's `intent: 'llm_enrichment'` distinguishes it from
163
163
  the SMS tool above; `tool_body_type: 'rest_api'` plus an
164
164
  OpenAI-compatible `base_url` lets the platform route Ollama,
165
165
  Anthropic, OpenAI, or any other compatible inference server
@@ -174,7 +174,7 @@ discriminated-union schema even though Ollama ignores the header.
174
174
  const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
175
175
  name: `Cookbook LLM Tool ${runSuffix}`,
176
176
  description: 'Ollama-backed chat-completion endpoint for Contact Us triage classification.',
177
- intent: 'chat_completion',
177
+ intent: 'llm_enrichment',
178
178
  status: 'active',
179
179
  datalake_id: ctx.datalakeId,
180
180
  body: {
@@ -454,7 +454,7 @@ const submissionIds = [
454
454
  ]
455
455
  const idList = submissionIds.map((id) => `'${id}'`).join(', ')
456
456
 
457
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
457
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
458
458
  sql_where_clause: `submission_id IN (${idList})`,
459
459
  mode: 'live',
460
460
  manual_override: true,
@@ -465,7 +465,7 @@ ctx.runBatchId = runResp.data.batch_id!
465
465
  const deadline = Date.now() + 240_000
466
466
  let status: string | null = null
467
467
  while (Date.now() < deadline) {
468
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
468
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
469
469
  status = log.status ?? null
470
470
  if (status && status !== 'pending') break
471
471
  await new Promise((r) => setTimeout(r, 2_000))
@@ -489,7 +489,7 @@ would mean the agent's output did not actually steer the decision;
489
489
  this step fails loudly in that case.
490
490
 
491
491
  ```typescript
492
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
492
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
493
493
  const ourWels = (wfLogs.data ?? []).filter(
494
494
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
495
495
  )
@@ -401,7 +401,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
401
401
  one row passed and one was filtered.
402
402
 
403
403
  ```typescript
404
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
404
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
405
405
  sql_where_clause: `ra.batch_id IN ('${ctx.batchVerified}', '${ctx.batchUnverified}')`,
406
406
  mode: 'live',
407
407
  manual_override: false,
@@ -412,7 +412,7 @@ ctx.runBatchId = runResp.data.batch_id!
412
412
  const deadline = Date.now() + 120_000
413
413
  let status: string | null = null
414
414
  while (Date.now() < deadline) {
415
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
415
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
416
416
  status = log.status ?? null
417
417
  if (status && status !== 'pending') break
418
418
  await new Promise((r) => setTimeout(r, 2_000))
@@ -433,7 +433,7 @@ passed — or both were filtered — would mean the filter is not
433
433
  actually evaluating both `customer.phone` and `customer.tax_id`.
434
434
 
435
435
  ```typescript
436
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
436
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
437
437
  const ourWels = (wfLogs.data ?? []).filter(
438
438
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
439
439
  )
@@ -511,7 +511,7 @@ the connected-app frontend does when the customer opens the
511
511
  payment portal, closing the SMS → reply loop end-to-end.
512
512
 
513
513
  ```typescript
514
- const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
514
+ const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
515
515
  short_path: ctx.dunningShortPath,
516
516
  user_agent: 'cookbook-doctest/dunning-sms',
517
517
  })
@@ -523,7 +523,7 @@ if (!(resolved.message?.body ?? '').includes('needs your attention')) {
523
523
  }
524
524
 
525
525
  const now = new Date().toISOString()
526
- const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
526
+ const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
527
527
  short_path: ctx.dunningShortPath,
528
528
  opened_at: now,
529
529
  form_submitted_at: now,
@@ -399,7 +399,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
399
399
  one row passed and one was filtered.
400
400
 
401
401
  ```typescript
402
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
402
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
403
403
  sql_where_clause: `rpa.batch_id IN ('${ctx.batchActive}', '${ctx.batchSuspended}')`,
404
404
  mode: 'live',
405
405
  manual_override: false,
@@ -410,7 +410,7 @@ ctx.runBatchId = runResp.data.batch_id!
410
410
  const deadline = Date.now() + 120_000
411
411
  let status: string | null = null
412
412
  while (Date.now() < deadline) {
413
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
413
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
414
414
  status = log.status ?? null
415
415
  if (status && status !== 'pending') break
416
416
  await new Promise((r) => setTimeout(r, 2_000))
@@ -431,7 +431,7 @@ filtered — would mean the filter is not actually evaluating
431
431
  `payment_account.status`.
432
432
 
433
433
  ```typescript
434
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
434
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
435
435
  const ourWels = (wfLogs.data ?? []).filter(
436
436
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
437
437
  )
@@ -509,7 +509,7 @@ the connected-app frontend does when the account holder opens the
509
509
  KYC portal, closing the SMS → reply loop end-to-end.
510
510
 
511
511
  ```typescript
512
- const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
512
+ const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
513
513
  short_path: ctx.kycShortPath,
514
514
  user_agent: 'cookbook-doctest/kyc-notification',
515
515
  })
@@ -521,7 +521,7 @@ if (!(resolved.message?.body ?? '').includes('Alvera PR notification')) {
521
521
  }
522
522
 
523
523
  const now = new Date().toISOString()
524
- const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
524
+ const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
525
525
  short_path: ctx.kycShortPath,
526
526
  opened_at: now,
527
527
  form_submitted_at: now,
@@ -106,7 +106,7 @@ toolId = smsToolResp.data.id!
106
106
 
107
107
  The Sanctions Review agent calls a chat-completion endpoint to
108
108
  disambiguate each gray-zone screening. The tool's
109
- `intent: 'chat_completion'` distinguishes it from the SMS tool
109
+ `intent: 'llm_enrichment'` distinguishes it from the SMS tool
110
110
  above; `tool_body_type: 'rest_api'` plus an OpenAI-compatible
111
111
  `base_url` lets the platform route Ollama, Anthropic, OpenAI, or
112
112
  any other compatible inference server through the same wire
@@ -119,7 +119,7 @@ though Ollama ignores the header.
119
119
  const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
120
120
  name: `Cookbook LLM Tool ${runSuffix}`,
121
121
  description: 'Ollama-backed chat-completion endpoint for sanctions-screening disambiguation.',
122
- intent: 'chat_completion',
122
+ intent: 'llm_enrichment',
123
123
  status: 'active',
124
124
  datalake_id: ctx.datalakeId,
125
125
  body: {
@@ -487,7 +487,7 @@ is generous because the gray-zone row's enrichment is a live LLM
487
487
  inference call.
488
488
 
489
489
  ```typescript
490
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
490
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
491
491
  sql_where_clause: `rcs.batch_id IN ('${ctx.batchGrayZone}', '${ctx.batchAutoClear}')`,
492
492
  mode: 'live',
493
493
  manual_override: false,
@@ -498,7 +498,7 @@ ctx.runBatchId = runResp.data.batch_id!
498
498
  const deadline = Date.now() + 240_000
499
499
  let status: string | null = null
500
500
  while (Date.now() < deadline) {
501
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
501
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
502
502
  status = log.status ?? null
503
503
  if (status && status !== 'pending') break
504
504
  await new Promise((r) => setTimeout(r, 2_000))
@@ -525,7 +525,7 @@ The pass-branch WEL carries exactly **two** action execution logs
525
525
  the decision rather than firing every action.
526
526
 
527
527
  ```typescript
528
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
528
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
529
529
  const ourWels = (wfLogs.data ?? []).filter(
530
530
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
531
531
  )
@@ -169,7 +169,7 @@ toolId = smsToolResp.data.id!
169
169
  ## 003 — create the LLM tool
170
170
 
171
171
  The Score Lead agent calls a chat-completion endpoint to classify each
172
- row. The tool's `intent: 'chat_completion'` distinguishes it from the
172
+ row. The tool's `intent: 'llm_enrichment'` distinguishes it from the
173
173
  SMS tool above; `tool_body_type: 'rest_api'` plus an OpenAI-compatible
174
174
  `base_url` lets the platform route Ollama, Anthropic, OpenAI, or any
175
175
  other compatible inference server through the same wire format. Local
@@ -182,7 +182,7 @@ Ollama itself ignores the header.
182
182
  const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
183
183
  name: `Cookbook LLM Tool ${runSuffix}`,
184
184
  description: 'Ollama-backed chat-completion endpoint for Score Lead classification.',
185
- intent: 'chat_completion',
185
+ intent: 'llm_enrichment',
186
186
  status: 'active',
187
187
  datalake_id: ctx.datalakeId,
188
188
  body: {
@@ -459,7 +459,7 @@ live LLM inference call.
459
459
  const regulatedTable = `regulated_${ctx.gtName}`
460
460
  const batchList = (ctx.leadBatchIds as string[]).map((b) => `'${b}'`).join(', ')
461
461
 
462
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
462
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
463
463
  sql_where_clause: `${regulatedTable}.batch_id IN (${batchList})`,
464
464
  mode: 'live',
465
465
  manual_override: true,
@@ -470,7 +470,7 @@ ctx.runBatchId = runResp.data.batch_id!
470
470
  const deadline = Date.now() + 240_000
471
471
  let status: string | null = null
472
472
  while (Date.now() < deadline) {
473
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
473
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
474
474
  status = log.status ?? null
475
475
  if (status && status !== 'pending') break
476
476
  await new Promise((r) => setTimeout(r, 2_000))
@@ -495,7 +495,7 @@ would mean the agent's output did not actually steer the decision;
495
495
  this step fails loudly in that case.
496
496
 
497
497
  ```typescript
498
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
498
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
499
499
  const ourWels = (wfLogs.data ?? []).filter(
500
500
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
501
501
  )
@@ -388,7 +388,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
388
388
  one row passed and one was filtered.
389
389
 
390
390
  ```typescript
391
- const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
391
+ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
392
392
  sql_where_clause: `ra.batch_id IN ('${ctx.batchWithPhone}', '${ctx.batchNoPhone}')`,
393
393
  mode: 'live',
394
394
  manual_override: false,
@@ -399,7 +399,7 @@ ctx.runBatchId = runResp.data.batch_id!
399
399
  const deadline = Date.now() + 120_000
400
400
  let status: string | null = null
401
401
  while (Date.now() < deadline) {
402
- const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
402
+ const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
403
403
  status = log.status ?? null
404
404
  if (status && status !== 'pending') break
405
405
  await new Promise((r) => setTimeout(r, 2_000))
@@ -420,7 +420,7 @@ filtered — would mean the filter is not actually evaluating
420
420
  `customer.phone`.
421
421
 
422
422
  ```typescript
423
- const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
423
+ const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
424
424
  const ourWels = (wfLogs.data ?? []).filter(
425
425
  (w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
426
426
  )
@@ -498,7 +498,7 @@ the connected-app frontend does when the customer opens the
498
498
  billing portal, closing the SMS → reply loop end-to-end.
499
499
 
500
500
  ```typescript
501
- const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
501
+ const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
502
502
  short_path: ctx.welcomeShortPath,
503
503
  user_agent: 'cookbook-doctest/welcome-sms',
504
504
  })
@@ -510,7 +510,7 @@ if (!(resolved.message?.body ?? '').includes('welcome to Alvera Billing')) {
510
510
  }
511
511
 
512
512
  const now = new Date().toISOString()
513
- const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
513
+ const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
514
514
  short_path: ctx.welcomeShortPath,
515
515
  opened_at: now,
516
516
  form_submitted_at: now,
package/.agent/tools.md CHANGED
@@ -103,13 +103,12 @@ create time:
103
103
  voice → rest_api (Twilio Voice, etc.)
104
104
  export → s3, aws_lambda
105
105
  status_poller → cloud_watch_log_group, rest_api
106
- chat_completion → rest_api (against an OpenAI-compatible
107
- LLM endpoint; the platform appends
108
- /chat/completions to base_url)
109
- context_extraction → rest_api (same OpenAI-compatible endpoint
110
- shape as chat_completion; used by
111
- AI agents for structured row
112
- enrichment vs free-form chat)
106
+ llm_enrichment → rest_api (against an OpenAI-compatible LLM
107
+ endpoint; the platform appends
108
+ /chat/completions to base_url; the
109
+ tool is the complete LLM provider
110
+ adapter and AI agents bind it for
111
+ structured row enrichment)
113
112
  ```
114
113
 
115
114
  A misaligned pair (e.g. `intent: sms` + `tool_body_type: sftp`)