@vertesia/client 1.4.0-dev.20260615.042549Z → 1.4.0-dev.20260629.130134Z
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/README.md +29 -0
- package/lib/AppsApi.d.ts +5 -33
- package/lib/AppsApi.d.ts.map +1 -1
- package/lib/AppsApi.js +4 -63
- package/lib/AppsApi.js.map +1 -1
- package/lib/IamApi.d.ts +8 -1
- package/lib/IamApi.d.ts.map +1 -1
- package/lib/IamApi.js +9 -0
- package/lib/IamApi.js.map +1 -1
- package/lib/RemoteMcpConnectionsApi.d.ts +1 -0
- package/lib/RemoteMcpConnectionsApi.d.ts.map +1 -1
- package/lib/RemoteMcpConnectionsApi.js +5 -0
- package/lib/RemoteMcpConnectionsApi.js.map +1 -1
- package/lib/RunsApi.d.ts +4 -1
- package/lib/RunsApi.d.ts.map +1 -1
- package/lib/RunsApi.js +3 -1
- package/lib/RunsApi.js.map +1 -1
- package/lib/ToolsApi.d.ts +5 -3
- package/lib/ToolsApi.d.ts.map +1 -1
- package/lib/ToolsApi.js +7 -3
- package/lib/ToolsApi.js.map +1 -1
- package/lib/client.d.ts +7 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +22 -12
- package/lib/client.js.map +1 -1
- package/lib/execute.d.ts.map +1 -1
- package/lib/execute.js +17 -1
- package/lib/execute.js.map +1 -1
- package/lib/store/AgentsApi.d.ts +5 -1
- package/lib/store/AgentsApi.d.ts.map +1 -1
- package/lib/store/AgentsApi.js +26 -68
- package/lib/store/AgentsApi.js.map +1 -1
- package/lib/store/DataApi.d.ts +12 -1
- package/lib/store/DataApi.d.ts.map +1 -1
- package/lib/store/DataApi.js +13 -0
- package/lib/store/DataApi.js.map +1 -1
- package/lib/store/WorkflowsApi.d.ts.map +1 -1
- package/lib/store/WorkflowsApi.js +19 -7
- package/lib/store/WorkflowsApi.js.map +1 -1
- package/lib/store/client.d.ts +1 -2
- package/lib/store/client.d.ts.map +1 -1
- package/lib/store/client.js +3 -2
- package/lib/store/client.js.map +1 -1
- package/lib/vertesia-client.js +1 -1
- package/lib/vertesia-client.js.map +1 -1
- package/package.json +10 -9
- package/src/AppsApi.ts +4 -89
- package/src/IamApi.ts +11 -0
- package/src/RemoteMcpConnectionsApi.ts +7 -0
- package/src/RunsApi.ts +3 -0
- package/src/ToolsApi.ts +8 -3
- package/src/client.test.ts +6 -4
- package/src/client.ts +31 -13
- package/src/execute.ts +24 -5
- package/src/store/AgentsApi.ts +31 -64
- package/src/store/DataApi.ts +16 -0
- package/src/store/WorkflowsApi.ts +19 -7
- package/src/store/client.ts +4 -2
- package/src/store/stream-gap.test.ts +230 -0
- package/lib/store/ToolsApi.d.ts +0 -14
- package/lib/store/ToolsApi.d.ts.map +0 -1
- package/lib/store/ToolsApi.js +0 -17
- package/lib/store/ToolsApi.js.map +0 -1
- package/src/store/ToolsApi.ts +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertesia/client",
|
|
3
|
-
"version": "1.4.0-dev.
|
|
3
|
+
"version": "1.4.0-dev.20260629.130134Z",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
15
15
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
16
16
|
"@rollup/plugin-terser": "^1.0.0",
|
|
17
|
-
"@types/node": "^24.
|
|
18
|
-
"rollup": "^4.61.
|
|
17
|
+
"@types/node": "^24.13.2",
|
|
18
|
+
"rollup": "^4.61.1",
|
|
19
19
|
"typescript": "^6.0.3",
|
|
20
|
-
"vitest": "^4.1.
|
|
20
|
+
"vitest": "^4.1.9",
|
|
21
21
|
"@vertesia/tsconfig": "0.1.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"eventsource": "^4.1.0",
|
|
25
|
-
"@llumiverse/common": "1.
|
|
26
|
-
"@vertesia/
|
|
27
|
-
"@vertesia/
|
|
25
|
+
"@llumiverse/common": "1.4.0-dev.20260629.090753Z",
|
|
26
|
+
"@vertesia/common": "1.4.0-dev.20260629.130134Z",
|
|
27
|
+
"@vertesia/api-fetch-client": "1.4.0-dev.20260629.130134Z"
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
],
|
|
54
54
|
"scripts": {
|
|
55
55
|
"lint": "biome lint src",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
56
|
+
"clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
|
|
57
|
+
"build": "pnpm run clean:lib && tsc -p tsconfig.json && pnpm exec rollup -c",
|
|
58
|
+
"clean": "rimraf ./node_modules ./lib ./tsconfig.tsbuildinfo",
|
|
58
59
|
"test": "vitest run",
|
|
59
60
|
"typecheck:test": "tsc -p tsconfig.test.json --noEmit"
|
|
60
61
|
}
|
package/src/AppsApi.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { ApiTopic, type ClientBase, type ServerError } from '@vertesia/api-fetch-client';
|
|
2
2
|
import type {
|
|
3
|
-
ActivateAppVersionResponse,
|
|
4
|
-
AppBuildProgress,
|
|
5
|
-
AppDeleteSummary,
|
|
6
|
-
AppInspectionResult,
|
|
7
3
|
AppInstallation,
|
|
8
4
|
AppInstallationKind,
|
|
9
5
|
AppInstallationListEntry,
|
|
@@ -11,18 +7,11 @@ import type {
|
|
|
11
7
|
AppInstallationWithManifest,
|
|
12
8
|
AppManifest,
|
|
13
9
|
AppManifestData,
|
|
14
|
-
AppPackage,
|
|
15
|
-
AppPackageScope,
|
|
16
10
|
AppToolCollection,
|
|
17
|
-
AppVersionListQuery,
|
|
18
|
-
AppVersionRecord,
|
|
19
11
|
CountResult,
|
|
20
12
|
ProjectRef,
|
|
21
13
|
RequireAtLeastOne,
|
|
22
|
-
StartAppBuildRequest,
|
|
23
|
-
StartAppBuildResponse,
|
|
24
14
|
UpdateAppInstallationToolAllowlistPayload,
|
|
25
|
-
UpsertAppVersionRequest,
|
|
26
15
|
ValidateUrlRequest,
|
|
27
16
|
ValidateUrlResponse,
|
|
28
17
|
} from '@vertesia/common';
|
|
@@ -45,55 +34,14 @@ export default class AppsApi extends ApiTopic {
|
|
|
45
34
|
}
|
|
46
35
|
|
|
47
36
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
37
|
+
* Delete an app manifest by id. The owning account must permit the operation.
|
|
38
|
+
* Note: also uninstall any existing installations of this manifest before deleting,
|
|
39
|
+
* otherwise the server may refuse or leave orphaned installation records.
|
|
50
40
|
*/
|
|
51
|
-
|
|
41
|
+
delete(id: string): Promise<CountResult> {
|
|
52
42
|
return this.del(`/${id}`);
|
|
53
43
|
}
|
|
54
44
|
|
|
55
|
-
/**
|
|
56
|
-
* Cascade-delete an app and everything attached to it (versions,
|
|
57
|
-
* installations + ACEs, git repo on the app-git server). Pass through
|
|
58
|
-
* the dry-run summary; confirm flag is required server-side.
|
|
59
|
-
*/
|
|
60
|
-
deleteApp(id: string): Promise<AppDeleteSummary> {
|
|
61
|
-
return this.del(`/${id}`, { query: { confirm: 'true' } });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
listVersions(query?: AppVersionListQuery): Promise<AppVersionRecord[]> {
|
|
65
|
-
return this.get('/versions', {
|
|
66
|
-
query: {
|
|
67
|
-
...(query?.app_id && { app_id: query.app_id }),
|
|
68
|
-
...(query?.kind && { kind: query.kind }),
|
|
69
|
-
...(query?.include_expired !== undefined && { include_expired: query.include_expired }),
|
|
70
|
-
...(query?.limit !== undefined && { limit: query.limit }),
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
upsertVersion(payload: UpsertAppVersionRequest): Promise<AppVersionRecord> {
|
|
76
|
-
return this.post('/versions', { payload });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
getVersion(recordId: string): Promise<AppVersionRecord> {
|
|
80
|
-
return this.get(`/versions/${recordId}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
activateVersion(recordId: string): Promise<ActivateAppVersionResponse> {
|
|
84
|
-
return this.post(`/versions/${recordId}/activate`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
startBuild(appIdOrRecordId: string, payload: StartAppBuildRequest): Promise<StartAppBuildResponse> {
|
|
88
|
-
return this.post(`/${encodeURIComponent(appIdOrRecordId)}/builds`, { payload });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
getBuildProgress(appIdOrRecordId: string, workflowId: string, runId: string): Promise<AppBuildProgress> {
|
|
92
|
-
return this.get(
|
|
93
|
-
`/${encodeURIComponent(appIdOrRecordId)}/builds/${encodeURIComponent(workflowId)}/${encodeURIComponent(runId)}/progress`,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
45
|
/**
|
|
98
46
|
* Get the list if tools provided by the given app.
|
|
99
47
|
* @param appId
|
|
@@ -103,39 +51,6 @@ export default class AppsApi extends ApiTopic {
|
|
|
103
51
|
return this.get(`/installations/${appInstallId}/tools`);
|
|
104
52
|
}
|
|
105
53
|
|
|
106
|
-
/**
|
|
107
|
-
* Inspect an app's registration: resolved manifest state, what the published
|
|
108
|
-
* package actually exposes per capability, endpoint reachability, install
|
|
109
|
-
* state, and diagnostics. Use this to verify what an app registers vs what it
|
|
110
|
-
* declares, instead of inferring it from failed object/import calls.
|
|
111
|
-
*/
|
|
112
|
-
inspect(appIdOrName: string): Promise<AppInspectionResult> {
|
|
113
|
-
return this.get(`/${appIdOrName}/inspect`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Get package capabilities exposed by an app installation.
|
|
118
|
-
*/
|
|
119
|
-
getAppInstallationPackage(
|
|
120
|
-
appInstallId: string,
|
|
121
|
-
scope: AppPackageScope | AppPackageScope[] = 'all',
|
|
122
|
-
): Promise<AppPackage> {
|
|
123
|
-
return this.get(`/installations/${appInstallId}/package`, {
|
|
124
|
-
query: {
|
|
125
|
-
scope: Array.isArray(scope) ? scope.join(',') : scope,
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Fetch the always-on system tools package served by studio-server.
|
|
132
|
-
* Tools and skills (`learn_*`) are returned on separate fields so UIs can
|
|
133
|
-
* render them distinctly. URLs are already resolved per deployment.
|
|
134
|
-
*/
|
|
135
|
-
getSystemToolsPackage(scope: string = 'tools'): Promise<AppPackage> {
|
|
136
|
-
return this.get('/studio-tools/package', { query: { scope } });
|
|
137
|
-
}
|
|
138
|
-
|
|
139
54
|
/**
|
|
140
55
|
* @param ids - ids to filter by
|
|
141
56
|
* @returns the app manifests but without the agent.tool property which can be big.
|
package/src/IamApi.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
DeleteByIdResult,
|
|
8
8
|
PrincipalIdentity,
|
|
9
9
|
RoleDefinition,
|
|
10
|
+
SystemRoleDefinition,
|
|
10
11
|
} from '@vertesia/common';
|
|
11
12
|
import { GroupsApi } from './GroupsApi.js';
|
|
12
13
|
|
|
@@ -42,9 +43,19 @@ export class RolesApi extends ApiTopic {
|
|
|
42
43
|
super(parent, '/roles');
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/** List every built-in role across all partitions. Permissions are loosely typed (`string[]`). */
|
|
45
47
|
list(): Promise<RoleDefinition[]> {
|
|
46
48
|
return this.get('/');
|
|
47
49
|
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List system-domain roles only. Permissions are tightly typed as `Permission[]` —
|
|
53
|
+
* suited for client-side permission gating (button visibility, route guards).
|
|
54
|
+
* ABAC roles are excluded and must be consumed via the JWT `content_security` claim.
|
|
55
|
+
*/
|
|
56
|
+
listSystem(): Promise<SystemRoleDefinition[]> {
|
|
57
|
+
return this.get('/system');
|
|
58
|
+
}
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
export class AcesApi extends ApiTopic {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
OAuthAuthorizeResponse,
|
|
8
8
|
OAuthAuthStatus,
|
|
9
9
|
OAuthMetadataResponse,
|
|
10
|
+
OAuthProviderExchangePayload,
|
|
10
11
|
} from '@vertesia/common';
|
|
11
12
|
|
|
12
13
|
export default class RemoteMcpConnectionsApi extends ApiTopic {
|
|
@@ -30,6 +31,12 @@ export default class RemoteMcpConnectionsApi extends ApiTopic {
|
|
|
30
31
|
return this.get(`/authorize/${appInstallId}/${collectionId}`);
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
exchange(code: string, state: string): Promise<McpOAuthConnectResponse> {
|
|
35
|
+
return this.post('/exchange', {
|
|
36
|
+
payload: { code, state } satisfies OAuthProviderExchangePayload,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
connect(appInstallId: string, collectionId: string): Promise<McpOAuthConnectResponse> {
|
|
34
41
|
return this.post(`/connect/${appInstallId}/${collectionId}`, {});
|
|
35
42
|
}
|
package/src/RunsApi.ts
CHANGED
|
@@ -90,6 +90,7 @@ export class RunsApi extends ApiTopic {
|
|
|
90
90
|
|
|
91
91
|
async create<ResultT = unknown, ParamsT = unknown>(
|
|
92
92
|
payload: RunCreatePayload,
|
|
93
|
+
options?: { timeoutMs?: number | false | null; signal?: AbortSignal },
|
|
93
94
|
): Promise<EnhancedExecutionRun<ResultT, ParamsT>> {
|
|
94
95
|
const sessionTags = (this.client as VertesiaClient).sessionTags;
|
|
95
96
|
if (sessionTags) {
|
|
@@ -103,6 +104,8 @@ export class RunsApi extends ApiTopic {
|
|
|
103
104
|
}
|
|
104
105
|
const r = await this.post<ExecutionRun<ParamsT>>('/', {
|
|
105
106
|
payload,
|
|
107
|
+
timeoutMs: options?.timeoutMs,
|
|
108
|
+
signal: options?.signal,
|
|
106
109
|
});
|
|
107
110
|
return enhanceExecutionRun<ResultT, ParamsT>(r);
|
|
108
111
|
}
|
package/src/ToolsApi.ts
CHANGED
|
@@ -13,9 +13,7 @@ import type {
|
|
|
13
13
|
* - `GET /api/v1/tools` — list every tool visible to the current principal across all sources
|
|
14
14
|
* - `POST /api/v1/tools/validate` — resolve a list of names against the same registry
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
* — that one is global and source-incomplete (no app/interaction tools). Use this client for anything
|
|
18
|
-
* project-aware.
|
|
16
|
+
* This is the canonical client for tool discovery and validation.
|
|
19
17
|
*/
|
|
20
18
|
export default class ToolsApi extends ApiTopic {
|
|
21
19
|
constructor(parent: ClientBase) {
|
|
@@ -42,6 +40,13 @@ export default class ToolsApi extends ApiTopic {
|
|
|
42
40
|
return this.get('/', { query: Object.keys(params).length > 0 ? params : undefined });
|
|
43
41
|
}
|
|
44
42
|
|
|
43
|
+
/**
|
|
44
|
+
* List only Vertesia-provided builtin tools and system skills.
|
|
45
|
+
*/
|
|
46
|
+
listBuiltins(): Promise<AggregatedTool[]> {
|
|
47
|
+
return this.list({ sources: ['builtin'] });
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
/**
|
|
46
51
|
* Validate a list of tool names. Each result reports validity, source, app provenance,
|
|
47
52
|
* and (for invalid names) a closest-match suggestion.
|
package/src/client.test.ts
CHANGED
|
@@ -59,7 +59,8 @@ describe('Test Vertesia Client', () => {
|
|
|
59
59
|
expect(client).toBeDefined();
|
|
60
60
|
expect(client.baseUrl).toBe('https://api-preview.vertesia.io');
|
|
61
61
|
expect(client.storeUrl).toBe('https://api-preview.vertesia.io');
|
|
62
|
-
|
|
62
|
+
// preview keeps its env segment: api-preview → sts-preview (its own STS)
|
|
63
|
+
expect(client.tokenServerUrl).toBe('https://sts-preview.vertesia.io');
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
test('Initialization with site api.dev1.vertesia.io', () => {
|
|
@@ -81,7 +82,8 @@ describe('Test Vertesia Client', () => {
|
|
|
81
82
|
expect(client).toBeDefined();
|
|
82
83
|
expect(client.baseUrl).toBe('https://api-preview.dev1.vertesia.io');
|
|
83
84
|
expect(client.storeUrl).toBe('https://api-preview.dev1.vertesia.io');
|
|
84
|
-
|
|
85
|
+
// preview keeps its env segment: api-preview → sts-preview (its own STS)
|
|
86
|
+
expect(client.tokenServerUrl).toBe('https://sts-preview.dev1.vertesia.io');
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
test('Initialization with regional serverUrl (api.us1)', () => {
|
|
@@ -116,8 +118,8 @@ describe('Test Vertesia Client', () => {
|
|
|
116
118
|
|
|
117
119
|
expect(client).toBeDefined();
|
|
118
120
|
expect(client.baseUrl).toBe('https://api-preview.us1.vertesia.io');
|
|
119
|
-
// preview
|
|
120
|
-
expect(client.tokenServerUrl).toBe('https://sts.us1.vertesia.io');
|
|
121
|
+
// preview keeps its env segment: api-preview → sts-preview (its own versioned STS)
|
|
122
|
+
expect(client.tokenServerUrl).toBe('https://sts-preview.us1.vertesia.io');
|
|
121
123
|
});
|
|
122
124
|
|
|
123
125
|
test('Initialization with site localhost', () => {
|
package/src/client.ts
CHANGED
|
@@ -65,6 +65,12 @@ export type VertesiaClientProps = {
|
|
|
65
65
|
onRequest?: (request: Request) => void;
|
|
66
66
|
onResponse?: (response: Response) => void;
|
|
67
67
|
retryPolicy?: IRequestRetryPolicy;
|
|
68
|
+
/**
|
|
69
|
+
* Default request timeout in ms applied to every request (studio + store) unless overridden
|
|
70
|
+
* per-request via `timeoutMs`. `false`/`null`/`0` disables it. Aborts the whole request
|
|
71
|
+
* (connection + headers + body) via AbortSignal — browser + Node safe.
|
|
72
|
+
*/
|
|
73
|
+
timeout?: number | false | null;
|
|
68
74
|
fetch?: FETCH_FN | Promise<FETCH_FN>;
|
|
69
75
|
};
|
|
70
76
|
|
|
@@ -107,7 +113,7 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
107
113
|
static async fromAuthToken(
|
|
108
114
|
token: string,
|
|
109
115
|
payload?: AuthTokenPayload,
|
|
110
|
-
endpoints?: { studio: string; store: string; token?: string
|
|
116
|
+
endpoints?: { studio: string; store: string; token?: string },
|
|
111
117
|
) {
|
|
112
118
|
if (!payload) {
|
|
113
119
|
payload = decodeJWT(token);
|
|
@@ -152,14 +158,17 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
152
158
|
if (opts.tokenServerUrl) {
|
|
153
159
|
this.tokenServerUrl = opts.tokenServerUrl;
|
|
154
160
|
} else if (opts.site) {
|
|
155
|
-
//
|
|
156
|
-
//
|
|
161
|
+
// Replace the leading api prefix with sts, preserving the env segment so each
|
|
162
|
+
// environment hits its own STS. preview/preprod run a separately versioned STS
|
|
163
|
+
// (sts-preview / sts-preprod); collapsing them onto the production sts.<region>
|
|
164
|
+
// can route to an older STS that rejects scopes the env-matched STS supports
|
|
165
|
+
// (e.g. `offline_access`).
|
|
157
166
|
// Examples:
|
|
158
|
-
// api.vertesia.io
|
|
159
|
-
// api-preview.vertesia.io
|
|
160
|
-
// api.us1.vertesia.io
|
|
161
|
-
// api-preview.eu1.vertesia.io -> sts.eu1.vertesia.io
|
|
162
|
-
const stsHost = opts.site.replace(
|
|
167
|
+
// api.vertesia.io -> sts.vertesia.io
|
|
168
|
+
// api-preview.vertesia.io -> sts-preview.vertesia.io
|
|
169
|
+
// api.us1.vertesia.io -> sts.us1.vertesia.io
|
|
170
|
+
// api-preview.eu1.vertesia.io -> sts-preview.eu1.vertesia.io
|
|
171
|
+
const stsHost = opts.site.replace(/^api/, 'sts');
|
|
163
172
|
this.tokenServerUrl = `https://${stsHost}`;
|
|
164
173
|
} else if (opts.serverUrl || opts.storeUrl) {
|
|
165
174
|
// Determine STS URL based on environment in serverUrl or storeUrl
|
|
@@ -167,11 +176,12 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
167
176
|
try {
|
|
168
177
|
const url = new URL(urlToCheck);
|
|
169
178
|
if (url.hostname.startsWith('api')) {
|
|
170
|
-
//
|
|
179
|
+
// Replace the leading api prefix with sts, preserving the env segment so
|
|
180
|
+
// preview/preprod hit their own STS rather than production's.
|
|
171
181
|
// api.us1.vertesia.io -> sts.us1.vertesia.io
|
|
172
|
-
// api-preview.us1.vertesia.io -> sts.us1.vertesia.io
|
|
182
|
+
// api-preview.us1.vertesia.io -> sts-preview.us1.vertesia.io
|
|
173
183
|
// api.vertesia.io -> sts.vertesia.io
|
|
174
|
-
const stsHost = url.hostname.replace(
|
|
184
|
+
const stsHost = url.hostname.replace(/^api/, 'sts');
|
|
175
185
|
this.tokenServerUrl = `https://${stsHost}`;
|
|
176
186
|
} else {
|
|
177
187
|
this.tokenServerUrl = 'https://sts.dev1.vertesia.io';
|
|
@@ -190,6 +200,7 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
190
200
|
onRequest: opts.onRequest,
|
|
191
201
|
onResponse: opts.onResponse,
|
|
192
202
|
retryPolicy: opts.retryPolicy,
|
|
203
|
+
timeout: opts.timeout,
|
|
193
204
|
fetch: opts.fetch,
|
|
194
205
|
});
|
|
195
206
|
|
|
@@ -197,6 +208,10 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
197
208
|
this.withRetryPolicy(opts.retryPolicy);
|
|
198
209
|
}
|
|
199
210
|
|
|
211
|
+
if (opts.timeout !== undefined) {
|
|
212
|
+
this.withTimeout(opts.timeout);
|
|
213
|
+
}
|
|
214
|
+
|
|
200
215
|
if (opts.apikey) {
|
|
201
216
|
void this.withApiKey(opts.apikey);
|
|
202
217
|
}
|
|
@@ -236,6 +251,11 @@ export class VertesiaClient extends AbstractFetchClient<VertesiaClient> {
|
|
|
236
251
|
return super.withRetryPolicy(policy);
|
|
237
252
|
}
|
|
238
253
|
|
|
254
|
+
withTimeout(timeoutMs?: number | false | null) {
|
|
255
|
+
this.store.withTimeout(timeoutMs);
|
|
256
|
+
return super.withTimeout(timeoutMs);
|
|
257
|
+
}
|
|
258
|
+
|
|
239
259
|
async withApiKey(apiKey: string | null) {
|
|
240
260
|
return this.withAuthCallback(
|
|
241
261
|
apiKey
|
|
@@ -471,7 +491,6 @@ function getEndpointsFromDomain(domain: string) {
|
|
|
471
491
|
studio: `http://localhost:8091`,
|
|
472
492
|
store: `http://localhost:8092`,
|
|
473
493
|
token: getRuntimeStsUrl() ?? 'https://sts.dev1.vertesia.io',
|
|
474
|
-
git: 'https://git.dev1.vertesia.io',
|
|
475
494
|
};
|
|
476
495
|
} else {
|
|
477
496
|
const url = `https://${domain}`;
|
|
@@ -481,7 +500,6 @@ function getEndpointsFromDomain(domain: string) {
|
|
|
481
500
|
studio: url,
|
|
482
501
|
store: url,
|
|
483
502
|
token: url.replace('api', 'sts'),
|
|
484
|
-
git: url.replace('api', 'git'),
|
|
485
503
|
};
|
|
486
504
|
}
|
|
487
505
|
}
|
package/src/execute.ts
CHANGED
|
@@ -10,6 +10,18 @@ import {
|
|
|
10
10
|
} from '@vertesia/common';
|
|
11
11
|
import type { VertesiaClient } from './client.js';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Client-side timeout for a synchronous (non-streaming) interaction execution. The request blocks on
|
|
15
|
+
* studio-server until the model finishes — and the LLM (e.g. image generation) can legitimately take
|
|
16
|
+
* minutes — so the per-request timeout here is long, overriding any short client default. Override
|
|
17
|
+
* via the VERTESIA_INTERACTION_TIMEOUT_MS env var.
|
|
18
|
+
*/
|
|
19
|
+
const INTERACTION_EXECUTION_TIMEOUT_MS = (() => {
|
|
20
|
+
const raw = typeof process !== 'undefined' ? process.env?.VERTESIA_INTERACTION_TIMEOUT_MS : undefined;
|
|
21
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
22
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 30 * 60 * 1000; // 30 minutes
|
|
23
|
+
})();
|
|
24
|
+
|
|
13
25
|
export async function EventSourceProvider(): Promise<typeof EventSource> {
|
|
14
26
|
if (typeof globalThis.EventSource === 'function') {
|
|
15
27
|
return globalThis.EventSource;
|
|
@@ -35,11 +47,16 @@ export async function executeInteraction<P = unknown>(
|
|
|
35
47
|
onChunk?: (chunk: string) => void,
|
|
36
48
|
): Promise<InteractionExecutionResult<P>> {
|
|
37
49
|
const stream = !!onChunk;
|
|
38
|
-
const response = (await client.runs.create(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
const response = (await client.runs.create(
|
|
51
|
+
{
|
|
52
|
+
...payload,
|
|
53
|
+
interaction: interactionId,
|
|
54
|
+
stream,
|
|
55
|
+
},
|
|
56
|
+
// Synchronous interaction execution blocks on the model; keep a long client timeout.
|
|
57
|
+
// (Streaming returns the run immediately, then streams over a separate channel — see below.)
|
|
58
|
+
{ timeoutMs: stream ? undefined : INTERACTION_EXECUTION_TIMEOUT_MS },
|
|
59
|
+
)) as unknown as InteractionExecutionResult<P>;
|
|
43
60
|
if (stream) {
|
|
44
61
|
if (response.status === ExecutionRunStatus.failed) {
|
|
45
62
|
return response;
|
|
@@ -78,6 +95,8 @@ export async function executeInteractionByName<P = unknown>(
|
|
|
78
95
|
interaction,
|
|
79
96
|
stream,
|
|
80
97
|
} as NamedInteractionExecutionPayload,
|
|
98
|
+
// Synchronous interaction execution blocks on the model; keep a long client timeout.
|
|
99
|
+
timeoutMs: stream ? undefined : INTERACTION_EXECUTION_TIMEOUT_MS,
|
|
81
100
|
});
|
|
82
101
|
if (stream) {
|
|
83
102
|
if (response.status === ExecutionRunStatus.failed) {
|
package/src/store/AgentsApi.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
type AgentRunResponse,
|
|
11
11
|
type AgentRunUpdatesResponse,
|
|
12
12
|
type BindRunWorkflowPayload,
|
|
13
|
-
type CompactMessage,
|
|
14
13
|
type CreateAgentRunPayload,
|
|
15
14
|
type CreateProcessRunPayload,
|
|
16
15
|
type ErrorAnalyticsResponse,
|
|
@@ -55,6 +54,11 @@ import { EventSourceProvider } from '../execute.js';
|
|
|
55
54
|
import { fetchSignedUrl } from './signed-url.js';
|
|
56
55
|
import { shouldCloseAgentRunStream, shouldCloseCompactRunStream } from './stream-termination.js';
|
|
57
56
|
|
|
57
|
+
export interface AgentRunStreamMessagesOptions {
|
|
58
|
+
onHistoryLoaded?: (messages: AgentMessage[]) => void;
|
|
59
|
+
onHistoryError?: (error: unknown) => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
export class AgentsApi extends ApiTopic {
|
|
59
63
|
constructor(parent: ClientBase) {
|
|
60
64
|
super(parent, '/api/v1/agents');
|
|
@@ -278,7 +282,7 @@ export class AgentsApi extends ApiTopic {
|
|
|
278
282
|
async retrieveMessages(id: string, since?: number): Promise<AgentMessage[]> {
|
|
279
283
|
const query = since ? { since } : undefined;
|
|
280
284
|
const response = (await this.get(`/${id}/updates`, { query })) as AgentRunUpdatesResponse;
|
|
281
|
-
return response.messages.map((m
|
|
285
|
+
return response.messages.map((m) => toAgentMessage(parseMessage(m), id));
|
|
282
286
|
}
|
|
283
287
|
|
|
284
288
|
/**
|
|
@@ -303,6 +307,7 @@ export class AgentsApi extends ApiTopic {
|
|
|
303
307
|
onMessage?: (message: AgentMessage, exitFn?: (payload: unknown) => void) => void,
|
|
304
308
|
since?: number,
|
|
305
309
|
signal?: AbortSignal,
|
|
310
|
+
options?: AgentRunStreamMessagesOptions,
|
|
306
311
|
): Promise<unknown> {
|
|
307
312
|
let resolveFn: (value: unknown) => void = () => {};
|
|
308
313
|
let rejectFn: (reason?: unknown) => void = () => {};
|
|
@@ -313,6 +318,7 @@ export class AgentsApi extends ApiTopic {
|
|
|
313
318
|
|
|
314
319
|
let reconnectAttempts = 0;
|
|
315
320
|
let lastMessageTimestamp = since || 0;
|
|
321
|
+
const historyFetchStartedAt = Date.now();
|
|
316
322
|
let isClosed = false;
|
|
317
323
|
let currentSse: EventSource | null = null;
|
|
318
324
|
let interval: ReturnType<typeof setInterval> | null = null;
|
|
@@ -356,61 +362,6 @@ export class AgentsApi extends ApiTopic {
|
|
|
356
362
|
}
|
|
357
363
|
};
|
|
358
364
|
|
|
359
|
-
// SSE reconnection exhausted. The run is durable and still executing
|
|
360
|
-
// server-side — only the live view dropped. Failing fatally here aborts
|
|
361
|
-
// headless consumers (CLI `agents stream`, agent test harnesses) before
|
|
362
|
-
// they can read the result. Instead degrade gracefully: poll GET /updates
|
|
363
|
-
// for any messages we missed and exit cleanly once the run is terminal,
|
|
364
|
-
// even if the terminal message never streamed.
|
|
365
|
-
const POLL_INTERVAL_MS = 5000;
|
|
366
|
-
const isTerminalStatus = (status: unknown): boolean =>
|
|
367
|
-
typeof status === 'string' && ['completed', 'failed', 'cancelled'].includes(status);
|
|
368
|
-
const degradeToPolling = (reason: unknown) => {
|
|
369
|
-
if (isClosed) return;
|
|
370
|
-
cleanup();
|
|
371
|
-
console.warn(
|
|
372
|
-
`Agent stream ${id}: SSE unavailable after ${maxReconnectAttempts} attempts; falling back to /updates polling.`,
|
|
373
|
-
reason instanceof Error ? reason.message : reason,
|
|
374
|
-
);
|
|
375
|
-
const tick = async () => {
|
|
376
|
-
if (isClosed) return;
|
|
377
|
-
try {
|
|
378
|
-
const recent = await this.retrieveMessages(id, lastMessageTimestamp || undefined);
|
|
379
|
-
for (const msg of recent) {
|
|
380
|
-
if (isClosed) return;
|
|
381
|
-
if ((msg.timestamp || 0) <= lastMessageTimestamp) continue;
|
|
382
|
-
lastMessageTimestamp = msg.timestamp || lastMessageTimestamp;
|
|
383
|
-
if (onMessage) onMessage(msg, exit);
|
|
384
|
-
if (isClosed) return;
|
|
385
|
-
if (shouldCloseAgentRunStream(msg, id)) {
|
|
386
|
-
exit(null);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
} catch {
|
|
391
|
-
// transient poll error — retry next tick
|
|
392
|
-
}
|
|
393
|
-
if (isClosed) return;
|
|
394
|
-
// Exit even when no terminal message ever streams, by reading the run record.
|
|
395
|
-
try {
|
|
396
|
-
const run = (await this.get(`/${id}`)) as { status?: unknown };
|
|
397
|
-
if (isTerminalStatus(run?.status)) {
|
|
398
|
-
exit(null);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
} catch {
|
|
402
|
-
// ignore — retry next tick
|
|
403
|
-
}
|
|
404
|
-
if (!isClosed) {
|
|
405
|
-
reconnectTimer = setTimeout(() => {
|
|
406
|
-
reconnectTimer = null;
|
|
407
|
-
void tick();
|
|
408
|
-
}, POLL_INTERVAL_MS);
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
void tick();
|
|
412
|
-
};
|
|
413
|
-
|
|
414
365
|
if (signal) {
|
|
415
366
|
if (signal.aborted) {
|
|
416
367
|
isClosed = true;
|
|
@@ -428,23 +379,35 @@ export class AgentsApi extends ApiTopic {
|
|
|
428
379
|
try {
|
|
429
380
|
if (!isClosed) {
|
|
430
381
|
const historical = await this.retrieveMessages(id, since);
|
|
431
|
-
|
|
382
|
+
options?.onHistoryLoaded?.(historical);
|
|
383
|
+
let shouldCloseAfterHistory = false;
|
|
384
|
+
for (let index = 0; index < historical.length; index++) {
|
|
385
|
+
const msg = historical[index];
|
|
432
386
|
if (isClosed) break;
|
|
433
387
|
lastMessageTimestamp = Math.max(lastMessageTimestamp, msg.timestamp || 0);
|
|
434
388
|
if (onMessage) onMessage(msg, exit);
|
|
435
389
|
if (isClosed) break;
|
|
436
390
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
391
|
+
shouldCloseAfterHistory = index === historical.length - 1 && shouldCloseAgentRunStream(msg, id);
|
|
392
|
+
}
|
|
393
|
+
if (shouldCloseAfterHistory) {
|
|
394
|
+
exit(null);
|
|
395
|
+
return promise;
|
|
441
396
|
}
|
|
442
397
|
}
|
|
443
398
|
} catch (err) {
|
|
444
399
|
if (!isClosed) {
|
|
400
|
+
options?.onHistoryError?.(err);
|
|
445
401
|
console.warn('Failed to fetch historical messages, continuing with SSE:', err);
|
|
446
402
|
}
|
|
447
403
|
}
|
|
404
|
+
if (!isClosed && lastMessageTimestamp <= 0) {
|
|
405
|
+
// The server only replays the GET-to-SSE handoff gap when `since > 0`.
|
|
406
|
+
// New runs often have no history yet, so use the GET start time as the
|
|
407
|
+
// cursor to avoid dropping messages emitted before the SSE subscription
|
|
408
|
+
// is active.
|
|
409
|
+
lastMessageTimestamp = Math.max(1, historyFetchStartedAt - 1);
|
|
410
|
+
}
|
|
448
411
|
|
|
449
412
|
// 2. Connect to SSE for real-time updates
|
|
450
413
|
const setupStream = async (isReconnect: boolean = false) => {
|
|
@@ -536,7 +499,9 @@ export class AgentsApi extends ApiTopic {
|
|
|
536
499
|
if (!isClosed) void setupStream(true);
|
|
537
500
|
}, delay);
|
|
538
501
|
} else {
|
|
539
|
-
|
|
502
|
+
isClosed = true;
|
|
503
|
+
cleanup();
|
|
504
|
+
rejectFn(
|
|
540
505
|
new Error(`SSE connection failed after ${maxReconnectAttempts} reconnection attempts`),
|
|
541
506
|
);
|
|
542
507
|
}
|
|
@@ -551,7 +516,9 @@ export class AgentsApi extends ApiTopic {
|
|
|
551
516
|
if (!isClosed) void setupStream(true);
|
|
552
517
|
}, delay);
|
|
553
518
|
} else {
|
|
554
|
-
|
|
519
|
+
isClosed = true;
|
|
520
|
+
cleanup();
|
|
521
|
+
rejectFn(err);
|
|
555
522
|
}
|
|
556
523
|
}
|
|
557
524
|
};
|
package/src/store/DataApi.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
type DataStoreArchiveResult,
|
|
13
13
|
type DataStoreDownloadInfo,
|
|
14
14
|
type DataStoreItem,
|
|
15
|
+
type DataStoreMutateRowsPayload,
|
|
16
|
+
type DataStoreMutateRowsResult,
|
|
15
17
|
type DataStoreTableDetail,
|
|
16
18
|
type DataStoreTableDropResult,
|
|
17
19
|
type DataStoreVersion,
|
|
@@ -393,6 +395,20 @@ export class DataApi extends ApiTopic {
|
|
|
393
395
|
return this.post(`/${id}/query/batch`, { payload: { queries }, headers: this.storeHeaders(id) });
|
|
394
396
|
}
|
|
395
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Execute a single row mutation statement against the data store.
|
|
400
|
+
*
|
|
401
|
+
* Only UPDATE and DELETE statements are accepted. The mutation is versioned
|
|
402
|
+
* and rolled back automatically if the statement or subsequent persistence fails.
|
|
403
|
+
*
|
|
404
|
+
* @param id - Data store ID
|
|
405
|
+
* @param payload - Mutation SQL and commit message
|
|
406
|
+
* @returns Resulting version ID and affected table row counts
|
|
407
|
+
*/
|
|
408
|
+
mutateRows(id: string, payload: DataStoreMutateRowsPayload): Promise<DataStoreMutateRowsResult> {
|
|
409
|
+
return this.post(`/${id}/mutate`, { payload, headers: this.storeHeaders(id) });
|
|
410
|
+
}
|
|
411
|
+
|
|
396
412
|
/**
|
|
397
413
|
* Validate SQL queries without executing them.
|
|
398
414
|
*
|