@klarkxy/dsh-model-picker 0.1.0 → 0.1.1

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/lib/client.js CHANGED
@@ -1076,6 +1076,281 @@ var EffortPreference = class {
1076
1076
  }
1077
1077
  };
1078
1078
  //#endregion
1079
+ //#region ../dsh-plugin-kit/src/log-shared.ts
1080
+ const PLUGIN_LOG_LEVELS = [
1081
+ "debug",
1082
+ "info",
1083
+ "warn",
1084
+ "error",
1085
+ "fault"
1086
+ ];
1087
+ const DEFAULT_PLUGIN_LOG_LEVEL = "info";
1088
+ const PLUGIN_LOG_MAX_MESSAGE_CHARS = 16e3;
1089
+ const SOURCE_RE = /^[a-z][a-z0-9-]{0,63}$/;
1090
+ const EVENT_RE = /^[a-z][a-z0-9._-]{0,127}$/;
1091
+ const CONTEXT_KEY_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,63}$/;
1092
+ const SECRET_KEY_RE = /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|bearer|authorization|cookie|set[_-]?cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|z_c0|d_c0)$/i;
1093
+ const CHAT_PAYLOAD_RE = /"role"\s*:\s*"(?:system|user|assistant|tool)"|"(?:messages|content|prompt|input_text|input_image|image_url)"\s*:\s*[\[{"']/i;
1094
+ const CREDENTIAL_BODY_RE = /-----BEGIN (?:RSA |EC |OPENSSH |PRIVATE )?PRIVATE KEY-----|"type"\s*:\s*"service_account"|credentials\.json|\.dsh[/\\](?:credentials|auth)/i;
1095
+ const AUTH_HEADER_RE = /\b(Authorization|Proxy-Authorization|Cookie|Set-Cookie)\s*:\s*[^\r\n]*/gi;
1096
+ const BEARER_RE = /\bBearer\s+[A-Za-z0-9._\-+=/]{8,}/g;
1097
+ const SECRET_FIELD_RE = /(["']?(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|bearer|authorization|cookie|set-cookie|nonce|credential|private[_-]?key|password|secret|app[_-]?secret|client[_-]?secret|password|z_c0|d_c0)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,&;}]+)/gi;
1098
+ const SECRET_QUERY_RE = /([?&](?:token|api[_-]?key|key|secret|password|bootstrap|code|sig|signature|credential|password)=)[^\s&#"']+/gi;
1099
+ const URL_CREDENTIAL_RE = /\b(https?:\/\/)[^\s/:@]+:[^\s/@]+@/gi;
1100
+ const BOOTSTRAP_RE = /\/bootstrap\/[^\s"'?&#]+/gi;
1101
+ const KNOWN_TOKEN_RE = /\b(?:sk-[A-Za-z0-9_-]{10,}|gh[pousr]_[A-Za-z0-9_]{16,}|github_pat_[A-Za-z0-9_]{20,}|tvly-[A-Za-z0-9_-]{10,}|AKIA[0-9A-Z]{16})\b/g;
1102
+ const PATH_RE = /(?:[A-Za-z]:\\|\\\\)[^\s"'`<>|]+|(?:\/(?:home|Users|tmp|var|root)\/|~\/)[^\s"'`<>|]+/g;
1103
+ function isPluginLogLevel(value) {
1104
+ return typeof value === "string" && PLUGIN_LOG_LEVELS.includes(value);
1105
+ }
1106
+ function pluginLogLevelEnabled(level, minimum) {
1107
+ return PLUGIN_LOG_LEVELS.indexOf(level) >= PLUGIN_LOG_LEVELS.indexOf(minimum);
1108
+ }
1109
+ function normalizePluginLogSource(raw) {
1110
+ const stripped = String(raw || "").trim().replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
1111
+ const bounded = (/^[a-z]/.test(stripped) ? stripped : `p-${stripped}`).slice(0, 64).replace(/-+$/g, "");
1112
+ return SOURCE_RE.test(bounded) ? bounded : "dsh-plugin";
1113
+ }
1114
+ function normalizePluginLogEvent(raw) {
1115
+ const cleaned = String(raw || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 128);
1116
+ return EVENT_RE.test(cleaned) ? cleaned : "plugin.event";
1117
+ }
1118
+ /**
1119
+ * Parse a level specification like `debug` or `dsh-zhihu=debug,dsh-dot:warn,info`.
1120
+ * Source-specific rules win over a bare fallback level.
1121
+ */
1122
+ function parsePluginLogLevel(spec, source, fallback = DEFAULT_PLUGIN_LOG_LEVEL) {
1123
+ if (!spec || typeof spec !== "string") return fallback;
1124
+ const normalizedSource = source ? normalizePluginLogSource(source) : void 0;
1125
+ let wildcard;
1126
+ for (const token of spec.split(",").map((part) => part.trim()).filter(Boolean)) {
1127
+ const lowered = token.toLowerCase();
1128
+ if (isPluginLogLevel(lowered)) {
1129
+ wildcard = lowered;
1130
+ continue;
1131
+ }
1132
+ const match = /^([^=:]+)[=:]([a-z]+)$/i.exec(token);
1133
+ if (!match) continue;
1134
+ const target = match[1].trim();
1135
+ const candidate = match[2].trim().toLowerCase();
1136
+ if (!isPluginLogLevel(candidate)) continue;
1137
+ if (target === "*") {
1138
+ wildcard = candidate;
1139
+ continue;
1140
+ }
1141
+ if (normalizedSource && normalizePluginLogSource(target) === normalizedSource) return candidate;
1142
+ }
1143
+ return wildcard ?? fallback;
1144
+ }
1145
+ function sanitizePluginLogText(text, maxChars = PLUGIN_LOG_MAX_MESSAGE_CHARS) {
1146
+ if (!text) return "";
1147
+ if (CREDENTIAL_BODY_RE.test(text)) return "[omitted credential material]";
1148
+ if (CHAT_PAYLOAD_RE.test(text)) return "[omitted chat payload]";
1149
+ return truncateLogText(text.replace(AUTH_HEADER_RE, "$1: [redacted]").replace(BEARER_RE, "Bearer [redacted]").replace(SECRET_FIELD_RE, (full, prefix) => {
1150
+ const value = full.slice(prefix.length);
1151
+ if (value.startsWith("\"")) return `${prefix}"[redacted]"`;
1152
+ if (value.startsWith("'")) return `${prefix}'[redacted]'`;
1153
+ return `${prefix}[redacted]`;
1154
+ }).replace(SECRET_QUERY_RE, "$1[redacted]").replace(URL_CREDENTIAL_RE, "$1[redacted]@").replace(BOOTSTRAP_RE, "/bootstrap/[redacted]").replace(KNOWN_TOKEN_RE, "[redacted]").replace(PATH_RE, "[path]"), maxChars);
1155
+ }
1156
+ function sanitizePluginLogContext(input) {
1157
+ if (!input) return void 0;
1158
+ const output = {};
1159
+ for (const [key, value] of Object.entries(input).slice(0, 32)) {
1160
+ if (value === void 0 || !CONTEXT_KEY_RE.test(key)) continue;
1161
+ if (typeof value === "number" && !Number.isFinite(value)) continue;
1162
+ if (SECRET_KEY_RE.test(key)) {
1163
+ output[key] = "[redacted]";
1164
+ continue;
1165
+ }
1166
+ output[key] = typeof value === "string" ? sanitizePluginLogText(value, 1e3) : value;
1167
+ }
1168
+ if (!Object.keys(output).length) return void 0;
1169
+ if (JSON.stringify(output).length <= 8e3) return output;
1170
+ return {
1171
+ omitted: true,
1172
+ reason: "Diagnostic context exceeded its size limit."
1173
+ };
1174
+ }
1175
+ function sanitizePluginLogError(error) {
1176
+ if (error === void 0) return void 0;
1177
+ const row = error && typeof error === "object" ? error : {};
1178
+ const rawMessage = typeof row.message === "string" ? row.message : String(error);
1179
+ const result = {
1180
+ name: typeof row.name === "string" && row.name.trim() ? sanitizePluginLogText(row.name.trim(), 100) : "Error",
1181
+ message: sanitizePluginLogText(rawMessage) || "Unknown error"
1182
+ };
1183
+ if (typeof row.stack === "string" && row.stack.trim()) result.stack = sanitizePluginLogText(row.stack, 24e3);
1184
+ if (typeof row.code === "string" && /^[A-Za-z0-9_./:-]{1,100}$/.test(row.code)) result.code = sanitizePluginLogText(row.code, 100);
1185
+ else if (typeof row.code === "number" && Number.isFinite(row.code)) result.code = String(row.code);
1186
+ return result;
1187
+ }
1188
+ function mergePluginLogContext(base, extra) {
1189
+ if (!base) return extra;
1190
+ if (!extra) return base;
1191
+ return {
1192
+ ...base,
1193
+ ...extra
1194
+ };
1195
+ }
1196
+ function buildPluginLogEntry(input, meta) {
1197
+ const entry = {
1198
+ version: 1,
1199
+ at: meta.at.toISOString(),
1200
+ level: isPluginLogLevel(input.level) ? input.level : "info",
1201
+ source: normalizePluginLogSource(meta.source),
1202
+ surface: meta.surface,
1203
+ ...meta.profile ? { profile: meta.profile } : {},
1204
+ event: normalizePluginLogEvent(input.event),
1205
+ message: sanitizePluginLogText(input.message || "No diagnostic message was provided.", PLUGIN_LOG_MAX_MESSAGE_CHARS),
1206
+ ...meta.pid !== void 0 ? { pid: meta.pid } : {}
1207
+ };
1208
+ const context = sanitizePluginLogContext(mergePluginLogContext(meta.correlation, input.context));
1209
+ if (context) entry.context = context;
1210
+ const error = sanitizePluginLogError(input.error);
1211
+ if (error) entry.error = error;
1212
+ return entry;
1213
+ }
1214
+ function createChildPluginLogger(parent, bound) {
1215
+ return {
1216
+ get source() {
1217
+ return parent.source;
1218
+ },
1219
+ get file() {
1220
+ return parent.file;
1221
+ },
1222
+ get lastWriteError() {
1223
+ return parent.lastWriteError;
1224
+ },
1225
+ debug: (event, message, context) => parent.debug(event, message, mergePluginLogContext(bound, context)),
1226
+ info: (event, message, context) => parent.info(event, message, mergePluginLogContext(bound, context)),
1227
+ warn: (event, message, error, context) => parent.warn(event, message, error, mergePluginLogContext(bound, context)),
1228
+ error: (event, message, error, context) => parent.error(event, message, error, mergePluginLogContext(bound, context)),
1229
+ fault: (event, message, error, context) => parent.fault(event, message, error, mergePluginLogContext(bound, context)),
1230
+ write: (input) => parent.write({
1231
+ ...input,
1232
+ context: mergePluginLogContext(bound, input.context)
1233
+ }),
1234
+ child: (next) => createChildPluginLogger(parent, mergePluginLogContext(bound, next) ?? {})
1235
+ };
1236
+ }
1237
+ function truncateLogText(text, maxChars) {
1238
+ if (text.length <= maxChars) return text;
1239
+ const marker = "[truncated]\n";
1240
+ if (maxChars <= 12) return text.slice(0, Math.max(0, maxChars));
1241
+ return `${marker}${text.slice(-(maxChars - 12))}`;
1242
+ }
1243
+ //#endregion
1244
+ //#region ../dsh-plugin-kit/src/client-logger.ts
1245
+ const CLIENT_PLUGIN_LOG_GLOBAL = "__KLARKXY_PLUGIN_LOGS__";
1246
+ function readStorageFlag(key) {
1247
+ try {
1248
+ const value = (globalThis.window?.localStorage)?.getItem?.(key);
1249
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1250
+ } catch {
1251
+ return;
1252
+ }
1253
+ }
1254
+ function sharedClientBuffer(limit) {
1255
+ const carrier = globalThis;
1256
+ if (!Array.isArray(carrier["__KLARKXY_PLUGIN_LOGS__"])) try {
1257
+ carrier[CLIENT_PLUGIN_LOG_GLOBAL] = [];
1258
+ } catch {
1259
+ return [];
1260
+ }
1261
+ const list = carrier[CLIENT_PLUGIN_LOG_GLOBAL];
1262
+ const overflow = list.length - limit;
1263
+ if (overflow > 0) list.splice(0, overflow);
1264
+ return list;
1265
+ }
1266
+ function createClientPluginLogger(source, ctx, options = {}) {
1267
+ const normalized = normalizePluginLogSource(source);
1268
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
1269
+ const limit = Math.max(10, options.bufferLimit ?? 500);
1270
+ const browserCtx = ctx && typeof ctx === "object" ? ctx : void 0;
1271
+ const resolveMinimum = () => {
1272
+ if (options.minimumLevel) return options.minimumLevel;
1273
+ return parsePluginLogLevel(readStorageFlag("dsh-plugin-log-level"), normalized, DEFAULT_PLUGIN_LOG_LEVEL);
1274
+ };
1275
+ const logger = {
1276
+ source: normalized,
1277
+ file: null,
1278
+ lastWriteError: null,
1279
+ debug: (event, message, context) => logger.write({
1280
+ level: "debug",
1281
+ event,
1282
+ message,
1283
+ ...context ? { context } : {}
1284
+ }),
1285
+ info: (event, message, context) => logger.write({
1286
+ level: "info",
1287
+ event,
1288
+ message,
1289
+ ...context ? { context } : {}
1290
+ }),
1291
+ warn: (event, message, error, context) => logger.write({
1292
+ level: "warn",
1293
+ event,
1294
+ message,
1295
+ ...error !== void 0 ? { error } : {},
1296
+ ...context ? { context } : {}
1297
+ }),
1298
+ error: (event, message, error, context) => logger.write({
1299
+ level: "error",
1300
+ event,
1301
+ message,
1302
+ ...error !== void 0 ? { error } : {},
1303
+ ...context ? { context } : {}
1304
+ }),
1305
+ fault: (event, message, error, context) => logger.write({
1306
+ level: "fault",
1307
+ event,
1308
+ message,
1309
+ ...error !== void 0 ? { error } : {},
1310
+ ...context ? { context } : {}
1311
+ }),
1312
+ child: (context) => createChildPluginLogger(logger, context),
1313
+ write(input) {
1314
+ try {
1315
+ if (!pluginLogLevelEnabled(input.level, resolveMinimum())) return false;
1316
+ const entry = buildPluginLogEntry(input, {
1317
+ at: now(),
1318
+ source: normalized,
1319
+ surface: "client"
1320
+ });
1321
+ const buffer = sharedClientBuffer(limit);
1322
+ buffer.push(entry);
1323
+ const overflow = buffer.length - limit;
1324
+ if (overflow > 0) buffer.splice(0, overflow);
1325
+ if (options.forwardToHostLogger !== false && browserCtx?.logger) {
1326
+ const summary = sanitizePluginLogText(`${normalized} [${entry.event}]: ${entry.message}`, 500);
1327
+ if (entry.level === "warn" && typeof browserCtx.logger.warn === "function") browserCtx.logger.warn(summary);
1328
+ else if ((entry.level === "error" || entry.level === "fault") && typeof browserCtx.logger.error === "function") browserCtx.logger.error(summary);
1329
+ }
1330
+ if (readStorageFlag("dsh-plugin-log-console") === "1") {
1331
+ const con = globalThis.console;
1332
+ (entry.level === "debug" ? con?.debug : entry.level === "info" ? con?.info : entry.level === "warn" ? con?.warn : con?.error)?.call(con, `[${normalized}] ${entry.event}: ${entry.message}`, entry);
1333
+ }
1334
+ return true;
1335
+ } catch {
1336
+ return false;
1337
+ }
1338
+ }
1339
+ };
1340
+ return logger;
1341
+ }
1342
+ function installClientPluginLogger(ctx, source, options = {}) {
1343
+ const logger = createClientPluginLogger(source, ctx, options);
1344
+ logger.info("client.loaded", "Client plugin initialized.", options.loadedContext);
1345
+ const browserCtx = ctx && typeof ctx === "object" ? ctx : void 0;
1346
+ if (typeof browserCtx?.effect === "function") try {
1347
+ browserCtx.effect(() => () => {
1348
+ logger.debug("client.disposed", "Client plugin disposed.");
1349
+ }, `${logger.source}.client-logger`);
1350
+ } catch {}
1351
+ return logger;
1352
+ }
1353
+ //#endregion
1079
1354
  //#region src/client.tsx
1080
1355
  /**
1081
1356
  * Model picker, browser half.
@@ -1627,7 +1902,7 @@ function ModelMenuFallback(props) {
1627
1902
  * follow the default. Memory updates strictly on `result.ok === true`; a
1628
1903
  * failure leaves the remembered route untouched. Exported for the wiring spec.
1629
1904
  */
1630
- function createPreferredSelect(directory, preference) {
1905
+ function createPreferredSelect(directory, preference, logger) {
1631
1906
  return async (selection) => {
1632
1907
  const state = directory.store.getSnapshot();
1633
1908
  const current = state.current;
@@ -1642,11 +1917,22 @@ function createPreferredSelect(directory, preference) {
1642
1917
  };
1643
1918
  }
1644
1919
  const result = await directory.select(final);
1645
- if (result?.ok === true) preference.remember(final, final.reasoningEffort);
1920
+ if (result?.ok === true) {
1921
+ preference.remember(final, final.reasoningEffort);
1922
+ logger?.debug("model.selected", "Applied model selection.", {
1923
+ provider: final.provider,
1924
+ model: final.model,
1925
+ effort: final.reasoningEffort ?? null
1926
+ });
1927
+ } else if (result && result.ok === false) logger?.warn("model.select-rejected", "Model selection failed.", void 0, {
1928
+ provider: final.provider,
1929
+ model: final.model
1930
+ });
1646
1931
  return result;
1647
1932
  };
1648
1933
  }
1649
1934
  function apply(ctx) {
1935
+ const logger = installClientPluginLogger(ctx, "@klarkxy/dsh-model-picker");
1650
1936
  const preference = new EffortPreference();
1651
1937
  ctx.effect(() => {
1652
1938
  const zh = ctx.locale?.register(NS, "zh", dictZh);
@@ -1689,12 +1975,15 @@ function apply(ctx) {
1689
1975
  available: true,
1690
1976
  directory: directory.store,
1691
1977
  load: () => {
1692
- directory.load().catch(() => {});
1978
+ directory.load().catch((error) => {
1979
+ logger.warn("directory.load-failed", "Model directory load failed.", error, { sessionId });
1980
+ });
1693
1981
  },
1694
1982
  resolveText: (text) => ctx.locale?.resolveText(text) ?? String(text),
1695
- select: createPreferredSelect(directory, preference)
1983
+ select: createPreferredSelect(directory, preference, logger)
1696
1984
  };
1697
- } catch {
1985
+ } catch (error) {
1986
+ logger.warn("directory.unavailable", "Could not resolve model directory for session.", error, { sessionId });
1698
1987
  return unavailable;
1699
1988
  }
1700
1989
  }
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Context } from "@deepseek-ai/cordis";
1
2
  //#region src/index.d.ts
2
3
  /**
3
4
  * Model picker, node half. Pure UI plugin: the empty apply exists so the
@@ -6,7 +7,7 @@
6
7
  * declaration.
7
8
  */
8
9
  /** Host plugin body — no host-side behavior for this surface plugin. */
9
- declare function apply(): void;
10
+ declare function apply(ctx?: Context): void;
10
11
  //#endregion
11
12
  export { apply };
12
13
  //# sourceMappingURL=index.d.ts.map