@vertesia/client 0.79.1 → 0.80.0-dev-20251118
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 +6 -1
- package/src/InteractionCatalogApi.ts +72 -0
- package/src/InteractionsApi.ts +19 -3
- package/src/ProjectsApi.ts +7 -1
- package/src/RunsApi.ts +8 -1
- package/src/client.test.ts +2 -0
- package/src/client.ts +128 -57
- package/src/index.ts +1 -0
- package/src/store/CollectionsApi.ts +15 -2
- package/src/store/FilesApi.ts +10 -8
- package/src/store/ObjectsApi.ts +7 -23
- package/src/store/WorkflowsApi.ts +166 -0
- package/src/store/client.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertesia/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.0-dev-20251118",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "./lib/types/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"require": "./lib/cjs/nodejs/index.js"
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/vertesia/composableai.git",
|
|
53
|
+
"directory": "packages/client"
|
|
54
|
+
},
|
|
50
55
|
"typesVersions": {
|
|
51
56
|
"*": {
|
|
52
57
|
"node": [
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ApiTopic, ClientBase } from "@vertesia/api-fetch-client";
|
|
2
|
+
import {
|
|
3
|
+
CatalogInteractionRef,
|
|
4
|
+
InCodeInteraction,
|
|
5
|
+
InteractionStatus
|
|
6
|
+
} from "@vertesia/common";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export class InteractionCatalogApi extends ApiTopic {
|
|
10
|
+
constructor(parent: ClientBase) {
|
|
11
|
+
super(parent, "/api/v1/interactions/catalog");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* List all project interactions
|
|
16
|
+
*/
|
|
17
|
+
list(query: { status?: InteractionStatus, tag?: string } = {}): Promise<CatalogInteractionRef[]> {
|
|
18
|
+
return this.get("/", {
|
|
19
|
+
query
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* List all stored interactions
|
|
25
|
+
*/
|
|
26
|
+
listStoredInteractions(query: { status?: InteractionStatus, tag?: string } = {}): Promise<CatalogInteractionRef[]> {
|
|
27
|
+
return this.get("/stored", {
|
|
28
|
+
query
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* List sys interactions
|
|
34
|
+
*/
|
|
35
|
+
listSysInteractions(tag?: string): Promise<CatalogInteractionRef[]> {
|
|
36
|
+
return this.get(`/sys`, {
|
|
37
|
+
query: {
|
|
38
|
+
tag
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* List sys interactions
|
|
45
|
+
*/
|
|
46
|
+
listAppInteractions(appName: string, tag?: string): Promise<CatalogInteractionRef[]> {
|
|
47
|
+
return this.get(`/apps/${appName}`, {
|
|
48
|
+
query: {
|
|
49
|
+
tag
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* List all app interactions
|
|
56
|
+
*/
|
|
57
|
+
listAllAppInteractions(tag?: string): Promise<CatalogInteractionRef[]> {
|
|
58
|
+
return this.get(`/apps`, {
|
|
59
|
+
query: {
|
|
60
|
+
tag
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve an interaction given its id to a InCodeInteraction
|
|
67
|
+
* @param id Interaction id
|
|
68
|
+
*/
|
|
69
|
+
resolve(id: string): Promise<InCodeInteraction> {
|
|
70
|
+
return this.get(`/resolve/${id}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/InteractionsApi.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { ApiTopic, ClientBase, ServerError } from "@vertesia/api-fetch-client";
|
|
2
2
|
import {
|
|
3
3
|
AsyncExecutionPayload, ComputeInteractionFacetPayload, GenerateInteractionPayload, GenerateTestDataPayload, ImprovePromptPayload,
|
|
4
|
-
|
|
4
|
+
ImprovePromptPayloadConfig,
|
|
5
|
+
Interaction, InteractionCreatePayload, InteractionEndpoint, InteractionEndpointQuery,
|
|
6
|
+
InteractionExecutionPayload, InteractionForkPayload,
|
|
5
7
|
InteractionPublishPayload, InteractionRef, InteractionRefWithSchema, InteractionSearchPayload, InteractionSearchQuery,
|
|
6
|
-
InteractionsExportPayload, InteractionUpdatePayload,
|
|
8
|
+
InteractionsExportPayload, InteractionUpdatePayload,
|
|
9
|
+
RateLimitRequestPayload, RateLimitRequestResponse
|
|
7
10
|
} from "@vertesia/common";
|
|
8
11
|
import { VertesiaClient } from "./client.js";
|
|
9
12
|
import { checkRateLimit, executeInteraction, executeInteractionAsync, executeInteractionByName } from "./execute.js";
|
|
13
|
+
import { InteractionCatalogApi } from "./InteractionCatalogApi.js";
|
|
10
14
|
import { EnhancedExecutionRun, EnhancedInteractionExecutionResult, enhanceExecutionRun, enhanceInteractionExecutionResult } from "./InteractionOutput.js";
|
|
11
15
|
|
|
12
16
|
export interface ComputeInteractionFacetsResponse {
|
|
@@ -20,8 +24,11 @@ export interface AsyncExecutionResult {
|
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export default class InteractionsApi extends ApiTopic {
|
|
27
|
+
catalog: InteractionCatalogApi;
|
|
28
|
+
|
|
23
29
|
constructor(parent: ClientBase) {
|
|
24
30
|
super(parent, "/api/v1/interactions");
|
|
31
|
+
this.catalog = new InteractionCatalogApi(parent);
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
/**
|
|
@@ -221,14 +228,23 @@ export default class InteractionsApi extends ApiTopic {
|
|
|
221
228
|
|
|
222
229
|
/**
|
|
223
230
|
* Suggest Improvement for a prompt
|
|
231
|
+
* @deprecated use suggestPromptImprovements instead
|
|
224
232
|
*/
|
|
225
|
-
async suggestImprovements<ResultT = any, ParamsT = any>(id: string, payload:
|
|
233
|
+
async suggestImprovements<ResultT = any, ParamsT = any>(id: string, payload: ImprovePromptPayloadConfig): Promise<EnhancedExecutionRun<ResultT, ParamsT>> {
|
|
226
234
|
const r = await this.post(`${id}/suggest-prompt-improvements`, {
|
|
227
235
|
payload
|
|
228
236
|
});
|
|
229
237
|
return enhanceExecutionRun<ResultT, ParamsT>(r);
|
|
230
238
|
}
|
|
231
239
|
|
|
240
|
+
async suggestPromptImprovements<ResultT = any, ParamsT = any>(payload: ImprovePromptPayload): Promise<EnhancedInteractionExecutionResult<ResultT, ParamsT>> {
|
|
241
|
+
const r = await this.post(`/improve`, {
|
|
242
|
+
payload
|
|
243
|
+
});
|
|
244
|
+
return enhanceInteractionExecutionResult<ResultT, ParamsT>(r);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
232
248
|
/**
|
|
233
249
|
* List the versions of the interaction. Returns an empty array if no versions are found
|
|
234
250
|
* @param id
|
package/src/ProjectsApi.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiTopic, ClientBase } from "@vertesia/api-fetch-client";
|
|
2
|
-
import { AwsConfiguration, GithubConfiguration, GladiaConfiguration, ICreateProjectPayload, MagicPdfConfiguration, Project, ProjectIntegrationListEntry, ProjectRef, SupportedIntegrations } from "@vertesia/common";
|
|
2
|
+
import { AwsConfiguration, GithubConfiguration, GladiaConfiguration, ICreateProjectPayload, MagicPdfConfiguration, Project, ProjectConfiguration, ProjectIntegrationListEntry, ProjectRef, SupportedIntegrations } from "@vertesia/common";
|
|
3
3
|
|
|
4
4
|
export default class ProjectsApi extends ApiTopic {
|
|
5
5
|
constructor(parent: ClientBase) {
|
|
@@ -26,6 +26,12 @@ export default class ProjectsApi extends ApiTopic {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
updateConfiguration(projectId: string, payload: Partial<ProjectConfiguration>): Promise<ProjectConfiguration> {
|
|
30
|
+
return this.put(`/${projectId}/configuration`, {
|
|
31
|
+
payload
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
integrations: IntegrationsConfigurationApi = new IntegrationsConfigurationApi(this);
|
|
30
36
|
|
|
31
37
|
}
|
package/src/RunsApi.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExecutionResponse } from "@llumiverse/common";
|
|
1
2
|
import { ApiTopic, ClientBase } from "@vertesia/api-fetch-client";
|
|
2
3
|
import {
|
|
3
4
|
CheckpointConversationPayload,
|
|
@@ -5,6 +6,7 @@ import {
|
|
|
5
6
|
ExecutionRun,
|
|
6
7
|
ExecutionRunRef,
|
|
7
8
|
FindPayload,
|
|
9
|
+
PopulatedExecutionRun,
|
|
8
10
|
RunCreatePayload,
|
|
9
11
|
RunListingFilters,
|
|
10
12
|
RunListingQueryOptions,
|
|
@@ -13,7 +15,6 @@ import {
|
|
|
13
15
|
UserMessagePayload,
|
|
14
16
|
} from "@vertesia/common";
|
|
15
17
|
import { VertesiaClient } from "./client.js";
|
|
16
|
-
import type { ExecutionResponse } from "@llumiverse/common";
|
|
17
18
|
import { EnhancedExecutionRun, enhanceExecutionRun } from "./InteractionOutput.js";
|
|
18
19
|
|
|
19
20
|
export interface FilterOption {
|
|
@@ -69,6 +70,12 @@ export class RunsApi extends ApiTopic {
|
|
|
69
70
|
return enhanceExecutionRun<ResultT, ParamsT>(r);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
retrievePopulated<P = any>(id: string): Promise<PopulatedExecutionRun<P>> {
|
|
74
|
+
return this.get("/" + id, {
|
|
75
|
+
query: { populate: "true" },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
/**
|
|
73
80
|
* Get filter options for a field
|
|
74
81
|
* return FilterOption[]
|
package/src/client.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ describe('Test Vertesia Client', () => {
|
|
|
6
6
|
const client = new VertesiaClient({
|
|
7
7
|
serverUrl: 'https://api.vertesia.io',
|
|
8
8
|
storeUrl: 'https://api.vertesia.io',
|
|
9
|
+
tokenServerUrl: 'https://sts.vertesia.io',
|
|
9
10
|
apikey: '1234',
|
|
10
11
|
});
|
|
11
12
|
expect(client).toBeDefined();
|
|
@@ -73,6 +74,7 @@ describe('Test Vertesia Client', () => {
|
|
|
73
74
|
const client = new VertesiaClient({
|
|
74
75
|
serverUrl: 'http://localhost:8091',
|
|
75
76
|
storeUrl: 'http://localhost:8092',
|
|
77
|
+
tokenServerUrl: 'http://localhost:8093',
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
expect(client).toBeDefined();
|
package/src/client.ts
CHANGED
|
@@ -37,18 +37,21 @@ export type VertesiaClientProps = {
|
|
|
37
37
|
* @default api.vertesia.io
|
|
38
38
|
* @since 0.52.0
|
|
39
39
|
*/
|
|
40
|
-
site?:
|
|
40
|
+
site?:
|
|
41
|
+
| "api.vertesia.io"
|
|
42
|
+
| "api-preview.vertesia.io"
|
|
43
|
+
| "api-staging.vertesia.io";
|
|
41
44
|
serverUrl?: string;
|
|
42
45
|
storeUrl?: string;
|
|
46
|
+
tokenServerUrl?: string;
|
|
43
47
|
apikey?: string;
|
|
44
48
|
projectId?: string;
|
|
45
49
|
sessionTags?: string | string[];
|
|
46
50
|
onRequest?: (request: Request) => void;
|
|
47
51
|
onResponse?: (response: Response) => void;
|
|
48
|
-
}
|
|
52
|
+
};
|
|
49
53
|
|
|
50
54
|
export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
51
|
-
|
|
52
55
|
/**
|
|
53
56
|
* The JWT token linked to the API KEY (sk or pk)
|
|
54
57
|
*/
|
|
@@ -64,11 +67,15 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
64
67
|
*/
|
|
65
68
|
sessionTags?: string | string[];
|
|
66
69
|
|
|
70
|
+
/**
|
|
71
|
+
* tokenServerUrl
|
|
72
|
+
*/
|
|
73
|
+
tokenServerUrl: string;
|
|
67
74
|
|
|
68
75
|
/**
|
|
69
|
-
* Create a client from the given token.
|
|
76
|
+
* Create a client from the given token.
|
|
70
77
|
* If you already have the decoded token you can pass it as the second argument to avoid decodinf it again.
|
|
71
|
-
*
|
|
78
|
+
*
|
|
72
79
|
* @param token the raw JWT token
|
|
73
80
|
* @param payload the decoded JWT token as an AuthTokenPayload - optional
|
|
74
81
|
*/
|
|
@@ -76,21 +83,21 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
76
83
|
if (!payload) {
|
|
77
84
|
payload = decodeJWT(token);
|
|
78
85
|
}
|
|
79
|
-
|
|
86
|
+
|
|
87
|
+
const endpoints = decodeEndpoints(payload.endpoints);
|
|
80
88
|
return await new VertesiaClient({
|
|
81
89
|
serverUrl: endpoints.studio,
|
|
82
|
-
storeUrl: endpoints.store
|
|
90
|
+
storeUrl: endpoints.store,
|
|
91
|
+
tokenServerUrl: payload.iss,
|
|
83
92
|
}).withApiKey(token);
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
static decodeEndpoints() {
|
|
87
|
-
|
|
88
|
-
}
|
|
95
|
+
static decodeEndpoints() {}
|
|
89
96
|
|
|
90
97
|
constructor(
|
|
91
98
|
opts: VertesiaClientProps = {
|
|
92
|
-
site:
|
|
93
|
-
}
|
|
99
|
+
site: "api.vertesia.io",
|
|
100
|
+
},
|
|
94
101
|
) {
|
|
95
102
|
let studioServerUrl: string;
|
|
96
103
|
let zenoServerUrl: string;
|
|
@@ -100,7 +107,9 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
100
107
|
} else if (opts.site) {
|
|
101
108
|
studioServerUrl = `https://${opts.site}`;
|
|
102
109
|
} else {
|
|
103
|
-
throw new Error(
|
|
110
|
+
throw new Error(
|
|
111
|
+
"Parameter 'site' or 'serverUrl' is required for VertesiaClient",
|
|
112
|
+
);
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
if (opts.storeUrl) {
|
|
@@ -108,25 +117,67 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
108
117
|
} else if (opts.site) {
|
|
109
118
|
zenoServerUrl = `https://${opts.site}`;
|
|
110
119
|
} else {
|
|
111
|
-
throw new Error(
|
|
120
|
+
throw new Error(
|
|
121
|
+
"Parameter 'site' or 'storeUrl' is required for VertesiaClient",
|
|
122
|
+
);
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
super(studioServerUrl);
|
|
115
126
|
|
|
127
|
+
if (opts.tokenServerUrl) {
|
|
128
|
+
this.tokenServerUrl = opts.tokenServerUrl;
|
|
129
|
+
} else if (opts.site) {
|
|
130
|
+
this.tokenServerUrl = `https://${opts.site.replace(/^api/, "sts")}`;
|
|
131
|
+
} else if (opts.serverUrl || opts.storeUrl) {
|
|
132
|
+
// Determine STS URL based on environment in serverUrl or storeUrl
|
|
133
|
+
const urlToCheck = opts.serverUrl || opts.storeUrl || "";
|
|
134
|
+
try {
|
|
135
|
+
const url = new URL(urlToCheck);
|
|
136
|
+
// Check for environment patterns
|
|
137
|
+
if (url.hostname.includes("-production.")) {
|
|
138
|
+
// zeno-server-production.api.vertesia.io -> sts.vertesia.io
|
|
139
|
+
this.tokenServerUrl = "https://sts.vertesia.io";
|
|
140
|
+
} else if (url.hostname.includes("-preview.")) {
|
|
141
|
+
// zeno-server-preview.api.vertesia.io -> sts-preview.vertesia.io
|
|
142
|
+
this.tokenServerUrl = "https://sts-preview.vertesia.io";
|
|
143
|
+
} else if (url.hostname === "api.vertesia.io") {
|
|
144
|
+
// api.vertesia.io -> sts.vertesia.io
|
|
145
|
+
this.tokenServerUrl = "https://sts.vertesia.io";
|
|
146
|
+
} else if (url.hostname === "api-preview.vertesia.io") {
|
|
147
|
+
// api-preview.vertesia.io -> sts-preview.vertesia.io
|
|
148
|
+
this.tokenServerUrl = "https://sts-preview.vertesia.io";
|
|
149
|
+
} else if (url.hostname === "api-staging.vertesia.io") {
|
|
150
|
+
// api-staging.vertesia.io -> sts-staging.vertesia.io
|
|
151
|
+
this.tokenServerUrl = "https://sts-staging.vertesia.io";
|
|
152
|
+
} else if (url.hostname.startsWith("api")) {
|
|
153
|
+
// Generic api.* pattern replacement
|
|
154
|
+
url.hostname = url.hostname.replace(/^api/, "sts");
|
|
155
|
+
this.tokenServerUrl = url.toString();
|
|
156
|
+
} else {
|
|
157
|
+
// Default to staging for everything else
|
|
158
|
+
this.tokenServerUrl = "https://sts-staging.vertesia.io";
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Default to staging if URL parsing fails
|
|
162
|
+
this.tokenServerUrl = "https://sts-staging.vertesia.io";
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Default to staging if no URL provided
|
|
166
|
+
this.tokenServerUrl = "https://sts-staging.vertesia.io";
|
|
167
|
+
}
|
|
168
|
+
|
|
116
169
|
this.store = new ZenoClient({
|
|
117
170
|
serverUrl: zenoServerUrl,
|
|
171
|
+
tokenServerUrl: this.tokenServerUrl,
|
|
118
172
|
apikey: opts.apikey,
|
|
119
173
|
onRequest: opts.onRequest,
|
|
120
|
-
onResponse: opts.onResponse
|
|
174
|
+
onResponse: opts.onResponse,
|
|
121
175
|
});
|
|
122
176
|
|
|
123
177
|
if (opts.apikey) {
|
|
124
178
|
this.withApiKey(opts.apikey);
|
|
125
179
|
}
|
|
126
|
-
|
|
127
|
-
if (opts.projectId) {
|
|
128
|
-
this.headers["x-project-id"] = opts.projectId;
|
|
129
|
-
}
|
|
180
|
+
|
|
130
181
|
this.onRequest = opts.onRequest;
|
|
131
182
|
this.onResponse = opts.onResponse;
|
|
132
183
|
this.sessionTags = opts.sessionTags;
|
|
@@ -153,25 +204,28 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
153
204
|
|
|
154
205
|
async withApiKey(apiKey: string | null) {
|
|
155
206
|
return this.withAuthCallback(
|
|
156
|
-
apiKey
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
207
|
+
apiKey
|
|
208
|
+
? async () => {
|
|
209
|
+
if (!isApiKey(apiKey)) {
|
|
210
|
+
return `Bearer ${apiKey}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (isTokenExpired(this._jwt)) {
|
|
214
|
+
const jwt = await this.getAuthToken(apiKey);
|
|
215
|
+
this._jwt = jwt.token;
|
|
216
|
+
}
|
|
217
|
+
return `Bearer ${this._jwt}`;
|
|
218
|
+
}
|
|
219
|
+
: undefined,
|
|
167
220
|
);
|
|
168
221
|
}
|
|
169
222
|
|
|
170
223
|
async getRawJWT() {
|
|
171
224
|
if (!this._jwt && this._auth) {
|
|
172
225
|
const auth = await this._auth();
|
|
173
|
-
if (!this._jwt) {
|
|
174
|
-
|
|
226
|
+
if (!this._jwt) {
|
|
227
|
+
// the _jwt may be set by the auth callback
|
|
228
|
+
this._jwt = auth.trim().split(" ")[1]; // remove Bearer prefix
|
|
175
229
|
}
|
|
176
230
|
}
|
|
177
231
|
return this._jwt || null;
|
|
@@ -221,21 +275,29 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
221
275
|
return this.store.baseUrl;
|
|
222
276
|
}
|
|
223
277
|
|
|
224
|
-
|
|
225
278
|
/**
|
|
226
279
|
*
|
|
227
|
-
* Generate a token for use with other
|
|
280
|
+
* Generate a token for use with other Vertesia's services
|
|
228
281
|
*
|
|
229
|
-
* @param accountId: selected account to generate the token for
|
|
230
282
|
* @returns AuthTokenResponse
|
|
231
283
|
*/
|
|
232
|
-
async getAuthToken(token?: string
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
284
|
+
async getAuthToken(token?: string): Promise<AuthTokenResponse> {
|
|
285
|
+
return fetch(`${this.tokenServerUrl}/token/issue`, {
|
|
286
|
+
method: "POST",
|
|
287
|
+
headers: {
|
|
288
|
+
"Content-Type": "application/json",
|
|
289
|
+
Authorization: `Bearer ${token}`,
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
.then((response) => response.json())
|
|
293
|
+
.then((data) => data as AuthTokenResponse)
|
|
294
|
+
.catch((error) => {
|
|
295
|
+
console.error(
|
|
296
|
+
`Error fetching token from ${this.tokenServerUrl}:`,
|
|
297
|
+
{ error },
|
|
298
|
+
);
|
|
299
|
+
throw error;
|
|
300
|
+
});
|
|
239
301
|
}
|
|
240
302
|
|
|
241
303
|
get initialHeaders() {
|
|
@@ -263,7 +325,7 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
263
325
|
}
|
|
264
326
|
|
|
265
327
|
function isApiKey(apiKey: string) {
|
|
266
|
-
return
|
|
328
|
+
return apiKey.startsWith("pk-") || apiKey.startsWith("sk-");
|
|
267
329
|
}
|
|
268
330
|
|
|
269
331
|
function isTokenExpired(token: string | null) {
|
|
@@ -274,38 +336,45 @@ function isTokenExpired(token: string | null) {
|
|
|
274
336
|
const decoded = decodeJWT(token);
|
|
275
337
|
const exp = decoded.exp;
|
|
276
338
|
const currentTime = Date.now();
|
|
277
|
-
return
|
|
339
|
+
return currentTime <= exp * 1000 - EXPIRATION_THRESHOLD;
|
|
278
340
|
}
|
|
279
341
|
|
|
280
342
|
export function decodeJWT(jwt: string): AuthTokenPayload {
|
|
281
|
-
const payloadBase64 = jwt.split(
|
|
343
|
+
const payloadBase64 = jwt.split(".")[1];
|
|
282
344
|
const decodedJson = base64UrlDecode(payloadBase64);
|
|
283
|
-
return JSON.parse(decodedJson)
|
|
345
|
+
return JSON.parse(decodedJson);
|
|
284
346
|
}
|
|
285
347
|
|
|
286
348
|
function base64UrlDecode(input: string): string {
|
|
287
349
|
// Convert base64url to base64
|
|
288
|
-
const base64 = input
|
|
350
|
+
const base64 = input
|
|
351
|
+
.replace(/-/g, "+")
|
|
352
|
+
.replace(/_/g, "/")
|
|
289
353
|
// Pad with '=' to make length a multiple of 4
|
|
290
|
-
.padEnd(Math.ceil(input.length / 4) * 4,
|
|
354
|
+
.padEnd(Math.ceil(input.length / 4) * 4, "=");
|
|
291
355
|
|
|
292
|
-
if (typeof Buffer !==
|
|
356
|
+
if (typeof Buffer !== "undefined") {
|
|
293
357
|
// Node.js
|
|
294
|
-
return Buffer.from(base64,
|
|
295
|
-
} else if (
|
|
358
|
+
return Buffer.from(base64, "base64").toString("utf-8");
|
|
359
|
+
} else if (
|
|
360
|
+
typeof atob !== "undefined" &&
|
|
361
|
+
typeof TextDecoder !== "undefined"
|
|
362
|
+
) {
|
|
296
363
|
// Browser
|
|
297
364
|
const binary = atob(base64);
|
|
298
|
-
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
|
365
|
+
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
299
366
|
// decode to utf8
|
|
300
367
|
return new TextDecoder().decode(bytes);
|
|
301
368
|
} else {
|
|
302
|
-
throw new Error(
|
|
369
|
+
throw new Error("No base64 decoder available");
|
|
303
370
|
}
|
|
304
371
|
}
|
|
305
372
|
|
|
306
|
-
export function decodeEndpoints(
|
|
373
|
+
export function decodeEndpoints(
|
|
374
|
+
endpoints: string | Record<string, string> | undefined,
|
|
375
|
+
): Record<string, string> {
|
|
307
376
|
if (!endpoints) {
|
|
308
|
-
return getEndpointsFromDomain("api.vertesia.io")
|
|
377
|
+
return getEndpointsFromDomain("api.vertesia.io");
|
|
309
378
|
}
|
|
310
379
|
if (typeof endpoints === "string") {
|
|
311
380
|
return getEndpointsFromDomain(endpoints);
|
|
@@ -319,12 +388,14 @@ function getEndpointsFromDomain(domain: string) {
|
|
|
319
388
|
return {
|
|
320
389
|
studio: `http://localhost:8091`,
|
|
321
390
|
store: `http://localhost:8092`,
|
|
322
|
-
|
|
391
|
+
token: process.env.STS_URL ?? "https://sts-staging.vertesia.io",
|
|
392
|
+
};
|
|
323
393
|
} else {
|
|
324
394
|
const url = `https://${domain}`;
|
|
325
395
|
return {
|
|
326
396
|
studio: url,
|
|
327
397
|
store: url,
|
|
328
|
-
|
|
398
|
+
token: url.replace("api", "sts"),
|
|
399
|
+
};
|
|
329
400
|
}
|
|
330
401
|
}
|
package/src/index.ts
CHANGED
|
@@ -128,7 +128,6 @@ export class CollectionsApi extends ApiTopic {
|
|
|
128
128
|
updatePermissions(collectionId: string, permissions: Record<string, string[]>): Promise<{
|
|
129
129
|
id: string;
|
|
130
130
|
security: Record<string, string[]>;
|
|
131
|
-
objectsUpdated: number;
|
|
132
131
|
}> {
|
|
133
132
|
return this.put(`/${collectionId}/permissions`, {
|
|
134
133
|
payload: permissions
|
|
@@ -145,9 +144,23 @@ export class CollectionsApi extends ApiTopic {
|
|
|
145
144
|
id: string;
|
|
146
145
|
message: string;
|
|
147
146
|
security?: Record<string, string[]>;
|
|
148
|
-
objectsUpdated: number;
|
|
149
147
|
}> {
|
|
150
148
|
return this.post(`/${collectionId}/propagate-permissions`);
|
|
151
149
|
}
|
|
152
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Manually trigger shared properties propagation from collection to member objects
|
|
153
|
+
* Useful for debugging and fixing shared properties issues
|
|
154
|
+
* @param collectionId - The collection ID
|
|
155
|
+
* @returns Object with collection id, message, and number of objects updated
|
|
156
|
+
*/
|
|
157
|
+
propagateSharedProperties(collectionId: string): Promise<{
|
|
158
|
+
id: string;
|
|
159
|
+
message: string;
|
|
160
|
+
shared_properties: string[]
|
|
161
|
+
}> {
|
|
162
|
+
return this.post(`/${collectionId}/propagate-shared-props`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
153
166
|
}
|
package/src/store/FilesApi.ts
CHANGED
|
@@ -59,12 +59,14 @@ export class FilesApi extends ApiTopic {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
// Strictly typed: provide either simple args or a full payload via a separate method
|
|
63
|
+
getDownloadUrl(file: string, name?: string, disposition?: "inline" | "attachment"): Promise<GetFileUrlResponse> {
|
|
64
|
+
const payload: GetFileUrlPayload = { file, name, disposition };
|
|
65
|
+
return this.post("/download-url", { payload });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getDownloadUrlWithOptions(payload: GetFileUrlPayload): Promise<GetFileUrlResponse> {
|
|
69
|
+
return this.post("/download-url", { payload });
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/**
|
|
@@ -120,9 +122,9 @@ export class FilesApi extends ApiTopic {
|
|
|
120
122
|
if (res.ok) {
|
|
121
123
|
return res;
|
|
122
124
|
} else if (res.status === 404) {
|
|
123
|
-
throw new Error(`File ${
|
|
125
|
+
throw new Error(`File at ${url} not found`); //TODO: type fetch error better with a fetch error class
|
|
124
126
|
} else if (res.status === 403) {
|
|
125
|
-
throw new Error(`File ${
|
|
127
|
+
throw new Error(`File at ${url} is forbidden`);
|
|
126
128
|
} else {
|
|
127
129
|
console.log(res);
|
|
128
130
|
throw new Error(
|
package/src/store/ObjectsApi.ts
CHANGED
|
@@ -27,24 +27,6 @@ import { StreamSource } from "../StreamSource.js";
|
|
|
27
27
|
import { AnalyzeDocApi } from "./AnalyzeDocApi.js";
|
|
28
28
|
import { ZenoClient } from "./client.js";
|
|
29
29
|
|
|
30
|
-
export interface UploadContentObjectPayload
|
|
31
|
-
extends Omit<CreateContentObjectPayload, "content"> {
|
|
32
|
-
content?:
|
|
33
|
-
| StreamSource
|
|
34
|
-
| File
|
|
35
|
-
| {
|
|
36
|
-
// the source URI
|
|
37
|
-
source: string;
|
|
38
|
-
// the original name of the input file if any
|
|
39
|
-
name?: string;
|
|
40
|
-
// the mime type of the content source.
|
|
41
|
-
type?: string;
|
|
42
|
-
|
|
43
|
-
// the target id in the content store
|
|
44
|
-
id?: string;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
30
|
export interface ComputeFacetsResponse {
|
|
49
31
|
type?: { _id: string; count: number }[];
|
|
50
32
|
location?: { _id: string; count: number }[];
|
|
@@ -72,14 +54,16 @@ export class ObjectsApi extends ApiTopic {
|
|
|
72
54
|
});
|
|
73
55
|
}
|
|
74
56
|
|
|
75
|
-
getDownloadUrl(fileUri: string): Promise<{ url: string }> {
|
|
57
|
+
getDownloadUrl(fileUri: string, name?: string, disposition?: "inline" | "attachment"): Promise<{ url: string }> {
|
|
76
58
|
return this.post("/download-url", {
|
|
77
|
-
payload: {
|
|
78
|
-
file: fileUri,
|
|
79
|
-
} satisfies GetFileUrlPayload,
|
|
59
|
+
payload: { file: fileUri, name, disposition } satisfies GetFileUrlPayload,
|
|
80
60
|
});
|
|
81
61
|
}
|
|
82
62
|
|
|
63
|
+
getDownloadUrlWithOptions(payload: GetFileUrlPayload): Promise<{ url: string }> {
|
|
64
|
+
return this.post("/download-url", { payload });
|
|
65
|
+
}
|
|
66
|
+
|
|
83
67
|
getContentSource(objectId: string): Promise<ContentSource> {
|
|
84
68
|
return this.get(`/${objectId}/content-source`);
|
|
85
69
|
}
|
|
@@ -217,7 +201,7 @@ export class ObjectsApi extends ApiTopic {
|
|
|
217
201
|
}
|
|
218
202
|
|
|
219
203
|
async create(
|
|
220
|
-
payload:
|
|
204
|
+
payload: CreateContentObjectPayload,
|
|
221
205
|
options?: {
|
|
222
206
|
collection_id?: string;
|
|
223
207
|
processing_priority?: ContentObjectProcessingPriority;
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
ListWorkflowInteractionsResponse,
|
|
11
11
|
ListWorkflowRunsPayload,
|
|
12
12
|
ListWorkflowRunsResponse,
|
|
13
|
+
WebSocketClientMessage,
|
|
14
|
+
WebSocketServerMessage,
|
|
13
15
|
WorkflowActionPayload,
|
|
14
16
|
WorkflowDefinitionRef,
|
|
15
17
|
WorkflowRule,
|
|
@@ -251,6 +253,170 @@ export class WorkflowsApi extends ApiTopic {
|
|
|
251
253
|
});
|
|
252
254
|
}
|
|
253
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Stream workflow messages via WebSocket (for mobile/React Native clients)
|
|
258
|
+
* @param workflowId The workflow ID
|
|
259
|
+
* @param runId The run ID
|
|
260
|
+
* @param onMessage Callback for incoming messages
|
|
261
|
+
* @param since Optional timestamp to resume from
|
|
262
|
+
* @returns Promise that resolves with cleanup function and sendSignal helper
|
|
263
|
+
*/
|
|
264
|
+
async streamMessagesWS(
|
|
265
|
+
workflowId: string,
|
|
266
|
+
runId: string,
|
|
267
|
+
onMessage?: (message: AgentMessage) => void,
|
|
268
|
+
since?: number
|
|
269
|
+
): Promise<{ cleanup: () => void; sendSignal: (signalName: string, data: any) => void }> {
|
|
270
|
+
return new Promise((resolve, reject) => {
|
|
271
|
+
let reconnectAttempts = 0;
|
|
272
|
+
const maxReconnectAttempts = 10;
|
|
273
|
+
const baseDelay = 1000;
|
|
274
|
+
const maxDelay = 30000;
|
|
275
|
+
let ws: WebSocket | null = null;
|
|
276
|
+
let lastMessageTimestamp = since || 0;
|
|
277
|
+
let isClosed = false;
|
|
278
|
+
|
|
279
|
+
const calculateBackoffDelay = (attempts: number): number => {
|
|
280
|
+
const exponentialDelay = Math.min(baseDelay * Math.pow(2, attempts), maxDelay);
|
|
281
|
+
const jitter = Math.random() * 0.1 * exponentialDelay;
|
|
282
|
+
return exponentialDelay + jitter;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const connect = async () => {
|
|
286
|
+
if (isClosed) return;
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const client = this.client as VertesiaClient;
|
|
290
|
+
const wsUrl = new URL(client.workflows.baseUrl + `/runs/${workflowId}/${runId}/ws`);
|
|
291
|
+
|
|
292
|
+
// Replace http/https with ws/wss
|
|
293
|
+
wsUrl.protocol = wsUrl.protocol.replace('http', 'ws');
|
|
294
|
+
|
|
295
|
+
// Add query parameters
|
|
296
|
+
if (lastMessageTimestamp > 0) {
|
|
297
|
+
wsUrl.searchParams.set('since', lastMessageTimestamp.toString());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const bearerToken = client._auth ? await client._auth() : undefined;
|
|
301
|
+
if (!bearerToken) {
|
|
302
|
+
reject(new Error('No auth token available'));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const token = bearerToken.split(' ')[1];
|
|
307
|
+
wsUrl.searchParams.set('access_token', token);
|
|
308
|
+
|
|
309
|
+
if (reconnectAttempts > 0) {
|
|
310
|
+
console.log(`Reconnecting to WebSocket for run ${runId} (attempt ${reconnectAttempts + 1}/${maxReconnectAttempts})`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
ws = new WebSocket(wsUrl.href);
|
|
314
|
+
|
|
315
|
+
ws.onopen = () => {
|
|
316
|
+
if (reconnectAttempts > 0) {
|
|
317
|
+
console.log(`Successfully reconnected to WebSocket for run ${runId}`);
|
|
318
|
+
}
|
|
319
|
+
reconnectAttempts = 0;
|
|
320
|
+
|
|
321
|
+
// Resolve with helpers on first successful connection
|
|
322
|
+
if (!isClosed) {
|
|
323
|
+
resolve({
|
|
324
|
+
cleanup: () => {
|
|
325
|
+
isClosed = true;
|
|
326
|
+
if (ws) {
|
|
327
|
+
ws.close();
|
|
328
|
+
ws = null;
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
sendSignal: (signalName: string, data: any) => {
|
|
332
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
333
|
+
const message: WebSocketClientMessage = {
|
|
334
|
+
type: 'signal',
|
|
335
|
+
signalName,
|
|
336
|
+
data,
|
|
337
|
+
requestId: Date.now()
|
|
338
|
+
};
|
|
339
|
+
ws.send(JSON.stringify(message));
|
|
340
|
+
} else {
|
|
341
|
+
console.warn('WebSocket not open, cannot send signal');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
ws.onmessage = (event: MessageEvent) => {
|
|
349
|
+
try {
|
|
350
|
+
const message = JSON.parse(event.data) as WebSocketServerMessage;
|
|
351
|
+
|
|
352
|
+
// Handle different message types
|
|
353
|
+
if ('workflow_run_id' in message) {
|
|
354
|
+
// This is an AgentMessage
|
|
355
|
+
const agentMessage = message as AgentMessage;
|
|
356
|
+
|
|
357
|
+
if (agentMessage.timestamp) {
|
|
358
|
+
lastMessageTimestamp = Math.max(lastMessageTimestamp, agentMessage.timestamp);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (onMessage) onMessage(agentMessage);
|
|
362
|
+
|
|
363
|
+
// Check for stream completion
|
|
364
|
+
const streamIsOver =
|
|
365
|
+
agentMessage.type === AgentMessageType.TERMINATED ||
|
|
366
|
+
(agentMessage.type === AgentMessageType.COMPLETE &&
|
|
367
|
+
(!agentMessage.workstream_id || agentMessage.workstream_id === 'main'));
|
|
368
|
+
|
|
369
|
+
if (streamIsOver) {
|
|
370
|
+
console.log('Closing WebSocket due to workflow completion');
|
|
371
|
+
isClosed = true;
|
|
372
|
+
if (ws) {
|
|
373
|
+
ws.close();
|
|
374
|
+
ws = null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else if (message.type === 'pong') {
|
|
378
|
+
// Heartbeat response
|
|
379
|
+
console.debug('Received pong');
|
|
380
|
+
} else if (message.type === 'ack') {
|
|
381
|
+
console.debug('Signal acknowledged', message);
|
|
382
|
+
} else if (message.type === 'error') {
|
|
383
|
+
console.error('WebSocket error message', message);
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.error('Failed to parse WebSocket message', err);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
ws.onerror = (err) => {
|
|
391
|
+
console.error('WebSocket error', err);
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
ws.onclose = () => {
|
|
395
|
+
if (!isClosed && reconnectAttempts < maxReconnectAttempts) {
|
|
396
|
+
const delay = calculateBackoffDelay(reconnectAttempts);
|
|
397
|
+
console.log(`WebSocket closed, reconnecting in ${delay}ms (attempt ${reconnectAttempts + 1}/${maxReconnectAttempts})`);
|
|
398
|
+
reconnectAttempts++;
|
|
399
|
+
setTimeout(connect, delay);
|
|
400
|
+
} else if (reconnectAttempts >= maxReconnectAttempts) {
|
|
401
|
+
reject(new Error(`WebSocket connection failed after ${maxReconnectAttempts} attempts`));
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
} catch (err) {
|
|
405
|
+
console.error('Error setting up WebSocket', err);
|
|
406
|
+
if (reconnectAttempts < maxReconnectAttempts) {
|
|
407
|
+
const delay = calculateBackoffDelay(reconnectAttempts);
|
|
408
|
+
reconnectAttempts++;
|
|
409
|
+
setTimeout(connect, delay);
|
|
410
|
+
} else {
|
|
411
|
+
reject(err);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
connect();
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
254
420
|
rules = new WorkflowsRulesApi(this);
|
|
255
421
|
definitions = new WorkflowsDefinitionApi(this);
|
|
256
422
|
}
|
package/src/store/client.ts
CHANGED