Skip to content

Make file write labeling consistent #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
prettier
  • Loading branch information
courtney-sims committed Apr 21, 2025
commit 03e20caaeab6c96e585344a06fcd3247cb00cad0
97 changes: 49 additions & 48 deletions apps/sandbox-container/container/fileUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import mime from "mime"
import mock from "mock-fs"
import mime from 'mime'
import mock from 'mock-fs'
import { afterEach, describe, expect, it, vi } from 'vitest'

import { get_file_name_from_path, get_mime_type, list_files_in_directory } from './fileUtils'

vi.mock('mime', () => {
return{
return {
default: {
getType: vi.fn()
}
getType: vi.fn(),
},
}
})

afterEach(async () => {
mock.restore()
vi.restoreAllMocks();
vi.restoreAllMocks()
})

describe('get_file_name_from_path', () => {
Expand All @@ -30,49 +31,49 @@ describe('get_file_name_from_path', () => {
expect(path).toBe('/birds')
})
}),
describe('list_files_in_directory', () => {
it('lists the files in a directory', async () => {
mock({
testDir: {
"cats": "aurora, luna",
"dogs": "penny",
}
})
const listFiles = await list_files_in_directory("testDir")
expect(listFiles).toEqual(["file:///testDir/cats", "file:///testDir/dogs"])
describe('list_files_in_directory', () => {
it('lists the files in a directory', async () => {
mock({
testDir: {
cats: 'aurora, luna',
dogs: 'penny',
},
})
const listFiles = await list_files_in_directory('testDir')
expect(listFiles).toEqual(['file:///testDir/cats', 'file:///testDir/dogs'])
}),
it('throws an error if path is not a directory', async () => {
mock({
testDir: {
cats: 'aurora, luna',
dogs: 'penny',
},
})
await expect(async () => await list_files_in_directory('testDir/cats')).rejects.toThrow(
'Failed to read directory'
)
}),
it('treats empty strings as cwd', async () => {
mock({
testDir: {
cats: 'aurora, luna',
dogs: 'penny',
},
})

const listFiles = await list_files_in_directory('')
expect(listFiles).toEqual(['file:///../../../../../../testDir'])
})
}),
it('throws an error if path is not a directory', async () => {
mock({
testDir: {
"cats": "aurora, luna",
"dogs": "penny",
}
describe('get_mime_type', async () => {
it("provides the natural mime type when not 'inode/directory'", async () => {
vi.mocked(mime.getType).mockReturnValueOnce('theType')
const mimeType = await get_mime_type('someFile')
expect(mimeType).toEqual('theType')
})
await expect(async () => await list_files_in_directory("testDir/cats")).rejects.toThrow("Failed to read directory")
}),
it('treats empty strings as cwd', async () => {
mock({
testDir: {
"cats": "aurora, luna",
"dogs": "penny",
}
it("overrides mime type for 'inode/directory'", async () => {
vi.mocked(mime.getType).mockReturnValueOnce('inode/directory')
const mimeType = await get_mime_type('someDirectory')
expect(mimeType).toEqual('text/directory')
})

const listFiles = await list_files_in_directory("")
expect(listFiles).toEqual(["file:///../../../../../../testDir"])
})
}),
describe("get_mime_type", async () => {
it("provides the natural mime type when not 'inode/directory'", async () => {
vi.mocked(mime.getType).mockReturnValueOnce("theType")
const mimeType = await get_mime_type("someFile")
expect(mimeType).toEqual("theType")
})
it("overrides mime type for 'inode/directory'", async () => {
vi.mocked(mime.getType).mockReturnValueOnce("inode/directory")
const mimeType = await get_mime_type("someDirectory")
expect(mimeType).toEqual("text/directory")
})
}
)

20 changes: 10 additions & 10 deletions apps/sandbox-container/container/fileUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import mime from 'mime';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import * as fs from 'node:fs/promises'
import path from 'node:path'
import mime from 'mime'

// this is because there isn't a "real" directory mime type, so we're reusing the "text/directory" mime type
// so claude doesn't give an error
export const DIRECTORY_CONTENT_TYPE = 'text/directory';
export const DIRECTORY_CONTENT_TYPE = 'text/directory'

export async function get_file_name_from_path(path: string): Promise<string> {
path = path.replace('/files/contents', '')
Expand All @@ -13,7 +13,7 @@ export async function get_file_name_from_path(path: string): Promise<string> {
return path
}

export async function list_files_in_directory(dirPath: string): Promise<string[]>{
export async function list_files_in_directory(dirPath: string): Promise<string[]> {
const files: string[] = []
try {
const dir = await fs.readdir(path.join(process.cwd(), dirPath), {
Expand All @@ -23,16 +23,16 @@ export async function list_files_in_directory(dirPath: string): Promise<string[]
const relPath = path.relative(process.cwd(), `${dirPath}/${dirent.name}`)
files.push(`file:///${relPath}`)
}
} catch(error) {
throw new Error('Failed to read directory');
} catch (error) {
throw new Error('Failed to read directory')
}

return files
}

export async function get_mime_type(path: string): Promise<string | null>{
export async function get_mime_type(path: string): Promise<string | null> {
let mimeType = mime.getType(path)
if (mimeType && (mimeType === 'inode/directory')) {
if (mimeType && mimeType === 'inode/directory') {
mimeType = DIRECTORY_CONTENT_TYPE
}
return mimeType
Expand Down
8 changes: 4 additions & 4 deletions apps/sandbox-container/container/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { exec } from 'node:child_process'
import * as fs from 'node:fs/promises'
import path from 'node:path'
import { serve } from '@hono/node-server'
import { zValidator } from '@hono/zod-validator'
import { Hono } from 'hono'
import { streamText } from 'hono/streaming'
import mime from 'mime'
import { exec } from 'node:child_process'
import * as fs from 'node:fs/promises'
import path from 'node:path'

import { ExecParams, FilesWrite } from '../shared/schema.ts'
import {
Expand Down Expand Up @@ -70,7 +70,7 @@ app.get('/files/ls', async (c) => {
app.get('/files/contents/*', async (c) => {
const reqPath = await get_file_name_from_path(c.req.path)
try {
const mimeType = await get_mime_type(reqPath)
const mimeType = await get_mime_type(reqPath)
const headers = mimeType ? { 'Content-Type': mimeType } : undefined
const contents = await fs.readFile(path.join(process.cwd(), reqPath))
return c.newResponse(contents, 200, headers)
Expand Down
2 changes: 1 addition & 1 deletion apps/sandbox-container/server/containerMcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { getContainerManager } from './containerManager'
import { BASE_INSTRUCTIONS } from './prompts'
import { fileToBase64, stripProtocolFromFilePath } from './utils'

import type { Env, Props } from '.'
import type { FileList } from '../shared/schema'
import type { Env, Props } from '.'

export class ContainerMcpAgent extends McpAgent<Env, Props> {
server = new McpServer(
Expand Down