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 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 condition fields support operators (`is`, `isNot`); bare strings are sugar for `{ is: "..." }`.
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" }); // explicit equality
88
- flow.step({ toolName: { isNot: "abort" } }, { nextTool: "continue" }); // negation
89
- flow.step({ toolName: "tool_a" }, { nextTool: "tool_b" }); // bare string = sugar for { is: "tool_a" }
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: condition, handler: input });
36
+ steps.push({ kind: "handler", match, handler: input });
38
37
  } else {
39
38
  steps.push({
40
39
  kind: "logic",
41
40
  statement: {
42
- match: condition,
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
- const toolCondition = normalizeFieldCondition(match.toolName);
124
- return evaluateCondition(toolCondition, ctx.toolName);
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 isIndexable(field) {
127
- if (typeof field === "string") return true;
128
- return "is" in field;
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 indexKey(field) {
131
- if (typeof field === "string") return field;
132
- if ("is" in field) return field.is;
133
- throw new Error("Cannot derive index key from non-indexable condition");
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
- const normalized = normalizeFieldCondition(match.toolName);
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 '${label}' in flow '${flow.id}'. Each step must have a unique 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 exactIndex = /* @__PURE__ */ new Map();
167
- const broadSteps = [];
168
- for (const step of flow.steps) {
169
- const match = getMatch(step);
170
- if (isIndexable(match.toolName)) {
171
- exactIndex.set(indexKey(match.toolName), step);
172
- } else {
173
- broadSteps.push(step);
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 = broadSteps.length > 0;
222
+ const hasBroadSteps = broadStepIndices.length > 0;
177
223
  const registryIndex = buildRegistryIndex(affordances);
178
224
  return {
179
225
  async resolve(ctx) {
180
- let step;
181
- if (!hasBroadSteps) {
182
- step = exactIndex.get(ctx.toolName);
183
- } else {
184
- for (const candidate of flow.steps) {
185
- if (evaluateMatch(getMatch(candidate), ctx)) {
186
- step = candidate;
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 (!step) {
280
+ if (!matchedStep) {
192
281
  onMissingStep?.(ctx);
193
282
  return null;
194
283
  }
195
284
  let result;
196
- if (step.kind === "logic") {
197
- result = step.statement.decision;
285
+ if (matchedStep.kind === "logic") {
286
+ result = matchedStep.statement.decision;
198
287
  } else {
199
- result = await step.handler(ctx);
288
+ result = await matchedStep.handler(ctx);
200
289
  }
201
290
  if (!result) return null;
202
291
  if (devMode && result.nextTool === "") {
@@ -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: "value" } -- exact equality (field === value)
99
- * - { isNot: "value" } -- not equal (field !== value)
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: string;
109
+ is: unknown;
107
110
  } | {
108
- isNot: string;
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
- * Each field accepts a FieldCondition -- either a bare value (sugar for
120
- * { is: value }) or an explicit operator object ({ is: ... }, { isNot: ... }).
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
- * Designed to expand with additional fields (params, result, state, etc.).
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: FieldCondition;
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. A string is sugar for { toolName: string }.
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: string | MatchCondition, input: StepInput): void;
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 with explicit operator
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 isNot operator
217
+ * @example Matching on params and result
215
218
  * ```ts
216
- * flow.step({ toolName: { isNot: "abort_session" } }, { nextTool: "continue_flow" });
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 Sugar forms (backwards compatible)
220
- * Bare strings are sugar for { toolName: { is: string } }:
229
+ * @example isNot operator
221
230
  * ```ts
222
- * flow.step({ toolName: "scan_repo" }, { nextTool: "configure_app" }); // sugar for { is: "scan_repo" }
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 Decision Manifestations
254
+ * Inputs -> Decision -> Manifestations
247
255
  *
248
256
  * are unified into a single resolve() call.
249
257
  *
250
- * @example Default slots (backwards compatible)
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 FieldCondition, 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 };
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: "value" } -- exact equality (field === value)
99
- * - { isNot: "value" } -- not equal (field !== value)
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: string;
109
+ is: unknown;
107
110
  } | {
108
- isNot: string;
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
- * Each field accepts a FieldCondition -- either a bare value (sugar for
120
- * { is: value }) or an explicit operator object ({ is: ... }, { isNot: ... }).
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
- * Designed to expand with additional fields (params, result, state, etc.).
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: FieldCondition;
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. A string is sugar for { toolName: string }.
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: string | MatchCondition, input: StepInput): void;
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 with explicit operator
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 isNot operator
217
+ * @example Matching on params and result
215
218
  * ```ts
216
- * flow.step({ toolName: { isNot: "abort_session" } }, { nextTool: "continue_flow" });
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 Sugar forms (backwards compatible)
220
- * Bare strings are sugar for { toolName: { is: string } }:
229
+ * @example isNot operator
221
230
  * ```ts
222
- * flow.step({ toolName: "scan_repo" }, { nextTool: "configure_app" }); // sugar for { is: "scan_repo" }
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 Decision Manifestations
254
+ * Inputs -> Decision -> Manifestations
247
255
  *
248
256
  * are unified into a single resolve() call.
249
257
  *
250
- * @example Default slots (backwards compatible)
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 FieldCondition, 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 };
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: condition, handler: input });
7
+ steps.push({ kind: "handler", match, handler: input });
9
8
  } else {
10
9
  steps.push({
11
10
  kind: "logic",
12
11
  statement: {
13
- match: condition,
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
- const toolCondition = normalizeFieldCondition(match.toolName);
95
- return evaluateCondition(toolCondition, ctx.toolName);
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 isIndexable(field) {
98
- if (typeof field === "string") return true;
99
- return "is" in field;
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 indexKey(field) {
102
- if (typeof field === "string") return field;
103
- if ("is" in field) return field.is;
104
- throw new Error("Cannot derive index key from non-indexable condition");
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
- const normalized = normalizeFieldCondition(match.toolName);
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 '${label}' in flow '${flow.id}'. Each step must have a unique 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 exactIndex = /* @__PURE__ */ new Map();
138
- const broadSteps = [];
139
- for (const step of flow.steps) {
140
- const match = getMatch(step);
141
- if (isIndexable(match.toolName)) {
142
- exactIndex.set(indexKey(match.toolName), step);
143
- } else {
144
- broadSteps.push(step);
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 = broadSteps.length > 0;
193
+ const hasBroadSteps = broadStepIndices.length > 0;
148
194
  const registryIndex = buildRegistryIndex(affordances);
149
195
  return {
150
196
  async resolve(ctx) {
151
- let step;
152
- if (!hasBroadSteps) {
153
- step = exactIndex.get(ctx.toolName);
154
- } else {
155
- for (const candidate of flow.steps) {
156
- if (evaluateMatch(getMatch(candidate), ctx)) {
157
- step = candidate;
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 (!step) {
251
+ if (!matchedStep) {
163
252
  onMissingStep?.(ctx);
164
253
  return null;
165
254
  }
166
255
  let result;
167
- if (step.kind === "logic") {
168
- result = step.statement.decision;
256
+ if (matchedStep.kind === "logic") {
257
+ result = matchedStep.statement.decision;
169
258
  } else {
170
- result = await step.handler(ctx);
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taias",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Taias - Give your MCP server an opinion, guide your users to achieve outcomes",
6
6
  "license": "Apache-2.0",