feat(core): deliver CodeMode catalog through instructions (#38003)
This commit is contained in:
parent
c4fa5e6619
commit
7ec1b6e580
10 changed files with 267 additions and 38 deletions
|
|
@ -36,34 +36,23 @@ type CollectedFiles = {
|
|||
readonly files: Array<typeof ExecuteFile.Type>
|
||||
}
|
||||
|
||||
interface Registration {
|
||||
export interface Registration {
|
||||
readonly tool: AnyTool
|
||||
readonly name: string
|
||||
readonly namespace?: string
|
||||
}
|
||||
|
||||
// Invariant model-facing guidance; the changing tool catalog is delivered through Instructions.
|
||||
const description = [
|
||||
"Run JavaScript in a confined Code Mode runtime through { code }.",
|
||||
"Call Code Mode tools through `tools` using the exact paths and signatures from the instructions.",
|
||||
"Use `search({ query })` to discover exact signatures when needed.",
|
||||
"Await important calls and use `Promise.all` for independent calls.",
|
||||
].join("\n")
|
||||
|
||||
export const create = (registrations: ReadonlyMap<string, Registration>) => {
|
||||
const runtime = (
|
||||
invoke: (name: string, registration: Registration, input: unknown) => Effect.Effect<unknown, unknown>,
|
||||
hooks?: CodeMode.ToolCallHooks,
|
||||
) => {
|
||||
const tools: Record<string, Tool.Definition<never>> = {}
|
||||
for (const [name, registration] of registrations) {
|
||||
const child = definition(name, registration.tool)
|
||||
const value = Tool.make({
|
||||
description: child.description,
|
||||
input: child.inputSchema,
|
||||
output: child.outputSchema,
|
||||
run: (input) => invoke(name, registration, input),
|
||||
})
|
||||
const path = registration.namespace === undefined ? registration.name : `${registration.namespace}.${registration.name}`
|
||||
tools[path] = value
|
||||
}
|
||||
return CodeMode.make<typeof tools>({ tools, ...hooks })
|
||||
}
|
||||
const discovery = runtime(() => Effect.fail(toolError("Execute context is unavailable")))
|
||||
return make({
|
||||
description: discovery.instructions(),
|
||||
description,
|
||||
input: CodeMode.Input,
|
||||
output: ExecuteOutput,
|
||||
structured: ExecuteMetadata,
|
||||
|
|
@ -92,6 +81,7 @@ export const create = (registrations: ReadonlyMap<string, Registration>) => {
|
|||
),
|
||||
)
|
||||
const result = yield* runtime(
|
||||
registrations,
|
||||
(name, registration, input) =>
|
||||
Effect.gen(function* () {
|
||||
const index = yield* Ref.getAndUpdate(callIndex, (index) => index + 1)
|
||||
|
|
@ -141,6 +131,29 @@ export const create = (registrations: ReadonlyMap<string, Registration>) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const instructions = (registrations: ReadonlyMap<string, Registration>) => {
|
||||
return runtime(registrations, () => Effect.fail(toolError("Execute context is unavailable"))).instructions()
|
||||
}
|
||||
|
||||
function runtime(
|
||||
registrations: ReadonlyMap<string, Registration>,
|
||||
invoke: (name: string, registration: Registration, input: unknown) => Effect.Effect<unknown, unknown>,
|
||||
hooks?: CodeMode.ToolCallHooks,
|
||||
) {
|
||||
const tools: Record<string, Tool.Definition<never>> = {}
|
||||
for (const [name, registration] of registrations) {
|
||||
const child = definition(name, registration.tool)
|
||||
const path = registration.namespace === undefined ? registration.name : `${registration.namespace}.${registration.name}`
|
||||
tools[path] = Tool.make({
|
||||
description: child.description,
|
||||
input: child.inputSchema,
|
||||
output: child.outputSchema,
|
||||
run: (input) => invoke(name, registration, input),
|
||||
})
|
||||
}
|
||||
return CodeMode.make<typeof tools>({ tools, ...hooks })
|
||||
}
|
||||
|
||||
function displayInput(input: unknown): Record<string, unknown> | undefined {
|
||||
if (input === null || input === undefined) return
|
||||
if (typeof input !== "object" || Array.isArray(input)) return { input }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { SessionMessage } from "../session/message"
|
|||
import { SessionSchema } from "../session/schema"
|
||||
import { ToolOutputStore } from "../tool-output-store"
|
||||
import { Wildcard } from "../util/wildcard"
|
||||
import { ExecuteTool } from "./execute"
|
||||
import { CodeMode } from "../codemode"
|
||||
import {
|
||||
definition,
|
||||
permission,
|
||||
|
|
@ -74,6 +74,7 @@ const registryLayer = Layer.effect(
|
|||
const resources = yield* ToolOutputStore.Service
|
||||
const toolHooks = yield* ToolHooks.Service
|
||||
const image = yield* Image.Service
|
||||
const codeMode = yield* CodeMode.Service
|
||||
|
||||
type NormalizedItem = ToolOutput["content"][number] | "decode" | "size"
|
||||
const normalizeImages = Effect.fn("ToolRegistry.normalizeImages")(function* (content: ToolOutput["content"]) {
|
||||
|
|
@ -111,7 +112,6 @@ const registryLayer = Layer.effect(
|
|||
readonly tool: AnyTool
|
||||
readonly name: string
|
||||
readonly namespace?: string
|
||||
readonly codemode: boolean
|
||||
}
|
||||
const local = new Map<string, Array<{ readonly token: object; readonly registration: Registration }>>()
|
||||
const registrationLock = Semaphore.makeUnsafe(1)
|
||||
|
|
@ -212,15 +212,22 @@ const registryLayer = Layer.effect(
|
|||
return yield* Effect.fail(
|
||||
new RegistrationError({ name: reserved.key, message: 'Tool name "execute" is reserved for CodeMode' }),
|
||||
)
|
||||
return { entries, codemode }
|
||||
return { tools, options, entries, codemode }
|
||||
}),
|
||||
)
|
||||
if (planned.every((plan) => plan.entries.length === 0)) return
|
||||
// CodeMode registrations live in the CodeMode service; the registry keeps only direct tools.
|
||||
yield* Effect.forEach(
|
||||
planned.filter((plan) => plan.codemode && plan.entries.length > 0),
|
||||
(plan) => codeMode.register(plan.tools, plan.options),
|
||||
{ discard: true },
|
||||
)
|
||||
const direct = planned.filter((plan) => !plan.codemode)
|
||||
if (direct.every((plan) => plan.entries.length === 0)) return
|
||||
yield* Effect.uninterruptible(
|
||||
registrationLock.withPermit(
|
||||
Effect.gen(function* () {
|
||||
const token = {}
|
||||
for (const { entries, codemode } of planned)
|
||||
for (const { entries } of direct)
|
||||
for (const entry of entries)
|
||||
local.set(entry.key, [
|
||||
...(local.get(entry.key) ?? []),
|
||||
|
|
@ -230,14 +237,13 @@ const registryLayer = Layer.effect(
|
|||
tool: entry.tool,
|
||||
name: entry.name,
|
||||
namespace: entry.namespace,
|
||||
codemode,
|
||||
},
|
||||
},
|
||||
])
|
||||
yield* Effect.addFinalizer(() =>
|
||||
registrationLock.withPermit(
|
||||
Effect.sync(() => {
|
||||
for (const { entries } of planned)
|
||||
for (const { entries } of direct)
|
||||
for (const entry of entries) {
|
||||
const registrations =
|
||||
local.get(entry.key)?.filter((registration) => registration.token !== token) ?? []
|
||||
|
|
@ -265,19 +271,16 @@ const registryLayer = Layer.effect(
|
|||
registerBatch,
|
||||
materialize: Effect.fn("ToolRegistry.materialize")((permissions) =>
|
||||
registrationLock.withPermit(
|
||||
Effect.sync(() => {
|
||||
Effect.gen(function* () {
|
||||
const direct = new Map<string, Registration>()
|
||||
const codemode = new Map<string, Registration>()
|
||||
const rules = permissions ?? []
|
||||
for (const [name, entries] of local) {
|
||||
const registration = entries.at(-1)?.registration
|
||||
if (!registration) continue
|
||||
if (whollyDisabled(permission(registration.tool, name), rules)) continue
|
||||
if (registration.codemode) codemode.set(name, registration)
|
||||
else direct.set(name, registration)
|
||||
direct.set(name, registration)
|
||||
}
|
||||
const execute =
|
||||
codemode.size > 0 && !whollyDisabled("execute", rules) ? ExecuteTool.create(codemode) : undefined
|
||||
const execute = (yield* codeMode.materialize(permissions)).tool
|
||||
return {
|
||||
definitions: [
|
||||
...Array.from(direct, ([name, registration]) => definition(name, registration.tool)),
|
||||
|
|
@ -315,11 +318,11 @@ function whollyDisabled(action: string, rules: PermissionV2.Ruleset) {
|
|||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
deps: [ToolOutputStore.node, ToolHooks.node, Image.node],
|
||||
deps: [CodeMode.node, ToolOutputStore.node, ToolHooks.node, Image.node],
|
||||
})
|
||||
|
||||
export const toolsNode = makeLocationNode({
|
||||
service: Tools.Service,
|
||||
layer,
|
||||
deps: [ToolOutputStore.node, ToolHooks.node, Image.node],
|
||||
deps: [CodeMode.node, ToolOutputStore.node, ToolHooks.node, Image.node],
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue