@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.
@@ -9,8 +9,13 @@ exports.appendText = appendText;
9
9
  exports.applyKey = applyKey;
10
10
  exports.SLASH_COMMANDS = [
11
11
  { name: '/login', hint: 'Sign in with the browser' },
12
+ { name: '/logout', hint: 'Sign out of this terminal' },
12
13
  { name: '/new', hint: 'Start a new conversation (alias: /clear)' },
13
14
  { name: '/resume', hint: 'Resume a conversation (alias: /history)' },
15
+ { name: '/model', hint: 'Switch model (alias: /m)' },
16
+ { name: '/models', hint: 'List models available to your account' },
17
+ { name: '/copy', hint: 'Copy the last assistant reply' },
18
+ { name: '/export', hint: 'Write this conversation to a markdown file' },
14
19
  { name: '/status', hint: 'Show session status' },
15
20
  { name: '/ls', hint: 'List local workspace files' },
16
21
  { name: '/training', hint: 'Training mode' },
@@ -23,6 +28,7 @@ const SLASH_ALIASES = {
23
28
  '/clear': '/new',
24
29
  '/history': '/resume',
25
30
  '/exit': '/quit',
31
+ '/m': '/model',
26
32
  };
27
33
  let nextEntryId = 1;
28
34
  function entryId() {
@@ -38,6 +44,9 @@ function createPagerState() {
38
44
  slashOpen: false,
39
45
  conversationId: '',
40
46
  slashIndex: 0,
47
+ draftIndex: 0,
48
+ historyIndex: -1,
49
+ stashDraft: '',
41
50
  };
42
51
  }
43
52
  function clampSelected(entries, selected) {
@@ -59,11 +68,40 @@ function clampSlashIndex(draft, slashIndex) {
59
68
  return 0;
60
69
  return slashIndex;
61
70
  }
62
- function withDraft(state, draft, slashOpen) {
71
+ function draftChars(draft) {
72
+ return Array.from(draft);
73
+ }
74
+ function clampDraftIndex(draft, index) {
75
+ const n = draftChars(draft).length;
76
+ if (index < 0)
77
+ return 0;
78
+ if (index > n)
79
+ return n;
80
+ return index;
81
+ }
82
+ function insertAt(draft, index, value) {
83
+ const chars = draftChars(draft);
84
+ const at = clampDraftIndex(draft, index);
85
+ chars.splice(at, 0, ...Array.from(value));
86
+ return { draft: chars.join(''), draftIndex: at + Array.from(value).length };
87
+ }
88
+ function deleteBefore(draft, index) {
89
+ const chars = draftChars(draft);
90
+ const at = clampDraftIndex(draft, index);
91
+ if (at <= 0)
92
+ return { draft, draftIndex: 0 };
93
+ chars.splice(at - 1, 1);
94
+ return { draft: chars.join(''), draftIndex: at - 1 };
95
+ }
96
+ function userPrompts(state) {
97
+ return state.entries.filter((entry) => entry.kind === 'user').map((entry) => entry.text);
98
+ }
99
+ function withDraft(state, draft, slashOpen, draftIndex) {
63
100
  const open = slashOpen ?? (draft.startsWith('/') ? state.slashOpen || draft === '/' : false);
64
101
  return {
65
102
  ...state,
66
103
  draft,
104
+ draftIndex: clampDraftIndex(draft, draftIndex ?? draftChars(draft).length),
67
105
  slashOpen: open && draft.startsWith('/'),
68
106
  slashIndex: open && draft.startsWith('/') ? clampSlashIndex(draft, state.slashIndex) : 0,
69
107
  };
@@ -140,29 +178,57 @@ function appendText(state, text) {
140
178
  const seeded = startAssistant(state);
141
179
  return appendText(seeded, text);
142
180
  }
181
+ function applyHistory(state, delta) {
182
+ const prompts = userPrompts(state);
183
+ if (prompts.length === 0) {
184
+ if (state.draft === '' && state.entries.length > 0) {
185
+ const selected = clampSelected(state.entries, state.selected + delta);
186
+ return { ...state, selected };
187
+ }
188
+ return state;
189
+ }
190
+ let historyIndex = state.historyIndex;
191
+ let stashDraft = state.stashDraft;
192
+ if (historyIndex < 0) {
193
+ if (delta > 0)
194
+ return state;
195
+ stashDraft = state.draft;
196
+ historyIndex = prompts.length - 1;
197
+ }
198
+ else {
199
+ historyIndex += delta;
200
+ }
201
+ if (historyIndex < 0)
202
+ historyIndex = 0;
203
+ if (historyIndex >= prompts.length) {
204
+ return withDraft({ ...state, historyIndex: -1, stashDraft: '' }, stashDraft, stashDraft.startsWith('/'));
205
+ }
206
+ const draft = prompts[historyIndex];
207
+ return withDraft({ ...state, historyIndex, stashDraft }, draft, draft.startsWith('/'));
208
+ }
143
209
  function applyKey(state, key) {
144
210
  switch (key.type) {
145
211
  case 'char': {
146
- const draft = state.draft + key.value;
212
+ const next = insertAt(state.draft, state.draftIndex, key.value);
147
213
  if (key.value === '/' && state.draft === '') {
148
- return { ...state, draft: '/', slashOpen: true, slashIndex: 0 };
214
+ return { ...state, draft: '/', draftIndex: 1, slashOpen: true, slashIndex: 0, historyIndex: -1 };
149
215
  }
150
- if (state.slashOpen || draft.startsWith('/')) {
151
- return withDraft(state, draft, draft.startsWith('/'));
216
+ if (state.slashOpen || next.draft.startsWith('/')) {
217
+ return withDraft({ ...state, historyIndex: -1 }, next.draft, next.draft.startsWith('/'), next.draftIndex);
152
218
  }
153
- return { ...state, draft };
219
+ return { ...state, draft: next.draft, draftIndex: next.draftIndex, historyIndex: -1 };
154
220
  }
155
221
  case 'backspace': {
156
222
  if (!state.draft)
157
223
  return state;
158
- const draft = state.draft.slice(0, -1);
224
+ const next = deleteBefore(state.draft, state.draftIndex);
159
225
  if (state.slashOpen) {
160
- if (!draft.startsWith('/')) {
161
- return { ...state, draft, slashOpen: false, slashIndex: 0 };
226
+ if (!next.draft.startsWith('/')) {
227
+ return { ...state, draft: next.draft, draftIndex: next.draftIndex, slashOpen: false, slashIndex: 0 };
162
228
  }
163
- return withDraft(state, draft, true);
229
+ return withDraft(state, next.draft, true, next.draftIndex);
164
230
  }
165
- return { ...state, draft };
231
+ return { ...state, draft: next.draft, draftIndex: next.draftIndex };
166
232
  }
167
233
  case 'escape': {
168
234
  if (state.slashOpen) {
@@ -171,13 +237,20 @@ function applyKey(state, key) {
171
237
  return state;
172
238
  }
173
239
  case 'enter': {
174
- // Command execution is owned by the app layer; model only retains draft.
175
- return state;
240
+ return { ...state, historyIndex: -1, stashDraft: '' };
176
241
  }
177
- case 'left':
242
+ case 'left': {
243
+ if (state.draft) {
244
+ return { ...state, draftIndex: clampDraftIndex(state.draft, state.draftIndex - 1) };
245
+ }
178
246
  return setEntryFold(state, true);
179
- case 'right':
247
+ }
248
+ case 'right': {
249
+ if (state.draft) {
250
+ return { ...state, draftIndex: clampDraftIndex(state.draft, state.draftIndex + 1) };
251
+ }
180
252
  return setEntryFold(state, false);
253
+ }
181
254
  case 'up': {
182
255
  if (state.slashOpen) {
183
256
  const items = filterSlash(state.draft);
@@ -186,11 +259,7 @@ function applyKey(state, key) {
186
259
  const slashIndex = (state.slashIndex - 1 + items.length) % items.length;
187
260
  return { ...state, slashIndex };
188
261
  }
189
- if (state.draft === '' && state.entries.length > 0) {
190
- const selected = clampSelected(state.entries, state.selected - 1);
191
- return { ...state, selected };
192
- }
193
- return state;
262
+ return applyHistory(state, -1);
194
263
  }
195
264
  case 'down': {
196
265
  if (state.slashOpen) {
@@ -200,11 +269,17 @@ function applyKey(state, key) {
200
269
  const slashIndex = (state.slashIndex + 1) % items.length;
201
270
  return { ...state, slashIndex };
202
271
  }
203
- if (state.draft === '' && state.entries.length > 0) {
204
- const selected = clampSelected(state.entries, state.selected + 1);
205
- return { ...state, selected };
206
- }
207
- return state;
272
+ return applyHistory(state, 1);
273
+ }
274
+ case 'pageup': {
275
+ if (state.entries.length === 0)
276
+ return state;
277
+ return { ...state, selected: clampSelected(state.entries, state.selected - 8) };
278
+ }
279
+ case 'pagedown': {
280
+ if (state.entries.length === 0)
281
+ return state;
282
+ return { ...state, selected: clampSelected(state.entries, state.selected + 8) };
208
283
  }
209
284
  default:
210
285
  return state;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toProviderMessages = toProviderMessages;
4
+ exports.completeUrl = completeUrl;
5
+ exports.openaiCompatUrl = openaiCompatUrl;
6
+ exports.buildCompleteRequest = buildCompleteRequest;
7
+ exports.isServerSideAgentPayload = isServerSideAgentPayload;
8
+ exports.parseCompleteSse = parseCompleteSse;
9
+ exports.parseOpenAiChatCompletion = parseOpenAiChatCompletion;
10
+ exports.createSisuCloudModel = createSisuCloudModel;
11
+ const client_1 = require("../client");
12
+ const http_1 = require("../http");
13
+ const sse_1 = require("../sse");
14
+ const suite_1 = require("./suite");
15
+ /** OpenAI/Poe wire: assistant.tool_calls is {id,type,function:{name,arguments:string}}. */
16
+ function toProviderMessages(messages) {
17
+ return messages.map((row) => {
18
+ const out = { role: row.role, content: row.content };
19
+ if (row.tool_call_id)
20
+ out.tool_call_id = row.tool_call_id;
21
+ if (row.name)
22
+ out.name = row.name;
23
+ if (row.tool_calls?.length) {
24
+ out.tool_calls = row.tool_calls.map((call) => ({
25
+ id: call.id,
26
+ type: 'function',
27
+ function: {
28
+ name: call.name,
29
+ arguments: JSON.stringify(call.arguments || {}),
30
+ },
31
+ }));
32
+ }
33
+ return out;
34
+ });
35
+ }
36
+ function completeUrl(apiBase) {
37
+ return `${apiBase.replace(/\/+$/, '')}${suite_1.COMPLETE_PATH}`;
38
+ }
39
+ function openaiCompatUrl(apiBase) {
40
+ return `${apiBase.replace(/\/+$/, '')}${suite_1.OPENAI_COMPAT_PATH}`;
41
+ }
42
+ function buildCompleteRequest(request, options = {}) {
43
+ const stamp = (0, client_1.clientStamp)(options.client || 'cli');
44
+ return {
45
+ model: request.model,
46
+ messages: toProviderMessages(request.messages),
47
+ tools: request.tools,
48
+ stream: true,
49
+ client: stamp.client,
50
+ client_version: stamp.client_version,
51
+ client_request_id: stamp.client_request_id,
52
+ };
53
+ }
54
+ function isServerSideAgentPayload(body) {
55
+ if (!body || typeof body !== 'object')
56
+ return false;
57
+ const row = body;
58
+ return row.task_category === 'coding' && typeof row.message === 'string' && !Array.isArray(row.messages);
59
+ }
60
+ function asToolCall(raw, index) {
61
+ if (!raw || typeof raw !== 'object')
62
+ return null;
63
+ const row = raw;
64
+ const fn = row.function && typeof row.function === 'object' ? row.function : row;
65
+ const name = String(fn.name || row.name || '');
66
+ if (!name)
67
+ return null;
68
+ let args = {};
69
+ const rawArgs = fn.arguments ?? row.arguments ?? row.input;
70
+ if (typeof rawArgs === 'string') {
71
+ try {
72
+ args = JSON.parse(rawArgs);
73
+ }
74
+ catch {
75
+ args = { raw: rawArgs };
76
+ }
77
+ }
78
+ else if (rawArgs && typeof rawArgs === 'object') {
79
+ args = rawArgs;
80
+ }
81
+ return { id: String(row.id || `call_${index}`), name, arguments: args };
82
+ }
83
+ function parseCompleteSse(buffer) {
84
+ const parsed = (0, sse_1.consumeSse)(buffer.endsWith('\n\n') ? buffer : `${buffer}\n\n`);
85
+ let text = '';
86
+ const tool_calls = [];
87
+ for (const event of parsed.events) {
88
+ if (event.type === 'error') {
89
+ throw new Error(typeof event.data === 'string' ? event.data : 'stream error');
90
+ }
91
+ if (event.name === 'tool_call') {
92
+ const mapped = asToolCall(event.data, tool_calls.length);
93
+ if (mapped)
94
+ tool_calls.push(mapped);
95
+ continue;
96
+ }
97
+ if (event.name === 'delta' || event.type === 'text' || event.name === 'text') {
98
+ const chunk = (0, sse_1.sseEventText)(event);
99
+ if (chunk)
100
+ text += chunk;
101
+ }
102
+ if (event.data && typeof event.data === 'object') {
103
+ const row = event.data;
104
+ if (Array.isArray(row.tool_calls)) {
105
+ row.tool_calls.forEach((item, index) => {
106
+ const mapped = asToolCall(item, tool_calls.length + index);
107
+ if (mapped)
108
+ tool_calls.push(mapped);
109
+ });
110
+ }
111
+ if (typeof row.content === 'string')
112
+ text += row.content;
113
+ }
114
+ }
115
+ return { text, tool_calls };
116
+ }
117
+ function parseOpenAiChatCompletion(body) {
118
+ const row = body && typeof body === 'object' ? body : {};
119
+ const choices = Array.isArray(row.choices) ? row.choices : [];
120
+ const first = choices[0] && typeof choices[0] === 'object' ? choices[0] : {};
121
+ const message = first.message && typeof first.message === 'object' ? first.message : first;
122
+ const text = typeof message.content === 'string' ? message.content : '';
123
+ const rawCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
124
+ const tool_calls = rawCalls
125
+ .map((item, index) => asToolCall(item, index))
126
+ .filter((item) => Boolean(item));
127
+ return { text, tool_calls };
128
+ }
129
+ function createSisuCloudModel(http, options) {
130
+ return {
131
+ async complete(request) {
132
+ const payload = buildCompleteRequest(request, { client: options.client });
133
+ if (isServerSideAgentPayload(payload)) {
134
+ throw new Error('refusing server-side /api/chat/send agent payload');
135
+ }
136
+ const sent = await http(completeUrl(options.apiBase), {
137
+ method: 'POST',
138
+ headers: (0, http_1.authHeaders)(options.token),
139
+ body: JSON.stringify(payload),
140
+ });
141
+ if (!sent.ok) {
142
+ const body = await sent.json().catch(() => ({}));
143
+ throw new Error((0, http_1.errorDetail)(body, `complete failed (${sent.status})`));
144
+ }
145
+ const stream = sent.stream;
146
+ if (stream) {
147
+ let buffer = '';
148
+ for await (const chunk of stream())
149
+ buffer += chunk;
150
+ return parseCompleteSse(buffer);
151
+ }
152
+ const raw = await sent.text();
153
+ if (raw.trim().startsWith('{')) {
154
+ try {
155
+ return parseOpenAiChatCompletion(JSON.parse(raw));
156
+ }
157
+ catch {
158
+ // fall through to SSE
159
+ }
160
+ }
161
+ return parseCompleteSse(raw);
162
+ },
163
+ };
164
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.launchGrokBuildHeadless = exports.findGrokBuildBinary = exports.execLocalTurn = exports.createLocalRuntimeTransport = exports.resolveWorkspaceRoot = exports.localToolDefinitions = exports.dispatchLocalTool = exports.runLocalTurn = exports.createScriptedModel = exports.createLaunchStubModel = exports.collectLocalTurn = exports.toProviderMessages = exports.parseOpenAiChatCompletion = exports.parseCompleteSse = exports.isServerSideAgentPayload = exports.createSisuCloudModel = exports.completeUrl = exports.buildCompleteRequest = exports.PRODUCT_NAME = exports.PRODUCT_BIN = exports.grokBuildSuitePresent = exports.grokBuildRoot = exports.grokBuildPath = exports.assertGrokBuildSuite = void 0;
4
+ var suite_1 = require("./suite");
5
+ Object.defineProperty(exports, "assertGrokBuildSuite", { enumerable: true, get: function () { return suite_1.assertGrokBuildSuite; } });
6
+ Object.defineProperty(exports, "grokBuildPath", { enumerable: true, get: function () { return suite_1.grokBuildPath; } });
7
+ Object.defineProperty(exports, "grokBuildRoot", { enumerable: true, get: function () { return suite_1.grokBuildRoot; } });
8
+ Object.defineProperty(exports, "grokBuildSuitePresent", { enumerable: true, get: function () { return suite_1.grokBuildSuitePresent; } });
9
+ Object.defineProperty(exports, "PRODUCT_BIN", { enumerable: true, get: function () { return suite_1.PRODUCT_BIN; } });
10
+ Object.defineProperty(exports, "PRODUCT_NAME", { enumerable: true, get: function () { return suite_1.PRODUCT_NAME; } });
11
+ var adapter_1 = require("./adapter");
12
+ Object.defineProperty(exports, "buildCompleteRequest", { enumerable: true, get: function () { return adapter_1.buildCompleteRequest; } });
13
+ Object.defineProperty(exports, "completeUrl", { enumerable: true, get: function () { return adapter_1.completeUrl; } });
14
+ Object.defineProperty(exports, "createSisuCloudModel", { enumerable: true, get: function () { return adapter_1.createSisuCloudModel; } });
15
+ Object.defineProperty(exports, "isServerSideAgentPayload", { enumerable: true, get: function () { return adapter_1.isServerSideAgentPayload; } });
16
+ Object.defineProperty(exports, "parseCompleteSse", { enumerable: true, get: function () { return adapter_1.parseCompleteSse; } });
17
+ Object.defineProperty(exports, "parseOpenAiChatCompletion", { enumerable: true, get: function () { return adapter_1.parseOpenAiChatCompletion; } });
18
+ Object.defineProperty(exports, "toProviderMessages", { enumerable: true, get: function () { return adapter_1.toProviderMessages; } });
19
+ var loop_1 = require("./loop");
20
+ Object.defineProperty(exports, "collectLocalTurn", { enumerable: true, get: function () { return loop_1.collectLocalTurn; } });
21
+ Object.defineProperty(exports, "createLaunchStubModel", { enumerable: true, get: function () { return loop_1.createLaunchStubModel; } });
22
+ Object.defineProperty(exports, "createScriptedModel", { enumerable: true, get: function () { return loop_1.createScriptedModel; } });
23
+ Object.defineProperty(exports, "runLocalTurn", { enumerable: true, get: function () { return loop_1.runLocalTurn; } });
24
+ var tools_1 = require("./tools");
25
+ Object.defineProperty(exports, "dispatchLocalTool", { enumerable: true, get: function () { return tools_1.dispatchLocalTool; } });
26
+ Object.defineProperty(exports, "localToolDefinitions", { enumerable: true, get: function () { return tools_1.localToolDefinitions; } });
27
+ Object.defineProperty(exports, "resolveWorkspaceRoot", { enumerable: true, get: function () { return tools_1.resolveWorkspaceRoot; } });
28
+ var transport_1 = require("./transport");
29
+ Object.defineProperty(exports, "createLocalRuntimeTransport", { enumerable: true, get: function () { return transport_1.createLocalRuntimeTransport; } });
30
+ Object.defineProperty(exports, "execLocalTurn", { enumerable: true, get: function () { return transport_1.execLocalTurn; } });
31
+ var launch_1 = require("./launch");
32
+ Object.defineProperty(exports, "findGrokBuildBinary", { enumerable: true, get: function () { return launch_1.findGrokBuildBinary; } });
33
+ Object.defineProperty(exports, "launchGrokBuildHeadless", { enumerable: true, get: function () { return launch_1.launchGrokBuildHeadless; } });
@@ -0,0 +1,94 @@
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.grokBuildBinaryCandidates = grokBuildBinaryCandidates;
7
+ exports.findGrokBuildBinary = findGrokBuildBinary;
8
+ exports.sisuRuntimeApiBase = sisuRuntimeApiBase;
9
+ exports.writeSisuGrokConfig = writeSisuGrokConfig;
10
+ exports.sisuGrokBuildEnv = sisuGrokBuildEnv;
11
+ exports.launchGrokBuildHeadless = launchGrokBuildHeadless;
12
+ const child_process_1 = require("child_process");
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const store_1 = require("../store");
16
+ const suite_1 = require("./suite");
17
+ const adapter_1 = require("./adapter");
18
+ function grokBuildBinaryCandidates() {
19
+ const env = (process.env.SISU_GROK_BIN || '').trim();
20
+ const root = (0, suite_1.grokBuildRoot)();
21
+ const packaged = path_1.default.resolve(__dirname, '..', 'bin', 'xai-grok-pager');
22
+ return [
23
+ env,
24
+ packaged,
25
+ path_1.default.join(root, 'target', 'release', 'xai-grok-pager'),
26
+ path_1.default.join(root, 'target', 'debug', 'xai-grok-pager'),
27
+ path_1.default.join(root, 'target', 'release', 'sisu-agent'),
28
+ ].filter(Boolean);
29
+ }
30
+ function findGrokBuildBinary() {
31
+ for (const candidate of grokBuildBinaryCandidates()) {
32
+ if (candidate && fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isFile())
33
+ return candidate;
34
+ }
35
+ return null;
36
+ }
37
+ function sisuRuntimeApiBase(apiBase) {
38
+ return (0, adapter_1.openaiCompatUrl)(apiBase).replace(/\/chat\/completions$/, '');
39
+ }
40
+ function writeSisuGrokConfig() {
41
+ const auth = (0, store_1.readAuth)();
42
+ const home = (0, store_1.getSisuHome)();
43
+ fs_1.default.mkdirSync(home, { recursive: true, mode: 0o700 });
44
+ const file = path_1.default.join(home, 'config.toml');
45
+ const runtimeBase = sisuRuntimeApiBase(auth?.api_base || process.env.SISU_API_BASE || 'https://www.sisu.chat');
46
+ const body = [
47
+ '# sisu-managed grok-build config — SiSu auth + models + quota',
48
+ '[endpoints]',
49
+ `xai_api_base_url = "${runtimeBase}"`,
50
+ '',
51
+ ].join('\n');
52
+ const existing = fs_1.default.existsSync(file) ? fs_1.default.readFileSync(file, 'utf8') : '';
53
+ if (!existing || existing.includes('sisu-managed grok-build')) {
54
+ fs_1.default.writeFileSync(file, `${body}\n`, { encoding: 'utf8', mode: 0o600 });
55
+ }
56
+ return file;
57
+ }
58
+ function sisuGrokBuildEnv() {
59
+ const auth = (0, store_1.readAuth)();
60
+ const home = (0, store_1.getSisuHome)();
61
+ const runtimeBase = auth ? sisuRuntimeApiBase(auth.api_base) : '';
62
+ return {
63
+ ...process.env,
64
+ GROK_HOME: process.env.GROK_HOME || home,
65
+ SISU_HOME: home,
66
+ GROK_TELEMETRY_ENABLED: process.env.GROK_TELEMETRY_ENABLED || '0',
67
+ XAI_API_KEY: process.env.XAI_API_KEY || auth?.token || '',
68
+ SISU_API_BASE: auth?.api_base || process.env.SISU_API_BASE || 'https://www.sisu.chat',
69
+ ...(runtimeBase
70
+ ? {
71
+ GROK_XAI_API_BASE_URL: runtimeBase,
72
+ XAI_API_BASE_URL: runtimeBase,
73
+ }
74
+ : {}),
75
+ };
76
+ }
77
+ function launchGrokBuildHeadless(prompt, cwd) {
78
+ const binary = findGrokBuildBinary();
79
+ if (!binary) {
80
+ return { status: 127, stdout: '', stderr: 'grok-build binary not built', binary: null };
81
+ }
82
+ const result = (0, child_process_1.spawnSync)(binary, ['-p', prompt], {
83
+ cwd,
84
+ encoding: 'utf8',
85
+ env: sisuGrokBuildEnv(),
86
+ timeout: 30_000,
87
+ });
88
+ return {
89
+ status: result.status ?? 1,
90
+ stdout: result.stdout || '',
91
+ stderr: result.stderr || result.error?.message || '',
92
+ binary,
93
+ };
94
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runLocalTurn = runLocalTurn;
4
+ exports.collectLocalTurn = collectLocalTurn;
5
+ exports.createLaunchStubModel = createLaunchStubModel;
6
+ exports.createScriptedModel = createScriptedModel;
7
+ const crypto_1 = require("crypto");
8
+ const tools_1 = require("./tools");
9
+ const DEFAULT_MAX_ROUNDS = 8;
10
+ async function* runLocalTurn(options) {
11
+ const prompt = options.prompt.trim();
12
+ if (!prompt)
13
+ throw new Error('prompt is required');
14
+ const cwd = (0, tools_1.resolveWorkspaceRoot)(options.cwd);
15
+ const conversationId = options.conversationId || (0, crypto_1.randomUUID)();
16
+ yield { type: 'bound', text: conversationId };
17
+ const messages = [...(options.messages || []), { role: 'user', content: prompt }];
18
+ const tools = (0, tools_1.localToolDefinitions)();
19
+ const toolResults = [];
20
+ const requests = [];
21
+ let text = '';
22
+ const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
23
+ for (let round = 0; round < maxRounds; round += 1) {
24
+ const request = { model: options.model, messages: messages.map((row) => ({ ...row })), tools };
25
+ requests.push(request);
26
+ const completion = await options.client.complete(request);
27
+ if (completion.text) {
28
+ text += completion.text;
29
+ yield { type: 'text', text: completion.text };
30
+ }
31
+ if (!completion.tool_calls.length) {
32
+ return { conversationId, text, toolResults, requests };
33
+ }
34
+ messages.push({
35
+ role: 'assistant',
36
+ content: completion.text || '',
37
+ tool_calls: completion.tool_calls,
38
+ });
39
+ for (const call of completion.tool_calls) {
40
+ const result = (0, tools_1.dispatchLocalTool)(cwd, {
41
+ ...call,
42
+ id: call.id || (0, crypto_1.randomUUID)(),
43
+ arguments: call.arguments || {},
44
+ });
45
+ toolResults.push(result);
46
+ yield { type: 'tool', text: `${result.name} · ${result.ok ? 'ok' : 'error'} · ${result.content}` };
47
+ messages.push({
48
+ role: 'tool',
49
+ name: result.name,
50
+ tool_call_id: result.id,
51
+ content: result.content,
52
+ });
53
+ }
54
+ }
55
+ yield { type: 'status', text: `stopped after ${maxRounds} tool rounds` };
56
+ return { conversationId, text, toolResults, requests };
57
+ }
58
+ async function collectLocalTurn(options) {
59
+ const gen = runLocalTurn(options);
60
+ let step = await gen.next();
61
+ while (!step.done)
62
+ step = await gen.next();
63
+ return step.value;
64
+ }
65
+ function createLaunchStubModel() {
66
+ let step = 0;
67
+ return {
68
+ async complete(request) {
69
+ step += 1;
70
+ if (step === 1) {
71
+ return {
72
+ text: '',
73
+ tool_calls: [{ id: 'stub-read', name: 'read_file', arguments: { target_file: 'hello.txt' } }],
74
+ };
75
+ }
76
+ const lastTool = [...request.messages].reverse().find((row) => row.role === 'tool');
77
+ return { text: `local tool result:\n${lastTool?.content || ''}`, tool_calls: [] };
78
+ },
79
+ };
80
+ }
81
+ function createScriptedModel(script) {
82
+ const remaining = [...script];
83
+ return {
84
+ async complete() {
85
+ const next = remaining.shift();
86
+ if (!next)
87
+ return { text: '', tool_calls: [] };
88
+ return { text: next.text || '', tool_calls: next.tool_calls || [] };
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchModelCatalog = fetchModelCatalog;
4
+ exports.resolveCatalogModel = resolveCatalogModel;
5
+ exports.resolveRuntimeModel = resolveRuntimeModel;
6
+ const http_1 = require("../http");
7
+ const store_1 = require("../store");
8
+ function normalizeModelKey(value) {
9
+ return value.toLowerCase().replace(/[-_.\s]/g, '');
10
+ }
11
+ async function fetchModelCatalog(http = http_1.defaultHttp) {
12
+ const auth = (0, store_1.requireAuth)();
13
+ const response = await http(`${auth.api_base}/api/chat/models`, { headers: (0, http_1.authHeaders)(auth.token) });
14
+ const body = await response.json().catch(() => ({}));
15
+ if (!response.ok)
16
+ throw new Error((0, http_1.errorDetail)(body, `models failed (${response.status})`));
17
+ const rows = Array.isArray(body?.models) ? body.models : [];
18
+ const models = rows
19
+ .map((row) => {
20
+ const name = String(row?.name || '').trim();
21
+ if (!name)
22
+ return null;
23
+ return { name, label: String(row.display_name || row.label || name) };
24
+ })
25
+ .filter((row) => Boolean(row));
26
+ return { models, defaultModel: String(body?.default_model || '') };
27
+ }
28
+ function resolveCatalogModel(query, models) {
29
+ const needle = normalizeModelKey(query);
30
+ if (!needle)
31
+ return undefined;
32
+ return (models.find((row) => normalizeModelKey(row.name) === needle) ||
33
+ models.find((row) => normalizeModelKey(row.label) === needle) ||
34
+ models.find((row) => normalizeModelKey(row.name).includes(needle) || normalizeModelKey(row.label).includes(needle)));
35
+ }
36
+ async function resolveRuntimeModel(http, options = {}) {
37
+ if (options.stub)
38
+ return (options.explicit || '').trim() || 'stub';
39
+ const wanted = (options.explicit || '').trim();
40
+ const { models, defaultModel } = await fetchModelCatalog(http);
41
+ if (wanted) {
42
+ const match = resolveCatalogModel(wanted, models);
43
+ if (!match)
44
+ throw new Error(`unknown model ${wanted}`);
45
+ (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_model: match.name });
46
+ return match.name;
47
+ }
48
+ const last = ((0, store_1.readSession)().last_model || '').trim();
49
+ if (last && models.some((row) => row.name === last))
50
+ return last;
51
+ const name = defaultModel || models[0]?.name || '';
52
+ if (!name)
53
+ throw new Error('no SiSu model available');
54
+ (0, store_1.writeSession)({ ...(0, store_1.readSession)(), last_model: name });
55
+ return name;
56
+ }