feat(client): expose v2 project APIs (#34456)

This commit is contained in:
Kit Langton 2026-06-29 14:10:11 -04:00 committed by GitHub
commit 935ac2db91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 473 additions and 244 deletions

View file

@ -88,6 +88,18 @@ const call = (input: typeof ShellTool.Input.Type, id = "call-shell") => ({
call: { type: "tool-call" as const, id, name: "shell", input },
})
const isWindows = process.platform === "win32"
const cwdCommand = isWindows ? "(Get-Location).Path; Start-Sleep -Milliseconds 100" : "pwd"
const helloCommand = isWindows ? "[Console]::Out.Write('hello'); Start-Sleep -Milliseconds 100" : "printf hello"
const idleCommand = isWindows ? "Start-Sleep -Seconds 60" : "sleep 60"
const bodyExitCommand = isWindows
? "[Console]::Out.Write('body'); Start-Sleep -Milliseconds 100; exit 7"
: "printf body && exit 7"
const overflowCommand = (bytes: number) =>
isWindows
? `[Console]::Out.Write(('x' * ${bytes})); Start-Sleep -Milliseconds 100`
: `head -c ${bytes} /dev/zero | tr '\\0' 'x'`
const it = testEffect(Layer.empty)
describe("ShellTool", () => {
@ -103,14 +115,14 @@ describe("ShellTool", () => {
expect(definitions[0]?.outputSchema).not.toHaveProperty("properties.output")
expect(yield* toolDefinitions(registry, [{ action: "shell", resource: "*", effect: "deny" }])).toEqual([])
const settled = yield* settleTool(registry, call({ command: "printf hello" }))
const settled = yield* settleTool(registry, call({ command: helloCommand }))
expect(settled.output?.structured).toMatchObject({ exit: 0, truncated: false })
expect(settled.output?.content[0]).toEqual({ type: "text", text: "hello" })
expect(settled.output?.content[1]).toMatchObject({
type: "text",
text: expect.stringContaining("Command exited with code 0."),
})
expect(assertions).toMatchObject([{ sessionID, action: "shell", resources: ["printf hello"] }])
expect(assertions).toMatchObject([{ sessionID, action: "shell", resources: [helloCommand] }])
}),
)
},
@ -128,7 +140,9 @@ describe("ShellTool", () => {
reset()
return Effect.promise(() => fs.mkdir(path.join(tmp.path, "src"))).pipe(
Effect.andThen(
withTool(data.path, tmp.path, (registry) => settleTool(registry, call({ command: "pwd", workdir: "src" }))),
withTool(data.path, tmp.path, (registry) =>
settleTool(registry, call({ command: cwdCommand, workdir: "src" })),
),
),
Effect.andThen((settled) =>
Effect.sync(() =>
@ -163,7 +177,7 @@ describe("ShellTool", () => {
return Effect.promise(() => fs.mkdir(workdir)).pipe(
Effect.andThen(
withTool(data.path, tmp.path, (registry) =>
executeTool(registry, call({ command: "pwd", workdir: "src" })),
executeTool(registry, call({ command: cwdCommand, workdir: "src" })),
),
),
Effect.andThen(Effect.sync(() => expect(assertions.map((input) => input.action)).toEqual(["shell"]))),
@ -182,7 +196,7 @@ describe("ShellTool", () => {
([data, active, outside]) => {
reset()
return withTool(data.path, active.path, (registry) =>
executeTool(registry, call({ command: "pwd", workdir: outside.path })),
executeTool(registry, call({ command: cwdCommand, workdir: outside.path })),
).pipe(
Effect.andThen(
Effect.sync(() => {
@ -213,13 +227,13 @@ describe("ShellTool", () => {
reset()
denyAction = "external_directory"
yield* withTool(data.path, active.path, (registry) =>
executeTool(registry, call({ command: "pwd", workdir: outside.path })),
executeTool(registry, call({ command: cwdCommand, workdir: outside.path })),
)
expect(assertions.map((item) => item.action)).toEqual(["external_directory"])
reset()
denyAction = "shell"
yield* withTool(data.path, active.path, (registry) => executeTool(registry, call({ command: "pwd" })))
yield* withTool(data.path, active.path, (registry) => executeTool(registry, call({ command: cwdCommand })))
expect(assertions.map((item) => item.action)).toEqual(["shell"])
}),
([data, active, outside]) =>
@ -272,7 +286,7 @@ describe("ShellTool", () => {
([data, tmp]) => {
reset()
return withTool(data.path, tmp.path, (registry) =>
settleTool(registry, call({ command: "printf body && exit 7" }, "call-nonzero")),
settleTool(registry, call({ command: bodyExitCommand }, "call-nonzero")),
).pipe(
Effect.andThen((settled) =>
Effect.sync(() => {
@ -300,7 +314,7 @@ describe("ShellTool", () => {
reset()
const bytes = ShellTool.MAX_CAPTURE_BYTES + 1024
return withTool(data.path, tmp.path, (registry) =>
settleTool(registry, call({ command: `head -c ${bytes} /dev/zero | tr '\\0' 'x'` }, "call-overflow")),
settleTool(registry, call({ command: overflowCommand(bytes) }, "call-overflow")),
).pipe(
Effect.andThen((settled) =>
Effect.sync(() => {
@ -326,7 +340,7 @@ describe("ShellTool", () => {
([data, tmp]) => {
reset()
return withTool(data.path, tmp.path, (registry) =>
settleTool(registry, call({ command: "sleep 60", timeout: 50 })),
settleTool(registry, call({ command: idleCommand, timeout: 50 })),
).pipe(
Effect.andThen((settled) =>
Effect.sync(() => {