@inferencesh/sdk 0.6.10 → 0.6.12

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.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/actions.js +3 -3
  3. package/dist/agent/actions.test.js +6 -0
  4. package/dist/api/agents.d.ts +10 -0
  5. package/dist/api/agents.js +17 -3
  6. package/dist/api/agents.test.js +215 -0
  7. package/dist/api/api-keys.d.ts +24 -0
  8. package/dist/api/api-keys.js +26 -0
  9. package/dist/api/api-keys.test.d.ts +1 -0
  10. package/dist/api/api-keys.test.js +44 -0
  11. package/dist/api/apps.js +1 -1
  12. package/dist/api/apps.test.js +71 -3
  13. package/dist/api/chats.d.ts +14 -0
  14. package/dist/api/chats.js +18 -0
  15. package/dist/api/chats.test.js +52 -0
  16. package/dist/api/engines.d.ts +12 -0
  17. package/dist/api/engines.js +18 -0
  18. package/dist/api/engines.test.js +78 -0
  19. package/dist/api/files.test.js +183 -0
  20. package/dist/api/flow-runs.test.js +42 -0
  21. package/dist/api/flows.d.ts +4 -0
  22. package/dist/api/flows.js +6 -0
  23. package/dist/api/flows.test.js +75 -0
  24. package/dist/api/integrations.d.ts +41 -0
  25. package/dist/api/integrations.js +56 -0
  26. package/dist/api/integrations.test.d.ts +1 -0
  27. package/dist/api/integrations.test.js +109 -0
  28. package/dist/api/knowledge.d.ts +112 -0
  29. package/dist/api/knowledge.js +163 -0
  30. package/dist/api/knowledge.test.d.ts +1 -0
  31. package/dist/api/knowledge.test.js +94 -0
  32. package/dist/api/mcp-servers.d.ts +45 -0
  33. package/dist/api/mcp-servers.js +62 -0
  34. package/dist/api/mcp-servers.test.d.ts +1 -0
  35. package/dist/api/mcp-servers.test.js +64 -0
  36. package/dist/api/projects.d.ts +29 -0
  37. package/dist/api/projects.js +38 -0
  38. package/dist/api/projects.test.d.ts +1 -0
  39. package/dist/api/projects.test.js +52 -0
  40. package/dist/api/search.d.ts +21 -0
  41. package/dist/api/search.js +20 -0
  42. package/dist/api/search.test.d.ts +1 -0
  43. package/dist/api/search.test.js +39 -0
  44. package/dist/api/secrets.d.ts +29 -0
  45. package/dist/api/secrets.js +38 -0
  46. package/dist/api/secrets.test.d.ts +1 -0
  47. package/dist/api/secrets.test.js +61 -0
  48. package/dist/api/tasks.d.ts +13 -1
  49. package/dist/api/tasks.js +18 -0
  50. package/dist/api/tasks.test.js +134 -0
  51. package/dist/api/teams.d.ts +71 -0
  52. package/dist/api/teams.js +92 -0
  53. package/dist/api/teams.test.d.ts +1 -0
  54. package/dist/api/teams.test.js +79 -0
  55. package/dist/http/client.d.ts +5 -0
  56. package/dist/http/client.js +42 -23
  57. package/dist/http/client.test.js +245 -0
  58. package/dist/http/errors.test.js +13 -0
  59. package/dist/index.d.ts +25 -0
  60. package/dist/index.js +25 -0
  61. package/dist/tool-builder.test.js +11 -1
  62. package/dist/types.d.ts +110 -5
  63. package/dist/types.js +58 -2
  64. package/package.json +4 -4
@@ -0,0 +1,79 @@
1
+ import { HttpClient } from '../http/client';
2
+ import { TeamsAPI } from './teams';
3
+ const mockFetch = jest.fn();
4
+ global.fetch = mockFetch;
5
+ function mockJsonResponse(body) {
6
+ mockFetch.mockResolvedValueOnce({
7
+ ok: true,
8
+ status: 200,
9
+ text: () => Promise.resolve(JSON.stringify(body)),
10
+ });
11
+ }
12
+ describe('TeamsAPI', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ });
16
+ const api = () => new TeamsAPI(new HttpClient({ apiKey: 'test-key' }));
17
+ it('should GET /me for me()', async () => {
18
+ const me = { user: { id: 'user-1' }, team: { id: 'team-1' } };
19
+ mockJsonResponse(me);
20
+ const result = await api().me();
21
+ expect(result).toEqual(me);
22
+ const [url, init] = mockFetch.mock.calls[0];
23
+ expect(url).toContain('/me');
24
+ expect(init.method).toBe('GET');
25
+ });
26
+ it('should GET /teams for list()', async () => {
27
+ const teams = [{ id: 'team-1', name: 'Acme' }];
28
+ mockJsonResponse(teams);
29
+ const result = await api().list();
30
+ expect(result).toEqual(teams);
31
+ const [url, init] = mockFetch.mock.calls[0];
32
+ expect(url).toContain('/teams');
33
+ expect(init.method).toBe('GET');
34
+ });
35
+ it('should POST /teams for create()', async () => {
36
+ const payload = { name: 'New Team', username: 'new-team' };
37
+ const team = { id: 'team-new', ...payload };
38
+ mockJsonResponse(team);
39
+ const result = await api().create(payload);
40
+ expect(result).toEqual(team);
41
+ const [url, init] = mockFetch.mock.calls[0];
42
+ expect(url).toContain('/teams');
43
+ expect(init.method).toBe('POST');
44
+ expect(JSON.parse(init.body)).toEqual(payload);
45
+ });
46
+ it('should GET /teams/check-username with username param for checkUsername()', async () => {
47
+ mockJsonResponse({ available: true });
48
+ const result = await api().checkUsername('acme-corp');
49
+ expect(result).toEqual({ available: true });
50
+ const [url] = mockFetch.mock.calls[0];
51
+ expect(url).toContain('/teams/check-username');
52
+ expect(url).toContain('username=acme-corp');
53
+ });
54
+ it('should POST role for updateMemberRole()', async () => {
55
+ const member = { user_id: 'user-2', role: 'admin' };
56
+ mockJsonResponse(member);
57
+ await api().updateMemberRole('team-1', 'user-2', 'admin');
58
+ const [url, init] = mockFetch.mock.calls[0];
59
+ expect(url).toContain('/teams/team-1/members/user-2/role');
60
+ expect(JSON.parse(init.body)).toEqual({ role: 'admin' });
61
+ });
62
+ it('should DELETE /teams/{id}/members/{userId} for removeMember()', async () => {
63
+ mockJsonResponse(null);
64
+ await api().removeMember('team-1', 'user-3');
65
+ const [url, init] = mockFetch.mock.calls[0];
66
+ expect(url).toContain('/teams/team-1/members/user-3');
67
+ expect(init.method).toBe('DELETE');
68
+ });
69
+ it('should POST /teams/{id}/invites for createInvite()', async () => {
70
+ const payload = { email: 'dev@example.com', role: 'member' };
71
+ const invite = { id: 'inv-1', ...payload };
72
+ mockJsonResponse(invite);
73
+ const result = await api().createInvite('team-1', payload);
74
+ expect(result).toEqual(invite);
75
+ const [url, init] = mockFetch.mock.calls[0];
76
+ expect(url).toContain('/teams/team-1/invites');
77
+ expect(JSON.parse(init.body)).toEqual(payload);
78
+ });
79
+ });
@@ -84,6 +84,11 @@ export declare class HttpClient {
84
84
  url: string;
85
85
  headers: Record<string, string>;
86
86
  };
87
+ /**
88
+ * Pull a human-readable detail string from a parsed problem+json / API error
89
+ * body (RFC 9457 `detail`/`title`, or a plain `message`).
90
+ */
91
+ private extractErrorDetail;
87
92
  /**
88
93
  * Create an EventSource for SSE streaming
89
94
  * @deprecated Use getStreamableConfig() with StreamableManager instead
@@ -128,7 +128,7 @@ export class HttpClient {
128
128
  }
129
129
  let errorDetail;
130
130
  if (data && typeof data === 'object') {
131
- errorDetail = data.detail || data.title || data.message || JSON.stringify(data);
131
+ errorDetail = this.extractErrorDetail(data) ?? JSON.stringify(data);
132
132
  }
133
133
  else if (responseText) {
134
134
  errorDetail = responseText.slice(0, 500);
@@ -164,6 +164,16 @@ export class HttpClient {
164
164
  }
165
165
  return { url, headers };
166
166
  }
167
+ /**
168
+ * Pull a human-readable detail string from a parsed problem+json / API error
169
+ * body (RFC 9457 `detail`/`title`, or a plain `message`).
170
+ */
171
+ extractErrorDetail(data) {
172
+ if (!data || typeof data !== 'object')
173
+ return undefined;
174
+ const d = data;
175
+ return (d.detail || d.title || d.message);
176
+ }
167
177
  /**
168
178
  * Create an EventSource for SSE streaming
169
179
  * @deprecated Use getStreamableConfig() with StreamableManager instead
@@ -183,30 +193,39 @@ export class HttpClient {
183
193
  fetchUrl = targetUrl.toString();
184
194
  }
185
195
  const resolvedHeaders = this.resolveHeaders();
186
- return Promise.resolve(new EventSource(fetchUrl, {
187
- fetch: (input, init) => {
188
- const headers = {
189
- ...init?.headers,
190
- ...resolvedHeaders,
191
- };
192
- if (isProxyMode) {
193
- // Proxy mode: also send target URL as header (for non-browser clients)
194
- headers['x-inf-target-url'] = targetUrl.toString();
196
+ // The EventSource error event omits the response body, so a failed initial
197
+ // connection (e.g. 403 otp_required) is handled here in the fetch wrapper:
198
+ // route it through onError like request() does, and hand the retried
199
+ // response (e.g. after OTP verification) back so the stream connects.
200
+ const doFetch = async (input, init) => {
201
+ const headers = {
202
+ ...init?.headers,
203
+ ...resolvedHeaders,
204
+ };
205
+ if (isProxyMode) {
206
+ headers['x-inf-target-url'] = targetUrl.toString();
207
+ }
208
+ else {
209
+ const token = this.getAuthToken();
210
+ if (token) {
211
+ headers['Authorization'] = `Bearer ${token}`;
195
212
  }
196
- else {
197
- // Direct mode: include authorization header
198
- const token = this.getAuthToken();
199
- if (token) {
200
- headers['Authorization'] = `Bearer ${token}`;
201
- }
213
+ }
214
+ const response = await fetch(input, { ...init, headers, credentials: this.credentials });
215
+ if (!response.ok && this.onError) {
216
+ const body = await response.clone().text().catch(() => '');
217
+ let data;
218
+ try {
219
+ data = JSON.parse(body);
202
220
  }
203
- return fetch(input, {
204
- ...init,
205
- headers,
206
- credentials: this.credentials,
207
- });
208
- },
209
- }));
221
+ catch { /* non-JSON body */ }
222
+ const error = new InferenceError(response.status, this.extractErrorDetail(data) ?? 'Request failed', body);
223
+ const handled = await this.onError(error, () => doFetch(input, init));
224
+ return handled ?? response;
225
+ }
226
+ return response;
227
+ };
228
+ return Promise.resolve(new EventSource(fetchUrl, { fetch: doFetch }));
210
229
  }
211
230
  }
212
231
  /**
@@ -114,6 +114,32 @@ describe('HttpClient', () => {
114
114
  expect(calledUrl).toContain('ids=');
115
115
  expect(decodeURIComponent(calledUrl)).toContain('["a","b"]');
116
116
  });
117
+ it('should serialize object query params as JSON', async () => {
118
+ mockJsonResponse([]);
119
+ await client().request('get', '/tasks', {
120
+ params: { filter: { status: 'active', team_id: 'team-1' } },
121
+ });
122
+ const calledUrl = mockFetch.mock.calls[0][0];
123
+ expect(calledUrl).toContain('filter=');
124
+ expect(decodeURIComponent(calledUrl)).toContain('"status":"active"');
125
+ expect(decodeURIComponent(calledUrl)).toContain('"team_id":"team-1"');
126
+ });
127
+ it('should omit Authorization when getToken returns null', async () => {
128
+ mockJsonResponse({ id: 'task-1' });
129
+ const tokenClient = new HttpClient({ getToken: () => null });
130
+ await tokenClient.request('get', '/tasks/task-1');
131
+ const [, init] = mockFetch.mock.calls[0];
132
+ const headers = init.headers;
133
+ expect(headers.Authorization).toBeUndefined();
134
+ });
135
+ it('should omit Authorization when getToken returns undefined', async () => {
136
+ mockJsonResponse({ id: 'task-1' });
137
+ const tokenClient = new HttpClient({ getToken: () => undefined });
138
+ await tokenClient.request('get', '/tasks/task-1');
139
+ const [, init] = mockFetch.mock.calls[0];
140
+ const headers = init.headers;
141
+ expect(headers.Authorization).toBeUndefined();
142
+ });
117
143
  it('should use top-level message field in HTTP error responses', async () => {
118
144
  mockFetch.mockResolvedValueOnce({
119
145
  ok: false,
@@ -125,6 +151,99 @@ describe('HttpClient', () => {
125
151
  message: expect.stringContaining('service unavailable'),
126
152
  });
127
153
  });
154
+ it('should use getToken for Authorization on regular requests', async () => {
155
+ mockJsonResponse({ id: 'task-1' });
156
+ const tokenClient = new HttpClient({ getToken: () => 'dynamic-key' });
157
+ await tokenClient.request('get', '/tasks/task-1');
158
+ const [, init] = mockFetch.mock.calls[0];
159
+ const headers = init.headers;
160
+ expect(headers.Authorization).toBe('Bearer dynamic-key');
161
+ });
162
+ it('should resolve function-valued headers on each request', async () => {
163
+ mockJsonResponse({ id: 'task-1' });
164
+ mockJsonResponse({ id: 'task-2' });
165
+ let teamId = 'team-a';
166
+ const client = new HttpClient({
167
+ apiKey: 'test-key',
168
+ headers: {
169
+ 'X-Team-ID': () => teamId,
170
+ 'X-Request-Id': () => 'req-1',
171
+ },
172
+ });
173
+ await client.request('get', '/tasks/task-1');
174
+ teamId = 'team-b';
175
+ await client.request('get', '/tasks/task-2');
176
+ const firstHeaders = mockFetch.mock.calls[0][1]?.headers;
177
+ const secondHeaders = mockFetch.mock.calls[1][1]?.headers;
178
+ expect(firstHeaders['X-Team-ID']).toBe('team-a');
179
+ expect(secondHeaders['X-Team-ID']).toBe('team-b');
180
+ expect(firstHeaders['X-Request-Id']).toBe('req-1');
181
+ });
182
+ it('should omit headers when a resolver returns undefined', async () => {
183
+ mockJsonResponse({ id: 'task-1' });
184
+ const client = new HttpClient({
185
+ apiKey: 'test-key',
186
+ headers: {
187
+ 'X-Team-ID': () => undefined,
188
+ 'X-Custom': 'static',
189
+ },
190
+ });
191
+ await client.request('get', '/tasks/task-1');
192
+ const headers = mockFetch.mock.calls[0][1]?.headers;
193
+ expect(headers['X-Team-ID']).toBeUndefined();
194
+ expect(headers['X-Custom']).toBe('static');
195
+ });
196
+ it('should send X-API-Version 2 and X-Client-Source on every request', async () => {
197
+ mockJsonResponse({ id: 'task-1' });
198
+ await client().request('get', '/tasks/task-1');
199
+ const [, init] = mockFetch.mock.calls[0];
200
+ const headers = init.headers;
201
+ expect(headers['X-API-Version']).toBe('2');
202
+ expect(headers['X-Client-Source']).toMatch(/inference-sdk-js\//);
203
+ });
204
+ it('should prefer RFC 9457 detail over title in error responses', async () => {
205
+ mockFetch.mockResolvedValueOnce({
206
+ ok: false,
207
+ status: 422,
208
+ text: () => Promise.resolve(JSON.stringify({
209
+ type: 'about:blank',
210
+ title: 'Validation failed',
211
+ detail: 'app field is required',
212
+ })),
213
+ });
214
+ await expect(client().request('post', '/apps')).rejects.toMatchObject({
215
+ name: 'InferenceError',
216
+ message: expect.stringContaining('app field is required'),
217
+ });
218
+ });
219
+ it('should fall back to RFC 9457 title when detail is absent', async () => {
220
+ mockFetch.mockResolvedValueOnce({
221
+ ok: false,
222
+ status: 403,
223
+ text: () => Promise.resolve(JSON.stringify({ type: 'about:blank', title: 'Forbidden' })),
224
+ });
225
+ await expect(client().request('get', '/tasks/1')).rejects.toMatchObject({
226
+ name: 'InferenceError',
227
+ message: expect.stringContaining('Forbidden'),
228
+ });
229
+ });
230
+ it('should use raw response text when error body is not JSON', async () => {
231
+ mockFetch.mockResolvedValueOnce({
232
+ ok: false,
233
+ status: 502,
234
+ text: () => Promise.resolve('Bad Gateway from upstream'),
235
+ });
236
+ await expect(client().request('get', '/tasks/1')).rejects.toMatchObject({
237
+ name: 'InferenceError',
238
+ message: expect.stringContaining('Bad Gateway from upstream'),
239
+ });
240
+ });
241
+ it('should not unwrap legacy v1 APIResponse envelopes', async () => {
242
+ const v1Envelope = { success: true, data: { id: 'task-legacy' } };
243
+ mockJsonResponse(v1Envelope);
244
+ const result = await client().request('get', '/tasks/legacy');
245
+ expect(result).toEqual(v1Envelope);
246
+ });
128
247
  });
129
248
  describe('getStreamableConfig', () => {
130
249
  it('should include bearer token in direct mode', () => {
@@ -146,6 +265,14 @@ describe('HttpClient', () => {
146
265
  }).getStreamableConfig('/tasks/task-1/stream');
147
266
  expect(config.headers.Authorization).toBe('Bearer dynamic-token');
148
267
  });
268
+ it('should include resolved dynamic headers for streaming requests', () => {
269
+ const config = new HttpClient({
270
+ apiKey: 'secret-key',
271
+ headers: { 'X-Team-ID': () => 'team-stream' },
272
+ }).getStreamableConfig('/tasks/task-1/stream');
273
+ expect(config.headers['X-Team-ID']).toBe('team-stream');
274
+ expect(config.headers.Authorization).toBe('Bearer secret-key');
275
+ });
149
276
  });
150
277
  describe('createEventSource', () => {
151
278
  beforeEach(() => {
@@ -186,5 +313,123 @@ describe('HttpClient', () => {
186
313
  }),
187
314
  }));
188
315
  });
316
+ it('should attach resolved dynamic headers on SSE custom fetch', async () => {
317
+ let capturedFetch;
318
+ MockEventSource.mockImplementation((_url, options) => {
319
+ capturedFetch = options?.fetch;
320
+ return { close: jest.fn(), onmessage: null, onerror: null };
321
+ });
322
+ mockFetch.mockResolvedValueOnce({ ok: true, status: 200 });
323
+ const client = new HttpClient({
324
+ apiKey: 'sse-key',
325
+ headers: { 'X-Team-ID': () => 'team-sse' },
326
+ });
327
+ await client.createEventSource('/tasks/task-1/stream');
328
+ await capturedFetch('https://api.inference.sh/tasks/task-1/stream', { headers: {} });
329
+ expect(mockFetch).toHaveBeenCalledWith('https://api.inference.sh/tasks/task-1/stream', expect.objectContaining({
330
+ headers: expect.objectContaining({
331
+ 'X-Team-ID': 'team-sse',
332
+ Authorization: 'Bearer sse-key',
333
+ }),
334
+ }));
335
+ });
336
+ it('should use getToken for Authorization on SSE custom fetch', async () => {
337
+ let capturedFetch;
338
+ MockEventSource.mockImplementation((_url, options) => {
339
+ capturedFetch = options?.fetch;
340
+ return { close: jest.fn(), onmessage: null, onerror: null };
341
+ });
342
+ mockFetch.mockResolvedValueOnce({ ok: true, status: 200 });
343
+ const client = new HttpClient({ getToken: () => 'session-token' });
344
+ await client.createEventSource('/tasks/task-1/stream');
345
+ await capturedFetch('https://api.inference.sh/tasks/task-1/stream', { headers: {} });
346
+ expect(mockFetch).toHaveBeenCalledWith('https://api.inference.sh/tasks/task-1/stream', expect.objectContaining({
347
+ headers: expect.objectContaining({
348
+ Authorization: 'Bearer session-token',
349
+ }),
350
+ }));
351
+ });
352
+ it('should omit Authorization on SSE fetch when getToken returns null', async () => {
353
+ let capturedFetch;
354
+ MockEventSource.mockImplementation((_url, options) => {
355
+ capturedFetch = options?.fetch;
356
+ return { close: jest.fn(), onmessage: null, onerror: null };
357
+ });
358
+ mockFetch.mockResolvedValueOnce({ ok: true, status: 200 });
359
+ const client = new HttpClient({ getToken: () => null });
360
+ await client.createEventSource('/tasks/task-1/stream');
361
+ await capturedFetch('https://api.inference.sh/tasks/task-1/stream', { headers: {} });
362
+ const [, init] = mockFetch.mock.calls[0];
363
+ const headers = init.headers;
364
+ expect(headers.Authorization).toBeUndefined();
365
+ });
366
+ it('should return EventSource without awaiting the initial fetch', async () => {
367
+ MockEventSource.mockImplementation(() => ({ close: jest.fn() }));
368
+ const client = new HttpClient({ apiKey: 'sse-key' });
369
+ const eventSource = await client.createEventSource('/tasks/task-1/stream');
370
+ expect(eventSource).toBeDefined();
371
+ expect(MockEventSource).toHaveBeenCalled();
372
+ expect(mockFetch).not.toHaveBeenCalled();
373
+ });
374
+ function mockFailedResponse(status, body) {
375
+ return {
376
+ ok: false,
377
+ status,
378
+ clone: () => ({
379
+ text: () => Promise.resolve(body),
380
+ }),
381
+ };
382
+ }
383
+ it('should route failed initial SSE response through onError and return retried response', async () => {
384
+ let capturedFetch;
385
+ MockEventSource.mockImplementation((_url, options) => {
386
+ capturedFetch = options?.fetch;
387
+ return { close: jest.fn(), onmessage: null, onerror: null };
388
+ });
389
+ const onError = jest.fn(async (_error, retry) => retry());
390
+ const client = new HttpClient({ apiKey: 'sse-key', onError });
391
+ mockFetch
392
+ .mockResolvedValueOnce(mockFailedResponse(403, JSON.stringify({ detail: 'otp_required' })))
393
+ .mockResolvedValueOnce({ ok: true, status: 200 });
394
+ await client.createEventSource('/tasks/task-1/stream');
395
+ const response = await capturedFetch('https://api.inference.sh/tasks/task-1/stream', {});
396
+ expect(onError).toHaveBeenCalledTimes(1);
397
+ const [error] = onError.mock.calls[0];
398
+ expect(error).toBeInstanceOf(InferenceError);
399
+ expect(error.statusCode).toBe(403);
400
+ expect(error.message).toContain('otp_required');
401
+ expect(response).toEqual({ ok: true, status: 200 });
402
+ expect(mockFetch).toHaveBeenCalledTimes(2);
403
+ });
404
+ it('should return original failed response when onError does not retry', async () => {
405
+ let capturedFetch;
406
+ MockEventSource.mockImplementation((_url, options) => {
407
+ capturedFetch = options?.fetch;
408
+ return { close: jest.fn(), onmessage: null, onerror: null };
409
+ });
410
+ const failed = mockFailedResponse(401, JSON.stringify({ title: 'Unauthorized' }));
411
+ const onError = jest.fn(async () => undefined);
412
+ const client = new HttpClient({ apiKey: 'sse-key', onError });
413
+ mockFetch.mockResolvedValueOnce(failed);
414
+ await client.createEventSource('/tasks/task-1/stream');
415
+ const response = await capturedFetch('https://api.inference.sh/tasks/task-1/stream', {});
416
+ expect(onError).toHaveBeenCalledTimes(1);
417
+ expect(response).toBe(failed);
418
+ expect(mockFetch).toHaveBeenCalledTimes(1);
419
+ });
420
+ it('should return failed response when no onError handler is configured', async () => {
421
+ let capturedFetch;
422
+ MockEventSource.mockImplementation((_url, options) => {
423
+ capturedFetch = options?.fetch;
424
+ return { close: jest.fn(), onmessage: null, onerror: null };
425
+ });
426
+ const failed = mockFailedResponse(503, 'service unavailable');
427
+ const client = new HttpClient({ apiKey: 'sse-key' });
428
+ mockFetch.mockResolvedValueOnce(failed);
429
+ await client.createEventSource('/tasks/task-1/stream');
430
+ const response = await capturedFetch('https://api.inference.sh/tasks/task-1/stream', {});
431
+ expect(response).toBe(failed);
432
+ expect(mockFetch).toHaveBeenCalledTimes(1);
433
+ });
189
434
  });
190
435
  });
@@ -1,4 +1,5 @@
1
1
  import { InferenceError, RequirementsNotMetException, SessionEndedError, SessionExpiredError, SessionNotFoundError, WorkerLostError, isInferenceError, isRequirementsNotMetException, isSessionError, } from './errors';
2
+ import { RequirementTypeIntegration, RequirementTypeScope, RequirementTypeSecret, } from '../types';
2
3
  describe('error type guards', () => {
3
4
  it('isRequirementsNotMetException should match instances and plain objects', () => {
4
5
  const instance = new RequirementsNotMetException([
@@ -43,6 +44,18 @@ describe('error classes', () => {
43
44
  expect(err.statusCode).toBe(412);
44
45
  expect(err.message).toBe('requirements not met');
45
46
  });
47
+ it('RequirementsNotMetException should preserve typed RequirementType values', () => {
48
+ const errors = [
49
+ { type: RequirementTypeSecret, key: 'API_KEY', message: 'Missing secret' },
50
+ { type: RequirementTypeIntegration, key: 'google', message: 'Not connected' },
51
+ { type: RequirementTypeScope, key: 'calendar.readonly', message: 'Missing scope' },
52
+ ];
53
+ const err = new RequirementsNotMetException(errors);
54
+ expect(err.errors[0].type).toBe('secret');
55
+ expect(err.errors[1].type).toBe('integration');
56
+ expect(err.errors[2].type).toBe('scope');
57
+ expect(isRequirementsNotMetException(err)).toBe(true);
58
+ });
46
59
  it('session error subclasses should expose sessionId and statusCode', () => {
47
60
  expect(new SessionNotFoundError('sess-a').sessionId).toBe('sess-a');
48
61
  expect(new SessionNotFoundError('sess-a').statusCode).toBe(404);
package/dist/index.d.ts CHANGED
@@ -12,6 +12,14 @@ export { ChatsAPI } from './api/chats';
12
12
  export { FlowsAPI } from './api/flows';
13
13
  export { FlowRunsAPI } from './api/flow-runs';
14
14
  export { EnginesAPI } from './api/engines';
15
+ export { KnowledgeAPI, SkillsAPI } from './api/knowledge';
16
+ export { TeamsAPI, MeResponse } from './api/teams';
17
+ export { SecretsAPI } from './api/secrets';
18
+ export { ApiKeysAPI } from './api/api-keys';
19
+ export { IntegrationsAPI } from './api/integrations';
20
+ export { SearchAPI } from './api/search';
21
+ export { ProjectsAPI } from './api/projects';
22
+ export { MCPServersAPI } from './api/mcp-servers';
15
23
  export { tool, appTool, agentTool, webhookTool, httpTool, callTool, mcpTool, internalTools, string, number, integer, boolean, enumOf, object, array, optional, } from './tool-builder';
16
24
  export type { ClientTool, ClientToolHandler } from './tool-builder';
17
25
  export { parseStatus, isTerminalStatus } from './utils';
@@ -27,6 +35,14 @@ import { ChatsAPI } from './api/chats';
27
35
  import { FlowsAPI } from './api/flows';
28
36
  import { FlowRunsAPI } from './api/flow-runs';
29
37
  import { EnginesAPI } from './api/engines';
38
+ import { KnowledgeAPI, SkillsAPI } from './api/knowledge';
39
+ import { TeamsAPI } from './api/teams';
40
+ import { SecretsAPI } from './api/secrets';
41
+ import { ApiKeysAPI } from './api/api-keys';
42
+ import { IntegrationsAPI } from './api/integrations';
43
+ import { SearchAPI } from './api/search';
44
+ import { ProjectsAPI } from './api/projects';
45
+ import { MCPServersAPI } from './api/mcp-servers';
30
46
  import { ApiAppRunRequest, TaskDTO as Task, AgentConfigInput as AgentConfig, FileDTO as File } from './types';
31
47
  export interface InferenceConfig {
32
48
  /** Your inference.sh API key (required unless using proxyUrl) */
@@ -68,6 +84,15 @@ export declare class Inference {
68
84
  readonly flows: FlowsAPI;
69
85
  readonly flowRuns: FlowRunsAPI;
70
86
  readonly engines: EnginesAPI;
87
+ readonly knowledge: KnowledgeAPI;
88
+ readonly skills: SkillsAPI;
89
+ readonly teams: TeamsAPI;
90
+ readonly secrets: SecretsAPI;
91
+ readonly apiKeys: ApiKeysAPI;
92
+ readonly integrations: IntegrationsAPI;
93
+ readonly search: SearchAPI;
94
+ readonly projects: ProjectsAPI;
95
+ readonly mcpServers: MCPServersAPI;
71
96
  constructor(config: InferenceConfig | HttpClientConfig);
72
97
  /** @internal */
73
98
  _request<T>(method: 'get' | 'post' | 'put' | 'delete', endpoint: string, options?: {
package/dist/index.js CHANGED
@@ -16,6 +16,14 @@ export { ChatsAPI } from './api/chats';
16
16
  export { FlowsAPI } from './api/flows';
17
17
  export { FlowRunsAPI } from './api/flow-runs';
18
18
  export { EnginesAPI } from './api/engines';
19
+ export { KnowledgeAPI, SkillsAPI } from './api/knowledge';
20
+ export { TeamsAPI } from './api/teams';
21
+ export { SecretsAPI } from './api/secrets';
22
+ export { ApiKeysAPI } from './api/api-keys';
23
+ export { IntegrationsAPI } from './api/integrations';
24
+ export { SearchAPI } from './api/search';
25
+ export { ProjectsAPI } from './api/projects';
26
+ export { MCPServersAPI } from './api/mcp-servers';
19
27
  // Tool Builder (fluent API)
20
28
  export { tool, appTool, agentTool, webhookTool, httpTool, callTool, mcpTool, internalTools, string, number, integer, boolean, enumOf, object, array, optional, } from './tool-builder';
21
29
  // Status utilities (handle both int and string status values)
@@ -35,6 +43,14 @@ import { ChatsAPI } from './api/chats';
35
43
  import { FlowsAPI } from './api/flows';
36
44
  import { FlowRunsAPI } from './api/flow-runs';
37
45
  import { EnginesAPI } from './api/engines';
46
+ import { KnowledgeAPI, SkillsAPI } from './api/knowledge';
47
+ import { TeamsAPI } from './api/teams';
48
+ import { SecretsAPI } from './api/secrets';
49
+ import { ApiKeysAPI } from './api/api-keys';
50
+ import { IntegrationsAPI } from './api/integrations';
51
+ import { SearchAPI } from './api/search';
52
+ import { ProjectsAPI } from './api/projects';
53
+ import { MCPServersAPI } from './api/mcp-servers';
38
54
  /**
39
55
  * Inference.sh SDK Client
40
56
  *
@@ -67,6 +83,15 @@ export class Inference {
67
83
  this.flows = new FlowsAPI(this.http);
68
84
  this.flowRuns = new FlowRunsAPI(this.http);
69
85
  this.engines = new EnginesAPI(this.http);
86
+ this.knowledge = new KnowledgeAPI(this.http);
87
+ this.skills = new SkillsAPI(this.http);
88
+ this.teams = new TeamsAPI(this.http);
89
+ this.secrets = new SecretsAPI(this.http);
90
+ this.apiKeys = new ApiKeysAPI(this.http);
91
+ this.integrations = new IntegrationsAPI(this.http);
92
+ this.search = new SearchAPI(this.http);
93
+ this.projects = new ProjectsAPI(this.http);
94
+ this.mcpServers = new MCPServersAPI(this.http);
70
95
  }
71
96
  // Legacy methods for backward compatibility
72
97
  /** @internal */
@@ -1,5 +1,5 @@
1
1
  import { tool, appTool, agentTool, webhookTool, httpTool, callTool, mcpTool, internalTools, string, number, integer, boolean, enumOf, object, array, optional, } from './tool-builder';
2
- import { ToolTypeClient, ToolTypeApp, ToolTypeAgent, ToolTypeHook, ToolTypeHTTP, ToolTypeMCP, IntegrationProviderGoogle, } from './types';
2
+ import { ToolTypeClient, ToolTypeApp, ToolTypeAgent, ToolTypeHook, ToolTypeHTTP, ToolTypeMCP, IntegrationProviderGoogle, IntegrationProviderGoogleSA, } from './types';
3
3
  describe('Schema Helpers', () => {
4
4
  describe('string', () => {
5
5
  it('creates string schema without description', () => {
@@ -276,6 +276,16 @@ describe('HTTPToolBuilder (httpTool)', () => {
276
276
  integration_id: 'int-123',
277
277
  });
278
278
  });
279
+ it('should attach integration auth for google-sa service account provider', () => {
280
+ const t = httpTool('calendar_read', 'https://api.example.com/calendar')
281
+ .auth({ integration: IntegrationProviderGoogleSA, integrationId: 'sa-int-1' })
282
+ .build();
283
+ expect(t.http?.auth).toEqual({
284
+ type: 'integration',
285
+ provider: IntegrationProviderGoogleSA,
286
+ integration_id: 'sa-int-1',
287
+ });
288
+ });
279
289
  it('should attach api key auth with default header', () => {
280
290
  const t = httpTool('fetch', 'https://api.example.com').auth({ apiKey: 'KEY' }).build();
281
291
  expect(t.http?.auth).toEqual({