@nordsym/apiclaw 1.2.10 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/convex/_generated/api.d.ts +2 -0
- package/convex/capabilities.ts +157 -0
- package/convex/schema.ts +63 -0
- package/dist/capability-router.d.ts +41 -0
- package/dist/capability-router.d.ts.map +1 -0
- package/dist/capability-router.js +241 -0
- package/dist/capability-router.js.map +1 -0
- package/dist/index.js +98 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/seed-capabilities.ts +190 -0
- package/src/capability-router.ts +331 -0
- package/src/index.ts +110 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type * as analytics from "../analytics.js";
|
|
12
|
+
import type * as capabilities from "../capabilities.js";
|
|
12
13
|
import type * as credits from "../credits.js";
|
|
13
14
|
import type * as directCall from "../directCall.js";
|
|
14
15
|
import type * as http from "../http.js";
|
|
@@ -27,6 +28,7 @@ import type {
|
|
|
27
28
|
|
|
28
29
|
declare const fullApi: ApiFromModules<{
|
|
29
30
|
analytics: typeof analytics;
|
|
31
|
+
capabilities: typeof capabilities;
|
|
30
32
|
credits: typeof credits;
|
|
31
33
|
directCall: typeof directCall;
|
|
32
34
|
http: typeof http;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { query, mutation } from "./_generated/server";
|
|
3
|
+
|
|
4
|
+
// Get a capability by ID
|
|
5
|
+
export const getById = query({
|
|
6
|
+
args: { id: v.string() },
|
|
7
|
+
handler: async (ctx, args) => {
|
|
8
|
+
return await ctx.db
|
|
9
|
+
.query("capabilities")
|
|
10
|
+
.withIndex("by_capability_id", (q) => q.eq("id", args.id))
|
|
11
|
+
.first();
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// List all capabilities
|
|
16
|
+
export const list = query({
|
|
17
|
+
args: {},
|
|
18
|
+
handler: async (ctx) => {
|
|
19
|
+
return await ctx.db.query("capabilities").collect();
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// List capabilities by category
|
|
24
|
+
export const listByCategory = query({
|
|
25
|
+
args: { category: v.string() },
|
|
26
|
+
handler: async (ctx, args) => {
|
|
27
|
+
return await ctx.db
|
|
28
|
+
.query("capabilities")
|
|
29
|
+
.withIndex("by_category", (q) => q.eq("category", args.category))
|
|
30
|
+
.collect();
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Get providers for a capability (sorted by priority, filtered by enabled + healthy)
|
|
35
|
+
export const getProviders = query({
|
|
36
|
+
args: { capabilityId: v.string(), region: v.optional(v.string()) },
|
|
37
|
+
handler: async (ctx, args) => {
|
|
38
|
+
let providers = await ctx.db
|
|
39
|
+
.query("providerCapabilities")
|
|
40
|
+
.withIndex("by_capabilityId_enabled", (q) =>
|
|
41
|
+
q.eq("capabilityId", args.capabilityId).eq("enabled", true)
|
|
42
|
+
)
|
|
43
|
+
.collect();
|
|
44
|
+
|
|
45
|
+
// Filter by region if specified
|
|
46
|
+
if (args.region) {
|
|
47
|
+
providers = providers.filter(p => p.regions.includes(args.region!));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Filter out unhealthy providers
|
|
51
|
+
providers = providers.filter(p => p.healthStatus !== "down");
|
|
52
|
+
|
|
53
|
+
// Sort by priority, then price, then latency
|
|
54
|
+
providers.sort((a, b) => {
|
|
55
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
56
|
+
if (a.pricePerUnit !== b.pricePerUnit) return a.pricePerUnit - b.pricePerUnit;
|
|
57
|
+
return a.avgLatencyMs - b.avgLatencyMs;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return providers;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Create a capability
|
|
65
|
+
export const create = mutation({
|
|
66
|
+
args: {
|
|
67
|
+
id: v.string(),
|
|
68
|
+
name: v.string(),
|
|
69
|
+
description: v.string(),
|
|
70
|
+
category: v.string(),
|
|
71
|
+
standardParams: v.array(v.object({
|
|
72
|
+
name: v.string(),
|
|
73
|
+
type: v.string(),
|
|
74
|
+
required: v.boolean(),
|
|
75
|
+
description: v.string(),
|
|
76
|
+
default: v.optional(v.any()),
|
|
77
|
+
})),
|
|
78
|
+
},
|
|
79
|
+
handler: async (ctx, args) => {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
return await ctx.db.insert("capabilities", {
|
|
82
|
+
...args,
|
|
83
|
+
createdAt: now,
|
|
84
|
+
updatedAt: now,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Add provider to capability
|
|
90
|
+
export const addProvider = mutation({
|
|
91
|
+
args: {
|
|
92
|
+
providerId: v.string(),
|
|
93
|
+
capabilityId: v.string(),
|
|
94
|
+
priority: v.number(),
|
|
95
|
+
regions: v.array(v.string()),
|
|
96
|
+
pricePerUnit: v.number(),
|
|
97
|
+
currency: v.string(),
|
|
98
|
+
avgLatencyMs: v.number(),
|
|
99
|
+
paramMapping: v.any(),
|
|
100
|
+
},
|
|
101
|
+
handler: async (ctx, args) => {
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
return await ctx.db.insert("providerCapabilities", {
|
|
104
|
+
...args,
|
|
105
|
+
enabled: true,
|
|
106
|
+
healthStatus: "healthy",
|
|
107
|
+
createdAt: now,
|
|
108
|
+
updatedAt: now,
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Update provider health status
|
|
114
|
+
export const updateHealth = mutation({
|
|
115
|
+
args: {
|
|
116
|
+
providerId: v.string(),
|
|
117
|
+
capabilityId: v.string(),
|
|
118
|
+
healthStatus: v.string(),
|
|
119
|
+
},
|
|
120
|
+
handler: async (ctx, args) => {
|
|
121
|
+
const mapping = await ctx.db
|
|
122
|
+
.query("providerCapabilities")
|
|
123
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", args.providerId))
|
|
124
|
+
.filter((q) => q.eq(q.field("capabilityId"), args.capabilityId))
|
|
125
|
+
.first();
|
|
126
|
+
|
|
127
|
+
if (mapping) {
|
|
128
|
+
await ctx.db.patch(mapping._id, {
|
|
129
|
+
healthStatus: args.healthStatus,
|
|
130
|
+
lastHealthCheck: Date.now(),
|
|
131
|
+
updatedAt: Date.now(),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Log capability usage
|
|
138
|
+
export const logUsage = mutation({
|
|
139
|
+
args: {
|
|
140
|
+
capabilityId: v.string(),
|
|
141
|
+
providerId: v.string(),
|
|
142
|
+
userId: v.string(),
|
|
143
|
+
action: v.string(),
|
|
144
|
+
success: v.boolean(),
|
|
145
|
+
fallbackUsed: v.boolean(),
|
|
146
|
+
fallbackReason: v.optional(v.string()),
|
|
147
|
+
latencyMs: v.number(),
|
|
148
|
+
cost: v.number(),
|
|
149
|
+
currency: v.string(),
|
|
150
|
+
},
|
|
151
|
+
handler: async (ctx, args) => {
|
|
152
|
+
return await ctx.db.insert("capabilityLogs", {
|
|
153
|
+
...args,
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
});
|
package/convex/schema.ts
CHANGED
|
@@ -307,4 +307,67 @@ export default defineSchema({
|
|
|
307
307
|
})
|
|
308
308
|
.index("by_email", ["email"])
|
|
309
309
|
.index("by_type", ["type"]),
|
|
310
|
+
|
|
311
|
+
// ============================================
|
|
312
|
+
// CAPABILITY LAYER (abstraction over providers)
|
|
313
|
+
// ============================================
|
|
314
|
+
|
|
315
|
+
// Capability definitions (sms, email, invoice, search, etc.)
|
|
316
|
+
capabilities: defineTable({
|
|
317
|
+
id: v.string(), // "sms", "email", "invoice"
|
|
318
|
+
name: v.string(), // "SMS Messaging"
|
|
319
|
+
description: v.string(),
|
|
320
|
+
category: v.string(), // "communication", "business", "ai"
|
|
321
|
+
standardParams: v.array(v.object({
|
|
322
|
+
name: v.string(),
|
|
323
|
+
type: v.string(), // "string" | "number" | "boolean"
|
|
324
|
+
required: v.boolean(),
|
|
325
|
+
description: v.string(),
|
|
326
|
+
default: v.optional(v.any()),
|
|
327
|
+
})),
|
|
328
|
+
createdAt: v.number(),
|
|
329
|
+
updatedAt: v.number(),
|
|
330
|
+
})
|
|
331
|
+
.index("by_capability_id", ["id"])
|
|
332
|
+
.index("by_category", ["category"]),
|
|
333
|
+
|
|
334
|
+
// Provider → Capability mappings (which providers offer which capabilities)
|
|
335
|
+
providerCapabilities: defineTable({
|
|
336
|
+
providerId: v.string(), // "46elks", "twilio"
|
|
337
|
+
capabilityId: v.string(), // "sms"
|
|
338
|
+
priority: v.number(), // 1 = primary, 2 = fallback
|
|
339
|
+
regions: v.array(v.string()), // ["SE", "EU", "US"]
|
|
340
|
+
pricePerUnit: v.number(), // in smallest currency unit (cents/öre)
|
|
341
|
+
currency: v.string(), // "SEK", "USD"
|
|
342
|
+
avgLatencyMs: v.number(),
|
|
343
|
+
paramMapping: v.any(), // Record<string, string> - capability param → provider param
|
|
344
|
+
enabled: v.boolean(),
|
|
345
|
+
healthStatus: v.string(), // "healthy" | "degraded" | "down"
|
|
346
|
+
lastHealthCheck: v.optional(v.number()),
|
|
347
|
+
createdAt: v.number(),
|
|
348
|
+
updatedAt: v.number(),
|
|
349
|
+
})
|
|
350
|
+
.index("by_providerId", ["providerId"])
|
|
351
|
+
.index("by_capabilityId", ["capabilityId"])
|
|
352
|
+
.index("by_capabilityId_enabled", ["capabilityId", "enabled"])
|
|
353
|
+
.index("by_healthStatus", ["healthStatus"]),
|
|
354
|
+
|
|
355
|
+
// Capability usage logs (for analytics and billing)
|
|
356
|
+
capabilityLogs: defineTable({
|
|
357
|
+
capabilityId: v.string(),
|
|
358
|
+
providerId: v.string(),
|
|
359
|
+
userId: v.string(),
|
|
360
|
+
action: v.string(),
|
|
361
|
+
success: v.boolean(),
|
|
362
|
+
fallbackUsed: v.boolean(),
|
|
363
|
+
fallbackReason: v.optional(v.string()),
|
|
364
|
+
latencyMs: v.number(),
|
|
365
|
+
cost: v.number(),
|
|
366
|
+
currency: v.string(),
|
|
367
|
+
timestamp: v.number(),
|
|
368
|
+
})
|
|
369
|
+
.index("by_capabilityId", ["capabilityId"])
|
|
370
|
+
.index("by_providerId", ["providerId"])
|
|
371
|
+
.index("by_userId", ["userId"])
|
|
372
|
+
.index("by_timestamp", ["timestamp"]),
|
|
310
373
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APIClaw Capability Router
|
|
3
|
+
* Routes capability requests to the best available provider
|
|
4
|
+
*/
|
|
5
|
+
interface CapabilityPreferences {
|
|
6
|
+
region?: string;
|
|
7
|
+
maxPrice?: number;
|
|
8
|
+
preferredProvider?: string;
|
|
9
|
+
fallback?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface CapabilityResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
capability: string;
|
|
14
|
+
action: string;
|
|
15
|
+
providerUsed?: string;
|
|
16
|
+
fallbackAttempted: boolean;
|
|
17
|
+
fallbackReason?: string;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
error?: string;
|
|
20
|
+
cost?: number;
|
|
21
|
+
currency?: string;
|
|
22
|
+
latencyMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Execute a capability request with automatic provider selection and fallback
|
|
26
|
+
*/
|
|
27
|
+
export declare function executeCapability(capabilityId: string, action: string, params: Record<string, unknown>, userId: string, preferences?: CapabilityPreferences): Promise<CapabilityResult>;
|
|
28
|
+
/**
|
|
29
|
+
* List available capabilities
|
|
30
|
+
*/
|
|
31
|
+
export declare function listCapabilities(): Promise<{
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
category: string;
|
|
35
|
+
}[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Check if a capability exists
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasCapability(capabilityId: string): Promise<boolean>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=capability-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-router.d.ts","sourceRoot":"","sources":["../src/capability-router.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4ED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,EACd,WAAW,GAAE,qBAA0B,GACtC,OAAO,CAAC,gBAAgB,CAAC,CAmJ3B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAwBlG;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqB1E"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APIClaw Capability Router
|
|
3
|
+
* Routes capability requests to the best available provider
|
|
4
|
+
*/
|
|
5
|
+
import { executeAPICall } from './execute.js';
|
|
6
|
+
import { logAPICall } from './analytics.js';
|
|
7
|
+
// Convex HTTP API for capability queries
|
|
8
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://adventurous-avocet-799.convex.cloud';
|
|
9
|
+
/**
|
|
10
|
+
* Query Convex for providers that support a capability
|
|
11
|
+
*/
|
|
12
|
+
async function getProvidersForCapability(capabilityId, region) {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(`${CONVEX_URL}/api/query`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
path: 'capabilities:getProviders',
|
|
19
|
+
args: { capabilityId, region },
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
return [];
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (Array.isArray(data))
|
|
26
|
+
return data;
|
|
27
|
+
return (data.value || []);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
console.error('Failed to fetch capability providers:', e);
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Map capability params to provider-specific params
|
|
36
|
+
*/
|
|
37
|
+
function mapParams(params, mapping) {
|
|
38
|
+
const result = {};
|
|
39
|
+
for (const [capParam, value] of Object.entries(params)) {
|
|
40
|
+
const providerParam = mapping[capParam] || capParam;
|
|
41
|
+
result[providerParam] = value;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Log capability usage to Convex
|
|
47
|
+
*/
|
|
48
|
+
async function logCapabilityUsage(params) {
|
|
49
|
+
try {
|
|
50
|
+
await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
path: 'capabilities:logUsage',
|
|
55
|
+
args: params,
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
console.error('Failed to log capability usage:', e);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Execute a capability request with automatic provider selection and fallback
|
|
65
|
+
*/
|
|
66
|
+
export async function executeCapability(capabilityId, action, params, userId, preferences = {}) {
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
const enableFallback = preferences.fallback !== false; // Default true
|
|
69
|
+
// Get providers for this capability
|
|
70
|
+
const providers = await getProvidersForCapability(capabilityId, preferences.region);
|
|
71
|
+
if (providers.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
capability: capabilityId,
|
|
75
|
+
action,
|
|
76
|
+
fallbackAttempted: false,
|
|
77
|
+
error: `No providers available for capability: ${capabilityId}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Filter by max price if specified
|
|
81
|
+
let filteredProviders = providers;
|
|
82
|
+
if (preferences.maxPrice !== undefined) {
|
|
83
|
+
filteredProviders = providers.filter(p => p.pricePerUnit <= preferences.maxPrice);
|
|
84
|
+
}
|
|
85
|
+
// Prefer specific provider if requested
|
|
86
|
+
if (preferences.preferredProvider) {
|
|
87
|
+
const preferred = filteredProviders.find(p => p.providerId === preferences.preferredProvider);
|
|
88
|
+
if (preferred) {
|
|
89
|
+
filteredProviders = [preferred, ...filteredProviders.filter(p => p.providerId !== preferences.preferredProvider)];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (filteredProviders.length === 0) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
capability: capabilityId,
|
|
96
|
+
action,
|
|
97
|
+
fallbackAttempted: false,
|
|
98
|
+
error: 'No providers match your preferences (region/price)',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Try providers in order
|
|
102
|
+
let fallbackAttempted = false;
|
|
103
|
+
let lastError = '';
|
|
104
|
+
for (let i = 0; i < filteredProviders.length; i++) {
|
|
105
|
+
const provider = filteredProviders[i];
|
|
106
|
+
const isFirstAttempt = i === 0;
|
|
107
|
+
if (!isFirstAttempt) {
|
|
108
|
+
fallbackAttempted = true;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
// Map params to provider-specific format
|
|
112
|
+
const mappedParams = mapParams(params, provider.paramMapping || {});
|
|
113
|
+
// Execute via existing executeAPICall
|
|
114
|
+
const result = await executeAPICall(provider.providerId, action, mappedParams, userId);
|
|
115
|
+
const latencyMs = Date.now() - startTime;
|
|
116
|
+
if (result.success) {
|
|
117
|
+
// Log successful usage
|
|
118
|
+
logCapabilityUsage({
|
|
119
|
+
capabilityId,
|
|
120
|
+
providerId: provider.providerId,
|
|
121
|
+
userId,
|
|
122
|
+
action,
|
|
123
|
+
success: true,
|
|
124
|
+
fallbackUsed: fallbackAttempted,
|
|
125
|
+
fallbackReason: fallbackAttempted ? lastError : undefined,
|
|
126
|
+
latencyMs,
|
|
127
|
+
cost: provider.pricePerUnit,
|
|
128
|
+
currency: provider.currency,
|
|
129
|
+
});
|
|
130
|
+
// Also log to file-based analytics
|
|
131
|
+
logAPICall({
|
|
132
|
+
timestamp: new Date().toISOString(),
|
|
133
|
+
provider: provider.providerId,
|
|
134
|
+
action,
|
|
135
|
+
type: 'direct',
|
|
136
|
+
userId,
|
|
137
|
+
success: true,
|
|
138
|
+
latencyMs,
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
capability: capabilityId,
|
|
143
|
+
action,
|
|
144
|
+
providerUsed: provider.providerId,
|
|
145
|
+
fallbackAttempted,
|
|
146
|
+
fallbackReason: fallbackAttempted ? lastError : undefined,
|
|
147
|
+
data: result.data,
|
|
148
|
+
cost: provider.pricePerUnit,
|
|
149
|
+
currency: provider.currency,
|
|
150
|
+
latencyMs,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// Provider returned error, try next
|
|
154
|
+
lastError = result.error || 'Unknown error';
|
|
155
|
+
if (!enableFallback) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
lastError = e.message || 'Provider execution failed';
|
|
161
|
+
if (!enableFallback) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// All providers failed
|
|
167
|
+
const latencyMs = Date.now() - startTime;
|
|
168
|
+
logCapabilityUsage({
|
|
169
|
+
capabilityId,
|
|
170
|
+
providerId: filteredProviders[0].providerId,
|
|
171
|
+
userId,
|
|
172
|
+
action,
|
|
173
|
+
success: false,
|
|
174
|
+
fallbackUsed: fallbackAttempted,
|
|
175
|
+
fallbackReason: lastError,
|
|
176
|
+
latencyMs,
|
|
177
|
+
cost: 0,
|
|
178
|
+
currency: 'SEK',
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
capability: capabilityId,
|
|
183
|
+
action,
|
|
184
|
+
fallbackAttempted,
|
|
185
|
+
error: `All providers failed. Last error: ${lastError}`,
|
|
186
|
+
latencyMs,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* List available capabilities
|
|
191
|
+
*/
|
|
192
|
+
export async function listCapabilities() {
|
|
193
|
+
try {
|
|
194
|
+
const res = await fetch(`${CONVEX_URL}/api/query`, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({
|
|
198
|
+
path: 'capabilities:list',
|
|
199
|
+
args: {},
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
if (!res.ok)
|
|
203
|
+
return [];
|
|
204
|
+
const data = await res.json();
|
|
205
|
+
const capabilities = Array.isArray(data) ? data : (data.value || []);
|
|
206
|
+
return capabilities.map(c => ({
|
|
207
|
+
id: c.id,
|
|
208
|
+
name: c.name,
|
|
209
|
+
category: c.category,
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Check if a capability exists
|
|
218
|
+
*/
|
|
219
|
+
export async function hasCapability(capabilityId) {
|
|
220
|
+
try {
|
|
221
|
+
const res = await fetch(`${CONVEX_URL}/api/query`, {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
headers: { 'Content-Type': 'application/json' },
|
|
224
|
+
body: JSON.stringify({
|
|
225
|
+
path: 'capabilities:getById',
|
|
226
|
+
args: { id: capabilityId },
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
if (!res.ok)
|
|
230
|
+
return false;
|
|
231
|
+
const data = await res.json();
|
|
232
|
+
if (data && typeof data === 'object' && 'value' in data) {
|
|
233
|
+
return !!data.value;
|
|
234
|
+
}
|
|
235
|
+
return !!data;
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=capability-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-router.js","sourceRoot":"","sources":["../src/capability-router.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { executeAPICall, getConnectedProviders } from './execute.js';
|
|
|
20
20
|
import { logAPICall } from './analytics.js';
|
|
21
21
|
import { isOpenAPI, executeOpenAPI, listOpenAPIs } from './open-apis.js';
|
|
22
22
|
import { requiresConfirmationAsync, createPendingAction, consumePendingAction, generatePreview, validateParams } from './confirmation.js';
|
|
23
|
+
import { executeCapability, listCapabilities, hasCapability } from './capability-router.js';
|
|
23
24
|
// Default agent ID for MVP (in production, this would come from auth)
|
|
24
25
|
const DEFAULT_AGENT_ID = 'agent_default';
|
|
25
26
|
/**
|
|
@@ -196,6 +197,46 @@ const tools = [
|
|
|
196
197
|
type: 'object',
|
|
197
198
|
properties: {}
|
|
198
199
|
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'capability',
|
|
203
|
+
description: 'Execute an action by capability, not provider. APIClaw automatically selects the best provider, handles fallback, and optimizes for cost/speed. Example: capability("sms", "send", {to: "+46...", message: "Hello"})',
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: 'object',
|
|
206
|
+
properties: {
|
|
207
|
+
capability: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
description: 'Capability ID: "sms", "email", "search", "tts", "invoice", "llm"'
|
|
210
|
+
},
|
|
211
|
+
action: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
description: 'Action to perform: "send", "search", "generate", etc.'
|
|
214
|
+
},
|
|
215
|
+
params: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
description: 'Parameters for the action (capability-standard params, not provider-specific)'
|
|
218
|
+
},
|
|
219
|
+
preferences: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
description: 'Optional routing preferences',
|
|
222
|
+
properties: {
|
|
223
|
+
region: { type: 'string', description: 'Preferred region: "SE", "EU", "US"' },
|
|
224
|
+
maxPrice: { type: 'number', description: 'Max price per unit in cents/öre' },
|
|
225
|
+
preferredProvider: { type: 'string', description: 'Hint to prefer a specific provider' },
|
|
226
|
+
fallback: { type: 'boolean', description: 'Enable fallback to other providers (default: true)' }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
required: ['capability', 'action', 'params']
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'list_capabilities',
|
|
235
|
+
description: 'List all available capabilities and their providers.',
|
|
236
|
+
inputSchema: {
|
|
237
|
+
type: 'object',
|
|
238
|
+
properties: {}
|
|
239
|
+
}
|
|
199
240
|
}
|
|
200
241
|
];
|
|
201
242
|
// Create server
|
|
@@ -594,6 +635,63 @@ Docs: https://apiclaw.nordsym.com
|
|
|
594
635
|
]
|
|
595
636
|
};
|
|
596
637
|
}
|
|
638
|
+
case 'capability': {
|
|
639
|
+
const capabilityId = args?.capability;
|
|
640
|
+
const action = args?.action;
|
|
641
|
+
const params = args?.params || {};
|
|
642
|
+
const preferences = args?.preferences || {};
|
|
643
|
+
// Check if capability exists
|
|
644
|
+
const exists = await hasCapability(capabilityId);
|
|
645
|
+
if (!exists) {
|
|
646
|
+
// Try to help with available capabilities
|
|
647
|
+
const available = await listCapabilities();
|
|
648
|
+
return {
|
|
649
|
+
content: [{
|
|
650
|
+
type: 'text',
|
|
651
|
+
text: JSON.stringify({
|
|
652
|
+
status: 'error',
|
|
653
|
+
error: `Unknown capability: ${capabilityId}`,
|
|
654
|
+
available_capabilities: available.map(c => c.id),
|
|
655
|
+
hint: 'Use list_capabilities to see all available capabilities.'
|
|
656
|
+
}, null, 2)
|
|
657
|
+
}],
|
|
658
|
+
isError: true
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
// Execute capability
|
|
662
|
+
const result = await executeCapability(capabilityId, action, params, DEFAULT_AGENT_ID, preferences);
|
|
663
|
+
return {
|
|
664
|
+
content: [{
|
|
665
|
+
type: 'text',
|
|
666
|
+
text: JSON.stringify({
|
|
667
|
+
status: result.success ? 'success' : 'error',
|
|
668
|
+
capability: result.capability,
|
|
669
|
+
action: result.action,
|
|
670
|
+
provider_used: result.providerUsed,
|
|
671
|
+
fallback_attempted: result.fallbackAttempted,
|
|
672
|
+
...(result.fallbackReason ? { fallback_reason: result.fallbackReason } : {}),
|
|
673
|
+
...(result.success ? { data: result.data } : { error: result.error }),
|
|
674
|
+
...(result.cost !== undefined ? { cost: result.cost, currency: result.currency } : {}),
|
|
675
|
+
latency_ms: result.latencyMs,
|
|
676
|
+
}, null, 2)
|
|
677
|
+
}],
|
|
678
|
+
isError: !result.success
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
case 'list_capabilities': {
|
|
682
|
+
const capabilities = await listCapabilities();
|
|
683
|
+
return {
|
|
684
|
+
content: [{
|
|
685
|
+
type: 'text',
|
|
686
|
+
text: JSON.stringify({
|
|
687
|
+
status: 'success',
|
|
688
|
+
message: 'Available capabilities - use capability() to execute',
|
|
689
|
+
capabilities,
|
|
690
|
+
usage: 'capability("sms", "send", {to: "+46...", message: "Hello"})'
|
|
691
|
+
}, null, 2)
|
|
692
|
+
}]
|
|
693
|
+
};
|
|
694
|
+
}
|
|
597
695
|
default:
|
|
598
696
|
return {
|
|
599
697
|
content: [
|