@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.
Files changed (58) hide show
  1. package/README.md +96 -127
  2. package/dist/cli/init.d.ts +3 -0
  3. package/dist/cli/init.d.ts.map +1 -0
  4. package/dist/cli/init.js +100 -0
  5. package/dist/cli/init.js.map +1 -0
  6. package/dist/client/index.d.ts +50 -173
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +129 -263
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/component/_generated/api.d.ts +4 -4
  11. package/dist/component/_generated/api.d.ts.map +1 -1
  12. package/dist/component/_generated/component.d.ts +66 -162
  13. package/dist/component/_generated/component.d.ts.map +1 -1
  14. package/dist/component/agentBridgeUtils.d.ts +8 -0
  15. package/dist/component/agentBridgeUtils.d.ts.map +1 -0
  16. package/dist/component/agentBridgeUtils.js +33 -0
  17. package/dist/component/agentBridgeUtils.js.map +1 -0
  18. package/dist/component/agents.d.ts +27 -0
  19. package/dist/component/agents.d.ts.map +1 -0
  20. package/dist/component/agents.js +94 -0
  21. package/dist/component/agents.js.map +1 -0
  22. package/dist/component/gateway.d.ts +30 -44
  23. package/dist/component/gateway.d.ts.map +1 -1
  24. package/dist/component/gateway.js +127 -132
  25. package/dist/component/gateway.js.map +1 -1
  26. package/dist/component/permissions.d.ts +30 -84
  27. package/dist/component/permissions.d.ts.map +1 -1
  28. package/dist/component/permissions.js +80 -203
  29. package/dist/component/permissions.js.map +1 -1
  30. package/dist/component/schema.d.ts +55 -153
  31. package/dist/component/schema.d.ts.map +1 -1
  32. package/dist/component/schema.js +30 -80
  33. package/dist/component/schema.js.map +1 -1
  34. package/dist/react/index.d.ts +2 -2
  35. package/dist/react/index.d.ts.map +1 -1
  36. package/dist/react/index.js +2 -3
  37. package/dist/react/index.js.map +1 -1
  38. package/package.json +7 -3
  39. package/src/cli/init.ts +116 -0
  40. package/src/client/index.ts +228 -389
  41. package/src/component/_generated/api.ts +4 -4
  42. package/src/component/_generated/component.ts +79 -195
  43. package/src/component/agentBridgeUtils.ts +52 -0
  44. package/src/component/agents.ts +106 -0
  45. package/src/component/gateway.ts +149 -163
  46. package/src/component/permissions.ts +89 -259
  47. package/src/component/schema.ts +31 -96
  48. package/src/react/index.ts +5 -6
  49. package/dist/component/provisioning.d.ts +0 -87
  50. package/dist/component/provisioning.d.ts.map +0 -1
  51. package/dist/component/provisioning.js +0 -343
  52. package/dist/component/provisioning.js.map +0 -1
  53. package/dist/component/registry.d.ts +0 -46
  54. package/dist/component/registry.d.ts.map +0 -1
  55. package/dist/component/registry.js +0 -121
  56. package/dist/component/registry.js.map +0 -1
  57. package/src/component/provisioning.ts +0 -402
  58. package/src/component/registry.ts +0 -152
@@ -1,477 +1,316 @@
1
1
  import { httpActionGeneric } from "convex/server";
2
2
  import type {
3
- GenericActionCtx,
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
- // Convenient context types with minimal required capabilities.
13
- type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
14
- type MutationCtx = Pick<
15
- GenericMutationCtx<GenericDataModel>,
16
- "runQuery" | "runMutation"
17
- >;
18
- type ActionCtx = Pick<
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 DefaultPermission {
37
- pattern: string;
38
- permission: "allow" | "deny" | "rate_limited";
39
- rateLimitConfig?: {
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
- /** Unique name identifying this app (e.g. "okr", "hr", "incentives") */
47
- appName: string;
48
- /** Default permissions applied to new agents during provisioning */
49
- defaultPermissions?: DefaultPermission[];
26
+ functions: Record<
27
+ string,
28
+ UnknownFunctionReference | AgentBridgeFunctionDefinition
29
+ >;
30
+ metadata?: Record<string, AgentBridgeFunctionMetadata>;
50
31
  }
51
32
 
52
- // --- AgentBridge Client Class ---
53
-
54
- /**
55
- * Client class that wraps component calls for ergonomic use in the host app.
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
- * Initialize the component configuration.
80
- * Should be called once during app setup (e.g. in a seed/setup mutation).
81
- */
82
- async configure(ctx: MutationCtx): Promise<void> {
83
- await ctx.runMutation(this.component.provisioning.configure, {
84
- appName: this.appName,
85
- defaultPermissions: this.defaultPermissions,
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
- * Register a function that agents can call.
91
- * The host app must create the function handle via createFunctionHandle().
92
- */
93
- async registerFunction(ctx: MutationCtx, fn: FunctionDef): Promise<string> {
94
- return await ctx.runMutation(this.component.registry.register, {
95
- appName: this.appName,
96
- functionName: fn.name,
97
- functionHandle: fn.handle,
98
- functionType: fn.type,
99
- description: fn.description,
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
- * Register multiple functions in bulk.
105
- */
106
- async registerFunctions(
107
- ctx: MutationCtx,
108
- functions: FunctionDef[],
109
- ): Promise<string[]> {
110
- const ids: string[] = [];
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
- * List all registered agents.
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
- * Revoke a specific app instance for an agent.
181
- */
182
- async revokeAppInstance(
183
- ctx: MutationCtx,
184
- opts: { agentId: string },
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
- * Set a permission for an agent.
197
- */
198
- async setPermission(
199
- ctx: MutationCtx,
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
- * Remove a permission for an agent.
220
- */
221
- async removePermission(
222
- ctx: MutationCtx,
223
- opts: { agentId: string; functionPattern: string },
224
- ): Promise<boolean> {
225
- return await ctx.runMutation(this.component.permissions.removePermission, {
226
- agentId: opts.agentId,
227
- appName: this.appName,
228
- functionPattern: opts.functionPattern,
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
- * List permissions for an agent on this app.
234
- */
235
- async listPermissions(ctx: QueryCtx, agentId: string) {
236
- return await ctx.runQuery(this.component.permissions.listPermissions, {
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
- // --- HTTP Route Registration ---
258
-
259
- /**
260
- * Register HTTP routes for the agent bridge component.
261
- * This exposes endpoints that OpenClaw agents call to execute functions,
262
- * provision themselves, and check health.
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
- config: { appName: string; pathPrefix?: string },
130
+ bridgeConfig: AgentBridgeConfig,
131
+ options?: { pathPrefix?: string },
281
132
  ) {
282
- const prefix = config.pathPrefix ?? "/agent-bridge";
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
- let body: {
291
- instanceToken?: string;
292
- functionName?: string;
293
- args?: Record<string, unknown>;
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({ error: "Invalid JSON body" }, 400);
150
+ return jsonResponse(
151
+ { success: false, error: "Invalid JSON body" },
152
+ 400,
153
+ );
299
154
  }
300
155
 
301
- const { instanceToken, functionName, args } = body;
302
-
303
- if (!instanceToken || !functionName) {
156
+ const functionKey = body.functionKey?.trim();
157
+ if (!functionKey) {
304
158
  return jsonResponse(
305
- { error: "Missing required fields: instanceToken, functionName" },
159
+ { success: false, error: "Missing required field: functionKey" },
306
160
  400,
307
161
  );
308
162
  }
309
163
 
310
- // Step 1: Authorize the request (mutation -- validates token, checks permissions,
311
- // updates counters, returns function handle)
312
- const authResult = await ctx.runMutation(
313
- component.gateway.authorizeRequest,
314
- {
315
- instanceToken,
316
- functionName,
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
- if (!authResult.authorized) {
322
- const detailSuffix =
323
- authResult.matchedPattern && authResult.matchedPermission
324
- ? ` (matchedPattern="${authResult.matchedPattern}", permission="${authResult.matchedPermission}")`
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
- return jsonResponse(
336
- { error: authResult.error },
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
- switch (authResult.functionType) {
347
- case "query":
348
- result = await ctx.runQuery(
349
- authResult.functionHandle as FunctionHandle<"query">,
350
- args ?? {},
351
- );
352
- break;
353
- case "mutation":
354
- result = await ctx.runMutation(
355
- authResult.functionHandle as FunctionHandle<"mutation">,
356
- args ?? {},
357
- );
358
- break;
359
- case "action":
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
- appName: config.appName,
373
- functionCalled: functionName,
374
- permission: "allow",
375
- durationMs,
215
+ functionKey,
216
+ args,
217
+ result,
218
+ duration: Date.now() - startTime,
219
+ timestamp: Date.now(),
376
220
  });
377
221
 
378
- return jsonResponse({ result, durationMs }, 200);
222
+ return jsonResponse({ success: true, result }, 200);
379
223
  } catch (error: unknown) {
380
- const durationMs = Date.now() - startTime;
381
224
  const errorMessage =
382
- error instanceof Error ? error.message : "Unknown 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
- appName: config.appName,
388
- functionCalled: functionName,
389
- permission: "allow",
390
- errorMessage,
391
- durationMs,
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}/provision`,
403
- method: "POST",
404
- handler: httpActionGeneric(async (ctx, request) => {
405
- let body: { provisioningToken?: string };
406
- try {
407
- body = await request.json();
408
- } catch {
409
- return jsonResponse({ error: "Invalid JSON body" }, 400);
410
- }
411
-
412
- const { provisioningToken } = body;
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
- if (!provisioningToken) {
415
- return jsonResponse(
416
- { error: "Missing required field: provisioningToken" },
417
- 400,
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
- try {
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
- return jsonResponse(result, 200);
431
- } catch (error: unknown) {
432
- const errorMessage =
433
- error instanceof Error ? error.message : "Provisioning failed";
434
- return jsonResponse({ error: errorMessage }, 400);
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
- // --- GET /agent-bridge/health ---
440
- // Health check endpoint
441
- http.route({
442
- path: `${prefix}/health`,
443
- method: "GET",
444
- handler: httpActionGeneric(async (ctx) => {
445
- try {
446
- const functions = await ctx.runQuery(
447
- component.registry.listFunctions,
448
- { appName: config.appName },
449
- );
450
-
451
- return jsonResponse(
452
- {
453
- status: "ok",
454
- appName: config.appName,
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
- // --- Helper ---
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), {