dsh-reference-anything 0.5.1 → 0.5.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 +22 -22
- package/NOTICE.md +110 -110
- package/README.md +472 -459
- package/cordis.patch.yml +136 -136
- package/docs/release/0.4.4/README.md +75 -0
- package/{release-notes → docs/release}/0.4.4/features.json +36 -36
- package/docs/release/0.5.0/README.md +148 -0
- package/{release-notes → docs/release}/0.5.0/features.json +65 -65
- package/docs/release/0.5.1/README.md +33 -0
- package/docs/release/0.5.2/README.md +51 -0
- package/docs/release/0.5.2/features.json +29 -0
- package/{release-notes → docs/release}/README.md +42 -38
- package/images/agents/LICENSE +22 -22
- package/images/agents/README.md +7 -3
- package/images/agents/antigravity.svg +1 -1
- package/images/agents/cline.svg +1 -1
- package/images/agents/continue.svg +1 -1
- package/images/agents/copilot.svg +27 -0
- package/images/agents/crush.svg +1 -1
- package/images/agents/doubao.svg +1 -0
- package/images/agents/goose.svg +1 -1
- package/images/agents/omp.svg +1 -0
- package/images/agents/reasonix.svg +5 -5
- package/images/agents/teleagent.svg +1 -1
- package/images/agents/trae.svg +1 -1
- package/images/agents/vibe.svg +1 -1
- package/images/agents/workbuddy.svg +37 -37
- package/images/agents/zed.svg +1 -1
- package/lib/chat-import-compatibility.js +8 -5
- package/lib/client.js +36526 -4
- package/lib/contract.d.ts +4 -0
- package/lib/host.d.ts +2 -2
- package/lib/local-conversation-reader.js +16 -11
- package/lib/render.js +21 -21
- package/lib/store/spec.d.ts +9 -1
- package/lib/tool.js +3 -2
- package/lib/wire.d.ts +13 -1
- package/lib/wire.js +5 -4
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/opencli-plugin/chatgpt.js +128 -128
- package/opencli-plugin/claude.js +138 -138
- package/opencli-plugin/common.js +452 -452
- package/opencli-plugin/deepseek.js +155 -155
- package/opencli-plugin/gemini.js +169 -169
- package/opencli-plugin/grok.js +124 -124
- package/opencli-plugin/kimi.js +126 -126
- package/opencli-plugin/opencli-plugin.json +6 -6
- package/opencli-plugin/package.json +8 -8
- package/opencli-plugin/since-guard.js +45 -45
- package/package.json +263 -262
- package/vendor/chat-import-reader/LICENSE +22 -22
- package/vendor/chat-import-reader/LICENSE.fzstd +20 -20
- package/vendor/chat-import-reader/README.md +54 -48
- package/vendor/chat-import-reader/availability-worker.mjs +119 -114
- package/vendor/chat-import-reader/parsers.d.mts +6 -6
- package/vendor/chat-import-reader/parsers.mjs +13069 -11168
- package/vendor/chat-import-reader/scan-worker.mjs +7 -7
- package/vendor/chat-import-reader/scanners.mjs +5523 -4286
- package/vendor/chat-import-reader/upstream-sha256.json +114 -101
- package/release-notes/0.4.4/commands.png +0 -0
- package/release-notes/0.4.4/demos/agent-import.gif +0 -0
- package/release-notes/0.4.4/demos/chat-import-upgrade.gif +0 -0
- package/release-notes/0.4.4/demos/folders.gif +0 -0
- package/release-notes/0.4.4/demos/local-plugins.gif +0 -0
- package/release-notes/0.4.4/demos/shortcuts.gif +0 -0
- package/release-notes/0.4.4/files.png +0 -0
- package/release-notes/0.5.0/demos/agent-import.gif +0 -0
- package/release-notes/0.5.0/demos/chat-import-upgrade.gif +0 -0
- package/release-notes/0.5.0/demos/folders.gif +0 -0
- package/release-notes/0.5.0/demos/local-plugins.gif +0 -0
- package/release-notes/0.5.0/demos/local-references.gif +0 -0
- package/release-notes/0.5.0/demos/shortcuts.gif +0 -0
package/opencli-plugin/kimi.js
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import { domFallbackScript, registerProvider, sinceGuardSource } from './common.js'
|
|
2
|
-
// OpenCLI discovery marker: registerProvider below performs the cli() calls.
|
|
3
|
-
|
|
4
|
-
const authHeaders = String.raw`() => {
|
|
5
|
-
const token = localStorage.getItem('access_token') || ''
|
|
6
|
-
return { Accept: 'application/json', 'Content-Type': 'application/json', ...(token ? { Authorization: 'Bearer ' + token } : {}) }
|
|
7
|
-
}`
|
|
8
|
-
|
|
9
|
-
const historyScript = String.raw`async function (args) {
|
|
10
|
-
const stopEarly = (${sinceGuardSource})(args && args.since)
|
|
11
|
-
const headers = (${authHeaders})()
|
|
12
|
-
const rows = []
|
|
13
|
-
const toDate = value => {
|
|
14
|
-
const numeric = Number(value)
|
|
15
|
-
return Number.isFinite(numeric) && numeric > 0 ? new Date(numeric < 1e12 ? numeric * 1000 : numeric).toISOString() : String(value || '')
|
|
16
|
-
}
|
|
17
|
-
try {
|
|
18
|
-
for (let offset = 0, page = 0; page < 500; page++, offset += 100) {
|
|
19
|
-
const response = await fetch('/api/chat/list', { method: 'POST', credentials: 'include', headers,
|
|
20
|
-
body: JSON.stringify({ offset, size: 100 }) })
|
|
21
|
-
if (response.status === 401 || response.status === 403) return JSON.stringify({ ok: false, code: 'AUTH' })
|
|
22
|
-
if (response.status === 429) return JSON.stringify({ ok: false, code: 'RATE_LIMIT' })
|
|
23
|
-
if (!response.ok) throw new Error('history HTTP ' + response.status)
|
|
24
|
-
const payload = await response.json()
|
|
25
|
-
const items = payload.items || payload.chats || payload.data && (payload.data.items || payload.data.chats) || []
|
|
26
|
-
const pageStart = rows.length
|
|
27
|
-
for (const item of items) {
|
|
28
|
-
const id = String(item.id || item.chat_id || '')
|
|
29
|
-
if (!id) continue
|
|
30
|
-
rows.push({ provider: 'kimi', accountScope: '', id,
|
|
31
|
-
title: String(item.name || item.title || 'Untitled Kimi chat'),
|
|
32
|
-
url: location.origin + '/chat/' + encodeURIComponent(id),
|
|
33
|
-
createdAt: toDate(item.created_at || item.createdAt || item.created),
|
|
34
|
-
updatedAt: toDate(item.updated_at || item.updatedAt || item.last_updated_at || item.created_at),
|
|
35
|
-
messageCount: Number(item.message_count || item.messageCount || item.messages_count || 0),
|
|
36
|
-
cursor: String(item.updated_at || item.updatedAt || ''), partial: false })
|
|
37
|
-
}
|
|
38
|
-
if (items.length < 100 || stopEarly(rows.slice(pageStart))) break
|
|
39
|
-
}
|
|
40
|
-
return JSON.stringify({ ok: true, rows })
|
|
41
|
-
} catch (error) {
|
|
42
|
-
const seen = new Set()
|
|
43
|
-
for (const link of document.querySelectorAll('a[href*="/chat/"]')) {
|
|
44
|
-
const match = link.href.match(/\/chat\/([0-9a-f-]{8,})/i)
|
|
45
|
-
if (!match || seen.has(match[1])) continue
|
|
46
|
-
seen.add(match[1]); rows.push({ provider: 'kimi', accountScope: '', id: match[1],
|
|
47
|
-
title: (link.textContent || '').trim() || 'Untitled Kimi chat', url: link.href,
|
|
48
|
-
createdAt: '', updatedAt: '', messageCount: 0, cursor: '', partial: true })
|
|
49
|
-
}
|
|
50
|
-
return JSON.stringify({ ok: rows.length > 0, rows, message: rows.length ? '' : String(error && error.message || error) })
|
|
51
|
-
}
|
|
52
|
-
}`
|
|
53
|
-
|
|
54
|
-
export const detailScript = String.raw`async function (args) {
|
|
55
|
-
const headers = (${authHeaders})()
|
|
56
|
-
const textOf = value => typeof value === 'string' ? value
|
|
57
|
-
: Array.isArray(value) ? value.map(textOf).filter(Boolean).join('\n')
|
|
58
|
-
: value && typeof value === 'object' ? textOf(value.text || value.content || value.value || value.output || '') : ''
|
|
59
|
-
const attachmentOf = (file, index) => {
|
|
60
|
-
if (!file || typeof file !== 'object') return null
|
|
61
|
-
const candidate = String(file.download_url || file.url || file.path || '')
|
|
62
|
-
let locator = ''
|
|
63
|
-
try { if (candidate) { const url = new URL(candidate, location.origin); if (url.origin === location.origin && url.pathname !== '/') locator = url.pathname + url.search } } catch {}
|
|
64
|
-
const name = String(file.name || file.file_name || 'attachment')
|
|
65
|
-
const mimeType = String(file.mime_type || file.mimeType || file.content_type || '')
|
|
66
|
-
return { attachmentId: String(file.id || file.file_id || index),
|
|
67
|
-
kind: mimeType.startsWith('image/') || /\.(?:gif|jpe?g|png|webp)$/i.test(name) ? 'image' : 'file',
|
|
68
|
-
name, mimeType, size: Number(file.size || file.file_size || 0), locator,
|
|
69
|
-
status: locator ? 'available' : 'unavailable' }
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
const messages = []
|
|
73
|
-
let pageToken = ''
|
|
74
|
-
for (let page = 0; page < 500; page++) {
|
|
75
|
-
let response = await fetch('/apiv2/kimi.chat.v1.ChatService/ListMessages', { method: 'POST', credentials: 'include', headers,
|
|
76
|
-
body: JSON.stringify({ chat_id: args.id, ...(pageToken ? { pageToken } : {}) }) })
|
|
77
|
-
if ((response.status === 404 || response.status === 405) && page === 0) {
|
|
78
|
-
response = await fetch('/api/chat/' + encodeURIComponent(args.id) + '/messages', { credentials: 'include', headers })
|
|
79
|
-
}
|
|
80
|
-
if (response.status === 401 || response.status === 403) return JSON.stringify({ ok: false, code: 'AUTH' })
|
|
81
|
-
if (response.status === 429) return JSON.stringify({ ok: false, code: 'RATE_LIMIT' })
|
|
82
|
-
if (!response.ok) throw new Error('detail HTTP ' + response.status)
|
|
83
|
-
const payload = await response.json()
|
|
84
|
-
const items = payload.messages || payload.items || payload.data && (payload.data.messages || payload.data.items) || []
|
|
85
|
-
messages.push(...items)
|
|
86
|
-
const next = String(payload.nextPageToken || payload.next_page_token || payload.data && (payload.data.nextPageToken || payload.data.next_page_token) || '')
|
|
87
|
-
if (!next || next === pageToken || items.length === 0) break
|
|
88
|
-
pageToken = next
|
|
89
|
-
}
|
|
90
|
-
// Kimi lists newest messages first. The local mirror and every other
|
|
91
|
-
// adapter use chronological ordinals, so reverse only after all pages land.
|
|
92
|
-
messages.reverse()
|
|
93
|
-
const rows = messages.map((message, ordinal) => {
|
|
94
|
-
const roleRaw = String(message.role || message.sender || message.message_role || message.type || '').toLowerCase()
|
|
95
|
-
const role = /assistant|bot|model|kimi/.test(roleRaw) ? 'assistant' : 'user'
|
|
96
|
-
const files = message.attachments || message.files || message.file_infos || []
|
|
97
|
-
const attachments = (Array.isArray(files) ? files : []).map(attachmentOf).filter(Boolean)
|
|
98
|
-
return { conversationId: args.id, ordinal,
|
|
99
|
-
messageId: String(message.id || message.message_id || ordinal),
|
|
100
|
-
parentId: String(message.parent_id || message.parentId || ''), branchId: '', activeBranch: true,
|
|
101
|
-
role, text: textOf(message.blocks || message.contents || message.content || message.text || message.fragments || ''),
|
|
102
|
-
createdAt: String(message.created_at || message.createdAt || message.timestamp || ''),
|
|
103
|
-
attachmentsJson: JSON.stringify(attachments), partial: false }
|
|
104
|
-
}).filter(row => row.text || row.attachmentsJson !== '[]')
|
|
105
|
-
return JSON.stringify({ ok: rows.length > 0, rows, message: rows.length ? '' : 'response contained no messages' })
|
|
106
|
-
} catch (error) {
|
|
107
|
-
return JSON.stringify({ ok: false, message: String(error && error.message || error) })
|
|
108
|
-
}
|
|
109
|
-
}`
|
|
110
|
-
|
|
111
|
-
export const whoamiScript = String.raw`async function () {
|
|
112
|
-
const headers = (${authHeaders})()
|
|
113
|
-
const response = await fetch('/api/user', { credentials: 'include', headers })
|
|
114
|
-
if (response.status === 401 || response.status === 403) return JSON.stringify({ ok: false, code: 'AUTH' })
|
|
115
|
-
if (response.status === 429) return JSON.stringify({ ok: false, code: 'RATE_LIMIT' })
|
|
116
|
-
if (!response.ok) return JSON.stringify({ ok: false, message: 'user HTTP ' + response.status })
|
|
117
|
-
const user = await response.json(); const identity = String(user.id || user.user_id || user.email || '')
|
|
118
|
-
return JSON.stringify({ ok: Boolean(identity), identity, code: identity ? undefined : 'AUTH' })
|
|
119
|
-
}`
|
|
120
|
-
|
|
121
|
-
registerProvider({
|
|
122
|
-
site: 'dsh-kimi', provider: 'Kimi', domain: 'kimi.com', home: 'https://www.kimi.com/',
|
|
123
|
-
conversationUrl: id => `https://www.kimi.com/chat/${encodeURIComponent(id)}`,
|
|
124
|
-
whoamiScript,
|
|
125
|
-
historyScript, detailScript, fallbackScript: domFallbackScript,
|
|
126
|
-
})
|
|
1
|
+
import { domFallbackScript, registerProvider, sinceGuardSource } from './common.js'
|
|
2
|
+
// OpenCLI discovery marker: registerProvider below performs the cli() calls.
|
|
3
|
+
|
|
4
|
+
const authHeaders = String.raw`() => {
|
|
5
|
+
const token = localStorage.getItem('access_token') || ''
|
|
6
|
+
return { Accept: 'application/json', 'Content-Type': 'application/json', ...(token ? { Authorization: 'Bearer ' + token } : {}) }
|
|
7
|
+
}`
|
|
8
|
+
|
|
9
|
+
const historyScript = String.raw`async function (args) {
|
|
10
|
+
const stopEarly = (${sinceGuardSource})(args && args.since)
|
|
11
|
+
const headers = (${authHeaders})()
|
|
12
|
+
const rows = []
|
|
13
|
+
const toDate = value => {
|
|
14
|
+
const numeric = Number(value)
|
|
15
|
+
return Number.isFinite(numeric) && numeric > 0 ? new Date(numeric < 1e12 ? numeric * 1000 : numeric).toISOString() : String(value || '')
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
for (let offset = 0, page = 0; page < 500; page++, offset += 100) {
|
|
19
|
+
const response = await fetch('/api/chat/list', { method: 'POST', credentials: 'include', headers,
|
|
20
|
+
body: JSON.stringify({ offset, size: 100 }) })
|
|
21
|
+
if (response.status === 401 || response.status === 403) return JSON.stringify({ ok: false, code: 'AUTH' })
|
|
22
|
+
if (response.status === 429) return JSON.stringify({ ok: false, code: 'RATE_LIMIT' })
|
|
23
|
+
if (!response.ok) throw new Error('history HTTP ' + response.status)
|
|
24
|
+
const payload = await response.json()
|
|
25
|
+
const items = payload.items || payload.chats || payload.data && (payload.data.items || payload.data.chats) || []
|
|
26
|
+
const pageStart = rows.length
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
const id = String(item.id || item.chat_id || '')
|
|
29
|
+
if (!id) continue
|
|
30
|
+
rows.push({ provider: 'kimi', accountScope: '', id,
|
|
31
|
+
title: String(item.name || item.title || 'Untitled Kimi chat'),
|
|
32
|
+
url: location.origin + '/chat/' + encodeURIComponent(id),
|
|
33
|
+
createdAt: toDate(item.created_at || item.createdAt || item.created),
|
|
34
|
+
updatedAt: toDate(item.updated_at || item.updatedAt || item.last_updated_at || item.created_at),
|
|
35
|
+
messageCount: Number(item.message_count || item.messageCount || item.messages_count || 0),
|
|
36
|
+
cursor: String(item.updated_at || item.updatedAt || ''), partial: false })
|
|
37
|
+
}
|
|
38
|
+
if (items.length < 100 || stopEarly(rows.slice(pageStart))) break
|
|
39
|
+
}
|
|
40
|
+
return JSON.stringify({ ok: true, rows })
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const seen = new Set()
|
|
43
|
+
for (const link of document.querySelectorAll('a[href*="/chat/"]')) {
|
|
44
|
+
const match = link.href.match(/\/chat\/([0-9a-f-]{8,})/i)
|
|
45
|
+
if (!match || seen.has(match[1])) continue
|
|
46
|
+
seen.add(match[1]); rows.push({ provider: 'kimi', accountScope: '', id: match[1],
|
|
47
|
+
title: (link.textContent || '').trim() || 'Untitled Kimi chat', url: link.href,
|
|
48
|
+
createdAt: '', updatedAt: '', messageCount: 0, cursor: '', partial: true })
|
|
49
|
+
}
|
|
50
|
+
return JSON.stringify({ ok: rows.length > 0, rows, message: rows.length ? '' : String(error && error.message || error) })
|
|
51
|
+
}
|
|
52
|
+
}`
|
|
53
|
+
|
|
54
|
+
export const detailScript = String.raw`async function (args) {
|
|
55
|
+
const headers = (${authHeaders})()
|
|
56
|
+
const textOf = value => typeof value === 'string' ? value
|
|
57
|
+
: Array.isArray(value) ? value.map(textOf).filter(Boolean).join('\n')
|
|
58
|
+
: value && typeof value === 'object' ? textOf(value.text || value.content || value.value || value.output || '') : ''
|
|
59
|
+
const attachmentOf = (file, index) => {
|
|
60
|
+
if (!file || typeof file !== 'object') return null
|
|
61
|
+
const candidate = String(file.download_url || file.url || file.path || '')
|
|
62
|
+
let locator = ''
|
|
63
|
+
try { if (candidate) { const url = new URL(candidate, location.origin); if (url.origin === location.origin && url.pathname !== '/') locator = url.pathname + url.search } } catch {}
|
|
64
|
+
const name = String(file.name || file.file_name || 'attachment')
|
|
65
|
+
const mimeType = String(file.mime_type || file.mimeType || file.content_type || '')
|
|
66
|
+
return { attachmentId: String(file.id || file.file_id || index),
|
|
67
|
+
kind: mimeType.startsWith('image/') || /\.(?:gif|jpe?g|png|webp)$/i.test(name) ? 'image' : 'file',
|
|
68
|
+
name, mimeType, size: Number(file.size || file.file_size || 0), locator,
|
|
69
|
+
status: locator ? 'available' : 'unavailable' }
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const messages = []
|
|
73
|
+
let pageToken = ''
|
|
74
|
+
for (let page = 0; page < 500; page++) {
|
|
75
|
+
let response = await fetch('/apiv2/kimi.chat.v1.ChatService/ListMessages', { method: 'POST', credentials: 'include', headers,
|
|
76
|
+
body: JSON.stringify({ chat_id: args.id, ...(pageToken ? { pageToken } : {}) }) })
|
|
77
|
+
if ((response.status === 404 || response.status === 405) && page === 0) {
|
|
78
|
+
response = await fetch('/api/chat/' + encodeURIComponent(args.id) + '/messages', { credentials: 'include', headers })
|
|
79
|
+
}
|
|
80
|
+
if (response.status === 401 || response.status === 403) return JSON.stringify({ ok: false, code: 'AUTH' })
|
|
81
|
+
if (response.status === 429) return JSON.stringify({ ok: false, code: 'RATE_LIMIT' })
|
|
82
|
+
if (!response.ok) throw new Error('detail HTTP ' + response.status)
|
|
83
|
+
const payload = await response.json()
|
|
84
|
+
const items = payload.messages || payload.items || payload.data && (payload.data.messages || payload.data.items) || []
|
|
85
|
+
messages.push(...items)
|
|
86
|
+
const next = String(payload.nextPageToken || payload.next_page_token || payload.data && (payload.data.nextPageToken || payload.data.next_page_token) || '')
|
|
87
|
+
if (!next || next === pageToken || items.length === 0) break
|
|
88
|
+
pageToken = next
|
|
89
|
+
}
|
|
90
|
+
// Kimi lists newest messages first. The local mirror and every other
|
|
91
|
+
// adapter use chronological ordinals, so reverse only after all pages land.
|
|
92
|
+
messages.reverse()
|
|
93
|
+
const rows = messages.map((message, ordinal) => {
|
|
94
|
+
const roleRaw = String(message.role || message.sender || message.message_role || message.type || '').toLowerCase()
|
|
95
|
+
const role = /assistant|bot|model|kimi/.test(roleRaw) ? 'assistant' : 'user'
|
|
96
|
+
const files = message.attachments || message.files || message.file_infos || []
|
|
97
|
+
const attachments = (Array.isArray(files) ? files : []).map(attachmentOf).filter(Boolean)
|
|
98
|
+
return { conversationId: args.id, ordinal,
|
|
99
|
+
messageId: String(message.id || message.message_id || ordinal),
|
|
100
|
+
parentId: String(message.parent_id || message.parentId || ''), branchId: '', activeBranch: true,
|
|
101
|
+
role, text: textOf(message.blocks || message.contents || message.content || message.text || message.fragments || ''),
|
|
102
|
+
createdAt: String(message.created_at || message.createdAt || message.timestamp || ''),
|
|
103
|
+
attachmentsJson: JSON.stringify(attachments), partial: false }
|
|
104
|
+
}).filter(row => row.text || row.attachmentsJson !== '[]')
|
|
105
|
+
return JSON.stringify({ ok: rows.length > 0, rows, message: rows.length ? '' : 'response contained no messages' })
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return JSON.stringify({ ok: false, message: String(error && error.message || error) })
|
|
108
|
+
}
|
|
109
|
+
}`
|
|
110
|
+
|
|
111
|
+
export const whoamiScript = String.raw`async function () {
|
|
112
|
+
const headers = (${authHeaders})()
|
|
113
|
+
const response = await fetch('/api/user', { credentials: 'include', headers })
|
|
114
|
+
if (response.status === 401 || response.status === 403) return JSON.stringify({ ok: false, code: 'AUTH' })
|
|
115
|
+
if (response.status === 429) return JSON.stringify({ ok: false, code: 'RATE_LIMIT' })
|
|
116
|
+
if (!response.ok) return JSON.stringify({ ok: false, message: 'user HTTP ' + response.status })
|
|
117
|
+
const user = await response.json(); const identity = String(user.id || user.user_id || user.email || '')
|
|
118
|
+
return JSON.stringify({ ok: Boolean(identity), identity, code: identity ? undefined : 'AUTH' })
|
|
119
|
+
}`
|
|
120
|
+
|
|
121
|
+
registerProvider({
|
|
122
|
+
site: 'dsh-kimi', provider: 'Kimi', domain: 'kimi.com', home: 'https://www.kimi.com/',
|
|
123
|
+
conversationUrl: id => `https://www.kimi.com/chat/${encodeURIComponent(id)}`,
|
|
124
|
+
whoamiScript,
|
|
125
|
+
historyScript, detailScript, fallbackScript: domFallbackScript,
|
|
126
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-chat-history",
|
|
3
|
-
"version": "0.2.3",
|
|
4
|
-
"opencli": ">=2.0.0",
|
|
5
|
-
"description": "Full-history read adapters used by dsh-reference-anything"
|
|
6
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-chat-history",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"opencli": ">=2.0.0",
|
|
5
|
+
"description": "Full-history read adapters used by dsh-reference-anything"
|
|
6
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "opencli-plugin-dsh-chat-history",
|
|
3
|
-
"version": "0.2.3",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"peerDependencies": {
|
|
6
|
-
"@jackwener/opencli": ">=2.0.0"
|
|
7
|
-
}
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "opencli-plugin-dsh-chat-history",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"@jackwener/opencli": ">=2.0.0"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Early-stop helper shared by every provider's history script.
|
|
3
|
-
*
|
|
4
|
-
* Lives in its own module, free of imports, so it can be unit-tested without
|
|
5
|
-
* the OpenCLI registry that `common.js` pulls in.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Source of the guard, embedded into each provider's page script.
|
|
10
|
-
*
|
|
11
|
-
* Paging may only stop early while the provider actually returns
|
|
12
|
-
* conversations newest-first, which not every one of these endpoints
|
|
13
|
-
* documents. So the guard watches the `updatedAt` sequence it is fed and
|
|
14
|
-
* authorizes a stop only while that sequence has been non-increasing *and*
|
|
15
|
-
* the whole current page already predates the cutoff. A provider that pages
|
|
16
|
-
* in some other order, or that omits timestamps, walks its full history
|
|
17
|
-
* exactly as it did before `since` existed.
|
|
18
|
-
*/
|
|
19
|
-
export const sinceGuardSource = String.raw`function (since) {
|
|
20
|
-
const cutoff = since ? Date.parse(since) : NaN
|
|
21
|
-
let previous = Infinity
|
|
22
|
-
let ordered = true
|
|
23
|
-
return function (page) {
|
|
24
|
-
if (!(cutoff > 0) || page.length === 0) return false
|
|
25
|
-
let older = true
|
|
26
|
-
for (const row of page) {
|
|
27
|
-
const at = Date.parse(row.updatedAt || '')
|
|
28
|
-
if (!(at > 0)) return false
|
|
29
|
-
if (at > previous) ordered = false
|
|
30
|
-
previous = at
|
|
31
|
-
if (at >= cutoff) older = false
|
|
32
|
-
}
|
|
33
|
-
return ordered && older
|
|
34
|
-
}
|
|
35
|
-
}`
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Materialize the guard for tests and for any caller that wants it directly.
|
|
39
|
-
* @param {string} since - ISO instant, or '' to disable early stopping.
|
|
40
|
-
* @returns {(page: Array<{updatedAt?: string}>) => boolean} whether paging may stop.
|
|
41
|
-
*/
|
|
42
|
-
export function createSinceGuard(since) {
|
|
43
|
-
// eslint-disable-next-line no-eval -- the source is a package constant, never caller data.
|
|
44
|
-
return (0, eval)(`(${sinceGuardSource})`)(since)
|
|
45
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Early-stop helper shared by every provider's history script.
|
|
3
|
+
*
|
|
4
|
+
* Lives in its own module, free of imports, so it can be unit-tested without
|
|
5
|
+
* the OpenCLI registry that `common.js` pulls in.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Source of the guard, embedded into each provider's page script.
|
|
10
|
+
*
|
|
11
|
+
* Paging may only stop early while the provider actually returns
|
|
12
|
+
* conversations newest-first, which not every one of these endpoints
|
|
13
|
+
* documents. So the guard watches the `updatedAt` sequence it is fed and
|
|
14
|
+
* authorizes a stop only while that sequence has been non-increasing *and*
|
|
15
|
+
* the whole current page already predates the cutoff. A provider that pages
|
|
16
|
+
* in some other order, or that omits timestamps, walks its full history
|
|
17
|
+
* exactly as it did before `since` existed.
|
|
18
|
+
*/
|
|
19
|
+
export const sinceGuardSource = String.raw`function (since) {
|
|
20
|
+
const cutoff = since ? Date.parse(since) : NaN
|
|
21
|
+
let previous = Infinity
|
|
22
|
+
let ordered = true
|
|
23
|
+
return function (page) {
|
|
24
|
+
if (!(cutoff > 0) || page.length === 0) return false
|
|
25
|
+
let older = true
|
|
26
|
+
for (const row of page) {
|
|
27
|
+
const at = Date.parse(row.updatedAt || '')
|
|
28
|
+
if (!(at > 0)) return false
|
|
29
|
+
if (at > previous) ordered = false
|
|
30
|
+
previous = at
|
|
31
|
+
if (at >= cutoff) older = false
|
|
32
|
+
}
|
|
33
|
+
return ordered && older
|
|
34
|
+
}
|
|
35
|
+
}`
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Materialize the guard for tests and for any caller that wants it directly.
|
|
39
|
+
* @param {string} since - ISO instant, or '' to disable early stopping.
|
|
40
|
+
* @returns {(page: Array<{updatedAt?: string}>) => boolean} whether paging may stop.
|
|
41
|
+
*/
|
|
42
|
+
export function createSinceGuard(since) {
|
|
43
|
+
// eslint-disable-next-line no-eval -- the source is a package constant, never caller data.
|
|
44
|
+
return (0, eval)(`(${sinceGuardSource})`)(since)
|
|
45
|
+
}
|