@openlines/opl-pi-sht 0.1.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/LICENSE +21 -0
- package/README.md +191 -0
- package/configs/opl-browser.json.sample +8 -0
- package/configs/opl-footer.json +48 -0
- package/configs/opl-footer.json.sample +48 -0
- package/configs/opl-input.json +22 -0
- package/configs/opl-input.json.sample +22 -0
- package/configs/opl-modes.json +291 -0
- package/configs/opl-modes.json.sample +285 -0
- package/configs/opl-simplebench.json +8 -0
- package/configs/opl-simplebench.json.sample +8 -0
- package/configs/opl-todo.json +12 -0
- package/configs/opl-todo.json.sample +12 -0
- package/configs/opl-webaccess.json +31 -0
- package/configs/opl-webaccess.json.sample +31 -0
- package/extensions/opl-browser/README.md +83 -0
- package/extensions/opl-browser/browser.ts +185 -0
- package/extensions/opl-browser/config.ts +37 -0
- package/extensions/opl-browser/index.ts +89 -0
- package/extensions/opl-browser/package-lock.json +60 -0
- package/extensions/opl-browser/package.json +10 -0
- package/extensions/opl-ctxtrim/README.md +78 -0
- package/extensions/opl-ctxtrim/index.ts +151 -0
- package/extensions/opl-footer/README.md +193 -0
- package/extensions/opl-footer/config.ts +91 -0
- package/extensions/opl-footer/git-status.ts +170 -0
- package/extensions/opl-footer/icons.ts +49 -0
- package/extensions/opl-footer/index.ts +368 -0
- package/extensions/opl-footer/segments/caveman.ts +25 -0
- package/extensions/opl-footer/segments/chat-mode.ts +24 -0
- package/extensions/opl-footer/segments/context.ts +92 -0
- package/extensions/opl-footer/segments/cost.ts +25 -0
- package/extensions/opl-footer/segments/git.ts +47 -0
- package/extensions/opl-footer/segments/helpers.ts +18 -0
- package/extensions/opl-footer/segments/index.ts +51 -0
- package/extensions/opl-footer/segments/mode-switcher.ts +24 -0
- package/extensions/opl-footer/segments/model.ts +22 -0
- package/extensions/opl-footer/segments/path.ts +34 -0
- package/extensions/opl-footer/segments/pi.ts +11 -0
- package/extensions/opl-footer/segments/plan-mode.ts +24 -0
- package/extensions/opl-footer/segments/separator.ts +9 -0
- package/extensions/opl-footer/segments/session-stats.ts +82 -0
- package/extensions/opl-footer/segments/thinking.ts +51 -0
- package/extensions/opl-footer/segments/tokens.ts +66 -0
- package/extensions/opl-footer/theme.ts +113 -0
- package/extensions/opl-footer/types.ts +172 -0
- package/extensions/opl-init/README.md +23 -0
- package/extensions/opl-init/index.ts +451 -0
- package/extensions/opl-input/README.md +83 -0
- package/extensions/opl-input/config.ts +137 -0
- package/extensions/opl-input/index.ts +284 -0
- package/extensions/opl-input/mode-style.ts +38 -0
- package/extensions/opl-input/utils.ts +378 -0
- package/extensions/opl-modes/README.md +132 -0
- package/extensions/opl-modes/config.ts +473 -0
- package/extensions/opl-modes/index.ts +1081 -0
- package/extensions/opl-modes/menus.ts +60 -0
- package/extensions/opl-modes/state.ts +152 -0
- package/extensions/opl-modes/types.ts +164 -0
- package/extensions/opl-modes/utils.ts +138 -0
- package/extensions/opl-questionnaire/README.md +26 -0
- package/extensions/opl-questionnaire/index.ts +445 -0
- package/extensions/opl-questionnaire/types.ts +55 -0
- package/extensions/opl-simplebench/README.md +242 -0
- package/extensions/opl-simplebench/artifact.ts +41 -0
- package/extensions/opl-simplebench/benchmark.ts +873 -0
- package/extensions/opl-simplebench/coding.ts +282 -0
- package/extensions/opl-simplebench/index.ts +203 -0
- package/extensions/opl-simplebench/llama-server.ts +290 -0
- package/extensions/opl-simplebench/metrics.ts +73 -0
- package/extensions/opl-simplebench/report.ts +46 -0
- package/extensions/opl-simplebench/research.ts +200 -0
- package/extensions/opl-simplebench/scoring.ts +48 -0
- package/extensions/opl-simplebench/tests.ts +39 -0
- package/extensions/opl-simplebench/types.ts +6 -0
- package/extensions/opl-simplebench/util/config.ts +170 -0
- package/extensions/opl-simplebench/util/debug.ts +32 -0
- package/extensions/opl-simplebench/util/format.ts +402 -0
- package/extensions/opl-simplebench/util/providers.ts +841 -0
- package/extensions/opl-todo/README.md +43 -0
- package/extensions/opl-todo/config.ts +71 -0
- package/extensions/opl-todo/index.ts +415 -0
- package/extensions/opl-webaccess/README.md +47 -0
- package/extensions/opl-webaccess/config.ts +55 -0
- package/extensions/opl-webaccess/extract.ts +83 -0
- package/extensions/opl-webaccess/index.ts +232 -0
- package/extensions/opl-webaccess/package-lock.json +345 -0
- package/extensions/opl-webaccess/package.json +18 -0
- package/extensions/opl-webaccess/pdf.ts +8 -0
- package/extensions/opl-webaccess/providers/ddgs.ts +72 -0
- package/extensions/opl-webaccess/providers/exa.ts +74 -0
- package/extensions/opl-webaccess/providers/gemini.ts +60 -0
- package/extensions/opl-webaccess/providers/searxng.ts +86 -0
- package/extensions/opl-webaccess/providers/tavily.ts +60 -0
- package/extensions/opl-webaccess/search.ts +36 -0
- package/extensions/opl-webaccess/storage.ts +51 -0
- package/extensions/opl-webaccess/turndown.d.ts +1 -0
- package/extensions/opl-webaccess/types.ts +26 -0
- package/extensions/opl-webaccess/utils.ts +28 -0
- package/package.json +55 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ReasoningFixture {
|
|
2
|
+
name: string;
|
|
3
|
+
prompt: string;
|
|
4
|
+
expectedAnswer: string;
|
|
5
|
+
category: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const finalAnswer = "Reason as needed. Put only the exact answer on your final line.";
|
|
9
|
+
|
|
10
|
+
export const REASONING_TESTS: ReasoningFixture[] = [
|
|
11
|
+
{ name: "snail_wall", prompt: `A snail climbs 3 feet each day and slides 2 feet each night. The wall is 10 feet tall. How many days until it first reaches the top? ${finalAnswer}`, expectedAnswer: "8", category: "logic" },
|
|
12
|
+
{ name: "math_sequence", prompt: `What is the next number: 2, 6, 18, 54, ? ${finalAnswer}`, expectedAnswer: "162", category: "math" },
|
|
13
|
+
{ name: "spatial_directions", prompt: `Face north. Turn 90 degrees clockwise, then 180 degrees counter-clockwise. Which direction are you facing? ${finalAnswer}`, expectedAnswer: "west", category: "spatial" },
|
|
14
|
+
{ name: "commonsense", prompt: `Can a rooster lay an egg? Answer with a yes or a no only. ${finalAnswer}`, expectedAnswer: "no", category: "commonsense" },
|
|
15
|
+
{ name: "code_simplify", prompt: `What is x after: let x = 0; for (let i = 1; i <= 5; i++) x += i; ${finalAnswer}`, expectedAnswer: "15", category: "code" },
|
|
16
|
+
{ name: "bat_and_ball", prompt: `A bat and ball cost 110 cents. The bat costs 100 cents more than the ball. How many cents does the ball cost? Answer with digits only. ${finalAnswer}`, expectedAnswer: "5", category: "counterint" },
|
|
17
|
+
{ name: "scale_weight", prompt: `One weight is 100 grams. Another is four times as heavy. How many grams does the second weigh? Answer with digits only. ${finalAnswer}`, expectedAnswer: "400", category: "counterint" },
|
|
18
|
+
{ name: "syllogism", prompt: `All mammals are warm-blooded. All dogs are mammals. What property do all dogs have? Answer exactly: warm-blooded. ${finalAnswer}`, expectedAnswer: "warm-blooded", category: "logic" },
|
|
19
|
+
{ name: "if_then_chain", prompt: `If it rains, the ground gets wet. If the ground gets wet, grass grows. It is raining. What happens to the grass? Answer exactly: grass grows. ${finalAnswer}`, expectedAnswer: "grass grows", category: "logic" },
|
|
20
|
+
{ name: "cause_effect", prompt: `A tomato plant produces 4 tomatoes each day for 3 days. How many tomatoes does it produce? Answer with digits only. ${finalAnswer}`, expectedAnswer: "12", category: "causal" },
|
|
21
|
+
{ name: "relative_quantities", prompt: `Tom has three times as many apples as Sara. Sara has five apples. How many apples does Tom have? Answer with digits only. ${finalAnswer}`, expectedAnswer: "15", category: "comparative" },
|
|
22
|
+
{ name: "analogy_1", prompt: `Use the explicitly stated relationship “is the capital city of”: Paris is to France as Tokyo is to which country? Answer exactly one word: Japan or China. Reason as needed. Put only the exact answer on your final line. ${finalAnswer}`, expectedAnswer: "japan", category: "analogy" },
|
|
23
|
+
{ name: "analogy_2", prompt: `3 is to 6 as 5 is to what number? ${finalAnswer}`, expectedAnswer: "10", category: "analogy" },
|
|
24
|
+
{ name: "physics_1", prompt: `Which has more mass? Answer exactly: bowling ball or tennis ball. ${finalAnswer}`, expectedAnswer: "bowling ball", category: "commonsense" },
|
|
25
|
+
{ name: "physics_2", prompt: `Compared with before heating, is a metal spoon hotter or colder after it is heated? ${finalAnswer}`, expectedAnswer: "hotter", category: "commonsense" },
|
|
26
|
+
{ name: "objects_1", prompt: `What tool cuts paper? ${finalAnswer}`, expectedAnswer: "scissors", category: "commonsense" },
|
|
27
|
+
{ name: "social_1", prompt: `Someone says please and thank you. Are they polite or rude? Answer polite or rude. ${finalAnswer}`, expectedAnswer: "polite", category: "commonsense" },
|
|
28
|
+
{ name: "animals_1", prompt: `Dolphins live in water, not on land. Answer exactly: water or land. ${finalAnswer}`, expectedAnswer: "water", category: "commonsense" },
|
|
29
|
+
{ name: "gk_1", prompt: `Which planet is known as the Red Planet? ${finalAnswer}`, expectedAnswer: "mars", category: "commonsense" },
|
|
30
|
+
{ name: "gk_2", prompt: `How many days are in a leap year? ${finalAnswer}`, expectedAnswer: "366", category: "commonsense" },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const MULTISTEP_INSTRUCTION = `Respond with only this JSON object, with no Markdown or explanation:
|
|
34
|
+
{"operation":"status","requestId":"bench-42","ok":true}`;
|
|
35
|
+
|
|
36
|
+
export const CALC_TOOL_DEFINITION = {
|
|
37
|
+
type: "function" as const,
|
|
38
|
+
function: { name: "calculate", description: "Perform a mathematical calculation", parameters: { type: "object", properties: { expression: { type: "string" } }, required: ["expression"] } },
|
|
39
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ServerStats } from "./llama-server";
|
|
2
|
+
|
|
3
|
+
export interface SimplebenchOptions { model?: string; allModels: boolean; writeArtifact: boolean; thinkingMax?: boolean; codingLite?: boolean; testAll?: boolean; researchLive?: boolean; llamaServer?: boolean; llamagputop?: boolean; tag?: string; }
|
|
4
|
+
export interface RequestMetrics { requestCount: number; retryCount: number; wallTimeMs: number; timeToAnswerMs: number; timeToFirstTokenMs: number | null; startedAt: string | null; finishedAt: string | null; inputTokens: number | null; outputTokens: number | null; totalTokens: number | null; outputTokensPerSecond: number | null; toolCalls: Array<{ name: string; arguments?: unknown }>; }
|
|
5
|
+
export interface TestRecord { id: string; kind: "reasoning" | "instructions" | "tools" | "coding" | "research-grounded" | "research-live"; category?: string; prompt: string; response: string | null; expectedAnswer?: string | string[]; extractedAnswer?: string; score: string; passed: boolean; error: string | null; metrics: RequestMetrics; coding?: { publicPassed: boolean; hiddenPassed: boolean; verifiedAfterEdit: boolean; unrelatedFiles: string[]; toolCalls: number; turns: number; efficiency?: "STRONG" | "MODERATE" | "WEAK" | "FAIL"; }; research?: { toolCalls: string[]; files: string[]; }; }
|
|
6
|
+
export interface RunArtifact { schemaVersion: 1; benchmark: { name: "opl-simplebench"; suite?: "baseline" | "coding-lite" | "test-all"; tag?: string; model: string; provider: string; providerKind: string; thinking: { requested: "default" | "max"; effective: "provider-default" | "openai-reasoning-effort" | "pi-bedrock-reasoning"; level: "max" | null; modelMetadataSource: "active-context" | "scoped-model" | null; }; startedAt: string; finishedAt: string; wallTimeMs: number; artifactEnabled: boolean; }; tests: TestRecord[]; summary: Record<string, unknown> & { serverStats?: ServerStats }; }
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test utilities for simplebench extension.
|
|
3
|
+
* Extracted from extensions/simplebench.ts to reduce duplication between
|
|
4
|
+
* Ollama-specific and Provider-specific test variants.
|
|
5
|
+
*
|
|
6
|
+
* @module shared/simplebench-utils
|
|
7
|
+
* @writtenby VTSTech — https://www.vts-tech.org
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Configuration Constants
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configuration constants for model testing.
|
|
20
|
+
* Centralized to make tuning and maintenance easier.
|
|
21
|
+
*
|
|
22
|
+
* @property DEFAULT_TIMEOUT_MS - Default timeout for Ollama API calls (~16.7 min)
|
|
23
|
+
* @property CONNECT_TIMEOUT_S - Connection timeout for fetch (seconds)
|
|
24
|
+
* @property MAX_RETRIES - Number of retry attempts for transient failures
|
|
25
|
+
* @property RETRY_DELAY_MS - Delay between retry attempts (milliseconds)
|
|
26
|
+
* @property MIN_THINKING_LENGTH - Minimum characters to consider thinking tokens valid
|
|
27
|
+
* @property TOOL_TEST_TIMEOUT_MS - Timeout for tool usage tests
|
|
28
|
+
* @property TOOL_SUPPORT_TIMEOUT_MS - Timeout for tool support detection
|
|
29
|
+
* @property TAGS_TIMEOUT_MS - Timeout for /api/tags requests
|
|
30
|
+
*/
|
|
31
|
+
export const CONFIG = {
|
|
32
|
+
// General API settings - standardized across all providers
|
|
33
|
+
DEFAULT_TIMEOUT_MS: 300000, // 5 minutes - reasonable timeout for all providers
|
|
34
|
+
CONNECT_TIMEOUT_S: 60, // 60 seconds to establish connection
|
|
35
|
+
MAX_RETRIES: 2, // Two retries for transient failures (standardized)
|
|
36
|
+
RETRY_DELAY_MS: 15000, // 15 seconds between retries (standardized)
|
|
37
|
+
|
|
38
|
+
// Test-specific settings - standardized across all providers
|
|
39
|
+
MIN_THINKING_LENGTH: 10, // Minimum chars to consider thinking tokens valid
|
|
40
|
+
TOOL_TEST_TIMEOUT_MS: 300000, // 5 minutes - consistent timeout for tool usage tests
|
|
41
|
+
TOOL_SUPPORT_TIMEOUT_MS: 300000, // 5 minutes - consistent timeout for tool support detection
|
|
42
|
+
|
|
43
|
+
// Metadata retrieval
|
|
44
|
+
TAGS_TIMEOUT_MS: 15000, // 15 seconds for /api/tags
|
|
45
|
+
MODEL_INFO_TIMEOUT_MS: 30000, // 30 seconds for model info lookup
|
|
46
|
+
|
|
47
|
+
// Provider API settings
|
|
48
|
+
PROVIDER_TIMEOUT_MS: 300000, // 5 minutes - consistent with Ollama
|
|
49
|
+
PROVIDER_TOOL_TIMEOUT_MS: 300000, // 5 minutes - consistent with Ollama tool tests
|
|
50
|
+
|
|
51
|
+
// Context length fetching
|
|
52
|
+
CONTEXT_BATCH_SIZE: 3, // Concurrent requests when fetching model context lengths
|
|
53
|
+
|
|
54
|
+
// Rate limiting
|
|
55
|
+
TEST_DELAY_MS: 4000, // 4 seconds between tests to avoid rate limiting
|
|
56
|
+
} as const;
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// User Configuration Overrides
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
const TEST_CONFIG_DIR = process.env.PI_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
|
|
63
|
+
export const TEST_CONFIG_PATH = path.join(TEST_CONFIG_DIR, "configs", "opl-simplebench.json");
|
|
64
|
+
const LEGACY_TEST_CONFIG_PATH = path.join(TEST_CONFIG_DIR, "simplebench-config.json");
|
|
65
|
+
|
|
66
|
+
/** Shape of the user configuration file. */
|
|
67
|
+
export interface ModelTestUserConfig {
|
|
68
|
+
defaultTimeoutMs?: number;
|
|
69
|
+
connectTimeoutS?: number;
|
|
70
|
+
maxRetries?: number;
|
|
71
|
+
retryDelayMs?: number;
|
|
72
|
+
testDelayMs?: number;
|
|
73
|
+
toolTestTimeoutMs?: number;
|
|
74
|
+
providerTimeoutMs?: number;
|
|
75
|
+
providerToolTimeoutMs?: number;
|
|
76
|
+
contextBatchSize?: number;
|
|
77
|
+
researchSearchProvider?: "ddgs" | "searxng";
|
|
78
|
+
researchSearchUrl?: string;
|
|
79
|
+
researchMaxResults?: number;
|
|
80
|
+
llamaServerUrl?: string;
|
|
81
|
+
llamagputopUrl?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Read user configuration from ~/.pi/agent/simplebench-config.json.
|
|
86
|
+
* Returns an empty object if the file doesn't exist or is invalid.
|
|
87
|
+
*/
|
|
88
|
+
export function readTestConfig(): ModelTestUserConfig {
|
|
89
|
+
try {
|
|
90
|
+
const configPath = fs.existsSync(TEST_CONFIG_PATH) ? TEST_CONFIG_PATH : LEGACY_TEST_CONFIG_PATH;
|
|
91
|
+
if (fs.existsSync(configPath)) return JSON.parse(fs.readFileSync(configPath, "utf-8")) as ModelTestUserConfig;
|
|
92
|
+
} catch { /* config read/parse failure is non-critical — defaults are used */ }
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get effective test configuration by merging user overrides with defaults.
|
|
98
|
+
* User values take precedence over CONFIG defaults.
|
|
99
|
+
*/
|
|
100
|
+
export function getEffectiveConfig(): typeof CONFIG {
|
|
101
|
+
const userConfig = readTestConfig();
|
|
102
|
+
return {
|
|
103
|
+
...CONFIG,
|
|
104
|
+
DEFAULT_TIMEOUT_MS: (userConfig.defaultTimeoutMs ?? CONFIG.DEFAULT_TIMEOUT_MS) as typeof CONFIG.DEFAULT_TIMEOUT_MS,
|
|
105
|
+
CONNECT_TIMEOUT_S: (userConfig.connectTimeoutS ?? CONFIG.CONNECT_TIMEOUT_S) as typeof CONFIG.CONNECT_TIMEOUT_S,
|
|
106
|
+
MAX_RETRIES: (userConfig.maxRetries ?? CONFIG.MAX_RETRIES) as typeof CONFIG.MAX_RETRIES,
|
|
107
|
+
RETRY_DELAY_MS: (userConfig.retryDelayMs ?? CONFIG.RETRY_DELAY_MS) as typeof CONFIG.RETRY_DELAY_MS,
|
|
108
|
+
TEST_DELAY_MS: (userConfig.testDelayMs ?? CONFIG.TEST_DELAY_MS) as typeof CONFIG.TEST_DELAY_MS,
|
|
109
|
+
TOOL_TEST_TIMEOUT_MS: (userConfig.toolTestTimeoutMs ?? CONFIG.TOOL_TEST_TIMEOUT_MS) as typeof CONFIG.TOOL_TEST_TIMEOUT_MS,
|
|
110
|
+
PROVIDER_TIMEOUT_MS: (userConfig.providerTimeoutMs ?? CONFIG.PROVIDER_TIMEOUT_MS) as typeof CONFIG.PROVIDER_TIMEOUT_MS,
|
|
111
|
+
PROVIDER_TOOL_TIMEOUT_MS: (userConfig.providerToolTimeoutMs ?? CONFIG.PROVIDER_TOOL_TIMEOUT_MS) as typeof CONFIG.PROVIDER_TOOL_TIMEOUT_MS,
|
|
112
|
+
CONTEXT_BATCH_SIZE: (userConfig.contextBatchSize ?? CONFIG.CONTEXT_BATCH_SIZE) as typeof CONFIG.CONTEXT_BATCH_SIZE,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Weather Tool Definition (shared across all tool tests)
|
|
118
|
+
// ============================================================================
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Standard get_weather tool schema used by all tool usage tests.
|
|
122
|
+
* Previously copy-pasted in 3+ places — now a single shared constant.
|
|
123
|
+
*/
|
|
124
|
+
export const WEATHER_TOOL_DEFINITION = {
|
|
125
|
+
type: "function" as const,
|
|
126
|
+
function: {
|
|
127
|
+
name: "get_weather",
|
|
128
|
+
description: "Get the current weather for a location",
|
|
129
|
+
parameters: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
location: { type: "string", description: "City name" },
|
|
133
|
+
unit: { type: "string", enum: ["celsius", "fahrenheit"] },
|
|
134
|
+
},
|
|
135
|
+
required: ["location"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Tool Support Cache (path only — backing file is no longer written)
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
const TOOL_SUPPORT_CACHE_DIR = path.join(os.homedir(), ".pi", "agent", "cache");
|
|
145
|
+
export const TOOL_SUPPORT_CACHE_PATH = path.join(TOOL_SUPPORT_CACHE_DIR, "tool_support.json");
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// ChatFn Abstraction
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Abstraction over Ollama and Provider chat APIs.
|
|
153
|
+
* Callers wrap their specific chat implementation into this shape.
|
|
154
|
+
*/
|
|
155
|
+
export type ChatMessage = { role: string; content: string; tool_calls?: any[]; tool_call_id?: string };
|
|
156
|
+
|
|
157
|
+
export function buildToolResultMessages(assistantContent: string, calls: any[], results: string[]): ChatMessage[] {
|
|
158
|
+
const toolCalls = calls.map((call, index) => ({ ...call, id: call.id || `toolcall${index + 1}` }));
|
|
159
|
+
return [
|
|
160
|
+
{ role: "assistant", content: assistantContent || "", tool_calls: toolCalls },
|
|
161
|
+
...toolCalls.map((call, index) => ({ role: "tool", tool_call_id: call.id, content: results[index] || "INVALID_ARGUMENTS" })),
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type ChatFn = (
|
|
166
|
+
model: string,
|
|
167
|
+
messages: ChatMessage[],
|
|
168
|
+
options?: Record<string, unknown>,
|
|
169
|
+
) => Promise<{ content: string; toolCalls?: any[]; elapsedMs: number; raw?: any; requestCount?: number; retryCount?: number; startedAt?: string; finishedAt?: string; timeToFirstTokenMs?: number | null }>;
|
|
170
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug logging utility for Pi Coding Agent extensions.
|
|
3
|
+
*
|
|
4
|
+
* Only emits output when the PI_EXTENSIONS_DEBUG environment variable is set to "1".
|
|
5
|
+
* This allows developers to enable verbose diagnostic logging without impacting
|
|
6
|
+
* production performance or cluttering normal output.
|
|
7
|
+
*
|
|
8
|
+
* @module shared/debug
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEBUG_ENABLED = process?.env?.PI_EXTENSIONS_DEBUG === "1";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Log a debug message to stderr (via console.debug).
|
|
15
|
+
*
|
|
16
|
+
* @param module - Short identifier for the emitting module (e.g., "ollama", "security")
|
|
17
|
+
* @param message - Human-readable message describing the event
|
|
18
|
+
* @param args - Additional values to log alongside the message
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { debugLog } from "../shared/debug";
|
|
23
|
+
* try { … } catch (err) {
|
|
24
|
+
* debugLog("ollama", "failed to read models.json", err);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function debugLog(module: string, message: string, ...args: unknown[]): void {
|
|
29
|
+
if (!DEBUG_ENABLED) return;
|
|
30
|
+
const timestamp = new Date().toISOString();
|
|
31
|
+
console.debug(`[pi-ext:${module}] ${timestamp} ${message}`, ...args);
|
|
32
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared formatting utilities for Pi Coding Agent extensions.
|
|
3
|
+
* Extracted from diag.ts and simplebench.ts to eliminate duplication.
|
|
4
|
+
*
|
|
5
|
+
* @module shared/format
|
|
6
|
+
* @writtenby VTSTech — https://www.vts-tech.org
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Section & Indicator Helpers
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render a section header with a horizontal rule.
|
|
15
|
+
*
|
|
16
|
+
* Creates a visually distinct section header in the format:
|
|
17
|
+
* `── TITLE ──────────────────────────────────────────────`
|
|
18
|
+
*
|
|
19
|
+
* The horizontal rule automatically fills the remaining space
|
|
20
|
+
* to create a consistent 60-character wide header.
|
|
21
|
+
*
|
|
22
|
+
* @param title - The section title to display
|
|
23
|
+
* @returns A formatted section header string
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* section("REASONING TEST");
|
|
28
|
+
* // Returns: "\n── REASONING TEST ───────────────────────────────────────"
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function section(title: string): string {
|
|
32
|
+
return `\n── ${title} ${"─".repeat(Math.max(1, 60 - title.length - 4))}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format a success message with a checkmark indicator.
|
|
37
|
+
*
|
|
38
|
+
* @param msg - The message to format
|
|
39
|
+
* @returns The formatted message with ✅ prefix
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* ok("Tool call successful");
|
|
44
|
+
* // Returns: " ✅ Tool call successful"
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function ok(msg: string): string { return ` ✅ ${msg}`; }
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format a failure message with an X indicator.
|
|
51
|
+
*
|
|
52
|
+
* @param msg - The message to format
|
|
53
|
+
* @returns The formatted message with ❌ prefix
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* fail("Connection timeout");
|
|
58
|
+
* // Returns: " ❌ Connection timeout"
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function fail(msg: string): string { return ` ❌ ${msg}`; }
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Format a warning message with a warning indicator.
|
|
65
|
+
*
|
|
66
|
+
* @param msg - The message to format
|
|
67
|
+
* @returns The formatted message with ⚠️ prefix
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* warn("Model may be too slow for production use");
|
|
72
|
+
* // Returns: " ⚠️ Model may be too slow for production use"
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function warn(msg: string): string { return ` ⚠️ ${msg}`; }
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Format an informational message with an info indicator.
|
|
79
|
+
*
|
|
80
|
+
* @param msg - The message to format
|
|
81
|
+
* @returns The formatted message with ℹ️ prefix
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* info("Testing reasoning capability...");
|
|
86
|
+
* // Returns: " ℹ️ Testing reasoning capability..."
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function info(msg: string): string { return ` ℹ️ ${msg}`; }
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Numeric Formatters
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format bytes as a human-readable string with appropriate units.
|
|
97
|
+
*
|
|
98
|
+
* Automatically selects the most appropriate unit (B, KB, MB, GB, TB)
|
|
99
|
+
* and formats the number with one decimal place.
|
|
100
|
+
*
|
|
101
|
+
* @param bytes - The number of bytes to format
|
|
102
|
+
* @returns A human-readable string representation
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* bytesHuman(512); // "512.0B"
|
|
107
|
+
* bytesHuman(1536); // "1.5KB"
|
|
108
|
+
* bytesHuman(1048576); // "1.0MB"
|
|
109
|
+
* bytesHuman(1073741824); // "1.0GB"
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export function bytesHuman(bytes: number): string {
|
|
113
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
114
|
+
let i = 0;
|
|
115
|
+
let b = bytes;
|
|
116
|
+
while (b >= 1024 && i < units.length - 1) { b /= 1024; i++; }
|
|
117
|
+
return `${b.toFixed(1)}${units[i]}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Format milliseconds as a human-readable duration string.
|
|
122
|
+
*
|
|
123
|
+
* Automatically selects the most appropriate unit (ms, s, m)
|
|
124
|
+
* based on the magnitude of the input.
|
|
125
|
+
*
|
|
126
|
+
* @param ms - The duration in milliseconds
|
|
127
|
+
* @returns A human-readable string representation
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* msHuman(500); // "500ms"
|
|
132
|
+
* msHuman(1500); // "1.5s"
|
|
133
|
+
* msHuman(90000); // "1.5m"
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function msHuman(ms: number): string {
|
|
137
|
+
if (ms < 1000) return `${ms.toFixed(0)}ms`;
|
|
138
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
139
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Format bytes as a compact string for status bar display.
|
|
144
|
+
*
|
|
145
|
+
* Uses single-letter suffixes (K, M, G) without spaces,
|
|
146
|
+
* optimized for narrow display contexts like status bars.
|
|
147
|
+
*
|
|
148
|
+
* @param b - The number of bytes to format
|
|
149
|
+
* @returns A compact string representation
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* fmtBytes(512); // "512K"
|
|
154
|
+
* fmtBytes(1048576); // "1.0M"
|
|
155
|
+
* fmtBytes(1073741824); // "1.0G"
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export function fmtBytes(b: number): string {
|
|
159
|
+
if (b === 0) return "0B";
|
|
160
|
+
if (b < 1024) return `${b}B`;
|
|
161
|
+
if (b >= 1073741824) return `${(b / 1073741824).toFixed(1)}G`;
|
|
162
|
+
if (b >= 1048576) return `${(b / 1048576).toFixed(0)}M`;
|
|
163
|
+
return `${(b / 1024).toFixed(0)}K`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Format milliseconds as a compact duration for status bar display.
|
|
168
|
+
*
|
|
169
|
+
* For durations over a minute, displays as `Xm Ys` format.
|
|
170
|
+
* For shorter durations, displays as seconds with one decimal.
|
|
171
|
+
*
|
|
172
|
+
* @param ms - The duration in milliseconds
|
|
173
|
+
* @returns A compact string representation
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* fmtDur(500); // "500ms"
|
|
178
|
+
* fmtDur(1500); // "1.5s"
|
|
179
|
+
* fmtDur(90000); // "1m30s"
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function fmtDur(ms: number): string {
|
|
183
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
184
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
185
|
+
return `${Math.floor(ms / 60000)}m${Math.round((ms % 60000) / 1000)}s`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Calculate and format a percentage.
|
|
190
|
+
*
|
|
191
|
+
* @param used - The used/occupied amount
|
|
192
|
+
* @param total - The total capacity
|
|
193
|
+
* @returns A percentage string with one decimal place
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* pct(512, 1024); // "50.0%"
|
|
198
|
+
* pct(75, 100); // "75.0%"
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export function pct(used: number, total: number): string {
|
|
202
|
+
if (total === 0) return "0.0%";
|
|
203
|
+
return `${((used / total) * 100).toFixed(1)}%`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// String Utilities
|
|
208
|
+
// ============================================================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Truncate a string to a maximum length with an ellipsis suffix.
|
|
212
|
+
*
|
|
213
|
+
* If the string exceeds the maximum length, it is truncated
|
|
214
|
+
* and "..." is appended to indicate truncation.
|
|
215
|
+
*
|
|
216
|
+
* @param s - The string to truncate
|
|
217
|
+
* @param max - Maximum length before truncation
|
|
218
|
+
* @returns The original string or truncated version
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* truncate("Hello, World!", 5); // "He..."
|
|
223
|
+
* truncate("Short", 10); // "Short"
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
export function truncate(s: string, max: number): string {
|
|
227
|
+
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Strip markdown code fences and truncate large content for clean report output.
|
|
232
|
+
*
|
|
233
|
+
* Performs the following transformations:
|
|
234
|
+
* 1. Removes markdown code fence markers (``` and ```lang)
|
|
235
|
+
* 2. Collapses excessive blank lines
|
|
236
|
+
* 3. Detects and truncates HTML content (error pages)
|
|
237
|
+
* 4. Caps output at a maximum number of lines
|
|
238
|
+
*
|
|
239
|
+
* @param s - The string to sanitize
|
|
240
|
+
* @param maxLines - Maximum number of lines to output (default: 40)
|
|
241
|
+
* @returns The sanitized and potentially truncated string
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* sanitizeForReport("```json\n{\"key\": \"value\"}\n```");
|
|
246
|
+
* // Returns: "{\"key\": \"value\"}"
|
|
247
|
+
*
|
|
248
|
+
* sanitizeForReport(largeHtmlContent);
|
|
249
|
+
* // Returns: "<!DOCTYPE html>...\n ℹ️ (HTML response truncated)"
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
export function sanitizeForReport(s: string, maxLines = 40): string {
|
|
253
|
+
let cleaned = s.replace(/^\s*```[a-zA-Z]*[ \t]*\n?/gm, "");
|
|
254
|
+
cleaned = cleaned.replace(/^\s*```[ \t]*\n?/gm, "");
|
|
255
|
+
cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
|
|
256
|
+
|
|
257
|
+
// Detect HTML content (error pages, curl failures) and truncate to first few lines
|
|
258
|
+
// Use strict detection: require a closing tag AND a common HTML opening tag to avoid
|
|
259
|
+
// false positives on code that mentions HTML-like syntax (e.g., "use <Item> from 'react'")
|
|
260
|
+
if (/<!DOCTYPE\b|<html[\s>]/i.test(cleaned) || (/<[a-z][\s\S]*>/i.test(cleaned) && cleaned.includes("</") && /<(?:div|span|p|head|body|html|table|form|script)\b/i.test(cleaned))) {
|
|
261
|
+
const firstLine = cleaned.split("\n")[0];
|
|
262
|
+
return truncate(firstLine, 200) + "\n ℹ️ (HTML response truncated)";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Cap line count for any large output
|
|
266
|
+
const lines = cleaned.split("\n");
|
|
267
|
+
if (lines.length > maxLines) {
|
|
268
|
+
cleaned = lines.slice(0, maxLines).join("\n") + `\n ℹ️ (truncated, ${lines.length - maxLines} more lines)`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return cleaned;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Right-pad a string to a specified length with spaces.
|
|
276
|
+
*
|
|
277
|
+
* If the string is already longer than the target length,
|
|
278
|
+
* it is returned unchanged.
|
|
279
|
+
*
|
|
280
|
+
* @param s - The string to pad
|
|
281
|
+
* @param n - The target length
|
|
282
|
+
* @returns The padded string
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```typescript
|
|
286
|
+
* padRight("test", 10); // "test "
|
|
287
|
+
* padRight("toolong", 5); // "toolong"
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export function padRight(s: string, n: number): string {
|
|
291
|
+
return s + " ".repeat(Math.max(0, n - s.length));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ============================================================================
|
|
295
|
+
// Model Estimation
|
|
296
|
+
// ============================================================================
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Estimate memory usage (in bytes) from parameter size and quantization level.
|
|
300
|
+
*
|
|
301
|
+
* Uses bits-per-parameter based on the quantization format:
|
|
302
|
+
* - FP32/BF16/F16: 16 bits (full precision)
|
|
303
|
+
* - Q8_0: 8 bits
|
|
304
|
+
* - Q5_K_M/Q5_M/Q5_K_S/Q5_0/Q5_1: 5.5 bits (average)
|
|
305
|
+
* - Q4_K_M/Q4_K_S/Q4_0/Q4_1/Q4_M: 4.5 bits (average)
|
|
306
|
+
* - Q3_K_M/Q3_K_S/Q3_K_L: 3.5 bits (average)
|
|
307
|
+
* - Q2_K: 2.5 bits (average)
|
|
308
|
+
* - IQ variants (IQ3_XS, IQ4_XS, etc.): mapped to nearest Q equivalent
|
|
309
|
+
* - Fallback: 5 bits
|
|
310
|
+
*
|
|
311
|
+
* Returns both GPU (VRAM) and CPU (RAM) estimates:
|
|
312
|
+
* - GPU: base size + 10% overhead (KV cache, runtime allocs — weights dominate)
|
|
313
|
+
* - CPU: context-aware overhead — KV cache is pre-allocated at model load
|
|
314
|
+
* and scales linearly with context window. Formula calibrated against
|
|
315
|
+
* real-world Colab CPU observations:
|
|
316
|
+
* 1.5 + (contextLength / 100_000)
|
|
317
|
+
* Without context, falls back to 2.5× (typical mid-range CPU deployment).
|
|
318
|
+
*
|
|
319
|
+
* @param parameterSize - Human-readable parameter count (e.g., "7B", "1.5B", "350M")
|
|
320
|
+
* @param quantizationLevel - Quantization format string (e.g., "Q4_K_M", "BF16")
|
|
321
|
+
* @param contextLength - Model context window in tokens (e.g., 32768, 131072)
|
|
322
|
+
* @returns Object with GPU and CPU estimates in bytes, or undefined if unparseable
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```typescript
|
|
326
|
+
* estimateMemory("7B", "Q4_K_M", 32768); // { gpu: ~3.5GB, cpu: ~5.6GB }
|
|
327
|
+
* estimateMemory("7B", "Q4_K_M", 131072); // { gpu: ~3.5GB, cpu: ~15.8GB }
|
|
328
|
+
* estimateMemory("350M", "BF16", 32768); // { gpu: ~0.7GB, cpu: ~1.1GB }
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
export function estimateMemory(parameterSize: string, quantizationLevel: string, contextLength?: number): { gpu: number; cpu: number } | undefined {
|
|
332
|
+
const params = parseParamCount(parameterSize);
|
|
333
|
+
if (params === undefined) return undefined;
|
|
334
|
+
|
|
335
|
+
const bitsPerParam = bitsPerParamForQuant(quantizationLevel);
|
|
336
|
+
// Base model size in bytes
|
|
337
|
+
const modelBytes = (params * bitsPerParam) / 8;
|
|
338
|
+
|
|
339
|
+
// CPU overhead scales with context window — KV cache is pre-allocated at load
|
|
340
|
+
// Calibrated: nemotron 4B Q4, 131k ctx → 2.8× observed (formula gives 2.82×)
|
|
341
|
+
const cpuMultiplier = contextLength != null
|
|
342
|
+
? 1.5 + (contextLength / 100_000)
|
|
343
|
+
: 2.5; // flat fallback when context unknown
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
gpu: Math.ceil(modelBytes * 1.1), // 10% overhead — GPU: weights dominate
|
|
347
|
+
cpu: Math.ceil(modelBytes * cpuMultiplier), // context-aware — CPU: KV cache dominates
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Parse a human-readable parameter count string to a numeric value.
|
|
353
|
+
*
|
|
354
|
+
* Handles formats like "7B", "1.5B", "350M", "13a" (Apple-style),
|
|
355
|
+
* "70B", "0.6B", "494.03M", etc.
|
|
356
|
+
*
|
|
357
|
+
* @param s - The parameter size string
|
|
358
|
+
* @returns Parameter count as a number, or undefined if unparseable
|
|
359
|
+
*/
|
|
360
|
+
function parseParamCount(s: string): number | undefined {
|
|
361
|
+
if (!s || typeof s !== "string") return undefined;
|
|
362
|
+
const str = s.trim().toLowerCase();
|
|
363
|
+
|
|
364
|
+
// Match: number + suffix (B/b, M/m, T/t, a/apple)
|
|
365
|
+
const match = str.match(/^([\d.]+)\s*([bmt]?|a(?:pple)?)$/);
|
|
366
|
+
if (!match) return undefined;
|
|
367
|
+
|
|
368
|
+
const num = parseFloat(match[1]);
|
|
369
|
+
if (isNaN(num) || num <= 0) return undefined;
|
|
370
|
+
|
|
371
|
+
const suffix = match[2];
|
|
372
|
+
switch (suffix) {
|
|
373
|
+
case "b": return num * 1e9;
|
|
374
|
+
case "m": return num * 1e6;
|
|
375
|
+
case "t": return num * 1e12;
|
|
376
|
+
case "a": return num * 1e9; // Apple-style (e.g., "3a" = 3B parameters)
|
|
377
|
+
case "": return num * 1e9; // Bare number assumed to be billions
|
|
378
|
+
default: return undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Determine bits per parameter based on quantization format string.
|
|
384
|
+
*/
|
|
385
|
+
function bitsPerParamForQuant(quant: string): number {
|
|
386
|
+
const q = quant.toUpperCase().replace(/[-_.]/g, "");
|
|
387
|
+
|
|
388
|
+
if (q.startsWith("FP32") || q === "F32" || q === "TF32") return 32;
|
|
389
|
+
if (q.startsWith("F16") || q === "BF16") return 16;
|
|
390
|
+
if (q.startsWith("Q8")) return 8;
|
|
391
|
+
if (q.startsWith("IQ4")) return 4.5;
|
|
392
|
+
if (q.startsWith("IQ3")) return 3.5;
|
|
393
|
+
if (q.startsWith("IQ2")) return 2.5;
|
|
394
|
+
if (q.startsWith("IQ1")) return 1.75;
|
|
395
|
+
if (q.startsWith("Q5") || q.startsWith("Q6")) return 5.5;
|
|
396
|
+
if (q.startsWith("Q4")) return 4.5;
|
|
397
|
+
if (q.startsWith("Q3")) return 3.5;
|
|
398
|
+
if (q.startsWith("Q2")) return 2.5;
|
|
399
|
+
if (q.startsWith("Q1")) return 1.75;
|
|
400
|
+
|
|
401
|
+
return 5; // conservative fallback
|
|
402
|
+
}
|