taias 0.2.1 → 0.4.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/dist/index.cjs CHANGED
@@ -54,11 +54,13 @@ function makeBindingKey(slot, binding) {
54
54
  // src/uiAffordances/indexing.ts
55
55
  function buildRegistryIndex(registry) {
56
56
  const byBindingKey = /* @__PURE__ */ new Map();
57
- if (!registry) return { byBindingKey };
57
+ const slots = /* @__PURE__ */ new Set();
58
+ if (!registry) return { byBindingKey, slots };
58
59
  for (const h of registry.handles) {
60
+ slots.add(h.slot);
59
61
  byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);
60
62
  }
61
- return { byBindingKey };
63
+ return { byBindingKey, slots };
62
64
  }
63
65
 
64
66
  // src/uiAffordances/select.ts
@@ -68,14 +70,13 @@ var DEFAULT_SLOT_MATCH = {
68
70
  widgetVariant: "nextTool"
69
71
  };
70
72
  function selectUiAffordances(decision, index, opts = {}) {
71
- const slotMatch = { ...DEFAULT_SLOT_MATCH, ...opts.slotMatch ?? {} };
73
+ const slotMatch = opts.slotMatch;
72
74
  const devMode = !!opts.devMode;
73
75
  const warn = opts.onWarn ?? (() => {
74
76
  });
75
77
  const selections = {};
76
- const slots = Object.keys(DEFAULT_SLOT_MATCH);
77
- for (const slot of slots) {
78
- const field = slotMatch[slot];
78
+ for (const slot of index.slots) {
79
+ const field = slotMatch?.[slot] ?? DEFAULT_SLOT_MATCH[slot] ?? "nextTool";
79
80
  const value = decision[field];
80
81
  if (!value) continue;
81
82
  const k = makeBindingKey(slot, { key: field, value });
@@ -84,7 +85,10 @@ function selectUiAffordances(decision, index, opts = {}) {
84
85
  if (devMode) warn(`[Taias] No affordance for slot "${slot}" when ${field}="${value}"`);
85
86
  continue;
86
87
  }
87
- selections[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };
88
+ selections[slot] = {
89
+ handleId: handle.handleId,
90
+ bindsTo: handle.bindsTo
91
+ };
88
92
  }
89
93
  return selections;
90
94
  }
@@ -128,9 +132,7 @@ function createTaias(options) {
128
132
  if (devMode && result.nextTool === "") {
129
133
  warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);
130
134
  }
131
- const decision = {
132
- nextTool: result.nextTool
133
- };
135
+ const decision = { ...result };
134
136
  const selections = selectUiAffordances(decision, registryIndex, {
135
137
  devMode,
136
138
  onWarn: warn,
@@ -148,18 +150,17 @@ function createTaias(options) {
148
150
  // src/uiAffordances/defineAffordances.ts
149
151
  function defineAffordances(builder) {
150
152
  const handles = [];
151
- const push = (slot, handleId, bindsTo) => {
152
- handles.push({
153
- slot,
154
- handleId,
155
- bindsTo: normalizeBinding(bindsTo)
156
- });
157
- };
158
- const registrar = {
159
- primaryCta: (handleId, bindsTo) => push("primaryCta", handleId, bindsTo),
160
- secondaryCta: (handleId, bindsTo) => push("secondaryCta", handleId, bindsTo),
161
- widgetVariant: (handleId, bindsTo) => push("widgetVariant", handleId, bindsTo)
162
- };
153
+ const registrar = new Proxy({}, {
154
+ get(_, slot) {
155
+ return (handleId, bindsTo) => {
156
+ handles.push({
157
+ slot,
158
+ handleId,
159
+ bindsTo: normalizeBinding(bindsTo)
160
+ });
161
+ };
162
+ }
163
+ });
163
164
  builder(registrar);
164
165
  return { handles };
165
166
  }
@@ -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 {\n CanonicalSlot,\n Binding,\n BindingInput,\n HandleRegistration,\n Selection,\n UiSelections,\n AffordanceRegistry,\n SlotMatch,\n} from \"./uiAffordances/types\";\n\n// Core + flow type exports\nexport type {\n Decision, // moved to core\n TaiasContext,\n StepDecision,\n Affordances,\n StepHandler,\n FlowStep,\n FlowDefinition,\n FlowBuilder,\n TaiasOptions,\n Taias,\n} from \"./types\";\n","import type { FlowBuilder, FlowDefinition, FlowStep, StepHandler } 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\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step(\"scan_repo\", (ctx) => ({\n * nextTool: \"configure_app\",\n * }));\n * });\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(toolName: string, handler: StepHandler): void {\n steps.push({ toolName, handler });\n },\n };\n\n builder(flowBuilder);\n\n return {\n id: flowId,\n steps,\n };\n}\n\n","export type CanonicalSlot = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\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\nexport type HandleRegistration = {\n slot: CanonicalSlot;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type UiSelections = Partial<Record<CanonicalSlot, Selection>>;\n\nexport type AffordanceRegistry = {\n handles: HandleRegistration[];\n};\n\nexport type SlotMatch = Partial<Record<CanonicalSlot, string>>;\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\nexport function makeBindingKey(slot: CanonicalSlot, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type RegistryIndex = {\n byBindingKey: Map<string, HandleRegistration>;\n};\n\nexport function buildRegistryIndex(registry?: AffordanceRegistry): RegistryIndex {\n const byBindingKey = new Map<string, HandleRegistration>();\n\n if (!registry) return { byBindingKey };\n\n for (const h of registry.handles) {\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey };\n}\n","import type { Decision } from \"../types\";\nimport type { CanonicalSlot, SlotMatch, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nconst DEFAULT_SLOT_MATCH: Required<Record<CanonicalSlot, string>> = {\n primaryCta: \"nextTool\",\n secondaryCta: \"nextTool\",\n widgetVariant: \"nextTool\",\n};\n\nexport type SelectOptions = {\n slotMatch?: SlotMatch;\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function selectUiAffordances(\n decision: Decision,\n index: RegistryIndex,\n opts: SelectOptions = {}\n): UiSelections {\n const slotMatch = { ...DEFAULT_SLOT_MATCH, ...(opts.slotMatch ?? {}) };\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections = {};\n const slots = Object.keys(DEFAULT_SLOT_MATCH) as CanonicalSlot[];\n\n for (const slot of slots) {\n const field = slotMatch[slot];\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[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };\n }\n\n return selections;\n}\n","import type { Affordances, Taias, TaiasContext, TaiasOptions, Decision } from \"./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 * createTaias constructs a decision engine.\n *\n * Taias resolves tool 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 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 */\nexport function createTaias(options: TaiasOptions): Taias {\n const {\n flow,\n affordances,\n slotMatch,\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 toolNames\n if (devMode) {\n const seenTools = new Set<string>();\n for (const step of flow.steps) {\n if (seenTools.has(step.toolName)) {\n throw new Error(\n `Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances | null> {\n const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n onMissingStep?.(ctx);\n return null;\n }\n\n const result = await handler(ctx);\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\n const decision: Decision = {\n nextTool: result.nextTool,\n };\n\n // Compute UI selections (may be empty if no registry passed)\n const selections = selectUiAffordances(decision, registryIndex, {\n devMode,\n onWarn: warn,\n slotMatch,\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 CanonicalSlot,\n HandleRegistration,\n } from \"./types\";\n import { normalizeBinding } from \"./types\";\n \n type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;\n \n export interface AffordanceRegistrar {\n primaryCta: RegistrarFn;\n secondaryCta: RegistrarFn;\n widgetVariant: RegistrarFn;\n }\n \n export function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry {\n const handles: HandleRegistration[] = [];\n \n const push = (slot: CanonicalSlot, handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n \n const registrar: AffordanceRegistrar = {\n primaryCta: (handleId, bindsTo) => push(\"primaryCta\", handleId, bindsTo),\n secondaryCta: (handleId, bindsTo) => push(\"secondaryCta\", handleId, bindsTo),\n widgetVariant: (handleId, bindsTo) => push(\"widgetVariant\", handleId, bindsTo),\n };\n \n builder(registrar);\n return { handles };\n }\n ","import type { AffordanceRegistry } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function mergeAffordances(\n registries: AffordanceRegistry[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry = { 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 SlotMatch,\n} from \"./uiAffordances/types\";\n\n// Core + flow type exports\nexport type {\n Decision,\n TaiasContext,\n StepDecision,\n Affordances,\n StepHandler,\n FlowStep,\n FlowDefinition,\n FlowBuilder,\n TaiasOptions,\n Taias,\n} from \"./types\";\n","import type { FlowBuilder, FlowDefinition, FlowStep, StepHandler } 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\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step(\"scan_repo\", (ctx) => ({\n * nextTool: \"configure_app\",\n * }));\n * });\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(toolName: string, handler: StepHandler): void {\n steps.push({ toolName, handler });\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\n/**\n * Mapping of slots to decision fields.\n * Generic over slot type S for custom slot support.\n */\nexport type SlotMatch<S extends string = DefaultSlots> = Partial<Record<S, string>>;\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};\n\n/**\n * Build an index from a registry for efficient lookup during selection.\n * Tracks which slots have registered handles.\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\n if (!registry) return { byBindingKey, slots };\n\n for (const h of registry.handles) {\n slots.add(h.slot);\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey, slots };\n}\n","import type { Decision } from \"../types\";\nimport type { DefaultSlots, SlotMatch, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\n/**\n * Default slot-to-field mappings for the canonical slots.\n * Custom slots default to \"nextTool\" if not specified in slotMatch.\n */\nconst DEFAULT_SLOT_MATCH: Record<DefaultSlots, string> = {\n primaryCta: \"nextTool\",\n secondaryCta: \"nextTool\",\n widgetVariant: \"nextTool\",\n};\n\nexport type SelectOptions<S extends string = DefaultSlots> = {\n slotMatch?: SlotMatch<S>;\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Select UI affordances based on flow decision.\n * Iterates over registered slots (from index) rather than hardcoded list.\n */\nexport function selectUiAffordances<S extends string = DefaultSlots>(\n decision: Decision,\n index: RegistryIndex<S>,\n opts: SelectOptions<S> = {}\n): UiSelections<S> {\n const slotMatch = opts.slotMatch as Record<string, string> | undefined;\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections<S> = {};\n\n // Iterate over registered slots, not hardcoded list\n for (const slot of index.slots) {\n // Use custom slotMatch, then DEFAULT_SLOT_MATCH for canonical slots, then \"nextTool\"\n const field =\n slotMatch?.[slot] ??\n DEFAULT_SLOT_MATCH[slot as DefaultSlots] ??\n \"nextTool\";\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, 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 * createTaias constructs a decision engine.\n *\n * Taias resolves tool 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 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 taias = createTaias<MySlots>({\n * flow,\n * affordances,\n * slotMatch: { contentArea: \"contentArea\", headerStyle: \"headerStyle\" },\n * });\n * ```\n */\nexport function createTaias<S extends string = DefaultSlots>(\n options: TaiasOptions<S>\n): Taias<S> {\n const {\n flow,\n affordances,\n slotMatch,\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 toolNames\n if (devMode) {\n const seenTools = new Set<string>();\n for (const step of flow.steps) {\n if (seenTools.has(step.toolName)) {\n throw new Error(\n `Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));\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 const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n onMissingStep?.(ctx);\n return null;\n }\n\n const result = await handler(ctx);\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 slotMatch,\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;;;ACkBO,SAAS,WACd,QACA,SACgB;AAChB,QAAM,QAAoB,CAAC;AAE3B,QAAM,cAA2B;AAAA,IAC/B,KAAK,UAAkB,SAA4B;AACjD,YAAM,KAAK,EAAE,UAAU,QAAQ,CAAC;AAAA,IAClC;AAAA,EACF;AAEA,UAAQ,WAAW;AAEnB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ;AAAA,EACF;AACF;;;ACuBO,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;;;ACtDO,SAAS,mBACd,UACkB;AAClB,QAAM,eAAe,oBAAI,IAAmC;AAC5D,QAAM,QAAQ,oBAAI,IAAO;AAEzB,MAAI,CAAC,SAAU,QAAO,EAAE,cAAc,MAAM;AAE5C,aAAW,KAAK,SAAS,SAAS;AAChC,UAAM,IAAI,EAAE,IAAI;AAChB,iBAAa,IAAI,eAAe,EAAE,MAAM,EAAE,OAAO,GAAG,CAAC;AAAA,EACvD;AAEA,SAAO,EAAE,cAAc,MAAM;AAC/B;;;ACrBA,IAAM,qBAAmD;AAAA,EACvD,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,eAAe;AACjB;AAYO,SAAS,oBACd,UACA,OACA,OAAyB,CAAC,GACT;AACjB,QAAM,YAAY,KAAK;AACvB,QAAM,UAAU,CAAC,CAAC,KAAK;AACvB,QAAM,OAAO,KAAK,WAAW,MAAM;AAAA,EAAC;AAEpC,QAAM,aAA8B,CAAC;AAGrC,aAAW,QAAQ,MAAM,OAAO;AAE9B,UAAM,QACJ,YAAY,IAAI,KAChB,mBAAmB,IAAoB,KACvC;AACF,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;;;ACrDA,SAAS,eAAe,UAA0B;AAChD,SAAO,0DAA0D,QAAQ;AAC3E;AAmCO,SAAS,YACd,SACU;AACV,QAAM;AAAA,IACJ;AAAA,IACA;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,YAAY,oBAAI,IAAY;AAClC,eAAW,QAAQ,KAAK,OAAO;AAC7B,UAAI,UAAU,IAAI,KAAK,QAAQ,GAAG;AAChC,cAAM,IAAI;AAAA,UACR,mCAAmC,KAAK,QAAQ,cAAc,KAAK,EAAE;AAAA,QACvE;AAAA,MACF;AACA,gBAAU,IAAI,KAAK,QAAQ;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,UAAU,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,UAAU,KAAK,OAAO,CAAC,CAAC;AAG/E,QAAM,gBAAgB,mBAAsB,WAAW;AAEvD,SAAO;AAAA,IACL,MAAM,QAAQ,KAAmD;AAC/D,YAAM,UAAU,QAAQ,IAAI,IAAI,QAAQ;AAExC,UAAI,CAAC,SAAS;AACZ,wBAAgB,GAAG;AACnB,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,MAAM,QAAQ,GAAG;AAChC,UAAI,CAAC,OAAQ,QAAO;AAEpB,UAAI,WAAW,OAAO,aAAa,IAAI;AACrC,aAAK,6BAA6B,IAAI,QAAQ,aAAa;AAAA,MAC7D;AAGA,YAAM,WAAqB,EAAE,GAAG,OAAO;AAGvC,YAAM,aAAa,oBAAuB,UAAU,eAAe;AAAA,QACjE;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAED,aAAO;AAAA,QACL,QAAQ,eAAe,OAAO,QAAQ;AAAA,QACtC;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC1EO,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
@@ -1,4 +1,13 @@
1
- type CanonicalSlot = "primaryCta" | "secondaryCta" | "widgetVariant";
1
+ /**
2
+ * Default slots for backwards compatibility.
3
+ * Users can define custom slots by passing a type parameter.
4
+ */
5
+ type DefaultSlots = "primaryCta" | "secondaryCta" | "widgetVariant";
6
+ /**
7
+ * Alias for backwards compatibility in exports.
8
+ * @deprecated Use DefaultSlots or define your own slot type
9
+ */
10
+ type CanonicalSlot = DefaultSlots;
2
11
  type Binding = {
3
12
  key: string;
4
13
  value: string;
@@ -14,8 +23,12 @@ type BindingInput = {
14
23
  key: string;
15
24
  value: string;
16
25
  };
17
- type HandleRegistration = {
18
- slot: CanonicalSlot;
26
+ /**
27
+ * A registered UI affordance handle.
28
+ * Generic over slot type S for custom slot support.
29
+ */
30
+ type HandleRegistration<S extends string = DefaultSlots> = {
31
+ slot: S;
19
32
  handleId: string;
20
33
  bindsTo: Binding;
21
34
  };
@@ -23,15 +36,27 @@ type Selection = {
23
36
  handleId: string;
24
37
  bindsTo: Binding;
25
38
  };
26
- type UiSelections = Partial<Record<CanonicalSlot, Selection>>;
27
- type AffordanceRegistry = {
28
- handles: HandleRegistration[];
39
+ /**
40
+ * UI selections keyed by slot name.
41
+ * Generic over slot type S for custom slot support.
42
+ */
43
+ type UiSelections<S extends string = DefaultSlots> = Partial<Record<S, Selection>>;
44
+ /**
45
+ * Collection of registered handles.
46
+ * Generic over slot type S for custom slot support.
47
+ */
48
+ type AffordanceRegistry<S extends string = DefaultSlots> = {
49
+ handles: HandleRegistration<S>[];
29
50
  };
30
- type SlotMatch = Partial<Record<CanonicalSlot, string>>;
51
+ /**
52
+ * Mapping of slots to decision fields.
53
+ * Generic over slot type S for custom slot support.
54
+ */
55
+ type SlotMatch<S extends string = DefaultSlots> = Partial<Record<S, string>>;
31
56
 
32
57
  /**
33
58
  * Generalized decision object.
34
- * Currently uses decision.nextTool. The structure supports additional keys.
59
+ * Contains nextTool plus any custom fields returned by step handlers.
35
60
  */
36
61
  type Decision = Record<string, string | undefined>;
37
62
  /**
@@ -42,20 +67,24 @@ type TaiasContext = {
42
67
  };
43
68
  /**
44
69
  * Decision returned by a step handler specifying the next tool.
70
+ * Additional custom fields can be included for multi-dimensional UI control.
45
71
  */
46
72
  type StepDecision = {
47
73
  nextTool: string;
74
+ [key: string]: string;
48
75
  };
49
76
  /**
50
77
  * Affordances returned by resolve():
51
78
  * - advice: LLM guidance text
52
- * - decision: generalized decision object (currently includes nextTool)
79
+ * - decision: generalized decision object (contains nextTool + custom fields)
53
80
  * - selections: UI affordance selections (may be empty)
81
+ *
82
+ * Generic over slot type S for custom slot support.
54
83
  */
55
- type Affordances = {
84
+ type Affordances<S extends string = DefaultSlots> = {
56
85
  advice: string;
57
86
  decision: Decision;
58
- selections: UiSelections;
87
+ selections: UiSelections<S>;
59
88
  };
60
89
  /**
61
90
  * Handler function for a flow step.
@@ -84,20 +113,22 @@ interface FlowBuilder {
84
113
  }
85
114
  /**
86
115
  * Options for creating a Taias instance.
116
+ * Generic over slot type S for custom slot support.
87
117
  */
88
- type TaiasOptions = {
118
+ type TaiasOptions<S extends string = DefaultSlots> = {
89
119
  flow: FlowDefinition;
90
- affordances?: AffordanceRegistry;
91
- slotMatch?: SlotMatch;
120
+ affordances?: AffordanceRegistry<S>;
121
+ slotMatch?: SlotMatch<S>;
92
122
  devMode?: boolean;
93
123
  onMissingStep?: (ctx: TaiasContext) => void;
94
124
  onWarn?: (msg: string) => void;
95
125
  };
96
126
  /**
97
127
  * The Taias instance interface.
128
+ * Generic over slot type S for custom slot support.
98
129
  */
99
- interface Taias {
100
- resolve(ctx: TaiasContext): Affordances | null | Promise<Affordances | null>;
130
+ interface Taias<S extends string = DefaultSlots> {
131
+ resolve(ctx: TaiasContext): Affordances<S> | null | Promise<Affordances<S> | null>;
101
132
  }
102
133
 
103
134
  /**
@@ -135,21 +166,73 @@ declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void
135
166
  * Inputs → Decision → Manifestations
136
167
  *
137
168
  * are unified into a single resolve() call.
169
+ *
170
+ * @example Default slots (backwards compatible)
171
+ * ```ts
172
+ * const taias = createTaias({ flow, affordances });
173
+ * ```
174
+ *
175
+ * @example Custom slots (fully type-safe)
176
+ * ```ts
177
+ * type MySlots = "primaryCta" | "contentArea" | "headerStyle";
178
+ * const taias = createTaias<MySlots>({
179
+ * flow,
180
+ * affordances,
181
+ * slotMatch: { contentArea: "contentArea", headerStyle: "headerStyle" },
182
+ * });
183
+ * ```
138
184
  */
139
- declare function createTaias(options: TaiasOptions): Taias;
185
+ declare function createTaias<S extends string = DefaultSlots>(options: TaiasOptions<S>): Taias<S>;
140
186
 
141
- type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;
142
- interface AffordanceRegistrar {
143
- primaryCta: RegistrarFn;
144
- secondaryCta: RegistrarFn;
145
- widgetVariant: RegistrarFn;
146
- }
147
- declare function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry;
187
+ /**
188
+ * Mapped type that creates a registration method for each slot in S.
189
+ * This enables fully typed custom slots via generics.
190
+ */
191
+ type AffordanceRegistrar<S extends string = DefaultSlots> = {
192
+ [K in S]: (handleId: string, bindsTo: BindingInput) => void;
193
+ };
194
+ /**
195
+ * Define UI affordances for a widget using a builder pattern.
196
+ *
197
+ * @example Default slots (backwards compatible)
198
+ * ```ts
199
+ * const affordances = defineAffordances((r) => {
200
+ * r.primaryCta("cta.recommend", { toolName: "get_recommendations" });
201
+ * r.widgetVariant("variant.discovery", { toolName: "get_recommendations" });
202
+ * });
203
+ * ```
204
+ *
205
+ * @example Custom slots (fully type-safe)
206
+ * ```ts
207
+ * type MySlots = "primaryCta" | "contentArea" | "headerStyle";
208
+ * const affordances = defineAffordances<MySlots>((r) => {
209
+ * r.primaryCta("cta.create", { toolName: "createUser" });
210
+ * r.contentArea("content.form", { key: "contentArea", value: "email-form" });
211
+ * r.headerStyle("header.progress", { key: "headerStyle", value: "step-1" });
212
+ * });
213
+ * ```
214
+ */
215
+ declare function defineAffordances<S extends string = DefaultSlots>(builder: (r: AffordanceRegistrar<S>) => void): AffordanceRegistry<S>;
148
216
 
149
217
  type MergeAffordancesOptions = {
150
218
  devMode?: boolean;
151
219
  onWarn?: (msg: string) => void;
152
220
  };
153
- declare function mergeAffordances(registries: AffordanceRegistry[], opts?: MergeAffordancesOptions): AffordanceRegistry;
221
+ /**
222
+ * Merge multiple affordance registries into one.
223
+ * Generic over slot type S for custom slot support.
224
+ *
225
+ * @example Default slots
226
+ * ```ts
227
+ * const merged = mergeAffordances([widgetA, widgetB]);
228
+ * ```
229
+ *
230
+ * @example Custom slots
231
+ * ```ts
232
+ * type MySlots = "primaryCta" | "contentArea";
233
+ * const merged = mergeAffordances<MySlots>([widgetA, widgetB]);
234
+ * ```
235
+ */
236
+ declare function mergeAffordances<S extends string = DefaultSlots>(registries: AffordanceRegistry<S>[], opts?: MergeAffordancesOptions): AffordanceRegistry<S>;
154
237
 
155
- export { type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Decision, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type Selection, type SlotMatch, type StepDecision, type StepHandler, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
238
+ export { type AffordanceRegistrar, type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Decision, type DefaultSlots, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type Selection, type SlotMatch, type StepDecision, type StepHandler, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,13 @@
1
- type CanonicalSlot = "primaryCta" | "secondaryCta" | "widgetVariant";
1
+ /**
2
+ * Default slots for backwards compatibility.
3
+ * Users can define custom slots by passing a type parameter.
4
+ */
5
+ type DefaultSlots = "primaryCta" | "secondaryCta" | "widgetVariant";
6
+ /**
7
+ * Alias for backwards compatibility in exports.
8
+ * @deprecated Use DefaultSlots or define your own slot type
9
+ */
10
+ type CanonicalSlot = DefaultSlots;
2
11
  type Binding = {
3
12
  key: string;
4
13
  value: string;
@@ -14,8 +23,12 @@ type BindingInput = {
14
23
  key: string;
15
24
  value: string;
16
25
  };
17
- type HandleRegistration = {
18
- slot: CanonicalSlot;
26
+ /**
27
+ * A registered UI affordance handle.
28
+ * Generic over slot type S for custom slot support.
29
+ */
30
+ type HandleRegistration<S extends string = DefaultSlots> = {
31
+ slot: S;
19
32
  handleId: string;
20
33
  bindsTo: Binding;
21
34
  };
@@ -23,15 +36,27 @@ type Selection = {
23
36
  handleId: string;
24
37
  bindsTo: Binding;
25
38
  };
26
- type UiSelections = Partial<Record<CanonicalSlot, Selection>>;
27
- type AffordanceRegistry = {
28
- handles: HandleRegistration[];
39
+ /**
40
+ * UI selections keyed by slot name.
41
+ * Generic over slot type S for custom slot support.
42
+ */
43
+ type UiSelections<S extends string = DefaultSlots> = Partial<Record<S, Selection>>;
44
+ /**
45
+ * Collection of registered handles.
46
+ * Generic over slot type S for custom slot support.
47
+ */
48
+ type AffordanceRegistry<S extends string = DefaultSlots> = {
49
+ handles: HandleRegistration<S>[];
29
50
  };
30
- type SlotMatch = Partial<Record<CanonicalSlot, string>>;
51
+ /**
52
+ * Mapping of slots to decision fields.
53
+ * Generic over slot type S for custom slot support.
54
+ */
55
+ type SlotMatch<S extends string = DefaultSlots> = Partial<Record<S, string>>;
31
56
 
32
57
  /**
33
58
  * Generalized decision object.
34
- * Currently uses decision.nextTool. The structure supports additional keys.
59
+ * Contains nextTool plus any custom fields returned by step handlers.
35
60
  */
36
61
  type Decision = Record<string, string | undefined>;
37
62
  /**
@@ -42,20 +67,24 @@ type TaiasContext = {
42
67
  };
43
68
  /**
44
69
  * Decision returned by a step handler specifying the next tool.
70
+ * Additional custom fields can be included for multi-dimensional UI control.
45
71
  */
46
72
  type StepDecision = {
47
73
  nextTool: string;
74
+ [key: string]: string;
48
75
  };
49
76
  /**
50
77
  * Affordances returned by resolve():
51
78
  * - advice: LLM guidance text
52
- * - decision: generalized decision object (currently includes nextTool)
79
+ * - decision: generalized decision object (contains nextTool + custom fields)
53
80
  * - selections: UI affordance selections (may be empty)
81
+ *
82
+ * Generic over slot type S for custom slot support.
54
83
  */
55
- type Affordances = {
84
+ type Affordances<S extends string = DefaultSlots> = {
56
85
  advice: string;
57
86
  decision: Decision;
58
- selections: UiSelections;
87
+ selections: UiSelections<S>;
59
88
  };
60
89
  /**
61
90
  * Handler function for a flow step.
@@ -84,20 +113,22 @@ interface FlowBuilder {
84
113
  }
85
114
  /**
86
115
  * Options for creating a Taias instance.
116
+ * Generic over slot type S for custom slot support.
87
117
  */
88
- type TaiasOptions = {
118
+ type TaiasOptions<S extends string = DefaultSlots> = {
89
119
  flow: FlowDefinition;
90
- affordances?: AffordanceRegistry;
91
- slotMatch?: SlotMatch;
120
+ affordances?: AffordanceRegistry<S>;
121
+ slotMatch?: SlotMatch<S>;
92
122
  devMode?: boolean;
93
123
  onMissingStep?: (ctx: TaiasContext) => void;
94
124
  onWarn?: (msg: string) => void;
95
125
  };
96
126
  /**
97
127
  * The Taias instance interface.
128
+ * Generic over slot type S for custom slot support.
98
129
  */
99
- interface Taias {
100
- resolve(ctx: TaiasContext): Affordances | null | Promise<Affordances | null>;
130
+ interface Taias<S extends string = DefaultSlots> {
131
+ resolve(ctx: TaiasContext): Affordances<S> | null | Promise<Affordances<S> | null>;
101
132
  }
102
133
 
103
134
  /**
@@ -135,21 +166,73 @@ declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void
135
166
  * Inputs → Decision → Manifestations
136
167
  *
137
168
  * are unified into a single resolve() call.
169
+ *
170
+ * @example Default slots (backwards compatible)
171
+ * ```ts
172
+ * const taias = createTaias({ flow, affordances });
173
+ * ```
174
+ *
175
+ * @example Custom slots (fully type-safe)
176
+ * ```ts
177
+ * type MySlots = "primaryCta" | "contentArea" | "headerStyle";
178
+ * const taias = createTaias<MySlots>({
179
+ * flow,
180
+ * affordances,
181
+ * slotMatch: { contentArea: "contentArea", headerStyle: "headerStyle" },
182
+ * });
183
+ * ```
138
184
  */
139
- declare function createTaias(options: TaiasOptions): Taias;
185
+ declare function createTaias<S extends string = DefaultSlots>(options: TaiasOptions<S>): Taias<S>;
140
186
 
141
- type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;
142
- interface AffordanceRegistrar {
143
- primaryCta: RegistrarFn;
144
- secondaryCta: RegistrarFn;
145
- widgetVariant: RegistrarFn;
146
- }
147
- declare function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry;
187
+ /**
188
+ * Mapped type that creates a registration method for each slot in S.
189
+ * This enables fully typed custom slots via generics.
190
+ */
191
+ type AffordanceRegistrar<S extends string = DefaultSlots> = {
192
+ [K in S]: (handleId: string, bindsTo: BindingInput) => void;
193
+ };
194
+ /**
195
+ * Define UI affordances for a widget using a builder pattern.
196
+ *
197
+ * @example Default slots (backwards compatible)
198
+ * ```ts
199
+ * const affordances = defineAffordances((r) => {
200
+ * r.primaryCta("cta.recommend", { toolName: "get_recommendations" });
201
+ * r.widgetVariant("variant.discovery", { toolName: "get_recommendations" });
202
+ * });
203
+ * ```
204
+ *
205
+ * @example Custom slots (fully type-safe)
206
+ * ```ts
207
+ * type MySlots = "primaryCta" | "contentArea" | "headerStyle";
208
+ * const affordances = defineAffordances<MySlots>((r) => {
209
+ * r.primaryCta("cta.create", { toolName: "createUser" });
210
+ * r.contentArea("content.form", { key: "contentArea", value: "email-form" });
211
+ * r.headerStyle("header.progress", { key: "headerStyle", value: "step-1" });
212
+ * });
213
+ * ```
214
+ */
215
+ declare function defineAffordances<S extends string = DefaultSlots>(builder: (r: AffordanceRegistrar<S>) => void): AffordanceRegistry<S>;
148
216
 
149
217
  type MergeAffordancesOptions = {
150
218
  devMode?: boolean;
151
219
  onWarn?: (msg: string) => void;
152
220
  };
153
- declare function mergeAffordances(registries: AffordanceRegistry[], opts?: MergeAffordancesOptions): AffordanceRegistry;
221
+ /**
222
+ * Merge multiple affordance registries into one.
223
+ * Generic over slot type S for custom slot support.
224
+ *
225
+ * @example Default slots
226
+ * ```ts
227
+ * const merged = mergeAffordances([widgetA, widgetB]);
228
+ * ```
229
+ *
230
+ * @example Custom slots
231
+ * ```ts
232
+ * type MySlots = "primaryCta" | "contentArea";
233
+ * const merged = mergeAffordances<MySlots>([widgetA, widgetB]);
234
+ * ```
235
+ */
236
+ declare function mergeAffordances<S extends string = DefaultSlots>(registries: AffordanceRegistry<S>[], opts?: MergeAffordancesOptions): AffordanceRegistry<S>;
154
237
 
155
- export { type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Decision, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type Selection, type SlotMatch, type StepDecision, type StepHandler, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
238
+ export { type AffordanceRegistrar, type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Decision, type DefaultSlots, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type Selection, type SlotMatch, type StepDecision, type StepHandler, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
package/dist/index.js CHANGED
@@ -25,11 +25,13 @@ function makeBindingKey(slot, binding) {
25
25
  // src/uiAffordances/indexing.ts
26
26
  function buildRegistryIndex(registry) {
27
27
  const byBindingKey = /* @__PURE__ */ new Map();
28
- if (!registry) return { byBindingKey };
28
+ const slots = /* @__PURE__ */ new Set();
29
+ if (!registry) return { byBindingKey, slots };
29
30
  for (const h of registry.handles) {
31
+ slots.add(h.slot);
30
32
  byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);
31
33
  }
32
- return { byBindingKey };
34
+ return { byBindingKey, slots };
33
35
  }
34
36
 
35
37
  // src/uiAffordances/select.ts
@@ -39,14 +41,13 @@ var DEFAULT_SLOT_MATCH = {
39
41
  widgetVariant: "nextTool"
40
42
  };
41
43
  function selectUiAffordances(decision, index, opts = {}) {
42
- const slotMatch = { ...DEFAULT_SLOT_MATCH, ...opts.slotMatch ?? {} };
44
+ const slotMatch = opts.slotMatch;
43
45
  const devMode = !!opts.devMode;
44
46
  const warn = opts.onWarn ?? (() => {
45
47
  });
46
48
  const selections = {};
47
- const slots = Object.keys(DEFAULT_SLOT_MATCH);
48
- for (const slot of slots) {
49
- const field = slotMatch[slot];
49
+ for (const slot of index.slots) {
50
+ const field = slotMatch?.[slot] ?? DEFAULT_SLOT_MATCH[slot] ?? "nextTool";
50
51
  const value = decision[field];
51
52
  if (!value) continue;
52
53
  const k = makeBindingKey(slot, { key: field, value });
@@ -55,7 +56,10 @@ function selectUiAffordances(decision, index, opts = {}) {
55
56
  if (devMode) warn(`[Taias] No affordance for slot "${slot}" when ${field}="${value}"`);
56
57
  continue;
57
58
  }
58
- selections[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };
59
+ selections[slot] = {
60
+ handleId: handle.handleId,
61
+ bindsTo: handle.bindsTo
62
+ };
59
63
  }
60
64
  return selections;
61
65
  }
@@ -99,9 +103,7 @@ function createTaias(options) {
99
103
  if (devMode && result.nextTool === "") {
100
104
  warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);
101
105
  }
102
- const decision = {
103
- nextTool: result.nextTool
104
- };
106
+ const decision = { ...result };
105
107
  const selections = selectUiAffordances(decision, registryIndex, {
106
108
  devMode,
107
109
  onWarn: warn,
@@ -119,18 +121,17 @@ function createTaias(options) {
119
121
  // src/uiAffordances/defineAffordances.ts
120
122
  function defineAffordances(builder) {
121
123
  const handles = [];
122
- const push = (slot, handleId, bindsTo) => {
123
- handles.push({
124
- slot,
125
- handleId,
126
- bindsTo: normalizeBinding(bindsTo)
127
- });
128
- };
129
- const registrar = {
130
- primaryCta: (handleId, bindsTo) => push("primaryCta", handleId, bindsTo),
131
- secondaryCta: (handleId, bindsTo) => push("secondaryCta", handleId, bindsTo),
132
- widgetVariant: (handleId, bindsTo) => push("widgetVariant", handleId, bindsTo)
133
- };
124
+ const registrar = new Proxy({}, {
125
+ get(_, slot) {
126
+ return (handleId, bindsTo) => {
127
+ handles.push({
128
+ slot,
129
+ handleId,
130
+ bindsTo: normalizeBinding(bindsTo)
131
+ });
132
+ };
133
+ }
134
+ });
134
135
  builder(registrar);
135
136
  return { handles };
136
137
  }
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, StepHandler } 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\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step(\"scan_repo\", (ctx) => ({\n * nextTool: \"configure_app\",\n * }));\n * });\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(toolName: string, handler: StepHandler): void {\n steps.push({ toolName, handler });\n },\n };\n\n builder(flowBuilder);\n\n return {\n id: flowId,\n steps,\n };\n}\n\n","export type CanonicalSlot = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\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\nexport type HandleRegistration = {\n slot: CanonicalSlot;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type UiSelections = Partial<Record<CanonicalSlot, Selection>>;\n\nexport type AffordanceRegistry = {\n handles: HandleRegistration[];\n};\n\nexport type SlotMatch = Partial<Record<CanonicalSlot, string>>;\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\nexport function makeBindingKey(slot: CanonicalSlot, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type RegistryIndex = {\n byBindingKey: Map<string, HandleRegistration>;\n};\n\nexport function buildRegistryIndex(registry?: AffordanceRegistry): RegistryIndex {\n const byBindingKey = new Map<string, HandleRegistration>();\n\n if (!registry) return { byBindingKey };\n\n for (const h of registry.handles) {\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey };\n}\n","import type { Decision } from \"../types\";\nimport type { CanonicalSlot, SlotMatch, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nconst DEFAULT_SLOT_MATCH: Required<Record<CanonicalSlot, string>> = {\n primaryCta: \"nextTool\",\n secondaryCta: \"nextTool\",\n widgetVariant: \"nextTool\",\n};\n\nexport type SelectOptions = {\n slotMatch?: SlotMatch;\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function selectUiAffordances(\n decision: Decision,\n index: RegistryIndex,\n opts: SelectOptions = {}\n): UiSelections {\n const slotMatch = { ...DEFAULT_SLOT_MATCH, ...(opts.slotMatch ?? {}) };\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections = {};\n const slots = Object.keys(DEFAULT_SLOT_MATCH) as CanonicalSlot[];\n\n for (const slot of slots) {\n const field = slotMatch[slot];\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[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };\n }\n\n return selections;\n}\n","import type { Affordances, Taias, TaiasContext, TaiasOptions, Decision } from \"./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 * createTaias constructs a decision engine.\n *\n * Taias resolves tool 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 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 */\nexport function createTaias(options: TaiasOptions): Taias {\n const {\n flow,\n affordances,\n slotMatch,\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 toolNames\n if (devMode) {\n const seenTools = new Set<string>();\n for (const step of flow.steps) {\n if (seenTools.has(step.toolName)) {\n throw new Error(\n `Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances | null> {\n const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n onMissingStep?.(ctx);\n return null;\n }\n\n const result = await handler(ctx);\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\n const decision: Decision = {\n nextTool: result.nextTool,\n };\n\n // Compute UI selections (may be empty if no registry passed)\n const selections = selectUiAffordances(decision, registryIndex, {\n devMode,\n onWarn: warn,\n slotMatch,\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 CanonicalSlot,\n HandleRegistration,\n } from \"./types\";\n import { normalizeBinding } from \"./types\";\n \n type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;\n \n export interface AffordanceRegistrar {\n primaryCta: RegistrarFn;\n secondaryCta: RegistrarFn;\n widgetVariant: RegistrarFn;\n }\n \n export function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry {\n const handles: HandleRegistration[] = [];\n \n const push = (slot: CanonicalSlot, handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n \n const registrar: AffordanceRegistrar = {\n primaryCta: (handleId, bindsTo) => push(\"primaryCta\", handleId, bindsTo),\n secondaryCta: (handleId, bindsTo) => push(\"secondaryCta\", handleId, bindsTo),\n widgetVariant: (handleId, bindsTo) => push(\"widgetVariant\", handleId, bindsTo),\n };\n \n builder(registrar);\n return { handles };\n }\n ","import type { AffordanceRegistry } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function mergeAffordances(\n registries: AffordanceRegistry[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry = { 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, StepHandler } 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\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step(\"scan_repo\", (ctx) => ({\n * nextTool: \"configure_app\",\n * }));\n * });\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(toolName: string, handler: StepHandler): void {\n steps.push({ toolName, handler });\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\n/**\n * Mapping of slots to decision fields.\n * Generic over slot type S for custom slot support.\n */\nexport type SlotMatch<S extends string = DefaultSlots> = Partial<Record<S, string>>;\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};\n\n/**\n * Build an index from a registry for efficient lookup during selection.\n * Tracks which slots have registered handles.\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\n if (!registry) return { byBindingKey, slots };\n\n for (const h of registry.handles) {\n slots.add(h.slot);\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey, slots };\n}\n","import type { Decision } from \"../types\";\nimport type { DefaultSlots, SlotMatch, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\n/**\n * Default slot-to-field mappings for the canonical slots.\n * Custom slots default to \"nextTool\" if not specified in slotMatch.\n */\nconst DEFAULT_SLOT_MATCH: Record<DefaultSlots, string> = {\n primaryCta: \"nextTool\",\n secondaryCta: \"nextTool\",\n widgetVariant: \"nextTool\",\n};\n\nexport type SelectOptions<S extends string = DefaultSlots> = {\n slotMatch?: SlotMatch<S>;\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\n/**\n * Select UI affordances based on flow decision.\n * Iterates over registered slots (from index) rather than hardcoded list.\n */\nexport function selectUiAffordances<S extends string = DefaultSlots>(\n decision: Decision,\n index: RegistryIndex<S>,\n opts: SelectOptions<S> = {}\n): UiSelections<S> {\n const slotMatch = opts.slotMatch as Record<string, string> | undefined;\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections<S> = {};\n\n // Iterate over registered slots, not hardcoded list\n for (const slot of index.slots) {\n // Use custom slotMatch, then DEFAULT_SLOT_MATCH for canonical slots, then \"nextTool\"\n const field =\n slotMatch?.[slot] ??\n DEFAULT_SLOT_MATCH[slot as DefaultSlots] ??\n \"nextTool\";\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, 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 * createTaias constructs a decision engine.\n *\n * Taias resolves tool 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 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 taias = createTaias<MySlots>({\n * flow,\n * affordances,\n * slotMatch: { contentArea: \"contentArea\", headerStyle: \"headerStyle\" },\n * });\n * ```\n */\nexport function createTaias<S extends string = DefaultSlots>(\n options: TaiasOptions<S>\n): Taias<S> {\n const {\n flow,\n affordances,\n slotMatch,\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 toolNames\n if (devMode) {\n const seenTools = new Set<string>();\n for (const step of flow.steps) {\n if (seenTools.has(step.toolName)) {\n throw new Error(\n `Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));\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 const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n onMissingStep?.(ctx);\n return null;\n }\n\n const result = await handler(ctx);\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 slotMatch,\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.2.1",
3
+ "version": "0.4.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",