@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,793 @@
1
+ import * as Context from "effect/Context"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Option from "effect/Option"
4
+ import * as Ref from "effect/Ref"
5
+ import * as Schema from "effect/Schema"
6
+ import * as Stream from "effect/Stream"
7
+ import { makeCliConnector, type CliConnectorShape } from "../Connector.ts"
8
+ import type { CliConnectorConfig } from "../ConnectorConfig.ts"
9
+ import {
10
+ InvalidRequestError,
11
+ ParseError,
12
+ ProviderError,
13
+ TurnLimitError,
14
+ type LlmError
15
+ } from "../Errors.ts"
16
+ import type { StructuredResult } from "../LlmService.ts"
17
+ import {
18
+ ConnectorIds,
19
+ HealthStatus,
20
+ LlmChunk,
21
+ TokenUsage,
22
+ type JsonSchema,
23
+ type LlmConfig,
24
+ type Message
25
+ } from "../Models.ts"
26
+ import { collect } from "../Streaming.ts"
27
+ import { jsonCandidates, parseFromText, withSchemaHint } from "../StructuredOutput.ts"
28
+ import {
29
+ failClassifiedCliError,
30
+ jsonField,
31
+ jsonIntField,
32
+ jsonStringField,
33
+ jsonText,
34
+ parseJsonLine
35
+ } from "./CliSupport.ts"
36
+
37
+ export class GeminiCliLogLine extends Schema.TaggedClass<GeminiCliLogLine>()("LogLine", {
38
+ line: Schema.String
39
+ }) {}
40
+
41
+ export class GeminiCliInit extends Schema.TaggedClass<GeminiCliInit>()("Init", {
42
+ model: Schema.optionalKey(Schema.String),
43
+ sessionId: Schema.optionalKey(Schema.String)
44
+ }) {}
45
+
46
+ export class GeminiCliMessage extends Schema.TaggedClass<GeminiCliMessage>()("Message", {
47
+ role: Schema.optionalKey(Schema.String),
48
+ content: Schema.optionalKey(Schema.String),
49
+ delta: Schema.Boolean
50
+ }) {}
51
+
52
+ export class GeminiCliToolUse extends Schema.TaggedClass<GeminiCliToolUse>()("ToolUse", {
53
+ toolName: Schema.optionalKey(Schema.String),
54
+ toolId: Schema.optionalKey(Schema.String),
55
+ input: Schema.optionalKey(Schema.String)
56
+ }) {}
57
+
58
+ export class GeminiCliToolResult extends Schema.TaggedClass<GeminiCliToolResult>()("ToolResult", {
59
+ toolId: Schema.optionalKey(Schema.String),
60
+ status: Schema.optionalKey(Schema.String),
61
+ content: Schema.optionalKey(Schema.String)
62
+ }) {}
63
+
64
+ export class GeminiCliError extends Schema.TaggedClass<GeminiCliError>()("Error", {
65
+ message: Schema.optionalKey(Schema.String),
66
+ code: Schema.optionalKey(Schema.Int),
67
+ errorType: Schema.optionalKey(Schema.String)
68
+ }) {}
69
+
70
+ export class GeminiStreamStats extends Schema.Class<GeminiStreamStats>("GeminiStreamStats")({
71
+ totalTokens: Schema.optionalKey(Schema.Int),
72
+ inputTokens: Schema.optionalKey(Schema.Int),
73
+ outputTokens: Schema.optionalKey(Schema.Int),
74
+ cached: Schema.optionalKey(Schema.Int)
75
+ }) {}
76
+
77
+ export class GeminiCliResult extends Schema.TaggedClass<GeminiCliResult>()("Result", {
78
+ status: Schema.optionalKey(Schema.String),
79
+ errorMessage: Schema.optionalKey(Schema.String),
80
+ stats: Schema.optionalKey(GeminiStreamStats)
81
+ }) {}
82
+
83
+ export const GeminiCliStreamEvent = Schema.Union([
84
+ GeminiCliLogLine,
85
+ GeminiCliInit,
86
+ GeminiCliMessage,
87
+ GeminiCliToolUse,
88
+ GeminiCliToolResult,
89
+ GeminiCliError,
90
+ GeminiCliResult
91
+ ])
92
+ export type GeminiCliStreamEvent = typeof GeminiCliStreamEvent.Type
93
+
94
+ export const GeminiSandbox = Schema.Literals([
95
+ "Docker",
96
+ "Podman",
97
+ "SeatbeltMacOS",
98
+ "Runsc",
99
+ "Lxc",
100
+ "Default"
101
+ ])
102
+ export type GeminiSandbox = typeof GeminiSandbox.Type
103
+
104
+ const emptyDirectories: ReadonlyArray<string> = Object.freeze([])
105
+
106
+ export class GeminiCliExecutionContext extends Schema.Class<GeminiCliExecutionContext>(
107
+ "GeminiCliExecutionContext"
108
+ )({
109
+ cwd: Schema.optionalKey(Schema.String),
110
+ includeDirectories: Schema.Array(Schema.String).pipe(
111
+ Schema.withConstructorDefault(Effect.succeed(emptyDirectories))
112
+ ),
113
+ sandbox: Schema.optionalKey(GeminiSandbox),
114
+ turnLimit: Schema.optionalKey(Schema.Int),
115
+ readOnly: Schema.Boolean.pipe(Schema.withConstructorDefault(Effect.succeed(false)))
116
+ }) {}
117
+
118
+ export const geminiCliExecutionContextFrom = (
119
+ config: CliConnectorConfig
120
+ ): GeminiCliExecutionContext =>
121
+ GeminiCliExecutionContext.make({
122
+ ...(config.workingDir === undefined ? {} : { cwd: config.workingDir }),
123
+ ...(config.turnLimit === undefined ? {} : { turnLimit: config.turnLimit }),
124
+ readOnly: config.readOnly,
125
+ ...(config.sandbox === undefined
126
+ ? {}
127
+ : {
128
+ sandbox: config.sandbox._tag
129
+ })
130
+ })
131
+
132
+ export interface GeminiCliExecutorShape {
133
+ readonly checkGeminiInstalled: Effect.Effect<void, LlmError>
134
+ readonly runGeminiProcess: (
135
+ prompt: string,
136
+ config: LlmConfig,
137
+ context: GeminiCliExecutionContext
138
+ ) => Effect.Effect<string, LlmError>
139
+ readonly runGeminiProcessStream: (
140
+ prompt: string,
141
+ config: LlmConfig,
142
+ context: GeminiCliExecutionContext
143
+ ) => Stream.Stream<GeminiCliStreamEvent, LlmError>
144
+ }
145
+
146
+ export class GeminiCliExecutor extends Context.Service<GeminiCliExecutor, GeminiCliExecutorShape>()(
147
+ "@llm4ts/core/providers/GeminiCliExecutor"
148
+ ) {}
149
+
150
+ export const geminiSandboxEnvValue = (sandbox: GeminiSandbox): string | undefined => {
151
+ switch (sandbox) {
152
+ case "Docker":
153
+ return "docker"
154
+ case "Podman":
155
+ return "podman"
156
+ case "SeatbeltMacOS":
157
+ return "sandbox-exec"
158
+ case "Runsc":
159
+ return "runsc"
160
+ case "Lxc":
161
+ return "lxc"
162
+ case "Default":
163
+ return undefined
164
+ }
165
+ }
166
+
167
+ export const buildGeminiArgs = (
168
+ config: LlmConfig,
169
+ context: GeminiCliExecutionContext,
170
+ outputFormat: string
171
+ ): ReadonlyArray<string> => {
172
+ const directories = [...new Set(context.includeDirectories)].flatMap((path) => [
173
+ "--include-directories",
174
+ path
175
+ ])
176
+ return [
177
+ "-m",
178
+ config.model,
179
+ ...(context.readOnly ? ["--approval-mode", "plan"] : ["-y"]),
180
+ "--output-format",
181
+ outputFormat,
182
+ ...directories,
183
+ ...(context.sandbox === undefined ? [] : ["-s"])
184
+ ]
185
+ }
186
+
187
+ export const geminiTurnLimitSettingsJson = (turns: number): string =>
188
+ JSON.stringify({
189
+ model: {
190
+ maxSessionTurns: turns
191
+ }
192
+ })
193
+
194
+ export const geminiProcessEnv = (
195
+ context: GeminiCliExecutionContext,
196
+ turnLimitSettingsPath?: string
197
+ ): Readonly<Record<string, string>> => {
198
+ const sandbox = context.sandbox === undefined ? undefined : geminiSandboxEnvValue(context.sandbox)
199
+ return {
200
+ GEMINI_CLI_TRUST_WORKSPACE: "true",
201
+ ...(sandbox === undefined ? {} : { GEMINI_SANDBOX: sandbox }),
202
+ ...(turnLimitSettingsPath === undefined
203
+ ? {}
204
+ : {
205
+ GEMINI_CLI_SYSTEM_DEFAULTS_PATH: turnLimitSettingsPath
206
+ })
207
+ }
208
+ }
209
+
210
+ export const geminiQuotaDiagnostic = (stderr: ReadonlyArray<string>): string | undefined =>
211
+ stderr
212
+ .find((line) => {
213
+ const lower = line.toLowerCase()
214
+ return (
215
+ lower.includes("exhausted your capacity") ||
216
+ lower.includes("quota") ||
217
+ lower.includes("resource_exhausted") ||
218
+ lower.includes("rate_limit") ||
219
+ lower.includes("rate limit") ||
220
+ lower.includes("429")
221
+ )
222
+ })
223
+ ?.trim()
224
+
225
+ export const withGeminiStderrDiagnostic = (
226
+ event: GeminiCliStreamEvent,
227
+ stderr: ReadonlyArray<string>
228
+ ): GeminiCliStreamEvent => {
229
+ const diagnostic = geminiQuotaDiagnostic(stderr)
230
+ if (diagnostic === undefined) {
231
+ return event
232
+ }
233
+ const merged = (base: string | undefined): string => {
234
+ const normalized = base?.trim()
235
+ if (normalized !== undefined && geminiQuotaDiagnostic([normalized]) !== undefined) {
236
+ return normalized
237
+ }
238
+ return normalized === undefined || normalized.length === 0
239
+ ? diagnostic
240
+ : `${normalized} — ${diagnostic}`
241
+ }
242
+
243
+ switch (event._tag) {
244
+ case "Error":
245
+ return GeminiCliError.make({
246
+ message: merged(event.message),
247
+ ...(event.code === undefined ? {} : { code: event.code }),
248
+ ...(event.errorType === undefined ? {} : { errorType: event.errorType })
249
+ })
250
+ case "Result":
251
+ return event.status === "error"
252
+ ? GeminiCliResult.make({
253
+ status: event.status,
254
+ errorMessage: merged(event.errorMessage),
255
+ ...(event.stats === undefined ? {} : { stats: event.stats })
256
+ })
257
+ : event
258
+ default:
259
+ return event
260
+ }
261
+ }
262
+
263
+ export const validateGeminiExitCode = (
264
+ exitCode: number,
265
+ stderr: string,
266
+ turnLimit?: number
267
+ ): Effect.Effect<void, LlmError> => {
268
+ switch (exitCode) {
269
+ case 0:
270
+ return Effect.void
271
+ case 42:
272
+ return Effect.fail(InvalidRequestError.make({ message: stderr }))
273
+ case 53:
274
+ return Effect.fail(
275
+ TurnLimitError.make({
276
+ ...(turnLimit === undefined ? {} : { limit: turnLimit })
277
+ })
278
+ )
279
+ default:
280
+ return failClassifiedCliError("gemini", `Gemini CLI exited with code ${exitCode}`, stderr)
281
+ }
282
+ }
283
+
284
+ export const validateGeminiStreamExit = (
285
+ exitCode: number,
286
+ stderr: ReadonlyArray<string>,
287
+ sawAssistantText: boolean,
288
+ turnLimit?: number
289
+ ): Effect.Effect<void, LlmError> => {
290
+ const diagnostic = geminiQuotaDiagnostic(stderr)
291
+ const stderrText = stderr.join("\n").trim()
292
+ if (exitCode === 0) {
293
+ return diagnostic !== undefined && !sawAssistantText
294
+ ? failClassifiedCliError("gemini", "Gemini CLI quota exhausted", diagnostic)
295
+ : Effect.void
296
+ }
297
+ if (exitCode === 42 || exitCode === 53) {
298
+ return validateGeminiExitCode(
299
+ exitCode,
300
+ stderrText.length === 0 ? `Gemini stream process exited with code ${exitCode}` : stderrText,
301
+ turnLimit
302
+ )
303
+ }
304
+ return diagnostic === undefined
305
+ ? validateGeminiExitCode(
306
+ exitCode,
307
+ stderrText.length === 0 ? `Gemini stream process exited with code ${exitCode}` : stderrText,
308
+ turnLimit
309
+ )
310
+ : failClassifiedCliError("gemini", "Gemini CLI quota exhausted", diagnostic)
311
+ }
312
+
313
+ class GeminiHeadlessError extends Schema.Class<GeminiHeadlessError>("GeminiHeadlessError")({
314
+ type: Schema.optionalKey(Schema.String),
315
+ message: Schema.optionalKey(Schema.String),
316
+ code: Schema.optionalKey(Schema.Int)
317
+ }) {}
318
+
319
+ class GeminiHeadlessResponse extends Schema.Class<GeminiHeadlessResponse>("GeminiHeadlessResponse")(
320
+ {
321
+ response: Schema.optionalKey(Schema.NullOr(Schema.String)),
322
+ error: Schema.optionalKey(Schema.NullOr(GeminiHeadlessError))
323
+ }
324
+ ) {}
325
+
326
+ export type GeminiResponseExtraction =
327
+ | {
328
+ readonly ok: true
329
+ readonly value: string
330
+ }
331
+ | {
332
+ readonly ok: false
333
+ readonly error: string
334
+ }
335
+
336
+ const preamblePatterns: ReadonlyArray<RegExp> = [
337
+ /^Loaded cached .*/,
338
+ /^Loading extension: .*/,
339
+ /^Server '.+' supports tool updates.*/,
340
+ /^Attempt \d+ failed: .*/,
341
+ /^YOLO mode is enabled.*/
342
+ ]
343
+
344
+ export const isGeminiPreambleLine = (line: string): boolean =>
345
+ preamblePatterns.some((pattern) => pattern.test(line))
346
+
347
+ export const isKnownGeminiStderrNoise = (line: string): boolean => {
348
+ const normalized = line.trim()
349
+ return (
350
+ normalized.length === 0 ||
351
+ normalized.includes("256-color support not detected") ||
352
+ normalized.includes("color support detected") ||
353
+ normalized.includes("support not detected") ||
354
+ normalized.includes("tmux") ||
355
+ normalized.startsWith("set -g ") ||
356
+ normalized.startsWith("set -ga ") ||
357
+ normalized.startsWith("Ripgrep is not available") ||
358
+ normalized.startsWith("YOLO mode is enabled") ||
359
+ normalized.startsWith("Shell cwd was reset to") ||
360
+ normalized.startsWith("Loaded cached") ||
361
+ normalized.startsWith("Loading extension") ||
362
+ normalized.includes("[IDEClient]")
363
+ )
364
+ }
365
+
366
+ const decodeHeadless = (raw: string): GeminiResponseExtraction | undefined => {
367
+ const response = Option.getOrUndefined(
368
+ Schema.decodeUnknownOption(Schema.fromJsonString(GeminiHeadlessResponse))(raw)
369
+ )
370
+ if (response === undefined) {
371
+ return undefined
372
+ }
373
+ const message = response.error?.message?.trim()
374
+ if (message !== undefined && message.length > 0) {
375
+ const details = [
376
+ response.error?.type === undefined ? undefined : `type=${response.error.type}`,
377
+ response.error?.code === undefined ? undefined : `code=${response.error.code}`
378
+ ].filter((detail) => detail !== undefined)
379
+ return {
380
+ ok: false,
381
+ error:
382
+ details.length === 0
383
+ ? `Gemini CLI returned an error: ${message}`
384
+ : `Gemini CLI returned an error (${details.join(", ")}): ${message}`
385
+ }
386
+ }
387
+ const content = response.response?.trim()
388
+ return content === undefined || content.length === 0
389
+ ? {
390
+ ok: false,
391
+ error: "Gemini CLI JSON output did not contain a response"
392
+ }
393
+ : {
394
+ ok: true,
395
+ value: content
396
+ }
397
+ }
398
+
399
+ export const extractGeminiCliResponse = (output: string): GeminiResponseExtraction => {
400
+ const normalized = output.replaceAll("\r\n", "\n").trim()
401
+ if (normalized.length === 0) {
402
+ return {
403
+ ok: false,
404
+ error: "Gemini CLI returned empty output"
405
+ }
406
+ }
407
+
408
+ for (const candidate of jsonCandidates(normalized)) {
409
+ const decoded = decodeHeadless(candidate)
410
+ if (decoded !== undefined) {
411
+ return decoded
412
+ }
413
+ }
414
+
415
+ const content = normalized
416
+ .split("\n")
417
+ .filter((line, index, lines) => {
418
+ const previousAccepted = lines
419
+ .slice(0, index)
420
+ .some((prior) => !isGeminiPreambleLine(prior.trim()))
421
+ return previousAccepted || !isGeminiPreambleLine(line.trim())
422
+ })
423
+ .join("\n")
424
+ .trim()
425
+ return content.length === 0
426
+ ? {
427
+ ok: false,
428
+ error: "Failed to decode Gemini CLI JSON output: output did not contain a JSON envelope"
429
+ }
430
+ : {
431
+ ok: true,
432
+ value: content
433
+ }
434
+ }
435
+
436
+ export const parseGeminiCliStreamEvent = (line: string): GeminiCliStreamEvent => {
437
+ const trimmed = line.trim()
438
+ const json = parseJsonLine(trimmed)
439
+ if (json === undefined) {
440
+ return GeminiCliLogLine.make({ line })
441
+ }
442
+
443
+ const nestedError = jsonField(json, "error")
444
+ switch (jsonStringField(json, "type")) {
445
+ case "init":
446
+ return GeminiCliInit.make({
447
+ ...(jsonStringField(json, "model") === undefined
448
+ ? {}
449
+ : { model: jsonStringField(json, "model") ?? "" }),
450
+ ...(jsonStringField(json, "session_id") === undefined
451
+ ? {}
452
+ : { sessionId: jsonStringField(json, "session_id") ?? "" })
453
+ })
454
+ case "message":
455
+ return GeminiCliMessage.make({
456
+ ...(jsonStringField(json, "role") === undefined
457
+ ? {}
458
+ : { role: jsonStringField(json, "role") ?? "" }),
459
+ ...(jsonStringField(json, "content") === undefined
460
+ ? {}
461
+ : { content: jsonStringField(json, "content") ?? "" }),
462
+ delta: jsonField(json, "delta") === true
463
+ })
464
+ case "tool_use":
465
+ return GeminiCliToolUse.make({
466
+ ...(jsonStringField(json, "tool_name") === undefined
467
+ ? {}
468
+ : { toolName: jsonStringField(json, "tool_name") ?? "" }),
469
+ ...(jsonStringField(json, "tool_id") === undefined
470
+ ? {}
471
+ : { toolId: jsonStringField(json, "tool_id") ?? "" }),
472
+ ...(jsonField(json, "parameters") === undefined
473
+ ? {}
474
+ : { input: jsonText(jsonField(json, "parameters") ?? {}) })
475
+ })
476
+ case "tool_result":
477
+ return GeminiCliToolResult.make({
478
+ ...(jsonStringField(json, "tool_id") === undefined
479
+ ? {}
480
+ : { toolId: jsonStringField(json, "tool_id") ?? "" }),
481
+ ...(jsonStringField(json, "status") === undefined
482
+ ? {}
483
+ : { status: jsonStringField(json, "status") ?? "" }),
484
+ ...(jsonStringField(json, "output") === undefined
485
+ ? {}
486
+ : { content: jsonStringField(json, "output") ?? "" })
487
+ })
488
+ case "error": {
489
+ const message =
490
+ jsonStringField(json, "text")?.trim() ||
491
+ jsonStringField(json, "message")?.trim() ||
492
+ jsonStringField(nestedError, "message") ||
493
+ trimmed
494
+ return GeminiCliError.make({
495
+ message,
496
+ ...(jsonIntField(nestedError, "code") === undefined
497
+ ? {}
498
+ : { code: jsonIntField(nestedError, "code") ?? 0 }),
499
+ ...(jsonStringField(nestedError, "type") === undefined
500
+ ? {}
501
+ : { errorType: jsonStringField(nestedError, "type") ?? "" })
502
+ })
503
+ }
504
+ case "result": {
505
+ const stats = jsonField(json, "stats")
506
+ const decodedStats =
507
+ stats === undefined
508
+ ? undefined
509
+ : GeminiStreamStats.make({
510
+ ...(jsonIntField(stats, "total_tokens") === undefined
511
+ ? {}
512
+ : { totalTokens: jsonIntField(stats, "total_tokens") ?? 0 }),
513
+ ...(jsonIntField(stats, "input_tokens") === undefined
514
+ ? {}
515
+ : { inputTokens: jsonIntField(stats, "input_tokens") ?? 0 }),
516
+ ...(jsonIntField(stats, "output_tokens") === undefined
517
+ ? {}
518
+ : { outputTokens: jsonIntField(stats, "output_tokens") ?? 0 }),
519
+ ...(jsonIntField(stats, "cached") === undefined
520
+ ? {}
521
+ : { cached: jsonIntField(stats, "cached") ?? 0 })
522
+ })
523
+ return GeminiCliResult.make({
524
+ ...(jsonStringField(json, "status") === undefined
525
+ ? {}
526
+ : { status: jsonStringField(json, "status") ?? "" }),
527
+ ...(jsonStringField(nestedError, "message") === undefined
528
+ ? {}
529
+ : {
530
+ errorMessage: jsonStringField(nestedError, "message") ?? ""
531
+ }),
532
+ ...(decodedStats === undefined ? {} : { stats: decodedStats })
533
+ })
534
+ }
535
+ default:
536
+ return GeminiCliLogLine.make({ line })
537
+ }
538
+ }
539
+
540
+ const geminiUsage = (stats: GeminiStreamStats | undefined): TokenUsage | undefined =>
541
+ stats?.inputTokens === undefined ||
542
+ stats.outputTokens === undefined ||
543
+ stats.totalTokens === undefined
544
+ ? undefined
545
+ : TokenUsage.make({
546
+ prompt: stats.inputTokens,
547
+ completion: stats.outputTokens,
548
+ total: stats.totalTokens,
549
+ ...(stats.cached === undefined ? {} : { cached: stats.cached })
550
+ })
551
+
552
+ const formatGeminiHistory = (
553
+ messages: ReadonlyArray<Message>
554
+ ): Effect.Effect<string, InvalidRequestError> => {
555
+ const system = messages.filter((message) => message.role === "System")
556
+ const turns = messages.filter((message) => message.role !== "System")
557
+ if (turns.length === 0) {
558
+ return Effect.fail(
559
+ InvalidRequestError.make({
560
+ message: "History must contain at least one user or assistant message"
561
+ })
562
+ )
563
+ }
564
+ const systemBlock =
565
+ system.length === 0
566
+ ? ""
567
+ : `[SYSTEM CONTEXT]\n${system.map((message) => message.content).join("\n")}\n---\n\n`
568
+ const history = turns
569
+ .map((message) => {
570
+ const role =
571
+ message.role === "User"
572
+ ? "**User:**"
573
+ : message.role === "Assistant"
574
+ ? "**Assistant:**"
575
+ : message.role
576
+ return `${role} ${message.content}`
577
+ })
578
+ .join("\n\n")
579
+ return Effect.succeed(systemBlock + history)
580
+ }
581
+
582
+ export const makeGeminiCliProvider = (
583
+ config: LlmConfig,
584
+ executor: GeminiCliExecutorShape,
585
+ executionContext: GeminiCliExecutionContext = GeminiCliExecutionContext.make({})
586
+ ): CliConnectorShape => {
587
+ const healthy = HealthStatus.make({
588
+ availability: "Healthy",
589
+ authStatus: "Valid"
590
+ })
591
+ const unhealthy = HealthStatus.make({
592
+ availability: "Unhealthy",
593
+ authStatus: "Unknown"
594
+ })
595
+ const healthCheck = executor.checkGeminiInstalled.pipe(
596
+ Effect.as(healthy),
597
+ Effect.catch(() => Effect.succeed(unhealthy))
598
+ )
599
+
600
+ const executeStream = (prompt: string): Stream.Stream<LlmChunk, LlmError> =>
601
+ Stream.concat(
602
+ Stream.fromEffect(executor.checkGeminiInstalled).pipe(Stream.drain),
603
+ Stream.unwrap(
604
+ Effect.map(
605
+ Ref.make<Readonly<Record<string, string>>>({
606
+ provider: "gemini-cli",
607
+ model: config.model
608
+ }),
609
+ (metadata) =>
610
+ executor.runGeminiProcessStream(prompt, config, executionContext).pipe(
611
+ Stream.flatMap((event) => {
612
+ switch (event._tag) {
613
+ case "Init":
614
+ return Stream.fromEffect(
615
+ Ref.update(metadata, (current) => ({
616
+ ...current,
617
+ ...(event.model === undefined ? {} : { model: event.model }),
618
+ ...(event.sessionId === undefined
619
+ ? {}
620
+ : {
621
+ session_id: event.sessionId,
622
+ sessionId: event.sessionId
623
+ })
624
+ }))
625
+ ).pipe(Stream.drain)
626
+ case "Message":
627
+ return event.role?.toLowerCase() === "assistant" &&
628
+ event.content !== undefined &&
629
+ event.content.length > 0
630
+ ? Stream.fromEffect(
631
+ Effect.map(Ref.get(metadata), (current) =>
632
+ LlmChunk.make({
633
+ delta: event.content ?? "",
634
+ metadata: current
635
+ })
636
+ )
637
+ )
638
+ : Stream.empty
639
+ case "ToolUse":
640
+ return Stream.fromEffect(
641
+ Effect.map(Ref.get(metadata), (current) =>
642
+ LlmChunk.make({
643
+ delta: "",
644
+ metadata: {
645
+ ...current,
646
+ event: "tool_use",
647
+ tool_name: event.toolName ?? "",
648
+ tool_id: event.toolId ?? "",
649
+ tool_input: event.input ?? "",
650
+ toolName: event.toolName ?? "",
651
+ toolId: event.toolId ?? "",
652
+ toolInput: event.input ?? ""
653
+ }
654
+ })
655
+ )
656
+ )
657
+ case "ToolResult":
658
+ return Stream.fromEffect(
659
+ Effect.map(Ref.get(metadata), (current) =>
660
+ LlmChunk.make({
661
+ delta: "",
662
+ metadata: {
663
+ ...current,
664
+ event: "tool_result",
665
+ tool_id: event.toolId ?? "",
666
+ tool_status: event.status ?? "",
667
+ tool_content: event.content ?? "",
668
+ toolId: event.toolId ?? "",
669
+ toolStatus: event.status ?? "",
670
+ toolResult: event.content ?? ""
671
+ }
672
+ })
673
+ )
674
+ )
675
+ case "Error": {
676
+ const details = [
677
+ event.errorType === undefined ? undefined : `type=${event.errorType}`,
678
+ event.code === undefined ? undefined : `code=${event.code}`
679
+ ].filter((detail) => detail !== undefined)
680
+ const raw =
681
+ details.length === 0
682
+ ? `Gemini CLI stream error: ${event.message ?? "unknown error"}`
683
+ : `Gemini CLI stream error (${details.join(", ")}): ${event.message ?? "unknown error"}`
684
+ return Stream.fromEffect(
685
+ failClassifiedCliError("gemini", "Gemini CLI stream error", raw)
686
+ )
687
+ }
688
+ case "Result":
689
+ if (event.status === "error") {
690
+ return Stream.fromEffect(
691
+ failClassifiedCliError(
692
+ "gemini",
693
+ "Gemini CLI returned an error",
694
+ event.errorMessage ?? "Gemini CLI returned an error"
695
+ )
696
+ )
697
+ }
698
+ return Stream.fromEffect(
699
+ Effect.map(Ref.get(metadata), (current) => {
700
+ const usage = geminiUsage(event.stats)
701
+ return LlmChunk.make({
702
+ delta: "",
703
+ finishReason: "stop",
704
+ metadata: current,
705
+ ...(usage === undefined ? {} : { usage })
706
+ })
707
+ })
708
+ )
709
+ case "LogLine":
710
+ return Stream.empty
711
+ }
712
+ })
713
+ )
714
+ )
715
+ )
716
+ )
717
+
718
+ const complete = Effect.fn("@llm4ts/core/providers/GeminiCliProvider.complete")(function* (
719
+ prompt: string
720
+ ) {
721
+ const output = yield* executor.runGeminiProcess(prompt, config, executionContext)
722
+ const extracted = extractGeminiCliResponse(output)
723
+ return extracted.ok
724
+ ? extracted.value
725
+ : yield* ParseError.make({
726
+ message: extracted.error,
727
+ raw: output
728
+ })
729
+ })
730
+
731
+ const base = makeCliConnector({
732
+ id: ConnectorIds.GeminiCli,
733
+ interactionSupport: "InteractiveStdin",
734
+ buildArgv: (prompt, context) => [
735
+ "gemini",
736
+ "--yolo",
737
+ ...(context.repoPath.length === 0 ? [] : ["--include-directories", context.repoPath]),
738
+ "-p",
739
+ prompt
740
+ ],
741
+ buildInteractiveArgv: (context) => [
742
+ "gemini",
743
+ "--yolo",
744
+ ...(context.repoPath.length === 0 ? [] : ["--include-directories", context.repoPath])
745
+ ],
746
+ complete,
747
+ completeStream: executeStream,
748
+ healthCheck,
749
+ isAvailable: Effect.map(healthCheck, (status) => status.availability === "Healthy")
750
+ })
751
+
752
+ const executeStructuredWithUsage = <A, E, RD, RE>(
753
+ prompt: string,
754
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
755
+ jsonSchema: JsonSchema
756
+ ): Effect.Effect<StructuredResult<A>, LlmError, RD> =>
757
+ Effect.gen(function* () {
758
+ const hinted = withSchemaHint(prompt, jsonSchema)
759
+ const response = yield* collect(executeStream(hinted))
760
+ const value = yield* parseFromText(response.content, schema, jsonSchema).pipe(
761
+ Effect.mapError((error) => {
762
+ if (error._tag !== "ProviderError" || !error.message.includes("empty response")) {
763
+ return error
764
+ }
765
+ const model = response.metadata.model ?? config.model
766
+ const usage =
767
+ response.usage === undefined
768
+ ? "no token usage reported"
769
+ : `input_tokens=${response.usage.prompt}, output_tokens=${response.usage.completion}`
770
+ return ProviderError.make({
771
+ message: `${error.message} (model=${model}, prompt=${hinted.length} chars, ${usage})`,
772
+ ...(error.cause === undefined ? {} : { cause: error.cause })
773
+ })
774
+ })
775
+ )
776
+ const result: StructuredResult<A> = [value, response.usage, response.metadata.model]
777
+ return result
778
+ })
779
+
780
+ return {
781
+ ...base,
782
+ executeStream,
783
+ executeStreamWithHistory: (messages) =>
784
+ Stream.unwrap(Effect.map(formatGeminiHistory(messages), (history) => executeStream(history))),
785
+ executeStructured: <A, E, RD, RE>(
786
+ prompt: string,
787
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
788
+ jsonSchema: JsonSchema
789
+ ): Effect.Effect<A, LlmError, RD> =>
790
+ Effect.map(executeStructuredWithUsage(prompt, schema, jsonSchema), ([value]) => value),
791
+ executeStructuredWithUsage
792
+ }
793
+ }