@flamingo-stack/openframe-frontend-core 0.0.532 → 0.0.533
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/chat-protocol/confirm-tool.d.ts +50 -0
- package/dist/chat-protocol/confirm-tool.d.ts.map +1 -0
- package/dist/chat-protocol/decode.d.ts +1 -9
- package/dist/chat-protocol/decode.d.ts.map +1 -1
- package/dist/chat-protocol/events.d.ts +35 -0
- package/dist/chat-protocol/events.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +72 -3
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.d.ts +1 -0
- package/dist/chat-protocol/index.d.ts.map +1 -1
- package/dist/chat-protocol/index.js +67 -4
- package/dist/chat-protocol/index.js.map +1 -1
- package/dist/chat-protocol/leading-frames.d.ts +38 -0
- package/dist/chat-protocol/leading-frames.d.ts.map +1 -0
- package/dist/chat-protocol/nats-decoder.d.ts +41 -0
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
- package/dist/{chunk-QEMI34VA.cjs → chunk-2MYDGLJS.cjs} +87 -87
- package/dist/{chunk-QEMI34VA.cjs.map → chunk-2MYDGLJS.cjs.map} +1 -1
- package/dist/{chunk-TQ7EPKKO.js → chunk-2TUOBFPK.js} +2 -2
- package/dist/{chunk-XBYEPSNZ.js → chunk-2XGHEJZK.js} +4 -4
- package/dist/{chunk-OAEJWP7H.js → chunk-46FKXLNE.js} +2 -2
- package/dist/{chunk-ECR37JHH.js → chunk-4QKXNWH5.js} +2 -2
- package/dist/{chunk-GWFC3NE5.cjs → chunk-4ZRHVOEB.cjs} +31 -31
- package/dist/{chunk-GWFC3NE5.cjs.map → chunk-4ZRHVOEB.cjs.map} +1 -1
- package/dist/{chunk-AZBS4PZG.js → chunk-5U67WTAS.js} +2 -2
- package/dist/{chunk-3G7FDE5E.js → chunk-6SMLYZLH.js} +4 -4
- package/dist/{chunk-7SGBF2I2.js → chunk-6W3ULZ4X.js} +6 -6
- package/dist/{chunk-ALXLWF75.js → chunk-6YXN5RHW.js} +2 -2
- package/dist/{chunk-2QP67D2L.cjs → chunk-DFQLUK75.cjs} +3 -3
- package/dist/{chunk-2QP67D2L.cjs.map → chunk-DFQLUK75.cjs.map} +1 -1
- package/dist/{chunk-ETF2QXXU.cjs → chunk-ENPGPZIB.cjs} +61 -61
- package/dist/{chunk-ETF2QXXU.cjs.map → chunk-ENPGPZIB.cjs.map} +1 -1
- package/dist/{chunk-HJDCDOJM.cjs → chunk-GNOK5J34.cjs} +37 -37
- package/dist/{chunk-HJDCDOJM.cjs.map → chunk-GNOK5J34.cjs.map} +1 -1
- package/dist/{chunk-4GL2XJ5C.cjs → chunk-J4BE4FIR.cjs} +4 -4
- package/dist/{chunk-4GL2XJ5C.cjs.map → chunk-J4BE4FIR.cjs.map} +1 -1
- package/dist/{chunk-HXHA3DCI.cjs → chunk-JDW2IL7A.cjs} +11 -11
- package/dist/{chunk-HXHA3DCI.cjs.map → chunk-JDW2IL7A.cjs.map} +1 -1
- package/dist/{chunk-TMZS63G2.cjs → chunk-KMB66NYX.cjs} +7 -7
- package/dist/{chunk-TMZS63G2.cjs.map → chunk-KMB66NYX.cjs.map} +1 -1
- package/dist/{chunk-5W3RCZIK.js → chunk-LE3BJ5H2.js} +2 -2
- package/dist/{chunk-WINT757E.js → chunk-LIUARTOZ.js} +206 -64
- package/dist/chunk-LIUARTOZ.js.map +1 -0
- package/dist/{chunk-65NR333L.js → chunk-MVGL5NNJ.js} +2 -2
- package/dist/{chunk-LYWSHNK4.cjs → chunk-NAV4FRNL.cjs} +26 -26
- package/dist/{chunk-LYWSHNK4.cjs.map → chunk-NAV4FRNL.cjs.map} +1 -1
- package/dist/{chunk-2MBS3YEY.cjs → chunk-OLOKQQAP.cjs} +5 -5
- package/dist/{chunk-2MBS3YEY.cjs.map → chunk-OLOKQQAP.cjs.map} +1 -1
- package/dist/{chunk-QYNLVIGQ.cjs → chunk-QZ2NAPLF.cjs} +9 -9
- package/dist/{chunk-QYNLVIGQ.cjs.map → chunk-QZ2NAPLF.cjs.map} +1 -1
- package/dist/{chunk-L6EKEYF7.cjs → chunk-RKITTO5V.cjs} +14 -14
- package/dist/{chunk-L6EKEYF7.cjs.map → chunk-RKITTO5V.cjs.map} +1 -1
- package/dist/{chunk-UP4RA54U.js → chunk-U555TMPC.js} +5 -5
- package/dist/{chunk-GVLA2BPW.cjs → chunk-UPHN2VRZ.cjs} +259 -117
- package/dist/chunk-UPHN2VRZ.cjs.map +1 -0
- package/dist/{chunk-IZOGFZ7N.js → chunk-XN4JKIJY.js} +2 -2
- package/dist/{chunk-KYQZBQYV.js → chunk-YLB6XXO6.js} +2 -2
- package/dist/{chunk-JZ5XDZYD.cjs → chunk-Z3NOI7X7.cjs} +26 -26
- package/dist/{chunk-JZ5XDZYD.cjs.map → chunk-Z3NOI7X7.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/embeddable-chat.d.ts +13 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +10 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +9 -1
- package/dist/components/chat/stream/chat-stream-reducer.d.ts +9 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +15 -0
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/network.types.d.ts +4 -0
- package/dist/components/chat/types/network.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +7 -1
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/approval-display.d.ts +49 -0
- package/dist/components/chat/utils/approval-display.d.ts.map +1 -0
- package/dist/components/chat/utils/index.d.ts +1 -0
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts +25 -3
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +140 -132
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +19 -11
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +34 -34
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +10 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +9 -1
- package/dist/index.cjs +10 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +9 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +70 -0
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +108 -1
- package/src/chat-protocol/confirm-tool.ts +67 -0
- package/src/chat-protocol/decode.ts +7 -153
- package/src/chat-protocol/events.ts +40 -0
- package/src/chat-protocol/index.ts +3 -0
- package/src/chat-protocol/leading-frames.ts +175 -0
- package/src/chat-protocol/nats-decoder.ts +111 -3
- package/src/components/chat/embeddable-chat.tsx +18 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +7 -16
- package/src/components/chat/stream/chat-stream-reducer.ts +49 -3
- package/src/components/chat/types/message.types.ts +15 -0
- package/src/components/chat/types/network.types.ts +4 -0
- package/src/components/chat/types/processing.types.ts +12 -1
- package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +92 -0
- package/src/components/chat/utils/approval-display.ts +70 -0
- package/src/components/chat/utils/index.ts +9 -0
- package/src/components/chat/utils/message-segment-accumulator.ts +62 -32
- package/src/components/chat/utils/process-historical-messages.ts +40 -6
- package/dist/chunk-GVLA2BPW.cjs.map +0 -1
- package/dist/chunk-WINT757E.js.map +0 -1
- /package/dist/{chunk-TQ7EPKKO.js.map → chunk-2TUOBFPK.js.map} +0 -0
- /package/dist/{chunk-XBYEPSNZ.js.map → chunk-2XGHEJZK.js.map} +0 -0
- /package/dist/{chunk-OAEJWP7H.js.map → chunk-46FKXLNE.js.map} +0 -0
- /package/dist/{chunk-ECR37JHH.js.map → chunk-4QKXNWH5.js.map} +0 -0
- /package/dist/{chunk-AZBS4PZG.js.map → chunk-5U67WTAS.js.map} +0 -0
- /package/dist/{chunk-3G7FDE5E.js.map → chunk-6SMLYZLH.js.map} +0 -0
- /package/dist/{chunk-7SGBF2I2.js.map → chunk-6W3ULZ4X.js.map} +0 -0
- /package/dist/{chunk-ALXLWF75.js.map → chunk-6YXN5RHW.js.map} +0 -0
- /package/dist/{chunk-5W3RCZIK.js.map → chunk-LE3BJ5H2.js.map} +0 -0
- /package/dist/{chunk-65NR333L.js.map → chunk-MVGL5NNJ.js.map} +0 -0
- /package/dist/{chunk-UP4RA54U.js.map → chunk-U555TMPC.js.map} +0 -0
- /package/dist/{chunk-IZOGFZ7N.js.map → chunk-XN4JKIJY.js.map} +0 -0
- /package/dist/{chunk-KYQZBQYV.js.map → chunk-YLB6XXO6.js.map} +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leading-frame → `ChatStreamEvent` table — the ONE mapping both transports read.
|
|
3
|
+
*
|
|
4
|
+
* The SSE decoder (`./decode.ts`) reaches it while walking the hub's byte
|
|
5
|
+
* framing. The NATS decoder (`./nats-decoder.ts`) reaches it for `GUIDE` chunks
|
|
6
|
+
* carrying a `payload`: the saas-ai-agent re-streams the Product Guide's hub
|
|
7
|
+
* frames VERBATIM into the Mingo chunk stream (`GuideStreamingService`), so the
|
|
8
|
+
* bytes a guide answer is made of are the same frames on both transports — only
|
|
9
|
+
* the envelope differs. Decoding them twice would drift the moment the hub adds
|
|
10
|
+
* a frame kind, which is why this table lives outside either decoder.
|
|
11
|
+
*
|
|
12
|
+
* The NATS side does NOT take every event this table can produce; see
|
|
13
|
+
* `guideFrameEvent` in `./nats-decoder.ts` for which ones cross over and why.
|
|
14
|
+
*
|
|
15
|
+
* Server-safe: no React, no browser APIs.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ChatStreamEvent } from './events'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Escape `<` so markdown renderers that pass HTML through (rehypeRaw)
|
|
22
|
+
* don't treat XML-like tokens in Claude's thinking output as elements.
|
|
23
|
+
* `<` → `<` preserves the visible character without breaking
|
|
24
|
+
* blockquote `>` markers. Per-character, so it distributes over
|
|
25
|
+
* concatenation: escape(a + b) === escape(a) + escape(b) — callers may
|
|
26
|
+
* apply it per-delta or on the accumulated string interchangeably.
|
|
27
|
+
*/
|
|
28
|
+
export function escapeThinkingTags(text: string): string {
|
|
29
|
+
return text.replace(/</g, '<')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Map one parsed leading frame to normalized events, replicating the
|
|
34
|
+
* legacy parser's else-if chain ORDER and its exact truthiness/typeof
|
|
35
|
+
* gates. Frames that matched a branch but failed its inner validation
|
|
36
|
+
* (e.g. `routing` without a string `routedComplexity`) produce NO event,
|
|
37
|
+
* exactly like the legacy no-op.
|
|
38
|
+
*
|
|
39
|
+
* Every branch pushes AT MOST ONE event — `guideFrameEvent` relies on that
|
|
40
|
+
* to hand a single event back to `decodeNatsChunk`'s per-chunk contract.
|
|
41
|
+
*/
|
|
42
|
+
export function mapLeadingFrame(meta: any, out: ChatStreamEvent[]): void {
|
|
43
|
+
if (meta.status === 'thinking') {
|
|
44
|
+
out.push({ type: 'status', phase: 'thinking' })
|
|
45
|
+
} else if (meta.kind === 'thinking-delta' && typeof meta.text === 'string') {
|
|
46
|
+
// Wire is ALREADY delta — emit verbatim, append-only contract.
|
|
47
|
+
out.push({ type: 'thinking-delta', text: meta.text })
|
|
48
|
+
} else if (meta.kind === 'usage' && meta.stage === 'start') {
|
|
49
|
+
out.push({
|
|
50
|
+
type: 'usage',
|
|
51
|
+
stage: 'start',
|
|
52
|
+
input_tokens: meta.input_tokens,
|
|
53
|
+
cache_read_input_tokens: meta.cache_read_input_tokens,
|
|
54
|
+
cache_creation_input_tokens: meta.cache_creation_input_tokens,
|
|
55
|
+
})
|
|
56
|
+
} else if (meta.kind === 'decision_resolved' && typeof meta.action === 'string') {
|
|
57
|
+
const status = meta.action === 'rejected' ? 'rejected' : 'approved'
|
|
58
|
+
const toolName = typeof meta.tool_name === 'string' ? meta.tool_name : undefined
|
|
59
|
+
const result = meta.result ?? null
|
|
60
|
+
const card = meta.card ?? null
|
|
61
|
+
out.push({
|
|
62
|
+
type: 'approval-resolved',
|
|
63
|
+
status,
|
|
64
|
+
ok: meta.ok === true,
|
|
65
|
+
willAutoContinue: meta.willAutoContinue === true,
|
|
66
|
+
...(toolName ? { toolName } : {}),
|
|
67
|
+
...(result ? { result } : {}),
|
|
68
|
+
...(card?.marker ? { marker: card.marker } : {}),
|
|
69
|
+
...(card?.ref ? { cardRef: card.ref } : {}),
|
|
70
|
+
...(card?.type ? { cardType: card.type } : {}),
|
|
71
|
+
...(typeof meta.receiptText === 'string' ? { receiptText: meta.receiptText } : {}),
|
|
72
|
+
requestId: typeof meta.proposalId === 'string' ? meta.proposalId : undefined,
|
|
73
|
+
})
|
|
74
|
+
} else if (meta.kind === 'approval_batch' && meta.batchId && Array.isArray(meta.proposals)) {
|
|
75
|
+
// Server-grouped multi-proposal turn → ONE batch event carrying a
|
|
76
|
+
// tool-call row per proposal. `toolExecutionRequestId` is the row's
|
|
77
|
+
// PROPOSAL id (each row resolves through its own per-proposal
|
|
78
|
+
// confirm); the batch's `requestId` is the stable anchor the shell
|
|
79
|
+
// uses for status flips. Field rows ride as the row's expandable
|
|
80
|
+
// args so per-proposal detail stays reachable inside the batch.
|
|
81
|
+
const toolCalls = (meta.proposals as Array<Record<string, any>>)
|
|
82
|
+
.filter((p) => p && typeof p.proposalId === 'string')
|
|
83
|
+
.map((p) => {
|
|
84
|
+
const rawFields = Array.isArray(p.fields)
|
|
85
|
+
? (p.fields as Array<{ label?: string; value?: string }>).filter(
|
|
86
|
+
(f) => f && f.label && f.value,
|
|
87
|
+
)
|
|
88
|
+
: []
|
|
89
|
+
// Prefer human-readable identity labels for the row's
|
|
90
|
+
// disambiguator; opaque-id labels ("Task", "Ticket") only as
|
|
91
|
+
// the last-resort first field.
|
|
92
|
+
const detail =
|
|
93
|
+
rawFields.find((f) => /^(title|subject|name)$/i.test(String(f.label))) ??
|
|
94
|
+
rawFields[0]
|
|
95
|
+
const base =
|
|
96
|
+
typeof p.title === 'string' && p.title.length > 0 ? p.title : String(p.toolName ?? 'Tool call')
|
|
97
|
+
return {
|
|
98
|
+
toolExecutionRequestId: String(p.proposalId),
|
|
99
|
+
toolName: String(p.toolName ?? 'tool'),
|
|
100
|
+
toolTitle: detail ? `${base} — ${detail.value}` : base,
|
|
101
|
+
requiresApproval: true,
|
|
102
|
+
toolCallArguments:
|
|
103
|
+
rawFields.length > 0
|
|
104
|
+
? Object.fromEntries(rawFields.map((f) => [String(f.label), String(f.value)]))
|
|
105
|
+
: null,
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
if (toolCalls.length > 0) {
|
|
109
|
+
out.push({
|
|
110
|
+
type: 'approval-request',
|
|
111
|
+
requestId: String(meta.batchId),
|
|
112
|
+
approvalType: 'chat',
|
|
113
|
+
toolCalls,
|
|
114
|
+
status: 'pending',
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
} else if (meta.kind === 'approval_request' && meta.proposalId) {
|
|
118
|
+
const proposalId = String(meta.proposalId)
|
|
119
|
+
const toolName = String(meta.toolName ?? 'tool')
|
|
120
|
+
const headline =
|
|
121
|
+
typeof meta.title === 'string' && meta.title.length > 0 ? meta.title : toolName
|
|
122
|
+
const rawFields = Array.isArray(meta.fields)
|
|
123
|
+
? (meta.fields as Array<{ label?: string; value?: string }>)
|
|
124
|
+
: []
|
|
125
|
+
const fields: Array<{ label: string; value: string }> = []
|
|
126
|
+
for (const f of rawFields) {
|
|
127
|
+
if (!f || !f.label || !f.value) continue
|
|
128
|
+
fields.push({ label: f.label, value: f.value })
|
|
129
|
+
}
|
|
130
|
+
out.push({
|
|
131
|
+
type: 'approval-request',
|
|
132
|
+
requestId: proposalId,
|
|
133
|
+
approvalType: toolName,
|
|
134
|
+
command: headline,
|
|
135
|
+
fields,
|
|
136
|
+
status: 'pending',
|
|
137
|
+
})
|
|
138
|
+
} else if (meta.kind === 'text-leading' && typeof meta.text === 'string') {
|
|
139
|
+
out.push({ type: 'text-delta', text: meta.text, leading: true })
|
|
140
|
+
} else if (meta.kind === 'tool_error') {
|
|
141
|
+
const msg =
|
|
142
|
+
typeof meta.message === 'string' && meta.message.length > 0
|
|
143
|
+
? meta.message
|
|
144
|
+
: 'Could not complete the requested action right now.'
|
|
145
|
+
out.push({ type: 'error', title: msg })
|
|
146
|
+
} else if (meta.kind === 'routing') {
|
|
147
|
+
if (typeof meta.routedComplexity === 'string') {
|
|
148
|
+
out.push({
|
|
149
|
+
type: 'metadata',
|
|
150
|
+
routing: {
|
|
151
|
+
routedComplexity: meta.routedComplexity,
|
|
152
|
+
...(typeof meta.routedModel === 'string' ? { routedModel: meta.routedModel } : {}),
|
|
153
|
+
routedThinkingBudget:
|
|
154
|
+
typeof meta.routedThinkingBudget === 'number' ? meta.routedThinkingBudget : null,
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
// Catch-all metadata-ish frame. Raw values pass through UNVALIDATED
|
|
160
|
+
// (possibly undefined) so the consumer can replicate the legacy
|
|
161
|
+
// presence/truthiness gates exactly — including the `model`-presence
|
|
162
|
+
// trigger whose value is never stored.
|
|
163
|
+
out.push({
|
|
164
|
+
type: 'metadata',
|
|
165
|
+
sources: meta.sources,
|
|
166
|
+
refs: meta.refs,
|
|
167
|
+
provider: meta.provider,
|
|
168
|
+
modelLabel: meta.modelLabel,
|
|
169
|
+
modelName: meta.model,
|
|
170
|
+
contextWindowMaxTokens: meta.contextWindowMaxTokens,
|
|
171
|
+
scrollAnchor: meta.scrollAnchor,
|
|
172
|
+
conversationId: meta.conversationId,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -18,13 +18,19 @@
|
|
|
18
18
|
|
|
19
19
|
import { MESSAGE_TYPE } from '../components/chat/types/message.types'
|
|
20
20
|
import type { AskOptionData } from '../components/chat/types/message.types'
|
|
21
|
-
import { ESCALATION_STATE, escalationResolvedStatus } from './events'
|
|
21
|
+
import { ESCALATION_STATE, GUIDE_ORIGIN, escalationResolvedStatus } from './events'
|
|
22
22
|
import type { ApprovalToolCall, ChatStreamEvent } from './events'
|
|
23
|
+
import { escapeThinkingTags, mapLeadingFrame } from './leading-frames'
|
|
23
24
|
|
|
24
25
|
/** Minimal structural view of a NATS chunk (see `ChunkData` in
|
|
25
26
|
* `src/components/chat/types/network.types.ts`). */
|
|
26
27
|
type NatsChunk = Record<string, any>
|
|
27
28
|
|
|
29
|
+
// `GUIDE_ORIGIN` moved to `./events`, beside the `GuideOrigin` type it is the
|
|
30
|
+
// only value of — the SSE half reads it too, so it never belonged to the NATS
|
|
31
|
+
// decoder. Re-exported here because that is the import path consumers know.
|
|
32
|
+
export { GUIDE_ORIGIN } from './events'
|
|
33
|
+
|
|
28
34
|
/** Coerce the wire's `toolCalls[]` into the batch-approval shape, dropping
|
|
29
35
|
* non-object entries and defaulting every field. */
|
|
30
36
|
function normalizeToolCalls(raw: unknown): ApprovalToolCall[] {
|
|
@@ -61,6 +67,99 @@ export function normalizeAskOptions(raw: unknown): AskOptionData[] {
|
|
|
61
67
|
.filter((option) => option.label.length > 0)
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Events whose meaning belongs to the AGENT's dialog rather than to the guide
|
|
72
|
+
* turn embedded inside it. The hub emits its own copies of these, and the Mingo
|
|
73
|
+
* dialog already has authoritative ones: letting the hub's through would
|
|
74
|
+
* double-count the dialog's tokens or fight the agent's own phase chunks.
|
|
75
|
+
*
|
|
76
|
+
* Note this is a DENYLIST, and that is the point. `./leading-frames` is the ONE
|
|
77
|
+
* place a hub frame kind is taught to the client; whatever it learns to emit —
|
|
78
|
+
* including kinds that do not exist yet — must reach a Mingo dialog without a
|
|
79
|
+
* second edit here. An allowlist would silently swallow every card the hub adds
|
|
80
|
+
* next, and the guide half would quietly lag the guide chat.
|
|
81
|
+
*/
|
|
82
|
+
const AGENT_OWNED_EVENTS: ReadonlySet<ChatStreamEvent['type']> = new Set([
|
|
83
|
+
'usage',
|
|
84
|
+
'token-usage',
|
|
85
|
+
'status',
|
|
86
|
+
'compaction',
|
|
87
|
+
'participant',
|
|
88
|
+
'dialog-closed',
|
|
89
|
+
])
|
|
90
|
+
|
|
91
|
+
/** Event types that declare `origin` (see `GuideOrigin`). Data rather than a
|
|
92
|
+
* branch per type, so a future hub card opts in by adding the field to its
|
|
93
|
+
* interface and its name here — the pass-through below needs no edit. */
|
|
94
|
+
const ORIGIN_BEARING_EVENTS: ReadonlySet<ChatStreamEvent['type']> = new Set([
|
|
95
|
+
'approval-request',
|
|
96
|
+
'approval-resolved',
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Adapt ONE Product Guide event to the NATS kernel.
|
|
101
|
+
*
|
|
102
|
+
* The guide half of a Mingo dialog is the hub's own stream, so it is decoded by
|
|
103
|
+
* the shared table in `./leading-frames` and then passed through here — the ONE
|
|
104
|
+
* place that reconciles the two kernels. Both entry points use it: frames
|
|
105
|
+
* re-streamed by the agent inside `GUIDE` chunks, and the SSE response of the
|
|
106
|
+
* hub's confirm-tool route, which a host replays into the same dialog.
|
|
107
|
+
*
|
|
108
|
+
* Pass-through is the DEFAULT; only these rules alter an event, and each exists
|
|
109
|
+
* because the two kernels genuinely disagree:
|
|
110
|
+
*
|
|
111
|
+
* - `text-delta` becomes `guide-delta`: the body of a guide turn belongs
|
|
112
|
+
* inside the "OpenFrame Guide" card, and a `text` segment would strand part
|
|
113
|
+
* of the same answer outside it.
|
|
114
|
+
* - thinking is escaped HERE. The SSE kernel escapes `<` on the way in, the
|
|
115
|
+
* NATS kernel does not, and the guide's thinking is full of XML-ish tokens
|
|
116
|
+
* that would otherwise render as markup.
|
|
117
|
+
* - `metadata` survives ONLY to carry `conversationId`, which the hub mints
|
|
118
|
+
* and every confirm-tool call must quote back. Everything else on that
|
|
119
|
+
* frame (the hub's model, routing) is dropped: `applyNats` rebuilds the
|
|
120
|
+
* dialog's live model from a metadata event, so letting it through would
|
|
121
|
+
* relabel a Mingo turn with the hub's model, and a routing frame — which
|
|
122
|
+
* carries no model at all — would blank the badge mid-answer.
|
|
123
|
+
* - dialog-level events stop here (`AGENT_OWNED_EVENTS`).
|
|
124
|
+
*
|
|
125
|
+
* Everything else crosses over as the hub typed it, gaining only `origin`.
|
|
126
|
+
* `approvalType` in particular stays the TOOL NAME, exactly as in the hub's own
|
|
127
|
+
* chat: the NATS kernel gates approvals on approval TIER and escalates the rest,
|
|
128
|
+
* which is an agent-side concept a hub proposal has no tier for. Rewriting the
|
|
129
|
+
* tool name into a fake tier to slip past that gate would make the guide half of
|
|
130
|
+
* the stream diverge from the guide chat itself.
|
|
131
|
+
*/
|
|
132
|
+
export function guideEventForNats(event: ChatStreamEvent): ChatStreamEvent | null {
|
|
133
|
+
switch (event.type) {
|
|
134
|
+
case 'text-delta':
|
|
135
|
+
return { type: 'guide-delta', text: event.text }
|
|
136
|
+
case 'thinking-delta':
|
|
137
|
+
return { type: 'thinking-delta', text: escapeThinkingTags(event.text) }
|
|
138
|
+
case 'metadata':
|
|
139
|
+
return typeof event.conversationId === 'string' && event.conversationId
|
|
140
|
+
? { type: 'metadata', conversationId: event.conversationId, origin: GUIDE_ORIGIN }
|
|
141
|
+
: null
|
|
142
|
+
default:
|
|
143
|
+
if (AGENT_OWNED_EVENTS.has(event.type)) return null
|
|
144
|
+
return ORIGIN_BEARING_EVENTS.has(event.type)
|
|
145
|
+
? ({ ...event, origin: GUIDE_ORIGIN } as ChatStreamEvent)
|
|
146
|
+
: event
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Decode one re-streamed Product Guide frame (a `GUIDE` chunk's `payload`) into
|
|
152
|
+
* a NATS event. The frame grammar is decoded by the shared table, never by a
|
|
153
|
+
* second copy here; the kernel reconciliation is `guideEventForNats`.
|
|
154
|
+
*/
|
|
155
|
+
export function guideFrameEvent(frame: Record<string, unknown>): ChatStreamEvent | null {
|
|
156
|
+
const events: ChatStreamEvent[] = []
|
|
157
|
+
// Every branch of the table pushes at most one event, so the first is the one.
|
|
158
|
+
mapLeadingFrame(frame, events)
|
|
159
|
+
const event = events[0]
|
|
160
|
+
return event ? guideEventForNats(event) : null
|
|
161
|
+
}
|
|
162
|
+
|
|
64
163
|
/**
|
|
65
164
|
* Parse one raw NATS chunk into a normalized event. Returns `null` for
|
|
66
165
|
* unknown/malformed chunks — an unrecognized `type`, a missing required
|
|
@@ -111,11 +210,20 @@ export function decodeNatsChunk(chunk: unknown): ChatStreamEvent | null {
|
|
|
111
210
|
}
|
|
112
211
|
return null
|
|
113
212
|
|
|
114
|
-
|
|
213
|
+
// Two shapes share this chunk type. `text` is the answer body the agent
|
|
214
|
+
// streams (and persists). `payload` is a Product Guide frame re-streamed
|
|
215
|
+
// verbatim from the hub — decoded through the shared frame table, with the
|
|
216
|
+
// narrowing `guideFrameEvent` documents.
|
|
217
|
+
case MESSAGE_TYPE.GUIDE: {
|
|
115
218
|
if (typeof data.text === 'string') {
|
|
116
219
|
return { type: 'guide-delta', text: data.text, ...seq }
|
|
117
220
|
}
|
|
118
|
-
|
|
221
|
+
const frame = data.payload
|
|
222
|
+
if (!frame || typeof frame !== 'object' || Array.isArray(frame)) return null
|
|
223
|
+
const event = guideFrameEvent(frame as Record<string, unknown>)
|
|
224
|
+
if (!event) return null
|
|
225
|
+
return { ...event, ...seq }
|
|
226
|
+
}
|
|
119
227
|
|
|
120
228
|
// An ask card is only an ask card with something to pick: a question and at
|
|
121
229
|
// least one option. Anything less is dropped rather than rendered as an
|
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
type UseUnifiedChatModes,
|
|
83
83
|
} from './hooks/use-unified-chat'
|
|
84
84
|
import type { UnifiedChatState } from './types/unified-chat-state.types'
|
|
85
|
+
import type { MessageSegment } from './types/message.types'
|
|
85
86
|
import type {
|
|
86
87
|
UseNatsChatAdapterConfig,
|
|
87
88
|
FetchDialogsParams,
|
|
@@ -209,6 +210,19 @@ export interface EmbeddableChatProps {
|
|
|
209
210
|
*/
|
|
210
211
|
mingoState?: UnifiedChatState
|
|
211
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Approval cards to pin as the sticky footer under the thread.
|
|
215
|
+
*
|
|
216
|
+
* Hosts that lift PENDING approvals out of the message list (mingo does:
|
|
217
|
+
* a pending card is filtered out of its bubble so an interrupted retry cannot
|
|
218
|
+
* render the same request twice) must hand them back here — otherwise the
|
|
219
|
+
* card exists in the reducer, is stripped on the way to the view, and is
|
|
220
|
+
* displayed nowhere at all. `ChatMessageList` renders them with the same
|
|
221
|
+
* component the inline path uses, so the approve/reject handlers stamped on
|
|
222
|
+
* the segments keep working.
|
|
223
|
+
*/
|
|
224
|
+
pendingApprovals?: MessageSegment[]
|
|
225
|
+
|
|
212
226
|
/**
|
|
213
227
|
* Dialog-management capabilities for injected Mingo mode (`mingoState`).
|
|
214
228
|
*
|
|
@@ -909,6 +923,7 @@ function EmbeddableChatInner({
|
|
|
909
923
|
tableIdForDocumentType,
|
|
910
924
|
modes,
|
|
911
925
|
mingoState,
|
|
926
|
+
pendingApprovals,
|
|
912
927
|
mingoDialogCapabilities,
|
|
913
928
|
activeMode: controlledActiveMode,
|
|
914
929
|
onActiveModeChange,
|
|
@@ -2350,6 +2365,9 @@ function EmbeddableChatInner({
|
|
|
2350
2365
|
<ChatMessageList
|
|
2351
2366
|
messages={messages}
|
|
2352
2367
|
isTyping={chatLoading}
|
|
2368
|
+
// Sticky footer for approvals the host lifted out of the
|
|
2369
|
+
// thread — see the prop's docblock.
|
|
2370
|
+
pendingApprovals={pendingApprovals}
|
|
2353
2371
|
// Real drawer: the library's smart follow. Passive in-page
|
|
2354
2372
|
// demo (previewMode): deterministic hard pin instead — a
|
|
2355
2373
|
// scripted assistant-only stream from a cold mount never
|
|
@@ -59,6 +59,7 @@ import type { MessageSegment } from '../types/message.types'
|
|
|
59
59
|
import { useSlashCommandRegistry, type SlashCommandSummary } from './use-slash-commands'
|
|
60
60
|
import { chatAuthedFetch } from '../utils/chat-authed-fetch'
|
|
61
61
|
import type { ScrollAnchor } from '../utils/scroll-anchor'
|
|
62
|
+
import { buildConfirmToolBody, readServerErrorMessage } from '../../../chat-protocol/confirm-tool'
|
|
62
63
|
import { createSseFrameDecoder } from '../../../chat-protocol/decode'
|
|
63
64
|
import type { ChatStreamEvent } from '../../../chat-protocol/events'
|
|
64
65
|
import {
|
|
@@ -437,13 +438,11 @@ export function useSseChatAdapter(
|
|
|
437
438
|
? endpointsRef.current.approvalToolUrl
|
|
438
439
|
: endpointsRef.current.chatStreamUrl
|
|
439
440
|
const requestBody = approvalAction
|
|
440
|
-
?
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
conversationId,
|
|
446
|
-
}
|
|
441
|
+
? // Shared with every other transport that resolves a hub proposal —
|
|
442
|
+
// the body shape is the hub's, not this adapter's. `conversationId`
|
|
443
|
+
// is always present here: an approval can only happen inside an
|
|
444
|
+
// established conversation (the proposal turn captured the id).
|
|
445
|
+
buildConfirmToolBody({ ...approvalAction, conversationId })
|
|
447
446
|
: {
|
|
448
447
|
messages: [{ role: 'user', content: text }],
|
|
449
448
|
...(commandOverride ? { commandOverride } : {}),
|
|
@@ -476,15 +475,7 @@ export function useSseChatAdapter(
|
|
|
476
475
|
// `{error, code}`) — a bare "Chat request failed: 409" told
|
|
477
476
|
// the user nothing when e.g. a batch approval expired; the
|
|
478
477
|
// server ships real copy ("This approval expired — ...").
|
|
479
|
-
|
|
480
|
-
try {
|
|
481
|
-
const errBody = (await response.json()) as { error?: unknown }
|
|
482
|
-
if (typeof errBody?.error === 'string' && errBody.error.length > 0) {
|
|
483
|
-
serverMessage = errBody.error
|
|
484
|
-
}
|
|
485
|
-
} catch {
|
|
486
|
-
/* non-JSON error body — fall through to the generic copy */
|
|
487
|
-
}
|
|
478
|
+
const serverMessage = await readServerErrorMessage(response)
|
|
488
479
|
throw new Error(serverMessage ?? `Chat request failed: ${response.status}`)
|
|
489
480
|
}
|
|
490
481
|
const reader = response.body?.getReader()
|
|
@@ -58,6 +58,8 @@ import {
|
|
|
58
58
|
import { getCommandText } from '../utils/tool-call-helpers'
|
|
59
59
|
import { parseScrollAnchor, type ScrollAnchor } from '../utils/scroll-anchor'
|
|
60
60
|
import { escapeThinkingTags } from '../../../chat-protocol/decode'
|
|
61
|
+
import { isGuideOrigin } from '../../../chat-protocol/events'
|
|
62
|
+
import { approvalDisplaysInline, guideApprovalOrigin } from '../utils/approval-display'
|
|
61
63
|
import { buildChatRefKey } from '../types/chat.types'
|
|
62
64
|
import type {
|
|
63
65
|
ApprovalBatchSegment,
|
|
@@ -162,6 +164,15 @@ export interface ChatReducerState {
|
|
|
162
164
|
contextWindowMaxTokens: number | null
|
|
163
165
|
} | null
|
|
164
166
|
approvalStatuses: Record<string, ChatApprovalStatus>
|
|
167
|
+
/**
|
|
168
|
+
* Hub conversation id for the Product Guide half of this dialog, learned from
|
|
169
|
+
* a guide metadata frame. The hub mints it and requires it back on every
|
|
170
|
+
* confirm-tool call, so a host resolving a guide approval card reads it from
|
|
171
|
+
* here. Null until a guide turn has streamed — an approval that arrives from
|
|
172
|
+
* history (after a reload) has no live frame to learn it from, which is why
|
|
173
|
+
* the agent should also expose it per dialog.
|
|
174
|
+
*/
|
|
175
|
+
guideConversationId: string | null
|
|
165
176
|
}
|
|
166
177
|
|
|
167
178
|
/** Escalated-approval bookkeeping entry (mirrors the legacy processor). */
|
|
@@ -520,6 +531,7 @@ export function createChatStreamReducer(
|
|
|
520
531
|
let streamingPhase: StreamingPhase = 'idle'
|
|
521
532
|
let dialogTokenUsage: DialogTokenUsage | null = null
|
|
522
533
|
let liveModel: ChatReducerState['liveModel'] = null
|
|
534
|
+
let guideConversationId: string | null = null
|
|
523
535
|
let approvalStatuses: Record<string, ChatApprovalStatus> = {
|
|
524
536
|
...(options.approvalStatuses ?? {}),
|
|
525
537
|
}
|
|
@@ -682,6 +694,7 @@ export function createChatStreamReducer(
|
|
|
682
694
|
dialogTokenUsage,
|
|
683
695
|
liveModel,
|
|
684
696
|
approvalStatuses,
|
|
697
|
+
guideConversationId,
|
|
685
698
|
}
|
|
686
699
|
}
|
|
687
700
|
return stateCache
|
|
@@ -989,6 +1002,17 @@ export function createChatStreamReducer(
|
|
|
989
1002
|
}
|
|
990
1003
|
|
|
991
1004
|
case 'metadata': {
|
|
1005
|
+
// A guide metadata event carries ONLY the hub's conversation id (the
|
|
1006
|
+
// decoder strips the rest): record it and stop. Falling through would
|
|
1007
|
+
// rebuild `liveModel` from an event with no model in it and blank the
|
|
1008
|
+
// dialog's model badge mid-answer.
|
|
1009
|
+
if (isGuideOrigin(event)) {
|
|
1010
|
+
if (typeof event.conversationId === 'string' && event.conversationId) {
|
|
1011
|
+
guideConversationId = event.conversationId
|
|
1012
|
+
invalidate()
|
|
1013
|
+
}
|
|
1014
|
+
break
|
|
1015
|
+
}
|
|
992
1016
|
// Legacy `parseChunkToAction` action shape, reconstructed for the
|
|
993
1017
|
// callback contract.
|
|
994
1018
|
emit('onMetadata', {
|
|
@@ -1101,11 +1125,17 @@ export function createChatStreamReducer(
|
|
|
1101
1125
|
const requestId = event.requestId
|
|
1102
1126
|
const approvalType = event.approvalType ?? 'USER'
|
|
1103
1127
|
const toolCalls = event.toolCalls
|
|
1128
|
+
// Where this card renders is ONE rule, shared with the SSE kernel and
|
|
1129
|
+
// the history replay (`approval-display`) — a Product Guide proposal
|
|
1130
|
+
// that renders inline live must not move on the next page load.
|
|
1131
|
+
const guideOrigin = guideApprovalOrigin(event)
|
|
1132
|
+
const displayInline = (type: string) =>
|
|
1133
|
+
approvalDisplaysInline(event, type, displayApprovalTypes)
|
|
1104
1134
|
|
|
1105
1135
|
if (toolCalls && toolCalls.length > 0) {
|
|
1106
1136
|
// ── Batch form ──
|
|
1107
1137
|
const status = (approvalStatuses[requestId] || 'pending') as ChatApprovalStatus
|
|
1108
|
-
if (!
|
|
1138
|
+
if (!displayInline(approvalType)) {
|
|
1109
1139
|
// Escalated: keep batch context locally for replay on result;
|
|
1110
1140
|
// surface a summary command via the legacy escalation callback.
|
|
1111
1141
|
const required = toolCalls.find((c) => c.requiresApproval) ?? toolCalls[0]
|
|
@@ -1127,7 +1157,15 @@ export function createChatStreamReducer(
|
|
|
1127
1157
|
}
|
|
1128
1158
|
if (batchApprovalsEnabled) {
|
|
1129
1159
|
const before = accumulator.getSegments().length
|
|
1130
|
-
const segments = accumulator.addApprovalBatch(
|
|
1160
|
+
const segments = accumulator.addApprovalBatch(
|
|
1161
|
+
requestId,
|
|
1162
|
+
approvalType,
|
|
1163
|
+
toolCalls,
|
|
1164
|
+
status,
|
|
1165
|
+
undefined,
|
|
1166
|
+
undefined,
|
|
1167
|
+
guideOrigin,
|
|
1168
|
+
)
|
|
1131
1169
|
applyAccumulated(before, segments)
|
|
1132
1170
|
break
|
|
1133
1171
|
}
|
|
@@ -1145,6 +1183,8 @@ export function createChatStreamReducer(
|
|
|
1145
1183
|
call.toolExplanation,
|
|
1146
1184
|
approvalType,
|
|
1147
1185
|
status,
|
|
1186
|
+
undefined,
|
|
1187
|
+
guideOrigin,
|
|
1148
1188
|
)
|
|
1149
1189
|
}
|
|
1150
1190
|
applyAccumulated(before, segments)
|
|
@@ -1154,7 +1194,7 @@ export function createChatStreamReducer(
|
|
|
1154
1194
|
// ── Single form ──
|
|
1155
1195
|
const command = event.command ?? ''
|
|
1156
1196
|
const explanation = event.explanation
|
|
1157
|
-
if (
|
|
1197
|
+
if (displayInline(approvalType)) {
|
|
1158
1198
|
const status = (approvalStatuses[requestId] || 'pending') as ChatApprovalStatus
|
|
1159
1199
|
const before = accumulator.getSegments().length
|
|
1160
1200
|
const segments = accumulator.addApprovalRequest(
|
|
@@ -1163,6 +1203,10 @@ export function createChatStreamReducer(
|
|
|
1163
1203
|
explanation,
|
|
1164
1204
|
approvalType,
|
|
1165
1205
|
status,
|
|
1206
|
+
// SSE-shaped cards (a re-streamed Product Guide proposal) carry
|
|
1207
|
+
// their body as structured rows rather than prose.
|
|
1208
|
+
event.fields,
|
|
1209
|
+
guideOrigin,
|
|
1166
1210
|
)
|
|
1167
1211
|
applyAccumulated(before, segments)
|
|
1168
1212
|
} else {
|
|
@@ -1707,6 +1751,7 @@ export function createChatStreamReducer(
|
|
|
1707
1751
|
streamingPhase = 'idle'
|
|
1708
1752
|
dialogTokenUsage = null
|
|
1709
1753
|
liveModel = null
|
|
1754
|
+
guideConversationId = null
|
|
1710
1755
|
approvalStatuses = {}
|
|
1711
1756
|
accumulator.reset()
|
|
1712
1757
|
pendingEscalated.clear()
|
|
@@ -1755,6 +1800,7 @@ export function createChatStreamReducer(
|
|
|
1755
1800
|
streamingPhase = 'idle'
|
|
1756
1801
|
dialogTokenUsage = null
|
|
1757
1802
|
liveModel = null
|
|
1803
|
+
guideConversationId = null
|
|
1758
1804
|
accumulator.reset()
|
|
1759
1805
|
pendingEscalated.clear()
|
|
1760
1806
|
isInStream = false
|
|
@@ -113,6 +113,12 @@ export interface ApprovalRequestData {
|
|
|
113
113
|
requestId?: string
|
|
114
114
|
approvalRequestId?: string
|
|
115
115
|
approvalType?: string
|
|
116
|
+
/** `'guide'` when the card came from a Product Guide frame (see `GuideOrigin`
|
|
117
|
+
* in `chat-protocol/events`). Carried onto the segment because hosts route
|
|
118
|
+
* and filter approvals by where they came from: mingo lifts its OWN pending
|
|
119
|
+
* cards into a sticky footer, and a guide card must stay inline in the turn
|
|
120
|
+
* the way it does in the hub's chat. */
|
|
121
|
+
origin?: 'guide'
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
export interface ApprovalResultData {
|
|
@@ -189,6 +195,11 @@ export interface ApprovalBatchData {
|
|
|
189
195
|
* batch itself is approved.
|
|
190
196
|
*/
|
|
191
197
|
executions?: Record<string, ApprovalBatchExecutionState>
|
|
198
|
+
/** `'guide'` when the batch came from a Product Guide `approval_batch` frame.
|
|
199
|
+
* Same contract and the same reasons as `ApprovalRequestData.origin` — the
|
|
200
|
+
* hub groups multiple proposals into one card, and that card resolves
|
|
201
|
+
* through the hub exactly like a single one. */
|
|
202
|
+
origin?: 'guide'
|
|
192
203
|
}
|
|
193
204
|
|
|
194
205
|
/** Approve/reject handler stamped onto approval segments. MAY resolve a
|
|
@@ -334,6 +345,10 @@ export interface ThinkingMessageData extends MessageDataBase {
|
|
|
334
345
|
export interface GuideMessageData extends MessageDataBase {
|
|
335
346
|
type: 'GUIDE'
|
|
336
347
|
text?: string
|
|
348
|
+
/** Persisted Product Guide frame (GraphQL `GuideData.payload`, a JSON scalar)
|
|
349
|
+
* for rows that carry a card instead of answer text — replayed through the
|
|
350
|
+
* live path's `guideFrameEvent`. */
|
|
351
|
+
payload?: Record<string, unknown>
|
|
337
352
|
}
|
|
338
353
|
|
|
339
354
|
/** Persisted `ASK` row (GraphQL `AskData`). `text` is the intro sentence, which
|
|
@@ -61,6 +61,10 @@ export interface ChunkData {
|
|
|
61
61
|
/** On APPROVAL_RESULT chunks: display name of the user who resolved the request. */
|
|
62
62
|
resolvedByName?: string
|
|
63
63
|
toolCalls?: any[]
|
|
64
|
+
/** On GUIDE chunks: a Product Guide frame the agent re-streamed verbatim from
|
|
65
|
+
* the hub, instead of the answer text `text` carries. Decoded by
|
|
66
|
+
* `decodeNatsChunk` through the shared leading-frame table. */
|
|
67
|
+
payload?: Record<string, unknown>
|
|
64
68
|
modelName?: string
|
|
65
69
|
providerName?: string
|
|
66
70
|
provider?: string
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
* Contains types for message parsing, accumulation, and processing
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ApprovalRequestField,
|
|
8
|
+
MessageSegment,
|
|
9
|
+
PendingToolCallData,
|
|
10
|
+
ExecutingToolState,
|
|
11
|
+
} from './message.types'
|
|
7
12
|
import type { ChatApprovalStatus, AssistantType } from './chat.types'
|
|
8
13
|
import type { ChunkData } from './network.types'
|
|
9
14
|
|
|
@@ -18,6 +23,12 @@ export interface PendingApproval {
|
|
|
18
23
|
command: string
|
|
19
24
|
explanation?: string
|
|
20
25
|
approvalType: string
|
|
26
|
+
/** Structured label/value rows — a Product Guide card is almost entirely
|
|
27
|
+
* these, so a path that forgets them renders a bare title. */
|
|
28
|
+
fields?: ApprovalRequestField[]
|
|
29
|
+
/** Where the card came from; `'guide'` keeps it inline and routes its
|
|
30
|
+
* buttons to the hub (see `ApprovalRequestData.origin`). */
|
|
31
|
+
origin?: 'guide'
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
export interface AccumulatorState {
|