@circuitorg/agent-sdk 1.0.0
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 +149 -0
- package/index.d.ts +475 -0
- package/index.js +1 -0
- package/package.json +70 -0
- package/stubs/subscriptions-stub.js +5 -0
- package/stubs/ws-stub.js +3 -0
- package/utils/auth-loader.cjs +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Circuit Agent SDK - Typescript
|
|
2
|
+
|
|
3
|
+
> **Clean, type-safe TypeScript SDK for building cross-chain agents on the circuit platform**
|
|
4
|
+
|
|
5
|
+
A TypeScript SDK for building automated agents to deploy on Circuit. Features a simple API surface with just **2 main methods** and full type safety.
|
|
6
|
+
|
|
7
|
+
> **💡 Best used with [Circuit Agents CLI](https://github.com/circuitorg/agents-cli)** - Deploy, manage, and test your agents with ease
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- **🎯 Simple API**: Only 2 main methods - `addMessage()` and `signAndSend()`
|
|
12
|
+
- **🔒 Type Safety**: Network parameter determines valid request shapes automatically
|
|
13
|
+
- **🚀 Cross-Chain**: Unified interface for EVM and Solana networks
|
|
14
|
+
|
|
15
|
+
## 🚀 Quick Start
|
|
16
|
+
### Install the SDK
|
|
17
|
+
```bash
|
|
18
|
+
npm install @circuitorg/agents-sdk
|
|
19
|
+
# or
|
|
20
|
+
yarn add @circuitorg/agents-sdk
|
|
21
|
+
# or
|
|
22
|
+
bun add @circuitorg/agents-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Sample SDK Usage
|
|
26
|
+
>**NOTE:** The fastest, and recommended, way to get started is to setup an agent via the circuit [CLI](https://github.com/circuitorg/agents-cli)'s 'circuit agent init' command. This will setup a sample agent directory with the necessary agent wireframe, and configure the cli to allow you for easy testing and deployment. You just simply need to add in your secret formula to the execution and stop functions.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { AgentSdk } from "@circuitorg/agents-sdk";
|
|
30
|
+
|
|
31
|
+
// Initialize the sdk
|
|
32
|
+
const sdk = new AgentSdk({
|
|
33
|
+
sessionId: 123,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 🎯 Core SDK API (Only 2 Methods!)
|
|
38
|
+
|
|
39
|
+
### 1. Add Messages to Timeline
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
await sdk.addMessage({
|
|
43
|
+
type: "observe",
|
|
44
|
+
shortMessage: "Starting swap operation"
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Sign & Send Transactions
|
|
49
|
+
|
|
50
|
+
#### Ethereum (any EVM chain)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Native ETH transfer
|
|
54
|
+
await sdk.signAndSend({
|
|
55
|
+
network: "ethereum:1", // Chain ID in network string
|
|
56
|
+
request: {
|
|
57
|
+
toAddress: "0x742d35cc6634C0532925a3b8D65e95f32B6b5582" as `0x${string}`,
|
|
58
|
+
data: "0x" as `0x${string}`,
|
|
59
|
+
value: "1000000000000000000" // 1 ETH in wei
|
|
60
|
+
},
|
|
61
|
+
message: "Sending 1 ETH"
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Contract interaction
|
|
65
|
+
await sdk.signAndSend({
|
|
66
|
+
network: "ethereum:42161", // Arbitrum
|
|
67
|
+
request: {
|
|
68
|
+
toAddress: "0xTokenContract..." as `0x${string}`,
|
|
69
|
+
data: "0xa9059cbb..." as `0x${string}`, // encoded transfer()
|
|
70
|
+
value: "0"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Solana
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
await sdk.signAndSend({
|
|
79
|
+
network: "solana",
|
|
80
|
+
request: {
|
|
81
|
+
hexTransaction: "010001030a0b..." // serialized VersionedTransaction
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## 🔧 Sample Agent Structure
|
|
88
|
+
|
|
89
|
+
### SDK Configuration
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Agent, AgentSdk, ExecutionFunctionContract, StopFunctionContract } from "@circuitorg/agents-sdk";
|
|
93
|
+
|
|
94
|
+
// Agent execution function using the new AgentSdk v1.0 API
|
|
95
|
+
const executionFunction: ExecutionFunctionContract = async (request) => {
|
|
96
|
+
console.log(`🚀 Agent execution called for session ${request.sessionId}`);
|
|
97
|
+
|
|
98
|
+
const sdk = new AgentSdk({
|
|
99
|
+
sessionId: request.sessionId,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// Get user's wallet address (use vitalik.eth for demo if no address provided)
|
|
104
|
+
const userAddress = request.sessionWalletAddress
|
|
105
|
+
|
|
106
|
+
await sdk.addMessage({
|
|
107
|
+
type: "observe",
|
|
108
|
+
shortMessage: `🚀 Agent SDK v1.0 demo - Hello ${userAddress}`
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return { success: true};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
await sdk.addMessage({
|
|
114
|
+
type: "error",
|
|
115
|
+
shortMessage: `Agent execution error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
116
|
+
});
|
|
117
|
+
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const stopFunction: StopFunctionContract = async (request) => {
|
|
122
|
+
const sdk = new AgentSdk({
|
|
123
|
+
sessionId: request.sessionId,
|
|
124
|
+
testing: true
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await sdk.addMessage({
|
|
129
|
+
type: "observe",
|
|
130
|
+
shortMessage: "🛑 Agent stopping with new SDK v1.0"
|
|
131
|
+
});
|
|
132
|
+
return { success: true };
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// DONT MODIFY BELOW THIS
|
|
139
|
+
const agent = new Agent({ executionFunction, stopFunction });
|
|
140
|
+
|
|
141
|
+
// Start the server for local development
|
|
142
|
+
agent.run();
|
|
143
|
+
|
|
144
|
+
// Export the agent for Cloudflare Workers
|
|
145
|
+
export default agent.getWorkerExport();
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core type definitions for the Agent SDK
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for the SDK client
|
|
10
|
+
*/
|
|
11
|
+
interface SDKConfig {
|
|
12
|
+
/** Session ID for the current agent instance */
|
|
13
|
+
sessionId: number;
|
|
14
|
+
/** Enable verbose logging for debugging */
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
/** Enable testing mode to return mock responses */
|
|
17
|
+
testing?: boolean;
|
|
18
|
+
/** Optional base URL for local development (auto-detected if omitted) */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Network type definitions and guard functions
|
|
24
|
+
*/
|
|
25
|
+
type Network = `ethereum:${number}` | "solana";
|
|
26
|
+
/**
|
|
27
|
+
* Type guard to check if network is Ethereum-based
|
|
28
|
+
* @param network - Network to check
|
|
29
|
+
* @returns true if network is ethereum:chainId format
|
|
30
|
+
*/
|
|
31
|
+
declare function isEthereumNetwork(network: Network): network is `ethereum:${number}`;
|
|
32
|
+
/**
|
|
33
|
+
* Type guard to check if network is Solana
|
|
34
|
+
* @param network - Network to check
|
|
35
|
+
* @returns true if network is "solana"
|
|
36
|
+
*/
|
|
37
|
+
declare function isSolanaNetwork(network: Network): network is "solana";
|
|
38
|
+
/**
|
|
39
|
+
* Extract chain ID from Ethereum network string
|
|
40
|
+
* @param network - Ethereum network string
|
|
41
|
+
* @returns Chain ID as number
|
|
42
|
+
*/
|
|
43
|
+
declare function getChainIdFromNetwork(network: `ethereum:${number}`): number;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Request type definitions with conditional shapes based on network
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Main signAndSend request type with network-specific conditional shapes
|
|
51
|
+
*/
|
|
52
|
+
type SignAndSendRequest = {
|
|
53
|
+
network: Network;
|
|
54
|
+
/** Optional short message to be attached to the transaction for observability */
|
|
55
|
+
message?: string;
|
|
56
|
+
} & ({
|
|
57
|
+
network: `ethereum:${number}`;
|
|
58
|
+
request: {
|
|
59
|
+
toAddress: `0x${string}`;
|
|
60
|
+
data: `0x${string}`;
|
|
61
|
+
value: string;
|
|
62
|
+
};
|
|
63
|
+
} | {
|
|
64
|
+
network: "solana";
|
|
65
|
+
request: {
|
|
66
|
+
hexTransaction: string;
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Message request for addMessage function
|
|
71
|
+
*/
|
|
72
|
+
type AddMessageRequest = {
|
|
73
|
+
type: "observe" | "validate" | "reflect" | "error" | "warning";
|
|
74
|
+
shortMessage: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Response type definitions
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Standard response from signAndSend operations
|
|
82
|
+
*/
|
|
83
|
+
type SignAndSendResponse = {
|
|
84
|
+
/** Internal transaction ID for tracking */
|
|
85
|
+
internalTransactionId: number;
|
|
86
|
+
/** Transaction hash once broadcast */
|
|
87
|
+
txHash: string;
|
|
88
|
+
/** Optional transaction URL (explorer link) */
|
|
89
|
+
transactionUrl?: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Main AgentSdk class with simplified API surface
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Main SDK entrypoint used by agents to interact with the Circuit backend.
|
|
98
|
+
*
|
|
99
|
+
* Provides a minimal, type-safe surface with two core methods that cover the
|
|
100
|
+
* majority of agent interactions:
|
|
101
|
+
*
|
|
102
|
+
* - `addMessage()` — emit timeline messages for observability and UX
|
|
103
|
+
* - `signAndSend()` — sign and broadcast transactions across networks
|
|
104
|
+
*
|
|
105
|
+
* Quick start:
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { AgentSdk } from "@circuit/agent-sdk";
|
|
108
|
+
*
|
|
109
|
+
* const sdk = new AgentSdk({
|
|
110
|
+
* sessionId: 12345,
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* await sdk.addMessage({ type: "observe", shortMessage: "Starting" });
|
|
114
|
+
* const tx = await sdk.signAndSend({
|
|
115
|
+
* network: "ethereum:42161",
|
|
116
|
+
* request: {
|
|
117
|
+
* toAddress: "0xabc..." as `0x${string}`,
|
|
118
|
+
* data: "0x" as `0x${string}`,
|
|
119
|
+
* value: "0"
|
|
120
|
+
* },
|
|
121
|
+
* message: "Funding user"
|
|
122
|
+
* });
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
declare class AgentSdk {
|
|
126
|
+
private client;
|
|
127
|
+
private config;
|
|
128
|
+
/**
|
|
129
|
+
* Create a new `AgentSdk` instance.
|
|
130
|
+
*
|
|
131
|
+
* @param config - SDK configuration
|
|
132
|
+
* @param config.sessionId - Numeric session identifier that scopes auth and actions
|
|
133
|
+
* @param config.verbose - When true, prints detailed request/response logs
|
|
134
|
+
* @param config.testing - When true, short-circuits network calls with mock values
|
|
135
|
+
* @param config.baseUrl - Override API base URL (detected automatically otherwise)
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const sdk = new AgentSdk({ sessionId: 42, verbose: true });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
constructor(config: SDKConfig);
|
|
142
|
+
private log;
|
|
143
|
+
/**
|
|
144
|
+
* Add a message to the agent timeline.
|
|
145
|
+
*
|
|
146
|
+
* Messages show up in session traces and UIs and are useful for observability,
|
|
147
|
+
* human-in-the-loop reviews, and debugging.
|
|
148
|
+
*
|
|
149
|
+
* @param message - Timeline message
|
|
150
|
+
* @param message.type - One of: `"observe" | "validate" | "reflect" | "error" | "warning"`
|
|
151
|
+
* @param message.shortMessage - Short, human-readable message (<= 250 chars)
|
|
152
|
+
* @returns Resolves when the message is accepted by the backend
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* await sdk.addMessage({ type: "observe", shortMessage: "Starting swap" });
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
addMessage(message: AddMessageRequest): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Sign and broadcast a transaction on the specified network.
|
|
161
|
+
*
|
|
162
|
+
* The backend performs validation and policy checks; you only need to provide
|
|
163
|
+
* the minimal request shape for the selected network.
|
|
164
|
+
*
|
|
165
|
+
* @param request - Network-specific transaction input
|
|
166
|
+
* @param request.network - `"solana"` or `"ethereum:${number}"`
|
|
167
|
+
* @param request.message - Optional human-readable context message (short), stored with the transaction
|
|
168
|
+
* @param request.request - Transaction payload
|
|
169
|
+
* - For Ethereum: `{ toAddress, data, value }`
|
|
170
|
+
* - `toAddress`: recipient contract or EOA as `0x${string}`
|
|
171
|
+
* - `data`: calldata as `0x${string}` (use `"0x"` for simple transfers)
|
|
172
|
+
* - `value`: stringified wei amount
|
|
173
|
+
* - For Solana: `{ hexTransaction }`
|
|
174
|
+
* - `hexTransaction`: serialized `VersionedTransaction` as hex string
|
|
175
|
+
* @returns Promise resolving to `{ internalTransactionId, txHash, transactionUrl? }`
|
|
176
|
+
* @throws Error if the network is unsupported or the backend rejects the request
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* // Ethereum (Arbitrum) native transfer
|
|
180
|
+
* await sdk.signAndSend({
|
|
181
|
+
* network: "ethereum:42161",
|
|
182
|
+
* request: {
|
|
183
|
+
* toAddress: "0xabc..." as `0x${string}`,
|
|
184
|
+
* data: "0x" as `0x${string}`,
|
|
185
|
+
* value: "1000000000000000" // 0.001 ETH
|
|
186
|
+
* },
|
|
187
|
+
* message: "Self-transfer demo"
|
|
188
|
+
* });
|
|
189
|
+
*
|
|
190
|
+
* // Solana
|
|
191
|
+
* await sdk.signAndSend({
|
|
192
|
+
* network: "solana",
|
|
193
|
+
* request: { hexTransaction: "01ab..." },
|
|
194
|
+
* message: "Swap with Jupiter"
|
|
195
|
+
* });
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
signAndSend(request: SignAndSendRequest): Promise<SignAndSendResponse>;
|
|
199
|
+
/**
|
|
200
|
+
* Handle EVM transaction signing and broadcasting
|
|
201
|
+
*/
|
|
202
|
+
private handleEvmTransaction;
|
|
203
|
+
/**
|
|
204
|
+
* Handle Solana transaction signing and broadcasting
|
|
205
|
+
*/
|
|
206
|
+
private handleSolanaTransaction;
|
|
207
|
+
/**
|
|
208
|
+
* Send messages to the agent timeline (migrated from AgentToolset)
|
|
209
|
+
*/
|
|
210
|
+
private messageSend;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Low-level HTTP client used internally by the SDK.
|
|
215
|
+
*
|
|
216
|
+
* - Automatically detects Cloudflare Worker environment and uses service bindings
|
|
217
|
+
* - Falls back to HTTP requests for standard environments
|
|
218
|
+
* - For local development, automatically reads auth token from ~/.config/circuit/auth.json
|
|
219
|
+
* - Adds session ID and agent slug headers automatically
|
|
220
|
+
* - Emits verbose request/response logs when `SDKConfig.verbose` is enabled
|
|
221
|
+
*
|
|
222
|
+
* Although exported for advanced scenarios, most users should interact with
|
|
223
|
+
* higher-level abstractions like `AgentSdk` and `AgentUtils`.
|
|
224
|
+
*/
|
|
225
|
+
declare class APIClient {
|
|
226
|
+
private config;
|
|
227
|
+
private baseUrl;
|
|
228
|
+
private isCloudflareWorker;
|
|
229
|
+
private hasServiceBinding;
|
|
230
|
+
/**
|
|
231
|
+
* Create an API client.
|
|
232
|
+
* @param config - SDK configuration
|
|
233
|
+
* @param config.sessionId - Numeric session identifier propagated as header
|
|
234
|
+
* @param config.baseUrl - Override base URL for local development (auto-detected if omitted)
|
|
235
|
+
* @param config.verbose - Enable detailed logs for debugging
|
|
236
|
+
*/
|
|
237
|
+
constructor(config: SDKConfig);
|
|
238
|
+
private getAgentSlug;
|
|
239
|
+
private getAuthHeaders;
|
|
240
|
+
private loadAuthConfig;
|
|
241
|
+
private log;
|
|
242
|
+
/**
|
|
243
|
+
* Perform a JSON HTTP request.
|
|
244
|
+
*
|
|
245
|
+
* Automatically uses service bindings when available, otherwise falls back to HTTP.
|
|
246
|
+
* Throws with a helpful message when the response is not ok.
|
|
247
|
+
*
|
|
248
|
+
* @param endpoint - API path beginning with `/v1/...`
|
|
249
|
+
* @param options - Fetch options (method, headers, body)
|
|
250
|
+
* @returns Parsed JSON response
|
|
251
|
+
* @throws Error when `response.ok` is false
|
|
252
|
+
*/
|
|
253
|
+
private makeRequest;
|
|
254
|
+
/**
|
|
255
|
+
* HTTP GET convenience method.
|
|
256
|
+
* @param endpoint - API path beginning with `/v1/...`
|
|
257
|
+
*/
|
|
258
|
+
get<T>(endpoint: string): Promise<T>;
|
|
259
|
+
/**
|
|
260
|
+
* HTTP POST convenience method sending a JSON body.
|
|
261
|
+
* @param endpoint - API path beginning with `/v1/...`
|
|
262
|
+
* @param data - Optional JSON payload to serialize
|
|
263
|
+
*/
|
|
264
|
+
post<T>(endpoint: string, data?: any): Promise<T>;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Request object for agent functions containing session and wallet information.
|
|
269
|
+
* Provided to your `execution`, `chat`, and `stop` handlers.
|
|
270
|
+
*/
|
|
271
|
+
declare const AgentRequestSchema: z.ZodObject<{
|
|
272
|
+
sessionId: z.ZodNumber;
|
|
273
|
+
sessionWalletAddress: z.ZodString;
|
|
274
|
+
otherParameters: z.ZodOptional<z.ZodObject<{}, z.core.$strip>>;
|
|
275
|
+
}, z.core.$strip>;
|
|
276
|
+
/**
|
|
277
|
+
* Standard response format for agent functions (execute and stop commands).
|
|
278
|
+
*/
|
|
279
|
+
declare const AgentResponseSchema: z.ZodObject<{
|
|
280
|
+
success: z.ZodBoolean;
|
|
281
|
+
error: z.ZodOptional<z.ZodString>;
|
|
282
|
+
message: z.ZodOptional<z.ZodString>;
|
|
283
|
+
}, z.core.$strip>;
|
|
284
|
+
/**
|
|
285
|
+
* Health check response format.
|
|
286
|
+
*/
|
|
287
|
+
declare const HealthResponseSchema: z.ZodObject<{
|
|
288
|
+
status: z.ZodString;
|
|
289
|
+
}, z.core.$strip>;
|
|
290
|
+
type AgentRequest = z.infer<typeof AgentRequestSchema>;
|
|
291
|
+
type AgentResponse = z.infer<typeof AgentResponseSchema>;
|
|
292
|
+
type HealthResponse = z.infer<typeof HealthResponseSchema>;
|
|
293
|
+
/**
|
|
294
|
+
* Contract for agent execution functions. Called to perform the agent's main task.
|
|
295
|
+
* @param request - Contains session info and wallet address to operate with
|
|
296
|
+
* @returns Promise resolving to a success/error response
|
|
297
|
+
* @example
|
|
298
|
+
* ```typescript
|
|
299
|
+
* // Execution function implementation
|
|
300
|
+
* const executionFunction: ExecutionFunctionContract = async (request) => {
|
|
301
|
+
* const agentToolset = new AgentToolset({ sessionId: request.sessionId });
|
|
302
|
+
*
|
|
303
|
+
* try {
|
|
304
|
+
* // Send status message
|
|
305
|
+
* await agentToolset.messageSend([{
|
|
306
|
+
* type: "observe",
|
|
307
|
+
* shortMessage: "Starting agent execution..."
|
|
308
|
+
* }]);
|
|
309
|
+
*
|
|
310
|
+
* // Example: Send a transaction
|
|
311
|
+
* const { txResult, broadcastResult } = await agentToolset.transactionSignAndBroadcast({
|
|
312
|
+
* chainId: 1,
|
|
313
|
+
* toAddress: request.sessionWalletAddress,
|
|
314
|
+
* data: "0x",
|
|
315
|
+
* valueWei: "1000000000000000000" // 1 ETH
|
|
316
|
+
* });
|
|
317
|
+
*
|
|
318
|
+
* if (broadcastResult.status === "broadcasted") {
|
|
319
|
+
* return {
|
|
320
|
+
* success: true,
|
|
321
|
+
* message: "Transaction executed successfully"
|
|
322
|
+
* };
|
|
323
|
+
* }
|
|
324
|
+
*
|
|
325
|
+
* return {
|
|
326
|
+
* success: false,
|
|
327
|
+
* error: "Transaction failed to broadcast",
|
|
328
|
+
* message: "Execution failed"
|
|
329
|
+
* };
|
|
330
|
+
* } catch (error) {
|
|
331
|
+
* return {
|
|
332
|
+
* success: false,
|
|
333
|
+
* error: error instanceof Error ? error.message : "Unknown error",
|
|
334
|
+
* message: "Execution failed"
|
|
335
|
+
* };
|
|
336
|
+
* }
|
|
337
|
+
* };
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
type ExecutionFunctionContract = (request: AgentRequest) => Promise<AgentResponse>;
|
|
341
|
+
/**
|
|
342
|
+
* Contract for agent stop/winddown function. Called to gracefully shut down and
|
|
343
|
+
* run cleanup logic (e.g. withdrawing positions, updating schedules).
|
|
344
|
+
* @param request - Contains session information and wallet address to operate with
|
|
345
|
+
* @returns Promise resolving to a success/error response
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* // Winddown function implementation
|
|
349
|
+
* const stopFunction: StopFunctionContract = async (request) => {
|
|
350
|
+
* const agentToolset = new AgentToolset({ sessionId: request.sessionId });
|
|
351
|
+
*
|
|
352
|
+
* try {
|
|
353
|
+
* // Notify about shutdown
|
|
354
|
+
* await agentToolset.messageSend([{
|
|
355
|
+
* type: "observe",
|
|
356
|
+
* shortMessage: "Agent shutting down...",
|
|
357
|
+
* longMessage: "Cleaning up resources and saving state"
|
|
358
|
+
* }]);
|
|
359
|
+
*
|
|
360
|
+
* // Example: Update sleep interval before shutdown
|
|
361
|
+
* await agentToolset.sessionUpdateSleepInterval({
|
|
362
|
+
* sleepMinutes: 60 // Set longer interval during inactive period
|
|
363
|
+
* });
|
|
364
|
+
*
|
|
365
|
+
* // Example: Final status message
|
|
366
|
+
* await agentToolset.messageSend([{
|
|
367
|
+
* type: "observe",
|
|
368
|
+
* shortMessage: "Shutdown complete",
|
|
369
|
+
* longMessage: `Agent ${request.sessionId} successfully shut down`
|
|
370
|
+
* }]);
|
|
371
|
+
*
|
|
372
|
+
* return {
|
|
373
|
+
* success: true,
|
|
374
|
+
* message: "Agent stopped successfully"
|
|
375
|
+
* };
|
|
376
|
+
* } catch (error) {
|
|
377
|
+
* return {
|
|
378
|
+
* success: false,
|
|
379
|
+
* error: error instanceof Error ? error.message : "Failed to clean up",
|
|
380
|
+
* message: "Stop operation failed"
|
|
381
|
+
* };
|
|
382
|
+
* }
|
|
383
|
+
* };
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
type StopFunctionContract = (request: AgentRequest) => Promise<AgentResponse>;
|
|
387
|
+
/**
|
|
388
|
+
* Contract for agent chat functions. Handle interactive messaging with the agent.
|
|
389
|
+
* @param request - Contains session information and wallet address to operate with
|
|
390
|
+
* @returns Promise resolving to a success/error response
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* // Chat function implementation
|
|
394
|
+
* const chatFunction: ChatFunctionContract = async (request) => {
|
|
395
|
+
* const agentToolset = new AgentToolset({ sessionId: request.sessionId });
|
|
396
|
+
*
|
|
397
|
+
* await agentToolset.messageSend([{
|
|
398
|
+
* type: "observe",
|
|
399
|
+
* shortMessage: "Processing chat message",
|
|
400
|
+
* longMessage: `Received message for wallet ${request.sessionWalletAddress}`
|
|
401
|
+
* }]);
|
|
402
|
+
*
|
|
403
|
+
* return {
|
|
404
|
+
* success: true,
|
|
405
|
+
* message: "Chat message processed"
|
|
406
|
+
* };
|
|
407
|
+
* };
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
type ChatFunctionContract = (request: AgentRequest) => Promise<AgentResponse>;
|
|
411
|
+
/**
|
|
412
|
+
* Contract for agent health check functions. Handle health status requests.
|
|
413
|
+
* @returns Promise resolving to an object with a status field
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* // Health check function implementation
|
|
417
|
+
* const healthCheckFunction: HealthCheckFunctionContract = async () => {
|
|
418
|
+
* // Check database connection, external services, etc.
|
|
419
|
+
* const isHealthy = await checkSystemHealth();
|
|
420
|
+
*
|
|
421
|
+
* return {
|
|
422
|
+
* status: isHealthy ? "healthy" : "unhealthy",
|
|
423
|
+
* // Optional additional fields
|
|
424
|
+
* uptime: process.uptime(),
|
|
425
|
+
* timestamp: new Date().toISOString()
|
|
426
|
+
* };
|
|
427
|
+
* };
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
430
|
+
type HealthCheckFunctionContract = () => Promise<HealthResponse>;
|
|
431
|
+
/**
|
|
432
|
+
* Configuration object for creating a new agent
|
|
433
|
+
*/
|
|
434
|
+
interface AgentConfig {
|
|
435
|
+
/** Main execution function that implements the agent's core logic */
|
|
436
|
+
executionFunction: ExecutionFunctionContract;
|
|
437
|
+
/** Optional chat function for handling interactive messaging */
|
|
438
|
+
chatFunction?: ChatFunctionContract;
|
|
439
|
+
/** Optional winddown function for cleanup operations, this is called when the agent is stopped */
|
|
440
|
+
stopFunction?: StopFunctionContract;
|
|
441
|
+
/** Optional health check function for monitoring agent health status */
|
|
442
|
+
healthCheckFunction?: HealthCheckFunctionContract;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* HTTP server wrapper for agent functions.
|
|
446
|
+
*
|
|
447
|
+
* Exposes the following endpoints:
|
|
448
|
+
* - `POST /execute` — required, calls your execution function
|
|
449
|
+
* - `POST /chat` — optional, when a `chatFunction` is provided
|
|
450
|
+
* - `POST /stop` and `DELETE /` — optional, when a `stopFunction` is provided
|
|
451
|
+
* - `GET /health` — always available, uses provided or default health check
|
|
452
|
+
*/
|
|
453
|
+
declare class Agent {
|
|
454
|
+
private app;
|
|
455
|
+
private executionFunction;
|
|
456
|
+
private chatFunction?;
|
|
457
|
+
private stopFunction?;
|
|
458
|
+
private healthCheckFunction?;
|
|
459
|
+
/**
|
|
460
|
+
* Create a new `Agent` with the provided handlers.
|
|
461
|
+
* @param config - Execution function is required; chat/stop/health are optional.
|
|
462
|
+
*/
|
|
463
|
+
constructor(config: AgentConfig);
|
|
464
|
+
private setupRoutes;
|
|
465
|
+
private getPortFromPackageJson;
|
|
466
|
+
run(port?: number): {
|
|
467
|
+
fetch: (request: Request, Env?: unknown, executionCtx?: hono.ExecutionContext) => Response | Promise<Response>;
|
|
468
|
+
} | undefined;
|
|
469
|
+
/** Get the worker export for Cloudflare Workers environments. */
|
|
470
|
+
getWorkerExport(): {
|
|
471
|
+
fetch: (request: Request, Env?: unknown, executionCtx?: hono.ExecutionContext) => Response | Promise<Response>;
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export { APIClient, type AddMessageRequest, Agent, AgentSdk, type ExecutionFunctionContract, type Network, type SDKConfig, type SignAndSendRequest, type SignAndSendResponse, type StopFunctionContract, getChainIdFromNetwork, isEthereumNetwork, isSolanaNetwork };
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var __getOwnPropNames=Object.getOwnPropertyNames,__require=(t=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(t,{get:(t,e)=>("undefined"!=typeof require?require:t)[e]}):t)(function(t){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')}),__commonJS=(t,e)=>function(){return e||(0,t[__getOwnPropNames(t)[0]])((e={exports:{}}).exports,e),e.exports},require_auth_loader=__commonJS({"src/utils/auth-loader.cjs"(exports,module){function loadAuthFromFileSystem(){try{if("undefined"==typeof process||!process.env?.HOME)return;const fs=eval("require")("fs"),path=eval("require")("path"),authPath=path.join(process.env.HOME,".config","circuit","auth.json");if(!fs.existsSync(authPath))return;const authContent=fs.readFileSync(authPath,"utf-8");return JSON.parse(authContent)}catch(t){return}}module.exports={loadAuthFromFileSystem:loadAuthFromFileSystem}}});import{z}from"zod";var API_BASE_URL_LOCAL="https://agents.circuit.org",MessageTypeSchema=z.enum(["observe","validate","reflect","error","warning"]),MessageSchema=z.object({type:MessageTypeSchema,shortMessage:z.string().max(250)}),MessagesRequestSchema=z.array(MessageSchema),MessagesResponseSchema=z.object({status:z.number(),message:z.string()}),TransactionSendRequestSchema=z.object({chainId:z.number(),toAddress:z.string(),data:z.string(),valueWei:z.string(),message:z.string().max(250).optional(),gas:z.number().optional(),maxFeePerGas:z.string().optional(),maxPriorityFeePerGas:z.string().optional()}),SolanaTransactionRequestSchema=z.object({hexTransaction:z.string(),message:z.string().max(250).optional()}),APIClient=class{config;baseUrl;isCloudflareWorker(){return"undefined"!=typeof globalThis&&void 0!==globalThis.Cloudflare}hasServiceBinding(){return this.isCloudflareWorker()&&void 0!==globalThis.AGENT_TOOLSET_SERVICE}constructor(t){this.config=t,this.baseUrl=t.baseUrl||API_BASE_URL_LOCAL}getAgentSlug(){return"undefined"!=typeof process&&process.env?.CIRCUIT_AGENT_SLUG?process.env.CIRCUIT_AGENT_SLUG:void 0!==globalThis.CIRCUIT_AGENT_SLUG?globalThis.CIRCUIT_AGENT_SLUG:void 0}getAuthHeaders(){const t={};t["X-Session-Id"]=this.config.sessionId.toString();const e=this.getAgentSlug();if(e&&(t["X-Agent-Slug"]=e),!this.hasServiceBinding())try{const e=this.loadAuthConfig();e?.sessionToken&&(t.Authorization=`Bearer ${e.sessionToken}`)}catch(t){}return t}loadAuthConfig(){try{const{loadAuthFromFileSystem:t}=require_auth_loader();return t()}catch(t){this.config.verbose}}log(t,e){this.config.verbose}async makeRequest(t,e={}){const s={...{"Content-Type":"application/json",...this.getAuthHeaders()},...e.headers};let r;if(this.log("=== REQUEST DETAILS ==="),this.log("Endpoint:",t),this.log("Method:",e.method||"GET"),this.log("Headers:",s),this.log("Body:",e.body),this.log("Session ID:",this.config.sessionId),this.log("Agent Slug:",this.getAgentSlug()||"not set"),this.log("Using Service Binding:",this.hasServiceBinding()),this.log("====================="),this.hasServiceBinding()){const n=globalThis.AGENT_TOOLSET_SERVICE,o=`https://agents.circuit.org${t}`,a={...e,headers:s};r=await n.fetch(o,a)}else{const n=`${this.baseUrl}${t}`,o={...e,headers:s};r=await fetch(n,o)}if(this.log("=== RESPONSE DETAILS ==="),this.log("Status:",r.status),this.log("Status Text:",r.statusText),this.log("Response Headers:",Object.fromEntries(r.headers.entries())),this.log("======================"),!r.ok){const t=await r.json().catch(()=>({}));throw this.log("=== ERROR RESPONSE ==="),this.log("Error Data:",t),this.log("===================="),new Error(t.message||`HTTP ${r.status}: ${r.statusText}`)}const n=await r.json();return this.log("=== SUCCESS RESPONSE ==="),this.log("Response Data:",n),this.log("======================"),n}async get(t){return this.makeRequest(t,{method:"GET"})}async post(t,e){return this.makeRequest(t,{method:"POST",body:e?JSON.stringify(e):void 0})}};function isEthereumNetwork(t){return t.startsWith("ethereum:")}function isSolanaNetwork(t){return"solana"===t}function getChainIdFromNetwork(t){return Number(t.split(":")[1])}var AgentSdk=class{client;config;constructor(t){this.config=t,this.client=new APIClient(t)}log(t,e){this.config.verbose}async addMessage(t){this.log("=== ADD MESSAGE ==="),this.log("Message:",t),this.log("===================");const e=[{type:t.type,shortMessage:t.shortMessage}];await this.messageSend(e)}async signAndSend(t){if(this.log("=== SIGN AND SEND ==="),this.log("Request:",t),this.log("Testing mode:",this.config.testing),this.log("===================="),this.config.testing)return{internalTransactionId:123,txHash:isEthereumNetwork(t.network)?"0xTEST":"TEST_SOL_TX",transactionUrl:void 0};if(isEthereumNetwork(t.network)){const e=getChainIdFromNetwork(t.network);if("toAddress"in t.request)return this.handleEvmTransaction({chainId:e,toAddress:t.request.toAddress,data:t.request.data,valueWei:t.request.value,message:t.message})}if(isSolanaNetwork(t.network)&&"hexTransaction"in t.request)return this.handleSolanaTransaction({hexTransaction:t.request.hexTransaction,message:t.message});throw new Error(`Unsupported network: ${t.network}`)}async handleEvmTransaction(t){const e=await this.client.post("/v1/transactions/evm",t),s=await this.client.post(`/v1/transactions/evm/${e.internalTransactionId}/broadcast`);return{internalTransactionId:e.internalTransactionId,txHash:s.txHash,transactionUrl:s.transactionUrl}}async handleSolanaTransaction(t){const e=await this.client.post("/v1/transactions/solana",t),s=await this.client.post(`/v1/transactions/solana/${e.internalTransactionId}/broadcast`);return{internalTransactionId:e.internalTransactionId,txHash:s.txHash,transactionUrl:s.transactionUrl}}async messageSend(t){return this.config.testing?{status:200,message:"Messages added successfully (TESTING)"}:this.client.post("/v1/messages",t)}};import{zValidator}from"@hono/zod-validator";import{Hono}from"hono";import{cors}from"hono/cors";import{z as z2}from"zod";var AgentRequestSchema=z2.object({sessionId:z2.number(),sessionWalletAddress:z2.string(),otherParameters:z2.object().optional()}),AgentResponseSchema=z2.object({success:z2.boolean(),error:z2.string().optional(),message:z2.string().optional()}),HealthResponseSchema=z2.object({status:z2.string()}),Agent=class{app;executionFunction;chatFunction;stopFunction;healthCheckFunction;constructor(t){this.app=new Hono,this.executionFunction=t.executionFunction,this.chatFunction=t.chatFunction,this.stopFunction=t.stopFunction,this.healthCheckFunction=t.healthCheckFunction||(async()=>({status:"healthy"})),this.app.use("*",cors()),this.setupRoutes()}setupRoutes(){this.app.post("/execute",zValidator("json",AgentRequestSchema),async t=>{try{const e=t.req.valid("json"),s=await this.executionFunction(e);return t.json(s)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:"Unknown error",message:"Execution failed"},500)}}),this.chatFunction&&this.app.post("/chat",zValidator("json",AgentRequestSchema),async t=>{try{const e=t.req.valid("json"),s=await(this.chatFunction?.(e));return t.json(s)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:"Unknown error",message:"Chat failed"},500)}}),this.stopFunction&&(this.app.post("/stop",zValidator("json",AgentRequestSchema),async t=>{try{const e=t.req.valid("json"),s=await(this.stopFunction?.(e));return t.json(s)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:"Unknown error",message:"Stop operation failed"},500)}}),this.app.delete("/",zValidator("json",AgentRequestSchema),async t=>{try{const e=t.req.valid("json"),s=await(this.stopFunction?.(e));return t.json(s)}catch(e){return t.json({success:!1,error:e instanceof Error?e.message:"Unknown error",message:"Stop operation failed"},500)}})),this.app.get("/health",async t=>{try{const e=await(this.healthCheckFunction?.());return t.json(e)}catch(e){return t.json({status:"unhealthy",error:e instanceof Error?e.message:"Unknown error",timestamp:(new Date).toISOString()},500)}})}getPortFromPackageJson(){try{const t=__require("fs"),e=__require("path").join(process.cwd(),"package.json");if(t.existsSync(e)){const s=JSON.parse(t.readFileSync(e,"utf-8"));if(s.circuit?.port)return Number.parseInt(s.circuit.port,10)}}catch(t){}return null}run(port){const isCloudflareWorker="undefined"!=typeof globalThis&&void 0!==globalThis.Cloudflare;if(isCloudflareWorker)return this.getWorkerExport();const bunEnv=globalThis.Bun?.env,envPort=process.env.AGENT_PORT||bunEnv?.AGENT_PORT,packageJsonPort=this.getPortFromPackageJson();let finalPort=port;!finalPort&&envPort&&(finalPort=Number.parseInt(envPort,10)),!finalPort&&packageJsonPort&&(finalPort=packageJsonPort),finalPort||(finalPort=3e3);try{const req=eval("require"),{serve:serve}=req("@hono/node-server");serve({fetch:this.app.fetch,port:finalPort})}catch(t){process.exit(1)}}getWorkerExport(){return{fetch:this.app.fetch}}};function createAgentHandler(t,e,s,r){return new Agent({executionFunction:t,chatFunction:e,stopFunction:s,healthCheckFunction:r})}export{APIClient,Agent,AgentSdk,getChainIdFromNetwork,isEthereumNetwork,isSolanaNetwork};
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@circuitorg/agent-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "typescript sdk for the Agent Toolset Service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup",
|
|
10
|
+
"postbuild": "cp package.json dist && cp README.md dist",
|
|
11
|
+
"dev": "tsup --watch",
|
|
12
|
+
"test": "jest --passWithNoTests",
|
|
13
|
+
"test:package": "./scripts/test-package.sh",
|
|
14
|
+
"test:install": "./scripts/test-install.sh",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"lint": "biome check .",
|
|
17
|
+
"lint:fix": "biome check --write .",
|
|
18
|
+
"format": "biome format --write ."
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"agent",
|
|
22
|
+
"toolset",
|
|
23
|
+
"sdk",
|
|
24
|
+
"blockchain",
|
|
25
|
+
"transactions"
|
|
26
|
+
],
|
|
27
|
+
"author": "circuitorg",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/circuitorg/agents-sdk/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/circuitorg/agents-sdk#readme",
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"browser": {
|
|
35
|
+
"ws": "./src/stubs/ws-stub.js",
|
|
36
|
+
"@solana/rpc-subscriptions": "./src/stubs/subscriptions-stub.js",
|
|
37
|
+
"@solana/rpc-subscriptions-spec": "./src/stubs/subscriptions-stub.js",
|
|
38
|
+
"@solana/rpc-subscriptions-channel-websocket": "./src/stubs/subscriptions-stub.js",
|
|
39
|
+
"@solana/subscribable": "./src/stubs/subscriptions-stub.js",
|
|
40
|
+
"@solana/transaction-confirmation": "./src/stubs/subscriptions-stub.js"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@hono/zod-validator": "^0.7.2",
|
|
44
|
+
"@solana-program/system": "^0.7.0",
|
|
45
|
+
"@solana/kit": "^2.3.0",
|
|
46
|
+
"@solana/transactions": "^2.3.0",
|
|
47
|
+
"hono": "^4.9.1",
|
|
48
|
+
"zod": "^4.0.17"
|
|
49
|
+
},
|
|
50
|
+
"optionalDependencies": {
|
|
51
|
+
"@hono/node-server": "^1.18.2",
|
|
52
|
+
"@solana-program/token": "0.5.1",
|
|
53
|
+
"@solana-program/token-2022": "0.4.2"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@biomejs/biome": "^1.7.0",
|
|
57
|
+
"@types/jest": "29.5.14",
|
|
58
|
+
"@types/node": "^20.19.7",
|
|
59
|
+
"dts-bundle-generator": "^9.5.1",
|
|
60
|
+
"esbuild": "^0.25.9",
|
|
61
|
+
"jest": "29.7.0",
|
|
62
|
+
"terser": "^5.43.1",
|
|
63
|
+
"tsup": "^8.5.0",
|
|
64
|
+
"typescript": "5.8.3"
|
|
65
|
+
},
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/circuitorg/agents-sdk.git"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/stubs/ws-stub.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// This file is intentionally in JavaScript to avoid TypeScript/bundler transformations
|
|
2
|
+
// It provides a way to load auth config from the file system in Node.js environments
|
|
3
|
+
|
|
4
|
+
function loadAuthFromFileSystem() {
|
|
5
|
+
try {
|
|
6
|
+
// Check if we're in a Node.js environment
|
|
7
|
+
if (typeof process === 'undefined' || !process.env?.HOME) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Use dynamic require to avoid bundler processing
|
|
12
|
+
const fs = eval("require")('fs');
|
|
13
|
+
const path = eval("require")('path');
|
|
14
|
+
|
|
15
|
+
const authPath = path.join(process.env.HOME, '.config', 'circuit', 'auth.json');
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(authPath)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const authContent = fs.readFileSync(authPath, 'utf-8');
|
|
22
|
+
return JSON.parse(authContent);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// Silently fail if we can't read the auth config
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { loadAuthFromFileSystem };
|