@erdoai/server 0.1.9 → 0.1.11
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/dist/index.d.ts +56 -8
- package/dist/index.js +3 -3
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -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 });
|
|
@@ -53,18 +82,37 @@ declare class ErdoClient {
|
|
|
53
82
|
/**
|
|
54
83
|
* List threads for the external user
|
|
55
84
|
*
|
|
56
|
-
*
|
|
57
|
-
* Returns threads that the external user has access to via RBAC.
|
|
85
|
+
* Returns threads that the user has access to via RBAC.
|
|
58
86
|
*
|
|
59
|
-
*
|
|
87
|
+
* Supports two authentication patterns:
|
|
88
|
+
* - **Token auth**: Uses the external_user_id embedded in the token (recommended)
|
|
89
|
+
* - **API key auth**: Pass `externalUserId` to filter threads for that user
|
|
90
|
+
*
|
|
91
|
+
* ## Security Warning
|
|
92
|
+
*
|
|
93
|
+
* When using `externalUserId` with API key auth, you MUST determine the user ID
|
|
94
|
+
* from your own authentication system (session, JWT, etc.). **NEVER** accept
|
|
95
|
+
* `externalUserId` from client requests - this would allow users to access
|
|
96
|
+
* other users' threads.
|
|
97
|
+
*
|
|
98
|
+
* @example Token auth (B2B2C) - Recommended:
|
|
60
99
|
* ```typescript
|
|
100
|
+
* // Token contains externalUserId - secure by design
|
|
61
101
|
* const { threads } = await client.listThreads();
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @example API key auth with externalUserId (proxy pattern):
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // SECURE: Get user ID from YOUR auth system, not from request
|
|
107
|
+
* const session = await getServerSession();
|
|
108
|
+
* const { threads } = await client.listThreads({
|
|
109
|
+
* externalUserId: session.user.id // From your auth, NOT from request body
|
|
110
|
+
* });
|
|
65
111
|
* ```
|
|
66
112
|
*/
|
|
67
|
-
listThreads(
|
|
113
|
+
listThreads(params?: {
|
|
114
|
+
externalUserId?: string;
|
|
115
|
+
}): Promise<ListThreadsResponse>;
|
|
68
116
|
/**
|
|
69
117
|
* Get a specific thread by ID
|
|
70
118
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
`);o=u.pop()||"";for(let
|
|
3
|
-
`+d;}}}}finally{e.releaseLock();}}function
|
|
1
|
+
var v="https://api.erdo.ai";function E(){let a=typeof process<"u"?process.env:{};return {endpoint:a.ERDO_ENDPOINT||v,authToken:a.ERDO_AUTH_TOKEN}}function k(a){let e=E(),t=a?.endpoint||e.endpoint||v,o=a?.authToken||e.authToken,n=a?.token;return {endpoint:t,authToken:o,token:n}}async function*f(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:r,value:c}=await e.read();if(r){if(s!==void 0){let i=S(n,s);i&&(yield i);}break}o+=t.decode(c,{stream:!0});let u=o.split(`
|
|
2
|
+
`);o=u.pop()||"";for(let i of u){let h=i.trim();if(!h){if(s!==void 0){let d=S(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 S(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 l(a){let e=[];for await(let t of f(a))e.push(t);return e}function p(a){if(typeof a=="string")return a;if(a&&typeof a=="object"&&"String"in a&&"Valid"in a&&a.Valid)return a.String}var g=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;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 i=await u.json();t=Object.values(i.bots);let h=e.botKeys.filter(d=>!i.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 r=await s.json();return {tokenId:r.token_id,token:r.token,expiresAt:r.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 r=await n.text();throw new Error(`Failed to create thread: ${n.status}: ${r}`)}let s=await n.json();return {id:s.id,name:p(s.name)||"",createdAt:s.created_at,updatedAt:s.updated_at}}async listThreads(e){if(!this.token&&!this.authToken)throw new Error("listThreads requires authentication (token or authToken)");let t=`${this.endpoint}/threads-user`;e?.externalUserId&&(t+=`?external_user_id=${encodeURIComponent(e.externalUserId)}`);let o=this.token?`Bearer ${this.token}`:`Bearer ${this.authToken}`,n=await fetch(t,{method:"GET",headers:{Authorization:o}});if(!n.ok){let r=await n.text();throw new Error(`Failed to list threads: ${n.status}: ${r}`)}return {threads:(await n.json()).threads.map(r=>({id:r.id,name:p(r.name)||"",createdAt:r.created_at,updatedAt:r.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:p(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.message.id,thread_id:s.message.thread_id,role:s.message.author_entity_type==="user"?"user":"assistant",created_at:s.message.created_at,updated_at:s.message.updated_at,contents:s.contents.map(r=>({id:r.id,content_type:r.content_type,ui_content_type:p(r.ui_content_type),content:r.content,user_visibility:r.user_visibility,bot_visibility:r.bot_visibility,created_at:r.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 r=await s.text();throw new Error(`Failed to send message: ${s.status}: ${r}`)}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 r=await s.text();throw new Error(`Failed to send message: ${s.status}: ${r}`)}return l(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 l(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,r=await fetch(o,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(n)});if(!r.ok){let c=await r.text();throw new Error(`API request failed with status ${r.status}: ${c}`)}return r}extractResult(e,t){let o=[],n=[],s=new Set,r,c;for(let u of t){if(!u)continue;let i=u.payload,h=u.metadata;if(i&&typeof i=="object"&&"invocation_id"in i&&!c&&(c=i.invocation_id),i&&typeof i=="object"&&"action_type"in i&&"key"in i){let d=i.key;s.has(d)||(s.add(d),n.push({key:d,action:i.action_type,status:"completed"}));}if(i&&typeof i=="object"&&"output"in i&&h?.user_visibility==="visible"){let d=i.output;if(d&&typeof d=="object"&&"content"in d){let w=d.content;if(Array.isArray(w)){for(let m of w)if(m.content_type==="text"){let _=h?.role||"assistant";o.push({role:_,content:m.content||""});}}}}i&&typeof i=="object"&&"status"in i&&"output"in i&&(r={status:i.status,parameters:i.parameters,output:i.output,message:i.message,error:i.error});}return {success:true,botKey:e,invocationId:c,result:r,messages:o,events:t,steps:n}}},y=null;function T(){return y||(y=new g),y}async function b(a,e={}){return T().invoke(a,e)}async function*x(a,e={}){yield*T().invokeStream(a,e);}export{g as ErdoClient,l as collectSSEEvents,E as getConfigFromEnv,b as invoke,x 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.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Erdo server SDK for invoking agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"directory": "packages/server"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@erdoai/types": "^0.1.
|
|
43
|
+
"@erdoai/types": "^0.1.10"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^24.10.1",
|