@vertesia/client 1.6.0-dev.20260918.115658Z → 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.
Files changed (112) hide show
  1. package/lib/AppsApi.d.ts +5 -0
  2. package/lib/AppsApi.d.ts.map +1 -1
  3. package/lib/AppsApi.js +9 -0
  4. package/lib/AppsApi.js.map +1 -1
  5. package/lib/EnvironmentsApi.d.ts +2 -1
  6. package/lib/EnvironmentsApi.d.ts.map +1 -1
  7. package/lib/EnvironmentsApi.js +3 -0
  8. package/lib/EnvironmentsApi.js.map +1 -1
  9. package/lib/InferenceProfilesApi.d.ts +14 -0
  10. package/lib/InferenceProfilesApi.d.ts.map +1 -0
  11. package/lib/InferenceProfilesApi.js +33 -0
  12. package/lib/InferenceProfilesApi.js.map +1 -0
  13. package/lib/InteractionBase.d.ts +2 -878
  14. package/lib/InteractionBase.d.ts.map +1 -1
  15. package/lib/InteractionConfigurationsApi.d.ts +9 -0
  16. package/lib/InteractionConfigurationsApi.d.ts.map +1 -0
  17. package/lib/InteractionConfigurationsApi.js +16 -0
  18. package/lib/InteractionConfigurationsApi.js.map +1 -0
  19. package/lib/InteractionOutput.d.ts +4 -0
  20. package/lib/InteractionOutput.d.ts.map +1 -1
  21. package/lib/InteractionOutput.js +13 -0
  22. package/lib/InteractionOutput.js.map +1 -1
  23. package/lib/InteractionsApi.d.ts +2 -7
  24. package/lib/InteractionsApi.d.ts.map +1 -1
  25. package/lib/InteractionsApi.js.map +1 -1
  26. package/lib/InternalAccountsApi.d.ts +7 -0
  27. package/lib/InternalAccountsApi.d.ts.map +1 -0
  28. package/lib/InternalAccountsApi.js +10 -0
  29. package/lib/InternalAccountsApi.js.map +1 -0
  30. package/lib/InternalRunBudgetsApi.d.ts +8 -0
  31. package/lib/InternalRunBudgetsApi.d.ts.map +1 -0
  32. package/lib/InternalRunBudgetsApi.js +11 -0
  33. package/lib/InternalRunBudgetsApi.js.map +1 -0
  34. package/lib/OAuthServerApi.d.ts +2 -1
  35. package/lib/OAuthServerApi.d.ts.map +1 -1
  36. package/lib/OAuthServerApi.js +3 -0
  37. package/lib/OAuthServerApi.js.map +1 -1
  38. package/lib/client.d.ts +10 -10
  39. package/lib/client.d.ts.map +1 -1
  40. package/lib/client.js +10 -0
  41. package/lib/client.js.map +1 -1
  42. package/lib/index.d.ts +2 -0
  43. package/lib/index.d.ts.map +1 -1
  44. package/lib/index.js +2 -0
  45. package/lib/index.js.map +1 -1
  46. package/lib/store/AgentsApi.d.ts +6 -1
  47. package/lib/store/AgentsApi.d.ts.map +1 -1
  48. package/lib/store/AgentsApi.js +22 -0
  49. package/lib/store/AgentsApi.js.map +1 -1
  50. package/lib/store/ContentApi.d.ts +111 -0
  51. package/lib/store/ContentApi.d.ts.map +1 -0
  52. package/lib/store/ContentApi.js +83 -0
  53. package/lib/store/ContentApi.js.map +1 -0
  54. package/lib/store/DataApi.d.ts +1 -1
  55. package/lib/store/DataApi.js +1 -1
  56. package/lib/store/IndexingApi.d.ts +1 -1
  57. package/lib/store/IndexingApi.d.ts.map +1 -1
  58. package/lib/store/IndexingApi.js +2 -1
  59. package/lib/store/IndexingApi.js.map +1 -1
  60. package/lib/store/client.d.ts +2 -0
  61. package/lib/store/client.d.ts.map +1 -1
  62. package/lib/store/client.js +2 -0
  63. package/lib/store/client.js.map +1 -1
  64. package/lib/store/index.d.ts +1 -0
  65. package/lib/store/index.d.ts.map +1 -1
  66. package/lib/store/index.js +1 -0
  67. package/lib/store/index.js.map +1 -1
  68. package/lib/vertesia-client.js +4 -4
  69. package/lib/vertesia-client.js.map +1 -1
  70. package/package.json +12 -11
  71. package/src/AppsApi.ts +11 -0
  72. package/src/EnvironmentsApi.ts +6 -0
  73. package/src/InferenceProfilesApi.ts +47 -0
  74. package/src/InteractionConfigurationsApi.ts +20 -0
  75. package/src/InteractionOutput.ts +15 -0
  76. package/src/InteractionsApi.ts +2 -9
  77. package/src/InternalAccountsApi.ts +12 -0
  78. package/src/InternalRunBudgetsApi.ts +13 -0
  79. package/src/OAuthServerApi.ts +6 -0
  80. package/src/client.ts +12 -0
  81. package/src/index.ts +2 -0
  82. package/src/store/AgentsApi.ts +26 -0
  83. package/src/store/ContentApi.ts +158 -0
  84. package/src/store/DataApi.ts +1 -1
  85. package/src/store/IndexingApi.ts +2 -0
  86. package/src/store/client.ts +2 -0
  87. package/src/store/index.ts +1 -0
  88. package/src/AccountApi.test.ts +0 -30
  89. package/src/AppsApi.test.ts +0 -132
  90. package/src/EnvironmentsApi.test.ts +0 -39
  91. package/src/InteractionOutput.test.ts +0 -324
  92. package/src/OAuthServerApi.test.ts +0 -56
  93. package/src/ProjectsApi.test.ts +0 -33
  94. package/src/RunsApi.test.ts +0 -24
  95. package/src/ViewsApi.test.ts +0 -38
  96. package/src/bundle-isolation.test.ts +0 -37
  97. package/src/client.test.ts +0 -332
  98. package/src/execute.test.ts +0 -22
  99. package/src/store/AgentsApi.artifacts.test.ts +0 -119
  100. package/src/store/AgentsApi.history.test.ts +0 -29
  101. package/src/store/AgentsApi.stream.test.ts +0 -354
  102. package/src/store/DataApi.test.ts +0 -42
  103. package/src/store/EmbeddingsApi.test.ts +0 -31
  104. package/src/store/EventsApi.test.ts +0 -131
  105. package/src/store/FilesApi.test.ts +0 -105
  106. package/src/store/ObjectsApi.test.ts +0 -50
  107. package/src/store/ProcessApi.test.ts +0 -24
  108. package/src/store/ViewsApi.test.ts +0 -33
  109. package/src/store/signed-url.test.ts +0 -204
  110. package/src/store/stream-gap.test.ts +0 -230
  111. package/src/store/stream-termination.test.ts +0 -82
  112. package/src/store/uploadMimeType.test.ts +0 -22
@@ -1,354 +0,0 @@
1
- import { type AgentMessage, AgentMessageType, type CompactMessage } from '@vertesia/common';
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { VertesiaClient } from '../client.js';
4
-
5
- const AGENT_RUN_ID = 'run-1';
6
- const STORE_URL = 'https://store.example.test';
7
-
8
- class FakeEventSource {
9
- static readonly CONNECTING = 0;
10
- static readonly OPEN = 1;
11
- static readonly CLOSED = 2;
12
-
13
- static instances: FakeEventSource[] = [];
14
- static urls: string[] = [];
15
-
16
- readonly url: string;
17
- readonly withCredentials = false;
18
- readyState = FakeEventSource.CONNECTING;
19
- onopen: ((event: Event) => void) | null = null;
20
- onmessage: ((event: MessageEvent) => void) | null = null;
21
- onerror: ((event: Event) => void) | null = null;
22
-
23
- constructor(url: string) {
24
- this.url = url;
25
- FakeEventSource.urls.push(url);
26
- FakeEventSource.instances.push(this);
27
- }
28
-
29
- close() {
30
- this.readyState = FakeEventSource.CLOSED;
31
- }
32
-
33
- addEventListener() {}
34
- removeEventListener() {}
35
- dispatchEvent() {
36
- return true;
37
- }
38
-
39
- fail() {
40
- this.onerror?.(new Event('error'));
41
- }
42
-
43
- emit(message: CompactMessage) {
44
- this.onmessage?.({ data: JSON.stringify(message) } as MessageEvent);
45
- }
46
- }
47
-
48
- interface Harness {
49
- client: VertesiaClient;
50
- /** `since` query value of every GET /updates call after the initial history fetch. */
51
- pollSince: number[];
52
- setHistory: (messages: CompactMessage[]) => void;
53
- setPollResponse: (handler: (since: number) => CompactMessage[]) => void;
54
- setRunStatus: (status: string) => void;
55
- issuedTokens: string[];
56
- }
57
-
58
- function createHarness(): Harness {
59
- let history: CompactMessage[] = [];
60
- let pollResponse: (since: number) => CompactMessage[] = () => [];
61
- let runStatus = 'running';
62
- let historyServed = false;
63
- const pollSince: number[] = [];
64
- const issuedTokens: string[] = [];
65
-
66
- const fetchMock = vi.fn(async (input: Request | string) => {
67
- const url = new URL(typeof input === 'string' ? input : input.url);
68
- if (url.pathname.endsWith('/updates')) {
69
- if (!historyServed) {
70
- historyServed = true;
71
- return Response.json({ messages: history });
72
- }
73
- const since = Number(url.searchParams.get('since') ?? 0);
74
- pollSince.push(since);
75
- return Response.json({ messages: pollResponse(since) });
76
- }
77
- if (url.pathname.endsWith(`/${AGENT_RUN_ID}`)) {
78
- return Response.json({ id: AGENT_RUN_ID, status: runStatus });
79
- }
80
- return Response.json({ error: 'not found' }, { status: 404 });
81
- });
82
-
83
- const client = new VertesiaClient({
84
- serverUrl: 'https://studio.example.test',
85
- storeUrl: STORE_URL,
86
- fetch: fetchMock as unknown as typeof fetch,
87
- });
88
-
89
- // Mimics a refreshing credential provider: every call hands out a newer token.
90
- client.withAuthCallback(async () => {
91
- const token = `token-${issuedTokens.length + 1}`;
92
- issuedTokens.push(token);
93
- return `Bearer ${token}`;
94
- });
95
-
96
- return {
97
- client,
98
- pollSince,
99
- issuedTokens,
100
- setHistory: (messages) => {
101
- history = messages;
102
- },
103
- setPollResponse: (handler) => {
104
- pollResponse = handler;
105
- },
106
- setRunStatus: (status) => {
107
- runStatus = status;
108
- },
109
- };
110
- }
111
-
112
- /** Advance fake timers and drain the microtask queue so awaited fetches settle. */
113
- async function settle(ms = 0) {
114
- await vi.advanceTimersByTimeAsync(ms);
115
- for (let i = 0; i < 20; i++) {
116
- await Promise.resolve();
117
- }
118
- }
119
-
120
- /** Longer than the capped backoff delay (30s + jitter). */
121
- const BACKOFF_CEILING_MS = 40000;
122
-
123
- async function failCurrentConnection() {
124
- FakeEventSource.instances[FakeEventSource.instances.length - 1].fail();
125
- await settle(BACKOFF_CEILING_MS);
126
- }
127
-
128
- describe('AgentsApi.streamMessages reconnection', () => {
129
- let logs: string[];
130
- let warnings: string[];
131
-
132
- beforeEach(() => {
133
- vi.useFakeTimers();
134
- FakeEventSource.instances = [];
135
- FakeEventSource.urls = [];
136
- logs = [];
137
- warnings = [];
138
- vi.stubGlobal('EventSource', FakeEventSource);
139
- vi.spyOn(console, 'log').mockImplementation((...args: unknown[]) => {
140
- logs.push(args.map(String).join(' '));
141
- });
142
- vi.spyOn(console, 'warn').mockImplementation((...args: unknown[]) => {
143
- warnings.push(args.map(String).join(' '));
144
- });
145
- vi.spyOn(console, 'error').mockImplementation(() => {});
146
- });
147
-
148
- afterEach(() => {
149
- vi.unstubAllGlobals();
150
- vi.restoreAllMocks();
151
- vi.useRealTimers();
152
- });
153
-
154
- it('keeps interactive streams open on root IDLE by default', async () => {
155
- const harness = createHarness();
156
- const settled = vi.fn();
157
- const received: AgentMessage[] = [];
158
- const done = harness.client.agents
159
- .streamMessages(AGENT_RUN_ID, (message) => received.push(message))
160
- .then(settled);
161
- await settle();
162
-
163
- FakeEventSource.instances[0].emit({
164
- t: AgentMessageType.IDLE,
165
- m: 'Waiting for your command...',
166
- w: 'main',
167
- ts: 1_000,
168
- });
169
- await settle();
170
- expect(settled).not.toHaveBeenCalled();
171
-
172
- FakeEventSource.instances[0].emit({
173
- t: AgentMessageType.ANSWER,
174
- m: 'Follow-up response',
175
- w: 'main',
176
- ts: 1_500,
177
- });
178
- await settle();
179
- expect(received.map((message) => message.message)).toEqual([
180
- 'Waiting for your command...',
181
- 'Follow-up response',
182
- ]);
183
-
184
- FakeEventSource.instances[0].emit({
185
- t: AgentMessageType.COMPLETE,
186
- m: 'Conversation complete',
187
- w: 'main',
188
- ts: 2_000,
189
- });
190
- await expect(done).resolves.toBeUndefined();
191
- });
192
-
193
- it('lets headless followers opt into closing on root IDLE', async () => {
194
- const harness = createHarness();
195
- const done = harness.client.agents.streamMessages(AGENT_RUN_ID, undefined, undefined, undefined, {
196
- closeOnIdle: true,
197
- });
198
- await settle();
199
-
200
- FakeEventSource.instances[0].emit({
201
- t: AgentMessageType.IDLE,
202
- m: 'Waiting for your command...',
203
- w: 'main',
204
- ts: 1_000,
205
- });
206
-
207
- await expect(done).resolves.toBeNull();
208
- });
209
-
210
- it('resolves a fresh auth token for every reconnection attempt', async () => {
211
- const harness = createHarness();
212
- const abort = new AbortController();
213
- void harness.client.agents.streamMessages(AGENT_RUN_ID, undefined, undefined, abort.signal);
214
- await settle();
215
-
216
- expect(FakeEventSource.instances).toHaveLength(1);
217
- await failCurrentConnection();
218
- await failCurrentConnection();
219
- expect(FakeEventSource.instances).toHaveLength(3);
220
-
221
- const tokens = FakeEventSource.urls.map((url) => new URL(url).searchParams.get('access_token'));
222
- expect(tokens).toHaveLength(3);
223
- expect(new Set(tokens).size).toBe(3);
224
- expect(tokens.every((token) => token?.startsWith('token-'))).toBe(true);
225
-
226
- abort.abort();
227
- await settle();
228
- });
229
-
230
- it('never announces an attempt number above the stated maximum', async () => {
231
- const harness = createHarness();
232
- const abort = new AbortController();
233
- void harness.client.agents.streamMessages(AGENT_RUN_ID, undefined, undefined, abort.signal);
234
- await settle();
235
-
236
- // 10 reconnects are allowed; the 11th failure must degrade instead of announcing 11/10.
237
- for (let i = 0; i < 11; i++) {
238
- await failCurrentConnection();
239
- }
240
-
241
- const announced = logs
242
- .map((line) => /\(attempt (\d+)\/(\d+)\)/.exec(line))
243
- .filter((match): match is RegExpExecArray => match !== null)
244
- .map((match) => ({ attempt: Number(match[1]), max: Number(match[2]) }));
245
-
246
- expect(announced).toHaveLength(10);
247
- expect(announced.map((entry) => entry.attempt)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
248
- expect(announced.every((entry) => entry.attempt <= entry.max)).toBe(true);
249
- expect(FakeEventSource.instances).toHaveLength(11);
250
- expect(warnings.some((line) => line.includes('SSE unavailable after 10 attempts'))).toBe(true);
251
-
252
- abort.abort();
253
- await settle();
254
- });
255
-
256
- it('polls GET /updates from the last received timestamp and emits the messages', async () => {
257
- const harness = createHarness();
258
- harness.setHistory([{ t: AgentMessageType.UPDATE, m: 'history', ts: 1000 }]);
259
- harness.setPollResponse((since) =>
260
- since < 2000 ? [{ t: AgentMessageType.UPDATE, m: 'polled', ts: 2000 }] : [],
261
- );
262
-
263
- const received: AgentMessage[] = [];
264
- const abort = new AbortController();
265
- void harness.client.agents.streamMessages(
266
- AGENT_RUN_ID,
267
- (message) => received.push(message),
268
- undefined,
269
- abort.signal,
270
- );
271
- await settle();
272
-
273
- for (let i = 0; i < 11; i++) {
274
- await failCurrentConnection();
275
- }
276
- await settle(6000);
277
-
278
- // The first poll resumes at the history cursor, later polls at the last polled message.
279
- expect(harness.pollSince[0]).toBe(1000);
280
- expect(harness.pollSince[1]).toBe(2000);
281
- expect(received.map((message) => message.message)).toEqual(['history', 'polled']);
282
- // Polled messages reach the caller as fully-formed AgentMessages, like the SSE path.
283
- expect(received[1]).toMatchObject({
284
- type: AgentMessageType.UPDATE,
285
- workflow_run_id: AGENT_RUN_ID,
286
- workstream_id: 'main',
287
- timestamp: 2000,
288
- });
289
-
290
- abort.abort();
291
- await settle();
292
- });
293
-
294
- it('warns on every polling failure instead of failing silently', async () => {
295
- const harness = createHarness();
296
- harness.setHistory([{ t: AgentMessageType.UPDATE, m: 'history', ts: 1000 }]);
297
- harness.setPollResponse(() => {
298
- throw new Error('boom');
299
- });
300
-
301
- const abort = new AbortController();
302
- void harness.client.agents.streamMessages(AGENT_RUN_ID, undefined, undefined, abort.signal);
303
- await settle();
304
-
305
- for (let i = 0; i < 11; i++) {
306
- await failCurrentConnection();
307
- }
308
- await settle(6000);
309
-
310
- const pollWarnings = warnings.filter((line) => line.includes('GET /updates failed while polling'));
311
- expect(pollWarnings.length).toBeGreaterThanOrEqual(2);
312
- expect(pollWarnings[0]).toContain('consecutive failures: 1');
313
- expect(pollWarnings[1]).toContain('consecutive failures: 2');
314
-
315
- abort.abort();
316
- await settle();
317
- });
318
-
319
- it('retries SSE after the polling fallback so an outage is not permanent', async () => {
320
- const harness = createHarness();
321
- const abort = new AbortController();
322
- void harness.client.agents.streamMessages(AGENT_RUN_ID, undefined, undefined, abort.signal);
323
- await settle();
324
-
325
- for (let i = 0; i < 11; i++) {
326
- await failCurrentConnection();
327
- }
328
- const connectionsBeforeRetry = FakeEventSource.instances.length;
329
- expect(warnings.some((line) => line.includes('SSE retried every 60s'))).toBe(true);
330
-
331
- await settle(61000);
332
-
333
- expect(FakeEventSource.instances.length).toBe(connectionsBeforeRetry + 1);
334
- expect(logs.some((line) => line.includes('retrying the SSE connection'))).toBe(true);
335
-
336
- abort.abort();
337
- await settle();
338
- });
339
-
340
- it('closes the stream when polling observes a terminal run status', async () => {
341
- const harness = createHarness();
342
- const abort = new AbortController();
343
- const done = harness.client.agents.streamMessages(AGENT_RUN_ID, undefined, undefined, abort.signal);
344
- await settle();
345
-
346
- for (let i = 0; i < 11; i++) {
347
- await failCurrentConnection();
348
- }
349
- harness.setRunStatus('completed');
350
- await settle(6000);
351
-
352
- await expect(done).resolves.toBeNull();
353
- });
354
- });
@@ -1,42 +0,0 @@
1
- import type { ImportDataPayload } from '@vertesia/common';
2
- import { describe, expect, it } from 'vitest';
3
- import { ZenoClient } from './client.js';
4
-
5
- describe('DataApi', () => {
6
- // `import` is the name applications built against the 1.4 SDK call, through the client the
7
- // platform serves them rather than one they bundle -- so renaming it (as 1.5 briefly did, to
8
- // `importData`) breaks them at deploy time rather than at their next upgrade. This pins both
9
- // the name and the request it issues.
10
- it('exposes `import` as POST {store}/import with the data store header', async () => {
11
- const requests: { url: string; method: string; storeHeader: string | null; body: string }[] = [];
12
- const fetchImport = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
13
- const request = new Request(input, init);
14
- requests.push({
15
- url: request.url,
16
- method: request.method,
17
- storeHeader: request.headers.get('x-data-store-id'),
18
- body: await request.text(),
19
- });
20
- return new Response(JSON.stringify({ id: 'import-1', status: 'completed' }), {
21
- status: 200,
22
- headers: { 'content-type': 'application/json' },
23
- });
24
- };
25
-
26
- const client = new ZenoClient({ serverUrl: 'https://store.test', apikey: 'token', fetch: fetchImport });
27
- const payload: ImportDataPayload = {
28
- mode: 'append',
29
- message: 'test import',
30
- tables: { customers: { source: 'inline', data: [{ id: 1 }] } },
31
- };
32
-
33
- const job = await client.data.import('store-1', payload);
34
-
35
- expect(job).toEqual({ id: 'import-1', status: 'completed' });
36
- expect(requests).toHaveLength(1);
37
- expect(requests[0].method).toBe('POST');
38
- expect(requests[0].url).toBe('https://store.test/api/v1/data/store-1/import');
39
- expect(requests[0].storeHeader).toBe('store-1');
40
- expect(JSON.parse(requests[0].body)).toEqual(payload);
41
- });
42
- });
@@ -1,31 +0,0 @@
1
- import { SupportedEmbeddingTypes } from '@vertesia/common';
2
- import { describe, expect, it, vi } from 'vitest';
3
- import { ZenoClient } from './client.js';
4
-
5
- describe('EmbeddingsApi', () => {
6
- it('omits the mode by default and sends the explicit sync override when requested', async () => {
7
- const requests: Request[] = [];
8
- const client = new ZenoClient({
9
- serverUrl: 'https://store.example.com',
10
- apikey: 'token',
11
- fetch: vi.fn(
12
- async () =>
13
- new Response(JSON.stringify({ status: 'success', message: 'started' }), {
14
- status: 200,
15
- headers: { 'content-type': 'application/json' },
16
- }),
17
- ),
18
- onRequest: (request) => requests.push(request),
19
- });
20
-
21
- await client.embeddings.recalculate(SupportedEmbeddingTypes.text);
22
- await client.embeddings.recalculate(SupportedEmbeddingTypes.text, { mode: 'sync' });
23
- await client.embeddings.recalculate(SupportedEmbeddingTypes.image, { force: true });
24
-
25
- expect(requests.map(({ url }) => url)).toEqual([
26
- 'https://store.example.com/api/v1/embeddings/text/recalculate',
27
- 'https://store.example.com/api/v1/embeddings/text/recalculate?mode=sync',
28
- 'https://store.example.com/api/v1/embeddings/image/recalculate?force=true',
29
- ]);
30
- });
31
- });
@@ -1,131 +0,0 @@
1
- import type { EventDeliveryStreamSnapshot } from '@vertesia/common';
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { VertesiaClient } from '../client.js';
4
-
5
- type Listener = (event: MessageEvent) => void;
6
-
7
- class FakeEventSource {
8
- static readonly CONNECTING = 0;
9
- static readonly OPEN = 1;
10
- static readonly CLOSED = 2;
11
-
12
- static instances: FakeEventSource[] = [];
13
- static urls: string[] = [];
14
-
15
- readonly url: string;
16
- readonly withCredentials = false;
17
- readyState = FakeEventSource.CONNECTING;
18
- onopen: ((event: Event) => void) | null = null;
19
- onmessage: ((event: MessageEvent) => void) | null = null;
20
- onerror: ((event: Event) => void) | null = null;
21
- private readonly listeners = new Map<string, Listener[]>();
22
-
23
- constructor(url: string) {
24
- this.url = url;
25
- FakeEventSource.urls.push(url);
26
- FakeEventSource.instances.push(this);
27
- }
28
-
29
- close() {
30
- this.readyState = FakeEventSource.CLOSED;
31
- }
32
-
33
- addEventListener(type: string, listener: Listener) {
34
- const listeners = this.listeners.get(type) ?? [];
35
- listeners.push(listener);
36
- this.listeners.set(type, listeners);
37
- }
38
-
39
- removeEventListener() {}
40
-
41
- dispatchEvent() {
42
- return true;
43
- }
44
-
45
- emit(type: string, data: unknown, lastEventId = '') {
46
- const event = { data: JSON.stringify(data), lastEventId } as MessageEvent;
47
- for (const listener of this.listeners.get(type) ?? []) {
48
- listener(event);
49
- }
50
- if (type === 'message') {
51
- this.onmessage?.(event);
52
- }
53
- }
54
- }
55
-
56
- function createClient(): VertesiaClient {
57
- const client = new VertesiaClient({
58
- serverUrl: 'https://studio.example.test',
59
- storeUrl: 'https://store.example.test',
60
- });
61
- client.withAuthCallback(async () => 'Bearer test-token');
62
- return client;
63
- }
64
-
65
- describe('EventsApi.subscribeDeliveries', () => {
66
- beforeEach(() => {
67
- FakeEventSource.instances = [];
68
- FakeEventSource.urls = [];
69
- vi.stubGlobal('EventSource', FakeEventSource);
70
- });
71
-
72
- afterEach(() => {
73
- vi.unstubAllGlobals();
74
- vi.restoreAllMocks();
75
- });
76
-
77
- it('serializes stream filters and auth token into the EventSource URL', async () => {
78
- const client = createClient();
79
- const subscription = client.events.subscribeDeliveries({
80
- resource_id: 'agentic-coworker-platform',
81
- resource_type: ['app', 'workflow_run'],
82
- event_category: ['workflow', 'system'],
83
- action: ['create', 'workflow_completed'],
84
- outbox_status: ['failed'],
85
- include_event: true,
86
- poll_interval_ms: 1500,
87
- on_envelope: () => {},
88
- });
89
-
90
- await vi.waitFor(() => expect(FakeEventSource.urls).toHaveLength(1));
91
- const url = new URL(FakeEventSource.urls[0]);
92
-
93
- expect(url.href).toContain('https://store.example.test/api/v1/events/stream');
94
- expect(url.searchParams.get('access_token')).toBe('test-token');
95
- expect(url.searchParams.get('resource_id')).toBe('agentic-coworker-platform');
96
- expect(url.searchParams.get('resource_type')).toBe('app,workflow_run');
97
- expect(url.searchParams.get('event_category')).toBe('workflow,system');
98
- expect(url.searchParams.get('action')).toBe('create,workflow_completed');
99
- expect(url.searchParams.get('outbox_status')).toBe('failed');
100
- expect(url.searchParams.get('include_event')).toBe('true');
101
- expect(url.searchParams.get('poll_interval_ms')).toBe('1500');
102
-
103
- subscription.close();
104
- });
105
-
106
- it('dispatches typed snapshot envelopes to the snapshot callback', async () => {
107
- const client = createClient();
108
- const snapshots: EventDeliveryStreamSnapshot[] = [];
109
- const subscription = client.events.subscribeDeliveries({
110
- resource_id: 'demo-app',
111
- on_snapshot: (_items, envelope) => snapshots.push(envelope),
112
- });
113
-
114
- await vi.waitFor(() => expect(FakeEventSource.instances).toHaveLength(1));
115
- FakeEventSource.instances[0].emit(
116
- 'snapshot',
117
- {
118
- type: 'snapshot',
119
- emitted_at: '2026-06-27T00:00:00.000Z',
120
- cursor: 'evt_1',
121
- deliveries: [],
122
- } satisfies EventDeliveryStreamSnapshot,
123
- 'evt_1',
124
- );
125
-
126
- expect(snapshots).toHaveLength(1);
127
- expect(snapshots[0].cursor).toBe('evt_1');
128
-
129
- subscription.close();
130
- });
131
- });
@@ -1,105 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { ZenoClient } from './client.js';
3
-
4
- describe('FilesApi', () => {
5
- afterEach(() => {
6
- vi.restoreAllMocks();
7
- vi.unstubAllGlobals();
8
- });
9
-
10
- it('retries signed upload URL creation after a transient connection failure', async () => {
11
- let attempts = 0;
12
- const fetchSignedUploadUrl = async (): Promise<Response> => {
13
- attempts++;
14
- if (attempts === 1) {
15
- throw new TypeError('fetch failed');
16
- }
17
- return new Response(
18
- JSON.stringify({ id: 'file-1', path: 'agents/run/conversation.json', url: 'https://signed' }),
19
- {
20
- status: 200,
21
- headers: { 'content-type': 'application/json' },
22
- },
23
- );
24
- };
25
-
26
- const client = new ZenoClient({
27
- serverUrl: 'https://store.test',
28
- apikey: 'token',
29
- fetch: fetchSignedUploadUrl,
30
- });
31
-
32
- const result = await client.files.getUploadUrl({
33
- name: 'conversation.json',
34
- id: 'agents/run/conversation.json',
35
- });
36
-
37
- expect(result).toEqual({ id: 'file-1', path: 'agents/run/conversation.json', url: 'https://signed' });
38
- expect(attempts).toBe(2);
39
- });
40
-
41
- it('retries signed upload URL creation after a transient 503', async () => {
42
- let attempts = 0;
43
- const fetchSignedUploadUrl = async (): Promise<Response> => {
44
- attempts++;
45
- if (attempts === 1) {
46
- return new Response(JSON.stringify({ message: 'try again' }), {
47
- status: 503,
48
- headers: { 'content-type': 'application/json' },
49
- });
50
- }
51
- return new Response(
52
- JSON.stringify({ id: 'file-1', path: 'agents/run/conversation.json', url: 'https://signed' }),
53
- {
54
- status: 200,
55
- headers: { 'content-type': 'application/json' },
56
- },
57
- );
58
- };
59
-
60
- const client = new ZenoClient({
61
- serverUrl: 'https://store.test',
62
- apikey: 'token',
63
- fetch: fetchSignedUploadUrl,
64
- });
65
-
66
- const result = await client.files.getUploadUrl({
67
- name: 'conversation.json',
68
- id: 'agents/run/conversation.json',
69
- });
70
-
71
- expect(result).toEqual({ id: 'file-1', path: 'agents/run/conversation.json', url: 'https://signed' });
72
- expect(attempts).toBe(2);
73
- });
74
-
75
- it('does not expose a signed URL when a file download returns 404', async () => {
76
- const signedUrl =
77
- 'https://storage.googleapis.com/test-bucket/agents/run/files/missing.png?X-Goog-Credential=secret&X-Goog-Signature=do-not-log';
78
- const fetchDownloadUrl = (): Promise<Response> =>
79
- Promise.resolve(
80
- new Response(JSON.stringify({ url: signedUrl }), {
81
- status: 200,
82
- headers: { 'content-type': 'application/json' },
83
- }),
84
- );
85
- vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(null, { status: 404, statusText: 'Not Found' })));
86
- const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
87
- const client = new ZenoClient({
88
- serverUrl: 'https://store.test',
89
- apikey: 'token',
90
- fetch: fetchDownloadUrl,
91
- });
92
-
93
- const error = await client.files.downloadFile('agents/run/files/missing.png').catch((cause: unknown) => cause);
94
-
95
- expect(error).toMatchObject({
96
- name: 'FileDownloadError',
97
- status: 404,
98
- message: 'File download not found: agents/run/files/missing.png',
99
- });
100
- expect(String(error)).not.toContain('X-Goog-Credential');
101
- expect(String(error)).not.toContain('X-Goog-Signature');
102
- expect(String(error)).not.toContain('do-not-log');
103
- expect(consoleError).not.toHaveBeenCalled();
104
- });
105
- });