@octavus/client-sdk 0.0.4 → 0.0.6

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/index.d.ts CHANGED
@@ -1,83 +1,98 @@
1
- import { ToolCallInfo, DisplayMode, MessagePart } from '@octavus/core';
1
+ import { UIMessage, StreamEvent, UIMessagePart } from '@octavus/core';
2
2
  export * from '@octavus/core';
3
3
 
4
- interface ToolCallWithDescription extends ToolCallInfo {
5
- description?: string;
6
- display?: DisplayMode;
7
- }
8
- interface ExecutionStep {
9
- id: string;
10
- name: string;
11
- type: string;
12
- display: DisplayMode;
13
- description?: string;
14
- outputToChat: boolean;
15
- summary?: string;
16
- fullOutput?: string;
17
- reasoning?: string;
18
- toolCalls?: ToolCallWithDescription[];
19
- thread?: string;
20
- }
21
- interface Message {
22
- id: string;
23
- role: 'user' | 'assistant' | 'system';
4
+ type ChatStatus = 'idle' | 'streaming' | 'error';
5
+ type TriggerFunction = (triggerName: string, input?: Record<string, unknown>) => Promise<Response>;
6
+ /**
7
+ * Input for creating a user message.
8
+ * Currently supports text content. Will be extended to support attachments, images, etc.
9
+ */
10
+ interface UserMessageInput {
11
+ /** Text content of the message */
24
12
  content: string;
25
- toolCalls?: ToolCallWithDescription[];
26
- executionSteps?: ExecutionStep[];
27
- /** If false, message is not shown to user (internal directive) */
28
- visible?: boolean;
29
- /** Ordered message parts - single source of truth for display */
30
- parts?: MessagePart[];
31
- reasoning?: string;
32
- createdAt: Date;
33
13
  }
34
- type BlockStatus = 'pending' | 'running' | 'completed' | 'error';
35
- interface ExecutionBlock {
36
- id: string;
37
- name: string;
38
- type: string;
39
- status: BlockStatus;
40
- display: DisplayMode;
41
- description?: string;
42
- /** False for independent blocks */
43
- outputToChat: boolean;
44
- thread?: string;
45
- streamingText: string;
46
- summary?: string;
47
- reasoning?: string;
48
- toolCalls: ToolCallWithDescription[];
49
- startedAt: Date;
50
- completedAt?: Date;
51
- }
52
- type ChatStatus = 'idle' | 'loading' | 'streaming' | 'error';
53
- type TriggerFunction = (triggerName: string, input?: Record<string, unknown>) => Promise<Response>;
54
- interface UseOctavusChatOptions {
14
+ interface OctavusChatOptions {
15
+ /** Function to make the API call to trigger the agent */
55
16
  onTrigger: TriggerFunction;
56
- initialMessages?: Message[];
57
- onMessage?: (message: Message) => void;
17
+ /** Initial messages (for session refresh) */
18
+ initialMessages?: UIMessage[];
19
+ /** Callback when an error occurs */
58
20
  onError?: (error: Error) => void;
59
- onDone?: () => void;
21
+ /** Callback when streaming finishes */
22
+ onFinish?: () => void;
23
+ /** Callback when a resource is updated */
60
24
  onResourceUpdate?: (name: string, value: unknown) => void;
61
- onBlockStart?: (block: ExecutionBlock) => void;
62
- onBlockEnd?: (block: ExecutionBlock) => void;
63
25
  }
64
- interface UseOctavusChatReturn {
65
- messages: Message[];
66
- status: ChatStatus;
67
- isLoading: boolean;
68
- error: Error | null;
26
+ type Listener = () => void;
27
+ /**
28
+ * Framework-agnostic chat client for Octavus agents.
29
+ * Manages chat state and streaming, allowing reactive frameworks to subscribe to updates.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const chat = new OctavusChat({
34
+ * onTrigger: (triggerName, input) =>
35
+ * fetch('/api/trigger', {
36
+ * method: 'POST',
37
+ * body: JSON.stringify({ sessionId, triggerName, input }),
38
+ * }),
39
+ * });
40
+ *
41
+ * // Subscribe to updates
42
+ * const unsubscribe = chat.subscribe(() => {
43
+ * console.log('Messages:', chat.messages);
44
+ * console.log('Status:', chat.status);
45
+ * });
46
+ *
47
+ * // Send a message
48
+ * await chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });
49
+ *
50
+ * // Cleanup
51
+ * unsubscribe();
52
+ * ```
53
+ */
54
+ declare class OctavusChat {
55
+ private _messages;
56
+ private _status;
57
+ private _error;
58
+ private options;
59
+ private abortController;
60
+ private streamingState;
61
+ private listeners;
62
+ constructor(options: OctavusChatOptions);
63
+ get messages(): UIMessage[];
64
+ get status(): ChatStatus;
65
+ get error(): Error | null;
69
66
  /**
70
- * Add a user message to the UI state.
71
- * Does NOT trigger the agent - call triggerAction() after.
67
+ * Subscribe to state changes. The callback is called whenever messages, status, or error changes.
68
+ * @returns Unsubscribe function
72
69
  */
73
- addUserMessage: (content: string) => void;
74
- triggerAction: (triggerName: string, input?: Record<string, unknown>) => Promise<void>;
75
- streamingText: string;
76
- /** Streaming parts in MessagePart format - single source of truth */
77
- streamingParts: MessagePart[];
78
- executionBlocks: ExecutionBlock[];
79
- reasoningText: string;
70
+ subscribe(listener: Listener): () => void;
71
+ private notifyListeners;
72
+ private setMessages;
73
+ private setStatus;
74
+ private setError;
75
+ /**
76
+ * Trigger the agent and optionally add a user message to the chat.
77
+ *
78
+ * @param triggerName - The trigger name defined in the agent's protocol.yaml
79
+ * @param input - Input parameters for the trigger (variable substitutions)
80
+ * @param options.userMessage - If provided, adds a user message to the chat before triggering
81
+ */
82
+ send(triggerName: string, input?: Record<string, unknown>, sendOptions?: {
83
+ userMessage?: UserMessageInput;
84
+ }): Promise<void>;
85
+ /** Stop the current streaming and finalize any partial message */
86
+ stop(): void;
87
+ private updateStreamingMessage;
80
88
  }
81
- declare function useOctavusChat(options: UseOctavusChatOptions): UseOctavusChatReturn;
82
89
 
83
- export { type BlockStatus, type ChatStatus, type ExecutionBlock, type ExecutionStep, type Message, type ToolCallWithDescription, type TriggerFunction, type UseOctavusChatOptions, type UseOctavusChatReturn, useOctavusChat };
90
+ /**
91
+ * Parse SSE stream events
92
+ */
93
+ declare function parseSSEStream(response: Response): AsyncGenerator<StreamEvent, void, unknown>;
94
+
95
+ /** Non-main thread content (e.g., "summary") is typically displayed differently. */
96
+ declare function isOtherThread(part: UIMessagePart): boolean;
97
+
98
+ export { type ChatStatus, OctavusChat, type OctavusChatOptions, type TriggerFunction, type UserMessageInput, isOtherThread, parseSSEStream };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- // src/hooks/use-octavus-chat.ts
2
- import { useState, useCallback, useRef } from "react";
3
- import { generateId } from "@octavus/core";
1
+ // src/chat.ts
2
+ import {
3
+ generateId
4
+ } from "@octavus/core";
4
5
 
5
6
  // src/stream/reader.ts
6
7
  import { safeParseStreamEvent } from "@octavus/core";
@@ -39,397 +40,417 @@ async function* parseSSEStream(response) {
39
40
  }
40
41
  }
41
42
 
42
- // src/hooks/use-octavus-chat.ts
43
+ // src/chat.ts
44
+ var OPERATION_BLOCK_TYPES = /* @__PURE__ */ new Set(["set-resource", "serialize-thread"]);
43
45
  async function parseErrorResponse(response) {
44
46
  try {
45
47
  const data = await response.json();
46
- return data.error ?? data.errorText ?? data.message ?? `Request failed: ${response.status}`;
48
+ return data.error ?? data.message ?? `Request failed: ${response.status}`;
47
49
  } catch {
48
50
  return `Request failed: ${response.status}`;
49
51
  }
50
52
  }
51
- function findOrCreateTextPart(parts, thread) {
52
- for (let i = parts.length - 1; i >= 0; i--) {
53
- const part = parts[i];
54
- if (!part) continue;
55
- if (part.type === "text" && part.thread === thread) {
56
- return { part, index: i };
57
- }
58
- if (part.type !== "text") break;
59
- }
60
- const newPart = {
61
- type: "text",
62
- visible: true,
63
- content: "",
64
- thread
53
+ function createUserMessage(input) {
54
+ return {
55
+ id: generateId(),
56
+ role: "user",
57
+ parts: [{ type: "text", text: input.content, status: "done" }],
58
+ status: "done",
59
+ createdAt: /* @__PURE__ */ new Date()
60
+ };
61
+ }
62
+ function createEmptyStreamingState() {
63
+ return {
64
+ messageId: generateId(),
65
+ parts: [],
66
+ activeBlock: null,
67
+ blocks: /* @__PURE__ */ new Map(),
68
+ currentTextPartIndex: null,
69
+ currentReasoningPartIndex: null
65
70
  };
66
- parts.push(newPart);
67
- return { part: newPart, index: parts.length - 1 };
68
71
  }
69
- function useOctavusChat(options) {
70
- const {
71
- onTrigger,
72
- initialMessages = [],
73
- onMessage,
74
- onError,
75
- onDone,
76
- onResourceUpdate,
77
- onBlockStart,
78
- onBlockEnd
79
- } = options;
80
- const [messages, setMessages] = useState(initialMessages);
81
- const [status, setStatus] = useState("idle");
82
- const [error, setError] = useState(null);
83
- const [streamingText, setStreamingText] = useState("");
84
- const [streamingParts, setStreamingParts] = useState([]);
85
- const [executionBlocks, setExecutionBlocks] = useState([]);
86
- const [reasoningText, setReasoningText] = useState("");
87
- const abortControllerRef = useRef(null);
88
- const triggerAction = useCallback(
89
- async (triggerName, input) => {
90
- abortControllerRef.current?.abort();
91
- abortControllerRef.current = null;
92
- const abortController = new AbortController();
93
- abortControllerRef.current = abortController;
94
- setStatus("loading");
95
- setError(null);
96
- setStreamingText("");
97
- setStreamingParts([]);
98
- setExecutionBlocks([]);
99
- setReasoningText("");
100
- const blocks = [];
101
- let activeBlock = null;
102
- const toolCalls = [];
103
- let visibleFullText = "";
104
- let executionReasoningText = "";
105
- const reasoningStack = [];
106
- const getCurrentReasoning = () => reasoningStack[reasoningStack.length - 1];
107
- const parts = [];
108
- try {
109
- const response = await onTrigger(triggerName, input);
110
- if (!response.ok) {
111
- const errorMessage = await parseErrorResponse(response);
112
- throw new Error(errorMessage);
72
+ function buildMessageFromState(state, status) {
73
+ return {
74
+ id: state.messageId,
75
+ role: "assistant",
76
+ parts: [...state.parts],
77
+ status,
78
+ createdAt: /* @__PURE__ */ new Date()
79
+ };
80
+ }
81
+ var OctavusChat = class {
82
+ // Private state
83
+ _messages;
84
+ _status = "idle";
85
+ _error = null;
86
+ options;
87
+ abortController = null;
88
+ streamingState = null;
89
+ // Listener sets for reactive frameworks
90
+ listeners = /* @__PURE__ */ new Set();
91
+ constructor(options) {
92
+ this.options = options;
93
+ this._messages = options.initialMessages ?? [];
94
+ }
95
+ // =========================================================================
96
+ // Public Getters
97
+ // =========================================================================
98
+ get messages() {
99
+ return this._messages;
100
+ }
101
+ get status() {
102
+ return this._status;
103
+ }
104
+ get error() {
105
+ return this._error;
106
+ }
107
+ // =========================================================================
108
+ // Subscription Methods (for reactive frameworks)
109
+ // =========================================================================
110
+ /**
111
+ * Subscribe to state changes. The callback is called whenever messages, status, or error changes.
112
+ * @returns Unsubscribe function
113
+ */
114
+ subscribe(listener) {
115
+ this.listeners.add(listener);
116
+ return () => this.listeners.delete(listener);
117
+ }
118
+ notifyListeners() {
119
+ this.listeners.forEach((l) => l());
120
+ }
121
+ // =========================================================================
122
+ // Private Setters (notify listeners)
123
+ // =========================================================================
124
+ setMessages(messages) {
125
+ this._messages = messages;
126
+ this.notifyListeners();
127
+ }
128
+ setStatus(status) {
129
+ this._status = status;
130
+ this.notifyListeners();
131
+ }
132
+ setError(error) {
133
+ this._error = error;
134
+ this.notifyListeners();
135
+ }
136
+ // =========================================================================
137
+ // Public Methods
138
+ // =========================================================================
139
+ /**
140
+ * Trigger the agent and optionally add a user message to the chat.
141
+ *
142
+ * @param triggerName - The trigger name defined in the agent's protocol.yaml
143
+ * @param input - Input parameters for the trigger (variable substitutions)
144
+ * @param options.userMessage - If provided, adds a user message to the chat before triggering
145
+ */
146
+ async send(triggerName, input, sendOptions) {
147
+ this.abortController?.abort();
148
+ const abortController = new AbortController();
149
+ this.abortController = abortController;
150
+ if (sendOptions?.userMessage !== void 0) {
151
+ const userMsg = createUserMessage(sendOptions.userMessage);
152
+ this.setMessages([...this._messages, userMsg]);
153
+ }
154
+ this.setStatus("streaming");
155
+ this.setError(null);
156
+ this.streamingState = createEmptyStreamingState();
157
+ try {
158
+ const response = await this.options.onTrigger(triggerName, input);
159
+ if (!response.ok) {
160
+ const errorMessage = await parseErrorResponse(response);
161
+ throw new Error(errorMessage);
162
+ }
163
+ for await (const event of parseSSEStream(response)) {
164
+ if (abortController.signal.aborted) {
165
+ break;
113
166
  }
114
- setStatus("streaming");
115
- for await (const event of parseSSEStream(response)) {
116
- if (abortController.signal.aborted) {
167
+ const state = this.streamingState;
168
+ if (!state) break;
169
+ switch (event.type) {
170
+ case "start":
117
171
  break;
118
- }
119
- switch (event.type) {
120
- case "start":
121
- break;
122
- case "block-start": {
123
- const newBlock = {
124
- id: event.blockId,
125
- name: event.blockName,
126
- type: event.blockType,
172
+ case "block-start": {
173
+ const block = {
174
+ blockId: event.blockId,
175
+ blockName: event.blockName,
176
+ blockType: event.blockType,
177
+ display: event.display,
178
+ description: event.description,
179
+ outputToChat: event.outputToChat ?? true,
180
+ thread: event.thread,
181
+ reasoning: "",
182
+ text: "",
183
+ toolCalls: /* @__PURE__ */ new Map()
184
+ };
185
+ state.blocks.set(event.blockId, block);
186
+ state.activeBlock = block;
187
+ const isOperation = OPERATION_BLOCK_TYPES.has(event.blockType);
188
+ const isHidden = event.display === "hidden";
189
+ if (isOperation && !isHidden) {
190
+ const thread = event.thread;
191
+ const operationPart = {
192
+ type: "operation",
193
+ operationId: event.blockId,
194
+ name: event.description || event.blockName,
195
+ operationType: event.blockType,
127
196
  status: "running",
128
- display: event.display,
129
- description: event.description,
130
- outputToChat: event.outputToChat ?? false,
131
- thread: event.thread,
132
- streamingText: "",
133
- toolCalls: [],
134
- startedAt: /* @__PURE__ */ new Date()
197
+ thread: thread && thread !== "main" ? thread : void 0
135
198
  };
136
- blocks.push(newBlock);
137
- activeBlock = newBlock;
138
- setExecutionBlocks([...blocks]);
139
- onBlockStart?.(newBlock);
140
- break;
199
+ state.parts.push(operationPart);
141
200
  }
142
- case "block-end": {
143
- if (activeBlock && activeBlock.id === event.blockId) {
144
- activeBlock.status = "completed";
145
- activeBlock.summary = event.summary;
146
- activeBlock.completedAt = /* @__PURE__ */ new Date();
147
- setExecutionBlocks([...blocks]);
148
- onBlockEnd?.(activeBlock);
149
- activeBlock = null;
150
- }
151
- break;
201
+ state.currentTextPartIndex = null;
202
+ state.currentReasoningPartIndex = null;
203
+ this.updateStreamingMessage();
204
+ break;
205
+ }
206
+ case "block-end": {
207
+ const operationPartIndex = state.parts.findIndex(
208
+ (p) => p.type === "operation" && p.operationId === event.blockId
209
+ );
210
+ if (operationPartIndex >= 0) {
211
+ const part = state.parts[operationPartIndex];
212
+ state.parts[operationPartIndex] = { ...part, status: "done" };
152
213
  }
153
- case "text-delta":
154
- if (activeBlock) {
155
- activeBlock.streamingText += event.delta;
156
- setExecutionBlocks([...blocks]);
157
- const isNonMainThread = activeBlock.thread !== void 0 && activeBlock.thread !== "main";
158
- const shouldCaptureText = activeBlock.outputToChat || isNonMainThread;
159
- if (shouldCaptureText) {
160
- const { part, index } = findOrCreateTextPart(parts, activeBlock.thread);
161
- part.content = (part.content ?? "") + event.delta;
162
- parts[index] = { ...part };
163
- if (activeBlock.outputToChat) {
164
- visibleFullText += event.delta;
165
- setStreamingText(visibleFullText);
166
- }
167
- setStreamingParts([...parts]);
168
- }
169
- }
170
- break;
171
- case "text-start":
172
- case "text-end":
173
- break;
174
- case "reasoning-start": {
175
- const isNonMainThread = activeBlock?.thread !== void 0 && activeBlock.thread !== "main";
176
- const shouldCapture = (activeBlock?.outputToChat ?? false) || isNonMainThread;
177
- const newPart = {
178
- type: "reasoning",
179
- visible: false,
180
- content: "",
181
- thread: activeBlock?.thread
182
- };
183
- parts.push(newPart);
184
- reasoningStack.push({
185
- id: event.id,
186
- outputToChat: shouldCapture,
187
- partIndex: parts.length - 1
188
- });
189
- setReasoningText("");
190
- break;
214
+ if (state.activeBlock?.blockId === event.blockId) {
215
+ state.activeBlock = null;
191
216
  }
192
- case "reasoning-delta": {
193
- const ctx = getCurrentReasoning();
194
- const reasoningPart = ctx ? parts[ctx.partIndex] : void 0;
195
- if (ctx && reasoningPart) {
196
- reasoningPart.content = (reasoningPart.content ?? "") + event.delta;
197
- parts[ctx.partIndex] = { ...reasoningPart };
198
- }
199
- if (activeBlock?.outputToChat) {
200
- executionReasoningText += event.delta;
201
- }
202
- setReasoningText((prev) => prev + event.delta);
203
- if (activeBlock) {
204
- activeBlock.reasoning = (activeBlock.reasoning ?? "") + event.delta;
205
- setExecutionBlocks([...blocks]);
206
- }
207
- if (ctx?.outputToChat) {
208
- setStreamingParts([...parts]);
209
- }
210
- break;
217
+ this.updateStreamingMessage();
218
+ break;
219
+ }
220
+ case "reasoning-start": {
221
+ const thread = state.activeBlock?.thread;
222
+ const reasoningPart = {
223
+ type: "reasoning",
224
+ text: "",
225
+ status: "streaming",
226
+ thread: thread && thread !== "main" ? thread : void 0
227
+ };
228
+ state.parts.push(reasoningPart);
229
+ state.currentReasoningPartIndex = state.parts.length - 1;
230
+ this.updateStreamingMessage();
231
+ break;
232
+ }
233
+ case "reasoning-delta": {
234
+ if (state.currentReasoningPartIndex !== null) {
235
+ const part = state.parts[state.currentReasoningPartIndex];
236
+ part.text += event.delta;
237
+ state.parts[state.currentReasoningPartIndex] = { ...part };
211
238
  }
212
- case "reasoning-end": {
213
- const ctx = reasoningStack.pop();
214
- setReasoningText("");
215
- const endReasoningPart = ctx ? parts[ctx.partIndex] : void 0;
216
- if (ctx && endReasoningPart && !endReasoningPart.content?.trim()) {
217
- parts.splice(ctx.partIndex, 1);
218
- }
219
- if (ctx?.outputToChat) {
220
- setStreamingParts([...parts]);
221
- }
222
- break;
239
+ if (state.activeBlock) {
240
+ state.activeBlock.reasoning += event.delta;
223
241
  }
224
- case "tool-input-start": {
225
- const newToolCall = {
226
- id: event.toolCallId,
227
- name: event.toolName,
228
- arguments: {},
242
+ this.updateStreamingMessage();
243
+ break;
244
+ }
245
+ case "reasoning-end": {
246
+ if (state.currentReasoningPartIndex !== null) {
247
+ const part = state.parts[state.currentReasoningPartIndex];
248
+ part.status = "done";
249
+ state.parts[state.currentReasoningPartIndex] = { ...part };
250
+ state.currentReasoningPartIndex = null;
251
+ }
252
+ this.updateStreamingMessage();
253
+ break;
254
+ }
255
+ case "text-start": {
256
+ const thread = state.activeBlock?.thread;
257
+ const isOtherThread2 = Boolean(thread && thread !== "main");
258
+ const shouldAddPart = state.activeBlock?.outputToChat !== false || isOtherThread2;
259
+ if (shouldAddPart) {
260
+ const textPart = {
261
+ type: "text",
262
+ text: "",
229
263
  status: "streaming",
230
- description: event.title
264
+ thread: isOtherThread2 ? thread : void 0
231
265
  };
232
- toolCalls.push(newToolCall);
233
- if (activeBlock) {
234
- activeBlock.toolCalls = [...activeBlock.toolCalls, newToolCall];
235
- setExecutionBlocks([...blocks]);
236
- }
237
- const isVisibleBlock = activeBlock?.display !== "hidden";
238
- if (isVisibleBlock) {
239
- const toolPart = {
240
- type: "tool-call",
241
- visible: true,
242
- toolCall: newToolCall,
243
- thread: activeBlock?.thread
244
- };
245
- parts.push(toolPart);
246
- setStreamingParts([...parts]);
247
- }
248
- break;
266
+ state.parts.push(textPart);
267
+ state.currentTextPartIndex = state.parts.length - 1;
249
268
  }
250
- case "tool-input-delta": {
251
- const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);
252
- if (toolCall) {
253
- try {
254
- toolCall.arguments = JSON.parse(event.inputTextDelta);
255
- } catch {
256
- }
257
- const argsPart = parts.find(
258
- (p) => p.type === "tool-call" && p.toolCall?.id === event.toolCallId
259
- );
260
- if (argsPart?.toolCall) {
261
- argsPart.toolCall = { ...argsPart.toolCall, arguments: toolCall.arguments };
262
- setStreamingParts([...parts]);
263
- }
264
- if (activeBlock) {
265
- setExecutionBlocks([...blocks]);
266
- }
267
- }
268
- break;
269
+ this.updateStreamingMessage();
270
+ break;
271
+ }
272
+ case "text-delta": {
273
+ if (state.currentTextPartIndex !== null) {
274
+ const part = state.parts[state.currentTextPartIndex];
275
+ part.text += event.delta;
276
+ state.parts[state.currentTextPartIndex] = { ...part };
269
277
  }
270
- case "tool-input-end": {
271
- break;
278
+ if (state.activeBlock) {
279
+ state.activeBlock.text += event.delta;
272
280
  }
273
- case "tool-input-available": {
274
- const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);
275
- if (toolCall) {
276
- toolCall.arguments = event.input;
277
- toolCall.status = "available";
278
- const inputPart = parts.find(
279
- (p) => p.type === "tool-call" && p.toolCall?.id === event.toolCallId
280
- );
281
- if (inputPart?.toolCall) {
282
- inputPart.toolCall = {
283
- ...inputPart.toolCall,
284
- arguments: toolCall.arguments,
285
- status: "available"
286
- };
287
- setStreamingParts([...parts]);
288
- }
289
- if (activeBlock) {
290
- setExecutionBlocks([...blocks]);
291
- }
292
- }
293
- break;
281
+ this.updateStreamingMessage();
282
+ break;
283
+ }
284
+ case "text-end": {
285
+ if (state.currentTextPartIndex !== null) {
286
+ const part = state.parts[state.currentTextPartIndex];
287
+ part.status = "done";
288
+ state.parts[state.currentTextPartIndex] = { ...part };
289
+ state.currentTextPartIndex = null;
294
290
  }
295
- case "tool-output-available": {
296
- const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);
297
- if (toolCall) {
298
- toolCall.status = "available";
299
- toolCall.result = event.output;
300
- const resultPart = parts.find(
301
- (p) => p.type === "tool-call" && p.toolCall?.id === event.toolCallId
302
- );
303
- if (resultPart?.toolCall) {
304
- resultPart.toolCall = {
305
- ...resultPart.toolCall,
306
- status: "available",
307
- result: event.output
308
- };
309
- setStreamingParts([...parts]);
310
- }
311
- if (activeBlock) {
312
- setExecutionBlocks([...blocks]);
313
- }
314
- }
315
- break;
291
+ this.updateStreamingMessage();
292
+ break;
293
+ }
294
+ case "tool-input-start": {
295
+ const thread = state.activeBlock?.thread;
296
+ const toolPart = {
297
+ type: "tool-call",
298
+ toolCallId: event.toolCallId,
299
+ toolName: event.toolName,
300
+ displayName: event.title,
301
+ args: {},
302
+ status: "pending",
303
+ thread: thread && thread !== "main" ? thread : void 0
304
+ };
305
+ state.parts.push(toolPart);
306
+ if (state.activeBlock) {
307
+ state.activeBlock.toolCalls.set(event.toolCallId, toolPart);
316
308
  }
317
- case "tool-output-error": {
318
- const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);
319
- if (toolCall) {
320
- toolCall.status = "error";
321
- toolCall.error = event.errorText;
322
- const errorPart = parts.find(
323
- (p) => p.type === "tool-call" && p.toolCall?.id === event.toolCallId
324
- );
325
- if (errorPart?.toolCall) {
326
- errorPart.toolCall = {
327
- ...errorPart.toolCall,
328
- status: "error",
329
- error: event.errorText
330
- };
331
- setStreamingParts([...parts]);
332
- }
333
- if (activeBlock) {
334
- setExecutionBlocks([...blocks]);
335
- }
309
+ this.updateStreamingMessage();
310
+ break;
311
+ }
312
+ case "tool-input-delta": {
313
+ const toolPartIndex = state.parts.findIndex(
314
+ (p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
315
+ );
316
+ if (toolPartIndex >= 0) {
317
+ try {
318
+ const part = state.parts[toolPartIndex];
319
+ part.args = JSON.parse(event.inputTextDelta);
320
+ state.parts[toolPartIndex] = { ...part };
321
+ this.updateStreamingMessage();
322
+ } catch {
336
323
  }
337
- break;
338
324
  }
339
- case "resource-update":
340
- onResourceUpdate?.(event.name, event.value);
341
- break;
342
- case "finish": {
343
- while (reasoningStack.length > 0) {
344
- const ctx = reasoningStack.pop();
345
- const remainingPart = ctx ? parts[ctx.partIndex] : void 0;
346
- if (ctx && remainingPart && !remainingPart.content?.trim()) {
347
- parts.splice(ctx.partIndex, 1);
348
- }
325
+ break;
326
+ }
327
+ case "tool-input-end":
328
+ break;
329
+ case "tool-input-available": {
330
+ const toolPartIndex = state.parts.findIndex(
331
+ (p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
332
+ );
333
+ if (toolPartIndex >= 0) {
334
+ const part = state.parts[toolPartIndex];
335
+ part.args = event.input;
336
+ part.status = "running";
337
+ state.parts[toolPartIndex] = { ...part };
338
+ this.updateStreamingMessage();
339
+ }
340
+ break;
341
+ }
342
+ case "tool-output-available": {
343
+ const toolPartIndex = state.parts.findIndex(
344
+ (p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
345
+ );
346
+ if (toolPartIndex >= 0) {
347
+ const part = state.parts[toolPartIndex];
348
+ part.result = event.output;
349
+ part.status = "done";
350
+ state.parts[toolPartIndex] = { ...part };
351
+ this.updateStreamingMessage();
352
+ }
353
+ break;
354
+ }
355
+ case "tool-output-error": {
356
+ const toolPartIndex = state.parts.findIndex(
357
+ (p) => p.type === "tool-call" && p.toolCallId === event.toolCallId
358
+ );
359
+ if (toolPartIndex >= 0) {
360
+ const part = state.parts[toolPartIndex];
361
+ part.error = event.errorText;
362
+ part.status = "error";
363
+ state.parts[toolPartIndex] = { ...part };
364
+ this.updateStreamingMessage();
365
+ }
366
+ break;
367
+ }
368
+ case "resource-update":
369
+ this.options.onResourceUpdate?.(event.name, event.value);
370
+ break;
371
+ case "finish": {
372
+ const finalMessage = buildMessageFromState(state, "done");
373
+ finalMessage.parts = finalMessage.parts.map((part) => {
374
+ if (part.type === "text" || part.type === "reasoning") {
375
+ return { ...part, status: "done" };
349
376
  }
350
- const executionSteps = blocks.filter((b) => b.status === "completed").map((b) => ({
351
- id: b.id,
352
- name: b.name,
353
- type: b.type,
354
- display: b.display,
355
- description: b.description,
356
- outputToChat: b.outputToChat,
357
- summary: b.summary,
358
- fullOutput: b.streamingText || void 0,
359
- reasoning: b.reasoning || void 0,
360
- toolCalls: b.toolCalls.length > 0 ? b.toolCalls : void 0,
361
- thread: b.thread
362
- }));
363
- if (visibleFullText || executionSteps.length > 0 || executionReasoningText) {
364
- const assistantMessage = {
365
- id: generateId(),
366
- role: "assistant",
367
- content: visibleFullText,
368
- toolCalls: toolCalls.filter((tc) => tc.status === "available"),
369
- executionSteps: executionSteps.length > 0 ? executionSteps : void 0,
370
- parts: parts.length > 0 ? parts : void 0,
371
- reasoning: executionReasoningText || void 0,
372
- createdAt: /* @__PURE__ */ new Date()
373
- };
374
- setMessages((prev) => [...prev, assistantMessage]);
375
- onMessage?.(assistantMessage);
377
+ return part;
378
+ });
379
+ if (finalMessage.parts.length > 0) {
380
+ const messages = [...this._messages];
381
+ const lastMsg = messages[messages.length - 1];
382
+ if (lastMsg && lastMsg.id === state.messageId) {
383
+ messages[messages.length - 1] = finalMessage;
384
+ } else {
385
+ messages.push(finalMessage);
376
386
  }
377
- setStatus("idle");
378
- setStreamingText("");
379
- setStreamingParts([]);
380
- setExecutionBlocks([]);
381
- setReasoningText("");
382
- onDone?.();
383
- break;
387
+ this.setMessages(messages);
384
388
  }
385
- case "error":
386
- throw new Error(event.errorText);
387
- case "tool-request":
388
- break;
389
+ this.setStatus("idle");
390
+ this.streamingState = null;
391
+ this.options.onFinish?.();
392
+ break;
389
393
  }
394
+ case "error":
395
+ throw new Error(event.errorText);
396
+ case "tool-request":
397
+ break;
390
398
  }
391
- } catch (err) {
392
- const errorObj = err instanceof Error ? err : new Error("Unknown error");
393
- setError(errorObj);
394
- setStatus("error");
395
- if (activeBlock) {
396
- activeBlock.status = "error";
397
- setExecutionBlocks([...blocks]);
398
- }
399
- onError?.(errorObj);
400
- } finally {
401
- abortControllerRef.current = null;
402
399
  }
403
- },
404
- [onTrigger, onMessage, onError, onDone, onResourceUpdate, onBlockStart, onBlockEnd]
405
- );
406
- const addUserMessage = useCallback(
407
- (content) => {
408
- const userMessage = {
409
- id: generateId(),
410
- role: "user",
411
- content,
412
- createdAt: /* @__PURE__ */ new Date()
413
- };
414
- setMessages((prev) => [...prev, userMessage]);
415
- onMessage?.(userMessage);
416
- },
417
- [onMessage]
418
- );
419
- return {
420
- messages,
421
- status,
422
- isLoading: status === "loading" || status === "streaming",
423
- error,
424
- addUserMessage,
425
- triggerAction,
426
- streamingText,
427
- streamingParts,
428
- executionBlocks,
429
- reasoningText
430
- };
400
+ } catch (err) {
401
+ const errorObj = err instanceof Error ? err : new Error("Unknown error");
402
+ this.setError(errorObj);
403
+ this.setStatus("error");
404
+ this.streamingState = null;
405
+ this.options.onError?.(errorObj);
406
+ } finally {
407
+ this.abortController = null;
408
+ }
409
+ }
410
+ /** Stop the current streaming and finalize any partial message */
411
+ stop() {
412
+ this.abortController?.abort();
413
+ this.abortController = null;
414
+ const state = this.streamingState;
415
+ if (state && state.parts.length > 0) {
416
+ const finalMessage = buildMessageFromState(state, "done");
417
+ const messages = [...this._messages];
418
+ const lastMsg = messages[messages.length - 1];
419
+ if (lastMsg && lastMsg.id === state.messageId) {
420
+ messages[messages.length - 1] = finalMessage;
421
+ } else {
422
+ messages.push(finalMessage);
423
+ }
424
+ this.setMessages(messages);
425
+ }
426
+ this.streamingState = null;
427
+ this.setStatus("idle");
428
+ }
429
+ // =========================================================================
430
+ // Private Helpers
431
+ // =========================================================================
432
+ updateStreamingMessage() {
433
+ const state = this.streamingState;
434
+ if (!state) return;
435
+ const msg = buildMessageFromState(state, "streaming");
436
+ const messages = [...this._messages];
437
+ const lastMsg = messages[messages.length - 1];
438
+ if (lastMsg && lastMsg.id === state.messageId) {
439
+ messages[messages.length - 1] = msg;
440
+ } else {
441
+ messages.push(msg);
442
+ }
443
+ this.setMessages(messages);
444
+ }
445
+ };
446
+
447
+ // src/utils/message-helpers.ts
448
+ function isOtherThread(part) {
449
+ return Boolean(part.thread && part.thread !== "main");
431
450
  }
432
451
  export {
433
- useOctavusChat
452
+ OctavusChat,
453
+ isOtherThread,
454
+ parseSSEStream
434
455
  };
435
456
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/use-octavus-chat.ts","../src/stream/reader.ts"],"sourcesContent":["'use client';\n\nimport { useState, useCallback, useRef } from 'react';\nimport { generateId, type ToolCallInfo, type DisplayMode, type MessagePart } from '@octavus/core';\nimport { parseSSEStream } from '@/stream/reader';\n\nexport interface ToolCallWithDescription extends ToolCallInfo {\n description?: string;\n display?: DisplayMode;\n}\n\nexport interface ExecutionStep {\n id: string;\n name: string;\n type: string;\n display: DisplayMode;\n description?: string;\n outputToChat: boolean;\n summary?: string;\n fullOutput?: string;\n reasoning?: string;\n toolCalls?: ToolCallWithDescription[];\n thread?: string;\n}\n\nexport interface Message {\n id: string;\n role: 'user' | 'assistant' | 'system';\n content: string;\n toolCalls?: ToolCallWithDescription[];\n executionSteps?: ExecutionStep[];\n /** If false, message is not shown to user (internal directive) */\n visible?: boolean;\n /** Ordered message parts - single source of truth for display */\n parts?: MessagePart[];\n reasoning?: string;\n createdAt: Date;\n}\n\nexport type BlockStatus = 'pending' | 'running' | 'completed' | 'error';\n\nexport interface ExecutionBlock {\n id: string;\n name: string;\n type: string;\n status: BlockStatus;\n display: DisplayMode;\n description?: string;\n /** False for independent blocks */\n outputToChat: boolean;\n thread?: string;\n streamingText: string;\n summary?: string;\n reasoning?: string;\n toolCalls: ToolCallWithDescription[];\n startedAt: Date;\n completedAt?: Date;\n}\n\nexport type ChatStatus = 'idle' | 'loading' | 'streaming' | 'error';\n\nexport type TriggerFunction = (\n triggerName: string,\n input?: Record<string, unknown>,\n) => Promise<Response>;\n\nexport interface UseOctavusChatOptions {\n onTrigger: TriggerFunction;\n initialMessages?: Message[];\n onMessage?: (message: Message) => void;\n onError?: (error: Error) => void;\n onDone?: () => void;\n onResourceUpdate?: (name: string, value: unknown) => void;\n onBlockStart?: (block: ExecutionBlock) => void;\n onBlockEnd?: (block: ExecutionBlock) => void;\n}\n\nexport interface UseOctavusChatReturn {\n messages: Message[];\n status: ChatStatus;\n isLoading: boolean;\n error: Error | null;\n /**\n * Add a user message to the UI state.\n * Does NOT trigger the agent - call triggerAction() after.\n */\n addUserMessage: (content: string) => void;\n triggerAction: (triggerName: string, input?: Record<string, unknown>) => Promise<void>;\n streamingText: string;\n /** Streaming parts in MessagePart format - single source of truth */\n streamingParts: MessagePart[];\n executionBlocks: ExecutionBlock[];\n reasoningText: string;\n}\n\ninterface ErrorResponse {\n error?: string;\n message?: string;\n errorText?: string;\n}\n\nasync function parseErrorResponse(response: Response): Promise<string> {\n try {\n const data = (await response.json()) as ErrorResponse;\n return data.error ?? data.errorText ?? data.message ?? `Request failed: ${response.status}`;\n } catch {\n return `Request failed: ${response.status}`;\n }\n}\n\n/**\n * Helper to find or create a text part for the current block\n */\nfunction findOrCreateTextPart(\n parts: MessagePart[],\n thread: string | undefined,\n): { part: MessagePart; index: number } {\n // Look for an existing text part for this thread at the end\n for (let i = parts.length - 1; i >= 0; i--) {\n const part = parts[i];\n if (!part) continue;\n if (part.type === 'text' && part.thread === thread) {\n return { part, index: i };\n }\n // If we hit a different part type, stop looking\n if (part.type !== 'text') break;\n }\n // Create new text part\n const newPart: MessagePart = {\n type: 'text',\n visible: true,\n content: '',\n thread,\n };\n parts.push(newPart);\n return { part: newPart, index: parts.length - 1 };\n}\n\nexport function useOctavusChat(options: UseOctavusChatOptions): UseOctavusChatReturn {\n const {\n onTrigger,\n initialMessages = [],\n onMessage,\n onError,\n onDone,\n onResourceUpdate,\n onBlockStart,\n onBlockEnd,\n } = options;\n\n const [messages, setMessages] = useState<Message[]>(initialMessages);\n const [status, setStatus] = useState<ChatStatus>('idle');\n const [error, setError] = useState<Error | null>(null);\n const [streamingText, setStreamingText] = useState('');\n const [streamingParts, setStreamingParts] = useState<MessagePart[]>([]);\n const [executionBlocks, setExecutionBlocks] = useState<ExecutionBlock[]>([]);\n const [reasoningText, setReasoningText] = useState('');\n\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const triggerAction = useCallback(\n async (triggerName: string, input?: Record<string, unknown>) => {\n // Abort any previous request\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n setStatus('loading');\n setError(null);\n setStreamingText('');\n setStreamingParts([]);\n setExecutionBlocks([]);\n setReasoningText('');\n\n const blocks: ExecutionBlock[] = [];\n let activeBlock: ExecutionBlock | null = null;\n const toolCalls: ToolCallInfo[] = [];\n\n let visibleFullText = '';\n let executionReasoningText = '';\n\n interface ReasoningContext {\n id: string;\n outputToChat: boolean;\n partIndex: number;\n }\n const reasoningStack: ReasoningContext[] = [];\n const getCurrentReasoning = () => reasoningStack[reasoningStack.length - 1];\n\n // Build MessagePart[] directly during streaming\n const parts: MessagePart[] = [];\n\n try {\n const response = await onTrigger(triggerName, input);\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new Error(errorMessage);\n }\n\n setStatus('streaming');\n\n for await (const event of parseSSEStream(response)) {\n if (abortController.signal.aborted) {\n break;\n }\n\n switch (event.type) {\n case 'start':\n break;\n\n case 'block-start': {\n const newBlock: ExecutionBlock = {\n id: event.blockId,\n name: event.blockName,\n type: event.blockType,\n status: 'running',\n display: event.display,\n description: event.description,\n outputToChat: event.outputToChat ?? false,\n thread: event.thread,\n streamingText: '',\n toolCalls: [],\n startedAt: new Date(),\n };\n blocks.push(newBlock);\n activeBlock = newBlock;\n setExecutionBlocks([...blocks]);\n onBlockStart?.(newBlock);\n break;\n }\n\n case 'block-end': {\n if (activeBlock && activeBlock.id === event.blockId) {\n activeBlock.status = 'completed';\n activeBlock.summary = event.summary;\n activeBlock.completedAt = new Date();\n setExecutionBlocks([...blocks]);\n onBlockEnd?.(activeBlock);\n activeBlock = null;\n }\n break;\n }\n\n case 'text-delta':\n if (activeBlock) {\n activeBlock.streamingText += event.delta;\n setExecutionBlocks([...blocks]);\n\n // Capture text if outputToChat OR non-main thread\n const isNonMainThread =\n activeBlock.thread !== undefined && activeBlock.thread !== 'main';\n const shouldCaptureText = activeBlock.outputToChat || isNonMainThread;\n\n if (shouldCaptureText) {\n const { part, index } = findOrCreateTextPart(parts, activeBlock.thread);\n part.content = (part.content ?? '') + event.delta;\n parts[index] = { ...part };\n\n if (activeBlock.outputToChat) {\n visibleFullText += event.delta;\n setStreamingText(visibleFullText);\n }\n\n setStreamingParts([...parts]);\n }\n }\n break;\n\n case 'text-start':\n case 'text-end':\n break;\n\n case 'reasoning-start': {\n // Non-main threads always capture content (shown as orange cards)\n const isNonMainThread =\n activeBlock?.thread !== undefined && activeBlock.thread !== 'main';\n const shouldCapture = (activeBlock?.outputToChat ?? false) || isNonMainThread;\n\n const newPart: MessagePart = {\n type: 'reasoning',\n visible: false,\n content: '',\n thread: activeBlock?.thread,\n };\n parts.push(newPart);\n\n reasoningStack.push({\n id: event.id,\n outputToChat: shouldCapture,\n partIndex: parts.length - 1,\n });\n\n setReasoningText('');\n break;\n }\n\n case 'reasoning-delta': {\n const ctx = getCurrentReasoning();\n const reasoningPart = ctx ? parts[ctx.partIndex] : undefined;\n if (ctx && reasoningPart) {\n reasoningPart.content = (reasoningPart.content ?? '') + event.delta;\n parts[ctx.partIndex] = { ...reasoningPart };\n }\n\n if (activeBlock?.outputToChat) {\n executionReasoningText += event.delta;\n }\n setReasoningText((prev) => prev + event.delta);\n\n if (activeBlock) {\n activeBlock.reasoning = (activeBlock.reasoning ?? '') + event.delta;\n setExecutionBlocks([...blocks]);\n }\n\n if (ctx?.outputToChat) {\n setStreamingParts([...parts]);\n }\n break;\n }\n\n case 'reasoning-end': {\n const ctx = reasoningStack.pop();\n setReasoningText('');\n\n const endReasoningPart = ctx ? parts[ctx.partIndex] : undefined;\n if (ctx && endReasoningPart && !endReasoningPart.content?.trim()) {\n parts.splice(ctx.partIndex, 1);\n }\n\n // Update streaming parts\n if (ctx?.outputToChat) {\n setStreamingParts([...parts]);\n }\n break;\n }\n\n case 'tool-input-start': {\n const newToolCall: ToolCallWithDescription = {\n id: event.toolCallId,\n name: event.toolName,\n arguments: {},\n status: 'streaming',\n description: event.title,\n };\n toolCalls.push(newToolCall);\n\n if (activeBlock) {\n activeBlock.toolCalls = [...activeBlock.toolCalls, newToolCall];\n setExecutionBlocks([...blocks]);\n }\n\n // Add tool call part (visible unless hidden block)\n const isVisibleBlock = activeBlock?.display !== 'hidden';\n if (isVisibleBlock) {\n const toolPart: MessagePart = {\n type: 'tool-call',\n visible: true,\n toolCall: newToolCall,\n thread: activeBlock?.thread,\n };\n parts.push(toolPart);\n setStreamingParts([...parts]);\n }\n break;\n }\n\n case 'tool-input-delta': {\n const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);\n if (toolCall) {\n try {\n toolCall.arguments = JSON.parse(event.inputTextDelta) as Record<string, unknown>;\n } catch {\n // Partial args may not be valid JSON yet\n }\n\n const argsPart = parts.find(\n (p) => p.type === 'tool-call' && p.toolCall?.id === event.toolCallId,\n );\n if (argsPart?.toolCall) {\n argsPart.toolCall = { ...argsPart.toolCall, arguments: toolCall.arguments };\n setStreamingParts([...parts]);\n }\n\n if (activeBlock) {\n setExecutionBlocks([...blocks]);\n }\n }\n break;\n }\n\n case 'tool-input-end': {\n // Tool input streaming has ended - no action needed\n // The full input is available in tool-input-available\n break;\n }\n\n case 'tool-input-available': {\n const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);\n if (toolCall) {\n toolCall.arguments = event.input as Record<string, unknown>;\n toolCall.status = 'available';\n\n const inputPart = parts.find(\n (p) => p.type === 'tool-call' && p.toolCall?.id === event.toolCallId,\n );\n if (inputPart?.toolCall) {\n inputPart.toolCall = {\n ...inputPart.toolCall,\n arguments: toolCall.arguments,\n status: 'available',\n };\n setStreamingParts([...parts]);\n }\n\n if (activeBlock) {\n setExecutionBlocks([...blocks]);\n }\n }\n break;\n }\n\n case 'tool-output-available': {\n const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);\n if (toolCall) {\n toolCall.status = 'available';\n toolCall.result = event.output;\n\n const resultPart = parts.find(\n (p) => p.type === 'tool-call' && p.toolCall?.id === event.toolCallId,\n );\n if (resultPart?.toolCall) {\n resultPart.toolCall = {\n ...resultPart.toolCall,\n status: 'available',\n result: event.output,\n };\n setStreamingParts([...parts]);\n }\n\n if (activeBlock) {\n setExecutionBlocks([...blocks]);\n }\n }\n break;\n }\n\n case 'tool-output-error': {\n const toolCall = toolCalls.find((tc) => tc.id === event.toolCallId);\n if (toolCall) {\n toolCall.status = 'error';\n toolCall.error = event.errorText;\n\n const errorPart = parts.find(\n (p) => p.type === 'tool-call' && p.toolCall?.id === event.toolCallId,\n );\n if (errorPart?.toolCall) {\n errorPart.toolCall = {\n ...errorPart.toolCall,\n status: 'error',\n error: event.errorText,\n };\n setStreamingParts([...parts]);\n }\n\n if (activeBlock) {\n setExecutionBlocks([...blocks]);\n }\n }\n break;\n }\n\n case 'resource-update':\n onResourceUpdate?.(event.name, event.value);\n break;\n\n case 'finish': {\n while (reasoningStack.length > 0) {\n const ctx = reasoningStack.pop();\n const remainingPart = ctx ? parts[ctx.partIndex] : undefined;\n if (ctx && remainingPart && !remainingPart.content?.trim()) {\n parts.splice(ctx.partIndex, 1);\n }\n }\n\n const executionSteps: ExecutionStep[] = blocks\n .filter((b) => b.status === 'completed')\n .map((b) => ({\n id: b.id,\n name: b.name,\n type: b.type,\n display: b.display,\n description: b.description,\n outputToChat: b.outputToChat,\n summary: b.summary,\n fullOutput: b.streamingText || undefined,\n reasoning: b.reasoning || undefined,\n toolCalls: b.toolCalls.length > 0 ? b.toolCalls : undefined,\n thread: b.thread,\n }));\n\n if (visibleFullText || executionSteps.length > 0 || executionReasoningText) {\n const assistantMessage: Message = {\n id: generateId(),\n role: 'assistant',\n content: visibleFullText,\n toolCalls: toolCalls.filter((tc) => tc.status === 'available'),\n executionSteps: executionSteps.length > 0 ? executionSteps : undefined,\n parts: parts.length > 0 ? parts : undefined,\n reasoning: executionReasoningText || undefined,\n createdAt: new Date(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n onMessage?.(assistantMessage);\n }\n setStatus('idle');\n setStreamingText('');\n setStreamingParts([]);\n setExecutionBlocks([]);\n setReasoningText('');\n onDone?.();\n break;\n }\n\n case 'error':\n throw new Error(event.errorText);\n\n case 'tool-request':\n // Handled by server-sdk, should not reach client\n break;\n }\n }\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error('Unknown error');\n setError(errorObj);\n setStatus('error');\n\n if (activeBlock) {\n activeBlock.status = 'error';\n setExecutionBlocks([...blocks]);\n }\n\n onError?.(errorObj);\n } finally {\n abortControllerRef.current = null;\n }\n },\n [onTrigger, onMessage, onError, onDone, onResourceUpdate, onBlockStart, onBlockEnd],\n );\n\n const addUserMessage = useCallback(\n (content: string) => {\n const userMessage: Message = {\n id: generateId(),\n role: 'user',\n content,\n createdAt: new Date(),\n };\n setMessages((prev) => [...prev, userMessage]);\n onMessage?.(userMessage);\n },\n [onMessage],\n );\n\n return {\n messages,\n status,\n isLoading: status === 'loading' || status === 'streaming',\n error,\n addUserMessage,\n triggerAction,\n streamingText,\n streamingParts,\n executionBlocks,\n reasoningText,\n };\n}\n","import { safeParseStreamEvent, type StreamEvent } from '@octavus/core';\n\n/**\n * Parse SSE stream events\n */\nexport async function* parseSSEStream(\n response: Response,\n): AsyncGenerator<StreamEvent, void, unknown> {\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n let reading = true;\n while (reading) {\n const { done, value } = await reader.read();\n\n if (done) {\n reading = false;\n continue;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data: ') && line !== 'data: [DONE]') {\n try {\n const parsed = safeParseStreamEvent(JSON.parse(line.slice(6)));\n if (parsed.success) {\n yield parsed.data;\n }\n // Skip malformed events silently\n } catch {\n // Skip malformed JSON - no logging in production\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/chat.ts","../src/stream/reader.ts","../src/utils/message-helpers.ts"],"sourcesContent":["import {\n generateId,\n type UIMessage,\n type UIMessagePart,\n type UITextPart,\n type UIReasoningPart,\n type UIToolCallPart,\n type UIOperationPart,\n type DisplayMode,\n} from '@octavus/core';\nimport { parseSSEStream } from './stream/reader';\n\n/** Block types that are internal operations (not LLM-driven) */\nconst OPERATION_BLOCK_TYPES = new Set(['set-resource', 'serialize-thread']);\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type ChatStatus = 'idle' | 'streaming' | 'error';\n\nexport type TriggerFunction = (\n triggerName: string,\n input?: Record<string, unknown>,\n) => Promise<Response>;\n\n/**\n * Input for creating a user message.\n * Currently supports text content. Will be extended to support attachments, images, etc.\n */\nexport interface UserMessageInput {\n /** Text content of the message */\n content: string;\n}\n\nexport interface OctavusChatOptions {\n /** Function to make the API call to trigger the agent */\n onTrigger: TriggerFunction;\n /** Initial messages (for session refresh) */\n initialMessages?: UIMessage[];\n /** Callback when an error occurs */\n onError?: (error: Error) => void;\n /** Callback when streaming finishes */\n onFinish?: () => void;\n /** Callback when a resource is updated */\n onResourceUpdate?: (name: string, value: unknown) => void;\n}\n\n// =============================================================================\n// Internal Types\n// =============================================================================\n\ninterface BlockState {\n blockId: string;\n blockName: string;\n blockType: string;\n display: DisplayMode;\n description?: string;\n outputToChat: boolean;\n thread?: string;\n reasoning: string;\n text: string;\n toolCalls: Map<string, UIToolCallPart>;\n}\n\ninterface StreamingState {\n messageId: string;\n parts: UIMessagePart[];\n activeBlock: BlockState | null;\n blocks: Map<string, BlockState>;\n // Track current text/reasoning part indices for delta updates\n currentTextPartIndex: number | null;\n currentReasoningPartIndex: number | null;\n}\n\ntype Listener = () => void;\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nasync function parseErrorResponse(response: Response): Promise<string> {\n try {\n const data = (await response.json()) as { error?: string; message?: string };\n return data.error ?? data.message ?? `Request failed: ${response.status}`;\n } catch {\n return `Request failed: ${response.status}`;\n }\n}\n\nfunction createUserMessage(input: UserMessageInput): UIMessage {\n return {\n id: generateId(),\n role: 'user',\n parts: [{ type: 'text', text: input.content, status: 'done' }],\n status: 'done',\n createdAt: new Date(),\n };\n}\n\nfunction createEmptyStreamingState(): StreamingState {\n return {\n messageId: generateId(),\n parts: [],\n activeBlock: null,\n blocks: new Map(),\n currentTextPartIndex: null,\n currentReasoningPartIndex: null,\n };\n}\n\nfunction buildMessageFromState(state: StreamingState, status: 'streaming' | 'done'): UIMessage {\n return {\n id: state.messageId,\n role: 'assistant',\n parts: [...state.parts],\n status,\n createdAt: new Date(),\n };\n}\n\n// =============================================================================\n// OctavusChat Class\n// =============================================================================\n\n/**\n * Framework-agnostic chat client for Octavus agents.\n * Manages chat state and streaming, allowing reactive frameworks to subscribe to updates.\n *\n * @example\n * ```typescript\n * const chat = new OctavusChat({\n * onTrigger: (triggerName, input) =>\n * fetch('/api/trigger', {\n * method: 'POST',\n * body: JSON.stringify({ sessionId, triggerName, input }),\n * }),\n * });\n *\n * // Subscribe to updates\n * const unsubscribe = chat.subscribe(() => {\n * console.log('Messages:', chat.messages);\n * console.log('Status:', chat.status);\n * });\n *\n * // Send a message\n * await chat.send('user-message', { USER_MESSAGE: 'Hello' }, { userMessage: { content: 'Hello' } });\n *\n * // Cleanup\n * unsubscribe();\n * ```\n */\nexport class OctavusChat {\n // Private state\n private _messages: UIMessage[];\n private _status: ChatStatus = 'idle';\n private _error: Error | null = null;\n private options: OctavusChatOptions;\n private abortController: AbortController | null = null;\n private streamingState: StreamingState | null = null;\n\n // Listener sets for reactive frameworks\n private listeners = new Set<Listener>();\n\n constructor(options: OctavusChatOptions) {\n this.options = options;\n this._messages = options.initialMessages ?? [];\n }\n\n // =========================================================================\n // Public Getters\n // =========================================================================\n\n get messages(): UIMessage[] {\n return this._messages;\n }\n\n get status(): ChatStatus {\n return this._status;\n }\n\n get error(): Error | null {\n return this._error;\n }\n\n // =========================================================================\n // Subscription Methods (for reactive frameworks)\n // =========================================================================\n\n /**\n * Subscribe to state changes. The callback is called whenever messages, status, or error changes.\n * @returns Unsubscribe function\n */\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((l) => l());\n }\n\n // =========================================================================\n // Private Setters (notify listeners)\n // =========================================================================\n\n private setMessages(messages: UIMessage[]): void {\n this._messages = messages;\n this.notifyListeners();\n }\n\n private setStatus(status: ChatStatus): void {\n this._status = status;\n this.notifyListeners();\n }\n\n private setError(error: Error | null): void {\n this._error = error;\n this.notifyListeners();\n }\n\n // =========================================================================\n // Public Methods\n // =========================================================================\n\n /**\n * Trigger the agent and optionally add a user message to the chat.\n *\n * @param triggerName - The trigger name defined in the agent's protocol.yaml\n * @param input - Input parameters for the trigger (variable substitutions)\n * @param options.userMessage - If provided, adds a user message to the chat before triggering\n */\n async send(\n triggerName: string,\n input?: Record<string, unknown>,\n sendOptions?: { userMessage?: UserMessageInput },\n ): Promise<void> {\n // Abort any previous request\n this.abortController?.abort();\n\n const abortController = new AbortController();\n this.abortController = abortController;\n\n // Add user message if provided\n if (sendOptions?.userMessage !== undefined) {\n const userMsg = createUserMessage(sendOptions.userMessage);\n this.setMessages([...this._messages, userMsg]);\n }\n\n // Reset state\n this.setStatus('streaming');\n this.setError(null);\n this.streamingState = createEmptyStreamingState();\n\n try {\n const response = await this.options.onTrigger(triggerName, input);\n\n if (!response.ok) {\n const errorMessage = await parseErrorResponse(response);\n throw new Error(errorMessage);\n }\n\n for await (const event of parseSSEStream(response)) {\n if (abortController.signal.aborted) {\n break;\n }\n\n const state: StreamingState | null = this.streamingState;\n if (!state) break;\n\n switch (event.type) {\n case 'start':\n break;\n\n case 'block-start': {\n const block: BlockState = {\n blockId: event.blockId,\n blockName: event.blockName,\n blockType: event.blockType,\n display: event.display,\n description: event.description,\n outputToChat: event.outputToChat ?? true,\n thread: event.thread,\n reasoning: '',\n text: '',\n toolCalls: new Map(),\n };\n state.blocks.set(event.blockId, block);\n state.activeBlock = block;\n\n // Create operation part for internal operation blocks (if not hidden)\n const isOperation = OPERATION_BLOCK_TYPES.has(event.blockType);\n const isHidden = event.display === 'hidden';\n if (isOperation && !isHidden) {\n const thread = event.thread;\n const operationPart: UIOperationPart = {\n type: 'operation',\n operationId: event.blockId,\n name: event.description || event.blockName,\n operationType: event.blockType,\n status: 'running',\n thread: thread && thread !== 'main' ? thread : undefined,\n };\n state.parts.push(operationPart);\n }\n\n state.currentTextPartIndex = null;\n state.currentReasoningPartIndex = null;\n\n this.updateStreamingMessage();\n break;\n }\n\n case 'block-end': {\n // Mark operation part as done if it exists\n const operationPartIndex = state.parts.findIndex(\n (p: UIMessagePart) => p.type === 'operation' && p.operationId === event.blockId,\n );\n if (operationPartIndex >= 0) {\n const part = state.parts[operationPartIndex] as UIOperationPart;\n state.parts[operationPartIndex] = { ...part, status: 'done' };\n }\n\n if (state.activeBlock?.blockId === event.blockId) {\n state.activeBlock = null;\n }\n this.updateStreamingMessage();\n break;\n }\n\n case 'reasoning-start': {\n const thread = state.activeBlock?.thread;\n const reasoningPart: UIReasoningPart = {\n type: 'reasoning',\n text: '',\n status: 'streaming',\n thread: thread && thread !== 'main' ? thread : undefined,\n };\n state.parts.push(reasoningPart);\n state.currentReasoningPartIndex = state.parts.length - 1;\n this.updateStreamingMessage();\n break;\n }\n\n case 'reasoning-delta': {\n if (state.currentReasoningPartIndex !== null) {\n const part = state.parts[state.currentReasoningPartIndex] as UIReasoningPart;\n part.text += event.delta;\n state.parts[state.currentReasoningPartIndex] = { ...part };\n }\n\n if (state.activeBlock) {\n state.activeBlock.reasoning += event.delta;\n }\n\n this.updateStreamingMessage();\n break;\n }\n\n case 'reasoning-end': {\n if (state.currentReasoningPartIndex !== null) {\n const part = state.parts[state.currentReasoningPartIndex] as UIReasoningPart;\n part.status = 'done';\n state.parts[state.currentReasoningPartIndex] = { ...part };\n state.currentReasoningPartIndex = null;\n }\n this.updateStreamingMessage();\n break;\n }\n\n case 'text-start': {\n const thread = state.activeBlock?.thread;\n const isOtherThread = Boolean(thread && thread !== 'main');\n const shouldAddPart = state.activeBlock?.outputToChat !== false || isOtherThread;\n\n if (shouldAddPart) {\n const textPart: UITextPart = {\n type: 'text',\n text: '',\n status: 'streaming',\n thread: isOtherThread ? thread : undefined,\n };\n state.parts.push(textPart);\n state.currentTextPartIndex = state.parts.length - 1;\n }\n this.updateStreamingMessage();\n break;\n }\n\n case 'text-delta': {\n if (state.currentTextPartIndex !== null) {\n const part = state.parts[state.currentTextPartIndex] as UITextPart;\n part.text += event.delta;\n state.parts[state.currentTextPartIndex] = { ...part };\n }\n\n if (state.activeBlock) {\n state.activeBlock.text += event.delta;\n }\n\n this.updateStreamingMessage();\n break;\n }\n\n case 'text-end': {\n if (state.currentTextPartIndex !== null) {\n const part = state.parts[state.currentTextPartIndex] as UITextPart;\n part.status = 'done';\n state.parts[state.currentTextPartIndex] = { ...part };\n state.currentTextPartIndex = null;\n }\n this.updateStreamingMessage();\n break;\n }\n\n case 'tool-input-start': {\n const thread = state.activeBlock?.thread;\n const toolPart: UIToolCallPart = {\n type: 'tool-call',\n toolCallId: event.toolCallId,\n toolName: event.toolName,\n displayName: event.title,\n args: {},\n status: 'pending',\n thread: thread && thread !== 'main' ? thread : undefined,\n };\n state.parts.push(toolPart);\n\n if (state.activeBlock) {\n state.activeBlock.toolCalls.set(event.toolCallId, toolPart);\n }\n\n this.updateStreamingMessage();\n break;\n }\n\n case 'tool-input-delta': {\n const toolPartIndex = state.parts.findIndex(\n (p: UIMessagePart) => p.type === 'tool-call' && p.toolCallId === event.toolCallId,\n );\n if (toolPartIndex >= 0) {\n try {\n const part = state.parts[toolPartIndex] as UIToolCallPart;\n part.args = JSON.parse(event.inputTextDelta) as Record<string, unknown>;\n state.parts[toolPartIndex] = { ...part };\n this.updateStreamingMessage();\n } catch {\n // Partial JSON, ignore\n }\n }\n break;\n }\n\n case 'tool-input-end':\n break;\n\n case 'tool-input-available': {\n const toolPartIndex = state.parts.findIndex(\n (p: UIMessagePart) => p.type === 'tool-call' && p.toolCallId === event.toolCallId,\n );\n if (toolPartIndex >= 0) {\n const part = state.parts[toolPartIndex] as UIToolCallPart;\n part.args = event.input as Record<string, unknown>;\n part.status = 'running';\n state.parts[toolPartIndex] = { ...part };\n this.updateStreamingMessage();\n }\n break;\n }\n\n case 'tool-output-available': {\n const toolPartIndex = state.parts.findIndex(\n (p: UIMessagePart) => p.type === 'tool-call' && p.toolCallId === event.toolCallId,\n );\n if (toolPartIndex >= 0) {\n const part = state.parts[toolPartIndex] as UIToolCallPart;\n part.result = event.output;\n part.status = 'done';\n state.parts[toolPartIndex] = { ...part };\n this.updateStreamingMessage();\n }\n break;\n }\n\n case 'tool-output-error': {\n const toolPartIndex = state.parts.findIndex(\n (p: UIMessagePart) => p.type === 'tool-call' && p.toolCallId === event.toolCallId,\n );\n if (toolPartIndex >= 0) {\n const part = state.parts[toolPartIndex] as UIToolCallPart;\n part.error = event.errorText;\n part.status = 'error';\n state.parts[toolPartIndex] = { ...part };\n this.updateStreamingMessage();\n }\n break;\n }\n\n case 'resource-update':\n this.options.onResourceUpdate?.(event.name, event.value);\n break;\n\n case 'finish': {\n const finalMessage = buildMessageFromState(state, 'done');\n\n finalMessage.parts = finalMessage.parts.map((part) => {\n if (part.type === 'text' || part.type === 'reasoning') {\n return { ...part, status: 'done' as const };\n }\n return part;\n });\n\n if (finalMessage.parts.length > 0) {\n const messages = [...this._messages];\n const lastMsg = messages[messages.length - 1];\n if (lastMsg && lastMsg.id === state.messageId) {\n messages[messages.length - 1] = finalMessage;\n } else {\n messages.push(finalMessage);\n }\n this.setMessages(messages);\n }\n\n this.setStatus('idle');\n this.streamingState = null;\n this.options.onFinish?.();\n break;\n }\n\n case 'error':\n throw new Error(event.errorText);\n\n case 'tool-request':\n // Handled by server-sdk\n break;\n }\n }\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error('Unknown error');\n this.setError(errorObj);\n this.setStatus('error');\n this.streamingState = null;\n this.options.onError?.(errorObj);\n } finally {\n this.abortController = null;\n }\n }\n\n /** Stop the current streaming and finalize any partial message */\n stop(): void {\n this.abortController?.abort();\n this.abortController = null;\n\n const state = this.streamingState;\n if (state && state.parts.length > 0) {\n const finalMessage = buildMessageFromState(state, 'done');\n const messages = [...this._messages];\n const lastMsg = messages[messages.length - 1];\n if (lastMsg && lastMsg.id === state.messageId) {\n messages[messages.length - 1] = finalMessage;\n } else {\n messages.push(finalMessage);\n }\n this.setMessages(messages);\n }\n\n this.streamingState = null;\n this.setStatus('idle');\n }\n\n // =========================================================================\n // Private Helpers\n // =========================================================================\n\n private updateStreamingMessage(): void {\n const state = this.streamingState;\n if (!state) return;\n\n const msg = buildMessageFromState(state, 'streaming');\n const messages = [...this._messages];\n\n const lastMsg = messages[messages.length - 1];\n if (lastMsg && lastMsg.id === state.messageId) {\n messages[messages.length - 1] = msg;\n } else {\n messages.push(msg);\n }\n\n this.setMessages(messages);\n }\n}\n","import { safeParseStreamEvent, type StreamEvent } from '@octavus/core';\n\n/**\n * Parse SSE stream events\n */\nexport async function* parseSSEStream(\n response: Response,\n): AsyncGenerator<StreamEvent, void, unknown> {\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error('Response body is not readable');\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n let reading = true;\n while (reading) {\n const { done, value } = await reader.read();\n\n if (done) {\n reading = false;\n continue;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (line.startsWith('data: ') && line !== 'data: [DONE]') {\n try {\n const parsed = safeParseStreamEvent(JSON.parse(line.slice(6)));\n if (parsed.success) {\n yield parsed.data;\n }\n // Skip malformed events silently\n } catch {\n // Skip malformed JSON - no logging in production\n }\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n","import type { UIMessagePart } from '@octavus/core';\n\n/** Non-main thread content (e.g., \"summary\") is typically displayed differently. */\nexport function isOtherThread(part: UIMessagePart): boolean {\n return Boolean(part.thread && part.thread !== 'main');\n}\n"],"mappings":"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","names":["isOtherThread"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@octavus/client-sdk",
3
- "version": "0.0.4",
4
- "description": "Client SDK for streaming Octavus agent responses",
3
+ "version": "0.0.6",
4
+ "description": "Framework-agnostic client SDK for Octavus agents",
5
5
  "license": "MIT",
6
6
  "author": "Octavus AI <hello@octavus.ai>",
7
7
  "keywords": [
@@ -9,7 +9,6 @@
9
9
  "ai",
10
10
  "agents",
11
11
  "sdk",
12
- "react",
13
12
  "streaming"
14
13
  ],
15
14
  "type": "module",
@@ -29,14 +28,9 @@
29
28
  "access": "public"
30
29
  },
31
30
  "dependencies": {
32
- "@octavus/core": "^0.0.4"
33
- },
34
- "peerDependencies": {
35
- "react": ">=18.0.0"
31
+ "@octavus/core": "^0.0.6"
36
32
  },
37
33
  "devDependencies": {
38
- "@types/react": "^19.0.0",
39
- "react": "^19.0.0",
40
34
  "tsup": "^8.3.5",
41
35
  "typescript": "^5.6.3"
42
36
  },