@kadi.build/deploy-ability 0.0.3 → 0.0.5
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/targets/akash/bids.d.ts +183 -0
- package/dist/targets/akash/bids.d.ts.map +1 -0
- package/dist/targets/akash/bids.js +247 -0
- package/dist/targets/akash/bids.js.map +1 -0
- package/dist/targets/akash/certificate-manager.d.ts +89 -167
- package/dist/targets/akash/certificate-manager.d.ts.map +1 -1
- package/dist/targets/akash/certificate-manager.js +193 -301
- package/dist/targets/akash/certificate-manager.js.map +1 -1
- package/dist/targets/akash/client.d.ts +644 -0
- package/dist/targets/akash/client.d.ts.map +1 -0
- package/dist/targets/akash/client.js +972 -0
- package/dist/targets/akash/client.js.map +1 -0
- package/dist/targets/akash/constants.d.ts +12 -149
- package/dist/targets/akash/constants.d.ts.map +1 -1
- package/dist/targets/akash/constants.js +14 -136
- package/dist/targets/akash/constants.js.map +1 -1
- package/dist/targets/akash/deployer.d.ts +3 -82
- package/dist/targets/akash/deployer.d.ts.map +1 -1
- package/dist/targets/akash/deployer.js +122 -160
- package/dist/targets/akash/deployer.js.map +1 -1
- package/dist/targets/akash/environment.d.ts +16 -214
- package/dist/targets/akash/environment.d.ts.map +1 -1
- package/dist/targets/akash/environment.js +20 -210
- package/dist/targets/akash/environment.js.map +1 -1
- package/dist/targets/akash/index.d.ts +95 -189
- package/dist/targets/akash/index.d.ts.map +1 -1
- package/dist/targets/akash/index.js +69 -197
- package/dist/targets/akash/index.js.map +1 -1
- package/dist/targets/akash/lease-monitor.d.ts +3 -21
- package/dist/targets/akash/lease-monitor.d.ts.map +1 -1
- package/dist/targets/akash/lease-monitor.js +39 -56
- package/dist/targets/akash/lease-monitor.js.map +1 -1
- package/dist/targets/akash/logs.d.ts +103 -4
- package/dist/targets/akash/logs.d.ts.map +1 -1
- package/dist/targets/akash/logs.js +12 -3
- package/dist/targets/akash/logs.js.map +1 -1
- package/dist/targets/akash/pricing.d.ts +12 -191
- package/dist/targets/akash/pricing.d.ts.map +1 -1
- package/dist/targets/akash/pricing.js +12 -188
- package/dist/targets/akash/pricing.js.map +1 -1
- package/dist/targets/akash/provider-manager.d.ts +120 -0
- package/dist/targets/akash/provider-manager.d.ts.map +1 -0
- package/dist/targets/akash/provider-manager.js +574 -0
- package/dist/targets/akash/provider-manager.js.map +1 -0
- package/dist/targets/akash/sdl-generator.d.ts +2 -2
- package/dist/targets/akash/sdl-generator.d.ts.map +1 -1
- package/dist/targets/akash/sdl-generator.js +6 -39
- package/dist/targets/akash/sdl-generator.js.map +1 -1
- package/dist/targets/akash/types.d.ts +66 -243
- package/dist/targets/akash/types.d.ts.map +1 -1
- package/dist/targets/akash/types.js +4 -41
- package/dist/targets/akash/types.js.map +1 -1
- package/dist/targets/akash/wallet-manager.d.ts +35 -352
- package/dist/targets/akash/wallet-manager.d.ts.map +1 -1
- package/dist/targets/akash/wallet-manager.js +37 -439
- package/dist/targets/akash/wallet-manager.js.map +1 -1
- package/dist/targets/local/compose-generator.d.ts.map +1 -1
- package/dist/targets/local/compose-generator.js +1 -0
- package/dist/targets/local/compose-generator.js.map +1 -1
- package/dist/targets/local/deployer.js +4 -4
- package/dist/targets/local/deployer.js.map +1 -1
- package/dist/targets/local/types.d.ts +4 -0
- package/dist/targets/local/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/options.d.ts +45 -4
- package/dist/types/options.d.ts.map +1 -1
- package/dist/utils/registry/manager.js +6 -6
- package/dist/utils/registry/manager.js.map +1 -1
- package/dist/utils/registry/setup.js +4 -4
- package/dist/utils/registry/setup.js.map +1 -1
- package/docs/KADI_ABILITY_CONVERSION.md +1365 -0
- package/docs/PIPELINE_BUILDER_DESIGN.md +1149 -0
- package/package.json +8 -11
- package/dist/targets/akash/bid-selectors.d.ts +0 -251
- package/dist/targets/akash/bid-selectors.d.ts.map +0 -1
- package/dist/targets/akash/bid-selectors.js +0 -322
- package/dist/targets/akash/bid-selectors.js.map +0 -1
- package/dist/targets/akash/bid-types.d.ts +0 -297
- package/dist/targets/akash/bid-types.d.ts.map +0 -1
- package/dist/targets/akash/bid-types.js +0 -89
- package/dist/targets/akash/bid-types.js.map +0 -1
- package/dist/targets/akash/blockchain-client.d.ts +0 -577
- package/dist/targets/akash/blockchain-client.d.ts.map +0 -1
- package/dist/targets/akash/blockchain-client.js +0 -803
- package/dist/targets/akash/blockchain-client.js.map +0 -1
- package/dist/targets/akash/logs.types.d.ts +0 -102
- package/dist/targets/akash/logs.types.d.ts.map +0 -1
- package/dist/targets/akash/logs.types.js +0 -9
- package/dist/targets/akash/logs.types.js.map +0 -1
- package/dist/targets/akash/provider-client.d.ts +0 -114
- package/dist/targets/akash/provider-client.d.ts.map +0 -1
- package/dist/targets/akash/provider-client.js +0 -318
- package/dist/targets/akash/provider-client.js.map +0 -1
- package/dist/targets/akash/provider-metadata.d.ts +0 -228
- package/dist/targets/akash/provider-metadata.d.ts.map +0 -1
- package/dist/targets/akash/provider-metadata.js +0 -14
- package/dist/targets/akash/provider-metadata.js.map +0 -1
- package/dist/targets/akash/provider-service.d.ts +0 -133
- package/dist/targets/akash/provider-service.d.ts.map +0 -1
- package/dist/targets/akash/provider-service.js +0 -391
- package/dist/targets/akash/provider-service.js.map +0 -1
- package/dist/targets/akash/query-client.d.ts +0 -125
- package/dist/targets/akash/query-client.d.ts.map +0 -1
- package/dist/targets/akash/query-client.js +0 -332
- package/dist/targets/akash/query-client.js.map +0 -1
- package/docs/EXAMPLES.md +0 -293
- package/docs/PLACEMENT.md +0 -433
- package/docs/STORAGE.md +0 -318
|
@@ -0,0 +1,1365 @@
|
|
|
1
|
+
# KADI Ability Conversion - Preserving Fluent APIs
|
|
2
|
+
|
|
3
|
+
> **Document Purpose**: Guide for converting deploy-ability into a KADI ability while preserving the fluent API design
|
|
4
|
+
>
|
|
5
|
+
> **Author**: KADI Team
|
|
6
|
+
>
|
|
7
|
+
> **Date**: 2025-11-18
|
|
8
|
+
>
|
|
9
|
+
> **Status**: Design Proposal
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Executive Summary
|
|
14
|
+
|
|
15
|
+
### The Question
|
|
16
|
+
|
|
17
|
+
**Can we turn deploy-ability into a KADI ability while keeping the Pipeline Builder's fluent API?**
|
|
18
|
+
|
|
19
|
+
### The Answer
|
|
20
|
+
|
|
21
|
+
**Yes, absolutely!** Using the **dual export pattern** from secret-ability, you can have:
|
|
22
|
+
|
|
23
|
+
- ✅ **Fluent API** for local/CLI use (best developer experience)
|
|
24
|
+
- ✅ **Tool-based RPC** for distributed/remote use (best for agents)
|
|
25
|
+
- ✅ **Both interfaces** maintained in the same package
|
|
26
|
+
- ✅ **No breaking changes** to existing code
|
|
27
|
+
|
|
28
|
+
### Key Insight
|
|
29
|
+
|
|
30
|
+
**Fluent APIs and KADI abilities are NOT mutually exclusive.** They serve different use cases:
|
|
31
|
+
|
|
32
|
+
| Use Case | Interface | Why |
|
|
33
|
+
|----------|-----------|-----|
|
|
34
|
+
| Local CLI commands | Fluent API | Best DX, discoverable, type-safe |
|
|
35
|
+
| Agent-to-agent calls | Tool-based RPC | Stateless, distributed, simple |
|
|
36
|
+
| Scripts/automation | Fluent API | Flexible, composable, clear |
|
|
37
|
+
| Broker/MCP integration | Tool-based RPC | Standard protocol, interoperable |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## The Dual Export Pattern
|
|
42
|
+
|
|
43
|
+
### How secret-ability Does It
|
|
44
|
+
|
|
45
|
+
secret-ability successfully implements this pattern:
|
|
46
|
+
|
|
47
|
+
**package.json:**
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"name": "secret-ability",
|
|
51
|
+
"main": "./dist/kadi-ability.js",
|
|
52
|
+
"exports": {
|
|
53
|
+
".": {
|
|
54
|
+
"types": "./dist/kadi-ability.d.ts",
|
|
55
|
+
"import": "./dist/kadi-ability.js"
|
|
56
|
+
},
|
|
57
|
+
"./lib": {
|
|
58
|
+
"types": "./dist/index.d.ts",
|
|
59
|
+
"import": "./dist/index.js"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**File structure:**
|
|
66
|
+
```
|
|
67
|
+
secret-ability/
|
|
68
|
+
├── src/
|
|
69
|
+
│ ├── index.ts # Library export - Fluent API (Secrets class)
|
|
70
|
+
│ ├── kadi-ability.ts # Ability export - Tool-based RPC
|
|
71
|
+
│ ├── core/
|
|
72
|
+
│ │ └── secrets.ts # The Secrets class with fluent initializer
|
|
73
|
+
│ └── ...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Two Interfaces, One Codebase
|
|
77
|
+
|
|
78
|
+
**Library Interface (`/lib`):**
|
|
79
|
+
```typescript
|
|
80
|
+
import { Secrets } from '@kadi.build/secret-ability/lib';
|
|
81
|
+
|
|
82
|
+
const secrets = new Secrets();
|
|
83
|
+
await secrets.init()
|
|
84
|
+
.ensure('wallet', { type: 'custom', path: './wallet.vault' })
|
|
85
|
+
.use('wallet');
|
|
86
|
+
|
|
87
|
+
await secrets.set('MNEMONIC', 'word1 word2 ...');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Ability Interface (default):**
|
|
91
|
+
```typescript
|
|
92
|
+
const secrets = await client.load('secret-ability', 'native');
|
|
93
|
+
|
|
94
|
+
await secrets.init({ vaults: [{ name: 'wallet', type: 'custom', path: './wallet.vault' }] });
|
|
95
|
+
await secrets.useVault({ vaultName: 'wallet' });
|
|
96
|
+
await secrets.set({ key: 'MNEMONIC', value: 'word1 word2 ...' });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Same functionality, different interfaces for different use cases!
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Current State: deploy-ability
|
|
104
|
+
|
|
105
|
+
### Current Architecture
|
|
106
|
+
|
|
107
|
+
deploy-ability is currently a **pure library** - not yet a KADI ability.
|
|
108
|
+
|
|
109
|
+
**How it's used today:**
|
|
110
|
+
```typescript
|
|
111
|
+
import { setupRegistryIfNeeded, loadProfile, deploy } from '@kadi.build/deploy-ability';
|
|
112
|
+
|
|
113
|
+
// Manual setup and cleanup
|
|
114
|
+
const registryManager = new DockerRegistryManager();
|
|
115
|
+
await registryManager.startTemporaryRegistry();
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const profile = await loadProfile('./agent.json', 'production');
|
|
119
|
+
await deploy(profile, wallet, certificate);
|
|
120
|
+
} finally {
|
|
121
|
+
await registryManager.cleanup(); // Easy to forget!
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### The Pipeline Builder (Proposed)
|
|
126
|
+
|
|
127
|
+
The fluent API design from `PIPELINE_BUILDER_DESIGN.md`:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { DeploymentPipeline } from '@kadi.build/deploy-ability/lib';
|
|
131
|
+
|
|
132
|
+
const result = await DeploymentPipeline
|
|
133
|
+
.create({ projectRoot: './', profile: 'production' })
|
|
134
|
+
.withLocalImageSupport({ tunnelService: 'serveo' })
|
|
135
|
+
.withWallet(wallet)
|
|
136
|
+
.withCertificate(certificate)
|
|
137
|
+
.execute();
|
|
138
|
+
// Cleanup happens automatically! ✨
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**This is excellent design** - we want to preserve it!
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Proposed State: deploy-ability as KADI Ability
|
|
146
|
+
|
|
147
|
+
### Package Structure
|
|
148
|
+
|
|
149
|
+
**Updated package.json:**
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"name": "@kadi.build/deploy-ability",
|
|
153
|
+
"version": "0.0.5",
|
|
154
|
+
"description": "Multi-target deployment orchestration for KADI",
|
|
155
|
+
"type": "module",
|
|
156
|
+
"main": "./dist/kadi-ability.js",
|
|
157
|
+
"types": "./dist/kadi-ability.d.ts",
|
|
158
|
+
"exports": {
|
|
159
|
+
".": {
|
|
160
|
+
"types": "./dist/kadi-ability.d.ts",
|
|
161
|
+
"import": "./dist/kadi-ability.js"
|
|
162
|
+
},
|
|
163
|
+
"./lib": {
|
|
164
|
+
"types": "./dist/index.d.ts",
|
|
165
|
+
"import": "./dist/index.js"
|
|
166
|
+
},
|
|
167
|
+
"./types": {
|
|
168
|
+
"types": "./dist/types/index.d.ts",
|
|
169
|
+
"import": "./dist/types/index.js"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"dependencies": {
|
|
173
|
+
"@kadi.build/core": "^0.0.1-alpha.15",
|
|
174
|
+
"zod": "^3.22.4"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### File Structure
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
deploy-ability/
|
|
183
|
+
├── src/
|
|
184
|
+
│ ├── index.ts # Library export - FLUENT API ✅
|
|
185
|
+
│ │ # Exports: DeploymentPipeline, types, utilities
|
|
186
|
+
│ │
|
|
187
|
+
│ ├── kadi-ability.ts # Ability export - TOOL-BASED RPC
|
|
188
|
+
│ │ # Registers tools, wraps fluent API
|
|
189
|
+
│ │
|
|
190
|
+
│ ├── pipeline-builder.ts # The beautiful fluent API
|
|
191
|
+
│ │ # DeploymentPipeline class
|
|
192
|
+
│ │
|
|
193
|
+
│ ├── targets/
|
|
194
|
+
│ │ ├── local/
|
|
195
|
+
│ │ │ └── local.ts
|
|
196
|
+
│ │ └── akash/
|
|
197
|
+
│ │ ├── akash.ts
|
|
198
|
+
│ │ └── deployer.ts
|
|
199
|
+
│ │
|
|
200
|
+
│ ├── utils/
|
|
201
|
+
│ │ └── registry/
|
|
202
|
+
│ │ ├── manager.ts
|
|
203
|
+
│ │ └── setup.ts
|
|
204
|
+
│ │
|
|
205
|
+
│ └── types/
|
|
206
|
+
│ └── index.ts
|
|
207
|
+
│
|
|
208
|
+
└── docs/
|
|
209
|
+
├── PIPELINE_BUILDER_DESIGN.md
|
|
210
|
+
└── KADI_ABILITY_CONVERSION.md # This document
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Two Interfaces Working Together
|
|
214
|
+
|
|
215
|
+
The **ability layer** internally uses the **fluent library**:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
┌─────────────────────────────────────────────────────┐
|
|
219
|
+
│ Consumer Code │
|
|
220
|
+
├─────────────────────────────────────────────────────┤
|
|
221
|
+
│ │
|
|
222
|
+
│ Option A: Direct Library Import │
|
|
223
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
224
|
+
│ │ import { DeploymentPipeline } │ │
|
|
225
|
+
│ │ from '@kadi.build/deploy-ability/lib' │ │
|
|
226
|
+
│ │ │ │
|
|
227
|
+
│ │ await DeploymentPipeline.create(...) │ │
|
|
228
|
+
│ └─────────────────────────────────────────┘ │
|
|
229
|
+
│ ↓ │
|
|
230
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
231
|
+
│ │ Pipeline Builder (Fluent API) │ │
|
|
232
|
+
│ └─────────────────────────────────────────┘ │
|
|
233
|
+
│ │
|
|
234
|
+
│ Option B: KADI Ability Loading │
|
|
235
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
236
|
+
│ │ const deploy = await client.load(...) │ │
|
|
237
|
+
│ │ await deploy.deploy({ ... }) │ │
|
|
238
|
+
│ └─────────────────────────────────────────┘ │
|
|
239
|
+
│ ↓ │
|
|
240
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
241
|
+
│ │ Tool-based RPC Interface │ │
|
|
242
|
+
│ │ (internally uses Pipeline Builder) │ │
|
|
243
|
+
│ └─────────────────────────────────────────┘ │
|
|
244
|
+
│ ↓ │
|
|
245
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
246
|
+
│ │ Pipeline Builder (Fluent API) │ │
|
|
247
|
+
│ └─────────────────────────────────────────┘ │
|
|
248
|
+
│ │
|
|
249
|
+
└─────────────────────────────────────────────────────┘
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Code Examples - Before/After
|
|
255
|
+
|
|
256
|
+
### Current Usage (Library Only)
|
|
257
|
+
|
|
258
|
+
**kadi-deploy using deploy-ability today:**
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// kadi-deploy/src/index.ts
|
|
262
|
+
import { loadProfile, setupRegistryIfNeeded } from '@kadi.build/deploy-ability';
|
|
263
|
+
import { deploy as deployToAkash } from '@kadi.build/deploy-ability/targets/akash';
|
|
264
|
+
|
|
265
|
+
export async function deploy(options: DeployOptions) {
|
|
266
|
+
// Load profile
|
|
267
|
+
const profile = await loadProfile(
|
|
268
|
+
options.projectRoot,
|
|
269
|
+
options.profile,
|
|
270
|
+
options
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Setup registry if needed (manual!)
|
|
274
|
+
const registryCleanup = await setupRegistryIfNeeded(profile);
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Deploy
|
|
278
|
+
if (profile.target === 'akash') {
|
|
279
|
+
await deployToAkash(profile, wallet, certificate);
|
|
280
|
+
}
|
|
281
|
+
// ... other targets
|
|
282
|
+
} finally {
|
|
283
|
+
// Cleanup (easy to forget!)
|
|
284
|
+
if (registryCleanup) {
|
|
285
|
+
await registryCleanup();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Problems:**
|
|
292
|
+
- Manual registry management
|
|
293
|
+
- Easy to forget cleanup
|
|
294
|
+
- Verbose error handling
|
|
295
|
+
- Hard to compose operations
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### Proposed Usage (As KADI Ability)
|
|
300
|
+
|
|
301
|
+
#### Option A: Library Import (for local/CLI use)
|
|
302
|
+
|
|
303
|
+
**kadi-deploy using the fluent library API:**
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// kadi-deploy/src/index.ts
|
|
307
|
+
import { DeploymentPipeline } from '@kadi.build/deploy-ability/lib';
|
|
308
|
+
|
|
309
|
+
export async function deploy(options: DeployOptions) {
|
|
310
|
+
// Beautiful fluent API! ✨
|
|
311
|
+
const result = await DeploymentPipeline
|
|
312
|
+
.create({
|
|
313
|
+
projectRoot: options.projectRoot,
|
|
314
|
+
profile: options.profile
|
|
315
|
+
})
|
|
316
|
+
.withLocalImageSupport({
|
|
317
|
+
tunnelService: options.tunnelService || 'serveo'
|
|
318
|
+
})
|
|
319
|
+
.withWallet(wallet)
|
|
320
|
+
.withCertificate(certificate)
|
|
321
|
+
.execute();
|
|
322
|
+
|
|
323
|
+
// Cleanup happens automatically!
|
|
324
|
+
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Benefits:**
|
|
330
|
+
- ✅ Automatic cleanup
|
|
331
|
+
- ✅ Discoverable API
|
|
332
|
+
- ✅ Type-safe
|
|
333
|
+
- ✅ Easy to read and understand
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
#### Option B: Ability Loading (for distributed use)
|
|
338
|
+
|
|
339
|
+
**Remote agent calling deploy-ability as a KADI ability:**
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// model-manager-agent/src/services/deployment-controller.ts
|
|
343
|
+
import { KadiClient } from '@kadi.build/core';
|
|
344
|
+
|
|
345
|
+
export class DeploymentController {
|
|
346
|
+
private deployAbility: any;
|
|
347
|
+
|
|
348
|
+
async initialize() {
|
|
349
|
+
const client = new KadiClient({ name: 'model-manager' });
|
|
350
|
+
|
|
351
|
+
// Load deploy-ability as a KADI ability
|
|
352
|
+
this.deployAbility = await client.load('deploy-ability', 'native');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async deployModel(request: DeploymentRequest) {
|
|
356
|
+
// Tool-based interface (works remotely)
|
|
357
|
+
const result = await this.deployAbility.deploy({
|
|
358
|
+
projectRoot: './templates',
|
|
359
|
+
profile: 'ollama-runtime',
|
|
360
|
+
localImageSupport: {
|
|
361
|
+
tunnelService: 'serveo'
|
|
362
|
+
},
|
|
363
|
+
wallet: this.wallet,
|
|
364
|
+
certificate: this.certificate,
|
|
365
|
+
// Additional deployment-specific options
|
|
366
|
+
modelName: request.modelName,
|
|
367
|
+
gpuType: request.gpu
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
if (!result.success) {
|
|
371
|
+
throw new Error(`Deployment failed: ${result.message}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return result;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Benefits:**
|
|
380
|
+
- ✅ Works across process boundaries
|
|
381
|
+
- ✅ Version-isolated through KADI
|
|
382
|
+
- ✅ Stateless RPC
|
|
383
|
+
- ✅ Simple parameter passing
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Implementation Guide
|
|
388
|
+
|
|
389
|
+
### Step 1: Create `kadi-ability.ts`
|
|
390
|
+
|
|
391
|
+
This is the new file that wraps your fluent API in a tool-based interface.
|
|
392
|
+
|
|
393
|
+
**File: `src/kadi-ability.ts`**
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
/**
|
|
397
|
+
* KADI Ability Interface for deploy-ability
|
|
398
|
+
*
|
|
399
|
+
* Exposes deployment functionality through KADI's ability system.
|
|
400
|
+
* Uses KadiClient to register tools with proper schemas and handlers.
|
|
401
|
+
*
|
|
402
|
+
* IMPORTANT: This is a thin wrapper around the fluent library API.
|
|
403
|
+
* For local/CLI use, prefer importing from '/lib' for the fluent interface.
|
|
404
|
+
*
|
|
405
|
+
* @module kadi-ability
|
|
406
|
+
*/
|
|
407
|
+
|
|
408
|
+
import { KadiClient, z } from '@kadi.build/core';
|
|
409
|
+
import { DeploymentPipeline } from './pipeline-builder.js';
|
|
410
|
+
import type {
|
|
411
|
+
DeploymentResult,
|
|
412
|
+
AkashWallet,
|
|
413
|
+
AkashCertificate
|
|
414
|
+
} from './types/index.js';
|
|
415
|
+
|
|
416
|
+
// Create the KadiClient instance
|
|
417
|
+
const client = new KadiClient({
|
|
418
|
+
name: 'deploy-ability',
|
|
419
|
+
version: '0.0.5',
|
|
420
|
+
description: 'Multi-target deployment orchestration for KADI',
|
|
421
|
+
role: 'ability'
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// ============================================================================
|
|
425
|
+
// Tool Registration
|
|
426
|
+
// ============================================================================
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* deploy - Deploy to target platform
|
|
430
|
+
*
|
|
431
|
+
* Main deployment tool that orchestrates the entire deployment process.
|
|
432
|
+
* Internally uses the DeploymentPipeline fluent API for clean resource management.
|
|
433
|
+
*/
|
|
434
|
+
const deployInputSchema = z.object({
|
|
435
|
+
projectRoot: z.string().describe('Project root directory'),
|
|
436
|
+
profile: z.string().describe('Deployment profile name'),
|
|
437
|
+
|
|
438
|
+
// Optional enhancements
|
|
439
|
+
localImageSupport: z.object({
|
|
440
|
+
tunnelService: z.enum(['serveo', 'ngrok', 'bore']).optional()
|
|
441
|
+
.describe('Tunnel service for exposing local registry'),
|
|
442
|
+
registryPort: z.number().optional()
|
|
443
|
+
.describe('Local registry port (default: 5001)')
|
|
444
|
+
}).optional().describe('Enable local image support with temporary registry'),
|
|
445
|
+
|
|
446
|
+
wallet: z.any().optional()
|
|
447
|
+
.describe('Akash wallet for blockchain transactions'),
|
|
448
|
+
|
|
449
|
+
certificate: z.any().optional()
|
|
450
|
+
.describe('Akash certificate for provider authentication'),
|
|
451
|
+
|
|
452
|
+
// CLI overrides
|
|
453
|
+
verbose: z.boolean().optional()
|
|
454
|
+
.describe('Enable verbose logging'),
|
|
455
|
+
|
|
456
|
+
dryRun: z.boolean().optional()
|
|
457
|
+
.describe('Simulate deployment without executing'),
|
|
458
|
+
|
|
459
|
+
yes: z.boolean().optional()
|
|
460
|
+
.describe('Auto-confirm all prompts'),
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const deployOutputSchema = z.object({
|
|
464
|
+
success: z.boolean(),
|
|
465
|
+
deploymentId: z.string().optional()
|
|
466
|
+
.describe('Unique deployment identifier'),
|
|
467
|
+
dseq: z.string().optional()
|
|
468
|
+
.describe('Akash deployment sequence number'),
|
|
469
|
+
endpoints: z.array(z.string()).optional()
|
|
470
|
+
.describe('Accessible service endpoints'),
|
|
471
|
+
leaseInfo: z.object({
|
|
472
|
+
provider: z.string(),
|
|
473
|
+
price: z.string(),
|
|
474
|
+
priceUsd: z.number().optional()
|
|
475
|
+
}).optional().describe('Lease information for Akash deployments'),
|
|
476
|
+
message: z.string().optional()
|
|
477
|
+
.describe('Status or error message'),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
client.registerTool({
|
|
481
|
+
name: 'deploy',
|
|
482
|
+
description: 'Deploy agent to target platform using specified profile',
|
|
483
|
+
input: deployInputSchema,
|
|
484
|
+
output: deployOutputSchema
|
|
485
|
+
}, async (params: z.infer<typeof deployInputSchema>) => {
|
|
486
|
+
try {
|
|
487
|
+
// Build the deployment pipeline using the fluent API
|
|
488
|
+
let pipeline = DeploymentPipeline.create({
|
|
489
|
+
projectRoot: params.projectRoot,
|
|
490
|
+
profile: params.profile,
|
|
491
|
+
verbose: params.verbose,
|
|
492
|
+
dryRun: params.dryRun,
|
|
493
|
+
yes: params.yes
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Add local image support if requested
|
|
497
|
+
if (params.localImageSupport) {
|
|
498
|
+
pipeline = pipeline.withLocalImageSupport({
|
|
499
|
+
tunnelService: params.localImageSupport.tunnelService || 'serveo',
|
|
500
|
+
registryPort: params.localImageSupport.registryPort
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Add wallet if provided (Akash deployments)
|
|
505
|
+
if (params.wallet) {
|
|
506
|
+
pipeline = pipeline.withWallet(params.wallet as AkashWallet);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Add certificate if provided (Akash deployments)
|
|
510
|
+
if (params.certificate) {
|
|
511
|
+
pipeline = pipeline.withCertificate(params.certificate as AkashCertificate);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Execute the pipeline
|
|
515
|
+
// The pipeline handles all resource management and cleanup automatically
|
|
516
|
+
const result = await pipeline.execute();
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
success: true,
|
|
520
|
+
deploymentId: result.deploymentId,
|
|
521
|
+
dseq: result.dseq,
|
|
522
|
+
endpoints: result.endpoints,
|
|
523
|
+
leaseInfo: result.leaseInfo,
|
|
524
|
+
message: 'Deployment completed successfully'
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
} catch (error) {
|
|
528
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
success: false,
|
|
532
|
+
message: `Deployment failed: ${errorMsg}`
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* getDeploymentStatus - Check deployment status
|
|
539
|
+
*
|
|
540
|
+
* Query the status of an existing deployment.
|
|
541
|
+
*/
|
|
542
|
+
const getStatusInputSchema = z.object({
|
|
543
|
+
deploymentId: z.string().describe('Deployment identifier to check'),
|
|
544
|
+
target: z.enum(['local', 'akash']).describe('Deployment target')
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const getStatusOutputSchema = z.object({
|
|
548
|
+
success: z.boolean(),
|
|
549
|
+
status: z.enum(['pending', 'active', 'failed', 'closed']).optional(),
|
|
550
|
+
message: z.string().optional()
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
client.registerTool({
|
|
554
|
+
name: 'getDeploymentStatus',
|
|
555
|
+
description: 'Get the current status of a deployment',
|
|
556
|
+
input: getStatusInputSchema,
|
|
557
|
+
output: getStatusOutputSchema
|
|
558
|
+
}, async (params: z.infer<typeof getStatusInputSchema>) => {
|
|
559
|
+
try {
|
|
560
|
+
// Implementation would query deployment status
|
|
561
|
+
// This is a placeholder for the actual implementation
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
success: true,
|
|
565
|
+
status: 'active',
|
|
566
|
+
message: 'Deployment is running'
|
|
567
|
+
};
|
|
568
|
+
} catch (error) {
|
|
569
|
+
return {
|
|
570
|
+
success: false,
|
|
571
|
+
message: error instanceof Error ? error.message : String(error)
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* closeDeployment - Close/stop a deployment
|
|
578
|
+
*
|
|
579
|
+
* Gracefully shut down and clean up a deployment.
|
|
580
|
+
*/
|
|
581
|
+
const closeInputSchema = z.object({
|
|
582
|
+
deploymentId: z.string().describe('Deployment identifier to close'),
|
|
583
|
+
target: z.enum(['local', 'akash']).describe('Deployment target')
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const closeOutputSchema = z.object({
|
|
587
|
+
success: z.boolean(),
|
|
588
|
+
message: z.string().optional()
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
client.registerTool({
|
|
592
|
+
name: 'closeDeployment',
|
|
593
|
+
description: 'Close and cleanup a deployment',
|
|
594
|
+
input: closeInputSchema,
|
|
595
|
+
output: closeOutputSchema
|
|
596
|
+
}, async (params: z.infer<typeof closeInputSchema>) => {
|
|
597
|
+
try {
|
|
598
|
+
// Implementation would close the deployment
|
|
599
|
+
// This is a placeholder for the actual implementation
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
success: true,
|
|
603
|
+
message: 'Deployment closed successfully'
|
|
604
|
+
};
|
|
605
|
+
} catch (error) {
|
|
606
|
+
return {
|
|
607
|
+
success: false,
|
|
608
|
+
message: error instanceof Error ? error.message : String(error)
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// Export the client as default
|
|
614
|
+
export default client;
|
|
615
|
+
|
|
616
|
+
// Allow running standalone as stdio server
|
|
617
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
618
|
+
await client.serve('stdio');
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
### Step 2: Update Library Export (`index.ts`)
|
|
625
|
+
|
|
626
|
+
Make sure the library export exposes the fluent API:
|
|
627
|
+
|
|
628
|
+
**File: `src/index.ts`**
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
/**
|
|
632
|
+
* deploy-ability - Library Export
|
|
633
|
+
*
|
|
634
|
+
* Main library interface for deploy-ability.
|
|
635
|
+
* Provides fluent API through DeploymentPipeline.
|
|
636
|
+
*
|
|
637
|
+
* @module deploy-ability/lib
|
|
638
|
+
*/
|
|
639
|
+
|
|
640
|
+
// ============================================================================
|
|
641
|
+
// Fluent API - Primary Interface
|
|
642
|
+
// ============================================================================
|
|
643
|
+
|
|
644
|
+
export { DeploymentPipeline } from './pipeline-builder.js';
|
|
645
|
+
|
|
646
|
+
// ============================================================================
|
|
647
|
+
// Types
|
|
648
|
+
// ============================================================================
|
|
649
|
+
|
|
650
|
+
export type {
|
|
651
|
+
DeploymentProfile,
|
|
652
|
+
AkashDeploymentProfile,
|
|
653
|
+
LocalDeploymentProfile,
|
|
654
|
+
DeploymentResult,
|
|
655
|
+
AkashWallet,
|
|
656
|
+
AkashCertificate,
|
|
657
|
+
ServiceConfig,
|
|
658
|
+
// ... all other types
|
|
659
|
+
} from './types/index.js';
|
|
660
|
+
|
|
661
|
+
// ============================================================================
|
|
662
|
+
// Legacy Exports (for backward compatibility)
|
|
663
|
+
// ============================================================================
|
|
664
|
+
|
|
665
|
+
// Keep these for existing code that uses the old API
|
|
666
|
+
export { loadProfile } from './utils/profileUtils.js';
|
|
667
|
+
export { setupRegistryIfNeeded } from './utils/registry/setup.js';
|
|
668
|
+
export { DockerRegistryManager } from './utils/registry/manager.js';
|
|
669
|
+
|
|
670
|
+
// Target implementations (advanced users)
|
|
671
|
+
export { deploy as deployToLocal } from './targets/local/local.js';
|
|
672
|
+
export { deploy as deployToAkash } from './targets/akash/akash.js';
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
### Step 3: Update `package.json`
|
|
678
|
+
|
|
679
|
+
Add the exports configuration:
|
|
680
|
+
|
|
681
|
+
```json
|
|
682
|
+
{
|
|
683
|
+
"name": "@kadi.build/deploy-ability",
|
|
684
|
+
"version": "0.0.5",
|
|
685
|
+
"description": "Multi-target deployment orchestration for KADI",
|
|
686
|
+
"type": "module",
|
|
687
|
+
"main": "./dist/kadi-ability.js",
|
|
688
|
+
"types": "./dist/kadi-ability.d.ts",
|
|
689
|
+
"exports": {
|
|
690
|
+
".": {
|
|
691
|
+
"types": "./dist/kadi-ability.d.ts",
|
|
692
|
+
"import": "./dist/kadi-ability.js"
|
|
693
|
+
},
|
|
694
|
+
"./lib": {
|
|
695
|
+
"types": "./dist/index.d.ts",
|
|
696
|
+
"import": "./dist/index.js"
|
|
697
|
+
},
|
|
698
|
+
"./types": {
|
|
699
|
+
"types": "./dist/types/index.d.ts",
|
|
700
|
+
"import": "./dist/types/index.js"
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
"scripts": {
|
|
704
|
+
"build": "tsc",
|
|
705
|
+
"build:watch": "tsc --watch",
|
|
706
|
+
"test": "vitest run"
|
|
707
|
+
},
|
|
708
|
+
"dependencies": {
|
|
709
|
+
"@kadi.build/core": "^0.0.1-alpha.15",
|
|
710
|
+
"zod": "^3.22.4"
|
|
711
|
+
},
|
|
712
|
+
"devDependencies": {
|
|
713
|
+
"@types/node": "^20.0.0",
|
|
714
|
+
"typescript": "^5.9.2",
|
|
715
|
+
"vitest": "^1.0.0"
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
---
|
|
721
|
+
|
|
722
|
+
### Step 4: TypeScript Configuration
|
|
723
|
+
|
|
724
|
+
Ensure TypeScript outputs both files correctly:
|
|
725
|
+
|
|
726
|
+
**tsconfig.json:**
|
|
727
|
+
```json
|
|
728
|
+
{
|
|
729
|
+
"compilerOptions": {
|
|
730
|
+
"target": "ES2022",
|
|
731
|
+
"module": "ES2022",
|
|
732
|
+
"moduleResolution": "bundler",
|
|
733
|
+
"outDir": "./dist",
|
|
734
|
+
"rootDir": "./src",
|
|
735
|
+
"declaration": true,
|
|
736
|
+
"declarationMap": true,
|
|
737
|
+
"sourceMap": true,
|
|
738
|
+
"esModuleInterop": true,
|
|
739
|
+
"skipLibCheck": true,
|
|
740
|
+
"strict": true
|
|
741
|
+
},
|
|
742
|
+
"include": ["src/**/*"],
|
|
743
|
+
"exclude": ["node_modules", "dist"]
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Usage Patterns
|
|
750
|
+
|
|
751
|
+
### Pattern 1: CLI Commands (kadi deploy)
|
|
752
|
+
|
|
753
|
+
**Use the library's fluent API:**
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
// kadi-deploy/src/commands/deploy.ts
|
|
757
|
+
import { DeploymentPipeline } from '@kadi.build/deploy-ability/lib';
|
|
758
|
+
|
|
759
|
+
export async function deployCommand(options: CommandOptions) {
|
|
760
|
+
const logger = createLogger(options.verbose);
|
|
761
|
+
|
|
762
|
+
logger.info('Starting deployment...');
|
|
763
|
+
|
|
764
|
+
try {
|
|
765
|
+
const result = await DeploymentPipeline
|
|
766
|
+
.create({
|
|
767
|
+
projectRoot: options.projectRoot || './',
|
|
768
|
+
profile: options.profile,
|
|
769
|
+
verbose: options.verbose,
|
|
770
|
+
dryRun: options.dryRun
|
|
771
|
+
})
|
|
772
|
+
.withLocalImageSupport({
|
|
773
|
+
tunnelService: options.tunnelService || 'serveo'
|
|
774
|
+
})
|
|
775
|
+
.withWallet(await loadWallet(options.walletPath))
|
|
776
|
+
.withCertificate(await loadCertificate(options.certPath))
|
|
777
|
+
.execute();
|
|
778
|
+
|
|
779
|
+
logger.success('Deployment completed!');
|
|
780
|
+
logger.info(`Deployment ID: ${result.deploymentId}`);
|
|
781
|
+
|
|
782
|
+
if (result.endpoints) {
|
|
783
|
+
logger.info('Endpoints:');
|
|
784
|
+
result.endpoints.forEach(ep => logger.info(` - ${ep}`));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
} catch (error) {
|
|
788
|
+
logger.error('Deployment failed:', error);
|
|
789
|
+
process.exit(1);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
**Why fluent API here?**
|
|
795
|
+
- Running locally, in-process
|
|
796
|
+
- Best developer experience
|
|
797
|
+
- Type-safe and discoverable
|
|
798
|
+
- Automatic cleanup
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
### Pattern 2: Agent-to-Agent Calls
|
|
803
|
+
|
|
804
|
+
**Use the ability's tool interface:**
|
|
805
|
+
|
|
806
|
+
```typescript
|
|
807
|
+
// model-manager-agent/src/services/deployment-controller.ts
|
|
808
|
+
import { KadiClient } from '@kadi.build/core';
|
|
809
|
+
|
|
810
|
+
export class DeploymentController {
|
|
811
|
+
private client: KadiClient;
|
|
812
|
+
private deployAbility: any;
|
|
813
|
+
|
|
814
|
+
constructor() {
|
|
815
|
+
this.client = new KadiClient({
|
|
816
|
+
name: 'model-manager-agent',
|
|
817
|
+
projectRoot: process.cwd()
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
async initialize() {
|
|
822
|
+
// Load deploy-ability as a KADI ability
|
|
823
|
+
this.deployAbility = await this.client.load('deploy-ability', 'native');
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async deployModel(request: DeployModelRequest): Promise<DeploymentInfo> {
|
|
827
|
+
// Call the deploy tool
|
|
828
|
+
const result = await this.deployAbility.deploy({
|
|
829
|
+
projectRoot: './templates',
|
|
830
|
+
profile: this.selectProfile(request),
|
|
831
|
+
localImageSupport: {
|
|
832
|
+
tunnelService: 'serveo'
|
|
833
|
+
},
|
|
834
|
+
wallet: this.wallet,
|
|
835
|
+
certificate: this.certificate
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
if (!result.success) {
|
|
839
|
+
throw new Error(`Deployment failed: ${result.message}`);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return {
|
|
843
|
+
deploymentId: result.deploymentId,
|
|
844
|
+
dseq: result.dseq,
|
|
845
|
+
endpoints: result.endpoints,
|
|
846
|
+
status: 'active'
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**Why tool interface here?**
|
|
853
|
+
- Might be running in different process
|
|
854
|
+
- Stateless RPC
|
|
855
|
+
- Simple parameter passing
|
|
856
|
+
- Version isolation through KADI
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
### Pattern 3: Automation Scripts
|
|
861
|
+
|
|
862
|
+
**Use the library's fluent API:**
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
// scripts/deploy-all-profiles.ts
|
|
866
|
+
import { DeploymentPipeline } from '@kadi.build/deploy-ability/lib';
|
|
867
|
+
import { loadWallet, loadCertificate } from './utils.js';
|
|
868
|
+
|
|
869
|
+
const profiles = ['staging', 'production', 'backup'];
|
|
870
|
+
|
|
871
|
+
async function deployAll() {
|
|
872
|
+
const wallet = await loadWallet();
|
|
873
|
+
const cert = await loadCertificate();
|
|
874
|
+
|
|
875
|
+
for (const profile of profiles) {
|
|
876
|
+
console.log(`\nDeploying profile: ${profile}`);
|
|
877
|
+
|
|
878
|
+
try {
|
|
879
|
+
const result = await DeploymentPipeline
|
|
880
|
+
.create({ projectRoot: './', profile })
|
|
881
|
+
.withWallet(wallet)
|
|
882
|
+
.withCertificate(cert)
|
|
883
|
+
.execute();
|
|
884
|
+
|
|
885
|
+
console.log(`✅ ${profile}: ${result.deploymentId}`);
|
|
886
|
+
} catch (error) {
|
|
887
|
+
console.error(`❌ ${profile} failed:`, error);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
deployAll();
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
**Why fluent API here?**
|
|
896
|
+
- Local automation
|
|
897
|
+
- Clear, readable code
|
|
898
|
+
- Composable operations
|
|
899
|
+
- Automatic resource management
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
### Pattern 4: MCP/Broker Integration
|
|
904
|
+
|
|
905
|
+
**Use the ability's tool interface:**
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
// broker-integration/src/deploy-service.ts
|
|
909
|
+
export class DeployService {
|
|
910
|
+
private client: KadiClient;
|
|
911
|
+
|
|
912
|
+
async handleDeployRequest(request: BrokerRequest): Promise<BrokerResponse> {
|
|
913
|
+
// Load deploy-ability through broker's client
|
|
914
|
+
const deployAbility = await this.client.load('deploy-ability', 'native');
|
|
915
|
+
|
|
916
|
+
// Parse request parameters
|
|
917
|
+
const params = JSON.parse(request.parameters);
|
|
918
|
+
|
|
919
|
+
// Execute deployment via tool
|
|
920
|
+
const result = await deployAbility.deploy({
|
|
921
|
+
projectRoot: params.projectRoot,
|
|
922
|
+
profile: params.profile,
|
|
923
|
+
wallet: params.wallet,
|
|
924
|
+
certificate: params.certificate
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
return {
|
|
928
|
+
success: result.success,
|
|
929
|
+
data: result,
|
|
930
|
+
message: result.message
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
**Why tool interface here?**
|
|
937
|
+
- Standard protocol (MCP/JSON-RPC)
|
|
938
|
+
- Broker expects tool-based interface
|
|
939
|
+
- May cross process boundaries
|
|
940
|
+
- Needs to be serializable
|
|
941
|
+
|
|
942
|
+
---
|
|
943
|
+
|
|
944
|
+
## Comparison: Why This Works
|
|
945
|
+
|
|
946
|
+
### Fluent APIs vs Tool-based RPC
|
|
947
|
+
|
|
948
|
+
| Aspect | Fluent API | Tool-based RPC |
|
|
949
|
+
|--------|-----------|----------------|
|
|
950
|
+
| **State** | Stateful (builder pattern) | Stateless (each call independent) |
|
|
951
|
+
| **Location** | Local/in-process | Can be distributed |
|
|
952
|
+
| **Composability** | High (method chaining) | Medium (parameter objects) |
|
|
953
|
+
| **Discoverability** | Excellent (IDE autocomplete) | Good (schema-based) |
|
|
954
|
+
| **Type Safety** | Excellent (compile-time) | Good (runtime validation) |
|
|
955
|
+
| **Error Handling** | Synchronous exceptions | Result objects |
|
|
956
|
+
| **Resource Management** | RAII pattern (automatic) | Manual or tool-managed |
|
|
957
|
+
| **Network Serialization** | Not applicable | Required |
|
|
958
|
+
| **Best For** | CLI, scripts, local dev | Agents, distributed, remote |
|
|
959
|
+
|
|
960
|
+
### Why Both Are Valid
|
|
961
|
+
|
|
962
|
+
**Fluent API strengths:**
|
|
963
|
+
- Builder pattern allows incremental configuration
|
|
964
|
+
- Automatic resource cleanup (RAII)
|
|
965
|
+
- Type-safe at compile time
|
|
966
|
+
- Great IDE support
|
|
967
|
+
- Easy to read and understand
|
|
968
|
+
|
|
969
|
+
**Tool-based RPC strengths:**
|
|
970
|
+
- Works across process boundaries
|
|
971
|
+
- Simple serialization (JSON)
|
|
972
|
+
- Standard protocol (compatible with MCP, JSON-RPC)
|
|
973
|
+
- Stateless (easy to scale)
|
|
974
|
+
- Version isolation through KADI
|
|
975
|
+
|
|
976
|
+
**The solution: Offer both!**
|
|
977
|
+
|
|
978
|
+
---
|
|
979
|
+
|
|
980
|
+
## Migration Strategy
|
|
981
|
+
|
|
982
|
+
### Phase 1: Add Ability Layer (No Breaking Changes)
|
|
983
|
+
|
|
984
|
+
**Week 1-2:**
|
|
985
|
+
1. Create `kadi-ability.ts`
|
|
986
|
+
2. Add exports to `package.json`
|
|
987
|
+
3. Write tests for tool interface
|
|
988
|
+
4. Update documentation
|
|
989
|
+
|
|
990
|
+
**Result:**
|
|
991
|
+
- Existing code keeps working (uses `/lib` export)
|
|
992
|
+
- New consumers can choose ability or library
|
|
993
|
+
- No migration required
|
|
994
|
+
|
|
995
|
+
---
|
|
996
|
+
|
|
997
|
+
### Phase 2: Update Internal Consumers
|
|
998
|
+
|
|
999
|
+
**Week 3-4:**
|
|
1000
|
+
|
|
1001
|
+
Update kadi-deploy and other consumers to use the fluent API:
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
// Before (old verbose API)
|
|
1005
|
+
const registryCleanup = await setupRegistryIfNeeded(profile);
|
|
1006
|
+
try {
|
|
1007
|
+
await deploy(profile, wallet, cert);
|
|
1008
|
+
} finally {
|
|
1009
|
+
await registryCleanup?.();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// After (new fluent API)
|
|
1013
|
+
await DeploymentPipeline
|
|
1014
|
+
.create({ projectRoot: './', profile: 'prod' })
|
|
1015
|
+
.withLocalImageSupport()
|
|
1016
|
+
.withWallet(wallet)
|
|
1017
|
+
.withCertificate(cert)
|
|
1018
|
+
.execute();
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
**Result:**
|
|
1022
|
+
- Cleaner code
|
|
1023
|
+
- Automatic cleanup
|
|
1024
|
+
- Better error handling
|
|
1025
|
+
- Same functionality
|
|
1026
|
+
|
|
1027
|
+
---
|
|
1028
|
+
|
|
1029
|
+
### Phase 3: Gradual Adoption
|
|
1030
|
+
|
|
1031
|
+
**Month 2:**
|
|
1032
|
+
|
|
1033
|
+
As new use cases emerge:
|
|
1034
|
+
- **Local/CLI** → Use fluent API from `/lib`
|
|
1035
|
+
- **Distributed/Remote** → Use ability interface
|
|
1036
|
+
|
|
1037
|
+
**Documentation updates:**
|
|
1038
|
+
- Add examples for both patterns
|
|
1039
|
+
- Explain when to use each
|
|
1040
|
+
- Migration guide for existing code
|
|
1041
|
+
|
|
1042
|
+
**Result:**
|
|
1043
|
+
- Full ecosystem support
|
|
1044
|
+
- Clear patterns established
|
|
1045
|
+
- Best practices documented
|
|
1046
|
+
|
|
1047
|
+
---
|
|
1048
|
+
|
|
1049
|
+
## Benefits & Trade-offs
|
|
1050
|
+
|
|
1051
|
+
### Benefits
|
|
1052
|
+
|
|
1053
|
+
✅ **Fluent API Preserved**
|
|
1054
|
+
- Keep the excellent Pipeline Builder design
|
|
1055
|
+
- Best developer experience for local use
|
|
1056
|
+
- Type-safe, discoverable, clean
|
|
1057
|
+
|
|
1058
|
+
✅ **Distributed Capability Added**
|
|
1059
|
+
- Agents can call deployments remotely
|
|
1060
|
+
- Standard tool-based interface
|
|
1061
|
+
- Works with KADI ecosystem
|
|
1062
|
+
|
|
1063
|
+
✅ **Ecosystem Integration**
|
|
1064
|
+
- Version management through KADI
|
|
1065
|
+
- Ability loading and isolation
|
|
1066
|
+
- Compatible with MCP/broker protocols
|
|
1067
|
+
|
|
1068
|
+
✅ **Backward Compatibility**
|
|
1069
|
+
- Existing code keeps working
|
|
1070
|
+
- No forced migration
|
|
1071
|
+
- Gradual adoption path
|
|
1072
|
+
|
|
1073
|
+
✅ **Resource Management**
|
|
1074
|
+
- Automatic cleanup via RAII (fluent API)
|
|
1075
|
+
- No manual cleanup needed
|
|
1076
|
+
- Fewer bugs from forgotten cleanup
|
|
1077
|
+
|
|
1078
|
+
✅ **Flexibility**
|
|
1079
|
+
- Choose the right interface for your use case
|
|
1080
|
+
- Not locked into one pattern
|
|
1081
|
+
- Can mix both in same project
|
|
1082
|
+
|
|
1083
|
+
---
|
|
1084
|
+
|
|
1085
|
+
### Trade-offs
|
|
1086
|
+
|
|
1087
|
+
⚠️ **Slightly More Complex Package**
|
|
1088
|
+
- Two entry points (`./` and `./lib`)
|
|
1089
|
+
- Need to understand which to use when
|
|
1090
|
+
- More documentation needed
|
|
1091
|
+
|
|
1092
|
+
**Mitigation:**
|
|
1093
|
+
- Clear documentation with examples
|
|
1094
|
+
- Decision tree for which interface to use
|
|
1095
|
+
- Both are well-designed and intuitive
|
|
1096
|
+
|
|
1097
|
+
⚠️ **Two Interfaces to Maintain**
|
|
1098
|
+
- Library interface (fluent API)
|
|
1099
|
+
- Ability interface (tools)
|
|
1100
|
+
- Need to keep both in sync
|
|
1101
|
+
|
|
1102
|
+
**Mitigation:**
|
|
1103
|
+
- Ability wraps library (single source of truth)
|
|
1104
|
+
- Changes to library automatically available in ability
|
|
1105
|
+
- Thin wrapper means less maintenance
|
|
1106
|
+
|
|
1107
|
+
⚠️ **Learning Curve**
|
|
1108
|
+
- Developers need to know both patterns exist
|
|
1109
|
+
- When to use fluent vs tools
|
|
1110
|
+
|
|
1111
|
+
**Mitigation:**
|
|
1112
|
+
- Good defaults (CLI → fluent, agents → tools)
|
|
1113
|
+
- Clear documentation and examples
|
|
1114
|
+
- Each interface is simple on its own
|
|
1115
|
+
|
|
1116
|
+
---
|
|
1117
|
+
|
|
1118
|
+
### Overall Assessment
|
|
1119
|
+
|
|
1120
|
+
**Score: 8/10**
|
|
1121
|
+
|
|
1122
|
+
The dual export pattern is:
|
|
1123
|
+
- ✅ **Worth implementing** - benefits outweigh costs
|
|
1124
|
+
- ✅ **Proven pattern** - secret-ability demonstrates success
|
|
1125
|
+
- ✅ **Best of both worlds** - no compromises on either interface
|
|
1126
|
+
- ✅ **Future-proof** - supports both local and distributed use cases
|
|
1127
|
+
|
|
1128
|
+
---
|
|
1129
|
+
|
|
1130
|
+
## Comparison with secret-ability
|
|
1131
|
+
|
|
1132
|
+
### How They Use the Same Pattern
|
|
1133
|
+
|
|
1134
|
+
Both use the dual export pattern, but for different domains:
|
|
1135
|
+
|
|
1136
|
+
**secret-ability:**
|
|
1137
|
+
```typescript
|
|
1138
|
+
// Library (fluent API for vault management)
|
|
1139
|
+
import { Secrets } from '@kadi.build/secret-ability/lib';
|
|
1140
|
+
|
|
1141
|
+
await secrets.init()
|
|
1142
|
+
.ensure('wallet', { type: 'custom', path: './wallet.vault' })
|
|
1143
|
+
.use('wallet');
|
|
1144
|
+
|
|
1145
|
+
await secrets.set('KEY', 'value');
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
**deploy-ability (proposed):**
|
|
1149
|
+
```typescript
|
|
1150
|
+
// Library (fluent API for deployment)
|
|
1151
|
+
import { DeploymentPipeline } from '@kadi.build/deploy-ability/lib';
|
|
1152
|
+
|
|
1153
|
+
await DeploymentPipeline
|
|
1154
|
+
.create({ projectRoot: './', profile: 'production' })
|
|
1155
|
+
.withLocalImageSupport()
|
|
1156
|
+
.execute();
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
### Lessons Learned from secret-ability
|
|
1160
|
+
|
|
1161
|
+
1. **Fluent initializer pattern works great**
|
|
1162
|
+
- secret-ability's `SecretsInitializer` is excellent
|
|
1163
|
+
- Chainable setup, then execute
|
|
1164
|
+
- We're using the same pattern
|
|
1165
|
+
|
|
1166
|
+
2. **Tool interface is simple**
|
|
1167
|
+
- Each tool maps to a library method
|
|
1168
|
+
- Thin wrapper, minimal complexity
|
|
1169
|
+
- Easy to maintain
|
|
1170
|
+
|
|
1171
|
+
3. **Clear separation of concerns**
|
|
1172
|
+
- Library handles business logic
|
|
1173
|
+
- Ability handles RPC and protocols
|
|
1174
|
+
- Works well in practice
|
|
1175
|
+
|
|
1176
|
+
4. **Documentation is key**
|
|
1177
|
+
- Need clear examples for both interfaces
|
|
1178
|
+
- Explain when to use which
|
|
1179
|
+
- Show the connection between them
|
|
1180
|
+
|
|
1181
|
+
### Differences in Use Cases
|
|
1182
|
+
|
|
1183
|
+
**secret-ability:**
|
|
1184
|
+
- Manages vault initialization and lifecycle
|
|
1185
|
+
- Secret CRUD operations
|
|
1186
|
+
- Vault registry management
|
|
1187
|
+
|
|
1188
|
+
**deploy-ability:**
|
|
1189
|
+
- Orchestrates deployment pipelines
|
|
1190
|
+
- Manages temporary resources (registry, tunnel)
|
|
1191
|
+
- Coordinates multiple services
|
|
1192
|
+
|
|
1193
|
+
**Both benefit from fluent APIs** because:
|
|
1194
|
+
- Complex multi-step processes
|
|
1195
|
+
- Resource lifecycle management
|
|
1196
|
+
- Clear configuration builders
|
|
1197
|
+
- Automatic cleanup needed
|
|
1198
|
+
|
|
1199
|
+
---
|
|
1200
|
+
|
|
1201
|
+
## FAQ
|
|
1202
|
+
|
|
1203
|
+
### "Why not just use fluent API for the ability too?"
|
|
1204
|
+
|
|
1205
|
+
**Short answer:** Fluent APIs don't work across process boundaries.
|
|
1206
|
+
|
|
1207
|
+
**Long answer:**
|
|
1208
|
+
|
|
1209
|
+
Fluent APIs require maintaining state (the builder object):
|
|
1210
|
+
|
|
1211
|
+
```typescript
|
|
1212
|
+
const pipeline = DeploymentPipeline.create({...}); // State created
|
|
1213
|
+
pipeline.withWallet(wallet); // State modified
|
|
1214
|
+
pipeline.withCertificate(cert); // State modified
|
|
1215
|
+
await pipeline.execute(); // State executed
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
In a distributed system:
|
|
1219
|
+
- Each method call might go to a different instance
|
|
1220
|
+
- You can't serialize a builder object with all its state
|
|
1221
|
+
- The server doesn't maintain session state between calls
|
|
1222
|
+
|
|
1223
|
+
Tool-based RPC is stateless:
|
|
1224
|
+
```typescript
|
|
1225
|
+
await deployAbility.deploy({
|
|
1226
|
+
// Everything in one call
|
|
1227
|
+
profile: 'prod',
|
|
1228
|
+
wallet: wallet,
|
|
1229
|
+
certificate: cert
|
|
1230
|
+
});
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
Each call is independent and self-contained.
|
|
1234
|
+
|
|
1235
|
+
---
|
|
1236
|
+
|
|
1237
|
+
### "Can I use the fluent API from remote services?"
|
|
1238
|
+
|
|
1239
|
+
**Yes, but only if you import the library directly:**
|
|
1240
|
+
|
|
1241
|
+
```typescript
|
|
1242
|
+
// In a remote agent
|
|
1243
|
+
import { DeploymentPipeline } from '@kadi.build/deploy-ability/lib';
|
|
1244
|
+
|
|
1245
|
+
// This works because you're using the library, not the ability
|
|
1246
|
+
await DeploymentPipeline.create({...}).execute();
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
**However:**
|
|
1250
|
+
- You lose KADI version isolation
|
|
1251
|
+
- Direct npm dependency instead of ability loading
|
|
1252
|
+
- Might have version conflicts
|
|
1253
|
+
|
|
1254
|
+
**Recommendation:** If you're remote, use the ability interface instead.
|
|
1255
|
+
|
|
1256
|
+
---
|
|
1257
|
+
|
|
1258
|
+
### "Which interface should I use?"
|
|
1259
|
+
|
|
1260
|
+
**Decision tree:**
|
|
1261
|
+
|
|
1262
|
+
```
|
|
1263
|
+
Are you writing...
|
|
1264
|
+
├─ CLI command? → Use fluent API (/lib)
|
|
1265
|
+
├─ Local script? → Use fluent API (/lib)
|
|
1266
|
+
├─ Agent calling another agent? → Use ability interface (default)
|
|
1267
|
+
├─ Broker/MCP integration? → Use ability interface (default)
|
|
1268
|
+
└─ Not sure? → Use fluent API if local, ability if distributed
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
**Rule of thumb:**
|
|
1272
|
+
- **Same process** → Fluent API (`/lib`)
|
|
1273
|
+
- **Different process** → Ability interface (default)
|
|
1274
|
+
|
|
1275
|
+
---
|
|
1276
|
+
|
|
1277
|
+
### "Will this break existing code?"
|
|
1278
|
+
|
|
1279
|
+
**No! Here's why:**
|
|
1280
|
+
|
|
1281
|
+
**For deploy-ability consumers:**
|
|
1282
|
+
- Current code imports from default export
|
|
1283
|
+
- We're keeping all existing exports in `/lib`
|
|
1284
|
+
- Old API remains available
|
|
1285
|
+
|
|
1286
|
+
**Example:**
|
|
1287
|
+
```typescript
|
|
1288
|
+
// Old code (still works!)
|
|
1289
|
+
import { loadProfile, setupRegistryIfNeeded } from '@kadi.build/deploy-ability';
|
|
1290
|
+
|
|
1291
|
+
// New code (better!)
|
|
1292
|
+
import { DeploymentPipeline } from '@kadi.build/deploy-ability/lib';
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
Both work! No forced migration.
|
|
1296
|
+
|
|
1297
|
+
---
|
|
1298
|
+
|
|
1299
|
+
### "Do I need to update my code immediately?"
|
|
1300
|
+
|
|
1301
|
+
**No, it's optional:**
|
|
1302
|
+
|
|
1303
|
+
The dual export pattern means:
|
|
1304
|
+
- ✅ Existing code keeps working
|
|
1305
|
+
- ✅ New code can use fluent API
|
|
1306
|
+
- ✅ Gradual migration possible
|
|
1307
|
+
- ✅ No breaking changes
|
|
1308
|
+
|
|
1309
|
+
**When to update:**
|
|
1310
|
+
- When you want the fluent API benefits
|
|
1311
|
+
- When you need remote deployment capability
|
|
1312
|
+
- When you're writing new code
|
|
1313
|
+
- At your own pace
|
|
1314
|
+
|
|
1315
|
+
---
|
|
1316
|
+
|
|
1317
|
+
## Conclusion
|
|
1318
|
+
|
|
1319
|
+
### Summary
|
|
1320
|
+
|
|
1321
|
+
**The dual export pattern enables deploy-ability to be both:**
|
|
1322
|
+
1. A KADI ability (tool-based RPC for distributed use)
|
|
1323
|
+
2. A fluent API library (for local/CLI use)
|
|
1324
|
+
|
|
1325
|
+
**This is possible because:**
|
|
1326
|
+
- The ability layer is a thin wrapper around the library
|
|
1327
|
+
- Both interfaces serve different, valid use cases
|
|
1328
|
+
- secret-ability proves this pattern works
|
|
1329
|
+
|
|
1330
|
+
**The Pipeline Builder fluent API is preserved and enhanced:**
|
|
1331
|
+
- ✅ Available via `/lib` export
|
|
1332
|
+
- ✅ Best-in-class developer experience
|
|
1333
|
+
- ✅ Automatic resource management
|
|
1334
|
+
- ✅ Type-safe and discoverable
|
|
1335
|
+
|
|
1336
|
+
**The ability interface adds new capabilities:**
|
|
1337
|
+
- ✅ Distributed deployments
|
|
1338
|
+
- ✅ Agent-to-agent communication
|
|
1339
|
+
- ✅ KADI ecosystem integration
|
|
1340
|
+
- ✅ Standard tool-based protocol
|
|
1341
|
+
|
|
1342
|
+
### Recommendation
|
|
1343
|
+
|
|
1344
|
+
**Proceed with converting deploy-ability to a KADI ability using the dual export pattern.**
|
|
1345
|
+
|
|
1346
|
+
**Implementation priority:**
|
|
1347
|
+
1. ✅ Create `kadi-ability.ts` with tool wrappers
|
|
1348
|
+
2. ✅ Update `package.json` with dual exports
|
|
1349
|
+
3. ✅ Update `index.ts` to export fluent API
|
|
1350
|
+
4. ✅ Write comprehensive documentation
|
|
1351
|
+
5. ✅ Add examples for both use cases
|
|
1352
|
+
6. ✅ Update existing consumers gradually
|
|
1353
|
+
|
|
1354
|
+
**Expected outcome:**
|
|
1355
|
+
- Best of both worlds
|
|
1356
|
+
- No breaking changes
|
|
1357
|
+
- Future-proof architecture
|
|
1358
|
+
- Happy developers ✨
|
|
1359
|
+
|
|
1360
|
+
---
|
|
1361
|
+
|
|
1362
|
+
**Document Version:** 1.0
|
|
1363
|
+
**Last Updated:** 2025-11-18
|
|
1364
|
+
**Author:** KADI Team
|
|
1365
|
+
**Status:** Design Proposal - Ready for Implementation
|