@leo000001/opencode-quota-sidebar 4.0.9 → 4.0.12
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/README.md +504 -446
- package/README.zh-CN.md +516 -458
- package/dist/cli.d.ts +31 -0
- package/dist/cli.js +123 -46
- package/dist/cli_render.d.ts +4 -4
- package/dist/cli_render.js +74 -74
- package/dist/cost.d.ts +21 -4
- package/dist/cost.js +493 -264
- package/dist/format.d.ts +5 -5
- package/dist/format.js +288 -287
- package/dist/history_usage.d.ts +15 -9
- package/dist/history_usage.js +28 -22
- package/dist/index.js +15 -1
- package/dist/models_dev_pricing.d.ts +6 -0
- package/dist/models_dev_pricing.js +226 -0
- package/dist/opencode_pricing.d.ts +14 -0
- package/dist/opencode_pricing.js +273 -0
- package/dist/storage.d.ts +3 -3
- package/dist/storage.js +27 -28
- package/dist/storage_parse.d.ts +1 -1
- package/dist/storage_parse.js +51 -45
- package/dist/storage_paths.d.ts +1 -0
- package/dist/storage_paths.js +26 -11
- package/dist/title_apply.d.ts +1 -17
- package/dist/title_apply.js +17 -48
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +29 -15
- package/dist/tui.d.ts +1 -1
- package/dist/tui.tsx +481 -471
- package/dist/tui_helpers.d.ts +5 -3
- package/dist/tui_helpers.js +62 -34
- package/dist/types.d.ts +8 -10
- package/dist/usage.d.ts +9 -6
- package/dist/usage.js +27 -21
- package/dist/usage_service.d.ts +8 -7
- package/dist/usage_service.js +261 -150
- package/package.json +1 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { debug, isRecord } from "./helpers.js";
|
|
4
|
+
function stripJsonComments(input) {
|
|
5
|
+
let output = "";
|
|
6
|
+
let inString = false;
|
|
7
|
+
let escaping = false;
|
|
8
|
+
let lineComment = false;
|
|
9
|
+
let blockComment = false;
|
|
10
|
+
for (let index = 0; index < input.length; index++) {
|
|
11
|
+
const char = input[index];
|
|
12
|
+
const next = input[index + 1];
|
|
13
|
+
if (lineComment) {
|
|
14
|
+
if (char === "\n") {
|
|
15
|
+
lineComment = false;
|
|
16
|
+
output += char;
|
|
17
|
+
}
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (blockComment) {
|
|
21
|
+
if (char === "*" && next === "/") {
|
|
22
|
+
blockComment = false;
|
|
23
|
+
index += 1;
|
|
24
|
+
}
|
|
25
|
+
else if (char === "\n") {
|
|
26
|
+
output += char;
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (inString) {
|
|
31
|
+
output += char;
|
|
32
|
+
if (escaping) {
|
|
33
|
+
escaping = false;
|
|
34
|
+
}
|
|
35
|
+
else if (char === "\\") {
|
|
36
|
+
escaping = true;
|
|
37
|
+
}
|
|
38
|
+
else if (char === '"') {
|
|
39
|
+
inString = false;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (char === '"') {
|
|
44
|
+
inString = true;
|
|
45
|
+
output += char;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === "/" && next === "/") {
|
|
49
|
+
lineComment = true;
|
|
50
|
+
index += 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (char === "/" && next === "*") {
|
|
54
|
+
blockComment = true;
|
|
55
|
+
index += 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
output += char;
|
|
59
|
+
}
|
|
60
|
+
return output;
|
|
61
|
+
}
|
|
62
|
+
function stripTrailingCommas(input) {
|
|
63
|
+
let output = "";
|
|
64
|
+
let inString = false;
|
|
65
|
+
let escaping = false;
|
|
66
|
+
for (let index = 0; index < input.length; index++) {
|
|
67
|
+
const char = input[index];
|
|
68
|
+
if (inString) {
|
|
69
|
+
output += char;
|
|
70
|
+
if (escaping) {
|
|
71
|
+
escaping = false;
|
|
72
|
+
}
|
|
73
|
+
else if (char === "\\") {
|
|
74
|
+
escaping = true;
|
|
75
|
+
}
|
|
76
|
+
else if (char === '"') {
|
|
77
|
+
inString = false;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '"') {
|
|
82
|
+
inString = true;
|
|
83
|
+
output += char;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (char !== ",") {
|
|
87
|
+
output += char;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
let lookahead = index + 1;
|
|
91
|
+
while (lookahead < input.length && /\s/.test(input[lookahead])) {
|
|
92
|
+
lookahead += 1;
|
|
93
|
+
}
|
|
94
|
+
if (input[lookahead] === "}" || input[lookahead] === "]")
|
|
95
|
+
continue;
|
|
96
|
+
output += char;
|
|
97
|
+
}
|
|
98
|
+
return output;
|
|
99
|
+
}
|
|
100
|
+
export function parseJsonc(text) {
|
|
101
|
+
return JSON.parse(stripTrailingCommas(stripJsonComments(text)));
|
|
102
|
+
}
|
|
103
|
+
function providerEntriesFromCollection(value) {
|
|
104
|
+
const providers = [];
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
for (const item of value) {
|
|
107
|
+
if (!isRecord(item))
|
|
108
|
+
continue;
|
|
109
|
+
const providerID = typeof item.id === "string" ? item.id : undefined;
|
|
110
|
+
if (!providerID)
|
|
111
|
+
continue;
|
|
112
|
+
providers.push({ providerKey: providerID, providerID, value: item });
|
|
113
|
+
}
|
|
114
|
+
return providers;
|
|
115
|
+
}
|
|
116
|
+
if (!isRecord(value))
|
|
117
|
+
return providers;
|
|
118
|
+
for (const [providerKey, providerValue] of Object.entries(value)) {
|
|
119
|
+
if (!isRecord(providerValue))
|
|
120
|
+
continue;
|
|
121
|
+
const providerID = typeof providerValue.id === "string" ? providerValue.id : providerKey;
|
|
122
|
+
providers.push({ providerKey, providerID, value: providerValue });
|
|
123
|
+
}
|
|
124
|
+
return providers;
|
|
125
|
+
}
|
|
126
|
+
function mergeRecord(base, next) {
|
|
127
|
+
if (!base)
|
|
128
|
+
return next;
|
|
129
|
+
if (!next)
|
|
130
|
+
return base;
|
|
131
|
+
return {
|
|
132
|
+
...base,
|
|
133
|
+
...next,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function mergeDeepRecord(base, next) {
|
|
137
|
+
if (!base)
|
|
138
|
+
return next;
|
|
139
|
+
if (!next)
|
|
140
|
+
return base;
|
|
141
|
+
const merged = { ...base };
|
|
142
|
+
for (const [key, value] of Object.entries(next)) {
|
|
143
|
+
const existing = merged[key];
|
|
144
|
+
merged[key] =
|
|
145
|
+
isRecord(existing) && isRecord(value)
|
|
146
|
+
? mergeDeepRecord(existing, value)
|
|
147
|
+
: value;
|
|
148
|
+
}
|
|
149
|
+
return merged;
|
|
150
|
+
}
|
|
151
|
+
function mergeCost(base, next) {
|
|
152
|
+
if (isRecord(base) && isRecord(next)) {
|
|
153
|
+
return mergeDeepRecord(base, next);
|
|
154
|
+
}
|
|
155
|
+
return next ?? base;
|
|
156
|
+
}
|
|
157
|
+
function extractModelsFromProvider(provider) {
|
|
158
|
+
const models = provider.value.models;
|
|
159
|
+
const entries = [];
|
|
160
|
+
const pushModel = (modelKey, value) => {
|
|
161
|
+
const modelID = typeof value.id === "string" ? value.id : modelKey;
|
|
162
|
+
if (!modelID)
|
|
163
|
+
return;
|
|
164
|
+
entries.push({
|
|
165
|
+
providerKey: provider.providerKey,
|
|
166
|
+
providerID: provider.providerID,
|
|
167
|
+
modelID,
|
|
168
|
+
modelKey,
|
|
169
|
+
cost: value.cost,
|
|
170
|
+
options: isRecord(value.options) ? value.options : undefined,
|
|
171
|
+
headers: isRecord(value.headers) ? value.headers : undefined,
|
|
172
|
+
api: isRecord(value.api) ? value.api : undefined,
|
|
173
|
+
limit: isRecord(value.limit) ? value.limit : undefined,
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
if (Array.isArray(models)) {
|
|
177
|
+
for (const item of models) {
|
|
178
|
+
if (!isRecord(item))
|
|
179
|
+
continue;
|
|
180
|
+
pushModel(typeof item.id === "string" ? item.id : undefined, item);
|
|
181
|
+
}
|
|
182
|
+
return entries;
|
|
183
|
+
}
|
|
184
|
+
if (!isRecord(models))
|
|
185
|
+
return entries;
|
|
186
|
+
for (const [modelKey, modelValue] of Object.entries(models)) {
|
|
187
|
+
if (!isRecord(modelValue))
|
|
188
|
+
continue;
|
|
189
|
+
pushModel(modelKey, modelValue);
|
|
190
|
+
}
|
|
191
|
+
return entries;
|
|
192
|
+
}
|
|
193
|
+
function mergedModelMapKey(model) {
|
|
194
|
+
return `${model.providerKey || model.providerID}:${model.modelKey || model.modelID}`;
|
|
195
|
+
}
|
|
196
|
+
function mergedProviderID(existing, next) {
|
|
197
|
+
if (next.providerKey &&
|
|
198
|
+
next.providerID === next.providerKey &&
|
|
199
|
+
existing?.providerID) {
|
|
200
|
+
return existing.providerID;
|
|
201
|
+
}
|
|
202
|
+
return (next.providerID || existing?.providerID || next.providerKey || "unknown");
|
|
203
|
+
}
|
|
204
|
+
function mergedModelID(existing, next) {
|
|
205
|
+
if (next.modelKey && next.modelID === next.modelKey && existing?.modelID) {
|
|
206
|
+
return existing.modelID;
|
|
207
|
+
}
|
|
208
|
+
return next.modelID || existing?.modelID || next.modelKey || "unknown";
|
|
209
|
+
}
|
|
210
|
+
export function extractOpenCodePricingModels(config) {
|
|
211
|
+
if (!isRecord(config))
|
|
212
|
+
return [];
|
|
213
|
+
const providers = [
|
|
214
|
+
...providerEntriesFromCollection(config.provider),
|
|
215
|
+
...providerEntriesFromCollection(config.providers),
|
|
216
|
+
];
|
|
217
|
+
const merged = new Map();
|
|
218
|
+
for (const provider of providers) {
|
|
219
|
+
for (const model of extractModelsFromProvider(provider)) {
|
|
220
|
+
const key = mergedModelMapKey(model);
|
|
221
|
+
const existing = merged.get(key);
|
|
222
|
+
merged.set(key, {
|
|
223
|
+
providerKey: model.providerKey ?? existing?.providerKey,
|
|
224
|
+
providerID: mergedProviderID(existing, model),
|
|
225
|
+
modelID: mergedModelID(existing, model),
|
|
226
|
+
modelKey: model.modelKey ?? existing?.modelKey,
|
|
227
|
+
cost: mergeCost(existing?.cost, model.cost),
|
|
228
|
+
options: mergeRecord(existing?.options, model.options),
|
|
229
|
+
headers: mergeRecord(existing?.headers, model.headers),
|
|
230
|
+
api: mergeRecord(existing?.api, model.api),
|
|
231
|
+
limit: mergeRecord(existing?.limit, model.limit),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return [...merged.values()];
|
|
236
|
+
}
|
|
237
|
+
export async function loadOpenCodePricingModels(paths) {
|
|
238
|
+
const seen = new Set();
|
|
239
|
+
const merged = new Map();
|
|
240
|
+
for (const originalPath of paths) {
|
|
241
|
+
const filePath = path.resolve(originalPath);
|
|
242
|
+
const key = process.platform === "win32" ? filePath.toLowerCase() : filePath;
|
|
243
|
+
if (seen.has(key))
|
|
244
|
+
continue;
|
|
245
|
+
seen.add(key);
|
|
246
|
+
const stat = await fs.stat(filePath).catch(() => undefined);
|
|
247
|
+
if (!stat?.isFile())
|
|
248
|
+
continue;
|
|
249
|
+
const parsed = await fs
|
|
250
|
+
.readFile(filePath, "utf8")
|
|
251
|
+
.then((text) => parseJsonc(text))
|
|
252
|
+
.catch((error) => {
|
|
253
|
+
debug(`loadOpenCodePricingModels skipped ${filePath}: ${String(error)}`);
|
|
254
|
+
return undefined;
|
|
255
|
+
});
|
|
256
|
+
for (const model of extractOpenCodePricingModels(parsed)) {
|
|
257
|
+
const mergedKey = mergedModelMapKey(model);
|
|
258
|
+
const existing = merged.get(mergedKey);
|
|
259
|
+
merged.set(mergedKey, {
|
|
260
|
+
providerKey: model.providerKey ?? existing?.providerKey,
|
|
261
|
+
providerID: mergedProviderID(existing, model),
|
|
262
|
+
modelID: mergedModelID(existing, model),
|
|
263
|
+
modelKey: model.modelKey ?? existing?.modelKey,
|
|
264
|
+
cost: mergeCost(existing?.cost, model.cost),
|
|
265
|
+
options: mergeRecord(existing?.options, model.options),
|
|
266
|
+
headers: mergeRecord(existing?.headers, model.headers),
|
|
267
|
+
api: mergeRecord(existing?.api, model.api),
|
|
268
|
+
limit: mergeRecord(existing?.limit, model.limit),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return [...merged.values()];
|
|
273
|
+
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { dateKeyFromTimestamp, normalizeTimestampMs } from
|
|
2
|
-
import { authFilePath, resolveOpencodeConfigDir, resolveOpencodeDataDir, stateFilePath } from
|
|
3
|
-
import type { CachedSessionUsage, IncrementalCursor, QuotaSidebarConfig, QuotaSidebarState, SessionState } from
|
|
1
|
+
import { dateKeyFromTimestamp, normalizeTimestampMs } from "./storage_dates.js";
|
|
2
|
+
import { authFilePath, resolveOpencodeConfigDir, resolveOpencodeDataDir, stateFilePath } from "./storage_paths.js";
|
|
3
|
+
import type { CachedSessionUsage, IncrementalCursor, QuotaSidebarConfig, QuotaSidebarState, SessionState } from "./types.js";
|
|
4
4
|
export { authFilePath, dateKeyFromTimestamp, normalizeTimestampMs, resolveOpencodeConfigDir, resolveOpencodeDataDir, stateFilePath, };
|
|
5
5
|
export declare function quotaConfigPaths(worktree: string, directory: string): string[];
|
|
6
6
|
export declare const defaultConfig: QuotaSidebarConfig;
|
package/dist/storage.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { asBoolean, asNumber, debug, isRecord, mapConcurrent, swallow, } from
|
|
4
|
-
import { discoverChunks, readDayChunk, safeWriteFile, writeDayChunk, } from
|
|
5
|
-
import { dateKeyFromTimestamp, dateKeysInRange, dateStartFromKey, isDateKey, normalizeTimestampMs, } from
|
|
6
|
-
import { parseQuotaCache } from
|
|
7
|
-
import { authFilePath, chunkRootPathFromStateFile, resolveOpencodeConfigDir, resolveOpencodeDataDir, stateFilePath, } from
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { asBoolean, asNumber, debug, isRecord, mapConcurrent, swallow, } from "./helpers.js";
|
|
4
|
+
import { discoverChunks, readDayChunk, safeWriteFile, writeDayChunk, } from "./storage_chunks.js";
|
|
5
|
+
import { dateKeyFromTimestamp, dateKeysInRange, dateStartFromKey, isDateKey, normalizeTimestampMs, } from "./storage_dates.js";
|
|
6
|
+
import { parseQuotaCache } from "./storage_parse.js";
|
|
7
|
+
import { authFilePath, chunkRootPathFromStateFile, resolveOpencodeConfigDir, resolveOpencodeDataDir, stateFilePath, } from "./storage_paths.js";
|
|
8
8
|
export { authFilePath, dateKeyFromTimestamp, normalizeTimestampMs, resolveOpencodeConfigDir, resolveOpencodeDataDir, stateFilePath, };
|
|
9
9
|
export function quotaConfigPaths(worktree, directory) {
|
|
10
10
|
const configDir = resolveOpencodeConfigDir();
|
|
11
11
|
const configOverride = process.env.OPENCODE_QUOTA_CONFIG?.trim();
|
|
12
12
|
return [
|
|
13
|
-
path.join(configDir,
|
|
14
|
-
path.join(worktree,
|
|
15
|
-
path.join(directory,
|
|
16
|
-
path.join(worktree,
|
|
17
|
-
path.join(directory,
|
|
13
|
+
path.join(configDir, "quota-sidebar.config.json"),
|
|
14
|
+
path.join(worktree, "quota-sidebar.config.json"),
|
|
15
|
+
path.join(directory, "quota-sidebar.config.json"),
|
|
16
|
+
path.join(worktree, ".opencode", "quota-sidebar.config.json"),
|
|
17
|
+
path.join(directory, ".opencode", "quota-sidebar.config.json"),
|
|
18
18
|
...(configOverride ? [path.resolve(configOverride)] : []),
|
|
19
19
|
];
|
|
20
20
|
}
|
|
@@ -23,8 +23,7 @@ export const defaultConfig = {
|
|
|
23
23
|
sidebar: {
|
|
24
24
|
enabled: true,
|
|
25
25
|
width: 36,
|
|
26
|
-
titleMode:
|
|
27
|
-
multilineTitle: true,
|
|
26
|
+
titleMode: "auto",
|
|
28
27
|
showCost: true,
|
|
29
28
|
showQuota: true,
|
|
30
29
|
wrapQuotaLines: true,
|
|
@@ -101,12 +100,11 @@ export async function loadConfig(paths) {
|
|
|
101
100
|
sidebar: {
|
|
102
101
|
enabled: asBoolean(sidebar.enabled, base.sidebar.enabled),
|
|
103
102
|
width: Math.max(20, Math.min(60, asNumber(sidebar.width, base.sidebar.width))),
|
|
104
|
-
titleMode: sidebar.titleMode ===
|
|
105
|
-
sidebar.titleMode ===
|
|
106
|
-
sidebar.titleMode ===
|
|
103
|
+
titleMode: sidebar.titleMode === "compact" ||
|
|
104
|
+
sidebar.titleMode === "multiline" ||
|
|
105
|
+
sidebar.titleMode === "auto"
|
|
107
106
|
? sidebar.titleMode
|
|
108
|
-
: (base.sidebar.titleMode ??
|
|
109
|
-
multilineTitle: asBoolean(sidebar.multilineTitle, base.sidebar.multilineTitle ?? true),
|
|
107
|
+
: (base.sidebar.titleMode ?? "auto"),
|
|
110
108
|
showCost: asBoolean(sidebar.showCost, base.sidebar.showCost),
|
|
111
109
|
showQuota: asBoolean(sidebar.showQuota, base.sidebar.showQuota),
|
|
112
110
|
wrapQuotaLines: asBoolean(sidebar.wrapQuotaLines, base.sidebar.wrapQuotaLines),
|
|
@@ -142,17 +140,17 @@ export async function loadConfig(paths) {
|
|
|
142
140
|
let config = defaultConfig;
|
|
143
141
|
for (const originalPath of paths) {
|
|
144
142
|
const filePath = path.resolve(originalPath);
|
|
145
|
-
const key = process.platform ===
|
|
143
|
+
const key = process.platform === "win32" ? filePath.toLowerCase() : filePath;
|
|
146
144
|
if (seen.has(key))
|
|
147
145
|
continue;
|
|
148
146
|
seen.add(key);
|
|
149
|
-
const stat = await fs.stat(filePath).catch(swallow(
|
|
147
|
+
const stat = await fs.stat(filePath).catch(swallow("loadConfig:stat"));
|
|
150
148
|
if (!stat || !stat.isFile())
|
|
151
149
|
continue;
|
|
152
150
|
const parsed = await fs
|
|
153
|
-
.readFile(filePath,
|
|
151
|
+
.readFile(filePath, "utf8")
|
|
154
152
|
.then((value) => JSON.parse(value))
|
|
155
|
-
.catch(swallow(
|
|
153
|
+
.catch(swallow("loadConfig:read"));
|
|
156
154
|
if (!isRecord(parsed))
|
|
157
155
|
continue;
|
|
158
156
|
config = mergeLayer(config, parsed);
|
|
@@ -165,12 +163,12 @@ async function loadVersion2State(raw, statePath) {
|
|
|
165
163
|
const titleEnabled = asBoolean(raw.titleEnabled, true);
|
|
166
164
|
const quotaCache = parseQuotaCache(raw.quotaCache);
|
|
167
165
|
const rootPath = chunkRootPathFromStateFile(statePath);
|
|
168
|
-
const hasSessionDateMap = Object.prototype.hasOwnProperty.call(raw,
|
|
166
|
+
const hasSessionDateMap = Object.prototype.hasOwnProperty.call(raw, "sessionDateMap");
|
|
169
167
|
const sessionDateMapRaw = isRecord(raw.sessionDateMap)
|
|
170
168
|
? raw.sessionDateMap
|
|
171
169
|
: {};
|
|
172
170
|
const sessionDateMap = Object.entries(sessionDateMapRaw).reduce((acc, [sessionID, value]) => {
|
|
173
|
-
if (typeof value !==
|
|
171
|
+
if (typeof value !== "string")
|
|
174
172
|
return acc;
|
|
175
173
|
if (!isDateKey(value))
|
|
176
174
|
return acc;
|
|
@@ -181,7 +179,7 @@ async function loadVersion2State(raw, statePath) {
|
|
|
181
179
|
? raw.deletedSessionDateMap
|
|
182
180
|
: {};
|
|
183
181
|
const deletedSessionDateMap = Object.entries(deletedSessionDateMapRaw).reduce((acc, [sessionID, value]) => {
|
|
184
|
-
if (typeof value !==
|
|
182
|
+
if (typeof value !== "string")
|
|
185
183
|
return acc;
|
|
186
184
|
if (!isDateKey(value))
|
|
187
185
|
return acc;
|
|
@@ -228,9 +226,9 @@ async function loadVersion2State(raw, statePath) {
|
|
|
228
226
|
}
|
|
229
227
|
export async function loadState(statePath) {
|
|
230
228
|
const raw = await fs
|
|
231
|
-
.readFile(statePath,
|
|
229
|
+
.readFile(statePath, "utf8")
|
|
232
230
|
.then((value) => JSON.parse(value))
|
|
233
|
-
.catch(swallow(
|
|
231
|
+
.catch(swallow("loadState"));
|
|
234
232
|
if (!isRecord(raw))
|
|
235
233
|
return defaultState();
|
|
236
234
|
if (raw.version === 2)
|
|
@@ -495,6 +493,7 @@ export async function updateSessionsInDayChunks(statePath, updates) {
|
|
|
495
493
|
...existing,
|
|
496
494
|
usage: item.usage,
|
|
497
495
|
cursor: item.cursor,
|
|
496
|
+
dirty: false,
|
|
498
497
|
};
|
|
499
498
|
changed = true;
|
|
500
499
|
}
|
package/dist/storage_parse.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { QuotaSnapshot, SessionState } from
|
|
1
|
+
import type { QuotaSnapshot, SessionState } from "./types.js";
|
|
2
2
|
export declare function parseSessionState(value: unknown): SessionState | undefined;
|
|
3
3
|
export declare function parseQuotaCache(value: unknown): Record<string, QuotaSnapshot>;
|
package/dist/storage_parse.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { asNumber, isRecord } from
|
|
2
|
-
import { isSupportedQuotaSnapshot } from
|
|
3
|
-
import { normalizeTimestampMs } from
|
|
1
|
+
import { asNumber, isRecord } from "./helpers.js";
|
|
2
|
+
import { isSupportedQuotaSnapshot } from "./supported_quota.js";
|
|
3
|
+
import { normalizeTimestampMs } from "./storage_dates.js";
|
|
4
4
|
function parseSessionTitleState(value) {
|
|
5
5
|
if (!isRecord(value))
|
|
6
6
|
return undefined;
|
|
7
|
-
if (typeof value.baseTitle !==
|
|
7
|
+
if (typeof value.baseTitle !== "string")
|
|
8
8
|
return undefined;
|
|
9
9
|
if (value.lastAppliedTitle !== undefined &&
|
|
10
|
-
typeof value.lastAppliedTitle !==
|
|
10
|
+
typeof value.lastAppliedTitle !== "string") {
|
|
11
11
|
return undefined;
|
|
12
12
|
}
|
|
13
13
|
return {
|
|
@@ -69,13 +69,13 @@ function parseRecentProviders(value) {
|
|
|
69
69
|
const parsed = value
|
|
70
70
|
.filter((item) => isRecord(item))
|
|
71
71
|
.map((item) => ({
|
|
72
|
-
providerID: typeof item.providerID ===
|
|
72
|
+
providerID: typeof item.providerID === "string" && item.providerID
|
|
73
73
|
? item.providerID
|
|
74
74
|
: undefined,
|
|
75
75
|
completedAt: asNumber(item.completedAt),
|
|
76
76
|
}))
|
|
77
|
-
.filter((item) => typeof item.providerID ===
|
|
78
|
-
typeof item.completedAt ===
|
|
77
|
+
.filter((item) => typeof item.providerID === "string" &&
|
|
78
|
+
typeof item.completedAt === "number" &&
|
|
79
79
|
Number.isFinite(item.completedAt));
|
|
80
80
|
return parsed.length > 0 ? parsed : undefined;
|
|
81
81
|
}
|
|
@@ -92,6 +92,12 @@ function parseCachedUsage(value) {
|
|
|
92
92
|
}, {});
|
|
93
93
|
return {
|
|
94
94
|
billingVersion: asNumber(value.billingVersion),
|
|
95
|
+
pricingFingerprint: typeof value.pricingFingerprint === "string"
|
|
96
|
+
? value.pricingFingerprint
|
|
97
|
+
: undefined,
|
|
98
|
+
pricingKeys: Array.isArray(value.pricingKeys)
|
|
99
|
+
? value.pricingKeys.filter((item) => typeof item === "string" && !!item)
|
|
100
|
+
: undefined,
|
|
95
101
|
input: asNumber(value.input, 0),
|
|
96
102
|
output: asNumber(value.output, 0),
|
|
97
103
|
reasoning: asNumber(value.reasoning, 0),
|
|
@@ -113,81 +119,81 @@ function parseQuotaSnapshot(value) {
|
|
|
113
119
|
if (!checkedAt)
|
|
114
120
|
return undefined;
|
|
115
121
|
const status = value.status;
|
|
116
|
-
if (status !==
|
|
117
|
-
status !==
|
|
118
|
-
status !==
|
|
119
|
-
status !==
|
|
122
|
+
if (status !== "ok" &&
|
|
123
|
+
status !== "unavailable" &&
|
|
124
|
+
status !== "unsupported" &&
|
|
125
|
+
status !== "error") {
|
|
120
126
|
return undefined;
|
|
121
127
|
}
|
|
122
|
-
const label = typeof value.label ===
|
|
123
|
-
const adapterID = typeof value.adapterID ===
|
|
124
|
-
const shortLabel = typeof value.shortLabel ===
|
|
125
|
-
const sortOrder = typeof value.sortOrder ===
|
|
128
|
+
const label = typeof value.label === "string" ? value.label : "";
|
|
129
|
+
const adapterID = typeof value.adapterID === "string" ? value.adapterID : undefined;
|
|
130
|
+
const shortLabel = typeof value.shortLabel === "string" ? value.shortLabel : undefined;
|
|
131
|
+
const sortOrder = typeof value.sortOrder === "number" ? value.sortOrder : undefined;
|
|
126
132
|
const balance = isRecord(value.balance)
|
|
127
133
|
? {
|
|
128
|
-
amount: typeof value.balance.amount ===
|
|
129
|
-
currency: typeof value.balance.currency ===
|
|
134
|
+
amount: typeof value.balance.amount === "number" ? value.balance.amount : 0,
|
|
135
|
+
currency: typeof value.balance.currency === "string"
|
|
130
136
|
? value.balance.currency
|
|
131
|
-
:
|
|
137
|
+
: "$",
|
|
132
138
|
}
|
|
133
139
|
: undefined;
|
|
134
140
|
const windows = Array.isArray(value.windows)
|
|
135
141
|
? value.windows
|
|
136
142
|
.filter((window) => isRecord(window))
|
|
137
143
|
.map((window) => ({
|
|
138
|
-
label: typeof window.label ===
|
|
139
|
-
showPercent: typeof window.showPercent ===
|
|
144
|
+
label: typeof window.label === "string" ? window.label : "",
|
|
145
|
+
showPercent: typeof window.showPercent === "boolean"
|
|
140
146
|
? window.showPercent
|
|
141
147
|
: undefined,
|
|
142
|
-
resetLabel: typeof window.resetLabel ===
|
|
148
|
+
resetLabel: typeof window.resetLabel === "string"
|
|
143
149
|
? window.resetLabel
|
|
144
150
|
: undefined,
|
|
145
|
-
note: typeof window.note ===
|
|
146
|
-
remainingPercent: typeof window.remainingPercent ===
|
|
151
|
+
note: typeof window.note === "string" ? window.note : undefined,
|
|
152
|
+
remainingPercent: typeof window.remainingPercent === "number"
|
|
147
153
|
? window.remainingPercent
|
|
148
154
|
: undefined,
|
|
149
|
-
usedPercent: typeof window.usedPercent ===
|
|
155
|
+
usedPercent: typeof window.usedPercent === "number"
|
|
150
156
|
? window.usedPercent
|
|
151
157
|
: undefined,
|
|
152
|
-
resetAt: typeof window.resetAt ===
|
|
158
|
+
resetAt: typeof window.resetAt === "string" ? window.resetAt : undefined,
|
|
153
159
|
}))
|
|
154
160
|
.filter((window) => window.label || window.remainingPercent !== undefined)
|
|
155
161
|
: undefined;
|
|
156
162
|
const stale = isRecord(value.stale)
|
|
157
163
|
? (() => {
|
|
158
|
-
const staleReasonKind = value.stale.staleReasonKind ===
|
|
159
|
-
value.stale.staleReasonKind ===
|
|
160
|
-
value.stale.staleReasonKind ===
|
|
161
|
-
value.stale.staleReasonKind ===
|
|
162
|
-
value.stale.staleReasonKind ===
|
|
163
|
-
value.stale.staleReasonKind ===
|
|
164
|
+
const staleReasonKind = value.stale.staleReasonKind === "timeout" ||
|
|
165
|
+
value.stale.staleReasonKind === "network" ||
|
|
166
|
+
value.stale.staleReasonKind === "http_5xx" ||
|
|
167
|
+
value.stale.staleReasonKind === "http_transient" ||
|
|
168
|
+
value.stale.staleReasonKind === "invalid_response" ||
|
|
169
|
+
value.stale.staleReasonKind === "unknown"
|
|
164
170
|
? value.stale.staleReasonKind
|
|
165
|
-
:
|
|
171
|
+
: "unknown";
|
|
166
172
|
return {
|
|
167
|
-
staleAt: typeof value.stale.staleAt ===
|
|
168
|
-
staleReason: typeof value.stale.staleReason ===
|
|
173
|
+
staleAt: typeof value.stale.staleAt === "number" ? value.stale.staleAt : 0,
|
|
174
|
+
staleReason: typeof value.stale.staleReason === "string"
|
|
169
175
|
? value.stale.staleReason
|
|
170
|
-
:
|
|
176
|
+
: "",
|
|
171
177
|
staleReasonKind,
|
|
172
178
|
};
|
|
173
179
|
})()
|
|
174
180
|
: undefined;
|
|
175
181
|
const parsed = {
|
|
176
|
-
providerID: typeof value.providerID ===
|
|
182
|
+
providerID: typeof value.providerID === "string" ? value.providerID : label,
|
|
177
183
|
adapterID,
|
|
178
184
|
label,
|
|
179
185
|
shortLabel,
|
|
180
186
|
sortOrder,
|
|
181
187
|
status,
|
|
182
188
|
checkedAt,
|
|
183
|
-
remainingPercent: typeof value.remainingPercent ===
|
|
189
|
+
remainingPercent: typeof value.remainingPercent === "number"
|
|
184
190
|
? value.remainingPercent
|
|
185
191
|
: undefined,
|
|
186
|
-
usedPercent: typeof value.usedPercent ===
|
|
187
|
-
resetAt: typeof value.resetAt ===
|
|
188
|
-
expiresAt: typeof value.expiresAt ===
|
|
192
|
+
usedPercent: typeof value.usedPercent === "number" ? value.usedPercent : undefined,
|
|
193
|
+
resetAt: typeof value.resetAt === "string" ? value.resetAt : undefined,
|
|
194
|
+
expiresAt: typeof value.expiresAt === "string" ? value.expiresAt : undefined,
|
|
189
195
|
balance,
|
|
190
|
-
note: typeof value.note ===
|
|
196
|
+
note: typeof value.note === "string" ? value.note : undefined,
|
|
191
197
|
windows,
|
|
192
198
|
stale: stale && stale.staleAt > 0 && stale.staleReason ? stale : undefined,
|
|
193
199
|
};
|
|
@@ -223,10 +229,10 @@ function parseCursor(value) {
|
|
|
223
229
|
return undefined;
|
|
224
230
|
const idsRaw = value.lastMessageIdsAtTime;
|
|
225
231
|
const lastMessageIdsAtTime = Array.isArray(idsRaw)
|
|
226
|
-
? idsRaw.filter((item) => typeof item ===
|
|
232
|
+
? idsRaw.filter((item) => typeof item === "string" && !!item)
|
|
227
233
|
: undefined;
|
|
228
234
|
return {
|
|
229
|
-
lastMessageId: typeof value.lastMessageId ===
|
|
235
|
+
lastMessageId: typeof value.lastMessageId === "string" ? value.lastMessageId : undefined,
|
|
230
236
|
lastMessageTime: asNumber(value.lastMessageTime),
|
|
231
237
|
lastMessageIdsAtTime: lastMessageIdsAtTime && lastMessageIdsAtTime.length
|
|
232
238
|
? Array.from(new Set(lastMessageIdsAtTime)).sort()
|
|
@@ -245,7 +251,7 @@ export function parseSessionState(value) {
|
|
|
245
251
|
return {
|
|
246
252
|
...title,
|
|
247
253
|
createdAt,
|
|
248
|
-
parentID: typeof value.parentID ===
|
|
254
|
+
parentID: typeof value.parentID === "string" ? value.parentID : undefined,
|
|
249
255
|
expiryToastShown: value.expiryToastShown === true,
|
|
250
256
|
usage: parseCachedUsage(value.usage),
|
|
251
257
|
dirty: value.dirty === true,
|
package/dist/storage_paths.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare function resolveOpencodeDataDir(): string;
|
|
|
15
15
|
* Uses XDG config conventions with optional plugin-scoped override.
|
|
16
16
|
*/
|
|
17
17
|
export declare function resolveOpencodeConfigDir(): string;
|
|
18
|
+
export declare function opencodeConfigPaths(worktree: string, directory: string): string[];
|
|
18
19
|
export declare function stateFilePath(dataDir: string): string;
|
|
19
20
|
export declare function authFilePath(dataDir: string): string;
|
|
20
21
|
export declare function chunkRootPathFromStateFile(statePath: string): string;
|