mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-10 23:03:34 +00:00
89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
export * as Generate from "./generate"
|
|
|
|
import { LLM, LLMClient, AIError } from "@opencode-ai/ai"
|
|
import { Context, Effect, Layer, Schema } from "effect"
|
|
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
|
import { llmClient } from "./effect/app-node-platform"
|
|
import { ModelResolver } from "./model-resolver"
|
|
import { Model } from "./model"
|
|
|
|
export interface TextInput {
|
|
readonly prompt: string
|
|
readonly model?: Model.Ref
|
|
}
|
|
|
|
export class ModelSelectionError extends Schema.TaggedErrorClass<ModelSelectionError>()(
|
|
"Generate.ModelSelectionError",
|
|
{ message: Schema.String },
|
|
) {}
|
|
|
|
export class UnavailableError extends Schema.TaggedErrorClass<UnavailableError>()("Generate.UnavailableError", {
|
|
message: Schema.String,
|
|
service: Schema.optional(Schema.String),
|
|
}) {}
|
|
|
|
export type Error = ModelSelectionError | UnavailableError
|
|
|
|
export interface Interface {
|
|
readonly text: (input: TextInput) => Effect.Effect<string, Error>
|
|
}
|
|
|
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Generate") {}
|
|
|
|
export const layer = Layer.effect(
|
|
Service,
|
|
Effect.gen(function* () {
|
|
const llm = yield* LLMClient.Service
|
|
const resolver = yield* ModelResolver.Service
|
|
|
|
const runText = Effect.fn("Generate.text")(function* (input: TextInput) {
|
|
const resolved = yield* resolver.resolve(input.model).pipe(
|
|
Effect.catchTags({
|
|
"SessionRunnerModel.VariantUnavailableError": (error) =>
|
|
input.model
|
|
? new ModelSelectionError({ message: error.message })
|
|
: new UnavailableError({ message: error.message, service: error.providerID }),
|
|
"SessionRunnerModel.UnsupportedPackageError": (error) =>
|
|
input.model
|
|
? new ModelSelectionError({ message: error.message })
|
|
: new UnavailableError({ message: error.message, service: error.providerID }),
|
|
}),
|
|
)
|
|
if (!resolved)
|
|
return yield* new ModelSelectionError({
|
|
message: input.model
|
|
? `Model unavailable: ${input.model.providerID}/${input.model.id}`
|
|
: "No model specified and no supported model is available",
|
|
})
|
|
const response = yield* llm.generate(LLM.request({ model: resolved.model, prompt: input.prompt })).pipe(
|
|
Effect.mapError(
|
|
(error: AIError) =>
|
|
new UnavailableError({
|
|
message: error.message,
|
|
service: resolved.ref.providerID,
|
|
}),
|
|
),
|
|
)
|
|
return response.text
|
|
})
|
|
|
|
const text: Interface["text"] = (input) =>
|
|
runText(input).pipe(
|
|
Effect.catchTag(
|
|
"Integration.Authorization",
|
|
() =>
|
|
new UnavailableError({
|
|
message: "Generation credentials are unavailable",
|
|
}),
|
|
),
|
|
)
|
|
|
|
return Service.of({ text })
|
|
}),
|
|
)
|
|
|
|
export const node = makeLocationNode({
|
|
service: Service,
|
|
layer,
|
|
deps: [ModelResolver.node, llmClient],
|
|
})
|