1+ /**
2+ * Core LLM abstraction for generating text
3+ */
4+ import { FunctionDefinition , GenerateOptions , LLMResponse , Message , ToolCall } from './types.js' ;
5+ import { LLMProvider } from './provider.js' ;
6+
7+ /**
8+ * Generate text using the specified LLM provider
9+ *
10+ * @param provider The LLM provider implementation
11+ * @param options Options for generation including messages, functions, etc.
12+ * @returns A response containing generated text and/or tool calls
13+ */
14+ export async function generateText (
15+ provider : LLMProvider ,
16+ options : GenerateOptions
17+ ) : Promise < LLMResponse > {
18+ // Validate options
19+ if ( ! options . messages || options . messages . length === 0 ) {
20+ throw new Error ( 'Messages array cannot be empty' ) ;
21+ }
22+
23+ // Use the provider to generate the response
24+ return provider . generateText ( options ) ;
25+ }
26+
27+ /**
28+ * Format tool calls for consistent usage across providers
29+ *
30+ * @param rawToolCalls Tool calls from provider
31+ * @returns Normalized tool calls
32+ */
33+ export function normalizeToolCalls ( rawToolCalls : any [ ] ) : ToolCall [ ] {
34+ if ( ! rawToolCalls || ! Array . isArray ( rawToolCalls ) || rawToolCalls . length === 0 ) {
35+ return [ ] ;
36+ }
37+
38+ return rawToolCalls . map ( ( call ) => {
39+ // Handle different provider formats
40+ if ( typeof call . arguments === 'string' ) {
41+ // Already in correct format
42+ return {
43+ id : call . id || `tool-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 11 ) } ` ,
44+ name : call . name || call . function ?. name ,
45+ arguments : call . arguments
46+ } ;
47+ } else if ( typeof call . arguments === 'object' ) {
48+ // Convert object to JSON string
49+ return {
50+ id : call . id || `tool-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 11 ) } ` ,
51+ name : call . name || call . function ?. name ,
52+ arguments : JSON . stringify ( call . arguments )
53+ } ;
54+ } else {
55+ throw new Error ( `Unsupported tool call format: ${ JSON . stringify ( call ) } ` ) ;
56+ }
57+ } ) ;
58+ }
59+
60+ /**
61+ * Format function definitions for provider compatibility
62+ *
63+ * @param functions Function definitions
64+ * @returns Normalized function definitions
65+ */
66+ export function normalizeFunctionDefinitions (
67+ functions ?: FunctionDefinition [ ]
68+ ) : FunctionDefinition [ ] {
69+ if ( ! functions || functions . length === 0 ) {
70+ return [ ] ;
71+ }
72+
73+ return functions . map ( ( fn ) => ( {
74+ name : fn . name ,
75+ description : fn . description ,
76+ parameters : fn . parameters
77+ } ) ) ;
78+ }
79+
80+ /**
81+ * Convert messages to provider-specific format if needed
82+ *
83+ * @param messages Array of messages
84+ * @returns Normalized messages
85+ */
86+ export function normalizeMessages ( messages : Message [ ] ) : Message [ ] {
87+ return messages . map ( ( msg : any ) => {
88+ // Ensure content is a string
89+ if ( typeof msg . content !== 'string' ) {
90+ throw new Error ( `Message content must be a string: ${ JSON . stringify ( msg ) } ` ) ;
91+ }
92+
93+ // Handle each role type explicitly
94+ switch ( msg . role ) {
95+ case 'system' :
96+ return {
97+ role : 'system' ,
98+ content : msg . content
99+ } ;
100+ case 'user' :
101+ return {
102+ role : 'user' ,
103+ content : msg . content
104+ } ;
105+ case 'assistant' :
106+ return {
107+ role : 'assistant' ,
108+ content : msg . content
109+ } ;
110+ case 'tool' :
111+ return {
112+ role : 'tool' ,
113+ content : msg . content ,
114+ name : msg . name || 'unknown_tool' // Ensure name is always present for tool messages
115+ } ;
116+ default :
117+ // Use type assertion for unknown roles
118+ console . warn ( `Unexpected message role: ${ String ( msg . role ) } , treating as user message` ) ;
119+ return {
120+ role : 'user' ,
121+ content : msg . content
122+ } ;
123+ }
124+ } ) ;
125+ }
0 commit comments