(upstream: FetchLike = fetch)
| 6 | |
| 7 | // Exported for testing: intercepts Cortex-specific request/response quirks. |
| 8 | export function cortexFetch(upstream: FetchLike = fetch) { |
| 9 | return async (url: string | URL | Request, init?: RequestInit): Promise<Response> => { |
| 10 | if (init?.body && typeof init.body === "string") { |
| 11 | try { |
| 12 | const body = JSON.parse(init.body) |
| 13 | if ("max_tokens" in body) { |
| 14 | body.max_completion_tokens = body.max_tokens |
| 15 | delete body.max_tokens |
| 16 | init = { ...init, body: JSON.stringify(body) } |
| 17 | } |
| 18 | } catch {} |
| 19 | } |
| 20 | |
| 21 | const response = await upstream(url, init) |
| 22 | |
| 23 | // Cortex returns 400 "conversation complete" as a normal stop condition |
| 24 | if (!response.ok && response.status === 400) { |
| 25 | try { |
| 26 | const errorData = (await response.clone().json()) as Record<string, unknown> |
| 27 | if ( |
| 28 | String(errorData.message || errorData.error || "") |
| 29 | .toLowerCase() |
| 30 | .includes("conversation complete") |
| 31 | ) { |
| 32 | return new Response( |
| 33 | JSON.stringify({ choices: [{ finish_reason: "stop", message: { content: "", role: "assistant" } }] }), |
| 34 | { status: 200, headers: new Headers({ "content-type": "application/json" }) }, |
| 35 | ) |
| 36 | } |
| 37 | } catch {} |
| 38 | } |
| 39 | |
| 40 | // Cortex returns role:"" in streaming deltas; the AI SDK schema requires "assistant" |
| 41 | if (response.body && response.headers.get("content-type")?.includes("text/event-stream")) { |
| 42 | const reader = response.body.getReader() |
| 43 | const encoder = new TextEncoder() |
| 44 | const decoder = new TextDecoder() |
| 45 | const stream = new ReadableStream({ |
| 46 | async pull(ctrl) { |
| 47 | const { done, value } = await reader.read() |
| 48 | if (done) { |
| 49 | ctrl.close() |
| 50 | return |
| 51 | } |
| 52 | ctrl.enqueue( |
| 53 | encoder.encode(decoder.decode(value, { stream: true }).replace(/"role"\s*:\s*""/g, '"role":"assistant"')), |
| 54 | ) |
| 55 | }, |
| 56 | cancel() { |
| 57 | reader.cancel() |
| 58 | }, |
| 59 | }) |
| 60 | return new Response(stream, { headers: response.headers, status: response.status }) |
| 61 | } |
| 62 | |
| 63 | return response |
| 64 | } |
| 65 | } |
no test coverage detected