import { Config, Effect, Formatter, Layer, Schema, Stream } from "effect"
import { LLM, LLMClient, Provider, ProviderID, Tool, type ProviderModelOptions } from "@opencode-ai/llm"
import { Route, Auth, Endpoint, Framing, Protocol, RequestExecutor } from "@opencode-ai/llm/route"
import { OpenAI } from "@opencode-ai/llm/providers"
* A runnable walkthrough of the LLM package use-site API.
*
* Run from `packages/llm` with an OpenAI key in the environment:
*
* OPENAI_API_KEY=... bun example/tutorial.ts
*
* The file is intentionally written as a normal TypeScript program. You can
* hover imports and local values to see how the public API is typed.
*/
const apiKey = Config.redacted("OPENAI_API_KEY")
const model = OpenAI.model("gpt-4o-mini", {
apiKey,
generation: { maxTokens: 160 },
providerOptions: {
openai: { store: false },
},
})
const request = LLM.request({
model,
system: "You are concise and practical.",
prompt: "Tell me a joke",
generation: { maxTokens: 80, temperature: 0.7 },
providerOptions: {
openai: { promptCacheKey: "tutorial-joke" },
},
})
const rawOverlayExample = LLM.request({
model,
prompt: "Show the final HTTP overlay shape.",
http: {
body: { metadata: { example: "tutorial" } },
headers: { "x-opencode-tutorial": "1" },
query: { debug: "1" },
},
})
const generateOnce = Effect.gen(function* () {
const response = yield* LLM.generate(request)
console.log("\n== generate ==")
console.log("generated text:", response.text)
console.log("usage", Formatter.formatJson(response.usage, { space: 2 }))
})
const streamText = LLM.stream(request).pipe(
Stream.tap((event) =>
Effect.sync(() => {
if (event.type === "text-delta") process.stdout.write(`\ntext: ${event.text}`)
if (event.type === "finish") process.stdout.write(`\nfinish: ${event.reason}\n`)
}),
),
Stream.runDrain,
)
const tools = {
get_weather: Tool.make({
description: "Get current weather for a city.",
parameters: Schema.Struct({ city: Schema.String }),
success: Schema.Struct({ forecast: Schema.String }),
execute: (input) => Effect.succeed({ forecast: `${input.city}: sunny, 72F` }),
}),
}
const streamWithTools = LLM.stream({
request: LLM.request({
model,
prompt: "Use get_weather for San Francisco, then answer in one sentence.",
generation: { maxTokens: 80, temperature: 0 },
}),
tools,
stopWhen: LLM.stepCountIs(3),
}).pipe(
Stream.tap((event) =>
Effect.sync(() => {
if (event.type === "tool-call") console.log("tool call", event.name, event.input)
if (event.type === "tool-result") console.log("tool result", event.name, event.result)
if (event.type === "text-delta") process.stdout.write(event.text)
}),
),
Stream.runDrain,
)
const WeatherReport = Schema.Struct({
city: Schema.String,
forecast: Schema.String,
highFahrenheit: Schema.Number,
})
const generateStructuredObject = Effect.gen(function* () {
const response = yield* LLM.generateObject({
model,
system: "Return only structured weather data.",
prompt: "Give me today's weather for San Francisco.",
schema: WeatherReport,
generation: { maxTokens: 120, temperature: 0 },
})
console.log("\n== generateObject ==")
console.log(Formatter.formatJson(response.object, { space: 2 }))
})
const generateDynamicObject = LLM.generateObject({
model,
prompt: "Extract the city and forecast from: San Francisco is sunny.",
jsonSchema: {
type: "object",
properties: {
city: { type: "string" },
forecast: { type: "string" },
},
required: ["city", "forecast"],
},
})
const FakeBody = Schema.Struct({
model: Schema.String,
input: Schema.String,
})
type FakeBody = Schema.Schema.Type<typeof FakeBody>
const FakeProtocol = Protocol.make<FakeBody, string, string, void>({
id: "fake-echo",
body: {
schema: FakeBody,
from: (request) =>
Effect.succeed({
model: request.model.id,
input: request.messages
.flatMap((message) => message.content)
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n"),
}),
},
stream: {
event: Schema.String,
initial: () => undefined,
step: (_, frame) => Effect.succeed([undefined, [{ type: "text-delta", id: "text-0", text: frame }]] as const),
onHalt: () => [{ type: "finish", reason: "stop" }],
},
})
const FakeAdapter = Route.make({
id: "fake-echo",
protocol: FakeProtocol,
endpoint: Endpoint.path("/v1/echo"),
auth: Auth.passthrough,
framing: Framing.sse,
})
const fakeEchoModel = Route.model(FakeAdapter, { provider: "fake-echo", baseURL: "https://fake.local" })
const FakeEcho = Provider.make({
id: ProviderID.make("fake-echo"),
model: (id: string, options: ProviderModelOptions = {}) => fakeEchoModel({ id, ...options }),
})
const inspectFakeProvider = Effect.gen(function* () {
const prepared = yield* LLMClient.prepare(
LLM.request({
model: FakeEcho.model("tiny-echo"),
prompt: "Show me the provider pipeline.",
}),
)
console.log("\n== fake provider prepare ==")
console.log("route:", prepared.route)
console.log("body:", Formatter.formatJson(prepared.body, { space: 2 }))
})
const requestExecutorLayer = RequestExecutor.defaultLayer
const llmClientLayer = LLMClient.layer.pipe(Layer.provide(requestExecutorLayer))
const program = Effect.gen(function* () {
yield* streamWithTools
}).pipe(Effect.provide(Layer.mergeAll(requestExecutorLayer, llmClientLayer)))
Effect.runPromise(program)