@jini-ai/chat 0.3.11 → 0.5.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/CHANGELOG.md +126 -0
- package/README.md +152 -6
- package/dist/core/ag-ui/entry.d.ts +53 -0
- package/dist/core/ag-ui/entry.js +135 -0
- package/dist/core/agentic/chat-capabilities.d.ts +1 -1
- package/dist/core/agentic/index.d.ts +1 -1
- package/dist/core/agentic/index.js +1 -1
- package/dist/core/assistant-content.d.ts +6 -2
- package/dist/core/assistant-content.js +2 -2
- package/dist/core/compact-events.d.ts +3 -1
- package/dist/core/compact-events.js +1 -1
- package/dist/core/events.d.ts +1 -1
- package/dist/core/events.js +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/messages.d.ts +4 -2
- package/dist/core/messages.js +2 -2
- package/dist/core/partial-json.d.ts +6 -2
- package/dist/core/partial-json.js +3 -3
- package/dist/core/persistence/index.d.ts +1 -1
- package/dist/core/persistence/index.js +1 -1
- package/dist/core/persistence/ports.d.ts +2 -123
- package/dist/core/persistence/title.d.ts +3 -1
- package/dist/core/persistence/title.js +1 -1
- package/dist/core/question-form/format.d.ts +16 -7
- package/dist/core/question-form/format.js +3 -3
- package/dist/core/question-form/normalize.d.ts +7 -2
- package/dist/core/question-form/normalize.js +5 -5
- package/dist/core/question-form/scan.d.ts +18 -6
- package/dist/core/question-form/scan.js +14 -14
- package/dist/core/run-activity.d.ts +16 -4
- package/dist/core/run-activity.js +11 -11
- package/dist/core/run-events/entry.d.ts +121 -0
- package/dist/core/run-events/entry.js +290 -0
- package/dist/core/todos.d.ts +20 -8
- package/dist/core/todos.js +11 -11
- package/dist/core/tool-events.d.ts +3 -1
- package/dist/core/tool-events.js +1 -1
- package/dist/core/tool-output.d.ts +3 -1
- package/dist/core/tool-output.js +1 -1
- package/dist/core/tools.d.ts +14 -3
- package/dist/core/tools.js +3 -3
- package/dist/core/transcript.d.ts +13 -5
- package/dist/core/transcript.js +5 -5
- package/dist/core/util/manifest.d.ts +9 -3
- package/dist/core/util/manifest.js +3 -3
- package/dist/core/util/markdown-context.d.ts +11 -3
- package/dist/core/util/markdown-context.js +3 -3
- package/dist/core/util/markup-attributes.d.ts +3 -1
- package/dist/core/util/markup-attributes.js +1 -1
- package/dist/core/util/parser.d.ts +9 -5
- package/dist/core/util/parser.js +13 -13
- package/dist/core/util/recover.d.ts +6 -2
- package/dist/core/util/recover.js +6 -6
- package/dist/core/util/strip.d.ts +19 -5
- package/dist/core/util/strip.js +17 -17
- package/dist/core/util/validate.d.ts +3 -1
- package/dist/core/util/validate.js +1 -1
- package/dist/embed-widget/browser/page-actions.d.ts +121 -0
- package/dist/embed-widget/browser/page-actions.js +113 -0
- package/dist/embed-widget/browser/session-store.d.ts +44 -0
- package/dist/embed-widget/browser/session-store.js +103 -0
- package/dist/embed-widget/react/embed.d.ts +45 -0
- package/dist/embed-widget/react/embed.js +87 -0
- package/dist/embed-widget/react/header.d.ts +21 -0
- package/dist/embed-widget/react/header.js +18 -0
- package/dist/embed-widget/transports/fetch-sse.d.ts +75 -0
- package/dist/embed-widget/transports/fetch-sse.js +175 -0
- package/dist/react/agent-bridge/frontend-session-bridge.d.ts +15 -3
- package/dist/react/agent-bridge/frontend-session-bridge.js +13 -6
- package/dist/react/artifact-types.d.ts +3 -3
- package/dist/react/artifact-types.js +3 -3
- package/dist/react/components/A2uiSurfaceCard.js +34 -13
- package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -2
- package/dist/react/components/AttachmentPreviewModal.hooks.js +5 -5
- package/dist/react/components/AttachmentPreviewModal.js +2 -2
- package/dist/react/components/AttachmentTray.d.ts +3 -1
- package/dist/react/components/AttachmentTray.js +2 -2
- package/dist/react/components/ChatFab.js +1 -1
- package/dist/react/components/Composer.js +9 -9
- package/dist/react/components/ConversationList.js +1 -1
- package/dist/react/components/McpUiSurfaceCard.d.ts +4 -2
- package/dist/react/components/McpUiSurfaceCard.js +5 -5
- package/dist/react/components/MessageList.js +1 -1
- package/dist/react/components/MessageRow.js +9 -9
- package/dist/react/components/QuestionForm.js +5 -5
- package/dist/react/components/ToolCard.js +9 -9
- package/dist/react/components/composer-discovery.d.ts +29 -8
- package/dist/react/components/composer-discovery.js +10 -10
- package/dist/react/ext-event-renderer-registry.d.ts +15 -5
- package/dist/react/ext-event-renderer-registry.js +4 -4
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +3 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +3 -3
- package/dist/react/features/chat-pane/components/ChatPane.js +43 -43
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.d.ts +4 -1
- package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +5 -35
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.d.ts +5 -2
- package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +1 -1
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +38 -10
- package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +15 -15
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +31 -31
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.d.ts +3 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +3 -3
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.d.ts +4 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.js +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +30 -30
- package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +57 -57
- package/dist/react/features/chat-pane/rules.d.ts +13 -4
- package/dist/react/features/chat-pane/rules.js +5 -5
- package/dist/react/features/chat-pane/types.d.ts +1 -1
- package/dist/react/hooks/attachment-preview-cache.d.ts +7 -2
- package/dist/react/hooks/attachment-preview-cache.js +2 -2
- package/dist/react/hooks/composer-draft-cache.d.ts +17 -5
- package/dist/react/hooks/composer-draft-cache.js +6 -6
- package/dist/react/hooks/useArtifactStream.d.ts +5 -1
- package/dist/react/hooks/useArtifactStream.js +3 -3
- package/dist/react/hooks/useChatFabDrag.d.ts +9 -4
- package/dist/react/hooks/useChatFabDrag.js +4 -4
- package/dist/react/hooks/useComposer.js +8 -8
- package/dist/react/hooks/useConversation.js +56 -4
- package/dist/react/hooks/useExtEventGroups.d.ts +5 -1
- package/dist/react/hooks/useExtEventGroups.js +1 -1
- package/dist/react/hooks/useLatestOperation.d.ts +8 -3
- package/dist/react/hooks/useLatestOperation.js +4 -4
- package/dist/react/hooks/usePinnedTodos.d.ts +3 -1
- package/dist/react/hooks/usePinnedTodos.js +3 -3
- package/dist/react/hooks/useQuestionForms.d.ts +7 -2
- package/dist/react/hooks/useQuestionForms.js +7 -7
- package/dist/react/hooks/useRunActivity.d.ts +6 -1
- package/dist/react/hooks/useRunActivity.js +3 -3
- package/dist/react/hooks/useRunStream.d.ts +3 -1
- package/dist/react/hooks/useRunStream.js +1 -1
- package/dist/react/hooks/useToolTimeline.d.ts +3 -1
- package/dist/react/hooks/useToolTimeline.js +3 -3
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +6 -6
- package/dist/react/message-blocks.d.ts +10 -4
- package/dist/react/message-blocks.js +2 -2
- package/dist/react/styles/reference.css +3 -3
- package/dist/react/tool-renderer-registry.d.ts +8 -3
- package/dist/react/tool-renderer-registry.js +3 -3
- package/dist/react/util/defined-props.d.ts +3 -1
- package/dist/react/util/defined-props.js +1 -1
- package/dist/server/run-finalizer/entry.d.ts +47 -0
- package/dist/server/run-finalizer/entry.js +156 -0
- package/dist/server/run-finalizer/ports.d.ts +52 -0
- package/dist/store/contracts/index.d.ts +5 -0
- package/dist/store/contracts/index.js +5 -0
- package/dist/store/errors.d.ts +12 -0
- package/dist/store/errors.js +18 -0
- package/dist/store/index.d.ts +4 -0
- package/dist/store/index.js +2 -0
- package/dist/store/legacy/index.d.ts +3 -0
- package/dist/store/legacy/index.js +2 -0
- package/dist/store/legacy/sqlite/conversations.d.ts +129 -0
- package/dist/store/legacy/sqlite/conversations.js +274 -0
- package/dist/store/legacy/sqlite/index.d.ts +6 -0
- package/dist/store/legacy/sqlite/index.js +5 -0
- package/dist/store/legacy/sqlite/messages.d.ts +156 -0
- package/dist/store/legacy/sqlite/messages.js +246 -0
- package/dist/store/legacy/sqlite/schema.d.ts +3 -0
- package/dist/store/legacy/sqlite/schema.js +40 -0
- package/dist/store/pglite/index.d.ts +21 -0
- package/dist/store/pglite/index.js +17 -0
- package/dist/store/ports.d.ts +121 -0
- package/dist/store/ports.js +2 -0
- package/dist/store/postgres/index.d.ts +21 -0
- package/dist/store/postgres/index.js +17 -0
- package/dist/store/sql/boundary.d.ts +28 -0
- package/dist/store/sql/boundary.js +32 -0
- package/dist/store/sql/cursor.d.ts +24 -0
- package/dist/store/sql/cursor.js +49 -0
- package/dist/store/sql/maintenance.d.ts +9 -0
- package/dist/store/sql/maintenance.js +16 -0
- package/dist/store/sql/store.d.ts +22 -0
- package/dist/store/sql/store.js +340 -0
- package/dist/store/sql/tables.d.ts +34 -0
- package/dist/store/sql/tables.js +2 -0
- package/dist/store/sqlite/index.d.ts +36 -0
- package/dist/store/sqlite/index.js +29 -0
- package/dist/store/sqlite/schema.d.ts +49 -0
- package/dist/store/sqlite/schema.js +80 -0
- package/package.json +211 -14
- package/dist/react/features/model-picker/components/CredentialStatusBadge.d.ts +0 -7
- package/dist/react/features/model-picker/components/CredentialStatusBadge.js +0 -20
- package/dist/react/features/model-picker/components/ModelPicker.d.ts +0 -9
- package/dist/react/features/model-picker/components/ModelPicker.js +0 -34
- package/dist/react/features/model-picker/constants.d.ts +0 -6
- package/dist/react/features/model-picker/constants.js +0 -9
- package/dist/react/features/model-picker/dependencies.d.ts +0 -11
- package/dist/react/features/model-picker/dependencies.js +0 -10
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.d.ts +0 -27
- package/dist/react/features/model-picker/hooks/useModelPicker.hooks.js +0 -62
- package/dist/react/features/model-picker/index.d.ts +0 -32
- package/dist/react/features/model-picker/index.js +0 -29
- package/dist/react/features/model-picker/ports.d.ts +0 -11
- package/dist/react/features/model-picker/rules.d.ts +0 -26
- package/dist/react/features/model-picker/rules.js +0 -84
- package/dist/react/features/model-picker/types.d.ts +0 -38
- package/dist/react/features/model-picker/types.js +0 -2
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.d.ts +0 -9
- package/dist/react/poc-mcp-ui-official/PocMcpUiCard.js +0 -29
- package/dist/react/poc-mcp-ui-official/create-poc-resource.d.ts +0 -30
- package/dist/react/poc-mcp-ui-official/create-poc-resource.js +0 -44
- /package/dist/{react/features/model-picker → server/run-finalizer}/ports.js +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Resolved target supplied by the host; the client never derives a path from a title. */
|
|
2
|
+
export interface ResolvedPageTarget {
|
|
3
|
+
readonly slug: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly path: string;
|
|
6
|
+
}
|
|
7
|
+
export type PageAction = {
|
|
8
|
+
readonly type: 'navigate';
|
|
9
|
+
readonly target: ResolvedPageTarget;
|
|
10
|
+
readonly auto: boolean;
|
|
11
|
+
} | {
|
|
12
|
+
readonly type: 'scroll_to' | 'highlight';
|
|
13
|
+
readonly target: ResolvedPageTarget;
|
|
14
|
+
};
|
|
15
|
+
export type NavigateAction = Extract<PageAction, {
|
|
16
|
+
type: 'navigate';
|
|
17
|
+
}>;
|
|
18
|
+
export type NonNavigateAction = Exclude<PageAction, {
|
|
19
|
+
type: 'navigate';
|
|
20
|
+
}>;
|
|
21
|
+
export interface DirectiveCarryingEvent {
|
|
22
|
+
readonly kind: string;
|
|
23
|
+
readonly name?: string;
|
|
24
|
+
readonly data?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface SplitPageActions {
|
|
27
|
+
readonly navigate: NavigateAction | null;
|
|
28
|
+
readonly other: NonNavigateAction | null;
|
|
29
|
+
}
|
|
30
|
+
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
31
|
+
export declare function isQueuedPageAction(args: {
|
|
32
|
+
value: unknown;
|
|
33
|
+
}): args is {
|
|
34
|
+
value: PageAction;
|
|
35
|
+
};
|
|
36
|
+
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
37
|
+
export declare function isPageActionDirective(args: {
|
|
38
|
+
value: unknown;
|
|
39
|
+
}): args is {
|
|
40
|
+
value: {
|
|
41
|
+
kind: 'page_action';
|
|
42
|
+
action: PageAction;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
46
|
+
export declare function extractPageActions({ events, directiveEventName }: {
|
|
47
|
+
events: readonly DirectiveCarryingEvent[] | undefined;
|
|
48
|
+
directiveEventName: string;
|
|
49
|
+
}): PageAction[];
|
|
50
|
+
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
51
|
+
export declare function splitPageActions({ actions }: {
|
|
52
|
+
actions: readonly PageAction[];
|
|
53
|
+
}): SplitPageActions;
|
|
54
|
+
export interface TargetSelectionPolicy {
|
|
55
|
+
readonly candidateSelector: string;
|
|
56
|
+
readonly excludedAncestorSelector: string;
|
|
57
|
+
}
|
|
58
|
+
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
59
|
+
export declare function findTargetElement({ title, root, selection }: {
|
|
60
|
+
title: string;
|
|
61
|
+
root: Pick<ParentNode, 'querySelectorAll'>;
|
|
62
|
+
selection: TargetSelectionPolicy;
|
|
63
|
+
}): Element | null;
|
|
64
|
+
export interface PageActionTimerPort {
|
|
65
|
+
schedule(args: {
|
|
66
|
+
callback: () => void;
|
|
67
|
+
delayMs: number;
|
|
68
|
+
}): unknown;
|
|
69
|
+
cancel(args: {
|
|
70
|
+
handle: unknown;
|
|
71
|
+
}): void;
|
|
72
|
+
}
|
|
73
|
+
export interface PageActionPort {
|
|
74
|
+
allows(args: {
|
|
75
|
+
action: PageAction;
|
|
76
|
+
}): boolean;
|
|
77
|
+
navigate(args: {
|
|
78
|
+
action: NavigateAction;
|
|
79
|
+
}): boolean;
|
|
80
|
+
execute(args: {
|
|
81
|
+
action: NonNavigateAction;
|
|
82
|
+
}): boolean;
|
|
83
|
+
clearHighlight(args: Record<string, never>): void;
|
|
84
|
+
}
|
|
85
|
+
export interface BrowserPageActionController extends PageActionPort {
|
|
86
|
+
applyHighlight(args: {
|
|
87
|
+
element: Element;
|
|
88
|
+
}): void;
|
|
89
|
+
scrollToElement(args: {
|
|
90
|
+
element: Element;
|
|
91
|
+
}): void;
|
|
92
|
+
}
|
|
93
|
+
export interface BrowserPageActionsArgs {
|
|
94
|
+
readonly root: Pick<ParentNode, 'querySelectorAll'>;
|
|
95
|
+
readonly selection: TargetSelectionPolicy;
|
|
96
|
+
readonly allowPath: (args: {
|
|
97
|
+
path: string;
|
|
98
|
+
}) => boolean;
|
|
99
|
+
readonly navigate: (args: {
|
|
100
|
+
path: string;
|
|
101
|
+
}) => void;
|
|
102
|
+
readonly reducedMotion: (args: Record<string, never>) => boolean;
|
|
103
|
+
readonly scroll: (args: {
|
|
104
|
+
element: Element;
|
|
105
|
+
options: ScrollIntoViewOptions;
|
|
106
|
+
}) => void;
|
|
107
|
+
readonly timers: PageActionTimerPort;
|
|
108
|
+
readonly highlightClass: string;
|
|
109
|
+
readonly fadeAnimationName: string;
|
|
110
|
+
readonly cleanupFallbackMs: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
114
|
+
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
115
|
+
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
116
|
+
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
117
|
+
* never guessed from a title; each controller owns its mutable highlight state.
|
|
118
|
+
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
119
|
+
*/
|
|
120
|
+
export declare function createBrowserPageActions(args: BrowserPageActionsArgs): BrowserPageActionController;
|
|
121
|
+
//# sourceMappingURL=page-actions.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function record(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function target(value) {
|
|
5
|
+
return record(value) && typeof value.slug === 'string' && typeof value.title === 'string' && typeof value.path === 'string';
|
|
6
|
+
}
|
|
7
|
+
/** Narrows an untrusted, bare queued action. URL authorization remains a separate host policy. O(1). */
|
|
8
|
+
export function isQueuedPageAction(args) {
|
|
9
|
+
const { value } = args;
|
|
10
|
+
if (!record(value) || !target(value.target))
|
|
11
|
+
return false;
|
|
12
|
+
return value.type === 'navigate' ? typeof value.auto === 'boolean' : value.type === 'scroll_to' || value.type === 'highlight';
|
|
13
|
+
}
|
|
14
|
+
/** Narrows an untrusted directive envelope, dropping unknown kinds. O(1). */
|
|
15
|
+
export function isPageActionDirective(args) {
|
|
16
|
+
const { value } = args;
|
|
17
|
+
return record(value) && value.kind === 'page_action' && isQueuedPageAction({ value: value.action });
|
|
18
|
+
}
|
|
19
|
+
/** Preserves valid action order while ignoring unrelated events. O(events) time and output space. */
|
|
20
|
+
export function extractPageActions({ events, directiveEventName }) {
|
|
21
|
+
const actions = [];
|
|
22
|
+
for (const event of events ?? []) {
|
|
23
|
+
const envelope = { value: event.data };
|
|
24
|
+
if (event.kind === 'ext' && event.name === directiveEventName && isPageActionDirective(envelope)) {
|
|
25
|
+
actions.push(envelope.value.action);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return actions;
|
|
29
|
+
}
|
|
30
|
+
/** Returns only the first navigation and first page treatment. O(actions), O(1) extra space. */
|
|
31
|
+
export function splitPageActions({ actions }) {
|
|
32
|
+
return {
|
|
33
|
+
navigate: actions.find((a) => a.type === 'navigate') ?? null,
|
|
34
|
+
other: actions.find((a) => a.type !== 'navigate') ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Exact title match first, case-insensitive fallback; never searches excluded UI. O(candidates). */
|
|
38
|
+
export function findTargetElement({ title, root, selection }) {
|
|
39
|
+
const needle = title.trim();
|
|
40
|
+
if (!needle)
|
|
41
|
+
return null;
|
|
42
|
+
const candidates = Array.from(root.querySelectorAll(selection.candidateSelector))
|
|
43
|
+
.filter(el => !el.closest(selection.excludedAncestorSelector));
|
|
44
|
+
return candidates.find(el => (el.textContent ?? '').trim() === needle)
|
|
45
|
+
?? candidates.find(el => (el.textContent ?? '').trim().toLowerCase() === needle.toLowerCase()) ?? null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates page effects with injected DOM, navigation, URL policy, scroll and timers.
|
|
49
|
+
* At most one highlight belongs to this instance; cleanup and navigation remove it.
|
|
50
|
+
* URL policy is checked before queueing, navigation or page effects. Stale timers and descendant
|
|
51
|
+
* animation events cannot remove the current highlight. Navigation paths are resolved by the host,
|
|
52
|
+
* never guessed from a title; each controller owns its mutable highlight state.
|
|
53
|
+
* @complexity Execute O(candidates); other operations O(1), with one timer/listener retained.
|
|
54
|
+
*/
|
|
55
|
+
export function createBrowserPageActions(args) {
|
|
56
|
+
if (!Number.isFinite(args.cleanupFallbackMs) || args.cleanupFallbackMs < 0)
|
|
57
|
+
throw new Error('Invalid highlight deadline');
|
|
58
|
+
let current = null;
|
|
59
|
+
const clearHighlight = (_args) => {
|
|
60
|
+
if (!current)
|
|
61
|
+
return;
|
|
62
|
+
const old = current;
|
|
63
|
+
current = null;
|
|
64
|
+
args.timers.cancel({ handle: old.handle });
|
|
65
|
+
old.element.removeEventListener('animationend', old.onEnd);
|
|
66
|
+
old.element.classList.remove(args.highlightClass);
|
|
67
|
+
};
|
|
68
|
+
function highlight(element) {
|
|
69
|
+
if (current?.element === element)
|
|
70
|
+
return;
|
|
71
|
+
clearHighlight({});
|
|
72
|
+
const onEnd = event => {
|
|
73
|
+
if (event.target === element && event.animationName === args.fadeAnimationName)
|
|
74
|
+
clearHighlight({});
|
|
75
|
+
};
|
|
76
|
+
element.classList.add(args.highlightClass);
|
|
77
|
+
element.addEventListener('animationend', onEnd);
|
|
78
|
+
// Identity check makes even a late callback from a canceled timer harmless.
|
|
79
|
+
const handle = args.timers.schedule({ delayMs: args.cleanupFallbackMs, callback: () => {
|
|
80
|
+
if (current?.element === element && current.onEnd === onEnd)
|
|
81
|
+
clearHighlight({});
|
|
82
|
+
} });
|
|
83
|
+
current = { element, handle, onEnd };
|
|
84
|
+
}
|
|
85
|
+
const scrollToElement = ({ element }) => {
|
|
86
|
+
args.scroll({ element, options: { behavior: args.reducedMotion({}) ? 'auto' : 'smooth', block: 'center' } });
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
applyHighlight({ element }) { highlight(element); },
|
|
90
|
+
scrollToElement,
|
|
91
|
+
allows({ action }) { return args.allowPath({ path: action.target.path }); },
|
|
92
|
+
navigate({ action }) {
|
|
93
|
+
if (!args.allowPath({ path: action.target.path }))
|
|
94
|
+
return false;
|
|
95
|
+
clearHighlight({});
|
|
96
|
+
args.navigate({ path: action.target.path });
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
execute({ action }) {
|
|
100
|
+
if (!args.allowPath({ path: action.target.path }))
|
|
101
|
+
return false;
|
|
102
|
+
const element = findTargetElement({ title: action.target.title, root: args.root, selection: args.selection });
|
|
103
|
+
if (!element)
|
|
104
|
+
return false;
|
|
105
|
+
scrollToElement({ element });
|
|
106
|
+
if (action.type === 'highlight')
|
|
107
|
+
highlight(element);
|
|
108
|
+
return true;
|
|
109
|
+
},
|
|
110
|
+
clearHighlight,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=page-actions.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ChatMessage } from '../../core/messages.js';
|
|
2
|
+
/** Host-owned per-tab storage. No browser global or storage backend is selected here. */
|
|
3
|
+
export interface SessionStoragePort {
|
|
4
|
+
getItem(key: string): string | null;
|
|
5
|
+
setItem(key: string, value: string): void;
|
|
6
|
+
removeItem(key: string): void;
|
|
7
|
+
}
|
|
8
|
+
export interface PersistedEmbedState {
|
|
9
|
+
readonly open: boolean;
|
|
10
|
+
readonly messages: ChatMessage[];
|
|
11
|
+
}
|
|
12
|
+
export interface BrowserSessionStore {
|
|
13
|
+
load(args: Record<string, never>): PersistedEmbedState;
|
|
14
|
+
save(args: {
|
|
15
|
+
state: PersistedEmbedState;
|
|
16
|
+
}): void;
|
|
17
|
+
clear(args: Record<string, never>): void;
|
|
18
|
+
enqueue(args: {
|
|
19
|
+
action: unknown;
|
|
20
|
+
}): void;
|
|
21
|
+
drain(args: Record<string, never>): unknown | null;
|
|
22
|
+
}
|
|
23
|
+
export interface BrowserSessionStoreArgs {
|
|
24
|
+
readonly storage: SessionStoragePort;
|
|
25
|
+
readonly transcriptKey: string;
|
|
26
|
+
readonly actionKey: string;
|
|
27
|
+
readonly maxMessages: number;
|
|
28
|
+
readonly maxBytes: number;
|
|
29
|
+
readonly validateMessage: (args: {
|
|
30
|
+
value: unknown;
|
|
31
|
+
}) => boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates bounded, fail-soft transcript storage and a single-slot action queue.
|
|
35
|
+
* Corrupt transcripts are cleared as a whole. Actions are deleted before delivery,
|
|
36
|
+
* and a failed delete prevents delivery. Serialization/storage failures never escape.
|
|
37
|
+
* Reject oversized stored envelopes before parsing, validate the whole message batch, then trim
|
|
38
|
+
* oldest messages first. Circular serialization is fail-soft. Keys, limits, validator and Storage
|
|
39
|
+
* are injected so separate controllers never accidentally share a product-selected namespace.
|
|
40
|
+
* @complexity Load O(bytes + messages); save O(messages * serialized bytes) in the
|
|
41
|
+
* worst trimming case; queue O(serialized action bytes). State is instance-local.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createBrowserSessionStore(args: BrowserSessionStoreArgs): BrowserSessionStore;
|
|
44
|
+
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates bounded, fail-soft transcript storage and a single-slot action queue.
|
|
3
|
+
* Corrupt transcripts are cleared as a whole. Actions are deleted before delivery,
|
|
4
|
+
* and a failed delete prevents delivery. Serialization/storage failures never escape.
|
|
5
|
+
* Reject oversized stored envelopes before parsing, validate the whole message batch, then trim
|
|
6
|
+
* oldest messages first. Circular serialization is fail-soft. Keys, limits, validator and Storage
|
|
7
|
+
* are injected so separate controllers never accidentally share a product-selected namespace.
|
|
8
|
+
* @complexity Load O(bytes + messages); save O(messages * serialized bytes) in the
|
|
9
|
+
* worst trimming case; queue O(serialized action bytes). State is instance-local.
|
|
10
|
+
*/
|
|
11
|
+
export function createBrowserSessionStore(args) {
|
|
12
|
+
const { storage, transcriptKey, actionKey, maxMessages, maxBytes, validateMessage } = args;
|
|
13
|
+
if (!transcriptKey || !actionKey || transcriptKey === actionKey) {
|
|
14
|
+
throw new Error('Transcript and action keys must be nonempty and distinct');
|
|
15
|
+
}
|
|
16
|
+
if (!Number.isSafeInteger(maxMessages) || maxMessages < 0)
|
|
17
|
+
throw new Error('Invalid message limit');
|
|
18
|
+
// The smallest envelope (open=false) is 28 UTF-8 bytes.
|
|
19
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 28)
|
|
20
|
+
throw new Error('Invalid byte limit');
|
|
21
|
+
const empty = () => ({ open: false, messages: [] });
|
|
22
|
+
const bytes = (value) => new TextEncoder().encode(value).length;
|
|
23
|
+
const clear = (_args) => {
|
|
24
|
+
try {
|
|
25
|
+
storage.removeItem(transcriptKey);
|
|
26
|
+
}
|
|
27
|
+
catch { /* unavailable storage */ }
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
load(_args) {
|
|
31
|
+
let raw;
|
|
32
|
+
try {
|
|
33
|
+
raw = storage.getItem(transcriptKey);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return empty();
|
|
37
|
+
}
|
|
38
|
+
if (raw === null)
|
|
39
|
+
return empty();
|
|
40
|
+
try {
|
|
41
|
+
if (bytes(raw) > maxBytes)
|
|
42
|
+
throw new Error('Transcript exceeds byte limit');
|
|
43
|
+
const value = JSON.parse(raw);
|
|
44
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
45
|
+
throw new Error('Invalid envelope');
|
|
46
|
+
const state = value;
|
|
47
|
+
if (typeof state.open !== 'boolean' || !Array.isArray(state.messages)
|
|
48
|
+
|| state.messages.length > maxMessages || !state.messages.every(value => validateMessage({ value })))
|
|
49
|
+
throw new Error('Invalid transcript');
|
|
50
|
+
return { open: state.open, messages: state.messages };
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
clear({});
|
|
54
|
+
return empty();
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
save({ state }) {
|
|
58
|
+
try {
|
|
59
|
+
let messages = maxMessages === 0 ? [] : state.messages.slice(-maxMessages);
|
|
60
|
+
let serialized = JSON.stringify({ open: state.open, messages });
|
|
61
|
+
while (messages.length > 0 && bytes(serialized) > maxBytes) {
|
|
62
|
+
messages = messages.slice(1);
|
|
63
|
+
serialized = JSON.stringify({ open: state.open, messages });
|
|
64
|
+
}
|
|
65
|
+
storage.setItem(transcriptKey, serialized);
|
|
66
|
+
}
|
|
67
|
+
catch { /* The live conversation continues even when serialization or storage fails. */ }
|
|
68
|
+
},
|
|
69
|
+
clear,
|
|
70
|
+
enqueue({ action }) {
|
|
71
|
+
try {
|
|
72
|
+
const serialized = JSON.stringify(action);
|
|
73
|
+
if (serialized !== undefined)
|
|
74
|
+
storage.setItem(actionKey, serialized);
|
|
75
|
+
}
|
|
76
|
+
catch { /* The live page continues without a queued action. */ }
|
|
77
|
+
},
|
|
78
|
+
drain(_args) {
|
|
79
|
+
let raw;
|
|
80
|
+
try {
|
|
81
|
+
raw = storage.getItem(actionKey);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (raw === null)
|
|
87
|
+
return null;
|
|
88
|
+
try {
|
|
89
|
+
storage.removeItem(actionKey);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(raw);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ChatTransport } from '../../core/transport.js';
|
|
2
|
+
import type { ChatPaneAgent } from '../../react/chat-pane.js';
|
|
3
|
+
import type { BrowserSessionStore } from '../browser/session-store.js';
|
|
4
|
+
import type { PageActionPort } from '../browser/page-actions.js';
|
|
5
|
+
import type { EmbedHeaderCopy, EscapeKeyPort } from './header.js';
|
|
6
|
+
export { EmbedChatHeader } from './header.js';
|
|
7
|
+
export type { EmbedChatHeaderArgs, EmbedHeaderCopy, EscapeKeyPort } from './header.js';
|
|
8
|
+
export interface EmbedChatCopy extends EmbedHeaderCopy {
|
|
9
|
+
readonly title: string;
|
|
10
|
+
readonly fabLabel: string;
|
|
11
|
+
readonly placeholder: string;
|
|
12
|
+
readonly proposalLabel: (args: {
|
|
13
|
+
title: string;
|
|
14
|
+
}) => string;
|
|
15
|
+
readonly proposalGo: string;
|
|
16
|
+
}
|
|
17
|
+
export interface EmbedChatWidgetArgs {
|
|
18
|
+
readonly agent: ChatPaneAgent;
|
|
19
|
+
readonly copy: EmbedChatCopy;
|
|
20
|
+
readonly transport: ChatTransport;
|
|
21
|
+
readonly session: BrowserSessionStore;
|
|
22
|
+
readonly pageActions: PageActionPort;
|
|
23
|
+
readonly directiveEventName: string;
|
|
24
|
+
readonly keyboard: EscapeKeyPort;
|
|
25
|
+
/** Schedules after host layout; returns cancellation. No browser global is captured. */
|
|
26
|
+
readonly scheduleFrame: (args: {
|
|
27
|
+
callback: () => void;
|
|
28
|
+
}) => () => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Wires the existing pane/fab to per-instance transcript and page-action ports.
|
|
32
|
+
* Keep effect ports stable for the lifetime of a mounted widget; remount for a new
|
|
33
|
+
* session/agent. Restored replies are marked processed before the pane mounts, so
|
|
34
|
+
* rehydration cannot replay navigation. Reset remounts the pane after confirmation.
|
|
35
|
+
* @complexity O(messages + latest reply events) per transcript update; O(message IDs) retained.
|
|
36
|
+
*/
|
|
37
|
+
export declare function EmbedChatWidget(args: EmbedChatWidgetArgs): import("react").JSX.Element;
|
|
38
|
+
/** Mounts into a host-provided node and returns lifecycle cleanup. O(1) plus React rendering. */
|
|
39
|
+
export declare function mountEmbedChat({ element, widget }: {
|
|
40
|
+
element: Element;
|
|
41
|
+
widget: EmbedChatWidgetArgs;
|
|
42
|
+
}): {
|
|
43
|
+
unmount: (args: Record<string, never>) => void;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=embed.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { createRoot } from 'react-dom/client';
|
|
4
|
+
import { isTerminalRunStatus } from '../../core/messages.js';
|
|
5
|
+
import { ChatPane } from '../../react/chat-pane.js';
|
|
6
|
+
import { ChatFab } from '../../react/components/ChatFab.js';
|
|
7
|
+
import { JiniChatProvider } from '../../react/components/JiniChatProvider.js';
|
|
8
|
+
import { extractPageActions, splitPageActions, isQueuedPageAction } from '../browser/page-actions.js';
|
|
9
|
+
import { EmbedChatHeader } from './header.js';
|
|
10
|
+
export { EmbedChatHeader } from './header.js';
|
|
11
|
+
const shellStyles = `
|
|
12
|
+
.jini-chat-embed__panel { position:fixed; right:var(--jini-embed-right,24px); bottom:var(--jini-embed-bottom,88px); width:min(var(--jini-embed-width,440px),calc(100vw - 32px)); height:min(var(--jini-embed-height,620px),calc(100dvh - 120px)); z-index:var(--jini-embed-z-index,1000); display:flex; flex-direction:column; }
|
|
13
|
+
.jini-chat-embed__panel[hidden] { display:none; }
|
|
14
|
+
.jini-chat-embed__panel > .jini-chat-pane { flex:1; min-height:0; }
|
|
15
|
+
.jini-chat-embed__reset-confirm,.jini-chat-embed__proposal { display:flex; align-items:center; gap:8px; }
|
|
16
|
+
.jini-chat-embed__proposal { padding:12px; background:var(--jini-embed-proposal-background,Canvas); color:var(--jini-embed-proposal-color,CanvasText); }
|
|
17
|
+
`;
|
|
18
|
+
/**
|
|
19
|
+
* Wires the existing pane/fab to per-instance transcript and page-action ports.
|
|
20
|
+
* Keep effect ports stable for the lifetime of a mounted widget; remount for a new
|
|
21
|
+
* session/agent. Restored replies are marked processed before the pane mounts, so
|
|
22
|
+
* rehydration cannot replay navigation. Reset remounts the pane after confirmation.
|
|
23
|
+
* @complexity O(messages + latest reply events) per transcript update; O(message IDs) retained.
|
|
24
|
+
*/
|
|
25
|
+
export function EmbedChatWidget(args) {
|
|
26
|
+
const { agent, copy, transport, session, pageActions, directiveEventName, keyboard, scheduleFrame } = args;
|
|
27
|
+
const [initial] = useState(() => session.load({}));
|
|
28
|
+
const [open, setOpen] = useState(initial.open);
|
|
29
|
+
const [messages, setMessages] = useState(initial.messages);
|
|
30
|
+
const [paneKey, setPaneKey] = useState(0);
|
|
31
|
+
const [pending, setPending] = useState(null);
|
|
32
|
+
const processed = useRef(new Set(initial.messages.map(message => message.id)));
|
|
33
|
+
useEffect(() => { session.save({ state: { open, messages } }); }, [session, open, messages]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const queued = { value: session.drain({}) };
|
|
36
|
+
if (!isQueuedPageAction(queued) || queued.value.type === 'navigate')
|
|
37
|
+
return;
|
|
38
|
+
const action = queued.value;
|
|
39
|
+
return scheduleFrame({ callback: () => { pageActions.execute({ action }); } });
|
|
40
|
+
}, [session, pageActions, scheduleFrame]);
|
|
41
|
+
useEffect(() => () => pageActions.clearHighlight({}), [pageActions]);
|
|
42
|
+
const reset = useCallback(() => {
|
|
43
|
+
session.clear({});
|
|
44
|
+
setMessages([]);
|
|
45
|
+
setPaneKey(key => key + 1);
|
|
46
|
+
setPending(null);
|
|
47
|
+
processed.current.clear();
|
|
48
|
+
}, [session]);
|
|
49
|
+
const go = useCallback((proposal, transcript) => {
|
|
50
|
+
if (!pageActions.allows({ action: proposal.navigate }))
|
|
51
|
+
return;
|
|
52
|
+
if (proposal.bundled && pageActions.allows({ action: proposal.bundled }))
|
|
53
|
+
session.enqueue({ action: proposal.bundled });
|
|
54
|
+
// Both writes must finish before the host navigation can tear down this page.
|
|
55
|
+
session.save({ state: { open, messages: transcript } });
|
|
56
|
+
pageActions.navigate({ action: proposal.navigate });
|
|
57
|
+
}, [session, pageActions, open]);
|
|
58
|
+
const onMessagesChange = useCallback((next) => {
|
|
59
|
+
setMessages(next);
|
|
60
|
+
const latest = next.at(-1);
|
|
61
|
+
if (!latest || latest.role !== 'assistant' || !isTerminalRunStatus({ status: latest.runStatus }) || processed.current.has(latest.id))
|
|
62
|
+
return;
|
|
63
|
+
processed.current.add(latest.id);
|
|
64
|
+
setPending(null);
|
|
65
|
+
const actions = extractPageActions({ events: latest.events, directiveEventName })
|
|
66
|
+
.filter(action => pageActions.allows({ action }));
|
|
67
|
+
const { navigate, other } = splitPageActions({ actions });
|
|
68
|
+
if (!navigate) {
|
|
69
|
+
if (other)
|
|
70
|
+
pageActions.execute({ action: other });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const proposal = { navigate, bundled: other };
|
|
74
|
+
if (navigate.auto)
|
|
75
|
+
go(proposal, next);
|
|
76
|
+
else
|
|
77
|
+
setPending(proposal);
|
|
78
|
+
}, [directiveEventName, pageActions, go]);
|
|
79
|
+
return _jsxs("div", { className: "jini-chat-embed", children: [_jsx("style", { children: shellStyles }), _jsx(ChatFab, { open: open, onToggle: () => setOpen(current => !current), label: copy.fabLabel }), _jsx(JiniChatProvider, { transport: transport, children: _jsxs("div", { className: "jini-chat-embed__panel", hidden: !open, children: [_jsx(ChatPane, { transport: transport, agents: [agent], initialSelection: { agentId: agent.id }, initialMessages: messages, title: copy.title, placeholder: copy.placeholder, workingDirectoryControlPlacement: "none", header: _jsx(EmbedChatHeader, { title: copy.title, hasMessages: messages.length > 0, onReset: reset, copy: copy, keyboard: keyboard }), onMessagesChange: onMessagesChange }, paneKey), pending ? _jsxs("div", { className: "jini-chat-embed__proposal", children: [_jsx("span", { children: copy.proposalLabel({ title: pending.navigate.target.title }) }), _jsx("button", { type: "button", onClick: () => { setPending(null); go(pending, messages); }, children: copy.proposalGo })] }) : null] }) })] });
|
|
80
|
+
}
|
|
81
|
+
/** Mounts into a host-provided node and returns lifecycle cleanup. O(1) plus React rendering. */
|
|
82
|
+
export function mountEmbedChat({ element, widget }) {
|
|
83
|
+
const root = createRoot(element);
|
|
84
|
+
root.render(_jsx(EmbedChatWidget, { ...widget }));
|
|
85
|
+
return { unmount: (_args) => root.unmount() };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=embed.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface EmbedHeaderCopy {
|
|
2
|
+
readonly eyebrow: string;
|
|
3
|
+
readonly newThread: string;
|
|
4
|
+
readonly cancel: string;
|
|
5
|
+
readonly discard: string;
|
|
6
|
+
}
|
|
7
|
+
export interface EscapeKeyPort {
|
|
8
|
+
subscribe(args: {
|
|
9
|
+
onEscape: () => void;
|
|
10
|
+
}): () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface EmbedChatHeaderArgs {
|
|
13
|
+
readonly title: string;
|
|
14
|
+
readonly hasMessages: boolean;
|
|
15
|
+
readonly onReset: () => void;
|
|
16
|
+
readonly copy: EmbedHeaderCopy;
|
|
17
|
+
readonly keyboard: EscapeKeyPort;
|
|
18
|
+
}
|
|
19
|
+
/** Two-step reset with safe initial focus, Cancel and host-wide Escape. O(1) state and listeners. */
|
|
20
|
+
export declare function EmbedChatHeader({ title, hasMessages, onReset, copy, keyboard }: EmbedChatHeaderArgs): import("react").JSX.Element;
|
|
21
|
+
//# sourceMappingURL=header.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
/** Two-step reset with safe initial focus, Cancel and host-wide Escape. O(1) state and listeners. */
|
|
4
|
+
export function EmbedChatHeader({ title, hasMessages, onReset, copy, keyboard }) {
|
|
5
|
+
const [confirming, setConfirming] = useState(false);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!confirming)
|
|
8
|
+
return;
|
|
9
|
+
return keyboard.subscribe({ onEscape: () => setConfirming(false) });
|
|
10
|
+
}, [confirming, keyboard]);
|
|
11
|
+
return _jsxs("div", { className: "jini-chat-pane__header", children: [_jsxs("div", { className: "jini-chat-pane__heading", children: [_jsx("span", { className: "jini-chat-pane__eyebrow", children: copy.eyebrow }), _jsx("h2", { className: "jini-chat-pane__title", children: title })] }), confirming ? _jsxs("div", { className: "jini-chat-embed__reset-confirm", children: [_jsx("button", { type: "button", className: "jini-chat-embed__reset-cancel", autoFocus: true, onClick: () => setConfirming(false), children: copy.cancel }), _jsx("button", { type: "button", className: "jini-chat-pane__new-thread", onClick: () => { setConfirming(false); onReset(); }, children: copy.discard })] }) : _jsx("button", { type: "button", className: "jini-chat-pane__new-thread", onClick: () => {
|
|
12
|
+
if (hasMessages)
|
|
13
|
+
setConfirming(true);
|
|
14
|
+
else
|
|
15
|
+
onReset();
|
|
16
|
+
}, children: copy.newThread })] });
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=header.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { AgentEvent } from '../../core/events.js';
|
|
2
|
+
import type { ChatMessage } from '../../core/messages.js';
|
|
3
|
+
import type { ChatTransport, StartRunInput } from '../../core/transport.js';
|
|
4
|
+
export interface DecodedSseFrame {
|
|
5
|
+
readonly event: string | null;
|
|
6
|
+
readonly data: string;
|
|
7
|
+
}
|
|
8
|
+
/** Structurally satisfied by the existing agent-runtime decodeSseStream; host binds it. */
|
|
9
|
+
export type SseDecoderPort = (args: {
|
|
10
|
+
source: AsyncIterable<Uint8Array | string>;
|
|
11
|
+
}) => AsyncIterable<DecodedSseFrame>;
|
|
12
|
+
export interface HistoryTurn {
|
|
13
|
+
readonly role: ChatMessage['role'];
|
|
14
|
+
readonly content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FetchSseRequest {
|
|
17
|
+
readonly endpoint: string;
|
|
18
|
+
readonly agentId: string;
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly history: readonly HistoryTurn[];
|
|
21
|
+
readonly input: StartRunInput;
|
|
22
|
+
}
|
|
23
|
+
export type FetchSseFrameResult = {
|
|
24
|
+
readonly event?: AgentEvent;
|
|
25
|
+
readonly error?: Error;
|
|
26
|
+
readonly done?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export interface FetchSseTransportArgs {
|
|
29
|
+
readonly endpoint: string;
|
|
30
|
+
readonly agentId: string;
|
|
31
|
+
readonly fetch: (url: string, init: RequestInit) => Promise<Response>;
|
|
32
|
+
readonly ids: (args: Record<string, never>) => string;
|
|
33
|
+
readonly decode: SseDecoderPort;
|
|
34
|
+
readonly requestBuilder: (args: FetchSseRequest) => RequestInit;
|
|
35
|
+
readonly frameMapper: (args: {
|
|
36
|
+
frame: DecodedSseFrame;
|
|
37
|
+
}) => FetchSseFrameResult;
|
|
38
|
+
readonly maxHistoryMessages: number;
|
|
39
|
+
readonly maxHistoryMessageChars: number;
|
|
40
|
+
readonly copy: {
|
|
41
|
+
readonly noUserMessage: string;
|
|
42
|
+
readonly missingBody: string;
|
|
43
|
+
readonly requestFailed: (args: {
|
|
44
|
+
status: number;
|
|
45
|
+
}) => string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface FetchSseTransportOptions {
|
|
49
|
+
readonly createAbortController?: (args: Record<string, never>) => AbortController;
|
|
50
|
+
readonly onLifecycle?: (args: {
|
|
51
|
+
runId: string;
|
|
52
|
+
phase: 'started' | 'settled' | 'error' | 'aborted';
|
|
53
|
+
}) => void;
|
|
54
|
+
}
|
|
55
|
+
/** Default JSON wire request is an explicit host choice, never a hidden endpoint default. O(history). */
|
|
56
|
+
export declare function buildJsonChatRequest({ message, history }: FetchSseRequest): RequestInit;
|
|
57
|
+
/** Maps the text/error/end/directive JSON protocol with required directive vocabulary and copy. O(data). */
|
|
58
|
+
export declare function mapJsonChatSseFrame({ frame, directiveEventName, streamError }: {
|
|
59
|
+
frame: DecodedSseFrame;
|
|
60
|
+
directiveEventName: string;
|
|
61
|
+
streamError: string;
|
|
62
|
+
}): FetchSseFrameResult;
|
|
63
|
+
/**
|
|
64
|
+
* Creates a cancellable, one-POST-per-run transport with no reattachable server state.
|
|
65
|
+
* Uses the host-bound SSE decoder rather than implementing another parser. Every run
|
|
66
|
+
* owns its controller, listeners and event log; cancellation and end settle once and
|
|
67
|
+
* prevent late events. Input and cancelSignal aborts release readers/listeners, and frames after
|
|
68
|
+
* end cannot mutate the settled reply. A later settlement clears an earlier pending directive.
|
|
69
|
+
* Default request bytes retain the {message, history} wire shape; a host requestBuilder may carry
|
|
70
|
+
* required agent identity differently. No browser module imports a Node-backed runtime barrel.
|
|
71
|
+
* The returned positional methods implement the existing ChatTransport.
|
|
72
|
+
* @complexity O(history + streamed bytes + emitted events); O(emitted events + decoder buffer) space.
|
|
73
|
+
*/
|
|
74
|
+
export declare function createFetchSseTransport(args: FetchSseTransportArgs, options?: FetchSseTransportOptions): ChatTransport;
|
|
75
|
+
//# sourceMappingURL=fetch-sse.d.ts.map
|