feat(llm): move core to package

This commit is contained in:
Kit Langton
2026-04-25 16:52:55 -04:00
parent edd176c490
commit 79683710c0
17 changed files with 458 additions and 205 deletions
-215
View File
@@ -1,215 +0,0 @@
import { Effect, Stream } from "effect"
import type { AnyPatch, Patch, PatchInput, PatchRegistry } from "./patch"
import { context, emptyRegistry, plan, registry as makePatchRegistry, target as targetPatch } from "./patch"
import type { TargetBuilder } from "./target"
import type { Transport } from "./transport"
import type {
LLMError,
LLMEvent,
LLMRequest,
ModelRef,
PatchTrace,
PreparedRequest,
Protocol,
TransportRequest,
} from "./schema"
import { LLMResponse, NoAdapterError, PreparedRequest as PreparedRequestSchema } from "./schema"
interface Compiled<Target> {
readonly request: LLMRequest
readonly target: Target
readonly transport: TransportRequest
readonly patchTrace: ReadonlyArray<PatchTrace>
}
export interface TransportContext {
readonly request: LLMRequest
readonly patchTrace: ReadonlyArray<PatchTrace>
}
export interface RaiseState {
readonly request: LLMRequest
readonly patchTrace: ReadonlyArray<PatchTrace>
}
export interface Adapter<Draft, Target, Chunk> {
readonly id: string
readonly protocol: Protocol
readonly builder: TargetBuilder<Draft, Target>
readonly patches: ReadonlyArray<Patch<Draft>>
readonly redact: (target: Target) => unknown
readonly prepare: (request: LLMRequest) => Effect.Effect<Draft, LLMError>
readonly toTransport: (target: Target, context: TransportContext) => Effect.Effect<TransportRequest, LLMError>
readonly parse: (response: Response) => Stream.Stream<Chunk, LLMError>
readonly raise: (chunk: Chunk, state: RaiseState) => Stream.Stream<LLMEvent, LLMError>
}
export interface AdapterInput<Draft, Target, Chunk> {
readonly id: string
readonly protocol: Protocol
readonly builder: TargetBuilder<Draft, Target>
readonly patches?: ReadonlyArray<Patch<Draft>>
readonly redact: (target: Target) => unknown
readonly prepare: (request: LLMRequest) => Effect.Effect<Draft, LLMError>
readonly toTransport: (target: Target, context: TransportContext) => Effect.Effect<TransportRequest, LLMError>
readonly parse: (response: Response) => Stream.Stream<Chunk, LLMError>
readonly raise: (chunk: Chunk, state: RaiseState) => Stream.Stream<LLMEvent, LLMError>
}
export interface AdapterDefinition<Draft, Target, Chunk> extends Adapter<Draft, Target, Chunk> {
readonly patch: (id: string, input: PatchInput<Draft>) => Patch<Draft>
readonly withPatches: (patches: ReadonlyArray<Patch<Draft>>) => AdapterDefinition<Draft, Target, Chunk>
}
export interface LLMClient {
readonly prepare: (request: LLMRequest) => Effect.Effect<PreparedRequest, LLMError>
readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, LLMError>
readonly generate: (request: LLMRequest) => Effect.Effect<LLMResponse, LLMError>
}
export interface ClientOptions<Draft, Target, Chunk> {
readonly adapter: Adapter<Draft, Target, Chunk>
readonly transport: Transport
readonly patches?: PatchRegistry | ReadonlyArray<AnyPatch>
readonly small?: boolean
readonly flags?: Record<string, string | number | boolean | undefined>
}
const assertProtocol = (model: ModelRef, adapter: { readonly protocol: Protocol }) => {
if (model.protocol === adapter.protocol) return Effect.void
return Effect.fail(new NoAdapterError({ protocol: model.protocol, provider: model.provider, model: model.id }))
}
const normalizeRegistry = (patches: PatchRegistry | ReadonlyArray<AnyPatch> | undefined): PatchRegistry => {
if (!patches) return emptyRegistry
if ("request" in patches) return patches
return makePatchRegistry(patches)
}
export function define<Draft, Target, Chunk>(input: AdapterInput<Draft, Target, Chunk>): AdapterDefinition<Draft, Target, Chunk> {
const build = (patches: ReadonlyArray<Patch<Draft>>): AdapterDefinition<Draft, Target, Chunk> => ({
id: input.id,
protocol: input.protocol,
builder: input.builder,
patches,
redact: input.redact,
prepare: input.prepare,
toTransport: input.toTransport,
parse: input.parse,
raise: input.raise,
patch: (id, patchInput) => targetPatch(`${input.id}.${id}`, patchInput),
withPatches: (next) => build([...patches, ...next]),
})
return build(input.patches ?? [])
}
export function makeClient<Draft, Target, Chunk>(options: ClientOptions<Draft, Target, Chunk>): LLMClient {
const registry = normalizeRegistry(options.patches)
const compile = Effect.fn("LLMCore.compile")(function* (request: LLMRequest) {
yield* assertProtocol(request.model, options.adapter)
const requestPlan = plan({
phase: "request",
context: context({ request, small: options.small, flags: options.flags }),
patches: registry.request,
})
const requestAfterRequestPatches = requestPlan.apply(request)
const promptPlan = plan({
phase: "prompt",
context: context({ request: requestAfterRequestPatches, small: options.small, flags: options.flags }),
patches: registry.prompt,
})
const requestBeforeToolPatches = promptPlan.apply(requestAfterRequestPatches)
const toolSchemaPlan = plan({
phase: "tool-schema",
context: context({ request: requestBeforeToolPatches, small: options.small, flags: options.flags }),
patches: registry.toolSchema,
})
const patchedRequest =
requestBeforeToolPatches.tools.length === 0
? requestBeforeToolPatches
: { ...requestBeforeToolPatches, tools: requestBeforeToolPatches.tools.map(toolSchemaPlan.apply) }
const patchContext = context({ request: patchedRequest, small: options.small, flags: options.flags })
const draft = yield* options.adapter.prepare(patchedRequest)
const targetPlan = plan({
phase: "target",
context: patchContext,
patches: [...options.adapter.patches, ...(registry.target as ReadonlyArray<Patch<Draft>>)],
})
const target = yield* options.adapter.builder.validate(targetPlan.apply(draft))
const targetPatchTrace = [
...requestPlan.trace,
...promptPlan.trace,
...(requestBeforeToolPatches.tools.length === 0 ? [] : toolSchemaPlan.trace),
...targetPlan.trace,
]
const rawTransport = yield* options.adapter.toTransport(target, { request: patchedRequest, patchTrace: targetPatchTrace })
const transportPlan = plan({
phase: "transport",
context: patchContext,
patches: registry.transport,
})
const patchTrace = [...targetPatchTrace, ...transportPlan.trace]
const transport = transportPlan.apply(rawTransport)
return { request: patchedRequest, target, transport, patchTrace }
})
const prepare = Effect.fn("LLMCore.prepare")(function* (request: LLMRequest) {
const compiled = yield* compile(request)
return new PreparedRequestSchema({
id: compiled.request.id ?? "request",
adapter: options.adapter.id,
model: compiled.request.model,
target: compiled.target,
redactedTarget: options.adapter.redact(compiled.target),
transport: compiled.transport,
patchTrace: compiled.patchTrace,
})
})
const stream = (request: LLMRequest) =>
Stream.unwrap(
Effect.gen(function* () {
const compiled = yield* compile(request)
const response = yield* options.transport.fetch(compiled.transport)
const streamPlan = plan({
phase: "stream",
context: context({ request: compiled.request, small: options.small, flags: options.flags }),
patches: registry.stream,
})
const events = options.adapter.parse(response).pipe(
Stream.flatMap((chunk) =>
options.adapter.raise(chunk, {
request: compiled.request,
patchTrace: compiled.patchTrace,
}),
),
)
if (streamPlan.patches.length === 0) return events
return events.pipe(Stream.map(streamPlan.apply))
}),
)
const generate = Effect.fn("LLMCore.generate")(function* (request: LLMRequest) {
const events = Array.from(yield* stream(request).pipe(Stream.runCollect))
const usage = events.reduce<LLMResponse["usage"]>(
(last, event) => ("usage" in event && event.usage !== undefined ? event.usage : last),
undefined,
)
return new LLMResponse({ events, usage })
})
return { prepare, stream, generate }
}
export const client = makeClient
export const Adapter = {
define,
}
export * as LLMCore from "./adapter"
-187
View File
@@ -1,187 +0,0 @@
import type { LLMEvent, LLMRequest, ModelRef, PatchPhase, Protocol, ToolDefinition, TransportRequest } from "./schema"
import { PatchTrace } from "./schema"
export interface PatchContext {
readonly request: LLMRequest
readonly model: ModelRef
readonly protocol: ModelRef["protocol"]
readonly small: boolean
readonly flags: Record<string, string | number | boolean | undefined>
}
export interface Patch<A> {
readonly id: string
readonly phase: PatchPhase
readonly reason: string
readonly order?: number
readonly when: (context: PatchContext) => boolean
readonly apply: (value: A, context: PatchContext) => A
}
export interface AnyPatch {
readonly id: string
readonly phase: PatchPhase
readonly reason: string
readonly order?: number
readonly when: (context: PatchContext) => boolean
readonly apply: (value: never, context: PatchContext) => unknown
}
export interface PatchInput<A> {
readonly reason: string
readonly order?: number
readonly when?: PatchPredicate | ((context: PatchContext) => boolean)
readonly apply: (value: A, context: PatchContext) => A
}
export interface PatchPredicate {
(context: PatchContext): boolean
readonly and: (...predicates: ReadonlyArray<PatchPredicate>) => PatchPredicate
readonly or: (...predicates: ReadonlyArray<PatchPredicate>) => PatchPredicate
readonly not: () => PatchPredicate
}
export interface PatchPlan<A> {
readonly phase: PatchPhase
readonly patches: ReadonlyArray<Patch<A>>
readonly trace: ReadonlyArray<PatchTrace>
readonly apply: (value: A) => A
}
export interface PatchRegistry {
readonly request: ReadonlyArray<Patch<LLMRequest>>
readonly prompt: ReadonlyArray<Patch<LLMRequest>>
readonly toolSchema: ReadonlyArray<Patch<ToolDefinition>>
readonly target: ReadonlyArray<Patch<unknown>>
readonly transport: ReadonlyArray<Patch<TransportRequest>>
readonly stream: ReadonlyArray<Patch<LLMEvent>>
}
export const emptyRegistry: PatchRegistry = {
request: [],
prompt: [],
toolSchema: [],
target: [],
transport: [],
stream: [],
}
export const predicate = (run: (context: PatchContext) => boolean): PatchPredicate => {
const self = Object.assign(run, {
and: (...predicates: ReadonlyArray<PatchPredicate>) =>
predicate((context) => self(context) && predicates.every((item) => item(context))),
or: (...predicates: ReadonlyArray<PatchPredicate>) =>
predicate((context) => self(context) || predicates.some((item) => item(context))),
not: () => predicate((context) => !self(context)),
})
return self
}
export const Model = {
provider: (provider: string) => predicate((context) => context.model.provider === provider),
protocol: (protocol: Protocol) => predicate((context) => context.protocol === protocol),
id: (id: string) => predicate((context) => context.model.id === id),
idIncludes: (value: string) => predicate((context) => context.model.id.toLowerCase().includes(value.toLowerCase())),
}
export const Request = {
small: () => predicate((context) => context.small),
flag: (name: string) => predicate((context) => context.flags[name] === true),
}
export const make = <A>(id: string, phase: PatchPhase, input: PatchInput<A>): Patch<A> => ({
id,
phase,
reason: input.reason,
order: input.order,
when: input.when ?? (() => true),
apply: input.apply,
})
export const request = (id: string, input: PatchInput<LLMRequest>) => make(`request.${id}`, "request", input)
export const prompt = (id: string, input: PatchInput<LLMRequest>) => make(`prompt.${id}`, "prompt", input)
export const toolSchema = (id: string, input: PatchInput<ToolDefinition>) => make(`schema.${id}`, "tool-schema", input)
export const target = <A>(id: string, input: PatchInput<A>) => make(`target.${id}`, "target", input)
export const transport = (id: string, input: PatchInput<TransportRequest>) => make(`transport.${id}`, "transport", input)
export const stream = (id: string, input: PatchInput<LLMEvent>) => make(`stream.${id}`, "stream", input)
export function registry(patches: ReadonlyArray<AnyPatch>): PatchRegistry {
return {
request: patches.filter((patch): patch is Patch<LLMRequest> => patch.phase === "request"),
prompt: patches.filter((patch): patch is Patch<LLMRequest> => patch.phase === "prompt"),
toolSchema: patches.filter((patch): patch is Patch<ToolDefinition> => patch.phase === "tool-schema"),
target: patches.filter((patch) => patch.phase === "target") as unknown as ReadonlyArray<Patch<unknown>>,
transport: patches.filter((patch): patch is Patch<TransportRequest> => patch.phase === "transport"),
stream: patches.filter((patch): patch is Patch<LLMEvent> => patch.phase === "stream"),
}
}
export const Patch = {
make,
request,
prompt,
toolSchema,
target,
transport,
stream,
registry,
}
export function context(input: {
readonly request: LLMRequest
readonly small?: boolean
readonly flags?: Record<string, string | number | boolean | undefined>
}): PatchContext {
return {
request: input.request,
model: input.request.model,
protocol: input.request.model.protocol,
small: input.small ?? false,
flags: input.flags ?? {},
}
}
export function plan<A>(input: {
readonly phase: PatchPhase
readonly context: PatchContext
readonly patches: ReadonlyArray<Patch<A>>
}): PatchPlan<A> {
const patches = input.patches
.filter((patch) => patch.phase === input.phase && patch.when(input.context))
.toSorted((left, right) => (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id))
return {
phase: input.phase,
patches,
trace: patches.map(
(patch) =>
new PatchTrace({
id: patch.id,
phase: patch.phase,
reason: patch.reason,
}),
),
apply: (value) => patches.reduce((next, patch) => patch.apply(next, input.context), value),
}
}
export function mergeRegistries(registries: ReadonlyArray<PatchRegistry>): PatchRegistry {
return registries.reduce(
(merged, registry) => ({
request: [...merged.request, ...registry.request],
prompt: [...merged.prompt, ...registry.prompt],
toolSchema: [...merged.toolSchema, ...registry.toolSchema],
target: [...merged.target, ...registry.target],
transport: [...merged.transport, ...registry.transport],
stream: [...merged.stream, ...registry.stream],
}),
emptyRegistry,
)
}
export * as LLMCorePatch from "./patch"
-424
View File
@@ -1,424 +0,0 @@
import { Schema } from "effect"
export const Protocol = Schema.Literals([
"openai-chat",
"openai-responses",
"anthropic-messages",
"gemini",
"bedrock-converse",
])
export type Protocol = Schema.Schema.Type<typeof Protocol>
export const ReasoningEffort = Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh", "max"])
export type ReasoningEffort = Schema.Schema.Type<typeof ReasoningEffort>
export const TargetSlot = Schema.Literals([
"model",
"system",
"messages",
"tools",
"tool-choice",
"generation",
"reasoning",
"cache",
"response-format",
"headers",
"extensions",
])
export type TargetSlot = Schema.Schema.Type<typeof TargetSlot>
export const PatchPhase = Schema.Literals(["request", "prompt", "tool-schema", "target", "transport", "stream"])
export type PatchPhase = Schema.Schema.Type<typeof PatchPhase>
export const MessageRole = Schema.Literals(["user", "assistant", "tool"])
export type MessageRole = Schema.Schema.Type<typeof MessageRole>
export const FinishReason = Schema.Literals(["stop", "length", "tool-calls", "content-filter", "error", "unknown"])
export type FinishReason = Schema.Schema.Type<typeof FinishReason>
export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown)
export type JsonSchema = Schema.Schema.Type<typeof JsonSchema>
export class ModelCapabilities extends Schema.Class<ModelCapabilities>("LLM.ModelCapabilities")({
input: Schema.Struct({
text: Schema.Boolean,
image: Schema.Boolean,
audio: Schema.Boolean,
video: Schema.Boolean,
pdf: Schema.Boolean,
}),
output: Schema.Struct({
text: Schema.Boolean,
reasoning: Schema.Boolean,
}),
tools: Schema.Struct({
calls: Schema.Boolean,
streamingInput: Schema.Boolean,
providerExecuted: Schema.Boolean,
}),
cache: Schema.Struct({
prompt: Schema.Boolean,
messageBlocks: Schema.Boolean,
contentBlocks: Schema.Boolean,
}),
reasoning: Schema.Struct({
efforts: Schema.Array(ReasoningEffort),
summaries: Schema.Boolean,
encryptedContent: Schema.Boolean,
}),
}) {}
export class ModelLimits extends Schema.Class<ModelLimits>("LLM.ModelLimits")({
context: Schema.optional(Schema.Number),
output: Schema.optional(Schema.Number),
}) {}
export class ModelRef extends Schema.Class<ModelRef>("LLM.ModelRef")({
id: Schema.String,
provider: Schema.String,
protocol: Protocol,
baseURL: Schema.optional(Schema.String),
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
capabilities: ModelCapabilities,
limits: ModelLimits,
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {}
export class CacheHint extends Schema.Class<CacheHint>("LLM.CacheHint")({
type: Schema.Literals(["ephemeral", "persistent"]),
ttlSeconds: Schema.optional(Schema.Number),
}) {}
export const SystemPart = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.SystemPart" })
export type SystemPart = Schema.Schema.Type<typeof SystemPart>
export const TextPart = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.Content.Text" })
export type TextPart = Schema.Schema.Type<typeof TextPart>
export const MediaPart = Schema.Struct({
type: Schema.Literal("media"),
mediaType: Schema.String,
data: Schema.Union([Schema.String, Schema.Uint8Array]),
filename: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.Content.Media" })
export type MediaPart = Schema.Schema.Type<typeof MediaPart>
export const ToolResultValue = Schema.Struct({
type: Schema.Literals(["json", "text", "error"]),
value: Schema.Unknown,
}).annotate({ identifier: "LLM.ToolResult" })
export type ToolResultValue = Schema.Schema.Type<typeof ToolResultValue>
export const ToolCallPart = Schema.Struct({
type: Schema.Literal("tool-call"),
id: Schema.String,
name: Schema.String,
input: Schema.Unknown,
providerExecuted: Schema.optional(Schema.Boolean),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.Content.ToolCall" })
export type ToolCallPart = Schema.Schema.Type<typeof ToolCallPart>
export const ToolResultPart = Schema.Struct({
type: Schema.Literal("tool-result"),
id: Schema.String,
name: Schema.String,
result: ToolResultValue,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.Content.ToolResult" })
export type ToolResultPart = Schema.Schema.Type<typeof ToolResultPart>
export const ReasoningPart = Schema.Struct({
type: Schema.Literal("reasoning"),
text: Schema.String,
encrypted: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.Content.Reasoning" })
export type ReasoningPart = Schema.Schema.Type<typeof ReasoningPart>
export const ContentPart = Schema.Union([TextPart, MediaPart, ToolCallPart, ToolResultPart, ReasoningPart]).pipe(
Schema.toTaggedUnion("type"),
)
export type ContentPart = Schema.Schema.Type<typeof ContentPart>
export class Message extends Schema.Class<Message>("LLM.Message")({
id: Schema.optional(Schema.String),
role: MessageRole,
content: Schema.Array(ContentPart),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {}
export class ToolDefinition extends Schema.Class<ToolDefinition>("LLM.ToolDefinition")({
name: Schema.String,
description: Schema.String,
inputSchema: JsonSchema,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {}
export class ToolChoice extends Schema.Class<ToolChoice>("LLM.ToolChoice")({
type: Schema.Literals(["auto", "none", "required", "tool"]),
name: Schema.optional(Schema.String),
}) {}
export class GenerationOptions extends Schema.Class<GenerationOptions>("LLM.GenerationOptions")({
maxTokens: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
topP: Schema.optional(Schema.Number),
stop: Schema.optional(Schema.Array(Schema.String)),
}) {}
export class ReasoningIntent extends Schema.Class<ReasoningIntent>("LLM.ReasoningIntent")({
enabled: Schema.Boolean,
effort: Schema.optional(ReasoningEffort),
summary: Schema.optional(Schema.Boolean),
encryptedContent: Schema.optional(Schema.Boolean),
}) {}
export class CacheIntent extends Schema.Class<CacheIntent>("LLM.CacheIntent")({
enabled: Schema.Boolean,
key: Schema.optional(Schema.String),
}) {}
export const ResponseFormat = Schema.Union([
Schema.Struct({ type: Schema.Literal("text") }),
Schema.Struct({ type: Schema.Literal("json"), schema: JsonSchema }),
Schema.Struct({ type: Schema.Literal("tool"), tool: ToolDefinition }),
])
export type ResponseFormat = Schema.Schema.Type<typeof ResponseFormat>
export class LLMRequest extends Schema.Class<LLMRequest>("LLM.Request")({
id: Schema.optional(Schema.String),
model: ModelRef,
system: Schema.Array(SystemPart),
messages: Schema.Array(Message),
tools: Schema.Array(ToolDefinition),
toolChoice: Schema.optional(ToolChoice),
generation: GenerationOptions,
reasoning: Schema.optional(ReasoningIntent),
cache: Schema.optional(CacheIntent),
responseFormat: Schema.optional(ResponseFormat),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {}
export class Usage extends Schema.Class<Usage>("LLM.Usage")({
inputTokens: Schema.optional(Schema.Number),
outputTokens: Schema.optional(Schema.Number),
reasoningTokens: Schema.optional(Schema.Number),
cacheReadInputTokens: Schema.optional(Schema.Number),
cacheWriteInputTokens: Schema.optional(Schema.Number),
totalTokens: Schema.optional(Schema.Number),
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {}
export const RequestStart = Schema.Struct({
type: Schema.Literal("request-start"),
id: Schema.String,
model: ModelRef,
}).annotate({ identifier: "LLM.Event.RequestStart" })
export type RequestStart = Schema.Schema.Type<typeof RequestStart>
export const StepStart = Schema.Struct({
type: Schema.Literal("step-start"),
index: Schema.Number,
}).annotate({ identifier: "LLM.Event.StepStart" })
export type StepStart = Schema.Schema.Type<typeof StepStart>
export const TextStart = Schema.Struct({
type: Schema.Literal("text-start"),
id: Schema.String,
}).annotate({ identifier: "LLM.Event.TextStart" })
export type TextStart = Schema.Schema.Type<typeof TextStart>
export const TextDelta = Schema.Struct({
type: Schema.Literal("text-delta"),
id: Schema.optional(Schema.String),
text: Schema.String,
}).annotate({ identifier: "LLM.Event.TextDelta" })
export type TextDelta = Schema.Schema.Type<typeof TextDelta>
export const TextEnd = Schema.Struct({
type: Schema.Literal("text-end"),
id: Schema.String,
}).annotate({ identifier: "LLM.Event.TextEnd" })
export type TextEnd = Schema.Schema.Type<typeof TextEnd>
export const ReasoningDelta = Schema.Struct({
type: Schema.Literal("reasoning-delta"),
id: Schema.optional(Schema.String),
text: Schema.String,
}).annotate({ identifier: "LLM.Event.ReasoningDelta" })
export type ReasoningDelta = Schema.Schema.Type<typeof ReasoningDelta>
export const ToolInputDelta = Schema.Struct({
type: Schema.Literal("tool-input-delta"),
id: Schema.String,
name: Schema.String,
text: Schema.String,
}).annotate({ identifier: "LLM.Event.ToolInputDelta" })
export type ToolInputDelta = Schema.Schema.Type<typeof ToolInputDelta>
export const ToolCall = Schema.Struct({
type: Schema.Literal("tool-call"),
id: Schema.String,
name: Schema.String,
input: Schema.Unknown,
}).annotate({ identifier: "LLM.Event.ToolCall" })
export type ToolCall = Schema.Schema.Type<typeof ToolCall>
export const ToolResult = Schema.Struct({
type: Schema.Literal("tool-result"),
id: Schema.String,
name: Schema.String,
result: ToolResultValue,
}).annotate({ identifier: "LLM.Event.ToolResult" })
export type ToolResult = Schema.Schema.Type<typeof ToolResult>
export const ToolError = Schema.Struct({
type: Schema.Literal("tool-error"),
id: Schema.String,
name: Schema.String,
message: Schema.String,
}).annotate({ identifier: "LLM.Event.ToolError" })
export type ToolError = Schema.Schema.Type<typeof ToolError>
export const StepFinish = Schema.Struct({
type: Schema.Literal("step-finish"),
index: Schema.Number,
reason: FinishReason,
usage: Schema.optional(Usage),
}).annotate({ identifier: "LLM.Event.StepFinish" })
export type StepFinish = Schema.Schema.Type<typeof StepFinish>
export const RequestFinish = Schema.Struct({
type: Schema.Literal("request-finish"),
reason: FinishReason,
usage: Schema.optional(Usage),
}).annotate({ identifier: "LLM.Event.RequestFinish" })
export type RequestFinish = Schema.Schema.Type<typeof RequestFinish>
export const ProviderErrorEvent = Schema.Struct({
type: Schema.Literal("provider-error"),
message: Schema.String,
retryable: Schema.optional(Schema.Boolean),
}).annotate({ identifier: "LLM.Event.ProviderError" })
export type ProviderErrorEvent = Schema.Schema.Type<typeof ProviderErrorEvent>
export const LLMEvent = Schema.Union([
RequestStart,
StepStart,
TextStart,
TextDelta,
TextEnd,
ReasoningDelta,
ToolInputDelta,
ToolCall,
ToolResult,
ToolError,
StepFinish,
RequestFinish,
ProviderErrorEvent,
]).pipe(Schema.toTaggedUnion("type"))
export type LLMEvent = Schema.Schema.Type<typeof LLMEvent>
export class PatchTrace extends Schema.Class<PatchTrace>("LLM.PatchTrace")({
id: Schema.String,
phase: PatchPhase,
reason: Schema.String,
}) {}
export class TransportRequest extends Schema.Class<TransportRequest>("LLM.TransportRequest")({
url: Schema.String,
method: Schema.Literal("POST"),
headers: Schema.Record(Schema.String, Schema.String),
body: Schema.String,
timeoutMs: Schema.optional(Schema.Number),
}) {}
export class PreparedRequest extends Schema.Class<PreparedRequest>("LLM.PreparedRequest")({
id: Schema.String,
adapter: Schema.String,
model: ModelRef,
target: Schema.Unknown,
redactedTarget: Schema.Unknown,
transport: TransportRequest,
patchTrace: Schema.Array(PatchTrace),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {}
export class LLMResponse extends Schema.Class<LLMResponse>("LLM.Response")({
events: Schema.Array(LLMEvent),
usage: Schema.optional(Usage),
}) {}
export class InvalidRequestError extends Schema.TaggedErrorClass<InvalidRequestError>()("LLM.InvalidRequestError", {
message: Schema.String,
}) {}
export class NoAdapterError extends Schema.TaggedErrorClass<NoAdapterError>()("LLM.NoAdapterError", {
protocol: Protocol,
provider: Schema.String,
model: Schema.String,
}) {
override get message() {
return `No LLM adapter for ${this.provider}/${this.model} using ${this.protocol}`
}
}
export class TargetMergeError extends Schema.TaggedErrorClass<TargetMergeError>()("LLM.TargetMergeError", {
slot: TargetSlot,
message: Schema.String,
}) {}
export class TargetValidationError extends Schema.TaggedErrorClass<TargetValidationError>()(
"LLM.TargetValidationError",
{
adapter: Schema.String,
message: Schema.String,
patchTrace: Schema.Array(PatchTrace),
},
) {}
export class ProviderRequestError extends Schema.TaggedErrorClass<ProviderRequestError>()("LLM.ProviderRequestError", {
adapter: Schema.String,
provider: Schema.String,
model: Schema.String,
status: Schema.optional(Schema.Number),
message: Schema.String,
body: Schema.optional(Schema.String),
patchTrace: Schema.Array(PatchTrace),
}) {}
export class ProviderChunkError extends Schema.TaggedErrorClass<ProviderChunkError>()("LLM.ProviderChunkError", {
adapter: Schema.String,
message: Schema.String,
raw: Schema.optional(Schema.String),
}) {}
export class TransportError extends Schema.TaggedErrorClass<TransportError>()("LLM.TransportError", {
message: Schema.String,
}) {}
export type LLMError =
| InvalidRequestError
| NoAdapterError
| TargetMergeError
| TargetValidationError
| ProviderRequestError
| ProviderChunkError
| TransportError
export * as LLMCoreSchema from "./schema"
-10
View File
@@ -1,10 +0,0 @@
import { Effect } from "effect"
import type { LLMError } from "./schema"
export interface TargetBuilder<Draft, Target> {
readonly empty: Draft
readonly concat: (left: Draft, right: Draft) => Effect.Effect<Draft, LLMError>
readonly validate: (draft: Draft) => Effect.Effect<Target, LLMError>
}
export * as LLMCoreTarget from "./target"
@@ -1,8 +0,0 @@
import type { Effect } from "effect"
import type { LLMError, TransportRequest } from "./schema"
export interface Transport {
readonly fetch: (request: TransportRequest) => Effect.Effect<Response, LLMError>
}
export * as LLMCoreTransport from "./transport"