@getpaseo/server 0.1.101 → 0.1.102
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/.env.example +8 -0
- package/dist/scripts/supervisor.js +26 -8
- package/dist/server/server/agent/activity-curator.d.ts +17 -0
- package/dist/server/server/agent/activity-curator.js +101 -24
- package/dist/server/server/agent/agent-manager.js +5 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +7 -2
- package/dist/server/server/agent/provider-registry.js +12 -11
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +8 -1
- package/dist/server/server/agent/provider-snapshot-manager.js +78 -33
- package/dist/server/server/agent/providers/acp-agent.d.ts +13 -0
- package/dist/server/server/agent/providers/acp-agent.js +37 -1
- package/dist/server/server/agent/providers/claude/agent.js +51 -14
- package/dist/server/server/agent/providers/claude/query.d.ts +3 -0
- package/dist/server/server/agent/providers/claude/query.js +4 -2
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +2 -1
- package/dist/server/server/agent/providers/generic-acp-agent.js +2 -1
- package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
- package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +8 -0
- package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode/paths.js +7 -0
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode/server-manager.js +34 -5
- package/dist/server/server/agent/providers/opencode-agent.d.ts +4 -0
- package/dist/server/server/agent/providers/opencode-agent.js +14 -2
- package/dist/server/server/agent/providers/pi/agent.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/agent.js +9 -3
- package/dist/server/server/agent/providers/provider-image-output.js +11 -6
- package/dist/server/server/agent/tools/paseo-tools.d.ts +1 -1
- package/dist/server/server/agent/tools/paseo-tools.js +0 -2
- package/dist/server/server/bootstrap.d.ts +7 -1
- package/dist/server/server/bootstrap.js +18 -0
- package/dist/server/server/config.d.ts +2 -0
- package/dist/server/server/config.js +57 -1
- package/dist/server/server/daemon-worker.js +19 -7
- package/dist/server/server/lifecycle-reasons.d.ts +4 -0
- package/dist/server/server/lifecycle-reasons.js +6 -0
- package/dist/server/server/persisted-config.d.ts +13 -2
- package/dist/server/server/persisted-config.js +30 -11
- package/dist/server/server/process-diagnostics.d.ts +17 -0
- package/dist/server/server/process-diagnostics.js +22 -0
- package/dist/server/server/relay-transport.js +1 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +3 -1
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
- package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
- package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +12 -0
- package/dist/server/server/session/daemon/daemon-session.js +12 -0
- package/dist/server/server/session/daemon/diagnostics.js +10 -0
- package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
- package/dist/server/server/session/daemon/install-origin.js +64 -0
- package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
- package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
- package/dist/server/server/session/provider/provider-catalog-session.js +8 -4
- package/dist/server/server/session.d.ts +5 -3
- package/dist/server/server/session.js +74 -32
- package/dist/server/server/speech/providers/openai/config.d.ts +0 -2
- package/dist/server/server/speech/providers/openai/config.js +71 -34
- package/dist/server/server/speech/providers/openai/runtime.js +4 -4
- package/dist/server/server/web-ui.d.ts +10 -0
- package/dist/server/server/web-ui.js +205 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +3 -0
- package/dist/server/server/websocket-server.d.ts +3 -0
- package/dist/server/server/websocket-server.js +190 -32
- package/dist/server/services/quota-fetcher/manifest.js +5 -0
- package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
- package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +2 -2
- package/dist/server/utils/checkout-git.js +156 -3
- package/dist/server/utils/directory-suggestions.js +1 -4
- package/dist/server/utils/path.d.ts +2 -0
- package/dist/server/utils/path.js +13 -0
- package/dist/server/utils/worktree.d.ts +1 -0
- package/dist/server/utils/worktree.js +92 -11
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
- package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-5fa9347afb28c7d61bbf73684f3bdafa.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-5fa9347afb28c7d61bbf73684f3bdafa.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-5fa9347afb28c7d61bbf73684f3bdafa.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-35159ed50cf218b741c5dab5a6f4a889.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-35159ed50cf218b741c5dab5a6f4a889.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-35159ed50cf218b741c5dab5a6f4a889.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-cf63ba3f4de4164d2d9c3738e1b791b2.js +16157 -0
- package/dist/server/web-ui/_expo/static/js/web/index-cf63ba3f4de4164d2d9c3738e1b791b2.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-cf63ba3f4de4164d2d9c3738e1b791b2.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-6a6cfdd9f06d4fd2b05adc1a8771c58c.js +1 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-6a6cfdd9f06d4fd2b05adc1a8771c58c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-6a6cfdd9f06d4fd2b05adc1a8771c58c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b31888f9a7de4f0c52d6a58fcddbcb20.js +3 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b31888f9a7de4f0c52d6a58fcddbcb20.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-b31888f9a7de4f0c52d6a58fcddbcb20.js.gz +0 -0
- package/dist/server/web-ui/apple-touch-icon.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
- package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
- package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
- package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
- package/dist/server/web-ui/favicon.ico +0 -0
- package/dist/server/web-ui/index.html +90 -0
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/dist/server/web-ui/manifest.json +27 -0
- package/dist/server/web-ui/manifest.json.br +0 -0
- package/dist/server/web-ui/manifest.json.gz +0 -0
- package/dist/server/web-ui/metadata.json +1 -0
- package/dist/server/web-ui/metadata.json.br +1 -0
- package/dist/server/web-ui/metadata.json.gz +0 -0
- package/dist/server/web-ui/pwa-icon-192.png +0 -0
- package/dist/server/web-ui/pwa-icon-512.png +0 -0
- package/dist/server/web-ui/robots.txt +2 -0
- package/dist/src/server/persisted-config.js +30 -11
- package/package.json +7 -7
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WebSocketServer } from "ws";
|
|
2
2
|
import { basename, join } from "path";
|
|
3
3
|
import { hostname as getHostname } from "node:os";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
5
6
|
import { WSInboundMessageSchema, wrapSessionMessage, } from "./messages.js";
|
|
6
7
|
import { asUint8Array, decodeBinaryFrame } from "@getpaseo/protocol/binary-frames/index";
|
|
@@ -15,6 +16,8 @@ import { createGitHubService } from "../services/github-service.js";
|
|
|
15
16
|
import { extractWsBearerProtocol, extractWsBearerToken, isBearerTokenValid, } from "./auth.js";
|
|
16
17
|
import { WebSocketRuntimeMetricsWindow, } from "./websocket/runtime-metrics.js";
|
|
17
18
|
import { ProviderUsageService } from "../services/quota-fetcher/service.js";
|
|
19
|
+
import { getProcessMemoryDiagnostics, getProcessUptimeSeconds } from "./process-diagnostics.js";
|
|
20
|
+
import { CLIENT_SHUTDOWN_RPC_REASON, normalizeClientRestartRpcReason, } from "./lifecycle-reasons.js";
|
|
18
21
|
const WS_CLOSE_DAEMON_AUTH_FAILED = 4401;
|
|
19
22
|
function resolveTerminalAttentionReason(input) {
|
|
20
23
|
if (input.attentionReason === "finished")
|
|
@@ -222,6 +225,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
222
225
|
constructor(server, logger, serverId, agentManager, agentStorage, downloadTokenStore, paseoHome, daemonConfigStore, mcpBaseUrl, wsConfig, auth, speech, terminalManager, dictation, daemonVersion, onLifecycleIntent, projectRegistry, workspaceRegistry, chatService, loopService, scheduleService, checkoutDiffManager, serviceProxy, scriptRuntimeStore, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, workspaceGitService, github, pushNotificationSender, providerSnapshotManager, daemonRuntimeConfig, serviceProxyPublicBaseUrl) {
|
|
223
226
|
this.pendingConnections = new Map();
|
|
224
227
|
this.sessions = new Map();
|
|
228
|
+
this.socketIdentities = new Map();
|
|
225
229
|
this.externalSessionsByKey = new Map();
|
|
226
230
|
this.voiceSpeakHandlers = new Map();
|
|
227
231
|
this.voiceCallerContexts = new Map();
|
|
@@ -391,9 +395,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
391
395
|
callback(false, 403, "Host not allowed");
|
|
392
396
|
return;
|
|
393
397
|
}
|
|
394
|
-
const sameOrigin =
|
|
395
|
-
!!requestHost &&
|
|
396
|
-
(origin === `http://${requestHost}` || origin === `https://${requestHost}`);
|
|
398
|
+
const sameOrigin = isWebSocketSameOrigin(origin, requestHost);
|
|
397
399
|
if (!origin || allowedOrigins.has("*") || allowedOrigins.has(origin) || sameOrigin) {
|
|
398
400
|
callback(true);
|
|
399
401
|
}
|
|
@@ -508,6 +510,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
508
510
|
this.workspaceGitService.dispose();
|
|
509
511
|
this.pendingConnections.clear();
|
|
510
512
|
this.sessions.clear();
|
|
513
|
+
this.socketIdentities.clear();
|
|
511
514
|
this.externalSessionsByKey.clear();
|
|
512
515
|
this.wss.close();
|
|
513
516
|
}
|
|
@@ -550,25 +553,13 @@ export class VoiceAssistantWebSocketServer {
|
|
|
550
553
|
}
|
|
551
554
|
async attachSocket(ws, request, metadata) {
|
|
552
555
|
const requestMetadata = extractSocketRequestMetadata(request);
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (requestMetadata.host) {
|
|
557
|
-
connectionLoggerFields.host = requestMetadata.host;
|
|
558
|
-
}
|
|
559
|
-
if (requestMetadata.origin) {
|
|
560
|
-
connectionLoggerFields.origin = requestMetadata.origin;
|
|
561
|
-
}
|
|
562
|
-
if (requestMetadata.userAgent) {
|
|
563
|
-
connectionLoggerFields.userAgent = requestMetadata.userAgent;
|
|
564
|
-
}
|
|
565
|
-
if (requestMetadata.remoteAddress) {
|
|
566
|
-
connectionLoggerFields.remoteAddress = requestMetadata.remoteAddress;
|
|
567
|
-
}
|
|
568
|
-
const connectionLogger = this.logger.child(connectionLoggerFields);
|
|
556
|
+
const identity = createWebSocketConnectionIdentity(requestMetadata, metadata);
|
|
557
|
+
this.socketIdentities.set(ws, identity);
|
|
558
|
+
const connectionLogger = this.logger.child(toConnectionLogFields(identity));
|
|
569
559
|
const pending = {
|
|
570
560
|
connectionLogger,
|
|
571
561
|
helloTimeout: null,
|
|
562
|
+
identity,
|
|
572
563
|
};
|
|
573
564
|
const timeout = setTimeout(() => {
|
|
574
565
|
if (this.pendingConnections.get(ws) !== pending) {
|
|
@@ -576,7 +567,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
576
567
|
}
|
|
577
568
|
pending.helloTimeout = null;
|
|
578
569
|
this.pendingConnections.delete(ws);
|
|
579
|
-
pending.connectionLogger.warn({ timeoutMs: HELLO_TIMEOUT_MS }, "Closing connection due to missing hello");
|
|
570
|
+
pending.connectionLogger.warn({ ...toConnectionLogFields(identity), timeoutMs: HELLO_TIMEOUT_MS }, "Closing connection due to missing hello");
|
|
580
571
|
try {
|
|
581
572
|
ws.close(WS_CLOSE_HELLO_TIMEOUT, "Hello timeout");
|
|
582
573
|
}
|
|
@@ -589,7 +580,8 @@ export class VoiceAssistantWebSocketServer {
|
|
|
589
580
|
this.pendingConnections.set(ws, pending);
|
|
590
581
|
this.incrementRuntimeCounter("connectedAwaitingHello");
|
|
591
582
|
this.bindSocketHandlers(ws);
|
|
592
|
-
pending.connectionLogger.
|
|
583
|
+
pending.connectionLogger.info({
|
|
584
|
+
...toConnectionLogFields(identity),
|
|
593
585
|
totalPendingConnections: this.pendingConnections.size,
|
|
594
586
|
}, "Client connected; awaiting hello");
|
|
595
587
|
}
|
|
@@ -744,6 +736,10 @@ export class VoiceAssistantWebSocketServer {
|
|
|
744
736
|
return;
|
|
745
737
|
}
|
|
746
738
|
this.clearPendingConnection(ws);
|
|
739
|
+
pending.identity.clientId = clientId;
|
|
740
|
+
if (message.appVersion) {
|
|
741
|
+
pending.identity.appVersion = message.appVersion;
|
|
742
|
+
}
|
|
747
743
|
const existing = this.externalSessionsByKey.get(clientId);
|
|
748
744
|
if (existing) {
|
|
749
745
|
this.incrementRuntimeCounter("helloResumed");
|
|
@@ -764,9 +760,10 @@ export class VoiceAssistantWebSocketServer {
|
|
|
764
760
|
}
|
|
765
761
|
existing.sockets.add(ws);
|
|
766
762
|
this.sessions.set(ws, existing);
|
|
763
|
+
pending.identity.sessionId = existing.session.getSessionId();
|
|
767
764
|
this.sendToClient(ws, this.createServerInfoMessage());
|
|
768
|
-
|
|
769
|
-
|
|
765
|
+
pending.connectionLogger.info({
|
|
766
|
+
...toConnectionLogFields(pending.identity),
|
|
770
767
|
resumed: true,
|
|
771
768
|
totalSessions: this.sessions.size,
|
|
772
769
|
}, "Client connected via hello");
|
|
@@ -783,9 +780,10 @@ export class VoiceAssistantWebSocketServer {
|
|
|
783
780
|
});
|
|
784
781
|
this.sessions.set(ws, connection);
|
|
785
782
|
this.externalSessionsByKey.set(clientId, connection);
|
|
783
|
+
pending.identity.sessionId = connection.session.getSessionId();
|
|
786
784
|
this.sendToClient(ws, this.createServerInfoMessage());
|
|
787
|
-
connection.connectionLogger.
|
|
788
|
-
|
|
785
|
+
connection.connectionLogger.info({
|
|
786
|
+
...toConnectionLogFields(pending.identity),
|
|
789
787
|
resumed: false,
|
|
790
788
|
totalSessions: this.sessions.size,
|
|
791
789
|
}, "Client connected via hello");
|
|
@@ -825,6 +823,10 @@ export class VoiceAssistantWebSocketServer {
|
|
|
825
823
|
agentDetach: true,
|
|
826
824
|
// COMPAT(daemonDiagnostics): added in v0.1.100, remove gate after 2026-12-25 once daemon floor >= v0.1.100.
|
|
827
825
|
daemonDiagnostics: true,
|
|
826
|
+
// COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
|
|
827
|
+
daemonSelfUpdate: true,
|
|
828
|
+
// COMPAT(agentForkContext): added in v0.1.102, remove gate after 2026-12-28.
|
|
829
|
+
agentForkContext: true,
|
|
828
830
|
},
|
|
829
831
|
};
|
|
830
832
|
}
|
|
@@ -882,21 +884,34 @@ export class VoiceAssistantWebSocketServer {
|
|
|
882
884
|
return this.voiceCallerContexts.get(callerAgentId) ?? null;
|
|
883
885
|
}
|
|
884
886
|
async detachSocket(ws, details) {
|
|
887
|
+
const identity = this.socketIdentities.get(ws);
|
|
888
|
+
const identityFields = identity ? toConnectionLogFields(identity) : {};
|
|
885
889
|
const pending = this.clearPendingConnection(ws);
|
|
886
890
|
if (pending) {
|
|
887
891
|
this.incrementRuntimeCounter("pendingDisconnected");
|
|
888
|
-
pending.connectionLogger.
|
|
892
|
+
pending.connectionLogger.info({
|
|
893
|
+
...identityFields,
|
|
889
894
|
code: details.code,
|
|
890
895
|
reason: stringifyCloseReason(details.reason),
|
|
891
896
|
}, "Pending client disconnected");
|
|
897
|
+
this.socketIdentities.delete(ws);
|
|
892
898
|
return;
|
|
893
899
|
}
|
|
894
900
|
const connection = this.sessions.get(ws);
|
|
895
901
|
if (!connection) {
|
|
902
|
+
if (identity) {
|
|
903
|
+
this.logger.info({
|
|
904
|
+
...identityFields,
|
|
905
|
+
code: details.code,
|
|
906
|
+
reason: stringifyCloseReason(details.reason),
|
|
907
|
+
}, "Client socket closed without active session");
|
|
908
|
+
this.socketIdentities.delete(ws);
|
|
909
|
+
}
|
|
896
910
|
return;
|
|
897
911
|
}
|
|
898
912
|
this.sessions.delete(ws);
|
|
899
913
|
connection.sockets.delete(ws);
|
|
914
|
+
this.socketIdentities.delete(ws);
|
|
900
915
|
if (connection.sockets.size === 0) {
|
|
901
916
|
this.incrementRuntimeCounter("sessionDisconnectedWaitingReconnect");
|
|
902
917
|
if (connection.externalDisconnectCleanupTimeout) {
|
|
@@ -910,8 +925,8 @@ export class VoiceAssistantWebSocketServer {
|
|
|
910
925
|
void this.cleanupConnection(connection, "Client disconnected (grace timeout)");
|
|
911
926
|
}, EXTERNAL_SESSION_DISCONNECT_GRACE_MS);
|
|
912
927
|
connection.externalDisconnectCleanupTimeout = timeout;
|
|
913
|
-
connection.connectionLogger.
|
|
914
|
-
|
|
928
|
+
connection.connectionLogger.info({
|
|
929
|
+
...identityFields,
|
|
915
930
|
code: details.code,
|
|
916
931
|
reason: stringifyCloseReason(details.reason),
|
|
917
932
|
reconnectGraceMs: EXTERNAL_SESSION_DISCONNECT_GRACE_MS,
|
|
@@ -920,8 +935,8 @@ export class VoiceAssistantWebSocketServer {
|
|
|
920
935
|
}
|
|
921
936
|
if (connection.sockets.size > 0) {
|
|
922
937
|
this.incrementRuntimeCounter("sessionSocketDisconnectedAttached");
|
|
923
|
-
connection.connectionLogger.
|
|
924
|
-
|
|
938
|
+
connection.connectionLogger.info({
|
|
939
|
+
...identityFields,
|
|
925
940
|
remainingSockets: connection.sockets.size,
|
|
926
941
|
code: details.code,
|
|
927
942
|
reason: stringifyCloseReason(details.reason),
|
|
@@ -938,6 +953,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
938
953
|
}
|
|
939
954
|
for (const socket of connection.sockets) {
|
|
940
955
|
this.sessions.delete(socket);
|
|
956
|
+
this.socketIdentities.delete(socket);
|
|
941
957
|
}
|
|
942
958
|
connection.sockets.clear();
|
|
943
959
|
const existing = this.externalSessionsByKey.get(connection.clientId);
|
|
@@ -1112,7 +1128,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1112
1128
|
return;
|
|
1113
1129
|
}
|
|
1114
1130
|
if (message.type === "session") {
|
|
1115
|
-
void this.dispatchSessionMessage(activeConnection, message).catch((error) => {
|
|
1131
|
+
void this.dispatchSessionMessage(ws, activeConnection, message).catch((error) => {
|
|
1116
1132
|
this.handleRawMessageError({ ws, data, error, log: activeConnection.connectionLogger });
|
|
1117
1133
|
});
|
|
1118
1134
|
}
|
|
@@ -1121,8 +1137,16 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1121
1137
|
this.handleRawMessageError({ ws, data, error, log });
|
|
1122
1138
|
}
|
|
1123
1139
|
}
|
|
1124
|
-
async dispatchSessionMessage(activeConnection, message) {
|
|
1140
|
+
async dispatchSessionMessage(ws, activeConnection, message) {
|
|
1125
1141
|
this.recordInboundSessionRequestType(message.message.type);
|
|
1142
|
+
const controlRpc = getControlRpcLogInfo(message.message);
|
|
1143
|
+
if (controlRpc) {
|
|
1144
|
+
const identity = this.socketIdentities.get(ws);
|
|
1145
|
+
activeConnection.connectionLogger.warn({
|
|
1146
|
+
...(identity ? toConnectionLogFields(identity) : { clientId: activeConnection.clientId }),
|
|
1147
|
+
...controlRpc,
|
|
1148
|
+
}, "ws_control_rpc_received");
|
|
1149
|
+
}
|
|
1126
1150
|
const startMs = performance.now();
|
|
1127
1151
|
await activeConnection.session.handleMessage(message.message);
|
|
1128
1152
|
const durationMs = performance.now() - startMs;
|
|
@@ -1258,6 +1282,8 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1258
1282
|
outboundBinaryFrameTypesTop: runtimeMetrics.outboundBinaryFrameTypesTop,
|
|
1259
1283
|
bufferedAmount: runtimeMetrics.bufferedAmount,
|
|
1260
1284
|
eventLoopDelay: this.snapshotEventLoopDelay(),
|
|
1285
|
+
uptimeSeconds: getProcessUptimeSeconds(),
|
|
1286
|
+
memory: getProcessMemoryDiagnostics(),
|
|
1261
1287
|
runtime: sessionMetrics,
|
|
1262
1288
|
latency: runtimeMetrics.latency,
|
|
1263
1289
|
agents: agentSnapshot,
|
|
@@ -1390,6 +1416,31 @@ export class VoiceAssistantWebSocketServer {
|
|
|
1390
1416
|
}
|
|
1391
1417
|
}
|
|
1392
1418
|
}
|
|
1419
|
+
function createWebSocketConnectionIdentity(requestMetadata, metadata) {
|
|
1420
|
+
return {
|
|
1421
|
+
connectionId: `conn_${randomUUID().replaceAll("-", "")}`,
|
|
1422
|
+
transport: metadata?.transport === "relay" ? "relay" : "direct",
|
|
1423
|
+
...(requestMetadata.host ? { host: requestMetadata.host } : {}),
|
|
1424
|
+
...(requestMetadata.origin ? { origin: requestMetadata.origin } : {}),
|
|
1425
|
+
...(requestMetadata.userAgent ? { userAgent: requestMetadata.userAgent } : {}),
|
|
1426
|
+
...(requestMetadata.remoteAddress ? { remoteAddress: requestMetadata.remoteAddress } : {}),
|
|
1427
|
+
...(metadata?.relayConnectionId ? { relayConnectionId: metadata.relayConnectionId } : {}),
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
function toConnectionLogFields(identity) {
|
|
1431
|
+
return {
|
|
1432
|
+
connectionId: identity.connectionId,
|
|
1433
|
+
transport: identity.transport,
|
|
1434
|
+
...(identity.host ? { host: identity.host } : {}),
|
|
1435
|
+
...(identity.origin ? { origin: identity.origin } : {}),
|
|
1436
|
+
...(identity.userAgent ? { userAgent: identity.userAgent } : {}),
|
|
1437
|
+
...(identity.remoteAddress ? { remoteAddress: identity.remoteAddress } : {}),
|
|
1438
|
+
...(identity.relayConnectionId ? { relayConnectionId: identity.relayConnectionId } : {}),
|
|
1439
|
+
...(identity.clientId ? { clientId: identity.clientId } : {}),
|
|
1440
|
+
...(identity.sessionId ? { sessionId: identity.sessionId } : {}),
|
|
1441
|
+
...(identity.appVersion ? { appVersion: identity.appVersion } : {}),
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1393
1444
|
function extractSocketRequestMetadata(request) {
|
|
1394
1445
|
if (!request || typeof request !== "object") {
|
|
1395
1446
|
return {};
|
|
@@ -1406,6 +1457,88 @@ function extractSocketRequestMetadata(request) {
|
|
|
1406
1457
|
...(remoteAddress ? { remoteAddress } : {}),
|
|
1407
1458
|
};
|
|
1408
1459
|
}
|
|
1460
|
+
function stripIpv6Brackets(hostname) {
|
|
1461
|
+
return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
1462
|
+
}
|
|
1463
|
+
function parseHostAuthority(host) {
|
|
1464
|
+
const trimmed = host.trim();
|
|
1465
|
+
if (!trimmed) {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
if (trimmed.startsWith("[")) {
|
|
1469
|
+
const end = trimmed.indexOf("]");
|
|
1470
|
+
if (end === -1) {
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
const hostname = stripIpv6Brackets(trimmed.slice(0, end + 1)).toLowerCase();
|
|
1474
|
+
const rest = trimmed.slice(end + 1);
|
|
1475
|
+
if (!rest) {
|
|
1476
|
+
return { hostname, port: null };
|
|
1477
|
+
}
|
|
1478
|
+
if (!rest.startsWith(":")) {
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
const port = rest.slice(1);
|
|
1482
|
+
return port ? { hostname, port } : null;
|
|
1483
|
+
}
|
|
1484
|
+
const firstColon = trimmed.indexOf(":");
|
|
1485
|
+
if (firstColon === -1) {
|
|
1486
|
+
return { hostname: trimmed.toLowerCase(), port: null };
|
|
1487
|
+
}
|
|
1488
|
+
if (trimmed.indexOf(":", firstColon + 1) !== -1) {
|
|
1489
|
+
return { hostname: trimmed.toLowerCase(), port: null };
|
|
1490
|
+
}
|
|
1491
|
+
const hostname = trimmed.slice(0, firstColon).toLowerCase();
|
|
1492
|
+
const port = trimmed.slice(firstColon + 1);
|
|
1493
|
+
return hostname && port ? { hostname, port } : null;
|
|
1494
|
+
}
|
|
1495
|
+
function defaultPortForOriginProtocol(protocol) {
|
|
1496
|
+
if (protocol === "http:") {
|
|
1497
|
+
return "80";
|
|
1498
|
+
}
|
|
1499
|
+
if (protocol === "https:") {
|
|
1500
|
+
return "443";
|
|
1501
|
+
}
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
function isLoopbackAlias(hostname) {
|
|
1505
|
+
const normalized = stripIpv6Brackets(hostname).toLowerCase();
|
|
1506
|
+
if (normalized === "localhost" || normalized.endsWith(".localhost")) {
|
|
1507
|
+
return true;
|
|
1508
|
+
}
|
|
1509
|
+
if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
return /^127(?:\.\d{1,3}){3}$/.test(normalized);
|
|
1513
|
+
}
|
|
1514
|
+
export function isWebSocketSameOrigin(origin, requestHost) {
|
|
1515
|
+
if (!origin || !requestHost) {
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1518
|
+
if (origin === `http://${requestHost}` || origin === `https://${requestHost}`) {
|
|
1519
|
+
return true;
|
|
1520
|
+
}
|
|
1521
|
+
let originUrl;
|
|
1522
|
+
try {
|
|
1523
|
+
originUrl = new URL(origin);
|
|
1524
|
+
}
|
|
1525
|
+
catch {
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
const originPort = originUrl.port || defaultPortForOriginProtocol(originUrl.protocol);
|
|
1529
|
+
if (!originPort) {
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
const requestAuthority = parseHostAuthority(requestHost);
|
|
1533
|
+
if (!requestAuthority) {
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
const requestPort = requestAuthority.port || defaultPortForOriginProtocol(originUrl.protocol);
|
|
1537
|
+
if (originPort !== requestPort) {
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1540
|
+
return isLoopbackAlias(originUrl.hostname) && isLoopbackAlias(requestAuthority.hostname);
|
|
1541
|
+
}
|
|
1409
1542
|
function selectWebSocketProtocol(protocols, password) {
|
|
1410
1543
|
if (!password) {
|
|
1411
1544
|
return protocols.values().next().value ?? false;
|
|
@@ -1432,6 +1565,31 @@ function stringifyCloseReason(reason) {
|
|
|
1432
1565
|
const text = String(reason);
|
|
1433
1566
|
return text.length > 0 ? text : null;
|
|
1434
1567
|
}
|
|
1568
|
+
function getControlRpcLogInfo(message) {
|
|
1569
|
+
if (message.type === "shutdown_server_request") {
|
|
1570
|
+
return {
|
|
1571
|
+
requestType: message.type,
|
|
1572
|
+
requestId: message.requestId,
|
|
1573
|
+
reason: CLIENT_SHUTDOWN_RPC_REASON,
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
if (message.type === "restart_server_request") {
|
|
1577
|
+
const reason = normalizeClientRestartRpcReason(message.reason);
|
|
1578
|
+
return {
|
|
1579
|
+
requestType: message.type,
|
|
1580
|
+
requestId: message.requestId,
|
|
1581
|
+
reason,
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
if (message.type === "daemon.update.request") {
|
|
1585
|
+
return {
|
|
1586
|
+
requestType: message.type,
|
|
1587
|
+
requestId: message.requestId,
|
|
1588
|
+
reason: "daemon_update",
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1435
1593
|
function extractRequestInfoFromUnknownWsInbound(payload) {
|
|
1436
1594
|
if (!payload || typeof payload !== "object") {
|
|
1437
1595
|
return null;
|
|
@@ -4,6 +4,7 @@ import { CopilotQuotaProvider } from "./providers/copilot.js";
|
|
|
4
4
|
import { CursorQuotaProvider } from "./providers/cursor.js";
|
|
5
5
|
import { GrokQuotaProvider } from "./providers/grok.js";
|
|
6
6
|
import { KimiQuotaProvider } from "./providers/kimi.js";
|
|
7
|
+
import { MiniMaxQuotaProvider } from "./providers/minimax.js";
|
|
7
8
|
import { ZaiQuotaProvider } from "./providers/zai.js";
|
|
8
9
|
export const PROVIDER_USAGE_FETCHERS = [
|
|
9
10
|
{
|
|
@@ -40,6 +41,10 @@ export const PROVIDER_USAGE_FETCHERS = [
|
|
|
40
41
|
providerId: "kimi",
|
|
41
42
|
create: (options) => new KimiQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
42
43
|
},
|
|
44
|
+
{
|
|
45
|
+
providerId: "minimax",
|
|
46
|
+
create: (options) => new MiniMaxQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
47
|
+
},
|
|
43
48
|
];
|
|
44
49
|
export function createProviderUsageFetchers(options) {
|
|
45
50
|
return PROVIDER_USAGE_FETCHERS.map((entry) => entry.create(options));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../../server/messages.js";
|
|
3
|
+
import type { ProviderApiFetch, ProviderUsageFetcher } from "../provider.js";
|
|
4
|
+
interface MiniMaxQuotaProviderOptions {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
fetch?: ProviderApiFetch;
|
|
7
|
+
configPath?: string;
|
|
8
|
+
credentialsPath?: string;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
now?: () => number;
|
|
11
|
+
}
|
|
12
|
+
export declare class MiniMaxQuotaProvider implements ProviderUsageFetcher {
|
|
13
|
+
readonly providerId = "minimax";
|
|
14
|
+
readonly displayName = "MiniMax";
|
|
15
|
+
private readonly logger;
|
|
16
|
+
private readonly fetchApi;
|
|
17
|
+
private readonly configPath;
|
|
18
|
+
private readonly credentialsPath;
|
|
19
|
+
private readonly env;
|
|
20
|
+
private readonly now;
|
|
21
|
+
constructor(options: MiniMaxQuotaProviderOptions);
|
|
22
|
+
fetchUsage(): Promise<ProviderUsage>;
|
|
23
|
+
private resolveAuth;
|
|
24
|
+
private isExpired;
|
|
25
|
+
private readCredentials;
|
|
26
|
+
private readConfig;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=minimax.d.ts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ApiNumberSchema, ApiOptionalStringSchema, fetchProviderApi, unavailableUsage, windowFromUsedPct, } from "../usage.js";
|
|
6
|
+
const MINIMAX_GLOBAL_BASE_URL = "https://api.minimax.io";
|
|
7
|
+
const MINIMAX_CN_BASE_URL = "https://api.minimaxi.com";
|
|
8
|
+
const MINIMAX_CREDENTIALS_PATH = join(homedir(), ".mmx", "credentials.json");
|
|
9
|
+
const MINIMAX_CONFIG_PATH = join(homedir(), ".mmx", "config.json");
|
|
10
|
+
const MiniMaxModelRemainSchema = z.object({
|
|
11
|
+
model_name: ApiOptionalStringSchema,
|
|
12
|
+
start_time: ApiNumberSchema.optional(),
|
|
13
|
+
end_time: ApiNumberSchema.optional(),
|
|
14
|
+
remains_time: ApiNumberSchema.optional(),
|
|
15
|
+
current_interval_total_count: ApiNumberSchema.optional(),
|
|
16
|
+
current_interval_usage_count: ApiNumberSchema.optional(),
|
|
17
|
+
current_interval_remaining_percent: ApiNumberSchema.optional(),
|
|
18
|
+
current_weekly_total_count: ApiNumberSchema.optional(),
|
|
19
|
+
current_weekly_usage_count: ApiNumberSchema.optional(),
|
|
20
|
+
current_weekly_remaining_percent: ApiNumberSchema.optional(),
|
|
21
|
+
current_interval_status: ApiNumberSchema.optional(),
|
|
22
|
+
current_weekly_status: ApiNumberSchema.optional(),
|
|
23
|
+
weekly_start_time: ApiNumberSchema.optional(),
|
|
24
|
+
weekly_end_time: ApiNumberSchema.optional(),
|
|
25
|
+
weekly_remains_time: ApiNumberSchema.optional(),
|
|
26
|
+
weekly_boost_permille: ApiNumberSchema.optional(),
|
|
27
|
+
});
|
|
28
|
+
const MiniMaxQuotaResponseSchema = z.object({
|
|
29
|
+
model_remains: z.array(MiniMaxModelRemainSchema).optional(),
|
|
30
|
+
});
|
|
31
|
+
const MiniMaxCredentialsSchema = z.object({
|
|
32
|
+
access_token: z.string().optional(),
|
|
33
|
+
refresh_token: z.string().optional(),
|
|
34
|
+
expires_at: ApiOptionalStringSchema,
|
|
35
|
+
resource_url: ApiOptionalStringSchema,
|
|
36
|
+
});
|
|
37
|
+
const MiniMaxConfigSchema = z.object({
|
|
38
|
+
api_key: z.string().optional(),
|
|
39
|
+
region: z.string().optional(),
|
|
40
|
+
base_url: ApiOptionalStringSchema,
|
|
41
|
+
oauth: MiniMaxCredentialsSchema.optional(),
|
|
42
|
+
});
|
|
43
|
+
function resolveBaseUrl(input) {
|
|
44
|
+
const explicit = input.baseUrl;
|
|
45
|
+
if (explicit && explicit.startsWith("http"))
|
|
46
|
+
return explicit;
|
|
47
|
+
if (input.region === "cn")
|
|
48
|
+
return MINIMAX_CN_BASE_URL;
|
|
49
|
+
return MINIMAX_GLOBAL_BASE_URL;
|
|
50
|
+
}
|
|
51
|
+
function computeUsedPct(remaining, total) {
|
|
52
|
+
if (typeof remaining !== "number" || typeof total !== "number")
|
|
53
|
+
return null;
|
|
54
|
+
if (!Number.isFinite(total) || total <= 0)
|
|
55
|
+
return null;
|
|
56
|
+
if (!Number.isFinite(remaining))
|
|
57
|
+
return null;
|
|
58
|
+
const used = total - remaining;
|
|
59
|
+
return Math.max(0, Math.min(100, (used / total) * 100));
|
|
60
|
+
}
|
|
61
|
+
function epochMsToIso(value) {
|
|
62
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
|
|
63
|
+
return null;
|
|
64
|
+
return new Date(value).toISOString();
|
|
65
|
+
}
|
|
66
|
+
function toneForStatus(status) {
|
|
67
|
+
if (status === 2)
|
|
68
|
+
return "danger";
|
|
69
|
+
if (status === 3)
|
|
70
|
+
return "default";
|
|
71
|
+
return "ok";
|
|
72
|
+
}
|
|
73
|
+
function toIntervalWindow(modelName, model) {
|
|
74
|
+
const total = model.current_interval_total_count ?? null;
|
|
75
|
+
const used = model.current_interval_usage_count ?? null;
|
|
76
|
+
const remainingPercent = model.current_interval_remaining_percent ?? null;
|
|
77
|
+
const usedPct = typeof remainingPercent === "number" && Number.isFinite(remainingPercent)
|
|
78
|
+
? Math.max(0, Math.min(100, 100 - remainingPercent))
|
|
79
|
+
: computeUsedPct(typeof total === "number" && typeof used === "number" ? total - used : null, total);
|
|
80
|
+
if (usedPct === null)
|
|
81
|
+
return null;
|
|
82
|
+
return windowFromUsedPct({
|
|
83
|
+
id: `interval_${modelName}`,
|
|
84
|
+
label: `${modelName} · Interval`,
|
|
85
|
+
utilizationPct: usedPct,
|
|
86
|
+
resetsAt: epochMsToIso(model.end_time),
|
|
87
|
+
tone: toneForStatus(model.current_interval_status),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function toWeeklyWindow(modelName, model) {
|
|
91
|
+
const total = model.current_weekly_total_count ?? null;
|
|
92
|
+
const used = model.current_weekly_usage_count ?? null;
|
|
93
|
+
const remainingPercent = model.current_weekly_remaining_percent ?? null;
|
|
94
|
+
let usedPct = null;
|
|
95
|
+
if (typeof remainingPercent === "number" && Number.isFinite(remainingPercent)) {
|
|
96
|
+
usedPct = Math.max(0, Math.min(100, 100 - remainingPercent));
|
|
97
|
+
}
|
|
98
|
+
else if (typeof total === "number" && typeof used === "number") {
|
|
99
|
+
usedPct = computeUsedPct(total - used, total);
|
|
100
|
+
}
|
|
101
|
+
if (usedPct === null)
|
|
102
|
+
return null;
|
|
103
|
+
return windowFromUsedPct({
|
|
104
|
+
id: `weekly_${modelName}`,
|
|
105
|
+
label: `${modelName} · Weekly`,
|
|
106
|
+
utilizationPct: usedPct,
|
|
107
|
+
resetsAt: epochMsToIso(model.weekly_end_time),
|
|
108
|
+
tone: toneForStatus(model.current_weekly_status),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export class MiniMaxQuotaProvider {
|
|
112
|
+
constructor(options) {
|
|
113
|
+
this.providerId = "minimax";
|
|
114
|
+
this.displayName = "MiniMax";
|
|
115
|
+
this.logger = options.logger;
|
|
116
|
+
this.fetchApi = options.fetch ?? fetch;
|
|
117
|
+
this.configPath = options.configPath ?? MINIMAX_CONFIG_PATH;
|
|
118
|
+
this.credentialsPath = options.credentialsPath ?? MINIMAX_CREDENTIALS_PATH;
|
|
119
|
+
this.env = options.env ?? process.env;
|
|
120
|
+
this.now = options.now ?? Date.now;
|
|
121
|
+
}
|
|
122
|
+
async fetchUsage() {
|
|
123
|
+
const auth = await this.resolveAuth();
|
|
124
|
+
if (!auth)
|
|
125
|
+
return unavailableUsage(this);
|
|
126
|
+
const res = await fetchProviderApi(this.fetchApi, `${auth.baseUrl}/v1/token_plan/remains`, {
|
|
127
|
+
headers: {
|
|
128
|
+
Authorization: `Bearer ${auth.token}`,
|
|
129
|
+
Accept: "application/json",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
this.logger.debug({ status: res.status }, "MiniMax usage fetch failed");
|
|
134
|
+
return unavailableUsage(this);
|
|
135
|
+
}
|
|
136
|
+
const resp = MiniMaxQuotaResponseSchema.parse(await res.json());
|
|
137
|
+
const models = resp.model_remains ?? [];
|
|
138
|
+
const windows = [];
|
|
139
|
+
for (const model of models) {
|
|
140
|
+
const name = model.model_name ?? "token-plan";
|
|
141
|
+
const intervalWindow = toIntervalWindow(name, model);
|
|
142
|
+
if (intervalWindow)
|
|
143
|
+
windows.push(intervalWindow);
|
|
144
|
+
const weeklyWindow = toWeeklyWindow(name, model);
|
|
145
|
+
if (weeklyWindow)
|
|
146
|
+
windows.push(weeklyWindow);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
providerId: this.providerId,
|
|
150
|
+
displayName: this.displayName,
|
|
151
|
+
status: windows.length > 0 ? "available" : "unavailable",
|
|
152
|
+
planLabel: null,
|
|
153
|
+
windows,
|
|
154
|
+
balances: [],
|
|
155
|
+
details: [],
|
|
156
|
+
error: null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async resolveAuth() {
|
|
160
|
+
const envToken = this.env["MINIMAX_API_KEY"];
|
|
161
|
+
if (envToken) {
|
|
162
|
+
const envBase = this.env["MINIMAX_BASE_URL"];
|
|
163
|
+
return {
|
|
164
|
+
token: envToken,
|
|
165
|
+
baseUrl: resolveBaseUrl({ baseUrl: envBase }),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const credentials = await this.readCredentials();
|
|
169
|
+
if (credentials?.access_token && !this.isExpired(credentials.expires_at)) {
|
|
170
|
+
return {
|
|
171
|
+
token: credentials.access_token,
|
|
172
|
+
baseUrl: resolveBaseUrl({ baseUrl: credentials.resource_url }),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const config = await this.readConfig();
|
|
176
|
+
if (config?.api_key) {
|
|
177
|
+
return {
|
|
178
|
+
token: config.api_key,
|
|
179
|
+
baseUrl: resolveBaseUrl({
|
|
180
|
+
baseUrl: config.base_url,
|
|
181
|
+
region: config.region,
|
|
182
|
+
}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (config?.oauth?.access_token && !this.isExpired(config.oauth.expires_at)) {
|
|
186
|
+
return {
|
|
187
|
+
token: config.oauth.access_token,
|
|
188
|
+
baseUrl: resolveBaseUrl({
|
|
189
|
+
baseUrl: config.oauth.resource_url ?? config.base_url,
|
|
190
|
+
region: config.region,
|
|
191
|
+
}),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
isExpired(expiresAt) {
|
|
197
|
+
if (!expiresAt)
|
|
198
|
+
return false;
|
|
199
|
+
const parsed = Date.parse(expiresAt);
|
|
200
|
+
if (!Number.isFinite(parsed))
|
|
201
|
+
return false;
|
|
202
|
+
return parsed <= this.now();
|
|
203
|
+
}
|
|
204
|
+
async readCredentials() {
|
|
205
|
+
if (!existsSync(this.credentialsPath))
|
|
206
|
+
return null;
|
|
207
|
+
try {
|
|
208
|
+
const raw = JSON.parse(await fs.readFile(this.credentialsPath, "utf8"));
|
|
209
|
+
return MiniMaxCredentialsSchema.parse(raw);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async readConfig() {
|
|
216
|
+
if (!existsSync(this.configPath))
|
|
217
|
+
return null;
|
|
218
|
+
try {
|
|
219
|
+
const raw = JSON.parse(await fs.readFile(this.configPath, "utf8"));
|
|
220
|
+
return MiniMaxConfigSchema.parse(raw);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=minimax.js.map
|
|
@@ -42,11 +42,11 @@ function resolveAgentHookInstallPath(install, options) {
|
|
|
42
42
|
}
|
|
43
43
|
export function buildAgentHookShellCommand(provider, event) {
|
|
44
44
|
const hookCommand = `"\${PASEO_HOOK_CLI:-paseo}" hooks ${shellToken(provider.id)} ${shellToken(event.event)}`;
|
|
45
|
-
return `[ -n "$PASEO_TERMINAL_ID" ]
|
|
45
|
+
return `if [ -n "$PASEO_TERMINAL_ID" ]; then ${hookCommand}; fi`;
|
|
46
46
|
}
|
|
47
47
|
export function buildAgentHookWindowsCommand(provider, event) {
|
|
48
48
|
const hookArgs = `hooks ${windowsToken(provider.id)} ${windowsToken(event.event)}`;
|
|
49
|
-
return `if defined PASEO_TERMINAL_ID (if defined PASEO_HOOK_CLI ("%PASEO_HOOK_CLI%" ${hookArgs}) else (paseo ${hookArgs}))`;
|
|
49
|
+
return `if defined PASEO_TERMINAL_ID (if defined PASEO_HOOK_CLI ("%PASEO_HOOK_CLI%" ${hookArgs}) else (paseo ${hookArgs})) else (exit /b 0)`;
|
|
50
50
|
}
|
|
51
51
|
function installAgentHookPluginFile(install, options) {
|
|
52
52
|
const configPath = resolveAgentHookInstallPath(install, options);
|