@bunny-agent/sdk 0.9.28
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/LICENSE +201 -0
- package/README.md +323 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +790 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +438 -0
- package/dist/react/index.js +445 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
buildRunnerEnv,
|
|
4
|
+
DEFAULT_BUNNY_AGENT_DAEMON_URL,
|
|
5
|
+
isBunnyAgentDaemonHealthy,
|
|
6
|
+
LocalSandbox
|
|
7
|
+
} from "@bunny-agent/manager";
|
|
8
|
+
|
|
9
|
+
// src/provider/bunny-agent-language-model.ts
|
|
10
|
+
import {
|
|
11
|
+
BunnyAgent,
|
|
12
|
+
streamCodingRunFromSandbox
|
|
13
|
+
} from "@bunny-agent/manager";
|
|
14
|
+
|
|
15
|
+
// src/provider/logging.ts
|
|
16
|
+
function getProviderLogger(settings) {
|
|
17
|
+
if (settings.logger === false) {
|
|
18
|
+
return {
|
|
19
|
+
debug: () => {
|
|
20
|
+
},
|
|
21
|
+
info: () => {
|
|
22
|
+
},
|
|
23
|
+
warn: () => {
|
|
24
|
+
},
|
|
25
|
+
error: () => {
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (settings.logger) {
|
|
30
|
+
return settings.logger;
|
|
31
|
+
}
|
|
32
|
+
const isVerbose = settings.verbose ?? false;
|
|
33
|
+
return {
|
|
34
|
+
debug: (msg) => isVerbose && console.debug(msg),
|
|
35
|
+
info: (msg) => isVerbose && console.info(msg),
|
|
36
|
+
warn: (msg) => console.warn(msg),
|
|
37
|
+
error: (msg) => console.error(msg)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/provider/bunny-agent-language-model.ts
|
|
42
|
+
function formatErrorForLog(error) {
|
|
43
|
+
if (error instanceof Error) {
|
|
44
|
+
const parts = [error.message];
|
|
45
|
+
let cause = error.cause;
|
|
46
|
+
while (cause instanceof Error) {
|
|
47
|
+
parts.push(cause.message);
|
|
48
|
+
cause = cause.cause;
|
|
49
|
+
}
|
|
50
|
+
return parts.join(" | cause: ");
|
|
51
|
+
}
|
|
52
|
+
return String(error);
|
|
53
|
+
}
|
|
54
|
+
function asyncIterableToReadableStream(iterable) {
|
|
55
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
56
|
+
return new ReadableStream({
|
|
57
|
+
async pull(controller) {
|
|
58
|
+
while (true) {
|
|
59
|
+
const { done, value } = await iterator.next();
|
|
60
|
+
if (done) {
|
|
61
|
+
controller.close();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (value.byteLength > 0) {
|
|
65
|
+
controller.enqueue(value);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async cancel(reason) {
|
|
71
|
+
await iterator.return?.(reason);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function getLastUserTextFromMessages(messages) {
|
|
76
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
77
|
+
if (!lastUser) return "";
|
|
78
|
+
const c = lastUser.content;
|
|
79
|
+
if (typeof c === "string") return c;
|
|
80
|
+
return c.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
81
|
+
}
|
|
82
|
+
function createEmptyUsage() {
|
|
83
|
+
return {
|
|
84
|
+
inputTokens: {
|
|
85
|
+
total: 0,
|
|
86
|
+
noCache: 0,
|
|
87
|
+
cacheRead: 0,
|
|
88
|
+
cacheWrite: 0
|
|
89
|
+
},
|
|
90
|
+
outputTokens: {
|
|
91
|
+
total: 0,
|
|
92
|
+
text: void 0,
|
|
93
|
+
reasoning: void 0
|
|
94
|
+
},
|
|
95
|
+
raw: void 0
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
var BunnyAgentLanguageModel = class {
|
|
99
|
+
specificationVersion = "v3";
|
|
100
|
+
provider;
|
|
101
|
+
modelId;
|
|
102
|
+
supportedUrls = {
|
|
103
|
+
"image/*": [/.*/]
|
|
104
|
+
};
|
|
105
|
+
options;
|
|
106
|
+
logger;
|
|
107
|
+
sessionId;
|
|
108
|
+
toolNameMap = /* @__PURE__ */ new Map();
|
|
109
|
+
legacyTextPartCounter = 0;
|
|
110
|
+
constructor(modelOptions) {
|
|
111
|
+
this.modelId = modelOptions.id;
|
|
112
|
+
this.options = modelOptions.options;
|
|
113
|
+
this.logger = getProviderLogger(modelOptions.options);
|
|
114
|
+
this.provider = modelOptions.options.daemonUrl ? "bunny-agent-daemon" : "bunny-agent";
|
|
115
|
+
}
|
|
116
|
+
async doGenerate(options) {
|
|
117
|
+
const { stream, request } = await this.doStream(options);
|
|
118
|
+
const reader = stream.getReader();
|
|
119
|
+
const content = [];
|
|
120
|
+
const warnings = [];
|
|
121
|
+
let finishReason = {
|
|
122
|
+
unified: "other",
|
|
123
|
+
raw: void 0
|
|
124
|
+
};
|
|
125
|
+
let usage = createEmptyUsage();
|
|
126
|
+
let providerMetadata;
|
|
127
|
+
const textParts = /* @__PURE__ */ new Map();
|
|
128
|
+
const toolInputs = /* @__PURE__ */ new Map();
|
|
129
|
+
try {
|
|
130
|
+
while (true) {
|
|
131
|
+
const { done, value } = await reader.read();
|
|
132
|
+
if (done) break;
|
|
133
|
+
switch (value.type) {
|
|
134
|
+
case "text-start": {
|
|
135
|
+
textParts.set(value.id, { text: "" });
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "text-delta": {
|
|
139
|
+
const part = textParts.get(value.id);
|
|
140
|
+
if (part) {
|
|
141
|
+
part.text += value.delta;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "text-end": {
|
|
146
|
+
const part = textParts.get(value.id);
|
|
147
|
+
if (part) {
|
|
148
|
+
content.push({
|
|
149
|
+
type: "text",
|
|
150
|
+
text: part.text
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "tool-input-start": {
|
|
156
|
+
toolInputs.set(value.id, { toolName: value.toolName, input: "" });
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "tool-input-delta": {
|
|
160
|
+
const tool = toolInputs.get(value.id);
|
|
161
|
+
if (tool) {
|
|
162
|
+
tool.input += value.delta;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "tool-input-end": {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "tool-call": {
|
|
170
|
+
content.push({
|
|
171
|
+
type: "tool-call",
|
|
172
|
+
toolCallId: value.toolCallId,
|
|
173
|
+
toolName: value.toolName,
|
|
174
|
+
input: value.input,
|
|
175
|
+
providerExecuted: value.providerExecuted
|
|
176
|
+
});
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "stream-start": {
|
|
180
|
+
warnings.push(...value.warnings);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "error": {
|
|
184
|
+
const message = value.error instanceof Error ? value.error.message : typeof value.error === "string" ? value.error : value.error ? String(value.error) : "Unknown error";
|
|
185
|
+
content.push({ type: "text", text: message });
|
|
186
|
+
finishReason = { unified: "error", raw: "error" };
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case "finish": {
|
|
190
|
+
finishReason = value.finishReason;
|
|
191
|
+
usage = value.usage;
|
|
192
|
+
providerMetadata = value.providerMetadata;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
reader.releaseLock();
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
content,
|
|
202
|
+
finishReason,
|
|
203
|
+
usage,
|
|
204
|
+
providerMetadata,
|
|
205
|
+
request,
|
|
206
|
+
warnings
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async doStream(options) {
|
|
210
|
+
const { prompt, abortSignal } = options;
|
|
211
|
+
const messages = this.convertPromptToMessages(prompt);
|
|
212
|
+
this.resetStreamState();
|
|
213
|
+
this.logger.debug(
|
|
214
|
+
`[bunny-agent] Starting stream with ${messages.length} messages`
|
|
215
|
+
);
|
|
216
|
+
const sandbox = this.options.sandbox;
|
|
217
|
+
if (!sandbox) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
"BunnyAgent language model requires a sandbox adapter (set `sandbox` on the provider)."
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const daemonUrl = this.options.daemonUrl;
|
|
223
|
+
if (daemonUrl) {
|
|
224
|
+
const handle = await sandbox.attach();
|
|
225
|
+
const sandboxEnv2 = sandbox.getEnv?.() ?? {};
|
|
226
|
+
const runnerEnv = { ...sandboxEnv2, ...this.options.env };
|
|
227
|
+
const body = {
|
|
228
|
+
...this.buildCodingRunBody(messages, handle.getWorkdir()),
|
|
229
|
+
...Object.keys(runnerEnv).length > 0 ? { env: runnerEnv } : {}
|
|
230
|
+
};
|
|
231
|
+
const execOpts = {
|
|
232
|
+
cwd: this.options.cwd ?? handle.getWorkdir(),
|
|
233
|
+
signal: abortSignal
|
|
234
|
+
};
|
|
235
|
+
const iterable = streamCodingRunFromSandbox(
|
|
236
|
+
handle,
|
|
237
|
+
daemonUrl,
|
|
238
|
+
body,
|
|
239
|
+
execOpts
|
|
240
|
+
);
|
|
241
|
+
const bytesStream = asyncIterableToReadableStream(iterable);
|
|
242
|
+
return this.buildStreamResult(bytesStream, messages);
|
|
243
|
+
}
|
|
244
|
+
const sandboxEnv = sandbox.getEnv?.() ?? {};
|
|
245
|
+
const sandboxWorkdir = this.options.cwd ?? sandbox.getWorkdir?.() ?? "/workspace";
|
|
246
|
+
const agent = new BunnyAgent({
|
|
247
|
+
sandbox,
|
|
248
|
+
runner: this.options.runner,
|
|
249
|
+
env: { ...sandboxEnv, ...this.options.env }
|
|
250
|
+
});
|
|
251
|
+
try {
|
|
252
|
+
const bytesStream = await agent.stream({
|
|
253
|
+
messages,
|
|
254
|
+
workspace: {
|
|
255
|
+
path: sandboxWorkdir
|
|
256
|
+
},
|
|
257
|
+
resume: this.options.resume,
|
|
258
|
+
signal: abortSignal
|
|
259
|
+
});
|
|
260
|
+
return this.buildStreamResult(bytesStream, messages);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
await agent.destroy().catch(() => {
|
|
263
|
+
});
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
resetStreamState() {
|
|
268
|
+
this.sessionId = void 0;
|
|
269
|
+
this.toolNameMap = /* @__PURE__ */ new Map();
|
|
270
|
+
this.legacyTextPartCounter = 0;
|
|
271
|
+
}
|
|
272
|
+
buildCodingRunBody(messages, cwdFallback) {
|
|
273
|
+
const runner = this.options.runner;
|
|
274
|
+
const cwd = this.options.cwd ?? cwdFallback;
|
|
275
|
+
return {
|
|
276
|
+
runner: runner.runnerType ?? "claude",
|
|
277
|
+
model: this.modelId,
|
|
278
|
+
userInput: getLastUserTextFromMessages(messages),
|
|
279
|
+
cwd,
|
|
280
|
+
resume: this.options.resume,
|
|
281
|
+
systemPrompt: this.options.systemPrompt ?? runner.systemPrompt,
|
|
282
|
+
maxTurns: this.options.maxTurns ?? runner.maxTurns,
|
|
283
|
+
allowedTools: runner.allowedTools ?? this.options.allowedTools,
|
|
284
|
+
skillPaths: runner.skillPaths ?? this.options.skillPaths,
|
|
285
|
+
yolo: this.options.yolo
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
buildStreamResult(bytesStream, messages) {
|
|
289
|
+
const reader = bytesStream.getReader();
|
|
290
|
+
return {
|
|
291
|
+
stream: this.createLanguageModelStreamFromSseReader(reader),
|
|
292
|
+
request: {
|
|
293
|
+
body: JSON.stringify({ messages })
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
createLanguageModelStreamFromSseReader(reader) {
|
|
298
|
+
const self = this;
|
|
299
|
+
return new ReadableStream({
|
|
300
|
+
async start(controller) {
|
|
301
|
+
try {
|
|
302
|
+
let buffer = "";
|
|
303
|
+
while (true) {
|
|
304
|
+
const { done, value } = await reader.read();
|
|
305
|
+
if (done) {
|
|
306
|
+
if (buffer.trim()) {
|
|
307
|
+
const parts = self.parseSSEBuffer(buffer);
|
|
308
|
+
for (const part of parts) {
|
|
309
|
+
controller.enqueue(part);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
controller.close();
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
const text = new TextDecoder().decode(value);
|
|
316
|
+
buffer += text;
|
|
317
|
+
const lines = buffer.split("\n");
|
|
318
|
+
buffer = lines.pop() ?? "";
|
|
319
|
+
let foundDone = false;
|
|
320
|
+
for (const line of lines) {
|
|
321
|
+
const candidate = line.startsWith("data: ") ? line.slice(6) : line.trim();
|
|
322
|
+
if (!candidate) continue;
|
|
323
|
+
if (candidate === "[DONE]") {
|
|
324
|
+
foundDone = true;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const parts = self.parseSSEData(candidate);
|
|
329
|
+
for (const part of parts) {
|
|
330
|
+
controller.enqueue(part);
|
|
331
|
+
if (self.sessionId) {
|
|
332
|
+
const sessionId = self.sessionId;
|
|
333
|
+
if (self.options.artifactProcessors?.length) {
|
|
334
|
+
for (const processor of self.options.artifactProcessors) {
|
|
335
|
+
Promise.resolve().then(() => processor.onChange(part, sessionId)).catch((e) => {
|
|
336
|
+
self.logger.error(
|
|
337
|
+
`[bunny-agent] Artifact processor error: ${e}`
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} catch (e) {
|
|
345
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
346
|
+
if (!msg.includes("Unexpected token")) {
|
|
347
|
+
self.logger.error(
|
|
348
|
+
`[bunny-agent] Failed to parse stream data: ${e}`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (foundDone) {
|
|
354
|
+
controller.close();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
360
|
+
self.logger.info("[bunny-agent] Stream aborted by user");
|
|
361
|
+
} else {
|
|
362
|
+
self.logger.error(
|
|
363
|
+
`[bunny-agent] Stream error: ${formatErrorForLog(error)}`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
controller.error(error);
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
cancel() {
|
|
370
|
+
reader.cancel();
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
parseSSEBuffer(buffer) {
|
|
375
|
+
const parts = [];
|
|
376
|
+
const lines = buffer.split("\n");
|
|
377
|
+
for (const line of lines) {
|
|
378
|
+
const candidate = line.startsWith("data: ") ? line.slice(6) : line.trim();
|
|
379
|
+
if (!candidate || candidate === "[DONE]") continue;
|
|
380
|
+
try {
|
|
381
|
+
const parsedParts = this.parseSSEData(candidate);
|
|
382
|
+
parts.push(...parsedParts);
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return parts;
|
|
387
|
+
}
|
|
388
|
+
parseSSEData(data) {
|
|
389
|
+
const parts = [];
|
|
390
|
+
const parsed = JSON.parse(data);
|
|
391
|
+
const parsedType = typeof parsed.type === "string" ? parsed.type : void 0;
|
|
392
|
+
if (!parsedType && typeof parsed.error === "string") {
|
|
393
|
+
return [
|
|
394
|
+
{
|
|
395
|
+
type: "error",
|
|
396
|
+
error: new Error(parsed.error)
|
|
397
|
+
}
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
switch (parsedType) {
|
|
401
|
+
case "start": {
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
// Backward-compatible NDJSON chunk from daemon tests/mock runners.
|
|
405
|
+
case "text": {
|
|
406
|
+
const text = typeof parsed.text === "string" ? parsed.text : "";
|
|
407
|
+
if (text.length > 0) {
|
|
408
|
+
const id = `legacy-text-${++this.legacyTextPartCounter}`;
|
|
409
|
+
parts.push(
|
|
410
|
+
{
|
|
411
|
+
type: "text-start",
|
|
412
|
+
id,
|
|
413
|
+
providerMetadata: {
|
|
414
|
+
"bunny-agent": {
|
|
415
|
+
sessionId: this.sessionId
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
{ type: "text-delta", id, delta: text },
|
|
420
|
+
{ type: "text-end", id }
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case "message-metadata": {
|
|
426
|
+
const metadata = parsed.messageMetadata;
|
|
427
|
+
if (metadata?.sessionId && typeof metadata.sessionId === "string") {
|
|
428
|
+
this.sessionId = metadata.sessionId;
|
|
429
|
+
this.logger.debug(
|
|
430
|
+
`[bunny-agent] Session ID extracted: ${this.sessionId}`
|
|
431
|
+
);
|
|
432
|
+
parts.push({
|
|
433
|
+
type: "raw",
|
|
434
|
+
rawValue: this.sessionId
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case "text-start": {
|
|
440
|
+
parts.push({
|
|
441
|
+
type: "text-start",
|
|
442
|
+
id: parsed.id,
|
|
443
|
+
providerMetadata: {
|
|
444
|
+
"bunny-agent": {
|
|
445
|
+
sessionId: this.sessionId
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case "text-delta": {
|
|
452
|
+
parts.push({
|
|
453
|
+
type: "text-delta",
|
|
454
|
+
id: parsed.id,
|
|
455
|
+
delta: parsed.delta
|
|
456
|
+
});
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
case "text-end": {
|
|
460
|
+
parts.push({
|
|
461
|
+
type: "text-end",
|
|
462
|
+
id: parsed.id
|
|
463
|
+
});
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case "tool-input-start": {
|
|
467
|
+
parts.push({
|
|
468
|
+
type: "tool-input-start",
|
|
469
|
+
id: parsed.toolCallId,
|
|
470
|
+
toolName: parsed.toolName,
|
|
471
|
+
dynamic: parsed.dynamic,
|
|
472
|
+
providerExecuted: parsed.providerExecuted
|
|
473
|
+
});
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case "tool-input-delta": {
|
|
477
|
+
parts.push({
|
|
478
|
+
type: "tool-input-delta",
|
|
479
|
+
id: parsed.toolCallId,
|
|
480
|
+
delta: parsed.inputTextDelta
|
|
481
|
+
});
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case "tool-input-available": {
|
|
485
|
+
const toolCallId = parsed.toolCallId;
|
|
486
|
+
const toolName = parsed.toolName;
|
|
487
|
+
const input = parsed.input;
|
|
488
|
+
this.toolNameMap.set(toolCallId, toolName);
|
|
489
|
+
parts.push({
|
|
490
|
+
type: "tool-call",
|
|
491
|
+
toolCallId,
|
|
492
|
+
toolName,
|
|
493
|
+
input: JSON.stringify(input),
|
|
494
|
+
dynamic: parsed.dynamic,
|
|
495
|
+
providerExecuted: parsed.providerExecuted
|
|
496
|
+
});
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case "tool-output-available": {
|
|
500
|
+
const toolName = this.toolNameMap.get(parsed.toolCallId);
|
|
501
|
+
parts.push({
|
|
502
|
+
type: "tool-result",
|
|
503
|
+
toolCallId: parsed.toolCallId,
|
|
504
|
+
toolName: toolName ?? "",
|
|
505
|
+
result: parsed.output,
|
|
506
|
+
isError: parsed.isError,
|
|
507
|
+
dynamic: parsed.dynamic
|
|
508
|
+
});
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
case "error": {
|
|
512
|
+
parts.push({
|
|
513
|
+
type: "error",
|
|
514
|
+
error: new Error(
|
|
515
|
+
parsed.errorText || parsed.error || "Unknown stream error"
|
|
516
|
+
)
|
|
517
|
+
});
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
case "finish": {
|
|
521
|
+
const rawFinishReason = parsed.finishReason;
|
|
522
|
+
let finishReason;
|
|
523
|
+
if (typeof rawFinishReason === "object" && rawFinishReason !== null && "unified" in rawFinishReason) {
|
|
524
|
+
finishReason = rawFinishReason;
|
|
525
|
+
} else {
|
|
526
|
+
finishReason = this.mapFinishReason(rawFinishReason);
|
|
527
|
+
}
|
|
528
|
+
const messageMetadata = parsed.messageMetadata;
|
|
529
|
+
const rawUsage = messageMetadata?.usage;
|
|
530
|
+
const usage = this.convertUsage(rawUsage);
|
|
531
|
+
parts.push({
|
|
532
|
+
type: "finish",
|
|
533
|
+
finishReason,
|
|
534
|
+
usage,
|
|
535
|
+
providerMetadata: {
|
|
536
|
+
"bunny-agent": {
|
|
537
|
+
...parsed.messageMetadata ?? {},
|
|
538
|
+
sessionId: this.sessionId
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return parts;
|
|
546
|
+
}
|
|
547
|
+
convertPromptToMessages(prompt) {
|
|
548
|
+
const messages = [];
|
|
549
|
+
for (const message of prompt) {
|
|
550
|
+
switch (message.role) {
|
|
551
|
+
case "system": {
|
|
552
|
+
messages.push({
|
|
553
|
+
role: "system",
|
|
554
|
+
content: message.content
|
|
555
|
+
});
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case "user": {
|
|
559
|
+
const parts = message.content.map((part) => {
|
|
560
|
+
if (part.type === "text") {
|
|
561
|
+
return { type: "text", text: part.text };
|
|
562
|
+
}
|
|
563
|
+
if (part.type === "file") {
|
|
564
|
+
let dataStr = "";
|
|
565
|
+
if (part.data instanceof Uint8Array) {
|
|
566
|
+
dataStr = `data:${part.mediaType};base64,${Buffer.from(part.data).toString("base64")}`;
|
|
567
|
+
} else if (part.data instanceof URL) {
|
|
568
|
+
dataStr = part.data.toString();
|
|
569
|
+
} else if (typeof part.data === "string") {
|
|
570
|
+
dataStr = part.data;
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
type: "image",
|
|
574
|
+
mimeType: part.mediaType || "image/png",
|
|
575
|
+
data: dataStr
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}).filter(
|
|
580
|
+
(p) => p !== null
|
|
581
|
+
);
|
|
582
|
+
if (parts.length > 0) {
|
|
583
|
+
const isAllText = parts.every((p) => p.type === "text");
|
|
584
|
+
messages.push({
|
|
585
|
+
role: "user",
|
|
586
|
+
content: isAllText ? parts.map((p) => p.text).join("\n") : parts
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
case "assistant": {
|
|
592
|
+
const textParts = message.content.filter(
|
|
593
|
+
(part) => part.type === "text"
|
|
594
|
+
).map((part) => part.text);
|
|
595
|
+
if (textParts.length > 0) {
|
|
596
|
+
messages.push({
|
|
597
|
+
role: "assistant",
|
|
598
|
+
content: textParts.join("\n")
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
case "tool": {
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return messages;
|
|
609
|
+
}
|
|
610
|
+
mapFinishReason(reason) {
|
|
611
|
+
switch (reason) {
|
|
612
|
+
case "stop":
|
|
613
|
+
return { unified: "stop", raw: reason };
|
|
614
|
+
case "length":
|
|
615
|
+
return { unified: "length", raw: reason };
|
|
616
|
+
case "tool_calls":
|
|
617
|
+
case "tool-calls":
|
|
618
|
+
return { unified: "tool-calls", raw: reason };
|
|
619
|
+
case "content_filter":
|
|
620
|
+
case "content-filter":
|
|
621
|
+
return { unified: "content-filter", raw: reason };
|
|
622
|
+
case "error":
|
|
623
|
+
return { unified: "error", raw: reason };
|
|
624
|
+
default:
|
|
625
|
+
return { unified: "other", raw: reason ?? "unknown" };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
convertUsage(data) {
|
|
629
|
+
if (!data) {
|
|
630
|
+
return createEmptyUsage();
|
|
631
|
+
}
|
|
632
|
+
if ("inputTokens" in data && "outputTokens" in data) {
|
|
633
|
+
const inputTokens = data.inputTokens;
|
|
634
|
+
const outputTokens = data.outputTokens;
|
|
635
|
+
const rawData = "raw" in data ? data.raw : data;
|
|
636
|
+
return {
|
|
637
|
+
inputTokens: {
|
|
638
|
+
total: inputTokens.total ?? 0,
|
|
639
|
+
noCache: inputTokens.noCache ?? 0,
|
|
640
|
+
cacheRead: inputTokens.cacheRead ?? 0,
|
|
641
|
+
cacheWrite: inputTokens.cacheWrite ?? 0
|
|
642
|
+
},
|
|
643
|
+
outputTokens: {
|
|
644
|
+
total: outputTokens.total ?? 0,
|
|
645
|
+
text: outputTokens.text ?? outputTokens.textTokens ?? void 0,
|
|
646
|
+
reasoning: outputTokens.reasoning ?? outputTokens.reasoningTokens ?? void 0
|
|
647
|
+
},
|
|
648
|
+
raw: rawData
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const usage = data.usage ?? data;
|
|
652
|
+
if ("input_tokens" in usage || "output_tokens" in usage) {
|
|
653
|
+
const inputTokens = usage.input_tokens ?? 0;
|
|
654
|
+
const outputTokens = usage.output_tokens ?? 0;
|
|
655
|
+
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
656
|
+
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
657
|
+
const textTokens = usage.text_tokens ?? void 0;
|
|
658
|
+
const reasoningTokens = usage.reasoning_tokens ?? void 0;
|
|
659
|
+
return {
|
|
660
|
+
inputTokens: {
|
|
661
|
+
total: inputTokens + cacheWrite + cacheRead,
|
|
662
|
+
noCache: inputTokens,
|
|
663
|
+
cacheRead,
|
|
664
|
+
cacheWrite
|
|
665
|
+
},
|
|
666
|
+
outputTokens: {
|
|
667
|
+
total: outputTokens,
|
|
668
|
+
text: textTokens,
|
|
669
|
+
reasoning: reasoningTokens
|
|
670
|
+
},
|
|
671
|
+
raw: usage
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return createEmptyUsage();
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// src/provider/bunny-agent-provider.ts
|
|
679
|
+
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
680
|
+
function createBunnyAgent(defaultOptions) {
|
|
681
|
+
const logger = getProviderLogger(defaultOptions);
|
|
682
|
+
if (!defaultOptions.sandbox) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
"Provide a `sandbox` adapter (e.g. E2BSandbox, LocalSandbox). Optional `daemonUrl` uses in-sandbox HTTP to bunny-agent-daemon (no automatic `/healthz` probe). Use `isBunnyAgentDaemonHealthy` from `@bunny-agent/sdk` if you want to probe and omit `daemonUrl` for CLI fallback. Omit `daemonUrl` to always use CLI."
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
const createModel = (modelId, options = {}) => {
|
|
688
|
+
const mergedSkillPaths = options.skillPaths !== void 0 ? options.skillPaths : defaultOptions.skillPaths;
|
|
689
|
+
const mergedAllowedTools = options.allowedTools !== void 0 ? options.allowedTools : defaultOptions.allowedTools;
|
|
690
|
+
const runner = {
|
|
691
|
+
model: modelId,
|
|
692
|
+
runnerType: options.runnerType ?? defaultOptions.runnerType,
|
|
693
|
+
outputFormat: "stream",
|
|
694
|
+
maxTurns: options.maxTurns ?? defaultOptions.maxTurns,
|
|
695
|
+
...options.systemPrompt ?? defaultOptions.systemPrompt ? { systemPrompt: options.systemPrompt ?? defaultOptions.systemPrompt } : {},
|
|
696
|
+
...mergedSkillPaths && mergedSkillPaths.length > 0 ? { skillPaths: mergedSkillPaths } : {},
|
|
697
|
+
...mergedAllowedTools !== void 0 ? { allowedTools: mergedAllowedTools } : {}
|
|
698
|
+
};
|
|
699
|
+
const mergedOptions = {
|
|
700
|
+
...defaultOptions,
|
|
701
|
+
...options,
|
|
702
|
+
runner,
|
|
703
|
+
env: {
|
|
704
|
+
...defaultOptions.env,
|
|
705
|
+
...options.env
|
|
706
|
+
},
|
|
707
|
+
artifactProcessors: [
|
|
708
|
+
...defaultOptions.artifactProcessors ?? [],
|
|
709
|
+
...options.artifactProcessors ?? []
|
|
710
|
+
]
|
|
711
|
+
};
|
|
712
|
+
logger.debug(
|
|
713
|
+
`[bunny-agent] Creating model: ${modelId}${runner.runnerType ? ` (runnerType: ${runner.runnerType})` : ""}${runner.skillPaths?.length ? ` skillPaths=${runner.skillPaths.length}` : ""}`
|
|
714
|
+
);
|
|
715
|
+
return new BunnyAgentLanguageModel({
|
|
716
|
+
id: modelId,
|
|
717
|
+
options: mergedOptions
|
|
718
|
+
});
|
|
719
|
+
};
|
|
720
|
+
const provider = function(modelId, options) {
|
|
721
|
+
if (new.target) {
|
|
722
|
+
throw new Error(
|
|
723
|
+
"The BunnyAgent model function cannot be called with the new keyword."
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return createModel(modelId, options);
|
|
727
|
+
};
|
|
728
|
+
provider.languageModel = createModel;
|
|
729
|
+
provider.chat = createModel;
|
|
730
|
+
provider.specificationVersion = "v3";
|
|
731
|
+
provider.embeddingModel = (modelId) => {
|
|
732
|
+
throw new NoSuchModelError({
|
|
733
|
+
modelId,
|
|
734
|
+
modelType: "embeddingModel"
|
|
735
|
+
});
|
|
736
|
+
};
|
|
737
|
+
provider.textEmbeddingModel = (modelId) => {
|
|
738
|
+
throw new NoSuchModelError({
|
|
739
|
+
modelId,
|
|
740
|
+
modelType: "embeddingModel"
|
|
741
|
+
});
|
|
742
|
+
};
|
|
743
|
+
provider.imageModel = (modelId) => {
|
|
744
|
+
throw new NoSuchModelError({
|
|
745
|
+
modelId,
|
|
746
|
+
modelType: "imageModel"
|
|
747
|
+
});
|
|
748
|
+
};
|
|
749
|
+
return provider;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/provider/question-processor.ts
|
|
753
|
+
async function submitAnswer(sandbox, params, options) {
|
|
754
|
+
const { toolCallId, questions, answers } = params;
|
|
755
|
+
const basePath = options?.basePath ?? ".bunny-agent/approvals";
|
|
756
|
+
const allAnswered = questions.every(
|
|
757
|
+
(q) => answers[q.question] !== void 0 && answers[q.question] !== ""
|
|
758
|
+
);
|
|
759
|
+
const answerData = {
|
|
760
|
+
questions,
|
|
761
|
+
answers,
|
|
762
|
+
status: allAnswered ? "completed" : "pending",
|
|
763
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
764
|
+
};
|
|
765
|
+
const filename = `${toolCallId}.json`;
|
|
766
|
+
const handle = sandbox.getHandle() ?? await sandbox.attach();
|
|
767
|
+
const workdir = handle.getWorkdir();
|
|
768
|
+
const targetDir = workdir ? `${workdir.replace(/\/$/, "")}/${basePath}` : basePath;
|
|
769
|
+
await handle.upload(
|
|
770
|
+
[{ path: filename, content: JSON.stringify(answerData, null, 2) }],
|
|
771
|
+
targetDir
|
|
772
|
+
);
|
|
773
|
+
console.log(
|
|
774
|
+
`[submitAnswer] Answer submitted: ${targetDir}/${filename} (status: ${answerData.status}, workdir: ${workdir ?? "(none)"})`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/index.ts
|
|
779
|
+
var VERSION = "0.1.0";
|
|
780
|
+
export {
|
|
781
|
+
BunnyAgentLanguageModel,
|
|
782
|
+
DEFAULT_BUNNY_AGENT_DAEMON_URL,
|
|
783
|
+
LocalSandbox,
|
|
784
|
+
VERSION,
|
|
785
|
+
buildRunnerEnv,
|
|
786
|
+
createBunnyAgent,
|
|
787
|
+
isBunnyAgentDaemonHealthy,
|
|
788
|
+
submitAnswer
|
|
789
|
+
};
|
|
790
|
+
//# sourceMappingURL=index.js.map
|