diff --git a/src/agent/agents/subagents/tangentResearcher.ts b/src/agent/agents/subagents/tangentResearcher.ts index 43c902c6b..08ec7a963 100644 --- a/src/agent/agents/subagents/tangentResearcher.ts +++ b/src/agent/agents/subagents/tangentResearcher.ts @@ -14,7 +14,7 @@ */ import { Agent } from "@openai/agents"; -import { requireOrchestratorModel } from "../../config"; +import { getAgentModelConfig } from "../../config"; import { attachObservabilityHooks } from "../../middleware/observability"; import tangentResearcherPrompt from "../../prompts/tangentResearcher.md?raw"; import type { AgentSession, RecentPipelineRun } from "../../session"; @@ -44,7 +44,7 @@ export function createTangentResearcherAgent(session: AgentSession): Agent { hyperparameter-tuning and experiment ideas. Read-only — cannot edit the pipeline or submit runs.`, instructions, tools: [runTools.getRunStatus, csom.getPipelineState], - model: requireOrchestratorModel(), + ...getAgentModelConfig(session.aiConfig), modelSettings: { reasoning: { effort: "high" } }, }); attachObservabilityHooks(agent, session.emitStatus); diff --git a/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx b/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx index 0532b7f05..71a89824e 100644 --- a/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx +++ b/src/routes/v2/pages/RunView/components/RunDetailsContent.tsx @@ -13,14 +13,18 @@ import { CopyText } from "@/components/shared/CopyText/CopyText"; import PipelineIO from "@/components/shared/Execution/PipelineIO"; import { InfoBox } from "@/components/shared/InfoBox"; import { LoadingScreen } from "@/components/shared/LoadingScreen"; +import { useFlagValue } from "@/components/shared/Settings/useFlags"; import { StatusBar } from "@/components/shared/Status"; import { TagList } from "@/components/shared/Tags/TagList"; +import { Button } from "@/components/ui/button"; +import { Icon } from "@/components/ui/icon"; import { BlockStack, InlineStack } from "@/components/ui/layout"; import { Paragraph, Text } from "@/components/ui/typography"; import { useUserDetails } from "@/hooks/useUserDetails"; import type { ComponentSpec } from "@/models/componentSpec"; import { useBackend } from "@/providers/BackendProvider"; import { useExecutionData } from "@/providers/ExecutionDataProvider"; +import { useStartOptimizationChat } from "@/routes/v2/pages/RunView/hooks/useStartOptimizationChat"; import { useSpec } from "@/routes/v2/shared/providers/SpecContext"; import { PIPELINE_NOTES_ANNOTATION, @@ -121,6 +125,8 @@ function RunDetailsContentLoaded({ {spec.name ?? "Unnamed Pipeline"} + + {metadata && } {spec.description && ( @@ -155,6 +161,20 @@ function RunDetailsContentLoaded({ ); } +function OptimizationButton() { + const aiEnabled = useFlagValue("ai-assistant"); + const startOptimizationChat = useStartOptimizationChat(); + + if (!aiEnabled) return null; + + return ( + + ); +} + function RunInfoSection({ metadata }: { metadata: PipelineRunResponse }) { return ( { + if (!pendingPrompt || !thread) return; + const prompt = aiChat.consumePendingPrompt(); + if (prompt) handleSend(prompt); + }, [pendingPrompt, thread, aiChat]); + if (!isAiConfigured) { return ; } diff --git a/src/routes/v2/shared/components/AiChat/aiChatStore.ts b/src/routes/v2/shared/components/AiChat/aiChatStore.ts index b84be416a..69f5936ac 100644 --- a/src/routes/v2/shared/components/AiChat/aiChatStore.ts +++ b/src/routes/v2/shared/components/AiChat/aiChatStore.ts @@ -26,6 +26,12 @@ export interface AiChatStoreConfig { export class AiChatStore { @observable.shallow accessor threads: AgentThread[] = []; @observable accessor activeThreadId: string | null = null; + /** + * Prompt queued by a consumer (e.g. an external "ask the assistant" + * button) to be sent on the active thread. {@link AiChatContent} owns + * the tool bridge, so it observes this and dispatches the send. + */ + @observable accessor pendingPrompt: string | null = null; constructor(private readonly config: AiChatStoreConfig) { makeObservable(this); @@ -62,11 +68,30 @@ export class AiChatStore { return thread; } + /** + * Starts a fresh thread and queues a prompt to be sent on it. The + * dispatch happens in {@link AiChatContent}, which holds the tool + * bridge required to fulfil tool calls. + */ + @action startThreadWithPrompt(prompt: string): AgentThread { + const thread = this.newThread(); + this.pendingPrompt = prompt; + return thread; + } + + /** Returns and clears the queued prompt, guarding against double-sends. */ + @action consumePendingPrompt(): string | null { + const prompt = this.pendingPrompt; + this.pendingPrompt = null; + return prompt; + } + @action disposeAll() { for (const thread of this.threads) { thread.dispose(); } this.threads = []; this.activeThreadId = null; + this.pendingPrompt = null; } }