taias 0.3.0 → 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
  }
@@ -146,18 +150,17 @@ function createTaias(options) {
146
150
  // src/uiAffordances/defineAffordances.ts
147
151
  function defineAffordances(builder) {
148
152
  const handles = [];
149
- const push = (slot, handleId, bindsTo) => {
150
- handles.push({
151
- slot,
152
- handleId,
153
- bindsTo: normalizeBinding(bindsTo)
154
- });
155
- };
156
- const registrar = {
157
- primaryCta: (handleId, bindsTo) => push("primaryCta", handleId, bindsTo),
158
- secondaryCta: (handleId, bindsTo) => push("secondaryCta", handleId, bindsTo),
159
- widgetVariant: (handleId, bindsTo) => push("widgetVariant", handleId, bindsTo)
160
- };
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
+ });
161
164
  builder(registrar);
162
165
  return { handles };
163
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 (spread all fields)\n const decision: Decision = { ...result };\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":"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","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
  /**
@@ -51,13 +76,15 @@ type StepDecision = {
51
76
  /**
52
77
  * Affordances returned by resolve():
53
78
  * - advice: LLM guidance text
54
- * - decision: generalized decision object (currently includes nextTool)
79
+ * - decision: generalized decision object (contains nextTool + custom fields)
55
80
  * - selections: UI affordance selections (may be empty)
81
+ *
82
+ * Generic over slot type S for custom slot support.
56
83
  */
57
- type Affordances = {
84
+ type Affordances<S extends string = DefaultSlots> = {
58
85
  advice: string;
59
86
  decision: Decision;
60
- selections: UiSelections;
87
+ selections: UiSelections<S>;
61
88
  };
62
89
  /**
63
90
  * Handler function for a flow step.
@@ -86,20 +113,22 @@ interface FlowBuilder {
86
113
  }
87
114
  /**
88
115
  * Options for creating a Taias instance.
116
+ * Generic over slot type S for custom slot support.
89
117
  */
90
- type TaiasOptions = {
118
+ type TaiasOptions<S extends string = DefaultSlots> = {
91
119
  flow: FlowDefinition;
92
- affordances?: AffordanceRegistry;
93
- slotMatch?: SlotMatch;
120
+ affordances?: AffordanceRegistry<S>;
121
+ slotMatch?: SlotMatch<S>;
94
122
  devMode?: boolean;
95
123
  onMissingStep?: (ctx: TaiasContext) => void;
96
124
  onWarn?: (msg: string) => void;
97
125
  };
98
126
  /**
99
127
  * The Taias instance interface.
128
+ * Generic over slot type S for custom slot support.
100
129
  */
101
- interface Taias {
102
- 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>;
103
132
  }
104
133
 
105
134
  /**
@@ -137,21 +166,73 @@ declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void
137
166
  * Inputs → Decision → Manifestations
138
167
  *
139
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
+ * ```
140
184
  */
141
- declare function createTaias(options: TaiasOptions): Taias;
185
+ declare function createTaias<S extends string = DefaultSlots>(options: TaiasOptions<S>): Taias<S>;
142
186
 
143
- type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;
144
- interface AffordanceRegistrar {
145
- primaryCta: RegistrarFn;
146
- secondaryCta: RegistrarFn;
147
- widgetVariant: RegistrarFn;
148
- }
149
- 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>;
150
216
 
151
217
  type MergeAffordancesOptions = {
152
218
  devMode?: boolean;
153
219
  onWarn?: (msg: string) => void;
154
220
  };
155
- 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>;
156
237
 
157
- 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
  /**
@@ -51,13 +76,15 @@ type StepDecision = {
51
76
  /**
52
77
  * Affordances returned by resolve():
53
78
  * - advice: LLM guidance text
54
- * - decision: generalized decision object (currently includes nextTool)
79
+ * - decision: generalized decision object (contains nextTool + custom fields)
55
80
  * - selections: UI affordance selections (may be empty)
81
+ *
82
+ * Generic over slot type S for custom slot support.
56
83
  */
57
- type Affordances = {
84
+ type Affordances<S extends string = DefaultSlots> = {
58
85
  advice: string;
59
86
  decision: Decision;
60
- selections: UiSelections;
87
+ selections: UiSelections<S>;
61
88
  };
62
89
  /**
63
90
  * Handler function for a flow step.
@@ -86,20 +113,22 @@ interface FlowBuilder {
86
113
  }
87
114
  /**
88
115
  * Options for creating a Taias instance.
116
+ * Generic over slot type S for custom slot support.
89
117
  */
90
- type TaiasOptions = {
118
+ type TaiasOptions<S extends string = DefaultSlots> = {
91
119
  flow: FlowDefinition;
92
- affordances?: AffordanceRegistry;
93
- slotMatch?: SlotMatch;
120
+ affordances?: AffordanceRegistry<S>;
121
+ slotMatch?: SlotMatch<S>;
94
122
  devMode?: boolean;
95
123
  onMissingStep?: (ctx: TaiasContext) => void;
96
124
  onWarn?: (msg: string) => void;
97
125
  };
98
126
  /**
99
127
  * The Taias instance interface.
128
+ * Generic over slot type S for custom slot support.
100
129
  */
101
- interface Taias {
102
- 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>;
103
132
  }
104
133
 
105
134
  /**
@@ -137,21 +166,73 @@ declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void
137
166
  * Inputs → Decision → Manifestations
138
167
  *
139
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
+ * ```
140
184
  */
141
- declare function createTaias(options: TaiasOptions): Taias;
185
+ declare function createTaias<S extends string = DefaultSlots>(options: TaiasOptions<S>): Taias<S>;
142
186
 
143
- type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;
144
- interface AffordanceRegistrar {
145
- primaryCta: RegistrarFn;
146
- secondaryCta: RegistrarFn;
147
- widgetVariant: RegistrarFn;
148
- }
149
- 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>;
150
216
 
151
217
  type MergeAffordancesOptions = {
152
218
  devMode?: boolean;
153
219
  onWarn?: (msg: string) => void;
154
220
  };
155
- 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>;
156
237
 
157
- 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
  }
@@ -117,18 +121,17 @@ function createTaias(options) {
117
121
  // src/uiAffordances/defineAffordances.ts
118
122
  function defineAffordances(builder) {
119
123
  const handles = [];
120
- const push = (slot, handleId, bindsTo) => {
121
- handles.push({
122
- slot,
123
- handleId,
124
- bindsTo: normalizeBinding(bindsTo)
125
- });
126
- };
127
- const registrar = {
128
- primaryCta: (handleId, bindsTo) => push("primaryCta", handleId, bindsTo),
129
- secondaryCta: (handleId, bindsTo) => push("secondaryCta", handleId, bindsTo),
130
- widgetVariant: (handleId, bindsTo) => push("widgetVariant", handleId, bindsTo)
131
- };
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
+ });
132
135
  builder(registrar);
133
136
  return { handles };
134
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 (spread all fields)\n const decision: Decision = { ...result };\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.3.0",
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",