pi-provider-qoder-cn 0.2.2 → 0.2.3

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.
Files changed (3) hide show
  1. package/README.md +31 -3
  2. package/dist/index.js +100 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -138,11 +138,39 @@ at startup is:
138
138
 
139
139
  1. `QODER_VPC_ENDPOINT` / `QODERCN_VPC_ENDPOINT`
140
140
  2. `~/.pi/agent/qoder-cn-settings.json`
141
- 3. `~/.pi/agent/auth.json` (the `qoder-cn` entry)
142
- 4. `~/.qoder-cn/settings.json` (the Qoder IDE's own file)
141
+
142
+ Only these two count as chosen by you. This extension deliberately ignores
143
+ endpoint values written by other Qoder clients — pi is one client among several,
144
+ and another client's endpoint must not silently redirect pi's traffic.
143
145
 
144
146
  > The token and the endpoint must belong to the same instance. Pointing an
145
- > enterprise endpoint at a public-gateway token fails authentication.
147
+ > enterprise endpoint at a public-gateway token fails authentication, which is
148
+ > why `/login` settles the endpoint before it asks for the PAT.
149
+
150
+ ### Endpoint choice during `/login`
151
+
152
+ `/login qoder-cn` asks which endpoint to use **before** prompting for the PAT,
153
+ because the enterprise gateway also serves the token exchange. The list shows
154
+ the currently saved endpoint and defaults to the personal account:
155
+
156
+ ```text
157
+ Choose the Qoder CN endpoint (default: personal account)
158
+ → Personal account — public gateway (qoder.com.cn)
159
+ Enterprise account — use a VPC endpoint
160
+ Enterprise account — <detected value> (detected: stored credential)
161
+ ```
162
+
163
+ - **Personal account** uses the public gateway and pins that choice, so a value
164
+ left behind by another client cannot resurface. If `QODER_VPC_ENDPOINT` /
165
+ `QODERCN_VPC_ENDPOINT` is set, it still wins — `/login` says so and warns
166
+ that a personal pick reverts on restart until the variable is removed.
167
+ - **Enterprise account** then prompts for the instance name or domain.
168
+ - Switching back and forth is expected; re-running `/login` presents the saved
169
+ endpoint so you can change it.
170
+
171
+ A `vpc_endpoint` found in `~/.pi/agent/auth.json` is offered as the third option
172
+ but never applied on its own. `/qoder-endpoint` with no argument also reports it
173
+ under “Detected (not applied)”.
146
174
 
147
175
  ## Models
148
176
 
package/dist/index.js CHANGED
@@ -268,21 +268,14 @@ function readVpcEndpointFromAuth() {
268
268
  }
269
269
  return void 0;
270
270
  }
271
- function readVpcEndpointFromQoderIde() {
272
- try {
273
- const path = join2(getHomeDir2(), ".qoder-cn", "settings.json");
274
- if (!existsSync2(path)) return void 0;
275
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
276
- const value = parsed?.vpc_endpoint ?? parsed?.vpcEndpoint ?? parsed?.vpcInstanceName;
277
- if (value !== void 0 && value !== "") return String(value);
278
- } catch {
279
- }
280
- return void 0;
271
+ function readInheritedVpcEndpoint() {
272
+ const value = readVpcEndpointFromAuth();
273
+ return value ? { value, source: "stored credential" } : void 0;
281
274
  }
282
275
  function readQoderVpcEndpoint() {
283
276
  const fromEnv = process.env.QODER_VPC_ENDPOINT || process.env.QODERCN_VPC_ENDPOINT;
284
277
  if (fromEnv) return fromEnv;
285
- return readVpcEndpointFromSettings() ?? readVpcEndpointFromAuth() ?? readVpcEndpointFromQoderIde() ?? "";
278
+ return readVpcEndpointFromSettings() ?? "";
286
279
  }
287
280
  function isResetValue(value) {
288
281
  const lowered = value.toLowerCase();
@@ -1217,6 +1210,15 @@ import crypto2 from "node:crypto";
1217
1210
  function getPrompt(callbacks) {
1218
1211
  return callbacks.onPrompt;
1219
1212
  }
1213
+ function getSelect(callbacks) {
1214
+ const fn = callbacks.onSelect;
1215
+ return typeof fn === "function" ? fn : void 0;
1216
+ }
1217
+ function isLoginCancellation(error, callbacks) {
1218
+ if (getSignal(callbacks)?.aborted) return true;
1219
+ const message = error instanceof Error ? error.message : String(error);
1220
+ return message.toLowerCase().includes("cancel");
1221
+ }
1220
1222
  function getProgress(callbacks) {
1221
1223
  return callbacks.onProgress;
1222
1224
  }
@@ -1240,8 +1242,90 @@ function parseExpiresAt(s, expiresInSeconds) {
1240
1242
  }
1241
1243
  return Date.now() + 30 * 24 * 60 * 60 * 1e3;
1242
1244
  }
1245
+ async function confirmCnEndpoint(callbacks, mode) {
1246
+ if (mode !== "cn") return;
1247
+ const current = getQoderCNEndpoints();
1248
+ const inherited = readInheritedVpcEndpoint();
1249
+ const envNames = ["QODER_VPC_ENDPOINT", "QODERCN_VPC_ENDPOINT"].filter((name) => process.env[name]);
1250
+ const personalId = "personal";
1251
+ const enterpriseId = "enterprise";
1252
+ const inheritedId = "enterprise-inherited";
1253
+ const options = [
1254
+ { id: personalId, label: "Personal account \u2014 public gateway (qoder.com.cn)" },
1255
+ { id: enterpriseId, label: "Enterprise account \u2014 use a VPC endpoint" }
1256
+ ];
1257
+ if (inherited) {
1258
+ options.push({
1259
+ id: inheritedId,
1260
+ label: `Enterprise account \u2014 ${inherited.value} (detected: ${inherited.source})`
1261
+ });
1262
+ }
1263
+ let message = current.isDefault ? "Choose the Qoder CN endpoint to log in against (default: personal account)" : `Choose the Qoder CN endpoint to log in against (current: ${current.raw})`;
1264
+ if (envNames.length > 0) {
1265
+ message += `
1266
+ (current value comes from ${envNames.join(" / ")}; it still wins after a restart)`;
1267
+ }
1268
+ const choice = await askEndpointChoice(callbacks, message, options);
1269
+ if (choice === personalId) {
1270
+ setQoderCNEndpoint("");
1271
+ if (envNames.length > 0) {
1272
+ getProgress(callbacks)?.(
1273
+ `Personal gateway selected. However, ${envNames.join(" / ")} takes precedence and restores the enterprise endpoint after a restart; remove the variable, then log in again.`
1274
+ );
1275
+ }
1276
+ return;
1277
+ }
1278
+ if (choice === inheritedId && inherited) {
1279
+ setQoderCNEndpoint(inherited.value);
1280
+ return;
1281
+ }
1282
+ if (choice === enterpriseId) {
1283
+ const prompt = getPrompt(callbacks);
1284
+ const raw = await prompt({
1285
+ message: "Enterprise VPC endpoint (instance name or domain)",
1286
+ placeholder: "acme",
1287
+ allowEmpty: true
1288
+ });
1289
+ if (getSignal(callbacks)?.aborted) throw new Error("Login cancelled");
1290
+ const value = raw?.trim();
1291
+ if (!value) return;
1292
+ setQoderCNEndpoint(value);
1293
+ return;
1294
+ }
1295
+ }
1296
+ async function askEndpointChoice(callbacks, message, options) {
1297
+ const select = getSelect(callbacks);
1298
+ if (select) {
1299
+ try {
1300
+ const picked = await select({ message, options });
1301
+ if (picked) return picked;
1302
+ return void 0;
1303
+ } catch (error) {
1304
+ if (isLoginCancellation(error, callbacks)) throw error;
1305
+ }
1306
+ }
1307
+ const prompt = getPrompt(callbacks);
1308
+ const lines = options.map((option, index) => `${index + 1}. ${option.label}`);
1309
+ const raw = await prompt({
1310
+ message: `${message}
1311
+ ${lines.join("\n")}
1312
+ Enter a number (Enter = 1, personal account)`,
1313
+ placeholder: "1",
1314
+ allowEmpty: true
1315
+ });
1316
+ if (getSignal(callbacks)?.aborted) throw new Error("Login cancelled");
1317
+ const trimmed = raw?.trim();
1318
+ if (!trimmed) return options[0]?.id;
1319
+ const byIndex = Number.parseInt(trimmed, 10);
1320
+ if (!Number.isNaN(byIndex) && byIndex >= 1 && byIndex <= options.length) {
1321
+ return options[byIndex - 1]?.id;
1322
+ }
1323
+ const byId = options.find((option) => option.id === trimmed || option.label === trimmed);
1324
+ return byId?.id ?? options[0]?.id;
1325
+ }
1243
1326
  async function interactiveLogin(callbacks, mode) {
1244
1327
  const region = getQoderRegionConfig(mode);
1328
+ await confirmCnEndpoint(callbacks, mode);
1245
1329
  const prompt = getPrompt(callbacks);
1246
1330
  const pat = await prompt({
1247
1331
  message: !region.supportsBrowserLogin ? "Paste a Qoder CN Personal Access Token, or leave empty to cancel" : "Paste a Qoder Personal Access Token (pt-...), or leave empty for browser login",
@@ -1377,6 +1461,7 @@ var init_login = __esm({
1377
1461
  "use strict";
1378
1462
  init_cosy();
1379
1463
  init_region();
1464
+ init_vpc();
1380
1465
  init_pat();
1381
1466
  }
1382
1467
  });
@@ -1685,10 +1770,13 @@ function describeEndpoint() {
1685
1770
  async function runEndpointCommand(args, ctx) {
1686
1771
  const input = (args || "").trim();
1687
1772
  if (!input) {
1773
+ const inherited = readInheritedVpcEndpoint();
1774
+ const inheritedHint = inherited ? `
1775
+ Detected (not applied): ${inherited.value} from ${inherited.source} \u2014 run /qoder-endpoint ${inherited.value} to use it.` : "";
1688
1776
  ctx?.ui?.notify(
1689
1777
  `Current Qoder CN endpoint: ${describeEndpoint()}
1690
1778
  To set: /qoder-endpoint <domain>
1691
- To reset: /qoder-endpoint default`,
1779
+ To reset: /qoder-endpoint default${inheritedHint}`,
1692
1780
  "info"
1693
1781
  );
1694
1782
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-qoder-cn",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Pi extension for Qoder CN — PAT/OAuth auth, /qoder-cn.usage quota command, and enterprise VPC endpoint support",
5
5
  "type": "module",
6
6
  "license": "MIT",