@vertesia/client 1.5.0-dev.20260722.120446Z → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/client",
3
- "version": "1.5.0-dev.20260722.120446Z",
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",
@@ -12,16 +12,16 @@
12
12
  "license": "Apache-2.0",
13
13
  "devDependencies": {
14
14
  "@types/node": "^24.13.3",
15
- "rolldown": "1.1.5",
15
+ "rolldown": "1.2.0",
16
16
  "typescript": "^6.0.3",
17
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.20260722.112223Z",
23
- "@vertesia/common": "1.5.0-dev.20260722.120446Z",
24
- "@vertesia/api-fetch-client": "1.5.0-dev.20260722.120446Z"
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": "203c30e5a8ddc583ac9f1acce0f5445eb38a9551",
51
+ "gitHead": "57bcc2f27dd17b3387f27c18e89329c4c37f98ac",
52
52
  "scripts": {
53
53
  "lint": "biome lint src",
54
54
  "clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
@@ -106,10 +106,13 @@ describe('AppsApi', () => {
106
106
 
107
107
  expect(result.commit).toBe('2'.repeat(40));
108
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({
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({
113
116
  ref: 'main',
114
117
  expected_head: '1'.repeat(40),
115
118
  message: 'Add docs',
@@ -118,7 +121,12 @@ describe('AppsApi', () => {
118
121
  { field: 'file_1', path: 'docs/diagram.png' },
119
122
  ],
120
123
  });
121
- expect((form?.get('file_0') as File).name).toBe('guide.md');
122
- expect((form?.get('file_1') as File).size).toBe(3);
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);
123
131
  });
124
132
  });
package/src/AppsApi.ts CHANGED
@@ -2,6 +2,8 @@ import { ApiTopic, type ClientBase, type ServerError } from '@vertesia/api-fetch
2
2
  import type {
3
3
  AppBuildProgress,
4
4
  AppDeleteSummary,
5
+ AppDevelopmentTaskDetails,
6
+ AppDevelopmentTaskList,
5
7
  AppInspectionResult,
6
8
  AppInstallation,
7
9
  AppInstallationKind,
@@ -12,6 +14,7 @@ import type {
12
14
  AppManifestData,
13
15
  AppPackage,
14
16
  AppPackageScope,
17
+ AppRepoBranch,
15
18
  AppRepoCommits,
16
19
  AppRepoDocumentCommit,
17
20
  AppRepoRefs,
@@ -21,6 +24,7 @@ import type {
21
24
  AppVersionListQuery,
22
25
  AppVersionRecord,
23
26
  CountResult,
27
+ CreateAppRepoBranchRequest,
24
28
  DeleteAppVersionResponse,
25
29
  ProjectRef,
26
30
  PromoteAppVersionResponse,
@@ -218,6 +222,21 @@ export default class AppsApi extends ApiTopic {
218
222
  return this.get(`/${encodeURIComponent(appIdOrName)}/repo/refs`);
219
223
  }
220
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
+
221
240
  /**
222
241
  * Get the promoted package an app exposes, filtered by scope. App-owned (in-code) artifacts are
223
242
  * package-resolved (no per-id route), so this is how a client reads the full definition — type
@@ -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
  */
@@ -17,6 +17,7 @@ import type {
17
17
  } from '@vertesia/common';
18
18
  import { StreamSource } from '../StreamSource.js';
19
19
  import { fetchSignedUrl } from './signed-url.js';
20
+ import { getUploadMimeTypeHint, resolveUploadMimeType } from './uploadMimeType.js';
20
21
 
21
22
  export const MEMORIES_PREFIX = 'memories';
22
23
  export const ARTIFACTS_PREFIX = 'agents';
@@ -137,7 +138,7 @@ export class FilesApi extends ApiTopic {
137
138
  ): Promise<{ source: string; name: string; type?: string; etag?: string }[]> {
138
139
  const fileDescriptors = sources.map((s) => ({
139
140
  name: s.name,
140
- mime_type: s.type,
141
+ mime_type: getUploadMimeTypeHint(s.type),
141
142
  id: s instanceof StreamSource ? s.id : undefined,
142
143
  }));
143
144
 
@@ -153,7 +154,7 @@ export class FilesApi extends ApiTopic {
153
154
  const idx = i + j;
154
155
  const { url, id, mime_type } = files[idx];
155
156
  const isStream = source instanceof StreamSource;
156
- const sourceMimeType = source.type || mime_type;
157
+ const sourceMimeType = resolveUploadMimeType(source.type, mime_type);
157
158
 
158
159
  const res = await fetchSignedUrl(url, {
159
160
  method: 'PUT',
@@ -193,13 +194,18 @@ export class FilesApi extends ApiTopic {
193
194
  */
194
195
  async uploadFileWithPath(source: StreamSource | File): Promise<{ id: string; path: string }> {
195
196
  const isStream = source instanceof StreamSource;
196
- const { url, id, path } = await this.getUploadUrl(source);
197
+ const { url, id, path, mime_type } = await this.getUploadUrl({
198
+ id: isStream ? source.id : undefined,
199
+ name: source.name,
200
+ mime_type: getUploadMimeTypeHint(source.type),
201
+ });
202
+ const sourceMimeType = resolveUploadMimeType(source.type, mime_type) || 'application/gzip';
197
203
 
198
204
  const res = await fetchSignedUrl(url, {
199
205
  method: 'PUT',
200
206
  body: isStream ? source.stream : source,
201
207
  headers: {
202
- 'Content-Type': source.type || 'application/gzip',
208
+ 'Content-Type': sourceMimeType,
203
209
  },
204
210
  }).catch((err) => {
205
211
  console.error('Failed to upload file', { err, url, id, path });
@@ -56,6 +56,7 @@ import { StreamSource } from '../StreamSource.js';
56
56
  import { AnalyzeDocApi } from './AnalyzeDocApi.js';
57
57
  import type { ZenoClient } from './client.js';
58
58
  import { fetchSignedUrl } from './signed-url.js';
59
+ import { getUploadMimeTypeHint, resolveUploadMimeType } from './uploadMimeType.js';
59
60
 
60
61
  type ContentObjectWritePayload = Omit<CreateContentObjectPayload, 'content'> & {
61
62
  content?: ContentSource | File | StreamSource;
@@ -259,9 +260,9 @@ export class ObjectsApi extends ApiTopic {
259
260
  const { url, id, mime_type } = await this.getUploadUrl({
260
261
  id: isStream ? source.id : undefined,
261
262
  name: source.name,
262
- mime_type: source.type,
263
+ mime_type: getUploadMimeTypeHint(source.type),
263
264
  });
264
- const sourceMimeType = source.type || mime_type;
265
+ const sourceMimeType = resolveUploadMimeType(source.type, mime_type);
265
266
 
266
267
  // upload the file content to the signed URL
267
268
  const res = await fetchSignedUrl(url, {
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getUploadMimeTypeHint, resolveUploadMimeType } from './uploadMimeType.js';
3
+
4
+ describe('upload MIME type resolution', () => {
5
+ it('keeps a specific browser-provided content type', () => {
6
+ expect(getUploadMimeTypeHint('text/plain')).toBe('text/plain');
7
+ expect(resolveUploadMimeType('text/plain', 'text/markdown')).toBe('text/plain');
8
+ });
9
+
10
+ it('uses filename inference when the browser reports generic binary content', () => {
11
+ expect(getUploadMimeTypeHint('application/octet-stream')).toBeUndefined();
12
+ expect(resolveUploadMimeType('application/octet-stream', 'text/markdown')).toBe('text/markdown');
13
+ });
14
+
15
+ it('uses filename inference when the browser omits the content type', () => {
16
+ expect(resolveUploadMimeType('', 'text/markdown')).toBe('text/markdown');
17
+ });
18
+
19
+ it('retains octet-stream when no more specific type can be inferred', () => {
20
+ expect(resolveUploadMimeType('application/octet-stream', undefined)).toBe('application/octet-stream');
21
+ });
22
+ });
@@ -0,0 +1,16 @@
1
+ const GENERIC_BINARY_CONTENT_TYPE = 'application/octet-stream';
2
+
3
+ function normalizedContentType(contentType: string | undefined): string | undefined {
4
+ return contentType?.split(';', 1)[0]?.trim().toLowerCase() || undefined;
5
+ }
6
+
7
+ export function getUploadMimeTypeHint(contentType: string | undefined): string | undefined {
8
+ return normalizedContentType(contentType) === GENERIC_BINARY_CONTENT_TYPE ? undefined : contentType || undefined;
9
+ }
10
+
11
+ export function resolveUploadMimeType(
12
+ sourceContentType: string | undefined,
13
+ inferredContentType: string | undefined,
14
+ ): string | undefined {
15
+ return getUploadMimeTypeHint(sourceContentType) || inferredContentType || sourceContentType || undefined;
16
+ }