@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
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ var tslib = require('tslib');
4
+ var error = require('./error.js');
5
+
6
+ const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
7
+ function callOpenRouterStream(config, body, signal) {
8
+ return tslib.__awaiter(this, void 0, void 0, function* () {
9
+ var _a, _b;
10
+ const fetchImpl = (_a = config.fetchImpl) !== null && _a !== void 0 ? _a : fetch;
11
+ const response = yield fetchImpl(`${(_b = config.baseUrl) !== null && _b !== void 0 ? _b : OPENROUTER_BASE_URL}/chat/completions`, {
12
+ method: 'POST',
13
+ signal,
14
+ headers: Object.assign(Object.assign({ Authorization: `Bearer ${config.apiKey}`, 'Content-Type': 'application/json' }, (config.referer ? { 'HTTP-Referer': config.referer } : {})), (config.title ? { 'X-Title': config.title } : {})),
15
+ body: JSON.stringify(body),
16
+ });
17
+ if (!response.ok || !response.body) {
18
+ let errorData = null;
19
+ try {
20
+ errorData = yield response.clone().json();
21
+ }
22
+ catch (_c) {
23
+ errorData = { message: response.statusText };
24
+ }
25
+ throw Object.assign({ status: response.status, message: response.statusText }, ((typeof errorData === 'object' && errorData !== null) ? errorData : {}));
26
+ }
27
+ return {
28
+ response,
29
+ status: response.status,
30
+ };
31
+ });
32
+ }
33
+ function guardedOpenRouterStreamStart(response) {
34
+ return tslib.__awaiter(this, void 0, void 0, function* () {
35
+ var _a;
36
+ const reader = (_a = response.body) === null || _a === void 0 ? void 0 : _a.getReader();
37
+ if (!reader) {
38
+ return {
39
+ ok: false,
40
+ error: error.normalizeAIError(new Error('Empty upstream response body')),
41
+ };
42
+ }
43
+ const firstChunk = yield reader.read();
44
+ if (firstChunk.done || !firstChunk.value) {
45
+ return {
46
+ ok: false,
47
+ error: {
48
+ error: 'AI returned an empty response',
49
+ status: 'failed',
50
+ failureReason: 'empty_response',
51
+ upstreamStatusCode: response.status,
52
+ },
53
+ };
54
+ }
55
+ const stream = new ReadableStream({
56
+ start(controller) {
57
+ controller.enqueue(firstChunk.value);
58
+ },
59
+ pull(controller) {
60
+ return tslib.__awaiter(this, void 0, void 0, function* () {
61
+ const nextChunk = yield reader.read();
62
+ if (nextChunk.done) {
63
+ controller.close();
64
+ return;
65
+ }
66
+ controller.enqueue(nextChunk.value);
67
+ });
68
+ },
69
+ cancel(reason) {
70
+ void reader.cancel(reason);
71
+ },
72
+ });
73
+ return {
74
+ ok: true,
75
+ stream,
76
+ };
77
+ });
78
+ }
79
+
80
+ exports.callOpenRouterStream = callOpenRouterStream;
81
+ exports.guardedOpenRouterStreamStart = guardedOpenRouterStreamStart;
@@ -0,0 +1,78 @@
1
+ import { __awaiter } from 'tslib';
2
+ import { normalizeAIError } from './error.mjs';
3
+
4
+ const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
5
+ function callOpenRouterStream(config, body, signal) {
6
+ return __awaiter(this, void 0, void 0, function* () {
7
+ var _a, _b;
8
+ const fetchImpl = (_a = config.fetchImpl) !== null && _a !== void 0 ? _a : fetch;
9
+ const response = yield fetchImpl(`${(_b = config.baseUrl) !== null && _b !== void 0 ? _b : OPENROUTER_BASE_URL}/chat/completions`, {
10
+ method: 'POST',
11
+ signal,
12
+ headers: Object.assign(Object.assign({ Authorization: `Bearer ${config.apiKey}`, 'Content-Type': 'application/json' }, (config.referer ? { 'HTTP-Referer': config.referer } : {})), (config.title ? { 'X-Title': config.title } : {})),
13
+ body: JSON.stringify(body),
14
+ });
15
+ if (!response.ok || !response.body) {
16
+ let errorData = null;
17
+ try {
18
+ errorData = yield response.clone().json();
19
+ }
20
+ catch (_c) {
21
+ errorData = { message: response.statusText };
22
+ }
23
+ throw Object.assign({ status: response.status, message: response.statusText }, ((typeof errorData === 'object' && errorData !== null) ? errorData : {}));
24
+ }
25
+ return {
26
+ response,
27
+ status: response.status,
28
+ };
29
+ });
30
+ }
31
+ function guardedOpenRouterStreamStart(response) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ var _a;
34
+ const reader = (_a = response.body) === null || _a === void 0 ? void 0 : _a.getReader();
35
+ if (!reader) {
36
+ return {
37
+ ok: false,
38
+ error: normalizeAIError(new Error('Empty upstream response body')),
39
+ };
40
+ }
41
+ const firstChunk = yield reader.read();
42
+ if (firstChunk.done || !firstChunk.value) {
43
+ return {
44
+ ok: false,
45
+ error: {
46
+ error: 'AI returned an empty response',
47
+ status: 'failed',
48
+ failureReason: 'empty_response',
49
+ upstreamStatusCode: response.status,
50
+ },
51
+ };
52
+ }
53
+ const stream = new ReadableStream({
54
+ start(controller) {
55
+ controller.enqueue(firstChunk.value);
56
+ },
57
+ pull(controller) {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ const nextChunk = yield reader.read();
60
+ if (nextChunk.done) {
61
+ controller.close();
62
+ return;
63
+ }
64
+ controller.enqueue(nextChunk.value);
65
+ });
66
+ },
67
+ cancel(reason) {
68
+ void reader.cancel(reason);
69
+ },
70
+ });
71
+ return {
72
+ ok: true,
73
+ stream,
74
+ };
75
+ });
76
+ }
77
+
78
+ export { callOpenRouterStream, guardedOpenRouterStreamStart };
@@ -0,0 +1,6 @@
1
+ import type { AIRouteConfig } from './types';
2
+ export declare const runtime = "edge";
3
+ export declare const dynamic = "force-dynamic";
4
+ export declare const revalidate = 0;
5
+ export declare function createOpenRouterRoute(config: AIRouteConfig): (request: Request) => Promise<Response>;
6
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../src/services/ai/route.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,SAAS,CAAC;AAE/D,eAAO,MAAM,OAAO,SAAS,CAAC;AAC9B,eAAO,MAAM,OAAO,kBAAkB,CAAC;AACvC,eAAO,MAAM,UAAU,IAAI,CAAC;AA8F5B,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,IAG9B,SAAS,OAAO,uBA0G5C"}
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ var tslib = require('tslib');
4
+ var ai = require('@windrun-huaiin/contracts/ai');
5
+ var abort = require('./abort.js');
6
+ var env = require('./env.js');
7
+ var error = require('./error.js');
8
+ var messageBuilder = require('./message-builder.js');
9
+ var openrouterClient = require('./openrouter-client.js');
10
+
11
+ const runtime = 'edge';
12
+ const dynamic = 'force-dynamic';
13
+ const revalidate = 0;
14
+ const streamingHeaders = {
15
+ 'Content-Type': 'text/event-stream; charset=utf-8',
16
+ 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate, no-transform',
17
+ Connection: 'keep-alive',
18
+ Pragma: 'no-cache',
19
+ 'X-Accel-Buffering': 'no',
20
+ };
21
+ function createRequestId() {
22
+ try {
23
+ return crypto.randomUUID();
24
+ }
25
+ catch (_a) {
26
+ return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
27
+ }
28
+ }
29
+ function encodeEvent(event) {
30
+ return `data: ${JSON.stringify(event)}\n\n`;
31
+ }
32
+ function createEventStream(response, messageId) {
33
+ return tslib.__awaiter(this, void 0, void 0, function* () {
34
+ var _a;
35
+ const encoder = new TextEncoder();
36
+ const decoder = new TextDecoder();
37
+ const reader = (_a = response.body) === null || _a === void 0 ? void 0 : _a.getReader();
38
+ if (!reader) {
39
+ throw new Error('Missing upstream reader');
40
+ }
41
+ return new ReadableStream({
42
+ start(controller) {
43
+ return tslib.__awaiter(this, void 0, void 0, function* () {
44
+ controller.enqueue(encoder.encode(encodeEvent({
45
+ type: 'message_started',
46
+ messageId,
47
+ createdAt: Date.now(),
48
+ })));
49
+ try {
50
+ for (;;) {
51
+ const chunk = yield reader.read();
52
+ if (chunk.done) {
53
+ break;
54
+ }
55
+ const text = decoder.decode(chunk.value, { stream: true });
56
+ if (!text) {
57
+ continue;
58
+ }
59
+ controller.enqueue(encoder.encode(encodeEvent({
60
+ type: 'text_delta',
61
+ messageId,
62
+ text,
63
+ })));
64
+ }
65
+ controller.enqueue(encoder.encode(encodeEvent({
66
+ type: 'message_completed',
67
+ messageId,
68
+ createdAt: Date.now(),
69
+ })));
70
+ controller.close();
71
+ }
72
+ catch (error$1) {
73
+ controller.enqueue(encoder.encode(encodeEvent({
74
+ type: 'error',
75
+ error: error.normalizeAIError(error$1),
76
+ })));
77
+ controller.close();
78
+ }
79
+ });
80
+ },
81
+ cancel(reason) {
82
+ void reader.cancel(reason);
83
+ },
84
+ });
85
+ });
86
+ }
87
+ function createOpenRouterRoute(config) {
88
+ const openRouterConfig = env.createOpenRouterClientConfigFromEnv(config.openRouter);
89
+ return function POST(request) {
90
+ return tslib.__awaiter(this, void 0, void 0, function* () {
91
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
92
+ const parsedBody = ai.AIRuntimeRequestSchema.safeParse(yield request.json());
93
+ if (!parsedBody.success) {
94
+ const payload = ai.createAIErrorPayload({
95
+ message: 'Invalid AI runtime request body',
96
+ upstreamStatusCode: 400,
97
+ failureReason: 'invalid_request',
98
+ });
99
+ return Response.json(payload, { status: 400 });
100
+ }
101
+ const requestId = createRequestId();
102
+ const sessionId = (_a = parsedBody.data.sessionId) !== null && _a !== void 0 ? _a : ((_c = (_b = config.createSessionId) === null || _b === void 0 ? void 0 : _b.call(config)) !== null && _c !== void 0 ? _c : createRequestId());
103
+ const context = {
104
+ request,
105
+ input: parsedBody.data,
106
+ sessionId,
107
+ requestId,
108
+ startedAt: Date.now(),
109
+ metadata: parsedBody.data.metadata,
110
+ };
111
+ const mockHandler = (_d = config.mock) !== null && _d !== void 0 ? _d : env.createOpenRouterMockFromEnvForContext(context);
112
+ try {
113
+ yield ((_g = (_f = (_e = config.adapters) === null || _e === void 0 ? void 0 : _e.billing) === null || _f === void 0 ? void 0 : _f.reserve) === null || _g === void 0 ? void 0 : _g.call(_f, context));
114
+ yield ((_j = (_h = config.hooks) === null || _h === void 0 ? void 0 : _h.beforeCall) === null || _j === void 0 ? void 0 : _j.call(_h, context));
115
+ if (mockHandler) {
116
+ const mockResponse = yield mockHandler(context);
117
+ if (mockResponse) {
118
+ return mockResponse;
119
+ }
120
+ }
121
+ const runUpstream = () => tslib.__awaiter(this, void 0, void 0, function* () {
122
+ var _a, _b, _c, _d;
123
+ const upstreamSignal = abort.createUpstreamAbortSignal(request.signal, (_b = (_a = config.timeoutMs) !== null && _a !== void 0 ? _a : openRouterConfig.timeoutMs) !== null && _b !== void 0 ? _b : 60000);
124
+ const response = yield openrouterClient.callOpenRouterStream(openRouterConfig, {
125
+ model: (_c = parsedBody.data.modelName) !== null && _c !== void 0 ? _c : openRouterConfig.defaultModel,
126
+ messages: ((_d = config.buildModelMessages) !== null && _d !== void 0 ? _d : messageBuilder.buildModelMessages)(parsedBody.data.messages),
127
+ stream: true,
128
+ provider: openRouterConfig.provider,
129
+ temperature: openRouterConfig.temperature,
130
+ max_tokens: openRouterConfig.maxTokens,
131
+ }, upstreamSignal);
132
+ return response.response;
133
+ });
134
+ const upstreamResponse = ((_l = (_k = config.adapters) === null || _k === void 0 ? void 0 : _k.lock) === null || _l === void 0 ? void 0 : _l.withLock)
135
+ ? yield config.adapters.lock.withLock(`ai:${sessionId}`, runUpstream)
136
+ : yield runUpstream();
137
+ const guarded = yield openrouterClient.guardedOpenRouterStreamStart(upstreamResponse);
138
+ if (!guarded.ok) {
139
+ yield ((_o = (_m = config.hooks) === null || _m === void 0 ? void 0 : _m.onError) === null || _o === void 0 ? void 0 : _o.call(_m, context, guarded.error));
140
+ yield ((_r = (_q = (_p = config.adapters) === null || _p === void 0 ? void 0 : _p.billing) === null || _q === void 0 ? void 0 : _q.settle) === null || _r === void 0 ? void 0 : _r.call(_q, context, {
141
+ status: guarded.error.status,
142
+ upstreamStatusCode: guarded.error.upstreamStatusCode,
143
+ }));
144
+ return Response.json(guarded.error, {
145
+ status: (_s = guarded.error.upstreamStatusCode) !== null && _s !== void 0 ? _s : 500,
146
+ });
147
+ }
148
+ const messageId = `asst-${requestId}`;
149
+ const wrappedResponse = new Response(config.streamToEvents
150
+ ? yield config.streamToEvents(new Response(guarded.stream, { headers: upstreamResponse.headers }), context)
151
+ : yield createEventStream(new Response(guarded.stream, { headers: upstreamResponse.headers }), messageId), {
152
+ headers: streamingHeaders,
153
+ });
154
+ yield ((_u = (_t = config.hooks) === null || _t === void 0 ? void 0 : _t.afterCall) === null || _u === void 0 ? void 0 : _u.call(_t, context, {
155
+ status: 'streaming',
156
+ upstreamStatusCode: upstreamResponse.status,
157
+ }));
158
+ return wrappedResponse;
159
+ }
160
+ catch (error$1) {
161
+ const normalized = error.normalizeAIError(error$1);
162
+ yield ((_w = (_v = config.hooks) === null || _v === void 0 ? void 0 : _v.onError) === null || _w === void 0 ? void 0 : _w.call(_v, context, normalized));
163
+ yield ((_z = (_y = (_x = config.adapters) === null || _x === void 0 ? void 0 : _x.billing) === null || _y === void 0 ? void 0 : _y.settle) === null || _z === void 0 ? void 0 : _z.call(_y, context, {
164
+ status: normalized.status,
165
+ upstreamStatusCode: normalized.upstreamStatusCode,
166
+ }));
167
+ return Response.json(normalized, {
168
+ status: (_0 = normalized.upstreamStatusCode) !== null && _0 !== void 0 ? _0 : 500,
169
+ });
170
+ }
171
+ });
172
+ };
173
+ }
174
+
175
+ exports.createOpenRouterRoute = createOpenRouterRoute;
176
+ exports.dynamic = dynamic;
177
+ exports.revalidate = revalidate;
178
+ exports.runtime = runtime;
@@ -0,0 +1,173 @@
1
+ import { __awaiter } from 'tslib';
2
+ import { AIRuntimeRequestSchema, createAIErrorPayload } from '@windrun-huaiin/contracts/ai';
3
+ import { createUpstreamAbortSignal } from './abort.mjs';
4
+ import { createOpenRouterClientConfigFromEnv, createOpenRouterMockFromEnvForContext } from './env.mjs';
5
+ import { normalizeAIError } from './error.mjs';
6
+ import { buildModelMessages } from './message-builder.mjs';
7
+ import { callOpenRouterStream, guardedOpenRouterStreamStart } from './openrouter-client.mjs';
8
+
9
+ const runtime = 'edge';
10
+ const dynamic = 'force-dynamic';
11
+ const revalidate = 0;
12
+ const streamingHeaders = {
13
+ 'Content-Type': 'text/event-stream; charset=utf-8',
14
+ 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate, no-transform',
15
+ Connection: 'keep-alive',
16
+ Pragma: 'no-cache',
17
+ 'X-Accel-Buffering': 'no',
18
+ };
19
+ function createRequestId() {
20
+ try {
21
+ return crypto.randomUUID();
22
+ }
23
+ catch (_a) {
24
+ return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
25
+ }
26
+ }
27
+ function encodeEvent(event) {
28
+ return `data: ${JSON.stringify(event)}\n\n`;
29
+ }
30
+ function createEventStream(response, messageId) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ var _a;
33
+ const encoder = new TextEncoder();
34
+ const decoder = new TextDecoder();
35
+ const reader = (_a = response.body) === null || _a === void 0 ? void 0 : _a.getReader();
36
+ if (!reader) {
37
+ throw new Error('Missing upstream reader');
38
+ }
39
+ return new ReadableStream({
40
+ start(controller) {
41
+ return __awaiter(this, void 0, void 0, function* () {
42
+ controller.enqueue(encoder.encode(encodeEvent({
43
+ type: 'message_started',
44
+ messageId,
45
+ createdAt: Date.now(),
46
+ })));
47
+ try {
48
+ for (;;) {
49
+ const chunk = yield reader.read();
50
+ if (chunk.done) {
51
+ break;
52
+ }
53
+ const text = decoder.decode(chunk.value, { stream: true });
54
+ if (!text) {
55
+ continue;
56
+ }
57
+ controller.enqueue(encoder.encode(encodeEvent({
58
+ type: 'text_delta',
59
+ messageId,
60
+ text,
61
+ })));
62
+ }
63
+ controller.enqueue(encoder.encode(encodeEvent({
64
+ type: 'message_completed',
65
+ messageId,
66
+ createdAt: Date.now(),
67
+ })));
68
+ controller.close();
69
+ }
70
+ catch (error) {
71
+ controller.enqueue(encoder.encode(encodeEvent({
72
+ type: 'error',
73
+ error: normalizeAIError(error),
74
+ })));
75
+ controller.close();
76
+ }
77
+ });
78
+ },
79
+ cancel(reason) {
80
+ void reader.cancel(reason);
81
+ },
82
+ });
83
+ });
84
+ }
85
+ function createOpenRouterRoute(config) {
86
+ const openRouterConfig = createOpenRouterClientConfigFromEnv(config.openRouter);
87
+ return function POST(request) {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
90
+ const parsedBody = AIRuntimeRequestSchema.safeParse(yield request.json());
91
+ if (!parsedBody.success) {
92
+ const payload = createAIErrorPayload({
93
+ message: 'Invalid AI runtime request body',
94
+ upstreamStatusCode: 400,
95
+ failureReason: 'invalid_request',
96
+ });
97
+ return Response.json(payload, { status: 400 });
98
+ }
99
+ const requestId = createRequestId();
100
+ const sessionId = (_a = parsedBody.data.sessionId) !== null && _a !== void 0 ? _a : ((_c = (_b = config.createSessionId) === null || _b === void 0 ? void 0 : _b.call(config)) !== null && _c !== void 0 ? _c : createRequestId());
101
+ const context = {
102
+ request,
103
+ input: parsedBody.data,
104
+ sessionId,
105
+ requestId,
106
+ startedAt: Date.now(),
107
+ metadata: parsedBody.data.metadata,
108
+ };
109
+ const mockHandler = (_d = config.mock) !== null && _d !== void 0 ? _d : createOpenRouterMockFromEnvForContext(context);
110
+ try {
111
+ yield ((_g = (_f = (_e = config.adapters) === null || _e === void 0 ? void 0 : _e.billing) === null || _f === void 0 ? void 0 : _f.reserve) === null || _g === void 0 ? void 0 : _g.call(_f, context));
112
+ yield ((_j = (_h = config.hooks) === null || _h === void 0 ? void 0 : _h.beforeCall) === null || _j === void 0 ? void 0 : _j.call(_h, context));
113
+ if (mockHandler) {
114
+ const mockResponse = yield mockHandler(context);
115
+ if (mockResponse) {
116
+ return mockResponse;
117
+ }
118
+ }
119
+ const runUpstream = () => __awaiter(this, void 0, void 0, function* () {
120
+ var _a, _b, _c, _d;
121
+ const upstreamSignal = createUpstreamAbortSignal(request.signal, (_b = (_a = config.timeoutMs) !== null && _a !== void 0 ? _a : openRouterConfig.timeoutMs) !== null && _b !== void 0 ? _b : 60000);
122
+ const response = yield callOpenRouterStream(openRouterConfig, {
123
+ model: (_c = parsedBody.data.modelName) !== null && _c !== void 0 ? _c : openRouterConfig.defaultModel,
124
+ messages: ((_d = config.buildModelMessages) !== null && _d !== void 0 ? _d : buildModelMessages)(parsedBody.data.messages),
125
+ stream: true,
126
+ provider: openRouterConfig.provider,
127
+ temperature: openRouterConfig.temperature,
128
+ max_tokens: openRouterConfig.maxTokens,
129
+ }, upstreamSignal);
130
+ return response.response;
131
+ });
132
+ const upstreamResponse = ((_l = (_k = config.adapters) === null || _k === void 0 ? void 0 : _k.lock) === null || _l === void 0 ? void 0 : _l.withLock)
133
+ ? yield config.adapters.lock.withLock(`ai:${sessionId}`, runUpstream)
134
+ : yield runUpstream();
135
+ const guarded = yield guardedOpenRouterStreamStart(upstreamResponse);
136
+ if (!guarded.ok) {
137
+ yield ((_o = (_m = config.hooks) === null || _m === void 0 ? void 0 : _m.onError) === null || _o === void 0 ? void 0 : _o.call(_m, context, guarded.error));
138
+ yield ((_r = (_q = (_p = config.adapters) === null || _p === void 0 ? void 0 : _p.billing) === null || _q === void 0 ? void 0 : _q.settle) === null || _r === void 0 ? void 0 : _r.call(_q, context, {
139
+ status: guarded.error.status,
140
+ upstreamStatusCode: guarded.error.upstreamStatusCode,
141
+ }));
142
+ return Response.json(guarded.error, {
143
+ status: (_s = guarded.error.upstreamStatusCode) !== null && _s !== void 0 ? _s : 500,
144
+ });
145
+ }
146
+ const messageId = `asst-${requestId}`;
147
+ const wrappedResponse = new Response(config.streamToEvents
148
+ ? yield config.streamToEvents(new Response(guarded.stream, { headers: upstreamResponse.headers }), context)
149
+ : yield createEventStream(new Response(guarded.stream, { headers: upstreamResponse.headers }), messageId), {
150
+ headers: streamingHeaders,
151
+ });
152
+ yield ((_u = (_t = config.hooks) === null || _t === void 0 ? void 0 : _t.afterCall) === null || _u === void 0 ? void 0 : _u.call(_t, context, {
153
+ status: 'streaming',
154
+ upstreamStatusCode: upstreamResponse.status,
155
+ }));
156
+ return wrappedResponse;
157
+ }
158
+ catch (error) {
159
+ const normalized = normalizeAIError(error);
160
+ yield ((_w = (_v = config.hooks) === null || _v === void 0 ? void 0 : _v.onError) === null || _w === void 0 ? void 0 : _w.call(_v, context, normalized));
161
+ yield ((_z = (_y = (_x = config.adapters) === null || _x === void 0 ? void 0 : _x.billing) === null || _y === void 0 ? void 0 : _y.settle) === null || _z === void 0 ? void 0 : _z.call(_y, context, {
162
+ status: normalized.status,
163
+ upstreamStatusCode: normalized.upstreamStatusCode,
164
+ }));
165
+ return Response.json(normalized, {
166
+ status: (_0 = normalized.upstreamStatusCode) !== null && _0 !== void 0 ? _0 : 500,
167
+ });
168
+ }
169
+ });
170
+ };
171
+ }
172
+
173
+ export { createOpenRouterRoute, dynamic, revalidate, runtime };
@@ -0,0 +1,98 @@
1
+ import type { AIErrorPayload, AIMessageStatus, AIRuntimeRequest, AIStreamEvent, ConversationMessage } from '@windrun-huaiin/contracts/ai';
2
+ export type AIRuntimeContext = {
3
+ request: Request;
4
+ input: AIRuntimeRequest;
5
+ sessionId: string;
6
+ requestId: string;
7
+ startedAt: number;
8
+ metadata?: Record<string, unknown>;
9
+ };
10
+ export type AIRuntimeUsage = {
11
+ inputTokens?: number;
12
+ outputTokens?: number;
13
+ totalTokens?: number;
14
+ };
15
+ export type AIRuntimeResult = {
16
+ status: AIMessageStatus;
17
+ message?: ConversationMessage;
18
+ usage?: AIRuntimeUsage;
19
+ upstreamStatusCode?: number;
20
+ };
21
+ export type AIBeforeCallHook = (context: AIRuntimeContext) => Promise<void> | void;
22
+ export type AIAfterCallHook = (context: AIRuntimeContext, result: AIRuntimeResult) => Promise<void> | void;
23
+ export type AIErrorHook = (context: AIRuntimeContext, error: AIErrorPayload) => Promise<void> | void;
24
+ export type AIStorageAdapter = {
25
+ loadHistory?(input: {
26
+ sessionId: string;
27
+ userId?: string;
28
+ }): Promise<ConversationMessage[]>;
29
+ saveMessages?(input: {
30
+ sessionId: string;
31
+ userId?: string;
32
+ messages: ConversationMessage[];
33
+ }): Promise<void>;
34
+ };
35
+ export type AIBillingAdapter = {
36
+ reserve?(context: AIRuntimeContext): Promise<void>;
37
+ settle?(context: AIRuntimeContext, result: AIRuntimeResult): Promise<void>;
38
+ };
39
+ export type AILockAdapter = {
40
+ withLock?<T>(key: string, fn: () => Promise<T>): Promise<T>;
41
+ };
42
+ export type AIMockHandler = (context: AIRuntimeContext) => Promise<Response | null> | Response | null;
43
+ export type AIRuntimeHooks = {
44
+ beforeCall?: AIBeforeCallHook;
45
+ afterCall?: AIAfterCallHook;
46
+ onError?: AIErrorHook;
47
+ };
48
+ export type AIRuntimeAdapters = {
49
+ storage?: AIStorageAdapter;
50
+ billing?: AIBillingAdapter;
51
+ lock?: AILockAdapter;
52
+ };
53
+ export type OpenRouterRequestBody = {
54
+ model: string;
55
+ messages: Array<{
56
+ role: 'user' | 'assistant' | 'system' | 'tool';
57
+ content: string;
58
+ }>;
59
+ stream: true;
60
+ provider?: Record<string, unknown>;
61
+ temperature?: number;
62
+ max_tokens?: number;
63
+ };
64
+ export type OpenRouterClientConfig = {
65
+ apiKey: string;
66
+ baseUrl?: string;
67
+ defaultModel: string;
68
+ referer?: string;
69
+ title?: string;
70
+ timeoutMs?: number;
71
+ provider?: Record<string, unknown>;
72
+ temperature?: number;
73
+ maxTokens?: number;
74
+ fetchImpl?: typeof fetch;
75
+ };
76
+ export type OpenRouterStreamResult = {
77
+ response: Response;
78
+ status: number;
79
+ };
80
+ export type AIRouteConfig = {
81
+ openRouter?: Partial<OpenRouterClientConfig>;
82
+ timeoutMs?: number;
83
+ createSessionId?: () => string;
84
+ mock?: AIMockHandler;
85
+ hooks?: AIRuntimeHooks;
86
+ adapters?: AIRuntimeAdapters;
87
+ buildModelMessages?: (messages: AIRuntimeRequest['messages']) => OpenRouterRequestBody['messages'];
88
+ streamToEvents?: (response: Response, context: AIRuntimeContext) => Promise<ReadableStream<Uint8Array>>;
89
+ };
90
+ export type GuardedStreamStartResult = {
91
+ ok: true;
92
+ stream: ReadableStream<Uint8Array>;
93
+ } | {
94
+ ok: false;
95
+ error: AIErrorPayload;
96
+ };
97
+ export type EncodedAIStreamEvent = AIStreamEvent;
98
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/ai/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/backend-core",
3
- "version": "15.1.0",
3
+ "version": "16.0.0",
4
4
  "description": "Shared backend primitives: Prisma schema/client, database services, routing helpers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,6 +37,11 @@
37
37
  "import": "./dist/services/stripe/index.mjs",
38
38
  "require": "./dist/services/stripe/index.js"
39
39
  },
40
+ "./ai": {
41
+ "types": "./dist/services/ai/index.d.ts",
42
+ "import": "./dist/services/ai/index.mjs",
43
+ "require": "./dist/services/ai/index.js"
44
+ },
40
45
  "./lib": {
41
46
  "types": "./dist/lib/index.d.ts",
42
47
  "import": "./dist/lib/index.mjs",
@@ -107,8 +112,9 @@
107
112
  "svix": "^1.86.0",
108
113
  "tslib": "^2.8.1",
109
114
  "zod": "^4.3.6",
110
- "@windrun-huaiin/lib": "^15.1.0",
111
- "@windrun-huaiin/third-ui": "^15.1.0"
115
+ "@windrun-huaiin/contracts": "^1.0.0",
116
+ "@windrun-huaiin/lib": "^16.0.0",
117
+ "@windrun-huaiin/third-ui": "^16.0.0"
112
118
  },
113
119
  "devDependencies": {
114
120
  "@rollup/plugin-alias": "^5.1.1",
@@ -125,7 +131,8 @@
125
131
  "next": "16.1.6",
126
132
  "prisma": "^6.19.0",
127
133
  "stripe": "20.0.0",
128
- "svix": "^1.86.0"
134
+ "svix": "^1.86.0",
135
+ "@windrun-huaiin/contracts": "^1.0.0"
129
136
  },
130
137
  "publishConfig": {
131
138
  "access": "public"