@codilore/llm 1.15.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +321 -0
- package/README.md +131 -0
- package/example/call-sites.md +591 -0
- package/example/tutorial.ts +255 -0
- package/package.json +50 -0
- package/script/recording-cost-report.ts +250 -0
- package/script/setup-recording-env.ts +542 -0
- package/src/cache-policy.ts +111 -0
- package/src/index.ts +32 -0
- package/src/llm.ts +186 -0
- package/src/protocols/anthropic-messages.ts +841 -0
- package/src/protocols/bedrock-converse.ts +649 -0
- package/src/protocols/bedrock-event-stream.ts +87 -0
- package/src/protocols/gemini.ts +465 -0
- package/src/protocols/index.ts +6 -0
- package/src/protocols/openai-chat.ts +431 -0
- package/src/protocols/openai-compatible-chat.ts +24 -0
- package/src/protocols/openai-responses.ts +987 -0
- package/src/protocols/shared.ts +283 -0
- package/src/protocols/utils/bedrock-auth.ts +70 -0
- package/src/protocols/utils/bedrock-cache.ts +37 -0
- package/src/protocols/utils/bedrock-media.ts +80 -0
- package/src/protocols/utils/cache.ts +16 -0
- package/src/protocols/utils/gemini-tool-schema.ts +101 -0
- package/src/protocols/utils/lifecycle.ts +102 -0
- package/src/protocols/utils/openai-options.ts +84 -0
- package/src/protocols/utils/tool-stream.ts +218 -0
- package/src/provider.ts +37 -0
- package/src/providers/amazon-bedrock.ts +43 -0
- package/src/providers/anthropic.ts +35 -0
- package/src/providers/azure.ts +110 -0
- package/src/providers/cloudflare.ts +127 -0
- package/src/providers/github-copilot.ts +66 -0
- package/src/providers/google.ts +35 -0
- package/src/providers/index.ts +11 -0
- package/src/providers/openai-compatible-profile.ts +20 -0
- package/src/providers/openai-compatible.ts +65 -0
- package/src/providers/openai-options.ts +81 -0
- package/src/providers/openai.ts +63 -0
- package/src/providers/openrouter.ts +98 -0
- package/src/providers/xai.ts +56 -0
- package/src/route/auth-options.ts +57 -0
- package/src/route/auth.ts +156 -0
- package/src/route/client.ts +434 -0
- package/src/route/endpoint.ts +53 -0
- package/src/route/executor.ts +374 -0
- package/src/route/framing.ts +27 -0
- package/src/route/index.ts +25 -0
- package/src/route/protocol.ts +84 -0
- package/src/route/transport/http.ts +108 -0
- package/src/route/transport/index.ts +33 -0
- package/src/route/transport/websocket.ts +280 -0
- package/src/schema/errors.ts +203 -0
- package/src/schema/events.ts +370 -0
- package/src/schema/ids.ts +43 -0
- package/src/schema/index.ts +5 -0
- package/src/schema/messages.ts +404 -0
- package/src/schema/options.ts +221 -0
- package/src/tool-runtime.ts +78 -0
- package/src/tool.ts +241 -0
- package/src/utils/record.ts +3 -0
- package/sst-env.d.ts +10 -0
- package/test/adapter.test.ts +164 -0
- package/test/auth-options.types.ts +168 -0
- package/test/auth.test.ts +103 -0
- package/test/cache-policy.test.ts +262 -0
- package/test/continuation-scenarios.ts +104 -0
- package/test/endpoint.test.ts +58 -0
- package/test/executor.test.ts +418 -0
- package/test/exports.test.ts +62 -0
- package/test/fixtures/media/restroom.png +0 -0
- package/test/fixtures/recordings/anthropic-messages/accepts-malformed-assistant-tool-order-with-default-patch.json +29 -0
- package/test/fixtures/recordings/anthropic-messages/anthropic-opus-4-7-image-tool-result.json +43 -0
- package/test/fixtures/recordings/anthropic-messages/claude-opus-4-7-drives-a-tool-loop.json +56 -0
- package/test/fixtures/recordings/anthropic-messages/rejects-malformed-assistant-tool-order-without-patch.json +29 -0
- package/test/fixtures/recordings/anthropic-messages/streams-text.json +29 -0
- package/test/fixtures/recordings/anthropic-messages/streams-tool-call.json +29 -0
- package/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json +48 -0
- package/test/fixtures/recordings/bedrock-converse/drives-a-tool-loop.json +55 -0
- package/test/fixtures/recordings/bedrock-converse/streams-a-tool-call.json +29 -0
- package/test/fixtures/recordings/bedrock-converse/streams-text.json +29 -0
- package/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-gpt-oss-20b-tools-tool-call.json +32 -0
- package/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-llama-3-1-8b-text.json +32 -0
- package/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-gpt-oss-20b-tools-tool-call.json +32 -0
- package/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-llama-3-1-8b-text.json +32 -0
- package/test/fixtures/recordings/gemini/gemini-2-5-flash-image.json +32 -0
- package/test/fixtures/recordings/gemini/streams-text.json +28 -0
- package/test/fixtures/recordings/gemini/streams-tool-call.json +28 -0
- package/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json +46 -0
- package/test/fixtures/recordings/openai-chat/continues-after-tool-result.json +28 -0
- package/test/fixtures/recordings/openai-chat/drives-a-tool-loop-end-to-end.json +46 -0
- package/test/fixtures/recordings/openai-chat/streams-text.json +28 -0
- package/test/fixtures/recordings/openai-chat/streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/deepseek-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/groq-llama-3-3-70b-drives-a-tool-loop.json +53 -0
- package/test/fixtures/recordings/openai-compatible-chat/groq-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/groq-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-claude-opus-4-7-drives-a-tool-loop.json +54 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-4o-mini-drives-a-tool-loop.json +53 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-5-5-drives-a-tool-loop.json +54 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-responses/gpt-5-5-drives-a-tool-loop.json +54 -0
- package/test/fixtures/recordings/openai-responses/gpt-5-5-streams-text.json +28 -0
- package/test/fixtures/recordings/openai-responses/gpt-5-5-streams-tool-call.json +28 -0
- package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-image-tool-result.json +42 -0
- package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning-continuation.json +58 -0
- package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning.json +32 -0
- package/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json +46 -0
- package/test/generate-object.test.ts +184 -0
- package/test/lib/effect.ts +50 -0
- package/test/lib/http.ts +98 -0
- package/test/lib/openai-chunks.ts +27 -0
- package/test/lib/sse.ts +17 -0
- package/test/lib/tool-runtime.ts +146 -0
- package/test/llm.test.ts +167 -0
- package/test/provider/anthropic-messages-cache.recorded.test.ts +54 -0
- package/test/provider/anthropic-messages.recorded.test.ts +46 -0
- package/test/provider/anthropic-messages.test.ts +829 -0
- package/test/provider/bedrock-converse-cache.recorded.test.ts +54 -0
- package/test/provider/bedrock-converse.test.ts +707 -0
- package/test/provider/cloudflare.test.ts +230 -0
- package/test/provider/gemini-cache.recorded.test.ts +48 -0
- package/test/provider/gemini.test.ts +476 -0
- package/test/provider/golden.recorded.test.ts +219 -0
- package/test/provider/openai-chat.test.ts +446 -0
- package/test/provider/openai-compatible-chat.test.ts +238 -0
- package/test/provider/openai-responses-cache.recorded.test.ts +46 -0
- package/test/provider/openai-responses.test.ts +1322 -0
- package/test/provider/openrouter.test.ts +56 -0
- package/test/provider.types.ts +41 -0
- package/test/recorded-golden.ts +97 -0
- package/test/recorded-runner.ts +100 -0
- package/test/recorded-scenarios.ts +531 -0
- package/test/recorded-test.ts +74 -0
- package/test/recorded-utils.ts +56 -0
- package/test/recorded-websocket.ts +26 -0
- package/test/route.test.ts +43 -0
- package/test/schema.test.ts +97 -0
- package/test/tool-runtime.test.ts +802 -0
- package/test/tool-stream.test.ts +99 -0
- package/test/tool.types.ts +40 -0
- package/tsconfig.json +15 -0
package/src/llm.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Effect, JsonSchema, Schema } from "effect"
|
|
2
|
+
import { LLMClient } from "./route/client"
|
|
3
|
+
import {
|
|
4
|
+
GenerationOptions,
|
|
5
|
+
HttpOptions,
|
|
6
|
+
InvalidProviderOutputReason,
|
|
7
|
+
LLMError,
|
|
8
|
+
LLMEvent,
|
|
9
|
+
LLMRequest,
|
|
10
|
+
LLMResponse,
|
|
11
|
+
Message,
|
|
12
|
+
type ModelInput as SchemaModelInput,
|
|
13
|
+
SystemPart,
|
|
14
|
+
ToolChoice,
|
|
15
|
+
ToolDefinition,
|
|
16
|
+
type ContentPart,
|
|
17
|
+
ToolResultPart,
|
|
18
|
+
} from "./schema"
|
|
19
|
+
import { make as makeTool, toDefinitions, type ToolSchema } from "./tool"
|
|
20
|
+
|
|
21
|
+
export type ModelInput = SchemaModelInput
|
|
22
|
+
|
|
23
|
+
export type MessageInput = Message.Input
|
|
24
|
+
|
|
25
|
+
export type ToolChoiceInput = ToolChoice.Input
|
|
26
|
+
export type ToolChoiceMode = ToolChoice.Mode
|
|
27
|
+
|
|
28
|
+
export type ToolResultInput = Parameters<typeof ToolResultPart.make>[0]
|
|
29
|
+
|
|
30
|
+
/** Input accepted by `LLM.request`, normalized into the canonical `LLMRequest` class. */
|
|
31
|
+
export type RequestInput = Omit<
|
|
32
|
+
ConstructorParameters<typeof LLMRequest>[0],
|
|
33
|
+
"system" | "messages" | "tools" | "toolChoice" | "generation" | "http" | "providerOptions"
|
|
34
|
+
> & {
|
|
35
|
+
readonly system?: string | SystemPart | ReadonlyArray<SystemPart>
|
|
36
|
+
readonly prompt?: string | ContentPart | ReadonlyArray<ContentPart>
|
|
37
|
+
readonly messages?: ReadonlyArray<Message | MessageInput>
|
|
38
|
+
readonly tools?: ReadonlyArray<ToolDefinition.Input>
|
|
39
|
+
readonly toolChoice?: ToolChoiceInput
|
|
40
|
+
readonly generation?: GenerationOptions.Input
|
|
41
|
+
readonly providerOptions?: ConstructorParameters<typeof LLMRequest>[0]["providerOptions"]
|
|
42
|
+
readonly http?: HttpOptions.Input
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const generate = LLMClient.generate
|
|
46
|
+
|
|
47
|
+
export const stream = LLMClient.stream
|
|
48
|
+
|
|
49
|
+
export const requestInput = (input: LLMRequest): RequestInput => ({
|
|
50
|
+
...LLMRequest.input(input),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export const request = (input: RequestInput) => {
|
|
54
|
+
const {
|
|
55
|
+
system: requestSystem,
|
|
56
|
+
prompt,
|
|
57
|
+
messages,
|
|
58
|
+
tools,
|
|
59
|
+
toolChoice: requestToolChoice,
|
|
60
|
+
generation: requestGeneration,
|
|
61
|
+
providerOptions: requestProviderOptions,
|
|
62
|
+
http: requestHttp,
|
|
63
|
+
...rest
|
|
64
|
+
} = input
|
|
65
|
+
return new LLMRequest({
|
|
66
|
+
...rest,
|
|
67
|
+
system: SystemPart.content(requestSystem),
|
|
68
|
+
messages: [...(messages?.map(Message.make) ?? []), ...(prompt === undefined ? [] : [Message.user(prompt)])],
|
|
69
|
+
tools: tools?.map(ToolDefinition.make) ?? [],
|
|
70
|
+
toolChoice: requestToolChoice ? ToolChoice.make(requestToolChoice) : undefined,
|
|
71
|
+
generation: requestGeneration === undefined ? undefined : GenerationOptions.make(requestGeneration),
|
|
72
|
+
providerOptions: requestProviderOptions,
|
|
73
|
+
http: requestHttp === undefined ? undefined : HttpOptions.make(requestHttp),
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const updateRequest = (input: LLMRequest, patch: Partial<RequestInput>) =>
|
|
78
|
+
request({ ...requestInput(input), ...patch })
|
|
79
|
+
|
|
80
|
+
const GENERATE_OBJECT_TOOL_NAME = "generate_object"
|
|
81
|
+
|
|
82
|
+
const GENERATE_OBJECT_TOOL_DESCRIPTION = "Return the structured result by calling this tool."
|
|
83
|
+
|
|
84
|
+
type GenerateObjectBase = Omit<RequestInput, "tools" | "toolChoice" | "responseFormat">
|
|
85
|
+
|
|
86
|
+
export class GenerateObjectResponse<T> {
|
|
87
|
+
constructor(
|
|
88
|
+
readonly object: T,
|
|
89
|
+
readonly response: LLMResponse,
|
|
90
|
+
) {}
|
|
91
|
+
|
|
92
|
+
get events() {
|
|
93
|
+
return this.response.events
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get usage() {
|
|
97
|
+
return this.response.usage
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface GenerateObjectOptions<S extends ToolSchema<any>> extends GenerateObjectBase {
|
|
102
|
+
readonly schema: S
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface GenerateObjectDynamicOptions extends GenerateObjectBase {
|
|
106
|
+
/** Raw JSON Schema object describing the expected output shape. */
|
|
107
|
+
readonly jsonSchema: JsonSchema.JsonSchema
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const runGenerateObject = Effect.fn("LLM.generateObject")(function* (
|
|
111
|
+
options: GenerateObjectBase,
|
|
112
|
+
tool: ReturnType<typeof makeTool>,
|
|
113
|
+
) {
|
|
114
|
+
const baseRequest = request(options)
|
|
115
|
+
const generateRequest = LLMRequest.update(baseRequest, {
|
|
116
|
+
tools: toDefinitions({ [GENERATE_OBJECT_TOOL_NAME]: tool }),
|
|
117
|
+
toolChoice: ToolChoice.named(GENERATE_OBJECT_TOOL_NAME),
|
|
118
|
+
})
|
|
119
|
+
const response = yield* LLMClient.generate(generateRequest)
|
|
120
|
+
const call = response.toolCalls.find(
|
|
121
|
+
(event) => LLMEvent.is.toolCall(event) && event.name === GENERATE_OBJECT_TOOL_NAME,
|
|
122
|
+
)
|
|
123
|
+
if (!call || !LLMEvent.is.toolCall(call))
|
|
124
|
+
return yield* new LLMError({
|
|
125
|
+
module: "LLM",
|
|
126
|
+
method: "generateObject",
|
|
127
|
+
reason: new InvalidProviderOutputReason({
|
|
128
|
+
message: `generateObject: model did not call the forced \`${GENERATE_OBJECT_TOOL_NAME}\` tool`,
|
|
129
|
+
}),
|
|
130
|
+
})
|
|
131
|
+
const object = yield* tool._decode(call.input).pipe(
|
|
132
|
+
Effect.mapError(
|
|
133
|
+
(error) =>
|
|
134
|
+
new LLMError({
|
|
135
|
+
module: "LLM",
|
|
136
|
+
method: "generateObject",
|
|
137
|
+
reason: new InvalidProviderOutputReason({
|
|
138
|
+
message: `generateObject: tool input failed schema decode: ${error.message}`,
|
|
139
|
+
}),
|
|
140
|
+
}),
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
return new GenerateObjectResponse(object, response)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Run a model and decode its output against `schema`. Works on every protocol
|
|
148
|
+
* because it forces a synthetic tool call internally — provider-native JSON
|
|
149
|
+
* modes are intentionally avoided so behaviour is uniform.
|
|
150
|
+
*
|
|
151
|
+
* Two input modes:
|
|
152
|
+
*
|
|
153
|
+
* 1. `schema: EffectSchema<T>` — `.object` is decoded and typed as `T`.
|
|
154
|
+
* Decode failures surface as `LLMError`.
|
|
155
|
+
* 2. `jsonSchema: JsonSchema.JsonSchema` — `.object` is `unknown`. Use when
|
|
156
|
+
* the schema is only available at runtime (MCP, plugin manifests). Caller validates.
|
|
157
|
+
*/
|
|
158
|
+
export function generateObject<S extends ToolSchema<any>>(
|
|
159
|
+
options: GenerateObjectOptions<S>,
|
|
160
|
+
): Effect.Effect<GenerateObjectResponse<Schema.Schema.Type<S>>, LLMError>
|
|
161
|
+
export function generateObject(
|
|
162
|
+
options: GenerateObjectDynamicOptions,
|
|
163
|
+
): Effect.Effect<GenerateObjectResponse<unknown>, LLMError>
|
|
164
|
+
export function generateObject(options: GenerateObjectOptions<ToolSchema<any>> | GenerateObjectDynamicOptions) {
|
|
165
|
+
if ("schema" in options) {
|
|
166
|
+
const { schema, ...rest } = options
|
|
167
|
+
return runGenerateObject(
|
|
168
|
+
rest,
|
|
169
|
+
makeTool({
|
|
170
|
+
description: GENERATE_OBJECT_TOOL_DESCRIPTION,
|
|
171
|
+
parameters: schema,
|
|
172
|
+
success: Schema.Unknown as ToolSchema<unknown>,
|
|
173
|
+
execute: () => Effect.void,
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
const { jsonSchema, ...rest } = options
|
|
178
|
+
return runGenerateObject(
|
|
179
|
+
rest,
|
|
180
|
+
makeTool({
|
|
181
|
+
description: GENERATE_OBJECT_TOOL_DESCRIPTION,
|
|
182
|
+
jsonSchema,
|
|
183
|
+
execute: () => Effect.void,
|
|
184
|
+
}),
|
|
185
|
+
)
|
|
186
|
+
}
|