@vertesia/client 1.6.0-dev.20260923.085325Z → 1.6.0-dev.20261004.085740Z
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/lib/EnvironmentsApi.d.ts +2 -1
- package/lib/EnvironmentsApi.d.ts.map +1 -1
- package/lib/EnvironmentsApi.js +3 -0
- package/lib/EnvironmentsApi.js.map +1 -1
- package/lib/InferenceProfilesApi.d.ts +14 -0
- package/lib/InferenceProfilesApi.d.ts.map +1 -0
- package/lib/InferenceProfilesApi.js +33 -0
- package/lib/InferenceProfilesApi.js.map +1 -0
- package/lib/InteractionBase.d.ts +2 -954
- package/lib/InteractionBase.d.ts.map +1 -1
- package/lib/InteractionConfigurationsApi.d.ts +9 -0
- package/lib/InteractionConfigurationsApi.d.ts.map +1 -0
- package/lib/InteractionConfigurationsApi.js +16 -0
- package/lib/InteractionConfigurationsApi.js.map +1 -0
- package/lib/InteractionsApi.d.ts +2 -7
- package/lib/InteractionsApi.d.ts.map +1 -1
- package/lib/InteractionsApi.js.map +1 -1
- package/lib/InternalRunBudgetsApi.d.ts +8 -0
- package/lib/InternalRunBudgetsApi.d.ts.map +1 -0
- package/lib/InternalRunBudgetsApi.js +11 -0
- package/lib/InternalRunBudgetsApi.js.map +1 -0
- package/lib/client.d.ts +8 -10
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +7 -0
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/store/AgentsApi.d.ts +6 -1
- package/lib/store/AgentsApi.d.ts.map +1 -1
- package/lib/store/AgentsApi.js +7 -0
- package/lib/store/AgentsApi.js.map +1 -1
- package/lib/store/ContentApi.d.ts +111 -0
- package/lib/store/ContentApi.d.ts.map +1 -0
- package/lib/store/ContentApi.js +83 -0
- package/lib/store/ContentApi.js.map +1 -0
- package/lib/store/DataApi.d.ts +1 -1
- package/lib/store/DataApi.js +1 -1
- package/lib/store/IndexingApi.d.ts +1 -1
- package/lib/store/IndexingApi.d.ts.map +1 -1
- package/lib/store/IndexingApi.js +2 -1
- package/lib/store/IndexingApi.js.map +1 -1
- package/lib/store/client.d.ts +2 -0
- package/lib/store/client.d.ts.map +1 -1
- package/lib/store/client.js +2 -0
- package/lib/store/client.js.map +1 -1
- package/lib/store/index.d.ts +1 -0
- package/lib/store/index.d.ts.map +1 -1
- package/lib/store/index.js +1 -0
- package/lib/store/index.js.map +1 -1
- package/lib/vertesia-client.js +4 -4
- package/lib/vertesia-client.js.map +1 -1
- package/package.json +11 -10
- package/src/EnvironmentsApi.ts +6 -0
- package/src/InferenceProfilesApi.ts +47 -0
- package/src/InteractionConfigurationsApi.ts +20 -0
- package/src/InteractionsApi.ts +2 -9
- package/src/InternalRunBudgetsApi.ts +13 -0
- package/src/client.ts +7 -0
- package/src/index.ts +1 -0
- package/src/store/AgentsApi.ts +9 -0
- package/src/store/ContentApi.ts +158 -0
- package/src/store/DataApi.ts +1 -1
- package/src/store/IndexingApi.ts +2 -0
- package/src/store/client.ts +2 -0
- package/src/store/index.ts +1 -0
- package/src/AccountApi.test.ts +0 -30
- package/src/AppsApi.test.ts +0 -132
- package/src/EnvironmentsApi.test.ts +0 -39
- package/src/InteractionOutput.test.ts +0 -338
- package/src/OAuthServerApi.test.ts +0 -56
- package/src/ProjectsApi.test.ts +0 -33
- package/src/RunsApi.test.ts +0 -24
- package/src/ViewsApi.test.ts +0 -38
- package/src/app-session.test.ts +0 -23
- package/src/bundle-isolation.test.ts +0 -37
- package/src/client.test.ts +0 -332
- package/src/execute.test.ts +0 -22
- package/src/store/AgentsApi.artifacts.test.ts +0 -119
- package/src/store/AgentsApi.history.test.ts +0 -29
- package/src/store/AgentsApi.stream.test.ts +0 -386
- package/src/store/DataApi.test.ts +0 -42
- package/src/store/EmbeddingsApi.test.ts +0 -31
- package/src/store/EventsApi.test.ts +0 -131
- package/src/store/FilesApi.test.ts +0 -118
- package/src/store/ObjectsApi.test.ts +0 -50
- package/src/store/ProcessApi.test.ts +0 -24
- package/src/store/ViewsApi.test.ts +0 -33
- package/src/store/signed-url.test.ts +0 -204
- package/src/store/stream-gap.test.ts +0 -230
- package/src/store/stream-termination.test.ts +0 -82
- package/src/store/uploadMimeType.test.ts +0 -22
package/src/client.test.ts
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import { APP_VERSION_HEADER } from '@vertesia/common';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
-
import { decodeJWT, isTokenExpired, VertesiaClient, type VertesiaClientProps } from './client.js';
|
|
4
|
-
import { ZenoClient, type ZenoClientProps } from './store/client.js';
|
|
5
|
-
import { resetUnknownOptionWarnings } from './unknown-options.js';
|
|
6
|
-
|
|
7
|
-
describe('Test Vertesia Client', () => {
|
|
8
|
-
test('Initialization with studio and zeno URLs', () => {
|
|
9
|
-
const client = new VertesiaClient({
|
|
10
|
-
serverUrl: 'https://api.vertesia.io',
|
|
11
|
-
storeUrl: 'https://api.vertesia.io',
|
|
12
|
-
tokenServerUrl: 'https://sts.vertesia.io',
|
|
13
|
-
apikey: '1234',
|
|
14
|
-
});
|
|
15
|
-
expect(client).toBeDefined();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('Initialization with studio URL only', () => {
|
|
19
|
-
expect(() => {
|
|
20
|
-
new VertesiaClient({
|
|
21
|
-
serverUrl: 'https://api.vertesia.io',
|
|
22
|
-
storeUrl: '',
|
|
23
|
-
});
|
|
24
|
-
}).toThrowError("Parameter 'site' or 'storeUrl' is required for VertesiaClient");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('Initialization with zeno URL only', () => {
|
|
28
|
-
expect(() => {
|
|
29
|
-
new VertesiaClient({
|
|
30
|
-
serverUrl: '',
|
|
31
|
-
storeUrl: 'https://api.vertesia.io',
|
|
32
|
-
});
|
|
33
|
-
}).toThrowError("Parameter 'site' or 'serverUrl' is required for VertesiaClient");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('Initialization with same site', () => {
|
|
37
|
-
const client = new VertesiaClient({
|
|
38
|
-
serverUrl: 'https://api.vertesia.io',
|
|
39
|
-
storeUrl: 'https://api.vertesia.io',
|
|
40
|
-
site: 'api.vertesia.io',
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(client).toBeDefined();
|
|
44
|
-
expect(client.baseUrl).toBe('https://api.vertesia.io');
|
|
45
|
-
expect(client.storeUrl).toBe('https://api.vertesia.io');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('Initialization with default parameters', () => {
|
|
49
|
-
const client = new VertesiaClient();
|
|
50
|
-
|
|
51
|
-
expect(client).toBeDefined();
|
|
52
|
-
expect(client.baseUrl).toBe('https://api.vertesia.io');
|
|
53
|
-
expect(client.storeUrl).toBe('https://api.vertesia.io');
|
|
54
|
-
expect(client.tokenServerUrl).toBe('https://sts.vertesia.io');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('Initialization with site api-preview.vertesia.io', () => {
|
|
58
|
-
const client = new VertesiaClient({
|
|
59
|
-
site: 'api-preview.vertesia.io',
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
expect(client).toBeDefined();
|
|
63
|
-
expect(client.baseUrl).toBe('https://api-preview.vertesia.io');
|
|
64
|
-
expect(client.storeUrl).toBe('https://api-preview.vertesia.io');
|
|
65
|
-
// preview keeps its env segment: api-preview → sts-preview (its own STS)
|
|
66
|
-
expect(client.tokenServerUrl).toBe('https://sts-preview.vertesia.io');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('Initialization with site api.dev1.vertesia.io', () => {
|
|
70
|
-
const client = new VertesiaClient({
|
|
71
|
-
site: 'api.dev1.vertesia.io',
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
expect(client).toBeDefined();
|
|
75
|
-
expect(client.baseUrl).toBe('https://api.dev1.vertesia.io');
|
|
76
|
-
expect(client.storeUrl).toBe('https://api.dev1.vertesia.io');
|
|
77
|
-
expect(client.tokenServerUrl).toBe('https://sts.dev1.vertesia.io');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('Initialization with site api-preview.dev1.vertesia.io', () => {
|
|
81
|
-
const client = new VertesiaClient({
|
|
82
|
-
site: 'api-preview.dev1.vertesia.io',
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
expect(client).toBeDefined();
|
|
86
|
-
expect(client.baseUrl).toBe('https://api-preview.dev1.vertesia.io');
|
|
87
|
-
expect(client.storeUrl).toBe('https://api-preview.dev1.vertesia.io');
|
|
88
|
-
// preview keeps its env segment: api-preview → sts-preview (its own STS)
|
|
89
|
-
expect(client.tokenServerUrl).toBe('https://sts-preview.dev1.vertesia.io');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('Initialization with regional serverUrl (api.us1)', () => {
|
|
93
|
-
const client = new VertesiaClient({
|
|
94
|
-
serverUrl: 'https://api.us1.vertesia.io',
|
|
95
|
-
storeUrl: 'https://api.us1.vertesia.io',
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
expect(client).toBeDefined();
|
|
99
|
-
expect(client.baseUrl).toBe('https://api.us1.vertesia.io');
|
|
100
|
-
expect(client.storeUrl).toBe('https://api.us1.vertesia.io');
|
|
101
|
-
expect(client.tokenServerUrl).toBe('https://sts.us1.vertesia.io');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('Initialization with regional serverUrl (api.eu1)', () => {
|
|
105
|
-
const client = new VertesiaClient({
|
|
106
|
-
serverUrl: 'https://api.eu1.vertesia.io',
|
|
107
|
-
storeUrl: 'https://api.eu1.vertesia.io',
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(client).toBeDefined();
|
|
111
|
-
expect(client.baseUrl).toBe('https://api.eu1.vertesia.io');
|
|
112
|
-
expect(client.storeUrl).toBe('https://api.eu1.vertesia.io');
|
|
113
|
-
expect(client.tokenServerUrl).toBe('https://sts.eu1.vertesia.io');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test('Initialization with regional preview serverUrl (api-preview.us1)', () => {
|
|
117
|
-
const client = new VertesiaClient({
|
|
118
|
-
serverUrl: 'https://api-preview.us1.vertesia.io',
|
|
119
|
-
storeUrl: 'https://api-preview.us1.vertesia.io',
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
expect(client).toBeDefined();
|
|
123
|
-
expect(client.baseUrl).toBe('https://api-preview.us1.vertesia.io');
|
|
124
|
-
// preview keeps its env segment: api-preview → sts-preview (its own versioned STS)
|
|
125
|
-
expect(client.tokenServerUrl).toBe('https://sts-preview.us1.vertesia.io');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('Initialization with site localhost', () => {
|
|
129
|
-
const client = new VertesiaClient({
|
|
130
|
-
serverUrl: 'http://localhost:8091',
|
|
131
|
-
storeUrl: 'http://localhost:8092',
|
|
132
|
-
tokenServerUrl: 'http://localhost:8093',
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
expect(client).toBeDefined();
|
|
136
|
-
expect(client.baseUrl).toBe('http://localhost:8091');
|
|
137
|
-
expect(client.storeUrl).toBe('http://localhost:8092');
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('Initialization with overrides', () => {
|
|
141
|
-
const client = new VertesiaClient({
|
|
142
|
-
serverUrl: 'https://studio-server-production.api.becomposable.com',
|
|
143
|
-
storeUrl: 'https://zeno-server-production.api.becomposable.com',
|
|
144
|
-
site: 'api.vertesia.io',
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
expect(client).toBeDefined();
|
|
148
|
-
expect(client.baseUrl).toBe('https://studio-server-production.api.becomposable.com');
|
|
149
|
-
expect(client.storeUrl).toBe('https://zeno-server-production.api.becomposable.com');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('withAppVersion keeps Studio and Store requests pinned together', () => {
|
|
153
|
-
const client = new VertesiaClient({
|
|
154
|
-
serverUrl: 'https://studio-server-production.api.becomposable.com',
|
|
155
|
-
storeUrl: 'https://zeno-server-production.api.becomposable.com',
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
expect(client.withAppVersion('candidate-v1')).toBe(client);
|
|
159
|
-
expect(client.headers[APP_VERSION_HEADER]).toBe('candidate-v1');
|
|
160
|
-
expect(client.store.headers[APP_VERSION_HEADER]).toBe('candidate-v1');
|
|
161
|
-
|
|
162
|
-
client.withAppVersion(null);
|
|
163
|
-
expect(client.headers[APP_VERSION_HEADER]).toBeUndefined();
|
|
164
|
-
expect(client.store.headers[APP_VERSION_HEADER]).toBeUndefined();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('isTokenExpired', () => {
|
|
169
|
-
// EXPIRATION_THRESHOLD inside client.ts is 60000ms (60s). Tokens with `exp`
|
|
170
|
-
// less than 60s in the future are treated as expired so the caller refreshes
|
|
171
|
-
// proactively.
|
|
172
|
-
const REFRESH_WINDOW_MS = 60_000;
|
|
173
|
-
const NOW_MS = 1_700_000_000_000;
|
|
174
|
-
|
|
175
|
-
function base64UrlEncode(s: string): string {
|
|
176
|
-
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function makeToken(exp: number): string {
|
|
180
|
-
const header = base64UrlEncode(JSON.stringify({ alg: 'none', typ: 'JWT' }));
|
|
181
|
-
const payload = base64UrlEncode(JSON.stringify({ exp }));
|
|
182
|
-
return `${header}.${payload}.signature`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
beforeEach(() => {
|
|
186
|
-
vi.useFakeTimers();
|
|
187
|
-
vi.setSystemTime(new Date(NOW_MS));
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
afterEach(() => {
|
|
191
|
-
vi.useRealTimers();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('returns true for a null token', () => {
|
|
195
|
-
expect(isTokenExpired(null)).toBe(true);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('returns false for a fresh token comfortably inside its lifetime', () => {
|
|
199
|
-
// 1 hour in the future
|
|
200
|
-
const exp = Math.floor(NOW_MS / 1000) + 3600;
|
|
201
|
-
expect(isTokenExpired(makeToken(exp))).toBe(false);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test('returns true when the token is within the refresh threshold of expiry', () => {
|
|
205
|
-
// 30s in the future — inside the 60s refresh window
|
|
206
|
-
const exp = Math.floor((NOW_MS + 30_000) / 1000);
|
|
207
|
-
expect(isTokenExpired(makeToken(exp))).toBe(true);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test('returns true for a token whose exp is already in the past', () => {
|
|
211
|
-
// expired 100s ago
|
|
212
|
-
const exp = Math.floor((NOW_MS - 100_000) / 1000);
|
|
213
|
-
expect(isTokenExpired(makeToken(exp))).toBe(true);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('returns true exactly at the refresh-window boundary', () => {
|
|
217
|
-
// exp is exactly REFRESH_WINDOW_MS in the future — should already trigger refresh
|
|
218
|
-
const exp = Math.floor((NOW_MS + REFRESH_WINDOW_MS) / 1000);
|
|
219
|
-
expect(isTokenExpired(makeToken(exp))).toBe(true);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe('unknown constructor options', () => {
|
|
224
|
-
// Model the real escape hatch rather than a cast: TypeScript's excess-property check only fires
|
|
225
|
-
// on an object literal, so a spread of a wider config object reaches the constructor unchecked.
|
|
226
|
-
function optionsWith(extra: Record<string, unknown>): VertesiaClientProps {
|
|
227
|
-
return {
|
|
228
|
-
serverUrl: 'https://api.vertesia.io',
|
|
229
|
-
storeUrl: 'https://api.vertesia.io',
|
|
230
|
-
...extra,
|
|
231
|
-
} as VertesiaClientProps;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
beforeEach(() => {
|
|
235
|
-
resetUnknownOptionWarnings();
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
afterEach(() => {
|
|
239
|
-
vi.restoreAllMocks();
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// The option names below are deliberately spelled the way the SDK does NOT accept them. Passing
|
|
243
|
-
// them used to produce a client that looked fine and then failed every request with
|
|
244
|
-
// `401 Unauthorized: Authorization token is required`, with nothing pointing at the constructor.
|
|
245
|
-
test('names the option and what the caller meant', () => {
|
|
246
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
247
|
-
|
|
248
|
-
new VertesiaClient(optionsWith({ token: 'jwt' }));
|
|
249
|
-
|
|
250
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
251
|
-
const message = warn.mock.calls[0][0] as string;
|
|
252
|
-
expect(message).toContain('[VertesiaClient]');
|
|
253
|
-
expect(message).toContain('token');
|
|
254
|
-
expect(message).toContain('`apikey`');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test('reports each unknown option once, not once per client', () => {
|
|
258
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
259
|
-
const opts = optionsWith({ appVersion: 'v1' });
|
|
260
|
-
|
|
261
|
-
new VertesiaClient(opts);
|
|
262
|
-
new VertesiaClient(opts);
|
|
263
|
-
|
|
264
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
265
|
-
expect(warn.mock.calls[0][0]).toContain('withAppVersion');
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test('stays silent for a fully valid options object', () => {
|
|
269
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
270
|
-
|
|
271
|
-
new VertesiaClient({
|
|
272
|
-
serverUrl: 'https://api.vertesia.io',
|
|
273
|
-
storeUrl: 'https://api.vertesia.io',
|
|
274
|
-
tokenServerUrl: 'https://sts.vertesia.io',
|
|
275
|
-
apikey: 'sk-1234',
|
|
276
|
-
sessionTags: 'test',
|
|
277
|
-
timeout: 1000,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
expect(warn).not.toHaveBeenCalled();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
// `key in known` would treat these as known options, because every object inherits them.
|
|
284
|
-
test.each(['toString', 'constructor', 'valueOf', 'hasOwnProperty'])(
|
|
285
|
-
'reports %s, which is inherited from Object.prototype',
|
|
286
|
-
(key) => {
|
|
287
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
288
|
-
|
|
289
|
-
new VertesiaClient(optionsWith({ [key]: 'whatever' }));
|
|
290
|
-
|
|
291
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
292
|
-
expect(warn.mock.calls[0][0]).toContain(key);
|
|
293
|
-
},
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
test('covers the store client too', () => {
|
|
297
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
298
|
-
|
|
299
|
-
new ZenoClient({
|
|
300
|
-
serverUrl: 'https://api.vertesia.io',
|
|
301
|
-
...{ apiKey: 'sk-1234' },
|
|
302
|
-
} as ZenoClientProps);
|
|
303
|
-
|
|
304
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
305
|
-
expect(warn.mock.calls[0][0]).toContain('[ZenoClient]');
|
|
306
|
-
expect(warn.mock.calls[0][0]).toContain('apikey');
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
describe('decodeJWT', () => {
|
|
311
|
-
// An empty or non-JWT credential used to reach `''.split('.')[1]` and die inside the base64
|
|
312
|
-
// decoder with a TypeError naming neither the token nor the caller.
|
|
313
|
-
test.each([
|
|
314
|
-
['an empty string', ''],
|
|
315
|
-
['an API key', 'sk-abcdef'],
|
|
316
|
-
['a two-segment string', 'header.payload'],
|
|
317
|
-
])('names the failure for %s', (_label, value) => {
|
|
318
|
-
expect(() => decodeJWT(value)).toThrowError(/Invalid auth token: expected a JWT/);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
test('names a payload that is not JSON', () => {
|
|
322
|
-
expect(() => decodeJWT('aGVhZGVy.bm90LWpzb24.sig')).toThrowError(
|
|
323
|
-
'Invalid auth token: payload segment is not JSON',
|
|
324
|
-
);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test('rejects an empty token before decoding it', async () => {
|
|
328
|
-
await expect(VertesiaClient.fromAuthToken(' ')).rejects.toThrowError(
|
|
329
|
-
'VertesiaClient.fromAuthToken requires a non-empty auth token',
|
|
330
|
-
);
|
|
331
|
-
});
|
|
332
|
-
});
|
package/src/execute.test.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import type { VertesiaClient } from './client.js';
|
|
3
|
-
import { executeInteractionByName } from './execute.js';
|
|
4
|
-
|
|
5
|
-
describe('executeInteractionByName', () => {
|
|
6
|
-
it('forwards private per-request headers without placing them in the execution payload', async () => {
|
|
7
|
-
const post = vi.fn().mockResolvedValue({ id: 'run-1', status: 'completed' });
|
|
8
|
-
const client = { post } as unknown as VertesiaClient;
|
|
9
|
-
|
|
10
|
-
await executeInteractionByName(client, 'sys:test', { data: { message: 'hello' } }, undefined, {
|
|
11
|
-
headers: { 'x-vertesia-required-tool-name': 'app_workspace_init' },
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
expect(post).toHaveBeenCalledWith(
|
|
15
|
-
'/api/v1/execute',
|
|
16
|
-
expect.objectContaining({
|
|
17
|
-
headers: { 'x-vertesia-required-tool-name': 'app_workspace_init' },
|
|
18
|
-
payload: expect.not.objectContaining({ required_tool_name: expect.anything() }),
|
|
19
|
-
}),
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { escapeArtifactPathDelimiters } from './AgentsApi.js';
|
|
3
|
-
import { ZenoClient } from './client.js';
|
|
4
|
-
|
|
5
|
-
const SERVER_URL = 'https://store.test';
|
|
6
|
-
const RUN_ID = 'run-1';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A client whose injected fetch records the URL of every request it is handed. The URL is read off
|
|
10
|
-
* the Request object, so the assertions see the path after WHATWG URL parsing — which is where an
|
|
11
|
-
* unescaped `#` was being dropped.
|
|
12
|
-
*/
|
|
13
|
-
function clientRecordingUrls(body: unknown = { url: 'https://signed', path: 'p' }) {
|
|
14
|
-
const urls: string[] = [];
|
|
15
|
-
const client = new ZenoClient({
|
|
16
|
-
serverUrl: SERVER_URL,
|
|
17
|
-
apikey: 'token',
|
|
18
|
-
fetch: async (input: RequestInfo) => {
|
|
19
|
-
urls.push(input instanceof Request ? input.url : String(input));
|
|
20
|
-
return new Response(JSON.stringify(body), {
|
|
21
|
-
status: 200,
|
|
22
|
-
headers: { 'content-type': 'application/json' },
|
|
23
|
-
});
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
return { client, urls };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The artifact path the server reconstructs: the URL path after the route prefix, decoded per segment. */
|
|
30
|
-
function serverPath(url: string, prefix: string): string {
|
|
31
|
-
const tail = new URL(url).pathname.split(`/${prefix}/`)[1] ?? '';
|
|
32
|
-
return tail.split('/').map(decodeURIComponent).join('/');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
describe('escapeArtifactPathDelimiters', () => {
|
|
36
|
-
it('escapes the two characters that truncate a path', () => {
|
|
37
|
-
expect(escapeArtifactPathDelimiters('files/tpl (YYYY-0#).docx')).toBe('files/tpl (YYYY-0%23).docx');
|
|
38
|
-
expect(escapeArtifactPathDelimiters('files/report?v2.docx')).toBe('files/report%3Fv2.docx');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Scope guard. Anything this helper touches changes the bytes on the wire for filenames that
|
|
42
|
-
// work today, so it must leave everything except `#` and `?` exactly as it found it.
|
|
43
|
-
it.each([
|
|
44
|
-
['percent', 'files/100% done.docx'],
|
|
45
|
-
['spaces', 'files/MHA - Client IQ FSD.docx'],
|
|
46
|
-
['non-ascii and accents', 'files/café résumé.docx'],
|
|
47
|
-
['backslash', 'files/back\\slash.docx'],
|
|
48
|
-
['segment separators', 'files/nested/a b.txt'],
|
|
49
|
-
['already-encoded sequence', 'files/report%23.md'],
|
|
50
|
-
])('leaves %s untouched', (_label, path) => {
|
|
51
|
-
expect(escapeArtifactPathDelimiters(path)).toBe(path);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('AgentsApi artifact paths', () => {
|
|
56
|
-
afterEach(() => {
|
|
57
|
-
vi.restoreAllMocks();
|
|
58
|
-
vi.unstubAllGlobals();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// A `#` in an uploaded filename used to be parsed as the URL fragment, so the server saw a
|
|
62
|
-
// truncated path and stored the object under a key nothing could read back.
|
|
63
|
-
it('uploadArtifact preserves a path containing #', async () => {
|
|
64
|
-
vi.stubGlobal(
|
|
65
|
-
'fetch',
|
|
66
|
-
vi.fn(async () => new Response(null, { status: 200 })),
|
|
67
|
-
);
|
|
68
|
-
const { client, urls } = clientRecordingUrls();
|
|
69
|
-
const path = 'files/Design Template (YYYY-0#).docx';
|
|
70
|
-
|
|
71
|
-
await client.agents.uploadArtifact(RUN_ID, path, 'content');
|
|
72
|
-
|
|
73
|
-
expect(urls).toHaveLength(1);
|
|
74
|
-
expect(urls[0]).not.toContain('#');
|
|
75
|
-
expect(serverPath(urls[0], 'artifacts')).toBe(path);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// The query is concatenated onto the path, so an unescaped delimiter took it down too.
|
|
79
|
-
it.each([
|
|
80
|
-
['hash', 'files/Design (YYYY-0#).docx'],
|
|
81
|
-
['question mark', 'files/report?v2.docx'],
|
|
82
|
-
])('getArtifactUrl round-trips %s and keeps the query intact', async (_label, path) => {
|
|
83
|
-
const { client, urls } = clientRecordingUrls();
|
|
84
|
-
|
|
85
|
-
await client.agents.getArtifactUrl(RUN_ID, path, 'attachment', 'download-name.docx');
|
|
86
|
-
|
|
87
|
-
const url = new URL(urls[0]);
|
|
88
|
-
expect(serverPath(urls[0], 'artifacts')).toBe(path);
|
|
89
|
-
expect(url.hash).toBe('');
|
|
90
|
-
expect(url.searchParams.get('url')).toBe('1');
|
|
91
|
-
expect(url.searchParams.get('disposition')).toBe('attachment');
|
|
92
|
-
expect(url.searchParams.get('filename')).toBe('download-name.docx');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Pins the scope boundary: `%` must go on the wire exactly as it does with no escaping at all.
|
|
96
|
-
it('sends a path containing % exactly as it did before, unescaped', async () => {
|
|
97
|
-
const { client, urls } = clientRecordingUrls();
|
|
98
|
-
const path = 'files/100% done.docx';
|
|
99
|
-
|
|
100
|
-
await client.agents.getArtifactUrl(RUN_ID, path);
|
|
101
|
-
|
|
102
|
-
const unescaped = new URL(`${SERVER_URL}/api/v1/agents/${RUN_ID}/artifacts/${path}?url=1`);
|
|
103
|
-
expect(new URL(urls[0]).pathname).toBe(unescaped.pathname);
|
|
104
|
-
expect(new URL(urls[0]).pathname).toContain('100%%20done.docx');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('getArtifactContent and updateArtifactContent preserve a path containing #', async () => {
|
|
108
|
-
const { client, urls } = clientRecordingUrls({ content: '', generation: '1' });
|
|
109
|
-
const path = 'files/notes (v0#1).md';
|
|
110
|
-
|
|
111
|
-
await client.agents.getArtifactContent(RUN_ID, path);
|
|
112
|
-
await client.agents.updateArtifactContent(RUN_ID, path, { content: 'x', generation: '1' });
|
|
113
|
-
|
|
114
|
-
expect(urls).toHaveLength(2);
|
|
115
|
-
for (const url of urls) {
|
|
116
|
-
expect(serverPath(url, 'artifact-content')).toBe(path);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { VertesiaClient } from '../client.js';
|
|
3
|
-
|
|
4
|
-
describe('agent history cursor transport', () => {
|
|
5
|
-
it('passes the cursor and response metadata through for main and child details', async () => {
|
|
6
|
-
const urls: URL[] = [];
|
|
7
|
-
const response = { history: { type: 'agent', mode: 'delta', next_from: 'next', agentTasks: [] } };
|
|
8
|
-
const client = new VertesiaClient({
|
|
9
|
-
serverUrl: 'https://studio.test',
|
|
10
|
-
storeUrl: 'https://store.test',
|
|
11
|
-
fetch: (async (input: Request | string) => {
|
|
12
|
-
urls.push(new URL(typeof input === 'string' ? input : input.url));
|
|
13
|
-
return Response.json(response);
|
|
14
|
-
}) as typeof fetch,
|
|
15
|
-
});
|
|
16
|
-
const options = { includeHistory: true, hydratePayloads: true, from: 'opaque/+=' };
|
|
17
|
-
expect(await client.agents.getRunDetails('agent', options)).toEqual(response);
|
|
18
|
-
expect(await client.agents.getChildDetails('agent', 'child', options)).toEqual(response);
|
|
19
|
-
expect(urls.map((url) => url.pathname)).toEqual([
|
|
20
|
-
'/api/v1/agents/agent/details',
|
|
21
|
-
'/api/v1/agents/agent/children/child/details',
|
|
22
|
-
]);
|
|
23
|
-
for (const url of urls) {
|
|
24
|
-
expect(url.searchParams.get('from')).toBe(options.from);
|
|
25
|
-
expect(url.searchParams.get('include_history')).toBe('true');
|
|
26
|
-
expect(url.searchParams.get('hydrate_payloads')).toBe('true');
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
});
|