@windrun-huaiin/backend-core 15.1.0 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -0
- package/dist/index.mjs +8 -1
- package/dist/lib/index.js +19 -0
- package/dist/lib/index.mjs +1 -1
- package/dist/lib/upstash/qstash.d.ts +20 -7
- package/dist/lib/upstash/qstash.d.ts.map +1 -1
- package/dist/lib/upstash/qstash.js +33 -7
- package/dist/lib/upstash/qstash.mjs +33 -7
- package/dist/lib/upstash/redis-structures.d.ts +83 -0
- package/dist/lib/upstash/redis-structures.d.ts.map +1 -1
- package/dist/lib/upstash/redis-structures.js +220 -0
- package/dist/lib/upstash/redis-structures.mjs +202 -1
- package/dist/lib/upstash-config.d.ts.map +1 -1
- package/dist/lib/upstash-config.js +76 -4
- package/dist/lib/upstash-config.mjs +76 -4
- package/dist/services/ai/abort.d.ts +2 -0
- package/dist/services/ai/abort.d.ts.map +1 -0
- package/dist/services/ai/abort.js +24 -0
- package/dist/services/ai/abort.mjs +22 -0
- package/dist/services/ai/env.d.ts +21 -0
- package/dist/services/ai/env.d.ts.map +1 -0
- package/dist/services/ai/env.js +85 -0
- package/dist/services/ai/env.mjs +80 -0
- package/dist/services/ai/error.d.ts +3 -0
- package/dist/services/ai/error.d.ts.map +1 -0
- package/dist/services/ai/error.js +54 -0
- package/dist/services/ai/error.mjs +52 -0
- package/dist/services/ai/index.d.ts +9 -0
- package/dist/services/ai/index.d.ts.map +1 -0
- package/dist/services/ai/index.js +30 -0
- package/dist/services/ai/index.mjs +7 -0
- package/dist/services/ai/message-builder.d.ts +4 -0
- package/dist/services/ai/message-builder.d.ts.map +1 -0
- package/dist/services/ai/message-builder.js +15 -0
- package/dist/services/ai/message-builder.mjs +13 -0
- package/dist/services/ai/mock.d.ts +30 -0
- package/dist/services/ai/mock.d.ts.map +1 -0
- package/dist/services/ai/mock.js +314 -0
- package/dist/services/ai/mock.mjs +308 -0
- package/dist/services/ai/openrouter-client.d.ts +12 -0
- package/dist/services/ai/openrouter-client.d.ts.map +1 -0
- package/dist/services/ai/openrouter-client.js +81 -0
- package/dist/services/ai/openrouter-client.mjs +78 -0
- package/dist/services/ai/route.d.ts +6 -0
- package/dist/services/ai/route.d.ts.map +1 -0
- package/dist/services/ai/route.js +178 -0
- package/dist/services/ai/route.mjs +173 -0
- package/dist/services/ai/types.d.ts +98 -0
- package/dist/services/ai/types.d.ts.map +1 -0
- package/package.json +11 -4
- package/src/index.ts +1 -0
- package/src/lib/upstash/qstash.ts +55 -15
- package/src/lib/upstash/redis-structures.ts +248 -0
- package/src/lib/upstash-config.ts +106 -4
- package/src/services/ai/abort.ts +26 -0
- package/src/services/ai/env.ts +120 -0
- package/src/services/ai/error.ts +64 -0
- package/src/services/ai/index.ts +8 -0
- package/src/services/ai/message-builder.ts +17 -0
- package/src/services/ai/mock.ts +378 -0
- package/src/services/ai/openrouter-client.ts +94 -0
- package/src/services/ai/route.ts +218 -0
- package/src/services/ai/types.ts +131 -0
|
@@ -17,6 +17,7 @@ let qstashWarnedHealthCheck = false;
|
|
|
17
17
|
let qstashWarnedHealthSchedule = false;
|
|
18
18
|
let redisHealthTimer = null;
|
|
19
19
|
let qstashHealthTimer = null;
|
|
20
|
+
let cachedRedisPrefixed = null;
|
|
20
21
|
const isNonEmpty = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
21
22
|
const isValidUrl = (value) => {
|
|
22
23
|
try {
|
|
@@ -27,6 +28,75 @@ const isValidUrl = (value) => {
|
|
|
27
28
|
return false;
|
|
28
29
|
}
|
|
29
30
|
};
|
|
31
|
+
const getRequiredRedisAppName = () => {
|
|
32
|
+
const appName = process.env.NEXT_PUBLIC_APP_NAME;
|
|
33
|
+
if (!isNonEmpty(appName)) {
|
|
34
|
+
throw new Error('[Upstash Config] NEXT_PUBLIC_APP_NAME is required for Redis key prefixing and must not be empty');
|
|
35
|
+
}
|
|
36
|
+
const normalized = appName.replace(/\s+/g, '').toLowerCase();
|
|
37
|
+
if (!normalized) {
|
|
38
|
+
throw new Error('[Upstash Config] NEXT_PUBLIC_APP_NAME must contain non-whitespace characters for Redis key prefixing');
|
|
39
|
+
}
|
|
40
|
+
return normalized;
|
|
41
|
+
};
|
|
42
|
+
const getRedisKeyPrefix = () => {
|
|
43
|
+
const envSuffix = process.env.NODE_ENV === 'production' ? 'live' : 'test';
|
|
44
|
+
return `${getRequiredRedisAppName()}_${envSuffix}`;
|
|
45
|
+
};
|
|
46
|
+
const prefixRedisKey = (prefix, key) => `${prefix}:${key}`;
|
|
47
|
+
const prefixRedisKeys = (prefix, keys) => keys.map((key) => prefixRedisKey(prefix, key));
|
|
48
|
+
const prefixFirstStringArg = (args, prefix) => {
|
|
49
|
+
if (typeof args[0] !== 'string') {
|
|
50
|
+
return args;
|
|
51
|
+
}
|
|
52
|
+
const nextArgs = [...args];
|
|
53
|
+
nextArgs[0] = prefixRedisKey(prefix, args[0]);
|
|
54
|
+
return nextArgs;
|
|
55
|
+
};
|
|
56
|
+
const prefixAllStringArgs = (args, prefix) => {
|
|
57
|
+
return args.map((arg) => (typeof arg === 'string' ? prefixRedisKey(prefix, arg) : arg));
|
|
58
|
+
};
|
|
59
|
+
const keyArrayCommands = new Set(['mget', 'del']);
|
|
60
|
+
const allStringKeyCommands = new Set(['exists']);
|
|
61
|
+
const createPrefixedPipeline = (target, prefix) => {
|
|
62
|
+
return new Proxy(target, {
|
|
63
|
+
get(obj, prop, receiver) {
|
|
64
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
65
|
+
if (typeof value !== 'function') {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
return (...args) => {
|
|
69
|
+
if (prop === 'eval' || prop === 'evalsha' || prop === 'evalro' || prop === 'evalshaRo') {
|
|
70
|
+
const [script, keys, argv] = args;
|
|
71
|
+
return value.call(obj, script, prefixRedisKeys(prefix, keys), argv);
|
|
72
|
+
}
|
|
73
|
+
if (prop === 'pipeline' || prop === 'multi') {
|
|
74
|
+
const nested = value.call(obj);
|
|
75
|
+
return createPrefixedPipeline(nested, prefix);
|
|
76
|
+
}
|
|
77
|
+
if (typeof prop === 'string' && keyArrayCommands.has(prop)) {
|
|
78
|
+
const nextArgs = prefixAllStringArgs(args, prefix);
|
|
79
|
+
return value.apply(obj, nextArgs);
|
|
80
|
+
}
|
|
81
|
+
if (typeof prop === 'string' && allStringKeyCommands.has(prop)) {
|
|
82
|
+
const nextArgs = prefixAllStringArgs(args, prefix);
|
|
83
|
+
return value.apply(obj, nextArgs);
|
|
84
|
+
}
|
|
85
|
+
if (prop === 'mset') {
|
|
86
|
+
const [entries] = args;
|
|
87
|
+
const prefixedEntries = Object.fromEntries(Object.entries(entries).map(([key, entryValue]) => [prefixRedisKey(prefix, key), entryValue]));
|
|
88
|
+
return value.call(obj, prefixedEntries);
|
|
89
|
+
}
|
|
90
|
+
if (prop === 'hmget') {
|
|
91
|
+
const nextArgs = prefixFirstStringArg(args, prefix);
|
|
92
|
+
return value.apply(obj, nextArgs);
|
|
93
|
+
}
|
|
94
|
+
const nextArgs = prefixFirstStringArg(args, prefix);
|
|
95
|
+
return value.apply(obj, nextArgs);
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
};
|
|
30
100
|
const parseMinutes = (value, fallback) => {
|
|
31
101
|
if (!isNonEmpty(value)) {
|
|
32
102
|
return fallback;
|
|
@@ -129,11 +199,11 @@ const scheduleQstashHealthCheck = (token) => {
|
|
|
129
199
|
* - read-through cached instance only
|
|
130
200
|
*/
|
|
131
201
|
const getRedis = () => {
|
|
132
|
-
return
|
|
202
|
+
return cachedRedisPrefixed;
|
|
133
203
|
};
|
|
134
204
|
const ensureRedis = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
-
if (
|
|
136
|
-
return
|
|
205
|
+
if (cachedRedisPrefixed) {
|
|
206
|
+
return cachedRedisPrefixed;
|
|
137
207
|
}
|
|
138
208
|
if (redisInitPromise) {
|
|
139
209
|
return redisInitPromise;
|
|
@@ -155,14 +225,16 @@ const ensureRedis = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
155
225
|
return null;
|
|
156
226
|
}
|
|
157
227
|
try {
|
|
228
|
+
const keyPrefix = getRedisKeyPrefix();
|
|
158
229
|
const client = new Redis({
|
|
159
230
|
url: UPSTASH_REDIS_REST_URL,
|
|
160
231
|
token: UPSTASH_REDIS_REST_TOKEN,
|
|
161
232
|
});
|
|
162
233
|
yield client.ping();
|
|
163
234
|
cachedRedis = client;
|
|
235
|
+
cachedRedisPrefixed = createPrefixedPipeline(client, keyPrefix);
|
|
164
236
|
scheduleRedisHealthCheck();
|
|
165
|
-
return
|
|
237
|
+
return cachedRedisPrefixed;
|
|
166
238
|
}
|
|
167
239
|
catch (error) {
|
|
168
240
|
if (!redisWarnedInitError) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../../src/services/ai/abort.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAyBtF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function createUpstreamAbortSignal(requestSignal, timeoutMs) {
|
|
4
|
+
const controller = new AbortController();
|
|
5
|
+
const timeoutId = setTimeout(() => controller.abort('timeout'), timeoutMs);
|
|
6
|
+
const forwardAbort = () => {
|
|
7
|
+
var _a;
|
|
8
|
+
clearTimeout(timeoutId);
|
|
9
|
+
controller.abort((_a = requestSignal.reason) !== null && _a !== void 0 ? _a : 'request_aborted');
|
|
10
|
+
};
|
|
11
|
+
if (requestSignal.aborted) {
|
|
12
|
+
forwardAbort();
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
requestSignal.addEventListener('abort', forwardAbort, { once: true });
|
|
16
|
+
}
|
|
17
|
+
controller.signal.addEventListener('abort', () => {
|
|
18
|
+
clearTimeout(timeoutId);
|
|
19
|
+
requestSignal.removeEventListener('abort', forwardAbort);
|
|
20
|
+
}, { once: true });
|
|
21
|
+
return controller.signal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.createUpstreamAbortSignal = createUpstreamAbortSignal;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function createUpstreamAbortSignal(requestSignal, timeoutMs) {
|
|
2
|
+
const controller = new AbortController();
|
|
3
|
+
const timeoutId = setTimeout(() => controller.abort('timeout'), timeoutMs);
|
|
4
|
+
const forwardAbort = () => {
|
|
5
|
+
var _a;
|
|
6
|
+
clearTimeout(timeoutId);
|
|
7
|
+
controller.abort((_a = requestSignal.reason) !== null && _a !== void 0 ? _a : 'request_aborted');
|
|
8
|
+
};
|
|
9
|
+
if (requestSignal.aborted) {
|
|
10
|
+
forwardAbort();
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
requestSignal.addEventListener('abort', forwardAbort, { once: true });
|
|
14
|
+
}
|
|
15
|
+
controller.signal.addEventListener('abort', () => {
|
|
16
|
+
clearTimeout(timeoutId);
|
|
17
|
+
requestSignal.removeEventListener('abort', forwardAbort);
|
|
18
|
+
}, { once: true });
|
|
19
|
+
return controller.signal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { createUpstreamAbortSignal };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AIMockHandler, AIRuntimeContext, OpenRouterClientConfig } from './types';
|
|
2
|
+
export type OpenRouterEnvConfig = {
|
|
3
|
+
appName: string;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
modelName: string;
|
|
7
|
+
enableMock: boolean;
|
|
8
|
+
mockType: number;
|
|
9
|
+
mockTimeoutSeconds: number;
|
|
10
|
+
mockStreamChunkDelayMs: number;
|
|
11
|
+
mockStreamChunkSize: number;
|
|
12
|
+
contextWindowTurns: number;
|
|
13
|
+
debug: boolean;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
referer?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function getOpenRouterEnvConfig(): OpenRouterEnvConfig;
|
|
18
|
+
export declare function createOpenRouterClientConfigFromEnv(overrides?: Partial<OpenRouterClientConfig>): OpenRouterClientConfig;
|
|
19
|
+
export declare function createOpenRouterMockFromEnv(): AIMockHandler | undefined;
|
|
20
|
+
export declare function createOpenRouterMockFromEnvForContext(context?: AIRuntimeContext): AIMockHandler | undefined;
|
|
21
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/services/ai/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAgBvF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAUF,wBAAgB,sBAAsB,IAAI,mBAAmB,CAiB5D;AAED,wBAAgB,mCAAmC,CACjD,SAAS,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,GAC1C,sBAAsB,CAexB;AAED,wBAAgB,2BAA2B,IAAI,aAAa,GAAG,SAAS,CAEvE;AAWD,wBAAgB,qCAAqC,CACnD,OAAO,CAAC,EAAE,gBAAgB,GACzB,aAAa,GAAG,SAAS,CA0B3B"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var mock = require('./mock.js');
|
|
4
|
+
|
|
5
|
+
function parseNumber(value, fallback) {
|
|
6
|
+
const parsed = Number(value);
|
|
7
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
8
|
+
}
|
|
9
|
+
function parseBoolean(value, fallback) {
|
|
10
|
+
if (value === undefined) {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
return value === '1' || value === 'true' || value === 'TRUE';
|
|
14
|
+
}
|
|
15
|
+
function getOpenRouterEnvConfig() {
|
|
16
|
+
return {
|
|
17
|
+
appName: process.env.NEXT_PUBLIC_APP_NAME || 'DDaaS',
|
|
18
|
+
timeoutMs: parseNumber(process.env.OPENROUTER_TIMEOUT_SECONDS, 240) * 1000,
|
|
19
|
+
apiKey: process.env.OPENROUTER_API_KEY || '',
|
|
20
|
+
modelName: process.env.NEXT_PUBLIC_OPENROUTER_MODEL_NAME || 'google/gemini-2.0-flash-001',
|
|
21
|
+
enableMock: parseBoolean(process.env.OPENROUTER_ENABLE_MOCK, true),
|
|
22
|
+
mockType: parseNumber(process.env.OPENROUTER_MOCK_TYPE, 0),
|
|
23
|
+
mockTimeoutSeconds: parseNumber(process.env.OPENROUTER_MOCK_TIMEOUT_SECONDS, 3),
|
|
24
|
+
mockStreamChunkDelayMs: parseNumber(process.env.OPENROUTER_MOCK_STREAM_CHUNK_DELAY_MS, 60),
|
|
25
|
+
mockStreamChunkSize: parseNumber(process.env.OPENROUTER_MOCK_STREAM_CHUNK_SIZE, 8),
|
|
26
|
+
contextWindowTurns: parseNumber(process.env.NEXT_PUBLIC_CHAT_CONTEXT_WINDOW_TURNS, 6),
|
|
27
|
+
debug: parseBoolean(process.env.NEXT_PUBLIC_OPENROUTER_DEBUG, false),
|
|
28
|
+
baseUrl: process.env.OPENROUTER_BASE_URL,
|
|
29
|
+
referer: process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function createOpenRouterClientConfigFromEnv(overrides) {
|
|
33
|
+
var _a, _b, _c, _d, _e, _f;
|
|
34
|
+
const envConfig = getOpenRouterEnvConfig();
|
|
35
|
+
return {
|
|
36
|
+
apiKey: (_a = overrides === null || overrides === void 0 ? void 0 : overrides.apiKey) !== null && _a !== void 0 ? _a : envConfig.apiKey,
|
|
37
|
+
baseUrl: (_b = overrides === null || overrides === void 0 ? void 0 : overrides.baseUrl) !== null && _b !== void 0 ? _b : envConfig.baseUrl,
|
|
38
|
+
defaultModel: (_c = overrides === null || overrides === void 0 ? void 0 : overrides.defaultModel) !== null && _c !== void 0 ? _c : envConfig.modelName,
|
|
39
|
+
referer: (_d = overrides === null || overrides === void 0 ? void 0 : overrides.referer) !== null && _d !== void 0 ? _d : envConfig.referer,
|
|
40
|
+
title: (_e = overrides === null || overrides === void 0 ? void 0 : overrides.title) !== null && _e !== void 0 ? _e : envConfig.appName,
|
|
41
|
+
timeoutMs: (_f = overrides === null || overrides === void 0 ? void 0 : overrides.timeoutMs) !== null && _f !== void 0 ? _f : envConfig.timeoutMs,
|
|
42
|
+
provider: overrides === null || overrides === void 0 ? void 0 : overrides.provider,
|
|
43
|
+
temperature: overrides === null || overrides === void 0 ? void 0 : overrides.temperature,
|
|
44
|
+
maxTokens: overrides === null || overrides === void 0 ? void 0 : overrides.maxTokens,
|
|
45
|
+
fetchImpl: overrides === null || overrides === void 0 ? void 0 : overrides.fetchImpl,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function createOpenRouterMockFromEnv() {
|
|
49
|
+
return createOpenRouterMockFromEnvForContext();
|
|
50
|
+
}
|
|
51
|
+
function getRequestMockOverride(context) {
|
|
52
|
+
var _a;
|
|
53
|
+
const value = (_a = context === null || context === void 0 ? void 0 : context.input.metadata) === null || _a === void 0 ? void 0 : _a.mock;
|
|
54
|
+
if (!value || typeof value !== 'object') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function createOpenRouterMockFromEnvForContext(context) {
|
|
60
|
+
var _a, _b, _c, _d;
|
|
61
|
+
const envConfig = getOpenRouterEnvConfig();
|
|
62
|
+
if (!envConfig.enableMock) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const requestOverride = getRequestMockOverride(context);
|
|
66
|
+
if ((requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.enabled) === false) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const mockType = (_a = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.type) !== null && _a !== void 0 ? _a : envConfig.mockType;
|
|
70
|
+
const mockTimeoutSeconds = (_b = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.timeoutSeconds) !== null && _b !== void 0 ? _b : envConfig.mockTimeoutSeconds;
|
|
71
|
+
const mockStreamChunkDelayMs = (_c = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.chunkDelayMs) !== null && _c !== void 0 ? _c : envConfig.mockStreamChunkDelayMs;
|
|
72
|
+
const mockStreamChunkSize = (_d = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.chunkSize) !== null && _d !== void 0 ? _d : envConfig.mockStreamChunkSize;
|
|
73
|
+
return mock.createScenarioMockHandler({
|
|
74
|
+
text: 'This is a mock AI response from the shared backend-core runtime. Configure OPENROUTER_API_KEY and disable OPENROUTER_ENABLE_MOCK to use the real upstream model.',
|
|
75
|
+
mockType,
|
|
76
|
+
mockTimeoutSeconds,
|
|
77
|
+
mockStreamChunkDelayMs,
|
|
78
|
+
mockStreamChunkSize,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
exports.createOpenRouterClientConfigFromEnv = createOpenRouterClientConfigFromEnv;
|
|
83
|
+
exports.createOpenRouterMockFromEnv = createOpenRouterMockFromEnv;
|
|
84
|
+
exports.createOpenRouterMockFromEnvForContext = createOpenRouterMockFromEnvForContext;
|
|
85
|
+
exports.getOpenRouterEnvConfig = getOpenRouterEnvConfig;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createScenarioMockHandler } from './mock.mjs';
|
|
2
|
+
|
|
3
|
+
function parseNumber(value, fallback) {
|
|
4
|
+
const parsed = Number(value);
|
|
5
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
6
|
+
}
|
|
7
|
+
function parseBoolean(value, fallback) {
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
return value === '1' || value === 'true' || value === 'TRUE';
|
|
12
|
+
}
|
|
13
|
+
function getOpenRouterEnvConfig() {
|
|
14
|
+
return {
|
|
15
|
+
appName: process.env.NEXT_PUBLIC_APP_NAME || 'DDaaS',
|
|
16
|
+
timeoutMs: parseNumber(process.env.OPENROUTER_TIMEOUT_SECONDS, 240) * 1000,
|
|
17
|
+
apiKey: process.env.OPENROUTER_API_KEY || '',
|
|
18
|
+
modelName: process.env.NEXT_PUBLIC_OPENROUTER_MODEL_NAME || 'google/gemini-2.0-flash-001',
|
|
19
|
+
enableMock: parseBoolean(process.env.OPENROUTER_ENABLE_MOCK, true),
|
|
20
|
+
mockType: parseNumber(process.env.OPENROUTER_MOCK_TYPE, 0),
|
|
21
|
+
mockTimeoutSeconds: parseNumber(process.env.OPENROUTER_MOCK_TIMEOUT_SECONDS, 3),
|
|
22
|
+
mockStreamChunkDelayMs: parseNumber(process.env.OPENROUTER_MOCK_STREAM_CHUNK_DELAY_MS, 60),
|
|
23
|
+
mockStreamChunkSize: parseNumber(process.env.OPENROUTER_MOCK_STREAM_CHUNK_SIZE, 8),
|
|
24
|
+
contextWindowTurns: parseNumber(process.env.NEXT_PUBLIC_CHAT_CONTEXT_WINDOW_TURNS, 6),
|
|
25
|
+
debug: parseBoolean(process.env.NEXT_PUBLIC_OPENROUTER_DEBUG, false),
|
|
26
|
+
baseUrl: process.env.OPENROUTER_BASE_URL,
|
|
27
|
+
referer: process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function createOpenRouterClientConfigFromEnv(overrides) {
|
|
31
|
+
var _a, _b, _c, _d, _e, _f;
|
|
32
|
+
const envConfig = getOpenRouterEnvConfig();
|
|
33
|
+
return {
|
|
34
|
+
apiKey: (_a = overrides === null || overrides === void 0 ? void 0 : overrides.apiKey) !== null && _a !== void 0 ? _a : envConfig.apiKey,
|
|
35
|
+
baseUrl: (_b = overrides === null || overrides === void 0 ? void 0 : overrides.baseUrl) !== null && _b !== void 0 ? _b : envConfig.baseUrl,
|
|
36
|
+
defaultModel: (_c = overrides === null || overrides === void 0 ? void 0 : overrides.defaultModel) !== null && _c !== void 0 ? _c : envConfig.modelName,
|
|
37
|
+
referer: (_d = overrides === null || overrides === void 0 ? void 0 : overrides.referer) !== null && _d !== void 0 ? _d : envConfig.referer,
|
|
38
|
+
title: (_e = overrides === null || overrides === void 0 ? void 0 : overrides.title) !== null && _e !== void 0 ? _e : envConfig.appName,
|
|
39
|
+
timeoutMs: (_f = overrides === null || overrides === void 0 ? void 0 : overrides.timeoutMs) !== null && _f !== void 0 ? _f : envConfig.timeoutMs,
|
|
40
|
+
provider: overrides === null || overrides === void 0 ? void 0 : overrides.provider,
|
|
41
|
+
temperature: overrides === null || overrides === void 0 ? void 0 : overrides.temperature,
|
|
42
|
+
maxTokens: overrides === null || overrides === void 0 ? void 0 : overrides.maxTokens,
|
|
43
|
+
fetchImpl: overrides === null || overrides === void 0 ? void 0 : overrides.fetchImpl,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function createOpenRouterMockFromEnv() {
|
|
47
|
+
return createOpenRouterMockFromEnvForContext();
|
|
48
|
+
}
|
|
49
|
+
function getRequestMockOverride(context) {
|
|
50
|
+
var _a;
|
|
51
|
+
const value = (_a = context === null || context === void 0 ? void 0 : context.input.metadata) === null || _a === void 0 ? void 0 : _a.mock;
|
|
52
|
+
if (!value || typeof value !== 'object') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function createOpenRouterMockFromEnvForContext(context) {
|
|
58
|
+
var _a, _b, _c, _d;
|
|
59
|
+
const envConfig = getOpenRouterEnvConfig();
|
|
60
|
+
if (!envConfig.enableMock) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const requestOverride = getRequestMockOverride(context);
|
|
64
|
+
if ((requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.enabled) === false) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const mockType = (_a = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.type) !== null && _a !== void 0 ? _a : envConfig.mockType;
|
|
68
|
+
const mockTimeoutSeconds = (_b = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.timeoutSeconds) !== null && _b !== void 0 ? _b : envConfig.mockTimeoutSeconds;
|
|
69
|
+
const mockStreamChunkDelayMs = (_c = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.chunkDelayMs) !== null && _c !== void 0 ? _c : envConfig.mockStreamChunkDelayMs;
|
|
70
|
+
const mockStreamChunkSize = (_d = requestOverride === null || requestOverride === void 0 ? void 0 : requestOverride.chunkSize) !== null && _d !== void 0 ? _d : envConfig.mockStreamChunkSize;
|
|
71
|
+
return createScenarioMockHandler({
|
|
72
|
+
text: 'This is a mock AI response from the shared backend-core runtime. Configure OPENROUTER_API_KEY and disable OPENROUTER_ENABLE_MOCK to use the real upstream model.',
|
|
73
|
+
mockType,
|
|
74
|
+
mockTimeoutSeconds,
|
|
75
|
+
mockStreamChunkDelayMs,
|
|
76
|
+
mockStreamChunkSize,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { createOpenRouterClientConfigFromEnv, createOpenRouterMockFromEnv, createOpenRouterMockFromEnvForContext, getOpenRouterEnvConfig };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../src/services/ai/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,8BAA8B,CAAC;AA2BtC,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAiC/D"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ai = require('@windrun-huaiin/contracts/ai');
|
|
4
|
+
|
|
5
|
+
function isObject(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null;
|
|
7
|
+
}
|
|
8
|
+
function getProviderErrorMessage(data) {
|
|
9
|
+
if (!isObject(data)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const error = data.error;
|
|
13
|
+
if (isObject(error) && typeof error.message === 'string') {
|
|
14
|
+
return error.message;
|
|
15
|
+
}
|
|
16
|
+
if (typeof data.message === 'string') {
|
|
17
|
+
return data.message;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function isAbortError(error) {
|
|
22
|
+
return error instanceof DOMException && error.name === 'AbortError';
|
|
23
|
+
}
|
|
24
|
+
function normalizeAIError(error) {
|
|
25
|
+
var _a;
|
|
26
|
+
if (isObject(error) && typeof error.status === 'number') {
|
|
27
|
+
const message = (_a = getProviderErrorMessage(error)) !== null && _a !== void 0 ? _a : (typeof error.message === 'string' ? error.message : 'Error communicating with AI');
|
|
28
|
+
return ai.createAIErrorPayload({
|
|
29
|
+
message,
|
|
30
|
+
upstreamStatusCode: error.status,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (error instanceof Response) {
|
|
34
|
+
return ai.createAIErrorPayload({
|
|
35
|
+
message: error.statusText || 'Error communicating with AI',
|
|
36
|
+
upstreamStatusCode: error.status || 500,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (isAbortError(error)) {
|
|
40
|
+
return {
|
|
41
|
+
error: 'Request timed out',
|
|
42
|
+
status: 'timeout',
|
|
43
|
+
upstreamStatusCode: 408,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
error: error instanceof Error ? error.message : 'Error communicating with AI',
|
|
48
|
+
status: 'failed',
|
|
49
|
+
failureReason: 'unknown',
|
|
50
|
+
upstreamStatusCode: 500,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
exports.normalizeAIError = normalizeAIError;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createAIErrorPayload } from '@windrun-huaiin/contracts/ai';
|
|
2
|
+
|
|
3
|
+
function isObject(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null;
|
|
5
|
+
}
|
|
6
|
+
function getProviderErrorMessage(data) {
|
|
7
|
+
if (!isObject(data)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const error = data.error;
|
|
11
|
+
if (isObject(error) && typeof error.message === 'string') {
|
|
12
|
+
return error.message;
|
|
13
|
+
}
|
|
14
|
+
if (typeof data.message === 'string') {
|
|
15
|
+
return data.message;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function isAbortError(error) {
|
|
20
|
+
return error instanceof DOMException && error.name === 'AbortError';
|
|
21
|
+
}
|
|
22
|
+
function normalizeAIError(error) {
|
|
23
|
+
var _a;
|
|
24
|
+
if (isObject(error) && typeof error.status === 'number') {
|
|
25
|
+
const message = (_a = getProviderErrorMessage(error)) !== null && _a !== void 0 ? _a : (typeof error.message === 'string' ? error.message : 'Error communicating with AI');
|
|
26
|
+
return createAIErrorPayload({
|
|
27
|
+
message,
|
|
28
|
+
upstreamStatusCode: error.status,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (error instanceof Response) {
|
|
32
|
+
return createAIErrorPayload({
|
|
33
|
+
message: error.statusText || 'Error communicating with AI',
|
|
34
|
+
upstreamStatusCode: error.status || 500,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (isAbortError(error)) {
|
|
38
|
+
return {
|
|
39
|
+
error: 'Request timed out',
|
|
40
|
+
status: 'timeout',
|
|
41
|
+
upstreamStatusCode: 408,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
error: error instanceof Error ? error.message : 'Error communicating with AI',
|
|
46
|
+
status: 'failed',
|
|
47
|
+
failureReason: 'unknown',
|
|
48
|
+
upstreamStatusCode: 500,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { normalizeAIError };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './abort';
|
|
3
|
+
export * from './error';
|
|
4
|
+
export * from './env';
|
|
5
|
+
export * from './message-builder';
|
|
6
|
+
export * from './mock';
|
|
7
|
+
export * from './openrouter-client';
|
|
8
|
+
export * from './route';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/ai/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var abort = require('./abort.js');
|
|
4
|
+
var error = require('./error.js');
|
|
5
|
+
var env = require('./env.js');
|
|
6
|
+
var messageBuilder = require('./message-builder.js');
|
|
7
|
+
var mock = require('./mock.js');
|
|
8
|
+
var openrouterClient = require('./openrouter-client.js');
|
|
9
|
+
var route = require('./route.js');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
exports.createUpstreamAbortSignal = abort.createUpstreamAbortSignal;
|
|
14
|
+
exports.normalizeAIError = error.normalizeAIError;
|
|
15
|
+
exports.createOpenRouterClientConfigFromEnv = env.createOpenRouterClientConfigFromEnv;
|
|
16
|
+
exports.createOpenRouterMockFromEnv = env.createOpenRouterMockFromEnv;
|
|
17
|
+
exports.createOpenRouterMockFromEnvForContext = env.createOpenRouterMockFromEnvForContext;
|
|
18
|
+
exports.getOpenRouterEnvConfig = env.getOpenRouterEnvConfig;
|
|
19
|
+
exports.buildModelMessages = messageBuilder.buildModelMessages;
|
|
20
|
+
exports.createConfigurableMockHandler = mock.createConfigurableMockHandler;
|
|
21
|
+
exports.createErrorMockResponse = mock.createErrorMockResponse;
|
|
22
|
+
exports.createScenarioMockHandler = mock.createScenarioMockHandler;
|
|
23
|
+
exports.createSimpleMockHandler = mock.createSimpleMockHandler;
|
|
24
|
+
exports.getMockScenario = mock.getMockScenario;
|
|
25
|
+
exports.callOpenRouterStream = openrouterClient.callOpenRouterStream;
|
|
26
|
+
exports.guardedOpenRouterStreamStart = openrouterClient.guardedOpenRouterStreamStart;
|
|
27
|
+
exports.createOpenRouterRoute = route.createOpenRouterRoute;
|
|
28
|
+
exports.dynamic = route.dynamic;
|
|
29
|
+
exports.revalidate = route.revalidate;
|
|
30
|
+
exports.runtime = route.runtime;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createUpstreamAbortSignal } from './abort.mjs';
|
|
2
|
+
export { normalizeAIError } from './error.mjs';
|
|
3
|
+
export { createOpenRouterClientConfigFromEnv, createOpenRouterMockFromEnv, createOpenRouterMockFromEnvForContext, getOpenRouterEnvConfig } from './env.mjs';
|
|
4
|
+
export { buildModelMessages } from './message-builder.mjs';
|
|
5
|
+
export { createConfigurableMockHandler, createErrorMockResponse, createScenarioMockHandler, createSimpleMockHandler, getMockScenario } from './mock.mjs';
|
|
6
|
+
export { callOpenRouterStream, guardedOpenRouterStreamStart } from './openrouter-client.mjs';
|
|
7
|
+
export { createOpenRouterRoute, dynamic, revalidate, runtime } from './route.mjs';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type AIRuntimeRequest } from '@windrun-huaiin/contracts/ai';
|
|
2
|
+
import type { OpenRouterRequestBody } from './types';
|
|
3
|
+
export declare function buildModelMessages(messages: AIRuntimeRequest['messages']): OpenRouterRequestBody['messages'];
|
|
4
|
+
//# sourceMappingURL=message-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-builder.d.ts","sourceRoot":"","sources":["../../../src/services/ai/message-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,GACrC,qBAAqB,CAAC,UAAU,CAAC,CAQnC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ai = require('@windrun-huaiin/contracts/ai');
|
|
4
|
+
|
|
5
|
+
function buildModelMessages(messages) {
|
|
6
|
+
return messages
|
|
7
|
+
.filter((message) => message.status !== 'failed')
|
|
8
|
+
.map((message) => ({
|
|
9
|
+
role: message.role,
|
|
10
|
+
content: ai.getMessageText(message),
|
|
11
|
+
}))
|
|
12
|
+
.filter((message) => message.content.trim().length > 0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.buildModelMessages = buildModelMessages;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getMessageText } from '@windrun-huaiin/contracts/ai';
|
|
2
|
+
|
|
3
|
+
function buildModelMessages(messages) {
|
|
4
|
+
return messages
|
|
5
|
+
.filter((message) => message.status !== 'failed')
|
|
6
|
+
.map((message) => ({
|
|
7
|
+
role: message.role,
|
|
8
|
+
content: getMessageText(message),
|
|
9
|
+
}))
|
|
10
|
+
.filter((message) => message.content.trim().length > 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { buildModelMessages };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AIMockHandler } from './types';
|
|
2
|
+
export declare function createSimpleMockHandler(text: string): AIMockHandler;
|
|
3
|
+
export declare function createErrorMockResponse(statusCode: number, message: string): Response;
|
|
4
|
+
type MockFailureType = 'timeout' | 'request_aborted' | 'stream_error';
|
|
5
|
+
type MockScenario = {
|
|
6
|
+
mode?: 'text_stream' | 'event_sequence';
|
|
7
|
+
initialDelayMs?: number;
|
|
8
|
+
streamFailureType?: MockFailureType;
|
|
9
|
+
streamFailureAfterChunks?: number;
|
|
10
|
+
immediateErrorType?: MockFailureType;
|
|
11
|
+
};
|
|
12
|
+
export type ConfigurableMockOptions = {
|
|
13
|
+
text: string;
|
|
14
|
+
initialDelayMs?: number;
|
|
15
|
+
chunkDelayMs?: number;
|
|
16
|
+
chunkSize?: number;
|
|
17
|
+
streamFailureType?: MockFailureType;
|
|
18
|
+
streamFailureAfterChunks?: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function getMockScenario(mockType: number, mockTimeoutMs: number): MockScenario;
|
|
21
|
+
export declare function createConfigurableMockHandler(options: ConfigurableMockOptions): AIMockHandler;
|
|
22
|
+
export declare function createScenarioMockHandler(params: {
|
|
23
|
+
text: string;
|
|
24
|
+
mockType: number;
|
|
25
|
+
mockTimeoutSeconds: number;
|
|
26
|
+
mockStreamChunkDelayMs: number;
|
|
27
|
+
mockStreamChunkSize: number;
|
|
28
|
+
}): AIMockHandler;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../../src/services/ai/mock.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,SAAS,CAAC;AA8B/D,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAqBnE;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,YAQ1E;AAED,KAAK,eAAe,GAAG,SAAS,GAAG,iBAAiB,GAAG,cAAc,CAAC;AAEtE,KAAK,YAAY,GAAG;IAClB,IAAI,CAAC,EAAE,aAAa,GAAG,gBAAgB,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,eAAe,CAAC;IACpC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,kBAAkB,CAAC,EAAE,eAAe,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,eAAe,CAAC;IACpC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC,CAAC;AAEF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,YAAY,CA2CrF;AA6CD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,uBAAuB,GAAG,aAAa,CA0E7F;AAiGD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;CAC7B,GAAG,aAAa,CA0BhB"}
|