@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,226 @@
1
+ import * as Deferred from "effect/Deferred"
2
+ import * as Effect from "effect/Effect"
3
+ import * as PubSub from "effect/PubSub"
4
+ import * as Queue from "effect/Queue"
5
+ import * as Ref from "effect/Ref"
6
+ import type * as Scope from "effect/Scope"
7
+ import * as Stream from "effect/Stream"
8
+ import {
9
+ SessionDone,
10
+ SessionResult,
11
+ SessionTextDelta,
12
+ SessionToolResult,
13
+ SessionToolUse,
14
+ SessionUsage,
15
+ type AgentSessionShape,
16
+ type SessionEvent
17
+ } from "../AgentSession.ts"
18
+ import { ProviderError, type LlmError } from "../Errors.ts"
19
+ import { TokenUsage } from "../Models.ts"
20
+ import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
21
+ import {
22
+ jsonArray,
23
+ jsonBooleanField,
24
+ jsonField,
25
+ jsonIntField,
26
+ jsonStringField,
27
+ jsonText,
28
+ parseJsonLine,
29
+ type JsonValue
30
+ } from "./CliSupport.ts"
31
+
32
+ export const claudeSessionArgv = (
33
+ model: string | undefined,
34
+ extraFlags: ReadonlyArray<string>
35
+ ): ReadonlyArray<string> => [
36
+ "claude",
37
+ "--print",
38
+ "--input-format",
39
+ "stream-json",
40
+ "--output-format",
41
+ "stream-json",
42
+ "--verbose",
43
+ ...(model === undefined ? [] : ["--model", model]),
44
+ ...extraFlags
45
+ ]
46
+
47
+ export const claudeUserMessageJson = (text: string): string =>
48
+ JSON.stringify({
49
+ type: "user",
50
+ message: {
51
+ role: "user",
52
+ content: text
53
+ }
54
+ })
55
+
56
+ const contentBlocks = (json: JsonValue): ReadonlyArray<JsonValue> =>
57
+ jsonArray(jsonField(jsonField(json, "message"), "content"))
58
+
59
+ const assistantEvents = (json: JsonValue): ReadonlyArray<SessionEvent> =>
60
+ contentBlocks(json).flatMap((block): ReadonlyArray<SessionEvent> => {
61
+ switch (jsonStringField(block, "type")) {
62
+ case "text": {
63
+ const text = jsonStringField(block, "text")
64
+ return text === undefined || text.length === 0 ? [] : [SessionTextDelta.make({ text })]
65
+ }
66
+ case "tool_use":
67
+ return [
68
+ SessionToolUse.make({
69
+ tool: jsonStringField(block, "name") ?? "",
70
+ rawInput: jsonText(jsonField(block, "input") ?? {}),
71
+ id: jsonStringField(block, "id") ?? ""
72
+ })
73
+ ]
74
+ default:
75
+ return []
76
+ }
77
+ })
78
+
79
+ const toolResultEvents = (json: JsonValue): ReadonlyArray<SessionEvent> =>
80
+ contentBlocks(json).flatMap((block): ReadonlyArray<SessionEvent> => {
81
+ if (jsonStringField(block, "type") !== "tool_result") {
82
+ return []
83
+ }
84
+ const content = jsonField(block, "content")
85
+ return [
86
+ SessionToolResult.make({
87
+ id: jsonStringField(block, "tool_use_id") ?? "",
88
+ status: jsonBooleanField(block, "is_error") === true ? "error" : "success",
89
+ content: content === undefined ? "" : jsonText(content)
90
+ })
91
+ ]
92
+ })
93
+
94
+ const resultEvents = (json: JsonValue): ReadonlyArray<SessionEvent> => {
95
+ const usage = jsonField(json, "usage")
96
+ const usageEvents: ReadonlyArray<SessionEvent> =
97
+ usage === undefined
98
+ ? []
99
+ : [
100
+ SessionUsage.make({
101
+ usage: TokenUsage.make({
102
+ prompt: jsonIntField(usage, "input_tokens") ?? 0,
103
+ completion: jsonIntField(usage, "output_tokens") ?? 0,
104
+ total:
105
+ (jsonIntField(usage, "input_tokens") ?? 0) +
106
+ (jsonIntField(usage, "output_tokens") ?? 0),
107
+ ...(jsonIntField(usage, "cache_read_input_tokens") === undefined
108
+ ? {}
109
+ : {
110
+ cached: jsonIntField(usage, "cache_read_input_tokens") ?? 0
111
+ })
112
+ })
113
+ })
114
+ ]
115
+
116
+ return [
117
+ ...usageEvents,
118
+ SessionDone.make({
119
+ result: jsonStringField(json, "result") ?? ""
120
+ })
121
+ ]
122
+ }
123
+
124
+ export const parseClaudeSessionLine = (line: string): ReadonlyArray<SessionEvent> => {
125
+ const json = parseJsonLine(line)
126
+ if (json === undefined) {
127
+ return []
128
+ }
129
+ switch (jsonStringField(json, "type")) {
130
+ case "assistant":
131
+ return assistantEvents(json)
132
+ case "user":
133
+ return toolResultEvents(json)
134
+ case "result":
135
+ return resultEvents(json)
136
+ default:
137
+ return []
138
+ }
139
+ }
140
+
141
+ export const claudeSessionInitModel = (line: string): string | undefined => {
142
+ const json = parseJsonLine(line)
143
+ return json !== undefined && jsonStringField(json, "type") === "system"
144
+ ? jsonStringField(json, "model")
145
+ : undefined
146
+ }
147
+
148
+ export const openClaudeAgentSession = Effect.fn("@llm4ts/core/providers/ClaudeAgentSession.open")(
149
+ function* (
150
+ executor: ProcessExecutorShape,
151
+ argv: ReadonlyArray<string>,
152
+ cwd: string,
153
+ envVars: Readonly<Record<string, string>> = {}
154
+ ): Effect.fn.Return<AgentSessionShape, LlmError, Scope.Scope> {
155
+ const [stdin, stdout] = yield* executor.runBidirectional(argv, cwd, envVars)
156
+ const events = yield* PubSub.unbounded<SessionEvent>()
157
+ const done = yield* Deferred.make<SessionResult, LlmError>()
158
+ const model = yield* Ref.make<string | undefined>(undefined)
159
+ const text = yield* Ref.make("")
160
+ const usage = yield* Ref.make<TokenUsage | undefined>(undefined)
161
+
162
+ const trackEvent = Effect.fn("@llm4ts/core/providers/ClaudeAgentSession.trackEvent")(function* (
163
+ event: SessionEvent
164
+ ) {
165
+ switch (event._tag) {
166
+ case "TextDelta":
167
+ yield* Ref.update(text, (current) => current + event.text)
168
+ break
169
+ case "Usage":
170
+ yield* Ref.set(usage, event.usage)
171
+ break
172
+ case "Done": {
173
+ const accumulated = yield* Ref.get(text)
174
+ const finalUsage = yield* Ref.get(usage)
175
+ const finalModel = yield* Ref.get(model)
176
+ yield* Deferred.succeed(
177
+ done,
178
+ SessionResult.make({
179
+ text: event.result.length === 0 ? accumulated : event.result,
180
+ ...(finalUsage === undefined ? {} : { usage: finalUsage }),
181
+ ...(finalModel === undefined ? {} : { model: finalModel })
182
+ })
183
+ )
184
+ break
185
+ }
186
+ default:
187
+ break
188
+ }
189
+ yield* PubSub.publish(events, event)
190
+ })
191
+
192
+ yield* stdout.pipe(
193
+ Stream.mapEffect((line) => {
194
+ const initializedModel = claudeSessionInitModel(line)
195
+ return initializedModel === undefined
196
+ ? Effect.succeed(line)
197
+ : Ref.set(model, initializedModel).pipe(Effect.as(line))
198
+ }),
199
+ Stream.flatMap((line) => Stream.fromIterable(parseClaudeSessionLine(line))),
200
+ Stream.mapEffect(trackEvent),
201
+ Stream.runDrain,
202
+ Effect.catch((error) => Deferred.fail(done, error).pipe(Effect.asVoid)),
203
+ Effect.ensuring(
204
+ Deferred.fail(
205
+ done,
206
+ ProviderError.make({
207
+ message: "claude session ended before a result"
208
+ })
209
+ ).pipe(Effect.asVoid)
210
+ ),
211
+ Effect.forkScoped
212
+ )
213
+
214
+ const sendUserMessage = (message: string): Effect.Effect<void, LlmError> =>
215
+ Queue.offer(stdin, claudeUserMessageJson(message)).pipe(Effect.asVoid)
216
+
217
+ return {
218
+ events: Stream.fromPubSub(events),
219
+ sendUserMessage,
220
+ respondToAsk: sendUserMessage,
221
+ respondToApproval: (_id, _approved) => Effect.void,
222
+ awaitResult: Deferred.await(done),
223
+ cancel: Queue.shutdown(stdin)
224
+ }
225
+ }
226
+ )
@@ -0,0 +1,201 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Ref from "effect/Ref"
3
+ import type * as Schema from "effect/Schema"
4
+ import * as Stream from "effect/Stream"
5
+ import { makeCliConnector, type CliConnectorShape } from "../Connector.ts"
6
+ import type { CliConnectorConfig } from "../ConnectorConfig.ts"
7
+ import type { LlmError } from "../Errors.ts"
8
+ import type { StructuredResult } from "../LlmService.ts"
9
+ import {
10
+ ConnectorCapabilities,
11
+ ConnectorIds,
12
+ LlmChunk,
13
+ TokenUsage,
14
+ type JsonSchema
15
+ } from "../Models.ts"
16
+ import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
17
+ import { collect } from "../Streaming.ts"
18
+ import { parseFromText, withSchemaHint } from "../StructuredOutput.ts"
19
+ import {
20
+ failClassifiedCliError,
21
+ jsonArray,
22
+ jsonField,
23
+ jsonIntField,
24
+ jsonStringField,
25
+ optionalModelArgs,
26
+ parseJsonLine,
27
+ sortedFlagArgs,
28
+ toolEventChunk,
29
+ usageEventChunk
30
+ } from "./CliSupport.ts"
31
+
32
+ export const claudeCliExtraArgs = (config: CliConnectorConfig): ReadonlyArray<string> => {
33
+ const effectiveFlags = config.readOnly
34
+ ? {
35
+ ...config.flags,
36
+ "permission-mode": "default",
37
+ "disallowed-tools": "Write,Edit,NotebookEdit"
38
+ }
39
+ : config.flags
40
+ return [...optionalModelArgs(config.model), ...sortedFlagArgs(effectiveFlags)]
41
+ }
42
+
43
+ export const claudeCliInitModel = (line: string): string | undefined => {
44
+ const json = parseJsonLine(line)
45
+ return json !== undefined && jsonStringField(json, "type") === "system"
46
+ ? jsonStringField(json, "model")
47
+ : undefined
48
+ }
49
+
50
+ export const parseClaudeCliStreamLine = (line: string): ReadonlyArray<LlmChunk> => {
51
+ const json = parseJsonLine(line)
52
+ if (json === undefined) {
53
+ return []
54
+ }
55
+
56
+ switch (jsonStringField(json, "type")) {
57
+ case "assistant":
58
+ return jsonArray(jsonField(jsonField(json, "message"), "content")).flatMap(
59
+ (block): ReadonlyArray<LlmChunk> => {
60
+ switch (jsonStringField(block, "type")) {
61
+ case "text": {
62
+ const text = jsonStringField(block, "text")
63
+ return text === undefined || text.length === 0 ? [] : [LlmChunk.make({ delta: text })]
64
+ }
65
+ case "tool_use":
66
+ return [
67
+ toolEventChunk(jsonStringField(block, "name") ?? "", jsonField(block, "input"))
68
+ ]
69
+ default:
70
+ return []
71
+ }
72
+ }
73
+ )
74
+ case "result": {
75
+ const usage = jsonField(json, "usage")
76
+ if (usage === undefined) {
77
+ return []
78
+ }
79
+ const prompt = jsonIntField(usage, "input_tokens") ?? 0
80
+ const completion = jsonIntField(usage, "output_tokens") ?? 0
81
+ const cached = jsonIntField(usage, "cache_read_input_tokens")
82
+ return [
83
+ usageEventChunk(
84
+ undefined,
85
+ TokenUsage.make({
86
+ prompt,
87
+ completion,
88
+ total: prompt + completion,
89
+ ...(cached === undefined ? {} : { cached })
90
+ })
91
+ )
92
+ ]
93
+ }
94
+ default:
95
+ return []
96
+ }
97
+ }
98
+
99
+ export const makeClaudeCliConnector = (
100
+ config: CliConnectorConfig,
101
+ executor: ProcessExecutorShape
102
+ ): CliConnectorShape => {
103
+ const cwd = config.workingDir ?? "."
104
+ const extraArgs = claudeCliExtraArgs(config)
105
+
106
+ const complete = Effect.fn("@llm4ts/core/providers/ClaudeCliConnector.complete")(function* (
107
+ prompt: string
108
+ ) {
109
+ const result = yield* executor.runWithStdin(
110
+ ["claude", "--print", ...extraArgs],
111
+ cwd,
112
+ config.envVars,
113
+ prompt
114
+ )
115
+ if (result.exitCode !== 0) {
116
+ const raw = result.stdout.join("\n")
117
+ return yield* failClassifiedCliError(
118
+ "claude",
119
+ `claude exited with code ${result.exitCode}`,
120
+ raw
121
+ )
122
+ }
123
+ return result.stdout.join("\n")
124
+ })
125
+
126
+ const completeStream = (prompt: string) =>
127
+ Stream.unwrap(
128
+ Effect.map(Ref.make<string | undefined>(undefined), (model) =>
129
+ executor
130
+ .runStreamingWithStdin(
131
+ ["claude", "--print", "--output-format", "stream-json", "--verbose", ...extraArgs],
132
+ cwd,
133
+ config.envVars,
134
+ prompt
135
+ )
136
+ .pipe(
137
+ Stream.mapEffect((line) => {
138
+ const initializedModel = claudeCliInitModel(line)
139
+ return initializedModel === undefined
140
+ ? Effect.succeed(line)
141
+ : Ref.set(model, initializedModel).pipe(Effect.as(line))
142
+ }),
143
+ Stream.flatMap((line) => Stream.fromIterable(parseClaudeCliStreamLine(line))),
144
+ Stream.mapEffect((chunk) =>
145
+ chunk.usage === undefined
146
+ ? Effect.succeed(chunk)
147
+ : Effect.map(Ref.get(model), (servedModel) =>
148
+ servedModel === undefined
149
+ ? chunk
150
+ : LlmChunk.make({
151
+ ...chunk,
152
+ metadata: {
153
+ ...chunk.metadata,
154
+ model: servedModel
155
+ }
156
+ })
157
+ )
158
+ )
159
+ )
160
+ )
161
+ )
162
+
163
+ const base = makeCliConnector({
164
+ id: ConnectorIds.ClaudeCli,
165
+ interactionSupport: "InteractiveStdin",
166
+ capabilities: ConnectorCapabilities.make({
167
+ interactiveSessions: true,
168
+ askUser: true,
169
+ approval: true,
170
+ resumableSessions: true
171
+ }),
172
+ buildArgv: (prompt, _context) => ["claude", "--print", ...extraArgs, prompt],
173
+ buildInteractiveArgv: (_context) => ["claude", ...extraArgs],
174
+ complete,
175
+ completeStream,
176
+ versionProbe: { executor, binary: "claude", cwd }
177
+ })
178
+
179
+ const executeStructuredWithUsage = <A, E, RD, RE>(
180
+ prompt: string,
181
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
182
+ jsonSchema: JsonSchema
183
+ ): Effect.Effect<StructuredResult<A>, LlmError, RD> =>
184
+ Effect.gen(function* () {
185
+ const reply = yield* collect(completeStream(withSchemaHint(prompt, jsonSchema)))
186
+ const value = yield* parseFromText(reply.content, schema, jsonSchema)
187
+ const result: StructuredResult<A> = [value, reply.usage, reply.metadata.model]
188
+ return result
189
+ })
190
+
191
+ return {
192
+ ...base,
193
+ executeStructured: <A, E, RD, RE>(
194
+ prompt: string,
195
+ schema: Schema.ConstraintCodec<A, E, RD, RE>,
196
+ jsonSchema: JsonSchema
197
+ ): Effect.Effect<A, LlmError, RD> =>
198
+ Effect.map(executeStructuredWithUsage(prompt, schema, jsonSchema), ([value]) => value),
199
+ executeStructuredWithUsage
200
+ }
201
+ }
@@ -0,0 +1,100 @@
1
+ import * as DateTime from "effect/DateTime"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Option from "effect/Option"
4
+ import * as Schema from "effect/Schema"
5
+ import { ProviderError, type LlmError } from "../Errors.ts"
6
+ import { LlmChunk, type TokenUsage } from "../Models.ts"
7
+ import { classifyUsageLimit } from "../UsageLimits.ts"
8
+
9
+ export type JsonValue = typeof Schema.Json.Type
10
+ export type JsonRecord = Readonly<Record<string, JsonValue>>
11
+
12
+ export const isJsonRecord = (value: JsonValue): value is JsonRecord =>
13
+ value !== null && typeof value === "object" && !Array.isArray(value)
14
+
15
+ export const parseJsonLine = (line: string): JsonValue | undefined => {
16
+ const trimmed = line.trim()
17
+ if (trimmed.length === 0 || !trimmed.startsWith("{")) {
18
+ return undefined
19
+ }
20
+ return Option.getOrUndefined(
21
+ Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Json))(trimmed)
22
+ )
23
+ }
24
+
25
+ export const jsonField = (value: JsonValue | undefined, name: string): JsonValue | undefined =>
26
+ value !== undefined && isJsonRecord(value) ? value[name] : undefined
27
+
28
+ export const jsonStringField = (value: JsonValue | undefined, name: string): string | undefined => {
29
+ const field = jsonField(value, name)
30
+ return typeof field === "string" ? field : undefined
31
+ }
32
+
33
+ export const jsonIntField = (value: JsonValue | undefined, name: string): number | undefined => {
34
+ const field = jsonField(value, name)
35
+ return typeof field === "number" && Number.isFinite(field) ? Math.trunc(field) : undefined
36
+ }
37
+
38
+ export const jsonBooleanField = (
39
+ value: JsonValue | undefined,
40
+ name: string
41
+ ): boolean | undefined => {
42
+ const field = jsonField(value, name)
43
+ return typeof field === "boolean" ? field : undefined
44
+ }
45
+
46
+ export const jsonArray = (value: JsonValue | undefined): ReadonlyArray<JsonValue> =>
47
+ Array.isArray(value) ? value : []
48
+
49
+ export const jsonObjectEntries = (
50
+ value: JsonValue | undefined
51
+ ): ReadonlyArray<readonly [string, JsonValue]> =>
52
+ value !== undefined && isJsonRecord(value) ? Object.entries(value) : []
53
+
54
+ export const jsonText = (value: JsonValue): string => JSON.stringify(value) ?? "{}"
55
+
56
+ export const sortedFlagArgs = (flags: Readonly<Record<string, string>>): ReadonlyArray<string> =>
57
+ Object.entries(flags)
58
+ .sort(([left], [right]) => left.localeCompare(right))
59
+ .flatMap(([key, value]) => (value.length === 0 ? [`--${key}`] : [`--${key}`, value]))
60
+
61
+ export const optionalModelArgs = (model: string | undefined): ReadonlyArray<string> =>
62
+ model === undefined ? [] : ["--model", model]
63
+
64
+ export const toolEventChunk = (name: string, input: JsonValue | undefined): LlmChunk =>
65
+ LlmChunk.make({
66
+ delta: "",
67
+ metadata: {
68
+ event: "tool_use",
69
+ tool_name: name,
70
+ tool_input: input === undefined ? "{}" : jsonText(input)
71
+ }
72
+ })
73
+
74
+ export const usageEventChunk = (model: string | undefined, usage: TokenUsage): LlmChunk =>
75
+ LlmChunk.make({
76
+ delta: "",
77
+ finishReason: "stop",
78
+ usage,
79
+ metadata:
80
+ model === undefined
81
+ ? {}
82
+ : {
83
+ model
84
+ }
85
+ })
86
+
87
+ export const failClassifiedCliError = Effect.fn(
88
+ "@llm4ts/core/providers/CliSupport.failClassifiedCliError"
89
+ )(function* (
90
+ provider: string,
91
+ fallbackPrefix: string,
92
+ raw: string
93
+ ): Effect.fn.Return<never, LlmError> {
94
+ const now = yield* DateTime.now
95
+ const timeZone = yield* Effect.sync(() => Intl.DateTimeFormat().resolvedOptions().timeZone)
96
+ return yield* classifyUsageLimit(provider, raw, now, timeZone) ??
97
+ ProviderError.make({
98
+ message: `${fallbackPrefix}: ${raw}`
99
+ })
100
+ })