Merge pull request #938 from trheyi/main

feat: Update asset metadata and enhance Agent class in SDK
This commit is contained in:
Max 2025-05-05 16:24:24 +08:00 committed by GitHub
commit dc961e7af0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 409 additions and 363 deletions

File diff suppressed because one or more lines are too long

View file

@ -4,22 +4,69 @@
* @maintainer https://yaoapps.com * @maintainer https://yaoapps.com
*/ */
/**
* Message structure for agent responses
*/
interface AgentMessage {
text: string;
type?: string;
done?: boolean;
is_neo?: boolean;
assistant_id?: string;
assistant_name?: string;
assistant_avatar?: string;
props?: Record<string, any>;
tool_id?: string;
new?: boolean;
delta?: boolean;
previous_assistant_id?: string;
}
/**
* Done event data structure
*/
type AgentDoneData = AgentMessage[];
/**
* Event handler function types
*/
interface MessageHandler {
(message: AgentMessage): void;
}
interface DoneHandler {
(messages: AgentDoneData): void;
}
/**
* Event types that can be listened to
*/
type AgentEvent = "message" | "done";
/**
* Event handlers record type
*/
interface EventHandlers {
message?: MessageHandler;
done?: DoneHandler;
}
class Agent { class Agent {
private host: string; private host: string;
private token: string; private token: string;
private events: Record<AgentEvent, Handler>; private events: EventHandlers;
private assistant_id?: string; private assistant_id: string;
private chat_id?: string; private chat_id?: string;
/** /**
* Agent constructor * Agent constructor
* @param option Agent initialization options * @param option Agent initialization options
*/ */
constructor(option: AgentOption) { constructor(assistant_id: string, option: AgentOption) {
this.host = option.host || "/__yao/neo"; this.host = option.host || "/api/__yao/neo";
this.token = option.token; this.token = option.token;
this.events = {} as Record<AgentEvent, Handler>; this.events = {};
this.assistant_id = option.assistant_id; this.assistant_id = assistant_id;
this.chat_id = option.chat_id; this.chat_id = option.chat_id;
} }
@ -39,18 +86,32 @@ class Agent {
* @param handler Function to handle the event * @param handler Function to handle the event
* @returns The Agent instance for chaining * @returns The Agent instance for chaining
*/ */
On(event: AgentEvent, handler: Handler): Agent { On<E extends AgentEvent>(
this.events[event] = handler; event: E,
handler: E extends "message" ? MessageHandler : DoneHandler
): Agent {
if (event === "message") {
this.events.message = handler as MessageHandler;
} else if (event === "done") {
this.events.done = handler as DoneHandler;
}
return this; return this;
} }
/** /**
* Call the AI Agent * Call the AI Agent
* @param id Agent ID
* @param input Text message or input object with text and optional attachments * @param input Text message or input object with text and optional attachments
* @param args Additional arguments to pass to the agent * @param args Additional arguments to pass to the agent
*/ */
async Call(id: string, input: AgentInput, ...args: any[]) { async Call(input: AgentInput, ...args: any[]) {
const messages: AgentMessage[] = [];
let currentContent = "";
let lastAssistant = {
assistant_id: null as string | null,
assistant_name: null as string | null,
assistant_avatar: null as string | null,
};
// Process input content // Process input content
let content: AgentInputContent; let content: AgentInputContent;
if (typeof input === "string") { if (typeof input === "string") {
@ -76,11 +137,8 @@ class Agent {
const contentRaw = encodeURIComponent(JSON.stringify(content)); const contentRaw = encodeURIComponent(JSON.stringify(content));
const contextRaw = encodeURIComponent(JSON.stringify(args)); const contextRaw = encodeURIComponent(JSON.stringify(args));
const token = this.token; const token = this.token;
const assistantParam = this.assistant_id
? `&assistant_id=${this.assistant_id}`
: "";
const chatId = this.chat_id || this.makeChatID(); const chatId = this.chat_id || this.makeChatID();
const assistantParam = `&assistant_id=${this.assistant_id}`;
const status_endpoint = `${this.host}/status?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`; const status_endpoint = `${this.host}/status?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`;
const endpoint = `${this.host}?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`; const endpoint = `${this.host}?content=${contentRaw}&context=${contextRaw}&token=${token}&chat_id=${chatId}${assistantParam}`;
@ -115,7 +173,7 @@ class Agent {
"Connection failed: Please check your network connection"; "Connection failed: Please check your network connection";
} }
const messageHandler = this.events["message"]; const messageHandler = this.events["message"] as MessageHandler;
if (messageHandler) { if (messageHandler) {
messageHandler({ messageHandler({
text: errorMessage, text: errorMessage,
@ -125,7 +183,7 @@ class Agent {
}); });
} }
} catch (statusError) { } catch (statusError) {
const messageHandler = this.events["message"]; const messageHandler = this.events["message"] as MessageHandler;
if (messageHandler) { if (messageHandler) {
messageHandler({ messageHandler({
text: "Service unavailable, please try again later", text: "Service unavailable, please try again later",
@ -142,22 +200,8 @@ class Agent {
withCredentials: true, withCredentials: true,
}); });
// Track assistant information across messages
const last_assistant: {
assistant_id: string | null;
assistant_name: string | null;
assistant_avatar: string | null;
} = {
assistant_id: null,
assistant_name: null,
assistant_avatar: null,
};
let content = "";
let last_type: string | null = null;
es.onopen = () => { es.onopen = () => {
const messageHandler = this.events["message"]; const messageHandler = this.events["message"] as MessageHandler;
if (messageHandler) { if (messageHandler) {
messageHandler({ messageHandler({
text: "", text: "",
@ -172,7 +216,7 @@ class Agent {
const formated_data = JSON.parse(data); const formated_data = JSON.parse(data);
if (!formated_data) return; if (!formated_data) return;
const messageHandler = this.events["message"]; const messageHandler = this.events["message"] as MessageHandler;
if (!messageHandler) return; if (!messageHandler) return;
const { const {
@ -195,7 +239,7 @@ class Agent {
const { namespace, primary, data_item, action, extra } = const { namespace, primary, data_item, action, extra } =
props || {}; props || {};
if (action && Array.isArray(action)) { if (action && Array.isArray(action)) {
messageHandler({ const actionMessage = {
text: text || "", text: text || "",
type: "action", type: "action",
props: { props: {
@ -207,94 +251,130 @@ class Agent {
}, },
is_neo: true, is_neo: true,
done: !!done, done: !!done,
}); };
messages.push(actionMessage);
messageHandler(actionMessage);
if (done) { if (done) {
const doneHandler = this.events["done"]; const doneHandler = this.events["done"] as DoneHandler;
if (doneHandler) { doneHandler?.(messages);
doneHandler({
text: text || "",
type: "action",
done: true,
is_neo: true,
});
}
es.close(); es.close();
} }
return; return;
} }
} }
// Update content based on message properties // Check if we need to create a new message
if (text) { const shouldCreateNewMessage =
if (delta) { messages.length === 0 ||
content = content + text; (assistant_id &&
if (text?.startsWith("\r") || is_new) { messages[messages.length - 1].assistant_id !== assistant_id) ||
content = text.replace("\r", ""); (is_new && !delta); // Only create new message if it's new and not a delta update
}
} else {
content = text || "";
}
}
// Update assistant information // Update assistant information
if (assistant_id) { if (assistant_id) lastAssistant.assistant_id = assistant_id;
last_assistant.assistant_id = assistant_id; if (assistant_name) lastAssistant.assistant_name = assistant_name;
} if (assistant_avatar)
if (assistant_name) { lastAssistant.assistant_avatar = assistant_avatar;
last_assistant.assistant_name = assistant_name;
}
if (assistant_avatar) {
last_assistant.assistant_avatar = assistant_avatar;
}
// Prepare message data if (shouldCreateNewMessage) {
const message_data: any = { // Mark the last message as done if it exists
...formated_data, if (messages.length > 0 && messages[messages.length - 1].is_neo) {
text: content, messages[messages.length - 1] = {
assistant_id: last_assistant.assistant_id || undefined, ...messages[messages.length - 1],
assistant_name: last_assistant.assistant_name || undefined, done: true,
assistant_avatar: last_assistant.assistant_avatar || undefined, };
}; }
// Handle tool and think message types // Create new message with all original properties
if ((type === "tool" || type === "think") && delta) { const newMessage = {
message_data.type = "text"; text: text || "",
message_data.props = { type: type || "text",
...(message_data.props || {}), props,
id: tool_id, is_neo: true,
begin, new: is_new, // Only set new if it's from the original message
end, tool_id,
assistant_id: lastAssistant.assistant_id || undefined,
assistant_name: lastAssistant.assistant_name || undefined,
assistant_avatar: lastAssistant.assistant_avatar || undefined,
}; };
// Add closing tag if needed messages.push(newMessage);
if (!content.includes(`</${type}>`)) { messageHandler(newMessage);
message_data.text = `${content}</${type}>`; return;
}
// Get current message (we know it exists because we checked messages.length above)
const current_answer = messages[messages.length - 1];
// Set previous assistant id
if (messages.length > 1) {
const previous_message = messages[messages.length - 2];
if (previous_message.assistant_id) {
current_answer.previous_assistant_id =
previous_message.assistant_id;
} }
} }
// Send message to handler // Handle message completion (done flag is set)
messageHandler(message_data);
// Handle done event
if (done) { if (done) {
const doneHandler = this.events["done"]; if (text) {
if (doneHandler) { current_answer.text = text;
doneHandler({
text: content,
done: true,
is_neo: true,
type: message_data.type,
props: message_data.props,
assistant_id: last_assistant.assistant_id || undefined,
assistant_name: last_assistant.assistant_name || undefined,
assistant_avatar: last_assistant.assistant_avatar || undefined,
});
} }
if (type) {
current_answer.type = type;
}
if (props) {
current_answer.props = props;
}
// Mark all previous neo messages as done
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
if (message.is_neo) {
if (message.done) break;
messages[i] = { ...message, done: true };
}
}
const doneHandler = this.events["done"] as DoneHandler;
doneHandler?.(messages);
es.close(); es.close();
return;
} }
last_type = type || last_type; // Skip processing if no content to update
if (!text && !props && !type) return;
// Update props if available
if (props) {
if (type === "think" || type === "tool") {
current_answer.props = {
...(current_answer.props || {}),
id: tool_id,
begin,
end,
};
} else {
current_answer.props = props;
}
}
// Handle text content
if (text) {
if (delta) {
current_answer.text = (current_answer.text || "") + text;
if (text.startsWith("\r")) {
current_answer.text = text.replace("\r", "");
}
} else {
current_answer.text = text;
}
}
// Send current message to handler
messageHandler(current_answer);
} catch (err) { } catch (err) {
console.error("Failed to parse message:", err); console.error("Failed to parse message:", err);
} }
@ -310,11 +390,6 @@ class Agent {
} }
} }
/**
* Event types that can be listened to
*/
type AgentEvent = "message" | "done";
/** /**
* Attachment information for file uploads * Attachment information for file uploads
*/ */
@ -342,7 +417,12 @@ interface AgentInputContent {
/** /**
* Input type for agent calls, can be either a string or a structured input * Input type for agent calls, can be either a string or a structured input
*/ */
type AgentInput = string | AgentInputContent; type AgentInput =
| string
| {
text: string;
attachments?: AgentAttachment[];
};
/** /**
* Agent initialization options * Agent initialization options
@ -350,40 +430,5 @@ type AgentInput = string | AgentInputContent;
interface AgentOption { interface AgentOption {
host?: string; host?: string;
token: string; token: string;
assistant_id?: string;
chat_id?: string; chat_id?: string;
} }
/**
* Message structure for agent responses
*/
interface AgentMessage {
text: string;
type?: string;
done?: boolean;
is_neo?: boolean;
assistant_id?: string;
assistant_name?: string;
assistant_avatar?: string;
props?: Record<string, any>;
tool_id?: string;
new?: boolean;
delta?: boolean;
}
/**
* Event handler function type
*/
interface Handler {
(message: AgentMessage): void;
}
/**
* Options for agent call configuration
*/
interface AgentCallOption {
model: string;
prompt: string;
temperature: number;
max_tokens: number;
}