fix(core): sanitize registered tool names (#34512)
This commit is contained in:
parent
ecfa918760
commit
f928b5be07
4 changed files with 9 additions and 11 deletions
|
|
@ -41,9 +41,8 @@ export const layer = Layer.effect(
|
||||||
|
|
||||||
return Service.of({
|
return Service.of({
|
||||||
register: Effect.fn("ApplicationTools.register")(function* (tools) {
|
register: Effect.fn("ApplicationTools.register")(function* (tools) {
|
||||||
const entries = Object.entries(tools)
|
const entries = Tool.registrationEntries(tools)
|
||||||
if (entries.length === 0) return
|
if (entries.length === 0) return
|
||||||
yield* Effect.forEach(entries, ([name]) => Tool.validateName(name), { discard: true })
|
|
||||||
const registrations = entries.map(([name, tool]) => [name, { identity: {}, tool }] as const)
|
const registrations = entries.map(([name, tool]) => [name, { identity: {}, tool }] as const)
|
||||||
yield* state.transform((draft) => {
|
yield* state.transform((draft) => {
|
||||||
for (const [name, entry] of registrations) draft.set(name, entry)
|
for (const [name, entry] of registrations) draft.set(name, entry)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { SessionSchema } from "../session/schema"
|
||||||
import { ToolOutputStore } from "../tool-output-store"
|
import { ToolOutputStore } from "../tool-output-store"
|
||||||
import { Wildcard } from "../util/wildcard"
|
import { Wildcard } from "../util/wildcard"
|
||||||
import { ApplicationTools } from "./application-tools"
|
import { ApplicationTools } from "./application-tools"
|
||||||
import { definition, permission, settle, validateName, type AnyTool, type RegistrationError } from "./tool"
|
import { definition, permission, registrationEntries, settle, type AnyTool, type RegistrationError } from "./tool"
|
||||||
import { Tools } from "./tools"
|
import { Tools } from "./tools"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
import { makeLocationNode } from "../effect/app-node"
|
||||||
|
|
||||||
|
|
@ -83,9 +83,8 @@ const registryLayer = Layer.effect(
|
||||||
|
|
||||||
return Service.of({
|
return Service.of({
|
||||||
register: Effect.fn("ToolRegistry.register")(function* (tools) {
|
register: Effect.fn("ToolRegistry.register")(function* (tools) {
|
||||||
const entries = Object.entries(tools)
|
const entries = registrationEntries(tools)
|
||||||
if (entries.length === 0) return
|
if (entries.length === 0) return
|
||||||
yield* Effect.forEach(entries, ([name]) => validateName(name), { discard: true })
|
|
||||||
yield* Effect.uninterruptible(
|
yield* Effect.uninterruptible(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const token = {}
|
const token = {}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,9 @@ export const validateName = (name: string) =>
|
||||||
? Effect.void
|
? Effect.void
|
||||||
: Effect.fail(new RegistrationError({ name, message: `Invalid tool name: ${name}` }))
|
: Effect.fail(new RegistrationError({ name, message: `Invalid tool name: ${name}` }))
|
||||||
|
|
||||||
|
export const registrationEntries = (tools: Readonly<Record<string, AnyTool>>) =>
|
||||||
|
Object.entries(tools).map(([name, tool]) => [name.replace(/[^a-zA-Z0-9_-]/g, "_"), tool] as const)
|
||||||
|
|
||||||
export const withPermission = <Input extends SchemaType<any>, Output extends SchemaType<any>>(
|
export const withPermission = <Input extends SchemaType<any>, Output extends SchemaType<any>>(
|
||||||
tool: Definition<Input, Output>,
|
tool: Definition<Input, Output>,
|
||||||
permission: string,
|
permission: string,
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,14 @@ describe("ApplicationTools", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("exposes narrow scoped Location registration and validates names", () =>
|
it.effect("exposes narrow scoped Location registration and sanitizes names", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tools: Tools.Interface = yield* Tools.Service
|
const tools: Tools.Interface = yield* Tools.Service
|
||||||
const registry = yield* ToolRegistry.Service
|
const registry = yield* ToolRegistry.Service
|
||||||
const scope = yield* Scope.make()
|
const scope = yield* Scope.make()
|
||||||
|
|
||||||
yield* tools.register({ location_tool: contextual([]) }).pipe(Scope.provide(scope))
|
yield* tools.register({ "location.tool/search": contextual([]) }).pipe(Scope.provide(scope))
|
||||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["location_tool"])
|
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual(["location_tool_search"])
|
||||||
expect(yield* Effect.flip(tools.register({ "invalid name": contextual([]) }))).toBeInstanceOf(
|
|
||||||
Tool.RegistrationError,
|
|
||||||
)
|
|
||||||
|
|
||||||
yield* Scope.close(scope, Exit.void)
|
yield* Scope.close(scope, Exit.void)
|
||||||
expect(yield* toolDefinitions(registry)).toEqual([])
|
expect(yield* toolDefinitions(registry)).toEqual([])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue