@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.
Files changed (145) hide show
  1. package/AGENTS.md +321 -0
  2. package/README.md +131 -0
  3. package/example/call-sites.md +591 -0
  4. package/example/tutorial.ts +255 -0
  5. package/package.json +50 -0
  6. package/script/recording-cost-report.ts +250 -0
  7. package/script/setup-recording-env.ts +542 -0
  8. package/src/cache-policy.ts +111 -0
  9. package/src/index.ts +32 -0
  10. package/src/llm.ts +186 -0
  11. package/src/protocols/anthropic-messages.ts +841 -0
  12. package/src/protocols/bedrock-converse.ts +649 -0
  13. package/src/protocols/bedrock-event-stream.ts +87 -0
  14. package/src/protocols/gemini.ts +465 -0
  15. package/src/protocols/index.ts +6 -0
  16. package/src/protocols/openai-chat.ts +431 -0
  17. package/src/protocols/openai-compatible-chat.ts +24 -0
  18. package/src/protocols/openai-responses.ts +987 -0
  19. package/src/protocols/shared.ts +283 -0
  20. package/src/protocols/utils/bedrock-auth.ts +70 -0
  21. package/src/protocols/utils/bedrock-cache.ts +37 -0
  22. package/src/protocols/utils/bedrock-media.ts +80 -0
  23. package/src/protocols/utils/cache.ts +16 -0
  24. package/src/protocols/utils/gemini-tool-schema.ts +101 -0
  25. package/src/protocols/utils/lifecycle.ts +102 -0
  26. package/src/protocols/utils/openai-options.ts +84 -0
  27. package/src/protocols/utils/tool-stream.ts +218 -0
  28. package/src/provider.ts +37 -0
  29. package/src/providers/amazon-bedrock.ts +43 -0
  30. package/src/providers/anthropic.ts +35 -0
  31. package/src/providers/azure.ts +110 -0
  32. package/src/providers/cloudflare.ts +127 -0
  33. package/src/providers/github-copilot.ts +66 -0
  34. package/src/providers/google.ts +35 -0
  35. package/src/providers/index.ts +11 -0
  36. package/src/providers/openai-compatible-profile.ts +20 -0
  37. package/src/providers/openai-compatible.ts +65 -0
  38. package/src/providers/openai-options.ts +81 -0
  39. package/src/providers/openai.ts +63 -0
  40. package/src/providers/openrouter.ts +98 -0
  41. package/src/providers/xai.ts +56 -0
  42. package/src/route/auth-options.ts +57 -0
  43. package/src/route/auth.ts +156 -0
  44. package/src/route/client.ts +434 -0
  45. package/src/route/endpoint.ts +53 -0
  46. package/src/route/executor.ts +374 -0
  47. package/src/route/framing.ts +27 -0
  48. package/src/route/index.ts +25 -0
  49. package/src/route/protocol.ts +84 -0
  50. package/src/route/transport/http.ts +108 -0
  51. package/src/route/transport/index.ts +33 -0
  52. package/src/route/transport/websocket.ts +280 -0
  53. package/src/schema/errors.ts +203 -0
  54. package/src/schema/events.ts +370 -0
  55. package/src/schema/ids.ts +43 -0
  56. package/src/schema/index.ts +5 -0
  57. package/src/schema/messages.ts +404 -0
  58. package/src/schema/options.ts +221 -0
  59. package/src/tool-runtime.ts +78 -0
  60. package/src/tool.ts +241 -0
  61. package/src/utils/record.ts +3 -0
  62. package/sst-env.d.ts +10 -0
  63. package/test/adapter.test.ts +164 -0
  64. package/test/auth-options.types.ts +168 -0
  65. package/test/auth.test.ts +103 -0
  66. package/test/cache-policy.test.ts +262 -0
  67. package/test/continuation-scenarios.ts +104 -0
  68. package/test/endpoint.test.ts +58 -0
  69. package/test/executor.test.ts +418 -0
  70. package/test/exports.test.ts +62 -0
  71. package/test/fixtures/media/restroom.png +0 -0
  72. package/test/fixtures/recordings/anthropic-messages/accepts-malformed-assistant-tool-order-with-default-patch.json +29 -0
  73. package/test/fixtures/recordings/anthropic-messages/anthropic-opus-4-7-image-tool-result.json +43 -0
  74. package/test/fixtures/recordings/anthropic-messages/claude-opus-4-7-drives-a-tool-loop.json +56 -0
  75. package/test/fixtures/recordings/anthropic-messages/rejects-malformed-assistant-tool-order-without-patch.json +29 -0
  76. package/test/fixtures/recordings/anthropic-messages/streams-text.json +29 -0
  77. package/test/fixtures/recordings/anthropic-messages/streams-tool-call.json +29 -0
  78. package/test/fixtures/recordings/anthropic-messages-cache/writes-then-reads-cache-control-on-identical-second-call.json +48 -0
  79. package/test/fixtures/recordings/bedrock-converse/drives-a-tool-loop.json +55 -0
  80. package/test/fixtures/recordings/bedrock-converse/streams-a-tool-call.json +29 -0
  81. package/test/fixtures/recordings/bedrock-converse/streams-text.json +29 -0
  82. package/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-gpt-oss-20b-tools-tool-call.json +32 -0
  83. package/test/fixtures/recordings/cloudflare-ai-gateway/cloudflare-ai-gateway-workers-ai-llama-3-1-8b-text.json +32 -0
  84. package/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-gpt-oss-20b-tools-tool-call.json +32 -0
  85. package/test/fixtures/recordings/cloudflare-workers-ai/cloudflare-workers-ai-llama-3-1-8b-text.json +32 -0
  86. package/test/fixtures/recordings/gemini/gemini-2-5-flash-image.json +32 -0
  87. package/test/fixtures/recordings/gemini/streams-text.json +28 -0
  88. package/test/fixtures/recordings/gemini/streams-tool-call.json +28 -0
  89. package/test/fixtures/recordings/gemini-cache/reports-cachedcontenttokencount-on-identical-second-call.json +46 -0
  90. package/test/fixtures/recordings/openai-chat/continues-after-tool-result.json +28 -0
  91. package/test/fixtures/recordings/openai-chat/drives-a-tool-loop-end-to-end.json +46 -0
  92. package/test/fixtures/recordings/openai-chat/streams-text.json +28 -0
  93. package/test/fixtures/recordings/openai-chat/streams-tool-call.json +28 -0
  94. package/test/fixtures/recordings/openai-compatible-chat/deepseek-streams-text.json +28 -0
  95. package/test/fixtures/recordings/openai-compatible-chat/groq-llama-3-3-70b-drives-a-tool-loop.json +53 -0
  96. package/test/fixtures/recordings/openai-compatible-chat/groq-streams-text.json +28 -0
  97. package/test/fixtures/recordings/openai-compatible-chat/groq-streams-tool-call.json +28 -0
  98. package/test/fixtures/recordings/openai-compatible-chat/openrouter-claude-opus-4-7-drives-a-tool-loop.json +54 -0
  99. package/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-4o-mini-drives-a-tool-loop.json +53 -0
  100. package/test/fixtures/recordings/openai-compatible-chat/openrouter-gpt-5-5-drives-a-tool-loop.json +54 -0
  101. package/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-text.json +28 -0
  102. package/test/fixtures/recordings/openai-compatible-chat/openrouter-streams-tool-call.json +28 -0
  103. package/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-text.json +28 -0
  104. package/test/fixtures/recordings/openai-compatible-chat/togetherai-streams-tool-call.json +28 -0
  105. package/test/fixtures/recordings/openai-responses/gpt-5-5-drives-a-tool-loop.json +54 -0
  106. package/test/fixtures/recordings/openai-responses/gpt-5-5-streams-text.json +28 -0
  107. package/test/fixtures/recordings/openai-responses/gpt-5-5-streams-tool-call.json +28 -0
  108. package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-image-tool-result.json +42 -0
  109. package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning-continuation.json +58 -0
  110. package/test/fixtures/recordings/openai-responses/openai-responses-gpt-5-5-reasoning.json +32 -0
  111. package/test/fixtures/recordings/openai-responses-cache/reports-cached-tokens-on-identical-second-call.json +46 -0
  112. package/test/generate-object.test.ts +184 -0
  113. package/test/lib/effect.ts +50 -0
  114. package/test/lib/http.ts +98 -0
  115. package/test/lib/openai-chunks.ts +27 -0
  116. package/test/lib/sse.ts +17 -0
  117. package/test/lib/tool-runtime.ts +146 -0
  118. package/test/llm.test.ts +167 -0
  119. package/test/provider/anthropic-messages-cache.recorded.test.ts +54 -0
  120. package/test/provider/anthropic-messages.recorded.test.ts +46 -0
  121. package/test/provider/anthropic-messages.test.ts +829 -0
  122. package/test/provider/bedrock-converse-cache.recorded.test.ts +54 -0
  123. package/test/provider/bedrock-converse.test.ts +707 -0
  124. package/test/provider/cloudflare.test.ts +230 -0
  125. package/test/provider/gemini-cache.recorded.test.ts +48 -0
  126. package/test/provider/gemini.test.ts +476 -0
  127. package/test/provider/golden.recorded.test.ts +219 -0
  128. package/test/provider/openai-chat.test.ts +446 -0
  129. package/test/provider/openai-compatible-chat.test.ts +238 -0
  130. package/test/provider/openai-responses-cache.recorded.test.ts +46 -0
  131. package/test/provider/openai-responses.test.ts +1322 -0
  132. package/test/provider/openrouter.test.ts +56 -0
  133. package/test/provider.types.ts +41 -0
  134. package/test/recorded-golden.ts +97 -0
  135. package/test/recorded-runner.ts +100 -0
  136. package/test/recorded-scenarios.ts +531 -0
  137. package/test/recorded-test.ts +74 -0
  138. package/test/recorded-utils.ts +56 -0
  139. package/test/recorded-websocket.ts +26 -0
  140. package/test/route.test.ts +43 -0
  141. package/test/schema.test.ts +97 -0
  142. package/test/tool-runtime.test.ts +802 -0
  143. package/test/tool-stream.test.ts +99 -0
  144. package/test/tool.types.ts +40 -0
  145. 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
+ }