@psnext/slingcli 2.5.20260602-2 → 2.5.20260602-4

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/bin/sling.js CHANGED
@@ -128,6 +128,47 @@ function stripUpdateRequestFlags(argv) {
128
128
  return argv.filter((arg) => arg !== "-y" && arg !== "--yes");
129
129
  }
130
130
 
131
+ async function fetchLatestNpmVersion() {
132
+ try {
133
+ const res = await fetch("https://registry.npmjs.org/@psnext/slingcli/latest", {
134
+ signal: AbortSignal.timeout(10000),
135
+ headers: { accept: "application/json" },
136
+ });
137
+ if (!res.ok) return null;
138
+ const data = await res.json();
139
+ return typeof data?.version === "string" ? data.version : null;
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ async function fetchRemoteAppConfig() {
146
+ try {
147
+ const res = await fetch(
148
+ "https://raw.githubusercontent.com/psnext/slingcli/main/sling-app-config.json",
149
+ {
150
+ signal: AbortSignal.timeout(5000),
151
+ headers: { accept: "application/json" },
152
+ },
153
+ );
154
+ if (!res.ok) return null;
155
+ const data = await res.json();
156
+ if (!data || typeof data !== "object") return null;
157
+
158
+ const minVersion = typeof data.minVersion === "string" ? data.minVersion : null;
159
+ const updateMessage = typeof data.updateMessage === "string" ? data.updateMessage : null;
160
+
161
+ if (!minVersion || !semver.valid(minVersion)) return null;
162
+
163
+ return {
164
+ minVersion,
165
+ updateMessage,
166
+ };
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+
131
172
  function stripSlingWrapperFlags(argv) {
132
173
  // Wrapper-only flags. They should not be forwarded to pi-coding-agent,
133
174
  // which does not recognize them.
@@ -246,27 +287,27 @@ async function checkForUpdate(args) {
246
287
 
247
288
  const autoYes = hasUpdateRequest(args);
248
289
 
249
- if (!autoYes && (!process.stdin.isTTY || !process.stdout.isTTY)) return;
250
-
251
- let latestVersion;
252
- try {
253
- const res = await fetch("https://registry.npmjs.org/@psnext/slingcli/latest", {
254
- signal: AbortSignal.timeout(10000),
255
- headers: { accept: "application/json" },
256
- });
257
- if (!res.ok) return;
258
- const data = await res.json();
259
- latestVersion = data.version;
260
- } catch {
261
- return;
262
- }
290
+ const [latestVersion, remoteConfig] = await Promise.all([
291
+ fetchLatestNpmVersion(),
292
+ fetchRemoteAppConfig(),
293
+ ]);
263
294
 
264
295
  if (!latestVersion || !semver.gt(latestVersion, SLING_VERSION)) return;
265
296
 
266
- console.log(`A new version ${latestVersion} is available (current ${SLING_VERSION}).`);
297
+ // Gate 2: force if current < minVersion (user is below the minimum required)
298
+ const shouldForceUpdate = remoteConfig && semver.lt(SLING_VERSION, remoteConfig.minVersion);
299
+
300
+ if (!shouldForceUpdate && !autoYes && (!process.stdin.isTTY || !process.stdout.isTTY)) return;
301
+
302
+ if (shouldForceUpdate) {
303
+ if (remoteConfig?.updateMessage) console.log(remoteConfig.updateMessage);
304
+ console.log(`Required version ${latestVersion} detected. Updating now...`);
305
+ } else {
306
+ console.log(`A new version ${latestVersion} is available (current ${SLING_VERSION}).`);
307
+ }
267
308
 
268
- let shouldUpdate = autoYes;
269
- if (!autoYes) {
309
+ let shouldUpdate = autoYes || shouldForceUpdate;
310
+ if (!shouldUpdate) {
270
311
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
271
312
  let answer;
272
313
  try {
@@ -286,6 +327,7 @@ async function checkForUpdate(args) {
286
327
  console.error("✗ Auto-update failed. Run manually:");
287
328
  console.error(" npm install -g @psnext/slingcli@latest");
288
329
  console.error(" (you may need sudo)");
330
+ if (shouldForceUpdate) process.exit(1);
289
331
  return;
290
332
  }
291
333
 
@@ -9304,9 +9304,9 @@ export const MODELS = {
9304
9304
  reasoning: true,
9305
9305
  input: ["text"],
9306
9306
  cost: {
9307
- input: 0.3,
9308
- output: 2.5,
9309
- cacheRead: 0.06,
9307
+ input: 0.075,
9308
+ output: 0.625,
9309
+ cacheRead: 0.015,
9310
9310
  cacheWrite: 0,
9311
9311
  },
9312
9312
  contextWindow: 262144,
@@ -147,7 +147,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
147
147
  if (!apiKey) {
148
148
  throw new Error(`No API key for provider: ${model.provider}`);
149
149
  }
150
- const accountId = extractAccountId(apiKey);
150
+ const accountId = extractAccountId(apiKey) || "";
151
151
  let body = buildRequestBody(model, context, options);
152
152
  const nextBody = await options?.onPayload?.(body, model);
153
153
  if (nextBody !== undefined) {
@@ -319,7 +319,7 @@ function buildRequestBody(model, context, options) {
319
319
  stream: true,
320
320
  instructions: context.systemPrompt || "You are a helpful assistant.",
321
321
  input: messages,
322
- text: { verbosity: options?.textVerbosity || "low" },
322
+ text: { verbosity: options?.textVerbosity || "medium" },
323
323
  include: ["reasoning.encrypted_content"],
324
324
  prompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),
325
325
  tool_choice: "auto",
@@ -376,11 +376,7 @@ function resolveCodexServiceTier(responseServiceTier, requestServiceTier) {
376
376
  function resolveCodexUrl(baseUrl) {
377
377
  const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
378
378
  const normalized = raw.replace(/\/+$/, "");
379
- if (normalized.endsWith("/codex/responses"))
380
- return normalized;
381
- if (normalized.endsWith("/codex"))
382
- return `${normalized}/responses`;
383
- return `${normalized}/codex/responses`;
379
+ return `${normalized}/responses`;
384
380
  }
385
381
  function resolveCodexWebSocketUrl(baseUrl) {
386
382
  const url = new URL(resolveCodexUrl(baseUrl));
@@ -1117,15 +1113,13 @@ function extractAccountId(token) {
1117
1113
  try {
1118
1114
  const parts = token.split(".");
1119
1115
  if (parts.length !== 3)
1120
- throw new Error("Invalid token");
1116
+ return null;
1121
1117
  const payload = JSON.parse(atob(parts[1]));
1122
1118
  const accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;
1123
- if (!accountId)
1124
- throw new Error("No account ID in token");
1125
- return accountId;
1119
+ return accountId || null;
1126
1120
  }
1127
1121
  catch {
1128
- throw new Error("Failed to extract accountId from token");
1122
+ return null;
1129
1123
  }
1130
1124
  }
1131
1125
  function createCodexRequestId() {
@@ -1140,7 +1134,9 @@ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token)
1140
1134
  headers.set(key, value);
1141
1135
  }
1142
1136
  headers.set("Authorization", `Bearer ${token}`);
1143
- headers.set("chatgpt-account-id", accountId);
1137
+ if (accountId) {
1138
+ headers.set("chatgpt-account-id", accountId);
1139
+ }
1144
1140
  headers.set("originator", "pi");
1145
1141
  const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
1146
1142
  headers.set("User-Agent", userAgent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@psnext/slingcli",
3
- "version": "2.5.20260602-2",
3
+ "version": "2.5.20260602-4",
4
4
  "description": "Connects Sling CLI to Publicis Sapient Slingshot enterprise LLM gateway. Bundles the pi coding-agent runtime.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "type": "git",
30
30
  "url": "git+https://pscode.lioncloud.net/psaiproducts/slingcli.git"
31
31
  },
32
- "slingVersion": "2.5.20260602-2",
32
+ "slingVersion": "2.5.20260602-4",
33
33
  "dependencies": {
34
34
  "@earendil-works/pi-tui": "file:../.sling-pack/earendil-works-pi-tui-0.78.0.tgz",
35
35
  "@earendil-works/pi-ai": "file:../.sling-pack/earendil-works-pi-ai-0.78.0.tgz",