oira666_pi-limits-wait 0.4.11 → 0.4.13

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/src/settings.ts CHANGED
@@ -1,130 +1,130 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { homedir } from "os";
3
- import { join, resolve } from "path";
4
- import { getAgentDir, type ExtensionContext } from "@mariozechner/pi-coding-agent";
5
- import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
6
- import { FALLBACK_MODELS_KEY, SETTINGS_FILE_NAME } from "./constants.js";
7
- import { formatModel, modelKey } from "./models.js";
8
- import { state } from "./state.js";
9
- import type { ConfiguredModel } from "./types.js";
10
-
11
- function isThinkingLevel(value: unknown): value is ThinkingLevel {
12
- return ["off", "minimal", "low", "medium", "high", "xhigh"].includes(String(value));
13
- }
14
-
15
- export function parseConfiguredModels(raw: unknown): ConfiguredModel[] {
16
- const source = raw && typeof raw === "object" && Array.isArray((raw as { [FALLBACK_MODELS_KEY]?: unknown })[FALLBACK_MODELS_KEY])
17
- ? (raw as { [FALLBACK_MODELS_KEY]: unknown[] })[FALLBACK_MODELS_KEY]
18
- : [];
19
-
20
- const models: ConfiguredModel[] = [];
21
- for (const item of source) {
22
- if (!item || typeof item !== "object") continue;
23
- const record = item as Record<string, unknown>;
24
- const provider = typeof record.provider === "string" ? record.provider.trim() : "";
25
- const modelname =
26
- typeof record.modelname === "string" ? record.modelname.trim()
27
- : typeof record.modelName === "string" ? record.modelName.trim()
28
- : typeof record.model === "string" ? record.model.trim()
29
- : "";
30
- const reasoning = record["reasoning effort"] ?? record.reasoningEffort ?? record.reasoning_effort;
31
- if (!provider || !modelname) continue;
32
- models.push({ provider, modelname, reasoningEffort: isThinkingLevel(reasoning) ? reasoning : undefined });
33
- }
34
- return models;
35
- }
36
-
37
- export function configFileCandidates(cwd: string, agentDir = getAgentDir(), homeDir = homedir()): string[] {
38
- return [
39
- join(homeDir, ".config", ".pi", SETTINGS_FILE_NAME),
40
- join(agentDir, SETTINGS_FILE_NAME),
41
- resolve(cwd, `.${SETTINGS_FILE_NAME}`),
42
- resolve(cwd, ".pi", SETTINGS_FILE_NAME),
43
- ];
44
- }
45
-
46
- function mergeConfig(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {
47
- return { ...base, ...override };
48
- }
49
-
50
- export function readFallbackSettings(cwd: string, agentDir = getAgentDir(), homeDir = homedir()): { config: Record<string, unknown>; loadedPaths: string[]; warnings: string[] } {
51
- let config: Record<string, unknown> = {};
52
- const loadedPaths: string[] = [];
53
- const warnings: string[] = [];
54
- const seen = new Set<string>();
55
-
56
- for (const candidate of configFileCandidates(cwd, agentDir, homeDir)) {
57
- const path = resolve(candidate);
58
- if (seen.has(path)) continue;
59
- seen.add(path);
60
- if (!existsSync(path)) continue;
61
-
62
- try {
63
- const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
64
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
65
- warnings.push(`${path}: expected a JSON object`);
66
- continue;
67
- }
68
- config = mergeConfig(config, parsed as Record<string, unknown>);
69
- loadedPaths.push(path);
70
- } catch (err) {
71
- warnings.push(`${path}: ${err instanceof Error ? err.message : String(err)}`);
72
- }
73
- }
74
-
75
- return { config, loadedPaths, warnings };
76
- }
77
-
78
- export function __readFallbackSettingsForTests(cwd: string, agentDir: string, homeDir: string): { config: Record<string, unknown>; loadedPaths: string[]; warnings: string[] } {
79
- return readFallbackSettings(cwd, agentDir, homeDir);
80
- }
81
-
82
- export function loadFallbackSettings(ctx: ExtensionContext): void {
83
- state.fallbackModels = [];
84
-
85
- try {
86
- const { config, loadedPaths, warnings } = readFallbackSettings(ctx.cwd);
87
- const content = JSON.stringify({ config, loadedPaths, warnings });
88
- const shouldNotify = state.settingsSignature !== content;
89
- state.settingsSignature = content;
90
-
91
- const configured = parseConfiguredModels(config);
92
- if (configured.length === 0) {
93
- if (shouldNotify && warnings.length > 0) ctx.ui.notify(`Could not fully load ${SETTINGS_FILE_NAME}: ${warnings.join(", ")}`, "warning");
94
- return;
95
- }
96
-
97
- const modelWarnings: string[] = [];
98
- const seen = new Set<string>();
99
-
100
- for (const entry of configured) {
101
- const model = ctx.modelRegistry.find(entry.provider, entry.modelname);
102
- if (!model) {
103
- modelWarnings.push(`missing ${entry.provider}/${entry.modelname}`);
104
- continue;
105
- }
106
- if (!ctx.modelRegistry.hasConfiguredAuth(model)) {
107
- modelWarnings.push(`no auth ${entry.provider}/${entry.modelname}`);
108
- continue;
109
- }
110
- const key = modelKey(model);
111
- if (seen.has(key)) continue;
112
- seen.add(key);
113
- state.fallbackModels.push({ model, reasoningEffort: entry.reasoningEffort });
114
- }
115
-
116
- const lines = state.fallbackModels.map((entry, index) =>
117
- `${index + 1}. ${formatModel(entry.model)}${entry.reasoningEffort ? ` (${entry.reasoningEffort})` : ""}`,
118
- );
119
- const source = loadedPaths.length > 0 ? ` from ${loadedPaths.join(", ")}` : "";
120
- const message = lines.length > 0
121
- ? `Loaded ${SETTINGS_FILE_NAME}.${FALLBACK_MODELS_KEY}${source}:\n${lines.join("\n")}`
122
- : `Loaded ${SETTINGS_FILE_NAME}.${FALLBACK_MODELS_KEY}${source}, but no usable fallback models were found.`;
123
- const allWarnings = [...warnings, ...modelWarnings];
124
- if (shouldNotify) {
125
- ctx.ui.notify(allWarnings.length > 0 ? `${message}\nSkipped: ${allWarnings.join(", ")}` : message, allWarnings.length > 0 ? "warning" : "info");
126
- }
127
- } catch (err) {
128
- ctx.ui.notify(`Could not load ${SETTINGS_FILE_NAME}.${FALLBACK_MODELS_KEY}: ${err instanceof Error ? err.message : String(err)}`, "warning");
129
- }
130
- }
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join, resolve } from "path";
4
+ import { getAgentDir, type ExtensionContext } from "@mariozechner/pi-coding-agent";
5
+ import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
6
+ import { FALLBACK_MODELS_KEY, SETTINGS_FILE_NAME } from "./constants.js";
7
+ import { formatModel, modelKey } from "./models.js";
8
+ import { state } from "./state.js";
9
+ import type { ConfiguredModel } from "./types.js";
10
+
11
+ function isThinkingLevel(value: unknown): value is ThinkingLevel {
12
+ return ["off", "minimal", "low", "medium", "high", "xhigh"].includes(String(value));
13
+ }
14
+
15
+ export function parseConfiguredModels(raw: unknown): ConfiguredModel[] {
16
+ const source = raw && typeof raw === "object" && Array.isArray((raw as { [FALLBACK_MODELS_KEY]?: unknown })[FALLBACK_MODELS_KEY])
17
+ ? (raw as { [FALLBACK_MODELS_KEY]: unknown[] })[FALLBACK_MODELS_KEY]
18
+ : [];
19
+
20
+ const models: ConfiguredModel[] = [];
21
+ for (const item of source) {
22
+ if (!item || typeof item !== "object") continue;
23
+ const record = item as Record<string, unknown>;
24
+ const provider = typeof record.provider === "string" ? record.provider.trim() : "";
25
+ const modelname =
26
+ typeof record.modelname === "string" ? record.modelname.trim()
27
+ : typeof record.modelName === "string" ? record.modelName.trim()
28
+ : typeof record.model === "string" ? record.model.trim()
29
+ : "";
30
+ const reasoning = record["reasoning effort"] ?? record.reasoningEffort ?? record.reasoning_effort;
31
+ if (!provider || !modelname) continue;
32
+ models.push({ provider, modelname, reasoningEffort: isThinkingLevel(reasoning) ? reasoning : undefined });
33
+ }
34
+ return models;
35
+ }
36
+
37
+ export function configFileCandidates(cwd: string, agentDir = getAgentDir(), homeDir = homedir()): string[] {
38
+ return [
39
+ join(homeDir, ".config", ".pi", SETTINGS_FILE_NAME),
40
+ join(agentDir, SETTINGS_FILE_NAME),
41
+ resolve(cwd, `.${SETTINGS_FILE_NAME}`),
42
+ resolve(cwd, ".pi", SETTINGS_FILE_NAME),
43
+ ];
44
+ }
45
+
46
+ function mergeConfig(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {
47
+ return { ...base, ...override };
48
+ }
49
+
50
+ export function readFallbackSettings(cwd: string, agentDir = getAgentDir(), homeDir = homedir()): { config: Record<string, unknown>; loadedPaths: string[]; warnings: string[] } {
51
+ let config: Record<string, unknown> = {};
52
+ const loadedPaths: string[] = [];
53
+ const warnings: string[] = [];
54
+ const seen = new Set<string>();
55
+
56
+ for (const candidate of configFileCandidates(cwd, agentDir, homeDir)) {
57
+ const path = resolve(candidate);
58
+ if (seen.has(path)) continue;
59
+ seen.add(path);
60
+ if (!existsSync(path)) continue;
61
+
62
+ try {
63
+ const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
64
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
65
+ warnings.push(`${path}: expected a JSON object`);
66
+ continue;
67
+ }
68
+ config = mergeConfig(config, parsed as Record<string, unknown>);
69
+ loadedPaths.push(path);
70
+ } catch (err) {
71
+ warnings.push(`${path}: ${err instanceof Error ? err.message : String(err)}`);
72
+ }
73
+ }
74
+
75
+ return { config, loadedPaths, warnings };
76
+ }
77
+
78
+ export function __readFallbackSettingsForTests(cwd: string, agentDir: string, homeDir: string): { config: Record<string, unknown>; loadedPaths: string[]; warnings: string[] } {
79
+ return readFallbackSettings(cwd, agentDir, homeDir);
80
+ }
81
+
82
+ export function loadFallbackSettings(ctx: ExtensionContext): void {
83
+ state.fallbackModels = [];
84
+
85
+ try {
86
+ const { config, loadedPaths, warnings } = readFallbackSettings(ctx.cwd);
87
+ const content = JSON.stringify({ config, loadedPaths, warnings });
88
+ const shouldNotify = state.settingsSignature !== content;
89
+ state.settingsSignature = content;
90
+
91
+ const configured = parseConfiguredModels(config);
92
+ if (configured.length === 0) {
93
+ if (shouldNotify && warnings.length > 0) ctx.ui.notify(`Could not fully load ${SETTINGS_FILE_NAME}: ${warnings.join(", ")}`, "warning");
94
+ return;
95
+ }
96
+
97
+ const modelWarnings: string[] = [];
98
+ const seen = new Set<string>();
99
+
100
+ for (const entry of configured) {
101
+ const model = ctx.modelRegistry.find(entry.provider, entry.modelname);
102
+ if (!model) {
103
+ modelWarnings.push(`missing ${entry.provider}/${entry.modelname}`);
104
+ continue;
105
+ }
106
+ if (!ctx.modelRegistry.hasConfiguredAuth(model)) {
107
+ modelWarnings.push(`no auth ${entry.provider}/${entry.modelname}`);
108
+ continue;
109
+ }
110
+ const key = modelKey(model);
111
+ if (seen.has(key)) continue;
112
+ seen.add(key);
113
+ state.fallbackModels.push({ model, reasoningEffort: entry.reasoningEffort });
114
+ }
115
+
116
+ const lines = state.fallbackModels.map((entry, index) =>
117
+ `${index + 1}. ${formatModel(entry.model)}${entry.reasoningEffort ? ` (${entry.reasoningEffort})` : ""}`,
118
+ );
119
+ const source = loadedPaths.length > 0 ? ` from ${loadedPaths.join(", ")}` : "";
120
+ const message = lines.length > 0
121
+ ? `Loaded ${SETTINGS_FILE_NAME}.${FALLBACK_MODELS_KEY}${source}:\n${lines.join("\n")}`
122
+ : `Loaded ${SETTINGS_FILE_NAME}.${FALLBACK_MODELS_KEY}${source}, but no usable fallback models were found.`;
123
+ const allWarnings = [...warnings, ...modelWarnings];
124
+ if (shouldNotify) {
125
+ ctx.ui.notify(allWarnings.length > 0 ? `${message}\nSkipped: ${allWarnings.join(", ")}` : message, allWarnings.length > 0 ? "warning" : "info");
126
+ }
127
+ } catch (err) {
128
+ ctx.ui.notify(`Could not load ${SETTINGS_FILE_NAME}.${FALLBACK_MODELS_KEY}: ${err instanceof Error ? err.message : String(err)}`, "warning");
129
+ }
130
+ }
package/src/state.ts CHANGED
@@ -1,39 +1,39 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
- import type { Api, Model } from "@mariozechner/pi-ai";
3
- import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
4
- import { NON_RETRYABLE_MAX_ATTEMPTS, NON_RETRYABLE_RETRY_DELAY_MS } from "./constants.js";
5
- import type { FallbackModel, NonRetryableFailureMemory, RateLimitMemory, StreamSimpleFn } from "./types.js";
6
-
7
- export const state = {
8
- sharedCtx: undefined as ExtensionContext | undefined,
9
- extensionApi: undefined as ExtensionAPI | undefined,
10
- restoreFetch: undefined as (() => void) | undefined,
11
- ambientStatusCleanup: undefined as (() => void) | undefined,
12
- modelStatusCleanup: undefined as (() => void) | undefined,
13
- activeProviderRequests: 0,
14
- activeFallbackProviderRequests: 0,
15
- lastObservedHttpError: undefined as { at: number; message: string } | undefined,
16
- wrappedApis: new Set<Api>(),
17
- builtinStreamSimpleByApi: new Map<Api, StreamSimpleFn>(),
18
- fallbackModels: [] as FallbackModel[],
19
- nonRetryableMaxAttempts: NON_RETRYABLE_MAX_ATTEMPTS,
20
- nonRetryableRetryDelayMs: NON_RETRYABLE_RETRY_DELAY_MS,
21
- primaryModel: undefined as Model<Api> | undefined,
22
- primaryThinkingLevel: undefined as ThinkingLevel | undefined,
23
- suppressNextModelSelect: false,
24
- settingsSignature: undefined as string | undefined,
25
- rateLimitMemory: new Map<string, RateLimitMemory>(),
26
- nonRetryableFailureMemory: new Map<string, NonRetryableFailureMemory>(),
27
- };
28
-
29
- export function resetRuntimeStateForTests(models: FallbackModel[], ctx?: ExtensionContext): void {
30
- state.fallbackModels = models;
31
- state.sharedCtx = ctx;
32
- state.primaryModel = undefined;
33
- state.primaryThinkingLevel = undefined;
34
- state.rateLimitMemory.clear();
35
- state.nonRetryableFailureMemory.clear();
36
- state.lastObservedHttpError = undefined;
37
- state.ambientStatusCleanup?.();
38
- state.modelStatusCleanup?.();
39
- }
1
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+ import type { Api, Model } from "@mariozechner/pi-ai";
3
+ import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
4
+ import { NON_RETRYABLE_MAX_ATTEMPTS, NON_RETRYABLE_RETRY_DELAY_MS } from "./constants.js";
5
+ import type { FallbackModel, NonRetryableFailureMemory, RateLimitMemory, StreamSimpleFn } from "./types.js";
6
+
7
+ export const state = {
8
+ sharedCtx: undefined as ExtensionContext | undefined,
9
+ extensionApi: undefined as ExtensionAPI | undefined,
10
+ restoreFetch: undefined as (() => void) | undefined,
11
+ ambientStatusCleanup: undefined as (() => void) | undefined,
12
+ modelStatusCleanup: undefined as (() => void) | undefined,
13
+ activeProviderRequests: 0,
14
+ activeFallbackProviderRequests: 0,
15
+ lastObservedHttpError: undefined as { at: number; message: string } | undefined,
16
+ wrappedApis: new Set<Api>(),
17
+ builtinStreamSimpleByApi: new Map<Api, StreamSimpleFn>(),
18
+ fallbackModels: [] as FallbackModel[],
19
+ nonRetryableMaxAttempts: NON_RETRYABLE_MAX_ATTEMPTS,
20
+ nonRetryableRetryDelayMs: NON_RETRYABLE_RETRY_DELAY_MS,
21
+ primaryModel: undefined as Model<Api> | undefined,
22
+ primaryThinkingLevel: undefined as ThinkingLevel | undefined,
23
+ suppressNextModelSelect: false,
24
+ settingsSignature: undefined as string | undefined,
25
+ rateLimitMemory: new Map<string, RateLimitMemory>(),
26
+ nonRetryableFailureMemory: new Map<string, NonRetryableFailureMemory>(),
27
+ };
28
+
29
+ export function resetRuntimeStateForTests(models: FallbackModel[], ctx?: ExtensionContext): void {
30
+ state.fallbackModels = models;
31
+ state.sharedCtx = ctx;
32
+ state.primaryModel = undefined;
33
+ state.primaryThinkingLevel = undefined;
34
+ state.rateLimitMemory.clear();
35
+ state.nonRetryableFailureMemory.clear();
36
+ state.lastObservedHttpError = undefined;
37
+ state.ambientStatusCleanup?.();
38
+ state.modelStatusCleanup?.();
39
+ }