Skip to content

Commit 108d065

Browse files
committed
Wire structured AI edits and simplify providers
1 parent 9af574d commit 108d065

35 files changed

Lines changed: 1024 additions & 540 deletions

‎AGENTS.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ Rule format:
162162
- For task-scoped work, edit, stage, and commit only the files directly required for the requested change; do not widen the change set into unrelated user-owned or parallel worktree edits, do not touch changes owned by another agent, and if a blocker comes from that parallel work, wait briefly and re-check instead of patching around their in-flight fix.
163163
- Core AI infrastructure should stay minimal and predefined: prefer one factory that assembles known agents and known workflows, and avoid extra catalog or registry layers for skills, agents, or workflows unless a later task explicitly requires dynamic discovery.
164164
- All AI provider connections must stay on the Microsoft Agent Framework composition path used by PrompterOne agents and `ChatClientAgent`; do not bypass it with direct provider-specific SDK clients for production graph, assistant, or runtime AI behavior, because Agent Framework is the single configuration, provider selection, and orchestration boundary for every AI integration. Provider SDK objects may exist only as private adapters that feed the Agent Framework path; production calls must not execute through those SDK clients directly.
165+
- AI provider configuration must stay minimal and user-owned: do not ship hardcoded model/deployment presets, token limits, context-size fields, model-type fields, or hidden defaults in Settings or appsettings. A provider entry should capture only the provider kind, editable endpoint/base URL when needed, API key when needed, and user-added model or deployment names, with exactly one active provider at runtime unless a later task introduces explicit routing.
165166
- Core AI skill and article wiring should use official Microsoft Agent Framework context primitives such as `AgentSkillsProvider` and `AIContextProvider`; do not manually concatenate embedded skill markdown into an agent system prompt.
166167
- AI, agent context, script graph extraction, and assistant action-matching paths must not use ad-hoc language heuristics for meaning, intent, or similarity. Use an available LLM path for semantic understanding; if no LLM is available, only an explicit tokenizer/vector similarity fallback is allowed, including for action search by tokenizing action descriptions and the user query into comparable token vectors.
167168
- Script graph tokenizer/vector fallback must use the tokenizer/vector primitives shipped by `ManagedCode.MarkdownLd.Kb` when available; do not keep a parallel PrompterOne-owned tokenizer implementation beside the graph library.
@@ -176,6 +177,7 @@ Rule format:
176177
- The PrompterOne AI assistant belongs in global shell/header chrome, must be available on every routed screen through a spotlight-style surface and hotkey, and must not remain an editor-only action.
177178
- The AI spotlight is a command/search palette plus agent chat/execution surface, not a planning screen: it should suggest route-aware commands and navigation targets while typing, send the entered prompt to the agent on submit, show execution progress only as live process/status feedback, and use a macOS Spotlight-style overlay where the underlying route remains visible with only a very light dim and no dark blurred full-page backdrop.
178179
- AI spotlight must not fake agent execution: when a provider is configured, submitting a prompt must invoke a real Microsoft Agent Framework agent with the user prompt, route/editor context, and MCP-style tool catalog; when no provider is configured, the UI must clearly report that AI is unavailable instead of showing a pretend working state.
180+
- AI assistant responses must use structured output instead of free-form parsing: separate the visible chat reply from machine-readable document edit operations, and express document changes as exact range-based insert, replace, or delete operations that can be applied independently of the chat text.
179181
- The AI spotlight prompt input must receive focus immediately when the spotlight opens so users can start typing without an extra click.
180182
- Script graph views must prioritize a readable writer knowledge map over decorative circular placement or raw TPS/source dumps: highlight the main/root document node, group meaningful document structure, ideas, people, themes, story references, and relationships by graph value/kind, expose node and edge hover tooltips with source detail, provide zoom/fit/layout controls, avoid noisy duplicated node-list strips under the graph, and never present source lines, WPM/pace, timing, cue, archetype, or raw TPS attribute values as primary graph nodes or primary edge labels. Graph viewing must support both a full graph-only mode and a split source/graph mode with a user-draggable divider that resizes the zones. Graph extraction must be useful to scriptwriters at a high-level glance: it should show what the text is about, how blocks and ideas connect, which references or entities recur, and where the user can jump back into the source; regex, stop-word, capitalization, keyword, and hardcoded-domain semantic heuristics are strictly forbidden. The only non-LLM fallback is an explicit user-requested tokenizer/vector similarity mode based on TikToken-style token vectors and distance calculations.
181183
- Script graph labels, details, semantic scopes, tokenizer chunks, and knowledge-graph markdown input must use compiled TPS display text from the TPS SDK, matching the clean text shown in the prompter; raw TPS source may be used for editor ranges and metadata only, not as a post-hoc string-cleaned source for visible graph prose.

‎README.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ No PrompterOne backend. No desktop install. No account wall. Open the app, start
5353
- **Learn**: rehearse with ORP-aligned RSVP, context rails, phrase-aware timing, WPM controls, stepping, looping, and punctuation-safe word progression.
5454
- **Teleprompter**: read with persisted font and width controls, focal-line positioning, horizontal and vertical mirror toggles, orientation switching, browser fullscreen, segmented progress, and optional camera background.
5555
- **Onboarding**: walk the first-run flow with a localized tour that explains TPS, RSVP, the editor, Learn, Teleprompter, and Go Live, then reopen that tour later from Settings.
56-
- **Settings**: manage appearance, browser language, media permissions, camera and microphone setup, sync offsets, recording defaults, AI provider preferences, cloud snapshot targets, transport credentials, and onboarding restart from one routed screen.
57-
- **AI Spotlight**: when an AI provider is configured, run the global assistant as a real Microsoft Agent Framework agent with route context, editor text, selected ranges, graph summary, and PrompterOne MCP-style tools.
56+
- **Settings**: manage appearance, browser language, media permissions, camera and microphone setup, sync offsets, recording defaults, minimal active AI provider setup, cloud snapshot targets, transport credentials, and onboarding restart from one routed screen.
57+
- **AI Spotlight**: when an AI provider is configured, run the global assistant as a real Microsoft Agent Framework agent with route context, editor text, selected ranges, graph summary, PrompterOne MCP-style tools, and structured output that separates chat replies from exact document edits.
5858
- **Go Live**: run the browser-owned studio shell and save the composed program feed locally with decodable video and audio while remote transport and destination routing continue to expand.
5959

6060
## What You Get
@@ -196,7 +196,7 @@ Distribution targets such as YouTube, Twitch, and custom RTMP are capability-gat
196196

197197
### Settings
198198

199-
Settings holds the operational state for the rest of the app: appearance, browser language, cloud snapshot targets, camera selection with preview, microphone setup with live meters, delay and sync offsets, output quality profiles, recording defaults, AI provider preferences, transport credentials, and onboarding restart. Theme changes and layout preferences persist, and appearance changes propagate across tabs instead of drifting out of sync.
199+
Settings holds the operational state for the rest of the app: appearance, browser language, cloud snapshot targets, camera selection with preview, microphone setup with live meters, delay and sync offsets, output quality profiles, recording defaults, minimal AI provider preferences, transport credentials, and onboarding restart. AI provider setup stays intentionally small: choose one active provider, enter only the endpoint or base URL when needed, the API key when needed, and the model or deployment names you actually use. Theme changes and layout preferences persist, and appearance changes propagate across tabs instead of drifting out of sync.
200200

201201
---
202202

@@ -235,7 +235,7 @@ PrompterOne is in **active alpha**: the core authoring, rehearsal, reader, and l
235235
| **LiveKit transport** | 🟡 | Real transport-aware browser integration and guest-path work, with operational polish still expanding |
236236
| **Distribution routing** | 🟡 | Targets are capability-gated and blocked when the selected transport cannot service them |
237237
| **Cloud storage snapshots** | 🟡 | Browser-local provider configuration ships now; broader import/export maturity is still expanding |
238-
| **AI provider execution** | ✅ | AI Spotlight runs a configured Microsoft Agent Framework agent with route/editor context and MCP-style tools; script graph analysis uses the configured LLM extractor before any explicit tokenizer fallback |
238+
| **AI provider execution** | ✅ | AI Spotlight runs a configured Microsoft Agent Framework agent with route/editor context, MCP-style tools, structured chat-plus-edit output, and exact range edit application; script graph analysis uses the configured LLM extractor before any explicit tokenizer fallback |
239239
| **Generic RTMP fan-out** | ❌ | Intentionally unsupported without a real upstream transport path |
240240
| **PrompterOne backend** | ❌ | By design: the browser is the only app runtime |
241241

‎src/PrompterOne.Core/AI/Agents/AssistantScriptAgent.cs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ public sealed class AssistantScriptAgent : ScriptAgent
1919
You are the PrompterOne AI assistant.
2020
Answer the user's request using the active route, editor context, selected range, graph summary, and available tools.
2121
Use MCP-style tools when you need current document text, selected text, graph details, or app action metadata.
22+
When a user asks to change specific wording, use the exact text search or range-reading tools to identify the source offsets before emitting documentEdits.
2223
For partial document edits, work through explicit range-based tool contracts. Do not regenerate a whole script unless the user clearly asks for a full rewrite.
23-
If a change would mutate user text, propose a scoped edit and explain the reason clearly.
24+
Return structured output with `chatMessage` for the visible chat response and `documentEdits` for machine-applied script edits.
25+
Each document edit must use exact UTF-16 offsets with kind `insert`, `replace`, or `delete`; leave `documentEdits` empty when no document change is needed.
26+
For every `replace` or `delete` edit, include `expectedText` exactly as it appears in the current source range. If you cannot identify an exact source range and expected text, do not emit a document edit; explain the blocker in `chatMessage`.
2427
Keep responses concise and directly useful inside the app.
2528
""";
2629
}

‎src/PrompterOne.Core/AI/Models/ScriptAgentRunResult.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ public sealed record ScriptAgentRunResult(
55
string WorkflowName,
66
string Input,
77
IReadOnlyList<ScriptAgentStepResult> Steps,
8-
string Output);
8+
string Output,
9+
ScriptAgentStructuredOutput? StructuredOutput = null);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.ComponentModel;
2+
3+
namespace PrompterOne.Core.AI.Models;
4+
5+
public sealed class ScriptAgentStructuredOutput
6+
{
7+
[Description("Short text shown to the user in the AI Assistant chat surface.")]
8+
public string ChatMessage { get; set; } = string.Empty;
9+
10+
[Description("Exact source document edits to apply after the chat response. Leave empty when no document change is needed.")]
11+
public List<ScriptAgentStructuredEdit> DocumentEdits { get; set; } = [];
12+
}
13+
14+
public sealed class ScriptAgentStructuredEdit
15+
{
16+
[Description("Exclusive UTF-16 source offset where the edit ends.")]
17+
public int End { get; set; }
18+
19+
[Description("The exact current source text in the range. Required for replace and delete edits.")]
20+
public string ExpectedText { get; set; } = string.Empty;
21+
22+
[Description("Edit kind: insert, replace, or delete.")]
23+
public string Kind { get; set; } = "replace";
24+
25+
[Description("Inclusive UTF-16 source offset where the edit starts.")]
26+
public int Start { get; set; }
27+
28+
[Description("Inserted or replacement text. Leave empty for delete edits.")]
29+
public string Text { get; set; } = string.Empty;
30+
31+
public ScriptDocumentEditOperation? ToDocumentEditOperation()
32+
{
33+
var normalizedKind = Kind.Trim().ToLowerInvariant();
34+
return normalizedKind switch
35+
{
36+
"insert" => ScriptDocumentEditOperation.Insert(Start, Text),
37+
"replace" => ScriptDocumentEditOperation.Replace(new ScriptDocumentRange(Start, End), Text),
38+
"delete" => ScriptDocumentEditOperation.Delete(new ScriptDocumentRange(Start, End)),
39+
_ => null
40+
};
41+
}
42+
}

‎src/PrompterOne.Core/AI/Services/ScriptAgentRuntime.cs‎

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System.Text.Json;
12
using Microsoft.Agents.AI;
3+
using Microsoft.Extensions.AI;
24
using PrompterOne.Core.AI.Abstractions;
35
using PrompterOne.Core.AI.Agents;
46
using PrompterOne.Core.AI.Models;
@@ -11,6 +13,11 @@ public sealed class ScriptAgentRuntime(
1113
IEnumerable<ScriptWorkflow> workflows,
1214
IScriptAgentFactory agentFactory) : IScriptAgentRuntime
1315
{
16+
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
17+
{
18+
PropertyNameCaseInsensitive = true
19+
};
20+
1421
private readonly IReadOnlyDictionary<string, ScriptAgent> _agentsById = agents.ToDictionary(
1522
static agent => agent.Id,
1623
StringComparer.OrdinalIgnoreCase);
@@ -71,6 +78,31 @@ private static string ExtractOutput(AgentResponse response)
7178
return string.Join(Environment.NewLine + Environment.NewLine, messages);
7279
}
7380

81+
private static AgentRunOptions CreateStructuredOutputOptions() =>
82+
new()
83+
{
84+
ResponseFormat = ChatResponseFormat.ForJsonSchema<ScriptAgentStructuredOutput>()
85+
};
86+
87+
private static ScriptAgentStructuredOutput ExtractStructuredOutput(AgentResponse response)
88+
{
89+
var output = ExtractOutput(response);
90+
var structuredOutput = JsonSerializer.Deserialize<ScriptAgentStructuredOutput>(output, JsonOptions);
91+
if (structuredOutput is null)
92+
{
93+
throw new InvalidOperationException("The agent did not return the required structured output.");
94+
}
95+
96+
structuredOutput.ChatMessage = structuredOutput.ChatMessage.Trim();
97+
structuredOutput.DocumentEdits ??= [];
98+
return structuredOutput;
99+
}
100+
101+
private static string GetChatMessage(ScriptAgentStructuredOutput structuredOutput) =>
102+
string.IsNullOrWhiteSpace(structuredOutput.ChatMessage)
103+
? "Done."
104+
: structuredOutput.ChatMessage;
105+
74106
private ScriptAgent GetRequiredAgent(string agentId) =>
75107
_agentsById.TryGetValue(agentId, out var agent)
76108
? agent
@@ -98,6 +130,7 @@ private async Task<ScriptAgentRunResult> RunSequentialAsync(
98130
{
99131
var steps = new List<ScriptAgentStepResult>(workflow.AgentIds.Count);
100132
var nextInput = originalInput;
133+
ScriptAgentStructuredOutput? finalStructuredOutput = null;
101134

102135
foreach (var agentId in workflow.AgentIds)
103136
{
@@ -106,8 +139,10 @@ private async Task<ScriptAgentRunResult> RunSequentialAsync(
106139
var definition = GetRequiredAgent(agentId);
107140
var agent = await _agentFactory.CreateRequiredAsync(agentId, agentContext, cancellationToken);
108141
var session = await agent.CreateSessionAsync(cancellationToken);
109-
var response = await agent.RunAsync(nextInput, session, new AgentRunOptions(), cancellationToken);
110-
var output = ExtractOutput(response);
142+
var response = await agent.RunAsync(nextInput, session, CreateStructuredOutputOptions(), cancellationToken);
143+
var structuredOutput = ExtractStructuredOutput(response);
144+
var output = GetChatMessage(structuredOutput);
145+
finalStructuredOutput = structuredOutput;
111146

112147
steps.Add(new ScriptAgentStepResult(definition.Id, definition.Name, nextInput, output));
113148
nextInput = BuildNextInput(originalInput, output);
@@ -118,7 +153,8 @@ private async Task<ScriptAgentRunResult> RunSequentialAsync(
118153
workflow.Name,
119154
originalInput,
120155
steps,
121-
steps.Count == 0 ? string.Empty : steps[^1].Output);
156+
steps.Count == 0 ? string.Empty : steps[^1].Output,
157+
finalStructuredOutput);
122158
}
123159

124160
private async Task<ScriptAgentRunResult> RunWorkflowAgentAsync(
@@ -129,14 +165,17 @@ private async Task<ScriptAgentRunResult> RunWorkflowAgentAsync(
129165
{
130166
var workflowAgent = await workflow.CreateWorkflowAgentAsync(_agentFactory, agentContext, cancellationToken);
131167
var session = await workflowAgent.CreateSessionAsync(cancellationToken);
132-
var response = await workflowAgent.RunAsync(originalInput, session, new AgentRunOptions(), cancellationToken);
133-
var output = ExtractOutput(response);
168+
var response = await workflowAgent.RunAsync(originalInput, session, CreateStructuredOutputOptions(), cancellationToken);
169+
var structuredOutput = ExtractStructuredOutput(response);
170+
var output = GetChatMessage(structuredOutput);
134171

135172
return new ScriptAgentRunResult(
136173
workflow.Id,
137174
workflow.Name,
138175
originalInput,
139176
[new ScriptAgentStepResult(workflow.Id, workflow.Name, originalInput, output)],
140-
output);
177+
output,
178+
structuredOutput);
141179
}
180+
142181
}

‎src/PrompterOne.Core/AI/Services/ScriptAgentToolProvider.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public IList<AITool> CreateTools(ScriptAgentContext? context)
2020
ScriptAgentMcpToolFunctionFactory.Create(contextTools.GetActivePrompterContext),
2121
ScriptAgentMcpToolFunctionFactory.Create(contextTools.ListAvailablePrompterOneTools),
2222
ScriptAgentMcpToolFunctionFactory.Create(contextTools.RequestPrompterOneTool),
23+
ScriptAgentMcpToolFunctionFactory.Create(documentTools.FindScriptText),
2324
ScriptAgentMcpToolFunctionFactory.Create(documentTools.ReadScriptRange),
2425
ScriptAgentMcpToolFunctionFactory.Create(documentTools.ReadEditorSelection),
2526
ScriptAgentMcpToolFunctionFactory.Create(documentTools.ProposeScriptReplacement),

0 commit comments

Comments
 (0)