UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

292 lines (287 loc) 11.5 kB
import * as i0 from '@angular/core'; import { Injectable } from '@angular/core'; import * as i1 from '@c8y/client'; import { Observable } from 'rxjs'; var DataStreamType; (function (DataStreamType) { DataStreamType["TEXT_DELTA"] = "text-delta"; DataStreamType["TOOL_CALL"] = "tool-call"; DataStreamType["TOOL_CALL_STREAMING"] = "tool-call-streaming"; DataStreamType["TOOL_CALL_DELTA"] = "tool-call-delta"; DataStreamType["TOOL_RESULT"] = "tool-result"; DataStreamType["REASONING"] = "reasoning"; DataStreamType["REASONING_DELTA"] = "reasoning-delta"; DataStreamType["REDACTED_REASONING"] = "redacted-reasoning"; DataStreamType["REASONING_SIGNATURE"] = "reasoning-signature"; DataStreamType["FINISH_REASONING"] = "finish-reasoning"; DataStreamType["FINISH"] = "finish"; DataStreamType["FINISH_STEP"] = "finish-step"; DataStreamType["ERROR"] = "error"; DataStreamType["DATA"] = "data"; DataStreamType["MESSAGE_ANNOTATIONS"] = "message-annotations"; DataStreamType["SOURCE"] = "source"; DataStreamType["FILE"] = "file"; DataStreamType["STEP_START"] = "step-start"; DataStreamType["STEP_FINISH"] = "step-finish"; })(DataStreamType || (DataStreamType = {})); class AIService { constructor(client) { this.client = client; this.baseUrl = '/service/ai'; } async createOrUpdateAgent(agentsDef) { for (const def of agentsDef.definitions) { const health = await this.getAgentHealth(def.name); let resource = `${this.baseUrl}/agent/${def.type}`; let method = 'POST'; if (health.exists) { resource = `${this.baseUrl}/agent/${def.type}/${def.name}`; method = 'PUT'; } const response = await this.client.fetch(resource, { body: JSON.stringify(def), method, headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Failed to create agent: ${response.statusText}`); } } } /** * Check if an agent exists. * @param name Agent name * @returns Agent health check response. */ async getAgentHealth(name) { const response = await this.client.fetch(`${this.baseUrl}/agent/test/${name}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { return { exists: false, canCreate: false, isProviderConfigured: false }; } const json = await response.json(); return json; } /** * Send a text message to the agent. * @param name Agent name * @param messages Messages to send * @param variables Variables to include * @returns Text response from the agent. */ async text(name, messages, variables) { const parsedMessages = this.parseMessages(messages); const data = this.client.fetch(`${this.baseUrl}/agent/text/${name}`, { body: JSON.stringify({ messages: parsedMessages, variables }), method: 'POST', headers: { 'Content-Type': 'application/json' } }); const response = await data; if (!response.ok) { throw new Error(`Failed to talk with agent: ${response.statusText}`); } const text = await response.text(); return text; } /** * Stream a text message to the agent. * @param agentName Agent name * @param messages Messages to send * @param variables Variables to include * @param abortController An AbortController to cancel the request. * @returns An observable that emits partial AIMessage objects as they are received. The observable can be cancelled using the provided AbortController. * The observable will emit an error if the request fails or is aborted. * The observable will complete when the stream is finished. * * The messages sent to the agent can include special options: * - `hiddenContent`: If set, this content will be sent to the agent instead of the `content` field. * - `skipToLLM`: If set to true, this message will be skipped when sending to the agent. * * Example usage: * ```typescript * const abortController = new AbortController(); * const messages: AIMessage[] = [ * { role: 'user', content: 'Hello' }, * { role: 'assistant', content: 'Hi there!' }, * { role: 'user', content: 'Tell me a joke.', options: { hiddenContent: 'Tell me a joke about cats.' } } * ]; * const observable = aiService.stream$('my-agent', messages, {}, abortController); * const subscription = observable.subscribe({ * next: (message) => console.log('Received message part:', message), * error: (err) => console.error('Error:', err), * complete: () => console.log('Stream complete') * }); * * // To cancel the request: * abortController.abort(); * subscription.unsubscribe(); * ``` */ async stream$(agentName, messages, variables, abortController) { const parsedMessages = this.parseMessages(messages); const response = await this.client.fetch(`${this.baseUrl}/agent/text/${agentName}?fullResponse=true`, { method: 'POST', body: JSON.stringify({ messages: parsedMessages, variables }), headers: { ...this.client.defaultHeaders, 'content-type': 'application/json', accept: 'text/event-stream' }, signal: abortController.signal }); if (response.status > 300) { const data = await response.json(); throw new Error(JSON.stringify(data, null, 2)); } const stream = response.body; const decoder = new TextDecoder(); return new Observable(observer => { if (!stream) { observer.error('No response body'); throw 'No response body'; } const reader = stream.getReader(); const message = { role: 'assistant', content: '', steps: [] }; const abortHandler = () => { reader.cancel(); observer.error(new DOMException('Aborted', 'AbortError')); }; abortController.signal.addEventListener('abort', abortHandler); let buffer = ''; const read = () => { reader.read().then(({ done, value }) => { if (done) { if (buffer.trim()) this.processLine(buffer, observer, message); // process any remaining data observer.complete(); return; } buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim()) this.processLine(line, observer, message); } read(); }); }; read(); return () => { abortController.signal.removeEventListener('abort', abortHandler); reader.cancel(); }; }); } parseMessages(messages) { return messages .filter(message => !message.options?.skipToLLM) .map(message => { if (message.options?.hiddenContent) { return { role: message.role, content: message.options.hiddenContent }; } return { role: message.role, content: message.content }; }); } processLine(line, observer, message) { if (!line.trim()) { return; } const lastStep = message.steps?.[message.steps.length - 1]; try { let data = {}; let type = ''; try { data = JSON.parse(line.replace('data: ', '')); type = data.type; } catch (e) { console.error('Error parsing line', line, e); return; } switch (type) { case DataStreamType.STEP_START: message.steps = message.steps || []; message.steps.push({ type: 'text', toolCalls: [], toolResults: [], text: '' }); return; case DataStreamType.REASONING: if (lastStep.reasoning === undefined) { lastStep.reasoning = ''; } lastStep.reasoning += data.textDelta; observer.next(message); return; case DataStreamType.TEXT_DELTA: lastStep.text += data.textDelta; message.content += data.textDelta; observer.next(message); return; case DataStreamType.TOOL_CALL: lastStep.toolCalls = [data]; observer.next(message); return; case DataStreamType.TOOL_RESULT: lastStep.toolResults = [data]; observer.next(message); return; case DataStreamType.FINISH: message.finishReason = data.finishReason; observer.next(message); observer.complete(); return; case DataStreamType.ERROR: message.finishReason = 'error'; const errorMessage = `<div class="alert alert-danger" role="alert" > <strong>Error</strong> ${data.message || 'An unknown error occurred'} </div>`; message.content += errorMessage; lastStep.text += errorMessage; observer.next(message); observer.error(data); observer.complete(); return; } } catch (e) { observer.error(e); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AIService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AIService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AIService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.FetchClient }] }); /** * Generated bundle index. Do not edit. */ export { AIService, DataStreamType }; //# sourceMappingURL=c8y-ngx-components-ai.mjs.map