refactor(codemode): simplify tools types (#36941)

This commit is contained in:
Aiden Cline 2026-07-14 18:40:43 -05:00 committed by GitHub
commit 563f6a65de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 77 additions and 93 deletions

View file

@ -9,6 +9,7 @@ import {
outputTypeScript,
} from "./tool-schema.js"
import { isDefinition as isToolDefinition, type Definition } from "./tool.js"
import type { Tools } from "./tools.js"
import {
CodeModeDate,
CodeModeMap,
@ -21,28 +22,20 @@ import {
const estimateTokens = (input: string) => Math.max(0, Math.round(input.length / 4))
export type HostTool<R = never> = (...args: Array<unknown>) => Effect.Effect<unknown, unknown, R>
export type Services<T> = ServicesOf<T, []>
export type HostTools<R = never> = {
[name: string]: HostTool<R> | Definition<R> | HostTools<R>
}
export type Services<Tools> = ServicesOf<Tools, []>
type ServicesOf<Tools, Depth extends ReadonlyArray<unknown>> = Depth["length"] extends 8
type ServicesOf<T, Depth extends ReadonlyArray<unknown>> = Depth["length"] extends 8
? never
: Tools extends (...args: Array<unknown>) => Effect.Effect<unknown, unknown, infer R>
: T extends {
readonly _tag: "CodeModeTool"
readonly run: (input: unknown) => Effect.Effect<unknown, unknown, infer R>
}
? R
: Tools extends {
readonly _tag: "CodeModeTool"
readonly run: (input: unknown) => Effect.Effect<unknown, unknown, infer R>
}
? R
: Tools extends object
? string extends keyof Tools
? ServicesOf<Tools[string], [...Depth, unknown]>
: ServicesOf<Tools[keyof Tools], [...Depth, unknown]>
: never
: T extends object
? string extends keyof T
? ServicesOf<T[string], [...Depth, unknown]>
: ServicesOf<T[keyof T], [...Depth, unknown]>
: never
export type ToolCall = {
readonly name: string
@ -125,7 +118,7 @@ export class ToolRuntimeError extends Error {
}
}
const isDefinition = <R>(value: HostTool<R> | Definition<R> | HostTools<R>): value is Definition<R> =>
const isDefinition = <R>(value: Definition<R> | Tools<R>): value is Definition<R> =>
isToolDefinition<R>(value)
const runHost = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, ToolError, R> =>
@ -282,13 +275,13 @@ export const copyOut = (value: unknown, undefinedAsNull = false): unknown => {
}
const definitions = <R>(
tools: HostTools<R>,
tools: Tools<R>,
path: ReadonlyArray<string> = [],
): Array<{ path: string; definition: Definition<R> }> =>
Object.entries(tools).flatMap(([name, value]) => {
const next = [...path, name]
if (isDefinition(value)) return [{ path: next.join("."), definition: value }]
return typeof value === "function" ? [] : definitions(value, next)
return definitions(value, next)
})
const describeDefinition = <R>(path: string, definition: Definition<R>): ToolDescription => ({
@ -297,7 +290,7 @@ const describeDefinition = <R>(path: string, definition: Definition<R>): ToolDes
signature: `${toolExpression(path)}(input: ${inputTypeScript(definition, true)}): Promise<${outputTypeScript(definition, true)}>`,
})
const visibleDefinitions = <R>(tools: HostTools<R>) =>
const visibleDefinitions = <R>(tools: Tools<R>) =>
definitions(tools).map(({ path, definition }) => ({
path,
definition,
@ -415,11 +408,11 @@ const toSearchEntry = <R>(path: string, definition: Definition<R>, description:
.toLowerCase(),
})
export const searchIndex = <R>(tools: HostTools<R>): ReadonlyArray<SearchEntry> =>
export const searchIndex = <R>(tools: Tools<R>): ReadonlyArray<SearchEntry> =>
visibleDefinitions(tools).map(({ path, definition, description }) => toSearchEntry(path, definition, description))
// Budget signatures round-robin so every namespace remains visible.
export const prepare = <R>(tools: HostTools<R>, catalogBudget = defaultCatalogBudget): DiscoveryPlan => {
export const prepare = <R>(tools: Tools<R>, catalogBudget = defaultCatalogBudget): DiscoveryPlan => {
if (!Number.isSafeInteger(catalogBudget) || catalogBudget < 0) {
throw new RangeError("discovery.catalogBudget must be a non-negative safe integer")
}
@ -562,43 +555,33 @@ export const prepare = <R>(tools: HostTools<R>, catalogBudget = defaultCatalogBu
}
}
const namespaceKeys = <R>(tools: HostTools<R>, path: ReadonlyArray<string>): ReadonlyArray<string> => {
let value: HostTool<R> | Definition<R> | HostTools<R> = tools
const namespaceKeys = <R>(tools: Tools<R>, path: ReadonlyArray<string>): ReadonlyArray<string> => {
let value: Definition<R> | Tools<R> = tools
for (const segment of path) {
if (
isBlockedMember(segment) ||
typeof value === "function" ||
isDefinition(value) ||
!Object.hasOwn(value, segment)
) {
if (isBlockedMember(segment) || isDefinition(value) || !Object.hasOwn(value, segment)) {
throw new ToolRuntimeError("UnknownTool", `Unknown tool namespace '${path.join(".")}'.`, [
"Object.keys(tools) lists the available namespaces; search({ query }) finds described tools.",
])
}
value = value[segment] as HostTool<R> | Definition<R> | HostTools<R>
value = value[segment] as Definition<R> | Tools<R>
}
if (typeof value === "function" || isDefinition(value)) return []
if (isDefinition(value)) return []
return Object.keys(value)
}
const resolve = <R>(tools: HostTools<R>, path: ReadonlyArray<string>): HostTool<R> | Definition<R> => {
let value: HostTool<R> | Definition<R> | HostTools<R> = tools
const resolve = <R>(tools: Tools<R>, path: ReadonlyArray<string>): Definition<R> => {
let value: Definition<R> | Tools<R> = tools
for (const segment of path) {
if (
isBlockedMember(segment) ||
typeof value === "function" ||
isDefinition(value) ||
!Object.hasOwn(value, segment)
) {
if (isBlockedMember(segment) || isDefinition(value) || !Object.hasOwn(value, segment)) {
throw new ToolRuntimeError("UnknownTool", `Unknown tool '${path.join(".")}'.`, [
"Use search({ query }) to find available described tools.",
])
}
value = value[segment] as HostTool<R> | Definition<R> | HostTools<R>
value = value[segment] as Definition<R> | Tools<R>
}
if (typeof value !== "function" && !isDefinition(value)) {
if (!isDefinition(value)) {
throw new ToolRuntimeError("UnknownTool", `Tool '${path.join(".")}' is not callable.`)
}
@ -614,7 +597,7 @@ export type ToolRuntime<R = never> = {
}
export const make = <R>(
tools: HostTools<R>,
tools: Tools<R>,
maxToolCalls: number | undefined,
searchIndex: ReadonlyArray<SearchEntry>,
hooks?: ToolCallHooks<R>,
@ -701,14 +684,7 @@ export const make = <R>(
const name = path.join(".")
const externalArgs = args.map((arg) => copyOut(copyIn(arg, `Arguments for tool '${name}'`)))
const tool = resolve(tools, path)
if (isDefinition(tool)) return yield* invokeDefinition(name, tool, externalArgs)
const index = yield* recordAndObserve(name, externalArgs)
return yield* observeEnd(
Effect.gen(function* () {
return yield* decodeOutput(yield* runHost(Effect.suspend(() => tool(...externalArgs))), name)
}),
{ index, name, input: externalArgs },
)
return yield* invokeDefinition(name, tool, externalArgs)
}),
}
}