@stevezhou/sisu 0.1.10 → 0.2.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/NOTICE +12 -0
- package/README.md +3 -2
- package/dist/commands.js +58 -47
- package/dist/main.js +18 -5
- package/dist/pager/app.js +90 -5
- package/dist/pager/input.js +43 -20
- package/dist/pager/model.js +100 -25
- package/dist/runtime/adapter.js +164 -0
- package/dist/runtime/index.js +33 -0
- package/dist/runtime/launch.js +94 -0
- package/dist/runtime/loop.js +91 -0
- package/dist/runtime/models.js +56 -0
- package/dist/runtime/sessions.js +65 -0
- package/dist/runtime/suite.js +64 -0
- package/dist/runtime/tools.js +256 -0
- package/dist/runtime/transport.js +93 -0
- package/dist/runtime/types.js +2 -0
- package/dist/store.js +1 -0
- package/dist/transport.js +2 -0
- package/dist/tui.js +25 -3
- package/package.json +4 -3
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLocalSession = createLocalSession;
|
|
7
|
+
exports.loadLocalSession = loadLocalSession;
|
|
8
|
+
exports.saveLocalSession = saveLocalSession;
|
|
9
|
+
exports.listLocalSessions = listLocalSessions;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const crypto_1 = require("crypto");
|
|
13
|
+
const store_1 = require("../store");
|
|
14
|
+
function sessionsDir() {
|
|
15
|
+
return path_1.default.join((0, store_1.getSisuHome)(), 'sessions');
|
|
16
|
+
}
|
|
17
|
+
function sessionFile(id) {
|
|
18
|
+
return path_1.default.join(sessionsDir(), `${id}.json`);
|
|
19
|
+
}
|
|
20
|
+
function createLocalSession(title, cwd, model) {
|
|
21
|
+
const session = {
|
|
22
|
+
id: (0, crypto_1.randomUUID)(),
|
|
23
|
+
title: title.slice(0, 80) || 'session',
|
|
24
|
+
cwd,
|
|
25
|
+
model,
|
|
26
|
+
messages: [],
|
|
27
|
+
updatedAt: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
saveLocalSession(session);
|
|
30
|
+
return session;
|
|
31
|
+
}
|
|
32
|
+
function loadLocalSession(id) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs_1.default.readFileSync(sessionFile(id), 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveLocalSession(session) {
|
|
41
|
+
fs_1.default.mkdirSync(sessionsDir(), { recursive: true, mode: 0o700 });
|
|
42
|
+
const file = sessionFile(session.id);
|
|
43
|
+
fs_1.default.writeFileSync(file, JSON.stringify({ ...session, updatedAt: new Date().toISOString() }, null, 2) + '\n', {
|
|
44
|
+
encoding: 'utf8',
|
|
45
|
+
mode: 0o600,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function listLocalSessions() {
|
|
49
|
+
const dir = sessionsDir();
|
|
50
|
+
if (!fs_1.default.existsSync(dir))
|
|
51
|
+
return [];
|
|
52
|
+
return fs_1.default
|
|
53
|
+
.readdirSync(dir)
|
|
54
|
+
.filter((name) => name.endsWith('.json'))
|
|
55
|
+
.map((name) => {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, name), 'utf8'));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
.filter((row) => Boolean(row))
|
|
64
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
65
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OPENAI_COMPAT_PATH = exports.COMPLETE_PATH = exports.PRODUCT_BIN = exports.PRODUCT_NAME = exports.GROK_BUILD_SURFACE = void 0;
|
|
7
|
+
exports.grokBuildRoot = grokBuildRoot;
|
|
8
|
+
exports.grokBuildPath = grokBuildPath;
|
|
9
|
+
exports.grokBuildSuitePresent = grokBuildSuitePresent;
|
|
10
|
+
exports.assertGrokBuildSuite = assertGrokBuildSuite;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
/** Absolute path to the vendored grok-build first-party tree. */
|
|
14
|
+
function grokBuildRoot() {
|
|
15
|
+
return path_1.default.resolve(__dirname, '..', '..', 'vendor', 'grok-build');
|
|
16
|
+
}
|
|
17
|
+
exports.GROK_BUILD_SURFACE = {
|
|
18
|
+
agent: 'crates/codegen/xai-grok-agent',
|
|
19
|
+
tools: 'crates/codegen/xai-grok-tools',
|
|
20
|
+
pager: 'crates/codegen/xai-grok-pager',
|
|
21
|
+
hooks: 'crates/codegen/xai-grok-hooks',
|
|
22
|
+
mcp: 'crates/codegen/xai-grok-mcp',
|
|
23
|
+
plugins: 'crates/codegen/xai-grok-plugin-marketplace',
|
|
24
|
+
subagents: 'crates/codegen/xai-grok-subagent-resolution',
|
|
25
|
+
skills: 'crates/codegen/xai-grok-tools/src/implementations/skills',
|
|
26
|
+
};
|
|
27
|
+
function grokBuildPath(surface) {
|
|
28
|
+
const root = grokBuildRoot();
|
|
29
|
+
if (surface === 'license')
|
|
30
|
+
return path_1.default.join(root, 'LICENSE');
|
|
31
|
+
if (surface === 'notice')
|
|
32
|
+
return path_1.default.join(root, 'NOTICE');
|
|
33
|
+
if (surface === 'thirdParty')
|
|
34
|
+
return path_1.default.join(root, 'THIRD-PARTY-NOTICES');
|
|
35
|
+
return path_1.default.join(root, exports.GROK_BUILD_SURFACE[surface]);
|
|
36
|
+
}
|
|
37
|
+
function grokBuildSuitePresent() {
|
|
38
|
+
const keys = [
|
|
39
|
+
'agent',
|
|
40
|
+
'tools',
|
|
41
|
+
'pager',
|
|
42
|
+
'hooks',
|
|
43
|
+
'mcp',
|
|
44
|
+
'plugins',
|
|
45
|
+
'subagents',
|
|
46
|
+
'skills',
|
|
47
|
+
'license',
|
|
48
|
+
'notice',
|
|
49
|
+
];
|
|
50
|
+
return keys.map((surface) => {
|
|
51
|
+
const file = grokBuildPath(surface);
|
|
52
|
+
return { surface, path: file, ok: fs_1.default.existsSync(file) };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function assertGrokBuildSuite() {
|
|
56
|
+
const missing = grokBuildSuitePresent().filter((row) => !row.ok);
|
|
57
|
+
if (missing.length) {
|
|
58
|
+
throw new Error(`grok-build suite missing: ${missing.map((row) => row.surface).join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.PRODUCT_NAME = 'SiSu';
|
|
62
|
+
exports.PRODUCT_BIN = 'sisu';
|
|
63
|
+
exports.COMPLETE_PATH = '/api/runtime/complete';
|
|
64
|
+
exports.OPENAI_COMPAT_PATH = '/api/runtime/v1/chat/completions';
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LOCAL_TOOL_NAMES = void 0;
|
|
7
|
+
exports.localToolDefinitions = localToolDefinitions;
|
|
8
|
+
exports.resolveWorkspaceRoot = resolveWorkspaceRoot;
|
|
9
|
+
exports.resolveInWorkspace = resolveInWorkspace;
|
|
10
|
+
exports.dispatchLocalTool = dispatchLocalTool;
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const MAX_READ_BYTES = 256 * 1024;
|
|
15
|
+
const MAX_RESULT = 40_000;
|
|
16
|
+
exports.LOCAL_TOOL_NAMES = ['read_file', 'search_replace', 'grep', 'bash'];
|
|
17
|
+
function localToolDefinitions() {
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
type: 'function',
|
|
21
|
+
function: {
|
|
22
|
+
name: 'read_file',
|
|
23
|
+
description: 'Read a file from the local workspace. Path may be relative or absolute.',
|
|
24
|
+
parameters: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
target_file: { type: 'string' },
|
|
28
|
+
path: { type: 'string' },
|
|
29
|
+
offset: { type: 'integer' },
|
|
30
|
+
limit: { type: 'integer' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'function',
|
|
37
|
+
function: {
|
|
38
|
+
name: 'search_replace',
|
|
39
|
+
description: 'Edit a file. Empty old_string creates or overwrites. replace_all replaces every match.',
|
|
40
|
+
parameters: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
file_path: { type: 'string' },
|
|
44
|
+
old_string: { type: 'string' },
|
|
45
|
+
new_string: { type: 'string' },
|
|
46
|
+
replace_all: { type: 'boolean' },
|
|
47
|
+
},
|
|
48
|
+
required: ['file_path', 'old_string', 'new_string'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'function',
|
|
54
|
+
function: {
|
|
55
|
+
name: 'grep',
|
|
56
|
+
description: 'Search file contents in the workspace (ripgrep if present).',
|
|
57
|
+
parameters: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
pattern: { type: 'string' },
|
|
61
|
+
path: { type: 'string' },
|
|
62
|
+
glob: { type: 'string' },
|
|
63
|
+
},
|
|
64
|
+
required: ['pattern'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'function',
|
|
70
|
+
function: {
|
|
71
|
+
name: 'bash',
|
|
72
|
+
description: 'Run a shell command against the workspace cwd.',
|
|
73
|
+
parameters: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
command: { type: 'string' },
|
|
77
|
+
cmd: { type: 'string' },
|
|
78
|
+
},
|
|
79
|
+
required: ['command'],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
function resolveWorkspaceRoot(cwd) {
|
|
86
|
+
const root = path_1.default.resolve(cwd || process.cwd());
|
|
87
|
+
if (!fs_1.default.existsSync(root) || !fs_1.default.statSync(root).isDirectory()) {
|
|
88
|
+
throw new Error(`workspace is not a directory: ${root}`);
|
|
89
|
+
}
|
|
90
|
+
return root;
|
|
91
|
+
}
|
|
92
|
+
function resolveInWorkspace(root, requested) {
|
|
93
|
+
const trimmed = (requested || '').trim() || '.';
|
|
94
|
+
const absolute = path_1.default.isAbsolute(trimmed) ? path_1.default.resolve(trimmed) : path_1.default.resolve(root, trimmed);
|
|
95
|
+
const rel = path_1.default.relative(root, absolute);
|
|
96
|
+
if (rel.startsWith('..') || path_1.default.isAbsolute(rel)) {
|
|
97
|
+
throw new Error(`path escapes workspace: ${requested}`);
|
|
98
|
+
}
|
|
99
|
+
return absolute;
|
|
100
|
+
}
|
|
101
|
+
function clip(text) {
|
|
102
|
+
if (text.length <= MAX_RESULT)
|
|
103
|
+
return text;
|
|
104
|
+
return `${text.slice(0, MAX_RESULT)}\n…truncated`;
|
|
105
|
+
}
|
|
106
|
+
function str(input, ...keys) {
|
|
107
|
+
for (const key of keys) {
|
|
108
|
+
const value = input[key];
|
|
109
|
+
if (typeof value === 'string' && value)
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
function readFileTool(root, input) {
|
|
115
|
+
const target = str(input, 'target_file', 'path', 'file_path');
|
|
116
|
+
if (!target)
|
|
117
|
+
throw new Error('read_file requires target_file or path');
|
|
118
|
+
const file = resolveInWorkspace(root, target);
|
|
119
|
+
const raw = fs_1.default.readFileSync(file);
|
|
120
|
+
const start = Math.max(0, Number(input.offset) || 0);
|
|
121
|
+
const limit = Number(input.limit);
|
|
122
|
+
const text = raw.subarray(0, MAX_READ_BYTES).toString('utf8');
|
|
123
|
+
const lines = text.split('\n');
|
|
124
|
+
const slice = Number.isFinite(limit) && limit > 0 ? lines.slice(start, start + limit) : lines.slice(start);
|
|
125
|
+
return slice.join('\n');
|
|
126
|
+
}
|
|
127
|
+
function searchReplaceTool(root, input) {
|
|
128
|
+
const filePath = str(input, 'file_path', 'path', 'target_file');
|
|
129
|
+
if (!filePath)
|
|
130
|
+
throw new Error('search_replace requires file_path');
|
|
131
|
+
const oldString = typeof input.old_string === 'string' ? input.old_string : '';
|
|
132
|
+
const newString = typeof input.new_string === 'string' ? input.new_string : String(input.contents ?? input.content ?? '');
|
|
133
|
+
const file = resolveInWorkspace(root, filePath);
|
|
134
|
+
if (!oldString) {
|
|
135
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
136
|
+
fs_1.default.writeFileSync(file, newString, 'utf8');
|
|
137
|
+
return `The file ${filePath} has been created.`;
|
|
138
|
+
}
|
|
139
|
+
if (!fs_1.default.existsSync(file))
|
|
140
|
+
throw new Error(`file not found: ${filePath}`);
|
|
141
|
+
const before = fs_1.default.readFileSync(file, 'utf8');
|
|
142
|
+
if (!before.includes(oldString))
|
|
143
|
+
throw new Error(`old_string not found in ${filePath}`);
|
|
144
|
+
const replaceAll = Boolean(input.replace_all);
|
|
145
|
+
const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
146
|
+
fs_1.default.writeFileSync(file, after, 'utf8');
|
|
147
|
+
return `The file ${filePath} has been updated.`;
|
|
148
|
+
}
|
|
149
|
+
function walkFiles(dir, glob, acc) {
|
|
150
|
+
let entries;
|
|
151
|
+
try {
|
|
152
|
+
entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
if (entry.name === '.git' || entry.name === 'node_modules')
|
|
159
|
+
continue;
|
|
160
|
+
const full = path_1.default.join(dir, entry.name);
|
|
161
|
+
if (entry.isDirectory())
|
|
162
|
+
walkFiles(full, glob, acc);
|
|
163
|
+
else if (!glob || matchGlob(entry.name, glob))
|
|
164
|
+
acc.push(full);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function matchGlob(name, glob) {
|
|
168
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
169
|
+
return new RegExp(`^${escaped}$`).test(name);
|
|
170
|
+
}
|
|
171
|
+
function grepTool(root, input) {
|
|
172
|
+
const pattern = str(input, 'pattern');
|
|
173
|
+
if (!pattern)
|
|
174
|
+
throw new Error('grep requires pattern');
|
|
175
|
+
const searchRoot = resolveInWorkspace(root, str(input, 'path') || '.');
|
|
176
|
+
const glob = str(input, 'glob') || undefined;
|
|
177
|
+
const rg = (0, child_process_1.spawnSync)('rg', ['--line-number', '--no-heading', '--color', 'never', pattern, searchRoot], {
|
|
178
|
+
encoding: 'utf8',
|
|
179
|
+
timeout: 15_000,
|
|
180
|
+
maxBuffer: MAX_RESULT,
|
|
181
|
+
});
|
|
182
|
+
if (rg.error || rg.status === 127) {
|
|
183
|
+
const files = [];
|
|
184
|
+
if (fs_1.default.existsSync(searchRoot) && fs_1.default.statSync(searchRoot).isFile())
|
|
185
|
+
files.push(searchRoot);
|
|
186
|
+
else
|
|
187
|
+
walkFiles(searchRoot, glob, files);
|
|
188
|
+
const re = new RegExp(pattern);
|
|
189
|
+
const hits = [];
|
|
190
|
+
for (const file of files) {
|
|
191
|
+
let text = '';
|
|
192
|
+
try {
|
|
193
|
+
text = fs_1.default.readFileSync(file, 'utf8');
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const lines = text.split('\n');
|
|
199
|
+
lines.forEach((line, index) => {
|
|
200
|
+
if (re.test(line))
|
|
201
|
+
hits.push(`${file}:${index + 1}:${line}`);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return hits.length ? hits.join('\n') : 'no matches';
|
|
205
|
+
}
|
|
206
|
+
if (rg.status === 1)
|
|
207
|
+
return 'no matches';
|
|
208
|
+
if (rg.status !== 0)
|
|
209
|
+
throw new Error(rg.stderr || `rg failed (${rg.status})`);
|
|
210
|
+
return (rg.stdout || '').trim() || 'no matches';
|
|
211
|
+
}
|
|
212
|
+
function bashTool(root, input) {
|
|
213
|
+
const command = str(input, 'command', 'cmd');
|
|
214
|
+
if (!command)
|
|
215
|
+
throw new Error('bash requires command');
|
|
216
|
+
const result = (0, child_process_1.spawnSync)(command, {
|
|
217
|
+
cwd: root,
|
|
218
|
+
encoding: 'utf8',
|
|
219
|
+
shell: true,
|
|
220
|
+
timeout: 30_000,
|
|
221
|
+
maxBuffer: MAX_RESULT,
|
|
222
|
+
env: { ...process.env, SISU_WORKSPACE: root },
|
|
223
|
+
});
|
|
224
|
+
const out = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
225
|
+
if (result.error)
|
|
226
|
+
throw new Error(result.error.message);
|
|
227
|
+
if (result.status !== 0) {
|
|
228
|
+
return clip(out || `exit ${result.status}`);
|
|
229
|
+
}
|
|
230
|
+
return clip(out || '(ok)');
|
|
231
|
+
}
|
|
232
|
+
function dispatchLocalTool(root, call) {
|
|
233
|
+
try {
|
|
234
|
+
const name = call.name;
|
|
235
|
+
let content = '';
|
|
236
|
+
if (name === 'read_file')
|
|
237
|
+
content = readFileTool(root, call.arguments);
|
|
238
|
+
else if (name === 'search_replace')
|
|
239
|
+
content = searchReplaceTool(root, call.arguments);
|
|
240
|
+
else if (name === 'grep')
|
|
241
|
+
content = grepTool(root, call.arguments);
|
|
242
|
+
else if (name === 'bash' || name === 'run_terminal_cmd')
|
|
243
|
+
content = bashTool(root, call.arguments);
|
|
244
|
+
else
|
|
245
|
+
throw new Error(`unknown tool: ${name}`);
|
|
246
|
+
return { id: call.id, name, content: clip(content), ok: true };
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
return {
|
|
250
|
+
id: call.id,
|
|
251
|
+
name: call.name,
|
|
252
|
+
content: error instanceof Error ? error.message : String(error),
|
|
253
|
+
ok: false,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLocalRuntimeTransport = createLocalRuntimeTransport;
|
|
4
|
+
exports.execLocalTurn = execLocalTurn;
|
|
5
|
+
const store_1 = require("../store");
|
|
6
|
+
const adapter_1 = require("./adapter");
|
|
7
|
+
const loop_1 = require("./loop");
|
|
8
|
+
const models_1 = require("./models");
|
|
9
|
+
const sessions_1 = require("./sessions");
|
|
10
|
+
const tools_1 = require("./tools");
|
|
11
|
+
function createLocalRuntimeTransport(http, options = {}) {
|
|
12
|
+
return {
|
|
13
|
+
async *send(prompt, sendOptions = {}) {
|
|
14
|
+
const cwd = (0, tools_1.resolveWorkspaceRoot)(options.cwd);
|
|
15
|
+
let conversationId = sendOptions.conversationId || (!sendOptions.newConversation ? (0, store_1.readSession)().last_conversation_id : '') || '';
|
|
16
|
+
let existing = conversationId ? (0, sessions_1.loadLocalSession)(conversationId) : null;
|
|
17
|
+
if (!existing || sendOptions.newConversation) {
|
|
18
|
+
existing = (0, sessions_1.createLocalSession)(prompt.trim().slice(0, 50), cwd, (0, store_1.readSession)().last_model);
|
|
19
|
+
conversationId = existing.id;
|
|
20
|
+
}
|
|
21
|
+
(0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: conversationId });
|
|
22
|
+
const client = options.modelClient || cloudClient(http, options.client || 'tui');
|
|
23
|
+
const model = options.modelClient
|
|
24
|
+
? existing.model || (0, store_1.readSession)().last_model || 'stub'
|
|
25
|
+
: await (0, models_1.resolveRuntimeModel)(http, { explicit: existing.model || (0, store_1.readSession)().last_model });
|
|
26
|
+
const gen = (0, loop_1.runLocalTurn)({
|
|
27
|
+
prompt,
|
|
28
|
+
cwd,
|
|
29
|
+
model,
|
|
30
|
+
client,
|
|
31
|
+
messages: existing.messages,
|
|
32
|
+
conversationId,
|
|
33
|
+
});
|
|
34
|
+
let step = await gen.next();
|
|
35
|
+
while (!step.done) {
|
|
36
|
+
const event = step.value;
|
|
37
|
+
yield event;
|
|
38
|
+
step = await gen.next();
|
|
39
|
+
}
|
|
40
|
+
const result = step.value;
|
|
41
|
+
existing.messages.push({ role: 'user', content: prompt });
|
|
42
|
+
if (result.text)
|
|
43
|
+
existing.messages.push({ role: 'assistant', content: result.text });
|
|
44
|
+
(0, sessions_1.saveLocalSession)(existing);
|
|
45
|
+
return { conversationId: result.conversationId };
|
|
46
|
+
},
|
|
47
|
+
async listConversations() {
|
|
48
|
+
return (0, sessions_1.listLocalSessions)().map((row) => ({ id: row.id, title: row.title, client: 'tui' }));
|
|
49
|
+
},
|
|
50
|
+
async getConversation(id) {
|
|
51
|
+
const row = (0, sessions_1.loadLocalSession)(id);
|
|
52
|
+
if (!row)
|
|
53
|
+
throw new Error('conversation not found');
|
|
54
|
+
const messages = row.messages.map((message, index) => ({
|
|
55
|
+
id: `${row.id}-${index}`,
|
|
56
|
+
role: message.role === 'tool' ? 'assistant' : message.role,
|
|
57
|
+
content: message.content,
|
|
58
|
+
...(message.role === 'tool' ? { message_type: 'tool_result' } : {}),
|
|
59
|
+
}));
|
|
60
|
+
return { id: row.id, title: row.title, messages };
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function execLocalTurn(prompt, options = {}) {
|
|
65
|
+
const cwd = (0, tools_1.resolveWorkspaceRoot)(options.cwd);
|
|
66
|
+
let conversationId = options.conversationId || (!options.newConversation ? (0, store_1.readSession)().last_conversation_id : '') || '';
|
|
67
|
+
let existing = conversationId ? (0, sessions_1.loadLocalSession)(conversationId) : null;
|
|
68
|
+
if (!existing) {
|
|
69
|
+
existing = (0, sessions_1.createLocalSession)(prompt.trim().slice(0, 50), cwd, options.model);
|
|
70
|
+
conversationId = existing.id;
|
|
71
|
+
}
|
|
72
|
+
const client = options.modelClient || (options.http ? cloudClient(options.http, options.client || 'cli') : undefined);
|
|
73
|
+
if (!client)
|
|
74
|
+
throw new Error('model client required');
|
|
75
|
+
const result = await (0, loop_1.collectLocalTurn)({
|
|
76
|
+
prompt,
|
|
77
|
+
cwd,
|
|
78
|
+
model: options.model || existing.model || (0, store_1.readSession)().last_model || 'stub',
|
|
79
|
+
client,
|
|
80
|
+
messages: existing.messages,
|
|
81
|
+
conversationId,
|
|
82
|
+
});
|
|
83
|
+
existing.messages.push({ role: 'user', content: prompt });
|
|
84
|
+
if (result.text)
|
|
85
|
+
existing.messages.push({ role: 'assistant', content: result.text });
|
|
86
|
+
(0, sessions_1.saveLocalSession)(existing);
|
|
87
|
+
(0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_conversation_id: result.conversationId, last_model: options.model || (0, store_1.readSession)().last_model });
|
|
88
|
+
return { conversationId: result.conversationId, text: result.text, toolResults: result.toolResults };
|
|
89
|
+
}
|
|
90
|
+
function cloudClient(http, client) {
|
|
91
|
+
const auth = (0, store_1.requireAuth)();
|
|
92
|
+
return (0, adapter_1.createSisuCloudModel)(http, { apiBase: auth.api_base, token: auth.token, client });
|
|
93
|
+
}
|
package/dist/store.js
CHANGED
package/dist/transport.js
CHANGED
|
@@ -64,6 +64,7 @@ function createFastApiTransport(http) {
|
|
|
64
64
|
headers: (0, http_1.authHeaders)(auth.token),
|
|
65
65
|
body: JSON.stringify({
|
|
66
66
|
title: text.slice(0, 50),
|
|
67
|
+
model: session.last_model || undefined,
|
|
67
68
|
project_id: session.last_project_id || undefined,
|
|
68
69
|
client: stamp.client,
|
|
69
70
|
client_version: stamp.client_version,
|
|
@@ -87,6 +88,7 @@ function createFastApiTransport(http) {
|
|
|
87
88
|
body: JSON.stringify({
|
|
88
89
|
conversation_id: conversationId,
|
|
89
90
|
message: text,
|
|
91
|
+
model: session.last_model || undefined,
|
|
90
92
|
task_category: 'coding',
|
|
91
93
|
client: stamp.client,
|
|
92
94
|
client_version: stamp.client_version,
|
package/dist/tui.js
CHANGED
|
@@ -17,7 +17,9 @@ const mobius_1 = require("./mobius");
|
|
|
17
17
|
const app_1 = require("./pager/app");
|
|
18
18
|
const stdio_1 = require("./pager/stdio");
|
|
19
19
|
const store_1 = require("./store");
|
|
20
|
-
const
|
|
20
|
+
const launch_1 = require("./runtime/launch");
|
|
21
|
+
const transport_1 = require("./runtime/transport");
|
|
22
|
+
const child_process_1 = require("child_process");
|
|
21
23
|
function shouldAnimateSplash(env = process.env, tty = Boolean(process.stdout.isTTY)) {
|
|
22
24
|
if (env.SISU_TUI_STATIC === '1')
|
|
23
25
|
return false;
|
|
@@ -171,9 +173,14 @@ async function promptLogin(io, login) {
|
|
|
171
173
|
function tuiHelp() {
|
|
172
174
|
return [
|
|
173
175
|
'/login sign in with the browser',
|
|
176
|
+
'/logout sign out',
|
|
177
|
+
'/model switch model (alias /m)',
|
|
178
|
+
'/models list available models',
|
|
179
|
+
'/copy copy last reply to ~/.sisu/last-copy.txt',
|
|
180
|
+
'/export write the thread to a markdown file',
|
|
174
181
|
'/status account and quota',
|
|
175
182
|
'/ls local workspace files',
|
|
176
|
-
'/history saved
|
|
183
|
+
'/history saved local sessions',
|
|
177
184
|
'/open <id> continue a saved conversation',
|
|
178
185
|
'/new start a new conversation',
|
|
179
186
|
'/training on|off allow or refuse training use of new turns',
|
|
@@ -217,13 +224,28 @@ async function runTui(io, deps = {}) {
|
|
|
217
224
|
},
|
|
218
225
|
}, http);
|
|
219
226
|
};
|
|
227
|
+
if (usePager && !deps.pager) {
|
|
228
|
+
const grokBin = (0, launch_1.findGrokBuildBinary)();
|
|
229
|
+
if (grokBin && process.stdout.isTTY) {
|
|
230
|
+
(0, launch_1.writeSisuGrokConfig)();
|
|
231
|
+
io.close?.();
|
|
232
|
+
const child = (0, child_process_1.spawn)(grokBin, [], { stdio: 'inherit', env: (0, launch_1.sisuGrokBuildEnv)(), cwd: process.cwd() });
|
|
233
|
+
return await new Promise((resolve) => {
|
|
234
|
+
child.on('exit', (code) => resolve(code ?? 1));
|
|
235
|
+
child.on('error', () => resolve(1));
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
220
239
|
if (usePager) {
|
|
221
240
|
io.close?.();
|
|
222
|
-
const transport = (0, transport_1.
|
|
241
|
+
const transport = (0, transport_1.createLocalRuntimeTransport)(http, { client: 'tui' });
|
|
223
242
|
return await (deps.pager ?? app_1.runPager)((0, stdio_1.stdioPagerIo)(), transport, {
|
|
224
243
|
columns,
|
|
225
244
|
email: account?.email,
|
|
226
245
|
login: startWebLogin,
|
|
246
|
+
logout: commands_1.logoutCommand,
|
|
247
|
+
models: () => (0, commands_1.listModelsCommand)(http),
|
|
248
|
+
setModel: (name) => (0, commands_1.setModelCommand)(name, http),
|
|
227
249
|
intro: animate,
|
|
228
250
|
sleep: deps.sleep,
|
|
229
251
|
quota: async () => (0, commands_1.formatQuota)(await (0, commands_1.fetchBalance)(http)),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stevezhou/sisu",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "SiSu CLI —
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "SiSu CLI — local grok-build runtime, SiSu cloud models",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://www.sisu.chat",
|
|
7
7
|
"bugs": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"main": "dist/main.js",
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
-
"README.md"
|
|
25
|
+
"README.md",
|
|
26
|
+
"NOTICE"
|
|
26
27
|
],
|
|
27
28
|
"publishConfig": {
|
|
28
29
|
"access": "public"
|