@nextsparkjs/theme-default 0.1.0-beta.1
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.
- package/about/business.md +49 -0
- package/about/features.json +302 -0
- package/about/team.md +79 -0
- package/api/ai/chat/stream/route.ts +212 -0
- package/api/ai/orchestrator/route.ts +226 -0
- package/api/ai/single-agent/route.ts +291 -0
- package/api/ai/usage/route.ts +122 -0
- package/blocks/benefits/component.tsx +100 -0
- package/blocks/benefits/config.ts +11 -0
- package/blocks/benefits/examples.ts +85 -0
- package/blocks/benefits/fields.ts +156 -0
- package/blocks/benefits/schema.ts +33 -0
- package/blocks/cta-section/component.tsx +100 -0
- package/blocks/cta-section/config.ts +11 -0
- package/blocks/cta-section/examples.ts +41 -0
- package/blocks/cta-section/fields.ts +89 -0
- package/blocks/cta-section/index.ts +6 -0
- package/blocks/cta-section/schema.ts +32 -0
- package/blocks/cta-section/thumbnail.png +1 -0
- package/blocks/faq-accordion/component.tsx +156 -0
- package/blocks/faq-accordion/config.ts +11 -0
- package/blocks/faq-accordion/examples.ts +77 -0
- package/blocks/faq-accordion/fields.ts +119 -0
- package/blocks/faq-accordion/index.ts +6 -0
- package/blocks/faq-accordion/schema.ts +45 -0
- package/blocks/features-grid/component.tsx +112 -0
- package/blocks/features-grid/config.ts +11 -0
- package/blocks/features-grid/examples.ts +63 -0
- package/blocks/features-grid/fields.ts +97 -0
- package/blocks/features-grid/index.ts +6 -0
- package/blocks/features-grid/schema.ts +40 -0
- package/blocks/features-grid/thumbnail.png +1 -0
- package/blocks/hero/component.tsx +100 -0
- package/blocks/hero/config.ts +11 -0
- package/blocks/hero/examples.ts +35 -0
- package/blocks/hero/fields.ts +60 -0
- package/blocks/hero/index.ts +6 -0
- package/blocks/hero/schema.ts +32 -0
- package/blocks/hero/thumbnail.png +1 -0
- package/blocks/hero/thumbnail.png.txt +6 -0
- package/blocks/hero-with-form/component.tsx +232 -0
- package/blocks/hero-with-form/config.ts +11 -0
- package/blocks/hero-with-form/examples.ts +16 -0
- package/blocks/hero-with-form/fields.ts +207 -0
- package/blocks/hero-with-form/index.ts +6 -0
- package/blocks/hero-with-form/schema.ts +54 -0
- package/blocks/jumbotron/component.tsx +136 -0
- package/blocks/jumbotron/config.ts +11 -0
- package/blocks/jumbotron/examples.ts +36 -0
- package/blocks/jumbotron/fields.ts +202 -0
- package/blocks/jumbotron/index.ts +6 -0
- package/blocks/jumbotron/schema.ts +55 -0
- package/blocks/logo-cloud/component.tsx +154 -0
- package/blocks/logo-cloud/config.ts +11 -0
- package/blocks/logo-cloud/examples.ts +34 -0
- package/blocks/logo-cloud/fields.ts +133 -0
- package/blocks/logo-cloud/index.ts +6 -0
- package/blocks/logo-cloud/schema.ts +46 -0
- package/blocks/post-content/component.tsx +197 -0
- package/blocks/post-content/config.ts +11 -0
- package/blocks/post-content/examples.ts +33 -0
- package/blocks/post-content/fields.ts +165 -0
- package/blocks/post-content/index.ts +4 -0
- package/blocks/post-content/schema.ts +46 -0
- package/blocks/pricing-table/component.tsx +154 -0
- package/blocks/pricing-table/config.ts +11 -0
- package/blocks/pricing-table/examples.ts +96 -0
- package/blocks/pricing-table/fields.ts +161 -0
- package/blocks/pricing-table/index.ts +4 -0
- package/blocks/pricing-table/schema.ts +50 -0
- package/blocks/split-content/component.tsx +135 -0
- package/blocks/split-content/config.ts +11 -0
- package/blocks/split-content/examples.ts +38 -0
- package/blocks/split-content/fields.ts +198 -0
- package/blocks/split-content/index.ts +6 -0
- package/blocks/split-content/schema.ts +67 -0
- package/blocks/stats-counter/component.tsx +124 -0
- package/blocks/stats-counter/config.ts +11 -0
- package/blocks/stats-counter/examples.ts +61 -0
- package/blocks/stats-counter/fields.ts +134 -0
- package/blocks/stats-counter/index.ts +6 -0
- package/blocks/stats-counter/schema.ts +47 -0
- package/blocks/testimonials/component.tsx +114 -0
- package/blocks/testimonials/config.ts +11 -0
- package/blocks/testimonials/examples.ts +65 -0
- package/blocks/testimonials/fields.ts +105 -0
- package/blocks/testimonials/index.ts +6 -0
- package/blocks/testimonials/schema.ts +41 -0
- package/blocks/testimonials/thumbnail.png +1 -0
- package/blocks/text-content/component.tsx +97 -0
- package/blocks/text-content/config.ts +11 -0
- package/blocks/text-content/examples.ts +30 -0
- package/blocks/text-content/fields.ts +88 -0
- package/blocks/text-content/index.ts +6 -0
- package/blocks/text-content/schema.ts +30 -0
- package/blocks/text-content/thumbnail.png +1 -0
- package/blocks/timeline/component.tsx +267 -0
- package/blocks/timeline/config.ts +11 -0
- package/blocks/timeline/examples.ts +68 -0
- package/blocks/timeline/fields.ts +147 -0
- package/blocks/timeline/index.ts +6 -0
- package/blocks/timeline/schema.ts +49 -0
- package/blocks/video-hero/component.tsx +270 -0
- package/blocks/video-hero/config.ts +11 -0
- package/blocks/video-hero/examples.ts +24 -0
- package/blocks/video-hero/fields.ts +98 -0
- package/blocks/video-hero/index.ts +6 -0
- package/blocks/video-hero/schema.ts +39 -0
- package/components/ai-chat/ChatPanel.tsx +575 -0
- package/components/ai-chat/ConversationItem.tsx +266 -0
- package/components/ai-chat/ConversationSidebar.tsx +99 -0
- package/components/ai-chat/MarkdownRenderer.tsx +15 -0
- package/components/ai-chat/Message.tsx +42 -0
- package/components/ai-chat/MessageInput.tsx +49 -0
- package/components/ai-chat/MessageList.tsx +46 -0
- package/components/ai-chat/TypingIndicator.tsx +11 -0
- package/config/app.config.ts +367 -0
- package/config/billing.config.ts +349 -0
- package/config/dashboard.config.ts +506 -0
- package/config/dev.config.ts +104 -0
- package/config/features.config.ts +203 -0
- package/config/flows.config.ts +129 -0
- package/config/permissions.config.ts +245 -0
- package/config/theme.config.ts +74 -0
- package/docs/01-overview/01-introduction.md +335 -0
- package/docs/01-overview/02-customization.md +671 -0
- package/docs/02-features/01-components.md +155 -0
- package/docs/02-features/02-styling.md +139 -0
- package/docs/02-features/03-tasks-entity.md +407 -0
- package/docs/03-ai/01-overview.md +211 -0
- package/docs/03-ai/02-customization.md +436 -0
- package/entities/customers/customers.config.ts +75 -0
- package/entities/customers/customers.fields.ts +165 -0
- package/entities/customers/customers.service.ts +516 -0
- package/entities/customers/customers.types.ts +83 -0
- package/entities/customers/messages/en.json +66 -0
- package/entities/customers/messages/es.json +66 -0
- package/entities/customers/migrations/001_customers_table.sql +102 -0
- package/entities/customers/migrations/002_customers_metas.sql +92 -0
- package/entities/pages/messages/en.json +41 -0
- package/entities/pages/messages/es.json +41 -0
- package/entities/pages/migrations/001_pages_table.sql +112 -0
- package/entities/pages/migrations/002_pages_metas.sql +56 -0
- package/entities/pages/migrations/003_add_status.sql +50 -0
- package/entities/pages/pages-management.service.ts +610 -0
- package/entities/pages/pages.config.ts +94 -0
- package/entities/pages/pages.fields.ts +101 -0
- package/entities/pages/pages.service.ts +290 -0
- package/entities/pages/pages.types.ts +124 -0
- package/entities/posts/components/post-header.tsx +97 -0
- package/entities/posts/messages/en.json +55 -0
- package/entities/posts/messages/es.json +55 -0
- package/entities/posts/migrations/001_posts_table.sql +115 -0
- package/entities/posts/migrations/003_add_status.sql +44 -0
- package/entities/posts/migrations/004_entity_taxonomy_relations.sql +129 -0
- package/entities/posts/migrations/006_posts_metas.sql +56 -0
- package/entities/posts/posts.config.ts +101 -0
- package/entities/posts/posts.fields.ts +116 -0
- package/entities/posts/posts.service.ts +376 -0
- package/entities/posts/posts.types.ts +74 -0
- package/entities/tasks/messages/en.json +204 -0
- package/entities/tasks/messages/es.json +204 -0
- package/entities/tasks/migrations/001_tasks_table.sql +105 -0
- package/entities/tasks/migrations/002_task_metas.sql +85 -0
- package/entities/tasks/migrations/sample_data.json +77 -0
- package/entities/tasks/tasks.config.ts +79 -0
- package/entities/tasks/tasks.fields.ts +196 -0
- package/entities/tasks/tasks.service.ts +541 -0
- package/entities/tasks/tasks.types.ts +56 -0
- package/lib/hooks/useAiChat.ts +114 -0
- package/lib/hooks/useConversations.ts +376 -0
- package/lib/hooks/useOrchestratorChat.ts +122 -0
- package/lib/hooks/usePersistentChat.ts +315 -0
- package/lib/hooks/useStreamingChat.ts +127 -0
- package/lib/hooks/useTokenUsage.ts +63 -0
- package/lib/langchain/agents/customer-assistant.md +69 -0
- package/lib/langchain/agents/index.ts +61 -0
- package/lib/langchain/agents/orchestrator.md +59 -0
- package/lib/langchain/agents/page-assistant.md +85 -0
- package/lib/langchain/agents/single-agent.md +46 -0
- package/lib/langchain/agents/task-assistant.md +55 -0
- package/lib/langchain/config.ts +45 -0
- package/lib/langchain/handlers/customer-handler.ts +338 -0
- package/lib/langchain/handlers/page-handler.ts +232 -0
- package/lib/langchain/handlers/task-handler.ts +323 -0
- package/lib/langchain/langchain.config.ts +223 -0
- package/lib/langchain/observability.config.ts +30 -0
- package/lib/langchain/orchestrator.ts +562 -0
- package/lib/langchain/tools/customers.ts +176 -0
- package/lib/langchain/tools/index.ts +10 -0
- package/lib/langchain/tools/orchestrator.ts +92 -0
- package/lib/langchain/tools/pages.ts +289 -0
- package/lib/langchain/tools/tasks.ts +167 -0
- package/lib/scheduled-actions/billing.ts +149 -0
- package/lib/scheduled-actions/index.ts +170 -0
- package/lib/scheduled-actions/webhook.ts +231 -0
- package/lib/selectors.ts +197 -0
- package/messages/de/admin.json +219 -0
- package/messages/de/aiUsage.json +36 -0
- package/messages/de/buttons.json +19 -0
- package/messages/de/categories.json +35 -0
- package/messages/de/common.json +16 -0
- package/messages/de/dev.json +101 -0
- package/messages/de/docs.json +27 -0
- package/messages/de/entities.json +7 -0
- package/messages/de/features.json +119 -0
- package/messages/de/footer.json +22 -0
- package/messages/de/home.json +57 -0
- package/messages/de/index.ts +39 -0
- package/messages/de/mobileNav.json +13 -0
- package/messages/de/navigation.json +8 -0
- package/messages/de/observability.json +74 -0
- package/messages/de/posts.json +54 -0
- package/messages/de/pricing.json +102 -0
- package/messages/de/support.json +9 -0
- package/messages/de/teams.json +8 -0
- package/messages/en/admin.json +219 -0
- package/messages/en/aiUsage.json +36 -0
- package/messages/en/buttons.json +19 -0
- package/messages/en/categories.json +35 -0
- package/messages/en/common.json +16 -0
- package/messages/en/dev.json +106 -0
- package/messages/en/docs.json +27 -0
- package/messages/en/entities.json +7 -0
- package/messages/en/features.json +119 -0
- package/messages/en/footer.json +22 -0
- package/messages/en/home.json +57 -0
- package/messages/en/index.ts +39 -0
- package/messages/en/mobileNav.json +13 -0
- package/messages/en/navigation.json +8 -0
- package/messages/en/observability.json +74 -0
- package/messages/en/posts.json +54 -0
- package/messages/en/pricing.json +102 -0
- package/messages/en/support.json +9 -0
- package/messages/en/teams.json +8 -0
- package/messages/es/admin.json +219 -0
- package/messages/es/aiUsage.json +36 -0
- package/messages/es/buttons.json +19 -0
- package/messages/es/categories.json +35 -0
- package/messages/es/common.json +16 -0
- package/messages/es/dev.json +101 -0
- package/messages/es/docs.json +27 -0
- package/messages/es/entities.json +7 -0
- package/messages/es/features.json +119 -0
- package/messages/es/footer.json +22 -0
- package/messages/es/home.json +57 -0
- package/messages/es/index.ts +39 -0
- package/messages/es/mobileNav.json +13 -0
- package/messages/es/navigation.json +8 -0
- package/messages/es/observability.json +74 -0
- package/messages/es/posts.json +54 -0
- package/messages/es/pricing.json +102 -0
- package/messages/es/support.json +9 -0
- package/messages/es/teams.json +8 -0
- package/messages/fr/admin.json +219 -0
- package/messages/fr/aiUsage.json +36 -0
- package/messages/fr/buttons.json +19 -0
- package/messages/fr/categories.json +35 -0
- package/messages/fr/common.json +16 -0
- package/messages/fr/dev.json +101 -0
- package/messages/fr/docs.json +27 -0
- package/messages/fr/entities.json +7 -0
- package/messages/fr/features.json +119 -0
- package/messages/fr/footer.json +22 -0
- package/messages/fr/home.json +57 -0
- package/messages/fr/index.ts +39 -0
- package/messages/fr/mobileNav.json +13 -0
- package/messages/fr/navigation.json +8 -0
- package/messages/fr/observability.json +74 -0
- package/messages/fr/posts.json +54 -0
- package/messages/fr/pricing.json +102 -0
- package/messages/fr/support.json +9 -0
- package/messages/fr/teams.json +8 -0
- package/messages/it/admin.json +219 -0
- package/messages/it/aiUsage.json +36 -0
- package/messages/it/buttons.json +19 -0
- package/messages/it/categories.json +35 -0
- package/messages/it/common.json +16 -0
- package/messages/it/dev.json +101 -0
- package/messages/it/docs.json +27 -0
- package/messages/it/entities.json +7 -0
- package/messages/it/features.json +119 -0
- package/messages/it/footer.json +22 -0
- package/messages/it/home.json +57 -0
- package/messages/it/index.ts +39 -0
- package/messages/it/mobileNav.json +13 -0
- package/messages/it/navigation.json +8 -0
- package/messages/it/observability.json +74 -0
- package/messages/it/posts.json +54 -0
- package/messages/it/pricing.json +102 -0
- package/messages/it/support.json +9 -0
- package/messages/it/teams.json +8 -0
- package/messages/pt/admin.json +219 -0
- package/messages/pt/aiUsage.json +36 -0
- package/messages/pt/buttons.json +19 -0
- package/messages/pt/categories.json +35 -0
- package/messages/pt/common.json +16 -0
- package/messages/pt/dev.json +101 -0
- package/messages/pt/docs.json +27 -0
- package/messages/pt/entities.json +7 -0
- package/messages/pt/features.json +119 -0
- package/messages/pt/footer.json +22 -0
- package/messages/pt/home.json +57 -0
- package/messages/pt/index.ts +39 -0
- package/messages/pt/mobileNav.json +13 -0
- package/messages/pt/navigation.json +8 -0
- package/messages/pt/observability.json +74 -0
- package/messages/pt/posts.json +54 -0
- package/messages/pt/pricing.json +102 -0
- package/messages/pt/support.json +9 -0
- package/messages/pt/teams.json +8 -0
- package/migrations/089_add_editor_team_role.sql +39 -0
- package/migrations/090_demo_users_teams.sql +540 -0
- package/migrations/091_greek_teams_billing.sql +523 -0
- package/migrations/092_billing_sample_data.sql +774 -0
- package/migrations/093_pages_sample_data.sql +1158 -0
- package/migrations/094_posts_sample_data.sql +278 -0
- package/migrations/095_tasks_sample_data.sql +440 -0
- package/migrations/096_customers_sample_data.sql +358 -0
- package/migrations/097_scheduled_actions_sample_data.sql +111 -0
- package/package.json +22 -0
- package/public/docs/desktop-layout-example.png +0 -0
- package/styles/components.css +11 -0
- package/styles/globals.css +179 -0
- package/templates/(public)/blog/[slug]/page.tsx +65 -0
- package/templates/(public)/layout.tsx +25 -0
- package/templates/(public)/page.tsx +200 -0
- package/templates/(public)/support/page.tsx +321 -0
- package/templates/dashboard/(main)/agent-multi/page.tsx +63 -0
- package/templates/dashboard/(main)/agent-single/page.tsx +142 -0
- package/templates/dashboard/(main)/settings/ai-usage/page.tsx +157 -0
- package/templates/superadmin/ai-observability/[traceId]/page.tsx +27 -0
- package/templates/superadmin/ai-observability/page.tsx +17 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* ORCHESTRATOR HANDLER
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Routes user requests to specialized agents based on intent analysis.
|
|
7
|
+
*
|
|
8
|
+
* TWO MODES:
|
|
9
|
+
* 1. Graph-based (new, efficient) - Uses LangGraph explicit state machine
|
|
10
|
+
* - 1-2 LLM calls instead of 50+
|
|
11
|
+
* - No recursion limit issues
|
|
12
|
+
* - Deterministic execution
|
|
13
|
+
*
|
|
14
|
+
* 2. ReAct-based (legacy) - Uses ReAct loops with sub-agents
|
|
15
|
+
* - More flexible but slower
|
|
16
|
+
* - Can hit recursion limits on complex queries
|
|
17
|
+
*
|
|
18
|
+
* Set LANGCHAIN_USE_GRAPH_ORCHESTRATOR=true to enable graph mode.
|
|
19
|
+
*
|
|
20
|
+
* Configuration is centralized in: langchain.config.ts
|
|
21
|
+
*
|
|
22
|
+
* ============================================================================
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { createAgent } from '@/plugins/langchain/lib/agent-factory'
|
|
26
|
+
import { invokeOrchestrator as invokeGraphOrchestrator } from '@/plugins/langchain/lib/graph'
|
|
27
|
+
import { memoryStore, createHumanMessage, createAIMessage } from '@/plugins/langchain/lib/memory-store'
|
|
28
|
+
import { compilePrompt, hasTemplateVariables } from '@/plugins/langchain/lib/prompt-renderer'
|
|
29
|
+
import { tracer } from '@/plugins/langchain/lib/tracer'
|
|
30
|
+
import type { AgentContext } from '@/plugins/langchain/types/langchain.types'
|
|
31
|
+
import type { ModelConfig } from '@/plugins/langchain/lib/graph/types'
|
|
32
|
+
import { loadSystemPrompt } from './agents'
|
|
33
|
+
import {
|
|
34
|
+
getAgentConfig,
|
|
35
|
+
getAgentModelConfig,
|
|
36
|
+
getAgentTools,
|
|
37
|
+
getAgentSessionConfig,
|
|
38
|
+
getAgentEnrichContext,
|
|
39
|
+
AGENTS,
|
|
40
|
+
observabilityConfig,
|
|
41
|
+
} from './langchain.config'
|
|
42
|
+
import type { RoutingResult, ClarificationResult } from './tools/orchestrator'
|
|
43
|
+
import { orchestratorConfig } from './config'
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// INITIALIZATION
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
// Initialize tracer with theme's observability config
|
|
50
|
+
tracer.init(observabilityConfig.observability)
|
|
51
|
+
|
|
52
|
+
// Cache for compiled Handlebars templates (performance optimization)
|
|
53
|
+
const compiledTemplateCache = new Map<string, (context: AgentContext) => string>()
|
|
54
|
+
|
|
55
|
+
// Sub-agent types that can be routed to
|
|
56
|
+
type AgentType = 'task' | 'customer' | 'page'
|
|
57
|
+
|
|
58
|
+
// Mapping from routing type to agent name in config
|
|
59
|
+
const AGENT_NAME_MAP: Record<AgentType, string> = {
|
|
60
|
+
task: 'task-assistant',
|
|
61
|
+
customer: 'customer-assistant',
|
|
62
|
+
page: 'page-assistant',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Message type from LangChain (simplified for orchestration)
|
|
66
|
+
interface AgentMessage {
|
|
67
|
+
_getType(): string
|
|
68
|
+
content: string | unknown
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Context for orchestrator operations
|
|
73
|
+
*/
|
|
74
|
+
export interface OrchestratorContext {
|
|
75
|
+
userId: string
|
|
76
|
+
teamId: string
|
|
77
|
+
sessionId: string
|
|
78
|
+
/** User display name for logging (e.g., "Carlos García") */
|
|
79
|
+
userName?: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Response from the orchestrator
|
|
84
|
+
*/
|
|
85
|
+
export interface OrchestratorResponse {
|
|
86
|
+
content: string
|
|
87
|
+
sessionId: string
|
|
88
|
+
agentUsed?: 'orchestrator' | AgentType
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Format a clarification question for the user
|
|
93
|
+
*/
|
|
94
|
+
function formatClarificationQuestion(result: ClarificationResult): string {
|
|
95
|
+
let response = result.question + '\n\n'
|
|
96
|
+
result.options.forEach((opt, i) => {
|
|
97
|
+
response += `${i + 1}. **${opt.label}**: ${opt.description}\n`
|
|
98
|
+
})
|
|
99
|
+
return response
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse JSON content to determine routing
|
|
104
|
+
*/
|
|
105
|
+
function parseRoutingDecision(content: string): RoutingResult | ClarificationResult | null {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(content)
|
|
108
|
+
|
|
109
|
+
// Check if it's a clarification request
|
|
110
|
+
if (parsed.action === 'clarify') {
|
|
111
|
+
return parsed as ClarificationResult
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if it's a routing decision
|
|
115
|
+
if (parsed.agent && ['task', 'customer', 'page'].includes(parsed.agent)) {
|
|
116
|
+
return parsed as RoutingResult
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return null
|
|
120
|
+
} catch {
|
|
121
|
+
// Not a JSON response
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Extract routing decision from the LATEST turn only
|
|
128
|
+
*
|
|
129
|
+
* IMPORTANT: We only look at messages from the current turn (after the last human message)
|
|
130
|
+
* to avoid picking up stale routing decisions from conversation history.
|
|
131
|
+
*
|
|
132
|
+
* @param messages - Full message array from agent
|
|
133
|
+
* @returns Routing decision if found in current turn, null otherwise
|
|
134
|
+
*/
|
|
135
|
+
function extractRoutingFromMessages(messages: AgentMessage[]): RoutingResult | ClarificationResult | null {
|
|
136
|
+
// Find the index of the last human message (start of current turn)
|
|
137
|
+
let lastHumanIndex = -1
|
|
138
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
139
|
+
if (messages[i]._getType() === 'human') {
|
|
140
|
+
lastHumanIndex = i
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Only search for tool results AFTER the last human message (current turn)
|
|
146
|
+
for (let i = messages.length - 1; i > lastHumanIndex; i--) {
|
|
147
|
+
const msg = messages[i]
|
|
148
|
+
if (msg._getType() === 'tool') {
|
|
149
|
+
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
|
150
|
+
const decision = parseRoutingDecision(content)
|
|
151
|
+
if (decision) {
|
|
152
|
+
return decision
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Parse routing from AI text output (fallback for models that don't call tools properly)
|
|
161
|
+
*/
|
|
162
|
+
function parseRoutingFromAIText(content: string): RoutingResult | null {
|
|
163
|
+
const routeMatch = content.match(/route_to_(task|customer|page)\s*\(\s*\{[^}]*"message"\s*:\s*"([^"]+)"/)
|
|
164
|
+
if (routeMatch) {
|
|
165
|
+
return {
|
|
166
|
+
agent: routeMatch[1] as AgentType,
|
|
167
|
+
message: routeMatch[2],
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get system prompt for an agent
|
|
175
|
+
* Handles both inline prompts and file-based prompts
|
|
176
|
+
* Renders Handlebars templates with context data
|
|
177
|
+
* Uses compiled template cache for performance
|
|
178
|
+
*/
|
|
179
|
+
function getSystemPromptForAgent(agentName: string, context?: AgentContext): string {
|
|
180
|
+
const agentConfig = getAgentConfig(agentName)
|
|
181
|
+
if (!agentConfig?.systemPrompt) {
|
|
182
|
+
throw new Error(`No system prompt configured for agent: ${agentName}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check cache first for compiled template
|
|
186
|
+
let compiledFn = compiledTemplateCache.get(agentName)
|
|
187
|
+
|
|
188
|
+
if (!compiledFn) {
|
|
189
|
+
let template: string
|
|
190
|
+
|
|
191
|
+
// If it's an inline prompt (contains newlines), use directly
|
|
192
|
+
if (agentConfig.systemPrompt.includes('\n')) {
|
|
193
|
+
template = agentConfig.systemPrompt
|
|
194
|
+
} else {
|
|
195
|
+
// Otherwise load from .md file
|
|
196
|
+
template = loadSystemPrompt(agentConfig.systemPrompt as any)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Only compile if template has Handlebars syntax
|
|
200
|
+
if (hasTemplateVariables(template)) {
|
|
201
|
+
compiledFn = compilePrompt(template)
|
|
202
|
+
compiledTemplateCache.set(agentName, compiledFn)
|
|
203
|
+
} else {
|
|
204
|
+
// No template variables, return as-is
|
|
205
|
+
return template
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Render template with context if provided
|
|
210
|
+
if (context && compiledFn) {
|
|
211
|
+
return compiledFn(context)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Fallback: return raw template if no context
|
|
215
|
+
const rawTemplate = agentConfig.systemPrompt.includes('\n')
|
|
216
|
+
? agentConfig.systemPrompt
|
|
217
|
+
: loadSystemPrompt(agentConfig.systemPrompt as any)
|
|
218
|
+
return rawTemplate
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Invoke a specialized sub-agent
|
|
223
|
+
*
|
|
224
|
+
* CONTEXT INHERITANCE: If parentContext is provided (from orchestrator),
|
|
225
|
+
* it will be merged with sub-agent's enriched data to avoid duplicate queries.
|
|
226
|
+
*
|
|
227
|
+
* @param agentType - The type of agent to invoke
|
|
228
|
+
* @param message - The message to process
|
|
229
|
+
* @param context - Base orchestrator context
|
|
230
|
+
* @param parentContext - Optional pre-enriched context from parent (orchestrator)
|
|
231
|
+
* @param parentId - Optional parent trace ID for observability linking
|
|
232
|
+
*/
|
|
233
|
+
async function invokeSubAgent(
|
|
234
|
+
agentType: AgentType,
|
|
235
|
+
message: string,
|
|
236
|
+
context: OrchestratorContext,
|
|
237
|
+
parentContext?: AgentContext,
|
|
238
|
+
parentId?: string
|
|
239
|
+
): Promise<OrchestratorResponse> {
|
|
240
|
+
const { userId, teamId, sessionId } = context
|
|
241
|
+
const agentName = AGENT_NAME_MAP[agentType]
|
|
242
|
+
|
|
243
|
+
// Start with parent context if available, otherwise build base
|
|
244
|
+
let agentContext: AgentContext = parentContext
|
|
245
|
+
? { ...parentContext }
|
|
246
|
+
: { userId, teamId }
|
|
247
|
+
|
|
248
|
+
// Only enrich if not already enriched by parent OR if agent has specific enrichment
|
|
249
|
+
const enrichContext = getAgentEnrichContext(agentName)
|
|
250
|
+
if (enrichContext) {
|
|
251
|
+
// Check if already enriched by parent
|
|
252
|
+
const isAlreadyEnriched = parentContext?.__enriched === true
|
|
253
|
+
|
|
254
|
+
if (!isAlreadyEnriched) {
|
|
255
|
+
// First-time enrichment
|
|
256
|
+
agentContext = await enrichContext(agentContext)
|
|
257
|
+
agentContext.__enriched = true
|
|
258
|
+
} else {
|
|
259
|
+
// Parent already enriched - only add agent-specific data
|
|
260
|
+
// Merge parent context with any agent-specific additions
|
|
261
|
+
const agentSpecificContext = await enrichContext({ userId, teamId })
|
|
262
|
+
agentContext = {
|
|
263
|
+
...agentContext,
|
|
264
|
+
...agentSpecificContext,
|
|
265
|
+
// Keep the enriched flag and core fields
|
|
266
|
+
userId,
|
|
267
|
+
teamId,
|
|
268
|
+
__enriched: true,
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Get session config for memory customization
|
|
274
|
+
const sessionConfig = getAgentSessionConfig(agentName)
|
|
275
|
+
|
|
276
|
+
const agent = await createAgent({
|
|
277
|
+
sessionId: `${sessionId}-${agentType}`,
|
|
278
|
+
agentName,
|
|
279
|
+
systemPrompt: getSystemPromptForAgent(agentName, agentContext),
|
|
280
|
+
tools: getAgentTools(agentName, agentContext),
|
|
281
|
+
modelConfig: getAgentModelConfig(agentName),
|
|
282
|
+
context: agentContext,
|
|
283
|
+
sessionConfig,
|
|
284
|
+
parentId, // Link to orchestrator's trace for observability
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const response = await agent.chat(message)
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
content: response.content,
|
|
291
|
+
sessionId,
|
|
292
|
+
agentUsed: agentType,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Process a message through the orchestrator
|
|
298
|
+
*
|
|
299
|
+
* The orchestrator:
|
|
300
|
+
* 1. Analyzes the user's request
|
|
301
|
+
* 2. Determines which agent should handle it (or asks for clarification)
|
|
302
|
+
* 3. Routes to the appropriate specialized agent
|
|
303
|
+
* 4. Returns the response
|
|
304
|
+
*
|
|
305
|
+
* @param message - The user's message
|
|
306
|
+
* @param context - User and session context
|
|
307
|
+
* @returns The response from the appropriate agent
|
|
308
|
+
*/
|
|
309
|
+
export async function processWithOrchestrator(
|
|
310
|
+
message: string,
|
|
311
|
+
context: OrchestratorContext
|
|
312
|
+
): Promise<OrchestratorResponse> {
|
|
313
|
+
const { userId, teamId, sessionId } = context
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
// Build base context for orchestrator
|
|
317
|
+
let orchestratorContext: AgentContext = { userId, teamId }
|
|
318
|
+
|
|
319
|
+
// Enrich context with runtime data if enrichContext is defined
|
|
320
|
+
const enrichContext = getAgentEnrichContext('orchestrator')
|
|
321
|
+
if (enrichContext) {
|
|
322
|
+
orchestratorContext = await enrichContext(orchestratorContext)
|
|
323
|
+
// Mark as enriched to avoid duplicate enrichment in sub-agents
|
|
324
|
+
orchestratorContext.__enriched = true
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Get session config for memory customization
|
|
328
|
+
const sessionConfig = getAgentSessionConfig('orchestrator')
|
|
329
|
+
|
|
330
|
+
// Step 1: Create orchestrator agent to determine routing
|
|
331
|
+
const orchestratorAgent = await createAgent({
|
|
332
|
+
sessionId: `${sessionId}-orchestrator`,
|
|
333
|
+
agentName: 'orchestrator',
|
|
334
|
+
systemPrompt: getSystemPromptForAgent('orchestrator', orchestratorContext),
|
|
335
|
+
tools: getAgentTools('orchestrator', orchestratorContext),
|
|
336
|
+
modelConfig: getAgentModelConfig('orchestrator'),
|
|
337
|
+
context: orchestratorContext,
|
|
338
|
+
sessionConfig,
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
// Step 2: Get routing decision from orchestrator
|
|
342
|
+
const routingResponse = await orchestratorAgent.chat(message)
|
|
343
|
+
const orchestratorTraceId = routingResponse.traceId // Capture for parent-child linking
|
|
344
|
+
|
|
345
|
+
// Step 3: Extract routing decision from tool results in messages
|
|
346
|
+
let decision = extractRoutingFromMessages(routingResponse.messages || [])
|
|
347
|
+
|
|
348
|
+
// Fallback: If no tool was called, try to parse the AI's text output
|
|
349
|
+
if (!decision && typeof routingResponse.content === 'string') {
|
|
350
|
+
decision = parseRoutingFromAIText(routingResponse.content)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!decision) {
|
|
354
|
+
// Orchestrator responded directly (e.g., greeting, meta-question)
|
|
355
|
+
return {
|
|
356
|
+
content: routingResponse.content,
|
|
357
|
+
sessionId,
|
|
358
|
+
agentUsed: 'orchestrator',
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Step 4: Handle clarification requests
|
|
363
|
+
if ('action' in decision && decision.action === 'clarify') {
|
|
364
|
+
return {
|
|
365
|
+
content: formatClarificationQuestion(decision),
|
|
366
|
+
sessionId,
|
|
367
|
+
agentUsed: 'orchestrator',
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Step 5: Route to specialized agent with inherited context
|
|
372
|
+
if ('agent' in decision) {
|
|
373
|
+
return await invokeSubAgent(
|
|
374
|
+
decision.agent,
|
|
375
|
+
decision.message || message,
|
|
376
|
+
context,
|
|
377
|
+
orchestratorContext, // Pass enriched context to avoid duplicate queries
|
|
378
|
+
orchestratorTraceId // Pass trace ID for parent-child linking
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Fallback: return orchestrator's response
|
|
383
|
+
return {
|
|
384
|
+
content: routingResponse.content,
|
|
385
|
+
sessionId,
|
|
386
|
+
agentUsed: 'orchestrator',
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
error instanceof Error ? error.message : 'Failed to process message'
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// GRAPH-BASED ORCHESTRATOR (NEW - EFFICIENT)
|
|
397
|
+
// ============================================================================
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Check if graph-based orchestrator is enabled
|
|
401
|
+
*/
|
|
402
|
+
function isGraphOrchestratorEnabled(): boolean {
|
|
403
|
+
return process.env.LANGCHAIN_USE_GRAPH_ORCHESTRATOR === 'true'
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Process a message using the graph-based orchestrator
|
|
408
|
+
*
|
|
409
|
+
* BENEFITS:
|
|
410
|
+
* - 1-2 LLM calls instead of 50+
|
|
411
|
+
* - No recursion limit issues
|
|
412
|
+
* - Deterministic execution
|
|
413
|
+
* - Parallel handler execution for multi-intent queries
|
|
414
|
+
*
|
|
415
|
+
* @param message - The user's message
|
|
416
|
+
* @param context - User and session context
|
|
417
|
+
* @returns The response
|
|
418
|
+
*/
|
|
419
|
+
async function processWithGraphOrchestratorInternal(
|
|
420
|
+
message: string,
|
|
421
|
+
context: OrchestratorContext
|
|
422
|
+
): Promise<OrchestratorResponse> {
|
|
423
|
+
const { userId, teamId, sessionId, userName } = context
|
|
424
|
+
const agentContext: AgentContext = { userId, teamId, userName }
|
|
425
|
+
|
|
426
|
+
// Start trace for observability
|
|
427
|
+
const traceContext = await tracer.startTrace(
|
|
428
|
+
{ userId, teamId },
|
|
429
|
+
'graph-orchestrator',
|
|
430
|
+
message,
|
|
431
|
+
{
|
|
432
|
+
sessionId,
|
|
433
|
+
agentType: 'graph-orchestrator',
|
|
434
|
+
metadata: {
|
|
435
|
+
orchestratorType: 'langgraph',
|
|
436
|
+
version: '1.0',
|
|
437
|
+
},
|
|
438
|
+
tags: ['graph', 'orchestrator'],
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
const startTime = Date.now()
|
|
443
|
+
let llmCalls = 0
|
|
444
|
+
let toolCalls = 0
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
// Get conversation history from memory store
|
|
448
|
+
const conversationHistory = await memoryStore.getMessages(
|
|
449
|
+
`${sessionId}-orchestrator`,
|
|
450
|
+
agentContext
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
// Get model config for router
|
|
454
|
+
const orchestratorModelConfig = getAgentModelConfig('orchestrator')
|
|
455
|
+
const modelConfig: ModelConfig = {
|
|
456
|
+
provider: orchestratorModelConfig?.provider || 'openai',
|
|
457
|
+
model: orchestratorModelConfig?.model,
|
|
458
|
+
temperature: 0.1, // Low temperature for consistent routing
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Invoke the graph with orchestrator configuration (tools registered by theme)
|
|
462
|
+
const result = await invokeGraphOrchestrator(
|
|
463
|
+
message,
|
|
464
|
+
sessionId,
|
|
465
|
+
agentContext,
|
|
466
|
+
conversationHistory.slice(-5), // Last 5 messages for context
|
|
467
|
+
orchestratorConfig, // Pass config instead of individual handlers
|
|
468
|
+
{
|
|
469
|
+
traceId: traceContext?.traceId,
|
|
470
|
+
modelConfig,
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
// Count LLM calls based on what was executed
|
|
475
|
+
// Router always uses 1 LLM call
|
|
476
|
+
llmCalls = 1
|
|
477
|
+
// Combiner uses 1 LLM call if there were multiple intents or handlers
|
|
478
|
+
const handlersExecuted = result.completedHandlers?.length || 0
|
|
479
|
+
if (handlersExecuted > 1 || (handlersExecuted === 1 && result.intents?.length > 1)) {
|
|
480
|
+
llmCalls = 2
|
|
481
|
+
}
|
|
482
|
+
// Tool calls = number of handlers executed (task, customer, page operations)
|
|
483
|
+
toolCalls = handlersExecuted
|
|
484
|
+
|
|
485
|
+
// Save the interaction to memory
|
|
486
|
+
await memoryStore.addMessages(
|
|
487
|
+
`${sessionId}-orchestrator`,
|
|
488
|
+
[
|
|
489
|
+
createHumanMessage(message),
|
|
490
|
+
createAIMessage(result.finalResponse || 'No response generated'),
|
|
491
|
+
],
|
|
492
|
+
agentContext
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
// End trace with success
|
|
496
|
+
if (traceContext) {
|
|
497
|
+
await tracer.endTrace(
|
|
498
|
+
{ userId, teamId },
|
|
499
|
+
traceContext.traceId,
|
|
500
|
+
{
|
|
501
|
+
output: result.finalResponse || '',
|
|
502
|
+
llmCalls,
|
|
503
|
+
toolCalls,
|
|
504
|
+
metadata: {
|
|
505
|
+
durationMs: Date.now() - startTime,
|
|
506
|
+
intentsDetected: result.intents?.length || 0,
|
|
507
|
+
handlersExecuted: result.completedHandlers || [],
|
|
508
|
+
hadClarification: result.needsClarification || false,
|
|
509
|
+
},
|
|
510
|
+
}
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
content: result.finalResponse || 'I was unable to process your request.',
|
|
516
|
+
sessionId,
|
|
517
|
+
agentUsed: 'orchestrator',
|
|
518
|
+
}
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error('[GraphOrchestrator] Error:', error)
|
|
521
|
+
|
|
522
|
+
// End trace with error
|
|
523
|
+
if (traceContext) {
|
|
524
|
+
await tracer.endTrace(
|
|
525
|
+
{ userId, teamId },
|
|
526
|
+
traceContext.traceId,
|
|
527
|
+
{
|
|
528
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
529
|
+
llmCalls,
|
|
530
|
+
toolCalls,
|
|
531
|
+
metadata: {
|
|
532
|
+
durationMs: Date.now() - startTime,
|
|
533
|
+
},
|
|
534
|
+
}
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
throw new Error(
|
|
539
|
+
error instanceof Error ? error.message : 'Failed to process message with graph orchestrator'
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Unified entry point - uses graph or ReAct based on feature flag
|
|
546
|
+
*
|
|
547
|
+
* @param message - The user's message
|
|
548
|
+
* @param context - User and session context
|
|
549
|
+
* @returns The response from the appropriate agent
|
|
550
|
+
*/
|
|
551
|
+
export async function processMessage(
|
|
552
|
+
message: string,
|
|
553
|
+
context: OrchestratorContext
|
|
554
|
+
): Promise<OrchestratorResponse> {
|
|
555
|
+
if (isGraphOrchestratorEnabled()) {
|
|
556
|
+
console.log('[Orchestrator] Using graph-based orchestrator')
|
|
557
|
+
return processWithGraphOrchestratorInternal(message, context)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Fallback to legacy ReAct-based orchestrator
|
|
561
|
+
return processWithOrchestrator(message, context)
|
|
562
|
+
}
|