@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,322 @@
1
+ import * as Duration from "effect/Duration"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Schedule from "effect/Schedule"
4
+ import type * as Schema from "effect/Schema"
5
+ import * as Stream from "effect/Stream"
6
+ import { apiConnectorCapabilities, type ApiConnectorShape } from "../Connector.ts"
7
+ import type { LlmError } from "../Errors.ts"
8
+ import type { StructuredResult } from "../LlmService.ts"
9
+ import {
10
+ ConnectorIds,
11
+ HealthStatus,
12
+ LlmChunk,
13
+ TokenUsage,
14
+ ToolCallResponse,
15
+ type JsonSchema,
16
+ type LlmConfig,
17
+ type Message
18
+ } from "../Models.ts"
19
+ import { parseFromText } from "../StructuredOutput.ts"
20
+
21
+ export const mockResponse = (): string =>
22
+ "This is a mock response from the demo AI provider. " +
23
+ "The mock provider simulates LLM output without making any real API calls. " +
24
+ "All features work normally except AI-generated content is replaced with pre-canned responses."
25
+
26
+ interface MockIssueTemplate {
27
+ readonly title: string
28
+ readonly tags: ReadonlyArray<string>
29
+ readonly capabilities: ReadonlyArray<string>
30
+ readonly criteria: ReadonlyArray<string>
31
+ readonly body: string
32
+ }
33
+
34
+ const issueTemplates: ReadonlyArray<MockIssueTemplate> = [
35
+ {
36
+ title: "Set up Spring Boot project scaffold",
37
+ tags: ["setup", "bootstrap"],
38
+ capabilities: ["java", "spring-boot"],
39
+ criteria: [
40
+ "Maven/Gradle build compiles",
41
+ "Application starts on port 8080",
42
+ "Health endpoint returns 200"
43
+ ],
44
+ body:
45
+ "Initialize the Spring Boot 3.x project with Java 21, configure build tool, " +
46
+ "and add Actuator health endpoint."
47
+ },
48
+ {
49
+ title: "Implement REST controller for orders",
50
+ tags: ["rest", "api"],
51
+ capabilities: ["java", "spring-boot", "rest"],
52
+ criteria: [
53
+ "GET /api/orders returns list",
54
+ "POST /api/orders creates order",
55
+ "Validation errors return 400"
56
+ ],
57
+ body: "Create OrderController with CRUD endpoints, request validation, and proper HTTP status codes."
58
+ },
59
+ {
60
+ title: "Add JPA entity and repository for Order",
61
+ tags: ["database", "jpa"],
62
+ capabilities: ["java", "spring-data"],
63
+ criteria: [
64
+ "Order entity maps to orders table",
65
+ "Repository supports findByStatus",
66
+ "Flyway migration creates schema"
67
+ ],
68
+ body: "Define Order JPA entity with audit fields, OrderRepository interface, and initial Flyway migration script."
69
+ },
70
+ {
71
+ title: "Configure Spring Security with JWT authentication",
72
+ tags: ["security", "auth"],
73
+ capabilities: ["java", "spring-security"],
74
+ criteria: [
75
+ "Unauthenticated requests return 401",
76
+ "Valid JWT grants access",
77
+ "Tokens expire after configured TTL"
78
+ ],
79
+ body: "Set up Spring Security filter chain with JWT validation, configure public and protected endpoints."
80
+ },
81
+ {
82
+ title: "Write unit tests for OrderService",
83
+ tags: ["testing", "unit-test"],
84
+ capabilities: ["java", "testing"],
85
+ criteria: [
86
+ "Service methods have test coverage",
87
+ "Edge cases tested",
88
+ "Mocks used for repository layer"
89
+ ],
90
+ body: "Create JUnit 5 tests for OrderService using Mockito for repository dependencies."
91
+ },
92
+ {
93
+ title: "Add pagination and sorting to list endpoints",
94
+ tags: ["rest", "api"],
95
+ capabilities: ["java", "spring-boot"],
96
+ criteria: [
97
+ "GET /api/orders supports page and size params",
98
+ "Sort by createdAt default",
99
+ "Response includes total count"
100
+ ],
101
+ body: "Implement Spring Data Pageable support in OrderController with proper response envelope."
102
+ },
103
+ {
104
+ title: "Set up Docker container and Compose file",
105
+ tags: ["devops", "docker"],
106
+ capabilities: ["docker", "devops"],
107
+ criteria: [
108
+ "Dockerfile builds multi-stage image",
109
+ "docker-compose starts app with PostgreSQL",
110
+ "Container health check passes"
111
+ ],
112
+ body: "Create optimized multi-stage Dockerfile and docker-compose.yml with PostgreSQL service."
113
+ },
114
+ {
115
+ title: "Implement global exception handler",
116
+ tags: ["rest", "error-handling"],
117
+ capabilities: ["java", "spring-boot"],
118
+ criteria: [
119
+ "Validation errors return structured response",
120
+ "404 returns proper body",
121
+ "500 errors are logged"
122
+ ],
123
+ body: "Create @ControllerAdvice with handlers for common exceptions, returning consistent error response format."
124
+ },
125
+ {
126
+ title: "Add integration tests with TestContainers",
127
+ tags: ["testing", "integration"],
128
+ capabilities: ["java", "testing", "docker"],
129
+ criteria: [
130
+ "Tests run against real PostgreSQL",
131
+ "Database is reset between tests",
132
+ "CI pipeline can run tests"
133
+ ],
134
+ body: "Set up TestContainers with PostgreSQL for integration testing, create base test class with shared container."
135
+ },
136
+ {
137
+ title: "Configure structured logging with correlation IDs",
138
+ tags: ["observability", "logging"],
139
+ capabilities: ["java", "observability"],
140
+ criteria: [
141
+ "Logs include correlationId header",
142
+ "JSON log format in production",
143
+ "Request/response logged at DEBUG"
144
+ ],
145
+ body: "Set up Logback with JSON encoder, MDC-based correlation ID propagation via servlet filter."
146
+ },
147
+ {
148
+ title: "Create OpenAPI specification",
149
+ tags: ["documentation", "api"],
150
+ capabilities: ["java", "documentation"],
151
+ criteria: [
152
+ "Swagger UI accessible at /swagger-ui",
153
+ "All endpoints documented",
154
+ "Schema examples included"
155
+ ],
156
+ body: "Add springdoc-openapi dependency, annotate controllers with OpenAPI annotations."
157
+ },
158
+ {
159
+ title: "Implement rate limiting middleware",
160
+ tags: ["security", "performance"],
161
+ capabilities: ["java", "spring-boot"],
162
+ criteria: [
163
+ "Rate limit headers in response",
164
+ "429 returned when exceeded",
165
+ "Configurable per-endpoint limits"
166
+ ],
167
+ body: "Add Bucket4j-based rate limiting filter with configurable limits per endpoint."
168
+ },
169
+ {
170
+ title: "Set up GitHub Actions CI pipeline",
171
+ tags: ["devops", "ci"],
172
+ capabilities: ["devops", "ci"],
173
+ criteria: [
174
+ "Build runs on push to main",
175
+ "Tests execute in CI",
176
+ "Docker image pushed on release"
177
+ ],
178
+ body: "Create GitHub Actions workflow for build, test, and Docker image publishing."
179
+ },
180
+ {
181
+ title: "Add Actuator metrics and custom health indicators",
182
+ tags: ["observability", "monitoring"],
183
+ capabilities: ["java", "observability"],
184
+ criteria: [
185
+ "Custom health check for database",
186
+ "Business metrics exposed",
187
+ "Prometheus endpoint available"
188
+ ],
189
+ body: "Configure Actuator with Prometheus exporter, add custom HealthIndicator and MeterBinder implementations."
190
+ },
191
+ {
192
+ title: "Implement order status workflow",
193
+ tags: ["business-logic", "workflow"],
194
+ capabilities: ["java", "spring-boot"],
195
+ criteria: [
196
+ "Status transitions are validated",
197
+ "Invalid transitions return 422",
198
+ "Status history is tracked"
199
+ ],
200
+ body: "Create OrderStatusMachine with allowed transitions, persist status change events."
201
+ }
202
+ ]
203
+
204
+ const mockIssue = (index: number): Readonly<Record<string, unknown>> => {
205
+ const template = issueTemplates[(index - 1) % issueTemplates.length]
206
+ return {
207
+ id: `demo-issue-${index}`,
208
+ title: template?.title ?? "",
209
+ priority: index <= 3 ? "high" : index <= 7 ? "medium" : "low",
210
+ assignedAgent: null,
211
+ requiredCapabilities: template?.capabilities ?? [],
212
+ blockedBy: [],
213
+ tags: template?.tags ?? [],
214
+ acceptanceCriteria: template?.criteria ?? [],
215
+ estimate: "2h",
216
+ proofOfWork: ["diff-stat"],
217
+ body: template?.body ?? ""
218
+ }
219
+ }
220
+
221
+ const requestedIssueCount = (prompt: string): number => {
222
+ const matched = /(\d+)\s*issue/i.exec(prompt)?.[1]
223
+ return Math.min(matched === undefined ? 10 : Number(matched), 100)
224
+ }
225
+
226
+ export const mockStructuredResponse = (prompt: string, schema: JsonSchema): string => {
227
+ const schemaText = JSON.stringify(schema)
228
+ if (schemaText.includes('"summary"') && schemaText.includes('"issues"')) {
229
+ const count = requestedIssueCount(prompt)
230
+ return JSON.stringify({
231
+ summary: `Generated ${count} issue drafts for Spring Boot microservice demo.`,
232
+ issues: Array.from({ length: count }, (_, index) => mockIssue(index + 1))
233
+ })
234
+ }
235
+ return JSON.stringify({
236
+ result: "mock structured response"
237
+ })
238
+ }
239
+
240
+ const lastUserPrompt = (messages: ReadonlyArray<Message>): string => {
241
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
242
+ const message = messages[index]
243
+ if (message?.role === "User") {
244
+ return message.content
245
+ }
246
+ }
247
+ return ""
248
+ }
249
+
250
+ export const makeMockProvider = (config: LlmConfig): ApiConnectorShape => {
251
+ const executeStructuredWithUsage = <A, E, RD, RE>(
252
+ prompt: string,
253
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
254
+ jsonSchema: JsonSchema
255
+ ): Effect.Effect<StructuredResult<A>, LlmError, RD> =>
256
+ Effect.gen(function* () {
257
+ const value = yield* parseFromText(
258
+ mockStructuredResponse(prompt, jsonSchema),
259
+ schema,
260
+ jsonSchema
261
+ )
262
+ const result: StructuredResult<A> = [value, undefined, undefined]
263
+ return result
264
+ })
265
+
266
+ const executeStream = (_prompt: string): Stream.Stream<LlmChunk, LlmError> => {
267
+ const words = mockResponse().split(" ")
268
+ return Stream.fromIterable(
269
+ words.map((word, index) => {
270
+ const last = index === words.length - 1
271
+ return LlmChunk.make({
272
+ delta: `${index === 0 ? "" : " "}${word}`,
273
+ metadata: {
274
+ provider: "mock",
275
+ model: config.model
276
+ },
277
+ ...(last ? { finishReason: "stop" } : {}),
278
+ ...(last
279
+ ? {
280
+ usage: TokenUsage.make({
281
+ prompt: 10,
282
+ completion: words.length,
283
+ total: 10 + words.length
284
+ })
285
+ }
286
+ : {})
287
+ })
288
+ })
289
+ ).pipe(Stream.schedule(Schedule.spaced(Duration.millis(50))))
290
+ }
291
+
292
+ return {
293
+ id: ConnectorIds.Mock,
294
+ kind: "Api",
295
+ capabilities: apiConnectorCapabilities(),
296
+ healthCheck: Effect.succeed(
297
+ HealthStatus.make({
298
+ availability: "Healthy",
299
+ authStatus: "Valid",
300
+ latency: Duration.zero
301
+ })
302
+ ),
303
+ executeStream,
304
+ executeStreamWithHistory: (messages) => executeStream(lastUserPrompt(messages)),
305
+ executeWithTools: (_prompt) =>
306
+ Effect.succeed(
307
+ ToolCallResponse.make({
308
+ content: mockResponse(),
309
+ toolCalls: [],
310
+ finishReason: "stop"
311
+ })
312
+ ),
313
+ executeStructured: <A, E, RD, RE>(
314
+ prompt: string,
315
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
316
+ jsonSchema: JsonSchema
317
+ ): Effect.Effect<A, LlmError, RD> =>
318
+ Effect.map(executeStructuredWithUsage(prompt, schema, jsonSchema), ([value]) => value),
319
+ executeStructuredWithUsage,
320
+ isAvailable: Effect.succeed(true)
321
+ }
322
+ }
@@ -0,0 +1,64 @@
1
+ import * as Schema from "effect/Schema"
2
+
3
+ export class OllamaOptions extends Schema.Class<OllamaOptions>("OllamaOptions")({
4
+ temperature: Schema.optionalKey(Schema.Number),
5
+ num_predict: Schema.optionalKey(Schema.Int)
6
+ }) {}
7
+
8
+ export class OllamaGenerateRequest extends Schema.Class<OllamaGenerateRequest>(
9
+ "OllamaGenerateRequest"
10
+ )({
11
+ model: Schema.String,
12
+ prompt: Schema.String,
13
+ stream: Schema.Boolean,
14
+ format: Schema.optionalKey(Schema.String),
15
+ options: Schema.optionalKey(OllamaOptions)
16
+ }) {}
17
+
18
+ export class OllamaMessage extends Schema.Class<OllamaMessage>("OllamaMessage")({
19
+ role: Schema.String,
20
+ content: Schema.String
21
+ }) {}
22
+
23
+ export class OllamaChatRequest extends Schema.Class<OllamaChatRequest>("OllamaChatRequest")({
24
+ model: Schema.String,
25
+ messages: Schema.Array(OllamaMessage),
26
+ stream: Schema.Boolean,
27
+ format: Schema.optionalKey(Schema.String),
28
+ options: Schema.optionalKey(OllamaOptions)
29
+ }) {}
30
+
31
+ export class OllamaGenerateResponse extends Schema.Class<OllamaGenerateResponse>(
32
+ "OllamaGenerateResponse"
33
+ )({
34
+ model: Schema.String,
35
+ response: Schema.String,
36
+ done: Schema.Boolean,
37
+ context: Schema.optionalKey(Schema.Array(Schema.Int)),
38
+ total_duration: Schema.optionalKey(Schema.Int),
39
+ load_duration: Schema.optionalKey(Schema.Int),
40
+ prompt_eval_count: Schema.optionalKey(Schema.Int),
41
+ eval_count: Schema.optionalKey(Schema.Int)
42
+ }) {}
43
+
44
+ export class OllamaChatResponse extends Schema.Class<OllamaChatResponse>("OllamaChatResponse")({
45
+ model: Schema.String,
46
+ message: OllamaMessage,
47
+ done: Schema.Boolean,
48
+ total_duration: Schema.optionalKey(Schema.Int),
49
+ load_duration: Schema.optionalKey(Schema.Int),
50
+ prompt_eval_count: Schema.optionalKey(Schema.Int),
51
+ eval_count: Schema.optionalKey(Schema.Int)
52
+ }) {}
53
+
54
+ export class OllamaModelInfo extends Schema.Class<OllamaModelInfo>("OllamaModelInfo")({
55
+ name: Schema.String,
56
+ modified_at: Schema.String,
57
+ size: Schema.Int
58
+ }) {}
59
+
60
+ export class OllamaModelsResponse extends Schema.Class<OllamaModelsResponse>(
61
+ "OllamaModelsResponse"
62
+ )({
63
+ models: Schema.Array(OllamaModelInfo)
64
+ }) {}
@@ -0,0 +1,236 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Schema from "effect/Schema"
3
+ import * as Stream from "effect/Stream"
4
+ import { makeApiConnector, type ApiConnectorShape } from "../Connector.ts"
5
+ import { ConfigError, InvalidRequestError, ParseError, type LlmError } from "../Errors.ts"
6
+ import type { HttpClientShape } from "../HttpClient.ts"
7
+ import type { StructuredResult } from "../LlmService.ts"
8
+ import {
9
+ ConnectorIds,
10
+ LlmChunk,
11
+ TokenUsage,
12
+ type JsonSchema,
13
+ type LlmConfig,
14
+ type Message
15
+ } from "../Models.ts"
16
+ import { parseFromText } from "../StructuredOutput.ts"
17
+ import {
18
+ OllamaChatRequest,
19
+ OllamaChatResponse,
20
+ OllamaGenerateRequest,
21
+ OllamaGenerateResponse,
22
+ OllamaMessage,
23
+ OllamaOptions
24
+ } from "./OllamaModels.ts"
25
+
26
+ const noHeaders: Readonly<Record<string, string>> = Object.freeze({})
27
+
28
+ const baseUrlEffect = (config: LlmConfig): Effect.Effect<string, ConfigError> =>
29
+ config.baseUrl === undefined
30
+ ? Effect.fail(
31
+ ConfigError.make({
32
+ message: "Missing baseUrl for Ollama provider"
33
+ })
34
+ )
35
+ : Effect.succeed(config.baseUrl.replace(/\/+$/, ""))
36
+
37
+ const options = (config: LlmConfig): OllamaOptions =>
38
+ OllamaOptions.make({
39
+ ...(config.temperature === undefined ? {} : { temperature: config.temperature }),
40
+ ...(config.maxTokens === undefined ? {} : { num_predict: config.maxTokens })
41
+ })
42
+
43
+ export const ollamaHistory = (messages: ReadonlyArray<Message>): ReadonlyArray<OllamaMessage> =>
44
+ messages.map((message) =>
45
+ OllamaMessage.make({
46
+ role:
47
+ message.role === "System" ? "system" : message.role === "Assistant" ? "assistant" : "user",
48
+ content: message.content
49
+ })
50
+ )
51
+
52
+ const generateUsage = (response: OllamaGenerateResponse): TokenUsage | undefined =>
53
+ response.prompt_eval_count === undefined || response.eval_count === undefined
54
+ ? undefined
55
+ : TokenUsage.make({
56
+ prompt: response.prompt_eval_count,
57
+ completion: response.eval_count,
58
+ total: response.prompt_eval_count + response.eval_count
59
+ })
60
+
61
+ const chatUsage = (response: OllamaChatResponse): TokenUsage | undefined =>
62
+ response.prompt_eval_count === undefined || response.eval_count === undefined
63
+ ? undefined
64
+ : TokenUsage.make({
65
+ prompt: response.prompt_eval_count,
66
+ completion: response.eval_count,
67
+ total: response.prompt_eval_count + response.eval_count
68
+ })
69
+
70
+ const decodeGenerate = (
71
+ raw: string,
72
+ stream: boolean
73
+ ): Effect.Effect<OllamaGenerateResponse, ParseError> =>
74
+ Schema.decodeUnknownEffect(Schema.fromJsonString(OllamaGenerateResponse))(raw).pipe(
75
+ Effect.mapError((error) =>
76
+ ParseError.make({
77
+ message:
78
+ `${stream ? "Failed to parse Ollama stream chunk" : "Failed to decode Ollama response"}: ` +
79
+ String(error),
80
+ raw
81
+ })
82
+ )
83
+ )
84
+
85
+ const decodeChat = (raw: string): Effect.Effect<OllamaChatResponse, ParseError> =>
86
+ Schema.decodeUnknownEffect(Schema.fromJsonString(OllamaChatResponse))(raw).pipe(
87
+ Effect.mapError((error) =>
88
+ ParseError.make({
89
+ message: `Failed to parse Ollama chat stream chunk: ${String(error)}`,
90
+ raw
91
+ })
92
+ )
93
+ )
94
+
95
+ export const makeOllamaProvider = (
96
+ config: LlmConfig,
97
+ httpClient: HttpClientShape
98
+ ): ApiConnectorShape => {
99
+ const generateStream = (prompt: string, format?: string): Stream.Stream<LlmChunk, LlmError> =>
100
+ Stream.unwrap(
101
+ Effect.map(baseUrlEffect(config), (baseUrl) => {
102
+ const request = OllamaGenerateRequest.make({
103
+ model: config.model,
104
+ prompt,
105
+ stream: true,
106
+ options: options(config),
107
+ ...(format === undefined ? {} : { format })
108
+ })
109
+ return httpClient
110
+ .postJsonStream(
111
+ `${baseUrl}/api/generate`,
112
+ JSON.stringify(request),
113
+ noHeaders,
114
+ config.timeout
115
+ )
116
+ .pipe(
117
+ Stream.filter((line) => line.trim().length > 0),
118
+ Stream.mapEffect((line) => decodeGenerate(line, true)),
119
+ Stream.flatMap((response) => {
120
+ const usage = response.done ? generateUsage(response) : undefined
121
+ return response.response.length > 0 || response.done
122
+ ? Stream.succeed(
123
+ LlmChunk.make({
124
+ delta: response.response,
125
+ metadata: {
126
+ provider: "ollama",
127
+ model: response.model
128
+ },
129
+ ...(response.done ? { finishReason: "stop" } : {}),
130
+ ...(usage === undefined ? {} : { usage })
131
+ })
132
+ )
133
+ : Stream.empty
134
+ })
135
+ )
136
+ })
137
+ )
138
+
139
+ const chatStream = (messages: ReadonlyArray<OllamaMessage>): Stream.Stream<LlmChunk, LlmError> =>
140
+ Stream.unwrap(
141
+ Effect.map(baseUrlEffect(config), (baseUrl) => {
142
+ const request = OllamaChatRequest.make({
143
+ model: config.model,
144
+ messages,
145
+ stream: true,
146
+ options: options(config)
147
+ })
148
+ return httpClient
149
+ .postJsonStream(`${baseUrl}/api/chat`, JSON.stringify(request), noHeaders, config.timeout)
150
+ .pipe(
151
+ Stream.filter((line) => line.trim().length > 0),
152
+ Stream.mapEffect(decodeChat),
153
+ Stream.flatMap((response) => {
154
+ const usage = response.done ? chatUsage(response) : undefined
155
+ return response.message.content.length > 0 || response.done
156
+ ? Stream.succeed(
157
+ LlmChunk.make({
158
+ delta: response.message.content,
159
+ metadata: {
160
+ provider: "ollama",
161
+ model: response.model
162
+ },
163
+ ...(response.done ? { finishReason: "stop" } : {}),
164
+ ...(usage === undefined ? {} : { usage })
165
+ })
166
+ )
167
+ : Stream.empty
168
+ })
169
+ )
170
+ })
171
+ )
172
+
173
+ const generate = (
174
+ prompt: string,
175
+ format?: string
176
+ ): Effect.Effect<readonly [response: OllamaGenerateResponse, raw: string], LlmError> =>
177
+ Effect.gen(function* () {
178
+ const baseUrl = yield* baseUrlEffect(config)
179
+ const request = OllamaGenerateRequest.make({
180
+ model: config.model,
181
+ prompt,
182
+ stream: false,
183
+ options: options(config),
184
+ ...(format === undefined ? {} : { format })
185
+ })
186
+ const raw = yield* httpClient.postJson(
187
+ `${baseUrl}/api/generate`,
188
+ JSON.stringify(request),
189
+ noHeaders,
190
+ config.timeout
191
+ )
192
+ const response = yield* decodeGenerate(raw, false)
193
+ return [response, raw]
194
+ })
195
+
196
+ const executeStructuredWithUsage = <A, E, RD, RE>(
197
+ prompt: string,
198
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
199
+ jsonSchema: JsonSchema
200
+ ): Effect.Effect<StructuredResult<A>, LlmError, RD> =>
201
+ Effect.gen(function* () {
202
+ const [response] = yield* generate(
203
+ `${prompt}\n\nPlease respond with valid JSON matching the provided schema.`,
204
+ "json"
205
+ )
206
+ const value = yield* parseFromText(response.response, schema, jsonSchema)
207
+ const result: StructuredResult<A> = [value, undefined, undefined]
208
+ return result
209
+ })
210
+
211
+ const isAvailable: Effect.Effect<boolean> =
212
+ config.baseUrl === undefined
213
+ ? Effect.succeed(false)
214
+ : httpClient
215
+ .get(`${config.baseUrl.replace(/\/+$/, "")}/api/tags`, noHeaders, config.timeout)
216
+ .pipe(
217
+ Effect.match({
218
+ onFailure: () => false,
219
+ onSuccess: () => true
220
+ })
221
+ )
222
+
223
+ return makeApiConnector({
224
+ id: ConnectorIds.Ollama,
225
+ executeStream: (prompt) => generateStream(prompt),
226
+ executeStreamWithHistory: (messages) => chatStream(ollamaHistory(messages)),
227
+ executeWithTools: () =>
228
+ Effect.fail(
229
+ InvalidRequestError.make({
230
+ message: "Ollama provider does not support tool calling"
231
+ })
232
+ ),
233
+ executeStructuredWithUsage,
234
+ isAvailable
235
+ })
236
+ }