omniagent 0.1.6 → 0.1.8
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 +25 -1
- package/dist/claude-Dmv_YFKX.js +146 -0
- package/dist/cli.js +1566 -12
- package/dist/codex-Cl1dWwMk.js +274 -0
- package/dist/gemini-CskI3Qjp.js +168 -0
- package/dist/pty-CZBSAJzE.js +362 -0
- package/package.json +3 -1
- package/CLAUDE.md +0 -68
- package/CONTRIBUTING.md +0 -56
- package/biome.json +0 -41
- package/tasks.md +0 -25
- package/tsconfig.json +0 -13
- package/vite.config.ts +0 -22
- package/vitest.config.ts +0 -10
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { c as cleanControlOutput, b as parsePercentRemaining, m as makeUsageLimit, d as parseResetText } from "./cli.js";
|
|
2
|
+
import { r as runPtyScenario, t as typeTextSteps, e as enterKey } from "./pty-CZBSAJzE.js";
|
|
3
|
+
const CODEX_WINDOWS = [
|
|
4
|
+
["main", "5h", "main5hLimit"],
|
|
5
|
+
["main", "weekly", "mainWeeklyLimit"],
|
|
6
|
+
["spark", "5h", "spark5hLimit"],
|
|
7
|
+
["spark", "weekly", "sparkWeeklyLimit"]
|
|
8
|
+
];
|
|
9
|
+
const CLEAR_LINE = "";
|
|
10
|
+
async function extractCodexUsage(context) {
|
|
11
|
+
const command = context.command ?? context.launch?.command ?? "codex";
|
|
12
|
+
const ptyResult = await runPtyScenario({
|
|
13
|
+
command,
|
|
14
|
+
args: context.launch?.args ?? ["--no-alt-screen"],
|
|
15
|
+
cwd: context.repoRoot,
|
|
16
|
+
cols: 100,
|
|
17
|
+
rows: 40,
|
|
18
|
+
timeoutMs: context.launch?.timeoutMs ?? 6e4,
|
|
19
|
+
signal: context.signal,
|
|
20
|
+
debug: context.debug,
|
|
21
|
+
steps: [
|
|
22
|
+
{ waitFor: isCodexPromptReady, waitForTimeoutMs: 1e4 },
|
|
23
|
+
...typeTextSteps("/status", 20),
|
|
24
|
+
{ write: enterKey() },
|
|
25
|
+
{
|
|
26
|
+
waitFor: hasCodexStatusResponse,
|
|
27
|
+
waitForTimeoutMs: 15e3,
|
|
28
|
+
capture: "status",
|
|
29
|
+
captureWaitMs: 500
|
|
30
|
+
},
|
|
31
|
+
{ waitMs: 5e3, skipIf: hasCodexStatusLimits },
|
|
32
|
+
{ write: `${CLEAR_LINE}/status${enterKey()}`, skipIf: hasCodexStatusLimits },
|
|
33
|
+
{
|
|
34
|
+
waitFor: hasCodexStatusLimits,
|
|
35
|
+
waitForTimeoutMs: 15e3,
|
|
36
|
+
skipIf: hasCodexStatusLimits,
|
|
37
|
+
optional: true,
|
|
38
|
+
capture: "statusRetry",
|
|
39
|
+
captureWaitMs: 500
|
|
40
|
+
},
|
|
41
|
+
{ write: `${CLEAR_LINE}/exit${enterKey()}` },
|
|
42
|
+
{ waitMs: 500 }
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
const parsed = selectCodexStatus(ptyResult);
|
|
46
|
+
const result = buildCodexUsageResult(parsed, {
|
|
47
|
+
targetId: context.targetId,
|
|
48
|
+
displayName: context.displayName,
|
|
49
|
+
now: context.now,
|
|
50
|
+
command
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
...result,
|
|
54
|
+
debug: ptyResult.debug.length > 0 ? ptyResult.debug : void 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function selectCodexStatus(result) {
|
|
58
|
+
const snapshots = [
|
|
59
|
+
result.snapshots.statusRetry,
|
|
60
|
+
result,
|
|
61
|
+
result.snapshots.status
|
|
62
|
+
];
|
|
63
|
+
for (const snapshot of snapshots) {
|
|
64
|
+
if (snapshot == null) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const content of [`${snapshot.screen}
|
|
68
|
+
${snapshot.raw}`, snapshot.screen, snapshot.raw]) {
|
|
69
|
+
const parsed = parseCodexStatus(cleanControlOutput(content));
|
|
70
|
+
if (parsed.main5hLimit || parsed.mainWeeklyLimit) {
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const fallback = result.snapshots.statusRetry ?? result.snapshots.status ?? result;
|
|
76
|
+
return parseCodexStatus(cleanControlOutput(`${fallback.screen}
|
|
77
|
+
${fallback.raw}`));
|
|
78
|
+
}
|
|
79
|
+
function isCodexPromptReady(snapshot) {
|
|
80
|
+
const cleanedOutput = cleanControlOutput(`${snapshot.screen}
|
|
81
|
+
${snapshot.raw}`);
|
|
82
|
+
return /(?:\u203a|>)\s/.test(cleanedOutput) && /\bContext\b/i.test(cleanedOutput);
|
|
83
|
+
}
|
|
84
|
+
function hasCodexStatusLimits(snapshot) {
|
|
85
|
+
const cleanedOutput = cleanControlOutput(`${snapshot.screen}
|
|
86
|
+
${snapshot.raw}`);
|
|
87
|
+
const parsed = parseCodexStatus(cleanedOutput);
|
|
88
|
+
return Boolean(parsed.main5hLimit && parsed.mainWeeklyLimit);
|
|
89
|
+
}
|
|
90
|
+
function hasCodexStatusResponse(snapshot) {
|
|
91
|
+
const cleanedOutput = cleanControlOutput(`${snapshot.screen}
|
|
92
|
+
${snapshot.raw}`);
|
|
93
|
+
const parsed = parseCodexStatus(cleanedOutput);
|
|
94
|
+
return Boolean(parsed.main5hLimit || parsed.mainWeeklyLimit) || /refresh requested/i.test(cleanedOutput);
|
|
95
|
+
}
|
|
96
|
+
function buildCodexUsageResult(parsed, context) {
|
|
97
|
+
assertAnyRequiredCodexLimit(parsed);
|
|
98
|
+
const errors = buildCodexUsageErrors(parsed, context);
|
|
99
|
+
return {
|
|
100
|
+
targetId: context.targetId,
|
|
101
|
+
displayName: context.displayName,
|
|
102
|
+
command: context.command,
|
|
103
|
+
limits: buildCodexUsageLimits(parsed, context),
|
|
104
|
+
errors: errors.length > 0 ? errors : void 0
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function buildCodexUsageLimits(parsed, context) {
|
|
108
|
+
return CODEX_WINDOWS.flatMap(([scope, window, key]) => {
|
|
109
|
+
const raw = parsed[key]?.trim() ?? "";
|
|
110
|
+
if (!raw) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
const percentRemaining = parsePercentRemaining(raw);
|
|
114
|
+
return [
|
|
115
|
+
makeUsageLimit({
|
|
116
|
+
targetId: context.targetId,
|
|
117
|
+
scope,
|
|
118
|
+
window,
|
|
119
|
+
percentUsed: percentRemaining == null ? null : 100 - percentRemaining,
|
|
120
|
+
percentRemaining,
|
|
121
|
+
resetText: parseResetText(raw),
|
|
122
|
+
raw,
|
|
123
|
+
now: context.now
|
|
124
|
+
})
|
|
125
|
+
];
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function parseCodexStatus(cleanedOutput) {
|
|
129
|
+
const values = {};
|
|
130
|
+
let inStatus = false;
|
|
131
|
+
let section = "main";
|
|
132
|
+
let key = "";
|
|
133
|
+
for (const rawLine of cleanedOutput.split(/\n/)) {
|
|
134
|
+
const normalizedLine = normalizeCodexLine(rawLine);
|
|
135
|
+
if (!inStatus) {
|
|
136
|
+
if (normalizedLine === "Model:" || normalizedLine.startsWith("Model: ")) {
|
|
137
|
+
inStatus = true;
|
|
138
|
+
key = "model";
|
|
139
|
+
setValue(values, key, normalizedLine.slice("Model:".length).trim());
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (normalizedLine.includes("/exit exit Codex")) {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
if (normalizedLine.startsWith("› ")) {
|
|
147
|
+
key = "";
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
let line = normalizedLine;
|
|
151
|
+
if (!line || line === "[" || line === "]") {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
line = line.replace(/^\]\s*/, "").trim();
|
|
155
|
+
if (!line) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const labelMatch = /^([-A-Za-z0-9_. ]+):\s*(.*)$/.exec(line);
|
|
159
|
+
if (labelMatch != null) {
|
|
160
|
+
const label = labelMatch[1].trim();
|
|
161
|
+
const inlineValue = labelMatch[2].trim();
|
|
162
|
+
if (isCodexSparkLimitLabel(label)) {
|
|
163
|
+
section = "spark";
|
|
164
|
+
key = "";
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
key = labelToCodexKey(label, section);
|
|
168
|
+
if (key && inlineValue) {
|
|
169
|
+
setValue(values, key, inlineValue);
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (key && isCodexContinuationLine(line, values[key])) {
|
|
174
|
+
appendValue(values, key, line);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
model: values.model ?? "",
|
|
179
|
+
directory: values.directory ?? "",
|
|
180
|
+
permissions: values.permissions ?? "",
|
|
181
|
+
agentsMd: values.agentsMd ?? "",
|
|
182
|
+
account: values.account ?? "",
|
|
183
|
+
collaborationMode: values.collaborationMode ?? "",
|
|
184
|
+
session: values.session ?? "",
|
|
185
|
+
main5hLimit: values.main5hLimit ?? "",
|
|
186
|
+
mainWeeklyLimit: values.mainWeeklyLimit ?? "",
|
|
187
|
+
spark5hLimit: values.spark5hLimit ?? "",
|
|
188
|
+
sparkWeeklyLimit: values.sparkWeeklyLimit ?? ""
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function assertAnyRequiredCodexLimit(parsed) {
|
|
192
|
+
if (parsed.main5hLimit || parsed.mainWeeklyLimit) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
throw new Error("Codex usage output did not include the required 5h and weekly limit rows.");
|
|
196
|
+
}
|
|
197
|
+
function buildCodexUsageErrors(parsed, context) {
|
|
198
|
+
const missing = [];
|
|
199
|
+
if (!parsed.main5hLimit) {
|
|
200
|
+
missing.push("5h");
|
|
201
|
+
}
|
|
202
|
+
if (!parsed.mainWeeklyLimit) {
|
|
203
|
+
missing.push("weekly");
|
|
204
|
+
}
|
|
205
|
+
if (missing.length === 0) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
targetId: context.targetId,
|
|
211
|
+
displayName: context.displayName,
|
|
212
|
+
code: "partial_parse",
|
|
213
|
+
message: `Codex usage output did not include the ${missing.join(" and ")} limit row.`
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
function isCodexSparkLimitLabel(label) {
|
|
218
|
+
return /\bspark\b/i.test(label) && /\blimit\b/i.test(label);
|
|
219
|
+
}
|
|
220
|
+
function labelToCodexKey(label, section) {
|
|
221
|
+
if (label === "Model") return "model";
|
|
222
|
+
if (label === "Directory") return "directory";
|
|
223
|
+
if (label === "Permissions") return "permissions";
|
|
224
|
+
if (label === "Agents.md") return "agentsMd";
|
|
225
|
+
if (label === "Account") return "account";
|
|
226
|
+
if (label === "Collaboration mode") return "collaborationMode";
|
|
227
|
+
if (label === "Session") return "session";
|
|
228
|
+
if (label === "5h limit") return section === "spark" ? "spark5hLimit" : "main5hLimit";
|
|
229
|
+
if (label === "Weekly limit") {
|
|
230
|
+
return section === "spark" ? "sparkWeeklyLimit" : "mainWeeklyLimit";
|
|
231
|
+
}
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
function setValue(values, key, value) {
|
|
235
|
+
const sanitized = sanitizeCodexValue(value);
|
|
236
|
+
if (!sanitized) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
values[key] = sanitized;
|
|
240
|
+
}
|
|
241
|
+
function appendValue(values, key, value) {
|
|
242
|
+
const sanitized = sanitizeCodexValue(value);
|
|
243
|
+
if (!sanitized) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
values[key] = values[key] == null || values[key] === "" ? sanitized : `${values[key]} ${sanitized}`;
|
|
247
|
+
}
|
|
248
|
+
function isCodexContinuationLine(line, currentValue) {
|
|
249
|
+
if (!line) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const hasPercent = /\d+(?:\.\d+)?\s*%/.test(line);
|
|
253
|
+
const currentHasPercent = /\d+(?:\.\d+)?\s*%/.test(currentValue ?? "");
|
|
254
|
+
if (hasPercent) {
|
|
255
|
+
return !currentHasPercent;
|
|
256
|
+
}
|
|
257
|
+
return /\breset/i.test(line);
|
|
258
|
+
}
|
|
259
|
+
function normalizeCodexLine(line) {
|
|
260
|
+
return line.replace(/[│╭╮╰╯─]/g, " ").replace(/[ \t]+/g, " ").trim();
|
|
261
|
+
}
|
|
262
|
+
function sanitizeCodexValue(value) {
|
|
263
|
+
const sanitized = value.replace(/›.*$/g, "").trim();
|
|
264
|
+
const limitMatch = /^(.+?\d+(?:\.\d+)?\s*%\s*(?:left|remaining|used)(?:\s*\([^)]*\))?)/i.exec(
|
|
265
|
+
sanitized
|
|
266
|
+
);
|
|
267
|
+
return limitMatch?.[1].trim() ?? sanitized;
|
|
268
|
+
}
|
|
269
|
+
export {
|
|
270
|
+
buildCodexUsageLimits,
|
|
271
|
+
buildCodexUsageResult,
|
|
272
|
+
extractCodexUsage,
|
|
273
|
+
parseCodexStatus
|
|
274
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { c as cleanControlOutput, m as makeUsageLimit, a as compactLines } from "./cli.js";
|
|
2
|
+
import { r as runPtyScenario, t as typeTextSteps, e as enterKey, a as escapeKey } from "./pty-CZBSAJzE.js";
|
|
3
|
+
const TIER_MODEL_IDS = /* @__PURE__ */ new Map([
|
|
4
|
+
["Flash", "flash"],
|
|
5
|
+
["Flash Lite", "flash-lite"],
|
|
6
|
+
["Pro", "pro"]
|
|
7
|
+
]);
|
|
8
|
+
async function extractGeminiUsage(context) {
|
|
9
|
+
const command = context.command ?? context.launch?.command ?? "gemini";
|
|
10
|
+
const ptyResult = await runPtyScenario({
|
|
11
|
+
command,
|
|
12
|
+
args: context.launch?.args ?? ["--skip-trust"],
|
|
13
|
+
cwd: context.repoRoot,
|
|
14
|
+
cols: 110,
|
|
15
|
+
rows: 42,
|
|
16
|
+
timeoutMs: context.launch?.timeoutMs ?? 7e4,
|
|
17
|
+
signal: context.signal,
|
|
18
|
+
debug: context.debug,
|
|
19
|
+
steps: [
|
|
20
|
+
{ waitFor: isGeminiPromptReady, waitForTimeoutMs: 12e3 },
|
|
21
|
+
...typeTextSteps("/model", 20),
|
|
22
|
+
{ waitMs: 150, write: enterKey() },
|
|
23
|
+
{
|
|
24
|
+
waitFor: hasGeminiModelUsage,
|
|
25
|
+
waitForTimeoutMs: 15e3,
|
|
26
|
+
capture: "model",
|
|
27
|
+
captureWaitMs: 500
|
|
28
|
+
},
|
|
29
|
+
{ write: escapeKey() },
|
|
30
|
+
{ waitMs: 500 },
|
|
31
|
+
...typeTextSteps("/quit", 20),
|
|
32
|
+
{ waitMs: 150, write: enterKey() },
|
|
33
|
+
{ waitMs: 500 }
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
const modelSnapshot = ptyResult.snapshots.model ?? ptyResult;
|
|
37
|
+
const cleanedOutput = cleanControlOutput(modelSnapshot.raw);
|
|
38
|
+
const parsed = parseGeminiModelDialog(modelSnapshot.screen, cleanedOutput);
|
|
39
|
+
if (parsed.usage.length === 0) {
|
|
40
|
+
throw new Error("Gemini usage output did not include model usage rows.");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
targetId: context.targetId,
|
|
44
|
+
displayName: context.displayName,
|
|
45
|
+
command,
|
|
46
|
+
limits: parsed.usage.map((row) => {
|
|
47
|
+
const modelId = resolveModelId(row.name, parsed.availableModels);
|
|
48
|
+
return makeUsageLimit({
|
|
49
|
+
targetId: context.targetId,
|
|
50
|
+
scope: modelScope(modelId),
|
|
51
|
+
window: "model",
|
|
52
|
+
label: row.name,
|
|
53
|
+
modelId,
|
|
54
|
+
modelLabel: row.name,
|
|
55
|
+
percentUsed: row.percentUsed,
|
|
56
|
+
percentRemaining: 100 - row.percentUsed,
|
|
57
|
+
resetText: row.resetText,
|
|
58
|
+
raw: row.raw,
|
|
59
|
+
now: context.now
|
|
60
|
+
});
|
|
61
|
+
}),
|
|
62
|
+
debug: ptyResult.debug.length > 0 ? ptyResult.debug : void 0
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function isGeminiPromptReady(snapshot) {
|
|
66
|
+
const screen = snapshot.screen || cleanControlOutput(snapshot.raw);
|
|
67
|
+
return /Type your message|quota/i.test(screen);
|
|
68
|
+
}
|
|
69
|
+
function hasGeminiModelUsage(snapshot) {
|
|
70
|
+
const parsed = parseGeminiModelDialog(snapshot.screen, cleanControlOutput(snapshot.raw));
|
|
71
|
+
return parsed.usage.length > 0;
|
|
72
|
+
}
|
|
73
|
+
function parseGeminiModelDialog(screen, cleanedOutput = "") {
|
|
74
|
+
const fromScreen = parseGeminiLines(compactLines(screen));
|
|
75
|
+
if (fromScreen.usage.length > 0) {
|
|
76
|
+
return fromScreen;
|
|
77
|
+
}
|
|
78
|
+
return parseGeminiLines(compactLines(cleanedOutput));
|
|
79
|
+
}
|
|
80
|
+
function parseGeminiLines(lines) {
|
|
81
|
+
const availableModels = [];
|
|
82
|
+
const usage = [];
|
|
83
|
+
let selectedModel = "";
|
|
84
|
+
let inUsage = false;
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const content = stripGeminiFrame(line);
|
|
87
|
+
if (!content) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const model = parseAvailableModel(content);
|
|
91
|
+
if (model != null) {
|
|
92
|
+
availableModels.push(model.id);
|
|
93
|
+
if (model.selected) {
|
|
94
|
+
selectedModel = model.id;
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (content === "Model usage") {
|
|
99
|
+
inUsage = true;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!inUsage) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (content.startsWith("(")) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
const row = parseUsageRow(content);
|
|
109
|
+
if (row != null) {
|
|
110
|
+
usage.push(row);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
selectedModel,
|
|
115
|
+
availableModels,
|
|
116
|
+
usage
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function parseAvailableModel(line) {
|
|
120
|
+
const match = /^(\u25cf)?\s*(\d+)\.\s+(\S+)$/u.exec(line);
|
|
121
|
+
if (match == null) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
selected: match[1] != null,
|
|
126
|
+
index: Number(match[2]),
|
|
127
|
+
id: match[3]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function parseUsageRow(line) {
|
|
131
|
+
const match = /^(.*?)\s+(\d{1,3})%\s*(?:Resets:\s*(.*?))?$/u.exec(line);
|
|
132
|
+
if (match == null) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
name: stripUsageBar(match[1]),
|
|
137
|
+
percentUsed: Number(match[2]),
|
|
138
|
+
resetText: (match[3] ?? "").trim(),
|
|
139
|
+
raw: line
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function stripUsageBar(value) {
|
|
143
|
+
return value.replace(/[\s#=\-_\u2500-\u257F\u2580-\u259F\u25AC]+$/giu, "").trim();
|
|
144
|
+
}
|
|
145
|
+
function resolveModelId(name, availableModels) {
|
|
146
|
+
const tierModelId = TIER_MODEL_IDS.get(name);
|
|
147
|
+
if (tierModelId != null) {
|
|
148
|
+
return tierModelId;
|
|
149
|
+
}
|
|
150
|
+
const truncatedPrefix = name.endsWith("...") ? name.slice(0, -3) : name.endsWith("…") ? name.slice(0, -1) : null;
|
|
151
|
+
if (truncatedPrefix) {
|
|
152
|
+
const match = availableModels.find((model) => model.startsWith(truncatedPrefix));
|
|
153
|
+
if (match != null) {
|
|
154
|
+
return match;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return name;
|
|
158
|
+
}
|
|
159
|
+
function modelScope(modelId) {
|
|
160
|
+
return modelId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
161
|
+
}
|
|
162
|
+
function stripGeminiFrame(line) {
|
|
163
|
+
return line.replace(/^\s*\u2502\s?/u, "").replace(/\s*\u2502\s*$/u, "").trim();
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
extractGeminiUsage,
|
|
167
|
+
parseGeminiModelDialog
|
|
168
|
+
};
|