@mariozechner/pi-coding-agent 0.14.2 → 0.15.0
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/CHANGELOG.md +6 -0
- package/README.md +10 -1
- package/dist/cli/args.d.ts +30 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +179 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/file-processor.d.ts +11 -0
- package/dist/cli/file-processor.d.ts.map +1 -0
- package/dist/cli/file-processor.js +82 -0
- package/dist/cli/file-processor.js.map +1 -0
- package/dist/cli/session-picker.d.ts +7 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +29 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -18
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -9
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +287 -0
- package/dist/core/agent-session.d.ts.map +1 -0
- package/dist/core/agent-session.js +735 -0
- package/dist/core/agent-session.js.map +1 -0
- package/dist/core/bash-executor.d.ts +41 -0
- package/dist/core/bash-executor.d.ts.map +1 -0
- package/dist/core/bash-executor.js +132 -0
- package/dist/core/bash-executor.js.map +1 -0
- package/dist/{compaction.d.ts → core/compaction.d.ts} +5 -1
- package/dist/core/compaction.d.ts.map +1 -0
- package/dist/{compaction.js → core/compaction.js} +23 -1
- package/dist/core/compaction.js.map +1 -0
- package/dist/core/export-html.d.ts.map +1 -0
- package/dist/{export-html.js → core/export-html.js} +1 -1
- package/dist/{export-html.d.ts.map → core/export-html.js.map} +1 -1
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/{model-config.js → core/model-config.js} +1 -1
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-resolver.d.ts +48 -0
- package/dist/core/model-resolver.d.ts.map +1 -0
- package/dist/core/model-resolver.js +244 -0
- package/dist/core/model-resolver.js.map +1 -0
- package/dist/core/oauth/anthropic.d.ts.map +1 -0
- package/dist/core/oauth/anthropic.js.map +1 -0
- package/dist/core/oauth/index.d.ts.map +1 -0
- package/dist/{oauth/index.d.ts.map → core/oauth/index.js.map} +1 -1
- package/dist/core/oauth/storage.d.ts.map +1 -0
- package/dist/{oauth → core/oauth}/storage.js +1 -1
- package/dist/core/oauth/storage.js.map +1 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/{session-manager.js → core/session-manager.js} +1 -1
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -0
- package/dist/{settings-manager.js → core/settings-manager.js} +1 -1
- package/dist/core/settings-manager.js.map +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -0
- package/dist/{slash-commands.js → core/slash-commands.js} +1 -1
- package/dist/core/slash-commands.js.map +1 -0
- package/dist/core/system-prompt.d.ts +17 -0
- package/dist/core/system-prompt.d.ts.map +1 -0
- package/dist/core/system-prompt.js +203 -0
- package/dist/core/system-prompt.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/{tools → core/tools}/bash.js +1 -1
- package/dist/core/tools/bash.js.map +1 -0
- package/dist/core/tools/edit.d.ts.map +1 -0
- package/dist/core/tools/edit.js.map +1 -0
- package/dist/core/tools/find.d.ts.map +1 -0
- package/dist/{tools → core/tools}/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/{tools → core/tools}/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/ls.d.ts.map +1 -0
- package/dist/core/tools/ls.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -0
- package/dist/core/tools/read.js.map +1 -0
- package/dist/core/tools/truncate.d.ts.map +1 -0
- package/dist/core/tools/truncate.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -0
- package/dist/core/tools/write.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +176 -1082
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +7 -0
- package/dist/modes/index.d.ts.map +1 -0
- package/dist/modes/index.js +8 -0
- package/dist/modes/index.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -0
- package/dist/{tui → modes/interactive/components}/bash-execution.d.ts +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/bash-execution.js +1 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -0
- package/dist/modes/interactive/components/compaction.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction.js.map +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -0
- package/dist/{tui → modes/interactive/components}/model-selector.d.ts +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/model-selector.js +3 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -0
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/oauth-selector.js +2 -2
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
- package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/queue-mode-selector.js.map +1 -0
- package/dist/{tui → modes/interactive/components}/session-selector.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
- package/dist/{tui → modes/interactive/components}/session-selector.js +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -0
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
- package/dist/{tui/theme-selector.d.ts.map → modes/interactive/components/theme-selector.js.map} +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/user-message.js.map +1 -0
- package/dist/{tui/tui-renderer.d.ts → modes/interactive/interactive-mode.d.ts} +36 -38
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-mode.js +1217 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/dist/{theme → modes/interactive/theme}/theme.js +1 -1
- package/dist/modes/interactive/theme/theme.js.map +1 -0
- package/dist/modes/print-mode.d.ts +21 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +53 -0
- package/dist/modes/print-mode.js.map +1 -0
- package/dist/modes/rpc-mode.d.ts +21 -0
- package/dist/modes/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc-mode.js +77 -0
- package/dist/modes/rpc-mode.js.map +1 -0
- package/dist/{changelog.d.ts → utils/changelog.d.ts} +1 -1
- package/dist/{changelog.js.map → utils/changelog.d.ts.map} +1 -1
- package/dist/{changelog.js → utils/changelog.js} +1 -1
- package/dist/utils/changelog.js.map +1 -0
- package/dist/utils/clipboard.d.ts.map +1 -0
- package/dist/utils/clipboard.js.map +1 -0
- package/dist/utils/fuzzy.d.ts.map +1 -0
- package/dist/utils/fuzzy.js.map +1 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/{shell.js → utils/shell.js} +1 -1
- package/dist/utils/shell.js.map +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -0
- package/dist/{tools-manager.js → utils/tools-manager.js} +1 -1
- package/dist/utils/tools-manager.js.map +1 -0
- package/package.json +6 -6
- package/dist/changelog.d.ts.map +0 -1
- package/dist/clipboard.d.ts.map +0 -1
- package/dist/clipboard.js.map +0 -1
- package/dist/compaction.d.ts.map +0 -1
- package/dist/compaction.js.map +0 -1
- package/dist/export-html.js.map +0 -1
- package/dist/fuzzy.d.ts.map +0 -1
- package/dist/fuzzy.js.map +0 -1
- package/dist/messages.d.ts.map +0 -1
- package/dist/messages.js.map +0 -1
- package/dist/model-config.d.ts.map +0 -1
- package/dist/model-config.js.map +0 -1
- package/dist/oauth/anthropic.d.ts.map +0 -1
- package/dist/oauth/anthropic.js.map +0 -1
- package/dist/oauth/index.js.map +0 -1
- package/dist/oauth/storage.d.ts.map +0 -1
- package/dist/oauth/storage.js.map +0 -1
- package/dist/session-manager.d.ts.map +0 -1
- package/dist/session-manager.js.map +0 -1
- package/dist/settings-manager.d.ts.map +0 -1
- package/dist/settings-manager.js.map +0 -1
- package/dist/shell.d.ts.map +0 -1
- package/dist/shell.js.map +0 -1
- package/dist/slash-commands.d.ts.map +0 -1
- package/dist/slash-commands.js.map +0 -1
- package/dist/theme/theme.d.ts.map +0 -1
- package/dist/theme/theme.js.map +0 -1
- package/dist/tools/bash.d.ts.map +0 -1
- package/dist/tools/bash.js.map +0 -1
- package/dist/tools/edit.d.ts.map +0 -1
- package/dist/tools/edit.js.map +0 -1
- package/dist/tools/find.d.ts.map +0 -1
- package/dist/tools/find.js.map +0 -1
- package/dist/tools/grep.d.ts.map +0 -1
- package/dist/tools/grep.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/ls.d.ts.map +0 -1
- package/dist/tools/ls.js.map +0 -1
- package/dist/tools/read.d.ts.map +0 -1
- package/dist/tools/read.js.map +0 -1
- package/dist/tools/truncate.d.ts.map +0 -1
- package/dist/tools/truncate.js.map +0 -1
- package/dist/tools/write.d.ts.map +0 -1
- package/dist/tools/write.js.map +0 -1
- package/dist/tools-manager.d.ts.map +0 -1
- package/dist/tools-manager.js.map +0 -1
- package/dist/tui/assistant-message.d.ts.map +0 -1
- package/dist/tui/assistant-message.js.map +0 -1
- package/dist/tui/bash-execution.d.ts.map +0 -1
- package/dist/tui/bash-execution.js.map +0 -1
- package/dist/tui/compaction.d.ts.map +0 -1
- package/dist/tui/compaction.js.map +0 -1
- package/dist/tui/custom-editor.d.ts.map +0 -1
- package/dist/tui/custom-editor.js.map +0 -1
- package/dist/tui/dynamic-border.d.ts.map +0 -1
- package/dist/tui/dynamic-border.js.map +0 -1
- package/dist/tui/footer.d.ts.map +0 -1
- package/dist/tui/footer.js.map +0 -1
- package/dist/tui/model-selector.d.ts.map +0 -1
- package/dist/tui/model-selector.js.map +0 -1
- package/dist/tui/oauth-selector.d.ts.map +0 -1
- package/dist/tui/oauth-selector.js.map +0 -1
- package/dist/tui/queue-mode-selector.d.ts.map +0 -1
- package/dist/tui/queue-mode-selector.js.map +0 -1
- package/dist/tui/session-selector.d.ts.map +0 -1
- package/dist/tui/session-selector.js.map +0 -1
- package/dist/tui/theme-selector.js.map +0 -1
- package/dist/tui/thinking-selector.d.ts.map +0 -1
- package/dist/tui/thinking-selector.js.map +0 -1
- package/dist/tui/tool-execution.d.ts.map +0 -1
- package/dist/tui/tool-execution.js.map +0 -1
- package/dist/tui/tui-renderer.d.ts.map +0 -1
- package/dist/tui/tui-renderer.js +0 -1937
- package/dist/tui/tui-renderer.js.map +0 -1
- package/dist/tui/user-message-selector.d.ts.map +0 -1
- package/dist/tui/user-message-selector.js.map +0 -1
- package/dist/tui/user-message.d.ts.map +0 -1
- package/dist/tui/user-message.js.map +0 -1
- /package/dist/{export-html.d.ts → core/export-html.d.ts} +0 -0
- /package/dist/{messages.d.ts → core/messages.d.ts} +0 -0
- /package/dist/{messages.js → core/messages.js} +0 -0
- /package/dist/{model-config.d.ts → core/model-config.d.ts} +0 -0
- /package/dist/{oauth → core/oauth}/anthropic.d.ts +0 -0
- /package/dist/{oauth → core/oauth}/anthropic.js +0 -0
- /package/dist/{oauth → core/oauth}/index.d.ts +0 -0
- /package/dist/{oauth → core/oauth}/index.js +0 -0
- /package/dist/{oauth → core/oauth}/storage.d.ts +0 -0
- /package/dist/{session-manager.d.ts → core/session-manager.d.ts} +0 -0
- /package/dist/{settings-manager.d.ts → core/settings-manager.d.ts} +0 -0
- /package/dist/{slash-commands.d.ts → core/slash-commands.d.ts} +0 -0
- /package/dist/{tools → core/tools}/bash.d.ts +0 -0
- /package/dist/{tools → core/tools}/edit.d.ts +0 -0
- /package/dist/{tools → core/tools}/edit.js +0 -0
- /package/dist/{tools → core/tools}/find.d.ts +0 -0
- /package/dist/{tools → core/tools}/grep.d.ts +0 -0
- /package/dist/{tools → core/tools}/index.d.ts +0 -0
- /package/dist/{tools → core/tools}/index.js +0 -0
- /package/dist/{tools → core/tools}/ls.d.ts +0 -0
- /package/dist/{tools → core/tools}/ls.js +0 -0
- /package/dist/{tools → core/tools}/read.d.ts +0 -0
- /package/dist/{tools → core/tools}/read.js +0 -0
- /package/dist/{tools → core/tools}/truncate.d.ts +0 -0
- /package/dist/{tools → core/tools}/truncate.js +0 -0
- /package/dist/{tools → core/tools}/write.d.ts +0 -0
- /package/dist/{tools → core/tools}/write.js +0 -0
- /package/dist/{tui → modes/interactive/components}/assistant-message.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/assistant-message.js +0 -0
- /package/dist/{tui → modes/interactive/components}/compaction.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/compaction.js +0 -0
- /package/dist/{tui → modes/interactive/components}/custom-editor.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/custom-editor.js +0 -0
- /package/dist/{tui → modes/interactive/components}/dynamic-border.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/dynamic-border.js +0 -0
- /package/dist/{tui → modes/interactive/components}/footer.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/oauth-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/queue-mode-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/queue-mode-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/theme-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/theme-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/thinking-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/thinking-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/tool-execution.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/tool-execution.js +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message-selector.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message-selector.js +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message.d.ts +0 -0
- /package/dist/{tui → modes/interactive/components}/user-message.js +0 -0
- /package/dist/{theme → modes/interactive/theme}/dark.json +0 -0
- /package/dist/{theme → modes/interactive/theme}/light.json +0 -0
- /package/dist/{theme → modes/interactive/theme}/theme-schema.json +0 -0
- /package/dist/{theme → modes/interactive/theme}/theme.d.ts +0 -0
- /package/dist/{clipboard.d.ts → utils/clipboard.d.ts} +0 -0
- /package/dist/{clipboard.js → utils/clipboard.js} +0 -0
- /package/dist/{fuzzy.d.ts → utils/fuzzy.d.ts} +0 -0
- /package/dist/{fuzzy.js → utils/fuzzy.js} +0 -0
- /package/dist/{shell.d.ts → utils/shell.d.ts} +0 -0
- /package/dist/{tools-manager.d.ts → utils/tools-manager.d.ts} +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model resolution, scoping, and initial selection
|
|
3
|
+
*/
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { isValidThinkingLevel } from "../cli/args.js";
|
|
6
|
+
import { findModel, getApiKeyForModel, getAvailableModels } from "./model-config.js";
|
|
7
|
+
/** Default model IDs for each known provider */
|
|
8
|
+
export const defaultModelPerProvider = {
|
|
9
|
+
anthropic: "claude-sonnet-4-5",
|
|
10
|
+
openai: "gpt-5.1-codex",
|
|
11
|
+
google: "gemini-2.5-pro",
|
|
12
|
+
openrouter: "openai/gpt-5.1-codex",
|
|
13
|
+
xai: "grok-4-fast-non-reasoning",
|
|
14
|
+
groq: "openai/gpt-oss-120b",
|
|
15
|
+
cerebras: "zai-glm-4.6",
|
|
16
|
+
zai: "glm-4.6",
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Resolve model patterns to actual Model objects with optional thinking levels
|
|
20
|
+
* Format: "pattern:level" where :level is optional
|
|
21
|
+
* For each pattern, finds all matching models and picks the best version:
|
|
22
|
+
* 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)
|
|
23
|
+
* 2. If no alias, pick the latest dated version
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveModelScope(patterns) {
|
|
26
|
+
const { models: availableModels, error } = await getAvailableModels();
|
|
27
|
+
if (error) {
|
|
28
|
+
console.warn(chalk.yellow(`Warning: Error loading models: ${error}`));
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const scopedModels = [];
|
|
32
|
+
for (const pattern of patterns) {
|
|
33
|
+
// Parse pattern:level format
|
|
34
|
+
const parts = pattern.split(":");
|
|
35
|
+
const modelPattern = parts[0];
|
|
36
|
+
let thinkingLevel = "off";
|
|
37
|
+
if (parts.length > 1) {
|
|
38
|
+
const level = parts[1];
|
|
39
|
+
if (isValidThinkingLevel(level)) {
|
|
40
|
+
thinkingLevel = level;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.warn(chalk.yellow(`Warning: Invalid thinking level "${level}" in pattern "${pattern}". Using "off" instead.`));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Check for provider/modelId format (provider is everything before the first /)
|
|
47
|
+
const slashIndex = modelPattern.indexOf("/");
|
|
48
|
+
if (slashIndex !== -1) {
|
|
49
|
+
const provider = modelPattern.substring(0, slashIndex);
|
|
50
|
+
const modelId = modelPattern.substring(slashIndex + 1);
|
|
51
|
+
const providerMatch = availableModels.find((m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase());
|
|
52
|
+
if (providerMatch) {
|
|
53
|
+
if (!scopedModels.find((sm) => sm.model.id === providerMatch.id && sm.model.provider === providerMatch.provider)) {
|
|
54
|
+
scopedModels.push({ model: providerMatch, thinkingLevel });
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// No exact provider/model match - fall through to other matching
|
|
59
|
+
}
|
|
60
|
+
// Check for exact ID match (case-insensitive)
|
|
61
|
+
const exactMatch = availableModels.find((m) => m.id.toLowerCase() === modelPattern.toLowerCase());
|
|
62
|
+
if (exactMatch) {
|
|
63
|
+
// Exact match found - use it directly
|
|
64
|
+
if (!scopedModels.find((sm) => sm.model.id === exactMatch.id && sm.model.provider === exactMatch.provider)) {
|
|
65
|
+
scopedModels.push({ model: exactMatch, thinkingLevel });
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// No exact match - fall back to partial matching
|
|
70
|
+
const matches = availableModels.filter((m) => m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
|
|
71
|
+
m.name?.toLowerCase().includes(modelPattern.toLowerCase()));
|
|
72
|
+
if (matches.length === 0) {
|
|
73
|
+
console.warn(chalk.yellow(`Warning: No models match pattern "${modelPattern}"`));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Helper to check if a model ID looks like an alias (no date suffix)
|
|
77
|
+
// Dates are typically in format: -20241022 or -20250929
|
|
78
|
+
const isAlias = (id) => {
|
|
79
|
+
// Check if ID ends with -latest
|
|
80
|
+
if (id.endsWith("-latest"))
|
|
81
|
+
return true;
|
|
82
|
+
// Check if ID ends with a date pattern (-YYYYMMDD)
|
|
83
|
+
const datePattern = /-\d{8}$/;
|
|
84
|
+
return !datePattern.test(id);
|
|
85
|
+
};
|
|
86
|
+
// Separate into aliases and dated versions
|
|
87
|
+
const aliases = matches.filter((m) => isAlias(m.id));
|
|
88
|
+
const datedVersions = matches.filter((m) => !isAlias(m.id));
|
|
89
|
+
let bestMatch;
|
|
90
|
+
if (aliases.length > 0) {
|
|
91
|
+
// Prefer alias - if multiple aliases, pick the one that sorts highest
|
|
92
|
+
aliases.sort((a, b) => b.id.localeCompare(a.id));
|
|
93
|
+
bestMatch = aliases[0];
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// No alias found, pick latest dated version
|
|
97
|
+
datedVersions.sort((a, b) => b.id.localeCompare(a.id));
|
|
98
|
+
bestMatch = datedVersions[0];
|
|
99
|
+
}
|
|
100
|
+
// Avoid duplicates
|
|
101
|
+
if (!scopedModels.find((sm) => sm.model.id === bestMatch.id && sm.model.provider === bestMatch.provider)) {
|
|
102
|
+
scopedModels.push({ model: bestMatch, thinkingLevel });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return scopedModels;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Find the initial model to use based on priority:
|
|
109
|
+
* 1. CLI args (provider + model)
|
|
110
|
+
* 2. First model from scoped models (if not continuing/resuming)
|
|
111
|
+
* 3. Restored from session (if continuing/resuming)
|
|
112
|
+
* 4. Saved default from settings
|
|
113
|
+
* 5. First available model with valid API key
|
|
114
|
+
*/
|
|
115
|
+
export async function findInitialModel(options) {
|
|
116
|
+
const { cliProvider, cliModel, scopedModels, isContinuing, settingsManager } = options;
|
|
117
|
+
let model = null;
|
|
118
|
+
let thinkingLevel = "off";
|
|
119
|
+
// 1. CLI args take priority
|
|
120
|
+
if (cliProvider && cliModel) {
|
|
121
|
+
const { model: found, error } = findModel(cliProvider, cliModel);
|
|
122
|
+
if (error) {
|
|
123
|
+
console.error(chalk.red(error));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
if (!found) {
|
|
127
|
+
console.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
return { model: found, thinkingLevel: "off", fallbackMessage: null };
|
|
131
|
+
}
|
|
132
|
+
// 2. Use first model from scoped models (skip if continuing/resuming)
|
|
133
|
+
if (scopedModels.length > 0 && !isContinuing) {
|
|
134
|
+
return {
|
|
135
|
+
model: scopedModels[0].model,
|
|
136
|
+
thinkingLevel: scopedModels[0].thinkingLevel,
|
|
137
|
+
fallbackMessage: null,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// 3. Try saved default from settings
|
|
141
|
+
const defaultProvider = settingsManager.getDefaultProvider();
|
|
142
|
+
const defaultModelId = settingsManager.getDefaultModel();
|
|
143
|
+
if (defaultProvider && defaultModelId) {
|
|
144
|
+
const { model: found, error } = findModel(defaultProvider, defaultModelId);
|
|
145
|
+
if (error) {
|
|
146
|
+
console.error(chalk.red(error));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
if (found) {
|
|
150
|
+
model = found;
|
|
151
|
+
// Also load saved thinking level
|
|
152
|
+
const savedThinking = settingsManager.getDefaultThinkingLevel();
|
|
153
|
+
if (savedThinking) {
|
|
154
|
+
thinkingLevel = savedThinking;
|
|
155
|
+
}
|
|
156
|
+
return { model, thinkingLevel, fallbackMessage: null };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// 4. Try first available model with valid API key
|
|
160
|
+
const { models: availableModels, error } = await getAvailableModels();
|
|
161
|
+
if (error) {
|
|
162
|
+
console.error(chalk.red(error));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
if (availableModels.length > 0) {
|
|
166
|
+
// Try to find a default model from known providers
|
|
167
|
+
for (const provider of Object.keys(defaultModelPerProvider)) {
|
|
168
|
+
const defaultId = defaultModelPerProvider[provider];
|
|
169
|
+
const match = availableModels.find((m) => m.provider === provider && m.id === defaultId);
|
|
170
|
+
if (match) {
|
|
171
|
+
return { model: match, thinkingLevel: "off", fallbackMessage: null };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// If no default found, use first available
|
|
175
|
+
return { model: availableModels[0], thinkingLevel: "off", fallbackMessage: null };
|
|
176
|
+
}
|
|
177
|
+
// 5. No model found
|
|
178
|
+
return { model: null, thinkingLevel: "off", fallbackMessage: null };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Restore model from session, with fallback to available models
|
|
182
|
+
*/
|
|
183
|
+
export async function restoreModelFromSession(savedProvider, savedModelId, currentModel, shouldPrintMessages) {
|
|
184
|
+
const { model: restoredModel, error } = findModel(savedProvider, savedModelId);
|
|
185
|
+
if (error) {
|
|
186
|
+
console.error(chalk.red(error));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
// Check if restored model exists and has a valid API key
|
|
190
|
+
const hasApiKey = restoredModel ? !!(await getApiKeyForModel(restoredModel)) : false;
|
|
191
|
+
if (restoredModel && hasApiKey) {
|
|
192
|
+
if (shouldPrintMessages) {
|
|
193
|
+
console.log(chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`));
|
|
194
|
+
}
|
|
195
|
+
return { model: restoredModel, fallbackMessage: null };
|
|
196
|
+
}
|
|
197
|
+
// Model not found or no API key - fall back
|
|
198
|
+
const reason = !restoredModel ? "model no longer exists" : "no API key available";
|
|
199
|
+
if (shouldPrintMessages) {
|
|
200
|
+
console.error(chalk.yellow(`Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`));
|
|
201
|
+
}
|
|
202
|
+
// If we already have a model, use it as fallback
|
|
203
|
+
if (currentModel) {
|
|
204
|
+
if (shouldPrintMessages) {
|
|
205
|
+
console.log(chalk.dim(`Falling back to: ${currentModel.provider}/${currentModel.id}`));
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
model: currentModel,
|
|
209
|
+
fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Try to find any available model
|
|
213
|
+
const { models: availableModels, error: availableError } = await getAvailableModels();
|
|
214
|
+
if (availableError) {
|
|
215
|
+
console.error(chalk.red(availableError));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
if (availableModels.length > 0) {
|
|
219
|
+
// Try to find a default model from known providers
|
|
220
|
+
let fallbackModel = null;
|
|
221
|
+
for (const provider of Object.keys(defaultModelPerProvider)) {
|
|
222
|
+
const defaultId = defaultModelPerProvider[provider];
|
|
223
|
+
const match = availableModels.find((m) => m.provider === provider && m.id === defaultId);
|
|
224
|
+
if (match) {
|
|
225
|
+
fallbackModel = match;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// If no default found, use first available
|
|
230
|
+
if (!fallbackModel) {
|
|
231
|
+
fallbackModel = availableModels[0];
|
|
232
|
+
}
|
|
233
|
+
if (shouldPrintMessages) {
|
|
234
|
+
console.log(chalk.dim(`Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`));
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
model: fallbackModel,
|
|
238
|
+
fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// No models available
|
|
242
|
+
return { model: null, fallbackMessage: null };
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=model-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-resolver.js","sourceRoot":"","sources":["../../src/core/model-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model resolution, scoping, and initial selection\n */\n\nimport type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport type { Api, KnownProvider, Model } from \"@mariozechner/pi-ai\";\nimport chalk from \"chalk\";\nimport { isValidThinkingLevel } from \"../cli/args.js\";\nimport { findModel, getApiKeyForModel, getAvailableModels } from \"./model-config.js\";\nimport type { SettingsManager } from \"./settings-manager.js\";\n\n/** Default model IDs for each known provider */\nexport const defaultModelPerProvider: Record<KnownProvider, string> = {\n\tanthropic: \"claude-sonnet-4-5\",\n\topenai: \"gpt-5.1-codex\",\n\tgoogle: \"gemini-2.5-pro\",\n\topenrouter: \"openai/gpt-5.1-codex\",\n\txai: \"grok-4-fast-non-reasoning\",\n\tgroq: \"openai/gpt-oss-120b\",\n\tcerebras: \"zai-glm-4.6\",\n\tzai: \"glm-4.6\",\n};\n\nexport interface ScopedModel {\n\tmodel: Model<Api>;\n\tthinkingLevel: ThinkingLevel;\n}\n\n/**\n * Resolve model patterns to actual Model objects with optional thinking levels\n * Format: \"pattern:level\" where :level is optional\n * For each pattern, finds all matching models and picks the best version:\n * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)\n * 2. If no alias, pick the latest dated version\n */\nexport async function resolveModelScope(patterns: string[]): Promise<ScopedModel[]> {\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.warn(chalk.yellow(`Warning: Error loading models: ${error}`));\n\t\treturn [];\n\t}\n\n\tconst scopedModels: ScopedModel[] = [];\n\n\tfor (const pattern of patterns) {\n\t\t// Parse pattern:level format\n\t\tconst parts = pattern.split(\":\");\n\t\tconst modelPattern = parts[0];\n\t\tlet thinkingLevel: ThinkingLevel = \"off\";\n\n\t\tif (parts.length > 1) {\n\t\t\tconst level = parts[1];\n\t\t\tif (isValidThinkingLevel(level)) {\n\t\t\t\tthinkingLevel = level;\n\t\t\t} else {\n\t\t\t\tconsole.warn(\n\t\t\t\t\tchalk.yellow(`Warning: Invalid thinking level \"${level}\" in pattern \"${pattern}\". Using \"off\" instead.`),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Check for provider/modelId format (provider is everything before the first /)\n\t\tconst slashIndex = modelPattern.indexOf(\"/\");\n\t\tif (slashIndex !== -1) {\n\t\t\tconst provider = modelPattern.substring(0, slashIndex);\n\t\t\tconst modelId = modelPattern.substring(slashIndex + 1);\n\t\t\tconst providerMatch = availableModels.find(\n\t\t\t\t(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),\n\t\t\t);\n\t\t\tif (providerMatch) {\n\t\t\t\tif (\n\t\t\t\t\t!scopedModels.find(\n\t\t\t\t\t\t(sm) => sm.model.id === providerMatch.id && sm.model.provider === providerMatch.provider,\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\tscopedModels.push({ model: providerMatch, thinkingLevel });\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// No exact provider/model match - fall through to other matching\n\t\t}\n\n\t\t// Check for exact ID match (case-insensitive)\n\t\tconst exactMatch = availableModels.find((m) => m.id.toLowerCase() === modelPattern.toLowerCase());\n\t\tif (exactMatch) {\n\t\t\t// Exact match found - use it directly\n\t\t\tif (!scopedModels.find((sm) => sm.model.id === exactMatch.id && sm.model.provider === exactMatch.provider)) {\n\t\t\t\tscopedModels.push({ model: exactMatch, thinkingLevel });\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// No exact match - fall back to partial matching\n\t\tconst matches = availableModels.filter(\n\t\t\t(m) =>\n\t\t\t\tm.id.toLowerCase().includes(modelPattern.toLowerCase()) ||\n\t\t\t\tm.name?.toLowerCase().includes(modelPattern.toLowerCase()),\n\t\t);\n\n\t\tif (matches.length === 0) {\n\t\t\tconsole.warn(chalk.yellow(`Warning: No models match pattern \"${modelPattern}\"`));\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Helper to check if a model ID looks like an alias (no date suffix)\n\t\t// Dates are typically in format: -20241022 or -20250929\n\t\tconst isAlias = (id: string): boolean => {\n\t\t\t// Check if ID ends with -latest\n\t\t\tif (id.endsWith(\"-latest\")) return true;\n\n\t\t\t// Check if ID ends with a date pattern (-YYYYMMDD)\n\t\t\tconst datePattern = /-\\d{8}$/;\n\t\t\treturn !datePattern.test(id);\n\t\t};\n\n\t\t// Separate into aliases and dated versions\n\t\tconst aliases = matches.filter((m) => isAlias(m.id));\n\t\tconst datedVersions = matches.filter((m) => !isAlias(m.id));\n\n\t\tlet bestMatch: Model<Api>;\n\n\t\tif (aliases.length > 0) {\n\t\t\t// Prefer alias - if multiple aliases, pick the one that sorts highest\n\t\t\taliases.sort((a, b) => b.id.localeCompare(a.id));\n\t\t\tbestMatch = aliases[0];\n\t\t} else {\n\t\t\t// No alias found, pick latest dated version\n\t\t\tdatedVersions.sort((a, b) => b.id.localeCompare(a.id));\n\t\t\tbestMatch = datedVersions[0];\n\t\t}\n\n\t\t// Avoid duplicates\n\t\tif (!scopedModels.find((sm) => sm.model.id === bestMatch.id && sm.model.provider === bestMatch.provider)) {\n\t\t\tscopedModels.push({ model: bestMatch, thinkingLevel });\n\t\t}\n\t}\n\n\treturn scopedModels;\n}\n\nexport interface InitialModelResult {\n\tmodel: Model<Api> | null;\n\tthinkingLevel: ThinkingLevel;\n\tfallbackMessage: string | null;\n}\n\n/**\n * Find the initial model to use based on priority:\n * 1. CLI args (provider + model)\n * 2. First model from scoped models (if not continuing/resuming)\n * 3. Restored from session (if continuing/resuming)\n * 4. Saved default from settings\n * 5. First available model with valid API key\n */\nexport async function findInitialModel(options: {\n\tcliProvider?: string;\n\tcliModel?: string;\n\tscopedModels: ScopedModel[];\n\tisContinuing: boolean;\n\tsettingsManager: SettingsManager;\n}): Promise<InitialModelResult> {\n\tconst { cliProvider, cliModel, scopedModels, isContinuing, settingsManager } = options;\n\n\tlet model: Model<Api> | null = null;\n\tlet thinkingLevel: ThinkingLevel = \"off\";\n\n\t// 1. CLI args take priority\n\tif (cliProvider && cliModel) {\n\t\tconst { model: found, error } = findModel(cliProvider, cliModel);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!found) {\n\t\t\tconsole.error(chalk.red(`Model ${cliProvider}/${cliModel} not found`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\treturn { model: found, thinkingLevel: \"off\", fallbackMessage: null };\n\t}\n\n\t// 2. Use first model from scoped models (skip if continuing/resuming)\n\tif (scopedModels.length > 0 && !isContinuing) {\n\t\treturn {\n\t\t\tmodel: scopedModels[0].model,\n\t\t\tthinkingLevel: scopedModels[0].thinkingLevel,\n\t\t\tfallbackMessage: null,\n\t\t};\n\t}\n\n\t// 3. Try saved default from settings\n\tconst defaultProvider = settingsManager.getDefaultProvider();\n\tconst defaultModelId = settingsManager.getDefaultModel();\n\tif (defaultProvider && defaultModelId) {\n\t\tconst { model: found, error } = findModel(defaultProvider, defaultModelId);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (found) {\n\t\t\tmodel = found;\n\t\t\t// Also load saved thinking level\n\t\t\tconst savedThinking = settingsManager.getDefaultThinkingLevel();\n\t\t\tif (savedThinking) {\n\t\t\t\tthinkingLevel = savedThinking;\n\t\t\t}\n\t\t\treturn { model, thinkingLevel, fallbackMessage: null };\n\t\t}\n\t}\n\n\t// 4. Try first available model with valid API key\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.error(chalk.red(error));\n\t\tprocess.exit(1);\n\t}\n\n\tif (availableModels.length > 0) {\n\t\t// Try to find a default model from known providers\n\t\tfor (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {\n\t\t\tconst defaultId = defaultModelPerProvider[provider];\n\t\t\tconst match = availableModels.find((m) => m.provider === provider && m.id === defaultId);\n\t\t\tif (match) {\n\t\t\t\treturn { model: match, thinkingLevel: \"off\", fallbackMessage: null };\n\t\t\t}\n\t\t}\n\n\t\t// If no default found, use first available\n\t\treturn { model: availableModels[0], thinkingLevel: \"off\", fallbackMessage: null };\n\t}\n\n\t// 5. No model found\n\treturn { model: null, thinkingLevel: \"off\", fallbackMessage: null };\n}\n\n/**\n * Restore model from session, with fallback to available models\n */\nexport async function restoreModelFromSession(\n\tsavedProvider: string,\n\tsavedModelId: string,\n\tcurrentModel: Model<Api> | null,\n\tshouldPrintMessages: boolean,\n): Promise<{ model: Model<Api> | null; fallbackMessage: string | null }> {\n\tconst { model: restoredModel, error } = findModel(savedProvider, savedModelId);\n\n\tif (error) {\n\t\tconsole.error(chalk.red(error));\n\t\tprocess.exit(1);\n\t}\n\n\t// Check if restored model exists and has a valid API key\n\tconst hasApiKey = restoredModel ? !!(await getApiKeyForModel(restoredModel)) : false;\n\n\tif (restoredModel && hasApiKey) {\n\t\tif (shouldPrintMessages) {\n\t\t\tconsole.log(chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`));\n\t\t}\n\t\treturn { model: restoredModel, fallbackMessage: null };\n\t}\n\n\t// Model not found or no API key - fall back\n\tconst reason = !restoredModel ? \"model no longer exists\" : \"no API key available\";\n\n\tif (shouldPrintMessages) {\n\t\tconsole.error(chalk.yellow(`Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`));\n\t}\n\n\t// If we already have a model, use it as fallback\n\tif (currentModel) {\n\t\tif (shouldPrintMessages) {\n\t\t\tconsole.log(chalk.dim(`Falling back to: ${currentModel.provider}/${currentModel.id}`));\n\t\t}\n\t\treturn {\n\t\t\tmodel: currentModel,\n\t\t\tfallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,\n\t\t};\n\t}\n\n\t// Try to find any available model\n\tconst { models: availableModels, error: availableError } = await getAvailableModels();\n\tif (availableError) {\n\t\tconsole.error(chalk.red(availableError));\n\t\tprocess.exit(1);\n\t}\n\n\tif (availableModels.length > 0) {\n\t\t// Try to find a default model from known providers\n\t\tlet fallbackModel: Model<Api> | null = null;\n\t\tfor (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {\n\t\t\tconst defaultId = defaultModelPerProvider[provider];\n\t\t\tconst match = availableModels.find((m) => m.provider === provider && m.id === defaultId);\n\t\t\tif (match) {\n\t\t\t\tfallbackModel = match;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// If no default found, use first available\n\t\tif (!fallbackModel) {\n\t\t\tfallbackModel = availableModels[0];\n\t\t}\n\n\t\tif (shouldPrintMessages) {\n\t\t\tconsole.log(chalk.dim(`Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`));\n\t\t}\n\n\t\treturn {\n\t\t\tmodel: fallbackModel,\n\t\t\tfallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,\n\t\t};\n\t}\n\n\t// No models available\n\treturn { model: null, fallbackMessage: null };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/core/oauth/anthropic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,cAAc,CAAC;AAiB3E;;GAEG;AACH,wBAAsB,cAAc,CACnC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAiEf;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiC3F","sourcesContent":["import { createHash, randomBytes } from \"crypto\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\";\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Login with Anthropic OAuth (device code flow)\n */\nexport async function loginAnthropic(\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Build authorization URL\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\n\tconst authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;\n\n\t// Notify caller with URL to open\n\tonAuthUrl(authUrl);\n\n\t// Wait for user to paste authorization code (format: code#state)\n\tconst authCode = await onPromptCode();\n\tconst splits = authCode.split(\"#\");\n\tconst code = splits[0];\n\tconst state = splits[1];\n\n\t// Exchange code for tokens\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode: code,\n\t\t\tstate: state,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t// Save credentials\n\tconst credentials: OAuthCredentials = {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n\n\tsaveOAuthCredentials(\"anthropic\", credentials);\n}\n\n/**\n * Refresh Anthropic OAuth token using refresh token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token refresh failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/core/oauth/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from \"crypto\";\nimport { type OAuthCredentials, saveOAuthCredentials } from \"./storage.js\";\n\nconst CLIENT_ID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\";\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\n/**\n * Generate PKCE code verifier and challenge\n */\nfunction generatePKCE(): { verifier: string; challenge: string } {\n\tconst verifier = randomBytes(32).toString(\"base64url\");\n\tconst challenge = createHash(\"sha256\").update(verifier).digest(\"base64url\");\n\treturn { verifier, challenge };\n}\n\n/**\n * Login with Anthropic OAuth (device code flow)\n */\nexport async function loginAnthropic(\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tconst { verifier, challenge } = generatePKCE();\n\n\t// Build authorization URL\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\n\tconst authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;\n\n\t// Notify caller with URL to open\n\tonAuthUrl(authUrl);\n\n\t// Wait for user to paste authorization code (format: code#state)\n\tconst authCode = await onPromptCode();\n\tconst splits = authCode.split(\"#\");\n\tconst code = splits[0];\n\tconst state = splits[1];\n\n\t// Exchange code for tokens\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode: code,\n\t\t\tstate: state,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t// Save credentials\n\tconst credentials: OAuthCredentials = {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n\n\tsaveOAuthCredentials(\"anthropic\", credentials);\n}\n\n/**\n * Refresh Anthropic OAuth token using refresh token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token refresh failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/oauth/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,kBAAkB,IAAI,6BAA6B,EAKnD,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,6BAA6B,IAAI,kBAAkB,EAAE,CAAC;AAE/D,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,sBAAsB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,EAAE,CAavD;AAED;;GAEG;AACH,wBAAsB,KAAK,CAC1B,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBpF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoB5F","sourcesContent":["import { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\nimport {\n\tlistOAuthProviders as listOAuthProvidersFromStorage,\n\tloadOAuthCredentials,\n\ttype OAuthCredentials,\n\tremoveOAuthCredentials,\n\tsaveOAuthCredentials,\n} from \"./storage.js\";\n\n// Re-export for convenience\nexport { listOAuthProvidersFromStorage as listOAuthProviders };\n\nexport type SupportedOAuthProvider = \"anthropic\" | \"github-copilot\";\n\nexport interface OAuthProviderInfo {\n\tid: SupportedOAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot (coming soon)\",\n\t\t\tavailable: false,\n\t\t},\n\t];\n}\n\n/**\n * Login with OAuth provider\n */\nexport async function login(\n\tprovider: SupportedOAuthProvider,\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tawait loginAnthropic(onAuthUrl, onPromptCode);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n}\n\n/**\n * Logout from OAuth provider\n */\nexport async function logout(provider: SupportedOAuthProvider): Promise<void> {\n\tremoveOAuthCredentials(provider);\n}\n\n/**\n * Refresh OAuth token for provider\n */\nexport async function refreshToken(provider: SupportedOAuthProvider): Promise<string> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\t// Save new credentials\n\tsaveOAuthCredentials(provider, newCredentials);\n\n\treturn newCredentials.access;\n}\n\n/**\n * Get OAuth token for provider (auto-refreshes if expired)\n */\nexport async function getOAuthToken(provider: SupportedOAuthProvider): Promise<string | null> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\treturn null;\n\t}\n\n\t// Check if token is expired (with 5 min buffer already applied)\n\tif (Date.now() >= credentials.expires) {\n\t\t// Token expired - refresh it\n\t\ttry {\n\t\t\treturn await refreshToken(provider);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to refresh OAuth token for ${provider}:`, error);\n\t\t\t// Remove invalid credentials\n\t\t\tremoveOAuthCredentials(provider);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn credentials.access;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/oauth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACN,kBAAkB,IAAI,6BAA6B,EACnD,oBAAoB,EAEpB,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,cAAc,CAAC;AAEtB,4BAA4B;AAC5B,OAAO,EAAE,6BAA6B,IAAI,kBAAkB,EAAE,CAAC;AAU/D;;GAEG;AACH,MAAM,UAAU,iBAAiB,GAAwB;IACxD,OAAO;QACN;YACC,EAAE,EAAE,WAAW;YACf,IAAI,EAAE,4BAA4B;YAClC,SAAS,EAAE,IAAI;SACf;QACD;YACC,EAAE,EAAE,gBAAgB;YACpB,IAAI,EAAE,8BAA8B;YACpC,SAAS,EAAE,KAAK;SAChB;KACD,CAAC;AAAA,CACF;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAC1B,QAAgC,EAChC,SAAgC,EAChC,YAAmC,EACnB;IAChB,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,MAAM,cAAc,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YAC9C,MAAM;QACP,KAAK,gBAAgB;YACpB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAChE;YACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;AAAA,CACD;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAgC,EAAiB;IAC7E,sBAAsB,CAAC,QAAQ,CAAC,CAAC;AAAA,CACjC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgC,EAAmB;IACrF,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,cAAgC,CAAC;IAErC,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,cAAc,GAAG,MAAM,qBAAqB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClE,MAAM;QACP,KAAK,gBAAgB;YACpB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAChE;YACC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,uBAAuB;IACvB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAE/C,OAAO,cAAc,CAAC,MAAM,CAAC;AAAA,CAC7B;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgC,EAA0B;IAC7F,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,gEAAgE;IAChE,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QACvC,6BAA6B;QAC7B,IAAI,CAAC;YACJ,OAAO,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,qCAAqC,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;YACvE,6BAA6B;YAC7B,sBAAsB,CAAC,QAAQ,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,OAAO,WAAW,CAAC,MAAM,CAAC;AAAA,CAC1B","sourcesContent":["import { loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\nimport {\n\tlistOAuthProviders as listOAuthProvidersFromStorage,\n\tloadOAuthCredentials,\n\ttype OAuthCredentials,\n\tremoveOAuthCredentials,\n\tsaveOAuthCredentials,\n} from \"./storage.js\";\n\n// Re-export for convenience\nexport { listOAuthProvidersFromStorage as listOAuthProviders };\n\nexport type SupportedOAuthProvider = \"anthropic\" | \"github-copilot\";\n\nexport interface OAuthProviderInfo {\n\tid: SupportedOAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n\n/**\n * Get list of OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInfo[] {\n\treturn [\n\t\t{\n\t\t\tid: \"anthropic\",\n\t\t\tname: \"Anthropic (Claude Pro/Max)\",\n\t\t\tavailable: true,\n\t\t},\n\t\t{\n\t\t\tid: \"github-copilot\",\n\t\t\tname: \"GitHub Copilot (coming soon)\",\n\t\t\tavailable: false,\n\t\t},\n\t];\n}\n\n/**\n * Login with OAuth provider\n */\nexport async function login(\n\tprovider: SupportedOAuthProvider,\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<void> {\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tawait loginAnthropic(onAuthUrl, onPromptCode);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n}\n\n/**\n * Logout from OAuth provider\n */\nexport async function logout(provider: SupportedOAuthProvider): Promise<void> {\n\tremoveOAuthCredentials(provider);\n}\n\n/**\n * Refresh OAuth token for provider\n */\nexport async function refreshToken(provider: SupportedOAuthProvider): Promise<string> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\tthrow new Error(`No OAuth credentials found for ${provider}`);\n\t}\n\n\tlet newCredentials: OAuthCredentials;\n\n\tswitch (provider) {\n\t\tcase \"anthropic\":\n\t\t\tnewCredentials = await refreshAnthropicToken(credentials.refresh);\n\t\t\tbreak;\n\t\tcase \"github-copilot\":\n\t\t\tthrow new Error(\"GitHub Copilot OAuth is not yet implemented\");\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown OAuth provider: ${provider}`);\n\t}\n\n\t// Save new credentials\n\tsaveOAuthCredentials(provider, newCredentials);\n\n\treturn newCredentials.access;\n}\n\n/**\n * Get OAuth token for provider (auto-refreshes if expired)\n */\nexport async function getOAuthToken(provider: SupportedOAuthProvider): Promise<string | null> {\n\tconst credentials = loadOAuthCredentials(provider);\n\tif (!credentials) {\n\t\treturn null;\n\t}\n\n\t// Check if token is expired (with 5 min buffer already applied)\n\tif (Date.now() >= credentials.expires) {\n\t\t// Token expired - refresh it\n\t\ttry {\n\t\t\treturn await refreshToken(provider);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to refresh OAuth token for ${provider}:`, error);\n\t\t\t// Remove invalid credentials\n\t\t\tremoveOAuthCredentials(provider);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn credentials.access;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/core/oauth/storage.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CAChB;AA6CD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAG9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAIpF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAI7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAG7C","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { getAgentDir, getOAuthPath } from \"../../config.js\";\n\nexport interface OAuthCredentials {\n\ttype: \"oauth\";\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n}\n\ninterface OAuthStorageFormat {\n\t[provider: string]: OAuthCredentials;\n}\n\n/**\n * Ensure the config directory exists\n */\nfunction ensureConfigDir(): void {\n\tconst configDir = getAgentDir();\n\tif (!existsSync(configDir)) {\n\t\tmkdirSync(configDir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Load all OAuth credentials from oauth.json\n */\nfunction loadStorage(): OAuthStorageFormat {\n\tconst filePath = getOAuthPath();\n\tif (!existsSync(filePath)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\treturn JSON.parse(content);\n\t} catch (error) {\n\t\tconsole.error(`Warning: Failed to load OAuth credentials: ${error}`);\n\t\treturn {};\n\t}\n}\n\n/**\n * Save all OAuth credentials to oauth.json\n */\nfunction saveStorage(storage: OAuthStorageFormat): void {\n\tensureConfigDir();\n\tconst filePath = getOAuthPath();\n\twriteFileSync(filePath, JSON.stringify(storage, null, 2), \"utf-8\");\n\t// Set permissions to owner read/write only\n\tchmodSync(filePath, 0o600);\n}\n\n/**\n * Load OAuth credentials for a specific provider\n */\nexport function loadOAuthCredentials(provider: string): OAuthCredentials | null {\n\tconst storage = loadStorage();\n\treturn storage[provider] || null;\n}\n\n/**\n * Save OAuth credentials for a specific provider\n */\nexport function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void {\n\tconst storage = loadStorage();\n\tstorage[provider] = creds;\n\tsaveStorage(storage);\n}\n\n/**\n * Remove OAuth credentials for a specific provider\n */\nexport function removeOAuthCredentials(provider: string): void {\n\tconst storage = loadStorage();\n\tdelete storage[provider];\n\tsaveStorage(storage);\n}\n\n/**\n * List all providers with OAuth credentials\n */\nexport function listOAuthProviders(): string[] {\n\tconst storage = loadStorage();\n\treturn Object.keys(storage);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/core/oauth/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAa5D;;GAEG;AACH,SAAS,eAAe,GAAS;IAChC,MAAM,SAAS,GAAG,WAAW,EAAE,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC;AAAA,CACD;AAED;;GAEG;AACH,SAAS,WAAW,GAAuB;IAC1C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,8CAA8C,KAAK,EAAE,CAAC,CAAC;QACrE,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,OAA2B,EAAQ;IACvD,eAAe,EAAE,CAAC;IAClB,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACnE,2CAA2C;IAC3C,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAAA,CAC3B;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAA2B;IAC/E,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;AAAA,CACjC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,KAAuB,EAAQ;IACrF,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;IAC1B,WAAW,CAAC,OAAO,CAAC,CAAC;AAAA,CACrB;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAgB,EAAQ;IAC9D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzB,WAAW,CAAC,OAAO,CAAC,CAAC;AAAA,CACrB;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,GAAa;IAC9C,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAAA,CAC5B","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { getAgentDir, getOAuthPath } from \"../../config.js\";\n\nexport interface OAuthCredentials {\n\ttype: \"oauth\";\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n}\n\ninterface OAuthStorageFormat {\n\t[provider: string]: OAuthCredentials;\n}\n\n/**\n * Ensure the config directory exists\n */\nfunction ensureConfigDir(): void {\n\tconst configDir = getAgentDir();\n\tif (!existsSync(configDir)) {\n\t\tmkdirSync(configDir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Load all OAuth credentials from oauth.json\n */\nfunction loadStorage(): OAuthStorageFormat {\n\tconst filePath = getOAuthPath();\n\tif (!existsSync(filePath)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\treturn JSON.parse(content);\n\t} catch (error) {\n\t\tconsole.error(`Warning: Failed to load OAuth credentials: ${error}`);\n\t\treturn {};\n\t}\n}\n\n/**\n * Save all OAuth credentials to oauth.json\n */\nfunction saveStorage(storage: OAuthStorageFormat): void {\n\tensureConfigDir();\n\tconst filePath = getOAuthPath();\n\twriteFileSync(filePath, JSON.stringify(storage, null, 2), \"utf-8\");\n\t// Set permissions to owner read/write only\n\tchmodSync(filePath, 0o600);\n}\n\n/**\n * Load OAuth credentials for a specific provider\n */\nexport function loadOAuthCredentials(provider: string): OAuthCredentials | null {\n\tconst storage = loadStorage();\n\treturn storage[provider] || null;\n}\n\n/**\n * Save OAuth credentials for a specific provider\n */\nexport function saveOAuthCredentials(provider: string, creds: OAuthCredentials): void {\n\tconst storage = loadStorage();\n\tstorage[provider] = creds;\n\tsaveStorage(storage);\n}\n\n/**\n * Remove OAuth credentials for a specific provider\n */\nexport function removeOAuthCredentials(provider: string): void {\n\tconst storage = loadStorage();\n\tdelete storage[provider];\n\tsaveStorage(storage);\n}\n\n/**\n * List all providers with OAuth credentials\n */\nexport function listOAuthProviders(): string[] {\n\tconst storage = loadStorage();\n\treturn Object.keys(storage);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/core/session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState, AppMessage } from \"@mariozechner/pi-agent-core\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n// ============================================================================\n// Session entry types\n// ============================================================================\n\nexport interface SessionHeader {\n\ttype: \"session\";\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tprovider: string;\n\tmodelId: string;\n\tthinkingLevel: string;\n\tbranchedFrom?: string;\n}\n\nexport interface SessionMessageEntry {\n\ttype: \"message\";\n\ttimestamp: string;\n\tmessage: AppMessage;\n}\n\nexport interface ThinkingLevelChangeEntry {\n\ttype: \"thinking_level_change\";\n\ttimestamp: string;\n\tthinkingLevel: string;\n}\n\nexport interface ModelChangeEntry {\n\ttype: \"model_change\";\n\ttimestamp: string;\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport interface CompactionEntry {\n\ttype: \"compaction\";\n\ttimestamp: string;\n\tsummary: string;\n\tfirstKeptEntryIndex: number; // Index into session entries where we start keeping\n\ttokensBefore: number;\n}\n\n/** Union of all session entry types */\nexport type SessionEntry =\n\t| SessionHeader\n\t| SessionMessageEntry\n\t| ThinkingLevelChangeEntry\n\t| ModelChangeEntry\n\t| CompactionEntry;\n\n// ============================================================================\n// Session loading with compaction support\n// ============================================================================\n\nexport interface LoadedSession {\n\tmessages: AppMessage[];\n\tthinkingLevel: string;\n\tmodel: { provider: string; modelId: string } | null;\n}\n\nexport const SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:\n\n<summary>\n`;\n\nexport const SUMMARY_SUFFIX = `\n</summary>`;\n\n/**\n * Create a user message containing the summary with the standard prefix.\n */\nexport function createSummaryMessage(summary: string): AppMessage {\n\treturn {\n\t\trole: \"user\",\n\t\tcontent: SUMMARY_PREFIX + summary + SUMMARY_SUFFIX,\n\t\ttimestamp: Date.now(),\n\t};\n}\n\n/**\n * Parse session file content into entries.\n */\nexport function parseSessionEntries(content: string): SessionEntry[] {\n\tconst entries: SessionEntry[] = [];\n\tconst lines = content.trim().split(\"\\n\");\n\n\tfor (const line of lines) {\n\t\tif (!line.trim()) continue;\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\tentries.push(entry);\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\treturn entries;\n}\n\n/**\n * Load session from entries, handling compaction events.\n *\n * Algorithm:\n * 1. Find latest compaction event (if any)\n * 2. Keep all entries from firstKeptEntryIndex onwards (extracting messages)\n * 3. Prepend summary as user message\n */\n/**\n * Get the latest compaction entry from session entries, if any.\n */\nexport function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null {\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tif (entries[i].type === \"compaction\") {\n\t\t\treturn entries[i] as CompactionEntry;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function loadSessionFromEntries(entries: SessionEntry[]): LoadedSession {\n\t// Find model and thinking level (always scan all entries)\n\tlet thinkingLevel = \"off\";\n\tlet model: { provider: string; modelId: string } | null = null;\n\n\tfor (const entry of entries) {\n\t\tif (entry.type === \"session\") {\n\t\t\tthinkingLevel = entry.thinkingLevel;\n\t\t\tmodel = { provider: entry.provider, modelId: entry.modelId };\n\t\t} else if (entry.type === \"thinking_level_change\") {\n\t\t\tthinkingLevel = entry.thinkingLevel;\n\t\t} else if (entry.type === \"model_change\") {\n\t\t\tmodel = { provider: entry.provider, modelId: entry.modelId };\n\t\t}\n\t}\n\n\t// Find latest compaction event\n\tlet latestCompactionIndex = -1;\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tif (entries[i].type === \"compaction\") {\n\t\t\tlatestCompactionIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// No compaction: return all messages\n\tif (latestCompactionIndex === -1) {\n\t\tconst messages: AppMessage[] = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type === \"message\") {\n\t\t\t\tmessages.push(entry.message);\n\t\t\t}\n\t\t}\n\t\treturn { messages, thinkingLevel, model };\n\t}\n\n\tconst compactionEvent = entries[latestCompactionIndex] as CompactionEntry;\n\n\t// Extract messages from firstKeptEntryIndex to end (skipping compaction entries)\n\tconst keptMessages: AppMessage[] = [];\n\tfor (let i = compactionEvent.firstKeptEntryIndex; i < entries.length; i++) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"message\") {\n\t\t\tkeptMessages.push(entry.message);\n\t\t}\n\t}\n\n\t// Build final messages: summary + kept messages\n\tconst summaryMessage = createSummaryMessage(compactionEvent.summary);\n\tconst messages = [summaryMessage, ...keptMessages];\n\n\treturn { messages, thinkingLevel, model };\n}\n\nexport class SessionManager {\n\tprivate sessionId!: string;\n\tprivate sessionFile!: string;\n\tprivate sessionDir: string;\n\tprivate enabled: boolean = true;\n\tprivate sessionInitialized: boolean = false;\n\tprivate pendingMessages: any[] = [];\n\n\tconstructor(continueSession: boolean = false, customSessionPath?: string) {\n\t\tthis.sessionDir = this.getSessionDirectory();\n\n\t\tif (customSessionPath) {\n\t\t\t// Use custom session file path\n\t\t\tthis.sessionFile = resolve(customSessionPath);\n\t\t\tthis.loadSessionId();\n\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\tthis.sessionInitialized = existsSync(this.sessionFile);\n\t\t} else if (continueSession) {\n\t\t\tconst mostRecent = this.findMostRecentlyModifiedSession();\n\t\t\tif (mostRecent) {\n\t\t\t\tthis.sessionFile = mostRecent;\n\t\t\t\tthis.loadSessionId();\n\t\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\t\tthis.sessionInitialized = true;\n\t\t\t} else {\n\t\t\t\tthis.initNewSession();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.initNewSession();\n\t\t}\n\t}\n\n\t/** Disable session saving (for --no-session mode) */\n\tdisable() {\n\t\tthis.enabled = false;\n\t}\n\n\tprivate getSessionDirectory(): string {\n\t\tconst cwd = process.cwd();\n\t\t// Replace all path separators and colons (for Windows drive letters) with dashes\n\t\tconst safePath = \"--\" + cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\") + \"--\";\n\n\t\tconst configDir = getAgentDir();\n\t\tconst sessionDir = join(configDir, \"sessions\", safePath);\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tprivate initNewSession(): void {\n\t\tthis.sessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tthis.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`);\n\t}\n\n\t/** Reset to a fresh session. Clears pending messages and starts a new session file. */\n\treset(): void {\n\t\tthis.pendingMessages = [];\n\t\tthis.sessionInitialized = false;\n\t\tthis.initNewSession();\n\t}\n\n\tprivate findMostRecentlyModifiedSession(): string | null {\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => ({\n\t\t\t\t\tname: f,\n\t\t\t\t\tpath: join(this.sessionDir, f),\n\t\t\t\t\tmtime: statSync(join(this.sessionDir, f)).mtime,\n\t\t\t\t}))\n\t\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\t\treturn files[0]?.path || null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate loadSessionId(): void {\n\t\tif (!existsSync(this.sessionFile)) return;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\") {\n\t\t\t\t\tthis.sessionId = entry.id;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\t\tthis.sessionId = uuidv4();\n\t}\n\n\tstartSession(state: AgentState): void {\n\t\tif (!this.enabled || this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tprovider: state.model.provider,\n\t\t\tmodelId: state.model.id,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write any queued messages\n\t\tfor (const msg of this.pendingMessages) {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(msg) + \"\\n\");\n\t\t}\n\t\tthis.pendingMessages = [];\n\t}\n\n\tsaveMessage(message: any): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(provider: string, modelId: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveCompaction(entry: CompactionEntry): void {\n\t\tif (!this.enabled) return;\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t}\n\n\t/**\n\t * Load session data (messages, model, thinking level) with compaction support.\n\t */\n\tloadSession(): LoadedSession {\n\t\tconst entries = this.loadEntries();\n\t\treturn loadSessionFromEntries(entries);\n\t}\n\n\t/**\n\t * @deprecated Use loadSession().messages instead\n\t */\n\tloadMessages(): AppMessage[] {\n\t\treturn this.loadSession().messages;\n\t}\n\n\t/**\n\t * @deprecated Use loadSession().thinkingLevel instead\n\t */\n\tloadThinkingLevel(): string {\n\t\treturn this.loadSession().thinkingLevel;\n\t}\n\n\t/**\n\t * @deprecated Use loadSession().model instead\n\t */\n\tloadModel(): { provider: string; modelId: string } | null {\n\t\treturn this.loadSession().model;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.sessionFile;\n\t}\n\n\t/**\n\t * Load all entries from the session file.\n\t */\n\tloadEntries(): SessionEntry[] {\n\t\tif (!existsSync(this.sessionFile)) return [];\n\n\t\tconst content = readFileSync(this.sessionFile, \"utf8\");\n\t\tconst entries: SessionEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/**\n\t * Load all sessions for the current directory with metadata\n\t */\n\tloadAllSessions(): Array<{\n\t\tpath: string;\n\t\tid: string;\n\t\tcreated: Date;\n\t\tmodified: Date;\n\t\tmessageCount: number;\n\t\tfirstMessage: string;\n\t\tallMessagesText: string;\n\t}> {\n\t\tconst sessions: Array<{\n\t\t\tpath: string;\n\t\t\tid: string;\n\t\t\tcreated: Date;\n\t\t\tmodified: Date;\n\t\t\tmessageCount: number;\n\t\t\tfirstMessage: string;\n\t\t\tallMessagesText: string;\n\t\t}> = [];\n\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => join(this.sessionDir, f));\n\n\t\t\tfor (const file of files) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(file);\n\t\t\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\t\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\t\t\t\tlet sessionId = \"\";\n\t\t\t\t\tlet created = stats.birthtime;\n\t\t\t\t\tlet messageCount = 0;\n\t\t\t\t\tlet firstMessage = \"\";\n\t\t\t\t\tconst allMessages: string[] = [];\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst entry = JSON.parse(line);\n\n\t\t\t\t\t\t\t// Extract session ID from first session entry\n\t\t\t\t\t\t\tif (entry.type === \"session\" && !sessionId) {\n\t\t\t\t\t\t\t\tsessionId = entry.id;\n\t\t\t\t\t\t\t\tcreated = new Date(entry.timestamp);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Count messages and collect all text\n\t\t\t\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\t\t\t\tmessageCount++;\n\n\t\t\t\t\t\t\t\t// Extract text from user and assistant messages\n\t\t\t\t\t\t\t\tif (entry.message.role === \"user\" || entry.message.role === \"assistant\") {\n\t\t\t\t\t\t\t\t\tconst textContent = entry.message.content\n\t\t\t\t\t\t\t\t\t\t.filter((c: any) => c.type === \"text\")\n\t\t\t\t\t\t\t\t\t\t.map((c: any) => c.text)\n\t\t\t\t\t\t\t\t\t\t.join(\" \");\n\n\t\t\t\t\t\t\t\t\tif (textContent) {\n\t\t\t\t\t\t\t\t\t\tallMessages.push(textContent);\n\n\t\t\t\t\t\t\t\t\t\t// Get first user message for display\n\t\t\t\t\t\t\t\t\t\tif (!firstMessage && entry.message.role === \"user\") {\n\t\t\t\t\t\t\t\t\t\t\tfirstMessage = textContent;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Skip malformed lines\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsessions.push({\n\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\tid: sessionId || \"unknown\",\n\t\t\t\t\t\tcreated,\n\t\t\t\t\t\tmodified: stats.mtime,\n\t\t\t\t\t\tmessageCount,\n\t\t\t\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\t\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Skip files that can't be read\n\t\t\t\t\tconsole.error(`Failed to read session file ${file}:`, error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by modified date (most recent first)\n\t\t\tsessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load sessions:\", error);\n\t\t}\n\n\t\treturn sessions;\n\t}\n\n\t/**\n\t * Set the session file to an existing session\n\t */\n\tsetSessionFile(path: string): void {\n\t\tthis.sessionFile = path;\n\t\tthis.loadSessionId();\n\t\t// Mark as initialized since we're loading an existing session\n\t\tthis.sessionInitialized = existsSync(path);\n\t}\n\n\t/**\n\t * Check if we should initialize the session based on message history.\n\t * Session is initialized when we have at least 1 user message and 1 assistant message.\n\t */\n\tshouldInitializeSession(messages: any[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n\n\t/**\n\t * Create a branched session from a specific message index.\n\t * If branchFromIndex is -1, creates an empty session.\n\t * Returns the new session file path.\n\t */\n\tcreateBranchedSession(state: any, branchFromIndex: number): string {\n\t\t// Create a new session ID for the branch\n\t\tconst newSessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tconst newSessionFile = join(this.sessionDir, `${timestamp}_${newSessionId}.jsonl`);\n\n\t\t// Write session header\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: newSessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tprovider: state.model.provider,\n\t\t\tmodelId: state.model.id,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t\tbranchedFrom: this.sessionFile,\n\t\t};\n\t\tappendFileSync(newSessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write messages up to and including the branch point (if >= 0)\n\t\tif (branchFromIndex >= 0) {\n\t\t\tconst messagesToWrite = state.messages.slice(0, branchFromIndex + 1);\n\t\t\tfor (const message of messagesToWrite) {\n\t\t\t\tconst messageEntry: SessionMessageEntry = {\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tmessage,\n\t\t\t\t};\n\t\t\t\tappendFileSync(newSessionFile, JSON.stringify(messageEntry) + \"\\n\");\n\t\t\t}\n\t\t}\n\n\t\treturn newSessionFile;\n\t}\n\n\t/**\n\t * Create a branched session from session entries up to (but not including) a specific entry index.\n\t * This preserves compaction events and all entry types.\n\t * Returns the new session file path.\n\t */\n\tcreateBranchedSessionFromEntries(entries: SessionEntry[], branchBeforeIndex: number): string {\n\t\tconst newSessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tconst newSessionFile = join(this.sessionDir, `${timestamp}_${newSessionId}.jsonl`);\n\n\t\t// Copy all entries up to (but not including) the branch point\n\t\tfor (let i = 0; i < branchBeforeIndex; i++) {\n\t\t\tconst entry = entries[i];\n\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\t// Rewrite session header with new ID and branchedFrom\n\t\t\t\tconst newHeader: SessionHeader = {\n\t\t\t\t\t...entry,\n\t\t\t\t\tid: newSessionId,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tbranchedFrom: this.sessionFile,\n\t\t\t\t};\n\t\t\t\tappendFileSync(newSessionFile, JSON.stringify(newHeader) + \"\\n\");\n\t\t\t} else {\n\t\t\t\t// Copy other entries as-is\n\t\t\t\tappendFileSync(newSessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t\t}\n\t\t}\n\n\t\treturn newSessionFile;\n\t}\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "fs";
|
|
3
3
|
import { join, resolve } from "path";
|
|
4
|
-
import { getAgentDir } from "
|
|
4
|
+
import { getAgentDir } from "../config.js";
|
|
5
5
|
function uuidv4() {
|
|
6
6
|
const bytes = randomBytes(16);
|
|
7
7
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../src/core/session-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAChG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,SAAS,MAAM,GAAW;IACzB,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;AAAA,CAC/G;AA8DD,MAAM,CAAC,MAAM,cAAc,GAAG;;;CAG7B,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;WACnB,CAAC;AAEZ;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe,EAAc;IACjE,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,cAAc,GAAG,OAAO,GAAG,cAAc;QAClD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACrB,CAAC;AAAA,CACF;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAkB;IACpE,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAC3B,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACR,uBAAuB;QACxB,CAAC;IACF,CAAC;IAED,OAAO,OAAO,CAAC;AAAA,CACf;AAED;;;;;;;GAOG;AACH;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAuB,EAA0B;IACzF,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACtC,OAAO,OAAO,CAAC,CAAC,CAAoB,CAAC;QACtC,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAuB,EAAiB;IAC9E,0DAA0D;IAC1D,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,KAAK,GAAiD,IAAI,CAAC;IAE/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;YACpC,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QAC9D,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YACnD,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;QACrC,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAC1C,KAAK,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QAC9D,CAAC;IACF,CAAC;IAED,+BAA+B;IAC/B,IAAI,qBAAqB,GAAG,CAAC,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACtC,qBAAqB,GAAG,CAAC,CAAC;YAC1B,MAAM;QACP,CAAC;IACF,CAAC;IAED,qCAAqC;IACrC,IAAI,qBAAqB,KAAK,CAAC,CAAC,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAiB,EAAE,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC9B,CAAC;QACF,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAoB,CAAC;IAE1E,iFAAiF;IACjF,MAAM,YAAY,GAAiB,EAAE,CAAC;IACtC,KAAK,IAAI,CAAC,GAAG,eAAe,CAAC,mBAAmB,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3E,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;IACF,CAAC;IAED,gDAAgD;IAChD,MAAM,cAAc,GAAG,oBAAoB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAG,CAAC,cAAc,EAAE,GAAG,YAAY,CAAC,CAAC;IAEnD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AAAA,CAC1C;AAED,MAAM,OAAO,cAAc;IAClB,SAAS,CAAU;IACnB,WAAW,CAAU;IACrB,UAAU,CAAS;IACnB,OAAO,GAAY,IAAI,CAAC;IACxB,kBAAkB,GAAY,KAAK,CAAC;IACpC,eAAe,GAAU,EAAE,CAAC;IAEpC,YAAY,eAAe,GAAY,KAAK,EAAE,iBAA0B,EAAE;QACzE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAE7C,IAAI,iBAAiB,EAAE,CAAC;YACvB,+BAA+B;YAC/B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;YAC9C,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,8DAA8D;YAC9D,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxD,CAAC;aAAM,IAAI,eAAe,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,+BAA+B,EAAE,CAAC;YAC1D,IAAI,UAAU,EAAE,CAAC;gBAChB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;gBAC9B,IAAI,CAAC,aAAa,EAAE,CAAC;gBACrB,8DAA8D;gBAC9D,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACP,IAAI,CAAC,cAAc,EAAE,CAAC;YACvB,CAAC;QACF,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,cAAc,EAAE,CAAC;QACvB,CAAC;IAAA,CACD;IAED,qDAAqD;IACrD,OAAO,GAAG;QACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IAAA,CACrB;IAEO,mBAAmB,GAAW;QACrC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1B,iFAAiF;QACjF,MAAM,QAAQ,GAAG,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;QAEjF,MAAM,SAAS,GAAG,WAAW,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,UAAU,CAAC;IAAA,CAClB;IAEO,cAAc,GAAS;QAC9B,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACjE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,IAAI,IAAI,CAAC,SAAS,QAAQ,CAAC,CAAC;IAAA,CACjF;IAED,uFAAuF;IACvF,KAAK,GAAS;QACb,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;QAChC,IAAI,CAAC,cAAc,EAAE,CAAC;IAAA,CACtB;IAEO,+BAA+B,GAAkB;QACxD,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;iBACxC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;iBACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACZ,IAAI,EAAE,CAAC;gBACP,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;gBAC9B,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK;aAC/C,CAAC,CAAC;iBACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAExD,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,IAAI,CAAC;QACb,CAAC;IAAA,CACD;IAEO,aAAa,GAAS;QAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;YAAE,OAAO;QAE1C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;oBAC9B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC;oBAC1B,OAAO;gBACR,CAAC;YACF,CAAC;YAAC,MAAM,CAAC;gBACR,uBAAuB;YACxB,CAAC;QACF,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC;IAAA,CAC1B;IAED,YAAY,CAAC,KAAiB,EAAQ;QACrC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,kBAAkB;YAAE,OAAO;QACrD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAE/B,MAAM,KAAK,GAAkB;YAC5B,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,IAAI,CAAC,SAAS;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ;YAC9B,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;YACvB,aAAa,EAAE,KAAK,CAAC,aAAa;SAClC,CAAC;QACF,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAE/D,4BAA4B;QAC5B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACxC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;IAAA,CAC1B;IAED,WAAW,CAAC,OAAY,EAAQ;QAC/B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,KAAK,GAAwB;YAClC,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,OAAO;SACP,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACP,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,uBAAuB,CAAC,aAAqB,EAAQ;QACpD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,KAAK,GAA6B;YACvC,IAAI,EAAE,uBAAuB;YAC7B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,aAAa;SACb,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACP,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,eAAe,CAAC,QAAgB,EAAE,OAAe,EAAQ;QACxD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,KAAK,GAAqB;YAC/B,IAAI,EAAE,cAAc;YACpB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,QAAQ;YACR,OAAO;SACP,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACP,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IAAA,CACD;IAED,cAAc,CAAC,KAAsB,EAAQ;QAC5C,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAAA,CAC/D;IAED;;OAEG;IACH,WAAW,GAAkB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACnC,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAAA,CACvC;IAED;;OAEG;IACH,YAAY,GAAiB;QAC5B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC;IAAA,CACnC;IAED;;OAEG;IACH,iBAAiB,GAAW;QAC3B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,aAAa,CAAC;IAAA,CACxC;IAED;;OAEG;IACH,SAAS,GAAiD;QACzD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC;IAAA,CAChC;IAED,YAAY,GAAW;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC;IAAA,CACtB;IAED,cAAc,GAAW;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC;IAAA,CACxB;IAED;;OAEG;IACH,WAAW,GAAmB;QAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;YAAE,OAAO,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACvD,MAAM,OAAO,GAAmB,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS;YAC3B,IAAI,CAAC;gBACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;gBAC/C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACR,uBAAuB;YACxB,CAAC;QACF,CAAC;QAED,OAAO,OAAO,CAAC;IAAA,CACf;IAED;;OAEG;IACH,eAAe,GAQZ;QACF,MAAM,QAAQ,GAQT,EAAE,CAAC;QAER,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;iBACxC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;iBACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;YAEvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACJ,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;oBAC7B,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oBAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAEzC,IAAI,SAAS,GAAG,EAAE,CAAC;oBACnB,IAAI,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC;oBAC9B,IAAI,YAAY,GAAG,CAAC,CAAC;oBACrB,IAAI,YAAY,GAAG,EAAE,CAAC;oBACtB,MAAM,WAAW,GAAa,EAAE,CAAC;oBAEjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;wBAC1B,IAAI,CAAC;4BACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;4BAE/B,8CAA8C;4BAC9C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;gCAC5C,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC;gCACrB,OAAO,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;4BACrC,CAAC;4BAED,sCAAsC;4BACtC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gCAC9B,YAAY,EAAE,CAAC;gCAEf,gDAAgD;gCAChD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oCACzE,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO;yCACvC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;yCACrC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;yCACvB,IAAI,CAAC,GAAG,CAAC,CAAC;oCAEZ,IAAI,WAAW,EAAE,CAAC;wCACjB,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;wCAE9B,qCAAqC;wCACrC,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;4CACpD,YAAY,GAAG,WAAW,CAAC;wCAC5B,CAAC;oCACF,CAAC;gCACF,CAAC;4BACF,CAAC;wBACF,CAAC;wBAAC,MAAM,CAAC;4BACR,uBAAuB;wBACxB,CAAC;oBACF,CAAC;oBAED,QAAQ,CAAC,IAAI,CAAC;wBACb,IAAI,EAAE,IAAI;wBACV,EAAE,EAAE,SAAS,IAAI,SAAS;wBAC1B,OAAO;wBACP,QAAQ,EAAE,KAAK,CAAC,KAAK;wBACrB,YAAY;wBACZ,YAAY,EAAE,YAAY,IAAI,eAAe;wBAC7C,eAAe,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;qBACtC,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,gCAAgC;oBAChC,OAAO,CAAC,KAAK,CAAC,+BAA+B,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC9D,CAAC;YACF,CAAC;YAED,4CAA4C;YAC5C,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;QAClD,CAAC;QAED,OAAO,QAAQ,CAAC;IAAA,CAChB;IAED;;OAEG;IACH,cAAc,CAAC,IAAY,EAAQ;QAClC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,8DAA8D;QAC9D,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAAA,CAC3C;IAED;;;OAGG;IACH,uBAAuB,CAAC,QAAe,EAAW;QACjD,IAAI,IAAI,CAAC,kBAAkB;YAAE,OAAO,KAAK,CAAC;QAE1C,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QAC/D,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAEzE,OAAO,YAAY,CAAC,MAAM,IAAI,CAAC,IAAI,iBAAiB,CAAC,MAAM,IAAI,CAAC,CAAC;IAAA,CACjE;IAED;;;;OAIG;IACH,qBAAqB,CAAC,KAAU,EAAE,eAAuB,EAAU;QAClE,yCAAyC;QACzC,MAAM,YAAY,GAAG,MAAM,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,IAAI,YAAY,QAAQ,CAAC,CAAC;QAEnF,uBAAuB;QACvB,MAAM,KAAK,GAAkB;YAC5B,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,YAAY;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ;YAC9B,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;YACvB,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,YAAY,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC;QACF,cAAc,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAE7D,gEAAgE;QAChE,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC;YACrE,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;gBACvC,MAAM,YAAY,GAAwB;oBACzC,IAAI,EAAE,SAAS;oBACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,OAAO;iBACP,CAAC;gBACF,cAAc,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC;YACrE,CAAC;QACF,CAAC;QAED,OAAO,cAAc,CAAC;IAAA,CACtB;IAED;;;;OAIG;IACH,gCAAgC,CAAC,OAAuB,EAAE,iBAAyB,EAAU;QAC5F,MAAM,YAAY,GAAG,MAAM,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,IAAI,YAAY,QAAQ,CAAC,CAAC;QAEnF,8DAA8D;QAC9D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,iBAAiB,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAEzB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,sDAAsD;gBACtD,MAAM,SAAS,GAAkB;oBAChC,GAAG,KAAK;oBACR,EAAE,EAAE,YAAY;oBAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,YAAY,EAAE,IAAI,CAAC,WAAW;iBAC9B,CAAC;gBACF,cAAc,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;YAClE,CAAC;iBAAM,CAAC;gBACP,2BAA2B;gBAC3B,cAAc,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;YAC9D,CAAC;QACF,CAAC;QAED,OAAO,cAAc,CAAC;IAAA,CACtB;CACD","sourcesContent":["import type { AgentState, AppMessage } from \"@mariozechner/pi-agent-core\";\nimport { randomBytes } from \"crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nfunction uuidv4(): string {\n\tconst bytes = randomBytes(16);\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\tconst hex = bytes.toString(\"hex\");\n\treturn `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;\n}\n\n// ============================================================================\n// Session entry types\n// ============================================================================\n\nexport interface SessionHeader {\n\ttype: \"session\";\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tprovider: string;\n\tmodelId: string;\n\tthinkingLevel: string;\n\tbranchedFrom?: string;\n}\n\nexport interface SessionMessageEntry {\n\ttype: \"message\";\n\ttimestamp: string;\n\tmessage: AppMessage;\n}\n\nexport interface ThinkingLevelChangeEntry {\n\ttype: \"thinking_level_change\";\n\ttimestamp: string;\n\tthinkingLevel: string;\n}\n\nexport interface ModelChangeEntry {\n\ttype: \"model_change\";\n\ttimestamp: string;\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport interface CompactionEntry {\n\ttype: \"compaction\";\n\ttimestamp: string;\n\tsummary: string;\n\tfirstKeptEntryIndex: number; // Index into session entries where we start keeping\n\ttokensBefore: number;\n}\n\n/** Union of all session entry types */\nexport type SessionEntry =\n\t| SessionHeader\n\t| SessionMessageEntry\n\t| ThinkingLevelChangeEntry\n\t| ModelChangeEntry\n\t| CompactionEntry;\n\n// ============================================================================\n// Session loading with compaction support\n// ============================================================================\n\nexport interface LoadedSession {\n\tmessages: AppMessage[];\n\tthinkingLevel: string;\n\tmodel: { provider: string; modelId: string } | null;\n}\n\nexport const SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:\n\n<summary>\n`;\n\nexport const SUMMARY_SUFFIX = `\n</summary>`;\n\n/**\n * Create a user message containing the summary with the standard prefix.\n */\nexport function createSummaryMessage(summary: string): AppMessage {\n\treturn {\n\t\trole: \"user\",\n\t\tcontent: SUMMARY_PREFIX + summary + SUMMARY_SUFFIX,\n\t\ttimestamp: Date.now(),\n\t};\n}\n\n/**\n * Parse session file content into entries.\n */\nexport function parseSessionEntries(content: string): SessionEntry[] {\n\tconst entries: SessionEntry[] = [];\n\tconst lines = content.trim().split(\"\\n\");\n\n\tfor (const line of lines) {\n\t\tif (!line.trim()) continue;\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\tentries.push(entry);\n\t\t} catch {\n\t\t\t// Skip malformed lines\n\t\t}\n\t}\n\n\treturn entries;\n}\n\n/**\n * Load session from entries, handling compaction events.\n *\n * Algorithm:\n * 1. Find latest compaction event (if any)\n * 2. Keep all entries from firstKeptEntryIndex onwards (extracting messages)\n * 3. Prepend summary as user message\n */\n/**\n * Get the latest compaction entry from session entries, if any.\n */\nexport function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null {\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tif (entries[i].type === \"compaction\") {\n\t\t\treturn entries[i] as CompactionEntry;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function loadSessionFromEntries(entries: SessionEntry[]): LoadedSession {\n\t// Find model and thinking level (always scan all entries)\n\tlet thinkingLevel = \"off\";\n\tlet model: { provider: string; modelId: string } | null = null;\n\n\tfor (const entry of entries) {\n\t\tif (entry.type === \"session\") {\n\t\t\tthinkingLevel = entry.thinkingLevel;\n\t\t\tmodel = { provider: entry.provider, modelId: entry.modelId };\n\t\t} else if (entry.type === \"thinking_level_change\") {\n\t\t\tthinkingLevel = entry.thinkingLevel;\n\t\t} else if (entry.type === \"model_change\") {\n\t\t\tmodel = { provider: entry.provider, modelId: entry.modelId };\n\t\t}\n\t}\n\n\t// Find latest compaction event\n\tlet latestCompactionIndex = -1;\n\tfor (let i = entries.length - 1; i >= 0; i--) {\n\t\tif (entries[i].type === \"compaction\") {\n\t\t\tlatestCompactionIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// No compaction: return all messages\n\tif (latestCompactionIndex === -1) {\n\t\tconst messages: AppMessage[] = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.type === \"message\") {\n\t\t\t\tmessages.push(entry.message);\n\t\t\t}\n\t\t}\n\t\treturn { messages, thinkingLevel, model };\n\t}\n\n\tconst compactionEvent = entries[latestCompactionIndex] as CompactionEntry;\n\n\t// Extract messages from firstKeptEntryIndex to end (skipping compaction entries)\n\tconst keptMessages: AppMessage[] = [];\n\tfor (let i = compactionEvent.firstKeptEntryIndex; i < entries.length; i++) {\n\t\tconst entry = entries[i];\n\t\tif (entry.type === \"message\") {\n\t\t\tkeptMessages.push(entry.message);\n\t\t}\n\t}\n\n\t// Build final messages: summary + kept messages\n\tconst summaryMessage = createSummaryMessage(compactionEvent.summary);\n\tconst messages = [summaryMessage, ...keptMessages];\n\n\treturn { messages, thinkingLevel, model };\n}\n\nexport class SessionManager {\n\tprivate sessionId!: string;\n\tprivate sessionFile!: string;\n\tprivate sessionDir: string;\n\tprivate enabled: boolean = true;\n\tprivate sessionInitialized: boolean = false;\n\tprivate pendingMessages: any[] = [];\n\n\tconstructor(continueSession: boolean = false, customSessionPath?: string) {\n\t\tthis.sessionDir = this.getSessionDirectory();\n\n\t\tif (customSessionPath) {\n\t\t\t// Use custom session file path\n\t\t\tthis.sessionFile = resolve(customSessionPath);\n\t\t\tthis.loadSessionId();\n\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\tthis.sessionInitialized = existsSync(this.sessionFile);\n\t\t} else if (continueSession) {\n\t\t\tconst mostRecent = this.findMostRecentlyModifiedSession();\n\t\t\tif (mostRecent) {\n\t\t\t\tthis.sessionFile = mostRecent;\n\t\t\t\tthis.loadSessionId();\n\t\t\t\t// Mark as initialized since we're loading an existing session\n\t\t\t\tthis.sessionInitialized = true;\n\t\t\t} else {\n\t\t\t\tthis.initNewSession();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.initNewSession();\n\t\t}\n\t}\n\n\t/** Disable session saving (for --no-session mode) */\n\tdisable() {\n\t\tthis.enabled = false;\n\t}\n\n\tprivate getSessionDirectory(): string {\n\t\tconst cwd = process.cwd();\n\t\t// Replace all path separators and colons (for Windows drive letters) with dashes\n\t\tconst safePath = \"--\" + cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\") + \"--\";\n\n\t\tconst configDir = getAgentDir();\n\t\tconst sessionDir = join(configDir, \"sessions\", safePath);\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\t\treturn sessionDir;\n\t}\n\n\tprivate initNewSession(): void {\n\t\tthis.sessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tthis.sessionFile = join(this.sessionDir, `${timestamp}_${this.sessionId}.jsonl`);\n\t}\n\n\t/** Reset to a fresh session. Clears pending messages and starts a new session file. */\n\treset(): void {\n\t\tthis.pendingMessages = [];\n\t\tthis.sessionInitialized = false;\n\t\tthis.initNewSession();\n\t}\n\n\tprivate findMostRecentlyModifiedSession(): string | null {\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => ({\n\t\t\t\t\tname: f,\n\t\t\t\t\tpath: join(this.sessionDir, f),\n\t\t\t\t\tmtime: statSync(join(this.sessionDir, f)).mtime,\n\t\t\t\t}))\n\t\t\t\t.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n\n\t\t\treturn files[0]?.path || null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate loadSessionId(): void {\n\t\tif (!existsSync(this.sessionFile)) return;\n\n\t\tconst lines = readFileSync(this.sessionFile, \"utf8\").trim().split(\"\\n\");\n\t\tfor (const line of lines) {\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line);\n\t\t\t\tif (entry.type === \"session\") {\n\t\t\t\t\tthis.sessionId = entry.id;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\t\tthis.sessionId = uuidv4();\n\t}\n\n\tstartSession(state: AgentState): void {\n\t\tif (!this.enabled || this.sessionInitialized) return;\n\t\tthis.sessionInitialized = true;\n\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: this.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tprovider: state.model.provider,\n\t\t\tmodelId: state.model.id,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t};\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write any queued messages\n\t\tfor (const msg of this.pendingMessages) {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(msg) + \"\\n\");\n\t\t}\n\t\tthis.pendingMessages = [];\n\t}\n\n\tsaveMessage(message: any): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: SessionMessageEntry = {\n\t\t\ttype: \"message\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveThinkingLevelChange(thinkingLevel: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ThinkingLevelChangeEntry = {\n\t\t\ttype: \"thinking_level_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveModelChange(provider: string, modelId: string): void {\n\t\tif (!this.enabled) return;\n\t\tconst entry: ModelChangeEntry = {\n\t\t\ttype: \"model_change\",\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t};\n\n\t\tif (!this.sessionInitialized) {\n\t\t\tthis.pendingMessages.push(entry);\n\t\t} else {\n\t\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t}\n\t}\n\n\tsaveCompaction(entry: CompactionEntry): void {\n\t\tif (!this.enabled) return;\n\t\tappendFileSync(this.sessionFile, JSON.stringify(entry) + \"\\n\");\n\t}\n\n\t/**\n\t * Load session data (messages, model, thinking level) with compaction support.\n\t */\n\tloadSession(): LoadedSession {\n\t\tconst entries = this.loadEntries();\n\t\treturn loadSessionFromEntries(entries);\n\t}\n\n\t/**\n\t * @deprecated Use loadSession().messages instead\n\t */\n\tloadMessages(): AppMessage[] {\n\t\treturn this.loadSession().messages;\n\t}\n\n\t/**\n\t * @deprecated Use loadSession().thinkingLevel instead\n\t */\n\tloadThinkingLevel(): string {\n\t\treturn this.loadSession().thinkingLevel;\n\t}\n\n\t/**\n\t * @deprecated Use loadSession().model instead\n\t */\n\tloadModel(): { provider: string; modelId: string } | null {\n\t\treturn this.loadSession().model;\n\t}\n\n\tgetSessionId(): string {\n\t\treturn this.sessionId;\n\t}\n\n\tgetSessionFile(): string {\n\t\treturn this.sessionFile;\n\t}\n\n\t/**\n\t * Load all entries from the session file.\n\t */\n\tloadEntries(): SessionEntry[] {\n\t\tif (!existsSync(this.sessionFile)) return [];\n\n\t\tconst content = readFileSync(this.sessionFile, \"utf8\");\n\t\tconst entries: SessionEntry[] = [];\n\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\tfor (const line of lines) {\n\t\t\tif (!line.trim()) continue;\n\t\t\ttry {\n\t\t\t\tconst entry = JSON.parse(line) as SessionEntry;\n\t\t\t\tentries.push(entry);\n\t\t\t} catch {\n\t\t\t\t// Skip malformed lines\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/**\n\t * Load all sessions for the current directory with metadata\n\t */\n\tloadAllSessions(): Array<{\n\t\tpath: string;\n\t\tid: string;\n\t\tcreated: Date;\n\t\tmodified: Date;\n\t\tmessageCount: number;\n\t\tfirstMessage: string;\n\t\tallMessagesText: string;\n\t}> {\n\t\tconst sessions: Array<{\n\t\t\tpath: string;\n\t\t\tid: string;\n\t\t\tcreated: Date;\n\t\t\tmodified: Date;\n\t\t\tmessageCount: number;\n\t\t\tfirstMessage: string;\n\t\t\tallMessagesText: string;\n\t\t}> = [];\n\n\t\ttry {\n\t\t\tconst files = readdirSync(this.sessionDir)\n\t\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t\t.map((f) => join(this.sessionDir, f));\n\n\t\t\tfor (const file of files) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(file);\n\t\t\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\t\t\tconst lines = content.trim().split(\"\\n\");\n\n\t\t\t\t\tlet sessionId = \"\";\n\t\t\t\t\tlet created = stats.birthtime;\n\t\t\t\t\tlet messageCount = 0;\n\t\t\t\t\tlet firstMessage = \"\";\n\t\t\t\t\tconst allMessages: string[] = [];\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst entry = JSON.parse(line);\n\n\t\t\t\t\t\t\t// Extract session ID from first session entry\n\t\t\t\t\t\t\tif (entry.type === \"session\" && !sessionId) {\n\t\t\t\t\t\t\t\tsessionId = entry.id;\n\t\t\t\t\t\t\t\tcreated = new Date(entry.timestamp);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Count messages and collect all text\n\t\t\t\t\t\t\tif (entry.type === \"message\") {\n\t\t\t\t\t\t\t\tmessageCount++;\n\n\t\t\t\t\t\t\t\t// Extract text from user and assistant messages\n\t\t\t\t\t\t\t\tif (entry.message.role === \"user\" || entry.message.role === \"assistant\") {\n\t\t\t\t\t\t\t\t\tconst textContent = entry.message.content\n\t\t\t\t\t\t\t\t\t\t.filter((c: any) => c.type === \"text\")\n\t\t\t\t\t\t\t\t\t\t.map((c: any) => c.text)\n\t\t\t\t\t\t\t\t\t\t.join(\" \");\n\n\t\t\t\t\t\t\t\t\tif (textContent) {\n\t\t\t\t\t\t\t\t\t\tallMessages.push(textContent);\n\n\t\t\t\t\t\t\t\t\t\t// Get first user message for display\n\t\t\t\t\t\t\t\t\t\tif (!firstMessage && entry.message.role === \"user\") {\n\t\t\t\t\t\t\t\t\t\t\tfirstMessage = textContent;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Skip malformed lines\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tsessions.push({\n\t\t\t\t\t\tpath: file,\n\t\t\t\t\t\tid: sessionId || \"unknown\",\n\t\t\t\t\t\tcreated,\n\t\t\t\t\t\tmodified: stats.mtime,\n\t\t\t\t\t\tmessageCount,\n\t\t\t\t\t\tfirstMessage: firstMessage || \"(no messages)\",\n\t\t\t\t\t\tallMessagesText: allMessages.join(\" \"),\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Skip files that can't be read\n\t\t\t\t\tconsole.error(`Failed to read session file ${file}:`, error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by modified date (most recent first)\n\t\t\tsessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load sessions:\", error);\n\t\t}\n\n\t\treturn sessions;\n\t}\n\n\t/**\n\t * Set the session file to an existing session\n\t */\n\tsetSessionFile(path: string): void {\n\t\tthis.sessionFile = path;\n\t\tthis.loadSessionId();\n\t\t// Mark as initialized since we're loading an existing session\n\t\tthis.sessionInitialized = existsSync(path);\n\t}\n\n\t/**\n\t * Check if we should initialize the session based on message history.\n\t * Session is initialized when we have at least 1 user message and 1 assistant message.\n\t */\n\tshouldInitializeSession(messages: any[]): boolean {\n\t\tif (this.sessionInitialized) return false;\n\n\t\tconst userMessages = messages.filter((m) => m.role === \"user\");\n\t\tconst assistantMessages = messages.filter((m) => m.role === \"assistant\");\n\n\t\treturn userMessages.length >= 1 && assistantMessages.length >= 1;\n\t}\n\n\t/**\n\t * Create a branched session from a specific message index.\n\t * If branchFromIndex is -1, creates an empty session.\n\t * Returns the new session file path.\n\t */\n\tcreateBranchedSession(state: any, branchFromIndex: number): string {\n\t\t// Create a new session ID for the branch\n\t\tconst newSessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tconst newSessionFile = join(this.sessionDir, `${timestamp}_${newSessionId}.jsonl`);\n\n\t\t// Write session header\n\t\tconst entry: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tid: newSessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: process.cwd(),\n\t\t\tprovider: state.model.provider,\n\t\t\tmodelId: state.model.id,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t\tbranchedFrom: this.sessionFile,\n\t\t};\n\t\tappendFileSync(newSessionFile, JSON.stringify(entry) + \"\\n\");\n\n\t\t// Write messages up to and including the branch point (if >= 0)\n\t\tif (branchFromIndex >= 0) {\n\t\t\tconst messagesToWrite = state.messages.slice(0, branchFromIndex + 1);\n\t\t\tfor (const message of messagesToWrite) {\n\t\t\t\tconst messageEntry: SessionMessageEntry = {\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tmessage,\n\t\t\t\t};\n\t\t\t\tappendFileSync(newSessionFile, JSON.stringify(messageEntry) + \"\\n\");\n\t\t\t}\n\t\t}\n\n\t\treturn newSessionFile;\n\t}\n\n\t/**\n\t * Create a branched session from session entries up to (but not including) a specific entry index.\n\t * This preserves compaction events and all entry types.\n\t * Returns the new session file path.\n\t */\n\tcreateBranchedSessionFromEntries(entries: SessionEntry[], branchBeforeIndex: number): string {\n\t\tconst newSessionId = uuidv4();\n\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\tconst newSessionFile = join(this.sessionDir, `${timestamp}_${newSessionId}.jsonl`);\n\n\t\t// Copy all entries up to (but not including) the branch point\n\t\tfor (let i = 0; i < branchBeforeIndex; i++) {\n\t\t\tconst entry = entries[i];\n\n\t\t\tif (entry.type === \"session\") {\n\t\t\t\t// Rewrite session header with new ID and branchedFrom\n\t\t\t\tconst newHeader: SessionHeader = {\n\t\t\t\t\t...entry,\n\t\t\t\t\tid: newSessionId,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tbranchedFrom: this.sessionFile,\n\t\t\t\t};\n\t\t\t\tappendFileSync(newSessionFile, JSON.stringify(newHeader) + \"\\n\");\n\t\t\t} else {\n\t\t\t\t// Copy other entries as-is\n\t\t\t\tappendFileSync(newSessionFile, JSON.stringify(entry) + \"\\n\");\n\t\t\t}\n\t\t}\n\n\t\treturn newSessionFile;\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-manager.d.ts","sourceRoot":"","sources":["../../src/core/settings-manager.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/E,SAAS,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,qBAAa,eAAe;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAW;IAE3B,YAAY,OAAO,CAAC,EAAE,MAAM,EAI3B;IAED,OAAO,CAAC,IAAI;IAcZ,OAAO,CAAC,IAAI;IAcZ,uBAAuB,IAAI,MAAM,GAAG,SAAS,CAE5C;IAED,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAG7C;IAED,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAEvC;IAED,eAAe,IAAI,MAAM,GAAG,SAAS,CAEpC;IAED,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGzC;IAED,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGrC;IAED,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAIlE;IAED,YAAY,IAAI,KAAK,GAAG,eAAe,CAEtC;IAED,YAAY,CAAC,IAAI,EAAE,KAAK,GAAG,eAAe,GAAG,IAAI,CAGhD;IAED,QAAQ,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;IAED,uBAAuB,IAAI,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAE7F;IAED,uBAAuB,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAG5F;IAED,oBAAoB,IAAI,OAAO,CAE9B;IAED,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAM3C;IAED,0BAA0B,IAAI,MAAM,CAEnC;IAED,6BAA6B,IAAI,MAAM,CAEtC;IAED,qBAAqB,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAM7F;IAED,oBAAoB,IAAI,OAAO,CAE9B;IAED,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAGxC;IAED,YAAY,IAAI,MAAM,GAAG,SAAS,CAEjC;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAG3C;IAED,oBAAoB,IAAI,OAAO,CAE9B;IAED,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAG5C;CACD","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nexport interface CompactionSettings {\n\tenabled?: boolean; // default: true\n\treserveTokens?: number; // default: 16384\n\tkeepRecentTokens?: number; // default: 20000\n}\n\nexport interface Settings {\n\tlastChangelogVersion?: string;\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\tqueueMode?: \"all\" | \"one-at-a-time\";\n\ttheme?: string;\n\tcompaction?: CompactionSettings;\n\thideThinkingBlock?: boolean;\n\tshellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)\n\tcollapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)\n}\n\nexport class SettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: Settings;\n\n\tconstructor(baseDir?: string) {\n\t\tconst dir = baseDir || getAgentDir();\n\t\tthis.settingsPath = join(dir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): Settings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not read settings file: ${error}`);\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\t// Ensure directory exists\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetLastChangelogVersion(): string | undefined {\n\t\treturn this.settings.lastChangelogVersion;\n\t}\n\n\tsetLastChangelogVersion(version: string): void {\n\t\tthis.settings.lastChangelogVersion = version;\n\t\tthis.save();\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tsetDefaultProvider(provider: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.save();\n\t}\n\n\tsetDefaultModel(modelId: string): void {\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetQueueMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.settings.queueMode || \"one-at-a-time\";\n\t}\n\n\tsetQueueMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.settings.queueMode = mode;\n\t\tthis.save();\n\t}\n\n\tgetTheme(): string | undefined {\n\t\treturn this.settings.theme;\n\t}\n\n\tsetTheme(theme: string): void {\n\t\tthis.settings.theme = theme;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | undefined {\n\t\treturn this.settings.defaultThinkingLevel;\n\t}\n\n\tsetDefaultThinkingLevel(level: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"): void {\n\t\tthis.settings.defaultThinkingLevel = level;\n\t\tthis.save();\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? true;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tif (!this.settings.compaction) {\n\t\t\tthis.settings.compaction = {};\n\t\t}\n\t\tthis.settings.compaction.enabled = enabled;\n\t\tthis.save();\n\t}\n\n\tgetCompactionReserveTokens(): number {\n\t\treturn this.settings.compaction?.reserveTokens ?? 16384;\n\t}\n\n\tgetCompactionKeepRecentTokens(): number {\n\t\treturn this.settings.compaction?.keepRecentTokens ?? 20000;\n\t}\n\n\tgetCompactionSettings(): { enabled: boolean; reserveTokens: number; keepRecentTokens: number } {\n\t\treturn {\n\t\t\tenabled: this.getCompactionEnabled(),\n\t\t\treserveTokens: this.getCompactionReserveTokens(),\n\t\t\tkeepRecentTokens: this.getCompactionKeepRecentTokens(),\n\t\t};\n\t}\n\n\tgetHideThinkingBlock(): boolean {\n\t\treturn this.settings.hideThinkingBlock ?? false;\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.settings.hideThinkingBlock = hide;\n\t\tthis.save();\n\t}\n\n\tgetShellPath(): string | undefined {\n\t\treturn this.settings.shellPath;\n\t}\n\n\tsetShellPath(path: string | undefined): void {\n\t\tthis.settings.shellPath = path;\n\t\tthis.save();\n\t}\n\n\tgetCollapseChangelog(): boolean {\n\t\treturn this.settings.collapseChangelog ?? false;\n\t}\n\n\tsetCollapseChangelog(collapse: boolean): void {\n\t\tthis.settings.collapseChangelog = collapse;\n\t\tthis.save();\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-manager.js","sourceRoot":"","sources":["../../src/core/settings-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getAgentDir } from \"../config.js\";\n\nexport interface CompactionSettings {\n\tenabled?: boolean; // default: true\n\treserveTokens?: number; // default: 16384\n\tkeepRecentTokens?: number; // default: 20000\n}\n\nexport interface Settings {\n\tlastChangelogVersion?: string;\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\";\n\tqueueMode?: \"all\" | \"one-at-a-time\";\n\ttheme?: string;\n\tcompaction?: CompactionSettings;\n\thideThinkingBlock?: boolean;\n\tshellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)\n\tcollapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)\n}\n\nexport class SettingsManager {\n\tprivate settingsPath: string;\n\tprivate settings: Settings;\n\n\tconstructor(baseDir?: string) {\n\t\tconst dir = baseDir || getAgentDir();\n\t\tthis.settingsPath = join(dir, \"settings.json\");\n\t\tthis.settings = this.load();\n\t}\n\n\tprivate load(): Settings {\n\t\tif (!existsSync(this.settingsPath)) {\n\t\t\treturn {};\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(this.settingsPath, \"utf-8\");\n\t\t\treturn JSON.parse(content);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not read settings file: ${error}`);\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\ttry {\n\t\t\t// Ensure directory exists\n\t\t\tconst dir = dirname(this.settingsPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\t}\n\n\t\t\twriteFileSync(this.settingsPath, JSON.stringify(this.settings, null, 2), \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(`Warning: Could not save settings file: ${error}`);\n\t\t}\n\t}\n\n\tgetLastChangelogVersion(): string | undefined {\n\t\treturn this.settings.lastChangelogVersion;\n\t}\n\n\tsetLastChangelogVersion(version: string): void {\n\t\tthis.settings.lastChangelogVersion = version;\n\t\tthis.save();\n\t}\n\n\tgetDefaultProvider(): string | undefined {\n\t\treturn this.settings.defaultProvider;\n\t}\n\n\tgetDefaultModel(): string | undefined {\n\t\treturn this.settings.defaultModel;\n\t}\n\n\tsetDefaultProvider(provider: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.save();\n\t}\n\n\tsetDefaultModel(modelId: string): void {\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tsetDefaultModelAndProvider(provider: string, modelId: string): void {\n\t\tthis.settings.defaultProvider = provider;\n\t\tthis.settings.defaultModel = modelId;\n\t\tthis.save();\n\t}\n\n\tgetQueueMode(): \"all\" | \"one-at-a-time\" {\n\t\treturn this.settings.queueMode || \"one-at-a-time\";\n\t}\n\n\tsetQueueMode(mode: \"all\" | \"one-at-a-time\"): void {\n\t\tthis.settings.queueMode = mode;\n\t\tthis.save();\n\t}\n\n\tgetTheme(): string | undefined {\n\t\treturn this.settings.theme;\n\t}\n\n\tsetTheme(theme: string): void {\n\t\tthis.settings.theme = theme;\n\t\tthis.save();\n\t}\n\n\tgetDefaultThinkingLevel(): \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | undefined {\n\t\treturn this.settings.defaultThinkingLevel;\n\t}\n\n\tsetDefaultThinkingLevel(level: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"): void {\n\t\tthis.settings.defaultThinkingLevel = level;\n\t\tthis.save();\n\t}\n\n\tgetCompactionEnabled(): boolean {\n\t\treturn this.settings.compaction?.enabled ?? true;\n\t}\n\n\tsetCompactionEnabled(enabled: boolean): void {\n\t\tif (!this.settings.compaction) {\n\t\t\tthis.settings.compaction = {};\n\t\t}\n\t\tthis.settings.compaction.enabled = enabled;\n\t\tthis.save();\n\t}\n\n\tgetCompactionReserveTokens(): number {\n\t\treturn this.settings.compaction?.reserveTokens ?? 16384;\n\t}\n\n\tgetCompactionKeepRecentTokens(): number {\n\t\treturn this.settings.compaction?.keepRecentTokens ?? 20000;\n\t}\n\n\tgetCompactionSettings(): { enabled: boolean; reserveTokens: number; keepRecentTokens: number } {\n\t\treturn {\n\t\t\tenabled: this.getCompactionEnabled(),\n\t\t\treserveTokens: this.getCompactionReserveTokens(),\n\t\t\tkeepRecentTokens: this.getCompactionKeepRecentTokens(),\n\t\t};\n\t}\n\n\tgetHideThinkingBlock(): boolean {\n\t\treturn this.settings.hideThinkingBlock ?? false;\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.settings.hideThinkingBlock = hide;\n\t\tthis.save();\n\t}\n\n\tgetShellPath(): string | undefined {\n\t\treturn this.settings.shellPath;\n\t}\n\n\tsetShellPath(path: string | undefined): void {\n\t\tthis.settings.shellPath = path;\n\t\tthis.save();\n\t}\n\n\tgetCollapseChangelog(): boolean {\n\t\treturn this.settings.collapseChangelog ?? false;\n\t}\n\n\tsetCollapseChangelog(collapse: boolean): void {\n\t\tthis.settings.collapseChangelog = collapse;\n\t\tthis.save();\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CACf;AAgCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CA+B7D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAatE;AAqED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,gBAAgB,EAAE,CAYtD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAczF","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME, getCommandsDir } from \"../config.js\";\n\n/**\n * Represents a custom slash command loaded from a file\n */\nexport interface FileSlashCommand {\n\tname: string;\n\tdescription: string;\n\tcontent: string;\n\tsource: string; // e.g., \"(user)\", \"(project)\", \"(project:frontend)\"\n}\n\n/**\n * Parse YAML frontmatter from markdown content\n * Returns { frontmatter, content } where content has frontmatter stripped\n */\nfunction parseFrontmatter(content: string): { frontmatter: Record<string, string>; content: string } {\n\tconst frontmatter: Record<string, string> = {};\n\n\tif (!content.startsWith(\"---\")) {\n\t\treturn { frontmatter, content };\n\t}\n\n\tconst endIndex = content.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, content };\n\t}\n\n\tconst frontmatterBlock = content.slice(4, endIndex);\n\tconst remainingContent = content.slice(endIndex + 4).trim();\n\n\t// Simple YAML parsing - just key: value pairs\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w+):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tfrontmatter[match[1]] = match[2].trim();\n\t\t}\n\t}\n\n\treturn { frontmatter, content: remainingContent };\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in command content\n * Supports $1, $2, ... for positional args and $@ for all args\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $@ with all args joined\n\tresult = result.replace(/\\$@/g, args.join(\" \"));\n\n\t// Replace $1, $2, etc. with positional args\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\treturn result;\n}\n\n/**\n * Recursively scan a directory for .md files and load them as slash commands\n */\nfunction loadCommandsFromDir(dir: string, source: \"user\" | \"project\", subdir: string = \"\"): FileSlashCommand[] {\n\tconst commands: FileSlashCommand[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn commands;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\t// Recurse into subdirectory\n\t\t\t\tconst newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;\n\t\t\t\tcommands.push(...loadCommandsFromDir(fullPath, source, newSubdir));\n\t\t\t} else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\tconst { frontmatter, content } = parseFrontmatter(rawContent);\n\n\t\t\t\t\tconst name = entry.name.slice(0, -3); // Remove .md extension\n\n\t\t\t\t\t// Build source string\n\t\t\t\t\tlet sourceStr: string;\n\t\t\t\t\tif (source === \"user\") {\n\t\t\t\t\t\tsourceStr = subdir ? `(user:${subdir})` : \"(user)\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsourceStr = subdir ? `(project:${subdir})` : \"(project)\";\n\t\t\t\t\t}\n\n\t\t\t\t\t// Get description from frontmatter or first non-empty line\n\t\t\t\t\tlet description = frontmatter.description || \"\";\n\t\t\t\t\tif (!description) {\n\t\t\t\t\t\tconst firstLine = content.split(\"\\n\").find((line) => line.trim());\n\t\t\t\t\t\tif (firstLine) {\n\t\t\t\t\t\t\t// Truncate if too long\n\t\t\t\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Append source to description\n\t\t\t\t\tdescription = description ? `${description} ${sourceStr}` : sourceStr;\n\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription,\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\tsource: sourceStr,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Silently skip files that can't be read\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\t// Silently skip directories that can't be read\n\t}\n\n\treturn commands;\n}\n\n/**\n * Load all custom slash commands from:\n * 1. Global: ~/{CONFIG_DIR_NAME}/agent/commands/\n * 2. Project: ./{CONFIG_DIR_NAME}/commands/\n */\nexport function loadSlashCommands(): FileSlashCommand[] {\n\tconst commands: FileSlashCommand[] = [];\n\n\t// 1. Load global commands from ~/{CONFIG_DIR_NAME}/agent/commands/\n\tconst globalCommandsDir = getCommandsDir();\n\tcommands.push(...loadCommandsFromDir(globalCommandsDir, \"user\"));\n\n\t// 2. Load project commands from ./{CONFIG_DIR_NAME}/commands/\n\tconst projectCommandsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"commands\");\n\tcommands.push(...loadCommandsFromDir(projectCommandsDir, \"project\"));\n\n\treturn commands;\n}\n\n/**\n * Expand a slash command if it matches a file-based command.\n * Returns the expanded content or the original text if not a slash command.\n */\nexport function expandSlashCommand(text: string, fileCommands: FileSlashCommand[]): string {\n\tif (!text.startsWith(\"/\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst fileCommand = fileCommands.find((cmd) => cmd.name === commandName);\n\tif (fileCommand) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn substituteArgs(fileCommand.content, args);\n\t}\n\n\treturn text;\n}\n"]}
|