@gnomondigital/nebulas-kit-core 0.1.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/LICENSE +29 -0
- package/README.md +82 -0
- package/dist/a2a-client.d.ts +12 -0
- package/dist/a2a-client.d.ts.map +1 -0
- package/dist/a2a-client.js +126 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/nebulas-client.d.ts +38 -0
- package/dist/nebulas-client.d.ts.map +1 -0
- package/dist/nebulas-client.js +46 -0
- package/dist/server/auth-handler.d.ts +17 -0
- package/dist/server/auth-handler.d.ts.map +1 -0
- package/dist/server/auth-handler.js +34 -0
- package/dist/server/auth-strategies.d.ts +16 -0
- package/dist/server/auth-strategies.d.ts.map +1 -0
- package/dist/server/auth-strategies.js +83 -0
- package/dist/server/config-handler.d.ts +29 -0
- package/dist/server/config-handler.d.ts.map +1 -0
- package/dist/server/config-handler.js +47 -0
- package/dist/server/express.d.ts +41 -0
- package/dist/server/express.d.ts.map +1 -0
- package/dist/server/express.js +96 -0
- package/dist/server/nextjs.d.ts +54 -0
- package/dist/server/nextjs.d.ts.map +1 -0
- package/dist/server/nextjs.js +125 -0
- package/dist/server/proxy-handler.d.ts +22 -0
- package/dist/server/proxy-handler.d.ts.map +1 -0
- package/dist/server/proxy-handler.js +78 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/utils/date-timezone.d.ts +8 -0
- package/dist/utils/date-timezone.d.ts.map +1 -0
- package/dist/utils/date-timezone.js +109 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[Gnomon Digital] Software License Agreement
|
|
2
|
+
|
|
3
|
+
This software is the exclusive property of Gnomon Digital. All rights reserved.
|
|
4
|
+
|
|
5
|
+
By installing, copying, or otherwise using the Software, you agree to be bound by the terms of this License Agreement.
|
|
6
|
+
|
|
7
|
+
1. Usage rights
|
|
8
|
+
The use of this software is strictly reserved for employees and contractors of Gnomon Digital within the scope of their professional duties. Any other use, including distribution, modification, reproduction, or dissemination of this software, is prohibited without the prior written permission of Gnomon Digital.
|
|
9
|
+
|
|
10
|
+
2. Restrictions
|
|
11
|
+
You may not:
|
|
12
|
+
Modify, decompile, reverse-engineer, disassemble, or otherwise attempt to derive the source code of the Software.
|
|
13
|
+
Redistribute, sell, lease, sublicense, or otherwise transfer rights to the Software.
|
|
14
|
+
Remove or alter any proprietary notices or labels on the Software.
|
|
15
|
+
|
|
16
|
+
3. Confidentiality
|
|
17
|
+
The source code of this software is confidential and must not be disclosed to third parties without the prior written permission of Gnomon Digital.
|
|
18
|
+
|
|
19
|
+
4. Modifications
|
|
20
|
+
Gnomon Digital reserves the right to modify this license at any time. Modifications will be communicated to authorized users and will take effect on the date specified in the notification.
|
|
21
|
+
|
|
22
|
+
5. Termination
|
|
23
|
+
This license is effective until terminated. It will terminate automatically without notice from Gnomon Digital if you fail to comply with any provision of this agreement. Upon termination, you must destroy all copies of the Software.
|
|
24
|
+
|
|
25
|
+
6. No Warranty
|
|
26
|
+
The Software is provided “AS IS” without warranty of any kind, express or implied. Gnomon Digital does not warrant that the Software will meet your requirements or that its operation will be uninterrupted or error-free.
|
|
27
|
+
|
|
28
|
+
7. Limitation of Liability
|
|
29
|
+
In no event shall Gnomon Digital be liable for any damages (including, without limitation, lost profits, business interruption, or lost information) arising out of the use or inability to use the Software.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @gnomondigital/nebulas-kit-core
|
|
2
|
+
|
|
3
|
+
Framework-agnostic A2A chat client, Nebulas client, and server handlers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @gnomondigital/nebulas-kit-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Environment Variables
|
|
12
|
+
|
|
13
|
+
| Variable | Required | Description |
|
|
14
|
+
|----------|----------|-------------|
|
|
15
|
+
| `A2A_URL` | Yes | A2A agent base URL |
|
|
16
|
+
| `A2A_AUTH_STRATEGY` | Yes | `session` \| `client_credentials` \| `resource_owner_password` \| `api_key_only` |
|
|
17
|
+
| `A2A_API_KEY` | For api_key_only, or fallback | x-api-key header |
|
|
18
|
+
| `AUTH0_DOMAIN` | For Auth0 strategies | Auth0 tenant domain |
|
|
19
|
+
| `AUTH0_CLIENT_ID` | For client_credentials, ROP | Auth0 client ID |
|
|
20
|
+
| `AUTH0_CLIENT_SECRET` | For client_credentials, ROP | Auth0 client secret |
|
|
21
|
+
| `AUTH0_A2A_AUDIENCE` | For Auth0 strategies | API audience |
|
|
22
|
+
| `NEBULAS_API_URL` | For Nebulas | Conversation API base URL |
|
|
23
|
+
| `NEBULAS_PROJECT_ID` | For Nebulas | Project ID |
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Client: sendMessageStream
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { sendMessageStream } from "@gnomondigital/nebulas-kit-core";
|
|
31
|
+
|
|
32
|
+
const content = await sendMessageStream(
|
|
33
|
+
"Hello",
|
|
34
|
+
(chunk) => console.log(chunk),
|
|
35
|
+
signal,
|
|
36
|
+
files,
|
|
37
|
+
true,
|
|
38
|
+
sessionId,
|
|
39
|
+
{ endpoint: "/api/a2a", getHeaders: async () => ({ Authorization: `Bearer ${token}` }) }
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Next.js App Router
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// app/api/a2a/route.ts
|
|
47
|
+
import { handleA2ANextJsPOST } from "@nebulas-kit/core";
|
|
48
|
+
|
|
49
|
+
export async function POST(req: NextRequest) {
|
|
50
|
+
return handleA2ANextJsPOST(req);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// app/api/a2a/auth/route.ts
|
|
56
|
+
import { handleA2AAuthNextJsPOST } from "@gnomondigital/nebulas-kit-core";
|
|
57
|
+
|
|
58
|
+
export async function POST(req: NextRequest) {
|
|
59
|
+
return handleA2AAuthNextJsPOST(req);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// app/api/a2a/config/route.ts
|
|
65
|
+
import { handleA2AConfigNextJsGET } from "@gnomondigital/nebulas-kit-core";
|
|
66
|
+
|
|
67
|
+
export async function GET() {
|
|
68
|
+
return handleA2AConfigNextJsGET();
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Express
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import express from "express";
|
|
76
|
+
import { createA2AExpressRouter } from "@gnomondigital/nebulas-kit-core";
|
|
77
|
+
|
|
78
|
+
const app = express();
|
|
79
|
+
app.use(express.json());
|
|
80
|
+
app.use("/api/a2a", createA2AExpressRouter(express));
|
|
81
|
+
app.listen(3000);
|
|
82
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A JSON-RPC client for AI Chat.
|
|
3
|
+
* Communicates with the A2A agent via proxy (configurable endpoint).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Send a message and stream the response via SSE.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sendMessageStream(message: string, onDelta: (chunk: string) => void, signal?: AbortSignal, files?: File[], stream?: boolean, sessionId?: string, options?: {
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
getHeaders?: () => Promise<Record<string, string>>;
|
|
11
|
+
}): Promise<string>;
|
|
12
|
+
//# sourceMappingURL=a2a-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a2a-client.d.ts","sourceRoot":"","sources":["../src/a2a-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAChC,MAAM,CAAC,EAAE,WAAW,EACpB,KAAK,CAAC,EAAE,IAAI,EAAE,EACd,MAAM,UAAO,EACb,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,GAClF,OAAO,CAAC,MAAM,CAAC,CAgIjB"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A JSON-RPC client for AI Chat.
|
|
3
|
+
* Communicates with the A2A agent via proxy (configurable endpoint).
|
|
4
|
+
*/
|
|
5
|
+
async function fileToBase64(file) {
|
|
6
|
+
const buf = await file.arrayBuffer();
|
|
7
|
+
const bytes = new Uint8Array(buf);
|
|
8
|
+
let binary = "";
|
|
9
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
10
|
+
binary += String.fromCharCode(bytes[i]);
|
|
11
|
+
}
|
|
12
|
+
return btoa(binary);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Send a message and stream the response via SSE.
|
|
16
|
+
*/
|
|
17
|
+
export async function sendMessageStream(message, onDelta, signal, files, stream = true, sessionId, options) {
|
|
18
|
+
const endpoint = options?.endpoint ?? "/api/a2a";
|
|
19
|
+
const parts = [{ kind: "text", text: message }];
|
|
20
|
+
if (files?.length) {
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
const base64 = await fileToBase64(file);
|
|
23
|
+
parts.push({
|
|
24
|
+
kind: "file",
|
|
25
|
+
file: {
|
|
26
|
+
name: file.name,
|
|
27
|
+
mimeType: file.type || "application/octet-stream",
|
|
28
|
+
bytes: base64,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const method = stream ? "message/stream" : "message/send";
|
|
34
|
+
const messageId = crypto.randomUUID();
|
|
35
|
+
const headers = { "Content-Type": "application/json" };
|
|
36
|
+
if (options?.getHeaders) {
|
|
37
|
+
const extra = await options.getHeaders();
|
|
38
|
+
Object.assign(headers, extra);
|
|
39
|
+
}
|
|
40
|
+
const res = await fetch(endpoint, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers,
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
jsonrpc: "2.0",
|
|
45
|
+
method,
|
|
46
|
+
id: messageId,
|
|
47
|
+
params: {
|
|
48
|
+
message: {
|
|
49
|
+
role: "user",
|
|
50
|
+
parts,
|
|
51
|
+
messageId,
|
|
52
|
+
metadata: {
|
|
53
|
+
...(sessionId && { session_id: sessionId }),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
signal,
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
const body = (await res.json().catch(() => ({ error: res.statusText })));
|
|
62
|
+
const err = new Error(body.error || body.detail || `A2A error: ${res.status}`);
|
|
63
|
+
if (body.code)
|
|
64
|
+
err.code = body.code;
|
|
65
|
+
if (body.requireReauth)
|
|
66
|
+
err.requireReauth = body.requireReauth;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
if (!stream) {
|
|
70
|
+
const data = await res.json();
|
|
71
|
+
const result = data.result ?? data;
|
|
72
|
+
const content = result.append ??
|
|
73
|
+
result.content ??
|
|
74
|
+
result.text ??
|
|
75
|
+
result.status?.message?.parts?.[0]?.text ??
|
|
76
|
+
(typeof result.message === "string" ? result.message : "");
|
|
77
|
+
const text = typeof content === "string" ? content : "";
|
|
78
|
+
onDelta(text);
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
const reader = res.body.getReader();
|
|
82
|
+
const decoder = new TextDecoder();
|
|
83
|
+
let accumulated = "";
|
|
84
|
+
let buffer = "";
|
|
85
|
+
while (true) {
|
|
86
|
+
const { done, value } = await reader.read();
|
|
87
|
+
if (done)
|
|
88
|
+
break;
|
|
89
|
+
buffer += decoder.decode(value, { stream: true });
|
|
90
|
+
const lines = buffer.split("\n");
|
|
91
|
+
buffer = lines.pop() ?? "";
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
if (!line.startsWith("data: "))
|
|
94
|
+
continue;
|
|
95
|
+
const data = line.slice(6);
|
|
96
|
+
if (data === "[DONE]" || data.trim() === "")
|
|
97
|
+
continue;
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(data);
|
|
100
|
+
const result = parsed.result ?? parsed;
|
|
101
|
+
if (typeof result.append === "string") {
|
|
102
|
+
accumulated += result.append;
|
|
103
|
+
onDelta(result.append);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (result.message && typeof result.message === "string") {
|
|
107
|
+
accumulated += result.message;
|
|
108
|
+
onDelta(result.message);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const content = result.content ?? result.text ?? "";
|
|
112
|
+
if (content) {
|
|
113
|
+
accumulated += content;
|
|
114
|
+
onDelta(content);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
if (data.trim()) {
|
|
119
|
+
accumulated += data;
|
|
120
|
+
onDelta(data);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return accumulated;
|
|
126
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gnomondigital/nebulas-kit-core
|
|
3
|
+
* Framework-agnostic A2A chat client, Nebulas client, and server handlers.
|
|
4
|
+
*/
|
|
5
|
+
export { sendMessageStream, } from "./a2a-client.js";
|
|
6
|
+
export { createNebulasClient, nebulasClient, type NebulasConversation, type NebulasChatMessage, type CreateChatMessageParams, type NebulasClientOptions, } from "./nebulas-client.js";
|
|
7
|
+
export { transformUtcDatesToLocal, } from "./utils/date-timezone.js";
|
|
8
|
+
export type { ChatMessage, ChatMessagePayload, SendMessageStreamOptions } from "./types.js";
|
|
9
|
+
export { handleA2AProxy, type ProxyRequest, type ProxyResponse, type ProxyHandlerOptions, } from "./server/proxy-handler.js";
|
|
10
|
+
export { getAuthStrategy, getAuthHeaders, type AuthStrategy, } from "./server/auth-strategies.js";
|
|
11
|
+
export { exchangePasswordForToken, type ROPAuthRequest, type ROPAuthResponse, } from "./server/auth-handler.js";
|
|
12
|
+
export { createConfigResponse, type ConfigResponse, type AgentCard, type AgentCardCapabilities, } from "./server/config-handler.js";
|
|
13
|
+
export { createA2AExpressRouter, type A2AExpressRouterOptions, } from "./server/express.js";
|
|
14
|
+
export { handleA2ANextJsPOST, handleA2AAuthNextJsPOST, handleA2AConfigNextJsGET, createConfigGetHeadersForAuth0, type HandleA2AConfigOptions, type Auth0ClientForConfig, } from "./server/nextjs.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE5F,OAAO,EACL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,mBAAmB,GACzB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,eAAe,EACf,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,wBAAwB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,sBAAsB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gnomondigital/nebulas-kit-core
|
|
3
|
+
* Framework-agnostic A2A chat client, Nebulas client, and server handlers.
|
|
4
|
+
*/
|
|
5
|
+
export { sendMessageStream, } from "./a2a-client.js";
|
|
6
|
+
export { createNebulasClient, nebulasClient, } from "./nebulas-client.js";
|
|
7
|
+
export { transformUtcDatesToLocal, } from "./utils/date-timezone.js";
|
|
8
|
+
export { handleA2AProxy, } from "./server/proxy-handler.js";
|
|
9
|
+
export { getAuthStrategy, getAuthHeaders, } from "./server/auth-strategies.js";
|
|
10
|
+
export { exchangePasswordForToken, } from "./server/auth-handler.js";
|
|
11
|
+
export { createConfigResponse, } from "./server/config-handler.js";
|
|
12
|
+
export { createA2AExpressRouter, } from "./server/express.js";
|
|
13
|
+
// Usage: import express from "express"; app.use("/api/a2a", createA2AExpressRouter(express));
|
|
14
|
+
export { handleA2ANextJsPOST, handleA2AAuthNextJsPOST, handleA2AConfigNextJsGET, createConfigGetHeadersForAuth0, } from "./server/nextjs.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nebulas API client for conversations and chat messages.
|
|
3
|
+
*/
|
|
4
|
+
export interface NebulasConversation {
|
|
5
|
+
id: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface NebulasChatMessage {
|
|
9
|
+
id: string;
|
|
10
|
+
content: string;
|
|
11
|
+
role?: string | null;
|
|
12
|
+
parent_id?: string | null;
|
|
13
|
+
conversation_id?: string | null;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface CreateChatMessageParams {
|
|
17
|
+
id?: string;
|
|
18
|
+
content: string;
|
|
19
|
+
role?: "user" | "assistant";
|
|
20
|
+
conversation_id: string;
|
|
21
|
+
parent_id?: string | null;
|
|
22
|
+
files?: unknown[];
|
|
23
|
+
}
|
|
24
|
+
export interface NebulasClientOptions {
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a Nebulas client with configurable base URL.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createNebulasClient(options?: NebulasClientOptions): {
|
|
31
|
+
createConversation: (query: string, projectId: string) => Promise<NebulasConversation>;
|
|
32
|
+
addChatMessage: (params: CreateChatMessageParams) => Promise<NebulasChatMessage[]>;
|
|
33
|
+
};
|
|
34
|
+
export declare const nebulasClient: {
|
|
35
|
+
createConversation: (query: string, projectId: string) => Promise<NebulasConversation>;
|
|
36
|
+
addChatMessage: (params: CreateChatMessageParams) => Promise<NebulasChatMessage[]>;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=nebulas-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nebulas-client.d.ts","sourceRoot":"","sources":["../src/nebulas-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB;gCAI3D,MAAM,aACF,MAAM,KAChB,OAAO,CAAC,mBAAmB,CAAC;6BAiBrB,uBAAuB,KAC9B,OAAO,CAAC,kBAAkB,EAAE,CAAC;EAsBjC;AAED,eAAO,MAAM,aAAa;gCA5Cf,MAAM,aACF,MAAM,KAChB,OAAO,CAAC,mBAAmB,CAAC;6BAiBrB,uBAAuB,KAC9B,OAAO,CAAC,kBAAkB,EAAE,CAAC;CAwBgB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nebulas API client for conversations and chat messages.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Create a Nebulas client with configurable base URL.
|
|
6
|
+
*/
|
|
7
|
+
export function createNebulasClient(options = {}) {
|
|
8
|
+
const baseUrl = options.baseUrl ?? "/api/nebulas";
|
|
9
|
+
async function createConversation(query, projectId) {
|
|
10
|
+
const params = new URLSearchParams({
|
|
11
|
+
query,
|
|
12
|
+
project_id: projectId,
|
|
13
|
+
});
|
|
14
|
+
const res = await fetch(`${baseUrl}/v1/conversations/?${params}`, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
const body = await res.json().catch(() => ({ detail: res.statusText }));
|
|
19
|
+
throw new Error(body.detail || body.error || `Failed to create conversation: ${res.status}`);
|
|
20
|
+
}
|
|
21
|
+
const data = await res.json();
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
async function addChatMessage(params) {
|
|
25
|
+
const res = await fetch(`${baseUrl}/v1/chat_messages/`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
id: params.id,
|
|
30
|
+
content: params.content,
|
|
31
|
+
role: params.role ?? "user",
|
|
32
|
+
conversation_id: params.conversation_id,
|
|
33
|
+
parent_id: params.parent_id ?? null,
|
|
34
|
+
files: params.files ?? null,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const body = await res.json().catch(() => ({ detail: res.statusText }));
|
|
39
|
+
throw new Error(body.detail || body.error || `Failed to add message: ${res.status}`);
|
|
40
|
+
}
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
return Array.isArray(data) ? data : [data];
|
|
43
|
+
}
|
|
44
|
+
return { createConversation, addChatMessage };
|
|
45
|
+
}
|
|
46
|
+
export const nebulasClient = createNebulasClient();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth handler for Resource Owner Password flow.
|
|
3
|
+
* Exchanges username/password for Auth0 token.
|
|
4
|
+
*/
|
|
5
|
+
export interface ROPAuthRequest {
|
|
6
|
+
username: string;
|
|
7
|
+
password: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ROPAuthResponse {
|
|
10
|
+
access_token: string;
|
|
11
|
+
expires_in?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Exchange username/password for Auth0 access token.
|
|
15
|
+
*/
|
|
16
|
+
export declare function exchangePasswordForToken(username: string, password: string): Promise<ROPAuthResponse>;
|
|
17
|
+
//# sourceMappingURL=auth-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-handler.d.ts","sourceRoot":"","sources":["../../src/server/auth-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC,CA8B1B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth handler for Resource Owner Password flow.
|
|
3
|
+
* Exchanges username/password for Auth0 token.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Exchange username/password for Auth0 access token.
|
|
7
|
+
*/
|
|
8
|
+
export async function exchangePasswordForToken(username, password) {
|
|
9
|
+
const domain = process.env.AUTH0_DOMAIN;
|
|
10
|
+
const clientId = process.env.AUTH0_CLIENT_ID;
|
|
11
|
+
const clientSecret = process.env.AUTH0_CLIENT_SECRET;
|
|
12
|
+
const audience = process.env.AUTH0_A2A_AUDIENCE;
|
|
13
|
+
if (!domain || !clientId || !clientSecret) {
|
|
14
|
+
throw new Error("AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET required for ROP");
|
|
15
|
+
}
|
|
16
|
+
const res = await fetch(`https://${domain}/oauth/token`, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
19
|
+
body: new URLSearchParams({
|
|
20
|
+
grant_type: "password",
|
|
21
|
+
username,
|
|
22
|
+
password,
|
|
23
|
+
client_id: clientId,
|
|
24
|
+
client_secret: clientSecret,
|
|
25
|
+
...(audience && { audience }),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const body = await res.json().catch(() => ({}));
|
|
30
|
+
throw new Error(body.error_description || body.error || `Auth failed: ${res.status}`);
|
|
31
|
+
}
|
|
32
|
+
const data = (await res.json());
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth strategies for A2A proxy.
|
|
3
|
+
* Framework-agnostic; returns headers to add to the A2A request.
|
|
4
|
+
*/
|
|
5
|
+
export type AuthStrategy = "session" | "client_credentials" | "resource_owner_password" | "api_key_only";
|
|
6
|
+
/**
|
|
7
|
+
* Get headers for A2A request based on strategy.
|
|
8
|
+
* For session/bearer, the caller must pass the token (from session or Authorization header).
|
|
9
|
+
*/
|
|
10
|
+
export declare function getAuthHeaders(options: {
|
|
11
|
+
strategy: AuthStrategy;
|
|
12
|
+
bearerToken?: string;
|
|
13
|
+
getSessionToken?: () => Promise<string | undefined>;
|
|
14
|
+
}): Promise<Record<string, string>>;
|
|
15
|
+
export declare function getAuthStrategy(): AuthStrategy;
|
|
16
|
+
//# sourceMappingURL=auth-strategies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-strategies.d.ts","sourceRoot":"","sources":["../../src/server/auth-strategies.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,oBAAoB,GACpB,yBAAyB,GACzB,cAAc,CAAC;AA8CnB;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrD,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA2BlC;AAED,wBAAgB,eAAe,IAAI,YAAY,CAW9C"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth strategies for A2A proxy.
|
|
3
|
+
* Framework-agnostic; returns headers to add to the A2A request.
|
|
4
|
+
*/
|
|
5
|
+
let m2mTokenCache = null;
|
|
6
|
+
/**
|
|
7
|
+
* Get Auth0 token via client credentials (M2M).
|
|
8
|
+
*/
|
|
9
|
+
async function getClientCredentialsToken() {
|
|
10
|
+
const domain = process.env.AUTH0_DOMAIN;
|
|
11
|
+
const clientId = process.env.AUTH0_CLIENT_ID;
|
|
12
|
+
const clientSecret = process.env.AUTH0_CLIENT_SECRET;
|
|
13
|
+
const audience = process.env.AUTH0_A2A_AUDIENCE;
|
|
14
|
+
if (!domain || !clientId || !clientSecret || !audience) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
if (m2mTokenCache && m2mTokenCache.expiresAt > Date.now() + 60000) {
|
|
18
|
+
return m2mTokenCache.token;
|
|
19
|
+
}
|
|
20
|
+
const res = await fetch(`https://${domain}/oauth/token`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
23
|
+
body: new URLSearchParams({
|
|
24
|
+
grant_type: "client_credentials",
|
|
25
|
+
client_id: clientId,
|
|
26
|
+
client_secret: clientSecret,
|
|
27
|
+
audience,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
const body = await res.json().catch(() => ({}));
|
|
32
|
+
throw new Error(body.error_description || body.error || `Auth0 token failed: ${res.status}`);
|
|
33
|
+
}
|
|
34
|
+
const data = (await res.json());
|
|
35
|
+
const expiresIn = (data.expires_in ?? 86400) * 1000;
|
|
36
|
+
m2mTokenCache = {
|
|
37
|
+
token: data.access_token,
|
|
38
|
+
expiresAt: Date.now() + expiresIn,
|
|
39
|
+
};
|
|
40
|
+
return data.access_token;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get headers for A2A request based on strategy.
|
|
44
|
+
* For session/bearer, the caller must pass the token (from session or Authorization header).
|
|
45
|
+
*/
|
|
46
|
+
export async function getAuthHeaders(options) {
|
|
47
|
+
const headers = {};
|
|
48
|
+
const apiKey = process.env.A2A_API_KEY;
|
|
49
|
+
switch (options.strategy) {
|
|
50
|
+
case "api_key_only":
|
|
51
|
+
if (apiKey)
|
|
52
|
+
headers["x-api-key"] = apiKey;
|
|
53
|
+
break;
|
|
54
|
+
case "client_credentials": {
|
|
55
|
+
const token = await getClientCredentialsToken();
|
|
56
|
+
if (token)
|
|
57
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
58
|
+
if (apiKey)
|
|
59
|
+
headers["x-api-key"] = apiKey;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "resource_owner_password":
|
|
63
|
+
case "session": {
|
|
64
|
+
const token = options.bearerToken ?? (options.getSessionToken ? await options.getSessionToken() : undefined);
|
|
65
|
+
if (token)
|
|
66
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
67
|
+
if (apiKey)
|
|
68
|
+
headers["x-api-key"] = apiKey;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return headers;
|
|
73
|
+
}
|
|
74
|
+
export function getAuthStrategy() {
|
|
75
|
+
const s = process.env.A2A_AUTH_STRATEGY?.toLowerCase();
|
|
76
|
+
if (s === "session" ||
|
|
77
|
+
s === "client_credentials" ||
|
|
78
|
+
s === "resource_owner_password" ||
|
|
79
|
+
s === "api_key_only") {
|
|
80
|
+
return s;
|
|
81
|
+
}
|
|
82
|
+
return "api_key_only";
|
|
83
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config handler - returns nebulasProjectId and agent card-derived streaming capability.
|
|
3
|
+
* Fetches agent card from A2A_URL/.well-known/agent-card.json when A2A_URL is set.
|
|
4
|
+
* Framework-agnostic; returns JSON response body.
|
|
5
|
+
*/
|
|
6
|
+
export interface AgentCardCapabilities {
|
|
7
|
+
streaming?: boolean;
|
|
8
|
+
pushNotifications?: boolean;
|
|
9
|
+
extendedAgentCard?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface AgentCard {
|
|
12
|
+
name?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
capabilities?: AgentCardCapabilities;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface ConfigResponse {
|
|
18
|
+
nebulasProjectId: string | null;
|
|
19
|
+
agentCard?: AgentCard | null;
|
|
20
|
+
streaming?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface CreateConfigResponseOptions {
|
|
23
|
+
/** When false, skip agent card fetch (streaming defaults to true). */
|
|
24
|
+
includeAgentCard?: boolean;
|
|
25
|
+
/** Headers to use when fetching agent card (e.g. Authorization, x-api-key). Same as A2A proxy. */
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
export declare function createConfigResponse(options?: CreateConfigResponseOptions): Promise<ConfigResponse>;
|
|
29
|
+
//# sourceMappingURL=config-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-handler.d.ts","sourceRoot":"","sources":["../../src/server/config-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAyBD,wBAAsB,oBAAoB,CACxC,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,cAAc,CAAC,CAmBzB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config handler - returns nebulasProjectId and agent card-derived streaming capability.
|
|
3
|
+
* Fetches agent card from A2A_URL/.well-known/agent-card.json when A2A_URL is set.
|
|
4
|
+
* Framework-agnostic; returns JSON response body.
|
|
5
|
+
*/
|
|
6
|
+
const A2A_URL = process.env.NEXT_PUBLIC_A2A_URL || process.env.A2A_URL || "";
|
|
7
|
+
async function fetchAgentCard(headers) {
|
|
8
|
+
const base = A2A_URL.replace(/\/$/, "");
|
|
9
|
+
if (!base)
|
|
10
|
+
return null;
|
|
11
|
+
const url = `${base}/.well-known/agent-card.json`;
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
signal: AbortSignal.timeout(5000),
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
...headers,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok)
|
|
21
|
+
return null;
|
|
22
|
+
const data = (await res.json());
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.warn("[Config] Agent card fetch failed:", err);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function createConfigResponse(options) {
|
|
31
|
+
const nebulasProjectId = process.env.NEBULAS_PROJECT_ID ?? null;
|
|
32
|
+
const includeAgentCard = options?.includeAgentCard !== false;
|
|
33
|
+
if (!includeAgentCard) {
|
|
34
|
+
return {
|
|
35
|
+
nebulasProjectId,
|
|
36
|
+
agentCard: null,
|
|
37
|
+
streaming: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const agentCard = await fetchAgentCard(options?.headers);
|
|
41
|
+
const streaming = agentCard?.capabilities?.streaming ?? true;
|
|
42
|
+
return {
|
|
43
|
+
nebulasProjectId,
|
|
44
|
+
agentCard: agentCard ?? null,
|
|
45
|
+
streaming,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express adapter for A2A proxy, auth, and config.
|
|
3
|
+
* Use: app.use(express.json()); app.use("/api/a2a", createA2AExpressRouter(express));
|
|
4
|
+
*/
|
|
5
|
+
interface ExpressRequest {
|
|
6
|
+
method: string;
|
|
7
|
+
headers: Record<string, string | string[] | undefined>;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
query?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
interface ExpressResponse {
|
|
12
|
+
status: (code: number) => ExpressResponse;
|
|
13
|
+
setHeader: (name: string, value: string) => ExpressResponse;
|
|
14
|
+
send: (body?: unknown) => void;
|
|
15
|
+
write: (chunk: Buffer) => void;
|
|
16
|
+
end: () => void;
|
|
17
|
+
json: (body: unknown) => void;
|
|
18
|
+
}
|
|
19
|
+
interface ExpressRouter {
|
|
20
|
+
post: (path: string, handler: (req: ExpressRequest, res: ExpressResponse) => void | Promise<void>) => void;
|
|
21
|
+
get: (path: string, handler: (req: ExpressRequest, res: ExpressResponse) => void) => void;
|
|
22
|
+
}
|
|
23
|
+
interface ExpressModule {
|
|
24
|
+
Router: () => ExpressRouter;
|
|
25
|
+
}
|
|
26
|
+
type Request = ExpressRequest;
|
|
27
|
+
import { type ProxyHandlerOptions } from "./proxy-handler.js";
|
|
28
|
+
export interface A2AExpressRouterOptions extends ProxyHandlerOptions {
|
|
29
|
+
/** When provided, uses these headers when fetching agent card (same auth as A2A proxy). */
|
|
30
|
+
configGetHeaders?: (req: Request) => Promise<Record<string, string>>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create Express router for A2A proxy, auth (ROP), and config.
|
|
34
|
+
* Mount at /api/a2a - provides:
|
|
35
|
+
* POST / - A2A proxy
|
|
36
|
+
* POST /auth - ROP token exchange (body: { username, password })
|
|
37
|
+
* GET /config - Config (nebulasProjectId)
|
|
38
|
+
*/
|
|
39
|
+
export declare function createA2AExpressRouter(express: ExpressModule, options?: A2AExpressRouterOptions): ExpressRouter;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/server/express.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAC;IAC1C,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,eAAe,CAAC;IAC5D,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/B;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAC3G,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,KAAK,IAAI,KAAK,IAAI,CAAC;CAC3F;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,aAAa,CAAC;CAC7B;AAED,KAAK,OAAO,GAAG,cAAc,CAAC;AAE9B,OAAO,EAAkB,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAI9E,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtE;AAeD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,aAAa,EACtB,OAAO,CAAC,EAAE,uBAAuB,iBAwElC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express adapter for A2A proxy, auth, and config.
|
|
3
|
+
* Use: app.use(express.json()); app.use("/api/a2a", createA2AExpressRouter(express));
|
|
4
|
+
*/
|
|
5
|
+
import { handleA2AProxy } from "./proxy-handler.js";
|
|
6
|
+
import { exchangePasswordForToken } from "./auth-handler.js";
|
|
7
|
+
import { createConfigResponse } from "./config-handler.js";
|
|
8
|
+
function getHeadersFromRequest(req) {
|
|
9
|
+
const headers = {};
|
|
10
|
+
const h = req.headers;
|
|
11
|
+
for (const [key, value] of Object.entries(h)) {
|
|
12
|
+
if (value && typeof value === "string") {
|
|
13
|
+
headers[key.toLowerCase()] = value;
|
|
14
|
+
}
|
|
15
|
+
else if (Array.isArray(value) && value.length) {
|
|
16
|
+
headers[key.toLowerCase()] = value[0];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return headers;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create Express router for A2A proxy, auth (ROP), and config.
|
|
23
|
+
* Mount at /api/a2a - provides:
|
|
24
|
+
* POST / - A2A proxy
|
|
25
|
+
* POST /auth - ROP token exchange (body: { username, password })
|
|
26
|
+
* GET /config - Config (nebulasProjectId)
|
|
27
|
+
*/
|
|
28
|
+
export function createA2AExpressRouter(express, options) {
|
|
29
|
+
const router = express.Router();
|
|
30
|
+
router.post("/", async (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
const body = typeof req.body === "string"
|
|
33
|
+
? req.body
|
|
34
|
+
: JSON.stringify(req.body ?? {});
|
|
35
|
+
const proxyReq = {
|
|
36
|
+
method: req.method,
|
|
37
|
+
headers: getHeadersFromRequest(req),
|
|
38
|
+
body: body || undefined,
|
|
39
|
+
};
|
|
40
|
+
const result = await handleA2AProxy(proxyReq, options);
|
|
41
|
+
res.status(result.status);
|
|
42
|
+
for (const [k, v] of Object.entries(result.headers)) {
|
|
43
|
+
res.setHeader(k, v);
|
|
44
|
+
}
|
|
45
|
+
if (typeof result.body === "string") {
|
|
46
|
+
res.send(result.body);
|
|
47
|
+
}
|
|
48
|
+
else if (result.body && typeof result.body.getReader === "function") {
|
|
49
|
+
const reader = result.body.getReader();
|
|
50
|
+
const pump = async () => {
|
|
51
|
+
while (true) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done)
|
|
54
|
+
break;
|
|
55
|
+
res.write(Buffer.from(value));
|
|
56
|
+
}
|
|
57
|
+
res.end();
|
|
58
|
+
};
|
|
59
|
+
pump();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.error("[A2A Express] Error:", err);
|
|
64
|
+
res.status(500).json({ error: "Internal server error" });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
router.post("/auth", async (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
const body = req.body;
|
|
70
|
+
const { username, password } = body ?? {};
|
|
71
|
+
if (!username || !password) {
|
|
72
|
+
res.status(400).json({ error: "username and password required" });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const data = await exchangePasswordForToken(username, password);
|
|
76
|
+
res.json(data);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
console.error("[A2A Auth] Error:", err);
|
|
80
|
+
res.status(401).json({
|
|
81
|
+
error: "Authentication failed",
|
|
82
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
router.get("/config", async (req, res) => {
|
|
87
|
+
const includeAgentCard = req.query?.includeAgentCard !== "false";
|
|
88
|
+
let headers;
|
|
89
|
+
if (options?.configGetHeaders && includeAgentCard) {
|
|
90
|
+
headers = await options.configGetHeaders(req);
|
|
91
|
+
}
|
|
92
|
+
const config = await createConfigResponse({ includeAgentCard, headers });
|
|
93
|
+
res.json(config);
|
|
94
|
+
});
|
|
95
|
+
return router;
|
|
96
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js adapter for A2A proxy, auth, and config.
|
|
3
|
+
* Use in App Router: app/api/a2a/route.ts
|
|
4
|
+
*/
|
|
5
|
+
import type { NextRequest } from "next/server";
|
|
6
|
+
import { type ProxyHandlerOptions } from "./proxy-handler.js";
|
|
7
|
+
/**
|
|
8
|
+
* Next.js App Router POST handler for A2A proxy.
|
|
9
|
+
*/
|
|
10
|
+
export declare function handleA2ANextJsPOST(request: NextRequest, options?: ProxyHandlerOptions): Promise<Response>;
|
|
11
|
+
/**
|
|
12
|
+
* Next.js App Router POST handler for ROP auth.
|
|
13
|
+
*/
|
|
14
|
+
export declare function handleA2AAuthNextJsPOST(request: NextRequest): Promise<Response>;
|
|
15
|
+
/**
|
|
16
|
+
* Options for the config GET handler.
|
|
17
|
+
*/
|
|
18
|
+
export interface HandleA2AConfigOptions {
|
|
19
|
+
/**
|
|
20
|
+
* When provided, fetches the agent card with these headers (Authorization, x-api-key).
|
|
21
|
+
* Use createConfigGetHeadersForAuth0 for Auth0 session strategy.
|
|
22
|
+
*/
|
|
23
|
+
getHeaders?: (request: NextRequest) => Promise<Record<string, string>>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Next.js App Router GET handler for config.
|
|
27
|
+
* When getHeaders is provided, uses those headers when fetching the agent card (same auth as A2A proxy).
|
|
28
|
+
*/
|
|
29
|
+
export declare function handleA2AConfigNextJsGET(request: NextRequest, options?: HandleA2AConfigOptions): Promise<Response>;
|
|
30
|
+
/**
|
|
31
|
+
* Auth0 client interface for config headers.
|
|
32
|
+
* Compatible with Auth0Client from @auth0/nextjs-auth0/server.
|
|
33
|
+
*/
|
|
34
|
+
export interface Auth0ClientForConfig {
|
|
35
|
+
getSession(req: unknown): Promise<{
|
|
36
|
+
user?: unknown;
|
|
37
|
+
} | null>;
|
|
38
|
+
getAccessToken(req: unknown, res: unknown, opts: {
|
|
39
|
+
audience: string;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
token: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates getHeaders for handleA2AConfigNextJsGET using Auth0 session.
|
|
46
|
+
* Uses same auth as A2A proxy: session, access token for A2A audience, x-api-key fallback.
|
|
47
|
+
* Throws NextResponse (401) when unauthorized.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createConfigGetHeadersForAuth0(options: {
|
|
50
|
+
auth0: Auth0ClientForConfig;
|
|
51
|
+
audience?: string;
|
|
52
|
+
apiKey?: string;
|
|
53
|
+
}): (request: NextRequest) => Promise<Record<string, string>>;
|
|
54
|
+
//# sourceMappingURL=nextjs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../../src/server/nextjs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAkB,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAY9E;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,mBAAmB,qBA4B9B;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,WAAW,qBAyBjE;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACxE;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,sBAAsB,qBAcjC;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC7D,cAAc,CACZ,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GACzB,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE;IACtD,KAAK,EAAE,oBAAoB,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAyC5D"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js adapter for A2A proxy, auth, and config.
|
|
3
|
+
* Use in App Router: app/api/a2a/route.ts
|
|
4
|
+
*/
|
|
5
|
+
import { NextResponse } from "next/server";
|
|
6
|
+
import { handleA2AProxy } from "./proxy-handler.js";
|
|
7
|
+
import { exchangePasswordForToken } from "./auth-handler.js";
|
|
8
|
+
import { createConfigResponse } from "./config-handler.js";
|
|
9
|
+
function headersFromRequest(req) {
|
|
10
|
+
const headers = {};
|
|
11
|
+
req.headers.forEach((value, key) => {
|
|
12
|
+
headers[key.toLowerCase()] = value;
|
|
13
|
+
});
|
|
14
|
+
return headers;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Next.js App Router POST handler for A2A proxy.
|
|
18
|
+
*/
|
|
19
|
+
export async function handleA2ANextJsPOST(request, options) {
|
|
20
|
+
const body = await request.text();
|
|
21
|
+
const proxyReq = {
|
|
22
|
+
method: request.method,
|
|
23
|
+
headers: headersFromRequest(request),
|
|
24
|
+
body: body || undefined,
|
|
25
|
+
};
|
|
26
|
+
const result = await handleA2AProxy(proxyReq, options);
|
|
27
|
+
const responseHeaders = new Headers();
|
|
28
|
+
for (const [k, v] of Object.entries(result.headers)) {
|
|
29
|
+
responseHeaders.set(k, v);
|
|
30
|
+
}
|
|
31
|
+
if (typeof result.body === "string") {
|
|
32
|
+
return new Response(result.body, {
|
|
33
|
+
status: result.status,
|
|
34
|
+
headers: responseHeaders,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (result.body instanceof ReadableStream) {
|
|
38
|
+
return new Response(result.body, {
|
|
39
|
+
status: result.status,
|
|
40
|
+
headers: responseHeaders,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return new Response(null, { status: result.status, headers: responseHeaders });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Next.js App Router POST handler for ROP auth.
|
|
47
|
+
*/
|
|
48
|
+
export async function handleA2AAuthNextJsPOST(request) {
|
|
49
|
+
try {
|
|
50
|
+
const body = await request.json();
|
|
51
|
+
const { username, password } = body ?? {};
|
|
52
|
+
if (!username || !password) {
|
|
53
|
+
return new Response(JSON.stringify({ error: "username and password required" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
54
|
+
}
|
|
55
|
+
const data = await exchangePasswordForToken(username, password);
|
|
56
|
+
return new Response(JSON.stringify(data), {
|
|
57
|
+
status: 200,
|
|
58
|
+
headers: { "Content-Type": "application/json" },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.error("[A2A Auth] Error:", err);
|
|
63
|
+
return new Response(JSON.stringify({
|
|
64
|
+
error: "Authentication failed",
|
|
65
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
66
|
+
}), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Next.js App Router GET handler for config.
|
|
71
|
+
* When getHeaders is provided, uses those headers when fetching the agent card (same auth as A2A proxy).
|
|
72
|
+
*/
|
|
73
|
+
export async function handleA2AConfigNextJsGET(request, options) {
|
|
74
|
+
const includeAgentCard = request.nextUrl.searchParams.get("includeAgentCard") !== "false";
|
|
75
|
+
let headers;
|
|
76
|
+
if (options?.getHeaders && includeAgentCard) {
|
|
77
|
+
headers = await options.getHeaders(request);
|
|
78
|
+
}
|
|
79
|
+
const config = await createConfigResponse({ includeAgentCard, headers });
|
|
80
|
+
return new Response(JSON.stringify(config), {
|
|
81
|
+
status: 200,
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Creates getHeaders for handleA2AConfigNextJsGET using Auth0 session.
|
|
87
|
+
* Uses same auth as A2A proxy: session, access token for A2A audience, x-api-key fallback.
|
|
88
|
+
* Throws NextResponse (401) when unauthorized.
|
|
89
|
+
*/
|
|
90
|
+
export function createConfigGetHeadersForAuth0(options) {
|
|
91
|
+
return async (request) => {
|
|
92
|
+
const session = await options.auth0.getSession(request);
|
|
93
|
+
if (!session) {
|
|
94
|
+
throw new NextResponse(JSON.stringify({ error: "Unauthorized" }), {
|
|
95
|
+
status: 401,
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const headers = {};
|
|
100
|
+
if (options.apiKey) {
|
|
101
|
+
headers["x-api-key"] = options.apiKey;
|
|
102
|
+
}
|
|
103
|
+
if (options.audience) {
|
|
104
|
+
try {
|
|
105
|
+
const authResponse = new NextResponse();
|
|
106
|
+
const { token } = await options.auth0.getAccessToken(request, authResponse, {
|
|
107
|
+
audience: options.audience,
|
|
108
|
+
});
|
|
109
|
+
if (token) {
|
|
110
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error("[Config] Failed to get access token:", err);
|
|
115
|
+
if (!options.apiKey) {
|
|
116
|
+
throw new NextResponse(JSON.stringify({
|
|
117
|
+
error: "Unauthorized",
|
|
118
|
+
hint: "Ensure Auth0 API has 'Allow Offline Access' enabled, MRRT is configured for the A2A audience, and you have logged out and back in to get a refresh token.",
|
|
119
|
+
}), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return headers;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic A2A proxy handler.
|
|
3
|
+
* Accepts a minimal request/response interface.
|
|
4
|
+
*/
|
|
5
|
+
export interface ProxyRequest {
|
|
6
|
+
method: string;
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
body?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ProxyResponse {
|
|
11
|
+
status: number;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
body?: string | ReadableStream<Uint8Array>;
|
|
14
|
+
}
|
|
15
|
+
export interface ProxyHandlerOptions {
|
|
16
|
+
getSessionToken?: (req: ProxyRequest) => Promise<string | undefined>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Handle A2A proxy request. Returns response to send.
|
|
20
|
+
*/
|
|
21
|
+
export declare function handleA2AProxy(req: ProxyRequest, options?: ProxyHandlerOptions): Promise<ProxyResponse>;
|
|
22
|
+
//# sourceMappingURL=proxy-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-handler.d.ts","sourceRoot":"","sources":["../../src/server/proxy-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACtE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,YAAY,EACjB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,aAAa,CAAC,CA0ExB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic A2A proxy handler.
|
|
3
|
+
* Accepts a minimal request/response interface.
|
|
4
|
+
*/
|
|
5
|
+
import { getAuthStrategy, getAuthHeaders, } from "./auth-strategies.js";
|
|
6
|
+
const A2A_URL = process.env.NEXT_PUBLIC_A2A_URL || process.env.A2A_URL || "http://localhost:9999";
|
|
7
|
+
/**
|
|
8
|
+
* Handle A2A proxy request. Returns response to send.
|
|
9
|
+
*/
|
|
10
|
+
export async function handleA2AProxy(req, options = {}) {
|
|
11
|
+
const url = A2A_URL.replace(/\/$/, "");
|
|
12
|
+
const strategy = getAuthStrategy();
|
|
13
|
+
let bearerToken;
|
|
14
|
+
const authHeader = req.headers["authorization"] || req.headers["Authorization"];
|
|
15
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
16
|
+
bearerToken = authHeader.slice(7);
|
|
17
|
+
}
|
|
18
|
+
else if (strategy === "session" && options.getSessionToken) {
|
|
19
|
+
bearerToken = await options.getSessionToken(req);
|
|
20
|
+
}
|
|
21
|
+
if (strategy === "session" && !bearerToken && !process.env.A2A_API_KEY) {
|
|
22
|
+
return {
|
|
23
|
+
status: 401,
|
|
24
|
+
headers: { "Content-Type": "application/json" },
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
error: "Unauthorized",
|
|
27
|
+
hint: "Session required or A2A_API_KEY for fallback.",
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const authHeaders = await getAuthHeaders({
|
|
32
|
+
strategy,
|
|
33
|
+
bearerToken,
|
|
34
|
+
getSessionToken: options.getSessionToken
|
|
35
|
+
? () => options.getSessionToken(req)
|
|
36
|
+
: undefined,
|
|
37
|
+
});
|
|
38
|
+
const headers = {
|
|
39
|
+
"Content-Type": req.headers["content-type"] || "application/json",
|
|
40
|
+
...authHeaders,
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(url, {
|
|
44
|
+
method: req.method,
|
|
45
|
+
headers,
|
|
46
|
+
body: req.body,
|
|
47
|
+
});
|
|
48
|
+
const resContentType = res.headers.get("content-type") ?? "";
|
|
49
|
+
const responseHeaders = {
|
|
50
|
+
"Content-Type": resContentType || "application/json",
|
|
51
|
+
};
|
|
52
|
+
if (resContentType.includes("text/event-stream")) {
|
|
53
|
+
responseHeaders["Cache-Control"] = "no-cache";
|
|
54
|
+
responseHeaders["Connection"] = "keep-alive";
|
|
55
|
+
}
|
|
56
|
+
if (resContentType.includes("text/event-stream") && res.body) {
|
|
57
|
+
return {
|
|
58
|
+
status: res.status,
|
|
59
|
+
headers: responseHeaders,
|
|
60
|
+
body: res.body,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const data = await res.arrayBuffer();
|
|
64
|
+
return {
|
|
65
|
+
status: res.status,
|
|
66
|
+
headers: responseHeaders,
|
|
67
|
+
body: new TextDecoder().decode(data),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error("[A2A Proxy] Request failed:", error);
|
|
72
|
+
return {
|
|
73
|
+
status: 502,
|
|
74
|
+
headers: { "Content-Type": "application/json" },
|
|
75
|
+
body: JSON.stringify({ error: "A2A service unavailable" }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for A2A chat.
|
|
3
|
+
*/
|
|
4
|
+
export interface ChatMessage {
|
|
5
|
+
id: string;
|
|
6
|
+
role: "user" | "agent";
|
|
7
|
+
content: string;
|
|
8
|
+
isStreaming?: boolean;
|
|
9
|
+
files?: {
|
|
10
|
+
name: string;
|
|
11
|
+
}[];
|
|
12
|
+
}
|
|
13
|
+
export interface ChatMessagePayload {
|
|
14
|
+
role: "user" | "assistant";
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SendMessageStreamOptions {
|
|
18
|
+
endpoint: string;
|
|
19
|
+
message: string;
|
|
20
|
+
onDelta: (chunk: string) => void;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
files?: File[];
|
|
23
|
+
stream?: boolean;
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
getHeaders?: () => Promise<Record<string, string>>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transforms UTC date-time strings in text to the user's local timezone.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Replaces UTC date-time strings in text with local timezone equivalents.
|
|
6
|
+
*/
|
|
7
|
+
export declare function transformUtcDatesToLocal(text: string, locale?: string): string;
|
|
8
|
+
//# sourceMappingURL=date-timezone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-timezone.d.ts","sourceRoot":"","sources":["../../src/utils/date-timezone.ts"],"names":[],"mappings":"AAAA;;GAEG;AAuHH;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAEG,GACV,MAAM,CA8BR"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transforms UTC date-time strings in text to the user's local timezone.
|
|
3
|
+
*/
|
|
4
|
+
const FRENCH_MONTHS = {
|
|
5
|
+
janvier: 0,
|
|
6
|
+
février: 1,
|
|
7
|
+
mars: 2,
|
|
8
|
+
avril: 3,
|
|
9
|
+
mai: 4,
|
|
10
|
+
juin: 5,
|
|
11
|
+
juillet: 6,
|
|
12
|
+
août: 7,
|
|
13
|
+
septembre: 8,
|
|
14
|
+
octobre: 9,
|
|
15
|
+
novembre: 10,
|
|
16
|
+
décembre: 11,
|
|
17
|
+
};
|
|
18
|
+
const ENGLISH_MONTHS = {
|
|
19
|
+
january: 0,
|
|
20
|
+
february: 1,
|
|
21
|
+
march: 2,
|
|
22
|
+
april: 3,
|
|
23
|
+
may: 4,
|
|
24
|
+
june: 5,
|
|
25
|
+
july: 6,
|
|
26
|
+
august: 7,
|
|
27
|
+
september: 8,
|
|
28
|
+
october: 9,
|
|
29
|
+
november: 10,
|
|
30
|
+
december: 11,
|
|
31
|
+
};
|
|
32
|
+
function tryParseUtcDate(match) {
|
|
33
|
+
const isoMatch = match.match(/(\d{4})-(\d{2})-(\d{2})T(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?Z/);
|
|
34
|
+
if (isoMatch) {
|
|
35
|
+
const [, y, m, d, h, min, sec] = isoMatch;
|
|
36
|
+
const month = parseInt(m, 10) - 1;
|
|
37
|
+
return new Date(Date.UTC(parseInt(y, 10), month, parseInt(d, 10), parseInt(h, 10), parseInt(min, 10), parseInt(sec ?? "0", 10)));
|
|
38
|
+
}
|
|
39
|
+
const longMatch = match.match(/(?:(\w+)\s+)?(\d{1,2})\s+(\w+)\s+(\d{4}),\s*(\d{1,2}):(\d{2})\s*UTC/i);
|
|
40
|
+
if (longMatch) {
|
|
41
|
+
const [, , dayStr, monthStr, yearStr, hourStr, minStr] = longMatch;
|
|
42
|
+
const month = (FRENCH_MONTHS[monthStr.toLowerCase()] ??
|
|
43
|
+
ENGLISH_MONTHS[monthStr.toLowerCase()]);
|
|
44
|
+
if (month !== undefined) {
|
|
45
|
+
return new Date(Date.UTC(parseInt(yearStr, 10), month, parseInt(dayStr, 10), parseInt(hourStr, 10), parseInt(minStr, 10)));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const timeOnlyMatch = match.match(/(\d{1,2}):(\d{2})\s*UTC/);
|
|
49
|
+
if (timeOnlyMatch) {
|
|
50
|
+
const now = new Date();
|
|
51
|
+
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), parseInt(timeOnlyMatch[1], 10), parseInt(timeOnlyMatch[2], 10)));
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function formatLocalDateTime(date, locale) {
|
|
56
|
+
if (typeof Intl === "undefined" || !Intl.DateTimeFormat) {
|
|
57
|
+
return date.toLocaleString();
|
|
58
|
+
}
|
|
59
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
60
|
+
weekday: "long",
|
|
61
|
+
year: "numeric",
|
|
62
|
+
month: "long",
|
|
63
|
+
day: "numeric",
|
|
64
|
+
hour: "2-digit",
|
|
65
|
+
minute: "2-digit",
|
|
66
|
+
hour12: false,
|
|
67
|
+
});
|
|
68
|
+
return formatter.format(date);
|
|
69
|
+
}
|
|
70
|
+
function formatLocalDateTimeShort(date, locale) {
|
|
71
|
+
if (typeof Intl === "undefined" || !Intl.DateTimeFormat) {
|
|
72
|
+
return date.toLocaleString();
|
|
73
|
+
}
|
|
74
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
75
|
+
year: "numeric",
|
|
76
|
+
month: "long",
|
|
77
|
+
day: "numeric",
|
|
78
|
+
hour: "2-digit",
|
|
79
|
+
minute: "2-digit",
|
|
80
|
+
hour12: false,
|
|
81
|
+
});
|
|
82
|
+
return formatter.format(date);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Replaces UTC date-time strings in text with local timezone equivalents.
|
|
86
|
+
*/
|
|
87
|
+
export function transformUtcDatesToLocal(text, locale = typeof navigator !== "undefined"
|
|
88
|
+
? navigator.language
|
|
89
|
+
: "fr-FR") {
|
|
90
|
+
if (!text || typeof text !== "string")
|
|
91
|
+
return text;
|
|
92
|
+
let result = text;
|
|
93
|
+
result = result.replace(/(\d{4}-\d{2}-\d{2}T\d{1,2}:\d{2}(?::\d{2})?(?:\.\d+)?Z)/g, (match) => {
|
|
94
|
+
const date = tryParseUtcDate(match);
|
|
95
|
+
return date ? formatLocalDateTime(date, locale) : match;
|
|
96
|
+
});
|
|
97
|
+
result = result.replace(/((?:\w+\s+)?\d{1,2}\s+\w+\s+\d{4},\s*\d{1,2}:\d{2}\s*)UTC/gi, (match) => {
|
|
98
|
+
const date = tryParseUtcDate(match);
|
|
99
|
+
if (date) {
|
|
100
|
+
return formatLocalDateTime(date, locale);
|
|
101
|
+
}
|
|
102
|
+
return match.replace(/\s*UTC\s*$/i, "");
|
|
103
|
+
});
|
|
104
|
+
result = result.replace(/(\d{1,2}:\d{2})\s*UTC\b/g, (match) => {
|
|
105
|
+
const date = tryParseUtcDate(match);
|
|
106
|
+
return date ? formatLocalDateTimeShort(date, locale) : match.replace(/\s*UTC\s*$/i, "");
|
|
107
|
+
});
|
|
108
|
+
return result;
|
|
109
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gnomondigital/nebulas-kit-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"next": ">=14",
|
|
19
|
+
"express": ">=4"
|
|
20
|
+
},
|
|
21
|
+
"peerDependenciesMeta": {
|
|
22
|
+
"next": {
|
|
23
|
+
"optional": true
|
|
24
|
+
},
|
|
25
|
+
"express": {
|
|
26
|
+
"optional": true
|
|
27
|
+
},
|
|
28
|
+
"@auth0/nextjs-auth0": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/express": "^4.17.21",
|
|
34
|
+
"@types/node": "^20.10.0",
|
|
35
|
+
"express": "^4.18.2",
|
|
36
|
+
"next": "^14.0.0",
|
|
37
|
+
"typescript": "^5.3.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"clean": "rm -rf dist"
|
|
42
|
+
}
|
|
43
|
+
}
|