@123toto/ai-app-assistant-server 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 123toto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @123toto/ai-app-assistant-server
2
+
3
+ Framework-neutral Node.js backend for contextual in-app AI documentation.
4
+
5
+ ```bash
6
+ npm install @123toto/ai-app-assistant-server
7
+ ```
8
+
9
+ Node.js 20 or later is required.
10
+
11
+ ## Minimal server
12
+
13
+ ```ts
14
+ import { createAiDocsServer } from "@123toto/ai-app-assistant-server";
15
+
16
+ const aiDocs = createAiDocsServer({
17
+ model: "mistral:mistral-small-latest",
18
+ documents,
19
+ http: {
20
+ resolveContext: request => auth.currentUser(request),
21
+ authorize: user => auth.requireAssistantAccess(user)
22
+ }
23
+ });
24
+
25
+ export const POST = (request: Request) => aiDocs.fetch.handle(request);
26
+ ```
27
+
28
+ The selected provider reads its conventional API key from the backend environment. `allowAnonymous: true` is available only for deliberately public prototypes.
29
+
30
+ ## Managed server
31
+
32
+ `createManagedAiDocsServer()` adds optional administration features without changing the minimal API:
33
+
34
+ - provider and model discovery;
35
+ - encrypted API-key persistence;
36
+ - connection tests and key revocation;
37
+ - access rules, quotas and audit history;
38
+ - Redis synchronization across instances;
39
+ - aggregated usage and normalized failure telemetry;
40
+ - complete Fetch API handlers for chat and administration.
41
+
42
+ ```ts
43
+ import { createManagedAiDocsServer } from "@123toto/ai-app-assistant-server";
44
+
45
+ const aiDocs = createManagedAiDocsServer({
46
+ configuration: {
47
+ storage: { type: "redis", client: redis },
48
+ encryptionKey: process.env.AI_DOCS_SECRET_ENCRYPTION_KEY!,
49
+ defaultConfiguration: {
50
+ provider: "mistral",
51
+ model: "mistral-small-latest",
52
+ access: { mode: "all" }
53
+ },
54
+ resolveDefaultApiKey: () => process.env.MISTRAL_API_KEY
55
+ },
56
+ documents,
57
+ http: {
58
+ resolveIdentity: request => auth.currentUser(request),
59
+ authorizeAdministration: user => auth.requireAdmin(user)
60
+ }
61
+ });
62
+
63
+ await aiDocs.initialize();
64
+ ```
65
+
66
+ Documents may also be loaded after application bootstrap with `aiDocs.setDocuments(documents)`.
67
+
68
+ ## Framework connectors
69
+
70
+ ```ts
71
+ import { createManagedAiDocsExpressHandler } from "@123toto/ai-app-assistant-server/express";
72
+
73
+ app.use("/api/ai-docs", createManagedAiDocsExpressHandler(aiDocs));
74
+ ```
75
+
76
+ For Nest, import `createManagedAiDocsNestModule` from `@123toto/ai-app-assistant-server/nest`. Nest, RxJS and reflect-metadata are optional peer dependencies and are not required by other consumers.
77
+
78
+ Native Node and Fetch-compatible runtimes can use `createAiDocsNodeHttpListener` or the standard `Request`/`Response` handlers.
79
+
80
+ ## Providers
81
+
82
+ OpenAI, Anthropic, Mistral, Google/Gemini and Ollama are supported through `provider:model` identifiers. Consumers may inject any Vercel AI SDK model or implement the provider-neutral `AnswerGenerator` interface.
83
+
84
+ ## Telemetry
85
+
86
+ The managed server records counts, durations, token usage and normalized failures. It never records questions, HTML, user identities or credentials. Redis persistence is selected automatically when managed Redis storage is configured; use `telemetry: false` to disable it.
87
+
88
+ Full documentation: [github.com/123toto/ai-docs-asker](https://github.com/123toto/ai-docs-asker)
@@ -0,0 +1,188 @@
1
+ import { LanguageModel } from 'ai';
2
+ import { ConversationMessage, EvidenceSource, GeneratedAnswer, TokenUsage } from '@123toto/ai-app-assistant-contracts';
3
+
4
+ /** Minimal OpenAPI shape used by the optional documentation audit command. */
5
+ interface OpenApiDocument {
6
+ openapi: string;
7
+ info?: {
8
+ title?: string;
9
+ description?: string;
10
+ };
11
+ paths?: Record<string, unknown>;
12
+ components?: {
13
+ schemas?: Record<string, unknown>;
14
+ };
15
+ }
16
+ /**
17
+ * A trusted document made available to the model for every question.
18
+ *
19
+ * `content` accepts JSON-compatible objects so an OpenAPI document can be
20
+ * supplied directly, without a dedicated integration or preprocessing step.
21
+ */
22
+ interface DocumentationSource {
23
+ id: string;
24
+ title: string;
25
+ content: string | object;
26
+ mediaType?: string;
27
+ }
28
+ /** One bounded piece of information that may support the generated answer. */
29
+ interface EvidenceItem {
30
+ source: EvidenceSource;
31
+ reference: string;
32
+ content: string;
33
+ relevance: number;
34
+ }
35
+ /** Input passed to an LLM adapter after local validation and preparation. */
36
+ interface EvidenceBundle {
37
+ question: string;
38
+ locale: string;
39
+ /** Recent turns used only to resolve follow-up questions. */
40
+ conversation?: ConversationMessage[];
41
+ items: EvidenceItem[];
42
+ }
43
+ /** Context limits used to size evidence before it reaches a provider. */
44
+ interface ModelCapabilities {
45
+ /** Total input and output window advertised by the selected model. */
46
+ contextWindowTokens: number;
47
+ /** Tokens kept available for the structured answer. */
48
+ maxOutputTokens: number;
49
+ /** Conservative tokenizer-independent estimate used for HTML and JSON. */
50
+ estimatedCharactersPerToken: number;
51
+ }
52
+ /** Observable progress produced while a provider builds a structured answer. */
53
+ type GenerationProgress = {
54
+ type: "partial";
55
+ text: string;
56
+ } | {
57
+ type: "retry";
58
+ attempt: number;
59
+ maxRetries: number;
60
+ delayMs: number;
61
+ };
62
+ interface GenerationOptions {
63
+ signal?: AbortSignal;
64
+ }
65
+ /** Generated content plus optional provider accounting data. */
66
+ type GenerationResult = GeneratedAnswer & {
67
+ usage?: TokenUsage;
68
+ };
69
+ /** Provider-neutral contract implemented by an LLM adapter. */
70
+ interface AnswerGenerator {
71
+ readonly modelId: string;
72
+ /** Optional limits let the assistant adapt automatically to another model. */
73
+ readonly capabilities?: ModelCapabilities;
74
+ generate(input: EvidenceBundle, signal?: AbortSignal): Promise<GenerationResult>;
75
+ /** Optional progressive path. The non-streaming `generate` method remains supported. */
76
+ stream?(input: EvidenceBundle, options?: GenerationOptions): AsyncGenerator<GenerationProgress, GenerationResult>;
77
+ }
78
+ /** Configuration shared by all calls made through an assistant instance. */
79
+ interface DocsAssistantOptions {
80
+ generator: AnswerGenerator;
81
+ /**
82
+ * Application documentation loaded and serialized once when the assistant is
83
+ * created. Markdown, OpenAPI JSON, wiki exports and similar text are all valid.
84
+ */
85
+ documents?: DocumentationSource[];
86
+ policies?: {
87
+ minimumEvidence?: number;
88
+ /** Maximum characters kept from the selected DOM element. */
89
+ maxSelectedElementEvidenceChars?: number;
90
+ /** Maximum characters kept from the complete rendered page. */
91
+ maxHtmlEvidenceChars?: number;
92
+ /** Maximum characters kept from one configured document. */
93
+ maxDocumentEvidenceChars?: number;
94
+ /** Maximum characters shared by all configured documents. */
95
+ maxDocumentTotalChars?: number;
96
+ };
97
+ }
98
+
99
+ /** Configuration for the Vercel AI SDK adapter. */
100
+ interface AiSdkGeneratorOptions {
101
+ /**
102
+ * Native provider and model, or a model instance supplied by the consumer.
103
+ *
104
+ * Strings use `provider:model` (or `provider/model`) and read only the active
105
+ * provider's conventional environment variable. Provider packages are
106
+ * transitive dependencies, so the consumer installs only `@123toto/ai-app-assistant-server`.
107
+ */
108
+ model: LanguageModel;
109
+ /** Overrides the active provider's environment variable when needed. */
110
+ apiKey?: string;
111
+ /** Base URL used by the built-in Ollama connector. */
112
+ baseURL?: string;
113
+ /** Optional label exposed in response metadata. It is inferred by default. */
114
+ modelId?: string;
115
+ /** Maximum duration of the complete generation, including retries. */
116
+ timeoutMs?: number;
117
+ /** Maximum duration of one provider attempt within the total timeout. */
118
+ attemptTimeoutMs?: number;
119
+ /** Overrides automatic model context detection for custom or local models. */
120
+ contextWindowTokens?: number;
121
+ /** Tokens reserved for the structured response. */
122
+ maxOutputTokens?: number;
123
+ /** Actual answer ceiling sent to the provider. Defaults to 1,200 tokens. */
124
+ responseMaxOutputTokens?: number;
125
+ /** Automatic retries after the initial call. Defaults to five. */
126
+ maxRetries?: number;
127
+ /** Initial retry delay; mainly useful to shorten deterministic tests. */
128
+ retryBaseDelayMs?: number;
129
+ }
130
+ /** Options for the short real provider call used by configuration screens. */
131
+ type AiSdkConnectionTestOptions = Pick<AiSdkGeneratorOptions, "apiKey" | "baseURL" | "model" | "timeoutMs">;
132
+ /** Safe result that a host backend can return to an administrator. */
133
+ type AiSdkConnectionTestResult = {
134
+ success: true;
135
+ model: string;
136
+ latencyMs: number;
137
+ } | {
138
+ success: false;
139
+ model: string;
140
+ latencyMs: number;
141
+ error: {
142
+ code: AiSdkFailureCode;
143
+ message: string;
144
+ retryable: boolean;
145
+ providerStatus?: number;
146
+ };
147
+ };
148
+ /**
149
+ * Creates an answer generator backed by the Vercel AI SDK.
150
+ *
151
+ * String models call OpenAI, Anthropic, Mistral, Google or Ollama directly with
152
+ * the corresponding API key. Consumers can still inject any AI SDK
153
+ * `LanguageModel` instance to override the built-in provider resolution.
154
+ */
155
+ declare function createAiSdkGenerator(options: AiSdkGeneratorOptions): AnswerGenerator;
156
+ /**
157
+ * Makes one deliberately small structured-output call to validate credentials,
158
+ * model access and the capability required by the assistant.
159
+ */
160
+ declare function testAiSdkConnection(options: AiSdkConnectionTestOptions): Promise<AiSdkConnectionTestResult>;
161
+ /** Configuration error raised before any provider request is sent. */
162
+ declare class AiSdkConfigurationError extends Error {
163
+ constructor(message: string);
164
+ }
165
+ type AiSdkFailureCode = "AUTHENTICATION" | "CANCELLED" | "CONFIGURATION" | "CONTEXT_LIMIT" | "NETWORK" | "PROVIDER_REJECTED" | "PROVIDER_UNAVAILABLE" | "RATE_LIMIT" | "STRUCTURED_OUTPUT" | "TIMEOUT" | "UNKNOWN";
166
+ /**
167
+ * Stable diagnostic exposed after retries are exhausted. It carries no prompt,
168
+ * page HTML or API key, so a host backend can safely put it in operational logs.
169
+ */
170
+ declare class AiSdkGenerationError extends Error {
171
+ readonly code: AiSdkFailureCode;
172
+ readonly attempts: number;
173
+ readonly retryable: boolean;
174
+ readonly providerStatus?: number;
175
+ constructor(input: {
176
+ code: AiSdkFailureCode;
177
+ attempts: number;
178
+ retryable: boolean;
179
+ message: string;
180
+ providerStatus?: number;
181
+ cause?: unknown;
182
+ });
183
+ }
184
+ declare function normalizeAiSdkGenerationError(error: unknown, attempts: number): AiSdkGenerationError;
185
+ /** Retries only failures that may succeed unchanged; configuration stays immediate. */
186
+ declare function isRetryableProviderError(error: unknown): boolean;
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 DocsAssistantOptions 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 };
@@ -0,0 +1,188 @@
1
+ import { LanguageModel } from 'ai';
2
+ import { ConversationMessage, EvidenceSource, GeneratedAnswer, TokenUsage } from '@123toto/ai-app-assistant-contracts';
3
+
4
+ /** Minimal OpenAPI shape used by the optional documentation audit command. */
5
+ interface OpenApiDocument {
6
+ openapi: string;
7
+ info?: {
8
+ title?: string;
9
+ description?: string;
10
+ };
11
+ paths?: Record<string, unknown>;
12
+ components?: {
13
+ schemas?: Record<string, unknown>;
14
+ };
15
+ }
16
+ /**
17
+ * A trusted document made available to the model for every question.
18
+ *
19
+ * `content` accepts JSON-compatible objects so an OpenAPI document can be
20
+ * supplied directly, without a dedicated integration or preprocessing step.
21
+ */
22
+ interface DocumentationSource {
23
+ id: string;
24
+ title: string;
25
+ content: string | object;
26
+ mediaType?: string;
27
+ }
28
+ /** One bounded piece of information that may support the generated answer. */
29
+ interface EvidenceItem {
30
+ source: EvidenceSource;
31
+ reference: string;
32
+ content: string;
33
+ relevance: number;
34
+ }
35
+ /** Input passed to an LLM adapter after local validation and preparation. */
36
+ interface EvidenceBundle {
37
+ question: string;
38
+ locale: string;
39
+ /** Recent turns used only to resolve follow-up questions. */
40
+ conversation?: ConversationMessage[];
41
+ items: EvidenceItem[];
42
+ }
43
+ /** Context limits used to size evidence before it reaches a provider. */
44
+ interface ModelCapabilities {
45
+ /** Total input and output window advertised by the selected model. */
46
+ contextWindowTokens: number;
47
+ /** Tokens kept available for the structured answer. */
48
+ maxOutputTokens: number;
49
+ /** Conservative tokenizer-independent estimate used for HTML and JSON. */
50
+ estimatedCharactersPerToken: number;
51
+ }
52
+ /** Observable progress produced while a provider builds a structured answer. */
53
+ type GenerationProgress = {
54
+ type: "partial";
55
+ text: string;
56
+ } | {
57
+ type: "retry";
58
+ attempt: number;
59
+ maxRetries: number;
60
+ delayMs: number;
61
+ };
62
+ interface GenerationOptions {
63
+ signal?: AbortSignal;
64
+ }
65
+ /** Generated content plus optional provider accounting data. */
66
+ type GenerationResult = GeneratedAnswer & {
67
+ usage?: TokenUsage;
68
+ };
69
+ /** Provider-neutral contract implemented by an LLM adapter. */
70
+ interface AnswerGenerator {
71
+ readonly modelId: string;
72
+ /** Optional limits let the assistant adapt automatically to another model. */
73
+ readonly capabilities?: ModelCapabilities;
74
+ generate(input: EvidenceBundle, signal?: AbortSignal): Promise<GenerationResult>;
75
+ /** Optional progressive path. The non-streaming `generate` method remains supported. */
76
+ stream?(input: EvidenceBundle, options?: GenerationOptions): AsyncGenerator<GenerationProgress, GenerationResult>;
77
+ }
78
+ /** Configuration shared by all calls made through an assistant instance. */
79
+ interface DocsAssistantOptions {
80
+ generator: AnswerGenerator;
81
+ /**
82
+ * Application documentation loaded and serialized once when the assistant is
83
+ * created. Markdown, OpenAPI JSON, wiki exports and similar text are all valid.
84
+ */
85
+ documents?: DocumentationSource[];
86
+ policies?: {
87
+ minimumEvidence?: number;
88
+ /** Maximum characters kept from the selected DOM element. */
89
+ maxSelectedElementEvidenceChars?: number;
90
+ /** Maximum characters kept from the complete rendered page. */
91
+ maxHtmlEvidenceChars?: number;
92
+ /** Maximum characters kept from one configured document. */
93
+ maxDocumentEvidenceChars?: number;
94
+ /** Maximum characters shared by all configured documents. */
95
+ maxDocumentTotalChars?: number;
96
+ };
97
+ }
98
+
99
+ /** Configuration for the Vercel AI SDK adapter. */
100
+ interface AiSdkGeneratorOptions {
101
+ /**
102
+ * Native provider and model, or a model instance supplied by the consumer.
103
+ *
104
+ * Strings use `provider:model` (or `provider/model`) and read only the active
105
+ * provider's conventional environment variable. Provider packages are
106
+ * transitive dependencies, so the consumer installs only `@123toto/ai-app-assistant-server`.
107
+ */
108
+ model: LanguageModel;
109
+ /** Overrides the active provider's environment variable when needed. */
110
+ apiKey?: string;
111
+ /** Base URL used by the built-in Ollama connector. */
112
+ baseURL?: string;
113
+ /** Optional label exposed in response metadata. It is inferred by default. */
114
+ modelId?: string;
115
+ /** Maximum duration of the complete generation, including retries. */
116
+ timeoutMs?: number;
117
+ /** Maximum duration of one provider attempt within the total timeout. */
118
+ attemptTimeoutMs?: number;
119
+ /** Overrides automatic model context detection for custom or local models. */
120
+ contextWindowTokens?: number;
121
+ /** Tokens reserved for the structured response. */
122
+ maxOutputTokens?: number;
123
+ /** Actual answer ceiling sent to the provider. Defaults to 1,200 tokens. */
124
+ responseMaxOutputTokens?: number;
125
+ /** Automatic retries after the initial call. Defaults to five. */
126
+ maxRetries?: number;
127
+ /** Initial retry delay; mainly useful to shorten deterministic tests. */
128
+ retryBaseDelayMs?: number;
129
+ }
130
+ /** Options for the short real provider call used by configuration screens. */
131
+ type AiSdkConnectionTestOptions = Pick<AiSdkGeneratorOptions, "apiKey" | "baseURL" | "model" | "timeoutMs">;
132
+ /** Safe result that a host backend can return to an administrator. */
133
+ type AiSdkConnectionTestResult = {
134
+ success: true;
135
+ model: string;
136
+ latencyMs: number;
137
+ } | {
138
+ success: false;
139
+ model: string;
140
+ latencyMs: number;
141
+ error: {
142
+ code: AiSdkFailureCode;
143
+ message: string;
144
+ retryable: boolean;
145
+ providerStatus?: number;
146
+ };
147
+ };
148
+ /**
149
+ * Creates an answer generator backed by the Vercel AI SDK.
150
+ *
151
+ * String models call OpenAI, Anthropic, Mistral, Google or Ollama directly with
152
+ * the corresponding API key. Consumers can still inject any AI SDK
153
+ * `LanguageModel` instance to override the built-in provider resolution.
154
+ */
155
+ declare function createAiSdkGenerator(options: AiSdkGeneratorOptions): AnswerGenerator;
156
+ /**
157
+ * Makes one deliberately small structured-output call to validate credentials,
158
+ * model access and the capability required by the assistant.
159
+ */
160
+ declare function testAiSdkConnection(options: AiSdkConnectionTestOptions): Promise<AiSdkConnectionTestResult>;
161
+ /** Configuration error raised before any provider request is sent. */
162
+ declare class AiSdkConfigurationError extends Error {
163
+ constructor(message: string);
164
+ }
165
+ type AiSdkFailureCode = "AUTHENTICATION" | "CANCELLED" | "CONFIGURATION" | "CONTEXT_LIMIT" | "NETWORK" | "PROVIDER_REJECTED" | "PROVIDER_UNAVAILABLE" | "RATE_LIMIT" | "STRUCTURED_OUTPUT" | "TIMEOUT" | "UNKNOWN";
166
+ /**
167
+ * Stable diagnostic exposed after retries are exhausted. It carries no prompt,
168
+ * page HTML or API key, so a host backend can safely put it in operational logs.
169
+ */
170
+ declare class AiSdkGenerationError extends Error {
171
+ readonly code: AiSdkFailureCode;
172
+ readonly attempts: number;
173
+ readonly retryable: boolean;
174
+ readonly providerStatus?: number;
175
+ constructor(input: {
176
+ code: AiSdkFailureCode;
177
+ attempts: number;
178
+ retryable: boolean;
179
+ message: string;
180
+ providerStatus?: number;
181
+ cause?: unknown;
182
+ });
183
+ }
184
+ declare function normalizeAiSdkGenerationError(error: unknown, attempts: number): AiSdkGenerationError;
185
+ /** Retries only failures that may succeed unchanged; configuration stays immediate. */
186
+ declare function isRetryableProviderError(error: unknown): boolean;
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 DocsAssistantOptions 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 };