@vertesia/client 1.6.0-dev.20260918.090710Z → 1.6.0-dev.20260923.085325Z

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 (54) 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 +6 -1
  6. package/lib/EnvironmentsApi.d.ts.map +1 -1
  7. package/lib/EnvironmentsApi.js +15 -0
  8. package/lib/EnvironmentsApi.js.map +1 -1
  9. package/lib/InteractionBase.d.ts +138 -62
  10. package/lib/InteractionBase.d.ts.map +1 -1
  11. package/lib/InteractionOutput.d.ts +4 -0
  12. package/lib/InteractionOutput.d.ts.map +1 -1
  13. package/lib/InteractionOutput.js +13 -0
  14. package/lib/InteractionOutput.js.map +1 -1
  15. package/lib/InternalAccountsApi.d.ts +7 -0
  16. package/lib/InternalAccountsApi.d.ts.map +1 -0
  17. package/lib/InternalAccountsApi.js +10 -0
  18. package/lib/InternalAccountsApi.js.map +1 -0
  19. package/lib/OAuthServerApi.d.ts +2 -1
  20. package/lib/OAuthServerApi.d.ts.map +1 -1
  21. package/lib/OAuthServerApi.js +3 -0
  22. package/lib/OAuthServerApi.js.map +1 -1
  23. package/lib/client.d.ts +2 -0
  24. package/lib/client.d.ts.map +1 -1
  25. package/lib/client.js +3 -0
  26. package/lib/client.js.map +1 -1
  27. package/lib/index.d.ts +1 -0
  28. package/lib/index.d.ts.map +1 -1
  29. package/lib/index.js +1 -0
  30. package/lib/index.js.map +1 -1
  31. package/lib/store/AgentsApi.d.ts.map +1 -1
  32. package/lib/store/AgentsApi.js +15 -0
  33. package/lib/store/AgentsApi.js.map +1 -1
  34. package/lib/store/EmbeddingsApi.d.ts +8 -2
  35. package/lib/store/EmbeddingsApi.d.ts.map +1 -1
  36. package/lib/store/EmbeddingsApi.js +20 -2
  37. package/lib/store/EmbeddingsApi.js.map +1 -1
  38. package/lib/vertesia-client.js +4 -4
  39. package/lib/vertesia-client.js.map +1 -1
  40. package/package.json +7 -7
  41. package/src/AppsApi.ts +11 -0
  42. package/src/EnvironmentsApi.ts +28 -0
  43. package/src/InteractionOutput.test.ts +14 -0
  44. package/src/InteractionOutput.ts +15 -0
  45. package/src/InternalAccountsApi.ts +12 -0
  46. package/src/OAuthServerApi.ts +6 -0
  47. package/src/app-session.test.ts +23 -0
  48. package/src/client.ts +5 -0
  49. package/src/index.ts +1 -0
  50. package/src/store/AgentsApi.stream.test.ts +33 -1
  51. package/src/store/AgentsApi.ts +17 -0
  52. package/src/store/EmbeddingsApi.test.ts +31 -0
  53. package/src/store/EmbeddingsApi.ts +47 -2
  54. package/src/store/FilesApi.test.ts +13 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/client",
3
- "version": "1.6.0-dev.20260918.090710Z",
3
+ "version": "1.6.0-dev.20260923.085325Z",
4
4
  "type": "module",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -11,16 +11,16 @@
11
11
  ],
12
12
  "license": "Apache-2.0",
13
13
  "devDependencies": {
14
- "@types/node": "^24.13.3",
14
+ "@types/node": "^24.13.5",
15
15
  "@vertesia/tsconfig": "0.1.0",
16
16
  "rolldown": "1.2.8",
17
17
  "typescript": "7.0.2",
18
- "vitest": "5.0.0"
18
+ "vitest": "5.0.1"
19
19
  },
20
20
  "dependencies": {
21
- "@llumiverse/common": "1.6.0-dev.20260918.055633Z",
22
- "@vertesia/api-fetch-client": "1.6.0-dev.20260918.090710Z",
23
- "@vertesia/common": "1.6.0-dev.20260918.090710Z",
21
+ "@llumiverse/common": "1.6.0-dev.20260923.085100Z",
22
+ "@vertesia/api-fetch-client": "1.6.0-dev.20260923.085325Z",
23
+ "@vertesia/common": "1.6.0-dev.20260923.085325Z",
24
24
  "eventsource": "^5.1.1"
25
25
  },
26
26
  "exports": {
@@ -48,7 +48,7 @@
48
48
  "ai",
49
49
  "typescript"
50
50
  ],
51
- "gitHead": "10f35da1a210e1950f9c8b1368fea89328a23ea0",
51
+ "gitHead": "8732631fd8099bdcab4f5375da2309f3cff34e19",
52
52
  "scripts": {
53
53
  "lint": "biome lint src",
54
54
  "clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
package/src/AppsApi.ts CHANGED
@@ -39,6 +39,7 @@ import type {
39
39
  StartAppDevelopmentTaskRequest,
40
40
  StartAppScaffoldRequest,
41
41
  StartAppScaffoldResponse,
42
+ UpdateAppInstallationOAuthApprovalPayload,
42
43
  UpdateAppInstallationToolAllowlistPayload,
43
44
  UpdateAppPayload,
44
45
  UpsertAppVersionRequest,
@@ -429,6 +430,16 @@ export default class AppsApi extends ApiTopic {
429
430
  });
430
431
  }
431
432
 
433
+ /**
434
+ * Set project-admin approval for an installation's OAuth scopes.
435
+ * Pass an empty list to revoke approval.
436
+ */
437
+ updateOAuthApproval(installId: string, approved_scopes: string[]): Promise<AppInstallationWithManifest> {
438
+ return this.put(`/installations/${installId}/oauth-approval`, {
439
+ payload: { approved_scopes } satisfies UpdateAppInstallationOAuthApprovalPayload,
440
+ });
441
+ }
442
+
432
443
  /**
433
444
  * Update the tool allowlist for an app installation.
434
445
  * Pass null to remove all restrictions (all tools permitted).
@@ -1,6 +1,11 @@
1
1
  import type { AIModel, ModelSearchPayload } from '@llumiverse/common';
2
2
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
3
3
  import type {
4
+ EmbeddingBatchCapabilityRequest,
5
+ EmbeddingBatchCapabilityResponse,
6
+ EmbeddingBatchCreateRequest,
7
+ EmbeddingBatchJobRequest,
8
+ EmbeddingBatchJobResponse,
4
9
  EmbeddingsApiRequest,
5
10
  EmbeddingsApiResult,
6
11
  EnableEnvironmentModelPayload,
@@ -84,6 +89,29 @@ export default class EnvironmentsApi extends ApiTopic {
84
89
  });
85
90
  }
86
91
 
92
+ embeddingBatchCapability(
93
+ id: string,
94
+ payload: EmbeddingBatchCapabilityRequest,
95
+ ): Promise<EmbeddingBatchCapabilityResponse> {
96
+ return this.post(`/${id}/embedding-batches/capability`, { payload });
97
+ }
98
+
99
+ createEmbeddingBatch(id: string, payload: EmbeddingBatchCreateRequest): Promise<EmbeddingBatchJobResponse> {
100
+ return this.post(`/${id}/embedding-batches`, { payload });
101
+ }
102
+
103
+ getEmbeddingBatch(id: string, payload: EmbeddingBatchJobRequest): Promise<EmbeddingBatchJobResponse> {
104
+ return this.post(`/${id}/embedding-batches/get`, { payload });
105
+ }
106
+
107
+ cancelEmbeddingBatch(id: string, payload: EmbeddingBatchJobRequest): Promise<EmbeddingBatchJobResponse> {
108
+ return this.post(`/${id}/embedding-batches/cancel`, { payload });
109
+ }
110
+
111
+ deleteEmbeddingBatch(id: string, payload: EmbeddingBatchJobRequest): Promise<EmbeddingBatchJobResponse> {
112
+ return this.post(`/${id}/embedding-batches/delete`, { payload });
113
+ }
114
+
87
115
  /**
88
116
  * Batch update the LLM Environment for multiple Interactions, including the model used and the
89
117
  * Environment that they belong to.
@@ -3,6 +3,20 @@ import { describe, expect, it } from 'vitest';
3
3
  import { InteractionOutput, IS_INTERACTION_OUTPUT } from './InteractionOutput.js';
4
4
 
5
5
  describe('InteractionOutput', () => {
6
+ it('exposes audio metadata and URI without treating it as text or JSON', () => {
7
+ const audio = {
8
+ type: 'audio' as const,
9
+ value: 'gs://bucket/speech.mp3',
10
+ mime_type: 'audio/mpeg',
11
+ container: 'mp3',
12
+ };
13
+ const output = InteractionOutput.from([audio]);
14
+ expect(output.hasAudio()).toBe(true);
15
+ expect(output.audio()).toEqual(audio);
16
+ expect(output.audios()).toEqual([audio]);
17
+ expect(output.text()).toBe('');
18
+ });
19
+
6
20
  const sampleResults: CompletionResult[] = [
7
21
  { type: 'text', value: 'Hello, ' },
8
22
  { type: 'text', value: 'World!' },
@@ -217,6 +217,21 @@ export class InteractionOutput<T = unknown> {
217
217
  return this.results.filter((r) => r.type === 'image').map((r) => r.value);
218
218
  }
219
219
 
220
+ hasAudio(): boolean {
221
+ return this.results.some((r) => r.type === 'audio');
222
+ }
223
+
224
+ /** Get complete audio metadata and its durable file reference. */
225
+ audios() {
226
+ return this.results.filter((r) => r.type === 'audio');
227
+ }
228
+
229
+ audio() {
230
+ const result = this.audios()[0];
231
+ if (!result) throw new Error('No audio result found');
232
+ return result;
233
+ }
234
+
220
235
  /** Get the first video result URI. */
221
236
  video(): string {
222
237
  const videoResult = this.results.find((r) => r.type === 'video');
@@ -0,0 +1,12 @@
1
+ import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
+ import type { AccountApiVersionPolicy } from '@vertesia/common';
3
+
4
+ export default class InternalAccountsApi extends ApiTopic {
5
+ constructor(parent: ClientBase) {
6
+ super(parent, '/internal/accounts');
7
+ }
8
+
9
+ getApiVersionPolicy(accountId: string): Promise<AccountApiVersionPolicy> {
10
+ return this.get(`/${encodeURIComponent(accountId)}/api-version-policy`);
11
+ }
12
+ }
@@ -7,6 +7,8 @@ import type {
7
7
  OAuthDeviceAuthorizationRequest,
8
8
  OAuthDeviceAuthorizationResponse,
9
9
  OAuthGrantableScopesResponse,
10
+ OAuthLoginDecisionResponse,
11
+ OAuthLoginPayload,
10
12
  } from '@vertesia/common';
11
13
 
12
14
  export default class OAuthServerApi extends ClientBase {
@@ -20,6 +22,10 @@ export default class OAuthServerApi extends ClientBase {
20
22
  this.verboseErrors = parent.verboseErrors;
21
23
  }
22
24
 
25
+ login(requestId: string, payload: OAuthLoginPayload): Promise<OAuthLoginDecisionResponse> {
26
+ return this.post(`/requests/${encodeURIComponent(requestId)}/login`, { payload });
27
+ }
28
+
23
29
  get headers() {
24
30
  return this.parent.headers;
25
31
  }
@@ -0,0 +1,23 @@
1
+ import { expect, it, vi } from 'vitest';
2
+ import { VertesiaClient } from './client.js';
3
+
4
+ it('mints an app session through STS with the caller authorization and declared scopes', async () => {
5
+ const transport = vi.fn<typeof fetch>(async () =>
6
+ Response.json({ token: 'scoped', token_type: 'Bearer', expires_in: 900 }),
7
+ );
8
+ const client = new VertesiaClient({
9
+ serverUrl: 'https://api.example',
10
+ storeUrl: 'https://store.example',
11
+ tokenServerUrl: 'https://sts.example',
12
+ apikey: 'caller',
13
+ fetch: transport,
14
+ });
15
+ const result = await client.mintAppSessionToken({ app_name: 'analytics', scopes: ['content:read'] });
16
+ expect(result.token).toBe('scoped');
17
+ const [url, options] = transport.mock.calls[0];
18
+ const request = new Request(url, options);
19
+ expect(request.url).toBe('https://sts.example/token/app-session');
20
+ expect(request.method).toBe('POST');
21
+ expect(request.headers.get('authorization')).toBe('Bearer caller');
22
+ expect(await request.json()).toEqual({ app_name: 'analytics', scopes: ['content:read'] });
23
+ });
package/src/client.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AbstractFetchClient, type FETCH_FN, type IRequestRetryPolicy } from '@vertesia/api-fetch-client';
2
+ import type { AppSessionTokenRequest, AppSessionTokenResponse } from '@vertesia/common';
2
3
  import { APP_VERSION_HEADER, type AuthTokenPayload, type AuthTokenResponse } from '@vertesia/common';
3
4
  import AccountApi from './AccountApi.js';
4
5
  import AccountsApi from './AccountsApi.js';
@@ -412,6 +413,10 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
412
413
  *
413
414
  * @returns AuthTokenResponse
414
415
  */
416
+ async mintAppSessionToken(request: AppSessionTokenRequest): Promise<AppSessionTokenResponse> {
417
+ return this.post<AppSessionTokenResponse>(`${this.tokenServerUrl}/token/app-session`, { payload: request });
418
+ }
419
+
415
420
  async getAuthToken(token?: string): Promise<AuthTokenResponse> {
416
421
  // Route through the base client (absolute URL) so the call benefits from the
417
422
  // retry policy. The default retry methods exclude POST as non-idempotent, but
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export { default as DelegationGrantsApi } from './DelegationGrantsApi.js';
7
7
  export type { GroupsQueryOptions } from './GroupsApi.js';
8
8
  export * from './InteractionBase.js';
9
9
  export * from './InteractionOutput.js';
10
+ export { default as InternalAccountsApi } from './InternalAccountsApi.js';
10
11
  export { default as InternalSecretsApi } from './InternalSecretsApi.js';
11
12
  export * from './managed-sse.js';
12
13
  export { default as OAuthClientsApi } from './OAuthClientsApi.js';
@@ -72,7 +72,16 @@ function createHarness(): Harness {
72
72
  }
73
73
  const since = Number(url.searchParams.get('since') ?? 0);
74
74
  pollSince.push(since);
75
- return Response.json({ messages: pollResponse(since) });
75
+ try {
76
+ return Response.json({ messages: pollResponse(since) });
77
+ } catch (err) {
78
+ // A thrown error carrying `status` models an HTTP error response (e.g. 404).
79
+ const status = (err as { status?: unknown }).status;
80
+ if (typeof status === 'number') {
81
+ return Response.json({ error: (err as Error).message }, { status });
82
+ }
83
+ throw err;
84
+ }
76
85
  }
77
86
  if (url.pathname.endsWith(`/${AGENT_RUN_ID}`)) {
78
87
  return Response.json({ id: AGENT_RUN_ID, status: runStatus });
@@ -337,6 +346,29 @@ describe('AgentsApi.streamMessages reconnection', () => {
337
346
  await settle();
338
347
  });
339
348
 
349
+ it('stops the polling fallback when the run no longer exists (404)', async () => {
350
+ const harness = createHarness();
351
+ harness.setHistory([{ t: AgentMessageType.UPDATE, m: 'history', ts: 1000 }]);
352
+ harness.setPollResponse(() => {
353
+ throw Object.assign(new Error('Not Found: Agent run not found'), { status: 404 });
354
+ });
355
+
356
+ const abort = new AbortController();
357
+ const done = harness.client.agents.streamMessages(AGENT_RUN_ID, undefined, undefined, abort.signal);
358
+ await settle();
359
+
360
+ for (let i = 0; i < 11; i++) {
361
+ await failCurrentConnection();
362
+ }
363
+ await settle(6000);
364
+
365
+ await expect(done).resolves.toBeNull();
366
+ expect(warnings.some((line) => line.includes('the run no longer exists (404)'))).toBe(true);
367
+ // A single 404 ends the loop: no retry warning, no further poll after the exit.
368
+ expect(warnings.filter((line) => line.includes('GET /updates failed while polling'))).toEqual([]);
369
+ expect(harness.pollSince).toHaveLength(1);
370
+ });
371
+
340
372
  it('closes the stream when polling observes a terminal run status', async () => {
341
373
  const harness = createHarness();
342
374
  const abort = new AbortController();
@@ -435,6 +435,15 @@ export class AgentsApi extends ApiTopic {
435
435
  );
436
436
  };
437
437
 
438
+ // A 404 while polling means the run itself is gone (deleted, or never persisted): nothing
439
+ // will ever arrive, so stop instead of polling the missing resource every 5s forever.
440
+ const isRunGone = (error: unknown): boolean =>
441
+ typeof error === 'object' && error !== null && (error as { status?: unknown }).status === 404;
442
+ const exitBecauseRunGone = () => {
443
+ console.warn(`Agent stream ${id}: the run no longer exists (404); stopping the polling fallback.`);
444
+ exit(null);
445
+ };
446
+
438
447
  const pollTick = async () => {
439
448
  if (isClosed || !isPolling) return;
440
449
  let polledMessages = false;
@@ -456,6 +465,10 @@ export class AgentsApi extends ApiTopic {
456
465
  }
457
466
  }
458
467
  } catch (err) {
468
+ if (isRunGone(err)) {
469
+ exitBecauseRunGone();
470
+ return;
471
+ }
459
472
  warnPollFailure('GET /updates', err);
460
473
  }
461
474
  if (isClosed || !isPolling) return;
@@ -468,6 +481,10 @@ export class AgentsApi extends ApiTopic {
468
481
  }
469
482
  if (polledMessages) consecutivePollFailures = 0;
470
483
  } catch (err) {
484
+ if (isRunGone(err)) {
485
+ exitBecauseRunGone();
486
+ return;
487
+ }
471
488
  warnPollFailure('run status check', err);
472
489
  }
473
490
  if (isClosed || !isPolling) return;
@@ -0,0 +1,31 @@
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,8 +1,16 @@
1
1
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
2
  import type {
3
+ EmbeddingBatchApplyRequest,
4
+ EmbeddingBatchApplyResponse,
5
+ EmbeddingBatchPrepareRequest,
6
+ EmbeddingBatchPrepareResponse,
7
+ EmbeddingBatchRenditionPageRequest,
8
+ EmbeddingBatchRenditionPageResponse,
9
+ EmbeddingBatchUpdateRequest,
3
10
  EmbeddingsStatusResponse,
4
11
  GenericCommandResponse,
5
12
  ProjectConfigurationEmbeddingEnablePayload,
13
+ RecalculateEmbeddingsQuery,
6
14
  SupportedEmbeddingTypes,
7
15
  } from '@vertesia/common';
8
16
 
@@ -33,7 +41,44 @@ export class EmbeddingsApi extends ApiTopic {
33
41
  return this.post(`${type}/disable`);
34
42
  }
35
43
 
36
- async recalculate(type: SupportedEmbeddingTypes): Promise<GenericCommandResponse> {
37
- return this.post(`${type}/recalculate`);
44
+ async recalculate(
45
+ type: SupportedEmbeddingTypes,
46
+ query: RecalculateEmbeddingsQuery = {},
47
+ ): Promise<GenericCommandResponse> {
48
+ return this.post(`${type}/recalculate`, Object.keys(query).length ? { query } : undefined);
49
+ }
50
+
51
+ async prepareBatch(
52
+ payload: EmbeddingBatchPrepareRequest,
53
+ timeoutMs: number | false | null = false,
54
+ ): Promise<EmbeddingBatchPrepareResponse> {
55
+ return this.post('/batch/prepare', { payload, timeoutMs });
56
+ }
57
+
58
+ async updateBatch(payload: EmbeddingBatchUpdateRequest): Promise<GenericCommandResponse> {
59
+ return this.post('/batch/update', { payload });
60
+ }
61
+
62
+ async prepareBatchRenditionPage(
63
+ payload: EmbeddingBatchRenditionPageRequest,
64
+ ): Promise<EmbeddingBatchRenditionPageResponse> {
65
+ return this.post('/batch/renditions/prepare', { payload });
66
+ }
67
+
68
+ async pollBatchRenditionPage(
69
+ payload: EmbeddingBatchRenditionPageRequest,
70
+ ): Promise<EmbeddingBatchRenditionPageResponse> {
71
+ return this.post('/batch/renditions/status', { payload });
72
+ }
73
+
74
+ async cancelBatchRenditionPage(payload: EmbeddingBatchRenditionPageRequest): Promise<GenericCommandResponse> {
75
+ return this.post('/batch/renditions/cancel', { payload });
76
+ }
77
+
78
+ async applyBatch(
79
+ payload: EmbeddingBatchApplyRequest,
80
+ timeoutMs: number | false | null = false,
81
+ ): Promise<EmbeddingBatchApplyResponse> {
82
+ return this.post('/batch/apply', { payload, timeoutMs });
38
83
  }
39
84
  }
@@ -2,6 +2,19 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
  import { ZenoClient } from './client.js';
3
3
 
4
4
  describe('FilesApi', () => {
5
+ it('retrieves a durable audio result through the authenticated file API', async () => {
6
+ const bytes = new Uint8Array([82, 73, 70, 70, 1, 2, 3]);
7
+ const sign = vi.fn(async () => Response.json({ url: 'https://signed.example/speech.wav' }));
8
+ vi.stubGlobal(
9
+ 'fetch',
10
+ vi.fn(async () => new Response(bytes, { headers: { 'content-type': 'audio/wav' } })),
11
+ );
12
+ const client = new ZenoClient({ serverUrl: 'https://store.test', apikey: 'test-token', fetch: sign });
13
+ const stream = await client.files.downloadFile('gs://bucket/runs/run-1/media/speech.wav');
14
+ expect(new Uint8Array(await new Response(stream).arrayBuffer())).toEqual(bytes);
15
+ expect(sign).toHaveBeenCalledOnce();
16
+ });
17
+
5
18
  afterEach(() => {
6
19
  vi.restoreAllMocks();
7
20
  vi.unstubAllGlobals();