@lovable.dev/sdk 0.1.10 → 1.1.0
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 +71 -40
- package/dist/index.d.ts +5281 -9468
- package/dist/index.js +737 -419
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +4174 -0
- package/dist/schemas.js +2031 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +27 -2
- package/src/client.ts +1597 -0
- package/src/generated/paths.ts +7743 -0
- package/src/generated/zod/zod.gen.ts +2018 -0
- package/src/index.ts +93 -0
- package/src/retryFetch.ts +44 -0
- package/src/schemas.ts +22 -0
- package/src/types.ts +493 -0
package/dist/index.js
CHANGED
|
@@ -1,23 +1,55 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
2
|
import createClient from "openapi-fetch";
|
|
3
3
|
|
|
4
|
+
// src/retryFetch.ts
|
|
5
|
+
var RETRY_DELAYS_MS = [100, 300, 500];
|
|
6
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
function parseRetryAfterMs(headers) {
|
|
8
|
+
const raw = headers.get("retry-after");
|
|
9
|
+
if (!raw) return void 0;
|
|
10
|
+
const seconds = Number(raw);
|
|
11
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
12
|
+
const dateMs = Date.parse(raw);
|
|
13
|
+
if (!Number.isNaN(dateMs)) {
|
|
14
|
+
const delta = dateMs - Date.now();
|
|
15
|
+
return delta > 0 ? delta : 0;
|
|
16
|
+
}
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
function makeRetryFetch(baseFetch = (input) => globalThis.fetch(input)) {
|
|
20
|
+
return async (input) => {
|
|
21
|
+
const clone = () => input.clone();
|
|
22
|
+
let response = await baseFetch(clone());
|
|
23
|
+
for (const planned of RETRY_DELAYS_MS) {
|
|
24
|
+
if (response.status !== 429) break;
|
|
25
|
+
const serverDelay = parseRetryAfterMs(response.headers);
|
|
26
|
+
await sleep(Math.max(planned, serverDelay ?? 0));
|
|
27
|
+
response = await baseFetch(clone());
|
|
28
|
+
}
|
|
29
|
+
return response;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
4
33
|
// src/types.ts
|
|
5
34
|
var ApiError = class extends Error {
|
|
6
35
|
status;
|
|
7
36
|
type;
|
|
8
37
|
detail;
|
|
9
38
|
props;
|
|
10
|
-
|
|
39
|
+
rateLimit;
|
|
40
|
+
constructor(status, message, type, detail, props, rateLimit) {
|
|
11
41
|
super(message);
|
|
12
42
|
this.status = status;
|
|
13
43
|
this.type = type;
|
|
14
44
|
this.detail = detail;
|
|
15
45
|
this.props = props;
|
|
46
|
+
this.rateLimit = rateLimit;
|
|
16
47
|
}
|
|
17
48
|
};
|
|
18
49
|
|
|
19
50
|
// src/client.ts
|
|
20
51
|
var DEFAULT_BASE_URL = "https://api.lovable.dev";
|
|
52
|
+
var PUBLIC_API_CURSOR_VERSION = 1;
|
|
21
53
|
function normalizeBaseUrl(url) {
|
|
22
54
|
if (!url) return DEFAULT_BASE_URL;
|
|
23
55
|
const normalized = url.replace(/\/$/, "");
|
|
@@ -26,6 +58,25 @@ function normalizeBaseUrl(url) {
|
|
|
26
58
|
}
|
|
27
59
|
return normalized;
|
|
28
60
|
}
|
|
61
|
+
function encodePublicCursor(id) {
|
|
62
|
+
const raw = JSON.stringify({ v: PUBLIC_API_CURSOR_VERSION, id });
|
|
63
|
+
const bytes = new TextEncoder().encode(raw);
|
|
64
|
+
let binary = "";
|
|
65
|
+
for (const byte of bytes) {
|
|
66
|
+
binary += String.fromCharCode(byte);
|
|
67
|
+
}
|
|
68
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
69
|
+
}
|
|
70
|
+
function normalizeWorkspaceList(body) {
|
|
71
|
+
return body.data ?? body.workspaces ?? [];
|
|
72
|
+
}
|
|
73
|
+
function cursorHasMore(body) {
|
|
74
|
+
return body.pagination?.has_more ?? body.has_more ?? false;
|
|
75
|
+
}
|
|
76
|
+
function requireCreateProjectId(project) {
|
|
77
|
+
if (!project.id) throw new Error("Create project response missing project ID");
|
|
78
|
+
return project;
|
|
79
|
+
}
|
|
29
80
|
var errorMiddleware = {
|
|
30
81
|
async onResponse({ response }) {
|
|
31
82
|
if (response.ok) return;
|
|
@@ -37,9 +88,32 @@ var errorMiddleware = {
|
|
|
37
88
|
const message = buildErrorMessage(errorBody, response.status, response.statusText);
|
|
38
89
|
const type = errorBody?.type ?? errorBody?.title;
|
|
39
90
|
const detail = errorBody?.detail ?? errorBody?.details;
|
|
40
|
-
throw new ApiError(response.status, message, type, detail, errorBody?.props);
|
|
91
|
+
throw new ApiError(response.status, message, type, detail, errorBody?.props, parseRateLimitInfo(response.headers));
|
|
41
92
|
}
|
|
42
93
|
};
|
|
94
|
+
function parseRateLimitInfo(headers) {
|
|
95
|
+
const limit = parsePositiveInt(headers.get("x-ratelimit-limit"));
|
|
96
|
+
const remaining = parsePositiveInt(headers.get("x-ratelimit-remaining"));
|
|
97
|
+
const retryAfterMs = parseRetryAfterMs2(headers.get("retry-after"));
|
|
98
|
+
if (limit == null && remaining == null && retryAfterMs == null) return void 0;
|
|
99
|
+
return { limit, remaining, retryAfterMs };
|
|
100
|
+
}
|
|
101
|
+
function parsePositiveInt(raw) {
|
|
102
|
+
if (raw == null) return void 0;
|
|
103
|
+
const n = Number(raw);
|
|
104
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
105
|
+
}
|
|
106
|
+
function parseRetryAfterMs2(raw) {
|
|
107
|
+
if (!raw) return void 0;
|
|
108
|
+
const seconds = Number(raw);
|
|
109
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
110
|
+
const dateMs = Date.parse(raw);
|
|
111
|
+
if (!Number.isNaN(dateMs)) {
|
|
112
|
+
const delta = dateMs - Date.now();
|
|
113
|
+
return delta > 0 ? delta : 0;
|
|
114
|
+
}
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
43
117
|
function buildErrorMessage(body, status, statusText) {
|
|
44
118
|
return body?.message || body?.title || (statusText ? `HTTP ${status}: ${statusText}` : `HTTP ${status}`);
|
|
45
119
|
}
|
|
@@ -69,7 +143,8 @@ var LovableClient = class {
|
|
|
69
143
|
...this.authHeaders,
|
|
70
144
|
...this.extraHeaders,
|
|
71
145
|
Accept: "application/json"
|
|
72
|
-
}
|
|
146
|
+
},
|
|
147
|
+
fetch: makeRetryFetch()
|
|
73
148
|
});
|
|
74
149
|
this.typedClient.use(errorMiddleware);
|
|
75
150
|
}
|
|
@@ -83,112 +158,98 @@ var LovableClient = class {
|
|
|
83
158
|
get typed() {
|
|
84
159
|
return this.typedClient;
|
|
85
160
|
}
|
|
86
|
-
async rawRequest(method, path, body, init) {
|
|
87
|
-
const url = `${this.baseUrl}${path}`;
|
|
88
|
-
const headers = {
|
|
89
|
-
"X-Client-Source": this.clientSource,
|
|
90
|
-
...init?.headers,
|
|
91
|
-
...this.authHeaders,
|
|
92
|
-
...this.extraHeaders,
|
|
93
|
-
Accept: "application/json"
|
|
94
|
-
};
|
|
95
|
-
if (body !== void 0) {
|
|
96
|
-
headers["Content-Type"] = "application/json";
|
|
97
|
-
}
|
|
98
|
-
const response = await fetch(url, {
|
|
99
|
-
method,
|
|
100
|
-
headers,
|
|
101
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
102
|
-
signal: init?.signal
|
|
103
|
-
});
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
let errorBody;
|
|
106
|
-
try {
|
|
107
|
-
errorBody = await response.json();
|
|
108
|
-
} catch {
|
|
109
|
-
}
|
|
110
|
-
const message = buildErrorMessage(errorBody, response.status, response.statusText);
|
|
111
|
-
const type = errorBody?.type ?? errorBody?.title;
|
|
112
|
-
const detail = errorBody?.detail ?? errorBody?.details;
|
|
113
|
-
throw new ApiError(response.status, message, type, detail, errorBody?.props);
|
|
114
|
-
}
|
|
115
|
-
return response;
|
|
116
|
-
}
|
|
117
|
-
async request(method, path, body) {
|
|
118
|
-
const response = await this.rawRequest(method, path, body);
|
|
119
|
-
if (response.status === 204) {
|
|
120
|
-
return void 0;
|
|
121
|
-
}
|
|
122
|
-
return response.json();
|
|
123
|
-
}
|
|
124
|
-
async requestText(method, path) {
|
|
125
|
-
const response = await this.rawRequest(method, path);
|
|
126
|
-
return response.text();
|
|
127
|
-
}
|
|
128
161
|
/**
|
|
129
162
|
* Get the current authenticated user and their workspaces.
|
|
130
163
|
* Useful for validating an API key and discovering workspace IDs.
|
|
131
164
|
*/
|
|
132
165
|
async me() {
|
|
133
166
|
const { data } = await this.typed.GET("/v1/me");
|
|
134
|
-
return {
|
|
167
|
+
return {
|
|
168
|
+
...data,
|
|
169
|
+
workspaces: normalizeWorkspaceList(
|
|
170
|
+
data
|
|
171
|
+
)
|
|
172
|
+
};
|
|
135
173
|
}
|
|
136
174
|
/**
|
|
137
|
-
* List
|
|
175
|
+
* List workspaces the authenticated user has access to.
|
|
138
176
|
*/
|
|
139
|
-
async listWorkspaces() {
|
|
140
|
-
const
|
|
141
|
-
|
|
177
|
+
async listWorkspaces(options = {}) {
|
|
178
|
+
const { data } = await this.typed.GET("/v1/workspaces", {
|
|
179
|
+
params: { query: options }
|
|
180
|
+
});
|
|
181
|
+
return { ...data, workspaces: normalizeWorkspaceList(data) };
|
|
142
182
|
}
|
|
143
183
|
/**
|
|
144
184
|
* Get a specific workspace by ID
|
|
145
185
|
*/
|
|
146
186
|
async getWorkspace(workspaceId) {
|
|
147
|
-
const
|
|
148
|
-
|
|
187
|
+
const { data } = await this.typed.GET("/v1/workspaces/{workspace_id}", {
|
|
188
|
+
params: { path: { workspace_id: workspaceId } }
|
|
189
|
+
});
|
|
190
|
+
return data.workspace;
|
|
149
191
|
}
|
|
150
192
|
/**
|
|
151
193
|
* List projects in a workspace.
|
|
152
194
|
* Supports full-text search, filtering by visibility/publish status/folder/creator,
|
|
153
|
-
* and pagination
|
|
195
|
+
* and cursor pagination.
|
|
154
196
|
*/
|
|
155
197
|
async listProjects(workspaceId, options) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
const { data } = await this.typed.GET("/v1/projects", {
|
|
199
|
+
params: {
|
|
200
|
+
query: {
|
|
201
|
+
workspace_id: workspaceId,
|
|
202
|
+
q: options?.query,
|
|
203
|
+
visibility: options?.visibility,
|
|
204
|
+
publish_status: options?.publish_status,
|
|
205
|
+
folder_id: options?.folder_id,
|
|
206
|
+
folder_ids: options?.folder_ids,
|
|
207
|
+
user_id: options?.user_id,
|
|
208
|
+
type: options?.type,
|
|
209
|
+
include_risk: options?.include_risk,
|
|
210
|
+
search_fields: options?.search_fields,
|
|
211
|
+
viewed_by_me: options?.viewed_by_me,
|
|
212
|
+
cursor: options?.cursor,
|
|
213
|
+
limit: options?.limit
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
const projects = data.projects ?? data.data ?? null;
|
|
218
|
+
const total = data.total;
|
|
219
|
+
return {
|
|
220
|
+
...data,
|
|
221
|
+
projects,
|
|
222
|
+
...total === void 0 ? {} : { total },
|
|
223
|
+
has_more: data.pagination?.has_more ?? data.has_more
|
|
224
|
+
};
|
|
171
225
|
}
|
|
172
226
|
/**
|
|
173
227
|
* Create a new project in a workspace
|
|
174
228
|
*/
|
|
175
229
|
async createProject(workspaceId, options) {
|
|
176
230
|
let fileRefs;
|
|
231
|
+
let ephemeralFileRefs;
|
|
177
232
|
if (options.uploadedFiles?.length) {
|
|
178
|
-
fileRefs = options.uploadedFiles;
|
|
233
|
+
({ fileRefs, ephemeralFileRefs } = this.partitionUploadedFiles(options.uploadedFiles));
|
|
179
234
|
} else if (options.files?.length) {
|
|
180
|
-
|
|
235
|
+
ephemeralFileRefs = await this.uploadEphemeralFiles(options.files);
|
|
181
236
|
}
|
|
182
237
|
const body = {
|
|
183
238
|
description: options.description,
|
|
184
239
|
template_project_id: options.templateProjectId
|
|
185
240
|
};
|
|
241
|
+
if (options.projectName) {
|
|
242
|
+
body.display_name = options.projectName;
|
|
243
|
+
}
|
|
186
244
|
if (options.visibility) {
|
|
187
245
|
body.visibility = options.visibility;
|
|
188
246
|
}
|
|
189
247
|
if (options.techStack) {
|
|
190
248
|
body.tech_stack = options.techStack;
|
|
191
249
|
}
|
|
250
|
+
if (options.sandboxTemplate) {
|
|
251
|
+
body.sandbox_template = options.sandboxTemplate;
|
|
252
|
+
}
|
|
192
253
|
if (options.selectedLibraries?.length) {
|
|
193
254
|
body.selected_libraries = options.selectedLibraries;
|
|
194
255
|
}
|
|
@@ -198,73 +259,101 @@ var LovableClient = class {
|
|
|
198
259
|
if (fileRefs?.length) {
|
|
199
260
|
body.files = fileRefs;
|
|
200
261
|
}
|
|
201
|
-
|
|
202
|
-
|
|
262
|
+
if (options.fileUrls?.length) {
|
|
263
|
+
body.file_urls = options.fileUrls;
|
|
264
|
+
}
|
|
265
|
+
if (ephemeralFileRefs?.length) {
|
|
266
|
+
body.ephemeral_files = ephemeralFileRefs;
|
|
267
|
+
}
|
|
268
|
+
const { data } = await this.typed.POST("/v1/projects", {
|
|
269
|
+
body: { ...body, workspace_id: workspaceId }
|
|
270
|
+
});
|
|
271
|
+
return requireCreateProjectId(data);
|
|
203
272
|
}
|
|
204
273
|
/**
|
|
205
274
|
* Send a chat message to a project.
|
|
206
275
|
*
|
|
207
276
|
* The API accepts the message and processes it in the background.
|
|
208
277
|
* Returns the message ID and status. Use `waitForMessageCompletion()`
|
|
209
|
-
* to poll for the AI response
|
|
278
|
+
* to poll for the AI response.
|
|
210
279
|
*/
|
|
211
280
|
async chat(projectId, options) {
|
|
212
281
|
let fileRefs;
|
|
282
|
+
let ephemeralFileRefs;
|
|
213
283
|
if (options.uploadedFiles?.length) {
|
|
214
|
-
fileRefs = options.uploadedFiles;
|
|
284
|
+
({ fileRefs, ephemeralFileRefs } = this.partitionUploadedFiles(options.uploadedFiles));
|
|
215
285
|
} else if (options.files?.length) {
|
|
216
|
-
fileRefs = await this.
|
|
286
|
+
fileRefs = await this.uploadProjectFiles(projectId, options.files);
|
|
217
287
|
}
|
|
218
288
|
const body = {
|
|
219
289
|
message: options.message
|
|
220
290
|
};
|
|
291
|
+
if (options.variantId) {
|
|
292
|
+
body.variant_id = options.variantId;
|
|
293
|
+
}
|
|
221
294
|
if (fileRefs) {
|
|
222
295
|
body.files = fileRefs;
|
|
223
296
|
}
|
|
297
|
+
if (ephemeralFileRefs) {
|
|
298
|
+
body.ephemeral_files = ephemeralFileRefs;
|
|
299
|
+
}
|
|
224
300
|
if (options.planMode) {
|
|
225
301
|
body.plan_mode = true;
|
|
226
302
|
}
|
|
227
|
-
if (options.customModel) {
|
|
228
|
-
body.custom_model_endpoint = options.customModel.endpoint;
|
|
229
|
-
body.custom_model_api_key = options.customModel.apiKey;
|
|
230
|
-
body.custom_model_name = options.customModel.modelName;
|
|
231
|
-
}
|
|
232
|
-
if (options.customModelDisableRace) {
|
|
233
|
-
body.custom_model_disable_race = true;
|
|
234
|
-
}
|
|
235
303
|
if (options.continuation) {
|
|
236
304
|
body.continuation = options.continuation;
|
|
237
305
|
}
|
|
238
|
-
|
|
306
|
+
const { data } = await this.typed.POST("/v1/messages", {
|
|
307
|
+
body: { ...body, project_id: projectId }
|
|
308
|
+
});
|
|
309
|
+
return data;
|
|
239
310
|
}
|
|
240
311
|
/**
|
|
241
|
-
*
|
|
312
|
+
* Create an independent variant from the project's current main branch, or from a full baseSha when provided.
|
|
242
313
|
*/
|
|
243
|
-
async
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
248
|
-
return
|
|
314
|
+
async createVariant(projectId, options = {}) {
|
|
315
|
+
const { data } = await this.typed.POST("/v1/projects/{project_id}/variants", {
|
|
316
|
+
params: { path: { project_id: projectId } },
|
|
317
|
+
body: { label: options.label, base_sha: options.baseSha }
|
|
318
|
+
});
|
|
319
|
+
return data;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get project details by ID
|
|
323
|
+
*/
|
|
324
|
+
async getProject(projectId) {
|
|
325
|
+
const { data } = await this.typed.GET("/v1/projects/{project_id}", {
|
|
326
|
+
params: { path: { project_id: projectId } }
|
|
327
|
+
});
|
|
328
|
+
return data;
|
|
249
329
|
}
|
|
250
330
|
/**
|
|
251
|
-
*
|
|
331
|
+
* Create an anonymous, one-hour static preview URL for one exact HTTPS parent origin.
|
|
252
332
|
*/
|
|
253
|
-
async
|
|
254
|
-
const
|
|
255
|
-
|
|
333
|
+
async createEmbedUrl(projectId, parentOrigin) {
|
|
334
|
+
const { data } = await this.typed.POST("/v1/projects/{project_id}/embed-url", {
|
|
335
|
+
params: { path: { project_id: projectId } },
|
|
336
|
+
body: { parent_origin: parentOrigin }
|
|
337
|
+
});
|
|
338
|
+
return data;
|
|
256
339
|
}
|
|
257
340
|
/**
|
|
258
|
-
*
|
|
341
|
+
* Update supported project fields.
|
|
259
342
|
*/
|
|
260
|
-
async
|
|
261
|
-
await this.
|
|
343
|
+
async updateProject(projectId, options) {
|
|
344
|
+
const { data } = await this.typed.PATCH("/v1/projects/{project_id}", {
|
|
345
|
+
params: { path: { project_id: projectId } },
|
|
346
|
+
body: options
|
|
347
|
+
});
|
|
348
|
+
return data;
|
|
262
349
|
}
|
|
263
350
|
/**
|
|
264
|
-
*
|
|
351
|
+
* Soft-delete a project. Repeated deletes are treated as successful.
|
|
265
352
|
*/
|
|
266
|
-
async
|
|
267
|
-
|
|
353
|
+
async deleteProject(projectId) {
|
|
354
|
+
await this.typed.DELETE("/v1/projects/{project_id}", {
|
|
355
|
+
params: { path: { project_id: projectId } }
|
|
356
|
+
});
|
|
268
357
|
}
|
|
269
358
|
/**
|
|
270
359
|
* Get the preview URL for a project.
|
|
@@ -297,7 +386,10 @@ var LovableClient = class {
|
|
|
297
386
|
* @returns Whether the database is enabled and which stack is used
|
|
298
387
|
*/
|
|
299
388
|
async getDatabaseStatus(projectId) {
|
|
300
|
-
|
|
389
|
+
const { data } = await this.typed.GET("/v1/database", {
|
|
390
|
+
params: { query: { project_id: projectId } }
|
|
391
|
+
});
|
|
392
|
+
return data;
|
|
301
393
|
}
|
|
302
394
|
/**
|
|
303
395
|
* Enable (provision) a cloud database for a project.
|
|
@@ -309,7 +401,10 @@ var LovableClient = class {
|
|
|
309
401
|
* @returns The database status after enablement
|
|
310
402
|
*/
|
|
311
403
|
async enableDatabase(projectId) {
|
|
312
|
-
|
|
404
|
+
const { data } = await this.typed.POST("/v1/database/enable", {
|
|
405
|
+
body: { project_id: projectId }
|
|
406
|
+
});
|
|
407
|
+
return data;
|
|
313
408
|
}
|
|
314
409
|
/**
|
|
315
410
|
* Execute a SQL query against the project's cloud database.
|
|
@@ -322,111 +417,211 @@ var LovableClient = class {
|
|
|
322
417
|
* @returns Query result rows as JSON objects
|
|
323
418
|
*/
|
|
324
419
|
async queryDatabase(projectId, sql) {
|
|
325
|
-
|
|
420
|
+
const compat = this.typedClient;
|
|
421
|
+
const { data } = await compat.POST("/v1/database/query", {
|
|
422
|
+
body: { project_id: projectId, sql }
|
|
423
|
+
});
|
|
424
|
+
return data;
|
|
326
425
|
}
|
|
327
426
|
// ---------------------------------------------------------------------------
|
|
328
427
|
// Messages
|
|
329
428
|
// ---------------------------------------------------------------------------
|
|
330
429
|
/**
|
|
331
430
|
* Get a message by ID. Returns the message content, status, and (for user messages)
|
|
332
|
-
* the AI response if available.
|
|
431
|
+
* the AI response if available.
|
|
432
|
+
*
|
|
433
|
+
* Pass `waitSeconds` to long-poll: the server holds the request until the
|
|
434
|
+
* message reaches a terminal state (completed / stopped / error / awaiting_input) or the
|
|
435
|
+
* duration elapses. This replaces client-side polling for `waitForMessageCompletion`.
|
|
333
436
|
*/
|
|
334
|
-
async getMessage(projectId, messageId) {
|
|
335
|
-
|
|
437
|
+
async getMessage(projectId, messageId, options) {
|
|
438
|
+
const waitSeconds = options?.waitSeconds;
|
|
439
|
+
const query = {
|
|
440
|
+
wait: waitSeconds && waitSeconds > 0 ? `${Math.floor(waitSeconds)}s` : void 0,
|
|
441
|
+
thread_id: options?.threadId
|
|
442
|
+
};
|
|
443
|
+
const { data } = await this.typed.GET("/v1/messages/{message_id}", {
|
|
444
|
+
params: { path: { message_id: messageId }, query: { ...query, project_id: projectId } }
|
|
445
|
+
});
|
|
446
|
+
return data;
|
|
336
447
|
}
|
|
337
448
|
/**
|
|
338
|
-
* List recent messages in a project, newest first. Use `
|
|
339
|
-
*
|
|
449
|
+
* List recent messages in a project, newest first. Use `cursor` from the
|
|
450
|
+
* previous page's `pagination.next_cursor` to paginate through history.
|
|
340
451
|
*/
|
|
341
452
|
async listMessages(projectId, params) {
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
453
|
+
const cursor = params?.cursor ?? (params?.before ? encodePublicCursor(params.before) : void 0);
|
|
454
|
+
const { data } = await this.typed.GET("/v1/messages", {
|
|
455
|
+
params: {
|
|
456
|
+
query: { project_id: projectId, limit: params?.limit, cursor }
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
return {
|
|
460
|
+
...data,
|
|
461
|
+
messages: data.data,
|
|
462
|
+
has_more: cursorHasMore(data)
|
|
463
|
+
};
|
|
347
464
|
}
|
|
348
465
|
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
466
|
+
* Wait for the AI response to reach a terminal status (completed, stopped, error, awaiting_input)
|
|
467
|
+
* or for `timeout` to elapse.
|
|
351
468
|
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
469
|
+
* Primary path is SSE against `/v1/messages/{message_id}/stream`: one
|
|
470
|
+
* held connection that pushes a snapshot on every relevant change and closes
|
|
471
|
+
* on terminal. If SSE isn't reachable (proxy strips text/event-stream, server
|
|
472
|
+
* returns 404 / 415 / 501) we fall back to the long-poll JSON endpoint on the
|
|
473
|
+
* same URL. Both paths share the same `MessageCompletionResult` shape.
|
|
354
474
|
*/
|
|
355
475
|
async waitForMessageCompletion(projectId, messageId, options) {
|
|
356
|
-
const pollInterval = options?.pollInterval ?? 3e3;
|
|
357
476
|
const timeout = options?.timeout ?? 6e5;
|
|
358
477
|
const deadline = Date.now() + timeout;
|
|
478
|
+
const sse = await this.waitForMessageCompletionViaSSE(projectId, messageId, deadline, options?.threadId);
|
|
479
|
+
if (sse.kind === "result") {
|
|
480
|
+
return sse.result;
|
|
481
|
+
}
|
|
482
|
+
if (Date.now() >= deadline) {
|
|
483
|
+
return timeoutResult(messageId, timeout);
|
|
484
|
+
}
|
|
485
|
+
return this.waitForMessageCompletionViaLongPoll(projectId, messageId, deadline, timeout, options);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* @deprecated Use `chat()` or `createProject()`'s returned `message_id`,
|
|
489
|
+
* then call `waitForMessageCompletion(projectId, messageId)`.
|
|
490
|
+
*
|
|
491
|
+
* Throws when the turn pauses for human input (`awaiting_input`) — the
|
|
492
|
+
* legacy `ChatResponse` shape cannot carry resume metadata. HITL-capable
|
|
493
|
+
* flows need `waitForMessageCompletion` plus `respondToTool`.
|
|
494
|
+
*/
|
|
495
|
+
async waitForResponse(projectId, options) {
|
|
496
|
+
const messages = await this.listMessages(projectId, { limit: 10 });
|
|
497
|
+
const latest = messages.messages?.find((message) => message.role === "user");
|
|
498
|
+
if (!latest?.message_id) {
|
|
499
|
+
throw new Error(`No messages found for project ${projectId}`);
|
|
500
|
+
}
|
|
501
|
+
const completionOptions = options?.timeout === void 0 ? void 0 : { timeout: options.timeout };
|
|
502
|
+
const result = await this.waitForMessageCompletion(projectId, latest.message_id, completionOptions);
|
|
503
|
+
if (result.status !== "completed" && result.status !== "stopped") {
|
|
504
|
+
throw new Error(result.error ?? `Message ${latest.message_id} did not complete (status: ${result.status})`);
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
content: result.content,
|
|
508
|
+
messageId: result.message_id,
|
|
509
|
+
previewUrl: this.getPreviewUrl(projectId)
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
async waitForMessageCompletionViaSSE(projectId, messageId, deadline, threadId) {
|
|
513
|
+
const remaining = deadline - Date.now();
|
|
514
|
+
if (remaining <= 0) return { kind: "timeout" };
|
|
515
|
+
const url = new URL(`${this.baseUrl}/v1/messages/${encodeURIComponent(messageId)}/stream`);
|
|
516
|
+
url.searchParams.set("project_id", projectId);
|
|
517
|
+
if (threadId) {
|
|
518
|
+
url.searchParams.set("thread_id", threadId);
|
|
519
|
+
}
|
|
520
|
+
const controller = new AbortController();
|
|
521
|
+
const timeoutId = setTimeout(() => controller.abort(), remaining);
|
|
522
|
+
let response;
|
|
523
|
+
try {
|
|
524
|
+
response = await fetch(url, {
|
|
525
|
+
headers: {
|
|
526
|
+
...this.authHeaders,
|
|
527
|
+
...this.extraHeaders,
|
|
528
|
+
"X-Client-Source": this.clientSource,
|
|
529
|
+
Accept: "text/event-stream"
|
|
530
|
+
},
|
|
531
|
+
signal: controller.signal
|
|
532
|
+
});
|
|
533
|
+
} catch (err) {
|
|
534
|
+
clearTimeout(timeoutId);
|
|
535
|
+
if (isAbortError(err)) return { kind: "timeout" };
|
|
536
|
+
return { kind: "fallback" };
|
|
537
|
+
}
|
|
538
|
+
if (!response.ok) {
|
|
539
|
+
clearTimeout(timeoutId);
|
|
540
|
+
if (response.status === 404 || response.status === 415 || response.status === 501) {
|
|
541
|
+
await cancelResponseBody(response);
|
|
542
|
+
return { kind: "fallback" };
|
|
543
|
+
}
|
|
544
|
+
if (response.status >= 500) {
|
|
545
|
+
await cancelResponseBody(response);
|
|
546
|
+
return { kind: "fallback" };
|
|
547
|
+
}
|
|
548
|
+
const detail = await safeReadText(response);
|
|
549
|
+
throw new ApiError(response.status, detail || `HTTP ${response.status}`);
|
|
550
|
+
}
|
|
551
|
+
if (!response.body) {
|
|
552
|
+
clearTimeout(timeoutId);
|
|
553
|
+
return { kind: "fallback" };
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
for await (const frame of parseSSEFrames(response.body)) {
|
|
557
|
+
if (!frame.data) continue;
|
|
558
|
+
let snapshot;
|
|
559
|
+
try {
|
|
560
|
+
snapshot = JSON.parse(frame.data);
|
|
561
|
+
} catch {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const queuedResult = queuedExitResult(snapshot, messageId);
|
|
565
|
+
if (queuedResult) return { kind: "result", result: queuedResult };
|
|
566
|
+
const terminal = terminalResultFromMessage(snapshot, messageId);
|
|
567
|
+
if (terminal) {
|
|
568
|
+
return { kind: "result", result: terminal };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return { kind: "fallback" };
|
|
572
|
+
} catch (err) {
|
|
573
|
+
if (isAbortError(err)) return { kind: "timeout" };
|
|
574
|
+
return { kind: "fallback" };
|
|
575
|
+
} finally {
|
|
576
|
+
clearTimeout(timeoutId);
|
|
577
|
+
controller.abort();
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async waitForMessageCompletionViaLongPoll(projectId, messageId, deadline, totalTimeoutMs, options) {
|
|
581
|
+
const waitSeconds = Math.max(1, Math.min(55, options?.waitSeconds ?? 30));
|
|
582
|
+
const transientBackoffMs = 1e3;
|
|
359
583
|
let notFoundSince = null;
|
|
360
584
|
const notFoundGraceMs = 15e3;
|
|
361
585
|
while (Date.now() < deadline) {
|
|
586
|
+
const remainingMs = deadline - Date.now();
|
|
587
|
+
const perCallSeconds = Math.min(waitSeconds, Math.floor(remainingMs / 1e3));
|
|
588
|
+
const waitOptions = perCallSeconds > 0 ? { waitSeconds: perCallSeconds } : void 0;
|
|
589
|
+
const callStartedAt = Date.now();
|
|
362
590
|
try {
|
|
363
|
-
const msg = await this.getMessage(projectId, messageId);
|
|
591
|
+
const msg = await this.getMessage(projectId, messageId, { ...waitOptions, threadId: options?.threadId });
|
|
364
592
|
notFoundSince = null;
|
|
365
|
-
|
|
366
|
-
|
|
593
|
+
const queuedResult = queuedExitResult(msg, messageId);
|
|
594
|
+
if (queuedResult) return queuedResult;
|
|
595
|
+
const terminal = terminalResultFromMessage(msg, messageId);
|
|
596
|
+
if (terminal) return terminal;
|
|
597
|
+
if (Date.now() - callStartedAt < transientBackoffMs) {
|
|
598
|
+
await sleep2(Math.min(transientBackoffMs, Math.max(0, deadline - Date.now())));
|
|
599
|
+
}
|
|
600
|
+
} catch (err) {
|
|
601
|
+
if (err instanceof ApiError) {
|
|
602
|
+
if (err.status === 404) {
|
|
603
|
+
if (notFoundSince === null) {
|
|
604
|
+
notFoundSince = Date.now();
|
|
605
|
+
}
|
|
606
|
+
if (Date.now() - notFoundSince < notFoundGraceMs) {
|
|
607
|
+
await sleep2(Math.min(transientBackoffMs, Math.max(0, deadline - Date.now())));
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
367
610
|
return {
|
|
368
611
|
status: "error",
|
|
369
612
|
message_id: messageId,
|
|
370
613
|
content: "",
|
|
371
|
-
error:
|
|
614
|
+
error: "Message not found. It may have been deleted from the queue."
|
|
372
615
|
};
|
|
373
616
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
const ai = msg.response;
|
|
378
|
-
if (ai) {
|
|
379
|
-
if (ai.status === "completed" || ai.status === "stopped") {
|
|
380
|
-
return {
|
|
381
|
-
status: ai.status === "completed" ? "completed" : "error",
|
|
382
|
-
message_id: ai.message_id,
|
|
383
|
-
content: ai.content,
|
|
384
|
-
edit_id: ai.edit_id,
|
|
385
|
-
commit_sha: ai.commit_sha,
|
|
386
|
-
summary: ai.summary,
|
|
387
|
-
cost_credits: ai.cost_credits
|
|
388
|
-
};
|
|
617
|
+
if (err.status < 500) {
|
|
618
|
+
throw err;
|
|
389
619
|
}
|
|
390
620
|
}
|
|
391
|
-
|
|
392
|
-
if (msg.status === "completed" || msg.status === "stopped") {
|
|
393
|
-
return {
|
|
394
|
-
status: msg.status === "completed" ? "completed" : "error",
|
|
395
|
-
message_id: msg.message_id,
|
|
396
|
-
content: msg.content,
|
|
397
|
-
edit_id: msg.edit_id,
|
|
398
|
-
commit_sha: msg.commit_sha,
|
|
399
|
-
summary: msg.summary,
|
|
400
|
-
cost_credits: msg.cost_credits
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
await sleep(pollInterval);
|
|
405
|
-
} catch (err) {
|
|
406
|
-
if (err instanceof ApiError && err.status === 404) {
|
|
407
|
-
if (notFoundSince === null) {
|
|
408
|
-
notFoundSince = Date.now();
|
|
409
|
-
}
|
|
410
|
-
if (Date.now() - notFoundSince < notFoundGraceMs) {
|
|
411
|
-
await sleep(pollInterval);
|
|
412
|
-
continue;
|
|
413
|
-
}
|
|
414
|
-
return {
|
|
415
|
-
status: "error",
|
|
416
|
-
message_id: messageId,
|
|
417
|
-
content: "",
|
|
418
|
-
error: "Message not found. It may have been deleted from the queue."
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
await sleep(pollInterval);
|
|
621
|
+
await sleep2(Math.min(transientBackoffMs, Math.max(0, deadline - Date.now())));
|
|
422
622
|
}
|
|
423
623
|
}
|
|
424
|
-
return
|
|
425
|
-
status: "timeout",
|
|
426
|
-
message_id: messageId,
|
|
427
|
-
content: "",
|
|
428
|
-
error: `Agent did not finish within ${timeout / 1e3}s`
|
|
429
|
-
};
|
|
624
|
+
return timeoutResult(messageId, totalTimeoutMs);
|
|
430
625
|
}
|
|
431
626
|
// ---------------------------------------------------------------------------
|
|
432
627
|
// Knowledge
|
|
@@ -440,73 +635,87 @@ var LovableClient = class {
|
|
|
440
635
|
}
|
|
441
636
|
/** Set workspace knowledge. Max 10,000 characters. */
|
|
442
637
|
async setWorkspaceKnowledge(workspaceId, content) {
|
|
443
|
-
|
|
638
|
+
const { data } = await this.typed.PUT("/v1/workspaces/{workspace_id}/knowledge", {
|
|
639
|
+
params: { path: { workspace_id: workspaceId } },
|
|
640
|
+
body: { content }
|
|
641
|
+
});
|
|
642
|
+
return data;
|
|
444
643
|
}
|
|
445
644
|
// ---------------------------------------------------------------------------
|
|
446
645
|
// Workspace skills
|
|
447
646
|
// ---------------------------------------------------------------------------
|
|
448
647
|
/** List workspace skills. */
|
|
449
|
-
async listWorkspaceSkills(workspaceId, options) {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
648
|
+
async listWorkspaceSkills(workspaceId, options = {}) {
|
|
649
|
+
const { data } = await this.typed.GET("/v1/workspaces/{workspace_id}/skills", {
|
|
650
|
+
params: {
|
|
651
|
+
path: { workspace_id: workspaceId },
|
|
652
|
+
query: { include_markdown: options.includeMarkdown, limit: options.limit, offset: options.offset }
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
return { ...data, skills: data.skills ?? [] };
|
|
454
656
|
}
|
|
455
657
|
/** Get a single workspace skill, including SKILL.md contents. */
|
|
456
658
|
async getWorkspaceSkill(workspaceId, skillName) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
659
|
+
const { data } = await this.typed.GET("/v1/workspaces/{workspace_id}/skills/{skill_name}", {
|
|
660
|
+
params: { path: { workspace_id: workspaceId, skill_name: skillName } }
|
|
661
|
+
});
|
|
662
|
+
return data;
|
|
461
663
|
}
|
|
462
664
|
/** Create a workspace skill from full SKILL.md markdown. */
|
|
463
665
|
async createWorkspaceSkill(workspaceId, skillName, markdown) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
666
|
+
const { data } = await this.typed.POST("/v1/workspaces/{workspace_id}/skills/{skill_name}", {
|
|
667
|
+
params: { path: { workspace_id: workspaceId, skill_name: skillName } },
|
|
668
|
+
body: { markdown }
|
|
669
|
+
});
|
|
670
|
+
return data;
|
|
469
671
|
}
|
|
470
672
|
/** Update a workspace skill by replacing its SKILL.md markdown. */
|
|
471
673
|
async updateWorkspaceSkill(workspaceId, skillName, markdown) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
674
|
+
const { data } = await this.typed.PUT("/v1/workspaces/{workspace_id}/skills/{skill_name}", {
|
|
675
|
+
params: { path: { workspace_id: workspaceId, skill_name: skillName } },
|
|
676
|
+
body: { markdown }
|
|
677
|
+
});
|
|
678
|
+
return data;
|
|
477
679
|
}
|
|
478
680
|
/** Delete a workspace skill. */
|
|
479
681
|
async deleteWorkspaceSkill(workspaceId, skillName) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
682
|
+
const { data } = await this.typed.DELETE("/v1/workspaces/{workspace_id}/skills/{skill_name}", {
|
|
683
|
+
params: { path: { workspace_id: workspaceId, skill_name: skillName } }
|
|
684
|
+
});
|
|
685
|
+
return data;
|
|
484
686
|
}
|
|
485
687
|
// ---------------------------------------------------------------------------
|
|
486
688
|
// Project skills
|
|
487
689
|
// ---------------------------------------------------------------------------
|
|
488
690
|
/** List project skills, including whether each skill is enabled. */
|
|
489
|
-
async listProjectSkills(projectId) {
|
|
490
|
-
|
|
691
|
+
async listProjectSkills(projectId, options = {}) {
|
|
692
|
+
const { data } = await this.typed.GET("/v1/skills", {
|
|
693
|
+
params: { query: { project_id: projectId, limit: options.limit, cursor: options.cursor } }
|
|
694
|
+
});
|
|
695
|
+
const skills = data.skills ?? data.data ?? [];
|
|
696
|
+
return { ...data, skills };
|
|
491
697
|
}
|
|
492
698
|
/** Enable or disable a project skill without removing it from the project repo. */
|
|
493
699
|
async setProjectSkillEnabled(projectId, skillName, enabled) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
700
|
+
const { data } = await this.typed.PATCH("/v1/skills/{skill_name}", {
|
|
701
|
+
params: { path: { skill_name: skillName } },
|
|
702
|
+
body: { project_id: projectId, enabled }
|
|
703
|
+
});
|
|
704
|
+
return data;
|
|
499
705
|
}
|
|
500
706
|
/** Get project knowledge (custom instructions for the AI agent). */
|
|
501
707
|
async getProjectKnowledge(projectId) {
|
|
502
|
-
const { data } = await this.typed.GET("/v1/
|
|
503
|
-
params: {
|
|
708
|
+
const { data } = await this.typed.GET("/v1/knowledge", {
|
|
709
|
+
params: { query: { project_id: projectId } }
|
|
504
710
|
});
|
|
505
711
|
return data;
|
|
506
712
|
}
|
|
507
713
|
/** Set project knowledge. Max 10,000 characters. */
|
|
508
714
|
async setProjectKnowledge(projectId, content) {
|
|
509
|
-
|
|
715
|
+
const { data } = await this.typed.PUT("/v1/knowledge", {
|
|
716
|
+
body: { project_id: projectId, content }
|
|
717
|
+
});
|
|
718
|
+
return data;
|
|
510
719
|
}
|
|
511
720
|
// ---------------------------------------------------------------------------
|
|
512
721
|
// Git operations
|
|
@@ -517,138 +726,207 @@ var LovableClient = class {
|
|
|
517
726
|
* or `sha` for a specific commit.
|
|
518
727
|
*/
|
|
519
728
|
async getDiff(projectId, params) {
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
async listFiles(projectId,
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
729
|
+
const { data } = await this.typed.GET("/v1/git/diff", {
|
|
730
|
+
params: {
|
|
731
|
+
query: { project_id: projectId, message_id: params.messageId, sha: params.sha, base_sha: params.baseSha }
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
return data;
|
|
735
|
+
}
|
|
736
|
+
async listFiles(projectId, refOrOptions, options = {}) {
|
|
737
|
+
const ref = typeof refOrOptions === "string" ? refOrOptions : void 0;
|
|
738
|
+
const pagination = typeof refOrOptions === "string" ? options : refOrOptions ?? options;
|
|
739
|
+
const { data } = await this.typed.GET("/v1/git/files", {
|
|
740
|
+
params: { query: { project_id: projectId, ref, limit: pagination.limit, cursor: pagination.cursor } }
|
|
741
|
+
});
|
|
742
|
+
const files = data.data ?? [];
|
|
743
|
+
return { ...data, data: files, files };
|
|
744
|
+
}
|
|
745
|
+
/** Read the raw content of a single file. Omitting ref uses the API default. */
|
|
532
746
|
async readFile(projectId, path, ref) {
|
|
533
|
-
const
|
|
534
|
-
|
|
747
|
+
const { data } = await this.typed.GET("/v1/git/files/{path}", {
|
|
748
|
+
params: { path: { path }, query: { project_id: projectId, ref } },
|
|
749
|
+
parseAs: "text"
|
|
750
|
+
});
|
|
751
|
+
return data;
|
|
535
752
|
}
|
|
536
753
|
// ---------------------------------------------------------------------------
|
|
537
754
|
// Edits
|
|
538
755
|
// ---------------------------------------------------------------------------
|
|
539
756
|
/** List the edit history of a project. */
|
|
540
757
|
async listEdits(projectId, params) {
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
758
|
+
const { data } = await this.typed.GET("/v1/edits", {
|
|
759
|
+
params: {
|
|
760
|
+
query: { project_id: projectId, limit: params?.limit, before: params?.before }
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
return data;
|
|
546
764
|
}
|
|
547
765
|
// ---------------------------------------------------------------------------
|
|
548
766
|
// File upload
|
|
549
767
|
// ---------------------------------------------------------------------------
|
|
550
|
-
/** Get
|
|
768
|
+
/** Get an ephemeral presigned URL for uploading a file before a project exists. */
|
|
551
769
|
async getFileUploadUrl(params) {
|
|
552
|
-
return this.
|
|
770
|
+
return this.getEphemeralFileUploadUrl({ content_type: params.content_type });
|
|
771
|
+
}
|
|
772
|
+
/** Get a project-scoped presigned URL for uploading a file. Returns the upload URL, file ID, and required PUT headers. */
|
|
773
|
+
async getProjectFileUploadUrl(projectId, params) {
|
|
774
|
+
const { data } = await this.typed.POST("/v1/project-files/upload-url", {
|
|
775
|
+
body: { project_id: projectId, ...params }
|
|
776
|
+
});
|
|
777
|
+
return data;
|
|
778
|
+
}
|
|
779
|
+
/** Get an ephemeral presigned URL for uploading a file before a project exists. */
|
|
780
|
+
async getEphemeralFileUploadUrl(params) {
|
|
781
|
+
const { data } = await this.typed.POST("/v1/files/ephemeral-upload-url", { body: params });
|
|
782
|
+
return data;
|
|
553
783
|
}
|
|
554
784
|
// ---------------------------------------------------------------------------
|
|
555
785
|
// Visibility
|
|
556
786
|
// ---------------------------------------------------------------------------
|
|
557
|
-
/** Set a project's visibility (draft, private, or public). */
|
|
787
|
+
/** Set a project's visibility (draft, private, workspace_view, or public). */
|
|
558
788
|
async setProjectVisibility(projectId, visibility) {
|
|
559
|
-
return this.
|
|
789
|
+
return this.updateProject(projectId, { visibility });
|
|
560
790
|
}
|
|
561
791
|
/** Set a folder's visibility (personal or workspace). */
|
|
562
792
|
async setFolderVisibility(workspaceId, folderId, visibility) {
|
|
563
|
-
|
|
564
|
-
|
|
793
|
+
const { data } = await this.typed.PUT("/v1/workspaces/{workspace_id}/folders/{folder_id}/visibility", {
|
|
794
|
+
params: { path: { workspace_id: workspaceId, folder_id: folderId } },
|
|
795
|
+
body: { visibility }
|
|
565
796
|
});
|
|
797
|
+
return data;
|
|
798
|
+
}
|
|
799
|
+
/** Move projects into a folder, removing existing folder memberships first. */
|
|
800
|
+
async moveProjectsToFolder(workspaceId, folderId, projectIds) {
|
|
801
|
+
const { data } = await this.typed.POST("/v1/workspaces/{workspace_id}/folders/{folder_id}/projects/move", {
|
|
802
|
+
params: { path: { workspace_id: workspaceId, folder_id: folderId } },
|
|
803
|
+
body: { project_ids: projectIds }
|
|
804
|
+
});
|
|
805
|
+
return data;
|
|
566
806
|
}
|
|
567
807
|
// ---------------------------------------------------------------------------
|
|
568
808
|
// Library & template projects
|
|
569
809
|
// ---------------------------------------------------------------------------
|
|
570
810
|
/** List available design system library projects in a workspace. */
|
|
571
|
-
async listLibraryProjects(workspaceId) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
811
|
+
async listLibraryProjects(workspaceId, options = {}) {
|
|
812
|
+
const { data } = await this.typed.GET("/v1/workspaces/{workspace_id}/available-library-projects", {
|
|
813
|
+
params: { path: { workspace_id: workspaceId }, query: options }
|
|
814
|
+
});
|
|
815
|
+
return { ...data, libraries: data.libraries ?? [] };
|
|
576
816
|
}
|
|
577
817
|
/** List available template projects in a workspace. */
|
|
578
|
-
async listTemplateProjects(workspaceId) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
818
|
+
async listTemplateProjects(workspaceId, options = {}) {
|
|
819
|
+
const { data } = await this.typed.GET("/v1/workspaces/{workspace_id}/available-template-projects", {
|
|
820
|
+
params: { path: { workspace_id: workspaceId }, query: options }
|
|
821
|
+
});
|
|
822
|
+
return { ...data, templates: data.templates ?? [] };
|
|
583
823
|
}
|
|
584
824
|
// ---------------------------------------------------------------------------
|
|
585
825
|
// Connectors (MCP servers)
|
|
586
826
|
// ---------------------------------------------------------------------------
|
|
587
827
|
/** List all connectors in a workspace. */
|
|
588
|
-
async listConnectors(workspaceId) {
|
|
589
|
-
|
|
828
|
+
async listConnectors(workspaceId, options = {}) {
|
|
829
|
+
const { data } = await this.typed.GET("/v1/connectors", {
|
|
830
|
+
params: {
|
|
831
|
+
query: {
|
|
832
|
+
workspace_id: workspaceId,
|
|
833
|
+
type: options.type,
|
|
834
|
+
status: options.status,
|
|
835
|
+
limit: options.limit,
|
|
836
|
+
cursor: options.cursor
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
const connectors = data.connectors ?? data.data ?? [];
|
|
841
|
+
return { ...data, data: connectors, connectors, has_more: cursorHasMore(data) };
|
|
590
842
|
}
|
|
591
843
|
/** Add a connector to a workspace. The server URL is tested before saving. */
|
|
592
844
|
async addConnector(workspaceId, body) {
|
|
593
|
-
|
|
845
|
+
const { data } = await this.typed.POST("/v1/connectors", {
|
|
846
|
+
body: { ...body, workspace_id: workspaceId }
|
|
847
|
+
});
|
|
848
|
+
return data;
|
|
594
849
|
}
|
|
595
850
|
/** Remove a connector from a workspace. */
|
|
596
851
|
async removeConnector(workspaceId, connectorId) {
|
|
597
|
-
|
|
852
|
+
const { data } = await this.typed.DELETE("/v1/connectors/{connector_id}", {
|
|
853
|
+
params: { path: { connector_id: connectorId }, query: { workspace_id: workspaceId } }
|
|
854
|
+
});
|
|
855
|
+
return data;
|
|
598
856
|
}
|
|
599
857
|
/** Browse available connector templates. */
|
|
600
|
-
async listAvailableConnectors(workspaceId) {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
858
|
+
async listAvailableConnectors(workspaceId, options = {}) {
|
|
859
|
+
const { data } = await this.typed.GET("/v1/available-connectors", {
|
|
860
|
+
params: { query: { workspace_id: workspaceId, limit: options.limit, cursor: options.cursor } }
|
|
861
|
+
});
|
|
862
|
+
const catalog = data.catalog ?? data.data ?? [];
|
|
863
|
+
return { ...data, data: catalog, catalog, has_more: cursorHasMore(data) };
|
|
605
864
|
}
|
|
606
865
|
// ---------------------------------------------------------------------------
|
|
607
866
|
// Connectors
|
|
608
867
|
// ---------------------------------------------------------------------------
|
|
609
868
|
/** List standard (OAuth-based) connectors in a workspace. */
|
|
610
|
-
async listStandardConnectors(workspaceId) {
|
|
611
|
-
|
|
612
|
-
"
|
|
613
|
-
|
|
614
|
-
|
|
869
|
+
async listStandardConnectors(workspaceId, options = {}) {
|
|
870
|
+
const { data } = await this.typed.GET("/v1/connectors", {
|
|
871
|
+
params: { query: { workspace_id: workspaceId, type: "standard", limit: options.limit, cursor: options.cursor } }
|
|
872
|
+
});
|
|
873
|
+
const connectors = data.connectors ?? data.data ?? [];
|
|
874
|
+
return { ...data, data: connectors, connectors, has_more: cursorHasMore(data) };
|
|
615
875
|
}
|
|
616
876
|
/** List seamless (zero-config) connectors in a workspace. */
|
|
617
|
-
async listSeamlessConnectors(workspaceId) {
|
|
618
|
-
|
|
619
|
-
"
|
|
620
|
-
|
|
621
|
-
|
|
877
|
+
async listSeamlessConnectors(workspaceId, options = {}) {
|
|
878
|
+
const { data } = await this.typed.GET("/v1/connectors", {
|
|
879
|
+
params: { query: { workspace_id: workspaceId, type: "seamless", limit: options.limit, cursor: options.cursor } }
|
|
880
|
+
});
|
|
881
|
+
const connectors = data.connectors ?? data.data ?? [];
|
|
882
|
+
return { ...data, data: connectors, connectors, has_more: cursorHasMore(data) };
|
|
622
883
|
}
|
|
623
884
|
/** List MCP connectors in a workspace. */
|
|
624
|
-
async listMCPConnectors(workspaceId) {
|
|
625
|
-
|
|
626
|
-
"
|
|
627
|
-
|
|
628
|
-
|
|
885
|
+
async listMCPConnectors(workspaceId, options = {}) {
|
|
886
|
+
const { data } = await this.typed.GET("/v1/connectors", {
|
|
887
|
+
params: { query: { workspace_id: workspaceId, type: "mcp", limit: options.limit, cursor: options.cursor } }
|
|
888
|
+
});
|
|
889
|
+
const connectors = data.connectors ?? data.data ?? [];
|
|
890
|
+
return { ...data, data: connectors, connectors, has_more: cursorHasMore(data) };
|
|
629
891
|
}
|
|
630
892
|
/** List authenticated connections (accounts) in a workspace. */
|
|
631
|
-
async listConnections(workspaceId, params) {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
893
|
+
async listConnections(workspaceId, params = {}) {
|
|
894
|
+
const { data } = await this.typed.GET("/v1/connections", {
|
|
895
|
+
params: {
|
|
896
|
+
query: {
|
|
897
|
+
workspace_id: workspaceId,
|
|
898
|
+
connector_id: params.connector_id,
|
|
899
|
+
limit: params.limit,
|
|
900
|
+
cursor: params.cursor
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
const connections = data.data ?? [];
|
|
905
|
+
return { ...data, data: connections, connections, has_more: cursorHasMore(data) };
|
|
636
906
|
}
|
|
637
907
|
// ---------------------------------------------------------------------------
|
|
638
908
|
// Analytics
|
|
639
909
|
// ---------------------------------------------------------------------------
|
|
640
910
|
/** Get historical analytics for a published project. */
|
|
641
911
|
async getProjectAnalytics(projectId, params) {
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
912
|
+
const { data } = await this.typed.GET("/v1/analytics", {
|
|
913
|
+
params: {
|
|
914
|
+
query: {
|
|
915
|
+
project_id: projectId,
|
|
916
|
+
startDate: params.startDate,
|
|
917
|
+
endDate: params.endDate,
|
|
918
|
+
granularity: params.granularity
|
|
919
|
+
}
|
|
920
|
+
}
|
|
645
921
|
});
|
|
646
|
-
|
|
647
|
-
return this.request("GET", `/v1/projects/${projectId}/analytics?${qs.toString()}`);
|
|
922
|
+
return data;
|
|
648
923
|
}
|
|
649
924
|
/** Get real-time visitor trend for a published project. */
|
|
650
925
|
async getProjectAnalyticsTrend(projectId) {
|
|
651
|
-
|
|
926
|
+
const { data } = await this.typed.GET("/v1/analytics/trend", {
|
|
927
|
+
params: { query: { project_id: projectId } }
|
|
928
|
+
});
|
|
929
|
+
return data;
|
|
652
930
|
}
|
|
653
931
|
/**
|
|
654
932
|
* Publish a project.
|
|
@@ -661,50 +939,57 @@ var LovableClient = class {
|
|
|
661
939
|
* @returns Deployment info including deployment ID
|
|
662
940
|
*/
|
|
663
941
|
async publish(projectId, options) {
|
|
664
|
-
|
|
665
|
-
name: options?.name
|
|
942
|
+
const { data } = await this.typed.POST("/v1/deployments", {
|
|
943
|
+
body: { project_id: projectId, name: options?.name }
|
|
666
944
|
});
|
|
945
|
+
return data;
|
|
667
946
|
}
|
|
668
947
|
/**
|
|
669
948
|
* Remix (fork) an existing project, optionally at a specific message point in time.
|
|
670
949
|
*
|
|
671
|
-
* When `messageId` is provided, the remix captures the project state
|
|
672
|
-
*
|
|
673
|
-
*
|
|
950
|
+
* When `messageId` is provided, the remix captures the project state after
|
|
951
|
+
* that message and its AI response by default. Set `remixMode: "before"` to
|
|
952
|
+
* start before the message was processed.
|
|
674
953
|
* Without `messageId`, the full current state is remixed.
|
|
675
954
|
*
|
|
676
955
|
* @param sourceProjectId - The project to remix from
|
|
677
956
|
* @param options.workspaceId - Target workspace for the new project
|
|
678
957
|
* @param options.messageId - Optional message ID to snapshot at
|
|
679
|
-
* @param options.remixMode - "
|
|
958
|
+
* @param options.remixMode - "including" (server default): state after the message and its AI response; "before": state before the message
|
|
680
959
|
* @param options.includeHistory - Whether to preserve chat history (default: false)
|
|
681
960
|
* @param options.includeCustomKnowledge - Whether to copy custom instructions (default: false)
|
|
682
961
|
* @param options.initialMessage - Optional initial message to send after remix
|
|
962
|
+
* @param options.description - Optional custom description for the new project
|
|
683
963
|
* @returns The remix job ID for polling progress
|
|
684
964
|
*/
|
|
685
965
|
async remixProject(sourceProjectId, options) {
|
|
686
966
|
const body = {
|
|
687
967
|
workspace_id: options.workspaceId,
|
|
968
|
+
source_project_id: sourceProjectId,
|
|
688
969
|
include_history: options.includeHistory,
|
|
689
970
|
include_custom_knowledge: options.includeCustomKnowledge,
|
|
690
|
-
|
|
971
|
+
description: options.description,
|
|
972
|
+
display_name: options.projectName,
|
|
691
973
|
skip_initial_remix_message: options.skipInitialRemixMessage,
|
|
692
974
|
skip_integrations: options.skipIntegrations
|
|
693
975
|
};
|
|
694
976
|
if (options.messageId) {
|
|
695
977
|
body.message_id = options.messageId;
|
|
696
|
-
|
|
978
|
+
if (options.remixMode) {
|
|
979
|
+
body.remix_mode = options.remixMode;
|
|
980
|
+
}
|
|
697
981
|
}
|
|
698
982
|
if (options.initialMessage) {
|
|
699
|
-
body.initial_message =
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
983
|
+
body.initial_message = options.initialMessage;
|
|
984
|
+
}
|
|
985
|
+
const { data } = await this.typed.POST("/v1/projects", {
|
|
986
|
+
body
|
|
987
|
+
});
|
|
988
|
+
const remix = data;
|
|
989
|
+
if (!remix?.job_id) {
|
|
990
|
+
throw new Error("Failed to get job ID from remix create");
|
|
705
991
|
}
|
|
706
|
-
|
|
707
|
-
return response.job_id;
|
|
992
|
+
return remix.job_id;
|
|
708
993
|
}
|
|
709
994
|
/**
|
|
710
995
|
* Wait for a remix operation to complete.
|
|
@@ -724,21 +1009,25 @@ var LovableClient = class {
|
|
|
724
1009
|
const timeout = options?.timeout ?? 3e5;
|
|
725
1010
|
const startTime = Date.now();
|
|
726
1011
|
while (true) {
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1012
|
+
const { data } = await this.typed.GET("/v1/projects/{project_id}/remix/progress", {
|
|
1013
|
+
params: {
|
|
1014
|
+
path: { project_id: sourceProjectId },
|
|
1015
|
+
query: { job_id: jobId }
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
const progress = data;
|
|
1019
|
+
const status = progress.status;
|
|
1020
|
+
options?.onProgress?.(status, progress.step);
|
|
1021
|
+
if (status === "completed" && progress.result) {
|
|
733
1022
|
return { projectId: progress.result.project_id };
|
|
734
1023
|
}
|
|
735
|
-
if (
|
|
1024
|
+
if (status === "error") {
|
|
736
1025
|
throw new Error(progress.error_message ?? "Remix failed");
|
|
737
1026
|
}
|
|
738
1027
|
if (Date.now() - startTime > timeout) {
|
|
739
1028
|
throw new Error(`Timeout waiting for remix of project ${sourceProjectId}`);
|
|
740
1029
|
}
|
|
741
|
-
await
|
|
1030
|
+
await sleep2(pollInterval);
|
|
742
1031
|
}
|
|
743
1032
|
}
|
|
744
1033
|
/**
|
|
@@ -771,140 +1060,59 @@ var LovableClient = class {
|
|
|
771
1060
|
if (Date.now() - startTime > timeout) {
|
|
772
1061
|
throw new Error(`Timeout waiting for project ${projectId} to be ready`);
|
|
773
1062
|
}
|
|
774
|
-
await
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Wait for the AI response to a chat message.
|
|
779
|
-
*
|
|
780
|
-
* Connects to the project's message stream (SSE) and accumulates the
|
|
781
|
-
* response content until the message is complete. Returns the full
|
|
782
|
-
* response text along with the project's preview URL.
|
|
783
|
-
*
|
|
784
|
-
* Use this after `chat()` or after `createProject()` with `initialMessage`.
|
|
785
|
-
*
|
|
786
|
-
* @param projectId - The project ID to listen for
|
|
787
|
-
* @param options.timeout - Maximum time to wait in ms (default: 300000 = 5 minutes)
|
|
788
|
-
* @returns The AI response content and preview URL
|
|
789
|
-
* @throws Error if the stream fails or timeout is reached
|
|
790
|
-
*/
|
|
791
|
-
async waitForResponse(projectId, options) {
|
|
792
|
-
const timeout = options?.timeout ?? 3e5;
|
|
793
|
-
const url = `${this.baseUrl}/v1/projects/${projectId}/messages/stream`;
|
|
794
|
-
const controller = new AbortController();
|
|
795
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
796
|
-
try {
|
|
797
|
-
const response = await fetch(url, {
|
|
798
|
-
headers: {
|
|
799
|
-
...this.authHeaders,
|
|
800
|
-
...this.extraHeaders
|
|
801
|
-
},
|
|
802
|
-
signal: controller.signal
|
|
803
|
-
});
|
|
804
|
-
if (!response.ok) {
|
|
805
|
-
throw new ApiError(response.status, `Failed to connect to message stream: HTTP ${response.status}`);
|
|
806
|
-
}
|
|
807
|
-
if (!response.body) {
|
|
808
|
-
throw new Error("Response body is not readable");
|
|
809
|
-
}
|
|
810
|
-
const result = await this.consumeSSEStream(response.body);
|
|
811
|
-
return {
|
|
812
|
-
content: result.content,
|
|
813
|
-
messageId: result.messageId,
|
|
814
|
-
previewUrl: this.getPreviewUrl(projectId)
|
|
815
|
-
};
|
|
816
|
-
} catch (err) {
|
|
817
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
818
|
-
throw new Error(`Timeout waiting for response on project ${projectId}`);
|
|
819
|
-
}
|
|
820
|
-
throw err;
|
|
821
|
-
} finally {
|
|
822
|
-
clearTimeout(timeoutId);
|
|
1063
|
+
await sleep2(pollInterval);
|
|
823
1064
|
}
|
|
824
1065
|
}
|
|
825
1066
|
isFileInput(file) {
|
|
826
1067
|
return "data" in file;
|
|
827
1068
|
}
|
|
828
|
-
async uploadFile(file) {
|
|
1069
|
+
async uploadFile(file, getUploadUrl) {
|
|
829
1070
|
const fileName = this.isFileInput(file) ? file.name : file.name;
|
|
830
1071
|
const mimeType = this.isFileInput(file) ? file.type : file.type;
|
|
831
1072
|
const body = this.isFileInput(file) ? file.data : file;
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
"/v1/files/upload-url",
|
|
835
|
-
{
|
|
836
|
-
file_name: fileName,
|
|
837
|
-
content_type: mimeType
|
|
838
|
-
}
|
|
839
|
-
);
|
|
1073
|
+
const uploadUrl = await getUploadUrl(fileName, mimeType);
|
|
1074
|
+
const { url, file_id: objectPath, headers } = uploadUrl;
|
|
840
1075
|
const uploadResponse = await fetch(url, {
|
|
841
1076
|
method: "PUT",
|
|
842
1077
|
body,
|
|
843
|
-
headers: { "Content-Type": mimeType }
|
|
1078
|
+
headers: { "Content-Type": mimeType, ...headers }
|
|
844
1079
|
});
|
|
845
1080
|
if (!uploadResponse.ok) {
|
|
846
1081
|
throw new Error(`File upload failed for "${fileName}": HTTP ${uploadResponse.status}`);
|
|
847
1082
|
}
|
|
848
1083
|
return { file_id: objectPath, type: "user_upload", file_name: fileName, mime_type: mimeType };
|
|
849
1084
|
}
|
|
850
|
-
async
|
|
851
|
-
return Promise.all(
|
|
1085
|
+
async uploadProjectFiles(projectId, files) {
|
|
1086
|
+
return Promise.all(
|
|
1087
|
+
files.map(
|
|
1088
|
+
(file) => this.uploadFile(
|
|
1089
|
+
file,
|
|
1090
|
+
(_fileName, mimeType) => this.getProjectFileUploadUrl(projectId, { content_type: mimeType })
|
|
1091
|
+
)
|
|
1092
|
+
)
|
|
1093
|
+
);
|
|
852
1094
|
}
|
|
853
|
-
async
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
const lines = part.split("\n");
|
|
869
|
-
let eventType = "";
|
|
870
|
-
let eventData = "";
|
|
871
|
-
for (const line of lines) {
|
|
872
|
-
if (line.startsWith("event: ")) {
|
|
873
|
-
eventType = line.slice(7);
|
|
874
|
-
} else if (line.startsWith("data: ")) {
|
|
875
|
-
eventData = line.slice(6);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
if (eventType === "message" && eventData) {
|
|
879
|
-
try {
|
|
880
|
-
const data = JSON.parse(eventData);
|
|
881
|
-
if (typeof data.content === "string") {
|
|
882
|
-
content += data.content;
|
|
883
|
-
}
|
|
884
|
-
if (typeof data.message_id === "string" && data.message_id) {
|
|
885
|
-
messageId = data.message_id;
|
|
886
|
-
}
|
|
887
|
-
if (data.is_final) {
|
|
888
|
-
return { content, messageId };
|
|
889
|
-
}
|
|
890
|
-
} catch {
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
if (eventType === "error") {
|
|
894
|
-
let detail = "Stream error from server";
|
|
895
|
-
try {
|
|
896
|
-
const data = JSON.parse(eventData);
|
|
897
|
-
if (data.message) detail = data.message;
|
|
898
|
-
} catch {
|
|
899
|
-
}
|
|
900
|
-
throw new Error(detail);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
1095
|
+
async uploadEphemeralFiles(files) {
|
|
1096
|
+
return Promise.all(
|
|
1097
|
+
files.map(
|
|
1098
|
+
(file) => this.uploadFile(file, (_fileName, mimeType) => this.getEphemeralFileUploadUrl({ content_type: mimeType }))
|
|
1099
|
+
)
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
partitionUploadedFiles(files) {
|
|
1103
|
+
const fileRefs = [];
|
|
1104
|
+
const ephemeralFileRefs = [];
|
|
1105
|
+
for (const file of files) {
|
|
1106
|
+
if (file.file_id.startsWith("ephemeral/")) {
|
|
1107
|
+
ephemeralFileRefs.push(file);
|
|
1108
|
+
} else {
|
|
1109
|
+
fileRefs.push(file);
|
|
903
1110
|
}
|
|
904
|
-
} finally {
|
|
905
|
-
void reader.cancel();
|
|
906
1111
|
}
|
|
907
|
-
return {
|
|
1112
|
+
return {
|
|
1113
|
+
fileRefs: fileRefs.length > 0 ? fileRefs : void 0,
|
|
1114
|
+
ephemeralFileRefs: ephemeralFileRefs.length > 0 ? ephemeralFileRefs : void 0
|
|
1115
|
+
};
|
|
908
1116
|
}
|
|
909
1117
|
/**
|
|
910
1118
|
* Wait for a project to be published (deployed).
|
|
@@ -934,13 +1142,123 @@ var LovableClient = class {
|
|
|
934
1142
|
if (Date.now() - startTime > timeout) {
|
|
935
1143
|
throw new Error(`Timeout waiting for project ${projectId} to be published`);
|
|
936
1144
|
}
|
|
937
|
-
await
|
|
1145
|
+
await sleep2(pollInterval);
|
|
938
1146
|
}
|
|
939
1147
|
}
|
|
940
1148
|
};
|
|
941
|
-
function
|
|
1149
|
+
function sleep2(ms) {
|
|
942
1150
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
943
1151
|
}
|
|
1152
|
+
function isAbortError(err) {
|
|
1153
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
1154
|
+
}
|
|
1155
|
+
async function safeReadText(response) {
|
|
1156
|
+
try {
|
|
1157
|
+
return await response.text();
|
|
1158
|
+
} catch {
|
|
1159
|
+
return "";
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
async function cancelResponseBody(response) {
|
|
1163
|
+
try {
|
|
1164
|
+
await response.body?.cancel();
|
|
1165
|
+
} catch {
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
async function* parseSSEFrames(body) {
|
|
1169
|
+
const reader = body.getReader();
|
|
1170
|
+
const decoder = new TextDecoder();
|
|
1171
|
+
let buffer = "";
|
|
1172
|
+
try {
|
|
1173
|
+
while (true) {
|
|
1174
|
+
const { done, value } = await reader.read();
|
|
1175
|
+
if (done) break;
|
|
1176
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1177
|
+
let sep = buffer.indexOf("\n\n");
|
|
1178
|
+
while (sep !== -1) {
|
|
1179
|
+
const raw = buffer.slice(0, sep);
|
|
1180
|
+
buffer = buffer.slice(sep + 2);
|
|
1181
|
+
sep = buffer.indexOf("\n\n");
|
|
1182
|
+
let event = "message";
|
|
1183
|
+
const dataLines = [];
|
|
1184
|
+
for (const line of raw.split("\n")) {
|
|
1185
|
+
if (!line || line.startsWith(":")) continue;
|
|
1186
|
+
if (line.startsWith("event:")) {
|
|
1187
|
+
event = line.slice(6).trimStart();
|
|
1188
|
+
} else if (line.startsWith("data:")) {
|
|
1189
|
+
dataLines.push(line.slice(5).trimStart());
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (dataLines.length === 0) continue;
|
|
1193
|
+
yield { event, data: dataLines.join("\n") };
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
} finally {
|
|
1197
|
+
try {
|
|
1198
|
+
reader.releaseLock();
|
|
1199
|
+
} catch {
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
await body.cancel();
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function terminalResultFromMessage(msg, fallbackMessageId) {
|
|
1208
|
+
const ai = msg.response;
|
|
1209
|
+
if (ai && isTerminalAIStatus(ai.status)) {
|
|
1210
|
+
return {
|
|
1211
|
+
status: messageCompletionStatus(ai.status),
|
|
1212
|
+
message_id: ai.message_id || fallbackMessageId,
|
|
1213
|
+
content: ai.content,
|
|
1214
|
+
edit_id: ai.edit_id,
|
|
1215
|
+
commit_sha: ai.commit_sha,
|
|
1216
|
+
summary: ai.summary,
|
|
1217
|
+
cost_credits: ai.cost_credits,
|
|
1218
|
+
awaiting_input: ai.awaiting_input,
|
|
1219
|
+
...ai.status === "error" ? { error: "Agent reported an error." } : {}
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
if (msg.role === "assistant" && isTerminalAIStatus(msg.status)) {
|
|
1223
|
+
return {
|
|
1224
|
+
status: messageCompletionStatus(msg.status),
|
|
1225
|
+
message_id: msg.message_id || fallbackMessageId,
|
|
1226
|
+
content: msg.content,
|
|
1227
|
+
edit_id: msg.edit_id,
|
|
1228
|
+
commit_sha: msg.commit_sha,
|
|
1229
|
+
summary: msg.summary,
|
|
1230
|
+
cost_credits: msg.cost_credits,
|
|
1231
|
+
awaiting_input: msg.awaiting_input,
|
|
1232
|
+
...msg.status === "error" ? { error: "Agent reported an error." } : {}
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
function messageCompletionStatus(status) {
|
|
1238
|
+
return status === "completed" || status === "stopped" || status === "awaiting_input" ? status : "error";
|
|
1239
|
+
}
|
|
1240
|
+
function isTerminalAIStatus(status) {
|
|
1241
|
+
return status === "completed" || status === "stopped" || status === "error" || status === "awaiting_input";
|
|
1242
|
+
}
|
|
1243
|
+
function queuedExitResult(msg, fallbackMessageId) {
|
|
1244
|
+
if (msg.status !== "queued") return null;
|
|
1245
|
+
if (!msg.queue_paused) return null;
|
|
1246
|
+
if (msg.queue_pause_reason === "hitl_tool") return null;
|
|
1247
|
+
return {
|
|
1248
|
+
status: "error",
|
|
1249
|
+
message_id: fallbackMessageId,
|
|
1250
|
+
content: "",
|
|
1251
|
+
error: `Message is queued (position ${msg.queue_position ?? "unknown"}) but the queue is paused` + (msg.queue_pause_reason ? ` (reason: ${msg.queue_pause_reason})` : "") + `. Unpause the queue in the Lovable editor, or use wait=false to return immediately.`
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
function timeoutResult(messageId, timeoutMs) {
|
|
1255
|
+
return {
|
|
1256
|
+
status: "timeout",
|
|
1257
|
+
message_id: messageId,
|
|
1258
|
+
content: "",
|
|
1259
|
+
error: `Agent did not finish within ${Math.max(0, Math.round(timeoutMs / 1e3))}s`
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
944
1262
|
export {
|
|
945
1263
|
ApiError,
|
|
946
1264
|
LovableClient
|