shadow-claw 1.23.3 → 1.24.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/README.md +42 -5
- package/bin/assert-version-bump.mjs +60 -38
- package/bin/assert-version-bump.test.mjs +74 -11
- package/bin/cli.mjs +166 -0
- package/bin/cli.test.mjs +60 -0
- package/bin/commands/backup.mjs +70 -0
- package/bin/commands/clients.mjs +41 -0
- package/bin/commands/peer-id.mjs +76 -0
- package/bin/commands/peer-id.test.mjs +104 -0
- package/bin/commands/send.mjs +51 -0
- package/bin/commands/tasks.mjs +59 -0
- package/bin/commands/webrtc-listen.mjs +254 -0
- package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
- package/bin/rolldown.config.test.mjs +401 -8
- package/bin/utils/control-client.mjs +243 -0
- package/bin/utils/control-client.test.mjs +83 -0
- package/bin/utils/ulid.mjs +45 -0
- package/bin/utils/webrtc-control-client.mjs +699 -0
- package/bin/utils/webrtc-control-client.test.mjs +318 -0
- package/dist/electron/main.cjs +1 -1
- package/dist/public/AGENTS.md +5 -3
- package/dist/public/README.md +42 -5
- package/dist/public/agent.worker.js +6 -6
- package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
- package/dist/public/backup-controller-C_s9in2t.js +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
- package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
- package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
- package/dist/public/config-CrHchneq.js +1 -0
- package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
- package/dist/public/docs/README.md +3 -1
- package/dist/public/docs/architecture/overview.md +12 -12
- package/dist/public/docs/architecture/storage.md +5 -2
- package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +2 -2
- package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
- package/dist/public/docs/guides/server-development-configuration.md +24 -0
- package/dist/public/docs/publishing/index.html +19 -76
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/docs/subsystems/backup.md +56 -0
- package/dist/public/docs/subsystems/cli.md +124 -0
- package/dist/public/docs/subsystems/control-plane.md +158 -0
- package/dist/public/docs/subsystems/notifications.md +54 -2
- package/dist/public/docs/subsystems/reactive-ui.md +1 -0
- package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
- package/dist/public/e2e/README.md +5 -1
- package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
- package/dist/public/initControlPlane-C9jpnWev.js +1 -0
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
- package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker/push-handler.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
- package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
- package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
- package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
- package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
- package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
- package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
- package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
- package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
- package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
- package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/static-main-manifest.json +1 -1
- package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
- package/dist/server.js +286 -189
- package/package.json +2 -1
- package/src/__mocks__/jszip.cjs +15 -5
- package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
- package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
- package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
- package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
- package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
- package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
- package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
- package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
- package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
- package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
- package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
- package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
- package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
- package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
- package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
- package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
- package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
- package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
- package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
- package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
- package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
- package/src/config/config.test.ts +9 -0
- package/src/config/config.ts +15 -0
- package/src/core/app-routes.test.ts +56 -0
- package/src/core/app-routes.ts +8 -1
- package/src/core/backup-controller.test.ts +101 -0
- package/src/core/backup-controller.ts +137 -0
- package/src/core/control-plane-client.test.ts +408 -0
- package/src/core/control-plane-client.ts +425 -0
- package/src/core/orchestrator/orchestrator.ts +4 -0
- package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
- package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
- package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
- package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
- package/src/core/utils/initControlPlane.test.ts +276 -0
- package/src/core/utils/initControlPlane.ts +437 -0
- package/src/core/utils/initializeApp.ts +18 -0
- package/src/db/getSession.test.ts +13 -1
- package/src/db/openDatabase.test.ts +10 -1
- package/src/db/openDatabase.ts +3 -1
- package/src/server/app.test.ts +12 -0
- package/src/server/app.ts +18 -8
- package/src/server/client-registry.test.ts +250 -0
- package/src/server/client-registry.ts +477 -0
- package/src/server/config.ts +15 -0
- package/src/server/control-plane-types.ts +120 -0
- package/src/server/control-plane.test.ts +472 -0
- package/src/server/control-plane.ts +581 -0
- package/src/server/routes/backup.test.ts +263 -0
- package/src/server/routes/backup.ts +262 -0
- package/src/server/routes/integrations-email.test.ts +532 -0
- package/src/server/routes/llamafile.test.ts +172 -48
- package/src/server/routes/mesh-llm.test.ts +192 -0
- package/src/server/routes/ollama.test.ts +49 -0
- package/src/server/server-peer.test.ts +86 -0
- package/src/server/server-peer.ts +330 -0
- package/src/server/server.ts +35 -0
- package/src/server/services/llamafile-manager.test.ts +230 -6
- package/src/server/services/transformers-runtime.test.ts +186 -18
- package/src/server/webrtc.test.ts +21 -0
- package/src/server/webrtc.ts +72 -0
- package/src/service-worker/fetch-proxy.test.ts +64 -1
- package/src/service-worker/push-handler.ts +39 -0
- package/src/shell/vm.test.ts +744 -12
- package/src/storage/ensureMainGroupIndex.test.ts +153 -0
- package/src/storage/getGroupFile.test.ts +66 -0
- package/src/storage/memoryStorage.test.ts +82 -2
- package/src/storage/migrateLegacyOpfs.test.ts +327 -0
- package/src/storage/migrateLegacyOpfs.ts +117 -0
- package/src/storage/writeGroupFileBytes.test.ts +143 -0
- package/src/stores/chat-ui.test.ts +20 -0
- package/src/stores/orchestrator.test.ts +134 -0
- package/src/stores/toast.test.ts +50 -0
- package/src/stores/tools.test.ts +147 -10
- package/src/subsystems/channels/imessage.test.ts +59 -0
- package/src/subsystems/channels/peerjs.test.ts +500 -62
- package/src/subsystems/channels/peerjs.ts +71 -4
- package/src/subsystems/channels/telegram.test.ts +177 -0
- package/src/subsystems/email/catalog.test.ts +1 -0
- package/src/subsystems/email/connections.test.ts +39 -1
- package/src/subsystems/git/credentials.test.ts +160 -825
- package/src/subsystems/git/git.test.ts +463 -0
- package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
- package/src/subsystems/notifications/push-routes.test.ts +33 -0
- package/src/subsystems/notifications/push-routes.ts +23 -0
- package/src/subsystems/notifications/push-store.ts +1 -1
- package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
- package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
- package/src/subsystems/providers/model-registry.test.ts +115 -0
- package/src/subsystems/providers/providers.test.ts +94 -1
- package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
- package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
- package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
- package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
- package/src/subsystems/skills/activateSkill.test.ts +101 -0
- package/src/subsystems/skills/discoverSkills.test.ts +251 -0
- package/src/subsystems/tools/cron.test.ts +64 -0
- package/src/subsystems/tools/declarative.test.ts +255 -89
- package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
- package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
- package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
- package/src/testing/jest-setup.ts +328 -40
- package/src/testing/mock-v86.mjs +49 -0
- package/src/ui/a2ui/types.ts +4 -3
- package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
- package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
- package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
- package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
- package/src/worker/tools/email/email.test.ts +193 -8
- package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
- package/src/worker/tools/git/git.test.ts +214 -3
- package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
- package/src/worker/tools/rooms/leave-room.test.ts +40 -0
- package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
- package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
- package/src/worker/tools/ui/ask-user.test.ts +50 -0
- package/src/worker/tools/ui/clear-chat.test.ts +19 -0
- package/src/worker/tools/ui/get-current-time.test.ts +19 -0
- package/src/worker/tools/ui/javascript.test.ts +116 -0
- package/src/worker/tools/ui/list-components.test.ts +10 -0
- package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
- package/src/worker/tools/ui/manage-tools.test.ts +34 -0
- package/src/worker/tools/ui/render-component.test.ts +346 -0
- package/src/worker/tools/ui/send-notification.test.ts +40 -0
- package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
- package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
- package/src/worker/tools/workspace/attach-file.test.ts +84 -0
- package/src/worker/tools/workspace/copy-file.test.ts +69 -0
- package/src/worker/tools/workspace/create-directory.test.ts +40 -0
- package/src/worker/tools/workspace/delete-file.test.ts +73 -0
- package/src/worker/tools/workspace/diff-files.test.ts +94 -0
- package/src/worker/tools/workspace/list-files.test.ts +36 -0
- package/src/worker/tools/workspace/move-file.test.ts +66 -0
- package/src/worker/tools/workspace/open-file.test.ts +57 -0
- package/src/worker/tools/workspace/patch-file.test.ts +81 -0
- package/src/worker/tools/workspace/read-file.test.ts +116 -0
- package/src/worker/tools/workspace/send-file.test.ts +82 -0
- package/src/worker/tools/workspace/update-memory.test.ts +29 -0
- package/src/worker/tools/workspace/write-file.test.ts +28 -0
- package/src/worker/utils/rate-limit.test.ts +199 -0
- package/src/worker/utils/tool-state.test.ts +29 -0
- package/src/worker/utils/transformers-js-utils.test.ts +59 -0
- package/dist/public/config-AL77QAx4.js +0 -1
- package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
- package/dist/public/file-viewer-CCSABMsE.js +0 -1
- package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
- package/dist/public/orchestrator-4su7iyWj.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
- package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
- package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
|
@@ -1,15 +1,41 @@
|
|
|
1
1
|
import { jest } from "@jest/globals";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const mockCreateConversation = jest.fn<any>().mockResolvedValue({
|
|
4
|
+
sendMessage: jest.fn<any>().mockResolvedValue(undefined),
|
|
5
|
+
sendMessageStreaming: jest.fn<any>().mockImplementation(async function* () {
|
|
6
|
+
yield {
|
|
7
|
+
content: [
|
|
8
|
+
{
|
|
9
|
+
text: JSON.stringify({
|
|
10
|
+
type: "response",
|
|
11
|
+
response: "Hello from LiteRT!",
|
|
12
|
+
}),
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
jest.unstable_mockModule("@litert-lm/core", () => ({
|
|
20
|
+
Engine: {
|
|
21
|
+
create: jest.fn<any>().mockResolvedValue({
|
|
22
|
+
createConversation: mockCreateConversation,
|
|
23
|
+
delete: jest.fn<any>().mockResolvedValue(undefined),
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const {
|
|
4
29
|
isLiteRtLmSupported,
|
|
5
30
|
loadLiteRtModelStream,
|
|
6
31
|
parseModelSpecificToolCall,
|
|
7
32
|
parseLiteRtStructured,
|
|
8
|
-
|
|
33
|
+
invokeWithLiteRtLm,
|
|
34
|
+
} = await import("./litert-lm-provider.js");
|
|
9
35
|
|
|
10
36
|
// jsdom does not implement Blob fully, so we mock it for the tests
|
|
11
|
-
if (typeof
|
|
12
|
-
(
|
|
37
|
+
if (typeof globalThis !== "undefined") {
|
|
38
|
+
(globalThis as any).Blob = class MockBlob {
|
|
13
39
|
private parts: any[];
|
|
14
40
|
public size: number;
|
|
15
41
|
constructor(parts: any[]) {
|
|
@@ -64,8 +90,6 @@ if (typeof global !== "undefined") {
|
|
|
64
90
|
};
|
|
65
91
|
}
|
|
66
92
|
|
|
67
|
-
// TextEncoder / TextDecoder are not available in every Jest environment;
|
|
68
|
-
// fall back to Node's Buffer.
|
|
69
93
|
function encodeText(s: string): Uint8Array {
|
|
70
94
|
if (typeof TextEncoder !== "undefined") {
|
|
71
95
|
return new TextEncoder().encode(s);
|
|
@@ -82,10 +106,6 @@ function decodeBytes(b: Uint8Array): string {
|
|
|
82
106
|
return Buffer.from(b).toString("utf8");
|
|
83
107
|
}
|
|
84
108
|
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
// Helpers
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
|
|
89
109
|
function makeBodyStream(data: Uint8Array): ReadableStream<Uint8Array> {
|
|
90
110
|
return new ReadableStream<Uint8Array>({
|
|
91
111
|
start(controller) {
|
|
@@ -114,14 +134,9 @@ function makeFetchResponse(
|
|
|
114
134
|
};
|
|
115
135
|
}
|
|
116
136
|
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// isLiteRtLmSupported
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
|
|
121
137
|
describe("LiteRT-LM Provider", () => {
|
|
122
138
|
describe("isLiteRtLmSupported", () => {
|
|
123
139
|
let originalNavigator: any;
|
|
124
|
-
|
|
125
140
|
let originalWebAssembly: any;
|
|
126
141
|
|
|
127
142
|
beforeEach(() => {
|
|
@@ -204,13 +219,8 @@ describe("LiteRT-LM Provider", () => {
|
|
|
204
219
|
});
|
|
205
220
|
});
|
|
206
221
|
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
// loadLiteRtModelStream — full integration of cache + network
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
|
|
211
222
|
describe("loadLiteRtModelStream", () => {
|
|
212
223
|
let mockFetch: jest.Mock<any>;
|
|
213
|
-
// cache storage: key -> { body: Uint8Array | null, headers: Headers }
|
|
214
224
|
const cacheStore = new Map<
|
|
215
225
|
string,
|
|
216
226
|
{ body: Uint8Array | null; headers: Headers }
|
|
@@ -222,9 +232,6 @@ describe("LiteRT-LM Provider", () => {
|
|
|
222
232
|
}
|
|
223
233
|
|
|
224
234
|
function makeCacheApi() {
|
|
225
|
-
// A single cache object backed by `cacheStore`; shared across all
|
|
226
|
-
// `caches.open()` calls so that writes from one call site are visible
|
|
227
|
-
// to reads from another.
|
|
228
235
|
const cacheObject = {
|
|
229
236
|
put: jest.fn<any>(async (key: string, response: any) => {
|
|
230
237
|
const headers = response.init
|
|
@@ -310,11 +317,8 @@ describe("LiteRT-LM Provider", () => {
|
|
|
310
317
|
}
|
|
311
318
|
});
|
|
312
319
|
|
|
313
|
-
// -- Complete meta hit (after a previous successful download) ---------------
|
|
314
|
-
|
|
315
320
|
it("returns stream from cached chunks when meta is complete (no network)", async () => {
|
|
316
321
|
const chunk0 = new Uint8Array([1, 2, 3, 4]);
|
|
317
|
-
// Populate the cache as if a previous download completed
|
|
318
322
|
cacheStore.set("http://model?__sc_meta=1", {
|
|
319
323
|
body: encodeText(
|
|
320
324
|
JSON.stringify({
|
|
@@ -335,11 +339,9 @@ describe("LiteRT-LM Provider", () => {
|
|
|
335
339
|
const onProgress = jest.fn<any>();
|
|
336
340
|
const stream = await loadLiteRtModelStream("http://model", onProgress);
|
|
337
341
|
|
|
338
|
-
// Should read from cache — no network calls
|
|
339
342
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
340
343
|
expect(onProgress).toHaveBeenCalledWith(4, 4, true);
|
|
341
344
|
|
|
342
|
-
// Stream should yield the cached bytes
|
|
343
345
|
const reader = stream.getReader();
|
|
344
346
|
const collected: number[] = [];
|
|
345
347
|
while (true) {
|
|
@@ -354,10 +356,7 @@ describe("LiteRT-LM Provider", () => {
|
|
|
354
356
|
expect(collected).toEqual([1, 2, 3, 4]);
|
|
355
357
|
});
|
|
356
358
|
|
|
357
|
-
// -- Fresh download (no meta) -----------------------------------------------
|
|
358
|
-
|
|
359
359
|
it("downloads model and writes chunks + meta to CacheStorage", async () => {
|
|
360
|
-
// 3 bytes fits in a single chunk (way below 16MB flush threshold)
|
|
361
360
|
const modelData = new Uint8Array([10, 20, 30]);
|
|
362
361
|
mockFetch.mockResolvedValueOnce(makeFetchResponse(200, modelData) as any);
|
|
363
362
|
|
|
@@ -372,25 +371,19 @@ describe("LiteRT-LM Provider", () => {
|
|
|
372
371
|
if (done) break;
|
|
373
372
|
}
|
|
374
373
|
|
|
375
|
-
// At least one chunk entry should have been written
|
|
376
374
|
const chunkEntry = cacheStore.get("http://model?__sc_chunk=0");
|
|
377
375
|
expect(chunkEntry).toBeDefined();
|
|
378
376
|
|
|
379
|
-
// Meta should exist and be marked complete
|
|
380
377
|
const metaRaw = cacheStore.get("http://model?__sc_meta=1");
|
|
381
378
|
expect(metaRaw).toBeDefined();
|
|
382
379
|
const meta = JSON.parse(decodeBytes(metaRaw!.body!));
|
|
383
380
|
expect(meta.complete).toBe(true);
|
|
384
381
|
expect(meta.received).toBe(3);
|
|
385
382
|
|
|
386
|
-
// Progress callback should have fired
|
|
387
383
|
expect(onProgress).toHaveBeenCalled();
|
|
388
384
|
});
|
|
389
385
|
|
|
390
|
-
// -- Resume after crash -----------------------------------------------------
|
|
391
|
-
|
|
392
386
|
it("resumes download from partial meta offset after crash", async () => {
|
|
393
|
-
// Partial state: 4 bytes already cached in chunk 0, 4 more bytes remain
|
|
394
387
|
const partialBytes = new Uint8Array([1, 2, 3, 4]);
|
|
395
388
|
cacheStore.set("http://model?__sc_meta=1", {
|
|
396
389
|
body: encodeText(
|
|
@@ -409,7 +402,6 @@ describe("LiteRT-LM Provider", () => {
|
|
|
409
402
|
headers: new Headers({ "content-length": "4" }),
|
|
410
403
|
});
|
|
411
404
|
|
|
412
|
-
// Server returns the remaining 4 bytes via 206
|
|
413
405
|
const remaining = new Uint8Array([5, 6, 7, 8]);
|
|
414
406
|
mockFetch.mockResolvedValueOnce({
|
|
415
407
|
status: 206,
|
|
@@ -425,7 +417,6 @@ describe("LiteRT-LM Provider", () => {
|
|
|
425
417
|
const onProgress = jest.fn<any>();
|
|
426
418
|
const stream = await loadLiteRtModelStream("http://model", onProgress);
|
|
427
419
|
|
|
428
|
-
// Must have issued a Range request
|
|
429
420
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
430
421
|
const [, fetchInit] = mockFetch.mock.calls[0] as any[];
|
|
431
422
|
expect(fetchInit.headers["Range"]).toBe("bytes=4-");
|
|
@@ -436,17 +427,13 @@ describe("LiteRT-LM Provider", () => {
|
|
|
436
427
|
if (done) break;
|
|
437
428
|
}
|
|
438
429
|
|
|
439
|
-
// Meta should now be complete
|
|
440
430
|
const metaRaw = cacheStore.get("http://model?__sc_meta=1");
|
|
441
431
|
const meta = JSON.parse(decodeBytes(metaRaw!.body!));
|
|
442
432
|
expect(meta.complete).toBe(true);
|
|
443
433
|
expect(meta.received).toBe(8);
|
|
444
434
|
});
|
|
445
435
|
|
|
446
|
-
// -- Resume when server ignores Range (returns 200) -------------------------
|
|
447
|
-
|
|
448
436
|
it("restarts from scratch when server ignores Range request (200 instead of 206)", async () => {
|
|
449
|
-
// Partial state exists in cache
|
|
450
437
|
cacheStore.set("http://model?__sc_meta=1", {
|
|
451
438
|
body: encodeText(
|
|
452
439
|
JSON.stringify({
|
|
@@ -464,7 +451,6 @@ describe("LiteRT-LM Provider", () => {
|
|
|
464
451
|
headers: new Headers({ "content-length": "4" }),
|
|
465
452
|
});
|
|
466
453
|
|
|
467
|
-
// Server returns 200 (ignores Range) with full data
|
|
468
454
|
const fullData = new Uint8Array([10, 20, 30, 40, 50, 60, 70, 80]);
|
|
469
455
|
mockFetch.mockResolvedValueOnce(makeFetchResponse(200, fullData) as any);
|
|
470
456
|
|
|
@@ -479,15 +465,12 @@ describe("LiteRT-LM Provider", () => {
|
|
|
479
465
|
if (done) break;
|
|
480
466
|
}
|
|
481
467
|
|
|
482
|
-
// Final meta should reflect the full 8 bytes, not 4+4
|
|
483
468
|
const metaRaw = cacheStore.get("http://model?__sc_meta=1");
|
|
484
469
|
const meta = JSON.parse(decodeBytes(metaRaw!.body!));
|
|
485
470
|
expect(meta.complete).toBe(true);
|
|
486
471
|
expect(meta.received).toBe(8);
|
|
487
472
|
});
|
|
488
473
|
|
|
489
|
-
// -- Retry on 500 -----------------------------------------------------------
|
|
490
|
-
|
|
491
474
|
it("retries on 500 error", async () => {
|
|
492
475
|
const modelData = new Uint8Array([1, 2, 3]);
|
|
493
476
|
mockFetch
|
|
@@ -511,8 +494,6 @@ describe("LiteRT-LM Provider", () => {
|
|
|
511
494
|
expect(meta.complete).toBe(true);
|
|
512
495
|
});
|
|
513
496
|
|
|
514
|
-
// -- Abort ------------------------------------------------------------------
|
|
515
|
-
|
|
516
497
|
it("aborts download if signal is aborted", async () => {
|
|
517
498
|
mockFetch.mockRejectedValue(
|
|
518
499
|
new DOMException("Aborted", "AbortError") as any,
|
|
@@ -530,6 +511,188 @@ describe("LiteRT-LM Provider", () => {
|
|
|
530
511
|
).rejects.toThrow("Aborted");
|
|
531
512
|
});
|
|
532
513
|
});
|
|
514
|
+
|
|
515
|
+
describe("invokeWithLiteRtLm", () => {
|
|
516
|
+
it("emits error when LiteRT is unsupported in current environment", async () => {
|
|
517
|
+
const emit: any = jest.fn();
|
|
518
|
+
await invokeWithLiteRtLm(
|
|
519
|
+
{} as any,
|
|
520
|
+
"g1",
|
|
521
|
+
"system",
|
|
522
|
+
[{ role: "user", content: "hello" }],
|
|
523
|
+
1000,
|
|
524
|
+
emit,
|
|
525
|
+
undefined,
|
|
526
|
+
"litert-community/gemma-4-E2B-it-litert-lm",
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
expect(emit).toHaveBeenCalledWith({
|
|
530
|
+
type: "response",
|
|
531
|
+
payload: {
|
|
532
|
+
groupId: "g1",
|
|
533
|
+
text: expect.stringContaining(
|
|
534
|
+
"LiteRT-LM requires WebGPU and WebAssembly.Suspending",
|
|
535
|
+
),
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("handles model failure when supported but initialization fails", async () => {
|
|
541
|
+
Object.defineProperty(global, "navigator", {
|
|
542
|
+
value: { gpu: {} },
|
|
543
|
+
configurable: true,
|
|
544
|
+
writable: true,
|
|
545
|
+
});
|
|
546
|
+
Object.defineProperty(global, "WebAssembly", {
|
|
547
|
+
value: { Suspending: class {} },
|
|
548
|
+
configurable: true,
|
|
549
|
+
writable: true,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const emitted: any[] = [];
|
|
553
|
+
const emit = jest.fn((msg: any) => {
|
|
554
|
+
emitted.push(msg);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Pass an unsupported model ID
|
|
558
|
+
await invokeWithLiteRtLm(
|
|
559
|
+
{} as any,
|
|
560
|
+
"g1",
|
|
561
|
+
"system",
|
|
562
|
+
[{ role: "user", content: "hello" }],
|
|
563
|
+
1000,
|
|
564
|
+
emit,
|
|
565
|
+
undefined,
|
|
566
|
+
"unsupported-model-id",
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
expect(
|
|
570
|
+
emitted.some(
|
|
571
|
+
(m) =>
|
|
572
|
+
m.type === "model-download-progress" &&
|
|
573
|
+
m.payload.status === "error",
|
|
574
|
+
),
|
|
575
|
+
).toBe(true);
|
|
576
|
+
expect(
|
|
577
|
+
emitted.some(
|
|
578
|
+
(m) =>
|
|
579
|
+
m.type === "response" &&
|
|
580
|
+
m.payload.text.includes("LiteRT-LM failed to initialize"),
|
|
581
|
+
),
|
|
582
|
+
).toBe(true);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it("handles aborted signal gracefully during model download", async () => {
|
|
586
|
+
Object.defineProperty(global, "navigator", {
|
|
587
|
+
value: { gpu: {} },
|
|
588
|
+
configurable: true,
|
|
589
|
+
writable: true,
|
|
590
|
+
});
|
|
591
|
+
Object.defineProperty(global, "WebAssembly", {
|
|
592
|
+
value: { Suspending: class {} },
|
|
593
|
+
configurable: true,
|
|
594
|
+
writable: true,
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const abortController = new AbortController();
|
|
598
|
+
abortController.abort();
|
|
599
|
+
|
|
600
|
+
const emitted: any[] = [];
|
|
601
|
+
const emit = jest.fn((msg: any) => {
|
|
602
|
+
emitted.push(msg);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
await invokeWithLiteRtLm(
|
|
606
|
+
{} as any,
|
|
607
|
+
"g1",
|
|
608
|
+
"system",
|
|
609
|
+
[{ role: "user", content: "hello" }],
|
|
610
|
+
1000,
|
|
611
|
+
emit,
|
|
612
|
+
abortController.signal,
|
|
613
|
+
"litert-community/gemma-4-E2B-it-litert-lm",
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
expect(
|
|
617
|
+
emitted.some(
|
|
618
|
+
(m) =>
|
|
619
|
+
m.type === "model-download-progress" &&
|
|
620
|
+
m.payload.status === "error",
|
|
621
|
+
),
|
|
622
|
+
).toBe(true);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("emits warning response when LiteRT-LM is not supported in browser", async () => {
|
|
626
|
+
Object.defineProperty(global, "navigator", {
|
|
627
|
+
value: {},
|
|
628
|
+
configurable: true,
|
|
629
|
+
writable: true,
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
const emitted: any[] = [];
|
|
633
|
+
const emit = jest.fn((msg: any) => {
|
|
634
|
+
emitted.push(msg);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
await invokeWithLiteRtLm(
|
|
638
|
+
{} as any,
|
|
639
|
+
"g1",
|
|
640
|
+
"system",
|
|
641
|
+
[{ role: "user", content: "hello" }],
|
|
642
|
+
1000,
|
|
643
|
+
emit,
|
|
644
|
+
undefined,
|
|
645
|
+
"litert-community/gemma-4-E2B-it-litert-lm",
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
expect(
|
|
649
|
+
emitted.some(
|
|
650
|
+
(m) =>
|
|
651
|
+
m.type === "response" &&
|
|
652
|
+
m.payload.text.includes("LiteRT-LM requires WebGPU"),
|
|
653
|
+
),
|
|
654
|
+
).toBe(true);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it("successfully creates conversation and streams response when supported", async () => {
|
|
658
|
+
Object.defineProperty(global, "navigator", {
|
|
659
|
+
value: { gpu: {} },
|
|
660
|
+
configurable: true,
|
|
661
|
+
writable: true,
|
|
662
|
+
});
|
|
663
|
+
Object.defineProperty(global, "WebAssembly", {
|
|
664
|
+
value: { Suspending: class {} },
|
|
665
|
+
configurable: true,
|
|
666
|
+
writable: true,
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
global.fetch = jest.fn<any>().mockResolvedValue({
|
|
670
|
+
ok: true,
|
|
671
|
+
status: 200,
|
|
672
|
+
headers: new Headers({ "content-length": "100" }),
|
|
673
|
+
body: makeBodyStream(new Uint8Array(100)),
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const emitted: any[] = [];
|
|
677
|
+
const emit = jest.fn((msg: any) => {
|
|
678
|
+
emitted.push(msg);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
await invokeWithLiteRtLm(
|
|
682
|
+
{} as any,
|
|
683
|
+
"g1",
|
|
684
|
+
"system",
|
|
685
|
+
[{ role: "user", content: "hello" }],
|
|
686
|
+
1000,
|
|
687
|
+
emit,
|
|
688
|
+
undefined,
|
|
689
|
+
"litert-community/gemma-4-E2B-it-litert-lm",
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
expect(emitted.some((m) => m.type === "streaming-chunk")).toBe(true);
|
|
693
|
+
expect(emitted.some((m) => m.type === "streaming-done")).toBe(true);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
533
696
|
});
|
|
534
697
|
|
|
535
698
|
// ---------------------------------------------------------------------------
|
|
@@ -547,7 +710,6 @@ describe("parseModelSpecificToolCall", () => {
|
|
|
547
710
|
});
|
|
548
711
|
|
|
549
712
|
it('parses a Gemma 4 native tool call with encoded quote tokens <|"|">', () => {
|
|
550
|
-
// Standard inline output
|
|
551
713
|
const rawInline =
|
|
552
714
|
'<|tool_call>call:web_search{queries:[<|"|>Blueberries latest details<|"|>]}<tool_call|>';
|
|
553
715
|
const resultInline = parseModelSpecificToolCall(rawInline);
|
|
@@ -555,7 +717,6 @@ describe("parseModelSpecificToolCall", () => {
|
|
|
555
717
|
expect(resultInline!.name).toBe("web_search");
|
|
556
718
|
expect(resultInline!.input.queries[0]).toBe("Blueberries latest details");
|
|
557
719
|
|
|
558
|
-
// Real-world variant: The model frequently injects a newline before the closing tag
|
|
559
720
|
const rawWithNewline =
|
|
560
721
|
'<|tool_call>call:web_search{queries:[<|"|>Blueberries latest details<|"|>]}\n<tool_call|>';
|
|
561
722
|
const resultWithNewline = parseModelSpecificToolCall(rawWithNewline);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
1
2
|
import { modelRegistry } from "./model-registry.js";
|
|
2
3
|
|
|
3
4
|
describe("ModelRegistry", () => {
|
|
@@ -259,4 +260,118 @@ describe("ModelRegistry", () => {
|
|
|
259
260
|
(globalThis as any).fetch = originalFetch;
|
|
260
261
|
}
|
|
261
262
|
});
|
|
263
|
+
|
|
264
|
+
it("should clear registered models", () => {
|
|
265
|
+
modelRegistry.registerModelInfo("model-to-clear", {
|
|
266
|
+
contextWindow: 4096,
|
|
267
|
+
maxOutput: 1024,
|
|
268
|
+
});
|
|
269
|
+
expect(modelRegistry.getModelInfo("model-to-clear")).not.toBeNull();
|
|
270
|
+
modelRegistry.clear();
|
|
271
|
+
expect(modelRegistry.getModelInfo("model-to-clear")).toBeNull();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should do nothing when provider has no modelsUrl", async () => {
|
|
275
|
+
await expect(
|
|
276
|
+
modelRegistry.fetchModelInfo({ id: "no-url" } as any),
|
|
277
|
+
).resolves.toBeUndefined();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should handle apiKeyHeader, apiKeyHeaderFormat, headers, and extraHeaders", async () => {
|
|
281
|
+
const originalFetch = (globalThis as any).fetch;
|
|
282
|
+
let sentHeaders: any = null;
|
|
283
|
+
|
|
284
|
+
(globalThis as any).fetch = async (_url: string, opts: any) => {
|
|
285
|
+
sentHeaders = opts.headers;
|
|
286
|
+
return {
|
|
287
|
+
ok: true,
|
|
288
|
+
json: async () => ({
|
|
289
|
+
data: [{ id: "test-auth-model", context_length: 16000 }],
|
|
290
|
+
}),
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
await modelRegistry.fetchModelInfo(
|
|
296
|
+
{
|
|
297
|
+
id: "hf-provider",
|
|
298
|
+
modelsUrl: "https://api.hf.co/models",
|
|
299
|
+
headers: { "X-Custom": "CustomVal" },
|
|
300
|
+
apiKeyHeader: "Authorization",
|
|
301
|
+
apiKeyHeaderFormat: "Bearer {key}",
|
|
302
|
+
} as any,
|
|
303
|
+
"secret-key",
|
|
304
|
+
{ "X-Extra": "ExtraVal" },
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
expect(sentHeaders.get("Authorization")).toBe("Bearer secret-key");
|
|
308
|
+
expect(sentHeaders.get("X-Custom")).toBe("CustomVal");
|
|
309
|
+
expect(sentHeaders.get("X-Extra")).toBe("ExtraVal");
|
|
310
|
+
expect(modelRegistry.getModelInfo("test-auth-model")).toEqual({
|
|
311
|
+
contextWindow: 16000,
|
|
312
|
+
maxOutput: null,
|
|
313
|
+
});
|
|
314
|
+
} finally {
|
|
315
|
+
(globalThis as any).fetch = originalFetch;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("should handle error when fetch response is not ok", async () => {
|
|
320
|
+
const originalFetch = (globalThis as any).fetch;
|
|
321
|
+
const consoleSpy = jest
|
|
322
|
+
.spyOn(console, "error")
|
|
323
|
+
.mockImplementation(() => {});
|
|
324
|
+
(globalThis as any).fetch = async () => ({
|
|
325
|
+
ok: false,
|
|
326
|
+
status: 403,
|
|
327
|
+
statusText: "Forbidden",
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
await modelRegistry.fetchModelInfo({
|
|
332
|
+
id: "fail-provider",
|
|
333
|
+
modelsUrl: "https://fail.com/models",
|
|
334
|
+
} as any);
|
|
335
|
+
|
|
336
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
337
|
+
"[ModelRegistry] Error fetching models for fail-provider:",
|
|
338
|
+
expect.any(Error),
|
|
339
|
+
);
|
|
340
|
+
expect(modelRegistry.loading).toBe(false);
|
|
341
|
+
} finally {
|
|
342
|
+
(globalThis as any).fetch = originalFetch;
|
|
343
|
+
consoleSpy.mockRestore();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("should parse text->image architecture modality string", async () => {
|
|
348
|
+
const originalFetch = (globalThis as any).fetch;
|
|
349
|
+
(globalThis as any).fetch = async () => ({
|
|
350
|
+
ok: true,
|
|
351
|
+
json: async () => ({
|
|
352
|
+
data: [
|
|
353
|
+
{
|
|
354
|
+
id: "vision-model",
|
|
355
|
+
context_length: 32000,
|
|
356
|
+
architecture: {
|
|
357
|
+
modality: "text+image->text",
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
}),
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
await modelRegistry.fetchModelInfo({
|
|
366
|
+
id: "vision-provider",
|
|
367
|
+
modelsUrl: "https://vision.com/models",
|
|
368
|
+
} as any);
|
|
369
|
+
|
|
370
|
+
expect(
|
|
371
|
+
modelRegistry.getModelInfo("vision-model")?.supportsImageInput,
|
|
372
|
+
).toBe(true);
|
|
373
|
+
} finally {
|
|
374
|
+
(globalThis as any).fetch = originalFetch;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
262
377
|
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
formatRequest,
|
|
6
6
|
getAdapter,
|
|
7
7
|
getContextLimit,
|
|
8
|
+
normalizeMeshLlmResult,
|
|
8
9
|
parseResponse,
|
|
9
10
|
} from "./providers.js";
|
|
10
11
|
|
|
@@ -64,10 +65,47 @@ describe("providers.js", () => {
|
|
|
64
65
|
it("should return correct limit for Claude 3 models", () => {
|
|
65
66
|
expect(getContextLimit("claude-3-opus")).toBe(200000);
|
|
66
67
|
expect(getContextLimit("claude-3-haiku")).toBe(200000);
|
|
68
|
+
expect(getContextLimit("claude-opus-4")).toBe(1000000);
|
|
67
69
|
});
|
|
68
70
|
|
|
69
|
-
it("should return correct limit for GPT-4", () => {
|
|
71
|
+
it("should return correct limit for GPT-4 and reasoning models", () => {
|
|
70
72
|
expect(getContextLimit("gpt-4")).toBe(8192);
|
|
73
|
+
expect(getContextLimit("o1-preview")).toBe(200000);
|
|
74
|
+
expect(getContextLimit("o3-mini")).toBe(200000);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should return correct limit for Gemini models", () => {
|
|
78
|
+
expect(getContextLimit("gemini-1.5-pro")).toBe(2097152);
|
|
79
|
+
expect(getContextLimit("gemini-2.0-flash")).toBe(1048576);
|
|
80
|
+
expect(getContextLimit("gemini-1.0-pro")).toBe(32768);
|
|
81
|
+
expect(getContextLimit("gemini-nano")).toBe(4096);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should return correct limit for Llama, Gemma, and Mistral models", () => {
|
|
85
|
+
expect(getContextLimit("llama-3.1-70b")).toBe(128000);
|
|
86
|
+
expect(getContextLimit("llama-3-8b")).toBe(8192);
|
|
87
|
+
expect(getContextLimit("gemma-4-12b")).toBe(256000);
|
|
88
|
+
expect(getContextLimit("gemma-4-e2b")).toBe(128000);
|
|
89
|
+
expect(getContextLimit("gemma-3-12b")).toBe(128000);
|
|
90
|
+
expect(getContextLimit("gemma-3-1b")).toBe(32000);
|
|
91
|
+
expect(getContextLimit("gemma-2-9b")).toBe(8192);
|
|
92
|
+
expect(getContextLimit("mistral-large")).toBe(128000);
|
|
93
|
+
expect(getContextLimit("mistral-small")).toBe(32000);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should return correct limit for Bedrock, Phi, Qwen, and Mesh models", () => {
|
|
97
|
+
expect(getContextLimit("amazon.nova-micro")).toBe(128000);
|
|
98
|
+
expect(getContextLimit("amazon.nova-pro")).toBe(300000);
|
|
99
|
+
expect(getContextLimit("amazon.titan-text")).toBe(8192);
|
|
100
|
+
expect(getContextLimit("cohere.command-r")).toBe(128000);
|
|
101
|
+
expect(getContextLimit("phi-4-mini")).toBe(128000);
|
|
102
|
+
expect(getContextLimit("phi-4")).toBe(16384);
|
|
103
|
+
expect(getContextLimit("phi-3.5")).toBe(128000);
|
|
104
|
+
expect(getContextLimit("phi-3")).toBe(4096);
|
|
105
|
+
expect(getContextLimit("qwen3.5-9b")).toBe(262144);
|
|
106
|
+
expect(getContextLimit("qwen3-0.6b")).toBe(40960);
|
|
107
|
+
expect(getContextLimit("qwen3-8b")).toBe(128000);
|
|
108
|
+
expect(getContextLimit("mesh")).toBe(242144);
|
|
71
109
|
});
|
|
72
110
|
|
|
73
111
|
it("should return default limit for unknown models", () => {
|
|
@@ -1157,4 +1195,59 @@ describe("providers.js", () => {
|
|
|
1157
1195
|
expect(headers["X-Token"]).toBe("Token abc123");
|
|
1158
1196
|
});
|
|
1159
1197
|
});
|
|
1198
|
+
|
|
1199
|
+
describe("normalizeMeshLlmResult & MeshLlmAdapter", () => {
|
|
1200
|
+
it("returns unchanged for empty or invalid results", () => {
|
|
1201
|
+
expect(normalizeMeshLlmResult(null)).toBeNull();
|
|
1202
|
+
expect(normalizeMeshLlmResult(undefined)).toBeUndefined();
|
|
1203
|
+
expect(normalizeMeshLlmResult({ content: "not-an-array" })).toEqual({
|
|
1204
|
+
content: "not-an-array",
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
it("normalizes mesh-llm result stripping thought tags and parsing tool calls", () => {
|
|
1209
|
+
const result = {
|
|
1210
|
+
content: [
|
|
1211
|
+
{
|
|
1212
|
+
type: "text",
|
|
1213
|
+
text: '<|channel>Thinking deeply...<channel|>Hello user!<|tool_call>call:search{"q":"test"}<tool_call|>',
|
|
1214
|
+
},
|
|
1215
|
+
{ type: "image", data: "123" },
|
|
1216
|
+
],
|
|
1217
|
+
stop_reason: "end_turn",
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
const normalized = normalizeMeshLlmResult(result);
|
|
1221
|
+
expect(normalized.content).toEqual([
|
|
1222
|
+
{ type: "text", text: "Hello user!" },
|
|
1223
|
+
expect.objectContaining({
|
|
1224
|
+
type: "tool_use",
|
|
1225
|
+
name: "search",
|
|
1226
|
+
input: { q: "test" },
|
|
1227
|
+
}),
|
|
1228
|
+
{ type: "image", data: "123" },
|
|
1229
|
+
]);
|
|
1230
|
+
expect(normalized.stop_reason).toBe("tool_use");
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
it("returns MeshLlmAdapter for mesh-llm provider format", () => {
|
|
1234
|
+
const provider: any = { format: "mesh-llm" };
|
|
1235
|
+
const adapter = getAdapter(provider);
|
|
1236
|
+
expect(adapter.constructor.name).toBe("MeshLlmAdapter");
|
|
1237
|
+
|
|
1238
|
+
const response = {
|
|
1239
|
+
choices: [
|
|
1240
|
+
{
|
|
1241
|
+
message: {
|
|
1242
|
+
content: "<|channel>reasoning<channel|>Result text",
|
|
1243
|
+
},
|
|
1244
|
+
finish_reason: "stop",
|
|
1245
|
+
},
|
|
1246
|
+
],
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
const parsed = parseResponse(provider, response);
|
|
1250
|
+
expect(parsed.content).toEqual([{ type: "text", text: "Result text" }]);
|
|
1251
|
+
});
|
|
1252
|
+
});
|
|
1160
1253
|
});
|