taias 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -52
- package/dist/index.cjs +126 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -14
- package/dist/index.d.ts +73 -14
- package/dist/index.js +123 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,59 +128,10 @@ const affordances = await taias.resolve({ toolName: "scan_repo" });
|
|
|
128
128
|
- `ctx.toolName` - The name of the tool being called
|
|
129
129
|
|
|
130
130
|
**Returns:** `Affordances | null`
|
|
131
|
-
- Returns `
|
|
131
|
+
- Returns an `Affordances` object with `advice` (and more) if a matching step is found
|
|
132
132
|
- Returns `null` if no step matches or handler returns null
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<details>
|
|
137
|
-
<summary>View all types</summary>
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
type TaiasContext = {
|
|
141
|
-
toolName: string;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
type StepDecision = {
|
|
145
|
-
nextTool: string;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
type Affordances = {
|
|
149
|
-
advice: string;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
type StepHandler = (
|
|
153
|
-
ctx: TaiasContext
|
|
154
|
-
) => StepDecision | null | Promise<StepDecision | null>;
|
|
155
|
-
|
|
156
|
-
type FlowStep = {
|
|
157
|
-
toolName: string;
|
|
158
|
-
handler: StepHandler;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
type FlowDefinition = {
|
|
162
|
-
id: string;
|
|
163
|
-
steps: Array<FlowStep>;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
interface FlowBuilder {
|
|
167
|
-
step(toolName: string, handler: StepHandler): void;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
type TaiasOptions = {
|
|
171
|
-
flow: FlowDefinition;
|
|
172
|
-
devMode?: boolean;
|
|
173
|
-
onMissingStep?: (ctx: TaiasContext) => void;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
interface Taias {
|
|
177
|
-
resolve(ctx: TaiasContext): Affordances | null | Promise<Affordances | null>;
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
</details>
|
|
182
|
-
|
|
183
|
-
See the [full documentation](https://taias.xyz/docs) for more details.
|
|
134
|
+
See the [full documentation](https://taias.xyz/docs) for complete API reference and types.
|
|
184
135
|
|
|
185
136
|
## Dev Mode
|
|
186
137
|
|
|
@@ -191,7 +142,7 @@ When `devMode: true`, Taias performs additional validation:
|
|
|
191
142
|
|
|
192
143
|
1. **Duplicate toolName detection** — Throws an error if a flow defines the same tool name twice:
|
|
193
144
|
```
|
|
194
|
-
Taias: Duplicate step for tool 'scan_repo' in flow 'onboard_repo'.
|
|
145
|
+
Taias: Duplicate step for tool 'scan_repo' in flow 'onboard_repo'. Only one handler per tool is supported.
|
|
195
146
|
```
|
|
196
147
|
|
|
197
148
|
2. **Empty nextTool warning** — Logs a warning if a handler returns empty nextTool:
|
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
createTaias: () => createTaias,
|
|
24
|
-
|
|
24
|
+
defineAffordances: () => defineAffordances,
|
|
25
|
+
defineFlow: () => defineFlow,
|
|
26
|
+
mergeAffordances: () => mergeAffordances
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(index_exports);
|
|
27
29
|
|
|
@@ -40,51 +42,160 @@ function defineFlow(flowId, builder) {
|
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
// src/uiAffordances/types.ts
|
|
46
|
+
function normalizeBinding(input) {
|
|
47
|
+
if ("toolName" in input) return { key: "nextTool", value: input.toolName };
|
|
48
|
+
return { key: input.key, value: input.value };
|
|
49
|
+
}
|
|
50
|
+
function makeBindingKey(slot, binding) {
|
|
51
|
+
return `${slot}::${binding.key}::${binding.value}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/uiAffordances/indexing.ts
|
|
55
|
+
function buildRegistryIndex(registry) {
|
|
56
|
+
const byBindingKey = /* @__PURE__ */ new Map();
|
|
57
|
+
if (!registry) return { byBindingKey };
|
|
58
|
+
for (const h of registry.handles) {
|
|
59
|
+
byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);
|
|
60
|
+
}
|
|
61
|
+
return { byBindingKey };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/uiAffordances/select.ts
|
|
65
|
+
var DEFAULT_SLOT_MATCH = {
|
|
66
|
+
primaryCta: "nextTool",
|
|
67
|
+
secondaryCta: "nextTool",
|
|
68
|
+
widgetVariant: "nextTool"
|
|
69
|
+
};
|
|
70
|
+
function selectUiAffordances(decision, index, opts = {}) {
|
|
71
|
+
const slotMatch = { ...DEFAULT_SLOT_MATCH, ...opts.slotMatch ?? {} };
|
|
72
|
+
const devMode = !!opts.devMode;
|
|
73
|
+
const warn = opts.onWarn ?? (() => {
|
|
74
|
+
});
|
|
75
|
+
const selections = {};
|
|
76
|
+
const slots = Object.keys(DEFAULT_SLOT_MATCH);
|
|
77
|
+
for (const slot of slots) {
|
|
78
|
+
const field = slotMatch[slot];
|
|
79
|
+
const value = decision[field];
|
|
80
|
+
if (!value) continue;
|
|
81
|
+
const k = makeBindingKey(slot, { key: field, value });
|
|
82
|
+
const handle = index.byBindingKey.get(k);
|
|
83
|
+
if (!handle) {
|
|
84
|
+
if (devMode) warn(`[Taias] No affordance for slot "${slot}" when ${field}="${value}"`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
selections[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };
|
|
88
|
+
}
|
|
89
|
+
return selections;
|
|
90
|
+
}
|
|
91
|
+
|
|
43
92
|
// src/createTaias.ts
|
|
44
93
|
function generateAdvice(nextTool) {
|
|
45
94
|
return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;
|
|
46
95
|
}
|
|
47
96
|
function createTaias(options) {
|
|
48
|
-
const {
|
|
97
|
+
const {
|
|
98
|
+
flow,
|
|
99
|
+
affordances,
|
|
100
|
+
slotMatch,
|
|
101
|
+
devMode = false,
|
|
102
|
+
onMissingStep,
|
|
103
|
+
onWarn
|
|
104
|
+
} = options;
|
|
105
|
+
const warn = onWarn ?? ((msg) => console.warn(msg));
|
|
49
106
|
if (devMode) {
|
|
50
107
|
const seenTools = /* @__PURE__ */ new Set();
|
|
51
108
|
for (const step of flow.steps) {
|
|
52
109
|
if (seenTools.has(step.toolName)) {
|
|
53
110
|
throw new Error(
|
|
54
|
-
`Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'.
|
|
111
|
+
`Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`
|
|
55
112
|
);
|
|
56
113
|
}
|
|
57
114
|
seenTools.add(step.toolName);
|
|
58
115
|
}
|
|
59
116
|
}
|
|
60
|
-
const stepMap = new Map(
|
|
61
|
-
|
|
62
|
-
);
|
|
117
|
+
const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));
|
|
118
|
+
const registryIndex = buildRegistryIndex(affordances);
|
|
63
119
|
return {
|
|
64
120
|
async resolve(ctx) {
|
|
65
121
|
const handler = stepMap.get(ctx.toolName);
|
|
66
122
|
if (!handler) {
|
|
67
|
-
|
|
68
|
-
onMissingStep(ctx);
|
|
69
|
-
}
|
|
123
|
+
onMissingStep?.(ctx);
|
|
70
124
|
return null;
|
|
71
125
|
}
|
|
72
126
|
const result = await handler(ctx);
|
|
73
|
-
if (!result)
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
127
|
+
if (!result) return null;
|
|
76
128
|
if (devMode && result.nextTool === "") {
|
|
77
|
-
|
|
129
|
+
warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);
|
|
78
130
|
}
|
|
131
|
+
const decision = {
|
|
132
|
+
nextTool: result.nextTool
|
|
133
|
+
};
|
|
134
|
+
const selections = selectUiAffordances(decision, registryIndex, {
|
|
135
|
+
devMode,
|
|
136
|
+
onWarn: warn,
|
|
137
|
+
slotMatch
|
|
138
|
+
});
|
|
79
139
|
return {
|
|
80
|
-
advice: generateAdvice(result.nextTool)
|
|
140
|
+
advice: generateAdvice(result.nextTool),
|
|
141
|
+
decision,
|
|
142
|
+
selections
|
|
81
143
|
};
|
|
82
144
|
}
|
|
83
145
|
};
|
|
84
146
|
}
|
|
147
|
+
|
|
148
|
+
// src/uiAffordances/defineAffordances.ts
|
|
149
|
+
function defineAffordances(builder) {
|
|
150
|
+
const handles = [];
|
|
151
|
+
const push = (slot, handleId, bindsTo) => {
|
|
152
|
+
handles.push({
|
|
153
|
+
slot,
|
|
154
|
+
handleId,
|
|
155
|
+
bindsTo: normalizeBinding(bindsTo)
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
const registrar = {
|
|
159
|
+
primaryCta: (handleId, bindsTo) => push("primaryCta", handleId, bindsTo),
|
|
160
|
+
secondaryCta: (handleId, bindsTo) => push("secondaryCta", handleId, bindsTo),
|
|
161
|
+
widgetVariant: (handleId, bindsTo) => push("widgetVariant", handleId, bindsTo)
|
|
162
|
+
};
|
|
163
|
+
builder(registrar);
|
|
164
|
+
return { handles };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/uiAffordances/mergeAffordances.ts
|
|
168
|
+
function mergeAffordances(registries, opts = {}) {
|
|
169
|
+
const devMode = !!opts.devMode;
|
|
170
|
+
const warn = opts.onWarn ?? (() => {
|
|
171
|
+
});
|
|
172
|
+
const merged = { handles: registries.flatMap((r) => r.handles) };
|
|
173
|
+
if (!devMode) return merged;
|
|
174
|
+
const seenHandleIds = /* @__PURE__ */ new Set();
|
|
175
|
+
for (const h of merged.handles) {
|
|
176
|
+
if (seenHandleIds.has(h.handleId)) {
|
|
177
|
+
throw new Error(`[Taias] Duplicate handleId "${h.handleId}"`);
|
|
178
|
+
}
|
|
179
|
+
seenHandleIds.add(h.handleId);
|
|
180
|
+
}
|
|
181
|
+
const seenTriples = /* @__PURE__ */ new Set();
|
|
182
|
+
for (const h of merged.handles) {
|
|
183
|
+
const k = makeBindingKey(h.slot, h.bindsTo);
|
|
184
|
+
if (seenTriples.has(k)) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`[Taias] Ambiguous affordance: slot "${h.slot}" has multiple handles bound to (${h.bindsTo.key}="${h.bindsTo.value}")`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
seenTriples.add(k);
|
|
190
|
+
}
|
|
191
|
+
warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);
|
|
192
|
+
return merged;
|
|
193
|
+
}
|
|
85
194
|
// Annotate the CommonJS export names for ESM import in node:
|
|
86
195
|
0 && (module.exports = {
|
|
87
196
|
createTaias,
|
|
88
|
-
|
|
197
|
+
defineAffordances,
|
|
198
|
+
defineFlow,
|
|
199
|
+
mergeAffordances
|
|
89
200
|
});
|
|
90
201
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/flow.ts","../src/createTaias.ts"],"sourcesContent":["// Main exports\nexport { defineFlow } from \"./flow\";\nexport { createTaias } from \"./createTaias\";\n\n// Type exports\nexport type {\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","import type { Affordances, Taias, TaiasContext, TaiasOptions } from \"./types\";\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 * Create a Taias instance from a single flow.\n *\n * @param options - Configuration options\n * @returns A Taias instance with a resolve method\n *\n * @example\n * ```ts\n * const taias = createTaias({\n * flow: onboardRepoFlow,\n * devMode: process.env.NODE_ENV !== \"production\",\n * });\n *\n * const decision = await taias.resolve({ toolName: \"scan_repo\" });\n * // decision.advice → \"FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE configure_app TOOL NEXT!!!!!\"\n * ```\n */\nexport function createTaias(options: TaiasOptions): Taias {\n const { flow, devMode = false, onMissingStep } = options;\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}'. V1 supports one handler per tool.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(\n flow.steps.map((step) => [step.toolName, step.handler])\n );\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances | null> {\n const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n if (onMissingStep) {\n onMissingStep(ctx);\n }\n return null;\n }\n\n const result = await handler(ctx);\n\n if (!result) {\n return null;\n }\n\n // Dev mode: Warn if nextTool is empty\n if (devMode && result.nextTool === \"\") {\n console.warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n // Auto-generate the advice text\n return {\n advice: generateAdvice(result.nextTool),\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACkBO,SAAS,WACd,QACA,SACgB;AAChB,QAAM,QAAoB,CAAC;AAE3B,QAAM,cAA2B;AAAA,IAC/B,KAAK,UAAkB,SAA4B;AACjD,YAAM,KAAK,EAAE,UAAU,QAAQ,CAAC;AAAA,IAClC;AAAA,EACF;AAEA,UAAQ,WAAW;AAEnB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ;AAAA,EACF;AACF;;;AC/BA,SAAS,eAAe,UAA0B;AAChD,SAAO,0DAA0D,QAAQ;AAC3E;AAmBO,SAAS,YAAY,SAA8B;AACxD,QAAM,EAAE,MAAM,UAAU,OAAO,cAAc,IAAI;AAGjD,MAAI,SAAS;AACX,UAAM,YAAY,oBAAI,IAAY;AAClC,eAAW,QAAQ,KAAK,OAAO;AAC7B,UAAI,UAAU,IAAI,KAAK,QAAQ,GAAG;AAChC,cAAM,IAAI;AAAA,UACR,mCAAmC,KAAK,QAAQ,cAAc,KAAK,EAAE;AAAA,QACvE;AAAA,MACF;AACA,gBAAU,IAAI,KAAK,QAAQ;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,UAAU,IAAI;AAAA,IAClB,KAAK,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,UAAU,KAAK,OAAO,CAAC;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,KAAgD;AAC5D,YAAM,UAAU,QAAQ,IAAI,IAAI,QAAQ;AAExC,UAAI,CAAC,SAAS;AACZ,YAAI,eAAe;AACjB,wBAAc,GAAG;AAAA,QACnB;AACA,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,MAAM,QAAQ,GAAG;AAEhC,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,OAAO,aAAa,IAAI;AACrC,gBAAQ,KAAK,6BAA6B,IAAI,QAAQ,aAAa;AAAA,MACrE;AAGA,aAAO;AAAA,QACL,QAAQ,eAAe,OAAO,QAAQ;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;","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 {\n CanonicalSlot,\n Binding,\n BindingInput,\n HandleRegistration,\n Selection,\n UiSelections,\n AffordanceRegistry,\n SlotMatch,\n} from \"./uiAffordances/types\";\n\n// Core + flow type exports\nexport type {\n Decision, // moved to core\n TaiasContext,\n StepDecision,\n Affordances,\n StepHandler,\n FlowStep,\n FlowDefinition,\n FlowBuilder,\n TaiasOptions,\n Taias,\n} from \"./types\";\n","import type { FlowBuilder, FlowDefinition, FlowStep, StepHandler } from \"./types\";\n\n/**\n * Define a flow with its steps.\n *\n * @param flowId - Unique identifier for the flow\n * @param builder - Callback that receives a FlowBuilder to define steps\n * @returns A FlowDefinition object\n *\n * @example\n * ```ts\n * const onboardRepoFlow = defineFlow(\"onboard_repo\", (flow) => {\n * flow.step(\"scan_repo\", (ctx) => ({\n * nextTool: \"configure_app\",\n * }));\n * });\n * ```\n */\nexport function defineFlow(\n flowId: string,\n builder: (flow: FlowBuilder) => void\n): FlowDefinition {\n const steps: FlowStep[] = [];\n\n const flowBuilder: FlowBuilder = {\n step(toolName: string, handler: StepHandler): void {\n steps.push({ toolName, handler });\n },\n };\n\n builder(flowBuilder);\n\n return {\n id: flowId,\n steps,\n };\n}\n\n","export type CanonicalSlot = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\n\nexport type Binding = {\n key: string;\n value: string;\n};\n\n/**\n * Input format for registering affordance bindings.\n * - { toolName } is shorthand for { key: \"nextTool\", value: toolName }\n * - { key, value } is the generalized form for custom bindings\n */\nexport type BindingInput = { toolName: string } | { key: string; value: string };\n\nexport type HandleRegistration = {\n slot: CanonicalSlot;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type UiSelections = Partial<Record<CanonicalSlot, Selection>>;\n\nexport type AffordanceRegistry = {\n handles: HandleRegistration[];\n};\n\nexport type SlotMatch = Partial<Record<CanonicalSlot, string>>;\n\nexport function normalizeBinding(input: BindingInput): Binding {\n if (\"toolName\" in input) return { key: \"nextTool\", value: input.toolName };\n return { key: input.key, value: input.value };\n}\n\nexport function makeBindingKey(slot: CanonicalSlot, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type RegistryIndex = {\n byBindingKey: Map<string, HandleRegistration>;\n};\n\nexport function buildRegistryIndex(registry?: AffordanceRegistry): RegistryIndex {\n const byBindingKey = new Map<string, HandleRegistration>();\n\n if (!registry) return { byBindingKey };\n\n for (const h of registry.handles) {\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey };\n}\n","import type { Decision } from \"../types\";\nimport type { CanonicalSlot, SlotMatch, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nconst DEFAULT_SLOT_MATCH: Required<Record<CanonicalSlot, string>> = {\n primaryCta: \"nextTool\",\n secondaryCta: \"nextTool\",\n widgetVariant: \"nextTool\",\n};\n\nexport type SelectOptions = {\n slotMatch?: SlotMatch;\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function selectUiAffordances(\n decision: Decision,\n index: RegistryIndex,\n opts: SelectOptions = {}\n): UiSelections {\n const slotMatch = { ...DEFAULT_SLOT_MATCH, ...(opts.slotMatch ?? {}) };\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections = {};\n const slots = Object.keys(DEFAULT_SLOT_MATCH) as CanonicalSlot[];\n\n for (const slot of slots) {\n const field = slotMatch[slot];\n const value = decision[field];\n if (!value) continue;\n\n const k = makeBindingKey(slot, { key: field, value });\n const handle = index.byBindingKey.get(k);\n\n if (!handle) {\n if (devMode) warn(`[Taias] No affordance for slot \"${slot}\" when ${field}=\"${value}\"`);\n continue;\n }\n\n selections[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };\n }\n\n return selections;\n}\n","import type { Affordances, Taias, TaiasContext, TaiasOptions, Decision } from \"./types\";\nimport { buildRegistryIndex } from \"./uiAffordances/indexing\";\nimport { selectUiAffordances } from \"./uiAffordances/select\";\n\n/**\n * Generate advice text for a given next tool.\n */\nfunction generateAdvice(nextTool: string): string {\n return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;\n}\n\n/**\n * createTaias constructs a decision engine.\n *\n * Taias resolves tool context into a generalized Decision object,\n * and then manifests that decision into concrete affordances:\n *\n * - LLM guidance (advice)\n * - UI affordance selections\n *\n * Flow logic determines *what should happen next*.\n * UI affordances determine *how that decision appears in the interface*.\n *\n * This file is the boundary where:\n *\n * Inputs → Decision → Manifestations\n *\n * are unified into a single resolve() call.\n */\nexport function createTaias(options: TaiasOptions): Taias {\n const {\n flow,\n affordances,\n slotMatch,\n devMode = false,\n onMissingStep,\n onWarn,\n } = options;\n\n const warn = onWarn ?? ((msg: string) => console.warn(msg));\n\n // Dev mode: Check for duplicate toolNames\n if (devMode) {\n const seenTools = new Set<string>();\n for (const step of flow.steps) {\n if (seenTools.has(step.toolName)) {\n throw new Error(\n `Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances | null> {\n const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n onMissingStep?.(ctx);\n return null;\n }\n\n const result = await handler(ctx);\n if (!result) return null;\n\n if (devMode && result.nextTool === \"\") {\n warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n // Build decision object from flow result\n const decision: Decision = {\n nextTool: result.nextTool,\n };\n\n // Compute UI selections (may be empty if no registry passed)\n const selections = selectUiAffordances(decision, registryIndex, {\n devMode,\n onWarn: warn,\n slotMatch,\n });\n\n return {\n advice: generateAdvice(result.nextTool),\n decision,\n selections,\n };\n },\n };\n}\n","import type {\n AffordanceRegistry,\n BindingInput,\n CanonicalSlot,\n HandleRegistration,\n } from \"./types\";\n import { normalizeBinding } from \"./types\";\n \n type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;\n \n export interface AffordanceRegistrar {\n primaryCta: RegistrarFn;\n secondaryCta: RegistrarFn;\n widgetVariant: RegistrarFn;\n }\n \n export function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry {\n const handles: HandleRegistration[] = [];\n \n const push = (slot: CanonicalSlot, handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n \n const registrar: AffordanceRegistrar = {\n primaryCta: (handleId, bindsTo) => push(\"primaryCta\", handleId, bindsTo),\n secondaryCta: (handleId, bindsTo) => push(\"secondaryCta\", handleId, bindsTo),\n widgetVariant: (handleId, bindsTo) => push(\"widgetVariant\", handleId, bindsTo),\n };\n \n builder(registrar);\n return { handles };\n }\n ","import type { AffordanceRegistry } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function mergeAffordances(\n registries: AffordanceRegistry[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry = { handles: registries.flatMap((r) => r.handles) };\n\n if (!devMode) return merged;\n\n // Check for duplicate handleIds\n const seenHandleIds = new Set<string>();\n for (const h of merged.handles) {\n if (seenHandleIds.has(h.handleId)) {\n throw new Error(`[Taias] Duplicate handleId \"${h.handleId}\"`);\n }\n seenHandleIds.add(h.handleId);\n }\n\n // Check for ambiguous bindings (same slot + key + value)\n const seenTriples = new Set<string>();\n for (const h of merged.handles) {\n const k = makeBindingKey(h.slot, h.bindsTo);\n if (seenTriples.has(k)) {\n throw new Error(\n `[Taias] Ambiguous affordance: slot \"${h.slot}\" has multiple handles bound to (${h.bindsTo.key}=\"${h.bindsTo.value}\")`\n );\n }\n seenTriples.add(k);\n }\n\n warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);\n return merged;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
type CanonicalSlot = "primaryCta" | "secondaryCta" | "widgetVariant";
|
|
2
|
+
type Binding = {
|
|
3
|
+
key: string;
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Input format for registering affordance bindings.
|
|
8
|
+
* - { toolName } is shorthand for { key: "nextTool", value: toolName }
|
|
9
|
+
* - { key, value } is the generalized form for custom bindings
|
|
10
|
+
*/
|
|
11
|
+
type BindingInput = {
|
|
12
|
+
toolName: string;
|
|
13
|
+
} | {
|
|
14
|
+
key: string;
|
|
15
|
+
value: string;
|
|
16
|
+
};
|
|
17
|
+
type HandleRegistration = {
|
|
18
|
+
slot: CanonicalSlot;
|
|
19
|
+
handleId: string;
|
|
20
|
+
bindsTo: Binding;
|
|
21
|
+
};
|
|
22
|
+
type Selection = {
|
|
23
|
+
handleId: string;
|
|
24
|
+
bindsTo: Binding;
|
|
25
|
+
};
|
|
26
|
+
type UiSelections = Partial<Record<CanonicalSlot, Selection>>;
|
|
27
|
+
type AffordanceRegistry = {
|
|
28
|
+
handles: HandleRegistration[];
|
|
29
|
+
};
|
|
30
|
+
type SlotMatch = Partial<Record<CanonicalSlot, string>>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generalized decision object.
|
|
34
|
+
* Currently uses decision.nextTool. The structure supports additional keys.
|
|
35
|
+
*/
|
|
36
|
+
type Decision = Record<string, string | undefined>;
|
|
1
37
|
/**
|
|
2
38
|
* Context passed to a step handler.
|
|
3
39
|
*/
|
|
@@ -11,10 +47,15 @@ type StepDecision = {
|
|
|
11
47
|
nextTool: string;
|
|
12
48
|
};
|
|
13
49
|
/**
|
|
14
|
-
* Affordances returned by resolve()
|
|
50
|
+
* Affordances returned by resolve():
|
|
51
|
+
* - advice: LLM guidance text
|
|
52
|
+
* - decision: generalized decision object (currently includes nextTool)
|
|
53
|
+
* - selections: UI affordance selections (may be empty)
|
|
15
54
|
*/
|
|
16
55
|
type Affordances = {
|
|
17
56
|
advice: string;
|
|
57
|
+
decision: Decision;
|
|
58
|
+
selections: UiSelections;
|
|
18
59
|
};
|
|
19
60
|
/**
|
|
20
61
|
* Handler function for a flow step.
|
|
@@ -46,8 +87,11 @@ interface FlowBuilder {
|
|
|
46
87
|
*/
|
|
47
88
|
type TaiasOptions = {
|
|
48
89
|
flow: FlowDefinition;
|
|
90
|
+
affordances?: AffordanceRegistry;
|
|
91
|
+
slotMatch?: SlotMatch;
|
|
49
92
|
devMode?: boolean;
|
|
50
93
|
onMissingStep?: (ctx: TaiasContext) => void;
|
|
94
|
+
onWarn?: (msg: string) => void;
|
|
51
95
|
};
|
|
52
96
|
/**
|
|
53
97
|
* The Taias instance interface.
|
|
@@ -75,22 +119,37 @@ interface Taias {
|
|
|
75
119
|
declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void): FlowDefinition;
|
|
76
120
|
|
|
77
121
|
/**
|
|
78
|
-
*
|
|
122
|
+
* createTaias constructs a decision engine.
|
|
79
123
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
124
|
+
* Taias resolves tool context into a generalized Decision object,
|
|
125
|
+
* and then manifests that decision into concrete affordances:
|
|
82
126
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* const taias = createTaias({
|
|
86
|
-
* flow: onboardRepoFlow,
|
|
87
|
-
* devMode: process.env.NODE_ENV !== "production",
|
|
88
|
-
* });
|
|
127
|
+
* - LLM guidance (advice)
|
|
128
|
+
* - UI affordance selections
|
|
89
129
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
130
|
+
* Flow logic determines *what should happen next*.
|
|
131
|
+
* UI affordances determine *how that decision appears in the interface*.
|
|
132
|
+
*
|
|
133
|
+
* This file is the boundary where:
|
|
134
|
+
*
|
|
135
|
+
* Inputs → Decision → Manifestations
|
|
136
|
+
*
|
|
137
|
+
* are unified into a single resolve() call.
|
|
93
138
|
*/
|
|
94
139
|
declare function createTaias(options: TaiasOptions): Taias;
|
|
95
140
|
|
|
96
|
-
|
|
141
|
+
type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;
|
|
142
|
+
interface AffordanceRegistrar {
|
|
143
|
+
primaryCta: RegistrarFn;
|
|
144
|
+
secondaryCta: RegistrarFn;
|
|
145
|
+
widgetVariant: RegistrarFn;
|
|
146
|
+
}
|
|
147
|
+
declare function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry;
|
|
148
|
+
|
|
149
|
+
type MergeAffordancesOptions = {
|
|
150
|
+
devMode?: boolean;
|
|
151
|
+
onWarn?: (msg: string) => void;
|
|
152
|
+
};
|
|
153
|
+
declare function mergeAffordances(registries: AffordanceRegistry[], opts?: MergeAffordancesOptions): AffordanceRegistry;
|
|
154
|
+
|
|
155
|
+
export { type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Decision, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type Selection, type SlotMatch, type StepDecision, type StepHandler, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
type CanonicalSlot = "primaryCta" | "secondaryCta" | "widgetVariant";
|
|
2
|
+
type Binding = {
|
|
3
|
+
key: string;
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Input format for registering affordance bindings.
|
|
8
|
+
* - { toolName } is shorthand for { key: "nextTool", value: toolName }
|
|
9
|
+
* - { key, value } is the generalized form for custom bindings
|
|
10
|
+
*/
|
|
11
|
+
type BindingInput = {
|
|
12
|
+
toolName: string;
|
|
13
|
+
} | {
|
|
14
|
+
key: string;
|
|
15
|
+
value: string;
|
|
16
|
+
};
|
|
17
|
+
type HandleRegistration = {
|
|
18
|
+
slot: CanonicalSlot;
|
|
19
|
+
handleId: string;
|
|
20
|
+
bindsTo: Binding;
|
|
21
|
+
};
|
|
22
|
+
type Selection = {
|
|
23
|
+
handleId: string;
|
|
24
|
+
bindsTo: Binding;
|
|
25
|
+
};
|
|
26
|
+
type UiSelections = Partial<Record<CanonicalSlot, Selection>>;
|
|
27
|
+
type AffordanceRegistry = {
|
|
28
|
+
handles: HandleRegistration[];
|
|
29
|
+
};
|
|
30
|
+
type SlotMatch = Partial<Record<CanonicalSlot, string>>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generalized decision object.
|
|
34
|
+
* Currently uses decision.nextTool. The structure supports additional keys.
|
|
35
|
+
*/
|
|
36
|
+
type Decision = Record<string, string | undefined>;
|
|
1
37
|
/**
|
|
2
38
|
* Context passed to a step handler.
|
|
3
39
|
*/
|
|
@@ -11,10 +47,15 @@ type StepDecision = {
|
|
|
11
47
|
nextTool: string;
|
|
12
48
|
};
|
|
13
49
|
/**
|
|
14
|
-
* Affordances returned by resolve()
|
|
50
|
+
* Affordances returned by resolve():
|
|
51
|
+
* - advice: LLM guidance text
|
|
52
|
+
* - decision: generalized decision object (currently includes nextTool)
|
|
53
|
+
* - selections: UI affordance selections (may be empty)
|
|
15
54
|
*/
|
|
16
55
|
type Affordances = {
|
|
17
56
|
advice: string;
|
|
57
|
+
decision: Decision;
|
|
58
|
+
selections: UiSelections;
|
|
18
59
|
};
|
|
19
60
|
/**
|
|
20
61
|
* Handler function for a flow step.
|
|
@@ -46,8 +87,11 @@ interface FlowBuilder {
|
|
|
46
87
|
*/
|
|
47
88
|
type TaiasOptions = {
|
|
48
89
|
flow: FlowDefinition;
|
|
90
|
+
affordances?: AffordanceRegistry;
|
|
91
|
+
slotMatch?: SlotMatch;
|
|
49
92
|
devMode?: boolean;
|
|
50
93
|
onMissingStep?: (ctx: TaiasContext) => void;
|
|
94
|
+
onWarn?: (msg: string) => void;
|
|
51
95
|
};
|
|
52
96
|
/**
|
|
53
97
|
* The Taias instance interface.
|
|
@@ -75,22 +119,37 @@ interface Taias {
|
|
|
75
119
|
declare function defineFlow(flowId: string, builder: (flow: FlowBuilder) => void): FlowDefinition;
|
|
76
120
|
|
|
77
121
|
/**
|
|
78
|
-
*
|
|
122
|
+
* createTaias constructs a decision engine.
|
|
79
123
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
124
|
+
* Taias resolves tool context into a generalized Decision object,
|
|
125
|
+
* and then manifests that decision into concrete affordances:
|
|
82
126
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* const taias = createTaias({
|
|
86
|
-
* flow: onboardRepoFlow,
|
|
87
|
-
* devMode: process.env.NODE_ENV !== "production",
|
|
88
|
-
* });
|
|
127
|
+
* - LLM guidance (advice)
|
|
128
|
+
* - UI affordance selections
|
|
89
129
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
130
|
+
* Flow logic determines *what should happen next*.
|
|
131
|
+
* UI affordances determine *how that decision appears in the interface*.
|
|
132
|
+
*
|
|
133
|
+
* This file is the boundary where:
|
|
134
|
+
*
|
|
135
|
+
* Inputs → Decision → Manifestations
|
|
136
|
+
*
|
|
137
|
+
* are unified into a single resolve() call.
|
|
93
138
|
*/
|
|
94
139
|
declare function createTaias(options: TaiasOptions): Taias;
|
|
95
140
|
|
|
96
|
-
|
|
141
|
+
type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;
|
|
142
|
+
interface AffordanceRegistrar {
|
|
143
|
+
primaryCta: RegistrarFn;
|
|
144
|
+
secondaryCta: RegistrarFn;
|
|
145
|
+
widgetVariant: RegistrarFn;
|
|
146
|
+
}
|
|
147
|
+
declare function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry;
|
|
148
|
+
|
|
149
|
+
type MergeAffordancesOptions = {
|
|
150
|
+
devMode?: boolean;
|
|
151
|
+
onWarn?: (msg: string) => void;
|
|
152
|
+
};
|
|
153
|
+
declare function mergeAffordances(registries: AffordanceRegistry[], opts?: MergeAffordancesOptions): AffordanceRegistry;
|
|
154
|
+
|
|
155
|
+
export { type AffordanceRegistry, type Affordances, type Binding, type BindingInput, type CanonicalSlot, type Decision, type FlowBuilder, type FlowDefinition, type FlowStep, type HandleRegistration, type Selection, type SlotMatch, type StepDecision, type StepHandler, type Taias, type TaiasContext, type TaiasOptions, type UiSelections, createTaias, defineAffordances, defineFlow, mergeAffordances };
|
package/dist/index.js
CHANGED
|
@@ -13,50 +13,159 @@ function defineFlow(flowId, builder) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// src/uiAffordances/types.ts
|
|
17
|
+
function normalizeBinding(input) {
|
|
18
|
+
if ("toolName" in input) return { key: "nextTool", value: input.toolName };
|
|
19
|
+
return { key: input.key, value: input.value };
|
|
20
|
+
}
|
|
21
|
+
function makeBindingKey(slot, binding) {
|
|
22
|
+
return `${slot}::${binding.key}::${binding.value}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/uiAffordances/indexing.ts
|
|
26
|
+
function buildRegistryIndex(registry) {
|
|
27
|
+
const byBindingKey = /* @__PURE__ */ new Map();
|
|
28
|
+
if (!registry) return { byBindingKey };
|
|
29
|
+
for (const h of registry.handles) {
|
|
30
|
+
byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);
|
|
31
|
+
}
|
|
32
|
+
return { byBindingKey };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/uiAffordances/select.ts
|
|
36
|
+
var DEFAULT_SLOT_MATCH = {
|
|
37
|
+
primaryCta: "nextTool",
|
|
38
|
+
secondaryCta: "nextTool",
|
|
39
|
+
widgetVariant: "nextTool"
|
|
40
|
+
};
|
|
41
|
+
function selectUiAffordances(decision, index, opts = {}) {
|
|
42
|
+
const slotMatch = { ...DEFAULT_SLOT_MATCH, ...opts.slotMatch ?? {} };
|
|
43
|
+
const devMode = !!opts.devMode;
|
|
44
|
+
const warn = opts.onWarn ?? (() => {
|
|
45
|
+
});
|
|
46
|
+
const selections = {};
|
|
47
|
+
const slots = Object.keys(DEFAULT_SLOT_MATCH);
|
|
48
|
+
for (const slot of slots) {
|
|
49
|
+
const field = slotMatch[slot];
|
|
50
|
+
const value = decision[field];
|
|
51
|
+
if (!value) continue;
|
|
52
|
+
const k = makeBindingKey(slot, { key: field, value });
|
|
53
|
+
const handle = index.byBindingKey.get(k);
|
|
54
|
+
if (!handle) {
|
|
55
|
+
if (devMode) warn(`[Taias] No affordance for slot "${slot}" when ${field}="${value}"`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
selections[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };
|
|
59
|
+
}
|
|
60
|
+
return selections;
|
|
61
|
+
}
|
|
62
|
+
|
|
16
63
|
// src/createTaias.ts
|
|
17
64
|
function generateAdvice(nextTool) {
|
|
18
65
|
return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;
|
|
19
66
|
}
|
|
20
67
|
function createTaias(options) {
|
|
21
|
-
const {
|
|
68
|
+
const {
|
|
69
|
+
flow,
|
|
70
|
+
affordances,
|
|
71
|
+
slotMatch,
|
|
72
|
+
devMode = false,
|
|
73
|
+
onMissingStep,
|
|
74
|
+
onWarn
|
|
75
|
+
} = options;
|
|
76
|
+
const warn = onWarn ?? ((msg) => console.warn(msg));
|
|
22
77
|
if (devMode) {
|
|
23
78
|
const seenTools = /* @__PURE__ */ new Set();
|
|
24
79
|
for (const step of flow.steps) {
|
|
25
80
|
if (seenTools.has(step.toolName)) {
|
|
26
81
|
throw new Error(
|
|
27
|
-
`Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'.
|
|
82
|
+
`Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`
|
|
28
83
|
);
|
|
29
84
|
}
|
|
30
85
|
seenTools.add(step.toolName);
|
|
31
86
|
}
|
|
32
87
|
}
|
|
33
|
-
const stepMap = new Map(
|
|
34
|
-
|
|
35
|
-
);
|
|
88
|
+
const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));
|
|
89
|
+
const registryIndex = buildRegistryIndex(affordances);
|
|
36
90
|
return {
|
|
37
91
|
async resolve(ctx) {
|
|
38
92
|
const handler = stepMap.get(ctx.toolName);
|
|
39
93
|
if (!handler) {
|
|
40
|
-
|
|
41
|
-
onMissingStep(ctx);
|
|
42
|
-
}
|
|
94
|
+
onMissingStep?.(ctx);
|
|
43
95
|
return null;
|
|
44
96
|
}
|
|
45
97
|
const result = await handler(ctx);
|
|
46
|
-
if (!result)
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
98
|
+
if (!result) return null;
|
|
49
99
|
if (devMode && result.nextTool === "") {
|
|
50
|
-
|
|
100
|
+
warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);
|
|
51
101
|
}
|
|
102
|
+
const decision = {
|
|
103
|
+
nextTool: result.nextTool
|
|
104
|
+
};
|
|
105
|
+
const selections = selectUiAffordances(decision, registryIndex, {
|
|
106
|
+
devMode,
|
|
107
|
+
onWarn: warn,
|
|
108
|
+
slotMatch
|
|
109
|
+
});
|
|
52
110
|
return {
|
|
53
|
-
advice: generateAdvice(result.nextTool)
|
|
111
|
+
advice: generateAdvice(result.nextTool),
|
|
112
|
+
decision,
|
|
113
|
+
selections
|
|
54
114
|
};
|
|
55
115
|
}
|
|
56
116
|
};
|
|
57
117
|
}
|
|
118
|
+
|
|
119
|
+
// src/uiAffordances/defineAffordances.ts
|
|
120
|
+
function defineAffordances(builder) {
|
|
121
|
+
const handles = [];
|
|
122
|
+
const push = (slot, handleId, bindsTo) => {
|
|
123
|
+
handles.push({
|
|
124
|
+
slot,
|
|
125
|
+
handleId,
|
|
126
|
+
bindsTo: normalizeBinding(bindsTo)
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
const registrar = {
|
|
130
|
+
primaryCta: (handleId, bindsTo) => push("primaryCta", handleId, bindsTo),
|
|
131
|
+
secondaryCta: (handleId, bindsTo) => push("secondaryCta", handleId, bindsTo),
|
|
132
|
+
widgetVariant: (handleId, bindsTo) => push("widgetVariant", handleId, bindsTo)
|
|
133
|
+
};
|
|
134
|
+
builder(registrar);
|
|
135
|
+
return { handles };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/uiAffordances/mergeAffordances.ts
|
|
139
|
+
function mergeAffordances(registries, opts = {}) {
|
|
140
|
+
const devMode = !!opts.devMode;
|
|
141
|
+
const warn = opts.onWarn ?? (() => {
|
|
142
|
+
});
|
|
143
|
+
const merged = { handles: registries.flatMap((r) => r.handles) };
|
|
144
|
+
if (!devMode) return merged;
|
|
145
|
+
const seenHandleIds = /* @__PURE__ */ new Set();
|
|
146
|
+
for (const h of merged.handles) {
|
|
147
|
+
if (seenHandleIds.has(h.handleId)) {
|
|
148
|
+
throw new Error(`[Taias] Duplicate handleId "${h.handleId}"`);
|
|
149
|
+
}
|
|
150
|
+
seenHandleIds.add(h.handleId);
|
|
151
|
+
}
|
|
152
|
+
const seenTriples = /* @__PURE__ */ new Set();
|
|
153
|
+
for (const h of merged.handles) {
|
|
154
|
+
const k = makeBindingKey(h.slot, h.bindsTo);
|
|
155
|
+
if (seenTriples.has(k)) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`[Taias] Ambiguous affordance: slot "${h.slot}" has multiple handles bound to (${h.bindsTo.key}="${h.bindsTo.value}")`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
seenTriples.add(k);
|
|
161
|
+
}
|
|
162
|
+
warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);
|
|
163
|
+
return merged;
|
|
164
|
+
}
|
|
58
165
|
export {
|
|
59
166
|
createTaias,
|
|
60
|
-
|
|
167
|
+
defineAffordances,
|
|
168
|
+
defineFlow,
|
|
169
|
+
mergeAffordances
|
|
61
170
|
};
|
|
62
171
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/flow.ts","../src/createTaias.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","import type { Affordances, Taias, TaiasContext, TaiasOptions } from \"./types\";\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 * Create a Taias instance from a single flow.\n *\n * @param options - Configuration options\n * @returns A Taias instance with a resolve method\n *\n * @example\n * ```ts\n * const taias = createTaias({\n * flow: onboardRepoFlow,\n * devMode: process.env.NODE_ENV !== \"production\",\n * });\n *\n * const decision = await taias.resolve({ toolName: \"scan_repo\" });\n * // decision.advice → \"FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE configure_app TOOL NEXT!!!!!\"\n * ```\n */\nexport function createTaias(options: TaiasOptions): Taias {\n const { flow, devMode = false, onMissingStep } = options;\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}'. V1 supports one handler per tool.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(\n flow.steps.map((step) => [step.toolName, step.handler])\n );\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances | null> {\n const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n if (onMissingStep) {\n onMissingStep(ctx);\n }\n return null;\n }\n\n const result = await handler(ctx);\n\n if (!result) {\n return null;\n }\n\n // Dev mode: Warn if nextTool is empty\n if (devMode && result.nextTool === \"\") {\n console.warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n // Auto-generate the advice text\n return {\n advice: generateAdvice(result.nextTool),\n };\n },\n };\n}\n"],"mappings":";AAkBO,SAAS,WACd,QACA,SACgB;AAChB,QAAM,QAAoB,CAAC;AAE3B,QAAM,cAA2B;AAAA,IAC/B,KAAK,UAAkB,SAA4B;AACjD,YAAM,KAAK,EAAE,UAAU,QAAQ,CAAC;AAAA,IAClC;AAAA,EACF;AAEA,UAAQ,WAAW;AAEnB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ;AAAA,EACF;AACF;;;AC/BA,SAAS,eAAe,UAA0B;AAChD,SAAO,0DAA0D,QAAQ;AAC3E;AAmBO,SAAS,YAAY,SAA8B;AACxD,QAAM,EAAE,MAAM,UAAU,OAAO,cAAc,IAAI;AAGjD,MAAI,SAAS;AACX,UAAM,YAAY,oBAAI,IAAY;AAClC,eAAW,QAAQ,KAAK,OAAO;AAC7B,UAAI,UAAU,IAAI,KAAK,QAAQ,GAAG;AAChC,cAAM,IAAI;AAAA,UACR,mCAAmC,KAAK,QAAQ,cAAc,KAAK,EAAE;AAAA,QACvE;AAAA,MACF;AACA,gBAAU,IAAI,KAAK,QAAQ;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,UAAU,IAAI;AAAA,IAClB,KAAK,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,UAAU,KAAK,OAAO,CAAC;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,KAAgD;AAC5D,YAAM,UAAU,QAAQ,IAAI,IAAI,QAAQ;AAExC,UAAI,CAAC,SAAS;AACZ,YAAI,eAAe;AACjB,wBAAc,GAAG;AAAA,QACnB;AACA,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,MAAM,QAAQ,GAAG;AAEhC,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,OAAO,aAAa,IAAI;AACrC,gBAAQ,KAAK,6BAA6B,IAAI,QAAQ,aAAa;AAAA,MACrE;AAGA,aAAO;AAAA,QACL,QAAQ,eAAe,OAAO,QAAQ;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;","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","export type CanonicalSlot = \"primaryCta\" | \"secondaryCta\" | \"widgetVariant\";\n\nexport type Binding = {\n key: string;\n value: string;\n};\n\n/**\n * Input format for registering affordance bindings.\n * - { toolName } is shorthand for { key: \"nextTool\", value: toolName }\n * - { key, value } is the generalized form for custom bindings\n */\nexport type BindingInput = { toolName: string } | { key: string; value: string };\n\nexport type HandleRegistration = {\n slot: CanonicalSlot;\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type Selection = {\n handleId: string;\n bindsTo: Binding;\n};\n\nexport type UiSelections = Partial<Record<CanonicalSlot, Selection>>;\n\nexport type AffordanceRegistry = {\n handles: HandleRegistration[];\n};\n\nexport type SlotMatch = Partial<Record<CanonicalSlot, string>>;\n\nexport function normalizeBinding(input: BindingInput): Binding {\n if (\"toolName\" in input) return { key: \"nextTool\", value: input.toolName };\n return { key: input.key, value: input.value };\n}\n\nexport function makeBindingKey(slot: CanonicalSlot, binding: Binding): string {\n return `${slot}::${binding.key}::${binding.value}`;\n}\n","import type { AffordanceRegistry, HandleRegistration } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type RegistryIndex = {\n byBindingKey: Map<string, HandleRegistration>;\n};\n\nexport function buildRegistryIndex(registry?: AffordanceRegistry): RegistryIndex {\n const byBindingKey = new Map<string, HandleRegistration>();\n\n if (!registry) return { byBindingKey };\n\n for (const h of registry.handles) {\n byBindingKey.set(makeBindingKey(h.slot, h.bindsTo), h);\n }\n\n return { byBindingKey };\n}\n","import type { Decision } from \"../types\";\nimport type { CanonicalSlot, SlotMatch, UiSelections } from \"./types\";\nimport type { RegistryIndex } from \"./indexing\";\nimport { makeBindingKey } from \"./types\";\n\nconst DEFAULT_SLOT_MATCH: Required<Record<CanonicalSlot, string>> = {\n primaryCta: \"nextTool\",\n secondaryCta: \"nextTool\",\n widgetVariant: \"nextTool\",\n};\n\nexport type SelectOptions = {\n slotMatch?: SlotMatch;\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function selectUiAffordances(\n decision: Decision,\n index: RegistryIndex,\n opts: SelectOptions = {}\n): UiSelections {\n const slotMatch = { ...DEFAULT_SLOT_MATCH, ...(opts.slotMatch ?? {}) };\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const selections: UiSelections = {};\n const slots = Object.keys(DEFAULT_SLOT_MATCH) as CanonicalSlot[];\n\n for (const slot of slots) {\n const field = slotMatch[slot];\n const value = decision[field];\n if (!value) continue;\n\n const k = makeBindingKey(slot, { key: field, value });\n const handle = index.byBindingKey.get(k);\n\n if (!handle) {\n if (devMode) warn(`[Taias] No affordance for slot \"${slot}\" when ${field}=\"${value}\"`);\n continue;\n }\n\n selections[slot] = { handleId: handle.handleId, bindsTo: handle.bindsTo };\n }\n\n return selections;\n}\n","import type { Affordances, Taias, TaiasContext, TaiasOptions, Decision } from \"./types\";\nimport { buildRegistryIndex } from \"./uiAffordances/indexing\";\nimport { selectUiAffordances } from \"./uiAffordances/select\";\n\n/**\n * Generate advice text for a given next tool.\n */\nfunction generateAdvice(nextTool: string): string {\n return `FOR THE BEST USER EXPERIENCE, TELL THE USER TO USE THE ${nextTool} TOOL NEXT!!!!!`;\n}\n\n/**\n * createTaias constructs a decision engine.\n *\n * Taias resolves tool context into a generalized Decision object,\n * and then manifests that decision into concrete affordances:\n *\n * - LLM guidance (advice)\n * - UI affordance selections\n *\n * Flow logic determines *what should happen next*.\n * UI affordances determine *how that decision appears in the interface*.\n *\n * This file is the boundary where:\n *\n * Inputs → Decision → Manifestations\n *\n * are unified into a single resolve() call.\n */\nexport function createTaias(options: TaiasOptions): Taias {\n const {\n flow,\n affordances,\n slotMatch,\n devMode = false,\n onMissingStep,\n onWarn,\n } = options;\n\n const warn = onWarn ?? ((msg: string) => console.warn(msg));\n\n // Dev mode: Check for duplicate toolNames\n if (devMode) {\n const seenTools = new Set<string>();\n for (const step of flow.steps) {\n if (seenTools.has(step.toolName)) {\n throw new Error(\n `Taias: Duplicate step for tool '${step.toolName}' in flow '${flow.id}'. Only one handler per tool is supported.`\n );\n }\n seenTools.add(step.toolName);\n }\n }\n\n // Build a lookup map for efficient resolution\n const stepMap = new Map(flow.steps.map((step) => [step.toolName, step.handler]));\n\n // Build affordance index once (if provided)\n const registryIndex = buildRegistryIndex(affordances);\n\n return {\n async resolve(ctx: TaiasContext): Promise<Affordances | null> {\n const handler = stepMap.get(ctx.toolName);\n\n if (!handler) {\n onMissingStep?.(ctx);\n return null;\n }\n\n const result = await handler(ctx);\n if (!result) return null;\n\n if (devMode && result.nextTool === \"\") {\n warn(`Taias: nextTool for tool '${ctx.toolName}' is empty.`);\n }\n\n // Build decision object from flow result\n const decision: Decision = {\n nextTool: result.nextTool,\n };\n\n // Compute UI selections (may be empty if no registry passed)\n const selections = selectUiAffordances(decision, registryIndex, {\n devMode,\n onWarn: warn,\n slotMatch,\n });\n\n return {\n advice: generateAdvice(result.nextTool),\n decision,\n selections,\n };\n },\n };\n}\n","import type {\n AffordanceRegistry,\n BindingInput,\n CanonicalSlot,\n HandleRegistration,\n } from \"./types\";\n import { normalizeBinding } from \"./types\";\n \n type RegistrarFn = (handleId: string, bindsTo: BindingInput) => void;\n \n export interface AffordanceRegistrar {\n primaryCta: RegistrarFn;\n secondaryCta: RegistrarFn;\n widgetVariant: RegistrarFn;\n }\n \n export function defineAffordances(builder: (r: AffordanceRegistrar) => void): AffordanceRegistry {\n const handles: HandleRegistration[] = [];\n \n const push = (slot: CanonicalSlot, handleId: string, bindsTo: BindingInput) => {\n handles.push({\n slot,\n handleId,\n bindsTo: normalizeBinding(bindsTo),\n });\n };\n \n const registrar: AffordanceRegistrar = {\n primaryCta: (handleId, bindsTo) => push(\"primaryCta\", handleId, bindsTo),\n secondaryCta: (handleId, bindsTo) => push(\"secondaryCta\", handleId, bindsTo),\n widgetVariant: (handleId, bindsTo) => push(\"widgetVariant\", handleId, bindsTo),\n };\n \n builder(registrar);\n return { handles };\n }\n ","import type { AffordanceRegistry } from \"./types\";\nimport { makeBindingKey } from \"./types\";\n\nexport type MergeAffordancesOptions = {\n devMode?: boolean;\n onWarn?: (msg: string) => void;\n};\n\nexport function mergeAffordances(\n registries: AffordanceRegistry[],\n opts: MergeAffordancesOptions = {}\n): AffordanceRegistry {\n const devMode = !!opts.devMode;\n const warn = opts.onWarn ?? (() => {});\n\n const merged: AffordanceRegistry = { handles: registries.flatMap((r) => r.handles) };\n\n if (!devMode) return merged;\n\n // Check for duplicate handleIds\n const seenHandleIds = new Set<string>();\n for (const h of merged.handles) {\n if (seenHandleIds.has(h.handleId)) {\n throw new Error(`[Taias] Duplicate handleId \"${h.handleId}\"`);\n }\n seenHandleIds.add(h.handleId);\n }\n\n // Check for ambiguous bindings (same slot + key + value)\n const seenTriples = new Set<string>();\n for (const h of merged.handles) {\n const k = makeBindingKey(h.slot, h.bindsTo);\n if (seenTriples.has(k)) {\n throw new Error(\n `[Taias] Ambiguous affordance: slot \"${h.slot}\" has multiple handles bound to (${h.bindsTo.key}=\"${h.bindsTo.value}\")`\n );\n }\n seenTriples.add(k);\n }\n\n warn(`[Taias] Loaded ${merged.handles.length} UI affordance handles`);\n return merged;\n}\n"],"mappings":"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","names":[]}
|