@thevinci/web 1.0.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 +197 -0
- package/bin/cli-entry.js +55 -0
- package/bin/cli-output.js +145 -0
- package/bin/cli.js +4887 -0
- package/bin/cli.test.js +64 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +528 -0
- package/dist/assets/JsonTreeView-CSm9OzXG.js +1 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/MarkdownRendererImpl-DensKOLc.js +6 -0
- package/dist/assets/MultiRunWindow-Bo7THayo.js +1 -0
- package/dist/assets/OnboardingScreen-BDqmzTVR.js +2 -0
- package/dist/assets/SettingsWindow-coz__Ykw.js +1 -0
- package/dist/assets/TerminalView-DrZ-i3Dr.js +1 -0
- package/dist/assets/ToolOutputDialog-Eglzslt3.js +16 -0
- package/dist/assets/es-4o9ciP61.js +15 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/assets/index-DLTDToSP.css +1 -0
- package/dist/assets/index-DgiFEKGN.js +1 -0
- package/dist/assets/ko-B20imCHE.js +15 -0
- package/dist/assets/main-BV3KOtdA.css +1 -0
- package/dist/assets/main-CDKJj0sH.js +226 -0
- package/dist/assets/main-LC-PSNVM.js +2 -0
- package/dist/assets/miniChat-CQUiG_cr.js +2 -0
- package/dist/assets/modelPrefsAutoSave-Dm799vzR.js +6986 -0
- package/dist/assets/pl-DQJ7LSzj.js +15 -0
- package/dist/assets/pt-BR-OmjHUz9y.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-CARbeW0G.js +2 -0
- package/dist/assets/uk-BNFxOlO4.js +15 -0
- package/dist/assets/vendor--DBfsbEis.css +1 -0
- package/dist/assets/vendor-.bun-B9l0ZNi2.js +4094 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasmSttWorker-Dtlxac_K.js +1 -0
- package/dist/assets/wasmSttWorker-oo7Dm_jy.js +1806 -0
- package/dist/assets/worker-CbT6TVo7.js +155 -0
- package/dist/assets/zh-CN-C6T-Ac7F.js +15 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +528 -0
- package/dist/index.html +607 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.png +0 -0
- package/dist/logo-dark-512x512.svg +528 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.png +0 -0
- package/dist/logo-light-512x512.svg +528 -0
- package/dist/mini-chat.html +16 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +21 -0
- package/dist/sw.js +1 -0
- package/package.json +118 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +528 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +528 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.png +0 -0
- package/public/logo-dark-512x512.svg +528 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.png +0 -0
- package/public/logo-light-512x512.svg +528 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +21 -0
- package/server/TERMINAL_WS_PROTOCOL.md +48 -0
- package/server/index.d.ts +39 -0
- package/server/index.js +1311 -0
- package/server/lib/cloudflare-tunnel.js +650 -0
- package/server/lib/event-stream/DOCUMENTATION.md +61 -0
- package/server/lib/event-stream/directory-ws-bridge.js +185 -0
- package/server/lib/event-stream/global-hub.js +158 -0
- package/server/lib/event-stream/global-hub.test.js +140 -0
- package/server/lib/event-stream/global-ws-bridge.js +206 -0
- package/server/lib/event-stream/index.js +25 -0
- package/server/lib/event-stream/protocol.js +131 -0
- package/server/lib/event-stream/protocol.test.js +182 -0
- package/server/lib/event-stream/runtime.js +180 -0
- package/server/lib/event-stream/runtime.test.js +512 -0
- package/server/lib/event-stream/upstream-reader.js +226 -0
- package/server/lib/event-stream/upstream-reader.test.js +276 -0
- package/server/lib/fs/DOCUMENTATION.md +36 -0
- package/server/lib/fs/routes.js +1040 -0
- package/server/lib/fs/search.js +238 -0
- package/server/lib/git/DOCUMENTATION.md +152 -0
- package/server/lib/git/credentials.js +74 -0
- package/server/lib/git/identity-storage.js +112 -0
- package/server/lib/git/index.js +6 -0
- package/server/lib/git/routes.js +972 -0
- package/server/lib/git/service.js +3432 -0
- package/server/lib/git/service.test.js +39 -0
- package/server/lib/github/DOCUMENTATION.md +171 -0
- package/server/lib/github/auth.js +307 -0
- package/server/lib/github/device-flow.js +50 -0
- package/server/lib/github/index.js +24 -0
- package/server/lib/github/octokit.js +10 -0
- package/server/lib/github/pr-status.js +519 -0
- package/server/lib/github/repo/fork-detection.js +102 -0
- package/server/lib/github/repo/index.js +55 -0
- package/server/lib/github/routes.js +1560 -0
- package/server/lib/magic-prompts/routes.js +63 -0
- package/server/lib/magic-prompts/runtime.js +119 -0
- package/server/lib/notifications/DOCUMENTATION.md +122 -0
- package/server/lib/notifications/emitter-runtime.js +102 -0
- package/server/lib/notifications/index.js +4 -0
- package/server/lib/notifications/message.js +52 -0
- package/server/lib/notifications/message.test.js +34 -0
- package/server/lib/notifications/push-runtime.js +304 -0
- package/server/lib/notifications/routes.js +315 -0
- package/server/lib/notifications/runtime.js +566 -0
- package/server/lib/notifications/template-runtime.js +349 -0
- package/server/lib/notifications/template-runtime.test.js +26 -0
- package/server/lib/opencode/DOCUMENTATION.md +362 -0
- package/server/lib/opencode/agents.js +634 -0
- package/server/lib/opencode/auth-state-runtime.js +88 -0
- package/server/lib/opencode/auth.js +83 -0
- package/server/lib/opencode/bootstrap-runtime.js +131 -0
- package/server/lib/opencode/cli-entry-runtime.js +43 -0
- package/server/lib/opencode/cli-options.js +128 -0
- package/server/lib/opencode/commands.js +339 -0
- package/server/lib/opencode/config-entity-routes.js +370 -0
- package/server/lib/opencode/core-routes.js +500 -0
- package/server/lib/opencode/core-routes.test.js +26 -0
- package/server/lib/opencode/env-config.js +74 -0
- package/server/lib/opencode/env-keys.js +68 -0
- package/server/lib/opencode/env-runtime.js +1162 -0
- package/server/lib/opencode/env-runtime.test.js +116 -0
- package/server/lib/opencode/feature-routes-runtime.js +244 -0
- package/server/lib/opencode/hmr-state-runtime.js +85 -0
- package/server/lib/opencode/index.js +66 -0
- package/server/lib/opencode/lifecycle.js +1019 -0
- package/server/lib/opencode/lifecycle.test.js +240 -0
- package/server/lib/opencode/mcp.js +278 -0
- package/server/lib/opencode/network-runtime.js +104 -0
- package/server/lib/opencode/network-runtime.test.js +37 -0
- package/server/lib/opencode/opencode-resolution-runtime.js +71 -0
- package/server/lib/opencode/path-utils.js +100 -0
- package/server/lib/opencode/path-utils.test.js +71 -0
- package/server/lib/opencode/project-directory-runtime.js +124 -0
- package/server/lib/opencode/project-icon-routes.js +399 -0
- package/server/lib/opencode/project-icon-routes.test.js +107 -0
- package/server/lib/opencode/providers.js +96 -0
- package/server/lib/opencode/proxy.js +445 -0
- package/server/lib/opencode/pwa-manifest-routes.js +257 -0
- package/server/lib/opencode/pwa-manifest-routes.test.js +133 -0
- package/server/lib/opencode/routes.js +541 -0
- package/server/lib/opencode/server-startup-runtime.js +156 -0
- package/server/lib/opencode/server-utils-runtime.js +168 -0
- package/server/lib/opencode/server-utils-runtime.test.js +135 -0
- package/server/lib/opencode/session-runtime.js +356 -0
- package/server/lib/opencode/session-runtime.test.js +151 -0
- package/server/lib/opencode/settings-helpers.js +770 -0
- package/server/lib/opencode/settings-helpers.test.js +109 -0
- package/server/lib/opencode/settings-normalization-runtime.js +428 -0
- package/server/lib/opencode/settings-runtime.js +826 -0
- package/server/lib/opencode/settings-runtime.test.js +85 -0
- package/server/lib/opencode/shared.js +615 -0
- package/server/lib/opencode/shutdown-runtime.js +139 -0
- package/server/lib/opencode/shutdown-runtime.test.js +58 -0
- package/server/lib/opencode/skill-routes.js +701 -0
- package/server/lib/opencode/skills.js +548 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +130 -0
- package/server/lib/opencode/static-routes-runtime.js +65 -0
- package/server/lib/opencode/theme-runtime.js +167 -0
- package/server/lib/opencode/tunnel-auth.js +591 -0
- package/server/lib/opencode/tunnel-wiring-runtime.js +94 -0
- package/server/lib/opencode/vinci-routes.js +76 -0
- package/server/lib/opencode/watcher.js +115 -0
- package/server/lib/opencode/watcher.test.js +239 -0
- package/server/lib/preview/proxy-runtime.js +1333 -0
- package/server/lib/preview/proxy-runtime.test.js +144 -0
- package/server/lib/projects/project-config.js +567 -0
- package/server/lib/projects/project-config.test.js +175 -0
- package/server/lib/projects/project-id.js +13 -0
- package/server/lib/quota/DOCUMENTATION.md +58 -0
- package/server/lib/quota/index.js +25 -0
- package/server/lib/quota/providers/claude.js +107 -0
- package/server/lib/quota/providers/codex.js +113 -0
- package/server/lib/quota/providers/copilot.js +165 -0
- package/server/lib/quota/providers/google/api.js +92 -0
- package/server/lib/quota/providers/google/auth.js +108 -0
- package/server/lib/quota/providers/google/index.js +124 -0
- package/server/lib/quota/providers/google/transforms.js +109 -0
- package/server/lib/quota/providers/index.js +168 -0
- package/server/lib/quota/providers/interface.js +55 -0
- package/server/lib/quota/providers/kimi.js +108 -0
- package/server/lib/quota/providers/minimax-cn-coding-plan.js +140 -0
- package/server/lib/quota/providers/minimax-coding-plan.js +139 -0
- package/server/lib/quota/providers/nanogpt.js +124 -0
- package/server/lib/quota/providers/ollama-cloud.js +112 -0
- package/server/lib/quota/providers/openai.js +91 -0
- package/server/lib/quota/providers/openrouter.js +92 -0
- package/server/lib/quota/providers/zai.js +91 -0
- package/server/lib/quota/providers/zhipuai-coding-plan.js +133 -0
- package/server/lib/quota/providers/zhipuai.js +114 -0
- package/server/lib/quota/routes.js +27 -0
- package/server/lib/quota/utils/auth.js +50 -0
- package/server/lib/quota/utils/formatters.js +85 -0
- package/server/lib/quota/utils/formatters.test.js +54 -0
- package/server/lib/quota/utils/index.js +10 -0
- package/server/lib/quota/utils/transformers.js +55 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +44 -0
- package/server/lib/scheduled-tasks/routes.js +235 -0
- package/server/lib/scheduled-tasks/runtime.js +773 -0
- package/server/lib/scheduled-tasks/runtime.test.js +100 -0
- package/server/lib/security/request-security.js +115 -0
- package/server/lib/session-folders/routes.js +63 -0
- package/server/lib/session-folders/routes.test.js +102 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
- package/server/lib/skills-catalog/cache.js +29 -0
- package/server/lib/skills-catalog/clawdhub/api.js +158 -0
- package/server/lib/skills-catalog/clawdhub/index.js +30 -0
- package/server/lib/skills-catalog/clawdhub/install.js +238 -0
- package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
- package/server/lib/skills-catalog/curated-sources.js +21 -0
- package/server/lib/skills-catalog/git.js +77 -0
- package/server/lib/skills-catalog/index.js +42 -0
- package/server/lib/skills-catalog/install.js +294 -0
- package/server/lib/skills-catalog/scan.js +221 -0
- package/server/lib/skills-catalog/source.js +87 -0
- package/server/lib/terminal/DOCUMENTATION.md +76 -0
- package/server/lib/terminal/index.js +31 -0
- package/server/lib/terminal/output-replay-buffer.js +78 -0
- package/server/lib/terminal/output-replay-buffer.test.js +75 -0
- package/server/lib/terminal/runtime.js +850 -0
- package/server/lib/terminal/runtime.test.js +96 -0
- package/server/lib/terminal/terminal-ws-protocol.js +68 -0
- package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
- package/server/lib/text/DOCUMENTATION.md +35 -0
- package/server/lib/text/summarization.js +138 -0
- package/server/lib/text/summarization.test.js +34 -0
- package/server/lib/tts/DOCUMENTATION.md +146 -0
- package/server/lib/tts/base-url.js +62 -0
- package/server/lib/tts/capability-runtime.js +31 -0
- package/server/lib/tts/index.js +19 -0
- package/server/lib/tts/routes.js +261 -0
- package/server/lib/tts/routes.test.js +53 -0
- package/server/lib/tts/service.js +178 -0
- package/server/lib/tts/stt.js +75 -0
- package/server/lib/tunnels/DOCUMENTATION.md +18 -0
- package/server/lib/tunnels/index.js +166 -0
- package/server/lib/tunnels/managed-config.js +201 -0
- package/server/lib/tunnels/providers/cloudflare.js +260 -0
- package/server/lib/tunnels/registry.js +51 -0
- package/server/lib/tunnels/routes.js +605 -0
- package/server/lib/tunnels/types.js +219 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +38 -0
- package/server/lib/ui-auth/ui-auth.js +673 -0
- package/server/lib/ui-auth/ui-passkeys.js +545 -0
- package/server/opencode-proxy.test.js +151 -0
- package/server/proxy-headers.js +61 -0
- package/server/proxy-headers.test.js +58 -0
- package/server/sse-routes.test.js +152 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { mkdtemp, rm, readFile, writeFile } from 'fs/promises';
|
|
5
|
+
import { createProjectConfigRuntime } from './project-config.js';
|
|
6
|
+
|
|
7
|
+
const createRuntime = async () => {
|
|
8
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'oc-scheduled-project-config-'));
|
|
9
|
+
const runtime = createProjectConfigRuntime({
|
|
10
|
+
fsPromises: await import('fs/promises'),
|
|
11
|
+
path,
|
|
12
|
+
projectsDirPath: tempRoot,
|
|
13
|
+
createTaskID: () => 'task-fixed-id',
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
runtime,
|
|
17
|
+
cleanup: async () => {
|
|
18
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('project-config runtime', () => {
|
|
24
|
+
it('creates and persists a scheduled task', async () => {
|
|
25
|
+
const { runtime, cleanup } = await createRuntime();
|
|
26
|
+
try {
|
|
27
|
+
const result = await runtime.upsertScheduledTask('project-test', {
|
|
28
|
+
name: 'Nightly digest',
|
|
29
|
+
enabled: true,
|
|
30
|
+
schedule: {
|
|
31
|
+
kind: 'daily',
|
|
32
|
+
time: '09:30',
|
|
33
|
+
timezone: 'UTC',
|
|
34
|
+
},
|
|
35
|
+
execution: {
|
|
36
|
+
prompt: 'Summarize repository changes',
|
|
37
|
+
providerID: 'openai',
|
|
38
|
+
modelID: 'gpt-4.1',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(result.created).toBe(true);
|
|
43
|
+
expect(result.task.id).toBe('task-fixed-id');
|
|
44
|
+
const reloaded = await runtime.listScheduledTasks('project-test');
|
|
45
|
+
expect(reloaded).toHaveLength(1);
|
|
46
|
+
expect(reloaded[0].name).toBe('Nightly digest');
|
|
47
|
+
expect(reloaded[0].schedule.timezone).toBe('UTC');
|
|
48
|
+
expect(reloaded[0].schedule.times).toEqual(['09:30']);
|
|
49
|
+
} finally {
|
|
50
|
+
await cleanup();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('rejects invalid cron expressions', async () => {
|
|
55
|
+
const { runtime, cleanup } = await createRuntime();
|
|
56
|
+
try {
|
|
57
|
+
await expect(runtime.upsertScheduledTask('project-test', {
|
|
58
|
+
name: 'Invalid cron task',
|
|
59
|
+
enabled: true,
|
|
60
|
+
schedule: {
|
|
61
|
+
kind: 'cron',
|
|
62
|
+
cron: 'invalid cron',
|
|
63
|
+
timezone: 'UTC',
|
|
64
|
+
},
|
|
65
|
+
execution: {
|
|
66
|
+
prompt: 'Run checks',
|
|
67
|
+
providerID: 'openai',
|
|
68
|
+
modelID: 'gpt-4.1',
|
|
69
|
+
},
|
|
70
|
+
})).rejects.toThrow('schedule.cron is invalid');
|
|
71
|
+
} finally {
|
|
72
|
+
await cleanup();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('preserves unknown project config keys when writing scheduled tasks', async () => {
|
|
77
|
+
const { runtime, cleanup } = await createRuntime();
|
|
78
|
+
try {
|
|
79
|
+
const projectID = 'path_preserve';
|
|
80
|
+
const filePath = path.join(runtime.resolveProjectConfigPath(projectID));
|
|
81
|
+
await writeFile(
|
|
82
|
+
filePath,
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
projectNotes: 'hello notes',
|
|
85
|
+
projectTodos: [{ id: 't1', text: 'buy milk', completed: false, createdAt: 1 }],
|
|
86
|
+
projectActions: [{ id: 'a1', name: 'Run', command: 'bun run dev' }],
|
|
87
|
+
projectActionsPrimaryId: 'a1',
|
|
88
|
+
'setup-worktree': ['bun install'],
|
|
89
|
+
projectPlanFiles: [{ id: 'p1', path: '/tmp/plans/p1.md', createdAt: 2 }],
|
|
90
|
+
projectPath: '/tmp/demo',
|
|
91
|
+
}, null, 2),
|
|
92
|
+
'utf8',
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
await runtime.upsertScheduledTask(projectID, {
|
|
96
|
+
name: 'nightly',
|
|
97
|
+
enabled: true,
|
|
98
|
+
schedule: { kind: 'daily', time: '09:00', timezone: 'UTC' },
|
|
99
|
+
execution: { prompt: 'run', providerID: 'openai', modelID: 'gpt-4.1' },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const raw = JSON.parse(await readFile(filePath, 'utf8'));
|
|
103
|
+
expect(raw.projectNotes).toBe('hello notes');
|
|
104
|
+
expect(raw.projectTodos).toEqual([{ id: 't1', text: 'buy milk', completed: false, createdAt: 1 }]);
|
|
105
|
+
expect(raw.projectActions).toHaveLength(1);
|
|
106
|
+
expect(raw.projectActionsPrimaryId).toBe('a1');
|
|
107
|
+
expect(raw['setup-worktree']).toEqual(['bun install']);
|
|
108
|
+
expect(raw.projectPlanFiles).toEqual([{ id: 'p1', path: '/tmp/plans/p1.md', createdAt: 2 }]);
|
|
109
|
+
expect(raw.projectPath).toBe('/tmp/demo');
|
|
110
|
+
expect(raw.scheduledTasks).toHaveLength(1);
|
|
111
|
+
expect(raw.version).toBe(1);
|
|
112
|
+
} finally {
|
|
113
|
+
await cleanup();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('preserves scheduled task state timestamps when listing tasks', async () => {
|
|
118
|
+
const { runtime, cleanup } = await createRuntime();
|
|
119
|
+
try {
|
|
120
|
+
const projectID = 'timestamp_preserve';
|
|
121
|
+
const filePath = path.join(runtime.resolveProjectConfigPath(projectID));
|
|
122
|
+
await writeFile(
|
|
123
|
+
filePath,
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
scheduledTasks: [{
|
|
126
|
+
id: 'task-existing',
|
|
127
|
+
name: 'nightly',
|
|
128
|
+
enabled: true,
|
|
129
|
+
schedule: { kind: 'daily', times: ['09:00'], timezone: 'UTC' },
|
|
130
|
+
execution: { prompt: 'run', providerID: 'openai', modelID: 'gpt-4.1' },
|
|
131
|
+
state: { createdAt: 10, updatedAt: 20, lastStatus: 'idle' },
|
|
132
|
+
}],
|
|
133
|
+
}, null, 2),
|
|
134
|
+
'utf8',
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const first = await runtime.listScheduledTasks(projectID);
|
|
138
|
+
const second = await runtime.listScheduledTasks(projectID);
|
|
139
|
+
|
|
140
|
+
expect(first[0].state.createdAt).toBe(10);
|
|
141
|
+
expect(first[0].state.updatedAt).toBe(20);
|
|
142
|
+
expect(second[0].state.updatedAt).toBe(20);
|
|
143
|
+
} finally {
|
|
144
|
+
await cleanup();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('accepts one-time schedule with date and time', async () => {
|
|
149
|
+
const { runtime, cleanup } = await createRuntime();
|
|
150
|
+
try {
|
|
151
|
+
const result = await runtime.upsertScheduledTask('project-test', {
|
|
152
|
+
name: 'One-time review',
|
|
153
|
+
enabled: true,
|
|
154
|
+
schedule: {
|
|
155
|
+
kind: 'once',
|
|
156
|
+
date: '2026-04-20',
|
|
157
|
+
time: '13:45',
|
|
158
|
+
timezone: 'Europe/Kyiv',
|
|
159
|
+
},
|
|
160
|
+
execution: {
|
|
161
|
+
prompt: 'Create a release summary',
|
|
162
|
+
providerID: 'openai',
|
|
163
|
+
modelID: 'gpt-4.1',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(result.task.schedule.kind).toBe('once');
|
|
168
|
+
expect(result.task.schedule.date).toBe('2026-04-20');
|
|
169
|
+
expect(result.task.schedule.time).toBe('13:45');
|
|
170
|
+
expect(result.task.schedule.timezone).toBe('Europe/Kyiv');
|
|
171
|
+
} finally {
|
|
172
|
+
await cleanup();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const normalizeProjectPathForId = (value) => {
|
|
2
|
+
if (typeof value !== 'string') return '';
|
|
3
|
+
return value.replace(/\\/g, '/').replace(/\/+$/g, '') || value;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const createProjectIdFromPath = (projectPath) => {
|
|
7
|
+
const normalized = normalizeProjectPathForId(projectPath).trim();
|
|
8
|
+
if (!normalized) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return `path_${Buffer.from(normalized, 'utf8').toString('base64url')}`;
|
|
13
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Quota Module Documentation
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
This module fetches quota and usage signals for supported providers in the web server runtime.
|
|
5
|
+
|
|
6
|
+
## Entrypoints and structure
|
|
7
|
+
- `packages/web/server/lib/quota/index.js`: public entrypoint imported by `packages/web/server/index.js`.
|
|
8
|
+
- `packages/web/server/lib/quota/routes.js`: Express route registration for quota endpoints.
|
|
9
|
+
- `packages/web/server/lib/quota/providers/index.js`: provider registry, configured-provider list, and provider dispatcher.
|
|
10
|
+
- `packages/web/server/lib/quota/providers/interface.js`: JSDoc provider contract used as implementation reference.
|
|
11
|
+
- `packages/web/server/lib/quota/providers/google/`: Google-specific auth, API, and transform modules.
|
|
12
|
+
- `packages/web/server/lib/quota/utils/`: shared auth, transform, and formatting helpers.
|
|
13
|
+
|
|
14
|
+
## Supported provider IDs (dispatcher)
|
|
15
|
+
|
|
16
|
+
These provider IDs are currently dispatchable via `fetchQuotaForProvider(providerId)` in `packages/web/server/lib/quota/providers/index.js`.
|
|
17
|
+
|
|
18
|
+
| Provider ID | Display name | Module | Auth aliases/keys |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| `claude` | Claude | `providers/claude.js` | `anthropic`, `claude` |
|
|
21
|
+
| `codex` | Codex | `providers/codex.js` | `openai`, `codex`, `chatgpt` |
|
|
22
|
+
| `google` | Google | `providers/google/index.js` | `google`, `google.oauth`, Antigravity accounts file |
|
|
23
|
+
| `github-copilot` | GitHub Copilot | `providers/copilot.js` | `github-copilot`, `copilot` |
|
|
24
|
+
| `github-copilot-addon` | GitHub Copilot Add-on | `providers/copilot.js` | `github-copilot`, `copilot` |
|
|
25
|
+
| `kimi-for-coding` | Kimi for Coding | `providers/kimi.js` | `kimi-for-coding`, `kimi` |
|
|
26
|
+
| `nano-gpt` | NanoGPT | `providers/nanogpt.js` | `nano-gpt`, `nanogpt`, `nano_gpt` |
|
|
27
|
+
| `openrouter` | OpenRouter | `providers/openrouter.js` | `openrouter` |
|
|
28
|
+
| `zai-coding-plan` | z.ai | `providers/zai.js` | `zai-coding-plan`, `zai`, `z.ai` |
|
|
29
|
+
| `zhipuai-coding-plan` | Zhipu AI Coding Plan | `providers/zhipuai-coding-plan.js` | `zhipuai-coding-plan` |
|
|
30
|
+
| `minimax-coding-plan` | MiniMax Coding Plan (minimax.io) | `providers/minimax-coding-plan.js` | `minimax-coding-plan` |
|
|
31
|
+
| `minimax-cn-coding-plan` | MiniMax Coding Plan (minimaxi.com) | `providers/minimax-cn-coding-plan.js` | `minimax-cn-coding-plan` |
|
|
32
|
+
| `ollama-cloud` | Ollama Cloud | `providers/ollama-cloud.js` | Cookie file at `~/.config/ollama-quota/cookie` (raw session cookie string) |
|
|
33
|
+
| `zhipuai-coding-plan` | ZhipuAI | `providers/zhipuai.js` | `zhipuai-coding-plan`, `zhipuai`, `zhipu` |
|
|
34
|
+
|
|
35
|
+
## Internal-only provider module
|
|
36
|
+
- `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
|
|
37
|
+
|
|
38
|
+
## Response contract
|
|
39
|
+
All providers should return results via shared helpers to preserve API shape:
|
|
40
|
+
- Required fields: `providerId`, `providerName`, `ok`, `configured`, `usage`, `fetchedAt`
|
|
41
|
+
- Optional field: `error`
|
|
42
|
+
- Unsupported provider requests should return `ok: false`, `configured: false`, `error: Unsupported provider`
|
|
43
|
+
|
|
44
|
+
## Add a new provider (quick steps)
|
|
45
|
+
1. Choose module shape based on complexity:
|
|
46
|
+
- Simple providers: create `packages/web/server/lib/quota/providers/<provider>.js`.
|
|
47
|
+
- Complex providers (multi-source auth, multiple API calls, non-trivial transforms): create `packages/web/server/lib/quota/providers/<provider>/` with split modules like Google (`index.js`, `auth.js`, `api.js`, `transforms.js`).
|
|
48
|
+
2. Export `providerId`, `providerName`, `aliases`, `isConfigured`, and `fetchQuota`.
|
|
49
|
+
3. Use shared helpers from `packages/web/server/lib/quota/utils/index.js` (`buildResult`, `toUsageWindow`, auth/conversion helpers) to keep payload shape consistent.
|
|
50
|
+
4. Register the provider in `packages/web/server/lib/quota/providers/index.js`.
|
|
51
|
+
5. If needed for direct use, export a named fetcher from `packages/web/server/lib/quota/providers/index.js` and `packages/web/server/lib/quota/index.js`.
|
|
52
|
+
6. Update this file with the new provider ID, module path, and alias/auth details.
|
|
53
|
+
7. Validate with `bun run type-check`, `bun run lint`, and `bun run build`.
|
|
54
|
+
|
|
55
|
+
## Notes for contributors
|
|
56
|
+
- Keep provider IDs stable; clients use them directly.
|
|
57
|
+
- Avoid adding alias-based dispatch in `fetchQuotaForProvider`; dispatch currently expects exact provider IDs.
|
|
58
|
+
- Keep Google behavior changes isolated and review `providers/google/*` together.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota module
|
|
3
|
+
*
|
|
4
|
+
* Provides quota usage tracking for various AI provider services.
|
|
5
|
+
* @module quota
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
listConfiguredQuotaProviders,
|
|
10
|
+
fetchQuotaForProvider,
|
|
11
|
+
fetchClaudeQuota,
|
|
12
|
+
fetchOpenaiQuota,
|
|
13
|
+
fetchGoogleQuota,
|
|
14
|
+
fetchCodexQuota,
|
|
15
|
+
fetchCopilotQuota,
|
|
16
|
+
fetchCopilotAddonQuota,
|
|
17
|
+
fetchKimiQuota,
|
|
18
|
+
fetchOpenRouterQuota,
|
|
19
|
+
fetchZaiQuota,
|
|
20
|
+
fetchNanoGptQuota,
|
|
21
|
+
fetchMinimaxCodingPlanQuota,
|
|
22
|
+
fetchMinimaxCnCodingPlanQuota,
|
|
23
|
+
fetchOllamaCloudQuota,
|
|
24
|
+
fetchZhipuaiQuota
|
|
25
|
+
} from './providers/index.js';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode/auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
toTimestamp
|
|
9
|
+
} from '../utils/index.js';
|
|
10
|
+
|
|
11
|
+
export const providerId = 'claude';
|
|
12
|
+
export const providerName = 'Claude';
|
|
13
|
+
export const aliases = ['anthropic', 'claude'];
|
|
14
|
+
|
|
15
|
+
export const isConfigured = () => {
|
|
16
|
+
const auth = readAuthFile();
|
|
17
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
|
+
return Boolean(entry?.access || entry?.token);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const fetchQuota = async () => {
|
|
22
|
+
const auth = readAuthFile();
|
|
23
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
|
+
const accessToken = entry?.access ?? entry?.token;
|
|
25
|
+
|
|
26
|
+
if (!accessToken) {
|
|
27
|
+
return buildResult({
|
|
28
|
+
providerId,
|
|
29
|
+
providerName,
|
|
30
|
+
ok: false,
|
|
31
|
+
configured: false,
|
|
32
|
+
error: 'Not configured'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch('https://api.anthropic.com/api/oauth/usage', {
|
|
38
|
+
method: 'GET',
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${accessToken}`,
|
|
41
|
+
'anthropic-beta': 'oauth-2025-04-20'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
return buildResult({
|
|
47
|
+
providerId,
|
|
48
|
+
providerName,
|
|
49
|
+
ok: false,
|
|
50
|
+
configured: true,
|
|
51
|
+
error: `API error: ${response.status}`
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const payload = await response.json();
|
|
56
|
+
const windows = {};
|
|
57
|
+
const fiveHour = payload?.five_hour ?? null;
|
|
58
|
+
const sevenDay = payload?.seven_day ?? null;
|
|
59
|
+
const sevenDaySonnet = payload?.seven_day_sonnet ?? null;
|
|
60
|
+
const sevenDayOpus = payload?.seven_day_opus ?? null;
|
|
61
|
+
|
|
62
|
+
if (fiveHour) {
|
|
63
|
+
windows['5h'] = toUsageWindow({
|
|
64
|
+
usedPercent: toNumber(fiveHour.utilization),
|
|
65
|
+
windowSeconds: null,
|
|
66
|
+
resetAt: toTimestamp(fiveHour.resets_at)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (sevenDay) {
|
|
70
|
+
windows['7d'] = toUsageWindow({
|
|
71
|
+
usedPercent: toNumber(sevenDay.utilization),
|
|
72
|
+
windowSeconds: null,
|
|
73
|
+
resetAt: toTimestamp(sevenDay.resets_at)
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (sevenDaySonnet) {
|
|
77
|
+
windows['7d-sonnet'] = toUsageWindow({
|
|
78
|
+
usedPercent: toNumber(sevenDaySonnet.utilization),
|
|
79
|
+
windowSeconds: null,
|
|
80
|
+
resetAt: toTimestamp(sevenDaySonnet.resets_at)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (sevenDayOpus) {
|
|
84
|
+
windows['7d-opus'] = toUsageWindow({
|
|
85
|
+
usedPercent: toNumber(sevenDayOpus.utilization),
|
|
86
|
+
windowSeconds: null,
|
|
87
|
+
resetAt: toTimestamp(sevenDayOpus.resets_at)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return buildResult({
|
|
92
|
+
providerId,
|
|
93
|
+
providerName,
|
|
94
|
+
ok: true,
|
|
95
|
+
configured: true,
|
|
96
|
+
usage: { windows }
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return buildResult({
|
|
100
|
+
providerId,
|
|
101
|
+
providerName,
|
|
102
|
+
ok: false,
|
|
103
|
+
configured: true,
|
|
104
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode/auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
toTimestamp,
|
|
9
|
+
formatMoney
|
|
10
|
+
} from '../utils/index.js';
|
|
11
|
+
|
|
12
|
+
export const providerId = 'codex';
|
|
13
|
+
export const providerName = 'Codex';
|
|
14
|
+
export const aliases = ['openai', 'codex', 'chatgpt'];
|
|
15
|
+
|
|
16
|
+
export const isConfigured = () => {
|
|
17
|
+
const auth = readAuthFile();
|
|
18
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
19
|
+
return Boolean(entry?.access || entry?.token);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const fetchQuota = async () => {
|
|
23
|
+
const auth = readAuthFile();
|
|
24
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
25
|
+
const accessToken = entry?.access ?? entry?.token;
|
|
26
|
+
const accountId = entry?.accountId;
|
|
27
|
+
|
|
28
|
+
if (!accessToken) {
|
|
29
|
+
return buildResult({
|
|
30
|
+
providerId,
|
|
31
|
+
providerName,
|
|
32
|
+
ok: false,
|
|
33
|
+
configured: false,
|
|
34
|
+
error: 'Not configured'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const headers = {
|
|
40
|
+
Authorization: `Bearer ${accessToken}`,
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
...(accountId ? { 'ChatGPT-Account-Id': accountId } : {})
|
|
43
|
+
};
|
|
44
|
+
const response = await fetch('https://chatgpt.com/backend-api/wham/usage', {
|
|
45
|
+
method: 'GET',
|
|
46
|
+
headers
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
return buildResult({
|
|
51
|
+
providerId,
|
|
52
|
+
providerName,
|
|
53
|
+
ok: false,
|
|
54
|
+
configured: true,
|
|
55
|
+
error: response.status === 401
|
|
56
|
+
? 'Session expired \u2014 please re-authenticate with OpenAI'
|
|
57
|
+
: `API error: ${response.status}`
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const payload = await response.json();
|
|
62
|
+
const primary = payload?.rate_limit?.primary_window ?? null;
|
|
63
|
+
const secondary = payload?.rate_limit?.secondary_window ?? null;
|
|
64
|
+
const credits = payload?.credits ?? null;
|
|
65
|
+
|
|
66
|
+
const windows = {};
|
|
67
|
+
if (primary) {
|
|
68
|
+
windows['5h'] = toUsageWindow({
|
|
69
|
+
usedPercent: toNumber(primary.used_percent),
|
|
70
|
+
windowSeconds: toNumber(primary.limit_window_seconds),
|
|
71
|
+
resetAt: toTimestamp(primary.reset_at)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (secondary) {
|
|
75
|
+
windows['weekly'] = toUsageWindow({
|
|
76
|
+
usedPercent: toNumber(secondary.used_percent),
|
|
77
|
+
windowSeconds: toNumber(secondary.limit_window_seconds),
|
|
78
|
+
resetAt: toTimestamp(secondary.reset_at)
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (credits) {
|
|
82
|
+
const balance = toNumber(credits.balance);
|
|
83
|
+
const unlimited = Boolean(credits.unlimited);
|
|
84
|
+
const label = unlimited
|
|
85
|
+
? 'Unlimited'
|
|
86
|
+
: balance !== null
|
|
87
|
+
? `$${formatMoney(balance)} remaining`
|
|
88
|
+
: null;
|
|
89
|
+
windows.credits = toUsageWindow({
|
|
90
|
+
usedPercent: null,
|
|
91
|
+
windowSeconds: null,
|
|
92
|
+
resetAt: null,
|
|
93
|
+
valueLabel: label
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return buildResult({
|
|
98
|
+
providerId,
|
|
99
|
+
providerName,
|
|
100
|
+
ok: true,
|
|
101
|
+
configured: true,
|
|
102
|
+
usage: { windows }
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return buildResult({
|
|
106
|
+
providerId,
|
|
107
|
+
providerName,
|
|
108
|
+
ok: false,
|
|
109
|
+
configured: true,
|
|
110
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode/auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
toTimestamp
|
|
9
|
+
} from '../utils/index.js';
|
|
10
|
+
|
|
11
|
+
const buildCopilotWindows = (payload) => {
|
|
12
|
+
const quota = payload?.quota_snapshots ?? {};
|
|
13
|
+
const resetAt = toTimestamp(payload?.quota_reset_date);
|
|
14
|
+
const windows = {};
|
|
15
|
+
|
|
16
|
+
const addWindow = (label, snapshot) => {
|
|
17
|
+
if (!snapshot) return;
|
|
18
|
+
const entitlement = toNumber(snapshot.entitlement);
|
|
19
|
+
const remaining = toNumber(snapshot.remaining);
|
|
20
|
+
const usedPercent = entitlement && remaining !== null
|
|
21
|
+
? Math.max(0, 100 - (remaining / entitlement) * 100)
|
|
22
|
+
: null;
|
|
23
|
+
const valueLabel = entitlement !== null && remaining !== null
|
|
24
|
+
? `${remaining.toFixed(0)} / ${entitlement.toFixed(0)} left`
|
|
25
|
+
: null;
|
|
26
|
+
windows[label] = toUsageWindow({
|
|
27
|
+
usedPercent,
|
|
28
|
+
windowSeconds: null,
|
|
29
|
+
resetAt,
|
|
30
|
+
valueLabel
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
addWindow('chat', quota.chat);
|
|
35
|
+
addWindow('completions', quota.completions);
|
|
36
|
+
addWindow('premium', quota.premium_interactions);
|
|
37
|
+
|
|
38
|
+
return windows;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const providerId = 'github-copilot';
|
|
42
|
+
export const providerName = 'GitHub Copilot';
|
|
43
|
+
export const aliases = ['github-copilot', 'copilot'];
|
|
44
|
+
|
|
45
|
+
export const isConfigured = () => {
|
|
46
|
+
const auth = readAuthFile();
|
|
47
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
48
|
+
return Boolean(entry?.access || entry?.token);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const fetchQuota = async () => {
|
|
52
|
+
const auth = readAuthFile();
|
|
53
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
54
|
+
const accessToken = entry?.access ?? entry?.token;
|
|
55
|
+
|
|
56
|
+
if (!accessToken) {
|
|
57
|
+
return buildResult({
|
|
58
|
+
providerId,
|
|
59
|
+
providerName,
|
|
60
|
+
ok: false,
|
|
61
|
+
configured: false,
|
|
62
|
+
error: 'Not configured'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch('https://api.github.com/copilot_internal/user', {
|
|
68
|
+
method: 'GET',
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: `token ${accessToken}`,
|
|
71
|
+
Accept: 'application/json',
|
|
72
|
+
'Editor-Version': 'vscode/1.96.2',
|
|
73
|
+
'X-Github-Api-Version': '2025-04-01'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
return buildResult({
|
|
79
|
+
providerId,
|
|
80
|
+
providerName,
|
|
81
|
+
ok: false,
|
|
82
|
+
configured: true,
|
|
83
|
+
error: `API error: ${response.status}`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const payload = await response.json();
|
|
88
|
+
return buildResult({
|
|
89
|
+
providerId,
|
|
90
|
+
providerName,
|
|
91
|
+
ok: true,
|
|
92
|
+
configured: true,
|
|
93
|
+
usage: { windows: buildCopilotWindows(payload) }
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return buildResult({
|
|
97
|
+
providerId,
|
|
98
|
+
providerName,
|
|
99
|
+
ok: false,
|
|
100
|
+
configured: true,
|
|
101
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const providerIdAddon = 'github-copilot-addon';
|
|
107
|
+
export const providerNameAddon = 'GitHub Copilot Add-on';
|
|
108
|
+
|
|
109
|
+
export const fetchQuotaAddon = async () => {
|
|
110
|
+
const auth = readAuthFile();
|
|
111
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
112
|
+
const accessToken = entry?.access ?? entry?.token;
|
|
113
|
+
|
|
114
|
+
if (!accessToken) {
|
|
115
|
+
return buildResult({
|
|
116
|
+
providerId: providerIdAddon,
|
|
117
|
+
providerName: providerNameAddon,
|
|
118
|
+
ok: false,
|
|
119
|
+
configured: false,
|
|
120
|
+
error: 'Not configured'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch('https://api.github.com/copilot_internal/user', {
|
|
126
|
+
method: 'GET',
|
|
127
|
+
headers: {
|
|
128
|
+
Authorization: `token ${accessToken}`,
|
|
129
|
+
Accept: 'application/json',
|
|
130
|
+
'Editor-Version': 'vscode/1.96.2',
|
|
131
|
+
'X-Github-Api-Version': '2025-04-01'
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
return buildResult({
|
|
137
|
+
providerId: providerIdAddon,
|
|
138
|
+
providerName: providerNameAddon,
|
|
139
|
+
ok: false,
|
|
140
|
+
configured: true,
|
|
141
|
+
error: `API error: ${response.status}`
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const payload = await response.json();
|
|
146
|
+
const windows = buildCopilotWindows(payload);
|
|
147
|
+
const premium = windows.premium ? { premium: windows.premium } : windows;
|
|
148
|
+
|
|
149
|
+
return buildResult({
|
|
150
|
+
providerId: providerIdAddon,
|
|
151
|
+
providerName: providerNameAddon,
|
|
152
|
+
ok: true,
|
|
153
|
+
configured: true,
|
|
154
|
+
usage: { windows: premium }
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return buildResult({
|
|
158
|
+
providerId: providerIdAddon,
|
|
159
|
+
providerName: providerNameAddon,
|
|
160
|
+
ok: false,
|
|
161
|
+
configured: true,
|
|
162
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
};
|