@windrun-huaiin/backend-core 15.1.0 → 16.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.
Files changed (66) hide show
  1. package/LICENSE +1 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +44 -0
  5. package/dist/index.mjs +8 -1
  6. package/dist/lib/index.js +19 -0
  7. package/dist/lib/index.mjs +1 -1
  8. package/dist/lib/upstash/qstash.d.ts +20 -7
  9. package/dist/lib/upstash/qstash.d.ts.map +1 -1
  10. package/dist/lib/upstash/qstash.js +33 -7
  11. package/dist/lib/upstash/qstash.mjs +33 -7
  12. package/dist/lib/upstash/redis-structures.d.ts +83 -0
  13. package/dist/lib/upstash/redis-structures.d.ts.map +1 -1
  14. package/dist/lib/upstash/redis-structures.js +220 -0
  15. package/dist/lib/upstash/redis-structures.mjs +202 -1
  16. package/dist/lib/upstash-config.d.ts.map +1 -1
  17. package/dist/lib/upstash-config.js +76 -4
  18. package/dist/lib/upstash-config.mjs +76 -4
  19. package/dist/services/ai/abort.d.ts +2 -0
  20. package/dist/services/ai/abort.d.ts.map +1 -0
  21. package/dist/services/ai/abort.js +24 -0
  22. package/dist/services/ai/abort.mjs +22 -0
  23. package/dist/services/ai/env.d.ts +21 -0
  24. package/dist/services/ai/env.d.ts.map +1 -0
  25. package/dist/services/ai/env.js +85 -0
  26. package/dist/services/ai/env.mjs +80 -0
  27. package/dist/services/ai/error.d.ts +3 -0
  28. package/dist/services/ai/error.d.ts.map +1 -0
  29. package/dist/services/ai/error.js +54 -0
  30. package/dist/services/ai/error.mjs +52 -0
  31. package/dist/services/ai/index.d.ts +9 -0
  32. package/dist/services/ai/index.d.ts.map +1 -0
  33. package/dist/services/ai/index.js +30 -0
  34. package/dist/services/ai/index.mjs +7 -0
  35. package/dist/services/ai/message-builder.d.ts +4 -0
  36. package/dist/services/ai/message-builder.d.ts.map +1 -0
  37. package/dist/services/ai/message-builder.js +15 -0
  38. package/dist/services/ai/message-builder.mjs +13 -0
  39. package/dist/services/ai/mock.d.ts +30 -0
  40. package/dist/services/ai/mock.d.ts.map +1 -0
  41. package/dist/services/ai/mock.js +314 -0
  42. package/dist/services/ai/mock.mjs +308 -0
  43. package/dist/services/ai/openrouter-client.d.ts +12 -0
  44. package/dist/services/ai/openrouter-client.d.ts.map +1 -0
  45. package/dist/services/ai/openrouter-client.js +81 -0
  46. package/dist/services/ai/openrouter-client.mjs +78 -0
  47. package/dist/services/ai/route.d.ts +6 -0
  48. package/dist/services/ai/route.d.ts.map +1 -0
  49. package/dist/services/ai/route.js +178 -0
  50. package/dist/services/ai/route.mjs +173 -0
  51. package/dist/services/ai/types.d.ts +98 -0
  52. package/dist/services/ai/types.d.ts.map +1 -0
  53. package/package.json +11 -4
  54. package/src/index.ts +1 -0
  55. package/src/lib/upstash/qstash.ts +55 -15
  56. package/src/lib/upstash/redis-structures.ts +248 -0
  57. package/src/lib/upstash-config.ts +106 -4
  58. package/src/services/ai/abort.ts +26 -0
  59. package/src/services/ai/env.ts +120 -0
  60. package/src/services/ai/error.ts +64 -0
  61. package/src/services/ai/index.ts +8 -0
  62. package/src/services/ai/message-builder.ts +17 -0
  63. package/src/services/ai/mock.ts +378 -0
  64. package/src/services/ai/openrouter-client.ts +94 -0
  65. package/src/services/ai/route.ts +218 -0
  66. 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 cachedRedis;
202
+ return cachedRedisPrefixed;
133
203
  };
134
204
  const ensureRedis = () => __awaiter(void 0, void 0, void 0, function* () {
135
- if (cachedRedis) {
136
- return cachedRedis;
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 cachedRedis;
237
+ return cachedRedisPrefixed;
166
238
  }
167
239
  catch (error) {
168
240
  if (!redisWarnedInitError) {
@@ -0,0 +1,2 @@
1
+ export declare function createUpstreamAbortSignal(requestSignal: AbortSignal, timeoutMs: number): AbortSignal;
2
+ //# sourceMappingURL=abort.d.ts.map
@@ -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,3 @@
1
+ import { type AIErrorPayload } from '@windrun-huaiin/contracts/ai';
2
+ export declare function normalizeAIError(error: unknown): AIErrorPayload;
3
+ //# sourceMappingURL=error.d.ts.map
@@ -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"}