@hachej/boring-agent 0.1.48 → 0.1.50
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/{chunk-HEEJSOFF.js → chunk-G6YPXDHR.js} +2 -1
- package/dist/front/index.d.ts +45 -40
- package/dist/front/index.js +102 -39
- package/dist/front/styles.css +3 -3
- package/dist/{piChatEvent-DNKo77Xw.d.ts → piChatEvent-B6GDifo2.d.ts} +1 -0
- package/dist/server/index.js +188 -44
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +1 -1
- package/package.json +2 -2
|
@@ -170,7 +170,8 @@ var BoringChatPartSchema = z2.discriminatedUnion("type", [
|
|
|
170
170
|
id: z2.string().optional(),
|
|
171
171
|
filename: z2.string().optional(),
|
|
172
172
|
mediaType: z2.string().optional(),
|
|
173
|
-
url: z2.string().optional()
|
|
173
|
+
url: z2.string().optional(),
|
|
174
|
+
path: z2.string().optional()
|
|
174
175
|
}),
|
|
175
176
|
z2.object({
|
|
176
177
|
type: z2.literal("notice"),
|
package/dist/front/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { FileUIPart,
|
|
5
|
-
import { T as ToolUiMetadata, B as BoringChatPart, d as BoringChatMessage, m as PiChatStatus, r as QueuedUserMessage, C as ChatError, P as PiChatEvent, o as PromptPayload, p as PromptReceipt, F as FollowUpPayload, i as FollowUpReceipt, Q as QueueClearPayload, q as QueueClearReceipt, I as InterruptPayload, e as CommandReceipt, S as StopPayload, s as StopReceipt } from '../piChatEvent-
|
|
3
|
+
import { ComponentProps, HTMLAttributes, FormEvent, ReactNode, ComponentType } from 'react';
|
|
4
|
+
import { FileUIPart, ChatStatus, UIMessage } from 'ai';
|
|
5
|
+
import { T as ToolUiMetadata, B as BoringChatPart, d as BoringChatMessage, m as PiChatStatus, r as QueuedUserMessage, C as ChatError, P as PiChatEvent, o as PromptPayload, p as PromptReceipt, F as FollowUpPayload, i as FollowUpReceipt, Q as QueueClearPayload, q as QueueClearReceipt, I as InterruptPayload, e as CommandReceipt, S as StopPayload, s as StopReceipt } from '../piChatEvent-B6GDifo2.js';
|
|
6
6
|
import { e as SessionSummary } from '../chatSubmitPayload-DHqQL2wD.js';
|
|
7
|
-
import {
|
|
7
|
+
import { InputGroupAddon, InputGroupButton, InputGroupTextarea, Button, Collapsible, CollapsibleContent, CollapsibleTrigger } from '@hachej/boring-ui-kit';
|
|
8
8
|
import { Streamdown } from 'streamdown';
|
|
9
9
|
import { StickToBottom } from 'use-stick-to-bottom';
|
|
10
10
|
import { ClassValue } from 'clsx';
|
|
@@ -15,6 +15,7 @@ interface UploadFileOptions {
|
|
|
15
15
|
workspaceRequestId?: string | null;
|
|
16
16
|
directory?: string;
|
|
17
17
|
sourcePath?: string;
|
|
18
|
+
responseUrl?: 'markdown' | 'raw';
|
|
18
19
|
fetch?: typeof globalThis.fetch;
|
|
19
20
|
}
|
|
20
21
|
interface UploadFileResult {
|
|
@@ -23,6 +24,45 @@ interface UploadFileResult {
|
|
|
23
24
|
}
|
|
24
25
|
declare function uploadFile(file: File, opts?: UploadFileOptions): Promise<UploadFileResult>;
|
|
25
26
|
|
|
27
|
+
type PromptInputFilePart = FileUIPart & {
|
|
28
|
+
path?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type PromptInputFooterProps = ComponentProps<typeof InputGroupAddon>;
|
|
32
|
+
declare const PromptInputFooter: ({ align, className, ...props }: PromptInputFooterProps) => react_jsx_runtime.JSX.Element;
|
|
33
|
+
|
|
34
|
+
interface PromptInputMessage {
|
|
35
|
+
text: string;
|
|
36
|
+
files: PromptInputFilePart[];
|
|
37
|
+
}
|
|
38
|
+
type PromptInputProps = Omit<HTMLAttributes<HTMLFormElement>, "onSubmit" | "onError"> & {
|
|
39
|
+
accept?: string;
|
|
40
|
+
multiple?: boolean;
|
|
41
|
+
globalDrop?: boolean;
|
|
42
|
+
syncHiddenInput?: boolean;
|
|
43
|
+
maxFiles?: number;
|
|
44
|
+
maxFileSize?: number;
|
|
45
|
+
onError?: (err: {
|
|
46
|
+
code: "max_files" | "max_file_size" | "accept";
|
|
47
|
+
message: string;
|
|
48
|
+
}) => void;
|
|
49
|
+
onSubmit: (message: PromptInputMessage, event: FormEvent<HTMLFormElement>) => false | void | Promise<false | void>;
|
|
50
|
+
/** When provided, files are uploaded to the server immediately on add and the
|
|
51
|
+
* attachment URL is replaced with the stable server path before submit. */
|
|
52
|
+
onUploadFile?: (file: File) => Promise<{
|
|
53
|
+
url: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
}>;
|
|
56
|
+
};
|
|
57
|
+
declare const PromptInput: ({ className, accept, multiple, globalDrop, syncHiddenInput, maxFiles, maxFileSize, onError, onSubmit, onUploadFile, children, ...props }: PromptInputProps) => react_jsx_runtime.JSX.Element;
|
|
58
|
+
type PromptInputTextareaProps = ComponentProps<typeof InputGroupTextarea>;
|
|
59
|
+
declare const PromptInputTextarea: ({ onChange, onKeyDown, className, placeholder, ...props }: PromptInputTextareaProps) => react_jsx_runtime.JSX.Element;
|
|
60
|
+
type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {
|
|
61
|
+
status?: ChatStatus;
|
|
62
|
+
onStop?: () => void;
|
|
63
|
+
};
|
|
64
|
+
declare const PromptInputSubmit: ({ className, variant, size, status, onStop, onClick, children, ...props }: PromptInputSubmitProps) => react_jsx_runtime.JSX.Element;
|
|
65
|
+
|
|
26
66
|
/**
|
|
27
67
|
* Extended-thinking budget. Sent through the agent runtime to providers that
|
|
28
68
|
* support reasoning controls. 'off' means no reasoning chunks; the higher
|
|
@@ -499,7 +539,7 @@ interface ComposerBlocker {
|
|
|
499
539
|
|
|
500
540
|
type ChatSubmitSource = 'composer' | 'suggestion' | 'auto-submit';
|
|
501
541
|
interface ChatSubmitContext {
|
|
502
|
-
files:
|
|
542
|
+
files: PromptInputFilePart[];
|
|
503
543
|
sessionId: string;
|
|
504
544
|
source: ChatSubmitSource;
|
|
505
545
|
}
|
|
@@ -674,41 +714,6 @@ type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
674
714
|
};
|
|
675
715
|
declare const CodeBlock: ({ code, language, showLineNumbers, className, children, ...props }: CodeBlockProps) => react_jsx_runtime.JSX.Element;
|
|
676
716
|
|
|
677
|
-
type PromptInputFooterProps = ComponentProps<typeof InputGroupAddon>;
|
|
678
|
-
declare const PromptInputFooter: ({ align, className, ...props }: PromptInputFooterProps) => react_jsx_runtime.JSX.Element;
|
|
679
|
-
|
|
680
|
-
interface PromptInputMessage {
|
|
681
|
-
text: string;
|
|
682
|
-
files: FileUIPart[];
|
|
683
|
-
}
|
|
684
|
-
type PromptInputProps = Omit<HTMLAttributes<HTMLFormElement>, "onSubmit" | "onError"> & {
|
|
685
|
-
accept?: string;
|
|
686
|
-
multiple?: boolean;
|
|
687
|
-
globalDrop?: boolean;
|
|
688
|
-
syncHiddenInput?: boolean;
|
|
689
|
-
maxFiles?: number;
|
|
690
|
-
maxFileSize?: number;
|
|
691
|
-
onError?: (err: {
|
|
692
|
-
code: "max_files" | "max_file_size" | "accept";
|
|
693
|
-
message: string;
|
|
694
|
-
}) => void;
|
|
695
|
-
onSubmit: (message: PromptInputMessage, event: FormEvent<HTMLFormElement>) => false | void | Promise<false | void>;
|
|
696
|
-
/** When provided, files are uploaded to the server immediately on add and the
|
|
697
|
-
* attachment URL is replaced with the stable server path before submit. */
|
|
698
|
-
onUploadFile?: (file: File) => Promise<{
|
|
699
|
-
url: string;
|
|
700
|
-
path?: string;
|
|
701
|
-
}>;
|
|
702
|
-
};
|
|
703
|
-
declare const PromptInput: ({ className, accept, multiple, globalDrop, syncHiddenInput, maxFiles, maxFileSize, onError, onSubmit, onUploadFile, children, ...props }: PromptInputProps) => react_jsx_runtime.JSX.Element;
|
|
704
|
-
type PromptInputTextareaProps = ComponentProps<typeof InputGroupTextarea>;
|
|
705
|
-
declare const PromptInputTextarea: ({ onChange, onKeyDown, className, placeholder, ...props }: PromptInputTextareaProps) => react_jsx_runtime.JSX.Element;
|
|
706
|
-
type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {
|
|
707
|
-
status?: ChatStatus;
|
|
708
|
-
onStop?: () => void;
|
|
709
|
-
};
|
|
710
|
-
declare const PromptInputSubmit: ({ className, variant, size, status, onStop, onClick, children, ...props }: PromptInputSubmitProps) => react_jsx_runtime.JSX.Element;
|
|
711
|
-
|
|
712
717
|
declare function cn(...inputs: ClassValue[]): string;
|
|
713
718
|
|
|
714
719
|
export { type AgentCommandContribution, type AgentCommandOptions, ArtifactOpenProvider, ChatEmptyState, type ChatEmptyStateProps, PiChatPanel as ChatPanel, type PiChatPanelProps as ChatPanelProps, type ChatSuggestion, CodeBlock, type CommandRegistry, Conversation, ConversationContent, ConversationScrollButton, DebugDrawer, type GroupedToolEntry, Message, MessageAction, MessageActions, MessageContent, MessageResponse, type OpenArtifactHandler, type ParsedCommand, PiChatPanel, type PiChatPanelProps, SessionBrowser as PiSessionBrowser, type PiSessionCreateInit, SessionList as PiSessionList, PromptInput, PromptInputFooter, PromptInputSubmit, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, type SessionListProps, type SlashCommand, type SlashCommandContext, ToolCallGroup, type ToolPart, type ToolRenderer, type ToolRendererOverrides, type UploadFileOptions, type UploadFileResult, type UsePiSessionsOptions, type UsePiSessionsResult, activeSessionStorageKey, builtinCommands, clearActiveSessionId, cn, createCommandRegistry, defaultChatSuggestions, defaultToolRenderers, getAgentCommands, mergeShadcnToolRenderers, mergeToolRenderers, parseSlashCommand, readActiveSessionId, resolveToolRenderer, uploadFile, useOpenArtifact, usePiSessions, writeActiveSessionId };
|
package/dist/front/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
StopReceiptSchema,
|
|
14
14
|
extractToolUiMetadata,
|
|
15
15
|
sanitizeToolUiMetadata
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-G6YPXDHR.js";
|
|
17
17
|
import {
|
|
18
18
|
DebugDrawer,
|
|
19
19
|
cn,
|
|
@@ -30,7 +30,7 @@ function readAsDataUrl(file) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
async function uploadFile(file, opts = {}) {
|
|
33
|
-
const { apiBaseUrl = "", workspaceRequestId, directory, sourcePath, fetch: fetchImpl = globalThis.fetch } = opts;
|
|
33
|
+
const { apiBaseUrl = "", workspaceRequestId, directory, sourcePath, responseUrl = "markdown", fetch: fetchImpl = globalThis.fetch } = opts;
|
|
34
34
|
const dataUrl = await readAsDataUrl(file);
|
|
35
35
|
const comma = dataUrl.indexOf(",");
|
|
36
36
|
const contentBase64 = comma >= 0 ? dataUrl.slice(comma + 1) : "";
|
|
@@ -51,9 +51,20 @@ async function uploadFile(file, opts = {}) {
|
|
|
51
51
|
});
|
|
52
52
|
if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
|
|
53
53
|
const body = await res.json();
|
|
54
|
-
const
|
|
54
|
+
const path = body.path;
|
|
55
|
+
if (responseUrl === "raw") {
|
|
56
|
+
if (!path) throw new Error("Upload response missing path");
|
|
57
|
+
return { url: rawWorkspaceFileUrl(path, { apiBaseUrl, workspaceRequestId }), path };
|
|
58
|
+
}
|
|
59
|
+
const url = body.markdownUrl ?? path;
|
|
55
60
|
if (!url) throw new Error("Upload response missing url");
|
|
56
|
-
return { url, path:
|
|
61
|
+
return { url, path: path ?? url };
|
|
62
|
+
}
|
|
63
|
+
function rawWorkspaceFileUrl(path, opts) {
|
|
64
|
+
const base = (opts.apiBaseUrl ?? "").replace(/\/$/, "");
|
|
65
|
+
const params = new URLSearchParams({ path });
|
|
66
|
+
if (opts.workspaceRequestId) params.set("workspaceId", opts.workspaceRequestId);
|
|
67
|
+
return `${base}/api/v1/files/raw?${params.toString()}`;
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
// src/front/chat/PiChatPanel.tsx
|
|
@@ -291,15 +302,6 @@ var builtinCommands = [
|
|
|
291
302
|
if (ctx.openThinkingPicker?.() === false) return { preserveDraft: true };
|
|
292
303
|
}
|
|
293
304
|
},
|
|
294
|
-
{
|
|
295
|
-
name: "think",
|
|
296
|
-
description: "Alias for /thinking",
|
|
297
|
-
handler(args, ctx) {
|
|
298
|
-
const query = args.trim();
|
|
299
|
-
if (query) return ctx.selectComposerThinking?.(query);
|
|
300
|
-
if (ctx.openThinkingPicker?.() === false) return { preserveDraft: true };
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
305
|
{
|
|
304
306
|
name: "help",
|
|
305
307
|
description: "Show available commands",
|
|
@@ -2231,7 +2233,7 @@ async function resolveAttachmentUrls(files) {
|
|
|
2231
2233
|
filename: file.filename,
|
|
2232
2234
|
mediaType: file.mediaType,
|
|
2233
2235
|
url: file.url.startsWith("blob:") ? await convertBlobUrlToDataUrl(file.url) ?? file.url : file.url,
|
|
2234
|
-
...
|
|
2236
|
+
...file.path ? { path: file.path } : {}
|
|
2235
2237
|
})));
|
|
2236
2238
|
}
|
|
2237
2239
|
async function readFileAsText(file) {
|
|
@@ -2247,6 +2249,9 @@ async function readFileAsText(file) {
|
|
|
2247
2249
|
}
|
|
2248
2250
|
|
|
2249
2251
|
// src/front/chatSubmit.ts
|
|
2252
|
+
function escapeAttachmentAttr(value) {
|
|
2253
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2254
|
+
}
|
|
2250
2255
|
async function createEnrichedSubmitPayload({
|
|
2251
2256
|
text,
|
|
2252
2257
|
files,
|
|
@@ -2256,18 +2261,23 @@ async function createEnrichedSubmitPayload({
|
|
|
2256
2261
|
for (const file of files ?? []) {
|
|
2257
2262
|
const label = file.filename ?? "attachment";
|
|
2258
2263
|
const mime = file.mediaType ?? "application/octet-stream";
|
|
2259
|
-
const workspacePath =
|
|
2260
|
-
const
|
|
2261
|
-
|
|
2262
|
-
|
|
2264
|
+
const workspacePath = file.path;
|
|
2265
|
+
const attrs = [
|
|
2266
|
+
["data-boring-agent", "composer-file"],
|
|
2267
|
+
["filename", label],
|
|
2268
|
+
["mime", mime]
|
|
2269
|
+
];
|
|
2270
|
+
if (workspacePath) attrs.push(["path", workspacePath]);
|
|
2271
|
+
const attrText = attrs.map(([name, value]) => `${name}="${escapeAttachmentAttr(value)}"`).join(" ");
|
|
2263
2272
|
const content = await readFileAsText(file);
|
|
2264
2273
|
if (content !== null) {
|
|
2265
|
-
attachmentSummaries.push(
|
|
2274
|
+
attachmentSummaries.push(`<attachment ${attrText}>
|
|
2266
2275
|
\`\`\`
|
|
2267
2276
|
${content}
|
|
2268
|
-
|
|
2277
|
+
\`\`\`
|
|
2278
|
+
</attachment>`);
|
|
2269
2279
|
} else {
|
|
2270
|
-
attachmentSummaries.push(
|
|
2280
|
+
attachmentSummaries.push(`<attachment ${attrText} binary="true" />`);
|
|
2271
2281
|
}
|
|
2272
2282
|
}
|
|
2273
2283
|
const mentionNote = mentionedFiles.length > 0 ? `@files: ${mentionedFiles.join(", ")}` : null;
|
|
@@ -2500,7 +2510,8 @@ var PiComposerPolicyController = class {
|
|
|
2500
2510
|
return files.map((file) => ({
|
|
2501
2511
|
filename: file.filename,
|
|
2502
2512
|
mediaType: file.mediaType,
|
|
2503
|
-
url: file.url
|
|
2513
|
+
url: file.url,
|
|
2514
|
+
...file.path ? { path: file.path } : {}
|
|
2504
2515
|
}));
|
|
2505
2516
|
}
|
|
2506
2517
|
};
|
|
@@ -4904,7 +4915,8 @@ function toOptimisticUserMessage(payload) {
|
|
|
4904
4915
|
id: `optimistic:${payload.clientNonce}:file:${index}`,
|
|
4905
4916
|
filename: attachment.filename,
|
|
4906
4917
|
mediaType: attachment.mediaType,
|
|
4907
|
-
url: attachment.url
|
|
4918
|
+
url: attachment.url,
|
|
4919
|
+
path: attachment.path
|
|
4908
4920
|
}))
|
|
4909
4921
|
]
|
|
4910
4922
|
};
|
|
@@ -6736,6 +6748,8 @@ function PiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRen
|
|
|
6736
6748
|
const textParts = message.parts.filter((part) => part.type === "text");
|
|
6737
6749
|
const fileParts = message.parts.filter((part) => part.type === "file");
|
|
6738
6750
|
const finalParts = groupRenderableParts(message);
|
|
6751
|
+
const attachmentSummaryPaths = role === "user" ? attachmentPathsFromTextParts(textParts) : [];
|
|
6752
|
+
const openArtifact = useOpenArtifact();
|
|
6739
6753
|
const shouldReserveStreamingActions = isStreaming && isAssistant && isLast;
|
|
6740
6754
|
return /* @__PURE__ */ jsxs20(
|
|
6741
6755
|
Message,
|
|
@@ -6767,17 +6781,39 @@ function PiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRen
|
|
|
6767
6781
|
role === "user" ? "mb-2 w-full gap-2" : "gap-1.5",
|
|
6768
6782
|
role === "user" ? "[&>div]:max-w-full [&>div]:border-input/60 [&>div]:bg-background/35 [&>div]:px-1.5 [&>div]:text-secondary-foreground [&>div:hover]:bg-background/45" : void 0
|
|
6769
6783
|
),
|
|
6770
|
-
children: fileParts.map((file, index) =>
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6784
|
+
children: fileParts.map((file, index) => {
|
|
6785
|
+
const openPath = file.path ?? attachmentSummaryPaths[index];
|
|
6786
|
+
const canOpen = Boolean(openArtifact && openPath);
|
|
6787
|
+
const openAttachment = () => {
|
|
6788
|
+
if (!openPath) return;
|
|
6789
|
+
openArtifact?.(openPath);
|
|
6790
|
+
};
|
|
6791
|
+
const openAttachmentFromKeyboard = (event) => {
|
|
6792
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
6793
|
+
event.preventDefault();
|
|
6794
|
+
openAttachment();
|
|
6795
|
+
};
|
|
6796
|
+
return /* @__PURE__ */ jsxs20(
|
|
6797
|
+
Attachment,
|
|
6798
|
+
{
|
|
6799
|
+
data: toAttachmentData(file, `file-${message.id}-${index}`),
|
|
6800
|
+
...canOpen ? {
|
|
6801
|
+
role: "button",
|
|
6802
|
+
tabIndex: 0,
|
|
6803
|
+
title: `Open ${openPath} in workspace`,
|
|
6804
|
+
"aria-label": `Open ${file.filename ?? openPath} in workspace`,
|
|
6805
|
+
"data-workspace-path": openPath,
|
|
6806
|
+
onClick: openAttachment,
|
|
6807
|
+
onKeyDown: openAttachmentFromKeyboard
|
|
6808
|
+
} : void 0,
|
|
6809
|
+
children: [
|
|
6810
|
+
/* @__PURE__ */ jsx22(AttachmentPreview, { className: role === "user" ? "!size-5 shrink-0 rounded-[var(--radius-sm)]" : "size-10 shrink-0 rounded-[var(--radius-md)]" }),
|
|
6811
|
+
/* @__PURE__ */ jsx22(AttachmentInfo, { className: role === "user" ? "min-w-0 max-w-[220px] flex-1 text-[12px]" : "min-w-0 flex-1" })
|
|
6812
|
+
]
|
|
6813
|
+
},
|
|
6814
|
+
`file-${message.id}-${index}`
|
|
6815
|
+
);
|
|
6816
|
+
})
|
|
6781
6817
|
}
|
|
6782
6818
|
) : null,
|
|
6783
6819
|
finalParts.map((item) => {
|
|
@@ -6951,7 +6987,27 @@ function stripAttachmentSummaryBlocks(text) {
|
|
|
6951
6987
|
return lines.slice(0, firstSummaryIndex).join("\n").trim();
|
|
6952
6988
|
}
|
|
6953
6989
|
function isAttachmentSummaryLine(line) {
|
|
6954
|
-
|
|
6990
|
+
const trimmed = line.trim();
|
|
6991
|
+
return trimmed.startsWith("[attached: ") || trimmed.startsWith("<attachment ") && trimmed.includes('data-boring-agent="composer-file"');
|
|
6992
|
+
}
|
|
6993
|
+
function attachmentPathsFromTextParts(parts) {
|
|
6994
|
+
return parts.flatMap((part) => part.text.split("\n").flatMap((line) => {
|
|
6995
|
+
const trimmed = line.trim();
|
|
6996
|
+
const taggedPath = attachmentTagAttr(trimmed, "path");
|
|
6997
|
+
if (taggedPath) return [taggedPath];
|
|
6998
|
+
const oldInline = trimmed.match(/^\[attached: .+ Saved in workspace at: (.+?) Use the workspace file\/read tools/);
|
|
6999
|
+
if (oldInline?.[1]) return [oldInline[1]];
|
|
7000
|
+
const oldMultiline = trimmed.match(/^Saved in workspace at: (.+)$/);
|
|
7001
|
+
return oldMultiline?.[1] ? [oldMultiline[1]] : [];
|
|
7002
|
+
}));
|
|
7003
|
+
}
|
|
7004
|
+
function attachmentTagAttr(line, name) {
|
|
7005
|
+
if (!line.startsWith("<attachment ") || !line.includes('data-boring-agent="composer-file"')) return void 0;
|
|
7006
|
+
const match = line.match(new RegExp(`\\b${name}="([^"]*)"`));
|
|
7007
|
+
return match?.[1] ? unescapeAttachmentAttr(match[1]) : void 0;
|
|
7008
|
+
}
|
|
7009
|
+
function unescapeAttachmentAttr(value) {
|
|
7010
|
+
return value.replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&");
|
|
6955
7011
|
}
|
|
6956
7012
|
function MessageActionsBar({
|
|
6957
7013
|
text,
|
|
@@ -8802,11 +8858,13 @@ function PiChatComposerSurface({
|
|
|
8802
8858
|
onSubmitMessage,
|
|
8803
8859
|
onStop
|
|
8804
8860
|
}) {
|
|
8861
|
+
const workspaceRequestId = getHeaderValue(requestHeaders, "x-boring-workspace-id");
|
|
8805
8862
|
const uploadAttachment = useCallback16((file) => uploadFile(file, {
|
|
8806
8863
|
apiBaseUrl,
|
|
8807
|
-
workspaceRequestId
|
|
8864
|
+
workspaceRequestId,
|
|
8865
|
+
responseUrl: "raw",
|
|
8808
8866
|
fetch: fetch2
|
|
8809
|
-
}), [apiBaseUrl, fetch2,
|
|
8867
|
+
}), [apiBaseUrl, fetch2, workspaceRequestId]);
|
|
8810
8868
|
const resizeTextarea = useCallback16((node) => {
|
|
8811
8869
|
if (!node) return;
|
|
8812
8870
|
node.style.height = "auto";
|
|
@@ -9025,7 +9083,7 @@ function PiChatComposerSurface({
|
|
|
9025
9083
|
className: cn(
|
|
9026
9084
|
"h-8 w-8 shrink-0 rounded-full",
|
|
9027
9085
|
"bg-foreground",
|
|
9028
|
-
"
|
|
9086
|
+
"text-background",
|
|
9029
9087
|
"transition-all duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
9030
9088
|
"hover:bg-foreground/90 hover:shadow-[0_0_0_3px_oklch(from_var(--foreground)_l_c_h/0.12)] hover:scale-[1.04]",
|
|
9031
9089
|
"active:scale-[0.93] active:brightness-95",
|
|
@@ -9092,6 +9150,10 @@ function PiChatComposerSurface({
|
|
|
9092
9150
|
)
|
|
9093
9151
|
] });
|
|
9094
9152
|
}
|
|
9153
|
+
function getHeaderValue(headers, name) {
|
|
9154
|
+
if (!headers) return void 0;
|
|
9155
|
+
return headers[name] ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
|
9156
|
+
}
|
|
9095
9157
|
|
|
9096
9158
|
// src/front/chat/piChatPanelHooks.ts
|
|
9097
9159
|
import { useCallback as useCallback17, useEffect as useEffect20, useState as useState19, useSyncExternalStore } from "react";
|
|
@@ -9828,10 +9890,11 @@ function PiChatPanel({
|
|
|
9828
9890
|
}, [addLocalNotice, policy]);
|
|
9829
9891
|
const stop = useCallback18(() => {
|
|
9830
9892
|
onComposerStop?.();
|
|
9893
|
+
clearLocalSubmitted(activeChatSessionId);
|
|
9831
9894
|
void policy?.stop().catch((error) => {
|
|
9832
9895
|
addLocalNotice({ id: "stop-error", level: "error", text: errorMessage2(error, "Could not stop the chat session."), dismissible: true });
|
|
9833
9896
|
});
|
|
9834
|
-
}, [addLocalNotice, onComposerStop, policy]);
|
|
9897
|
+
}, [activeChatSessionId, addLocalNotice, clearLocalSubmitted, onComposerStop, policy]);
|
|
9835
9898
|
const interrupt = useCallback18(() => {
|
|
9836
9899
|
void policy?.interrupt().catch((error) => {
|
|
9837
9900
|
addLocalNotice({ id: "interrupt-error", level: "error", text: errorMessage2(error, "Could not interrupt the chat session."), dismissible: true });
|
package/dist/front/styles.css
CHANGED
|
@@ -1494,9 +1494,6 @@
|
|
|
1494
1494
|
.whitespace-pre-wrap {
|
|
1495
1495
|
white-space: pre-wrap;
|
|
1496
1496
|
}
|
|
1497
|
-
.\!text-background {
|
|
1498
|
-
color: var(--background) !important;
|
|
1499
|
-
}
|
|
1500
1497
|
.\!text-muted-foreground\/75 {
|
|
1501
1498
|
color: var(--muted-foreground) !important;
|
|
1502
1499
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -1578,6 +1575,9 @@
|
|
|
1578
1575
|
.text-amber-600 {
|
|
1579
1576
|
color: var(--color-amber-600);
|
|
1580
1577
|
}
|
|
1578
|
+
.text-background {
|
|
1579
|
+
color: var(--background);
|
|
1580
|
+
}
|
|
1581
1581
|
.text-destructive {
|
|
1582
1582
|
color: var(--destructive);
|
|
1583
1583
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
StopPayloadSchema,
|
|
14
14
|
extractToolUiMetadata,
|
|
15
15
|
sanitizeToolUiMetadata2 as sanitizeToolUiMetadata
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-G6YPXDHR.js";
|
|
17
17
|
|
|
18
18
|
// src/server/sandbox/direct/createDirectSandbox.ts
|
|
19
19
|
import { spawn } from "child_process";
|
|
@@ -2621,7 +2621,31 @@ function buildFileRecordsResult(args) {
|
|
|
2621
2621
|
var log2 = createLogger("boring/workspace-settings");
|
|
2622
2622
|
var BORING_SETTINGS_PATH = ".boring/settings";
|
|
2623
2623
|
var DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR = "assets/images";
|
|
2624
|
+
var DEFAULT_FILE_UPLOAD_DIR = "assets/uploads";
|
|
2624
2625
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
2626
|
+
var IMAGE_UPLOAD_EXTENSIONS = /* @__PURE__ */ new Set(["avif", "gif", "jpg", "jpeg", "png", "webp"]);
|
|
2627
|
+
var SAFE_UNKNOWN_FILE_EXTENSIONS = /* @__PURE__ */ new Set(["csv", "doc", "docx", "json", "md", "pdf", "ppt", "pptx", "rtf", "txt", "xls", "xlsx", "zip"]);
|
|
2628
|
+
var MIME_UPLOAD_EXTENSIONS = {
|
|
2629
|
+
"application/json": ["json"],
|
|
2630
|
+
"application/msword": ["doc"],
|
|
2631
|
+
"application/pdf": ["pdf"],
|
|
2632
|
+
"application/rtf": ["rtf"],
|
|
2633
|
+
"application/vnd.ms-excel": ["xls"],
|
|
2634
|
+
"application/vnd.ms-powerpoint": ["ppt"],
|
|
2635
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"],
|
|
2636
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"],
|
|
2637
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"],
|
|
2638
|
+
"application/zip": ["zip"],
|
|
2639
|
+
"image/avif": ["avif"],
|
|
2640
|
+
"image/gif": ["gif"],
|
|
2641
|
+
"image/jpeg": ["jpg", "jpeg"],
|
|
2642
|
+
"image/png": ["png"],
|
|
2643
|
+
"image/svg+xml": ["bin"],
|
|
2644
|
+
"image/webp": ["webp"],
|
|
2645
|
+
"text/csv": ["csv"],
|
|
2646
|
+
"text/markdown": ["md"],
|
|
2647
|
+
"text/plain": ["txt"]
|
|
2648
|
+
};
|
|
2625
2649
|
function defaultWorkspaceSettings() {
|
|
2626
2650
|
return { markdown: { imageUploadDir: DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR } };
|
|
2627
2651
|
}
|
|
@@ -2716,12 +2740,11 @@ function normalizeUploadDir(value) {
|
|
|
2716
2740
|
}
|
|
2717
2741
|
function extForUpload(filename, contentType) {
|
|
2718
2742
|
const fromName = extname2(filename).toLowerCase().replace(/^\./, "");
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
if (
|
|
2722
|
-
if (contentType
|
|
2723
|
-
if (contentType === "
|
|
2724
|
-
if (contentType === "image/svg+xml") return "svg";
|
|
2743
|
+
const safeFromName = /^[a-z0-9]{1,12}$/.test(fromName) ? fromName : "";
|
|
2744
|
+
const allowed = MIME_UPLOAD_EXTENSIONS[contentType];
|
|
2745
|
+
if (allowed) return safeFromName && allowed.includes(safeFromName) ? safeFromName : allowed[0] ?? "bin";
|
|
2746
|
+
if (contentType.startsWith("image/")) return safeFromName && IMAGE_UPLOAD_EXTENSIONS.has(safeFromName) ? safeFromName : "bin";
|
|
2747
|
+
if (contentType === "application/octet-stream" && safeFromName && SAFE_UNKNOWN_FILE_EXTENSIONS.has(safeFromName)) return safeFromName;
|
|
2725
2748
|
return "bin";
|
|
2726
2749
|
}
|
|
2727
2750
|
function basenameForUpload(filename) {
|
|
@@ -2751,17 +2774,32 @@ function contentTypeForPath(path4) {
|
|
|
2751
2774
|
return "image/svg+xml";
|
|
2752
2775
|
case ".webp":
|
|
2753
2776
|
return "image/webp";
|
|
2754
|
-
case ".
|
|
2755
|
-
return "text/
|
|
2756
|
-
case ".
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
return "text/javascript; charset=utf-8";
|
|
2777
|
+
case ".txt":
|
|
2778
|
+
return "text/plain; charset=utf-8";
|
|
2779
|
+
case ".md":
|
|
2780
|
+
return "text/markdown; charset=utf-8";
|
|
2781
|
+
case ".json":
|
|
2782
|
+
return "application/json; charset=utf-8";
|
|
2783
|
+
case ".csv":
|
|
2784
|
+
return "text/csv; charset=utf-8";
|
|
2763
2785
|
case ".pdf":
|
|
2764
2786
|
return "application/pdf";
|
|
2787
|
+
case ".doc":
|
|
2788
|
+
return "application/msword";
|
|
2789
|
+
case ".docx":
|
|
2790
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
2791
|
+
case ".xls":
|
|
2792
|
+
return "application/vnd.ms-excel";
|
|
2793
|
+
case ".xlsx":
|
|
2794
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
2795
|
+
case ".ppt":
|
|
2796
|
+
return "application/vnd.ms-powerpoint";
|
|
2797
|
+
case ".pptx":
|
|
2798
|
+
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
2799
|
+
case ".rtf":
|
|
2800
|
+
return "application/rtf";
|
|
2801
|
+
case ".zip":
|
|
2802
|
+
return "application/zip";
|
|
2765
2803
|
default:
|
|
2766
2804
|
return "application/octet-stream";
|
|
2767
2805
|
}
|
|
@@ -2795,7 +2833,7 @@ function fileRoutes(app, opts, done) {
|
|
|
2795
2833
|
});
|
|
2796
2834
|
}
|
|
2797
2835
|
const bytes = await workspace.readBinaryFile(path4);
|
|
2798
|
-
return reply.header("content-type", contentTypeForPath(path4)).header("content-length", String(bytes.byteLength)).header("cache-control", "no-store").send(Buffer.from(bytes));
|
|
2836
|
+
return reply.header("content-type", contentTypeForPath(path4)).header("content-length", String(bytes.byteLength)).header("cache-control", "no-store").header("x-content-type-options", "nosniff").send(Buffer.from(bytes));
|
|
2799
2837
|
} catch (err) {
|
|
2800
2838
|
return classifyError(err, reply, "file");
|
|
2801
2839
|
}
|
|
@@ -2911,16 +2949,12 @@ function fileRoutes(app, opts, done) {
|
|
|
2911
2949
|
if (filename === null) return;
|
|
2912
2950
|
const contentBase64 = requireStringParam(body?.contentBase64, "contentBase64", reply);
|
|
2913
2951
|
if (contentBase64 === null) return;
|
|
2914
|
-
const contentType = typeof body.contentType === "string" ? body.contentType.trim().toLowerCase() : "";
|
|
2915
|
-
if (!contentType.startsWith("image/")) {
|
|
2916
|
-
return reply.code(400).send({
|
|
2917
|
-
error: { code: ERROR_CODE_VALIDATION_ERROR, message: "contentType must be an image/* MIME type", field: "contentType" }
|
|
2918
|
-
});
|
|
2919
|
-
}
|
|
2952
|
+
const contentType = typeof body.contentType === "string" && body.contentType.trim() ? body.contentType.trim().toLowerCase() : "application/octet-stream";
|
|
2920
2953
|
try {
|
|
2921
2954
|
const workspace = await resolveWorkspace(request);
|
|
2922
2955
|
const settings = await readWorkspaceSettings(workspace);
|
|
2923
|
-
const
|
|
2956
|
+
const isImage = contentType.startsWith("image/");
|
|
2957
|
+
const dir = isImage ? normalizeUploadDir(body.directory) ?? normalizeUploadDir(settings.markdown?.imageUploadDir) ?? DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR : DEFAULT_FILE_UPLOAD_DIR;
|
|
2924
2958
|
const ext = extForUpload(filename, contentType);
|
|
2925
2959
|
const base = basenameForUpload(filename);
|
|
2926
2960
|
const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -5006,7 +5040,7 @@ function createVercelSandboxExec(sandbox, execOpts = {}) {
|
|
|
5006
5040
|
};
|
|
5007
5041
|
} catch (error) {
|
|
5008
5042
|
if (timedOut) {
|
|
5009
|
-
return timeoutResult(Date.now() - start);
|
|
5043
|
+
return timeoutResult(Math.max(Date.now() - start, timeoutMs));
|
|
5010
5044
|
}
|
|
5011
5045
|
throw error;
|
|
5012
5046
|
} finally {
|
|
@@ -9832,7 +9866,8 @@ function filePartsFromContent(content, messageId) {
|
|
|
9832
9866
|
id: `${messageId}:file:${index}`,
|
|
9833
9867
|
...optionalString(part.filename) ? { filename: optionalString(part.filename) } : {},
|
|
9834
9868
|
...mediaType ? { mediaType } : {},
|
|
9835
|
-
...imagePartUrl(part, mediaType) ? { url: imagePartUrl(part, mediaType) } : {}
|
|
9869
|
+
...imagePartUrl(part, mediaType) ? { url: imagePartUrl(part, mediaType) } : {},
|
|
9870
|
+
...optionalString(part.path) ? { path: optionalString(part.path) } : {}
|
|
9836
9871
|
}
|
|
9837
9872
|
];
|
|
9838
9873
|
});
|
|
@@ -10396,7 +10431,8 @@ function filePartsFromContent2(content, messageId) {
|
|
|
10396
10431
|
id: `${messageId}:file:${index}`,
|
|
10397
10432
|
...optionalString2(part.filename) ? { filename: optionalString2(part.filename) } : {},
|
|
10398
10433
|
...mediaType ? { mediaType } : {},
|
|
10399
|
-
...url ? { url } : {}
|
|
10434
|
+
...url ? { url } : {},
|
|
10435
|
+
...optionalString2(part.path) ? { path: optionalString2(part.path) } : {}
|
|
10400
10436
|
}];
|
|
10401
10437
|
});
|
|
10402
10438
|
}
|
|
@@ -10490,11 +10526,13 @@ var PiChatMessageMetadataReconciler = class {
|
|
|
10490
10526
|
recordPrompt(sessionId, payload) {
|
|
10491
10527
|
const metadata = this.promptMetadata.get(sessionId) ?? [];
|
|
10492
10528
|
const displayText = payload.displayMessage ?? payload.message;
|
|
10529
|
+
const files = filePartsFromPromptPayload(payload);
|
|
10493
10530
|
metadata.push({
|
|
10494
10531
|
displayText,
|
|
10495
10532
|
...displayText !== payload.message ? { serverText: payload.message } : {},
|
|
10496
10533
|
clientNonce: payload.clientNonce,
|
|
10497
|
-
recordedAt: Date.now()
|
|
10534
|
+
recordedAt: Date.now(),
|
|
10535
|
+
...files ? { files } : {}
|
|
10498
10536
|
});
|
|
10499
10537
|
this.promptMetadata.set(sessionId, metadata);
|
|
10500
10538
|
}
|
|
@@ -10561,9 +10599,16 @@ var PiChatMessageMetadataReconciler = class {
|
|
|
10561
10599
|
}
|
|
10562
10600
|
if (event.type === "message-start" && event.role === "user" && !hasFollowUpSelector2(event)) {
|
|
10563
10601
|
const promptMetadata = this.findPrompt(sessionId, event.text);
|
|
10564
|
-
if (promptMetadata) return
|
|
10602
|
+
if (promptMetadata) return withMessageStartMetadata(event, promptMetadata);
|
|
10565
10603
|
const metadata = this.findFollowUp(sessionId, event.text);
|
|
10566
|
-
if (metadata) return
|
|
10604
|
+
if (metadata) return withMessageStartMetadata(event, metadata);
|
|
10605
|
+
}
|
|
10606
|
+
if (event.type === "message-end" && event.final.role === "user" && !hasMessageSelector(event.final)) {
|
|
10607
|
+
const text = messageText2(event.final);
|
|
10608
|
+
const promptMetadata = this.findPrompt(sessionId, text);
|
|
10609
|
+
if (promptMetadata) return { ...event, final: withMessageSelector(event.final, promptMetadata) };
|
|
10610
|
+
const metadata = this.findFollowUp(sessionId, text);
|
|
10611
|
+
if (metadata) return { ...event, final: withMessageSelector(event.final, metadata) };
|
|
10567
10612
|
}
|
|
10568
10613
|
return event;
|
|
10569
10614
|
}
|
|
@@ -10642,13 +10687,24 @@ var PiChatMessageMetadataReconciler = class {
|
|
|
10642
10687
|
return this.promptMetadata.get(sessionId)?.find((entry) => matchesMetadataText(entry, text));
|
|
10643
10688
|
}
|
|
10644
10689
|
consumePromptFromEvent(sessionId, event) {
|
|
10645
|
-
if (event.type
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10690
|
+
if (event.type === "message-start" && event.role === "user") {
|
|
10691
|
+
if (event.clientSeq !== void 0) return;
|
|
10692
|
+
const prompt = event.clientNonce ? this.promptMetadata.get(sessionId)?.find((entry) => entry.clientNonce === event.clientNonce) : this.findPrompt(sessionId, event.text);
|
|
10693
|
+
if (prompt?.serverText) return;
|
|
10694
|
+
if (event.clientNonce) {
|
|
10695
|
+
this.removePrompt(sessionId, { clientNonce: event.clientNonce });
|
|
10696
|
+
return;
|
|
10697
|
+
}
|
|
10698
|
+
this.removePrompt(sessionId, { displayText: event.text });
|
|
10649
10699
|
return;
|
|
10650
10700
|
}
|
|
10651
|
-
|
|
10701
|
+
if (event.type !== "message-end" || event.final.role !== "user") return;
|
|
10702
|
+
if (event.final.clientSeq !== void 0) return;
|
|
10703
|
+
if (event.final.clientNonce) {
|
|
10704
|
+
this.removePrompt(sessionId, { clientNonce: event.final.clientNonce });
|
|
10705
|
+
return;
|
|
10706
|
+
}
|
|
10707
|
+
this.removePrompt(sessionId, { displayText: messageText2(event.final) });
|
|
10652
10708
|
}
|
|
10653
10709
|
removeFirstFollowUpByText(sessionId, text) {
|
|
10654
10710
|
if (!text) return;
|
|
@@ -10702,9 +10758,18 @@ function matchesMetadataText(entry, text) {
|
|
|
10702
10758
|
if (!entry || !text) return false;
|
|
10703
10759
|
return entry.displayText === text || entry.serverText === text;
|
|
10704
10760
|
}
|
|
10761
|
+
function withMessageStartMetadata(event, metadata) {
|
|
10762
|
+
return {
|
|
10763
|
+
...event,
|
|
10764
|
+
text: metadata.displayText,
|
|
10765
|
+
...metadata.clientNonce ? { clientNonce: metadata.clientNonce } : {},
|
|
10766
|
+
...metadata.clientSeq !== void 0 ? { clientSeq: metadata.clientSeq } : {},
|
|
10767
|
+
...metadata.files && metadata.files.length > 0 ? { files: metadata.files } : {}
|
|
10768
|
+
};
|
|
10769
|
+
}
|
|
10705
10770
|
function withMessageSelector(message, metadata) {
|
|
10706
10771
|
return {
|
|
10707
|
-
...withMessageDisplayText(message, metadata.displayText),
|
|
10772
|
+
...withMessageFiles(withMessageDisplayText(message, metadata.displayText), metadata.files),
|
|
10708
10773
|
...metadata.clientNonce ? { clientNonce: metadata.clientNonce } : {},
|
|
10709
10774
|
...metadata.clientSeq !== void 0 ? { clientSeq: metadata.clientSeq } : {}
|
|
10710
10775
|
};
|
|
@@ -10719,6 +10784,22 @@ function withMessageDisplayText(message, displayText) {
|
|
|
10719
10784
|
});
|
|
10720
10785
|
return { ...message, parts };
|
|
10721
10786
|
}
|
|
10787
|
+
function withMessageFiles(message, files) {
|
|
10788
|
+
if (!files || files.length === 0) return message;
|
|
10789
|
+
const nonFileParts = message.parts.filter((part) => part.type !== "file");
|
|
10790
|
+
return { ...message, parts: [...nonFileParts, ...files] };
|
|
10791
|
+
}
|
|
10792
|
+
function filePartsFromPromptPayload(payload) {
|
|
10793
|
+
if (!payload.attachments || payload.attachments.length === 0) return void 0;
|
|
10794
|
+
return payload.attachments.map((attachment, index) => ({
|
|
10795
|
+
type: "file",
|
|
10796
|
+
id: `prompt:${payload.clientNonce}:file:${index}`,
|
|
10797
|
+
filename: attachment.filename,
|
|
10798
|
+
mediaType: attachment.mediaType,
|
|
10799
|
+
url: attachment.url,
|
|
10800
|
+
path: attachment.path
|
|
10801
|
+
}));
|
|
10802
|
+
}
|
|
10722
10803
|
function withQueuedSelector(followUp, metadata) {
|
|
10723
10804
|
return {
|
|
10724
10805
|
...followUp,
|
|
@@ -11216,10 +11297,13 @@ var PiChatMeteringCoordinator = class {
|
|
|
11216
11297
|
};
|
|
11217
11298
|
|
|
11218
11299
|
// src/server/pi-chat/harnessPiChatService.ts
|
|
11300
|
+
var MAX_PROMPT_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
11301
|
+
var PROMPT_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".avif", ".gif", ".jpg", ".jpeg", ".png", ".webp"]);
|
|
11219
11302
|
var HarnessPiChatService = class {
|
|
11220
11303
|
harness;
|
|
11221
11304
|
sessionStore;
|
|
11222
11305
|
workdir;
|
|
11306
|
+
workspace;
|
|
11223
11307
|
channels = /* @__PURE__ */ new Map();
|
|
11224
11308
|
// Single-flight guard so concurrent cold callers (e.g. two browser tabs each
|
|
11225
11309
|
// opening /events while the session is still being created) converge on one
|
|
@@ -11235,6 +11319,7 @@ var HarnessPiChatService = class {
|
|
|
11235
11319
|
this.harness = options.harness;
|
|
11236
11320
|
this.sessionStore = options.sessionStore;
|
|
11237
11321
|
this.workdir = options.workdir;
|
|
11322
|
+
this.workspace = options.workspace;
|
|
11238
11323
|
this.metering = options.metering ? new PiChatMeteringCoordinator(options.metering, options.meteringLogger) : void 0;
|
|
11239
11324
|
}
|
|
11240
11325
|
/** Test/diagnostic hook: resolves once queued metering sink calls settle. */
|
|
@@ -11328,7 +11413,8 @@ var HarnessPiChatService = class {
|
|
|
11328
11413
|
const channel = this.channels.get(sessionId);
|
|
11329
11414
|
const receiptCursor = nextPromptReceiptCursor(channel);
|
|
11330
11415
|
try {
|
|
11331
|
-
const
|
|
11416
|
+
const input = await toPiPromptInput(payload, this.workspace);
|
|
11417
|
+
const run2 = this.trackActiveRun(sessionId, adapter.prompt(input));
|
|
11332
11418
|
run2.catch((error) => {
|
|
11333
11419
|
this.metering?.failPromptRun(sessionId, payload.clientNonce);
|
|
11334
11420
|
if (!this.messageMetadata.hasPrompt(sessionId, { clientNonce: payload.clientNonce, displayText: payload.displayMessage ?? payload.message })) return;
|
|
@@ -11628,7 +11714,8 @@ function promptPayloadFileParts(payload, messageId) {
|
|
|
11628
11714
|
id: `${messageId}:file:${index}`,
|
|
11629
11715
|
filename: attachment.filename,
|
|
11630
11716
|
mediaType: attachment.mediaType,
|
|
11631
|
-
url: attachment.url
|
|
11717
|
+
url: attachment.url,
|
|
11718
|
+
path: attachment.path
|
|
11632
11719
|
}));
|
|
11633
11720
|
}
|
|
11634
11721
|
function promptPayloadMessage(payload, messageId, createdAt, turnId) {
|
|
@@ -11666,21 +11753,64 @@ function messageTime(message) {
|
|
|
11666
11753
|
const timestamp = Date.parse(message.createdAt);
|
|
11667
11754
|
return Number.isFinite(timestamp) ? timestamp : void 0;
|
|
11668
11755
|
}
|
|
11669
|
-
function toPiPromptInput(payload) {
|
|
11670
|
-
const images = promptImagesFromAttachments(payload.attachments);
|
|
11756
|
+
async function toPiPromptInput(payload, workspace) {
|
|
11757
|
+
const images = await promptImagesFromAttachments(payload.attachments, workspace);
|
|
11671
11758
|
if (images.length === 0) return payload.message;
|
|
11672
11759
|
return { text: payload.message, options: { images } };
|
|
11673
11760
|
}
|
|
11674
|
-
function promptImagesFromAttachments(attachments) {
|
|
11761
|
+
async function promptImagesFromAttachments(attachments, workspace) {
|
|
11675
11762
|
const images = [];
|
|
11676
11763
|
for (const attachment of attachments ?? []) {
|
|
11677
11764
|
const match = attachment.url.match(/^data:(image\/[^;]+);base64,(.+)$/);
|
|
11678
|
-
if (
|
|
11679
|
-
|
|
11680
|
-
|
|
11765
|
+
if (match) {
|
|
11766
|
+
const [, mimeType, data] = match;
|
|
11767
|
+
images.push({ type: "image", mimeType, data });
|
|
11768
|
+
continue;
|
|
11769
|
+
}
|
|
11770
|
+
if (!workspace?.readBinaryFile || !isWorkspaceImageAttachment(attachment)) continue;
|
|
11771
|
+
try {
|
|
11772
|
+
const stat11 = await workspace.stat(attachment.path);
|
|
11773
|
+
if (stat11.kind !== "file" || stat11.size <= 0 || stat11.size > MAX_PROMPT_IMAGE_BYTES) continue;
|
|
11774
|
+
const bytes = await workspace.readBinaryFile(attachment.path);
|
|
11775
|
+
if (bytes.byteLength <= 0 || bytes.byteLength > MAX_PROMPT_IMAGE_BYTES) continue;
|
|
11776
|
+
const detectedMimeType = detectPromptImageMimeType(bytes);
|
|
11777
|
+
if (!detectedMimeType) continue;
|
|
11778
|
+
images.push({
|
|
11779
|
+
type: "image",
|
|
11780
|
+
mimeType: detectedMimeType,
|
|
11781
|
+
data: Buffer.from(bytes).toString("base64")
|
|
11782
|
+
});
|
|
11783
|
+
} catch {
|
|
11784
|
+
}
|
|
11681
11785
|
}
|
|
11682
11786
|
return images;
|
|
11683
11787
|
}
|
|
11788
|
+
function isWorkspaceImageAttachment(attachment) {
|
|
11789
|
+
if (!attachment.mediaType?.startsWith("image/") || !attachment.path) return false;
|
|
11790
|
+
const lowerPath = attachment.path.toLowerCase();
|
|
11791
|
+
return [...PROMPT_IMAGE_EXTENSIONS].some((ext) => lowerPath.endsWith(ext));
|
|
11792
|
+
}
|
|
11793
|
+
function detectPromptImageMimeType(bytes) {
|
|
11794
|
+
const buffer = Buffer.from(bytes);
|
|
11795
|
+
if (buffer.length >= 8 && buffer.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
11796
|
+
return "image/png";
|
|
11797
|
+
}
|
|
11798
|
+
if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
|
|
11799
|
+
return "image/jpeg";
|
|
11800
|
+
}
|
|
11801
|
+
if (buffer.length >= 6) {
|
|
11802
|
+
const gif = buffer.subarray(0, 6).toString("ascii");
|
|
11803
|
+
if (gif === "GIF87a" || gif === "GIF89a") return "image/gif";
|
|
11804
|
+
}
|
|
11805
|
+
if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
11806
|
+
return "image/webp";
|
|
11807
|
+
}
|
|
11808
|
+
if (buffer.length >= 12 && buffer.subarray(4, 8).toString("ascii") === "ftyp") {
|
|
11809
|
+
const brand = buffer.subarray(8, 12).toString("ascii");
|
|
11810
|
+
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
11811
|
+
}
|
|
11812
|
+
return null;
|
|
11813
|
+
}
|
|
11684
11814
|
function removedFollowUps(before, after) {
|
|
11685
11815
|
const afterCounts = /* @__PURE__ */ new Map();
|
|
11686
11816
|
for (const text of after) afterCounts.set(text, (afterCounts.get(text) ?? 0) + 1);
|
|
@@ -11837,6 +11967,7 @@ async function createAgentApp(opts = {}) {
|
|
|
11837
11967
|
harness,
|
|
11838
11968
|
sessionStore: harness.sessions,
|
|
11839
11969
|
workdir: runtimeBundle.workspace.root,
|
|
11970
|
+
workspace: runtimeBundle.workspace,
|
|
11840
11971
|
metering: opts.metering
|
|
11841
11972
|
});
|
|
11842
11973
|
await app.register(piChatRoutes, { service: piChatService });
|
|
@@ -12074,6 +12205,17 @@ function selectRuntimeModeAdapter(mode, sandboxHandleStore) {
|
|
|
12074
12205
|
function getRequestWorkspaceId(request) {
|
|
12075
12206
|
return request.workspaceContext?.workspaceId ?? DEFAULT_WORKSPACE_ID3;
|
|
12076
12207
|
}
|
|
12208
|
+
function promoteRawFileWorkspaceQueryToHeader(request) {
|
|
12209
|
+
const pathname = request.url.split("?")[0] ?? request.url;
|
|
12210
|
+
if (pathname !== "/api/v1/files/raw") return;
|
|
12211
|
+
const hasWorkspaceHeader = Object.keys(request.headers).some((key) => key.toLowerCase() === "x-boring-workspace-id");
|
|
12212
|
+
if (hasWorkspaceHeader) return;
|
|
12213
|
+
const queryIndex = request.url.indexOf("?");
|
|
12214
|
+
if (queryIndex < 0) return;
|
|
12215
|
+
const workspaceId = new URLSearchParams(request.url.slice(queryIndex + 1)).get("workspaceId")?.trim();
|
|
12216
|
+
if (!workspaceId) return;
|
|
12217
|
+
request.headers["x-boring-workspace-id"] = workspaceId;
|
|
12218
|
+
}
|
|
12077
12219
|
function isWorkspaceAgnosticAgentRequest(request, options) {
|
|
12078
12220
|
const pathname = request.url.split("?")[0] ?? request.url;
|
|
12079
12221
|
if (pathname === "/api/v1/ready-status") return !options?.readyStatusWorkspaceScoped;
|
|
@@ -12566,6 +12708,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12566
12708
|
app.addHook("onRequest", async (request, reply) => {
|
|
12567
12709
|
const user = request.user;
|
|
12568
12710
|
let workspaceId = DEFAULT_WORKSPACE_ID3;
|
|
12711
|
+
promoteRawFileWorkspaceQueryToHeader(request);
|
|
12569
12712
|
if (opts.getWorkspaceId && !isWorkspaceAgnosticAgentRequest(request, { readyStatusWorkspaceScoped: requestScopedRuntime })) {
|
|
12570
12713
|
try {
|
|
12571
12714
|
workspaceId = (await opts.getWorkspaceId(request)).trim();
|
|
@@ -12626,6 +12769,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12626
12769
|
harness: binding.harness,
|
|
12627
12770
|
sessionStore: binding.harness.sessions,
|
|
12628
12771
|
workdir: binding.runtimeBundle.workspace.root,
|
|
12772
|
+
workspace: binding.runtimeBundle.workspace,
|
|
12629
12773
|
metering: opts.metering
|
|
12630
12774
|
});
|
|
12631
12775
|
return binding.piChatService;
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../agentPluginEvents-Dgm57gEU.js';
|
|
2
2
|
export { a as AgentHarness, C as CommandNotifyPayload, E as Entry, b as ExecOptions, c as ExecResult, I as IsolatedCodeInput, d as IsolatedCodeOutput, J as JSONSchema, R as RunContext, e as SandboxCapability, f as SandboxHandleRecord, g as SandboxHandleStore, h as SendMessageInput, i as Stat, T as TelemetryEvent, j as TelemetrySink, k as ToolExecContext, l as ToolResult, m as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, n as WORKSPACE_COMMAND_NOTIFY_EVENT, o as WorkspaceRuntimeContext, p as noopTelemetry, s as safeCapture } from '../agentPluginEvents-Dgm57gEU.js';
|
|
3
|
-
import { B as BoringChatPart, T as ToolUiMetadata } from '../piChatEvent-
|
|
4
|
-
export { A as ApiErrorPayload, a as ApiErrorPayloadSchema, b as ApiErrorResponse, c as ApiErrorResponseSchema, d as BoringChatMessage, C as ChatError, e as CommandReceipt, E as ERROR_CODES, f as ErrorCode, g as ErrorLogFields, h as ErrorLogFieldsSchema, F as FollowUpPayload, i as FollowUpReceipt, I as InterruptPayload, j as InterruptReceipt, P as PiChatEvent, k as PiChatHeartbeatFrame, l as PiChatSnapshot, m as PiChatStatus, n as PiChatStreamFrame, o as PromptPayload, p as PromptReceipt, Q as QueueClearPayload, q as QueueClearReceipt, r as QueuedUserMessage, S as StopPayload, s as StopReceipt, t as extractToolUiMetadata, u as isToolUiMetadata } from '../piChatEvent-
|
|
3
|
+
import { B as BoringChatPart, T as ToolUiMetadata } from '../piChatEvent-B6GDifo2.js';
|
|
4
|
+
export { A as ApiErrorPayload, a as ApiErrorPayloadSchema, b as ApiErrorResponse, c as ApiErrorResponseSchema, d as BoringChatMessage, C as ChatError, e as CommandReceipt, E as ERROR_CODES, f as ErrorCode, g as ErrorLogFields, h as ErrorLogFieldsSchema, F as FollowUpPayload, i as FollowUpReceipt, I as InterruptPayload, j as InterruptReceipt, P as PiChatEvent, k as PiChatHeartbeatFrame, l as PiChatSnapshot, m as PiChatStatus, n as PiChatStreamFrame, o as PromptPayload, p as PromptReceipt, Q as QueueClearPayload, q as QueueClearReceipt, r as QueuedUserMessage, S as StopPayload, s as StopReceipt, t as extractToolUiMetadata, u as isToolUiMetadata } from '../piChatEvent-B6GDifo2.js';
|
|
5
5
|
export { C as ChatAttachmentPayload, a as ChatModelSelection, b as ChatSubmitPayload, S as SessionCtx, c as SessionDetail, d as SessionStore, e as SessionSummary, T as ThinkingLevel } from '../chatSubmitPayload-DHqQL2wD.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
|
package/dist/shared/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"use-stick-to-bottom": "^1.1.3",
|
|
75
75
|
"yaml": "^2.8.3",
|
|
76
76
|
"zod": "^3.25.76",
|
|
77
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
77
|
+
"@hachej/boring-ui-kit": "0.1.50"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@antithesishq/bombadil": "0.5.0",
|