@ljwei-stak/dsh-model-route-bot 0.66.3 → 0.67.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/.dsh-plugin/bot-product.mjs +81 -5
- package/.dsh-plugin/client.js +17490 -1791
- package/README.i18n.yaml +4 -4
- package/README.md +409 -364
- package/README.zh.md +409 -364
- package/apps/integration-client/README.md +31 -7
- package/apps/integration-client/api.mjs +14 -3
- package/apps/integration-client/binding-editor.jsx +308 -109
- package/apps/integration-client/binding-model.mjs +262 -0
- package/apps/integration-client/binding.css +144 -0
- package/apps/integration-client/bot-model-status.mjs +18 -0
- package/apps/integration-client/bot-profile-identity.jsx +197 -0
- package/apps/integration-client/bot-profile.css +57 -0
- package/apps/integration-client/bot-profile.mjs +97 -0
- package/apps/integration-client/bot-settings-dialog.css +18 -0
- package/apps/integration-client/bot-settings-dialog.jsx +111 -0
- package/apps/integration-client/bot-thread-header-bridge.css +20 -0
- package/apps/integration-client/bot-thread-header.jsx +278 -0
- package/apps/integration-client/bot-threads-panel.jsx +378 -0
- package/apps/integration-client/bot-threads.css +169 -0
- package/apps/integration-client/chat-design.mjs +250 -0
- package/apps/integration-client/chat-ui.jsx +175 -0
- package/apps/integration-client/chat.css +477 -0
- package/apps/integration-client/composer-draft-binding.mjs +50 -0
- package/apps/integration-client/composer-draft-bound.jsx +14 -0
- package/apps/integration-client/composer-draft-host.jsx +142 -0
- package/apps/integration-client/composer-draft-session.jsx +287 -0
- package/apps/integration-client/composer-draft-witness.mjs +115 -0
- package/apps/integration-client/composer-draft.css +4 -0
- package/apps/integration-client/composer-draft.mjs +322 -0
- package/apps/integration-client/contact-attention.jsx +54 -0
- package/apps/integration-client/data-panel.jsx +439 -0
- package/apps/integration-client/data.css +69 -0
- package/apps/integration-client/desktop-bridge.mjs +1 -0
- package/apps/integration-client/desktop-http-socket.mjs +49 -0
- package/apps/integration-client/desktop-keyboard.mjs +32 -0
- package/apps/integration-client/desktop-pane.jsx +99 -30
- package/apps/integration-client/desktop-stream.mjs +13 -3
- package/apps/integration-client/desktop-tools.jsx +19 -0
- package/apps/integration-client/desktop-touch.mjs +27 -0
- package/apps/integration-client/desktop.css +41 -0
- package/apps/integration-client/independent-chat-controller.mjs +224 -0
- package/apps/integration-client/independent-chat-entry.jsx +61 -0
- package/apps/integration-client/independent-chat-navigation.mjs +21 -0
- package/apps/integration-client/independent-chat-page.jsx +148 -0
- package/apps/integration-client/independent-chat-rich.jsx +64 -0
- package/apps/integration-client/independent-chat-rich.mjs +30 -0
- package/apps/integration-client/independent-chat.css +73 -0
- package/apps/integration-client/index.jsx +24 -16
- package/apps/integration-client/library-panel.jsx +4 -1
- package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
- package/apps/integration-client/main-route-editor.jsx +94 -0
- package/apps/integration-client/main-route-model.mjs +100 -0
- package/apps/integration-client/model-picker.css +37 -0
- package/apps/integration-client/model-picker.jsx +83 -0
- package/apps/integration-client/native-activity.css +141 -0
- package/apps/integration-client/native-activity.jsx +276 -0
- package/apps/integration-client/native-activity.mjs +384 -0
- package/apps/integration-client/notifications-panel.jsx +186 -0
- package/apps/integration-client/notifications.css +126 -0
- package/apps/integration-client/pairing-qr.jsx +79 -0
- package/apps/integration-client/panels.jsx +126 -115
- package/apps/integration-client/product-slots.jsx +46 -0
- package/apps/integration-client/routine-recorder.mjs +23 -2
- package/apps/integration-client/secure-pairing.jsx +84 -0
- package/apps/integration-client/tasks-panel.jsx +32 -10
- package/apps/integration-client/ui-icons.jsx +17 -0
- package/apps/integration-client/usage-explorer.jsx +43 -0
- package/apps/integration-client/usage-report.mjs +71 -0
- package/apps/integration-client/usage.css +34 -0
- package/apps/integration-client/web-panel-model.mjs +140 -0
- package/apps/integration-client/web-panel-page.jsx +221 -0
- package/apps/integration-client/web-panel-pins-live.jsx +28 -0
- package/apps/integration-client/web-panel.css +139 -0
- package/apps/integration-client/workspace-model.mjs +67 -0
- package/apps/integration-client/workspace-navigation.mjs +56 -0
- package/apps/integration-client/workspace-ui.jsx +173 -0
- package/apps/integration-client/workspace.css +332 -0
- package/modules/bot-runtime/SOURCE.json +176 -50
- package/modules/bot-runtime/index.mjs +169 -13
- package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
- package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
- package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
- package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
- package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
- package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
- package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
- package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
- package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
- package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
- package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
- package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
- package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
- package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
- package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
- package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
- package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
- package/modules/bot-runtime/read-model.mjs +19 -4
- package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
- package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
- package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
- package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
- package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
- package/modules/independent-runtime/index.mjs +152 -62
- package/modules/independent-runtime/remote-tools.mjs +8 -5
- package/modules/openmaus-protocol/NOTICE +10 -0
- package/modules/openmaus-protocol/README.md +15 -0
- package/modules/openmaus-protocol/SOURCE.json +68 -0
- package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
- package/modules/openmaus-protocol/index.mjs +1 -0
- package/modules/openmaus-protocol/transport.mjs +77 -0
- package/modules/openmaus-protocol/vendor/LICENSE +202 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
- package/package.json +20 -6
- package/packages/bot-acp/README.md +156 -0
- package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-acp/licenses/NOTICE.md +27 -0
- package/packages/bot-acp/src/acp-adapter.mjs +795 -0
- package/packages/bot-acp/src/acp-host.mjs +148 -0
- package/packages/bot-acp/src/native-launch.mjs +121 -0
- package/packages/bot-acp/src/permission-policy.mjs +283 -0
- package/packages/bot-acp/src/rpc-codec.mjs +265 -0
- package/packages/bot-acp/src/session-core.mjs +581 -0
- package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
- package/packages/bot-engines/README.md +281 -0
- package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-engines/licenses/NOTICE.md +32 -0
- package/packages/bot-engines/src/claude-engine.mjs +772 -0
- package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
- package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
- package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
- package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
- package/packages/bot-engines/src/claude-protocol.mjs +177 -0
- package/packages/bot-engines/src/driver-catalog.mjs +334 -0
- package/packages/bot-engines/src/host-loop-request.mjs +95 -0
- package/packages/bot-engines/src/host-registrations.mjs +105 -0
- package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
- package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
- package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
- package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
- package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
- package/packages/cli-runtime/src/native-peer.ps1 +51 -3
- package/packages/cli-runtime/src/native-process.mjs +77 -14
- package/packages/model-router-plugin/README.md +3 -3
- package/packages/model-router-plugin/package.json +1 -1
- package/packages/orchestration/src/binding-product.mjs +19 -0
- package/packages/orchestration/src/bot-data-actions.mjs +45 -0
- package/packages/orchestration/src/bot-data-chart.mjs +109 -0
- package/packages/orchestration/src/bot-data-context.mjs +44 -0
- package/packages/orchestration/src/bot-data-engine.mjs +809 -0
- package/packages/orchestration/src/bot-data-io.mjs +186 -0
- package/packages/orchestration/src/bot-data-sql.mjs +195 -0
- package/packages/orchestration/src/bot-data-surface.mjs +144 -0
- package/packages/orchestration/src/bot-data-tools.mjs +68 -0
- package/packages/orchestration/src/bot-data-worker.mjs +228 -0
- package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
- package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
- package/packages/orchestration/src/bot-loop.mjs +152 -19
- package/packages/orchestration/src/bot-notifications.mjs +159 -0
- package/packages/orchestration/src/bot-policy.mjs +43 -4
- package/packages/orchestration/src/bot-profile.mjs +171 -0
- package/packages/orchestration/src/bot-routine.mjs +4 -4
- package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
- package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
- package/packages/orchestration/src/bot-serving.mjs +164 -0
- package/packages/orchestration/src/bot-session-store.mjs +105 -9
- package/packages/orchestration/src/bot-settings.mjs +50 -11
- package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
- package/packages/orchestration/src/bot-thread-host.mjs +187 -0
- package/packages/orchestration/src/bot-threads.mjs +790 -0
- package/packages/orchestration/src/data-product-access.mjs +37 -0
- package/packages/orchestration/src/data-tool-contract.mjs +291 -0
- package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
- package/packages/orchestration/src/desktop-proxy.mjs +311 -0
- package/packages/orchestration/src/desktop-sessions.mjs +43 -9
- package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
- package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
- package/packages/orchestration/src/independent-chat-page.mjs +39 -0
- package/packages/orchestration/src/independent-chat-service.mjs +308 -0
- package/packages/orchestration/src/independent-context-state.mjs +44 -0
- package/packages/orchestration/src/independent-message-wire.mjs +84 -0
- package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
- package/packages/orchestration/src/independent-tool-families.mjs +73 -0
- package/packages/orchestration/src/independent-tool-host.mjs +344 -0
- package/packages/orchestration/src/main-bot-grant.mjs +15 -0
- package/packages/orchestration/src/main-model-routes.mjs +208 -0
- package/packages/orchestration/src/model-accounting.mjs +99 -35
- package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
- package/packages/orchestration/src/model-loop-frame.mjs +179 -0
- package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
- package/packages/orchestration/src/model-request-bounds.mjs +111 -0
- package/packages/orchestration/src/native-activity-contract.mjs +491 -0
- package/packages/orchestration/src/native-activity-service.mjs +102 -0
- package/packages/orchestration/src/native-activity-store.mjs +249 -0
- package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
- package/packages/orchestration/src/native-data-tools.mjs +100 -0
- package/packages/orchestration/src/native-remote-actions.mjs +742 -0
- package/packages/orchestration/src/native-team-tools.mjs +73 -0
- package/packages/orchestration/src/pairing-invite.mjs +37 -0
- package/packages/orchestration/src/product-composition.mjs +1559 -1001
- package/packages/orchestration/src/remote-machines.mjs +82 -14
- package/packages/orchestration/src/routine-execution.mjs +5 -3
- package/packages/orchestration/src/routine-session.mjs +19 -13
- package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
- package/packages/orchestration/src/task-journal.mjs +29 -0
- package/packages/orchestration/src/task-product.mjs +17 -0
- package/packages/orchestration/src/team-assignment-store.mjs +120 -0
- package/packages/orchestration/src/team-bridge.mjs +282 -0
- package/packages/orchestration/src/team-scheduler.mjs +83 -0
- package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
- package/packages/orchestration/src/team-tool-contract.mjs +284 -0
- package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
- package/packages/orchestration/src/web-panel-actions.mjs +103 -0
- package/packages/orchestration/src/web-panel-framing.mjs +227 -0
- package/packages/orchestration/src/web-panel-store.mjs +206 -0
- package/remote-computer-agent/README.md +39 -29
- package/remote-computer-agent/build.mjs +3 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
- package/remote-computer-agent/dist/cli.mjs +1688 -168
- package/remote-computer-agent/dist/cli.mjs.map +4 -4
- package/remote-computer-agent/dist/index.mjs +1579 -122
- package/remote-computer-agent/dist/index.mjs.map +4 -4
- package/remote-computer-agent/dist/native-peer.ps1 +51 -3
- package/remote-computer-agent/package-lock.json +308 -2
- package/remote-computer-agent/package.json +2 -1
- package/remote-computer-agent/pair-device.ps1 +20 -0
- package/remote-computer-agent/setup.ps1 +38 -16
- package/remote-computer-agent/src/cli.ts +24 -3
- package/remote-computer-agent/src/desktop-session.ts +11 -3
- package/remote-computer-agent/src/e2e-server.ts +81 -0
- package/remote-computer-agent/src/pairing-page.ts +35 -0
- package/remote-computer-agent/src/protocol.ts +25 -25
- package/remote-computer-agent/src/secure-pairing.ts +105 -0
- package/remote-computer-agent/src/store.ts +20 -5
- package/remote-computer-agent/src/transport.ts +39 -15
|
@@ -0,0 +1,314 @@
|
|
|
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
|
+
// The DeepSeek account's sign-in grant (dsh-deepseek-account-platform).
|
|
9
|
+
const ACCOUNT_RECORD = 'deepseek-account-platform/default'
|
|
10
|
+
|
|
11
|
+
export function install(rt) {
|
|
12
|
+
const { ctx, state } = rt
|
|
13
|
+
|
|
14
|
+
let modelsSynced = null
|
|
15
|
+
let catalogCache = { at: 0, value: [] }
|
|
16
|
+
// Routes DSH serves that cannot answer a request at all — a hosted loop engine
|
|
17
|
+
// publishes one so its Sessions can be prompted. Once one refuses with
|
|
18
|
+
// HOSTED_ENGINE_ROUTE it is remembered with the models its provider served then,
|
|
19
|
+
// and nothing chooses it again until that provider serves different models.
|
|
20
|
+
const refused = new Map()
|
|
21
|
+
const answerable = entries => (refused.size === 0 ? entries : entries.filter(entry => !refused.has(entry.ref)))
|
|
22
|
+
// The models a ref's provider serves, as a string to compare; undefined when it
|
|
23
|
+
// cannot be listed, e.g. the provider is not registered.
|
|
24
|
+
async function servedModels(ref) {
|
|
25
|
+
try { return JSON.stringify(await ctx.llm.listModels(splitRef(ref).provider)) } catch { return undefined }
|
|
26
|
+
}
|
|
27
|
+
// An adapter update may have replaced a refused placeholder with a real route, which
|
|
28
|
+
// serves other models; the same models again are the same route, still refused.
|
|
29
|
+
async function forgetChangedRefusals() {
|
|
30
|
+
for (const [ref, served] of refused) {
|
|
31
|
+
const now = await servedModels(ref)
|
|
32
|
+
if (now !== undefined && now !== served) refused.delete(ref)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// A model's thinking levels, resolved once and kept for a short while: they change
|
|
36
|
+
// only with the provider, which also drops the catalog cache.
|
|
37
|
+
const reasoningCache = new Map()
|
|
38
|
+
const dropCatalog = () => { catalogCache = { at: 0, value: [] } }
|
|
39
|
+
// A key set or a sign-in may be the user's fix for a Bot's failed turn (turns.js); a
|
|
40
|
+
// key removed or a sign-out is not. A provider that registers or changes fixes
|
|
41
|
+
// nothing by itself: providers register late at every start.
|
|
42
|
+
ctx.on?.('llm/adapters-updated', () => { dropCatalog(); reasoningCache.clear(); return forgetChangedRefusals() })
|
|
43
|
+
ctx.on?.('credentials/reference-updated', async (ref) => {
|
|
44
|
+
dropCatalog()
|
|
45
|
+
if (await inPlace(credentials => credentials.describe(ref))) rt.unblockBots?.({ ref })
|
|
46
|
+
})
|
|
47
|
+
// A sign-in writes the account's record. A sign-out removes it, and so does a token
|
|
48
|
+
// the provider refused, while the turn that failed on it still runs. Other plugins'
|
|
49
|
+
// records (an OAuth token refresh) say nothing about the account.
|
|
50
|
+
ctx.on?.('credentials/record-updated', async (key) => {
|
|
51
|
+
dropCatalog()
|
|
52
|
+
if (String(key) !== ACCOUNT_RECORD) return
|
|
53
|
+
if (await inPlace(credentials => credentials.describeRecord(key))) rt.unblockBots?.({ signedIn: true })
|
|
54
|
+
})
|
|
55
|
+
ctx.on?.('settings/document-updated', dropCatalog)
|
|
56
|
+
|
|
57
|
+
// Whether a credential is there after a change. One that cannot be read counts as
|
|
58
|
+
// there, so a fix is not missed.
|
|
59
|
+
async function inPlace(describe) {
|
|
60
|
+
const credentials = ctx.get?.('credentials')
|
|
61
|
+
if (credentials === undefined) return true
|
|
62
|
+
try { return (await describe(credentials))?.configured !== false } catch { return true }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The thinking levels a model serves: `{efforts: [{id, name, description?}],
|
|
66
|
+
// defaultEffort?}`, or undefined when it has none or the provider cannot say.
|
|
67
|
+
async function reasoningOf(ref) {
|
|
68
|
+
const cached = reasoningCache.get(ref)
|
|
69
|
+
if (cached !== undefined && Date.now() - cached.at < CATALOG_TTL_MS) return cached.value
|
|
70
|
+
const route = splitRef(ref)
|
|
71
|
+
const info = route === undefined ? undefined : await ctx.llm.resolveModelInfo?.(route.provider, route.model).catch(() => undefined)
|
|
72
|
+
const value = info?.reasoning === undefined ? undefined : {
|
|
73
|
+
efforts: (info.reasoning.efforts ?? []).map(effort => ({ id: effort.id, name: effort.name ?? effort.id, ...(effort.description ? { description: effort.description } : {}) })),
|
|
74
|
+
...(info.reasoning.defaultEffort === undefined ? {} : { defaultEffort: info.reasoning.defaultEffort }),
|
|
75
|
+
}
|
|
76
|
+
reasoningCache.set(ref, { at: Date.now(), value })
|
|
77
|
+
return value
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// A provider's model list can change in DSH settings without any event, so a picker
|
|
81
|
+
// that is about to be used asks for a `fresh` one. Entries for models a Bot uses
|
|
82
|
+
// also carry their `reasoning`, so the picker can offer thinking levels.
|
|
83
|
+
async function catalog({ fresh = false } = {}) {
|
|
84
|
+
if (!fresh && Date.now() - catalogCache.at < CATALOG_TTL_MS && catalogCache.value.length > 0) return answerable(catalogCache.value)
|
|
85
|
+
const entries = []
|
|
86
|
+
for (const provider of ctx.llm.listProviders()) {
|
|
87
|
+
let models = []
|
|
88
|
+
try { models = await ctx.llm.listModels(provider.id) } catch { continue }
|
|
89
|
+
for (const model of models) {
|
|
90
|
+
entries.push({
|
|
91
|
+
ref: `${provider.id}/${model.id}`,
|
|
92
|
+
provider: provider.id,
|
|
93
|
+
providerName: provider.name ?? provider.id,
|
|
94
|
+
id: model.id,
|
|
95
|
+
name: model.name ?? model.id,
|
|
96
|
+
...(model.inputModalities ? { input: [...model.inputModalities] } : {}),
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// DSH serves a provider's models whether or not its key is set; without one, every
|
|
101
|
+
// turn on them fails.
|
|
102
|
+
const missing = await rt.missingKeys?.([...new Set(entries.map(entry => entry.provider))]) ?? new Set()
|
|
103
|
+
for (const entry of entries) if (missing.has(entry.provider)) entry.needsKey = true
|
|
104
|
+
// A key can arrive without an event (an environment variable at the next start), so
|
|
105
|
+
// a provider listed with its key again lifts what its missing key blocked.
|
|
106
|
+
rt.unblockBots?.({ keyed: new Set(entries.filter(entry => !entry.needsKey).map(entry => entry.provider)) })
|
|
107
|
+
const used = new Set(Object.values(state.bots ?? {}).flatMap(bot => [bot.model, bot.appliedModel].filter(Boolean)))
|
|
108
|
+
await Promise.all(entries.filter(entry => used.has(entry.ref)).map(async (entry) => {
|
|
109
|
+
const reasoning = await reasoningOf(entry.ref)
|
|
110
|
+
if (reasoning !== undefined) entry.reasoning = reasoning
|
|
111
|
+
}))
|
|
112
|
+
catalogCache = { at: Date.now(), value: entries }
|
|
113
|
+
return answerable(entries)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The profile default model of DSH as set, served or not, and able to answer.
|
|
117
|
+
const defaultRef = () => {
|
|
118
|
+
const selection = ctx.get?.('agentDefaultModel')?.currentSelection?.()
|
|
119
|
+
if (!selection?.provider || !selection?.model) return undefined
|
|
120
|
+
const ref = `${selection.provider}/${selection.model}`
|
|
121
|
+
return refused.has(ref) ? undefined : ref
|
|
122
|
+
}
|
|
123
|
+
// A model whose provider has no key fails every turn, so no Bot is given one unasked.
|
|
124
|
+
const usableOf = entries => entries.filter(entry => entry.needsKey !== true)
|
|
125
|
+
// By the catalog as last listed, without waiting for a fresh one.
|
|
126
|
+
const isUsable = ref => usableOf(catalogCache.value).some(entry => entry.ref === ref)
|
|
127
|
+
// The profile default model of DSH, when it is served and usable; else the first
|
|
128
|
+
// usable model; else none.
|
|
129
|
+
function pickDefault(entries) {
|
|
130
|
+
const usable = usableOf(entries)
|
|
131
|
+
const ref = defaultRef()
|
|
132
|
+
// Neither a refused route nor one that fails for a missing key is picked.
|
|
133
|
+
const list = answerable(usable)
|
|
134
|
+
return list.find(entry => entry.ref === ref)?.ref ?? list[0]?.ref
|
|
135
|
+
}
|
|
136
|
+
const defaultModel = async () => pickDefault(await catalog())
|
|
137
|
+
|
|
138
|
+
// The model a create that names none starts on: the team's pick while the catalog
|
|
139
|
+
// still serves it, else the DSH default. A create fixes the ref then, so a later
|
|
140
|
+
// change of either default leaves the Bot alone.
|
|
141
|
+
const defaultBotModel = async () => {
|
|
142
|
+
const entries = await catalog()
|
|
143
|
+
const preferred = state.prefs.defaultBotModel
|
|
144
|
+
return entries.some(entry => entry.ref === preferred) ? preferred : pickDefault(entries)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// A full `provider/model` ref, or a model id or name that only one provider serves.
|
|
148
|
+
async function resolveModel(text) {
|
|
149
|
+
const entries = await catalog({ fresh: true })
|
|
150
|
+
const wanted = String(text ?? '').trim()
|
|
151
|
+
const exact = entries.find(entry => entry.ref === wanted)
|
|
152
|
+
if (exact) return exact.ref
|
|
153
|
+
const key = normalize(wanted)
|
|
154
|
+
const named = entries.filter(entry => normalize(entry.id) === key || normalize(entry.name) === key)
|
|
155
|
+
if (named.length === 1) return named[0].ref
|
|
156
|
+
const known = entries.map(entry => entry.ref).join(', ') || 'none'
|
|
157
|
+
throw new Error(named.length > 1
|
|
158
|
+
? `Model ${wanted} is served by more than one provider; use one of ${named.map(entry => entry.ref).join(', ')}`
|
|
159
|
+
: `Model ${wanted} is not available. Available models: ${known}`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// A Bot with no model yet waits for the user to pick one and changes nothing. A
|
|
163
|
+
// model that is no longer served falls back to the last one it ran on, then the
|
|
164
|
+
// default; a fallback is never one that needs a key.
|
|
165
|
+
async function desiredModel(bot) {
|
|
166
|
+
if (!bot.model) return undefined
|
|
167
|
+
const entries = await catalog()
|
|
168
|
+
if (entries.some(entry => entry.ref === bot.model)) return bot.model
|
|
169
|
+
return usableOf(entries).some(entry => entry.ref === bot.appliedModel) ? bot.appliedModel : pickDefault(entries)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// The thinking level a Bot asked for, when the model it runs on serves it; else the
|
|
173
|
+
// model's own default applies. A level the model does not have is ignored, never
|
|
174
|
+
// sent: an explicit effort a provider does not know is a hard refusal there.
|
|
175
|
+
async function desiredEffort(bot, ref) {
|
|
176
|
+
if (bot.effort === undefined || ref === undefined) return undefined
|
|
177
|
+
const reasoning = await reasoningOf(ref)
|
|
178
|
+
return reasoning?.efforts?.some(effort => effort.id === bot.effort) ? bot.effort : undefined
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// A thinking level id, or a name only one level of the model has, like resolveModel.
|
|
182
|
+
async function resolveEffort(ref, text) {
|
|
183
|
+
const wanted = String(text ?? '').trim()
|
|
184
|
+
if (wanted === '') return undefined
|
|
185
|
+
const reasoning = await reasoningOf(ref)
|
|
186
|
+
const efforts = reasoning?.efforts ?? []
|
|
187
|
+
if (efforts.length === 0) throw new Error(`Model ${ref} has no thinking levels`)
|
|
188
|
+
const key = normalize(wanted)
|
|
189
|
+
const named = efforts.filter(effort => normalize(effort.id) === key || normalize(effort.name) === key)
|
|
190
|
+
if (named.length === 1) return named[0].id
|
|
191
|
+
throw new Error(`Thinking level ${wanted} is not available for ${ref}. Available: ${efforts.map(effort => effort.name).join(', ')}`)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The profile default model's thinking level, when the model a Bot starts on
|
|
195
|
+
// serves it. A level the model does not know is ignored, like an inherited
|
|
196
|
+
// `bot.effort` — the model's own default applies instead.
|
|
197
|
+
async function defaultEffort(ref) {
|
|
198
|
+
const wanted = ctx.get?.('agentDefaultModel')?.currentSelection?.()?.reasoningEffort
|
|
199
|
+
if (ref === undefined || wanted === undefined) return undefined
|
|
200
|
+
return resolveEffort(ref, wanted).catch(() => undefined)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// DSH's selectModel also saves the selection as the profile default model, in the
|
|
204
|
+
// background. A Bot's model is its own, so the user's default is written back after
|
|
205
|
+
// it. Selections run one at a time and wait for the write-back: profile writes land in
|
|
206
|
+
// submission order, so the next one reads the user's default, not a Bot's.
|
|
207
|
+
let selecting = Promise.resolve()
|
|
208
|
+
// Recheck group Sessions after every restart: Bot-level applied fields cannot
|
|
209
|
+
// prove that an older group Session received the member's thinking level.
|
|
210
|
+
const sessionSelections = new Map()
|
|
211
|
+
function selectSessionModel(sessionId, { provider, model, reasoningEffort }) {
|
|
212
|
+
const run = selecting.then(async () => {
|
|
213
|
+
const defaults = ctx.get?.('agentDefaultModel')
|
|
214
|
+
const before = defaults?.currentSelection?.()
|
|
215
|
+
const result = await ctx.sessionController.selectModel({
|
|
216
|
+
sessionId, provider, model,
|
|
217
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
218
|
+
})
|
|
219
|
+
const saved = result?.selected ?? { provider, model, ...(reasoningEffort === undefined ? {} : { reasoningEffort }) }
|
|
220
|
+
if (before?.provider && before?.model && !sameSelection(before, saved)) {
|
|
221
|
+
await defaults.saveSelection(before).catch(error => rt.warn('ds-bot: the default model was not restored: %s', error))
|
|
222
|
+
}
|
|
223
|
+
sessionSelections.set(sessionId, { provider, model, reasoningEffort })
|
|
224
|
+
return result
|
|
225
|
+
})
|
|
226
|
+
selecting = run.catch(() => {})
|
|
227
|
+
return run
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const sameSelection = (a, b) => a.provider === b.provider && a.model === b.model && a.reasoningEffort === b.reasoningEffort
|
|
231
|
+
|
|
232
|
+
// `force` re-selects even when the record says it is applied: the composer's own
|
|
233
|
+
// model picker can move the Session without telling this plugin.
|
|
234
|
+
async function applyModel(bot, { force = false } = {}) {
|
|
235
|
+
const ref = await desiredModel(bot)
|
|
236
|
+
if (ref === undefined) return ref
|
|
237
|
+
const effort = await desiredEffort(bot, ref)
|
|
238
|
+
const ownChanged = force || bot.appliedModel !== ref || (bot.appliedEffort ?? undefined) !== effort
|
|
239
|
+
const route = { ...splitRef(ref), ...(effort === undefined ? {} : { reasoningEffort: effort }) }
|
|
240
|
+
if (ownChanged) await selectSessionModel(rt.chatOf(bot.id), route)
|
|
241
|
+
for (const sessionId of rt.groupSessionsOf(bot.id)) {
|
|
242
|
+
const applied = sessionSelections.get(sessionId)
|
|
243
|
+
if (!force && applied !== undefined && sameSelection(applied, route)) continue
|
|
244
|
+
await selectSessionModel(sessionId, route)
|
|
245
|
+
.catch(error => rt.warn('ds-bot: model for %s in a group not applied: %s', bot.name, error))
|
|
246
|
+
}
|
|
247
|
+
if (!ownChanged) return ref
|
|
248
|
+
bot.appliedModel = ref
|
|
249
|
+
if (effort === undefined) delete bot.appliedEffort
|
|
250
|
+
else bot.appliedEffort = effort
|
|
251
|
+
await rt.save()
|
|
252
|
+
return ref
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// A provider may register after the plugin starts; a greeting waits for its Bot's
|
|
256
|
+
// model instead of failing with "no adapter registered".
|
|
257
|
+
async function waitForModel(bot, ms = ROUTE_WAIT_MS) {
|
|
258
|
+
const deadline = Date.now() + ms
|
|
259
|
+
for (;;) {
|
|
260
|
+
if (!bot.model) return undefined
|
|
261
|
+
catalogCache = { at: 0, value: [] }
|
|
262
|
+
const served = new Set((await catalog()).map(entry => entry.ref))
|
|
263
|
+
if (served.has(bot.model) || Date.now() >= deadline) return desiredModel(bot)
|
|
264
|
+
await new Promise((resolve) => {
|
|
265
|
+
const timer = setTimeout(done, Math.min(2000, deadline - Date.now()))
|
|
266
|
+
const off = ctx.on?.('llm/adapters-updated', done)
|
|
267
|
+
function done() { clearTimeout(timer); off?.(); resolve() }
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Sequential, and again whenever the answerable models change (a refused route is
|
|
273
|
+
// not one of them): a provider that registers late still gets its Bots' models selected.
|
|
274
|
+
let syncedKey
|
|
275
|
+
const syncModels = () => {
|
|
276
|
+
const key = answerable(catalogCache.value).map(entry => entry.ref).join(',')
|
|
277
|
+
if (key !== syncedKey) { syncedKey = key; modelsSynced = modelsSynced?.then(syncAll, syncAll) ?? syncAll() }
|
|
278
|
+
return modelsSynced
|
|
279
|
+
}
|
|
280
|
+
async function syncAll() {
|
|
281
|
+
for (const bot of Object.values(state.bots)) {
|
|
282
|
+
await applyModel(bot).catch(error => rt.warn('ds-bot: model for %s not applied: %s', bot.name, error))
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// A route that refused a request for not being a model endpoint: it stays out of
|
|
287
|
+
// the catalog from now on, the Bots pinned to it move to what they would be given
|
|
288
|
+
// today, and each of them re-selects even when its record looks applied (`force`):
|
|
289
|
+
// its Session may still be on the route, or on another Bot's route in a group.
|
|
290
|
+
async function routeRefused(ref, bot) {
|
|
291
|
+
const served = await servedModels(ref)
|
|
292
|
+
if (!refused.has(ref)) {
|
|
293
|
+
refused.set(ref, served)
|
|
294
|
+
rt.warn('ds-bot: %s is not a model endpoint, so it is not chosen again', ref)
|
|
295
|
+
const entries = await catalog()
|
|
296
|
+
const pinned = Object.values(state.bots).filter(other => other.model === ref || other.appliedModel === ref)
|
|
297
|
+
for (const other of pinned) {
|
|
298
|
+
if (other.model === ref) other.model = pickDefault(entries)
|
|
299
|
+
if (other.appliedModel === ref) delete other.appliedModel
|
|
300
|
+
}
|
|
301
|
+
await rt.save()
|
|
302
|
+
for (const other of pinned) {
|
|
303
|
+
if (other !== bot) await applyModel(other, { force: true }).catch(error => rt.warn('ds-bot: model for %s not applied: %s', other.name, error))
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (bot !== undefined) await applyModel(bot, { force: true })
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
Object.assign(rt, {
|
|
310
|
+
catalog, cachedCatalog: () => answerable(catalogCache.value), cachedDefault: () => pickDefault(catalogCache.value),
|
|
311
|
+
defaultRef, defaultModel, defaultBotModel, defaultEffort, isUsable, resolveModel, desiredModel, reasoningOf, resolveEffort, desiredEffort,
|
|
312
|
+
selectSessionModel, applyModel, waitForModel, syncModels, routeRefused, dropCatalog,
|
|
313
|
+
})
|
|
314
|
+
}
|