taias 0.7.1 → 0.8.0
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/README.md +13 -6
- package/dist/index.cjs +130 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -37
- package/dist/index.d.ts +34 -37
- package/dist/index.js +130 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,8 +31,8 @@ npm install taias
|
|
|
31
31
|
import { defineFlow, createTaias } from "taias";
|
|
32
32
|
|
|
33
33
|
const flow = defineFlow("onboard", (flow) => {
|
|
34
|
-
flow.step({ toolName: "scan_repo" }, { nextTool: "configure_app" });
|
|
35
|
-
flow.step({ toolName: "configure_app" }, { nextTool: "deploy" });
|
|
34
|
+
flow.step({ toolName: { is: "scan_repo" } }, { nextTool: "configure_app" });
|
|
35
|
+
flow.step({ toolName: { is: "configure_app" } }, { nextTool: "deploy" });
|
|
36
36
|
});
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -80,13 +80,20 @@ return {
|
|
|
80
80
|
|
|
81
81
|
### `defineFlow(flowId, builder)`
|
|
82
82
|
|
|
83
|
-
Creates a flow definition. Each step is a logic statement: a match condition paired with a decision. Match
|
|
83
|
+
Creates a flow definition. Each step is a logic statement: a match condition paired with a decision. Match conditions use explicit operators (`{ is: ... }`, `{ isNot: ... }`) and can match on `toolName`, `params`, and `result`.
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
86
|
const myFlow = defineFlow("my_flow", (flow) => {
|
|
87
|
-
flow.step({ toolName: { is: "tool_a" } }, { nextTool: "tool_b" });
|
|
88
|
-
flow.step({ toolName: { isNot: "abort" } }, { nextTool: "continue" });
|
|
89
|
-
flow.step(
|
|
87
|
+
flow.step({ toolName: { is: "tool_a" } }, { nextTool: "tool_b" });
|
|
88
|
+
flow.step({ toolName: { isNot: "abort" } }, { nextTool: "continue" });
|
|
89
|
+
flow.step(
|
|
90
|
+
{ toolName: { is: "scan_repo" }, params: { language: { is: "python" } } },
|
|
91
|
+
{ nextTool: "configure_python" },
|
|
92
|
+
);
|
|
93
|
+
flow.step(
|
|
94
|
+
{ result: { hasConfig: { is: true } } },
|
|
95
|
+
{ nextTool: "review_config" },
|
|
96
|
+
);
|
|
90
97
|
});
|
|
91
98
|
```
|
|
92
99
|
|
package/dist/index.cjs
CHANGED
|
@@ -32,14 +32,13 @@ function defineFlow(flowId, builder) {
|
|
|
32
32
|
const steps = [];
|
|
33
33
|
const flowBuilder = {
|
|
34
34
|
step(match, input) {
|
|
35
|
-
const condition = typeof match === "string" ? { toolName: match } : match;
|
|
36
35
|
if (typeof input === "function") {
|
|
37
|
-
steps.push({ kind: "handler", match
|
|
36
|
+
steps.push({ kind: "handler", match, handler: input });
|
|
38
37
|
} else {
|
|
39
38
|
steps.push({
|
|
40
39
|
kind: "logic",
|
|
41
40
|
statement: {
|
|
42
|
-
match
|
|
41
|
+
match,
|
|
43
42
|
decision: input
|
|
44
43
|
}
|
|
45
44
|
});
|
|
@@ -111,33 +110,61 @@ function selectUiAffordances(decision, index, opts = {}) {
|
|
|
111
110
|
function generateAdvice(nextTool) {
|
|
112
111
|
return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;
|
|
113
112
|
}
|
|
114
|
-
function normalizeFieldCondition(field) {
|
|
115
|
-
return typeof field === "string" ? { is: field } : field;
|
|
116
|
-
}
|
|
117
113
|
function evaluateCondition(condition, value) {
|
|
118
114
|
if ("is" in condition) return value === condition.is;
|
|
119
115
|
if ("isNot" in condition) return value !== condition.isNot;
|
|
120
116
|
return false;
|
|
121
117
|
}
|
|
122
118
|
function evaluateMatch(match, ctx) {
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
if (match.toolName && !evaluateCondition(match.toolName, ctx.toolName)) return false;
|
|
120
|
+
if (match.params) {
|
|
121
|
+
if (!ctx.params) return false;
|
|
122
|
+
for (const [key, condition] of Object.entries(match.params)) {
|
|
123
|
+
if (!evaluateCondition(condition, ctx.params[key])) return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (match.result) {
|
|
127
|
+
if (!ctx.result) return false;
|
|
128
|
+
for (const [key, condition] of Object.entries(match.result)) {
|
|
129
|
+
if (!evaluateCondition(condition, ctx.result[key])) return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
function extractIsConditions(match) {
|
|
135
|
+
const conditions = [];
|
|
136
|
+
if (match.toolName && "is" in match.toolName) {
|
|
137
|
+
conditions.push({ path: "toolName", value: match.toolName.is });
|
|
138
|
+
}
|
|
139
|
+
if (match.params) {
|
|
140
|
+
for (const [key, cond] of Object.entries(match.params)) {
|
|
141
|
+
if ("is" in cond) conditions.push({ path: `params.${key}`, value: cond.is });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (match.result) {
|
|
145
|
+
for (const [key, cond] of Object.entries(match.result)) {
|
|
146
|
+
if ("is" in cond) conditions.push({ path: `result.${key}`, value: cond.is });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return conditions;
|
|
125
150
|
}
|
|
126
|
-
function
|
|
127
|
-
if (
|
|
128
|
-
|
|
151
|
+
function hasConditionOnField(match, path) {
|
|
152
|
+
if (path === "toolName") return !!match.toolName;
|
|
153
|
+
if (path.startsWith("params.")) return !!match.params?.[path.slice(7)];
|
|
154
|
+
if (path.startsWith("result.")) return !!match.result?.[path.slice(7)];
|
|
155
|
+
return false;
|
|
129
156
|
}
|
|
130
|
-
function
|
|
131
|
-
if (
|
|
132
|
-
if ("
|
|
133
|
-
|
|
157
|
+
function getContextValue(ctx, path) {
|
|
158
|
+
if (path === "toolName") return ctx.toolName;
|
|
159
|
+
if (path.startsWith("params.")) return ctx.params?.[path.slice(7)];
|
|
160
|
+
if (path.startsWith("result.")) return ctx.result?.[path.slice(7)];
|
|
161
|
+
return void 0;
|
|
134
162
|
}
|
|
135
163
|
function getMatch(step) {
|
|
136
164
|
return step.kind === "logic" ? step.statement.match : step.match;
|
|
137
165
|
}
|
|
138
166
|
function serializeMatch(match) {
|
|
139
|
-
|
|
140
|
-
return JSON.stringify({ toolName: normalized });
|
|
167
|
+
return JSON.stringify(match);
|
|
141
168
|
}
|
|
142
169
|
function createTaias(options) {
|
|
143
170
|
const {
|
|
@@ -153,50 +180,112 @@ function createTaias(options) {
|
|
|
153
180
|
for (const step of flow.steps) {
|
|
154
181
|
const key = serializeMatch(getMatch(step));
|
|
155
182
|
if (seenKeys.has(key)) {
|
|
156
|
-
const match = getMatch(step);
|
|
157
|
-
const normalized = normalizeFieldCondition(match.toolName);
|
|
158
|
-
const label = "is" in normalized ? normalized.is : `isNot:${normalized.isNot}`;
|
|
159
183
|
throw new Error(
|
|
160
|
-
`Taias: Duplicate match condition
|
|
184
|
+
`Taias: Duplicate match condition in flow '${flow.id}'. Each step must have a unique match condition. Duplicate: ${key}`
|
|
161
185
|
);
|
|
162
186
|
}
|
|
163
187
|
seenKeys.add(key);
|
|
164
188
|
}
|
|
165
189
|
}
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
const fieldIndexes = /* @__PURE__ */ new Map();
|
|
191
|
+
const indexableStepIndices = [];
|
|
192
|
+
const broadStepIndices = [];
|
|
193
|
+
for (let i = 0; i < flow.steps.length; i++) {
|
|
194
|
+
const match = getMatch(flow.steps[i]);
|
|
195
|
+
const isConditions = extractIsConditions(match);
|
|
196
|
+
if (isConditions.length === 0) {
|
|
197
|
+
broadStepIndices.push(i);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
indexableStepIndices.push(i);
|
|
201
|
+
for (const { path, value } of isConditions) {
|
|
202
|
+
let fieldIndex = fieldIndexes.get(path);
|
|
203
|
+
if (!fieldIndex) {
|
|
204
|
+
fieldIndex = { valueMap: /* @__PURE__ */ new Map(), unconstrained: [] };
|
|
205
|
+
fieldIndexes.set(path, fieldIndex);
|
|
206
|
+
}
|
|
207
|
+
let stepList = fieldIndex.valueMap.get(value);
|
|
208
|
+
if (!stepList) {
|
|
209
|
+
stepList = [];
|
|
210
|
+
fieldIndex.valueMap.set(value, stepList);
|
|
211
|
+
}
|
|
212
|
+
stepList.push(i);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const [fieldPath, fieldIndex] of fieldIndexes) {
|
|
216
|
+
for (const i of indexableStepIndices) {
|
|
217
|
+
if (!hasConditionOnField(getMatch(flow.steps[i]), fieldPath)) {
|
|
218
|
+
fieldIndex.unconstrained.push(i);
|
|
219
|
+
}
|
|
174
220
|
}
|
|
175
221
|
}
|
|
176
|
-
const hasBroadSteps =
|
|
222
|
+
const hasBroadSteps = broadStepIndices.length > 0;
|
|
177
223
|
const registryIndex = buildRegistryIndex(affordances);
|
|
178
224
|
return {
|
|
179
225
|
async resolve(ctx) {
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
226
|
+
let matchedStep;
|
|
227
|
+
const applicableFieldPaths = [];
|
|
228
|
+
for (const fieldPath of fieldIndexes.keys()) {
|
|
229
|
+
const ctxValue = getContextValue(ctx, fieldPath);
|
|
230
|
+
if (ctxValue !== void 0) {
|
|
231
|
+
applicableFieldPaths.push(fieldPath);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (applicableFieldPaths.length > 0) {
|
|
235
|
+
let candidates = null;
|
|
236
|
+
for (const fieldPath of applicableFieldPaths) {
|
|
237
|
+
const fieldIndex = fieldIndexes.get(fieldPath);
|
|
238
|
+
const ctxValue = getContextValue(ctx, fieldPath);
|
|
239
|
+
const fieldCandidates = /* @__PURE__ */ new Set();
|
|
240
|
+
const indexed = fieldIndex.valueMap.get(ctxValue);
|
|
241
|
+
if (indexed) {
|
|
242
|
+
for (const idx of indexed) fieldCandidates.add(idx);
|
|
243
|
+
}
|
|
244
|
+
for (const idx of fieldIndex.unconstrained) {
|
|
245
|
+
fieldCandidates.add(idx);
|
|
246
|
+
}
|
|
247
|
+
if (candidates === null) {
|
|
248
|
+
candidates = fieldCandidates;
|
|
249
|
+
} else {
|
|
250
|
+
for (const idx of candidates) {
|
|
251
|
+
if (!fieldCandidates.has(idx)) candidates.delete(idx);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (candidates && candidates.size > 0) {
|
|
256
|
+
const sorted = [...candidates].sort((a, b) => a - b);
|
|
257
|
+
for (const idx of sorted) {
|
|
258
|
+
if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {
|
|
259
|
+
matchedStep = flow.steps[idx];
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} else if (indexableStepIndices.length > 0) {
|
|
265
|
+
for (const idx of indexableStepIndices) {
|
|
266
|
+
if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {
|
|
267
|
+
matchedStep = flow.steps[idx];
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!matchedStep && hasBroadSteps) {
|
|
273
|
+
for (const idx of broadStepIndices) {
|
|
274
|
+
if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {
|
|
275
|
+
matchedStep = flow.steps[idx];
|
|
187
276
|
break;
|
|
188
277
|
}
|
|
189
278
|
}
|
|
190
279
|
}
|
|
191
|
-
if (!
|
|
280
|
+
if (!matchedStep) {
|
|
192
281
|
onMissingStep?.(ctx);
|
|
193
282
|
return null;
|
|
194
283
|
}
|
|
195
284
|
let result;
|
|
196
|
-
if (
|
|
197
|
-
result =
|
|
285
|
+
if (matchedStep.kind === "logic") {
|
|
286
|
+
result = matchedStep.statement.decision;
|
|
198
287
|
} else {
|
|
199
|
-
result = await
|
|
288
|
+
result = await matchedStep.handler(ctx);
|
|
200
289
|
}
|
|
201
290
|
if (!result) return null;
|
|
202
291
|
if (devMode && result.nextTool === "") {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/flow.ts","../src/uiAffordances/types.ts","../src/uiAffordances/indexing.ts","../src/uiAffordances/select.ts","../src/createTaias.ts","../src/uiAffordances/defineAffordances.ts","../src/uiAffordances/mergeAffordances.ts"],"sourcesContent":["// Main exports\nexport { defineFlow } from \"./flow\";\nexport { createTaias } from \"./createTaias\";\n\n// UI affordances exports\nexport { defineAffordances } from \"./uiAffordances/defineAffordances\";\nexport { mergeAffordances } from \"./uiAffordances/mergeAffordances\";\nexport type { AffordanceRegistrar } from \"./uiAffordances/defineAffordances\";\nexport type {\n DefaultSlots,\n CanonicalSlot,\n Binding,\n BindingInput,\n HandleRegistration,\n Selection,\n UiSelections,\n AffordanceRegistry,\n} from \"./uiAffordances/types\";\n\n// Core + flow type exports\nexport type {\n Condition,\n FieldCondition,\n Decision,\n TaiasContext,\n StepDecision,\n Affordances,\n StepHandler,\n StepInput,\n MatchCondition,\n LogicStatement,\n FlowStep,\n FlowDefinition,\n FlowBuilder,\n TaiasOptions,\n Taias,\n} from \"./types\";\n","import type { FlowBuilder, FlowDefinition, FlowStep, MatchCondition, StepInput } from \"./types\";\n\n/**\n * Define a flow with its steps.\n *\n * @param flowId - Unique identifier for the flow\n * @param builder - Callback that receives a FlowBuilder to define steps\n * @returns A FlowDefinition object\n *\n * @example Logic statement with explicit operator\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step({ toolName: { is: \"scan_repo\" } }, { nextTool: \"configure_app\" });\n * });\n * ```\n *\n * @example isNot operator\n * ```ts\n * flow.step({ toolName: { isNot: \"abort_session\" } }, { nextTool: \"continue_flow\" });\n * ```\n *\n * @example Sugar forms (backwards compatible)\n * Bare strings are sugar for { toolName: { is: string } }:\n * ```ts\n * flow.step({ toolName: \"scan_repo\" }, { nextTool: \"configure_app\" }); // sugar for { is: \"scan_repo\" }\n * flow.step(\"scan_repo\", { nextTool: \"configure_app\" }); // string sugar for { toolName: { is: \"scan_repo\" } }\n * ```\n */\nexport function defineFlow(\n flowId: string,\n builder: (flow: FlowBuilder) => void\n): FlowDefinition {\n const steps: FlowStep[] = [];\n\n const flowBuilder: FlowBuilder = {\n step(match: string | MatchCondition, input: StepInput): void {\n // Normalize: string is sugar for { toolName: string }\n const condition: MatchCondition =\n typeof match === \"string\" ? { toolName: match } : match;\n\n if (typeof input === \"function\") {\n // Handler function -- backwards-compatible escape hatch.\n // The match condition is stored alongside the handler since\n // the function itself has no formal match conditions.\n steps.push({ kind: \"handler\", match: condition, handler: input });\n } else {\n // Static logic statement -- the core primitive.\n // The statement is the sole source of truth for its match\n // conditions and decision.\n steps.push({\n kind: \"logic\",\n statement: {\n match: condition,\n decision: input,\n },\n });\n }\n },\n };\n\n builder(flowBuilder);\n\n return {\n id: flowId,\n steps,\n };\n}\n\n","/**\n * Default slots for backwards compatibility.\n * Users can define custom slots by passing a type parameter.\n */\nexport type DefaultSlots = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\n\n/**\n * Alias for backwards compatibility in exports.\n * @deprecated Use DefaultSlots or define your own slot type\n */\nexport type CanonicalSlot = DefaultSlots;\n\nexport type Binding = {\n key: string;\n value: string;\n};\n\n/**\n * Input format for registering affordance bindings.\n * - { toolName } is shorthand for { key: \"nextTool\", value: toolName }\n * - { key, value } is the generalized form for custom bindings\n */\nexport type BindingInput = { toolName: string } | { key: string; value: string };\n\n/**\n * A registered UI affordance handle.\n * Generic over slot type S for custom slot support.\n */\nexport type HandleRegistration<S extends string = DefaultSlots> = {\n slot: S;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\n/**\n * UI selections keyed by slot name.\n * Generic over slot type S for custom slot support.\n */\nexport type UiSelections<S extends string = DefaultSlots> = Partial<Record<S, Selection>>;\n\n/**\n * Collection of registered handles.\n * Generic over slot type S for custom slot support.\n */\nexport type AffordanceRegistry<S extends string = DefaultSlots> = {\n handles: HandleRegistration<S>[];\n};\n\nexport function normalizeBinding(input: BindingInput): Binding {\n if (\"toolName\" in input) return { key: \"nextTool\", value: input.toolName };\n return { key: input.key, value: input.value };\n}\n\n/**\n * Creates a binding key for indexing.\n * Accepts any string for slot to support custom slots.\n */\nexport function makeBindingKey(slot: string, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, DefaultSlots, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\n/**\n * Index structure for efficient affordance lookup.\n * Generic over slot type S for custom slot support.\n */\nexport type RegistryIndex<S extends string = DefaultSlots> = {\n byBindingKey: Map<string, HandleRegistration<S>>;\n slots: Set<S>;\n /** Inferred decision field for each slot, derived from handle bindings. */\n slotKeyMap: Map<S, string>;\n};\n\n/**\n * Build an index from a registry for efficient lookup during selection.\n * Tracks which slots have registered handles and infers the decision field\n * for each slot from its handle bindings.\n *\n * @throws Error if handles for the same slot bind to different keys\n */\nexport function buildRegistryIndex<S extends string = DefaultSlots>(\n registry?: AffordanceRegistry<S>\n): RegistryIndex<S> {\n const byBindingKey = new Map<string, HandleRegistration<S>>();\n const slots = new Set<S>();\n const slotKeyMap = new Map<S, string>();\n\n if (!registry) return { byBindingKey, slots, slotKeyMap };\n\n for (const h of registry.handles) {\n slots.add(h.slot);\n\n // Infer and validate slot key\n const existingKey = slotKeyMap.get(h.slot);\n if (existingKey && existingKey !== h.bindsTo.key) {\n throw new Error(\n `[Taias] Slot \"${h.slot}\" has handles bound to different keys: \"${existingKey}\" and \"${h.bindsTo.key}\". ` +\n `All handles for a slot must use the same decision field.`\n );\n }\n slotKeyMap.set(h.slot, h.bindsTo.key);\n\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey, slots, slotKeyMap };\n}\n","import type { Decision } from \"../types\";\nimport type { DefaultSlots, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nexport type SelectOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Select UI affordances based on flow decision.\n * Uses the inferred decision field for each slot from the registry index.\n */\nexport function selectUiAffordances<S extends string = DefaultSlots>(\n decision: Decision,\n index: RegistryIndex<S>,\n opts: SelectOptions = {}\n): UiSelections<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections<S> = {};\n\n for (const slot of index.slots) {\n // Use inferred key from handle bindings\n const field = index.slotKeyMap.get(slot);\n if (!field) continue;\n\n const value = decision[field];\n if (!value) continue;\n\n const k = makeBindingKey(slot, { key: field, value });\n const handle = index.byBindingKey.get(k);\n\n if (!handle) {\n if (devMode) warn(`[Taias] No affordance for slot \"${slot}\" when ${field}=\"${value}\"`);\n continue;\n }\n\n (selections as Record<string, unknown>)[slot] = {\n handleId: handle.handleId,\n bindsTo: handle.bindsTo,\n };\n }\n\n return selections;\n}\n","import type { Affordances, Condition, FieldCondition, FlowStep, MatchCondition, StepDecision, Taias, TaiasContext, TaiasOptions, Decision } from \"./types\";\nimport type { DefaultSlots } from \"./uiAffordances/types\";\nimport { buildRegistryIndex } from \"./uiAffordances/indexing\";\nimport { selectUiAffordances } from \"./uiAffordances/select\";\n\n/**\n * Generate advice text for a given next tool.\n */\nfunction generateAdvice(nextTool: string): string {\n return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;\n}\n\n// ---------------------------------------------------------------------------\n// Condition normalization and evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize a FieldCondition to a canonical Condition object.\n * A bare string is sugar for { is: string }.\n */\nfunction normalizeFieldCondition(field: FieldCondition): Condition {\n return typeof field === \"string\" ? { is: field } : field;\n}\n\n/**\n * Evaluate a single Condition against a value.\n */\nfunction evaluateCondition(condition: Condition, value: string): boolean {\n if (\"is\" in condition) return value === condition.is;\n if (\"isNot\" in condition) return value !== condition.isNot;\n return false;\n}\n\n/**\n * Evaluate a full MatchCondition against a TaiasContext.\n * All conditions in the match must be satisfied.\n */\nfunction evaluateMatch(match: MatchCondition, ctx: TaiasContext): boolean {\n const toolCondition = normalizeFieldCondition(match.toolName);\n return evaluateCondition(toolCondition, ctx.toolName);\n}\n\n/**\n * Check whether a FieldCondition is indexable (i.e., uses the `is` operator).\n * Indexable conditions enable O(1) Map lookup at resolve time.\n */\nfunction isIndexable(field: FieldCondition): boolean {\n if (typeof field === \"string\") return true;\n return \"is\" in field;\n}\n\n/**\n * Extract the index key from an indexable FieldCondition.\n * Only call this when isIndexable() returns true.\n */\nfunction indexKey(field: FieldCondition): string {\n if (typeof field === \"string\") return field;\n if (\"is\" in field) return field.is;\n throw new Error(\"Cannot derive index key from non-indexable condition\");\n}\n\n// ---------------------------------------------------------------------------\n// Step access helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the match condition from a FlowStep.\n *\n * - Logic-based steps: match comes from statement.match (the statement is\n * the sole source of truth for its match conditions)\n * - Handler-based steps (backwards compatibility): match is stored directly on the step\n */\nfunction getMatch(step: FlowStep): MatchCondition {\n return step.kind === \"logic\" ? step.statement.match : step.match;\n}\n\n/**\n * Serialize a MatchCondition into a stable string for duplicate detection.\n * Normalizes sugar forms so that equivalent conditions produce the same key.\n */\nfunction serializeMatch(match: MatchCondition): string {\n const normalized = normalizeFieldCondition(match.toolName);\n return JSON.stringify({ toolName: normalized });\n}\n\n/**\n * createTaias constructs a decision engine.\n *\n * Taias resolves context into a generalized Decision object,\n * and then manifests that decision into concrete affordances:\n *\n * - LLM guidance (advice)\n * - UI affordance selections\n *\n * Flow logic is expressed as logic statements -- structured data that\n * Taias understands. (Handler functions remain as a backwards-compatible\n * escape hatch.)\n * \n * Flow logic determines *what should happen next*.\n * UI affordances determine *how that decision appears in the interface*.\n *\n * This file is the boundary where:\n *\n * Inputs → Decision → Manifestations\n *\n * are unified into a single resolve() call.\n *\n * @example Default slots (backwards compatible)\n * ```ts\n * const taias = createTaias({ flow, affordances });\n * ```\n *\n * @example Custom slots (fully type-safe)\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\" | \"headerStyle\";\n * const affordances = defineAffordances<MySlots>((r) => {\n * r.primaryCta(\"cta\", { key: \"nextTool\", value: \"createUser\" });\n * r.contentArea(\"content\", { key: \"contentArea\", value: \"form\" });\n * r.headerStyle(\"header\", { key: \"headerStyle\", value: \"progress\" });\n * });\n * const taias = createTaias<MySlots>({ flow, affordances });\n * ```\n */\nexport function createTaias<S extends string = DefaultSlots>(\n options: TaiasOptions<S>\n): Taias<S> {\n const {\n flow,\n affordances,\n devMode = false,\n onMissingStep,\n onWarn,\n } = options;\n\n const warn = onWarn ?? ((msg: string) => console.warn(msg));\n\n // Dev mode: Check for duplicate match conditions.\n // Two steps with structurally identical normalized conditions are duplicates.\n if (devMode) {\n const seenKeys = new Set<string>();\n for (const step of flow.steps) {\n const key = serializeMatch(getMatch(step));\n if (seenKeys.has(key)) {\n const match = getMatch(step);\n const normalized = normalizeFieldCondition(match.toolName);\n const label = \"is\" in normalized ? normalized.is : `isNot:${normalized.isNot}`;\n throw new Error(\n `Taias: Duplicate match condition '${label}' in flow '${flow.id}'. Each step must have a unique match condition.`\n );\n }\n seenKeys.add(key);\n }\n }\n\n // Build internal indexes for efficient resolution.\n //\n // Steps with indexable conditions (is / string sugar) go into an exact\n // Map for O(1) lookup. Steps with non-indexable conditions (isNot) go\n // into a separate list. When no broad steps exist, resolve uses the\n // fast path (Map only). When broad steps exist, resolve evaluates all\n // steps in definition order.\n //\n // This indexing is a performance optimization derived from the current\n // set of operators, not a permanent architectural choice. It will evolve\n // as operators and match condition fields expand.\n const exactIndex = new Map<string, FlowStep>();\n const broadSteps: FlowStep[] = [];\n\n for (const step of flow.steps) {\n const match = getMatch(step);\n if (isIndexable(match.toolName)) {\n exactIndex.set(indexKey(match.toolName), step);\n } else {\n broadSteps.push(step);\n }\n }\n\n const hasBroadSteps = broadSteps.length > 0;\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex<S>(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances<S> | null> {\n let step: FlowStep | undefined;\n\n if (!hasBroadSteps) {\n // Fast path: all steps use indexable conditions (is / string sugar).\n // O(1) Map lookup -- same performance as before operators were introduced.\n step = exactIndex.get(ctx.toolName);\n } else {\n // Full evaluation: some steps use non-indexable conditions (isNot).\n // Evaluate all steps in definition order; first match wins.\n for (const candidate of flow.steps) {\n if (evaluateMatch(getMatch(candidate), ctx)) {\n step = candidate;\n break;\n }\n }\n }\n\n if (!step) {\n onMissingStep?.(ctx);\n return null;\n }\n\n // Evaluate the step based on its kind:\n // - Logic statements: return the decision directly (no function call)\n // - Handler functions (backwards compatibility): call the handler and await the result\n let result: StepDecision | null;\n\n if (step.kind === \"logic\") {\n result = step.statement.decision;\n } else {\n result = await step.handler(ctx);\n }\n\n if (!result) return null;\n\n if (devMode && result.nextTool === \"\") {\n warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n // Build decision object from flow result (spread all fields)\n const decision: Decision = { ...result };\n\n // Compute UI selections (may be empty if no registry passed)\n const selections = selectUiAffordances<S>(decision, registryIndex, {\n devMode,\n onWarn: warn,\n });\n\n return {\n advice: generateAdvice(result.nextTool),\n decision,\n selections,\n };\n },\n };\n}\n","import type {\n AffordanceRegistry,\n BindingInput,\n DefaultSlots,\n HandleRegistration,\n} from \"./types\";\nimport { normalizeBinding } from \"./types\";\n\n/**\n * Mapped type that creates a registration method for each slot in S.\n * This enables fully typed custom slots via generics.\n */\nexport type AffordanceRegistrar<S extends string = DefaultSlots> = {\n [K in S]: (handleId: string, bindsTo: BindingInput) => void;\n};\n\n/**\n * Define UI affordances for a widget using a builder pattern.\n *\n * @example Default slots (backwards compatible)\n * ```ts\n * const affordances = defineAffordances((r) => {\n * r.primaryCta(\"cta.recommend\", { toolName: \"get_recommendations\" });\n * r.widgetVariant(\"variant.discovery\", { toolName: \"get_recommendations\" });\n * });\n * ```\n *\n * @example Custom slots (fully type-safe)\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\" | \"headerStyle\";\n * const affordances = defineAffordances<MySlots>((r) => {\n * r.primaryCta(\"cta.create\", { toolName: \"createUser\" });\n * r.contentArea(\"content.form\", { key: \"contentArea\", value: \"email-form\" });\n * r.headerStyle(\"header.progress\", { key: \"headerStyle\", value: \"step-1\" });\n * });\n * ```\n */\nexport function defineAffordances<S extends string = DefaultSlots>(\n builder: (r: AffordanceRegistrar<S>) => void\n): AffordanceRegistry<S> {\n const handles: HandleRegistration<S>[] = [];\n\n // Proxy creates methods on-the-fly for any slot name.\n // TypeScript ensures only valid slot names (from S) are called.\n const registrar = new Proxy({} as AffordanceRegistrar<S>, {\n get(_, slot: string) {\n return (handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot: slot as S,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n },\n });\n\n builder(registrar);\n return { handles };\n}\n","import type { AffordanceRegistry, DefaultSlots } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Merge multiple affordance registries into one.\n * Generic over slot type S for custom slot support.\n *\n * @example Default slots\n * ```ts\n * const merged = mergeAffordances([widgetA, widgetB]);\n * ```\n *\n * @example Custom slots\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\";\n * const merged = mergeAffordances<MySlots>([widgetA, widgetB]);\n * ```\n */\nexport function mergeAffordances<S extends string = DefaultSlots>(\n registries: AffordanceRegistry<S>[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry<S> = { handles: registries.flatMap((r) => r.handles) };\n\n if (!devMode) return merged;\n\n // Check for duplicate handleIds\n const seenHandleIds = new Set<string>();\n for (const h of merged.handles) {\n if (seenHandleIds.has(h.handleId)) {\n throw new Error(`[Taias] Duplicate handleId \"${h.handleId}\"`);\n }\n seenHandleIds.add(h.handleId);\n }\n\n // Check for ambiguous bindings (same slot + key + value)\n const seenTriples = new Set<string>();\n for (const h of merged.handles) {\n const k = makeBindingKey(h.slot, h.bindsTo);\n if (seenTriples.has(k)) {\n throw new Error(\n `[Taias] Ambiguous affordance: slot \"${h.slot}\" has multiple handles bound to (${h.bindsTo.key}=\"${h.bindsTo.value}\")`\n );\n }\n seenTriples.add(k);\n }\n\n warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);\n return merged;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/flow.ts","../src/uiAffordances/types.ts","../src/uiAffordances/indexing.ts","../src/uiAffordances/select.ts","../src/createTaias.ts","../src/uiAffordances/defineAffordances.ts","../src/uiAffordances/mergeAffordances.ts"],"sourcesContent":["// Main exports\nexport { defineFlow } from \"./flow\";\nexport { createTaias } from \"./createTaias\";\n\n// UI affordances exports\nexport { defineAffordances } from \"./uiAffordances/defineAffordances\";\nexport { mergeAffordances } from \"./uiAffordances/mergeAffordances\";\nexport type { AffordanceRegistrar } from \"./uiAffordances/defineAffordances\";\nexport type {\n DefaultSlots,\n CanonicalSlot,\n Binding,\n BindingInput,\n HandleRegistration,\n Selection,\n UiSelections,\n AffordanceRegistry,\n} from \"./uiAffordances/types\";\n\n// Core + flow type exports\nexport type {\n Condition,\n Decision,\n TaiasContext,\n StepDecision,\n Affordances,\n StepHandler,\n StepInput,\n MatchCondition,\n LogicStatement,\n FlowStep,\n FlowDefinition,\n FlowBuilder,\n TaiasOptions,\n Taias,\n} from \"./types\";\n","import type { FlowBuilder, FlowDefinition, FlowStep, MatchCondition, StepInput } from \"./types\";\n\n/**\n * Define a flow with its steps.\n *\n * @param flowId - Unique identifier for the flow\n * @param builder - Callback that receives a FlowBuilder to define steps\n * @returns A FlowDefinition object\n *\n * @example Logic statement matching on toolName\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step({ toolName: { is: \"scan_repo\" } }, { nextTool: \"configure_app\" });\n * });\n * ```\n *\n * @example Matching on params and result\n * ```ts\n * flow.step(\n * { toolName: { is: \"scan_repo\" }, params: { language: { is: \"python\" } } },\n * { nextTool: \"configure_python\" },\n * );\n * flow.step(\n * { result: { hasConfig: { is: true } } },\n * { nextTool: \"review_config\" },\n * );\n * ```\n *\n * @example isNot operator\n * ```ts\n * flow.step({ toolName: { isNot: \"abort_session\" } }, { nextTool: \"continue_flow\" });\n * ```\n */\nexport function defineFlow(\n flowId: string,\n builder: (flow: FlowBuilder) => void\n): FlowDefinition {\n const steps: FlowStep[] = [];\n\n const flowBuilder: FlowBuilder = {\n step(match: MatchCondition, input: StepInput): void {\n if (typeof input === \"function\") {\n steps.push({ kind: \"handler\", match, handler: input });\n } else {\n steps.push({\n kind: \"logic\",\n statement: {\n match,\n decision: input,\n },\n });\n }\n },\n };\n\n builder(flowBuilder);\n\n return {\n id: flowId,\n steps,\n };\n}\n","/**\n * Default slots for backwards compatibility.\n * Users can define custom slots by passing a type parameter.\n */\nexport type DefaultSlots = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\n\n/**\n * Alias for backwards compatibility in exports.\n * @deprecated Use DefaultSlots or define your own slot type\n */\nexport type CanonicalSlot = DefaultSlots;\n\nexport type Binding = {\n key: string;\n value: string;\n};\n\n/**\n * Input format for registering affordance bindings.\n * - { toolName } is shorthand for { key: \"nextTool\", value: toolName }\n * - { key, value } is the generalized form for custom bindings\n */\nexport type BindingInput = { toolName: string } | { key: string; value: string };\n\n/**\n * A registered UI affordance handle.\n * Generic over slot type S for custom slot support.\n */\nexport type HandleRegistration<S extends string = DefaultSlots> = {\n slot: S;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\n/**\n * UI selections keyed by slot name.\n * Generic over slot type S for custom slot support.\n */\nexport type UiSelections<S extends string = DefaultSlots> = Partial<Record<S, Selection>>;\n\n/**\n * Collection of registered handles.\n * Generic over slot type S for custom slot support.\n */\nexport type AffordanceRegistry<S extends string = DefaultSlots> = {\n handles: HandleRegistration<S>[];\n};\n\nexport function normalizeBinding(input: BindingInput): Binding {\n if (\"toolName\" in input) return { key: \"nextTool\", value: input.toolName };\n return { key: input.key, value: input.value };\n}\n\n/**\n * Creates a binding key for indexing.\n * Accepts any string for slot to support custom slots.\n */\nexport function makeBindingKey(slot: string, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, DefaultSlots, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\n/**\n * Index structure for efficient affordance lookup.\n * Generic over slot type S for custom slot support.\n */\nexport type RegistryIndex<S extends string = DefaultSlots> = {\n byBindingKey: Map<string, HandleRegistration<S>>;\n slots: Set<S>;\n /** Inferred decision field for each slot, derived from handle bindings. */\n slotKeyMap: Map<S, string>;\n};\n\n/**\n * Build an index from a registry for efficient lookup during selection.\n * Tracks which slots have registered handles and infers the decision field\n * for each slot from its handle bindings.\n *\n * @throws Error if handles for the same slot bind to different keys\n */\nexport function buildRegistryIndex<S extends string = DefaultSlots>(\n registry?: AffordanceRegistry<S>\n): RegistryIndex<S> {\n const byBindingKey = new Map<string, HandleRegistration<S>>();\n const slots = new Set<S>();\n const slotKeyMap = new Map<S, string>();\n\n if (!registry) return { byBindingKey, slots, slotKeyMap };\n\n for (const h of registry.handles) {\n slots.add(h.slot);\n\n // Infer and validate slot key\n const existingKey = slotKeyMap.get(h.slot);\n if (existingKey && existingKey !== h.bindsTo.key) {\n throw new Error(\n `[Taias] Slot \"${h.slot}\" has handles bound to different keys: \"${existingKey}\" and \"${h.bindsTo.key}\". ` +\n `All handles for a slot must use the same decision field.`\n );\n }\n slotKeyMap.set(h.slot, h.bindsTo.key);\n\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey, slots, slotKeyMap };\n}\n","import type { Decision } from \"../types\";\nimport type { DefaultSlots, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nexport type SelectOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Select UI affordances based on flow decision.\n * Uses the inferred decision field for each slot from the registry index.\n */\nexport function selectUiAffordances<S extends string = DefaultSlots>(\n decision: Decision,\n index: RegistryIndex<S>,\n opts: SelectOptions = {}\n): UiSelections<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections<S> = {};\n\n for (const slot of index.slots) {\n // Use inferred key from handle bindings\n const field = index.slotKeyMap.get(slot);\n if (!field) continue;\n\n const value = decision[field];\n if (!value) continue;\n\n const k = makeBindingKey(slot, { key: field, value });\n const handle = index.byBindingKey.get(k);\n\n if (!handle) {\n if (devMode) warn(`[Taias] No affordance for slot \"${slot}\" when ${field}=\"${value}\"`);\n continue;\n }\n\n (selections as Record<string, unknown>)[slot] = {\n handleId: handle.handleId,\n bindsTo: handle.bindsTo,\n };\n }\n\n return selections;\n}\n","import type { Affordances, Condition, FlowStep, MatchCondition, StepDecision, Taias, TaiasContext, TaiasOptions, Decision } from \"./types\";\nimport type { DefaultSlots } from \"./uiAffordances/types\";\nimport { buildRegistryIndex } from \"./uiAffordances/indexing\";\nimport { selectUiAffordances } from \"./uiAffordances/select\";\n\n/**\n * Generate advice text for a given next tool.\n */\nfunction generateAdvice(nextTool: string): string {\n return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;\n}\n\n// ---------------------------------------------------------------------------\n// Condition evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a single Condition against a value using strict equality.\n */\nfunction evaluateCondition(condition: Condition, value: unknown): boolean {\n if (\"is\" in condition) return value === condition.is;\n if (\"isNot\" in condition) return value !== condition.isNot;\n return false;\n}\n\n/**\n * Evaluate a full MatchCondition against a TaiasContext.\n * All present conditions must be satisfied (logical AND).\n *\n * - toolName: compared directly against ctx.toolName\n * - params: subset match -- each specified key is checked against ctx.params\n * - result: subset match -- each specified key is checked against ctx.result\n *\n * If a step specifies params/result conditions but the context doesn't\n * include params/result, the step does not match.\n */\nfunction evaluateMatch(match: MatchCondition, ctx: TaiasContext): boolean {\n if (match.toolName && !evaluateCondition(match.toolName, ctx.toolName)) return false;\n\n if (match.params) {\n if (!ctx.params) return false;\n for (const [key, condition] of Object.entries(match.params)) {\n if (!evaluateCondition(condition, ctx.params[key])) return false;\n }\n }\n\n if (match.result) {\n if (!ctx.result) return false;\n for (const [key, condition] of Object.entries(match.result)) {\n if (!evaluateCondition(condition, ctx.result[key])) return false;\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Per-field indexing\n// ---------------------------------------------------------------------------\n\ninterface FieldIndex {\n valueMap: Map<unknown, number[]>;\n unconstrained: number[];\n}\n\n/**\n * Extract all `is` conditions from a match condition as field-path / value pairs.\n */\nfunction extractIsConditions(match: MatchCondition): Array<{ path: string; value: unknown }> {\n const conditions: Array<{ path: string; value: unknown }> = [];\n if (match.toolName && \"is\" in match.toolName) {\n conditions.push({ path: \"toolName\", value: match.toolName.is });\n }\n if (match.params) {\n for (const [key, cond] of Object.entries(match.params)) {\n if (\"is\" in cond) conditions.push({ path: `params.${key}`, value: cond.is });\n }\n }\n if (match.result) {\n for (const [key, cond] of Object.entries(match.result)) {\n if (\"is\" in cond) conditions.push({ path: `result.${key}`, value: cond.is });\n }\n }\n return conditions;\n}\n\n/**\n * Check whether a match condition has a condition on a given field path.\n */\nfunction hasConditionOnField(match: MatchCondition, path: string): boolean {\n if (path === \"toolName\") return !!match.toolName;\n if (path.startsWith(\"params.\")) return !!match.params?.[path.slice(7)];\n if (path.startsWith(\"result.\")) return !!match.result?.[path.slice(7)];\n return false;\n}\n\n/**\n * Get the value from a TaiasContext for a given field path.\n */\nfunction getContextValue(ctx: TaiasContext, path: string): unknown {\n if (path === \"toolName\") return ctx.toolName;\n if (path.startsWith(\"params.\")) return ctx.params?.[path.slice(7)];\n if (path.startsWith(\"result.\")) return ctx.result?.[path.slice(7)];\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Step access helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the match condition from a FlowStep.\n *\n * - Logic-based steps: match comes from statement.match\n * - Handler-based steps (backwards compatibility): match is stored directly on the step\n */\nfunction getMatch(step: FlowStep): MatchCondition {\n return step.kind === \"logic\" ? step.statement.match : step.match;\n}\n\n/**\n * Serialize a MatchCondition into a stable string for duplicate detection.\n */\nfunction serializeMatch(match: MatchCondition): string {\n return JSON.stringify(match);\n}\n\n/**\n * createTaias constructs a decision engine.\n *\n * Taias resolves context into a generalized Decision object,\n * and then manifests that decision into concrete affordances:\n *\n * - LLM guidance (advice)\n * - UI affordance selections\n *\n * Flow logic is expressed as logic statements -- structured data that\n * Taias understands. (Handler functions remain as a backwards-compatible\n * escape hatch.)\n *\n * Flow logic determines *what should happen next*.\n * UI affordances determine *how that decision appears in the interface*.\n *\n * This file is the boundary where:\n *\n * Inputs -> Decision -> Manifestations\n *\n * are unified into a single resolve() call.\n *\n * @example\n * ```ts\n * const taias = createTaias({ flow, affordances });\n * ```\n */\nexport function createTaias<S extends string = DefaultSlots>(\n options: TaiasOptions<S>\n): Taias<S> {\n const {\n flow,\n affordances,\n devMode = false,\n onMissingStep,\n onWarn,\n } = options;\n\n const warn = onWarn ?? ((msg: string) => console.warn(msg));\n\n // Dev mode: Check for duplicate match conditions.\n if (devMode) {\n const seenKeys = new Set<string>();\n for (const step of flow.steps) {\n const key = serializeMatch(getMatch(step));\n if (seenKeys.has(key)) {\n throw new Error(\n `Taias: Duplicate match condition in flow '${flow.id}'. Each step must have a unique match condition. Duplicate: ${key}`\n );\n }\n seenKeys.add(key);\n }\n }\n\n // -----------------------------------------------------------------------\n // Build per-field indexes\n // -----------------------------------------------------------------------\n\n const fieldIndexes = new Map<string, FieldIndex>();\n const indexableStepIndices: number[] = [];\n const broadStepIndices: number[] = [];\n\n for (let i = 0; i < flow.steps.length; i++) {\n const match = getMatch(flow.steps[i]);\n const isConditions = extractIsConditions(match);\n\n if (isConditions.length === 0) {\n broadStepIndices.push(i);\n continue;\n }\n\n indexableStepIndices.push(i);\n\n for (const { path, value } of isConditions) {\n let fieldIndex = fieldIndexes.get(path);\n if (!fieldIndex) {\n fieldIndex = { valueMap: new Map(), unconstrained: [] };\n fieldIndexes.set(path, fieldIndex);\n }\n let stepList = fieldIndex.valueMap.get(value);\n if (!stepList) {\n stepList = [];\n fieldIndex.valueMap.set(value, stepList);\n }\n stepList.push(i);\n }\n }\n\n // Build unconstrained sets: for each field index, find indexable steps\n // that don't have a condition on that field.\n for (const [fieldPath, fieldIndex] of fieldIndexes) {\n for (const i of indexableStepIndices) {\n if (!hasConditionOnField(getMatch(flow.steps[i]), fieldPath)) {\n fieldIndex.unconstrained.push(i);\n }\n }\n }\n\n const hasBroadSteps = broadStepIndices.length > 0;\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex<S>(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances<S> | null> {\n let matchedStep: FlowStep | undefined;\n\n // Phase 1: Find candidates from per-field indexes via intersection\n const applicableFieldPaths: string[] = [];\n for (const fieldPath of fieldIndexes.keys()) {\n const ctxValue = getContextValue(ctx, fieldPath);\n if (ctxValue !== undefined) {\n applicableFieldPaths.push(fieldPath);\n }\n }\n\n if (applicableFieldPaths.length > 0) {\n // Build candidate sets for each applicable field and intersect\n let candidates: Set<number> | null = null;\n\n for (const fieldPath of applicableFieldPaths) {\n const fieldIndex = fieldIndexes.get(fieldPath)!;\n const ctxValue = getContextValue(ctx, fieldPath);\n\n const fieldCandidates = new Set<number>();\n\n // Indexed matches for this field's value\n const indexed = fieldIndex.valueMap.get(ctxValue);\n if (indexed) {\n for (const idx of indexed) fieldCandidates.add(idx);\n }\n\n // Unconstrained steps (don't care about this field)\n for (const idx of fieldIndex.unconstrained) {\n fieldCandidates.add(idx);\n }\n\n if (candidates === null) {\n candidates = fieldCandidates;\n } else {\n // Intersect\n for (const idx of candidates) {\n if (!fieldCandidates.has(idx)) candidates.delete(idx);\n }\n }\n }\n\n // Evaluate full conditions on narrowed candidates (definition order)\n if (candidates && candidates.size > 0) {\n const sorted = [...candidates].sort((a, b) => a - b);\n for (const idx of sorted) {\n if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {\n matchedStep = flow.steps[idx];\n break;\n }\n }\n }\n } else if (indexableStepIndices.length > 0) {\n // Context has no fields that match any index -- evaluate all\n // indexable steps that are unconstrained on everything\n // (i.e., steps with is conditions on fields not present in context).\n // evaluateMatch handles this correctly.\n for (const idx of indexableStepIndices) {\n if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {\n matchedStep = flow.steps[idx];\n break;\n }\n }\n }\n\n // Phase 2: If no indexed match, evaluate broad steps\n if (!matchedStep && hasBroadSteps) {\n for (const idx of broadStepIndices) {\n if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {\n matchedStep = flow.steps[idx];\n break;\n }\n }\n }\n\n if (!matchedStep) {\n onMissingStep?.(ctx);\n return null;\n }\n\n // Evaluate the step based on its kind\n let result: StepDecision | null;\n\n if (matchedStep.kind === \"logic\") {\n result = matchedStep.statement.decision;\n } else {\n result = await matchedStep.handler(ctx);\n }\n\n if (!result) return null;\n\n if (devMode && result.nextTool === \"\") {\n warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n const decision: Decision = { ...result };\n\n const selections = selectUiAffordances<S>(decision, registryIndex, {\n devMode,\n onWarn: warn,\n });\n\n return {\n advice: generateAdvice(result.nextTool),\n decision,\n selections,\n };\n },\n };\n}\n","import type {\n AffordanceRegistry,\n BindingInput,\n DefaultSlots,\n HandleRegistration,\n} from \"./types\";\nimport { normalizeBinding } from \"./types\";\n\n/**\n * Mapped type that creates a registration method for each slot in S.\n * This enables fully typed custom slots via generics.\n */\nexport type AffordanceRegistrar<S extends string = DefaultSlots> = {\n [K in S]: (handleId: string, bindsTo: BindingInput) => void;\n};\n\n/**\n * Define UI affordances for a widget using a builder pattern.\n *\n * @example Default slots (backwards compatible)\n * ```ts\n * const affordances = defineAffordances((r) => {\n * r.primaryCta(\"cta.recommend\", { toolName: \"get_recommendations\" });\n * r.widgetVariant(\"variant.discovery\", { toolName: \"get_recommendations\" });\n * });\n * ```\n *\n * @example Custom slots (fully type-safe)\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\" | \"headerStyle\";\n * const affordances = defineAffordances<MySlots>((r) => {\n * r.primaryCta(\"cta.create\", { toolName: \"createUser\" });\n * r.contentArea(\"content.form\", { key: \"contentArea\", value: \"email-form\" });\n * r.headerStyle(\"header.progress\", { key: \"headerStyle\", value: \"step-1\" });\n * });\n * ```\n */\nexport function defineAffordances<S extends string = DefaultSlots>(\n builder: (r: AffordanceRegistrar<S>) => void\n): AffordanceRegistry<S> {\n const handles: HandleRegistration<S>[] = [];\n\n // Proxy creates methods on-the-fly for any slot name.\n // TypeScript ensures only valid slot names (from S) are called.\n const registrar = new Proxy({} as AffordanceRegistrar<S>, {\n get(_, slot: string) {\n return (handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot: slot as S,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n },\n });\n\n builder(registrar);\n return { handles };\n}\n","import type { AffordanceRegistry, DefaultSlots } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Merge multiple affordance registries into one.\n * Generic over slot type S for custom slot support.\n *\n * @example Default slots\n * ```ts\n * const merged = mergeAffordances([widgetA, widgetB]);\n * ```\n *\n * @example Custom slots\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\";\n * const merged = mergeAffordances<MySlots>([widgetA, widgetB]);\n * ```\n */\nexport function mergeAffordances<S extends string = DefaultSlots>(\n registries: AffordanceRegistry<S>[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry<S> = { handles: registries.flatMap((r) => r.handles) };\n\n if (!devMode) return merged;\n\n // Check for duplicate handleIds\n const seenHandleIds = new Set<string>();\n for (const h of merged.handles) {\n if (seenHandleIds.has(h.handleId)) {\n throw new Error(`[Taias] Duplicate handleId \"${h.handleId}\"`);\n }\n seenHandleIds.add(h.handleId);\n }\n\n // Check for ambiguous bindings (same slot + key + value)\n const seenTriples = new Set<string>();\n for (const h of merged.handles) {\n const k = makeBindingKey(h.slot, h.bindsTo);\n if (seenTriples.has(k)) {\n throw new Error(\n `[Taias] Ambiguous affordance: slot \"${h.slot}\" has multiple handles bound to (${h.bindsTo.key}=\"${h.bindsTo.value}\")`\n );\n }\n seenTriples.add(k);\n }\n\n warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);\n return merged;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiCO,SAAS,WACd,QACA,SACgB;AAChB,QAAM,QAAoB,CAAC;AAE3B,QAAM,cAA2B;AAAA,IAC/B,KAAK,OAAuB,OAAwB;AAClD,UAAI,OAAO,UAAU,YAAY;AAC/B,cAAM,KAAK,EAAE,MAAM,WAAW,OAAO,SAAS,MAAM,CAAC;AAAA,MACvD,OAAO;AACL,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,WAAW;AAAA,YACT;AAAA,YACA,UAAU;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,WAAW;AAEnB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ;AAAA,EACF;AACF;;;ACRO,SAAS,iBAAiB,OAA8B;AAC7D,MAAI,cAAc,MAAO,QAAO,EAAE,KAAK,YAAY,OAAO,MAAM,SAAS;AACzE,SAAO,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,MAAM;AAC9C;AAMO,SAAS,eAAe,MAAc,SAA0B;AACrE,SAAO,GAAG,IAAI,KAAK,QAAQ,GAAG,KAAK,QAAQ,KAAK;AAClD;;;AC3CO,SAAS,mBACd,UACkB;AAClB,QAAM,eAAe,oBAAI,IAAmC;AAC5D,QAAM,QAAQ,oBAAI,IAAO;AACzB,QAAM,aAAa,oBAAI,IAAe;AAEtC,MAAI,CAAC,SAAU,QAAO,EAAE,cAAc,OAAO,WAAW;AAExD,aAAW,KAAK,SAAS,SAAS;AAChC,UAAM,IAAI,EAAE,IAAI;AAGhB,UAAM,cAAc,WAAW,IAAI,EAAE,IAAI;AACzC,QAAI,eAAe,gBAAgB,EAAE,QAAQ,KAAK;AAChD,YAAM,IAAI;AAAA,QACR,iBAAiB,EAAE,IAAI,2CAA2C,WAAW,UAAU,EAAE,QAAQ,GAAG;AAAA,MAEtG;AAAA,IACF;AACA,eAAW,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAG;AAEpC,iBAAa,IAAI,eAAe,EAAE,MAAM,EAAE,OAAO,GAAG,CAAC;AAAA,EACvD;AAEA,SAAO,EAAE,cAAc,OAAO,WAAW;AAC3C;;;ACjCO,SAAS,oBACd,UACA,OACA,OAAsB,CAAC,GACN;AACjB,QAAM,UAAU,CAAC,CAAC,KAAK;AACvB,QAAM,OAAO,KAAK,WAAW,MAAM;AAAA,EAAC;AAEpC,QAAM,aAA8B,CAAC;AAErC,aAAW,QAAQ,MAAM,OAAO;AAE9B,UAAM,QAAQ,MAAM,WAAW,IAAI,IAAI;AACvC,QAAI,CAAC,MAAO;AAEZ,UAAM,QAAQ,SAAS,KAAK;AAC5B,QAAI,CAAC,MAAO;AAEZ,UAAM,IAAI,eAAe,MAAM,EAAE,KAAK,OAAO,MAAM,CAAC;AACpD,UAAM,SAAS,MAAM,aAAa,IAAI,CAAC;AAEvC,QAAI,CAAC,QAAQ;AACX,UAAI,QAAS,MAAK,mCAAmC,IAAI,UAAU,KAAK,KAAK,KAAK,GAAG;AACrF;AAAA,IACF;AAEA,IAAC,WAAuC,IAAI,IAAI;AAAA,MAC9C,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AACT;;;ACvCA,SAAS,eAAe,UAA0B;AAChD,SAAO,0DAA0D,QAAQ;AAC3E;AASA,SAAS,kBAAkB,WAAsB,OAAyB;AACxE,MAAI,QAAQ,UAAW,QAAO,UAAU,UAAU;AAClD,MAAI,WAAW,UAAW,QAAO,UAAU,UAAU;AACrD,SAAO;AACT;AAaA,SAAS,cAAc,OAAuB,KAA4B;AACxE,MAAI,MAAM,YAAY,CAAC,kBAAkB,MAAM,UAAU,IAAI,QAAQ,EAAG,QAAO;AAE/E,MAAI,MAAM,QAAQ;AAChB,QAAI,CAAC,IAAI,OAAQ,QAAO;AACxB,eAAW,CAAC,KAAK,SAAS,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AAC3D,UAAI,CAAC,kBAAkB,WAAW,IAAI,OAAO,GAAG,CAAC,EAAG,QAAO;AAAA,IAC7D;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ;AAChB,QAAI,CAAC,IAAI,OAAQ,QAAO;AACxB,eAAW,CAAC,KAAK,SAAS,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AAC3D,UAAI,CAAC,kBAAkB,WAAW,IAAI,OAAO,GAAG,CAAC,EAAG,QAAO;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO;AACT;AAcA,SAAS,oBAAoB,OAAgE;AAC3F,QAAM,aAAsD,CAAC;AAC7D,MAAI,MAAM,YAAY,QAAQ,MAAM,UAAU;AAC5C,eAAW,KAAK,EAAE,MAAM,YAAY,OAAO,MAAM,SAAS,GAAG,CAAC;AAAA,EAChE;AACA,MAAI,MAAM,QAAQ;AAChB,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AACtD,UAAI,QAAQ,KAAM,YAAW,KAAK,EAAE,MAAM,UAAU,GAAG,IAAI,OAAO,KAAK,GAAG,CAAC;AAAA,IAC7E;AAAA,EACF;AACA,MAAI,MAAM,QAAQ;AAChB,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AACtD,UAAI,QAAQ,KAAM,YAAW,KAAK,EAAE,MAAM,UAAU,GAAG,IAAI,OAAO,KAAK,GAAG,CAAC;AAAA,IAC7E;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,oBAAoB,OAAuB,MAAuB;AACzE,MAAI,SAAS,WAAY,QAAO,CAAC,CAAC,MAAM;AACxC,MAAI,KAAK,WAAW,SAAS,EAAG,QAAO,CAAC,CAAC,MAAM,SAAS,KAAK,MAAM,CAAC,CAAC;AACrE,MAAI,KAAK,WAAW,SAAS,EAAG,QAAO,CAAC,CAAC,MAAM,SAAS,KAAK,MAAM,CAAC,CAAC;AACrE,SAAO;AACT;AAKA,SAAS,gBAAgB,KAAmB,MAAuB;AACjE,MAAI,SAAS,WAAY,QAAO,IAAI;AACpC,MAAI,KAAK,WAAW,SAAS,EAAG,QAAO,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC;AACjE,MAAI,KAAK,WAAW,SAAS,EAAG,QAAO,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC;AACjE,SAAO;AACT;AAYA,SAAS,SAAS,MAAgC;AAChD,SAAO,KAAK,SAAS,UAAU,KAAK,UAAU,QAAQ,KAAK;AAC7D;AAKA,SAAS,eAAe,OAA+B;AACrD,SAAO,KAAK,UAAU,KAAK;AAC7B;AA6BO,SAAS,YACd,SACU;AACV,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,OAAO,WAAW,CAAC,QAAgB,QAAQ,KAAK,GAAG;AAGzD,MAAI,SAAS;AACX,UAAM,WAAW,oBAAI,IAAY;AACjC,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,MAAM,eAAe,SAAS,IAAI,CAAC;AACzC,UAAI,SAAS,IAAI,GAAG,GAAG;AACrB,cAAM,IAAI;AAAA,UACR,6CAA6C,KAAK,EAAE,+DAA+D,GAAG;AAAA,QACxH;AAAA,MACF;AACA,eAAS,IAAI,GAAG;AAAA,IAClB;AAAA,EACF;AAMA,QAAM,eAAe,oBAAI,IAAwB;AACjD,QAAM,uBAAiC,CAAC;AACxC,QAAM,mBAA6B,CAAC;AAEpC,WAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,UAAM,QAAQ,SAAS,KAAK,MAAM,CAAC,CAAC;AACpC,UAAM,eAAe,oBAAoB,KAAK;AAE9C,QAAI,aAAa,WAAW,GAAG;AAC7B,uBAAiB,KAAK,CAAC;AACvB;AAAA,IACF;AAEA,yBAAqB,KAAK,CAAC;AAE3B,eAAW,EAAE,MAAM,MAAM,KAAK,cAAc;AAC1C,UAAI,aAAa,aAAa,IAAI,IAAI;AACtC,UAAI,CAAC,YAAY;AACf,qBAAa,EAAE,UAAU,oBAAI,IAAI,GAAG,eAAe,CAAC,EAAE;AACtD,qBAAa,IAAI,MAAM,UAAU;AAAA,MACnC;AACA,UAAI,WAAW,WAAW,SAAS,IAAI,KAAK;AAC5C,UAAI,CAAC,UAAU;AACb,mBAAW,CAAC;AACZ,mBAAW,SAAS,IAAI,OAAO,QAAQ;AAAA,MACzC;AACA,eAAS,KAAK,CAAC;AAAA,IACjB;AAAA,EACF;AAIA,aAAW,CAAC,WAAW,UAAU,KAAK,cAAc;AAClD,eAAW,KAAK,sBAAsB;AACpC,UAAI,CAAC,oBAAoB,SAAS,KAAK,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG;AAC5D,mBAAW,cAAc,KAAK,CAAC;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,iBAAiB,SAAS;AAGhD,QAAM,gBAAgB,mBAAsB,WAAW;AAEvD,SAAO;AAAA,IACL,MAAM,QAAQ,KAAmD;AAC/D,UAAI;AAGJ,YAAM,uBAAiC,CAAC;AACxC,iBAAW,aAAa,aAAa,KAAK,GAAG;AAC3C,cAAM,WAAW,gBAAgB,KAAK,SAAS;AAC/C,YAAI,aAAa,QAAW;AAC1B,+BAAqB,KAAK,SAAS;AAAA,QACrC;AAAA,MACF;AAEA,UAAI,qBAAqB,SAAS,GAAG;AAEnC,YAAI,aAAiC;AAErC,mBAAW,aAAa,sBAAsB;AAC5C,gBAAM,aAAa,aAAa,IAAI,SAAS;AAC7C,gBAAM,WAAW,gBAAgB,KAAK,SAAS;AAE/C,gBAAM,kBAAkB,oBAAI,IAAY;AAGxC,gBAAM,UAAU,WAAW,SAAS,IAAI,QAAQ;AAChD,cAAI,SAAS;AACX,uBAAW,OAAO,QAAS,iBAAgB,IAAI,GAAG;AAAA,UACpD;AAGA,qBAAW,OAAO,WAAW,eAAe;AAC1C,4BAAgB,IAAI,GAAG;AAAA,UACzB;AAEA,cAAI,eAAe,MAAM;AACvB,yBAAa;AAAA,UACf,OAAO;AAEL,uBAAW,OAAO,YAAY;AAC5B,kBAAI,CAAC,gBAAgB,IAAI,GAAG,EAAG,YAAW,OAAO,GAAG;AAAA,YACtD;AAAA,UACF;AAAA,QACF;AAGA,YAAI,cAAc,WAAW,OAAO,GAAG;AACrC,gBAAM,SAAS,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACnD,qBAAW,OAAO,QAAQ;AACxB,gBAAI,cAAc,SAAS,KAAK,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG;AACjD,4BAAc,KAAK,MAAM,GAAG;AAC5B;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,WAAW,qBAAqB,SAAS,GAAG;AAK1C,mBAAW,OAAO,sBAAsB;AACtC,cAAI,cAAc,SAAS,KAAK,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG;AACjD,0BAAc,KAAK,MAAM,GAAG;AAC5B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,CAAC,eAAe,eAAe;AACjC,mBAAW,OAAO,kBAAkB;AAClC,cAAI,cAAc,SAAS,KAAK,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG;AACjD,0BAAc,KAAK,MAAM,GAAG;AAC5B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,UAAI,CAAC,aAAa;AAChB,wBAAgB,GAAG;AACnB,eAAO;AAAA,MACT;AAGA,UAAI;AAEJ,UAAI,YAAY,SAAS,SAAS;AAChC,iBAAS,YAAY,UAAU;AAAA,MACjC,OAAO;AACL,iBAAS,MAAM,YAAY,QAAQ,GAAG;AAAA,MACxC;AAEA,UAAI,CAAC,OAAQ,QAAO;AAEpB,UAAI,WAAW,OAAO,aAAa,IAAI;AACrC,aAAK,6BAA6B,IAAI,QAAQ,aAAa;AAAA,MAC7D;AAEA,YAAM,WAAqB,EAAE,GAAG,OAAO;AAEvC,YAAM,aAAa,oBAAuB,UAAU,eAAe;AAAA,QACjE;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAED,aAAO;AAAA,QACL,QAAQ,eAAe,OAAO,QAAQ;AAAA,QACtC;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AChTO,SAAS,kBACd,SACuB;AACvB,QAAM,UAAmC,CAAC;AAI1C,QAAM,YAAY,IAAI,MAAM,CAAC,GAA6B;AAAA,IACxD,IAAI,GAAG,MAAc;AACnB,aAAO,CAAC,UAAkB,YAA0B;AAClD,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA;AAAA,UACA,SAAS,iBAAiB,OAAO;AAAA,QACnC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ,SAAS;AACjB,SAAO,EAAE,QAAQ;AACnB;;;ACnCO,SAAS,iBACd,YACA,OAAgC,CAAC,GACV;AACvB,QAAM,UAAU,CAAC,CAAC,KAAK;AACvB,QAAM,OAAO,KAAK,WAAW,MAAM;AAAA,EAAC;AAEpC,QAAM,SAAgC,EAAE,SAAS,WAAW,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE;AAEtF,MAAI,CAAC,QAAS,QAAO;AAGrB,QAAM,gBAAgB,oBAAI,IAAY;AACtC,aAAW,KAAK,OAAO,SAAS;AAC9B,QAAI,cAAc,IAAI,EAAE,QAAQ,GAAG;AACjC,YAAM,IAAI,MAAM,+BAA+B,EAAE,QAAQ,GAAG;AAAA,IAC9D;AACA,kBAAc,IAAI,EAAE,QAAQ;AAAA,EAC9B;AAGA,QAAM,cAAc,oBAAI,IAAY;AACpC,aAAW,KAAK,OAAO,SAAS;AAC9B,UAAM,IAAI,eAAe,EAAE,MAAM,EAAE,OAAO;AAC1C,QAAI,YAAY,IAAI,CAAC,GAAG;AACtB,YAAM,IAAI;AAAA,QACR,uCAAuC,EAAE,IAAI,oCAAoC,EAAE,QAAQ,GAAG,KAAK,EAAE,QAAQ,KAAK;AAAA,MACpH;AAAA,IACF;AACA,gBAAY,IAAI,CAAC;AAAA,EACnB;AAEA,OAAK,kBAAkB,OAAO,QAAQ,MAAM,wBAAwB;AACpE,SAAO;AACT;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -95,34 +95,37 @@ type StepHandler = (ctx: TaiasContext) => StepDecision | null | Promise<StepDeci
|
|
|
95
95
|
/**
|
|
96
96
|
* A condition operator applied to a single field value.
|
|
97
97
|
*
|
|
98
|
-
* - { is:
|
|
99
|
-
* - { isNot:
|
|
98
|
+
* - { is: value } -- exact equality (field === value)
|
|
99
|
+
* - { isNot: value } -- not equal (field !== value)
|
|
100
|
+
*
|
|
101
|
+
* Condition accepts unknown values, enabling matching on strings, numbers,
|
|
102
|
+
* booleans, and any other value type. Comparison uses strict equality (===).
|
|
100
103
|
*
|
|
101
104
|
* The operator system is pure data (not wrapper functions), aligning with
|
|
102
105
|
* the logic-as-data philosophy. New operators (oneOf, contains, etc.) can
|
|
103
106
|
* be added as union members without changing the evaluation architecture.
|
|
104
107
|
*/
|
|
105
108
|
type Condition = {
|
|
106
|
-
is:
|
|
109
|
+
is: unknown;
|
|
107
110
|
} | {
|
|
108
|
-
isNot:
|
|
111
|
+
isNot: unknown;
|
|
109
112
|
};
|
|
110
|
-
/**
|
|
111
|
-
* A field condition is either:
|
|
112
|
-
* - A bare string: sugar for { is: string }
|
|
113
|
-
* - An explicit Condition object
|
|
114
|
-
*/
|
|
115
|
-
type FieldCondition = string | Condition;
|
|
116
113
|
/**
|
|
117
114
|
* Match condition for a logic statement.
|
|
118
115
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
116
|
+
* All fields are optional -- steps can match on any combination of
|
|
117
|
+
* toolName, params, and result. Each field uses explicit Condition
|
|
118
|
+
* operators ({ is: ... } or { isNot: ... }).
|
|
121
119
|
*
|
|
122
|
-
*
|
|
120
|
+
* For params and result, conditions are specified per-key. Only the
|
|
121
|
+
* specified keys are checked (subset matching); unspecified keys are
|
|
122
|
+
* ignored. If a step specifies a params/result condition but the
|
|
123
|
+
* context doesn't include params/result, the step does not match.
|
|
123
124
|
*/
|
|
124
125
|
type MatchCondition = {
|
|
125
|
-
toolName
|
|
126
|
+
toolName?: Condition;
|
|
127
|
+
params?: Record<string, Condition>;
|
|
128
|
+
result?: Record<string, Condition>;
|
|
126
129
|
};
|
|
127
130
|
/**
|
|
128
131
|
* A declarative logic statement -- the core primitive of the decision engine.
|
|
@@ -171,12 +174,12 @@ type StepInput = StepHandler | StepDecision;
|
|
|
171
174
|
*
|
|
172
175
|
* step() takes two arguments:
|
|
173
176
|
* - match: a MatchCondition object describing the conditions under which
|
|
174
|
-
* this step applies.
|
|
177
|
+
* this step applies. All fields use explicit operator objects.
|
|
175
178
|
* - input: a StepDecision object (creates a logic statement).
|
|
176
179
|
* A StepHandler function is also accepted for backwards compatibility.
|
|
177
180
|
*/
|
|
178
181
|
interface FlowBuilder {
|
|
179
|
-
step(match:
|
|
182
|
+
step(match: MatchCondition, input: StepInput): void;
|
|
180
183
|
}
|
|
181
184
|
/**
|
|
182
185
|
* Options for creating a Taias instance.
|
|
@@ -204,23 +207,28 @@ interface Taias<S extends string = DefaultSlots> {
|
|
|
204
207
|
* @param builder - Callback that receives a FlowBuilder to define steps
|
|
205
208
|
* @returns A FlowDefinition object
|
|
206
209
|
*
|
|
207
|
-
* @example Logic statement
|
|
210
|
+
* @example Logic statement matching on toolName
|
|
208
211
|
* ```ts
|
|
209
212
|
* const onboardRepoFlow = defineFlow("onboard_repo", (flow) => {
|
|
210
213
|
* flow.step({ toolName: { is: "scan_repo" } }, { nextTool: "configure_app" });
|
|
211
214
|
* });
|
|
212
215
|
* ```
|
|
213
216
|
*
|
|
214
|
-
* @example
|
|
217
|
+
* @example Matching on params and result
|
|
215
218
|
* ```ts
|
|
216
|
-
* flow.step(
|
|
219
|
+
* flow.step(
|
|
220
|
+
* { toolName: { is: "scan_repo" }, params: { language: { is: "python" } } },
|
|
221
|
+
* { nextTool: "configure_python" },
|
|
222
|
+
* );
|
|
223
|
+
* flow.step(
|
|
224
|
+
* { result: { hasConfig: { is: true } } },
|
|
225
|
+
* { nextTool: "review_config" },
|
|
226
|
+
* );
|
|
217
227
|
* ```
|
|
218
228
|
*
|
|
219
|
-
* @example
|
|
220
|
-
* Bare strings are sugar for { toolName: { is: string } }:
|
|
229
|
+
* @example isNot operator
|
|
221
230
|
* ```ts
|
|
222
|
-
* flow.step({ toolName:
|
|
223
|
-
* flow.step("scan_repo", { nextTool: "configure_app" }); // string sugar for { toolName: { is: "scan_repo" } }
|
|
231
|
+
* flow.step({ toolName: { isNot: "abort_session" } }, { nextTool: "continue_flow" });
|
|
224
232
|
* ```
|
|
225
233
|
*/
|
|
226
234
|
declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void): FlowDefinition;
|
|
@@ -243,25 +251,14 @@ declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void
|
|
|
243
251
|
*
|
|
244
252
|
* This file is the boundary where:
|
|
245
253
|
*
|
|
246
|
-
* Inputs
|
|
254
|
+
* Inputs -> Decision -> Manifestations
|
|
247
255
|
*
|
|
248
256
|
* are unified into a single resolve() call.
|
|
249
257
|
*
|
|
250
|
-
* @example
|
|
258
|
+
* @example
|
|
251
259
|
* ```ts
|
|
252
260
|
* const taias = createTaias({ flow, affordances });
|
|
253
261
|
* ```
|
|
254
|
-
*
|
|
255
|
-
* @example Custom slots (fully type-safe)
|
|
256
|
-
* ```ts
|
|
257
|
-
* type MySlots = "primaryCta" | "contentArea" | "headerStyle";
|
|
258
|
-
* const affordances = defineAffordances<MySlots>((r) => {
|
|
259
|
-
* r.primaryCta("cta", { key: "nextTool", value: "createUser" });
|
|
260
|
-
* r.contentArea("content", { key: "contentArea", value: "form" });
|
|
261
|
-
* r.headerStyle("header", { key: "headerStyle", value: "progress" });
|
|
262
|
-
* });
|
|
263
|
-
* const taias = createTaias<MySlots>({ flow, affordances });
|
|
264
|
-
* ```
|
|
265
262
|
*/
|
|
266
263
|
declare function createTaias<S extends string = DefaultSlots>(options: TaiasOptions<S>): Taias<S>;
|
|
267
264
|
|
|
@@ -316,4 +313,4 @@ type MergeAffordancesOptions = {
|
|
|
316
313
|
*/
|
|
317
314
|
declare function mergeAffordances<S extends string = DefaultSlots>(registries: AffordanceRegistry<S>[], opts?: MergeAffordancesOptions): AffordanceRegistry<S>;
|
|
318
315
|
|
|
319
|
-
export { type AffordanceRegistrar, type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Condition, type Decision, type DefaultSlots, type
|
|
316
|
+
export { type AffordanceRegistrar, type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Condition, type Decision, type DefaultSlots, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type LogicStatement, type MatchCondition, type Selection, type StepDecision, type StepHandler, type StepInput, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
|
package/dist/index.d.ts
CHANGED
|
@@ -95,34 +95,37 @@ type StepHandler = (ctx: TaiasContext) => StepDecision | null | Promise<StepDeci
|
|
|
95
95
|
/**
|
|
96
96
|
* A condition operator applied to a single field value.
|
|
97
97
|
*
|
|
98
|
-
* - { is:
|
|
99
|
-
* - { isNot:
|
|
98
|
+
* - { is: value } -- exact equality (field === value)
|
|
99
|
+
* - { isNot: value } -- not equal (field !== value)
|
|
100
|
+
*
|
|
101
|
+
* Condition accepts unknown values, enabling matching on strings, numbers,
|
|
102
|
+
* booleans, and any other value type. Comparison uses strict equality (===).
|
|
100
103
|
*
|
|
101
104
|
* The operator system is pure data (not wrapper functions), aligning with
|
|
102
105
|
* the logic-as-data philosophy. New operators (oneOf, contains, etc.) can
|
|
103
106
|
* be added as union members without changing the evaluation architecture.
|
|
104
107
|
*/
|
|
105
108
|
type Condition = {
|
|
106
|
-
is:
|
|
109
|
+
is: unknown;
|
|
107
110
|
} | {
|
|
108
|
-
isNot:
|
|
111
|
+
isNot: unknown;
|
|
109
112
|
};
|
|
110
|
-
/**
|
|
111
|
-
* A field condition is either:
|
|
112
|
-
* - A bare string: sugar for { is: string }
|
|
113
|
-
* - An explicit Condition object
|
|
114
|
-
*/
|
|
115
|
-
type FieldCondition = string | Condition;
|
|
116
113
|
/**
|
|
117
114
|
* Match condition for a logic statement.
|
|
118
115
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
116
|
+
* All fields are optional -- steps can match on any combination of
|
|
117
|
+
* toolName, params, and result. Each field uses explicit Condition
|
|
118
|
+
* operators ({ is: ... } or { isNot: ... }).
|
|
121
119
|
*
|
|
122
|
-
*
|
|
120
|
+
* For params and result, conditions are specified per-key. Only the
|
|
121
|
+
* specified keys are checked (subset matching); unspecified keys are
|
|
122
|
+
* ignored. If a step specifies a params/result condition but the
|
|
123
|
+
* context doesn't include params/result, the step does not match.
|
|
123
124
|
*/
|
|
124
125
|
type MatchCondition = {
|
|
125
|
-
toolName
|
|
126
|
+
toolName?: Condition;
|
|
127
|
+
params?: Record<string, Condition>;
|
|
128
|
+
result?: Record<string, Condition>;
|
|
126
129
|
};
|
|
127
130
|
/**
|
|
128
131
|
* A declarative logic statement -- the core primitive of the decision engine.
|
|
@@ -171,12 +174,12 @@ type StepInput = StepHandler | StepDecision;
|
|
|
171
174
|
*
|
|
172
175
|
* step() takes two arguments:
|
|
173
176
|
* - match: a MatchCondition object describing the conditions under which
|
|
174
|
-
* this step applies.
|
|
177
|
+
* this step applies. All fields use explicit operator objects.
|
|
175
178
|
* - input: a StepDecision object (creates a logic statement).
|
|
176
179
|
* A StepHandler function is also accepted for backwards compatibility.
|
|
177
180
|
*/
|
|
178
181
|
interface FlowBuilder {
|
|
179
|
-
step(match:
|
|
182
|
+
step(match: MatchCondition, input: StepInput): void;
|
|
180
183
|
}
|
|
181
184
|
/**
|
|
182
185
|
* Options for creating a Taias instance.
|
|
@@ -204,23 +207,28 @@ interface Taias<S extends string = DefaultSlots> {
|
|
|
204
207
|
* @param builder - Callback that receives a FlowBuilder to define steps
|
|
205
208
|
* @returns A FlowDefinition object
|
|
206
209
|
*
|
|
207
|
-
* @example Logic statement
|
|
210
|
+
* @example Logic statement matching on toolName
|
|
208
211
|
* ```ts
|
|
209
212
|
* const onboardRepoFlow = defineFlow("onboard_repo", (flow) => {
|
|
210
213
|
* flow.step({ toolName: { is: "scan_repo" } }, { nextTool: "configure_app" });
|
|
211
214
|
* });
|
|
212
215
|
* ```
|
|
213
216
|
*
|
|
214
|
-
* @example
|
|
217
|
+
* @example Matching on params and result
|
|
215
218
|
* ```ts
|
|
216
|
-
* flow.step(
|
|
219
|
+
* flow.step(
|
|
220
|
+
* { toolName: { is: "scan_repo" }, params: { language: { is: "python" } } },
|
|
221
|
+
* { nextTool: "configure_python" },
|
|
222
|
+
* );
|
|
223
|
+
* flow.step(
|
|
224
|
+
* { result: { hasConfig: { is: true } } },
|
|
225
|
+
* { nextTool: "review_config" },
|
|
226
|
+
* );
|
|
217
227
|
* ```
|
|
218
228
|
*
|
|
219
|
-
* @example
|
|
220
|
-
* Bare strings are sugar for { toolName: { is: string } }:
|
|
229
|
+
* @example isNot operator
|
|
221
230
|
* ```ts
|
|
222
|
-
* flow.step({ toolName:
|
|
223
|
-
* flow.step("scan_repo", { nextTool: "configure_app" }); // string sugar for { toolName: { is: "scan_repo" } }
|
|
231
|
+
* flow.step({ toolName: { isNot: "abort_session" } }, { nextTool: "continue_flow" });
|
|
224
232
|
* ```
|
|
225
233
|
*/
|
|
226
234
|
declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void): FlowDefinition;
|
|
@@ -243,25 +251,14 @@ declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void
|
|
|
243
251
|
*
|
|
244
252
|
* This file is the boundary where:
|
|
245
253
|
*
|
|
246
|
-
* Inputs
|
|
254
|
+
* Inputs -> Decision -> Manifestations
|
|
247
255
|
*
|
|
248
256
|
* are unified into a single resolve() call.
|
|
249
257
|
*
|
|
250
|
-
* @example
|
|
258
|
+
* @example
|
|
251
259
|
* ```ts
|
|
252
260
|
* const taias = createTaias({ flow, affordances });
|
|
253
261
|
* ```
|
|
254
|
-
*
|
|
255
|
-
* @example Custom slots (fully type-safe)
|
|
256
|
-
* ```ts
|
|
257
|
-
* type MySlots = "primaryCta" | "contentArea" | "headerStyle";
|
|
258
|
-
* const affordances = defineAffordances<MySlots>((r) => {
|
|
259
|
-
* r.primaryCta("cta", { key: "nextTool", value: "createUser" });
|
|
260
|
-
* r.contentArea("content", { key: "contentArea", value: "form" });
|
|
261
|
-
* r.headerStyle("header", { key: "headerStyle", value: "progress" });
|
|
262
|
-
* });
|
|
263
|
-
* const taias = createTaias<MySlots>({ flow, affordances });
|
|
264
|
-
* ```
|
|
265
262
|
*/
|
|
266
263
|
declare function createTaias<S extends string = DefaultSlots>(options: TaiasOptions<S>): Taias<S>;
|
|
267
264
|
|
|
@@ -316,4 +313,4 @@ type MergeAffordancesOptions = {
|
|
|
316
313
|
*/
|
|
317
314
|
declare function mergeAffordances<S extends string = DefaultSlots>(registries: AffordanceRegistry<S>[], opts?: MergeAffordancesOptions): AffordanceRegistry<S>;
|
|
318
315
|
|
|
319
|
-
export { type AffordanceRegistrar, type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Condition, type Decision, type DefaultSlots, type
|
|
316
|
+
export { type AffordanceRegistrar, type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Condition, type Decision, type DefaultSlots, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type LogicStatement, type MatchCondition, type Selection, type StepDecision, type StepHandler, type StepInput, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
|
package/dist/index.js
CHANGED
|
@@ -3,14 +3,13 @@ function defineFlow(flowId, builder) {
|
|
|
3
3
|
const steps = [];
|
|
4
4
|
const flowBuilder = {
|
|
5
5
|
step(match, input) {
|
|
6
|
-
const condition = typeof match === "string" ? { toolName: match } : match;
|
|
7
6
|
if (typeof input === "function") {
|
|
8
|
-
steps.push({ kind: "handler", match
|
|
7
|
+
steps.push({ kind: "handler", match, handler: input });
|
|
9
8
|
} else {
|
|
10
9
|
steps.push({
|
|
11
10
|
kind: "logic",
|
|
12
11
|
statement: {
|
|
13
|
-
match
|
|
12
|
+
match,
|
|
14
13
|
decision: input
|
|
15
14
|
}
|
|
16
15
|
});
|
|
@@ -82,33 +81,61 @@ function selectUiAffordances(decision, index, opts = {}) {
|
|
|
82
81
|
function generateAdvice(nextTool) {
|
|
83
82
|
return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;
|
|
84
83
|
}
|
|
85
|
-
function normalizeFieldCondition(field) {
|
|
86
|
-
return typeof field === "string" ? { is: field } : field;
|
|
87
|
-
}
|
|
88
84
|
function evaluateCondition(condition, value) {
|
|
89
85
|
if ("is" in condition) return value === condition.is;
|
|
90
86
|
if ("isNot" in condition) return value !== condition.isNot;
|
|
91
87
|
return false;
|
|
92
88
|
}
|
|
93
89
|
function evaluateMatch(match, ctx) {
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
if (match.toolName && !evaluateCondition(match.toolName, ctx.toolName)) return false;
|
|
91
|
+
if (match.params) {
|
|
92
|
+
if (!ctx.params) return false;
|
|
93
|
+
for (const [key, condition] of Object.entries(match.params)) {
|
|
94
|
+
if (!evaluateCondition(condition, ctx.params[key])) return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (match.result) {
|
|
98
|
+
if (!ctx.result) return false;
|
|
99
|
+
for (const [key, condition] of Object.entries(match.result)) {
|
|
100
|
+
if (!evaluateCondition(condition, ctx.result[key])) return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
function extractIsConditions(match) {
|
|
106
|
+
const conditions = [];
|
|
107
|
+
if (match.toolName && "is" in match.toolName) {
|
|
108
|
+
conditions.push({ path: "toolName", value: match.toolName.is });
|
|
109
|
+
}
|
|
110
|
+
if (match.params) {
|
|
111
|
+
for (const [key, cond] of Object.entries(match.params)) {
|
|
112
|
+
if ("is" in cond) conditions.push({ path: `params.${key}`, value: cond.is });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (match.result) {
|
|
116
|
+
for (const [key, cond] of Object.entries(match.result)) {
|
|
117
|
+
if ("is" in cond) conditions.push({ path: `result.${key}`, value: cond.is });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return conditions;
|
|
96
121
|
}
|
|
97
|
-
function
|
|
98
|
-
if (
|
|
99
|
-
|
|
122
|
+
function hasConditionOnField(match, path) {
|
|
123
|
+
if (path === "toolName") return !!match.toolName;
|
|
124
|
+
if (path.startsWith("params.")) return !!match.params?.[path.slice(7)];
|
|
125
|
+
if (path.startsWith("result.")) return !!match.result?.[path.slice(7)];
|
|
126
|
+
return false;
|
|
100
127
|
}
|
|
101
|
-
function
|
|
102
|
-
if (
|
|
103
|
-
if ("
|
|
104
|
-
|
|
128
|
+
function getContextValue(ctx, path) {
|
|
129
|
+
if (path === "toolName") return ctx.toolName;
|
|
130
|
+
if (path.startsWith("params.")) return ctx.params?.[path.slice(7)];
|
|
131
|
+
if (path.startsWith("result.")) return ctx.result?.[path.slice(7)];
|
|
132
|
+
return void 0;
|
|
105
133
|
}
|
|
106
134
|
function getMatch(step) {
|
|
107
135
|
return step.kind === "logic" ? step.statement.match : step.match;
|
|
108
136
|
}
|
|
109
137
|
function serializeMatch(match) {
|
|
110
|
-
|
|
111
|
-
return JSON.stringify({ toolName: normalized });
|
|
138
|
+
return JSON.stringify(match);
|
|
112
139
|
}
|
|
113
140
|
function createTaias(options) {
|
|
114
141
|
const {
|
|
@@ -124,50 +151,112 @@ function createTaias(options) {
|
|
|
124
151
|
for (const step of flow.steps) {
|
|
125
152
|
const key = serializeMatch(getMatch(step));
|
|
126
153
|
if (seenKeys.has(key)) {
|
|
127
|
-
const match = getMatch(step);
|
|
128
|
-
const normalized = normalizeFieldCondition(match.toolName);
|
|
129
|
-
const label = "is" in normalized ? normalized.is : `isNot:${normalized.isNot}`;
|
|
130
154
|
throw new Error(
|
|
131
|
-
`Taias: Duplicate match condition
|
|
155
|
+
`Taias: Duplicate match condition in flow '${flow.id}'. Each step must have a unique match condition. Duplicate: ${key}`
|
|
132
156
|
);
|
|
133
157
|
}
|
|
134
158
|
seenKeys.add(key);
|
|
135
159
|
}
|
|
136
160
|
}
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
161
|
+
const fieldIndexes = /* @__PURE__ */ new Map();
|
|
162
|
+
const indexableStepIndices = [];
|
|
163
|
+
const broadStepIndices = [];
|
|
164
|
+
for (let i = 0; i < flow.steps.length; i++) {
|
|
165
|
+
const match = getMatch(flow.steps[i]);
|
|
166
|
+
const isConditions = extractIsConditions(match);
|
|
167
|
+
if (isConditions.length === 0) {
|
|
168
|
+
broadStepIndices.push(i);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
indexableStepIndices.push(i);
|
|
172
|
+
for (const { path, value } of isConditions) {
|
|
173
|
+
let fieldIndex = fieldIndexes.get(path);
|
|
174
|
+
if (!fieldIndex) {
|
|
175
|
+
fieldIndex = { valueMap: /* @__PURE__ */ new Map(), unconstrained: [] };
|
|
176
|
+
fieldIndexes.set(path, fieldIndex);
|
|
177
|
+
}
|
|
178
|
+
let stepList = fieldIndex.valueMap.get(value);
|
|
179
|
+
if (!stepList) {
|
|
180
|
+
stepList = [];
|
|
181
|
+
fieldIndex.valueMap.set(value, stepList);
|
|
182
|
+
}
|
|
183
|
+
stepList.push(i);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const [fieldPath, fieldIndex] of fieldIndexes) {
|
|
187
|
+
for (const i of indexableStepIndices) {
|
|
188
|
+
if (!hasConditionOnField(getMatch(flow.steps[i]), fieldPath)) {
|
|
189
|
+
fieldIndex.unconstrained.push(i);
|
|
190
|
+
}
|
|
145
191
|
}
|
|
146
192
|
}
|
|
147
|
-
const hasBroadSteps =
|
|
193
|
+
const hasBroadSteps = broadStepIndices.length > 0;
|
|
148
194
|
const registryIndex = buildRegistryIndex(affordances);
|
|
149
195
|
return {
|
|
150
196
|
async resolve(ctx) {
|
|
151
|
-
let
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
let matchedStep;
|
|
198
|
+
const applicableFieldPaths = [];
|
|
199
|
+
for (const fieldPath of fieldIndexes.keys()) {
|
|
200
|
+
const ctxValue = getContextValue(ctx, fieldPath);
|
|
201
|
+
if (ctxValue !== void 0) {
|
|
202
|
+
applicableFieldPaths.push(fieldPath);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (applicableFieldPaths.length > 0) {
|
|
206
|
+
let candidates = null;
|
|
207
|
+
for (const fieldPath of applicableFieldPaths) {
|
|
208
|
+
const fieldIndex = fieldIndexes.get(fieldPath);
|
|
209
|
+
const ctxValue = getContextValue(ctx, fieldPath);
|
|
210
|
+
const fieldCandidates = /* @__PURE__ */ new Set();
|
|
211
|
+
const indexed = fieldIndex.valueMap.get(ctxValue);
|
|
212
|
+
if (indexed) {
|
|
213
|
+
for (const idx of indexed) fieldCandidates.add(idx);
|
|
214
|
+
}
|
|
215
|
+
for (const idx of fieldIndex.unconstrained) {
|
|
216
|
+
fieldCandidates.add(idx);
|
|
217
|
+
}
|
|
218
|
+
if (candidates === null) {
|
|
219
|
+
candidates = fieldCandidates;
|
|
220
|
+
} else {
|
|
221
|
+
for (const idx of candidates) {
|
|
222
|
+
if (!fieldCandidates.has(idx)) candidates.delete(idx);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (candidates && candidates.size > 0) {
|
|
227
|
+
const sorted = [...candidates].sort((a, b) => a - b);
|
|
228
|
+
for (const idx of sorted) {
|
|
229
|
+
if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {
|
|
230
|
+
matchedStep = flow.steps[idx];
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
} else if (indexableStepIndices.length > 0) {
|
|
236
|
+
for (const idx of indexableStepIndices) {
|
|
237
|
+
if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {
|
|
238
|
+
matchedStep = flow.steps[idx];
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!matchedStep && hasBroadSteps) {
|
|
244
|
+
for (const idx of broadStepIndices) {
|
|
245
|
+
if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {
|
|
246
|
+
matchedStep = flow.steps[idx];
|
|
158
247
|
break;
|
|
159
248
|
}
|
|
160
249
|
}
|
|
161
250
|
}
|
|
162
|
-
if (!
|
|
251
|
+
if (!matchedStep) {
|
|
163
252
|
onMissingStep?.(ctx);
|
|
164
253
|
return null;
|
|
165
254
|
}
|
|
166
255
|
let result;
|
|
167
|
-
if (
|
|
168
|
-
result =
|
|
256
|
+
if (matchedStep.kind === "logic") {
|
|
257
|
+
result = matchedStep.statement.decision;
|
|
169
258
|
} else {
|
|
170
|
-
result = await
|
|
259
|
+
result = await matchedStep.handler(ctx);
|
|
171
260
|
}
|
|
172
261
|
if (!result) return null;
|
|
173
262
|
if (devMode && result.nextTool === "") {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/flow.ts","../src/uiAffordances/types.ts","../src/uiAffordances/indexing.ts","../src/uiAffordances/select.ts","../src/createTaias.ts","../src/uiAffordances/defineAffordances.ts","../src/uiAffordances/mergeAffordances.ts"],"sourcesContent":["import type { FlowBuilder, FlowDefinition, FlowStep, MatchCondition, StepInput } from \"./types\";\n\n/**\n * Define a flow with its steps.\n *\n * @param flowId - Unique identifier for the flow\n * @param builder - Callback that receives a FlowBuilder to define steps\n * @returns A FlowDefinition object\n *\n * @example Logic statement with explicit operator\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step({ toolName: { is: \"scan_repo\" } }, { nextTool: \"configure_app\" });\n * });\n * ```\n *\n * @example isNot operator\n * ```ts\n * flow.step({ toolName: { isNot: \"abort_session\" } }, { nextTool: \"continue_flow\" });\n * ```\n *\n * @example Sugar forms (backwards compatible)\n * Bare strings are sugar for { toolName: { is: string } }:\n * ```ts\n * flow.step({ toolName: \"scan_repo\" }, { nextTool: \"configure_app\" }); // sugar for { is: \"scan_repo\" }\n * flow.step(\"scan_repo\", { nextTool: \"configure_app\" }); // string sugar for { toolName: { is: \"scan_repo\" } }\n * ```\n */\nexport function defineFlow(\n flowId: string,\n builder: (flow: FlowBuilder) => void\n): FlowDefinition {\n const steps: FlowStep[] = [];\n\n const flowBuilder: FlowBuilder = {\n step(match: string | MatchCondition, input: StepInput): void {\n // Normalize: string is sugar for { toolName: string }\n const condition: MatchCondition =\n typeof match === \"string\" ? { toolName: match } : match;\n\n if (typeof input === \"function\") {\n // Handler function -- backwards-compatible escape hatch.\n // The match condition is stored alongside the handler since\n // the function itself has no formal match conditions.\n steps.push({ kind: \"handler\", match: condition, handler: input });\n } else {\n // Static logic statement -- the core primitive.\n // The statement is the sole source of truth for its match\n // conditions and decision.\n steps.push({\n kind: \"logic\",\n statement: {\n match: condition,\n decision: input,\n },\n });\n }\n },\n };\n\n builder(flowBuilder);\n\n return {\n id: flowId,\n steps,\n };\n}\n\n","/**\n * Default slots for backwards compatibility.\n * Users can define custom slots by passing a type parameter.\n */\nexport type DefaultSlots = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\n\n/**\n * Alias for backwards compatibility in exports.\n * @deprecated Use DefaultSlots or define your own slot type\n */\nexport type CanonicalSlot = DefaultSlots;\n\nexport type Binding = {\n key: string;\n value: string;\n};\n\n/**\n * Input format for registering affordance bindings.\n * - { toolName } is shorthand for { key: \"nextTool\", value: toolName }\n * - { key, value } is the generalized form for custom bindings\n */\nexport type BindingInput = { toolName: string } | { key: string; value: string };\n\n/**\n * A registered UI affordance handle.\n * Generic over slot type S for custom slot support.\n */\nexport type HandleRegistration<S extends string = DefaultSlots> = {\n slot: S;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\n/**\n * UI selections keyed by slot name.\n * Generic over slot type S for custom slot support.\n */\nexport type UiSelections<S extends string = DefaultSlots> = Partial<Record<S, Selection>>;\n\n/**\n * Collection of registered handles.\n * Generic over slot type S for custom slot support.\n */\nexport type AffordanceRegistry<S extends string = DefaultSlots> = {\n handles: HandleRegistration<S>[];\n};\n\nexport function normalizeBinding(input: BindingInput): Binding {\n if (\"toolName\" in input) return { key: \"nextTool\", value: input.toolName };\n return { key: input.key, value: input.value };\n}\n\n/**\n * Creates a binding key for indexing.\n * Accepts any string for slot to support custom slots.\n */\nexport function makeBindingKey(slot: string, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, DefaultSlots, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\n/**\n * Index structure for efficient affordance lookup.\n * Generic over slot type S for custom slot support.\n */\nexport type RegistryIndex<S extends string = DefaultSlots> = {\n byBindingKey: Map<string, HandleRegistration<S>>;\n slots: Set<S>;\n /** Inferred decision field for each slot, derived from handle bindings. */\n slotKeyMap: Map<S, string>;\n};\n\n/**\n * Build an index from a registry for efficient lookup during selection.\n * Tracks which slots have registered handles and infers the decision field\n * for each slot from its handle bindings.\n *\n * @throws Error if handles for the same slot bind to different keys\n */\nexport function buildRegistryIndex<S extends string = DefaultSlots>(\n registry?: AffordanceRegistry<S>\n): RegistryIndex<S> {\n const byBindingKey = new Map<string, HandleRegistration<S>>();\n const slots = new Set<S>();\n const slotKeyMap = new Map<S, string>();\n\n if (!registry) return { byBindingKey, slots, slotKeyMap };\n\n for (const h of registry.handles) {\n slots.add(h.slot);\n\n // Infer and validate slot key\n const existingKey = slotKeyMap.get(h.slot);\n if (existingKey && existingKey !== h.bindsTo.key) {\n throw new Error(\n `[Taias] Slot \"${h.slot}\" has handles bound to different keys: \"${existingKey}\" and \"${h.bindsTo.key}\". ` +\n `All handles for a slot must use the same decision field.`\n );\n }\n slotKeyMap.set(h.slot, h.bindsTo.key);\n\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey, slots, slotKeyMap };\n}\n","import type { Decision } from \"../types\";\nimport type { DefaultSlots, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nexport type SelectOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Select UI affordances based on flow decision.\n * Uses the inferred decision field for each slot from the registry index.\n */\nexport function selectUiAffordances<S extends string = DefaultSlots>(\n decision: Decision,\n index: RegistryIndex<S>,\n opts: SelectOptions = {}\n): UiSelections<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections<S> = {};\n\n for (const slot of index.slots) {\n // Use inferred key from handle bindings\n const field = index.slotKeyMap.get(slot);\n if (!field) continue;\n\n const value = decision[field];\n if (!value) continue;\n\n const k = makeBindingKey(slot, { key: field, value });\n const handle = index.byBindingKey.get(k);\n\n if (!handle) {\n if (devMode) warn(`[Taias] No affordance for slot \"${slot}\" when ${field}=\"${value}\"`);\n continue;\n }\n\n (selections as Record<string, unknown>)[slot] = {\n handleId: handle.handleId,\n bindsTo: handle.bindsTo,\n };\n }\n\n return selections;\n}\n","import type { Affordances, Condition, FieldCondition, FlowStep, MatchCondition, StepDecision, Taias, TaiasContext, TaiasOptions, Decision } from \"./types\";\nimport type { DefaultSlots } from \"./uiAffordances/types\";\nimport { buildRegistryIndex } from \"./uiAffordances/indexing\";\nimport { selectUiAffordances } from \"./uiAffordances/select\";\n\n/**\n * Generate advice text for a given next tool.\n */\nfunction generateAdvice(nextTool: string): string {\n return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;\n}\n\n// ---------------------------------------------------------------------------\n// Condition normalization and evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Normalize a FieldCondition to a canonical Condition object.\n * A bare string is sugar for { is: string }.\n */\nfunction normalizeFieldCondition(field: FieldCondition): Condition {\n return typeof field === \"string\" ? { is: field } : field;\n}\n\n/**\n * Evaluate a single Condition against a value.\n */\nfunction evaluateCondition(condition: Condition, value: string): boolean {\n if (\"is\" in condition) return value === condition.is;\n if (\"isNot\" in condition) return value !== condition.isNot;\n return false;\n}\n\n/**\n * Evaluate a full MatchCondition against a TaiasContext.\n * All conditions in the match must be satisfied.\n */\nfunction evaluateMatch(match: MatchCondition, ctx: TaiasContext): boolean {\n const toolCondition = normalizeFieldCondition(match.toolName);\n return evaluateCondition(toolCondition, ctx.toolName);\n}\n\n/**\n * Check whether a FieldCondition is indexable (i.e., uses the `is` operator).\n * Indexable conditions enable O(1) Map lookup at resolve time.\n */\nfunction isIndexable(field: FieldCondition): boolean {\n if (typeof field === \"string\") return true;\n return \"is\" in field;\n}\n\n/**\n * Extract the index key from an indexable FieldCondition.\n * Only call this when isIndexable() returns true.\n */\nfunction indexKey(field: FieldCondition): string {\n if (typeof field === \"string\") return field;\n if (\"is\" in field) return field.is;\n throw new Error(\"Cannot derive index key from non-indexable condition\");\n}\n\n// ---------------------------------------------------------------------------\n// Step access helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the match condition from a FlowStep.\n *\n * - Logic-based steps: match comes from statement.match (the statement is\n * the sole source of truth for its match conditions)\n * - Handler-based steps (backwards compatibility): match is stored directly on the step\n */\nfunction getMatch(step: FlowStep): MatchCondition {\n return step.kind === \"logic\" ? step.statement.match : step.match;\n}\n\n/**\n * Serialize a MatchCondition into a stable string for duplicate detection.\n * Normalizes sugar forms so that equivalent conditions produce the same key.\n */\nfunction serializeMatch(match: MatchCondition): string {\n const normalized = normalizeFieldCondition(match.toolName);\n return JSON.stringify({ toolName: normalized });\n}\n\n/**\n * createTaias constructs a decision engine.\n *\n * Taias resolves context into a generalized Decision object,\n * and then manifests that decision into concrete affordances:\n *\n * - LLM guidance (advice)\n * - UI affordance selections\n *\n * Flow logic is expressed as logic statements -- structured data that\n * Taias understands. (Handler functions remain as a backwards-compatible\n * escape hatch.)\n * \n * Flow logic determines *what should happen next*.\n * UI affordances determine *how that decision appears in the interface*.\n *\n * This file is the boundary where:\n *\n * Inputs → Decision → Manifestations\n *\n * are unified into a single resolve() call.\n *\n * @example Default slots (backwards compatible)\n * ```ts\n * const taias = createTaias({ flow, affordances });\n * ```\n *\n * @example Custom slots (fully type-safe)\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\" | \"headerStyle\";\n * const affordances = defineAffordances<MySlots>((r) => {\n * r.primaryCta(\"cta\", { key: \"nextTool\", value: \"createUser\" });\n * r.contentArea(\"content\", { key: \"contentArea\", value: \"form\" });\n * r.headerStyle(\"header\", { key: \"headerStyle\", value: \"progress\" });\n * });\n * const taias = createTaias<MySlots>({ flow, affordances });\n * ```\n */\nexport function createTaias<S extends string = DefaultSlots>(\n options: TaiasOptions<S>\n): Taias<S> {\n const {\n flow,\n affordances,\n devMode = false,\n onMissingStep,\n onWarn,\n } = options;\n\n const warn = onWarn ?? ((msg: string) => console.warn(msg));\n\n // Dev mode: Check for duplicate match conditions.\n // Two steps with structurally identical normalized conditions are duplicates.\n if (devMode) {\n const seenKeys = new Set<string>();\n for (const step of flow.steps) {\n const key = serializeMatch(getMatch(step));\n if (seenKeys.has(key)) {\n const match = getMatch(step);\n const normalized = normalizeFieldCondition(match.toolName);\n const label = \"is\" in normalized ? normalized.is : `isNot:${normalized.isNot}`;\n throw new Error(\n `Taias: Duplicate match condition '${label}' in flow '${flow.id}'. Each step must have a unique match condition.`\n );\n }\n seenKeys.add(key);\n }\n }\n\n // Build internal indexes for efficient resolution.\n //\n // Steps with indexable conditions (is / string sugar) go into an exact\n // Map for O(1) lookup. Steps with non-indexable conditions (isNot) go\n // into a separate list. When no broad steps exist, resolve uses the\n // fast path (Map only). When broad steps exist, resolve evaluates all\n // steps in definition order.\n //\n // This indexing is a performance optimization derived from the current\n // set of operators, not a permanent architectural choice. It will evolve\n // as operators and match condition fields expand.\n const exactIndex = new Map<string, FlowStep>();\n const broadSteps: FlowStep[] = [];\n\n for (const step of flow.steps) {\n const match = getMatch(step);\n if (isIndexable(match.toolName)) {\n exactIndex.set(indexKey(match.toolName), step);\n } else {\n broadSteps.push(step);\n }\n }\n\n const hasBroadSteps = broadSteps.length > 0;\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex<S>(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances<S> | null> {\n let step: FlowStep | undefined;\n\n if (!hasBroadSteps) {\n // Fast path: all steps use indexable conditions (is / string sugar).\n // O(1) Map lookup -- same performance as before operators were introduced.\n step = exactIndex.get(ctx.toolName);\n } else {\n // Full evaluation: some steps use non-indexable conditions (isNot).\n // Evaluate all steps in definition order; first match wins.\n for (const candidate of flow.steps) {\n if (evaluateMatch(getMatch(candidate), ctx)) {\n step = candidate;\n break;\n }\n }\n }\n\n if (!step) {\n onMissingStep?.(ctx);\n return null;\n }\n\n // Evaluate the step based on its kind:\n // - Logic statements: return the decision directly (no function call)\n // - Handler functions (backwards compatibility): call the handler and await the result\n let result: StepDecision | null;\n\n if (step.kind === \"logic\") {\n result = step.statement.decision;\n } else {\n result = await step.handler(ctx);\n }\n\n if (!result) return null;\n\n if (devMode && result.nextTool === \"\") {\n warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n // Build decision object from flow result (spread all fields)\n const decision: Decision = { ...result };\n\n // Compute UI selections (may be empty if no registry passed)\n const selections = selectUiAffordances<S>(decision, registryIndex, {\n devMode,\n onWarn: warn,\n });\n\n return {\n advice: generateAdvice(result.nextTool),\n decision,\n selections,\n };\n },\n };\n}\n","import type {\n AffordanceRegistry,\n BindingInput,\n DefaultSlots,\n HandleRegistration,\n} from \"./types\";\nimport { normalizeBinding } from \"./types\";\n\n/**\n * Mapped type that creates a registration method for each slot in S.\n * This enables fully typed custom slots via generics.\n */\nexport type AffordanceRegistrar<S extends string = DefaultSlots> = {\n [K in S]: (handleId: string, bindsTo: BindingInput) => void;\n};\n\n/**\n * Define UI affordances for a widget using a builder pattern.\n *\n * @example Default slots (backwards compatible)\n * ```ts\n * const affordances = defineAffordances((r) => {\n * r.primaryCta(\"cta.recommend\", { toolName: \"get_recommendations\" });\n * r.widgetVariant(\"variant.discovery\", { toolName: \"get_recommendations\" });\n * });\n * ```\n *\n * @example Custom slots (fully type-safe)\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\" | \"headerStyle\";\n * const affordances = defineAffordances<MySlots>((r) => {\n * r.primaryCta(\"cta.create\", { toolName: \"createUser\" });\n * r.contentArea(\"content.form\", { key: \"contentArea\", value: \"email-form\" });\n * r.headerStyle(\"header.progress\", { key: \"headerStyle\", value: \"step-1\" });\n * });\n * ```\n */\nexport function defineAffordances<S extends string = DefaultSlots>(\n builder: (r: AffordanceRegistrar<S>) => void\n): AffordanceRegistry<S> {\n const handles: HandleRegistration<S>[] = [];\n\n // Proxy creates methods on-the-fly for any slot name.\n // TypeScript ensures only valid slot names (from S) are called.\n const registrar = new Proxy({} as AffordanceRegistrar<S>, {\n get(_, slot: string) {\n return (handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot: slot as S,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n },\n });\n\n builder(registrar);\n return { handles };\n}\n","import type { AffordanceRegistry, DefaultSlots } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Merge multiple affordance registries into one.\n * Generic over slot type S for custom slot support.\n *\n * @example Default slots\n * ```ts\n * const merged = mergeAffordances([widgetA, widgetB]);\n * ```\n *\n * @example Custom slots\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\";\n * const merged = mergeAffordances<MySlots>([widgetA, widgetB]);\n * ```\n */\nexport function mergeAffordances<S extends string = DefaultSlots>(\n registries: AffordanceRegistry<S>[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry<S> = { handles: registries.flatMap((r) => r.handles) };\n\n if (!devMode) return merged;\n\n // Check for duplicate handleIds\n const seenHandleIds = new Set<string>();\n for (const h of merged.handles) {\n if (seenHandleIds.has(h.handleId)) {\n throw new Error(`[Taias] Duplicate handleId \"${h.handleId}\"`);\n }\n seenHandleIds.add(h.handleId);\n }\n\n // Check for ambiguous bindings (same slot + key + value)\n const seenTriples = new Set<string>();\n for (const h of merged.handles) {\n const k = makeBindingKey(h.slot, h.bindsTo);\n if (seenTriples.has(k)) {\n throw new Error(\n `[Taias] Ambiguous affordance: slot \"${h.slot}\" has multiple handles bound to (${h.bindsTo.key}=\"${h.bindsTo.value}\")`\n );\n }\n seenTriples.add(k);\n }\n\n warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);\n return merged;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/flow.ts","../src/uiAffordances/types.ts","../src/uiAffordances/indexing.ts","../src/uiAffordances/select.ts","../src/createTaias.ts","../src/uiAffordances/defineAffordances.ts","../src/uiAffordances/mergeAffordances.ts"],"sourcesContent":["import type { FlowBuilder, FlowDefinition, FlowStep, MatchCondition, StepInput } from \"./types\";\n\n/**\n * Define a flow with its steps.\n *\n * @param flowId - Unique identifier for the flow\n * @param builder - Callback that receives a FlowBuilder to define steps\n * @returns A FlowDefinition object\n *\n * @example Logic statement matching on toolName\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step({ toolName: { is: \"scan_repo\" } }, { nextTool: \"configure_app\" });\n * });\n * ```\n *\n * @example Matching on params and result\n * ```ts\n * flow.step(\n * { toolName: { is: \"scan_repo\" }, params: { language: { is: \"python\" } } },\n * { nextTool: \"configure_python\" },\n * );\n * flow.step(\n * { result: { hasConfig: { is: true } } },\n * { nextTool: \"review_config\" },\n * );\n * ```\n *\n * @example isNot operator\n * ```ts\n * flow.step({ toolName: { isNot: \"abort_session\" } }, { nextTool: \"continue_flow\" });\n * ```\n */\nexport function defineFlow(\n flowId: string,\n builder: (flow: FlowBuilder) => void\n): FlowDefinition {\n const steps: FlowStep[] = [];\n\n const flowBuilder: FlowBuilder = {\n step(match: MatchCondition, input: StepInput): void {\n if (typeof input === \"function\") {\n steps.push({ kind: \"handler\", match, handler: input });\n } else {\n steps.push({\n kind: \"logic\",\n statement: {\n match,\n decision: input,\n },\n });\n }\n },\n };\n\n builder(flowBuilder);\n\n return {\n id: flowId,\n steps,\n };\n}\n","/**\n * Default slots for backwards compatibility.\n * Users can define custom slots by passing a type parameter.\n */\nexport type DefaultSlots = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\n\n/**\n * Alias for backwards compatibility in exports.\n * @deprecated Use DefaultSlots or define your own slot type\n */\nexport type CanonicalSlot = DefaultSlots;\n\nexport type Binding = {\n key: string;\n value: string;\n};\n\n/**\n * Input format for registering affordance bindings.\n * - { toolName } is shorthand for { key: \"nextTool\", value: toolName }\n * - { key, value } is the generalized form for custom bindings\n */\nexport type BindingInput = { toolName: string } | { key: string; value: string };\n\n/**\n * A registered UI affordance handle.\n * Generic over slot type S for custom slot support.\n */\nexport type HandleRegistration<S extends string = DefaultSlots> = {\n slot: S;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\n/**\n * UI selections keyed by slot name.\n * Generic over slot type S for custom slot support.\n */\nexport type UiSelections<S extends string = DefaultSlots> = Partial<Record<S, Selection>>;\n\n/**\n * Collection of registered handles.\n * Generic over slot type S for custom slot support.\n */\nexport type AffordanceRegistry<S extends string = DefaultSlots> = {\n handles: HandleRegistration<S>[];\n};\n\nexport function normalizeBinding(input: BindingInput): Binding {\n if (\"toolName\" in input) return { key: \"nextTool\", value: input.toolName };\n return { key: input.key, value: input.value };\n}\n\n/**\n * Creates a binding key for indexing.\n * Accepts any string for slot to support custom slots.\n */\nexport function makeBindingKey(slot: string, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, DefaultSlots, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\n/**\n * Index structure for efficient affordance lookup.\n * Generic over slot type S for custom slot support.\n */\nexport type RegistryIndex<S extends string = DefaultSlots> = {\n byBindingKey: Map<string, HandleRegistration<S>>;\n slots: Set<S>;\n /** Inferred decision field for each slot, derived from handle bindings. */\n slotKeyMap: Map<S, string>;\n};\n\n/**\n * Build an index from a registry for efficient lookup during selection.\n * Tracks which slots have registered handles and infers the decision field\n * for each slot from its handle bindings.\n *\n * @throws Error if handles for the same slot bind to different keys\n */\nexport function buildRegistryIndex<S extends string = DefaultSlots>(\n registry?: AffordanceRegistry<S>\n): RegistryIndex<S> {\n const byBindingKey = new Map<string, HandleRegistration<S>>();\n const slots = new Set<S>();\n const slotKeyMap = new Map<S, string>();\n\n if (!registry) return { byBindingKey, slots, slotKeyMap };\n\n for (const h of registry.handles) {\n slots.add(h.slot);\n\n // Infer and validate slot key\n const existingKey = slotKeyMap.get(h.slot);\n if (existingKey && existingKey !== h.bindsTo.key) {\n throw new Error(\n `[Taias] Slot \"${h.slot}\" has handles bound to different keys: \"${existingKey}\" and \"${h.bindsTo.key}\". ` +\n `All handles for a slot must use the same decision field.`\n );\n }\n slotKeyMap.set(h.slot, h.bindsTo.key);\n\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey, slots, slotKeyMap };\n}\n","import type { Decision } from \"../types\";\nimport type { DefaultSlots, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nexport type SelectOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Select UI affordances based on flow decision.\n * Uses the inferred decision field for each slot from the registry index.\n */\nexport function selectUiAffordances<S extends string = DefaultSlots>(\n decision: Decision,\n index: RegistryIndex<S>,\n opts: SelectOptions = {}\n): UiSelections<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections<S> = {};\n\n for (const slot of index.slots) {\n // Use inferred key from handle bindings\n const field = index.slotKeyMap.get(slot);\n if (!field) continue;\n\n const value = decision[field];\n if (!value) continue;\n\n const k = makeBindingKey(slot, { key: field, value });\n const handle = index.byBindingKey.get(k);\n\n if (!handle) {\n if (devMode) warn(`[Taias] No affordance for slot \"${slot}\" when ${field}=\"${value}\"`);\n continue;\n }\n\n (selections as Record<string, unknown>)[slot] = {\n handleId: handle.handleId,\n bindsTo: handle.bindsTo,\n };\n }\n\n return selections;\n}\n","import type { Affordances, Condition, FlowStep, MatchCondition, StepDecision, Taias, TaiasContext, TaiasOptions, Decision } from \"./types\";\nimport type { DefaultSlots } from \"./uiAffordances/types\";\nimport { buildRegistryIndex } from \"./uiAffordances/indexing\";\nimport { selectUiAffordances } from \"./uiAffordances/select\";\n\n/**\n * Generate advice text for a given next tool.\n */\nfunction generateAdvice(nextTool: string): string {\n return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;\n}\n\n// ---------------------------------------------------------------------------\n// Condition evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a single Condition against a value using strict equality.\n */\nfunction evaluateCondition(condition: Condition, value: unknown): boolean {\n if (\"is\" in condition) return value === condition.is;\n if (\"isNot\" in condition) return value !== condition.isNot;\n return false;\n}\n\n/**\n * Evaluate a full MatchCondition against a TaiasContext.\n * All present conditions must be satisfied (logical AND).\n *\n * - toolName: compared directly against ctx.toolName\n * - params: subset match -- each specified key is checked against ctx.params\n * - result: subset match -- each specified key is checked against ctx.result\n *\n * If a step specifies params/result conditions but the context doesn't\n * include params/result, the step does not match.\n */\nfunction evaluateMatch(match: MatchCondition, ctx: TaiasContext): boolean {\n if (match.toolName && !evaluateCondition(match.toolName, ctx.toolName)) return false;\n\n if (match.params) {\n if (!ctx.params) return false;\n for (const [key, condition] of Object.entries(match.params)) {\n if (!evaluateCondition(condition, ctx.params[key])) return false;\n }\n }\n\n if (match.result) {\n if (!ctx.result) return false;\n for (const [key, condition] of Object.entries(match.result)) {\n if (!evaluateCondition(condition, ctx.result[key])) return false;\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Per-field indexing\n// ---------------------------------------------------------------------------\n\ninterface FieldIndex {\n valueMap: Map<unknown, number[]>;\n unconstrained: number[];\n}\n\n/**\n * Extract all `is` conditions from a match condition as field-path / value pairs.\n */\nfunction extractIsConditions(match: MatchCondition): Array<{ path: string; value: unknown }> {\n const conditions: Array<{ path: string; value: unknown }> = [];\n if (match.toolName && \"is\" in match.toolName) {\n conditions.push({ path: \"toolName\", value: match.toolName.is });\n }\n if (match.params) {\n for (const [key, cond] of Object.entries(match.params)) {\n if (\"is\" in cond) conditions.push({ path: `params.${key}`, value: cond.is });\n }\n }\n if (match.result) {\n for (const [key, cond] of Object.entries(match.result)) {\n if (\"is\" in cond) conditions.push({ path: `result.${key}`, value: cond.is });\n }\n }\n return conditions;\n}\n\n/**\n * Check whether a match condition has a condition on a given field path.\n */\nfunction hasConditionOnField(match: MatchCondition, path: string): boolean {\n if (path === \"toolName\") return !!match.toolName;\n if (path.startsWith(\"params.\")) return !!match.params?.[path.slice(7)];\n if (path.startsWith(\"result.\")) return !!match.result?.[path.slice(7)];\n return false;\n}\n\n/**\n * Get the value from a TaiasContext for a given field path.\n */\nfunction getContextValue(ctx: TaiasContext, path: string): unknown {\n if (path === \"toolName\") return ctx.toolName;\n if (path.startsWith(\"params.\")) return ctx.params?.[path.slice(7)];\n if (path.startsWith(\"result.\")) return ctx.result?.[path.slice(7)];\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Step access helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the match condition from a FlowStep.\n *\n * - Logic-based steps: match comes from statement.match\n * - Handler-based steps (backwards compatibility): match is stored directly on the step\n */\nfunction getMatch(step: FlowStep): MatchCondition {\n return step.kind === \"logic\" ? step.statement.match : step.match;\n}\n\n/**\n * Serialize a MatchCondition into a stable string for duplicate detection.\n */\nfunction serializeMatch(match: MatchCondition): string {\n return JSON.stringify(match);\n}\n\n/**\n * createTaias constructs a decision engine.\n *\n * Taias resolves context into a generalized Decision object,\n * and then manifests that decision into concrete affordances:\n *\n * - LLM guidance (advice)\n * - UI affordance selections\n *\n * Flow logic is expressed as logic statements -- structured data that\n * Taias understands. (Handler functions remain as a backwards-compatible\n * escape hatch.)\n *\n * Flow logic determines *what should happen next*.\n * UI affordances determine *how that decision appears in the interface*.\n *\n * This file is the boundary where:\n *\n * Inputs -> Decision -> Manifestations\n *\n * are unified into a single resolve() call.\n *\n * @example\n * ```ts\n * const taias = createTaias({ flow, affordances });\n * ```\n */\nexport function createTaias<S extends string = DefaultSlots>(\n options: TaiasOptions<S>\n): Taias<S> {\n const {\n flow,\n affordances,\n devMode = false,\n onMissingStep,\n onWarn,\n } = options;\n\n const warn = onWarn ?? ((msg: string) => console.warn(msg));\n\n // Dev mode: Check for duplicate match conditions.\n if (devMode) {\n const seenKeys = new Set<string>();\n for (const step of flow.steps) {\n const key = serializeMatch(getMatch(step));\n if (seenKeys.has(key)) {\n throw new Error(\n `Taias: Duplicate match condition in flow '${flow.id}'. Each step must have a unique match condition. Duplicate: ${key}`\n );\n }\n seenKeys.add(key);\n }\n }\n\n // -----------------------------------------------------------------------\n // Build per-field indexes\n // -----------------------------------------------------------------------\n\n const fieldIndexes = new Map<string, FieldIndex>();\n const indexableStepIndices: number[] = [];\n const broadStepIndices: number[] = [];\n\n for (let i = 0; i < flow.steps.length; i++) {\n const match = getMatch(flow.steps[i]);\n const isConditions = extractIsConditions(match);\n\n if (isConditions.length === 0) {\n broadStepIndices.push(i);\n continue;\n }\n\n indexableStepIndices.push(i);\n\n for (const { path, value } of isConditions) {\n let fieldIndex = fieldIndexes.get(path);\n if (!fieldIndex) {\n fieldIndex = { valueMap: new Map(), unconstrained: [] };\n fieldIndexes.set(path, fieldIndex);\n }\n let stepList = fieldIndex.valueMap.get(value);\n if (!stepList) {\n stepList = [];\n fieldIndex.valueMap.set(value, stepList);\n }\n stepList.push(i);\n }\n }\n\n // Build unconstrained sets: for each field index, find indexable steps\n // that don't have a condition on that field.\n for (const [fieldPath, fieldIndex] of fieldIndexes) {\n for (const i of indexableStepIndices) {\n if (!hasConditionOnField(getMatch(flow.steps[i]), fieldPath)) {\n fieldIndex.unconstrained.push(i);\n }\n }\n }\n\n const hasBroadSteps = broadStepIndices.length > 0;\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex<S>(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances<S> | null> {\n let matchedStep: FlowStep | undefined;\n\n // Phase 1: Find candidates from per-field indexes via intersection\n const applicableFieldPaths: string[] = [];\n for (const fieldPath of fieldIndexes.keys()) {\n const ctxValue = getContextValue(ctx, fieldPath);\n if (ctxValue !== undefined) {\n applicableFieldPaths.push(fieldPath);\n }\n }\n\n if (applicableFieldPaths.length > 0) {\n // Build candidate sets for each applicable field and intersect\n let candidates: Set<number> | null = null;\n\n for (const fieldPath of applicableFieldPaths) {\n const fieldIndex = fieldIndexes.get(fieldPath)!;\n const ctxValue = getContextValue(ctx, fieldPath);\n\n const fieldCandidates = new Set<number>();\n\n // Indexed matches for this field's value\n const indexed = fieldIndex.valueMap.get(ctxValue);\n if (indexed) {\n for (const idx of indexed) fieldCandidates.add(idx);\n }\n\n // Unconstrained steps (don't care about this field)\n for (const idx of fieldIndex.unconstrained) {\n fieldCandidates.add(idx);\n }\n\n if (candidates === null) {\n candidates = fieldCandidates;\n } else {\n // Intersect\n for (const idx of candidates) {\n if (!fieldCandidates.has(idx)) candidates.delete(idx);\n }\n }\n }\n\n // Evaluate full conditions on narrowed candidates (definition order)\n if (candidates && candidates.size > 0) {\n const sorted = [...candidates].sort((a, b) => a - b);\n for (const idx of sorted) {\n if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {\n matchedStep = flow.steps[idx];\n break;\n }\n }\n }\n } else if (indexableStepIndices.length > 0) {\n // Context has no fields that match any index -- evaluate all\n // indexable steps that are unconstrained on everything\n // (i.e., steps with is conditions on fields not present in context).\n // evaluateMatch handles this correctly.\n for (const idx of indexableStepIndices) {\n if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {\n matchedStep = flow.steps[idx];\n break;\n }\n }\n }\n\n // Phase 2: If no indexed match, evaluate broad steps\n if (!matchedStep && hasBroadSteps) {\n for (const idx of broadStepIndices) {\n if (evaluateMatch(getMatch(flow.steps[idx]), ctx)) {\n matchedStep = flow.steps[idx];\n break;\n }\n }\n }\n\n if (!matchedStep) {\n onMissingStep?.(ctx);\n return null;\n }\n\n // Evaluate the step based on its kind\n let result: StepDecision | null;\n\n if (matchedStep.kind === \"logic\") {\n result = matchedStep.statement.decision;\n } else {\n result = await matchedStep.handler(ctx);\n }\n\n if (!result) return null;\n\n if (devMode && result.nextTool === \"\") {\n warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n const decision: Decision = { ...result };\n\n const selections = selectUiAffordances<S>(decision, registryIndex, {\n devMode,\n onWarn: warn,\n });\n\n return {\n advice: generateAdvice(result.nextTool),\n decision,\n selections,\n };\n },\n };\n}\n","import type {\n AffordanceRegistry,\n BindingInput,\n DefaultSlots,\n HandleRegistration,\n} from \"./types\";\nimport { normalizeBinding } from \"./types\";\n\n/**\n * Mapped type that creates a registration method for each slot in S.\n * This enables fully typed custom slots via generics.\n */\nexport type AffordanceRegistrar<S extends string = DefaultSlots> = {\n [K in S]: (handleId: string, bindsTo: BindingInput) => void;\n};\n\n/**\n * Define UI affordances for a widget using a builder pattern.\n *\n * @example Default slots (backwards compatible)\n * ```ts\n * const affordances = defineAffordances((r) => {\n * r.primaryCta(\"cta.recommend\", { toolName: \"get_recommendations\" });\n * r.widgetVariant(\"variant.discovery\", { toolName: \"get_recommendations\" });\n * });\n * ```\n *\n * @example Custom slots (fully type-safe)\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\" | \"headerStyle\";\n * const affordances = defineAffordances<MySlots>((r) => {\n * r.primaryCta(\"cta.create\", { toolName: \"createUser\" });\n * r.contentArea(\"content.form\", { key: \"contentArea\", value: \"email-form\" });\n * r.headerStyle(\"header.progress\", { key: \"headerStyle\", value: \"step-1\" });\n * });\n * ```\n */\nexport function defineAffordances<S extends string = DefaultSlots>(\n builder: (r: AffordanceRegistrar<S>) => void\n): AffordanceRegistry<S> {\n const handles: HandleRegistration<S>[] = [];\n\n // Proxy creates methods on-the-fly for any slot name.\n // TypeScript ensures only valid slot names (from S) are called.\n const registrar = new Proxy({} as AffordanceRegistrar<S>, {\n get(_, slot: string) {\n return (handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot: slot as S,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n },\n });\n\n builder(registrar);\n return { handles };\n}\n","import type { AffordanceRegistry, DefaultSlots } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Merge multiple affordance registries into one.\n * Generic over slot type S for custom slot support.\n *\n * @example Default slots\n * ```ts\n * const merged = mergeAffordances([widgetA, widgetB]);\n * ```\n *\n * @example Custom slots\n * ```ts\n * type MySlots = \"primaryCta\" | \"contentArea\";\n * const merged = mergeAffordances<MySlots>([widgetA, widgetB]);\n * ```\n */\nexport function mergeAffordances<S extends string = DefaultSlots>(\n registries: AffordanceRegistry<S>[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry<S> {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry<S> = { handles: registries.flatMap((r) => r.handles) };\n\n if (!devMode) return merged;\n\n // Check for duplicate handleIds\n const seenHandleIds = new Set<string>();\n for (const h of merged.handles) {\n if (seenHandleIds.has(h.handleId)) {\n throw new Error(`[Taias] Duplicate handleId \"${h.handleId}\"`);\n }\n seenHandleIds.add(h.handleId);\n }\n\n // Check for ambiguous bindings (same slot + key + value)\n const seenTriples = new Set<string>();\n for (const h of merged.handles) {\n const k = makeBindingKey(h.slot, h.bindsTo);\n if (seenTriples.has(k)) {\n throw new Error(\n `[Taias] Ambiguous affordance: slot \"${h.slot}\" has multiple handles bound to (${h.bindsTo.key}=\"${h.bindsTo.value}\")`\n );\n }\n seenTriples.add(k);\n }\n\n warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);\n return merged;\n}\n"],"mappings":"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","names":[]}
|