feat: tool timeout auto-retry, file tree attachment, force stop button
- Add configurable tool timeout with auto-retry: when a tool exceeds the timeout threshold, abort and send a follow-up message asking Claude to try a different approach - Add "+" button on file tree items to attach files directly to chat input via new /api/files/raw endpoint and custom event bridge - Add force stop button in streaming status bar when tool runs >90s, with warning indicators at 60s - Improve doc preview markdown rendering (padding, list indentation) - Bump version to 0.10.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
8747464c2f
commit
4fa26032a4
15 files changed
+314
-16
No files matched your search
@@ -10,8 +10,8 @@ export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body: SendMessageRequest & { files?: FileAttachment[] } = await request.json();
|
||||
const { session_id, content, model, mode, files } = body;
|
||||
const body: SendMessageRequest & { files?: FileAttachment[]; toolTimeout?: number } = await request.json();
|
||||
const { session_id, content, model, mode, files, toolTimeout } = body;
|
||||
|
||||
if (!session_id || !content) {
|
||||
return new Response(JSON.stringify({ error: 'session_id and content are required' }), {
|
||||
@@ -103,6 +103,7 @@ export async function POST(request: NextRequest) {
|
||||
abortController,
|
||||
permissionMode,
|
||||
files: fileAttachments,
|
||||
toolTimeoutSeconds: toolTimeout || 120,
|
||||
});
|
||||
|
||||
// Tee the stream: one for client, one for collecting the response
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { NextRequest } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { isPathSafe } from '@/lib/files';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const MIME_TYPES: Record<string, string> = {
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.avif': 'image/avif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ico': 'image/x-icon',
|
||||
'.bmp': 'image/bmp',
|
||||
'.pdf': 'application/pdf',
|
||||
'.txt': 'text/plain',
|
||||
'.md': 'text/markdown',
|
||||
'.mdx': 'text/markdown',
|
||||
'.json': 'application/json',
|
||||
'.csv': 'text/csv',
|
||||
'.xml': 'text/xml',
|
||||
'.html': 'text/html',
|
||||
'.htm': 'text/html',
|
||||
'.css': 'text/css',
|
||||
'.js': 'application/javascript',
|
||||
'.ts': 'application/typescript',
|
||||
'.tsx': 'application/typescript',
|
||||
'.jsx': 'application/javascript',
|
||||
'.py': 'text/x-python',
|
||||
'.go': 'text/x-go',
|
||||
'.rs': 'text/x-rust',
|
||||
'.java': 'text/x-java',
|
||||
'.rb': 'text/x-ruby',
|
||||
'.sh': 'text/x-shellscript',
|
||||
'.yaml': 'text/yaml',
|
||||
'.yml': 'text/yaml',
|
||||
'.toml': 'text/toml',
|
||||
'.sql': 'text/x-sql',
|
||||
'.swift': 'text/x-swift',
|
||||
'.kt': 'text/x-kotlin',
|
||||
'.c': 'text/x-c',
|
||||
'.cpp': 'text/x-c++',
|
||||
'.h': 'text/x-c',
|
||||
'.hpp': 'text/x-c++',
|
||||
'.cs': 'text/x-csharp',
|
||||
'.php': 'text/x-php',
|
||||
'.dart': 'text/x-dart',
|
||||
'.lua': 'text/x-lua',
|
||||
'.zig': 'text/x-zig',
|
||||
'.vue': 'text/x-vue',
|
||||
'.svelte': 'text/x-svelte',
|
||||
'.graphql': 'text/x-graphql',
|
||||
'.gql': 'text/x-graphql',
|
||||
'.prisma': 'text/x-prisma',
|
||||
'.dockerfile': 'text/x-dockerfile',
|
||||
'.scss': 'text/x-scss',
|
||||
'.less': 'text/x-less',
|
||||
'.doc': 'application/msword',
|
||||
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'.xls': 'application/vnd.ms-excel',
|
||||
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'.ppt': 'application/vnd.ms-powerpoint',
|
||||
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'.zip': 'application/zip',
|
||||
'.tar': 'application/x-tar',
|
||||
'.gz': 'application/gzip',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.wav': 'audio/wav',
|
||||
'.mp4': 'video/mp4',
|
||||
'.mov': 'video/quicktime',
|
||||
'.webm': 'video/webm',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
'.ttf': 'font/ttf',
|
||||
'.otf': 'font/otf',
|
||||
};
|
||||
|
||||
/**
|
||||
* Serve raw file content from the user's home directory.
|
||||
* Security: only allows reading files within the user's home directory.
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const filePath = request.nextUrl.searchParams.get('path');
|
||||
|
||||
if (!filePath) {
|
||||
return new Response(JSON.stringify({ error: 'path parameter is required' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
const homeDir = os.homedir();
|
||||
const resolved = path.resolve(filePath);
|
||||
|
||||
if (!isPathSafe(homeDir, resolved)) {
|
||||
return new Response(JSON.stringify({ error: 'Access denied' }), {
|
||||
status: 403,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
if (!fs.existsSync(resolved)) {
|
||||
return new Response(JSON.stringify({ error: 'File not found' }), {
|
||||
status: 404,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
const stat = fs.statSync(resolved);
|
||||
if (!stat.isFile()) {
|
||||
return new Response(JSON.stringify({ error: 'Not a file' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
const buffer = fs.readFileSync(resolved);
|
||||
const ext = path.extname(resolved).toLowerCase();
|
||||
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
||||
|
||||
return new Response(buffer, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Content-Disposition': `inline; filename="${path.basename(resolved)}"`,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user