@vertesia/client 1.5.0-dev.20260714.072725Z → 1.5.0-dev.20260722.120446Z

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 (87) hide show
  1. package/lib/AppsApi.d.ts +89 -5
  2. package/lib/AppsApi.d.ts.map +1 -1
  3. package/lib/AppsApi.js +153 -4
  4. package/lib/AppsApi.js.map +1 -1
  5. package/lib/AuditTrailApi.d.ts +2 -1
  6. package/lib/AuditTrailApi.d.ts.map +1 -1
  7. package/lib/AuditTrailApi.js +3 -0
  8. package/lib/AuditTrailApi.js.map +1 -1
  9. package/lib/ProjectsApi.d.ts +3 -1
  10. package/lib/ProjectsApi.d.ts.map +1 -1
  11. package/lib/ProjectsApi.js +16 -8
  12. package/lib/ProjectsApi.js.map +1 -1
  13. package/lib/RunsApi.d.ts +8 -2
  14. package/lib/RunsApi.d.ts.map +1 -1
  15. package/lib/RunsApi.js +6 -2
  16. package/lib/RunsApi.js.map +1 -1
  17. package/lib/ViewsApi.d.ts +21 -0
  18. package/lib/ViewsApi.d.ts.map +1 -0
  19. package/lib/ViewsApi.js +41 -0
  20. package/lib/ViewsApi.js.map +1 -0
  21. package/lib/client.d.ts +10 -1
  22. package/lib/client.d.ts.map +1 -1
  23. package/lib/client.js +20 -0
  24. package/lib/client.js.map +1 -1
  25. package/lib/index.d.ts +2 -0
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +2 -0
  28. package/lib/index.js.map +1 -1
  29. package/lib/managed-sse.d.ts +27 -0
  30. package/lib/managed-sse.d.ts.map +1 -0
  31. package/lib/managed-sse.js +159 -0
  32. package/lib/managed-sse.js.map +1 -0
  33. package/lib/store/AgentsApi.d.ts.map +1 -1
  34. package/lib/store/AgentsApi.js +61 -6
  35. package/lib/store/AgentsApi.js.map +1 -1
  36. package/lib/store/AnalyzeDocApi.d.ts +9 -11
  37. package/lib/store/AnalyzeDocApi.d.ts.map +1 -1
  38. package/lib/store/AnalyzeDocApi.js +12 -36
  39. package/lib/store/AnalyzeDocApi.js.map +1 -1
  40. package/lib/store/EventsApi.d.ts +15 -1
  41. package/lib/store/EventsApi.d.ts.map +1 -1
  42. package/lib/store/EventsApi.js +57 -0
  43. package/lib/store/EventsApi.js.map +1 -1
  44. package/lib/store/FilesApi.d.ts.map +1 -1
  45. package/lib/store/FilesApi.js +6 -0
  46. package/lib/store/FilesApi.js.map +1 -1
  47. package/lib/store/ViewsApi.d.ts +12 -0
  48. package/lib/store/ViewsApi.d.ts.map +1 -0
  49. package/lib/store/ViewsApi.js +17 -0
  50. package/lib/store/ViewsApi.js.map +1 -0
  51. package/lib/store/client.d.ts +5 -1
  52. package/lib/store/client.d.ts.map +1 -1
  53. package/lib/store/client.js +13 -0
  54. package/lib/store/client.js.map +1 -1
  55. package/lib/store/index.d.ts +2 -0
  56. package/lib/store/index.d.ts.map +1 -1
  57. package/lib/store/index.js +2 -0
  58. package/lib/store/index.js.map +1 -1
  59. package/lib/store/version.d.ts +1 -0
  60. package/lib/store/version.d.ts.map +1 -1
  61. package/lib/store/version.js +1 -0
  62. package/lib/store/version.js.map +1 -1
  63. package/lib/vertesia-client.js +10 -10
  64. package/lib/vertesia-client.js.map +1 -1
  65. package/package.json +8 -7
  66. package/src/AppsApi.test.ts +124 -0
  67. package/src/AppsApi.ts +205 -4
  68. package/src/AuditTrailApi.ts +10 -1
  69. package/src/ProjectsApi.test.ts +33 -0
  70. package/src/ProjectsApi.ts +19 -8
  71. package/src/RunsApi.ts +12 -2
  72. package/src/ViewsApi.test.ts +36 -0
  73. package/src/ViewsApi.ts +60 -0
  74. package/src/client.ts +21 -2
  75. package/src/index.ts +2 -0
  76. package/src/managed-sse.ts +205 -0
  77. package/src/store/AgentsApi.ts +57 -6
  78. package/src/store/AnalyzeDocApi.ts +16 -54
  79. package/src/store/EventsApi.test.ts +131 -0
  80. package/src/store/EventsApi.ts +80 -0
  81. package/src/store/FilesApi.test.ts +69 -0
  82. package/src/store/FilesApi.ts +8 -1
  83. package/src/store/ViewsApi.test.ts +33 -0
  84. package/src/store/ViewsApi.ts +20 -0
  85. package/src/store/client.ts +13 -1
  86. package/src/store/index.ts +2 -0
  87. package/src/store/version.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/client",
3
- "version": "1.5.0-dev.20260714.072725Z",
3
+ "version": "1.5.0-dev.20260722.120446Z",
4
4
  "type": "module",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -11,17 +11,17 @@
11
11
  ],
12
12
  "license": "Apache-2.0",
13
13
  "devDependencies": {
14
- "@types/node": "^24.13.2",
15
- "rolldown": "1.1.3",
14
+ "@types/node": "^24.13.3",
15
+ "rolldown": "1.1.5",
16
16
  "typescript": "^6.0.3",
17
- "vitest": "^4.1.9",
17
+ "vitest": "^4.1.10",
18
18
  "@vertesia/tsconfig": "0.1.0"
19
19
  },
20
20
  "dependencies": {
21
21
  "eventsource": "^4.1.0",
22
- "@llumiverse/common": "1.5.0-dev.20260713.114357Z",
23
- "@vertesia/api-fetch-client": "1.5.0-dev.20260714.072725Z",
24
- "@vertesia/common": "1.5.0-dev.20260714.072725Z"
22
+ "@llumiverse/common": "1.5.0-dev.20260722.112223Z",
23
+ "@vertesia/common": "1.5.0-dev.20260722.120446Z",
24
+ "@vertesia/api-fetch-client": "1.5.0-dev.20260722.120446Z"
25
25
  },
26
26
  "exports": {
27
27
  ".": {
@@ -48,6 +48,7 @@
48
48
  "ai",
49
49
  "typescript"
50
50
  ],
51
+ "gitHead": "203c30e5a8ddc583ac9f1acce0f5445eb38a9551",
51
52
  "scripts": {
52
53
  "lint": "biome lint src",
53
54
  "clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { VertesiaClient } from './client.js';
3
+
4
+ describe('AppsApi', () => {
5
+ it('starts an in-place version rebuild without a caller-controlled payload', async () => {
6
+ const requests: Request[] = [];
7
+ const fetchMock = vi.fn(async () =>
8
+ Response.json({
9
+ workflow_id: 'workflow-1',
10
+ run_id: 'run-1',
11
+ app_id: 'sample-app',
12
+ version_id: 'version-1',
13
+ rebuild_version_record_id: 'record-1',
14
+ }),
15
+ );
16
+ const client = new VertesiaClient({
17
+ serverUrl: 'https://studio.example.com',
18
+ storeUrl: 'https://zeno.example.com',
19
+ fetch: fetchMock,
20
+ onRequest: (request) => requests.push(request),
21
+ });
22
+
23
+ const result = await client.apps.rebuildVersion('record-1');
24
+
25
+ expect(result.version_id).toBe('version-1');
26
+ expect(requests[0]?.method).toBe('POST');
27
+ expect(requests[0]?.url).toBe('https://studio.example.com/api/v1/apps/versions/record-1/rebuild');
28
+ });
29
+
30
+ it('deletes a version through its dedicated endpoint', async () => {
31
+ const requests: Request[] = [];
32
+ const fetchMock = vi.fn(async () =>
33
+ Response.json({
34
+ id: 'record-1',
35
+ app_id: 'sample-app',
36
+ version_id: 'version-1',
37
+ deleted: true,
38
+ warnings: [],
39
+ }),
40
+ );
41
+ const client = new VertesiaClient({
42
+ serverUrl: 'https://studio.example.com',
43
+ storeUrl: 'https://zeno.example.com',
44
+ fetch: fetchMock,
45
+ onRequest: (request) => requests.push(request),
46
+ });
47
+
48
+ const result = await client.apps.deleteVersion('record-1');
49
+
50
+ expect(result.deleted).toBe(true);
51
+ expect(requests[0]?.method).toBe('DELETE');
52
+ expect(requests[0]?.url).toBe('https://studio.example.com/api/v1/apps/versions/record-1');
53
+ });
54
+
55
+ it('returns the original response bytes and content type', async () => {
56
+ const requests: Request[] = [];
57
+ const bytes = new Uint8Array([0, 255, 10, 42]);
58
+ const fetchMock = vi.fn(
59
+ async () =>
60
+ new Response(bytes, {
61
+ status: 200,
62
+ headers: { 'content-type': 'application/octet-stream' },
63
+ }),
64
+ );
65
+ const client = new VertesiaClient({
66
+ serverUrl: 'https://studio.example.com',
67
+ storeUrl: 'https://zeno.example.com',
68
+ fetch: fetchMock,
69
+ onRequest: (request) => requests.push(request),
70
+ });
71
+
72
+ const response = await client.apps.getRepoFile('sample-app', 'docs/file.bin', { ref: 'feature/demo' });
73
+
74
+ expect(response.headers.get('content-type')).toBe('application/octet-stream');
75
+ expect(new Uint8Array(await response.arrayBuffer())).toEqual(bytes);
76
+ expect(requests[0]?.url).toBe(
77
+ 'https://studio.example.com/api/v1/apps/sample-app/repo/file?path=docs%2Ffile.bin&ref=feature%2Fdemo',
78
+ );
79
+ });
80
+
81
+ it('uploads multiple documents as one multipart commit request', async () => {
82
+ const requests: Request[] = [];
83
+ const fetchMock = vi.fn(async () =>
84
+ Response.json({
85
+ ref: 'main',
86
+ previous_commit: '1'.repeat(40),
87
+ commit: '2'.repeat(40),
88
+ paths: ['docs/guide.md', 'docs/diagram.png'],
89
+ }),
90
+ );
91
+ const client = new VertesiaClient({
92
+ serverUrl: 'https://studio.example.com',
93
+ storeUrl: 'https://zeno.example.com',
94
+ fetch: fetchMock,
95
+ onRequest: (request) => requests.push(request),
96
+ });
97
+
98
+ const result = await client.apps.commitRepoDocuments(
99
+ 'sample-app',
100
+ [
101
+ { path: 'docs/guide.md', content: new Blob(['# Guide']), fileName: 'guide.md' },
102
+ { path: 'docs/diagram.png', content: new Blob([new Uint8Array([1, 2, 3])]), fileName: 'diagram.png' },
103
+ ],
104
+ { ref: 'main', expectedHead: '1'.repeat(40), message: 'Add docs' },
105
+ );
106
+
107
+ expect(result.commit).toBe('2'.repeat(40));
108
+ const request = requests[0];
109
+ expect(request?.url).toBe('https://studio.example.com/api/v1/apps/sample-app/repo/documents');
110
+ expect(request?.headers.get('content-type')).toContain('multipart/form-data; boundary=');
111
+ const form = await request?.formData();
112
+ expect(JSON.parse(String(form?.get('metadata')))).toEqual({
113
+ ref: 'main',
114
+ expected_head: '1'.repeat(40),
115
+ message: 'Add docs',
116
+ files: [
117
+ { field: 'file_0', path: 'docs/guide.md' },
118
+ { field: 'file_1', path: 'docs/diagram.png' },
119
+ ],
120
+ });
121
+ expect((form?.get('file_0') as File).name).toBe('guide.md');
122
+ expect((form?.get('file_1') as File).size).toBe(3);
123
+ });
124
+ });
package/src/AppsApi.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { ApiTopic, type ClientBase, type ServerError } from '@vertesia/api-fetch-client';
2
2
  import type {
3
+ AppBuildProgress,
4
+ AppDeleteSummary,
5
+ AppInspectionResult,
3
6
  AppInstallation,
4
7
  AppInstallationKind,
5
8
  AppInstallationListEntry,
@@ -7,11 +10,27 @@ import type {
7
10
  AppInstallationWithManifest,
8
11
  AppManifest,
9
12
  AppManifestData,
13
+ AppPackage,
14
+ AppPackageScope,
15
+ AppRepoCommits,
16
+ AppRepoDocumentCommit,
17
+ AppRepoRefs,
18
+ AppRepoTree,
19
+ AppScaffoldProgress,
10
20
  AppToolCollection,
21
+ AppVersionListQuery,
22
+ AppVersionRecord,
11
23
  CountResult,
24
+ DeleteAppVersionResponse,
12
25
  ProjectRef,
26
+ PromoteAppVersionResponse,
13
27
  RequireAtLeastOne,
28
+ StartAppBuildRequest,
29
+ StartAppBuildResponse,
30
+ StartAppScaffoldRequest,
31
+ StartAppScaffoldResponse,
14
32
  UpdateAppInstallationToolAllowlistPayload,
33
+ UpsertAppVersionRequest,
15
34
  ValidateUrlRequest,
16
35
  ValidateUrlResponse,
17
36
  } from '@vertesia/common';
@@ -34,14 +53,71 @@ export default class AppsApi extends ApiTopic {
34
53
  }
35
54
 
36
55
  /**
37
- * Delete an app manifest by id. The owning account must permit the operation.
38
- * Note: also uninstall any existing installations of this manifest before deleting,
39
- * otherwise the server may refuse or leave orphaned installation records.
56
+ * Preview what the cascade delete would remove. Calls DELETE without confirm,
57
+ * which the server treats as a dry-run and returns counts + paths.
40
58
  */
41
- delete(id: string): Promise<CountResult> {
59
+ previewDelete(id: string): Promise<AppDeleteSummary> {
42
60
  return this.del(`/${id}`);
43
61
  }
44
62
 
63
+ /**
64
+ * Cascade-delete an app and everything attached to it (versions,
65
+ * installations + ACEs, git repo on the app-git server). Pass through
66
+ * the dry-run summary; confirm flag is required server-side.
67
+ */
68
+ deleteApp(id: string): Promise<AppDeleteSummary> {
69
+ return this.del(`/${id}`, { query: { confirm: 'true' } });
70
+ }
71
+
72
+ listVersions(query?: AppVersionListQuery): Promise<AppVersionRecord[]> {
73
+ return this.get('/versions', {
74
+ query: {
75
+ ...(query?.app_id && { app_id: query.app_id }),
76
+ ...(query?.kind && { kind: query.kind }),
77
+ ...(query?.include_expired !== undefined && { include_expired: query.include_expired }),
78
+ ...(query?.limit !== undefined && { limit: query.limit }),
79
+ },
80
+ });
81
+ }
82
+
83
+ upsertVersion(payload: UpsertAppVersionRequest): Promise<AppVersionRecord> {
84
+ return this.post('/versions', { payload });
85
+ }
86
+
87
+ getVersion(recordId: string): Promise<AppVersionRecord> {
88
+ return this.get(`/versions/${recordId}`);
89
+ }
90
+
91
+ promoteVersion(recordId: string): Promise<PromoteAppVersionResponse> {
92
+ return this.post(`/versions/${recordId}/promote`);
93
+ }
94
+
95
+ rebuildVersion(recordId: string): Promise<StartAppBuildResponse> {
96
+ return this.post(`/versions/${recordId}/rebuild`);
97
+ }
98
+
99
+ deleteVersion(recordId: string): Promise<DeleteAppVersionResponse> {
100
+ return this.del(`/versions/${recordId}`);
101
+ }
102
+
103
+ startBuild(appIdOrRecordId: string, payload: StartAppBuildRequest): Promise<StartAppBuildResponse> {
104
+ return this.post(`/${encodeURIComponent(appIdOrRecordId)}/builds`, { payload });
105
+ }
106
+
107
+ getBuildProgress(appIdOrRecordId: string, workflowId: string, runId: string): Promise<AppBuildProgress> {
108
+ return this.get(
109
+ `/${encodeURIComponent(appIdOrRecordId)}/builds/${encodeURIComponent(workflowId)}/${encodeURIComponent(runId)}/progress`,
110
+ );
111
+ }
112
+
113
+ startScaffold(payload: StartAppScaffoldRequest): Promise<StartAppScaffoldResponse> {
114
+ return this.post('/scaffolds', { payload });
115
+ }
116
+
117
+ getScaffoldProgress(workflowId: string, runId: string): Promise<AppScaffoldProgress> {
118
+ return this.get(`/scaffolds/${encodeURIComponent(workflowId)}/${encodeURIComponent(runId)}/progress`);
119
+ }
120
+
45
121
  /**
46
122
  * Get the list if tools provided by the given app.
47
123
  * @param appId
@@ -51,6 +127,131 @@ export default class AppsApi extends ApiTopic {
51
127
  return this.get(`/installations/${appInstallId}/tools`);
52
128
  }
53
129
 
130
+ /**
131
+ * Inspect an app's registration: resolved manifest state, what the published
132
+ * package actually exposes per capability, endpoint reachability, install
133
+ * state, and diagnostics. Use this to verify what an app registers vs what it
134
+ * declares, instead of inferring it from failed object/import calls.
135
+ */
136
+ inspect(appIdOrName: string): Promise<AppInspectionResult> {
137
+ return this.get(`/${appIdOrName}/inspect`);
138
+ }
139
+
140
+ /**
141
+ * List files/directories under a prefix in the app's git repository (default
142
+ * branch unless `ref` is given). Read-only; reads live from the git server
143
+ * without a clone. Used by the Build > Design view to surface generated `docs/`.
144
+ */
145
+ getRepoTree(appIdOrName: string, options?: { prefix?: string; ref?: string }): Promise<AppRepoTree> {
146
+ return this.get(`/${encodeURIComponent(appIdOrName)}/repo/tree`, {
147
+ query: { prefix: options?.prefix, ref: options?.ref },
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Read the original bytes of a single file in the app's git repository (default
153
+ * branch unless `ref` is given). The caller decides whether to consume the
154
+ * successful response as text, a Blob, or an ArrayBuffer.
155
+ */
156
+ async getRepoFile(appIdOrName: string, path: string, options?: { ref?: string }): Promise<Response> {
157
+ const endpoint = `/${encodeURIComponent(appIdOrName)}/repo/file`;
158
+ const response = await this.get<Response>(endpoint, {
159
+ query: { path, ref: options?.ref },
160
+ reader: (rawResponse) => rawResponse,
161
+ });
162
+ if (!response.ok) {
163
+ const payload = await this.readJSONPayload(response);
164
+ const request = await this.createRequest(response.url || this.getUrl(endpoint), { method: 'GET' });
165
+ throw this.createServerError(request, response, payload);
166
+ }
167
+ return response;
168
+ }
169
+
170
+ /**
171
+ * Commit one or more files under the repository's allowed document roots.
172
+ * The Git service currently permits only `docs/`. All files are written in
173
+ * one commit and `expectedHead` prevents overwriting a concurrent push.
174
+ */
175
+ commitRepoDocuments(
176
+ appIdOrName: string,
177
+ files: Array<{ path: string; content: Blob; fileName?: string }>,
178
+ options: { ref: string; expectedHead: string; message?: string },
179
+ ): Promise<AppRepoDocumentCommit> {
180
+ const form = new FormData();
181
+ const metadata = {
182
+ ref: options.ref,
183
+ expected_head: options.expectedHead,
184
+ message: options.message || 'Upload documentation',
185
+ files: files.map((file, index) => ({ field: `file_${index}`, path: file.path })),
186
+ };
187
+ form.append('metadata', JSON.stringify(metadata));
188
+ files.forEach((file, index) => {
189
+ form.append(`file_${index}`, file.content, file.fileName || file.path.split('/').pop() || 'document');
190
+ });
191
+ return this.post(`/${encodeURIComponent(appIdOrName)}/repo/documents`, {
192
+ payload: form,
193
+ jsonPayload: false,
194
+ timeoutMs: 120_000,
195
+ });
196
+ }
197
+
198
+ /**
199
+ * List repository commits newest first, optionally filtered to commits that changed a file.
200
+ * History starts at the default branch unless a branch, tag, or commit SHA is supplied as `ref`.
201
+ */
202
+ getRepoCommits(
203
+ appIdOrName: string,
204
+ path?: string,
205
+ options?: { ref?: string; limit?: number; cursor?: string },
206
+ ): Promise<AppRepoCommits> {
207
+ return this.get(`/${encodeURIComponent(appIdOrName)}/repo/commits`, {
208
+ query: { path, ref: options?.ref, limit: options?.limit, cursor: options?.cursor },
209
+ });
210
+ }
211
+
212
+ /**
213
+ * List the branches and tags of the app's git repository, each resolved to its latest commit.
214
+ * Read-only; reads live from the git server without a clone. Used by the Source view to let a
215
+ * user pick a ref to build from.
216
+ */
217
+ getRepoRefs(appIdOrName: string): Promise<AppRepoRefs> {
218
+ return this.get(`/${encodeURIComponent(appIdOrName)}/repo/refs`);
219
+ }
220
+
221
+ /**
222
+ * Get the promoted package an app exposes, filtered by scope. App-owned (in-code) artifacts are
223
+ * package-resolved (no per-id route), so this is how a client reads the full definition — type
224
+ * schema, interaction prompt, process definition, dashboard spec — to visualize an exposed id.
225
+ */
226
+ getAppPackage(appIdOrName: string, scope: AppPackageScope | AppPackageScope[] = 'all'): Promise<AppPackage> {
227
+ return this.get(`/${encodeURIComponent(appIdOrName)}/package`, {
228
+ query: { scope: Array.isArray(scope) ? scope.join(',') : scope },
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Get package capabilities exposed by an app installation.
234
+ */
235
+ getAppInstallationPackage(
236
+ appInstallId: string,
237
+ scope: AppPackageScope | AppPackageScope[] = 'all',
238
+ ): Promise<AppPackage> {
239
+ return this.get(`/installations/${appInstallId}/package`, {
240
+ query: {
241
+ scope: Array.isArray(scope) ? scope.join(',') : scope,
242
+ },
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Fetch the always-on system tools package served by studio-server.
248
+ * Tools and skills (`learn_*`) are returned on separate fields so UIs can
249
+ * render them distinctly. URLs are already resolved per deployment.
250
+ */
251
+ getSystemToolsPackage(scope: string = 'tools'): Promise<AppPackage> {
252
+ return this.get('/studio-tools/package', { query: { scope } });
253
+ }
254
+
54
255
  /**
55
256
  * @param ids - ids to filter by
56
257
  * @returns the app manifests but without the agent.tool property which can be big.
@@ -1,5 +1,10 @@
1
1
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
- import type { AuditTrailQuery, AuditTrailResponse } from '@vertesia/common';
2
+ import type {
3
+ AuditAggregationQuery,
4
+ AuditAggregationResponse,
5
+ AuditTrailQuery,
6
+ AuditTrailResponse,
7
+ } from '@vertesia/common';
3
8
 
4
9
  export default class AuditTrailApi extends ApiTopic {
5
10
  constructor(parent: ClientBase) {
@@ -24,4 +29,8 @@ export default class AuditTrailApi extends ApiTopic {
24
29
  const qs = params.toString();
25
30
  return this.get(`/${qs ? `?${qs}` : ''}`);
26
31
  }
32
+
33
+ aggregate(query: AuditAggregationQuery): Promise<AuditAggregationResponse> {
34
+ return this.post('/aggregate', { payload: query });
35
+ }
27
36
  }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { VertesiaClient } from './client.js';
3
+
4
+ describe('ProjectsApi', () => {
5
+ it('encodes app View ids as one path segment', async () => {
6
+ const requests: Request[] = [];
7
+ const fetchMock = vi.fn(async () => {
8
+ return new Response(
9
+ JSON.stringify({
10
+ id: 'app:content:document-lib',
11
+ name: 'document-lib',
12
+ definition: { name: 'Document library' },
13
+ }),
14
+ {
15
+ status: 200,
16
+ headers: { 'content-type': 'application/json' },
17
+ },
18
+ );
19
+ });
20
+ const client = new VertesiaClient({
21
+ serverUrl: 'https://api.example.com',
22
+ storeUrl: 'https://api.example.com',
23
+ fetch: fetchMock,
24
+ onRequest: (request) => requests.push(request),
25
+ });
26
+
27
+ await client.projects.getAppView('project/with/slash', 'app:content:../../environments');
28
+
29
+ expect(requests[0]?.url).toBe(
30
+ 'https://api.example.com/api/v1/projects/project%2Fwith%2Fslash/app-views/app%3Acontent%3A..%2F..%2Fenvironments',
31
+ );
32
+ });
33
+ });
@@ -7,6 +7,7 @@ import type {
7
7
  ICreateProjectPayload,
8
8
  InCodeProcessDefinition,
9
9
  InCodeTypeDefinition,
10
+ InCodeViewDefinition,
10
11
  Project,
11
12
  ProjectConfiguration,
12
13
  ProjectIntegrationConfigRequest,
@@ -80,44 +81,54 @@ export default class ProjectsApi extends ApiTopic {
80
81
  }
81
82
 
82
83
  listAppContentTypes(projectId: string, tag?: string): Promise<InCodeTypeDefinition[]> {
83
- return this.get(`/${projectId}/app-types`, {
84
+ return this.get(`/${encodeURIComponent(projectId)}/app-types`, {
84
85
  query: { tag },
85
86
  });
86
87
  }
87
88
 
88
89
  getAppContentType(projectId: string, typeId: string): Promise<InCodeTypeDefinition> {
89
- return this.get(`/${projectId}/app-types/${typeId}`);
90
+ return this.get(`/${encodeURIComponent(projectId)}/app-types/${encodeURIComponent(typeId)}`);
90
91
  }
91
92
 
92
93
  listAppProcesses(projectId: string, tag?: string): Promise<InCodeProcessDefinition[]> {
93
- return this.get(`/${projectId}/app-processes`, {
94
+ return this.get(`/${encodeURIComponent(projectId)}/app-processes`, {
94
95
  query: { tag },
95
96
  });
96
97
  }
97
98
 
98
99
  getAppProcess(projectId: string, processId: string): Promise<InCodeProcessDefinition> {
99
- return this.get(`/${projectId}/app-processes/${processId}`);
100
+ return this.get(`/${encodeURIComponent(projectId)}/app-processes/${encodeURIComponent(processId)}`);
101
+ }
102
+
103
+ listAppViews(projectId: string, tag?: string): Promise<InCodeViewDefinition[]> {
104
+ return this.get(`/${encodeURIComponent(projectId)}/app-views`, {
105
+ query: { tag },
106
+ });
107
+ }
108
+
109
+ getAppView(projectId: string, viewId: string): Promise<InCodeViewDefinition> {
110
+ return this.get(`/${encodeURIComponent(projectId)}/app-views/${encodeURIComponent(viewId)}`);
100
111
  }
101
112
 
102
113
  listAppRenderingTemplates(projectId: string, tag?: string): Promise<RenderingTemplateDefinitionRef[]> {
103
- return this.get(`/${projectId}/app-templates`, {
114
+ return this.get(`/${encodeURIComponent(projectId)}/app-templates`, {
104
115
  query: { tag },
105
116
  });
106
117
  }
107
118
 
108
119
  getAppRenderingTemplate(projectId: string, templateUri: string): Promise<RenderingTemplateDefinition> {
109
- return this.get(`/${projectId}/app-templates/${templateUri}`);
120
+ return this.get(`/${encodeURIComponent(projectId)}/app-templates/${encodeURIComponent(templateUri)}`);
110
121
  }
111
122
 
112
123
  getCompositeAppConfiguration(projectId: string): Promise<CompositeAppConfig> {
113
- return this.get(`/${projectId}/composite-app`);
124
+ return this.get(`/${encodeURIComponent(projectId)}/composite-app`);
114
125
  }
115
126
 
116
127
  updateCompositeAppConfiguration(
117
128
  projectId: string,
118
129
  payload: CompositeAppConfigPayload,
119
130
  ): Promise<CompositeAppConfig> {
120
- return this.put(`/${projectId}/composite-app`, {
131
+ return this.put(`/${encodeURIComponent(projectId)}/composite-app`, {
121
132
  payload,
122
133
  });
123
134
  }
package/src/RunsApi.ts CHANGED
@@ -115,9 +115,14 @@ export class RunsApi extends ApiTopic {
115
115
  * @param payload
116
116
  * @returns
117
117
  */
118
- sendToolResults(payload: ToolResultsPayload): Promise<ExecutionResponse> {
118
+ sendToolResults(
119
+ payload: ToolResultsPayload,
120
+ options?: { timeoutMs?: number | false | null; signal?: AbortSignal },
121
+ ): Promise<ExecutionResponse> {
119
122
  return this.post(`/tool-results`, {
120
123
  payload,
124
+ timeoutMs: options?.timeoutMs,
125
+ signal: options?.signal,
121
126
  });
122
127
  }
123
128
 
@@ -126,9 +131,14 @@ export class RunsApi extends ApiTopic {
126
131
  * @param payload
127
132
  * @returns
128
133
  */
129
- sendUserMessage(payload: UserMessagePayload): Promise<ExecutionResponse> {
134
+ sendUserMessage(
135
+ payload: UserMessagePayload,
136
+ options?: { timeoutMs?: number | false | null; signal?: AbortSignal },
137
+ ): Promise<ExecutionResponse> {
130
138
  return this.post(`/user-message`, {
131
139
  payload,
140
+ timeoutMs: options?.timeoutMs,
141
+ signal: options?.signal,
132
142
  });
133
143
  }
134
144
 
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { VertesiaClient } from './client.js';
3
+
4
+ describe('ViewsApi', () => {
5
+ it('keeps CRUD on Studio and proxies execution to Zeno', async () => {
6
+ const requests: Request[] = [];
7
+ const fetchMock = vi.fn(async () => {
8
+ return new Response(JSON.stringify({}), {
9
+ status: 200,
10
+ headers: { 'content-type': 'application/json' },
11
+ });
12
+ });
13
+ const client = new VertesiaClient({
14
+ serverUrl: 'https://studio.example.com',
15
+ storeUrl: 'https://zeno.example.com',
16
+ fetch: fetchMock,
17
+ onRequest: (request) => requests.push(request),
18
+ });
19
+
20
+ await client.views.retrieve('document-library');
21
+ await client.views.execute('document-library');
22
+ await client.views.preview({
23
+ configuration: {
24
+ name: 'Document library',
25
+ description: 'Browse documents.',
26
+ scope: {},
27
+ },
28
+ });
29
+
30
+ expect(requests.map((request) => request.url)).toEqual([
31
+ 'https://studio.example.com/api/v1/views/document-library',
32
+ 'https://zeno.example.com/api/v1/view-executions/document-library/execute',
33
+ 'https://zeno.example.com/api/v1/view-executions/preview',
34
+ ]);
35
+ });
36
+ });
@@ -0,0 +1,60 @@
1
+ import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
+ import type {
3
+ CreateViewExperienceRequest,
4
+ DeleteByIdResult,
5
+ ExecuteViewRequest,
6
+ PreviewViewExperienceRequest,
7
+ UpdateViewExperienceRequest,
8
+ ViewExecutionResult,
9
+ ViewExperience,
10
+ ViewExperienceListQuery,
11
+ } from '@vertesia/common';
12
+ import type { StoreViewsApi } from './store/ViewsApi.js';
13
+
14
+ export default class ViewsApi extends ApiTopic {
15
+ constructor(
16
+ parent: ClientBase,
17
+ private readonly executionApi: StoreViewsApi,
18
+ ) {
19
+ super(parent, '/api/v1/views');
20
+ }
21
+
22
+ list(query: ViewExperienceListQuery = {}): Promise<ViewExperience[]> {
23
+ return this.get('/', {
24
+ query: {
25
+ limit: query.limit,
26
+ offset: query.offset,
27
+ },
28
+ });
29
+ }
30
+
31
+ create(payload: CreateViewExperienceRequest): Promise<ViewExperience> {
32
+ return this.post('/', { payload });
33
+ }
34
+
35
+ retrieve(id: string): Promise<ViewExperience> {
36
+ return this.get(`/${encodeURIComponent(id)}`);
37
+ }
38
+
39
+ update(id: string, payload: UpdateViewExperienceRequest): Promise<ViewExperience> {
40
+ return this.put(`/${encodeURIComponent(id)}`, { payload });
41
+ }
42
+
43
+ delete(id: string): Promise<DeleteByIdResult> {
44
+ return this.del(`/${encodeURIComponent(id)}`);
45
+ }
46
+
47
+ /**
48
+ * Execute a persisted or app-contributed View through the content service.
49
+ */
50
+ execute(id: string, payload: ExecuteViewRequest = {}): Promise<ViewExecutionResult> {
51
+ return this.executionApi.execute(id, payload);
52
+ }
53
+
54
+ /**
55
+ * Validate and execute an unsaved View through the content service.
56
+ */
57
+ preview(payload: PreviewViewExperienceRequest): Promise<ViewExecutionResult> {
58
+ return this.executionApi.preview(payload);
59
+ }
60
+ }