@erdoai/server 0.1.5 → 0.1.7

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
@@ -17,7 +17,7 @@ import { ErdoClient } from '@erdoai/server';
17
17
 
18
18
  const client = new ErdoClient({
19
19
  endpoint: 'https://api.erdo.ai',
20
- authToken: process.env.ERDO_API_KEY,
20
+ authToken: process.env.ERDO_AUTH_TOKEN,
21
21
  });
22
22
 
23
23
  // Get final result
@@ -48,6 +48,63 @@ for await (const event of client.invokeStream('data-analyst', {
48
48
  }
49
49
  ```
50
50
 
51
+ ### Token Authentication (B2B)
52
+
53
+ For B2B applications, create scoped tokens for your users:
54
+
55
+ ```typescript
56
+ // Server-side: Create a scoped token
57
+ const serverClient = new ErdoClient({
58
+ authToken: process.env.ERDO_AUTH_TOKEN,
59
+ });
60
+
61
+ const { token, expiresAt } = await serverClient.createToken({
62
+ botKeys: ['my-org.data-analyst'],
63
+ externalUserId: 'user_123',
64
+ expiresInSeconds: 3600,
65
+ });
66
+
67
+ // Client-side: Use the scoped token
68
+ const clientClient = new ErdoClient({
69
+ endpoint: 'https://api.erdo.ai',
70
+ token: token, // Scoped token instead of authToken
71
+ });
72
+
73
+ const result = await clientClient.invoke('my-org.data-analyst', {
74
+ messages: [{ role: 'user', content: 'Analyze my data' }],
75
+ });
76
+ ```
77
+
78
+ ### Thread Management
79
+
80
+ Threads provide persistent conversations. Requires token authentication with `externalUserId`:
81
+
82
+ ```typescript
83
+ const client = new ErdoClient({
84
+ endpoint: 'https://api.erdo.ai',
85
+ token: scopedToken,
86
+ });
87
+
88
+ // Create a thread
89
+ const thread = await client.createThread({ name: 'Data Analysis' });
90
+
91
+ // List user's threads
92
+ const { threads } = await client.listThreads();
93
+
94
+ // Send a message and stream the response
95
+ for await (const event of client.sendMessage(thread.id, {
96
+ content: 'What insights can you find?',
97
+ botKey: 'my-org.data-analyst',
98
+ })) {
99
+ console.log(event.type, event.payload);
100
+ }
101
+
102
+ // Or wait for the complete response
103
+ const events = await client.sendMessageAndWait(thread.id, {
104
+ content: 'Summarize recent trends',
105
+ });
106
+ ```
107
+
51
108
  ### Environment Variables
52
109
 
53
110
  The client can be configured via environment variables:
@@ -70,11 +127,34 @@ const client = new ErdoClient();
70
127
  class ErdoClient {
71
128
  constructor(options?: {
72
129
  endpoint?: string; // API endpoint (default: env or https://api.erdo.ai)
73
- authToken?: string; // API key (default: env ERDO_AUTH_TOKEN)
130
+ authToken?: string; // API key for server-side (default: env ERDO_AUTH_TOKEN)
131
+ token?: string; // Scoped token for client-side
74
132
  });
75
133
 
134
+ // Token creation (requires authToken)
135
+ createToken(params: CreateTokenParams): Promise<TokenResponse>;
136
+
137
+ // Agent invocation
76
138
  invoke(botKey: string, params: InvokeParams): Promise<InvokeResult>;
77
139
  invokeStream(botKey: string, params: InvokeParams): AsyncGenerator<SSEEvent>;
140
+
141
+ // Thread management (requires token with externalUserId)
142
+ createThread(params?: CreateThreadParams): Promise<Thread>;
143
+ listThreads(): Promise<ListThreadsResponse>;
144
+ getThread(threadId: string): Promise<Thread>;
145
+ sendMessage(threadId: string, params: SendMessageParams): AsyncGenerator<SSEEvent>;
146
+ sendMessageAndWait(threadId: string, params: SendMessageParams): Promise<SSEEvent[]>;
147
+ }
148
+ ```
149
+
150
+ ### `CreateTokenParams`
151
+
152
+ ```typescript
153
+ interface CreateTokenParams {
154
+ botKeys?: string[]; // Bot keys the token can access
155
+ datasetIds?: string[]; // Dataset IDs the token can access
156
+ externalUserId?: string; // External user ID for RBAC (enables thread access)
157
+ expiresInSeconds?: number; // Token lifetime (default: 3600)
78
158
  }
79
159
  ```
80
160
 
@@ -89,6 +169,15 @@ interface InvokeParams {
89
169
  }
90
170
  ```
91
171
 
172
+ ### `SendMessageParams`
173
+
174
+ ```typescript
175
+ interface SendMessageParams {
176
+ content: string; // Message content
177
+ botKey?: string; // Optional bot to use
178
+ }
179
+ ```
180
+
92
181
  ### `SSEEvent`
93
182
 
94
183
  ```typescript
package/dist/index.d.ts CHANGED
@@ -1,15 +1,110 @@
1
- import { ErdoClientConfig, InvokeParams, InvokeResult, SSEEvent } from '@erdoai/types';
1
+ import { ErdoClientConfig, CreateTokenParams, TokenResponse, CreateThreadParams, Thread, ListThreadsResponse, SendMessageParams, SSEEvent, InvokeParams, InvokeResult } from '@erdoai/types';
2
2
 
3
3
  /**
4
4
  * Erdo Client for invoking agents
5
5
  *
6
+ * Supports two authentication modes:
7
+ * 1. authToken (API key) - for server-side requests, can create tokens
8
+ * 2. token (scoped token) - for client-side requests, limited to token scope
9
+ *
6
10
  * Ported from: erdo-python-sdk/erdo/invoke/client.py and invoke.py
7
11
  */
8
12
 
9
13
  declare class ErdoClient {
10
14
  private readonly endpoint;
11
- private readonly authToken;
15
+ private readonly authToken?;
16
+ private readonly token?;
12
17
  constructor(config?: Partial<ErdoClientConfig>);
18
+ /**
19
+ * Create a scoped token for client-side use
20
+ *
21
+ * Requires authToken (API key) authentication.
22
+ * The returned token can be passed to a client-side ErdoClient for
23
+ * scoped access to specific bots.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // Server-side: Create a token for the frontend
28
+ * const serverClient = new ErdoClient({ authToken: process.env.ERDO_AUTH_TOKEN });
29
+ * const { token, expiresAt } = await serverClient.createToken({
30
+ * botKeys: ['my-org.data-analyst'],
31
+ * expiresInSeconds: 3600,
32
+ * });
33
+ *
34
+ * // Client-side: Use the token
35
+ * const clientClient = new ErdoClient({ endpoint: 'https://api.erdo.ai', token });
36
+ * const result = await clientClient.invoke('my-org.data-analyst', { messages: [...] });
37
+ * ```
38
+ */
39
+ createToken(params: CreateTokenParams): Promise<TokenResponse>;
40
+ /**
41
+ * Create a new thread for the external user
42
+ *
43
+ * Requires token authentication with external_user_id.
44
+ * The thread is automatically associated with the external user.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const thread = await client.createThread({ name: 'Support Chat' });
49
+ * console.log(thread.id, thread.name);
50
+ * ```
51
+ */
52
+ createThread(params?: CreateThreadParams): Promise<Thread>;
53
+ /**
54
+ * List threads for the external user
55
+ *
56
+ * Requires token authentication with external_user_id.
57
+ * Returns threads that the external user has access to via RBAC.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const { threads } = await client.listThreads();
62
+ * for (const thread of threads) {
63
+ * console.log(thread.id, thread.name);
64
+ * }
65
+ * ```
66
+ */
67
+ listThreads(): Promise<ListThreadsResponse>;
68
+ /**
69
+ * Get a specific thread by ID
70
+ *
71
+ * Requires token authentication.
72
+ * Access is granted via RBAC external_user or explicit thread scope.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const thread = await client.getThread(threadId);
77
+ * console.log(thread.name, thread.createdAt);
78
+ * ```
79
+ */
80
+ getThread(threadId: string): Promise<Thread>;
81
+ /**
82
+ * Send a message to a thread and stream the bot response
83
+ *
84
+ * Requires token authentication.
85
+ * Access is granted via RBAC external_user or explicit thread scope.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * for await (const event of client.sendMessage(threadId, { content: 'Hello!' })) {
90
+ * console.log(event.type, event.payload);
91
+ * }
92
+ * ```
93
+ */
94
+ sendMessage(threadId: string, params: SendMessageParams): AsyncGenerator<SSEEvent, void, unknown>;
95
+ /**
96
+ * Send a message to a thread and wait for the complete response
97
+ *
98
+ * Requires token authentication.
99
+ * Returns all SSE events collected from the stream.
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const events = await client.sendMessageAndWait(threadId, { content: 'Hello!' });
104
+ * console.log('Received', events.length, 'events');
105
+ * ```
106
+ */
107
+ sendMessageAndWait(threadId: string, params: SendMessageParams): Promise<SSEEvent[]>;
13
108
  /**
14
109
  * Invoke an agent and wait for the complete result
15
110
  */
@@ -67,14 +162,28 @@ declare function invokeStream(botKey: string, params?: InvokeParams): AsyncGener
67
162
  * Ported from: erdo-python-sdk/erdo/config/config.py
68
163
  */
69
164
 
165
+ /**
166
+ * Resolved configuration - authToken is optional when using token
167
+ */
168
+ interface ResolvedConfig {
169
+ endpoint: string;
170
+ authToken?: string;
171
+ token?: string;
172
+ }
70
173
  /**
71
174
  * Get configuration from environment variables
72
175
  */
73
176
  declare function getConfigFromEnv(): ErdoClientConfig;
74
177
  /**
75
178
  * Merge user config with environment config
179
+ *
180
+ * Auth can be provided via:
181
+ * - authToken: API key for server-side use (can create tokens, full access)
182
+ * - token: Scoped token for client-side use (limited to token scope)
183
+ *
184
+ * At least one of authToken or token must be provided.
76
185
  */
77
- declare function resolveConfig(userConfig?: Partial<ErdoClientConfig>): Required<ErdoClientConfig>;
186
+ declare function resolveConfig(userConfig?: Partial<ErdoClientConfig>): ResolvedConfig;
78
187
 
79
188
  /**
80
189
  * SSE Client for Server-Sent Events streaming
@@ -84,6 +193,13 @@ declare function resolveConfig(userConfig?: Partial<ErdoClientConfig>): Required
84
193
 
85
194
  /**
86
195
  * Parse SSE events from a ReadableStream
196
+ *
197
+ * SSE format:
198
+ * event: <event-type>
199
+ * data: <json-data>
200
+ * <blank line>
201
+ *
202
+ * The event type is extracted from the "event:" line and added to the parsed data.
87
203
  */
88
204
  declare function parseSSEStream(response: Response): AsyncGenerator<SSEEvent, void, unknown>;
89
205
  /**
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- var y="https://api.erdo.ai";function S(){let o=typeof process<"u"?process.env:{};return {endpoint:o.ERDO_ENDPOINT||o.NEXT_PUBLIC_ERDO_ENDPOINT||y,authToken:o.ERDO_AUTH_TOKEN||o.NEXT_PUBLIC_ERDO_API_KEY}}function p(o){let t=S(),e=o?.endpoint||t.endpoint||y,s=o?.authToken||t.authToken;if(!s)throw new Error("No auth token configured. Set ERDO_AUTH_TOKEN environment variable or pass authToken to ErdoClient.");return {endpoint:e,authToken:s}}async function*d(o){if(!o.body)throw new Error("Response body is null");let t=o.body.getReader(),e=new TextDecoder,s="";try{for(;;){let{done:r,value:i}=await t.read();if(r){if(s.trim()){let a=h(s);a&&(yield a);}break}s+=e.decode(i,{stream:!0});let u=s.split(`
2
- `);s=u.pop()||"";for(let a of u){let l=h(a);l&&(yield l);}}}finally{t.releaseLock();}}function h(o){let t=o.trim();if(!t)return null;if(t.startsWith("data: ")){let e=t.slice(6);if(!e.trim())return null;try{return JSON.parse(e)}catch{return {raw:e}}}return null}async function v(o){let t=[];for await(let e of d(o))t.push(e);return t}var f=class{endpoint;authToken;constructor(t){let e=p(t);this.endpoint=e.endpoint,this.authToken=e.authToken;}async invoke(t,e={}){let s=await this.collectEvents(t,e);return this.extractResult(t,s)}async*invokeStream(t,e={}){let s=await this.makeRequest(t,e);yield*d(s);}async collectEvents(t,e){let s=await this.makeRequest(t,e);return v(s)}async makeRequest(t,e){let s=`${this.endpoint}/bots/${t}/invoke`,r={};e.messages&&(r.messages=e.messages),e.parameters&&(r.parameters=e.parameters),e.datasets&&(r.dataset_slugs=e.datasets),e.mode&&(r.mode=e.mode),e.manualMocks&&(r.manual_mocks=e.manualMocks);let i=await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${this.authToken}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(r)});if(!i.ok){let u=await i.text();throw new Error(`API request failed with status ${i.status}: ${u}`)}return i}extractResult(t,e){let s=[],r=[],i=new Set,u,a;for(let l of e){if(!l)continue;let n=l.payload,m=l.metadata;if(n&&typeof n=="object"&&"invocation_id"in n&&!a&&(a=n.invocation_id),n&&typeof n=="object"&&"action_type"in n&&"key"in n){let c=n.key;i.has(c)||(i.add(c),r.push({key:c,action:n.action_type,status:"completed"}));}if(n&&typeof n=="object"&&"output"in n&&m?.user_visibility==="visible"){let c=n.output;if(c&&typeof c=="object"&&"content"in c){let g=c.content;if(Array.isArray(g)){for(let k of g)if(k.content_type==="text"){let w=m?.role||"assistant";s.push({role:w,content:k.content||""});}}}}n&&typeof n=="object"&&"status"in n&&"output"in n&&(u={status:n.status,parameters:n.parameters,output:n.output,message:n.message,error:n.error});}return {success:true,botId:t,invocationId:a,result:u,messages:s,events:e,steps:r}}},E=null;function R(){return E||(E=new f),E}async function C(o,t={}){return R().invoke(o,t)}async function*T(o,t={}){yield*R().invokeStream(o,t);}export{f as ErdoClient,v as collectSSEEvents,S as getConfigFromEnv,C as invoke,T as invokeStream,d as parseSSEStream,p as resolveConfig};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/server",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Erdo server SDK for invoking agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,7 @@
39
39
  "directory": "packages/server"
40
40
  },
41
41
  "dependencies": {
42
- "@erdoai/types": "workspace:^"
42
+ "@erdoai/types": "^0.1.6"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^24.10.1",
@@ -47,4 +47,4 @@
47
47
  "typescript": "^5.9.3",
48
48
  "vitest": "^4.0.15"
49
49
  }
50
- }
50
+ }