@inferencesh/sdk 0.6.8 → 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.
- package/CHANGELOG.md +7 -1
- package/README.md +70 -1
- package/dist/agent/actions.js +3 -3
- package/dist/agent/actions.test.js +89 -0
- package/dist/agent/api.test.js +86 -8
- package/dist/api/agents.d.ts +10 -0
- package/dist/api/agents.js +17 -3
- package/dist/api/agents.test.js +303 -92
- package/dist/api/api-keys.d.ts +24 -0
- package/dist/api/api-keys.js +26 -0
- package/dist/api/api-keys.test.d.ts +1 -0
- package/dist/api/api-keys.test.js +44 -0
- package/dist/api/apps.js +1 -1
- package/dist/api/apps.test.d.ts +1 -0
- package/dist/api/apps.test.js +135 -0
- package/dist/api/chats.d.ts +14 -0
- package/dist/api/chats.js +18 -0
- package/dist/api/chats.test.d.ts +1 -0
- package/dist/api/chats.test.js +85 -0
- package/dist/api/engines.d.ts +12 -0
- package/dist/api/engines.js +18 -0
- package/dist/api/engines.test.d.ts +1 -0
- package/dist/api/engines.test.js +133 -0
- package/dist/api/files.test.js +186 -6
- package/dist/api/flow-runs.test.d.ts +1 -0
- package/dist/api/flow-runs.test.js +97 -0
- package/dist/api/flows.d.ts +4 -0
- package/dist/api/flows.js +6 -0
- package/dist/api/flows.test.d.ts +1 -0
- package/dist/api/flows.test.js +118 -0
- package/dist/api/integrations.d.ts +41 -0
- package/dist/api/integrations.js +56 -0
- package/dist/api/integrations.test.d.ts +1 -0
- package/dist/api/integrations.test.js +109 -0
- package/dist/api/knowledge.d.ts +112 -0
- package/dist/api/knowledge.js +163 -0
- package/dist/api/knowledge.test.d.ts +1 -0
- package/dist/api/knowledge.test.js +94 -0
- package/dist/api/mcp-servers.d.ts +45 -0
- package/dist/api/mcp-servers.js +62 -0
- package/dist/api/mcp-servers.test.d.ts +1 -0
- package/dist/api/mcp-servers.test.js +64 -0
- package/dist/api/projects.d.ts +29 -0
- package/dist/api/projects.js +38 -0
- package/dist/api/projects.test.d.ts +1 -0
- package/dist/api/projects.test.js +52 -0
- package/dist/api/search.d.ts +21 -0
- package/dist/api/search.js +20 -0
- package/dist/api/search.test.d.ts +1 -0
- package/dist/api/search.test.js +39 -0
- package/dist/api/secrets.d.ts +29 -0
- package/dist/api/secrets.js +38 -0
- package/dist/api/secrets.test.d.ts +1 -0
- package/dist/api/secrets.test.js +61 -0
- package/dist/api/sessions.test.js +4 -4
- package/dist/api/tasks.d.ts +13 -1
- package/dist/api/tasks.js +18 -0
- package/dist/api/tasks.test.js +174 -22
- package/dist/api/teams.d.ts +71 -0
- package/dist/api/teams.js +92 -0
- package/dist/api/teams.test.d.ts +1 -0
- package/dist/api/teams.test.js +79 -0
- package/dist/client.test.js +8 -8
- package/dist/http/client.d.ts +5 -0
- package/dist/http/client.js +46 -48
- package/dist/http/client.test.js +296 -13
- package/dist/http/errors.test.js +13 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/proxy/express.test.d.ts +1 -0
- package/dist/proxy/express.test.js +106 -0
- package/dist/proxy/index.test.js +10 -1
- package/dist/tool-builder.test.js +11 -1
- package/dist/types.d.ts +793 -23
- package/dist/types.js +180 -8
- package/package.json +4 -4
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects API
|
|
3
|
+
*/
|
|
4
|
+
export class ProjectsAPI {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* List projects with cursor-based pagination
|
|
10
|
+
*/
|
|
11
|
+
async list(params) {
|
|
12
|
+
return this.http.request('post', '/projects/list', { data: params });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get a project by ID
|
|
16
|
+
*/
|
|
17
|
+
async get(id) {
|
|
18
|
+
return this.http.request('get', `/projects/${id}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a project
|
|
22
|
+
*/
|
|
23
|
+
async create(data) {
|
|
24
|
+
return this.http.request('post', '/projects', { data });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Update a project
|
|
28
|
+
*/
|
|
29
|
+
async update(id, data) {
|
|
30
|
+
return this.http.request('post', `/projects/${id}`, { data });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Delete a project
|
|
34
|
+
*/
|
|
35
|
+
async delete(id) {
|
|
36
|
+
return this.http.request('delete', `/projects/${id}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { HttpClient } from '../http/client';
|
|
2
|
+
import { ProjectsAPI } from './projects';
|
|
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('ProjectsAPI', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
const api = () => new ProjectsAPI(new HttpClient({ apiKey: 'test-key' }));
|
|
17
|
+
it('should POST /projects/list for list()', async () => {
|
|
18
|
+
const page = { items: [{ id: 'proj-1' }], next_cursor: null };
|
|
19
|
+
mockJsonResponse(page);
|
|
20
|
+
const result = await api().list({ limit: 20 });
|
|
21
|
+
expect(result).toEqual(page);
|
|
22
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
23
|
+
expect(url).toContain('/projects/list');
|
|
24
|
+
expect(init.method).toBe('POST');
|
|
25
|
+
});
|
|
26
|
+
it('should GET /projects/{id} for get()', async () => {
|
|
27
|
+
const project = { id: 'proj-1', name: 'Demo' };
|
|
28
|
+
mockJsonResponse(project);
|
|
29
|
+
const result = await api().get('proj-1');
|
|
30
|
+
expect(result).toEqual(project);
|
|
31
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
32
|
+
expect(url).toContain('/projects/proj-1');
|
|
33
|
+
expect(init.method).toBe('GET');
|
|
34
|
+
});
|
|
35
|
+
it('should POST /projects for create()', async () => {
|
|
36
|
+
const payload = { name: 'New Project' };
|
|
37
|
+
const project = { id: 'proj-new', ...payload };
|
|
38
|
+
mockJsonResponse(project);
|
|
39
|
+
const result = await api().create(payload);
|
|
40
|
+
expect(result).toEqual(project);
|
|
41
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
42
|
+
expect(url).toContain('/projects');
|
|
43
|
+
expect(init.method).toBe('POST');
|
|
44
|
+
});
|
|
45
|
+
it('should DELETE /projects/{id} for delete()', async () => {
|
|
46
|
+
mockJsonResponse(null);
|
|
47
|
+
await api().delete('proj-1');
|
|
48
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
49
|
+
expect(url).toContain('/projects/proj-1');
|
|
50
|
+
expect(init.method).toBe('DELETE');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HttpClient } from '../http/client';
|
|
2
|
+
import { SuggestRequest, SuggestResponse } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Search API
|
|
5
|
+
*/
|
|
6
|
+
export declare class SearchAPI {
|
|
7
|
+
private readonly http;
|
|
8
|
+
constructor(http: HttpClient);
|
|
9
|
+
/**
|
|
10
|
+
* Unified search across skills, knowledge, and apps
|
|
11
|
+
*/
|
|
12
|
+
suggest(params: Partial<SuggestRequest>): Promise<SuggestResponse>;
|
|
13
|
+
/**
|
|
14
|
+
* Full-text search via Meilisearch
|
|
15
|
+
*/
|
|
16
|
+
search(params: {
|
|
17
|
+
q: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}): Promise<unknown>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search API
|
|
3
|
+
*/
|
|
4
|
+
export class SearchAPI {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Unified search across skills, knowledge, and apps
|
|
10
|
+
*/
|
|
11
|
+
async suggest(params) {
|
|
12
|
+
return this.http.request('post', '/suggest', { data: params });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Full-text search via Meilisearch
|
|
16
|
+
*/
|
|
17
|
+
async search(params) {
|
|
18
|
+
return this.http.request('post', '/search', { data: params });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { HttpClient } from '../http/client';
|
|
2
|
+
import { SearchAPI } from './search';
|
|
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('SearchAPI', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
const api = () => new SearchAPI(new HttpClient({ apiKey: 'test-key' }));
|
|
17
|
+
it('should POST /suggest for suggest()', async () => {
|
|
18
|
+
const payload = { query: 'image gen', types: ['apps', 'skills'] };
|
|
19
|
+
const response = { results: [{ type: 'app', id: 'app-1' }] };
|
|
20
|
+
mockJsonResponse(response);
|
|
21
|
+
const result = await api().suggest(payload);
|
|
22
|
+
expect(result).toEqual(response);
|
|
23
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
24
|
+
expect(url).toContain('/suggest');
|
|
25
|
+
expect(init.method).toBe('POST');
|
|
26
|
+
expect(JSON.parse(init.body)).toEqual(payload);
|
|
27
|
+
});
|
|
28
|
+
it('should POST /search for search()', async () => {
|
|
29
|
+
const payload = { q: 'claude', type: 'apps', limit: 5 };
|
|
30
|
+
const response = { hits: [{ id: 'app-1' }] };
|
|
31
|
+
mockJsonResponse(response);
|
|
32
|
+
const result = await api().search(payload);
|
|
33
|
+
expect(result).toEqual(response);
|
|
34
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
35
|
+
expect(url).toContain('/search');
|
|
36
|
+
expect(init.method).toBe('POST');
|
|
37
|
+
expect(JSON.parse(init.body)).toEqual(payload);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { HttpClient } from '../http/client';
|
|
2
|
+
import { SecretDTO, SecretCreateRequest, CursorListRequest, CursorListResponse } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Secrets API
|
|
5
|
+
*/
|
|
6
|
+
export declare class SecretsAPI {
|
|
7
|
+
private readonly http;
|
|
8
|
+
constructor(http: HttpClient);
|
|
9
|
+
/**
|
|
10
|
+
* List secrets with cursor-based pagination
|
|
11
|
+
*/
|
|
12
|
+
list(params?: Partial<CursorListRequest>): Promise<CursorListResponse<SecretDTO>>;
|
|
13
|
+
/**
|
|
14
|
+
* Create a secret
|
|
15
|
+
*/
|
|
16
|
+
create(data: SecretCreateRequest): Promise<SecretDTO>;
|
|
17
|
+
/**
|
|
18
|
+
* Update a secret
|
|
19
|
+
*/
|
|
20
|
+
update(key: string, data: Partial<SecretCreateRequest>): Promise<SecretDTO>;
|
|
21
|
+
/**
|
|
22
|
+
* Delete a secret
|
|
23
|
+
*/
|
|
24
|
+
delete(key: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Reveal a secret value
|
|
27
|
+
*/
|
|
28
|
+
reveal(key: string): Promise<SecretDTO>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets API
|
|
3
|
+
*/
|
|
4
|
+
export class SecretsAPI {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* List secrets with cursor-based pagination
|
|
10
|
+
*/
|
|
11
|
+
async list(params) {
|
|
12
|
+
return this.http.request('post', '/secrets/list', { data: params });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create a secret
|
|
16
|
+
*/
|
|
17
|
+
async create(data) {
|
|
18
|
+
return this.http.request('post', '/secrets', { data });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Update a secret
|
|
22
|
+
*/
|
|
23
|
+
async update(key, data) {
|
|
24
|
+
return this.http.request('put', `/secrets/${key}`, { data });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Delete a secret
|
|
28
|
+
*/
|
|
29
|
+
async delete(key) {
|
|
30
|
+
return this.http.request('delete', `/secrets/${key}`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Reveal a secret value
|
|
34
|
+
*/
|
|
35
|
+
async reveal(key) {
|
|
36
|
+
return this.http.request('get', `/secrets/reveal/${key}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { HttpClient } from '../http/client';
|
|
2
|
+
import { SecretsAPI } from './secrets';
|
|
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('SecretsAPI', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
const api = () => new SecretsAPI(new HttpClient({ apiKey: 'test-key' }));
|
|
17
|
+
it('should POST /secrets/list for list()', async () => {
|
|
18
|
+
const page = { items: [{ key: 'API_KEY' }], next_cursor: null };
|
|
19
|
+
mockJsonResponse(page);
|
|
20
|
+
const result = await api().list({ limit: 10 });
|
|
21
|
+
expect(result).toEqual(page);
|
|
22
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
23
|
+
expect(url).toContain('/secrets/list');
|
|
24
|
+
expect(init.method).toBe('POST');
|
|
25
|
+
});
|
|
26
|
+
it('should POST /secrets for create()', async () => {
|
|
27
|
+
const payload = { key: 'DB_PASSWORD', value: 'secret' };
|
|
28
|
+
const secret = { key: 'DB_PASSWORD' };
|
|
29
|
+
mockJsonResponse(secret);
|
|
30
|
+
const result = await api().create(payload);
|
|
31
|
+
expect(result).toEqual(secret);
|
|
32
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
33
|
+
expect(url).toContain('/secrets');
|
|
34
|
+
expect(init.method).toBe('POST');
|
|
35
|
+
expect(JSON.parse(init.body)).toEqual(payload);
|
|
36
|
+
});
|
|
37
|
+
it('should PUT /secrets/{key} for update()', async () => {
|
|
38
|
+
const secret = { key: 'DB_PASSWORD' };
|
|
39
|
+
mockJsonResponse(secret);
|
|
40
|
+
await api().update('DB_PASSWORD', { value: 'new-secret' });
|
|
41
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
42
|
+
expect(url).toContain('/secrets/DB_PASSWORD');
|
|
43
|
+
expect(init.method).toBe('PUT');
|
|
44
|
+
});
|
|
45
|
+
it('should GET /secrets/reveal/{key} for reveal()', async () => {
|
|
46
|
+
const secret = { key: 'API_KEY', value: 'sk-live-abc' };
|
|
47
|
+
mockJsonResponse(secret);
|
|
48
|
+
const result = await api().reveal('API_KEY');
|
|
49
|
+
expect(result).toEqual(secret);
|
|
50
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
51
|
+
expect(url).toContain('/secrets/reveal/API_KEY');
|
|
52
|
+
expect(init.method).toBe('GET');
|
|
53
|
+
});
|
|
54
|
+
it('should DELETE /secrets/{key} for delete()', async () => {
|
|
55
|
+
mockJsonResponse(null);
|
|
56
|
+
await api().delete('OLD_KEY');
|
|
57
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
58
|
+
expect(url).toContain('/secrets/OLD_KEY');
|
|
59
|
+
expect(init.method).toBe('DELETE');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -16,7 +16,7 @@ describe('SessionsAPI', () => {
|
|
|
16
16
|
const api = () => new SessionsAPI(new HttpClient({ apiKey: 'test-key' }));
|
|
17
17
|
it('should GET /sessions/{id} for get()', async () => {
|
|
18
18
|
const session = { id: 'sess_1', status: 'active' };
|
|
19
|
-
mockJsonResponse(
|
|
19
|
+
mockJsonResponse(session);
|
|
20
20
|
const result = await api().get('sess_1');
|
|
21
21
|
expect(result).toEqual(session);
|
|
22
22
|
const [url, init] = mockFetch.mock.calls[0];
|
|
@@ -24,7 +24,7 @@ describe('SessionsAPI', () => {
|
|
|
24
24
|
expect(init.method).toBe('GET');
|
|
25
25
|
});
|
|
26
26
|
it('should return an empty array when list() response is null', async () => {
|
|
27
|
-
mockJsonResponse(
|
|
27
|
+
mockJsonResponse(null);
|
|
28
28
|
const result = await api().list();
|
|
29
29
|
expect(result).toEqual([]);
|
|
30
30
|
const [url] = mockFetch.mock.calls[0];
|
|
@@ -32,7 +32,7 @@ describe('SessionsAPI', () => {
|
|
|
32
32
|
});
|
|
33
33
|
it('should POST /sessions/{id}/keepalive for keepalive()', async () => {
|
|
34
34
|
const session = { id: 'sess_2', status: 'active' };
|
|
35
|
-
mockJsonResponse(
|
|
35
|
+
mockJsonResponse(session);
|
|
36
36
|
const result = await api().keepalive('sess_2');
|
|
37
37
|
expect(result).toEqual(session);
|
|
38
38
|
const [url, init] = mockFetch.mock.calls[0];
|
|
@@ -40,7 +40,7 @@ describe('SessionsAPI', () => {
|
|
|
40
40
|
expect(init.method).toBe('POST');
|
|
41
41
|
});
|
|
42
42
|
it('should DELETE /sessions/{id} for end()', async () => {
|
|
43
|
-
mockJsonResponse(
|
|
43
|
+
mockJsonResponse(null);
|
|
44
44
|
await api().end('sess_3');
|
|
45
45
|
const [url, init] = mockFetch.mock.calls[0];
|
|
46
46
|
expect(url).toContain('/sessions/sess_3');
|
package/dist/api/tasks.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpClient } from '../http/client';
|
|
2
|
-
import { TaskDTO as Task, ApiAppRunRequest, CursorListRequest, CursorListResponse } from '../types';
|
|
2
|
+
import { TaskDTO as Task, TaskLogsDTO, TaskTimingsDTO, ApiAppRunRequest, CursorListRequest, CursorListResponse } from '../types';
|
|
3
3
|
export interface RunOptions {
|
|
4
4
|
/** Callback for real-time status updates */
|
|
5
5
|
onUpdate?: (update: Task) => void;
|
|
@@ -62,5 +62,17 @@ export declare class TasksAPI {
|
|
|
62
62
|
* Feature/unfeature a task
|
|
63
63
|
*/
|
|
64
64
|
feature(taskId: string, featured: boolean): Promise<Task>;
|
|
65
|
+
/**
|
|
66
|
+
* Get task logs
|
|
67
|
+
*/
|
|
68
|
+
getLogs(taskId: string): Promise<TaskLogsDTO>;
|
|
69
|
+
/**
|
|
70
|
+
* Get task timings
|
|
71
|
+
*/
|
|
72
|
+
getTimings(taskId: string): Promise<TaskTimingsDTO>;
|
|
73
|
+
/**
|
|
74
|
+
* Get task telemetry
|
|
75
|
+
*/
|
|
76
|
+
getTelemetry(taskId: string): Promise<Record<string, unknown>[]>;
|
|
65
77
|
}
|
|
66
78
|
export declare function createTasksAPI(http: HttpClient): TasksAPI;
|
package/dist/api/tasks.js
CHANGED
|
@@ -193,6 +193,24 @@ export class TasksAPI {
|
|
|
193
193
|
async feature(taskId, featured) {
|
|
194
194
|
return this.http.request('post', `/tasks/${taskId}/featured`, { data: { is_featured: featured } });
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Get task logs
|
|
198
|
+
*/
|
|
199
|
+
async getLogs(taskId) {
|
|
200
|
+
return this.http.request('get', `/tasks/${taskId}/logs`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get task timings
|
|
204
|
+
*/
|
|
205
|
+
async getTimings(taskId) {
|
|
206
|
+
return this.http.request('get', `/tasks/${taskId}/timings`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get task telemetry
|
|
210
|
+
*/
|
|
211
|
+
async getTelemetry(taskId) {
|
|
212
|
+
return this.http.request('get', `/tasks/${taskId}/telemetry`);
|
|
213
|
+
}
|
|
196
214
|
}
|
|
197
215
|
export function createTasksAPI(http) {
|
|
198
216
|
return new TasksAPI(http);
|
package/dist/api/tasks.test.js
CHANGED
|
@@ -35,10 +35,10 @@ describe('TasksAPI.run (polling mode)', () => {
|
|
|
35
35
|
it('should resolve when status polling detects completion', async () => {
|
|
36
36
|
const runningTask = makeTask();
|
|
37
37
|
const completedTask = makeTask({ status: TaskStatusCompleted, output: { ok: true } });
|
|
38
|
-
mockJsonResponse(
|
|
39
|
-
mockJsonResponse({
|
|
40
|
-
mockJsonResponse({
|
|
41
|
-
mockJsonResponse(
|
|
38
|
+
mockJsonResponse(runningTask);
|
|
39
|
+
mockJsonResponse({ status: TaskStatusRunning });
|
|
40
|
+
mockJsonResponse({ status: TaskStatusCompleted });
|
|
41
|
+
mockJsonResponse(completedTask);
|
|
42
42
|
const onUpdate = jest.fn();
|
|
43
43
|
const result = await api().run({ app: 'test-app', input: {} }, { prompt: 'hi' }, { wait: true, stream: false, onUpdate });
|
|
44
44
|
expect(result.status).toBe(TaskStatusCompleted);
|
|
@@ -47,42 +47,56 @@ describe('TasksAPI.run (polling mode)', () => {
|
|
|
47
47
|
it('should reject when polling detects a failed task', async () => {
|
|
48
48
|
const runningTask = makeTask();
|
|
49
49
|
const failedTask = makeTask({ status: TaskStatusFailed, error: 'model error' });
|
|
50
|
-
mockJsonResponse(
|
|
51
|
-
mockJsonResponse({
|
|
52
|
-
mockJsonResponse({
|
|
53
|
-
mockJsonResponse(
|
|
50
|
+
mockJsonResponse(runningTask);
|
|
51
|
+
mockJsonResponse({ status: TaskStatusRunning });
|
|
52
|
+
mockJsonResponse({ status: TaskStatusFailed });
|
|
53
|
+
mockJsonResponse(failedTask);
|
|
54
54
|
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true, stream: false })).rejects.toThrow('model error');
|
|
55
55
|
});
|
|
56
56
|
it('should reject when polling detects a cancelled task', async () => {
|
|
57
57
|
const runningTask = makeTask();
|
|
58
58
|
const cancelledTask = makeTask({ status: TaskStatusCancelled });
|
|
59
|
-
mockJsonResponse(
|
|
60
|
-
mockJsonResponse({
|
|
61
|
-
mockJsonResponse({
|
|
62
|
-
mockJsonResponse(
|
|
59
|
+
mockJsonResponse(runningTask);
|
|
60
|
+
mockJsonResponse({ status: TaskStatusRunning });
|
|
61
|
+
mockJsonResponse({ status: TaskStatusCancelled });
|
|
62
|
+
mockJsonResponse(cancelledTask);
|
|
63
63
|
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true, stream: false })).rejects.toThrow('task cancelled');
|
|
64
64
|
});
|
|
65
65
|
it('should reject when full task fetch fails after status change', async () => {
|
|
66
66
|
const runningTask = makeTask();
|
|
67
|
-
mockJsonResponse(
|
|
68
|
-
mockJsonResponse({
|
|
69
|
-
mockJsonResponse({
|
|
67
|
+
mockJsonResponse(runningTask);
|
|
68
|
+
mockJsonResponse({ status: TaskStatusRunning });
|
|
69
|
+
mockJsonResponse({ status: TaskStatusCompleted });
|
|
70
70
|
mockFetch.mockRejectedValueOnce(new Error('network down'));
|
|
71
71
|
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true, stream: false })).rejects.toThrow('network down');
|
|
72
72
|
});
|
|
73
73
|
it('should reject when status polling fails', async () => {
|
|
74
74
|
const runningTask = makeTask();
|
|
75
|
-
mockJsonResponse(
|
|
75
|
+
mockJsonResponse(runningTask);
|
|
76
76
|
mockFetch.mockRejectedValueOnce(new Error('status endpoint down'));
|
|
77
77
|
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true, stream: false })).rejects.toThrow('status endpoint down');
|
|
78
78
|
});
|
|
79
|
+
it('should not refetch full task when poll status is unchanged', async () => {
|
|
80
|
+
const runningTask = makeTask();
|
|
81
|
+
const completedTask = makeTask({ status: TaskStatusCompleted, output: { ok: true } });
|
|
82
|
+
mockJsonResponse(runningTask);
|
|
83
|
+
mockJsonResponse({ status: TaskStatusRunning });
|
|
84
|
+
mockJsonResponse({ status: TaskStatusRunning });
|
|
85
|
+
mockJsonResponse({ status: TaskStatusCompleted });
|
|
86
|
+
mockJsonResponse(completedTask);
|
|
87
|
+
await api().run({ app: 'test-app', input: {} }, {}, { wait: true, stream: false });
|
|
88
|
+
const fullTaskGets = mockFetch.mock.calls.filter(([url, init]) => String(url).includes('/tasks/task-1') &&
|
|
89
|
+
!String(url).includes('/status') &&
|
|
90
|
+
init.method === 'GET');
|
|
91
|
+
expect(fullTaskGets).toHaveLength(1);
|
|
92
|
+
});
|
|
79
93
|
it('should parse string terminal statuses from the status endpoint', async () => {
|
|
80
94
|
const runningTask = makeTask();
|
|
81
95
|
const completedTask = makeTask({ status: TaskStatusCompleted });
|
|
82
|
-
mockJsonResponse(
|
|
83
|
-
mockJsonResponse({
|
|
84
|
-
mockJsonResponse({
|
|
85
|
-
mockJsonResponse(
|
|
96
|
+
mockJsonResponse(runningTask);
|
|
97
|
+
mockJsonResponse({ status: TaskStatusRunning });
|
|
98
|
+
mockJsonResponse({ status: 'completed' });
|
|
99
|
+
mockJsonResponse(completedTask);
|
|
86
100
|
const result = await api().run({ app: 'test-app', input: {} }, {}, { wait: true, stream: false });
|
|
87
101
|
expect(result.status).toBe(TaskStatusCompleted);
|
|
88
102
|
});
|
|
@@ -111,7 +125,7 @@ describe('TasksAPI.run (general)', () => {
|
|
|
111
125
|
const streamingApi = () => new TasksAPI(new HttpClient({ apiKey: 'test-key', stream: true }));
|
|
112
126
|
it('should return immediately when wait is false', async () => {
|
|
113
127
|
const task = makeTask();
|
|
114
|
-
mockJsonResponse(
|
|
128
|
+
mockJsonResponse(task);
|
|
115
129
|
const result = await streamingApi().run({ app: 'test-app', input: {} }, { prompt: 'hi' }, { wait: false });
|
|
116
130
|
expect(result.id).toBe('task-1');
|
|
117
131
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -128,7 +142,7 @@ describe('TasksAPI.run (streaming mode)', () => {
|
|
|
128
142
|
return Promise.resolve({
|
|
129
143
|
ok: true,
|
|
130
144
|
status: 200,
|
|
131
|
-
text: () => Promise.resolve(JSON.stringify(
|
|
145
|
+
text: () => Promise.resolve(JSON.stringify(initialTask)),
|
|
132
146
|
});
|
|
133
147
|
}
|
|
134
148
|
return Promise.resolve(mockNdjsonStream(ndjsonChunks));
|
|
@@ -156,6 +170,24 @@ describe('TasksAPI.run (streaming mode)', () => {
|
|
|
156
170
|
]);
|
|
157
171
|
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true })).rejects.toThrow('task cancelled');
|
|
158
172
|
});
|
|
173
|
+
it('should reject when partial stream reports failure', async () => {
|
|
174
|
+
setupStreamMocks([
|
|
175
|
+
`${JSON.stringify({
|
|
176
|
+
data: { status: TaskStatusFailed, id: 'task-1', error: 'partial fail' },
|
|
177
|
+
fields: ['status'],
|
|
178
|
+
})}\n`,
|
|
179
|
+
]);
|
|
180
|
+
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true })).rejects.toThrow('partial fail');
|
|
181
|
+
});
|
|
182
|
+
it('should reject when partial stream reports cancellation', async () => {
|
|
183
|
+
setupStreamMocks([
|
|
184
|
+
`${JSON.stringify({
|
|
185
|
+
data: { status: TaskStatusCancelled, id: 'task-1' },
|
|
186
|
+
fields: ['status'],
|
|
187
|
+
})}\n`,
|
|
188
|
+
]);
|
|
189
|
+
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true })).rejects.toThrow('task cancelled');
|
|
190
|
+
});
|
|
159
191
|
it('should handle partial updates via onPartialUpdate', async () => {
|
|
160
192
|
setupStreamMocks([
|
|
161
193
|
`${JSON.stringify({
|
|
@@ -169,3 +201,123 @@ describe('TasksAPI.run (streaming mode)', () => {
|
|
|
169
201
|
expect(onPartialUpdate).toHaveBeenCalledWith(expect.objectContaining({ id: 'task-1', status: TaskStatusCompleted }), ['status']);
|
|
170
202
|
});
|
|
171
203
|
});
|
|
204
|
+
describe('TasksAPI.create', () => {
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
jest.clearAllMocks();
|
|
207
|
+
});
|
|
208
|
+
const api = () => new TasksAPI(new HttpClient({ apiKey: 'test-key' }));
|
|
209
|
+
it('should POST /apps/run for create()', async () => {
|
|
210
|
+
const task = makeTask();
|
|
211
|
+
mockJsonResponse(task);
|
|
212
|
+
const result = await api().create({ app: 'test-app', input: { prompt: 'hi' } });
|
|
213
|
+
expect(result).toEqual(task);
|
|
214
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
215
|
+
expect(url).toContain('/apps/run');
|
|
216
|
+
expect(init.method).toBe('POST');
|
|
217
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
218
|
+
app: 'test-app',
|
|
219
|
+
input: { prompt: 'hi' },
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('TasksAPI (CRUD and admin)', () => {
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
jest.clearAllMocks();
|
|
226
|
+
});
|
|
227
|
+
const api = () => new TasksAPI(new HttpClient({ apiKey: 'test-key' }));
|
|
228
|
+
it('should POST /tasks/list for list()', async () => {
|
|
229
|
+
const page = { items: [{ id: 'task-1' }], next_cursor: null };
|
|
230
|
+
mockJsonResponse(page);
|
|
231
|
+
const result = await api().list({ limit: 5 });
|
|
232
|
+
expect(result).toEqual(page);
|
|
233
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
234
|
+
expect(url).toContain('/tasks/list');
|
|
235
|
+
expect(init.method).toBe('POST');
|
|
236
|
+
expect(JSON.parse(init.body)).toEqual({ limit: 5 });
|
|
237
|
+
});
|
|
238
|
+
it('should GET /tasks/featured for listFeatured()', async () => {
|
|
239
|
+
const page = { items: [{ id: 'task-f' }], next_cursor: null };
|
|
240
|
+
mockJsonResponse(page);
|
|
241
|
+
const result = await api().listFeatured({ cursor: 'abc' });
|
|
242
|
+
expect(result).toEqual(page);
|
|
243
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
244
|
+
expect(url).toContain('/tasks/featured');
|
|
245
|
+
expect(init.method).toBe('GET');
|
|
246
|
+
});
|
|
247
|
+
it('should GET /tasks/{id} for get()', async () => {
|
|
248
|
+
const task = makeTask();
|
|
249
|
+
mockJsonResponse(task);
|
|
250
|
+
const result = await api().get('task-1');
|
|
251
|
+
expect(result).toEqual(task);
|
|
252
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
253
|
+
expect(url).toContain('/tasks/task-1');
|
|
254
|
+
expect(init.method).toBe('GET');
|
|
255
|
+
});
|
|
256
|
+
it('should DELETE /tasks/{id} for delete()', async () => {
|
|
257
|
+
mockJsonResponse(null);
|
|
258
|
+
await api().delete('task-1');
|
|
259
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
260
|
+
expect(url).toContain('/tasks/task-1');
|
|
261
|
+
expect(init.method).toBe('DELETE');
|
|
262
|
+
});
|
|
263
|
+
it('should POST /tasks/{id}/cancel for cancel()', async () => {
|
|
264
|
+
mockJsonResponse(null);
|
|
265
|
+
await api().cancel('task-1');
|
|
266
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
267
|
+
expect(url).toContain('/tasks/task-1/cancel');
|
|
268
|
+
expect(init.method).toBe('POST');
|
|
269
|
+
});
|
|
270
|
+
it('should POST visibility for updateVisibility()', async () => {
|
|
271
|
+
const task = makeTask({ visibility: 'public' });
|
|
272
|
+
mockJsonResponse(task);
|
|
273
|
+
const result = await api().updateVisibility('task-1', 'public');
|
|
274
|
+
expect(result).toEqual(task);
|
|
275
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
276
|
+
expect(JSON.parse(init.body)).toEqual({ visibility: 'public' });
|
|
277
|
+
});
|
|
278
|
+
it('should POST is_featured for feature()', async () => {
|
|
279
|
+
const task = makeTask({ is_featured: true });
|
|
280
|
+
mockJsonResponse(task);
|
|
281
|
+
await api().feature('task-1', true);
|
|
282
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
283
|
+
expect(url).toContain('/tasks/task-1/featured');
|
|
284
|
+
expect(JSON.parse(init.body)).toEqual({ is_featured: true });
|
|
285
|
+
});
|
|
286
|
+
it('should open SSE on /tasks/{id}/stream for stream()', async () => {
|
|
287
|
+
const http = new HttpClient({ apiKey: 'test-key' });
|
|
288
|
+
const createEventSource = jest
|
|
289
|
+
.spyOn(http, 'createEventSource')
|
|
290
|
+
.mockResolvedValue(null);
|
|
291
|
+
const tasks = new TasksAPI(http);
|
|
292
|
+
await tasks.stream('task-42');
|
|
293
|
+
expect(createEventSource).toHaveBeenCalledWith('/tasks/task-42/stream');
|
|
294
|
+
createEventSource.mockRestore();
|
|
295
|
+
});
|
|
296
|
+
it('should GET /tasks/{id}/logs for getLogs()', async () => {
|
|
297
|
+
const logs = { logs: [{ message: 'started', timestamp: '2026-01-01T00:00:00Z' }] };
|
|
298
|
+
mockJsonResponse(logs);
|
|
299
|
+
const result = await api().getLogs('task-1');
|
|
300
|
+
expect(result).toEqual(logs);
|
|
301
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
302
|
+
expect(url).toContain('/tasks/task-1/logs');
|
|
303
|
+
expect(init.method).toBe('GET');
|
|
304
|
+
});
|
|
305
|
+
it('should GET /tasks/{id}/timings for getTimings()', async () => {
|
|
306
|
+
const timings = { total_ms: 1200, stages: [{ name: 'inference', ms: 900 }] };
|
|
307
|
+
mockJsonResponse(timings);
|
|
308
|
+
const result = await api().getTimings('task-1');
|
|
309
|
+
expect(result).toEqual(timings);
|
|
310
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
311
|
+
expect(url).toContain('/tasks/task-1/timings');
|
|
312
|
+
expect(init.method).toBe('GET');
|
|
313
|
+
});
|
|
314
|
+
it('should GET /tasks/{id}/telemetry for getTelemetry()', async () => {
|
|
315
|
+
const telemetry = [{ metric: 'gpu_util', value: 0.82 }];
|
|
316
|
+
mockJsonResponse(telemetry);
|
|
317
|
+
const result = await api().getTelemetry('task-1');
|
|
318
|
+
expect(result).toEqual(telemetry);
|
|
319
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
320
|
+
expect(url).toContain('/tasks/task-1/telemetry');
|
|
321
|
+
expect(init.method).toBe('GET');
|
|
322
|
+
});
|
|
323
|
+
});
|