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 +22 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -26
- package/dist/index.d.ts +107 -26
- package/dist/index.js +22 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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 =
|
|
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
|
|
77
|
-
|
|
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] = {
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/flow.ts","../src/uiAffordances/types.ts","../src/uiAffordances/indexing.ts","../src/uiAffordances/select.ts","../src/createTaias.ts","../src/uiAffordances/defineAffordances.ts","../src/uiAffordances/mergeAffordances.ts"],"sourcesContent":["// Main exports\nexport { defineFlow } from \"./flow\";\nexport { createTaias } from \"./createTaias\";\n\n// UI affordances exports\nexport { defineAffordances } from \"./uiAffordances/defineAffordances\";\nexport { mergeAffordances } from \"./uiAffordances/mergeAffordances\";\nexport type {\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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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 (
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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 (
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
48
|
-
|
|
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] = {
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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":[]}
|