tool.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { Effect, Schema } from "effect"
  2. /**
  3. * JSON Schema subset accepted for render-only tool schemas.
  4. *
  5. * A JSON-Schema-described side of a tool is used to generate the model-visible TypeScript
  6. * signature only - CodeMode performs no validation against it. This is the natural shape for
  7. * adapter-provided tools (e.g. MCP definitions) whose schemas arrive as JSON Schema documents.
  8. */
  9. export type JsonSchema = {
  10. readonly type?: string | ReadonlyArray<string>
  11. readonly enum?: ReadonlyArray<unknown>
  12. readonly const?: unknown
  13. readonly anyOf?: ReadonlyArray<JsonSchema>
  14. readonly oneOf?: ReadonlyArray<JsonSchema>
  15. readonly allOf?: ReadonlyArray<JsonSchema>
  16. readonly properties?: Readonly<Record<string, JsonSchema>>
  17. readonly required?: ReadonlyArray<string>
  18. readonly items?: JsonSchema
  19. readonly additionalProperties?: boolean | JsonSchema
  20. readonly description?: string
  21. readonly default?: unknown
  22. readonly format?: string
  23. readonly deprecated?: boolean
  24. readonly minItems?: number
  25. readonly maxItems?: number
  26. readonly $ref?: string
  27. readonly $defs?: Readonly<Record<string, JsonSchema>>
  28. readonly definitions?: Readonly<Record<string, JsonSchema>>
  29. }
  30. /** Either a validating Effect Schema or a render-only JSON Schema document. */
  31. export type SchemaType = Schema.Decoder<unknown> | JsonSchema
  32. /** Schema-backed tool definition consumed by a CodeMode tool tree. */
  33. export type Definition<R = never> = {
  34. readonly _tag: "CodeModeTool"
  35. readonly description: string
  36. readonly input: SchemaType
  37. readonly output: SchemaType | undefined
  38. readonly run: (input: unknown) => Effect.Effect<unknown, unknown, R>
  39. }
  40. /** The value `run` receives: the decoded type for Effect Schemas, `unknown` for JSON Schemas. */
  41. type InputType<S> = S extends Schema.Decoder<unknown> ? S["Type"] : unknown
  42. /** The value `run` returns: the encoded type for Effect Schemas, `unknown` otherwise. */
  43. type ResultType<S> = S extends Schema.Decoder<unknown> ? S["Encoded"] : unknown
  44. /** Options for defining one CodeMode tool. */
  45. export type Options<I extends SchemaType, O extends SchemaType | undefined, R = never> = {
  46. readonly description: string
  47. readonly input: I
  48. readonly output?: O
  49. readonly run: (input: InputType<I>) => Effect.Effect<ResultType<O>, unknown, R>
  50. }
  51. export const isDefinition = <R = never>(value: unknown): value is Definition<R> =>
  52. typeof value === "object" && value !== null && "_tag" in value && value._tag === "CodeModeTool"
  53. /**
  54. * Defines one schema-described tool available to a CodeMode program through `tools.*`.
  55. *
  56. * `input` and `output` each accept a validating Effect Schema or a render-only JSON Schema
  57. * document. Effect Schema input is decoded before `run` is invoked, and `run` returns the
  58. * encoded representation of an Effect Schema `output`, which CodeMode decodes before returning
  59. * it to the program. JSON Schemas only shape the model-visible signature; values pass through
  60. * unvalidated. `output` is optional - without it the signature advertises `unknown` and the
  61. * host result is exposed as-is. The host tool remains responsible for authorization and
  62. * durable side-effect handling.
  63. *
  64. * @example
  65. * ```ts
  66. * const lookup = Tool.make({
  67. * description: "Look up an order",
  68. * input: Schema.Struct({ id: Schema.String }),
  69. * output: Schema.Struct({ status: Schema.String }),
  70. * run: ({ id }) => Effect.succeed({ status: "open" }),
  71. * })
  72. *
  73. * const fromJsonSchema = Tool.make({
  74. * description: "Call an adapter-described tool",
  75. * input: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
  76. * run: (input) => callHost(input),
  77. * })
  78. * ```
  79. */
  80. export const make = <I extends SchemaType, const O extends SchemaType | undefined = undefined, R = never>(
  81. options: Options<I, O, R>,
  82. ): Definition<R> => ({
  83. _tag: "CodeModeTool",
  84. description: options.description,
  85. input: options.input,
  86. output: options.output,
  87. run: (input) => options.run(input as InputType<I>),
  88. })