@erdoai/server 0.1.7 → 0.1.9

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/README.md CHANGED
@@ -10,7 +10,7 @@ npm install @erdoai/server
10
10
 
11
11
  ## Usage
12
12
 
13
- ### Basic Invocation
13
+ ### Basic Invocation (Server-Only)
14
14
 
15
15
  ```typescript
16
16
  import { ErdoClient } from '@erdoai/server';
@@ -28,7 +28,9 @@ const result = await client.invoke('data-analyst', {
28
28
  console.log(result.messages);
29
29
  ```
30
30
 
31
- ### Streaming
31
+ > **Note**: `invoke()` and `invokeStream()` are for server-side processing only. They use `/bots/{key}/invoke` which returns raw SSE events. For React UI rendering with `@erdoai/ui` components, use thread-based messaging (`sendMessage()` or the `useThread` hook).
32
+
33
+ ### Streaming (Server-Only)
32
34
 
33
35
  ```typescript
34
36
  for await (const event of client.invokeStream('data-analyst', {
@@ -58,7 +60,7 @@ const serverClient = new ErdoClient({
58
60
  authToken: process.env.ERDO_AUTH_TOKEN,
59
61
  });
60
62
 
61
- const { token, expiresAt } = await serverClient.createToken({
63
+ const { token, tokenId, expiresAt } = await serverClient.createToken({
62
64
  botKeys: ['my-org.data-analyst'],
63
65
  externalUserId: 'user_123',
64
66
  expiresInSeconds: 3600,
@@ -151,8 +153,9 @@ class ErdoClient {
151
153
 
152
154
  ```typescript
153
155
  interface CreateTokenParams {
154
- botKeys?: string[]; // Bot keys the token can access
156
+ botKeys?: string[]; // Bot keys the token can access (e.g., "my-org.data-analyst")
155
157
  datasetIds?: string[]; // Dataset IDs the token can access
158
+ threadIds?: string[]; // Thread IDs the token can access
156
159
  externalUserId?: string; // External user ID for RBAC (enables thread access)
157
160
  expiresInSeconds?: number; // Token lifetime (default: 3600)
158
161
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ErdoClientConfig, CreateTokenParams, TokenResponse, CreateThreadParams, Thread, ListThreadsResponse, SendMessageParams, SSEEvent, InvokeParams, InvokeResult } from '@erdoai/types';
1
+ import { ErdoClientConfig, CreateTokenParams, TokenResponse, CreateThreadParams, Thread, ListThreadsResponse, ListThreadMessagesResponse, SendMessageParams, SSEEvent, InvokeParams, InvokeResult } from '@erdoai/types';
2
2
 
3
3
  /**
4
4
  * Erdo Client for invoking agents
@@ -26,7 +26,7 @@ declare class ErdoClient {
26
26
  * ```typescript
27
27
  * // Server-side: Create a token for the frontend
28
28
  * const serverClient = new ErdoClient({ authToken: process.env.ERDO_AUTH_TOKEN });
29
- * const { token, expiresAt } = await serverClient.createToken({
29
+ * const { token, tokenId, expiresAt } = await serverClient.createToken({
30
30
  * botKeys: ['my-org.data-analyst'],
31
31
  * expiresInSeconds: 3600,
32
32
  * });
@@ -78,6 +78,21 @@ declare class ErdoClient {
78
78
  * ```
79
79
  */
80
80
  getThread(threadId: string): Promise<Thread>;
81
+ /**
82
+ * Get messages for a thread (chat history)
83
+ *
84
+ * Requires token authentication.
85
+ * Access is granted via RBAC external_user or explicit thread scope.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const { messages } = await client.getThreadMessages(threadId);
90
+ * for (const msg of messages) {
91
+ * console.log(msg.role, msg.contents.length, 'content items');
92
+ * }
93
+ * ```
94
+ */
95
+ getThreadMessages(threadId: string): Promise<ListThreadMessagesResponse>;
81
96
  /**
82
97
  * Send a message to a thread and stream the bot response
83
98
  *
@@ -106,11 +121,48 @@ declare class ErdoClient {
106
121
  */
107
122
  sendMessageAndWait(threadId: string, params: SendMessageParams): Promise<SSEEvent[]>;
108
123
  /**
109
- * Invoke an agent and wait for the complete result
124
+ * Invoke an agent and wait for the complete result.
125
+ *
126
+ * **Server-only**: This method uses the `/bots/{key}/invoke` endpoint which returns
127
+ * raw SSE events without message wrapping. Use this for server-side processing where
128
+ * you don't need React UI components.
129
+ *
130
+ * **For React UI rendering**, use thread-based messaging instead:
131
+ * - `useThread` hook (client-side with token auth)
132
+ * - `sendMessage()` / `sendMessageAndWait()` (server-side proxy)
133
+ *
134
+ * The thread endpoint wraps events with message metadata required by `handleSSEEvent`
135
+ * and the `Content` component for proper rendering.
136
+ *
137
+ * @example Server-side processing (no React)
138
+ * ```typescript
139
+ * const result = await client.invoke('data-analyst', {
140
+ * messages: [{ role: 'user', content: 'Analyze Q4 sales' }],
141
+ * });
142
+ * // Process result.messages, result.events, etc.
143
+ * ```
110
144
  */
111
145
  invoke(botKey: string, params?: InvokeParams): Promise<InvokeResult>;
112
146
  /**
113
- * Invoke an agent and stream SSE events
147
+ * Invoke an agent and stream SSE events.
148
+ *
149
+ * **Server-only**: This method uses the `/bots/{key}/invoke` endpoint which returns
150
+ * raw SSE events without message wrapping. Use this for server-side processing or
151
+ * proxying to custom clients.
152
+ *
153
+ * **For React UI rendering**, use thread-based messaging instead:
154
+ * - `useThread` hook (client-side with token auth)
155
+ * - `sendMessage()` (server-side proxy to client)
156
+ *
157
+ * The thread endpoint wraps events with message metadata required by `handleSSEEvent`
158
+ * and the `Content` component for proper rendering.
159
+ *
160
+ * @example Server-side streaming (no React)
161
+ * ```typescript
162
+ * for await (const event of client.invokeStream('data-analyst', params)) {
163
+ * console.log(event.type, event.payload);
164
+ * }
165
+ * ```
114
166
  */
115
167
  invokeStream(botKey: string, params?: InvokeParams): AsyncGenerator<SSEEvent, void, unknown>;
116
168
  /**
@@ -129,7 +181,10 @@ declare class ErdoClient {
129
181
  private extractResult;
130
182
  }
131
183
  /**
132
- * Invoke an agent with a clean API
184
+ * Invoke an agent with a clean API.
185
+ *
186
+ * **Server-only**: Uses `/bots/{key}/invoke` which returns raw SSE events.
187
+ * For React UI rendering, use thread-based messaging (`useThread` hook or `sendMessage()`).
133
188
  *
134
189
  * @example
135
190
  * ```typescript
@@ -143,7 +198,10 @@ declare class ErdoClient {
143
198
  */
144
199
  declare function invoke(botKey: string, params?: InvokeParams): Promise<InvokeResult>;
145
200
  /**
146
- * Invoke an agent and stream events
201
+ * Invoke an agent and stream events.
202
+ *
203
+ * **Server-only**: Uses `/bots/{key}/invoke` which returns raw SSE events.
204
+ * For React UI rendering, use thread-based messaging (`useThread` hook or `sendMessage()`).
147
205
  *
148
206
  * @example
149
207
  * ```typescript
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- var v="https://api.erdo.ai";function E(){let i=typeof process<"u"?process.env:{};return {endpoint:i.ERDO_ENDPOINT||v,authToken:i.ERDO_AUTH_TOKEN}}function k(i){let e=E(),t=i?.endpoint||e.endpoint||v,o=i?.authToken||e.authToken,n=i?.token;return {endpoint:t,authToken:o,token:n}}async function*f(i){if(!i.body)throw new Error("Response body is null");let e=i.body.getReader(),t=new TextDecoder,o="",n,s;try{for(;;){let{done:a,value:u}=await e.read();if(a){if(s!==void 0){let r=w(n,s);r&&(yield r);}break}o+=t.decode(u,{stream:!0});let h=o.split(`
2
- `);o=h.pop()||"";for(let r of h){let c=r.trim();if(!c){if(s!==void 0){let d=w(n,s);d&&(yield d),n=void 0,s=void 0;}continue}if(c.startsWith("event:"))n=c.slice(6).trim();else if(c.startsWith("data:")){let d=c.slice(5).trim();s===void 0?s=d:s+=`
3
- `+d;}}}}finally{e.releaseLock();}}function w(i,e){if(!e)return null;try{let t=JSON.parse(e);return i&&(t.type=i),t}catch{return {type:i,raw:e}}}async function p(i){let e=[];for await(let t of f(i))e.push(t);return e}var l=class{endpoint;authToken;token;constructor(e){let t=k(e);if(this.endpoint=t.endpoint,this.authToken=t.authToken,this.token=t.token,!this.authToken&&!this.token)throw new Error("Either authToken or token is required")}async createToken(e){if(!this.authToken)throw new Error("createToken requires authToken (API key) authentication");let t=`${this.endpoint}/tokens`,o={};e.botKeys&&(o.bot_keys=e.botKeys),e.datasetIds&&(o.dataset_ids=e.datasetIds),e.externalUserId&&(o.external_user_id=e.externalUserId),e.expiresInSeconds&&(o.expires_in_seconds=e.expiresInSeconds);let n=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${this.authToken}`,"Content-Type":"application/json"},body:JSON.stringify(o)});if(!n.ok){let a=await n.text();throw new Error(`Failed to create token: ${n.status}: ${a}`)}let s=await n.json();return {token:s.token,expiresAt:s.expires_at}}async createThread(e={}){if(!this.token)throw new Error("createThread requires token authentication");let t=`${this.endpoint}/threads`,o={};e.name&&(o.name=e.name),e.datasetIds&&(o.dataset_ids=e.datasetIds);let n=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json"},body:JSON.stringify(o)});if(!n.ok){let a=await n.text();throw new Error(`Failed to create thread: ${n.status}: ${a}`)}let s=await n.json();return {id:s.id,name:s.name,createdAt:s.created_at,updatedAt:s.updated_at}}async listThreads(){if(!this.token)throw new Error("listThreads requires token authentication");let e=`${this.endpoint}/threads-user`,t=await fetch(e,{method:"GET",headers:{Authorization:`Bearer ${this.token}`}});if(!t.ok){let n=await t.text();throw new Error(`Failed to list threads: ${t.status}: ${n}`)}return {threads:(await t.json()).threads.map(n=>({id:n.id,name:n.name,createdAt:n.created_at,updatedAt:n.updated_at}))}}async getThread(e){if(!this.token)throw new Error("getThread requires token authentication");let t=`${this.endpoint}/threads/${e}`,o=await fetch(t,{method:"GET",headers:{Authorization:`Bearer ${this.token}`}});if(!o.ok){let s=await o.text();throw new Error(`Failed to get thread: ${o.status}: ${s}`)}let n=await o.json();return {id:n.id,name:n.name,createdAt:n.created_at,updatedAt:n.updated_at}}async*sendMessage(e,t){if(!this.token)throw new Error("sendMessage requires token authentication");let o=`${this.endpoint}/threads/${e}/message`,n={content:t.content};t.botKey&&(n.bot_key=t.botKey);let s=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n)});if(!s.ok){let a=await s.text();throw new Error(`Failed to send message: ${s.status}: ${a}`)}yield*f(s);}async sendMessageAndWait(e,t){if(!this.token)throw new Error("sendMessageAndWait requires token authentication");let o=`${this.endpoint}/threads/${e}/message`,n={content:t.content};t.botKey&&(n.bot_key=t.botKey);let s=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n)});if(!s.ok){let a=await s.text();throw new Error(`Failed to send message: ${s.status}: ${a}`)}return p(s)}async invoke(e,t={}){let o=await this.collectEvents(e,t);return this.extractResult(e,o)}async*invokeStream(e,t={}){let o=await this.makeRequest(e,t);yield*f(o);}async collectEvents(e,t){let o=await this.makeRequest(e,t);return p(o)}async makeRequest(e,t){let o=`${this.endpoint}/bots/${e}/invoke`,n={};t.messages&&(n.messages=t.messages),t.parameters&&(n.parameters=t.parameters),t.datasets&&(n.dataset_slugs=t.datasets),t.mode&&(n.mode=t.mode),t.manualMocks&&(n.manual_mocks=t.manualMocks);let s=this.token||this.authToken,a=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n)});if(!a.ok){let u=await a.text();throw new Error(`API request failed with status ${a.status}: ${u}`)}return a}extractResult(e,t){let o=[],n=[],s=new Set,a,u;for(let h of t){if(!h)continue;let r=h.payload,c=h.metadata;if(r&&typeof r=="object"&&"invocation_id"in r&&!u&&(u=r.invocation_id),r&&typeof r=="object"&&"action_type"in r&&"key"in r){let d=r.key;s.has(d)||(s.add(d),n.push({key:d,action:r.action_type,status:"completed"}));}if(r&&typeof r=="object"&&"output"in r&&c?.user_visibility==="visible"){let d=r.output;if(d&&typeof d=="object"&&"content"in d){let y=d.content;if(Array.isArray(y)){for(let m of y)if(m.content_type==="text"){let T=c?.role||"assistant";o.push({role:T,content:m.content||""});}}}}r&&typeof r=="object"&&"status"in r&&"output"in r&&(a={status:r.status,parameters:r.parameters,output:r.output,message:r.message,error:r.error});}return {success:true,botKey:e,invocationId:u,result:a,messages:o,events:t,steps:n}}},g=null;function S(){return g||(g=new l),g}async function x(i,e={}){return S().invoke(i,e)}async function*P(i,e={}){yield*S().invokeStream(i,e);}export{l as ErdoClient,p as collectSSEEvents,E as getConfigFromEnv,x as invoke,P as invokeStream,f as parseSSEStream,k as resolveConfig};
1
+ var w="https://api.erdo.ai";function m(){let a=typeof process<"u"?process.env:{};return {endpoint:a.ERDO_ENDPOINT||w,authToken:a.ERDO_AUTH_TOKEN}}function g(a){let e=m(),t=a?.endpoint||e.endpoint||w,o=a?.authToken||e.authToken,n=a?.token;return {endpoint:t,authToken:o,token:n}}async function*l(a){if(!a.body)throw new Error("Response body is null");let e=a.body.getReader(),t=new TextDecoder,o="",n,s;try{for(;;){let{done:i,value:c}=await e.read();if(i){if(s!==void 0){let r=E(n,s);r&&(yield r);}break}o+=t.decode(c,{stream:!0});let u=o.split(`
2
+ `);o=u.pop()||"";for(let r of u){let h=r.trim();if(!h){if(s!==void 0){let d=E(n,s);d&&(yield d),n=void 0,s=void 0;}continue}if(h.startsWith("event:"))n=h.slice(6).trim();else if(h.startsWith("data:")){let d=h.slice(5).trim();s===void 0?s=d:s+=`
3
+ `+d;}}}}finally{e.releaseLock();}}function E(a,e){if(!e)return null;try{let t=JSON.parse(e);return a&&(t.type=a),t}catch{return {type:a,raw:e}}}async function f(a){let e=[];for await(let t of l(a))e.push(t);return e}var p=class{endpoint;authToken;token;constructor(e){let t=g(e);if(this.endpoint=t.endpoint,this.authToken=t.authToken,this.token=t.token,!this.authToken&&!this.token)throw new Error("Either authToken or token is required")}async createToken(e){if(!this.authToken)throw new Error("createToken requires authToken (API key) authentication");let t;if(e.botKeys&&e.botKeys.length>0){let c=`${this.endpoint}/resolve-bot-keys`,u=await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${this.authToken}`,"Content-Type":"application/json"},body:JSON.stringify({keys:e.botKeys})});if(!u.ok){let d=await u.text();throw new Error(`Failed to resolve bot keys: ${u.status}: ${d}`)}let r=await u.json();t=Object.values(r.bots);let h=e.botKeys.filter(d=>!r.bots[d]);if(h.length>0)throw new Error(`Bot keys not found or access denied: ${h.join(", ")}`)}let o=`${this.endpoint}/tokens`,n={};t&&(n.bot_ids=t),e.datasetIds&&(n.dataset_ids=e.datasetIds),e.threadIds&&(n.thread_ids=e.threadIds),e.externalUserId&&(n.external_user_id=e.externalUserId),e.expiresInSeconds&&(n.expires_in_seconds=e.expiresInSeconds);let s=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${this.authToken}`,"Content-Type":"application/json"},body:JSON.stringify(n)});if(!s.ok){let c=await s.text();throw new Error(`Failed to create token: ${s.status}: ${c}`)}let i=await s.json();return {tokenId:i.token_id,token:i.token,expiresAt:i.expires_at}}async createThread(e={}){if(!this.token)throw new Error("createThread requires token authentication");let t=`${this.endpoint}/threads`,o={};e.name&&(o.name=e.name),e.datasetIds&&(o.dataset_ids=e.datasetIds);let n=await fetch(t,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json"},body:JSON.stringify(o)});if(!n.ok){let i=await n.text();throw new Error(`Failed to create thread: ${n.status}: ${i}`)}let s=await n.json();return {id:s.id,name:s.name,createdAt:s.created_at,updatedAt:s.updated_at}}async listThreads(){if(!this.token)throw new Error("listThreads requires token authentication");let e=`${this.endpoint}/threads-user`,t=await fetch(e,{method:"GET",headers:{Authorization:`Bearer ${this.token}`}});if(!t.ok){let n=await t.text();throw new Error(`Failed to list threads: ${t.status}: ${n}`)}return {threads:(await t.json()).threads.map(n=>({id:n.id,name:n.name,createdAt:n.created_at,updatedAt:n.updated_at}))}}async getThread(e){if(!this.token)throw new Error("getThread requires token authentication");let t=`${this.endpoint}/threads/${e}`,o=await fetch(t,{method:"GET",headers:{Authorization:`Bearer ${this.token}`}});if(!o.ok){let s=await o.text();throw new Error(`Failed to get thread: ${o.status}: ${s}`)}let n=await o.json();return {id:n.id,name:n.name,createdAt:n.created_at,updatedAt:n.updated_at}}async getThreadMessages(e){if(!this.token)throw new Error("getThreadMessages requires token authentication");let t=`${this.endpoint}/threads/${e}/messages`,o=await fetch(t,{method:"GET",headers:{Authorization:`Bearer ${this.token}`}});if(!o.ok){let s=await o.text();throw new Error(`Failed to get thread messages: ${o.status}: ${s}`)}return {messages:(await o.json()).messages.map(s=>({id:s.id,thread_id:s.thread_id,role:s.role,created_at:s.created_at,updated_at:s.updated_at,contents:s.contents.map(i=>({id:i.id,content_type:i.content_type,ui_content_type:i.ui_content_type,content:i.content,user_visibility:i.user_visibility,bot_visibility:i.bot_visibility,created_at:i.created_at}))}))}}async*sendMessage(e,t){if(!this.token)throw new Error("sendMessage requires token authentication");let o=`${this.endpoint}/threads/${e}/message`,n={content:t.content};t.botKey&&(n.bot_key=t.botKey);let s=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n)});if(!s.ok){let i=await s.text();throw new Error(`Failed to send message: ${s.status}: ${i}`)}yield*l(s);}async sendMessageAndWait(e,t){if(!this.token)throw new Error("sendMessageAndWait requires token authentication");let o=`${this.endpoint}/threads/${e}/message`,n={content:t.content};t.botKey&&(n.bot_key=t.botKey);let s=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n)});if(!s.ok){let i=await s.text();throw new Error(`Failed to send message: ${s.status}: ${i}`)}return f(s)}async invoke(e,t={}){let o=await this.collectEvents(e,t);return this.extractResult(e,o)}async*invokeStream(e,t={}){let o=await this.makeRequest(e,t);yield*l(o);}async collectEvents(e,t){let o=await this.makeRequest(e,t);return f(o)}async makeRequest(e,t){let o=`${this.endpoint}/bots/${e}/invoke`,n={};t.messages&&(n.messages=t.messages),t.parameters&&(n.parameters=t.parameters),t.datasets&&(n.dataset_slugs=t.datasets),t.mode&&(n.mode=t.mode),t.manualMocks&&(n.manual_mocks=t.manualMocks);let s=this.token||this.authToken,i=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n)});if(!i.ok){let c=await i.text();throw new Error(`API request failed with status ${i.status}: ${c}`)}return i}extractResult(e,t){let o=[],n=[],s=new Set,i,c;for(let u of t){if(!u)continue;let r=u.payload,h=u.metadata;if(r&&typeof r=="object"&&"invocation_id"in r&&!c&&(c=r.invocation_id),r&&typeof r=="object"&&"action_type"in r&&"key"in r){let d=r.key;s.has(d)||(s.add(d),n.push({key:d,action:r.action_type,status:"completed"}));}if(r&&typeof r=="object"&&"output"in r&&h?.user_visibility==="visible"){let d=r.output;if(d&&typeof d=="object"&&"content"in d){let y=d.content;if(Array.isArray(y)){for(let v of y)if(v.content_type==="text"){let S=h?.role||"assistant";o.push({role:S,content:v.content||""});}}}}r&&typeof r=="object"&&"status"in r&&"output"in r&&(i={status:r.status,parameters:r.parameters,output:r.output,message:r.message,error:r.error});}return {success:true,botKey:e,invocationId:c,result:i,messages:o,events:t,steps:n}}},k=null;function T(){return k||(k=new p),k}async function _(a,e={}){return T().invoke(a,e)}async function*b(a,e={}){yield*T().invokeStream(a,e);}export{p as ErdoClient,f as collectSSEEvents,m as getConfigFromEnv,_ as invoke,b as invokeStream,l as parseSSEStream,g as resolveConfig};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/server",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Erdo server SDK for invoking agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,6 +21,7 @@
21
21
  },
22
22
  "scripts": {
23
23
  "build": "tsup",
24
+ "build:check": "tsup --outDir dist-check",
24
25
  "dev": "tsup --watch",
25
26
  "typecheck": "tsc --noEmit",
26
27
  "test": "vitest run",
@@ -39,7 +40,7 @@
39
40
  "directory": "packages/server"
40
41
  },
41
42
  "dependencies": {
42
- "@erdoai/types": "^0.1.6"
43
+ "@erdoai/types": "^0.1.8"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/node": "^24.10.1",