pleumrouter 0.5.13 → 0.5.14
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/package.json +1 -1
- package/src/config.mjs +0 -18
- package/src/run.mjs +15 -10
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -105,24 +105,6 @@ export async function requireAuth() {
|
|
|
105
105
|
return refreshed;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
/** @deprecated use requireAuth — kept as alias for gradual call-site migration */
|
|
109
|
-
export function requireKey() {
|
|
110
|
-
// sync callers still exist; they must switch to await requireAuth().
|
|
111
|
-
// For sync contexts we only check presence and return access_token (no refresh).
|
|
112
|
-
const cfg = readConfig();
|
|
113
|
-
if (cfg?.key && !cfg?.access_token) {
|
|
114
|
-
console.error(
|
|
115
|
-
t("Legacy API key login is no longer supported. Run:\n pleum login"),
|
|
116
|
-
);
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
if (!cfg?.access_token) {
|
|
120
|
-
console.error(t("Login required. Run this first:\n pleum login"));
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
return cfg.access_token;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
108
|
export async function refreshAccessToken(cfg = readConfig()) {
|
|
127
109
|
if (!cfg?.refresh_token) return null;
|
|
128
110
|
const base = (cfg.base_url || ROOT).replace(/\/+$/, "").replace(/\/v1$/, "");
|
package/src/run.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, lstatSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { requireAuth, authFetch, HISTORY_FILE, SESSIONS_DIR } from "./config.mjs";
|
|
5
5
|
import { fetchModels, formatTable, matchModels, formatModelLabel, safeModelText } from "./models.mjs";
|
|
6
6
|
import { style, divider, sanitizeTerminalText } from "./ansi.mjs";
|
|
7
7
|
import { createMarkdownStream } from "./markdown.mjs";
|
|
@@ -200,7 +200,8 @@ export async function run(rest) {
|
|
|
200
200
|
let model = argModel;
|
|
201
201
|
const piped = await readStdin();
|
|
202
202
|
const oneShot = Boolean(inlinePrompt || piped || attachments.length);
|
|
203
|
-
|
|
203
|
+
// 부팅 시점 1회 로그인 게이트. access_token은 각 요청이 authFetch에서 refresh한다.
|
|
204
|
+
await requireAuth();
|
|
204
205
|
const callOpts = { tags, skipCache, reasoningEffort, serviceTier, reasoningMode };
|
|
205
206
|
|
|
206
207
|
if (!model && !oneShot) {
|
|
@@ -226,7 +227,7 @@ export async function run(rest) {
|
|
|
226
227
|
console.error(`ℹ ${t("Attaching and uploading {n} explicit local image(s).", { n })}`);
|
|
227
228
|
}
|
|
228
229
|
const messages = [{ role: "user", content }];
|
|
229
|
-
await streamChat(
|
|
230
|
+
await streamChat(model, messages, { verbose, quiet: true, ...callOpts });
|
|
230
231
|
} catch (e) {
|
|
231
232
|
console.error(`${style.red("✗")} ${sanitizeTerminalText(e.message)}`);
|
|
232
233
|
process.exit(1);
|
|
@@ -234,10 +235,10 @@ export async function run(rest) {
|
|
|
234
235
|
process.exit(0);
|
|
235
236
|
}
|
|
236
237
|
|
|
237
|
-
await repl(
|
|
238
|
+
await repl(model, verbose, callOpts);
|
|
238
239
|
}
|
|
239
240
|
|
|
240
|
-
async function repl(
|
|
241
|
+
async function repl(initialModel, initialVerbose, {
|
|
241
242
|
tags = [],
|
|
242
243
|
skipCache = false,
|
|
243
244
|
reasoningEffort = null,
|
|
@@ -263,7 +264,7 @@ async function repl(key, initialModel, initialVerbose, {
|
|
|
263
264
|
const req = system ? [{ role: "system", content: system }, ...messages] : messages;
|
|
264
265
|
const ac = new AbortController();
|
|
265
266
|
currentAbort = ac;
|
|
266
|
-
const reply = await streamChat(
|
|
267
|
+
const reply = await streamChat(model, req, {
|
|
267
268
|
verbose,
|
|
268
269
|
signal: ac.signal,
|
|
269
270
|
tags,
|
|
@@ -487,7 +488,7 @@ async function handleSlash(text, ctx) {
|
|
|
487
488
|
|
|
488
489
|
// SSE 스트리밍 채팅. fetchImpl 주입 가능(테스트용 — 네트워크 없이 SSE 파싱 검증).
|
|
489
490
|
// 비용은 마지막 SSE 청크의 cost.krw — 스트리밍 응답엔 X-Cost-Krw 헤더가 없다(백엔드 계약).
|
|
490
|
-
export async function streamChat(
|
|
491
|
+
export async function streamChat(model, messages, {
|
|
491
492
|
verbose = false,
|
|
492
493
|
quiet = false,
|
|
493
494
|
signal,
|
|
@@ -496,21 +497,25 @@ export async function streamChat(key, model, messages, {
|
|
|
496
497
|
reasoningEffort = null,
|
|
497
498
|
serviceTier = null,
|
|
498
499
|
reasoningMode = null,
|
|
499
|
-
fetchImpl =
|
|
500
|
+
fetchImpl = null,
|
|
500
501
|
} = {}) {
|
|
501
502
|
const t0 = Date.now();
|
|
502
503
|
const sp = spinner(t("{model} thinking…", { model: sanitizeTerminalText(model) }));
|
|
503
504
|
let res;
|
|
504
505
|
try {
|
|
505
506
|
// 게이트웨이 패리티(0095): X-Pleum-Skip-Cache 헤더 + bounded tags 본문.
|
|
506
|
-
|
|
507
|
+
// authFetch가 Bearer + 401→refresh→재시도를 소유한다. 긴 REPL 세션 도중
|
|
508
|
+
// access_token이 만료돼도 자동 갱신돼야 한다(2026-07-27 버그 수정).
|
|
509
|
+
const headers = { "Content-Type": "application/json" };
|
|
507
510
|
if (skipCache) headers["X-Pleum-Skip-Cache"] = "1";
|
|
508
511
|
const bodyObj = { model, messages, stream: true, stream_options: { include_usage: true } };
|
|
509
512
|
if (tags.length > 0) bodyObj.tags = tags.slice(0, 10);
|
|
510
513
|
if (reasoningEffort) bodyObj.reasoning_effort = reasoningEffort;
|
|
511
514
|
if (serviceTier) bodyObj.service_tier = serviceTier;
|
|
512
515
|
if (reasoningMode) bodyObj.reasoning_mode = reasoningMode;
|
|
513
|
-
|
|
516
|
+
// fetchImpl: 테스트용 네트워크 치환. 프로덕션은 authFetch(Bearer + 401 refresh).
|
|
517
|
+
const doFetch = fetchImpl || ((path, init) => authFetch(path, init));
|
|
518
|
+
res = await doFetch("/chat/completions", {
|
|
514
519
|
method: "POST",
|
|
515
520
|
headers,
|
|
516
521
|
body: JSON.stringify(bodyObj),
|