ds-bot 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +234 -3
- package/client.js +12176 -0
- package/cordis.patch.yml +17 -0
- package/index.js +52 -0
- package/package.json +64 -6
- package/src/host/agents.js +186 -0
- package/src/host/browser.js +258 -0
- package/src/host/channel.js +313 -0
- package/src/host/chat-index.js +293 -0
- package/src/host/connectors.js +169 -0
- package/src/host/constants.js +62 -0
- package/src/host/delivery.js +59 -0
- package/src/host/diagnostics.js +247 -0
- package/src/host/groups.js +442 -0
- package/src/host/home-lock.js +91 -0
- package/src/host/images.js +174 -0
- package/src/host/memory.js +1351 -0
- package/src/host/models.js +145 -0
- package/src/host/parts.js +886 -0
- package/src/host/prompt.js +67 -0
- package/src/host/roster.js +397 -0
- package/src/host/schedules.js +151 -0
- package/src/host/secrets.js +530 -0
- package/src/host/steward.js +164 -0
- package/src/host/store.js +259 -0
- package/src/host/text.js +47 -0
- package/src/host/tools.js +374 -0
- package/src/host/turns.js +250 -0
- package/src/host/update.js +205 -0
- package/src/host/usage.js +572 -0
- package/web-search.js +748 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Models. Every Bot runs on one model of its own, fixed when it is created and changed
|
|
2
|
+
// only in its settings or by a Main Bot. The team's first Main Bot has none until the
|
|
3
|
+
// user picks one in its chat. The Session keeps the selection durably.
|
|
4
|
+
import { normalize, splitRef } from './text.js'
|
|
5
|
+
|
|
6
|
+
const CATALOG_TTL_MS = 10_000
|
|
7
|
+
const ROUTE_WAIT_MS = 30_000
|
|
8
|
+
|
|
9
|
+
export function install(rt) {
|
|
10
|
+
const { ctx, state } = rt
|
|
11
|
+
|
|
12
|
+
let modelsSynced = null
|
|
13
|
+
let catalogCache = { at: 0, value: [] }
|
|
14
|
+
ctx.on?.('llm/adapters-updated', () => { catalogCache = { at: 0, value: [] } })
|
|
15
|
+
|
|
16
|
+
// A provider's model list can change in DSH settings without any event, so a picker
|
|
17
|
+
// that is about to be used asks for a `fresh` one.
|
|
18
|
+
async function catalog({ fresh = false } = {}) {
|
|
19
|
+
if (!fresh && Date.now() - catalogCache.at < CATALOG_TTL_MS && catalogCache.value.length > 0) return catalogCache.value
|
|
20
|
+
const entries = []
|
|
21
|
+
for (const provider of ctx.llm.listProviders()) {
|
|
22
|
+
let models = []
|
|
23
|
+
try { models = await ctx.llm.listModels(provider.id) } catch { continue }
|
|
24
|
+
for (const model of models) {
|
|
25
|
+
entries.push({
|
|
26
|
+
ref: `${provider.id}/${model.id}`,
|
|
27
|
+
provider: provider.id,
|
|
28
|
+
providerName: provider.name ?? provider.id,
|
|
29
|
+
id: model.id,
|
|
30
|
+
name: model.name ?? model.id,
|
|
31
|
+
...(model.inputModalities ? { input: [...model.inputModalities] } : {}),
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catalogCache = { at: Date.now(), value: entries }
|
|
36
|
+
return entries
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The profile default model of DSH as set, served or not.
|
|
40
|
+
const defaultRef = () => {
|
|
41
|
+
const selection = ctx.get?.('agentDefaultModel')?.currentSelection?.()
|
|
42
|
+
return selection?.provider && selection?.model ? `${selection.provider}/${selection.model}` : undefined
|
|
43
|
+
}
|
|
44
|
+
// The profile default model of DSH, when it is served; else the first served model.
|
|
45
|
+
function pickDefault(entries) {
|
|
46
|
+
const ref = defaultRef()
|
|
47
|
+
return entries.find(entry => entry.ref === ref)?.ref ?? entries[0]?.ref
|
|
48
|
+
}
|
|
49
|
+
const defaultModel = async () => pickDefault(await catalog())
|
|
50
|
+
|
|
51
|
+
// A full `provider/model` ref, or a model id or name that only one provider serves.
|
|
52
|
+
async function resolveModel(text) {
|
|
53
|
+
const entries = await catalog({ fresh: true })
|
|
54
|
+
const wanted = String(text ?? '').trim()
|
|
55
|
+
const exact = entries.find(entry => entry.ref === wanted)
|
|
56
|
+
if (exact) return exact.ref
|
|
57
|
+
const key = normalize(wanted)
|
|
58
|
+
const named = entries.filter(entry => normalize(entry.id) === key || normalize(entry.name) === key)
|
|
59
|
+
if (named.length === 1) return named[0].ref
|
|
60
|
+
const known = entries.map(entry => entry.ref).join(', ') || 'none'
|
|
61
|
+
throw new Error(named.length > 1
|
|
62
|
+
? `Model ${wanted} is served by more than one provider; use one of ${named.map(entry => entry.ref).join(', ')}`
|
|
63
|
+
: `Model ${wanted} is not available. Available models: ${known}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A Bot with no model yet waits for the user to pick one and changes nothing. A
|
|
67
|
+
// model that is no longer served falls back to the last one it ran on, then the default.
|
|
68
|
+
async function desiredModel(bot) {
|
|
69
|
+
if (!bot.model) return undefined
|
|
70
|
+
const entries = await catalog()
|
|
71
|
+
const served = new Set(entries.map(entry => entry.ref))
|
|
72
|
+
return [bot.model, bot.appliedModel].find(ref => ref && served.has(ref)) ?? pickDefault(entries)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// DSH's selectModel also saves the selection as the profile default model, in the
|
|
76
|
+
// background. A Bot's model is its own, so the user's default is written back after
|
|
77
|
+
// it. Selections run one at a time and wait for the write-back: profile writes land in
|
|
78
|
+
// submission order, so the next one reads the user's default, not a Bot's.
|
|
79
|
+
let selecting = Promise.resolve()
|
|
80
|
+
function selectSessionModel(sessionId, { provider, model }) {
|
|
81
|
+
const run = selecting.then(async () => {
|
|
82
|
+
const defaults = ctx.get?.('agentDefaultModel')
|
|
83
|
+
const before = defaults?.currentSelection?.()
|
|
84
|
+
const result = await ctx.sessionController.selectModel({ sessionId, provider, model })
|
|
85
|
+
if (before?.provider && before?.model && (before.provider !== provider || before.model !== model)) {
|
|
86
|
+
await defaults.saveSelection(before).catch(error => rt.warn('ds-bot: the default model was not restored: %s', error))
|
|
87
|
+
}
|
|
88
|
+
return result
|
|
89
|
+
})
|
|
90
|
+
selecting = run.catch(() => {})
|
|
91
|
+
return run
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// `force` re-selects even when the record says it is applied: the composer's own
|
|
95
|
+
// model picker can move the Session without telling this plugin.
|
|
96
|
+
async function applyModel(bot, { force = false } = {}) {
|
|
97
|
+
const ref = await desiredModel(bot)
|
|
98
|
+
if (ref === undefined || (!force && bot.appliedModel === ref)) return ref
|
|
99
|
+
const route = splitRef(ref)
|
|
100
|
+
await selectSessionModel(rt.chatOf(bot.id), route)
|
|
101
|
+
for (const sessionId of rt.groupSessionsOf(bot.id)) {
|
|
102
|
+
await selectSessionModel(sessionId, route)
|
|
103
|
+
.catch(error => rt.warn('ds-bot: model for %s in a group not applied: %s', bot.name, error))
|
|
104
|
+
}
|
|
105
|
+
bot.appliedModel = ref
|
|
106
|
+
await rt.save()
|
|
107
|
+
return ref
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// A provider may register after the plugin starts; a greeting waits for its Bot's
|
|
111
|
+
// model instead of failing with "no adapter registered".
|
|
112
|
+
async function waitForModel(bot, ms = ROUTE_WAIT_MS) {
|
|
113
|
+
const deadline = Date.now() + ms
|
|
114
|
+
for (;;) {
|
|
115
|
+
if (!bot.model) return undefined
|
|
116
|
+
catalogCache = { at: 0, value: [] }
|
|
117
|
+
const served = new Set((await catalog()).map(entry => entry.ref))
|
|
118
|
+
if (served.has(bot.model) || Date.now() >= deadline) return desiredModel(bot)
|
|
119
|
+
await new Promise((resolve) => {
|
|
120
|
+
const timer = setTimeout(done, Math.min(2000, deadline - Date.now()))
|
|
121
|
+
const off = ctx.on?.('llm/adapters-updated', done)
|
|
122
|
+
function done() { clearTimeout(timer); off?.(); resolve() }
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Sequential, and again whenever the served models change: a provider that
|
|
128
|
+
// registers late still gets its Bots' models selected.
|
|
129
|
+
let syncedKey
|
|
130
|
+
const syncModels = () => {
|
|
131
|
+
const key = catalogCache.value.map(entry => entry.ref).join(',')
|
|
132
|
+
if (key !== syncedKey) { syncedKey = key; modelsSynced = modelsSynced?.then(syncAll, syncAll) ?? syncAll() }
|
|
133
|
+
return modelsSynced
|
|
134
|
+
}
|
|
135
|
+
async function syncAll() {
|
|
136
|
+
for (const bot of Object.values(state.bots)) {
|
|
137
|
+
await applyModel(bot).catch(error => rt.warn('ds-bot: model for %s not applied: %s', bot.name, error))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Object.assign(rt, {
|
|
142
|
+
catalog, cachedCatalog: () => catalogCache.value, cachedDefault: () => pickDefault(catalogCache.value),
|
|
143
|
+
defaultRef, defaultModel, resolveModel, desiredModel, selectSessionModel, applyModel, waitForModel, syncModels,
|
|
144
|
+
})
|
|
145
|
+
}
|