@vertesia/client 1.5.0-dev.20260717.131047Z → 1.5.0-dev.20260725.083715Z

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 (76) hide show
  1. package/lib/AppsApi.d.ts +41 -7
  2. package/lib/AppsApi.d.ts.map +1 -1
  3. package/lib/AppsApi.js +72 -7
  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/ViewsApi.d.ts +21 -0
  14. package/lib/ViewsApi.d.ts.map +1 -0
  15. package/lib/ViewsApi.js +41 -0
  16. package/lib/ViewsApi.js.map +1 -0
  17. package/lib/client.d.ts +3 -1
  18. package/lib/client.d.ts.map +1 -1
  19. package/lib/client.js +4 -1
  20. package/lib/client.js.map +1 -1
  21. package/lib/index.d.ts +1 -0
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/index.js +1 -0
  24. package/lib/index.js.map +1 -1
  25. package/lib/store/AgentsApi.d.ts +5 -1
  26. package/lib/store/AgentsApi.d.ts.map +1 -1
  27. package/lib/store/AgentsApi.js +8 -0
  28. package/lib/store/AgentsApi.js.map +1 -1
  29. package/lib/store/AnalyzeDocApi.d.ts +9 -11
  30. package/lib/store/AnalyzeDocApi.d.ts.map +1 -1
  31. package/lib/store/AnalyzeDocApi.js +12 -36
  32. package/lib/store/AnalyzeDocApi.js.map +1 -1
  33. package/lib/store/FilesApi.d.ts.map +1 -1
  34. package/lib/store/FilesApi.js +10 -4
  35. package/lib/store/FilesApi.js.map +1 -1
  36. package/lib/store/ObjectsApi.d.ts.map +1 -1
  37. package/lib/store/ObjectsApi.js +3 -2
  38. package/lib/store/ObjectsApi.js.map +1 -1
  39. package/lib/store/ViewsApi.d.ts +12 -0
  40. package/lib/store/ViewsApi.d.ts.map +1 -0
  41. package/lib/store/ViewsApi.js +17 -0
  42. package/lib/store/ViewsApi.js.map +1 -0
  43. package/lib/store/client.d.ts +2 -0
  44. package/lib/store/client.d.ts.map +1 -1
  45. package/lib/store/client.js +2 -0
  46. package/lib/store/client.js.map +1 -1
  47. package/lib/store/index.d.ts +1 -0
  48. package/lib/store/index.d.ts.map +1 -1
  49. package/lib/store/index.js +1 -0
  50. package/lib/store/index.js.map +1 -1
  51. package/lib/store/uploadMimeType.d.ts +3 -0
  52. package/lib/store/uploadMimeType.d.ts.map +1 -0
  53. package/lib/store/uploadMimeType.js +11 -0
  54. package/lib/store/uploadMimeType.js.map +1 -0
  55. package/lib/vertesia-client.js +5 -5
  56. package/lib/vertesia-client.js.map +1 -1
  57. package/package.json +8 -8
  58. package/src/AppsApi.test.ts +132 -0
  59. package/src/AppsApi.ts +100 -9
  60. package/src/AuditTrailApi.ts +10 -1
  61. package/src/ProjectsApi.test.ts +33 -0
  62. package/src/ProjectsApi.ts +19 -8
  63. package/src/ViewsApi.test.ts +36 -0
  64. package/src/ViewsApi.ts +60 -0
  65. package/src/client.ts +4 -1
  66. package/src/index.ts +1 -0
  67. package/src/store/AgentsApi.ts +17 -0
  68. package/src/store/AnalyzeDocApi.ts +16 -54
  69. package/src/store/FilesApi.ts +10 -4
  70. package/src/store/ObjectsApi.ts +3 -2
  71. package/src/store/ViewsApi.test.ts +33 -0
  72. package/src/store/ViewsApi.ts +20 -0
  73. package/src/store/client.ts +2 -0
  74. package/src/store/index.ts +1 -0
  75. package/src/store/uploadMimeType.test.ts +22 -0
  76. package/src/store/uploadMimeType.ts +16 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/client",
3
- "version": "1.5.0-dev.20260717.131047Z",
3
+ "version": "1.5.0-dev.20260725.083715Z",
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.4",
14
+ "@types/node": "^24.13.3",
15
+ "rolldown": "1.2.0",
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/common": "1.5.0-dev.20260717.131047Z",
24
- "@vertesia/api-fetch-client": "1.5.0-dev.20260717.131047Z"
22
+ "@llumiverse/common": "1.5.0-dev.20260725.065646Z",
23
+ "@vertesia/api-fetch-client": "1.5.0-dev.20260725.083715Z",
24
+ "@vertesia/common": "1.5.0-dev.20260725.083715Z"
25
25
  },
26
26
  "exports": {
27
27
  ".": {
@@ -48,7 +48,7 @@
48
48
  "ai",
49
49
  "typescript"
50
50
  ],
51
- "gitHead": "8aaad228827b2927db8e6aa75b27bfac90fec36f",
51
+ "gitHead": "57bcc2f27dd17b3387f27c18e89329c4c37f98ac",
52
52
  "scripts": {
53
53
  "lint": "biome lint src",
54
54
  "clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
@@ -0,0 +1,132 @@
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
+ if (!request) {
110
+ throw new Error('Expected a multipart document request');
111
+ }
112
+ expect(request.url).toBe('https://studio.example.com/api/v1/apps/sample-app/repo/documents');
113
+ expect(request.headers.get('content-type')).toContain('multipart/form-data; boundary=');
114
+ const form = await request.formData();
115
+ expect(JSON.parse(String(form.get('metadata')))).toEqual({
116
+ ref: 'main',
117
+ expected_head: '1'.repeat(40),
118
+ message: 'Add docs',
119
+ files: [
120
+ { field: 'file_0', path: 'docs/guide.md' },
121
+ { field: 'file_1', path: 'docs/diagram.png' },
122
+ ],
123
+ });
124
+ const guideFile = form.get('file_0');
125
+ const diagramFile = form.get('file_1');
126
+ if (!(guideFile instanceof File) || !(diagramFile instanceof File)) {
127
+ throw new Error('Expected multipart file entries');
128
+ }
129
+ expect(guideFile.name).toBe('guide.md');
130
+ expect(diagramFile.size).toBe(3);
131
+ });
132
+ });
package/src/AppsApi.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { ApiTopic, type ClientBase, type ServerError } from '@vertesia/api-fetch-client';
2
2
  import type {
3
- ActivateAppVersionResponse,
4
3
  AppBuildProgress,
5
4
  AppDeleteSummary,
5
+ AppDevelopmentTaskDetails,
6
+ AppDevelopmentTaskList,
6
7
  AppInspectionResult,
7
8
  AppInstallation,
8
9
  AppInstallationKind,
@@ -13,17 +14,25 @@ import type {
13
14
  AppManifestData,
14
15
  AppPackage,
15
16
  AppPackageScope,
16
- AppRepoFile,
17
+ AppRepoBranch,
18
+ AppRepoCommits,
19
+ AppRepoDocumentCommit,
17
20
  AppRepoRefs,
18
21
  AppRepoTree,
22
+ AppScaffoldProgress,
19
23
  AppToolCollection,
20
24
  AppVersionListQuery,
21
25
  AppVersionRecord,
22
26
  CountResult,
27
+ CreateAppRepoBranchRequest,
28
+ DeleteAppVersionResponse,
23
29
  ProjectRef,
30
+ PromoteAppVersionResponse,
24
31
  RequireAtLeastOne,
25
32
  StartAppBuildRequest,
26
33
  StartAppBuildResponse,
34
+ StartAppScaffoldRequest,
35
+ StartAppScaffoldResponse,
27
36
  UpdateAppInstallationToolAllowlistPayload,
28
37
  UpsertAppVersionRequest,
29
38
  ValidateUrlRequest,
@@ -83,8 +92,16 @@ export default class AppsApi extends ApiTopic {
83
92
  return this.get(`/versions/${recordId}`);
84
93
  }
85
94
 
86
- activateVersion(recordId: string): Promise<ActivateAppVersionResponse> {
87
- return this.post(`/versions/${recordId}/activate`);
95
+ promoteVersion(recordId: string): Promise<PromoteAppVersionResponse> {
96
+ return this.post(`/versions/${recordId}/promote`);
97
+ }
98
+
99
+ rebuildVersion(recordId: string): Promise<StartAppBuildResponse> {
100
+ return this.post(`/versions/${recordId}/rebuild`);
101
+ }
102
+
103
+ deleteVersion(recordId: string): Promise<DeleteAppVersionResponse> {
104
+ return this.del(`/versions/${recordId}`);
88
105
  }
89
106
 
90
107
  startBuild(appIdOrRecordId: string, payload: StartAppBuildRequest): Promise<StartAppBuildResponse> {
@@ -97,6 +114,14 @@ export default class AppsApi extends ApiTopic {
97
114
  );
98
115
  }
99
116
 
117
+ startScaffold(payload: StartAppScaffoldRequest): Promise<StartAppScaffoldResponse> {
118
+ return this.post('/scaffolds', { payload });
119
+ }
120
+
121
+ getScaffoldProgress(workflowId: string, runId: string): Promise<AppScaffoldProgress> {
122
+ return this.get(`/scaffolds/${encodeURIComponent(workflowId)}/${encodeURIComponent(runId)}/progress`);
123
+ }
124
+
100
125
  /**
101
126
  * Get the list if tools provided by the given app.
102
127
  * @param appId
@@ -128,12 +153,63 @@ export default class AppsApi extends ApiTopic {
128
153
  }
129
154
 
130
155
  /**
131
- * Read the UTF-8 content of a single file in the app's git repository (default
132
- * branch unless `ref` is given). Read-only; reads live from the git server.
156
+ * Read the original bytes of a single file in the app's git repository (default
157
+ * branch unless `ref` is given). The caller decides whether to consume the
158
+ * successful response as text, a Blob, or an ArrayBuffer.
133
159
  */
134
- getRepoFile(appIdOrName: string, path: string, options?: { ref?: string }): Promise<AppRepoFile> {
135
- return this.get(`/${encodeURIComponent(appIdOrName)}/repo/file`, {
160
+ async getRepoFile(appIdOrName: string, path: string, options?: { ref?: string }): Promise<Response> {
161
+ const endpoint = `/${encodeURIComponent(appIdOrName)}/repo/file`;
162
+ const response = await this.get<Response>(endpoint, {
136
163
  query: { path, ref: options?.ref },
164
+ reader: (rawResponse) => rawResponse,
165
+ });
166
+ if (!response.ok) {
167
+ const payload = await this.readJSONPayload(response);
168
+ const request = await this.createRequest(response.url || this.getUrl(endpoint), { method: 'GET' });
169
+ throw this.createServerError(request, response, payload);
170
+ }
171
+ return response;
172
+ }
173
+
174
+ /**
175
+ * Commit one or more files under the repository's allowed document roots.
176
+ * The Git service currently permits only `docs/`. All files are written in
177
+ * one commit and `expectedHead` prevents overwriting a concurrent push.
178
+ */
179
+ commitRepoDocuments(
180
+ appIdOrName: string,
181
+ files: Array<{ path: string; content: Blob; fileName?: string }>,
182
+ options: { ref: string; expectedHead: string; message?: string },
183
+ ): Promise<AppRepoDocumentCommit> {
184
+ const form = new FormData();
185
+ const metadata = {
186
+ ref: options.ref,
187
+ expected_head: options.expectedHead,
188
+ message: options.message || 'Upload documentation',
189
+ files: files.map((file, index) => ({ field: `file_${index}`, path: file.path })),
190
+ };
191
+ form.append('metadata', JSON.stringify(metadata));
192
+ files.forEach((file, index) => {
193
+ form.append(`file_${index}`, file.content, file.fileName || file.path.split('/').pop() || 'document');
194
+ });
195
+ return this.post(`/${encodeURIComponent(appIdOrName)}/repo/documents`, {
196
+ payload: form,
197
+ jsonPayload: false,
198
+ timeoutMs: 120_000,
199
+ });
200
+ }
201
+
202
+ /**
203
+ * List repository commits newest first, optionally filtered to commits that changed a file.
204
+ * History starts at the default branch unless a branch, tag, or commit SHA is supplied as `ref`.
205
+ */
206
+ getRepoCommits(
207
+ appIdOrName: string,
208
+ path?: string,
209
+ options?: { ref?: string; limit?: number; cursor?: string },
210
+ ): Promise<AppRepoCommits> {
211
+ return this.get(`/${encodeURIComponent(appIdOrName)}/repo/commits`, {
212
+ query: { path, ref: options?.ref, limit: options?.limit, cursor: options?.cursor },
137
213
  });
138
214
  }
139
215
 
@@ -146,8 +222,23 @@ export default class AppsApi extends ApiTopic {
146
222
  return this.get(`/${encodeURIComponent(appIdOrName)}/repo/refs`);
147
223
  }
148
224
 
225
+ /** List mutable development tasks represented by `agent/*` repository branches. */
226
+ listDevelopmentTasks(appIdOrName: string): Promise<AppDevelopmentTaskList> {
227
+ return this.get(`/${encodeURIComponent(appIdOrName)}/development-tasks`);
228
+ }
229
+
230
+ /** Get a development task and its latest parent Studio Assistant run, when started. */
231
+ getDevelopmentTask(appIdOrName: string, taskId: string): Promise<AppDevelopmentTaskDetails> {
232
+ return this.get(`/${encodeURIComponent(appIdOrName)}/development-tasks/${encodeURIComponent(taskId)}`);
233
+ }
234
+
235
+ /** Create a repository branch from an existing branch, tag, or commit. */
236
+ createRepoBranch(appIdOrName: string, payload: CreateAppRepoBranchRequest): Promise<AppRepoBranch> {
237
+ return this.post(`/${encodeURIComponent(appIdOrName)}/repo/branches`, { payload });
238
+ }
239
+
149
240
  /**
150
- * Get the published package an app exposes, filtered by scope. App-owned (in-code) artifacts are
241
+ * Get the promoted package an app exposes, filtered by scope. App-owned (in-code) artifacts are
151
242
  * package-resolved (no per-id route), so this is how a client reads the full definition — type
152
243
  * schema, interaction prompt, process definition, dashboard spec — to visualize an exposed id.
153
244
  */
@@ -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
  }
@@ -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
+ }
package/src/client.ts CHANGED
@@ -28,6 +28,7 @@ import { VERSION, VERSION_HEADER } from './store/version.js';
28
28
  import ToolsApi from './ToolsApi.js';
29
29
  import TrainingApi from './TrainingApi.js';
30
30
  import UsersApi from './UsersApi.js';
31
+ import ViewsApi from './ViewsApi.js';
31
32
 
32
33
  /**
33
34
  * 1 min threshold constant in ms
@@ -206,6 +207,7 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
206
207
  timeout: opts.timeout,
207
208
  fetch: opts.fetch,
208
209
  });
210
+ this.views = new ViewsApi(this, this.store.views);
209
211
 
210
212
  if (opts.retryPolicy) {
211
213
  this.withRetryPolicy(opts.retryPolicy);
@@ -243,7 +245,7 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
243
245
  /**
244
246
  * Pin the app version this client's requests resolve against. A generated app's UI sets this from
245
247
  * its build-time `VITE_APP_VERSION` so studio/zeno resolve its `app:<app>:…` refs against that
246
- * published version (candidate testing) instead of current. Pass null/empty to clear (→ current).
248
+ * exact built version instead of the promoted version. Pass null/empty to clear (→ promoted).
247
249
  */
248
250
  withAppVersion(version: string | null | undefined) {
249
251
  if (!version) {
@@ -425,6 +427,7 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
425
427
  apps = new AppsApi(this);
426
428
  tools = new ToolsApi(this);
427
429
  quota = new QuotaApi(this);
430
+ views: ViewsApi;
428
431
  }
429
432
 
430
433
  function isApiKey(apiKey: string) {
package/src/index.ts CHANGED
@@ -18,4 +18,5 @@ export type { ComputeRunFacetsResponse, FilterOption } from './RunsApi.js';
18
18
  export { default as SecretsApi } from './SecretsApi.js';
19
19
  export * from './StreamSource.js';
20
20
  export * from './store/index.js';
21
+ export { default as ViewsApi } from './ViewsApi.js';
21
22
  export type { AsyncExecutionResult };
@@ -1,6 +1,7 @@
1
1
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
2
  import {
3
3
  type ActiveWorkstreamsQueryResult,
4
+ type AgentArtifactContentResponse,
4
5
  type AgentArtifactUrlResponse,
5
6
  type AgentEvent,
6
7
  type AgentMessage,
@@ -41,6 +42,8 @@ import {
41
42
  type ToolParameterAnalyticsResponse,
42
43
  type TopPrincipalsAnalyticsResponse,
43
44
  toAgentMessage,
45
+ type UpdateAgentArtifactContentPayload,
46
+ type UpdateAgentArtifactContentResponse,
44
47
  type UpdateAgentRunStatusPayload,
45
48
  type WorkflowAnalyticsFilterOptionsResponse,
46
49
  type WorkflowAnalyticsSummaryQuery,
@@ -734,6 +737,20 @@ export class AgentsApi extends ApiTopic {
734
737
  return this.get(`/${id}/artifacts`, { query });
735
738
  }
736
739
 
740
+ /** Read a text artifact together with its conditional-write generation token. */
741
+ getArtifactContent(id: string, path: string): Promise<AgentArtifactContentResponse> {
742
+ return this.get(`/${id}/artifact-content/${path}`);
743
+ }
744
+
745
+ /** Conditionally replace the text content of an agent artifact. */
746
+ updateArtifactContent(
747
+ id: string,
748
+ path: string,
749
+ payload: UpdateAgentArtifactContentPayload,
750
+ ): Promise<UpdateAgentArtifactContentResponse> {
751
+ return this.put(`/${id}/artifact-content/${path}`, { payload });
752
+ }
753
+
737
754
  /**
738
755
  * Get a signed download URL for an artifact.
739
756
  */