@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
292 lines (287 loc) • 11.5 kB
JavaScript
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