| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- import { describe, expect, test } from "bun:test"
- import { Effect } from "effect"
- import { LLM } from "../src"
- import { OpenAIChat } from "../src/protocols"
- import { ToolSchemaProjection } from "../src/protocols/utils/tool-schema"
- import { Auth } from "../src/route"
- import { compileRequest } from "../src/route/client"
- import { it } from "./lib/effect"
- describe("tool schema projections", () => {
- test("moonshot strips $ref siblings and converts tuple arrays to a schema object", () => {
- expect(
- ToolSchemaProjection.moonshot({
- type: "object",
- properties: {
- linked: { $ref: "#/$defs/Linked", description: "drop me" },
- tuple: { type: "array", items: [{ type: "string" }, { type: "number" }] },
- prefixTuple: { type: "array", prefixItems: [{ type: "boolean" }, { type: "string" }] },
- },
- }),
- ).toEqual({
- type: "object",
- properties: {
- linked: { $ref: "#/$defs/Linked" },
- tuple: { type: "array", items: { anyOf: [{ type: "string" }, { type: "number" }] } },
- prefixTuple: { type: "array", items: { anyOf: [{ type: "boolean" }, { type: "string" }] } },
- },
- })
- })
- test("gemini handles numeric enums, dangling required fields, untyped arrays, and scalar object keys", () => {
- expect(
- ToolSchemaProjection.gemini({
- type: "object",
- required: ["status", "missing"],
- properties: {
- status: { type: "integer", enum: [1, 2] },
- tags: { type: "array" },
- name: { type: "string", properties: { ignored: { type: "string" } }, required: ["ignored"] },
- },
- }),
- ).toEqual({
- type: "object",
- required: ["status"],
- properties: {
- status: { type: "string", enum: ["1", "2"] },
- tags: { type: "array", items: { type: "string" } },
- name: { type: "string" },
- },
- })
- })
- test("openai keeps one flat object top-level schema", () => {
- expect(
- ToolSchemaProjection.openAI({
- anyOf: [
- {
- type: "object",
- properties: {
- path: { type: "string" },
- maybe: { anyOf: [{ type: "string" }, { type: "null" }] },
- },
- },
- { type: "object", properties: { resource: { type: "string" } } },
- ],
- }),
- ).toEqual({
- type: "object",
- properties: {
- path: { type: "string" },
- maybe: { type: "string" },
- resource: { type: "string" },
- },
- additionalProperties: false,
- })
- })
- it.effect("applies model compatibility before protocol projection", () =>
- Effect.gen(function* () {
- const model = OpenAIChat.route
- .with({ endpoint: { baseURL: "https://api.openai.test/v1/" }, auth: Auth.bearer("test") })
- .model({ id: "kimi-k2", compatibility: { toolSchema: "moonshot" } })
- const prepared = yield* compileRequest(
- LLM.request({
- model,
- prompt: "Use the tool.",
- tools: [
- {
- name: "lookup",
- description: "Lookup data.",
- inputSchema: {
- type: "object",
- anyOf: [
- {
- type: "object",
- properties: {
- tuple: { type: "array", items: [{ type: "string" }, { type: "number" }] },
- linked: { $ref: "#/$defs/Linked", description: "drop me" },
- },
- },
- ],
- },
- },
- ],
- }),
- )
- expect(prepared.body.tools?.[0]?.function.parameters).toEqual({
- type: "object",
- properties: {
- tuple: { type: "array", items: { anyOf: [{ type: "string" }, { type: "number" }] } },
- linked: { $ref: "#/$defs/Linked" },
- },
- additionalProperties: false,
- })
- }),
- )
- })
|