@inferencesh/sdk 0.5.3 → 0.5.5
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/README.md +22 -0
- package/dist/agent/actions.js +54 -1
- package/dist/agent/provider.d.ts +1 -1
- package/dist/agent/provider.js +9 -1
- package/dist/agent/types.d.ts +12 -3
- package/dist/api/agents.d.ts +8 -1
- package/dist/api/agents.js +84 -9
- package/dist/api/tasks.d.ts +6 -0
- package/dist/api/tasks.js +51 -1
- package/dist/http/client.d.ts +16 -0
- package/dist/http/client.js +10 -0
- package/dist/http/poll.d.ts +39 -0
- package/dist/http/poll.js +73 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +3 -0
- package/dist/tool-builder.js +2 -0
- package/dist/tool-builder.test.js +11 -0
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -264,6 +264,28 @@ await agent.sendMessage('What is the weather in Paris?', {
|
|
|
264
264
|
});
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
+
### Structured Output
|
|
268
|
+
|
|
269
|
+
Use `output_schema` to get structured JSON responses:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const agent = client.agents.create({
|
|
273
|
+
core_app: { ref: 'infsh/claude-sonnet-4@latest' },
|
|
274
|
+
output_schema: {
|
|
275
|
+
type: 'object',
|
|
276
|
+
properties: {
|
|
277
|
+
summary: { type: 'string' },
|
|
278
|
+
sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
|
|
279
|
+
confidence: { type: 'number' },
|
|
280
|
+
},
|
|
281
|
+
required: ['summary', 'sentiment', 'confidence'],
|
|
282
|
+
},
|
|
283
|
+
internal_tools: { finish: true },
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const response = await agent.sendMessage('Analyze: Great product!');
|
|
287
|
+
```
|
|
288
|
+
|
|
267
289
|
### Agent Methods
|
|
268
290
|
|
|
269
291
|
| Method | Description |
|
package/dist/agent/actions.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { ToolInvocationStatusAwaitingInput, ToolTypeClient, } from '../types';
|
|
8
8
|
import { StreamManager } from '../http/stream';
|
|
9
|
+
import { PollManager } from '../http/poll';
|
|
9
10
|
import { isAdHocConfig, extractClientToolHandlers } from './types';
|
|
10
11
|
import * as api from './api';
|
|
11
12
|
// =============================================================================
|
|
@@ -16,7 +17,7 @@ const dispatchedToolInvocations = new Set();
|
|
|
16
17
|
// Action Creators
|
|
17
18
|
// =============================================================================
|
|
18
19
|
export function createActions(ctx) {
|
|
19
|
-
const { client, dispatch, getConfig, getChatId, getClientToolHandlers, getStreamManager, setStreamManager, callbacks } = ctx;
|
|
20
|
+
const { client, dispatch, getConfig, getChatId, getClientToolHandlers, getStreamManager, setStreamManager, getStreamEnabled, getPollIntervalMs, callbacks } = ctx;
|
|
20
21
|
// =========================================================================
|
|
21
22
|
// Internal helpers
|
|
22
23
|
// =========================================================================
|
|
@@ -92,6 +93,11 @@ export function createActions(ctx) {
|
|
|
92
93
|
callbacks.onStatusChange?.('idle');
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
96
|
+
if (!getStreamEnabled()) {
|
|
97
|
+
// Polling mode
|
|
98
|
+
pollChat(id);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
95
101
|
// Single unified stream with TypedEvents (both Chat and ChatMessage events)
|
|
96
102
|
const manager = new StreamManager({
|
|
97
103
|
createEventSource: () => api.createUnifiedStream(client, id),
|
|
@@ -129,6 +135,53 @@ export function createActions(ctx) {
|
|
|
129
135
|
setStreamManager(manager);
|
|
130
136
|
manager.connect();
|
|
131
137
|
};
|
|
138
|
+
/** Poll-based alternative to streaming for restricted environments */
|
|
139
|
+
const pollChat = (id) => {
|
|
140
|
+
let prevStatus = null;
|
|
141
|
+
const manager = new PollManager({
|
|
142
|
+
pollFunction: () => client.http.request('get', `/chats/${id}/status`),
|
|
143
|
+
intervalMs: getPollIntervalMs(),
|
|
144
|
+
onData: async (statusData) => {
|
|
145
|
+
if (statusData.status === prevStatus)
|
|
146
|
+
return;
|
|
147
|
+
prevStatus = statusData.status;
|
|
148
|
+
// Status changed — fetch full chat
|
|
149
|
+
try {
|
|
150
|
+
const chat = await api.fetchChat(client, id);
|
|
151
|
+
if (chat) {
|
|
152
|
+
setChat(chat);
|
|
153
|
+
if (chat.chat_messages) {
|
|
154
|
+
for (const message of chat.chat_messages) {
|
|
155
|
+
updateMessage(message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
console.warn('[AgentSDK] Poll fetch error:', err);
|
|
162
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
onStart: () => {
|
|
166
|
+
dispatch({ type: 'SET_STATUS', payload: 'streaming' });
|
|
167
|
+
callbacks.onStatusChange?.('streaming');
|
|
168
|
+
},
|
|
169
|
+
onStop: () => {
|
|
170
|
+
if (getStreamManager()) {
|
|
171
|
+
setStreamManager(undefined);
|
|
172
|
+
dispatch({ type: 'SET_STATUS', payload: 'idle' });
|
|
173
|
+
dispatch({ type: 'SET_IS_GENERATING', payload: false });
|
|
174
|
+
callbacks.onStatusChange?.('idle');
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
onError: (error) => {
|
|
178
|
+
console.warn('[AgentSDK] Poll error:', error);
|
|
179
|
+
callbacks.onError?.(error);
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
setStreamManager(manager);
|
|
183
|
+
manager.start();
|
|
184
|
+
};
|
|
132
185
|
const stopStream = () => {
|
|
133
186
|
const manager = getStreamManager();
|
|
134
187
|
// Clear ref first so onStop callback (from manager.stop) is a no-op
|
package/dist/agent/provider.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ import type { AgentChatProviderProps } from './types';
|
|
|
27
27
|
* }
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
-
export declare function AgentChatProvider({ client, agentConfig, chatId, clientToolHandlers: extraHandlers, onChatCreated, onStatusChange, onError, children, }: AgentChatProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare function AgentChatProvider({ client, agentConfig, chatId, clientToolHandlers: extraHandlers, onChatCreated, onStatusChange, onError, stream, pollIntervalMs, children, }: AgentChatProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
31
31
|
export declare namespace AgentChatProvider {
|
|
32
32
|
var displayName: string;
|
|
33
33
|
}
|
package/dist/agent/provider.js
CHANGED
|
@@ -39,13 +39,15 @@ function mergeHandlers(base, extra) {
|
|
|
39
39
|
* }
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandlers: extraHandlers, onChatCreated, onStatusChange, onError, children, }) {
|
|
42
|
+
export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandlers: extraHandlers, onChatCreated, onStatusChange, onError, stream, pollIntervalMs, children, }) {
|
|
43
43
|
// Core state via useReducer
|
|
44
44
|
const [state, dispatch] = useReducer(chatReducer, initialState);
|
|
45
45
|
// Refs for mutable values that actions need access to
|
|
46
46
|
const configRef = useRef(agentConfig);
|
|
47
47
|
const chatIdRef = useRef(chatId ?? null);
|
|
48
48
|
const streamManagerRef = useRef(undefined);
|
|
49
|
+
const streamRef = useRef(stream);
|
|
50
|
+
const pollIntervalMsRef = useRef(pollIntervalMs);
|
|
49
51
|
const clientToolHandlersRef = useRef(mergeHandlers(getClientToolHandlers(agentConfig), extraHandlers));
|
|
50
52
|
const callbacksRef = useRef({ onChatCreated, onStatusChange, onError });
|
|
51
53
|
// Keep refs in sync with props
|
|
@@ -56,6 +58,10 @@ export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandl
|
|
|
56
58
|
useEffect(() => {
|
|
57
59
|
callbacksRef.current = { onChatCreated, onStatusChange, onError };
|
|
58
60
|
}, [onChatCreated, onStatusChange, onError]);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
streamRef.current = stream;
|
|
63
|
+
pollIntervalMsRef.current = pollIntervalMs;
|
|
64
|
+
}, [stream, pollIntervalMs]);
|
|
59
65
|
// Keep chatIdRef synced with state
|
|
60
66
|
useEffect(() => {
|
|
61
67
|
chatIdRef.current = state.chatId;
|
|
@@ -69,6 +75,8 @@ export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandl
|
|
|
69
75
|
getClientToolHandlers: () => clientToolHandlersRef.current,
|
|
70
76
|
getStreamManager: () => streamManagerRef.current,
|
|
71
77
|
setStreamManager: (manager) => { streamManagerRef.current = manager; },
|
|
78
|
+
getStreamEnabled: () => streamRef.current ?? client.http.getStreamDefault(),
|
|
79
|
+
getPollIntervalMs: () => pollIntervalMsRef.current ?? client.http.getPollIntervalMs(),
|
|
72
80
|
callbacks: callbacksRef.current,
|
|
73
81
|
}), [client]);
|
|
74
82
|
// Re-bind callbacks when they change
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { Dispatch } from 'react';
|
|
|
7
7
|
import type { ChatDTO, ChatMessageDTO, AgentTool, AgentConfig as GeneratedAgentConfig, CoreAppConfig } from '../types';
|
|
8
8
|
import type { HttpClient } from '../http/client';
|
|
9
9
|
import type { StreamManager } from '../http/stream';
|
|
10
|
+
import type { PollManager } from '../http/poll';
|
|
10
11
|
/**
|
|
11
12
|
* Minimal file interface returned by upload (just needs uri and content_type)
|
|
12
13
|
*/
|
|
@@ -20,7 +21,7 @@ export interface UploadedFile {
|
|
|
20
21
|
*/
|
|
21
22
|
export interface AgentClient {
|
|
22
23
|
/** HTTP client for API requests */
|
|
23
|
-
http: Pick<HttpClient, 'request' | 'createEventSource'>;
|
|
24
|
+
http: Pick<HttpClient, 'request' | 'createEventSource' | 'getStreamDefault' | 'getPollIntervalMs'>;
|
|
24
25
|
/** Files API for uploads */
|
|
25
26
|
files: {
|
|
26
27
|
upload: (data: string | Blob | globalThis.File) => Promise<UploadedFile>;
|
|
@@ -142,6 +143,10 @@ export interface AgentChatProviderProps {
|
|
|
142
143
|
onStatusChange?: (status: ChatStatus) => void;
|
|
143
144
|
/** Callback when an error occurs */
|
|
144
145
|
onError?: (error: Error) => void;
|
|
146
|
+
/** Use SSE streaming (true, default) or polling (false) for real-time updates */
|
|
147
|
+
stream?: boolean;
|
|
148
|
+
/** Polling interval in ms when stream is false (default: 2000) */
|
|
149
|
+
pollIntervalMs?: number;
|
|
145
150
|
/** Children */
|
|
146
151
|
children: React.ReactNode;
|
|
147
152
|
}
|
|
@@ -203,14 +208,18 @@ export type ChatAction = {
|
|
|
203
208
|
/**
|
|
204
209
|
* Context for action creators
|
|
205
210
|
*/
|
|
211
|
+
/** Union of manager types used for real-time updates */
|
|
212
|
+
export type UpdateManager = StreamManager<unknown> | PollManager<unknown>;
|
|
206
213
|
export interface ActionsContext {
|
|
207
214
|
client: AgentClient;
|
|
208
215
|
dispatch: Dispatch<ChatAction>;
|
|
209
216
|
getConfig: () => AgentOptions | null;
|
|
210
217
|
getChatId: () => string | null;
|
|
211
218
|
getClientToolHandlers: () => Map<string, ClientToolHandlerFn>;
|
|
212
|
-
getStreamManager: () =>
|
|
213
|
-
setStreamManager: (manager:
|
|
219
|
+
getStreamManager: () => UpdateManager | undefined;
|
|
220
|
+
setStreamManager: (manager: UpdateManager | undefined) => void;
|
|
221
|
+
getStreamEnabled: () => boolean;
|
|
222
|
+
getPollIntervalMs: () => number;
|
|
214
223
|
callbacks: {
|
|
215
224
|
onChatCreated?: (chatId: string) => void;
|
|
216
225
|
onStatusChange?: (status: ChatStatus) => void;
|
package/dist/api/agents.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export interface SendMessageOptions {
|
|
|
27
27
|
name: string;
|
|
28
28
|
args: Record<string, unknown>;
|
|
29
29
|
}) => void;
|
|
30
|
+
/** Use SSE streaming (true) or polling (false). Overrides client default. */
|
|
31
|
+
stream?: boolean;
|
|
32
|
+
/** Polling interval in ms when stream is false. Overrides client default. */
|
|
33
|
+
pollIntervalMs?: number;
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
32
36
|
* Agent for chat interactions
|
|
@@ -40,6 +44,7 @@ export declare class Agent {
|
|
|
40
44
|
private readonly agentName;
|
|
41
45
|
private chatId;
|
|
42
46
|
private stream;
|
|
47
|
+
private poller;
|
|
43
48
|
private dispatchedToolCalls;
|
|
44
49
|
/** @internal */
|
|
45
50
|
constructor(http: HttpClient, files: FilesAPI, config: string | AgentConfig, options?: AgentOptions);
|
|
@@ -64,7 +69,7 @@ export declare class Agent {
|
|
|
64
69
|
};
|
|
65
70
|
form_data?: Record<string, unknown>;
|
|
66
71
|
}): Promise<void>;
|
|
67
|
-
/** Stop streaming and cleanup */
|
|
72
|
+
/** Stop streaming/polling and cleanup */
|
|
68
73
|
disconnect(): void;
|
|
69
74
|
/** Reset the agent (start fresh chat) */
|
|
70
75
|
reset(): void;
|
|
@@ -74,6 +79,8 @@ export declare class Agent {
|
|
|
74
79
|
startStreaming(options?: Omit<SendMessageOptions, 'files'>): void;
|
|
75
80
|
/** Stream events until chat becomes idle */
|
|
76
81
|
private streamUntilIdle;
|
|
82
|
+
/** Poll until chat becomes idle, dispatching callbacks on changes */
|
|
83
|
+
private pollUntilIdle;
|
|
77
84
|
}
|
|
78
85
|
/**
|
|
79
86
|
* Agents API
|
package/dist/api/agents.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StreamManager } from '../http/stream';
|
|
2
|
+
import { PollManager } from '../http/poll';
|
|
2
3
|
import { ToolTypeClient, ToolInvocationStatusAwaitingInput, } from '../types';
|
|
3
4
|
/**
|
|
4
5
|
* Agent for chat interactions
|
|
@@ -10,6 +11,7 @@ export class Agent {
|
|
|
10
11
|
constructor(http, files, config, options) {
|
|
11
12
|
this.chatId = null;
|
|
12
13
|
this.stream = null;
|
|
14
|
+
this.poller = null;
|
|
13
15
|
this.dispatchedToolCalls = new Set();
|
|
14
16
|
this.http = http;
|
|
15
17
|
this.files = files;
|
|
@@ -59,24 +61,28 @@ export class Agent {
|
|
|
59
61
|
agent_name: this.agentName ?? this.config.name,
|
|
60
62
|
input: { text, images: imageUris, files: fileUris, role: 'user', context: [], system_prompt: '', context_size: 0 },
|
|
61
63
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
const useStream = options.stream ?? this.http.getStreamDefault();
|
|
65
|
+
const waitFn = useStream
|
|
66
|
+
? (opts) => this.streamUntilIdle(opts)
|
|
67
|
+
: (opts) => this.pollUntilIdle(opts);
|
|
68
|
+
// For existing chats with callbacks: Start waiting BEFORE POST so we don't miss updates
|
|
69
|
+
let waitPromise = null;
|
|
64
70
|
if (this.chatId && hasCallbacks) {
|
|
65
|
-
|
|
71
|
+
waitPromise = waitFn(options);
|
|
66
72
|
}
|
|
67
73
|
// Make the POST request
|
|
68
74
|
const response = await this.http.request('post', '/agents/run', { data: body });
|
|
69
|
-
// For new chats: Set chatId and start
|
|
75
|
+
// For new chats: Set chatId and start waiting immediately after POST
|
|
70
76
|
const isNewChat = !this.chatId && response.assistant_message.chat_id;
|
|
71
77
|
if (isNewChat) {
|
|
72
78
|
this.chatId = response.assistant_message.chat_id;
|
|
73
79
|
if (hasCallbacks) {
|
|
74
|
-
|
|
80
|
+
waitPromise = waitFn(options);
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
|
-
// Wait for
|
|
78
|
-
if (
|
|
79
|
-
await
|
|
83
|
+
// Wait for completion
|
|
84
|
+
if (waitPromise) {
|
|
85
|
+
await waitPromise;
|
|
80
86
|
}
|
|
81
87
|
return { userMessage: response.user_message, assistantMessage: response.assistant_message };
|
|
82
88
|
}
|
|
@@ -100,10 +106,12 @@ export class Agent {
|
|
|
100
106
|
const result = typeof resultOrAction === 'string' ? resultOrAction : JSON.stringify(resultOrAction);
|
|
101
107
|
await this.http.request('post', `/tools/${toolInvocationId}`, { data: { result } });
|
|
102
108
|
}
|
|
103
|
-
/** Stop streaming and cleanup */
|
|
109
|
+
/** Stop streaming/polling and cleanup */
|
|
104
110
|
disconnect() {
|
|
105
111
|
this.stream?.stop();
|
|
106
112
|
this.stream = null;
|
|
113
|
+
this.poller?.stop();
|
|
114
|
+
this.poller = null;
|
|
107
115
|
}
|
|
108
116
|
/** Reset the agent (start fresh chat) */
|
|
109
117
|
reset() {
|
|
@@ -155,6 +163,73 @@ export class Agent {
|
|
|
155
163
|
this.stream.connect();
|
|
156
164
|
});
|
|
157
165
|
}
|
|
166
|
+
/** Poll until chat becomes idle, dispatching callbacks on changes */
|
|
167
|
+
pollUntilIdle(options) {
|
|
168
|
+
if (!this.chatId)
|
|
169
|
+
return Promise.resolve();
|
|
170
|
+
const intervalMs = options.pollIntervalMs ?? this.http.getPollIntervalMs();
|
|
171
|
+
let prevStatus = null;
|
|
172
|
+
let knownMessageIds = new Set();
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
this.poller?.stop();
|
|
175
|
+
this.poller = new PollManager({
|
|
176
|
+
pollFunction: async () => {
|
|
177
|
+
// Lightweight status check first
|
|
178
|
+
const status = await this.http.request('get', `/chats/${this.chatId}/status`);
|
|
179
|
+
if (status.status === prevStatus) {
|
|
180
|
+
// No change — return a stub to skip processing
|
|
181
|
+
return { status: status.status };
|
|
182
|
+
}
|
|
183
|
+
// Status changed — fetch full chat
|
|
184
|
+
return this.http.request('get', `/chats/${this.chatId}`);
|
|
185
|
+
},
|
|
186
|
+
intervalMs,
|
|
187
|
+
onData: (chat) => {
|
|
188
|
+
if (chat.status === prevStatus && !chat.chat_messages)
|
|
189
|
+
return;
|
|
190
|
+
prevStatus = chat.status;
|
|
191
|
+
options.onChat?.(chat);
|
|
192
|
+
// Dispatch new/updated messages
|
|
193
|
+
if (chat.chat_messages && options.onMessage) {
|
|
194
|
+
for (const message of chat.chat_messages) {
|
|
195
|
+
if (!knownMessageIds.has(message.id)) {
|
|
196
|
+
knownMessageIds.add(message.id);
|
|
197
|
+
options.onMessage(message);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Re-dispatch for potential updates
|
|
201
|
+
options.onMessage(message);
|
|
202
|
+
}
|
|
203
|
+
// Handle client tool invocations
|
|
204
|
+
if (message.tool_invocations && options.onToolCall) {
|
|
205
|
+
for (const inv of message.tool_invocations) {
|
|
206
|
+
if (this.dispatchedToolCalls.has(inv.id))
|
|
207
|
+
continue;
|
|
208
|
+
if (inv.type === ToolTypeClient && inv.status === ToolInvocationStatusAwaitingInput) {
|
|
209
|
+
this.dispatchedToolCalls.add(inv.id);
|
|
210
|
+
options.onToolCall({
|
|
211
|
+
id: inv.id,
|
|
212
|
+
name: inv.function?.name || '',
|
|
213
|
+
args: inv.function?.arguments || {},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (chat.status === 'idle') {
|
|
221
|
+
this.poller?.stop();
|
|
222
|
+
this.poller = null;
|
|
223
|
+
resolve();
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
onError: (error) => {
|
|
227
|
+
console.warn('[Agent] Poll error:', error);
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
this.poller.start();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
158
233
|
}
|
|
159
234
|
/**
|
|
160
235
|
* Agents API
|
package/dist/api/tasks.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export interface RunOptions {
|
|
|
13
13
|
maxReconnects?: number;
|
|
14
14
|
/** Delay between reconnection attempts in ms (default: 1000) */
|
|
15
15
|
reconnectDelayMs?: number;
|
|
16
|
+
/** Use SSE streaming (true) or polling (false). Overrides client default. */
|
|
17
|
+
stream?: boolean;
|
|
18
|
+
/** Polling interval in ms when stream is false. Overrides client default. */
|
|
19
|
+
pollIntervalMs?: number;
|
|
16
20
|
}
|
|
17
21
|
/**
|
|
18
22
|
* Tasks API
|
|
@@ -52,6 +56,8 @@ export declare class TasksAPI {
|
|
|
52
56
|
* Run a task and optionally wait for completion
|
|
53
57
|
*/
|
|
54
58
|
run(params: ApiAppRunRequest, processedInput: unknown, options?: RunOptions): Promise<Task>;
|
|
59
|
+
/** Poll GET /tasks/{id}/status until terminal, full-fetch on status change. */
|
|
60
|
+
private pollUntilTerminal;
|
|
55
61
|
/**
|
|
56
62
|
* Update task visibility
|
|
57
63
|
*/
|
package/dist/api/tasks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StreamManager } from '../http/stream';
|
|
2
|
+
import { PollManager } from '../http/poll';
|
|
2
3
|
import { TaskStatusCompleted, TaskStatusFailed, TaskStatusCancelled, } from '../types';
|
|
3
4
|
//TODO: This is ugly...
|
|
4
5
|
function stripTask(task) {
|
|
@@ -78,7 +79,11 @@ export class TasksAPI {
|
|
|
78
79
|
if (!wait) {
|
|
79
80
|
return stripTask(task);
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
+
const useStream = options.stream ?? this.http.getStreamDefault();
|
|
83
|
+
if (!useStream) {
|
|
84
|
+
return this.pollUntilTerminal(task, options);
|
|
85
|
+
}
|
|
86
|
+
// Wait for completion with optional updates via SSE
|
|
82
87
|
// Accumulate state across partial updates to preserve fields like session_id
|
|
83
88
|
let accumulatedTask = { ...task };
|
|
84
89
|
return new Promise((resolve, reject) => {
|
|
@@ -131,6 +136,51 @@ export class TasksAPI {
|
|
|
131
136
|
streamManager.connect();
|
|
132
137
|
});
|
|
133
138
|
}
|
|
139
|
+
/** Poll GET /tasks/{id}/status until terminal, full-fetch on status change. */
|
|
140
|
+
pollUntilTerminal(task, options) {
|
|
141
|
+
const { onUpdate, maxReconnects = 5 } = options;
|
|
142
|
+
const intervalMs = options.pollIntervalMs ?? this.http.getPollIntervalMs();
|
|
143
|
+
let prevStatus = task.status;
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const poller = new PollManager({
|
|
146
|
+
pollFunction: () => this.http.request('get', `/tasks/${task.id}/status`),
|
|
147
|
+
intervalMs,
|
|
148
|
+
maxRetries: maxReconnects,
|
|
149
|
+
onData: async (statusData) => {
|
|
150
|
+
if (statusData.status === prevStatus)
|
|
151
|
+
return;
|
|
152
|
+
prevStatus = statusData.status;
|
|
153
|
+
// Status changed — fetch full task
|
|
154
|
+
try {
|
|
155
|
+
const fullTask = await this.http.request('get', `/tasks/${task.id}`);
|
|
156
|
+
const stripped = stripTask(fullTask);
|
|
157
|
+
onUpdate?.(stripped);
|
|
158
|
+
if (fullTask.status === TaskStatusCompleted) {
|
|
159
|
+
poller.stop();
|
|
160
|
+
resolve(stripped);
|
|
161
|
+
}
|
|
162
|
+
else if (fullTask.status === TaskStatusFailed) {
|
|
163
|
+
poller.stop();
|
|
164
|
+
reject(new Error(fullTask.error || 'task failed'));
|
|
165
|
+
}
|
|
166
|
+
else if (fullTask.status === TaskStatusCancelled) {
|
|
167
|
+
poller.stop();
|
|
168
|
+
reject(new Error('task cancelled'));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
poller.stop();
|
|
173
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
onError: (error) => {
|
|
177
|
+
reject(error);
|
|
178
|
+
poller.stop();
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
poller.start();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
134
184
|
/**
|
|
135
185
|
* Update task visibility
|
|
136
186
|
*/
|
package/dist/http/client.d.ts
CHANGED
|
@@ -25,6 +25,16 @@ export interface HttpClientConfig {
|
|
|
25
25
|
* Can be used for retry logic, auth refresh, OTP handling, etc.
|
|
26
26
|
*/
|
|
27
27
|
onError?: ErrorHandler;
|
|
28
|
+
/**
|
|
29
|
+
* Use polling instead of SSE for real-time updates (default: true = SSE).
|
|
30
|
+
* Set to false for environments that can't maintain long-lived connections
|
|
31
|
+
* (Convex actions, Cloudflare Workers, restricted edge runtimes).
|
|
32
|
+
*/
|
|
33
|
+
stream?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Polling interval in milliseconds when stream is false (default: 2000).
|
|
36
|
+
*/
|
|
37
|
+
pollIntervalMs?: number;
|
|
28
38
|
}
|
|
29
39
|
/**
|
|
30
40
|
* Low-level HTTP client for inference.sh API
|
|
@@ -40,9 +50,15 @@ export declare class HttpClient {
|
|
|
40
50
|
private readonly customHeaders;
|
|
41
51
|
private readonly credentials;
|
|
42
52
|
private readonly onError;
|
|
53
|
+
private readonly streamDefault;
|
|
54
|
+
private readonly pollInterval;
|
|
43
55
|
constructor(config: HttpClientConfig);
|
|
44
56
|
/** Get the base URL */
|
|
45
57
|
getBaseUrl(): string;
|
|
58
|
+
/** Whether SSE streaming is the default transport (true) or polling (false) */
|
|
59
|
+
getStreamDefault(): boolean;
|
|
60
|
+
/** Get the default poll interval in ms */
|
|
61
|
+
getPollIntervalMs(): number;
|
|
46
62
|
/** Check if in proxy mode */
|
|
47
63
|
isProxyMode(): boolean;
|
|
48
64
|
/** Resolve dynamic headers */
|
package/dist/http/client.js
CHANGED
|
@@ -19,11 +19,21 @@ export class HttpClient {
|
|
|
19
19
|
this.customHeaders = config.headers || {};
|
|
20
20
|
this.credentials = config.credentials || 'include';
|
|
21
21
|
this.onError = config.onError;
|
|
22
|
+
this.streamDefault = config.stream ?? true;
|
|
23
|
+
this.pollInterval = config.pollIntervalMs ?? 2000;
|
|
22
24
|
}
|
|
23
25
|
/** Get the base URL */
|
|
24
26
|
getBaseUrl() {
|
|
25
27
|
return this.baseUrl;
|
|
26
28
|
}
|
|
29
|
+
/** Whether SSE streaming is the default transport (true) or polling (false) */
|
|
30
|
+
getStreamDefault() {
|
|
31
|
+
return this.streamDefault;
|
|
32
|
+
}
|
|
33
|
+
/** Get the default poll interval in ms */
|
|
34
|
+
getPollIntervalMs() {
|
|
35
|
+
return this.pollInterval;
|
|
36
|
+
}
|
|
27
37
|
/** Check if in proxy mode */
|
|
28
38
|
isProxyMode() {
|
|
29
39
|
return !!this.proxyUrl;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PollManager — polling transport alternative to StreamManager.
|
|
3
|
+
*
|
|
4
|
+
* Used when long-lived SSE connections are unavailable (Convex actions,
|
|
5
|
+
* Cloudflare Workers, restricted edge runtimes).
|
|
6
|
+
*/
|
|
7
|
+
export interface PollManagerOptions<T> {
|
|
8
|
+
/** Function that fetches the latest data each poll cycle */
|
|
9
|
+
pollFunction: () => Promise<T>;
|
|
10
|
+
/** Milliseconds between polls (default 2000) */
|
|
11
|
+
intervalMs?: number;
|
|
12
|
+
/** Maximum consecutive errors before giving up (default 5) */
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
/** Delay after an error before retrying (default 1000) */
|
|
15
|
+
retryDelayMs?: number;
|
|
16
|
+
/** Called with data on every successful poll */
|
|
17
|
+
onData?: (data: T) => void;
|
|
18
|
+
/** Called when an error occurs */
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
/** Called when polling starts */
|
|
21
|
+
onStart?: () => void;
|
|
22
|
+
/** Called when polling stops (intentionally or after max retries) */
|
|
23
|
+
onStop?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare class PollManager<T> {
|
|
26
|
+
private options;
|
|
27
|
+
private interval;
|
|
28
|
+
private retryTimeout;
|
|
29
|
+
private consecutiveErrors;
|
|
30
|
+
private isStopped;
|
|
31
|
+
private polling;
|
|
32
|
+
constructor(options: PollManagerOptions<T>);
|
|
33
|
+
/** Start polling. First poll is immediate, then at intervalMs. */
|
|
34
|
+
start(): void;
|
|
35
|
+
/** Stop polling and clean up. */
|
|
36
|
+
stop(): void;
|
|
37
|
+
private cleanup;
|
|
38
|
+
private poll;
|
|
39
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PollManager — polling transport alternative to StreamManager.
|
|
3
|
+
*
|
|
4
|
+
* Used when long-lived SSE connections are unavailable (Convex actions,
|
|
5
|
+
* Cloudflare Workers, restricted edge runtimes).
|
|
6
|
+
*/
|
|
7
|
+
export class PollManager {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.interval = null;
|
|
10
|
+
this.retryTimeout = null;
|
|
11
|
+
this.consecutiveErrors = 0;
|
|
12
|
+
this.isStopped = true;
|
|
13
|
+
this.polling = false;
|
|
14
|
+
this.options = {
|
|
15
|
+
intervalMs: 2000,
|
|
16
|
+
maxRetries: 5,
|
|
17
|
+
retryDelayMs: 1000,
|
|
18
|
+
...options,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Start polling. First poll is immediate, then at intervalMs. */
|
|
22
|
+
start() {
|
|
23
|
+
if (!this.isStopped)
|
|
24
|
+
return;
|
|
25
|
+
this.isStopped = false;
|
|
26
|
+
this.consecutiveErrors = 0;
|
|
27
|
+
this.options.onStart?.();
|
|
28
|
+
this.poll(); // immediate first poll
|
|
29
|
+
this.interval = setInterval(() => this.poll(), this.options.intervalMs);
|
|
30
|
+
}
|
|
31
|
+
/** Stop polling and clean up. */
|
|
32
|
+
stop() {
|
|
33
|
+
if (this.isStopped)
|
|
34
|
+
return;
|
|
35
|
+
this.isStopped = true;
|
|
36
|
+
this.cleanup();
|
|
37
|
+
this.options.onStop?.();
|
|
38
|
+
}
|
|
39
|
+
cleanup() {
|
|
40
|
+
if (this.interval) {
|
|
41
|
+
clearInterval(this.interval);
|
|
42
|
+
this.interval = null;
|
|
43
|
+
}
|
|
44
|
+
if (this.retryTimeout) {
|
|
45
|
+
clearTimeout(this.retryTimeout);
|
|
46
|
+
this.retryTimeout = null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async poll() {
|
|
50
|
+
if (this.isStopped || this.polling)
|
|
51
|
+
return;
|
|
52
|
+
this.polling = true;
|
|
53
|
+
try {
|
|
54
|
+
const data = await this.options.pollFunction();
|
|
55
|
+
this.polling = false;
|
|
56
|
+
if (this.isStopped)
|
|
57
|
+
return;
|
|
58
|
+
this.consecutiveErrors = 0;
|
|
59
|
+
this.options.onData?.(data);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
this.polling = false;
|
|
63
|
+
if (this.isStopped)
|
|
64
|
+
return;
|
|
65
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
66
|
+
this.consecutiveErrors++;
|
|
67
|
+
this.options.onError?.(error);
|
|
68
|
+
if (this.consecutiveErrors >= this.options.maxRetries) {
|
|
69
|
+
this.stop();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
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 { PollManager, PollManagerOptions } from './http/poll';
|
|
3
4
|
export { InferenceError, RequirementsNotMetException, SessionError, SessionNotFoundError, SessionExpiredError, SessionEndedError, WorkerLostError, } from './http/errors';
|
|
4
5
|
export { TasksAPI, RunOptions } from './api/tasks';
|
|
5
6
|
export { FilesAPI, UploadFileOptions } from './api/files';
|
|
@@ -35,6 +36,15 @@ export interface InferenceConfig {
|
|
|
35
36
|
* When set, requests are routed through your proxy server to protect API keys.
|
|
36
37
|
*/
|
|
37
38
|
proxyUrl?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Use SSE streaming (true, default) or polling (false) for real-time updates.
|
|
41
|
+
* Set to false for environments that can't maintain long-lived connections.
|
|
42
|
+
*/
|
|
43
|
+
stream?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Polling interval in milliseconds when stream is false (default: 2000).
|
|
46
|
+
*/
|
|
47
|
+
pollIntervalMs?: number;
|
|
38
48
|
}
|
|
39
49
|
/**
|
|
40
50
|
* Inference.sh SDK Client
|
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 { PollManager } from './http/poll';
|
|
4
5
|
export { InferenceError, RequirementsNotMetException, SessionError, SessionNotFoundError, SessionExpiredError, SessionEndedError, WorkerLostError, } from './http/errors';
|
|
5
6
|
// API modules
|
|
6
7
|
export { TasksAPI } from './api/tasks';
|
|
@@ -49,6 +50,8 @@ export class Inference {
|
|
|
49
50
|
headers: 'headers' in config ? config.headers : undefined,
|
|
50
51
|
credentials: 'credentials' in config ? config.credentials : undefined,
|
|
51
52
|
onError: 'onError' in config ? config.onError : undefined,
|
|
53
|
+
stream: 'stream' in config ? config.stream : undefined,
|
|
54
|
+
pollIntervalMs: 'pollIntervalMs' in config ? config.pollIntervalMs : undefined,
|
|
52
55
|
});
|
|
53
56
|
this.files = new FilesAPI(this.http);
|
|
54
57
|
this.tasks = new TasksAPI(this.http);
|
package/dist/tool-builder.js
CHANGED
|
@@ -28,6 +28,8 @@ function toJsonSchema(params) {
|
|
|
28
28
|
if (schema.properties) {
|
|
29
29
|
const nested = toJsonSchema(schema.properties);
|
|
30
30
|
prop.properties = nested.properties;
|
|
31
|
+
if (nested.required.length > 0)
|
|
32
|
+
prop.required = nested.required;
|
|
31
33
|
}
|
|
32
34
|
if (schema.items) {
|
|
33
35
|
const itemSchema = toJsonSchema({ _item: schema.items });
|
|
@@ -149,8 +149,19 @@ describe('ClientToolBuilder (tool)', () => {
|
|
|
149
149
|
name: { type: 'string', description: 'Name' },
|
|
150
150
|
email: { type: 'string', description: 'Email' },
|
|
151
151
|
},
|
|
152
|
+
required: ['name', 'email'],
|
|
152
153
|
});
|
|
153
154
|
});
|
|
155
|
+
it('propagates required into nested objects with optional fields', () => {
|
|
156
|
+
const t = tool('update_user')
|
|
157
|
+
.param('user', object({
|
|
158
|
+
name: string('Name'),
|
|
159
|
+
bio: optional(string('Bio')),
|
|
160
|
+
}))
|
|
161
|
+
.build();
|
|
162
|
+
const userProp = t.client?.input_schema.properties?.user;
|
|
163
|
+
expect(userProp.required).toEqual(['name']);
|
|
164
|
+
});
|
|
154
165
|
it('creates tool with array parameters', () => {
|
|
155
166
|
const t = tool('process_items')
|
|
156
167
|
.param('items', array(string('Item'), 'List of items'))
|
package/dist/types.d.ts
CHANGED
|
@@ -946,6 +946,14 @@ export interface PermissionModelDTO {
|
|
|
946
946
|
team?: TeamRelationDTO;
|
|
947
947
|
visibility: Visibility;
|
|
948
948
|
}
|
|
949
|
+
/**
|
|
950
|
+
* ResourceStatusDTO is a lightweight status-only response for polling transports.
|
|
951
|
+
*/
|
|
952
|
+
export interface ResourceStatusDTO {
|
|
953
|
+
id: string;
|
|
954
|
+
status: any;
|
|
955
|
+
updated_at: string;
|
|
956
|
+
}
|
|
949
957
|
export type ChatStatus = string;
|
|
950
958
|
export declare const ChatStatusBusy: ChatStatus;
|
|
951
959
|
export declare const ChatStatusIdle: ChatStatus;
|