@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,71 @@
|
|
|
1
|
+
import { HttpClient } from '../http/client';
|
|
2
|
+
import { UserDTO, TeamRelationDTO, TeamMemberDTO, TeamInviteDTO, TeamCreateRequest, TeamMemberAddRequest, TeamInviteCreateRequest } from '../types';
|
|
3
|
+
export interface MeResponse {
|
|
4
|
+
user: UserDTO;
|
|
5
|
+
team?: TeamRelationDTO;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Teams API
|
|
9
|
+
*/
|
|
10
|
+
export declare class TeamsAPI {
|
|
11
|
+
private readonly http;
|
|
12
|
+
constructor(http: HttpClient);
|
|
13
|
+
/**
|
|
14
|
+
* Get current user and team context
|
|
15
|
+
*/
|
|
16
|
+
me(): Promise<MeResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* List user's teams
|
|
19
|
+
*/
|
|
20
|
+
list(): Promise<TeamRelationDTO[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Get a team by ID
|
|
23
|
+
*/
|
|
24
|
+
get(teamId: string): Promise<TeamRelationDTO>;
|
|
25
|
+
/**
|
|
26
|
+
* Create a new team
|
|
27
|
+
*/
|
|
28
|
+
create(data: TeamCreateRequest): Promise<TeamRelationDTO>;
|
|
29
|
+
/**
|
|
30
|
+
* Update a team
|
|
31
|
+
*/
|
|
32
|
+
update(teamId: string, data: Partial<TeamCreateRequest>): Promise<TeamRelationDTO>;
|
|
33
|
+
/**
|
|
34
|
+
* Delete a team
|
|
35
|
+
*/
|
|
36
|
+
delete(teamId: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Check username availability
|
|
39
|
+
*/
|
|
40
|
+
checkUsername(username: string): Promise<{
|
|
41
|
+
available: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Get team members
|
|
45
|
+
*/
|
|
46
|
+
getMembers(teamId: string): Promise<TeamMemberDTO[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Add a team member
|
|
49
|
+
*/
|
|
50
|
+
addMember(teamId: string, data: TeamMemberAddRequest): Promise<TeamMemberDTO>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove a team member
|
|
53
|
+
*/
|
|
54
|
+
removeMember(teamId: string, userId: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Update a member's role
|
|
57
|
+
*/
|
|
58
|
+
updateMemberRole(teamId: string, userId: string, role: string): Promise<TeamMemberDTO>;
|
|
59
|
+
/**
|
|
60
|
+
* List team invites
|
|
61
|
+
*/
|
|
62
|
+
listInvites(teamId: string): Promise<TeamInviteDTO[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Create an invite
|
|
65
|
+
*/
|
|
66
|
+
createInvite(teamId: string, data: TeamInviteCreateRequest): Promise<TeamInviteDTO>;
|
|
67
|
+
/**
|
|
68
|
+
* Revoke an invite
|
|
69
|
+
*/
|
|
70
|
+
revokeInvite(teamId: string, inviteId: string): Promise<void>;
|
|
71
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teams API
|
|
3
|
+
*/
|
|
4
|
+
export class TeamsAPI {
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get current user and team context
|
|
10
|
+
*/
|
|
11
|
+
async me() {
|
|
12
|
+
return this.http.request('get', '/me');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* List user's teams
|
|
16
|
+
*/
|
|
17
|
+
async list() {
|
|
18
|
+
return this.http.request('get', '/teams');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get a team by ID
|
|
22
|
+
*/
|
|
23
|
+
async get(teamId) {
|
|
24
|
+
return this.http.request('get', `/teams/${teamId}`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a new team
|
|
28
|
+
*/
|
|
29
|
+
async create(data) {
|
|
30
|
+
return this.http.request('post', '/teams', { data });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Update a team
|
|
34
|
+
*/
|
|
35
|
+
async update(teamId, data) {
|
|
36
|
+
return this.http.request('post', `/teams/${teamId}`, { data });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Delete a team
|
|
40
|
+
*/
|
|
41
|
+
async delete(teamId) {
|
|
42
|
+
return this.http.request('delete', `/teams/${teamId}`);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check username availability
|
|
46
|
+
*/
|
|
47
|
+
async checkUsername(username) {
|
|
48
|
+
return this.http.request('get', '/teams/check-username', { params: { username } });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get team members
|
|
52
|
+
*/
|
|
53
|
+
async getMembers(teamId) {
|
|
54
|
+
return this.http.request('get', `/teams/${teamId}/members`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Add a team member
|
|
58
|
+
*/
|
|
59
|
+
async addMember(teamId, data) {
|
|
60
|
+
return this.http.request('post', `/teams/${teamId}/members`, { data });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Remove a team member
|
|
64
|
+
*/
|
|
65
|
+
async removeMember(teamId, userId) {
|
|
66
|
+
return this.http.request('delete', `/teams/${teamId}/members/${userId}`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Update a member's role
|
|
70
|
+
*/
|
|
71
|
+
async updateMemberRole(teamId, userId, role) {
|
|
72
|
+
return this.http.request('post', `/teams/${teamId}/members/${userId}/role`, { data: { role } });
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* List team invites
|
|
76
|
+
*/
|
|
77
|
+
async listInvites(teamId) {
|
|
78
|
+
return this.http.request('get', `/teams/${teamId}/invites`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create an invite
|
|
82
|
+
*/
|
|
83
|
+
async createInvite(teamId, data) {
|
|
84
|
+
return this.http.request('post', `/teams/${teamId}/invites`, { data });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Revoke an invite
|
|
88
|
+
*/
|
|
89
|
+
async revokeInvite(teamId, inviteId) {
|
|
90
|
+
return this.http.request('delete', `/teams/${teamId}/invites/${inviteId}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|
package/dist/client.test.js
CHANGED
|
@@ -38,7 +38,7 @@ describe('Inference', () => {
|
|
|
38
38
|
input: { message: 'hello world' },
|
|
39
39
|
output: { result: 'success' },
|
|
40
40
|
};
|
|
41
|
-
const responseData =
|
|
41
|
+
const responseData = mockTask;
|
|
42
42
|
mockFetch.mockResolvedValueOnce({
|
|
43
43
|
ok: true,
|
|
44
44
|
status: 200,
|
|
@@ -58,7 +58,7 @@ describe('Inference', () => {
|
|
|
58
58
|
}));
|
|
59
59
|
});
|
|
60
60
|
it('should throw error on API failure', async () => {
|
|
61
|
-
const responseData = {
|
|
61
|
+
const responseData = { message: 'Invalid app' };
|
|
62
62
|
mockFetch.mockResolvedValueOnce({
|
|
63
63
|
ok: false,
|
|
64
64
|
status: 400,
|
|
@@ -141,7 +141,7 @@ describe('Inference', () => {
|
|
|
141
141
|
});
|
|
142
142
|
describe('cancel', () => {
|
|
143
143
|
it('should make a POST request to cancel endpoint', async () => {
|
|
144
|
-
const responseData =
|
|
144
|
+
const responseData = null;
|
|
145
145
|
mockFetch.mockResolvedValueOnce({
|
|
146
146
|
ok: true,
|
|
147
147
|
status: 200,
|
|
@@ -185,7 +185,7 @@ describe('namespaced APIs', () => {
|
|
|
185
185
|
updated_at: new Date().toISOString(),
|
|
186
186
|
input: { message: 'hello world' },
|
|
187
187
|
};
|
|
188
|
-
const responseData =
|
|
188
|
+
const responseData = mockTask;
|
|
189
189
|
mockFetch.mockResolvedValueOnce({
|
|
190
190
|
ok: true,
|
|
191
191
|
status: 200,
|
|
@@ -202,7 +202,7 @@ describe('namespaced APIs', () => {
|
|
|
202
202
|
});
|
|
203
203
|
it('should get task via tasks.get()', async () => {
|
|
204
204
|
const mockTask = { id: 'task-123', status: 7 };
|
|
205
|
-
const responseData =
|
|
205
|
+
const responseData = mockTask;
|
|
206
206
|
mockFetch.mockResolvedValueOnce({
|
|
207
207
|
ok: true,
|
|
208
208
|
status: 200,
|
|
@@ -215,7 +215,7 @@ describe('namespaced APIs', () => {
|
|
|
215
215
|
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/tasks/task-123'), expect.objectContaining({ method: 'GET' }));
|
|
216
216
|
});
|
|
217
217
|
it('should cancel task via tasks.cancel()', async () => {
|
|
218
|
-
const responseData =
|
|
218
|
+
const responseData = null;
|
|
219
219
|
mockFetch.mockResolvedValueOnce({
|
|
220
220
|
ok: true,
|
|
221
221
|
status: 200,
|
|
@@ -298,7 +298,7 @@ describe('uploadFile', () => {
|
|
|
298
298
|
uri: 'https://example.com/file.png',
|
|
299
299
|
upload_url: 'https://upload.example.com/signed-url',
|
|
300
300
|
};
|
|
301
|
-
const responseData =
|
|
301
|
+
const responseData = [mockFile];
|
|
302
302
|
mockFetch
|
|
303
303
|
.mockResolvedValueOnce({
|
|
304
304
|
ok: true,
|
|
@@ -322,7 +322,7 @@ describe('uploadFile', () => {
|
|
|
322
322
|
uri: 'https://example.com/file.png',
|
|
323
323
|
// Missing upload_url
|
|
324
324
|
};
|
|
325
|
-
const responseData =
|
|
325
|
+
const responseData = [mockFile];
|
|
326
326
|
mockFetch.mockResolvedValueOnce({
|
|
327
327
|
ok: true,
|
|
328
328
|
status: 200,
|
package/dist/http/client.d.ts
CHANGED
|
@@ -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
|
package/dist/http/client.js
CHANGED
|
@@ -16,7 +16,7 @@ export class HttpClient {
|
|
|
16
16
|
this.baseUrl = config.baseUrl || 'https://api.inference.sh';
|
|
17
17
|
this.proxyUrl = config.proxyUrl;
|
|
18
18
|
this.getToken = config.getToken;
|
|
19
|
-
this.customHeaders = { 'X-Client-Source': 'inference-sdk-js/0.5.13', ...config.headers };
|
|
19
|
+
this.customHeaders = { 'X-Client-Source': 'inference-sdk-js/0.5.13', 'X-API-Version': '2', ...config.headers };
|
|
20
20
|
this.credentials = config.credentials || 'include';
|
|
21
21
|
this.onError = config.onError;
|
|
22
22
|
this.streamDefault = config.stream ?? true;
|
|
@@ -114,7 +114,6 @@ export class HttpClient {
|
|
|
114
114
|
}
|
|
115
115
|
const response = await fetch(fetchUrl, fetchOptions);
|
|
116
116
|
const responseText = await response.text();
|
|
117
|
-
// Try to parse as JSON
|
|
118
117
|
let data = null;
|
|
119
118
|
try {
|
|
120
119
|
data = JSON.parse(responseText);
|
|
@@ -122,44 +121,24 @@ export class HttpClient {
|
|
|
122
121
|
catch {
|
|
123
122
|
// Not JSON
|
|
124
123
|
}
|
|
125
|
-
//
|
|
124
|
+
// HTTP errors → RFC 9457 problem+json
|
|
126
125
|
if (!response.ok) {
|
|
127
|
-
// Check for RequirementsNotMetException (412 with errors array)
|
|
128
126
|
if (response.status === 412 && data && 'errors' in data && Array.isArray(data.errors)) {
|
|
129
127
|
throw RequirementsNotMetException.fromResponse(data, response.status);
|
|
130
128
|
}
|
|
131
|
-
// General error handling
|
|
132
129
|
let errorDetail;
|
|
133
130
|
if (data && typeof data === 'object') {
|
|
134
|
-
|
|
135
|
-
if (apiData.error) {
|
|
136
|
-
errorDetail = typeof apiData.error === 'object' ? apiData.error.message : String(apiData.error);
|
|
137
|
-
}
|
|
138
|
-
else if ('message' in data) {
|
|
139
|
-
errorDetail = String(data.message);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
errorDetail = JSON.stringify(data);
|
|
143
|
-
}
|
|
131
|
+
errorDetail = this.extractErrorDetail(data) ?? JSON.stringify(data);
|
|
144
132
|
}
|
|
145
133
|
else if (responseText) {
|
|
146
134
|
errorDetail = responseText.slice(0, 500);
|
|
147
135
|
}
|
|
148
136
|
throw new InferenceError(response.status, errorDetail || 'Request failed', responseText);
|
|
149
137
|
}
|
|
150
|
-
|
|
151
|
-
if (response.status === 204) {
|
|
138
|
+
if (response.status === 204 || !responseText) {
|
|
152
139
|
return undefined;
|
|
153
140
|
}
|
|
154
|
-
|
|
155
|
-
if (!apiResponse?.success) {
|
|
156
|
-
let errorMessage = apiResponse?.error?.message;
|
|
157
|
-
if (!errorMessage) {
|
|
158
|
-
errorMessage = `Request failed (success=false). Response: ${responseText.slice(0, 500)}`;
|
|
159
|
-
}
|
|
160
|
-
throw new InferenceError(response.status, errorMessage, responseText);
|
|
161
|
-
}
|
|
162
|
-
return (apiResponse.data ?? null);
|
|
141
|
+
return data;
|
|
163
142
|
}
|
|
164
143
|
/**
|
|
165
144
|
* Get URL and headers for NDJSON streaming.
|
|
@@ -185,6 +164,16 @@ export class HttpClient {
|
|
|
185
164
|
}
|
|
186
165
|
return { url, headers };
|
|
187
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
|
+
}
|
|
188
177
|
/**
|
|
189
178
|
* Create an EventSource for SSE streaming
|
|
190
179
|
* @deprecated Use getStreamableConfig() with StreamableManager instead
|
|
@@ -204,30 +193,39 @@ export class HttpClient {
|
|
|
204
193
|
fetchUrl = targetUrl.toString();
|
|
205
194
|
}
|
|
206
195
|
const resolvedHeaders = this.resolveHeaders();
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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}`;
|
|
216
212
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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);
|
|
223
220
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
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 }));
|
|
231
229
|
}
|
|
232
230
|
}
|
|
233
231
|
/**
|