@dainprotocol/service-sdk 2.0.0 → 2.0.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 ADDED
@@ -0,0 +1,1082 @@
1
+ # DAIN Service SDK
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@dainprotocol/service-sdk.svg)](https://www.npmjs.com/package/@dainprotocol/service-sdk)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+
6
+ The official TypeScript SDK for building AI-powered services on the DAIN Protocol. Create intelligent services that can be discovered and used by AI agents across multiple platforms.
7
+
8
+ ## 📋 Table of Contents
9
+
10
+ - [Features](#-features)
11
+ - [Installation](#-installation)
12
+ - [Quick Start](#-quick-start)
13
+ - [Core Concepts](#-core-concepts)
14
+ - [Multi-Runtime Support](#-multi-runtime-support)
15
+ - [Tools](#-tools)
16
+ - [OAuth2 Integration](#-oauth2-integration)
17
+ - [Plugins](#-plugins)
18
+ - [Client SDK](#-client-sdk)
19
+ - [Advanced Features](#-advanced-features)
20
+ - [API Reference](#-api-reference)
21
+ - [Examples](#-examples)
22
+
23
+ ## ✨ Features
24
+
25
+ - 🚀 **Multi-Runtime Support** - Works seamlessly on Node.js, Deno, Cloudflare Workers, and Next.js
26
+ - 🔧 **Type-Safe Tools** - Define tools with Zod schemas for automatic validation and type inference
27
+ - 🔐 **OAuth2 Integration** - Built-in OAuth2 support with flexible storage adapters
28
+ - 🔌 **Plugin System** - Extend functionality with crypto, citations, time, and custom plugins
29
+ - 📡 **Real-Time Streaming** - Server-Sent Events (SSE) for streaming responses
30
+ - 🔄 **Long-Running Processes** - State machine-based process management
31
+ - 💳 **Payment Integration** - Built-in support for Stripe payments
32
+ - 🎨 **UI Components** - Return rich UI responses alongside data
33
+ - 📊 **Contexts & Datasources** - Provide dynamic context and data to AI agents
34
+ - 🛡️ **Signature-Based Auth** - Secure authentication using Ed25519 signatures
35
+
36
+ ## 📦 Installation
37
+
38
+ ```bash
39
+ npm install @dainprotocol/service-sdk zod
40
+ ```
41
+
42
+ ### Optional Dependencies
43
+
44
+ For OAuth2 with persistent storage:
45
+ ```bash
46
+ npm install @dainprotocol/oauth2-token-manager @dainprotocol/oauth2-storage-drizzle
47
+ ```
48
+
49
+ For process management with Redis:
50
+ ```bash
51
+ npm install ioredis
52
+ ```
53
+
54
+ ## 🚀 Quick Start
55
+
56
+ ### Creating a Simple Weather Service (Node.js)
57
+
58
+ ```typescript
59
+ import { defineDAINService } from '@dainprotocol/service-sdk';
60
+ import { z } from 'zod';
61
+
62
+ // Define a tool
63
+ const getWeatherTool = {
64
+ id: 'get-weather',
65
+ name: 'Get Weather',
66
+ description: 'Fetches current weather for a city',
67
+ input: z.object({
68
+ city: z.string().describe('The name of the city'),
69
+ }),
70
+ output: z.object({
71
+ temperature: z.number().describe('Temperature in Celsius'),
72
+ condition: z.string().describe('Weather condition'),
73
+ }),
74
+ pricing: { pricePerUse: 0.01, currency: 'USD' },
75
+ handler: async ({ city }, agentInfo) => {
76
+ // Your logic here
77
+ return {
78
+ text: `The weather in ${city} is 22°C and Sunny`,
79
+ data: { temperature: 22, condition: 'Sunny' },
80
+ ui: undefined,
81
+ };
82
+ },
83
+ };
84
+
85
+ // Create and start the service
86
+ const service = defineDAINService({
87
+ metadata: {
88
+ title: 'Weather Service',
89
+ description: 'A service for weather information',
90
+ version: '1.0.0',
91
+ author: 'Your Name',
92
+ tags: ['weather'],
93
+ },
94
+ identity: {
95
+ apiKey: process.env.DAIN_API_KEY,
96
+ },
97
+ tools: [getWeatherTool],
98
+ });
99
+
100
+ // Start the service
101
+ service.startNode({ port: 3000 }).then(() => {
102
+ console.log('Weather service running on port 3000');
103
+ });
104
+ ```
105
+
106
+ ### Using the Client SDK
107
+
108
+ ```typescript
109
+ import { DainServiceConnection, DainClientAuth } from '@dainprotocol/service-sdk/client';
110
+
111
+ // Initialize authentication
112
+ const auth = new DainClientAuth({
113
+ apiKey: process.env.DAIN_API_KEY,
114
+ });
115
+
116
+ // Connect to a service
117
+ const service = new DainServiceConnection('http://localhost:3000', auth);
118
+
119
+ // Call a tool
120
+ const result = await service.callTool('get-weather', {
121
+ city: 'San Francisco',
122
+ });
123
+
124
+ console.log(result.text); // "The weather in San Francisco is 22°C and Sunny"
125
+ console.log(result.data); // { temperature: 22, condition: 'Sunny' }
126
+ ```
127
+
128
+ ## 🧩 Core Concepts
129
+
130
+ ### Services
131
+
132
+ A **service** is a collection of tools, contexts, datasources, and widgets that provide specific functionality to AI agents.
133
+
134
+ ```typescript
135
+ const service = defineDAINService({
136
+ metadata: { /* ... */ },
137
+ identity: { /* ... */ },
138
+ tools: [ /* ... */ ],
139
+ contexts: [ /* ... */ ],
140
+ datasources: [ /* ... */ ],
141
+ widgets: [ /* ... */ ],
142
+ });
143
+ ```
144
+
145
+ ### Tools
146
+
147
+ **Tools** are functions that AI agents can call. Each tool has:
148
+ - A unique ID
149
+ - Input/output schemas (using Zod)
150
+ - A handler function
151
+ - Optional pricing information
152
+
153
+ ```typescript
154
+ const myTool = {
155
+ id: 'my-tool',
156
+ name: 'My Tool',
157
+ description: 'Does something useful',
158
+ input: z.object({ param: z.string() }),
159
+ output: z.object({ result: z.string() }),
160
+ handler: async (input, agentInfo, context) => {
161
+ return {
162
+ text: 'Human-readable response',
163
+ data: { result: 'structured data' },
164
+ ui: { /* optional UI component */ },
165
+ };
166
+ },
167
+ };
168
+ ```
169
+
170
+ ### Agent Info
171
+
172
+ Every tool handler receives `agentInfo` with details about the calling agent:
173
+
174
+ ```typescript
175
+ interface AgentInfo {
176
+ agentId: string; // Unique agent identifier
177
+ address: string; // Agent's blockchain address
178
+ smartAccountPDA?: string;
179
+ id: string;
180
+ webhookUrl?: string; // For sending async updates
181
+ }
182
+ ```
183
+
184
+ ### Tool Context
185
+
186
+ The `context` parameter provides access to:
187
+ - The Hono app instance
188
+ - OAuth2 client for authenticated API calls
189
+ - Custom extra data
190
+ - UI update functions
191
+ - Process management
192
+
193
+ ```typescript
194
+ interface ToolContext {
195
+ app: Hono;
196
+ oauth2Client?: OAuth2Client;
197
+ extraData?: any;
198
+ updateUI?: (update: { ui: any }) => Promise<void>;
199
+ addProcess?: (processId: string) => Promise<void>;
200
+ }
201
+ ```
202
+
203
+ ## 🌐 Multi-Runtime Support
204
+
205
+ The SDK automatically adapts to your runtime environment through conditional exports.
206
+
207
+ ### Node.js
208
+
209
+ ```typescript
210
+ import { defineDAINService } from '@dainprotocol/service-sdk';
211
+
212
+ const service = defineDAINService({ /* config */ });
213
+ await service.startNode({ port: 3000 });
214
+ ```
215
+
216
+ ### Deno
217
+
218
+ ```typescript
219
+ import { defineDAINService } from '@dainprotocol/service-sdk';
220
+
221
+ const service = defineDAINService({ /* config */ });
222
+ await service.startDeno({ port: 3000 });
223
+ ```
224
+
225
+ ### Cloudflare Workers
226
+
227
+ ```typescript
228
+ import { defineDAINService } from '@dainprotocol/service-sdk';
229
+
230
+ const service = defineDAINService({ /* config */ });
231
+
232
+ export default {
233
+ fetch: service.startWorkers(),
234
+ };
235
+ ```
236
+
237
+ ### Next.js (App Router)
238
+
239
+ ```typescript
240
+ // app/api/dain/[...dain]/route.ts
241
+ import { createNextDainService } from '@dainprotocol/service-sdk/next';
242
+
243
+ const { GET, POST } = createNextDainService({
244
+ metadata: { /* ... */ },
245
+ identity: { /* ... */ },
246
+ tools: [ /* ... */ ],
247
+ });
248
+
249
+ export { GET, POST };
250
+ ```
251
+
252
+ ## 🔧 Tools
253
+
254
+ ### Creating Tools
255
+
256
+ Use the `createTool` helper for better type inference:
257
+
258
+ ```typescript
259
+ import { createTool } from '@dainprotocol/service-sdk';
260
+
261
+ const weatherTool = createTool({
262
+ id: 'get-weather',
263
+ name: 'Get Weather',
264
+ description: 'Get current weather',
265
+ input: z.object({
266
+ lat: z.number(),
267
+ lon: z.number(),
268
+ }),
269
+ output: z.object({
270
+ temp: z.number(),
271
+ description: z.string(),
272
+ }),
273
+ handler: async ({ lat, lon }) => {
274
+ const response = await fetch(
275
+ `https://api.openweather.org/data/2.5/weather?lat=${lat}&lon=${lon}`
276
+ );
277
+ const data = await response.json();
278
+
279
+ return {
280
+ text: `Temperature: ${data.main.temp}°C`,
281
+ data: {
282
+ temp: data.main.temp,
283
+ description: data.weather[0].description,
284
+ },
285
+ ui: undefined,
286
+ };
287
+ },
288
+ });
289
+ ```
290
+
291
+ ### Tool Interfaces
292
+
293
+ Tools can implement standardized interfaces for better interoperability:
294
+
295
+ ```typescript
296
+ import { createToolWithInterface, ToolInterfaceType } from '@dainprotocol/service-sdk';
297
+
298
+ const searchTool = createToolWithInterface({
299
+ id: 'search-docs',
300
+ name: 'Search Documentation',
301
+ description: 'Search through documentation',
302
+ interface: ToolInterfaceType.KNOWLEDGE_SEARCH,
303
+ input: z.object({
304
+ query: z.string(),
305
+ }),
306
+ output: z.object({
307
+ results: z.array(z.object({
308
+ title: z.string(),
309
+ content: z.string(),
310
+ citations: z.array(CitationSchema),
311
+ })),
312
+ }),
313
+ handler: async ({ query }) => {
314
+ // Implementation
315
+ },
316
+ });
317
+ ```
318
+
319
+ ### Tool Pricing
320
+
321
+ Add pricing to monetize your tools:
322
+
323
+ ```typescript
324
+ const paidTool = {
325
+ id: 'premium-analysis',
326
+ name: 'Premium Analysis',
327
+ description: 'Advanced data analysis',
328
+ pricing: {
329
+ pricePerUse: 0.50, // $0.50 per use
330
+ currency: 'USD',
331
+ },
332
+ // ... rest of tool config
333
+ };
334
+ ```
335
+
336
+ ### Toolboxes
337
+
338
+ Group related tools into toolboxes:
339
+
340
+ ```typescript
341
+ import { createToolbox } from '@dainprotocol/service-sdk';
342
+
343
+ const weatherToolbox = createToolbox({
344
+ id: 'weather-toolbox',
345
+ name: 'Weather Tools',
346
+ description: 'Complete weather information toolkit',
347
+ tools: ['get-weather', 'get-forecast', 'get-alerts'],
348
+ metadata: {
349
+ complexity: 'simple',
350
+ applicableFields: ['weather', 'climate'],
351
+ },
352
+ recommendedPrompt: 'Use these tools to get comprehensive weather information',
353
+ });
354
+ ```
355
+
356
+ ## 🔐 OAuth2 Integration
357
+
358
+ The SDK includes comprehensive OAuth2 support with automatic token management and refresh.
359
+
360
+ ### Basic OAuth2 Setup
361
+
362
+ ```typescript
363
+ import { defineDAINService } from '@dainprotocol/service-sdk';
364
+ import { InMemoryStorageAdapter } from '@dainprotocol/oauth2-token-manager';
365
+
366
+ const service = defineDAINService({
367
+ metadata: { /* ... */ },
368
+ identity: { /* ... */ },
369
+ tools: [ /* ... */ ],
370
+ oauth2: {
371
+ baseUrl: 'https://your-service.com',
372
+ tokenStore: new InMemoryStorageAdapter(),
373
+ providers: {
374
+ github: {
375
+ clientId: process.env.GITHUB_CLIENT_ID!,
376
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
377
+ authorizationUrl: 'https://github.com/login/oauth/authorize',
378
+ tokenUrl: 'https://github.com/login/oauth/access_token',
379
+ scopes: ['user', 'repo'],
380
+ reason: 'Access your GitHub repositories',
381
+ requiredTools: ['get-repos', 'create-gist'],
382
+ },
383
+ },
384
+ },
385
+ });
386
+ ```
387
+
388
+ ### Persistent Storage (PostgreSQL)
389
+
390
+ ```typescript
391
+ import { DrizzleStorageAdapter } from '@dainprotocol/oauth2-storage-drizzle';
392
+ import { drizzle } from 'drizzle-orm/postgres-js';
393
+ import postgres from 'postgres';
394
+
395
+ const sql = postgres(process.env.DATABASE_URL!);
396
+ const db = drizzle(sql);
397
+
398
+ const service = defineDAINService({
399
+ oauth2: {
400
+ baseUrl: 'https://your-service.com',
401
+ tokenStore: new DrizzleStorageAdapter(db, { dialect: 'postgres' }),
402
+ providers: { /* ... */ },
403
+ },
404
+ // ... rest of config
405
+ });
406
+ ```
407
+
408
+ ### Custom Token Paths
409
+
410
+ For OAuth2 providers with non-standard response formats (like Slack):
411
+
412
+ ```typescript
413
+ const service = defineDAINService({
414
+ oauth2: {
415
+ baseUrl: 'https://your-service.com',
416
+ providers: {
417
+ slack: {
418
+ clientId: process.env.SLACK_CLIENT_ID!,
419
+ clientSecret: process.env.SLACK_CLIENT_SECRET!,
420
+ authorizationUrl: 'https://slack.com/oauth/v2/authorize',
421
+ tokenUrl: 'https://slack.com/api/oauth.v2.access',
422
+ scopes: ['chat:write', 'channels:read'],
423
+ // Custom paths for extracting tokens from response
424
+ tokenPaths: {
425
+ accessToken: 'authed_user.access_token',
426
+ tokenType: 'token_type',
427
+ scope: 'scope',
428
+ },
429
+ },
430
+ },
431
+ },
432
+ // ... rest of config
433
+ });
434
+ ```
435
+
436
+ ### Using OAuth2 in Tools
437
+
438
+ ```typescript
439
+ import { createOAuth2Tool } from '@dainprotocol/service-sdk';
440
+
441
+ const getGitHubProfileTool = createOAuth2Tool({
442
+ id: 'get-github-profile',
443
+ name: 'Get GitHub Profile',
444
+ description: 'Get the authenticated user GitHub profile',
445
+ provider: 'github',
446
+ input: z.object({}),
447
+ output: z.object({
448
+ login: z.string(),
449
+ name: z.string(),
450
+ email: z.string().nullable(),
451
+ bio: z.string().nullable(),
452
+ }),
453
+ handler: async (input, agentInfo, context) => {
454
+ const accessToken = await context.oauth2Client!.getAccessToken(
455
+ 'github',
456
+ `${agentInfo.agentId}@dain.local`
457
+ );
458
+
459
+ const response = await fetch('https://api.github.com/user', {
460
+ headers: {
461
+ Authorization: `Bearer ${accessToken}`,
462
+ Accept: 'application/vnd.github.v3+json',
463
+ },
464
+ });
465
+
466
+ const data = await response.json();
467
+
468
+ return {
469
+ text: `GitHub Profile: ${data.login}`,
470
+ data: {
471
+ login: data.login,
472
+ name: data.name,
473
+ email: data.email,
474
+ bio: data.bio,
475
+ },
476
+ ui: undefined,
477
+ };
478
+ },
479
+ });
480
+ ```
481
+
482
+ ### OAuth2 Provider Options
483
+
484
+ ```typescript
485
+ interface OAuth2ProviderConfig {
486
+ clientId: string;
487
+ clientSecret: string;
488
+ authorizationUrl: string;
489
+ tokenUrl: string;
490
+ scopes: string[];
491
+ usePKCE?: boolean; // Use PKCE flow (recommended for public clients)
492
+ useBasicAuth?: boolean; // Use Basic Auth for token exchange
493
+ reason?: string; // Explain why OAuth is needed (shown to users)
494
+ requiredTools?: string[]; // Tools that require this OAuth connection
495
+ extraAuthParams?: Record<string, string>; // Additional auth parameters
496
+ responseRootKey?: string; // Root key for token response
497
+ tokenPaths?: { // Custom token extraction paths
498
+ accessToken?: string | string[];
499
+ refreshToken?: string | string[];
500
+ expiresIn?: string | string[];
501
+ tokenType?: string | string[];
502
+ scope?: string | string[];
503
+ };
504
+ onSuccess?: (agentId: string, tokens: OAuth2Tokens) => Promise<void>;
505
+ }
506
+ ```
507
+
508
+ ## 🔌 Plugins
509
+
510
+ Plugins extend functionality on both the service and client side.
511
+
512
+ ### Built-in Plugins
513
+
514
+ #### Crypto Plugin
515
+
516
+ ```typescript
517
+ import { CryptoPlugin } from '@dainprotocol/service-sdk/plugins';
518
+
519
+ const cryptoPlugin = new CryptoPlugin();
520
+
521
+ const service = defineDAINService({
522
+ plugins: [cryptoPlugin],
523
+ // ... rest of config
524
+ });
525
+ ```
526
+
527
+ Provides:
528
+ - Message signing and verification
529
+ - Encryption/decryption
530
+ - Hash generation
531
+ - Key management
532
+
533
+ #### Citations Plugin
534
+
535
+ ```typescript
536
+ import { CitationsPlugin } from '@dainprotocol/service-sdk/plugins';
537
+
538
+ const citationsPlugin = new CitationsPlugin({
539
+ namespace: 'my-service',
540
+ });
541
+
542
+ const service = defineDAINService({
543
+ plugins: [citationsPlugin],
544
+ // ... rest of config
545
+ });
546
+ ```
547
+
548
+ Provides:
549
+ - Citation tracking
550
+ - Source verification
551
+ - Citation formatting
552
+
553
+ #### Time Plugin
554
+
555
+ ```typescript
556
+ import { TimePlugin } from '@dainprotocol/service-sdk/plugins';
557
+
558
+ const timePlugin = new TimePlugin();
559
+
560
+ const service = defineDAINService({
561
+ plugins: [timePlugin],
562
+ // ... rest of config
563
+ });
564
+ ```
565
+
566
+ Provides:
567
+ - Timezone conversion
568
+ - Timestamp validation
569
+ - Time-based operations
570
+
571
+ ### Creating Custom Plugins
572
+
573
+ ```typescript
574
+ import { DainPlugin } from '@dainprotocol/service-sdk';
575
+
576
+ class MyCustomPlugin implements DainPlugin {
577
+ id = 'my-custom-plugin';
578
+
579
+ async initialize(service: DAINService): Promise<void> {
580
+ console.log('Plugin initialized');
581
+ }
582
+
583
+ async processInputClient?(input: any): Promise<any> {
584
+ // Process input on the client side
585
+ return { myData: 'client-processed' };
586
+ }
587
+
588
+ async processInputService?(input: any, agentInfo: AgentInfo): Promise<any> {
589
+ // Process input on the service side
590
+ return { myData: 'service-processed' };
591
+ }
592
+
593
+ async processOutputService?(output: any, agentInfo: AgentInfo): Promise<any> {
594
+ // Process output before sending to client
595
+ return output;
596
+ }
597
+ }
598
+
599
+ // Use it
600
+ const service = defineDAINService({
601
+ plugins: [new MyCustomPlugin()],
602
+ // ... rest of config
603
+ });
604
+ ```
605
+
606
+ ## 📡 Client SDK
607
+
608
+ ### Connecting to a Service
609
+
610
+ ```typescript
611
+ import { DainServiceConnection, DainClientAuth } from '@dainprotocol/service-sdk/client';
612
+
613
+ const auth = new DainClientAuth({
614
+ apiKey: process.env.DAIN_API_KEY,
615
+ });
616
+
617
+ const service = new DainServiceConnection('http://localhost:3000', auth, {
618
+ plugins: [/* optional plugins */],
619
+ });
620
+ ```
621
+
622
+ ### Getting Service Metadata
623
+
624
+ ```typescript
625
+ const metadata = await service.getMetadata();
626
+ console.log(metadata.title);
627
+ console.log(metadata.description);
628
+ console.log(metadata.version);
629
+ ```
630
+
631
+ ### Listing Available Tools
632
+
633
+ ```typescript
634
+ const tools = await service.getTools();
635
+ tools.forEach(tool => {
636
+ console.log(`${tool.name}: ${tool.description}`);
637
+ });
638
+ ```
639
+
640
+ ### Calling Tools
641
+
642
+ ```typescript
643
+ const result = await service.callTool('tool-id', {
644
+ param1: 'value1',
645
+ param2: 'value2',
646
+ });
647
+
648
+ console.log(result.text); // Human-readable response
649
+ console.log(result.data); // Structured data
650
+ console.log(result.ui); // Optional UI component
651
+ ```
652
+
653
+ ### Streaming Responses
654
+
655
+ ```typescript
656
+ const stream = await service.callToolStream('tool-id', {
657
+ query: 'What is the weather?',
658
+ });
659
+
660
+ for await (const chunk of stream) {
661
+ if (chunk.type === 'text-delta') {
662
+ process.stdout.write(chunk.textDelta);
663
+ } else if (chunk.type === 'tool-result') {
664
+ console.log('\nFinal result:', chunk.result);
665
+ }
666
+ }
667
+ ```
668
+
669
+ ### Working with Contexts
670
+
671
+ ```typescript
672
+ const contexts = await service.getContexts();
673
+ const userContext = await service.getContext('user-context', {
674
+ param: 'value',
675
+ });
676
+
677
+ console.log(userContext.data);
678
+ ```
679
+
680
+ ### Working with Datasources
681
+
682
+ ```typescript
683
+ const datasources = await service.getDatasources();
684
+ const data = await service.getDatasource('my-datasource', {
685
+ filter: 'active',
686
+ });
687
+
688
+ console.log(data.data);
689
+ ```
690
+
691
+ ### OAuth2 from Client
692
+
693
+ ```typescript
694
+ // Check available OAuth2 providers
695
+ const providers = await service.getOAuth2Providers();
696
+
697
+ // Initiate OAuth flow
698
+ const authResult = await service.callTool('oauth2-github', {});
699
+ console.log('Auth URL:', authResult.data.authUrl);
700
+
701
+ // After authentication, call OAuth-protected tools
702
+ const profile = await service.callTool('get-github-profile', {});
703
+ ```
704
+
705
+ ### Using with AI SDK
706
+
707
+ ```typescript
708
+ import { generateText } from 'ai';
709
+ import { anthropic } from '@ai-sdk/anthropic';
710
+
711
+ const tools = await service.getToolsAsAISDKTools();
712
+
713
+ const result = await generateText({
714
+ model: anthropic('claude-3-5-sonnet-20241022'),
715
+ prompt: 'What is the weather in San Francisco?',
716
+ tools,
717
+ maxSteps: 10,
718
+ });
719
+
720
+ console.log(result.text);
721
+ ```
722
+
723
+ ## 🚀 Advanced Features
724
+
725
+ ### Contexts
726
+
727
+ Contexts provide dynamic information about the service or user state:
728
+
729
+ ```typescript
730
+ const userContext = {
731
+ id: 'user-preferences',
732
+ name: 'User Preferences',
733
+ description: 'User-specific preferences and settings',
734
+ getContextData: async (agentInfo, extraData) => {
735
+ // Fetch user preferences from database
736
+ return {
737
+ theme: 'dark',
738
+ language: 'en',
739
+ timezone: 'America/Los_Angeles',
740
+ };
741
+ },
742
+ };
743
+
744
+ const service = defineDAINService({
745
+ contexts: [userContext],
746
+ // ... rest of config
747
+ });
748
+ ```
749
+
750
+ ### Datasources
751
+
752
+ Datasources provide queryable data to AI agents:
753
+
754
+ ```typescript
755
+ const productDatasource = {
756
+ id: 'products',
757
+ name: 'Product Catalog',
758
+ description: 'Search and browse products',
759
+ type: 'data' as const,
760
+ input: z.object({
761
+ category: z.string().optional(),
762
+ minPrice: z.number().optional(),
763
+ maxPrice: z.number().optional(),
764
+ }),
765
+ getDatasource: async (agentInfo, params, extraData) => {
766
+ // Query database
767
+ const products = await db.products.findMany({
768
+ where: {
769
+ category: params.category,
770
+ price: {
771
+ gte: params.minPrice,
772
+ lte: params.maxPrice,
773
+ },
774
+ },
775
+ });
776
+
777
+ return products;
778
+ },
779
+ };
780
+
781
+ const service = defineDAINService({
782
+ datasources: [productDatasource],
783
+ // ... rest of config
784
+ });
785
+ ```
786
+
787
+ ### Widgets
788
+
789
+ Widgets display information in the DAIN UI:
790
+
791
+ ```typescript
792
+ const dashboardWidget = {
793
+ id: 'dashboard',
794
+ name: 'Dashboard',
795
+ description: 'Overview dashboard',
796
+ icon: 'dashboard',
797
+ size: 'lg' as const,
798
+ getWidget: async (agentInfo, extraData) => {
799
+ const stats = await getStats(agentInfo.agentId);
800
+
801
+ return {
802
+ text: 'Dashboard overview',
803
+ data: stats,
804
+ ui: {
805
+ type: 'dashboard',
806
+ children: [
807
+ { type: 'stat', label: 'Total Users', value: stats.users },
808
+ { type: 'stat', label: 'Active Sessions', value: stats.sessions },
809
+ ],
810
+ },
811
+ };
812
+ },
813
+ };
814
+
815
+ const service = defineDAINService({
816
+ widgets: [dashboardWidget],
817
+ // ... rest of config
818
+ });
819
+ ```
820
+
821
+ ### Long-Running Processes
822
+
823
+ For operations that take time to complete:
824
+
825
+ ```typescript
826
+ import { MemoryProcessStore } from '@dainprotocol/service-sdk';
827
+
828
+ const service = defineDAINService({
829
+ processStore: new MemoryProcessStore(),
830
+ // ... rest of config
831
+ });
832
+
833
+ // In a tool handler
834
+ const processTool = {
835
+ id: 'start-analysis',
836
+ name: 'Start Analysis',
837
+ description: 'Start a long-running analysis',
838
+ handler: async (input, agentInfo, context) => {
839
+ const processId = generateId();
840
+
841
+ // Start background process
842
+ startAnalysisProcess(processId, input);
843
+
844
+ return {
845
+ text: 'Analysis started',
846
+ data: { processId },
847
+ ui: undefined,
848
+ processes: [{
849
+ id: processId,
850
+ name: 'Data Analysis',
851
+ description: 'Analyzing your data...',
852
+ type: 'one-time',
853
+ }],
854
+ };
855
+ },
856
+ };
857
+ ```
858
+
859
+ ### Human-in-the-Loop Actions
860
+
861
+ Request human approval or input during tool execution:
862
+
863
+ ```typescript
864
+ const service = defineDAINService({
865
+ onHumanActionResponse: async ({ process, stepId, actionId, responseText, data }) => {
866
+ console.log('Human responded:', responseText);
867
+ // Continue the process based on human response
868
+ },
869
+ // ... rest of config
870
+ });
871
+ ```
872
+
873
+ ### Custom Routes
874
+
875
+ Add custom endpoints to your service:
876
+
877
+ ```typescript
878
+ const service = defineDAINService({
879
+ routes: (app) => {
880
+ app.get('/health', (c) => {
881
+ return c.json({ status: 'healthy' });
882
+ });
883
+
884
+ app.post('/webhook', async (c) => {
885
+ const body = await c.req.json();
886
+ // Handle webhook
887
+ return c.json({ received: true });
888
+ });
889
+ },
890
+ // ... rest of config
891
+ });
892
+ ```
893
+
894
+ ### Payment Integration
895
+
896
+ ```typescript
897
+ import { createStripePaymentIntent } from '@dainprotocol/service-sdk/payments';
898
+
899
+ const paidTool = {
900
+ id: 'premium-feature',
901
+ name: 'Premium Feature',
902
+ description: 'A premium paid feature',
903
+ pricing: { pricePerUse: 5.00, currency: 'USD' },
904
+ handler: async (input, agentInfo) => {
905
+ // Request payment
906
+ const paymentIntent = await createStripePaymentIntent({
907
+ amount: 500, // $5.00 in cents
908
+ currency: 'usd',
909
+ metadata: {
910
+ agentId: agentInfo.agentId,
911
+ toolId: 'premium-feature',
912
+ },
913
+ });
914
+
915
+ return {
916
+ text: 'Please complete payment to continue',
917
+ data: {},
918
+ ui: undefined,
919
+ pleasePay: paymentIntent,
920
+ };
921
+ },
922
+ };
923
+ ```
924
+
925
+ ## 📚 API Reference
926
+
927
+ ### Service Configuration
928
+
929
+ ```typescript
930
+ interface DAINServiceConfig {
931
+ metadata: {
932
+ title: string;
933
+ description: string;
934
+ version: string;
935
+ author: string;
936
+ tags: string[];
937
+ logo?: string;
938
+ };
939
+ identity: {
940
+ apiKey?: string;
941
+ publicKey?: string;
942
+ privateKey?: string;
943
+ agentId?: string;
944
+ orgId?: string;
945
+ };
946
+ tools: ToolConfig[];
947
+ toolboxes?: ToolboxConfig[];
948
+ services?: ServiceConfig[];
949
+ contexts?: ServiceContext[];
950
+ datasources?: ServiceDatasource[];
951
+ widgets?: ServiceWidget[];
952
+ agents?: ServiceAgent[];
953
+ plugins?: DainPlugin[];
954
+ oauth2?: {
955
+ baseUrl: string;
956
+ tokenStore?: StorageAdapter;
957
+ providers: Record<string, OAuth2ProviderConfig>;
958
+ };
959
+ processStore?: ProcessStoreAdapter;
960
+ routes?: (app: Hono) => void;
961
+ serverExtensions?: NodeServerExtension[];
962
+ baseUrl?: string;
963
+ exampleQueries?: {
964
+ category: string;
965
+ queries: string[];
966
+ }[];
967
+ getUserWidgets?: (agentInfo: AgentInfo, extraData?: any) => Promise<string[]>;
968
+ homeUI?: string | ((agentInfo: AgentInfo, extraData?: any) => Promise<string>);
969
+ onHumanActionResponse?: (response: {
970
+ process: Process;
971
+ stepId: string;
972
+ actionId: string;
973
+ responseText?: string;
974
+ data?: any;
975
+ }) => Promise<void>;
976
+ }
977
+ ```
978
+
979
+ ### Tool Configuration
980
+
981
+ ```typescript
982
+ interface ToolConfig<TInput extends z.ZodType, TOutput extends z.ZodType> {
983
+ id: string;
984
+ name: string;
985
+ description: string;
986
+ input: TInput;
987
+ output: TOutput;
988
+ pricing?: {
989
+ pricePerUse: number;
990
+ currency: string;
991
+ };
992
+ interface?: string;
993
+ suggestConfirmation?: boolean;
994
+ suggestConfirmationUI?: (input: z.input<TInput>) => Promise<{
995
+ success?: boolean;
996
+ ui?: any;
997
+ }>;
998
+ handler: (
999
+ input: z.input<TInput>,
1000
+ agentInfo: AgentInfo,
1001
+ context: ToolContext
1002
+ ) => Promise<{
1003
+ text: string;
1004
+ data: z.output<TOutput>;
1005
+ ui: any | undefined;
1006
+ pleasePay?: PaymentIntent;
1007
+ processes?: string[] | ProcessInfo[];
1008
+ }>;
1009
+ handleInputError?: (
1010
+ error: ZodError,
1011
+ agentInfo: AgentInfo,
1012
+ extraData?: any
1013
+ ) => Promise<ToolResponse>;
1014
+ }
1015
+ ```
1016
+
1017
+ ### Runtime Methods
1018
+
1019
+ ```typescript
1020
+ // Node.js
1021
+ await service.startNode({ port?: number });
1022
+
1023
+ // Deno
1024
+ await service.startDeno({ port?: number });
1025
+
1026
+ // Cloudflare Workers
1027
+ export default { fetch: service.startWorkers() };
1028
+
1029
+ // Next.js
1030
+ const { GET, POST } = createNextDainService(config);
1031
+ ```
1032
+
1033
+ ## 📖 Examples
1034
+
1035
+ ### Complete Weather Service
1036
+
1037
+ See [example/simpleWeatherService-node.ts](./example/simpleWeatherService-node.ts)
1038
+
1039
+ ### OAuth2 Integration
1040
+
1041
+ See [example/oauth-client-example.ts](./example/oauth-client-example.ts)
1042
+
1043
+ ### Long-Running Processes
1044
+
1045
+ See [example/processService.ts](./example/processService.ts)
1046
+
1047
+ ### Using Plugins
1048
+
1049
+ See [example/crypto-plugin-usage.ts](./example/crypto-plugin-usage.ts)
1050
+
1051
+ ### Next.js Integration
1052
+
1053
+ See [examples/nextjs-app-router](./examples/nextjs-app-router)
1054
+
1055
+ ## 🔄 Migration Guide
1056
+
1057
+ If you're upgrading from an older version, see [OAUTH_MIGRATION.md](./OAUTH_MIGRATION.md) for OAuth2 token store migration instructions.
1058
+
1059
+ ## 🤝 Contributing
1060
+
1061
+ Contributions are welcome! Please feel free to submit a Pull Request.
1062
+
1063
+ ## 📄 License
1064
+
1065
+ ISC License - see LICENSE file for details
1066
+
1067
+ ## 🔗 Links
1068
+
1069
+ - [DAIN Protocol Documentation](https://docs.dainprotocol.ai)
1070
+ - [OAuth2 Token Manager](https://github.com/dainprotocol/oauth2-token-manager)
1071
+ - [Example Services](./example)
1072
+ - [NPM Package](https://www.npmjs.com/package/@dainprotocol/service-sdk)
1073
+
1074
+ ## 💬 Support
1075
+
1076
+ - GitHub Issues: [Report a bug or request a feature](https://github.com/dainprotocol/service-sdk/issues)
1077
+ - Discord: [Join our community](https://discord.gg/dainprotocol)
1078
+ - Twitter: [@dainprotocol](https://twitter.com/dainprotocol)
1079
+
1080
+ ---
1081
+
1082
+ Built with ❤️ by the DAIN Protocol team