Line data Source code
1 : import * as React from 'react';
2 :
3 : import type { AgentBridge } from './agent_bridge.js';
4 : import { AgentConnectionStatus } from './agent_client.js';
5 : import type { ScriptData, SessionScripts } from '../session/scripts/session_scripts.js';
6 : import { scriptDisplayName } from '../session/scripts/script_types.js';
7 : import { isAgentAnswering, useAgentQuestions, type AgentFollowTarget } from './use_agent_questions.js';
8 : import { useAgentSessionContext, type ComposerAgent, type ComposerAgentScript, type ComposerAgentTurn } from './agent_prompt_panel.js';
9 :
10 : /// The session of the composer and what the feed does for the agent
11 : export interface ComposerAgentArgs {
12 : /// The agent bridge, null outside of the provider
13 : agent: AgentBridge | null;
14 : /// The scripts of the session
15 : sessionScripts: SessionScripts;
16 : /// Runs a script that the agent wrote
17 : runScript: (next: SessionScripts, scriptData: ScriptData) => void;
18 : /// Follows the agent to a script, every time the agent edits or runs it
19 : followScript: (scriptKey: number, target: AgentFollowTarget) => void;
20 : /// Shows a script that the user picked in a question
21 : showScript: (scriptKey: number) => void;
22 : }
23 :
24 : /// The agent of the composer
25 : export interface ComposerAgentState {
26 : /// The agent on top of the composer, null without agent access
27 : agent: ComposerAgent | null;
28 : /// Can the composer ask the agent?
29 : available: boolean;
30 : /// Ask the agent a question
31 : ask: (question: string) => Promise<void>;
32 : }
33 :
34 : /// The agent of a session in the composer: its harness, its status lines and its answers.
35 : /// The scripts of the feed only show the edits of the agent.
36 1 : export function useComposerAgent(args: ComposerAgentArgs): ComposerAgentState {
37 1 : const { agent, sessionScripts, showScript } = args;
38 1 : const sessionId = sessionScripts.sessionId;
39 1 : const questions = useAgentQuestions({
40 1 : agent,
41 1 : sessionScripts,
42 1 : runScript: args.runScript,
43 1 : followScript: args.followScript,
44 1 : });
45 :
46 : // The harness context changes with every finished question
47 1 : let workingCount = 0;
48 1 : for (const question of questions.questions.values()) {
49 1 : if (isAgentAnswering(question)) ++workingCount;
50 : }
51 1 : const context = useAgentSessionContext(agent, sessionId, workingCount);
52 :
53 1 : const access = agent != null && agent.supported && agent.hasAccess(sessionId);
54 1 : const composerAgent = React.useMemo<ComposerAgent | null>(() => {
55 1 : if (agent == null || !access) return null;
56 1 : const turns: ComposerAgentTurn[] = [];
57 1 : for (const [questionId, question] of questions.questions) {
58 : // Deleted scripts drop out
59 1 : const scripts: ComposerAgentScript[] = [];
60 1 : for (const scriptKey of question.scripts) {
61 1 : const scriptData = sessionScripts.scripts[scriptKey];
62 1 : if (scriptData != null) scripts.push({ scriptKey, scriptName: scriptDisplayName(scriptData.fileName) });
63 1 : }
64 1 : turns.push({ questionId, question, scripts });
65 1 : }
66 1 : return {
67 1 : connected: agent.status === AgentConnectionStatus.Connected,
68 1 : port: agent.port,
69 1 : context,
70 1 : turns,
71 1 : onStop: questions.stop,
72 1 : onDismiss: questions.dismiss,
73 1 : onShowScript: showScript,
74 1 : };
75 1 : }, [agent, access, context, questions.questions, questions.stop, questions.dismiss, sessionScripts.scripts, showScript]);
76 1 : return { agent: composerAgent, available: questions.available, ask: questions.ask };
77 1 : }
|