qwenproxy-cli 1.0.0 → 1.0.2
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 +14 -14
- package/README.md +906 -906
- package/bin/qwenproxy.js +5 -1
- package/package.json +77 -78
- package/src/api/error-classifier.ts +159 -159
- package/src/api/error-helpers.ts +118 -118
- package/src/api/models.ts +261 -261
- package/src/api/server.ts +860 -859
- package/src/cache/memory-cache.ts +385 -385
- package/src/clean-cache.ts +204 -204
- package/src/core/account-concurrency.ts +671 -671
- package/src/core/account-manager.ts +301 -297
- package/src/core/account-priority.ts +163 -163
- package/src/core/accounts.ts +186 -186
- package/src/core/config.ts +383 -383
- package/src/core/crypto-utils.ts +79 -79
- package/src/core/database.ts +276 -276
- package/src/core/errors.ts +118 -118
- package/src/core/logger.ts +269 -269
- package/src/core/memory-usage.ts +84 -84
- package/src/core/metrics.ts +291 -291
- package/src/core/model-alias.ts +77 -77
- package/src/core/model-registry.ts +544 -544
- package/src/core/mutex.ts +119 -119
- package/src/core/paths.ts +199 -199
- package/src/core/prompt-limits.ts +214 -214
- package/src/core/reasoning-effort.ts +102 -102
- package/src/core/stream-registry.ts +96 -96
- package/src/core/waf-isolation.ts +117 -117
- package/src/core/watchdog.ts +195 -195
- package/src/delete-chats.ts +23 -23
- package/src/index.ts +65 -64
- package/src/login.ts +147 -147
- package/src/reset-cooldowns.ts +11 -11
- package/src/routes/anthropic/index.ts +355 -355
- package/src/routes/anthropic/translate.ts +522 -522
- package/src/routes/anthropic/types.ts +154 -154
- package/src/routes/anthropic/validation.ts +144 -144
- package/src/routes/chat/account.ts +1817 -1817
- package/src/routes/chat/context.ts +241 -241
- package/src/routes/chat/errors.ts +85 -85
- package/src/routes/chat/helpers.ts +268 -268
- package/src/routes/chat/index.ts +618 -618
- package/src/routes/chat/media.ts +285 -285
- package/src/routes/chat/retry-policy.ts +754 -754
- package/src/routes/chat/stop.ts +98 -98
- package/src/routes/chat/streaming.ts +2710 -2710
- package/src/routes/chat/validation.ts +526 -526
- package/src/routes/chat.ts +2 -2
- package/src/routes/completions.ts +290 -290
- package/src/routes/images.ts +139 -139
- package/src/routes/responses/adapter.ts +503 -503
- package/src/routes/responses/index.ts +405 -405
- package/src/routes/responses/state.ts +230 -230
- package/src/routes/responses/streaming.ts +528 -528
- package/src/routes/responses/types.ts +285 -285
- package/src/routes/responses/validation.ts +202 -202
- package/src/routes/upload.ts +731 -731
- package/src/routes/videos.ts +214 -214
- package/src/services/auth-playwright.ts +173 -173
- package/src/services/captcha-coordinator.ts +161 -161
- package/src/services/captcha-solver.ts +553 -553
- package/src/services/chat-cleanup.ts +80 -80
- package/src/services/context-meter.ts +317 -317
- package/src/services/fingerprint.ts +242 -242
- package/src/services/human-behavior.ts +173 -173
- package/src/services/media-generation.ts +1748 -1748
- package/src/services/playwright.ts +2878 -2800
- package/src/services/qwen-chat-pool.ts +345 -345
- package/src/services/qwen-errors.ts +133 -133
- package/src/services/qwen-headers.ts +79 -79
- package/src/services/qwen-thread-state.ts +393 -393
- package/src/services/qwen-url.ts +19 -19
- package/src/services/qwen.ts +3126 -3126
- package/src/services/session-keeper.ts +88 -88
- package/src/services/token-estimation-metrics.ts +118 -118
- package/src/sync/claude-code.ts +75 -75
- package/src/sync/codex.ts +123 -123
- package/src/sync/index.ts +362 -362
- package/src/sync/omp.ts +105 -105
- package/src/sync/opencode.ts +214 -214
- package/src/sync/types.ts +53 -53
- package/src/sync/utils.ts +27 -27
- package/src/sync-clients.ts +189 -189
- package/src/tools/instructions.ts +137 -137
- package/src/tools/manifest.ts +81 -81
- package/src/tools/parser.ts +2989 -2989
- package/src/tools/toolcall-tags.ts +142 -142
- package/src/tui/app.ts +259 -264
- package/src/tui/index.ts +61 -61
- package/src/tui/markdown.ts +258 -258
- package/src/tui/proxy-client.ts +331 -326
- package/src/tui/screen.ts +294 -278
- package/src/tui/server-manager.ts +270 -270
- package/src/tui/theme.ts +432 -432
- package/src/tui/types.ts +33 -33
- package/src/tui/views/accounts-view.ts +656 -656
- package/src/tui/views/chat-view.ts +1018 -823
- package/src/tui/views/logs-view.ts +479 -413
- package/src/tui/views/status-view.ts +204 -204
- package/src/tui/views/storage-view.ts +304 -291
- package/src/tui/views/sync-view.ts +409 -409
- package/src/types/ali-oss.d.ts +32 -32
- package/src/update-cli.ts +121 -0
- package/src/utils/context-truncation.ts +84 -84
- package/src/utils/json.ts +380 -380
- package/src/utils/session-id.ts +37 -37
- package/src/utils/tool-call-guard.ts +84 -84
- package/src/utils/types.ts +109 -109
|
@@ -1,230 +1,230 @@
|
|
|
1
|
-
import type { ResponsesResponse } from "./types.ts";
|
|
2
|
-
import { getDatabase } from "../../core/database.ts";
|
|
3
|
-
|
|
4
|
-
// ============ State management for previous_response_id ============
|
|
5
|
-
//
|
|
6
|
-
// The Responses API supports stateful conversations via `previous_response_id`.
|
|
7
|
-
// We store completed responses in SQLite (durable) with an in-memory LRU cache
|
|
8
|
-
// for fast lookups. This ensures memory persists across server restarts.
|
|
9
|
-
|
|
10
|
-
export interface StoredResponse {
|
|
11
|
-
response: ResponsesResponse;
|
|
12
|
-
/** The full list of Chat Completions messages sent to the upstream for this response */
|
|
13
|
-
chatMessages: Array<{
|
|
14
|
-
role: "system" | "user" | "assistant" | "tool";
|
|
15
|
-
content: string | null;
|
|
16
|
-
tool_calls?: Array<{
|
|
17
|
-
id: string;
|
|
18
|
-
type: "function";
|
|
19
|
-
function: { name: string; arguments: string };
|
|
20
|
-
}>;
|
|
21
|
-
tool_call_id?: string;
|
|
22
|
-
}>;
|
|
23
|
-
/** Timestamp of storage */
|
|
24
|
-
storedAt: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// In-memory LRU cache (responseId → StoredResponse)
|
|
28
|
-
const cache = new Map<string, StoredResponse>();
|
|
29
|
-
const MAX_CACHE_SIZE = 500;
|
|
30
|
-
// Max age (ms) - 7 days for SQLite, 24h for cache
|
|
31
|
-
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
32
|
-
const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
33
|
-
|
|
34
|
-
let tableReady = false;
|
|
35
|
-
|
|
36
|
-
function ensureTable(): void {
|
|
37
|
-
if (tableReady) return;
|
|
38
|
-
try {
|
|
39
|
-
const db = getDatabase();
|
|
40
|
-
db.exec(`
|
|
41
|
-
CREATE TABLE IF NOT EXISTS responses_store (
|
|
42
|
-
response_id TEXT PRIMARY KEY,
|
|
43
|
-
response_json TEXT NOT NULL,
|
|
44
|
-
chat_messages_json TEXT NOT NULL,
|
|
45
|
-
stored_at INTEGER NOT NULL
|
|
46
|
-
)
|
|
47
|
-
`);
|
|
48
|
-
db.exec(`
|
|
49
|
-
CREATE INDEX IF NOT EXISTS idx_responses_store_stored_at
|
|
50
|
-
ON responses_store(stored_at)
|
|
51
|
-
`);
|
|
52
|
-
tableReady = true;
|
|
53
|
-
} catch {
|
|
54
|
-
// Database not available — fall back to memory-only
|
|
55
|
-
tableReady = false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Store a completed response for future `previous_response_id` lookups.
|
|
61
|
-
* Persists to SQLite + in-memory cache.
|
|
62
|
-
*/
|
|
63
|
-
export function storeResponse(
|
|
64
|
-
responseId: string,
|
|
65
|
-
response: ResponsesResponse,
|
|
66
|
-
chatMessages: StoredResponse["chatMessages"],
|
|
67
|
-
): void {
|
|
68
|
-
const entry: StoredResponse = {
|
|
69
|
-
response,
|
|
70
|
-
chatMessages,
|
|
71
|
-
storedAt: Date.now(),
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// In-memory cache (LRU eviction)
|
|
75
|
-
if (cache.size >= MAX_CACHE_SIZE) {
|
|
76
|
-
const oldest = [...cache.entries()]
|
|
77
|
-
.sort((a, b) => a[1].storedAt - b[1].storedAt)
|
|
78
|
-
.slice(0, Math.floor(MAX_CACHE_SIZE * 0.1));
|
|
79
|
-
for (const [key] of oldest) {
|
|
80
|
-
cache.delete(key);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
cache.set(responseId, entry);
|
|
84
|
-
|
|
85
|
-
// SQLite persistence
|
|
86
|
-
ensureTable();
|
|
87
|
-
if (tableReady) {
|
|
88
|
-
try {
|
|
89
|
-
const db = getDatabase();
|
|
90
|
-
db.prepare(
|
|
91
|
-
`INSERT OR REPLACE INTO responses_store (response_id, response_json, chat_messages_json, stored_at)
|
|
92
|
-
VALUES (?, ?, ?, ?)`,
|
|
93
|
-
).run(
|
|
94
|
-
responseId,
|
|
95
|
-
JSON.stringify(response),
|
|
96
|
-
JSON.stringify(chatMessages),
|
|
97
|
-
entry.storedAt,
|
|
98
|
-
);
|
|
99
|
-
} catch {
|
|
100
|
-
// Non-fatal: memory cache still works
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Retrieve stored history for a `previous_response_id`.
|
|
107
|
-
* Returns null if not found.
|
|
108
|
-
*/
|
|
109
|
-
export function getResponseHistory(
|
|
110
|
-
previousResponseId: string,
|
|
111
|
-
): StoredResponse["chatMessages"] | null {
|
|
112
|
-
// Check memory cache first
|
|
113
|
-
const cached = cache.get(previousResponseId);
|
|
114
|
-
if (cached) {
|
|
115
|
-
if (Date.now() - cached.storedAt > CACHE_MAX_AGE_MS) {
|
|
116
|
-
cache.delete(previousResponseId);
|
|
117
|
-
} else {
|
|
118
|
-
return cached.chatMessages;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Fall back to SQLite
|
|
123
|
-
ensureTable();
|
|
124
|
-
if (!tableReady) return null;
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const db = getDatabase();
|
|
128
|
-
const row = db
|
|
129
|
-
.prepare(
|
|
130
|
-
`SELECT chat_messages_json, stored_at FROM responses_store WHERE response_id = ?`,
|
|
131
|
-
)
|
|
132
|
-
.get(previousResponseId) as
|
|
133
|
-
| { chat_messages_json: string; stored_at: number }
|
|
134
|
-
| undefined;
|
|
135
|
-
|
|
136
|
-
if (!row) return null;
|
|
137
|
-
|
|
138
|
-
if (Date.now() - row.stored_at > MAX_AGE_MS) {
|
|
139
|
-
db.prepare(`DELETE FROM responses_store WHERE response_id = ?`).run(
|
|
140
|
-
previousResponseId,
|
|
141
|
-
);
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const chatMessages = JSON.parse(row.chat_messages_json);
|
|
146
|
-
// Re-populate cache
|
|
147
|
-
const responseRow = db
|
|
148
|
-
.prepare(`SELECT response_json FROM responses_store WHERE response_id = ?`)
|
|
149
|
-
.get(previousResponseId) as { response_json: string } | undefined;
|
|
150
|
-
if (responseRow) {
|
|
151
|
-
cache.set(previousResponseId, {
|
|
152
|
-
response: JSON.parse(responseRow.response_json),
|
|
153
|
-
chatMessages,
|
|
154
|
-
storedAt: row.stored_at,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return chatMessages;
|
|
159
|
-
} catch {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Retrieve the full stored response (for GET /v1/responses/:id).
|
|
166
|
-
* Returns null if not found.
|
|
167
|
-
*/
|
|
168
|
-
export function getStoredResponse(
|
|
169
|
-
responseId: string,
|
|
170
|
-
): ResponsesResponse | null {
|
|
171
|
-
// Check memory cache first
|
|
172
|
-
const cached = cache.get(responseId);
|
|
173
|
-
if (cached) {
|
|
174
|
-
if (Date.now() - cached.storedAt > CACHE_MAX_AGE_MS) {
|
|
175
|
-
cache.delete(responseId);
|
|
176
|
-
} else {
|
|
177
|
-
return cached.response;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Fall back to SQLite
|
|
182
|
-
ensureTable();
|
|
183
|
-
if (!tableReady) return null;
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const db = getDatabase();
|
|
187
|
-
const row = db
|
|
188
|
-
.prepare(
|
|
189
|
-
`SELECT response_json, chat_messages_json, stored_at FROM responses_store WHERE response_id = ?`,
|
|
190
|
-
)
|
|
191
|
-
.get(responseId) as
|
|
192
|
-
| { response_json: string; chat_messages_json: string; stored_at: number }
|
|
193
|
-
| undefined;
|
|
194
|
-
|
|
195
|
-
if (!row) return null;
|
|
196
|
-
|
|
197
|
-
if (Date.now() - row.stored_at > MAX_AGE_MS) {
|
|
198
|
-
db.prepare(`DELETE FROM responses_store WHERE response_id = ?`).run(
|
|
199
|
-
responseId,
|
|
200
|
-
);
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return JSON.parse(row.response_json);
|
|
205
|
-
} catch {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Delete a stored response.
|
|
212
|
-
*/
|
|
213
|
-
export function deleteStoredResponse(responseId: string): boolean {
|
|
214
|
-
const fromCache = cache.delete(responseId);
|
|
215
|
-
|
|
216
|
-
ensureTable();
|
|
217
|
-
if (tableReady) {
|
|
218
|
-
try {
|
|
219
|
-
const db = getDatabase();
|
|
220
|
-
const result = db
|
|
221
|
-
.prepare(`DELETE FROM responses_store WHERE response_id = ?`)
|
|
222
|
-
.run(responseId);
|
|
223
|
-
return fromCache || result.changes > 0;
|
|
224
|
-
} catch {
|
|
225
|
-
return fromCache;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return fromCache;
|
|
230
|
-
}
|
|
1
|
+
import type { ResponsesResponse } from "./types.ts";
|
|
2
|
+
import { getDatabase } from "../../core/database.ts";
|
|
3
|
+
|
|
4
|
+
// ============ State management for previous_response_id ============
|
|
5
|
+
//
|
|
6
|
+
// The Responses API supports stateful conversations via `previous_response_id`.
|
|
7
|
+
// We store completed responses in SQLite (durable) with an in-memory LRU cache
|
|
8
|
+
// for fast lookups. This ensures memory persists across server restarts.
|
|
9
|
+
|
|
10
|
+
export interface StoredResponse {
|
|
11
|
+
response: ResponsesResponse;
|
|
12
|
+
/** The full list of Chat Completions messages sent to the upstream for this response */
|
|
13
|
+
chatMessages: Array<{
|
|
14
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
15
|
+
content: string | null;
|
|
16
|
+
tool_calls?: Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
type: "function";
|
|
19
|
+
function: { name: string; arguments: string };
|
|
20
|
+
}>;
|
|
21
|
+
tool_call_id?: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** Timestamp of storage */
|
|
24
|
+
storedAt: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// In-memory LRU cache (responseId → StoredResponse)
|
|
28
|
+
const cache = new Map<string, StoredResponse>();
|
|
29
|
+
const MAX_CACHE_SIZE = 500;
|
|
30
|
+
// Max age (ms) - 7 days for SQLite, 24h for cache
|
|
31
|
+
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
32
|
+
const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
33
|
+
|
|
34
|
+
let tableReady = false;
|
|
35
|
+
|
|
36
|
+
function ensureTable(): void {
|
|
37
|
+
if (tableReady) return;
|
|
38
|
+
try {
|
|
39
|
+
const db = getDatabase();
|
|
40
|
+
db.exec(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS responses_store (
|
|
42
|
+
response_id TEXT PRIMARY KEY,
|
|
43
|
+
response_json TEXT NOT NULL,
|
|
44
|
+
chat_messages_json TEXT NOT NULL,
|
|
45
|
+
stored_at INTEGER NOT NULL
|
|
46
|
+
)
|
|
47
|
+
`);
|
|
48
|
+
db.exec(`
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_responses_store_stored_at
|
|
50
|
+
ON responses_store(stored_at)
|
|
51
|
+
`);
|
|
52
|
+
tableReady = true;
|
|
53
|
+
} catch {
|
|
54
|
+
// Database not available — fall back to memory-only
|
|
55
|
+
tableReady = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Store a completed response for future `previous_response_id` lookups.
|
|
61
|
+
* Persists to SQLite + in-memory cache.
|
|
62
|
+
*/
|
|
63
|
+
export function storeResponse(
|
|
64
|
+
responseId: string,
|
|
65
|
+
response: ResponsesResponse,
|
|
66
|
+
chatMessages: StoredResponse["chatMessages"],
|
|
67
|
+
): void {
|
|
68
|
+
const entry: StoredResponse = {
|
|
69
|
+
response,
|
|
70
|
+
chatMessages,
|
|
71
|
+
storedAt: Date.now(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// In-memory cache (LRU eviction)
|
|
75
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
76
|
+
const oldest = [...cache.entries()]
|
|
77
|
+
.sort((a, b) => a[1].storedAt - b[1].storedAt)
|
|
78
|
+
.slice(0, Math.floor(MAX_CACHE_SIZE * 0.1));
|
|
79
|
+
for (const [key] of oldest) {
|
|
80
|
+
cache.delete(key);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
cache.set(responseId, entry);
|
|
84
|
+
|
|
85
|
+
// SQLite persistence
|
|
86
|
+
ensureTable();
|
|
87
|
+
if (tableReady) {
|
|
88
|
+
try {
|
|
89
|
+
const db = getDatabase();
|
|
90
|
+
db.prepare(
|
|
91
|
+
`INSERT OR REPLACE INTO responses_store (response_id, response_json, chat_messages_json, stored_at)
|
|
92
|
+
VALUES (?, ?, ?, ?)`,
|
|
93
|
+
).run(
|
|
94
|
+
responseId,
|
|
95
|
+
JSON.stringify(response),
|
|
96
|
+
JSON.stringify(chatMessages),
|
|
97
|
+
entry.storedAt,
|
|
98
|
+
);
|
|
99
|
+
} catch {
|
|
100
|
+
// Non-fatal: memory cache still works
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Retrieve stored history for a `previous_response_id`.
|
|
107
|
+
* Returns null if not found.
|
|
108
|
+
*/
|
|
109
|
+
export function getResponseHistory(
|
|
110
|
+
previousResponseId: string,
|
|
111
|
+
): StoredResponse["chatMessages"] | null {
|
|
112
|
+
// Check memory cache first
|
|
113
|
+
const cached = cache.get(previousResponseId);
|
|
114
|
+
if (cached) {
|
|
115
|
+
if (Date.now() - cached.storedAt > CACHE_MAX_AGE_MS) {
|
|
116
|
+
cache.delete(previousResponseId);
|
|
117
|
+
} else {
|
|
118
|
+
return cached.chatMessages;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Fall back to SQLite
|
|
123
|
+
ensureTable();
|
|
124
|
+
if (!tableReady) return null;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const db = getDatabase();
|
|
128
|
+
const row = db
|
|
129
|
+
.prepare(
|
|
130
|
+
`SELECT chat_messages_json, stored_at FROM responses_store WHERE response_id = ?`,
|
|
131
|
+
)
|
|
132
|
+
.get(previousResponseId) as
|
|
133
|
+
| { chat_messages_json: string; stored_at: number }
|
|
134
|
+
| undefined;
|
|
135
|
+
|
|
136
|
+
if (!row) return null;
|
|
137
|
+
|
|
138
|
+
if (Date.now() - row.stored_at > MAX_AGE_MS) {
|
|
139
|
+
db.prepare(`DELETE FROM responses_store WHERE response_id = ?`).run(
|
|
140
|
+
previousResponseId,
|
|
141
|
+
);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const chatMessages = JSON.parse(row.chat_messages_json);
|
|
146
|
+
// Re-populate cache
|
|
147
|
+
const responseRow = db
|
|
148
|
+
.prepare(`SELECT response_json FROM responses_store WHERE response_id = ?`)
|
|
149
|
+
.get(previousResponseId) as { response_json: string } | undefined;
|
|
150
|
+
if (responseRow) {
|
|
151
|
+
cache.set(previousResponseId, {
|
|
152
|
+
response: JSON.parse(responseRow.response_json),
|
|
153
|
+
chatMessages,
|
|
154
|
+
storedAt: row.stored_at,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return chatMessages;
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Retrieve the full stored response (for GET /v1/responses/:id).
|
|
166
|
+
* Returns null if not found.
|
|
167
|
+
*/
|
|
168
|
+
export function getStoredResponse(
|
|
169
|
+
responseId: string,
|
|
170
|
+
): ResponsesResponse | null {
|
|
171
|
+
// Check memory cache first
|
|
172
|
+
const cached = cache.get(responseId);
|
|
173
|
+
if (cached) {
|
|
174
|
+
if (Date.now() - cached.storedAt > CACHE_MAX_AGE_MS) {
|
|
175
|
+
cache.delete(responseId);
|
|
176
|
+
} else {
|
|
177
|
+
return cached.response;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Fall back to SQLite
|
|
182
|
+
ensureTable();
|
|
183
|
+
if (!tableReady) return null;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const db = getDatabase();
|
|
187
|
+
const row = db
|
|
188
|
+
.prepare(
|
|
189
|
+
`SELECT response_json, chat_messages_json, stored_at FROM responses_store WHERE response_id = ?`,
|
|
190
|
+
)
|
|
191
|
+
.get(responseId) as
|
|
192
|
+
| { response_json: string; chat_messages_json: string; stored_at: number }
|
|
193
|
+
| undefined;
|
|
194
|
+
|
|
195
|
+
if (!row) return null;
|
|
196
|
+
|
|
197
|
+
if (Date.now() - row.stored_at > MAX_AGE_MS) {
|
|
198
|
+
db.prepare(`DELETE FROM responses_store WHERE response_id = ?`).run(
|
|
199
|
+
responseId,
|
|
200
|
+
);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return JSON.parse(row.response_json);
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Delete a stored response.
|
|
212
|
+
*/
|
|
213
|
+
export function deleteStoredResponse(responseId: string): boolean {
|
|
214
|
+
const fromCache = cache.delete(responseId);
|
|
215
|
+
|
|
216
|
+
ensureTable();
|
|
217
|
+
if (tableReady) {
|
|
218
|
+
try {
|
|
219
|
+
const db = getDatabase();
|
|
220
|
+
const result = db
|
|
221
|
+
.prepare(`DELETE FROM responses_store WHERE response_id = ?`)
|
|
222
|
+
.run(responseId);
|
|
223
|
+
return fromCache || result.changes > 0;
|
|
224
|
+
} catch {
|
|
225
|
+
return fromCache;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return fromCache;
|
|
230
|
+
}
|