@link-assistant/agent 0.0.9 → 0.0.12
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/EXAMPLES.md +36 -0
- package/MODELS.md +72 -24
- package/README.md +59 -2
- package/TOOLS.md +20 -0
- package/package.json +35 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +469 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +144 -119
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +39 -24
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +346 -199
- package/src/json-standard/index.ts +67 -51
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/session/prompt.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import os from
|
|
3
|
-
import fs from
|
|
4
|
-
import z from
|
|
5
|
-
import { Identifier } from
|
|
6
|
-
import { MessageV2 } from
|
|
7
|
-
import { Log } from
|
|
8
|
-
import { SessionRevert } from
|
|
9
|
-
import { Session } from
|
|
10
|
-
import { Agent } from
|
|
11
|
-
import { Provider } from
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import z from 'zod';
|
|
5
|
+
import { Identifier } from '../id/id';
|
|
6
|
+
import { MessageV2 } from './message-v2';
|
|
7
|
+
import { Log } from '../util/log';
|
|
8
|
+
import { SessionRevert } from './revert';
|
|
9
|
+
import { Session } from '.';
|
|
10
|
+
import { Agent } from '../agent/agent';
|
|
11
|
+
import { Provider } from '../provider/provider';
|
|
12
12
|
import {
|
|
13
13
|
generateText,
|
|
14
14
|
streamText,
|
|
@@ -18,68 +18,70 @@ import {
|
|
|
18
18
|
wrapLanguageModel,
|
|
19
19
|
stepCountIs,
|
|
20
20
|
jsonSchema,
|
|
21
|
-
} from
|
|
22
|
-
import { SessionCompaction } from
|
|
23
|
-
import { Instance } from
|
|
24
|
-
import { Bus } from
|
|
25
|
-
import { ProviderTransform } from
|
|
26
|
-
import { SystemPrompt } from
|
|
27
|
-
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
21
|
+
} from 'ai';
|
|
22
|
+
import { SessionCompaction } from './compaction';
|
|
23
|
+
import { Instance } from '../project/instance';
|
|
24
|
+
import { Bus } from '../bus';
|
|
25
|
+
import { ProviderTransform } from '../provider/transform';
|
|
26
|
+
import { SystemPrompt } from './system';
|
|
27
|
+
import { Flag } from '../flag/flag';
|
|
28
|
+
import { Token } from '../util/token';
|
|
29
|
+
|
|
30
|
+
import PROMPT_PLAN from '../session/prompt/plan.txt';
|
|
31
|
+
import BUILD_SWITCH from '../session/prompt/build-switch.txt';
|
|
32
|
+
import { defer } from '../util/defer';
|
|
33
|
+
import { mergeDeep, pipe } from 'remeda';
|
|
34
|
+
import { ToolRegistry } from '../tool/registry';
|
|
35
|
+
import { Wildcard } from '../util/wildcard';
|
|
36
|
+
import { MCP } from '../mcp';
|
|
37
|
+
import { ReadTool } from '../tool/read';
|
|
38
|
+
import { ListTool } from '../tool/ls';
|
|
39
|
+
import { FileTime } from '../file/time';
|
|
40
|
+
import { ulid } from 'ulid';
|
|
41
|
+
import { spawn } from 'child_process';
|
|
42
|
+
import { Command } from '../command';
|
|
43
|
+
import { $, fileURLToPath } from 'bun';
|
|
44
|
+
import { ConfigMarkdown } from '../config/markdown';
|
|
45
|
+
import { SessionSummary } from './summary';
|
|
46
|
+
import { NamedError } from '../util/error';
|
|
47
|
+
import { fn } from '../util/fn';
|
|
48
|
+
import { SessionProcessor } from './processor';
|
|
49
|
+
import { TaskTool } from '../tool/task';
|
|
50
|
+
import { SessionStatus } from './status';
|
|
49
51
|
|
|
50
52
|
export namespace SessionPrompt {
|
|
51
|
-
const log = Log.create({ service:
|
|
52
|
-
export const OUTPUT_TOKEN_MAX = 32_000
|
|
53
|
+
const log = Log.create({ service: 'session.prompt' });
|
|
54
|
+
export const OUTPUT_TOKEN_MAX = 32_000;
|
|
53
55
|
|
|
54
56
|
const state = Instance.state(
|
|
55
57
|
() => {
|
|
56
58
|
const data: Record<
|
|
57
59
|
string,
|
|
58
60
|
{
|
|
59
|
-
abort: AbortController
|
|
61
|
+
abort: AbortController;
|
|
60
62
|
callbacks: {
|
|
61
|
-
resolve(input: MessageV2.WithParts): void
|
|
62
|
-
reject(): void
|
|
63
|
-
}[]
|
|
63
|
+
resolve(input: MessageV2.WithParts): void;
|
|
64
|
+
reject(): void;
|
|
65
|
+
}[];
|
|
64
66
|
}
|
|
65
|
-
> = {}
|
|
66
|
-
return data
|
|
67
|
+
> = {};
|
|
68
|
+
return data;
|
|
67
69
|
},
|
|
68
70
|
async (current) => {
|
|
69
71
|
for (const item of Object.values(current)) {
|
|
70
|
-
item.abort.abort()
|
|
72
|
+
item.abort.abort();
|
|
71
73
|
}
|
|
72
|
-
}
|
|
73
|
-
)
|
|
74
|
+
}
|
|
75
|
+
);
|
|
74
76
|
|
|
75
77
|
export function assertNotBusy(sessionID: string) {
|
|
76
|
-
const match = state()[sessionID]
|
|
77
|
-
if (match) throw new Session.BusyError(sessionID)
|
|
78
|
+
const match = state()[sessionID];
|
|
79
|
+
if (match) throw new Session.BusyError(sessionID);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
export const PromptInput = z.object({
|
|
81
|
-
sessionID: Identifier.schema(
|
|
82
|
-
messageID: Identifier.schema(
|
|
83
|
+
sessionID: Identifier.schema('session'),
|
|
84
|
+
messageID: Identifier.schema('message').optional(),
|
|
83
85
|
model: z
|
|
84
86
|
.object({
|
|
85
87
|
providerID: z.string(),
|
|
@@ -92,7 +94,7 @@ export namespace SessionPrompt {
|
|
|
92
94
|
appendSystem: z.string().optional(),
|
|
93
95
|
tools: z.record(z.string(), z.boolean()).optional(),
|
|
94
96
|
parts: z.array(
|
|
95
|
-
z.discriminatedUnion(
|
|
97
|
+
z.discriminatedUnion('type', [
|
|
96
98
|
MessageV2.TextPart.omit({
|
|
97
99
|
messageID: true,
|
|
98
100
|
sessionID: true,
|
|
@@ -101,7 +103,7 @@ export namespace SessionPrompt {
|
|
|
101
103
|
id: true,
|
|
102
104
|
})
|
|
103
105
|
.meta({
|
|
104
|
-
ref:
|
|
106
|
+
ref: 'TextPartInput',
|
|
105
107
|
}),
|
|
106
108
|
MessageV2.FilePart.omit({
|
|
107
109
|
messageID: true,
|
|
@@ -111,7 +113,7 @@ export namespace SessionPrompt {
|
|
|
111
113
|
id: true,
|
|
112
114
|
})
|
|
113
115
|
.meta({
|
|
114
|
-
ref:
|
|
116
|
+
ref: 'FilePartInput',
|
|
115
117
|
}),
|
|
116
118
|
MessageV2.AgentPart.omit({
|
|
117
119
|
messageID: true,
|
|
@@ -121,7 +123,7 @@ export namespace SessionPrompt {
|
|
|
121
123
|
id: true,
|
|
122
124
|
})
|
|
123
125
|
.meta({
|
|
124
|
-
ref:
|
|
126
|
+
ref: 'AgentPartInput',
|
|
125
127
|
}),
|
|
126
128
|
MessageV2.SubtaskPart.omit({
|
|
127
129
|
messageID: true,
|
|
@@ -131,160 +133,176 @@ export namespace SessionPrompt {
|
|
|
131
133
|
id: true,
|
|
132
134
|
})
|
|
133
135
|
.meta({
|
|
134
|
-
ref:
|
|
136
|
+
ref: 'SubtaskPartInput',
|
|
135
137
|
}),
|
|
136
|
-
])
|
|
138
|
+
])
|
|
137
139
|
),
|
|
138
|
-
})
|
|
139
|
-
export type PromptInput = z.infer<typeof PromptInput
|
|
140
|
+
});
|
|
141
|
+
export type PromptInput = z.infer<typeof PromptInput>;
|
|
140
142
|
|
|
141
|
-
export async function resolvePromptParts(
|
|
142
|
-
|
|
143
|
+
export async function resolvePromptParts(
|
|
144
|
+
template: string
|
|
145
|
+
): Promise<PromptInput['parts']> {
|
|
146
|
+
const parts: PromptInput['parts'] = [
|
|
143
147
|
{
|
|
144
|
-
type:
|
|
148
|
+
type: 'text',
|
|
145
149
|
text: template,
|
|
146
150
|
},
|
|
147
|
-
]
|
|
148
|
-
const files = ConfigMarkdown.files(template)
|
|
151
|
+
];
|
|
152
|
+
const files = ConfigMarkdown.files(template);
|
|
149
153
|
await Promise.all(
|
|
150
154
|
files.map(async (match) => {
|
|
151
|
-
const name = match[1]
|
|
152
|
-
const filepath = name.startsWith(
|
|
155
|
+
const name = match[1];
|
|
156
|
+
const filepath = name.startsWith('~/')
|
|
153
157
|
? path.join(os.homedir(), name.slice(2))
|
|
154
|
-
: path.resolve(Instance.worktree, name)
|
|
158
|
+
: path.resolve(Instance.worktree, name);
|
|
155
159
|
|
|
156
|
-
const stats = await fs.stat(filepath).catch(() => undefined)
|
|
160
|
+
const stats = await fs.stat(filepath).catch(() => undefined);
|
|
157
161
|
if (!stats) {
|
|
158
|
-
const agent = await Agent.get(name)
|
|
162
|
+
const agent = await Agent.get(name);
|
|
159
163
|
if (agent) {
|
|
160
164
|
parts.push({
|
|
161
|
-
type:
|
|
165
|
+
type: 'agent',
|
|
162
166
|
name: agent.name,
|
|
163
|
-
})
|
|
167
|
+
});
|
|
164
168
|
}
|
|
165
|
-
return
|
|
169
|
+
return;
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
if (stats.isDirectory()) {
|
|
169
173
|
parts.push({
|
|
170
|
-
type:
|
|
174
|
+
type: 'file',
|
|
171
175
|
url: `file://${filepath}`,
|
|
172
176
|
filename: name,
|
|
173
|
-
mime:
|
|
174
|
-
})
|
|
175
|
-
return
|
|
177
|
+
mime: 'application/x-directory',
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
parts.push({
|
|
179
|
-
type:
|
|
183
|
+
type: 'file',
|
|
180
184
|
url: `file://${filepath}`,
|
|
181
185
|
filename: name,
|
|
182
|
-
mime:
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
)
|
|
186
|
-
return parts
|
|
186
|
+
mime: 'text/plain',
|
|
187
|
+
});
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
return parts;
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
export const prompt = fn(PromptInput, async (input) => {
|
|
190
|
-
const session = await Session.get(input.sessionID)
|
|
191
|
-
await SessionRevert.cleanup(session)
|
|
194
|
+
const session = await Session.get(input.sessionID);
|
|
195
|
+
await SessionRevert.cleanup(session);
|
|
192
196
|
|
|
193
|
-
const message = await createUserMessage(input)
|
|
194
|
-
await Session.touch(input.sessionID)
|
|
197
|
+
const message = await createUserMessage(input);
|
|
198
|
+
await Session.touch(input.sessionID);
|
|
195
199
|
|
|
196
200
|
if (input.noReply) {
|
|
197
|
-
return message
|
|
201
|
+
return message;
|
|
198
202
|
}
|
|
199
203
|
|
|
200
|
-
return loop(input.sessionID)
|
|
201
|
-
})
|
|
204
|
+
return loop(input.sessionID);
|
|
205
|
+
});
|
|
202
206
|
|
|
203
207
|
function start(sessionID: string) {
|
|
204
|
-
const s = state()
|
|
205
|
-
if (s[sessionID]) return
|
|
206
|
-
const controller = new AbortController()
|
|
208
|
+
const s = state();
|
|
209
|
+
if (s[sessionID]) return;
|
|
210
|
+
const controller = new AbortController();
|
|
207
211
|
s[sessionID] = {
|
|
208
212
|
abort: controller,
|
|
209
213
|
callbacks: [],
|
|
210
|
-
}
|
|
211
|
-
return controller.signal
|
|
214
|
+
};
|
|
215
|
+
return controller.signal;
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
export function cancel(sessionID: string) {
|
|
215
|
-
log.info(
|
|
216
|
-
const s = state()
|
|
217
|
-
const match = s[sessionID]
|
|
218
|
-
if (!match) return
|
|
219
|
-
match.abort.abort()
|
|
219
|
+
log.info('cancel', { sessionID });
|
|
220
|
+
const s = state();
|
|
221
|
+
const match = s[sessionID];
|
|
222
|
+
if (!match) return;
|
|
223
|
+
match.abort.abort();
|
|
220
224
|
for (const item of match.callbacks) {
|
|
221
|
-
item.reject()
|
|
225
|
+
item.reject();
|
|
222
226
|
}
|
|
223
|
-
delete s[sessionID]
|
|
224
|
-
SessionStatus.set(sessionID, { type:
|
|
225
|
-
return
|
|
227
|
+
delete s[sessionID];
|
|
228
|
+
SessionStatus.set(sessionID, { type: 'idle' });
|
|
229
|
+
return;
|
|
226
230
|
}
|
|
227
231
|
|
|
228
|
-
export const loop = fn(Identifier.schema(
|
|
229
|
-
const abort = start(sessionID)
|
|
232
|
+
export const loop = fn(Identifier.schema('session'), async (sessionID) => {
|
|
233
|
+
const abort = start(sessionID);
|
|
230
234
|
if (!abort) {
|
|
231
235
|
return new Promise<MessageV2.WithParts>((resolve, reject) => {
|
|
232
|
-
const callbacks = state()[sessionID].callbacks
|
|
233
|
-
callbacks.push({ resolve, reject })
|
|
234
|
-
})
|
|
236
|
+
const callbacks = state()[sessionID].callbacks;
|
|
237
|
+
callbacks.push({ resolve, reject });
|
|
238
|
+
});
|
|
235
239
|
}
|
|
236
240
|
|
|
237
|
-
using _ = defer(() => cancel(sessionID))
|
|
241
|
+
using _ = defer(() => cancel(sessionID));
|
|
238
242
|
|
|
239
|
-
let step = 0
|
|
243
|
+
let step = 0;
|
|
240
244
|
while (true) {
|
|
241
|
-
log.info(
|
|
242
|
-
if (abort.aborted) break
|
|
243
|
-
let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID))
|
|
244
|
-
|
|
245
|
-
let lastUser: MessageV2.User | undefined
|
|
246
|
-
let lastAssistant: MessageV2.Assistant | undefined
|
|
247
|
-
let lastFinished: MessageV2.Assistant | undefined
|
|
248
|
-
let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = []
|
|
245
|
+
log.info('loop', { step, sessionID });
|
|
246
|
+
if (abort.aborted) break;
|
|
247
|
+
let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID));
|
|
248
|
+
|
|
249
|
+
let lastUser: MessageV2.User | undefined;
|
|
250
|
+
let lastAssistant: MessageV2.Assistant | undefined;
|
|
251
|
+
let lastFinished: MessageV2.Assistant | undefined;
|
|
252
|
+
let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = [];
|
|
249
253
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
250
|
-
const msg = msgs[i]
|
|
251
|
-
if (!lastUser && msg.info.role ===
|
|
252
|
-
|
|
253
|
-
if (!
|
|
254
|
-
|
|
255
|
-
if (
|
|
256
|
-
|
|
254
|
+
const msg = msgs[i];
|
|
255
|
+
if (!lastUser && msg.info.role === 'user')
|
|
256
|
+
lastUser = msg.info as MessageV2.User;
|
|
257
|
+
if (!lastAssistant && msg.info.role === 'assistant')
|
|
258
|
+
lastAssistant = msg.info as MessageV2.Assistant;
|
|
259
|
+
if (!lastFinished && msg.info.role === 'assistant' && msg.info.finish)
|
|
260
|
+
lastFinished = msg.info as MessageV2.Assistant;
|
|
261
|
+
if (lastUser && lastFinished) break;
|
|
262
|
+
const task = msg.parts.filter(
|
|
263
|
+
(part) => part.type === 'compaction' || part.type === 'subtask'
|
|
264
|
+
);
|
|
257
265
|
if (task && !lastFinished) {
|
|
258
|
-
tasks.push(...task)
|
|
266
|
+
tasks.push(...task);
|
|
259
267
|
}
|
|
260
268
|
}
|
|
261
269
|
|
|
262
|
-
if (!lastUser)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
if (!lastUser)
|
|
271
|
+
throw new Error(
|
|
272
|
+
'No user message found in stream. This should never happen.'
|
|
273
|
+
);
|
|
274
|
+
if (
|
|
275
|
+
lastAssistant?.finish &&
|
|
276
|
+
lastAssistant.finish !== 'tool-calls' &&
|
|
277
|
+
lastUser.id < lastAssistant.id
|
|
278
|
+
) {
|
|
279
|
+
log.info('exiting loop', { sessionID });
|
|
280
|
+
break;
|
|
266
281
|
}
|
|
267
282
|
|
|
268
|
-
step
|
|
283
|
+
step++;
|
|
269
284
|
if (step === 1)
|
|
270
285
|
ensureTitle({
|
|
271
286
|
session: await Session.get(sessionID),
|
|
272
287
|
modelID: lastUser.model.modelID,
|
|
273
288
|
providerID: lastUser.model.providerID,
|
|
274
|
-
message: msgs.find((m) => m.info.role ===
|
|
289
|
+
message: msgs.find((m) => m.info.role === 'user')!,
|
|
275
290
|
history: msgs,
|
|
276
|
-
})
|
|
291
|
+
});
|
|
277
292
|
|
|
278
|
-
const model = await Provider.getModel(
|
|
279
|
-
|
|
293
|
+
const model = await Provider.getModel(
|
|
294
|
+
lastUser.model.providerID,
|
|
295
|
+
lastUser.model.modelID
|
|
296
|
+
);
|
|
297
|
+
const task = tasks.pop();
|
|
280
298
|
|
|
281
299
|
// pending subtask
|
|
282
300
|
// TODO: centralize "invoke tool" logic
|
|
283
|
-
if (task?.type ===
|
|
284
|
-
const taskTool = await TaskTool.init()
|
|
301
|
+
if (task?.type === 'subtask') {
|
|
302
|
+
const taskTool = await TaskTool.init();
|
|
285
303
|
const assistantMessage = (await Session.updateMessage({
|
|
286
|
-
id: Identifier.ascending(
|
|
287
|
-
role:
|
|
304
|
+
id: Identifier.ascending('message'),
|
|
305
|
+
role: 'assistant',
|
|
288
306
|
parentID: lastUser.id,
|
|
289
307
|
sessionID,
|
|
290
308
|
mode: task.agent,
|
|
@@ -304,16 +322,16 @@ export namespace SessionPrompt {
|
|
|
304
322
|
time: {
|
|
305
323
|
created: Date.now(),
|
|
306
324
|
},
|
|
307
|
-
})) as MessageV2.Assistant
|
|
325
|
+
})) as MessageV2.Assistant;
|
|
308
326
|
let part = (await Session.updatePart({
|
|
309
|
-
id: Identifier.ascending(
|
|
327
|
+
id: Identifier.ascending('part'),
|
|
310
328
|
messageID: assistantMessage.id,
|
|
311
329
|
sessionID: assistantMessage.sessionID,
|
|
312
|
-
type:
|
|
330
|
+
type: 'tool',
|
|
313
331
|
callID: ulid(),
|
|
314
332
|
tool: TaskTool.id,
|
|
315
333
|
state: {
|
|
316
|
-
status:
|
|
334
|
+
status: 'running',
|
|
317
335
|
input: {
|
|
318
336
|
prompt: task.prompt,
|
|
319
337
|
description: task.description,
|
|
@@ -323,7 +341,7 @@ export namespace SessionPrompt {
|
|
|
323
341
|
start: Date.now(),
|
|
324
342
|
},
|
|
325
343
|
},
|
|
326
|
-
})) as MessageV2.ToolPart
|
|
344
|
+
})) as MessageV2.ToolPart;
|
|
327
345
|
const result = await taskTool
|
|
328
346
|
.execute(
|
|
329
347
|
{
|
|
@@ -339,24 +357,24 @@ export namespace SessionPrompt {
|
|
|
339
357
|
async metadata(input) {
|
|
340
358
|
await Session.updatePart({
|
|
341
359
|
...part,
|
|
342
|
-
type:
|
|
360
|
+
type: 'tool',
|
|
343
361
|
state: {
|
|
344
362
|
...part.state,
|
|
345
363
|
...input,
|
|
346
364
|
},
|
|
347
|
-
} satisfies MessageV2.ToolPart)
|
|
365
|
+
} satisfies MessageV2.ToolPart);
|
|
348
366
|
},
|
|
349
|
-
}
|
|
367
|
+
}
|
|
350
368
|
)
|
|
351
|
-
.catch(() => {})
|
|
352
|
-
assistantMessage.finish =
|
|
353
|
-
assistantMessage.time.completed = Date.now()
|
|
354
|
-
await Session.updateMessage(assistantMessage)
|
|
355
|
-
if (result && part.state.status ===
|
|
369
|
+
.catch(() => {});
|
|
370
|
+
assistantMessage.finish = 'tool-calls';
|
|
371
|
+
assistantMessage.time.completed = Date.now();
|
|
372
|
+
await Session.updateMessage(assistantMessage);
|
|
373
|
+
if (result && part.state.status === 'running') {
|
|
356
374
|
await Session.updatePart({
|
|
357
375
|
...part,
|
|
358
376
|
state: {
|
|
359
|
-
status:
|
|
377
|
+
status: 'completed',
|
|
360
378
|
input: part.state.input,
|
|
361
379
|
title: result.title,
|
|
362
380
|
metadata: result.metadata,
|
|
@@ -367,28 +385,31 @@ export namespace SessionPrompt {
|
|
|
367
385
|
end: Date.now(),
|
|
368
386
|
},
|
|
369
387
|
},
|
|
370
|
-
} satisfies MessageV2.ToolPart)
|
|
388
|
+
} satisfies MessageV2.ToolPart);
|
|
371
389
|
}
|
|
372
390
|
if (!result) {
|
|
373
391
|
await Session.updatePart({
|
|
374
392
|
...part,
|
|
375
393
|
state: {
|
|
376
|
-
status:
|
|
377
|
-
error:
|
|
394
|
+
status: 'error',
|
|
395
|
+
error: 'Tool execution failed',
|
|
378
396
|
time: {
|
|
379
|
-
start:
|
|
397
|
+
start:
|
|
398
|
+
part.state.status === 'running'
|
|
399
|
+
? part.state.time.start
|
|
400
|
+
: Date.now(),
|
|
380
401
|
end: Date.now(),
|
|
381
402
|
},
|
|
382
403
|
metadata: part.metadata,
|
|
383
404
|
input: part.state.input,
|
|
384
405
|
},
|
|
385
|
-
} satisfies MessageV2.ToolPart)
|
|
406
|
+
} satisfies MessageV2.ToolPart);
|
|
386
407
|
}
|
|
387
|
-
continue
|
|
408
|
+
continue;
|
|
388
409
|
}
|
|
389
410
|
|
|
390
411
|
// pending compaction
|
|
391
|
-
if (task?.type ===
|
|
412
|
+
if (task?.type === 'compaction') {
|
|
392
413
|
const result = await SessionCompaction.process({
|
|
393
414
|
messages: msgs,
|
|
394
415
|
parentID: lastUser.id,
|
|
@@ -398,35 +419,38 @@ export namespace SessionPrompt {
|
|
|
398
419
|
modelID: model.modelID,
|
|
399
420
|
},
|
|
400
421
|
sessionID,
|
|
401
|
-
})
|
|
402
|
-
if (result ===
|
|
403
|
-
continue
|
|
422
|
+
});
|
|
423
|
+
if (result === 'stop') break;
|
|
424
|
+
continue;
|
|
404
425
|
}
|
|
405
426
|
|
|
406
427
|
// context overflow, needs compaction
|
|
407
428
|
if (
|
|
408
429
|
lastFinished &&
|
|
409
430
|
lastFinished.summary !== true &&
|
|
410
|
-
SessionCompaction.isOverflow({
|
|
431
|
+
SessionCompaction.isOverflow({
|
|
432
|
+
tokens: lastFinished.tokens,
|
|
433
|
+
model: model.info,
|
|
434
|
+
})
|
|
411
435
|
) {
|
|
412
436
|
await SessionCompaction.create({
|
|
413
437
|
sessionID,
|
|
414
438
|
model: lastUser.model,
|
|
415
|
-
})
|
|
416
|
-
continue
|
|
439
|
+
});
|
|
440
|
+
continue;
|
|
417
441
|
}
|
|
418
442
|
|
|
419
443
|
// normal processing
|
|
420
|
-
const agent = await Agent.get(lastUser.agent)
|
|
444
|
+
const agent = await Agent.get(lastUser.agent);
|
|
421
445
|
msgs = insertReminders({
|
|
422
446
|
messages: msgs,
|
|
423
447
|
agent,
|
|
424
|
-
})
|
|
448
|
+
});
|
|
425
449
|
const processor = SessionProcessor.create({
|
|
426
450
|
assistantMessage: (await Session.updateMessage({
|
|
427
|
-
id: Identifier.ascending(
|
|
451
|
+
id: Identifier.ascending('message'),
|
|
428
452
|
parentID: lastUser.id,
|
|
429
|
-
role:
|
|
453
|
+
role: 'assistant',
|
|
430
454
|
mode: agent.name,
|
|
431
455
|
path: {
|
|
432
456
|
cwd: Instance.directory,
|
|
@@ -450,58 +474,114 @@ export namespace SessionPrompt {
|
|
|
450
474
|
model: model.info,
|
|
451
475
|
providerID: model.providerID,
|
|
452
476
|
abort,
|
|
453
|
-
})
|
|
477
|
+
});
|
|
454
478
|
const system = await resolveSystemPrompt({
|
|
455
479
|
providerID: model.providerID,
|
|
456
480
|
modelID: model.info.id,
|
|
457
481
|
agent,
|
|
458
482
|
system: lastUser.system,
|
|
459
483
|
appendSystem: lastUser.appendSystem,
|
|
460
|
-
})
|
|
484
|
+
});
|
|
461
485
|
const tools = await resolveTools({
|
|
462
486
|
agent,
|
|
463
487
|
sessionID,
|
|
464
488
|
model: lastUser.model,
|
|
465
489
|
tools: lastUser.tools,
|
|
466
490
|
processor,
|
|
467
|
-
})
|
|
491
|
+
});
|
|
468
492
|
const params = {
|
|
469
493
|
temperature: model.info.temperature
|
|
470
|
-
? (agent.temperature ??
|
|
494
|
+
? (agent.temperature ??
|
|
495
|
+
ProviderTransform.temperature(model.providerID, model.modelID))
|
|
471
496
|
: undefined,
|
|
472
|
-
topP:
|
|
497
|
+
topP:
|
|
498
|
+
agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
|
|
473
499
|
options: {
|
|
474
|
-
...ProviderTransform.options(
|
|
500
|
+
...ProviderTransform.options(
|
|
501
|
+
model.providerID,
|
|
502
|
+
model.modelID,
|
|
503
|
+
model.npm ?? '',
|
|
504
|
+
sessionID
|
|
505
|
+
),
|
|
475
506
|
...model.info.options,
|
|
476
507
|
...agent.options,
|
|
477
508
|
},
|
|
478
|
-
}
|
|
509
|
+
};
|
|
479
510
|
|
|
480
511
|
if (step === 1) {
|
|
481
512
|
SessionSummary.summarize({
|
|
482
513
|
sessionID: sessionID,
|
|
483
514
|
messageID: lastUser.id,
|
|
484
|
-
})
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Verbose logging: output request details for debugging
|
|
519
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
520
|
+
const systemTokens = system.reduce(
|
|
521
|
+
(acc, s) => acc + Token.estimate(s),
|
|
522
|
+
0
|
|
523
|
+
);
|
|
524
|
+
const userMessages = msgs.filter((m) => m.info.role === 'user');
|
|
525
|
+
const userTokens = userMessages.reduce(
|
|
526
|
+
(acc, m) =>
|
|
527
|
+
acc +
|
|
528
|
+
m.parts.reduce(
|
|
529
|
+
(a, p) => a + Token.estimate('text' in p ? p.text || '' : ''),
|
|
530
|
+
0
|
|
531
|
+
),
|
|
532
|
+
0
|
|
533
|
+
);
|
|
534
|
+
const totalEstimatedTokens = systemTokens + userTokens;
|
|
535
|
+
|
|
536
|
+
log.info('=== VERBOSE: API Request Details ===');
|
|
537
|
+
log.info(`Model: ${model.providerID}/${model.modelID}`);
|
|
538
|
+
log.info(`Session ID: ${sessionID}`);
|
|
539
|
+
log.info(`Agent: ${agent.name}`);
|
|
540
|
+
log.info(`Temperature: ${params.temperature ?? 'default'}`);
|
|
541
|
+
log.info(`Top P: ${params.topP ?? 'default'}`);
|
|
542
|
+
log.info(
|
|
543
|
+
`Active Tools: ${Object.keys(tools)
|
|
544
|
+
.filter((x) => x !== 'invalid')
|
|
545
|
+
.join(', ')}`
|
|
546
|
+
);
|
|
547
|
+
log.info('--- System Prompt ---');
|
|
548
|
+
for (let i = 0; i < system.length; i++) {
|
|
549
|
+
const tokens = Token.estimate(system[i]);
|
|
550
|
+
log.info(`System Message ${i + 1} (${tokens} tokens estimated):`);
|
|
551
|
+
log.info(
|
|
552
|
+
system[i].slice(0, 2000) +
|
|
553
|
+
(system[i].length > 2000 ? '... [truncated]' : '')
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
log.info('--- Token Summary ---');
|
|
557
|
+
log.info(`System prompt tokens (estimated): ${systemTokens}`);
|
|
558
|
+
log.info(`User message tokens (estimated): ${userTokens}`);
|
|
559
|
+
log.info(`Total estimated tokens: ${totalEstimatedTokens}`);
|
|
560
|
+
log.info(
|
|
561
|
+
`Model context limit: ${model.info.limit.context || 'unknown'}`
|
|
562
|
+
);
|
|
563
|
+
log.info(`Model output limit: ${model.info.limit.output || 'unknown'}`);
|
|
564
|
+
log.info('=== END VERBOSE ===');
|
|
485
565
|
}
|
|
486
566
|
|
|
487
567
|
const result = await processor.process(() =>
|
|
488
568
|
streamText({
|
|
489
569
|
onError(error) {
|
|
490
|
-
log.error(
|
|
570
|
+
log.error('stream error', {
|
|
491
571
|
error,
|
|
492
|
-
})
|
|
572
|
+
});
|
|
493
573
|
},
|
|
494
574
|
async experimental_repairToolCall(input) {
|
|
495
|
-
const lower = input.toolCall.toolName.toLowerCase()
|
|
575
|
+
const lower = input.toolCall.toolName.toLowerCase();
|
|
496
576
|
if (lower !== input.toolCall.toolName && tools[lower]) {
|
|
497
|
-
log.info(
|
|
577
|
+
log.info('repairing tool call', {
|
|
498
578
|
tool: input.toolCall.toolName,
|
|
499
579
|
repaired: lower,
|
|
500
|
-
})
|
|
580
|
+
});
|
|
501
581
|
return {
|
|
502
582
|
...input.toolCall,
|
|
503
583
|
toolName: lower,
|
|
504
|
-
}
|
|
584
|
+
};
|
|
505
585
|
}
|
|
506
586
|
return {
|
|
507
587
|
...input.toolCall,
|
|
@@ -509,53 +589,60 @@ export namespace SessionPrompt {
|
|
|
509
589
|
tool: input.toolCall.toolName,
|
|
510
590
|
error: input.error.message,
|
|
511
591
|
}),
|
|
512
|
-
toolName:
|
|
513
|
-
}
|
|
592
|
+
toolName: 'invalid',
|
|
593
|
+
};
|
|
514
594
|
},
|
|
515
595
|
headers: {
|
|
516
|
-
...(model.providerID ===
|
|
596
|
+
...(model.providerID === 'opencode'
|
|
517
597
|
? {
|
|
518
|
-
|
|
519
|
-
|
|
598
|
+
'x-opencode-session': sessionID,
|
|
599
|
+
'x-opencode-request': lastUser.id,
|
|
520
600
|
}
|
|
521
601
|
: undefined),
|
|
522
602
|
...model.info.headers,
|
|
523
603
|
},
|
|
524
604
|
// set to 0, we handle loop
|
|
525
605
|
maxRetries: 0,
|
|
526
|
-
activeTools: Object.keys(tools).filter((x) => x !==
|
|
606
|
+
activeTools: Object.keys(tools).filter((x) => x !== 'invalid'),
|
|
527
607
|
maxOutputTokens: ProviderTransform.maxOutputTokens(
|
|
528
608
|
model.providerID,
|
|
529
609
|
params.options,
|
|
530
610
|
model.info.limit.output,
|
|
531
|
-
OUTPUT_TOKEN_MAX
|
|
611
|
+
OUTPUT_TOKEN_MAX
|
|
532
612
|
),
|
|
533
613
|
abortSignal: abort,
|
|
534
|
-
providerOptions: ProviderTransform.providerOptions(
|
|
614
|
+
providerOptions: ProviderTransform.providerOptions(
|
|
615
|
+
model.npm,
|
|
616
|
+
model.providerID,
|
|
617
|
+
params.options
|
|
618
|
+
),
|
|
535
619
|
stopWhen: stepCountIs(1),
|
|
536
620
|
temperature: params.temperature,
|
|
537
621
|
topP: params.topP,
|
|
538
622
|
messages: [
|
|
539
623
|
...system.map(
|
|
540
624
|
(x): ModelMessage => ({
|
|
541
|
-
role:
|
|
625
|
+
role: 'system',
|
|
542
626
|
content: x,
|
|
543
|
-
})
|
|
627
|
+
})
|
|
544
628
|
),
|
|
545
629
|
...MessageV2.toModelMessage(
|
|
546
630
|
msgs.filter((m) => {
|
|
547
|
-
if (m.info.role !==
|
|
548
|
-
return true
|
|
631
|
+
if (m.info.role !== 'assistant' || m.info.error === undefined) {
|
|
632
|
+
return true;
|
|
549
633
|
}
|
|
550
634
|
if (
|
|
551
635
|
MessageV2.AbortedError.isInstance(m.info.error) &&
|
|
552
|
-
m.parts.some(
|
|
636
|
+
m.parts.some(
|
|
637
|
+
(part) =>
|
|
638
|
+
part.type !== 'step-start' && part.type !== 'reasoning'
|
|
639
|
+
)
|
|
553
640
|
) {
|
|
554
|
-
return true
|
|
641
|
+
return true;
|
|
555
642
|
}
|
|
556
643
|
|
|
557
|
-
return false
|
|
558
|
-
})
|
|
644
|
+
return false;
|
|
645
|
+
})
|
|
559
646
|
),
|
|
560
647
|
],
|
|
561
648
|
tools: model.info.tool_call === false ? undefined : tools,
|
|
@@ -564,93 +651,116 @@ export namespace SessionPrompt {
|
|
|
564
651
|
middleware: [
|
|
565
652
|
{
|
|
566
653
|
async transformParams(args) {
|
|
567
|
-
if (args.type ===
|
|
654
|
+
if (args.type === 'stream') {
|
|
568
655
|
// @ts-expect-error
|
|
569
|
-
args.params.prompt = ProviderTransform.message(
|
|
656
|
+
args.params.prompt = ProviderTransform.message(
|
|
657
|
+
args.params.prompt,
|
|
658
|
+
model.providerID,
|
|
659
|
+
model.modelID
|
|
660
|
+
);
|
|
570
661
|
}
|
|
571
|
-
return args.params
|
|
662
|
+
return args.params;
|
|
572
663
|
},
|
|
573
664
|
},
|
|
574
665
|
],
|
|
575
666
|
}),
|
|
576
|
-
})
|
|
577
|
-
)
|
|
578
|
-
if (result ===
|
|
579
|
-
continue
|
|
667
|
+
})
|
|
668
|
+
);
|
|
669
|
+
if (result === 'stop') break;
|
|
670
|
+
continue;
|
|
580
671
|
}
|
|
581
|
-
SessionCompaction.prune({ sessionID })
|
|
672
|
+
SessionCompaction.prune({ sessionID });
|
|
582
673
|
for await (const item of MessageV2.stream(sessionID)) {
|
|
583
|
-
if (item.info.role ===
|
|
584
|
-
const queued = state()[sessionID]?.callbacks ?? []
|
|
674
|
+
if (item.info.role === 'user') continue;
|
|
675
|
+
const queued = state()[sessionID]?.callbacks ?? [];
|
|
585
676
|
for (const q of queued) {
|
|
586
|
-
q.resolve(item)
|
|
677
|
+
q.resolve(item);
|
|
587
678
|
}
|
|
588
|
-
return item
|
|
679
|
+
return item;
|
|
589
680
|
}
|
|
590
|
-
throw new Error(
|
|
591
|
-
})
|
|
681
|
+
throw new Error('Impossible');
|
|
682
|
+
});
|
|
592
683
|
|
|
593
|
-
async function resolveModel(input: {
|
|
684
|
+
async function resolveModel(input: {
|
|
685
|
+
model: PromptInput['model'];
|
|
686
|
+
agent: Agent.Info;
|
|
687
|
+
}) {
|
|
594
688
|
if (input.model) {
|
|
595
|
-
return input.model
|
|
689
|
+
return input.model;
|
|
596
690
|
}
|
|
597
691
|
if (input.agent.model) {
|
|
598
|
-
return input.agent.model
|
|
692
|
+
return input.agent.model;
|
|
599
693
|
}
|
|
600
|
-
return Provider.defaultModel()
|
|
694
|
+
return Provider.defaultModel();
|
|
601
695
|
}
|
|
602
696
|
|
|
603
697
|
async function resolveSystemPrompt(input: {
|
|
604
|
-
system?: string
|
|
605
|
-
appendSystem?: string
|
|
606
|
-
agent: Agent.Info
|
|
607
|
-
providerID: string
|
|
608
|
-
modelID: string
|
|
698
|
+
system?: string;
|
|
699
|
+
appendSystem?: string;
|
|
700
|
+
agent: Agent.Info;
|
|
701
|
+
providerID: string;
|
|
702
|
+
modelID: string;
|
|
609
703
|
}) {
|
|
610
|
-
|
|
704
|
+
// When --system-message is provided, use it exclusively without any
|
|
705
|
+
// additional context (no environment, no custom instructions, no header).
|
|
706
|
+
// This is critical for models with low token limits (e.g., qwen3-32b with 6K TPM).
|
|
707
|
+
if (input.system) {
|
|
708
|
+
return [input.system];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let system = SystemPrompt.header(input.providerID);
|
|
611
712
|
system.push(
|
|
612
713
|
...(() => {
|
|
613
|
-
|
|
614
|
-
|
|
714
|
+
const base = input.agent.prompt
|
|
715
|
+
? [input.agent.prompt]
|
|
716
|
+
: SystemPrompt.provider(input.modelID);
|
|
615
717
|
if (input.appendSystem) {
|
|
616
|
-
return [base[0] +
|
|
718
|
+
return [base[0] + '\n' + input.appendSystem];
|
|
617
719
|
}
|
|
618
|
-
return base
|
|
619
|
-
})()
|
|
620
|
-
)
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
720
|
+
return base;
|
|
721
|
+
})()
|
|
722
|
+
);
|
|
723
|
+
system.push(...(await SystemPrompt.environment()));
|
|
724
|
+
system.push(...(await SystemPrompt.custom()));
|
|
725
|
+
|
|
625
726
|
// max 2 system prompt messages for caching purposes
|
|
626
|
-
const [first, ...rest] = system
|
|
627
|
-
system = [first, rest.join(
|
|
628
|
-
return system
|
|
727
|
+
const [first, ...rest] = system;
|
|
728
|
+
system = [first, rest.join('\n')];
|
|
729
|
+
return system;
|
|
629
730
|
}
|
|
630
731
|
|
|
631
732
|
async function resolveTools(input: {
|
|
632
|
-
agent: Agent.Info
|
|
733
|
+
agent: Agent.Info;
|
|
633
734
|
model: {
|
|
634
|
-
providerID: string
|
|
635
|
-
modelID: string
|
|
636
|
-
}
|
|
637
|
-
sessionID: string
|
|
638
|
-
tools?: Record<string, boolean
|
|
639
|
-
processor: SessionProcessor.Info
|
|
735
|
+
providerID: string;
|
|
736
|
+
modelID: string;
|
|
737
|
+
};
|
|
738
|
+
sessionID: string;
|
|
739
|
+
tools?: Record<string, boolean>;
|
|
740
|
+
processor: SessionProcessor.Info;
|
|
640
741
|
}) {
|
|
641
|
-
const tools: Record<string, AITool> = {}
|
|
742
|
+
const tools: Record<string, AITool> = {};
|
|
642
743
|
const enabledTools = pipe(
|
|
643
744
|
input.agent.tools,
|
|
644
|
-
mergeDeep(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
745
|
+
mergeDeep(
|
|
746
|
+
await ToolRegistry.enabled(
|
|
747
|
+
input.model.providerID,
|
|
748
|
+
input.model.modelID,
|
|
749
|
+
input.agent
|
|
750
|
+
)
|
|
751
|
+
),
|
|
752
|
+
mergeDeep(input.tools ?? {})
|
|
753
|
+
);
|
|
754
|
+
for (const item of await ToolRegistry.tools(
|
|
755
|
+
input.model.providerID,
|
|
756
|
+
input.model.modelID
|
|
757
|
+
)) {
|
|
758
|
+
if (Wildcard.all(item.id, enabledTools) === false) continue;
|
|
649
759
|
const schema = ProviderTransform.schema(
|
|
650
760
|
input.model.providerID,
|
|
651
761
|
input.model.modelID,
|
|
652
|
-
z.toJSONSchema(item.parameters)
|
|
653
|
-
)
|
|
762
|
+
z.toJSONSchema(item.parameters)
|
|
763
|
+
);
|
|
654
764
|
tools[item.id] = tool({
|
|
655
765
|
id: item.id as any,
|
|
656
766
|
description: item.description,
|
|
@@ -664,84 +774,86 @@ export namespace SessionPrompt {
|
|
|
664
774
|
extra: input.model,
|
|
665
775
|
agent: input.agent.name,
|
|
666
776
|
metadata: async (val) => {
|
|
667
|
-
const match = input.processor.partFromToolCall(
|
|
668
|
-
|
|
777
|
+
const match = input.processor.partFromToolCall(
|
|
778
|
+
options.toolCallId
|
|
779
|
+
);
|
|
780
|
+
if (match && match.state.status === 'running') {
|
|
669
781
|
await Session.updatePart({
|
|
670
782
|
...match,
|
|
671
783
|
state: {
|
|
672
784
|
title: val.title,
|
|
673
785
|
metadata: val.metadata,
|
|
674
|
-
status:
|
|
786
|
+
status: 'running',
|
|
675
787
|
input: args,
|
|
676
788
|
time: {
|
|
677
789
|
start: Date.now(),
|
|
678
790
|
},
|
|
679
791
|
},
|
|
680
|
-
})
|
|
792
|
+
});
|
|
681
793
|
}
|
|
682
794
|
},
|
|
683
|
-
})
|
|
684
|
-
return result
|
|
795
|
+
});
|
|
796
|
+
return result;
|
|
685
797
|
},
|
|
686
798
|
toModelOutput(result) {
|
|
687
799
|
return {
|
|
688
|
-
type:
|
|
800
|
+
type: 'text',
|
|
689
801
|
value: result.output,
|
|
690
|
-
}
|
|
802
|
+
};
|
|
691
803
|
},
|
|
692
|
-
})
|
|
804
|
+
});
|
|
693
805
|
}
|
|
694
806
|
|
|
695
807
|
for (const [key, item] of Object.entries(await MCP.tools())) {
|
|
696
|
-
if (Wildcard.all(key, enabledTools) === false) continue
|
|
697
|
-
const execute = item.execute
|
|
698
|
-
if (!execute) continue
|
|
808
|
+
if (Wildcard.all(key, enabledTools) === false) continue;
|
|
809
|
+
const execute = item.execute;
|
|
810
|
+
if (!execute) continue;
|
|
699
811
|
item.execute = async (args, opts) => {
|
|
700
|
-
const result = await execute(args, opts)
|
|
812
|
+
const result = await execute(args, opts);
|
|
701
813
|
|
|
702
|
-
const textParts: string[] = []
|
|
703
|
-
const attachments: MessageV2.FilePart[] = []
|
|
814
|
+
const textParts: string[] = [];
|
|
815
|
+
const attachments: MessageV2.FilePart[] = [];
|
|
704
816
|
|
|
705
817
|
for (const item of result.content) {
|
|
706
|
-
if (item.type ===
|
|
707
|
-
textParts.push(item.text)
|
|
708
|
-
} else if (item.type ===
|
|
818
|
+
if (item.type === 'text') {
|
|
819
|
+
textParts.push(item.text);
|
|
820
|
+
} else if (item.type === 'image') {
|
|
709
821
|
attachments.push({
|
|
710
|
-
id: Identifier.ascending(
|
|
822
|
+
id: Identifier.ascending('part'),
|
|
711
823
|
sessionID: input.sessionID,
|
|
712
824
|
messageID: input.processor.message.id,
|
|
713
|
-
type:
|
|
825
|
+
type: 'file',
|
|
714
826
|
mime: item.mimeType,
|
|
715
827
|
url: `data:${item.mimeType};base64,${item.data}`,
|
|
716
|
-
})
|
|
828
|
+
});
|
|
717
829
|
}
|
|
718
830
|
// Add support for other types if needed
|
|
719
831
|
}
|
|
720
832
|
|
|
721
833
|
return {
|
|
722
|
-
title:
|
|
834
|
+
title: '',
|
|
723
835
|
metadata: result.metadata ?? {},
|
|
724
|
-
output: textParts.join(
|
|
836
|
+
output: textParts.join('\n\n'),
|
|
725
837
|
attachments,
|
|
726
838
|
content: result.content, // directly return content to preserve ordering when outputting to model
|
|
727
|
-
}
|
|
728
|
-
}
|
|
839
|
+
};
|
|
840
|
+
};
|
|
729
841
|
item.toModelOutput = (result) => {
|
|
730
842
|
return {
|
|
731
|
-
type:
|
|
843
|
+
type: 'text',
|
|
732
844
|
value: result.output,
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
tools[key] = item
|
|
845
|
+
};
|
|
846
|
+
};
|
|
847
|
+
tools[key] = item;
|
|
736
848
|
}
|
|
737
|
-
return tools
|
|
849
|
+
return tools;
|
|
738
850
|
}
|
|
739
851
|
|
|
740
852
|
async function createUserMessage(input: PromptInput) {
|
|
741
|
-
const agent = await Agent.get(input.agent ??
|
|
853
|
+
const agent = await Agent.get(input.agent ?? 'build');
|
|
742
854
|
const info: MessageV2.Info = {
|
|
743
|
-
id: input.messageID ?? Identifier.ascending(
|
|
744
|
-
role:
|
|
855
|
+
id: input.messageID ?? Identifier.ascending('message'),
|
|
856
|
+
role: 'user',
|
|
745
857
|
sessionID: input.sessionID,
|
|
746
858
|
time: {
|
|
747
859
|
created: Date.now(),
|
|
@@ -754,80 +866,80 @@ export namespace SessionPrompt {
|
|
|
754
866
|
model: input.model,
|
|
755
867
|
agent,
|
|
756
868
|
}),
|
|
757
|
-
}
|
|
869
|
+
};
|
|
758
870
|
|
|
759
871
|
const parts = await Promise.all(
|
|
760
872
|
input.parts.map(async (part): Promise<MessageV2.Part[]> => {
|
|
761
|
-
if (part.type ===
|
|
762
|
-
const url = new URL(part.url)
|
|
873
|
+
if (part.type === 'file') {
|
|
874
|
+
const url = new URL(part.url);
|
|
763
875
|
switch (url.protocol) {
|
|
764
|
-
case
|
|
765
|
-
if (part.mime ===
|
|
876
|
+
case 'data:':
|
|
877
|
+
if (part.mime === 'text/plain') {
|
|
766
878
|
return [
|
|
767
879
|
{
|
|
768
|
-
id: Identifier.ascending(
|
|
880
|
+
id: Identifier.ascending('part'),
|
|
769
881
|
messageID: info.id,
|
|
770
882
|
sessionID: input.sessionID,
|
|
771
|
-
type:
|
|
883
|
+
type: 'text',
|
|
772
884
|
synthetic: true,
|
|
773
885
|
text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`,
|
|
774
886
|
},
|
|
775
887
|
{
|
|
776
|
-
id: Identifier.ascending(
|
|
888
|
+
id: Identifier.ascending('part'),
|
|
777
889
|
messageID: info.id,
|
|
778
890
|
sessionID: input.sessionID,
|
|
779
|
-
type:
|
|
891
|
+
type: 'text',
|
|
780
892
|
synthetic: true,
|
|
781
|
-
text: Buffer.from(part.url,
|
|
893
|
+
text: Buffer.from(part.url, 'base64url').toString(),
|
|
782
894
|
},
|
|
783
895
|
{
|
|
784
896
|
...part,
|
|
785
|
-
id: part.id ?? Identifier.ascending(
|
|
897
|
+
id: part.id ?? Identifier.ascending('part'),
|
|
786
898
|
messageID: info.id,
|
|
787
899
|
sessionID: input.sessionID,
|
|
788
900
|
},
|
|
789
|
-
]
|
|
901
|
+
];
|
|
790
902
|
}
|
|
791
|
-
break
|
|
792
|
-
case
|
|
793
|
-
log.info(
|
|
903
|
+
break;
|
|
904
|
+
case 'file:':
|
|
905
|
+
log.info('file', { mime: part.mime });
|
|
794
906
|
// have to normalize, symbol search returns absolute paths
|
|
795
907
|
// Decode the pathname since URL constructor doesn't automatically decode it
|
|
796
|
-
const filepath = fileURLToPath(part.url)
|
|
797
|
-
const stat = await Bun.file(filepath).stat()
|
|
908
|
+
const filepath = fileURLToPath(part.url);
|
|
909
|
+
const stat = await Bun.file(filepath).stat();
|
|
798
910
|
|
|
799
911
|
if (stat.isDirectory()) {
|
|
800
|
-
part.mime =
|
|
912
|
+
part.mime = 'application/x-directory';
|
|
801
913
|
}
|
|
802
914
|
|
|
803
|
-
if (part.mime ===
|
|
804
|
-
let offset: number | undefined = undefined
|
|
805
|
-
let limit: number | undefined = undefined
|
|
915
|
+
if (part.mime === 'text/plain') {
|
|
916
|
+
let offset: number | undefined = undefined;
|
|
917
|
+
let limit: number | undefined = undefined;
|
|
806
918
|
const range = {
|
|
807
|
-
start: url.searchParams.get(
|
|
808
|
-
end: url.searchParams.get(
|
|
809
|
-
}
|
|
919
|
+
start: url.searchParams.get('start'),
|
|
920
|
+
end: url.searchParams.get('end'),
|
|
921
|
+
};
|
|
810
922
|
if (range.start != null) {
|
|
811
|
-
const filePathURI = part.url.split(
|
|
812
|
-
let start = parseInt(range.start)
|
|
813
|
-
let end = range.end ? parseInt(range.end) : undefined
|
|
814
|
-
offset = Math.max(start - 1, 0)
|
|
923
|
+
const filePathURI = part.url.split('?')[0];
|
|
924
|
+
let start = parseInt(range.start);
|
|
925
|
+
let end = range.end ? parseInt(range.end) : undefined;
|
|
926
|
+
offset = Math.max(start - 1, 0);
|
|
815
927
|
if (end) {
|
|
816
|
-
limit = end - offset
|
|
928
|
+
limit = end - offset;
|
|
817
929
|
}
|
|
818
930
|
}
|
|
819
|
-
const args = { filePath: filepath, offset, limit }
|
|
931
|
+
const args = { filePath: filepath, offset, limit };
|
|
820
932
|
|
|
821
933
|
const pieces: MessageV2.Part[] = [
|
|
822
934
|
{
|
|
823
|
-
id: Identifier.ascending(
|
|
935
|
+
id: Identifier.ascending('part'),
|
|
824
936
|
messageID: info.id,
|
|
825
937
|
sessionID: input.sessionID,
|
|
826
|
-
type:
|
|
938
|
+
type: 'text',
|
|
827
939
|
synthetic: true,
|
|
828
940
|
text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
|
|
829
941
|
},
|
|
830
|
-
]
|
|
942
|
+
];
|
|
831
943
|
|
|
832
944
|
await ReadTool.init()
|
|
833
945
|
.then(async (t) => {
|
|
@@ -838,48 +950,49 @@ export namespace SessionPrompt {
|
|
|
838
950
|
messageID: info.id,
|
|
839
951
|
extra: { bypassCwdCheck: true, ...info.model },
|
|
840
952
|
metadata: async () => {},
|
|
841
|
-
})
|
|
953
|
+
});
|
|
842
954
|
pieces.push(
|
|
843
955
|
{
|
|
844
|
-
id: Identifier.ascending(
|
|
956
|
+
id: Identifier.ascending('part'),
|
|
845
957
|
messageID: info.id,
|
|
846
958
|
sessionID: input.sessionID,
|
|
847
|
-
type:
|
|
959
|
+
type: 'text',
|
|
848
960
|
synthetic: true,
|
|
849
961
|
text: result.output,
|
|
850
962
|
},
|
|
851
963
|
{
|
|
852
964
|
...part,
|
|
853
|
-
id: part.id ?? Identifier.ascending(
|
|
965
|
+
id: part.id ?? Identifier.ascending('part'),
|
|
854
966
|
messageID: info.id,
|
|
855
967
|
sessionID: input.sessionID,
|
|
856
|
-
}
|
|
857
|
-
)
|
|
968
|
+
}
|
|
969
|
+
);
|
|
858
970
|
})
|
|
859
971
|
.catch((error) => {
|
|
860
|
-
log.error(
|
|
861
|
-
const message =
|
|
972
|
+
log.error('failed to read file', { error });
|
|
973
|
+
const message =
|
|
974
|
+
error instanceof Error ? error.message : error.toString();
|
|
862
975
|
Bus.publish(Session.Event.Error, {
|
|
863
976
|
sessionID: input.sessionID,
|
|
864
977
|
error: new NamedError.Unknown({
|
|
865
978
|
message,
|
|
866
979
|
}).toObject(),
|
|
867
|
-
})
|
|
980
|
+
});
|
|
868
981
|
pieces.push({
|
|
869
|
-
id: Identifier.ascending(
|
|
982
|
+
id: Identifier.ascending('part'),
|
|
870
983
|
messageID: info.id,
|
|
871
984
|
sessionID: input.sessionID,
|
|
872
|
-
type:
|
|
985
|
+
type: 'text',
|
|
873
986
|
synthetic: true,
|
|
874
987
|
text: `Read tool failed to read ${filepath} with the following error: ${message}`,
|
|
875
|
-
})
|
|
876
|
-
})
|
|
988
|
+
});
|
|
989
|
+
});
|
|
877
990
|
|
|
878
|
-
return pieces
|
|
991
|
+
return pieces;
|
|
879
992
|
}
|
|
880
993
|
|
|
881
|
-
if (part.mime ===
|
|
882
|
-
const args = { path: filepath }
|
|
994
|
+
if (part.mime === 'application/x-directory') {
|
|
995
|
+
const args = { path: filepath };
|
|
883
996
|
const result = await ListTool.init().then((t) =>
|
|
884
997
|
t.execute(args, {
|
|
885
998
|
sessionID: input.sessionID,
|
|
@@ -888,168 +1001,177 @@ export namespace SessionPrompt {
|
|
|
888
1001
|
messageID: info.id,
|
|
889
1002
|
extra: { bypassCwdCheck: true },
|
|
890
1003
|
metadata: async () => {},
|
|
891
|
-
})
|
|
892
|
-
)
|
|
1004
|
+
})
|
|
1005
|
+
);
|
|
893
1006
|
return [
|
|
894
1007
|
{
|
|
895
|
-
id: Identifier.ascending(
|
|
1008
|
+
id: Identifier.ascending('part'),
|
|
896
1009
|
messageID: info.id,
|
|
897
1010
|
sessionID: input.sessionID,
|
|
898
|
-
type:
|
|
1011
|
+
type: 'text',
|
|
899
1012
|
synthetic: true,
|
|
900
1013
|
text: `Called the list tool with the following input: ${JSON.stringify(args)}`,
|
|
901
1014
|
},
|
|
902
1015
|
{
|
|
903
|
-
id: Identifier.ascending(
|
|
1016
|
+
id: Identifier.ascending('part'),
|
|
904
1017
|
messageID: info.id,
|
|
905
1018
|
sessionID: input.sessionID,
|
|
906
|
-
type:
|
|
1019
|
+
type: 'text',
|
|
907
1020
|
synthetic: true,
|
|
908
1021
|
text: result.output,
|
|
909
1022
|
},
|
|
910
1023
|
{
|
|
911
1024
|
...part,
|
|
912
|
-
id: part.id ?? Identifier.ascending(
|
|
1025
|
+
id: part.id ?? Identifier.ascending('part'),
|
|
913
1026
|
messageID: info.id,
|
|
914
1027
|
sessionID: input.sessionID,
|
|
915
1028
|
},
|
|
916
|
-
]
|
|
1029
|
+
];
|
|
917
1030
|
}
|
|
918
1031
|
|
|
919
|
-
const file = Bun.file(filepath)
|
|
920
|
-
FileTime.read(input.sessionID, filepath)
|
|
1032
|
+
const file = Bun.file(filepath);
|
|
1033
|
+
FileTime.read(input.sessionID, filepath);
|
|
921
1034
|
return [
|
|
922
1035
|
{
|
|
923
|
-
id: Identifier.ascending(
|
|
1036
|
+
id: Identifier.ascending('part'),
|
|
924
1037
|
messageID: info.id,
|
|
925
1038
|
sessionID: input.sessionID,
|
|
926
|
-
type:
|
|
1039
|
+
type: 'text',
|
|
927
1040
|
text: `Called the Read tool with the following input: {\"filePath\":\"${filepath}\"}`,
|
|
928
1041
|
synthetic: true,
|
|
929
1042
|
},
|
|
930
1043
|
{
|
|
931
|
-
id: part.id ?? Identifier.ascending(
|
|
1044
|
+
id: part.id ?? Identifier.ascending('part'),
|
|
932
1045
|
messageID: info.id,
|
|
933
1046
|
sessionID: input.sessionID,
|
|
934
|
-
type:
|
|
935
|
-
url:
|
|
1047
|
+
type: 'file',
|
|
1048
|
+
url:
|
|
1049
|
+
`data:${part.mime};base64,` +
|
|
1050
|
+
Buffer.from(await file.bytes()).toString('base64'),
|
|
936
1051
|
mime: part.mime,
|
|
937
1052
|
filename: part.filename!,
|
|
938
1053
|
source: part.source,
|
|
939
1054
|
},
|
|
940
|
-
]
|
|
1055
|
+
];
|
|
941
1056
|
}
|
|
942
1057
|
}
|
|
943
1058
|
|
|
944
|
-
if (part.type ===
|
|
1059
|
+
if (part.type === 'agent') {
|
|
945
1060
|
return [
|
|
946
1061
|
{
|
|
947
|
-
id: Identifier.ascending(
|
|
1062
|
+
id: Identifier.ascending('part'),
|
|
948
1063
|
...part,
|
|
949
1064
|
messageID: info.id,
|
|
950
1065
|
sessionID: input.sessionID,
|
|
951
1066
|
},
|
|
952
1067
|
{
|
|
953
|
-
id: Identifier.ascending(
|
|
1068
|
+
id: Identifier.ascending('part'),
|
|
954
1069
|
messageID: info.id,
|
|
955
1070
|
sessionID: input.sessionID,
|
|
956
|
-
type:
|
|
1071
|
+
type: 'text',
|
|
957
1072
|
synthetic: true,
|
|
958
1073
|
text:
|
|
959
|
-
|
|
1074
|
+
'Use the above message and context to generate a prompt and call the task tool with subagent: ' +
|
|
960
1075
|
part.name,
|
|
961
1076
|
},
|
|
962
|
-
]
|
|
1077
|
+
];
|
|
963
1078
|
}
|
|
964
1079
|
|
|
965
1080
|
return [
|
|
966
1081
|
{
|
|
967
|
-
id: Identifier.ascending(
|
|
1082
|
+
id: Identifier.ascending('part'),
|
|
968
1083
|
...part,
|
|
969
1084
|
messageID: info.id,
|
|
970
1085
|
sessionID: input.sessionID,
|
|
971
1086
|
},
|
|
972
|
-
]
|
|
973
|
-
})
|
|
974
|
-
).then((x) => x.flat())
|
|
1087
|
+
];
|
|
1088
|
+
})
|
|
1089
|
+
).then((x) => x.flat());
|
|
975
1090
|
|
|
976
|
-
await Session.updateMessage(info)
|
|
1091
|
+
await Session.updateMessage(info);
|
|
977
1092
|
for (const part of parts) {
|
|
978
|
-
await Session.updatePart(part)
|
|
1093
|
+
await Session.updatePart(part);
|
|
979
1094
|
}
|
|
980
1095
|
|
|
981
1096
|
return {
|
|
982
1097
|
info,
|
|
983
1098
|
parts,
|
|
984
|
-
}
|
|
1099
|
+
};
|
|
985
1100
|
}
|
|
986
1101
|
|
|
987
|
-
function insertReminders(input: {
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1102
|
+
function insertReminders(input: {
|
|
1103
|
+
messages: MessageV2.WithParts[];
|
|
1104
|
+
agent: Agent.Info;
|
|
1105
|
+
}) {
|
|
1106
|
+
const userMessage = input.messages.findLast(
|
|
1107
|
+
(msg) => msg.info.role === 'user'
|
|
1108
|
+
);
|
|
1109
|
+
if (!userMessage) return input.messages;
|
|
1110
|
+
if (input.agent.name === 'plan') {
|
|
991
1111
|
userMessage.parts.push({
|
|
992
|
-
id: Identifier.ascending(
|
|
1112
|
+
id: Identifier.ascending('part'),
|
|
993
1113
|
messageID: userMessage.info.id,
|
|
994
1114
|
sessionID: userMessage.info.sessionID,
|
|
995
|
-
type:
|
|
1115
|
+
type: 'text',
|
|
996
1116
|
text: PROMPT_PLAN,
|
|
997
1117
|
synthetic: true,
|
|
998
|
-
})
|
|
1118
|
+
});
|
|
999
1119
|
}
|
|
1000
|
-
const wasPlan = input.messages.some(
|
|
1001
|
-
|
|
1120
|
+
const wasPlan = input.messages.some(
|
|
1121
|
+
(msg) => msg.info.role === 'assistant' && msg.info.mode === 'plan'
|
|
1122
|
+
);
|
|
1123
|
+
if (wasPlan && input.agent.name === 'build') {
|
|
1002
1124
|
userMessage.parts.push({
|
|
1003
|
-
id: Identifier.ascending(
|
|
1125
|
+
id: Identifier.ascending('part'),
|
|
1004
1126
|
messageID: userMessage.info.id,
|
|
1005
1127
|
sessionID: userMessage.info.sessionID,
|
|
1006
|
-
type:
|
|
1128
|
+
type: 'text',
|
|
1007
1129
|
text: BUILD_SWITCH,
|
|
1008
1130
|
synthetic: true,
|
|
1009
|
-
})
|
|
1131
|
+
});
|
|
1010
1132
|
}
|
|
1011
|
-
return input.messages
|
|
1133
|
+
return input.messages;
|
|
1012
1134
|
}
|
|
1013
1135
|
|
|
1014
1136
|
export const ShellInput = z.object({
|
|
1015
|
-
sessionID: Identifier.schema(
|
|
1137
|
+
sessionID: Identifier.schema('session'),
|
|
1016
1138
|
agent: z.string(),
|
|
1017
1139
|
command: z.string(),
|
|
1018
|
-
})
|
|
1019
|
-
export type ShellInput = z.infer<typeof ShellInput
|
|
1140
|
+
});
|
|
1141
|
+
export type ShellInput = z.infer<typeof ShellInput>;
|
|
1020
1142
|
export async function shell(input: ShellInput) {
|
|
1021
|
-
const session = await Session.get(input.sessionID)
|
|
1143
|
+
const session = await Session.get(input.sessionID);
|
|
1022
1144
|
if (session.revert) {
|
|
1023
|
-
SessionRevert.cleanup(session)
|
|
1145
|
+
SessionRevert.cleanup(session);
|
|
1024
1146
|
}
|
|
1025
|
-
const agent = await Agent.get(input.agent)
|
|
1026
|
-
const model = await resolveModel({ agent, model: undefined })
|
|
1147
|
+
const agent = await Agent.get(input.agent);
|
|
1148
|
+
const model = await resolveModel({ agent, model: undefined });
|
|
1027
1149
|
const userMsg: MessageV2.User = {
|
|
1028
|
-
id: Identifier.ascending(
|
|
1150
|
+
id: Identifier.ascending('message'),
|
|
1029
1151
|
sessionID: input.sessionID,
|
|
1030
1152
|
time: {
|
|
1031
1153
|
created: Date.now(),
|
|
1032
1154
|
},
|
|
1033
|
-
role:
|
|
1155
|
+
role: 'user',
|
|
1034
1156
|
agent: input.agent,
|
|
1035
1157
|
model: {
|
|
1036
1158
|
providerID: model.providerID,
|
|
1037
1159
|
modelID: model.modelID,
|
|
1038
1160
|
},
|
|
1039
|
-
}
|
|
1040
|
-
await Session.updateMessage(userMsg)
|
|
1161
|
+
};
|
|
1162
|
+
await Session.updateMessage(userMsg);
|
|
1041
1163
|
const userPart: MessageV2.Part = {
|
|
1042
|
-
type:
|
|
1043
|
-
id: Identifier.ascending(
|
|
1164
|
+
type: 'text',
|
|
1165
|
+
id: Identifier.ascending('part'),
|
|
1044
1166
|
messageID: userMsg.id,
|
|
1045
1167
|
sessionID: input.sessionID,
|
|
1046
|
-
text:
|
|
1168
|
+
text: 'The following tool was executed by the user',
|
|
1047
1169
|
synthetic: true,
|
|
1048
|
-
}
|
|
1049
|
-
await Session.updatePart(userPart)
|
|
1170
|
+
};
|
|
1171
|
+
await Session.updatePart(userPart);
|
|
1050
1172
|
|
|
1051
1173
|
const msg: MessageV2.Assistant = {
|
|
1052
|
-
id: Identifier.ascending(
|
|
1174
|
+
id: Identifier.ascending('message'),
|
|
1053
1175
|
sessionID: input.sessionID,
|
|
1054
1176
|
parentID: userMsg.id,
|
|
1055
1177
|
mode: input.agent,
|
|
@@ -1061,7 +1183,7 @@ export namespace SessionPrompt {
|
|
|
1061
1183
|
time: {
|
|
1062
1184
|
created: Date.now(),
|
|
1063
1185
|
},
|
|
1064
|
-
role:
|
|
1186
|
+
role: 'assistant',
|
|
1065
1187
|
tokens: {
|
|
1066
1188
|
input: 0,
|
|
1067
1189
|
output: 0,
|
|
@@ -1070,17 +1192,17 @@ export namespace SessionPrompt {
|
|
|
1070
1192
|
},
|
|
1071
1193
|
modelID: model.modelID,
|
|
1072
1194
|
providerID: model.providerID,
|
|
1073
|
-
}
|
|
1074
|
-
await Session.updateMessage(msg)
|
|
1195
|
+
};
|
|
1196
|
+
await Session.updateMessage(msg);
|
|
1075
1197
|
const part: MessageV2.Part = {
|
|
1076
|
-
type:
|
|
1077
|
-
id: Identifier.ascending(
|
|
1198
|
+
type: 'tool',
|
|
1199
|
+
id: Identifier.ascending('part'),
|
|
1078
1200
|
messageID: msg.id,
|
|
1079
1201
|
sessionID: input.sessionID,
|
|
1080
|
-
tool:
|
|
1202
|
+
tool: 'bash',
|
|
1081
1203
|
callID: ulid(),
|
|
1082
1204
|
state: {
|
|
1083
|
-
status:
|
|
1205
|
+
status: 'running',
|
|
1084
1206
|
time: {
|
|
1085
1207
|
start: Date.now(),
|
|
1086
1208
|
},
|
|
@@ -1088,22 +1210,22 @@ export namespace SessionPrompt {
|
|
|
1088
1210
|
command: input.command,
|
|
1089
1211
|
},
|
|
1090
1212
|
},
|
|
1091
|
-
}
|
|
1092
|
-
await Session.updatePart(part)
|
|
1093
|
-
const shell = process.env[
|
|
1094
|
-
const shellName = path.basename(shell)
|
|
1213
|
+
};
|
|
1214
|
+
await Session.updatePart(part);
|
|
1215
|
+
const shell = process.env['SHELL'] ?? 'bash';
|
|
1216
|
+
const shellName = path.basename(shell);
|
|
1095
1217
|
|
|
1096
1218
|
const invocations: Record<string, { args: string[] }> = {
|
|
1097
1219
|
nu: {
|
|
1098
|
-
args: [
|
|
1220
|
+
args: ['-c', input.command],
|
|
1099
1221
|
},
|
|
1100
1222
|
fish: {
|
|
1101
|
-
args: [
|
|
1223
|
+
args: ['-c', input.command],
|
|
1102
1224
|
},
|
|
1103
1225
|
zsh: {
|
|
1104
1226
|
args: [
|
|
1105
|
-
|
|
1106
|
-
|
|
1227
|
+
'-c',
|
|
1228
|
+
'-l',
|
|
1107
1229
|
`
|
|
1108
1230
|
[[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true
|
|
1109
1231
|
[[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true
|
|
@@ -1113,8 +1235,8 @@ export namespace SessionPrompt {
|
|
|
1113
1235
|
},
|
|
1114
1236
|
bash: {
|
|
1115
1237
|
args: [
|
|
1116
|
-
|
|
1117
|
-
|
|
1238
|
+
'-c',
|
|
1239
|
+
'-l',
|
|
1118
1240
|
`
|
|
1119
1241
|
[[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
|
|
1120
1242
|
${input.command}
|
|
@@ -1122,88 +1244,88 @@ export namespace SessionPrompt {
|
|
|
1122
1244
|
],
|
|
1123
1245
|
},
|
|
1124
1246
|
// Fallback: any shell that doesn't match those above
|
|
1125
|
-
|
|
1126
|
-
args: [
|
|
1247
|
+
'': {
|
|
1248
|
+
args: ['-c', '-l', `${input.command}`],
|
|
1127
1249
|
},
|
|
1128
|
-
}
|
|
1250
|
+
};
|
|
1129
1251
|
|
|
1130
|
-
const matchingInvocation = invocations[shellName] ?? invocations[
|
|
1131
|
-
const args = matchingInvocation?.args
|
|
1252
|
+
const matchingInvocation = invocations[shellName] ?? invocations[''];
|
|
1253
|
+
const args = matchingInvocation?.args;
|
|
1132
1254
|
|
|
1133
1255
|
const proc = spawn(shell, args, {
|
|
1134
1256
|
cwd: Instance.directory,
|
|
1135
1257
|
detached: true,
|
|
1136
|
-
stdio: [
|
|
1258
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1137
1259
|
env: {
|
|
1138
1260
|
...process.env,
|
|
1139
|
-
TERM:
|
|
1261
|
+
TERM: 'dumb',
|
|
1140
1262
|
},
|
|
1141
|
-
})
|
|
1263
|
+
});
|
|
1142
1264
|
|
|
1143
|
-
let output =
|
|
1265
|
+
let output = '';
|
|
1144
1266
|
|
|
1145
|
-
proc.stdout?.on(
|
|
1146
|
-
output += chunk.toString()
|
|
1147
|
-
if (part.state.status ===
|
|
1267
|
+
proc.stdout?.on('data', (chunk) => {
|
|
1268
|
+
output += chunk.toString();
|
|
1269
|
+
if (part.state.status === 'running') {
|
|
1148
1270
|
part.state.metadata = {
|
|
1149
1271
|
output: output,
|
|
1150
|
-
description:
|
|
1151
|
-
}
|
|
1152
|
-
Session.updatePart(part)
|
|
1272
|
+
description: '',
|
|
1273
|
+
};
|
|
1274
|
+
Session.updatePart(part);
|
|
1153
1275
|
}
|
|
1154
|
-
})
|
|
1276
|
+
});
|
|
1155
1277
|
|
|
1156
|
-
proc.stderr?.on(
|
|
1157
|
-
output += chunk.toString()
|
|
1158
|
-
if (part.state.status ===
|
|
1278
|
+
proc.stderr?.on('data', (chunk) => {
|
|
1279
|
+
output += chunk.toString();
|
|
1280
|
+
if (part.state.status === 'running') {
|
|
1159
1281
|
part.state.metadata = {
|
|
1160
1282
|
output: output,
|
|
1161
|
-
description:
|
|
1162
|
-
}
|
|
1163
|
-
Session.updatePart(part)
|
|
1283
|
+
description: '',
|
|
1284
|
+
};
|
|
1285
|
+
Session.updatePart(part);
|
|
1164
1286
|
}
|
|
1165
|
-
})
|
|
1287
|
+
});
|
|
1166
1288
|
|
|
1167
1289
|
await new Promise<void>((resolve) => {
|
|
1168
|
-
proc.on(
|
|
1169
|
-
resolve()
|
|
1170
|
-
})
|
|
1171
|
-
})
|
|
1172
|
-
msg.time.completed = Date.now()
|
|
1173
|
-
await Session.updateMessage(msg)
|
|
1174
|
-
if (part.state.status ===
|
|
1290
|
+
proc.on('close', () => {
|
|
1291
|
+
resolve();
|
|
1292
|
+
});
|
|
1293
|
+
});
|
|
1294
|
+
msg.time.completed = Date.now();
|
|
1295
|
+
await Session.updateMessage(msg);
|
|
1296
|
+
if (part.state.status === 'running') {
|
|
1175
1297
|
part.state = {
|
|
1176
|
-
status:
|
|
1298
|
+
status: 'completed',
|
|
1177
1299
|
time: {
|
|
1178
1300
|
...part.state.time,
|
|
1179
1301
|
end: Date.now(),
|
|
1180
1302
|
},
|
|
1181
1303
|
input: part.state.input,
|
|
1182
|
-
title:
|
|
1304
|
+
title: '',
|
|
1183
1305
|
metadata: {
|
|
1184
1306
|
output,
|
|
1185
|
-
description:
|
|
1307
|
+
description: '',
|
|
1186
1308
|
},
|
|
1187
1309
|
output,
|
|
1188
|
-
}
|
|
1189
|
-
await Session.updatePart(part)
|
|
1310
|
+
};
|
|
1311
|
+
await Session.updatePart(part);
|
|
1190
1312
|
}
|
|
1191
|
-
return { info: msg, parts: [part] }
|
|
1313
|
+
return { info: msg, parts: [part] };
|
|
1192
1314
|
}
|
|
1193
1315
|
|
|
1194
1316
|
export const CommandInput = z.object({
|
|
1195
|
-
messageID: Identifier.schema(
|
|
1196
|
-
sessionID: Identifier.schema(
|
|
1317
|
+
messageID: Identifier.schema('message').optional(),
|
|
1318
|
+
sessionID: Identifier.schema('session'),
|
|
1197
1319
|
agent: z.string().optional(),
|
|
1198
1320
|
model: z.string().optional(),
|
|
1199
1321
|
arguments: z.string(),
|
|
1200
1322
|
command: z.string(),
|
|
1201
|
-
})
|
|
1202
|
-
export type CommandInput = z.infer<typeof CommandInput
|
|
1203
|
-
const bashRegex = /!`([^`]+)`/g
|
|
1204
|
-
const argsRegex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g
|
|
1205
|
-
const placeholderRegex = /\$(\d+)/g
|
|
1206
|
-
const quoteTrimRegex = /^["']|["']$/g
|
|
1323
|
+
});
|
|
1324
|
+
export type CommandInput = z.infer<typeof CommandInput>;
|
|
1325
|
+
const bashRegex = /!`([^`]+)`/g;
|
|
1326
|
+
const argsRegex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g;
|
|
1327
|
+
const placeholderRegex = /\$(\d+)/g;
|
|
1328
|
+
const quoteTrimRegex = /^["']|["']$/g;
|
|
1207
1329
|
/**
|
|
1208
1330
|
* Regular expression to match @ file references in text
|
|
1209
1331
|
* Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks
|
|
@@ -1211,75 +1333,81 @@ export namespace SessionPrompt {
|
|
|
1211
1333
|
*/
|
|
1212
1334
|
|
|
1213
1335
|
export async function command(input: CommandInput) {
|
|
1214
|
-
log.info(
|
|
1215
|
-
const command = await Command.get(input.command)
|
|
1216
|
-
const agentName = command.agent ?? input.agent ??
|
|
1336
|
+
log.info('command', input);
|
|
1337
|
+
const command = await Command.get(input.command);
|
|
1338
|
+
const agentName = command.agent ?? input.agent ?? 'build';
|
|
1217
1339
|
|
|
1218
|
-
const raw = input.arguments.match(argsRegex) ?? []
|
|
1219
|
-
const args = raw.map((arg) => arg.replace(quoteTrimRegex,
|
|
1340
|
+
const raw = input.arguments.match(argsRegex) ?? [];
|
|
1341
|
+
const args = raw.map((arg) => arg.replace(quoteTrimRegex, ''));
|
|
1220
1342
|
|
|
1221
|
-
const placeholders = command.template.match(placeholderRegex) ?? []
|
|
1222
|
-
let last = 0
|
|
1343
|
+
const placeholders = command.template.match(placeholderRegex) ?? [];
|
|
1344
|
+
let last = 0;
|
|
1223
1345
|
for (const item of placeholders) {
|
|
1224
|
-
const value = Number(item.slice(1))
|
|
1225
|
-
if (value > last) last = value
|
|
1346
|
+
const value = Number(item.slice(1));
|
|
1347
|
+
if (value > last) last = value;
|
|
1226
1348
|
}
|
|
1227
1349
|
|
|
1228
1350
|
// Let the final placeholder swallow any extra arguments so prompts read naturally
|
|
1229
|
-
const withArgs = command.template.replaceAll(
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1351
|
+
const withArgs = command.template.replaceAll(
|
|
1352
|
+
placeholderRegex,
|
|
1353
|
+
(_, index) => {
|
|
1354
|
+
const position = Number(index);
|
|
1355
|
+
const argIndex = position - 1;
|
|
1356
|
+
if (argIndex >= args.length) return '';
|
|
1357
|
+
if (position === last) return args.slice(argIndex).join(' ');
|
|
1358
|
+
return args[argIndex];
|
|
1359
|
+
}
|
|
1360
|
+
);
|
|
1361
|
+
let template = withArgs.replaceAll('$ARGUMENTS', input.arguments);
|
|
1237
1362
|
|
|
1238
|
-
const shell = ConfigMarkdown.shell(template)
|
|
1363
|
+
const shell = ConfigMarkdown.shell(template);
|
|
1239
1364
|
if (shell.length > 0) {
|
|
1240
1365
|
const results = await Promise.all(
|
|
1241
1366
|
shell.map(async ([, cmd]) => {
|
|
1242
1367
|
try {
|
|
1243
|
-
return await $`${{ raw: cmd }}`.nothrow().text()
|
|
1368
|
+
return await $`${{ raw: cmd }}`.nothrow().text();
|
|
1244
1369
|
} catch (error) {
|
|
1245
|
-
return `Error executing command: ${error instanceof Error ? error.message : String(error)}
|
|
1370
|
+
return `Error executing command: ${error instanceof Error ? error.message : String(error)}`;
|
|
1246
1371
|
}
|
|
1247
|
-
})
|
|
1248
|
-
)
|
|
1249
|
-
let index = 0
|
|
1250
|
-
template = template.replace(bashRegex, () => results[index++])
|
|
1372
|
+
})
|
|
1373
|
+
);
|
|
1374
|
+
let index = 0;
|
|
1375
|
+
template = template.replace(bashRegex, () => results[index++]);
|
|
1251
1376
|
}
|
|
1252
|
-
template = template.trim()
|
|
1377
|
+
template = template.trim();
|
|
1253
1378
|
|
|
1254
1379
|
const model = await (async () => {
|
|
1255
1380
|
if (command.model) {
|
|
1256
|
-
return Provider.parseModel(command.model)
|
|
1381
|
+
return Provider.parseModel(command.model);
|
|
1257
1382
|
}
|
|
1258
1383
|
if (command.agent) {
|
|
1259
|
-
const cmdAgent = await Agent.get(command.agent)
|
|
1384
|
+
const cmdAgent = await Agent.get(command.agent);
|
|
1260
1385
|
if (cmdAgent.model) {
|
|
1261
|
-
return cmdAgent.model
|
|
1386
|
+
return cmdAgent.model;
|
|
1262
1387
|
}
|
|
1263
1388
|
}
|
|
1264
1389
|
if (input.model) {
|
|
1265
|
-
return Provider.parseModel(input.model)
|
|
1390
|
+
return Provider.parseModel(input.model);
|
|
1266
1391
|
}
|
|
1267
|
-
return await Provider.defaultModel()
|
|
1268
|
-
})()
|
|
1269
|
-
const agent = await Agent.get(agentName)
|
|
1392
|
+
return await Provider.defaultModel();
|
|
1393
|
+
})();
|
|
1394
|
+
const agent = await Agent.get(agentName);
|
|
1270
1395
|
|
|
1271
1396
|
const parts =
|
|
1272
|
-
(agent.mode ===
|
|
1397
|
+
(agent.mode === 'subagent' && command.subtask !== false) ||
|
|
1398
|
+
command.subtask === true
|
|
1273
1399
|
? [
|
|
1274
1400
|
{
|
|
1275
|
-
type:
|
|
1401
|
+
type: 'subtask' as const,
|
|
1276
1402
|
agent: agent.name,
|
|
1277
|
-
description: command.description ??
|
|
1403
|
+
description: command.description ?? '',
|
|
1278
1404
|
// TODO: how can we make task tool accept a more complex input?
|
|
1279
|
-
prompt: await resolvePromptParts(template).then(
|
|
1405
|
+
prompt: await resolvePromptParts(template).then(
|
|
1406
|
+
(x) => x.find((y) => y.type === 'text')?.text ?? ''
|
|
1407
|
+
),
|
|
1280
1408
|
},
|
|
1281
1409
|
]
|
|
1282
|
-
: await resolvePromptParts(template)
|
|
1410
|
+
: await resolvePromptParts(template);
|
|
1283
1411
|
|
|
1284
1412
|
const result = (await prompt({
|
|
1285
1413
|
sessionID: input.sessionID,
|
|
@@ -1287,62 +1415,75 @@ export namespace SessionPrompt {
|
|
|
1287
1415
|
model,
|
|
1288
1416
|
agent: agentName,
|
|
1289
1417
|
parts,
|
|
1290
|
-
})) as MessageV2.WithParts
|
|
1418
|
+
})) as MessageV2.WithParts;
|
|
1291
1419
|
|
|
1292
1420
|
Bus.publish(Command.Event.Executed, {
|
|
1293
1421
|
name: input.command,
|
|
1294
1422
|
sessionID: input.sessionID,
|
|
1295
1423
|
arguments: input.arguments,
|
|
1296
1424
|
messageID: result.info.id,
|
|
1297
|
-
})
|
|
1425
|
+
});
|
|
1298
1426
|
|
|
1299
|
-
return result
|
|
1427
|
+
return result;
|
|
1300
1428
|
}
|
|
1301
1429
|
|
|
1302
1430
|
// TODO: wire this back up
|
|
1303
1431
|
async function ensureTitle(input: {
|
|
1304
|
-
session: Session.Info
|
|
1305
|
-
message: MessageV2.WithParts
|
|
1306
|
-
history: MessageV2.WithParts[]
|
|
1307
|
-
providerID: string
|
|
1308
|
-
modelID: string
|
|
1432
|
+
session: Session.Info;
|
|
1433
|
+
message: MessageV2.WithParts;
|
|
1434
|
+
history: MessageV2.WithParts[];
|
|
1435
|
+
providerID: string;
|
|
1436
|
+
modelID: string;
|
|
1309
1437
|
}) {
|
|
1310
|
-
if (input.session.parentID) return
|
|
1311
|
-
if (!Session.isDefaultTitle(input.session.title)) return
|
|
1438
|
+
if (input.session.parentID) return;
|
|
1439
|
+
if (!Session.isDefaultTitle(input.session.title)) return;
|
|
1312
1440
|
const isFirst =
|
|
1313
|
-
input.history.filter(
|
|
1314
|
-
|
|
1315
|
-
|
|
1441
|
+
input.history.filter(
|
|
1442
|
+
(m) =>
|
|
1443
|
+
m.info.role === 'user' &&
|
|
1444
|
+
!m.parts.every((p) => 'synthetic' in p && p.synthetic)
|
|
1445
|
+
).length === 1;
|
|
1446
|
+
if (!isFirst) return;
|
|
1316
1447
|
const small =
|
|
1317
|
-
(await Provider.getSmallModel(input.providerID)) ??
|
|
1448
|
+
(await Provider.getSmallModel(input.providerID)) ??
|
|
1449
|
+
(await Provider.getModel(input.providerID, input.modelID));
|
|
1318
1450
|
const options = {
|
|
1319
|
-
...ProviderTransform.options(
|
|
1451
|
+
...ProviderTransform.options(
|
|
1452
|
+
small.providerID,
|
|
1453
|
+
small.modelID,
|
|
1454
|
+
small.npm ?? '',
|
|
1455
|
+
input.session.id
|
|
1456
|
+
),
|
|
1320
1457
|
...small.info.options,
|
|
1321
|
-
}
|
|
1322
|
-
if (small.providerID ===
|
|
1323
|
-
if (small.modelID.includes(
|
|
1324
|
-
options[
|
|
1458
|
+
};
|
|
1459
|
+
if (small.providerID === 'openai' || small.modelID.includes('gpt-5')) {
|
|
1460
|
+
if (small.modelID.includes('5.1')) {
|
|
1461
|
+
options['reasoningEffort'] = 'low';
|
|
1325
1462
|
} else {
|
|
1326
|
-
options[
|
|
1463
|
+
options['reasoningEffort'] = 'minimal';
|
|
1327
1464
|
}
|
|
1328
1465
|
}
|
|
1329
|
-
if (small.providerID ===
|
|
1330
|
-
options[
|
|
1466
|
+
if (small.providerID === 'google') {
|
|
1467
|
+
options['thinkingConfig'] = {
|
|
1331
1468
|
thinkingBudget: 0,
|
|
1332
|
-
}
|
|
1469
|
+
};
|
|
1333
1470
|
}
|
|
1334
1471
|
await generateText({
|
|
1335
1472
|
maxOutputTokens: small.info.reasoning ? 1500 : 20,
|
|
1336
|
-
providerOptions: ProviderTransform.providerOptions(
|
|
1473
|
+
providerOptions: ProviderTransform.providerOptions(
|
|
1474
|
+
small.npm,
|
|
1475
|
+
small.providerID,
|
|
1476
|
+
options
|
|
1477
|
+
),
|
|
1337
1478
|
messages: [
|
|
1338
1479
|
...SystemPrompt.title(small.providerID).map(
|
|
1339
1480
|
(x): ModelMessage => ({
|
|
1340
|
-
role:
|
|
1481
|
+
role: 'system',
|
|
1341
1482
|
content: x,
|
|
1342
|
-
})
|
|
1483
|
+
})
|
|
1343
1484
|
),
|
|
1344
1485
|
{
|
|
1345
|
-
role:
|
|
1486
|
+
role: 'user' as const,
|
|
1346
1487
|
content: `
|
|
1347
1488
|
The following is the text to summarize:
|
|
1348
1489
|
`,
|
|
@@ -1350,13 +1491,16 @@ export namespace SessionPrompt {
|
|
|
1350
1491
|
...MessageV2.toModelMessage([
|
|
1351
1492
|
{
|
|
1352
1493
|
info: {
|
|
1353
|
-
id: Identifier.ascending(
|
|
1354
|
-
role:
|
|
1494
|
+
id: Identifier.ascending('message'),
|
|
1495
|
+
role: 'user',
|
|
1355
1496
|
sessionID: input.session.id,
|
|
1356
1497
|
time: {
|
|
1357
1498
|
created: Date.now(),
|
|
1358
1499
|
},
|
|
1359
|
-
agent:
|
|
1500
|
+
agent:
|
|
1501
|
+
input.message.info.role === 'user'
|
|
1502
|
+
? input.message.info.agent
|
|
1503
|
+
: 'build',
|
|
1360
1504
|
model: {
|
|
1361
1505
|
providerID: input.providerID,
|
|
1362
1506
|
modelID: input.modelID,
|
|
@@ -1373,18 +1517,19 @@ export namespace SessionPrompt {
|
|
|
1373
1517
|
if (result.text)
|
|
1374
1518
|
return Session.update(input.session.id, (draft) => {
|
|
1375
1519
|
const cleaned = result.text
|
|
1376
|
-
.replace(/<think>[\s\S]*?<\/think>\s*/g,
|
|
1377
|
-
.split(
|
|
1520
|
+
.replace(/<think>[\s\S]*?<\/think>\s*/g, '')
|
|
1521
|
+
.split('\n')
|
|
1378
1522
|
.map((line) => line.trim())
|
|
1379
|
-
.find((line) => line.length > 0)
|
|
1380
|
-
if (!cleaned) return
|
|
1523
|
+
.find((line) => line.length > 0);
|
|
1524
|
+
if (!cleaned) return;
|
|
1381
1525
|
|
|
1382
|
-
const title =
|
|
1383
|
-
|
|
1384
|
-
|
|
1526
|
+
const title =
|
|
1527
|
+
cleaned.length > 100 ? cleaned.substring(0, 97) + '...' : cleaned;
|
|
1528
|
+
draft.title = title;
|
|
1529
|
+
});
|
|
1385
1530
|
})
|
|
1386
1531
|
.catch((error) => {
|
|
1387
|
-
log.error(
|
|
1388
|
-
})
|
|
1532
|
+
log.error('failed to generate title', { error, model: small.info.id });
|
|
1533
|
+
});
|
|
1389
1534
|
}
|
|
1390
1535
|
}
|