@123toto/ai-app-assistant-server 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/dist/{ai-sdk-QImICd56.d.cts → ai-sdk-qU3KgNu-.d.cts} +2 -2
- package/dist/{ai-sdk-QImICd56.d.ts → ai-sdk-qU3KgNu-.d.ts} +2 -2
- package/dist/ai-sdk.d.cts +1 -1
- package/dist/ai-sdk.d.ts +1 -1
- package/dist/{chunk-OA7OXUK7.js → chunk-Z4IVCZ5M.js} +15 -15
- package/dist/chunk-Z4IVCZ5M.js.map +1 -0
- package/dist/express.cjs +8 -8
- package/dist/express.cjs.map +1 -1
- package/dist/express.d.cts +11 -11
- package/dist/express.d.ts +11 -11
- package/dist/express.js +7 -7
- package/dist/express.js.map +1 -1
- package/dist/index.cjs +167 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -33
- package/dist/index.d.ts +33 -33
- package/dist/index.js +142 -142
- package/dist/index.js.map +1 -1
- package/dist/{managed-server-7iurKxF1.d.cts → managed-server-BGQ-1jkx.d.ts} +148 -148
- package/dist/{managed-server-CrZumvVU.d.ts → managed-server-CthTKEQ6.d.cts} +148 -148
- package/dist/nest.cjs +31 -31
- package/dist/nest.cjs.map +1 -1
- package/dist/nest.d.cts +12 -12
- package/dist/nest.d.ts +12 -12
- package/dist/nest.js +28 -28
- package/dist/nest.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-OA7OXUK7.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @123toto/ai-app-assistant-server
|
|
2
2
|
|
|
3
|
-
Framework-neutral Node.js backend for contextual
|
|
3
|
+
Framework-neutral Node.js backend for contextual AI App Assistant integrations.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @123toto/ai-app-assistant-server
|
|
@@ -11,9 +11,9 @@ Node.js 20 or later is required.
|
|
|
11
11
|
## Minimal server
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import {
|
|
14
|
+
import { createAiAppAssistantServer } from "@123toto/ai-app-assistant-server";
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const aiAppAssistant = createAiAppAssistantServer({
|
|
17
17
|
model: "mistral:mistral-small-latest",
|
|
18
18
|
documents,
|
|
19
19
|
http: {
|
|
@@ -22,14 +22,14 @@ const aiDocs = createAiDocsServer({
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
export const POST = (request: Request) =>
|
|
25
|
+
export const POST = (request: Request) => aiAppAssistant.fetch.handle(request);
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The selected provider reads its conventional API key from the backend environment. `allowAnonymous: true` is available only for deliberately public prototypes.
|
|
29
29
|
|
|
30
30
|
## Managed server
|
|
31
31
|
|
|
32
|
-
`
|
|
32
|
+
`createManagedAiAppAssistantServer()` adds optional administration features without changing the minimal API:
|
|
33
33
|
|
|
34
34
|
- provider and model discovery;
|
|
35
35
|
- encrypted API-key persistence;
|
|
@@ -40,12 +40,12 @@ The selected provider reads its conventional API key from the backend environmen
|
|
|
40
40
|
- complete Fetch API handlers for chat and administration.
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
import {
|
|
43
|
+
import { createManagedAiAppAssistantServer } from "@123toto/ai-app-assistant-server";
|
|
44
44
|
|
|
45
|
-
const
|
|
45
|
+
const aiAppAssistant = createManagedAiAppAssistantServer({
|
|
46
46
|
configuration: {
|
|
47
47
|
storage: { type: "redis", client: redis },
|
|
48
|
-
encryptionKey: process.env.
|
|
48
|
+
encryptionKey: process.env.AI_APP_ASSISTANT_SECRET_ENCRYPTION_KEY!,
|
|
49
49
|
defaultConfiguration: {
|
|
50
50
|
provider: "mistral",
|
|
51
51
|
model: "mistral-small-latest",
|
|
@@ -60,22 +60,22 @@ const aiDocs = createManagedAiDocsServer({
|
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
await
|
|
63
|
+
await aiAppAssistant.initialize();
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Documents may also be loaded after application bootstrap with `
|
|
66
|
+
Documents may also be loaded after application bootstrap with `aiAppAssistant.setDocuments(documents)`.
|
|
67
67
|
|
|
68
68
|
## Framework connectors
|
|
69
69
|
|
|
70
70
|
```ts
|
|
71
|
-
import {
|
|
71
|
+
import { createManagedAiAppAssistantExpressHandler } from "@123toto/ai-app-assistant-server/express";
|
|
72
72
|
|
|
73
|
-
app.use("/api/ai-
|
|
73
|
+
app.use("/api/ai-app-assistant", createManagedAiAppAssistantExpressHandler(aiAppAssistant));
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
For Nest, import `
|
|
76
|
+
For Nest, import `createManagedAiAppAssistantNestModule` from `@123toto/ai-app-assistant-server/nest`. Nest, RxJS and reflect-metadata are optional peer dependencies and are not required by other consumers.
|
|
77
77
|
|
|
78
|
-
Native Node and Fetch-compatible runtimes can use `
|
|
78
|
+
Native Node and Fetch-compatible runtimes can use `createAiAppAssistantNodeHttpListener` or the standard `Request`/`Response` handlers.
|
|
79
79
|
|
|
80
80
|
## Providers
|
|
81
81
|
|
|
@@ -76,7 +76,7 @@ interface AnswerGenerator {
|
|
|
76
76
|
stream?(input: EvidenceBundle, options?: GenerationOptions): AsyncGenerator<GenerationProgress, GenerationResult>;
|
|
77
77
|
}
|
|
78
78
|
/** Configuration shared by all calls made through an assistant instance. */
|
|
79
|
-
interface
|
|
79
|
+
interface AiAppAssistantOptions {
|
|
80
80
|
generator: AnswerGenerator;
|
|
81
81
|
/**
|
|
82
82
|
* Application documentation loaded and serialized once when the assistant is
|
|
@@ -185,4 +185,4 @@ declare function normalizeAiSdkGenerationError(error: unknown, attempts: number)
|
|
|
185
185
|
/** Retries only failures that may succeed unchanged; configuration stays immediate. */
|
|
186
186
|
declare function isRetryableProviderError(error: unknown): boolean;
|
|
187
187
|
|
|
188
|
-
export { type AnswerGenerator as A, type DocumentationSource as D, type EvidenceBundle as E, type GenerationOptions as G, type ModelCapabilities as M, type OpenApiDocument as O, type
|
|
188
|
+
export { type AnswerGenerator as A, type DocumentationSource as D, type EvidenceBundle as E, type GenerationOptions as G, type ModelCapabilities as M, type OpenApiDocument as O, type AiAppAssistantOptions as a, AiSdkConfigurationError as b, type AiSdkConnectionTestOptions as c, type AiSdkConnectionTestResult as d, type AiSdkFailureCode as e, AiSdkGenerationError as f, type AiSdkGeneratorOptions as g, type EvidenceItem as h, type GenerationProgress as i, createAiSdkGenerator as j, isRetryableProviderError as k, normalizeAiSdkGenerationError as n, testAiSdkConnection as t };
|
|
@@ -76,7 +76,7 @@ interface AnswerGenerator {
|
|
|
76
76
|
stream?(input: EvidenceBundle, options?: GenerationOptions): AsyncGenerator<GenerationProgress, GenerationResult>;
|
|
77
77
|
}
|
|
78
78
|
/** Configuration shared by all calls made through an assistant instance. */
|
|
79
|
-
interface
|
|
79
|
+
interface AiAppAssistantOptions {
|
|
80
80
|
generator: AnswerGenerator;
|
|
81
81
|
/**
|
|
82
82
|
* Application documentation loaded and serialized once when the assistant is
|
|
@@ -185,4 +185,4 @@ declare function normalizeAiSdkGenerationError(error: unknown, attempts: number)
|
|
|
185
185
|
/** Retries only failures that may succeed unchanged; configuration stays immediate. */
|
|
186
186
|
declare function isRetryableProviderError(error: unknown): boolean;
|
|
187
187
|
|
|
188
|
-
export { type AnswerGenerator as A, type DocumentationSource as D, type EvidenceBundle as E, type GenerationOptions as G, type ModelCapabilities as M, type OpenApiDocument as O, type
|
|
188
|
+
export { type AnswerGenerator as A, type DocumentationSource as D, type EvidenceBundle as E, type GenerationOptions as G, type ModelCapabilities as M, type OpenApiDocument as O, type AiAppAssistantOptions as a, AiSdkConfigurationError as b, type AiSdkConnectionTestOptions as c, type AiSdkConnectionTestResult as d, type AiSdkFailureCode as e, AiSdkGenerationError as f, type AiSdkGeneratorOptions as g, type EvidenceItem as h, type GenerationProgress as i, createAiSdkGenerator as j, isRetryableProviderError as k, normalizeAiSdkGenerationError as n, testAiSdkConnection as t };
|
package/dist/ai-sdk.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'ai';
|
|
2
|
-
export { b as AiSdkConfigurationError, c as AiSdkConnectionTestOptions, d as AiSdkConnectionTestResult, e as AiSdkFailureCode, f as AiSdkGenerationError, g as AiSdkGeneratorOptions, j as createAiSdkGenerator, k as isRetryableProviderError, n as normalizeAiSdkGenerationError, t as testAiSdkConnection } from './ai-sdk-
|
|
2
|
+
export { b as AiSdkConfigurationError, c as AiSdkConnectionTestOptions, d as AiSdkConnectionTestResult, e as AiSdkFailureCode, f as AiSdkGenerationError, g as AiSdkGeneratorOptions, j as createAiSdkGenerator, k as isRetryableProviderError, n as normalizeAiSdkGenerationError, t as testAiSdkConnection } from './ai-sdk-qU3KgNu-.cjs';
|
|
3
3
|
import '@123toto/ai-app-assistant-contracts';
|
package/dist/ai-sdk.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'ai';
|
|
2
|
-
export { b as AiSdkConfigurationError, c as AiSdkConnectionTestOptions, d as AiSdkConnectionTestResult, e as AiSdkFailureCode, f as AiSdkGenerationError, g as AiSdkGeneratorOptions, j as createAiSdkGenerator, k as isRetryableProviderError, n as normalizeAiSdkGenerationError, t as testAiSdkConnection } from './ai-sdk-
|
|
2
|
+
export { b as AiSdkConfigurationError, c as AiSdkConnectionTestOptions, d as AiSdkConnectionTestResult, e as AiSdkFailureCode, f as AiSdkGenerationError, g as AiSdkGeneratorOptions, j as createAiSdkGenerator, k as isRetryableProviderError, n as normalizeAiSdkGenerationError, t as testAiSdkConnection } from './ai-sdk-qU3KgNu-.js';
|
|
3
3
|
import '@123toto/ai-app-assistant-contracts';
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
// src/http.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
aiAppAssistantRequestSchema
|
|
4
4
|
} from "@123toto/ai-app-assistant-contracts";
|
|
5
5
|
import { ZodError } from "zod";
|
|
6
|
-
var
|
|
6
|
+
var AiAppAssistantRequestError = class extends Error {
|
|
7
7
|
constructor(status, code, message) {
|
|
8
8
|
super(message);
|
|
9
9
|
this.status = status;
|
|
10
10
|
this.code = code;
|
|
11
|
-
this.name = "
|
|
11
|
+
this.name = "AiAppAssistantRequestError";
|
|
12
12
|
}
|
|
13
13
|
status;
|
|
14
14
|
code;
|
|
15
15
|
};
|
|
16
|
-
function
|
|
16
|
+
function createAiAppAssistantFetchHandlers(options) {
|
|
17
17
|
const prepare = async (request) => {
|
|
18
18
|
assertPost(request);
|
|
19
19
|
const raw = await readJson(request, options.maxBodyBytes ?? 86e5);
|
|
20
|
-
const validated =
|
|
20
|
+
const validated = aiAppAssistantRequestSchema.parse(raw);
|
|
21
21
|
if (!options.resolveContext && !options.authorize && !options.allowAnonymous) {
|
|
22
|
-
throw new
|
|
22
|
+
throw new AiAppAssistantRequestError(401, "unauthorized", "Authentication is required");
|
|
23
23
|
}
|
|
24
24
|
const context = options.resolveContext ? await options.resolveContext(request) : void 0;
|
|
25
25
|
await options.authorize?.(context, request);
|
|
26
26
|
const input = options.transformRequest ? await options.transformRequest(validated, context) : validated;
|
|
27
|
-
return { input:
|
|
27
|
+
return { input: aiAppAssistantRequestSchema.parse(input), context };
|
|
28
28
|
};
|
|
29
29
|
const present = async (response, context) => options.transformResponse ? options.transformResponse(response, context) : response;
|
|
30
30
|
const ask = async (request) => {
|
|
@@ -87,27 +87,27 @@ function createAiDocsFetchHandlers(options) {
|
|
|
87
87
|
}
|
|
88
88
|
function assertPost(request) {
|
|
89
89
|
if (request.method !== "POST") {
|
|
90
|
-
throw new
|
|
90
|
+
throw new AiAppAssistantRequestError(405, "method_not_allowed", "Only POST is supported");
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
async function readJson(request, maxBodyBytes) {
|
|
94
94
|
const contentLength = Number(request.headers.get("content-length"));
|
|
95
95
|
if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
|
|
96
|
-
throw new
|
|
96
|
+
throw new AiAppAssistantRequestError(413, "request_too_large", "Request body is too large");
|
|
97
97
|
}
|
|
98
98
|
const text = await request.text();
|
|
99
99
|
if (new TextEncoder().encode(text).byteLength > maxBodyBytes) {
|
|
100
|
-
throw new
|
|
100
|
+
throw new AiAppAssistantRequestError(413, "request_too_large", "Request body is too large");
|
|
101
101
|
}
|
|
102
102
|
try {
|
|
103
103
|
return JSON.parse(text);
|
|
104
104
|
} catch {
|
|
105
|
-
throw new
|
|
105
|
+
throw new AiAppAssistantRequestError(400, "invalid_json", "Request body must be valid JSON");
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
async function mapError(error, request, customMapper) {
|
|
109
109
|
if (customMapper) return customMapper(error, request);
|
|
110
|
-
if (error instanceof
|
|
110
|
+
if (error instanceof AiAppAssistantRequestError) {
|
|
111
111
|
return jsonResponse({ error: error.code, message: error.message }, error.status);
|
|
112
112
|
}
|
|
113
113
|
if (error instanceof ZodError) {
|
|
@@ -130,7 +130,7 @@ function jsonResponse(value, status = 200) {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
export {
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
AiAppAssistantRequestError,
|
|
134
|
+
createAiAppAssistantFetchHandlers
|
|
135
135
|
};
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
136
|
+
//# sourceMappingURL=chunk-Z4IVCZ5M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http.ts"],"sourcesContent":["import {\n aiAppAssistantRequestSchema,\n type AiAppAssistantRequest,\n type AiAppAssistantResponse\n} from \"@123toto/ai-app-assistant-contracts\";\nimport { ZodError } from \"zod\";\nimport type { AiAppAssistant, AiAppAssistantStreamEvent } from \"./assistant.js\";\n\nexport interface AiAppAssistantFetchHandlerOptions<TContext = undefined> {\n assistant: AiAppAssistant;\n /** Resolves framework-specific authentication into an application context. */\n resolveContext?: (request: Request) => Promise<TContext> | TContext;\n /** Optional application authorization executed before assistant work. */\n authorize?: (context: TContext, request: Request) => Promise<void> | void;\n /** Explicit opt-in for public prototypes without authentication hooks. */\n allowAnonymous?: boolean;\n /** Optional privacy/input policy applied after protocol validation. */\n transformRequest?: (\n input: AiAppAssistantRequest,\n context: TContext\n ) => Promise<AiAppAssistantRequest> | AiAppAssistantRequest;\n /** Optional privacy/output policy applied before serialization. */\n transformResponse?: (\n response: AiAppAssistantResponse,\n context: TContext\n ) => Promise<AiAppAssistantResponse> | AiAppAssistantResponse;\n /** Applies privacy rules to progressive text before it leaves the backend. */\n transformStreamEvent?: (\n event: AiAppAssistantStreamEvent,\n context: TContext\n ) => Promise<AiAppAssistantStreamEvent> | AiAppAssistantStreamEvent;\n /** Maximum HTTP request size. Defaults slightly above the protocol limit. */\n maxBodyBytes?: number;\n /** Maps application errors without coupling the library to one framework. */\n onError?: (error: unknown, request: Request) => Promise<Response> | Response;\n}\n\nexport interface AiAppAssistantFetchHandlers {\n ask(request: Request): Promise<Response>;\n stream(request: Request): Promise<Response>;\n /** Dispatches to `stream` when the pathname ends in `/stream`, otherwise to `ask`. */\n handle(request: Request): Promise<Response>;\n}\n\n/** Explicit safe HTTP error that host authorization hooks can throw. */\nexport class AiAppAssistantRequestError extends Error {\n public constructor(\n readonly status: number,\n readonly code: string,\n message: string\n ) {\n super(message);\n this.name = \"AiAppAssistantRequestError\";\n }\n}\n\n/**\n * Creates Fetch API handlers usable by standards-based runtimes and thin\n * Express, Fastify, Nest, Next.js, Hono, Bun or serverless adapters.\n */\nexport function createAiAppAssistantFetchHandlers<TContext = undefined>(\n options: AiAppAssistantFetchHandlerOptions<TContext>\n): AiAppAssistantFetchHandlers {\n const prepare = async (request: Request): Promise<{\n input: AiAppAssistantRequest;\n context: TContext;\n }> => {\n assertPost(request);\n const raw = await readJson(request, options.maxBodyBytes ?? 8_600_000);\n const validated = aiAppAssistantRequestSchema.parse(raw);\n if (!options.resolveContext && !options.authorize && !options.allowAnonymous) {\n throw new AiAppAssistantRequestError(401, \"unauthorized\", \"Authentication is required\");\n }\n const context = options.resolveContext\n ? await options.resolveContext(request)\n : undefined as TContext;\n await options.authorize?.(context, request);\n const input = options.transformRequest\n ? await options.transformRequest(validated, context)\n : validated;\n return { input: aiAppAssistantRequestSchema.parse(input), context };\n };\n\n const present = async (\n response: AiAppAssistantResponse,\n context: TContext\n ): Promise<AiAppAssistantResponse> => options.transformResponse\n ? options.transformResponse(response, context)\n : response;\n\n const ask = async (request: Request): Promise<Response> => {\n try {\n const { input, context } = await prepare(request);\n return jsonResponse(await present(await options.assistant.answer(input, {\n signal: request.signal\n }), context));\n } catch (error) {\n return mapError(error, request, options.onError);\n }\n };\n\n const stream = async (request: Request): Promise<Response> => {\n try {\n const { input, context } = await prepare(request);\n const encoder = new TextEncoder();\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n const generation = options.assistant.stream(input, { signal: request.signal });\n while (true) {\n const next = await generation.next();\n if (next.done) break;\n let event = next.value.type === \"complete\"\n ? { ...next.value, response: await present(next.value.response, context) }\n : next.value;\n if (options.transformStreamEvent) {\n event = await options.transformStreamEvent(event, context);\n }\n controller.enqueue(encoder.encode(`${JSON.stringify(event)}\\n`));\n }\n } catch {\n controller.enqueue(encoder.encode(`${JSON.stringify({\n type: \"error\",\n message: \"The assistant response could not be generated.\",\n retryable: false\n })}\\n`));\n } finally {\n controller.close();\n }\n }\n });\n return new Response(body, {\n status: 200,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"application/x-ndjson; charset=utf-8\",\n \"x-content-type-options\": \"nosniff\"\n }\n });\n } catch (error) {\n return mapError(error, request, options.onError);\n }\n };\n\n return {\n ask,\n stream,\n handle: (request) => new URL(request.url).pathname.endsWith(\"/stream\")\n ? stream(request)\n : ask(request)\n };\n}\n\nfunction assertPost(request: Request): void {\n if (request.method !== \"POST\") {\n throw new AiAppAssistantRequestError(405, \"method_not_allowed\", \"Only POST is supported\");\n }\n}\n\nasync function readJson(request: Request, maxBodyBytes: number): Promise<unknown> {\n const contentLength = Number(request.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {\n throw new AiAppAssistantRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n const text = await request.text();\n if (new TextEncoder().encode(text).byteLength > maxBodyBytes) {\n throw new AiAppAssistantRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n try {\n return JSON.parse(text) as unknown;\n } catch {\n throw new AiAppAssistantRequestError(400, \"invalid_json\", \"Request body must be valid JSON\");\n }\n}\n\nasync function mapError(\n error: unknown,\n request: Request,\n customMapper?: AiAppAssistantFetchHandlerOptions<unknown>[\"onError\"]\n): Promise<Response> {\n if (customMapper) return customMapper(error, request);\n if (error instanceof AiAppAssistantRequestError) {\n return jsonResponse({ error: error.code, message: error.message }, error.status);\n }\n if (error instanceof ZodError) {\n return jsonResponse({ error: \"invalid_request\", message: \"The assistant request is invalid.\" }, 400);\n }\n return jsonResponse({\n error: \"assistant_error\",\n message: \"The assistant response could not be generated.\"\n }, 500);\n}\n\nfunction jsonResponse(value: unknown, status = 200): Response {\n return new Response(JSON.stringify(value), {\n status,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"application/json; charset=utf-8\",\n \"x-content-type-options\": \"nosniff\"\n }\n });\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,OAGK;AACP,SAAS,gBAAgB;AAwClB,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAC7C,YACI,QACA,MACT,SACA;AACA,UAAM,OAAO;AAJJ;AACA;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EANW;AAAA,EACA;AAMb;AAMO,SAAS,kCACd,SAC6B;AAC7B,QAAM,UAAU,OAAO,YAGjB;AACJ,eAAW,OAAO;AAClB,UAAM,MAAM,MAAM,SAAS,SAAS,QAAQ,gBAAgB,IAAS;AACrE,UAAM,YAAY,4BAA4B,MAAM,GAAG;AACvD,QAAI,CAAC,QAAQ,kBAAkB,CAAC,QAAQ,aAAa,CAAC,QAAQ,gBAAgB;AAC5E,YAAM,IAAI,2BAA2B,KAAK,gBAAgB,4BAA4B;AAAA,IACxF;AACA,UAAM,UAAU,QAAQ,iBACpB,MAAM,QAAQ,eAAe,OAAO,IACpC;AACJ,UAAM,QAAQ,YAAY,SAAS,OAAO;AAC1C,UAAM,QAAQ,QAAQ,mBAClB,MAAM,QAAQ,iBAAiB,WAAW,OAAO,IACjD;AACJ,WAAO,EAAE,OAAO,4BAA4B,MAAM,KAAK,GAAG,QAAQ;AAAA,EACpE;AAEA,QAAM,UAAU,OACd,UACA,YACoC,QAAQ,oBAC1C,QAAQ,kBAAkB,UAAU,OAAO,IAC3C;AAEJ,QAAM,MAAM,OAAO,YAAwC;AACzD,QAAI;AACF,YAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,QAAQ,OAAO;AAChD,aAAO,aAAa,MAAM,QAAQ,MAAM,QAAQ,UAAU,OAAO,OAAO;AAAA,QACtE,QAAQ,QAAQ;AAAA,MAClB,CAAC,GAAG,OAAO,CAAC;AAAA,IACd,SAAS,OAAO;AACd,aAAO,SAAS,OAAO,SAAS,QAAQ,OAAO;AAAA,IACjD;AAAA,EACF;AAEA,QAAM,SAAS,OAAO,YAAwC;AAC5D,QAAI;AACF,YAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,QAAQ,OAAO;AAChD,YAAM,UAAU,IAAI,YAAY;AAChC,YAAM,OAAO,IAAI,eAA2B;AAAA,QAC1C,MAAM,MAAM,YAAY;AACtB,cAAI;AACF,kBAAM,aAAa,QAAQ,UAAU,OAAO,OAAO,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAC7E,mBAAO,MAAM;AACX,oBAAM,OAAO,MAAM,WAAW,KAAK;AACnC,kBAAI,KAAK,KAAM;AACf,kBAAI,QAAQ,KAAK,MAAM,SAAS,aAC5B,EAAE,GAAG,KAAK,OAAO,UAAU,MAAM,QAAQ,KAAK,MAAM,UAAU,OAAO,EAAE,IACvE,KAAK;AACT,kBAAI,QAAQ,sBAAsB;AAChC,wBAAQ,MAAM,QAAQ,qBAAqB,OAAO,OAAO;AAAA,cAC3D;AACA,yBAAW,QAAQ,QAAQ,OAAO,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI,CAAC;AAAA,YACjE;AAAA,UACF,QAAQ;AACN,uBAAW,QAAQ,QAAQ,OAAO,GAAG,KAAK,UAAU;AAAA,cAClD,MAAM;AAAA,cACN,SAAS;AAAA,cACT,WAAW;AAAA,YACb,CAAC,CAAC;AAAA,CAAI,CAAC;AAAA,UACT,UAAE;AACA,uBAAW,MAAM;AAAA,UACnB;AAAA,QACF;AAAA,MACF,CAAC;AACD,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,UAChB,0BAA0B;AAAA,QAC5B;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,aAAO,SAAS,OAAO,SAAS,QAAQ,OAAO;AAAA,IACjD;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ,CAAC,YAAY,IAAI,IAAI,QAAQ,GAAG,EAAE,SAAS,SAAS,SAAS,IACjE,OAAO,OAAO,IACd,IAAI,OAAO;AAAA,EACjB;AACF;AAEA,SAAS,WAAW,SAAwB;AAC1C,MAAI,QAAQ,WAAW,QAAQ;AAC7B,UAAM,IAAI,2BAA2B,KAAK,sBAAsB,wBAAwB;AAAA,EAC1F;AACF;AAEA,eAAe,SAAS,SAAkB,cAAwC;AAChF,QAAM,gBAAgB,OAAO,QAAQ,QAAQ,IAAI,gBAAgB,CAAC;AAClE,MAAI,OAAO,SAAS,aAAa,KAAK,gBAAgB,cAAc;AAClE,UAAM,IAAI,2BAA2B,KAAK,qBAAqB,2BAA2B;AAAA,EAC5F;AACA,QAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,MAAI,IAAI,YAAY,EAAE,OAAO,IAAI,EAAE,aAAa,cAAc;AAC5D,UAAM,IAAI,2BAA2B,KAAK,qBAAqB,2BAA2B;AAAA,EAC5F;AACA,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,2BAA2B,KAAK,gBAAgB,iCAAiC;AAAA,EAC7F;AACF;AAEA,eAAe,SACb,OACA,SACA,cACmB;AACnB,MAAI,aAAc,QAAO,aAAa,OAAO,OAAO;AACpD,MAAI,iBAAiB,4BAA4B;AAC/C,WAAO,aAAa,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ,GAAG,MAAM,MAAM;AAAA,EACjF;AACA,MAAI,iBAAiB,UAAU;AAC7B,WAAO,aAAa,EAAE,OAAO,mBAAmB,SAAS,oCAAoC,GAAG,GAAG;AAAA,EACrG;AACA,SAAO,aAAa;AAAA,IAClB,OAAO;AAAA,IACP,SAAS;AAAA,EACX,GAAG,GAAG;AACR;AAEA,SAAS,aAAa,OAAgB,SAAS,KAAe;AAC5D,SAAO,IAAI,SAAS,KAAK,UAAU,KAAK,GAAG;AAAA,IACzC;AAAA,IACA,SAAS;AAAA,MACP,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,MAChB,0BAA0B;AAAA,IAC5B;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/express.cjs
CHANGED
|
@@ -20,26 +20,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/express.ts
|
|
21
21
|
var express_exports = {};
|
|
22
22
|
__export(express_exports, {
|
|
23
|
-
|
|
23
|
+
createManagedAiAppAssistantExpressHandler: () => createManagedAiAppAssistantExpressHandler
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(express_exports);
|
|
26
26
|
|
|
27
27
|
// src/http.ts
|
|
28
28
|
var import_ai_app_assistant_contracts = require("@123toto/ai-app-assistant-contracts");
|
|
29
29
|
var import_zod = require("zod");
|
|
30
|
-
var
|
|
30
|
+
var AiAppAssistantRequestError = class extends Error {
|
|
31
31
|
constructor(status, code, message) {
|
|
32
32
|
super(message);
|
|
33
33
|
this.status = status;
|
|
34
34
|
this.code = code;
|
|
35
|
-
this.name = "
|
|
35
|
+
this.name = "AiAppAssistantRequestError";
|
|
36
36
|
}
|
|
37
37
|
status;
|
|
38
38
|
code;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
// src/express.ts
|
|
42
|
-
function
|
|
42
|
+
function createManagedAiAppAssistantExpressHandler(server, options = {}) {
|
|
43
43
|
return async (request, response, next) => {
|
|
44
44
|
const abortController = new AbortController();
|
|
45
45
|
const abort = () => abortController.abort(new DOMException("Client disconnected", "AbortError"));
|
|
@@ -96,7 +96,7 @@ async function readBody(request, maxBodyBytes) {
|
|
|
96
96
|
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : new Uint8Array(chunk);
|
|
97
97
|
size += bytes.byteLength;
|
|
98
98
|
if (size > maxBodyBytes) {
|
|
99
|
-
throw new
|
|
99
|
+
throw new AiAppAssistantRequestError(413, "request_too_large", "Request body is too large");
|
|
100
100
|
}
|
|
101
101
|
chunks.push(bytes);
|
|
102
102
|
}
|
|
@@ -124,14 +124,14 @@ async function writeFetchResponse(response, result) {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
function writeConnectorError(response, error) {
|
|
127
|
-
const status = error instanceof
|
|
128
|
-
const code = error instanceof
|
|
127
|
+
const status = error instanceof AiAppAssistantRequestError ? error.status : 500;
|
|
128
|
+
const code = error instanceof AiAppAssistantRequestError ? error.code : "assistant_error";
|
|
129
129
|
response.statusCode = status;
|
|
130
130
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
131
131
|
response.end(JSON.stringify({ error: code }));
|
|
132
132
|
}
|
|
133
133
|
// Annotate the CommonJS export names for ESM import in node:
|
|
134
134
|
0 && (module.exports = {
|
|
135
|
-
|
|
135
|
+
createManagedAiAppAssistantExpressHandler
|
|
136
136
|
});
|
|
137
137
|
//# sourceMappingURL=express.cjs.map
|
package/dist/express.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/express.ts","../src/http.ts"],"sourcesContent":["import { AiDocsRequestError } from \"./http.js\";\nimport type { ManagedAiDocsServer } from \"./managed-server.js\";\n\n/** Minimal Express request shape; importing Express types is intentionally unnecessary. */\nexport interface AiDocsExpressRequest {\n method?: string;\n originalUrl?: string;\n url?: string;\n protocol?: string;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n on?(event: string, listener: () => void): unknown;\n off?(event: string, listener: () => void): unknown;\n [key: string]: unknown;\n}\n\n/** Minimal Express response shape used by the connector. */\nexport interface AiDocsExpressResponse {\n statusCode: number;\n setHeader(name: string, value: string): unknown;\n write(chunk: Uint8Array): boolean;\n end(chunk?: string): unknown;\n once?(event: string, listener: () => void): unknown;\n destroyed?: boolean;\n}\n\nexport type AiDocsExpressNext = (error?: unknown) => void;\n\nexport type AiDocsExpressManagedServer<\n TRequest extends AiDocsExpressRequest = AiDocsExpressRequest\n> = Pick<ManagedAiDocsServer<{ id: string; label: string }, TRequest>, \"fetch\">;\n\nexport interface AiDocsExpressHandlerOptions {\n /** Origin used only when Express does not expose a Host header. */\n origin?: string;\n /** Maximum size accepted when no JSON body parser populated request.body. */\n maxBodyBytes?: number;\n /** Lets another Express handler process unknown routes below the mounted prefix. */\n fallthrough?: boolean;\n}\n\nexport type AiDocsExpressHandler<TRequest extends AiDocsExpressRequest> = (\n request: TRequest,\n response: AiDocsExpressResponse,\n next?: AiDocsExpressNext\n) => Promise<void>;\n\n/**\n * Creates an Express-compatible handler for the complete managed API.\n *\n * @example\n * `app.use(\"/api/ai-docs\", createManagedAiDocsExpressHandler(aiDocs));`\n */\nexport function createManagedAiDocsExpressHandler<\n TRequest extends AiDocsExpressRequest = AiDocsExpressRequest\n>(\n server: AiDocsExpressManagedServer<TRequest>,\n options: AiDocsExpressHandlerOptions = {}\n): AiDocsExpressHandler<TRequest> {\n return async (request, response, next) => {\n const abortController = new AbortController();\n const abort = (): void => abortController.abort(new DOMException(\"Client disconnected\", \"AbortError\"));\n request.on?.(\"close\", abort);\n try {\n const webRequest = await toFetchRequest(request, abortController.signal, options);\n const result = await server.fetch.handle(webRequest, request);\n if (options.fallthrough && result.status === 404 && next) {\n next();\n return;\n }\n await writeFetchResponse(response, result);\n } catch (error) {\n if (next) {\n next(error);\n return;\n }\n writeConnectorError(response, error);\n } finally {\n request.off?.(\"close\", abort);\n }\n };\n}\n\nasync function toFetchRequest(\n request: AiDocsExpressRequest,\n signal: AbortSignal,\n options: AiDocsExpressHandlerOptions\n): Promise<Request> {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (Array.isArray(value)) value.forEach((item) => headers.append(name, item));\n else if (value !== undefined) headers.set(name, value);\n }\n const method = request.method?.toUpperCase() || \"GET\";\n const host = headers.get(\"host\");\n const protocol = request.protocol || headers.get(\"x-forwarded-proto\") || \"http\";\n const origin = options.origin ?? `${protocol}://${host || \"localhost\"}`;\n // originalUrl preserves the mount prefix stripped from request.url by Express.\n const url = new URL(request.originalUrl || request.url || \"/\", origin);\n const body = method === \"GET\" || method === \"HEAD\"\n ? undefined\n : request.body !== undefined\n ? serializeBody(request.body, headers)\n : await readBody(request, options.maxBodyBytes ?? 8_600_000);\n return new Request(url, {\n method,\n headers,\n signal,\n ...(body !== undefined ? { body } : {})\n });\n}\n\nfunction serializeBody(body: unknown, headers: Headers): string {\n if (typeof body === \"string\") return body;\n if (body instanceof Uint8Array) return new TextDecoder().decode(body);\n if (!headers.has(\"content-type\")) headers.set(\"content-type\", \"application/json\");\n return JSON.stringify(body) ?? \"null\";\n}\n\nasync function readBody(request: AiDocsExpressRequest, maxBodyBytes: number): Promise<string | undefined> {\n if (!(Symbol.asyncIterator in request)) return undefined;\n const chunks: Uint8Array[] = [];\n let size = 0;\n for await (const chunk of request as AsyncIterable<string | Uint8Array>) {\n const bytes = typeof chunk === \"string\" ? Buffer.from(chunk) : new Uint8Array(chunk);\n size += bytes.byteLength;\n if (size > maxBodyBytes) {\n throw new AiDocsRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n chunks.push(bytes);\n }\n return chunks.length ? Buffer.concat(chunks).toString(\"utf8\") : undefined;\n}\n\nasync function writeFetchResponse(response: AiDocsExpressResponse, result: Response): Promise<void> {\n response.statusCode = result.status;\n result.headers.forEach((value, name) => response.setHeader(name, value));\n if (!result.body) {\n response.end();\n return;\n }\n const reader = result.body.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (!response.write(value) && response.once) {\n await new Promise<void>((resolve) => response.once!(\"drain\", resolve));\n }\n }\n if (!response.destroyed) response.end();\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction writeConnectorError(response: AiDocsExpressResponse, error: unknown): void {\n const status = error instanceof AiDocsRequestError ? error.status : 500;\n const code = error instanceof AiDocsRequestError ? error.code : \"assistant_error\";\n response.statusCode = status;\n response.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n response.end(JSON.stringify({ error: code }));\n}\n","import {\n askDocumentationRequestSchema,\n type AskDocumentationRequest,\n type AskDocumentationResponse\n} from \"@123toto/ai-app-assistant-contracts\";\nimport { ZodError } from \"zod\";\nimport type { DocsAssistant, DocsAssistantStreamEvent } from \"./assistant.js\";\n\nexport interface AiDocsFetchHandlerOptions<TContext = undefined> {\n assistant: DocsAssistant;\n /** Resolves framework-specific authentication into an application context. */\n resolveContext?: (request: Request) => Promise<TContext> | TContext;\n /** Optional application authorization executed before assistant work. */\n authorize?: (context: TContext, request: Request) => Promise<void> | void;\n /** Explicit opt-in for public prototypes without authentication hooks. */\n allowAnonymous?: boolean;\n /** Optional privacy/input policy applied after protocol validation. */\n transformRequest?: (\n input: AskDocumentationRequest,\n context: TContext\n ) => Promise<AskDocumentationRequest> | AskDocumentationRequest;\n /** Optional privacy/output policy applied before serialization. */\n transformResponse?: (\n response: AskDocumentationResponse,\n context: TContext\n ) => Promise<AskDocumentationResponse> | AskDocumentationResponse;\n /** Applies privacy rules to progressive text before it leaves the backend. */\n transformStreamEvent?: (\n event: DocsAssistantStreamEvent,\n context: TContext\n ) => Promise<DocsAssistantStreamEvent> | DocsAssistantStreamEvent;\n /** Maximum HTTP request size. Defaults slightly above the protocol limit. */\n maxBodyBytes?: number;\n /** Maps application errors without coupling the library to one framework. */\n onError?: (error: unknown, request: Request) => Promise<Response> | Response;\n}\n\nexport interface AiDocsFetchHandlers {\n ask(request: Request): Promise<Response>;\n stream(request: Request): Promise<Response>;\n /** Dispatches to `stream` when the pathname ends in `/stream`, otherwise to `ask`. */\n handle(request: Request): Promise<Response>;\n}\n\n/** Explicit safe HTTP error that host authorization hooks can throw. */\nexport class AiDocsRequestError extends Error {\n public constructor(\n readonly status: number,\n readonly code: string,\n message: string\n ) {\n super(message);\n this.name = \"AiDocsRequestError\";\n }\n}\n\n/**\n * Creates Fetch API handlers usable by standards-based runtimes and thin\n * Express, Fastify, Nest, Next.js, Hono, Bun or serverless adapters.\n */\nexport function createAiDocsFetchHandlers<TContext = undefined>(\n options: AiDocsFetchHandlerOptions<TContext>\n): AiDocsFetchHandlers {\n const prepare = async (request: Request): Promise<{\n input: AskDocumentationRequest;\n context: TContext;\n }> => {\n assertPost(request);\n const raw = await readJson(request, options.maxBodyBytes ?? 8_600_000);\n const validated = askDocumentationRequestSchema.parse(raw);\n if (!options.resolveContext && !options.authorize && !options.allowAnonymous) {\n throw new AiDocsRequestError(401, \"unauthorized\", \"Authentication is required\");\n }\n const context = options.resolveContext\n ? await options.resolveContext(request)\n : undefined as TContext;\n await options.authorize?.(context, request);\n const input = options.transformRequest\n ? await options.transformRequest(validated, context)\n : validated;\n return { input: askDocumentationRequestSchema.parse(input), context };\n };\n\n const present = async (\n response: AskDocumentationResponse,\n context: TContext\n ): Promise<AskDocumentationResponse> => options.transformResponse\n ? options.transformResponse(response, context)\n : response;\n\n const ask = async (request: Request): Promise<Response> => {\n try {\n const { input, context } = await prepare(request);\n return jsonResponse(await present(await options.assistant.answer(input, {\n signal: request.signal\n }), context));\n } catch (error) {\n return mapError(error, request, options.onError);\n }\n };\n\n const stream = async (request: Request): Promise<Response> => {\n try {\n const { input, context } = await prepare(request);\n const encoder = new TextEncoder();\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n const generation = options.assistant.stream(input, { signal: request.signal });\n while (true) {\n const next = await generation.next();\n if (next.done) break;\n let event = next.value.type === \"complete\"\n ? { ...next.value, response: await present(next.value.response, context) }\n : next.value;\n if (options.transformStreamEvent) {\n event = await options.transformStreamEvent(event, context);\n }\n controller.enqueue(encoder.encode(`${JSON.stringify(event)}\\n`));\n }\n } catch {\n controller.enqueue(encoder.encode(`${JSON.stringify({\n type: \"error\",\n message: \"The assistant response could not be generated.\",\n retryable: false\n })}\\n`));\n } finally {\n controller.close();\n }\n }\n });\n return new Response(body, {\n status: 200,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"application/x-ndjson; charset=utf-8\",\n \"x-content-type-options\": \"nosniff\"\n }\n });\n } catch (error) {\n return mapError(error, request, options.onError);\n }\n };\n\n return {\n ask,\n stream,\n handle: (request) => new URL(request.url).pathname.endsWith(\"/stream\")\n ? stream(request)\n : ask(request)\n };\n}\n\nfunction assertPost(request: Request): void {\n if (request.method !== \"POST\") {\n throw new AiDocsRequestError(405, \"method_not_allowed\", \"Only POST is supported\");\n }\n}\n\nasync function readJson(request: Request, maxBodyBytes: number): Promise<unknown> {\n const contentLength = Number(request.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {\n throw new AiDocsRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n const text = await request.text();\n if (new TextEncoder().encode(text).byteLength > maxBodyBytes) {\n throw new AiDocsRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n try {\n return JSON.parse(text) as unknown;\n } catch {\n throw new AiDocsRequestError(400, \"invalid_json\", \"Request body must be valid JSON\");\n }\n}\n\nasync function mapError(\n error: unknown,\n request: Request,\n customMapper?: AiDocsFetchHandlerOptions<unknown>[\"onError\"]\n): Promise<Response> {\n if (customMapper) return customMapper(error, request);\n if (error instanceof AiDocsRequestError) {\n return jsonResponse({ error: error.code, message: error.message }, error.status);\n }\n if (error instanceof ZodError) {\n return jsonResponse({ error: \"invalid_request\", message: \"The assistant request is invalid.\" }, 400);\n }\n return jsonResponse({\n error: \"assistant_error\",\n message: \"The assistant response could not be generated.\"\n }, 500);\n}\n\nfunction jsonResponse(value: unknown, status = 200): Response {\n return new Response(JSON.stringify(value), {\n status,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"application/json; charset=utf-8\",\n \"x-content-type-options\": \"nosniff\"\n }\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/express.ts","../src/http.ts"],"sourcesContent":["import { AiAppAssistantRequestError } from \"./http.js\";\nimport type { ManagedAiAppAssistantServer } from \"./managed-server.js\";\n\n/** Minimal Express request shape; importing Express types is intentionally unnecessary. */\nexport interface AiAppAssistantExpressRequest {\n method?: string;\n originalUrl?: string;\n url?: string;\n protocol?: string;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n on?(event: string, listener: () => void): unknown;\n off?(event: string, listener: () => void): unknown;\n [key: string]: unknown;\n}\n\n/** Minimal Express response shape used by the connector. */\nexport interface AiAppAssistantExpressResponse {\n statusCode: number;\n setHeader(name: string, value: string): unknown;\n write(chunk: Uint8Array): boolean;\n end(chunk?: string): unknown;\n once?(event: string, listener: () => void): unknown;\n destroyed?: boolean;\n}\n\nexport type AiAppAssistantExpressNext = (error?: unknown) => void;\n\nexport type AiAppAssistantExpressManagedServer<\n TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest\n> = Pick<ManagedAiAppAssistantServer<{ id: string; label: string }, TRequest>, \"fetch\">;\n\nexport interface AiAppAssistantExpressHandlerOptions {\n /** Origin used only when Express does not expose a Host header. */\n origin?: string;\n /** Maximum size accepted when no JSON body parser populated request.body. */\n maxBodyBytes?: number;\n /** Lets another Express handler process unknown routes below the mounted prefix. */\n fallthrough?: boolean;\n}\n\nexport type AiAppAssistantExpressHandler<TRequest extends AiAppAssistantExpressRequest> = (\n request: TRequest,\n response: AiAppAssistantExpressResponse,\n next?: AiAppAssistantExpressNext\n) => Promise<void>;\n\n/**\n * Creates an Express-compatible handler for the complete managed API.\n *\n * @example\n * `app.use(\"/api/ai-app-assistant\", createManagedAiAppAssistantExpressHandler(aiAppAssistant));`\n */\nexport function createManagedAiAppAssistantExpressHandler<\n TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest\n>(\n server: AiAppAssistantExpressManagedServer<TRequest>,\n options: AiAppAssistantExpressHandlerOptions = {}\n): AiAppAssistantExpressHandler<TRequest> {\n return async (request, response, next) => {\n const abortController = new AbortController();\n const abort = (): void => abortController.abort(new DOMException(\"Client disconnected\", \"AbortError\"));\n request.on?.(\"close\", abort);\n try {\n const webRequest = await toFetchRequest(request, abortController.signal, options);\n const result = await server.fetch.handle(webRequest, request);\n if (options.fallthrough && result.status === 404 && next) {\n next();\n return;\n }\n await writeFetchResponse(response, result);\n } catch (error) {\n if (next) {\n next(error);\n return;\n }\n writeConnectorError(response, error);\n } finally {\n request.off?.(\"close\", abort);\n }\n };\n}\n\nasync function toFetchRequest(\n request: AiAppAssistantExpressRequest,\n signal: AbortSignal,\n options: AiAppAssistantExpressHandlerOptions\n): Promise<Request> {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (Array.isArray(value)) value.forEach((item) => headers.append(name, item));\n else if (value !== undefined) headers.set(name, value);\n }\n const method = request.method?.toUpperCase() || \"GET\";\n const host = headers.get(\"host\");\n const protocol = request.protocol || headers.get(\"x-forwarded-proto\") || \"http\";\n const origin = options.origin ?? `${protocol}://${host || \"localhost\"}`;\n // originalUrl preserves the mount prefix stripped from request.url by Express.\n const url = new URL(request.originalUrl || request.url || \"/\", origin);\n const body = method === \"GET\" || method === \"HEAD\"\n ? undefined\n : request.body !== undefined\n ? serializeBody(request.body, headers)\n : await readBody(request, options.maxBodyBytes ?? 8_600_000);\n return new Request(url, {\n method,\n headers,\n signal,\n ...(body !== undefined ? { body } : {})\n });\n}\n\nfunction serializeBody(body: unknown, headers: Headers): string {\n if (typeof body === \"string\") return body;\n if (body instanceof Uint8Array) return new TextDecoder().decode(body);\n if (!headers.has(\"content-type\")) headers.set(\"content-type\", \"application/json\");\n return JSON.stringify(body) ?? \"null\";\n}\n\nasync function readBody(request: AiAppAssistantExpressRequest, maxBodyBytes: number): Promise<string | undefined> {\n if (!(Symbol.asyncIterator in request)) return undefined;\n const chunks: Uint8Array[] = [];\n let size = 0;\n for await (const chunk of request as AsyncIterable<string | Uint8Array>) {\n const bytes = typeof chunk === \"string\" ? Buffer.from(chunk) : new Uint8Array(chunk);\n size += bytes.byteLength;\n if (size > maxBodyBytes) {\n throw new AiAppAssistantRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n chunks.push(bytes);\n }\n return chunks.length ? Buffer.concat(chunks).toString(\"utf8\") : undefined;\n}\n\nasync function writeFetchResponse(response: AiAppAssistantExpressResponse, result: Response): Promise<void> {\n response.statusCode = result.status;\n result.headers.forEach((value, name) => response.setHeader(name, value));\n if (!result.body) {\n response.end();\n return;\n }\n const reader = result.body.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (!response.write(value) && response.once) {\n await new Promise<void>((resolve) => response.once!(\"drain\", resolve));\n }\n }\n if (!response.destroyed) response.end();\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction writeConnectorError(response: AiAppAssistantExpressResponse, error: unknown): void {\n const status = error instanceof AiAppAssistantRequestError ? error.status : 500;\n const code = error instanceof AiAppAssistantRequestError ? error.code : \"assistant_error\";\n response.statusCode = status;\n response.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n response.end(JSON.stringify({ error: code }));\n}\n","import {\n aiAppAssistantRequestSchema,\n type AiAppAssistantRequest,\n type AiAppAssistantResponse\n} from \"@123toto/ai-app-assistant-contracts\";\nimport { ZodError } from \"zod\";\nimport type { AiAppAssistant, AiAppAssistantStreamEvent } from \"./assistant.js\";\n\nexport interface AiAppAssistantFetchHandlerOptions<TContext = undefined> {\n assistant: AiAppAssistant;\n /** Resolves framework-specific authentication into an application context. */\n resolveContext?: (request: Request) => Promise<TContext> | TContext;\n /** Optional application authorization executed before assistant work. */\n authorize?: (context: TContext, request: Request) => Promise<void> | void;\n /** Explicit opt-in for public prototypes without authentication hooks. */\n allowAnonymous?: boolean;\n /** Optional privacy/input policy applied after protocol validation. */\n transformRequest?: (\n input: AiAppAssistantRequest,\n context: TContext\n ) => Promise<AiAppAssistantRequest> | AiAppAssistantRequest;\n /** Optional privacy/output policy applied before serialization. */\n transformResponse?: (\n response: AiAppAssistantResponse,\n context: TContext\n ) => Promise<AiAppAssistantResponse> | AiAppAssistantResponse;\n /** Applies privacy rules to progressive text before it leaves the backend. */\n transformStreamEvent?: (\n event: AiAppAssistantStreamEvent,\n context: TContext\n ) => Promise<AiAppAssistantStreamEvent> | AiAppAssistantStreamEvent;\n /** Maximum HTTP request size. Defaults slightly above the protocol limit. */\n maxBodyBytes?: number;\n /** Maps application errors without coupling the library to one framework. */\n onError?: (error: unknown, request: Request) => Promise<Response> | Response;\n}\n\nexport interface AiAppAssistantFetchHandlers {\n ask(request: Request): Promise<Response>;\n stream(request: Request): Promise<Response>;\n /** Dispatches to `stream` when the pathname ends in `/stream`, otherwise to `ask`. */\n handle(request: Request): Promise<Response>;\n}\n\n/** Explicit safe HTTP error that host authorization hooks can throw. */\nexport class AiAppAssistantRequestError extends Error {\n public constructor(\n readonly status: number,\n readonly code: string,\n message: string\n ) {\n super(message);\n this.name = \"AiAppAssistantRequestError\";\n }\n}\n\n/**\n * Creates Fetch API handlers usable by standards-based runtimes and thin\n * Express, Fastify, Nest, Next.js, Hono, Bun or serverless adapters.\n */\nexport function createAiAppAssistantFetchHandlers<TContext = undefined>(\n options: AiAppAssistantFetchHandlerOptions<TContext>\n): AiAppAssistantFetchHandlers {\n const prepare = async (request: Request): Promise<{\n input: AiAppAssistantRequest;\n context: TContext;\n }> => {\n assertPost(request);\n const raw = await readJson(request, options.maxBodyBytes ?? 8_600_000);\n const validated = aiAppAssistantRequestSchema.parse(raw);\n if (!options.resolveContext && !options.authorize && !options.allowAnonymous) {\n throw new AiAppAssistantRequestError(401, \"unauthorized\", \"Authentication is required\");\n }\n const context = options.resolveContext\n ? await options.resolveContext(request)\n : undefined as TContext;\n await options.authorize?.(context, request);\n const input = options.transformRequest\n ? await options.transformRequest(validated, context)\n : validated;\n return { input: aiAppAssistantRequestSchema.parse(input), context };\n };\n\n const present = async (\n response: AiAppAssistantResponse,\n context: TContext\n ): Promise<AiAppAssistantResponse> => options.transformResponse\n ? options.transformResponse(response, context)\n : response;\n\n const ask = async (request: Request): Promise<Response> => {\n try {\n const { input, context } = await prepare(request);\n return jsonResponse(await present(await options.assistant.answer(input, {\n signal: request.signal\n }), context));\n } catch (error) {\n return mapError(error, request, options.onError);\n }\n };\n\n const stream = async (request: Request): Promise<Response> => {\n try {\n const { input, context } = await prepare(request);\n const encoder = new TextEncoder();\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n const generation = options.assistant.stream(input, { signal: request.signal });\n while (true) {\n const next = await generation.next();\n if (next.done) break;\n let event = next.value.type === \"complete\"\n ? { ...next.value, response: await present(next.value.response, context) }\n : next.value;\n if (options.transformStreamEvent) {\n event = await options.transformStreamEvent(event, context);\n }\n controller.enqueue(encoder.encode(`${JSON.stringify(event)}\\n`));\n }\n } catch {\n controller.enqueue(encoder.encode(`${JSON.stringify({\n type: \"error\",\n message: \"The assistant response could not be generated.\",\n retryable: false\n })}\\n`));\n } finally {\n controller.close();\n }\n }\n });\n return new Response(body, {\n status: 200,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"application/x-ndjson; charset=utf-8\",\n \"x-content-type-options\": \"nosniff\"\n }\n });\n } catch (error) {\n return mapError(error, request, options.onError);\n }\n };\n\n return {\n ask,\n stream,\n handle: (request) => new URL(request.url).pathname.endsWith(\"/stream\")\n ? stream(request)\n : ask(request)\n };\n}\n\nfunction assertPost(request: Request): void {\n if (request.method !== \"POST\") {\n throw new AiAppAssistantRequestError(405, \"method_not_allowed\", \"Only POST is supported\");\n }\n}\n\nasync function readJson(request: Request, maxBodyBytes: number): Promise<unknown> {\n const contentLength = Number(request.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {\n throw new AiAppAssistantRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n const text = await request.text();\n if (new TextEncoder().encode(text).byteLength > maxBodyBytes) {\n throw new AiAppAssistantRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n try {\n return JSON.parse(text) as unknown;\n } catch {\n throw new AiAppAssistantRequestError(400, \"invalid_json\", \"Request body must be valid JSON\");\n }\n}\n\nasync function mapError(\n error: unknown,\n request: Request,\n customMapper?: AiAppAssistantFetchHandlerOptions<unknown>[\"onError\"]\n): Promise<Response> {\n if (customMapper) return customMapper(error, request);\n if (error instanceof AiAppAssistantRequestError) {\n return jsonResponse({ error: error.code, message: error.message }, error.status);\n }\n if (error instanceof ZodError) {\n return jsonResponse({ error: \"invalid_request\", message: \"The assistant request is invalid.\" }, 400);\n }\n return jsonResponse({\n error: \"assistant_error\",\n message: \"The assistant response could not be generated.\"\n }, 500);\n}\n\nfunction jsonResponse(value: unknown, status = 200): Response {\n return new Response(JSON.stringify(value), {\n status,\n headers: {\n \"cache-control\": \"no-store\",\n \"content-type\": \"application/json; charset=utf-8\",\n \"x-content-type-options\": \"nosniff\"\n }\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/express.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { M as
|
|
2
|
-
import './ai-sdk-
|
|
1
|
+
import { M as ManagedAiAppAssistantServer } from './managed-server-CthTKEQ6.cjs';
|
|
2
|
+
import './ai-sdk-qU3KgNu-.cjs';
|
|
3
3
|
import 'ai';
|
|
4
4
|
import '@123toto/ai-app-assistant-contracts';
|
|
5
5
|
|
|
6
6
|
/** Minimal Express request shape; importing Express types is intentionally unnecessary. */
|
|
7
|
-
interface
|
|
7
|
+
interface AiAppAssistantExpressRequest {
|
|
8
8
|
method?: string;
|
|
9
9
|
originalUrl?: string;
|
|
10
10
|
url?: string;
|
|
@@ -16,7 +16,7 @@ interface AiDocsExpressRequest {
|
|
|
16
16
|
[key: string]: unknown;
|
|
17
17
|
}
|
|
18
18
|
/** Minimal Express response shape used by the connector. */
|
|
19
|
-
interface
|
|
19
|
+
interface AiAppAssistantExpressResponse {
|
|
20
20
|
statusCode: number;
|
|
21
21
|
setHeader(name: string, value: string): unknown;
|
|
22
22
|
write(chunk: Uint8Array): boolean;
|
|
@@ -24,12 +24,12 @@ interface AiDocsExpressResponse {
|
|
|
24
24
|
once?(event: string, listener: () => void): unknown;
|
|
25
25
|
destroyed?: boolean;
|
|
26
26
|
}
|
|
27
|
-
type
|
|
28
|
-
type
|
|
27
|
+
type AiAppAssistantExpressNext = (error?: unknown) => void;
|
|
28
|
+
type AiAppAssistantExpressManagedServer<TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest> = Pick<ManagedAiAppAssistantServer<{
|
|
29
29
|
id: string;
|
|
30
30
|
label: string;
|
|
31
31
|
}, TRequest>, "fetch">;
|
|
32
|
-
interface
|
|
32
|
+
interface AiAppAssistantExpressHandlerOptions {
|
|
33
33
|
/** Origin used only when Express does not expose a Host header. */
|
|
34
34
|
origin?: string;
|
|
35
35
|
/** Maximum size accepted when no JSON body parser populated request.body. */
|
|
@@ -37,13 +37,13 @@ interface AiDocsExpressHandlerOptions {
|
|
|
37
37
|
/** Lets another Express handler process unknown routes below the mounted prefix. */
|
|
38
38
|
fallthrough?: boolean;
|
|
39
39
|
}
|
|
40
|
-
type
|
|
40
|
+
type AiAppAssistantExpressHandler<TRequest extends AiAppAssistantExpressRequest> = (request: TRequest, response: AiAppAssistantExpressResponse, next?: AiAppAssistantExpressNext) => Promise<void>;
|
|
41
41
|
/**
|
|
42
42
|
* Creates an Express-compatible handler for the complete managed API.
|
|
43
43
|
*
|
|
44
44
|
* @example
|
|
45
|
-
* `app.use("/api/ai-
|
|
45
|
+
* `app.use("/api/ai-app-assistant", createManagedAiAppAssistantExpressHandler(aiAppAssistant));`
|
|
46
46
|
*/
|
|
47
|
-
declare function
|
|
47
|
+
declare function createManagedAiAppAssistantExpressHandler<TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest>(server: AiAppAssistantExpressManagedServer<TRequest>, options?: AiAppAssistantExpressHandlerOptions): AiAppAssistantExpressHandler<TRequest>;
|
|
48
48
|
|
|
49
|
-
export { type
|
|
49
|
+
export { type AiAppAssistantExpressHandler, type AiAppAssistantExpressHandlerOptions, type AiAppAssistantExpressManagedServer, type AiAppAssistantExpressNext, type AiAppAssistantExpressRequest, type AiAppAssistantExpressResponse, createManagedAiAppAssistantExpressHandler };
|
package/dist/express.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { M as
|
|
2
|
-
import './ai-sdk-
|
|
1
|
+
import { M as ManagedAiAppAssistantServer } from './managed-server-BGQ-1jkx.js';
|
|
2
|
+
import './ai-sdk-qU3KgNu-.js';
|
|
3
3
|
import 'ai';
|
|
4
4
|
import '@123toto/ai-app-assistant-contracts';
|
|
5
5
|
|
|
6
6
|
/** Minimal Express request shape; importing Express types is intentionally unnecessary. */
|
|
7
|
-
interface
|
|
7
|
+
interface AiAppAssistantExpressRequest {
|
|
8
8
|
method?: string;
|
|
9
9
|
originalUrl?: string;
|
|
10
10
|
url?: string;
|
|
@@ -16,7 +16,7 @@ interface AiDocsExpressRequest {
|
|
|
16
16
|
[key: string]: unknown;
|
|
17
17
|
}
|
|
18
18
|
/** Minimal Express response shape used by the connector. */
|
|
19
|
-
interface
|
|
19
|
+
interface AiAppAssistantExpressResponse {
|
|
20
20
|
statusCode: number;
|
|
21
21
|
setHeader(name: string, value: string): unknown;
|
|
22
22
|
write(chunk: Uint8Array): boolean;
|
|
@@ -24,12 +24,12 @@ interface AiDocsExpressResponse {
|
|
|
24
24
|
once?(event: string, listener: () => void): unknown;
|
|
25
25
|
destroyed?: boolean;
|
|
26
26
|
}
|
|
27
|
-
type
|
|
28
|
-
type
|
|
27
|
+
type AiAppAssistantExpressNext = (error?: unknown) => void;
|
|
28
|
+
type AiAppAssistantExpressManagedServer<TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest> = Pick<ManagedAiAppAssistantServer<{
|
|
29
29
|
id: string;
|
|
30
30
|
label: string;
|
|
31
31
|
}, TRequest>, "fetch">;
|
|
32
|
-
interface
|
|
32
|
+
interface AiAppAssistantExpressHandlerOptions {
|
|
33
33
|
/** Origin used only when Express does not expose a Host header. */
|
|
34
34
|
origin?: string;
|
|
35
35
|
/** Maximum size accepted when no JSON body parser populated request.body. */
|
|
@@ -37,13 +37,13 @@ interface AiDocsExpressHandlerOptions {
|
|
|
37
37
|
/** Lets another Express handler process unknown routes below the mounted prefix. */
|
|
38
38
|
fallthrough?: boolean;
|
|
39
39
|
}
|
|
40
|
-
type
|
|
40
|
+
type AiAppAssistantExpressHandler<TRequest extends AiAppAssistantExpressRequest> = (request: TRequest, response: AiAppAssistantExpressResponse, next?: AiAppAssistantExpressNext) => Promise<void>;
|
|
41
41
|
/**
|
|
42
42
|
* Creates an Express-compatible handler for the complete managed API.
|
|
43
43
|
*
|
|
44
44
|
* @example
|
|
45
|
-
* `app.use("/api/ai-
|
|
45
|
+
* `app.use("/api/ai-app-assistant", createManagedAiAppAssistantExpressHandler(aiAppAssistant));`
|
|
46
46
|
*/
|
|
47
|
-
declare function
|
|
47
|
+
declare function createManagedAiAppAssistantExpressHandler<TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest>(server: AiAppAssistantExpressManagedServer<TRequest>, options?: AiAppAssistantExpressHandlerOptions): AiAppAssistantExpressHandler<TRequest>;
|
|
48
48
|
|
|
49
|
-
export { type
|
|
49
|
+
export { type AiAppAssistantExpressHandler, type AiAppAssistantExpressHandlerOptions, type AiAppAssistantExpressManagedServer, type AiAppAssistantExpressNext, type AiAppAssistantExpressRequest, type AiAppAssistantExpressResponse, createManagedAiAppAssistantExpressHandler };
|
package/dist/express.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
AiAppAssistantRequestError
|
|
3
|
+
} from "./chunk-Z4IVCZ5M.js";
|
|
4
4
|
|
|
5
5
|
// src/express.ts
|
|
6
|
-
function
|
|
6
|
+
function createManagedAiAppAssistantExpressHandler(server, options = {}) {
|
|
7
7
|
return async (request, response, next) => {
|
|
8
8
|
const abortController = new AbortController();
|
|
9
9
|
const abort = () => abortController.abort(new DOMException("Client disconnected", "AbortError"));
|
|
@@ -60,7 +60,7 @@ async function readBody(request, maxBodyBytes) {
|
|
|
60
60
|
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : new Uint8Array(chunk);
|
|
61
61
|
size += bytes.byteLength;
|
|
62
62
|
if (size > maxBodyBytes) {
|
|
63
|
-
throw new
|
|
63
|
+
throw new AiAppAssistantRequestError(413, "request_too_large", "Request body is too large");
|
|
64
64
|
}
|
|
65
65
|
chunks.push(bytes);
|
|
66
66
|
}
|
|
@@ -88,13 +88,13 @@ async function writeFetchResponse(response, result) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
function writeConnectorError(response, error) {
|
|
91
|
-
const status = error instanceof
|
|
92
|
-
const code = error instanceof
|
|
91
|
+
const status = error instanceof AiAppAssistantRequestError ? error.status : 500;
|
|
92
|
+
const code = error instanceof AiAppAssistantRequestError ? error.code : "assistant_error";
|
|
93
93
|
response.statusCode = status;
|
|
94
94
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
95
95
|
response.end(JSON.stringify({ error: code }));
|
|
96
96
|
}
|
|
97
97
|
export {
|
|
98
|
-
|
|
98
|
+
createManagedAiAppAssistantExpressHandler
|
|
99
99
|
};
|
|
100
100
|
//# sourceMappingURL=express.js.map
|
package/dist/express.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/express.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../src/express.ts"],"sourcesContent":["import { AiAppAssistantRequestError } from \"./http.js\";\nimport type { ManagedAiAppAssistantServer } from \"./managed-server.js\";\n\n/** Minimal Express request shape; importing Express types is intentionally unnecessary. */\nexport interface AiAppAssistantExpressRequest {\n method?: string;\n originalUrl?: string;\n url?: string;\n protocol?: string;\n headers: Record<string, string | string[] | undefined>;\n body?: unknown;\n on?(event: string, listener: () => void): unknown;\n off?(event: string, listener: () => void): unknown;\n [key: string]: unknown;\n}\n\n/** Minimal Express response shape used by the connector. */\nexport interface AiAppAssistantExpressResponse {\n statusCode: number;\n setHeader(name: string, value: string): unknown;\n write(chunk: Uint8Array): boolean;\n end(chunk?: string): unknown;\n once?(event: string, listener: () => void): unknown;\n destroyed?: boolean;\n}\n\nexport type AiAppAssistantExpressNext = (error?: unknown) => void;\n\nexport type AiAppAssistantExpressManagedServer<\n TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest\n> = Pick<ManagedAiAppAssistantServer<{ id: string; label: string }, TRequest>, \"fetch\">;\n\nexport interface AiAppAssistantExpressHandlerOptions {\n /** Origin used only when Express does not expose a Host header. */\n origin?: string;\n /** Maximum size accepted when no JSON body parser populated request.body. */\n maxBodyBytes?: number;\n /** Lets another Express handler process unknown routes below the mounted prefix. */\n fallthrough?: boolean;\n}\n\nexport type AiAppAssistantExpressHandler<TRequest extends AiAppAssistantExpressRequest> = (\n request: TRequest,\n response: AiAppAssistantExpressResponse,\n next?: AiAppAssistantExpressNext\n) => Promise<void>;\n\n/**\n * Creates an Express-compatible handler for the complete managed API.\n *\n * @example\n * `app.use(\"/api/ai-app-assistant\", createManagedAiAppAssistantExpressHandler(aiAppAssistant));`\n */\nexport function createManagedAiAppAssistantExpressHandler<\n TRequest extends AiAppAssistantExpressRequest = AiAppAssistantExpressRequest\n>(\n server: AiAppAssistantExpressManagedServer<TRequest>,\n options: AiAppAssistantExpressHandlerOptions = {}\n): AiAppAssistantExpressHandler<TRequest> {\n return async (request, response, next) => {\n const abortController = new AbortController();\n const abort = (): void => abortController.abort(new DOMException(\"Client disconnected\", \"AbortError\"));\n request.on?.(\"close\", abort);\n try {\n const webRequest = await toFetchRequest(request, abortController.signal, options);\n const result = await server.fetch.handle(webRequest, request);\n if (options.fallthrough && result.status === 404 && next) {\n next();\n return;\n }\n await writeFetchResponse(response, result);\n } catch (error) {\n if (next) {\n next(error);\n return;\n }\n writeConnectorError(response, error);\n } finally {\n request.off?.(\"close\", abort);\n }\n };\n}\n\nasync function toFetchRequest(\n request: AiAppAssistantExpressRequest,\n signal: AbortSignal,\n options: AiAppAssistantExpressHandlerOptions\n): Promise<Request> {\n const headers = new Headers();\n for (const [name, value] of Object.entries(request.headers)) {\n if (Array.isArray(value)) value.forEach((item) => headers.append(name, item));\n else if (value !== undefined) headers.set(name, value);\n }\n const method = request.method?.toUpperCase() || \"GET\";\n const host = headers.get(\"host\");\n const protocol = request.protocol || headers.get(\"x-forwarded-proto\") || \"http\";\n const origin = options.origin ?? `${protocol}://${host || \"localhost\"}`;\n // originalUrl preserves the mount prefix stripped from request.url by Express.\n const url = new URL(request.originalUrl || request.url || \"/\", origin);\n const body = method === \"GET\" || method === \"HEAD\"\n ? undefined\n : request.body !== undefined\n ? serializeBody(request.body, headers)\n : await readBody(request, options.maxBodyBytes ?? 8_600_000);\n return new Request(url, {\n method,\n headers,\n signal,\n ...(body !== undefined ? { body } : {})\n });\n}\n\nfunction serializeBody(body: unknown, headers: Headers): string {\n if (typeof body === \"string\") return body;\n if (body instanceof Uint8Array) return new TextDecoder().decode(body);\n if (!headers.has(\"content-type\")) headers.set(\"content-type\", \"application/json\");\n return JSON.stringify(body) ?? \"null\";\n}\n\nasync function readBody(request: AiAppAssistantExpressRequest, maxBodyBytes: number): Promise<string | undefined> {\n if (!(Symbol.asyncIterator in request)) return undefined;\n const chunks: Uint8Array[] = [];\n let size = 0;\n for await (const chunk of request as AsyncIterable<string | Uint8Array>) {\n const bytes = typeof chunk === \"string\" ? Buffer.from(chunk) : new Uint8Array(chunk);\n size += bytes.byteLength;\n if (size > maxBodyBytes) {\n throw new AiAppAssistantRequestError(413, \"request_too_large\", \"Request body is too large\");\n }\n chunks.push(bytes);\n }\n return chunks.length ? Buffer.concat(chunks).toString(\"utf8\") : undefined;\n}\n\nasync function writeFetchResponse(response: AiAppAssistantExpressResponse, result: Response): Promise<void> {\n response.statusCode = result.status;\n result.headers.forEach((value, name) => response.setHeader(name, value));\n if (!result.body) {\n response.end();\n return;\n }\n const reader = result.body.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (!response.write(value) && response.once) {\n await new Promise<void>((resolve) => response.once!(\"drain\", resolve));\n }\n }\n if (!response.destroyed) response.end();\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction writeConnectorError(response: AiAppAssistantExpressResponse, error: unknown): void {\n const status = error instanceof AiAppAssistantRequestError ? error.status : 500;\n const code = error instanceof AiAppAssistantRequestError ? error.code : \"assistant_error\";\n response.statusCode = status;\n response.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n response.end(JSON.stringify({ error: code }));\n}\n"],"mappings":"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","names":[]}
|