| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761 |
- import { describe, expect, test } from "bun:test"
- import { LLMEvent, ToolFailure } from "@kirincode-ai/llm"
- import { LLMClient, RequestExecutor, WebSocketExecutor, type LLMClientShape } from "@kirincode-ai/llm/route"
- import { jsonSchema, tool, type ModelMessage, type Tool } from "ai"
- import { Effect, Fiber, Layer, Stream } from "effect"
- import { FetchHttpClient } from "effect/unstable/http"
- import { LLMNative } from "@/session/llm/native-request"
- import { LLMNativeRuntime } from "@/session/llm/native-runtime"
- import type { Provider } from "@/provider/provider"
- import { OAUTH_DUMMY_KEY } from "@/auth"
- import { testEffect } from "../lib/effect"
- import { ProviderV2 } from "@kirincode-ai/core/provider"
- import { ModelV2 } from "@kirincode-ai/core/model"
- const baseModel: Provider.Model = {
- id: ModelV2.ID.make("gpt-5-mini"),
- providerID: ProviderV2.ID.make("openai"),
- api: {
- id: "gpt-5-mini",
- url: "https://api.openai.com/v1",
- npm: "@ai-sdk/openai",
- },
- name: "GPT-5 Mini",
- capabilities: {
- temperature: true,
- reasoning: true,
- attachment: true,
- toolcall: true,
- input: {
- text: true,
- audio: false,
- image: true,
- video: false,
- pdf: false,
- },
- output: {
- text: true,
- audio: false,
- image: false,
- video: false,
- pdf: false,
- },
- interleaved: false,
- },
- cost: {
- input: 0,
- output: 0,
- cache: {
- read: 0,
- write: 0,
- },
- },
- limit: {
- context: 128_000,
- input: 128_000,
- output: 32_000,
- },
- status: "active",
- options: {},
- headers: {
- "x-model": "model-header",
- },
- release_date: "2026-01-01",
- }
- const providerInfo: Provider.Info = {
- id: ProviderV2.ID.make("openai"),
- name: "OpenAI",
- source: "config",
- env: ["OPENAI_API_KEY"],
- options: { apiKey: "test-openai-key" },
- models: {},
- }
- const it = testEffect(
- LLMClient.layer.pipe(
- Layer.provide(
- Layer.mergeAll(RequestExecutor.layer.pipe(Layer.provide(FetchHttpClient.layer)), WebSocketExecutor.layer),
- ),
- ),
- )
- function responsesStream(chunks: unknown[]) {
- return new Response(chunks.map((chunk) => `data: ${JSON.stringify(chunk)}`).join("\n\n") + "\n\n", {
- status: 200,
- headers: { "Content-Type": "text/event-stream" },
- })
- }
- type NativeRequestInput = Parameters<typeof LLMNative.request>[0]
- const sessionText = (text: string) => ({ type: "text" as const, text })
- const sessionOpenAIReasoning = (
- text: string,
- options: {
- readonly storedAs: "providerMetadata" | "providerOptions"
- readonly itemId: string
- readonly encryptedContent: string | null
- },
- ) => {
- const metadata = {
- openai: { itemId: options.itemId, reasoningEncryptedContent: options.encryptedContent },
- }
- if (options.storedAs === "providerMetadata")
- return Object.assign({ type: "reasoning" as const, text }, { providerMetadata: metadata })
- return Object.assign({ type: "reasoning" as const, text }, { providerOptions: metadata })
- }
- type SessionAssistantPart = ReturnType<typeof sessionText> | ReturnType<typeof sessionOpenAIReasoning>
- const storedSession = {
- user: (content: string): ModelMessage => ({ role: "user", content }),
- assistant: (content: SessionAssistantPart[]): ModelMessage => ({ role: "assistant", content }),
- text: sessionText,
- openaiReasoning: sessionOpenAIReasoning,
- }
- const openAIResponses = {
- user: (text: string) => ({ role: "user", content: [{ type: "input_text", text }] }),
- assistant: (text: string) => ({ role: "assistant", content: [{ type: "output_text", text }] }),
- openaiReasoning: (text: string, encryptedContent: string) => ({
- type: "reasoning",
- encrypted_content: encryptedContent,
- summary: [{ type: "summary_text", text }],
- }),
- }
- const prepareNativeRequest = (input: NativeRequestInput) => LLMClient.prepare(LLMNative.request(input))
- const expectOpenAIResponsesRequest = (input: {
- readonly history: NativeRequestInput["messages"]
- readonly providerOptions?: NativeRequestInput["providerOptions"]
- readonly maxOutputTokens?: NativeRequestInput["maxOutputTokens"]
- readonly headers?: NativeRequestInput["headers"]
- readonly expectedBody: unknown
- }) =>
- Effect.gen(function* () {
- expect(
- yield* prepareNativeRequest({
- model: baseModel,
- apiKey: "test-openai-key",
- messages: input.history,
- providerOptions: input.providerOptions,
- maxOutputTokens: input.maxOutputTokens,
- headers: input.headers,
- }),
- ).toMatchObject({
- route: "openai-responses",
- protocol: "openai-responses",
- body: input.expectedBody,
- })
- })
- describe("session.llm-native.request", () => {
- test("maps normalized stream inputs to a native LLM request", () => {
- const messages: ModelMessage[] = [
- {
- role: "system",
- content: "system from messages",
- },
- {
- role: "user",
- content: [
- { type: "text", text: "hello", providerOptions: { openai: { cacheControl: { type: "ephemeral" } } } },
- { type: "file", mediaType: "image/png", filename: "img.png", data: "data:image/png;base64,Zm9v" },
- ],
- },
- {
- role: "assistant",
- content: [
- { type: "reasoning", text: "thinking", providerOptions: { openai: { encryptedContent: "secret" } } },
- { type: "text", text: "I'll run it" },
- {
- type: "tool-call",
- toolCallId: "call-1",
- toolName: "bash",
- input: { command: "ls" },
- providerOptions: { openai: { itemId: "item-1" } },
- },
- ],
- },
- {
- role: "tool",
- content: [
- {
- type: "tool-result",
- toolCallId: "call-1",
- toolName: "bash",
- output: { type: "text", value: "ok" },
- providerOptions: { openai: { outputId: "output-1" } },
- },
- ],
- },
- ]
- const request = LLMNative.request({
- model: baseModel,
- system: ["agent system"],
- messages,
- tools: {
- bash: tool({
- description: "Run a shell command",
- inputSchema: jsonSchema({
- type: "object",
- properties: {
- command: { type: "string" },
- },
- required: ["command"],
- }),
- }),
- },
- toolChoice: "required",
- temperature: 0.2,
- topP: 0.9,
- topK: 40,
- maxOutputTokens: 1024,
- providerOptions: { openai: { store: false } },
- headers: { "x-request": "request-header" },
- })
- expect(request.model).toMatchObject({
- id: "gpt-5-mini",
- provider: "openai",
- route: { id: "openai-responses" },
- })
- expect(request.model.route.endpoint.baseURL).toBe("https://api.openai.com/v1")
- expect(request.model.route.defaults.headers).toEqual({
- "x-model": "model-header",
- "x-request": "request-header",
- })
- expect(request.model.route.defaults.limits).toMatchObject({
- context: 128_000,
- output: 32_000,
- })
- expect(request.system).toEqual([
- { type: "text", text: "agent system" },
- { type: "text", text: "system from messages" },
- ])
- expect(request.generation).toMatchObject({
- temperature: 0.2,
- topP: 0.9,
- topK: 40,
- maxTokens: 1024,
- })
- expect(request.providerOptions).toEqual({ openai: { store: false } })
- expect(request.toolChoice).toMatchObject({ type: "required" })
- expect(request.tools).toMatchObject([
- {
- name: "bash",
- description: "Run a shell command",
- inputSchema: {
- type: "object",
- properties: {
- command: { type: "string" },
- },
- required: ["command"],
- },
- },
- ])
- expect(request.messages).toMatchObject([
- {
- role: "user",
- content: [
- { type: "text", text: "hello", providerMetadata: { openai: { cacheControl: { type: "ephemeral" } } } },
- { type: "media", mediaType: "image/png", filename: "img.png", data: "data:image/png;base64,Zm9v" },
- ],
- },
- {
- role: "assistant",
- content: [
- { type: "reasoning", text: "thinking", providerMetadata: { openai: { encryptedContent: "secret" } } },
- { type: "text", text: "I'll run it" },
- {
- type: "tool-call",
- id: "call-1",
- name: "bash",
- input: { command: "ls" },
- providerMetadata: { openai: { itemId: "item-1" } },
- },
- ],
- },
- {
- role: "tool",
- content: [
- {
- type: "tool-result",
- id: "call-1",
- name: "bash",
- result: { type: "text", value: "ok" },
- providerMetadata: { openai: { outputId: "output-1" } },
- },
- ],
- },
- ])
- })
- test("maps stored provider metadata to native content metadata", () => {
- const reasoning = Object.assign(
- { type: "reasoning" as const, text: "thinking" },
- {
- providerMetadata: {
- openai: {
- itemId: "rs_1",
- reasoningEncryptedContent: "encrypted-state",
- },
- },
- },
- )
- const request = LLMNative.request({
- model: baseModel,
- messages: [
- {
- role: "assistant",
- content: [reasoning],
- },
- ],
- })
- expect(request.messages).toMatchObject([
- {
- role: "assistant",
- content: [
- {
- type: "reasoning",
- text: "thinking",
- providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
- },
- ],
- },
- ])
- })
- test("selects native request routes for provider packages", () => {
- const openai = LLMNative.model({
- model: { ...baseModel, api: { ...baseModel.api, url: "", npm: "@ai-sdk/openai" } },
- apiKey: "test-key",
- messages: [],
- })
- expect(openai.route.id).toBe("openai-responses")
- expect(openai.route.endpoint.baseURL).toBe("https://api.openai.com/v1")
- const anthropic = LLMNative.model({
- model: { ...baseModel, api: { ...baseModel.api, url: "", npm: "@ai-sdk/anthropic" } },
- apiKey: "test-key",
- messages: [],
- })
- expect(anthropic.route.id).toBe("anthropic-messages")
- expect(anthropic.route.endpoint.baseURL).toBe("https://api.anthropic.com/v1")
- const google = LLMNative.model({
- model: { ...baseModel, api: { ...baseModel.api, url: "", npm: "@ai-sdk/google" } },
- apiKey: "test-key",
- messages: [],
- })
- expect(google.route.id).toBe("gemini")
- expect(google.route.endpoint.baseURL).toBe("https://generativelanguage.googleapis.com/v1beta")
- const compatible = LLMNative.model({
- model: {
- ...baseModel,
- providerID: ProviderV2.ID.make("kirincode"),
- api: { ...baseModel.api, url: "https://ai.example.test/v1", npm: "@ai-sdk/openai-compatible" },
- },
- apiKey: "test-key",
- messages: [],
- })
- expect(compatible.route.id).toBe("openai-compatible-chat")
- expect(compatible.route.endpoint.baseURL).toBe("https://ai.example.test/v1")
- const openrouter = LLMNative.model({
- model: { ...baseModel, api: { ...baseModel.api, url: "", npm: "@openrouter/ai-sdk-provider" } },
- apiKey: "test-key",
- messages: [],
- })
- expect(openrouter.route.id).toBe("openrouter")
- expect(openrouter.route.endpoint.baseURL).toBe("https://openrouter.ai/api/v1")
- })
- test("fails fast for unsupported provider packages", () => {
- expect(() =>
- LLMNative.request({
- model: { ...baseModel, api: { ...baseModel.api, npm: "unknown-provider" } },
- messages: [],
- }),
- ).toThrow("Native LLM request adapter does not support provider package unknown-provider")
- })
- test("only enables native runtime for supported OpenAI API-key models", () => {
- expect(LLMNativeRuntime.status({ model: baseModel, provider: providerInfo, auth: undefined })).toMatchObject({
- type: "supported",
- apiKey: "test-openai-key",
- })
- expect(
- LLMNativeRuntime.status({
- model: { ...baseModel, providerID: ProviderV2.ID.make("kirincode") },
- provider: { ...providerInfo, id: ProviderV2.ID.make("kirincode") },
- auth: undefined,
- }),
- ).toMatchObject({
- type: "supported",
- apiKey: "test-openai-key",
- })
- expect(
- LLMNativeRuntime.status({
- model: {
- ...baseModel,
- providerID: ProviderV2.ID.make("kirincode"),
- api: { ...baseModel.api, npm: "@ai-sdk/openai-compatible" },
- },
- provider: { ...providerInfo, id: ProviderV2.ID.make("kirincode") },
- auth: undefined,
- }),
- ).toMatchObject({
- type: "supported",
- apiKey: "test-openai-key",
- })
- expect(
- LLMNativeRuntime.status({
- model: { ...baseModel, providerID: ProviderV2.ID.make("google") },
- provider: { ...providerInfo, id: ProviderV2.ID.make("google") },
- auth: undefined,
- }),
- ).toEqual({ type: "unsupported", reason: "provider is not openai, opencode, or anthropic" })
- expect(
- LLMNativeRuntime.status({
- model: baseModel,
- provider: providerInfo,
- auth: { type: "oauth", refresh: "refresh", access: "access", expires: 1 },
- }),
- ).toEqual({ type: "unsupported", reason: "OAuth auth requires a provider fetch override" })
- expect(
- LLMNativeRuntime.status({
- model: baseModel,
- provider: { ...providerInfo, options: { apiKey: OAUTH_DUMMY_KEY, fetch: async () => new Response() } },
- auth: { type: "oauth", refresh: "refresh", access: "access", expires: 1 },
- }),
- ).toMatchObject({ type: "supported", apiKey: OAUTH_DUMMY_KEY })
- expect(
- LLMNativeRuntime.status({
- model: { ...baseModel, api: { ...baseModel.api, npm: "@ai-sdk/google" } },
- provider: providerInfo,
- auth: undefined,
- }),
- ).toEqual({ type: "unsupported", reason: "provider package is not OpenAI, OpenAI-compatible, or Anthropic" })
- expect(
- LLMNativeRuntime.status({
- model: baseModel,
- provider: { ...providerInfo, options: {} },
- auth: undefined,
- }),
- ).toEqual({ type: "unsupported", reason: "API key is not configured" })
- })
- test("enables native runtime for Anthropic API-key models", () => {
- expect(
- LLMNativeRuntime.status({
- model: {
- ...baseModel,
- providerID: ProviderV2.ID.make("anthropic"),
- api: { ...baseModel.api, npm: "@ai-sdk/anthropic", url: "https://api.anthropic.com/v1" },
- },
- provider: {
- ...providerInfo,
- id: ProviderV2.ID.make("anthropic"),
- name: "Anthropic",
- env: ["ANTHROPIC_API_KEY"],
- options: { apiKey: "test-anthropic-key" },
- },
- auth: undefined,
- }),
- ).toMatchObject({ type: "supported", apiKey: "test-anthropic-key" })
- })
- test("prefers console provider api key over stored kirincode auth", () => {
- expect(
- LLMNativeRuntime.status({
- model: { ...baseModel, providerID: ProviderV2.ID.make("kirincode") },
- provider: {
- ...providerInfo,
- id: ProviderV2.ID.make("kirincode"),
- options: { apiKey: "console-token" },
- key: "zen-token",
- },
- auth: { type: "api", key: "zen-token" },
- }),
- ).toMatchObject({
- type: "supported",
- apiKey: "console-token",
- })
- expect(
- LLMNativeRuntime.status({
- model: baseModel,
- provider: { ...providerInfo, options: {}, key: "provider-key" },
- auth: undefined,
- }),
- ).toMatchObject({
- type: "supported",
- apiKey: "provider-key",
- })
- })
- it.effect("native tool wrapper converts thrown errors into typed ToolFailure", () =>
- Effect.gen(function* () {
- const wrapped = LLMNativeRuntime.nativeTools(
- {
- explode: {
- description: "always throws",
- inputSchema: jsonSchema({ type: "object" }),
- execute: async () => {
- throw new Error("boom")
- },
- } satisfies Tool,
- },
- { messages: [] as ModelMessage[], abort: new AbortController().signal },
- )
- const failure = yield* Effect.flip(wrapped.explode.execute({}, { id: "call-1", name: "explode" }))
- expect(failure).toBeInstanceOf(ToolFailure)
- expect(failure.message).toBe("boom")
- }),
- )
- it.effect("native tool wrapper raises ToolFailure when the source tool has no execute handler", () =>
- Effect.gen(function* () {
- // The AI SDK Tool shape allows execute to be omitted (e.g., client-side / MCP tools).
- // The native runtime owns execution, so encountering such a tool here means upstream
- // wiring is wrong; we want a typed failure, not a silent skip or unhandled exception.
- const wrapped = LLMNativeRuntime.nativeTools(
- { incomplete: { description: "no execute", inputSchema: jsonSchema({ type: "object" }) } satisfies Tool },
- { messages: [] as ModelMessage[], abort: new AbortController().signal },
- )
- const failure = yield* Effect.flip(wrapped.incomplete.execute({}, { id: "call-1", name: "incomplete" }))
- expect(failure).toBeInstanceOf(ToolFailure)
- expect(failure.message).toContain("incomplete")
- }),
- )
- it.effect("emits native tool calls before overlapping local settlements complete", () =>
- Effect.gen(function* () {
- const observed: string[] = []
- const started: string[] = []
- let release: (() => void) | undefined
- let notifyStarted: (() => void) | undefined
- const gate = new Promise<void>((resolve) => {
- release = resolve
- })
- const bothStarted = new Promise<void>((resolve) => {
- notifyStarted = resolve
- })
- const lookup = {
- description: "Lookup data",
- inputSchema: jsonSchema({ type: "object" }),
- execute: async (_args: unknown, options: { toolCallId: string }) => {
- started.push(options.toolCallId)
- if (started.length === 2) notifyStarted?.()
- await gate
- return { output: options.toolCallId }
- },
- } satisfies Tool
- const llmClient = {
- prepare: () => Effect.die("unused"),
- stream: () =>
- Stream.fromIterable([
- LLMEvent.toolCall({ id: "call-1", name: "lookup", input: {} }),
- LLMEvent.toolCall({ id: "call-2", name: "lookup", input: {} }),
- LLMEvent.finish({ reason: "tool-calls" }),
- ]),
- generate: () => Effect.die("unused"),
- } as LLMClientShape
- const native = LLMNativeRuntime.stream({
- model: baseModel,
- provider: providerInfo,
- auth: undefined,
- llmClient,
- messages: [],
- tools: { lookup },
- headers: {},
- abort: new AbortController().signal,
- })
- expect(native.type).toBe("supported")
- if (native.type === "unsupported") throw new Error(native.reason)
- const fiber = yield* native.stream.pipe(
- Stream.runForEach((event) => Effect.sync(() => observed.push(event.type))),
- Effect.forkScoped,
- )
- yield* Effect.promise(() => bothStarted)
- expect(started).toEqual(["call-1", "call-2"])
- expect(observed).toEqual(["tool-call", "tool-call", "finish"])
- release?.()
- yield* Fiber.join(fiber)
- expect(observed).toEqual(["tool-call", "tool-call", "finish", "tool-result", "tool-result"])
- }),
- )
- it.effect("compiles through the native OpenAI Responses route", () =>
- expectOpenAIResponsesRequest({
- history: [storedSession.user("hello")],
- providerOptions: { openai: { store: false, instructions: "You are concise." } },
- maxOutputTokens: 512,
- headers: { "x-request": "request-header" },
- expectedBody: {
- model: "gpt-5-mini",
- instructions: "You are concise.",
- input: [openAIResponses.user("hello")],
- max_output_tokens: 512,
- store: false,
- stream: true,
- },
- }),
- )
- it.effect("omits non-persisted OpenAI reasoning ids without encrypted state", () =>
- expectOpenAIResponsesRequest({
- history: [
- storedSession.user("What changed?"),
- storedSession.assistant([
- storedSession.openaiReasoning("Checked the previous diff.", {
- storedAs: "providerOptions",
- itemId: "rs_1",
- encryptedContent: null,
- }),
- storedSession.text("The parser changed."),
- ]),
- storedSession.user("Summarize it."),
- ],
- providerOptions: { openai: { store: false } },
- expectedBody: {
- input: [
- openAIResponses.user("What changed?"),
- openAIResponses.assistant("The parser changed."),
- openAIResponses.user("Summarize it."),
- ],
- store: false,
- },
- }),
- )
- it.effect("preserves encrypted OpenAI reasoning state through native request lowering", () =>
- expectOpenAIResponsesRequest({
- history: [
- storedSession.user("What changed?"),
- storedSession.assistant([
- storedSession.openaiReasoning("Checked the previous diff.", {
- storedAs: "providerMetadata",
- itemId: "rs_1",
- encryptedContent: "encrypted-state",
- }),
- storedSession.text("The parser changed."),
- ]),
- storedSession.user("Summarize it."),
- ],
- providerOptions: { openai: { store: false, include: ["reasoning.encrypted_content"] } },
- expectedBody: {
- input: [
- openAIResponses.user("What changed?"),
- openAIResponses.openaiReasoning("Checked the previous diff.", "encrypted-state"),
- openAIResponses.assistant("The parser changed."),
- openAIResponses.user("Summarize it."),
- ],
- include: ["reasoning.encrypted_content"],
- store: false,
- },
- }),
- )
- it.effect("preserves empty encrypted OpenAI reasoning items before tool output", () =>
- expectOpenAIResponsesRequest({
- history: [
- storedSession.assistant([
- storedSession.openaiReasoning("", {
- storedAs: "providerMetadata",
- itemId: "rs_1",
- encryptedContent: "encrypted-state",
- }),
- ]),
- ],
- providerOptions: { openai: { store: false, include: ["reasoning.encrypted_content"] } },
- expectedBody: {
- input: [{ type: "reasoning", summary: [], encrypted_content: "encrypted-state" }],
- include: ["reasoning.encrypted_content"],
- store: false,
- },
- }),
- )
- it.effect("references stored OpenAI reasoning items by id", () =>
- expectOpenAIResponsesRequest({
- history: [
- storedSession.assistant([
- storedSession.openaiReasoning("Checked the previous diff.", {
- storedAs: "providerMetadata",
- itemId: "rs_1",
- encryptedContent: null,
- }),
- ]),
- ],
- providerOptions: { openai: { store: true } },
- expectedBody: {
- input: [{ type: "item_reference", id: "rs_1" }],
- store: true,
- },
- }),
- )
- it.effect("uses provider fetch override for native OpenAI OAuth requests", () =>
- Effect.gen(function* () {
- const captures: Array<{ url: string; body: unknown }> = []
- const customFetch = Object.assign(
- async (input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]) => {
- const request = input instanceof Request ? input : new Request(input, init)
- captures.push({ url: request.url, body: await request.clone().json() })
- return responsesStream([
- { type: "response.output_text.delta", item_id: "msg_1", delta: "Hello" },
- { type: "response.completed", response: { usage: { input_tokens: 1, output_tokens: 1 } } },
- ])
- },
- { preconnect: () => undefined },
- ) satisfies typeof fetch
- const llmClient = yield* LLMClient.Service
- const native = LLMNativeRuntime.stream({
- model: baseModel,
- provider: { ...providerInfo, options: { apiKey: OAUTH_DUMMY_KEY, fetch: customFetch } },
- auth: { type: "oauth", refresh: "refresh", access: "access", expires: Date.now() + 60_000 },
- llmClient,
- messages: [{ role: "user", content: "hello" }],
- tools: {},
- providerOptions: { instructions: "You are concise." },
- headers: {},
- abort: new AbortController().signal,
- })
- expect(native.type).toBe("supported")
- if (native.type === "unsupported") throw new Error(native.reason)
- const events = Array.from(yield* native.stream.pipe(Stream.runCollect))
- expect(captures).toHaveLength(1)
- expect(captures[0]).toMatchObject({
- url: "https://api.openai.com/v1/responses",
- body: {
- model: "gpt-5-mini",
- instructions: "You are concise.",
- input: [{ role: "user", content: [{ type: "input_text", text: "hello" }] }],
- },
- })
- expect(events).toEqual(
- expect.arrayContaining([
- expect.objectContaining({ type: "text-delta", text: "Hello" }),
- expect.objectContaining({ type: "finish" }),
- ]),
- )
- }),
- )
- })
|