@portablecore/chat-runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @portablecore/chat-runtime
3
+ *
4
+ * Headless chat runtime for Portable platforms.
5
+ * SSE streaming, message state management, and lifecycle hooks.
6
+ *
7
+ * No UI components — compose with @portablecore/chat for the full
8
+ * unified chat experience, or use these primitives directly for
9
+ * custom surfaces.
10
+ */
11
+ export { useChatRuntime } from "./use-chat-runtime.js";
12
+ export type { UseChatRuntimeOptions, ChatRuntime, PersistMessageResult, } from "./use-chat-runtime.js";
13
+ export { useStreamingMessage } from "./use-streaming-message.js";
14
+ export type { UseStreamingMessageReturn } from "./use-streaming-message.js";
15
+ export { useSkillIndicators } from "./use-skill-indicators.js";
16
+ export type { UseSkillIndicatorsReturn, SkillThinkingStage, ActiveSkillState, SkillConfig, SkillResultEntry, } from "./use-skill-indicators.js";
17
+ export { parseSseStream, fetchWithRetry } from "./sse-client.js";
18
+ export type { SseEvent, SseThinkingEvent, SseSkillStartEvent, SseSkillResultEvent, SseTextEvent, SseReplaceContentEvent, SseDoneEvent, SseErrorEvent, SseFirstResponseEvent, LiteMessage, } from "@portablecore/types/chat";
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,YAAY,EACV,qBAAqB,EACrB,WAAW,EACX,oBAAoB,GACrB,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,YAAY,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AAG3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,YAAY,EACV,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,GACjB,MAAM,2BAA2B,CAAA;AAGlC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGhE,YAAY,EACV,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,WAAW,GACZ,MAAM,0BAA0B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * @portablecore/chat-runtime
4
+ *
5
+ * Headless chat runtime for Portable platforms.
6
+ * SSE streaming, message state management, and lifecycle hooks.
7
+ *
8
+ * No UI components — compose with @portablecore/chat for the full
9
+ * unified chat experience, or use these primitives directly for
10
+ * custom surfaces.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.fetchWithRetry = exports.parseSseStream = exports.useSkillIndicators = exports.useStreamingMessage = exports.useChatRuntime = void 0;
14
+ // Headless chat hook
15
+ var use_chat_runtime_js_1 = require("./use-chat-runtime.js");
16
+ Object.defineProperty(exports, "useChatRuntime", { enumerable: true, get: function () { return use_chat_runtime_js_1.useChatRuntime; } });
17
+ // RAF-batched streaming message accumulator
18
+ var use_streaming_message_js_1 = require("./use-streaming-message.js");
19
+ Object.defineProperty(exports, "useStreamingMessage", { enumerable: true, get: function () { return use_streaming_message_js_1.useStreamingMessage; } });
20
+ // Skill and thinking indicator state
21
+ var use_skill_indicators_js_1 = require("./use-skill-indicators.js");
22
+ Object.defineProperty(exports, "useSkillIndicators", { enumerable: true, get: function () { return use_skill_indicators_js_1.useSkillIndicators; } });
23
+ // SSE client
24
+ var sse_client_js_1 = require("./sse-client.js");
25
+ Object.defineProperty(exports, "parseSseStream", { enumerable: true, get: function () { return sse_client_js_1.parseSseStream; } });
26
+ Object.defineProperty(exports, "fetchWithRetry", { enumerable: true, get: function () { return sse_client_js_1.fetchWithRetry; } });
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAEH,qBAAqB;AACrB,6DAAsD;AAA7C,qHAAA,cAAc,OAAA;AAOvB,4CAA4C;AAC5C,uEAAgE;AAAvD,+HAAA,mBAAmB,OAAA;AAG5B,qCAAqC;AACrC,qEAA8D;AAArD,6HAAA,kBAAkB,OAAA;AAS3B,aAAa;AACb,iDAAgE;AAAvD,+GAAA,cAAc,OAAA;AAAE,+GAAA,cAAc,OAAA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SSE Client — Stream parsing and fetch utilities
3
+ *
4
+ * Shared utilities for connecting to /api/chat/stream endpoints
5
+ * and parsing Server-Sent Events into typed SseEvent objects.
6
+ *
7
+ * Both full chat and lite chat surfaces consume this same parser.
8
+ * Each surface handles only the events it cares about.
9
+ */
10
+ import type { SseEvent } from "@portablecore/types/chat";
11
+ /**
12
+ * Async generator that reads an SSE response body and yields typed SseEvent objects.
13
+ *
14
+ * Uses double-newline as event boundaries. Handles partial chunk buffering.
15
+ *
16
+ * Abort: pass an AbortSignal to the fetch call. When aborted, reader.read()
17
+ * rejects with AbortError, propagating to the consumer's catch block.
18
+ */
19
+ export declare function parseSseStream(response: Response): AsyncGenerator<SseEvent>;
20
+ /**
21
+ * Fetch wrapper with single retry for transient network failures.
22
+ * Does NOT retry on AbortError (user-initiated cancellation).
23
+ */
24
+ export declare function fetchWithRetry(url: string, options: RequestInit, maxRetries?: number): Promise<Response>;
25
+ //# sourceMappingURL=sse-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-client.d.ts","sourceRoot":"","sources":["../src/sse-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAyFxD;;;;;;;GAOG;AACH,wBAAuB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,CAsDlF;AAMD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,WAAW,EACpB,UAAU,SAAI,GACb,OAAO,CAAC,QAAQ,CAAC,CAoBnB"}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * SSE Client — Stream parsing and fetch utilities
4
+ *
5
+ * Shared utilities for connecting to /api/chat/stream endpoints
6
+ * and parsing Server-Sent Events into typed SseEvent objects.
7
+ *
8
+ * Both full chat and lite chat surfaces consume this same parser.
9
+ * Each surface handles only the events it cares about.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.parseSseStream = parseSseStream;
13
+ exports.fetchWithRetry = fetchWithRetry;
14
+ // ---------------------------------------------------------------------------
15
+ // SSE Stream Parser
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Parse a single SSE data line into a typed SseEvent.
19
+ * Returns null for non-data lines or unparseable content.
20
+ */
21
+ function parseSseLine(line) {
22
+ if (!line.startsWith("data: "))
23
+ return null;
24
+ try {
25
+ const data = JSON.parse(line.slice(6));
26
+ if (data.thinking) {
27
+ return {
28
+ type: "thinking",
29
+ stage: data.thinking.stage,
30
+ detail: data.thinking.detail,
31
+ showIcon: data.thinking.showIcon,
32
+ };
33
+ }
34
+ if (data.skill_start) {
35
+ return {
36
+ type: "skill_start",
37
+ skillSlug: data.skill_start.skillSlug,
38
+ skillName: data.skill_start.skillName,
39
+ contextualMessage: data.skill_start.contextualMessage,
40
+ };
41
+ }
42
+ if (data.skill_result) {
43
+ return {
44
+ type: "skill_result",
45
+ skillSlug: data.skill_result.skillSlug,
46
+ skillName: data.skill_result.skillName,
47
+ success: data.skill_result.success,
48
+ error: data.skill_result.error,
49
+ duration_ms: data.skill_result.duration_ms,
50
+ citations: data.skill_result.citations,
51
+ sources: data.skill_result.sources,
52
+ locations: data.skill_result.locations,
53
+ booking_links: data.skill_result.booking_links,
54
+ documents: data.skill_result.documents,
55
+ notion_results: data.skill_result.notion_results,
56
+ craft_results: data.skill_result.craft_results,
57
+ };
58
+ }
59
+ if (data.first_response) {
60
+ return {
61
+ type: "first_response",
62
+ messageId: data.first_response.messageId,
63
+ content: data.first_response.content,
64
+ };
65
+ }
66
+ if (data.replace_content !== undefined) {
67
+ return { type: "replace_content" };
68
+ }
69
+ if (data.text) {
70
+ return { type: "text", text: data.text };
71
+ }
72
+ if (data.done) {
73
+ return {
74
+ type: "done",
75
+ messageId: data.messageId,
76
+ provider: data.provider,
77
+ model: data.model,
78
+ skill_usage: data.skill_usage,
79
+ expert_suggestion: data.expert_suggestion,
80
+ };
81
+ }
82
+ if (data.error) {
83
+ return { type: "error", error: data.error };
84
+ }
85
+ return null;
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ }
91
+ /**
92
+ * Async generator that reads an SSE response body and yields typed SseEvent objects.
93
+ *
94
+ * Uses double-newline as event boundaries. Handles partial chunk buffering.
95
+ *
96
+ * Abort: pass an AbortSignal to the fetch call. When aborted, reader.read()
97
+ * rejects with AbortError, propagating to the consumer's catch block.
98
+ */
99
+ async function* parseSseStream(response) {
100
+ const reader = response.body?.getReader();
101
+ if (!reader) {
102
+ throw new Error("No response body");
103
+ }
104
+ const decoder = new TextDecoder();
105
+ let buffer = "";
106
+ try {
107
+ while (true) {
108
+ const { done, value } = await reader.read();
109
+ if (done)
110
+ break;
111
+ buffer += decoder.decode(value, { stream: true });
112
+ const events = buffer.split("\n\n");
113
+ buffer = events.pop() || "";
114
+ for (const rawEvent of events) {
115
+ const parsed = parseSseLine(rawEvent.trim());
116
+ if (parsed) {
117
+ yield parsed;
118
+ }
119
+ else {
120
+ for (const line of rawEvent.split("\n")) {
121
+ const lineParsed = parseSseLine(line.trim());
122
+ if (lineParsed) {
123
+ yield lineParsed;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ if (buffer.trim()) {
130
+ buffer += decoder.decode();
131
+ const remaining = buffer.split("\n\n");
132
+ for (const rawEvent of remaining) {
133
+ const parsed = parseSseLine(rawEvent.trim());
134
+ if (parsed) {
135
+ yield parsed;
136
+ }
137
+ else {
138
+ for (const line of rawEvent.split("\n")) {
139
+ const lineParsed = parseSseLine(line.trim());
140
+ if (lineParsed) {
141
+ yield lineParsed;
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ finally {
149
+ reader.releaseLock();
150
+ }
151
+ }
152
+ // ---------------------------------------------------------------------------
153
+ // Fetch with Retry
154
+ // ---------------------------------------------------------------------------
155
+ /**
156
+ * Fetch wrapper with single retry for transient network failures.
157
+ * Does NOT retry on AbortError (user-initiated cancellation).
158
+ */
159
+ async function fetchWithRetry(url, options, maxRetries = 1) {
160
+ let lastError = null;
161
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
162
+ try {
163
+ return await fetch(url, options);
164
+ }
165
+ catch (err) {
166
+ lastError = err instanceof Error ? err : new Error(String(err));
167
+ if (lastError.name === "AbortError") {
168
+ throw lastError;
169
+ }
170
+ if (attempt < maxRetries) {
171
+ await new Promise((resolve) => setTimeout(resolve, 500));
172
+ }
173
+ }
174
+ }
175
+ throw lastError || new Error("Fetch failed after retries");
176
+ }
177
+ //# sourceMappingURL=sse-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-client.js","sourceRoot":"","sources":["../src/sse-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { LiteMessage, SseEvent } from "@portablecore/types/chat";
2
+ /** Result of persisting a user message. */
3
+ export interface PersistMessageResult {
4
+ /** Persisted message ID, or null on failure */
5
+ id: string | null;
6
+ /** Sender's user ID, or null if unavailable */
7
+ userId: string | null;
8
+ }
9
+ export interface UseChatRuntimeOptions {
10
+ /** Current chat ID. Can be null initially (lazy creation). */
11
+ chatId: string | null;
12
+ /** Expert to stream responses from. */
13
+ expertId: string;
14
+ /**
15
+ * Persist a user message before streaming. Platform-specific.
16
+ * Expert: saves to Supabase messages table.
17
+ * Team: saves to Team's local messages table.
18
+ * If omitted, no local persistence (stream endpoint handles it).
19
+ */
20
+ persistMessage?: (chatId: string, content: string) => Promise<PersistMessageResult>;
21
+ /** Display name of the sender (for multi-human chat context). */
22
+ senderName?: string;
23
+ /**
24
+ * Pre-warmed context from useChatWarmup. Passed through to the stream endpoint.
25
+ */
26
+ warmContext?: any;
27
+ /**
28
+ * Streaming API endpoint. Defaults to "/api/chat/stream".
29
+ */
30
+ streamEndpoint?: string;
31
+ /**
32
+ * Extra parameters merged into the stream POST body.
33
+ * Use for surface-specific fields (provider overrides, voice mode, etc.).
34
+ */
35
+ streamBody?: Record<string, unknown>;
36
+ /**
37
+ * Callback for SSE events. Called for EVERY event so the surface
38
+ * has full visibility (thinking indicators, skill results, etc.).
39
+ */
40
+ onEvent?: (event: SseEvent) => void;
41
+ }
42
+ export interface ChatRuntime {
43
+ /** Current messages in the conversation. */
44
+ messages: LiteMessage[];
45
+ /** Whether an expert response is currently streaming. */
46
+ streaming: boolean;
47
+ /** Whether a user message is being saved (before streaming starts). */
48
+ sending: boolean;
49
+ /** First Responder → full response transition. */
50
+ frTransitioning: boolean;
51
+ /** Expert is executing a skill mid-stream. */
52
+ skillTransitioning: boolean;
53
+ /** The resolved chat ID. */
54
+ chatId: string | null;
55
+ /**
56
+ * Send a message: persist the user message, then stream the expert response.
57
+ * @param content - The message text.
58
+ * @param chatIdOverride - Optional chat ID override for first-send case.
59
+ */
60
+ send: (content: string, chatIdOverride?: string) => Promise<void>;
61
+ /** Abort the in-flight stream. Preserves any partial response. */
62
+ abort: () => void;
63
+ /** Clear all messages from the runtime state. */
64
+ clear: () => void;
65
+ }
66
+ export declare function useChatRuntime(options: UseChatRuntimeOptions): ChatRuntime;
67
+ //# sourceMappingURL=use-chat-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-chat-runtime.d.ts","sourceRoot":"","sources":["../src/use-chat-runtime.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAMrE,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACnF,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;OAEG;IAEH,WAAW,CAAC,EAAE,GAAG,CAAA;IACjB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,4CAA4C;IAC5C,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAA;IAClB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAA;IAChB,kDAAkD;IAClD,eAAe,EAAE,OAAO,CAAA;IACxB,8CAA8C;IAC9C,kBAAkB,EAAE,OAAO,CAAA;IAC3B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB;;;;OAIG;IACH,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE,kEAAkE;IAClE,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,iDAAiD;IACjD,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAMD,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,WAAW,CAkU1E"}
@@ -0,0 +1,274 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.useChatRuntime = useChatRuntime;
5
+ /**
6
+ * useChatRuntime — Headless chat lifecycle hook
7
+ *
8
+ * Manages the chat lifecycle without any UI: message state, streaming,
9
+ * persistence, and abort. Surfaces compose this hook with their own
10
+ * UI components and surface-specific features.
11
+ *
12
+ * Platform-specific concerns (message persistence, auth) are handled
13
+ * via callbacks rather than direct Supabase coupling.
14
+ *
15
+ * Usage:
16
+ * const chat = useChatRuntime({ chatId, expertId, ... })
17
+ * chat.messages // LiteMessage[]
18
+ * chat.streaming // boolean
19
+ * chat.send(content) // Save user message + trigger expert stream
20
+ * chat.abort() // Cancel in-flight stream
21
+ */
22
+ const react_1 = require("react");
23
+ const sse_client_js_1 = require("./sse-client.js");
24
+ // ---------------------------------------------------------------------------
25
+ // Hook
26
+ // ---------------------------------------------------------------------------
27
+ function useChatRuntime(options) {
28
+ const { chatId: externalChatId, expertId, persistMessage, senderName, warmContext, streamEndpoint = "/api/chat/stream", streamBody, onEvent, } = options;
29
+ // ---- State ----
30
+ const [messages, setMessages] = (0, react_1.useState)([]);
31
+ const [streaming, setStreaming] = (0, react_1.useState)(false);
32
+ const [sending, setSending] = (0, react_1.useState)(false);
33
+ const [frTransitioning, setFrTransitioning] = (0, react_1.useState)(false);
34
+ const frTransitioningRef = (0, react_1.useRef)(false);
35
+ const [skillTransitioning, setSkillTransitioning] = (0, react_1.useState)(false);
36
+ const skillTransitioningRef = (0, react_1.useRef)(false);
37
+ const [chatId, setChatId] = (0, react_1.useState)(externalChatId);
38
+ (0, react_1.useEffect)(() => {
39
+ if (externalChatId) {
40
+ setChatId(externalChatId);
41
+ }
42
+ }, [externalChatId]);
43
+ // ---- Refs ----
44
+ const abortRef = (0, react_1.useRef)(null);
45
+ const pendingText = (0, react_1.useRef)("");
46
+ const rafId = (0, react_1.useRef)(null);
47
+ const streamTempId = (0, react_1.useRef)(null);
48
+ const onEventRef = (0, react_1.useRef)(onEvent);
49
+ (0, react_1.useEffect)(() => {
50
+ onEventRef.current = onEvent;
51
+ }, [onEvent]);
52
+ // ---- RAF Batching ----
53
+ const flushContent = (0, react_1.useCallback)(() => {
54
+ rafId.current = null;
55
+ const text = pendingText.current;
56
+ const tempId = streamTempId.current;
57
+ if (!text || !tempId)
58
+ return;
59
+ pendingText.current = "";
60
+ setMessages((prev) => prev.map((m) => (m.id === tempId ? { ...m, content: m.content + text } : m)));
61
+ }, []);
62
+ const scheduleFlush = (0, react_1.useCallback)((text) => {
63
+ pendingText.current += text;
64
+ if (rafId.current === null) {
65
+ rafId.current = requestAnimationFrame(flushContent);
66
+ }
67
+ }, [flushContent]);
68
+ const forceFlush = (0, react_1.useCallback)(() => {
69
+ if (rafId.current !== null) {
70
+ cancelAnimationFrame(rafId.current);
71
+ rafId.current = null;
72
+ }
73
+ const text = pendingText.current;
74
+ const tempId = streamTempId.current;
75
+ if (text && tempId) {
76
+ pendingText.current = "";
77
+ setMessages((prev) => prev.map((m) => (m.id === tempId ? { ...m, content: m.content + text } : m)));
78
+ }
79
+ }, []);
80
+ (0, react_1.useEffect)(() => {
81
+ return () => {
82
+ if (rafId.current !== null) {
83
+ cancelAnimationFrame(rafId.current);
84
+ }
85
+ };
86
+ }, []);
87
+ // ---- Stream ----
88
+ const streamResponse = (0, react_1.useCallback)(async (activeChatId, userMessage, userMessageId, senderUserId) => {
89
+ setStreaming(true);
90
+ const tempId = `stream-${Date.now()}`;
91
+ streamTempId.current = tempId;
92
+ pendingText.current = "";
93
+ setMessages((prev) => [
94
+ ...prev,
95
+ {
96
+ id: tempId,
97
+ role: "assistant",
98
+ content: "",
99
+ streaming: true,
100
+ createdAt: new Date().toISOString(),
101
+ },
102
+ ]);
103
+ const controller = new AbortController();
104
+ abortRef.current = controller;
105
+ try {
106
+ const response = await fetch(streamEndpoint, {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/json" },
109
+ body: JSON.stringify({
110
+ chatId: activeChatId,
111
+ expertId,
112
+ message: userMessage,
113
+ userMessageId,
114
+ senderName,
115
+ senderUserId,
116
+ warmContext: warmContext || undefined,
117
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
118
+ ...streamBody,
119
+ }),
120
+ signal: controller.signal,
121
+ });
122
+ if (!response.ok || !response.body) {
123
+ setMessages((prev) => prev.map((m) => m.id === tempId
124
+ ? { ...m, content: "Something went wrong. Try again.", streaming: false }
125
+ : m));
126
+ setStreaming(false);
127
+ return;
128
+ }
129
+ for await (const event of (0, sse_client_js_1.parseSseStream)(response)) {
130
+ onEventRef.current?.(event);
131
+ switch (event.type) {
132
+ case "first_response": {
133
+ forceFlush();
134
+ pendingText.current = "";
135
+ const frTarget = streamTempId.current || tempId;
136
+ setMessages((prev) => prev.map((m) => (m.id === frTarget ? { ...m, content: m.content + "\n\n" } : m)));
137
+ frTransitioningRef.current = true;
138
+ setFrTransitioning(true);
139
+ break;
140
+ }
141
+ case "replace_content": {
142
+ forceFlush();
143
+ pendingText.current = "";
144
+ const replaceTarget = streamTempId.current || tempId;
145
+ setMessages((prev) => prev.filter((m) => m.id !== replaceTarget));
146
+ break;
147
+ }
148
+ case "skill_start": {
149
+ skillTransitioningRef.current = true;
150
+ setSkillTransitioning(true);
151
+ break;
152
+ }
153
+ case "text": {
154
+ if (frTransitioningRef.current) {
155
+ frTransitioningRef.current = false;
156
+ setFrTransitioning(false);
157
+ }
158
+ if (skillTransitioningRef.current) {
159
+ skillTransitioningRef.current = false;
160
+ setSkillTransitioning(false);
161
+ }
162
+ scheduleFlush(event.text);
163
+ break;
164
+ }
165
+ case "done": {
166
+ forceFlush();
167
+ const remainingText = pendingText.current;
168
+ pendingText.current = "";
169
+ const finalMessageId = event.messageId;
170
+ const doneTarget = streamTempId.current || tempId;
171
+ setMessages((prev) => prev.map((m) => m.id === doneTarget
172
+ ? {
173
+ ...m,
174
+ id: finalMessageId || doneTarget,
175
+ content: m.content + remainingText,
176
+ streaming: false,
177
+ }
178
+ : m));
179
+ break;
180
+ }
181
+ case "error": {
182
+ forceFlush();
183
+ pendingText.current = "";
184
+ const errorTarget = streamTempId.current || tempId;
185
+ setMessages((prev) => prev.map((m) => m.id === errorTarget
186
+ ? { ...m, content: m.content || event.error, streaming: false }
187
+ : m));
188
+ break;
189
+ }
190
+ }
191
+ }
192
+ setMessages((prev) => prev.map((m) => (m.streaming ? { ...m, streaming: false } : m)));
193
+ }
194
+ catch (err) {
195
+ if (err.name !== "AbortError") {
196
+ setMessages((prev) => prev.map((m) => m.id === tempId
197
+ ? { ...m, content: "Connection lost. Try again.", streaming: false }
198
+ : m));
199
+ }
200
+ }
201
+ finally {
202
+ if (rafId.current !== null) {
203
+ cancelAnimationFrame(rafId.current);
204
+ rafId.current = null;
205
+ }
206
+ pendingText.current = "";
207
+ streamTempId.current = null;
208
+ setStreaming(false);
209
+ frTransitioningRef.current = false;
210
+ setFrTransitioning(false);
211
+ skillTransitioningRef.current = false;
212
+ setSkillTransitioning(false);
213
+ abortRef.current = null;
214
+ }
215
+ }, [expertId, senderName, warmContext, streamEndpoint, streamBody, scheduleFlush, forceFlush]);
216
+ // ---- Public API ----
217
+ const send = (0, react_1.useCallback)(async (content, chatIdOverride) => {
218
+ const activeChatId = chatIdOverride || chatId;
219
+ if (!content.trim() || sending || streaming || !activeChatId)
220
+ return;
221
+ if (chatIdOverride && chatIdOverride !== chatId) {
222
+ setChatId(chatIdOverride);
223
+ }
224
+ setSending(true);
225
+ const userTempId = `user-${Date.now()}`;
226
+ setMessages((prev) => [
227
+ ...prev,
228
+ { id: userTempId, role: "user", content, createdAt: new Date().toISOString() },
229
+ ]);
230
+ try {
231
+ let savedId = null;
232
+ let senderUserId = null;
233
+ if (persistMessage) {
234
+ const result = await persistMessage(activeChatId, content);
235
+ savedId = result.id;
236
+ senderUserId = result.userId;
237
+ }
238
+ if (savedId) {
239
+ setMessages((prev) => prev.map((m) => (m.id === userTempId ? { ...m, id: savedId } : m)));
240
+ }
241
+ setSending(false);
242
+ await streamResponse(activeChatId, content, savedId, senderUserId);
243
+ }
244
+ catch (err) {
245
+ console.error("[ChatRuntime] Error in send:", err);
246
+ setSending(false);
247
+ }
248
+ }, [chatId, sending, streaming, persistMessage, streamResponse]);
249
+ const abort = (0, react_1.useCallback)(() => {
250
+ if (abortRef.current) {
251
+ abortRef.current.abort();
252
+ abortRef.current = null;
253
+ }
254
+ forceFlush();
255
+ pendingText.current = "";
256
+ setMessages((prev) => prev.map((m) => (m.streaming ? { ...m, streaming: false } : m)));
257
+ setStreaming(false);
258
+ }, [forceFlush]);
259
+ const clear = (0, react_1.useCallback)(() => {
260
+ setMessages([]);
261
+ }, []);
262
+ return {
263
+ messages,
264
+ streaming,
265
+ sending,
266
+ frTransitioning,
267
+ skillTransitioning,
268
+ chatId,
269
+ send,
270
+ abort,
271
+ clear,
272
+ };
273
+ }
274
+ //# sourceMappingURL=use-chat-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-chat-runtime.js","sourceRoot":"","sources":["../src/use-chat-runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { SseEvent } from "@portablecore/types/chat";
2
+ /** Thinking stage for the ThinkingIndicator component. */
3
+ export interface SkillThinkingStage {
4
+ stage: "memories" | "knowledge" | "context" | "skills" | "thinking";
5
+ detail?: string;
6
+ showIcon?: boolean;
7
+ }
8
+ /** Active skill indicator state for the SkillIndicator component. */
9
+ export interface ActiveSkillState {
10
+ slug: string;
11
+ name: string;
12
+ showSkillPill?: boolean;
13
+ skillPillText?: string | null;
14
+ }
15
+ /**
16
+ * Minimal skill config needed by the hook.
17
+ * Consumer passes their full skill array; only these fields are read.
18
+ */
19
+ export interface SkillConfig {
20
+ slug: string;
21
+ showSkillPill: boolean;
22
+ skillPillText: string | null;
23
+ }
24
+ /** Accumulated skill result entry from streaming. */
25
+ export interface SkillResultEntry {
26
+ skill_slug: string;
27
+ skill_name: string;
28
+ invoked_at: string;
29
+ duration_ms: number;
30
+ citations?: any[];
31
+ sources?: any[];
32
+ locations?: any[];
33
+ booking_links?: any[];
34
+ documents?: any[];
35
+ }
36
+ export interface UseSkillIndicatorsReturn {
37
+ /** Currently active skill (for skill pill indicator). Null when idle. */
38
+ activeSkill: ActiveSkillState | null;
39
+ /** Accumulated thinking stages for the current response. */
40
+ thinkingStages: SkillThinkingStage[];
41
+ /** Ref to accumulated skill results for the current response. */
42
+ pendingSkillResults: React.MutableRefObject<SkillResultEntry[]>;
43
+ /** Process a thinking, skill_start, or skill_result SSE event. */
44
+ handleEvent: (event: SseEvent) => void;
45
+ /** Clear thinking stages only. */
46
+ clearThinking: () => void;
47
+ /** Reset all indicator state. */
48
+ reset: () => void;
49
+ }
50
+ export declare function useSkillIndicators(chatSkills: SkillConfig[]): UseSkillIndicatorsReturn;
51
+ //# sourceMappingURL=use-skill-indicators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-skill-indicators.d.ts","sourceRoot":"","sources":["../src/use-skill-indicators.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAMxD,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAA;IACnE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,OAAO,CAAA;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IAEnB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAA;IACjB,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;IACf,SAAS,CAAC,EAAE,GAAG,EAAE,CAAA;IACjB,aAAa,CAAC,EAAE,GAAG,EAAE,CAAA;IACrB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAA;CAElB;AAED,MAAM,WAAW,wBAAwB;IACvC,yEAAyE;IACzE,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACpC,4DAA4D;IAC5D,cAAc,EAAE,kBAAkB,EAAE,CAAA;IACpC,iEAAiE;IACjE,mBAAmB,EAAE,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,CAAC,CAAA;IAC/D,kEAAkE;IAClE,WAAW,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;IACtC,kCAAkC;IAClC,aAAa,EAAE,MAAM,IAAI,CAAA;IACzB,iCAAiC;IACjC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAMD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,wBAAwB,CA6EtF"}
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.useSkillIndicators = useSkillIndicators;
5
+ /**
6
+ * useSkillIndicators — Skill and thinking indicator state management
7
+ *
8
+ * Manages UI state for cognition indicators (thinking stages) and
9
+ * skill execution indicators (active skill pill, pending results).
10
+ *
11
+ * Handles three SSE event types:
12
+ * - thinking → append to thinking stages
13
+ * - skill_start → show skill pill (if configured), clear thinking stages
14
+ * - skill_result → accumulate results, clear skill pill
15
+ */
16
+ const react_1 = require("react");
17
+ // ---------------------------------------------------------------------------
18
+ // Hook
19
+ // ---------------------------------------------------------------------------
20
+ function useSkillIndicators(chatSkills) {
21
+ const [activeSkill, setActiveSkill] = (0, react_1.useState)(null);
22
+ const [thinkingStages, setThinkingStages] = (0, react_1.useState)([]);
23
+ const pendingSkillResults = (0, react_1.useRef)([]);
24
+ const chatSkillsRef = (0, react_1.useRef)(chatSkills);
25
+ (0, react_1.useEffect)(() => {
26
+ chatSkillsRef.current = chatSkills;
27
+ }, [chatSkills]);
28
+ const handleEvent = (0, react_1.useCallback)((event) => {
29
+ switch (event.type) {
30
+ case "thinking": {
31
+ setThinkingStages((prev) => [
32
+ ...prev,
33
+ {
34
+ stage: event.stage,
35
+ detail: event.detail,
36
+ showIcon: event.showIcon,
37
+ },
38
+ ]);
39
+ break;
40
+ }
41
+ case "skill_start": {
42
+ const displayName = event.contextualMessage || event.skillName;
43
+ const skillInfo = chatSkillsRef.current.find((s) => s.slug === event.skillSlug);
44
+ const shouldShowPill = skillInfo?.showSkillPill ?? true;
45
+ if (shouldShowPill) {
46
+ setThinkingStages([]);
47
+ setActiveSkill({
48
+ slug: event.skillSlug,
49
+ name: displayName,
50
+ showSkillPill: true,
51
+ skillPillText: skillInfo?.skillPillText ?? null,
52
+ });
53
+ }
54
+ break;
55
+ }
56
+ case "skill_result": {
57
+ pendingSkillResults.current.push({
58
+ skill_slug: event.skillSlug,
59
+ skill_name: event.skillName,
60
+ invoked_at: new Date().toISOString(),
61
+ duration_ms: event.duration_ms || 0,
62
+ citations: event.citations,
63
+ sources: event.sources,
64
+ locations: event.locations,
65
+ booking_links: event.booking_links,
66
+ documents: event.documents,
67
+ });
68
+ setActiveSkill(null);
69
+ break;
70
+ }
71
+ }
72
+ }, []);
73
+ const clearThinking = (0, react_1.useCallback)(() => {
74
+ setThinkingStages([]);
75
+ }, []);
76
+ const reset = (0, react_1.useCallback)(() => {
77
+ setActiveSkill(null);
78
+ setThinkingStages([]);
79
+ pendingSkillResults.current = [];
80
+ }, []);
81
+ return {
82
+ activeSkill,
83
+ thinkingStages,
84
+ pendingSkillResults,
85
+ handleEvent,
86
+ clearThinking,
87
+ reset,
88
+ };
89
+ }
90
+ //# sourceMappingURL=use-skill-indicators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-skill-indicators.js","sourceRoot":"","sources":["../src/use-skill-indicators.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /** Minimal message shape required by the streaming engine. */
2
+ interface StreamableMessage {
3
+ id: string;
4
+ content: string;
5
+ }
6
+ /**
7
+ * The setMessages updater. Works with React.Dispatch<SetStateAction<M[]>>
8
+ * for any message type extending StreamableMessage.
9
+ */
10
+ type MessageUpdater<M extends StreamableMessage> = (updater: (prev: M[]) => M[]) => void;
11
+ export interface UseStreamingMessageReturn {
12
+ /**
13
+ * Ref tracking the ID of the message currently being streamed into.
14
+ * Set by the caller before streaming starts; read by RAF callbacks.
15
+ */
16
+ streamingMessageId: React.MutableRefObject<string | null>;
17
+ /**
18
+ * Append a text chunk. Chunks are accumulated and flushed at 60fps.
19
+ */
20
+ updateStreamingMessage: (content: string) => void;
21
+ /**
22
+ * Force-flush buffered text immediately.
23
+ * Call before changing streamingMessageId or cleaning up.
24
+ */
25
+ flushPendingContent: () => void;
26
+ }
27
+ export declare function useStreamingMessage<M extends StreamableMessage>(setMessages: MessageUpdater<M>): UseStreamingMessageReturn;
28
+ export {};
29
+ //# sourceMappingURL=use-streaming-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-streaming-message.d.ts","sourceRoot":"","sources":["../src/use-streaming-message.ts"],"names":[],"mappings":"AAqBA,8DAA8D;AAC9D,UAAU,iBAAiB;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;GAGG;AACH,KAAK,cAAc,CAAC,CAAC,SAAS,iBAAiB,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,KAAK,IAAI,CAAA;AAExF,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,kBAAkB,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACzD;;OAEG;IACH,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACjD;;;OAGG;IACH,mBAAmB,EAAE,MAAM,IAAI,CAAA;CAChC;AAMD,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EAC7D,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC,GAC7B,yBAAyB,CAkE3B"}
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.useStreamingMessage = useStreamingMessage;
5
+ /**
6
+ * useStreamingMessage — RAF-batched streaming text accumulator
7
+ *
8
+ * Provides low-level plumbing for appending streamed text chunks
9
+ * to a message list at 60fps. Used by both useChatRuntime (lite)
10
+ * and the full ChatInterface.
11
+ *
12
+ * The hook owns:
13
+ * - streamingMessageId ref: which message is currently being streamed into
14
+ * - updateStreamingMessage(text): accumulates text, flushes via RAF
15
+ * - flushPendingContent(): force-flush before ID swaps or cleanup
16
+ */
17
+ const react_1 = require("react");
18
+ // ---------------------------------------------------------------------------
19
+ // Hook
20
+ // ---------------------------------------------------------------------------
21
+ function useStreamingMessage(setMessages) {
22
+ const streamingMessageId = (0, react_1.useRef)(null);
23
+ const pendingContent = (0, react_1.useRef)("");
24
+ const rafId = (0, react_1.useRef)(null);
25
+ const flushPendingContent = (0, react_1.useCallback)(() => {
26
+ if (rafId.current !== null) {
27
+ cancelAnimationFrame(rafId.current);
28
+ rafId.current = null;
29
+ }
30
+ if (pendingContent.current && streamingMessageId.current) {
31
+ const contentToAdd = pendingContent.current;
32
+ const messageId = streamingMessageId.current;
33
+ pendingContent.current = "";
34
+ setMessages((prev) => prev.map((m) => {
35
+ if (m.id === messageId) {
36
+ return { ...m, content: m.content + contentToAdd };
37
+ }
38
+ return m;
39
+ }));
40
+ }
41
+ }, [setMessages]);
42
+ const updateStreamingMessage = (0, react_1.useCallback)((content) => {
43
+ if (!streamingMessageId.current)
44
+ return;
45
+ pendingContent.current += content;
46
+ if (rafId.current === null) {
47
+ rafId.current = requestAnimationFrame(() => {
48
+ const contentToAdd = pendingContent.current;
49
+ pendingContent.current = "";
50
+ rafId.current = null;
51
+ setMessages((prev) => prev.map((m) => {
52
+ if (m.id === streamingMessageId.current) {
53
+ return { ...m, content: m.content + contentToAdd };
54
+ }
55
+ return m;
56
+ }));
57
+ });
58
+ }
59
+ }, [setMessages]);
60
+ (0, react_1.useEffect)(() => {
61
+ return () => {
62
+ if (rafId.current !== null) {
63
+ cancelAnimationFrame(rafId.current);
64
+ }
65
+ };
66
+ }, []);
67
+ return {
68
+ streamingMessageId,
69
+ updateStreamingMessage,
70
+ flushPendingContent,
71
+ };
72
+ }
73
+ //# sourceMappingURL=use-streaming-message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-streaming-message.js","sourceRoot":"","sources":["../src/use-streaming-message.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;;;AAsDZ,kDAoEC;AAxHD;;;;;;;;;;;GAWG;AAEH,iCAAsD;AAmCtD,8EAA8E;AAC9E,OAAO;AACP,8EAA8E;AAE9E,SAAgB,mBAAmB,CACjC,WAA8B;IAE9B,MAAM,kBAAkB,GAAG,IAAA,cAAM,EAAgB,IAAI,CAAC,CAAA;IACtD,MAAM,cAAc,GAAG,IAAA,cAAM,EAAC,EAAE,CAAC,CAAA;IACjC,MAAM,KAAK,GAAG,IAAA,cAAM,EAAgB,IAAI,CAAC,CAAA;IAEzC,MAAM,mBAAmB,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QAC3C,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC3B,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YACnC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;QACtB,CAAC;QAED,IAAI,cAAc,CAAC,OAAO,IAAI,kBAAkB,CAAC,OAAO,EAAE,CAAC;YACzD,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAA;YAC3C,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAA;YAC5C,cAAc,CAAC,OAAO,GAAG,EAAE,CAAA;YAE3B,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CACnB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACb,IAAI,CAAC,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;oBACvB,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,GAAG,YAAY,EAAE,CAAA;gBACpD,CAAC;gBACD,OAAO,CAAC,CAAA;YACV,CAAC,CAAC,CACH,CAAA;QACH,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAA;IAEjB,MAAM,sBAAsB,GAAG,IAAA,mBAAW,EACxC,CAAC,OAAe,EAAE,EAAE;QAClB,IAAI,CAAC,kBAAkB,CAAC,OAAO;YAAE,OAAM;QAEvC,cAAc,CAAC,OAAO,IAAI,OAAO,CAAA;QAEjC,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC3B,KAAK,CAAC,OAAO,GAAG,qBAAqB,CAAC,GAAG,EAAE;gBACzC,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAA;gBAC3C,cAAc,CAAC,OAAO,GAAG,EAAE,CAAA;gBAC3B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;gBAEpB,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CACnB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;oBACb,IAAI,CAAC,CAAC,EAAE,KAAK,kBAAkB,CAAC,OAAO,EAAE,CAAC;wBACxC,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,GAAG,YAAY,EAAE,CAAA;oBACpD,CAAC;oBACD,OAAO,CAAC,CAAA;gBACV,CAAC,CAAC,CACH,CAAA;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,EACD,CAAC,WAAW,CAAC,CACd,CAAA;IAED,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC3B,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YACrC,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO;QACL,kBAAkB;QAClB,sBAAsB;QACtB,mBAAmB;KACpB,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@portablecore/chat-runtime",
3
+ "version": "0.1.0",
4
+ "description": "Headless chat runtime for Portable platforms — SSE streaming, message state, and lifecycle hooks",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "keywords": [
17
+ "portable",
18
+ "chat",
19
+ "runtime",
20
+ "sse",
21
+ "streaming"
22
+ ],
23
+ "author": "Portable",
24
+ "license": "UNLICENSED",
25
+ "peerDependencies": {
26
+ "react": ">=18.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.3.0",
30
+ "@types/react": "^18.0.0",
31
+ "react": "^18.0.0"
32
+ },
33
+ "dependencies": {
34
+ "@portablecore/types": "0.10.0"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "dev": "tsc --watch",
39
+ "clean": "rm -rf dist",
40
+ "typecheck": "tsc --noEmit"
41
+ }
42
+ }