aisdk.test.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import type { LanguageModelV3CallOptions } from "@ai-sdk/provider"
  2. import { AISDK } from "@opencode-ai/core/aisdk"
  3. import { ModelV2 } from "@opencode-ai/core/model"
  4. import { ProviderV2 } from "@opencode-ai/core/provider"
  5. import { LLM, Message } from "@opencode-ai/llm"
  6. import { LLMClient } from "@opencode-ai/llm/route"
  7. import { expect } from "bun:test"
  8. import { Effect } from "effect"
  9. import { testEffect } from "./lib/effect"
  10. const it = testEffect(AISDK.locationLayer)
  11. const model = (packageName: string, settings: Record<string, unknown> = {}) =>
  12. ModelV2.Info.make({
  13. ...ModelV2.Info.empty(ProviderV2.ID.make("test-provider"), ModelV2.ID.make("catalog-model")),
  14. modelID: ModelV2.ID.make("api-model"),
  15. package: ProviderV2.aisdk(packageName),
  16. settings,
  17. limit: { context: 100, output: 20 },
  18. })
  19. it.effect("keys language models by package and flattened overlays", () =>
  20. Effect.gen(function* () {
  21. const aisdk = yield* AISDK.Service
  22. const loaded: string[] = []
  23. yield* aisdk.hook.sdk((event) => {
  24. loaded.push(event.package)
  25. event.sdk = { languageModel: () => ({ package: event.package }) }
  26. })
  27. const first = yield* aisdk.language(model("first", { region: "us-east-1" }))
  28. const second = yield* aisdk.language(model("second", { region: "us-east-1" }))
  29. const third = yield* aisdk.language(model("second", { region: "us-west-2" }))
  30. expect(first).not.toBe(second)
  31. expect(second).not.toBe(third)
  32. expect(loaded).toEqual(["first", "second", "second"])
  33. }),
  34. )
  35. it.effect("projects request settings, headers, and body overlays", () =>
  36. Effect.gen(function* () {
  37. const aisdk = yield* AISDK.Service
  38. let body: unknown
  39. yield* aisdk.hook.sdk((event) => {
  40. body = event.options.body
  41. event.sdk = { languageModel: () => ({ provider: event.model.providerID }) }
  42. })
  43. const input = model("@ai-sdk/google", {
  44. apiKey: "secret",
  45. thinkingConfig: { thinkingBudget: 1024 },
  46. })
  47. const resolved = yield* aisdk.model({
  48. ...input,
  49. headers: { "x-test": "header" },
  50. body: { safety_setting: "strict" },
  51. })
  52. const prepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
  53. LLM.request({ model: resolved, prompt: "Hello" }),
  54. )
  55. expect(prepared.body.providerOptions).toEqual({
  56. google: { thinkingConfig: { thinkingBudget: 1024 } },
  57. })
  58. expect(prepared.body.headers).toEqual({ "x-test": "header" })
  59. expect(body).toEqual({ safety_setting: "strict" })
  60. }),
  61. )
  62. it.effect("projects replay metadata onto AI SDK prompt parts", () =>
  63. Effect.gen(function* () {
  64. const aisdk = yield* AISDK.Service
  65. yield* aisdk.hook.sdk((event) => {
  66. event.sdk = { languageModel: () => ({ provider: event.model.providerID }) }
  67. })
  68. const resolved = yield* aisdk.model(model("@ai-sdk/anthropic"))
  69. expect(resolved.route.providerMetadataKey).toBe("anthropic")
  70. const prepared = yield* LLMClient.prepare<LanguageModelV3CallOptions>(
  71. LLM.request({
  72. model: resolved,
  73. messages: [
  74. Message.assistant([
  75. { type: "reasoning", text: "Think", providerMetadata: { anthropic: { signature: "signed" } } },
  76. {
  77. type: "tool-call",
  78. id: "hosted",
  79. name: "web_search",
  80. input: { query: "Effect" },
  81. providerExecuted: true,
  82. providerMetadata: { anthropic: { blockType: "server_tool_use" } },
  83. },
  84. ]),
  85. ],
  86. }),
  87. )
  88. expect(prepared.body.prompt).toEqual([
  89. {
  90. role: "assistant",
  91. content: [
  92. {
  93. type: "reasoning",
  94. text: "Think",
  95. providerOptions: { anthropic: { signature: "signed" } },
  96. },
  97. {
  98. type: "tool-call",
  99. toolCallId: "hosted",
  100. toolName: "web_search",
  101. input: { query: "Effect" },
  102. providerExecuted: true,
  103. providerOptions: { anthropic: { blockType: "server_tool_use" } },
  104. },
  105. ],
  106. },
  107. ])
  108. }),
  109. )