@llm4ts/core 0.1.0

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 (284) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/AgentSession.d.ts +66 -0
  4. package/dist/AgentSession.d.ts.map +1 -0
  5. package/dist/AgentSession.js +53 -0
  6. package/dist/AgentSession.js.map +1 -0
  7. package/dist/Capability.d.ts +101 -0
  8. package/dist/Capability.d.ts.map +1 -0
  9. package/dist/Capability.js +210 -0
  10. package/dist/Capability.js.map +1 -0
  11. package/dist/Connector.d.ts +69 -0
  12. package/dist/Connector.d.ts.map +1 -0
  13. package/dist/Connector.js +86 -0
  14. package/dist/Connector.js.map +1 -0
  15. package/dist/ConnectorConfig.d.ts +70 -0
  16. package/dist/ConnectorConfig.d.ts.map +1 -0
  17. package/dist/ConnectorConfig.js +110 -0
  18. package/dist/ConnectorConfig.js.map +1 -0
  19. package/dist/ConnectorRegistry.d.ts +28 -0
  20. package/dist/ConnectorRegistry.d.ts.map +1 -0
  21. package/dist/ConnectorRegistry.js +87 -0
  22. package/dist/ConnectorRegistry.js.map +1 -0
  23. package/dist/ContextManagement.d.ts +78 -0
  24. package/dist/ContextManagement.d.ts.map +1 -0
  25. package/dist/ContextManagement.js +221 -0
  26. package/dist/ContextManagement.js.map +1 -0
  27. package/dist/Conversation.d.ts +18 -0
  28. package/dist/Conversation.d.ts.map +1 -0
  29. package/dist/Conversation.js +17 -0
  30. package/dist/Conversation.js.map +1 -0
  31. package/dist/Errors.d.ts +64 -0
  32. package/dist/Errors.d.ts.map +1 -0
  33. package/dist/Errors.js +76 -0
  34. package/dist/Errors.js.map +1 -0
  35. package/dist/HttpClient.d.ts +43 -0
  36. package/dist/HttpClient.d.ts.map +1 -0
  37. package/dist/HttpClient.js +99 -0
  38. package/dist/HttpClient.js.map +1 -0
  39. package/dist/LlmService.d.ts +24 -0
  40. package/dist/LlmService.d.ts.map +1 -0
  41. package/dist/LlmService.js +4 -0
  42. package/dist/LlmService.js.map +1 -0
  43. package/dist/Models.d.ts +138 -0
  44. package/dist/Models.d.ts.map +1 -0
  45. package/dist/Models.js +209 -0
  46. package/dist/Models.js.map +1 -0
  47. package/dist/ProcessExecutor.d.ts +57 -0
  48. package/dist/ProcessExecutor.d.ts.map +1 -0
  49. package/dist/ProcessExecutor.js +71 -0
  50. package/dist/ProcessExecutor.js.map +1 -0
  51. package/dist/RateLimiter.d.ts +44 -0
  52. package/dist/RateLimiter.d.ts.map +1 -0
  53. package/dist/RateLimiter.js +155 -0
  54. package/dist/RateLimiter.js.map +1 -0
  55. package/dist/Streaming.d.ts +17 -0
  56. package/dist/Streaming.d.ts.map +1 -0
  57. package/dist/Streaming.js +65 -0
  58. package/dist/Streaming.js.map +1 -0
  59. package/dist/StructuredOutput.d.ts +10 -0
  60. package/dist/StructuredOutput.d.ts.map +1 -0
  61. package/dist/StructuredOutput.js +97 -0
  62. package/dist/StructuredOutput.js.map +1 -0
  63. package/dist/TemporaryFiles.d.ts +20 -0
  64. package/dist/TemporaryFiles.d.ts.map +1 -0
  65. package/dist/TemporaryFiles.js +18 -0
  66. package/dist/TemporaryFiles.js.map +1 -0
  67. package/dist/UsageLimits.d.ts +5 -0
  68. package/dist/UsageLimits.d.ts.map +1 -0
  69. package/dist/UsageLimits.js +187 -0
  70. package/dist/UsageLimits.js.map +1 -0
  71. package/dist/eval/Checks.d.ts +6 -0
  72. package/dist/eval/Checks.d.ts.map +1 -0
  73. package/dist/eval/Checks.js +39 -0
  74. package/dist/eval/Checks.js.map +1 -0
  75. package/dist/eval/Eval.d.ts +57 -0
  76. package/dist/eval/Eval.d.ts.map +1 -0
  77. package/dist/eval/Eval.js +91 -0
  78. package/dist/eval/Eval.js.map +1 -0
  79. package/dist/eval/EvalSuite.d.ts +21 -0
  80. package/dist/eval/EvalSuite.d.ts.map +1 -0
  81. package/dist/eval/EvalSuite.js +30 -0
  82. package/dist/eval/EvalSuite.js.map +1 -0
  83. package/dist/eval/Evaluator.d.ts +10 -0
  84. package/dist/eval/Evaluator.d.ts.map +1 -0
  85. package/dist/eval/Evaluator.js +14 -0
  86. package/dist/eval/Evaluator.js.map +1 -0
  87. package/dist/eval/Judge.d.ts +15 -0
  88. package/dist/eval/Judge.d.ts.map +1 -0
  89. package/dist/eval/Judge.js +73 -0
  90. package/dist/eval/Judge.js.map +1 -0
  91. package/dist/observability/MeteredLlmService.d.ts +16 -0
  92. package/dist/observability/MeteredLlmService.d.ts.map +1 -0
  93. package/dist/observability/MeteredLlmService.js +66 -0
  94. package/dist/observability/MeteredLlmService.js.map +1 -0
  95. package/dist/observability/Metrics.d.ts +71 -0
  96. package/dist/observability/Metrics.d.ts.map +1 -0
  97. package/dist/observability/Metrics.js +147 -0
  98. package/dist/observability/Metrics.js.map +1 -0
  99. package/dist/observability/Redaction.d.ts +8 -0
  100. package/dist/observability/Redaction.d.ts.map +1 -0
  101. package/dist/observability/Redaction.js +38 -0
  102. package/dist/observability/Redaction.js.map +1 -0
  103. package/dist/observability/StreamRecorder.d.ts +9 -0
  104. package/dist/observability/StreamRecorder.d.ts.map +1 -0
  105. package/dist/observability/StreamRecorder.js +10 -0
  106. package/dist/observability/StreamRecorder.js.map +1 -0
  107. package/dist/observability/StructuredLogger.d.ts +31 -0
  108. package/dist/observability/StructuredLogger.d.ts.map +1 -0
  109. package/dist/observability/StructuredLogger.js +34 -0
  110. package/dist/observability/StructuredLogger.js.map +1 -0
  111. package/dist/observability/Tracing.d.ts +38 -0
  112. package/dist/observability/Tracing.d.ts.map +1 -0
  113. package/dist/observability/Tracing.js +75 -0
  114. package/dist/observability/Tracing.js.map +1 -0
  115. package/dist/providers/AnthropicModels.d.ts +95 -0
  116. package/dist/providers/AnthropicModels.d.ts.map +1 -0
  117. package/dist/providers/AnthropicModels.js +83 -0
  118. package/dist/providers/AnthropicModels.js.map +1 -0
  119. package/dist/providers/AnthropicProvider.d.ts +13 -0
  120. package/dist/providers/AnthropicProvider.d.ts.map +1 -0
  121. package/dist/providers/AnthropicProvider.js +177 -0
  122. package/dist/providers/AnthropicProvider.js.map +1 -0
  123. package/dist/providers/AntigravityConnector.d.ts +7 -0
  124. package/dist/providers/AntigravityConnector.d.ts.map +1 -0
  125. package/dist/providers/AntigravityConnector.js +52 -0
  126. package/dist/providers/AntigravityConnector.js.map +1 -0
  127. package/dist/providers/ClaudeAgentSession.d.ts +11 -0
  128. package/dist/providers/ClaudeAgentSession.d.ts.map +1 -0
  129. package/dist/providers/ClaudeAgentSession.js +158 -0
  130. package/dist/providers/ClaudeAgentSession.js.map +1 -0
  131. package/dist/providers/ClaudeCliConnector.d.ts +9 -0
  132. package/dist/providers/ClaudeCliConnector.d.ts.map +1 -0
  133. package/dist/providers/ClaudeCliConnector.js +123 -0
  134. package/dist/providers/ClaudeCliConnector.js.map +1 -0
  135. package/dist/providers/CliSupport.d.ts +21 -0
  136. package/dist/providers/CliSupport.d.ts.map +1 -0
  137. package/dist/providers/CliSupport.js +62 -0
  138. package/dist/providers/CliSupport.js.map +1 -0
  139. package/dist/providers/CodexConnector.d.ts +12 -0
  140. package/dist/providers/CodexConnector.d.ts.map +1 -0
  141. package/dist/providers/CodexConnector.js +175 -0
  142. package/dist/providers/CodexConnector.js.map +1 -0
  143. package/dist/providers/ConnectorFactories.d.ts +15 -0
  144. package/dist/providers/ConnectorFactories.d.ts.map +1 -0
  145. package/dist/providers/ConnectorFactories.js +74 -0
  146. package/dist/providers/ConnectorFactories.js.map +1 -0
  147. package/dist/providers/CopilotConnector.d.ts +6 -0
  148. package/dist/providers/CopilotConnector.d.ts.map +1 -0
  149. package/dist/providers/CopilotConnector.js +35 -0
  150. package/dist/providers/CopilotConnector.js.map +1 -0
  151. package/dist/providers/CursorConnector.d.ts +8 -0
  152. package/dist/providers/CursorConnector.d.ts.map +1 -0
  153. package/dist/providers/CursorConnector.js +82 -0
  154. package/dist/providers/CursorConnector.js.map +1 -0
  155. package/dist/providers/GeminiApiProvider.d.ts +9 -0
  156. package/dist/providers/GeminiApiProvider.d.ts.map +1 -0
  157. package/dist/providers/GeminiApiProvider.js +181 -0
  158. package/dist/providers/GeminiApiProvider.js.map +1 -0
  159. package/dist/providers/GeminiCliProvider.d.ts +106 -0
  160. package/dist/providers/GeminiCliProvider.d.ts.map +1 -0
  161. package/dist/providers/GeminiCliProvider.js +596 -0
  162. package/dist/providers/GeminiCliProvider.js.map +1 -0
  163. package/dist/providers/GeminiModels.d.ts +92 -0
  164. package/dist/providers/GeminiModels.d.ts.map +1 -0
  165. package/dist/providers/GeminiModels.js +80 -0
  166. package/dist/providers/GeminiModels.js.map +1 -0
  167. package/dist/providers/GrokCliConnector.d.ts +8 -0
  168. package/dist/providers/GrokCliConnector.d.ts.map +1 -0
  169. package/dist/providers/GrokCliConnector.js +82 -0
  170. package/dist/providers/GrokCliConnector.js.map +1 -0
  171. package/dist/providers/LmStudioModels.d.ts +114 -0
  172. package/dist/providers/LmStudioModels.d.ts.map +1 -0
  173. package/dist/providers/LmStudioModels.js +102 -0
  174. package/dist/providers/LmStudioModels.js.map +1 -0
  175. package/dist/providers/LmStudioProvider.d.ts +13 -0
  176. package/dist/providers/LmStudioProvider.d.ts.map +1 -0
  177. package/dist/providers/LmStudioProvider.js +158 -0
  178. package/dist/providers/LmStudioProvider.js.map +1 -0
  179. package/dist/providers/MockProvider.d.ts +6 -0
  180. package/dist/providers/MockProvider.d.ts.map +1 -0
  181. package/dist/providers/MockProvider.js +271 -0
  182. package/dist/providers/MockProvider.js.map +1 -0
  183. package/dist/providers/OllamaModels.d.ts +68 -0
  184. package/dist/providers/OllamaModels.d.ts.map +1 -0
  185. package/dist/providers/OllamaModels.js +59 -0
  186. package/dist/providers/OllamaModels.js.map +1 -0
  187. package/dist/providers/OllamaProvider.d.ts +7 -0
  188. package/dist/providers/OllamaProvider.d.ts.map +1 -0
  189. package/dist/providers/OllamaProvider.js +134 -0
  190. package/dist/providers/OllamaProvider.js.map +1 -0
  191. package/dist/providers/OpenAIModels.d.ts +139 -0
  192. package/dist/providers/OpenAIModels.d.ts.map +1 -0
  193. package/dist/providers/OpenAIModels.js +123 -0
  194. package/dist/providers/OpenAIModels.js.map +1 -0
  195. package/dist/providers/OpenAIProvider.d.ts +9 -0
  196. package/dist/providers/OpenAIProvider.d.ts.map +1 -0
  197. package/dist/providers/OpenAIProvider.js +185 -0
  198. package/dist/providers/OpenAIProvider.js.map +1 -0
  199. package/dist/providers/OpenCodeCliConnector.d.ts +8 -0
  200. package/dist/providers/OpenCodeCliConnector.d.ts.map +1 -0
  201. package/dist/providers/OpenCodeCliConnector.js +93 -0
  202. package/dist/providers/OpenCodeCliConnector.js.map +1 -0
  203. package/dist/providers/OpenCodeModels.d.ts +61 -0
  204. package/dist/providers/OpenCodeModels.d.ts.map +1 -0
  205. package/dist/providers/OpenCodeModels.js +53 -0
  206. package/dist/providers/OpenCodeModels.js.map +1 -0
  207. package/dist/providers/OpenCodeProvider.d.ts +10 -0
  208. package/dist/providers/OpenCodeProvider.d.ts.map +1 -0
  209. package/dist/providers/OpenCodeProvider.js +154 -0
  210. package/dist/providers/OpenCodeProvider.js.map +1 -0
  211. package/dist/providers/PiConnector.d.ts +8 -0
  212. package/dist/providers/PiConnector.d.ts.map +1 -0
  213. package/dist/providers/PiConnector.js +99 -0
  214. package/dist/providers/PiConnector.js.map +1 -0
  215. package/dist/tools/Tool.d.ts +83 -0
  216. package/dist/tools/Tool.d.ts.map +1 -0
  217. package/dist/tools/Tool.js +278 -0
  218. package/dist/tools/Tool.js.map +1 -0
  219. package/dist/tools/ToolCallingExecutor.d.ts +18 -0
  220. package/dist/tools/ToolCallingExecutor.d.ts.map +1 -0
  221. package/dist/tools/ToolCallingExecutor.js +64 -0
  222. package/dist/tools/ToolCallingExecutor.js.map +1 -0
  223. package/dist/tools/ToolRegistry.d.ts +45 -0
  224. package/dist/tools/ToolRegistry.d.ts.map +1 -0
  225. package/dist/tools/ToolRegistry.js +133 -0
  226. package/dist/tools/ToolRegistry.js.map +1 -0
  227. package/package.json +105 -0
  228. package/src/.gitkeep +1 -0
  229. package/src/AgentSession.ts +69 -0
  230. package/src/Capability.ts +240 -0
  231. package/src/Connector.ts +220 -0
  232. package/src/ConnectorConfig.ts +146 -0
  233. package/src/ConnectorRegistry.ts +184 -0
  234. package/src/ContextManagement.ts +372 -0
  235. package/src/Conversation.ts +21 -0
  236. package/src/Errors.ts +87 -0
  237. package/src/HttpClient.ts +202 -0
  238. package/src/LlmService.ts +45 -0
  239. package/src/Models.ts +250 -0
  240. package/src/ProcessExecutor.ts +157 -0
  241. package/src/RateLimiter.ts +238 -0
  242. package/src/Streaming.ts +164 -0
  243. package/src/StructuredOutput.ts +138 -0
  244. package/src/TemporaryFiles.ts +51 -0
  245. package/src/UsageLimits.ts +242 -0
  246. package/src/eval/Checks.ts +68 -0
  247. package/src/eval/Eval.ts +112 -0
  248. package/src/eval/EvalSuite.ts +63 -0
  249. package/src/eval/Evaluator.ts +31 -0
  250. package/src/eval/Judge.ts +92 -0
  251. package/src/observability/MeteredLlmService.ts +156 -0
  252. package/src/observability/Metrics.ts +195 -0
  253. package/src/observability/Redaction.ts +55 -0
  254. package/src/observability/StreamRecorder.ts +32 -0
  255. package/src/observability/StructuredLogger.ts +62 -0
  256. package/src/observability/Tracing.ts +141 -0
  257. package/src/providers/AnthropicModels.ts +96 -0
  258. package/src/providers/AnthropicProvider.ts +320 -0
  259. package/src/providers/AntigravityConnector.ts +77 -0
  260. package/src/providers/ClaudeAgentSession.ts +226 -0
  261. package/src/providers/ClaudeCliConnector.ts +201 -0
  262. package/src/providers/CliSupport.ts +100 -0
  263. package/src/providers/CodexConnector.ts +251 -0
  264. package/src/providers/ConnectorFactories.ts +142 -0
  265. package/src/providers/CopilotConnector.ts +47 -0
  266. package/src/providers/CursorConnector.ts +120 -0
  267. package/src/providers/GeminiApiProvider.ts +337 -0
  268. package/src/providers/GeminiCliProvider.ts +793 -0
  269. package/src/providers/GeminiModels.ts +97 -0
  270. package/src/providers/GrokCliConnector.ts +117 -0
  271. package/src/providers/LmStudioModels.ts +117 -0
  272. package/src/providers/LmStudioProvider.ts +260 -0
  273. package/src/providers/MockProvider.ts +322 -0
  274. package/src/providers/OllamaModels.ts +64 -0
  275. package/src/providers/OllamaProvider.ts +236 -0
  276. package/src/providers/OpenAIModels.ts +152 -0
  277. package/src/providers/OpenAIProvider.ts +331 -0
  278. package/src/providers/OpenCodeCliConnector.ts +135 -0
  279. package/src/providers/OpenCodeModels.ts +60 -0
  280. package/src/providers/OpenCodeProvider.ts +278 -0
  281. package/src/providers/PiConnector.ts +142 -0
  282. package/src/tools/Tool.ts +379 -0
  283. package/src/tools/ToolCallingExecutor.ts +106 -0
  284. package/src/tools/ToolRegistry.ts +224 -0
@@ -0,0 +1,21 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Schema from "effect/Schema"
3
+
4
+ export const PromptRole = Schema.Literals(["System", "User", "Assistant", "Tool"])
5
+ export type PromptRole = typeof PromptRole.Type
6
+
7
+ const emptyMetadata: Readonly<Record<string, string>> = Object.freeze({})
8
+
9
+ export class ConversationMessage extends Schema.Class<ConversationMessage>("ConversationMessage")({
10
+ id: Schema.String,
11
+ role: PromptRole,
12
+ content: Schema.String,
13
+ timestamp: Schema.DateTimeUtc,
14
+ tokens: Schema.Int.pipe(Schema.withConstructorDefault(Effect.succeed(0))),
15
+ model: Schema.optionalKey(Schema.String),
16
+ costUsd: Schema.optionalKey(Schema.Number),
17
+ metadata: Schema.Record(Schema.String, Schema.String).pipe(
18
+ Schema.withConstructorDefault(Effect.succeed(emptyMetadata))
19
+ ),
20
+ important: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false)))
21
+ }) {}
package/src/Errors.ts ADDED
@@ -0,0 +1,87 @@
1
+ import * as Duration from "effect/Duration"
2
+ import * as Schema from "effect/Schema"
3
+
4
+ export class ProviderError extends Schema.TaggedErrorClass<ProviderError>()("ProviderError", {
5
+ message: Schema.String,
6
+ cause: Schema.optionalKey(Schema.Defect())
7
+ }) {}
8
+
9
+ export class UsageLimitError extends Schema.TaggedErrorClass<UsageLimitError>()("UsageLimitError", {
10
+ resetAt: Schema.optionalKey(Schema.DateTimeUtc),
11
+ provider: Schema.String,
12
+ message: Schema.String
13
+ }) {}
14
+
15
+ export class AuthenticationError extends Schema.TaggedErrorClass<AuthenticationError>()(
16
+ "AuthenticationError",
17
+ {
18
+ message: Schema.String
19
+ }
20
+ ) {}
21
+
22
+ export class InvalidRequestError extends Schema.TaggedErrorClass<InvalidRequestError>()(
23
+ "InvalidRequestError",
24
+ {
25
+ message: Schema.String
26
+ }
27
+ ) {}
28
+
29
+ export class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", {
30
+ message: Schema.String,
31
+ raw: Schema.String
32
+ }) {}
33
+
34
+ export class ToolError extends Schema.TaggedErrorClass<ToolError>()("ToolError", {
35
+ toolName: Schema.String,
36
+ detail: Schema.String
37
+ }) {
38
+ get message(): string {
39
+ return `tool '${this.toolName}' failed: ${this.detail}`
40
+ }
41
+ }
42
+
43
+ export class ConfigError extends Schema.TaggedErrorClass<ConfigError>()("ConfigError", {
44
+ message: Schema.String
45
+ }) {}
46
+
47
+ export class RateLimitError extends Schema.TaggedErrorClass<RateLimitError>()("RateLimitError", {
48
+ retryAfter: Schema.optionalKey(Schema.Duration)
49
+ }) {
50
+ get message(): string {
51
+ return this.retryAfter === undefined
52
+ ? "rate limited"
53
+ : `rate limited; retry after ${Duration.format(this.retryAfter)}`
54
+ }
55
+ }
56
+
57
+ export class TimeoutError extends Schema.TaggedErrorClass<TimeoutError>()("TimeoutError", {
58
+ duration: Schema.Duration
59
+ }) {
60
+ get message(): string {
61
+ return `timed out after ${Duration.format(this.duration)}`
62
+ }
63
+ }
64
+
65
+ export class TurnLimitError extends Schema.TaggedErrorClass<TurnLimitError>()("TurnLimitError", {
66
+ limit: Schema.optionalKey(Schema.Int)
67
+ }) {
68
+ get message(): string {
69
+ return this.limit === undefined
70
+ ? "turn limit reached"
71
+ : `turn limit reached after ${this.limit} turn(s)`
72
+ }
73
+ }
74
+
75
+ export const LlmError = Schema.Union([
76
+ ProviderError,
77
+ UsageLimitError,
78
+ AuthenticationError,
79
+ InvalidRequestError,
80
+ ParseError,
81
+ ToolError,
82
+ ConfigError,
83
+ RateLimitError,
84
+ TimeoutError,
85
+ TurnLimitError
86
+ ])
87
+ export type LlmError = typeof LlmError.Type
@@ -0,0 +1,202 @@
1
+ import * as Context from "effect/Context"
2
+ import type * as Duration from "effect/Duration"
3
+ import * as Effect from "effect/Effect"
4
+ import * as Ref from "effect/Ref"
5
+ import * as Schema from "effect/Schema"
6
+ import * as Stream from "effect/Stream"
7
+ import { InvalidRequestError, type LlmError } from "./Errors.ts"
8
+
9
+ const emptyHeaders: Readonly<Record<string, string>> = Object.freeze({})
10
+
11
+ export class HttpRequest extends Schema.Class<HttpRequest>("HttpRequest")({
12
+ method: Schema.String,
13
+ url: Schema.String,
14
+ body: Schema.optionalKey(Schema.String),
15
+ headers: Schema.Record(Schema.String, Schema.String).pipe(
16
+ Schema.withConstructorDefault(Effect.succeed(emptyHeaders))
17
+ ),
18
+ contentType: Schema.String.pipe(
19
+ Schema.withConstructorDefault(Effect.succeed("application/json"))
20
+ ),
21
+ timeout: Schema.Duration
22
+ }) {}
23
+
24
+ export interface HttpClientShape {
25
+ readonly get: (
26
+ url: string,
27
+ headers: Readonly<Record<string, string>>,
28
+ timeout: Duration.Duration
29
+ ) => Effect.Effect<string, LlmError>
30
+ readonly postJson: (
31
+ url: string,
32
+ body: string,
33
+ headers: Readonly<Record<string, string>>,
34
+ timeout: Duration.Duration
35
+ ) => Effect.Effect<string, LlmError>
36
+ readonly send: (
37
+ method: string,
38
+ url: string,
39
+ body: string | undefined,
40
+ headers: Readonly<Record<string, string>>,
41
+ contentType: string,
42
+ timeout: Duration.Duration
43
+ ) => Effect.Effect<string, LlmError>
44
+ readonly postJsonStream: (
45
+ url: string,
46
+ body: string,
47
+ headers: Readonly<Record<string, string>>,
48
+ timeout: Duration.Duration
49
+ ) => Stream.Stream<string, LlmError>
50
+ readonly postJsonStreamSSE: (
51
+ url: string,
52
+ body: string,
53
+ headers: Readonly<Record<string, string>>,
54
+ timeout: Duration.Duration
55
+ ) => Stream.Stream<string, LlmError>
56
+ }
57
+
58
+ export class HttpClient extends Context.Service<HttpClient, HttpClientShape>()(
59
+ "@llm4ts/core/HttpClient"
60
+ ) {}
61
+
62
+ export interface HttpClientPrimitives {
63
+ readonly postJson: HttpClientShape["postJson"]
64
+ readonly get?: HttpClientShape["get"]
65
+ readonly send?: HttpClientShape["send"]
66
+ readonly postJsonStream?: HttpClientShape["postJsonStream"]
67
+ readonly postJsonStreamSSE?: HttpClientShape["postJsonStreamSSE"]
68
+ }
69
+
70
+ export const makeHttpClient = (primitives: HttpClientPrimitives): HttpClientShape => {
71
+ const get: HttpClientShape["get"] =
72
+ primitives.get ??
73
+ ((_url, _headers, _timeout) =>
74
+ Effect.fail(
75
+ InvalidRequestError.make({
76
+ message: "GET is not supported by this HttpClient implementation"
77
+ })
78
+ ))
79
+
80
+ const send: HttpClientShape["send"] =
81
+ primitives.send ??
82
+ ((_method, _url, _body, _headers, _contentType, _timeout) =>
83
+ Effect.fail(
84
+ InvalidRequestError.make({
85
+ message: "send is not supported by this HttpClient implementation"
86
+ })
87
+ ))
88
+
89
+ const postJsonStream: HttpClientShape["postJsonStream"] =
90
+ primitives.postJsonStream ??
91
+ ((url, body, headers, timeout) =>
92
+ Stream.flatMap(Stream.fromEffect(primitives.postJson(url, body, headers, timeout)), (raw) =>
93
+ Stream.fromIterable(raw.split(/\r?\n/))
94
+ ))
95
+
96
+ const postJsonStreamSSE: HttpClientShape["postJsonStreamSSE"] =
97
+ primitives.postJsonStreamSSE ??
98
+ ((url, body, headers, timeout) =>
99
+ postJsonStream(url, body, headers, timeout).pipe(
100
+ Stream.filter((line) => line.startsWith("data: ")),
101
+ Stream.map((line) => line.slice("data: ".length).trim()),
102
+ Stream.filter((payload) => payload.length > 0 && payload !== "[DONE]")
103
+ ))
104
+
105
+ return {
106
+ get,
107
+ postJson: primitives.postJson,
108
+ send,
109
+ postJsonStream,
110
+ postJsonStreamSSE
111
+ }
112
+ }
113
+
114
+ export interface RecordingHttpClient {
115
+ readonly client: HttpClientShape
116
+ readonly recorded: Effect.Effect<ReadonlyArray<HttpRequest>>
117
+ }
118
+
119
+ export const makeRecordingHttpClient = Effect.fn("@llm4ts/core/HttpClient.makeRecording")(
120
+ function* (
121
+ handler: (request: HttpRequest) => Effect.Effect<string, LlmError>,
122
+ streamHandler?: (request: HttpRequest) => Stream.Stream<string, LlmError>
123
+ ): Effect.fn.Return<RecordingHttpClient> {
124
+ const requests = yield* Ref.make<ReadonlyArray<HttpRequest>>([])
125
+ const record = (request: HttpRequest): Effect.Effect<void> =>
126
+ Ref.update(requests, (current) => [...current, request])
127
+
128
+ const postJson: HttpClientShape["postJson"] = (url, body, headers, timeout) => {
129
+ const request = HttpRequest.make({
130
+ method: "POST",
131
+ url,
132
+ body,
133
+ headers,
134
+ contentType: "application/json",
135
+ timeout
136
+ })
137
+ return record(request).pipe(Effect.andThen(handler(request)))
138
+ }
139
+
140
+ const client = makeHttpClient({
141
+ postJson,
142
+ get: (url, headers, timeout) => {
143
+ const request = HttpRequest.make({
144
+ method: "GET",
145
+ url,
146
+ headers,
147
+ timeout
148
+ })
149
+ return record(request).pipe(Effect.andThen(handler(request)))
150
+ },
151
+ send: (method, url, body, headers, contentType, timeout) => {
152
+ const request = HttpRequest.make({
153
+ method,
154
+ url,
155
+ ...(body === undefined ? {} : { body }),
156
+ headers,
157
+ contentType,
158
+ timeout
159
+ })
160
+ return record(request).pipe(Effect.andThen(handler(request)))
161
+ },
162
+ ...(streamHandler === undefined
163
+ ? {}
164
+ : {
165
+ postJsonStream: (url, body, headers, timeout) => {
166
+ const request = HttpRequest.make({
167
+ method: "POST",
168
+ url,
169
+ body,
170
+ headers,
171
+ contentType: "application/json",
172
+ timeout
173
+ })
174
+ return Stream.concat(
175
+ Stream.drain(Stream.fromEffect(record(request))),
176
+ Stream.suspend(() => streamHandler(request))
177
+ )
178
+ }
179
+ })
180
+ })
181
+
182
+ return {
183
+ client,
184
+ recorded: Ref.get(requests)
185
+ }
186
+ }
187
+ )
188
+
189
+ export const httpGet = (
190
+ url: string,
191
+ headers: Readonly<Record<string, string>> = emptyHeaders,
192
+ timeout: Duration.Duration
193
+ ): Effect.Effect<string, LlmError, HttpClient> =>
194
+ Effect.flatMap(HttpClient, (client) => client.get(url, headers, timeout))
195
+
196
+ export const httpPostJson = (
197
+ url: string,
198
+ body: string,
199
+ headers: Readonly<Record<string, string>> = emptyHeaders,
200
+ timeout: Duration.Duration
201
+ ): Effect.Effect<string, LlmError, HttpClient> =>
202
+ Effect.flatMap(HttpClient, (client) => client.postJson(url, body, headers, timeout))
@@ -0,0 +1,45 @@
1
+ import * as Context from "effect/Context"
2
+ import type * as Effect from "effect/Effect"
3
+ import type * as Stream from "effect/Stream"
4
+ import type * as Schema from "effect/Schema"
5
+ import type { LlmError } from "./Errors.ts"
6
+ import type {
7
+ JsonSchema,
8
+ LlmChunk,
9
+ Message,
10
+ TokenUsage,
11
+ ToolCallResponse,
12
+ ToolDefinition
13
+ } from "./Models.ts"
14
+
15
+ export type StructuredResult<A> = readonly [
16
+ value: A,
17
+ usage: TokenUsage | undefined,
18
+ model: string | undefined
19
+ ]
20
+
21
+ export interface LlmServiceShape {
22
+ readonly executeStream: (prompt: string) => Stream.Stream<LlmChunk, LlmError>
23
+ readonly executeStreamWithHistory: (
24
+ messages: ReadonlyArray<Message>
25
+ ) => Stream.Stream<LlmChunk, LlmError>
26
+ readonly executeWithTools: (
27
+ prompt: string,
28
+ tools: ReadonlyArray<ToolDefinition>
29
+ ) => Effect.Effect<ToolCallResponse, LlmError>
30
+ readonly executeStructured: <A, E, RD, RE>(
31
+ prompt: string,
32
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
33
+ jsonSchema: JsonSchema
34
+ ) => Effect.Effect<A, LlmError, RD>
35
+ readonly executeStructuredWithUsage: <A, E, RD, RE>(
36
+ prompt: string,
37
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
38
+ jsonSchema: JsonSchema
39
+ ) => Effect.Effect<StructuredResult<A>, LlmError, RD>
40
+ readonly isAvailable: Effect.Effect<boolean>
41
+ }
42
+
43
+ export class LlmService extends Context.Service<LlmService, LlmServiceShape>()(
44
+ "@llm4ts/core/LlmService"
45
+ ) {}
package/src/Models.ts ADDED
@@ -0,0 +1,250 @@
1
+ import * as Duration from "effect/Duration"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Schema from "effect/Schema"
4
+
5
+ export const LlmProvider = Schema.Literals([
6
+ "GeminiCli",
7
+ "GeminiApi",
8
+ "OpenAI",
9
+ "Anthropic",
10
+ "LmStudio",
11
+ "Ollama",
12
+ "OpenCode",
13
+ "Mock"
14
+ ])
15
+ export type LlmProvider = typeof LlmProvider.Type
16
+
17
+ export class ConnectorId extends Schema.Class<ConnectorId>("ConnectorId")({
18
+ value: Schema.String
19
+ }) {}
20
+
21
+ export const ConnectorIds = Object.freeze({
22
+ OpenAI: new ConnectorId({ value: "openai" }),
23
+ Anthropic: new ConnectorId({ value: "anthropic" }),
24
+ GeminiApi: new ConnectorId({ value: "gemini-api" }),
25
+ LmStudio: new ConnectorId({ value: "lm-studio" }),
26
+ Ollama: new ConnectorId({ value: "ollama" }),
27
+ ClaudeCli: new ConnectorId({ value: "claude-cli" }),
28
+ GeminiCli: new ConnectorId({ value: "gemini-cli" }),
29
+ OpenCode: new ConnectorId({ value: "opencode" }),
30
+ Codex: new ConnectorId({ value: "codex" }),
31
+ Copilot: new ConnectorId({ value: "copilot" }),
32
+ Pi: new ConnectorId({ value: "pi" }),
33
+ AntigravityCli: new ConnectorId({ value: "antigravity-cli" }),
34
+ Grok: new ConnectorId({ value: "grok" }),
35
+ Cursor: new ConnectorId({ value: "cursor" }),
36
+ Mock: new ConnectorId({ value: "mock" })
37
+ })
38
+
39
+ export const apiConnectorIds: ReadonlyArray<ConnectorId> = Object.freeze([
40
+ ConnectorIds.OpenAI,
41
+ ConnectorIds.Anthropic,
42
+ ConnectorIds.GeminiApi,
43
+ ConnectorIds.LmStudio,
44
+ ConnectorIds.Ollama
45
+ ])
46
+
47
+ export const cliConnectorIds: ReadonlyArray<ConnectorId> = Object.freeze([
48
+ ConnectorIds.ClaudeCli,
49
+ ConnectorIds.GeminiCli,
50
+ ConnectorIds.OpenCode,
51
+ ConnectorIds.Codex,
52
+ ConnectorIds.Copilot,
53
+ ConnectorIds.Pi,
54
+ ConnectorIds.AntigravityCli,
55
+ ConnectorIds.Grok,
56
+ ConnectorIds.Cursor
57
+ ])
58
+
59
+ export const connectorIds: ReadonlyArray<ConnectorId> = Object.freeze([
60
+ ...apiConnectorIds,
61
+ ...cliConnectorIds,
62
+ ConnectorIds.Mock
63
+ ])
64
+
65
+ export const defaultBaseUrl = (provider: LlmProvider): string | undefined => {
66
+ switch (provider) {
67
+ case "GeminiCli":
68
+ case "Mock":
69
+ return undefined
70
+ case "GeminiApi":
71
+ return "https://generativelanguage.googleapis.com"
72
+ case "OpenAI":
73
+ return "https://api.openai.com/v1"
74
+ case "Anthropic":
75
+ return "https://api.anthropic.com"
76
+ case "LmStudio":
77
+ return "http://localhost:1234/v1"
78
+ case "Ollama":
79
+ return "http://localhost:11434"
80
+ case "OpenCode":
81
+ return "http://localhost:4096"
82
+ }
83
+ }
84
+
85
+ const connectorProviderTable: Readonly<Record<string, LlmProvider>> = {
86
+ openai: "OpenAI",
87
+ anthropic: "Anthropic",
88
+ "gemini-api": "GeminiApi",
89
+ "lm-studio": "LmStudio",
90
+ ollama: "Ollama",
91
+ opencode: "OpenCode",
92
+ "gemini-cli": "GeminiCli",
93
+ mock: "Mock"
94
+ }
95
+
96
+ export const connectorProvider = (id: ConnectorId): LlmProvider | undefined =>
97
+ connectorProviderTable[id.value]
98
+
99
+ export const connectorDefaultBaseUrl = (id: ConnectorId): string | undefined => {
100
+ const provider = connectorProvider(id)
101
+ return provider === undefined ? undefined : defaultBaseUrl(provider)
102
+ }
103
+
104
+ export const providerConnectorId = (provider: LlmProvider): ConnectorId => {
105
+ switch (provider) {
106
+ case "GeminiCli":
107
+ return ConnectorIds.GeminiCli
108
+ case "GeminiApi":
109
+ return ConnectorIds.GeminiApi
110
+ case "OpenAI":
111
+ return ConnectorIds.OpenAI
112
+ case "Anthropic":
113
+ return ConnectorIds.Anthropic
114
+ case "LmStudio":
115
+ return ConnectorIds.LmStudio
116
+ case "Ollama":
117
+ return ConnectorIds.Ollama
118
+ case "OpenCode":
119
+ return ConnectorIds.OpenCode
120
+ case "Mock":
121
+ return ConnectorIds.Mock
122
+ }
123
+ }
124
+
125
+ export const ConnectorKind = Schema.Literals(["Api", "Cli"])
126
+ export type ConnectorKind = typeof ConnectorKind.Type
127
+
128
+ export const MessageRole = Schema.Literals(["System", "User", "Assistant", "Tool"])
129
+ export type MessageRole = typeof MessageRole.Type
130
+
131
+ export class Message extends Schema.Class<Message>("Message")({
132
+ role: MessageRole,
133
+ content: Schema.String
134
+ }) {}
135
+
136
+ export class TokenUsage extends Schema.Class<TokenUsage>("TokenUsage")({
137
+ prompt: Schema.Int,
138
+ completion: Schema.Int,
139
+ total: Schema.Int,
140
+ cached: Schema.optionalKey(Schema.Int)
141
+ }) {}
142
+
143
+ const emptyMetadata: Readonly<Record<string, string>> = Object.freeze({})
144
+
145
+ export class LlmResponse extends Schema.Class<LlmResponse>("LlmResponse")({
146
+ content: Schema.String,
147
+ usage: Schema.optionalKey(TokenUsage),
148
+ metadata: Schema.Record(Schema.String, Schema.String).pipe(
149
+ Schema.withConstructorDefault(Effect.succeed(emptyMetadata))
150
+ )
151
+ }) {}
152
+
153
+ export class LlmChunk extends Schema.Class<LlmChunk>("LlmChunk")({
154
+ delta: Schema.String,
155
+ finishReason: Schema.optionalKey(Schema.String),
156
+ usage: Schema.optionalKey(TokenUsage),
157
+ metadata: Schema.Record(Schema.String, Schema.String).pipe(
158
+ Schema.withConstructorDefault(Effect.succeed(emptyMetadata))
159
+ )
160
+ }) {}
161
+
162
+ export class StreamProgress extends Schema.Class<StreamProgress>("StreamProgress")({
163
+ tokensProcessed: Schema.Int,
164
+ tokensPerSecond: Schema.Number,
165
+ elapsedMs: Schema.Int,
166
+ estimatedRemainingMs: Schema.optionalKey(Schema.Int)
167
+ }) {}
168
+
169
+ export class LlmConfig extends Schema.Class<LlmConfig>("LlmConfig")({
170
+ provider: LlmProvider,
171
+ model: Schema.String,
172
+ baseUrl: Schema.optionalKey(Schema.String),
173
+ apiKey: Schema.optionalKey(
174
+ Schema.Redacted(Schema.String, {
175
+ disallowJsonEncode: true
176
+ })
177
+ ),
178
+ timeout: Schema.Duration.pipe(
179
+ Schema.withConstructorDefault(Effect.succeed(Duration.seconds(300)))
180
+ ),
181
+ maxRetries: Schema.Int.pipe(Schema.withConstructorDefault(Effect.succeed(3))),
182
+ requestsPerMinute: Schema.Int.pipe(Schema.withConstructorDefault(Effect.succeed(60))),
183
+ burstSize: Schema.Int.pipe(Schema.withConstructorDefault(Effect.succeed(10))),
184
+ acquireTimeout: Schema.Duration.pipe(
185
+ Schema.withConstructorDefault(Effect.succeed(Duration.seconds(30)))
186
+ ),
187
+ temperature: Schema.optionalKey(Schema.Number),
188
+ maxTokens: Schema.optionalKey(Schema.Int)
189
+ }) {}
190
+
191
+ export const withConfigDefaults = (config: LlmConfig): LlmConfig => {
192
+ if (config.baseUrl !== undefined) {
193
+ return config
194
+ }
195
+
196
+ const baseUrl = defaultBaseUrl(config.provider)
197
+ return baseUrl === undefined ? config : new LlmConfig({ ...config, baseUrl })
198
+ }
199
+
200
+ export class ToolCall extends Schema.Class<ToolCall>("ToolCall")({
201
+ id: Schema.String,
202
+ name: Schema.String,
203
+ arguments: Schema.String
204
+ }) {}
205
+
206
+ const emptyToolCalls: ReadonlyArray<ToolCall> = Object.freeze([])
207
+
208
+ export class ToolCallResponse extends Schema.Class<ToolCallResponse>("ToolCallResponse")({
209
+ content: Schema.optionalKey(Schema.String),
210
+ toolCalls: Schema.Array(ToolCall).pipe(
211
+ Schema.withConstructorDefault(Effect.succeed(emptyToolCalls))
212
+ ),
213
+ finishReason: Schema.String
214
+ }) {}
215
+
216
+ export const JsonSchema = Schema.Record(Schema.String, Schema.Json)
217
+ export type JsonSchema = typeof JsonSchema.Type
218
+
219
+ export class ToolDefinition extends Schema.Class<ToolDefinition>("ToolDefinition")({
220
+ name: Schema.String,
221
+ description: Schema.String,
222
+ parameters: JsonSchema
223
+ }) {}
224
+
225
+ export const Availability = Schema.Literals(["Healthy", "Degraded", "Unhealthy", "Unknown"])
226
+ export type Availability = typeof Availability.Type
227
+
228
+ export const AuthStatus = Schema.Literals(["Valid", "Missing", "Invalid", "Unknown"])
229
+ export type AuthStatus = typeof AuthStatus.Type
230
+
231
+ export class HealthStatus extends Schema.Class<HealthStatus>("HealthStatus")({
232
+ availability: Availability,
233
+ authStatus: AuthStatus,
234
+ latency: Schema.optionalKey(Schema.Duration)
235
+ }) {}
236
+
237
+ export const InteractionSupport = Schema.Literals(["InteractiveStdin", "ContinuationOnly"])
238
+ export type InteractionSupport = typeof InteractionSupport.Type
239
+
240
+ export class ConnectorCapabilities extends Schema.Class<ConnectorCapabilities>(
241
+ "ConnectorCapabilities"
242
+ )({
243
+ streaming: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(true))),
244
+ resumableSessions: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
245
+ interactiveSessions: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
246
+ askUser: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
247
+ approval: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false))),
248
+ structuredOutput: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(true))),
249
+ usageReporting: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(true)))
250
+ }) {}