@llmrtc/llmrtc-core 1.0.0

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.
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Playbook Engine
3
+ *
4
+ * Evaluates transition rules and manages stage transitions in a playbook.
5
+ */
6
+ import { PLAYBOOK_TRANSITION_TOOL } from './playbook.js';
7
+ /**
8
+ * Playbook Engine - Evaluates transitions and manages playbook state
9
+ */
10
+ export class PlaybookEngine {
11
+ constructor(playbook, options = {}) {
12
+ Object.defineProperty(this, "playbook", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: void 0
17
+ });
18
+ Object.defineProperty(this, "state", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: void 0
23
+ });
24
+ Object.defineProperty(this, "options", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: void 0
29
+ });
30
+ Object.defineProperty(this, "listeners", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: new Set()
35
+ });
36
+ Object.defineProperty(this, "sessionMetadata", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: {}
41
+ });
42
+ this.playbook = playbook;
43
+ this.options = options;
44
+ // Initialize state
45
+ const initialStage = playbook.stages.find(s => s.id === playbook.initialStage);
46
+ if (!initialStage) {
47
+ throw new Error(`Initial stage '${playbook.initialStage}' not found in playbook`);
48
+ }
49
+ this.state = {
50
+ currentStage: initialStage,
51
+ turnCount: 0,
52
+ stageEnteredAt: Date.now(),
53
+ conversationContext: {},
54
+ transitionHistory: [],
55
+ isComplete: false
56
+ };
57
+ }
58
+ /**
59
+ * Get current playbook state
60
+ */
61
+ getState() {
62
+ return { ...this.state };
63
+ }
64
+ /**
65
+ * Get current stage
66
+ */
67
+ getCurrentStage() {
68
+ return this.state.currentStage;
69
+ }
70
+ /**
71
+ * Get the playbook
72
+ */
73
+ getPlaybook() {
74
+ return this.playbook;
75
+ }
76
+ /**
77
+ * Set session metadata
78
+ */
79
+ setSessionMetadata(metadata) {
80
+ this.sessionMetadata = { ...this.sessionMetadata, ...metadata };
81
+ }
82
+ /**
83
+ * Update conversation context
84
+ */
85
+ updateContext(updates) {
86
+ this.state.conversationContext = {
87
+ ...this.state.conversationContext,
88
+ ...updates
89
+ };
90
+ }
91
+ /**
92
+ * Subscribe to playbook events
93
+ */
94
+ on(listener) {
95
+ this.listeners.add(listener);
96
+ return () => this.listeners.delete(listener);
97
+ }
98
+ /**
99
+ * Emit an event to all listeners
100
+ */
101
+ async emit(event) {
102
+ for (const listener of this.listeners) {
103
+ await listener(event);
104
+ }
105
+ }
106
+ /**
107
+ * Log a debug message
108
+ */
109
+ log(level, msg, ...args) {
110
+ if (this.options.logger) {
111
+ this.options.logger[level](msg, ...args);
112
+ }
113
+ else if (this.options.debug && level !== 'debug') {
114
+ console[level](`[PlaybookEngine] ${msg}`, ...args);
115
+ }
116
+ }
117
+ /**
118
+ * Build transition context for evaluation
119
+ */
120
+ buildTransitionContext(lastAssistantMessage, lastToolCalls) {
121
+ return {
122
+ currentStage: this.state.currentStage.id,
123
+ turnCount: this.state.turnCount,
124
+ timeInStage: Date.now() - this.state.stageEnteredAt,
125
+ lastAssistantMessage,
126
+ lastToolCalls,
127
+ conversationContext: this.state.conversationContext,
128
+ sessionMetadata: this.sessionMetadata
129
+ };
130
+ }
131
+ /**
132
+ * Evaluate a single transition condition
133
+ */
134
+ async evaluateCondition(condition, context) {
135
+ switch (condition.type) {
136
+ case 'tool_call':
137
+ return context.lastToolCalls?.some(tc => tc.name === condition.toolName) ?? false;
138
+ case 'intent':
139
+ // Intent detection would typically be done by the LLM or a classifier
140
+ // For now, check if the intent is in the conversation context
141
+ const detectedIntent = context.conversationContext.detectedIntent;
142
+ if (!detectedIntent)
143
+ return false;
144
+ if (condition.confidence !== undefined) {
145
+ const confidence = context.conversationContext.intentConfidence;
146
+ return detectedIntent === condition.intent && (confidence ?? 0) >= condition.confidence;
147
+ }
148
+ return detectedIntent === condition.intent;
149
+ case 'keyword':
150
+ const message = context.lastAssistantMessage?.toLowerCase() ?? '';
151
+ return condition.keywords.some(kw => message.includes(kw.toLowerCase()));
152
+ case 'llm_decision':
153
+ // LLM decision is handled via the playbook_transition tool
154
+ // Check if the LLM called the transition tool
155
+ return context.lastToolCalls?.some(tc => tc.name === 'playbook_transition') ?? false;
156
+ case 'max_turns':
157
+ return context.turnCount >= condition.count;
158
+ case 'timeout':
159
+ return context.timeInStage >= condition.durationMs;
160
+ case 'custom':
161
+ return await condition.evaluate(context);
162
+ default:
163
+ this.log('warn', `Unknown transition condition type: ${condition.type}`);
164
+ return false;
165
+ }
166
+ }
167
+ /**
168
+ * Evaluate all applicable transitions and return the highest priority match
169
+ */
170
+ async evaluateTransitions(lastAssistantMessage, lastToolCalls) {
171
+ const context = this.buildTransitionContext(lastAssistantMessage, lastToolCalls);
172
+ // Get transitions applicable to current stage (sorted by priority)
173
+ const applicableTransitions = this.playbook.transitions
174
+ .filter(t => t.from === '*' || t.from === this.state.currentStage.id)
175
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
176
+ for (const transition of applicableTransitions) {
177
+ const matches = await this.evaluateCondition(transition.condition, context);
178
+ if (matches) {
179
+ this.log('debug', `Transition '${transition.id}' matched`, {
180
+ from: this.state.currentStage.id,
181
+ to: transition.action.targetStage,
182
+ condition: transition.condition.type
183
+ });
184
+ return {
185
+ shouldTransition: true,
186
+ transition,
187
+ reason: `Condition '${transition.condition.type}' matched for transition '${transition.id}'`
188
+ };
189
+ }
190
+ }
191
+ return {
192
+ shouldTransition: false,
193
+ reason: 'No transition conditions matched'
194
+ };
195
+ }
196
+ /**
197
+ * Check if a specific transition should be triggered (e.g., from playbook_transition tool)
198
+ */
199
+ async evaluateExplicitTransition(targetStage, reason, data) {
200
+ // Verify target stage exists
201
+ const target = this.playbook.stages.find(s => s.id === targetStage);
202
+ if (!target) {
203
+ return {
204
+ shouldTransition: false,
205
+ reason: `Target stage '${targetStage}' not found in playbook`
206
+ };
207
+ }
208
+ // Find an LLM decision transition or create an implicit one
209
+ const llmTransition = this.playbook.transitions.find(t => (t.from === '*' || t.from === this.state.currentStage.id) &&
210
+ t.condition.type === 'llm_decision' &&
211
+ t.action.targetStage === targetStage);
212
+ if (llmTransition) {
213
+ return {
214
+ shouldTransition: true,
215
+ transition: llmTransition,
216
+ reason: `LLM requested transition: ${reason}`
217
+ };
218
+ }
219
+ // Allow implicit LLM-initiated transitions if no explicit rule exists
220
+ return {
221
+ shouldTransition: true,
222
+ transition: {
223
+ id: `implicit_llm_${Date.now()}`,
224
+ from: this.state.currentStage.id,
225
+ condition: { type: 'llm_decision' },
226
+ action: {
227
+ targetStage,
228
+ data
229
+ }
230
+ },
231
+ reason: `LLM requested implicit transition: ${reason}`
232
+ };
233
+ }
234
+ /**
235
+ * Execute a stage transition
236
+ */
237
+ async executeTransition(transition, overrideData) {
238
+ const fromStage = this.state.currentStage;
239
+ const toStage = this.playbook.stages.find(s => s.id === transition.action.targetStage);
240
+ if (!toStage) {
241
+ throw new Error(`Target stage '${transition.action.targetStage}' not found`);
242
+ }
243
+ this.log('info', `Transitioning from '${fromStage.id}' to '${toStage.id}'`);
244
+ // Build stage context for hooks
245
+ const exitContext = {
246
+ stage: fromStage,
247
+ otherStage: toStage,
248
+ transitionData: overrideData ?? transition.action.data,
249
+ sessionMetadata: this.sessionMetadata,
250
+ conversationContext: this.state.conversationContext
251
+ };
252
+ // Call exit hook
253
+ if (fromStage.onExit) {
254
+ await fromStage.onExit(exitContext);
255
+ }
256
+ // Emit exit event
257
+ await this.emit({ type: 'stage_exit', stage: fromStage, nextStage: toStage });
258
+ // Clear history if requested
259
+ if (transition.action.clearHistory) {
260
+ this.state.conversationContext = {};
261
+ }
262
+ // Record transition in history
263
+ this.state.transitionHistory.push({
264
+ from: fromStage.id,
265
+ to: toStage.id,
266
+ transitionId: transition.id,
267
+ timestamp: Date.now()
268
+ });
269
+ // Update state
270
+ this.state.currentStage = toStage;
271
+ this.state.turnCount = 0;
272
+ this.state.stageEnteredAt = Date.now();
273
+ // Merge transition data into context
274
+ if (transition.action.data || overrideData) {
275
+ this.state.conversationContext = {
276
+ ...this.state.conversationContext,
277
+ transitionData: overrideData ?? transition.action.data
278
+ };
279
+ }
280
+ // Build enter context
281
+ const enterContext = {
282
+ stage: toStage,
283
+ otherStage: fromStage,
284
+ transitionData: overrideData ?? transition.action.data,
285
+ sessionMetadata: this.sessionMetadata,
286
+ conversationContext: this.state.conversationContext
287
+ };
288
+ // Call enter hook
289
+ if (toStage.onEnter) {
290
+ await toStage.onEnter(enterContext);
291
+ }
292
+ // Emit events
293
+ await this.emit({ type: 'transition', transition, from: fromStage, to: toStage });
294
+ await this.emit({ type: 'stage_enter', stage: toStage, previousStage: fromStage });
295
+ }
296
+ /**
297
+ * Increment turn count and emit event
298
+ */
299
+ async completeTurn() {
300
+ this.state.turnCount++;
301
+ await this.emit({
302
+ type: 'turn_complete',
303
+ stage: this.state.currentStage,
304
+ turnCount: this.state.turnCount
305
+ });
306
+ }
307
+ /**
308
+ * Mark playbook as complete
309
+ */
310
+ async complete() {
311
+ this.state.isComplete = true;
312
+ await this.emit({
313
+ type: 'playbook_complete',
314
+ finalStage: this.state.currentStage
315
+ });
316
+ }
317
+ /**
318
+ * Get the effective system prompt for current stage
319
+ * (combines global prompt with stage-specific prompt)
320
+ */
321
+ getEffectiveSystemPrompt() {
322
+ const parts = [];
323
+ if (this.playbook.globalSystemPrompt) {
324
+ parts.push(this.playbook.globalSystemPrompt);
325
+ }
326
+ parts.push(this.state.currentStage.systemPrompt);
327
+ // Add available transitions context for LLM
328
+ const availableTransitions = this.playbook.transitions
329
+ .filter(t => t.from === '*' || t.from === this.state.currentStage.id)
330
+ .filter(t => t.condition.type === 'llm_decision');
331
+ if (availableTransitions.length > 0) {
332
+ const transitionDescriptions = availableTransitions.map(t => {
333
+ const targetStage = this.playbook.stages.find(s => s.id === t.action.targetStage);
334
+ return `- ${t.action.targetStage}: ${targetStage?.description ?? t.description ?? 'No description'}`;
335
+ });
336
+ parts.push(`\nYou can transition to the following stages when appropriate:\n${transitionDescriptions.join('\n')}`);
337
+ }
338
+ return parts.join('\n\n');
339
+ }
340
+ /**
341
+ * Get all tools available in current stage
342
+ * (combines global tools with stage-specific tools)
343
+ */
344
+ getAvailableTools() {
345
+ const tools = [];
346
+ // Add global tools
347
+ if (this.playbook.globalTools) {
348
+ tools.push(...this.playbook.globalTools);
349
+ }
350
+ // Add stage-specific tools
351
+ if (this.state.currentStage.tools) {
352
+ tools.push(...this.state.currentStage.tools);
353
+ }
354
+ // Add playbook_transition tool if LLM decision transitions exist
355
+ const hasLLMTransitions = this.playbook.transitions.some(t => (t.from === '*' || t.from === this.state.currentStage.id) &&
356
+ t.condition.type === 'llm_decision');
357
+ if (hasLLMTransitions) {
358
+ tools.push(PLAYBOOK_TRANSITION_TOOL);
359
+ }
360
+ return tools;
361
+ }
362
+ /**
363
+ * Get effective LLM config for current stage
364
+ */
365
+ getEffectiveLLMConfig() {
366
+ return {
367
+ ...this.playbook.defaultLLMConfig,
368
+ ...this.state.currentStage.llmConfig
369
+ };
370
+ }
371
+ /**
372
+ * Reset engine to initial state
373
+ */
374
+ reset() {
375
+ const initialStage = this.playbook.stages.find(s => s.id === this.playbook.initialStage);
376
+ if (!initialStage) {
377
+ throw new Error(`Initial stage '${this.playbook.initialStage}' not found`);
378
+ }
379
+ this.state = {
380
+ currentStage: initialStage,
381
+ turnCount: 0,
382
+ stageEnteredAt: Date.now(),
383
+ conversationContext: {},
384
+ transitionHistory: [],
385
+ isComplete: false
386
+ };
387
+ this.sessionMetadata = {};
388
+ }
389
+ }
390
+ //# sourceMappingURL=playbook-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playbook-engine.js","sourceRoot":"","sources":["../src/playbook-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Playbook Orchestrator
3
+ *
4
+ * Orchestrates turn execution with two-phase model:
5
+ * - Phase 1: Tool loop - LLM can call tools repeatedly until it decides to respond
6
+ * - Phase 2: Final answer - Generate the user-facing response
7
+ *
8
+ * Integrates with STT/TTS for voice-based interactions.
9
+ */
10
+ import type { LLMProvider, LLMResult, Message, VisionAttachment } from './types.js';
11
+ import type { ToolDefinition, ToolCallRequest, ToolCallResult } from './tools.js';
12
+ import { ToolRegistry } from './tools.js';
13
+ import { PlaybookEngine, PlaybookEvent } from './playbook-engine.js';
14
+ import type { Playbook, Stage, Transition } from './playbook.js';
15
+ /**
16
+ * Options for the playbook orchestrator
17
+ */
18
+ export interface PlaybookOrchestratorOptions {
19
+ /** Maximum number of tool calls per turn in Phase 1 */
20
+ maxToolCallsPerTurn?: number;
21
+ /** Timeout for Phase 1 tool loop (ms) */
22
+ phase1TimeoutMs?: number;
23
+ /** Number of LLM retry attempts on failure (default: 3) */
24
+ llmRetries?: number;
25
+ /** Maximum conversation history messages to retain (default: 50) */
26
+ historyLimit?: number;
27
+ /** Enable debug logging */
28
+ debug?: boolean;
29
+ /** Custom logger */
30
+ logger?: {
31
+ debug: (msg: string, ...args: unknown[]) => void;
32
+ info: (msg: string, ...args: unknown[]) => void;
33
+ warn: (msg: string, ...args: unknown[]) => void;
34
+ error: (msg: string, ...args: unknown[]) => void;
35
+ };
36
+ /** Abort signal for cancellation */
37
+ abortSignal?: AbortSignal;
38
+ }
39
+ /**
40
+ * Result of a single turn execution
41
+ */
42
+ export interface TurnResult {
43
+ /** The final assistant response */
44
+ response: string;
45
+ /** All tool calls made during the turn */
46
+ toolCalls: Array<{
47
+ request: ToolCallRequest;
48
+ result: ToolCallResult;
49
+ }>;
50
+ /** Whether a stage transition occurred */
51
+ transitioned: boolean;
52
+ /** The transition that was triggered (if any) */
53
+ transition?: Transition;
54
+ /** New stage after transition (if any) */
55
+ newStage?: Stage;
56
+ /** Raw LLM responses */
57
+ llmResponses: LLMResult[];
58
+ /** Stop reason from final LLM call */
59
+ stopReason?: string;
60
+ }
61
+ /**
62
+ * Event emitted during turn execution
63
+ */
64
+ export type OrchestratorEvent = {
65
+ type: 'phase1_start';
66
+ } | {
67
+ type: 'tool_call_start';
68
+ call: ToolCallRequest;
69
+ } | {
70
+ type: 'tool_call_complete';
71
+ call: ToolCallRequest;
72
+ result: ToolCallResult;
73
+ } | {
74
+ type: 'phase1_complete';
75
+ toolCallCount: number;
76
+ } | {
77
+ type: 'phase2_start';
78
+ } | {
79
+ type: 'phase2_complete';
80
+ response: string;
81
+ } | {
82
+ type: 'transition_triggered';
83
+ transition: Transition;
84
+ } | PlaybookEvent;
85
+ /**
86
+ * Listener for orchestrator events
87
+ */
88
+ export type OrchestratorEventListener = (event: OrchestratorEvent) => void | Promise<void>;
89
+ /**
90
+ * Playbook Orchestrator - Manages two-phase turn execution
91
+ */
92
+ export declare class PlaybookOrchestrator {
93
+ private readonly llmProvider;
94
+ private readonly engine;
95
+ private readonly toolRegistry;
96
+ private readonly toolExecutor;
97
+ private readonly options;
98
+ private readonly listeners;
99
+ private conversationHistory;
100
+ private turnLock;
101
+ private isExecutingTurn;
102
+ constructor(llmProvider: LLMProvider, playbook: Playbook, toolRegistry: ToolRegistry, options?: PlaybookOrchestratorOptions);
103
+ /**
104
+ * Register the built-in playbook_transition tool
105
+ */
106
+ private registerTransitionTool;
107
+ /**
108
+ * Get the playbook engine
109
+ */
110
+ getEngine(): PlaybookEngine;
111
+ /**
112
+ * Get current conversation history
113
+ */
114
+ getHistory(): Message[];
115
+ /**
116
+ * Clear conversation history
117
+ */
118
+ clearHistory(): void;
119
+ /**
120
+ * Subscribe to orchestrator events
121
+ */
122
+ on(listener: OrchestratorEventListener): () => void;
123
+ /**
124
+ * Emit an event to all listeners
125
+ */
126
+ private emit;
127
+ /**
128
+ * Log a message
129
+ */
130
+ private log;
131
+ /**
132
+ * Add message to history with automatic cleanup when limit exceeded.
133
+ * Uses smart trimming to preserve tool call/result pairs (required by OpenAI API).
134
+ */
135
+ private pushHistory;
136
+ /**
137
+ * Check if an error is retryable.
138
+ * Non-retryable: client errors (400, 401, 403, 404) except rate limits.
139
+ * Retryable: rate limits (429), server errors (5xx), timeouts.
140
+ */
141
+ private isRetryableError;
142
+ /**
143
+ * Call LLM with smart retry and exponential backoff.
144
+ * Only retries on retryable errors (rate limits, server errors, timeouts).
145
+ */
146
+ private callLLMWithRetry;
147
+ /**
148
+ * Build LLM request for current state
149
+ */
150
+ private buildLLMRequest;
151
+ /**
152
+ * Execute Phase 1: Tool loop
153
+ * LLM can call tools repeatedly until it decides to respond
154
+ */
155
+ private executePhase1;
156
+ /**
157
+ * Execute Phase 2: Generate final response
158
+ */
159
+ private executePhase2;
160
+ /**
161
+ * Execute a complete turn with user input.
162
+ * This method is serialized - concurrent calls will be queued.
163
+ * @param userMessage - The user's message text
164
+ * @param attachments - Optional vision attachments (images)
165
+ */
166
+ executeTurn(userMessage: string, attachments?: VisionAttachment[]): Promise<TurnResult>;
167
+ /**
168
+ * Internal turn execution logic
169
+ */
170
+ private _executeTurnInternal;
171
+ /**
172
+ * Execute a turn with streaming response.
173
+ * This method is serialized - concurrent calls will be queued.
174
+ * @param userMessage - The user's message text
175
+ * @param attachments - Optional vision attachments (images)
176
+ */
177
+ streamTurn(userMessage: string, attachments?: VisionAttachment[]): AsyncIterable<{
178
+ type: 'tool_call' | 'content' | 'done';
179
+ data: ToolCallRequest | string | TurnResult;
180
+ }>;
181
+ /**
182
+ * Internal streaming turn logic
183
+ */
184
+ private _streamTurnInternal;
185
+ /**
186
+ * Reset orchestrator to initial state
187
+ */
188
+ reset(): void;
189
+ }
190
+ /**
191
+ * Create a simple playbook with a single stage
192
+ */
193
+ export declare function createSimplePlaybook(id: string, systemPrompt: string, tools?: ToolDefinition[]): Playbook;