gencode-ai 0.1.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/.env.example +11 -0
- package/CLAUDE.md +70 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/agent/agent.d.ts +84 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +233 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/types.d.ts +47 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/components/App.d.ts +14 -0
- package/dist/cli/components/App.d.ts.map +1 -0
- package/dist/cli/components/App.js +395 -0
- package/dist/cli/components/App.js.map +1 -0
- package/dist/cli/components/CommandSuggestions.d.ts +13 -0
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/cli/components/CommandSuggestions.js +32 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -0
- package/dist/cli/components/Header.d.ts +9 -0
- package/dist/cli/components/Header.d.ts.map +1 -0
- package/dist/cli/components/Header.js +13 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/Input.d.ts +13 -0
- package/dist/cli/components/Input.d.ts.map +1 -0
- package/dist/cli/components/Input.js +27 -0
- package/dist/cli/components/Input.js.map +1 -0
- package/dist/cli/components/Logo.d.ts +2 -0
- package/dist/cli/components/Logo.d.ts.map +1 -0
- package/dist/cli/components/Logo.js +8 -0
- package/dist/cli/components/Logo.js.map +1 -0
- package/dist/cli/components/Messages.d.ts +37 -0
- package/dist/cli/components/Messages.d.ts.map +1 -0
- package/dist/cli/components/Messages.js +106 -0
- package/dist/cli/components/Messages.js.map +1 -0
- package/dist/cli/components/ModelSelector.d.ts +13 -0
- package/dist/cli/components/ModelSelector.d.ts.map +1 -0
- package/dist/cli/components/ModelSelector.js +72 -0
- package/dist/cli/components/ModelSelector.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +12 -0
- package/dist/cli/components/Spinner.d.ts.map +1 -0
- package/dist/cli/components/Spinner.js +45 -0
- package/dist/cli/components/Spinner.js.map +1 -0
- package/dist/cli/components/index.d.ts +12 -0
- package/dist/cli/components/index.d.ts.map +1 -0
- package/dist/cli/components/index.js +12 -0
- package/dist/cli/components/index.js.map +1 -0
- package/dist/cli/components/theme.d.ts +31 -0
- package/dist/cli/components/theme.d.ts.map +1 -0
- package/dist/cli/components/theme.js +36 -0
- package/dist/cli/components/theme.js.map +1 -0
- package/dist/cli/index-legacy.d.ts +7 -0
- package/dist/cli/index-legacy.d.ts.map +1 -0
- package/dist/cli/index-legacy.js +431 -0
- package/dist/cli/index-legacy.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ink-cli.d.ts +7 -0
- package/dist/cli/ink-cli.d.ts.map +1 -0
- package/dist/cli/ink-cli.js +105 -0
- package/dist/cli/ink-cli.js.map +1 -0
- package/dist/cli/session-picker.d.ts +16 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +280 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli/ui.d.ts +61 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +364 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/manager.d.ts +31 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +65 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/init.d.ts +20 -0
- package/dist/memory/init.d.ts.map +1 -0
- package/dist/memory/init.js +332 -0
- package/dist/memory/init.js.map +1 -0
- package/dist/memory/manager.d.ts +85 -0
- package/dist/memory/manager.d.ts.map +1 -0
- package/dist/memory/manager.js +234 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/types.d.ts +74 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +6 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/permissions/index.d.ts +7 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +32 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +79 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/types.d.ts +14 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +20 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +185 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +21 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +241 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +34 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +72 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +19 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +221 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +125 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +6 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/session/index.d.ts +6 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +6 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +101 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +295 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/types.d.ts +39 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +10 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +7 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +80 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit.d.ts +7 -0
- package/dist/tools/builtin/edit.d.ts.map +1 -0
- package/dist/tools/builtin/edit.js +32 -0
- package/dist/tools/builtin/edit.js.map +1 -0
- package/dist/tools/builtin/glob.d.ts +7 -0
- package/dist/tools/builtin/glob.d.ts.map +1 -0
- package/dist/tools/builtin/glob.js +36 -0
- package/dist/tools/builtin/glob.js.map +1 -0
- package/dist/tools/builtin/grep.d.ts +7 -0
- package/dist/tools/builtin/grep.d.ts.map +1 -0
- package/dist/tools/builtin/grep.js +59 -0
- package/dist/tools/builtin/grep.js.map +1 -0
- package/dist/tools/builtin/read.d.ts +7 -0
- package/dist/tools/builtin/read.d.ts.map +1 -0
- package/dist/tools/builtin/read.js +29 -0
- package/dist/tools/builtin/read.js.map +1 -0
- package/dist/tools/builtin/write.d.ts +7 -0
- package/dist/tools/builtin/write.d.ts.map +1 -0
- package/dist/tools/builtin/write.js +24 -0
- package/dist/tools/builtin/write.js.map +1 -0
- package/dist/tools/index.d.ts +38 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +22 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +71 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +62 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +126 -0
- package/dist/tools/types.js.map +1 -0
- package/docs/README.md +16 -0
- package/docs/proposals/0001-web-fetch-tool.md +293 -0
- package/docs/proposals/0002-web-search-tool.md +306 -0
- package/docs/proposals/0003-task-subagents.md +333 -0
- package/docs/proposals/0004-plan-mode.md +338 -0
- package/docs/proposals/0005-todo-system.md +299 -0
- package/docs/proposals/0006-memory-system.md +539 -0
- package/docs/proposals/0007-context-management.md +429 -0
- package/docs/proposals/0008-checkpointing.md +327 -0
- package/docs/proposals/0009-hooks-system.md +343 -0
- package/docs/proposals/0010-mcp-integration.md +382 -0
- package/docs/proposals/0011-custom-commands.md +374 -0
- package/docs/proposals/0012-ask-user-question.md +317 -0
- package/docs/proposals/0013-multi-edit-tool.md +345 -0
- package/docs/proposals/0014-lsp-tool.md +478 -0
- package/docs/proposals/0015-ls-tool.md +407 -0
- package/docs/proposals/0016-kill-shell-tool.md +455 -0
- package/docs/proposals/0017-background-tasks.md +489 -0
- package/docs/proposals/0018-parallel-tool-execution.md +415 -0
- package/docs/proposals/0019-session-enhancements.md +462 -0
- package/docs/proposals/0020-session-summarization.md +447 -0
- package/docs/proposals/0021-skills-system.md +409 -0
- package/docs/proposals/0022-plugin-system.md +467 -0
- package/docs/proposals/0023-permission-enhancements.md +470 -0
- package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
- package/docs/proposals/0025-cost-tracking.md +447 -0
- package/docs/proposals/0026-git-integration.md +475 -0
- package/docs/proposals/0027-enhanced-read-tool.md +514 -0
- package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
- package/docs/proposals/0029-notebook-edit-tool.md +413 -0
- package/docs/proposals/0030-plugin-marketplace.md +360 -0
- package/docs/proposals/0031-command-suggestions.md +295 -0
- package/docs/proposals/0032-ide-integrations.md +328 -0
- package/docs/proposals/0033-enterprise-deployment.md +221 -0
- package/docs/proposals/0034-sandboxing.md +273 -0
- package/docs/proposals/0035-auto-updater.md +311 -0
- package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
- package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
- package/docs/proposals/0038-interactive-cli-ui.md +373 -0
- package/docs/proposals/0039-streaming-enhancements.md +359 -0
- package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
- package/docs/proposals/README.md +84 -0
- package/docs/proposals/TEMPLATE.md +57 -0
- package/docs/proposals/research/claude-code-research.md +307 -0
- package/examples/agent-demo.ts +115 -0
- package/examples/basic.ts +166 -0
- package/package.json +50 -0
- package/src/agent/agent.ts +276 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/types.ts +62 -0
- package/src/cli/components/App.tsx +565 -0
- package/src/cli/components/CommandSuggestions.tsx +58 -0
- package/src/cli/components/Header.tsx +36 -0
- package/src/cli/components/Input.tsx +60 -0
- package/src/cli/components/Logo.tsx +16 -0
- package/src/cli/components/Messages.tsx +210 -0
- package/src/cli/components/ModelSelector.tsx +135 -0
- package/src/cli/components/Spinner.tsx +72 -0
- package/src/cli/components/index.ts +21 -0
- package/src/cli/components/theme.ts +36 -0
- package/src/cli/index.tsx +136 -0
- package/src/config/index.ts +7 -0
- package/src/config/manager.ts +77 -0
- package/src/config/types.ts +25 -0
- package/src/index.ts +86 -0
- package/src/permissions/index.ts +7 -0
- package/src/permissions/manager.ts +97 -0
- package/src/permissions/types.ts +29 -0
- package/src/providers/anthropic.ts +224 -0
- package/src/providers/gemini.ts +295 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/openai.ts +261 -0
- package/src/providers/types.ts +181 -0
- package/src/session/index.ts +6 -0
- package/src/session/manager.ts +354 -0
- package/src/session/types.ts +49 -0
- package/src/tools/builtin/bash.ts +92 -0
- package/src/tools/builtin/edit.ts +37 -0
- package/src/tools/builtin/glob.ts +42 -0
- package/src/tools/builtin/grep.ts +67 -0
- package/src/tools/builtin/read.ts +34 -0
- package/src/tools/builtin/write.ts +27 -0
- package/src/tools/index.ts +36 -0
- package/src/tools/registry.ts +83 -0
- package/src/tools/types.ts +172 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified types for LLM providers
|
|
3
|
+
* Abstracts differences between OpenAI, Anthropic, and Gemini APIs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Message Types
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export type MessageRole = 'system' | 'user' | 'assistant';
|
|
11
|
+
|
|
12
|
+
export interface TextContent {
|
|
13
|
+
type: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToolUseContent {
|
|
18
|
+
type: 'tool_use';
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
input: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ToolResultContent {
|
|
25
|
+
type: 'tool_result';
|
|
26
|
+
toolUseId: string;
|
|
27
|
+
content: string;
|
|
28
|
+
isError?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type MessageContent = TextContent | ToolUseContent | ToolResultContent;
|
|
32
|
+
|
|
33
|
+
export interface Message {
|
|
34
|
+
role: MessageRole;
|
|
35
|
+
content: string | MessageContent[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Tool Types
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
export interface JSONSchema {
|
|
43
|
+
type: string;
|
|
44
|
+
properties?: Record<string, JSONSchema>;
|
|
45
|
+
required?: string[];
|
|
46
|
+
description?: string;
|
|
47
|
+
items?: JSONSchema;
|
|
48
|
+
enum?: unknown[];
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ToolDefinition {
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
parameters: JSONSchema;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ToolCall {
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
input: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ToolResult {
|
|
65
|
+
toolUseId: string;
|
|
66
|
+
content: string;
|
|
67
|
+
isError?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Completion Types
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
export interface CompletionOptions {
|
|
75
|
+
model: string;
|
|
76
|
+
messages: Message[];
|
|
77
|
+
tools?: ToolDefinition[];
|
|
78
|
+
systemPrompt?: string;
|
|
79
|
+
maxTokens?: number;
|
|
80
|
+
temperature?: number;
|
|
81
|
+
stream?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence';
|
|
85
|
+
|
|
86
|
+
export interface CompletionResponse {
|
|
87
|
+
content: MessageContent[];
|
|
88
|
+
stopReason: StopReason;
|
|
89
|
+
usage?: {
|
|
90
|
+
inputTokens: number;
|
|
91
|
+
outputTokens: number;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Streaming Types
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
export interface StreamChunkText {
|
|
100
|
+
type: 'text';
|
|
101
|
+
text: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface StreamChunkToolStart {
|
|
105
|
+
type: 'tool_start';
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface StreamChunkToolInput {
|
|
111
|
+
type: 'tool_input';
|
|
112
|
+
id: string;
|
|
113
|
+
input: string; // Partial JSON string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface StreamChunkDone {
|
|
117
|
+
type: 'done';
|
|
118
|
+
response: CompletionResponse;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface StreamChunkError {
|
|
122
|
+
type: 'error';
|
|
123
|
+
error: Error;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type StreamChunk =
|
|
127
|
+
| StreamChunkText
|
|
128
|
+
| StreamChunkToolStart
|
|
129
|
+
| StreamChunkToolInput
|
|
130
|
+
| StreamChunkDone
|
|
131
|
+
| StreamChunkError;
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Provider Interface
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
export interface ModelInfo {
|
|
138
|
+
id: string;
|
|
139
|
+
name: string;
|
|
140
|
+
description?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface LLMProvider {
|
|
144
|
+
readonly name: string;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate a completion (non-streaming)
|
|
148
|
+
*/
|
|
149
|
+
complete(options: CompletionOptions): Promise<CompletionResponse>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Generate a streaming completion
|
|
153
|
+
*/
|
|
154
|
+
stream(options: CompletionOptions): AsyncGenerator<StreamChunk, void, unknown>;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* List available models from the provider
|
|
158
|
+
*/
|
|
159
|
+
listModels(): Promise<ModelInfo[]>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Provider Configuration
|
|
164
|
+
// ============================================================================
|
|
165
|
+
|
|
166
|
+
export interface OpenAIConfig {
|
|
167
|
+
apiKey?: string;
|
|
168
|
+
baseURL?: string;
|
|
169
|
+
organization?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface AnthropicConfig {
|
|
173
|
+
apiKey?: string;
|
|
174
|
+
baseURL?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface GeminiConfig {
|
|
178
|
+
apiKey?: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type ProviderConfig = OpenAIConfig | AnthropicConfig | GeminiConfig;
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager - Handles session persistence and retrieval
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import * as crypto from 'crypto';
|
|
9
|
+
import type { Message } from '../providers/types.js';
|
|
10
|
+
import type {
|
|
11
|
+
Session,
|
|
12
|
+
SessionMetadata,
|
|
13
|
+
SessionListItem,
|
|
14
|
+
SessionConfig,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
import { DEFAULT_SESSION_CONFIG } from './types.js';
|
|
17
|
+
|
|
18
|
+
export class SessionManager {
|
|
19
|
+
private config: SessionConfig;
|
|
20
|
+
private storageDir: string;
|
|
21
|
+
private currentSession: Session | null = null;
|
|
22
|
+
|
|
23
|
+
constructor(config?: Partial<SessionConfig>) {
|
|
24
|
+
this.config = { ...DEFAULT_SESSION_CONFIG, ...config };
|
|
25
|
+
this.storageDir = this.config.storageDir.replace('~', os.homedir());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize storage directory
|
|
30
|
+
*/
|
|
31
|
+
async init(): Promise<void> {
|
|
32
|
+
await fs.mkdir(this.storageDir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a new session ID
|
|
37
|
+
*/
|
|
38
|
+
private generateId(): string {
|
|
39
|
+
const timestamp = Date.now().toString(36);
|
|
40
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
41
|
+
return `${timestamp}-${random}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get session file path
|
|
46
|
+
*/
|
|
47
|
+
private getSessionPath(id: string): string {
|
|
48
|
+
return path.join(this.storageDir, `${id}.json`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a new session
|
|
53
|
+
*/
|
|
54
|
+
async create(options: {
|
|
55
|
+
provider: string;
|
|
56
|
+
model: string;
|
|
57
|
+
cwd?: string;
|
|
58
|
+
title?: string;
|
|
59
|
+
parentId?: string;
|
|
60
|
+
}): Promise<Session> {
|
|
61
|
+
const id = this.generateId();
|
|
62
|
+
const now = new Date().toISOString();
|
|
63
|
+
|
|
64
|
+
const session: Session = {
|
|
65
|
+
metadata: {
|
|
66
|
+
id,
|
|
67
|
+
title: options.title ?? `Session ${new Date().toLocaleString()}`,
|
|
68
|
+
createdAt: now,
|
|
69
|
+
updatedAt: now,
|
|
70
|
+
provider: options.provider,
|
|
71
|
+
model: options.model,
|
|
72
|
+
cwd: options.cwd ?? process.cwd(),
|
|
73
|
+
messageCount: 0,
|
|
74
|
+
parentId: options.parentId,
|
|
75
|
+
},
|
|
76
|
+
messages: [],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
this.currentSession = session;
|
|
80
|
+
|
|
81
|
+
if (this.config.autoSave) {
|
|
82
|
+
await this.save(session);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return session;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Fork an existing session
|
|
90
|
+
*/
|
|
91
|
+
async fork(sessionId: string, title?: string): Promise<Session> {
|
|
92
|
+
const parent = await this.load(sessionId);
|
|
93
|
+
if (!parent) {
|
|
94
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const id = this.generateId();
|
|
98
|
+
const now = new Date().toISOString();
|
|
99
|
+
|
|
100
|
+
const forked: Session = {
|
|
101
|
+
metadata: {
|
|
102
|
+
...parent.metadata,
|
|
103
|
+
id,
|
|
104
|
+
title: title ?? `Fork of ${parent.metadata.title}`,
|
|
105
|
+
createdAt: now,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
parentId: sessionId,
|
|
108
|
+
},
|
|
109
|
+
messages: [...parent.messages],
|
|
110
|
+
systemPrompt: parent.systemPrompt,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
this.currentSession = forked;
|
|
114
|
+
|
|
115
|
+
if (this.config.autoSave) {
|
|
116
|
+
await this.save(forked);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return forked;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Save a session to disk
|
|
124
|
+
*/
|
|
125
|
+
async save(session: Session): Promise<void> {
|
|
126
|
+
await this.init();
|
|
127
|
+
session.metadata.updatedAt = new Date().toISOString();
|
|
128
|
+
session.metadata.messageCount = session.messages.length;
|
|
129
|
+
|
|
130
|
+
const filePath = this.getSessionPath(session.metadata.id);
|
|
131
|
+
await fs.writeFile(filePath, JSON.stringify(session, null, 2), 'utf-8');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Load a session from disk
|
|
136
|
+
*/
|
|
137
|
+
async load(id: string): Promise<Session | null> {
|
|
138
|
+
try {
|
|
139
|
+
const filePath = this.getSessionPath(id);
|
|
140
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
141
|
+
const session = JSON.parse(content) as Session;
|
|
142
|
+
this.currentSession = session;
|
|
143
|
+
return session;
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Resume the most recent session
|
|
151
|
+
*/
|
|
152
|
+
async resumeLatest(): Promise<Session | null> {
|
|
153
|
+
const sessions = await this.list();
|
|
154
|
+
if (sessions.length === 0) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return this.load(sessions[0].id);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resume by index (1-based, most recent first)
|
|
162
|
+
*/
|
|
163
|
+
async resumeByIndex(index: number): Promise<Session | null> {
|
|
164
|
+
const sessions = await this.list();
|
|
165
|
+
if (index < 1 || index > sessions.length) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return this.load(sessions[index - 1].id);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* List sessions, optionally filtered by project directory
|
|
173
|
+
*/
|
|
174
|
+
async list(options?: { cwd?: string; all?: boolean }): Promise<SessionListItem[]> {
|
|
175
|
+
await this.init();
|
|
176
|
+
const filterCwd = options?.all ? undefined : (options?.cwd ?? process.cwd());
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const files = await fs.readdir(this.storageDir);
|
|
180
|
+
const sessions: SessionListItem[] = [];
|
|
181
|
+
|
|
182
|
+
for (const file of files) {
|
|
183
|
+
if (!file.endsWith('.json')) continue;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const filePath = path.join(this.storageDir, file);
|
|
187
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
188
|
+
const session = JSON.parse(content) as Session;
|
|
189
|
+
|
|
190
|
+
// Filter by cwd if specified
|
|
191
|
+
if (filterCwd && session.metadata.cwd !== filterCwd) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Get first user message as preview
|
|
196
|
+
const firstUserMsg = session.messages.find((m) => m.role === 'user');
|
|
197
|
+
let preview = '';
|
|
198
|
+
if (firstUserMsg) {
|
|
199
|
+
const text =
|
|
200
|
+
typeof firstUserMsg.content === 'string'
|
|
201
|
+
? firstUserMsg.content
|
|
202
|
+
: firstUserMsg.content
|
|
203
|
+
.filter((c) => c.type === 'text')
|
|
204
|
+
.map((c) => (c as { text: string }).text)
|
|
205
|
+
.join(' ');
|
|
206
|
+
preview = text.slice(0, 80) + (text.length > 80 ? '...' : '');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
sessions.push({
|
|
210
|
+
id: session.metadata.id,
|
|
211
|
+
title: session.metadata.title,
|
|
212
|
+
updatedAt: session.metadata.updatedAt,
|
|
213
|
+
messageCount: session.metadata.messageCount,
|
|
214
|
+
preview,
|
|
215
|
+
});
|
|
216
|
+
} catch {
|
|
217
|
+
// Skip invalid files
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Sort by updated time, newest first
|
|
222
|
+
sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
223
|
+
|
|
224
|
+
return sessions;
|
|
225
|
+
} catch {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Delete a session
|
|
232
|
+
*/
|
|
233
|
+
async delete(id: string): Promise<boolean> {
|
|
234
|
+
try {
|
|
235
|
+
const filePath = this.getSessionPath(id);
|
|
236
|
+
await fs.unlink(filePath);
|
|
237
|
+
|
|
238
|
+
if (this.currentSession?.metadata.id === id) {
|
|
239
|
+
this.currentSession = null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Clear old sessions based on config
|
|
250
|
+
*/
|
|
251
|
+
async cleanup(): Promise<number> {
|
|
252
|
+
const sessions = await this.list();
|
|
253
|
+
let deleted = 0;
|
|
254
|
+
|
|
255
|
+
const now = Date.now();
|
|
256
|
+
const maxAge = this.config.maxAge * 24 * 60 * 60 * 1000;
|
|
257
|
+
|
|
258
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
259
|
+
const session = sessions[i];
|
|
260
|
+
const age = now - new Date(session.updatedAt).getTime();
|
|
261
|
+
|
|
262
|
+
// Delete if too old or exceeds max count
|
|
263
|
+
if (age > maxAge || i >= this.config.maxSessions) {
|
|
264
|
+
if (await this.delete(session.id)) {
|
|
265
|
+
deleted++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return deleted;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get current session
|
|
275
|
+
*/
|
|
276
|
+
getCurrent(): Session | null {
|
|
277
|
+
return this.currentSession;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Set current session
|
|
282
|
+
*/
|
|
283
|
+
setCurrent(session: Session): void {
|
|
284
|
+
this.currentSession = session;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Add message to current session
|
|
289
|
+
*/
|
|
290
|
+
async addMessage(message: Message): Promise<void> {
|
|
291
|
+
if (!this.currentSession) {
|
|
292
|
+
throw new Error('No active session');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.currentSession.messages.push(message);
|
|
296
|
+
|
|
297
|
+
if (this.config.autoSave) {
|
|
298
|
+
await this.save(this.currentSession);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Update session title
|
|
304
|
+
*/
|
|
305
|
+
async updateTitle(id: string, title: string): Promise<void> {
|
|
306
|
+
const session = await this.load(id);
|
|
307
|
+
if (session) {
|
|
308
|
+
session.metadata.title = title;
|
|
309
|
+
await this.save(session);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get messages from current session
|
|
315
|
+
*/
|
|
316
|
+
getMessages(): Message[] {
|
|
317
|
+
return this.currentSession?.messages ?? [];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Clear current session messages
|
|
322
|
+
*/
|
|
323
|
+
clearMessages(): void {
|
|
324
|
+
if (this.currentSession) {
|
|
325
|
+
this.currentSession.messages = [];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Export session to JSON
|
|
331
|
+
*/
|
|
332
|
+
async export(id: string): Promise<string> {
|
|
333
|
+
const session = await this.load(id);
|
|
334
|
+
if (!session) {
|
|
335
|
+
throw new Error(`Session not found: ${id}`);
|
|
336
|
+
}
|
|
337
|
+
return JSON.stringify(session, null, 2);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Import session from JSON
|
|
342
|
+
*/
|
|
343
|
+
async import(json: string): Promise<Session> {
|
|
344
|
+
const session = JSON.parse(json) as Session;
|
|
345
|
+
|
|
346
|
+
// Generate new ID to avoid conflicts
|
|
347
|
+
session.metadata.id = this.generateId();
|
|
348
|
+
session.metadata.createdAt = new Date().toISOString();
|
|
349
|
+
session.metadata.updatedAt = new Date().toISOString();
|
|
350
|
+
|
|
351
|
+
await this.save(session);
|
|
352
|
+
return session;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Management Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Message } from '../providers/types.js';
|
|
6
|
+
|
|
7
|
+
export interface SessionMetadata {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
model: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
messageCount: number;
|
|
16
|
+
tokenUsage?: {
|
|
17
|
+
input: number;
|
|
18
|
+
output: number;
|
|
19
|
+
};
|
|
20
|
+
parentId?: string; // For forked sessions
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Session {
|
|
24
|
+
metadata: SessionMetadata;
|
|
25
|
+
messages: Message[];
|
|
26
|
+
systemPrompt?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SessionListItem {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
messageCount: number;
|
|
34
|
+
preview: string; // First user message preview
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SessionConfig {
|
|
38
|
+
storageDir: string;
|
|
39
|
+
maxSessions: number;
|
|
40
|
+
maxAge: number; // Days
|
|
41
|
+
autoSave: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const DEFAULT_SESSION_CONFIG: SessionConfig = {
|
|
45
|
+
storageDir: '~/.gencode/sessions',
|
|
46
|
+
maxSessions: 50,
|
|
47
|
+
maxAge: 30,
|
|
48
|
+
autoSave: true,
|
|
49
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash Tool - Execute shell commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import type { Tool, ToolContext, ToolResult } from '../types.js';
|
|
7
|
+
import { BashInputSchema, type BashInput } from '../types.js';
|
|
8
|
+
|
|
9
|
+
export const bashTool: Tool<BashInput> = {
|
|
10
|
+
name: 'Bash',
|
|
11
|
+
description: 'Execute a bash command and return its output. Use for running scripts, git commands, npm, etc.',
|
|
12
|
+
parameters: BashInputSchema,
|
|
13
|
+
|
|
14
|
+
async execute(input: BashInput, context: ToolContext): Promise<ToolResult> {
|
|
15
|
+
const timeout = input.timeout ?? 30000;
|
|
16
|
+
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const proc = spawn('bash', ['-c', input.command], {
|
|
19
|
+
cwd: context.cwd,
|
|
20
|
+
env: process.env,
|
|
21
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let stdout = '';
|
|
25
|
+
let stderr = '';
|
|
26
|
+
|
|
27
|
+
proc.stdout.on('data', (data) => {
|
|
28
|
+
stdout += data.toString();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
proc.stderr.on('data', (data) => {
|
|
32
|
+
stderr += data.toString();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Handle timeout
|
|
36
|
+
const timer = setTimeout(() => {
|
|
37
|
+
proc.kill('SIGTERM');
|
|
38
|
+
resolve({
|
|
39
|
+
success: false,
|
|
40
|
+
output: stdout,
|
|
41
|
+
error: `Command timed out after ${timeout}ms`,
|
|
42
|
+
});
|
|
43
|
+
}, timeout);
|
|
44
|
+
|
|
45
|
+
// Handle abort signal
|
|
46
|
+
if (context.abortSignal) {
|
|
47
|
+
context.abortSignal.addEventListener('abort', () => {
|
|
48
|
+
proc.kill('SIGTERM');
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
resolve({
|
|
51
|
+
success: false,
|
|
52
|
+
output: stdout,
|
|
53
|
+
error: 'Command aborted',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
proc.on('close', (code) => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
|
|
61
|
+
// Truncate output if too long
|
|
62
|
+
const maxLength = 30000;
|
|
63
|
+
let output = stdout;
|
|
64
|
+
if (output.length > maxLength) {
|
|
65
|
+
output = output.slice(0, maxLength) + '\n... (output truncated)';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (code === 0) {
|
|
69
|
+
resolve({
|
|
70
|
+
success: true,
|
|
71
|
+
output: output || '(no output)',
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
resolve({
|
|
75
|
+
success: false,
|
|
76
|
+
output,
|
|
77
|
+
error: stderr || `Command exited with code ${code}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
proc.on('error', (error) => {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
resolve({
|
|
85
|
+
success: false,
|
|
86
|
+
output: '',
|
|
87
|
+
error: `Failed to execute command: ${error.message}`,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit Tool - Edit file contents with string replacement
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import type { Tool, ToolResult } from '../types.js';
|
|
7
|
+
import { EditInputSchema, type EditInput, resolvePath, getErrorMessage } from '../types.js';
|
|
8
|
+
|
|
9
|
+
export const editTool: Tool<EditInput> = {
|
|
10
|
+
name: 'Edit',
|
|
11
|
+
description: 'Edit a file by replacing a specific string with another. The old_string must be unique in the file.',
|
|
12
|
+
parameters: EditInputSchema,
|
|
13
|
+
|
|
14
|
+
async execute(input, context): Promise<ToolResult> {
|
|
15
|
+
try {
|
|
16
|
+
const filePath = resolvePath(input.file_path, context.cwd);
|
|
17
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
18
|
+
const occurrences = content.split(input.old_string).length - 1;
|
|
19
|
+
|
|
20
|
+
if (occurrences === 0) {
|
|
21
|
+
return { success: false, output: '', error: 'The string to replace was not found in the file.' };
|
|
22
|
+
}
|
|
23
|
+
if (occurrences > 1) {
|
|
24
|
+
return { success: false, output: '', error: `The string to replace occurs ${occurrences} times. Please provide a more unique string.` };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const newContent = content.replace(input.old_string, input.new_string);
|
|
28
|
+
await fs.writeFile(filePath, newContent, 'utf-8');
|
|
29
|
+
|
|
30
|
+
const oldLines = input.old_string.split('\n').length;
|
|
31
|
+
const newLines = input.new_string.split('\n').length;
|
|
32
|
+
return { success: true, output: `Successfully edited ${filePath}: replaced ${oldLines} line(s) with ${newLines} line(s)` };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return { success: false, output: '', error: `Failed to edit file: ${getErrorMessage(error)}` };
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
};
|