@lovable.dev/sdk 0.0.9 → 0.1.1
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 +13 -9
- package/dist/index.d.ts +72 -42
- package/dist/index.js +117 -95
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,24 +94,24 @@ await client.chat(project.id, {
|
|
|
94
94
|
});
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
### Fetching message traces
|
|
97
|
+
### Fetching message traces (`_dev`)
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Trace APIs are available under the `_dev` namespace. These are not part of the stable v1 surface and may change without notice.
|
|
100
100
|
|
|
101
101
|
```typescript
|
|
102
102
|
const response = await client.waitForResponse(project.id);
|
|
103
103
|
|
|
104
104
|
// Fetch all traces for the message
|
|
105
|
-
const traces = await client.getMessageTraces(project.id, response.messageId);
|
|
105
|
+
const traces = await client._dev.getMessageTraces(project.id, response.messageId);
|
|
106
106
|
console.log(traces.spans);
|
|
107
107
|
|
|
108
108
|
// Filter by purpose (e.g. only the main agent span)
|
|
109
|
-
const agentTraces = await client.getMessageTraces(project.id, response.messageId, {
|
|
109
|
+
const agentTraces = await client._dev.getMessageTraces(project.id, response.messageId, {
|
|
110
110
|
purposes: ["main_agent"],
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
// Batch fetch traces for multiple messages
|
|
114
|
-
const result = await client.getMessageTracesBatch([
|
|
114
|
+
const result = await client._dev.getMessageTracesBatch([
|
|
115
115
|
{ projectId: "proj-1", messageId: "msg-1" },
|
|
116
116
|
{ projectId: "proj-2", messageId: "msg-2" },
|
|
117
117
|
], { purposes: ["main_agent"], concurrency: 5 });
|
|
@@ -176,8 +176,8 @@ Send a chat message to a project's AI agent.
|
|
|
176
176
|
Options:
|
|
177
177
|
|
|
178
178
|
- `message` (required): The message to send
|
|
179
|
-
- `chatOnly` (optional): If true, only chat without making code changes
|
|
180
179
|
- `files` (optional): Array of files to attach (browser `File` objects or `FileInput` objects)
|
|
180
|
+
- `chatOnly` (optional): If true, only chat without making code changes
|
|
181
181
|
- `customModel` (optional): Route the main agent to a custom OpenAI-compatible endpoint (see `CustomModelConfig`)
|
|
182
182
|
|
|
183
183
|
Note: This is an asynchronous operation. The API accepts the message and processes it in the background. Use `waitForResponse()` to wait for the AI's reply.
|
|
@@ -191,7 +191,7 @@ Use this after `chat()` or after `createProject()` with `initialMessage`.
|
|
|
191
191
|
Returns:
|
|
192
192
|
|
|
193
193
|
- `content` (string): The AI's full response text
|
|
194
|
-
- `messageId` (string): The AI message ID (use with `getMessageTraces()`)
|
|
194
|
+
- `messageId` (string): The AI message ID (use with `_dev.getMessageTraces()`)
|
|
195
195
|
- `previewUrl` (string): The project's preview URL
|
|
196
196
|
|
|
197
197
|
Options:
|
|
@@ -280,7 +280,11 @@ Options:
|
|
|
280
280
|
|
|
281
281
|
Throws an error if the remix fails or timeout is reached.
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
#### `_dev` (developer/experimental APIs)
|
|
284
|
+
|
|
285
|
+
These methods are not part of the stable v1 surface and may change without notice.
|
|
286
|
+
|
|
287
|
+
##### `_dev.getMessageTraces(projectId: string, messageId: string, options?): Promise<MessageTracesResponse>`
|
|
284
288
|
|
|
285
289
|
Fetch Braintrust trace spans for a specific chat message. The `messageId` is available from the `ChatResponse` returned by `waitForResponse()`.
|
|
286
290
|
|
|
@@ -297,7 +301,7 @@ Returns:
|
|
|
297
301
|
- `root_span_id` (string): The root span ID
|
|
298
302
|
- `spans` (TraceSpan[]): The filtered trace spans
|
|
299
303
|
|
|
300
|
-
##### `getMessageTracesBatch(queries, options?): Promise<BatchTracesResult>`
|
|
304
|
+
##### `_dev.getMessageTracesBatch(queries, options?): Promise<BatchTracesResult>`
|
|
301
305
|
|
|
302
306
|
Fetch traces for multiple messages across projects in parallel.
|
|
303
307
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
type ProjectVisibility = "draft" | "private" | "public";
|
|
2
2
|
type MemberRole = "admin" | "collaborator" | "invited" | "member" | "none" | "owner" | "viewer";
|
|
3
3
|
type ProjectStatus = "completed" | "in_progress" | "failed";
|
|
4
|
+
interface MeWorkspace {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
role: string;
|
|
8
|
+
}
|
|
9
|
+
interface MeResponse {
|
|
10
|
+
id: string;
|
|
11
|
+
email: string;
|
|
12
|
+
name: string;
|
|
13
|
+
workspaces: MeWorkspace[];
|
|
14
|
+
}
|
|
4
15
|
interface WorkspaceMembership {
|
|
5
16
|
email: string;
|
|
6
17
|
invited_at?: string;
|
|
@@ -65,16 +76,23 @@ interface WorkspaceMembershipResponse {
|
|
|
65
76
|
access_level?: string;
|
|
66
77
|
}>;
|
|
67
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Project response from the API.
|
|
81
|
+
*
|
|
82
|
+
* The v1 endpoints (getProject, createProject) return a lean subset:
|
|
83
|
+
* id, workspace_id, name, display_name, description, status, visibility,
|
|
84
|
+
* is_published, url. Other fields are available from list endpoints.
|
|
85
|
+
*/
|
|
68
86
|
interface ProjectResponse {
|
|
69
87
|
id: string;
|
|
70
88
|
name?: string;
|
|
71
89
|
display_name?: string;
|
|
72
|
-
description
|
|
73
|
-
tech_stack
|
|
74
|
-
status
|
|
90
|
+
description?: string;
|
|
91
|
+
tech_stack?: string;
|
|
92
|
+
status?: string;
|
|
75
93
|
visibility?: ProjectVisibility;
|
|
76
94
|
publish_visibility?: "private" | "public";
|
|
77
|
-
is_published
|
|
95
|
+
is_published?: boolean;
|
|
78
96
|
is_starred?: boolean;
|
|
79
97
|
is_template?: boolean;
|
|
80
98
|
is_github?: boolean;
|
|
@@ -82,12 +100,12 @@ interface ProjectResponse {
|
|
|
82
100
|
url?: string;
|
|
83
101
|
og_image_url?: string;
|
|
84
102
|
latest_screenshot_url?: string;
|
|
85
|
-
user_id
|
|
103
|
+
user_id?: string;
|
|
86
104
|
user_display_name?: string;
|
|
87
105
|
user_photo_url?: string;
|
|
88
|
-
created_at
|
|
106
|
+
created_at?: string;
|
|
89
107
|
created_by?: string;
|
|
90
|
-
updated_at
|
|
108
|
+
updated_at?: string;
|
|
91
109
|
deleted_at?: string;
|
|
92
110
|
last_edited_at?: string;
|
|
93
111
|
last_viewed_at?: string;
|
|
@@ -98,7 +116,7 @@ interface ProjectResponse {
|
|
|
98
116
|
edit_count?: number;
|
|
99
117
|
gen_count?: number;
|
|
100
118
|
user_message_count?: number;
|
|
101
|
-
remix_count
|
|
119
|
+
remix_count?: number;
|
|
102
120
|
remixed_from_project_id?: string;
|
|
103
121
|
template_project_id?: string;
|
|
104
122
|
credit_total?: number;
|
|
@@ -110,7 +128,7 @@ interface ProjectResponse {
|
|
|
110
128
|
deployment_target?: string;
|
|
111
129
|
active_deployment_job_id?: string;
|
|
112
130
|
environments_enabled?: boolean;
|
|
113
|
-
hide_badge
|
|
131
|
+
hide_badge?: boolean | null;
|
|
114
132
|
featured?: boolean;
|
|
115
133
|
featured_at?: string;
|
|
116
134
|
feature_rank?: number;
|
|
@@ -161,10 +179,10 @@ interface ChatRequest {
|
|
|
161
179
|
}
|
|
162
180
|
interface CreateProjectBody {
|
|
163
181
|
description: string;
|
|
164
|
-
tech_stack
|
|
182
|
+
tech_stack?: string;
|
|
165
183
|
visibility?: ProjectVisibility;
|
|
166
184
|
template_project_id?: string;
|
|
167
|
-
initial_message?:
|
|
185
|
+
initial_message?: string;
|
|
168
186
|
category?: string;
|
|
169
187
|
project_type?: string;
|
|
170
188
|
prompt_name?: string;
|
|
@@ -190,8 +208,8 @@ interface InviteCollaboratorOptions {
|
|
|
190
208
|
}
|
|
191
209
|
interface ChatMessageOptions {
|
|
192
210
|
message: string;
|
|
193
|
-
chatOnly?: boolean;
|
|
194
211
|
files?: (File | FileInput)[];
|
|
212
|
+
chatOnly?: boolean;
|
|
195
213
|
customModel?: CustomModelConfig;
|
|
196
214
|
}
|
|
197
215
|
interface LovableClientOptions {
|
|
@@ -289,6 +307,11 @@ declare class LovableClient {
|
|
|
289
307
|
private readonly baseUrl;
|
|
290
308
|
constructor(options: LovableClientOptions);
|
|
291
309
|
private request;
|
|
310
|
+
/**
|
|
311
|
+
* Get the current authenticated user and their workspaces.
|
|
312
|
+
* Useful for validating an API key and discovering workspace IDs.
|
|
313
|
+
*/
|
|
314
|
+
me(): Promise<MeResponse>;
|
|
292
315
|
/**
|
|
293
316
|
* List all workspaces the authenticated user has access to
|
|
294
317
|
*/
|
|
@@ -302,7 +325,8 @@ declare class LovableClient {
|
|
|
302
325
|
*/
|
|
303
326
|
listProjects(workspaceId: string, options?: {
|
|
304
327
|
limit?: number;
|
|
305
|
-
|
|
328
|
+
cursor?: string;
|
|
329
|
+
visibility?: "all" | "draft" | "personal" | "private" | "public" | "workspace";
|
|
306
330
|
}): Promise<ProjectResponse[]>;
|
|
307
331
|
/**
|
|
308
332
|
* Create a new project in a workspace
|
|
@@ -426,35 +450,41 @@ declare class LovableClient {
|
|
|
426
450
|
*/
|
|
427
451
|
waitForResponse(projectId: string, options?: ChatResponseOptions): Promise<ChatResponse>;
|
|
428
452
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* Returns the trace spans associated with the AI response message.
|
|
432
|
-
* The messageId is available from the ChatResponse returned by waitForResponse().
|
|
433
|
-
*
|
|
434
|
-
* Use the `purposes` option to filter which span types are returned.
|
|
435
|
-
* When a purpose has multiple spans (e.g. main_agent across turns),
|
|
436
|
-
* only the last span is returned — it contains the full accumulated context.
|
|
437
|
-
*
|
|
438
|
-
* @param projectId - The project ID
|
|
439
|
-
* @param messageId - The AI message ID (from ChatResponse.messageId)
|
|
440
|
-
* @param options.purposes - Filter spans by purpose (e.g. ["main_agent", "knowledge_rag"])
|
|
441
|
-
* @returns The trace data including filtered spans
|
|
442
|
-
*/
|
|
443
|
-
getMessageTraces(projectId: string, messageId: string, options?: GetMessageTracesOptions): Promise<MessageTracesResponse>;
|
|
444
|
-
/**
|
|
445
|
-
* Fetch traces for multiple messages across projects in parallel.
|
|
446
|
-
*
|
|
447
|
-
* Fires concurrent requests (up to `concurrency` at a time) and collects
|
|
448
|
-
* results. Failed requests are captured in `errors` instead of throwing.
|
|
449
|
-
*
|
|
450
|
-
* @param queries - Array of { projectId, messageId } to fetch
|
|
451
|
-
* @param options.purposes - Filter spans by purpose (applied to all queries)
|
|
452
|
-
* @param options.concurrency - Max parallel requests (default: 5)
|
|
453
|
-
* @returns Object with `traces` map (keyed by messageId) and `errors` map
|
|
453
|
+
* Developer/experimental APIs. These are not part of the stable v1 surface
|
|
454
|
+
* and may change without notice.
|
|
454
455
|
*/
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
get _dev(): {
|
|
457
|
+
/**
|
|
458
|
+
* Fetch Braintrust traces for a specific chat message.
|
|
459
|
+
*
|
|
460
|
+
* Returns the trace spans associated with the AI response message.
|
|
461
|
+
* The messageId is available from the ChatResponse returned by waitForResponse().
|
|
462
|
+
*
|
|
463
|
+
* Use the `purposes` option to filter which span types are returned.
|
|
464
|
+
* When a purpose has multiple spans (e.g. main_agent across turns),
|
|
465
|
+
* only the last span is returned — it contains the full accumulated context.
|
|
466
|
+
*
|
|
467
|
+
* @param projectId - The project ID
|
|
468
|
+
* @param messageId - The AI message ID (from ChatResponse.messageId)
|
|
469
|
+
* @param options.purposes - Filter spans by purpose (e.g. ["main_agent", "knowledge_rag"])
|
|
470
|
+
* @returns The trace data including filtered spans
|
|
471
|
+
*/
|
|
472
|
+
getMessageTraces: (projectId: string, messageId: string, options?: GetMessageTracesOptions) => Promise<MessageTracesResponse>;
|
|
473
|
+
/**
|
|
474
|
+
* Fetch traces for multiple messages across projects in parallel.
|
|
475
|
+
*
|
|
476
|
+
* Fires concurrent requests (up to `concurrency` at a time) and collects
|
|
477
|
+
* results. Failed requests are captured in `errors` instead of throwing.
|
|
478
|
+
*
|
|
479
|
+
* @param queries - Array of { projectId, messageId } to fetch
|
|
480
|
+
* @param options.purposes - Filter spans by purpose (applied to all queries)
|
|
481
|
+
* @param options.concurrency - Max parallel requests (default: 5)
|
|
482
|
+
* @returns Object with `traces` map (keyed by messageId) and `errors` map
|
|
483
|
+
*/
|
|
484
|
+
getMessageTracesBatch: (queries: TraceQuery[], options?: GetMessageTracesOptions & {
|
|
485
|
+
concurrency?: number;
|
|
486
|
+
}) => Promise<BatchTracesResult>;
|
|
487
|
+
};
|
|
458
488
|
private isFileInput;
|
|
459
489
|
private uploadFile;
|
|
460
490
|
private uploadFiles;
|
|
@@ -474,4 +504,4 @@ declare class LovableClient {
|
|
|
474
504
|
waitForProjectPublished(projectId: string, options?: WaitOptions): Promise<ProjectResponse>;
|
|
475
505
|
}
|
|
476
506
|
|
|
477
|
-
export { type AddUserToWorkspaceInputBody, type BatchTracesResult, type ChatMessageOptions, type ChatRequest, type ChatResponse, type ChatResponseOptions, type CreateProjectBody, type CreateProjectOptions, type CustomModelConfig, type DeploymentResponse, type FileInput, type GetMessageTracesOptions, type InviteCollaboratorOptions, LovableClient, type LovableClientOptions, type LovableError, type MemberRole, type MessageTracesResponse, type ProjectResponse, type ProjectStatus, type ProjectVisibility, type RemixJobStatus, type RemixJobStep, type RemixJobStepInfo, type RemixMode, type RemixProjectOptions, type RemixResult, type RemixWaitOptions, type TracePurpose, type TraceQuery, type TraceSpan, type UnscopedFile, type WaitOptions, type WorkspaceMembership, type WorkspaceMembershipResponse, type WorkspaceWithMembership };
|
|
507
|
+
export { type AddUserToWorkspaceInputBody, type BatchTracesResult, type ChatMessageOptions, type ChatRequest, type ChatResponse, type ChatResponseOptions, type CreateProjectBody, type CreateProjectOptions, type CustomModelConfig, type DeploymentResponse, type FileInput, type GetMessageTracesOptions, type InviteCollaboratorOptions, LovableClient, type LovableClientOptions, type LovableError, type MeResponse, type MeWorkspace, type MemberRole, type MessageTracesResponse, type ProjectResponse, type ProjectStatus, type ProjectVisibility, type RemixJobStatus, type RemixJobStep, type RemixJobStepInfo, type RemixMode, type RemixProjectOptions, type RemixResult, type RemixWaitOptions, type TracePurpose, type TraceQuery, type TraceSpan, type UnscopedFile, type WaitOptions, type WorkspaceMembership, type WorkspaceMembershipResponse, type WorkspaceWithMembership };
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
var DEFAULT_BASE_URL = "https://api.lovable.dev";
|
|
3
3
|
function normalizeBaseUrl(url) {
|
|
4
4
|
if (!url) return DEFAULT_BASE_URL;
|
|
5
|
-
|
|
5
|
+
const normalized = url.replace(/\/$/, "");
|
|
6
6
|
if (!normalized.startsWith("http://") && !normalized.startsWith("https://")) {
|
|
7
|
-
|
|
8
|
-
normalized = isLocalhost ? `http://${normalized}` : `https://${normalized}`;
|
|
7
|
+
throw new Error(`baseUrl must include a protocol (http:// or https://). Got: "${url}"`);
|
|
9
8
|
}
|
|
10
9
|
return normalized;
|
|
11
10
|
}
|
|
@@ -42,26 +41,30 @@ var LovableClient = class {
|
|
|
42
41
|
error.detail = errorBody?.detail;
|
|
43
42
|
throw error;
|
|
44
43
|
}
|
|
45
|
-
if (response.status === 204
|
|
44
|
+
if (response.status === 204) {
|
|
46
45
|
return void 0;
|
|
47
46
|
}
|
|
48
47
|
return response.json();
|
|
49
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the current authenticated user and their workspaces.
|
|
51
|
+
* Useful for validating an API key and discovering workspace IDs.
|
|
52
|
+
*/
|
|
53
|
+
async me() {
|
|
54
|
+
return this.request("GET", "/v1/me");
|
|
55
|
+
}
|
|
50
56
|
/**
|
|
51
57
|
* List all workspaces the authenticated user has access to
|
|
52
58
|
*/
|
|
53
59
|
async listWorkspaces() {
|
|
54
|
-
const response = await this.request("GET", "/
|
|
60
|
+
const response = await this.request("GET", "/v1/workspaces");
|
|
55
61
|
return response.workspaces ?? [];
|
|
56
62
|
}
|
|
57
63
|
/**
|
|
58
64
|
* Get a specific workspace by ID
|
|
59
65
|
*/
|
|
60
66
|
async getWorkspace(workspaceId) {
|
|
61
|
-
const response = await this.request(
|
|
62
|
-
"GET",
|
|
63
|
-
`/user/workspaces/${workspaceId}`
|
|
64
|
-
);
|
|
67
|
+
const response = await this.request("GET", `/v1/workspaces/${workspaceId}`);
|
|
65
68
|
return response.workspace;
|
|
66
69
|
}
|
|
67
70
|
/**
|
|
@@ -70,9 +73,10 @@ var LovableClient = class {
|
|
|
70
73
|
async listProjects(workspaceId, options) {
|
|
71
74
|
const params = new URLSearchParams();
|
|
72
75
|
if (options?.limit) params.set("limit", options.limit.toString());
|
|
76
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
73
77
|
if (options?.visibility) params.set("visibility", options.visibility);
|
|
74
78
|
const query = params.toString();
|
|
75
|
-
const path = `/workspaces/${workspaceId}/projects
|
|
79
|
+
const path = `/v1/workspaces/${workspaceId}/projects${query ? `?${query}` : ""}`;
|
|
76
80
|
const response = await this.request("GET", path);
|
|
77
81
|
return response.projects ?? [];
|
|
78
82
|
}
|
|
@@ -80,26 +84,31 @@ var LovableClient = class {
|
|
|
80
84
|
* Create a new project in a workspace
|
|
81
85
|
*/
|
|
82
86
|
async createProject(workspaceId, options) {
|
|
87
|
+
let uploadedFiles;
|
|
88
|
+
if (options.files?.length) {
|
|
89
|
+
uploadedFiles = await this.uploadFiles(options.files);
|
|
90
|
+
}
|
|
83
91
|
const body = {
|
|
84
92
|
description: options.description,
|
|
85
|
-
tech_stack: options.techStack ?? "",
|
|
86
93
|
visibility: options.visibility ?? "private",
|
|
87
94
|
template_project_id: options.templateProjectId
|
|
88
95
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
uploadedFiles = await this.uploadFiles(options.files);
|
|
96
|
+
if (options.techStack) {
|
|
97
|
+
body.tech_stack = options.techStack;
|
|
92
98
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
const hasFiles = uploadedFiles && uploadedFiles.length > 0;
|
|
100
|
+
if (options.initialMessage && !hasFiles) {
|
|
101
|
+
body.initial_message = options.initialMessage;
|
|
102
|
+
}
|
|
103
|
+
const project = await this.request("POST", `/v1/workspaces/${workspaceId}/projects`, body);
|
|
104
|
+
if (hasFiles && options.initialMessage) {
|
|
105
|
+
const chatBody = {
|
|
106
|
+
message: options.initialMessage,
|
|
99
107
|
files: uploadedFiles
|
|
100
108
|
};
|
|
109
|
+
await this.request("POST", `/v1/projects/${project.id}/messages`, chatBody);
|
|
101
110
|
}
|
|
102
|
-
return
|
|
111
|
+
return project;
|
|
103
112
|
}
|
|
104
113
|
/**
|
|
105
114
|
* Send a chat message to a project
|
|
@@ -113,18 +122,20 @@ var LovableClient = class {
|
|
|
113
122
|
uploadedFiles = await this.uploadFiles(options.files);
|
|
114
123
|
}
|
|
115
124
|
const body = {
|
|
116
|
-
|
|
117
|
-
message: options.message,
|
|
118
|
-
chat_only: options.chatOnly ?? false,
|
|
119
|
-
headless: true,
|
|
120
|
-
files: uploadedFiles,
|
|
121
|
-
...options.customModel && {
|
|
122
|
-
custom_model_endpoint: options.customModel.endpoint,
|
|
123
|
-
custom_model_api_key: options.customModel.apiKey,
|
|
124
|
-
custom_model_name: options.customModel.modelName
|
|
125
|
-
}
|
|
125
|
+
message: options.message
|
|
126
126
|
};
|
|
127
|
-
|
|
127
|
+
if (uploadedFiles) {
|
|
128
|
+
body.files = uploadedFiles;
|
|
129
|
+
}
|
|
130
|
+
if (options.chatOnly) {
|
|
131
|
+
body.chat_only = true;
|
|
132
|
+
}
|
|
133
|
+
if (options.customModel) {
|
|
134
|
+
body.custom_model_endpoint = options.customModel.endpoint;
|
|
135
|
+
body.custom_model_api_key = options.customModel.apiKey;
|
|
136
|
+
body.custom_model_name = options.customModel.modelName;
|
|
137
|
+
}
|
|
138
|
+
await this.request("POST", `/v1/projects/${projectId}/messages`, body);
|
|
128
139
|
}
|
|
129
140
|
/**
|
|
130
141
|
* Invite a user to a workspace as a collaborator
|
|
@@ -153,7 +164,7 @@ var LovableClient = class {
|
|
|
153
164
|
* Get project details by ID
|
|
154
165
|
*/
|
|
155
166
|
async getProject(projectId) {
|
|
156
|
-
return this.request("GET", `/projects/${projectId}
|
|
167
|
+
return this.request("GET", `/v1/projects/${projectId}`);
|
|
157
168
|
}
|
|
158
169
|
/**
|
|
159
170
|
* Get the preview URL for a project.
|
|
@@ -190,7 +201,7 @@ var LovableClient = class {
|
|
|
190
201
|
* @returns Deployment info including deployment ID
|
|
191
202
|
*/
|
|
192
203
|
async publish(projectId, options) {
|
|
193
|
-
return this.request("POST", `/projects/${projectId}/deployments`, {
|
|
204
|
+
return this.request("POST", `/v1/projects/${projectId}/deployments`, {
|
|
194
205
|
name: options?.name
|
|
195
206
|
});
|
|
196
207
|
}
|
|
@@ -318,7 +329,7 @@ var LovableClient = class {
|
|
|
318
329
|
*/
|
|
319
330
|
async waitForResponse(projectId, options) {
|
|
320
331
|
const timeout = options?.timeout ?? 3e5;
|
|
321
|
-
const url = `${this.baseUrl}/projects/${projectId}/
|
|
332
|
+
const url = `${this.baseUrl}/v1/projects/${projectId}/messages/stream`;
|
|
322
333
|
const controller = new AbortController();
|
|
323
334
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
324
335
|
try {
|
|
@@ -352,75 +363,86 @@ var LovableClient = class {
|
|
|
352
363
|
}
|
|
353
364
|
}
|
|
354
365
|
/**
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* Returns the trace spans associated with the AI response message.
|
|
358
|
-
* The messageId is available from the ChatResponse returned by waitForResponse().
|
|
359
|
-
*
|
|
360
|
-
* Use the `purposes` option to filter which span types are returned.
|
|
361
|
-
* When a purpose has multiple spans (e.g. main_agent across turns),
|
|
362
|
-
* only the last span is returned — it contains the full accumulated context.
|
|
363
|
-
*
|
|
364
|
-
* @param projectId - The project ID
|
|
365
|
-
* @param messageId - The AI message ID (from ChatResponse.messageId)
|
|
366
|
-
* @param options.purposes - Filter spans by purpose (e.g. ["main_agent", "knowledge_rag"])
|
|
367
|
-
* @returns The trace data including filtered spans
|
|
368
|
-
*/
|
|
369
|
-
async getMessageTraces(projectId, messageId, options) {
|
|
370
|
-
const params = new URLSearchParams();
|
|
371
|
-
if (options?.purposes?.length) {
|
|
372
|
-
params.set("purposes", options.purposes.join(","));
|
|
373
|
-
}
|
|
374
|
-
const query = params.toString();
|
|
375
|
-
const path = `/projects/${projectId}/messages/${messageId}/traces${query ? `?${query}` : ""}`;
|
|
376
|
-
return this.request("GET", path);
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Fetch traces for multiple messages across projects in parallel.
|
|
380
|
-
*
|
|
381
|
-
* Fires concurrent requests (up to `concurrency` at a time) and collects
|
|
382
|
-
* results. Failed requests are captured in `errors` instead of throwing.
|
|
383
|
-
*
|
|
384
|
-
* @param queries - Array of { projectId, messageId } to fetch
|
|
385
|
-
* @param options.purposes - Filter spans by purpose (applied to all queries)
|
|
386
|
-
* @param options.concurrency - Max parallel requests (default: 5)
|
|
387
|
-
* @returns Object with `traces` map (keyed by messageId) and `errors` map
|
|
366
|
+
* Developer/experimental APIs. These are not part of the stable v1 surface
|
|
367
|
+
* and may change without notice.
|
|
388
368
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
369
|
+
get _dev() {
|
|
370
|
+
return {
|
|
371
|
+
/**
|
|
372
|
+
* Fetch Braintrust traces for a specific chat message.
|
|
373
|
+
*
|
|
374
|
+
* Returns the trace spans associated with the AI response message.
|
|
375
|
+
* The messageId is available from the ChatResponse returned by waitForResponse().
|
|
376
|
+
*
|
|
377
|
+
* Use the `purposes` option to filter which span types are returned.
|
|
378
|
+
* When a purpose has multiple spans (e.g. main_agent across turns),
|
|
379
|
+
* only the last span is returned — it contains the full accumulated context.
|
|
380
|
+
*
|
|
381
|
+
* @param projectId - The project ID
|
|
382
|
+
* @param messageId - The AI message ID (from ChatResponse.messageId)
|
|
383
|
+
* @param options.purposes - Filter spans by purpose (e.g. ["main_agent", "knowledge_rag"])
|
|
384
|
+
* @returns The trace data including filtered spans
|
|
385
|
+
*/
|
|
386
|
+
getMessageTraces: async (projectId, messageId, options) => {
|
|
387
|
+
const params = new URLSearchParams();
|
|
388
|
+
if (options?.purposes?.length) {
|
|
389
|
+
params.set("purposes", options.purposes.join(","));
|
|
390
|
+
}
|
|
391
|
+
const query = params.toString();
|
|
392
|
+
const path = `/v1/_dev/projects/${projectId}/messages/${messageId}/traces${query ? `?${query}` : ""}`;
|
|
393
|
+
return this.request("GET", path);
|
|
394
|
+
},
|
|
395
|
+
/**
|
|
396
|
+
* Fetch traces for multiple messages across projects in parallel.
|
|
397
|
+
*
|
|
398
|
+
* Fires concurrent requests (up to `concurrency` at a time) and collects
|
|
399
|
+
* results. Failed requests are captured in `errors` instead of throwing.
|
|
400
|
+
*
|
|
401
|
+
* @param queries - Array of { projectId, messageId } to fetch
|
|
402
|
+
* @param options.purposes - Filter spans by purpose (applied to all queries)
|
|
403
|
+
* @param options.concurrency - Max parallel requests (default: 5)
|
|
404
|
+
* @returns Object with `traces` map (keyed by messageId) and `errors` map
|
|
405
|
+
*/
|
|
406
|
+
getMessageTracesBatch: async (queries, options) => {
|
|
407
|
+
const concurrency = options?.concurrency ?? 5;
|
|
408
|
+
const traces = /* @__PURE__ */ new Map();
|
|
409
|
+
const errors = /* @__PURE__ */ new Map();
|
|
410
|
+
const purposeOpts = options?.purposes ? { purposes: options.purposes } : void 0;
|
|
411
|
+
const pending = [...queries];
|
|
412
|
+
const executing = /* @__PURE__ */ new Set();
|
|
413
|
+
for (const query of pending) {
|
|
414
|
+
const task = this._dev.getMessageTraces(query.projectId, query.messageId, purposeOpts).then((result) => {
|
|
415
|
+
traces.set(query.messageId, result);
|
|
416
|
+
}).catch((err) => {
|
|
417
|
+
errors.set(query.messageId, err instanceof Error ? err : new Error(String(err)));
|
|
418
|
+
}).finally(() => {
|
|
419
|
+
executing.delete(task);
|
|
420
|
+
});
|
|
421
|
+
executing.add(task);
|
|
422
|
+
if (executing.size >= concurrency) {
|
|
423
|
+
await Promise.race(executing);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
await Promise.all(executing);
|
|
427
|
+
return { traces, errors };
|
|
407
428
|
}
|
|
408
|
-
}
|
|
409
|
-
await Promise.all(executing);
|
|
410
|
-
return { traces, errors };
|
|
429
|
+
};
|
|
411
430
|
}
|
|
412
431
|
isFileInput(file) {
|
|
413
432
|
return "data" in file;
|
|
414
433
|
}
|
|
415
434
|
async uploadFile(file) {
|
|
416
|
-
const fileId = crypto.randomUUID();
|
|
417
435
|
const fileName = this.isFileInput(file) ? file.name : file.name;
|
|
418
436
|
const mimeType = this.isFileInput(file) ? file.type : file.type;
|
|
419
437
|
const body = this.isFileInput(file) ? file.data : file;
|
|
420
|
-
const { url } = await this.request(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
438
|
+
const { url, file_id: objectPath } = await this.request(
|
|
439
|
+
"POST",
|
|
440
|
+
"/v1/files/upload-url",
|
|
441
|
+
{
|
|
442
|
+
file_name: fileName,
|
|
443
|
+
content_type: mimeType
|
|
444
|
+
}
|
|
445
|
+
);
|
|
424
446
|
const uploadResponse = await fetch(url, {
|
|
425
447
|
method: "PUT",
|
|
426
448
|
body,
|
|
@@ -429,7 +451,7 @@ var LovableClient = class {
|
|
|
429
451
|
if (!uploadResponse.ok) {
|
|
430
452
|
throw new Error(`File upload failed for "${fileName}": HTTP ${uploadResponse.status}`);
|
|
431
453
|
}
|
|
432
|
-
return { file_id:
|
|
454
|
+
return { file_id: objectPath, type: "user_upload", file_name: fileName, mime_type: mimeType };
|
|
433
455
|
}
|
|
434
456
|
async uploadFiles(files) {
|
|
435
457
|
return Promise.all(files.map((file) => this.uploadFile(file)));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n LovableClientOptions,\n LovableError,\n CreateProjectOptions,\n InviteCollaboratorOptions,\n ChatMessageOptions,\n WaitOptions,\n WorkspaceWithMembership,\n ProjectResponse,\n WorkspaceMembershipResponse,\n CreateProjectBody,\n ChatRequest,\n AddUserToWorkspaceInputBody,\n GetWorkspacesResponse,\n GetWorkspaceProjectsResponse,\n DeploymentResponse,\n ChatResponse,\n ChatResponseOptions,\n UnscopedFile,\n FileInput,\n RemixProjectOptions,\n RemixInitBody,\n RemixInitResponse,\n RemixProgressResponse,\n RemixResult,\n RemixWaitOptions,\n MessageTracesResponse,\n GetMessageTracesOptions,\n TraceQuery,\n BatchTracesResult,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.lovable.dev\";\n\nfunction normalizeBaseUrl(url: string | undefined): string {\n if (!url) return DEFAULT_BASE_URL;\n\n let normalized = url.replace(/\\/$/, \"\");\n\n // Add http:// for localhost URLs without protocol\n if (!normalized.startsWith(\"http://\") && !normalized.startsWith(\"https://\")) {\n const isLocalhost = normalized.startsWith(\"localhost\") || normalized.startsWith(\"127.0.0.1\");\n normalized = isLocalhost ? `http://${normalized}` : `https://${normalized}`;\n }\n\n return normalized;\n}\n\nexport class LovableClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(options: LovableClientOptions) {\n if (!options.apiKey) {\n throw new Error(\"API key is required\");\n }\n this.apiKey = options.apiKey;\n this.baseUrl = normalizeBaseUrl(options.baseUrl);\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = {\n \"Lovable-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n let errorBody: { title?: string; detail?: string } | undefined;\n try {\n errorBody = (await response.json()) as { title?: string; detail?: string };\n } catch {\n // Ignore JSON parse errors\n }\n\n const error = new Error(errorBody?.title ?? `HTTP ${response.status}: ${response.statusText}`) as LovableError;\n error.status = response.status;\n error.type = errorBody?.title;\n error.detail = errorBody?.detail;\n throw error;\n }\n\n if (response.status === 204 || response.status === 202) {\n return undefined as T;\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * List all workspaces the authenticated user has access to\n */\n async listWorkspaces(): Promise<WorkspaceWithMembership[]> {\n const response = await this.request<GetWorkspacesResponse>(\"GET\", \"/user/workspaces\");\n return response.workspaces ?? [];\n }\n\n /**\n * Get a specific workspace by ID\n */\n async getWorkspace(workspaceId: string): Promise<WorkspaceWithMembership> {\n const response = await this.request<{ workspace: WorkspaceWithMembership }>(\n \"GET\",\n `/user/workspaces/${workspaceId}`,\n );\n return response.workspace;\n }\n\n /**\n * List projects in a workspace\n */\n async listProjects(\n workspaceId: string,\n options?: { limit?: number; visibility?: \"all\" | \"personal\" | \"public\" | \"workspace\" },\n ): Promise<ProjectResponse[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.visibility) params.set(\"visibility\", options.visibility);\n\n const query = params.toString();\n const path = `/workspaces/${workspaceId}/projects/search${query ? `?${query}` : \"\"}`;\n\n const response = await this.request<GetWorkspaceProjectsResponse>(\"GET\", path);\n return response.projects ?? [];\n }\n\n /**\n * Create a new project in a workspace\n */\n async createProject(workspaceId: string, options: CreateProjectOptions): Promise<ProjectResponse> {\n const body: CreateProjectBody = {\n description: options.description,\n tech_stack: options.techStack ?? \"\",\n visibility: options.visibility ?? \"private\",\n template_project_id: options.templateProjectId,\n };\n\n let uploadedFiles: UnscopedFile[] | undefined;\n if (options.files?.length) {\n uploadedFiles = await this.uploadFiles(options.files);\n }\n\n if (options.initialMessage || uploadedFiles) {\n body.initial_message = {\n id: crypto.randomUUID(),\n message: options.initialMessage ?? options.description,\n chat_only: false,\n headless: true,\n files: uploadedFiles,\n };\n }\n\n return this.request<ProjectResponse>(\"POST\", `/workspaces/${workspaceId}/projects`, body);\n }\n\n /**\n * Send a chat message to a project\n *\n * Note: This sends a message to the project's AI agent. The response is\n * asynchronous - the API accepts the message and processes it in the background.\n */\n async chat(projectId: string, options: ChatMessageOptions): Promise<void> {\n let uploadedFiles: UnscopedFile[] | undefined;\n if (options.files?.length) {\n uploadedFiles = await this.uploadFiles(options.files);\n }\n\n const body: ChatRequest = {\n id: crypto.randomUUID(),\n message: options.message,\n chat_only: options.chatOnly ?? false,\n headless: true,\n files: uploadedFiles,\n ...(options.customModel && {\n custom_model_endpoint: options.customModel.endpoint,\n custom_model_api_key: options.customModel.apiKey,\n custom_model_name: options.customModel.modelName,\n }),\n };\n\n await this.request<void>(\"POST\", `/projects/${projectId}/chat`, body);\n }\n\n /**\n * Invite a user to a workspace as a collaborator\n */\n async inviteCollaborator(\n workspaceId: string,\n options: InviteCollaboratorOptions,\n ): Promise<WorkspaceMembershipResponse> {\n const body: AddUserToWorkspaceInputBody = {\n email: options.email,\n role: options.role ?? \"member\",\n };\n\n return this.request<WorkspaceMembershipResponse>(\"POST\", `/workspaces/${workspaceId}/memberships`, body);\n }\n\n /**\n * List members of a workspace\n */\n async listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMembershipResponse[]> {\n const response = await this.request<{\n memberships: WorkspaceMembershipResponse[] | null;\n }>(\"GET\", `/workspaces/${workspaceId}/memberships`);\n return response.memberships ?? [];\n }\n\n /**\n * Remove a member from a workspace\n */\n async removeWorkspaceMember(workspaceId: string, userId: string): Promise<void> {\n await this.request<void>(\"DELETE\", `/workspaces/${workspaceId}/memberships/${userId}`);\n }\n\n /**\n * Get project details by ID\n */\n async getProject(projectId: string): Promise<ProjectResponse> {\n return this.request<ProjectResponse>(\"GET\", `/projects/${projectId}/details`);\n }\n\n /**\n * Get the preview URL for a project.\n *\n * The preview URL is available once the project reaches \"completed\" status.\n * This URL allows viewing the project in development mode.\n *\n * @param projectId - The project ID\n * @returns The preview URL\n */\n getPreviewUrl(projectId: string): string {\n return `https://id-preview--${projectId}.lovable.app`;\n }\n\n /**\n * Get the published URL for a project (if published).\n *\n * Returns the public URL if the project has been published, or null if not.\n *\n * @param projectId - The project ID\n * @returns The published URL or null if not published\n */\n async getPublishedUrl(projectId: string): Promise<string | null> {\n const project = await this.getProject(projectId);\n return project.is_published && project.url ? project.url : null;\n }\n\n /**\n * Publish a project.\n *\n * This triggers a deployment which makes the project publicly accessible.\n * The deployment runs asynchronously - use waitForProjectPublished() to wait for completion.\n *\n * @param projectId - The project ID to publish\n * @param options.name - Optional custom slug for the published URL\n * @returns Deployment info including deployment ID\n */\n async publish(projectId: string, options?: { name?: string }): Promise<DeploymentResponse> {\n return this.request<DeploymentResponse>(\"POST\", `/projects/${projectId}/deployments`, {\n name: options?.name,\n });\n }\n\n /**\n * Remix (fork) an existing project, optionally at a specific message point in time.\n *\n * When `messageId` is provided, the remix captures the project state as it was\n * just before that message was processed (default). Set `remixMode: \"including\"`\n * to include the message and its AI response in the remix.\n * Without `messageId`, the full current state is remixed.\n *\n * @param sourceProjectId - The project to remix from\n * @param options.workspaceId - Target workspace for the new project\n * @param options.messageId - Optional message ID to snapshot at\n * @param options.remixMode - \"before\" (default): state before the message; \"including\": state after the message and its AI response\n * @param options.includeHistory - Whether to preserve chat history (default: false)\n * @param options.includeCustomKnowledge - Whether to copy custom instructions (default: false)\n * @param options.initialMessage - Optional initial message to send after remix\n * @returns The remix job ID for polling progress\n */\n async remixProject(sourceProjectId: string, options: RemixProjectOptions): Promise<string> {\n const body: RemixInitBody = {\n workspace_id: options.workspaceId,\n include_history: options.includeHistory,\n include_custom_knowledge: options.includeCustomKnowledge,\n skip_initial_remix_message: options.skipInitialRemixMessage,\n skip_integrations: options.skipIntegrations,\n };\n\n if (options.messageId) {\n body.message_id = options.messageId;\n body.remix_mode = options.remixMode ?? \"before\";\n }\n\n if (options.initialMessage) {\n body.initial_message = {\n id: crypto.randomUUID(),\n message: options.initialMessage,\n chat_only: false,\n headless: true,\n };\n }\n\n const response = await this.request<RemixInitResponse>(\"POST\", `/projects/${sourceProjectId}/remix/init`, body);\n return response.job_id;\n }\n\n /**\n * Wait for a remix operation to complete.\n *\n * Polls the remix progress endpoint until the job reaches \"completed\" or \"error\" status.\n *\n * @param sourceProjectId - The source project ID (used for the progress endpoint)\n * @param jobId - The job ID returned by `remixProject()`\n * @param options.pollInterval - Time between polls in ms (default: 2000)\n * @param options.timeout - Maximum time to wait in ms (default: 300000 = 5 minutes)\n * @param options.onProgress - Optional callback for status/step updates\n * @returns The new project ID\n * @throws Error if the remix fails or timeout is reached\n */\n async waitForRemix(sourceProjectId: string, jobId: string, options?: RemixWaitOptions): Promise<RemixResult> {\n const pollInterval = options?.pollInterval ?? 2000;\n const timeout = options?.timeout ?? 300000;\n const startTime = Date.now();\n\n while (true) {\n const progress = await this.request<RemixProgressResponse>(\n \"GET\",\n `/projects/${sourceProjectId}/remix/progress?job_id=${encodeURIComponent(jobId)}`,\n );\n\n options?.onProgress?.(progress.status, progress.step);\n\n if (progress.status === \"completed\" && progress.result) {\n return { projectId: progress.result.project_id };\n }\n\n if (progress.status === \"error\") {\n throw new Error(progress.error_message ?? \"Remix failed\");\n }\n\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for remix of project ${sourceProjectId}`);\n }\n\n await sleep(pollInterval);\n }\n }\n\n /**\n * Wait for a project to reach \"completed\" status.\n *\n * Projects start in \"in_progress\" status while being created/built.\n * This method polls until the status becomes \"completed\" or \"failed\".\n * A successful completion means the project's preview is ready to view.\n *\n * @param projectId - The project ID to wait for\n * @param options.pollInterval - Time between polls in ms (default: 2000)\n * @param options.timeout - Maximum time to wait in ms (default: 300000 = 5 minutes)\n * @param options.onProgress - Optional callback for status updates\n * @returns The completed project\n * @throws Error if project fails or timeout is reached\n */\n async waitForProjectReady(projectId: string, options?: WaitOptions): Promise<ProjectResponse> {\n const pollInterval = options?.pollInterval ?? 2000;\n const timeout = options?.timeout ?? 300000;\n const startTime = Date.now();\n\n while (true) {\n const project = await this.getProject(projectId);\n options?.onProgress?.(project);\n\n if (project.status === \"completed\") {\n return project;\n }\n\n if (project.status === \"failed\") {\n throw new Error(`Project ${projectId} failed to build`);\n }\n\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for project ${projectId} to be ready`);\n }\n\n await sleep(pollInterval);\n }\n }\n\n /**\n * Wait for the AI response to a chat message.\n *\n * Connects to the project's message stream (SSE) and accumulates the\n * response content until the message is complete. Returns the full\n * response text along with the project's preview URL.\n *\n * Use this after `chat()` or after `createProject()` with `initialMessage`.\n *\n * @param projectId - The project ID to listen for\n * @param options.timeout - Maximum time to wait in ms (default: 300000 = 5 minutes)\n * @returns The AI response content and preview URL\n * @throws Error if the stream fails or timeout is reached\n */\n async waitForResponse(projectId: string, options?: ChatResponseOptions): Promise<ChatResponse> {\n const timeout = options?.timeout ?? 300000;\n const url = `${this.baseUrl}/projects/${projectId}/latest-message`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n headers: {\n \"Lovable-API-Key\": this.apiKey,\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const error = new Error(`Failed to connect to message stream: HTTP ${response.status}`) as LovableError;\n error.status = response.status;\n throw error;\n }\n\n if (!response.body) {\n throw new Error(\"Response body is not readable\");\n }\n\n const result = await this.consumeSSEStream(response.body);\n return {\n content: result.content,\n messageId: result.messageId,\n previewUrl: this.getPreviewUrl(projectId),\n };\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new Error(`Timeout waiting for response on project ${projectId}`);\n }\n throw err;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Fetch Braintrust traces for a specific chat message.\n *\n * Returns the trace spans associated with the AI response message.\n * The messageId is available from the ChatResponse returned by waitForResponse().\n *\n * Use the `purposes` option to filter which span types are returned.\n * When a purpose has multiple spans (e.g. main_agent across turns),\n * only the last span is returned — it contains the full accumulated context.\n *\n * @param projectId - The project ID\n * @param messageId - The AI message ID (from ChatResponse.messageId)\n * @param options.purposes - Filter spans by purpose (e.g. [\"main_agent\", \"knowledge_rag\"])\n * @returns The trace data including filtered spans\n */\n async getMessageTraces(\n projectId: string,\n messageId: string,\n options?: GetMessageTracesOptions,\n ): Promise<MessageTracesResponse> {\n const params = new URLSearchParams();\n if (options?.purposes?.length) {\n params.set(\"purposes\", options.purposes.join(\",\"));\n }\n const query = params.toString();\n const path = `/projects/${projectId}/messages/${messageId}/traces${query ? `?${query}` : \"\"}`;\n return this.request<MessageTracesResponse>(\"GET\", path);\n }\n\n /**\n * Fetch traces for multiple messages across projects in parallel.\n *\n * Fires concurrent requests (up to `concurrency` at a time) and collects\n * results. Failed requests are captured in `errors` instead of throwing.\n *\n * @param queries - Array of { projectId, messageId } to fetch\n * @param options.purposes - Filter spans by purpose (applied to all queries)\n * @param options.concurrency - Max parallel requests (default: 5)\n * @returns Object with `traces` map (keyed by messageId) and `errors` map\n */\n async getMessageTracesBatch(\n queries: TraceQuery[],\n options?: GetMessageTracesOptions & { concurrency?: number },\n ): Promise<BatchTracesResult> {\n const concurrency = options?.concurrency ?? 5;\n const traces = new Map<string, MessageTracesResponse>();\n const errors = new Map<string, Error>();\n const purposeOpts = options?.purposes ? { purposes: options.purposes } : undefined;\n\n const pending = [...queries];\n const executing = new Set<Promise<void>>();\n\n for (const query of pending) {\n const task = this.getMessageTraces(query.projectId, query.messageId, purposeOpts)\n .then((result) => {\n traces.set(query.messageId, result);\n })\n .catch((err) => {\n errors.set(query.messageId, err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n executing.delete(task);\n });\n\n executing.add(task);\n\n if (executing.size >= concurrency) {\n await Promise.race(executing);\n }\n }\n\n await Promise.all(executing);\n\n return { traces, errors };\n }\n\n private isFileInput(file: File | FileInput): file is FileInput {\n return \"data\" in file;\n }\n\n private async uploadFile(file: File | FileInput): Promise<UnscopedFile> {\n const fileId = crypto.randomUUID();\n const fileName = this.isFileInput(file) ? file.name : file.name;\n const mimeType = this.isFileInput(file) ? file.type : file.type;\n const body = this.isFileInput(file) ? file.data : file;\n\n const { url } = await this.request<{ url: string }>(\"POST\", \"/files/generate-upload-url\", {\n file_name: fileId,\n content_type: mimeType,\n });\n\n const uploadResponse = await fetch(url, {\n method: \"PUT\",\n body,\n headers: { \"Content-Type\": mimeType },\n });\n if (!uploadResponse.ok) {\n throw new Error(`File upload failed for \"${fileName}\": HTTP ${uploadResponse.status}`);\n }\n\n return { file_id: fileId, type: \"user_upload\", file_name: fileName, mime_type: mimeType };\n }\n\n private async uploadFiles(files: (File | FileInput)[]): Promise<UnscopedFile[]> {\n return Promise.all(files.map((file) => this.uploadFile(file)));\n }\n\n private async consumeSSEStream(body: ReadableStream<Uint8Array>): Promise<{ content: string; messageId: string }> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let content = \"\";\n let messageId = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // SSE events are separated by double newlines\n const parts = buffer.split(\"\\n\\n\");\n buffer = parts.pop() ?? \"\";\n\n for (const part of parts) {\n if (!part.trim()) continue;\n\n const lines = part.split(\"\\n\");\n let eventType = \"\";\n let eventData = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n eventType = line.slice(7);\n } else if (line.startsWith(\"data: \")) {\n eventData = line.slice(6);\n }\n }\n\n if (eventType === \"message\" && eventData) {\n try {\n const data = JSON.parse(eventData);\n if (typeof data.content === \"string\") {\n content += data.content;\n }\n if (typeof data.message_id === \"string\" && data.message_id) {\n messageId = data.message_id;\n }\n if (data.is_final) {\n return { content, messageId };\n }\n } catch {\n // Skip non-JSON data lines\n }\n }\n\n if (eventType === \"error\") {\n let detail = \"Stream error from server\";\n try {\n const data = JSON.parse(eventData);\n if (data.message) detail = data.message;\n } catch {\n // use default message\n }\n throw new Error(detail);\n }\n }\n }\n } finally {\n void reader.cancel();\n }\n\n return { content, messageId };\n }\n\n /**\n * Wait for a project to be published (deployed).\n *\n * This method polls until the project has `is_published: true` and a `url`.\n *\n * @param projectId - The project ID to wait for\n * @param options.pollInterval - Time between polls in ms (default: 3000)\n * @param options.timeout - Maximum time to wait in ms (default: 600000 = 10 minutes)\n * @param options.onProgress - Optional callback for status updates\n * @returns The published project with URL\n * @throws Error if timeout is reached\n */\n async waitForProjectPublished(projectId: string, options?: WaitOptions): Promise<ProjectResponse> {\n const pollInterval = options?.pollInterval ?? 3000;\n const timeout = options?.timeout ?? 600000;\n const startTime = Date.now();\n\n while (true) {\n const project = await this.getProject(projectId);\n options?.onProgress?.(project);\n\n if (project.is_published && project.url) {\n return project;\n }\n\n if (project.status === \"failed\") {\n throw new Error(`Project ${projectId} failed to build`);\n }\n\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for project ${projectId} to be published`);\n }\n\n await sleep(pollInterval);\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type {\n LovableClientOptions,\n LovableError,\n CreateProjectOptions,\n InviteCollaboratorOptions,\n ChatMessageOptions,\n WaitOptions,\n WorkspaceWithMembership,\n ProjectResponse,\n WorkspaceMembershipResponse,\n CreateProjectBody,\n ChatRequest,\n AddUserToWorkspaceInputBody,\n GetWorkspacesResponse,\n GetWorkspaceProjectsResponse,\n DeploymentResponse,\n ChatResponse,\n ChatResponseOptions,\n UnscopedFile,\n FileInput,\n RemixProjectOptions,\n RemixInitBody,\n RemixInitResponse,\n RemixProgressResponse,\n RemixResult,\n RemixWaitOptions,\n MessageTracesResponse,\n GetMessageTracesOptions,\n TraceQuery,\n BatchTracesResult,\n MeResponse,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.lovable.dev\";\n\nfunction normalizeBaseUrl(url: string | undefined): string {\n if (!url) return DEFAULT_BASE_URL;\n\n const normalized = url.replace(/\\/$/, \"\");\n\n if (!normalized.startsWith(\"http://\") && !normalized.startsWith(\"https://\")) {\n throw new Error(`baseUrl must include a protocol (http:// or https://). Got: \"${url}\"`);\n }\n\n return normalized;\n}\n\nexport class LovableClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(options: LovableClientOptions) {\n if (!options.apiKey) {\n throw new Error(\"API key is required\");\n }\n this.apiKey = options.apiKey;\n this.baseUrl = normalizeBaseUrl(options.baseUrl);\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n\n const headers: Record<string, string> = {\n \"Lovable-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n };\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n let errorBody: { title?: string; detail?: string } | undefined;\n try {\n errorBody = (await response.json()) as { title?: string; detail?: string };\n } catch {\n // Ignore JSON parse errors\n }\n\n const error = new Error(errorBody?.title ?? `HTTP ${response.status}: ${response.statusText}`) as LovableError;\n error.status = response.status;\n error.type = errorBody?.title;\n error.detail = errorBody?.detail;\n throw error;\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * Get the current authenticated user and their workspaces.\n * Useful for validating an API key and discovering workspace IDs.\n */\n async me(): Promise<MeResponse> {\n return this.request<MeResponse>(\"GET\", \"/v1/me\");\n }\n\n /**\n * List all workspaces the authenticated user has access to\n */\n async listWorkspaces(): Promise<WorkspaceWithMembership[]> {\n const response = await this.request<GetWorkspacesResponse>(\"GET\", \"/v1/workspaces\");\n return response.workspaces ?? [];\n }\n\n /**\n * Get a specific workspace by ID\n */\n async getWorkspace(workspaceId: string): Promise<WorkspaceWithMembership> {\n const response = await this.request<{ workspace: WorkspaceWithMembership }>(\"GET\", `/v1/workspaces/${workspaceId}`);\n return response.workspace;\n }\n\n /**\n * List projects in a workspace\n */\n async listProjects(\n workspaceId: string,\n options?: {\n limit?: number;\n cursor?: string;\n visibility?: \"all\" | \"draft\" | \"personal\" | \"private\" | \"public\" | \"workspace\";\n },\n ): Promise<ProjectResponse[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.cursor) params.set(\"cursor\", options.cursor);\n if (options?.visibility) params.set(\"visibility\", options.visibility);\n\n const query = params.toString();\n const path = `/v1/workspaces/${workspaceId}/projects${query ? `?${query}` : \"\"}`;\n\n const response = await this.request<GetWorkspaceProjectsResponse>(\"GET\", path);\n return response.projects ?? [];\n }\n\n /**\n * Create a new project in a workspace\n */\n async createProject(workspaceId: string, options: CreateProjectOptions): Promise<ProjectResponse> {\n let uploadedFiles: UnscopedFile[] | undefined;\n if (options.files?.length) {\n uploadedFiles = await this.uploadFiles(options.files);\n }\n\n const body: CreateProjectBody = {\n description: options.description,\n visibility: options.visibility ?? \"private\",\n template_project_id: options.templateProjectId,\n };\n if (options.techStack) {\n body.tech_stack = options.techStack;\n }\n\n // The v1 create endpoint only accepts initial_message as a plain string,\n // so when files are attached we send the message via chat() after creation\n // to include the file references.\n const hasFiles = uploadedFiles && uploadedFiles.length > 0;\n if (options.initialMessage && !hasFiles) {\n body.initial_message = options.initialMessage;\n }\n\n const project = await this.request<ProjectResponse>(\"POST\", `/v1/workspaces/${workspaceId}/projects`, body);\n\n if (hasFiles && options.initialMessage) {\n const chatBody: { message: string; files?: UnscopedFile[] } = {\n message: options.initialMessage,\n files: uploadedFiles,\n };\n await this.request<void>(\"POST\", `/v1/projects/${project.id}/messages`, chatBody);\n }\n\n return project;\n }\n\n /**\n * Send a chat message to a project\n *\n * Note: This sends a message to the project's AI agent. The response is\n * asynchronous - the API accepts the message and processes it in the background.\n */\n async chat(projectId: string, options: ChatMessageOptions): Promise<void> {\n let uploadedFiles: UnscopedFile[] | undefined;\n if (options.files?.length) {\n uploadedFiles = await this.uploadFiles(options.files);\n }\n\n const body: {\n message: string;\n files?: UnscopedFile[];\n chat_only?: boolean;\n custom_model_endpoint?: string;\n custom_model_api_key?: string;\n custom_model_name?: string;\n } = {\n message: options.message,\n };\n if (uploadedFiles) {\n body.files = uploadedFiles;\n }\n if (options.chatOnly) {\n body.chat_only = true;\n }\n if (options.customModel) {\n body.custom_model_endpoint = options.customModel.endpoint;\n body.custom_model_api_key = options.customModel.apiKey;\n body.custom_model_name = options.customModel.modelName;\n }\n\n await this.request<void>(\"POST\", `/v1/projects/${projectId}/messages`, body);\n }\n\n /**\n * Invite a user to a workspace as a collaborator\n */\n async inviteCollaborator(\n workspaceId: string,\n options: InviteCollaboratorOptions,\n ): Promise<WorkspaceMembershipResponse> {\n const body: AddUserToWorkspaceInputBody = {\n email: options.email,\n role: options.role ?? \"member\",\n };\n\n return this.request<WorkspaceMembershipResponse>(\"POST\", `/workspaces/${workspaceId}/memberships`, body);\n }\n\n /**\n * List members of a workspace\n */\n async listWorkspaceMembers(workspaceId: string): Promise<WorkspaceMembershipResponse[]> {\n const response = await this.request<{\n memberships: WorkspaceMembershipResponse[] | null;\n }>(\"GET\", `/workspaces/${workspaceId}/memberships`);\n return response.memberships ?? [];\n }\n\n /**\n * Remove a member from a workspace\n */\n async removeWorkspaceMember(workspaceId: string, userId: string): Promise<void> {\n await this.request<void>(\"DELETE\", `/workspaces/${workspaceId}/memberships/${userId}`);\n }\n\n /**\n * Get project details by ID\n */\n async getProject(projectId: string): Promise<ProjectResponse> {\n return this.request<ProjectResponse>(\"GET\", `/v1/projects/${projectId}`);\n }\n\n /**\n * Get the preview URL for a project.\n *\n * The preview URL is available once the project reaches \"completed\" status.\n * This URL allows viewing the project in development mode.\n *\n * @param projectId - The project ID\n * @returns The preview URL\n */\n getPreviewUrl(projectId: string): string {\n return `https://id-preview--${projectId}.lovable.app`;\n }\n\n /**\n * Get the published URL for a project (if published).\n *\n * Returns the public URL if the project has been published, or null if not.\n *\n * @param projectId - The project ID\n * @returns The published URL or null if not published\n */\n async getPublishedUrl(projectId: string): Promise<string | null> {\n const project = await this.getProject(projectId);\n return project.is_published && project.url ? project.url : null;\n }\n\n /**\n * Publish a project.\n *\n * This triggers a deployment which makes the project publicly accessible.\n * The deployment runs asynchronously - use waitForProjectPublished() to wait for completion.\n *\n * @param projectId - The project ID to publish\n * @param options.name - Optional custom slug for the published URL\n * @returns Deployment info including deployment ID\n */\n async publish(projectId: string, options?: { name?: string }): Promise<DeploymentResponse> {\n return this.request<DeploymentResponse>(\"POST\", `/v1/projects/${projectId}/deployments`, {\n name: options?.name,\n });\n }\n\n /**\n * Remix (fork) an existing project, optionally at a specific message point in time.\n *\n * When `messageId` is provided, the remix captures the project state as it was\n * just before that message was processed (default). Set `remixMode: \"including\"`\n * to include the message and its AI response in the remix.\n * Without `messageId`, the full current state is remixed.\n *\n * @param sourceProjectId - The project to remix from\n * @param options.workspaceId - Target workspace for the new project\n * @param options.messageId - Optional message ID to snapshot at\n * @param options.remixMode - \"before\" (default): state before the message; \"including\": state after the message and its AI response\n * @param options.includeHistory - Whether to preserve chat history (default: false)\n * @param options.includeCustomKnowledge - Whether to copy custom instructions (default: false)\n * @param options.initialMessage - Optional initial message to send after remix\n * @returns The remix job ID for polling progress\n */\n async remixProject(sourceProjectId: string, options: RemixProjectOptions): Promise<string> {\n const body: RemixInitBody = {\n workspace_id: options.workspaceId,\n include_history: options.includeHistory,\n include_custom_knowledge: options.includeCustomKnowledge,\n skip_initial_remix_message: options.skipInitialRemixMessage,\n skip_integrations: options.skipIntegrations,\n };\n\n if (options.messageId) {\n body.message_id = options.messageId;\n body.remix_mode = options.remixMode ?? \"before\";\n }\n\n if (options.initialMessage) {\n body.initial_message = {\n id: crypto.randomUUID(),\n message: options.initialMessage,\n chat_only: false,\n headless: true,\n };\n }\n\n const response = await this.request<RemixInitResponse>(\"POST\", `/projects/${sourceProjectId}/remix/init`, body);\n return response.job_id;\n }\n\n /**\n * Wait for a remix operation to complete.\n *\n * Polls the remix progress endpoint until the job reaches \"completed\" or \"error\" status.\n *\n * @param sourceProjectId - The source project ID (used for the progress endpoint)\n * @param jobId - The job ID returned by `remixProject()`\n * @param options.pollInterval - Time between polls in ms (default: 2000)\n * @param options.timeout - Maximum time to wait in ms (default: 300000 = 5 minutes)\n * @param options.onProgress - Optional callback for status/step updates\n * @returns The new project ID\n * @throws Error if the remix fails or timeout is reached\n */\n async waitForRemix(sourceProjectId: string, jobId: string, options?: RemixWaitOptions): Promise<RemixResult> {\n const pollInterval = options?.pollInterval ?? 2000;\n const timeout = options?.timeout ?? 300000;\n const startTime = Date.now();\n\n while (true) {\n const progress = await this.request<RemixProgressResponse>(\n \"GET\",\n `/projects/${sourceProjectId}/remix/progress?job_id=${encodeURIComponent(jobId)}`,\n );\n\n options?.onProgress?.(progress.status, progress.step);\n\n if (progress.status === \"completed\" && progress.result) {\n return { projectId: progress.result.project_id };\n }\n\n if (progress.status === \"error\") {\n throw new Error(progress.error_message ?? \"Remix failed\");\n }\n\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for remix of project ${sourceProjectId}`);\n }\n\n await sleep(pollInterval);\n }\n }\n\n /**\n * Wait for a project to reach \"completed\" status.\n *\n * Projects start in \"in_progress\" status while being created/built.\n * This method polls until the status becomes \"completed\" or \"failed\".\n * A successful completion means the project's preview is ready to view.\n *\n * @param projectId - The project ID to wait for\n * @param options.pollInterval - Time between polls in ms (default: 2000)\n * @param options.timeout - Maximum time to wait in ms (default: 300000 = 5 minutes)\n * @param options.onProgress - Optional callback for status updates\n * @returns The completed project\n * @throws Error if project fails or timeout is reached\n */\n async waitForProjectReady(projectId: string, options?: WaitOptions): Promise<ProjectResponse> {\n const pollInterval = options?.pollInterval ?? 2000;\n const timeout = options?.timeout ?? 300000;\n const startTime = Date.now();\n\n while (true) {\n const project = await this.getProject(projectId);\n options?.onProgress?.(project);\n\n if (project.status === \"completed\") {\n return project;\n }\n\n if (project.status === \"failed\") {\n throw new Error(`Project ${projectId} failed to build`);\n }\n\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for project ${projectId} to be ready`);\n }\n\n await sleep(pollInterval);\n }\n }\n\n /**\n * Wait for the AI response to a chat message.\n *\n * Connects to the project's message stream (SSE) and accumulates the\n * response content until the message is complete. Returns the full\n * response text along with the project's preview URL.\n *\n * Use this after `chat()` or after `createProject()` with `initialMessage`.\n *\n * @param projectId - The project ID to listen for\n * @param options.timeout - Maximum time to wait in ms (default: 300000 = 5 minutes)\n * @returns The AI response content and preview URL\n * @throws Error if the stream fails or timeout is reached\n */\n async waitForResponse(projectId: string, options?: ChatResponseOptions): Promise<ChatResponse> {\n const timeout = options?.timeout ?? 300000;\n const url = `${this.baseUrl}/v1/projects/${projectId}/messages/stream`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(url, {\n headers: {\n \"Lovable-API-Key\": this.apiKey,\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const error = new Error(`Failed to connect to message stream: HTTP ${response.status}`) as LovableError;\n error.status = response.status;\n throw error;\n }\n\n if (!response.body) {\n throw new Error(\"Response body is not readable\");\n }\n\n const result = await this.consumeSSEStream(response.body);\n return {\n content: result.content,\n messageId: result.messageId,\n previewUrl: this.getPreviewUrl(projectId),\n };\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new Error(`Timeout waiting for response on project ${projectId}`);\n }\n throw err;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Developer/experimental APIs. These are not part of the stable v1 surface\n * and may change without notice.\n */\n get _dev() {\n return {\n /**\n * Fetch Braintrust traces for a specific chat message.\n *\n * Returns the trace spans associated with the AI response message.\n * The messageId is available from the ChatResponse returned by waitForResponse().\n *\n * Use the `purposes` option to filter which span types are returned.\n * When a purpose has multiple spans (e.g. main_agent across turns),\n * only the last span is returned — it contains the full accumulated context.\n *\n * @param projectId - The project ID\n * @param messageId - The AI message ID (from ChatResponse.messageId)\n * @param options.purposes - Filter spans by purpose (e.g. [\"main_agent\", \"knowledge_rag\"])\n * @returns The trace data including filtered spans\n */\n getMessageTraces: async (\n projectId: string,\n messageId: string,\n options?: GetMessageTracesOptions,\n ): Promise<MessageTracesResponse> => {\n const params = new URLSearchParams();\n if (options?.purposes?.length) {\n params.set(\"purposes\", options.purposes.join(\",\"));\n }\n const query = params.toString();\n const path = `/v1/_dev/projects/${projectId}/messages/${messageId}/traces${query ? `?${query}` : \"\"}`;\n return this.request<MessageTracesResponse>(\"GET\", path);\n },\n\n /**\n * Fetch traces for multiple messages across projects in parallel.\n *\n * Fires concurrent requests (up to `concurrency` at a time) and collects\n * results. Failed requests are captured in `errors` instead of throwing.\n *\n * @param queries - Array of { projectId, messageId } to fetch\n * @param options.purposes - Filter spans by purpose (applied to all queries)\n * @param options.concurrency - Max parallel requests (default: 5)\n * @returns Object with `traces` map (keyed by messageId) and `errors` map\n */\n getMessageTracesBatch: async (\n queries: TraceQuery[],\n options?: GetMessageTracesOptions & { concurrency?: number },\n ): Promise<BatchTracesResult> => {\n const concurrency = options?.concurrency ?? 5;\n const traces = new Map<string, MessageTracesResponse>();\n const errors = new Map<string, Error>();\n const purposeOpts = options?.purposes ? { purposes: options.purposes } : undefined;\n\n const pending = [...queries];\n const executing = new Set<Promise<void>>();\n\n for (const query of pending) {\n const task = this._dev\n .getMessageTraces(query.projectId, query.messageId, purposeOpts)\n .then((result) => {\n traces.set(query.messageId, result);\n })\n .catch((err) => {\n errors.set(query.messageId, err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n executing.delete(task);\n });\n\n executing.add(task);\n\n if (executing.size >= concurrency) {\n await Promise.race(executing);\n }\n }\n\n await Promise.all(executing);\n\n return { traces, errors };\n },\n };\n }\n\n private isFileInput(file: File | FileInput): file is FileInput {\n return \"data\" in file;\n }\n\n private async uploadFile(file: File | FileInput): Promise<UnscopedFile> {\n const fileName = this.isFileInput(file) ? file.name : file.name;\n const mimeType = this.isFileInput(file) ? file.type : file.type;\n const body = this.isFileInput(file) ? file.data : file;\n\n const { url, file_id: objectPath } = await this.request<{ url: string; file_id: string }>(\n \"POST\",\n \"/v1/files/upload-url\",\n {\n file_name: fileName,\n content_type: mimeType,\n },\n );\n\n const uploadResponse = await fetch(url, {\n method: \"PUT\",\n body,\n headers: { \"Content-Type\": mimeType },\n });\n if (!uploadResponse.ok) {\n throw new Error(`File upload failed for \"${fileName}\": HTTP ${uploadResponse.status}`);\n }\n\n return { file_id: objectPath, type: \"user_upload\", file_name: fileName, mime_type: mimeType };\n }\n\n private async uploadFiles(files: (File | FileInput)[]): Promise<UnscopedFile[]> {\n return Promise.all(files.map((file) => this.uploadFile(file)));\n }\n\n private async consumeSSEStream(body: ReadableStream<Uint8Array>): Promise<{ content: string; messageId: string }> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let content = \"\";\n let messageId = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // SSE events are separated by double newlines\n const parts = buffer.split(\"\\n\\n\");\n buffer = parts.pop() ?? \"\";\n\n for (const part of parts) {\n if (!part.trim()) continue;\n\n const lines = part.split(\"\\n\");\n let eventType = \"\";\n let eventData = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n eventType = line.slice(7);\n } else if (line.startsWith(\"data: \")) {\n eventData = line.slice(6);\n }\n }\n\n if (eventType === \"message\" && eventData) {\n try {\n const data = JSON.parse(eventData);\n if (typeof data.content === \"string\") {\n content += data.content;\n }\n if (typeof data.message_id === \"string\" && data.message_id) {\n messageId = data.message_id;\n }\n if (data.is_final) {\n return { content, messageId };\n }\n } catch {\n // Skip non-JSON data lines\n }\n }\n\n if (eventType === \"error\") {\n let detail = \"Stream error from server\";\n try {\n const data = JSON.parse(eventData);\n if (data.message) detail = data.message;\n } catch {\n // use default message\n }\n throw new Error(detail);\n }\n }\n }\n } finally {\n void reader.cancel();\n }\n\n return { content, messageId };\n }\n\n /**\n * Wait for a project to be published (deployed).\n *\n * This method polls until the project has `is_published: true` and a `url`.\n *\n * @param projectId - The project ID to wait for\n * @param options.pollInterval - Time between polls in ms (default: 3000)\n * @param options.timeout - Maximum time to wait in ms (default: 600000 = 10 minutes)\n * @param options.onProgress - Optional callback for status updates\n * @returns The published project with URL\n * @throws Error if timeout is reached\n */\n async waitForProjectPublished(projectId: string, options?: WaitOptions): Promise<ProjectResponse> {\n const pollInterval = options?.pollInterval ?? 3000;\n const timeout = options?.timeout ?? 600000;\n const startTime = Date.now();\n\n while (true) {\n const project = await this.getProject(projectId);\n options?.onProgress?.(project);\n\n if (project.is_published && project.url) {\n return project;\n }\n\n if (project.status === \"failed\") {\n throw new Error(`Project ${projectId} failed to build`);\n }\n\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for project ${projectId} to be published`);\n }\n\n await sleep(pollInterval);\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"],"mappings":"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","names":[]}
|