Skip to content

Commit d056588

Browse files
authored
Merge pull request #95 from levelcodeai/claude/beautiful-diffie-96e678
fix(cloud): inline edit and Agent Sketch say the session expired — not "No API key set"
2 parents 30fca4d + 4ef3ee0 commit d056588

5 files changed

Lines changed: 367 additions & 17 deletions

File tree

‎extensions/levelcode-ai/aiEdit.js‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
const vscode = require('vscode');
1313
const path = require('path');
14+
const { SESSION_EXPIRED_MESSAGE } = require('./providers/session');
1415

1516
const SCHEME = 'levelcode-ai';
1617
const CTX_DIFF_ACTIVE = 'levelcode.ai.diffActive';
18+
const SIGN_IN = 'Sign in'; // the button on an edit refused for an ended session, labelled as the chat card's is
1719

1820
/** @type {Map<string,string>} virtual-doc uri -> content */
1921
const contents = new Map();
@@ -105,7 +107,7 @@ function fullLineRange(editor) {
105107
return new vscode.Range(sel.start.line, 0, endLine, editor.document.lineAt(endLine).text.length);
106108
}
107109

108-
/** @param {{aiConfig:()=>any, prepProviderRequest:(o?:any)=>Promise<any>, streamChat:Function}} deps */
110+
/** @param {{aiConfig:()=>any, prepProviderRequest:(o?:any)=>Promise<any>, streamChat:Function, accountSignIn:()=>Promise<any>}} deps */
109111
async function editSelection(deps) {
110112
const ed = vscode.window.activeTextEditor;
111113
if (!ed || ed.selection.isEmpty) {
@@ -136,6 +138,10 @@ async function editSelection(deps) {
136138
const onDelta = (d) => { result += d; };
137139
const req = await deps.prepProviderRequest({ prompt: true });
138140
if (!req.ok) {
141+
// An ended LevelCode Cloud session is not a missing key: "No API key set for LevelCode
142+
// Cloud." sent the user looking for one they never needed. Say so, in the sentence chat
143+
// and the agent send for it, and mark it so the catch below can offer the way back.
144+
if (req.reason === 'signedOut') { throw Object.assign(new Error(SESSION_EXPIRED_MESSAGE), { code: 'session_expired' }); }
139145
throw new Error(
140146
req.reason === 'baseURL' ? 'Set a base URL for the custom OpenAI-compatible provider first (levelcode.ai.baseURL).'
141147
: req.reason === 'insecureBaseURL' ? 'Refusing to send your API key over plain http to a non-local host. Use an https (or localhost) base URL.'
@@ -149,7 +155,14 @@ async function editSelection(deps) {
149155
}
150156
);
151157
} catch (e) {
152-
vscode.window.showErrorMessage('LevelCode AI edit failed: ' + String((e && e.message) || e));
158+
const failed = 'LevelCode AI edit failed: ' + String((e && e.message) || e);
159+
// The one failure with its fix a click away: the same browser sign-in the chat's card starts.
160+
if (e && e.code === 'session_expired') {
161+
const pick = await vscode.window.showErrorMessage(failed, SIGN_IN);
162+
if (pick === SIGN_IN) { await deps.accountSignIn(); }
163+
return;
164+
}
165+
vscode.window.showErrorMessage(failed);
153166
return;
154167
}
155168

‎extensions/levelcode-ai/extension.js‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3233,13 +3233,25 @@ async function webHandoffUrl() {
32333233
return (data && data.url) || null;
32343234
} catch (e) { dbg('account.handoff', { error: String((e && e.message) || e) }); return null; }
32353235
}
3236-
/** Persist an editor session: access token (required), optional refresh token, and display profile. */
3236+
/**
3237+
* Persist an editor session: access token (required), the refresh token if the sign-in brought one,
3238+
* and the display profile.
3239+
*
3240+
* A sign-in REPLACES the session; it does not top one up. A refresh token left over from whatever
3241+
* was here before is what the new session's first renewal would be made with: refused, it ends the
3242+
* session that replaced it; still good, it hands this editor the previous account's access token
3243+
* under the new account's name. So the refresh token is settled first — stored, or forgotten when
3244+
* there is none — and only then the access token. A sign-in cut short between the two (the editor
3245+
* closing, a keychain that will not write) must not leave the new access token over the old
3246+
* refresh token either.
3247+
*/
32373248
async function storeSession(access, refresh, profile) {
32383249
if (!ctx || !access) { return; }
32393250
await withSessionLock(async () => {
32403251
sessionGeneration++; // a refresh still out for the session this replaces must not touch the new one
3241-
await ctx.secrets.store(ACCOUNT_TOKEN_KEY, access);
32423252
if (refresh) { await ctx.secrets.store(ACCOUNT_REFRESH_KEY, refresh); }
3253+
else { await ctx.secrets.delete(ACCOUNT_REFRESH_KEY); }
3254+
await ctx.secrets.store(ACCOUNT_TOKEN_KEY, access);
32433255
cloudSignedIn = true;
32443256
await ctx.globalState.update(ACCOUNT_PROFILE_KEY, {
32453257
name: (profile && profile.name) || '', email: (profile && profile.email) || '', plan: (profile && profile.plan) || ''
@@ -3421,7 +3433,8 @@ function activate(context) {
34213433
registerAiEdit(context, {
34223434
aiConfig,
34233435
prepProviderRequest,
3424-
streamChat: providers.streamChat
3436+
streamChat: providers.streamChat,
3437+
accountSignIn // the "Sign in" on an edit refused because the cloud session ended
34253438
});
34263439

34273440
// Claude as a native Language Model provider (powers VS Code's built-in chat/edit UI).

‎extensions/levelcode-ai/sketch.js‎

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const path = require("path");
2121
const fs = require("fs");
2222
const providers = require("./providers/index");
2323
const catalog = require("./providers/catalog");
24+
const { SESSION_EXPIRED_MESSAGE } = require("./providers/session");
2425
const { AGENT_GROUPS, AGENTS, AGENT_BY_ID } = require("./sketch/agentCatalog");
2526
const graph = require("./sketch/graph");
2627
const pricing = require("./sketch/pricing");
@@ -458,6 +459,24 @@ async function loadWorkspacePatchContext() {
458459
);
459460
}
460461

462+
/**
463+
* Why prepProviderRequest refused, as this panel says it. An ended LevelCode Cloud session is not a
464+
* missing key, and not a provider that is "not ready": it gets the sentence chat and the agent send
465+
* for it, so the user goes to sign in instead of hunting for a key they never needed. Any other
466+
* reason keeps the caller's wording (`otherwise`).
467+
* @param {{reason?:string, label?:string}} req
468+
* @param {string} otherwise
469+
*/
470+
function notReadyMessage(req, otherwise) {
471+
if (req.reason === "signedOut") {
472+
return SESSION_EXPIRED_MESSAGE;
473+
}
474+
if (req.reason === "key") {
475+
return "No API key set for " + req.label + ".";
476+
}
477+
return otherwise;
478+
}
479+
461480
// ---- the runner -------------------------------------------------------------------------------
462481
/** @param {{sketch:any, deps:any}} o deps = { prepProviderRequest } from extension.js */
463482
async function runSketch(o) {
@@ -497,10 +516,10 @@ async function runSketch(o) {
497516
clearSlot();
498517
post({
499518
type: "runError",
500-
message:
501-
req.reason === "key"
502-
? "No API key set for " + req.label + "."
503-
: "Provider not ready (" + (req.reason || "unknown") + ").",
519+
message: notReadyMessage(
520+
req,
521+
"Provider not ready (" + (req.reason || "unknown") + ").",
522+
),
504523
});
505524
return;
506525
}
@@ -745,10 +764,7 @@ async function boardCommand(o) {
745764
if (!req.ok) {
746765
post({
747766
type: "uiError",
748-
message:
749-
req.reason === "key"
750-
? "No API key set for " + req.label + "."
751-
: "Provider not ready.",
767+
message: notReadyMessage(req, "Provider not ready."),
752768
});
753769
return;
754770
}
@@ -923,10 +939,7 @@ async function generateFlow(o) {
923939
if (!req.ok) {
924940
post({
925941
type: "uiError",
926-
message:
927-
req.reason === "key"
928-
? "No API key set for " + req.label + "."
929-
: "Provider not ready.",
942+
message: notReadyMessage(req, "Provider not ready."),
930943
});
931944
return;
932945
}

0 commit comments

Comments
 (0)