@npv12/opencode-mini-session 0.0.1
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 +77 -0
- package/dist/agent.js +103 -0
- package/dist/components/ActionButton.js +39 -0
- package/dist/components/AnswerDialog.js +841 -0
- package/dist/components/HintBar.js +34 -0
- package/dist/config.js +27 -0
- package/dist/constants.js +24 -0
- package/dist/context.js +72 -0
- package/dist/counter.js +55 -0
- package/dist/diagnostics.js +59 -0
- package/dist/index.js +157 -0
- package/dist/keybinds.js +152 -0
- package/dist/model.js +75 -0
- package/dist/routing.js +20 -0
- package/dist/session.js +617 -0
- package/dist/theme.js +34 -0
- package/dist/types.js +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
2
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
3
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
4
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
5
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
6
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
7
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
8
|
+
/** @jsxImportSource @opentui/solid */
|
|
9
|
+
|
|
10
|
+
import { For } from "solid-js";
|
|
11
|
+
export function HintBar(props) {
|
|
12
|
+
return (() => {
|
|
13
|
+
var _el$ = _$createElement("box");
|
|
14
|
+
_$setProp(_el$, "flexDirection", "row");
|
|
15
|
+
_$setProp(_el$, "gap", 2);
|
|
16
|
+
_$insert(_el$, _$createComponent(For, {
|
|
17
|
+
get each() {
|
|
18
|
+
return props.items.filter(item => item.keybind);
|
|
19
|
+
},
|
|
20
|
+
children: item => (() => {
|
|
21
|
+
var _el$2 = _$createElement("text"),
|
|
22
|
+
_el$3 = _$createElement("b"),
|
|
23
|
+
_el$4 = _$createTextNode(` `);
|
|
24
|
+
_$insertNode(_el$2, _el$3);
|
|
25
|
+
_$insertNode(_el$2, _el$4);
|
|
26
|
+
_$insert(_el$3, () => item.keybind);
|
|
27
|
+
_$insert(_el$2, () => item.label, null);
|
|
28
|
+
_$effect(_$p => _$setProp(_el$2, "fg", props.context.theme.text.subdued, _$p));
|
|
29
|
+
return _el$2;
|
|
30
|
+
})()
|
|
31
|
+
}));
|
|
32
|
+
return _el$;
|
|
33
|
+
})();
|
|
34
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DEFAULT_FRESH_KEYBIND, DEFAULT_FULL_TOKEN_LIMIT, DEFAULT_KEYBIND, DEFAULT_TOGGLE_THINKING_KEYBIND } from "./constants.js";
|
|
2
|
+
export function parseConfig(options) {
|
|
3
|
+
const input = options && typeof options === "object" ? options : {};
|
|
4
|
+
return {
|
|
5
|
+
model: parseStringOption(input.model),
|
|
6
|
+
variant: parseStringOption(input.variant),
|
|
7
|
+
agent: parseStringOption(input.agent),
|
|
8
|
+
tokenLimit: parsePositiveNumber(input.tokenLimit, DEFAULT_FULL_TOKEN_LIMIT),
|
|
9
|
+
keybind: parseKeybind(input.keybind, DEFAULT_KEYBIND),
|
|
10
|
+
freshKeybind: parseKeybind(input.freshKeybind, DEFAULT_FRESH_KEYBIND),
|
|
11
|
+
enableThinking: typeof input.enableThinking === "boolean" ? input.enableThinking : false,
|
|
12
|
+
toggleThinkingKeybind: parseKeybind(input.toggleThinkingKeybind, DEFAULT_TOGGLE_THINKING_KEYBIND)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function parsePositiveNumber(value, fallback) {
|
|
16
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
17
|
+
}
|
|
18
|
+
function parseKeybind(value, fallback) {
|
|
19
|
+
if (value === false) return false;
|
|
20
|
+
if (typeof value !== "string") return fallback;
|
|
21
|
+
const keybind = value.trim();
|
|
22
|
+
if (!keybind) return fallback;
|
|
23
|
+
return keybind === "none" ? false : keybind;
|
|
24
|
+
}
|
|
25
|
+
function parseStringOption(value) {
|
|
26
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const PLUGIN_ID = "opencode-mini-session";
|
|
2
|
+
export const CMD_OPEN = "mini.open";
|
|
3
|
+
export const CMD_OPEN_FRESH = "mini.open-fresh";
|
|
4
|
+
export const CMD_TOGGLE_MAIN = "mini.toggle-main";
|
|
5
|
+
export const CMD_TOGGLE_FRESH = "mini.toggle-fresh";
|
|
6
|
+
export const CMD_HIDE = "mini.hide";
|
|
7
|
+
export const CMD_CLOSE = "mini.close";
|
|
8
|
+
export const CMD_CONTINUE = "mini.continue";
|
|
9
|
+
export const CMD_CHANGE_MODEL = "mini.change-model";
|
|
10
|
+
export const CMD_TOGGLE_THINKING = "mini.toggle-thinking";
|
|
11
|
+
export const CMD_SCROLL_UP = "mini.scroll-up";
|
|
12
|
+
export const CMD_SCROLL_DOWN = "mini.scroll-down";
|
|
13
|
+
export const CMD_PAGE_UP = "mini.page-up";
|
|
14
|
+
export const CMD_PAGE_DOWN = "mini.page-down";
|
|
15
|
+
export const CMD_SUBMIT = "mini.submit";
|
|
16
|
+
export const CMD_SCROLL_TOP = "mini.scroll-top";
|
|
17
|
+
export const CMD_SCROLL_BOTTOM = "mini.scroll-bottom";
|
|
18
|
+
export const SCROLL_LINE_DELTA = 4;
|
|
19
|
+
export const SCROLL_PAGE_DELTA = 14;
|
|
20
|
+
export const DEFAULT_FULL_TOKEN_LIMIT = 50_000;
|
|
21
|
+
export const DEFAULT_KEYBIND = "ctrl+n";
|
|
22
|
+
export const DEFAULT_FRESH_KEYBIND = "ctrl+f";
|
|
23
|
+
export const DEFAULT_TOGGLE_THINKING_KEYBIND = "ctrl+o";
|
|
24
|
+
export const THINKING_TEXT = "Thinking...";
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export function getSessionEntries(context, sessionID) {
|
|
2
|
+
return context.data.session.message.list(sessionID).slice().sort((a, b) => {
|
|
3
|
+
const aTime = isFinite(a.time?.created ?? NaN) ? a.time.created : 0;
|
|
4
|
+
const bTime = isFinite(b.time?.created ?? NaN) ? b.time.created : 0;
|
|
5
|
+
return aTime - bTime;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export function buildCopiedContext(entries, tokenLimit) {
|
|
9
|
+
const chunks = entries.map(entry => {
|
|
10
|
+
const text = formatEntry(entry);
|
|
11
|
+
return text ? {
|
|
12
|
+
text,
|
|
13
|
+
tokens: estimateTokens(text)
|
|
14
|
+
} : undefined;
|
|
15
|
+
}).filter(chunk => Boolean(chunk));
|
|
16
|
+
const totalAvailableTokens = chunks.reduce((total, chunk) => total + chunk.tokens, 0);
|
|
17
|
+
const selected = [];
|
|
18
|
+
let usedTokens = 0;
|
|
19
|
+
for (let i = chunks.length - 1; i >= 0; i--) {
|
|
20
|
+
const chunk = chunks[i];
|
|
21
|
+
if (selected.length > 0 && usedTokens + chunk.tokens > tokenLimit) break;
|
|
22
|
+
selected.push(chunk.text);
|
|
23
|
+
usedTokens += chunk.tokens;
|
|
24
|
+
if (usedTokens >= tokenLimit) break;
|
|
25
|
+
}
|
|
26
|
+
if (selected.length === 0) {
|
|
27
|
+
return {
|
|
28
|
+
text: "No conversation context available.",
|
|
29
|
+
usedTokens: 0,
|
|
30
|
+
totalAvailableTokens
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
text: selected.reverse().join("\n\n"),
|
|
35
|
+
usedTokens,
|
|
36
|
+
totalAvailableTokens
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function formatEntry(entry) {
|
|
40
|
+
if (entry.type === "user") {
|
|
41
|
+
return entry.text.trim() ? `user:\n${entry.text.trim()}` : "";
|
|
42
|
+
}
|
|
43
|
+
if (entry.type === "assistant") {
|
|
44
|
+
const lines = [];
|
|
45
|
+
for (const part of entry.content) {
|
|
46
|
+
if (part.type === "text" && part.text.trim()) lines.push(part.text.trim());
|
|
47
|
+
if (part.type === "tool") lines.push(`[tool: ${part.name}${formatToolInput(part.state)}]`);
|
|
48
|
+
}
|
|
49
|
+
return lines.length > 0 ? `assistant:\n${lines.join("\n")}` : "";
|
|
50
|
+
}
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
function formatToolInput(state) {
|
|
54
|
+
const input = state.input;
|
|
55
|
+
if (!input) return "";
|
|
56
|
+
if (typeof input === "string") return input ? ` ${input}` : "";
|
|
57
|
+
const pairs = Object.entries(input).slice(0, 4).map(([k, v]) => `${k}=${summarizeValue(v)}`);
|
|
58
|
+
return pairs.length > 0 ? ` ${pairs.join(" ")}` : "";
|
|
59
|
+
}
|
|
60
|
+
function summarizeValue(value) {
|
|
61
|
+
if (typeof value === "string") return truncate(value.replace(/\s+/g, " "), 48);
|
|
62
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
63
|
+
if (Array.isArray(value)) return `[${value.length}]`;
|
|
64
|
+
if (value && typeof value === "object") return "{...}";
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
function truncate(value, maxLength) {
|
|
68
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
|
|
69
|
+
}
|
|
70
|
+
export function estimateTokens(text) {
|
|
71
|
+
return Math.ceil(text.length / 3.4);
|
|
72
|
+
}
|
package/dist/counter.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const MINI_SESSION_WARNING_PERCENT = 85;
|
|
2
|
+
export const MINI_SESSION_LIMIT_PERCENT = 95;
|
|
3
|
+
export function formatTokenCount(value) {
|
|
4
|
+
if (value < 1000) return `${Math.round(value)}`;
|
|
5
|
+
if (value < 1_000_000) return `${(value / 1000).toFixed(1)}K`;
|
|
6
|
+
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
7
|
+
}
|
|
8
|
+
export function formatPercent(value) {
|
|
9
|
+
return `${Math.round(value)}%`;
|
|
10
|
+
}
|
|
11
|
+
export function getUsagePercent(usedTokens, limit) {
|
|
12
|
+
if (limit <= 0) return undefined;
|
|
13
|
+
return usedTokens / limit * 100;
|
|
14
|
+
}
|
|
15
|
+
export function getDisplayPercent(usedTokens, limit) {
|
|
16
|
+
const percent = getUsagePercent(usedTokens, limit);
|
|
17
|
+
return percent === undefined ? undefined : Math.round(percent);
|
|
18
|
+
}
|
|
19
|
+
export function isMiniSessionWarning(percentUsed) {
|
|
20
|
+
return Boolean(percentUsed !== undefined && percentUsed >= MINI_SESSION_WARNING_PERCENT);
|
|
21
|
+
}
|
|
22
|
+
export function isMiniSessionLimitReached(percentUsed) {
|
|
23
|
+
return Boolean(percentUsed !== undefined && percentUsed >= MINI_SESSION_LIMIT_PERCENT);
|
|
24
|
+
}
|
|
25
|
+
export function buildFooterCounterState(options) {
|
|
26
|
+
const copiedContextTotalTokens = options.copiedContextTotalTokens ?? options.copiedContextTokens;
|
|
27
|
+
const copiedContextTruncated = options.copiedContextTokens !== undefined && copiedContextTotalTokens !== undefined && copiedContextTotalTokens > options.copiedContextTokens;
|
|
28
|
+
const copiedContext = options.mode === "main" && options.copiedContextTokens !== undefined ? {
|
|
29
|
+
usedTokens: options.copiedContextTokens,
|
|
30
|
+
totalAvailableTokens: copiedContextTotalTokens ?? options.copiedContextTokens,
|
|
31
|
+
tokenLimit: options.tokenLimit,
|
|
32
|
+
text: formatCopiedContextCounter(options.copiedContextTokens, copiedContextTotalTokens ?? options.copiedContextTokens, options.tokenLimit),
|
|
33
|
+
truncated: copiedContextTruncated
|
|
34
|
+
} : undefined;
|
|
35
|
+
const miniSession = options.lastCompletedMiniInputTokens !== undefined ? buildMiniSessionCounter(options.lastCompletedMiniInputTokens, options.modelContextWindow) : undefined;
|
|
36
|
+
return {
|
|
37
|
+
copiedContext,
|
|
38
|
+
miniSession,
|
|
39
|
+
placeholder: miniSession?.limitReached ? "Session context limit reached..." : undefined
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function formatCopiedContextCounter(usedTokens, totalAvailableTokens, tokenLimit) {
|
|
43
|
+
if (totalAvailableTokens <= usedTokens) return `main ${formatTokenCount(usedTokens)}`;
|
|
44
|
+
return `main ${formatTokenCount(Math.min(usedTokens, tokenLimit))}/${formatTokenCount(totalAvailableTokens)}`;
|
|
45
|
+
}
|
|
46
|
+
function buildMiniSessionCounter(usedTokens, modelContextWindow) {
|
|
47
|
+
const percentUsed = modelContextWindow !== undefined ? getDisplayPercent(usedTokens, modelContextWindow) : undefined;
|
|
48
|
+
return {
|
|
49
|
+
usedTokens,
|
|
50
|
+
percentUsed,
|
|
51
|
+
text: percentUsed !== undefined ? `${formatTokenCount(usedTokens)} (${formatPercent(percentUsed)})` : formatTokenCount(usedTokens),
|
|
52
|
+
warning: isMiniSessionWarning(percentUsed),
|
|
53
|
+
limitReached: isMiniSessionLimitReached(percentUsed)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const FALLBACK_ERROR_MESSAGE = "The side question failed.";
|
|
2
|
+
const NESTED_ERROR_KEYS = ["error", "cause", "details"];
|
|
3
|
+
export function extractErrorMessage(error) {
|
|
4
|
+
const message = readErrorMessage(error);
|
|
5
|
+
if (message) return message;
|
|
6
|
+
return FALLBACK_ERROR_MESSAGE;
|
|
7
|
+
}
|
|
8
|
+
export function getErrorMessage(cause) {
|
|
9
|
+
const extracted = extractErrorMessage(cause);
|
|
10
|
+
if (cause instanceof Error && cause.message) {
|
|
11
|
+
const normalizedMessage = normalizeErrorText(cause.message);
|
|
12
|
+
if (normalizedMessage && !isGenericErrorLabel(normalizedMessage)) {
|
|
13
|
+
return normalizedMessage;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (extracted) return extracted;
|
|
17
|
+
if (cause instanceof Error && cause.name) return cause.name;
|
|
18
|
+
return FALLBACK_ERROR_MESSAGE;
|
|
19
|
+
}
|
|
20
|
+
function readErrorMessage(error, depth = 0) {
|
|
21
|
+
if (depth > 3 || !error) return undefined;
|
|
22
|
+
if (typeof error === "string") return normalizeErrorText(error);
|
|
23
|
+
if (Array.isArray(error)) {
|
|
24
|
+
for (const item of error) {
|
|
25
|
+
const message = readErrorMessage(item, depth + 1);
|
|
26
|
+
if (message) return message;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (!isRecord(error)) return undefined;
|
|
31
|
+
const dataMessage = readDataMessage(error);
|
|
32
|
+
if (dataMessage) return dataMessage;
|
|
33
|
+
for (const key of NESTED_ERROR_KEYS) {
|
|
34
|
+
const message = readErrorMessage(error[key], depth + 1);
|
|
35
|
+
if (message) return message;
|
|
36
|
+
}
|
|
37
|
+
const directMessage = normalizeErrorText(error.message);
|
|
38
|
+
if (directMessage && !isGenericErrorLabel(directMessage)) return directMessage;
|
|
39
|
+
const name = normalizeErrorText(error.name);
|
|
40
|
+
if (name) return name;
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function readDataMessage(error) {
|
|
44
|
+
const data = error.data;
|
|
45
|
+
if (!isRecord(data)) return undefined;
|
|
46
|
+
return normalizeErrorText(data.message);
|
|
47
|
+
}
|
|
48
|
+
function normalizeErrorText(value) {
|
|
49
|
+
if (typeof value !== "string") return undefined;
|
|
50
|
+
const trimmed = value.trim();
|
|
51
|
+
if (!trimmed) return undefined;
|
|
52
|
+
return trimmed.length > 600 ? `${trimmed.slice(0, 597)}...` : trimmed;
|
|
53
|
+
}
|
|
54
|
+
function isGenericErrorLabel(value) {
|
|
55
|
+
return /^(unknown(error)?|error)$/i.test(value.trim());
|
|
56
|
+
}
|
|
57
|
+
function isRecord(value) {
|
|
58
|
+
return typeof value === "object" && value !== null;
|
|
59
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
2
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
3
|
+
/** @jsxImportSource @opentui/solid */
|
|
4
|
+
|
|
5
|
+
import { createEffect, createSignal } from "solid-js";
|
|
6
|
+
import { createOverlaySlot } from "./components/AnswerDialog.js";
|
|
7
|
+
import { parseConfig } from "./config.js";
|
|
8
|
+
import { PLUGIN_ID } from "./constants.js";
|
|
9
|
+
import { buildGlobalCommands, buildPanelCommands } from "./keybinds.js";
|
|
10
|
+
import { openMiniSession, openModelPicker } from "./session.js";
|
|
11
|
+
import { resolveMiniRouteAction, runMiniRouteAction } from "./routing.js";
|
|
12
|
+
function KeymapManager(props) {
|
|
13
|
+
props.context.keymap.layer(() => ({
|
|
14
|
+
mode: "global",
|
|
15
|
+
commands: props.globalCmds.map(c => ({
|
|
16
|
+
id: c.id,
|
|
17
|
+
title: c.title,
|
|
18
|
+
description: c.description,
|
|
19
|
+
group: c.group,
|
|
20
|
+
palette: c.palette,
|
|
21
|
+
slash: c.slash,
|
|
22
|
+
bind: c.bind,
|
|
23
|
+
enabled: c.enabled,
|
|
24
|
+
run: c.run
|
|
25
|
+
}))
|
|
26
|
+
}));
|
|
27
|
+
props.context.keymap.layer(() => ({
|
|
28
|
+
mode: "global",
|
|
29
|
+
priority: 1000,
|
|
30
|
+
enabled: () => Boolean(props.overlay()),
|
|
31
|
+
commands: props.panelCmds.map(c => ({
|
|
32
|
+
id: c.id,
|
|
33
|
+
title: c.title,
|
|
34
|
+
bind: c.bind,
|
|
35
|
+
enabled: c.enabled,
|
|
36
|
+
run: c.run
|
|
37
|
+
}))
|
|
38
|
+
}));
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
export default {
|
|
42
|
+
id: PLUGIN_ID,
|
|
43
|
+
setup(context) {
|
|
44
|
+
const config = parseConfig(context.options);
|
|
45
|
+
const [overlay, setOverlay] = createSignal(undefined, {
|
|
46
|
+
equals: false
|
|
47
|
+
});
|
|
48
|
+
const [selectedModel, setSelectedModel] = createSignal(undefined, {
|
|
49
|
+
equals: false
|
|
50
|
+
});
|
|
51
|
+
const [thinkingEnabled, setThinkingEnabled] = createSignal(config.enableThinking);
|
|
52
|
+
let activeDialog;
|
|
53
|
+
let activeMode;
|
|
54
|
+
let modelPickerOpen = false;
|
|
55
|
+
const thinkingPreference = {
|
|
56
|
+
get: thinkingEnabled,
|
|
57
|
+
set: setThinkingEnabled
|
|
58
|
+
};
|
|
59
|
+
const globalCmds = buildGlobalCommands({
|
|
60
|
+
context,
|
|
61
|
+
config,
|
|
62
|
+
overlay,
|
|
63
|
+
modelPickerOpen: {
|
|
64
|
+
get: () => modelPickerOpen,
|
|
65
|
+
set: v => {
|
|
66
|
+
modelPickerOpen = v;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
triggerMiniMode: (mode, source) => triggerMiniMode(mode, source),
|
|
70
|
+
openModelPicker: () => {
|
|
71
|
+
const route = context.ui.router.current();
|
|
72
|
+
if (route.type !== "session") return;
|
|
73
|
+
openModelPicker(context, config, route.sessionID, {
|
|
74
|
+
get: selectedModel,
|
|
75
|
+
set: setSelectedModel
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
const panelCmds = buildPanelCommands({
|
|
80
|
+
context,
|
|
81
|
+
config,
|
|
82
|
+
overlay,
|
|
83
|
+
modelPickerOpen: {
|
|
84
|
+
get: () => modelPickerOpen,
|
|
85
|
+
set: v => {
|
|
86
|
+
modelPickerOpen = v;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
triggerMiniMode: (mode, source) => triggerMiniMode(mode, source),
|
|
90
|
+
openModelPicker: () => {
|
|
91
|
+
const route = context.ui.router.current();
|
|
92
|
+
if (route.type !== "session") return;
|
|
93
|
+
openModelPicker(context, config, route.sessionID, {
|
|
94
|
+
get: selectedModel,
|
|
95
|
+
set: setSelectedModel
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
context.ui.slot({
|
|
100
|
+
append: "app",
|
|
101
|
+
render: () => [_$createComponent(KeymapManager, {
|
|
102
|
+
context: context,
|
|
103
|
+
overlay: overlay,
|
|
104
|
+
globalCmds: globalCmds,
|
|
105
|
+
panelCmds: panelCmds
|
|
106
|
+
}), _$memo(() => createOverlaySlot(overlay)())]
|
|
107
|
+
});
|
|
108
|
+
createEffect(() => {
|
|
109
|
+
const route = context.ui.router.current();
|
|
110
|
+
if (route.type === "session") return;
|
|
111
|
+
if (!activeDialog) return;
|
|
112
|
+
setOverlay(undefined);
|
|
113
|
+
context.ui.toast.show({
|
|
114
|
+
variant: "info",
|
|
115
|
+
message: "mini session closed.",
|
|
116
|
+
duration: 1000
|
|
117
|
+
});
|
|
118
|
+
void activeDialog.close();
|
|
119
|
+
});
|
|
120
|
+
async function triggerMiniMode(mode, source) {
|
|
121
|
+
const route = context.ui.router.current();
|
|
122
|
+
if (route.type !== "session") return;
|
|
123
|
+
const nextAction = resolveMiniRouteAction({
|
|
124
|
+
source,
|
|
125
|
+
requestedMode: mode,
|
|
126
|
+
activeMode,
|
|
127
|
+
isVisible: activeDialog?.isVisible()
|
|
128
|
+
});
|
|
129
|
+
await runMiniRouteAction({
|
|
130
|
+
action: nextAction,
|
|
131
|
+
activeDialog,
|
|
132
|
+
open: () => {
|
|
133
|
+
const opened = openMiniSession(context, config, mode, setOverlay, {
|
|
134
|
+
get: () => activeDialog,
|
|
135
|
+
set: d => {
|
|
136
|
+
activeDialog = d;
|
|
137
|
+
if (!d) {
|
|
138
|
+
activeMode = undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, {
|
|
142
|
+
get: selectedModel,
|
|
143
|
+
set: setSelectedModel
|
|
144
|
+
}, thinkingPreference, onAfterSelect => openModelPicker(context, config, route.sessionID, {
|
|
145
|
+
get: selectedModel,
|
|
146
|
+
set: setSelectedModel
|
|
147
|
+
}, () => {
|
|
148
|
+
modelPickerOpen = false;
|
|
149
|
+
onAfterSelect();
|
|
150
|
+
}));
|
|
151
|
+
if (opened) activeMode = mode;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return () => void activeDialog?.close();
|
|
156
|
+
}
|
|
157
|
+
};
|
package/dist/keybinds.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { CMD_CHANGE_MODEL, CMD_CLOSE, CMD_CONTINUE, CMD_HIDE, CMD_OPEN, CMD_OPEN_FRESH, CMD_PAGE_DOWN, CMD_PAGE_UP, CMD_SCROLL_BOTTOM, CMD_SCROLL_DOWN, CMD_SCROLL_TOP, CMD_SCROLL_UP, CMD_SUBMIT, CMD_TOGGLE_FRESH, CMD_TOGGLE_MAIN, CMD_TOGGLE_THINKING, SCROLL_LINE_DELTA, SCROLL_PAGE_DELTA } from "./constants.js";
|
|
2
|
+
export function buildPanelCommands(ctx) {
|
|
3
|
+
const {
|
|
4
|
+
overlay,
|
|
5
|
+
modelPickerOpen
|
|
6
|
+
} = ctx;
|
|
7
|
+
const closePanel = () => {
|
|
8
|
+
if (modelPickerOpen.get()) {
|
|
9
|
+
modelPickerOpen.set(false);
|
|
10
|
+
} else {
|
|
11
|
+
overlay()?.onClose();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const whenOpen = () => Boolean(overlay());
|
|
15
|
+
return [{
|
|
16
|
+
id: CMD_HIDE,
|
|
17
|
+
bind: "up",
|
|
18
|
+
enabled: whenOpen,
|
|
19
|
+
run: () => overlay()?.onHide()
|
|
20
|
+
}, {
|
|
21
|
+
id: CMD_CLOSE,
|
|
22
|
+
title: "Close",
|
|
23
|
+
bind: "escape",
|
|
24
|
+
enabled: whenOpen,
|
|
25
|
+
run: closePanel
|
|
26
|
+
}, {
|
|
27
|
+
id: CMD_CLOSE,
|
|
28
|
+
bind: "ctrl+c",
|
|
29
|
+
enabled: whenOpen,
|
|
30
|
+
run: closePanel
|
|
31
|
+
}, {
|
|
32
|
+
id: CMD_CONTINUE,
|
|
33
|
+
title: "Continue",
|
|
34
|
+
bind: "shift+return",
|
|
35
|
+
enabled: whenOpen,
|
|
36
|
+
run: () => overlay()?.onContinue()
|
|
37
|
+
}, {
|
|
38
|
+
id: CMD_SUBMIT,
|
|
39
|
+
title: "Submit",
|
|
40
|
+
bind: "return",
|
|
41
|
+
enabled: whenOpen,
|
|
42
|
+
run: () => overlay()?.submit()
|
|
43
|
+
}, ...(ctx.config.toggleThinkingKeybind ? [{
|
|
44
|
+
id: CMD_TOGGLE_THINKING,
|
|
45
|
+
title: "Toggle thinking",
|
|
46
|
+
bind: ctx.config.toggleThinkingKeybind,
|
|
47
|
+
enabled: whenOpen,
|
|
48
|
+
run: () => overlay()?.onToggleThinking()
|
|
49
|
+
}] : []), {
|
|
50
|
+
id: CMD_CHANGE_MODEL,
|
|
51
|
+
title: "Change model",
|
|
52
|
+
bind: "tab",
|
|
53
|
+
enabled: whenOpen,
|
|
54
|
+
run: () => {
|
|
55
|
+
modelPickerOpen.set(true);
|
|
56
|
+
overlay()?.onChangeModel();
|
|
57
|
+
}
|
|
58
|
+
}, {
|
|
59
|
+
id: CMD_SCROLL_UP,
|
|
60
|
+
bind: "down",
|
|
61
|
+
enabled: whenOpen,
|
|
62
|
+
run: () => overlay()?.scrollBy(-SCROLL_LINE_DELTA)
|
|
63
|
+
}, {
|
|
64
|
+
id: CMD_SCROLL_DOWN,
|
|
65
|
+
bind: "down",
|
|
66
|
+
enabled: whenOpen,
|
|
67
|
+
run: () => overlay()?.scrollBy(SCROLL_LINE_DELTA)
|
|
68
|
+
}, {
|
|
69
|
+
id: CMD_PAGE_UP,
|
|
70
|
+
bind: "pageup",
|
|
71
|
+
enabled: whenOpen,
|
|
72
|
+
run: () => overlay()?.scrollBy(-SCROLL_PAGE_DELTA)
|
|
73
|
+
}, {
|
|
74
|
+
id: CMD_PAGE_DOWN,
|
|
75
|
+
bind: "pagedown",
|
|
76
|
+
enabled: whenOpen,
|
|
77
|
+
run: () => overlay()?.scrollBy(SCROLL_PAGE_DELTA)
|
|
78
|
+
}, {
|
|
79
|
+
id: CMD_SCROLL_TOP,
|
|
80
|
+
bind: "home",
|
|
81
|
+
enabled: whenOpen,
|
|
82
|
+
run: () => overlay()?.scrollTo(0)
|
|
83
|
+
}, {
|
|
84
|
+
id: CMD_SCROLL_BOTTOM,
|
|
85
|
+
bind: "end",
|
|
86
|
+
enabled: whenOpen,
|
|
87
|
+
run: () => overlay()?.scrollTo(Number.MAX_SAFE_INTEGER)
|
|
88
|
+
}];
|
|
89
|
+
}
|
|
90
|
+
export function buildGlobalCommands(ctx) {
|
|
91
|
+
const {
|
|
92
|
+
config,
|
|
93
|
+
triggerMiniMode,
|
|
94
|
+
openModelPicker
|
|
95
|
+
} = ctx;
|
|
96
|
+
const onSession = () => {
|
|
97
|
+
const route = ctx.context.ui.router.current();
|
|
98
|
+
return route.type === "session";
|
|
99
|
+
};
|
|
100
|
+
const commands = [];
|
|
101
|
+
if (config.keybind) {
|
|
102
|
+
commands.push({
|
|
103
|
+
id: CMD_TOGGLE_MAIN,
|
|
104
|
+
title: "Toggle mini session",
|
|
105
|
+
bind: config.keybind,
|
|
106
|
+
run: () => void triggerMiniMode("main", "keybind")
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (config.freshKeybind) {
|
|
110
|
+
commands.push({
|
|
111
|
+
id: CMD_TOGGLE_FRESH,
|
|
112
|
+
title: "Toggle mini fresh session",
|
|
113
|
+
bind: config.freshKeybind,
|
|
114
|
+
run: () => void triggerMiniMode("fresh", "keybind")
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
commands.push({
|
|
118
|
+
id: CMD_OPEN,
|
|
119
|
+
title: "mini",
|
|
120
|
+
description: "Open a mini session for side questions",
|
|
121
|
+
group: "Mini",
|
|
122
|
+
palette: true,
|
|
123
|
+
slash: {
|
|
124
|
+
name: "mini"
|
|
125
|
+
},
|
|
126
|
+
enabled: onSession,
|
|
127
|
+
run: () => void triggerMiniMode("main", "command")
|
|
128
|
+
}, {
|
|
129
|
+
id: CMD_OPEN_FRESH,
|
|
130
|
+
title: "mini fresh",
|
|
131
|
+
description: "Open a mini session without copied context",
|
|
132
|
+
group: "Mini",
|
|
133
|
+
palette: true,
|
|
134
|
+
slash: {
|
|
135
|
+
name: "mini-fresh"
|
|
136
|
+
},
|
|
137
|
+
enabled: onSession,
|
|
138
|
+
run: () => void triggerMiniMode("fresh", "command")
|
|
139
|
+
}, {
|
|
140
|
+
id: CMD_CHANGE_MODEL + ".global",
|
|
141
|
+
title: "mini model",
|
|
142
|
+
description: "Change the model for future mini-session questions",
|
|
143
|
+
group: "Mini",
|
|
144
|
+
palette: true,
|
|
145
|
+
slash: {
|
|
146
|
+
name: "mini-model"
|
|
147
|
+
},
|
|
148
|
+
enabled: onSession,
|
|
149
|
+
run: openModelPicker
|
|
150
|
+
});
|
|
151
|
+
return commands;
|
|
152
|
+
}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export function parseModelOverride(value) {
|
|
2
|
+
const [providerID, ...rest] = value.split("/");
|
|
3
|
+
const id = rest.join("/");
|
|
4
|
+
if (!providerID || !id) return undefined;
|
|
5
|
+
return {
|
|
6
|
+
providerID,
|
|
7
|
+
id
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function resolveModel(modelOverride, variantOverride, entries, sessionModel) {
|
|
11
|
+
if (modelOverride) {
|
|
12
|
+
const model = parseModelOverride(modelOverride);
|
|
13
|
+
if (model) return {
|
|
14
|
+
model: {
|
|
15
|
+
...model,
|
|
16
|
+
...(variantOverride ? {
|
|
17
|
+
variant: variantOverride
|
|
18
|
+
} : {})
|
|
19
|
+
},
|
|
20
|
+
source: "config"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (sessionModel) {
|
|
24
|
+
return {
|
|
25
|
+
model: {
|
|
26
|
+
providerID: sessionModel.providerID,
|
|
27
|
+
id: sessionModel.id,
|
|
28
|
+
variant: sessionModel.variant
|
|
29
|
+
},
|
|
30
|
+
source: "session"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
34
|
+
const entry = entries[i];
|
|
35
|
+
if (entry.type === "assistant" && entry.model) {
|
|
36
|
+
return {
|
|
37
|
+
model: {
|
|
38
|
+
providerID: entry.model.providerID,
|
|
39
|
+
id: entry.model.id,
|
|
40
|
+
variant: entry.model.variant
|
|
41
|
+
},
|
|
42
|
+
source: "session"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
model: {},
|
|
48
|
+
source: "default"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function resolveDefaultModel(models, configuredModel, configuredVariant, sessionModel, entries) {
|
|
52
|
+
const resolved = resolveModel(configuredModel, configuredVariant, entries, sessionModel);
|
|
53
|
+
if (resolved.source !== "config") return resolved;
|
|
54
|
+
if (isAvailableModel(models, resolved.model)) return resolved;
|
|
55
|
+
return {
|
|
56
|
+
...resolveModel(null, null, entries, sessionModel),
|
|
57
|
+
notice: `Configured mini model ${formatResolvedModel(resolved.model)} was not found. The main session model will be used.`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function isAvailableModel(models, resolved) {
|
|
61
|
+
if (!resolved.providerID || !resolved.id) return false;
|
|
62
|
+
const model = models?.find(m => m.providerID === resolved.providerID && m.id === resolved.id);
|
|
63
|
+
if (!model) return false;
|
|
64
|
+
if (!resolved.variant) return true;
|
|
65
|
+
return model.variants.some(v => v.id === resolved.variant);
|
|
66
|
+
}
|
|
67
|
+
export function formatResolvedModel(resolved) {
|
|
68
|
+
if (!resolved.providerID || !resolved.id) return "default";
|
|
69
|
+
const base = `${resolved.providerID}/${resolved.id}`;
|
|
70
|
+
return resolved.variant ? `${base} (${resolved.variant})` : base;
|
|
71
|
+
}
|
|
72
|
+
export function resolveModelContextWindow(models, resolved) {
|
|
73
|
+
if (!resolved.providerID || !resolved.id) return undefined;
|
|
74
|
+
return models?.find(m => m.providerID === resolved.providerID && m.id === resolved.id)?.limit?.context;
|
|
75
|
+
}
|
package/dist/routing.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function resolveMiniRouteAction(options) {
|
|
2
|
+
if (!options.activeMode) return "open";
|
|
3
|
+
if (options.activeMode !== options.requestedMode) return "switch";
|
|
4
|
+
if (options.isVisible === false) return "show";
|
|
5
|
+
return options.source === "keybind" ? "hide" : "show";
|
|
6
|
+
}
|
|
7
|
+
export async function runMiniRouteAction(options) {
|
|
8
|
+
if (options.action === "hide") {
|
|
9
|
+
options.activeDialog?.hide();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (options.action === "show") {
|
|
13
|
+
options.activeDialog?.show();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (options.action === "switch") {
|
|
17
|
+
await options.activeDialog?.close();
|
|
18
|
+
}
|
|
19
|
+
options.open();
|
|
20
|
+
}
|