@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 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 250+ ai models
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
 
@@ -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
+ });
@@ -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', () => {
@@ -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
  }
@@ -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);
@@ -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
  /**
@@ -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);
@@ -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' });
@@ -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' }] };
@@ -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(() => {
@@ -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
  });
@@ -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);
@@ -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 - not in read/run presets)
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 - not in read/run presets)
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inferencesh/sdk",
3
- "version": "0.6.13",
3
+ "version": "0.6.15",
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",