@lasso-ai/cli 1.2.6 → 1.3.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/dist/cli/agent.js +175 -31
- package/dist/cli/bridge.d.ts +9 -2
- package/dist/cli/bridge.js +199 -50
- package/dist/cli/host/next-host-entry.js +1 -1
- package/dist/cli/host/vite-host-entry.js +1 -1
- package/dist/cli/index.js +18 -1
- package/dist/cli/oneshot.d.ts +0 -1
- package/dist/cli/oneshot.js +137 -75
- package/dist/cli/project.d.ts +28 -0
- package/dist/cli/project.js +57 -0
- package/dist/overlay.js +49 -1
- package/package.json +1 -1
package/dist/cli/agent.js
CHANGED
|
@@ -14,12 +14,57 @@ exports.runLocalAgentPrompt = runLocalAgentPrompt;
|
|
|
14
14
|
exports.proposeChanges = proposeChanges;
|
|
15
15
|
exports.answerQuestion = answerQuestion;
|
|
16
16
|
exports.generateCommitMessage = generateCommitMessage;
|
|
17
|
-
const
|
|
17
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
const node_child_process_1 = require("node:child_process");
|
|
20
20
|
const node_util_1 = require("node:util");
|
|
21
|
+
const auth_1 = require("./auth");
|
|
21
22
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
22
23
|
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
24
|
+
function detectProjectStructure(cwd) {
|
|
25
|
+
const pkgPath = node_path_1.default.join(cwd, "package.json");
|
|
26
|
+
let framework = "unknown";
|
|
27
|
+
const directories = [];
|
|
28
|
+
const appDirs = [];
|
|
29
|
+
if (node_fs_1.default.existsSync(pkgPath)) {
|
|
30
|
+
try {
|
|
31
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, "utf8"));
|
|
32
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
33
|
+
if (deps.next)
|
|
34
|
+
framework = "Next.js";
|
|
35
|
+
else if (deps.vite)
|
|
36
|
+
framework = "Vite";
|
|
37
|
+
else if (deps.react)
|
|
38
|
+
framework = "React";
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Ignore errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Scan for important directories
|
|
45
|
+
const scanDirs = ["src", "app", "pages", "web", "lib", "components"];
|
|
46
|
+
for (const dir of scanDirs) {
|
|
47
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(cwd, dir))) {
|
|
48
|
+
directories.push(dir);
|
|
49
|
+
if (dir === "app")
|
|
50
|
+
appDirs.push(dir);
|
|
51
|
+
// Check for src/app structure
|
|
52
|
+
if (dir === "src" && node_fs_1.default.existsSync(node_path_1.default.join(cwd, "src", "app"))) {
|
|
53
|
+
directories.push("src/app");
|
|
54
|
+
appDirs.push("src/app");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Determine source root
|
|
59
|
+
let sourceRoot = ".";
|
|
60
|
+
if (directories.includes("src/app"))
|
|
61
|
+
sourceRoot = "src";
|
|
62
|
+
else if (directories.includes("app"))
|
|
63
|
+
sourceRoot = ".";
|
|
64
|
+
else if (directories.includes("src"))
|
|
65
|
+
sourceRoot = "src";
|
|
66
|
+
return { framework, sourceRoot, directories, multipleAppDirs: appDirs };
|
|
67
|
+
}
|
|
23
68
|
async function installPackages(cwd, packages, onProgress) {
|
|
24
69
|
const validPkgs = [...new Set(packages
|
|
25
70
|
.map((p) => p.trim().replace(/^['"`]|['"`]$/g, ""))
|
|
@@ -30,7 +75,7 @@ async function installPackages(cwd, packages, onProgress) {
|
|
|
30
75
|
let existingDeps = {};
|
|
31
76
|
try {
|
|
32
77
|
const pkgJsonPath = node_path_1.default.join(cwd, "package.json");
|
|
33
|
-
const raw =
|
|
78
|
+
const raw = node_fs_1.default.readFileSync(pkgJsonPath, "utf8");
|
|
34
79
|
const parsed = JSON.parse(raw);
|
|
35
80
|
existingDeps = { ...(parsed.dependencies || {}), ...(parsed.devDependencies || {}) };
|
|
36
81
|
}
|
|
@@ -41,7 +86,7 @@ async function installPackages(cwd, packages, onProgress) {
|
|
|
41
86
|
// Detect package manager
|
|
42
87
|
let cmd = "npm install";
|
|
43
88
|
try {
|
|
44
|
-
const rootFiles =
|
|
89
|
+
const rootFiles = node_fs_1.default.readdirSync(cwd);
|
|
45
90
|
if (rootFiles.includes("bun.lockb") || rootFiles.includes("bun.lock"))
|
|
46
91
|
cmd = "bun add";
|
|
47
92
|
else if (rootFiles.includes("pnpm-lock.yaml"))
|
|
@@ -164,7 +209,7 @@ const ignored = new Set(["node_modules", ".git", ".next", "dist", "build", ".tur
|
|
|
164
209
|
const sourceExtensions = /\.(tsx?|jsx?|vue|svelte|css|scss|html)$/i;
|
|
165
210
|
const layoutFiles = /^(layout|_layout|_app|app|_document|document|template|_template)\.(tsx?|jsx?)$/i;
|
|
166
211
|
const sourceFileCache = new Map();
|
|
167
|
-
|
|
212
|
+
function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
168
213
|
if (budget.remaining <= 0)
|
|
169
214
|
return [];
|
|
170
215
|
const cached = sourceFileCache.get(directory);
|
|
@@ -173,7 +218,7 @@ async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
|
173
218
|
budget.remaining -= files.length;
|
|
174
219
|
return files;
|
|
175
220
|
}
|
|
176
|
-
const entries =
|
|
221
|
+
const entries = node_fs_1.default.readdirSync(directory, { withFileTypes: true });
|
|
177
222
|
const files = [];
|
|
178
223
|
const layoutFilesList = [];
|
|
179
224
|
const otherFiles = [];
|
|
@@ -184,7 +229,7 @@ async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
|
184
229
|
if (budget.remaining <= 0)
|
|
185
230
|
break;
|
|
186
231
|
if (entry.isDirectory()) {
|
|
187
|
-
const nested =
|
|
232
|
+
const nested = sourceFiles(fullPath, budget);
|
|
188
233
|
files.push(...nested);
|
|
189
234
|
}
|
|
190
235
|
else if (sourceExtensions.test(entry.name)) {
|
|
@@ -202,10 +247,21 @@ async function sourceFiles(directory, budget = { remaining: 25 }) {
|
|
|
202
247
|
sourceFileCache.set(directory, { expiresAt: Date.now() + 30000, files });
|
|
203
248
|
return files;
|
|
204
249
|
}
|
|
205
|
-
|
|
250
|
+
function contextFor(cwd, element) {
|
|
206
251
|
const needle = element.label.replace(/^[^.#]+[.#]?/, "");
|
|
207
252
|
const hintedPath = element.sourceHint?.split(":")[0];
|
|
208
253
|
const sourceFile = hintedPath ? node_path_1.default.basename(hintedPath) : "";
|
|
254
|
+
// Add project structure information at the top
|
|
255
|
+
const structure = detectProjectStructure(cwd);
|
|
256
|
+
let projectStructureInfo = `PROJECT STRUCTURE:
|
|
257
|
+
- Framework: ${structure.framework}
|
|
258
|
+
- Source root: ${structure.sourceRoot}
|
|
259
|
+
- Directories found: ${structure.directories.join(", ") || "root"}
|
|
260
|
+
- Multiple app directories: ${structure.multipleAppDirs.length > 0 ? structure.multipleAppDirs.join(", ") : "none"}
|
|
261
|
+
- IMPORTANT: When creating files, use the existing directory structure. Do not create duplicate directories (e.g., if "src/app/" exists, use it instead of creating a new "app/").
|
|
262
|
+
- Path aliases: Check tsconfig.json or jsconfig.json for path aliases like "@/components"
|
|
263
|
+
|
|
264
|
+
`;
|
|
209
265
|
// Extract unique class names and attributes from the element for better matching
|
|
210
266
|
const classMatches = element.html?.match(/class="([^"]+)"/);
|
|
211
267
|
const classNames = classMatches ? classMatches[1].split(/\s+/).filter((c) => c.length > 3) : [];
|
|
@@ -222,20 +278,20 @@ async function contextFor(cwd, element) {
|
|
|
222
278
|
candidates.push(node_path_1.default.join(cwd, normalizedHint.slice(srcIndex + 1)));
|
|
223
279
|
for (const candidate of candidates) {
|
|
224
280
|
try {
|
|
225
|
-
const content =
|
|
226
|
-
return
|
|
281
|
+
const content = node_fs_1.default.readFileSync(candidate, "utf8");
|
|
282
|
+
return `${projectStructureInfo}FILE: ${node_path_1.default.relative(cwd, candidate)}\n${content.slice(0, 16000)}`;
|
|
227
283
|
}
|
|
228
284
|
catch {
|
|
229
285
|
// The runtime source hint can point to a different checkout.
|
|
230
286
|
}
|
|
231
287
|
}
|
|
232
288
|
}
|
|
233
|
-
const files =
|
|
289
|
+
const files = sourceFiles(cwd);
|
|
234
290
|
const snippets = [];
|
|
235
291
|
const scoredFiles = [];
|
|
236
|
-
const results =
|
|
292
|
+
const results = files.map((file) => {
|
|
237
293
|
try {
|
|
238
|
-
const content =
|
|
294
|
+
const content = node_fs_1.default.readFileSync(file, "utf8");
|
|
239
295
|
let score = 0;
|
|
240
296
|
// Score files based on relevance
|
|
241
297
|
if (sourceFile && file.endsWith(sourceFile))
|
|
@@ -263,7 +319,7 @@ async function contextFor(cwd, element) {
|
|
|
263
319
|
// A file can disappear while a dev server is rebuilding; skip it.
|
|
264
320
|
}
|
|
265
321
|
return null;
|
|
266
|
-
}));
|
|
322
|
+
}).filter((r) => r !== null);
|
|
267
323
|
// Sort by score and take top files
|
|
268
324
|
const sortedResults = results.filter((r) => r !== null).sort((a, b) => b.score - a.score);
|
|
269
325
|
for (const result of sortedResults) {
|
|
@@ -271,7 +327,7 @@ async function contextFor(cwd, element) {
|
|
|
271
327
|
snippets.push(`FILE: ${node_path_1.default.relative(cwd, result.file)}\n${result.content.slice(0, 6000)}`);
|
|
272
328
|
}
|
|
273
329
|
}
|
|
274
|
-
return snippets.join("\n\n---\n\n");
|
|
330
|
+
return projectStructureInfo + snippets.join("\n\n---\n\n");
|
|
275
331
|
}
|
|
276
332
|
function jsonObjectCandidates(text) {
|
|
277
333
|
const candidates = [];
|
|
@@ -307,8 +363,18 @@ function jsonObjectCandidates(text) {
|
|
|
307
363
|
return candidates;
|
|
308
364
|
}
|
|
309
365
|
function jsonFrom(text) {
|
|
310
|
-
|
|
311
|
-
const
|
|
366
|
+
// Remove common progress prefixes and tool output noise
|
|
367
|
+
const cleaned = text
|
|
368
|
+
.replace(/^(Thinking|Working|Planning|Analyzing|Executing|Searching|Scanning)\.\.\.?$/gm, '')
|
|
369
|
+
.replace(/^›/gm, '')
|
|
370
|
+
.replace(/^>/gm, '')
|
|
371
|
+
.replace(/^(grep|find|cat|ls|cd|npm|pnpm|yarn|git).*$/gm, '')
|
|
372
|
+
.replace(/^src\/.*:\d+:.*$/gm, '')
|
|
373
|
+
.replace(/^(The agent|Error|Warning|Info|Note):.*$/gm, '')
|
|
374
|
+
.replace(/^(Progress|Activity|Output|Result):.*$/gm, '')
|
|
375
|
+
.trim();
|
|
376
|
+
const fenced = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
|
|
377
|
+
const candidates = [...(fenced ? [fenced] : []), ...jsonObjectCandidates(cleaned)];
|
|
312
378
|
for (const candidate of candidates) {
|
|
313
379
|
try {
|
|
314
380
|
const parsed = JSON.parse(candidate.trim());
|
|
@@ -584,6 +650,18 @@ const AGENT_FAILURE_HINTS = [
|
|
|
584
650
|
pattern: /failed to fetch models\.dev/i,
|
|
585
651
|
message: "The local agent could not reach models.dev to refresh its model list. Check the network or proxy it uses, then retry.",
|
|
586
652
|
},
|
|
653
|
+
{
|
|
654
|
+
pattern: /quota exceeded|quota limit|out of quota|no quota|quota.*limit|usage.*quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted/i,
|
|
655
|
+
message: "Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.",
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
pattern: /payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i,
|
|
659
|
+
message: "Your subscription or plan requires an upgrade. Please check your billing settings and upgrade to continue.",
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
pattern: /api.*key.*invalid|api.*key.*expired|invalid.*api.*key|expired.*api.*key|authentication.*failed|unauthorized|forbidden|401|403/i,
|
|
663
|
+
message: "Authentication failed. Check your API key or sign in to your account, then retry.",
|
|
664
|
+
},
|
|
587
665
|
];
|
|
588
666
|
function agentFailureHint(line) {
|
|
589
667
|
return AGENT_FAILURE_HINTS.find((hint) => hint.pattern.test(line))?.message;
|
|
@@ -732,6 +810,10 @@ function localAgentError(command, stderr, exitCode) {
|
|
|
732
810
|
if (command === "codex" && output.includes("mcp.canva.com") && output.includes("invalid_token")) {
|
|
733
811
|
return "Codex started, but the Canva MCP connection has an expired OAuth token. Re-authenticate or remove the Canva MCP server from Codex, then retry.";
|
|
734
812
|
}
|
|
813
|
+
// Check for quota/plan exhaustion in the output
|
|
814
|
+
if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted/i.test(output)) {
|
|
815
|
+
return `${command === "opencode" ? "OpenCode" : command === "cursor" ? "Cursor" : command} request failed: Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.`;
|
|
816
|
+
}
|
|
735
817
|
return `${command} exited with code ${exitCode}${output ? `: ${output.slice(0, 500)}` : ""}`;
|
|
736
818
|
}
|
|
737
819
|
function localModel(provider, model) {
|
|
@@ -849,7 +931,14 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
849
931
|
activeAgentInputs.delete(taskId);
|
|
850
932
|
signal?.removeEventListener("abort", abort);
|
|
851
933
|
if (timedOut) {
|
|
852
|
-
|
|
934
|
+
const stderrTail = pendingStderr.trim();
|
|
935
|
+
// Check if timeout was due to quota exhaustion
|
|
936
|
+
if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted/i.test(stderrTail)) {
|
|
937
|
+
reject(new Error(`${label} did not finish within 5 minutes: Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.`));
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
reject(new Error(`${label} did not finish within 5 minutes. Check its login or approval prompt, then retry.`));
|
|
941
|
+
}
|
|
853
942
|
return;
|
|
854
943
|
}
|
|
855
944
|
const stdoutTail = pendingStdout.trim();
|
|
@@ -900,7 +989,7 @@ async function runLocalAgentPrompt(cwd, config, prompt, signal, onProgress, task
|
|
|
900
989
|
return extractLocalAgentText(result.stdout, provider);
|
|
901
990
|
}
|
|
902
991
|
async function proposeChanges(cwd, input, config, signal, onProgress, onPrompt) {
|
|
903
|
-
const context =
|
|
992
|
+
const context = contextFor(cwd, input.element);
|
|
904
993
|
const runtimeErrors = input.context?.runtimeErrors || [];
|
|
905
994
|
const visualContext = input.context ? { ...input.context, screenshots: undefined, runtimeErrors: undefined } : undefined;
|
|
906
995
|
const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || input.instruction;
|
|
@@ -911,6 +1000,8 @@ async function proposeChanges(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
911
1000
|
const runtimeErrorHint = runtimeErrors.length
|
|
912
1001
|
? `\n\nLIVE CONSOLE/RUNTIME ERRORS (reported by the user's browser — the page is currently broken by these):\n${runtimeErrors.map((error) => `! ${error}`).join("\n")}`
|
|
913
1002
|
: "";
|
|
1003
|
+
// Detect project structure for instruction
|
|
1004
|
+
const structure = detectProjectStructure(cwd);
|
|
914
1005
|
const instruction = `Selection context:
|
|
915
1006
|
${JSON.stringify(input.element, null, 2)}
|
|
916
1007
|
${JSON.stringify(visualContext, null, 2)}
|
|
@@ -928,6 +1019,8 @@ Relevant source context:
|
|
|
928
1019
|
${context || "No matching source context was found. Ask for a more specific selection rather than inventing a file."}${dragHint}${runtimeErrorHint}
|
|
929
1020
|
|
|
930
1021
|
CRITICAL INSTRUCTIONS:
|
|
1022
|
+
0. PROJECT STRUCTURE AWARENESS: This project has specific directory structure. Before creating or moving files, verify the actual structure. Do not assume standard locations.
|
|
1023
|
+
${structure.multipleAppDirs.length > 0 ? ` WARNING: Multiple app directories detected: ${structure.multipleAppDirs.join(", ")}. Use the correct one for this project.\n` : ""}
|
|
931
1024
|
1. You are an autonomous agent with the ability to install npm packages and edit source code.
|
|
932
1025
|
2. If the user's request requires or asks for third-party libraries, icon packs, animation tools, or utility packages (e.g. icon libraries, motion, charts, UI primitives, etc.), determine the best npm package for this project and declare them in the "packages" array (e.g. ["@iconify/react", "@hugeicons/react"]).
|
|
933
1026
|
3. In the "changes" array, propose concrete, minimal edits to the source code to implement the request. You may freely import and use the packages you specified in "packages".
|
|
@@ -948,6 +1041,9 @@ CRITICAL INSTRUCTIONS:
|
|
|
948
1041
|
}
|
|
949
1042
|
Do NOT return prose or markdown outside the JSON.`;
|
|
950
1043
|
const callServerGateway = async (gatewayKey) => {
|
|
1044
|
+
// Use CLI auth key if available, otherwise use the provided gateway key
|
|
1045
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
1046
|
+
const apiKeyToUse = cliCredentials?.apiKey || gatewayKey;
|
|
951
1047
|
let serverUrl = config.serverUrl || process.env.LASSO_SERVER_URL || process.env.NEXT_PUBLIC_LASSO_SERVER_URL || "https://api.lasso.byorello.space";
|
|
952
1048
|
if (serverUrl.includes("collab.lasso.byorello.space")) {
|
|
953
1049
|
serverUrl = serverUrl.replace("collab.lasso.byorello.space", "api.lasso.byorello.space");
|
|
@@ -964,7 +1060,7 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
964
1060
|
const response = await fetch(endpoint, {
|
|
965
1061
|
method: "POST",
|
|
966
1062
|
signal,
|
|
967
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${
|
|
1063
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKeyToUse}` },
|
|
968
1064
|
body: JSON.stringify({
|
|
969
1065
|
sessionId: input.taskId,
|
|
970
1066
|
prompt: instruction,
|
|
@@ -974,7 +1070,12 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
974
1070
|
});
|
|
975
1071
|
if (!response.ok) {
|
|
976
1072
|
const errData = await response.json().catch(() => ({}));
|
|
977
|
-
|
|
1073
|
+
const errorMessage = errData.error || `Lasso Agent Gateway request failed (${response.status})`;
|
|
1074
|
+
// Check for quota/plan exhaustion
|
|
1075
|
+
if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted|payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i.test(errorMessage)) {
|
|
1076
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1077
|
+
}
|
|
1078
|
+
throw new Error(errorMessage);
|
|
978
1079
|
}
|
|
979
1080
|
const data = await response.json();
|
|
980
1081
|
// If the server-side agent reported failure, surface it as a thrown error so the
|
|
@@ -1007,7 +1108,12 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
1007
1108
|
});
|
|
1008
1109
|
if (!response.ok) {
|
|
1009
1110
|
const errData = await response.json().catch(() => ({}));
|
|
1010
|
-
|
|
1111
|
+
const errorMessage = errData.error || `Lasso Gateway generate failed (${response.status})`;
|
|
1112
|
+
// Check for quota/plan exhaustion
|
|
1113
|
+
if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted|payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i.test(errorMessage)) {
|
|
1114
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1115
|
+
}
|
|
1116
|
+
throw new Error(errorMessage);
|
|
1011
1117
|
}
|
|
1012
1118
|
const data = await response.json();
|
|
1013
1119
|
if (!data.text?.trim())
|
|
@@ -1018,7 +1124,9 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
1018
1124
|
proposal = await proposeWithLocalAgent(cwd, instruction, context, config, signal, onProgress, input.taskId, onPrompt);
|
|
1019
1125
|
}
|
|
1020
1126
|
else {
|
|
1021
|
-
|
|
1127
|
+
// Check if using Lasso platform key (from CLI auth or env)
|
|
1128
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
1129
|
+
const isLassoKey = cliCredentials?.apiKey || config.apiKey?.startsWith("lss_live_") || config.apiKey?.startsWith("lss_");
|
|
1022
1130
|
const system = `You are Lasso, an autonomous agentic AI coding assistant.
|
|
1023
1131
|
You have the ability to run inspection commands, search the live web for documentation or libraries, ask the user for input or single/multi-selection choices, install npm packages, and edit source code.
|
|
1024
1132
|
|
|
@@ -1051,8 +1159,17 @@ RULES:
|
|
|
1051
1159
|
- If you already have sufficient context to fulfill the user's request immediately, return "propose_changes" (or standard {"summary": "...", "packages": [...], "changes": [...]}) directly!
|
|
1052
1160
|
- If live console/runtime errors are provided, resolving them is part of the task. Use run_command to verify package exports and file contents before proposing changes instead of guessing.
|
|
1053
1161
|
- Each oldString must match the existing file context exactly.
|
|
1054
|
-
- Return ONLY valid JSON with no markdown formatting
|
|
1055
|
-
|
|
1162
|
+
- CRITICAL: Return ONLY valid JSON with no markdown formatting, no code blocks, no prose, no thinking tags, and no progress output. Just the JSON object.
|
|
1163
|
+
- Do NOT include phrases like "Thinking...", "Working...", or any progress indicators in your response.
|
|
1164
|
+
- Do NOT wrap the JSON in code blocks. Return raw JSON only.`;
|
|
1165
|
+
const model = config.model;
|
|
1166
|
+
if (!model) {
|
|
1167
|
+
return {
|
|
1168
|
+
summary: "No model selected. Please select a model in settings.",
|
|
1169
|
+
changes: [],
|
|
1170
|
+
packages: [],
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1056
1173
|
const image = input.context?.screenshots?.element || input.context?.screenshots?.full;
|
|
1057
1174
|
const imageData = image?.replace(/^data:image\/[^;]+;base64,/, "");
|
|
1058
1175
|
const imageMime = image?.match(/^data:(image\/[^;]+);base64,/)?.[1] || "image/jpeg";
|
|
@@ -1068,7 +1185,8 @@ RULES:
|
|
|
1068
1185
|
// If using a Lasso platform key, route this turn through the /generate endpoint
|
|
1069
1186
|
// (full agentic loop via Lasso gateway instead of one-shot session endpoint)
|
|
1070
1187
|
if (isLassoKey && config.provider !== "ollama") {
|
|
1071
|
-
|
|
1188
|
+
const apiKeyToUse = cliCredentials?.apiKey || config.apiKey;
|
|
1189
|
+
rawText = await callGatewayGenerate(apiKeyToUse, system, currentPrompt);
|
|
1072
1190
|
}
|
|
1073
1191
|
else {
|
|
1074
1192
|
// Provider-direct inference path
|
|
@@ -1106,6 +1224,10 @@ RULES:
|
|
|
1106
1224
|
});
|
|
1107
1225
|
}
|
|
1108
1226
|
if (!response.ok) {
|
|
1227
|
+
// Check for quota/plan exhaustion before fallback
|
|
1228
|
+
if (response.status === 429 || response.status === 402 || response.status === 403) {
|
|
1229
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1230
|
+
}
|
|
1109
1231
|
if (config.lassoKey && (response.status === 429 || response.status === 401)) {
|
|
1110
1232
|
onProgress?.("Local provider key limit reached; routing via Lasso Gateway…", undefined, "working");
|
|
1111
1233
|
proposal = await callServerGateway(config.lassoKey);
|
|
@@ -1125,6 +1247,10 @@ RULES:
|
|
|
1125
1247
|
}
|
|
1126
1248
|
}
|
|
1127
1249
|
catch (err) {
|
|
1250
|
+
// Check for quota/plan exhaustion before fallback
|
|
1251
|
+
if (err.message?.includes("429") || err.message?.includes("402") || err.message?.includes("403")) {
|
|
1252
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1253
|
+
}
|
|
1128
1254
|
if (!isLassoKey && config.lassoKey && (err.message?.includes("429") || err.message?.includes("401"))) {
|
|
1129
1255
|
onProgress?.("Local provider key limit reached; routing via Lasso Gateway…", undefined, "working");
|
|
1130
1256
|
proposal = await callServerGateway(config.lassoKey);
|
|
@@ -1189,13 +1315,16 @@ RULES:
|
|
|
1189
1315
|
return finalProposal;
|
|
1190
1316
|
}
|
|
1191
1317
|
async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt) {
|
|
1192
|
-
const context =
|
|
1318
|
+
const context = contextFor(cwd, input.element);
|
|
1193
1319
|
const history = input.messages?.map((message) => `${message.role}: ${message.content}`).join("\n") || "None";
|
|
1194
1320
|
const prompt = `Answer the user's question conversationally and directly. Do not propose file changes and do not return JSON. If the question is about the selected UI, use the selection and source context below.\n\nUser question:\n${input.question}\n\nSelected element:\n${JSON.stringify(input.element, null, 2)}\n\nVisual context:\n${JSON.stringify({ ...input.context, screenshots: undefined }, null, 2)}\n\nConversation:\n${history}\n\nRelevant source context:\n${context || "No matching source context was found."}`;
|
|
1195
1321
|
if (config.provider === "claude-code" || config.provider === "codex" || config.provider === "opencode" || config.provider === "cursor") {
|
|
1196
1322
|
return (await runLocalAgentPrompt(cwd, config, prompt, signal, onProgress, input.taskId, onPrompt)).trim();
|
|
1197
1323
|
}
|
|
1198
1324
|
const callServerGatewayAnswer = async (gatewayKey) => {
|
|
1325
|
+
// Use CLI auth key if available, otherwise use the provided gateway key
|
|
1326
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
1327
|
+
const apiKeyToUse = cliCredentials?.apiKey || gatewayKey;
|
|
1199
1328
|
let serverUrl = config.serverUrl || process.env.LASSO_SERVER_URL || process.env.NEXT_PUBLIC_LASSO_SERVER_URL || "https://api.lasso.byorello.space";
|
|
1200
1329
|
if (serverUrl.includes("collab.lasso.byorello.space")) {
|
|
1201
1330
|
serverUrl = serverUrl.replace("collab.lasso.byorello.space", "api.lasso.byorello.space");
|
|
@@ -1204,7 +1333,7 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
1204
1333
|
const response = await fetch(endpoint, {
|
|
1205
1334
|
method: "POST",
|
|
1206
1335
|
signal,
|
|
1207
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${
|
|
1336
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKeyToUse}` },
|
|
1208
1337
|
body: JSON.stringify({
|
|
1209
1338
|
provider: config.provider,
|
|
1210
1339
|
model: config.model,
|
|
@@ -1214,18 +1343,29 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
1214
1343
|
});
|
|
1215
1344
|
if (!response.ok) {
|
|
1216
1345
|
const errData = await response.json().catch(() => ({}));
|
|
1217
|
-
|
|
1346
|
+
const errorMessage = errData.error || `Lasso Agent Gateway request failed (${response.status})`;
|
|
1347
|
+
// Check for quota/plan exhaustion
|
|
1348
|
+
if (/quota exceeded|quota limit|out of quota|no quota|plan.*limit|plan.*exhausted|credit.*exhausted|billing.*quota|rate.*limit|429|too many requests|request.*limit|insufficient.*credits|no.*credits|credits.*exhausted|payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i.test(errorMessage)) {
|
|
1349
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1350
|
+
}
|
|
1351
|
+
throw new Error(errorMessage);
|
|
1218
1352
|
}
|
|
1219
1353
|
const data = await response.json();
|
|
1220
1354
|
if (!data.text?.trim())
|
|
1221
1355
|
throw new Error("The agent returned an empty answer.");
|
|
1222
1356
|
return data.text.trim();
|
|
1223
1357
|
};
|
|
1224
|
-
|
|
1358
|
+
// Check if using Lasso platform key (from CLI auth or env)
|
|
1359
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
1360
|
+
const isLassoKey = cliCredentials?.apiKey || config.apiKey?.startsWith("lss_live_") || config.apiKey?.startsWith("lss_");
|
|
1225
1361
|
if (isLassoKey && config.provider !== "ollama") {
|
|
1226
|
-
|
|
1362
|
+
const apiKeyToUse = cliCredentials?.apiKey || config.apiKey;
|
|
1363
|
+
return await callServerGatewayAnswer(apiKeyToUse);
|
|
1364
|
+
}
|
|
1365
|
+
const model = config.model;
|
|
1366
|
+
if (!model) {
|
|
1367
|
+
return "No model selected. Please select a model in settings.";
|
|
1227
1368
|
}
|
|
1228
|
-
const model = config.model || (config.provider === "google" ? "gemini-2.5-flash" : config.provider === "openai" ? "gpt-4.1-mini" : config.provider === "ollama" ? "llama3.2" : config.provider === "nvidia" ? "meta/llama-3.2-11b-vision-instruct" : "claude-sonnet-4-20250514");
|
|
1229
1369
|
let response;
|
|
1230
1370
|
try {
|
|
1231
1371
|
if (config.provider === "openai" || config.provider === "ollama" || config.provider === "nvidia") {
|
|
@@ -1248,6 +1388,10 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
1248
1388
|
});
|
|
1249
1389
|
}
|
|
1250
1390
|
if (!response.ok) {
|
|
1391
|
+
// Check for quota/plan exhaustion before fallback
|
|
1392
|
+
if (response.status === 429 || response.status === 402 || response.status === 403) {
|
|
1393
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1394
|
+
}
|
|
1251
1395
|
if (config.lassoKey && (response.status === 429 || response.status === 401)) {
|
|
1252
1396
|
onProgress?.("Local provider key limit reached; routing via Lasso Gateway…", undefined, "working");
|
|
1253
1397
|
return await callServerGatewayAnswer(config.lassoKey);
|
package/dist/cli/bridge.d.ts
CHANGED
|
@@ -148,6 +148,8 @@ type ModelOption = {
|
|
|
148
148
|
id: string;
|
|
149
149
|
label: string;
|
|
150
150
|
provider: "anthropic" | "openai" | "google" | "ollama" | "nvidia" | "cli";
|
|
151
|
+
locked?: boolean;
|
|
152
|
+
lockedReason?: string;
|
|
151
153
|
};
|
|
152
154
|
type PageEntry = {
|
|
153
155
|
name: string;
|
|
@@ -217,6 +219,11 @@ export type ServerBridgeMessage = {
|
|
|
217
219
|
type: "applied";
|
|
218
220
|
taskId?: string;
|
|
219
221
|
message: string;
|
|
222
|
+
validation?: {
|
|
223
|
+
passed: boolean;
|
|
224
|
+
errors: string[];
|
|
225
|
+
command: string;
|
|
226
|
+
};
|
|
220
227
|
} | {
|
|
221
228
|
type: "undone";
|
|
222
229
|
taskId?: string;
|
|
@@ -233,8 +240,8 @@ export declare function restartBridge(port?: number): Promise<{
|
|
|
233
240
|
ok: boolean;
|
|
234
241
|
error?: string;
|
|
235
242
|
}>;
|
|
236
|
-
export declare function startBridge(cwd?: string, collabConfig?: CollabConfig | null, bridgePort?: number): {
|
|
243
|
+
export declare function startBridge(cwd?: string, collabConfig?: CollabConfig | null, bridgePort?: number): Promise<{
|
|
237
244
|
send: (msg: ServerBridgeMessage) => void;
|
|
238
245
|
close(): void;
|
|
239
|
-
}
|
|
246
|
+
}>;
|
|
240
247
|
export {};
|