@okrlinkhub/agent-bridge 0.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -127
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +100 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/client/index.d.ts +50 -173
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +129 -263
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +4 -4
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +66 -162
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/agentBridgeUtils.d.ts +8 -0
- package/dist/component/agentBridgeUtils.d.ts.map +1 -0
- package/dist/component/agentBridgeUtils.js +33 -0
- package/dist/component/agentBridgeUtils.js.map +1 -0
- package/dist/component/agents.d.ts +27 -0
- package/dist/component/agents.d.ts.map +1 -0
- package/dist/component/agents.js +94 -0
- package/dist/component/agents.js.map +1 -0
- package/dist/component/gateway.d.ts +30 -44
- package/dist/component/gateway.d.ts.map +1 -1
- package/dist/component/gateway.js +127 -132
- package/dist/component/gateway.js.map +1 -1
- package/dist/component/permissions.d.ts +30 -84
- package/dist/component/permissions.d.ts.map +1 -1
- package/dist/component/permissions.js +80 -203
- package/dist/component/permissions.js.map +1 -1
- package/dist/component/schema.d.ts +55 -153
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +30 -80
- package/dist/component/schema.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -3
- package/dist/react/index.js.map +1 -1
- package/package.json +7 -3
- package/src/cli/init.ts +116 -0
- package/src/client/index.ts +228 -389
- package/src/component/_generated/api.ts +4 -4
- package/src/component/_generated/component.ts +79 -195
- package/src/component/agentBridgeUtils.ts +52 -0
- package/src/component/agents.ts +106 -0
- package/src/component/gateway.ts +149 -163
- package/src/component/permissions.ts +89 -259
- package/src/component/schema.ts +31 -96
- package/src/react/index.ts +5 -6
- package/dist/component/provisioning.d.ts +0 -87
- package/dist/component/provisioning.d.ts.map +0 -1
- package/dist/component/provisioning.js +0 -343
- package/dist/component/provisioning.js.map +0 -1
- package/dist/component/registry.d.ts +0 -46
- package/dist/component/registry.d.ts.map +0 -1
- package/dist/component/registry.js +0 -121
- package/dist/component/registry.js.map +0 -1
- package/src/component/provisioning.ts +0 -402
- package/src/component/registry.ts +0 -152
package/src/client/index.ts
CHANGED
|
@@ -1,477 +1,316 @@
|
|
|
1
1
|
import { httpActionGeneric } from "convex/server";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
3
|
+
FunctionHandle,
|
|
4
4
|
GenericDataModel,
|
|
5
5
|
GenericMutationCtx,
|
|
6
|
-
GenericQueryCtx,
|
|
7
|
-
FunctionHandle,
|
|
8
6
|
HttpRouter,
|
|
9
7
|
} from "convex/server";
|
|
10
8
|
import type { ComponentApi } from "../component/_generated/component.js";
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type
|
|
19
|
-
GenericActionCtx<GenericDataModel>,
|
|
20
|
-
"runQuery" | "runMutation" | "runAction"
|
|
21
|
-
>;
|
|
22
|
-
|
|
23
|
-
// --- Types ---
|
|
24
|
-
|
|
25
|
-
export interface FunctionDef {
|
|
26
|
-
/** Alias name that agents use to call this function (e.g. "okr:getObjectives") */
|
|
27
|
-
name: string;
|
|
28
|
-
/** Function handle string from createFunctionHandle() */
|
|
29
|
-
handle: string;
|
|
30
|
-
/** Type of the Convex function */
|
|
31
|
-
type: "query" | "mutation" | "action";
|
|
32
|
-
/** Human-readable description */
|
|
33
|
-
description?: string;
|
|
10
|
+
export type AgentBridgeFunctionType = "query" | "mutation" | "action";
|
|
11
|
+
|
|
12
|
+
type UnknownFunctionReference = unknown;
|
|
13
|
+
|
|
14
|
+
export interface AgentBridgeFunctionDefinition {
|
|
15
|
+
ref: UnknownFunctionReference;
|
|
16
|
+
type?: AgentBridgeFunctionType;
|
|
34
17
|
}
|
|
35
18
|
|
|
36
|
-
export interface
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
requestsPerHour: number;
|
|
41
|
-
tokenBudget: number;
|
|
42
|
-
};
|
|
19
|
+
export interface AgentBridgeFunctionMetadata {
|
|
20
|
+
description?: string;
|
|
21
|
+
riskLevel?: "low" | "medium" | "high";
|
|
22
|
+
category?: string;
|
|
43
23
|
}
|
|
44
24
|
|
|
45
25
|
export interface AgentBridgeConfig {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
functions: Record<
|
|
27
|
+
string,
|
|
28
|
+
UnknownFunctionReference | AgentBridgeFunctionDefinition
|
|
29
|
+
>;
|
|
30
|
+
metadata?: Record<string, AgentBridgeFunctionMetadata>;
|
|
50
31
|
}
|
|
51
32
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* Usage:
|
|
58
|
-
* ```ts
|
|
59
|
-
* import { AgentBridge } from "@okrlinkhub/agent-bridge";
|
|
60
|
-
* import { components } from "./_generated/api";
|
|
61
|
-
*
|
|
62
|
-
* const bridge = new AgentBridge(components.agentBridge, { appName: "okr" });
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
export class AgentBridge {
|
|
66
|
-
public component: ComponentApi;
|
|
67
|
-
public appName: string;
|
|
68
|
-
private defaultPermissions: DefaultPermission[];
|
|
69
|
-
|
|
70
|
-
constructor(component: ComponentApi, config: AgentBridgeConfig) {
|
|
71
|
-
this.component = component;
|
|
72
|
-
this.appName = config.appName;
|
|
73
|
-
this.defaultPermissions = config.defaultPermissions ?? [
|
|
74
|
-
{ pattern: "*", permission: "deny" },
|
|
75
|
-
];
|
|
76
|
-
}
|
|
33
|
+
export function defineAgentBridgeConfig(
|
|
34
|
+
config: AgentBridgeConfig,
|
|
35
|
+
): AgentBridgeConfig {
|
|
36
|
+
return config;
|
|
37
|
+
}
|
|
77
38
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
}
|
|
39
|
+
export function generateAgentApiKey(prefix: string = "abk_live"): string {
|
|
40
|
+
const bytes = new Uint8Array(24);
|
|
41
|
+
crypto.getRandomValues(bytes);
|
|
42
|
+
const token = Array.from(bytes)
|
|
43
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
44
|
+
.join("");
|
|
45
|
+
return `${prefix}_${token}`;
|
|
46
|
+
}
|
|
88
47
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
48
|
+
type NormalizedFunctionDefinition = {
|
|
49
|
+
ref: UnknownFunctionReference;
|
|
50
|
+
type: AgentBridgeFunctionType;
|
|
51
|
+
metadata?: AgentBridgeFunctionMetadata;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type NormalizedAgentBridgeConfig = {
|
|
55
|
+
functions: Record<string, NormalizedFunctionDefinition>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function detectFunctionType(
|
|
59
|
+
fnRef: UnknownFunctionReference,
|
|
60
|
+
): AgentBridgeFunctionType | null {
|
|
61
|
+
if (!fnRef || typeof fnRef !== "object") {
|
|
62
|
+
return null;
|
|
101
63
|
}
|
|
102
64
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
for (const fn of functions) {
|
|
112
|
-
const id = await this.registerFunction(ctx, fn);
|
|
113
|
-
ids.push(id);
|
|
65
|
+
if ("_type" in fnRef) {
|
|
66
|
+
const candidate = (fnRef as { _type?: unknown })._type;
|
|
67
|
+
if (
|
|
68
|
+
candidate === "query" ||
|
|
69
|
+
candidate === "mutation" ||
|
|
70
|
+
candidate === "action"
|
|
71
|
+
) {
|
|
72
|
+
return candidate;
|
|
114
73
|
}
|
|
115
|
-
return ids;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Unregister a function.
|
|
120
|
-
*/
|
|
121
|
-
async unregisterFunction(
|
|
122
|
-
ctx: MutationCtx,
|
|
123
|
-
functionName: string,
|
|
124
|
-
): Promise<boolean> {
|
|
125
|
-
return await ctx.runMutation(this.component.registry.unregister, {
|
|
126
|
-
appName: this.appName,
|
|
127
|
-
functionName,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* List all registered functions for this app.
|
|
133
|
-
*/
|
|
134
|
-
async listFunctions(ctx: QueryCtx) {
|
|
135
|
-
return await ctx.runQuery(this.component.registry.listFunctions, {
|
|
136
|
-
appName: this.appName,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Generate a provisioning token (admin operation).
|
|
142
|
-
* Returns the plaintext token -- store/communicate securely!
|
|
143
|
-
*/
|
|
144
|
-
async generateProvisioningToken(
|
|
145
|
-
ctx: MutationCtx,
|
|
146
|
-
opts: {
|
|
147
|
-
employeeEmail: string;
|
|
148
|
-
department: string;
|
|
149
|
-
maxApps?: number;
|
|
150
|
-
expiresInDays?: number;
|
|
151
|
-
createdBy: string;
|
|
152
|
-
},
|
|
153
|
-
) {
|
|
154
|
-
return await ctx.runMutation(
|
|
155
|
-
this.component.provisioning.generateProvisioningToken,
|
|
156
|
-
opts,
|
|
157
|
-
);
|
|
158
74
|
}
|
|
159
75
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*/
|
|
163
|
-
async listAgents(ctx: QueryCtx, activeOnly?: boolean) {
|
|
164
|
-
return await ctx.runQuery(this.component.provisioning.listAgents, {
|
|
165
|
-
activeOnly,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Revoke an agent globally.
|
|
171
|
-
*/
|
|
172
|
-
async revokeAgent(
|
|
173
|
-
ctx: MutationCtx,
|
|
174
|
-
opts: { agentId: string; revokedBy: string },
|
|
175
|
-
): Promise<boolean> {
|
|
176
|
-
return await ctx.runMutation(this.component.provisioning.revokeAgent, opts);
|
|
177
|
-
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
178
78
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
): Promise<boolean> {
|
|
186
|
-
return await ctx.runMutation(
|
|
187
|
-
this.component.provisioning.revokeAppInstance,
|
|
188
|
-
{
|
|
189
|
-
agentId: opts.agentId,
|
|
190
|
-
appName: this.appName,
|
|
191
|
-
},
|
|
192
|
-
);
|
|
79
|
+
export function normalizeAgentBridgeConfig(
|
|
80
|
+
config: AgentBridgeConfig,
|
|
81
|
+
): NormalizedAgentBridgeConfig {
|
|
82
|
+
const functionEntries = Object.entries(config.functions);
|
|
83
|
+
if (functionEntries.length === 0) {
|
|
84
|
+
throw new Error("agent-bridge config requires at least one function");
|
|
193
85
|
}
|
|
194
86
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
opts: {
|
|
201
|
-
agentId: string;
|
|
202
|
-
functionPattern: string;
|
|
203
|
-
permission: "allow" | "deny" | "rate_limited";
|
|
204
|
-
rateLimitConfig?: { requestsPerHour: number; tokenBudget: number };
|
|
205
|
-
createdBy: string;
|
|
206
|
-
},
|
|
207
|
-
): Promise<string> {
|
|
208
|
-
return await ctx.runMutation(this.component.permissions.setPermission, {
|
|
209
|
-
agentId: opts.agentId,
|
|
210
|
-
appName: this.appName,
|
|
211
|
-
functionPattern: opts.functionPattern,
|
|
212
|
-
permission: opts.permission,
|
|
213
|
-
rateLimitConfig: opts.rateLimitConfig,
|
|
214
|
-
createdBy: opts.createdBy,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
87
|
+
const normalizedFunctions: Record<string, NormalizedFunctionDefinition> = {};
|
|
88
|
+
for (const [functionKey, entry] of functionEntries) {
|
|
89
|
+
if (!functionKey.trim()) {
|
|
90
|
+
throw new Error("function keys cannot be empty");
|
|
91
|
+
}
|
|
217
92
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
93
|
+
const metadata = config.metadata?.[functionKey];
|
|
94
|
+
const hasExplicitConfig =
|
|
95
|
+
entry && typeof entry === "object" && "ref" in (entry as object);
|
|
96
|
+
const ref = hasExplicitConfig
|
|
97
|
+
? (entry as AgentBridgeFunctionDefinition).ref
|
|
98
|
+
: entry;
|
|
99
|
+
const explicitType = hasExplicitConfig
|
|
100
|
+
? (entry as AgentBridgeFunctionDefinition).type
|
|
101
|
+
: undefined;
|
|
102
|
+
|
|
103
|
+
const detectedType = detectFunctionType(ref);
|
|
104
|
+
const functionType = explicitType ?? detectedType;
|
|
105
|
+
if (!functionType) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Cannot detect function type for "${functionKey}". Set { ref, type } explicitly in agent-bridge config.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
231
110
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
agentId,
|
|
238
|
-
appName: this.appName,
|
|
239
|
-
});
|
|
111
|
+
normalizedFunctions[functionKey] = {
|
|
112
|
+
ref,
|
|
113
|
+
type: functionType,
|
|
114
|
+
metadata,
|
|
115
|
+
};
|
|
240
116
|
}
|
|
241
117
|
|
|
242
|
-
|
|
243
|
-
* Query access logs.
|
|
244
|
-
*/
|
|
245
|
-
async queryAccessLog(
|
|
246
|
-
ctx: QueryCtx,
|
|
247
|
-
opts?: { agentId?: string; limit?: number },
|
|
248
|
-
) {
|
|
249
|
-
return await ctx.runQuery(this.component.gateway.queryAccessLog, {
|
|
250
|
-
agentId: opts?.agentId,
|
|
251
|
-
appName: this.appName,
|
|
252
|
-
limit: opts?.limit,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
118
|
+
return { functions: normalizedFunctions };
|
|
255
119
|
}
|
|
256
120
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
*
|
|
264
|
-
* Must be called in the host app's `convex/http.ts` file.
|
|
265
|
-
*
|
|
266
|
-
* @example
|
|
267
|
-
* ```ts
|
|
268
|
-
* import { httpRouter } from "convex/server";
|
|
269
|
-
* import { registerRoutes } from "@okrlinkhub/agent-bridge";
|
|
270
|
-
* import { components } from "./_generated/api";
|
|
271
|
-
*
|
|
272
|
-
* const http = httpRouter();
|
|
273
|
-
* registerRoutes(http, components.agentBridge, { appName: "okr" });
|
|
274
|
-
* export default http;
|
|
275
|
-
* ```
|
|
276
|
-
*/
|
|
121
|
+
type ExecuteRequestBody = {
|
|
122
|
+
functionKey?: string;
|
|
123
|
+
args?: Record<string, unknown>;
|
|
124
|
+
estimatedCost?: number;
|
|
125
|
+
};
|
|
126
|
+
|
|
277
127
|
export function registerRoutes(
|
|
278
128
|
http: HttpRouter,
|
|
279
129
|
component: ComponentApi,
|
|
280
|
-
|
|
130
|
+
bridgeConfig: AgentBridgeConfig,
|
|
131
|
+
options?: { pathPrefix?: string },
|
|
281
132
|
) {
|
|
282
|
-
const prefix =
|
|
133
|
+
const prefix = options?.pathPrefix ?? "/agent";
|
|
134
|
+
const normalizedConfig = normalizeAgentBridgeConfig(bridgeConfig);
|
|
135
|
+
const availableFunctionKeys = Object.keys(normalizedConfig.functions);
|
|
283
136
|
|
|
284
|
-
// --- POST /agent-bridge/execute ---
|
|
285
|
-
// Gateway: execute a registered function on behalf of an agent
|
|
286
137
|
http.route({
|
|
287
138
|
path: `${prefix}/execute`,
|
|
288
139
|
method: "POST",
|
|
289
140
|
handler: httpActionGeneric(async (ctx, request) => {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
141
|
+
const apiKey = request.headers.get("X-Agent-API-Key");
|
|
142
|
+
if (!apiKey) {
|
|
143
|
+
return jsonResponse({ success: false, error: "Missing API key" }, 401);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let body: ExecuteRequestBody;
|
|
295
147
|
try {
|
|
296
148
|
body = await request.json();
|
|
297
149
|
} catch {
|
|
298
|
-
return jsonResponse(
|
|
150
|
+
return jsonResponse(
|
|
151
|
+
{ success: false, error: "Invalid JSON body" },
|
|
152
|
+
400,
|
|
153
|
+
);
|
|
299
154
|
}
|
|
300
155
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
if (!instanceToken || !functionName) {
|
|
156
|
+
const functionKey = body.functionKey?.trim();
|
|
157
|
+
if (!functionKey) {
|
|
304
158
|
return jsonResponse(
|
|
305
|
-
{ error: "Missing required
|
|
159
|
+
{ success: false, error: "Missing required field: functionKey" },
|
|
306
160
|
400,
|
|
307
161
|
);
|
|
308
162
|
}
|
|
309
163
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
appName: config.appName,
|
|
318
|
-
},
|
|
319
|
-
);
|
|
164
|
+
const functionDef = normalizedConfig.functions[functionKey];
|
|
165
|
+
if (!functionDef) {
|
|
166
|
+
return jsonResponse(
|
|
167
|
+
{ success: false, error: `Function "${functionKey}" not found` },
|
|
168
|
+
404,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
320
171
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
// Log the denied access
|
|
327
|
-
await ctx.runMutation(component.gateway.logAccess, {
|
|
328
|
-
agentId: authResult.agentId ?? "unknown",
|
|
329
|
-
appName: config.appName,
|
|
330
|
-
functionCalled: functionName,
|
|
331
|
-
permission: "deny",
|
|
332
|
-
errorMessage: authResult.error + detailSuffix,
|
|
333
|
-
});
|
|
172
|
+
const authResult = await ctx.runMutation(component.gateway.authorizeRequest, {
|
|
173
|
+
apiKey,
|
|
174
|
+
functionKey,
|
|
175
|
+
estimatedCost: body.estimatedCost,
|
|
176
|
+
});
|
|
334
177
|
|
|
335
|
-
|
|
336
|
-
|
|
178
|
+
if (!authResult.authorized) {
|
|
179
|
+
const response = jsonResponse(
|
|
180
|
+
{ success: false, error: authResult.error },
|
|
337
181
|
authResult.statusCode,
|
|
338
182
|
);
|
|
183
|
+
if (authResult.statusCode === 429) {
|
|
184
|
+
response.headers.set(
|
|
185
|
+
"Retry-After",
|
|
186
|
+
String(authResult.retryAfterSeconds ?? 3600),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return response;
|
|
339
190
|
}
|
|
340
191
|
|
|
341
|
-
// Step 2: Execute the function via the registered handle
|
|
342
192
|
const startTime = Date.now();
|
|
343
193
|
try {
|
|
194
|
+
const args = body.args ?? {};
|
|
344
195
|
let result: unknown;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
result = await ctx.runAction(
|
|
361
|
-
authResult.functionHandle as FunctionHandle<"action">,
|
|
362
|
-
args ?? {},
|
|
363
|
-
);
|
|
364
|
-
break;
|
|
196
|
+
if (functionDef.type === "query") {
|
|
197
|
+
result = await ctx.runQuery(
|
|
198
|
+
functionDef.ref as FunctionHandle<"query">,
|
|
199
|
+
args,
|
|
200
|
+
);
|
|
201
|
+
} else if (functionDef.type === "mutation") {
|
|
202
|
+
result = await ctx.runMutation(
|
|
203
|
+
functionDef.ref as FunctionHandle<"mutation">,
|
|
204
|
+
args,
|
|
205
|
+
);
|
|
206
|
+
} else {
|
|
207
|
+
result = await ctx.runAction(
|
|
208
|
+
functionDef.ref as FunctionHandle<"action">,
|
|
209
|
+
args,
|
|
210
|
+
);
|
|
365
211
|
}
|
|
366
212
|
|
|
367
|
-
const durationMs = Date.now() - startTime;
|
|
368
|
-
|
|
369
|
-
// Step 3: Log successful access
|
|
370
213
|
await ctx.runMutation(component.gateway.logAccess, {
|
|
371
214
|
agentId: authResult.agentId,
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
215
|
+
functionKey,
|
|
216
|
+
args,
|
|
217
|
+
result,
|
|
218
|
+
duration: Date.now() - startTime,
|
|
219
|
+
timestamp: Date.now(),
|
|
376
220
|
});
|
|
377
221
|
|
|
378
|
-
return jsonResponse({
|
|
222
|
+
return jsonResponse({ success: true, result }, 200);
|
|
379
223
|
} catch (error: unknown) {
|
|
380
|
-
const durationMs = Date.now() - startTime;
|
|
381
224
|
const errorMessage =
|
|
382
|
-
error
|
|
225
|
+
error && typeof error === "object" && "message" in error
|
|
226
|
+
? (error.message as string)
|
|
227
|
+
: "Unknown error";
|
|
383
228
|
|
|
384
|
-
// Log the failed execution
|
|
385
229
|
await ctx.runMutation(component.gateway.logAccess, {
|
|
386
230
|
agentId: authResult.agentId,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
231
|
+
functionKey,
|
|
232
|
+
args: body.args ?? {},
|
|
233
|
+
error: errorMessage,
|
|
234
|
+
duration: Date.now() - startTime,
|
|
235
|
+
timestamp: Date.now(),
|
|
392
236
|
});
|
|
393
237
|
|
|
394
|
-
return jsonResponse({ error: errorMessage }, 500);
|
|
238
|
+
return jsonResponse({ success: false, error: errorMessage }, 500);
|
|
395
239
|
}
|
|
396
240
|
}),
|
|
397
241
|
});
|
|
398
242
|
|
|
399
|
-
// --- POST /agent-bridge/provision ---
|
|
400
|
-
// Agent self-provisioning endpoint
|
|
401
243
|
http.route({
|
|
402
|
-
path: `${prefix}/
|
|
403
|
-
method: "
|
|
404
|
-
handler: httpActionGeneric(async (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
244
|
+
path: `${prefix}/functions`,
|
|
245
|
+
method: "GET",
|
|
246
|
+
handler: httpActionGeneric(async () => {
|
|
247
|
+
const functions = availableFunctionKeys.map((functionKey) => ({
|
|
248
|
+
functionKey,
|
|
249
|
+
type: normalizedConfig.functions[functionKey].type,
|
|
250
|
+
metadata: normalizedConfig.functions[functionKey].metadata,
|
|
251
|
+
}));
|
|
252
|
+
return jsonResponse({ functions }, 200);
|
|
253
|
+
}),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
413
256
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
257
|
+
type PermissionRule = {
|
|
258
|
+
pattern: string;
|
|
259
|
+
permission: "allow" | "deny" | "rate_limited";
|
|
260
|
+
rateLimitConfig?: {
|
|
261
|
+
requestsPerHour: number;
|
|
262
|
+
tokenBudget?: number;
|
|
263
|
+
};
|
|
264
|
+
};
|
|
420
265
|
|
|
421
|
-
|
|
422
|
-
const result = await ctx.runMutation(
|
|
423
|
-
component.provisioning.provisionAgent,
|
|
424
|
-
{
|
|
425
|
-
provisioningToken,
|
|
426
|
-
appName: config.appName,
|
|
427
|
-
},
|
|
428
|
-
);
|
|
266
|
+
type MutationCtx = Pick<GenericMutationCtx<GenericDataModel>, "runMutation">;
|
|
429
267
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
268
|
+
export async function setAgentPermissions(
|
|
269
|
+
ctx: MutationCtx,
|
|
270
|
+
component: ComponentApi,
|
|
271
|
+
args: {
|
|
272
|
+
agentId: string;
|
|
273
|
+
rules: PermissionRule[];
|
|
274
|
+
config: AgentBridgeConfig;
|
|
275
|
+
},
|
|
276
|
+
) {
|
|
277
|
+
const availableFunctionKeys = Object.keys(args.config.functions);
|
|
278
|
+
return await ctx.runMutation(component.permissions.setAgentPermissions, {
|
|
279
|
+
agentId: args.agentId,
|
|
280
|
+
rules: args.rules,
|
|
281
|
+
availableFunctionKeys,
|
|
437
282
|
});
|
|
283
|
+
}
|
|
438
284
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
registeredFunctions: functions.length,
|
|
456
|
-
timestamp: Date.now(),
|
|
457
|
-
},
|
|
458
|
-
200,
|
|
459
|
-
);
|
|
460
|
-
} catch {
|
|
461
|
-
return jsonResponse(
|
|
462
|
-
{
|
|
463
|
-
status: "error",
|
|
464
|
-
appName: config.appName,
|
|
465
|
-
timestamp: Date.now(),
|
|
466
|
-
},
|
|
467
|
-
500,
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
}),
|
|
285
|
+
export async function setFunctionOverrides(
|
|
286
|
+
ctx: MutationCtx,
|
|
287
|
+
component: ComponentApi,
|
|
288
|
+
args: {
|
|
289
|
+
overrides: Array<{
|
|
290
|
+
key: string;
|
|
291
|
+
enabled: boolean;
|
|
292
|
+
globalRateLimit?: number;
|
|
293
|
+
}>;
|
|
294
|
+
config: AgentBridgeConfig;
|
|
295
|
+
},
|
|
296
|
+
) {
|
|
297
|
+
const availableFunctionKeys = Object.keys(args.config.functions);
|
|
298
|
+
return await ctx.runMutation(component.permissions.setFunctionOverrides, {
|
|
299
|
+
overrides: args.overrides,
|
|
300
|
+
availableFunctionKeys,
|
|
471
301
|
});
|
|
472
302
|
}
|
|
473
303
|
|
|
474
|
-
|
|
304
|
+
export function listConfiguredFunctions(config: AgentBridgeConfig) {
|
|
305
|
+
const normalizedConfig = normalizeAgentBridgeConfig(config);
|
|
306
|
+
return Object.entries(normalizedConfig.functions).map(
|
|
307
|
+
([functionKey, functionDef]) => ({
|
|
308
|
+
functionKey,
|
|
309
|
+
type: functionDef.type,
|
|
310
|
+
metadata: functionDef.metadata,
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
475
314
|
|
|
476
315
|
function jsonResponse(data: unknown, status: number): Response {
|
|
477
316
|
return new Response(JSON.stringify(data), {
|