@inferencesh/sdk 0.6.13 → 0.6.15
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 +1 -1
- package/dist/agent/actions.js +10 -0
- package/dist/agent/actions.test.js +104 -1
- package/dist/agent/api.test.js +21 -0
- package/dist/agent/provider.d.ts +1 -1
- package/dist/agent/provider.js +5 -5
- package/dist/agent/types.d.ts +3 -0
- package/dist/api/agents.test.js +120 -1
- package/dist/api/files.test.js +8 -0
- package/dist/api/search.test.js +11 -0
- package/dist/api/tasks.test.js +21 -0
- package/dist/http/poll.test.js +61 -0
- package/dist/http/streamable.test.js +100 -0
- package/dist/proxy/index.test.js +17 -0
- package/dist/stream.test.js +9 -0
- package/dist/types.d.ts +103 -16
- package/dist/types.js +22 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -677,7 +677,7 @@ import type {
|
|
|
677
677
|
|
|
678
678
|
- [documentation](https://inference.sh/docs) — getting started guides and api reference
|
|
679
679
|
- [blog](https://inference.sh/blog) — tutorials on ai agents, image generation, and more
|
|
680
|
-
- [app store](https://app.inference.sh) — browse
|
|
680
|
+
- [app store](https://app.inference.sh) — browse ai models
|
|
681
681
|
- [discord](https://discord.gg/inference) — community support
|
|
682
682
|
- [github](https://github.com/inference-sh) — open source projects
|
|
683
683
|
|
package/dist/agent/actions.js
CHANGED
|
@@ -18,6 +18,14 @@ const dispatchedToolInvocations = new Set();
|
|
|
18
18
|
// =============================================================================
|
|
19
19
|
export function createActions(ctx) {
|
|
20
20
|
const { client, dispatch, getConfig, getChatId, getClientToolHandlers, getStreamManager, setStreamManager, getStreamEnabled, getPollIntervalMs, callbacks } = ctx;
|
|
21
|
+
let prevChatWasBusy = false;
|
|
22
|
+
const checkTurnEnd = (chat) => {
|
|
23
|
+
const isBusy = chat.status === ChatStatusBusy;
|
|
24
|
+
if (prevChatWasBusy && !isBusy) {
|
|
25
|
+
callbacks.onTurnEnd?.(chat);
|
|
26
|
+
}
|
|
27
|
+
prevChatWasBusy = isBusy;
|
|
28
|
+
};
|
|
21
29
|
// =========================================================================
|
|
22
30
|
// Internal helpers
|
|
23
31
|
// =========================================================================
|
|
@@ -26,6 +34,7 @@ export function createActions(ctx) {
|
|
|
26
34
|
if (chat) {
|
|
27
35
|
const status = chat.status === ChatStatusBusy ? 'streaming' : 'idle';
|
|
28
36
|
callbacks.onStatusChange?.(status);
|
|
37
|
+
checkTurnEnd(chat);
|
|
29
38
|
}
|
|
30
39
|
};
|
|
31
40
|
const updateMessage = (message) => {
|
|
@@ -129,6 +138,7 @@ export function createActions(ctx) {
|
|
|
129
138
|
if (chatData) {
|
|
130
139
|
const status = chatData.status === ChatStatusBusy ? 'streaming' : 'idle';
|
|
131
140
|
callbacks.onStatusChange?.(status);
|
|
141
|
+
checkTurnEnd(chatData);
|
|
132
142
|
}
|
|
133
143
|
});
|
|
134
144
|
// Listen for ChatMessage updates
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChatStatusBusy, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, } from '../types';
|
|
2
|
-
import { createActions } from './actions';
|
|
2
|
+
import { createActions, getClientToolHandlers } from './actions';
|
|
3
3
|
import * as agentApi from './api';
|
|
4
4
|
import { PollManager } from '../http/poll';
|
|
5
5
|
import { StreamableManager } from '../http/streamable';
|
|
@@ -230,6 +230,35 @@ describe('createActions', () => {
|
|
|
230
230
|
expect(pollInstances[0].options.pollFunction).toBeDefined();
|
|
231
231
|
expect(pollInstances[0].start).toHaveBeenCalled();
|
|
232
232
|
});
|
|
233
|
+
it('should stop an existing stream manager before starting a new connection', async () => {
|
|
234
|
+
const { ctx, dispatch } = createTestContext();
|
|
235
|
+
const { internalActions } = createActions(ctx);
|
|
236
|
+
internalActions.streamChat('chat-full-id-123');
|
|
237
|
+
await Promise.resolve();
|
|
238
|
+
const firstManager = streamInstances[0];
|
|
239
|
+
internalActions.streamChat('chat-full-id-123');
|
|
240
|
+
await Promise.resolve();
|
|
241
|
+
expect(firstManager.stop).toHaveBeenCalled();
|
|
242
|
+
expect(streamInstances).toHaveLength(2);
|
|
243
|
+
expect(streamInstances[1].start).toHaveBeenCalled();
|
|
244
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
245
|
+
type: 'SET_CONNECTION_STATUS',
|
|
246
|
+
payload: 'connecting',
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
it('should dispatch streaming status when the stream manager starts', async () => {
|
|
250
|
+
const onStatusChange = jest.fn();
|
|
251
|
+
const { ctx, dispatch } = createTestContext({ callbacks: { onStatusChange } });
|
|
252
|
+
const { internalActions } = createActions(ctx);
|
|
253
|
+
internalActions.streamChat('chat-full-id-123');
|
|
254
|
+
await Promise.resolve();
|
|
255
|
+
streamInstances[0].options.onStart?.();
|
|
256
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
257
|
+
type: 'SET_CONNECTION_STATUS',
|
|
258
|
+
payload: 'streaming',
|
|
259
|
+
});
|
|
260
|
+
expect(onStatusChange).toHaveBeenCalledWith('streaming');
|
|
261
|
+
});
|
|
233
262
|
});
|
|
234
263
|
describe('stopStream', () => {
|
|
235
264
|
it('should clear the manager ref before stop so onEnd does not double-dispatch idle', async () => {
|
|
@@ -246,6 +275,19 @@ describe('createActions', () => {
|
|
|
246
275
|
// Only the explicit stopStream dispatch, not a second from onEnd
|
|
247
276
|
expect(idleDispatches).toHaveLength(1);
|
|
248
277
|
});
|
|
278
|
+
it('should reset to idle when the stream ends unexpectedly with manager still set', async () => {
|
|
279
|
+
const onStatusChange = jest.fn();
|
|
280
|
+
const { ctx, dispatch } = createTestContext({ callbacks: { onStatusChange } });
|
|
281
|
+
const { internalActions } = createActions(ctx);
|
|
282
|
+
internalActions.streamChat('chat-full-id-123');
|
|
283
|
+
await Promise.resolve();
|
|
284
|
+
streamInstances[0].options.onEnd?.();
|
|
285
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
286
|
+
type: 'SET_CONNECTION_STATUS',
|
|
287
|
+
payload: 'idle',
|
|
288
|
+
});
|
|
289
|
+
expect(onStatusChange).toHaveBeenCalledWith('idle');
|
|
290
|
+
});
|
|
249
291
|
it('should clear the manager ref before poll stop so onStop does not double-dispatch idle', async () => {
|
|
250
292
|
const onStatusChange = jest.fn();
|
|
251
293
|
const { ctx, dispatch, setStreamManager } = createTestContext({
|
|
@@ -264,6 +306,22 @@ describe('createActions', () => {
|
|
|
264
306
|
expect(idleDispatches).toHaveLength(1);
|
|
265
307
|
expect(onStatusChange).toHaveBeenCalledWith('idle');
|
|
266
308
|
});
|
|
309
|
+
it('should reset to idle when poll transport stops unexpectedly with manager still set', async () => {
|
|
310
|
+
const onStatusChange = jest.fn();
|
|
311
|
+
const { ctx, dispatch } = createTestContext({
|
|
312
|
+
getStreamEnabled: () => false,
|
|
313
|
+
callbacks: { onStatusChange },
|
|
314
|
+
});
|
|
315
|
+
const { internalActions } = createActions(ctx);
|
|
316
|
+
internalActions.streamChat('chat-full-id-123');
|
|
317
|
+
await Promise.resolve();
|
|
318
|
+
pollInstances[0].options.onStop?.();
|
|
319
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
320
|
+
type: 'SET_CONNECTION_STATUS',
|
|
321
|
+
payload: 'idle',
|
|
322
|
+
});
|
|
323
|
+
expect(onStatusChange).toHaveBeenCalledWith('idle');
|
|
324
|
+
});
|
|
267
325
|
});
|
|
268
326
|
describe('stream and poll error callbacks', () => {
|
|
269
327
|
it('should forward stream errors to onError callback', async () => {
|
|
@@ -382,6 +440,22 @@ describe('createActions', () => {
|
|
|
382
440
|
});
|
|
383
441
|
});
|
|
384
442
|
describe('pollChat', () => {
|
|
443
|
+
it('should dispatch streaming status when the poll manager starts', async () => {
|
|
444
|
+
const onStatusChange = jest.fn();
|
|
445
|
+
const { ctx, dispatch } = createTestContext({
|
|
446
|
+
getStreamEnabled: () => false,
|
|
447
|
+
callbacks: { onStatusChange },
|
|
448
|
+
});
|
|
449
|
+
const { internalActions } = createActions(ctx);
|
|
450
|
+
internalActions.streamChat('chat-full-id-123');
|
|
451
|
+
await Promise.resolve();
|
|
452
|
+
pollInstances[0].options.onStart?.();
|
|
453
|
+
expect(dispatch).toHaveBeenCalledWith({
|
|
454
|
+
type: 'SET_CONNECTION_STATUS',
|
|
455
|
+
payload: 'streaming',
|
|
456
|
+
});
|
|
457
|
+
expect(onStatusChange).toHaveBeenCalledWith('streaming');
|
|
458
|
+
});
|
|
385
459
|
it('should fetch full chat when poll status changes', async () => {
|
|
386
460
|
const { ctx: baseCtx } = createTestContext({ getStreamEnabled: () => false });
|
|
387
461
|
const { ctx } = createTestContext({
|
|
@@ -715,3 +789,32 @@ describe('createActions', () => {
|
|
|
715
789
|
});
|
|
716
790
|
});
|
|
717
791
|
});
|
|
792
|
+
describe('getClientToolHandlers', () => {
|
|
793
|
+
it('returns an empty map when config is null', () => {
|
|
794
|
+
expect(getClientToolHandlers(null)).toEqual(new Map());
|
|
795
|
+
});
|
|
796
|
+
it('returns an empty map for template agent refs', () => {
|
|
797
|
+
expect(getClientToolHandlers({ agent: 'inference/my-agent' })).toEqual(new Map());
|
|
798
|
+
});
|
|
799
|
+
it('returns an empty map when ad-hoc config has no tools', () => {
|
|
800
|
+
expect(getClientToolHandlers({
|
|
801
|
+
core_app: { ref: 'openrouter/claude@abc' },
|
|
802
|
+
system_prompt: 'test',
|
|
803
|
+
})).toEqual(new Map());
|
|
804
|
+
});
|
|
805
|
+
it('extracts client tool handlers from ad-hoc config tools', () => {
|
|
806
|
+
const handler = jest.fn();
|
|
807
|
+
const map = getClientToolHandlers({
|
|
808
|
+
core_app: { ref: 'openrouter/claude@abc' },
|
|
809
|
+
system_prompt: 'test',
|
|
810
|
+
tools: [
|
|
811
|
+
{
|
|
812
|
+
schema: { name: 'browser', type: ToolTypeClient, description: 'browse' },
|
|
813
|
+
handler,
|
|
814
|
+
},
|
|
815
|
+
],
|
|
816
|
+
});
|
|
817
|
+
expect(map.size).toBe(1);
|
|
818
|
+
expect(map.get('browser')).toBe(handler);
|
|
819
|
+
});
|
|
820
|
+
});
|
package/dist/agent/api.test.js
CHANGED
|
@@ -125,6 +125,15 @@ describe('agent/api', () => {
|
|
|
125
125
|
const body = JSON.parse(String(runInit.body));
|
|
126
126
|
expect(body.input.attachments).toEqual([fileRecord]);
|
|
127
127
|
});
|
|
128
|
+
it('should route template agent configs to /agents/run without agent_config', async () => {
|
|
129
|
+
mockJsonResponse(runResponse);
|
|
130
|
+
await sendMessage(makeClient(), { agent: 'agent-template-1' }, 'chat-existing', 'hi');
|
|
131
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
132
|
+
const body = JSON.parse(String(init.body));
|
|
133
|
+
expect(body.agent).toBe('agent-template-1');
|
|
134
|
+
expect(body.chat_id).toBe('chat-existing');
|
|
135
|
+
expect(body).not.toHaveProperty('agent_config');
|
|
136
|
+
});
|
|
128
137
|
});
|
|
129
138
|
describe('submitToolResult', () => {
|
|
130
139
|
it('should wrap string results in { result }', async () => {
|
|
@@ -143,6 +152,10 @@ describe('agent/api', () => {
|
|
|
143
152
|
const [, init] = mockFetch.mock.calls[0];
|
|
144
153
|
expect(JSON.parse(String(init.body))).toEqual(payload);
|
|
145
154
|
});
|
|
155
|
+
it('should rethrow when the request fails', async () => {
|
|
156
|
+
mockFetch.mockRejectedValueOnce(new Error('submit failed'));
|
|
157
|
+
await expect(submitToolResult(makeClient(), 'inv-3', 'done')).rejects.toThrow('submit failed');
|
|
158
|
+
});
|
|
146
159
|
});
|
|
147
160
|
describe('fetchChat', () => {
|
|
148
161
|
it('should return chat data on success', async () => {
|
|
@@ -196,6 +209,14 @@ describe('agent/api', () => {
|
|
|
196
209
|
mockFetch.mockRejectedValueOnce(new Error('approve failed'));
|
|
197
210
|
await expect(approveTool(makeClient(), 'inv-1')).rejects.toThrow('approve failed');
|
|
198
211
|
});
|
|
212
|
+
it('should rethrow when rejectTool request fails', async () => {
|
|
213
|
+
mockFetch.mockRejectedValueOnce(new Error('reject failed'));
|
|
214
|
+
await expect(rejectTool(makeClient(), 'inv-1', 'unsafe')).rejects.toThrow('reject failed');
|
|
215
|
+
});
|
|
216
|
+
it('should rethrow when alwaysAllowTool request fails', async () => {
|
|
217
|
+
mockFetch.mockRejectedValueOnce(new Error('allow failed'));
|
|
218
|
+
await expect(alwaysAllowTool(makeClient(), 'chat-1', 'inv-1', 'browser_tool')).rejects.toThrow('allow failed');
|
|
219
|
+
});
|
|
199
220
|
});
|
|
200
221
|
describe('getChatStreamConfig', () => {
|
|
201
222
|
it('should delegate to HttpClient.getStreamableConfig for the chat stream path', () => {
|
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, stream, pollIntervalMs, children, }: AgentChatProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
export declare function AgentChatProvider({ client, agentConfig, chatId, clientToolHandlers: extraHandlers, onChatCreated, onStatusChange, onError, onTurnEnd, 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,7 +39,7 @@ function mergeHandlers(base, extra) {
|
|
|
39
39
|
* }
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandlers: extraHandlers, onChatCreated, onStatusChange, onError, stream, pollIntervalMs, children, }) {
|
|
42
|
+
export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandlers: extraHandlers, onChatCreated, onStatusChange, onError, onTurnEnd, 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
|
|
@@ -49,15 +49,15 @@ export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandl
|
|
|
49
49
|
const streamRef = useRef(stream);
|
|
50
50
|
const pollIntervalMsRef = useRef(pollIntervalMs);
|
|
51
51
|
const clientToolHandlersRef = useRef(mergeHandlers(getClientToolHandlers(agentConfig), extraHandlers));
|
|
52
|
-
const callbacksRef = useRef({ onChatCreated, onStatusChange, onError });
|
|
52
|
+
const callbacksRef = useRef({ onChatCreated, onStatusChange, onError, onTurnEnd });
|
|
53
53
|
// Keep refs in sync with props
|
|
54
54
|
useEffect(() => {
|
|
55
55
|
configRef.current = agentConfig;
|
|
56
56
|
clientToolHandlersRef.current = mergeHandlers(getClientToolHandlers(agentConfig), extraHandlers);
|
|
57
57
|
}, [agentConfig, extraHandlers]);
|
|
58
58
|
useEffect(() => {
|
|
59
|
-
callbacksRef.current = { onChatCreated, onStatusChange, onError };
|
|
60
|
-
}, [onChatCreated, onStatusChange, onError]);
|
|
59
|
+
callbacksRef.current = { onChatCreated, onStatusChange, onError, onTurnEnd };
|
|
60
|
+
}, [onChatCreated, onStatusChange, onError, onTurnEnd]);
|
|
61
61
|
useEffect(() => {
|
|
62
62
|
streamRef.current = stream;
|
|
63
63
|
pollIntervalMsRef.current = pollIntervalMs;
|
|
@@ -82,7 +82,7 @@ export function AgentChatProvider({ client, agentConfig, chatId, clientToolHandl
|
|
|
82
82
|
// Re-bind callbacks when they change
|
|
83
83
|
useEffect(() => {
|
|
84
84
|
actionsContext.callbacks = callbacksRef.current;
|
|
85
|
-
}, [actionsContext, onChatCreated, onStatusChange, onError]);
|
|
85
|
+
}, [actionsContext, onChatCreated, onStatusChange, onError, onTurnEnd]);
|
|
86
86
|
const actionsResultRef = useRef(null);
|
|
87
87
|
if (!actionsResultRef.current) {
|
|
88
88
|
actionsResultRef.current = createActions(actionsContext);
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -135,6 +135,8 @@ export interface AgentChatProviderProps {
|
|
|
135
135
|
onStatusChange?: (status: ChatStatus) => void;
|
|
136
136
|
/** Callback when an error occurs */
|
|
137
137
|
onError?: (error: Error) => void;
|
|
138
|
+
/** Callback when the agent's turn ends (chat transitions from busy to idle/completed) */
|
|
139
|
+
onTurnEnd?: (chat: ChatDTO) => void;
|
|
138
140
|
/** Use SSE streaming (true, default) or polling (false) for real-time updates */
|
|
139
141
|
stream?: boolean;
|
|
140
142
|
/** Polling interval in ms when stream is false (default: 2000) */
|
|
@@ -216,6 +218,7 @@ export interface ActionsContext {
|
|
|
216
218
|
onChatCreated?: (chatId: string) => void;
|
|
217
219
|
onStatusChange?: (status: ChatStatus) => void;
|
|
218
220
|
onError?: (error: Error) => void;
|
|
221
|
+
onTurnEnd?: (chat: ChatDTO) => void;
|
|
219
222
|
};
|
|
220
223
|
}
|
|
221
224
|
/**
|
package/dist/api/agents.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HttpClient } from '../http/client';
|
|
2
2
|
import { StreamableManager } from '../http/streamable';
|
|
3
3
|
import { PollManager } from '../http/poll';
|
|
4
|
-
import { ChatStatusBusy, ChatStatusIdle, ToolInvocationStatusAwaitingInput, ToolTypeClient, } from '../types';
|
|
4
|
+
import { ChatStatusBusy, ChatStatusIdle, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, ToolTypeClient, } from '../types';
|
|
5
5
|
import { FilesAPI } from './files';
|
|
6
6
|
import { AgentsAPI } from './agents';
|
|
7
7
|
const mockFetch = jest.fn();
|
|
@@ -97,6 +97,40 @@ describe('Agent.sendMessage (polling mode)', () => {
|
|
|
97
97
|
init.method === 'GET');
|
|
98
98
|
expect(fullChatGets).toHaveLength(2);
|
|
99
99
|
});
|
|
100
|
+
it('should dispatch onToolCall for in_progress client tool invocations', async () => {
|
|
101
|
+
const toolInvocation = {
|
|
102
|
+
id: 'tool-inv-progress',
|
|
103
|
+
type: ToolTypeClient,
|
|
104
|
+
status: ToolInvocationStatusInProgress,
|
|
105
|
+
function: { name: 'my_tool', arguments: { x: 2 } },
|
|
106
|
+
};
|
|
107
|
+
const messageWithTool = makeMessage({ tool_invocations: [toolInvocation] });
|
|
108
|
+
mockJsonResponse({
|
|
109
|
+
user_message: makeMessage({ id: 'user-1', role: 'user' }),
|
|
110
|
+
assistant_message: makeMessage(),
|
|
111
|
+
});
|
|
112
|
+
mockJsonResponse({ status: ChatStatusBusy });
|
|
113
|
+
mockJsonResponse({
|
|
114
|
+
id: 'chat-1',
|
|
115
|
+
status: ChatStatusBusy,
|
|
116
|
+
chat_messages: [messageWithTool],
|
|
117
|
+
});
|
|
118
|
+
mockJsonResponse({ status: ChatStatusIdle });
|
|
119
|
+
mockJsonResponse({
|
|
120
|
+
id: 'chat-1',
|
|
121
|
+
status: ChatStatusIdle,
|
|
122
|
+
chat_messages: [messageWithTool],
|
|
123
|
+
});
|
|
124
|
+
const onMessage = jest.fn();
|
|
125
|
+
const onToolCall = jest.fn();
|
|
126
|
+
await agent().sendMessage('run tool', { stream: false, onMessage, onToolCall });
|
|
127
|
+
expect(onToolCall).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(onToolCall).toHaveBeenCalledWith({
|
|
129
|
+
id: 'tool-inv-progress',
|
|
130
|
+
name: 'my_tool',
|
|
131
|
+
args: { x: 2 },
|
|
132
|
+
});
|
|
133
|
+
});
|
|
100
134
|
it('should dispatch onToolCall once per client tool invocation', async () => {
|
|
101
135
|
const toolInvocation = {
|
|
102
136
|
id: 'tool-inv-1',
|
|
@@ -188,6 +222,30 @@ describe('Agent.sendMessage (polling mode)', () => {
|
|
|
188
222
|
const output = await agent().run('no finish tool');
|
|
189
223
|
expect(output).toBeNull();
|
|
190
224
|
});
|
|
225
|
+
it('should warn on repeated poll errors without resolving sendMessage', async () => {
|
|
226
|
+
jest.useFakeTimers();
|
|
227
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
228
|
+
mockJsonResponse({
|
|
229
|
+
user_message: makeMessage({ id: 'user-1', role: 'user' }),
|
|
230
|
+
assistant_message: makeMessage(),
|
|
231
|
+
});
|
|
232
|
+
mockFetch.mockRejectedValue(new Error('status endpoint down'));
|
|
233
|
+
const sendPromise = agent().sendMessage('hello', { stream: false });
|
|
234
|
+
for (let i = 0; i < 6; i++) {
|
|
235
|
+
await Promise.resolve();
|
|
236
|
+
jest.advanceTimersByTime(20);
|
|
237
|
+
await Promise.resolve();
|
|
238
|
+
}
|
|
239
|
+
expect(warnSpy).toHaveBeenCalledWith('[Agent] Poll error:', expect.any(Error));
|
|
240
|
+
let settled = false;
|
|
241
|
+
sendPromise.then(() => {
|
|
242
|
+
settled = true;
|
|
243
|
+
});
|
|
244
|
+
await Promise.resolve();
|
|
245
|
+
expect(settled).toBe(false);
|
|
246
|
+
warnSpy.mockRestore();
|
|
247
|
+
jest.useRealTimers();
|
|
248
|
+
});
|
|
191
249
|
});
|
|
192
250
|
describe('Agent.sendMessage (streaming mode)', () => {
|
|
193
251
|
beforeEach(() => {
|
|
@@ -220,6 +278,39 @@ describe('Agent.sendMessage (streaming mode)', () => {
|
|
|
220
278
|
expect(result.userMessage).toEqual(userMessage);
|
|
221
279
|
expect(onChat).toHaveBeenCalledWith(expect.objectContaining({ id: 'chat-1', status: ChatStatusIdle }));
|
|
222
280
|
});
|
|
281
|
+
it('should dispatch onToolCall for in_progress tools from chat_messages stream events', async () => {
|
|
282
|
+
const toolInvocation = {
|
|
283
|
+
id: 'tool-inv-progress',
|
|
284
|
+
type: ToolTypeClient,
|
|
285
|
+
status: ToolInvocationStatusInProgress,
|
|
286
|
+
function: { name: 'my_tool', arguments: { x: 2 } },
|
|
287
|
+
};
|
|
288
|
+
const messageWithTool = makeMessage({ tool_invocations: [toolInvocation] });
|
|
289
|
+
mockFetch.mockImplementation((url) => {
|
|
290
|
+
if (url.includes('/agents/run')) {
|
|
291
|
+
return Promise.resolve({
|
|
292
|
+
ok: true,
|
|
293
|
+
status: 200,
|
|
294
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
295
|
+
user_message: makeMessage({ id: 'user-1', role: 'user' }),
|
|
296
|
+
assistant_message: makeMessage(),
|
|
297
|
+
})),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return Promise.resolve(mockNdjsonStream([
|
|
301
|
+
`${JSON.stringify({ event: 'chat_messages', data: messageWithTool })}\n`,
|
|
302
|
+
`${JSON.stringify({ event: 'chats', data: { id: 'chat-1', status: ChatStatusIdle } })}\n`,
|
|
303
|
+
]));
|
|
304
|
+
});
|
|
305
|
+
const onToolCall = jest.fn();
|
|
306
|
+
await streamingAgent().sendMessage('run tool', { onToolCall });
|
|
307
|
+
expect(onToolCall).toHaveBeenCalledTimes(1);
|
|
308
|
+
expect(onToolCall).toHaveBeenCalledWith({
|
|
309
|
+
id: 'tool-inv-progress',
|
|
310
|
+
name: 'my_tool',
|
|
311
|
+
args: { x: 2 },
|
|
312
|
+
});
|
|
313
|
+
});
|
|
223
314
|
it('should dispatch onToolCall from chat_messages stream events', async () => {
|
|
224
315
|
const toolInvocation = {
|
|
225
316
|
id: 'tool-inv-1',
|
|
@@ -714,6 +805,20 @@ describe('Agent.getChat', () => {
|
|
|
714
805
|
expect(url).toContain('/chats/chat-42');
|
|
715
806
|
expect(init.method).toBe('GET');
|
|
716
807
|
});
|
|
808
|
+
it('should expose currentChatId after sendMessage establishes a chat', async () => {
|
|
809
|
+
const agentInstance = agent();
|
|
810
|
+
mockJsonResponse({
|
|
811
|
+
user_message: makeMessage({ id: 'user-1', role: 'user' }),
|
|
812
|
+
assistant_message: makeMessage(),
|
|
813
|
+
});
|
|
814
|
+
mockJsonResponse({ status: ChatStatusBusy });
|
|
815
|
+
mockJsonResponse({ id: 'chat-1', status: ChatStatusBusy, chat_messages: [] });
|
|
816
|
+
mockJsonResponse({ status: ChatStatusIdle });
|
|
817
|
+
mockJsonResponse({ id: 'chat-1', status: ChatStatusIdle, chat_messages: [] });
|
|
818
|
+
expect(agentInstance.currentChatId).toBeNull();
|
|
819
|
+
await agentInstance.sendMessage('hello', { stream: false });
|
|
820
|
+
expect(agentInstance.currentChatId).toBe('chat-1');
|
|
821
|
+
});
|
|
717
822
|
it('should GET /chats/{id} with established chat id when chatId is omitted', async () => {
|
|
718
823
|
const agentInstance = agent();
|
|
719
824
|
mockJsonResponse({
|
|
@@ -797,6 +902,20 @@ describe('AgentsAPI (template CRUD)', () => {
|
|
|
797
902
|
expect(url).toContain('/agents/internal-tools');
|
|
798
903
|
expect(init.method).toBe('GET');
|
|
799
904
|
});
|
|
905
|
+
it('should GET /agents/{id}/card for getA2ACard()', async () => {
|
|
906
|
+
const card = {
|
|
907
|
+
name: 'support-bot',
|
|
908
|
+
description: 'Customer support agent',
|
|
909
|
+
url: 'https://api.example.com/agents/support-bot',
|
|
910
|
+
version: '1.0.0',
|
|
911
|
+
};
|
|
912
|
+
mockJsonResponse(card);
|
|
913
|
+
const result = await api().getA2ACard('agent-1');
|
|
914
|
+
expect(result).toEqual(card);
|
|
915
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
916
|
+
expect(url).toContain('/agents/agent-1/card');
|
|
917
|
+
expect(init.method).toBe('GET');
|
|
918
|
+
});
|
|
800
919
|
it('should POST team_id for transferOwnership()', async () => {
|
|
801
920
|
const agent = { id: 'agent-1' };
|
|
802
921
|
mockJsonResponse(agent);
|
package/dist/api/files.test.js
CHANGED
|
@@ -41,6 +41,14 @@ describe('FilesAPI', () => {
|
|
|
41
41
|
expect(init.method).toBe('DELETE');
|
|
42
42
|
});
|
|
43
43
|
describe('processInput', () => {
|
|
44
|
+
it('should return falsy inputs unchanged without uploading', async () => {
|
|
45
|
+
await expect(api().processInput(null)).resolves.toBeNull();
|
|
46
|
+
await expect(api().processInput(undefined)).resolves.toBeUndefined();
|
|
47
|
+
await expect(api().processInput(0)).resolves.toBe(0);
|
|
48
|
+
await expect(api().processInput(false)).resolves.toBe(false);
|
|
49
|
+
await expect(api().processInput('')).resolves.toBe('');
|
|
50
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
44
52
|
it('should not treat short plain strings as base64 file uploads', async () => {
|
|
45
53
|
const result = await api().processInput({ key: 'key1', note: 'hello' });
|
|
46
54
|
expect(result).toEqual({ key: 'key1', note: 'hello' });
|
package/dist/api/search.test.js
CHANGED
|
@@ -25,6 +25,17 @@ describe('SearchAPI', () => {
|
|
|
25
25
|
expect(init.method).toBe('POST');
|
|
26
26
|
expect(JSON.parse(init.body)).toEqual(payload);
|
|
27
27
|
});
|
|
28
|
+
it('should forward conversation context in suggest() body', async () => {
|
|
29
|
+
const payload = {
|
|
30
|
+
query: 'flux',
|
|
31
|
+
context: 'previous conversation about image generation',
|
|
32
|
+
limit: 5,
|
|
33
|
+
};
|
|
34
|
+
mockJsonResponse({ results: [] });
|
|
35
|
+
await api().suggest(payload);
|
|
36
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
37
|
+
expect(JSON.parse(init.body)).toEqual(payload);
|
|
38
|
+
});
|
|
28
39
|
it('should POST /search for search()', async () => {
|
|
29
40
|
const payload = { q: 'claude', type: 'apps', limit: 5 };
|
|
30
41
|
const response = { hits: [{ id: 'app-1' }] };
|
package/dist/api/tasks.test.js
CHANGED
|
@@ -217,6 +217,27 @@ describe('TasksAPI.run (streaming mode)', () => {
|
|
|
217
217
|
expect(result.status).toBe(TaskStatusCompleted);
|
|
218
218
|
expect(onPartialUpdate).toHaveBeenCalledWith(expect.objectContaining({ id: 'task-1', status: TaskStatusCompleted }), ['status']);
|
|
219
219
|
});
|
|
220
|
+
it('should preserve session_id across partial stream updates that omit it', async () => {
|
|
221
|
+
setupStreamMocks([
|
|
222
|
+
`${JSON.stringify({
|
|
223
|
+
data: { status: TaskStatusRunning, id: 'task-1' },
|
|
224
|
+
fields: ['status'],
|
|
225
|
+
})}\n`,
|
|
226
|
+
`${JSON.stringify({
|
|
227
|
+
data: { status: TaskStatusCompleted, id: 'task-1', output: { ok: true } },
|
|
228
|
+
fields: ['status', 'output'],
|
|
229
|
+
})}\n`,
|
|
230
|
+
], makeTask({ session_id: 'sess-persist' }));
|
|
231
|
+
const onPartialUpdate = jest.fn();
|
|
232
|
+
const result = await api().run({ app: 'test-app', input: {} }, {}, { wait: true, onPartialUpdate });
|
|
233
|
+
expect(result.session_id).toBe('sess-persist');
|
|
234
|
+
expect(onPartialUpdate).toHaveBeenCalledWith(expect.objectContaining({ session_id: 'sess-persist', status: TaskStatusRunning }), ['status']);
|
|
235
|
+
expect(onPartialUpdate).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
236
|
+
session_id: 'sess-persist',
|
|
237
|
+
status: TaskStatusCompleted,
|
|
238
|
+
output: { ok: true },
|
|
239
|
+
}), ['status', 'output']);
|
|
240
|
+
});
|
|
220
241
|
});
|
|
221
242
|
describe('TasksAPI.run (HTTP contract)', () => {
|
|
222
243
|
beforeEach(() => {
|
package/dist/http/poll.test.js
CHANGED
|
@@ -82,4 +82,65 @@ describe('PollManager', () => {
|
|
|
82
82
|
await Promise.resolve();
|
|
83
83
|
expect(onData.mock.calls.length).toBe(callsBefore);
|
|
84
84
|
});
|
|
85
|
+
it('should not run overlapping polls when pollFunction is still in flight', async () => {
|
|
86
|
+
let resolvePoll = () => { };
|
|
87
|
+
const pollFunction = jest.fn().mockImplementation(() => new Promise((resolve) => {
|
|
88
|
+
resolvePoll = resolve;
|
|
89
|
+
}));
|
|
90
|
+
const manager = new PollManager({
|
|
91
|
+
pollFunction,
|
|
92
|
+
intervalMs: 100,
|
|
93
|
+
});
|
|
94
|
+
manager.start();
|
|
95
|
+
await Promise.resolve();
|
|
96
|
+
expect(pollFunction).toHaveBeenCalledTimes(1);
|
|
97
|
+
jest.advanceTimersByTime(100);
|
|
98
|
+
await Promise.resolve();
|
|
99
|
+
expect(pollFunction).toHaveBeenCalledTimes(1);
|
|
100
|
+
resolvePoll({ status: 'ok' });
|
|
101
|
+
await Promise.resolve();
|
|
102
|
+
jest.advanceTimersByTime(100);
|
|
103
|
+
await Promise.resolve();
|
|
104
|
+
expect(pollFunction).toHaveBeenCalledTimes(2);
|
|
105
|
+
manager.stop();
|
|
106
|
+
});
|
|
107
|
+
it('should ignore start() when polling is already active', async () => {
|
|
108
|
+
const onStart = jest.fn();
|
|
109
|
+
const pollFunction = jest.fn().mockResolvedValue({});
|
|
110
|
+
const manager = new PollManager({ pollFunction, onStart });
|
|
111
|
+
manager.start();
|
|
112
|
+
manager.start();
|
|
113
|
+
expect(onStart).toHaveBeenCalledTimes(1);
|
|
114
|
+
manager.stop();
|
|
115
|
+
});
|
|
116
|
+
it('should reset consecutive error count after a successful poll', async () => {
|
|
117
|
+
const onError = jest.fn();
|
|
118
|
+
const onStop = jest.fn();
|
|
119
|
+
const pollFunction = jest
|
|
120
|
+
.fn()
|
|
121
|
+
.mockRejectedValueOnce(new Error('fail 1'))
|
|
122
|
+
.mockResolvedValueOnce({ status: 'ok' })
|
|
123
|
+
.mockRejectedValueOnce(new Error('fail 2'))
|
|
124
|
+
.mockRejectedValueOnce(new Error('fail 3'));
|
|
125
|
+
const manager = new PollManager({
|
|
126
|
+
pollFunction,
|
|
127
|
+
intervalMs: 100,
|
|
128
|
+
maxRetries: 2,
|
|
129
|
+
onError,
|
|
130
|
+
onStop,
|
|
131
|
+
});
|
|
132
|
+
manager.start();
|
|
133
|
+
await Promise.resolve();
|
|
134
|
+
jest.advanceTimersByTime(100);
|
|
135
|
+
await Promise.resolve();
|
|
136
|
+
expect(onStop).not.toHaveBeenCalled();
|
|
137
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
138
|
+
jest.advanceTimersByTime(100);
|
|
139
|
+
await Promise.resolve();
|
|
140
|
+
jest.advanceTimersByTime(100);
|
|
141
|
+
await Promise.resolve();
|
|
142
|
+
expect(onStop).toHaveBeenCalled();
|
|
143
|
+
expect(onError).toHaveBeenCalledTimes(3);
|
|
144
|
+
manager.stop();
|
|
145
|
+
});
|
|
85
146
|
});
|
|
@@ -152,6 +152,18 @@ describe('streamable', () => {
|
|
|
152
152
|
}
|
|
153
153
|
}).rejects.toThrow('HTTP 401: Unauthorized');
|
|
154
154
|
});
|
|
155
|
+
it('should throw when the response has no body', async () => {
|
|
156
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
157
|
+
ok: true,
|
|
158
|
+
status: 200,
|
|
159
|
+
body: null,
|
|
160
|
+
});
|
|
161
|
+
await expect(async () => {
|
|
162
|
+
for await (const _ of streamable('http://test.com/stream')) {
|
|
163
|
+
// consume
|
|
164
|
+
}
|
|
165
|
+
}).rejects.toThrow('No response body');
|
|
166
|
+
});
|
|
155
167
|
// Edge cases for chunking/buffering
|
|
156
168
|
describe('chunking edge cases', () => {
|
|
157
169
|
it('should handle message split across many chunks', async () => {
|
|
@@ -379,6 +391,68 @@ describe('StreamableManager', () => {
|
|
|
379
391
|
await manager.start();
|
|
380
392
|
expect(events).toEqual(['start', 'data', 'end']);
|
|
381
393
|
});
|
|
394
|
+
it('should dispatch typed events to addEventListener subscribers', async () => {
|
|
395
|
+
global.fetch = mockFetch([
|
|
396
|
+
'{"event":"chats","data":{"id":"chat-1","status":"busy"}}\n',
|
|
397
|
+
'{"event":"chat_messages","data":{"id":"msg-1","content":"hi"}}\n',
|
|
398
|
+
]);
|
|
399
|
+
const onChat = jest.fn();
|
|
400
|
+
const onMessage = jest.fn();
|
|
401
|
+
const manager = new StreamableManager({
|
|
402
|
+
url: 'http://test.com/stream',
|
|
403
|
+
onData: jest.fn(),
|
|
404
|
+
});
|
|
405
|
+
manager.addEventListener('chats', onChat);
|
|
406
|
+
manager.addEventListener('chat_messages', onMessage);
|
|
407
|
+
await manager.start();
|
|
408
|
+
expect(onChat).toHaveBeenCalledWith({ id: 'chat-1', status: 'busy' });
|
|
409
|
+
expect(onMessage).toHaveBeenCalledWith({ id: 'msg-1', content: 'hi' });
|
|
410
|
+
});
|
|
411
|
+
it('should stop dispatching to an unsubscribed listener', async () => {
|
|
412
|
+
global.fetch = mockFetch([
|
|
413
|
+
'{"event":"chats","data":{"id":"chat-1"}}\n',
|
|
414
|
+
]);
|
|
415
|
+
const removedListener = jest.fn();
|
|
416
|
+
const activeListener = jest.fn();
|
|
417
|
+
const manager = new StreamableManager({
|
|
418
|
+
url: 'http://test.com/stream',
|
|
419
|
+
});
|
|
420
|
+
const unsubscribe = manager.addEventListener('chats', removedListener);
|
|
421
|
+
manager.addEventListener('chats', activeListener);
|
|
422
|
+
unsubscribe();
|
|
423
|
+
await manager.start();
|
|
424
|
+
expect(removedListener).not.toHaveBeenCalled();
|
|
425
|
+
expect(activeListener).toHaveBeenCalledWith({ id: 'chat-1' });
|
|
426
|
+
});
|
|
427
|
+
it('should ignore start() when a stream is already running', async () => {
|
|
428
|
+
let readCount = 0;
|
|
429
|
+
const mockReader = {
|
|
430
|
+
read: jest.fn().mockImplementation(async () => {
|
|
431
|
+
readCount++;
|
|
432
|
+
if (readCount > 3) {
|
|
433
|
+
return { done: true, value: undefined };
|
|
434
|
+
}
|
|
435
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
436
|
+
return { done: false, value: new TextEncoder().encode('{"id":1}\n') };
|
|
437
|
+
}),
|
|
438
|
+
releaseLock: jest.fn(),
|
|
439
|
+
};
|
|
440
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
441
|
+
ok: true,
|
|
442
|
+
body: { getReader: () => mockReader },
|
|
443
|
+
});
|
|
444
|
+
const onStart = jest.fn();
|
|
445
|
+
const manager = new StreamableManager({
|
|
446
|
+
url: 'http://test.com/stream',
|
|
447
|
+
onStart,
|
|
448
|
+
onData: () => { },
|
|
449
|
+
});
|
|
450
|
+
const firstStart = manager.start();
|
|
451
|
+
await manager.start();
|
|
452
|
+
await firstStart;
|
|
453
|
+
expect(onStart).toHaveBeenCalledTimes(1);
|
|
454
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
455
|
+
});
|
|
382
456
|
it('should handle stop()', async () => {
|
|
383
457
|
let readCount = 0;
|
|
384
458
|
const mockReader = {
|
|
@@ -407,4 +481,30 @@ describe('StreamableManager', () => {
|
|
|
407
481
|
// Should have stopped before reading all 10
|
|
408
482
|
expect(readCount).toBeLessThan(10);
|
|
409
483
|
});
|
|
484
|
+
it('should call onError when the stream request fails', async () => {
|
|
485
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
486
|
+
ok: false,
|
|
487
|
+
status: 503,
|
|
488
|
+
text: async () => 'Service unavailable',
|
|
489
|
+
});
|
|
490
|
+
const onError = jest.fn();
|
|
491
|
+
const manager = new StreamableManager({
|
|
492
|
+
url: 'http://test.com/stream',
|
|
493
|
+
onError,
|
|
494
|
+
});
|
|
495
|
+
await manager.start();
|
|
496
|
+
expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'HTTP 503: Service unavailable' }));
|
|
497
|
+
});
|
|
498
|
+
it('should route partial updates to onData when onPartialData is not provided', async () => {
|
|
499
|
+
global.fetch = mockFetch([
|
|
500
|
+
'{"data":{"id":1,"status":"busy"},"fields":["status"]}\n',
|
|
501
|
+
]);
|
|
502
|
+
const onData = jest.fn();
|
|
503
|
+
const manager = new StreamableManager({
|
|
504
|
+
url: 'http://test.com/stream',
|
|
505
|
+
onData,
|
|
506
|
+
});
|
|
507
|
+
await manager.start();
|
|
508
|
+
expect(onData).toHaveBeenCalledWith({ id: 1, status: 'busy' });
|
|
509
|
+
});
|
|
410
510
|
});
|
package/dist/proxy/index.test.js
CHANGED
|
@@ -106,6 +106,23 @@ describe('processProxyRequest', () => {
|
|
|
106
106
|
}),
|
|
107
107
|
}));
|
|
108
108
|
});
|
|
109
|
+
it('should prefer client Authorization header over env API key', async () => {
|
|
110
|
+
const target = 'https://api.inference.sh/v1/tasks/1';
|
|
111
|
+
await processProxyRequest(createTestAdapter({
|
|
112
|
+
header: (name) => {
|
|
113
|
+
if (name === INF_TARGET_HEADER)
|
|
114
|
+
return target;
|
|
115
|
+
if (name === 'authorization')
|
|
116
|
+
return 'Bearer client-session-token';
|
|
117
|
+
return undefined;
|
|
118
|
+
},
|
|
119
|
+
}));
|
|
120
|
+
expect(global.fetch).toHaveBeenCalledWith(target, expect.objectContaining({
|
|
121
|
+
headers: expect.objectContaining({
|
|
122
|
+
authorization: 'Bearer client-session-token',
|
|
123
|
+
}),
|
|
124
|
+
}));
|
|
125
|
+
});
|
|
109
126
|
it('should use query param fallback when header is missing (SSE clients)', async () => {
|
|
110
127
|
const target = 'https://api.inference.sh/v1/stream';
|
|
111
128
|
const encoded = encodeURIComponent(target);
|
package/dist/stream.test.js
CHANGED
|
@@ -34,6 +34,15 @@ describe('StreamManager', () => {
|
|
|
34
34
|
await manager.connect();
|
|
35
35
|
expect(onStart).toHaveBeenCalled();
|
|
36
36
|
});
|
|
37
|
+
it('should not call onStart when createEventSource returns null', async () => {
|
|
38
|
+
const onStart = jest.fn();
|
|
39
|
+
const manager = new StreamManager({
|
|
40
|
+
createEventSource: async () => null,
|
|
41
|
+
onStart,
|
|
42
|
+
});
|
|
43
|
+
await manager.connect();
|
|
44
|
+
expect(onStart).not.toHaveBeenCalled();
|
|
45
|
+
});
|
|
37
46
|
it('should parse JSON messages and call onData', async () => {
|
|
38
47
|
const onData = jest.fn();
|
|
39
48
|
const manager = new StreamManager({
|
package/dist/types.d.ts
CHANGED
|
@@ -506,6 +506,42 @@ export interface CreateApiKeyRequest {
|
|
|
506
506
|
expires_at?: string;
|
|
507
507
|
scopes?: string[];
|
|
508
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* EstimateCostRequest is the request for POST /store/apps/{appId}/estimate.
|
|
511
|
+
*/
|
|
512
|
+
export interface EstimateCostRequest {
|
|
513
|
+
input: {
|
|
514
|
+
[key: string]: any;
|
|
515
|
+
};
|
|
516
|
+
function?: string;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* EstimateCostResponse is the response from the cost estimation endpoint.
|
|
520
|
+
*/
|
|
521
|
+
export interface EstimateCostResponse {
|
|
522
|
+
/**
|
|
523
|
+
* Confidence: "exact" (all fees input-based), "range" (estimate expression),
|
|
524
|
+
* or "unknown" (output-dependent, no estimate expression).
|
|
525
|
+
*/
|
|
526
|
+
confidence: string;
|
|
527
|
+
/**
|
|
528
|
+
* Microcents is set when confidence is "exact".
|
|
529
|
+
*/
|
|
530
|
+
microcents?: number;
|
|
531
|
+
/**
|
|
532
|
+
* Min/Max are set when confidence is "range".
|
|
533
|
+
*/
|
|
534
|
+
min?: number;
|
|
535
|
+
max?: number;
|
|
536
|
+
/**
|
|
537
|
+
* DependsOn lists post-execution variables the pricing needs (when not exact).
|
|
538
|
+
*/
|
|
539
|
+
depends_on?: string[];
|
|
540
|
+
/**
|
|
541
|
+
* PricingDescription is the rendered human-readable pricing string.
|
|
542
|
+
*/
|
|
543
|
+
pricing_description?: string;
|
|
544
|
+
}
|
|
509
545
|
/**
|
|
510
546
|
* Scope represents an API key permission scope string.
|
|
511
547
|
*/
|
|
@@ -683,7 +719,7 @@ export declare const ScopeBillingRead: Scope;
|
|
|
683
719
|
*/
|
|
684
720
|
export declare const ScopeBillingWrite: Scope;
|
|
685
721
|
/**
|
|
686
|
-
* Action-level scopes for Secrets (sensitive -
|
|
722
|
+
* Action-level scopes for Secrets (sensitive - excluded from read-only preset)
|
|
687
723
|
*/
|
|
688
724
|
export declare const ScopeSecretsRead: Scope;
|
|
689
725
|
/**
|
|
@@ -794,6 +830,8 @@ export interface ScopePreset {
|
|
|
794
830
|
label: string;
|
|
795
831
|
description: string;
|
|
796
832
|
scopes: Scope[];
|
|
833
|
+
summary?: string[];
|
|
834
|
+
hidden?: boolean;
|
|
797
835
|
}
|
|
798
836
|
/**
|
|
799
837
|
* ApiKeyDTO for API responses
|
|
@@ -820,6 +858,19 @@ export interface AppPricing {
|
|
|
820
858
|
royalty_expression: string;
|
|
821
859
|
partner_expression: string;
|
|
822
860
|
total_expression: string;
|
|
861
|
+
/**
|
|
862
|
+
* Estimate is a single CEL expression for pre-execution cost estimation.
|
|
863
|
+
* Returns either an int (exact total in microcents) or a {"min": int, "max": int} map.
|
|
864
|
+
* Only has access to pre-execution variables: task_inputs, prices, fees, task_function.
|
|
865
|
+
* Used by the /estimate endpoint when the real expressions depend on post-execution data.
|
|
866
|
+
* Not needed when all fee expressions are already input-based (the system evaluates those directly).
|
|
867
|
+
*/
|
|
868
|
+
estimate?: string;
|
|
869
|
+
/**
|
|
870
|
+
* Estimable is computed at save time. True when all fee expressions can be
|
|
871
|
+
* evaluated from pre-execution data alone (task_inputs, prices, fees, task_function).
|
|
872
|
+
*/
|
|
873
|
+
estimable?: boolean;
|
|
823
874
|
description: string;
|
|
824
875
|
description_rendered?: string;
|
|
825
876
|
}
|
|
@@ -971,6 +1022,7 @@ export interface AppStoreListingDTO {
|
|
|
971
1022
|
max_concurrency: number;
|
|
972
1023
|
max_concurrency_per_team: number;
|
|
973
1024
|
min_concurrency: number;
|
|
1025
|
+
required_feature?: string;
|
|
974
1026
|
tags?: string[];
|
|
975
1027
|
}
|
|
976
1028
|
/**
|
|
@@ -1170,6 +1222,7 @@ export interface EngineDTO extends BaseModelDTO, PermissionModelDTO {
|
|
|
1170
1222
|
name: string;
|
|
1171
1223
|
api_url: string;
|
|
1172
1224
|
status: EngineStatus;
|
|
1225
|
+
engine_version: string;
|
|
1173
1226
|
system_info?: SystemInfo;
|
|
1174
1227
|
workers: (WorkerDTO | undefined)[];
|
|
1175
1228
|
}
|
|
@@ -1264,6 +1317,20 @@ export interface EntitlementDTO extends BaseModelDTO {
|
|
|
1264
1317
|
source: EntitlementSource;
|
|
1265
1318
|
enforcement: EnforcementMode;
|
|
1266
1319
|
expires_at?: string;
|
|
1320
|
+
team_plan_id?: string;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* EntitlementErrorMeta is the structured metadata returned in entitlement error responses.
|
|
1324
|
+
*/
|
|
1325
|
+
export interface EntitlementErrorMeta {
|
|
1326
|
+
resource: EntitlementResource;
|
|
1327
|
+
resource_label?: string;
|
|
1328
|
+
limit?: number;
|
|
1329
|
+
current?: number;
|
|
1330
|
+
upgrade_available: boolean;
|
|
1331
|
+
addon_plan_id?: string;
|
|
1332
|
+
addon_plan_name?: string;
|
|
1333
|
+
addon_plan_price?: number;
|
|
1267
1334
|
}
|
|
1268
1335
|
/**
|
|
1269
1336
|
* FileMetadata holds probed media metadata cached on File records.
|
|
@@ -1590,6 +1657,7 @@ export interface KnowledgeVersionDTO extends BaseModelDTO {
|
|
|
1590
1657
|
content_hash: string;
|
|
1591
1658
|
description: string;
|
|
1592
1659
|
tags: string[];
|
|
1660
|
+
scope?: string[];
|
|
1593
1661
|
metadata?: {
|
|
1594
1662
|
[key: string]: string;
|
|
1595
1663
|
};
|
|
@@ -1819,11 +1887,14 @@ export interface PlanDTO extends BaseModelDTO {
|
|
|
1819
1887
|
display_order: number;
|
|
1820
1888
|
active: boolean;
|
|
1821
1889
|
self_serve?: boolean;
|
|
1890
|
+
plan_type: PlanType;
|
|
1822
1891
|
price_monthly?: number;
|
|
1823
1892
|
price_yearly?: number;
|
|
1824
1893
|
credits_monthly: number;
|
|
1825
1894
|
provider_price_id_monthly?: string;
|
|
1826
1895
|
provider_price_id_yearly?: string;
|
|
1896
|
+
required_plan_ids?: string[];
|
|
1897
|
+
required_plan_names?: string[];
|
|
1827
1898
|
limits: PlanLimits;
|
|
1828
1899
|
}
|
|
1829
1900
|
/**
|
|
@@ -1854,6 +1925,7 @@ export interface RefRouteDTO extends BaseModelDTO {
|
|
|
1854
1925
|
alias_ref: string;
|
|
1855
1926
|
target_ref: string;
|
|
1856
1927
|
primary: boolean;
|
|
1928
|
+
mode: RefRouteMode;
|
|
1857
1929
|
description: string;
|
|
1858
1930
|
enabled: boolean;
|
|
1859
1931
|
}
|
|
@@ -1879,6 +1951,7 @@ export interface KnowledgeVersionInput {
|
|
|
1879
1951
|
content?: KnowledgeFile;
|
|
1880
1952
|
files?: KnowledgeFile[];
|
|
1881
1953
|
tags?: string[];
|
|
1954
|
+
scope?: string[];
|
|
1882
1955
|
metadata?: {
|
|
1883
1956
|
[key: string]: string;
|
|
1884
1957
|
};
|
|
@@ -1983,9 +2056,11 @@ export interface UpdateIntegrationScopesRequest {
|
|
|
1983
2056
|
*/
|
|
1984
2057
|
export interface SuggestRequest {
|
|
1985
2058
|
query: string;
|
|
2059
|
+
context?: string;
|
|
1986
2060
|
limit?: number;
|
|
1987
2061
|
category?: string;
|
|
1988
2062
|
agent?: boolean;
|
|
2063
|
+
scope?: string[];
|
|
1989
2064
|
}
|
|
1990
2065
|
/**
|
|
1991
2066
|
* SuggestResponse is the output of the suggest endpoint.
|
|
@@ -1999,6 +2074,7 @@ export interface SuggestResponse {
|
|
|
1999
2074
|
*/
|
|
2000
2075
|
export interface SuggestResult {
|
|
2001
2076
|
type: string;
|
|
2077
|
+
tag?: string;
|
|
2002
2078
|
name: string;
|
|
2003
2079
|
description: string;
|
|
2004
2080
|
command: string;
|
|
@@ -2084,7 +2160,6 @@ export interface SecretDTO extends BaseModelDTO, PermissionModelDTO {
|
|
|
2084
2160
|
*/
|
|
2085
2161
|
export interface SubscriptionDTO extends BaseModelDTO {
|
|
2086
2162
|
team_id: string;
|
|
2087
|
-
stripe_subscription_id?: string;
|
|
2088
2163
|
plan_id: string;
|
|
2089
2164
|
plan?: PlanDTO;
|
|
2090
2165
|
interval: SubscriptionInterval;
|
|
@@ -2620,6 +2695,21 @@ export declare const SubscriptionStatusPaused: SubscriptionStatus;
|
|
|
2620
2695
|
export type SubscriptionInterval = string;
|
|
2621
2696
|
export declare const SubscriptionIntervalMonthly: SubscriptionInterval;
|
|
2622
2697
|
export declare const SubscriptionIntervalYearly: SubscriptionInterval;
|
|
2698
|
+
export type PlanType = string;
|
|
2699
|
+
export declare const PlanTypeBase: PlanType;
|
|
2700
|
+
export declare const PlanTypeAddon: PlanType;
|
|
2701
|
+
export type EntitlementSource = string;
|
|
2702
|
+
export declare const EntitlementSourceTier: EntitlementSource;
|
|
2703
|
+
export declare const EntitlementSourceOverride: EntitlementSource;
|
|
2704
|
+
export declare const EntitlementSourceWhitelist: EntitlementSource;
|
|
2705
|
+
export declare const EntitlementSourceTrial: EntitlementSource;
|
|
2706
|
+
export declare const EntitlementSourceAddon: EntitlementSource;
|
|
2707
|
+
export type EntitlementType = string;
|
|
2708
|
+
export declare const EntitlementTypeBoolean: EntitlementType;
|
|
2709
|
+
export declare const EntitlementTypeLimit: EntitlementType;
|
|
2710
|
+
export type EnforcementMode = string;
|
|
2711
|
+
export declare const EnforcementBlock: EnforcementMode;
|
|
2712
|
+
export declare const EnforcementWarn: EnforcementMode;
|
|
2623
2713
|
export type ChatStatus = string;
|
|
2624
2714
|
export declare const ChatStatusBusy: ChatStatus;
|
|
2625
2715
|
export declare const ChatStatusIdle: ChatStatus;
|
|
@@ -2871,6 +2961,9 @@ export declare const GraphEdgeTypeParent: GraphEdgeType;
|
|
|
2871
2961
|
export declare const GraphEdgeTypeAncestor: GraphEdgeType;
|
|
2872
2962
|
export declare const GraphEdgeTypeDuplicate: GraphEdgeType;
|
|
2873
2963
|
export declare const GraphEdgeTypeReferences: GraphEdgeType;
|
|
2964
|
+
export declare const GraphEdgeTypeSupersedes: GraphEdgeType;
|
|
2965
|
+
export declare const GraphEdgeTypeInput: GraphEdgeType;
|
|
2966
|
+
export declare const GraphEdgeTypeOutput: GraphEdgeType;
|
|
2874
2967
|
/**
|
|
2875
2968
|
* SecretScope defines the visibility/purpose of a secret
|
|
2876
2969
|
*/
|
|
@@ -2887,20 +2980,6 @@ export declare const SecretScopeInternal: SecretScope;
|
|
|
2887
2980
|
* SecretScopeSystem is a global system setting, owned by system team, admin-only
|
|
2888
2981
|
*/
|
|
2889
2982
|
export declare const SecretScopeSystem: SecretScope;
|
|
2890
|
-
export type EntitlementSource = string;
|
|
2891
|
-
export declare const EntitlementSourceTier: EntitlementSource;
|
|
2892
|
-
export declare const EntitlementSourceOverride: EntitlementSource;
|
|
2893
|
-
export declare const EntitlementSourceWhitelist: EntitlementSource;
|
|
2894
|
-
export declare const EntitlementSourceTrial: EntitlementSource;
|
|
2895
|
-
export type EntitlementType = string;
|
|
2896
|
-
export declare const EntitlementTypeBoolean: EntitlementType;
|
|
2897
|
-
export declare const EntitlementTypeLimit: EntitlementType;
|
|
2898
|
-
/**
|
|
2899
|
-
* EnforcementMode controls how limit violations are handled.
|
|
2900
|
-
*/
|
|
2901
|
-
export type EnforcementMode = string;
|
|
2902
|
-
export declare const EnforcementBlock: EnforcementMode;
|
|
2903
|
-
export declare const EnforcementWarn: EnforcementMode;
|
|
2904
2983
|
export type PageStatus = number;
|
|
2905
2984
|
export declare const PageStatusUnknown: PageStatus;
|
|
2906
2985
|
export declare const PageStatusDraft: PageStatus;
|
|
@@ -3029,6 +3108,9 @@ export type RefRouteType = string;
|
|
|
3029
3108
|
export declare const RefRouteTypeApp: RefRouteType;
|
|
3030
3109
|
export declare const RefRouteTypeAgent: RefRouteType;
|
|
3031
3110
|
export declare const RefRouteTypeSkill: RefRouteType;
|
|
3111
|
+
export type RefRouteMode = string;
|
|
3112
|
+
export declare const RefRouteModeRewrite: RefRouteMode;
|
|
3113
|
+
export declare const RefRouteModeRedirect: RefRouteMode;
|
|
3032
3114
|
export type KnowledgeType = string;
|
|
3033
3115
|
export declare const KnowledgeTypeConcept: KnowledgeType;
|
|
3034
3116
|
export declare const KnowledgeTypeSkill: KnowledgeType;
|
|
@@ -3102,6 +3184,7 @@ export declare const ResourceTaskExecutions: EntitlementResource;
|
|
|
3102
3184
|
* Feature gates — only what has real cost/complexity
|
|
3103
3185
|
*/
|
|
3104
3186
|
export declare const ResourceFeatureBYOK: EntitlementResource;
|
|
3187
|
+
export declare const ResourceFeatureSeedance: EntitlementResource;
|
|
3105
3188
|
/**
|
|
3106
3189
|
* Legacy feature gates — kept for DB compatibility, no longer gated
|
|
3107
3190
|
*/
|
|
@@ -3242,6 +3325,10 @@ export declare const NotificationTypeSecurityAlert: NotificationType;
|
|
|
3242
3325
|
*/
|
|
3243
3326
|
export declare const NotificationTypeTaskComplete: NotificationType;
|
|
3244
3327
|
export declare const NotificationTypeTaskFailed: NotificationType;
|
|
3328
|
+
/**
|
|
3329
|
+
* Data export
|
|
3330
|
+
*/
|
|
3331
|
+
export declare const NotificationTypeDataExport: NotificationType;
|
|
3245
3332
|
/**
|
|
3246
3333
|
* System notifications
|
|
3247
3334
|
*/
|
package/dist/types.js
CHANGED
|
@@ -172,7 +172,7 @@ export const ScopeBillingRead = "billing:read";
|
|
|
172
172
|
*/
|
|
173
173
|
export const ScopeBillingWrite = "billing:write";
|
|
174
174
|
/**
|
|
175
|
-
* Action-level scopes for Secrets (sensitive -
|
|
175
|
+
* Action-level scopes for Secrets (sensitive - excluded from read-only preset)
|
|
176
176
|
*/
|
|
177
177
|
export const ScopeSecretsRead = "secrets:read";
|
|
178
178
|
/**
|
|
@@ -285,6 +285,17 @@ export const SubscriptionStatusCanceled = "canceled";
|
|
|
285
285
|
export const SubscriptionStatusPaused = "paused";
|
|
286
286
|
export const SubscriptionIntervalMonthly = "monthly";
|
|
287
287
|
export const SubscriptionIntervalYearly = "yearly";
|
|
288
|
+
export const PlanTypeBase = "base";
|
|
289
|
+
export const PlanTypeAddon = "addon";
|
|
290
|
+
export const EntitlementSourceTier = "tier";
|
|
291
|
+
export const EntitlementSourceOverride = "override";
|
|
292
|
+
export const EntitlementSourceWhitelist = "whitelist";
|
|
293
|
+
export const EntitlementSourceTrial = "trial";
|
|
294
|
+
export const EntitlementSourceAddon = "addon";
|
|
295
|
+
export const EntitlementTypeBoolean = "boolean";
|
|
296
|
+
export const EntitlementTypeLimit = "limit";
|
|
297
|
+
export const EnforcementBlock = "block";
|
|
298
|
+
export const EnforcementWarn = "warn";
|
|
288
299
|
export const ChatStatusBusy = "busy";
|
|
289
300
|
export const ChatStatusIdle = "idle";
|
|
290
301
|
export const ChatStatusAwaitingInput = "awaiting_input";
|
|
@@ -355,6 +366,9 @@ export const GraphEdgeTypeParent = "parent";
|
|
|
355
366
|
export const GraphEdgeTypeAncestor = "ancestor";
|
|
356
367
|
export const GraphEdgeTypeDuplicate = "duplicate";
|
|
357
368
|
export const GraphEdgeTypeReferences = "references";
|
|
369
|
+
export const GraphEdgeTypeSupersedes = "supersedes";
|
|
370
|
+
export const GraphEdgeTypeInput = "input";
|
|
371
|
+
export const GraphEdgeTypeOutput = "output";
|
|
358
372
|
/**
|
|
359
373
|
* SecretScopeTeam is a normal user secret, visible in team secret lists
|
|
360
374
|
*/
|
|
@@ -367,14 +381,6 @@ export const SecretScopeInternal = "internal";
|
|
|
367
381
|
* SecretScopeSystem is a global system setting, owned by system team, admin-only
|
|
368
382
|
*/
|
|
369
383
|
export const SecretScopeSystem = "system";
|
|
370
|
-
export const EntitlementSourceTier = "tier";
|
|
371
|
-
export const EntitlementSourceOverride = "override";
|
|
372
|
-
export const EntitlementSourceWhitelist = "whitelist";
|
|
373
|
-
export const EntitlementSourceTrial = "trial";
|
|
374
|
-
export const EntitlementTypeBoolean = "boolean";
|
|
375
|
-
export const EntitlementTypeLimit = "limit";
|
|
376
|
-
export const EnforcementBlock = "block";
|
|
377
|
-
export const EnforcementWarn = "warn";
|
|
378
384
|
export const PageStatusUnknown = 0;
|
|
379
385
|
export const PageStatusDraft = 1;
|
|
380
386
|
export const PageStatusPublished = 2;
|
|
@@ -464,6 +470,8 @@ export const TeamInviteStatusRevoked = "revoked";
|
|
|
464
470
|
export const RefRouteTypeApp = "app";
|
|
465
471
|
export const RefRouteTypeAgent = "agent";
|
|
466
472
|
export const RefRouteTypeSkill = "skill";
|
|
473
|
+
export const RefRouteModeRewrite = "rewrite";
|
|
474
|
+
export const RefRouteModeRedirect = "redirect";
|
|
467
475
|
export const KnowledgeTypeConcept = "concept";
|
|
468
476
|
export const KnowledgeTypeSkill = "skill";
|
|
469
477
|
export const KnowledgeTypeObservation = "observation";
|
|
@@ -528,6 +536,7 @@ export const ResourceTaskExecutions = "task_executions";
|
|
|
528
536
|
* Feature gates — only what has real cost/complexity
|
|
529
537
|
*/
|
|
530
538
|
export const ResourceFeatureBYOK = "feature:byok";
|
|
539
|
+
export const ResourceFeatureSeedance = "feature:seedance";
|
|
531
540
|
/**
|
|
532
541
|
* Legacy feature gates — kept for DB compatibility, no longer gated
|
|
533
542
|
*/
|
|
@@ -638,6 +647,10 @@ export const NotificationTypeSecurityAlert = "security_alert";
|
|
|
638
647
|
*/
|
|
639
648
|
export const NotificationTypeTaskComplete = "task_complete";
|
|
640
649
|
export const NotificationTypeTaskFailed = "task_failed";
|
|
650
|
+
/**
|
|
651
|
+
* Data export
|
|
652
|
+
*/
|
|
653
|
+
export const NotificationTypeDataExport = "data_export";
|
|
641
654
|
/**
|
|
642
655
|
* System notifications
|
|
643
656
|
*/
|