cray-code 1.0.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 (252) hide show
  1. package/README.md +316 -0
  2. package/dist/Tool.d.ts +217 -0
  3. package/dist/Tool.d.ts.map +1 -0
  4. package/dist/Tool.js +89 -0
  5. package/dist/Tool.js.map +1 -0
  6. package/dist/branding/logo.d.ts +8 -0
  7. package/dist/branding/logo.d.ts.map +1 -0
  8. package/dist/branding/logo.js +26 -0
  9. package/dist/branding/logo.js.map +1 -0
  10. package/dist/branding/theme.d.ts +27 -0
  11. package/dist/branding/theme.d.ts.map +1 -0
  12. package/dist/branding/theme.js +28 -0
  13. package/dist/branding/theme.js.map +1 -0
  14. package/dist/commands/registry.d.ts +32 -0
  15. package/dist/commands/registry.d.ts.map +1 -0
  16. package/dist/commands/registry.js +759 -0
  17. package/dist/commands/registry.js.map +1 -0
  18. package/dist/components/MessageView.d.ts +12 -0
  19. package/dist/components/MessageView.d.ts.map +1 -0
  20. package/dist/components/MessageView.js +35 -0
  21. package/dist/components/MessageView.js.map +1 -0
  22. package/dist/components/PermissionPrompt.d.ts +11 -0
  23. package/dist/components/PermissionPrompt.d.ts.map +1 -0
  24. package/dist/components/PermissionPrompt.js +6 -0
  25. package/dist/components/PermissionPrompt.js.map +1 -0
  26. package/dist/components/PluginManager.d.ts +27 -0
  27. package/dist/components/PluginManager.d.ts.map +1 -0
  28. package/dist/components/PluginManager.js +391 -0
  29. package/dist/components/PluginManager.js.map +1 -0
  30. package/dist/components/ThinkingBlock.d.ts +27 -0
  31. package/dist/components/ThinkingBlock.d.ts.map +1 -0
  32. package/dist/components/ThinkingBlock.js +29 -0
  33. package/dist/components/ThinkingBlock.js.map +1 -0
  34. package/dist/components/ToolCallBlock.d.ts +14 -0
  35. package/dist/components/ToolCallBlock.d.ts.map +1 -0
  36. package/dist/components/ToolCallBlock.js +83 -0
  37. package/dist/components/ToolCallBlock.js.map +1 -0
  38. package/dist/components/TrustDialog.d.ts +20 -0
  39. package/dist/components/TrustDialog.d.ts.map +1 -0
  40. package/dist/components/TrustDialog.js +80 -0
  41. package/dist/components/TrustDialog.js.map +1 -0
  42. package/dist/context.d.ts +25 -0
  43. package/dist/context.d.ts.map +1 -0
  44. package/dist/context.js +268 -0
  45. package/dist/context.js.map +1 -0
  46. package/dist/cray.d.ts +114 -0
  47. package/dist/cray.d.ts.map +1 -0
  48. package/dist/cray.js +338 -0
  49. package/dist/cray.js.map +1 -0
  50. package/dist/index.d.ts +16 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +122 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/plugins/registry.d.ts +106 -0
  55. package/dist/plugins/registry.d.ts.map +1 -0
  56. package/dist/plugins/registry.js +695 -0
  57. package/dist/plugins/registry.js.map +1 -0
  58. package/dist/query.d.ts +31 -0
  59. package/dist/query.d.ts.map +1 -0
  60. package/dist/query.js +637 -0
  61. package/dist/query.js.map +1 -0
  62. package/dist/queryStream.d.ts +36 -0
  63. package/dist/queryStream.d.ts.map +1 -0
  64. package/dist/queryStream.js +704 -0
  65. package/dist/queryStream.js.map +1 -0
  66. package/dist/screens/ReplScreen.d.ts +22 -0
  67. package/dist/screens/ReplScreen.d.ts.map +1 -0
  68. package/dist/screens/ReplScreen.js +763 -0
  69. package/dist/screens/ReplScreen.js.map +1 -0
  70. package/dist/services/agentRunner.d.ts +39 -0
  71. package/dist/services/agentRunner.d.ts.map +1 -0
  72. package/dist/services/agentRunner.js +115 -0
  73. package/dist/services/agentRunner.js.map +1 -0
  74. package/dist/services/compact.d.ts +34 -0
  75. package/dist/services/compact.d.ts.map +1 -0
  76. package/dist/services/compact.js +179 -0
  77. package/dist/services/compact.js.map +1 -0
  78. package/dist/services/loadPluginCommands.d.ts +55 -0
  79. package/dist/services/loadPluginCommands.d.ts.map +1 -0
  80. package/dist/services/loadPluginCommands.js +219 -0
  81. package/dist/services/loadPluginCommands.js.map +1 -0
  82. package/dist/services/mcp/index.d.ts +3 -0
  83. package/dist/services/mcp/index.d.ts.map +1 -0
  84. package/dist/services/mcp/index.js +3 -0
  85. package/dist/services/mcp/index.js.map +1 -0
  86. package/dist/services/mcp/manager.d.ts +24 -0
  87. package/dist/services/mcp/manager.d.ts.map +1 -0
  88. package/dist/services/mcp/manager.js +138 -0
  89. package/dist/services/mcp/manager.js.map +1 -0
  90. package/dist/services/mcp/types.d.ts +52 -0
  91. package/dist/services/mcp/types.d.ts.map +1 -0
  92. package/dist/services/mcp/types.js +5 -0
  93. package/dist/services/mcp/types.js.map +1 -0
  94. package/dist/services/memory.d.ts +38 -0
  95. package/dist/services/memory.d.ts.map +1 -0
  96. package/dist/services/memory.js +181 -0
  97. package/dist/services/memory.js.map +1 -0
  98. package/dist/services/permissionPrompt.d.ts +38 -0
  99. package/dist/services/permissionPrompt.d.ts.map +1 -0
  100. package/dist/services/permissionPrompt.js +83 -0
  101. package/dist/services/permissionPrompt.js.map +1 -0
  102. package/dist/services/permissions.d.ts +15 -0
  103. package/dist/services/permissions.d.ts.map +1 -0
  104. package/dist/services/permissions.js +237 -0
  105. package/dist/services/permissions.js.map +1 -0
  106. package/dist/services/sessionStorage.d.ts +51 -0
  107. package/dist/services/sessionStorage.d.ts.map +1 -0
  108. package/dist/services/sessionStorage.js +266 -0
  109. package/dist/services/sessionStorage.js.map +1 -0
  110. package/dist/setup.d.ts +22 -0
  111. package/dist/setup.d.ts.map +1 -0
  112. package/dist/setup.js +160 -0
  113. package/dist/setup.js.map +1 -0
  114. package/dist/skills/bundledSkills.d.ts +18 -0
  115. package/dist/skills/bundledSkills.d.ts.map +1 -0
  116. package/dist/skills/bundledSkills.js +277 -0
  117. package/dist/skills/bundledSkills.js.map +1 -0
  118. package/dist/skills/index.d.ts +4 -0
  119. package/dist/skills/index.d.ts.map +1 -0
  120. package/dist/skills/index.js +3 -0
  121. package/dist/skills/index.js.map +1 -0
  122. package/dist/skills/loadSkillsDir.d.ts +45 -0
  123. package/dist/skills/loadSkillsDir.d.ts.map +1 -0
  124. package/dist/skills/loadSkillsDir.js +233 -0
  125. package/dist/skills/loadSkillsDir.js.map +1 -0
  126. package/dist/state/AppState.d.ts +70 -0
  127. package/dist/state/AppState.d.ts.map +1 -0
  128. package/dist/state/AppState.js +106 -0
  129. package/dist/state/AppState.js.map +1 -0
  130. package/dist/tools/AgentTool.d.ts +62 -0
  131. package/dist/tools/AgentTool.d.ts.map +1 -0
  132. package/dist/tools/AgentTool.js +133 -0
  133. package/dist/tools/AgentTool.js.map +1 -0
  134. package/dist/tools/AskUserQuestionTool.d.ts +60 -0
  135. package/dist/tools/AskUserQuestionTool.d.ts.map +1 -0
  136. package/dist/tools/AskUserQuestionTool.js +52 -0
  137. package/dist/tools/AskUserQuestionTool.js.map +1 -0
  138. package/dist/tools/BashTool.d.ts +33 -0
  139. package/dist/tools/BashTool.d.ts.map +1 -0
  140. package/dist/tools/BashTool.js +211 -0
  141. package/dist/tools/BashTool.js.map +1 -0
  142. package/dist/tools/EditTool.d.ts +24 -0
  143. package/dist/tools/EditTool.d.ts.map +1 -0
  144. package/dist/tools/EditTool.js +102 -0
  145. package/dist/tools/EditTool.js.map +1 -0
  146. package/dist/tools/GlobTool.d.ts +17 -0
  147. package/dist/tools/GlobTool.d.ts.map +1 -0
  148. package/dist/tools/GlobTool.js +65 -0
  149. package/dist/tools/GlobTool.js.map +1 -0
  150. package/dist/tools/GrepTool.d.ts +30 -0
  151. package/dist/tools/GrepTool.d.ts.map +1 -0
  152. package/dist/tools/GrepTool.js +140 -0
  153. package/dist/tools/GrepTool.js.map +1 -0
  154. package/dist/tools/MCPTool.d.ts +24 -0
  155. package/dist/tools/MCPTool.d.ts.map +1 -0
  156. package/dist/tools/MCPTool.js +67 -0
  157. package/dist/tools/MCPTool.js.map +1 -0
  158. package/dist/tools/NotebookEditTool.d.ts +28 -0
  159. package/dist/tools/NotebookEditTool.d.ts.map +1 -0
  160. package/dist/tools/NotebookEditTool.js +213 -0
  161. package/dist/tools/NotebookEditTool.js.map +1 -0
  162. package/dist/tools/NotebookReadTool.d.ts +19 -0
  163. package/dist/tools/NotebookReadTool.d.ts.map +1 -0
  164. package/dist/tools/NotebookReadTool.js +191 -0
  165. package/dist/tools/NotebookReadTool.js.map +1 -0
  166. package/dist/tools/PlanTools.d.ts +17 -0
  167. package/dist/tools/PlanTools.d.ts.map +1 -0
  168. package/dist/tools/PlanTools.js +65 -0
  169. package/dist/tools/PlanTools.js.map +1 -0
  170. package/dist/tools/ReadTool.d.ts +21 -0
  171. package/dist/tools/ReadTool.d.ts.map +1 -0
  172. package/dist/tools/ReadTool.js +202 -0
  173. package/dist/tools/ReadTool.js.map +1 -0
  174. package/dist/tools/SkillTool.d.ts +32 -0
  175. package/dist/tools/SkillTool.d.ts.map +1 -0
  176. package/dist/tools/SkillTool.js +217 -0
  177. package/dist/tools/SkillTool.js.map +1 -0
  178. package/dist/tools/TodoWriteTool.d.ts +35 -0
  179. package/dist/tools/TodoWriteTool.d.ts.map +1 -0
  180. package/dist/tools/TodoWriteTool.js +58 -0
  181. package/dist/tools/TodoWriteTool.js.map +1 -0
  182. package/dist/tools/WebFetchTool.d.ts +17 -0
  183. package/dist/tools/WebFetchTool.d.ts.map +1 -0
  184. package/dist/tools/WebFetchTool.js +97 -0
  185. package/dist/tools/WebFetchTool.js.map +1 -0
  186. package/dist/tools/WebSearchTool.d.ts +18 -0
  187. package/dist/tools/WebSearchTool.d.ts.map +1 -0
  188. package/dist/tools/WebSearchTool.js +76 -0
  189. package/dist/tools/WebSearchTool.js.map +1 -0
  190. package/dist/tools/WriteTool.d.ts +17 -0
  191. package/dist/tools/WriteTool.d.ts.map +1 -0
  192. package/dist/tools/WriteTool.js +84 -0
  193. package/dist/tools/WriteTool.js.map +1 -0
  194. package/dist/tools/index.d.ts +21 -0
  195. package/dist/tools/index.d.ts.map +1 -0
  196. package/dist/tools/index.js +20 -0
  197. package/dist/tools/index.js.map +1 -0
  198. package/dist/tools.d.ts +34 -0
  199. package/dist/tools.d.ts.map +1 -0
  200. package/dist/tools.js +102 -0
  201. package/dist/tools.js.map +1 -0
  202. package/dist/types/events.d.ts +85 -0
  203. package/dist/types/events.d.ts.map +1 -0
  204. package/dist/types/events.js +12 -0
  205. package/dist/types/events.js.map +1 -0
  206. package/dist/types/index.d.ts +4 -0
  207. package/dist/types/index.d.ts.map +1 -0
  208. package/dist/types/index.js +4 -0
  209. package/dist/types/index.js.map +1 -0
  210. package/dist/types/message.d.ts +71 -0
  211. package/dist/types/message.d.ts.map +1 -0
  212. package/dist/types/message.js +5 -0
  213. package/dist/types/message.js.map +1 -0
  214. package/dist/types/permission.d.ts +56 -0
  215. package/dist/types/permission.d.ts.map +1 -0
  216. package/dist/types/permission.js +46 -0
  217. package/dist/types/permission.js.map +1 -0
  218. package/dist/types/processUserInput.d.ts +18 -0
  219. package/dist/types/processUserInput.d.ts.map +1 -0
  220. package/dist/types/processUserInput.js +8 -0
  221. package/dist/types/processUserInput.js.map +1 -0
  222. package/dist/types/tool.d.ts +32 -0
  223. package/dist/types/tool.d.ts.map +1 -0
  224. package/dist/types/tool.js +5 -0
  225. package/dist/types/tool.js.map +1 -0
  226. package/dist/utils/compactBoundary.d.ts +11 -0
  227. package/dist/utils/compactBoundary.d.ts.map +1 -0
  228. package/dist/utils/compactBoundary.js +26 -0
  229. package/dist/utils/compactBoundary.js.map +1 -0
  230. package/dist/utils/configStore.d.ts +41 -0
  231. package/dist/utils/configStore.d.ts.map +1 -0
  232. package/dist/utils/configStore.js +111 -0
  233. package/dist/utils/configStore.js.map +1 -0
  234. package/dist/utils/forkedAgent.d.ts +40 -0
  235. package/dist/utils/forkedAgent.d.ts.map +1 -0
  236. package/dist/utils/forkedAgent.js +231 -0
  237. package/dist/utils/forkedAgent.js.map +1 -0
  238. package/dist/utils/messages.d.ts +14 -0
  239. package/dist/utils/messages.d.ts.map +1 -0
  240. package/dist/utils/messages.js +29 -0
  241. package/dist/utils/messages.js.map +1 -0
  242. package/dist/utils/sandbox.d.ts +22 -0
  243. package/dist/utils/sandbox.d.ts.map +1 -0
  244. package/dist/utils/sandbox.js +59 -0
  245. package/dist/utils/sandbox.js.map +1 -0
  246. package/dist/utils/sideQuestion.d.ts +29 -0
  247. package/dist/utils/sideQuestion.d.ts.map +1 -0
  248. package/dist/utils/sideQuestion.js +81 -0
  249. package/dist/utils/sideQuestion.js.map +1 -0
  250. package/install.ps1 +86 -0
  251. package/install.sh +92 -0
  252. package/package.json +68 -0
@@ -0,0 +1,704 @@
1
+ /**
2
+ * queryStream.ts — Streaming query engine (async generator pattern).
3
+ *
4
+ * Mirrors Claude Code's React pattern: uses `async function*` to yield
5
+ * QueryEvents that the React/Ink REPL consumes via for-await.
6
+ *
7
+ * Also accepts StreamCallbacks for real-time text/thinking streaming
8
+ * that the UI renders character-by-character.
9
+ */
10
+ import { findTool } from './Tool.js';
11
+ import { buildToolPool } from './tools.js';
12
+ import { hasPermissionsToUseTool } from './services/permissions.js';
13
+ /**
14
+ * Streaming query async generator — yields events while also
15
+ * providing real-time text/thinking through callbacks.
16
+ *
17
+ * Usage:
18
+ * for await (const event of queryStream(input, callbacks)) {
19
+ * // update React state per event type
20
+ * }
21
+ */
22
+ export async function* queryStream(input, cb) {
23
+ const { prompt, context, systemPrompt, allowedTools, isSubAgent = false, maxTurns = 25 } = input;
24
+ const messages = [];
25
+ const { modelConfig } = context;
26
+ const provider = modelConfig.provider;
27
+ const model = input.model ?? modelConfig.model;
28
+ const apiKey = modelConfig.apiKey;
29
+ if (!apiKey && provider !== 'ollama') {
30
+ yield { type: 'error', message: 'No API key configured. Run `cray --setup` first.' };
31
+ yield { type: 'done', messages: [], totalTurns: 0 };
32
+ return;
33
+ }
34
+ const fullSystemPrompt = buildSP(context, systemPrompt, isSubAgent);
35
+ let toolPool = isSubAgent && allowedTools
36
+ ? buildToolPool(context.permissionContext).filter((t) => allowedTools.includes(t.name))
37
+ : buildToolPool(context.permissionContext);
38
+ let providerTools = convTools(toolPool);
39
+ // Build conversation from full message history (context persistence)
40
+ const conversation = historyToConversation(context.messages || [], prompt);
41
+ let totalIn = 0, totalOut = 0, tc = 0, allowAll = false;
42
+ while (tc < maxTurns) {
43
+ tc++;
44
+ if (context.abortSignal.aborted) {
45
+ yield { type: 'cancelled', message: 'Cancelled by user.' };
46
+ break;
47
+ }
48
+ context.logDebug(`[Turn ${tc}] ${provider}/${model}`);
49
+ yield { type: 'request_start', turn: tc, model };
50
+ let resp;
51
+ try {
52
+ resp = await callLLMStreaming({
53
+ provider,
54
+ model,
55
+ apiKey,
56
+ baseUrl: modelConfig.baseUrl,
57
+ systemPrompt: fullSystemPrompt,
58
+ messages: conversation,
59
+ tools: providerTools,
60
+ maxTokens: modelConfig.maxTokens,
61
+ abortSignal: context.abortSignal,
62
+ cb,
63
+ turnNumber: tc,
64
+ });
65
+ }
66
+ catch (err) {
67
+ if (err.name === 'AbortError') {
68
+ yield { type: 'cancelled', message: 'Cancelled by user.' };
69
+ }
70
+ else {
71
+ yield { type: 'error', message: `LLM call failed: ${err.message}` };
72
+ }
73
+ yield { type: 'done', messages, totalTurns: tc };
74
+ return;
75
+ }
76
+ totalIn += resp.usage.inputTokens;
77
+ totalOut += resp.usage.outputTokens;
78
+ // Emit thinking done event if we had thinking
79
+ if (resp.thinking) {
80
+ yield {
81
+ type: 'thinking_done',
82
+ fullText: resp.thinking,
83
+ elapsed: 0,
84
+ turn: tc,
85
+ };
86
+ }
87
+ // No tool calls → final response
88
+ if (!resp.toolCalls || resp.toolCalls.length === 0) {
89
+ const cont = [];
90
+ if (resp.thinking)
91
+ cont.push({ type: 'thinking', thinking: resp.thinking, signature: '' });
92
+ if (resp.text)
93
+ cont.push({ type: 'text', text: resp.text });
94
+ messages.push({
95
+ role: 'assistant',
96
+ id: `msg-${Date.now()}-${tc}`,
97
+ timestamp: Date.now(),
98
+ model,
99
+ stopReason: resp.stopReason,
100
+ usage: { inputTokens: totalIn, outputTokens: totalOut },
101
+ content: cont,
102
+ });
103
+ yield {
104
+ type: 'turn_end',
105
+ turn: tc,
106
+ usage: { inputTokens: totalIn, outputTokens: totalOut },
107
+ totalInputTokens: totalIn,
108
+ totalOutputTokens: totalOut,
109
+ };
110
+ yield { type: 'done', messages, totalTurns: tc };
111
+ return;
112
+ }
113
+ // Build assistant message with tool calls
114
+ const aCont = [];
115
+ if (resp.thinking)
116
+ aCont.push({ type: 'thinking', thinking: resp.thinking, signature: '' });
117
+ if (resp.text)
118
+ aCont.push({ type: 'text', text: resp.text });
119
+ aCont.push(...resp.toolCalls);
120
+ messages.push({
121
+ role: 'assistant',
122
+ id: `msg-${Date.now()}-${tc}`,
123
+ timestamp: Date.now(),
124
+ model,
125
+ stopReason: 'tool_use',
126
+ usage: { inputTokens: totalIn, outputTokens: totalOut },
127
+ content: aCont,
128
+ });
129
+ conversation.push({
130
+ role: 'assistant',
131
+ content: [
132
+ ...(resp.text ? [{ type: 'text', text: resp.text }] : []),
133
+ ...resp.toolCalls.map((t) => ({
134
+ type: 'tool_use',
135
+ id: t.id,
136
+ name: t.name,
137
+ input: t.input,
138
+ })),
139
+ ],
140
+ });
141
+ // Execute tools — yield events
142
+ const results = [];
143
+ for (const tu of resp.toolCalls) {
144
+ if (context.abortSignal.aborted) {
145
+ yield {
146
+ type: 'tool_end',
147
+ toolId: tu.id,
148
+ toolName: tu.name,
149
+ result: 'Cancelled.',
150
+ isError: true,
151
+ turn: tc,
152
+ };
153
+ results.push({ toolUseId: tu.id, content: 'Cancelled.', isError: true });
154
+ continue;
155
+ }
156
+ yield {
157
+ type: 'tool_start',
158
+ toolId: tu.id,
159
+ toolName: tu.name,
160
+ input: tu.input,
161
+ turn: tc,
162
+ };
163
+ const tool = findTool(toolPool, tu.name);
164
+ if (!tool) {
165
+ const m = `Tool "${tu.name}" not found.`;
166
+ yield {
167
+ type: 'tool_end',
168
+ toolId: tu.id,
169
+ toolName: tu.name,
170
+ result: m,
171
+ isError: true,
172
+ turn: tc,
173
+ };
174
+ results.push({ toolUseId: tu.id, content: m, isError: true });
175
+ continue;
176
+ }
177
+ // Permission check
178
+ const pr = await hasPermissionsToUseTool(tool, tu.input, context.permissionContext);
179
+ if (pr.behavior === 'deny') {
180
+ const m = pr.message || 'Denied.';
181
+ yield {
182
+ type: 'tool_end',
183
+ toolId: tu.id,
184
+ toolName: tu.name,
185
+ result: m,
186
+ isError: true,
187
+ turn: tc,
188
+ };
189
+ results.push({ toolUseId: tu.id, content: m, isError: true });
190
+ continue;
191
+ }
192
+ if (pr.behavior === 'ask') {
193
+ if (allowAll) { /* pass */ }
194
+ else {
195
+ const ans = await resolvePermission(tu.name, pr.message || `Allow ${tu.name}?`);
196
+ if (ans === 'deny') {
197
+ const m = 'User denied.';
198
+ yield { type: 'tool_end', toolId: tu.id, toolName: tu.name, result: m, isError: true, turn: tc };
199
+ results.push({ toolUseId: tu.id, content: m, isError: true });
200
+ continue;
201
+ }
202
+ if (ans === 'allowAll')
203
+ allowAll = true;
204
+ }
205
+ }
206
+ // Execute tool
207
+ try {
208
+ const tCtx = {
209
+ ...context,
210
+ abortSignal: context.abortSignal,
211
+ writeProgress(d) {
212
+ // Tool progress can be tracked via optional cb
213
+ },
214
+ };
215
+ const r = await tool.call(tu.input, tCtx);
216
+ const c = r.content ?? JSON.stringify(r.data);
217
+ yield {
218
+ type: 'tool_end',
219
+ toolId: tu.id,
220
+ toolName: tu.name,
221
+ result: c,
222
+ isError: r.isError ?? false,
223
+ newMessages: r.newMessages,
224
+ contextModifier: r.contextModifier,
225
+ turn: tc,
226
+ };
227
+ results.push({ toolUseId: tu.id, content: c, isError: r.isError ?? false });
228
+ // Process newMessages
229
+ if (r.newMessages && r.newMessages.length > 0) {
230
+ for (const msg of r.newMessages) {
231
+ if (msg.role === 'user') {
232
+ conversation.push({
233
+ role: 'user',
234
+ content: typeof msg.content === 'string'
235
+ ? msg.content
236
+ : msg.content.map((bl) => {
237
+ if (bl.type === 'text')
238
+ return { type: 'text', text: bl.text };
239
+ return { type: 'text', text: JSON.stringify(bl) };
240
+ }),
241
+ });
242
+ }
243
+ }
244
+ }
245
+ // Apply contextModifier
246
+ if (r.contextModifier) {
247
+ const modified = r.contextModifier(context);
248
+ if (modified.permissionContext !== context.permissionContext) {
249
+ Object.assign(context.permissionContext, modified.permissionContext);
250
+ toolPool = buildToolPool(modified.permissionContext);
251
+ providerTools = convTools(toolPool);
252
+ }
253
+ if (modified.modelConfig !== context.modelConfig) {
254
+ Object.assign(context.modelConfig, modified.modelConfig);
255
+ }
256
+ }
257
+ }
258
+ catch (err) {
259
+ yield {
260
+ type: 'tool_end',
261
+ toolId: tu.id,
262
+ toolName: tu.name,
263
+ result: `Error: ${err.message}`,
264
+ isError: true,
265
+ turn: tc,
266
+ };
267
+ results.push({ toolUseId: tu.id, content: `Error: ${err.message}`, isError: true });
268
+ }
269
+ }
270
+ conversation.push({
271
+ role: 'user',
272
+ content: results.map((r) => ({
273
+ type: 'tool_result',
274
+ tool_use_id: r.toolUseId,
275
+ content: r.content,
276
+ is_error: r.isError,
277
+ })),
278
+ });
279
+ // Also add tool results as user messages to the local messages array
280
+ // so they appear in the 'done' event and participate in context persistence.
281
+ // The API requires tool_result messages to follow assistant messages with tool_use blocks.
282
+ messages.push({
283
+ role: 'user',
284
+ id: `tr-${Date.now()}-${tc}`,
285
+ timestamp: Date.now(),
286
+ content: results.map((r) => ({
287
+ type: 'tool_result',
288
+ tool_use_id: r.toolUseId,
289
+ content: r.content,
290
+ is_error: r.isError,
291
+ })),
292
+ });
293
+ yield {
294
+ type: 'turn_end',
295
+ turn: tc,
296
+ usage: { inputTokens: totalIn, outputTokens: totalOut },
297
+ totalInputTokens: totalIn,
298
+ totalOutputTokens: totalOut,
299
+ };
300
+ }
301
+ yield { type: 'done', messages, totalTurns: tc };
302
+ }
303
+ // ─── Permission resolution — bridged from the REPL's askState ─────────
304
+ let permissionResolver = null;
305
+ export function setPermissionResolver(fn) {
306
+ permissionResolver = fn;
307
+ }
308
+ async function resolvePermission(toolName, message) {
309
+ if (permissionResolver) {
310
+ return permissionResolver(toolName, message);
311
+ }
312
+ return 'allow'; // Default: allow if no resolver is set (headless mode)
313
+ }
314
+ async function callLLMStreaming(i) {
315
+ switch (i.provider) {
316
+ case 'anthropic': return callA(i);
317
+ case 'ollama': return callO({ ...i, baseUrl: i.baseUrl || 'http://localhost:11434/v1' });
318
+ case 'openai': return callO({ ...i, baseUrl: i.baseUrl || 'https://api.openai.com/v1' });
319
+ case 'openrouter': return callO({ ...i, baseUrl: i.baseUrl || 'https://openrouter.ai/api/v1' });
320
+ case 'deepseek': return callO({ ...i, baseUrl: i.baseUrl || 'https://api.deepseek.com/v1' });
321
+ default: return callO(i);
322
+ }
323
+ }
324
+ // ─── Anthropic streaming ────────────────────────────────────────────────
325
+ async function callA(i) {
326
+ const { model, apiKey, systemPrompt, messages, tools, maxTokens, abortSignal, cb, turnNumber } = i;
327
+ const body = {
328
+ model,
329
+ max_tokens: maxTokens || 8192,
330
+ stream: true,
331
+ system: systemPrompt,
332
+ messages: convAnt(messages),
333
+ thinking: { type: 'enabled', budget_tokens: 4000 },
334
+ };
335
+ if (tools.length) {
336
+ body.tools = tools.map((t) => ({
337
+ name: t.name,
338
+ description: t.description,
339
+ input_schema: t.input_schema,
340
+ }));
341
+ }
342
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
343
+ method: 'POST',
344
+ headers: {
345
+ 'Content-Type': 'application/json',
346
+ 'x-api-key': apiKey,
347
+ 'anthropic-version': '2023-06-01',
348
+ },
349
+ body: JSON.stringify(body),
350
+ signal: abortSignal,
351
+ });
352
+ if (!res.ok)
353
+ throw new Error(`API ${res.status}: ${(await res.text()).slice(0, 300)}`);
354
+ const reader = res.body?.getReader();
355
+ if (!reader)
356
+ throw new Error('No stream');
357
+ const d = new TextDecoder();
358
+ const tp = [], hp = [], tcs = [];
359
+ const tbuf = new Map();
360
+ let it = 0, ot = 0, sr = 'end_turn', b = '';
361
+ while (true) {
362
+ const { done, value } = await reader.read();
363
+ if (done)
364
+ break;
365
+ b += d.decode(value, { stream: true });
366
+ const ls = b.split('\n');
367
+ b = ls.pop() || '';
368
+ for (const l of ls) {
369
+ if (!l.startsWith('data: '))
370
+ continue;
371
+ const js = l.slice(6).trim();
372
+ if (!js || js === '[DONE]')
373
+ continue;
374
+ let e;
375
+ try {
376
+ e = JSON.parse(js);
377
+ }
378
+ catch {
379
+ continue;
380
+ }
381
+ switch (e.type) {
382
+ case 'message_start':
383
+ it = e.message?.usage?.input_tokens ?? 0;
384
+ break;
385
+ case 'content_block_start':
386
+ if (e.content_block?.type === 'tool_use') {
387
+ tbuf.set(e.content_block.id, { name: e.content_block.name, input: '' });
388
+ }
389
+ break;
390
+ case 'content_block_delta':
391
+ if (e.delta?.type === 'text_delta') {
392
+ tp.push(e.delta.text);
393
+ cb.onText(e.delta.text, turnNumber);
394
+ }
395
+ else if (e.delta?.type === 'thinking_delta') {
396
+ hp.push(e.delta.thinking);
397
+ cb.onThinking(e.delta.thinking, turnNumber);
398
+ }
399
+ else if (e.delta?.type === 'input_json_delta') {
400
+ const bb = tbuf.get(e.index);
401
+ if (bb)
402
+ bb.input += e.delta.partial_json;
403
+ }
404
+ break;
405
+ case 'content_block_stop': {
406
+ const bb = tbuf.get(e.index);
407
+ if (bb) {
408
+ try {
409
+ tcs.push({ type: 'tool_use', id: e.index, name: bb.name, input: JSON.parse(bb.input) });
410
+ }
411
+ catch {
412
+ tcs.push({ type: 'tool_use', id: e.index, name: bb.name, input: { raw: bb.input } });
413
+ }
414
+ tbuf.delete(e.index);
415
+ }
416
+ break;
417
+ }
418
+ case 'message_delta':
419
+ ot = e.usage?.output_tokens ?? ot;
420
+ sr = e.delta?.stop_reason ?? sr;
421
+ break;
422
+ }
423
+ }
424
+ }
425
+ return {
426
+ text: tp.join('') || null,
427
+ thinking: hp.join('') || null,
428
+ toolCalls: tcs.length > 0 ? tcs : null,
429
+ usage: { inputTokens: it, outputTokens: ot },
430
+ stopReason: sr,
431
+ };
432
+ }
433
+ // ─── OpenAI-compatible streaming ────────────────────────────────────────
434
+ async function callO(i) {
435
+ const { model, apiKey, baseUrl, systemPrompt, messages, tools, maxTokens, abortSignal, cb, turnNumber } = i;
436
+ const oMsgs = [{ role: 'system', content: systemPrompt }, ...convOAI(messages)];
437
+ const body = {
438
+ model,
439
+ messages: oMsgs,
440
+ max_tokens: maxTokens || 4096,
441
+ temperature: 0.7,
442
+ stream: true,
443
+ stream_options: { include_usage: true },
444
+ };
445
+ if (tools.length) {
446
+ body.tools = tools.map((t) => ({
447
+ type: 'function',
448
+ function: { name: t.name, description: t.description, parameters: t.input_schema },
449
+ }));
450
+ body.tool_choice = 'auto';
451
+ }
452
+ const h = { 'Content-Type': 'application/json' };
453
+ if (baseUrl.includes('openrouter.ai')) {
454
+ h['Authorization'] = `Bearer ${apiKey}`;
455
+ h['HTTP-Referer'] = 'https://github.com/cray-code';
456
+ h['X-Title'] = 'Cray Code';
457
+ }
458
+ else if (apiKey) {
459
+ h['Authorization'] = `Bearer ${apiKey}`;
460
+ }
461
+ const res = await fetch(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
462
+ method: 'POST', headers: h, body: JSON.stringify(body), signal: abortSignal,
463
+ });
464
+ if (!res.ok)
465
+ throw new Error(`API ${res.status}: ${(await res.text()).slice(0, 300)}`);
466
+ const reader = res.body?.getReader();
467
+ if (!reader)
468
+ throw new Error('No stream');
469
+ const d = new TextDecoder();
470
+ const tp = [], hp = [], fcs = [];
471
+ const deltas = new Map();
472
+ let it = 0, ot = 0, sr = 'stop', b = '';
473
+ while (true) {
474
+ const { done, value } = await reader.read();
475
+ if (done)
476
+ break;
477
+ b += d.decode(value, { stream: true });
478
+ const ls = b.split('\n');
479
+ b = ls.pop() || '';
480
+ for (const l of ls) {
481
+ const dl = l.trim();
482
+ if (!dl.startsWith('data: '))
483
+ continue;
484
+ const js = dl.slice(6);
485
+ if (!js || js === '[DONE]')
486
+ continue;
487
+ let e;
488
+ try {
489
+ e = JSON.parse(js);
490
+ }
491
+ catch {
492
+ continue;
493
+ }
494
+ const dt = e.choices?.[0]?.delta;
495
+ if (!dt) {
496
+ if (e.usage) {
497
+ it = e.usage.prompt_tokens ?? 0;
498
+ ot = e.usage.completion_tokens ?? 0;
499
+ }
500
+ continue;
501
+ }
502
+ if (dt.reasoning_content) {
503
+ hp.push(dt.reasoning_content);
504
+ cb.onThinking(dt.reasoning_content, turnNumber);
505
+ }
506
+ else if (dt.reasoning) {
507
+ hp.push(dt.reasoning);
508
+ cb.onThinking(dt.reasoning, turnNumber);
509
+ }
510
+ if (dt.content) {
511
+ tp.push(dt.content);
512
+ cb.onText(dt.content, turnNumber);
513
+ }
514
+ if (dt.tool_calls) {
515
+ for (const tc of dt.tool_calls) {
516
+ const idx = tc.index ?? 0;
517
+ const ex = deltas.get(idx) || { id: '', name: '', args: '' };
518
+ if (tc.id)
519
+ ex.id = tc.id;
520
+ if (tc.function?.name)
521
+ ex.name = tc.function.name;
522
+ if (tc.function?.arguments)
523
+ ex.args += tc.function.arguments;
524
+ deltas.set(idx, ex);
525
+ }
526
+ }
527
+ if (e.choices?.[0]?.finish_reason)
528
+ sr = e.choices[0].finish_reason === 'tool_calls' ? 'tool_use' : e.choices[0].finish_reason;
529
+ if (e.usage) {
530
+ it = e.usage.prompt_tokens ?? 0;
531
+ ot = e.usage.completion_tokens ?? 0;
532
+ }
533
+ }
534
+ }
535
+ for (const [, tc] of deltas) {
536
+ if (tc.name) {
537
+ let inp = {};
538
+ try {
539
+ inp = JSON.parse(tc.args || '{}');
540
+ }
541
+ catch {
542
+ inp = { raw: tc.args };
543
+ }
544
+ fcs.push({ type: 'tool_use', id: tc.id || `call-${Date.now()}`, name: tc.name, input: inp });
545
+ }
546
+ }
547
+ return {
548
+ text: tp.join('') || null,
549
+ thinking: hp.join('') || null,
550
+ toolCalls: fcs.length > 0 ? fcs : null,
551
+ usage: { inputTokens: it, outputTokens: ot },
552
+ stopReason: sr,
553
+ };
554
+ }
555
+ // ─── History → Conversation Builder (context continuity) ─────────────
556
+ /**
557
+ * Convert the full message history (from context.messages) into the
558
+ * API conversation format, then append the new user prompt.
559
+ *
560
+ * This is the key difference from starting fresh every turn:
561
+ * the LLM sees all previous user/assistant exchanges, so it maintains
562
+ * context across turns.
563
+ */
564
+ function historyToConversation(history, newPrompt) {
565
+ const conversation = [];
566
+ for (const msg of history) {
567
+ if (msg.role === 'user') {
568
+ // User messages: content is string, or array of text/image/tool_result blocks.
569
+ // If array, keep only text + tool_result blocks for API format.
570
+ if (typeof msg.content === 'string') {
571
+ conversation.push({ role: 'user', content: msg.content });
572
+ }
573
+ else if (Array.isArray(msg.content)) {
574
+ const blocks = msg.content.filter((b) => b.type === 'text' || b.type === 'tool_result' || b.type === 'image');
575
+ if (blocks.length > 0) {
576
+ conversation.push({ role: 'user', content: blocks });
577
+ }
578
+ }
579
+ }
580
+ else if (msg.role === 'assistant') {
581
+ // Assistant messages: content is ContentBlock[] with text/thinking/tool_use.
582
+ // For API format, keep only text + tool_use (skip thinking).
583
+ const blocks = Array.isArray(msg.content) ? msg.content : [{ type: 'text', text: String(msg.content) }];
584
+ const apiBlocks = [];
585
+ for (const block of blocks) {
586
+ if (block.type === 'text') {
587
+ apiBlocks.push({ type: 'text', text: block.text });
588
+ }
589
+ else if (block.type === 'tool_use') {
590
+ apiBlocks.push({
591
+ type: 'tool_use',
592
+ id: block.id,
593
+ name: block.name,
594
+ input: block.input,
595
+ });
596
+ }
597
+ // Skip thinking and tool_result blocks in assistant messages
598
+ }
599
+ if (apiBlocks.length > 0) {
600
+ conversation.push({ role: 'assistant', content: apiBlocks });
601
+ }
602
+ }
603
+ // Skip system messages
604
+ }
605
+ // Append the new user prompt as the latest user message
606
+ conversation.push({ role: 'user', content: newPrompt });
607
+ return conversation;
608
+ }
609
+ // ─── Helpers ────────────────────────────────────────────────────────────
610
+ function buildSP(ctx, cp, sub) {
611
+ const p = [];
612
+ if (sub) {
613
+ p.push('You are a Cray Code sub-agent.');
614
+ }
615
+ else {
616
+ p.push('You are Cray Code, an AI coding assistant. Help users with software engineering tasks.');
617
+ p.push(`Working directory: ${ctx.cwd}`);
618
+ p.push(`Date: ${new Date().toISOString().split('T')[0]}`);
619
+ p.push(`Permission mode: ${ctx.permissionContext.mode}`);
620
+ }
621
+ if (cp)
622
+ p.push(cp);
623
+ p.push("Use tools when needed. Be concise. Respond in the user's language.");
624
+ return p.join('\n');
625
+ }
626
+ function convTools(tp) {
627
+ return tp.map((t) => ({
628
+ name: t.name,
629
+ description: t.searchHint ?? t.name,
630
+ input_schema: (t.inputJSONSchema ?? {
631
+ type: 'object',
632
+ properties: {},
633
+ additionalProperties: true,
634
+ }),
635
+ }));
636
+ }
637
+ function convAnt(msgs) {
638
+ return msgs.map((m) => {
639
+ const role = m.role === 'assistant' ? 'assistant' : 'user';
640
+ if (typeof m.content === 'string')
641
+ return { role, content: m.content };
642
+ if (Array.isArray(m.content)) {
643
+ return {
644
+ role,
645
+ content: m.content.map((c) => {
646
+ switch (c.type) {
647
+ case 'text': return { type: 'text', text: String(c.text ?? '') };
648
+ case 'tool_use': return { type: 'tool_use', id: String(c.id ?? ''), name: String(c.name ?? ''), input: { ...c.input } };
649
+ case 'tool_result': return { type: 'tool_result', tool_use_id: String(c.tool_use_id ?? ''), content: String(c.content ?? '') };
650
+ default: return { type: 'text', text: JSON.stringify(c) };
651
+ }
652
+ }),
653
+ };
654
+ }
655
+ return { role, content: String(m.content) };
656
+ });
657
+ }
658
+ function convOAI(msgs) {
659
+ const out = [];
660
+ for (const m of msgs) {
661
+ if (m.role === 'user') {
662
+ if (typeof m.content === 'string') {
663
+ out.push({ role: 'user', content: m.content });
664
+ continue;
665
+ }
666
+ if (Array.isArray(m.content)) {
667
+ const tls = [], blks = [];
668
+ for (const c of m.content) {
669
+ if (c.type === 'tool_result')
670
+ tls.push({ role: 'tool', tool_call_id: String(c.tool_use_id ?? ''), content: String(c.content ?? '') });
671
+ else if (c.type === 'text')
672
+ blks.push({ type: 'text', text: String(c.text ?? '') });
673
+ }
674
+ if (tls.length)
675
+ out.push(...tls);
676
+ if (blks.length)
677
+ out.push({ role: 'user', content: blks });
678
+ }
679
+ }
680
+ else if (m.role === 'assistant') {
681
+ if (typeof m.content === 'string') {
682
+ out.push({ role: 'assistant', content: m.content });
683
+ continue;
684
+ }
685
+ if (Array.isArray(m.content)) {
686
+ const txt = [], tcs = [];
687
+ for (const c of m.content) {
688
+ if (c.type === 'text')
689
+ txt.push(String(c.text ?? ''));
690
+ else if (c.type === 'tool_use')
691
+ tcs.push({ id: String(c.id ?? ''), type: 'function', function: { name: String(c.name ?? ''), arguments: JSON.stringify(c.input ?? {}) } });
692
+ }
693
+ const msg = { role: 'assistant' };
694
+ if (txt.length)
695
+ msg.content = txt.join('');
696
+ if (tcs.length)
697
+ msg.tool_calls = tcs;
698
+ out.push(msg);
699
+ }
700
+ }
701
+ }
702
+ return out;
703
+ }
704
+ //# sourceMappingURL=queryStream.js.map