@vertesia/client 1.5.0-dev.20260807.073259Z → 1.5.0-dev.20260901.005903Z

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 (124) hide show
  1. package/lib/AppsApi.d.ts +9 -8
  2. package/lib/AppsApi.d.ts.map +1 -1
  3. package/lib/AppsApi.js +11 -4
  4. package/lib/AppsApi.js.map +1 -1
  5. package/lib/GroupsApi.d.ts +4 -11
  6. package/lib/GroupsApi.d.ts.map +1 -1
  7. package/lib/GroupsApi.js.map +1 -1
  8. package/lib/InteractionBase.d.ts +114 -0
  9. package/lib/InteractionBase.d.ts.map +1 -1
  10. package/lib/InteractionCatalogApi.d.ts +3 -3
  11. package/lib/InteractionCatalogApi.d.ts.map +1 -1
  12. package/lib/InteractionCatalogApi.js +1 -1
  13. package/lib/InteractionOutput.d.ts +13 -3
  14. package/lib/InteractionOutput.d.ts.map +1 -1
  15. package/lib/InteractionOutput.js +33 -3
  16. package/lib/InteractionOutput.js.map +1 -1
  17. package/lib/OAuthServerApi.d.ts +1 -1
  18. package/lib/OAuthServerApi.d.ts.map +1 -1
  19. package/lib/OAuthServerApi.js +9 -2
  20. package/lib/OAuthServerApi.js.map +1 -1
  21. package/lib/ProjectsApi.d.ts +3 -3
  22. package/lib/ProjectsApi.d.ts.map +1 -1
  23. package/lib/ProjectsApi.js.map +1 -1
  24. package/lib/RunsApi.d.ts +6 -28
  25. package/lib/RunsApi.d.ts.map +1 -1
  26. package/lib/RunsApi.js +3 -3
  27. package/lib/RunsApi.js.map +1 -1
  28. package/lib/ToolsApi.d.ts +2 -1
  29. package/lib/ToolsApi.d.ts.map +1 -1
  30. package/lib/ToolsApi.js +9 -0
  31. package/lib/ToolsApi.js.map +1 -1
  32. package/lib/ViewsApi.d.ts +1 -1
  33. package/lib/ViewsApi.js +1 -1
  34. package/lib/client.d.ts +1 -0
  35. package/lib/client.d.ts.map +1 -1
  36. package/lib/client.js +3 -0
  37. package/lib/client.js.map +1 -1
  38. package/lib/execute.js +2 -2
  39. package/lib/execute.js.map +1 -1
  40. package/lib/store/AgentsApi.d.ts +4 -1
  41. package/lib/store/AgentsApi.d.ts.map +1 -1
  42. package/lib/store/AgentsApi.js +6 -4
  43. package/lib/store/AgentsApi.js.map +1 -1
  44. package/lib/store/CollectionsApi.d.ts +5 -2
  45. package/lib/store/CollectionsApi.d.ts.map +1 -1
  46. package/lib/store/CollectionsApi.js.map +1 -1
  47. package/lib/store/EmailApi.d.ts +9 -107
  48. package/lib/store/EmailApi.d.ts.map +1 -1
  49. package/lib/store/EmailApi.js.map +1 -1
  50. package/lib/store/FilesApi.d.ts +3 -1
  51. package/lib/store/FilesApi.d.ts.map +1 -1
  52. package/lib/store/FilesApi.js +7 -0
  53. package/lib/store/FilesApi.js.map +1 -1
  54. package/lib/store/ObjectsApi.d.ts +29 -4
  55. package/lib/store/ObjectsApi.d.ts.map +1 -1
  56. package/lib/store/ObjectsApi.js +0 -2
  57. package/lib/store/ObjectsApi.js.map +1 -1
  58. package/lib/store/PendingAsksApi.d.ts +2 -53
  59. package/lib/store/PendingAsksApi.d.ts.map +1 -1
  60. package/lib/store/PendingAsksApi.js.map +1 -1
  61. package/lib/store/ProcessApi.d.ts +15 -8
  62. package/lib/store/ProcessApi.d.ts.map +1 -1
  63. package/lib/store/ProcessApi.js +27 -1
  64. package/lib/store/ProcessApi.js.map +1 -1
  65. package/lib/store/ProcessTestRunApi.d.ts +26 -0
  66. package/lib/store/ProcessTestRunApi.d.ts.map +1 -0
  67. package/lib/store/ProcessTestRunApi.js +36 -0
  68. package/lib/store/ProcessTestRunApi.js.map +1 -0
  69. package/lib/store/QueryApi.d.ts +9 -51
  70. package/lib/store/QueryApi.d.ts.map +1 -1
  71. package/lib/store/QueryApi.js.map +1 -1
  72. package/lib/store/WorkflowsApi.d.ts +3 -3
  73. package/lib/store/WorkflowsApi.d.ts.map +1 -1
  74. package/lib/store/WorkflowsApi.js.map +1 -1
  75. package/lib/store/client.d.ts +2 -2
  76. package/lib/store/client.d.ts.map +1 -1
  77. package/lib/store/client.js +2 -2
  78. package/lib/store/client.js.map +1 -1
  79. package/lib/store/index.d.ts +1 -1
  80. package/lib/store/index.d.ts.map +1 -1
  81. package/lib/store/index.js +1 -1
  82. package/lib/store/index.js.map +1 -1
  83. package/lib/store/stream-termination.d.ts +2 -2
  84. package/lib/store/stream-termination.d.ts.map +1 -1
  85. package/lib/store/stream-termination.js +6 -4
  86. package/lib/store/stream-termination.js.map +1 -1
  87. package/lib/vertesia-client.js +8 -9
  88. package/lib/vertesia-client.js.map +1 -1
  89. package/package.json +7 -7
  90. package/src/AppsApi.ts +22 -8
  91. package/src/GroupsApi.ts +4 -10
  92. package/src/InteractionCatalogApi.ts +3 -3
  93. package/src/InteractionOutput.test.ts +41 -5
  94. package/src/InteractionOutput.ts +39 -3
  95. package/src/OAuthServerApi.test.ts +56 -0
  96. package/src/OAuthServerApi.ts +12 -2
  97. package/src/ProjectsApi.ts +4 -2
  98. package/src/RunsApi.ts +15 -14
  99. package/src/ToolsApi.ts +10 -0
  100. package/src/ViewsApi.test.ts +3 -1
  101. package/src/ViewsApi.ts +1 -1
  102. package/src/client.ts +4 -0
  103. package/src/execute.ts +2 -2
  104. package/src/store/AgentsApi.stream.test.ts +56 -0
  105. package/src/store/AgentsApi.ts +8 -4
  106. package/src/store/CollectionsApi.ts +8 -1
  107. package/src/store/EmailApi.ts +25 -107
  108. package/src/store/FilesApi.ts +12 -0
  109. package/src/store/ObjectsApi.ts +28 -4
  110. package/src/store/PendingAsksApi.ts +15 -57
  111. package/src/store/ProcessApi.test.ts +24 -0
  112. package/src/store/ProcessApi.ts +49 -8
  113. package/src/store/ProcessTestRunApi.ts +41 -0
  114. package/src/store/QueryApi.ts +9 -45
  115. package/src/store/WorkflowsApi.ts +4 -3
  116. package/src/store/client.ts +2 -2
  117. package/src/store/index.ts +1 -1
  118. package/src/store/stream-termination.test.ts +24 -0
  119. package/src/store/stream-termination.ts +10 -4
  120. package/lib/store/HiveMemoryApi.d.ts +0 -161
  121. package/lib/store/HiveMemoryApi.d.ts.map +0 -1
  122. package/lib/store/HiveMemoryApi.js +0 -170
  123. package/lib/store/HiveMemoryApi.js.map +0 -1
  124. package/src/store/HiveMemoryApi.ts +0 -220
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/client",
3
- "version": "1.5.0-dev.20260807.073259Z",
3
+ "version": "1.5.0-dev.20260901.005903Z",
4
4
  "type": "module",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -12,16 +12,16 @@
12
12
  "license": "Apache-2.0",
13
13
  "devDependencies": {
14
14
  "@types/node": "^24.13.3",
15
- "rolldown": "1.2.0",
15
+ "rolldown": "1.2.4",
16
16
  "typescript": "7.0.2",
17
17
  "vitest": "^4.1.10",
18
18
  "@vertesia/tsconfig": "0.1.0"
19
19
  },
20
20
  "dependencies": {
21
- "eventsource": "^4.1.0",
22
- "@vertesia/api-fetch-client": "1.5.0-dev.20260807.073259Z",
23
- "@vertesia/common": "1.5.0-dev.20260807.073259Z",
24
- "@llumiverse/common": "1.5.0-dev.20260807.072147Z"
21
+ "eventsource": "^5.0.0",
22
+ "@llumiverse/common": "1.5.0-dev.20260831.235912Z",
23
+ "@vertesia/common": "1.5.0-dev.20260901.005903Z",
24
+ "@vertesia/api-fetch-client": "1.5.0-dev.20260901.005903Z"
25
25
  },
26
26
  "exports": {
27
27
  ".": {
@@ -48,7 +48,7 @@
48
48
  "ai",
49
49
  "typescript"
50
50
  ],
51
- "gitHead": "e6ebc1482ad6c4df965d74c79f82eb25fc6e2ce3",
51
+ "gitHead": "73de8716696201bda20b5ec0d0134bafd597387e",
52
52
  "scripts": {
53
53
  "lint": "biome lint src",
54
54
  "clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
package/src/AppsApi.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ApiTopic, type ClientBase, type ServerError } from '@vertesia/api-fetch-client';
2
2
  import type {
3
+ AgentRunResponse,
3
4
  AppApiKeyCollectionParams,
4
5
  AppBuildProgress,
5
6
  AppDeleteSummary,
@@ -21,6 +22,7 @@ import type {
21
22
  AppRepoRefs,
22
23
  AppRepoTree,
23
24
  AppScaffoldProgress,
25
+ AppsQuery,
24
26
  AppToolCollection,
25
27
  AppVersionListQuery,
26
28
  AppVersionRecord,
@@ -34,17 +36,17 @@ import type {
34
36
  SetMcpApiKeyRequest,
35
37
  StartAppBuildRequest,
36
38
  StartAppBuildResponse,
39
+ StartAppDevelopmentTaskRequest,
37
40
  StartAppScaffoldRequest,
38
41
  StartAppScaffoldResponse,
39
42
  UpdateAppInstallationToolAllowlistPayload,
43
+ UpdateAppPayload,
40
44
  UpsertAppVersionRequest,
41
45
  ValidateUrlRequest,
42
46
  ValidateUrlResponse,
43
47
  } from '@vertesia/common';
44
48
 
45
- export interface OrphanedAppInstallation extends Omit<AppInstallation, 'manifest'> {
46
- manifest: null;
47
- }
49
+ export type { OrphanedAppInstallation } from '@vertesia/common';
48
50
 
49
51
  export default class AppsApi extends ApiTopic {
50
52
  constructor(parent: ClientBase) {
@@ -55,7 +57,7 @@ export default class AppsApi extends ApiTopic {
55
57
  return this.post('/', { payload: manifest });
56
58
  }
57
59
 
58
- update(id: string, manifest: AppManifestData): Promise<AppManifest> {
60
+ update(id: string, manifest: UpdateAppPayload): Promise<AppManifest> {
59
61
  return this.put(`/${id}`, { payload: manifest });
60
62
  }
61
63
 
@@ -251,11 +253,22 @@ export default class AppsApi extends ApiTopic {
251
253
  return this.get(`/${encodeURIComponent(appIdOrName)}/development-tasks`);
252
254
  }
253
255
 
254
- /** Get a development task and its latest parent Studio Assistant run, when started. */
256
+ /** Get a development task and its latest App Builder parent run, when started. */
255
257
  getDevelopmentTask(appIdOrName: string, taskId: string): Promise<AppDevelopmentTaskDetails> {
256
258
  return this.get(`/${encodeURIComponent(appIdOrName)}/development-tasks/${encodeURIComponent(taskId)}`);
257
259
  }
258
260
 
261
+ /** Start the policy-controlled App Builder parent on an existing development-task branch. */
262
+ startDevelopmentTask(
263
+ appIdOrName: string,
264
+ taskId: string,
265
+ payload: StartAppDevelopmentTaskRequest,
266
+ ): Promise<AgentRunResponse> {
267
+ return this.post(`/${encodeURIComponent(appIdOrName)}/development-tasks/${encodeURIComponent(taskId)}/runs`, {
268
+ payload,
269
+ });
270
+ }
271
+
259
272
  /** Create a repository branch from an existing branch, tag, or commit. */
260
273
  createRepoBranch(appIdOrName: string, payload: CreateAppRepoBranchRequest): Promise<AppRepoBranch> {
261
274
  return this.post(`/${encodeURIComponent(appIdOrName)}/repo/branches`, { payload });
@@ -296,11 +309,12 @@ export default class AppsApi extends ApiTopic {
296
309
  }
297
310
 
298
311
  /**
299
- * @param ids - ids to filter by
312
+ * @param query - pass `{ scope: 'project' }` to list only the apps installed in, or built in,
313
+ * the current project. Defaults to every app visible to the account, including the public catalog.
300
314
  * @returns the app manifests but without the agent.tool property which can be big.
301
315
  */
302
- list(): Promise<AppManifest[]> {
303
- return this.get('/');
316
+ list(query?: AppsQuery): Promise<AppManifest[]> {
317
+ return this.get('/', { query: { ...(query?.scope && { scope: query.scope }) } });
304
318
  }
305
319
 
306
320
  /**
package/src/GroupsApi.ts CHANGED
@@ -2,20 +2,14 @@ import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
2
  import type {
3
3
  CreateUserGroupPayload,
4
4
  DeleteByIdResult,
5
+ ListUserGroupsQuery,
5
6
  UpdateUserGroupPayload,
6
7
  UserGroup,
7
8
  UserRef,
8
9
  } from '@vertesia/common';
9
10
 
10
- export interface GroupsQueryOptions {
11
- search?: string;
12
- tags?: string[];
13
- limit?: number;
14
- offset?: number;
15
- /** Only return groups usable in this project (org-wide + restricted-to-this-project). */
16
- project?: string;
17
- [key: string]: string | string[] | number | undefined;
18
- }
11
+ /** @deprecated Use ListUserGroupsQuery from @vertesia/common. */
12
+ export type GroupsQueryOptions = ListUserGroupsQuery;
19
13
 
20
14
  export class GroupsApi extends ApiTopic {
21
15
  constructor(parent: ClientBase) {
@@ -27,7 +21,7 @@ export class GroupsApi extends ApiTopic {
27
21
  * @param options Query options for filtering and pagination
28
22
  * @returns Array of UserGroup objects
29
23
  */
30
- list(options?: GroupsQueryOptions): Promise<UserGroup[]> {
24
+ list(options?: ListUserGroupsQuery): Promise<UserGroup[]> {
31
25
  return this.get('/', { query: options });
32
26
  }
33
27
 
@@ -1,5 +1,5 @@
1
1
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
- import type { CatalogInteractionRef, InCodeInteraction, InteractionStatus } from '@vertesia/common';
2
+ import type { CatalogInteractionRef, InteractionStatus, ResolvedCatalogInteraction } from '@vertesia/common';
3
3
 
4
4
  export class InteractionCatalogApi extends ApiTopic {
5
5
  constructor(parent: ClientBase) {
@@ -58,10 +58,10 @@ export class InteractionCatalogApi extends ApiTopic {
58
58
  }
59
59
 
60
60
  /**
61
- * Resolve an interaction given its id to a InCodeInteraction
61
+ * Resolve an interaction by ID to its complete executable definition.
62
62
  * @param id Interaction id
63
63
  */
64
- resolve(id: string): Promise<InCodeInteraction> {
64
+ resolve(id: string): Promise<ResolvedCatalogInteraction> {
65
65
  return this.get(`/resolve/${id}`);
66
66
  }
67
67
  }
@@ -10,6 +10,7 @@ describe('InteractionOutput', () => {
10
10
  { type: 'json', value: { title: 'Engineer', level: 'Senior' } },
11
11
  { type: 'image', value: 'data:image/png;base64,iVBORw0K...' },
12
12
  { type: 'image', value: 'https://example.com/image.jpg' },
13
+ { type: 'video', value: 'gs://project-bucket/runs/run-1/media/video.mp4' },
13
14
  ];
14
15
 
15
16
  describe('text accessors', () => {
@@ -29,6 +30,22 @@ describe('InteractionOutput', () => {
29
30
  });
30
31
  });
31
32
 
33
+ describe('thought accessors', () => {
34
+ it('keeps thoughts separate from normal answer text', () => {
35
+ const output = InteractionOutput.from([
36
+ { type: 'thoughts', value: 'First reason' },
37
+ { type: 'thoughts', value: 'Second reason' },
38
+ { type: 'text', value: 'Answer' },
39
+ ]);
40
+
41
+ expect(output.hasThoughts()).toBe(true);
42
+ expect(output.thoughts(' | ')).toBe('First reason | Second reason');
43
+ expect(output.thoughtParts()).toEqual(['First reason', 'Second reason']);
44
+ expect(output.text()).toBe('Answer');
45
+ expect(output.stringify()).toBe('Answer');
46
+ });
47
+ });
48
+
32
49
  describe('object accessors', () => {
33
50
  it('should return first JSON object', () => {
34
51
  const output = InteractionOutput.from<{ name: string; age: number }>(sampleResults);
@@ -78,6 +95,24 @@ describe('InteractionOutput', () => {
78
95
  });
79
96
  });
80
97
 
98
+ describe('video accessors', () => {
99
+ it('should return video results', () => {
100
+ const output = InteractionOutput.from(sampleResults);
101
+
102
+ expect(output.hasVideo()).toBe(true);
103
+ expect(output.video()).toBe('gs://project-bucket/runs/run-1/media/video.mp4');
104
+ expect(output.videos()).toEqual(['gs://project-bucket/runs/run-1/media/video.mp4']);
105
+ });
106
+
107
+ it('should throw when no video result exists', () => {
108
+ const output = InteractionOutput.from([{ type: 'text', value: 'hello' }]);
109
+
110
+ expect(output.hasVideo()).toBe(false);
111
+ expect(output.videos()).toEqual([]);
112
+ expect(() => output.video()).toThrow('No video result found');
113
+ });
114
+ });
115
+
81
116
  describe('utility methods', () => {
82
117
  it('should convert to string (concatenated text)', () => {
83
118
  const output = InteractionOutput.from(sampleResults);
@@ -99,7 +134,8 @@ describe('InteractionOutput', () => {
99
134
  '{"name":"Alice","age":30}\n' +
100
135
  '{"title":"Engineer","level":"Senior"}\n' +
101
136
  'data:image/png;base64,iVBORw0K...\n' +
102
- 'https://example.com/image.jpg',
137
+ 'https://example.com/image.jpg\n' +
138
+ 'gs://project-bucket/runs/run-1/media/video.mp4',
103
139
  );
104
140
  });
105
141
 
@@ -147,11 +183,11 @@ describe('InteractionOutput', () => {
147
183
  expect(output[1]).toEqual({ type: 'text', value: 'World!' });
148
184
 
149
185
  // Array properties
150
- expect(output.length).toBe(6);
186
+ expect(output.length).toBe(7);
151
187
 
152
188
  // Array methods
153
189
  const types = output.map((r) => r.type);
154
- expect(types).toEqual(['text', 'text', 'json', 'json', 'image', 'image']);
190
+ expect(types).toEqual(['text', 'text', 'json', 'json', 'image', 'image', 'video']);
155
191
 
156
192
  const textResults = output.filter((r) => r.type === 'text');
157
193
  expect(textResults).toHaveLength(2);
@@ -161,7 +197,7 @@ describe('InteractionOutput', () => {
161
197
  const output = InteractionOutput.from<{ name: string; age: number }>(sampleResults);
162
198
 
163
199
  // Should work as array AND have custom methods
164
- expect(output.length).toBe(6);
200
+ expect(output.length).toBe(7);
165
201
  expect(output.text('')).toBe('Hello, World!');
166
202
  expect(output.object()).toEqual({ name: 'Alice', age: 30 });
167
203
  });
@@ -185,7 +221,7 @@ describe('InteractionOutput', () => {
185
221
 
186
222
  // Verify it still works correctly
187
223
  expect(output2.text()).toBe('Hello, \nWorld!');
188
- expect(output2.length).toBe(6);
224
+ expect(output2.length).toBe(7);
189
225
  });
190
226
 
191
227
  it('should not double-wrap when passing an InteractionOutputArray', () => {
@@ -23,7 +23,7 @@ export function enhanceExecutionRun<ResultT = unknown, ParamsT = unknown>(
23
23
 
24
24
  /**
25
25
  * A convenient wrapper around CompletionResult[] that provides ergonomic accessors
26
- * for different result types (text, JSON/objects, images) from interaction executions.
26
+ * for different result types (text, thoughts, JSON/objects, images) from interaction executions.
27
27
  *
28
28
  * Use the static from() method to create a proxied array that acts as both
29
29
  * an array and has these convenience methods.
@@ -112,10 +112,18 @@ export class InteractionOutput<T = unknown> {
112
112
  return this.results.some((r) => r.type === 'text');
113
113
  }
114
114
 
115
+ hasThoughts() {
116
+ return this.results.some((r) => r.type === 'thoughts');
117
+ }
118
+
115
119
  hasImage() {
116
120
  return this.results.some((r) => r.type === 'image');
117
121
  }
118
122
 
123
+ hasVideo() {
124
+ return this.results.some((r) => r.type === 'video');
125
+ }
126
+
119
127
  /**
120
128
  * Get the concatenated text from all text results.
121
129
  * Returns an empty string if no text results exist.
@@ -134,6 +142,18 @@ export class InteractionOutput<T = unknown> {
134
142
  return this.results.filter((r) => r.type === 'text').map((r) => r.value);
135
143
  }
136
144
 
145
+ /** Get visible reasoning without mixing it into the assistant answer. */
146
+ thoughts(delimiter = '\n'): string {
147
+ return this.results
148
+ .filter((r) => r.type === 'thoughts')
149
+ .map((r) => r.value)
150
+ .join(delimiter);
151
+ }
152
+
153
+ thoughtParts(): string[] {
154
+ return this.results.filter((r) => r.type === 'thoughts').map((r) => r.value);
155
+ }
156
+
137
157
  /**
138
158
  * Get the first JSON result as a parsed object.
139
159
  * If no JSON result exists, attempts to parse the concatenated text as JSON.
@@ -197,9 +217,24 @@ export class InteractionOutput<T = unknown> {
197
217
  return this.results.filter((r) => r.type === 'image').map((r) => r.value);
198
218
  }
199
219
 
220
+ /** Get the first video result URI. */
221
+ video(): string {
222
+ const videoResult = this.results.find((r) => r.type === 'video');
223
+ if (!videoResult) {
224
+ throw new Error('No video result found');
225
+ }
226
+ return videoResult.value;
227
+ }
228
+
229
+ /** Get all video result URIs. */
230
+ videos(): string[] {
231
+ return this.results.filter((r) => r.type === 'video').map((r) => r.value);
232
+ }
233
+
200
234
  /**
201
- * Convert all results to a string representation.
202
- * Text and image results are used as-is, JSON results are stringified with the specified indent.
235
+ * Convert all answer results to a string representation.
236
+ * Thoughts are available through thoughts() and remain separate from the answer.
237
+ * Text and media results are used as-is, JSON results are stringified with the specified indent.
203
238
  * All parts are joined using the specified separator.
204
239
  *
205
240
  * @param separator - The separator to use between parts (default: '\n')
@@ -216,6 +251,7 @@ export class InteractionOutput<T = unknown> {
216
251
  */
217
252
  stringify(separator = '\n', indent = 2): string {
218
253
  return this.results
254
+ .filter((r) => r.type !== 'thoughts')
219
255
  .map((r) => {
220
256
  switch (r.type) {
221
257
  case 'json':
@@ -0,0 +1,56 @@
1
+ import type { CreateOAuthAuthorizationRequestPayload } from '@vertesia/common';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { VertesiaClient } from './client.js';
4
+
5
+ const payload: CreateOAuthAuthorizationRequestPayload = {
6
+ response_type: 'code',
7
+ client_id: 'client-1',
8
+ redirect_uri: 'http://127.0.0.1/callback',
9
+ resource: 'https://mcp-dev-main.api.dev1.vertesia.io/mcp',
10
+ code_challenge: 'A'.repeat(43),
11
+ code_challenge_method: 'S256',
12
+ };
13
+
14
+ function createClient(requests: Request[]) {
15
+ return new VertesiaClient({
16
+ serverUrl: 'https://studio.example.com',
17
+ storeUrl: 'https://zeno.example.com',
18
+ tokenServerUrl: 'https://sts.dev1.vertesia.io',
19
+ fetch: vi.fn(async () =>
20
+ Response.json({
21
+ request_id: 'vor_1',
22
+ client_id: 'client-1',
23
+ client_name: 'Codex',
24
+ redirect_uri: payload.redirect_uri,
25
+ redirect_origin: 'http://127.0.0.1',
26
+ requested_scopes: [],
27
+ project_binding_mode: 'user_select',
28
+ status: 'pending',
29
+ created_at: '2026-08-10T00:00:00.000Z',
30
+ expires_at: '2026-08-10T00:10:00.000Z',
31
+ }),
32
+ ),
33
+ onRequest: (request) => requests.push(request),
34
+ });
35
+ }
36
+
37
+ describe('OAuthServerApi.createAuthorizationRequest', () => {
38
+ it('posts the initial request to the path-based tenant issuer', async () => {
39
+ const requests: Request[] = [];
40
+ const client = createClient(requests);
41
+
42
+ await client.oauthServer.createAuthorizationRequest(payload, '/env/dev1/dev-main');
43
+
44
+ expect(requests[0]?.url).toBe('https://sts.dev1.vertesia.io/env/dev1/dev-main/requests');
45
+ });
46
+
47
+ it('rejects an untrusted authorization-server path before making a request', async () => {
48
+ const requests: Request[] = [];
49
+ const client = createClient(requests);
50
+
51
+ await expect(
52
+ client.oauthServer.createAuthorizationRequest(payload, 'https://attacker.example.com/oauth'),
53
+ ).rejects.toThrow('Invalid OAuth authorization server path');
54
+ expect(requests).toHaveLength(0);
55
+ });
56
+ });
@@ -44,8 +44,18 @@ export default class OAuthServerApi extends ClientBase {
44
44
  return this.parent.getRetryPolicy();
45
45
  }
46
46
 
47
- createAuthorizationRequest(payload: CreateOAuthAuthorizationRequestPayload): Promise<OAuthAuthorizationRequest> {
48
- return this.post('/requests', { payload });
47
+ createAuthorizationRequest(
48
+ payload: CreateOAuthAuthorizationRequestPayload,
49
+ authorizationServerPath?: string,
50
+ ): Promise<OAuthAuthorizationRequest> {
51
+ if (!authorizationServerPath) {
52
+ return this.post('/requests', { payload });
53
+ }
54
+ if (!/^\/env\/[a-z][a-z0-9-]{1,15}\/dev-[a-z0-9-]+$/.test(authorizationServerPath)) {
55
+ return Promise.reject(new Error('Invalid OAuth authorization server path'));
56
+ }
57
+ const url = new URL(`${authorizationServerPath}/requests`, this.baseUrl).toString();
58
+ return this.post(url, { payload });
49
59
  }
50
60
 
51
61
  createDeviceAuthorization(payload: OAuthDeviceAuthorizationRequest): Promise<OAuthDeviceAuthorizationResponse> {
@@ -19,6 +19,8 @@ import type {
19
19
  RenderingTemplateDefinition,
20
20
  RenderingTemplateDefinitionRef,
21
21
  SupportedIntegrations,
22
+ UpdateProjectConfigurationPayload,
23
+ UpdateProjectPayload,
22
24
  } from '@vertesia/common';
23
25
 
24
26
  export default class ProjectsApi extends ApiTopic {
@@ -43,7 +45,7 @@ export default class ProjectsApi extends ApiTopic {
43
45
  });
44
46
  }
45
47
 
46
- update(projectId: string, payload: Partial<Project>): Promise<Project> {
48
+ update(projectId: string, payload: UpdateProjectPayload): Promise<Project> {
47
49
  return this.put(`/${projectId}`, {
48
50
  payload,
49
51
  });
@@ -53,7 +55,7 @@ export default class ProjectsApi extends ApiTopic {
53
55
  return this.del(`/${projectId}`);
54
56
  }
55
57
 
56
- updateConfiguration(projectId: string, payload: Partial<ProjectConfiguration>): Promise<ProjectConfiguration> {
58
+ updateConfiguration(projectId: string, payload: UpdateProjectConfigurationPayload): Promise<ProjectConfiguration> {
57
59
  return this.put(`/${projectId}/configuration`, {
58
60
  payload,
59
61
  });
package/src/RunsApi.ts CHANGED
@@ -2,10 +2,13 @@ import type { ExecutionResponse } from '@llumiverse/common';
2
2
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
3
3
  import type {
4
4
  ComputeRunFacetPayload,
5
+ ComputeRunFacetsResponse,
5
6
  ExecutionRun,
6
7
  ExecutionRunDocRef,
7
8
  ExecutionRunRef,
8
9
  FindPayload,
10
+ FindRunResult,
11
+ InteractionExecutionResult,
9
12
  PopulatedExecutionRun,
10
13
  RunClonePayload,
11
14
  RunCreatePayload,
@@ -16,7 +19,12 @@ import type {
16
19
  UserMessagePayload,
17
20
  } from '@vertesia/common';
18
21
  import type { VertesiaClient } from './client.js';
19
- import { type EnhancedExecutionRun, enhanceExecutionRun } from './InteractionOutput.js';
22
+ import {
23
+ type EnhancedExecutionRun,
24
+ type EnhancedInteractionExecutionResult,
25
+ enhanceExecutionRun,
26
+ enhanceInteractionExecutionResult,
27
+ } from './InteractionOutput.js';
20
28
 
21
29
  export interface FilterOption {
22
30
  id: string;
@@ -24,14 +32,7 @@ export interface FilterOption {
24
32
  count: number;
25
33
  }
26
34
 
27
- export interface ComputeRunFacetsResponse {
28
- environments?: { _id: string; count: number }[];
29
- interactions?: { _id: string; count: number }[];
30
- models?: { _id: string; count: number }[];
31
- tags?: { _id: string; count: number }[];
32
- status?: { _id: string; count: number }[];
33
- total?: number;
34
- }
35
+ export type { ComputeRunFacetsResponse } from '@vertesia/common';
35
36
 
36
37
  export class RunsApi extends ApiTopic {
37
38
  constructor(parent: ClientBase) {
@@ -54,7 +55,7 @@ export class RunsApi extends ApiTopic {
54
55
  return this.get('/', { query: query });
55
56
  }
56
57
 
57
- find(payload: FindPayload): Promise<ExecutionRun[]> {
58
+ find(payload: FindPayload): Promise<FindRunResult[]> {
58
59
  return this.post('/find', {
59
60
  payload,
60
61
  });
@@ -91,7 +92,7 @@ export class RunsApi extends ApiTopic {
91
92
  async create<ResultT = unknown, ParamsT = unknown>(
92
93
  payload: RunCreatePayload,
93
94
  options?: { timeoutMs?: number | false | null; signal?: AbortSignal },
94
- ): Promise<EnhancedExecutionRun<ResultT, ParamsT>> {
95
+ ): Promise<EnhancedInteractionExecutionResult<ResultT, ParamsT>> {
95
96
  const sessionTags = (this.client as VertesiaClient).sessionTags;
96
97
  if (sessionTags) {
97
98
  let tags = Array.isArray(sessionTags) ? sessionTags : [sessionTags];
@@ -102,12 +103,12 @@ export class RunsApi extends ApiTopic {
102
103
  }
103
104
  payload = { ...payload, tags };
104
105
  }
105
- const r = await this.post<ExecutionRun<ParamsT>>('/', {
106
+ const r = await this.post<InteractionExecutionResult<ParamsT>>('/', {
106
107
  payload,
107
108
  timeoutMs: options?.timeoutMs,
108
109
  signal: options?.signal,
109
110
  });
110
- return enhanceExecutionRun<ResultT, ParamsT>(r);
111
+ return enhanceInteractionExecutionResult<ResultT, ParamsT>(r);
111
112
  }
112
113
 
113
114
  /**
@@ -145,7 +146,7 @@ export class RunsApi extends ApiTopic {
145
146
  /**
146
147
  * Get the list of all runs facets
147
148
  * @param payload query payload to filter facet search
148
- * @returns ComputeRunFacetsResponse[]
149
+ * @returns Facet buckets and the total number of matching runs
149
150
  **/
150
151
  computeFacets(query: ComputeRunFacetPayload): Promise<ComputeRunFacetsResponse> {
151
152
  return this.post('/facets', {
package/src/ToolsApi.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { ApiTopic, type ClientBase } from '@vertesia/api-fetch-client';
2
2
  import type {
3
3
  AggregatedTool,
4
+ InspectProjectToolQuery,
4
5
  ListProjectToolsQuery,
6
+ ToolInspection,
5
7
  ValidateToolNamesPayload,
6
8
  ValidateToolNamesResponse,
7
9
  } from '@vertesia/common';
@@ -37,9 +39,17 @@ export default class ToolsApi extends ApiTopic {
37
39
  const params: Record<string, string> = {};
38
40
  if (query?.sources?.length) params.sources = query.sources.join(',');
39
41
  if (query?.exclude?.length) params.exclude = query.exclude.join(',');
42
+ if (query?.context) params.context = query.context;
40
43
  return this.get('/', { query: Object.keys(params).length > 0 ? params : undefined });
41
44
  }
42
45
 
46
+ inspect(name: string, query?: InspectProjectToolQuery): Promise<ToolInspection> {
47
+ if (!name.trim()) throw new Error('Tool name must not be empty');
48
+ return this.get(`/${encodeURIComponent(name)}`, {
49
+ query: query?.context ? { context: query.context } : undefined,
50
+ });
51
+ }
52
+
43
53
  /**
44
54
  * List only Vertesia-provided builtin tools and system skills.
45
55
  */
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
2
2
  import { VertesiaClient } from './client.js';
3
3
 
4
4
  describe('ViewsApi', () => {
5
- it('keeps CRUD on Studio and proxies execution to Zeno', async () => {
5
+ it('keeps CRUD on Studio and proxies persisted and app-contributed execution to Zeno', async () => {
6
6
  const requests: Request[] = [];
7
7
  const fetchMock = vi.fn(async () => {
8
8
  return new Response(JSON.stringify({}), {
@@ -19,6 +19,7 @@ describe('ViewsApi', () => {
19
19
 
20
20
  await client.views.retrieve('document-library');
21
21
  await client.views.execute('document-library');
22
+ await client.views.execute('app:content:document-library');
22
23
  await client.views.preview({
23
24
  configuration: {
24
25
  name: 'Document library',
@@ -30,6 +31,7 @@ describe('ViewsApi', () => {
30
31
  expect(requests.map((request) => request.url)).toEqual([
31
32
  'https://studio.example.com/api/v1/views/document-library',
32
33
  'https://zeno.example.com/api/v1/view-executions/document-library/execute',
34
+ 'https://zeno.example.com/api/v1/view-executions/app%3Acontent%3Adocument-library/execute',
33
35
  'https://zeno.example.com/api/v1/view-executions/preview',
34
36
  ]);
35
37
  });
package/src/ViewsApi.ts CHANGED
@@ -45,7 +45,7 @@ export default class ViewsApi extends ApiTopic {
45
45
  }
46
46
 
47
47
  /**
48
- * Execute a persisted or app-contributed View through the content service.
48
+ * Execute a persisted, built-in system, or app-contributed View through the content service.
49
49
  */
50
50
  execute(id: string, payload: ExecuteViewRequest = {}): Promise<ViewExecutionResult> {
51
51
  return this.executionApi.execute(id, payload);
package/src/client.ts CHANGED
@@ -350,6 +350,10 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
350
350
  return this.store.processes;
351
351
  }
352
352
 
353
+ get processTestRuns() {
354
+ return this.store.processTestRuns;
355
+ }
356
+
353
357
  get tasks() {
354
358
  return this.store.tasks;
355
359
  }
package/src/execute.ts CHANGED
@@ -47,7 +47,7 @@ export async function executeInteraction<P = unknown>(
47
47
  onChunk?: (chunk: string) => void,
48
48
  ): Promise<InteractionExecutionResult<P>> {
49
49
  const stream = !!onChunk;
50
- const response = (await client.runs.create(
50
+ const response = await client.runs.create<P, P>(
51
51
  {
52
52
  ...payload,
53
53
  interaction: interactionId,
@@ -56,7 +56,7 @@ export async function executeInteraction<P = unknown>(
56
56
  // Synchronous interaction execution blocks on the model; keep a long client timeout.
57
57
  // (Streaming returns the run immediately, then streams over a separate channel — see below.)
58
58
  { timeoutMs: stream ? undefined : INTERACTION_EXECUTION_TIMEOUT_MS },
59
- )) as unknown as InteractionExecutionResult<P>;
59
+ );
60
60
  if (stream) {
61
61
  if (response.status === ExecutionRunStatus.failed) {
62
62
  return response;