@falai/agent 4.0.0-alpha.4 → 4.0.0-alpha.5
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/dist/cjs/utils/template.d.ts +5 -0
- package/dist/cjs/utils/template.d.ts.map +1 -1
- package/dist/cjs/utils/template.js +28 -2
- package/dist/cjs/utils/template.js.map +1 -1
- package/dist/utils/template.d.ts +5 -0
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +28 -2
- package/dist/utils/template.js.map +1 -1
- package/docs/guides/actions-and-events.md +1 -1
- package/docs/migration/v3-to-v4.md +3 -2
- package/docs/reference/actions-events-conditions.md +1 -1
- package/docs/start/04-add-tools.md +1 -1
- package/package.json +3 -2
- package/src/utils/template.ts +27 -2
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* per-turn context) and `input` (the run's trigger payload). A path that
|
|
6
6
|
* resolves to nothing keeps its placeholder, so a typo stays visible instead
|
|
7
7
|
* of vanishing into an empty string.
|
|
8
|
+
*
|
|
9
|
+
* A path that resolves to an EMPTY string is a different case: the host knows
|
|
10
|
+
* the field and knows it is blank. Then the placeholder goes and `tidy` closes
|
|
11
|
+
* the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
|
|
12
|
+
* "Ola, tudo bem?" and never "Ola , tudo bem?".
|
|
8
13
|
*/
|
|
9
14
|
export interface TemplateScope {
|
|
10
15
|
data?: unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAUrE;AAiBD,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,GAAG,CAAC,CAS/D"}
|
|
@@ -6,16 +6,42 @@
|
|
|
6
6
|
* per-turn context) and `input` (the run's trigger payload). A path that
|
|
7
7
|
* resolves to nothing keeps its placeholder, so a typo stays visible instead
|
|
8
8
|
* of vanishing into an empty string.
|
|
9
|
+
*
|
|
10
|
+
* A path that resolves to an EMPTY string is a different case: the host knows
|
|
11
|
+
* the field and knows it is blank. Then the placeholder goes and `tidy` closes
|
|
12
|
+
* the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
|
|
13
|
+
* "Ola, tudo bem?" and never "Ola , tudo bem?".
|
|
9
14
|
*/
|
|
10
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
16
|
exports.render = render;
|
|
12
17
|
exports.renderDeep = renderDeep;
|
|
13
18
|
const PLACEHOLDER = /\{\{\s*([^}\s]+)\s*\}\}/g;
|
|
14
19
|
function render(template, scope) {
|
|
15
|
-
|
|
20
|
+
let emptied = false;
|
|
21
|
+
const out = template.replace(PLACEHOLDER, (match, path) => {
|
|
16
22
|
const value = lookup(scope, path.split("."));
|
|
17
|
-
|
|
23
|
+
if (value === undefined || value === null)
|
|
24
|
+
return match;
|
|
25
|
+
const text = stringify(value);
|
|
26
|
+
if (text === "")
|
|
27
|
+
emptied = true;
|
|
28
|
+
return text;
|
|
18
29
|
});
|
|
30
|
+
return emptied ? tidy(out) : out;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Close the hole an empty value leaves: a doubled space, a space before
|
|
34
|
+
* punctuation, a space at the end of a line.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately narrow. It only runs on a string where something substituted to
|
|
37
|
+
* "", and it only collapses a run of spaces that follows a visible character,
|
|
38
|
+
* so indentation in a markdown list survives.
|
|
39
|
+
*/
|
|
40
|
+
function tidy(text) {
|
|
41
|
+
return text
|
|
42
|
+
.replace(/(?<=\S)[ \t]{2,}/g, " ")
|
|
43
|
+
.replace(/ +([,.;:!?\u2026])/g, "$1")
|
|
44
|
+
.replace(/[ \t]+$/gm, "");
|
|
19
45
|
}
|
|
20
46
|
/** `render` over every string inside a value, recursively. Non-strings pass through. */
|
|
21
47
|
function renderDeep(value, scope) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAUH,wBAUC;AAkBD,gCASC;AAvCD,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,SAAgB,MAAM,CAAC,QAAgB,EAAE,KAAoB;IAC3D,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAChE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACxD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO,GAAG,IAAI,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,IAAI;SACR,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC;SACjC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC;SACpC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,wFAAwF;AACxF,SAAgB,UAAU,CAAI,KAAQ,EAAE,KAAoB;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAM,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAM,CAAC;IAC5F,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;IACT,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,IAAa,EAAE,IAAc;IAC3C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACtE,OAAO,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|
package/dist/utils/template.d.ts
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* per-turn context) and `input` (the run's trigger payload). A path that
|
|
6
6
|
* resolves to nothing keeps its placeholder, so a typo stays visible instead
|
|
7
7
|
* of vanishing into an empty string.
|
|
8
|
+
*
|
|
9
|
+
* A path that resolves to an EMPTY string is a different case: the host knows
|
|
10
|
+
* the field and knows it is blank. Then the placeholder goes and `tidy` closes
|
|
11
|
+
* the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
|
|
12
|
+
* "Ola, tudo bem?" and never "Ola , tudo bem?".
|
|
8
13
|
*/
|
|
9
14
|
export interface TemplateScope {
|
|
10
15
|
data?: unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAUrE;AAiBD,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,GAAG,CAAC,CAS/D"}
|
package/dist/utils/template.js
CHANGED
|
@@ -5,13 +5,39 @@
|
|
|
5
5
|
* per-turn context) and `input` (the run's trigger payload). A path that
|
|
6
6
|
* resolves to nothing keeps its placeholder, so a typo stays visible instead
|
|
7
7
|
* of vanishing into an empty string.
|
|
8
|
+
*
|
|
9
|
+
* A path that resolves to an EMPTY string is a different case: the host knows
|
|
10
|
+
* the field and knows it is blank. Then the placeholder goes and `tidy` closes
|
|
11
|
+
* the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
|
|
12
|
+
* "Ola, tudo bem?" and never "Ola , tudo bem?".
|
|
8
13
|
*/
|
|
9
14
|
const PLACEHOLDER = /\{\{\s*([^}\s]+)\s*\}\}/g;
|
|
10
15
|
export function render(template, scope) {
|
|
11
|
-
|
|
16
|
+
let emptied = false;
|
|
17
|
+
const out = template.replace(PLACEHOLDER, (match, path) => {
|
|
12
18
|
const value = lookup(scope, path.split("."));
|
|
13
|
-
|
|
19
|
+
if (value === undefined || value === null)
|
|
20
|
+
return match;
|
|
21
|
+
const text = stringify(value);
|
|
22
|
+
if (text === "")
|
|
23
|
+
emptied = true;
|
|
24
|
+
return text;
|
|
14
25
|
});
|
|
26
|
+
return emptied ? tidy(out) : out;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Close the hole an empty value leaves: a doubled space, a space before
|
|
30
|
+
* punctuation, a space at the end of a line.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately narrow. It only runs on a string where something substituted to
|
|
33
|
+
* "", and it only collapses a run of spaces that follows a visible character,
|
|
34
|
+
* so indentation in a markdown list survives.
|
|
35
|
+
*/
|
|
36
|
+
function tidy(text) {
|
|
37
|
+
return text
|
|
38
|
+
.replace(/(?<=\S)[ \t]{2,}/g, " ")
|
|
39
|
+
.replace(/ +([,.;:!?\u2026])/g, "$1")
|
|
40
|
+
.replace(/[ \t]+$/gm, "");
|
|
15
41
|
}
|
|
16
42
|
/** `render` over every string inside a value, recursively. Non-strings pass through. */
|
|
17
43
|
export function renderDeep(value, scope) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,KAAoB;IAC3D,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAChE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACxD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO,GAAG,IAAI,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,IAAI;SACR,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC;SACjC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC;SACpC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,UAAU,CAAI,KAAQ,EAAE,KAAoB;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAM,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAM,CAAC;IAC5F,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;IACT,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,IAAa,EAAE,IAAc;IAC3C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACtE,OAAO,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -53,7 +53,7 @@ A `do` step fills the parameters with `with`. When the agent is built, `validate
|
|
|
53
53
|
- a value has the wrong type; nothing is coerced, so `"3"` is not a number. A template such as `"{{data.cep}}"` is a string, so templates can only fill string parameters (and the items of a string array);
|
|
54
54
|
- `with` names a parameter the action does not have.
|
|
55
55
|
|
|
56
|
-
Templates in `with` are rendered right before `run` is called, against `data` (collected fields), `context` (this turn's host context) and `input` (the run's input).
|
|
56
|
+
Templates in `with` are rendered right before `run` is called, against `data` (collected fields), `context` (this turn's host context) and `input` (the run's input). An unknown path keeps its `{{...}}`, so a typo stays visible. A path that resolves to an empty string drops out, and the space or comma it left behind goes with it.
|
|
57
57
|
|
|
58
58
|
## What `run` sees
|
|
59
59
|
|
|
@@ -410,13 +410,14 @@ Host actions, events and conditions are registered once on the agent and referen
|
|
|
410
410
|
| `agent.dispatch`, `pendingDirective`, `Directive`, `flow.merge`, `flow.validate` | `then` / `else` on steps |
|
|
411
411
|
| `Flow` class, `Step` class, `flow` namespace, `FlowOptions`, `StepOptions` | plain objects: `Flow`, `Step` |
|
|
412
412
|
| `title`, `when`, `if`, `reentrant`, `requiredFields`, `optionalFields`, `onComplete`, flow `hooks` | `id` + `name`, `on[]`, `repeat`, `clearOnStart`, `onEnd`, `while` |
|
|
413
|
-
| `
|
|
413
|
+
| `skip`, `auto`, `reply`, step `hooks`, `prepare`, `finalize` | known-field skipping, `maxAsks`, `do`, `wait`, `say` |
|
|
414
|
+
| `requires` | an `if` step: `{ if: { known: [...] }, then: '<step>', else: 'end' }`. Not known-field skipping — that answers "do I still need to ask?", while `requires` answers "may this step run at all?". They differ exactly when the customer never answers: `maxAsks` retires the field and the step proceeds with it unknown. |
|
|
414
415
|
| `Signal`, `SignalContext`, `SignalFiring`, `signals`, `signalBatchSize`, `triggeredSignals` | `mention` flows, `repeat`, `claims` |
|
|
415
416
|
| `ToolContext.updateContext / updateData / setField / dispatch`, `ToolResult.dataUpdate / contextUpdate / directive`, `ToolManager`, `ToolScope`, tool config helpers | `Tool.handler(args, ctx) → { value?, data? }` |
|
|
416
417
|
| `PersistenceAdapter`, `SessionRepository`, `MessageRepository`, `PersistenceManager`, `SessionManager`, `restoreSession`, `createPersistedState`, `enterFlow`, `enterStep`, `completeCurrentFlow`, `mergeCollected` | `Store`, the seven `*Store` classes, `migrateSession` |
|
|
417
418
|
| `SessionState`, `CollectedStateData`, `SessionData` | `Session` |
|
|
418
419
|
| `AgentResponse.executedSteps / stoppedReason / endedFlows / appliedInstructions / isFlowComplete` | `TurnResult.outcomes / started / ended / skipped / messages / schedule / llmCalls` |
|
|
419
|
-
| `Template` as a function, `TemplateContext`, `ConditionEvaluator`, `ConditionWhen`, `ConditionIf
|
|
420
|
+
| `Template` as a function, `TemplateContext`, `ConditionEvaluator`, `ConditionWhen`, `ConditionIf` | `Template = string` with `{{data.x}}` `{{context.x}}` `{{input.x}}`; `Pred` (function or JSON). **`!` exclusions are not gone** — a phrase opening with `!` still rules a trigger out. Copy the list across unchanged. |
|
|
420
421
|
| `Term`, `terms` | put the glossary in `knowledgeBase` or an instruction |
|
|
421
422
|
| `Instruction.enabled / tags / metadata` | filter before passing |
|
|
422
423
|
| `promptCache`, `PromptSectionCache`, `PromptCacheConfig` | gone; every prompt is built per call. `compaction` stays and runs once per turn on the history you pass |
|
|
@@ -116,7 +116,7 @@ f.action<const P extends ParamDefs>(def: {
|
|
|
116
116
|
|
|
117
117
|
### Behaviour
|
|
118
118
|
|
|
119
|
-
- `with` is rendered before `run` sees it. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced inside every string, at any depth.
|
|
119
|
+
- `with` is rendered before `run` sees it. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced inside every string, at any depth. An unknown path keeps its placeholder, so a typo stays visible. A path that resolves to an empty string drops out instead, and the gap it left in the sentence closes.
|
|
120
120
|
- `with` is checked when the agent is built, not on the turn that reaches the step. A missing required parameter, an unknown parameter, or a value outside `enum` throws `FlowConfigurationError`. So does a wrong type: `"3"` is not a number, because values are never coerced. A string that contains `{{` skips the `enum` check, because its value is only known at run time.
|
|
121
121
|
- Actions run in the Run phase, by code, with zero model calls. They run while `silenced` too.
|
|
122
122
|
- A `do` step whose action name is not registered throws at build. If the registry changed under a running agent, the step reports `code: 'action-failed'` with `detail: 'unknown action "notify"'`.
|
|
@@ -174,7 +174,7 @@ const agent = f.agent({
|
|
|
174
174
|
|
|
175
175
|
`parameters` use the same `type`, `enum` and `description` as fields (plus `optional: true`), and `run` receives them already typed: `mensagem` is a `string` above, nothing to check.
|
|
176
176
|
|
|
177
|
-
`with` fills the parameters. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced before the action runs. A placeholder whose value is unknown stays as written, so a skipped `tamanho` reaches the seller as `{{data.tamanho}}`. Word the message without it, or fork first with an `if` step on `{ known: ["tamanho"] }`. A `do` step never talks to the model: zero calls.
|
|
177
|
+
`with` fills the parameters. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced before the action runs. A placeholder whose value is unknown stays as written, so a skipped `tamanho` reaches the seller as `{{data.tamanho}}`. One the host filled with an empty string drops out instead. Word the message without it, or fork first with an `if` step on `{ known: ["tamanho"] }`. A `do` step never talks to the model: zero calls.
|
|
178
178
|
|
|
179
179
|
The name and the parameters are checked when the agent is built. An unknown action, or a `with` missing a required parameter, throws `FlowConfigurationError` at startup with the step id and the fix.
|
|
180
180
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@falai/agent",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.5",
|
|
4
4
|
"description": "Conversational state engine for TypeScript where the AI understands, but the code is in control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"release:alpha": "bun publish --tag alpha",
|
|
60
60
|
"release": "bun publish",
|
|
61
61
|
"test": "bun test tests/*.test.ts tests/scenarios/*.test.ts",
|
|
62
|
-
"eval:live": "bun run scripts/eval/live.ts"
|
|
62
|
+
"eval:live": "bun run scripts/eval/live.ts",
|
|
63
|
+
"eval:exclusions": "bun run scripts/eval/exclusions.ts"
|
|
63
64
|
},
|
|
64
65
|
"keywords": [
|
|
65
66
|
"ai",
|
package/src/utils/template.ts
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* per-turn context) and `input` (the run's trigger payload). A path that
|
|
6
6
|
* resolves to nothing keeps its placeholder, so a typo stays visible instead
|
|
7
7
|
* of vanishing into an empty string.
|
|
8
|
+
*
|
|
9
|
+
* A path that resolves to an EMPTY string is a different case: the host knows
|
|
10
|
+
* the field and knows it is blank. Then the placeholder goes and `tidy` closes
|
|
11
|
+
* the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
|
|
12
|
+
* "Ola, tudo bem?" and never "Ola , tudo bem?".
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
15
|
export interface TemplateScope {
|
|
@@ -16,10 +21,30 @@ export interface TemplateScope {
|
|
|
16
21
|
const PLACEHOLDER = /\{\{\s*([^}\s]+)\s*\}\}/g;
|
|
17
22
|
|
|
18
23
|
export function render(template: string, scope: TemplateScope): string {
|
|
19
|
-
|
|
24
|
+
let emptied = false;
|
|
25
|
+
const out = template.replace(PLACEHOLDER, (match, path: string) => {
|
|
20
26
|
const value = lookup(scope, path.split("."));
|
|
21
|
-
|
|
27
|
+
if (value === undefined || value === null) return match;
|
|
28
|
+
const text = stringify(value);
|
|
29
|
+
if (text === "") emptied = true;
|
|
30
|
+
return text;
|
|
22
31
|
});
|
|
32
|
+
return emptied ? tidy(out) : out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Close the hole an empty value leaves: a doubled space, a space before
|
|
37
|
+
* punctuation, a space at the end of a line.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately narrow. It only runs on a string where something substituted to
|
|
40
|
+
* "", and it only collapses a run of spaces that follows a visible character,
|
|
41
|
+
* so indentation in a markdown list survives.
|
|
42
|
+
*/
|
|
43
|
+
function tidy(text: string): string {
|
|
44
|
+
return text
|
|
45
|
+
.replace(/(?<=\S)[ \t]{2,}/g, " ")
|
|
46
|
+
.replace(/ +([,.;:!?\u2026])/g, "$1")
|
|
47
|
+
.replace(/[ \t]+$/gm, "");
|
|
23
48
|
}
|
|
24
49
|
|
|
25
50
|
/** `render` over every string inside a value, recursively. Non-strings pass through. */
|