+
bolt
Click Generate to see a response
diff --git a/frontend/src/app/modules/prompts/form/prompt-form.component.ts b/frontend/src/app/modules/prompts/form/prompt-form.component.ts
index 5741e69a..a43c15a7 100644
--- a/frontend/src/app/modules/prompts/form/prompt-form.component.ts
+++ b/frontend/src/app/modules/prompts/form/prompt-form.component.ts
@@ -31,7 +31,18 @@ import { MatSliderModule } from '@angular/material/slider';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
-import type { CallSettings, FilePartExt, ImagePartExt, LlmInfo, LlmMessage, TextPart, UserContentExt, AssistantContentExt } from '#shared/llm/llm.model';
+import type {
+ AssistantContentExt,
+ CallSettings,
+ FilePartExt,
+ ImagePartExt,
+ LlmInfo,
+ LlmMessage,
+ ReasoningPart,
+ TextPart,
+ ToolCallPartExt,
+ UserContentExt,
+} from '#shared/llm/llm.model';
import type { Prompt } from '#shared/prompts/prompts.model';
import { type PromptCreatePayload, PromptGenerateResponseSchemaModel, type PromptSchemaModel, type PromptUpdatePayload } from '#shared/prompts/prompts.schema';
import { LlmService } from '../../llm.service';
@@ -98,7 +109,7 @@ export class PromptFormComponent implements OnInit, OnDestroy {
isLoading = signal(true);
isSaving = signal(false);
isGenerating = signal(false);
- generationResponse = signal
(null);
+ generationResult = signal(null);
generationError = signal(null);
private destroy$ = new Subject();
private llmsState$ = toObservable(this.llmService.llmsState);
@@ -418,13 +429,13 @@ export class PromptFormComponent implements OnInit, OnDestroy {
this.cdr.detectChanges();
}
- private _convertLlmContentToString(content: UserContentExt | AssistantContentExt | undefined): string {
+ private _convertLlmContentToString(content: LlmMessage['content'] | undefined): string {
if (typeof content === 'string') {
return content;
}
if (Array.isArray(content)) {
return content
- .map((part) => {
+ .map((part: any) => {
if (part.type === 'text') {
return (part as TextPart).text;
}
@@ -436,6 +447,19 @@ export class PromptFormComponent implements OnInit, OnDestroy {
const filePart = part as FilePartExt;
return `[File: ${filePart.filename || filePart.mediaType || 'file'}]`;
}
+ if (part.type === 'reasoning') {
+ return (part as ReasoningPart).text;
+ }
+ if (part.type === 'redacted-reasoning') {
+ return '[Redacted Reasoning]';
+ }
+ if (part.type === 'tool-call') {
+ const toolCallPart = part as ToolCallPartExt;
+ return `[Tool Call: ${toolCallPart.toolName}(${JSON.stringify(toolCallPart.input)})]`;
+ }
+ if (part.type === 'tool-result') {
+ return `[Tool Result: ${JSON.stringify(part.output)}]`;
+ }
// Fallback for any other part types that might appear in UserContentExt if extended
// Safely access .type, provide a default if it's not a known structure
const partType = (part as any)?.type || 'unknown';
@@ -630,6 +654,7 @@ export class PromptFormComponent implements OnInit, OnDestroy {
const generationOptions: CallSettings & { llmId?: string } = formValue.options;
+ this.generationResult.set(null);
this.isGenerating.set(true);
this.generationError.set(null);
@@ -644,7 +669,7 @@ export class PromptFormComponent implements OnInit, OnDestroy {
)
.subscribe({
next: (response) => {
- this.generationResponse.set(response.generatedMessage.content as AssistantContentExt);
+ this.generationResult.set(response);
this.cdr.detectChanges();
},
error: (error) => {
@@ -655,7 +680,7 @@ export class PromptFormComponent implements OnInit, OnDestroy {
}
addResponseToPrompt(): void {
- const responseContent = this.generationResponse();
+ const responseContent = this.generationResult()?.generatedMessage.content;
if (!responseContent) {
console.warn('No generated response to add');
return;
@@ -668,7 +693,7 @@ export class PromptFormComponent implements OnInit, OnDestroy {
messageGroup.get('fullContent')?.setValue(responseContent);
this.messagesFormArray.push(messageGroup);
- this.generationResponse.set(null);
+ this.generationResult.set(null);
this.generationError.set(null);
this.cdr.detectChanges();
@@ -826,4 +851,18 @@ export class PromptFormComponent implements OnInit, OnDestroy {
}
}
}
+
+ public getReasoningPart(content: AssistantContentExt): ReasoningPart | undefined {
+ if (Array.isArray(content)) {
+ return content.find((part) => part.type === 'reasoning') as ReasoningPart | undefined;
+ }
+ return undefined;
+ }
+
+ public getNonReasoningParts(content: AssistantContentExt): AssistantContentExt {
+ if (Array.isArray(content)) {
+ return content.filter((part) => part.type !== 'reasoning');
+ }
+ return content;
+ }
}
diff --git a/package.json b/package.json
index b6cc87ad..5f9f7959 100644
--- a/package.json
+++ b/package.json
@@ -44,16 +44,16 @@
"start": " node -r ts-node/register --env-file=variables/.env src/index.ts",
"start:local": "node -r ts-node/register --env-file=variables/local.env --inspect=0.0.0.0:9229 src/index.ts",
"emulators": "gcloud emulators firestore start --host-port=127.0.0.1:8243",
- "test": " npm run test:unit && npm run test:db",
+ "test": " pnpm run test:unit && pnpm run test:db",
"test:unit": " node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r esbuild-register -r \"./src/test/testSetup.ts\" \"src/**/*.test.[jt]s\" --exclude \"src/modules/{firestore,mongo,postgres}/*.test.ts\" --timeout 10000",
"test:firestore": "node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r esbuild-register -r \"./src/test/testSetup.ts\" \"src/modules/firestore/*.test.ts\" --timeout 10000",
"test:postgres": " node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r esbuild-register -r \"./src/test/testSetup.ts\" \"src/modules/postgres/*.test.ts\" --timeout 10000",
"test:mongo": " node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r esbuild-register -r \"./src/test/testSetup.ts\" \"src/modules/mongo/*.test.ts\" --timeout 10000",
"test:db": " node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r esbuild-register -r \"./src/test/testSetup.ts\" \"src/modules/{firestore,mongo,postgres}/*.test.ts\" --timeout 10000",
"test:single": " node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r esbuild-register -r \"./src/test/testSetup.ts\" --timeout 10000 --exit",
- "test:ci:firestore": "firebase emulators:exec --only firestore \"npm run test:firestore\"",
- "test:ci:postgres": " npm run test:postgres",
- "test:ci:mongo": " npm run test:mongo",
+ "test:ci:firestore": "firebase emulators:exec --only firestore \"pnpm run test:firestore\"",
+ "test:ci:postgres": " pnpm run test:postgres",
+ "test:ci:mongo": " pnpm run test:mongo",
"test:integration": "node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r ts-node/register -r \"./src/test/testSetup.ts\" \"src/**/*.int.[jt]s\" --timeout 0 --exit",
"test:system": " node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r ts-node/register \"src/**/*.sys.ts\" --timeout 0 --exit",
"test:unit:dev": "TS_NODE_PROJECT='./tsconfig.swc.json' node --env-file=variables/test.env ./node_modules/mocha/bin/mocha -r ts-node/register -t 0 --exit --colors \"src/**/*.test.[jt]s\"",
diff --git a/src/agent/agentSerialization.ts b/src/agent/agentSerialization.ts
index 5aa84b3a..b85de063 100644
--- a/src/agent/agentSerialization.ts
+++ b/src/agent/agentSerialization.ts
@@ -34,7 +34,7 @@ export function serializeContext(context: AgentContext): AgentContextApi {
codeTaskId: context.codeTaskId,
state: context.state ?? 'error',
callStack: context.callStack ?? [],
- error: context.error ?? undefined,
+ error: context.error || undefined,
output: context.output,
hilBudget: context.hilBudget ?? 0,
cost: context.cost ?? 0,
diff --git a/src/agent/autonomous/functions/agentFunctions.ts b/src/agent/autonomous/functions/agentFunctions.ts
index 277ed6b3..bf4c5b37 100644
--- a/src/agent/autonomous/functions/agentFunctions.ts
+++ b/src/agent/autonomous/functions/agentFunctions.ts
@@ -17,7 +17,7 @@ export const AGENT_COMPLETED_PARAM_NAME = 'note';
export class Agent {
/**
* Notifies that the user request has completed and there is no more work to be done, or that no more useful progress can be made with the functions.
- * @param {string} note A detailed description that answers/completes the user request.
+ * @param {string} note A detailed description that answers/completes the user request using Markdown formatting.
*/
@func()
async completed(note: string): Promise {
@@ -60,7 +60,7 @@ export class Agent {
async getMemory(key: string): Promise {
if (!key) throw new Error(`Parameter "key" must be provided. Was ${key}`);
const memory = agentContext().memory;
- if (!memory[key]) throw new Error(`Memory key ${key} does not exist`);
+ if (!memory[key]) throw new Error(`Memory key ${key} does not exist. Valid keys are ${Object.keys(memory).join(', ')}`);
return memory[key];
}
diff --git a/src/cli/functionResolver.ts b/src/cli/functionResolver.ts
index 4b7a3213..3052b9f3 100644
--- a/src/cli/functionResolver.ts
+++ b/src/cli/functionResolver.ts
@@ -25,6 +25,8 @@ import { functionRegistry } from '../functionRegistry';
const functionAliases: Record = {
f: AgentFeedback.name,
swe: SoftwareDeveloperAgent.name,
+ bash: CommandLineInterface.name,
+ shell: CommandLineInterface.name,
cli: CommandLineInterface.name,
code: CodeEditingAgent.name,
query: CodeFunctions.name,
diff --git a/src/cli/terminal.ts b/src/cli/terminal.ts
new file mode 100644
index 00000000..5872f776
--- /dev/null
+++ b/src/cli/terminal.ts
@@ -0,0 +1,31 @@
+// file: write-terminal.mjs
+import { createWriteStream } from 'node:fs';
+import { platform } from 'node:process';
+
+/**
+ * Writes a message directly to the controlling terminal, bypassing stdout/stderr redirection.
+ * This is useful for progress indicators, password prompts, or critical alerts
+ * that should always be visible to the user, even if they pipe the script's output to a file.
+ *
+ * @param {string} message The message to write to the terminal.
+ */
+export function terminalLog(message: string): void {
+ // Determine the correct path to the terminal device based on the OS.
+ const terminalPath = platform === 'win32' ? '\\\\.\\CON' : '/dev/tty';
+
+ try {
+ // Create a writable stream to the terminal device.
+ // This will fail if the process is not running in an interactive terminal
+ // (e.g., in a CI/CD pipeline, a cron job, or a non-interactive SSH session).
+ const terminalStream = createWriteStream(terminalPath, {
+ flags: 'a', // 'a' for append mode is safest
+ });
+
+ terminalStream.write(`${message}\n`);
+ terminalStream.end(); // Close the stream to release the file handle.
+ } catch (error) {
+ // If we can't write to the terminal (e.g., not in a TTY),
+ // we can fall back to stderr as a last resort for visibility.
+ // console.error(`Fallback: Could not write directly to terminal. Message: ${message}`);
+ }
+}
diff --git a/src/functions/commandLine.ts b/src/functions/commandLine.ts
index a4af89bd..bf79a82b 100644
--- a/src/functions/commandLine.ts
+++ b/src/functions/commandLine.ts
@@ -18,14 +18,17 @@ export class CommandLineInterface {
Current directory: ${fss.getWorkingDirectory()}
Git repo folder: ${fss.getVcsRoot() ?? ''}
`;
- const response = await llms().medium.generateText([
- system(
- 'You are to analyze the provided shell command to determine if it is safe to run, i.e. will not cause configuration changes, data loss or other unintended consequences to the host system or remote systems. Reading files/config and modifying files under version control is acceptable.',
- ),
- user(
- `The command which is being requested to execute is:\n${command}\n\n\n Think through the dangers of running this command and response with only a single word, either SAFE, UNSURE or DANGEROUS`,
- ),
- ]);
+ const response = await llms().medium.generateText(
+ [
+ system(
+ 'You are to analyze the provided shell command to determine if it is safe to run, i.e. will not cause configuration changes, data loss or other unintended consequences to the host system or remote systems. Reading files/config and modifying files under version control is acceptable.',
+ ),
+ user(
+ `The command which is being requested to execute is:\n${command}\n\n\n Think through the dangers of running this command and response with only a single word, either SAFE, UNSURE or DANGEROUS`,
+ ),
+ ],
+ { id: 'CLI command safety analysis' },
+ );
if (!response.includes('SAFE')) {
await humanInTheLoop(
agentContext(),
diff --git a/src/llm/services/ai-llm.ts b/src/llm/services/ai-llm.ts
index 6e2a8d15..03af80d5 100644
--- a/src/llm/services/ai-llm.ts
+++ b/src/llm/services/ai-llm.ts
@@ -151,6 +151,14 @@ export abstract class AiLLM extends BaseLLM {
// Fallback for unknown parts, though ideally all are handled
return part as any;
}) as Exclude;
+
+ // If there are multiple text parts, then concatenate them as some providers don't handle multiple text parts
+ const textParts = processedContent.filter((part) => part.type === 'text');
+ if (textParts.length > 1) {
+ const nonTextParts = processedContent.filter((part) => part.type !== 'text');
+ const text = textParts.map((part) => part.text).join('\n');
+ processedContent = [{ type: 'text', text }, ...nonTextParts] as CoreContent;
+ }
}
return { ...restOfMsg, content: processedContent } as CoreMessage;
});
diff --git a/src/llm/services/cerebras.ts b/src/llm/services/cerebras.ts
index 80b680e1..392f35fc 100644
--- a/src/llm/services/cerebras.ts
+++ b/src/llm/services/cerebras.ts
@@ -12,8 +12,8 @@ export const CEREBRAS_SERVICE = 'cerebras';
export function cerebrasLLMRegistry(): Record LLM> {
return {
'cerebras:qwen-3-32b': () => cerebrasQwen3_32b(),
- 'cerebras:qwen-3-235b-instruct-2507': () => cerebrasQwen3_235b_Instruct(),
- 'cerebras:qwen-3-235b-thinking-2507': () => cerebrasQwen3_235b_Thinking(),
+ 'cerebras:qwen-3-235b-a22b-instruct-2507': () => cerebrasQwen3_235b_Instruct(),
+ 'cerebras:qwen-3-235b-a22b-thinking-2507': () => cerebrasQwen3_235b_Thinking(),
'cerebras:qwen-3-coder-480b': () => cerebrasQwen3_Coder(),
'cerebras:llama-4-maverick-17b-128e-instruct': () => cerebrasLlamaMaverick(),
};
diff --git a/src/llm/services/defaultLlms.ts b/src/llm/services/defaultLlms.ts
index 09954fe0..bae771bf 100644
--- a/src/llm/services/defaultLlms.ts
+++ b/src/llm/services/defaultLlms.ts
@@ -11,7 +11,7 @@ import { cerebrasQwen3_235b_Thinking } from './cerebras';
import { Gemini_2_5_Flash, Gemini_2_5_Pro } from './gemini';
import { groqLlama4_Scout } from './groq';
import { Ollama_LLMs } from './ollama';
-import { openaiGPT5, openaiGPT5mini } from './openai';
+import { openaiGPT5, openaiGPT5mini, openaiGPT5nano } from './openai';
import { xai_Grok4 } from './xai';
let _summaryLLM: LLM;
@@ -31,11 +31,11 @@ export function defaultLLMs(): AgentLLMs {
// return _defaultLLMs;
// }
- const easyLLMs = [new FastEasyLLM(), vertexGemini_2_5_Flash(), Gemini_2_5_Flash(), groqLlama4_Scout(), openaiGPT5mini(), Claude3_5_Haiku()];
+ const easyLLMs = [new FastEasyLLM(), vertexGemini_2_5_Flash(), Gemini_2_5_Flash(), groqLlama4_Scout(), openaiGPT5nano(), Claude3_5_Haiku()];
const easy: LLM | undefined = easyLLMs.find((llm) => llm.isConfigured());
if (!easy) throw new Error('No default easy LLM configured');
- const mediumLLMs = [new FastMediumLLM(), vertexGemini_2_5_Flash(), Gemini_2_5_Flash(), cerebrasQwen3_235b_Thinking(), openaiGPT5(), Claude3_5_Haiku()];
+ const mediumLLMs = [new FastMediumLLM(), vertexGemini_2_5_Flash(), Gemini_2_5_Flash(), cerebrasQwen3_235b_Thinking(), openaiGPT5mini(), Claude3_5_Haiku()];
const medium: LLM | undefined = mediumLLMs.find((llm) => llm.isConfigured());
if (!medium) throw new Error('No default medium LLM configured');
diff --git a/src/routes/agent/getAgentDetailsRoute.ts b/src/routes/agent/getAgentDetailsRoute.ts
index b9704c13..1d4eee27 100644
--- a/src/routes/agent/getAgentDetailsRoute.ts
+++ b/src/routes/agent/getAgentDetailsRoute.ts
@@ -1,6 +1,6 @@
import { serializeContext } from '#agent/agentSerialization';
import type { AppFastifyInstance } from '#app/applicationTypes';
-import { send, sendNotFound } from '#fastify/index';
+import { send, sendBadRequest, sendNotFound } from '#fastify/index';
import { logger } from '#o11y/logger';
import { AGENT_API } from '#shared/agent/agent.api';
import type { AgentContext } from '#shared/agent/agent.model';
@@ -13,16 +13,13 @@ export async function getAgentDetailsRoute(fastify: AppFastifyInstance): Promise
const { agentId } = req.params;
try {
const agentContext: AgentContext = await fastify.agentStateService.load(agentId);
- // No need for: if (!agentContext) { ... } as load now throws
+ if (!agentContext) return sendBadRequest(reply, `Agent ${agentId} not found`);
+
const response: AgentContextApi = serializeContext(agentContext);
reply.sendJSON(response);
} catch (error) {
- if (error instanceof NotFound) {
- return sendNotFound(reply, error.message);
- }
- if (error instanceof NotAllowed) {
- return send(reply, 403, { error: error.message });
- }
+ if (error instanceof NotFound) return sendNotFound(reply, error.message);
+ if (error instanceof NotAllowed) return send(reply, 403, { error: error.message });
logger.error(error, `Error loading details for agent ${agentId} [error]`);
return send(reply, 500, { error: 'Failed to load agent details' });
}
diff --git a/src/swe/projectDetection.ts b/src/swe/projectDetection.ts
index f94945ea..3c190d15 100644
--- a/src/swe/projectDetection.ts
+++ b/src/swe/projectDetection.ts
@@ -275,7 +275,6 @@ export async function getProjectInfos(autoDetect = true): Promise {
- console.log(new Error(`getProjectInfo ${autoDetect}`));
const infos = await getProjectInfos(autoDetect); // This is now the robust version
if (!infos || infos.length === 0) {