dsh-monkeyai-sessions 0.1.1 → 0.1.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/README.md +1 -1
- package/README.zh.md +1 -1
- package/package.json +1 -1
- package/src/host/monkeyai-remote.js +57 -6
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ From a checkout:
|
|
|
61
61
|
npm install
|
|
62
62
|
npm run build
|
|
63
63
|
npm pack
|
|
64
|
-
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.
|
|
64
|
+
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.2.tgz
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Verified against a real DSH `0.2.2-rc.1` web profile and a live MonkeyAI runtime: authorization, full history loading, prompt delivery, model switching, and turn cancellation all work end to end.
|
package/README.zh.md
CHANGED
|
@@ -28,7 +28,7 @@ https://artenx.github.io/dsh-monkeyai-sessions/index.json
|
|
|
28
28
|
npm install
|
|
29
29
|
npm run build
|
|
30
30
|
npm pack
|
|
31
|
-
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.
|
|
31
|
+
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.2.tgz
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
已在真实的 DSH `0.2.2-rc.1` web profile 与在线 MonkeyAI runtime 上端到端验证:授权、完整历史加载、指令下发、模型切换、回合取消均正常工作。
|
package/package.json
CHANGED
|
@@ -390,6 +390,45 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
|
|
|
390
390
|
return this.exchangeAuthorization(auth, callbackUrl)
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
// The runtime requires an expectedRevision on every configuration write and
|
|
394
|
+
// rejects a stale one with configuration_conflict. The MonkeyAI client (or
|
|
395
|
+
// another panel) can advance the revision at any time, so read the current
|
|
396
|
+
// value from the conflict payload — or from the HTTP session state when we
|
|
397
|
+
// have none — and retry instead of failing the switch.
|
|
398
|
+
async setSessionModel({ acp, sessionId, model, httpBase, ticket, revision, signal, onRevision }) {
|
|
399
|
+
let expected = revision
|
|
400
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
401
|
+
if (!Number.isSafeInteger(expected)) {
|
|
402
|
+
expected = await readSessionConfigurationRevision({ httpBase, ticket, sessionId, signal })
|
|
403
|
+
}
|
|
404
|
+
if (!Number.isSafeInteger(expected)) throw new Error('无法读取会话配置版本,暂时不能切换模型')
|
|
405
|
+
try {
|
|
406
|
+
const result = await acp.request('_monkeyai/session/model/set', {
|
|
407
|
+
sessionId,
|
|
408
|
+
model,
|
|
409
|
+
_meta: { expectedRevision: expected },
|
|
410
|
+
}, { signal, timeoutMs: 120_000 })
|
|
411
|
+
const next = result?._meta?.configurationRevision
|
|
412
|
+
if (Number.isSafeInteger(next)) onRevision(next)
|
|
413
|
+
return typeof result?.model === 'string' ? result.model : model
|
|
414
|
+
} catch (error) {
|
|
415
|
+
if (signal?.aborted) throw error
|
|
416
|
+
const current = error?.data?.configurationRevision
|
|
417
|
+
if (error?.data?.reason === 'configuration_conflict' && Number.isSafeInteger(current) && current !== expected) {
|
|
418
|
+
expected = current
|
|
419
|
+
onRevision(current)
|
|
420
|
+
continue
|
|
421
|
+
}
|
|
422
|
+
if (error?.data?.reason === 'configuration_version_required') {
|
|
423
|
+
expected = undefined
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
throw error
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
throw new Error('会话配置持续被其他客户端修改,请重试切换模型')
|
|
430
|
+
}
|
|
431
|
+
|
|
393
432
|
async exchangeAuthorization(auth, callbackUrl) {
|
|
394
433
|
const callback = parseAuthorizationCallback(callbackUrl, auth)
|
|
395
434
|
const tokens = await requestJson(auth.serverOrigin, '/oauth/token', {
|
|
@@ -977,14 +1016,16 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
|
|
|
977
1016
|
|
|
978
1017
|
if (command.type === 'switch_model') {
|
|
979
1018
|
try {
|
|
980
|
-
const
|
|
1019
|
+
const model = await this.setSessionModel({
|
|
1020
|
+
acp: currentAcp,
|
|
981
1021
|
sessionId: selectedSession,
|
|
982
1022
|
model: command.model,
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1023
|
+
httpBase: ticket.httpBase,
|
|
1024
|
+
ticket: ticketValue,
|
|
1025
|
+
revision: httpConfigurationRevision,
|
|
1026
|
+
signal,
|
|
1027
|
+
onRevision: value => { httpConfigurationRevision = value },
|
|
1028
|
+
})
|
|
988
1029
|
currentLoadedModel = model
|
|
989
1030
|
emit({ type: 'model_switched', sessionId: selectedSession, data: { model } })
|
|
990
1031
|
} catch (switchError) {
|
|
@@ -1223,6 +1264,16 @@ async function readHistoryBodyText(response, maxBytes = HISTORY_PAGE_MAX_BYTES)
|
|
|
1223
1264
|
return new TextDecoder().decode(joined)
|
|
1224
1265
|
}
|
|
1225
1266
|
|
|
1267
|
+
// Reads the session's current configuration revision over the HTTP read
|
|
1268
|
+
// channel. Configuration writes must carry it, and the value changes whenever
|
|
1269
|
+
// any client edits the session.
|
|
1270
|
+
async function readSessionConfigurationRevision({ httpBase, ticket, sessionId, signal }) {
|
|
1271
|
+
const timeout = AbortSignal.any([AbortSignal.timeout(20_000), ...(signal ? [signal] : [])])
|
|
1272
|
+
const result = await readSessionHistoryPage({ httpBase, ticket, sessionId, limit: 1, signal: timeout })
|
|
1273
|
+
const revision = result.session?.configurationRevision
|
|
1274
|
+
return Number.isSafeInteger(revision) ? revision : undefined
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1226
1277
|
async function readSessionHistoryPage({ httpBase, ticket, sessionId, cursor, direction, limit = 50, signal }) {
|
|
1227
1278
|
const params = { sessionId, limit, ...(cursor ? { cursor, direction } : {}) }
|
|
1228
1279
|
const response = await fetch(`${httpBase.endsWith('/') ? httpBase : `${httpBase}/`}read/session`, {
|