goatchain 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +529 -0
- package/cli/args.mjs +113 -0
- package/cli/clack.mjs +111 -0
- package/cli/clipboard.mjs +320 -0
- package/cli/files.mjs +247 -0
- package/cli/index.mjs +299 -0
- package/cli/itermPaste.mjs +147 -0
- package/cli/persist.mjs +205 -0
- package/cli/repl.mjs +3141 -0
- package/cli/sdk.mjs +341 -0
- package/cli/sessionTransfer.mjs +118 -0
- package/cli/turn.mjs +751 -0
- package/cli/ui.mjs +138 -0
- package/cli.mjs +5 -0
- package/dist/index.cjs +4860 -0
- package/dist/index.d.cts +3479 -0
- package/dist/index.d.ts +3479 -0
- package/dist/index.js +4795 -0
- package/package.json +68 -0
package/cli/sdk.mjs
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
export async function loadSdk() {
|
|
4
|
+
try {
|
|
5
|
+
return await import('../dist/index.js')
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
9
|
+
throw new Error(
|
|
10
|
+
[
|
|
11
|
+
'Failed to load `./dist/index.js`.',
|
|
12
|
+
'If you are running from the repo, run `pnpm -s build` first.',
|
|
13
|
+
`cause: ${msg}`,
|
|
14
|
+
].join('\n'),
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function applyDefaultRequestOptions(model, defaults) {
|
|
20
|
+
const hasSetModelId = typeof model?.setModelId === 'function'
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
get modelId() {
|
|
24
|
+
return model.modelId
|
|
25
|
+
},
|
|
26
|
+
...(hasSetModelId
|
|
27
|
+
? {
|
|
28
|
+
setModelId(id) {
|
|
29
|
+
model.setModelId(id)
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
: {}),
|
|
33
|
+
async invoke(messages, options) {
|
|
34
|
+
return model.invoke(messages, options)
|
|
35
|
+
},
|
|
36
|
+
stream: (messagesOrRequest, options) => {
|
|
37
|
+
if (Array.isArray(messagesOrRequest)) {
|
|
38
|
+
return model.stream(messagesOrRequest, options)
|
|
39
|
+
}
|
|
40
|
+
return model.stream({ ...defaults, ...messagesOrRequest })
|
|
41
|
+
},
|
|
42
|
+
...(typeof model?.run === 'function'
|
|
43
|
+
? {
|
|
44
|
+
run: (req) => model.run({ ...defaults, ...req }),
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createTools(sdk, workspaceCwd) {
|
|
51
|
+
const {
|
|
52
|
+
ToolRegistry,
|
|
53
|
+
WriteTool,
|
|
54
|
+
ReadTool,
|
|
55
|
+
EditTool,
|
|
56
|
+
GlobTool,
|
|
57
|
+
GrepTool,
|
|
58
|
+
WebSearchTool,
|
|
59
|
+
} = sdk
|
|
60
|
+
|
|
61
|
+
if (![ToolRegistry, WriteTool, ReadTool, EditTool, GlobTool, GrepTool, WebSearchTool].every(v => typeof v === 'function')) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
[
|
|
64
|
+
'Missing builtin tool exports from `./dist/index.js`.',
|
|
65
|
+
'Run `pnpm -s build` (repo) or reinstall the package to get an up-to-date build.',
|
|
66
|
+
].join('\n'),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const cacheDir = path.join(workspaceCwd, '.goatchain')
|
|
71
|
+
|
|
72
|
+
const isSubpath = (parent, child) => {
|
|
73
|
+
const rel = path.relative(path.resolve(parent), path.resolve(child))
|
|
74
|
+
if (rel === '')
|
|
75
|
+
return true
|
|
76
|
+
if (rel === '..')
|
|
77
|
+
return false
|
|
78
|
+
return !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const ensureNotInCache = (absPath) => {
|
|
82
|
+
if (isSubpath(cacheDir, absPath)) {
|
|
83
|
+
throw new Error(`Access denied: ${absPath}\nRestricted: ${cacheDir}`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ensureAllowedFile = (filePath) => {
|
|
88
|
+
const abs = path.isAbsolute(filePath) ? filePath : path.resolve(workspaceCwd, filePath)
|
|
89
|
+
if (!isSubpath(workspaceCwd, abs)) {
|
|
90
|
+
throw new Error(`Access denied: ${abs}\nAllowed directory: ${workspaceCwd}`)
|
|
91
|
+
}
|
|
92
|
+
ensureNotInCache(abs)
|
|
93
|
+
return abs
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ensureAllowedDir = (dirPath) => {
|
|
97
|
+
const abs = path.isAbsolute(dirPath) ? dirPath : path.resolve(workspaceCwd, dirPath)
|
|
98
|
+
if (!isSubpath(workspaceCwd, abs)) {
|
|
99
|
+
throw new Error(`Access denied: ${abs}\nAllowed directory: ${workspaceCwd}`)
|
|
100
|
+
}
|
|
101
|
+
ensureNotInCache(abs)
|
|
102
|
+
return abs
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const registry = new ToolRegistry()
|
|
106
|
+
|
|
107
|
+
const writeInner = new WriteTool({ cwd: workspaceCwd, allowedDirectory: workspaceCwd })
|
|
108
|
+
registry.register({
|
|
109
|
+
name: writeInner.name,
|
|
110
|
+
description: writeInner.description,
|
|
111
|
+
parameters: writeInner.parameters,
|
|
112
|
+
riskLevel: writeInner.riskLevel,
|
|
113
|
+
execute: (args) => {
|
|
114
|
+
if (args && typeof args === 'object' && 'file_path' in args) {
|
|
115
|
+
ensureAllowedFile(args.file_path)
|
|
116
|
+
}
|
|
117
|
+
return writeInner.execute(args)
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const readInner = new ReadTool({ cwd: workspaceCwd, allowedDirectory: workspaceCwd })
|
|
122
|
+
registry.register({
|
|
123
|
+
name: readInner.name,
|
|
124
|
+
description: readInner.description,
|
|
125
|
+
parameters: readInner.parameters,
|
|
126
|
+
riskLevel: readInner.riskLevel,
|
|
127
|
+
execute: (args) => {
|
|
128
|
+
if (args && typeof args === 'object' && 'file_path' in args) {
|
|
129
|
+
ensureAllowedFile(args.file_path)
|
|
130
|
+
}
|
|
131
|
+
return readInner.execute(args)
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const editInner = new EditTool({ cwd: workspaceCwd })
|
|
136
|
+
registry.register({
|
|
137
|
+
name: editInner.name,
|
|
138
|
+
description: editInner.description,
|
|
139
|
+
parameters: editInner.parameters,
|
|
140
|
+
riskLevel: editInner.riskLevel,
|
|
141
|
+
execute: (args) => {
|
|
142
|
+
if (args && typeof args === 'object' && 'file_path' in args) {
|
|
143
|
+
ensureAllowedFile(args.file_path)
|
|
144
|
+
}
|
|
145
|
+
return editInner.execute(args)
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const globInner = new GlobTool({ cwd: workspaceCwd })
|
|
150
|
+
registry.register({
|
|
151
|
+
name: globInner.name,
|
|
152
|
+
description: globInner.description,
|
|
153
|
+
parameters: globInner.parameters,
|
|
154
|
+
riskLevel: globInner.riskLevel,
|
|
155
|
+
execute: (args) => {
|
|
156
|
+
if (args && typeof args === 'object' && 'path' in args && typeof args.path === 'string' && args.path.trim()) {
|
|
157
|
+
ensureAllowedDir(args.path)
|
|
158
|
+
}
|
|
159
|
+
return globInner.execute(args)
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const grepInner = new GrepTool({ cwd: workspaceCwd })
|
|
164
|
+
registry.register({
|
|
165
|
+
name: grepInner.name,
|
|
166
|
+
description: grepInner.description,
|
|
167
|
+
parameters: grepInner.parameters,
|
|
168
|
+
riskLevel: grepInner.riskLevel,
|
|
169
|
+
execute: (args) => {
|
|
170
|
+
if (args && typeof args === 'object' && 'path' in args && typeof args.path === 'string' && args.path.trim()) {
|
|
171
|
+
ensureAllowedDir(args.path)
|
|
172
|
+
}
|
|
173
|
+
return grepInner.execute(args)
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
if(process.env.SERPER_API_KEY){
|
|
178
|
+
const webSearchInner = new WebSearchTool({
|
|
179
|
+
apiKey: process.env.SERPER_API_KEY,
|
|
180
|
+
})
|
|
181
|
+
registry.register({
|
|
182
|
+
name: webSearchInner.name,
|
|
183
|
+
description: webSearchInner.description,
|
|
184
|
+
parameters: webSearchInner.parameters,
|
|
185
|
+
riskLevel: webSearchInner.riskLevel,
|
|
186
|
+
execute: (args) => webSearchInner.execute(args),
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { registry, workspaceCwd }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function createModelClient(sdk, { getApiKey, modelId, baseUrl, requestDefaults }) {
|
|
194
|
+
const { createModel, createOpenAIAdapter } = sdk
|
|
195
|
+
|
|
196
|
+
if (typeof createModel === 'function' && typeof createOpenAIAdapter === 'function') {
|
|
197
|
+
const secretProvider = {
|
|
198
|
+
get: (name) => {
|
|
199
|
+
if (name === 'OPENAI_API_KEY')
|
|
200
|
+
return getApiKey()
|
|
201
|
+
return process.env[name]
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const baseModel = createModel({
|
|
206
|
+
adapters: [
|
|
207
|
+
createOpenAIAdapter({
|
|
208
|
+
defaultModelId: modelId,
|
|
209
|
+
baseUrl,
|
|
210
|
+
apiKeySecretName: 'OPENAI_API_KEY',
|
|
211
|
+
secretProvider,
|
|
212
|
+
}),
|
|
213
|
+
],
|
|
214
|
+
})
|
|
215
|
+
return applyDefaultRequestOptions(baseModel, requestDefaults)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const { default: OpenAI } = await import('openai')
|
|
219
|
+
let cachedClient = null
|
|
220
|
+
let currentModelId = modelId
|
|
221
|
+
let currentBaseUrl = baseUrl
|
|
222
|
+
|
|
223
|
+
const getClient = () => {
|
|
224
|
+
if (cachedClient)
|
|
225
|
+
return cachedClient
|
|
226
|
+
const key = getApiKey() ?? process.env.OPENAI_API_KEY
|
|
227
|
+
if (!key)
|
|
228
|
+
throw new Error('Missing OPENAI_API_KEY (required for chatting)')
|
|
229
|
+
cachedClient = new OpenAI({ apiKey: key, baseURL: currentBaseUrl || undefined })
|
|
230
|
+
return cachedClient
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
get modelId() {
|
|
235
|
+
return currentModelId
|
|
236
|
+
},
|
|
237
|
+
setModelId(id) {
|
|
238
|
+
currentModelId = id
|
|
239
|
+
},
|
|
240
|
+
setBaseUrl(url) {
|
|
241
|
+
currentBaseUrl = url
|
|
242
|
+
cachedClient = null
|
|
243
|
+
},
|
|
244
|
+
resetClient() {
|
|
245
|
+
cachedClient = null
|
|
246
|
+
},
|
|
247
|
+
async invoke(messages) {
|
|
248
|
+
const client = getClient()
|
|
249
|
+
const resp = await client.chat.completions.create({
|
|
250
|
+
model: currentModelId,
|
|
251
|
+
messages,
|
|
252
|
+
stream: false,
|
|
253
|
+
max_completion_tokens: requestDefaults.maxOutputTokens ?? undefined,
|
|
254
|
+
temperature: requestDefaults.temperature ?? undefined,
|
|
255
|
+
top_p: requestDefaults.topP ?? undefined,
|
|
256
|
+
presence_penalty: requestDefaults.presencePenalty ?? undefined,
|
|
257
|
+
frequency_penalty: requestDefaults.frequencyPenalty ?? undefined,
|
|
258
|
+
seed: requestDefaults.seed ?? undefined,
|
|
259
|
+
})
|
|
260
|
+
const choice = Array.isArray(resp?.choices) ? resp.choices[0] : undefined
|
|
261
|
+
const content = typeof choice?.message?.content === 'string' ? choice.message.content : ''
|
|
262
|
+
return { message: { role: 'assistant', content } }
|
|
263
|
+
},
|
|
264
|
+
stream: async function* (messagesOrRequest, options) {
|
|
265
|
+
const client = getClient()
|
|
266
|
+
const isLegacy = Array.isArray(messagesOrRequest)
|
|
267
|
+
const messages = isLegacy ? messagesOrRequest : (messagesOrRequest?.messages ?? [])
|
|
268
|
+
const tools = isLegacy ? options?.tools : messagesOrRequest?.tools
|
|
269
|
+
const signal = isLegacy ? undefined : messagesOrRequest?.signal
|
|
270
|
+
const model = isLegacy ? currentModelId : (messagesOrRequest?.model?.modelId ?? currentModelId)
|
|
271
|
+
|
|
272
|
+
const resp = await client.chat.completions.create(
|
|
273
|
+
{
|
|
274
|
+
model,
|
|
275
|
+
messages,
|
|
276
|
+
tools,
|
|
277
|
+
stream: true,
|
|
278
|
+
stream_options: { include_usage: true },
|
|
279
|
+
max_completion_tokens: requestDefaults.maxOutputTokens ?? undefined,
|
|
280
|
+
temperature: requestDefaults.temperature ?? undefined,
|
|
281
|
+
top_p: requestDefaults.topP ?? undefined,
|
|
282
|
+
presence_penalty: requestDefaults.presencePenalty ?? undefined,
|
|
283
|
+
frequency_penalty: requestDefaults.frequencyPenalty ?? undefined,
|
|
284
|
+
seed: requestDefaults.seed ?? undefined,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
signal,
|
|
288
|
+
timeout: typeof requestDefaults.timeoutMs === 'number' ? requestDefaults.timeoutMs : undefined,
|
|
289
|
+
},
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
for await (const chunk of resp) {
|
|
293
|
+
const delta = chunk?.choices?.[0]?.delta?.content
|
|
294
|
+
if (typeof delta === 'string' && delta.length > 0) {
|
|
295
|
+
yield { type: 'text_delta', delta }
|
|
296
|
+
}
|
|
297
|
+
const usage = chunk?.usage
|
|
298
|
+
if (usage && typeof usage === 'object') {
|
|
299
|
+
yield {
|
|
300
|
+
type: 'usage',
|
|
301
|
+
usage: {
|
|
302
|
+
promptTokens: usage.prompt_tokens ?? 0,
|
|
303
|
+
completionTokens: usage.completion_tokens ?? 0,
|
|
304
|
+
totalTokens: usage.total_tokens ?? 0,
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
yield { type: 'done' }
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export async function createAgent({ getApiKey, modelId, system, baseUrl, requestDefaults }) {
|
|
316
|
+
const sdk = await loadSdk()
|
|
317
|
+
const { Agent } = sdk
|
|
318
|
+
|
|
319
|
+
const workspaceCwd = process.cwd()
|
|
320
|
+
const tools = createTools(sdk, workspaceCwd)
|
|
321
|
+
const model = await createModelClient(sdk, { getApiKey, modelId, baseUrl, requestDefaults })
|
|
322
|
+
|
|
323
|
+
const defaultSystemPrompt = [
|
|
324
|
+
'You are a helpful assistant.',
|
|
325
|
+
'If you need to inspect the local project (files, folders, code), you MUST use the provided tools (Read/Glob/Grep) and must not claim you did unless a tool was actually called.',
|
|
326
|
+
'When your plan depends on inspecting the project, call the tool(s) immediately instead of stopping after saying you will.',
|
|
327
|
+
'After using tools, continue and produce a user-visible result in the SAME run (don’t stop right after inspection).',
|
|
328
|
+
'For coding or file-creation tasks, you MUST implement by creating/editing files in the workspace using Write/Edit, then briefly summarize what you changed and where.',
|
|
329
|
+
'Write/Edit may require user approval in the CLI; still call them when needed (the CLI will prompt the user).',
|
|
330
|
+
'Do NOT end your response right after saying “I will create/implement …” — either ask a necessary clarifying question or start writing files with tools.',
|
|
331
|
+
].join('\n')
|
|
332
|
+
|
|
333
|
+
const agent = new Agent({
|
|
334
|
+
name: 'GoatChain CLI',
|
|
335
|
+
systemPrompt: system ?? defaultSystemPrompt,
|
|
336
|
+
model,
|
|
337
|
+
tools: tools.registry,
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
return { agent, model, tools }
|
|
341
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
function normalizeMessageContent(content) {
|
|
4
|
+
if (typeof content === 'string')
|
|
5
|
+
return content
|
|
6
|
+
try {
|
|
7
|
+
return JSON.stringify(content ?? '')
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return String(content ?? '')
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function selectSessionsById(allSessions, ids) {
|
|
15
|
+
const set = new Set(Array.isArray(ids) ? ids.map(String) : [])
|
|
16
|
+
return (Array.isArray(allSessions) ? allSessions : []).filter(s => s && set.has(String(s.sessionId)))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildSessionsExportBundle(sessions) {
|
|
20
|
+
const list = Array.isArray(sessions) ? sessions : []
|
|
21
|
+
return {
|
|
22
|
+
schemaVersion: 1,
|
|
23
|
+
exportedAt: Date.now(),
|
|
24
|
+
sessions: list.map(s => ({
|
|
25
|
+
sessionId: s.sessionId,
|
|
26
|
+
createdAt: s.createdAt,
|
|
27
|
+
updatedAt: s.updatedAt,
|
|
28
|
+
modelId: s.modelId,
|
|
29
|
+
systemPrompt: s.systemPrompt,
|
|
30
|
+
title: s.title,
|
|
31
|
+
summary: s.summary,
|
|
32
|
+
pinned: Boolean(s.pinned),
|
|
33
|
+
messages: Array.isArray(s.messages) ? s.messages : [],
|
|
34
|
+
})),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function sessionsToMarkdown(sessions, opts = {}) {
|
|
39
|
+
const exportedAt = typeof opts.exportedAt === 'string' ? opts.exportedAt : new Date().toISOString()
|
|
40
|
+
const list = Array.isArray(sessions) ? sessions : []
|
|
41
|
+
const lines = []
|
|
42
|
+
lines.push('# Sessions export')
|
|
43
|
+
lines.push('')
|
|
44
|
+
lines.push(`exportedAt: ${exportedAt}`)
|
|
45
|
+
lines.push('')
|
|
46
|
+
for (const s of list) {
|
|
47
|
+
lines.push('---')
|
|
48
|
+
lines.push(`## ${s?.title ?? 'New session'}`)
|
|
49
|
+
lines.push(`- id: ${s?.sessionId ?? ''}`)
|
|
50
|
+
if (s?.modelId)
|
|
51
|
+
lines.push(`- model: ${String(s.modelId)}`)
|
|
52
|
+
lines.push(`- pinned: ${Boolean(s?.pinned)}`)
|
|
53
|
+
if (s?.summary)
|
|
54
|
+
lines.push(`- summary: ${String(s.summary).replace(/\n/g, ' ')}`)
|
|
55
|
+
lines.push('')
|
|
56
|
+
lines.push('### Messages')
|
|
57
|
+
const msgs = Array.isArray(s?.messages) ? s.messages : []
|
|
58
|
+
for (const m of msgs) {
|
|
59
|
+
const role = m?.role ? String(m.role) : 'message'
|
|
60
|
+
const content = normalizeMessageContent(m?.content).replace(/\n/g, '\\n')
|
|
61
|
+
lines.push(`- ${role}: ${content}`)
|
|
62
|
+
}
|
|
63
|
+
lines.push('')
|
|
64
|
+
}
|
|
65
|
+
return `${lines.join('\n')}\n`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function parseSessionsImportJson(parsed) {
|
|
69
|
+
if (Array.isArray(parsed))
|
|
70
|
+
return parsed
|
|
71
|
+
if (parsed && typeof parsed === 'object') {
|
|
72
|
+
if (Array.isArray(parsed.sessions))
|
|
73
|
+
return parsed.sessions
|
|
74
|
+
if (typeof parsed.sessionId === 'string')
|
|
75
|
+
return [parsed]
|
|
76
|
+
}
|
|
77
|
+
return []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function planSessionImports(rawSessions, opts) {
|
|
81
|
+
const conflict = String(opts?.conflict ?? 'new_ids')
|
|
82
|
+
const exists = typeof opts?.exists === 'function' ? opts.exists : async () => false
|
|
83
|
+
const makeId = typeof opts?.makeId === 'function' ? opts.makeId : () => randomUUID()
|
|
84
|
+
|
|
85
|
+
const out = []
|
|
86
|
+
const list = Array.isArray(rawSessions) ? rawSessions : []
|
|
87
|
+
for (const s of list) {
|
|
88
|
+
if (!s || typeof s !== 'object')
|
|
89
|
+
continue
|
|
90
|
+
const baseId = typeof s.sessionId === 'string' && s.sessionId.trim() ? s.sessionId.trim() : makeId()
|
|
91
|
+
let id = baseId
|
|
92
|
+
// eslint-disable-next-line no-await-in-loop
|
|
93
|
+
const has = await exists(id)
|
|
94
|
+
if (has) {
|
|
95
|
+
if (conflict === 'skip')
|
|
96
|
+
continue
|
|
97
|
+
if (conflict === 'new_ids')
|
|
98
|
+
id = makeId()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
out.push({
|
|
102
|
+
sessionId: id,
|
|
103
|
+
session: {
|
|
104
|
+
sessionId: id,
|
|
105
|
+
createdAt: typeof s.createdAt === 'number' ? s.createdAt : Date.now(),
|
|
106
|
+
modelId: typeof s.modelId === 'string' ? s.modelId : undefined,
|
|
107
|
+
systemPrompt: typeof s.systemPrompt === 'string' ? s.systemPrompt : undefined,
|
|
108
|
+
title: typeof s.title === 'string' ? s.title : undefined,
|
|
109
|
+
summary: typeof s.summary === 'string' ? s.summary : undefined,
|
|
110
|
+
pinned: Boolean(s.pinned),
|
|
111
|
+
messages: Array.isArray(s.messages) ? s.messages : [],
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return out
|
|
117
|
+
}
|
|
118
|
+
|