salmon-loop 0.2.13 → 0.2.16

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 (218) hide show
  1. package/dist/cli/argv/headless-detection.js +27 -0
  2. package/dist/cli/chat-flow.js +11 -0
  3. package/dist/cli/chat.js +160 -24
  4. package/dist/cli/commands/chat.js +14 -7
  5. package/dist/cli/commands/flow-mode.js +63 -0
  6. package/dist/cli/commands/registry.js +2 -0
  7. package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
  8. package/dist/cli/commands/run/early-errors.js +23 -0
  9. package/dist/cli/commands/run/handler.js +115 -27
  10. package/dist/cli/commands/run/headless-error-writer.js +8 -0
  11. package/dist/cli/commands/run/loop-params.js +2 -0
  12. package/dist/cli/commands/run/mode.js +2 -5
  13. package/dist/cli/commands/run/parse-options.js +16 -0
  14. package/dist/cli/commands/run/persist-session.js +10 -1
  15. package/dist/cli/commands/run/preflight.js +10 -0
  16. package/dist/cli/commands/run/reporter-factory.js +4 -0
  17. package/dist/cli/commands/run/runtime-llm.js +38 -11
  18. package/dist/cli/commands/run/runtime-options.js +2 -2
  19. package/dist/cli/commands/serve.js +91 -71
  20. package/dist/cli/commands/tool-names.js +78 -78
  21. package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
  22. package/dist/cli/headless/json-protocol.js +37 -0
  23. package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
  24. package/dist/cli/headless/protocol-metadata.js +22 -0
  25. package/dist/cli/headless/stream-json-protocol.js +34 -1
  26. package/dist/cli/index.js +6 -4
  27. package/dist/cli/locales/en.js +30 -6
  28. package/dist/cli/program-bootstrap.js +8 -3
  29. package/dist/cli/program-commands.js +5 -1
  30. package/dist/cli/reporters/anthropic-stream.js +7 -1
  31. package/dist/cli/reporters/json.js +4 -0
  32. package/dist/cli/reporters/stream-json.js +17 -2
  33. package/dist/cli/run-cli.js +5 -3
  34. package/dist/cli/slash/runtime.js +27 -12
  35. package/dist/cli/ui/components/CommandInput.js +7 -3
  36. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  37. package/dist/cli/utils/command-option-source.js +13 -0
  38. package/dist/cli/utils/verify-resolver.js +8 -4
  39. package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
  40. package/dist/core/adapters/fs/file-adapter.js +6 -0
  41. package/dist/core/adapters/fs/filesystem.js +2 -1
  42. package/dist/core/adapters/git/git-adapter.js +78 -1
  43. package/dist/core/benchmark/patch-artifact.js +124 -0
  44. package/dist/core/benchmark/swe-bench.js +25 -0
  45. package/dist/core/config/load.js +18 -11
  46. package/dist/core/config/resolve-llm.js +12 -0
  47. package/dist/core/config/resolvers/server.js +0 -6
  48. package/dist/core/config/validate.js +73 -21
  49. package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
  50. package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
  51. package/dist/core/context/keywords.js +18 -4
  52. package/dist/core/context/service-deps.js +2 -2
  53. package/dist/core/context/service.js +8 -0
  54. package/dist/core/context/steps/context-gather.js +38 -0
  55. package/dist/core/context/summarization/summarizer.js +55 -12
  56. package/dist/core/context/targeting/target-resolver.js +4 -4
  57. package/dist/core/extensions/index.js +23 -5
  58. package/dist/core/extensions/paths.js +31 -0
  59. package/dist/core/extensions/schemas.js +8 -5
  60. package/dist/core/facades/cli-chat.js +6 -2
  61. package/dist/core/facades/cli-command-chat.js +1 -0
  62. package/dist/core/facades/cli-command-tool-names.js +2 -0
  63. package/dist/core/facades/cli-observability.js +1 -1
  64. package/dist/core/facades/cli-run-handler.js +4 -2
  65. package/dist/core/facades/cli-run-persist-session.js +1 -0
  66. package/dist/core/facades/cli-serve.js +2 -4
  67. package/dist/core/facades/cli-utils-worktree.js +1 -1
  68. package/dist/core/failure/diagnostics.js +53 -1
  69. package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
  70. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
  71. package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
  72. package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
  73. package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
  74. package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
  75. package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
  76. package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
  77. package/dist/core/grizzco/steps/answer.js +13 -14
  78. package/dist/core/grizzco/steps/autopilot.js +396 -0
  79. package/dist/core/grizzco/steps/cache-sharing.js +29 -0
  80. package/dist/core/grizzco/steps/explore.js +37 -21
  81. package/dist/core/grizzco/steps/generateReview.js +2 -5
  82. package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
  83. package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
  84. package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
  85. package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
  86. package/dist/core/grizzco/steps/patch.js +105 -146
  87. package/dist/core/grizzco/steps/plan.js +101 -25
  88. package/dist/core/grizzco/steps/preflight.js +5 -6
  89. package/dist/core/grizzco/steps/request-assembly.js +78 -0
  90. package/dist/core/grizzco/steps/research.js +39 -36
  91. package/dist/core/grizzco/steps/tool-runtime.js +47 -0
  92. package/dist/core/grizzco/steps/verify-shared.js +23 -0
  93. package/dist/core/grizzco/steps/verify.js +13 -21
  94. package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
  95. package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
  96. package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
  97. package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
  98. package/dist/core/llm/ai-sdk/request-params.js +73 -0
  99. package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
  100. package/dist/core/llm/ai-sdk.js +112 -27
  101. package/dist/core/llm/capabilities.js +12 -0
  102. package/dist/core/llm/contracts/repair.js +36 -30
  103. package/dist/core/llm/errors.js +83 -2
  104. package/dist/core/llm/message-composition.js +7 -22
  105. package/dist/core/llm/phase-router.js +29 -10
  106. package/dist/core/llm/redact.js +28 -3
  107. package/dist/core/llm/registry.js +2 -0
  108. package/dist/core/llm/request-augmentation.js +55 -0
  109. package/dist/core/llm/request-envelope.js +334 -0
  110. package/dist/core/llm/shared-request-assembly.js +35 -0
  111. package/dist/core/llm/stream-utils.js +13 -4
  112. package/dist/core/llm/utils.js +18 -29
  113. package/dist/core/memory/relevant-retrieval.js +144 -0
  114. package/dist/core/observability/logger.js +11 -2
  115. package/dist/core/patch/diff.js +1 -0
  116. package/dist/core/prompts/registry.js +39 -2
  117. package/dist/core/prompts/runtime.js +50 -12
  118. package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
  119. package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
  120. package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
  121. package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
  122. package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
  123. package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
  124. package/dist/core/prompts/templates/system/main_system.hbs +4 -16
  125. package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
  126. package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
  127. package/dist/core/prompts/templates/system/research_system.hbs +2 -0
  128. package/dist/core/protocols/a2a/agent-card.js +3 -2
  129. package/dist/core/protocols/a2a/sdk/executor.js +2 -1
  130. package/dist/core/protocols/a2a/sdk/server.js +0 -1
  131. package/dist/core/protocols/acp/formal-agent.js +74 -51
  132. package/dist/core/protocols/acp/handlers.js +5 -1
  133. package/dist/core/protocols/acp/permission-provider.js +1 -1
  134. package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
  135. package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
  136. package/dist/core/public-capabilities/projections.js +29 -0
  137. package/dist/core/public-capabilities/registry.js +26 -0
  138. package/dist/core/public-capabilities/types.js +2 -0
  139. package/dist/core/runtime/agent-server-runtime.js +47 -43
  140. package/dist/core/runtime/execution-profile.js +67 -0
  141. package/dist/core/session/artifact-state.js +160 -0
  142. package/dist/core/session/compaction/index.js +183 -0
  143. package/dist/core/session/compaction/microcompact.js +78 -0
  144. package/dist/core/session/compaction/tracking.js +48 -0
  145. package/dist/core/session/compaction/types.js +11 -0
  146. package/dist/core/session/compression.js +8 -0
  147. package/dist/core/session/manager.js +244 -8
  148. package/dist/core/session/pruning-strategy.js +55 -9
  149. package/dist/core/session/replacement-preview-provider.js +24 -0
  150. package/dist/core/session/replacement-state.js +131 -0
  151. package/dist/core/session/resume-repair/pipeline.js +79 -0
  152. package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
  153. package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
  154. package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
  155. package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
  156. package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
  157. package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
  158. package/dist/core/session/resume-repair/types.js +2 -0
  159. package/dist/core/session/summary-sync.js +164 -13
  160. package/dist/core/session/token-tracker.js +6 -0
  161. package/dist/core/skills/audit.js +34 -0
  162. package/dist/core/skills/bridge.js +84 -7
  163. package/dist/core/skills/discovery.js +94 -0
  164. package/dist/core/skills/feature-flags.js +52 -0
  165. package/dist/core/skills/index.js +1 -1
  166. package/dist/core/skills/loader.js +195 -20
  167. package/dist/core/skills/parser.js +296 -24
  168. package/dist/core/skills/permissions.js +117 -0
  169. package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
  170. package/dist/core/skills/runtime/SkillRunner.js +240 -61
  171. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
  172. package/dist/core/strata/layers/worktree.js +67 -10
  173. package/dist/core/strata/runtime/synchronizer.js +29 -2
  174. package/dist/core/streaming/stream-assembler.js +75 -31
  175. package/dist/core/sub-agent/context-snapshot.js +156 -0
  176. package/dist/core/sub-agent/core/loop.js +1 -1
  177. package/dist/core/sub-agent/core/manager.js +119 -20
  178. package/dist/core/sub-agent/dispatch-policy.js +29 -0
  179. package/dist/core/sub-agent/prefix-consistency.js +48 -0
  180. package/dist/core/sub-agent/registry-defaults.js +4 -0
  181. package/dist/core/sub-agent/tools/task-spawn.js +79 -2
  182. package/dist/core/sub-agent/types.js +134 -5
  183. package/dist/core/tools/audit.js +13 -4
  184. package/dist/core/tools/builtin/ast-grep.js +1 -1
  185. package/dist/core/tools/builtin/ast.js +1 -1
  186. package/dist/core/tools/builtin/benchmark.js +360 -0
  187. package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
  188. package/dist/core/tools/builtin/code-search/executor.js +6 -1
  189. package/dist/core/tools/builtin/code-search/spec.js +26 -2
  190. package/dist/core/tools/builtin/fs.js +256 -23
  191. package/dist/core/tools/builtin/git.js +2 -2
  192. package/dist/core/tools/builtin/index.js +51 -2
  193. package/dist/core/tools/builtin/interaction.js +8 -1
  194. package/dist/core/tools/builtin/plan.js +37 -15
  195. package/dist/core/tools/builtin/shell.js +1 -1
  196. package/dist/core/tools/loader.js +39 -16
  197. package/dist/core/tools/mapper.js +17 -3
  198. package/dist/core/tools/parallel/scheduler.js +35 -4
  199. package/dist/core/tools/permissions/permission-rules.js +5 -10
  200. package/dist/core/tools/policy.js +6 -1
  201. package/dist/core/tools/recoverable-tool-errors.js +10 -0
  202. package/dist/core/tools/router.js +24 -6
  203. package/dist/core/tools/session.js +458 -48
  204. package/dist/core/tools/tool-visibility.js +62 -0
  205. package/dist/core/tools/types.js +9 -1
  206. package/dist/core/types/execution.js +4 -0
  207. package/dist/core/types/flow-mode.js +8 -0
  208. package/dist/core/utils/path.js +52 -0
  209. package/dist/core/verification/runner.js +4 -1
  210. package/dist/languages/typescript/index.js +4 -1
  211. package/dist/locales/en.js +35 -2
  212. package/dist/utils/eol.js +1 -1
  213. package/package.json +13 -6
  214. package/scripts/fix-es-abstract-compat.js +77 -0
  215. package/dist/core/runtime/fastify-server-bundle.js +0 -26
  216. package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
  217. package/dist/core/runtime/sidecar-paths.js +0 -47
  218. package/dist/core/runtime/sidecar-route-catalog.js +0 -103
@@ -1,59 +1,101 @@
1
1
  import { randomUUID } from 'crypto';
2
- import { LIMITS } from '../config/limits.js';
3
- import { getPatchPrompt, getPlanPrompt } from '../prompts/runtime.js';
4
2
  import { executeAiSdkChatRequest, executeAiSdkChatStreamRequest } from './ai-sdk/chat-executor.js';
3
+ import { HIGH_LEVEL_PHASE_SPECS, } from './ai-sdk/high-level-phase-specs.js';
5
4
  import { toAiSdkMessages, toAiSdkToolSet } from './ai-sdk/message-mapper.js';
6
5
  import { withAuditObservationName } from './ai-sdk/observation-context.js';
7
- import { createAiSdkChatModel, resolveAiSdkModelId } from './ai-sdk/provider-factory.js';
8
- import { wrapPlanEmpty, sanitizeError, LlmError } from './errors.js';
9
- import { extractUnifiedDiffFromLLMContent, formatContextForPrompt, parsePlanFromLLMContent, } from './utils.js';
6
+ import { createAiSdkChatModel, resolveAiSdkModelId, resolveAiSdkProviderOptionsKey, } from './ai-sdk/provider-factory.js';
7
+ import { repairToJsonObject } from './contracts/repair.js';
8
+ import { buildSharedRequestEnvelope } from './shared-request-assembly.js';
9
+ import { formatContextForPrompt } from './utils.js';
10
10
  export class AiSdkLLM {
11
11
  cfg;
12
12
  model;
13
13
  modelId;
14
+ providerOptionsKey;
14
15
  timeoutMs;
15
16
  constructor(cfg) {
16
17
  this.cfg = cfg;
17
18
  this.modelId = resolveAiSdkModelId(cfg.modelId);
19
+ this.providerOptionsKey = resolveAiSdkProviderOptionsKey(cfg);
18
20
  this.timeoutMs = cfg.timeoutMs;
19
21
  this.model = createAiSdkChatModel(cfg, this.modelId);
20
22
  }
21
23
  getModelId() {
22
24
  return this.modelId;
23
25
  }
24
- getCapabilities() {
26
+ getCapabilities(_options) {
25
27
  return {
26
28
  toolCalling: true,
27
29
  responseFormatJsonObject: true,
28
30
  streaming: true,
31
+ ...this.cfg.capabilities,
29
32
  };
30
33
  }
34
+ applyCapabilityOptions(options = {}) {
35
+ const capabilities = this.getCapabilities({ phase: options.phase });
36
+ const requestOptions = {
37
+ ...options,
38
+ responseFormatJsonObjectSupported: capabilities.responseFormatJsonObject !== false,
39
+ };
40
+ if (capabilities.toolCalling === false) {
41
+ requestOptions.tools = undefined;
42
+ requestOptions.toolSpecs = undefined;
43
+ requestOptions.toolChoice = 'none';
44
+ }
45
+ return requestOptions;
46
+ }
47
+ async *chatStreamFromChat(messages, options = {}) {
48
+ const response = await this.chat(messages, options);
49
+ if (response.reasoning_content) {
50
+ yield {
51
+ role: 'assistant',
52
+ source: 'synthesized',
53
+ reasoningDelta: response.reasoning_content,
54
+ };
55
+ }
56
+ if (response.content) {
57
+ yield { role: 'assistant', source: 'synthesized', contentDelta: response.content };
58
+ }
59
+ if (Array.isArray(response.tool_calls) && response.tool_calls.length > 0) {
60
+ yield { role: 'assistant', source: 'synthesized', tool_calls: response.tool_calls };
61
+ }
62
+ yield { role: 'assistant', source: 'synthesized', done: true, finishReason: 'stop' };
63
+ }
31
64
  async chat(messages, options = {}) {
32
65
  const aiMessages = toAiSdkMessages(messages);
33
- const tools = toAiSdkToolSet(options.tools, options.toolSpecs);
66
+ const requestOptions = this.applyCapabilityOptions(options);
67
+ const tools = toAiSdkToolSet(requestOptions.tools, requestOptions.toolSpecs);
34
68
  return executeAiSdkChatRequest({
35
69
  model: this.model,
36
70
  modelId: this.modelId,
71
+ providerOptionsKey: this.providerOptionsKey,
37
72
  timeoutMs: this.timeoutMs,
38
73
  langfuseEnabled: Boolean(this.cfg.langfuseEnabled),
39
74
  requestId: randomUUID(),
40
75
  messages: aiMessages,
41
76
  tools,
42
- options,
77
+ options: requestOptions,
43
78
  });
44
79
  }
45
80
  async *chatStream(messages, options = {}) {
81
+ const requestOptions = this.applyCapabilityOptions(options);
82
+ const capabilities = this.getCapabilities({ phase: requestOptions.phase });
83
+ if (capabilities.streaming === false) {
84
+ yield* this.chatStreamFromChat(messages, requestOptions);
85
+ return;
86
+ }
46
87
  const aiMessages = toAiSdkMessages(messages);
47
- const tools = toAiSdkToolSet(options.tools, options.toolSpecs);
88
+ const tools = toAiSdkToolSet(requestOptions.tools, requestOptions.toolSpecs);
48
89
  yield* executeAiSdkChatStreamRequest({
49
90
  model: this.model,
50
91
  modelId: this.modelId,
92
+ providerOptionsKey: this.providerOptionsKey,
51
93
  timeoutMs: this.timeoutMs,
52
94
  langfuseEnabled: Boolean(this.cfg.langfuseEnabled),
53
95
  requestId: randomUUID(),
54
96
  messages: aiMessages,
55
97
  tools,
56
- options,
98
+ options: requestOptions,
57
99
  });
58
100
  }
59
101
  /**
@@ -66,27 +108,70 @@ export class AiSdkLLM {
66
108
  yield* this.chatStream(messages, options);
67
109
  }
68
110
  async createPlan(context, instruction, lastError, signal) {
69
- const prompt = await getPlanPrompt(formatContextForPrompt(context), instruction, LIMITS.maxFilesChanged, lastError);
70
- const response = await withAuditObservationName('PLAN:plan-json', async () => this.chat([{ role: 'user', content: prompt }], { signal }));
71
- const content = response.content;
72
- if (!content) {
73
- throw wrapPlanEmpty();
111
+ return this.runHighLevelPhase(HIGH_LEVEL_PHASE_SPECS.plan, {
112
+ context,
113
+ instruction,
114
+ lastError,
115
+ signal,
116
+ });
117
+ }
118
+ async createPatch(context, plan, lastError, signal) {
119
+ const planStr = JSON.stringify(plan, null, 2);
120
+ return this.runHighLevelPhase(HIGH_LEVEL_PHASE_SPECS.patch, {
121
+ context,
122
+ planStr,
123
+ lastError,
124
+ signal,
125
+ });
126
+ }
127
+ async runHighLevelPhase(spec, input) {
128
+ const contextPrompt = formatContextForPrompt(input.context);
129
+ const userPrompt = await spec.buildPrompt({ ...input, contextPrompt });
130
+ const attachments = spec.buildAttachments({ ...input, contextPrompt });
131
+ const content = await this.executeHighLevelPrompt({
132
+ phase: spec.name,
133
+ defaultNamespace: spec.namespace,
134
+ contextHash: input.context.contextHash,
135
+ userPrompt,
136
+ attachments,
137
+ observationName: spec.observationName,
138
+ signal: input.signal,
139
+ });
140
+ return spec.parseResult(content);
141
+ }
142
+ async executeHighLevelPrompt(params) {
143
+ const sharedEnvelope = buildSharedRequestEnvelope({
144
+ defaultNamespace: params.defaultNamespace,
145
+ contextHash: params.contextHash,
146
+ systemPrompt: '',
147
+ userPrompt: params.userPrompt,
148
+ attachments: params.attachments,
149
+ });
150
+ const response = await withAuditObservationName(params.observationName, async () => this.chat(sharedEnvelope.baseMessages, {
151
+ providerHints: sharedEnvelope.envelope.providerHints,
152
+ responseFormat: params.phase === 'plan' ? 'json_object' : undefined,
153
+ signal: params.signal,
154
+ }));
155
+ if (params.phase !== 'plan') {
156
+ return response.content;
74
157
  }
75
158
  try {
76
- return parsePlanFromLLMContent(content);
159
+ HIGH_LEVEL_PHASE_SPECS.plan.parseResult(response.content);
160
+ return response.content;
77
161
  }
78
- catch (e) {
79
- throw new LlmError('LLM plan parsing failed', 'LLM_PLAN_INVALID_JSON', {
80
- causeMessage: sanitizeError(e),
81
- });
162
+ catch (error) {
163
+ const repair = await withAuditObservationName('PLAN:plan-json-repair', async () => repairToJsonObject({
164
+ llm: this,
165
+ baseMessages: sharedEnvelope.baseMessages,
166
+ chatOptions: {
167
+ providerHints: sharedEnvelope.envelope.providerHints,
168
+ signal: params.signal,
169
+ },
170
+ badContent: response.content ?? '',
171
+ reason: error instanceof Error ? error.message : String(error),
172
+ }));
173
+ return repair.content;
82
174
  }
83
175
  }
84
- async createPatch(context, plan, lastError, signal) {
85
- const planStr = JSON.stringify(plan, null, 2);
86
- const formattedContext = formatContextForPrompt(context);
87
- const prompt = await getPatchPrompt(planStr, formattedContext, LIMITS.maxFilesChanged, LIMITS.maxDiffLines, lastError);
88
- const response = await withAuditObservationName('PATCH:unified-diff', async () => this.chat([{ role: 'user', content: prompt }], { signal }));
89
- return extractUnifiedDiffFromLLMContent(response.content || '');
90
- }
91
176
  }
92
177
  //# sourceMappingURL=ai-sdk.js.map
@@ -0,0 +1,12 @@
1
+ export function resolveLlmCapabilities(llm, phase) {
2
+ return llm.getCapabilities?.({ phase }) ?? {};
3
+ }
4
+ export function supportsLlmStreaming(llm, phase) {
5
+ const capabilities = resolveLlmCapabilities(llm, phase);
6
+ if (capabilities.streaming === false)
7
+ return false;
8
+ if (capabilities.streaming === true)
9
+ return typeof llm.chatStream === 'function';
10
+ return typeof llm.chatStream === 'function';
11
+ }
12
+ //# sourceMappingURL=capabilities.js.map
@@ -13,10 +13,11 @@ export async function repairToJsonObject(args) {
13
13
  'Your previous response did not satisfy the contract.',
14
14
  `Reason: ${reason}`,
15
15
  '',
16
- 'Return ONLY a single JSON object.',
17
- '- No Markdown fences.',
18
- '- No commentary.',
19
- '- No leading/trailing text.',
16
+ 'Return exactly one JSON object and nothing else.',
17
+ 'The first non-whitespace character must be {.',
18
+ 'The last non-whitespace character must be }.',
19
+ '',
20
+ 'Forbidden: Markdown fences, commentary, labels, multiple objects, or any leading/trailing text.',
20
21
  '',
21
22
  'The JSON object MUST include keys: goal, files, changes, verify.',
22
23
  '',
@@ -38,31 +39,36 @@ export async function repairToJsonObject(args) {
38
39
  });
39
40
  }
40
41
  export async function repairToUnifiedDiff(args) {
41
- const { llm, baseMessages, chatOptions, badContent, reason } = args;
42
- const prompt = [
43
- 'Your previous response did not satisfy the contract.',
44
- `Reason: ${reason}`,
45
- '',
46
- 'Return ONLY a standard git unified diff patch.',
47
- '- It MUST start with `diff --git`.',
48
- '- No Markdown fences.',
49
- '- No commentary.',
50
- '- Exactly one final patch block (no multiple alternatives).',
51
- '',
52
- 'Previous response (truncated):',
53
- truncateForPrompt(badContent, Math.min(1200, Math.max(400, LIMITS.maxContextChars / 100))),
54
- ].join('\n');
55
- return llm.chat([
56
- ...baseMessages,
57
- { role: 'assistant', content: badContent || '' },
58
- { role: 'user', content: prompt },
59
- ], {
60
- ...chatOptions,
61
- responseFormat: 'text',
62
- tools: undefined,
63
- toolSpecs: undefined,
64
- toolChoice: undefined,
65
- temperature: 0,
66
- });
42
+ const { badContent } = args;
43
+ const extractCanonicalDiff = (input) => {
44
+ if (!input)
45
+ return '';
46
+ const fromText = (value) => {
47
+ const start = value.search(/^\s*diff --git /m);
48
+ if (start === -1)
49
+ return '';
50
+ const section = value.slice(start).trim();
51
+ const fenceClose = section.search(/\n```/);
52
+ if (fenceClose !== -1)
53
+ return section.slice(0, fenceClose).trim();
54
+ return section;
55
+ };
56
+ const fencedBlocks = [];
57
+ const fenceRegex = /```(?:diff)?\s*\n([\s\S]*?)\n```/gi;
58
+ let match = null;
59
+ while ((match = fenceRegex.exec(input)) !== null) {
60
+ const block = match[1];
61
+ const extracted = fromText(block);
62
+ if (extracted)
63
+ fencedBlocks.push(extracted);
64
+ }
65
+ if (fencedBlocks.length > 0)
66
+ return fencedBlocks[fencedBlocks.length - 1];
67
+ return fromText(input);
68
+ };
69
+ return {
70
+ role: 'assistant',
71
+ content: extractCanonicalDiff(badContent || ''),
72
+ };
67
73
  }
68
74
  //# sourceMappingURL=repair.js.map
@@ -26,6 +26,11 @@ function extractProviderDetails(err) {
26
26
  if (typeof candidate.statusCode === 'number') {
27
27
  details.statusCode = candidate.statusCode;
28
28
  }
29
+ // Align with AI SDK error shapes that store HTTP status in response.status
30
+ const response = candidate.response;
31
+ if (typeof details.statusCode !== 'number' && response && typeof response.status === 'number') {
32
+ details.statusCode = response.status;
33
+ }
29
34
  if (typeof candidate.responseBody === 'string') {
30
35
  // Apply sanitization to responseBody immediately after truncation
31
36
  details.responseBody = sanitizeError(truncate(candidate.responseBody));
@@ -75,6 +80,42 @@ function extractProviderDetails(err) {
75
80
  }
76
81
  return details;
77
82
  }
83
+ function extractNetworkCode(err) {
84
+ if (!err || typeof err !== 'object')
85
+ return undefined;
86
+ const candidate = err;
87
+ const direct = candidate.code;
88
+ if (typeof direct === 'string' && direct.trim())
89
+ return direct;
90
+ const cause = candidate.cause;
91
+ if (cause && typeof cause === 'object' && typeof cause.code === 'string') {
92
+ const code = String(cause.code);
93
+ return code.trim() ? code : undefined;
94
+ }
95
+ return undefined;
96
+ }
97
+ function isAuthenticationFailure(input) {
98
+ if (input.statusCode === 401)
99
+ return true;
100
+ const lower = `${input.message} ${input.providerMessage ?? ''} ${input.sanitizedMessage}`.toLowerCase();
101
+ const authHints = [
102
+ 'unauthorized',
103
+ 'forbidden',
104
+ 'authentication failed',
105
+ 'auth failed',
106
+ 'invalid api key',
107
+ 'invalid api-key',
108
+ 'access denied',
109
+ 'permission denied',
110
+ 'credential',
111
+ 'appidnoautherror',
112
+ 'noautherror',
113
+ ];
114
+ if (authHints.some((hint) => lower.includes(hint))) {
115
+ return true;
116
+ }
117
+ return input.statusCode === 403 && /auth|access|permission|credential|forbidden/i.test(lower);
118
+ }
78
119
  /**
79
120
  * Sanitizes an error message using the shared utility to prevent leakage
80
121
  * of sensitive technical data.
@@ -83,8 +124,16 @@ export function sanitizeError(err) {
83
124
  return sanitizeErrorMessage(err);
84
125
  }
85
126
  export function toLlmError(err, provider) {
86
- let name = err instanceof Error ? err.name : 'UnknownError';
87
- let message = err instanceof Error ? err.message : String(err);
127
+ let name = err instanceof Error
128
+ ? err.name
129
+ : typeof err?.name === 'string'
130
+ ? String(err.name)
131
+ : 'UnknownError';
132
+ let message = err instanceof Error
133
+ ? err.message
134
+ : typeof err?.message === 'string'
135
+ ? String(err.message)
136
+ : String(err);
88
137
  // Unwrap RetryError to get the last error's message if available
89
138
  if (name === 'AI_RetryError' || err?.lastError) {
90
139
  const lastError = err.lastError;
@@ -137,6 +186,38 @@ export function toLlmError(err, provider) {
137
186
  })) {
138
187
  return new LlmError('LLM context length exceeded', 'LLM_CONTEXT_LENGTH_EXCEEDED', meta);
139
188
  }
189
+ const lower = `${message} ${meta.providerMessage ?? ''} ${sanitizedMessage}`.toLowerCase();
190
+ const statusCode = meta.statusCode;
191
+ const networkCode = extractNetworkCode(err)?.toUpperCase();
192
+ if (isAuthenticationFailure({
193
+ statusCode,
194
+ message,
195
+ providerMessage: meta.providerMessage,
196
+ sanitizedMessage,
197
+ })) {
198
+ return new LlmError('LLM authentication failed', 'LLM_AUTHENTICATION_FAILED', meta);
199
+ }
200
+ if (statusCode === 429 || lower.includes('rate limit') || lower.includes('too many requests')) {
201
+ return new LlmError('LLM rate limited', 'LLM_RATE_LIMITED', meta);
202
+ }
203
+ if (statusCode === 408 || lower.includes('timeout') || networkCode === 'ETIMEDOUT') {
204
+ return new LlmError('LLM request timed out', 'LLM_REQUEST_TIMEOUT', meta);
205
+ }
206
+ if (typeof statusCode === 'number' && statusCode >= 500 && statusCode < 600) {
207
+ return new LlmError('LLM upstream server error', 'LLM_UPSTREAM_5XX', meta);
208
+ }
209
+ if (typeof networkCode === 'string') {
210
+ const unreachable = new Set([
211
+ 'ECONNRESET',
212
+ 'ETIMEDOUT',
213
+ 'EAI_AGAIN',
214
+ 'ENOTFOUND',
215
+ 'ECONNREFUSED',
216
+ ]);
217
+ if (unreachable.has(networkCode)) {
218
+ return new LlmError('LLM network request failed', 'LLM_NETWORK_UNREACHABLE', meta);
219
+ }
220
+ }
140
221
  return new LlmError('LLM request failed', 'LLM_HTTP_REQUEST_FAILED', meta);
141
222
  }
142
223
  function isContextLengthExceeded(input) {
@@ -1,25 +1,10 @@
1
- function toSafeConversationMessage(msg) {
2
- if (!msg || typeof msg !== 'object')
3
- return null;
4
- if (msg.role !== 'user' && msg.role !== 'assistant')
5
- return null;
6
- if (typeof msg.content !== 'string')
7
- return null;
8
- const content = msg.content.trimEnd();
9
- if (!content)
10
- return null;
11
- return { role: msg.role, content };
12
- }
1
+ import { buildSharedRequestEnvelope } from './shared-request-assembly.js';
13
2
  export function composeChatMessages(params) {
14
- const out = [{ role: 'system', content: String(params.system ?? '') }];
15
- if (Array.isArray(params.conversationContext)) {
16
- for (const msg of params.conversationContext) {
17
- const safe = toSafeConversationMessage(msg);
18
- if (safe)
19
- out.push(safe);
20
- }
21
- }
22
- out.push({ role: 'user', content: String(params.user ?? '') });
23
- return out;
3
+ return buildSharedRequestEnvelope({
4
+ defaultNamespace: 'chat',
5
+ systemPrompt: params.system,
6
+ userPrompt: params.user,
7
+ conversationContext: params.conversationContext,
8
+ }).baseMessages;
24
9
  }
25
10
  //# sourceMappingURL=message-composition.js.map
@@ -12,16 +12,9 @@ export function createPhaseRoutingLlm(params) {
12
12
  chat(messages, options) {
13
13
  return resolve(options?.phase).chat(messages, options);
14
14
  },
15
- getCapabilities() {
16
- const base = defaultLlm.getCapabilities?.() ?? {};
17
- const hasToolCalling = Object.values(phaseLlms).some((llm) => llm?.getCapabilities?.().toolCalling);
18
- const hasStreaming = Object.values(phaseLlms).some((llm) => llm?.getCapabilities?.().streaming);
19
- const hasJsonMode = Object.values(phaseLlms).some((llm) => llm?.getCapabilities?.().responseFormatJsonObject);
20
- return {
21
- toolCalling: base.toolCalling || hasToolCalling,
22
- streaming: base.streaming || hasStreaming,
23
- responseFormatJsonObject: base.responseFormatJsonObject || hasJsonMode,
24
- };
15
+ getCapabilities(options) {
16
+ const selected = resolve(options?.phase);
17
+ return selected.getCapabilities?.(options) ?? {};
25
18
  },
26
19
  createPlan(context, instruction, lastError, signal) {
27
20
  return resolve(Phase.PLAN).createPlan(context, instruction, lastError, signal);
@@ -33,11 +26,37 @@ export function createPhaseRoutingLlm(params) {
33
26
  if (hasAnyStreaming) {
34
27
  routed.chatStream = async function* (messages, options) {
35
28
  const selected = resolve(options?.phase);
29
+ const capabilities = selected.getCapabilities?.(options) ?? {};
30
+ if (capabilities.streaming === false) {
31
+ const fallback = await selected.chat(messages, options);
32
+ if (fallback.reasoning_content) {
33
+ yield {
34
+ role: 'assistant',
35
+ source: 'synthesized',
36
+ reasoningDelta: fallback.reasoning_content,
37
+ };
38
+ }
39
+ if (fallback.content) {
40
+ yield { role: 'assistant', source: 'synthesized', contentDelta: fallback.content };
41
+ }
42
+ if (Array.isArray(fallback.tool_calls) && fallback.tool_calls.length > 0) {
43
+ yield { role: 'assistant', source: 'synthesized', tool_calls: fallback.tool_calls };
44
+ }
45
+ yield { role: 'assistant', source: 'synthesized', done: true, finishReason: 'stop' };
46
+ return;
47
+ }
36
48
  if (selected.chatStream) {
37
49
  yield* selected.chatStream(messages, options);
38
50
  return;
39
51
  }
40
52
  const fallback = await selected.chat(messages, options);
53
+ if (fallback.reasoning_content) {
54
+ yield {
55
+ role: 'assistant',
56
+ source: 'synthesized',
57
+ reasoningDelta: fallback.reasoning_content,
58
+ };
59
+ }
41
60
  if (fallback.content) {
42
61
  yield { role: 'assistant', source: 'synthesized', contentDelta: fallback.content };
43
62
  }
@@ -1,4 +1,22 @@
1
1
  const SECRET_KEY_REGEX = /(api[-_]?key|authorization|token|secret|password|cookie)/i;
2
+ const STRING_SECRET_PATTERNS = [
3
+ {
4
+ pattern: /(authorization\s*:\s*bearer\s+)[^\s'",`]+/gi,
5
+ replacement: '$1[REDACTED]',
6
+ },
7
+ {
8
+ pattern: /\bbearer\s+[a-z0-9._~+/=-]{16,}\b/gi,
9
+ replacement: 'Bearer [REDACTED]',
10
+ },
11
+ {
12
+ pattern: /\bsk-[a-z0-9_-]{16,}\b/gi,
13
+ replacement: '[REDACTED]',
14
+ },
15
+ {
16
+ pattern: /\b(api[-_]?key|token|secret|password|cookie)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s'",`]+)/gi,
17
+ replacement: '$1=[REDACTED]',
18
+ },
19
+ ];
2
20
  function isRecord(value) {
3
21
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
4
22
  }
@@ -7,9 +25,16 @@ function truncate(value, max = 500) {
7
25
  return value;
8
26
  return value.slice(0, max) + '...';
9
27
  }
28
+ function redactString(value) {
29
+ let redacted = value;
30
+ for (const { pattern, replacement } of STRING_SECRET_PATTERNS) {
31
+ redacted = redacted.replace(pattern, replacement);
32
+ }
33
+ return truncate(redacted, 500);
34
+ }
10
35
  export function redactValue(value) {
11
36
  if (typeof value === 'string')
12
- return truncate(value, 500);
37
+ return redactString(value);
13
38
  if (typeof value === 'number' || typeof value === 'boolean' || value === null)
14
39
  return value;
15
40
  if (Array.isArray(value))
@@ -29,9 +54,9 @@ export function redactValue(value) {
29
54
  return '[Unserializable]';
30
55
  }
31
56
  export function redactJsonString(raw) {
32
- return truncate(raw, 500);
57
+ return redactString(raw);
33
58
  }
34
59
  export function redactErrorMessage(raw) {
35
- return truncate(raw, 500);
60
+ return redactString(raw);
36
61
  }
37
62
  //# sourceMappingURL=redact.js.map
@@ -39,6 +39,7 @@ export function createDefaultLlmRegistry() {
39
39
  headers: resolved.api.headers,
40
40
  timeoutMs: resolved.api.timeoutMs,
41
41
  langfuseEnabled: options?.langfuseEnabled,
42
+ capabilities: resolved.capabilities,
42
43
  }),
43
44
  backend: 'ai-sdk',
44
45
  warnings,
@@ -73,6 +74,7 @@ export function createDefaultOpenAiFallback(resolved, options) {
73
74
  headers: resolved.api.headers,
74
75
  timeoutMs: resolved.api.timeoutMs,
75
76
  langfuseEnabled: options?.langfuseEnabled,
77
+ capabilities: resolved.capabilities,
76
78
  }),
77
79
  backend: 'ai-sdk',
78
80
  warnings,
@@ -0,0 +1,55 @@
1
+ const MEMORY_HEADER = '[Relevant memory]';
2
+ function defaultCountTokens(text) {
3
+ return Math.ceil(text.length / 4);
4
+ }
5
+ function normalizeEntry(entry) {
6
+ return {
7
+ path: String(entry.path),
8
+ title: String(entry.title),
9
+ summary: String(entry.summary),
10
+ tags: entry.tags,
11
+ };
12
+ }
13
+ function formatRelevantMemoryEntry(entry) {
14
+ return `- ${entry.path} | ${entry.title}\n ${entry.summary}`;
15
+ }
16
+ export function augmentPromptWithRelevantMemory(args) {
17
+ const basePrompt = String(args.basePrompt ?? '').trimEnd();
18
+ const selectedEntries = Array.isArray(args.selectedEntries)
19
+ ? args.selectedEntries.map(normalizeEntry)
20
+ : [];
21
+ if (selectedEntries.length === 0) {
22
+ return { prompt: basePrompt, injectedEntries: [] };
23
+ }
24
+ const countTokens = args.countTokens ?? defaultCountTokens;
25
+ const budgetTokens = typeof args.budgetTokens === 'number'
26
+ ? Math.max(0, Math.floor(args.budgetTokens))
27
+ : Number.MAX_SAFE_INTEGER;
28
+ const headerTokens = Math.max(0, Math.floor(countTokens(MEMORY_HEADER)));
29
+ if (headerTokens > budgetTokens) {
30
+ return { prompt: basePrompt, injectedEntries: [] };
31
+ }
32
+ let remainingBudget = budgetTokens - headerTokens;
33
+ const injectedEntries = [];
34
+ const renderedEntries = [];
35
+ for (const entry of selectedEntries) {
36
+ const rendered = formatRelevantMemoryEntry(entry);
37
+ const entryTokens = Math.max(0, Math.floor(countTokens(rendered)));
38
+ if (entryTokens > remainingBudget) {
39
+ break;
40
+ }
41
+ injectedEntries.push(entry);
42
+ renderedEntries.push(rendered);
43
+ remainingBudget -= entryTokens;
44
+ }
45
+ if (renderedEntries.length === 0) {
46
+ return { prompt: basePrompt, injectedEntries: [] };
47
+ }
48
+ const memoryBlock = [MEMORY_HEADER, ...renderedEntries].join('\n');
49
+ return {
50
+ prompt: basePrompt ? `${basePrompt}\n\n${memoryBlock}` : memoryBlock,
51
+ injectedEntries,
52
+ memoryBlock,
53
+ };
54
+ }
55
+ //# sourceMappingURL=request-augmentation.js.map