@guuey/chat 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/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/history-inputs.d.ts +24 -0
- package/dist/history-inputs.d.ts.map +1 -0
- package/dist/history-inputs.js +34 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/plan.d.ts +5 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +629 -0
- package/dist/policy.d.ts +110 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +75 -0
- package/dist/react/components.d.ts +87 -0
- package/dist/react/components.d.ts.map +1 -0
- package/dist/react/components.js +270 -0
- package/dist/react/guuey-chat.d.ts +84 -0
- package/dist/react/guuey-chat.d.ts.map +1 -0
- package/dist/react/guuey-chat.js +103 -0
- package/dist/react/markdown.d.ts +32 -0
- package/dist/react/markdown.d.ts.map +1 -0
- package/dist/react/markdown.js +40 -0
- package/dist/react/theme-css.d.ts +16 -0
- package/dist/react/theme-css.d.ts.map +1 -0
- package/dist/react/theme-css.js +37 -0
- package/dist/react/transcript.d.ts +42 -0
- package/dist/react/transcript.d.ts.map +1 -0
- package/dist/react/transcript.js +88 -0
- package/dist/react/use-transcript.d.ts +39 -0
- package/dist/react/use-transcript.d.ts.map +1 -0
- package/dist/react/use-transcript.js +201 -0
- package/dist/react.d.ts +21 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +20 -0
- package/dist/strings.d.ts +74 -0
- package/dist/strings.d.ts.map +1 -0
- package/dist/strings.js +45 -0
- package/dist/theme.d.ts +99 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +182 -0
- package/dist/types.d.ts +283 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +87 -0
- package/src/corpus/README.md +40 -0
- package/src/corpus/__snapshots__/corpus.test.ts.snap +1590 -0
- package/src/corpus/capture.ts +67 -0
- package/src/corpus/captures/issue2627-render-capture.coalesced.sse.txt +173 -0
- package/src/corpus/drive.ts +184 -0
- package/src/corpus/fixtures.ts +338 -0
- package/src/history-inputs.ts +48 -0
- package/src/index.ts +58 -0
- package/src/plan.ts +740 -0
- package/src/policy.ts +146 -0
- package/src/react/components.tsx +655 -0
- package/src/react/guuey-chat.tsx +227 -0
- package/src/react/markdown.tsx +114 -0
- package/src/react/theme-css.ts +50 -0
- package/src/react/transcript.tsx +187 -0
- package/src/react/use-transcript.ts +274 -0
- package/src/react.tsx +51 -0
- package/src/strings.ts +144 -0
- package/src/theme.ts +195 -0
- package/src/types.ts +320 -0
- package/styles.css +514 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixture corpus — the DEFINITION of "comfortably readable" (spec §8).
|
|
3
|
+
*
|
|
4
|
+
* Every fixture is a recorded `InvokeTurnEvent[]` sequence driven through
|
|
5
|
+
* the real `@silverprotocol/core` Reducer (see `drive.ts`); the corpus test
|
|
6
|
+
* asserts each fixture's spec-named property plus a full plan snapshot.
|
|
7
|
+
*
|
|
8
|
+
* THE STANDING RULE (see README.md): a new weird transcript found in
|
|
9
|
+
* production becomes a fixture BEFORE its fix lands. The corpus only grows.
|
|
10
|
+
*/
|
|
11
|
+
import type { AgEvent } from "@silverprotocol/core";
|
|
12
|
+
import type { InvokeTurnEvent } from "@guuey/agent-client";
|
|
13
|
+
import type { TranscriptInputs } from "../types.js";
|
|
14
|
+
import {
|
|
15
|
+
boot,
|
|
16
|
+
doneEvent,
|
|
17
|
+
driveTurn,
|
|
18
|
+
frame,
|
|
19
|
+
seqSource,
|
|
20
|
+
session,
|
|
21
|
+
textPart,
|
|
22
|
+
toolPart,
|
|
23
|
+
} from "./drive.js";
|
|
24
|
+
|
|
25
|
+
// The ggui render shapes below mirror mcp-apps-host's own capture-derived
|
|
26
|
+
// test fixtures (synthetic ids, production shape — ggui-render.test.ts).
|
|
27
|
+
const GGUI_RESOURCE_URI = "ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c0ffee";
|
|
28
|
+
const GGUI_META_KEY = "ai.ggui/render";
|
|
29
|
+
const GGUI_SLICE = {
|
|
30
|
+
sessionId: "render_00000000-0000-4000-8000-300000000001",
|
|
31
|
+
appId: "APP00000",
|
|
32
|
+
runtimeUrl: "https://dev.mcp.sandbox.ggui.ai/_ggui/iframe-runtime.js",
|
|
33
|
+
wsUrl: "wss://dev.mcp.sandbox.ggui.ai/ws",
|
|
34
|
+
wsToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
|
35
|
+
expiresAt: "2026-07-29T11:07:58.000Z",
|
|
36
|
+
lastSequence: 0,
|
|
37
|
+
propsJson: "{}",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** 1. forty-tools — grouping caps the visual rows; keys stable. */
|
|
41
|
+
export function fortyTools(): { full: TranscriptInputs; midStream: TranscriptInputs } {
|
|
42
|
+
const build = (count: number, settled: boolean): InvokeTurnEvent[] => {
|
|
43
|
+
const s = seqSource();
|
|
44
|
+
const events: InvokeTurnEvent[] = [session, frame(boot(s), { status: "thinking" })];
|
|
45
|
+
for (let i = 1; i <= count; i++) {
|
|
46
|
+
events.push(
|
|
47
|
+
frame(toolPart(s, `t${i}`, `step_${i}`), { status: "using-tool", activeTool: `step_${i}` }),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (settled) {
|
|
51
|
+
events.push(frame(textPart(s, "x", "All done."), { status: "responding", activeTool: null, text: "All done." }));
|
|
52
|
+
events.push(doneEvent);
|
|
53
|
+
}
|
|
54
|
+
return events;
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
full: driveTurn(build(40, true), { userText: "run the batch" }),
|
|
58
|
+
midStream: driveTurn(build(20, false), { userText: "run the batch" }),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 2. midstream-tool-failure — tool 3 of 5 fails; badge without unroll. */
|
|
63
|
+
export function midstreamToolFailure(): TranscriptInputs {
|
|
64
|
+
const s = seqSource();
|
|
65
|
+
const events: InvokeTurnEvent[] = [session, frame(boot(s), { status: "thinking" })];
|
|
66
|
+
for (let i = 1; i <= 5; i++) {
|
|
67
|
+
events.push(
|
|
68
|
+
frame(
|
|
69
|
+
toolPart(s, `t${i}`, `step_${i}`, i === 3 ? { outcome: "error", errorText: "boom" } : {}),
|
|
70
|
+
{ status: "using-tool", activeTool: `step_${i}` },
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
events.push(frame(textPart(s, "x", "Partial results in."), { status: "responding", activeTool: null, text: "Partial results in." }));
|
|
75
|
+
events.push(doneEvent);
|
|
76
|
+
return driveTurn(events, { userText: "sweep it" });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 3. cold-start — R12 escalation at 0 / 2.5 s / 15 s inputs. */
|
|
80
|
+
export function coldStart(elapsedMs: number): TranscriptInputs {
|
|
81
|
+
return driveTurn([], { userText: "hi", finalStatus: "connecting", statusElapsedMs: elapsedMs });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 4. consent-gate — R10 pending → answered collapse. */
|
|
85
|
+
export function consentGate(state: "pending" | "answered"): TranscriptInputs {
|
|
86
|
+
const s = seqSource();
|
|
87
|
+
const events: InvokeTurnEvent[] = [
|
|
88
|
+
session,
|
|
89
|
+
frame(boot(s), { status: "thinking" }),
|
|
90
|
+
{ kind: "profile-consent", request: { appId: "app-1", requested: "read" } },
|
|
91
|
+
frame(textPart(s, "x", "May I use your profile?"), { status: "responding", text: "May I use your profile?" }),
|
|
92
|
+
doneEvent,
|
|
93
|
+
];
|
|
94
|
+
return driveTurn(events, { userText: "book my usual", promptState: state });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 5. bypass-text-only — plan identical to a silver stream carrying only text. */
|
|
98
|
+
export function bypassVsSilver(): { bypass: TranscriptInputs; silver: TranscriptInputs } {
|
|
99
|
+
const bypassEvents: InvokeTurnEvent[] = [
|
|
100
|
+
session,
|
|
101
|
+
frame([], { status: "responding", text: "Hello!" }),
|
|
102
|
+
doneEvent,
|
|
103
|
+
];
|
|
104
|
+
const s = seqSource();
|
|
105
|
+
const silverEvents: InvokeTurnEvent[] = [
|
|
106
|
+
session,
|
|
107
|
+
frame(boot(s), { status: "thinking" }),
|
|
108
|
+
frame(textPart(s, "x", "Hello!"), { status: "responding", text: "Hello!" }),
|
|
109
|
+
doneEvent,
|
|
110
|
+
];
|
|
111
|
+
return {
|
|
112
|
+
bypass: driveTurn(bypassEvents, { userText: "hey" }),
|
|
113
|
+
silver: driveTurn(silverEvents, { userText: "hey" }),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** 6. giant-json-result — 2 MB result: capped, labeled, plan stays cheap. */
|
|
118
|
+
export function giantJsonResult(): TranscriptInputs {
|
|
119
|
+
const s = seqSource();
|
|
120
|
+
const giant = { blob: "x".repeat(2_000_000) };
|
|
121
|
+
const events: InvokeTurnEvent[] = [
|
|
122
|
+
session,
|
|
123
|
+
frame(boot(s), { status: "thinking" }),
|
|
124
|
+
frame(toolPart(s, "t1", "export_dataset", { structuredContent: giant }), {
|
|
125
|
+
status: "using-tool",
|
|
126
|
+
activeTool: "export_dataset",
|
|
127
|
+
}),
|
|
128
|
+
frame(textPart(s, "x", "Exported."), { status: "responding", activeTool: null, text: "Exported." }),
|
|
129
|
+
doneEvent,
|
|
130
|
+
];
|
|
131
|
+
return driveTurn(events, { userText: "export everything" });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 7. view-never-handshakes — no-handshake on BOTH channels, channel-aware labels. */
|
|
135
|
+
export function viewNeverHandshakes(): TranscriptInputs {
|
|
136
|
+
const s = seqSource();
|
|
137
|
+
const events: InvokeTurnEvent[] = [
|
|
138
|
+
session,
|
|
139
|
+
frame(boot(s), { status: "thinking" }),
|
|
140
|
+
frame(
|
|
141
|
+
toolPart(s, "tInline", "render_form", {
|
|
142
|
+
uiData: { uri: "ui://forms/1", mimeType: "text/html", text: "<p>form</p>" },
|
|
143
|
+
content: [],
|
|
144
|
+
}),
|
|
145
|
+
{ status: "using-tool", activeTool: "render_form" },
|
|
146
|
+
),
|
|
147
|
+
frame(
|
|
148
|
+
toolPart(s, "tGgui", "ggui_render", {
|
|
149
|
+
uiData: { sessionId: GGUI_SLICE.sessionId, resourceUri: GGUI_RESOURCE_URI, action: "create" },
|
|
150
|
+
content: [],
|
|
151
|
+
_meta: { [GGUI_META_KEY]: GGUI_SLICE },
|
|
152
|
+
}),
|
|
153
|
+
{ status: "using-tool", activeTool: "ggui_render" },
|
|
154
|
+
),
|
|
155
|
+
doneEvent,
|
|
156
|
+
];
|
|
157
|
+
return driveTurn(events, {
|
|
158
|
+
userText: "show me",
|
|
159
|
+
viewPhases: { "view.tInline": "no-handshake", "view.tGgui": "no-handshake" },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 8. aborted-mid-stream — partial kept + Stopped.; no orphaned spinner. */
|
|
164
|
+
export function abortedMidStream(): TranscriptInputs {
|
|
165
|
+
const s = seqSource();
|
|
166
|
+
const events: InvokeTurnEvent[] = [
|
|
167
|
+
session,
|
|
168
|
+
frame(boot(s), { status: "thinking" }),
|
|
169
|
+
frame(textPart(s, "x", "I was saying"), { status: "responding", text: "I was saying" }),
|
|
170
|
+
// The never-finished tool: start + args, no tool.done — then the abort.
|
|
171
|
+
frame(
|
|
172
|
+
[
|
|
173
|
+
{ type: "tool.start", toolCallId: "t9", name: "slow_tool", seq: s() },
|
|
174
|
+
{ type: "tool.args.assembled", toolCallId: "t9", input: {}, seq: s() },
|
|
175
|
+
],
|
|
176
|
+
{ status: "using-tool", activeTool: "slow_tool", text: "I was saying" },
|
|
177
|
+
),
|
|
178
|
+
];
|
|
179
|
+
return driveTurn(events, { userText: "go on", aborted: true });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** 9. history-dead-locators — one dead card beside a healthy locator card. */
|
|
183
|
+
export function historyDeadLocators(): TranscriptInputs {
|
|
184
|
+
const s = seqSource();
|
|
185
|
+
const events: InvokeTurnEvent[] = [
|
|
186
|
+
session,
|
|
187
|
+
frame(boot(s), { status: "thinking" }),
|
|
188
|
+
frame(textPart(s, "x", "Welcome back."), { status: "responding", text: "Welcome back." }),
|
|
189
|
+
doneEvent,
|
|
190
|
+
];
|
|
191
|
+
return driveTurn(events, {
|
|
192
|
+
userText: "hi again",
|
|
193
|
+
historyCards: [
|
|
194
|
+
{
|
|
195
|
+
seq: 1,
|
|
196
|
+
at: "2026-08-15T00:00:00Z",
|
|
197
|
+
cardSnapshot: {
|
|
198
|
+
parts: [
|
|
199
|
+
{ type: "tool-result", toolCallId: "c1", content: [], uiData: { resourceUri: "ui://ggui/render/old/1" } },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{ seq: 2, at: "2026-08-15T00:01:00Z", cardSnapshot: { note: "meta was stripped; no locator survived" } },
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 10. empty-turn — done with no text/blocks: no empty bubble. */
|
|
209
|
+
export function emptyTurn(): TranscriptInputs {
|
|
210
|
+
const s = seqSource();
|
|
211
|
+
return driveTurn([session, frame(boot(s), { status: "thinking" }), doneEvent], { userText: "ping" });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 11. unknown-block-storm — R15 labeled rows; nothing blank, nothing raw. */
|
|
215
|
+
export function unknownBlockStorm(): TranscriptInputs {
|
|
216
|
+
const s = seqSource();
|
|
217
|
+
const bootEvents = boot(s);
|
|
218
|
+
const storm: AgEvent[] = [0, 1, 2].map((i) => ({
|
|
219
|
+
type: "content.block",
|
|
220
|
+
block: { type: "provider-raw", vendor: "futurecorp", raw: { shape: i, payload: "??" } },
|
|
221
|
+
seq: s(),
|
|
222
|
+
}));
|
|
223
|
+
const events: InvokeTurnEvent[] = [
|
|
224
|
+
session,
|
|
225
|
+
frame(bootEvents, { status: "thinking" }),
|
|
226
|
+
frame(storm, { status: "responding" }),
|
|
227
|
+
doneEvent,
|
|
228
|
+
];
|
|
229
|
+
return driveTurn(events, { userText: "what is this" });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** 12. interleaved-media-code-citations — R7/R8/R9 ordering in one turn. */
|
|
233
|
+
export function interleavedMediaCodeCitations(): TranscriptInputs {
|
|
234
|
+
const s = seqSource();
|
|
235
|
+
const bootEvents = boot(s);
|
|
236
|
+
const blocks: AgEvent[] = [
|
|
237
|
+
{
|
|
238
|
+
type: "content.block",
|
|
239
|
+
block: { type: "image", source: { type: "url", url: "https://cdn.example/chart.png", mediaType: "image/png" } },
|
|
240
|
+
seq: s(),
|
|
241
|
+
},
|
|
242
|
+
{ type: "content.block", block: { type: "code", language: "python", code: "print('hi')" }, seq: s() },
|
|
243
|
+
{ type: "content.block", block: { type: "search-result", url: "https://a.example", title: "A" }, seq: s() },
|
|
244
|
+
{ type: "content.block", block: { type: "search-result", url: "https://b.example", title: "B" }, seq: s() },
|
|
245
|
+
];
|
|
246
|
+
const events: InvokeTurnEvent[] = [
|
|
247
|
+
session,
|
|
248
|
+
frame(bootEvents, { status: "thinking" }),
|
|
249
|
+
frame(blocks, { status: "responding" }),
|
|
250
|
+
frame(textPart(s, "x", "Sources above."), { status: "responding", text: "Sources above." }),
|
|
251
|
+
doneEvent,
|
|
252
|
+
];
|
|
253
|
+
return driveTurn(events, { userText: "chart + code + sources" });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** 13. saturated-then-served — the retry is invisible; only R12 waiting shows. */
|
|
257
|
+
export function saturatedThenServed(): { waiting: TranscriptInputs; served: TranscriptInputs } {
|
|
258
|
+
const s = seqSource();
|
|
259
|
+
const events: InvokeTurnEvent[] = [
|
|
260
|
+
session,
|
|
261
|
+
frame(boot(s), { status: "thinking" }),
|
|
262
|
+
frame(textPart(s, "x", "Served after the invisible retry."), { status: "responding", text: "Served after the invisible retry." }),
|
|
263
|
+
doneEvent,
|
|
264
|
+
];
|
|
265
|
+
return {
|
|
266
|
+
waiting: driveTurn([], { userText: "hi", finalStatus: "connecting", statusElapsedMs: 3000 }),
|
|
267
|
+
served: driveTurn(events, { userText: "hi" }),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** 14. reasoning-heavy — calm collapses to one line; debug expands. */
|
|
272
|
+
export function reasoningHeavy(): TranscriptInputs {
|
|
273
|
+
const s = seqSource();
|
|
274
|
+
const bootEvents = boot(s);
|
|
275
|
+
const reasoning: AgEvent[] = [
|
|
276
|
+
{ type: "reasoning.start", id: "r1", seq: s() },
|
|
277
|
+
{ type: "reasoning.delta", id: "r1", delta: "Considering all twelve constraints at length… ".repeat(20), seq: s() },
|
|
278
|
+
{ type: "reasoning.end", id: "r1", seq: s() },
|
|
279
|
+
];
|
|
280
|
+
const events: InvokeTurnEvent[] = [
|
|
281
|
+
session,
|
|
282
|
+
frame(bootEvents, { status: "thinking" }),
|
|
283
|
+
frame(reasoning, { status: "thinking" }),
|
|
284
|
+
frame(textPart(s, "x", "Thursday works."), { status: "responding", text: "Thursday works." }),
|
|
285
|
+
doneEvent,
|
|
286
|
+
];
|
|
287
|
+
return driveTurn(events, { userText: "which day?" });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 15. tools-around-a-view — the group SPLITS at the view's position. */
|
|
291
|
+
export function toolsAroundAView(): TranscriptInputs {
|
|
292
|
+
const s = seqSource();
|
|
293
|
+
const events: InvokeTurnEvent[] = [session, frame(boot(s), { status: "thinking" })];
|
|
294
|
+
for (const id of ["t1", "t2"]) {
|
|
295
|
+
events.push(frame(toolPart(s, id, `prep_${id}`), { status: "using-tool", activeTool: `prep_${id}` }));
|
|
296
|
+
}
|
|
297
|
+
events.push(
|
|
298
|
+
frame(
|
|
299
|
+
toolPart(s, "t3", "ggui_render", {
|
|
300
|
+
uiData: { uri: "ui://cards/slot-picker", mimeType: "text/html", text: "<div>picker</div>" },
|
|
301
|
+
content: [],
|
|
302
|
+
}),
|
|
303
|
+
{ status: "using-tool", activeTool: "ggui_render" },
|
|
304
|
+
),
|
|
305
|
+
);
|
|
306
|
+
for (const id of ["t4", "t5"]) {
|
|
307
|
+
events.push(frame(toolPart(s, id, `post_${id}`), { status: "using-tool", activeTool: `post_${id}` }));
|
|
308
|
+
}
|
|
309
|
+
events.push(doneEvent);
|
|
310
|
+
return driveTurn(events, { userText: "pick a slot" });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** 16. user-send-failure — R0 failed state with retry; never disappears. */
|
|
314
|
+
export function userSendFailure(): TranscriptInputs {
|
|
315
|
+
return driveTurn([], {
|
|
316
|
+
userText: "did this go through?",
|
|
317
|
+
clientMessageId: "cm-1",
|
|
318
|
+
finalStatus: "ready",
|
|
319
|
+
sendStates: { "cm-1": "failed" },
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** 17. stalled-then-adopted — #192: calm identical, debug carries the marker. */
|
|
324
|
+
export function stalledThenAdopted(): { adopted: TranscriptInputs; streamed: TranscriptInputs } {
|
|
325
|
+
const build = (): InvokeTurnEvent[] => {
|
|
326
|
+
const s = seqSource();
|
|
327
|
+
return [
|
|
328
|
+
session,
|
|
329
|
+
frame(boot(s), { status: "thinking" }),
|
|
330
|
+
frame(textPart(s, "x", "Booked for Thursday afternoon."), { status: "responding", text: "Booked for Thursday afternoon." }),
|
|
331
|
+
doneEvent,
|
|
332
|
+
];
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
adopted: driveTurn(build(), { userText: "move my booking", adopted: true }),
|
|
336
|
+
streamed: driveTurn(build(), { userText: "move my booking" }),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HISTORY input assembler (spec §7 "Input assembly") — persisted thread
|
|
3
|
+
* read → `TranscriptInputs`, no hook, no DOM, no React: the seam that makes
|
|
4
|
+
* server-side transcript rendering real. Give it what the read plane
|
|
5
|
+
* returned (an agent-client `HistoryLoadResult`, or `null` while the read is
|
|
6
|
+
* in flight) and plan the result with `planTranscript` anywhere Node runs.
|
|
7
|
+
*
|
|
8
|
+
* The LIVE twin (`useTranscriptInputs`, `@guuey/chat/react`) assembles the
|
|
9
|
+
* same shape from `useAgentInvoke`'s state; the two produce byte-identical
|
|
10
|
+
* inputs for the same settled conversation — that identity is what lets a
|
|
11
|
+
* server-rendered transcript hydrate under the live one without a repaint.
|
|
12
|
+
*/
|
|
13
|
+
import type { HistoryLoadResult } from "@guuey/agent-client";
|
|
14
|
+
import type { TranscriptInputs } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/** The settled-idle baseline every assembled input starts from. */
|
|
17
|
+
function idleInputs(): TranscriptInputs {
|
|
18
|
+
return {
|
|
19
|
+
result: null,
|
|
20
|
+
assistantText: "",
|
|
21
|
+
status: "ready",
|
|
22
|
+
statusElapsedMs: 0,
|
|
23
|
+
activeTool: null,
|
|
24
|
+
error: null,
|
|
25
|
+
prompts: [],
|
|
26
|
+
messages: [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Assemble transcript inputs from a history read.
|
|
32
|
+
*
|
|
33
|
+
* - `null` (read still in flight) → the R13 `loading` skeleton state;
|
|
34
|
+
* - `{ gone: true }` → the R13 `thread-gone` labeled empty state;
|
|
35
|
+
* - a transcript → the settled conversation, with any persisted cards
|
|
36
|
+
* riding the R13 → R6 remount path.
|
|
37
|
+
*/
|
|
38
|
+
export function transcriptInputsFromHistory(load: HistoryLoadResult | null): TranscriptInputs {
|
|
39
|
+
const base = idleInputs();
|
|
40
|
+
if (load === null) return { ...base, historyState: "loading" };
|
|
41
|
+
if ("gone" in load) return { ...base, historyState: "gone" };
|
|
42
|
+
return {
|
|
43
|
+
...base,
|
|
44
|
+
historyState: "loaded",
|
|
45
|
+
messages: load.messages,
|
|
46
|
+
...(load.cards && load.cards.length > 0 ? { historyCards: load.cards } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@guuey/chat` — the default end-user transcript UI for guuey agents.
|
|
3
|
+
*
|
|
4
|
+
* Wave 3a (guuey#135): the HEADLESS half — `planTranscript` (the pure
|
|
5
|
+
* view-model over the AgJSON fold + the invoke status surface), the
|
|
6
|
+
* `calm`/`debug` policy presets, the `GuueyChatTheme` platform-data token
|
|
7
|
+
* schema, and the `ChatStrings` i18n seam. Zero DOM, zero React.
|
|
8
|
+
*
|
|
9
|
+
* Wave 3b adds `./react` (the component kit + `<GuueyChat>` arrives in 3c);
|
|
10
|
+
* this root subpath stays React-free forever — server-side transcript
|
|
11
|
+
* rendering, tests, and RN bundlers consume the view-model directly.
|
|
12
|
+
*/
|
|
13
|
+
export {
|
|
14
|
+
planTranscript,
|
|
15
|
+
} from "./plan.js";
|
|
16
|
+
export {
|
|
17
|
+
calmPolicy,
|
|
18
|
+
debugPolicy,
|
|
19
|
+
type TranscriptPolicy,
|
|
20
|
+
} from "./policy.js";
|
|
21
|
+
export {
|
|
22
|
+
defaultChatStrings,
|
|
23
|
+
humanizeToolName,
|
|
24
|
+
type ChatStrings,
|
|
25
|
+
} from "./strings.js";
|
|
26
|
+
export { transcriptInputsFromHistory } from "./history-inputs.js";
|
|
27
|
+
export {
|
|
28
|
+
DEFAULT_CHAT_THEME,
|
|
29
|
+
GUUEY_CHAT_THEME,
|
|
30
|
+
GuueyChatPalette,
|
|
31
|
+
GuueyChatTheme,
|
|
32
|
+
resolveTheme,
|
|
33
|
+
} from "./theme.js";
|
|
34
|
+
export type {
|
|
35
|
+
CitationsItem,
|
|
36
|
+
CodeItem,
|
|
37
|
+
CompactionItem,
|
|
38
|
+
DataResultItem,
|
|
39
|
+
DisplayItem,
|
|
40
|
+
ErrorItem,
|
|
41
|
+
HistoryBoundaryItem,
|
|
42
|
+
ItemKey,
|
|
43
|
+
MediaItem,
|
|
44
|
+
PromptItem,
|
|
45
|
+
PromptItemInput,
|
|
46
|
+
ReasoningItem,
|
|
47
|
+
StatusLineItem,
|
|
48
|
+
TextItem,
|
|
49
|
+
ToolGroupItem,
|
|
50
|
+
ToolItem,
|
|
51
|
+
TranscriptInputs,
|
|
52
|
+
TranscriptMessage,
|
|
53
|
+
TranscriptOverrides,
|
|
54
|
+
TranscriptPlan,
|
|
55
|
+
UnknownItem,
|
|
56
|
+
UserMessageItem,
|
|
57
|
+
ViewMountItem,
|
|
58
|
+
} from "./types.js";
|