@monotykamary/pi-better-openai 0.1.23

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.
@@ -0,0 +1,112 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { ResolvedConfig, SupportedModel } from "./config.ts";
3
+ import { isRecord } from "./config.ts";
4
+
5
+ export function currentModelKey(ctx: ExtensionContext): string {
6
+ return ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "none";
7
+ }
8
+
9
+ export function supportsFast(ctx: ExtensionContext, supportedModels: SupportedModel[]): boolean {
10
+ const current = ctx.model;
11
+ if (!current) return false;
12
+ return supportedModels.some(
13
+ (model) => model.provider === current.provider && model.id === current.id,
14
+ );
15
+ }
16
+
17
+ export function modelList(supportedModels: SupportedModel[]): string {
18
+ return supportedModels.length > 0
19
+ ? supportedModels.map((model) => `${model.provider}/${model.id}`).join(", ")
20
+ : "none configured";
21
+ }
22
+
23
+ export function fastStateText(
24
+ ctx: ExtensionContext,
25
+ desiredActive: boolean,
26
+ active: boolean,
27
+ supportedModels: SupportedModel[],
28
+ ): string {
29
+ const model = currentModelKey(ctx);
30
+ if (active) return `Fast mode is on for ${model}.`;
31
+ if (desiredActive) {
32
+ return `Fast mode is requested, but inactive for unsupported model ${model}. Supported models: ${modelList(supportedModels)}.`;
33
+ }
34
+ return `Fast mode is off. Current model: ${model}.`;
35
+ }
36
+
37
+ export class FastController {
38
+ desiredActive = false;
39
+ active = false;
40
+ private lastInjectedAt: number | undefined;
41
+ private lastInjectedModel: string | undefined;
42
+ private lastInjectedTier: string | undefined;
43
+ private readonly serviceTier: string;
44
+
45
+ constructor(serviceTier: string) {
46
+ this.serviceTier = serviceTier;
47
+ }
48
+
49
+ applyDesiredState(ctx: ExtensionContext, cfg: ResolvedConfig): void {
50
+ this.active = this.desiredActive && supportsFast(ctx, cfg.supportedModels);
51
+ }
52
+
53
+ initializeForSession(ctx: ExtensionContext, cfg: ResolvedConfig, flagActive: boolean): void {
54
+ this.desiredActive = cfg.persistState ? cfg.desiredActive : false;
55
+ if (flagActive) this.desiredActive = true;
56
+ this.applyDesiredState(ctx, cfg);
57
+ }
58
+
59
+ setDesired(ctx: ExtensionContext, cfg: ResolvedConfig, next: boolean): void {
60
+ this.desiredActive = next;
61
+ this.applyDesiredState(ctx, cfg);
62
+ }
63
+
64
+ stateText(ctx: ExtensionContext, cfg: ResolvedConfig): string {
65
+ return fastStateText(ctx, this.desiredActive, this.active, cfg.supportedModels);
66
+ }
67
+
68
+ unsupportedRequestMessage(ctx: ExtensionContext, cfg: ResolvedConfig): string {
69
+ return `Fast mode requested, but ${currentModelKey(ctx)} is unsupported. It will activate automatically when you switch to a supported model: ${modelList(cfg.supportedModels)}.`;
70
+ }
71
+
72
+ inactiveForModelMessage(ctx: ExtensionContext): string {
73
+ return `Fast mode inactive for unsupported model ${currentModelKey(ctx)}.`;
74
+ }
75
+
76
+ settingsSummary(ctx: ExtensionContext, cfg: ResolvedConfig): string {
77
+ if (this.active) return "on";
78
+ if (this.desiredActive)
79
+ return supportsFast(ctx, cfg.supportedModels) ? "requested" : "requested inactive";
80
+ return "off";
81
+ }
82
+
83
+ statusSegment(ctx: ExtensionContext, cfg: ResolvedConfig): string | undefined {
84
+ return this.active && supportsFast(ctx, cfg.supportedModels)
85
+ ? `${ctx.model?.id ?? "model"} fast`
86
+ : undefined;
87
+ }
88
+
89
+ injectProviderPayload(
90
+ event: { payload?: unknown },
91
+ ctx: ExtensionContext,
92
+ cfg: ResolvedConfig,
93
+ ): unknown {
94
+ if (!this.active || !supportsFast(ctx, cfg.supportedModels) || !isRecord(event.payload))
95
+ return undefined;
96
+ this.lastInjectedAt = Date.now();
97
+ this.lastInjectedModel = currentModelKey(ctx);
98
+ this.lastInjectedTier = this.serviceTier;
99
+ return { ...event.payload, service_tier: this.serviceTier };
100
+ }
101
+
102
+ debugLines(ctx: ExtensionContext, cfg: ResolvedConfig): string[] {
103
+ return [
104
+ `Fast desired: ${this.desiredActive}`,
105
+ `Fast active: ${this.active}`,
106
+ `Current model: ${currentModelKey(ctx)}`,
107
+ `Supported model: ${supportsFast(ctx, cfg.supportedModels)}`,
108
+ `Configured service_tier: ${this.serviceTier}`,
109
+ `Last injected: ${this.lastInjectedAt ? `${new Date(this.lastInjectedAt).toLocaleTimeString()} (${this.lastInjectedModel}, ${this.lastInjectedTier})` : "never"}`,
110
+ ];
111
+ }
112
+ }
@@ -0,0 +1,97 @@
1
+ import { sep } from "node:path";
2
+ import type { PetPlacement } from "./config.ts";
3
+ import { truncateToWidth, visibleWidth } from "./format.ts";
4
+
5
+ export function abbreviateHomePath(
6
+ path: string,
7
+ home = process.env.HOME || process.env.USERPROFILE,
8
+ ): string {
9
+ if (!home) return path;
10
+ if (path === home) return "~";
11
+ const homePrefix = home.endsWith(sep) ? home : `${home}${sep}`;
12
+ return path.startsWith(homePrefix) ? `~/${path.slice(homePrefix.length)}` : path;
13
+ }
14
+
15
+ export function isInlinePetPlacement(placement: PetPlacement): boolean {
16
+ return placement === "inline-left" || placement === "inline-right" || placement === "badge";
17
+ }
18
+
19
+ export function petSizeCellsForPlacement(placement: PetPlacement, sizeCells: number): number {
20
+ return placement === "badge" ? Math.min(6, sizeCells) : sizeCells;
21
+ }
22
+
23
+ function spaces(width: number): string {
24
+ return " ".repeat(Math.max(0, width));
25
+ }
26
+
27
+ function padTextToWidth(value: string, width: number): string {
28
+ return value + spaces(width - visibleWidth(value));
29
+ }
30
+
31
+ export function isTerminalImageLine(line: string): boolean {
32
+ return line.includes("\x1b_G") || line.includes("\x1b]1337;File=");
33
+ }
34
+
35
+ function petLineCell(line: string, width: number): string {
36
+ if (!line) return spaces(width);
37
+ if (isTerminalImageLine(line)) return `\x1b[0m${line}`;
38
+ const clipped = truncateToWidth(line, width, "");
39
+ return clipped + spaces(width - visibleWidth(clipped));
40
+ }
41
+
42
+ function stripLeadingCursorUp(line: string): string {
43
+ if (!line.startsWith("\x1b[")) return line;
44
+ const end = line.indexOf("A", 2);
45
+ if (end === -1) return line;
46
+ return /^\d+$/.test(line.slice(2, end)) ? line.slice(end + 1) : line;
47
+ }
48
+
49
+ function stripTrailingCursorDown(line: string): string {
50
+ if (!line.endsWith("B")) return line;
51
+ const start = line.lastIndexOf("\x1b[");
52
+ if (start === -1) return line;
53
+ return /^\d+$/.test(line.slice(start + 2, -1)) ? line.slice(0, start) : line;
54
+ }
55
+
56
+ function terminalImageInlineLeftSequence(line: string, totalRows: number): string {
57
+ const moveUp = totalRows > 1 ? `\x1b[${totalRows - 1}A` : "";
58
+ const moveDown = totalRows > 1 ? `\x1b[${totalRows - 1}B` : "";
59
+ const balancedLine = stripTrailingCursorDown(stripLeadingCursorUp(line));
60
+ return `\x1b[0m\r${moveUp}${balancedLine}${moveDown}`;
61
+ }
62
+
63
+ export function combineInlinePetFooter(
64
+ petLines: string[],
65
+ textLines: string[],
66
+ width: number,
67
+ placement: PetPlacement,
68
+ petWidth: number,
69
+ ): string[] {
70
+ const gap = 2;
71
+ const textWidth = Math.max(1, width - petWidth - gap);
72
+ const totalRows = Math.max(petLines.length, textLines.length);
73
+ const hasTerminalImageLine = petLines.some(isTerminalImageLine);
74
+ const leftImageLine =
75
+ placement === "inline-left" ? petLines.find(isTerminalImageLine) : undefined;
76
+ const renderPetOnRight =
77
+ placement === "inline-right" || (placement !== "inline-left" && hasTerminalImageLine);
78
+ const lines: string[] = [];
79
+
80
+ for (let row = 0; row < totalRows; row++) {
81
+ const petLine = petLines[row] ?? "";
82
+ const textLine = textLines[row] ?? "";
83
+ const textPart = truncateToWidth(textLine, textWidth, "...");
84
+ const petPart = leftImageLine ? spaces(petWidth) : petLineCell(petLine, petWidth);
85
+ if (renderPetOnRight) {
86
+ lines.push(`${padTextToWidth(textPart, textWidth)}${spaces(gap)}${petPart}`);
87
+ } else {
88
+ lines.push(`${petPart}${spaces(gap)}${textPart}`);
89
+ }
90
+ }
91
+
92
+ if (leftImageLine && lines.length > 0) {
93
+ lines[lines.length - 1] += terminalImageInlineLeftSequence(leftImageLine, totalRows);
94
+ }
95
+
96
+ return lines;
97
+ }
package/src/format.ts ADDED
@@ -0,0 +1,104 @@
1
+ import {
2
+ truncateToWidth as truncateTerminalText,
3
+ visibleWidth as terminalVisibleWidth,
4
+ } from "@earendil-works/pi-tui";
5
+
6
+ const ANSI_ESCAPE_PATTERN = String.raw`\u001B\[[0-?]*[ -/]*[@-~]`;
7
+ const ANSI_ESCAPE_REGEXP = new RegExp(ANSI_ESCAPE_PATTERN, "g");
8
+ const DIAGNOSTIC_MAX_LENGTH = 500;
9
+ const REDACTED = "[REDACTED]";
10
+ const SENSITIVE_DIAGNOSTIC_KEY_SUFFIXES = [
11
+ "token",
12
+ "apikey",
13
+ "accesskey",
14
+ "authorization",
15
+ "password",
16
+ "passwd",
17
+ "secret",
18
+ "privatekey",
19
+ "credential",
20
+ "credentials",
21
+ "accountid",
22
+ ] as const;
23
+
24
+ function replaceControlCharacters(value: string): string {
25
+ let result = "";
26
+ for (const char of value) {
27
+ const code = char.charCodeAt(0);
28
+ result += code <= 31 || (code >= 127 && code <= 159) ? " " : char;
29
+ }
30
+ return result;
31
+ }
32
+
33
+ export function stripAnsi(value: string): string {
34
+ return value.replace(ANSI_ESCAPE_REGEXP, "");
35
+ }
36
+
37
+ export function visibleWidth(value: string): number {
38
+ return terminalVisibleWidth(value);
39
+ }
40
+
41
+ export function truncateToWidth(value: string, width: number, ellipsis = "..."): string {
42
+ return truncateTerminalText(value, width, ellipsis);
43
+ }
44
+
45
+ export function formatTokens(count: number): string {
46
+ if (count < 1000) return count.toString();
47
+ if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
48
+ if (count < 1000000) return `${Math.round(count / 1000)}k`;
49
+ if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;
50
+ return `${Math.round(count / 1000000)}M`;
51
+ }
52
+
53
+ export function sanitizeStatusText(text: string): string {
54
+ return text.replace(/[ \r\n\t]+/g, " ").trim();
55
+ }
56
+
57
+ export function maskIdentifier(value: string | undefined): string | undefined {
58
+ const trimmed = value?.trim();
59
+ if (!trimmed) return undefined;
60
+ if (trimmed.length <= 8) return "found";
61
+ return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
62
+ }
63
+
64
+ export function sanitizeDiagnosticError(
65
+ message: string,
66
+ maxLength = DIAGNOSTIC_MAX_LENGTH,
67
+ ): string {
68
+ const redactedSecrets = stripAnsi(message)
69
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]")
70
+ .replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "sk-[REDACTED]")
71
+ .replace(/\bacct_[A-Za-z0-9_-]{6,}\b/g, "acct_[REDACTED]")
72
+ .replace(
73
+ /(["']?(?:access|access_token|token|api[_-]?key|authorization|accountId|account_id)["']?\s*[:=]\s*["']?)([^"',\s}\]]+)/gi,
74
+ "$1[REDACTED]",
75
+ )
76
+ .replace(/\[REDACTED\](?:\])+/g, "[REDACTED]");
77
+ const redacted = replaceControlCharacters(redactedSecrets).replace(/ +/g, " ").trim();
78
+ const sanitized = redacted || "Unknown error.";
79
+ if (sanitized.length <= maxLength) return sanitized;
80
+ return `${sanitized.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
81
+ }
82
+
83
+ function isSensitiveDiagnosticKey(key: string): boolean {
84
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
85
+ return (
86
+ normalized === "access" ||
87
+ normalized === "auth" ||
88
+ normalized === "refresh" ||
89
+ SENSITIVE_DIAGNOSTIC_KEY_SUFFIXES.some((suffix) => normalized.endsWith(suffix))
90
+ );
91
+ }
92
+
93
+ export function redactDiagnosticValue(value: unknown): unknown {
94
+ if (typeof value === "string") return sanitizeDiagnosticError(value);
95
+ if (Array.isArray(value)) return value.map(redactDiagnosticValue);
96
+ if (typeof value !== "object" || value === null) return value;
97
+
98
+ return Object.fromEntries(
99
+ Object.entries(value).map(([key, entry]) => [
100
+ key,
101
+ isSensitiveDiagnosticKey(key) ? REDACTED : redactDiagnosticValue(entry),
102
+ ]),
103
+ );
104
+ }
@@ -0,0 +1,7 @@
1
+ export const EXTENSION_NAME = "pi-better-openai";
2
+ export const CONFIG_BASENAME = "pi-better-openai.json";
3
+ export const STATUS_KEY = "better-openai";
4
+
5
+ export function logPrefix(): string {
6
+ return `[${EXTENSION_NAME}]`;
7
+ }