@prmichaelsen/agentbase-core 0.1.0 → 0.1.2
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 +351 -0
- package/dist/client/api-types.generated.d.ts +4024 -0
- package/dist/client/api-types.generated.d.ts.map +1 -0
- package/dist/client/api-types.generated.js +6 -0
- package/dist/client/api-types.generated.js.map +1 -0
- package/dist/client/app.d.ts +126 -0
- package/dist/client/app.d.ts.map +1 -0
- package/dist/client/app.js +107 -0
- package/dist/client/app.js.map +1 -0
- package/dist/client/http-transport.d.ts +42 -0
- package/dist/client/http-transport.d.ts.map +1 -0
- package/dist/client/http-transport.js +113 -0
- package/dist/client/http-transport.js.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/oauth.d.ts +86 -0
- package/dist/client/oauth.d.ts.map +1 -0
- package/dist/client/oauth.js +148 -0
- package/dist/client/oauth.js.map +1 -0
- package/dist/client/svc.d.ts +511 -0
- package/dist/client/svc.d.ts.map +1 -0
- package/dist/client/svc.js +409 -0
- package/dist/client/svc.js.map +1 -0
- package/dist/config/index.d.ts +34 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +50 -0
- package/dist/config/index.js.map +1 -0
- package/dist/errors/app-errors.d.ts +34 -0
- package/dist/errors/app-errors.d.ts.map +1 -0
- package/dist/errors/app-errors.js +34 -0
- package/dist/errors/app-errors.js.map +1 -0
- package/dist/errors/base.error.d.ts +8 -0
- package/dist/errors/base.error.d.ts.map +1 -0
- package/dist/errors/base.error.js +9 -0
- package/dist/errors/base.error.js.map +1 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +12 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/auth/error-handler.d.ts +6 -0
- package/dist/lib/auth/error-handler.d.ts.map +1 -0
- package/dist/lib/auth/error-handler.js +18 -0
- package/dist/lib/auth/error-handler.js.map +1 -0
- package/dist/lib/auth/guards.d.ts +5 -6
- package/dist/lib/auth/guards.d.ts.map +1 -1
- package/dist/lib/auth/guards.js +7 -18
- package/dist/lib/auth/guards.js.map +1 -1
- package/dist/lib/auth/index.d.ts +1 -0
- package/dist/lib/auth/index.d.ts.map +1 -1
- package/dist/lib/auth/index.js +1 -0
- package/dist/lib/auth/index.js.map +1 -1
- package/dist/lib/auth/session.d.ts.map +1 -1
- package/dist/lib/auth/session.js +8 -7
- package/dist/lib/auth/session.js.map +1 -1
- package/dist/lib/rate-limiter.d.ts.map +1 -1
- package/dist/lib/rate-limiter.js +2 -1
- package/dist/lib/rate-limiter.js.map +1 -1
- package/dist/services/auth.interface.d.ts +15 -0
- package/dist/services/auth.interface.d.ts.map +1 -0
- package/dist/services/auth.interface.js +2 -0
- package/dist/services/auth.interface.js.map +1 -0
- package/dist/services/base.service.d.ts +17 -1
- package/dist/services/base.service.d.ts.map +1 -1
- package/dist/services/base.service.js +26 -3
- package/dist/services/base.service.js.map +1 -1
- package/dist/services/confirmation-token.service.d.ts +5 -1
- package/dist/services/confirmation-token.service.d.ts.map +1 -1
- package/dist/services/confirmation-token.service.js.map +1 -1
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/types/branded.d.ts +24 -0
- package/dist/types/branded.d.ts.map +1 -0
- package/dist/types/branded.js +9 -0
- package/dist/types/branded.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/result.d.ts +20 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +44 -0
- package/dist/types/result.js.map +1 -0
- package/package.json +22 -3
package/README.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# @prmichaelsen/agentbase-core
|
|
2
|
+
|
|
3
|
+
Shared service infrastructure for agentbase projects — BaseService, auth, Firebase wrappers, logging, typed errors, Result type, client SDK, and common utilities.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @prmichaelsen/agentbase-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer Dependencies
|
|
12
|
+
|
|
13
|
+
All peer dependencies are **optional** — install only what you use:
|
|
14
|
+
|
|
15
|
+
| Package | When needed |
|
|
16
|
+
|---------|-------------|
|
|
17
|
+
| `@prmichaelsen/firebase-admin-sdk-v8` | Server-side auth (`getServerSession`, `requireAuth`, `createSessionCookie`) |
|
|
18
|
+
| `firebase` | Client-side auth (`signIn`, `signUp`, `initializeFirebase`) |
|
|
19
|
+
| `jsonwebtoken` | JWT handling (if used directly) |
|
|
20
|
+
|
|
21
|
+
## Export Entry Points
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { ... } from '@prmichaelsen/agentbase-core' // everything
|
|
25
|
+
import { ... } from '@prmichaelsen/agentbase-core/services' // BaseService, ConfirmationTokenService
|
|
26
|
+
import { ... } from '@prmichaelsen/agentbase-core/lib' // logger, firebase, utilities
|
|
27
|
+
import { ... } from '@prmichaelsen/agentbase-core/lib/auth' // server auth guards
|
|
28
|
+
import { ... } from '@prmichaelsen/agentbase-core/types' // AuthUser, Result, branded types
|
|
29
|
+
import { ... } from '@prmichaelsen/agentbase-core/client' // HTTP client, SvcClient, AppClient, OAuth
|
|
30
|
+
import { ... } from '@prmichaelsen/agentbase-core/config' // loadConfig, validateConfig
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Services
|
|
34
|
+
|
|
35
|
+
### BaseService
|
|
36
|
+
|
|
37
|
+
Abstract base class with config injection, structured logging, lifecycle hooks, and state tracking.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { BaseService, ServiceState, type Logger } from '@prmichaelsen/agentbase-core/services'
|
|
41
|
+
|
|
42
|
+
class UserService extends BaseService<{ dbUrl: string }> {
|
|
43
|
+
constructor(config: { dbUrl: string }, logger: Logger) {
|
|
44
|
+
super(config, logger)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async initialize() {
|
|
48
|
+
await super.initialize() // sets state to Initialized
|
|
49
|
+
this.logger.info('Connected', { url: this.config.dbUrl })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async findUser(id: string) {
|
|
53
|
+
this.ensureInitialized() // throws if not initialized
|
|
54
|
+
// ...
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const svc = new UserService({ dbUrl: '...' }, logger)
|
|
59
|
+
svc.getState() // ServiceState.Uninitialized
|
|
60
|
+
await svc.initialize()
|
|
61
|
+
svc.getState() // ServiceState.Initialized
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### ConfirmationTokenService
|
|
65
|
+
|
|
66
|
+
In-memory token store for two-step confirmation flows. Tokens are single-use, user-scoped, and expire after TTL.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { ConfirmationTokenService } from '@prmichaelsen/agentbase-core/services'
|
|
70
|
+
|
|
71
|
+
const confirmations = new ConfirmationTokenService(5 * 60 * 1000) // 5 min TTL
|
|
72
|
+
|
|
73
|
+
// Step 1: Generate token
|
|
74
|
+
const token = confirmations.generateToken({
|
|
75
|
+
type: 'delete-account',
|
|
76
|
+
userId: 'user-123',
|
|
77
|
+
params: { accountId: 'abc' },
|
|
78
|
+
summary: 'Delete account abc',
|
|
79
|
+
createdAt: Date.now(),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Step 2: Consume token (single-use)
|
|
83
|
+
const action = confirmations.consumeToken(token, 'user-123')
|
|
84
|
+
if (action) { /* execute */ }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Error Types
|
|
88
|
+
|
|
89
|
+
Typed error hierarchy with discriminated `kind` field and HTTP status mapping.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import {
|
|
93
|
+
UnauthorizedError, ForbiddenError, ValidationError, NotFoundError,
|
|
94
|
+
ConflictError, RateLimitError, ExternalError, InternalError,
|
|
95
|
+
isAppError, errorToStatusCode,
|
|
96
|
+
} from '@prmichaelsen/agentbase-core'
|
|
97
|
+
|
|
98
|
+
// Throw typed errors
|
|
99
|
+
throw new ValidationError('Email is required', { field: 'email' })
|
|
100
|
+
throw new NotFoundError('User not found')
|
|
101
|
+
|
|
102
|
+
// Type guard
|
|
103
|
+
try {
|
|
104
|
+
await doSomething()
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (isAppError(err)) {
|
|
107
|
+
console.log(err.kind) // 'VALIDATION' | 'NOT_FOUND' | ...
|
|
108
|
+
console.log(err.statusCode) // 400, 404, ...
|
|
109
|
+
console.log(err.context) // { field: 'email' }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Map to HTTP status
|
|
114
|
+
const status = errorToStatusCode(err) // 400 for ValidationError, 500 for unknown
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Error | Kind | Status |
|
|
118
|
+
|-------|------|--------|
|
|
119
|
+
| `ValidationError` | `VALIDATION` | 400 |
|
|
120
|
+
| `UnauthorizedError` | `UNAUTHORIZED` | 401 |
|
|
121
|
+
| `ForbiddenError` | `FORBIDDEN` | 403 |
|
|
122
|
+
| `NotFoundError` | `NOT_FOUND` | 404 |
|
|
123
|
+
| `ConflictError` | `CONFLICT` | 409 |
|
|
124
|
+
| `RateLimitError` | `RATE_LIMIT` | 429 |
|
|
125
|
+
| `ExternalError` | `EXTERNAL` | 502 |
|
|
126
|
+
| `InternalError` | `INTERNAL` | 500 |
|
|
127
|
+
|
|
128
|
+
## Result Type
|
|
129
|
+
|
|
130
|
+
Discriminated `Result<T, E>` for operations where failure is expected.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { ok, err, isOk, isErr, mapOk, andThen, getOrElse, tryCatchAsync } from '@prmichaelsen/agentbase-core'
|
|
134
|
+
import type { Result } from '@prmichaelsen/agentbase-core'
|
|
135
|
+
|
|
136
|
+
// Create results
|
|
137
|
+
const success: Result<number> = ok(42)
|
|
138
|
+
const failure: Result<number, string> = err('not found')
|
|
139
|
+
|
|
140
|
+
// Type guards
|
|
141
|
+
if (isOk(success)) console.log(success.value) // 42
|
|
142
|
+
if (isErr(failure)) console.log(failure.error) // 'not found'
|
|
143
|
+
|
|
144
|
+
// Combinators
|
|
145
|
+
const doubled = mapOk(ok(5), x => x * 2) // Ok(10)
|
|
146
|
+
const chained = andThen(ok(5), x => x > 0 ? ok(x) : err('negative'))
|
|
147
|
+
const value = getOrElse(err('fail'), 0) // 0
|
|
148
|
+
|
|
149
|
+
// Wrap async functions
|
|
150
|
+
const result = await tryCatchAsync(
|
|
151
|
+
() => fetchUser(id),
|
|
152
|
+
(e) => new NotFoundError('User not found')
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Auth
|
|
157
|
+
|
|
158
|
+
### Server-Side (requires `@prmichaelsen/firebase-admin-sdk-v8`)
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import {
|
|
162
|
+
getServerSession, requireAuth, requireAdmin, isAdmin,
|
|
163
|
+
handleAuthError,
|
|
164
|
+
} from '@prmichaelsen/agentbase-core/lib/auth'
|
|
165
|
+
|
|
166
|
+
// Get session from request cookies
|
|
167
|
+
const session = await getServerSession(request)
|
|
168
|
+
|
|
169
|
+
// Guards throw typed errors — use try-catch
|
|
170
|
+
try {
|
|
171
|
+
await requireAuth(request) // throws UnauthorizedError
|
|
172
|
+
await requireAdmin(request, 'admin@example.com') // throws Unauthorized or Forbidden
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return handleAuthError(error) // converts to Response(401/403/500)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Boolean check (does not throw)
|
|
178
|
+
const admin = await isAdmin(request, 'admin@example.com')
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Client-Side (requires `firebase`)
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import {
|
|
185
|
+
initializeFirebase, signIn, signUp, signInAnonymously,
|
|
186
|
+
upgradeAnonymousAccount, logout, onAuthChange, getIdToken,
|
|
187
|
+
} from '@prmichaelsen/agentbase-core/lib'
|
|
188
|
+
|
|
189
|
+
initializeFirebase({ apiKey: '...', authDomain: '...', projectId: '...' })
|
|
190
|
+
|
|
191
|
+
await signIn('user@example.com', 'password')
|
|
192
|
+
await signUp('user@example.com', 'password')
|
|
193
|
+
await signInAnonymously()
|
|
194
|
+
await upgradeAnonymousAccount('user@example.com', 'password')
|
|
195
|
+
await logout()
|
|
196
|
+
|
|
197
|
+
const unsubscribe = onAuthChange((user) => { /* ... */ })
|
|
198
|
+
const token = await getIdToken()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Helpers
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { isRealUser, isRealUserServer } from '@prmichaelsen/agentbase-core/lib/auth'
|
|
205
|
+
|
|
206
|
+
if (isRealUser(user)) { /* non-anonymous */ }
|
|
207
|
+
if (isRealUserServer(authUser)) { /* non-anonymous, typed for AuthUser */ }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Client SDK
|
|
211
|
+
|
|
212
|
+
Full REST client for agentbase.me with 17 service classes, compound workflows, and OAuth support.
|
|
213
|
+
|
|
214
|
+
### Setup
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { HttpClient, AuthSvc, MemoriesSvc, OAuthClient } from '@prmichaelsen/agentbase-core/client'
|
|
218
|
+
|
|
219
|
+
// With API key
|
|
220
|
+
const http = new HttpClient({
|
|
221
|
+
baseUrl: 'https://agentbase.me',
|
|
222
|
+
auth: { type: 'apiKey', key: 'your-api-key' },
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// With OAuth bearer token
|
|
226
|
+
const http = new HttpClient({
|
|
227
|
+
baseUrl: 'https://agentbase.me',
|
|
228
|
+
auth: { type: 'bearer', token: () => oauthClient.getValidToken() },
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// With session cookie (browser)
|
|
232
|
+
const http = new HttpClient({
|
|
233
|
+
baseUrl: 'https://agentbase.me',
|
|
234
|
+
auth: { type: 'cookie' },
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### SvcClient (1:1 REST mirror)
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const memories = new MemoriesSvc(http)
|
|
242
|
+
|
|
243
|
+
// All methods return SdkResponse<T> — never throws
|
|
244
|
+
const result = await memories.search({ query: 'vacation photos', mode: 'semantic' })
|
|
245
|
+
if (result.error) {
|
|
246
|
+
console.error(result.error.message)
|
|
247
|
+
} else {
|
|
248
|
+
console.log(result.data) // { data: Memory[], total: number }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Or use throwOnError() for cleaner async/await
|
|
252
|
+
const { data } = await memories.feed({ limit: 10 }).then(r => r.throwOnError())
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Available services: `AuthSvc`, `OAuthSvc`, `MemoriesSvc`, `ConversationsSvc`, `ProfilesSvc`, `GroupsSvc`, `DmsSvc`, `SearchSvc`, `NotificationsSvc`, `RelationshipsSvc`, `BoardsSvc`, `TokensSvc`, `IntegrationsSvc`, `SettingsSvc`, `PaymentsSvc`, `UsageSvc`, `JobsSvc`
|
|
256
|
+
|
|
257
|
+
### AppClient (compound workflows)
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { AppClient } from '@prmichaelsen/agentbase-core/client'
|
|
261
|
+
|
|
262
|
+
const app = new AppClient({ auth, memories, conversations, profiles, groups, dms, search, notifications })
|
|
263
|
+
|
|
264
|
+
await app.loginAndGetSession(idToken)
|
|
265
|
+
await app.searchAndFetchMemories('vacation', 5)
|
|
266
|
+
await app.createGroupAndInvite('Book Club', 'Monthly reads', ['user-1', 'user-2'])
|
|
267
|
+
await app.getFullProfile('user-123') // profile + memory count
|
|
268
|
+
await app.startDm('user-456') // find existing or create new
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### OAuth Integration
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import {
|
|
275
|
+
OAuthClient, generateCodeVerifier, generateCodeChallenge,
|
|
276
|
+
buildAuthorizationUrl,
|
|
277
|
+
} from '@prmichaelsen/agentbase-core/client'
|
|
278
|
+
|
|
279
|
+
// 1. Generate PKCE params
|
|
280
|
+
const verifier = generateCodeVerifier()
|
|
281
|
+
const challenge = await generateCodeChallenge(verifier)
|
|
282
|
+
|
|
283
|
+
// 2. Build authorization URL
|
|
284
|
+
const url = buildAuthorizationUrl('https://agentbase.me', {
|
|
285
|
+
clientId: 'your-client-id',
|
|
286
|
+
redirectUri: 'http://localhost:3000/callback',
|
|
287
|
+
scope: 'read write',
|
|
288
|
+
codeChallenge: challenge,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// 3. After redirect, exchange code for tokens
|
|
292
|
+
const oauth = new OAuthClient(http, { clientId: 'your-client-id' })
|
|
293
|
+
await oauth.exchangeCode(code, redirectUri, verifier)
|
|
294
|
+
|
|
295
|
+
// 4. Get valid token (auto-refreshes if expired)
|
|
296
|
+
const token = await oauth.getValidToken()
|
|
297
|
+
|
|
298
|
+
// API token shortcut
|
|
299
|
+
await oauth.exchangeApiToken('your-api-token')
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Configuration
|
|
303
|
+
|
|
304
|
+
Centralized environment variable loading and validation.
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { loadConfig, validateConfig } from '@prmichaelsen/agentbase-core/config'
|
|
308
|
+
|
|
309
|
+
const config = loadConfig() // reads from process.env
|
|
310
|
+
validateConfig(config, { requireFirebaseAdmin: true }) // throws ValidationError if missing
|
|
311
|
+
|
|
312
|
+
// Or pass custom env
|
|
313
|
+
const config = loadConfig({ FIREBASE_PROJECT_ID: 'my-project', ... })
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Logger
|
|
317
|
+
|
|
318
|
+
Structured logger with automatic sensitive data redaction.
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import { createLogger, authLogger, apiLogger } from '@prmichaelsen/agentbase-core/lib'
|
|
322
|
+
|
|
323
|
+
const logger = createLogger('MyService')
|
|
324
|
+
logger.info('Request', { userId: 'abc', apiKey: 'secret' })
|
|
325
|
+
// Output: [MyService] Request { userId: 'abc', apiKey: '[REDACTED]' }
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Sanitization Utilities
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { sanitizeToken, sanitizeEmail, sanitizeUserId, sanitizeObject } from '@prmichaelsen/agentbase-core/lib'
|
|
332
|
+
|
|
333
|
+
sanitizeToken('abc123...') // "a1b2c3d4..."
|
|
334
|
+
sanitizeEmail('john@x.com') // "jo***@x.com"
|
|
335
|
+
sanitizeObject({ password: 'secret', name: 'ok' }) // { password: '[REDACTED]', name: 'ok' }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Types
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import type { AuthUser, ServerSession, Result, UserId, EmailAddress } from '@prmichaelsen/agentbase-core/types'
|
|
342
|
+
import { toUserId, toEmailAddress, toTimestamp } from '@prmichaelsen/agentbase-core/types'
|
|
343
|
+
|
|
344
|
+
// Branded primitives prevent mixing IDs
|
|
345
|
+
const uid: UserId = toUserId('firebase-uid')
|
|
346
|
+
const email: EmailAddress = toEmailAddress('user@example.com')
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## License
|
|
350
|
+
|
|
351
|
+
MIT
|