@inferencesh/sdk 0.4.26 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -3
- package/dist/agent/hooks.d.ts +2 -1
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/types.d.ts +21 -2
- package/dist/api/apps.d.ts +1 -11
- package/dist/api/files.js +16 -5
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/sessions.d.ts +67 -0
- package/dist/api/sessions.js +77 -0
- package/dist/api/tasks.js +10 -2
- package/dist/http/client.js +4 -0
- package/dist/http/errors.d.ts +82 -0
- package/dist/http/errors.js +98 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/sessions.integration.test.d.ts +13 -0
- package/dist/sessions.integration.test.js +312 -0
- package/dist/tool-builder.d.ts +6 -0
- package/dist/tool-builder.js +13 -0
- package/dist/tool-builder.test.js +30 -0
- package/dist/types.d.ts +220 -55
- package/dist/types.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions integration tests for @inferencesh/sdk
|
|
3
|
+
*
|
|
4
|
+
* These tests hit the real API and require INFERENCE_API_KEY to be set.
|
|
5
|
+
* Run with: npm run test:integration
|
|
6
|
+
*
|
|
7
|
+
* Prerequisites:
|
|
8
|
+
* - API and scheduler must be running
|
|
9
|
+
* - Test app deployed: infsh/session-test
|
|
10
|
+
*
|
|
11
|
+
* @jest-environment node
|
|
12
|
+
*/
|
|
13
|
+
import { Inference } from './index';
|
|
14
|
+
import { TaskStatusCompleted } from './types';
|
|
15
|
+
const API_KEY = process.env.INFERENCE_API_KEY;
|
|
16
|
+
const BASE_URL = process.env.INFERENCE_BASE_URL || 'https://api.inference.sh';
|
|
17
|
+
// Session test app with multi-function support
|
|
18
|
+
const SESSION_TEST_APP = 'infsh/session-test';
|
|
19
|
+
const describeIfApiKey = API_KEY ? describe : describe.skip;
|
|
20
|
+
describeIfApiKey('Sessions Integration Tests', () => {
|
|
21
|
+
let client;
|
|
22
|
+
beforeAll(() => {
|
|
23
|
+
client = new Inference({
|
|
24
|
+
apiKey: API_KEY,
|
|
25
|
+
baseUrl: BASE_URL,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
// ==========================================================================
|
|
29
|
+
// Session Creation Tests
|
|
30
|
+
// ==========================================================================
|
|
31
|
+
describe('Session Creation', () => {
|
|
32
|
+
it('should create a new session and return real session ID', async () => {
|
|
33
|
+
const result = await client.run({
|
|
34
|
+
app: SESSION_TEST_APP,
|
|
35
|
+
function: 'set_value',
|
|
36
|
+
input: { key: 'test_create', value: 'hello' },
|
|
37
|
+
session: 'new',
|
|
38
|
+
});
|
|
39
|
+
expect(result.status).toBe(TaskStatusCompleted);
|
|
40
|
+
expect(result.session_id).toBeDefined();
|
|
41
|
+
expect(result.session_id).not.toBe('new');
|
|
42
|
+
expect(result.session_id).toMatch(/^sess_/);
|
|
43
|
+
}, 60000);
|
|
44
|
+
it('should return correct output data', async () => {
|
|
45
|
+
const result = await client.run({
|
|
46
|
+
app: SESSION_TEST_APP,
|
|
47
|
+
function: 'set_value',
|
|
48
|
+
input: { key: 'output_test', value: 'test_value' },
|
|
49
|
+
session: 'new',
|
|
50
|
+
});
|
|
51
|
+
expect(result.status).toBe(TaskStatusCompleted);
|
|
52
|
+
const output = result.output;
|
|
53
|
+
expect(output.success).toBe(true);
|
|
54
|
+
expect(output.key).toBe('output_test');
|
|
55
|
+
expect(output.value).toBe('test_value');
|
|
56
|
+
}, 60000);
|
|
57
|
+
});
|
|
58
|
+
// ==========================================================================
|
|
59
|
+
// Session Continuity Tests
|
|
60
|
+
// ==========================================================================
|
|
61
|
+
describe('Session Continuity', () => {
|
|
62
|
+
it('should persist state across calls in same session', async () => {
|
|
63
|
+
// Create session with initial value
|
|
64
|
+
const result1 = await client.run({
|
|
65
|
+
app: SESSION_TEST_APP,
|
|
66
|
+
function: 'set_value',
|
|
67
|
+
input: { key: 'persist_key', value: 'persist_value' },
|
|
68
|
+
session: 'new',
|
|
69
|
+
});
|
|
70
|
+
expect(result1.status).toBe(TaskStatusCompleted);
|
|
71
|
+
const sessionId = result1.session_id;
|
|
72
|
+
expect(sessionId).toMatch(/^sess_/);
|
|
73
|
+
// Retrieve value from same session
|
|
74
|
+
const result2 = await client.run({
|
|
75
|
+
app: SESSION_TEST_APP,
|
|
76
|
+
function: 'get_value',
|
|
77
|
+
input: { key: 'persist_key' },
|
|
78
|
+
session: sessionId,
|
|
79
|
+
});
|
|
80
|
+
expect(result2.status).toBe(TaskStatusCompleted);
|
|
81
|
+
expect(result2.session_id).toBe(sessionId);
|
|
82
|
+
const output = result2.output;
|
|
83
|
+
expect(output.found).toBe(true);
|
|
84
|
+
expect(output.value).toBe('persist_value');
|
|
85
|
+
}, 120000);
|
|
86
|
+
it('should accumulate multiple values in session', async () => {
|
|
87
|
+
// Create session
|
|
88
|
+
const result1 = await client.run({
|
|
89
|
+
app: SESSION_TEST_APP,
|
|
90
|
+
function: 'set_value',
|
|
91
|
+
input: { key: 'key1', value: 'value1' },
|
|
92
|
+
session: 'new',
|
|
93
|
+
});
|
|
94
|
+
expect(result1.status).toBe(TaskStatusCompleted);
|
|
95
|
+
const sessionId = result1.session_id;
|
|
96
|
+
// Add second value
|
|
97
|
+
const result2 = await client.run({
|
|
98
|
+
app: SESSION_TEST_APP,
|
|
99
|
+
function: 'set_value',
|
|
100
|
+
input: { key: 'key2', value: 'value2' },
|
|
101
|
+
session: sessionId,
|
|
102
|
+
});
|
|
103
|
+
expect(result2.status).toBe(TaskStatusCompleted);
|
|
104
|
+
// Get all state
|
|
105
|
+
const result3 = await client.run({
|
|
106
|
+
app: SESSION_TEST_APP,
|
|
107
|
+
function: 'get_all',
|
|
108
|
+
input: {},
|
|
109
|
+
session: sessionId,
|
|
110
|
+
});
|
|
111
|
+
expect(result3.status).toBe(TaskStatusCompleted);
|
|
112
|
+
const output = result3.output;
|
|
113
|
+
expect(output.state.key1).toBe('value1');
|
|
114
|
+
expect(output.state.key2).toBe('value2');
|
|
115
|
+
}, 120000);
|
|
116
|
+
});
|
|
117
|
+
// ==========================================================================
|
|
118
|
+
// Multi-Function Routing Tests
|
|
119
|
+
// ==========================================================================
|
|
120
|
+
describe('Multi-Function Routing', () => {
|
|
121
|
+
it('should route to increment function correctly', async () => {
|
|
122
|
+
const result = await client.run({
|
|
123
|
+
app: SESSION_TEST_APP,
|
|
124
|
+
function: 'increment',
|
|
125
|
+
input: { key: 'counter', amount: 5 },
|
|
126
|
+
session: 'new',
|
|
127
|
+
});
|
|
128
|
+
expect(result.status).toBe(TaskStatusCompleted);
|
|
129
|
+
const output = result.output;
|
|
130
|
+
expect(output.key).toBe('counter');
|
|
131
|
+
expect(output.previous).toBe(0);
|
|
132
|
+
expect(output.current).toBe(5);
|
|
133
|
+
}, 60000);
|
|
134
|
+
it('should handle different input schemas per function', async () => {
|
|
135
|
+
// Create session
|
|
136
|
+
const result1 = await client.run({
|
|
137
|
+
app: SESSION_TEST_APP,
|
|
138
|
+
function: 'set_value',
|
|
139
|
+
input: { key: 'schema_test', value: 'v1' },
|
|
140
|
+
session: 'new',
|
|
141
|
+
});
|
|
142
|
+
expect(result1.status).toBe(TaskStatusCompleted);
|
|
143
|
+
const sessionId = result1.session_id;
|
|
144
|
+
// get_value has different schema (no 'value' field)
|
|
145
|
+
const result2 = await client.run({
|
|
146
|
+
app: SESSION_TEST_APP,
|
|
147
|
+
function: 'get_value',
|
|
148
|
+
input: { key: 'schema_test' },
|
|
149
|
+
session: sessionId,
|
|
150
|
+
});
|
|
151
|
+
expect(result2.status).toBe(TaskStatusCompleted);
|
|
152
|
+
// increment has different schema (amount field)
|
|
153
|
+
const result3 = await client.run({
|
|
154
|
+
app: SESSION_TEST_APP,
|
|
155
|
+
function: 'increment',
|
|
156
|
+
input: { key: 'cnt', amount: 10 },
|
|
157
|
+
session: sessionId,
|
|
158
|
+
});
|
|
159
|
+
expect(result3.status).toBe(TaskStatusCompleted);
|
|
160
|
+
// get_all has empty schema
|
|
161
|
+
const result4 = await client.run({
|
|
162
|
+
app: SESSION_TEST_APP,
|
|
163
|
+
function: 'get_all',
|
|
164
|
+
input: {},
|
|
165
|
+
session: sessionId,
|
|
166
|
+
});
|
|
167
|
+
expect(result4.status).toBe(TaskStatusCompleted);
|
|
168
|
+
}, 120000);
|
|
169
|
+
});
|
|
170
|
+
// ==========================================================================
|
|
171
|
+
// Session Isolation Tests
|
|
172
|
+
// ==========================================================================
|
|
173
|
+
describe('Session Isolation', () => {
|
|
174
|
+
it('should create different sessions for different "new" requests', async () => {
|
|
175
|
+
// Create first session
|
|
176
|
+
const result1 = await client.run({
|
|
177
|
+
app: SESSION_TEST_APP,
|
|
178
|
+
function: 'set_value',
|
|
179
|
+
input: { key: 'isolated', value: 'session1' },
|
|
180
|
+
session: 'new',
|
|
181
|
+
});
|
|
182
|
+
expect(result1.status).toBe(TaskStatusCompleted);
|
|
183
|
+
const session1 = result1.session_id;
|
|
184
|
+
// Create second session
|
|
185
|
+
const result2 = await client.run({
|
|
186
|
+
app: SESSION_TEST_APP,
|
|
187
|
+
function: 'set_value',
|
|
188
|
+
input: { key: 'isolated', value: 'session2' },
|
|
189
|
+
session: 'new',
|
|
190
|
+
});
|
|
191
|
+
expect(result2.status).toBe(TaskStatusCompleted);
|
|
192
|
+
const session2 = result2.session_id;
|
|
193
|
+
// Sessions should be different
|
|
194
|
+
expect(session1).not.toBe(session2);
|
|
195
|
+
}, 120000);
|
|
196
|
+
});
|
|
197
|
+
// ==========================================================================
|
|
198
|
+
// Sessions API Tests
|
|
199
|
+
// ==========================================================================
|
|
200
|
+
describe('Sessions API', () => {
|
|
201
|
+
it('should get session info', async () => {
|
|
202
|
+
// Create a session
|
|
203
|
+
const result = await client.run({
|
|
204
|
+
app: SESSION_TEST_APP,
|
|
205
|
+
function: 'set_value',
|
|
206
|
+
input: { key: 'api_test', value: 'hello' },
|
|
207
|
+
session: 'new',
|
|
208
|
+
});
|
|
209
|
+
expect(result.status).toBe(TaskStatusCompleted);
|
|
210
|
+
const sessionId = result.session_id;
|
|
211
|
+
// Get session info via API
|
|
212
|
+
const sessionInfo = await client.sessions.get(sessionId);
|
|
213
|
+
// API returns flattened structure (Go embedded structs)
|
|
214
|
+
expect(sessionInfo.id).toBe(sessionId);
|
|
215
|
+
expect(sessionInfo.status).toBe('active');
|
|
216
|
+
expect(sessionInfo.call_count).toBeGreaterThanOrEqual(1);
|
|
217
|
+
}, 60000);
|
|
218
|
+
it('should list sessions', async () => {
|
|
219
|
+
const sessions = await client.sessions.list();
|
|
220
|
+
expect(Array.isArray(sessions)).toBe(true);
|
|
221
|
+
// Should have at least one session from previous tests
|
|
222
|
+
}, 30000);
|
|
223
|
+
it('should keepalive a session', async () => {
|
|
224
|
+
// Create a session
|
|
225
|
+
const result = await client.run({
|
|
226
|
+
app: SESSION_TEST_APP,
|
|
227
|
+
function: 'set_value',
|
|
228
|
+
input: { key: 'keepalive_test', value: 'hello' },
|
|
229
|
+
session: 'new',
|
|
230
|
+
});
|
|
231
|
+
expect(result.status).toBe(TaskStatusCompleted);
|
|
232
|
+
const sessionId = result.session_id;
|
|
233
|
+
// Keepalive
|
|
234
|
+
const updated = await client.sessions.keepalive(sessionId);
|
|
235
|
+
// API returns flattened structure (Go embedded structs)
|
|
236
|
+
expect(updated.id).toBe(sessionId);
|
|
237
|
+
expect(updated.status).toBe('active');
|
|
238
|
+
}, 60000);
|
|
239
|
+
it('should end a session', async () => {
|
|
240
|
+
// Create a session
|
|
241
|
+
const result = await client.run({
|
|
242
|
+
app: SESSION_TEST_APP,
|
|
243
|
+
function: 'set_value',
|
|
244
|
+
input: { key: 'end_test', value: 'goodbye' },
|
|
245
|
+
session: 'new',
|
|
246
|
+
});
|
|
247
|
+
expect(result.status).toBe(TaskStatusCompleted);
|
|
248
|
+
const sessionId = result.session_id;
|
|
249
|
+
// End the session
|
|
250
|
+
await client.sessions.end(sessionId);
|
|
251
|
+
// Trying to use ended session should fail
|
|
252
|
+
await expect(client.run({
|
|
253
|
+
app: SESSION_TEST_APP,
|
|
254
|
+
function: 'get_value',
|
|
255
|
+
input: { key: 'end_test' },
|
|
256
|
+
session: sessionId,
|
|
257
|
+
})).rejects.toThrow();
|
|
258
|
+
}, 60000);
|
|
259
|
+
});
|
|
260
|
+
// ==========================================================================
|
|
261
|
+
// Error Cases
|
|
262
|
+
// ==========================================================================
|
|
263
|
+
describe('Error Cases', () => {
|
|
264
|
+
it('should fail with invalid session ID', async () => {
|
|
265
|
+
await expect(client.run({
|
|
266
|
+
app: SESSION_TEST_APP,
|
|
267
|
+
function: 'get_value',
|
|
268
|
+
input: { key: 'foo' },
|
|
269
|
+
session: 'sess_invalid_does_not_exist_12345',
|
|
270
|
+
})).rejects.toThrow();
|
|
271
|
+
}, 30000);
|
|
272
|
+
});
|
|
273
|
+
// ==========================================================================
|
|
274
|
+
// Performance Tests
|
|
275
|
+
// ==========================================================================
|
|
276
|
+
describe('Performance', () => {
|
|
277
|
+
it('should be faster on warm worker', async () => {
|
|
278
|
+
// Create session (cold start)
|
|
279
|
+
const result1 = await client.run({
|
|
280
|
+
app: SESSION_TEST_APP,
|
|
281
|
+
function: 'set_value',
|
|
282
|
+
input: { key: 'perf_test', value: '1' },
|
|
283
|
+
session: 'new',
|
|
284
|
+
});
|
|
285
|
+
expect(result1.status).toBe(TaskStatusCompleted);
|
|
286
|
+
const sessionId = result1.session_id;
|
|
287
|
+
// Second call should be fast (warm worker)
|
|
288
|
+
const start = Date.now();
|
|
289
|
+
const result2 = await client.run({
|
|
290
|
+
app: SESSION_TEST_APP,
|
|
291
|
+
function: 'set_value',
|
|
292
|
+
input: { key: 'perf_test', value: '2' },
|
|
293
|
+
session: sessionId,
|
|
294
|
+
});
|
|
295
|
+
const elapsed = Date.now() - start;
|
|
296
|
+
expect(result2.status).toBe(TaskStatusCompleted);
|
|
297
|
+
// Log for visibility
|
|
298
|
+
console.log(`Warm worker call took: ${elapsed}ms`);
|
|
299
|
+
// Should be under 2 seconds for warm worker
|
|
300
|
+
expect(elapsed).toBeLessThan(2000);
|
|
301
|
+
}, 120000);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
// Placeholder test that always runs
|
|
305
|
+
describe('Sessions Integration Test Setup', () => {
|
|
306
|
+
it('should have API key check', () => {
|
|
307
|
+
if (!API_KEY) {
|
|
308
|
+
console.log('⚠️ Skipping sessions integration tests - INFERENCE_API_KEY not set');
|
|
309
|
+
}
|
|
310
|
+
expect(true).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
});
|
package/dist/tool-builder.d.ts
CHANGED
|
@@ -54,11 +54,17 @@ declare class AppToolBuilder extends ToolBuilder {
|
|
|
54
54
|
private appRef;
|
|
55
55
|
private setupValues?;
|
|
56
56
|
private inputValues?;
|
|
57
|
+
private functionName?;
|
|
58
|
+
private sessionEnabledValue;
|
|
57
59
|
constructor(name: string, appRef: string);
|
|
58
60
|
/** Set one-time setup values (hidden from agent, passed on every call) */
|
|
59
61
|
setup(values: Record<string, unknown>): this;
|
|
60
62
|
/** Set default input values (agent can override these) */
|
|
61
63
|
input(values: Record<string, unknown>): this;
|
|
64
|
+
/** Set which function to call on multi-function apps */
|
|
65
|
+
function(name: string): this;
|
|
66
|
+
/** Enable session support (agent can pass session parameter and sees session_id in output) */
|
|
67
|
+
sessionEnabled(): this;
|
|
62
68
|
build(): AgentTool;
|
|
63
69
|
}
|
|
64
70
|
declare class AgentToolBuilder extends ToolBuilder {
|
package/dist/tool-builder.js
CHANGED
|
@@ -94,6 +94,7 @@ class ClientToolBuilder extends ToolBuilder {
|
|
|
94
94
|
class AppToolBuilder extends ToolBuilder {
|
|
95
95
|
constructor(name, appRef) {
|
|
96
96
|
super(name);
|
|
97
|
+
this.sessionEnabledValue = false;
|
|
97
98
|
this.appRef = appRef;
|
|
98
99
|
}
|
|
99
100
|
/** Set one-time setup values (hidden from agent, passed on every call) */
|
|
@@ -106,6 +107,16 @@ class AppToolBuilder extends ToolBuilder {
|
|
|
106
107
|
this.inputValues = values;
|
|
107
108
|
return this;
|
|
108
109
|
}
|
|
110
|
+
/** Set which function to call on multi-function apps */
|
|
111
|
+
function(name) {
|
|
112
|
+
this.functionName = name;
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/** Enable session support (agent can pass session parameter and sees session_id in output) */
|
|
116
|
+
sessionEnabled() {
|
|
117
|
+
this.sessionEnabledValue = true;
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
109
120
|
build() {
|
|
110
121
|
return {
|
|
111
122
|
name: this.name,
|
|
@@ -115,6 +126,8 @@ class AppToolBuilder extends ToolBuilder {
|
|
|
115
126
|
require_approval: this.approval || undefined,
|
|
116
127
|
app: {
|
|
117
128
|
ref: this.appRef,
|
|
129
|
+
function: this.functionName,
|
|
130
|
+
session_enabled: this.sessionEnabledValue || undefined,
|
|
118
131
|
setup: this.setupValues,
|
|
119
132
|
input: this.inputValues,
|
|
120
133
|
},
|
|
@@ -181,6 +181,36 @@ describe('AppToolBuilder (appTool)', () => {
|
|
|
181
181
|
expect(t.name).toBe('fetch');
|
|
182
182
|
// Note: App tools don't use client input_schema, params are for documentation
|
|
183
183
|
});
|
|
184
|
+
it('supports function selection for multi-function apps', () => {
|
|
185
|
+
const t = appTool('upscale', 'infsh/sdxl@v1')
|
|
186
|
+
.function('upscale')
|
|
187
|
+
.describe('Upscale an image')
|
|
188
|
+
.build();
|
|
189
|
+
expect(t.app?.function).toBe('upscale');
|
|
190
|
+
});
|
|
191
|
+
it('supports session enabled for stateful apps', () => {
|
|
192
|
+
const t = appTool('browser', 'infsh/browser-use@v1')
|
|
193
|
+
.sessionEnabled()
|
|
194
|
+
.describe('Control a browser')
|
|
195
|
+
.build();
|
|
196
|
+
expect(t.app?.session_enabled).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
it('supports full configuration with function and session', () => {
|
|
199
|
+
const t = appTool('chat', 'infsh/chatbot@v1')
|
|
200
|
+
.describe('Chat with context')
|
|
201
|
+
.function('stream')
|
|
202
|
+
.sessionEnabled()
|
|
203
|
+
.setup({ api_key: 'secret' })
|
|
204
|
+
.input({ temperature: 0.7 })
|
|
205
|
+
.build();
|
|
206
|
+
expect(t.app).toEqual({
|
|
207
|
+
ref: 'infsh/chatbot@v1',
|
|
208
|
+
function: 'stream',
|
|
209
|
+
session_enabled: true,
|
|
210
|
+
setup: { api_key: 'secret' },
|
|
211
|
+
input: { temperature: 0.7 },
|
|
212
|
+
});
|
|
213
|
+
});
|
|
184
214
|
});
|
|
185
215
|
describe('AgentToolBuilder (agentTool)', () => {
|
|
186
216
|
it('creates agent tool with reference', () => {
|