@juspay/neurolink 9.52.0 → 9.54.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 (151) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +19 -0
  3. package/dist/adapters/tts/cartesiaHandler.d.ts +12 -0
  4. package/dist/adapters/tts/cartesiaHandler.js +130 -0
  5. package/dist/agent/directTools.d.ts +2 -2
  6. package/dist/auth/errors.d.ts +1 -1
  7. package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
  8. package/dist/auth/providers/BaseAuthProvider.d.ts +1 -1
  9. package/dist/autoresearch/config.d.ts +11 -0
  10. package/dist/autoresearch/config.js +108 -0
  11. package/dist/autoresearch/errors.d.ts +40 -0
  12. package/dist/autoresearch/errors.js +20 -0
  13. package/dist/autoresearch/index.d.ts +23 -0
  14. package/dist/autoresearch/index.js +34 -0
  15. package/dist/autoresearch/phasePolicy.d.ts +9 -0
  16. package/dist/autoresearch/phasePolicy.js +69 -0
  17. package/dist/autoresearch/promptCompiler.d.ts +15 -0
  18. package/dist/autoresearch/promptCompiler.js +120 -0
  19. package/dist/autoresearch/repoPolicy.d.ts +32 -0
  20. package/dist/autoresearch/repoPolicy.js +128 -0
  21. package/dist/autoresearch/resultRecorder.d.ts +20 -0
  22. package/dist/autoresearch/resultRecorder.js +130 -0
  23. package/dist/autoresearch/runner.d.ts +10 -0
  24. package/dist/autoresearch/runner.js +102 -0
  25. package/dist/autoresearch/stateStore.d.ts +12 -0
  26. package/dist/autoresearch/stateStore.js +163 -0
  27. package/dist/autoresearch/summaryParser.d.ts +16 -0
  28. package/dist/autoresearch/summaryParser.js +94 -0
  29. package/dist/autoresearch/tools.d.ts +257 -0
  30. package/dist/autoresearch/tools.js +617 -0
  31. package/dist/autoresearch/worker.d.ts +71 -0
  32. package/dist/autoresearch/worker.js +417 -0
  33. package/dist/browser/neurolink.min.js +340 -326
  34. package/dist/cli/commands/autoresearch.d.ts +41 -0
  35. package/dist/cli/commands/autoresearch.js +487 -0
  36. package/dist/cli/commands/config.d.ts +1 -1
  37. package/dist/cli/commands/task.d.ts +2 -0
  38. package/dist/cli/commands/task.js +32 -3
  39. package/dist/cli/commands/voiceServer.d.ts +6 -0
  40. package/dist/cli/commands/voiceServer.js +17 -0
  41. package/dist/cli/parser.js +7 -1
  42. package/dist/core/baseProvider.js +18 -0
  43. package/dist/evaluation/errors/EvaluationError.d.ts +1 -1
  44. package/dist/lib/adapters/tts/cartesiaHandler.d.ts +12 -0
  45. package/dist/lib/adapters/tts/cartesiaHandler.js +131 -0
  46. package/dist/lib/agent/directTools.d.ts +2 -2
  47. package/dist/lib/auth/errors.d.ts +1 -1
  48. package/dist/lib/auth/middleware/AuthMiddleware.d.ts +1 -1
  49. package/dist/lib/auth/providers/BaseAuthProvider.d.ts +1 -1
  50. package/dist/lib/autoresearch/config.d.ts +11 -0
  51. package/dist/lib/autoresearch/config.js +109 -0
  52. package/dist/lib/autoresearch/errors.d.ts +40 -0
  53. package/dist/lib/autoresearch/errors.js +21 -0
  54. package/dist/lib/autoresearch/index.d.ts +23 -0
  55. package/dist/lib/autoresearch/index.js +35 -0
  56. package/dist/lib/autoresearch/phasePolicy.d.ts +9 -0
  57. package/dist/lib/autoresearch/phasePolicy.js +70 -0
  58. package/dist/lib/autoresearch/promptCompiler.d.ts +15 -0
  59. package/dist/lib/autoresearch/promptCompiler.js +121 -0
  60. package/dist/lib/autoresearch/repoPolicy.d.ts +32 -0
  61. package/dist/lib/autoresearch/repoPolicy.js +129 -0
  62. package/dist/lib/autoresearch/resultRecorder.d.ts +20 -0
  63. package/dist/lib/autoresearch/resultRecorder.js +131 -0
  64. package/dist/lib/autoresearch/runner.d.ts +10 -0
  65. package/dist/lib/autoresearch/runner.js +103 -0
  66. package/dist/lib/autoresearch/stateStore.d.ts +12 -0
  67. package/dist/lib/autoresearch/stateStore.js +164 -0
  68. package/dist/lib/autoresearch/summaryParser.d.ts +16 -0
  69. package/dist/lib/autoresearch/summaryParser.js +95 -0
  70. package/dist/lib/autoresearch/tools.d.ts +257 -0
  71. package/dist/lib/autoresearch/tools.js +618 -0
  72. package/dist/lib/autoresearch/worker.d.ts +71 -0
  73. package/dist/lib/autoresearch/worker.js +418 -0
  74. package/dist/lib/core/baseProvider.js +18 -0
  75. package/dist/lib/evaluation/errors/EvaluationError.d.ts +1 -1
  76. package/dist/lib/files/fileTools.d.ts +1 -1
  77. package/dist/lib/neurolink.js +22 -2
  78. package/dist/lib/providers/azureOpenai.d.ts +4 -1
  79. package/dist/lib/providers/azureOpenai.js +9 -3
  80. package/dist/lib/providers/litellm.js +2 -2
  81. package/dist/lib/providers/openRouter.js +2 -2
  82. package/dist/lib/providers/openaiCompatible.js +3 -1
  83. package/dist/lib/server/voice/frameBus.d.ts +8 -0
  84. package/dist/lib/server/voice/frameBus.js +25 -0
  85. package/dist/lib/server/voice/turnManager.d.ts +15 -0
  86. package/dist/lib/server/voice/turnManager.js +36 -0
  87. package/dist/lib/server/voice/types.d.ts +20 -0
  88. package/dist/lib/server/voice/types.js +2 -0
  89. package/dist/lib/server/voice/voiceServerApp.d.ts +1 -0
  90. package/dist/lib/server/voice/voiceServerApp.js +118 -0
  91. package/dist/lib/server/voice/voiceWebSocketHandler.d.ts +11 -0
  92. package/dist/lib/server/voice/voiceWebSocketHandler.js +536 -0
  93. package/dist/lib/tasks/autoresearchTaskExecutor.d.ts +32 -0
  94. package/dist/lib/tasks/autoresearchTaskExecutor.js +303 -0
  95. package/dist/lib/tasks/errors.d.ts +3 -1
  96. package/dist/lib/tasks/errors.js +1 -0
  97. package/dist/lib/tasks/taskExecutor.d.ts +4 -2
  98. package/dist/lib/tasks/taskExecutor.js +8 -1
  99. package/dist/lib/tasks/taskManager.js +27 -3
  100. package/dist/lib/tasks/tools/taskTools.d.ts +1 -1
  101. package/dist/lib/telemetry/attributes.d.ts +15 -0
  102. package/dist/lib/telemetry/attributes.js +16 -0
  103. package/dist/lib/telemetry/tracers.d.ts +1 -0
  104. package/dist/lib/telemetry/tracers.js +1 -0
  105. package/dist/lib/types/autoresearchTypes.d.ts +194 -0
  106. package/dist/lib/types/autoresearchTypes.js +18 -0
  107. package/dist/lib/types/common.d.ts +11 -0
  108. package/dist/lib/types/index.d.ts +16 -14
  109. package/dist/lib/types/index.js +21 -17
  110. package/dist/lib/types/taskTypes.d.ts +38 -0
  111. package/dist/lib/workflow/config.d.ts +3 -3
  112. package/dist/neurolink.js +22 -2
  113. package/dist/providers/azureOpenai.d.ts +4 -1
  114. package/dist/providers/azureOpenai.js +9 -3
  115. package/dist/providers/litellm.js +2 -2
  116. package/dist/providers/openRouter.js +2 -2
  117. package/dist/providers/openaiCompatible.js +3 -1
  118. package/dist/rag/errors/RAGError.d.ts +1 -1
  119. package/dist/server/voice/frameBus.d.ts +8 -0
  120. package/dist/server/voice/frameBus.js +24 -0
  121. package/dist/server/voice/public/app.js +275 -0
  122. package/dist/server/voice/public/index.html +18 -0
  123. package/dist/server/voice/public/pcm-worklet.js +67 -0
  124. package/dist/server/voice/public/styles.css +102 -0
  125. package/dist/server/voice/turnManager.d.ts +15 -0
  126. package/dist/server/voice/turnManager.js +35 -0
  127. package/dist/server/voice/types.d.ts +20 -0
  128. package/dist/server/voice/types.js +1 -0
  129. package/dist/server/voice/voiceServerApp.d.ts +1 -0
  130. package/dist/server/voice/voiceServerApp.js +117 -0
  131. package/dist/server/voice/voiceWebSocketHandler.d.ts +11 -0
  132. package/dist/server/voice/voiceWebSocketHandler.js +535 -0
  133. package/dist/tasks/autoresearchTaskExecutor.d.ts +32 -0
  134. package/dist/tasks/autoresearchTaskExecutor.js +302 -0
  135. package/dist/tasks/errors.d.ts +3 -1
  136. package/dist/tasks/errors.js +1 -0
  137. package/dist/tasks/taskExecutor.d.ts +4 -2
  138. package/dist/tasks/taskExecutor.js +8 -1
  139. package/dist/tasks/taskManager.js +27 -3
  140. package/dist/tasks/tools/taskTools.d.ts +1 -1
  141. package/dist/telemetry/attributes.d.ts +15 -0
  142. package/dist/telemetry/attributes.js +16 -0
  143. package/dist/telemetry/tracers.d.ts +1 -0
  144. package/dist/telemetry/tracers.js +1 -0
  145. package/dist/types/autoresearchTypes.d.ts +194 -0
  146. package/dist/types/autoresearchTypes.js +17 -0
  147. package/dist/types/common.d.ts +11 -0
  148. package/dist/types/index.d.ts +16 -14
  149. package/dist/types/index.js +21 -17
  150. package/dist/types/taskTypes.d.ts +38 -0
  151. package/package.json +2 -1
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Research worker — orchestrates the full experiment loop.
3
+ *
4
+ * Wires tools, state, policy, and NeuroLink generate() into a
5
+ * single experiment cycle. Can run standalone or via TaskManager.
6
+ *
7
+ * Emits autoresearch:* lifecycle events through an injected emitter
8
+ * and wraps key operations in OpenTelemetry spans for observability.
9
+ */
10
+ import { execFileSync } from "node:child_process";
11
+ import { ATTR } from "../telemetry/attributes.js";
12
+ import { tracers } from "../telemetry/tracers.js";
13
+ import { withSpan } from "../telemetry/withSpan.js";
14
+ import { withTimeout } from "../utils/errorHandling.js";
15
+ import { logger } from "../utils/logger.js";
16
+ import { resolveConfig, validateConfig } from "./config.js";
17
+ import { AutoresearchError } from "./errors.js";
18
+ import { getPhaseToolPolicy } from "./phasePolicy.js";
19
+ import { PromptCompiler } from "./promptCompiler.js";
20
+ import { RepoPolicy } from "./repoPolicy.js";
21
+ import { ResultRecorder } from "./resultRecorder.js";
22
+ import { ExperimentRunner } from "./runner.js";
23
+ import { ResearchStateStore } from "./stateStore.js";
24
+ import { createResearchTools } from "./tools.js";
25
+ function isBetter(candidate, best, direction) {
26
+ return direction === "lower" ? candidate < best : candidate > best;
27
+ }
28
+ function decideOutcome(metric, crashed, timedOut, bestMetric, direction) {
29
+ if (timedOut) {
30
+ return "timeout";
31
+ }
32
+ if (crashed || metric === null) {
33
+ return "crash";
34
+ }
35
+ if (bestMetric === null) {
36
+ return "keep";
37
+ } // First run is baseline
38
+ return isBetter(metric, bestMetric, direction) ? "keep" : "discard";
39
+ }
40
+ export class ResearchWorker {
41
+ config;
42
+ stateStore;
43
+ repoPolicy;
44
+ runner;
45
+ recorder;
46
+ promptCompiler;
47
+ initialized = false;
48
+ /** Event emitter injected by NeuroLink/TaskManager for lifecycle events. */
49
+ emitter;
50
+ constructor(configInput) {
51
+ this.config = resolveConfig(configInput);
52
+ this.stateStore = new ResearchStateStore(this.config.repoPath, this.config.statePath);
53
+ this.repoPolicy = new RepoPolicy(this.config);
54
+ this.runner = new ExperimentRunner(this.config);
55
+ this.recorder = new ResultRecorder(this.config);
56
+ this.promptCompiler = new PromptCompiler(this.config);
57
+ }
58
+ // ── Emitter integration ──────────────────────────────────
59
+ /** Set the event emitter (called by NeuroLink/TaskManager during integration). */
60
+ setEmitter(emitter) {
61
+ this.emitter = emitter;
62
+ }
63
+ /** Emit a lifecycle event. Safe to call when no emitter is set. */
64
+ emit(event, ...args) {
65
+ this.emitter?.emit(event, ...args);
66
+ }
67
+ // ── Lifecycle ────────────────────────────────────────────
68
+ /** Initialize: validate config, ensure branch, create state */
69
+ async initialize(tag) {
70
+ return withSpan({
71
+ name: "autoresearch.initialize",
72
+ tracer: tracers.autoresearch,
73
+ attributes: {
74
+ [ATTR.AR_TAG]: tag,
75
+ [ATTR.AR_BRANCH]: `${this.config.branchPrefix}${tag}`,
76
+ },
77
+ }, async (span) => {
78
+ validateConfig(this.config);
79
+ const branch = `${this.config.branchPrefix}${tag}`;
80
+ // Create branch if it doesn't exist
81
+ try {
82
+ const currentBranch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
83
+ cwd: this.config.repoPath,
84
+ encoding: "utf-8",
85
+ }).trim();
86
+ if (currentBranch !== branch) {
87
+ try {
88
+ execFileSync("git", ["checkout", "-b", branch], {
89
+ cwd: this.config.repoPath,
90
+ stdio: "ignore",
91
+ });
92
+ }
93
+ catch {
94
+ // Branch may already exist
95
+ execFileSync("git", ["checkout", branch], {
96
+ cwd: this.config.repoPath,
97
+ stdio: "ignore",
98
+ });
99
+ }
100
+ }
101
+ }
102
+ catch (error) {
103
+ this.emitError(tag, "BRANCH_ERROR", `Failed to setup branch ${branch}`);
104
+ throw AutoresearchError.create("BRANCH_ERROR", `Failed to setup branch ${branch}`, {
105
+ cause: error instanceof Error ? error : undefined,
106
+ });
107
+ }
108
+ // Initialize state
109
+ const state = await this.stateStore.initialize(tag, branch);
110
+ // Ensure results file exists
111
+ await this.recorder.ensureResultsFile();
112
+ this.initialized = true;
113
+ span.setAttribute(ATTR.AR_PHASE, state.currentPhase);
114
+ logger.info("[Autoresearch] Worker initialized", { tag, branch });
115
+ this.emit("autoresearch:initialized", {
116
+ tag,
117
+ branch,
118
+ config: {
119
+ repoPath: this.config.repoPath,
120
+ runCommand: this.config.runCommand,
121
+ metric: this.config.metric,
122
+ timeoutMs: this.config.timeoutMs,
123
+ },
124
+ });
125
+ return state;
126
+ });
127
+ }
128
+ /** Load existing state (for resuming) */
129
+ async resume() {
130
+ return withSpan({
131
+ name: "autoresearch.resume",
132
+ tracer: tracers.autoresearch,
133
+ }, async (span) => {
134
+ const state = await this.stateStore.load();
135
+ if (!state) {
136
+ throw AutoresearchError.create("STATE_NOT_FOUND", "No state file found. Run initialize() first.");
137
+ }
138
+ validateConfig(this.config);
139
+ // Ensure we're on the correct branch (may have changed after restart)
140
+ try {
141
+ const currentBranch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: this.config.repoPath, encoding: "utf-8" }).trim();
142
+ if (currentBranch !== state.branch) {
143
+ execFileSync("git", ["checkout", state.branch], {
144
+ cwd: this.config.repoPath,
145
+ stdio: "ignore",
146
+ });
147
+ }
148
+ }
149
+ catch (branchErr) {
150
+ logger.error("[Autoresearch] Failed to restore branch", {
151
+ expected: state.branch,
152
+ error: branchErr instanceof Error
153
+ ? branchErr.message
154
+ : String(branchErr),
155
+ });
156
+ throw AutoresearchError.create("BRANCH_ERROR", `Failed to checkout branch ${state.branch} during resume`, { cause: branchErr instanceof Error ? branchErr : undefined });
157
+ }
158
+ this.initialized = true;
159
+ span.setAttribute(ATTR.AR_TAG, state.tag);
160
+ span.setAttribute(ATTR.AR_BRANCH, state.branch);
161
+ span.setAttribute(ATTR.AR_RUN_COUNT, state.runCount);
162
+ span.setAttribute(ATTR.AR_PHASE, state.currentPhase);
163
+ this.emit("autoresearch:resumed", {
164
+ tag: state.tag,
165
+ branch: state.branch,
166
+ runCount: state.runCount,
167
+ currentPhase: state.currentPhase,
168
+ });
169
+ return state;
170
+ });
171
+ }
172
+ /** Run one full experiment cycle without AI — just the deterministic parts */
173
+ async runExperimentCycle(description) {
174
+ if (!this.initialized) {
175
+ throw AutoresearchError.create("WORKER_NOT_INITIALIZED", "Call initialize() or resume() first");
176
+ }
177
+ const state = await this.stateStore.load();
178
+ if (!state) {
179
+ throw AutoresearchError.create("STATE_NOT_FOUND", "State file missing");
180
+ }
181
+ return withSpan({
182
+ name: "autoresearch.experiment_cycle",
183
+ tracer: tracers.autoresearch,
184
+ attributes: {
185
+ [ATTR.AR_TAG]: state.tag,
186
+ [ATTR.AR_RUN_COUNT]: state.runCount,
187
+ [ATTR.AR_DESCRIPTION]: description,
188
+ },
189
+ }, async (span) => {
190
+ const cycleStart = Date.now();
191
+ this.emit("autoresearch:experiment-started", {
192
+ tag: state.tag,
193
+ runCount: state.runCount,
194
+ description,
195
+ });
196
+ await this.advancePhase("run");
197
+ // Run the experiment
198
+ logger.info("[Autoresearch] Running experiment", {
199
+ runCount: state.runCount,
200
+ description,
201
+ });
202
+ const summary = await withSpan({
203
+ name: "autoresearch.experiment_run",
204
+ tracer: tracers.autoresearch,
205
+ attributes: { [ATTR.AR_TAG]: state.tag },
206
+ }, async () => {
207
+ return withTimeout(this.runner.run(), this.config.timeoutMs + 30_000, new Error("Experiment runner exceeded safety timeout"));
208
+ });
209
+ await this.advancePhase("evaluate");
210
+ // Deterministic decision
211
+ const status = decideOutcome(summary.metric, summary.crashed, summary.timedOut, state.bestMetric, this.config.metric.direction);
212
+ // Get commit hash
213
+ const commit = this.repoPolicy.getHeadCommit() || "unknown";
214
+ // Build record
215
+ const record = {
216
+ commit,
217
+ metric: summary.metric,
218
+ memoryGb: summary.memoryValue,
219
+ status,
220
+ description,
221
+ timestamp: new Date().toISOString(),
222
+ };
223
+ // Record result
224
+ await this.recorder.appendTsv(record);
225
+ await this.recorder.appendJsonl(record);
226
+ await this.advancePhase("accept_or_revert");
227
+ // Update state based on outcome
228
+ if (status === "keep") {
229
+ await this.stateStore.update({
230
+ acceptedCommit: commit,
231
+ bestMetric: summary.metric,
232
+ baselineMetric: state.baselineMetric ?? summary.metric,
233
+ keepCount: state.keepCount + 1,
234
+ runCount: state.runCount + 1,
235
+ lastStatus: status,
236
+ candidateCommit: null,
237
+ });
238
+ // Emit metric-improved if this beats a previous best
239
+ if (summary.metric !== null &&
240
+ state.bestMetric !== null &&
241
+ isBetter(summary.metric, state.bestMetric, this.config.metric.direction)) {
242
+ this.emit("autoresearch:metric-improved", {
243
+ tag: state.tag,
244
+ previousBest: state.bestMetric,
245
+ newBest: summary.metric,
246
+ commit,
247
+ direction: this.config.metric.direction,
248
+ runCount: state.runCount + 1,
249
+ });
250
+ }
251
+ }
252
+ else {
253
+ // Revert on discard/crash/timeout
254
+ if (state.acceptedCommit) {
255
+ this.emit("autoresearch:revert", {
256
+ tag: state.tag,
257
+ targetCommit: state.acceptedCommit,
258
+ reason: status,
259
+ runCount: state.runCount,
260
+ });
261
+ try {
262
+ execFileSync("git", ["reset", "--hard", state.acceptedCommit], {
263
+ cwd: this.config.repoPath,
264
+ stdio: "ignore",
265
+ });
266
+ }
267
+ catch (error) {
268
+ const errorMsg = error instanceof Error ? error.message : String(error);
269
+ logger.error("[Autoresearch] Revert failed — state NOT updated", {
270
+ error: errorMsg,
271
+ });
272
+ this.emit("autoresearch:revert-failed", {
273
+ tag: state.tag,
274
+ targetCommit: state.acceptedCommit,
275
+ error: errorMsg,
276
+ runCount: state.runCount,
277
+ });
278
+ this.emitError(state.tag, "REVERT_FAILED", `Failed to revert to ${state.acceptedCommit}. Manual intervention required.`, state.currentPhase, state.runCount);
279
+ // Do NOT advance state — repo is in unknown state
280
+ throw AutoresearchError.create("REVERT_FAILED", `Failed to revert to ${state.acceptedCommit}. Manual intervention required.`, {
281
+ cause: error instanceof Error ? error : undefined,
282
+ });
283
+ }
284
+ }
285
+ // Only reach here if revert succeeded (or no acceptedCommit to revert to)
286
+ // currentPhase advancement happens unconditionally below via advancePhase("propose")
287
+ await this.stateStore.update({
288
+ runCount: state.runCount + 1,
289
+ lastStatus: status,
290
+ candidateCommit: null,
291
+ });
292
+ }
293
+ const durationMs = Date.now() - cycleStart;
294
+ // Set span attributes for the completed cycle
295
+ span.setAttribute(ATTR.AR_STATUS, status);
296
+ if (summary.metric !== null) {
297
+ span.setAttribute(ATTR.AR_METRIC, summary.metric);
298
+ }
299
+ span.setAttribute(ATTR.AR_COMMIT, commit);
300
+ span.setAttribute(ATTR.AR_DURATION_MS, durationMs);
301
+ logger.info("[Autoresearch] Experiment complete", {
302
+ status,
303
+ metric: summary.metric,
304
+ runCount: state.runCount + 1,
305
+ });
306
+ this.emit("autoresearch:experiment-completed", {
307
+ tag: state.tag,
308
+ runCount: state.runCount + 1,
309
+ status,
310
+ metric: summary.metric,
311
+ commit,
312
+ description,
313
+ durationMs,
314
+ });
315
+ // Emit state-updated with final state snapshot
316
+ const updatedState = await this.stateStore.load();
317
+ if (updatedState) {
318
+ this.emit("autoresearch:state-updated", {
319
+ tag: updatedState.tag,
320
+ phase: updatedState.currentPhase,
321
+ runCount: updatedState.runCount,
322
+ keepCount: updatedState.keepCount,
323
+ bestMetric: updatedState.bestMetric,
324
+ });
325
+ }
326
+ await this.advancePhase("propose");
327
+ return record;
328
+ });
329
+ }
330
+ /** Get the tools record for use with NeuroLink.generate() */
331
+ getTools() {
332
+ return createResearchTools({
333
+ config: this.config,
334
+ stateStore: this.stateStore,
335
+ repoPolicy: this.repoPolicy,
336
+ runner: this.runner,
337
+ recorder: this.recorder,
338
+ });
339
+ }
340
+ /** Build system prompt */
341
+ async getSystemPrompt() {
342
+ return this.promptCompiler.buildSystemPrompt();
343
+ }
344
+ /** Build cycle prompt */
345
+ async getCyclePrompt() {
346
+ const state = await this.stateStore.load();
347
+ if (!state) {
348
+ throw AutoresearchError.create("STATE_NOT_FOUND", "No state");
349
+ }
350
+ const results = await this.recorder.readAll();
351
+ return this.promptCompiler.buildCyclePrompt(state, results);
352
+ }
353
+ /** Get current state */
354
+ async getState() {
355
+ return this.stateStore.load();
356
+ }
357
+ /** Get results stats */
358
+ async getStats() {
359
+ return this.recorder.getStats();
360
+ }
361
+ /** Get config */
362
+ getConfig() {
363
+ return this.config;
364
+ }
365
+ // ── Phase management (Phase 1b/1c) ──────────────────────
366
+ /**
367
+ * Single authority for phase transitions.
368
+ * Persists the new phase to the state store and emits phase-changed event.
369
+ */
370
+ async advancePhase(phase) {
371
+ const currentState = await this.stateStore.load();
372
+ const fromPhase = currentState?.currentPhase ?? "bootstrap";
373
+ await this.stateStore.update({ currentPhase: phase });
374
+ logger.debug("[Autoresearch] Phase advanced", { phase });
375
+ if (fromPhase !== phase) {
376
+ this.emit("autoresearch:phase-changed", {
377
+ from: fromPhase,
378
+ to: phase,
379
+ runCount: currentState?.runCount ?? 0,
380
+ tag: currentState?.tag ?? "",
381
+ });
382
+ }
383
+ }
384
+ /**
385
+ * Returns the phase tool policy for the current phase.
386
+ * Reads the phase from persisted state.
387
+ */
388
+ async getPhaseToolPolicy() {
389
+ const state = await this.stateStore.load();
390
+ if (!state) {
391
+ throw AutoresearchError.create("STATE_NOT_FOUND", "No state for getPhaseToolPolicy");
392
+ }
393
+ return getPhaseToolPolicy(state.currentPhase);
394
+ }
395
+ /**
396
+ * Returns a tool filter object for the current phase, compatible
397
+ * with NeuroLink generate()'s toolFilter option.
398
+ *
399
+ * Returns { include: string[] } listing only the tools allowed
400
+ * in the current phase.
401
+ */
402
+ async getToolFilterForCurrentPhase() {
403
+ const policy = await this.getPhaseToolPolicy();
404
+ return { include: [...policy.activeTools] };
405
+ }
406
+ // ── Private helpers ──────────────────────────────────────
407
+ /** Emit an autoresearch:error event. */
408
+ emitError(tag, code, message, phase, runCount) {
409
+ this.emit("autoresearch:error", {
410
+ tag,
411
+ error: message,
412
+ code,
413
+ phase,
414
+ runCount,
415
+ });
416
+ }
417
+ }
418
+ //# sourceMappingURL=worker.js.map
@@ -776,6 +776,24 @@ export class BaseProvider {
776
776
  },
777
777
  responseTime: 0, // BaseProvider doesn't track response time directly
778
778
  toolsUsed: result.toolsUsed || [],
779
+ // Map toolExecutions from EnhancedGenerateResult shape to TextGenerationResult shape
780
+ // Preserve original timing/status fields when present, fall back to safe defaults
781
+ toolExecutions: result.toolExecutions?.map((te) => {
782
+ const t = te;
783
+ return {
784
+ // Spread original fields first so normalized fields take precedence
785
+ ...te,
786
+ toolName: te.name,
787
+ executionTime: typeof t.executionTime === "number"
788
+ ? t.executionTime
789
+ : typeof t.duration === "number"
790
+ ? t.duration
791
+ : 0,
792
+ success: typeof t.success === "boolean"
793
+ ? t.success
794
+ : t.status === undefined || t.status === "success",
795
+ };
796
+ }),
779
797
  enhancedWithTools: !!(result.toolsUsed && result.toolsUsed.length > 0),
780
798
  analytics: result.analytics,
781
799
  evaluation: result.evaluation,
@@ -69,7 +69,7 @@ export declare const evaluationErrors: {
69
69
  /** Rate limit hit during evaluation */
70
70
  readonly RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR";
71
71
  };
72
- create: (code: "PROVIDER_ERROR" | "CONFIGURATION_ERROR" | "EVALUATION_FAILED" | "PARSE_ERROR" | "STRATEGY_NOT_FOUND" | "CUSTOM_EVALUATOR_ERROR" | "BATCH_EVALUATION_ERROR" | "AGGREGATION_ERROR" | "REGISTRY_ERROR" | "MAX_RETRIES_EXCEEDED" | "TIMEOUT_ERROR" | "RATE_LIMIT_ERROR", message: string, options?: {
72
+ create: (code: "CONFIGURATION_ERROR" | "PROVIDER_ERROR" | "EVALUATION_FAILED" | "PARSE_ERROR" | "STRATEGY_NOT_FOUND" | "CUSTOM_EVALUATOR_ERROR" | "BATCH_EVALUATION_ERROR" | "AGGREGATION_ERROR" | "REGISTRY_ERROR" | "MAX_RETRIES_EXCEEDED" | "TIMEOUT_ERROR" | "RATE_LIMIT_ERROR", message: string, options?: {
73
73
  retryable?: boolean;
74
74
  details?: Record<string, unknown>;
75
75
  cause?: Error;
@@ -173,7 +173,7 @@ export declare function createFileTools(registry: FileReferenceRegistry): {
173
173
  } | undefined;
174
174
  columns?: string[] | undefined;
175
175
  entry_path?: string | undefined;
176
- format?: "detailed" | "text" | "summary" | undefined;
176
+ format?: "text" | "detailed" | "summary" | undefined;
177
177
  }, {
178
178
  success: false;
179
179
  error: string | undefined;
@@ -28,9 +28,9 @@ import { getContextOverflowProvider, isContextOverflowError, parseProviderOverfl
28
28
  import { ContextBudgetExceededError } from "./context/errors.js";
29
29
  import { repairToolPairs } from "./context/toolPairRepair.js";
30
30
  import { SYSTEM_LIMITS } from "./core/constants.js";
31
- import { createToolEventPayload } from "./core/toolEvents.js";
32
31
  import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
33
32
  import { AIProviderFactory } from "./core/factory.js";
33
+ import { createToolEventPayload } from "./core/toolEvents.js";
34
34
  import { ProviderRegistry } from "./factories/providerRegistry.js";
35
35
  import { FileReferenceRegistry } from "./files/fileReferenceRegistry.js";
36
36
  import { createFileTools } from "./files/fileTools.js";
@@ -2705,6 +2705,7 @@ Current user's request: ${currentInput}`;
2705
2705
  region: options.region,
2706
2706
  tts: options.tts,
2707
2707
  fileRegistry: this.fileRegistry,
2708
+ timeout: options.timeout,
2708
2709
  abortSignal: options.abortSignal,
2709
2710
  skipToolPromptInjection: options.skipToolPromptInjection,
2710
2711
  middleware: options.middleware,
@@ -4108,7 +4109,26 @@ Current user's request: ${currentInput}`;
4108
4109
  responseTime,
4109
4110
  finishReason: result.finishReason,
4110
4111
  toolsUsed: result.toolsUsed || [],
4111
- enhancedWithTools: false,
4112
+ // Map toolExecutions from EnhancedGenerateResult shape ({name,input,output})
4113
+ // to TextGenerationResult shape ({toolName,executionTime,success}).
4114
+ // Preserve original timing/status when present, fall back to safe defaults.
4115
+ toolExecutions: result.toolExecutions?.map((te) => {
4116
+ const t = te;
4117
+ return {
4118
+ // Spread original fields first so normalized fields take precedence
4119
+ ...te,
4120
+ toolName: te.name,
4121
+ executionTime: typeof t.executionTime === "number"
4122
+ ? t.executionTime
4123
+ : typeof t.duration === "number"
4124
+ ? t.duration
4125
+ : 0,
4126
+ success: typeof t.success === "boolean"
4127
+ ? t.success
4128
+ : t.status === "success",
4129
+ };
4130
+ }),
4131
+ enhancedWithTools: !!result.toolExecutions?.length,
4112
4132
  analytics: result.analytics,
4113
4133
  evaluation: result.evaluation,
4114
4134
  audio: result.audio,
@@ -13,7 +13,10 @@ export declare class AzureOpenAIProvider extends BaseProvider {
13
13
  getProviderName(): AIProviderName;
14
14
  getDefaultModel(): string;
15
15
  /**
16
- * Returns the Vercel AI SDK model instance for Azure OpenAI
16
+ * Returns the Vercel AI SDK model instance for Azure OpenAI.
17
+ * Uses .chat() explicitly because @ai-sdk/azure v3+ defaults the bare
18
+ * provider() call to the Responses API, which many Azure deployments
19
+ * do not support yet.
17
20
  */
18
21
  getAISDKModel(): LanguageModel;
19
22
  protected formatProviderError(error: unknown): Error;
@@ -43,11 +43,14 @@ export class AzureOpenAIProvider extends BaseProvider {
43
43
  validateApiKey(createAzureEndpointConfig());
44
44
  }
45
45
  // Create the Azure provider instance with proxy support
46
- // Let the Azure SDK handle all URL construction automatically
46
+ // useDeploymentBasedUrls is required because @ai-sdk/azure v3+ defaults to
47
+ // the /v1/ URL format, but most Azure deployments still require the legacy
48
+ // /deployments/{deployment}/ URL pattern.
47
49
  this.azureProvider = createAzure({
48
50
  resourceName: this.resourceName,
49
51
  apiKey: this.apiKey,
50
52
  apiVersion: this.apiVersion,
53
+ useDeploymentBasedUrls: true,
51
54
  fetch: createProxyFetch(),
52
55
  });
53
56
  logger.debug("Azure Vercel Provider initialized", {
@@ -63,10 +66,13 @@ export class AzureOpenAIProvider extends BaseProvider {
63
66
  return this.deployment;
64
67
  }
65
68
  /**
66
- * Returns the Vercel AI SDK model instance for Azure OpenAI
69
+ * Returns the Vercel AI SDK model instance for Azure OpenAI.
70
+ * Uses .chat() explicitly because @ai-sdk/azure v3+ defaults the bare
71
+ * provider() call to the Responses API, which many Azure deployments
72
+ * do not support yet.
67
73
  */
68
74
  getAISDKModel() {
69
- return this.azureProvider(this.deployment);
75
+ return this.azureProvider.chat(this.deployment);
70
76
  }
71
77
  formatProviderError(error) {
72
78
  if (error instanceof TimeoutError) {
@@ -1,6 +1,6 @@
1
1
  import { createOpenAI } from "@ai-sdk/openai";
2
2
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
3
- import { NoOutputGeneratedError, Output, streamText, } from "ai";
3
+ import { NoOutputGeneratedError, Output, stepCountIs, streamText, } from "ai";
4
4
  import { BaseProvider } from "../core/baseProvider.js";
5
5
  import { DEFAULT_MAX_STEPS } from "../core/constants.js";
6
6
  import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
@@ -165,7 +165,7 @@ export class LiteLLMProvider extends BaseProvider {
165
165
  Object.keys(tools).length > 0 && {
166
166
  tools,
167
167
  toolChoice: resolveToolChoice(options, tools, shouldUseTools),
168
- maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
168
+ stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
169
169
  }),
170
170
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
171
171
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
@@ -1,5 +1,5 @@
1
1
  import { createOpenRouter } from "@openrouter/ai-sdk-provider";
2
- import { NoOutputGeneratedError, Output, streamText, } from "ai";
2
+ import { NoOutputGeneratedError, Output, stepCountIs, streamText, } from "ai";
3
3
  import { AIProviderName } from "../constants/enums.js";
4
4
  import { BaseProvider } from "../core/baseProvider.js";
5
5
  import { DEFAULT_MAX_STEPS } from "../core/constants.js";
@@ -248,7 +248,7 @@ export class OpenRouterProvider extends BaseProvider {
248
248
  Object.keys(tools).length > 0 && {
249
249
  tools,
250
250
  toolChoice: resolveToolChoice(options, tools, shouldUseTools),
251
- maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
251
+ stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
252
252
  }),
253
253
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
254
254
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
@@ -1,6 +1,7 @@
1
1
  import { createOpenAI } from "@ai-sdk/openai";
2
- import { NoOutputGeneratedError, streamText, } from "ai";
2
+ import { NoOutputGeneratedError, stepCountIs, streamText, } from "ai";
3
3
  import { BaseProvider } from "../core/baseProvider.js";
4
+ import { DEFAULT_MAX_STEPS } from "../core/constants.js";
4
5
  import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
5
6
  import { createProxyFetch } from "../proxy/proxyFetch.js";
6
7
  import { logger } from "../utils/logger.js";
@@ -191,6 +192,7 @@ export class OpenAICompatibleProvider extends BaseProvider {
191
192
  : {}),
192
193
  tools,
193
194
  toolChoice: resolveToolChoice(options, tools, shouldUseTools),
195
+ stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
194
196
  abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
195
197
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
196
198
  onStepFinish: (event) => {
@@ -0,0 +1,8 @@
1
+ import type { Frame } from "./types.js";
2
+ export declare class FrameBus {
3
+ private handlers;
4
+ subscribe<T extends Frame["type"]>(type: T, fn: (frame: Extract<Frame, {
5
+ type: T;
6
+ }>) => void): void;
7
+ publish(frame: Frame): void;
8
+ }
@@ -0,0 +1,25 @@
1
+ import { logger } from "../../utils/logger.js";
2
+ export class FrameBus {
3
+ handlers = {};
4
+ subscribe(type, fn) {
5
+ if (!this.handlers[type]) {
6
+ this.handlers[type] = [];
7
+ }
8
+ this.handlers[type].push(fn);
9
+ }
10
+ publish(frame) {
11
+ const subs = this.handlers[frame.type];
12
+ if (!subs) {
13
+ return;
14
+ }
15
+ for (const fn of subs) {
16
+ try {
17
+ fn(frame);
18
+ }
19
+ catch (err) {
20
+ logger.error(`[FrameBus] Subscriber threw on ${frame.type}:`, err);
21
+ }
22
+ }
23
+ }
24
+ }
25
+ //# sourceMappingURL=frameBus.js.map
@@ -0,0 +1,15 @@
1
+ import type { FrameBus } from "./frameBus.js";
2
+ export declare enum TurnState {
3
+ IDLE = 0,
4
+ USER_SPEAKING = 1,
5
+ PROCESSING = 2,
6
+ ASSISTANT_SPEAKING = 3
7
+ }
8
+ export declare class TurnManager {
9
+ state: TurnState;
10
+ constructor(bus: FrameBus);
11
+ private onVadStart;
12
+ private onVadStop;
13
+ assistantSpeaking(): void;
14
+ reset(): void;
15
+ }