@slashfi/agents-sdk 0.11.1 → 0.12.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/dist/agent-definitions/auth.d.ts +4 -1
- package/dist/agent-definitions/auth.d.ts.map +1 -1
- package/dist/agent-definitions/auth.js +48 -3
- package/dist/agent-definitions/auth.js.map +1 -1
- package/dist/agent-definitions/integrations.d.ts +2 -14
- package/dist/agent-definitions/integrations.d.ts.map +1 -1
- package/dist/agent-definitions/integrations.js +46 -17
- package/dist/agent-definitions/integrations.js.map +1 -1
- package/dist/agent-definitions/remote-registry.d.ts +19 -14
- package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
- package/dist/agent-definitions/remote-registry.js +207 -381
- package/dist/agent-definitions/remote-registry.js.map +1 -1
- package/dist/agent-definitions/users.d.ts.map +1 -1
- package/dist/agent-definitions/users.js +29 -1
- package/dist/agent-definitions/users.js.map +1 -1
- package/dist/define.d.ts +6 -4
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +82 -3
- package/dist/define.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jwt.js +1 -1
- package/dist/jwt.js.map +1 -1
- package/dist/server.d.ts +42 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +223 -62
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +53 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agent-definitions/auth.ts +57 -5
- package/src/agent-definitions/integrations.ts +51 -26
- package/src/agent-definitions/remote-registry.ts +210 -513
- package/src/agent-definitions/users.ts +35 -1
- package/src/define.ts +98 -6
- package/src/index.ts +2 -1
- package/src/jwt.ts +1 -1
- package/src/server.test.ts +284 -0
- package/src/server.ts +331 -75
- package/src/types.ts +44 -1
package/src/server.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from "./agent-definitions/secrets.js";
|
|
33
33
|
import { verifyJwt } from "./jwt.js";
|
|
34
34
|
import type { SigningKey } from "./jwt.js";
|
|
35
|
-
import { generateSigningKey, importSigningKey, exportSigningKey, buildJwks, verifyJwtLocal, verifyJwtFromIssuer } from "./jwt.js";
|
|
35
|
+
import { generateSigningKey, importSigningKey, exportSigningKey, buildJwks, verifyJwtLocal, verifyJwtFromIssuer, signJwtES256 } from "./jwt.js";
|
|
36
36
|
import type { AgentRegistry } from "./registry.js";
|
|
37
37
|
import type { AgentDefinition, CallAgentRequest, Visibility } from "./types.js";
|
|
38
38
|
|
|
@@ -48,6 +48,37 @@ export interface TrustedIssuer {
|
|
|
48
48
|
scopes: string[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/** OAuth identity provider for /oauth/authorize + /oauth/callback flows */
|
|
54
|
+
export interface OAuthIdentityProvider {
|
|
55
|
+
/**
|
|
56
|
+
* Handle /oauth/authorize — redirect the user to an external IdP.
|
|
57
|
+
* Return a Response (typically a 302 redirect).
|
|
58
|
+
*/
|
|
59
|
+
authorize(req: Request, params: {
|
|
60
|
+
/** The verified JWT from the foreign registry */
|
|
61
|
+
token: string;
|
|
62
|
+
/** Claims from the verified JWT */
|
|
63
|
+
claims: Record<string, unknown>;
|
|
64
|
+
/** Where to redirect after completion */
|
|
65
|
+
redirectUri: string;
|
|
66
|
+
/** Base URL of this server */
|
|
67
|
+
baseUrl: string;
|
|
68
|
+
/** OAuth scope (e.g. "setup" for tenant creation flow) */
|
|
69
|
+
scope?: string;
|
|
70
|
+
}): Promise<Response>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle /oauth/callback — process the IdP response.
|
|
74
|
+
* Should link the foreign identity to a local user and redirect back.
|
|
75
|
+
* Return a Response (typically a 302 redirect to redirectUri).
|
|
76
|
+
*/
|
|
77
|
+
callback(req: Request, params: {
|
|
78
|
+
/** Base URL of this server */
|
|
79
|
+
baseUrl: string;
|
|
80
|
+
}): Promise<Response>;
|
|
81
|
+
}
|
|
51
82
|
export interface AgentServerOptions {
|
|
52
83
|
/** Port to listen on (default: 3000) */
|
|
53
84
|
port?: number;
|
|
@@ -67,9 +98,13 @@ export interface AgentServerOptions {
|
|
|
67
98
|
trustedIssuers?: (TrustedIssuer | string)[];
|
|
68
99
|
/** Pre-generated signing key (if not provided, one is generated on start) */
|
|
69
100
|
signingKey?: SigningKey;
|
|
101
|
+
/** OAuth identity provider for cross-registry user linking */
|
|
102
|
+
oauthIdentityProvider?: OAuthIdentityProvider;
|
|
70
103
|
}
|
|
71
104
|
|
|
72
105
|
export interface AgentServer {
|
|
106
|
+
/** Initialize signing keys without starting HTTP server */
|
|
107
|
+
initKeys(): Promise<void>;
|
|
73
108
|
/** Start the server */
|
|
74
109
|
start(): Promise<void>;
|
|
75
110
|
/** Stop the server */
|
|
@@ -80,6 +115,10 @@ export interface AgentServer {
|
|
|
80
115
|
url: string | null;
|
|
81
116
|
/** The agent registry this server uses */
|
|
82
117
|
registry: AgentRegistry;
|
|
118
|
+
/** Sign a JWT with the server's signing key (for outbound calls) */
|
|
119
|
+
signJwt(claims: Record<string, unknown>): Promise<string>;
|
|
120
|
+
/** Dynamically add a trusted JWT issuer at runtime */
|
|
121
|
+
addTrustedIssuer(issuerUrl: string, scopes?: string[]): void;
|
|
83
122
|
}
|
|
84
123
|
|
|
85
124
|
// ============================================
|
|
@@ -105,12 +144,13 @@ interface JsonRpcResponse {
|
|
|
105
144
|
// ============================================
|
|
106
145
|
|
|
107
146
|
export interface AuthConfig {
|
|
108
|
-
store
|
|
109
|
-
rootKey
|
|
110
|
-
tokenTtl
|
|
147
|
+
store?: AuthStore;
|
|
148
|
+
rootKey?: string;
|
|
149
|
+
tokenTtl?: number;
|
|
111
150
|
}
|
|
112
151
|
|
|
113
152
|
export interface ResolvedAuth {
|
|
153
|
+
issuer?: string;
|
|
114
154
|
callerId: string;
|
|
115
155
|
callerType: "agent" | "user" | "system";
|
|
116
156
|
scopes: string[];
|
|
@@ -184,7 +224,7 @@ function mcpResult(value: unknown, isError = false) {
|
|
|
184
224
|
// Auth Detection & Resolution
|
|
185
225
|
// ============================================
|
|
186
226
|
|
|
187
|
-
export function detectAuth(registry: AgentRegistry): AuthConfig
|
|
227
|
+
export function detectAuth(registry: AgentRegistry): AuthConfig {
|
|
188
228
|
const authAgent = registry.get("@auth") as
|
|
189
229
|
| (AgentDefinition & {
|
|
190
230
|
__authStore?: AuthStore;
|
|
@@ -193,7 +233,7 @@ export function detectAuth(registry: AgentRegistry): AuthConfig | null {
|
|
|
193
233
|
})
|
|
194
234
|
| undefined;
|
|
195
235
|
|
|
196
|
-
if (!authAgent?.__authStore || !authAgent.__rootKey) return
|
|
236
|
+
if (!authAgent?.__authStore || !authAgent.__rootKey) return {};
|
|
197
237
|
|
|
198
238
|
return {
|
|
199
239
|
store: authAgent.__authStore,
|
|
@@ -204,7 +244,7 @@ export function detectAuth(registry: AgentRegistry): AuthConfig | null {
|
|
|
204
244
|
|
|
205
245
|
export async function resolveAuth(
|
|
206
246
|
req: Request,
|
|
207
|
-
authConfig: AuthConfig
|
|
247
|
+
authConfig: AuthConfig,
|
|
208
248
|
jwksOptions?: { signingKeys?: SigningKey[]; trustedIssuers?: TrustedIssuer[] },
|
|
209
249
|
): Promise<ResolvedAuth | null> {
|
|
210
250
|
const authHeader = req.headers.get("Authorization");
|
|
@@ -214,7 +254,7 @@ export async function resolveAuth(
|
|
|
214
254
|
if (scheme?.toLowerCase() !== "bearer" || !credential) return null;
|
|
215
255
|
|
|
216
256
|
// Root key check
|
|
217
|
-
if (authConfig && credential === authConfig.rootKey) {
|
|
257
|
+
if (authConfig.rootKey && credential === authConfig.rootKey) {
|
|
218
258
|
return {
|
|
219
259
|
callerId: "root",
|
|
220
260
|
callerType: "system",
|
|
@@ -285,7 +325,7 @@ export async function resolveAuth(
|
|
|
285
325
|
exp?: number;
|
|
286
326
|
};
|
|
287
327
|
|
|
288
|
-
if (payload.sub && authConfig) {
|
|
328
|
+
if (payload.sub && authConfig.store) {
|
|
289
329
|
const client = await authConfig.store.getClient(payload.sub);
|
|
290
330
|
if (client) {
|
|
291
331
|
const verified = await verifyJwt(credential, client.clientSecretHash);
|
|
@@ -305,7 +345,7 @@ export async function resolveAuth(
|
|
|
305
345
|
}
|
|
306
346
|
|
|
307
347
|
// Legacy: opaque token validation (backwards compat)
|
|
308
|
-
if (!authConfig) return null;
|
|
348
|
+
if (!authConfig.store) return null;
|
|
309
349
|
const token = await authConfig.store.validateToken(credential);
|
|
310
350
|
if (!token) return null;
|
|
311
351
|
|
|
@@ -401,6 +441,7 @@ export function createAgentServer(
|
|
|
401
441
|
serverName = "agents-sdk",
|
|
402
442
|
serverVersion = "1.0.0",
|
|
403
443
|
secretStore,
|
|
444
|
+
oauthIdentityProvider,
|
|
404
445
|
} = options;
|
|
405
446
|
|
|
406
447
|
// Signing keys for JWKS-based auth
|
|
@@ -487,6 +528,7 @@ export function createAgentServer(
|
|
|
487
528
|
if (!req.metadata) req.metadata = {};
|
|
488
529
|
req.metadata.scopes = auth.scopes;
|
|
489
530
|
req.metadata.isRoot = auth.isRoot;
|
|
531
|
+
if (auth.issuer) req.metadata.issuer = auth.issuer;
|
|
490
532
|
}
|
|
491
533
|
if (auth?.isRoot) {
|
|
492
534
|
req.callerType = "system";
|
|
@@ -552,73 +594,193 @@ export function createAgentServer(
|
|
|
552
594
|
}
|
|
553
595
|
|
|
554
596
|
const contentType = req.headers.get("Content-Type") ?? "";
|
|
555
|
-
let
|
|
556
|
-
let clientId: string;
|
|
557
|
-
let clientSecret: string;
|
|
597
|
+
let params: Record<string, string>;
|
|
558
598
|
|
|
559
599
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
560
600
|
const body = await req.text();
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
clientId = params.get("client_id") ?? "";
|
|
564
|
-
clientSecret = params.get("client_secret") ?? "";
|
|
601
|
+
const urlParams = new URLSearchParams(body);
|
|
602
|
+
params = Object.fromEntries(urlParams.entries());
|
|
565
603
|
} else {
|
|
566
|
-
|
|
567
|
-
grantType = body.grant_type ?? "";
|
|
568
|
-
clientId = body.client_id ?? "";
|
|
569
|
-
clientSecret = body.client_secret ?? "";
|
|
604
|
+
params = (await req.json()) as Record<string, string>;
|
|
570
605
|
}
|
|
571
606
|
|
|
572
|
-
|
|
573
|
-
return jsonResponse(
|
|
574
|
-
{
|
|
575
|
-
error: "unsupported_grant_type",
|
|
576
|
-
error_description: "Only client_credentials is supported",
|
|
577
|
-
},
|
|
578
|
-
400,
|
|
579
|
-
);
|
|
580
|
-
}
|
|
607
|
+
const grantType = params.grant_type ?? "";
|
|
581
608
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
609
|
+
// ── jwt_exchange grant: verify foreign JWT, resolve local identity ──
|
|
610
|
+
if (grantType === "jwt_exchange") {
|
|
611
|
+
const assertion = params.assertion ?? "";
|
|
612
|
+
if (!assertion) {
|
|
613
|
+
return jsonResponse(
|
|
614
|
+
{ error: "invalid_request", error_description: "Missing assertion parameter" },
|
|
615
|
+
400,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
try {
|
|
620
|
+
const result = await registry.call({
|
|
621
|
+
action: "execute_tool",
|
|
622
|
+
path: "@auth",
|
|
623
|
+
tool: "exchange_token",
|
|
624
|
+
params: { token: assertion },
|
|
625
|
+
callerType: "system",
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const exchangeResult = (result as any)?.result;
|
|
629
|
+
// If the tool call failed, forward the error
|
|
630
|
+
if ((result as any)?.success === false) {
|
|
631
|
+
return jsonResponse(
|
|
632
|
+
{ error: "server_error", error_description: (result as any)?.error ?? "Exchange tool failed", raw: JSON.stringify(result)?.slice(0, 300) },
|
|
633
|
+
500,
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ── Reverse registration: if caller is an agent-registry, auto-store connection ──
|
|
638
|
+
try {
|
|
639
|
+
const assertionParts = assertion.split(".");
|
|
640
|
+
if (assertionParts.length === 3) {
|
|
641
|
+
const assertionPayload = JSON.parse(atob(assertionParts[1].replace(/-/g, "+").replace(/_/g, "/"))) as any;
|
|
642
|
+
if (assertionPayload.type === "agent-registry" && assertionPayload.iss) {
|
|
643
|
+
// Find or create @remote-registry agent and store the reverse connection
|
|
644
|
+
const rrAgent = registry.get("@remote-registry") ?? registry.get("/agents/@remote-registry");
|
|
645
|
+
if (rrAgent) {
|
|
646
|
+
const setupTool = (rrAgent as any).tools?.find((t: any) => t.name === "setup_integration");
|
|
647
|
+
if (setupTool?.execute) {
|
|
648
|
+
try {
|
|
649
|
+
await setupTool.execute(
|
|
650
|
+
{ url: assertionPayload.iss, name: assertionPayload.name ?? "remote-registry" },
|
|
651
|
+
{ callerId: "system", callerType: "system", tenantId: "default", agentPath: "@remote-registry" },
|
|
652
|
+
);
|
|
653
|
+
console.error(`[jwt_exchange] Reverse connection stored for ${assertionPayload.iss}`);
|
|
654
|
+
} catch (setupErr) {
|
|
655
|
+
console.error(`[jwt_exchange] Reverse registration setup failed:`, setupErr);
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
console.error("[jwt_exchange] @remote-registry has no setup_integration tool — reverse registration skipped");
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
console.error("[jwt_exchange] @remote-registry agent not found — reverse registration skipped");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
} catch (reverseErr) {
|
|
666
|
+
console.error("[jwt_exchange] Reverse registration check failed:", reverseErr);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (!exchangeResult) {
|
|
670
|
+
return jsonResponse(
|
|
671
|
+
{ error: "server_error", error_description: `Exchange returned null: ${JSON.stringify(result)?.slice(0, 300)}` },
|
|
672
|
+
500,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// User not linked yet — needs OAuth identity linking
|
|
677
|
+
if (exchangeResult.needsAuth) {
|
|
678
|
+
const baseUrl = new URL(req.url).origin;
|
|
679
|
+
const authorizeUrl = new URL(`${baseUrl}${basePath}/oauth/authorize`);
|
|
680
|
+
authorizeUrl.searchParams.set("token", assertion);
|
|
681
|
+
if (params.redirect_uri) {
|
|
682
|
+
authorizeUrl.searchParams.set("redirect_uri", params.redirect_uri);
|
|
683
|
+
}
|
|
684
|
+
if (params.scope) {
|
|
685
|
+
authorizeUrl.searchParams.set("scope", params.scope);
|
|
686
|
+
}
|
|
687
|
+
return jsonResponse(
|
|
688
|
+
{
|
|
689
|
+
error: "identity_required",
|
|
690
|
+
error_description: "User identity not linked. Redirect to authorize_url to complete linking.",
|
|
691
|
+
authorize_url: authorizeUrl.toString(),
|
|
692
|
+
tenant_id: exchangeResult.tenantId,
|
|
693
|
+
},
|
|
694
|
+
403,
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// User found — sign a local access token
|
|
699
|
+
if (exchangeResult.userId && serverSigningKeys.length > 0) {
|
|
700
|
+
const sigKey = serverSigningKeys[0];
|
|
701
|
+
const token = await signJwtES256(
|
|
702
|
+
{
|
|
703
|
+
sub: exchangeResult.userId,
|
|
704
|
+
name: exchangeResult.userId,
|
|
705
|
+
scopes: ["*"],
|
|
706
|
+
tenantId: exchangeResult.tenantId,
|
|
707
|
+
},
|
|
708
|
+
sigKey.privateKey,
|
|
709
|
+
sigKey.kid,
|
|
710
|
+
new URL(req.url).origin,
|
|
711
|
+
`${authConfig.tokenTtl ?? 3600}s`,
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
return jsonResponse({
|
|
715
|
+
access_token: token,
|
|
716
|
+
token_type: "Bearer",
|
|
717
|
+
expires_in: authConfig.tokenTtl ?? 3600,
|
|
718
|
+
user_id: exchangeResult.userId,
|
|
719
|
+
tenant_id: exchangeResult.tenantId,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return jsonResponse(exchangeResult);
|
|
724
|
+
} catch (err) {
|
|
725
|
+
console.error("[oauth] JWT exchange error:", err);
|
|
726
|
+
return jsonResponse(
|
|
727
|
+
{ error: "server_error", error_description: `JWT exchange failed: ${err instanceof Error ? err.message : String(err)}` },
|
|
728
|
+
500,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
590
731
|
}
|
|
591
732
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
tool: "token",
|
|
597
|
-
params: { clientId, clientSecret },
|
|
598
|
-
callerType: "system",
|
|
599
|
-
});
|
|
733
|
+
// ── client_credentials grant ──
|
|
734
|
+
if (grantType === "client_credentials") {
|
|
735
|
+
const clientId = params.client_id ?? "";
|
|
736
|
+
const clientSecret = params.client_secret ?? "";
|
|
600
737
|
|
|
601
|
-
|
|
602
|
-
if (!tokenResult?.accessToken) {
|
|
738
|
+
if (!clientId || !clientSecret) {
|
|
603
739
|
return jsonResponse(
|
|
604
|
-
{ error: "
|
|
605
|
-
|
|
740
|
+
{ error: "invalid_request", error_description: "Missing client_id or client_secret" },
|
|
741
|
+
400,
|
|
606
742
|
);
|
|
607
743
|
}
|
|
608
744
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
745
|
+
try {
|
|
746
|
+
const result = await registry.call({
|
|
747
|
+
action: "execute_tool",
|
|
748
|
+
path: "@auth",
|
|
749
|
+
tool: "token",
|
|
750
|
+
params: { clientId, clientSecret },
|
|
751
|
+
callerType: "system",
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const tokenResult = (result as any)?.result;
|
|
755
|
+
if (!tokenResult?.accessToken) {
|
|
756
|
+
return jsonResponse(
|
|
757
|
+
{ error: "invalid_client", error_description: "Authentication failed" },
|
|
758
|
+
401,
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return jsonResponse({
|
|
763
|
+
access_token: tokenResult.accessToken,
|
|
764
|
+
token_type: "Bearer",
|
|
765
|
+
expires_in: tokenResult.expiresIn ?? authConfig.tokenTtl,
|
|
766
|
+
refresh_token: tokenResult.refreshToken,
|
|
767
|
+
});
|
|
768
|
+
} catch (err) {
|
|
769
|
+
console.error("[oauth] Token error:", err);
|
|
770
|
+
return jsonResponse(
|
|
771
|
+
{ error: "server_error", error_description: "Token exchange failed" },
|
|
772
|
+
500,
|
|
773
|
+
);
|
|
774
|
+
}
|
|
621
775
|
}
|
|
776
|
+
|
|
777
|
+
return jsonResponse(
|
|
778
|
+
{
|
|
779
|
+
error: "unsupported_grant_type",
|
|
780
|
+
error_description: "Supported grant types: client_credentials, jwt_exchange",
|
|
781
|
+
},
|
|
782
|
+
400,
|
|
783
|
+
);
|
|
622
784
|
}
|
|
623
785
|
|
|
624
786
|
// ──────────────────────────────────────────
|
|
@@ -636,13 +798,10 @@ export function createAgentServer(
|
|
|
636
798
|
}
|
|
637
799
|
|
|
638
800
|
// Resolve auth for all requests
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
trustedIssuers: configTrustedIssuers,
|
|
644
|
-
})
|
|
645
|
-
: null;
|
|
801
|
+
const auth = await resolveAuth(req, authConfig, {
|
|
802
|
+
signingKeys: serverSigningKeys,
|
|
803
|
+
trustedIssuers: configTrustedIssuers,
|
|
804
|
+
});
|
|
646
805
|
|
|
647
806
|
// Also check header-based identity (for proxied requests)
|
|
648
807
|
const headerAuth: ResolvedAuth | null = !auth
|
|
@@ -670,12 +829,81 @@ export function createAgentServer(
|
|
|
670
829
|
return cors ? addCors(jsonResponse(result)) : jsonResponse(result);
|
|
671
830
|
}
|
|
672
831
|
|
|
673
|
-
// ── POST /oauth/token → OAuth2
|
|
832
|
+
// ── POST /oauth/token → OAuth2 token exchange ──
|
|
674
833
|
if (path === "/oauth/token" && req.method === "POST") {
|
|
675
834
|
const res = await handleOAuthToken(req);
|
|
676
835
|
return cors ? addCors(res) : res;
|
|
677
836
|
}
|
|
678
837
|
|
|
838
|
+
// ── GET /oauth/authorize → Identity linking redirect (browser flow) ──
|
|
839
|
+
if (path === "/oauth/authorize" && req.method === "GET") {
|
|
840
|
+
if (!oauthIdentityProvider) {
|
|
841
|
+
const res = jsonResponse(
|
|
842
|
+
{ error: "not_configured", error_description: "No OAuth identity provider configured" },
|
|
843
|
+
404,
|
|
844
|
+
);
|
|
845
|
+
return cors ? addCors(res) : res;
|
|
846
|
+
}
|
|
847
|
+
const url = new URL(req.url);
|
|
848
|
+
const token = url.searchParams.get("token") ?? "";
|
|
849
|
+
const redirectUri = url.searchParams.get("redirect_uri") ?? "";
|
|
850
|
+
|
|
851
|
+
if (!token) {
|
|
852
|
+
const res = jsonResponse(
|
|
853
|
+
{ error: "invalid_request", error_description: "Missing token parameter" },
|
|
854
|
+
400,
|
|
855
|
+
);
|
|
856
|
+
return cors ? addCors(res) : res;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Verify the JWT against trusted issuers
|
|
860
|
+
let claims: Record<string, unknown> | null = null;
|
|
861
|
+
const issuerUrls = configTrustedIssuers.map(i => typeof i === "string" ? i : i.issuer);
|
|
862
|
+
for (const issuerUrl of issuerUrls) {
|
|
863
|
+
try {
|
|
864
|
+
const result = await verifyJwtFromIssuer(token, issuerUrl);
|
|
865
|
+
if (result) {
|
|
866
|
+
claims = result as unknown as Record<string, unknown>;
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
} catch { /* try next issuer */ }
|
|
870
|
+
}
|
|
871
|
+
if (!claims) {
|
|
872
|
+
const res = jsonResponse(
|
|
873
|
+
{ error: "invalid_token", error_description: "JWT verification failed against all trusted issuers" },
|
|
874
|
+
401,
|
|
875
|
+
);
|
|
876
|
+
return cors ? addCors(res) : res;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const baseUrl = new URL(req.url).origin;
|
|
880
|
+
const scope = url.searchParams.get("scope") ?? undefined;
|
|
881
|
+
const res = await oauthIdentityProvider.authorize(req, {
|
|
882
|
+
token,
|
|
883
|
+
claims,
|
|
884
|
+
redirectUri,
|
|
885
|
+
baseUrl: baseUrl + basePath,
|
|
886
|
+
scope,
|
|
887
|
+
});
|
|
888
|
+
return cors ? addCors(res) : res;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// ── GET /oauth/callback → Identity linking callback ──
|
|
892
|
+
if (path === "/oauth/callback" && req.method === "GET") {
|
|
893
|
+
if (!oauthIdentityProvider) {
|
|
894
|
+
const res = jsonResponse(
|
|
895
|
+
{ error: "not_configured", error_description: "No OAuth identity provider configured" },
|
|
896
|
+
404,
|
|
897
|
+
);
|
|
898
|
+
return cors ? addCors(res) : res;
|
|
899
|
+
}
|
|
900
|
+
const baseUrl = new URL(req.url).origin;
|
|
901
|
+
const res = await oauthIdentityProvider.callback(req, {
|
|
902
|
+
baseUrl: baseUrl + basePath,
|
|
903
|
+
});
|
|
904
|
+
return cors ? addCors(res) : res;
|
|
905
|
+
}
|
|
906
|
+
|
|
679
907
|
// ── GET /health → Health check ──
|
|
680
908
|
if (path === "/health" && req.method === "GET") {
|
|
681
909
|
const res = jsonResponse({ status: "ok", agents: registry.listPaths() });
|
|
@@ -700,7 +928,8 @@ export function createAgentServer(
|
|
|
700
928
|
token_endpoint: `${baseUrl}/oauth/token`,
|
|
701
929
|
agents_endpoint: `${baseUrl}/list`,
|
|
702
930
|
call_endpoint: baseUrl,
|
|
703
|
-
supported_grant_types: ["client_credentials"],
|
|
931
|
+
supported_grant_types: ["client_credentials", "jwt_exchange"],
|
|
932
|
+
authorization_endpoint: `${baseUrl}/oauth/authorize`,
|
|
704
933
|
agents: registry.listPaths(),
|
|
705
934
|
});
|
|
706
935
|
return cors ? addCors(res) : res;
|
|
@@ -772,11 +1001,11 @@ export function createAgentServer(
|
|
|
772
1001
|
url: null,
|
|
773
1002
|
registry,
|
|
774
1003
|
|
|
775
|
-
async
|
|
776
|
-
// Load or generate signing
|
|
777
|
-
if (options.signingKey) {
|
|
1004
|
+
async initKeys() {
|
|
1005
|
+
// Load or generate signing keys (without starting Bun.serve)
|
|
1006
|
+
if (options.signingKey && serverSigningKeys.length === 0) {
|
|
778
1007
|
serverSigningKeys.push(options.signingKey);
|
|
779
|
-
} else if (authConfig?.store?.getSigningKeys) {
|
|
1008
|
+
} else if (authConfig?.store?.getSigningKeys && serverSigningKeys.length === 0) {
|
|
780
1009
|
const stored = await authConfig.store.getSigningKeys() ?? [];
|
|
781
1010
|
for (const exported of stored) {
|
|
782
1011
|
serverSigningKeys.push(await importSigningKey(exported));
|
|
@@ -789,6 +1018,10 @@ export function createAgentServer(
|
|
|
789
1018
|
await authConfig.store.storeSigningKey(await exportSigningKey(key));
|
|
790
1019
|
}
|
|
791
1020
|
}
|
|
1021
|
+
},
|
|
1022
|
+
|
|
1023
|
+
async start() {
|
|
1024
|
+
await this.initKeys();
|
|
792
1025
|
|
|
793
1026
|
serverInstance = Bun.serve({
|
|
794
1027
|
port,
|
|
@@ -808,5 +1041,28 @@ export function createAgentServer(
|
|
|
808
1041
|
},
|
|
809
1042
|
|
|
810
1043
|
fetch,
|
|
1044
|
+
|
|
1045
|
+
async signJwt(claims: Record<string, unknown>): Promise<string> {
|
|
1046
|
+
if (serverSigningKeys.length === 0) {
|
|
1047
|
+
throw new Error('No signing keys available. Call start() or initKeys() first.');
|
|
1048
|
+
}
|
|
1049
|
+
const key = serverSigningKeys[0];
|
|
1050
|
+
return signJwtES256(
|
|
1051
|
+
{ sub: 'system', name: 'atlas-os', scopes: ['*'], ...claims } as any,
|
|
1052
|
+
key.privateKey,
|
|
1053
|
+
key.kid,
|
|
1054
|
+
options.serverName ?? 'agents-sdk',
|
|
1055
|
+
'1h',
|
|
1056
|
+
);
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
addTrustedIssuer(issuerUrl: string, scopes?: string[]): void {
|
|
1060
|
+
// Avoid duplicates
|
|
1061
|
+
const existing = configTrustedIssuers.find(i => i.issuer === issuerUrl);
|
|
1062
|
+
if (!existing) {
|
|
1063
|
+
configTrustedIssuers.push({ issuer: issuerUrl, scopes: scopes ?? ['*'] });
|
|
1064
|
+
console.error(`[agent-server] Added trusted issuer: ${issuerUrl}`);
|
|
1065
|
+
}
|
|
1066
|
+
},
|
|
811
1067
|
};
|
|
812
1068
|
}
|
package/src/types.ts
CHANGED
|
@@ -20,6 +20,11 @@ export type JsonSchema = {
|
|
|
20
20
|
enum?: unknown[];
|
|
21
21
|
default?: unknown;
|
|
22
22
|
additionalProperties?: boolean | JsonSchema;
|
|
23
|
+
/** Agent refs (paths to other agents this agent can call) */
|
|
24
|
+
refs?: Record<string, { description?: string }>;
|
|
25
|
+
|
|
26
|
+
/** Tools to expose publicly (by name) */
|
|
27
|
+
public?: { tools?: string[] };
|
|
23
28
|
[key: string]: unknown;
|
|
24
29
|
};
|
|
25
30
|
|
|
@@ -73,6 +78,30 @@ export interface IntegrationMethods {
|
|
|
73
78
|
update(params: Record<string, unknown>, ctx: IntegrationMethodContext): Promise<IntegrationMethodResult>;
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/** Hooks for agents that implement the integrations interface. */
|
|
84
|
+
export interface IntegrationHooks {
|
|
85
|
+
/** Provider metadata */
|
|
86
|
+
provider: string;
|
|
87
|
+
displayName: string;
|
|
88
|
+
icon?: string;
|
|
89
|
+
category?: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
|
|
92
|
+
/** Set up this integration (discover, configure, establish trust) */
|
|
93
|
+
setup?(params: Record<string, unknown>, ctx: ToolContext): Promise<IntegrationMethodResult>;
|
|
94
|
+
/** Connect a user to this integration (OAuth, identity linking) */
|
|
95
|
+
connect?(params: Record<string, unknown>, ctx: ToolContext): Promise<IntegrationMethodResult>;
|
|
96
|
+
/** Discover available instances of this integration */
|
|
97
|
+
discover?(params: Record<string, unknown>, ctx: ToolContext): Promise<IntegrationMethodResult>;
|
|
98
|
+
/** List connected instances */
|
|
99
|
+
list?(params: Record<string, unknown>, ctx: ToolContext): Promise<IntegrationMethodResult>;
|
|
100
|
+
/** Get details of a specific instance */
|
|
101
|
+
get?(params: Record<string, unknown>, ctx: ToolContext): Promise<IntegrationMethodResult>;
|
|
102
|
+
/** Update an existing instance config */
|
|
103
|
+
update?(params: Record<string, unknown>, ctx: ToolContext): Promise<IntegrationMethodResult>;
|
|
104
|
+
}
|
|
76
105
|
export interface IntegrationConfig {
|
|
77
106
|
/** Provider identifier (e.g., "databases", "slack", "github") */
|
|
78
107
|
provider: string;
|
|
@@ -134,6 +163,11 @@ export interface AgentConfig {
|
|
|
134
163
|
modelParams?: {
|
|
135
164
|
maxTokens?: number;
|
|
136
165
|
temperature?: number;
|
|
166
|
+
/** Agent refs (paths to other agents this agent can call) */
|
|
167
|
+
refs?: Record<string, { description?: string }>;
|
|
168
|
+
|
|
169
|
+
/** Tools to expose publicly (by name) */
|
|
170
|
+
public?: { tools?: string[] };
|
|
137
171
|
[key: string]: unknown;
|
|
138
172
|
};
|
|
139
173
|
|
|
@@ -145,6 +179,11 @@ export interface AgentConfig {
|
|
|
145
179
|
integration?: IntegrationConfig;
|
|
146
180
|
|
|
147
181
|
/** Additional configuration */
|
|
182
|
+
/** Agent refs (paths to other agents this agent can call) */
|
|
183
|
+
refs?: Record<string, { description?: string }>;
|
|
184
|
+
|
|
185
|
+
/** Tools to expose publicly (by name) */
|
|
186
|
+
public?: { tools?: string[] };
|
|
148
187
|
[key: string]: unknown;
|
|
149
188
|
}
|
|
150
189
|
|
|
@@ -197,6 +236,11 @@ export interface ToolContext extends CoreContext {
|
|
|
197
236
|
callerType: CallerType;
|
|
198
237
|
|
|
199
238
|
/** Application-specific extensions (e.g., OS services, stores) */
|
|
239
|
+
/** Agent refs (paths to other agents this agent can call) */
|
|
240
|
+
refs?: Record<string, { description?: string }>;
|
|
241
|
+
|
|
242
|
+
/** Tools to expose publicly (by name) */
|
|
243
|
+
public?: { tools?: string[] };
|
|
200
244
|
[key: string]: unknown;
|
|
201
245
|
}
|
|
202
246
|
|
|
@@ -492,7 +536,6 @@ export interface AgentDefinition<TContext extends ToolContext = ToolContext> {
|
|
|
492
536
|
* When set alongside config.integration, this agent can be called
|
|
493
537
|
* by @integrations via standard methods (setup, list, connect, get, update).
|
|
494
538
|
*/
|
|
495
|
-
integrationMethods?: IntegrationMethods;
|
|
496
539
|
|
|
497
540
|
/**
|
|
498
541
|
* Lazy loader for lifecycle listeners.
|