mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
fix(api): resolve swarm-field tool names by own-property (#2123)
filterSwarmFieldsFromSchema() looked the tool name up with a bare SWARM_FIELDS_BY_TOOL[toolName] on a plain-object map. A tool whose name collides with an Object.prototype member (constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable) resolves the inherited function, which is truthy with .length === 1 — so the empty/undefined guard is bypassed and the subsequent for...of throws "is not iterable", failing schema construction for the entire request. Guard the lookup with Object.hasOwn so a prototype-named tool is treated as unmapped, matching the own-key guard already used for provider- supplied tool names in services/api/toolArgumentNormalization.ts.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { expect, test } from 'bun:test'
|
||||
import type Anthropic from '@anthropic-ai/sdk'
|
||||
|
||||
import { AGENT_TOOL_NAME } from '../tools/AgentTool/constants.js'
|
||||
import { filterSwarmFieldsFromSchema } from './api.js'
|
||||
|
||||
const schema: Anthropic.Tool.InputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
team_name: { type: 'string' },
|
||||
mode: { type: 'string' },
|
||||
prompt: { type: 'string' },
|
||||
},
|
||||
required: ['name', 'prompt'],
|
||||
}
|
||||
|
||||
// `filterSwarmFieldsFromSchema` looks the tool name up in a plain-object map.
|
||||
// A name that collides with an Object.prototype member (`constructor`,
|
||||
// `hasOwnProperty`, `isPrototypeOf`, `propertyIsEnumerable`) resolves the
|
||||
// inherited function — truthy with `.length === 1` — which slips past the
|
||||
// `!fieldsToRemove || length === 0` guard and then throws in the `for...of`.
|
||||
for (const name of [
|
||||
'constructor',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
]) {
|
||||
test(`prototype-named tool "${name}" is treated as unmapped, not crashed`, () => {
|
||||
expect(() => filterSwarmFieldsFromSchema(name, schema)).not.toThrow()
|
||||
// Unmapped tool → schema returned unchanged (same reference).
|
||||
expect(filterSwarmFieldsFromSchema(name, schema)).toBe(schema)
|
||||
})
|
||||
}
|
||||
|
||||
test('a genuinely mapped tool still has its swarm fields removed', () => {
|
||||
const filtered = filterSwarmFieldsFromSchema(AGENT_TOOL_NAME, schema)
|
||||
expect(Object.keys(filtered.properties ?? {})).toEqual(['prompt'])
|
||||
expect(filtered.required).toEqual(['prompt'])
|
||||
})
|
||||
|
||||
test('an ordinary unmapped tool is returned unchanged', () => {
|
||||
expect(filterSwarmFieldsFromSchema('mcp__server__do_thing', schema)).toBe(
|
||||
schema,
|
||||
)
|
||||
})
|
||||
+11
-2
@@ -94,11 +94,20 @@ const SWARM_FIELDS_BY_TOOL: Record<string, string[]> = {
|
||||
* Filter swarm-related fields from a tool's input schema.
|
||||
* Called at runtime when isAgentSwarmsEnabled() returns false.
|
||||
*/
|
||||
function filterSwarmFieldsFromSchema(
|
||||
export function filterSwarmFieldsFromSchema(
|
||||
toolName: string,
|
||||
schema: Anthropic.Tool.InputSchema,
|
||||
): Anthropic.Tool.InputSchema {
|
||||
const fieldsToRemove = SWARM_FIELDS_BY_TOOL[toolName]
|
||||
// Guard with Object.hasOwn: a bare `SWARM_FIELDS_BY_TOOL[toolName]` lookup
|
||||
// resolves inherited Object.prototype members for names like `constructor` or
|
||||
// `hasOwnProperty` to their functions. Those are truthy with `.length === 1`,
|
||||
// so the guard below is bypassed and the `for...of` on line ~111 throws
|
||||
// "is not iterable", breaking schema construction for the whole request.
|
||||
// Mirrors the own-key guard already used for provider-supplied tool names in
|
||||
// services/api/toolArgumentNormalization.ts.
|
||||
const fieldsToRemove = Object.hasOwn(SWARM_FIELDS_BY_TOOL, toolName)
|
||||
? SWARM_FIELDS_BY_TOOL[toolName]
|
||||
: undefined
|
||||
if (!fieldsToRemove || fieldsToRemove.length === 0) {
|
||||
return schema
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user