zai-images.test.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import { describe, expect } from "bun:test"
  2. import { Effect, Layer } from "effect"
  3. import { HttpClientRequest } from "effect/unstable/http"
  4. import { Image, ImageClient } from "../../src/index.js"
  5. import { ZAI } from "../../src/providers.js"
  6. import { it } from "../lib/effect.js"
  7. import { dynamicResponse, fixedResponse } from "../lib/http.js"
  8. describe("Z.ai Images", () => {
  9. it.effect("generates through the Z.ai Images API", () =>
  10. Effect.gen(function* () {
  11. const response = yield* Image.generate({
  12. model: ZAI.configure({
  13. apiKey: "test",
  14. baseURL: "https://api.z.ai.test/api/paas/v4",
  15. headers: { "x-default": "yes" },
  16. http: { body: { configured: true, quality: "configured" }, query: { trace: "default" } },
  17. }).image("glm-image"),
  18. prompt: "A red circle on a white background",
  19. options: {
  20. quality: "hd",
  21. userID: "alias-user",
  22. user_id: "raw-user",
  23. future_option: true,
  24. },
  25. http: {
  26. headers: { "x-request": "yes" },
  27. query: { trace: "request" },
  28. body: { quality: "final", user_id: "final-user" },
  29. },
  30. })
  31. expect(response.images).toHaveLength(1)
  32. expect(response.image?.mediaType).toBe("application/octet-stream")
  33. expect(response.image?.data).toBe("https://cdn.z.ai/generated.png")
  34. expect(response.providerMetadata).toEqual({
  35. zai: {
  36. created: 1_760_335_349,
  37. id: "generation-1",
  38. requestID: "request-1",
  39. contentFilter: [{ role: "future-role", level: 4.5 }],
  40. },
  41. })
  42. }).pipe(
  43. Effect.provide(
  44. ImageClient.layer.pipe(
  45. Layer.provide(
  46. dynamicResponse((input) =>
  47. Effect.gen(function* () {
  48. const request = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
  49. expect(request.url).toBe("https://api.z.ai.test/api/paas/v4/images/generations?trace=request")
  50. expect(request.headers.get("authorization")).toBe("Bearer test")
  51. expect(request.headers.get("x-default")).toBe("yes")
  52. expect(request.headers.get("x-request")).toBe("yes")
  53. expect(JSON.parse(input.text)).toEqual({
  54. model: "glm-image",
  55. prompt: "A red circle on a white background",
  56. quality: "final",
  57. user_id: "final-user",
  58. future_option: true,
  59. configured: true,
  60. })
  61. return input.respond(
  62. JSON.stringify({
  63. created: 1_760_335_349,
  64. id: "generation-1",
  65. request_id: "request-1",
  66. data: [{ url: "https://cdn.z.ai/generated.png" }],
  67. content_filter: [{ role: "future-role", level: 4.5 }],
  68. }),
  69. { headers: { "content-type": "application/json" } },
  70. )
  71. }),
  72. ),
  73. ),
  74. ),
  75. ),
  76. ),
  77. )
  78. it.effect("lets raw native options override aliases", () =>
  79. Image.generate({
  80. model: ZAI.configure({ apiKey: "test" }).image("model"),
  81. prompt: "test",
  82. options: { quality: "future-quality", userID: "x", user_id: "raw-user" },
  83. }).pipe(
  84. Effect.provide(
  85. ImageClient.layer.pipe(
  86. Layer.provide(
  87. dynamicResponse((input) => {
  88. expect(JSON.parse(input.text)).toMatchObject({ quality: "future-quality", user_id: "raw-user" })
  89. return Effect.succeed(
  90. input.respond(JSON.stringify({ data: [{ url: "https://example.test/image.jpg" }] }), {
  91. headers: { "content-type": "application/json" },
  92. }),
  93. )
  94. }),
  95. ),
  96. ),
  97. ),
  98. ),
  99. )
  100. it.effect("rejects invalid response structures", () =>
  101. Effect.gen(function* () {
  102. const model = ZAI.configure({ apiKey: "test" }).image("model")
  103. const payloads = [
  104. {},
  105. { data: [] },
  106. { data: [{ b64_json: "image" }] },
  107. { data: [{ url: 1 }] },
  108. { data: [{ url: "https://example.test/image.jpg" }], content_filter: [{ role: 1, level: "high" }] },
  109. ]
  110. yield* Effect.forEach(payloads, (payload) =>
  111. Image.generate({ model, prompt: "test" }).pipe(
  112. Effect.provide(
  113. ImageClient.layer.pipe(
  114. Layer.provide(
  115. fixedResponse(JSON.stringify(payload), { headers: { "content-type": "application/json" } }),
  116. ),
  117. ),
  118. ),
  119. Effect.flip,
  120. Effect.tap((error) => Effect.sync(() => expect(error.reason._tag).toBe("InvalidProviderOutput"))),
  121. ),
  122. )
  123. }),
  124. )
  125. })