provider-anthropic.test.ts 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { Catalog } from "@opencode-ai/core/catalog"
  4. import { ModelV2 } from "@opencode-ai/core/model"
  5. import { PluginV2 } from "@opencode-ai/core/plugin"
  6. import { PluginHost } from "@opencode-ai/core/plugin/host"
  7. import { AnthropicPlugin } from "@opencode-ai/core/plugin/provider/anthropic"
  8. import { ProviderV2 } from "@opencode-ai/core/provider"
  9. import { testEffect } from "../lib/effect"
  10. import { PluginTestLayer } from "./fixture"
  11. const it = testEffect(PluginTestLayer)
  12. const addPlugin = Effect.fn(function* () {
  13. const plugin = yield* PluginV2.Service
  14. const host = yield* PluginHost.make()
  15. yield* plugin.add({ id: AnthropicPlugin.id, effect: AnthropicPlugin.effect(host) })
  16. })
  17. function required<T>(value: T | undefined): T {
  18. if (value === undefined) throw new Error("Expected value")
  19. return value
  20. }
  21. describe("AnthropicPlugin", () => {
  22. it.effect("applies legacy beta headers", () =>
  23. Effect.gen(function* () {
  24. const catalog = yield* Catalog.Service
  25. yield* catalog.transform((catalog) => {
  26. const item = new ProviderV2.Info({
  27. ...ProviderV2.Info.empty(ProviderV2.ID.anthropic),
  28. api: { type: "aisdk", package: "@ai-sdk/anthropic" },
  29. request: { headers: { Existing: "1" }, body: {} },
  30. })
  31. catalog.provider.update(item.id, (draft) => {
  32. draft.api = item.api
  33. draft.request = item.request
  34. })
  35. })
  36. yield* addPlugin()
  37. expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers["anthropic-beta"]).toBe(
  38. "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
  39. )
  40. expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers.Existing).toBe("1")
  41. }),
  42. )
  43. it.effect("ignores non-Anthropic providers", () =>
  44. Effect.gen(function* () {
  45. const catalog = yield* Catalog.Service
  46. yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.openai, () => {}))
  47. yield* addPlugin()
  48. expect(
  49. required(yield* catalog.provider.get(ProviderV2.ID.openai)).request.headers["anthropic-beta"],
  50. ).toBeUndefined()
  51. }),
  52. )
  53. it.effect("creates Anthropic SDKs with the model provider ID as the SDK name", () =>
  54. Effect.gen(function* () {
  55. const plugin = yield* PluginV2.Service
  56. yield* addPlugin()
  57. const result = yield* plugin.trigger(
  58. "aisdk.sdk",
  59. {
  60. model: new ModelV2.Info({
  61. ...ModelV2.Info.empty(ProviderV2.ID.make("custom-anthropic"), ModelV2.ID.make("claude-sonnet-4-5")),
  62. api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "@ai-sdk/anthropic" },
  63. }),
  64. package: "@ai-sdk/anthropic",
  65. options: { name: "custom-anthropic", apiKey: "test" },
  66. },
  67. {},
  68. )
  69. expect(result.sdk.languageModel("claude-sonnet-4-5").provider).toBe("custom-anthropic")
  70. }),
  71. )
  72. it.effect("uses the Anthropic provider ID as the SDK name for the bundled Anthropic provider", () =>
  73. Effect.gen(function* () {
  74. const plugin = yield* PluginV2.Service
  75. yield* addPlugin()
  76. const result = yield* plugin.trigger(
  77. "aisdk.sdk",
  78. {
  79. model: new ModelV2.Info({
  80. ...ModelV2.Info.empty(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-5")),
  81. api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "@ai-sdk/anthropic" },
  82. }),
  83. package: "@ai-sdk/anthropic",
  84. options: { name: "anthropic", apiKey: "test" },
  85. },
  86. {},
  87. )
  88. expect(result.sdk.languageModel("claude-sonnet-4-5").provider).toBe("anthropic")
  89. }),
  90. )
  91. })