Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/sdk/src/effect/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,17 @@ export const make = (options?: ClientOptions) =>
})
.pipe(
Effect.flatMap(HttpClientResponse.filterStatusOk),
Effect.flatMap((response) => response.json),
Effect.map((data) => data as A),
Effect.flatMap((response) => response.text),
Effect.mapError((cause) => new ModelsDevError({ cause })),
Effect.flatMap((text) =>
Effect.try({
try: () => {
if (text === "") throw new SyntaxError("Unexpected end of JSON input")
return JSON.parse(text) as A
},
catch: (cause) => new ModelsDevError({ cause }),
}),
),
)

return {
Expand Down
19 changes: 17 additions & 2 deletions packages/sdk/test/effect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Effect, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { Models, ModelsDevError } from "../src/effect.js"

function stub(data: unknown, init?: ResponseInit) {
function stubResponse(body: string | null, init?: ResponseInit) {
const requests: Request[] = []
const fetch = (async (input: Parameters<typeof globalThis.fetch>[0], requestInit?: RequestInit) => {
requests.push(new Request(input instanceof URL ? input.href : (input as string), requestInit))
return new Response(JSON.stringify(data), {
return new Response(body, {
headers: { "content-type": "application/json" },
...init,
})
Expand All @@ -16,6 +16,10 @@ function stub(data: unknown, init?: ResponseInit) {
return { requests, layer }
}

function stub(data: unknown, init?: ResponseInit) {
return stubResponse(JSON.stringify(data) ?? null, init)
}

test("providers() succeeds through an injected transport", async () => {
const { requests, layer } = stub({ anthropic: { id: "anthropic" } })
const program = Effect.gen(function* () {
Expand Down Expand Up @@ -63,6 +67,17 @@ test("non-2xx fails with ModelsDevError in the error channel", async () => {
expect(error._tag).toBe("ModelsDevError")
})

test("empty body fails with ModelsDevError", async () => {
const { layer } = stubResponse("")
const program = Effect.gen(function* () {
const client = yield* Models.make()
return yield* client.providers()
})
const error = await program.pipe(Effect.flip, Effect.provide(layer), Effect.runPromise)
expect(error).toBeInstanceOf(ModelsDevError)
expect(error._tag).toBe("ModelsDevError")
})

test("Service and layer provide a shared client", async () => {
const { requests, layer } = stub({ "openai/gpt-oss-120b": { id: "openai/gpt-oss-120b" } })
const program = Effect.gen(function* () {
Expand Down
Loading