| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- import { Schema } from "effect"
- import { JsonSchema, MessageRole, ProviderMetadata } from "./ids"
- import { CacheHint, GenerationOptions, HttpOptions, ModelRef, ProviderOptions } from "./options"
- const isRecord = (value: unknown): value is Record<string, unknown> =>
- typeof value === "object" && value !== null && !Array.isArray(value)
- const systemPartSchema = 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 systemPartSchema>
- const makeSystemPart = (text: string): SystemPart => ({ type: "text", text })
- export const SystemPart = Object.assign(systemPartSchema, {
- make: makeSystemPart,
- content: (input?: string | SystemPart | ReadonlyArray<SystemPart>) => {
- if (input === undefined) return []
- return typeof input === "string" ? [makeSystemPart(input)] : Array.isArray(input) ? [...input] : [input]
- },
- })
- 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)),
- providerMetadata: Schema.optional(ProviderMetadata),
- }).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>
- const isToolResultValue = (value: unknown): value is ToolResultValue =>
- isRecord(value) && (value.type === "text" || value.type === "json" || value.type === "error") && "value" in value
- export const ToolResultValue = Object.assign(
- Schema.Struct({
- type: Schema.Literals(["json", "text", "error"]),
- value: Schema.Unknown,
- }).annotate({ identifier: "LLM.ToolResult" }),
- {
- make: (value: unknown, type: ToolResultValue["type"] = "json"): ToolResultValue =>
- isToolResultValue(value) ? value : { type, value },
- },
- )
- export type ToolResultValue = Schema.Schema.Type<typeof ToolResultValue>
- export const ToolCallPart = Object.assign(
- 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)),
- providerMetadata: Schema.optional(ProviderMetadata),
- }).annotate({ identifier: "LLM.Content.ToolCall" }),
- {
- make: (input: Omit<ToolCallPart, "type">): ToolCallPart => ({ type: "tool-call", ...input }),
- },
- )
- export type ToolCallPart = Schema.Schema.Type<typeof ToolCallPart>
- export const ToolResultPart = Object.assign(
- Schema.Struct({
- type: Schema.Literal("tool-result"),
- id: Schema.String,
- name: Schema.String,
- result: ToolResultValue,
- providerExecuted: Schema.optional(Schema.Boolean),
- metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
- providerMetadata: Schema.optional(ProviderMetadata),
- }).annotate({ identifier: "LLM.Content.ToolResult" }),
- {
- make: (
- input: Omit<ToolResultPart, "type" | "result"> & {
- readonly result: unknown
- readonly resultType?: ToolResultValue["type"]
- },
- ): ToolResultPart => ({
- type: "tool-result",
- id: input.id,
- name: input.name,
- result: ToolResultValue.make(input.result, input.resultType),
- providerExecuted: input.providerExecuted,
- metadata: input.metadata,
- providerMetadata: input.providerMetadata,
- }),
- },
- )
- 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)),
- providerMetadata: Schema.optional(ProviderMetadata),
- }).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 namespace Message {
- export type ContentInput = string | ContentPart | ReadonlyArray<ContentPart>
- export type Input = Omit<ConstructorParameters<typeof Message>[0], "content"> & {
- readonly content: ContentInput
- }
- export const text = (value: string): ContentPart => ({ type: "text", text: value })
- export const content = (input: ContentInput) =>
- typeof input === "string" ? [text(input)] : Array.isArray(input) ? [...input] : [input]
- export const make = (input: Message | Input) => {
- if (input instanceof Message) return input
- return new Message({ ...input, content: content(input.content) })
- }
- export const user = (content: ContentInput) => make({ role: "user", content })
- export const assistant = (content: ContentInput) => make({ role: "assistant", content })
- export const tool = (result: ToolResultPart | Parameters<typeof ToolResultPart.make>[0]) =>
- make({ role: "tool", content: ["type" in result ? result : ToolResultPart.make(result)] })
- }
- 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 namespace ToolDefinition {
- export type Input = ToolDefinition | ConstructorParameters<typeof ToolDefinition>[0]
- /** Normalize tool definition input into the canonical `ToolDefinition` class. */
- export const make = (input: Input) => (input instanceof ToolDefinition ? input : new ToolDefinition(input))
- }
- export class ToolChoice extends Schema.Class<ToolChoice>("LLM.ToolChoice")({
- type: Schema.Literals(["auto", "none", "required", "tool"]),
- name: Schema.optional(Schema.String),
- }) {}
- export namespace ToolChoice {
- export type Mode = Exclude<ToolChoice["type"], "tool">
- export type Input = ToolChoice | ConstructorParameters<typeof ToolChoice>[0] | ToolDefinition | string
- const isMode = (value: string): value is Mode => value === "auto" || value === "none" || value === "required"
- /** Select a specific named tool. */
- export const named = (value: string) => new ToolChoice({ type: "tool", name: value })
- /** Normalize ergonomic tool-choice inputs into the canonical `ToolChoice` class. */
- export const make = (input: Input) => {
- if (input instanceof ToolChoice) return input
- if (input instanceof ToolDefinition) return named(input.name)
- if (typeof input === "string") return isMode(input) ? new ToolChoice({ type: input }) : named(input)
- return new ToolChoice(input)
- }
- }
- 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 }),
- ]).pipe(Schema.toTaggedUnion("type"))
- 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: Schema.optional(GenerationOptions),
- providerOptions: Schema.optional(ProviderOptions),
- http: Schema.optional(HttpOptions),
- responseFormat: Schema.optional(ResponseFormat),
- metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
- }) {}
- export namespace LLMRequest {
- export type Input = ConstructorParameters<typeof LLMRequest>[0]
- export const input = (request: LLMRequest): Input => ({
- id: request.id,
- model: request.model,
- system: request.system,
- messages: request.messages,
- tools: request.tools,
- toolChoice: request.toolChoice,
- generation: request.generation,
- providerOptions: request.providerOptions,
- http: request.http,
- responseFormat: request.responseFormat,
- metadata: request.metadata,
- })
- export const update = (request: LLMRequest, patch: Partial<Input>) => {
- if (Object.keys(patch).length === 0) return request
- return new LLMRequest({
- ...input(request),
- ...patch,
- model: patch.model ?? request.model,
- })
- }
- }
|