@inferencesh/sdk 0.5.11 → 0.5.13

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.
@@ -5,7 +5,7 @@
5
5
  * These are created once per provider instance with access to dispatch.
6
6
  */
7
7
  import { ToolInvocationStatusAwaitingInput, ToolTypeClient, ChatStatusBusy, } from '../types';
8
- import { StreamManager } from '../http/stream';
8
+ import { StreamableManager } from '../http/streamable';
9
9
  import { PollManager } from '../http/poll';
10
10
  import { isAdHocConfig, extractClientToolHandlers } from './types';
11
11
  import * as api from './api';
@@ -77,7 +77,7 @@ export function createActions(ctx) {
77
77
  existingManager.stop();
78
78
  }
79
79
  setStreamManager(undefined);
80
- dispatch({ type: 'SET_STATUS', payload: 'connecting' });
80
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'connecting' });
81
81
  callbacks.onStatusChange?.('connecting');
82
82
  try {
83
83
  // Fetch initial chat
@@ -88,8 +88,7 @@ export function createActions(ctx) {
88
88
  }
89
89
  catch (error) {
90
90
  console.error('[AgentSDK] Failed to fetch chat:', error);
91
- dispatch({ type: 'SET_STATUS', payload: 'idle' });
92
- dispatch({ type: 'SET_IS_GENERATING', payload: false });
91
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
93
92
  callbacks.onStatusChange?.('idle');
94
93
  return;
95
94
  }
@@ -99,27 +98,25 @@ export function createActions(ctx) {
99
98
  return;
100
99
  }
101
100
  // Single unified stream with TypedEvents (both Chat and ChatMessage events)
102
- const manager = new StreamManager({
103
- createEventSource: () => api.createUnifiedStream(client, id),
104
- autoReconnect: true,
105
- maxReconnects: 3,
106
- reconnectDelayMs: 3000,
101
+ const { url, headers } = api.getChatStreamConfig(client, id);
102
+ const manager = new StreamableManager({
103
+ url,
104
+ headers,
107
105
  onError: (error) => {
108
106
  console.warn('[AgentSDK] Stream error:', error);
109
107
  callbacks.onError?.(error);
110
108
  },
111
109
  onStart: () => {
112
- dispatch({ type: 'SET_STATUS', payload: 'streaming' });
110
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'streaming' });
113
111
  callbacks.onStatusChange?.('streaming');
114
112
  },
115
- onStop: () => {
113
+ onEnd: () => {
116
114
  // Only reset if this is an unexpected stop (stream died, max reconnects exhausted).
117
115
  // If stopStream() was called intentionally, it clears the manager ref first,
118
116
  // so getStreamManager() will be undefined and we skip the duplicate dispatch.
119
117
  if (getStreamManager()) {
120
118
  setStreamManager(undefined);
121
- dispatch({ type: 'SET_STATUS', payload: 'idle' });
122
- dispatch({ type: 'SET_IS_GENERATING', payload: false });
119
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
123
120
  callbacks.onStatusChange?.('idle');
124
121
  }
125
122
  },
@@ -133,7 +130,7 @@ export function createActions(ctx) {
133
130
  updateMessage(message);
134
131
  });
135
132
  setStreamManager(manager);
136
- manager.connect();
133
+ manager.start();
137
134
  };
138
135
  /** Poll-based alternative to streaming for restricted environments */
139
136
  const pollChat = (id) => {
@@ -163,14 +160,13 @@ export function createActions(ctx) {
163
160
  }
164
161
  },
165
162
  onStart: () => {
166
- dispatch({ type: 'SET_STATUS', payload: 'streaming' });
163
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'streaming' });
167
164
  callbacks.onStatusChange?.('streaming');
168
165
  },
169
166
  onStop: () => {
170
167
  if (getStreamManager()) {
171
168
  setStreamManager(undefined);
172
- dispatch({ type: 'SET_STATUS', payload: 'idle' });
173
- dispatch({ type: 'SET_IS_GENERATING', payload: false });
169
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
174
170
  callbacks.onStatusChange?.('idle');
175
171
  }
176
172
  },
@@ -189,8 +185,7 @@ export function createActions(ctx) {
189
185
  if (manager) {
190
186
  manager.stop();
191
187
  }
192
- dispatch({ type: 'SET_STATUS', payload: 'idle' });
193
- dispatch({ type: 'SET_IS_GENERATING', payload: false });
188
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
194
189
  callbacks.onStatusChange?.('idle');
195
190
  };
196
191
  // =========================================================================
@@ -208,7 +203,7 @@ export function createActions(ctx) {
208
203
  if (!trimmedText)
209
204
  return;
210
205
  // Update status
211
- dispatch({ type: 'SET_STATUS', payload: 'streaming' });
206
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'streaming' });
212
207
  dispatch({ type: 'SET_ERROR', payload: undefined });
213
208
  try {
214
209
  const result = await api.sendMessage(client, agentConfig, chatId, trimmedText, files);
@@ -227,15 +222,14 @@ export function createActions(ctx) {
227
222
  }
228
223
  else {
229
224
  // API returned no result — reset status so we don't get stuck
230
- dispatch({ type: 'SET_STATUS', payload: 'idle' });
231
- dispatch({ type: 'SET_IS_GENERATING', payload: false });
225
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
232
226
  callbacks.onStatusChange?.('idle');
233
227
  }
234
228
  }
235
229
  catch (error) {
236
230
  console.error('[AgentSDK] Failed to send message:', error);
237
231
  const err = error instanceof Error ? error : new Error('Failed to send message');
238
- dispatch({ type: 'SET_STATUS', payload: 'error' });
232
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'error' });
239
233
  dispatch({ type: 'SET_ERROR', payload: err.message });
240
234
  callbacks.onError?.(err);
241
235
  }
@@ -257,7 +251,7 @@ export function createActions(ctx) {
257
251
  },
258
252
  clearError: () => {
259
253
  dispatch({ type: 'SET_ERROR', payload: undefined });
260
- dispatch({ type: 'SET_STATUS', payload: 'idle' });
254
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'idle' });
261
255
  },
262
256
  submitToolResult: async (toolInvocationId, result) => {
263
257
  try {
@@ -266,7 +260,7 @@ export function createActions(ctx) {
266
260
  catch (error) {
267
261
  console.error('[AgentSDK] Failed to submit tool result:', error);
268
262
  const err = error instanceof Error ? error : new Error('Failed to submit tool result');
269
- dispatch({ type: 'SET_STATUS', payload: 'error' });
263
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'error' });
270
264
  dispatch({ type: 'SET_ERROR', payload: err.message });
271
265
  callbacks.onError?.(err);
272
266
  throw error;
@@ -279,7 +273,7 @@ export function createActions(ctx) {
279
273
  catch (error) {
280
274
  console.error('[AgentSDK] Failed to approve tool:', error);
281
275
  const err = error instanceof Error ? error : new Error('Failed to approve tool');
282
- dispatch({ type: 'SET_STATUS', payload: 'error' });
276
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'error' });
283
277
  dispatch({ type: 'SET_ERROR', payload: err.message });
284
278
  callbacks.onError?.(err);
285
279
  throw error;
@@ -292,7 +286,7 @@ export function createActions(ctx) {
292
286
  catch (error) {
293
287
  console.error('[AgentSDK] Failed to reject tool:', error);
294
288
  const err = error instanceof Error ? error : new Error('Failed to reject tool');
295
- dispatch({ type: 'SET_STATUS', payload: 'error' });
289
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'error' });
296
290
  dispatch({ type: 'SET_ERROR', payload: err.message });
297
291
  callbacks.onError?.(err);
298
292
  throw error;
@@ -310,7 +304,7 @@ export function createActions(ctx) {
310
304
  catch (error) {
311
305
  console.error('[AgentSDK] Failed to always-allow tool:', error);
312
306
  const err = error instanceof Error ? error : new Error('Failed to always-allow tool');
313
- dispatch({ type: 'SET_STATUS', payload: 'error' });
307
+ dispatch({ type: 'SET_CONNECTION_STATUS', payload: 'error' });
314
308
  dispatch({ type: 'SET_ERROR', payload: err.message });
315
309
  callbacks.onError?.(err);
316
310
  throw error;
@@ -5,15 +5,15 @@
5
5
  * Uses the client passed in from the provider.
6
6
  */
7
7
  import type { ChatDTO, ChatMessageDTO } from '../types';
8
- import type { AdHocAgentConfig, TemplateAgentConfig, AgentOptions, AgentClient, UploadedFile } from './types';
8
+ import type { AdHocAgentConfig, TemplateAgentConfig, AgentOptions, AgentClient, FileRef } from './types';
9
9
  /**
10
10
  * File input that can be either a File to upload or an already-uploaded file
11
11
  */
12
- export type FileInput = globalThis.File | UploadedFile;
12
+ export type FileInput = globalThis.File | FileRef;
13
13
  /**
14
14
  * Send a message using ad-hoc agent config
15
15
  */
16
- export declare function sendAdHocMessage(client: AgentClient, config: AdHocAgentConfig, chatId: string | null, text: string, imageUris?: string[], fileUris?: string[]): Promise<{
16
+ export declare function sendAdHocMessage(client: AgentClient, config: AdHocAgentConfig, chatId: string | null, text: string, attachments?: FileRef[]): Promise<{
17
17
  chatId: string;
18
18
  userMessage: ChatMessageDTO;
19
19
  assistantMessage: ChatMessageDTO;
@@ -21,7 +21,7 @@ export declare function sendAdHocMessage(client: AgentClient, config: AdHocAgent
21
21
  /**
22
22
  * Send a message using template agent config
23
23
  */
24
- export declare function sendTemplateMessage(client: AgentClient, config: TemplateAgentConfig, chatId: string | null, text: string, imageUris?: string[], fileUris?: string[]): Promise<{
24
+ export declare function sendTemplateMessage(client: AgentClient, config: TemplateAgentConfig, chatId: string | null, text: string, attachments?: FileRef[]): Promise<{
25
25
  chatId: string;
26
26
  userMessage: ChatMessageDTO;
27
27
  assistantMessage: ChatMessageDTO;
@@ -67,8 +67,11 @@ export declare function alwaysAllowTool(client: AgentClient, chatId: string, too
67
67
  /**
68
68
  * Upload a file and return the uploaded file reference
69
69
  */
70
- export declare function uploadFile(client: AgentClient, file: globalThis.File): Promise<UploadedFile>;
70
+ export declare function uploadFile(client: AgentClient, file: globalThis.File): Promise<FileRef>;
71
71
  /**
72
- * Create a unified stream for chat events
72
+ * Get streamable config for chat streaming (NDJSON)
73
73
  */
74
- export declare function createUnifiedStream(client: AgentClient, chatId: string): Promise<EventSource | null>;
74
+ export declare function getChatStreamConfig(client: AgentClient, chatId: string): {
75
+ url: string;
76
+ headers: Record<string, string>;
77
+ };
package/dist/agent/api.js CHANGED
@@ -8,13 +8,13 @@ import { isAdHocConfig, extractToolSchemas } from './types';
8
8
  /**
9
9
  * Check if input is an already-uploaded file
10
10
  */
11
- function isUploadedFile(input) {
11
+ function isFileRef(input) {
12
12
  return 'uri' in input && typeof input.uri === 'string';
13
13
  }
14
14
  /**
15
15
  * Send a message using ad-hoc agent config
16
16
  */
17
- export async function sendAdHocMessage(client, config, chatId, text, imageUris, fileUris) {
17
+ export async function sendAdHocMessage(client, config, chatId, text, attachments) {
18
18
  // Extract just the schemas from tools (handlers are stripped out)
19
19
  const toolSchemas = config.tools ? extractToolSchemas(config.tools) : undefined;
20
20
  const request = {
@@ -27,8 +27,7 @@ export async function sendAdHocMessage(client, config, chatId, text, imageUris,
27
27
  agent_name: config.name,
28
28
  input: {
29
29
  text,
30
- images: imageUris,
31
- files: fileUris,
30
+ attachments,
32
31
  context_size: 0,
33
32
  system_prompt: '',
34
33
  context: [],
@@ -51,15 +50,14 @@ export async function sendAdHocMessage(client, config, chatId, text, imageUris,
51
50
  /**
52
51
  * Send a message using template agent config
53
52
  */
54
- export async function sendTemplateMessage(client, config, chatId, text, imageUris, fileUris) {
53
+ export async function sendTemplateMessage(client, config, chatId, text, attachments) {
55
54
  const request = {
56
55
  chat_id: chatId ?? undefined,
57
56
  // Only include agent if it's not empty (for existing chats, backend uses chat's agent)
58
57
  agent: config.agent || undefined,
59
58
  input: {
60
59
  text,
61
- images: imageUris,
62
- files: fileUris,
60
+ attachments,
63
61
  context_size: 0,
64
62
  system_prompt: '',
65
63
  context: [],
@@ -83,43 +81,36 @@ export async function sendTemplateMessage(client, config, chatId, text, imageUri
83
81
  * Send a message (unified interface)
84
82
  */
85
83
  export async function sendMessage(client, config, chatId, text, files) {
86
- // Process files - upload if needed, or use existing URIs
87
- let imageUris;
88
- let fileUris;
84
+ // Process files - upload if needed, collect as FileRef attachments
85
+ let attachments;
89
86
  if (files && files.length > 0) {
90
- const uploadedFiles = [];
87
+ attachments = [];
91
88
  for (const file of files) {
92
- // Check if already uploaded
93
- if (isUploadedFile(file)) {
94
- uploadedFiles.push(file);
89
+ // Check if already uploaded (FileRef)
90
+ if (isFileRef(file)) {
91
+ attachments.push(file);
95
92
  continue;
96
93
  }
97
94
  // Upload the file
98
95
  try {
99
96
  const result = await client.files.upload(file);
100
97
  if (result) {
101
- uploadedFiles.push(result);
98
+ attachments.push(result);
102
99
  }
103
100
  }
104
101
  catch (error) {
105
102
  console.error('[AgentSDK] Failed to upload file:', error);
106
103
  }
107
104
  }
108
- // Separate images from other files
109
- const images = uploadedFiles.filter(f => f.content_type?.startsWith('image/'));
110
- const otherFiles = uploadedFiles.filter(f => !f.content_type?.startsWith('image/'));
111
- if (images.length > 0) {
112
- imageUris = images.map(f => f.uri);
113
- }
114
- if (otherFiles.length > 0) {
115
- fileUris = otherFiles.map(f => f.uri);
105
+ if (attachments.length === 0) {
106
+ attachments = undefined;
116
107
  }
117
108
  }
118
109
  if (isAdHocConfig(config)) {
119
- return sendAdHocMessage(client, config, chatId, text, imageUris, fileUris);
110
+ return sendAdHocMessage(client, config, chatId, text, attachments);
120
111
  }
121
112
  else {
122
- return sendTemplateMessage(client, config, chatId, text, imageUris, fileUris);
113
+ return sendTemplateMessage(client, config, chatId, text, attachments);
123
114
  }
124
115
  }
125
116
  /**
@@ -204,8 +195,8 @@ export async function uploadFile(client, file) {
204
195
  return result;
205
196
  }
206
197
  /**
207
- * Create a unified stream for chat events
198
+ * Get streamable config for chat streaming (NDJSON)
208
199
  */
209
- export function createUnifiedStream(client, chatId) {
210
- return client.http.createEventSource(`/chats/${chatId}/stream`);
200
+ export function getChatStreamConfig(client, chatId) {
201
+ return client.http.getStreamableConfig(`/chats/${chatId}/stream`);
211
202
  }
@@ -8,13 +8,14 @@ import type { ChatMessageDTO } from '../types';
8
8
  /**
9
9
  * Hook to access chat state
10
10
  *
11
- * @returns Chat state (messages, status, isGenerating, etc.)
11
+ * @returns Chat state (chat, messages, connectionStatus, error)
12
12
  * @throws Error if used outside AgentChatProvider
13
13
  *
14
14
  * @example
15
15
  * ```tsx
16
16
  * function MyComponent() {
17
- * const { messages, isGenerating, status } = useAgentChat();
17
+ * const { chat, messages } = useAgentChat();
18
+ * const isBusy = chat?.status === 'busy';
18
19
  * return <div>{messages.length} messages</div>;
19
20
  * }
20
21
  * ```
@@ -30,11 +31,12 @@ export declare function useAgentChat(): AgentChatState;
30
31
  * ```tsx
31
32
  * function SendButton() {
32
33
  * const { sendMessage, stopGeneration } = useAgentActions();
33
- * const { isGenerating } = useAgentChat();
34
+ * const { chat } = useAgentChat();
35
+ * const isBusy = chat?.status === 'busy';
34
36
  *
35
37
  * return (
36
- * <button onClick={() => isGenerating ? stopGeneration() : sendMessage('Hello!')}>
37
- * {isGenerating ? 'Stop' : 'Send'}
38
+ * <button onClick={() => isBusy ? stopGeneration() : sendMessage('Hello!')}>
39
+ * {isBusy ? 'Stop' : 'Send'}
38
40
  * </button>
39
41
  * );
40
42
  * }
@@ -67,11 +69,12 @@ export declare function useMessage(messageId: string): ChatMessageDTO | undefine
67
69
  * function ChatInput() {
68
70
  * const { state, actions } = useAgentChatContext();
69
71
  * const [input, setInput] = useState('');
72
+ * const isBusy = state.chat?.status === 'busy';
70
73
  *
71
74
  * return (
72
75
  * <form onSubmit={() => actions.sendMessage(input)}>
73
76
  * <input value={input} onChange={e => setInput(e.target.value)} />
74
- * <button disabled={state.isGenerating}>Send</button>
77
+ * <button disabled={isBusy}>Send</button>
75
78
  * </form>
76
79
  * );
77
80
  * }
@@ -8,13 +8,14 @@ import { AgentChatContext } from './context';
8
8
  /**
9
9
  * Hook to access chat state
10
10
  *
11
- * @returns Chat state (messages, status, isGenerating, etc.)
11
+ * @returns Chat state (chat, messages, connectionStatus, error)
12
12
  * @throws Error if used outside AgentChatProvider
13
13
  *
14
14
  * @example
15
15
  * ```tsx
16
16
  * function MyComponent() {
17
- * const { messages, isGenerating, status } = useAgentChat();
17
+ * const { chat, messages } = useAgentChat();
18
+ * const isBusy = chat?.status === 'busy';
18
19
  * return <div>{messages.length} messages</div>;
19
20
  * }
20
21
  * ```
@@ -37,11 +38,12 @@ export function useAgentChat() {
37
38
  * ```tsx
38
39
  * function SendButton() {
39
40
  * const { sendMessage, stopGeneration } = useAgentActions();
40
- * const { isGenerating } = useAgentChat();
41
+ * const { chat } = useAgentChat();
42
+ * const isBusy = chat?.status === 'busy';
41
43
  *
42
44
  * return (
43
- * <button onClick={() => isGenerating ? stopGeneration() : sendMessage('Hello!')}>
44
- * {isGenerating ? 'Stop' : 'Send'}
45
+ * <button onClick={() => isBusy ? stopGeneration() : sendMessage('Hello!')}>
46
+ * {isBusy ? 'Stop' : 'Send'}
45
47
  * </button>
46
48
  * );
47
49
  * }
@@ -84,11 +86,12 @@ export function useMessage(messageId) {
84
86
  * function ChatInput() {
85
87
  * const { state, actions } = useAgentChatContext();
86
88
  * const [input, setInput] = useState('');
89
+ * const isBusy = state.chat?.status === 'busy';
87
90
  *
88
91
  * return (
89
92
  * <form onSubmit={() => actions.sendMessage(input)}>
90
93
  * <input value={input} onChange={e => setInput(e.target.value)} />
91
- * <button disabled={state.isGenerating}>Send</button>
94
+ * <button disabled={isBusy}>Send</button>
92
95
  * </form>
93
96
  * );
94
97
  * }
@@ -29,14 +29,15 @@
29
29
  * }
30
30
  *
31
31
  * function ChatUI() {
32
- * const { messages, isGenerating } = useAgentChat();
32
+ * const { chat, messages } = useAgentChat();
33
33
  * const { sendMessage, stopGeneration } = useAgentActions();
34
+ * const isBusy = chat?.status === 'busy';
34
35
  *
35
36
  * return (
36
37
  * <div>
37
38
  * {messages.map(m => <Message key={m.id} message={m} />)}
38
39
  * <input onSubmit={(text) => sendMessage(text)} />
39
- * {isGenerating && <button onClick={stopGeneration}>Stop</button>}
40
+ * {isBusy && <button onClick={stopGeneration}>Stop</button>}
40
41
  * </div>
41
42
  * );
42
43
  * }
@@ -45,5 +46,5 @@
45
46
  export { AgentChatProvider } from './provider';
46
47
  export { useAgentChat, useAgentActions, useAgentClient, useAgentChatContext, useMessage, } from './hooks';
47
48
  export { AgentChatContext, type AgentChatContextValue } from './context';
48
- export type { AgentClient, UploadedFile, AdHocAgentConfig, TemplateAgentConfig, AgentOptions, AgentChatState, AgentChatActions, ChatStatus, AgentChatProviderProps, ClientTool, ClientToolHandlerFn, } from './types';
49
+ export type { AgentClient, FileRef, AdHocAgentConfig, TemplateAgentConfig, AgentOptions, AgentChatState, AgentChatActions, ChatStatus, AgentChatProviderProps, ClientTool, ClientToolHandlerFn, } from './types';
49
50
  export { isAdHocConfig, isTemplateConfig, isClientTool, extractToolSchemas, extractClientToolHandlers, } from './types';
@@ -29,14 +29,15 @@
29
29
  * }
30
30
  *
31
31
  * function ChatUI() {
32
- * const { messages, isGenerating } = useAgentChat();
32
+ * const { chat, messages } = useAgentChat();
33
33
  * const { sendMessage, stopGeneration } = useAgentActions();
34
+ * const isBusy = chat?.status === 'busy';
34
35
  *
35
36
  * return (
36
37
  * <div>
37
38
  * {messages.map(m => <Message key={m.id} message={m} />)}
38
39
  * <input onSubmit={(text) => sendMessage(text)} />
39
- * {isGenerating && <button onClick={stopGeneration}>Stop</button>}
40
+ * {isBusy && <button onClick={stopGeneration}>Stop</button>}
40
41
  * </div>
41
42
  * );
42
43
  * }
@@ -9,8 +9,7 @@
9
9
  export const initialState = {
10
10
  chatId: null,
11
11
  messages: [],
12
- status: 'idle',
13
- isGenerating: false,
12
+ connectionStatus: 'idle',
14
13
  error: undefined,
15
14
  chat: null,
16
15
  };
@@ -24,12 +23,10 @@ export function chatReducer(state, action) {
24
23
  case 'SET_CHAT': {
25
24
  const chat = action.payload;
26
25
  if (!chat) {
27
- return { ...state, chat: null, messages: [], isGenerating: false, status: 'idle' };
26
+ return { ...state, chat: null, messages: [], connectionStatus: 'idle' };
28
27
  }
29
28
  const messages = [...(chat.chat_messages || [])].sort((a, b) => a.order - b.order);
30
- const isGenerating = chat.status === 'busy';
31
- const status = isGenerating ? 'streaming' : 'idle';
32
- return { ...state, chat, messages, isGenerating, status };
29
+ return { ...state, chat, messages };
33
30
  }
34
31
  case 'SET_MESSAGES':
35
32
  return { ...state, messages: action.payload };
@@ -51,14 +48,8 @@ export function chatReducer(state, action) {
51
48
  ...state,
52
49
  messages: [...state.messages, action.payload].sort((a, b) => a.order - b.order),
53
50
  };
54
- case 'SET_STATUS':
55
- return {
56
- ...state,
57
- status: action.payload,
58
- isGenerating: action.payload === 'streaming' || action.payload === 'connecting',
59
- };
60
- case 'SET_IS_GENERATING':
61
- return { ...state, isGenerating: action.payload };
51
+ case 'SET_CONNECTION_STATUS':
52
+ return { ...state, connectionStatus: action.payload };
62
53
  case 'SET_ERROR':
63
54
  return { ...state, error: action.payload };
64
55
  case 'RESET':
@@ -4,27 +4,21 @@
4
4
  * Public types for the Agent chat module.
5
5
  */
6
6
  import type { Dispatch } from 'react';
7
- import type { ChatDTO, ChatMessageDTO, AgentTool, AgentConfig as GeneratedAgentConfig, CoreAppConfig } from '../types';
7
+ import type { ChatDTO, ChatMessageDTO, AgentTool, AgentConfig as GeneratedAgentConfig, CoreAppConfig, FileRef } from '../types';
8
8
  import type { HttpClient } from '../http/client';
9
- import type { StreamManager } from '../http/stream';
9
+ import type { StreamableManager } from '../http/streamable';
10
10
  import type { PollManager } from '../http/poll';
11
- /**
12
- * Minimal file interface returned by upload (just needs uri and content_type)
13
- */
14
- export interface UploadedFile {
15
- uri: string;
16
- content_type?: string;
17
- }
11
+ export type { FileRef } from '../types';
18
12
  /**
19
13
  * Minimal client interface required by the agent module.
20
14
  * This allows extended clients (like the app client) to be used.
21
15
  */
22
16
  export interface AgentClient {
23
17
  /** HTTP client for API requests */
24
- http: Pick<HttpClient, 'request' | 'createEventSource' | 'getStreamDefault' | 'getPollIntervalMs'>;
18
+ http: Pick<HttpClient, 'request' | 'getStreamableConfig' | 'getStreamDefault' | 'getPollIntervalMs'>;
25
19
  /** Files API for uploads */
26
20
  files: {
27
- upload: (data: string | Blob | globalThis.File) => Promise<UploadedFile>;
21
+ upload: (data: string | Blob | globalThis.File) => Promise<FileRef>;
28
22
  };
29
23
  }
30
24
  /**
@@ -93,11 +87,9 @@ export interface AgentChatState {
93
87
  chatId: string | null;
94
88
  /** Chat messages */
95
89
  messages: ChatMessageDTO[];
96
- /** Chat status */
97
- status: ChatStatus;
98
- /** Whether the agent is currently generating a response */
99
- isGenerating: boolean;
100
- /** Error message if status is 'error' */
90
+ /** Connection status (stream/poll state, not chat.status) */
91
+ connectionStatus: ChatStatus;
92
+ /** Error message if connectionStatus is 'error' */
101
93
  error?: string;
102
94
  /** The full chat object (if loaded) */
103
95
  chat: ChatDTO | null;
@@ -107,9 +99,9 @@ export interface AgentChatState {
107
99
  */
108
100
  export interface AgentChatActions {
109
101
  /** Send a message to the agent */
110
- sendMessage: (text: string, files?: UploadedFile[]) => Promise<void>;
102
+ sendMessage: (text: string, files?: FileRef[]) => Promise<void>;
111
103
  /** Upload a file and return the uploaded file reference */
112
- uploadFile: (file: File) => Promise<UploadedFile>;
104
+ uploadFile: (file: File) => Promise<FileRef>;
113
105
  /** Stop the current generation */
114
106
  stopGeneration: () => void;
115
107
  /** Reset the chat (start fresh) */
@@ -194,11 +186,8 @@ export type ChatAction = {
194
186
  type: 'ADD_MESSAGE';
195
187
  payload: ChatMessageDTO;
196
188
  } | {
197
- type: 'SET_STATUS';
189
+ type: 'SET_CONNECTION_STATUS';
198
190
  payload: ChatStatus;
199
- } | {
200
- type: 'SET_IS_GENERATING';
201
- payload: boolean;
202
191
  } | {
203
192
  type: 'SET_ERROR';
204
193
  payload: string | undefined;
@@ -209,7 +198,7 @@ export type ChatAction = {
209
198
  * Context for action creators
210
199
  */
211
200
  /** Union of manager types used for real-time updates */
212
- export type UpdateManager = StreamManager<unknown> | PollManager<unknown>;
201
+ export type UpdateManager = StreamableManager<unknown> | PollManager<unknown>;
213
202
  export interface ActionsContext {
214
203
  client: AgentClient;
215
204
  dispatch: Dispatch<ChatAction>;
@@ -1,4 +1,4 @@
1
- import { StreamManager } from '../http/stream';
1
+ import { StreamableManager } from '../http/streamable';
2
2
  import { PollManager } from '../http/poll';
3
3
  import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ChatStatusBusy, } from '../types';
4
4
  /**
@@ -144,11 +144,12 @@ export class Agent {
144
144
  streamUntilIdle(options) {
145
145
  if (!this.chatId)
146
146
  return Promise.resolve();
147
+ const { url, headers } = this.http.getStreamableConfig(`/chats/${this.chatId}/stream`);
147
148
  return new Promise((resolve) => {
148
149
  this.stream?.stop();
149
- this.stream = new StreamManager({
150
- createEventSource: async () => this.http.createEventSource(`/chats/${this.chatId}/stream`),
151
- autoReconnect: true,
150
+ this.stream = new StreamableManager({
151
+ url,
152
+ headers,
152
153
  });
153
154
  this.stream.addEventListener('chats', (chat) => {
154
155
  options.onChat?.(chat);
@@ -173,7 +174,7 @@ export class Agent {
173
174
  }
174
175
  }
175
176
  });
176
- this.stream.connect();
177
+ this.stream.start();
177
178
  });
178
179
  }
179
180
  /** Poll until chat becomes idle, dispatching callbacks on changes */
package/dist/api/files.js CHANGED
@@ -81,10 +81,15 @@ export class FilesAPI {
81
81
  contentType = 'application/octet-stream';
82
82
  }
83
83
  }
84
+ // Extract filename from File object if not provided in options
85
+ let filename = options.filename;
86
+ if (!filename && data instanceof globalThis.File) {
87
+ filename = data.name;
88
+ }
84
89
  // Step 1: Create the file record
85
90
  const fileRequest = {
86
91
  uri: '', // Empty URI as it will be set by the server
87
- filename: options.filename,
92
+ filename,
88
93
  content_type: contentType,
89
94
  path: options.path,
90
95
  size: data instanceof Blob ? data.size : undefined,
package/dist/api/tasks.js CHANGED
@@ -1,4 +1,4 @@
1
- import { StreamManager } from '../http/stream';
1
+ import { StreamableManager } from '../http/streamable';
2
2
  import { PollManager } from '../http/poll';
3
3
  import { TaskStatusCompleted, TaskStatusFailed, TaskStatusCancelled, } from '../types';
4
4
  import { parseStatus } from '../utils';
@@ -84,15 +84,14 @@ export class TasksAPI {
84
84
  if (!useStream) {
85
85
  return this.pollUntilTerminal(task, options);
86
86
  }
87
- // Wait for completion with optional updates via SSE
87
+ // Wait for completion with optional updates via NDJSON streaming
88
88
  // Accumulate state across partial updates to preserve fields like session_id
89
89
  let accumulatedTask = { ...task };
90
+ const { url, headers } = this.http.getStreamableConfig(`/tasks/${task.id}/stream`);
90
91
  return new Promise((resolve, reject) => {
91
- const streamManager = new StreamManager({
92
- createEventSource: async () => this.http.createEventSource(`/tasks/${task.id}/stream`),
93
- autoReconnect,
94
- maxReconnects,
95
- reconnectDelayMs,
92
+ const streamManager = new StreamableManager({
93
+ url,
94
+ headers,
96
95
  onData: (data) => {
97
96
  // Merge new data, preserving existing fields if not in update
98
97
  accumulatedTask = { ...accumulatedTask, ...data };
@@ -134,7 +133,7 @@ export class TasksAPI {
134
133
  streamManager.stop();
135
134
  },
136
135
  });
137
- streamManager.connect();
136
+ streamManager.start();
138
137
  });
139
138
  }
140
139
  /** Poll GET /tasks/{id}/status until terminal, full-fetch on status change. */
@@ -76,8 +76,17 @@ export declare class HttpClient {
76
76
  * Execute the actual HTTP request (internal)
77
77
  */
78
78
  private executeRequest;
79
+ /**
80
+ * Get URL and headers for NDJSON streaming.
81
+ * Returns the full URL and auth headers needed for streamable requests.
82
+ */
83
+ getStreamableConfig(endpoint: string): {
84
+ url: string;
85
+ headers: Record<string, string>;
86
+ };
79
87
  /**
80
88
  * Create an EventSource for SSE streaming
89
+ * @deprecated Use getStreamableConfig() with StreamableManager instead
81
90
  */
82
91
  createEventSource(endpoint: string): Promise<EventSource | null>;
83
92
  }
@@ -161,8 +161,33 @@ export class HttpClient {
161
161
  }
162
162
  return apiResponse.data;
163
163
  }
164
+ /**
165
+ * Get URL and headers for NDJSON streaming.
166
+ * Returns the full URL and auth headers needed for streamable requests.
167
+ */
168
+ getStreamableConfig(endpoint) {
169
+ const targetUrl = new URL(`${this.baseUrl}${endpoint}`);
170
+ const isProxyMode = !!this.proxyUrl;
171
+ let url;
172
+ const headers = { ...this.resolveHeaders() };
173
+ if (isProxyMode) {
174
+ const proxyUrlWithQuery = new URL(this.proxyUrl, typeof window !== 'undefined' ? window.location.origin : 'http://localhost');
175
+ proxyUrlWithQuery.searchParams.set('__inf_target', targetUrl.toString());
176
+ url = proxyUrlWithQuery.toString();
177
+ headers['x-inf-target-url'] = targetUrl.toString();
178
+ }
179
+ else {
180
+ url = targetUrl.toString();
181
+ const token = this.getAuthToken();
182
+ if (token) {
183
+ headers['Authorization'] = `Bearer ${token}`;
184
+ }
185
+ }
186
+ return { url, headers };
187
+ }
164
188
  /**
165
189
  * Create an EventSource for SSE streaming
190
+ * @deprecated Use getStreamableConfig() with StreamableManager instead
166
191
  */
167
192
  createEventSource(endpoint) {
168
193
  const targetUrl = new URL(`${this.baseUrl}${endpoint}`);
@@ -56,7 +56,13 @@ export declare class StreamableManager<T> {
56
56
  private options;
57
57
  private abortController;
58
58
  private isRunning;
59
+ private eventListeners;
59
60
  constructor(options: StreamableManagerOptions<T>);
61
+ /**
62
+ * Add a listener for typed events (e.g., 'chats', 'chat_messages').
63
+ * Used when server sends events with {"event": "eventName", "data": ...} format.
64
+ */
65
+ addEventListener<E = unknown>(eventName: string, callback: (data: E) => void): () => void;
60
66
  start(): Promise<void>;
61
67
  stop(): void;
62
68
  }
@@ -79,8 +79,28 @@ export class StreamableManager {
79
79
  constructor(options) {
80
80
  this.abortController = null;
81
81
  this.isRunning = false;
82
+ this.eventListeners = new Map();
82
83
  this.options = options;
83
84
  }
85
+ /**
86
+ * Add a listener for typed events (e.g., 'chats', 'chat_messages').
87
+ * Used when server sends events with {"event": "eventName", "data": ...} format.
88
+ */
89
+ addEventListener(eventName, callback) {
90
+ const listeners = this.eventListeners.get(eventName) || new Set();
91
+ listeners.add(callback);
92
+ this.eventListeners.set(eventName, listeners);
93
+ // Return cleanup function
94
+ return () => {
95
+ const listeners = this.eventListeners.get(eventName);
96
+ if (listeners) {
97
+ listeners.delete(callback);
98
+ if (listeners.size === 0) {
99
+ this.eventListeners.delete(eventName);
100
+ }
101
+ }
102
+ };
103
+ }
84
104
  async start() {
85
105
  if (this.isRunning)
86
106
  return;
@@ -95,13 +115,20 @@ export class StreamableManager {
95
115
  })) {
96
116
  if (!this.isRunning)
97
117
  break;
118
+ const wrapper = message;
119
+ // Handle typed events ({"event": "...", "data": ...})
120
+ if (wrapper.event && this.eventListeners.has(wrapper.event)) {
121
+ const listeners = this.eventListeners.get(wrapper.event);
122
+ const eventData = wrapper.data !== undefined ? wrapper.data : message;
123
+ listeners.forEach(callback => callback(eventData));
124
+ continue;
125
+ }
98
126
  // Check if it's a partial update
99
127
  if (typeof message === 'object' &&
100
128
  message !== null &&
101
129
  'data' in message &&
102
130
  'fields' in message &&
103
- Array.isArray(message.fields)) {
104
- const wrapper = message;
131
+ Array.isArray(wrapper.fields)) {
105
132
  if (this.options.onPartialData && wrapper.data !== undefined) {
106
133
  this.options.onPartialData(wrapper.data, wrapper.fields);
107
134
  }
@@ -109,6 +136,10 @@ export class StreamableManager {
109
136
  this.options.onData?.(wrapper.data);
110
137
  }
111
138
  }
139
+ else if (wrapper.data !== undefined) {
140
+ // Has data wrapper but no fields - unwrap
141
+ this.options.onData?.(wrapper.data);
142
+ }
112
143
  else {
113
144
  this.options.onData?.(message);
114
145
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { HttpClient, HttpClientConfig, ErrorHandler, createHttpClient } from './http/client';
2
2
  export { StreamManager, StreamManagerOptions, PartialDataWrapper } from './http/stream';
3
+ export { StreamableManager, StreamableManagerOptions, StreamableMessage, streamable, streamableRaw } from './http/streamable';
3
4
  export { PollManager, PollManagerOptions } from './http/poll';
4
5
  export { InferenceError, RequirementsNotMetException, SessionError, SessionNotFoundError, SessionExpiredError, SessionEndedError, WorkerLostError, isRequirementsNotMetException, isInferenceError, isSessionError, } from './http/errors';
5
6
  export { TasksAPI, RunOptions } from './api/tasks';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // HTTP utilities
2
2
  export { HttpClient, createHttpClient } from './http/client';
3
3
  export { StreamManager } from './http/stream';
4
+ export { StreamableManager, streamable, streamableRaw } from './http/streamable';
4
5
  export { PollManager } from './http/poll';
5
6
  export { InferenceError, RequirementsNotMetException, SessionError, SessionNotFoundError, SessionExpiredError, SessionEndedError, WorkerLostError,
6
7
  // Type guards (use these instead of instanceof for reliability)
@@ -40,6 +40,7 @@ export function createHandler(options) {
40
40
  // Handle streaming responses
41
41
  const contentType = response.headers.get("content-type");
42
42
  if (contentType?.includes("text/event-stream") ||
43
+ contentType?.includes("application/x-ndjson") ||
43
44
  contentType?.includes("application/octet-stream")) {
44
45
  if (response.body) {
45
46
  const reader = response.body.getReader();
@@ -110,12 +110,13 @@ export async function processProxyRequest(adapter, options) {
110
110
  const userAgent = firstValue(adapter.header("user-agent"));
111
111
  const proxyId = `@inferencesh/sdk-proxy/${adapter.framework}`;
112
112
  // 6. Make upstream request
113
+ const accept = firstValue(adapter.header("accept"));
113
114
  const response = await fetch(targetUrl, {
114
115
  method: adapter.method,
115
116
  headers: {
116
117
  ...forwardHeaders,
117
118
  authorization: firstValue(adapter.header("authorization")) ?? `Bearer ${apiKey}`,
118
- accept: "application/json",
119
+ accept: accept || "application/json",
119
120
  "content-type": contentType || "application/json",
120
121
  "user-agent": userAgent || proxyId,
121
122
  "x-inf-proxy": proxyId,
package/dist/types.d.ts CHANGED
@@ -509,6 +509,17 @@ export interface CreateFlowRunRequest {
509
509
  flow: string;
510
510
  input: any;
511
511
  }
512
+ /**
513
+ * CreateAppRequest is the request body for POST /apps
514
+ */
515
+ export interface CreateAppRequest extends App {
516
+ /**
517
+ * PreserveCurrentVersion prevents auto-promoting the new version to current.
518
+ * Default false = new versions become current (what most users expect).
519
+ * Set true for admin deployments where you want to test before promoting.
520
+ */
521
+ preserve_current_version?: boolean;
522
+ }
512
523
  /**
513
524
  * CreateAgentRequest is the request body for POST /agents
514
525
  * For new agents: omit ID, backend generates it
@@ -596,6 +607,7 @@ export interface DeviceAuthPollResponse {
596
607
  export interface DeviceAuthApproveRequest {
597
608
  code: string;
598
609
  team_id: string;
610
+ scopes?: string[];
599
611
  }
600
612
  /**
601
613
  * DeviceAuthApproveResponse is returned after approval
@@ -750,6 +762,51 @@ export interface EngineConfig {
750
762
  */
751
763
  engine_internal_api_url: string;
752
764
  }
765
+ /**
766
+ * ScopeGroup identifies a category of scopes for UI grouping
767
+ */
768
+ export type ScopeGroup = string;
769
+ export declare const ScopeGroupAgents: ScopeGroup;
770
+ export declare const ScopeGroupApps: ScopeGroup;
771
+ export declare const ScopeGroupConversations: ScopeGroup;
772
+ export declare const ScopeGroupFiles: ScopeGroup;
773
+ export declare const ScopeGroupDatastores: ScopeGroup;
774
+ export declare const ScopeGroupFlows: ScopeGroup;
775
+ export declare const ScopeGroupProjects: ScopeGroup;
776
+ export declare const ScopeGroupTeams: ScopeGroup;
777
+ export declare const ScopeGroupBilling: ScopeGroup;
778
+ export declare const ScopeGroupSecrets: ScopeGroup;
779
+ export declare const ScopeGroupIntegrations: ScopeGroup;
780
+ export declare const ScopeGroupEngines: ScopeGroup;
781
+ export declare const ScopeGroupApiKeys: ScopeGroup;
782
+ export declare const ScopeGroupUser: ScopeGroup;
783
+ export declare const ScopeGroupSettings: ScopeGroup;
784
+ /**
785
+ * ScopeDefinition describes a single scope for UI rendering
786
+ */
787
+ export interface ScopeDefinition {
788
+ value: string;
789
+ label: string;
790
+ description: string;
791
+ group: ScopeGroup;
792
+ }
793
+ /**
794
+ * ScopeGroupDefinition describes a group of scopes for UI rendering
795
+ */
796
+ export interface ScopeGroupDefinition {
797
+ id: ScopeGroup;
798
+ label: string;
799
+ description: string;
800
+ }
801
+ /**
802
+ * ScopePreset represents a predefined bundle of scopes for common use cases
803
+ */
804
+ export interface ScopePreset {
805
+ id: string;
806
+ label: string;
807
+ description: string;
808
+ scopes: string[];
809
+ }
753
810
  export type AppCategory = string;
754
811
  export declare const AppCategoryImage: AppCategory;
755
812
  export declare const AppCategoryVideo: AppCategory;
@@ -1042,6 +1099,17 @@ export declare const IntegrationTypeSlack: IntegrationType;
1042
1099
  export declare const IntegrationTypeDiscord: IntegrationType;
1043
1100
  export declare const IntegrationTypeTeams: IntegrationType;
1044
1101
  export declare const IntegrationTypeTelegram: IntegrationType;
1102
+ /**
1103
+ * FileRef is a lightweight reference to a file with essential metadata.
1104
+ * Used in chat inputs/context instead of full File objects.
1105
+ */
1106
+ export interface FileRef {
1107
+ id?: string;
1108
+ uri: string;
1109
+ filename: string;
1110
+ content_type: string;
1111
+ size?: number;
1112
+ }
1045
1113
  export interface ChatMessageContent {
1046
1114
  type: ChatMessageContentType;
1047
1115
  error?: string;
@@ -1078,6 +1146,13 @@ export interface ChatTaskInput {
1078
1146
  role?: ChatMessageRole;
1079
1147
  text?: string;
1080
1148
  reasoning?: string;
1149
+ /**
1150
+ * Attachments is the SDK input field with full file metadata
1151
+ */
1152
+ attachments?: FileRef[];
1153
+ /**
1154
+ * Images and Files are internal fields for task workers (filled from Attachments or context)
1155
+ */
1081
1156
  images?: string[];
1082
1157
  files?: string[];
1083
1158
  tools?: Tool[];
package/dist/types.js CHANGED
@@ -4,6 +4,21 @@ export const ToolTypeAgent = "agent"; // Sub-agent tools - creates a sub-Chat
4
4
  export const ToolTypeHook = "hook"; // Webhook tools - HTTP POST to external URL
5
5
  export const ToolTypeClient = "client"; // Client tools - executed by frontend
6
6
  export const ToolTypeInternal = "internal"; // Internal/built-in tools (plan, memory, widget, finish)
7
+ export const ScopeGroupAgents = "agents";
8
+ export const ScopeGroupApps = "apps";
9
+ export const ScopeGroupConversations = "conversations";
10
+ export const ScopeGroupFiles = "files";
11
+ export const ScopeGroupDatastores = "datastores";
12
+ export const ScopeGroupFlows = "flows";
13
+ export const ScopeGroupProjects = "projects";
14
+ export const ScopeGroupTeams = "teams";
15
+ export const ScopeGroupBilling = "billing";
16
+ export const ScopeGroupSecrets = "secrets";
17
+ export const ScopeGroupIntegrations = "integrations";
18
+ export const ScopeGroupEngines = "engines";
19
+ export const ScopeGroupApiKeys = "apikeys";
20
+ export const ScopeGroupUser = "user";
21
+ export const ScopeGroupSettings = "settings";
7
22
  export const AppCategoryImage = "image";
8
23
  export const AppCategoryVideo = "video";
9
24
  export const AppCategoryAudio = "audio";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inferencesh/sdk",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Official JavaScript/TypeScript SDK for inference.sh - Run AI models with a simple API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",