@teaflask/assistant 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/assistant-transcript.js +1 -7
- package/dist/components/attachment-chips.js +11 -5
- package/dist/components/composer.js +5 -6
- package/dist/components/message-list.js +2 -1
- package/dist/components/open-minted-url.js +12 -0
- package/dist/components/primitives/button.js +3 -0
- package/dist/components/teaflask-assistant-provider.d.ts +4 -3
- package/dist/components/tool-call-rows.js +1 -8
- package/dist/components/tool-row.js +1 -2
- package/dist/components/tool-view-slot.js +0 -1
- package/dist/components/tool-views/view-dom.d.ts +3 -4
- package/dist/components/tool-views/view-dom.js +1 -4
- package/dist/components/transcript-follow.js +12 -0
- package/dist/components/use-child-replay-stream.js +9 -2
- package/dist/components/use-transcript.d.ts +3 -2
- package/dist/components/user-message.js +26 -1
- package/dist/contract/events.d.ts +1 -0
- package/dist/contract/events.js +1 -0
- package/dist/contract/telemetry.d.ts +23 -2
- package/dist/contract/threads.d.ts +0 -5
- package/dist/contract/threads.js +0 -1
- package/dist/core/assistant-session-registry.js +10 -0
- package/dist/core/composer-policy.d.ts +1 -2
- package/dist/core/composer-policy.js +0 -2
- package/dist/core/connection-epoch.d.ts +4 -5
- package/dist/core/connection-epoch.js +41 -12
- package/dist/core/conversation-adoption.js +7 -0
- package/dist/core/conversation-contract.d.ts +1 -0
- package/dist/core/conversation-store.js +0 -1
- package/dist/core/epoch-sync.js +3 -1
- package/dist/core/error-copy.js +9 -1
- package/dist/core/long-message.js +22 -0
- package/dist/core/message-copy.js +1 -0
- package/dist/core/tool-call-display.d.ts +1 -7
- package/dist/core/tool-call-presentation.js +0 -3
- package/dist/core/tool-outcome.js +0 -1
- package/dist/core/tool-view.d.ts +2 -7
- package/dist/core/transcript-rows.d.ts +0 -6
- package/dist/core/transcript-rows.js +0 -3
- package/dist/core/turn-failed-anchors.d.ts +7 -0
- package/dist/core/turn-failed-anchors.js +42 -0
- package/dist/core/withdrawn-anchors.d.ts +11 -0
- package/dist/core/withdrawn-anchors.js +76 -0
- package/dist/generated/models/apiErrorCode.d.ts +3 -0
- package/dist/generated/models/apiErrorCode.js +3 -0
- package/dist/generated/models/assistantStreamMarkerName.d.ts +1 -0
- package/dist/generated/models/assistantStreamMarkerName.js +1 -0
- package/dist/generated/models/failureReasonCode.d.ts +33 -0
- package/dist/generated/models/failureReasonCode.js +10 -0
- package/dist/generated/models/index.d.ts +2 -0
- package/dist/generated/models/index.js +2 -0
- package/dist/generated/models/replyWithdrawnMarker.d.ts +4 -0
- package/dist/generated/models/replyWithdrawnMarker.js +1 -0
- package/dist/generated/models/servingErrorCode.d.ts +5 -4
- package/dist/generated/models/servingErrorCode.js +1 -0
- package/dist/generated/models/turnFailedMarker.d.ts +3 -0
- package/dist/headless.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +0 -1
- package/dist/styles.css +1 -1
- package/dist/transport/replay-stream-agent.js +4 -0
- package/dist/transport/stream-resume.d.ts +6 -2
- package/dist/transport/stream-resume.js +12 -4
- package/package.json +1 -1
- package/dist/core/tool-offload-anchors.js +0 -7
|
@@ -6,7 +6,6 @@ import { connectionErrorRecorder, messagesSnapshotRecorder, } from "../core/mess
|
|
|
6
6
|
import { EMPTY_TOOL_CALL_DISPLAY_ANCHORS, toolCallDisplayRecorder, withToolCallDisplayAnchored, } from "../core/tool-call-display-anchors.js";
|
|
7
7
|
import { EMPTY_TOOL_CANCEL_ANCHORS, toolCancelRecorder, withToolCancelAnchored, } from "../core/tool-cancel-anchors.js";
|
|
8
8
|
import { EMPTY_TOOL_ERROR_ANCHORS, toolErrorRecorder, withToolErrorAnchored, } from "../core/tool-error-anchors.js";
|
|
9
|
-
import { EMPTY_TOOL_OFFLOAD_ANCHORS, toolOffloadRecorder, withToolOffloadAnchored, } from "../core/tool-offload-anchors.js";
|
|
10
9
|
import { EMPTY_TOOL_DENIAL_LEDGER, toolDenialRecorder, withToolDenialMarkerFolded, } from "../core/tool-denial-anchors.js";
|
|
11
10
|
import { EMPTY_TOOL_REFUSAL_ANCHORS, toolRefusalRecorder, withToolRefusalAnchored, } from "../core/tool-refusal-anchors.js";
|
|
12
11
|
import { transcriptBlocksOf, } from "../core/transcript-rows.js";
|
|
@@ -33,7 +32,6 @@ function useAgentRecording(agent, onStreamError) {
|
|
|
33
32
|
const [displayAnchors, setDisplayAnchors] = useState(EMPTY_TOOL_CALL_DISPLAY_ANCHORS);
|
|
34
33
|
const [errorAnchors, setErrorAnchors] = useState(EMPTY_TOOL_ERROR_ANCHORS);
|
|
35
34
|
const [cancelAnchors, setCancelAnchors] = useState(EMPTY_TOOL_CANCEL_ANCHORS);
|
|
36
|
-
const [offloadAnchors, setOffloadAnchors] = useState(EMPTY_TOOL_OFFLOAD_ANCHORS);
|
|
37
35
|
const [refusalAnchors, setRefusalAnchors] = useState(EMPTY_TOOL_REFUSAL_ANCHORS);
|
|
38
36
|
const [denialLedger, setDenialLedger] = useState(EMPTY_TOOL_DENIAL_LEDGER);
|
|
39
37
|
const streamErrorRef = useLatestRef(onStreamError);
|
|
@@ -46,7 +44,6 @@ function useAgentRecording(agent, onStreamError) {
|
|
|
46
44
|
agent.subscribe(toolCallDisplayRecorder(anchorInto(setDisplayAnchors, withToolCallDisplayAnchored))),
|
|
47
45
|
agent.subscribe(toolErrorRecorder(anchorInto(setErrorAnchors, withToolErrorAnchored))),
|
|
48
46
|
agent.subscribe(toolCancelRecorder(anchorInto(setCancelAnchors, withToolCancelAnchored))),
|
|
49
|
-
agent.subscribe(toolOffloadRecorder(anchorInto(setOffloadAnchors, withToolOffloadAnchored))),
|
|
50
47
|
agent.subscribe(toolRefusalRecorder(anchorInto(setRefusalAnchors, withToolRefusalAnchored))),
|
|
51
48
|
agent.subscribe(toolDenialRecorder(anchorInto(setDenialLedger, withToolDenialMarkerFolded))),
|
|
52
49
|
];
|
|
@@ -64,13 +61,12 @@ function useAgentRecording(agent, onStreamError) {
|
|
|
64
61
|
displayAnchors,
|
|
65
62
|
errorAnchors,
|
|
66
63
|
cancelAnchors,
|
|
67
|
-
offloadAnchors,
|
|
68
64
|
refusalAnchors,
|
|
69
65
|
denialLedger,
|
|
70
66
|
};
|
|
71
67
|
}
|
|
72
68
|
function AgentModeTranscript({ agent, onStreamError, markers, markerBoundaries, resumeAnchoredIds = NO_IDS, deliveryAnchoredIds = NO_IDS, turnAttachments, frameClaimsLiveness, scrollClassName, messageView, toolRow, input, welcomeScreen, threadList, }) {
|
|
73
|
-
const { snapshot, displayAnchors, errorAnchors, cancelAnchors,
|
|
69
|
+
const { snapshot, displayAnchors, errorAnchors, cancelAnchors, refusalAnchors, denialLedger, } = useAgentRecording(agent, onStreamError);
|
|
74
70
|
const delivered = snapshot ?? EMPTY_SNAPSHOT;
|
|
75
71
|
const anchors = useMemo(() => ({
|
|
76
72
|
resumeAnchors: new Map([...resumeAnchoredIds].map((id) => [id, null])),
|
|
@@ -81,7 +77,6 @@ function AgentModeTranscript({ agent, onStreamError, markers, markerBoundaries,
|
|
|
81
77
|
toolDenialAnchors: denialLedger.denied,
|
|
82
78
|
toolCallDisplayAnchors: displayAnchors,
|
|
83
79
|
turnFailedAnchors: NO_MAP,
|
|
84
|
-
toolOffloadAnchors: offloadAnchors,
|
|
85
80
|
toolSchemaAnchors: NO_MAP,
|
|
86
81
|
blockTimingAnchors: NO_MAP,
|
|
87
82
|
turnUsageAnchors: NO_MAP,
|
|
@@ -92,7 +87,6 @@ function AgentModeTranscript({ agent, onStreamError, markers, markerBoundaries,
|
|
|
92
87
|
deliveryAnchoredIds,
|
|
93
88
|
errorAnchors,
|
|
94
89
|
cancelAnchors,
|
|
95
|
-
offloadAnchors,
|
|
96
90
|
refusalAnchors,
|
|
97
91
|
denialLedger.denied,
|
|
98
92
|
displayAnchors,
|
|
@@ -3,24 +3,30 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { humanFileSize } from "./human-file-size.js";
|
|
5
5
|
import { FileIcon } from "./icons.js";
|
|
6
|
+
import { openMintedUrlInNewWindow } from "./open-minted-url.js";
|
|
7
|
+
import { TfButton } from "./primitives/button.js";
|
|
6
8
|
import { useOptionalAssistantSession } from "./teaflask-assistant-provider.js";
|
|
7
9
|
export function AttachmentChips({ attachments, }) {
|
|
8
10
|
if (attachments.length === 0) {
|
|
9
11
|
return null;
|
|
10
12
|
}
|
|
11
|
-
return (_jsx("div", { className: "tf:flex tf:flex-wrap tf:justify-end tf:gap-2", children: attachments.map((attachment) => attachment.kind === "image" ? (_jsx(AttachmentImage, { attachment: attachment }, attachment.id)) : (_jsx(FileChip, { attachment: attachment }, attachment.id))) }));
|
|
13
|
+
return (_jsx("div", { className: "tf:flex tf:flex-wrap tf:items-end tf:justify-end tf:gap-2", children: attachments.map((attachment) => attachment.kind === "image" ? (_jsx(AttachmentImage, { attachment: attachment }, attachment.id)) : (_jsx(FileChip, { attachment: attachment }, attachment.id))) }));
|
|
12
14
|
}
|
|
13
15
|
function AttachmentImage({ attachment }) {
|
|
14
16
|
const url = useAttachmentUrl(attachment.id);
|
|
15
17
|
if (url === null) {
|
|
16
18
|
return _jsx(FileChip, { attachment: attachment });
|
|
17
19
|
}
|
|
18
|
-
return (_jsx("
|
|
20
|
+
return (_jsx("a", { href: url, target: "_blank", rel: "noopener noreferrer", "aria-label": `Open ${attachment.filename}`, className: "tf:block tf:max-w-full tf:rounded-lg tf:focus-visible:outline-2 " +
|
|
21
|
+
"tf:focus-visible:outline-offset-2 tf:focus-visible:outline-tf-ring", children: _jsx("img", { src: url, alt: attachment.filename, loading: "lazy", className: "tf:block tf:max-h-48 tf:max-w-full tf:rounded-lg tf:ring-1 tf:ring-tf-border tf:ring-inset" }) }));
|
|
19
22
|
}
|
|
20
23
|
function FileChip({ attachment }) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const store = useOptionalAssistantSession()?.store ?? null;
|
|
25
|
+
return (_jsxs(TfButton, { variant: "fileTile", "aria-label": `Open ${attachment.filename}`, title: attachment.filename, onClick: () => {
|
|
26
|
+
if (store !== null) {
|
|
27
|
+
openMintedUrlInNewWindow(() => store.mintAttachmentDownloadUrl(attachment.id));
|
|
28
|
+
}
|
|
29
|
+
}, children: [_jsx("span", { className: "tf:text-tf-muted-foreground", children: _jsx(FileIcon, {}) }), _jsx("span", { className: "tf:min-w-0 tf:truncate", children: attachment.filename }), _jsx("span", { className: "tf:shrink-0 tf:text-tf-muted-foreground", children: humanFileSize(attachment.byte_size) })] }));
|
|
24
30
|
}
|
|
25
31
|
const URL_REFRESH_FLOOR_MS = 30_000;
|
|
26
32
|
function useAttachmentUrl(attachmentId) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useLayoutEffect, useRef, useState, } from "react";
|
|
4
|
-
import { ASSISTANT_MESSAGE_MAX_CHARS, } from "../contract/threads.js";
|
|
5
4
|
import { attachmentAcceptOf, ComposerAttachmentChip, } from "./composer-attachment-chip.js";
|
|
6
5
|
import { ComposerBanner } from "./composer-banner.js";
|
|
7
6
|
import { useConversation } from "./conversation-context.js";
|
|
@@ -13,7 +12,7 @@ import { ModelPickerShelf } from "./model-picker-chip.js";
|
|
|
13
12
|
import { useOptionalAssistantSession } from "./teaflask-assistant-provider.js";
|
|
14
13
|
import { NO_GATE } from "../core/connect-gate.js";
|
|
15
14
|
import { attachmentRoomOf } from "../core/composer-attachments.js";
|
|
16
|
-
import { attachmentDraftFailed, capSlotsSpentBy,
|
|
15
|
+
import { attachmentDraftFailed, capSlotsSpentBy, composerKeyHandlersOf, } from "../core/composer-policy.js";
|
|
17
16
|
import { ObservableCell } from "../core/observable-cell.js";
|
|
18
17
|
import { useCell } from "./use-store-cell.js";
|
|
19
18
|
const NO_POLICY = new ObservableCell(null);
|
|
@@ -172,16 +171,16 @@ function ComposerSlabAndShelf({ draft, setDraft, attachments, policy, gate, busy
|
|
|
172
171
|
"tf:transition-[border-color,box-shadow] " +
|
|
173
172
|
"tf:focus-within:border-tf-foreground/25", children: [attachments.length > 0 ? (_jsx("div", { className: "tf:flex tf:flex-wrap tf:gap-1.5 tf:px-1 tf:pt-1 tf:pb-2", children: attachments.map((item) => (_jsx(ComposerAttachmentChip, { item: item, onRemove: () => {
|
|
174
173
|
attach.removeAttachment(item.localId);
|
|
175
|
-
} }, item.localId))) })) : null, _jsx(TfTextarea, { ref: textareaRef, rows: 2, variant: "bare", "data-tf-autofocus": "", value: draft,
|
|
174
|
+
} }, item.localId))) })) : null, _jsx(TfTextarea, { ref: textareaRef, rows: 2, variant: "bare", "data-tf-autofocus": "", value: draft, placeholder: "Ask anything", "aria-label": "Message", onChange: (event) => {
|
|
176
175
|
setDraft(event.target.value);
|
|
177
|
-
}, onKeyDown: composerKeys, onFocus: onTextareaFocus, onBlur: onTextareaBlur }), _jsx(ComposerControlsRow, {
|
|
176
|
+
}, onKeyDown: composerKeys, onFocus: onTextareaFocus, onBlur: onTextareaBlur }), _jsx(ComposerControlsRow, { attachments: attachments, policy: policy, busy: busy, pendingSend: pendingSend, stopping: stopping, stopTurn: stopTurn, stopOnEscape: stopOnEscape, send: send, attach: attach, textareaRef: textareaRef })] }), gate.kind === "none" ? _jsx(ModelPickerShelf, {}) : null] }));
|
|
178
177
|
}
|
|
179
|
-
function ComposerControlsRow({
|
|
178
|
+
function ComposerControlsRow({ attachments, policy, busy, pendingSend, stopping, stopTurn, stopOnEscape, send, attach, textareaRef, }) {
|
|
180
179
|
const fileInputRef = useRef(null);
|
|
181
180
|
return (_jsxs("div", { className: "tf:flex tf:items-center tf:gap-2 tf:pt-1", children: [policy !== null ? (_jsxs(_Fragment, { children: [_jsx(TfFileInput, { ref: fileInputRef, multiple: true, accept: attachmentAcceptOf(policy), onPickFiles: attach.addFiles }), _jsx(TfButton, { variant: "icon", onClick: () => {
|
|
182
181
|
fileInputRef.current?.click();
|
|
183
182
|
}, disabled: capSlotsSpentBy(attachments) >= attach.attachmentRoom ||
|
|
184
|
-
send.sending, "aria-label": "Attach a file", children: _jsx(PlusIcon, {}) })] })) : null, _jsxs("div", { className: "tf:ml-auto tf:flex tf:shrink-0 tf:items-center tf:gap-2", children: [stopping ? (_jsx("span", { role: "status", className: "tf:text-xs tf:text-tf-muted-foreground", children: "Stopping\u2026" })) : null,
|
|
183
|
+
send.sending, "aria-label": "Attach a file", children: _jsx(PlusIcon, {}) })] })) : null, _jsxs("div", { className: "tf:ml-auto tf:flex tf:shrink-0 tf:items-center tf:gap-2", children: [stopping ? (_jsx("span", { role: "status", className: "tf:text-xs tf:text-tf-muted-foreground", children: "Stopping\u2026" })) : null, busy && !pendingSend ? (_jsx(TfButton, { variant: "stop", onClick: () => {
|
|
185
184
|
void stopTurn();
|
|
186
185
|
textareaRef.current?.focus();
|
|
187
186
|
}, onKeyDown: stopOnEscape, "aria-disabled": stopping, "aria-label": "Stop generating", children: _jsx(StopIcon, {}) })) : (_jsx(TfButton, { variant: "send", onClick: () => {
|
|
@@ -11,6 +11,7 @@ import { TfButton } from "./primitives/button.js";
|
|
|
11
11
|
import { ScrollFollowGuards } from "./scroll-follow-guards.js";
|
|
12
12
|
import { HostSlotBoundary } from "./slot-boundary.js";
|
|
13
13
|
import { ShimmerText, TypingIndicator } from "./streaming-states.js";
|
|
14
|
+
import { ReleaseFollowProvider } from "./transcript-follow.js";
|
|
14
15
|
import { useMessageListDerivations, } from "./use-transcript-folds.js";
|
|
15
16
|
export function MessageList({ cards, typing = false, live = false, turnOpen, identityToken = PRIMARY_AGENT_IDENTITY_TOKEN, decisionSurfacesInShelf = false, markers: Markers, markerBoundaries, turnStartIds, frameClaimsLiveness = false, emptyFallback, scrollClassName, messageView, toolRow, placementHolds = true, ...source }) {
|
|
16
17
|
const { foldedBlocks, displayRows, currentTurnStart, blockStarts, unrowedAnsweredAsks, liveIndex, hostEmptySurfaceShown, livenessClaimant, } = useMessageListDerivations(source, {
|
|
@@ -24,7 +25,7 @@ export function MessageList({ cards, typing = false, live = false, turnOpen, ide
|
|
|
24
25
|
emptyFallback,
|
|
25
26
|
});
|
|
26
27
|
const rowAt = (row, flatIndex) => (_jsx(Row, { row: row, cards: cards, turnLive: live && flatIndex === liveIndex, inCurrentTurn: placementHolds && flatIndex >= currentTurnStart, identityToken: identityToken, decisionSurfacesInShelf: decisionSurfacesInShelf, messageView: messageView, toolRow: toolRow }, row.key));
|
|
27
|
-
return (_jsxs(StickToBottom, { "data-tf-message-list": "", className: "tf:relative tf:min-h-0 tf:flex-1", resize: "instant", initial: "instant", role: "log", children: [_jsxs(StickToBottom.Content, { scrollClassName: scrollClassName, className: "tf:mx-auto tf:flex tf:w-full tf:max-w-3xl tf:flex-col tf:px-4 tf:py-4", children: [hostEmptySurfaceShown ? emptyFallback : null, foldedBlocks !== null ? (_jsx(MarkedBlocks, { blocks: foldedBlocks, blockStarts: blockStarts, markers: Markers, rowAt: rowAt })) : (displayRows.map((row, index) => rowAt(row, index))), _jsx(TranscriptTail, { unrowedAnsweredAsks: unrowedAnsweredAsks, livenessClaimant: livenessClaimant })] }), _jsx(ScrollFollowGuards, {}), _jsx(ScrollToBottom, { live: live })] }));
|
|
28
|
+
return (_jsxs(StickToBottom, { "data-tf-message-list": "", className: "tf:relative tf:min-h-0 tf:flex-1", resize: "instant", initial: "instant", role: "log", children: [_jsx(ReleaseFollowProvider, { children: _jsxs(StickToBottom.Content, { scrollClassName: scrollClassName, className: "tf:mx-auto tf:flex tf:w-full tf:max-w-3xl tf:flex-col tf:px-4 tf:py-4", children: [hostEmptySurfaceShown ? emptyFallback : null, foldedBlocks !== null ? (_jsx(MarkedBlocks, { blocks: foldedBlocks, blockStarts: blockStarts, markers: Markers, rowAt: rowAt })) : (displayRows.map((row, index) => rowAt(row, index))), _jsx(TranscriptTail, { unrowedAnsweredAsks: unrowedAnsweredAsks, livenessClaimant: livenessClaimant })] }) }), _jsx(ScrollFollowGuards, {}), _jsx(ScrollToBottom, { live: live })] }));
|
|
28
29
|
}
|
|
29
30
|
function MarkedBlocks({ blocks, blockStarts, markers: Markers, rowAt, }) {
|
|
30
31
|
return blocks.map((block, blockIndex) => (_jsxs(Fragment, { children: [Markers !== undefined ? (_jsx(HostSlotBoundary, { slot: "markers", fallback: null, children: _jsx(Markers, { message: { id: block.messageId }, position: "before" }) })) : null, block.rows.map((row, index) => rowAt(row, blockStarts[blockIndex] + index)), Markers !== undefined ? (_jsx(HostSlotBoundary, { slot: "markers", fallback: null, children: _jsx(Markers, { message: { id: block.messageId }, position: "after" }) })) : null] }, block.messageId)));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function openMintedUrlInNewWindow(mint) {
|
|
2
|
+
const opened = window.open("", "_blank");
|
|
3
|
+
if (opened === null) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
opened.opener = null;
|
|
7
|
+
mint().then((minted) => {
|
|
8
|
+
opened.location = minted.url;
|
|
9
|
+
}, () => {
|
|
10
|
+
opened.close();
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -65,6 +65,9 @@ const VARIANT_CLASSES = {
|
|
|
65
65
|
"tf:bg-tf-transparent tf:px-3 tf:py-2.5 tf:text-left tf:text-tf-label tf:text-tf-foreground " +
|
|
66
66
|
"tf:wrap-anywhere tf:hover:bg-tf-accent tf:hover:text-tf-accent-foreground " +
|
|
67
67
|
"tf:disabled:opacity-50 tf:aria-checked:bg-tf-emphasis tf:aria-checked:text-tf-foreground",
|
|
68
|
+
fileTile: "tf:max-w-full tf:cursor-pointer tf:rounded-lg tf:border-0 tf:bg-tf-background tf:px-2.5 tf:py-1.5 " +
|
|
69
|
+
"tf:text-xs tf:text-tf-foreground tf:ring-1 tf:ring-tf-border tf:ring-inset " +
|
|
70
|
+
"tf:hover:bg-tf-accent tf:hover:text-tf-accent-foreground",
|
|
68
71
|
};
|
|
69
72
|
export const TfButton = forwardRef(function TfButton({ variant = "primary", className, type, ...props }, ref) {
|
|
70
73
|
return (_jsx("button", { ref: ref, type: type ?? "button", ...(variant === "secondary" ? { "data-tf-secondary": "" } : {}), className: cx("tf:appearance-none", "tf:inline-flex tf:items-center tf:gap-1.5 tf:transition-colors", "tf:focus-visible:outline-2 tf:focus-visible:outline-offset-2 tf:focus-visible:outline-tf-ring", "tf:disabled:cursor-not-allowed", VARIANT_CLASSES[variant], className), ...props }));
|
|
@@ -86,9 +86,10 @@ export interface TeaflaskAssistantProviderBaseProps {
|
|
|
86
86
|
*/
|
|
87
87
|
onError?: (error: Error) => void;
|
|
88
88
|
/**
|
|
89
|
-
* Called with product analytics events (approval submits
|
|
90
|
-
*
|
|
91
|
-
* error goes to `onError`.
|
|
89
|
+
* Called with product analytics events (approval submits, stream failures
|
|
90
|
+
* and failed replies) that you can forward to your own analytics. If it
|
|
91
|
+
* throws, the error goes to `onError`. Without it, each failed reply writes
|
|
92
|
+
* one console warning with its reason code instead.
|
|
92
93
|
*/
|
|
93
94
|
onTelemetry?: (event: AssistantTelemetryEvent) => void;
|
|
94
95
|
/**
|
|
@@ -60,11 +60,6 @@ function _mandatoryStateWordOf(state, awaitingInput) {
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
function _toolViewOf(row) {
|
|
63
|
-
const offloaded = row.offloaded &&
|
|
64
|
-
row.errorText === undefined &&
|
|
65
|
-
row.state !== "denied" &&
|
|
66
|
-
row.state !== "cancelled" &&
|
|
67
|
-
row.state !== "refused";
|
|
68
63
|
return {
|
|
69
64
|
toolName: row.toolName,
|
|
70
65
|
state: row.state,
|
|
@@ -74,11 +69,9 @@ function _toolViewOf(row) {
|
|
|
74
69
|
output: row.errorText !== undefined ||
|
|
75
70
|
row.state === "denied" ||
|
|
76
71
|
row.state === "cancelled" ||
|
|
77
|
-
row.state === "refused"
|
|
78
|
-
offloaded
|
|
72
|
+
row.state === "refused"
|
|
79
73
|
? undefined
|
|
80
74
|
: row.result,
|
|
81
|
-
offloaded: offloaded || undefined,
|
|
82
75
|
errorText: row.errorText,
|
|
83
76
|
...(row.refusalText !== undefined ? { refusalText: row.refusalText } : {}),
|
|
84
77
|
display: row.display,
|
|
@@ -11,7 +11,6 @@ import { humanFailureSentenceOf } from "./tool-views/view-dom.js";
|
|
|
11
11
|
function heldOpenOf(view, toolView, decision) {
|
|
12
12
|
const hostViewBearing = toolView.views.some((candidate) => candidate.rung <= 2);
|
|
13
13
|
return (isCleanState(view.state) &&
|
|
14
|
-
view.offloaded !== true &&
|
|
15
14
|
(hostViewBearing || decision) &&
|
|
16
15
|
toolView.call.truncated !== true);
|
|
17
16
|
}
|
|
@@ -30,5 +29,5 @@ export function ToolRow({ view, awaitingInput = false, decisionBearing, inCurren
|
|
|
30
29
|
return (_jsxs(Disclosure, { variant: "bare", open: heldRowOpen || undefined, ...(view.toolCallId !== undefined && view.toolCallId !== ""
|
|
31
30
|
? { rootDataAttributes: { "data-tf-tool-call-id": view.toolCallId } }
|
|
32
31
|
: {}), className: "tf:w-full", summaryClassName: "tf:flex tf:w-fit tf:items-center tf:gap-3 tf:py-1.5 tf:text-tf-label tf:text-tf-muted-foreground tf:hover:text-tf-foreground", bodyClassName: "tf:flex tf:flex-col tf:gap-2 tf:pt-1 tf:pb-2 tf:ps-7", summary: _jsxs(_Fragment, { children: [toolView.icons.length === 0 ? (_jsx(OperationMark, { icon: presentation.icon, status: presentation.status })) : (_jsx(ToolViewIconSlot, { icons: toolView.icons, call: toolView.call, icon: presentation.icon })), presentation.status === "input-available" ? (_jsx(ShimmerText, { className: "tf:leading-none", children: presentation.headline })) : (_jsx("span", { className: "tf:leading-none", children: presentation.headline })), presentation.status === "output-error" ? (_jsx(StatePill, { destructive: true, children: "Failed" })) : presentation.status === "denied" ? (_jsx(StatePill, { children: "Not approved" })) : presentation.status === "cancelled" ||
|
|
33
|
-
presentation.status === "superseded" ? (_jsx(StatePill, { children: "Interrupted" })) : presentation.status === "refused" ? (_jsx(StatePill, { children: "Declined" })) : awaitingInput ? (_jsx(StatePill, { children: "Needs input" })) : null, _jsx(DisclosureChevron, { revealOnInteraction: true })] }), children: [_jsx("div", { "data-tf-semantic-view": "", className: "tf:mt-1", children: _jsx(ToolViewSlot, { views: toolView.views, call: toolView.call }) }), view.
|
|
32
|
+
presentation.status === "superseded" ? (_jsx(StatePill, { children: "Interrupted" })) : presentation.status === "refused" ? (_jsx(StatePill, { children: "Declined" })) : awaitingInput ? (_jsx(StatePill, { children: "Needs input" })) : null, _jsx(DisclosureChevron, { revealOnInteraction: true })] }), children: [_jsx("div", { "data-tf-semantic-view": "", className: "tf:mt-1", children: _jsx(ToolViewSlot, { views: toolView.views, call: toolView.call }) }), view.refusalText !== undefined && (_jsx("p", { "data-tf-refusal-reason": "", className: "tf:text-xs tf:text-tf-muted-foreground", children: view.refusalText })), view.errorText !== undefined && (_jsx("p", { "data-tf-error-reason": "", className: "tf:text-xs tf:text-tf-muted-foreground", children: humanFailureSentenceOf(view.errorText) }))] }));
|
|
34
33
|
}
|
|
@@ -142,7 +142,6 @@ function _sameDelivery(previous, next) {
|
|
|
142
142
|
before.status === after.status &&
|
|
143
143
|
before.awaitingDecision === after.awaitingDecision &&
|
|
144
144
|
before.resultText === after.resultText &&
|
|
145
|
-
before.offloaded === after.offloaded &&
|
|
146
145
|
before.errorText === after.errorText &&
|
|
147
146
|
before.refusalText === after.refusalText &&
|
|
148
147
|
before.argsSchema === after.argsSchema &&
|
|
@@ -58,8 +58,7 @@ export declare function stringOf(value: unknown): string | null;
|
|
|
58
58
|
export declare function statelessToolView(render: (props: ToolViewProps) => HTMLElement): ToolViewAdapter;
|
|
59
59
|
/**
|
|
60
60
|
* True when the call finished successfully and its own result is available
|
|
61
|
-
* (status "output-available"
|
|
62
|
-
* result.
|
|
61
|
+
* (status "output-available"). Check it before drawing a result.
|
|
63
62
|
*/
|
|
64
63
|
export declare function settledWithResult(call: ToolViewCall): boolean;
|
|
65
64
|
/**
|
|
@@ -69,8 +68,8 @@ export declare function settledWithResult(call: ToolViewCall): boolean;
|
|
|
69
68
|
export declare function settledOutcomeNotesOf(call: ToolViewCall): HTMLElement[];
|
|
70
69
|
/**
|
|
71
70
|
* A short sentence for a call that ended without a usable result (failed,
|
|
72
|
-
* declined, not approved, didn't run
|
|
73
|
-
*
|
|
71
|
+
* declined, not approved, didn't run or interrupted). Null while the call is
|
|
72
|
+
* running or when it finished normally.
|
|
74
73
|
*/
|
|
75
74
|
export declare function settledOutcomeNoteOf(call: ToolViewCall): string | null;
|
|
76
75
|
/** An action's recorded HTTP response, as read by actionEnvelopeOf. */
|
|
@@ -140,7 +140,7 @@ export function statelessToolView(render) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
export function settledWithResult(call) {
|
|
143
|
-
return call.status === "output-available"
|
|
143
|
+
return call.status === "output-available";
|
|
144
144
|
}
|
|
145
145
|
export function settledOutcomeNotesOf(call) {
|
|
146
146
|
const sentence = settledOutcomeNoteOf(call);
|
|
@@ -163,9 +163,6 @@ export function settledOutcomeNoteOf(call) {
|
|
|
163
163
|
case "output-available":
|
|
164
164
|
break;
|
|
165
165
|
}
|
|
166
|
-
if (call.offloaded === true) {
|
|
167
|
-
return "The result was replaced by a shortened preview and isn't rendered here.";
|
|
168
|
-
}
|
|
169
166
|
return null;
|
|
170
167
|
}
|
|
171
168
|
export function actionEnvelopeOf(resultText) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
import { useStickToBottomContext } from "use-stick-to-bottom";
|
|
5
|
+
const ReleaseFollowContext = createContext(() => undefined);
|
|
6
|
+
export function ReleaseFollowProvider({ children }) {
|
|
7
|
+
const { stopScroll } = useStickToBottomContext();
|
|
8
|
+
return (_jsx(ReleaseFollowContext.Provider, { value: stopScroll, children: children }));
|
|
9
|
+
}
|
|
10
|
+
export function useReleaseTranscriptFollow() {
|
|
11
|
+
return useContext(ReleaseFollowContext);
|
|
12
|
+
}
|
|
@@ -6,6 +6,7 @@ import { messagesRecorder, streamErrorRecorder, } from "../core/messages-cell.js
|
|
|
6
6
|
import { TailSlotPool } from "../core/tail-slot-pool.js";
|
|
7
7
|
import { toolCancelRecorder, withToolCancelAnchored, } from "../core/tool-cancel-anchors.js";
|
|
8
8
|
import { toolErrorRecorder, withToolErrorAnchored, } from "../core/tool-error-anchors.js";
|
|
9
|
+
import { EMPTY_WITHDRAWN_REPLIES, withdrawnReplyRecorder, withoutWithdrawnReplies, withRepliesWithdrawn, } from "../core/withdrawn-anchors.js";
|
|
9
10
|
import { ServingReplayStreamAgent } from "../transport/replay-stream-agent.js";
|
|
10
11
|
import { streamUrlForChild } from "../transport/serving-api.js";
|
|
11
12
|
import { resumeMarkerRecorder } from "../transport/stream-resume.js";
|
|
@@ -14,7 +15,6 @@ const THREADLESS_ANCHORS = {
|
|
|
14
15
|
subagentDeliveryAnchors: new Map(),
|
|
15
16
|
toolCallDisplayAnchors: new Map(),
|
|
16
17
|
toolSchemaAnchors: new Map(),
|
|
17
|
-
toolOffloadAnchors: new Set(),
|
|
18
18
|
toolRefusalAnchors: new Map(),
|
|
19
19
|
toolDecisionAnchors: new Set(),
|
|
20
20
|
toolDenialAnchors: new Set(),
|
|
@@ -38,6 +38,7 @@ export function useChildReplayStream({ session, threadId, childSessionId, refres
|
|
|
38
38
|
const [toolCancels, setToolCancels] = useState(() => new Set());
|
|
39
39
|
const [resumeAnchors, setResumeAnchors] = useState(() => new Map());
|
|
40
40
|
const [blockTimings, setBlockTimings] = useState(() => new Map());
|
|
41
|
+
const [withdrawnReplies, setWithdrawnReplies] = useState(EMPTY_WITHDRAWN_REPLIES);
|
|
41
42
|
const { tailed, slotDenied } = useChildTailLease(streamEnd);
|
|
42
43
|
useEffect(() => {
|
|
43
44
|
if (!tailed) {
|
|
@@ -56,6 +57,7 @@ export function useChildReplayStream({ session, threadId, childSessionId, refres
|
|
|
56
57
|
setToolCancels,
|
|
57
58
|
setResumeAnchors,
|
|
58
59
|
setBlockTimings,
|
|
60
|
+
setWithdrawnReplies,
|
|
59
61
|
},
|
|
60
62
|
});
|
|
61
63
|
}, [session, threadId, childSessionId, tailed, attempt, refreshLedger]);
|
|
@@ -66,8 +68,9 @@ export function useChildReplayStream({ session, threadId, childSessionId, refres
|
|
|
66
68
|
toolCancelAnchors: toolCancels,
|
|
67
69
|
blockTimingAnchors: blockTimings,
|
|
68
70
|
}), [resumeAnchors, toolErrors, toolCancels, blockTimings]);
|
|
71
|
+
const visibleMessages = useMemo(() => withoutWithdrawnReplies(messages, withdrawnReplies), [messages, withdrawnReplies]);
|
|
69
72
|
return {
|
|
70
|
-
messages,
|
|
73
|
+
messages: visibleMessages,
|
|
71
74
|
streamEnd,
|
|
72
75
|
tailed,
|
|
73
76
|
slotDenied,
|
|
@@ -176,11 +179,15 @@ function subscribeAnchorTaps(agent, record) {
|
|
|
176
179
|
: previous;
|
|
177
180
|
});
|
|
178
181
|
}));
|
|
182
|
+
const withdrawnTap = agent.subscribe(withdrawnReplyRecorder((marker) => {
|
|
183
|
+
record.setWithdrawnReplies((previous) => withRepliesWithdrawn(previous, marker));
|
|
184
|
+
}));
|
|
179
185
|
return () => {
|
|
180
186
|
messagesTap.unsubscribe();
|
|
181
187
|
errorAnchorTap.unsubscribe();
|
|
182
188
|
cancelAnchorTap.unsubscribe();
|
|
183
189
|
resumeAnchorTap.unsubscribe();
|
|
184
190
|
timingAnchorTap.unsubscribe();
|
|
191
|
+
withdrawnTap.unsubscribe();
|
|
185
192
|
};
|
|
186
193
|
}
|
|
@@ -38,7 +38,7 @@ export interface TranscriptSurface {
|
|
|
38
38
|
*/
|
|
39
39
|
decisionsPending: boolean;
|
|
40
40
|
/**
|
|
41
|
-
* The
|
|
41
|
+
* The message list behind the rows (user messages, assistant text,
|
|
42
42
|
* reasoning, tool calls and tool results). Use it with markerAnchors,
|
|
43
43
|
* running and turnMeta to call transcriptRowsOf yourself.
|
|
44
44
|
*/
|
|
@@ -65,7 +65,8 @@ export interface TranscriptSurface {
|
|
|
65
65
|
* as rows plus run state. Call it inside TeaflaskAssistantProvider (it throws
|
|
66
66
|
* otherwise); keep it mounted while the transcript is on screen, because that
|
|
67
67
|
* is what makes the assistant connect and load an existing conversation's
|
|
68
|
-
* history.
|
|
68
|
+
* history. A reply part the assistant withdrew is left out of messages and
|
|
69
|
+
* rows; the re-sent answer follows.
|
|
69
70
|
*/
|
|
70
71
|
export declare function useTranscript(options?: {
|
|
71
72
|
surface?: string;
|
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef, useState } from "react";
|
|
4
|
+
import { longMessagePreviewOf } from "../core/long-message.js";
|
|
3
5
|
import { conversationalTimeOf } from "../core/time-labels.js";
|
|
4
6
|
import { AttachmentChips } from "./attachment-chips.js";
|
|
5
7
|
import { CopyTextButton } from "./copy-text-button.js";
|
|
8
|
+
import { TfButton } from "./primitives/button.js";
|
|
9
|
+
import { useReleaseTranscriptFollow } from "./transcript-follow.js";
|
|
6
10
|
export function UserMessage({ children, attachments = [], createdAt, }) {
|
|
7
11
|
const time = createdAt !== undefined ? conversationalTimeOf(createdAt) : null;
|
|
8
|
-
return (_jsxs("div", { className: "tf:group/user-message tf:flex tf:flex-col tf:items-end tf:gap-2 tf:ps-20", "data-role": "user", children: [_jsx(AttachmentChips, { attachments: attachments }), children !== "" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "tf:min-w-0 tf:rounded-xl tf:bg-tf-muted tf:px-4 tf:py-2 tf:text-tf-body tf:break-words tf:whitespace-pre-wrap tf:text-tf-foreground", children: children }), _jsxs("div", { className: "tf:flex tf:h-7 tf:items-center tf:justify-end tf:gap-1", children: [time !== null ? (_jsx("time", { dateTime: createdAt, title: time.full, className: "tf:text-xs tf:text-tf-muted-foreground tf:opacity-0 tf:transition-opacity tf:group-hover/user-message:opacity-100 tf:group-focus-within/user-message:opacity-100 tf:pointer-coarse:opacity-100", children: time.label })) : null, _jsx(CopyTextButton, { text: children, label: "Copy message", className: "tf:opacity-0 tf:transition-opacity tf:group-hover/user-message:opacity-100 tf:group-focus-within/user-message:opacity-100 tf:focus-visible:opacity-100 tf:pointer-coarse:opacity-100" })] })] })) : null] }));
|
|
12
|
+
return (_jsxs("div", { className: "tf:group/user-message tf:flex tf:flex-col tf:items-end tf:gap-2 tf:ps-20", "data-role": "user", children: [_jsx(AttachmentChips, { attachments: attachments }), children !== "" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "tf:min-w-0 tf:rounded-xl tf:bg-tf-muted tf:px-4 tf:py-2 tf:text-tf-body tf:break-words tf:whitespace-pre-wrap tf:text-tf-foreground", children: _jsx(UserMessageText, { text: children }) }), _jsxs("div", { className: "tf:flex tf:h-7 tf:items-center tf:justify-end tf:gap-1", children: [time !== null ? (_jsx("time", { dateTime: createdAt, title: time.full, className: "tf:text-xs tf:text-tf-muted-foreground tf:opacity-0 tf:transition-opacity tf:group-hover/user-message:opacity-100 tf:group-focus-within/user-message:opacity-100 tf:pointer-coarse:opacity-100", children: time.label })) : null, _jsx(CopyTextButton, { text: children, label: "Copy message", className: "tf:opacity-0 tf:transition-opacity tf:group-hover/user-message:opacity-100 tf:group-focus-within/user-message:opacity-100 tf:focus-visible:opacity-100 tf:pointer-coarse:opacity-100" })] })] })) : null] }));
|
|
13
|
+
}
|
|
14
|
+
function UserMessageText({ text }) {
|
|
15
|
+
const preview = longMessagePreviewOf(text);
|
|
16
|
+
const [expanded, setExpanded] = useState(false);
|
|
17
|
+
const textId = useId();
|
|
18
|
+
const toggleRef = useRef(null);
|
|
19
|
+
const releaseFollow = useReleaseTranscriptFollow();
|
|
20
|
+
if (preview === null) {
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { id: textId, children: expanded ? text : `${preview}…` }), _jsx("div", { className: "tf:sticky tf:bottom-0 tf:bg-tf-muted tf:pt-1", children: _jsx(TfButton, { ref: toggleRef, variant: "bare", "aria-expanded": expanded, "aria-controls": textId, className: "tf:min-h-6 tf:text-tf-label tf:text-tf-muted-foreground tf:hover:text-tf-foreground", onClick: () => {
|
|
24
|
+
if (expanded) {
|
|
25
|
+
requestAnimationFrame(() => {
|
|
26
|
+
toggleRef.current?.scrollIntoView({ block: "nearest" });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
releaseFollow();
|
|
31
|
+
}
|
|
32
|
+
setExpanded(!expanded);
|
|
33
|
+
}, children: expanded ? "Show less" : "Show more" }) })] }));
|
|
9
34
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApprovalRequestedMarker, ApprovalResolvedMarker, InterruptAnswerConsumedMarker, SubagentResultsDeliveredMarker, ToolExecutionRequestedMarker } from "../generated/models/index.js";
|
|
2
2
|
export declare const RUN_RESUMED_EVENT_NAME: "run_resumed";
|
|
3
|
+
export declare const REPLY_WITHDRAWN_EVENT_NAME: "reply_withdrawn";
|
|
3
4
|
export declare const APPROVAL_REQUESTED_EVENT_NAME: "approval_requested";
|
|
4
5
|
export type ApprovalRequestedPayload = ApprovalRequestedMarker & Required<Pick<ApprovalRequestedMarker, "tool_args" | "tool_input_schema" | "tool_output_schema" | "round" | "gated" | "trust_available">>;
|
|
5
6
|
export declare const APPROVAL_RESOLVED_EVENT_NAME: "approval_resolved";
|
package/dist/contract/events.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AssistantStreamMarkerName } from "../generated/models/index.js";
|
|
2
2
|
export const RUN_RESUMED_EVENT_NAME = AssistantStreamMarkerName.run_resumed;
|
|
3
|
+
export const REPLY_WITHDRAWN_EVENT_NAME = AssistantStreamMarkerName.reply_withdrawn;
|
|
3
4
|
export const APPROVAL_REQUESTED_EVENT_NAME = AssistantStreamMarkerName.approval_requested;
|
|
4
5
|
export const APPROVAL_RESOLVED_EVENT_NAME = AssistantStreamMarkerName.approval_resolved;
|
|
5
6
|
export const INTERRUPT_ANSWER_CONSUMED_EVENT_NAME = AssistantStreamMarkerName.interrupt_answer_consumed;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { FailureReasonCode } from "../generated/models/index.js";
|
|
2
|
+
import type { FailureReasonCode } from "../generated/models/index.js";
|
|
1
3
|
/**
|
|
2
4
|
* Why clicking approve or deny sent nothing. "no_active_thread" means no
|
|
3
5
|
* conversation was open; "card_missing" means the request was no longer shown;
|
|
@@ -64,12 +66,31 @@ export interface StreamFailedTelemetryEvent {
|
|
|
64
66
|
last_committed_sse_id: string | null;
|
|
65
67
|
};
|
|
66
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Reported to onTelemetry when a reply fails while the user is watching. The
|
|
71
|
+
* user sees one general sentence, so this event's reason_code is how your page
|
|
72
|
+
* learns why; log it and never show it. Failures replayed when a conversation
|
|
73
|
+
* reopens are not reported again.
|
|
74
|
+
*/
|
|
75
|
+
export interface TurnFailedTelemetryEvent {
|
|
76
|
+
/** Always "turn_failed". */
|
|
77
|
+
name: "turn_failed";
|
|
78
|
+
/**
|
|
79
|
+
* The conversation's thread_id, the failed reply's turn_id, and its
|
|
80
|
+
* reason_code, the same code the stream's turn_failed event carries as
|
|
81
|
+
* reasonCode.
|
|
82
|
+
*/
|
|
83
|
+
properties: {
|
|
84
|
+
thread_id: string;
|
|
85
|
+
turn_id: string;
|
|
86
|
+
reason_code: FailureReasonCode;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
67
89
|
/**
|
|
68
90
|
* Any event passed to the provider's onTelemetry callback, for forwarding to
|
|
69
91
|
* your own analytics. None carry message text, tool arguments or prompts.
|
|
70
92
|
*/
|
|
71
|
-
export type AssistantTelemetryEvent = ApprovalSubmitTelemetryEvent | StreamFailedTelemetryEvent;
|
|
93
|
+
export type AssistantTelemetryEvent = ApprovalSubmitTelemetryEvent | StreamFailedTelemetryEvent | TurnFailedTelemetryEvent;
|
|
72
94
|
export declare function streamFailureClassOf(error: Error, context: {
|
|
73
95
|
sawBodyBytes: boolean;
|
|
74
96
|
}): StreamFailureClass;
|
|
75
|
-
export {};
|
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
export type { CreateServingAttachmentUploadResponse as AttachmentUploadIntent, HighlightResult, NavigateResult, PrefillFormResult, ReadPageResult, ResolveServingTurnApprovalRequest as ResolveTurnApprovalRequest, ResolveServingTurnApprovalResponse as ResolveTurnApprovalResponse, ResolveServingTurnToolResultsRequest as ResolveTurnToolResultsRequest, ResolveServingTurnToolResultsResponse as ResolveTurnToolResultsResponse, SendServingAssistantMessageRequest as SendAssistantMessageRequest, SendServingAssistantMessageResponse as SendAssistantMessageResponse, ServingAssistantThreadDetailResponse as AssistantThreadDetailResponse, ServingAssistantThreadResponse as ServingAssistantThread, ServingAssistantTurnResponse as ServingAssistantTurn, ServingAttachmentResponse as ServingAttachment, ServingTurnAttachmentResponse as TurnAttachment, StopServingTurnResponse as StopTurnResponse, ThreadModelPickResponse as ThreadModelPick, AssistantTurnStatus as TurnStatus, } from "../generated/models/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* The longest message a user can send, in characters; the composer stops input
|
|
4
|
-
* at this length and the server rejects longer messages.
|
|
5
|
-
*/
|
|
6
|
-
export declare const ASSISTANT_MESSAGE_MAX_CHARS = 4000;
|
|
7
2
|
export declare const ASSISTANT_MAX_ATTACHMENTS = 10;
|
package/dist/contract/threads.js
CHANGED
|
@@ -167,6 +167,7 @@ class SessionEntry {
|
|
|
167
167
|
this._reportToEveryRegistrant(error);
|
|
168
168
|
},
|
|
169
169
|
onTelemetry: (event) => {
|
|
170
|
+
this._warnOfAFailureNoHostLogs(event);
|
|
170
171
|
for (const registration of [...this.registrations]) {
|
|
171
172
|
try {
|
|
172
173
|
registration.registrant.onTelemetry?.(event);
|
|
@@ -230,6 +231,15 @@ class SessionEntry {
|
|
|
230
231
|
}
|
|
231
232
|
return merged;
|
|
232
233
|
}
|
|
234
|
+
_warnOfAFailureNoHostLogs(event) {
|
|
235
|
+
if (event.name !== "turn_failed" || this._someRegistrantLogsTelemetry()) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
console.warn("[teaflask] reply failed:", event.properties.reason_code, event.properties.turn_id);
|
|
239
|
+
}
|
|
240
|
+
_someRegistrantLogsTelemetry() {
|
|
241
|
+
return [...this.registrations].some((registration) => registration.registrant.onTelemetry !== null);
|
|
242
|
+
}
|
|
233
243
|
_reportToEveryRegistrant(error) {
|
|
234
244
|
for (const registration of [...this.registrations]) {
|
|
235
245
|
try {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ServingAttachment } from "../contract/threads.js";
|
|
2
2
|
import type { AttachmentKind } from "../generated/models/index.js";
|
|
3
|
-
export declare const CHARACTER_COUNT_VISIBLE_FROM: number;
|
|
4
3
|
/** One file staged in the composer for the next message. */
|
|
5
4
|
export interface AttachmentDraft<A = ServingAttachment> {
|
|
6
5
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Message } from "@ag-ui/core";
|
|
2
|
+
import type { AssistantTelemetryEvent } from "../contract/telemetry.js";
|
|
2
3
|
import { ServingReplayStreamAgent } from "../transport/replay-stream-agent.js";
|
|
3
4
|
import { type StreamResumeStore } from "../transport/stream-resume.js";
|
|
4
5
|
import type { TokenSession } from "../transport/token-session.js";
|
|
@@ -49,11 +50,6 @@ export interface TranscriptMarkers {
|
|
|
49
50
|
* The tool's explanation for each call it declined, keyed by tool call id.
|
|
50
51
|
*/
|
|
51
52
|
toolRefusalAnchors: ReadonlyMap<string, string>;
|
|
52
|
-
/**
|
|
53
|
-
* Ids of tool calls whose output was too large and was replaced by a
|
|
54
|
-
* shortened stand-in.
|
|
55
|
-
*/
|
|
56
|
-
toolOffloadAnchors: ReadonlySet<string>;
|
|
57
53
|
/**
|
|
58
54
|
* Ids of tool calls the user was asked to approve or answer a question for.
|
|
59
55
|
* Treat a missing set as empty.
|
|
@@ -114,6 +110,8 @@ export interface ConnectionEpochDeps {
|
|
|
114
110
|
onRunSettled: () => void;
|
|
115
111
|
onQuietClose: (stillStreaming: () => boolean) => void;
|
|
116
112
|
onWorkflowRestarted: () => void;
|
|
113
|
+
onTelemetry: (event: AssistantTelemetryEvent) => void;
|
|
114
|
+
settledFailedTurnId: string | null;
|
|
117
115
|
}
|
|
118
116
|
export interface ConnectionEpoch {
|
|
119
117
|
epochId: number;
|
|
@@ -123,6 +121,7 @@ export interface ConnectionEpoch {
|
|
|
123
121
|
threadId: string;
|
|
124
122
|
streamThreadId: string;
|
|
125
123
|
withExecutionInbox: (mutate: (inbox: ExecutionInbox) => boolean) => void;
|
|
124
|
+
visibleMessages: () => readonly Message[];
|
|
126
125
|
dispose: () => void;
|
|
127
126
|
}
|
|
128
127
|
export declare function beginConnectionEpoch(deps: ConnectionEpochDeps): ConnectionEpoch;
|