@theoremjs/studio 0.0.0-stage → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +295 -2
- package/agreement.d.ts +14 -0
- package/agreement.js +80 -0
- package/asks.d.ts +47 -0
- package/asks.js +54 -0
- package/browser-connection.d.ts +49 -0
- package/browser-connection.js +215 -0
- package/browser-engine.d.ts +11 -0
- package/browser-engine.js +11 -0
- package/browser-execution.d.ts +6 -0
- package/browser-execution.js +18 -0
- package/browser-live.d.ts +4 -0
- package/browser-live.js +132 -0
- package/browser-transport.d.ts +33 -0
- package/browser-transport.js +229 -0
- package/browser.d.ts +4 -0
- package/browser.js +4 -0
- package/canonical.d.ts +10 -0
- package/canonical.js +25 -0
- package/compile-workspace.d.ts +28 -0
- package/compile-workspace.js +295 -0
- package/compile.d.ts +65 -0
- package/compile.js +1605 -0
- package/concierge-demo.d.ts +17 -0
- package/concierge-demo.js +1230 -0
- package/conversation-key.d.ts +5 -0
- package/conversation-key.js +10 -0
- package/decision-example-images.d.ts +30 -0
- package/decision-example-images.js +30 -0
- package/deno.json +3 -0
- package/draft.d.ts +407 -0
- package/draft.js +505 -0
- package/editors.d.ts +29 -0
- package/editors.js +51 -0
- package/example-embedding-files.d.ts +11 -0
- package/example-embedding-files.js +20 -0
- package/example.d.ts +36 -0
- package/example.js +357 -0
- package/guardrail-probe.d.ts +100 -0
- package/guardrail-probe.js +722 -0
- package/held-credentials.d.ts +28 -0
- package/held-credentials.js +87 -0
- package/installed.mjs +40 -0
- package/lent-keys.d.ts +122 -0
- package/lent-keys.js +47 -0
- package/line-diff.d.ts +15 -0
- package/line-diff.js +41 -0
- package/live-connection.d.ts +22 -0
- package/live-connection.js +27 -0
- package/live-session-bridge.d.ts +15 -0
- package/live-session-bridge.js +202 -0
- package/map-line.d.ts +19 -0
- package/map-line.js +42 -0
- package/map.d.ts +154 -0
- package/map.js +525 -0
- package/media-accept.d.ts +19 -0
- package/media-accept.js +65 -0
- package/mod.d.ts +39 -0
- package/mod.js +33 -0
- package/origins.d.ts +52 -0
- package/origins.js +165 -0
- package/package.json +85 -4
- package/page/assets/Banner-BISz3nXA.js +2 -0
- package/page/assets/BottomSheet-BwfBDtje.js +1 -0
- package/page/assets/Card-Lp-0P5eL.js +1 -0
- package/page/assets/CardWall-CBRLdDZW.js +1 -0
- package/page/assets/Center-nHS1JSux.js +1 -0
- package/page/assets/CheckboxInput-Cj5pk-KI.js +2 -0
- package/page/assets/CodeBlock-C7XJ4ioE.js +51 -0
- package/page/assets/CollapsibleGroup-N7_SOjbm.js +2 -0
- package/page/assets/ContextMenu-CBu9H-9H.js +2 -0
- package/page/assets/DateInput-BGrQ5tPC.js +1 -0
- package/page/assets/Dialog-DELWQo-v.js +1 -0
- package/page/assets/EmptyState-C8W4JKBZ.js +1 -0
- package/page/assets/HoverCard-D1Y1ltWQ.js +1 -0
- package/page/assets/Icon-JrysT8aU.js +3 -0
- package/page/assets/IconAlertTriangle-BqAEqqdU.js +1 -0
- package/page/assets/IconButton-DlOGzJIX.js +1 -0
- package/page/assets/IconEye-BB5aY-wQ.js +1 -0
- package/page/assets/IconFileText-6zOFKKrF.js +1 -0
- package/page/assets/IconSearch-BTx66a40.js +1 -0
- package/page/assets/IconShield-DDOh7Gr5.js +1 -0
- package/page/assets/ItemSwipeLayer-Ckn4X6h9.js +1 -0
- package/page/assets/PaneState-BvA2tLQg.js +1 -0
- package/page/assets/PaneState-eUzUkNE8.css +1 -0
- package/page/assets/ScrollableArea-Ckugdj1N.js +1 -0
- package/page/assets/Section-DJ97F7QE.js +1 -0
- package/page/assets/SegmentedControlItem-h-aaXzyS.js +1 -0
- package/page/assets/SidePanel-BL32abk8.js +1 -0
- package/page/assets/StackItem-CQviqAfM.js +1 -0
- package/page/assets/Text-bum6zWWx.js +2 -0
- package/page/assets/TimestampHoverCard-DQIG0RZF.js +1 -0
- package/page/assets/ToolResultCharts-CgshSv9Q.js +1 -0
- package/page/assets/Tooltip-BH9xiRMl.js +1 -0
- package/page/assets/TraceInspector-_yZiz05W.js +1 -0
- package/page/assets/TraceOverview-BPO-HIYK.js +57 -0
- package/page/assets/VStack-CkVMcNiK.js +1 -0
- package/page/assets/_virtual_studio-type-sources-ByxqQaOG.js +12936 -0
- package/page/assets/arrive-DvbqpeRS.js +1 -0
- package/page/assets/browser-engine-CYq9DQAM.js +9 -0
- package/page/assets/browser-transport-D-2jDYe7.js +47 -0
- package/page/assets/codicon-DF1abBS2.ttf +0 -0
- package/page/assets/compile-egress-BSxOdX4K.js +23 -0
- package/page/assets/container.stylex-BmOi-8DQ.js +1 -0
- package/page/assets/createReactComponent-CZwsv6DD.js +1 -0
- package/page/assets/decision-example-images-CZGr7YIT.js +1 -0
- package/page/assets/devWarning-DxuplmRl.js +1 -0
- package/page/assets/editor-DjpsE8to.css +1 -0
- package/page/assets/editor.api-Cd3ViPQa.js +908 -0
- package/page/assets/editor.worker-6GY8RhDc.js +1 -0
- package/page/assets/editor.worker-BmdWdrHZ.js +33 -0
- package/page/assets/editors-BJX_S59A.js +1 -0
- package/page/assets/egress-automata-B5ZGE1mT.js +1 -0
- package/page/assets/embedding-space-view-CfQBPDxD.js +140 -0
- package/page/assets/encode-files-Bc3HJyAh.js +1 -0
- package/page/assets/export-agent-C3yyzXH8.js +43 -0
- package/page/assets/figtree-latin-400-normal-BD4aNku5.woff +0 -0
- package/page/assets/figtree-latin-400-normal-g7Dtegnw.woff2 +0 -0
- package/page/assets/figtree-latin-500-normal-BWnGEVsr.woff2 +0 -0
- package/page/assets/figtree-latin-500-normal-B_yy1phn.woff +0 -0
- package/page/assets/figtree-latin-600-normal-BM_oTo4n.woff +0 -0
- package/page/assets/figtree-latin-600-normal-Cv_xCTDl.woff2 +0 -0
- package/page/assets/figtree-latin-700-normal-BCCt2T1q.woff +0 -0
- package/page/assets/figtree-latin-700-normal-th6qEP7c.woff2 +0 -0
- package/page/assets/focusOutline.stylex-BT0kfa4t.js +1 -0
- package/page/assets/focusableSelector-DXZeMU85.js +1 -0
- package/page/assets/groupStyles-C7Ter7zC.js +1 -0
- package/page/assets/guardrail-tester-Gut5xK4a.js +9 -0
- package/page/assets/host-patterns-C9RMP16Q.js +1 -0
- package/page/assets/index-CvvmFKgS.js +105 -0
- package/page/assets/index-DOaHlqBo.css +1 -0
- package/page/assets/isRtlElement-BG33-k_H.js +1 -0
- package/page/assets/labels-provider-4TIWDvsE.js +3 -0
- package/page/assets/lent-keys-C32ems-I.js +1 -0
- package/page/assets/live-ingress-lDUGPwth.js +1 -0
- package/page/assets/live-messages-CnkL4GWi.js +1 -0
- package/page/assets/mergeProps-ptS-gIMz.js +1 -0
- package/page/assets/padding.stylex-B76907Zf.js +1 -0
- package/page/assets/preload-helper-DpV6A1kQ.js +1 -0
- package/page/assets/reader-BOCCYaRp.js +7 -0
- package/page/assets/register-CDgivxvX.js +2 -0
- package/page/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/page/assets/schemas-BQ9Jydzb.js +56 -0
- package/page/assets/shaped-data-BcarWqd5.js +16 -0
- package/page/assets/sharedResizeObserver-4U9Xq5Oq.js +1 -0
- package/page/assets/stub-DhdcU_Ew.js +471 -0
- package/page/assets/studio-code-pane-C_SAbcOa.js +8 -0
- package/page/assets/studio-console-DZwlEsvr.js +3 -0
- package/page/assets/studio-decision-B0FnCAXA.js +1 -0
- package/page/assets/studio-embedding-CmA51HVo.js +2 -0
- package/page/assets/studio-host-console-By7hqu2l.js +2 -0
- package/page/assets/studio-live-BrcVQcyV.js +1 -0
- package/page/assets/studio-map-B_xSia-z.js +1 -0
- package/page/assets/studio-map-Bb06Tw1F.css +1 -0
- package/page/assets/studio-monaco-CbKf90i6.css +1 -0
- package/page/assets/studio-monaco-xfyfezQS.js +34 -0
- package/page/assets/studio-ts.worker-B-EXG4Sv.js +67741 -0
- package/page/assets/studio-ts.worker-BXtW3z-O.js +1 -0
- package/page/assets/studio-type-checker-BOvxudki.js +23 -0
- package/page/assets/themeProps-3IqahpCr.js +1 -0
- package/page/assets/tokens.stylex-DkHf0P9C.js +1 -0
- package/page/assets/tool-schema-idaOe2nI.js +18 -0
- package/page/assets/trace-patterns-DnYJa0nI.js +1 -0
- package/page/assets/trace-record-C3zNksA_.js +1 -0
- package/page/assets/trace-schema-9CdEyG6Q.js +1 -0
- package/page/assets/tree-DqyAMsbI.js +3 -0
- package/page/assets/turn-client-Du3Um3Mg.js +2 -0
- package/page/assets/types-BhfXQCLx.js +1 -0
- package/page/assets/typescript-20GgZ8PJ.js +1 -0
- package/page/assets/use-one-call-Hcs8YBp1.js +1 -0
- package/page/assets/useAdaptivePresentation-BzmPCddb.js +1 -0
- package/page/assets/useClipboard-BgGRxRMJ.js +1 -0
- package/page/assets/useContainerReveal-BFy8N_QU.js +1 -0
- package/page/assets/useDevWarning-Z9SoixOP.js +1 -0
- package/page/assets/useLayerDismissal-B_G7IWpC.js +1 -0
- package/page/assets/useMediaQuery-BCT0NUeZ.js +1 -0
- package/page/assets/useScrollLock-Dyf6PHJt.js +1 -0
- package/page/assets/useScrollableArea-0NzdiBCq.js +1 -0
- package/page/assets/useTooltip-CJM_xhLB.js +1 -0
- package/page/assets/workspace-DZALqEDX.js +1 -0
- package/page/assets/zip-DTcEfps6.js +1 -0
- package/page/index.html +90 -0
- package/page.mjs +49 -0
- package/people.d.ts +87 -0
- package/people.js +110 -0
- package/policy.d.ts +43 -0
- package/policy.js +102 -0
- package/ports.d.mts +32 -0
- package/ports.mjs +85 -0
- package/probe-battery.d.ts +13 -0
- package/probe-battery.js +404 -0
- package/providers.d.ts +149 -0
- package/providers.js +306 -0
- package/read-source.d.ts +48 -0
- package/read-source.js +1301 -0
- package/read-steps.d.ts +23 -0
- package/read-steps.js +2 -0
- package/reader.d.ts +11 -0
- package/reader.js +11 -0
- package/registrations.d.ts +83 -0
- package/registrations.js +1 -0
- package/requirements.d.ts +22 -0
- package/requirements.js +38 -0
- package/run-payload-core.d.ts +28 -0
- package/run-payload-core.js +123 -0
- package/run-payload.d.ts +32 -0
- package/run-payload.js +26 -0
- package/runtime-scope.d.ts +84 -0
- package/runtime-scope.js +308 -0
- package/runtime.d.ts +84 -0
- package/runtime.js +387 -0
- package/section-notes.d.ts +33 -0
- package/section-notes.js +38 -0
- package/serve-page.mjs +96 -0
- package/server/draft-decide.d.ts +72 -0
- package/server/draft-decide.js +111 -0
- package/server/draft-embedding.d.ts +3 -0
- package/server/draft-embedding.js +35 -0
- package/server/draft-handler.d.ts +82 -0
- package/server/draft-handler.js +213 -0
- package/server/draft-probe.d.ts +14 -0
- package/server/draft-probe.js +61 -0
- package/server/draft-replies.d.ts +22 -0
- package/server/draft-replies.js +46 -0
- package/server/edited-session.d.ts +28 -0
- package/server/edited-session.js +83 -0
- package/server/edits.d.ts +38 -0
- package/server/edits.js +195 -0
- package/server/handler.d.ts +133 -0
- package/server/handler.js +598 -0
- package/server/lent-draft.d.ts +19 -0
- package/server/lent-draft.js +23 -0
- package/server/live.d.ts +15 -0
- package/server/live.js +96 -0
- package/server/open-editor.d.ts +63 -0
- package/server/open-editor.js +115 -0
- package/server/origins.d.ts +18 -0
- package/server/origins.js +330 -0
- package/server/project-source.d.ts +208 -0
- package/server/project-source.js +1042 -0
- package/server/project.d.ts +22 -0
- package/server/project.js +56 -0
- package/server/run-traces.d.ts +20 -0
- package/server/run-traces.js +96 -0
- package/server/save-new.d.ts +29 -0
- package/server/save-new.js +514 -0
- package/server/save-plan.d.ts +46 -0
- package/server/save-plan.js +1134 -0
- package/server/save-remove.d.ts +21 -0
- package/server/save-remove.js +528 -0
- package/server/save-session.d.ts +96 -0
- package/server/save-session.js +418 -0
- package/server/save-wire.d.ts +235 -0
- package/server/save-wire.js +8 -0
- package/server/save.d.ts +52 -0
- package/server/save.js +209 -0
- package/server/serve.d.ts +23 -0
- package/server/serve.js +413 -0
- package/server/test-connection.d.ts +17 -0
- package/server/test-connection.js +362 -0
- package/server/test-key.d.ts +2 -0
- package/server/test-key.js +83 -0
- package/server/watch.d.ts +28 -0
- package/server/watch.js +93 -0
- package/server/zod-edit.d.ts +74 -0
- package/server/zod-edit.js +558 -0
- package/server.d.ts +8 -0
- package/server.js +8 -0
- package/setups.mjs +170 -0
- package/shared-carry.d.ts +114 -0
- package/shared-carry.js +523 -0
- package/shared-settings.d.ts +34 -0
- package/shared-settings.js +70 -0
- package/source.d.ts +75 -0
- package/source.js +414 -0
- package/start.mjs +169 -0
- package/stream-lines.d.ts +9 -0
- package/stream-lines.js +27 -0
- package/stub.d.ts +6 -0
- package/stub.js +33 -0
- package/surface.d.ts +53 -0
- package/surface.js +631 -0
- package/system-markup.d.ts +20 -0
- package/system-markup.js +51 -0
- package/tool-schema.d.ts +23 -0
- package/tool-schema.js +69 -0
- package/tools.d.ts +16 -0
- package/tools.js +91 -0
- package/traces.d.ts +24 -0
- package/traces.js +29 -0
- package/transport.d.ts +57 -0
- package/transport.js +187 -0
- package/tree.d.ts +32 -0
- package/tree.js +83 -0
- package/types.d.ts +62 -0
- package/types.js +6 -0
- package/ui/acting-as-button.d.ts +2 -0
- package/ui/acting-as-button.js +16 -0
- package/ui/add-provider.d.ts +13 -0
- package/ui/add-provider.js +20 -0
- package/ui/boot-mark.css +53 -0
- package/ui/boot-mark.d.ts +19 -0
- package/ui/boot-mark.js +128 -0
- package/ui/boot-paint.d.ts +8 -0
- package/ui/boot-paint.js +8 -0
- package/ui/brand-icons.d.ts +57 -0
- package/ui/brand-icons.js +148 -0
- package/ui/built/theme.css +830 -0
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem-studio.d.ts +13 -0
- package/ui/built/theorem-studio.js +705 -0
- package/ui/built/theorem-studio.variants.d.ts +28 -0
- package/ui/code/monaco-contributions.d.ts +45 -0
- package/ui/code/studio-code.d.ts +7 -0
- package/ui/code/studio-code.js +253 -0
- package/ui/code/studio-monaco.d.ts +44 -0
- package/ui/code/studio-monaco.js +48 -0
- package/ui/code/studio-ts.worker.d.ts +1 -0
- package/ui/code/studio-ts.worker.js +16 -0
- package/ui/code/studio-type-checker.d.ts +12 -0
- package/ui/code/studio-type-checker.js +308 -0
- package/ui/code/studio-type-sources.d.ts +26 -0
- package/ui/confirm-write.d.ts +12 -0
- package/ui/confirm-write.js +45 -0
- package/ui/diff-block.d.ts +21 -0
- package/ui/diff-block.js +17 -0
- package/ui/figtree.css +59 -0
- package/ui/file-place.d.ts +4 -0
- package/ui/file-place.js +26 -0
- package/ui/guardrail-tester.d.ts +18 -0
- package/ui/guardrail-tester.js +248 -0
- package/ui/inspector-context.d.ts +119 -0
- package/ui/inspector-context.js +84 -0
- package/ui/inspector.d.ts +247 -0
- package/ui/inspector.js +284 -0
- package/ui/lib/acting-as.d.ts +25 -0
- package/ui/lib/acting-as.js +90 -0
- package/ui/lib/boot-sequence.d.ts +32 -0
- package/ui/lib/boot-sequence.js +57 -0
- package/ui/lib/export-agent.d.ts +11 -0
- package/ui/lib/export-agent.js +335 -0
- package/ui/lib/facet-icons.d.ts +20 -0
- package/ui/lib/facet-icons.js +21 -0
- package/ui/lib/panel-motion.d.ts +8 -0
- package/ui/lib/panel-motion.js +74 -0
- package/ui/lib/project-address.d.ts +14 -0
- package/ui/lib/project-address.js +24 -0
- package/ui/lib/project-starting.d.ts +13 -0
- package/ui/lib/project-starting.js +30 -0
- package/ui/lib/project-traces.d.ts +17 -0
- package/ui/lib/project-traces.js +54 -0
- package/ui/lib/read-map.d.ts +30 -0
- package/ui/lib/read-map.js +173 -0
- package/ui/lib/read-progress.d.ts +14 -0
- package/ui/lib/read-progress.js +12 -0
- package/ui/lib/refusal-lines.d.ts +3 -0
- package/ui/lib/refusal-lines.js +8 -0
- package/ui/lib/studio-activity.d.ts +5 -0
- package/ui/lib/studio-activity.js +17 -0
- package/ui/lib/studio-console.d.ts +27 -0
- package/ui/lib/studio-console.js +9 -0
- package/ui/lib/studio-conversation.d.ts +20 -0
- package/ui/lib/studio-conversation.js +130 -0
- package/ui/lib/studio-examples.d.ts +31 -0
- package/ui/lib/studio-examples.js +25 -0
- package/ui/lib/studio-labels.d.ts +6 -0
- package/ui/lib/studio-labels.js +13 -0
- package/ui/lib/studio-map-kept.d.ts +17 -0
- package/ui/lib/studio-map-kept.js +48 -0
- package/ui/lib/studio-open.d.ts +22 -0
- package/ui/lib/studio-open.js +54 -0
- package/ui/lib/studio-page.d.ts +33 -0
- package/ui/lib/studio-page.js +121 -0
- package/ui/lib/studio-prefs.d.ts +26 -0
- package/ui/lib/studio-prefs.js +87 -0
- package/ui/lib/studio-project.d.ts +106 -0
- package/ui/lib/studio-project.js +177 -0
- package/ui/lib/studio-restore.d.ts +49 -0
- package/ui/lib/studio-restore.js +251 -0
- package/ui/lib/studio-run-open.d.ts +15 -0
- package/ui/lib/studio-run-open.js +16 -0
- package/ui/lib/studio-session.d.ts +34 -0
- package/ui/lib/studio-session.js +17 -0
- package/ui/lib/studio-settings.d.ts +15 -0
- package/ui/lib/studio-settings.js +48 -0
- package/ui/lib/studio-store.d.ts +64 -0
- package/ui/lib/studio-store.js +391 -0
- package/ui/lib/studio-watch.d.ts +12 -0
- package/ui/lib/studio-watch.js +27 -0
- package/ui/lib/tab-fills.d.ts +6 -0
- package/ui/lib/tab-fills.js +14 -0
- package/ui/lib/tool-credentials.d.ts +3 -0
- package/ui/lib/tool-credentials.js +23 -0
- package/ui/lib/tool-probe.d.ts +19 -0
- package/ui/lib/tool-probe.js +104 -0
- package/ui/lib/type-icons.d.ts +35 -0
- package/ui/lib/type-icons.js +38 -0
- package/ui/lib/when-idle.d.ts +5 -0
- package/ui/lib/when-idle.js +14 -0
- package/ui/lib/zip.d.ts +5 -0
- package/ui/lib/zip.js +8 -0
- package/ui/map-token.css +11 -0
- package/ui/map-token.d.ts +17 -0
- package/ui/map-token.js +29 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/motion.css +113 -0
- package/ui/pan.d.ts +32 -0
- package/ui/pan.js +90 -0
- package/ui/profile-editor.d.ts +33 -0
- package/ui/profile-editor.js +2638 -0
- package/ui/provider-editor.d.ts +15 -0
- package/ui/provider-editor.js +92 -0
- package/ui/read-map.css +48 -0
- package/ui/read-map.d.ts +7 -0
- package/ui/read-map.js +163 -0
- package/ui/refusal-detail.d.ts +4 -0
- package/ui/refusal-detail.js +21 -0
- package/ui/shared-change.d.ts +9 -0
- package/ui/shared-change.js +28 -0
- package/ui/shell-motion.css +121 -0
- package/ui/shell-motion.d.ts +73 -0
- package/ui/shell-motion.js +194 -0
- package/ui/studio-code-pane.d.ts +19 -0
- package/ui/studio-code-pane.js +44 -0
- package/ui/studio-compare.d.ts +80 -0
- package/ui/studio-compare.js +326 -0
- package/ui/studio-conflicts.d.ts +17 -0
- package/ui/studio-conflicts.js +87 -0
- package/ui/studio-connection.d.ts +64 -0
- package/ui/studio-connection.js +286 -0
- package/ui/studio-decision.d.ts +6 -0
- package/ui/studio-decision.js +49 -0
- package/ui/studio-embedding.d.ts +6 -0
- package/ui/studio-embedding.js +30 -0
- package/ui/studio-host-console.d.ts +6 -0
- package/ui/studio-host-console.js +17 -0
- package/ui/studio-host.d.ts +73 -0
- package/ui/studio-host.js +12 -0
- package/ui/studio-live.d.ts +17 -0
- package/ui/studio-live.js +26 -0
- package/ui/studio-map.css +273 -0
- package/ui/studio-map.d.ts +17 -0
- package/ui/studio-map.js +397 -0
- package/ui/studio-nav.d.ts +24 -0
- package/ui/studio-nav.js +17 -0
- package/ui/studio-no-project.d.ts +5 -0
- package/ui/studio-no-project.js +9 -0
- package/ui/studio-run.css +76 -0
- package/ui/studio-run.d.ts +14 -0
- package/ui/studio-run.js +119 -0
- package/ui/studio-runner.d.ts +45 -0
- package/ui/studio-runner.js +187 -0
- package/ui/studio-save.d.ts +49 -0
- package/ui/studio-save.js +307 -0
- package/ui/studio-screen.d.ts +61 -0
- package/ui/studio-screen.js +2078 -0
- package/ui/studio-settings.d.ts +31 -0
- package/ui/studio-settings.js +288 -0
- package/ui/studio-shell.css +27 -0
- package/ui/studio-shell.d.ts +43 -0
- package/ui/studio-shell.js +87 -0
- package/ui/studio-theme.d.ts +7 -0
- package/ui/studio-theme.js +94 -0
- package/ui/studio.css +427 -0
- package/ui/theorem-mark.css +84 -0
- package/ui/theorem-mark.d.ts +22 -0
- package/ui/theorem-mark.js +21 -0
- package/vite.d.mts +30 -0
- package/vite.mjs +277 -0
- package/workspace.d.ts +230 -0
- package/workspace.js +1054 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asks a draft HTTP or MCP tool's endpoint one request, through the kernel's network guard, so the
|
|
3
|
+
* builder sees whether it answers before a model calls it.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { buildHttpToolTarget, credentialPlacement, errorKind, fetchGuarded, isUnsupportedMcpProtocolError, MCP_PROTOCOL_VERSIONS, parseMcpRpcResponse, TheoremError, withCredentialQuery, z, } from '@theoremjs/agents';
|
|
8
|
+
import { caughtStatus, clientError } from '@theoremjs/agents/host';
|
|
9
|
+
import { readBody } from '@theoremjs/react/server';
|
|
10
|
+
/** A draft's network guardrail, as the screen sends it beside the test. */
|
|
11
|
+
const draftNetworkSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
allowPrivateNetworks: z.boolean().optional(),
|
|
14
|
+
allowedHosts: z.array(z.string().min(1).max(253)).max(64).optional(),
|
|
15
|
+
allowedSchemes: z.array(z.string().min(1).max(32)).max(8).optional(),
|
|
16
|
+
})
|
|
17
|
+
.strict()
|
|
18
|
+
.optional();
|
|
19
|
+
function elapsedSince(start) {
|
|
20
|
+
return Date.now() - start;
|
|
21
|
+
}
|
|
22
|
+
/** Where the test credential goes: wherever the kernel would put the tool's own. */
|
|
23
|
+
function probeCredential(auth, testCredential) {
|
|
24
|
+
if (!auth?.type || !testCredential)
|
|
25
|
+
return { headers: {} };
|
|
26
|
+
// A pasted OAuth token is sent as the bearer token it is.
|
|
27
|
+
const credential = auth.type === 'api_key'
|
|
28
|
+
? { type: 'api_key', key: testCredential }
|
|
29
|
+
: { type: 'bearer', token: testCredential };
|
|
30
|
+
return credentialPlacement({
|
|
31
|
+
headerName: auth.headerName?.trim() || undefined,
|
|
32
|
+
headerPrefix: auth.headerPrefix,
|
|
33
|
+
queryParam: auth.queryParam?.trim() || undefined,
|
|
34
|
+
}, credential);
|
|
35
|
+
}
|
|
36
|
+
function isRedirect(res) {
|
|
37
|
+
return res.status >= 300 && res.status < 400;
|
|
38
|
+
}
|
|
39
|
+
function redirectResponse(res, start) {
|
|
40
|
+
const location = res.headers.get('location');
|
|
41
|
+
return Response.json({
|
|
42
|
+
ok: false,
|
|
43
|
+
status: res.status,
|
|
44
|
+
code: 'redirect',
|
|
45
|
+
error: location ? `Redirects to ${location}, which the test doesn't follow.` : 'Redirects.',
|
|
46
|
+
elapsedMs: elapsedSince(start),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** How long a probe waits for the whole response, body included. */
|
|
50
|
+
const TIMEOUT_MS = 10_000;
|
|
51
|
+
/** A probe whose address can't be called. */
|
|
52
|
+
const INVALID_URL = { code: 'invalid_url', error: "That isn't a web address the test can call." };
|
|
53
|
+
/** Why a probe got no answer, in the test's own words: the caught failure's text stays on the server. */
|
|
54
|
+
function unansweredReason(err) {
|
|
55
|
+
const kind = errorKind(err);
|
|
56
|
+
if (kind === 'blocked') {
|
|
57
|
+
return {
|
|
58
|
+
code: 'ssrf_blocked',
|
|
59
|
+
error: "The test doesn't call this address. It calls public web addresses only.",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (kind === 'request')
|
|
63
|
+
return INVALID_URL;
|
|
64
|
+
if (kind === 'timeout') {
|
|
65
|
+
return {
|
|
66
|
+
code: 'network_error',
|
|
67
|
+
error: `No complete response within ${String(TIMEOUT_MS / 1000)} s.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
code: 'network_error',
|
|
72
|
+
error: "The request didn't get a response. Check that the service is up.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** A probe that never got an answer: refused by the network guard, or failed on the way. */
|
|
76
|
+
function fetchErrorResponse(start, err) {
|
|
77
|
+
return Response.json({ ok: false, ...unansweredReason(err), elapsedMs: elapsedSince(start) });
|
|
78
|
+
}
|
|
79
|
+
/** How much of a response body a probe reads; enough for a large MCP tool list. */
|
|
80
|
+
const MAX_BODY_BYTES = 1_000_000;
|
|
81
|
+
/** Reads the body as text, stopping at `MAX_BODY_BYTES`. */
|
|
82
|
+
async function readCapped(res) {
|
|
83
|
+
if (!res.body)
|
|
84
|
+
return '';
|
|
85
|
+
const reader = res.body.getReader();
|
|
86
|
+
const decoder = new TextDecoder();
|
|
87
|
+
let text = '';
|
|
88
|
+
let bytes = 0;
|
|
89
|
+
try {
|
|
90
|
+
while (bytes < MAX_BODY_BYTES) {
|
|
91
|
+
const { done, value } = await reader.read();
|
|
92
|
+
if (done)
|
|
93
|
+
break;
|
|
94
|
+
bytes += value.byteLength;
|
|
95
|
+
text += decoder.decode(value, { stream: true });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await reader.cancel();
|
|
100
|
+
}
|
|
101
|
+
return text + decoder.decode();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Fetches `url` through the kernel's network guard and reads its body within `TIMEOUT_MS`.
|
|
105
|
+
* Redirects are never followed: the credential and headers are for `url`'s origin alone. A
|
|
106
|
+
* redirect comes back as the response.
|
|
107
|
+
*/
|
|
108
|
+
async function fetchWithTimeout(url, init, guard) {
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
const timeout = setTimeout(() => {
|
|
111
|
+
controller.abort();
|
|
112
|
+
}, TIMEOUT_MS);
|
|
113
|
+
try {
|
|
114
|
+
const res = await fetchGuarded(url, { ...init, signal: controller.signal }, { followRedirects: false, ...guard });
|
|
115
|
+
return { res, text: await readCapped(res) };
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (controller.signal.aborted) {
|
|
119
|
+
// labels-exempt: internal diagnostic; the visitor reads the test's own line for a timeout
|
|
120
|
+
throw new TheoremError('timeout', 'probe: no complete response in time');
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
clearTimeout(timeout);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function previewResponseBody(text, contentType) {
|
|
129
|
+
if (!contentType.includes('application/json'))
|
|
130
|
+
return text.slice(0, 300);
|
|
131
|
+
try {
|
|
132
|
+
return JSON.stringify(JSON.parse(text), null, 2).slice(0, 300);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return text.slice(0, 300);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Why the endpoint and the sample input made no URL, naming the path parameter left unfilled. The
|
|
140
|
+
* name is read from the endpoint the visitor sent; the caught failure's text stays on the server.
|
|
141
|
+
*/
|
|
142
|
+
function mappingReason(endpoint, pathParams, sampleInput) {
|
|
143
|
+
const named = [...endpoint.matchAll(/\{([a-zA-Z0-9_-]+)\}/g)].map(([, name]) => name);
|
|
144
|
+
const unlisted = named.find((name) => !pathParams.includes(name));
|
|
145
|
+
if (unlisted)
|
|
146
|
+
return `{${unlisted}} is in the endpoint, but isn't one of its path parameters.`;
|
|
147
|
+
const unfilled = named.find((name) => sampleInput[name] === undefined);
|
|
148
|
+
if (unfilled)
|
|
149
|
+
return `The sample input has no value for the path parameter {${unfilled}}.`;
|
|
150
|
+
return "The endpoint and the sample input don't make a URL. Start the endpoint with a fixed scheme and host, and keep . and .. out of its path parameters.";
|
|
151
|
+
}
|
|
152
|
+
async function handleHttpProbe(body, start, guard) {
|
|
153
|
+
if (typeof body.endpoint !== 'string' || !body.endpoint.trim()) {
|
|
154
|
+
return Response.json({ ok: false, error: 'Endpoint URL is required' }, { status: 400 });
|
|
155
|
+
}
|
|
156
|
+
const signed = probeCredential(body.auth, body.testCredential);
|
|
157
|
+
const headers = {
|
|
158
|
+
Accept: 'application/json, text/plain, */*',
|
|
159
|
+
...(body.headers ?? {}),
|
|
160
|
+
...signed.headers,
|
|
161
|
+
};
|
|
162
|
+
const method = (body.method?.toUpperCase() || 'GET');
|
|
163
|
+
const sampleInput = body.sampleInput ?? {};
|
|
164
|
+
let requestUrl = body.endpoint;
|
|
165
|
+
let requestBody;
|
|
166
|
+
try {
|
|
167
|
+
const target = buildHttpToolTarget(body.endpoint, method, sampleInput, {
|
|
168
|
+
pathParams: body.pathParams,
|
|
169
|
+
queryParams: body.queryParams,
|
|
170
|
+
bodyParam: body.bodyParam,
|
|
171
|
+
});
|
|
172
|
+
requestUrl = withCredentialQuery(new URL(target.url), signed.query).href;
|
|
173
|
+
requestBody = target.body;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return Response.json({
|
|
177
|
+
ok: false,
|
|
178
|
+
code: 'invalid_mapping',
|
|
179
|
+
error: mappingReason(body.endpoint, body.pathParams ?? [], sampleInput),
|
|
180
|
+
elapsedMs: elapsedSince(start),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const { res, text } = await fetchWithTimeout(requestUrl, { method, headers, ...(method === 'GET' ? {} : { body: requestBody }) }, guard);
|
|
185
|
+
if (isRedirect(res))
|
|
186
|
+
return redirectResponse(res, start);
|
|
187
|
+
const contentType = res.headers.get('content-type') || '';
|
|
188
|
+
return Response.json({
|
|
189
|
+
ok: res.ok,
|
|
190
|
+
status: res.status,
|
|
191
|
+
statusText: res.statusText,
|
|
192
|
+
contentType,
|
|
193
|
+
preview: previewResponseBody(text, contentType),
|
|
194
|
+
elapsedMs: elapsedSince(start),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (fetchErr) {
|
|
198
|
+
return fetchErrorResponse(start, fetchErr);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** A `tools/list` call under one protocol version. */
|
|
202
|
+
function mcpToolsListRequest(baseHeaders, protocolVersion) {
|
|
203
|
+
const rpcPayload = {
|
|
204
|
+
jsonrpc: '2.0',
|
|
205
|
+
id: 'test-ping-1',
|
|
206
|
+
method: 'tools/list',
|
|
207
|
+
params: {
|
|
208
|
+
_meta: {
|
|
209
|
+
'io.modelcontextprotocol/protocolVersion': protocolVersion,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: { ...baseHeaders, 'MCP-Protocol-Version': protocolVersion },
|
|
216
|
+
body: JSON.stringify(rpcPayload),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/** A body that isn't JSON-RPC: a plain-text version refusal tries the next version, else it fails. */
|
|
220
|
+
function nonJsonAttempt(res, text, protocolVersion, start) {
|
|
221
|
+
if (!res.ok &&
|
|
222
|
+
text.toLowerCase().includes('unsupported protocol version') &&
|
|
223
|
+
protocolVersion !== MCP_PROTOCOL_VERSIONS.at(-1)) {
|
|
224
|
+
return { status: res.status, protocolError: { code: -32600, message: text.slice(0, 300) } };
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
response: Response.json({
|
|
228
|
+
ok: false,
|
|
229
|
+
status: res.status,
|
|
230
|
+
code: 'invalid_json',
|
|
231
|
+
error: `MCP server returned non-JSON response: ${text.slice(0, 200)}`,
|
|
232
|
+
elapsedMs: elapsedSince(start),
|
|
233
|
+
}),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** What a JSON-RPC answer to `tools/list` says: an MCP error, not MCP at all, or the tool list. */
|
|
237
|
+
function mcpAnswerResponse(body, res, rpcData, protocolVersion, start) {
|
|
238
|
+
if (rpcData.error) {
|
|
239
|
+
return Response.json({
|
|
240
|
+
ok: false,
|
|
241
|
+
status: res.status,
|
|
242
|
+
code: `mcp_error_${String(rpcData.error.code)}`,
|
|
243
|
+
error: rpcData.error.message,
|
|
244
|
+
elapsedMs: elapsedSince(start),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (rpcData.jsonrpc !== '2.0' || !Array.isArray(rpcData.result?.tools)) {
|
|
248
|
+
return Response.json({
|
|
249
|
+
ok: false,
|
|
250
|
+
status: res.status,
|
|
251
|
+
code: 'not_mcp',
|
|
252
|
+
error: "Answered, but not as an MCP server: tools/list didn't return a tool list.",
|
|
253
|
+
elapsedMs: elapsedSince(start),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const tools = rpcData.result.tools;
|
|
257
|
+
const toolNames = tools.map((t) => t.name);
|
|
258
|
+
const targetName = body.mcpToolName?.trim();
|
|
259
|
+
return Response.json({
|
|
260
|
+
ok: res.ok,
|
|
261
|
+
status: res.status,
|
|
262
|
+
protocolVersion,
|
|
263
|
+
toolCount: tools.length,
|
|
264
|
+
tools: toolNames,
|
|
265
|
+
targetToolFound: targetName ? toolNames.includes(targetName) : undefined,
|
|
266
|
+
preview: JSON.stringify(rpcData.result ?? rpcData, null, 2).slice(0, 300),
|
|
267
|
+
elapsedMs: elapsedSince(start),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/** Asks the server for its tools under one protocol version. */
|
|
271
|
+
async function attemptMcpProbe(body, url, baseHeaders, protocolVersion, start, guard) {
|
|
272
|
+
const { res, text } = await fetchWithTimeout(url, mcpToolsListRequest(baseHeaders, protocolVersion), guard);
|
|
273
|
+
if (isRedirect(res))
|
|
274
|
+
return { response: redirectResponse(res, start) };
|
|
275
|
+
let rpcData;
|
|
276
|
+
try {
|
|
277
|
+
rpcData = parseMcpRpcResponse(text);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return nonJsonAttempt(res, text, protocolVersion, start);
|
|
281
|
+
}
|
|
282
|
+
if (rpcData.error && isUnsupportedMcpProtocolError(rpcData.error)) {
|
|
283
|
+
return { status: res.status, protocolError: rpcData.error };
|
|
284
|
+
}
|
|
285
|
+
return { response: mcpAnswerResponse(body, res, rpcData, protocolVersion, start) };
|
|
286
|
+
}
|
|
287
|
+
async function handleMcpProbe(body, start, guard) {
|
|
288
|
+
if (typeof body.serverUrl !== 'string' || !body.serverUrl.trim()) {
|
|
289
|
+
return Response.json({ ok: false, error: 'Server URL is required' }, { status: 400 });
|
|
290
|
+
}
|
|
291
|
+
const signed = probeCredential(body.auth, body.testCredential);
|
|
292
|
+
const baseHeaders = {
|
|
293
|
+
'Content-Type': 'application/json',
|
|
294
|
+
Accept: 'application/json, text/event-stream',
|
|
295
|
+
...(body.headers ?? {}),
|
|
296
|
+
...signed.headers,
|
|
297
|
+
};
|
|
298
|
+
const serverUrl = URL.parse(body.serverUrl);
|
|
299
|
+
if (!serverUrl) {
|
|
300
|
+
return Response.json({ ok: false, ...INVALID_URL, elapsedMs: elapsedSince(start) });
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
const url = withCredentialQuery(serverUrl, signed.query).href;
|
|
304
|
+
let lastStatus = 0;
|
|
305
|
+
let lastProtocolError;
|
|
306
|
+
// Preferred version first; a server that refuses one is asked again under the next.
|
|
307
|
+
for (const protocolVersion of MCP_PROTOCOL_VERSIONS) {
|
|
308
|
+
const attempt = await attemptMcpProbe(body, url, baseHeaders, protocolVersion, start, guard);
|
|
309
|
+
if ('response' in attempt)
|
|
310
|
+
return attempt.response;
|
|
311
|
+
lastStatus = attempt.status;
|
|
312
|
+
lastProtocolError = attempt.protocolError;
|
|
313
|
+
}
|
|
314
|
+
return Response.json({
|
|
315
|
+
ok: false,
|
|
316
|
+
status: lastStatus || 400,
|
|
317
|
+
code: lastProtocolError
|
|
318
|
+
? `mcp_error_${String(lastProtocolError.code)}`
|
|
319
|
+
: 'mcp_protocol_error',
|
|
320
|
+
error: lastProtocolError?.message ?? 'MCP protocol negotiation failed',
|
|
321
|
+
elapsedMs: elapsedSince(start),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
catch (fetchErr) {
|
|
325
|
+
return fetchErrorResponse(start, fetchErr);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* `POST <base>/test-connection`: one request to a draft HTTP or MCP tool's endpoint. It reaches
|
|
330
|
+
* public hosts only, unless `draftNetwork` is set: then it keeps to the network guardrail the
|
|
331
|
+
* draft sends (`network`), as the draft's runs do. A name is checked against private addresses
|
|
332
|
+
* only through `resolveHost`; without it, only the address as written is.
|
|
333
|
+
*/
|
|
334
|
+
export async function testConnection(request, options = {}) {
|
|
335
|
+
const start = Date.now();
|
|
336
|
+
try {
|
|
337
|
+
const rawBody = await readBody(request, z.unknown());
|
|
338
|
+
if (!rawBody || typeof rawBody !== 'object' || !('type' in rawBody)) {
|
|
339
|
+
return Response.json({ ok: false, error: 'Missing type in test connection request' }, { status: 400 });
|
|
340
|
+
}
|
|
341
|
+
const { type } = rawBody;
|
|
342
|
+
if (type !== 'http' && type !== 'mcp') {
|
|
343
|
+
return Response.json({ ok: false, error: 'Type must be http or mcp' }, { status: 400 });
|
|
344
|
+
}
|
|
345
|
+
const body = rawBody;
|
|
346
|
+
const sent = draftNetworkSchema.safeParse(rawBody.network);
|
|
347
|
+
if (!sent.success) {
|
|
348
|
+
throw new TheoremError('request', 'test connection: network is not a draft network guardrail'); // labels-exempt: internal diagnostic
|
|
349
|
+
}
|
|
350
|
+
const network = sent.data;
|
|
351
|
+
const guard = {
|
|
352
|
+
...(options.resolveHost ? { resolveHost: options.resolveHost } : {}),
|
|
353
|
+
...(options.draftNetwork && network ? { policy: network } : {}),
|
|
354
|
+
};
|
|
355
|
+
if (body.type === 'http')
|
|
356
|
+
return await handleHttpProbe(body, start, guard);
|
|
357
|
+
return await handleMcpProbe(body, start, guard);
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
return Response.json({ ok: false, ...clientError(err) }, { status: caughtStatus(err) });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asks a key's provider whether it accepts the key, so the builder can tell a bad key
|
|
3
|
+
* from a bad setting. The key is used for this one request: never logged, kept, or echoed back.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from '@theoremjs/agents';
|
|
6
|
+
import { readBody } from '@theoremjs/react/server';
|
|
7
|
+
const TIMEOUT_MS = 8000;
|
|
8
|
+
const MAX_KEY_LENGTH = 512;
|
|
9
|
+
const SAID_CAP = 300;
|
|
10
|
+
const PROVIDERS = [
|
|
11
|
+
{
|
|
12
|
+
provider: 'google',
|
|
13
|
+
matches: (key) => key.startsWith('AIza'),
|
|
14
|
+
request: (key) => new Request('https://generativelanguage.googleapis.com/v1beta/models?pageSize=1', {
|
|
15
|
+
headers: { 'x-goog-api-key': key },
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
provider: 'openrouter',
|
|
20
|
+
matches: (key) => key.startsWith('sk-or-'),
|
|
21
|
+
request: (key) => new Request('https://openrouter.ai/api/v1/key', {
|
|
22
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
/** The provider's own error message, if it sent one, without the key. */
|
|
27
|
+
async function providerSaid(response, key) {
|
|
28
|
+
try {
|
|
29
|
+
const body = await response.json();
|
|
30
|
+
const error = body?.error;
|
|
31
|
+
const first = Array.isArray(error) ? error[0] : error;
|
|
32
|
+
const message = first?.message;
|
|
33
|
+
if (typeof message !== 'string')
|
|
34
|
+
return undefined;
|
|
35
|
+
return message.replaceAll(key, '[the key]').slice(0, SAID_CAP);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** `POST <base>/test-key` `{ key }`. */
|
|
42
|
+
export async function testKey(request) {
|
|
43
|
+
if (request.method !== 'POST')
|
|
44
|
+
return new Response(null, { status: 405 });
|
|
45
|
+
let key = '';
|
|
46
|
+
try {
|
|
47
|
+
const body = await readBody(request, z.unknown());
|
|
48
|
+
const value = body?.key;
|
|
49
|
+
key = typeof value === 'string' ? value.trim() : '';
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return Response.json({ ok: false, error: 'Send { key }.' }, { status: 400 });
|
|
53
|
+
}
|
|
54
|
+
if (!key || key.length > MAX_KEY_LENGTH) {
|
|
55
|
+
return Response.json({ ok: false, error: 'No key to test.' }, {
|
|
56
|
+
status: 400,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const match = PROVIDERS.find((candidate) => candidate.matches(key));
|
|
60
|
+
if (!match) {
|
|
61
|
+
return Response.json({
|
|
62
|
+
ok: false,
|
|
63
|
+
error: 'Only Google (AIza…) and OpenRouter (sk-or-…) keys can be tested here.',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(match.request(key), { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
68
|
+
const result = {
|
|
69
|
+
ok: response.ok,
|
|
70
|
+
provider: match.provider,
|
|
71
|
+
status: response.status,
|
|
72
|
+
...(response.ok ? {} : { said: await providerSaid(response, key) }),
|
|
73
|
+
};
|
|
74
|
+
return Response.json(result);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return Response.json({
|
|
78
|
+
ok: false,
|
|
79
|
+
provider: match.provider,
|
|
80
|
+
error: `Couldn't reach ${match.provider}.`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** The changes under some folders, as `Deno.watchFs` gives them. */
|
|
2
|
+
export interface FolderWatch extends AsyncIterable<unknown> {
|
|
3
|
+
close(): void;
|
|
4
|
+
}
|
|
5
|
+
export interface FileWatchOptions {
|
|
6
|
+
/** The files to watch, by absolute path. Read again after every change, since an import can add one. */
|
|
7
|
+
files(): string[];
|
|
8
|
+
/** The files that hold something other than what the project loaded from, named for the builder. */
|
|
9
|
+
changed(): Promise<string[]>;
|
|
10
|
+
/** Called with those files, once per settled run of writes. */
|
|
11
|
+
tell(files: string[]): void;
|
|
12
|
+
/** Watches folders, not their subfolders. */
|
|
13
|
+
watch(folders: string[]): FolderWatch;
|
|
14
|
+
/** How long the files must be still before the page is told, in milliseconds. */
|
|
15
|
+
settle?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Starts watching, and answers how to stop. An editor that saves by writing a new file over the
|
|
19
|
+
* old one never touches the old one, so the folders are watched and the files are compared.
|
|
20
|
+
*/
|
|
21
|
+
export declare function watchFiles(options: FileWatchOptions): {
|
|
22
|
+
stop(): void;
|
|
23
|
+
};
|
|
24
|
+
/** The page's subscriptions to file changes, as server-sent events. */
|
|
25
|
+
export declare function createWatchFeed(): {
|
|
26
|
+
tell(files: string[]): void;
|
|
27
|
+
subscribe(headers: Record<string, string>): Response;
|
|
28
|
+
};
|
package/server/watch.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watching a project's files for the studio's page: when the builder's editor writes one the
|
|
3
|
+
* project's setup reads, the page is told which, once the writes settle.
|
|
4
|
+
*/
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
|
+
/**
|
|
7
|
+
* Starts watching, and answers how to stop. An editor that saves by writing a new file over the
|
|
8
|
+
* old one never touches the old one, so the folders are watched and the files are compared.
|
|
9
|
+
*/
|
|
10
|
+
export function watchFiles(options) {
|
|
11
|
+
const settle = options.settle ?? 150;
|
|
12
|
+
const folders = () => [...new Set(options.files().map((file) => dirname(file)))].sort();
|
|
13
|
+
let stopped = false;
|
|
14
|
+
let watching;
|
|
15
|
+
let timer;
|
|
16
|
+
const settled = async (over) => {
|
|
17
|
+
const files = await options.changed().catch(() => []);
|
|
18
|
+
if (stopped)
|
|
19
|
+
return;
|
|
20
|
+
if (files.length)
|
|
21
|
+
options.tell(files);
|
|
22
|
+
// A file in a folder not yet watched: watch again from there.
|
|
23
|
+
if (folders().join('\n') !== over)
|
|
24
|
+
watching?.close();
|
|
25
|
+
};
|
|
26
|
+
const run = async () => {
|
|
27
|
+
while (!stopped) {
|
|
28
|
+
const over = folders();
|
|
29
|
+
try {
|
|
30
|
+
watching = options.watch(over);
|
|
31
|
+
for await (const _ of watching) {
|
|
32
|
+
if (timer !== undefined)
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
timer = setTimeout(() => void settled(over.join('\n')), settle);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// A folder that went away is watched again when it is back.
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
void run();
|
|
44
|
+
return {
|
|
45
|
+
stop: () => {
|
|
46
|
+
stopped = true;
|
|
47
|
+
if (timer !== undefined)
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
watching?.close();
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** The page's subscriptions to file changes, as server-sent events. */
|
|
54
|
+
export function createWatchFeed() {
|
|
55
|
+
const encoder = new TextEncoder();
|
|
56
|
+
const pages = new Set();
|
|
57
|
+
const send = (page, text) => {
|
|
58
|
+
try {
|
|
59
|
+
page.enqueue(encoder.encode(text));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
pages.delete(page);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
tell: (files) => {
|
|
67
|
+
for (const page of pages)
|
|
68
|
+
send(page, `data: ${JSON.stringify({ files })}\n\n`);
|
|
69
|
+
},
|
|
70
|
+
subscribe: (headers) => {
|
|
71
|
+
let mine;
|
|
72
|
+
let beat;
|
|
73
|
+
const body = new ReadableStream({
|
|
74
|
+
start(controller) {
|
|
75
|
+
mine = controller;
|
|
76
|
+
pages.add(controller);
|
|
77
|
+
send(controller, ': watching\n\n');
|
|
78
|
+
// A line now and then keeps a quiet connection open.
|
|
79
|
+
beat = setInterval(() => send(controller, ': still watching\n\n'), 25_000);
|
|
80
|
+
},
|
|
81
|
+
cancel() {
|
|
82
|
+
if (mine)
|
|
83
|
+
pages.delete(mine);
|
|
84
|
+
if (beat !== undefined)
|
|
85
|
+
clearInterval(beat);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
return new Response(body, {
|
|
89
|
+
headers: { ...headers, 'content-type': 'text/event-stream', 'cache-control': 'no-cache' },
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tool's schema as the builder's file writes it in Zod: the `z.` call it starts from, each
|
|
3
|
+
* method called on it, and the object its fields are written in. Save reads a schema this way to
|
|
4
|
+
* change one part of it where that part is written, and writes a new part as Zod.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import ts from 'typescript';
|
|
9
|
+
import { type SourceStyle } from '../source.ts';
|
|
10
|
+
import { type Env, type Located, type ProjectSource, type ShapeEntry } from './project-source.ts';
|
|
11
|
+
/** One call in a schema's expression: `z.string()`, `.describe('…')`. */
|
|
12
|
+
export interface ZodCall {
|
|
13
|
+
/** The method called. */
|
|
14
|
+
name: string;
|
|
15
|
+
call: ts.CallExpression;
|
|
16
|
+
source: ts.SourceFile;
|
|
17
|
+
env?: Env;
|
|
18
|
+
}
|
|
19
|
+
/** A schema as the files write it. */
|
|
20
|
+
export interface ZodRead {
|
|
21
|
+
/** The `z.` call it starts from. */
|
|
22
|
+
base: ZodCall;
|
|
23
|
+
/** Each method called on it since, the last one first. */
|
|
24
|
+
chain: ZodCall[];
|
|
25
|
+
/** A name stands for some of it: a constant, a function's parameter. */
|
|
26
|
+
named: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Which side of a tool a schema checks. The kernel reads the two a little differently. */
|
|
29
|
+
export type SchemaSide = 'input' | 'output';
|
|
30
|
+
/** The name `source` calls Zod by, to write a new schema in it. */
|
|
31
|
+
export declare function zodName(source: ts.SourceFile): string | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Reads the schema an expression stands for. A name is followed to the constant it stands for,
|
|
34
|
+
* and a project function to what it returns. Undefined when it does not lead to a `z.` call.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readZod(project: ProjectSource, at: Located): ZodRead | undefined;
|
|
37
|
+
/** The first method the studio does not read a schema through: what it returns is for code to say. */
|
|
38
|
+
export declare function unread(read: ZodRead): ZodCall | undefined;
|
|
39
|
+
/** The last call of a method on a schema: the one that counts. */
|
|
40
|
+
export declare function methodCall(read: ZodRead, name: string): ZodCall | undefined;
|
|
41
|
+
/** The text of a method call with its dot, from the end of what it is called on. */
|
|
42
|
+
export declare function methodSpan(call: ZodCall): {
|
|
43
|
+
start: number;
|
|
44
|
+
end: number;
|
|
45
|
+
};
|
|
46
|
+
/** An object schema's fields, as the files write them. */
|
|
47
|
+
export interface ZodObject {
|
|
48
|
+
/** Each field by name. One an `.extend()` writes again is the one held. */
|
|
49
|
+
entries: Map<string, ShapeEntry>;
|
|
50
|
+
/** The object a new field is written in: the last `.extend()`, or the `z.object()` itself. */
|
|
51
|
+
own: {
|
|
52
|
+
node: ts.ObjectLiteralExpression;
|
|
53
|
+
source: ts.SourceFile;
|
|
54
|
+
};
|
|
55
|
+
/** Set when an `.extend()` adds to the fields. */
|
|
56
|
+
extended: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The fields of an object schema. Undefined for another kind of schema, and for an object whose
|
|
60
|
+
* fields the studio cannot list: a spread it could not follow, a key the file computes.
|
|
61
|
+
*/
|
|
62
|
+
export declare function zodObject(project: ProjectSource, read: ZodRead): ZodObject | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Whether a schema is written out in full where it stands, in Zod the studio writes itself. Such
|
|
65
|
+
* a schema can be written again from its JSON Schema with nothing lost: no name stands for a part
|
|
66
|
+
* of it, and it has no check or change that only its code holds.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isWrittenOut(project: ProjectSource, at: Located): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* A JSON Schema as Zod, laid out in `style`, with `name` for Zod. `optional` writes a field its
|
|
71
|
+
* object can leave out. Undefined when the studio has no Zod for it, or when the kernel would
|
|
72
|
+
* not read that Zod back as the schema the builder ran: Save then leaves it for code.
|
|
73
|
+
*/
|
|
74
|
+
export declare function zodSource(schema: unknown, optional: boolean, side: SchemaSide, name: string, style?: SourceStyle): string | undefined;
|