@inferencesh/sdk 0.6.12 → 0.6.13
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/dist/agent/actions.test.js +225 -1
- package/dist/agent/api.test.js +18 -1
- package/dist/api/agents.test.js +285 -0
- package/dist/api/knowledge.test.js +144 -0
- package/dist/api/mcp-servers.test.js +34 -0
- package/dist/api/projects.test.js +9 -0
- package/dist/api/sessions.test.js +12 -0
- package/dist/api/tasks.test.js +37 -0
- package/dist/api/teams.test.js +60 -0
- package/dist/client.test.js +112 -1
- package/dist/proxy/hono.test.d.ts +1 -0
- package/dist/proxy/hono.test.js +90 -0
- package/dist/proxy/nextjs.test.d.ts +1 -0
- package/dist/proxy/nextjs.test.js +125 -0
- package/dist/proxy/remix.test.d.ts +1 -0
- package/dist/proxy/remix.test.js +66 -0
- package/dist/proxy/svelte.test.d.ts +1 -0
- package/dist/proxy/svelte.test.js +69 -0
- package/package.json +3 -3
|
@@ -50,6 +50,55 @@ describe('KnowledgeAPI', () => {
|
|
|
50
50
|
expect(url).toContain('/knowledge/know-1/transfer');
|
|
51
51
|
expect(JSON.parse(init.body)).toEqual({ team_id: 'team-5' });
|
|
52
52
|
});
|
|
53
|
+
it('should GET /knowledge/{id} for get()', async () => {
|
|
54
|
+
const entry = { id: 'know-1', name: 'docs' };
|
|
55
|
+
mockJsonResponse(entry);
|
|
56
|
+
const result = await api().get('know-1');
|
|
57
|
+
expect(result).toEqual(entry);
|
|
58
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
59
|
+
expect(url).toContain('/knowledge/know-1');
|
|
60
|
+
expect(init.method).toBe('GET');
|
|
61
|
+
});
|
|
62
|
+
it('should POST /knowledge/{id} for update()', async () => {
|
|
63
|
+
const entry = { id: 'know-1', name: 'updated' };
|
|
64
|
+
mockJsonResponse(entry);
|
|
65
|
+
const result = await api().update('know-1', { name: 'updated' });
|
|
66
|
+
expect(result).toEqual(entry);
|
|
67
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
68
|
+
expect(JSON.parse(init.body)).toEqual({ name: 'updated' });
|
|
69
|
+
});
|
|
70
|
+
it('should DELETE /knowledge/{id} for delete()', async () => {
|
|
71
|
+
mockJsonResponse(null);
|
|
72
|
+
await api().delete('know-1');
|
|
73
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
74
|
+
expect(url).toContain('/knowledge/know-1');
|
|
75
|
+
expect(init.method).toBe('DELETE');
|
|
76
|
+
});
|
|
77
|
+
it('should POST /knowledge/{id}/versions/list for listVersions()', async () => {
|
|
78
|
+
const page = { items: [{ id: 'ver-1' }], next_cursor: null };
|
|
79
|
+
mockJsonResponse(page);
|
|
80
|
+
const result = await api().listVersions('know-1', { limit: 10 });
|
|
81
|
+
expect(result).toEqual(page);
|
|
82
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
83
|
+
expect(url).toContain('/knowledge/know-1/versions/list');
|
|
84
|
+
expect(JSON.parse(init.body)).toEqual({ limit: 10 });
|
|
85
|
+
});
|
|
86
|
+
it('should GET /knowledge/{id}/versions/{versionId} for getVersion()', async () => {
|
|
87
|
+
const version = { id: 'ver-1', knowledge_id: 'know-1' };
|
|
88
|
+
mockJsonResponse(version);
|
|
89
|
+
const result = await api().getVersion('know-1', 'ver-1');
|
|
90
|
+
expect(result).toEqual(version);
|
|
91
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
92
|
+
expect(url).toContain('/knowledge/know-1/versions/ver-1');
|
|
93
|
+
expect(init.method).toBe('GET');
|
|
94
|
+
});
|
|
95
|
+
it('should POST visibility for updateVisibility()', async () => {
|
|
96
|
+
const entry = { id: 'know-1', visibility: 'team' };
|
|
97
|
+
mockJsonResponse(entry);
|
|
98
|
+
await api().updateVisibility('know-1', 'team');
|
|
99
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
100
|
+
expect(JSON.parse(init.body)).toEqual({ visibility: 'team' });
|
|
101
|
+
});
|
|
53
102
|
});
|
|
54
103
|
describe('SkillsAPI', () => {
|
|
55
104
|
beforeEach(() => {
|
|
@@ -91,4 +140,99 @@ describe('SkillsAPI', () => {
|
|
|
91
140
|
expect(url).toContain('/store/skills/list');
|
|
92
141
|
expect(init.method).toBe('POST');
|
|
93
142
|
});
|
|
143
|
+
it('should GET /skills/resolve with ref only when skill is omitted', async () => {
|
|
144
|
+
const resolved = { ref: 'acme/skill@v1' };
|
|
145
|
+
mockJsonResponse(resolved);
|
|
146
|
+
const result = await api().resolve('acme/skill@v1');
|
|
147
|
+
expect(result).toEqual(resolved);
|
|
148
|
+
const [url] = mockFetch.mock.calls[0];
|
|
149
|
+
expect(url).toContain('/skills/resolve');
|
|
150
|
+
expect(url).toContain('ref=acme');
|
|
151
|
+
expect(url).not.toContain('skill=');
|
|
152
|
+
});
|
|
153
|
+
it('should GET /skills/{namespace}/{name}/content for getContent()', async () => {
|
|
154
|
+
mockJsonResponse({ body: '# Skill instructions' });
|
|
155
|
+
const result = await api().getContent('acme', 'research');
|
|
156
|
+
expect(result).toEqual({ body: '# Skill instructions' });
|
|
157
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
158
|
+
expect(url).toContain('/skills/acme/research/content');
|
|
159
|
+
expect(init.method).toBe('GET');
|
|
160
|
+
});
|
|
161
|
+
it('should POST team_id for transferOwnership()', async () => {
|
|
162
|
+
const skill = { id: 'skill-1' };
|
|
163
|
+
mockJsonResponse(skill);
|
|
164
|
+
await api().transferOwnership('skill-1', 'team-8');
|
|
165
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
166
|
+
expect(url).toContain('/skills/skill-1/transfer');
|
|
167
|
+
expect(JSON.parse(init.body)).toEqual({ team_id: 'team-8' });
|
|
168
|
+
});
|
|
169
|
+
it('should POST /skills/list for list()', async () => {
|
|
170
|
+
const page = { items: [{ id: 'skill-1' }], next_cursor: null };
|
|
171
|
+
mockJsonResponse(page);
|
|
172
|
+
const result = await api().list({ limit: 10 });
|
|
173
|
+
expect(result).toEqual(page);
|
|
174
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
175
|
+
expect(url).toContain('/skills/list');
|
|
176
|
+
expect(init.method).toBe('POST');
|
|
177
|
+
});
|
|
178
|
+
it('should GET /skills/{id} for get()', async () => {
|
|
179
|
+
const skill = { id: 'skill-1', name: 'research' };
|
|
180
|
+
mockJsonResponse(skill);
|
|
181
|
+
const result = await api().get('skill-1');
|
|
182
|
+
expect(result).toEqual(skill);
|
|
183
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
184
|
+
expect(url).toContain('/skills/skill-1');
|
|
185
|
+
expect(init.method).toBe('GET');
|
|
186
|
+
});
|
|
187
|
+
it('should POST /skills for create()', async () => {
|
|
188
|
+
const payload = { namespace: 'acme', name: 'research' };
|
|
189
|
+
const skill = { id: 'skill-new', ...payload };
|
|
190
|
+
mockJsonResponse(skill);
|
|
191
|
+
const result = await api().create(payload);
|
|
192
|
+
expect(result).toEqual(skill);
|
|
193
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
194
|
+
expect(url).toContain('/skills');
|
|
195
|
+
expect(init.method).toBe('POST');
|
|
196
|
+
expect(JSON.parse(init.body)).toEqual(payload);
|
|
197
|
+
});
|
|
198
|
+
it('should POST /skills/{id} for update()', async () => {
|
|
199
|
+
const skill = { id: 'skill-1', name: 'updated' };
|
|
200
|
+
mockJsonResponse(skill);
|
|
201
|
+
const result = await api().update('skill-1', { name: 'updated' });
|
|
202
|
+
expect(result).toEqual(skill);
|
|
203
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
204
|
+
expect(JSON.parse(init.body)).toEqual({ name: 'updated' });
|
|
205
|
+
});
|
|
206
|
+
it('should DELETE /skills/{id} for delete()', async () => {
|
|
207
|
+
mockJsonResponse(null);
|
|
208
|
+
await api().delete('skill-1');
|
|
209
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
210
|
+
expect(url).toContain('/skills/skill-1');
|
|
211
|
+
expect(init.method).toBe('DELETE');
|
|
212
|
+
});
|
|
213
|
+
it('should POST /skills/{id}/versions/list for listVersions()', async () => {
|
|
214
|
+
const page = { items: [{ id: 'ver-1' }], next_cursor: null };
|
|
215
|
+
mockJsonResponse(page);
|
|
216
|
+
const result = await api().listVersions('skill-1', { limit: 5 });
|
|
217
|
+
expect(result).toEqual(page);
|
|
218
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
219
|
+
expect(url).toContain('/skills/skill-1/versions/list');
|
|
220
|
+
expect(JSON.parse(init.body)).toEqual({ limit: 5 });
|
|
221
|
+
});
|
|
222
|
+
it('should GET /skills/{id}/versions/{versionId} for getVersion()', async () => {
|
|
223
|
+
const version = { id: 'ver-1', skill_id: 'skill-1' };
|
|
224
|
+
mockJsonResponse(version);
|
|
225
|
+
const result = await api().getVersion('skill-1', 'ver-1');
|
|
226
|
+
expect(result).toEqual(version);
|
|
227
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
228
|
+
expect(url).toContain('/skills/skill-1/versions/ver-1');
|
|
229
|
+
expect(init.method).toBe('GET');
|
|
230
|
+
});
|
|
231
|
+
it('should POST visibility for updateVisibility()', async () => {
|
|
232
|
+
const skill = { id: 'skill-1', visibility: 'team' };
|
|
233
|
+
mockJsonResponse(skill);
|
|
234
|
+
await api().updateVisibility('skill-1', 'team');
|
|
235
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
236
|
+
expect(JSON.parse(init.body)).toEqual({ visibility: 'team' });
|
|
237
|
+
});
|
|
94
238
|
});
|
|
@@ -61,4 +61,38 @@ describe('MCPServersAPI', () => {
|
|
|
61
61
|
expect(url).toContain('/mcp-servers/mcp-1');
|
|
62
62
|
expect(init.method).toBe('PUT');
|
|
63
63
|
});
|
|
64
|
+
it('should GET /mcps/{slug} for get()', async () => {
|
|
65
|
+
const server = { slug: 'filesystem', name: 'Filesystem MCP' };
|
|
66
|
+
mockJsonResponse(server);
|
|
67
|
+
const result = await api().get('filesystem');
|
|
68
|
+
expect(result).toEqual(server);
|
|
69
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
70
|
+
expect(url).toContain('/mcps/filesystem');
|
|
71
|
+
expect(init.method).toBe('GET');
|
|
72
|
+
});
|
|
73
|
+
it('should POST /mcp-servers/list for listOwned()', async () => {
|
|
74
|
+
const page = { items: [{ id: 'mcp-1' }], next_cursor: null };
|
|
75
|
+
mockJsonResponse(page);
|
|
76
|
+
const result = await api().listOwned({ limit: 5 });
|
|
77
|
+
expect(result).toEqual(page);
|
|
78
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
79
|
+
expect(url).toContain('/mcp-servers/list');
|
|
80
|
+
expect(init.method).toBe('POST');
|
|
81
|
+
});
|
|
82
|
+
it('should GET /mcp-servers/{id} for getOwned()', async () => {
|
|
83
|
+
const server = { id: 'mcp-1', name: 'My MCP' };
|
|
84
|
+
mockJsonResponse(server);
|
|
85
|
+
const result = await api().getOwned('mcp-1');
|
|
86
|
+
expect(result).toEqual(server);
|
|
87
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
88
|
+
expect(url).toContain('/mcp-servers/mcp-1');
|
|
89
|
+
expect(init.method).toBe('GET');
|
|
90
|
+
});
|
|
91
|
+
it('should DELETE /mcp-servers/{id} for delete()', async () => {
|
|
92
|
+
mockJsonResponse(null);
|
|
93
|
+
await api().delete('mcp-1');
|
|
94
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
95
|
+
expect(url).toContain('/mcp-servers/mcp-1');
|
|
96
|
+
expect(init.method).toBe('DELETE');
|
|
97
|
+
});
|
|
64
98
|
});
|
|
@@ -49,4 +49,13 @@ describe('ProjectsAPI', () => {
|
|
|
49
49
|
expect(url).toContain('/projects/proj-1');
|
|
50
50
|
expect(init.method).toBe('DELETE');
|
|
51
51
|
});
|
|
52
|
+
it('should POST /projects/{id} for update()', async () => {
|
|
53
|
+
const project = { id: 'proj-1', name: 'Renamed' };
|
|
54
|
+
mockJsonResponse(project);
|
|
55
|
+
const result = await api().update('proj-1', { name: 'Renamed' });
|
|
56
|
+
expect(result).toEqual(project);
|
|
57
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
58
|
+
expect(url).toContain('/projects/proj-1');
|
|
59
|
+
expect(JSON.parse(init.body)).toEqual({ name: 'Renamed' });
|
|
60
|
+
});
|
|
52
61
|
});
|
|
@@ -23,6 +23,18 @@ describe('SessionsAPI', () => {
|
|
|
23
23
|
expect(url).toContain('/sessions/sess_1');
|
|
24
24
|
expect(init.method).toBe('GET');
|
|
25
25
|
});
|
|
26
|
+
it('should GET /sessions and return session list', async () => {
|
|
27
|
+
const sessions = [
|
|
28
|
+
{ id: 'sess_1', status: 'active' },
|
|
29
|
+
{ id: 'sess_2', status: 'active' },
|
|
30
|
+
];
|
|
31
|
+
mockJsonResponse(sessions);
|
|
32
|
+
const result = await api().list();
|
|
33
|
+
expect(result).toEqual(sessions);
|
|
34
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
35
|
+
expect(url).toContain('/sessions');
|
|
36
|
+
expect(init.method).toBe('GET');
|
|
37
|
+
});
|
|
26
38
|
it('should return an empty array when list() response is null', async () => {
|
|
27
39
|
mockJsonResponse(null);
|
|
28
40
|
const result = await api().list();
|
package/dist/api/tasks.test.js
CHANGED
|
@@ -188,6 +188,23 @@ describe('TasksAPI.run (streaming mode)', () => {
|
|
|
188
188
|
]);
|
|
189
189
|
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true })).rejects.toThrow('task cancelled');
|
|
190
190
|
});
|
|
191
|
+
it('should reject when the NDJSON stream connection fails', async () => {
|
|
192
|
+
mockFetch.mockImplementation((url) => {
|
|
193
|
+
if (url.includes('/apps/run')) {
|
|
194
|
+
return Promise.resolve({
|
|
195
|
+
ok: true,
|
|
196
|
+
status: 200,
|
|
197
|
+
text: () => Promise.resolve(JSON.stringify(makeTask())),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return Promise.resolve({
|
|
201
|
+
ok: false,
|
|
202
|
+
status: 503,
|
|
203
|
+
text: () => Promise.resolve('service unavailable'),
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
await expect(api().run({ app: 'test-app', input: {} }, {}, { wait: true })).rejects.toThrow('HTTP 503');
|
|
207
|
+
});
|
|
191
208
|
it('should handle partial updates via onPartialUpdate', async () => {
|
|
192
209
|
setupStreamMocks([
|
|
193
210
|
`${JSON.stringify({
|
|
@@ -201,6 +218,26 @@ describe('TasksAPI.run (streaming mode)', () => {
|
|
|
201
218
|
expect(onPartialUpdate).toHaveBeenCalledWith(expect.objectContaining({ id: 'task-1', status: TaskStatusCompleted }), ['status']);
|
|
202
219
|
});
|
|
203
220
|
});
|
|
221
|
+
describe('TasksAPI.run (HTTP contract)', () => {
|
|
222
|
+
beforeEach(() => {
|
|
223
|
+
jest.clearAllMocks();
|
|
224
|
+
});
|
|
225
|
+
const api = () => new TasksAPI(new HttpClient({ apiKey: 'test-key' }));
|
|
226
|
+
it('should POST /apps/run with merged params and processedInput', async () => {
|
|
227
|
+
const task = makeTask();
|
|
228
|
+
mockJsonResponse(task);
|
|
229
|
+
const result = await api().run({ app: 'image-gen', version_id: 'ver-2', input: {} }, { prompt: 'sunset', width: 512 }, { wait: false });
|
|
230
|
+
expect(result).toEqual(task);
|
|
231
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
232
|
+
expect(url).toContain('/apps/run');
|
|
233
|
+
expect(init.method).toBe('POST');
|
|
234
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
235
|
+
app: 'image-gen',
|
|
236
|
+
version_id: 'ver-2',
|
|
237
|
+
input: { prompt: 'sunset', width: 512 },
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
204
241
|
describe('TasksAPI.create', () => {
|
|
205
242
|
beforeEach(() => {
|
|
206
243
|
jest.clearAllMocks();
|
package/dist/api/teams.test.js
CHANGED
|
@@ -76,4 +76,64 @@ describe('TeamsAPI', () => {
|
|
|
76
76
|
expect(url).toContain('/teams/team-1/invites');
|
|
77
77
|
expect(JSON.parse(init.body)).toEqual(payload);
|
|
78
78
|
});
|
|
79
|
+
it('should GET /teams/{id} for get()', async () => {
|
|
80
|
+
const team = { id: 'team-1', name: 'Acme' };
|
|
81
|
+
mockJsonResponse(team);
|
|
82
|
+
const result = await api().get('team-1');
|
|
83
|
+
expect(result).toEqual(team);
|
|
84
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
85
|
+
expect(url).toContain('/teams/team-1');
|
|
86
|
+
expect(init.method).toBe('GET');
|
|
87
|
+
});
|
|
88
|
+
it('should POST /teams/{id} for update()', async () => {
|
|
89
|
+
const team = { id: 'team-1', name: 'Acme Updated' };
|
|
90
|
+
mockJsonResponse(team);
|
|
91
|
+
const result = await api().update('team-1', { name: 'Acme Updated' });
|
|
92
|
+
expect(result).toEqual(team);
|
|
93
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
94
|
+
expect(url).toContain('/teams/team-1');
|
|
95
|
+
expect(JSON.parse(init.body)).toEqual({ name: 'Acme Updated' });
|
|
96
|
+
});
|
|
97
|
+
it('should DELETE /teams/{id} for delete()', async () => {
|
|
98
|
+
mockJsonResponse(null);
|
|
99
|
+
await api().delete('team-1');
|
|
100
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
101
|
+
expect(url).toContain('/teams/team-1');
|
|
102
|
+
expect(init.method).toBe('DELETE');
|
|
103
|
+
});
|
|
104
|
+
it('should GET /teams/{id}/members for getMembers()', async () => {
|
|
105
|
+
const members = [{ user_id: 'user-1', role: 'owner' }];
|
|
106
|
+
mockJsonResponse(members);
|
|
107
|
+
const result = await api().getMembers('team-1');
|
|
108
|
+
expect(result).toEqual(members);
|
|
109
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
110
|
+
expect(url).toContain('/teams/team-1/members');
|
|
111
|
+
expect(init.method).toBe('GET');
|
|
112
|
+
});
|
|
113
|
+
it('should POST /teams/{id}/members for addMember()', async () => {
|
|
114
|
+
const payload = { user_id: 'user-4', role: 'member' };
|
|
115
|
+
const member = { ...payload };
|
|
116
|
+
mockJsonResponse(member);
|
|
117
|
+
const result = await api().addMember('team-1', payload);
|
|
118
|
+
expect(result).toEqual(member);
|
|
119
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
120
|
+
expect(url).toContain('/teams/team-1/members');
|
|
121
|
+
expect(JSON.parse(init.body)).toEqual(payload);
|
|
122
|
+
});
|
|
123
|
+
it('should GET /teams/{id}/invites for listInvites()', async () => {
|
|
124
|
+
const invites = [{ id: 'inv-1', email: 'dev@example.com' }];
|
|
125
|
+
mockJsonResponse(invites);
|
|
126
|
+
const result = await api().listInvites('team-1');
|
|
127
|
+
expect(result).toEqual(invites);
|
|
128
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
129
|
+
expect(url).toContain('/teams/team-1/invites');
|
|
130
|
+
expect(init.method).toBe('GET');
|
|
131
|
+
});
|
|
132
|
+
it('should DELETE /teams/{id}/invites/{inviteId} for revokeInvite()', async () => {
|
|
133
|
+
mockJsonResponse(null);
|
|
134
|
+
await api().revokeInvite('team-1', 'inv-9');
|
|
135
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
136
|
+
expect(url).toContain('/teams/team-1/invites/inv-9');
|
|
137
|
+
expect(init.method).toBe('DELETE');
|
|
138
|
+
});
|
|
79
139
|
});
|
package/dist/client.test.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Inference, inference } from './index';
|
|
1
|
+
import { Inference, inference, createClient } from './index';
|
|
2
2
|
import { RequirementsNotMetException } from './http/errors';
|
|
3
|
+
import { HttpClient } from './http/client';
|
|
4
|
+
import { ChatStatusBusy, ChatStatusIdle } from './types';
|
|
3
5
|
// Mock fetch globally
|
|
4
6
|
const mockFetch = jest.fn();
|
|
5
7
|
global.fetch = mockFetch;
|
|
@@ -138,6 +140,45 @@ describe('Inference', () => {
|
|
|
138
140
|
expect(exception.errors[0].action?.scopes).toEqual(['calendar.readonly', 'calendar.events']);
|
|
139
141
|
}
|
|
140
142
|
});
|
|
143
|
+
it('should process Blob inputs via processInput before calling /apps/run', async () => {
|
|
144
|
+
const fileRecord = {
|
|
145
|
+
id: 'file-blob',
|
|
146
|
+
uri: 'inf://files/blob',
|
|
147
|
+
upload_url: 'https://upload.example.com/put',
|
|
148
|
+
content_type: 'image/png',
|
|
149
|
+
};
|
|
150
|
+
const mockTask = {
|
|
151
|
+
id: 'task-456',
|
|
152
|
+
status: 9,
|
|
153
|
+
created_at: new Date().toISOString(),
|
|
154
|
+
updated_at: new Date().toISOString(),
|
|
155
|
+
input: {},
|
|
156
|
+
output: { result: 'done' },
|
|
157
|
+
};
|
|
158
|
+
mockFetch
|
|
159
|
+
.mockResolvedValueOnce({
|
|
160
|
+
ok: true,
|
|
161
|
+
status: 200,
|
|
162
|
+
text: () => Promise.resolve(JSON.stringify([fileRecord])),
|
|
163
|
+
json: () => Promise.resolve([fileRecord]),
|
|
164
|
+
})
|
|
165
|
+
.mockResolvedValueOnce({ ok: true, status: 200 })
|
|
166
|
+
.mockResolvedValueOnce({
|
|
167
|
+
ok: true,
|
|
168
|
+
status: 200,
|
|
169
|
+
text: () => Promise.resolve(JSON.stringify(mockTask)),
|
|
170
|
+
json: () => Promise.resolve(mockTask),
|
|
171
|
+
});
|
|
172
|
+
const client = new Inference({ apiKey: 'test-api-key' });
|
|
173
|
+
const blob = new Blob(['png-bytes'], { type: 'image/png' });
|
|
174
|
+
const result = await client.run({ app: 'test-app', input: { image: blob } }, { wait: false });
|
|
175
|
+
expect(result.id).toBe('task-456');
|
|
176
|
+
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
177
|
+
const runCall = mockFetch.mock.calls.find((call) => String(call[0]).includes('/apps/run'));
|
|
178
|
+
expect(runCall).toBeDefined();
|
|
179
|
+
const runBody = JSON.parse(runCall[1].body);
|
|
180
|
+
expect(runBody.input.image).toBe('inf://files/blob');
|
|
181
|
+
});
|
|
141
182
|
});
|
|
142
183
|
describe('cancel', () => {
|
|
143
184
|
it('should make a POST request to cancel endpoint', async () => {
|
|
@@ -153,6 +194,76 @@ describe('Inference', () => {
|
|
|
153
194
|
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/tasks/task-123/cancel'), expect.objectContaining({ method: 'POST' }));
|
|
154
195
|
});
|
|
155
196
|
});
|
|
197
|
+
describe('legacy task helpers', () => {
|
|
198
|
+
it('should delegate getTask() to tasks.get', async () => {
|
|
199
|
+
const mockTask = { id: 'task-legacy', status: 9 };
|
|
200
|
+
mockFetch.mockResolvedValueOnce({
|
|
201
|
+
ok: true,
|
|
202
|
+
status: 200,
|
|
203
|
+
text: () => Promise.resolve(JSON.stringify(mockTask)),
|
|
204
|
+
json: () => Promise.resolve(mockTask),
|
|
205
|
+
});
|
|
206
|
+
const client = new Inference({ apiKey: 'test-api-key' });
|
|
207
|
+
const result = await client.getTask('task-legacy');
|
|
208
|
+
expect(result.id).toBe('task-legacy');
|
|
209
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/tasks/task-legacy'), expect.objectContaining({ method: 'GET' }));
|
|
210
|
+
});
|
|
211
|
+
it('should delegate streamTask() to tasks.stream', async () => {
|
|
212
|
+
const client = new Inference({ apiKey: 'test-api-key' });
|
|
213
|
+
const createEventSource = jest
|
|
214
|
+
.spyOn(HttpClient.prototype, 'createEventSource')
|
|
215
|
+
.mockResolvedValue(null);
|
|
216
|
+
await client.streamTask('task-stream');
|
|
217
|
+
expect(createEventSource).toHaveBeenCalledWith('/tasks/task-stream/stream');
|
|
218
|
+
createEventSource.mockRestore();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
describe('agent()', () => {
|
|
222
|
+
it('should return an Agent whose sendMessage hits POST /agents/run', async () => {
|
|
223
|
+
mockFetch
|
|
224
|
+
.mockResolvedValueOnce({
|
|
225
|
+
ok: true,
|
|
226
|
+
status: 200,
|
|
227
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
228
|
+
user_message: { id: 'user-1', chat_id: 'chat-1', role: 'user', content: 'hi' },
|
|
229
|
+
assistant_message: { id: 'asst-1', chat_id: 'chat-1', role: 'assistant', content: 'hello' },
|
|
230
|
+
})),
|
|
231
|
+
})
|
|
232
|
+
.mockResolvedValueOnce({
|
|
233
|
+
ok: true,
|
|
234
|
+
status: 200,
|
|
235
|
+
text: () => Promise.resolve(JSON.stringify({ status: ChatStatusBusy })),
|
|
236
|
+
})
|
|
237
|
+
.mockResolvedValueOnce({
|
|
238
|
+
ok: true,
|
|
239
|
+
status: 200,
|
|
240
|
+
text: () => Promise.resolve(JSON.stringify({ id: 'chat-1', status: ChatStatusBusy, chat_messages: [] })),
|
|
241
|
+
})
|
|
242
|
+
.mockResolvedValueOnce({
|
|
243
|
+
ok: true,
|
|
244
|
+
status: 200,
|
|
245
|
+
text: () => Promise.resolve(JSON.stringify({ status: ChatStatusIdle })),
|
|
246
|
+
})
|
|
247
|
+
.mockResolvedValueOnce({
|
|
248
|
+
ok: true,
|
|
249
|
+
status: 200,
|
|
250
|
+
text: () => Promise.resolve(JSON.stringify({ id: 'chat-1', status: ChatStatusIdle, chat_messages: [] })),
|
|
251
|
+
});
|
|
252
|
+
const client = new Inference({ apiKey: 'test-api-key', stream: false, pollIntervalMs: 20 });
|
|
253
|
+
const agentInstance = client.agent('my-agent');
|
|
254
|
+
await agentInstance.sendMessage('hi', { stream: false });
|
|
255
|
+
const runCall = mockFetch.mock.calls.find(([url]) => String(url).includes('/agents/run'));
|
|
256
|
+
expect(runCall).toBeDefined();
|
|
257
|
+
const body = JSON.parse(runCall[1].body);
|
|
258
|
+
expect(body.agent).toBe('my-agent');
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
describe('createClient', () => {
|
|
262
|
+
it('should create an Inference instance with extended HttpClient config', () => {
|
|
263
|
+
const client = createClient({ apiKey: 'extended-key', baseUrl: 'https://api.example.com' });
|
|
264
|
+
expect(client).toBeInstanceOf(Inference);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
156
267
|
describe('lowercase factory', () => {
|
|
157
268
|
it('should export lowercase inference factory', () => {
|
|
158
269
|
expect(typeof inference).toBe('function');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { INF_TARGET_HEADER } from './index';
|
|
2
|
+
import { createHandler } from './hono';
|
|
3
|
+
function createMockContext(overrides = {}) {
|
|
4
|
+
const headers = new Headers(overrides.headers ?? {});
|
|
5
|
+
const url = overrides.url ?? 'http://localhost/api/inference/proxy';
|
|
6
|
+
return {
|
|
7
|
+
req: {
|
|
8
|
+
method: overrides.method ?? 'POST',
|
|
9
|
+
url,
|
|
10
|
+
raw: { headers },
|
|
11
|
+
header: (name) => headers.get(name) ?? undefined,
|
|
12
|
+
text: () => Promise.resolve(overrides.body ?? '{"ok":true}'),
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
describe('hono createHandler', () => {
|
|
17
|
+
const originalFetch = global.fetch;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
process.env.INFERENCE_API_KEY = 'hono-test-key';
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
global.fetch = originalFetch;
|
|
24
|
+
});
|
|
25
|
+
it('should return 400 when target URL is missing', async () => {
|
|
26
|
+
const handler = createHandler();
|
|
27
|
+
const response = await handler(createMockContext());
|
|
28
|
+
expect(response.status).toBe(400);
|
|
29
|
+
expect(await response.json()).toEqual({
|
|
30
|
+
error: `Missing ${INF_TARGET_HEADER} header or __inf_target query param`,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it('should proxy JSON responses', async () => {
|
|
34
|
+
global.fetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ ok: true }), {
|
|
35
|
+
status: 200,
|
|
36
|
+
headers: { 'content-type': 'application/json' },
|
|
37
|
+
}));
|
|
38
|
+
const handler = createHandler();
|
|
39
|
+
const target = 'https://api.inference.sh/v1/run';
|
|
40
|
+
const response = await handler(createMockContext({
|
|
41
|
+
headers: { [INF_TARGET_HEADER]: target },
|
|
42
|
+
}));
|
|
43
|
+
expect(response.status).toBe(200);
|
|
44
|
+
expect(await response.json()).toEqual({ ok: true });
|
|
45
|
+
expect(global.fetch).toHaveBeenCalledWith(target, expect.objectContaining({
|
|
46
|
+
headers: expect.objectContaining({
|
|
47
|
+
authorization: 'Bearer hono-test-key',
|
|
48
|
+
}),
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
it('should passthrough SSE streaming responses', async () => {
|
|
52
|
+
const encoder = new TextEncoder();
|
|
53
|
+
const stream = new ReadableStream({
|
|
54
|
+
start(controller) {
|
|
55
|
+
controller.enqueue(encoder.encode('data: {"x":1}\n\n'));
|
|
56
|
+
controller.close();
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
global.fetch = jest.fn().mockResolvedValue(new Response(stream, {
|
|
60
|
+
status: 200,
|
|
61
|
+
headers: { 'content-type': 'text/event-stream' },
|
|
62
|
+
}));
|
|
63
|
+
const handler = createHandler();
|
|
64
|
+
const target = 'https://api.inference.sh/v1/stream';
|
|
65
|
+
const response = await handler(createMockContext({
|
|
66
|
+
headers: { [INF_TARGET_HEADER]: target },
|
|
67
|
+
}));
|
|
68
|
+
expect(response.status).toBe(200);
|
|
69
|
+
expect(response.headers.get('content-type')).toContain('text/event-stream');
|
|
70
|
+
expect(await response.text()).toContain('data:');
|
|
71
|
+
});
|
|
72
|
+
it('should resolve API key via resolveApiKey option', async () => {
|
|
73
|
+
global.fetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ ok: true }), {
|
|
74
|
+
status: 200,
|
|
75
|
+
headers: { 'content-type': 'application/json' },
|
|
76
|
+
}));
|
|
77
|
+
const handler = createHandler({
|
|
78
|
+
resolveApiKey: async () => 'custom-hono-key',
|
|
79
|
+
});
|
|
80
|
+
const target = 'https://api.inference.sh/v1/run';
|
|
81
|
+
await handler(createMockContext({
|
|
82
|
+
headers: { [INF_TARGET_HEADER]: target },
|
|
83
|
+
}));
|
|
84
|
+
expect(global.fetch).toHaveBeenCalledWith(target, expect.objectContaining({
|
|
85
|
+
headers: expect.objectContaining({
|
|
86
|
+
authorization: 'Bearer custom-hono-key',
|
|
87
|
+
}),
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|