@lasso-ai/cli 1.2.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/agent.js +100 -15
- package/dist/cli/bridge.d.ts +4 -2
- package/dist/cli/bridge.js +92 -42
- 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 +97 -46
- package/dist/cli/project.d.ts +28 -0
- package/dist/cli/project.js +57 -0
- package/package.json +1 -1
package/dist/cli/agent.js
CHANGED
|
@@ -18,6 +18,7 @@ const promises_1 = __importDefault(require("node:fs/promises"));
|
|
|
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);
|
|
23
24
|
async function installPackages(cwd, packages, onProgress) {
|
|
@@ -307,8 +308,18 @@ function jsonObjectCandidates(text) {
|
|
|
307
308
|
return candidates;
|
|
308
309
|
}
|
|
309
310
|
function jsonFrom(text) {
|
|
310
|
-
|
|
311
|
-
const
|
|
311
|
+
// Remove common progress prefixes and tool output noise
|
|
312
|
+
const cleaned = text
|
|
313
|
+
.replace(/^(Thinking|Working|Planning|Analyzing|Executing|Searching|Scanning)\.\.\.?$/gm, '')
|
|
314
|
+
.replace(/^›/gm, '')
|
|
315
|
+
.replace(/^>/gm, '')
|
|
316
|
+
.replace(/^(grep|find|cat|ls|cd|npm|pnpm|yarn|git).*$/gm, '')
|
|
317
|
+
.replace(/^src\/.*:\d+:.*$/gm, '')
|
|
318
|
+
.replace(/^(The agent|Error|Warning|Info|Note):.*$/gm, '')
|
|
319
|
+
.replace(/^(Progress|Activity|Output|Result):.*$/gm, '')
|
|
320
|
+
.trim();
|
|
321
|
+
const fenced = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1];
|
|
322
|
+
const candidates = [...(fenced ? [fenced] : []), ...jsonObjectCandidates(cleaned)];
|
|
312
323
|
for (const candidate of candidates) {
|
|
313
324
|
try {
|
|
314
325
|
const parsed = JSON.parse(candidate.trim());
|
|
@@ -584,6 +595,18 @@ const AGENT_FAILURE_HINTS = [
|
|
|
584
595
|
pattern: /failed to fetch models\.dev/i,
|
|
585
596
|
message: "The local agent could not reach models.dev to refresh its model list. Check the network or proxy it uses, then retry.",
|
|
586
597
|
},
|
|
598
|
+
{
|
|
599
|
+
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,
|
|
600
|
+
message: "Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.",
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
pattern: /payment.*required|subscription.*required|upgrade.*required|upgrade.*plan|billing.*required/i,
|
|
604
|
+
message: "Your subscription or plan requires an upgrade. Please check your billing settings and upgrade to continue.",
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
pattern: /api.*key.*invalid|api.*key.*expired|invalid.*api.*key|expired.*api.*key|authentication.*failed|unauthorized|forbidden|401|403/i,
|
|
608
|
+
message: "Authentication failed. Check your API key or sign in to your account, then retry.",
|
|
609
|
+
},
|
|
587
610
|
];
|
|
588
611
|
function agentFailureHint(line) {
|
|
589
612
|
return AGENT_FAILURE_HINTS.find((hint) => hint.pattern.test(line))?.message;
|
|
@@ -732,6 +755,10 @@ function localAgentError(command, stderr, exitCode) {
|
|
|
732
755
|
if (command === "codex" && output.includes("mcp.canva.com") && output.includes("invalid_token")) {
|
|
733
756
|
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
757
|
}
|
|
758
|
+
// Check for quota/plan exhaustion in the output
|
|
759
|
+
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)) {
|
|
760
|
+
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.`;
|
|
761
|
+
}
|
|
735
762
|
return `${command} exited with code ${exitCode}${output ? `: ${output.slice(0, 500)}` : ""}`;
|
|
736
763
|
}
|
|
737
764
|
function localModel(provider, model) {
|
|
@@ -849,7 +876,14 @@ function runLocalCommand(cwd, command, args, provider, signal, onProgress, taskI
|
|
|
849
876
|
activeAgentInputs.delete(taskId);
|
|
850
877
|
signal?.removeEventListener("abort", abort);
|
|
851
878
|
if (timedOut) {
|
|
852
|
-
|
|
879
|
+
const stderrTail = pendingStderr.trim();
|
|
880
|
+
// Check if timeout was due to quota exhaustion
|
|
881
|
+
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)) {
|
|
882
|
+
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.`));
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
reject(new Error(`${label} did not finish within 5 minutes. Check its login or approval prompt, then retry.`));
|
|
886
|
+
}
|
|
853
887
|
return;
|
|
854
888
|
}
|
|
855
889
|
const stdoutTail = pendingStdout.trim();
|
|
@@ -948,6 +982,9 @@ CRITICAL INSTRUCTIONS:
|
|
|
948
982
|
}
|
|
949
983
|
Do NOT return prose or markdown outside the JSON.`;
|
|
950
984
|
const callServerGateway = async (gatewayKey) => {
|
|
985
|
+
// Use CLI auth key if available, otherwise use the provided gateway key
|
|
986
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
987
|
+
const apiKeyToUse = cliCredentials?.apiKey || gatewayKey;
|
|
951
988
|
let serverUrl = config.serverUrl || process.env.LASSO_SERVER_URL || process.env.NEXT_PUBLIC_LASSO_SERVER_URL || "https://api.lasso.byorello.space";
|
|
952
989
|
if (serverUrl.includes("collab.lasso.byorello.space")) {
|
|
953
990
|
serverUrl = serverUrl.replace("collab.lasso.byorello.space", "api.lasso.byorello.space");
|
|
@@ -964,7 +1001,7 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
964
1001
|
const response = await fetch(endpoint, {
|
|
965
1002
|
method: "POST",
|
|
966
1003
|
signal,
|
|
967
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${
|
|
1004
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKeyToUse}` },
|
|
968
1005
|
body: JSON.stringify({
|
|
969
1006
|
sessionId: input.taskId,
|
|
970
1007
|
prompt: instruction,
|
|
@@ -974,7 +1011,12 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
974
1011
|
});
|
|
975
1012
|
if (!response.ok) {
|
|
976
1013
|
const errData = await response.json().catch(() => ({}));
|
|
977
|
-
|
|
1014
|
+
const errorMessage = errData.error || `Lasso Agent Gateway request failed (${response.status})`;
|
|
1015
|
+
// Check for quota/plan exhaustion
|
|
1016
|
+
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)) {
|
|
1017
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1018
|
+
}
|
|
1019
|
+
throw new Error(errorMessage);
|
|
978
1020
|
}
|
|
979
1021
|
const data = await response.json();
|
|
980
1022
|
// If the server-side agent reported failure, surface it as a thrown error so the
|
|
@@ -1007,7 +1049,12 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
1007
1049
|
});
|
|
1008
1050
|
if (!response.ok) {
|
|
1009
1051
|
const errData = await response.json().catch(() => ({}));
|
|
1010
|
-
|
|
1052
|
+
const errorMessage = errData.error || `Lasso Gateway generate failed (${response.status})`;
|
|
1053
|
+
// Check for quota/plan exhaustion
|
|
1054
|
+
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)) {
|
|
1055
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1056
|
+
}
|
|
1057
|
+
throw new Error(errorMessage);
|
|
1011
1058
|
}
|
|
1012
1059
|
const data = await response.json();
|
|
1013
1060
|
if (!data.text?.trim())
|
|
@@ -1018,7 +1065,9 @@ Do NOT return prose or markdown outside the JSON.`;
|
|
|
1018
1065
|
proposal = await proposeWithLocalAgent(cwd, instruction, context, config, signal, onProgress, input.taskId, onPrompt);
|
|
1019
1066
|
}
|
|
1020
1067
|
else {
|
|
1021
|
-
|
|
1068
|
+
// Check if using Lasso platform key (from CLI auth or env)
|
|
1069
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
1070
|
+
const isLassoKey = cliCredentials?.apiKey || config.apiKey?.startsWith("lss_live_") || config.apiKey?.startsWith("lss_");
|
|
1022
1071
|
const system = `You are Lasso, an autonomous agentic AI coding assistant.
|
|
1023
1072
|
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
1073
|
|
|
@@ -1051,8 +1100,17 @@ RULES:
|
|
|
1051
1100
|
- If you already have sufficient context to fulfill the user's request immediately, return "propose_changes" (or standard {"summary": "...", "packages": [...], "changes": [...]}) directly!
|
|
1052
1101
|
- 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
1102
|
- Each oldString must match the existing file context exactly.
|
|
1054
|
-
- Return ONLY valid JSON with no markdown formatting
|
|
1055
|
-
|
|
1103
|
+
- 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.
|
|
1104
|
+
- Do NOT include phrases like "Thinking...", "Working...", or any progress indicators in your response.
|
|
1105
|
+
- Do NOT wrap the JSON in code blocks. Return raw JSON only.`;
|
|
1106
|
+
const model = config.model;
|
|
1107
|
+
if (!model) {
|
|
1108
|
+
return {
|
|
1109
|
+
summary: "No model selected. Please select a model in settings.",
|
|
1110
|
+
changes: [],
|
|
1111
|
+
packages: [],
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1056
1114
|
const image = input.context?.screenshots?.element || input.context?.screenshots?.full;
|
|
1057
1115
|
const imageData = image?.replace(/^data:image\/[^;]+;base64,/, "");
|
|
1058
1116
|
const imageMime = image?.match(/^data:(image\/[^;]+);base64,/)?.[1] || "image/jpeg";
|
|
@@ -1068,7 +1126,8 @@ RULES:
|
|
|
1068
1126
|
// If using a Lasso platform key, route this turn through the /generate endpoint
|
|
1069
1127
|
// (full agentic loop via Lasso gateway instead of one-shot session endpoint)
|
|
1070
1128
|
if (isLassoKey && config.provider !== "ollama") {
|
|
1071
|
-
|
|
1129
|
+
const apiKeyToUse = cliCredentials?.apiKey || config.apiKey;
|
|
1130
|
+
rawText = await callGatewayGenerate(apiKeyToUse, system, currentPrompt);
|
|
1072
1131
|
}
|
|
1073
1132
|
else {
|
|
1074
1133
|
// Provider-direct inference path
|
|
@@ -1106,6 +1165,10 @@ RULES:
|
|
|
1106
1165
|
});
|
|
1107
1166
|
}
|
|
1108
1167
|
if (!response.ok) {
|
|
1168
|
+
// Check for quota/plan exhaustion before fallback
|
|
1169
|
+
if (response.status === 429 || response.status === 402 || response.status === 403) {
|
|
1170
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1171
|
+
}
|
|
1109
1172
|
if (config.lassoKey && (response.status === 429 || response.status === 401)) {
|
|
1110
1173
|
onProgress?.("Local provider key limit reached; routing via Lasso Gateway…", undefined, "working");
|
|
1111
1174
|
proposal = await callServerGateway(config.lassoKey);
|
|
@@ -1125,6 +1188,10 @@ RULES:
|
|
|
1125
1188
|
}
|
|
1126
1189
|
}
|
|
1127
1190
|
catch (err) {
|
|
1191
|
+
// Check for quota/plan exhaustion before fallback
|
|
1192
|
+
if (err.message?.includes("429") || err.message?.includes("402") || err.message?.includes("403")) {
|
|
1193
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1194
|
+
}
|
|
1128
1195
|
if (!isLassoKey && config.lassoKey && (err.message?.includes("429") || err.message?.includes("401"))) {
|
|
1129
1196
|
onProgress?.("Local provider key limit reached; routing via Lasso Gateway…", undefined, "working");
|
|
1130
1197
|
proposal = await callServerGateway(config.lassoKey);
|
|
@@ -1196,6 +1263,9 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
1196
1263
|
return (await runLocalAgentPrompt(cwd, config, prompt, signal, onProgress, input.taskId, onPrompt)).trim();
|
|
1197
1264
|
}
|
|
1198
1265
|
const callServerGatewayAnswer = async (gatewayKey) => {
|
|
1266
|
+
// Use CLI auth key if available, otherwise use the provided gateway key
|
|
1267
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
1268
|
+
const apiKeyToUse = cliCredentials?.apiKey || gatewayKey;
|
|
1199
1269
|
let serverUrl = config.serverUrl || process.env.LASSO_SERVER_URL || process.env.NEXT_PUBLIC_LASSO_SERVER_URL || "https://api.lasso.byorello.space";
|
|
1200
1270
|
if (serverUrl.includes("collab.lasso.byorello.space")) {
|
|
1201
1271
|
serverUrl = serverUrl.replace("collab.lasso.byorello.space", "api.lasso.byorello.space");
|
|
@@ -1204,7 +1274,7 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
1204
1274
|
const response = await fetch(endpoint, {
|
|
1205
1275
|
method: "POST",
|
|
1206
1276
|
signal,
|
|
1207
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${
|
|
1277
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKeyToUse}` },
|
|
1208
1278
|
body: JSON.stringify({
|
|
1209
1279
|
provider: config.provider,
|
|
1210
1280
|
model: config.model,
|
|
@@ -1214,18 +1284,29 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
1214
1284
|
});
|
|
1215
1285
|
if (!response.ok) {
|
|
1216
1286
|
const errData = await response.json().catch(() => ({}));
|
|
1217
|
-
|
|
1287
|
+
const errorMessage = errData.error || `Lasso Agent Gateway request failed (${response.status})`;
|
|
1288
|
+
// Check for quota/plan exhaustion
|
|
1289
|
+
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)) {
|
|
1290
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1291
|
+
}
|
|
1292
|
+
throw new Error(errorMessage);
|
|
1218
1293
|
}
|
|
1219
1294
|
const data = await response.json();
|
|
1220
1295
|
if (!data.text?.trim())
|
|
1221
1296
|
throw new Error("The agent returned an empty answer.");
|
|
1222
1297
|
return data.text.trim();
|
|
1223
1298
|
};
|
|
1224
|
-
|
|
1299
|
+
// Check if using Lasso platform key (from CLI auth or env)
|
|
1300
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
1301
|
+
const isLassoKey = cliCredentials?.apiKey || config.apiKey?.startsWith("lss_live_") || config.apiKey?.startsWith("lss_");
|
|
1225
1302
|
if (isLassoKey && config.provider !== "ollama") {
|
|
1226
|
-
|
|
1303
|
+
const apiKeyToUse = cliCredentials?.apiKey || config.apiKey;
|
|
1304
|
+
return await callServerGatewayAnswer(apiKeyToUse);
|
|
1305
|
+
}
|
|
1306
|
+
const model = config.model;
|
|
1307
|
+
if (!model) {
|
|
1308
|
+
return "No model selected. Please select a model in settings.";
|
|
1227
1309
|
}
|
|
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
1310
|
let response;
|
|
1230
1311
|
try {
|
|
1231
1312
|
if (config.provider === "openai" || config.provider === "ollama" || config.provider === "nvidia") {
|
|
@@ -1248,6 +1329,10 @@ async function answerQuestion(cwd, input, config, signal, onProgress, onPrompt)
|
|
|
1248
1329
|
});
|
|
1249
1330
|
}
|
|
1250
1331
|
if (!response.ok) {
|
|
1332
|
+
// Check for quota/plan exhaustion before fallback
|
|
1333
|
+
if (response.status === 429 || response.status === 402 || response.status === 403) {
|
|
1334
|
+
throw new Error("Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service.");
|
|
1335
|
+
}
|
|
1251
1336
|
if (config.lassoKey && (response.status === 429 || response.status === 401)) {
|
|
1252
1337
|
onProgress?.("Local provider key limit reached; routing via Lasso Gateway…", undefined, "working");
|
|
1253
1338
|
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;
|
|
@@ -233,8 +235,8 @@ export declare function restartBridge(port?: number): Promise<{
|
|
|
233
235
|
ok: boolean;
|
|
234
236
|
error?: string;
|
|
235
237
|
}>;
|
|
236
|
-
export declare function startBridge(cwd?: string, collabConfig?: CollabConfig | null, bridgePort?: number): {
|
|
238
|
+
export declare function startBridge(cwd?: string, collabConfig?: CollabConfig | null, bridgePort?: number): Promise<{
|
|
237
239
|
send: (msg: ServerBridgeMessage) => void;
|
|
238
240
|
close(): void;
|
|
239
|
-
}
|
|
241
|
+
}>;
|
|
240
242
|
export {};
|
package/dist/cli/bridge.js
CHANGED
|
@@ -164,7 +164,7 @@ function readEnvFile(cwd, filename) {
|
|
|
164
164
|
return {};
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
-
function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = exports.DEFAULT_BRIDGE_PORT) {
|
|
167
|
+
async function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = exports.DEFAULT_BRIDGE_PORT) {
|
|
168
168
|
const bridgeServer = node_http_1.default.createServer(); // dedicated, empty HTTP server
|
|
169
169
|
const wss = new ws_1.WebSocketServer({ server: bridgeServer });
|
|
170
170
|
let overlaySocket = null;
|
|
@@ -179,7 +179,10 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
179
179
|
...readEnvFile(root, ".env.local"),
|
|
180
180
|
...readEnvFile(root, ".env.production"),
|
|
181
181
|
}), {});
|
|
182
|
-
|
|
182
|
+
// Prioritize CLI auth credentials over env vars
|
|
183
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
184
|
+
const resolvedLassoKey = cliCredentials?.apiKey ||
|
|
185
|
+
(0, project_1.resolveLassoApiKey)(fileEnv) ||
|
|
183
186
|
process.env.LASSO_API_KEY ||
|
|
184
187
|
fileEnv.LASSO_API_KEY ||
|
|
185
188
|
process.env.VITE_LASSO_API_KEY ||
|
|
@@ -188,6 +191,19 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
188
191
|
fileEnv.NEXT_LASSO_API_KEY ||
|
|
189
192
|
"";
|
|
190
193
|
const lassoKeyConfigured = Boolean(resolvedLassoKey);
|
|
194
|
+
// Fetch workspace configuration from server (dashboard-configured providers)
|
|
195
|
+
let workspaceConfig = null;
|
|
196
|
+
if (lassoKeyConfigured) {
|
|
197
|
+
workspaceConfig = await (0, project_1.resolveWorkspaceConfig)(fileEnv);
|
|
198
|
+
}
|
|
199
|
+
// Fetch available models from server
|
|
200
|
+
let dynamicModels = { anthropic: [], openai: [], google: [], nvidia: [] };
|
|
201
|
+
try {
|
|
202
|
+
dynamicModels = await (0, project_1.fetchModels)(fileEnv);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.warn('[Lasso] Failed to fetch dynamic models, using fallback');
|
|
206
|
+
}
|
|
191
207
|
const localProviderKeys = {
|
|
192
208
|
google: process.env.GOOGLE_GENERATIVE_AI_API_KEY || fileEnv.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY || fileEnv.GEMINI_API_KEY,
|
|
193
209
|
openai: process.env.OPENAI_API_KEY || fileEnv.OPENAI_API_KEY,
|
|
@@ -197,8 +213,9 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
197
213
|
};
|
|
198
214
|
let agentConfig = (() => {
|
|
199
215
|
const provider = (process.env.LASSO_AGENT_PROVIDER || fileEnv.LASSO_AGENT_PROVIDER || process.env.AI_PROVIDER || fileEnv.AI_PROVIDER || "").toLowerCase();
|
|
200
|
-
const selectedProvider = provider === "google" || provider === "gemini" ? "google" : provider === "openai" ? "openai" : provider === "ollama" ? "ollama" : provider === "anthropic" ? "anthropic" : provider === "nvidia" ? "nvidia" : localProviderKeys.ollama ? "ollama" : localProviderKeys.nvidia ? "nvidia" : localProviderKeys.google ? "google" : localProviderKeys.openai ? "openai" : localProviderKeys.anthropic ? "anthropic" : "google";
|
|
216
|
+
const selectedProvider = provider === "google" || provider === "gemini" ? "google" : provider === "openai" ? "openai" : provider === "ollama" ? "ollama" : provider === "anthropic" ? "anthropic" : provider === "nvidia" ? "nvidia" : localProviderKeys.ollama ? "ollama" : localProviderKeys.nvidia ? "nvidia" : localProviderKeys.google ? "google" : localProviderKeys.openai ? "openai" : localProviderKeys.anthropic ? "anthropic" : (cliCredentials ? "anthropic" : "google");
|
|
201
217
|
const apiKey = selectedProvider === "ollama" ? "ollama" : localProviderKeys[selectedProvider];
|
|
218
|
+
// If user has CLI auth, use Lasso gateway by default
|
|
202
219
|
if (!apiKey && !lassoKeyConfigured)
|
|
203
220
|
return null;
|
|
204
221
|
return {
|
|
@@ -301,7 +318,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
301
318
|
return [];
|
|
302
319
|
}
|
|
303
320
|
}
|
|
304
|
-
const availableModels = async (collabConfig = null) => {
|
|
321
|
+
const availableModels = async (collabConfig = null, workspaceConfig = null) => {
|
|
305
322
|
localAgents = await (0, agent_1.detectLocalAgents)();
|
|
306
323
|
const locals = await localModels();
|
|
307
324
|
const discoveredOpenCodeModels = await openCodeModels();
|
|
@@ -310,37 +327,46 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
310
327
|
const base = process.env.OLLAMA_BASE_URL || fileEnv.OLLAMA_BASE_URL || "http://localhost:11434/api";
|
|
311
328
|
agentConfig = { provider: "ollama", apiKey: "ollama", model: process.env.OLLAMA_MODEL || fileEnv.OLLAMA_MODEL, baseUrl: `${base.replace(/\/api\/?$/, "")}/v1` };
|
|
312
329
|
}
|
|
313
|
-
|
|
314
|
-
const
|
|
330
|
+
// Check if user has CLI auth credentials (lasso auth login)
|
|
331
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
332
|
+
const hasCliAuth = Boolean(cliCredentials?.apiKey);
|
|
333
|
+
// Prioritize workspace config (dashboard) over project config
|
|
334
|
+
// Workspace config is fetched independently of project config
|
|
335
|
+
const workspacePlan = (workspaceConfig?.plan || "free").toLowerCase();
|
|
336
|
+
const workspaceProviders = workspaceConfig?.configuredProviders || [];
|
|
337
|
+
const projectPlan = (collabConfig?.plan || "free").toLowerCase();
|
|
338
|
+
const projectProviders = collabConfig?.configuredProviders || [];
|
|
339
|
+
// Use the highest-tier plan available
|
|
340
|
+
const isPaidPlan = workspacePlan !== "free" || projectPlan !== "free";
|
|
341
|
+
// Use workspace providers if available, otherwise fall back to project providers
|
|
342
|
+
const configuredProviders = workspaceProviders.length > 0 ? workspaceProviders : projectProviders;
|
|
343
|
+
// Build model list from dynamic server fetch, falling back to hardcoded models
|
|
315
344
|
const allModels = [
|
|
316
|
-
//
|
|
317
|
-
{ id:
|
|
318
|
-
{ id:
|
|
319
|
-
{ id:
|
|
320
|
-
{ id:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro", provider: "google" },
|
|
342
|
-
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", provider: "google" },
|
|
343
|
-
{ id: "gemini-2.5-flash-lite", label: "Gemini 2.5 Flash-Lite", provider: "google" },
|
|
345
|
+
// Dynamic models from server API
|
|
346
|
+
...dynamicModels.anthropic.map(m => ({ id: m.id, label: m.name, provider: m.provider })),
|
|
347
|
+
...dynamicModels.openai.map(m => ({ id: m.id, label: m.name, provider: m.provider })),
|
|
348
|
+
...dynamicModels.google.map(m => ({ id: m.id, label: m.name, provider: m.provider })),
|
|
349
|
+
...dynamicModels.nvidia.map(m => ({ id: m.id, label: m.name, provider: m.provider })),
|
|
350
|
+
// Fallback hardcoded models (only if dynamic fetch failed)
|
|
351
|
+
...(dynamicModels.anthropic.length === 0 ? [
|
|
352
|
+
{ id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "anthropic" },
|
|
353
|
+
{ id: "claude-opus-4-1-20250805", label: "Claude Opus 4.1", provider: "anthropic" },
|
|
354
|
+
] : []),
|
|
355
|
+
...(dynamicModels.openai.length === 0 ? [
|
|
356
|
+
{ id: "gpt-4.1", label: "GPT-4.1", provider: "openai" },
|
|
357
|
+
{ id: "gpt-4.1-mini", label: "GPT-4.1 mini", provider: "openai" },
|
|
358
|
+
] : []),
|
|
359
|
+
...(dynamicModels.google.length === 0 ? [
|
|
360
|
+
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", provider: "google" },
|
|
361
|
+
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro", provider: "google" },
|
|
362
|
+
] : []),
|
|
363
|
+
...(dynamicModels.nvidia.length === 0 ? [
|
|
364
|
+
{ id: "meta/llama-3.3-70b-instruct", label: "Llama 3.3 70B Instruct", provider: "nvidia" },
|
|
365
|
+
{ id: "meta/llama-3.2-11b-vision-instruct", label: "Llama 3.2 11B Vision", provider: "nvidia" },
|
|
366
|
+
{ id: "deepseek-ai/deepseek-v4-flash-0731", label: "DeepSeek V4 Flash", provider: "nvidia" },
|
|
367
|
+
{ id: "google/gemma-4-31b-it", label: "Gemma 4 31B", provider: "nvidia" },
|
|
368
|
+
] : []),
|
|
369
|
+
// Local CLI agents (always included)
|
|
344
370
|
...(localAgents.has("claude-code") ? [
|
|
345
371
|
{ id: "claude-code:sonnet", label: "Claude Code · Sonnet", provider: "cli" },
|
|
346
372
|
{ id: "claude-code:opus", label: "Claude Code · Opus", provider: "cli" },
|
|
@@ -387,16 +413,33 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
387
413
|
return model;
|
|
388
414
|
}
|
|
389
415
|
// Free plans: check if provider is configured
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
416
|
+
let isConfigured = false;
|
|
417
|
+
let lockedReason = "Configure your API key in the dashboard to use this model";
|
|
418
|
+
if (hasCliAuth) {
|
|
419
|
+
// User has CLI auth: check dashboard-configured providers first
|
|
420
|
+
isConfigured = configuredProviders.includes(model.provider);
|
|
421
|
+
lockedReason = "Configure this provider in your workspace dashboard to use this model";
|
|
422
|
+
}
|
|
423
|
+
// If not configured via dashboard, fall back to env-based detection
|
|
424
|
+
if (!isConfigured) {
|
|
425
|
+
const envKey = providerKeyMapping[model.provider];
|
|
426
|
+
isConfigured = Boolean(process.env[envKey]) ||
|
|
427
|
+
Boolean(fileEnv[envKey]) ||
|
|
428
|
+
Boolean(localProviderKeys[model.provider]);
|
|
429
|
+
if (!isConfigured) {
|
|
430
|
+
lockedReason = hasCliAuth
|
|
431
|
+
? "Configure this provider in your workspace dashboard, or set the provider API key in your .env file"
|
|
432
|
+
: "Run `lasso auth login` to configure providers in your dashboard, or set the provider API key in your .env file";
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
lockedReason = "";
|
|
436
|
+
}
|
|
437
|
+
}
|
|
395
438
|
if (!isConfigured) {
|
|
396
439
|
return {
|
|
397
440
|
...model,
|
|
398
441
|
locked: true,
|
|
399
|
-
lockedReason: "Configure your API key
|
|
442
|
+
lockedReason: lockedReason || "Configure your API key to use this model",
|
|
400
443
|
};
|
|
401
444
|
}
|
|
402
445
|
return model;
|
|
@@ -410,7 +453,7 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
410
453
|
? { name: creds.userName, email: creds.userEmail }
|
|
411
454
|
: (process.env.USER ? { name: process.env.USER } : null);
|
|
412
455
|
socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: lassoKeyConfigured || Boolean(agentConfig) || localAgents.size > 0, models: [], collab: collabConfig?.registered ? { ...collabConfig, plan: collabConfig.plan, configuredProviders: collabConfig.configuredProviders } : null, user: userInfo }));
|
|
413
|
-
void availableModels(collabConfig).then((models) => {
|
|
456
|
+
void availableModels(collabConfig, workspaceConfig).then((models) => {
|
|
414
457
|
if (socket.readyState === socket.OPEN)
|
|
415
458
|
socket.send(JSON.stringify({ type: "config", apiKeyConfigured: lassoKeyConfigured, agentConfigured: lassoKeyConfigured || Boolean(agentConfig) || localAgents.size > 0, models, collab: collabConfig?.registered ? { ...collabConfig, plan: collabConfig.plan, configuredProviders: collabConfig.configuredProviders } : null, user: userInfo }));
|
|
416
459
|
});
|
|
@@ -763,7 +806,14 @@ function startBridge(cwd = process.cwd(), collabConfig = null, bridgePort = expo
|
|
|
763
806
|
return;
|
|
764
807
|
}
|
|
765
808
|
const provider = (msg.provider || agentConfig?.provider || "google");
|
|
766
|
-
const model = msg.model || agentConfig?.model
|
|
809
|
+
const model = msg.model || agentConfig?.model;
|
|
810
|
+
if (!model) {
|
|
811
|
+
socket.send(JSON.stringify({
|
|
812
|
+
type: "error",
|
|
813
|
+
message: "No model selected. Please select a model in settings."
|
|
814
|
+
}));
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
767
817
|
const localKey = localProviderKeys[provider];
|
|
768
818
|
const serverUrl = collabConfig?.serverUrl || (0, auth_1.serverUrlFrom)(fileEnv);
|
|
769
819
|
const targetApiUrl = (serverUrl.includes("collab.lasso.byorello.space") ? "https://api.lasso.byorello.space" : serverUrl).replace(/\/api\/v1\/?$/, "");
|
|
@@ -75,7 +75,7 @@ void (async () => {
|
|
|
75
75
|
const nextBin = node_path_1.default.join(cwd, "node_modules", ".bin", "next");
|
|
76
76
|
if (!node_fs_1.default.existsSync(nextBin))
|
|
77
77
|
throw new Error("Next.js is not installed in this project.");
|
|
78
|
-
const bridge = (0, bridge_1.startBridge)(cwd, null, bridgePort);
|
|
78
|
+
const bridge = await (0, bridge_1.startBridge)(cwd, null, bridgePort);
|
|
79
79
|
const nextProcess = (0, node_child_process_1.spawn)(nextBin, ["dev", "--port", String(nextPort), "--hostname", "127.0.0.1"], {
|
|
80
80
|
cwd,
|
|
81
81
|
env: { ...process.env, PORT: String(nextPort) },
|
|
@@ -27,7 +27,7 @@ function lassoOverlayPlugin() {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
void (async () => {
|
|
30
|
-
const bridge = (0, bridge_1.startBridge)(cwd, null, bridgePort);
|
|
30
|
+
const bridge = await (0, bridge_1.startBridge)(cwd, null, bridgePort);
|
|
31
31
|
const require = (0, node_module_1.createRequire)(node_path_1.default.join(cwd, "package.json"));
|
|
32
32
|
const viteEntry = require.resolve("vite");
|
|
33
33
|
const { createServer } = await import((0, node_url_1.pathToFileURL)(viteEntry).href);
|
package/dist/cli/index.js
CHANGED
|
@@ -94,6 +94,23 @@ program.command("create [name]")
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
// prettier-ignore
|
|
97
|
+
program.command("whoami")
|
|
98
|
+
.description("Show the currently authenticated Lasso user")
|
|
99
|
+
.action(() => {
|
|
100
|
+
const credentials = (0, auth_1.loadCredentials)();
|
|
101
|
+
if (!credentials) {
|
|
102
|
+
console.log(chalk_1.default.dim("Not authenticated. Run ") + chalk_1.default.cyan("lasso auth login") + chalk_1.default.dim(" to sign in."));
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const summary = (0, auth_1.credentialSummary)(credentials);
|
|
107
|
+
console.log(chalk_1.default.green("✓") + ` Authenticated as ${chalk_1.default.bold(summary.userEmail || "your account")}` + (summary.userName ? ` (${summary.userName})` : ""));
|
|
108
|
+
if (summary.workspaceName)
|
|
109
|
+
console.log(chalk_1.default.dim(`Workspace: ${summary.workspaceName} (${summary.workspaceId})`));
|
|
110
|
+
if (summary.masked)
|
|
111
|
+
console.log(chalk_1.default.dim("Credential: ") + summary.masked);
|
|
112
|
+
});
|
|
113
|
+
// prettier-ignore
|
|
97
114
|
const daemon = program.command("daemon [action]")
|
|
98
115
|
.description("Start/stop Lasso Host — the local .lasso domain server (start|status|stop|restart|install|uninstall)")
|
|
99
116
|
.usage("[action]")
|
|
@@ -439,7 +456,7 @@ program.command("dev", { isDefault: true }).description("Start your dev server w
|
|
|
439
456
|
else {
|
|
440
457
|
console.log(chalk_1.default.yellow("!") + ` No ${chalk_1.default.bold(project_1.LASSO_CONFIG_FILE)} found. Run ${chalk_1.default.cyan("npx @lasso-ai/cli init")} to enable realtime collaboration.`);
|
|
441
458
|
}
|
|
442
|
-
(0, bridge_1.startBridge)(cwd, collabConfig);
|
|
459
|
+
await (0, bridge_1.startBridge)(cwd, collabConfig);
|
|
443
460
|
switch (framework) {
|
|
444
461
|
case "vite":
|
|
445
462
|
console.log(chalk_1.default.green("✓") + " Detected: Vite");
|
package/dist/cli/oneshot.d.ts
CHANGED
|
@@ -31,5 +31,4 @@ export interface ConversationMessage {
|
|
|
31
31
|
role: "user" | "assistant";
|
|
32
32
|
content: string;
|
|
33
33
|
}
|
|
34
|
-
export declare function isConversationalPrompt(prompt: string): boolean;
|
|
35
34
|
export declare function runOneShotAgent(cwd: string, prompt: string, scope: "project" | "component", config: AgentConfig, signal: AbortSignal, onProgress: ProgressCallback, onPrompt: PromptCallback, messages?: ConversationMessage[], serverUrl?: string, apiKey?: string, taskId?: string, pageContext?: any): Promise<OneShotResult>;
|
package/dist/cli/oneshot.js
CHANGED
|
@@ -3,13 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.isConversationalPrompt = isConversationalPrompt;
|
|
7
6
|
exports.runOneShotAgent = runOneShotAgent;
|
|
8
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
9
|
const node_child_process_1 = require("node:child_process");
|
|
11
10
|
const node_util_1 = require("node:util");
|
|
12
11
|
const agent_1 = require("./agent");
|
|
12
|
+
const auth_1 = require("./auth");
|
|
13
13
|
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
14
14
|
const CLI_PROVIDERS = new Set(["claude-code", "codex", "opencode", "cursor"]);
|
|
15
15
|
function isCliProvider(provider) {
|
|
@@ -40,28 +40,46 @@ function gatewayTargetForCli(provider, model) {
|
|
|
40
40
|
return { provider: "google", model: GATEWAY_MODELS.google };
|
|
41
41
|
return { provider: "anthropic", model: GATEWAY_MODELS.anthropic };
|
|
42
42
|
}
|
|
43
|
-
function isConversationalPrompt(prompt) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
async function isConversationalPrompt(prompt, config, apiKey) {
|
|
44
|
+
// Use the user's selected model to intelligently detect if prompt is conversational vs. action-oriented
|
|
45
|
+
try {
|
|
46
|
+
// Use the gateway to classify - this respects the user's selected model
|
|
47
|
+
const response = await fetch(`${config.serverUrl || "https://api.lasso.byorello.space"}/api/v1/agent/generate`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
"Authorization": `Bearer ${apiKey || ""}`,
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
model: {
|
|
55
|
+
id: config.model,
|
|
56
|
+
provider: config.provider,
|
|
57
|
+
},
|
|
58
|
+
prompt: `Classify this user prompt as either "conversational" or "action". Return ONLY the word "conversational" or "action" - nothing else.
|
|
59
|
+
|
|
60
|
+
Prompt: "${prompt}"`,
|
|
61
|
+
maxTokens: 10,
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
const classification = data.reply?.toLowerCase().trim();
|
|
67
|
+
console.log('[Lasso] Classification result:', classification);
|
|
68
|
+
return classification === "conversational";
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log('[Lasso] Classification failed:', response.status);
|
|
72
|
+
}
|
|
58
73
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (questionOnly && !actionVerbs.test(p)) {
|
|
62
|
-
return true;
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.log('[Lasso] Classification error:', err);
|
|
63
76
|
}
|
|
64
|
-
|
|
77
|
+
// Fallback: smarter heuristic
|
|
78
|
+
const p = prompt.trim().toLowerCase();
|
|
79
|
+
const actionKeywords = /(add|create|build|make|fix|update|modify|change|refactor|implement|delete|remove|style|install|wire|code|replace|insert|append|generate)/i;
|
|
80
|
+
const isAction = actionKeywords.test(p);
|
|
81
|
+
console.log('[Lasso] Fallback classification:', isAction ? 'action' : 'conversational');
|
|
82
|
+
return !isAction;
|
|
65
83
|
}
|
|
66
84
|
function runtimeErrorObservation(pageContext) {
|
|
67
85
|
const errors = Array.isArray(pageContext?.runtimeErrors) ? pageContext.runtimeErrors : [];
|
|
@@ -113,7 +131,10 @@ Do not output code changes, file patches, or JSON schemas — just talk to the d
|
|
|
113
131
|
return "Hello! I'm Lasso, your AI pair programmer. You can ask me questions about your project or tell me what to build, modify, or fix. Run `lasso auth login` (or add a provider API key in settings) to unlock AI replies.";
|
|
114
132
|
}
|
|
115
133
|
try {
|
|
116
|
-
const model = config.model
|
|
134
|
+
const model = config.model;
|
|
135
|
+
if (!model) {
|
|
136
|
+
return "No model selected. Please select a model in settings.";
|
|
137
|
+
}
|
|
117
138
|
if (config.provider === "openai" || config.provider === "ollama" || config.provider === "nvidia") {
|
|
118
139
|
const baseUrl = config.baseUrl || (config.provider === "nvidia" ? "https://integrate.api.nvidia.com/v1" : "https://api.openai.com/v1");
|
|
119
140
|
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
@@ -184,9 +205,10 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
184
205
|
if (signal.aborted) {
|
|
185
206
|
return { ok: false, error: "Operation was cancelled" };
|
|
186
207
|
}
|
|
187
|
-
const isConversational = isConversationalPrompt(prompt);
|
|
188
|
-
|
|
189
|
-
|
|
208
|
+
const isConversational = await isConversationalPrompt(prompt, config, apiKey);
|
|
209
|
+
console.log('[Lasso] Is conversational:', isConversational);
|
|
210
|
+
// Browser inspection & snapshot steps - skip for simple conversational queries
|
|
211
|
+
if (pageContext && !isConversational) {
|
|
190
212
|
onProgress("thinking", "Inspecting browser page...", `Page: "${pageContext.title || "Landing page"}" (${pageContext.route || "/"})`);
|
|
191
213
|
if (pageContext.viewport) {
|
|
192
214
|
onProgress("thinking", "Capturing visual snapshot...", `Viewport: ${pageContext.viewport.width}x${pageContext.viewport.height} (Document height: ${pageContext.viewport.scrollHeight}px)`);
|
|
@@ -201,8 +223,9 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
201
223
|
else {
|
|
202
224
|
onProgress("thinking", isConversational ? "Thinking..." : "Analyzing project structure...", isConversational ? undefined : "Scanning files and dependencies");
|
|
203
225
|
}
|
|
204
|
-
|
|
205
|
-
const
|
|
226
|
+
// Skip project structure analysis for simple conversational queries
|
|
227
|
+
const projectStructure = isConversational ? null : await analyzeProject(cwd);
|
|
228
|
+
const framework = isConversational ? null : detectFramework(cwd);
|
|
206
229
|
if (!isConversational && !pageContext) {
|
|
207
230
|
onProgress("thinking", "Inspecting application...", `Detected ${framework} framework`);
|
|
208
231
|
}
|
|
@@ -216,15 +239,18 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
216
239
|
}
|
|
217
240
|
const targetServerUrl = rawServerUrl.replace(/\/$/, "").replace(/\/api\/v1$/, "");
|
|
218
241
|
const useCliAgent = isCliProvider(config.provider);
|
|
219
|
-
|
|
242
|
+
// Prioritize CLI auth credentials over env vars
|
|
243
|
+
const cliCredentials = (0, auth_1.loadCredentials)();
|
|
244
|
+
const gatewayKey = cliCredentials?.apiKey || apiKey || config.lassoKey || (config.apiKey && (config.apiKey.startsWith("lss_live_") || config.apiKey.startsWith("lss_")) ? config.apiKey : "") || process.env.LASSO_API_KEY || "";
|
|
220
245
|
const hasLocalProviderKey = Boolean(config.apiKey &&
|
|
221
246
|
config.apiKey !== "ollama" &&
|
|
222
247
|
!config.apiKey.startsWith("lss_live_") &&
|
|
223
248
|
!config.apiKey.startsWith("lss_"));
|
|
249
|
+
// If the user has CLI auth, always route to server (unless using local agents)
|
|
224
250
|
// If the user does NOT have a local provider key in .env, route to Lasso Agent Gateway on server.
|
|
225
251
|
// When they DO have a local key in .env, it runs locally to save server resources!
|
|
226
252
|
// Ollama always runs locally (the server cannot reach the user's localhost).
|
|
227
|
-
const shouldRouteToServer = !useCliAgent && config.provider !== "ollama" && !hasLocalProviderKey && Boolean(targetServerUrl);
|
|
253
|
+
const shouldRouteToServer = !useCliAgent && config.provider !== "ollama" && (Boolean(cliCredentials) || !hasLocalProviderKey) && Boolean(targetServerUrl);
|
|
228
254
|
const callGateway = async (target) => {
|
|
229
255
|
try {
|
|
230
256
|
if (!isConversational && !pageContext) {
|
|
@@ -241,10 +267,15 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
241
267
|
const controller = new AbortController();
|
|
242
268
|
const onAbort = () => controller.abort();
|
|
243
269
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
270
|
+
// Add a 5-minute timeout for gateway requests
|
|
271
|
+
const timeout = setTimeout(() => {
|
|
272
|
+
controller.abort();
|
|
273
|
+
}, 5 * 60 * 1000);
|
|
274
|
+
timeout.unref();
|
|
244
275
|
// Build sourceHints: read relevant file contents to give the AI real code context.
|
|
245
276
|
// This is what enables code changes instead of "please share the file" responses.
|
|
246
277
|
const sourceHints = {};
|
|
247
|
-
if (!isConversational) {
|
|
278
|
+
if (!isConversational && projectStructure) {
|
|
248
279
|
const promptLower = prompt.toLowerCase();
|
|
249
280
|
const allCssFiles = projectStructure.files.filter((f) => /\.(css|scss)$/i.test(f));
|
|
250
281
|
const entryFiles = projectStructure.files.filter((f) => /(App|page|index|layout|main|hero|header|landing)\.(tsx|jsx|js|ts|html)$/i.test(f));
|
|
@@ -274,30 +305,43 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
274
305
|
provider: target.provider,
|
|
275
306
|
},
|
|
276
307
|
context: {
|
|
277
|
-
framework,
|
|
278
|
-
hasTypeScript: projectStructure
|
|
279
|
-
hasTailwind: projectStructure
|
|
280
|
-
hasReact: projectStructure
|
|
281
|
-
hasNext: projectStructure
|
|
282
|
-
hasVite: projectStructure
|
|
283
|
-
dependencies: projectStructure
|
|
284
|
-
devDependencies: projectStructure
|
|
285
|
-
files: projectStructure
|
|
308
|
+
framework: framework || undefined,
|
|
309
|
+
hasTypeScript: projectStructure?.hasTypeScript,
|
|
310
|
+
hasTailwind: projectStructure?.hasTailwind,
|
|
311
|
+
hasReact: projectStructure?.hasReact,
|
|
312
|
+
hasNext: projectStructure?.hasNext,
|
|
313
|
+
hasVite: projectStructure?.hasVite,
|
|
314
|
+
dependencies: projectStructure?.dependencies,
|
|
315
|
+
devDependencies: projectStructure?.devDependencies,
|
|
316
|
+
files: projectStructure?.files.slice(0, 100) || [],
|
|
286
317
|
sourceHints: Object.keys(sourceHints).length > 0 ? sourceHints : undefined,
|
|
287
318
|
currentRoute: pageContext?.route || "/",
|
|
288
319
|
pageContext,
|
|
289
320
|
},
|
|
290
321
|
}),
|
|
291
322
|
});
|
|
323
|
+
clearTimeout(timeout);
|
|
292
324
|
signal.removeEventListener("abort", onAbort);
|
|
325
|
+
if (signal.aborted) {
|
|
326
|
+
return { ok: false, error: "Request timed out after 5 minutes. Your plan quota may be exhausted. Please upgrade your plan or add credits to continue using this service." };
|
|
327
|
+
}
|
|
293
328
|
if (!response.ok) {
|
|
294
329
|
const errData = (await response.json().catch(() => ({})));
|
|
295
330
|
const errorMessage = errData.error || `Agent request failed (${response.status})`;
|
|
331
|
+
// Check for quota/plan exhaustion
|
|
332
|
+
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)) {
|
|
333
|
+
return { ok: false, error: "Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service." };
|
|
334
|
+
}
|
|
296
335
|
return { ok: false, error: errorMessage };
|
|
297
336
|
}
|
|
298
337
|
const data = (await response.json());
|
|
299
338
|
if (data.status === "failed" || data.error) {
|
|
300
|
-
|
|
339
|
+
const error = data.error || data.summary || "Agent session failed on server.";
|
|
340
|
+
// Check for quota/plan exhaustion
|
|
341
|
+
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(error)) {
|
|
342
|
+
return { ok: false, error: "Your plan quota has been exhausted. Please upgrade your plan or add credits to continue using this service." };
|
|
343
|
+
}
|
|
344
|
+
return { ok: false, error };
|
|
301
345
|
}
|
|
302
346
|
if (data) {
|
|
303
347
|
const changes = Array.isArray(data.changes) ? data.changes : [];
|
|
@@ -422,7 +466,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
422
466
|
}
|
|
423
467
|
if (useCliAgent && isConversational) {
|
|
424
468
|
try {
|
|
425
|
-
const reply = await generateConversationalReply(cwd, prompt, framework, config, signal, messages, pageContext, (message, detail) => onProgress("thinking", message, detail), taskId, (agentPrompt) => onPrompt({ question: agentPrompt.message, options: agentPrompt.options }));
|
|
469
|
+
const reply = await generateConversationalReply(cwd, prompt, framework || "", config, signal, messages, pageContext, (message, detail) => onProgress("thinking", message, detail), taskId, (agentPrompt) => onPrompt({ question: agentPrompt.message, options: agentPrompt.options }));
|
|
426
470
|
return {
|
|
427
471
|
ok: true,
|
|
428
472
|
summary: reply,
|
|
@@ -443,7 +487,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
443
487
|
}
|
|
444
488
|
// If it's a conversational prompt and server was not used or failed, respond directly without modifying files!
|
|
445
489
|
if (isConversational) {
|
|
446
|
-
const reply = await generateConversationalReply(cwd, prompt, framework, config, signal, messages, pageContext);
|
|
490
|
+
const reply = await generateConversationalReply(cwd, prompt, framework || "", config, signal, messages, pageContext);
|
|
447
491
|
return {
|
|
448
492
|
ok: true,
|
|
449
493
|
summary: reply,
|
|
@@ -454,7 +498,7 @@ async function runOneShotAgent(cwd, prompt, scope, config, signal, onProgress, o
|
|
|
454
498
|
}
|
|
455
499
|
// Phase 2: Local agent generation fallback for code changes
|
|
456
500
|
onProgress("thinking", "Generating implementation plan...", `Planning components for ${prompt}`);
|
|
457
|
-
const plan = await generatePlan(cwd, prompt, scope, projectStructure, framework, config, signal, messages, pageContext);
|
|
501
|
+
const plan = await generatePlan(cwd, prompt, scope, projectStructure || { framework: "", hasTypeScript: false, hasTailwind: false, hasReact: false, hasNext: false, hasVite: false, dependencies: [], devDependencies: [], files: [] }, framework || "", config, signal, messages, pageContext);
|
|
458
502
|
if (signal.aborted) {
|
|
459
503
|
return { ok: false, error: "Operation was cancelled during planning" };
|
|
460
504
|
}
|
|
@@ -589,8 +633,8 @@ async function analyzeProject(cwd) {
|
|
|
589
633
|
hasReact,
|
|
590
634
|
hasNext,
|
|
591
635
|
hasVite,
|
|
592
|
-
dependencies,
|
|
593
|
-
devDependencies,
|
|
636
|
+
dependencies: dependencies,
|
|
637
|
+
devDependencies: devDependencies,
|
|
594
638
|
files,
|
|
595
639
|
};
|
|
596
640
|
}
|
|
@@ -711,7 +755,14 @@ Analyze the existing code structure, then respond in JSON:`;
|
|
|
711
755
|
};
|
|
712
756
|
}
|
|
713
757
|
try {
|
|
714
|
-
const model = config.model
|
|
758
|
+
const model = config.model;
|
|
759
|
+
if (!model) {
|
|
760
|
+
return {
|
|
761
|
+
steps: [],
|
|
762
|
+
isConversational: true,
|
|
763
|
+
reply: "No model selected. Please select a model in settings.",
|
|
764
|
+
};
|
|
765
|
+
}
|
|
715
766
|
let text = "";
|
|
716
767
|
if (config.provider === "openai" || config.provider === "ollama" || config.provider === "nvidia") {
|
|
717
768
|
const baseUrl = config.baseUrl || (config.provider === "nvidia" ? "https://integrate.api.nvidia.com/v1" : "https://api.openai.com/v1");
|
package/dist/cli/project.d.ts
CHANGED
|
@@ -15,6 +15,24 @@ export interface CollabConfig {
|
|
|
15
15
|
plan?: string;
|
|
16
16
|
configuredProviders?: string[];
|
|
17
17
|
}
|
|
18
|
+
export interface WorkspaceConfig {
|
|
19
|
+
workspaceId?: string;
|
|
20
|
+
workspaceName?: string;
|
|
21
|
+
plan?: string;
|
|
22
|
+
configuredProviders?: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface ModelInfo {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
provider: string;
|
|
28
|
+
capabilities?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface ModelsResponse {
|
|
31
|
+
anthropic: ModelInfo[];
|
|
32
|
+
openai: ModelInfo[];
|
|
33
|
+
google: ModelInfo[];
|
|
34
|
+
nvidia: ModelInfo[];
|
|
35
|
+
}
|
|
18
36
|
export interface InitResult {
|
|
19
37
|
ok: boolean;
|
|
20
38
|
error?: string;
|
|
@@ -57,3 +75,13 @@ export declare function initProject(cwd: string, fileEnv: Record<string, string>
|
|
|
57
75
|
* safely degraded (no collaboration, local editing unaffected) on any failure.
|
|
58
76
|
*/
|
|
59
77
|
export declare function resolveCollabSession(cwd: string, fileEnv: Record<string, string>, projectId: string): Promise<CollabConfig>;
|
|
78
|
+
/**
|
|
79
|
+
* Fetch workspace configuration from the server using CLI auth credentials.
|
|
80
|
+
* This allows users to see their dashboard-configured providers even without a project config.
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveWorkspaceConfig(fileEnv: Record<string, string>): Promise<WorkspaceConfig>;
|
|
83
|
+
/**
|
|
84
|
+
* Fetch available models from the server's models endpoint.
|
|
85
|
+
* This fetches from provider APIs dynamically and caches results.
|
|
86
|
+
*/
|
|
87
|
+
export declare function fetchModels(fileEnv: Record<string, string>): Promise<ModelsResponse>;
|
package/dist/cli/project.js
CHANGED
|
@@ -10,6 +10,8 @@ exports.resolveLassoApiKey = resolveLassoApiKey;
|
|
|
10
10
|
exports.readProjectConfig = readProjectConfig;
|
|
11
11
|
exports.initProject = initProject;
|
|
12
12
|
exports.resolveCollabSession = resolveCollabSession;
|
|
13
|
+
exports.resolveWorkspaceConfig = resolveWorkspaceConfig;
|
|
14
|
+
exports.fetchModels = fetchModels;
|
|
13
15
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
17
|
const node_readline_1 = __importDefault(require("node:readline"));
|
|
@@ -351,3 +353,58 @@ async function resolveCollabSession(cwd, fileEnv, projectId) {
|
|
|
351
353
|
return config;
|
|
352
354
|
}
|
|
353
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Fetch workspace configuration from the server using CLI auth credentials.
|
|
358
|
+
* This allows users to see their dashboard-configured providers even without a project config.
|
|
359
|
+
*/
|
|
360
|
+
async function resolveWorkspaceConfig(fileEnv) {
|
|
361
|
+
const serverUrl = (0, auth_1.serverUrlFrom)(fileEnv).replace(/\/$/, "");
|
|
362
|
+
const apiUrl = `${serverUrl}/api/v1`;
|
|
363
|
+
const apiKey = resolveLassoApiKey(fileEnv);
|
|
364
|
+
if (!apiKey) {
|
|
365
|
+
return {};
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const response = await (0, auth_1.fetchWithTimeout)(`${apiUrl}/auth/workspace`, {
|
|
369
|
+
method: "GET",
|
|
370
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
371
|
+
});
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
return {};
|
|
374
|
+
}
|
|
375
|
+
const body = (await response.json());
|
|
376
|
+
if (!body.success || !body.workspace) {
|
|
377
|
+
return {};
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
workspaceId: body.workspace.id,
|
|
381
|
+
workspaceName: body.workspace.name,
|
|
382
|
+
plan: body.workspace.plan || "free",
|
|
383
|
+
configuredProviders: body.workspace.configuredProviders || [],
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return {};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Fetch available models from the server's models endpoint.
|
|
392
|
+
* This fetches from provider APIs dynamically and caches results.
|
|
393
|
+
*/
|
|
394
|
+
async function fetchModels(fileEnv) {
|
|
395
|
+
const serverUrl = (0, auth_1.serverUrlFrom)(fileEnv).replace(/\/$/, "");
|
|
396
|
+
const apiUrl = `${serverUrl}/api/v1`;
|
|
397
|
+
try {
|
|
398
|
+
const response = await (0, auth_1.fetchWithTimeout)(`${apiUrl}/models`, {
|
|
399
|
+
method: "GET",
|
|
400
|
+
});
|
|
401
|
+
if (!response.ok) {
|
|
402
|
+
return { anthropic: [], openai: [], google: [], nvidia: [] };
|
|
403
|
+
}
|
|
404
|
+
const body = await response.json();
|
|
405
|
+
return body;
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return { anthropic: [], openai: [], google: [], nvidia: [] };
|
|
409
|
+
}
|
|
410
|
+
}
|
package/package.json
CHANGED