@erdoai/server 0.1.8 → 0.1.10
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 +4 -2
- package/dist/index.d.ts +93 -6
- package/dist/index.js +3 -3
- package/package.json +3 -2
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
|
-
|
|
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', {
|
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
|
|
@@ -22,7 +22,36 @@ declare class ErdoClient {
|
|
|
22
22
|
* The returned token can be passed to a client-side ErdoClient for
|
|
23
23
|
* scoped access to specific bots.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
25
|
+
* ## External User IDs
|
|
26
|
+
*
|
|
27
|
+
* Use `externalUserId` to associate threads with your users. This enables:
|
|
28
|
+
* - `listThreads()` returns only that user's threads
|
|
29
|
+
* - Thread access is automatically scoped to the user
|
|
30
|
+
*
|
|
31
|
+
* **Important**: Store the external user ID in your database so you can
|
|
32
|
+
* create tokens with the same ID when the user returns:
|
|
33
|
+
*
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // In your database (e.g., users table):
|
|
36
|
+
* // { id: 'user-abc', email: '...', erdo_external_user_id: 'ext-123' }
|
|
37
|
+
*
|
|
38
|
+
* // When creating tokens, use the stored ID:
|
|
39
|
+
* const user = await db.users.find(userId);
|
|
40
|
+
* const { token } = await serverClient.createToken({
|
|
41
|
+
* botKeys: ['my-org.data-analyst'],
|
|
42
|
+
* externalUserId: user.erdo_external_user_id, // Persisted ID
|
|
43
|
+
* expiresInSeconds: 3600,
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // User can now list their threads from previous sessions
|
|
47
|
+
* const { threads } = await clientClient.listThreads();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* The external user ID can be your own user ID or a separate UUID you generate
|
|
51
|
+
* once per user and store. It's embedded in the token and never exposed to
|
|
52
|
+
* the client - users cannot access other users' threads.
|
|
53
|
+
*
|
|
54
|
+
* @example Basic token creation
|
|
26
55
|
* ```typescript
|
|
27
56
|
* // Server-side: Create a token for the frontend
|
|
28
57
|
* const serverClient = new ErdoClient({ authToken: process.env.ERDO_AUTH_TOKEN });
|
|
@@ -78,6 +107,21 @@ declare class ErdoClient {
|
|
|
78
107
|
* ```
|
|
79
108
|
*/
|
|
80
109
|
getThread(threadId: string): Promise<Thread>;
|
|
110
|
+
/**
|
|
111
|
+
* Get messages for a thread (chat history)
|
|
112
|
+
*
|
|
113
|
+
* Requires token authentication.
|
|
114
|
+
* Access is granted via RBAC external_user or explicit thread scope.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const { messages } = await client.getThreadMessages(threadId);
|
|
119
|
+
* for (const msg of messages) {
|
|
120
|
+
* console.log(msg.role, msg.contents.length, 'content items');
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
getThreadMessages(threadId: string): Promise<ListThreadMessagesResponse>;
|
|
81
125
|
/**
|
|
82
126
|
* Send a message to a thread and stream the bot response
|
|
83
127
|
*
|
|
@@ -106,11 +150,48 @@ declare class ErdoClient {
|
|
|
106
150
|
*/
|
|
107
151
|
sendMessageAndWait(threadId: string, params: SendMessageParams): Promise<SSEEvent[]>;
|
|
108
152
|
/**
|
|
109
|
-
* Invoke an agent and wait for the complete result
|
|
153
|
+
* Invoke an agent and wait for the complete result.
|
|
154
|
+
*
|
|
155
|
+
* **Server-only**: This method uses the `/bots/{key}/invoke` endpoint which returns
|
|
156
|
+
* raw SSE events without message wrapping. Use this for server-side processing where
|
|
157
|
+
* you don't need React UI components.
|
|
158
|
+
*
|
|
159
|
+
* **For React UI rendering**, use thread-based messaging instead:
|
|
160
|
+
* - `useThread` hook (client-side with token auth)
|
|
161
|
+
* - `sendMessage()` / `sendMessageAndWait()` (server-side proxy)
|
|
162
|
+
*
|
|
163
|
+
* The thread endpoint wraps events with message metadata required by `handleSSEEvent`
|
|
164
|
+
* and the `Content` component for proper rendering.
|
|
165
|
+
*
|
|
166
|
+
* @example Server-side processing (no React)
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const result = await client.invoke('data-analyst', {
|
|
169
|
+
* messages: [{ role: 'user', content: 'Analyze Q4 sales' }],
|
|
170
|
+
* });
|
|
171
|
+
* // Process result.messages, result.events, etc.
|
|
172
|
+
* ```
|
|
110
173
|
*/
|
|
111
174
|
invoke(botKey: string, params?: InvokeParams): Promise<InvokeResult>;
|
|
112
175
|
/**
|
|
113
|
-
* Invoke an agent and stream SSE events
|
|
176
|
+
* Invoke an agent and stream SSE events.
|
|
177
|
+
*
|
|
178
|
+
* **Server-only**: This method uses the `/bots/{key}/invoke` endpoint which returns
|
|
179
|
+
* raw SSE events without message wrapping. Use this for server-side processing or
|
|
180
|
+
* proxying to custom clients.
|
|
181
|
+
*
|
|
182
|
+
* **For React UI rendering**, use thread-based messaging instead:
|
|
183
|
+
* - `useThread` hook (client-side with token auth)
|
|
184
|
+
* - `sendMessage()` (server-side proxy to client)
|
|
185
|
+
*
|
|
186
|
+
* The thread endpoint wraps events with message metadata required by `handleSSEEvent`
|
|
187
|
+
* and the `Content` component for proper rendering.
|
|
188
|
+
*
|
|
189
|
+
* @example Server-side streaming (no React)
|
|
190
|
+
* ```typescript
|
|
191
|
+
* for await (const event of client.invokeStream('data-analyst', params)) {
|
|
192
|
+
* console.log(event.type, event.payload);
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
114
195
|
*/
|
|
115
196
|
invokeStream(botKey: string, params?: InvokeParams): AsyncGenerator<SSEEvent, void, unknown>;
|
|
116
197
|
/**
|
|
@@ -129,7 +210,10 @@ declare class ErdoClient {
|
|
|
129
210
|
private extractResult;
|
|
130
211
|
}
|
|
131
212
|
/**
|
|
132
|
-
* Invoke an agent with a clean API
|
|
213
|
+
* Invoke an agent with a clean API.
|
|
214
|
+
*
|
|
215
|
+
* **Server-only**: Uses `/bots/{key}/invoke` which returns raw SSE events.
|
|
216
|
+
* For React UI rendering, use thread-based messaging (`useThread` hook or `sendMessage()`).
|
|
133
217
|
*
|
|
134
218
|
* @example
|
|
135
219
|
* ```typescript
|
|
@@ -143,7 +227,10 @@ declare class ErdoClient {
|
|
|
143
227
|
*/
|
|
144
228
|
declare function invoke(botKey: string, params?: InvokeParams): Promise<InvokeResult>;
|
|
145
229
|
/**
|
|
146
|
-
* Invoke an agent and stream events
|
|
230
|
+
* Invoke an agent and stream events.
|
|
231
|
+
*
|
|
232
|
+
* **Server-only**: Uses `/bots/{key}/invoke` which returns raw SSE events.
|
|
233
|
+
* For React UI rendering, use thread-based messaging (`useThread` hook or `sendMessage()`).
|
|
147
234
|
*
|
|
148
235
|
* @example
|
|
149
236
|
* ```typescript
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
`);
|
|
3
|
-
`+d;}}}}finally{e.releaseLock();}}function E(
|
|
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.
|
|
3
|
+
"version": "0.1.10",
|
|
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.
|
|
43
|
+
"@erdoai/types": "^0.1.9"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@types/node": "^24.10.1",
|