@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/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