@cms.ai/brand_brain 0.0.1 → 0.1.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.
@@ -0,0 +1,206 @@
1
+ import { GuideEvent, SkillResponseType } from "./types";
2
+ import type {
3
+ BusinessCaseDraftRef,
4
+ BusinessCaseFormatOption,
5
+ ContentRecommendation,
6
+ DiagnoseSubtypeName,
7
+ DiagnosticHeader,
8
+ DiagnosticQuestion,
9
+ GuideDocument,
10
+ GuideSkillEvent,
11
+ HowToOutline,
12
+ HowToStep,
13
+ PlaylistHeader,
14
+ PlaylistItem,
15
+ SkillCTA,
16
+ SkillRecommendation,
17
+ SkillResponseTypeType,
18
+ SolutionDesignEdge,
19
+ SolutionDesignHeader,
20
+ SolutionDesignNode,
21
+ } from "./types";
22
+
23
+ /**
24
+ * Renderable state for one skill turn, folded from the `skill` events of a
25
+ * single {@link GuideClient.ask} stream. Which fields fill in depends on
26
+ * `skill`; everything else stays at its empty default:
27
+ *
28
+ * - `answer`: `documents`, `followUpRecommendations`, `ctas`
29
+ * - `recommend`: `contentRecommendations`, `ctas`
30
+ * - `diagnose`: `diagnoseHeader`, `diagnoseQuestions`
31
+ * - `playlist`: `playlistHeader`, `playlistItems`
32
+ * - `howto`: `howToOutline` (render first), then `howToSteps`
33
+ * - `businesscase`: `businessCaseFormats`/`businessCaseDrafts` (chooser), then
34
+ * `businessCaseContent[subType]` markdown as a format streams
35
+ * - `solutiondesign`: `solutionDesignHeader`, `solutionDesignNodes`/`Edges`
36
+ */
37
+ export interface GuideSkillTurn {
38
+ /** Which skill this turn ran. Null until `skill_start` arrives. */
39
+ skill: SkillResponseTypeType | null;
40
+ /** `streaming` until `skill_end`. */
41
+ status: "streaming" | "complete";
42
+ /** The assistant message this skill output belongs to. */
43
+ messageId: string | null;
44
+ /** Diagnose mode — `context_gathering` runs `followUpSkill` after answers are submitted. */
45
+ diagnoseSubtype: DiagnoseSubtypeName | null;
46
+ followUpSkill: string | null;
47
+ /** Accumulated long-form markdown body (`skill_content` deltas). */
48
+ content: string;
49
+ /** Source documents behind the reply — render as citation/source cards. */
50
+ documents: GuideDocument[];
51
+ /** The recommend skill's content cards. */
52
+ contentRecommendations: ContentRecommendation[];
53
+ /** Post-answer follow-up pills — `ask(prompt, { forcedSkill: skill })` on click. */
54
+ followUpRecommendations: SkillRecommendation[];
55
+ /** Calls-to-action — render the first below the response. */
56
+ ctas: SkillCTA[];
57
+ diagnoseHeader: DiagnosticHeader | null;
58
+ diagnoseQuestions: DiagnosticQuestion[];
59
+ playlistHeader: PlaylistHeader | null;
60
+ playlistItems: PlaylistItem[];
61
+ howToOutline: HowToOutline | null;
62
+ howToSteps: HowToStep[];
63
+ businessCaseFormats: BusinessCaseFormatOption[];
64
+ businessCaseDrafts: Record<string, BusinessCaseDraftRef>;
65
+ /** Accumulated draft markdown per business case subType. */
66
+ businessCaseContent: Record<string, string>;
67
+ solutionDesignHeader: SolutionDesignHeader | null;
68
+ solutionDesignNodes: SolutionDesignNode[];
69
+ solutionDesignEdges: SolutionDesignEdge[];
70
+ /** Persisted deliverable id (for `client.skills.get`/`update`), set on `skill_end`. */
71
+ skillInstanceId: string | null;
72
+ }
73
+
74
+ /** A fresh, empty turn state. */
75
+ export function createSkillTurn(): GuideSkillTurn {
76
+ return {
77
+ skill: null,
78
+ status: "streaming",
79
+ messageId: null,
80
+ diagnoseSubtype: null,
81
+ followUpSkill: null,
82
+ content: "",
83
+ documents: [],
84
+ contentRecommendations: [],
85
+ followUpRecommendations: [],
86
+ ctas: [],
87
+ diagnoseHeader: null,
88
+ diagnoseQuestions: [],
89
+ playlistHeader: null,
90
+ playlistItems: [],
91
+ howToOutline: null,
92
+ howToSteps: [],
93
+ businessCaseFormats: [],
94
+ businessCaseDrafts: {},
95
+ businessCaseContent: {},
96
+ solutionDesignHeader: null,
97
+ solutionDesignNodes: [],
98
+ solutionDesignEdges: [],
99
+ skillInstanceId: null,
100
+ };
101
+ }
102
+
103
+ /** A recommendations payload is content cards iff its entries carry `targetSkill`. */
104
+ function isContentRecommendations(
105
+ recommendations: ContentRecommendation[] | SkillRecommendation[],
106
+ ): recommendations is ContentRecommendation[] {
107
+ return recommendations.length > 0 && "targetSkill" in recommendations[0];
108
+ }
109
+
110
+ /**
111
+ * Fold one `skill` stream event into the turn state. Pure and immutable — the
112
+ * returned object is a new reference whenever anything changed, so the result
113
+ * can back React state directly:
114
+ *
115
+ * ```ts
116
+ * let turn = createSkillTurn();
117
+ * for await (const event of client.ask(message)) {
118
+ * if (event.type === "skill") turn = reduceSkillEvent(turn, event);
119
+ * }
120
+ * ```
121
+ */
122
+ export function reduceSkillEvent(turn: GuideSkillTurn, event: GuideSkillEvent): GuideSkillTurn {
123
+ switch (event.name) {
124
+ case GuideEvent.SkillStart: {
125
+ // A business case turn continues across skill_start when the visitor picks
126
+ // a format from the chooser — preserve the chooser + accumulated drafts.
127
+ const continuesBusinessCase =
128
+ event.value.skill === SkillResponseType.BusinessCase && turn.skill === SkillResponseType.BusinessCase;
129
+ const base = continuesBusinessCase
130
+ ? {
131
+ ...createSkillTurn(),
132
+ businessCaseFormats: turn.businessCaseFormats,
133
+ businessCaseDrafts: turn.businessCaseDrafts,
134
+ businessCaseContent: turn.businessCaseContent,
135
+ }
136
+ : createSkillTurn();
137
+ return {
138
+ ...base,
139
+ skill: event.value.skill,
140
+ messageId: event.value.messageId,
141
+ diagnoseSubtype: event.value.diagnoseSubtype ?? null,
142
+ followUpSkill: event.value.followUpSkill ?? null,
143
+ };
144
+ }
145
+ case GuideEvent.SkillContent:
146
+ return { ...turn, content: turn.content + event.value.delta };
147
+ case GuideEvent.SkillDocuments:
148
+ return { ...turn, documents: event.value.documents };
149
+ case GuideEvent.SkillRecommendations:
150
+ return isContentRecommendations(event.value.recommendations)
151
+ ? { ...turn, contentRecommendations: event.value.recommendations }
152
+ : { ...turn, followUpRecommendations: event.value.recommendations };
153
+ case GuideEvent.SkillCTAs:
154
+ return { ...turn, ctas: event.value.ctas };
155
+ case GuideEvent.SkillDiagnoseHeader:
156
+ return { ...turn, diagnoseHeader: event.value.header };
157
+ case GuideEvent.SkillDiagnoseQuestion:
158
+ return { ...turn, diagnoseQuestions: [...turn.diagnoseQuestions, event.value.question] };
159
+ case GuideEvent.SkillPlaylistHeader:
160
+ return { ...turn, playlistHeader: event.value.header };
161
+ case GuideEvent.SkillPlaylistItems:
162
+ return { ...turn, playlistItems: event.value.items };
163
+ case GuideEvent.SkillPlaylistRationale:
164
+ return {
165
+ ...turn,
166
+ playlistItems: turn.playlistItems.map((item) =>
167
+ item.id === event.value.itemId ? { ...item, rationale: event.value.rationale } : item,
168
+ ),
169
+ };
170
+ case GuideEvent.SkillHowToOutline:
171
+ // The how-to instance is persisted at outline time, so the id can arrive
172
+ // here — long before skill_end — letting consumers save progress early.
173
+ return {
174
+ ...turn,
175
+ howToOutline: event.value.outline,
176
+ skillInstanceId: event.value.skillInstanceId ?? turn.skillInstanceId,
177
+ };
178
+ case GuideEvent.SkillHowToSteps:
179
+ return { ...turn, howToSteps: event.value.steps };
180
+ case GuideEvent.SkillBusinessCaseChooser:
181
+ return { ...turn, businessCaseFormats: event.value.formats, businessCaseDrafts: event.value.drafts };
182
+ case GuideEvent.SkillBusinessCaseDelta: {
183
+ const existing = turn.businessCaseContent[event.value.subType] ?? "";
184
+ return {
185
+ ...turn,
186
+ businessCaseContent: { ...turn.businessCaseContent, [event.value.subType]: existing + event.value.delta },
187
+ };
188
+ }
189
+ case GuideEvent.SkillSolutionDesignHeader:
190
+ return { ...turn, solutionDesignHeader: event.value.header };
191
+ case GuideEvent.SkillSolutionDesignDiagram:
192
+ return { ...turn, solutionDesignNodes: event.value.nodes, solutionDesignEdges: event.value.edges };
193
+ case GuideEvent.SkillEnd:
194
+ return {
195
+ ...turn,
196
+ status: "complete",
197
+ // skillResponseId is the wire's legacy alias for the same id.
198
+ skillInstanceId: event.value.skillInstanceId ?? event.value.skillResponseId ?? turn.skillInstanceId,
199
+ };
200
+ default: {
201
+ // Exhaustiveness guard: a new GuideSkillEvent variant must be handled here.
202
+ const _exhaustive: never = event;
203
+ return _exhaustive;
204
+ }
205
+ }
206
+ }