@alvera-ai/platform-sdk 0.17.3 → 0.17.4-next.g6daa781
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/action_status_updaters.md +26 -15
- package/.agent/cookbook/action-status-updaters.md +9 -6
- package/.agent/cookbook/ai-agent-invoke.md +10 -0
- package/.agent/cookbook/appointment-review-sms-workflow.md +14 -0
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +14 -0
- package/.agent/cookbook/contact-us-triage-with-llm.md +15 -0
- package/.agent/cookbook/dunning-sms-for-delinquent.md +14 -0
- package/.agent/cookbook/kyc-notification-on-account-activation.md +14 -0
- package/.agent/cookbook/marketing-campaign-send.md +17 -5
- package/.agent/cookbook/paginated-restapi-poller.md +5 -4
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +15 -0
- package/.agent/cookbook/score-leads-with-llm-categorization.md +15 -0
- package/.agent/cookbook/triage-prospects-by-priority.md +15 -0
- package/.agent/cookbook/welcome-sms-for-customers.md +14 -0
- package/.agent/data_sources.md +3 -0
- package/.agent/tool-call-configs.md +6 -4
- package/.agent/tools.md +83 -28
- package/.agent/type_naming.md +2 -1
- package/dist/index.d.mts +69 -39
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -14,8 +14,8 @@ polls the provider's log group to mark each message as
|
|
|
14
14
|
SDK namespace: `api.actionStatusUpdaters`.
|
|
15
15
|
|
|
16
16
|
Action status updaters are **Datalake-DB-resident** — the
|
|
17
|
-
parent datalake must be `status: 'ready'
|
|
18
|
-
|
|
17
|
+
parent datalake must be `status: 'ready'`, and the polling tool
|
|
18
|
+
and the data source must exist before you `create()`.
|
|
19
19
|
|
|
20
20
|
## 1. Wire shape
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ const { data: created } = await api.actionStatusUpdaters.create(
|
|
|
33
33
|
cron_expression: '*/30 * * * *', // every 30 minutes
|
|
34
34
|
updater_type: 'cloud_watch', // polling source family
|
|
35
35
|
updater_tool_id: cloudWatchToolId, // the tool that issues the poll
|
|
36
|
-
|
|
36
|
+
data_source_id: dataSourceId, // the data source whose messages + action logs this updater updates
|
|
37
37
|
datalake_id: datalakeId,
|
|
38
38
|
|
|
39
39
|
// Polymorphic — discriminator: updater_body_type
|
|
@@ -354,7 +354,7 @@ anyway — a future release may start enforcing it, and a lying
|
|
|
354
354
|
`updater_type` is a debugging trap — but don't expect a 422 to catch
|
|
355
355
|
a mismatch for you today.
|
|
356
356
|
|
|
357
|
-
### `updater_tool_id` and `
|
|
357
|
+
### `updater_tool_id` and `data_source_id` serve different roles
|
|
358
358
|
|
|
359
359
|
- `updater_tool_id` (single) — the tool the updater uses to
|
|
360
360
|
ISSUE the poll. By convention this is a tool whose `intent` is
|
|
@@ -364,14 +364,25 @@ a mismatch for you today.
|
|
|
364
364
|
wrong-intent tool is accepted and only fails later, when the poll
|
|
365
365
|
actually runs. Wire a `status_poller` tool anyway; just don't rely
|
|
366
366
|
on a create-time 422 to enforce it.
|
|
367
|
-
- `
|
|
368
|
-
this updater
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
367
|
+
- `data_source_id` (single) — the data source whose messages and
|
|
368
|
+
action logs this updater updates. Every tool names a data source
|
|
369
|
+
(`tools.md`), and every message a tool sends carries that data
|
|
370
|
+
source's `uri`. The updater matches each polled event to a row by
|
|
371
|
+
the data source's `uri` plus the vendor's id (`external_id`): the
|
|
372
|
+
unique key on messages and action logs is
|
|
373
|
+
`(data_source_uri, external_id)`, and a vendor id that repeats
|
|
374
|
+
under one data source is kept with status `duplicate`.
|
|
375
|
+
|
|
376
|
+
So the updater is not tied to one sender tool: a message sent
|
|
377
|
+
through any tool on its data source is a candidate for its match.
|
|
378
|
+
One data source may hold SMS, MMS and voice tools, and nothing
|
|
379
|
+
checks what else is on it.
|
|
380
|
+
|
|
381
|
+
`data_source_id` is required on create AND update (PUT replaces the
|
|
382
|
+
whole body). The response reads it back nullable: updaters created
|
|
383
|
+
before it existed carry none, skip every run, and report
|
|
384
|
+
`no data source: …` as their last run error until an update names
|
|
385
|
+
one.
|
|
375
386
|
|
|
376
387
|
## 3. Field ownership
|
|
377
388
|
|
|
@@ -385,7 +396,7 @@ name required string
|
|
|
385
396
|
cron_expression required string — standard cron
|
|
386
397
|
updater_type required enum — e.g. 'cloud_watch'
|
|
387
398
|
updater_tool_id required UUID — the polling tool
|
|
388
|
-
|
|
399
|
+
data_source_id required UUID — the data source whose messages + action logs this updater updates (read back nullable)
|
|
389
400
|
datalake_id required UUID
|
|
390
401
|
updater_body required embed — { updater_body_type, ... }
|
|
391
402
|
message_config required embed — { type, body }
|
|
@@ -402,7 +413,7 @@ Standard JSON:API envelopes per `errors.md`. Common rejections:
|
|
|
402
413
|
|-------------------------------------|----------------------------------------------------|
|
|
403
414
|
| `/cron_expression` | invalid cron syntax or below minimum interval |
|
|
404
415
|
| `/updater_tool_id` | tool id doesn't exist or wrong datalake (intent is NOT checked — see §2) |
|
|
405
|
-
| `/
|
|
416
|
+
| `/data_source_id` | missing (`can't be blank`, create AND update) or the data source doesn't exist |
|
|
406
417
|
| `/updater_body/updater_body_type` | value not in the polymorphic embed's type enum |
|
|
407
418
|
| `/message_config/body` | empty when `type: 'custom'` |
|
|
408
419
|
| `/action_log_config` | missing entirely — `Missing field: action_log_config` (create AND update) |
|
|
@@ -626,7 +637,7 @@ is the part that matters:
|
|
|
626
637
|
- **No updater configured → no delivery truth.** Rows stay `sent`
|
|
627
638
|
forever; a delivery-rate number computed from them is fiction.
|
|
628
639
|
Updaters are created manually (this page §1) — the platform does
|
|
629
|
-
not auto-create them per
|
|
640
|
+
not auto-create them per data source.
|
|
630
641
|
- **The join is `{external_id, set_params}`,** exact-match, silent
|
|
631
642
|
on misses (gotcha 3). "Eventually honest": a just-sent row is
|
|
632
643
|
neither delivered nor failed yet — show the in-between state.
|
|
@@ -22,7 +22,8 @@ An **action status updater (ASU)** is a scheduled reconciler:
|
|
|
22
22
|
- the ASU runs on a `cron_expression`,
|
|
23
23
|
- its `message_config` Liquid maps each log event back to the message it came
|
|
24
24
|
from (by `external_id`) and the new `status`,
|
|
25
|
-
- `
|
|
25
|
+
- `data_source_id` scopes it to one data source: the messages and action logs
|
|
26
|
+
that carry that data source's `uri` — every message a tool on it sends.
|
|
26
27
|
|
|
27
28
|
This is **global** — the same shape reconciles any sender's delivery status. See
|
|
28
29
|
`action_status_updaters.md` for the wire reference and `tools.md` for the
|
|
@@ -104,7 +105,8 @@ ctx.cwToolId = cwTool.id!
|
|
|
104
105
|
## 004 — create the action status updater
|
|
105
106
|
|
|
106
107
|
The ASU ties it together: a `cron_expression` schedule, the `updater_tool_id`
|
|
107
|
-
(the poller), the `
|
|
108
|
+
(the poller), the `data_source_id` whose messages it reconciles — the one the
|
|
109
|
+
SMS sender tool names — and an `updater_body`
|
|
108
110
|
naming the log group + the time window. On `start_time`/`end_time`, use
|
|
109
111
|
`now_msec` — the injected variable holding unix milliseconds — piped through
|
|
110
112
|
`minutes_ago`; the bare `now` is a DateTime and raises here. The
|
|
@@ -122,7 +124,7 @@ const { data: asu } = await api.actionStatusUpdaters.create(tenantSlug, datalake
|
|
|
122
124
|
cron_expression: '*/30 * * * *',
|
|
123
125
|
updater_type: 'cloud_watch',
|
|
124
126
|
updater_tool_id: ctx.cwToolId,
|
|
125
|
-
|
|
127
|
+
data_source_id: dataSourceId,
|
|
126
128
|
datalake_id: ctx.datalakeId,
|
|
127
129
|
updater_body: {
|
|
128
130
|
updater_body_type: 'cloud_watch_request',
|
|
@@ -159,7 +161,7 @@ const { data: ck } = await api.actionStatusUpdaters.checksum(tenantSlug, datalak
|
|
|
159
161
|
cron_expression: row.cron_expression,
|
|
160
162
|
updater_type: 'cloud_watch',
|
|
161
163
|
updater_tool_id: ctx.cwToolId,
|
|
162
|
-
|
|
164
|
+
data_source_id: dataSourceId,
|
|
163
165
|
datalake_id: ctx.datalakeId,
|
|
164
166
|
updater_body: {
|
|
165
167
|
updater_body_type: 'cloud_watch_request',
|
|
@@ -235,8 +237,9 @@ blind.
|
|
|
235
237
|
# Gotchas
|
|
236
238
|
|
|
237
239
|
- **The poller tool's intent is `status_poller`, not the sender's intent.** The
|
|
238
|
-
reconciler reads logs; it doesn't send.
|
|
239
|
-
of sender tools
|
|
240
|
+
reconciler reads logs; it doesn't send. The messages it updates are named by
|
|
241
|
+
`data_source_id`, not by a list of sender tools: every tool names a data
|
|
242
|
+
source, and the messages it sends carry that data source's `uri`.
|
|
240
243
|
- **`message_config` renders once per event, and emits a FLAT object.** The event
|
|
241
244
|
is the assigns — there is no `events` list to loop — and every top-level key
|
|
242
245
|
that is not `external_id` IS the update (`status`, `delivered_at`, …). There is
|
|
@@ -99,12 +99,22 @@ const OPENAI_BASE_BODY =
|
|
|
99
99
|
const OPENAI_EXTRACTOR =
|
|
100
100
|
'{"output_json": "{{ msg.choices[0].message.content | json_escape }}", "explanation": "{{ msg.choices[0].message.reasoning_content | json_escape }}", "input_tokens": {{ msg.usage.prompt_tokens | default: 0 }}, "output_tokens": {{ msg.usage.completion_tokens | default: 0 }}, "total_tokens": {{ msg.usage.total_tokens | default: 0 }}}'
|
|
101
101
|
|
|
102
|
+
// Every tool names a data source; this one exists for the LLM tool alone.
|
|
103
|
+
const { data: toolSource } = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
104
|
+
name: `Extraction Tool Source ${runSuffix}`,
|
|
105
|
+
uri: 'openai.local',
|
|
106
|
+
description: 'Data source the LLM-enrichment tool names.',
|
|
107
|
+
status: 'active',
|
|
108
|
+
is_default: false,
|
|
109
|
+
})
|
|
110
|
+
|
|
102
111
|
const { data: tool } = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
103
112
|
name: `Legal-Entity Extraction Tool ${runSuffix}`,
|
|
104
113
|
description: 'Multimodal LLM-enrichment adapter for legal_entity extraction.',
|
|
105
114
|
intent: 'llm_enrichment',
|
|
106
115
|
status: 'active',
|
|
107
116
|
datalake_id: ctx.datalakeId,
|
|
117
|
+
data_source_id: toolSource.id!,
|
|
108
118
|
response_extractor: { type: 'custom', body: OPENAI_EXTRACTOR, output_schema: ENRICHMENT_OUTPUT_SCHEMA },
|
|
109
119
|
body: {
|
|
110
120
|
tool_body_type: 'rest_api',
|
|
@@ -77,13 +77,27 @@ dev points it at LocalStack on `http://localhost:4566` via
|
|
|
77
77
|
`intent: 'sms'` tags this tool for workflow actions that send SMS
|
|
78
78
|
(versus `data_exchange` for ingestion tools).
|
|
79
79
|
|
|
80
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
81
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
82
|
+
creates one for the SMS tool first.
|
|
83
|
+
|
|
80
84
|
```typescript
|
|
85
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
86
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
87
|
+
uri: 'sns.local',
|
|
88
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
89
|
+
status: 'active',
|
|
90
|
+
is_default: false,
|
|
91
|
+
})
|
|
92
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
93
|
+
|
|
81
94
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
82
95
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
83
96
|
description: 'SNS-backed SMS dispatcher for the Review SMS workflow, wired to LocalStack.',
|
|
84
97
|
intent: 'sms',
|
|
85
98
|
status: 'active',
|
|
86
99
|
datalake_id: ctx.datalakeId,
|
|
100
|
+
data_source_id: ctx.toolSourceId,
|
|
87
101
|
body: {
|
|
88
102
|
tool_body_type: 'sns',
|
|
89
103
|
auth_method: 'access_key',
|
|
@@ -70,13 +70,27 @@ local dev points it at LocalStack on `http://localhost:4566` via
|
|
|
70
70
|
`intent: 'sms'` tags this tool for workflow actions that send SMS
|
|
71
71
|
(versus `data_exchange` for ingestion tools).
|
|
72
72
|
|
|
73
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
74
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
75
|
+
creates one for the SMS tool first.
|
|
76
|
+
|
|
73
77
|
```typescript
|
|
78
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
79
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
80
|
+
uri: 'sns.local',
|
|
81
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
82
|
+
status: 'active',
|
|
83
|
+
is_default: false,
|
|
84
|
+
})
|
|
85
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
86
|
+
|
|
74
87
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
75
88
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
76
89
|
description: 'SNS-backed SMS dispatcher for the Happy Birthday workflow, wired to LocalStack.',
|
|
77
90
|
intent: 'sms',
|
|
78
91
|
status: 'active',
|
|
79
92
|
datalake_id: ctx.datalakeId,
|
|
93
|
+
data_source_id: ctx.toolSourceId,
|
|
80
94
|
body: {
|
|
81
95
|
tool_body_type: 'sns',
|
|
82
96
|
auth_method: 'access_key',
|
|
@@ -136,13 +136,27 @@ per-bucket SMS body lives on each workflow action, not on the tool.
|
|
|
136
136
|
SNS-backed LocalStack wiring — `intent: 'sms'` tags it for
|
|
137
137
|
workflow-action use.
|
|
138
138
|
|
|
139
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
140
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
141
|
+
creates one for the SMS tool first.
|
|
142
|
+
|
|
139
143
|
```typescript
|
|
144
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
145
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
146
|
+
uri: 'sns.local',
|
|
147
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
148
|
+
status: 'active',
|
|
149
|
+
is_default: false,
|
|
150
|
+
})
|
|
151
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
152
|
+
|
|
140
153
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
141
154
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
142
155
|
description: 'SNS-backed SMS dispatcher for the Contact Us Triage workflow, wired to LocalStack.',
|
|
143
156
|
intent: 'sms',
|
|
144
157
|
status: 'active',
|
|
145
158
|
datalake_id: ctx.datalakeId,
|
|
159
|
+
data_source_id: ctx.toolSourceId,
|
|
146
160
|
body: {
|
|
147
161
|
tool_body_type: 'sns',
|
|
148
162
|
auth_method: 'access_key',
|
|
@@ -194,6 +208,7 @@ const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
|
194
208
|
intent: 'llm_enrichment',
|
|
195
209
|
status: 'active',
|
|
196
210
|
datalake_id: ctx.datalakeId,
|
|
211
|
+
data_source_id: ctx.toolSourceId,
|
|
197
212
|
response_extractor: { type: 'custom', body: OLLAMA_EXTRACTOR, output_schema: ENRICHMENT_OUTPUT_SCHEMA },
|
|
198
213
|
body: {
|
|
199
214
|
tool_body_type: 'rest_api',
|
|
@@ -79,13 +79,27 @@ dev points it at LocalStack on `http://localhost:4566` via
|
|
|
79
79
|
`intent: 'sms'` tags this tool for workflow actions that send SMS
|
|
80
80
|
(versus `data_exchange` for ingestion tools).
|
|
81
81
|
|
|
82
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
83
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
84
|
+
creates one for the SMS tool first.
|
|
85
|
+
|
|
82
86
|
```typescript
|
|
87
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
88
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
89
|
+
uri: 'sns.local',
|
|
90
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
91
|
+
status: 'active',
|
|
92
|
+
is_default: false,
|
|
93
|
+
})
|
|
94
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
95
|
+
|
|
83
96
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
84
97
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
85
98
|
description: 'SNS-backed SMS dispatcher for the Dunning SMS workflow, wired to LocalStack.',
|
|
86
99
|
intent: 'sms',
|
|
87
100
|
status: 'active',
|
|
88
101
|
datalake_id: ctx.datalakeId,
|
|
102
|
+
data_source_id: ctx.toolSourceId,
|
|
89
103
|
body: {
|
|
90
104
|
tool_body_type: 'sns',
|
|
91
105
|
auth_method: 'access_key',
|
|
@@ -77,13 +77,27 @@ local dev points it at LocalStack on `http://localhost:4566` via
|
|
|
77
77
|
`intent: 'sms'` tags this tool for workflow actions that send SMS
|
|
78
78
|
(versus `data_exchange` for ingestion tools).
|
|
79
79
|
|
|
80
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
81
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
82
|
+
creates one for the SMS tool first.
|
|
83
|
+
|
|
80
84
|
```typescript
|
|
85
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
86
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
87
|
+
uri: 'sns.local',
|
|
88
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
89
|
+
status: 'active',
|
|
90
|
+
is_default: false,
|
|
91
|
+
})
|
|
92
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
93
|
+
|
|
81
94
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
82
95
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
83
96
|
description: 'SNS-backed SMS dispatcher for the KYC-notification workflow, wired to LocalStack.',
|
|
84
97
|
intent: 'sms',
|
|
85
98
|
status: 'active',
|
|
86
99
|
datalake_id: ctx.datalakeId,
|
|
100
|
+
data_source_id: ctx.toolSourceId,
|
|
87
101
|
body: {
|
|
88
102
|
tool_body_type: 'sns',
|
|
89
103
|
auth_method: 'access_key',
|
|
@@ -507,6 +507,15 @@ manifest therefore runs against any server; you are not asking the
|
|
|
507
507
|
deployment to be in "test mode". Swap `mock` for `ses` and the same
|
|
508
508
|
workflow sends real email.
|
|
509
509
|
|
|
510
|
+
The sender lives in the tool's Base Configuration: `base_from_email`
|
|
511
|
+
(required on every email tool) and `base_from_name`, Liquid templates
|
|
512
|
+
rendered when the tool is saved with the tool's own variables only
|
|
513
|
+
(`tool.name`, `tool.provider`, …). An email action's own `from_email` /
|
|
514
|
+
`from_name` / `reply_to` still win, field by field, when they render
|
|
515
|
+
non-blank. Both tools name the campaign's data source, as every tool
|
|
516
|
+
must; the messages they send carry its `uri`, which is what §009's
|
|
517
|
+
status updater matches on.
|
|
518
|
+
|
|
510
519
|
The connected app is the campaign's booking form. It is registered
|
|
511
520
|
here, and §010's actions deep-link into it.
|
|
512
521
|
|
|
@@ -519,6 +528,7 @@ const { data: smsTool } = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
|
519
528
|
intent: ToolIntent.SMS,
|
|
520
529
|
status: 'active',
|
|
521
530
|
datalake_id: ctx.datalakeId,
|
|
531
|
+
data_source_id: dataSourceId,
|
|
522
532
|
body: {
|
|
523
533
|
tool_body_type: 'sns',
|
|
524
534
|
auth_method: 'access_key',
|
|
@@ -537,11 +547,12 @@ const { data: emailTool } = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
|
537
547
|
intent: ToolIntent.EMAIL,
|
|
538
548
|
status: 'active',
|
|
539
549
|
datalake_id: ctx.datalakeId,
|
|
550
|
+
data_source_id: dataSourceId,
|
|
540
551
|
body: {
|
|
541
552
|
tool_body_type: 'email',
|
|
542
553
|
provider: 'mock',
|
|
543
|
-
|
|
544
|
-
|
|
554
|
+
base_from_email: { type: 'custom', body: 'campaigns@bloom-salon.example.com' },
|
|
555
|
+
base_from_name: { type: 'custom', body: 'Bloom Salon' },
|
|
545
556
|
},
|
|
546
557
|
})
|
|
547
558
|
ctx.emailToolId = emailTool.id!
|
|
@@ -652,11 +663,12 @@ const { data: sesTool } = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
|
652
663
|
intent: ToolIntent.EMAIL,
|
|
653
664
|
status: 'active',
|
|
654
665
|
datalake_id: ctx.datalakeId,
|
|
666
|
+
data_source_id: dataSourceId,
|
|
655
667
|
body: {
|
|
656
668
|
tool_body_type: 'email',
|
|
657
669
|
provider: 'ses',
|
|
658
|
-
|
|
659
|
-
|
|
670
|
+
base_from_email: { type: 'custom', body: sesSender },
|
|
671
|
+
base_from_name: { type: 'custom', body: 'Bloom Salon' },
|
|
660
672
|
region: 'us-east-1',
|
|
661
673
|
access_key_id: 'localkey',
|
|
662
674
|
secret_access_key: 'localsecret',
|
|
@@ -748,7 +760,7 @@ const { data: sesUpdater } = await api.actionStatusUpdaters.create(tenantSlug, d
|
|
|
748
760
|
cron_expression: '*/30 * * * *',
|
|
749
761
|
updater_type: 'cloud_watch',
|
|
750
762
|
updater_tool_id: sesReader.id!,
|
|
751
|
-
|
|
763
|
+
data_source_id: dataSourceId,
|
|
752
764
|
datalake_id: ctx.datalakeId,
|
|
753
765
|
updater_body: {
|
|
754
766
|
updater_body_type: 'cloud_watch_request',
|
|
@@ -57,8 +57,9 @@ dataSourceId = ds.id!
|
|
|
57
57
|
|
|
58
58
|
## 002 — create the sender tool whose messages get reconciled
|
|
59
59
|
|
|
60
|
-
The ASU updates messages
|
|
61
|
-
|
|
60
|
+
The ASU updates the messages sent through its data source; the sender names
|
|
61
|
+
that data source, so its messages carry the data source's `uri`. An
|
|
62
|
+
SNS-backed SMS sender (LocalStack locally) plays that role.
|
|
62
63
|
|
|
63
64
|
```typescript
|
|
64
65
|
const { data: smsTool } = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
@@ -126,7 +127,7 @@ try {
|
|
|
126
127
|
cron_expression: '*/30 * * * *',
|
|
127
128
|
updater_type: 'restapi',
|
|
128
129
|
updater_tool_id: ctx.restToolId,
|
|
129
|
-
|
|
130
|
+
data_source_id: dataSourceId,
|
|
130
131
|
datalake_id: ctx.datalakeId,
|
|
131
132
|
// FLOOR the platform enforces: an array whose items are objects listing
|
|
132
133
|
// `external_id` in `required`. A bare `{ type: 'array' }` is a 422 at
|
|
@@ -201,7 +202,7 @@ const { data: asu } = await api.actionStatusUpdaters.create(tenantSlug, datalake
|
|
|
201
202
|
cron_expression: '*/30 * * * *',
|
|
202
203
|
updater_type: 'restapi',
|
|
203
204
|
updater_tool_id: ctx.restToolId,
|
|
204
|
-
|
|
205
|
+
data_source_id: dataSourceId,
|
|
205
206
|
datalake_id: ctx.datalakeId,
|
|
206
207
|
events_output_schema: {
|
|
207
208
|
type: 'array',
|
|
@@ -82,13 +82,27 @@ local dev points it at LocalStack on `http://localhost:4566` via
|
|
|
82
82
|
`intent: 'sms'` tags this tool for workflow actions that send SMS
|
|
83
83
|
(versus `data_exchange` for ingestion tools).
|
|
84
84
|
|
|
85
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
86
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
87
|
+
creates one for the SMS tool first.
|
|
88
|
+
|
|
85
89
|
```typescript
|
|
90
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
91
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
92
|
+
uri: 'sns.local',
|
|
93
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
94
|
+
status: 'active',
|
|
95
|
+
is_default: false,
|
|
96
|
+
})
|
|
97
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
98
|
+
|
|
86
99
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
87
100
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
88
101
|
description: 'SNS-backed SMS dispatcher for the Sanctions Review workflow, wired to LocalStack.',
|
|
89
102
|
intent: 'sms',
|
|
90
103
|
status: 'active',
|
|
91
104
|
datalake_id: ctx.datalakeId,
|
|
105
|
+
data_source_id: ctx.toolSourceId,
|
|
92
106
|
body: {
|
|
93
107
|
tool_body_type: 'sns',
|
|
94
108
|
auth_method: 'access_key',
|
|
@@ -141,6 +155,7 @@ const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
|
141
155
|
intent: 'llm_enrichment',
|
|
142
156
|
status: 'active',
|
|
143
157
|
datalake_id: ctx.datalakeId,
|
|
158
|
+
data_source_id: ctx.toolSourceId,
|
|
144
159
|
response_extractor: { type: 'custom', body: OLLAMA_EXTRACTOR, output_schema: ENRICHMENT_OUTPUT_SCHEMA },
|
|
145
160
|
body: {
|
|
146
161
|
tool_body_type: 'rest_api',
|
|
@@ -146,13 +146,27 @@ per-band SMS body lives on each workflow action, not on the tool. Same
|
|
|
146
146
|
SNS-backed LocalStack wiring as the birthday cookbook — `intent: 'sms'`
|
|
147
147
|
tags it for workflow-action use.
|
|
148
148
|
|
|
149
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
150
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
151
|
+
creates one for the SMS tool first.
|
|
152
|
+
|
|
149
153
|
```typescript
|
|
154
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
155
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
156
|
+
uri: 'sns.local',
|
|
157
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
158
|
+
status: 'active',
|
|
159
|
+
is_default: false,
|
|
160
|
+
})
|
|
161
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
162
|
+
|
|
150
163
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
151
164
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
152
165
|
description: 'SNS-backed SMS dispatcher for the Score Lead workflow, wired to LocalStack.',
|
|
153
166
|
intent: 'sms',
|
|
154
167
|
status: 'active',
|
|
155
168
|
datalake_id: ctx.datalakeId,
|
|
169
|
+
data_source_id: ctx.toolSourceId,
|
|
156
170
|
body: {
|
|
157
171
|
tool_body_type: 'sns',
|
|
158
172
|
auth_method: 'access_key',
|
|
@@ -204,6 +218,7 @@ const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
|
204
218
|
intent: 'llm_enrichment',
|
|
205
219
|
status: 'active',
|
|
206
220
|
datalake_id: ctx.datalakeId,
|
|
221
|
+
data_source_id: ctx.toolSourceId,
|
|
207
222
|
response_extractor: { type: 'custom', body: OLLAMA_EXTRACTOR, output_schema: ENRICHMENT_OUTPUT_SCHEMA },
|
|
208
223
|
body: {
|
|
209
224
|
tool_body_type: 'rest_api',
|
|
@@ -65,13 +65,27 @@ A single SMS tool dispatches every band's message; the per-band body lives on
|
|
|
65
65
|
each workflow action. SNS-backed, pointed at LocalStack locally; `intent: 'sms'`
|
|
66
66
|
tags it for workflow actions.
|
|
67
67
|
|
|
68
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
69
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
70
|
+
creates one for the SMS tool first.
|
|
71
|
+
|
|
68
72
|
```typescript
|
|
73
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
74
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
75
|
+
uri: 'sns.local',
|
|
76
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
77
|
+
status: 'active',
|
|
78
|
+
is_default: false,
|
|
79
|
+
})
|
|
80
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
81
|
+
|
|
69
82
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
70
83
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
71
84
|
description: 'SNS-backed SMS dispatcher for the priority-triage workflow, wired to LocalStack.',
|
|
72
85
|
intent: 'sms',
|
|
73
86
|
status: 'active',
|
|
74
87
|
datalake_id: ctx.datalakeId,
|
|
88
|
+
data_source_id: ctx.toolSourceId,
|
|
75
89
|
body: {
|
|
76
90
|
tool_body_type: 'sns',
|
|
77
91
|
auth_method: 'access_key',
|
|
@@ -119,6 +133,7 @@ const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
|
119
133
|
intent: 'llm_enrichment',
|
|
120
134
|
status: 'active',
|
|
121
135
|
datalake_id: ctx.datalakeId,
|
|
136
|
+
data_source_id: ctx.toolSourceId,
|
|
122
137
|
response_extractor: { type: 'custom', body: OLLAMA_EXTRACTOR, output_schema: ENRICHMENT_OUTPUT_SCHEMA },
|
|
123
138
|
body: {
|
|
124
139
|
tool_body_type: 'rest_api',
|
|
@@ -76,13 +76,27 @@ dev points it at LocalStack on `http://localhost:4566` via
|
|
|
76
76
|
`intent: 'sms'` tags this tool for workflow actions that send SMS
|
|
77
77
|
(versus `data_exchange` for ingestion tools).
|
|
78
78
|
|
|
79
|
+
Every tool names a data source, and the messages a sender tool sends carry
|
|
80
|
+
that data source's `uri` — what a status updater matches on — so the step
|
|
81
|
+
creates one for the SMS tool first.
|
|
82
|
+
|
|
79
83
|
```typescript
|
|
84
|
+
const toolSourceResp = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
85
|
+
name: `Cookbook SMS Source ${runSuffix}`,
|
|
86
|
+
uri: 'sns.local',
|
|
87
|
+
description: 'Origin of the messages the SMS tool sends.',
|
|
88
|
+
status: 'active',
|
|
89
|
+
is_default: false,
|
|
90
|
+
})
|
|
91
|
+
ctx.toolSourceId = toolSourceResp.data.id!
|
|
92
|
+
|
|
80
93
|
const smsToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
81
94
|
name: `Cookbook SMS Tool ${runSuffix}`,
|
|
82
95
|
description: 'SNS-backed SMS dispatcher for the Welcome SMS workflow, wired to LocalStack.',
|
|
83
96
|
intent: 'sms',
|
|
84
97
|
status: 'active',
|
|
85
98
|
datalake_id: ctx.datalakeId,
|
|
99
|
+
data_source_id: ctx.toolSourceId,
|
|
86
100
|
body: {
|
|
87
101
|
tool_body_type: 'sns',
|
|
88
102
|
auth_method: 'access_key',
|
package/.agent/data_sources.md
CHANGED
|
@@ -210,6 +210,9 @@ asymmetric cascade behavior by dependent resource:
|
|
|
210
210
|
restricting FK (`on_delete: :restrict`), so the delete is
|
|
211
211
|
**rejected** while any action still points at the source —
|
|
212
212
|
detach those actions first.
|
|
213
|
+
- Every tool names its data source (`tools.md` §2), and that FK
|
|
214
|
+
restricts too: a data source that still has tools answers
|
|
215
|
+
`409` (`still has tools`). Delete or move its tools first.
|
|
213
216
|
|
|
214
217
|
The auto-created default Manual Upload source (see §2 "A
|
|
215
218
|
default Manual Upload data source is auto-created per
|
|
@@ -99,8 +99,10 @@ Sends an email message via the configured email provider.
|
|
|
99
99
|
- **to** (TemplateConfig) — Recipient email address (RFC 5322), rendered as Liquid template
|
|
100
100
|
- **subject** (TemplateConfig) — Email subject line, rendered as Liquid template
|
|
101
101
|
- **body** (TemplateConfig) — Email body, rendered as Liquid template
|
|
102
|
-
- **from_name** (TemplateConfig, optional) — Sender display name; overrides the email tool's `
|
|
103
|
-
- **from_email** (TemplateConfig, optional) — Sender address (RFC 5322); overrides the email tool's `
|
|
104
|
-
- **reply_to** (TemplateConfig, optional) — Reply-to address (RFC 5322); overrides the email tool's `
|
|
102
|
+
- **from_name** (TemplateConfig, optional) — Sender display name; overrides the email tool's `base_from_name`
|
|
103
|
+
- **from_email** (TemplateConfig, optional) — Sender address (RFC 5322); overrides the email tool's `base_from_email`
|
|
104
|
+
- **reply_to** (TemplateConfig, optional) — Reply-to address (RFC 5322); overrides the email tool's `base_reply_to`
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
The email tool's Base Configuration renders first. Each field the action leaves unset, or that renders blank,
|
|
107
|
+
keeps the tool's value — the sender fields, and `to`, `subject` and `body` against `base_to`, `base_subject`
|
|
108
|
+
and `base_body`.
|