@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
package/compile.js
ADDED
|
@@ -0,0 +1,1605 @@
|
|
|
1
|
+
import { defineProfile, describeError, liveIngressChannelDefault, TheoremError, } from '@theoremjs/agents';
|
|
2
|
+
import { BOUNDARIES, DETECT_DEFAULTS, DETECTOR_META, DETECTORS, detectProblem, hintProblem, patternListProblem, TOOL_BOUNDARIES, validateLabelOverrides, } from '@theoremjs/agents/guardrails';
|
|
3
|
+
import { activityLabelProblem, agentToolInputFor, agentToolOutput, jsonSchemaFromZod, mimeAllowed, outOfScopeFields, systemPromptProblem, } from '@theoremjs/agents/kernel';
|
|
4
|
+
import { resolveObservabilityPolicy } from '@theoremjs/agents/observability';
|
|
5
|
+
import { GOOGLE_SPEECH_FORMATS, GOOGLE_THINKING_LEVELS } from '@theoremjs/agents/presets/google';
|
|
6
|
+
import { HTTP_METHODS, IMAGE_ATTACHMENT_ACCEPT_MIMES, PROFILE_HANDLE_MAX_CHARS, PROFILE_ID_MAX_CHARS, } from '@theoremjs/agents/schema';
|
|
7
|
+
import { isCredentialName, MASK } from '@theoremjs/agents/surface';
|
|
8
|
+
import { STUDIO_KEY_SLOT_CAP } from "./browser-connection.js";
|
|
9
|
+
import { COMPACTION_DRAFT_DEFAULTS, draftAllows, draftFacets, INLINE_LABELS, takesContinueInstruction, } from "./draft.js";
|
|
10
|
+
import { isGoogleRoute, isProviderBuiltinId, modelBindingViolation } from "./policy.js";
|
|
11
|
+
import { modelRoute, servedProtocols, servesProfileType, } from "./providers.js";
|
|
12
|
+
import { bindingSlots, defaultEffortRequired, defaultModelRequired, inputLimitsRequired, } from "./requirements.js";
|
|
13
|
+
import { parseSystemMarkup } from "./system-markup.js";
|
|
14
|
+
import { parseJsonSchema } from "./tool-schema.js";
|
|
15
|
+
import { modelBindingNodeId, toolSpecNodeId } from "./tree.js";
|
|
16
|
+
import { STUDIO_TOOL_TYPE_MESSAGE, STUDIO_TOOL_TYPES } from "./types.js";
|
|
17
|
+
const NO_AGENTS = () => undefined;
|
|
18
|
+
const NO_AGENT_INPUTS = () => undefined;
|
|
19
|
+
function cleanList(list) {
|
|
20
|
+
return (list ?? []).map((item) => item.trim()).filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
/** Reports `field`, captioned `label`, unless `value` is unset or a whole number ≥ `min`. */
|
|
23
|
+
function checkWhole(report, nodeId, field, label, value, min) {
|
|
24
|
+
if (value === null || (Number.isInteger(value) && value >= min))
|
|
25
|
+
return;
|
|
26
|
+
report(nodeId, `${label} must be a ${min ? 'positive' : 'non-negative'} whole number.`, field);
|
|
27
|
+
}
|
|
28
|
+
const NAME_LIMITS = {
|
|
29
|
+
agentId: { label: 'Profile id', max: PROFILE_ID_MAX_CHARS },
|
|
30
|
+
handle: { label: 'Handle', max: PROFILE_HANDLE_MAX_CHARS },
|
|
31
|
+
};
|
|
32
|
+
/** A name is set, and fits the kernel's limit for it. */
|
|
33
|
+
function checkName(report, field, name) {
|
|
34
|
+
const { label, max } = NAME_LIMITS[field];
|
|
35
|
+
if (!name.trim()) {
|
|
36
|
+
report('identity', `${label} is required.`, field);
|
|
37
|
+
}
|
|
38
|
+
else if (Array.from(name).length > max) {
|
|
39
|
+
report('identity', `${label} is at most ${max} characters.`, field);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function checkIdentity(draft, report) {
|
|
43
|
+
checkName(report, 'agentId', draft.identity.agentId);
|
|
44
|
+
// A host has no agent identity: it runs no model.
|
|
45
|
+
if (draft.identity.profileType !== 'host') {
|
|
46
|
+
checkName(report, 'handle', draft.identity.handle);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** The model's provider is one the workspace has, and it serves this kind of agent on the model's protocol. */
|
|
50
|
+
function checkBindingRoute(binding, type, providers, report) {
|
|
51
|
+
const nodeId = modelBindingNodeId(binding.key);
|
|
52
|
+
const route = modelRoute(providers, type, { ...binding, apiId: binding.apiId.trim() });
|
|
53
|
+
const provider = providers.find((each) => each.id === binding.provider);
|
|
54
|
+
if (!provider) {
|
|
55
|
+
report(nodeId, `No provider is defined as ${binding.provider}.`, 'provider');
|
|
56
|
+
}
|
|
57
|
+
else if (!servesProfileType(provider, type)) {
|
|
58
|
+
report(nodeId, `${binding.provider} doesn't serve ${type} agents.`, 'provider');
|
|
59
|
+
}
|
|
60
|
+
else if (!servesProfileType(provider, type, binding.protocol)) {
|
|
61
|
+
report(nodeId, `${binding.provider} doesn't serve ${type} agents on ${binding.protocol}.`, 'protocol');
|
|
62
|
+
}
|
|
63
|
+
if (!route.apiId) {
|
|
64
|
+
report(nodeId, 'API model is required.', 'apiId');
|
|
65
|
+
}
|
|
66
|
+
return route;
|
|
67
|
+
}
|
|
68
|
+
/** The binding's efforts, keyed by alias, and its default; each problem is reported on the binding. */
|
|
69
|
+
function compileEfforts(binding, route, nodeId, report) {
|
|
70
|
+
const efforts = {};
|
|
71
|
+
binding.efforts.forEach(({ alias, level }, index) => {
|
|
72
|
+
const name = alias.trim();
|
|
73
|
+
if (!name)
|
|
74
|
+
report(nodeId, 'Every effort needs an alias.', 'efforts', index);
|
|
75
|
+
else if (name in efforts) {
|
|
76
|
+
report(nodeId, `Effort alias '${name}' is used twice.`, 'efforts', index);
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
efforts[name] = level;
|
|
80
|
+
if (isGoogleRoute(route) && !GOOGLE_THINKING_LEVELS.includes(level)) {
|
|
81
|
+
report(nodeId, `${binding.provider} doesn't take the ${level} thinking level.`, 'efforts', index);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
if (binding.allowEffortSelect && Object.keys(efforts).length < 2) {
|
|
85
|
+
report(nodeId, 'Effort select needs at least two efforts.', 'allowEffortSelect');
|
|
86
|
+
}
|
|
87
|
+
const defaultEffort = binding.defaultEffort.trim();
|
|
88
|
+
if (!defaultEffort && defaultEffortRequired(binding)) {
|
|
89
|
+
report(nodeId, 'Pick a default effort — there is more than one.', 'defaultEffort');
|
|
90
|
+
}
|
|
91
|
+
else if (defaultEffort && !(defaultEffort in efforts)) {
|
|
92
|
+
report(nodeId, `Default effort '${defaultEffort}' is not one of the efforts.`, 'defaultEffort');
|
|
93
|
+
}
|
|
94
|
+
return { efforts, defaultEffort };
|
|
95
|
+
}
|
|
96
|
+
function compileBinding(binding, type, providers, report, agentIdOf) {
|
|
97
|
+
const nodeId = modelBindingNodeId(binding.key);
|
|
98
|
+
const route = checkBindingRoute(binding, type, providers, report);
|
|
99
|
+
const { apiId } = route;
|
|
100
|
+
const { efforts, defaultEffort } = compileEfforts(binding, route, nodeId, report);
|
|
101
|
+
const effortCount = Object.keys(efforts).length;
|
|
102
|
+
checkWhole(report, nodeId, 'maxOutputTokens', 'Max output tokens', binding.maxOutputTokens, 1);
|
|
103
|
+
if (binding.temperature !== null &&
|
|
104
|
+
!(Number.isFinite(binding.temperature) && binding.temperature >= 0)) {
|
|
105
|
+
report(nodeId, 'Temperature must be zero or more.', 'temperature');
|
|
106
|
+
}
|
|
107
|
+
checkOptions(binding, providers, type, nodeId, report);
|
|
108
|
+
const compaction = compileCompaction(binding, type, nodeId, report, agentIdOf);
|
|
109
|
+
return {
|
|
110
|
+
protocol: binding.protocol,
|
|
111
|
+
provider: binding.provider,
|
|
112
|
+
apiId,
|
|
113
|
+
...(binding.keySlot ? { keySlot: binding.keySlot } : {}),
|
|
114
|
+
...(binding.fallbackKeySlot ? { fallbackKeySlot: binding.fallbackKeySlot } : {}),
|
|
115
|
+
...(effortCount ? { efforts } : {}),
|
|
116
|
+
...(defaultEffort ? { defaultEffort } : {}),
|
|
117
|
+
...(binding.allowEffortSelect ? { allowEffortSelect: true } : {}),
|
|
118
|
+
...(binding.summaries !== null ? { summaries: binding.summaries } : {}),
|
|
119
|
+
...(binding.maxOutputTokens !== null ? { maxOutputTokens: binding.maxOutputTokens } : {}),
|
|
120
|
+
...(binding.temperature !== null ? { temperature: binding.temperature } : {}),
|
|
121
|
+
...(binding.builtInTools.length ? { builtInTools: [...binding.builtInTools] } : {}),
|
|
122
|
+
...(Object.keys(binding.options).length ? { options: binding.options } : {}),
|
|
123
|
+
...(compaction ? { compaction } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The binding's options, as its protocol's own schema reads them, each problem at the option it is
|
|
128
|
+
* about. A protocol the studio does not hold is the project's to judge.
|
|
129
|
+
*/
|
|
130
|
+
function checkOptions(binding, providers, type, nodeId, report) {
|
|
131
|
+
const provider = providers.find((each) => each.id === binding.provider);
|
|
132
|
+
const protocol = provider && servedProtocols(provider, type)?.find((each) => each.id === binding.protocol);
|
|
133
|
+
for (const issue of protocol?.options.safeParse(binding.options).error?.issues ?? []) {
|
|
134
|
+
const name = issue.code === 'unrecognized_keys' ? issue.keys[0] : issue.path[0];
|
|
135
|
+
report(nodeId, issue.message, typeof name === 'string' ? `options.${name}` : 'options');
|
|
136
|
+
}
|
|
137
|
+
const apiId = binding.apiId.trim();
|
|
138
|
+
for (const key of apiId ? (protocol?.refusedOptions?.(apiId) ?? []) : []) {
|
|
139
|
+
if (binding.options[key] !== undefined)
|
|
140
|
+
report(nodeId, `${apiId} takes no ${key}.`, `options.${key}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** The agent compacts its own history, or another agent of the workspace writes the summary. */
|
|
144
|
+
function compileCompaction(binding, type, nodeId, report, agentIdOf) {
|
|
145
|
+
const timing = binding.compactTiming;
|
|
146
|
+
if (!timing)
|
|
147
|
+
return undefined;
|
|
148
|
+
if (type !== 'text') {
|
|
149
|
+
report(nodeId, 'Only a text agent compacts its own history.', 'compactTiming');
|
|
150
|
+
}
|
|
151
|
+
const maxTokens = binding.compactMaxTokens ?? null;
|
|
152
|
+
const compactAt = binding.compactAt ?? COMPACTION_DRAFT_DEFAULTS.compactAt;
|
|
153
|
+
const keep = binding.compactKeep ?? null;
|
|
154
|
+
if (maxTokens === null)
|
|
155
|
+
report(nodeId, 'Compaction needs a budget.', 'compactMaxTokens');
|
|
156
|
+
checkWhole(report, nodeId, 'compactMaxTokens', 'Budget', maxTokens, 1);
|
|
157
|
+
if (!(compactAt > 0 && compactAt < 1)) {
|
|
158
|
+
report(nodeId, 'Compaction must start between 0 and 1 of the budget.', 'compactAt');
|
|
159
|
+
}
|
|
160
|
+
const keepValid = keep !== null &&
|
|
161
|
+
(keep === 0 || (Number.isInteger(keep) && keep >= 1) || (keep > 0 && keep < compactAt));
|
|
162
|
+
if (!keepValid) {
|
|
163
|
+
report(nodeId, 'Keep a whole number of exchanges, a fraction of the budget below where compaction starts, or 0.', 'compactKeep');
|
|
164
|
+
}
|
|
165
|
+
const compactWith = binding.compactWith ?? '';
|
|
166
|
+
const profile = compactWith ? agentIdOf(compactWith) : undefined;
|
|
167
|
+
if (compactWith && !profile) {
|
|
168
|
+
report(nodeId, 'The agent that summarised is gone. Pick another, or this agent.', 'compactWith');
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
maxTokens: maxTokens ?? 0,
|
|
172
|
+
compactAt,
|
|
173
|
+
...(profile ? { profile } : {}),
|
|
174
|
+
previousExchanges: keep ?? 0,
|
|
175
|
+
timing,
|
|
176
|
+
...(binding.compactMeter ? { meter: binding.compactMeter } : {}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function validateModelPolicy(draft, models, report) {
|
|
180
|
+
const { models: policy, modelBindings } = draft;
|
|
181
|
+
if (!modelBindings.length)
|
|
182
|
+
report('models', 'Add at least one model.');
|
|
183
|
+
const defaultModel = policy.defaultModel.trim();
|
|
184
|
+
if (defaultModel && !(defaultModel in models)) {
|
|
185
|
+
report('models', `Default model '${defaultModel}' is not one of the models.`, 'defaultModel');
|
|
186
|
+
}
|
|
187
|
+
else if (!defaultModel && defaultModelRequired(draft)) {
|
|
188
|
+
report('models', 'Pick a default model — there is more than one.', 'defaultModel');
|
|
189
|
+
}
|
|
190
|
+
if (policy.maxSteps !== null && !Number.isInteger(policy.maxSteps)) {
|
|
191
|
+
report('models', 'Max steps must be a whole number.', 'maxSteps');
|
|
192
|
+
}
|
|
193
|
+
return defaultModel;
|
|
194
|
+
}
|
|
195
|
+
function compileModels(draft, type, report, agentIdOf) {
|
|
196
|
+
const { models: policy, modelBindings } = draft;
|
|
197
|
+
const models = {};
|
|
198
|
+
for (const binding of modelBindings) {
|
|
199
|
+
const compiled = compileBinding({
|
|
200
|
+
...binding,
|
|
201
|
+
keySlot: binding.keySlot || policy.key || undefined,
|
|
202
|
+
fallbackKeySlot: binding.fallbackKeySlot || policy.fallbackKey || undefined,
|
|
203
|
+
}, type, draft.providers, report, agentIdOf);
|
|
204
|
+
const modelId = binding.modelId.trim();
|
|
205
|
+
const nodeId = modelBindingNodeId(binding.key);
|
|
206
|
+
if (!modelId)
|
|
207
|
+
report(nodeId, 'Model id is required.', 'modelId');
|
|
208
|
+
else if (modelId in models) {
|
|
209
|
+
report(nodeId, `Model id '${modelId}' is used twice.`, 'modelId');
|
|
210
|
+
}
|
|
211
|
+
else
|
|
212
|
+
models[modelId] = compiled;
|
|
213
|
+
}
|
|
214
|
+
const defaultModel = validateModelPolicy(draft, models, report);
|
|
215
|
+
const heldElsewhere = policy.providerHistory === 'provider' || policy.providerHistory === 'stateless'
|
|
216
|
+
? policy.providerHistory
|
|
217
|
+
: undefined;
|
|
218
|
+
return {
|
|
219
|
+
models,
|
|
220
|
+
...(defaultModel ? { defaultModel } : {}),
|
|
221
|
+
...(heldElsewhere ? { providerContinuation: { history: heldElsewhere } } : {}),
|
|
222
|
+
...(policy.allowModelSelect ? { allowModelSelect: true } : {}),
|
|
223
|
+
...(policy.maxSteps !== null ? { maxSteps: policy.maxSteps } : {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A JSON object typed in a field: `undefined` when blank or empty, `null` when it isn't an
|
|
228
|
+
* object or a value fails `isValue`.
|
|
229
|
+
*/
|
|
230
|
+
function parseRecord(raw, isValue) {
|
|
231
|
+
if (!raw?.trim())
|
|
232
|
+
return undefined;
|
|
233
|
+
let parsed;
|
|
234
|
+
try {
|
|
235
|
+
parsed = JSON.parse(raw);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
241
|
+
return null;
|
|
242
|
+
const entries = Object.entries(parsed);
|
|
243
|
+
if (!entries.every(([, value]) => isValue(value)))
|
|
244
|
+
return null;
|
|
245
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
246
|
+
}
|
|
247
|
+
const isString = (value) => typeof value === 'string';
|
|
248
|
+
const isAny = (_value) => true;
|
|
249
|
+
function parseHeaders(raw) {
|
|
250
|
+
return parseRecord(raw, isString);
|
|
251
|
+
}
|
|
252
|
+
const isSystemPrompt = (value) => systemPromptProblem(value, 'system') === undefined;
|
|
253
|
+
/** Each role's text takes `{private: …}` sections like the system prompt; a list of parts passes as written. */
|
|
254
|
+
function compileSystemByRole(raw, report) {
|
|
255
|
+
const byRole = parseRecord(raw, isSystemPrompt);
|
|
256
|
+
if (byRole === null) {
|
|
257
|
+
report('Instructions by role must be a JSON object of text, or of lists of text and { "private": text }.');
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
if (!byRole)
|
|
261
|
+
return undefined;
|
|
262
|
+
const compiled = {};
|
|
263
|
+
for (const [role, prompt] of Object.entries(byRole)) {
|
|
264
|
+
if (typeof prompt !== 'string') {
|
|
265
|
+
compiled[role] = prompt;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const parsed = parseSystemMarkup(prompt);
|
|
269
|
+
if (!parsed.ok) {
|
|
270
|
+
report(`${role}: ${parsed.message}`);
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
if (parsed.prompt !== undefined)
|
|
274
|
+
compiled[role] = parsed.prompt;
|
|
275
|
+
}
|
|
276
|
+
return compiled;
|
|
277
|
+
}
|
|
278
|
+
/** A record field's value, reporting `message` on `field` when it doesn't parse. */
|
|
279
|
+
function recordField(raw, isValue, report) {
|
|
280
|
+
const parsed = parseRecord(raw, isValue);
|
|
281
|
+
if (parsed === null)
|
|
282
|
+
report();
|
|
283
|
+
return parsed ?? undefined;
|
|
284
|
+
}
|
|
285
|
+
function compileAuth(tool, fail) {
|
|
286
|
+
if (!tool.authType || tool.authType === 'none')
|
|
287
|
+
return undefined;
|
|
288
|
+
const service = tool.authService?.trim() ?? '';
|
|
289
|
+
if (!service)
|
|
290
|
+
fail('Service is required.', 'authService');
|
|
291
|
+
const scopes = cleanList(tool.authScopes);
|
|
292
|
+
const queryParam = tool.authType === 'api_key' ? tool.authQueryParam?.trim() : undefined;
|
|
293
|
+
if (queryParam && cleanList(tool.queryParams).includes(queryParam)) {
|
|
294
|
+
fail(`${queryParam} is also one of the query parameters the model fills.`, 'authQueryParam');
|
|
295
|
+
}
|
|
296
|
+
const placement = queryParam
|
|
297
|
+
? { queryParam }
|
|
298
|
+
: {
|
|
299
|
+
...(tool.authHeaderName?.trim() ? { headerName: tool.authHeaderName.trim() } : {}),
|
|
300
|
+
...(tool.authHeaderPrefix !== undefined ? { headerPrefix: tool.authHeaderPrefix } : {}),
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
slot: tool.authSlot?.trim() || 'default',
|
|
304
|
+
type: tool.authType,
|
|
305
|
+
service,
|
|
306
|
+
...placement,
|
|
307
|
+
onUnauthenticated: tool.authUnauthenticated ?? 'gate',
|
|
308
|
+
...(scopes.length ? { scopes } : {}),
|
|
309
|
+
...(tool.authClientId?.trim() ? { clientId: tool.authClientId.trim() } : {}),
|
|
310
|
+
...(tool.authRedirectUri?.trim() ? { redirectUri: tool.authRedirectUri.trim() } : {}),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function isUrl(raw) {
|
|
314
|
+
try {
|
|
315
|
+
new URL(raw);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function checkToolName(name, fail) {
|
|
323
|
+
if (!name)
|
|
324
|
+
fail('Tool name is required.', 'toolName');
|
|
325
|
+
else if (isProviderBuiltinId(name)) {
|
|
326
|
+
fail(`${name} is a provider builtin — turn it on under the model's built-in tools.`, 'toolName');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function toolCommon(tool, fail, agentInputOf) {
|
|
330
|
+
const name = tool.toolName.trim();
|
|
331
|
+
checkToolName(name, fail);
|
|
332
|
+
const description = tool.description.trim();
|
|
333
|
+
if (!description)
|
|
334
|
+
fail('Description is required.', 'description');
|
|
335
|
+
const schemas = tool.toolType === 'agent'
|
|
336
|
+
? {
|
|
337
|
+
inputSchema: agentInputOf(tool.agentKey ?? '') ?? AGENT_TOOL_TEXT_INPUT,
|
|
338
|
+
outputSchema: AGENT_TOOL_OUTPUT,
|
|
339
|
+
}
|
|
340
|
+
: toolSchemas(tool, fail);
|
|
341
|
+
const paths = cleanList(tool.paths);
|
|
342
|
+
return {
|
|
343
|
+
name,
|
|
344
|
+
description,
|
|
345
|
+
category: tool.category.trim() || 'studio',
|
|
346
|
+
access: tool.access,
|
|
347
|
+
permission: tool.permission,
|
|
348
|
+
loadTier: tool.loadTier,
|
|
349
|
+
paths: paths.length ? paths : ['*'],
|
|
350
|
+
...schemas,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function toolSchemas(tool, fail) {
|
|
354
|
+
const input = parseJsonSchema(tool.inputJson, 'Input');
|
|
355
|
+
if (!input.ok)
|
|
356
|
+
fail(input.error, 'inputJson');
|
|
357
|
+
const output = parseJsonSchema(tool.outputJson, 'Output');
|
|
358
|
+
if (!output.ok)
|
|
359
|
+
fail(output.error, 'outputJson');
|
|
360
|
+
return {
|
|
361
|
+
inputSchema: input.ok ? input.schema : {},
|
|
362
|
+
outputSchema: output.ok ? output.schema : {},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/** Every agent tool takes text; what else it takes is the called agent's `inputs`. */
|
|
366
|
+
const AGENT_TOOL_TEXT_INPUT = jsonSchemaFromZod(agentToolInputFor({ type: 'text' }), 'input');
|
|
367
|
+
/** An agent tool returns the agent's reply: the kernel fixes it. */
|
|
368
|
+
const AGENT_TOOL_OUTPUT = jsonSchemaFromZod(agentToolOutput, 'output');
|
|
369
|
+
/** What a tool that runs this draft's agent takes, as the kernel derives it from the agent's inputs. */
|
|
370
|
+
export function agentToolInputSchema(draft) {
|
|
371
|
+
const type = draft.identity.profileType;
|
|
372
|
+
if (type !== 'text' && type !== 'image' && type !== 'speech')
|
|
373
|
+
return undefined;
|
|
374
|
+
const inputs = type !== 'speech' && draftFacets(draft).includes('inputs')
|
|
375
|
+
? compileInputs(draft.inputs, type, () => { })
|
|
376
|
+
: undefined;
|
|
377
|
+
return jsonSchemaFromZod(agentToolInputFor({ type, ...(inputs ? { inputs } : {}) }), 'input');
|
|
378
|
+
}
|
|
379
|
+
/** The lines of a label as its box holds them: one line of wording, or a list with a line each. */
|
|
380
|
+
function labelLines(label) {
|
|
381
|
+
return (label ?? '')
|
|
382
|
+
.split('\n')
|
|
383
|
+
.map((line) => line.trim())
|
|
384
|
+
.filter(Boolean);
|
|
385
|
+
}
|
|
386
|
+
function toolLabels(tool, common, fail) {
|
|
387
|
+
const labels = {};
|
|
388
|
+
const schemas = {
|
|
389
|
+
activity: { input: common.inputSchema },
|
|
390
|
+
activityPast: { input: common.inputSchema, output: common.outputSchema },
|
|
391
|
+
activityFailed: { input: common.inputSchema },
|
|
392
|
+
request: { input: common.inputSchema },
|
|
393
|
+
};
|
|
394
|
+
for (const field of ['activity', 'activityPast', 'activityFailed', 'request']) {
|
|
395
|
+
const lines = labelLines(tool[field]);
|
|
396
|
+
const [only] = lines;
|
|
397
|
+
if (only === undefined)
|
|
398
|
+
continue;
|
|
399
|
+
const label = lines.length === 1 ? only : lines;
|
|
400
|
+
const problem = activityLabelProblem(label, schemas[field]);
|
|
401
|
+
if (problem) {
|
|
402
|
+
fail(problem, field);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
labels[field] = label;
|
|
406
|
+
}
|
|
407
|
+
return Object.keys(labels).length ? labels : undefined;
|
|
408
|
+
}
|
|
409
|
+
// Headers are saved in the draft, sent on every run, and written into the export; a secret there outlives the call.
|
|
410
|
+
/** Why these headers can't be saved, when one of them looks like a credential. */
|
|
411
|
+
export function credentialHeaderProblem(headers) {
|
|
412
|
+
// A masked one is the project's own: its value never came to the page, and the project fills it in.
|
|
413
|
+
const name = Object.entries(headers ?? {}).find(([header, value]) => isCredentialName(header) && value !== MASK)?.[0];
|
|
414
|
+
return name
|
|
415
|
+
? `${name} looks like a credential. Put it under Auth instead: Auth asks for it when a call runs and nothing saves it.` // labels-exempt: builder diagnostic
|
|
416
|
+
: undefined;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Why a tool's URL can't be saved, when a query parameter in it looks like a credential. A masked
|
|
420
|
+
* one is the project's own; a new one goes under Auth, which can send an API key in the URL.
|
|
421
|
+
*/
|
|
422
|
+
export function credentialUrlProblem(url) {
|
|
423
|
+
if (!url || !URL.canParse(url))
|
|
424
|
+
return undefined;
|
|
425
|
+
const name = [...new URL(url).searchParams].find(([param, value]) => isCredentialName(param) && value && value !== MASK)?.[0];
|
|
426
|
+
return name
|
|
427
|
+
? `${name} in the URL looks like a credential. Put it under Auth instead, as an API key with ${name} as its query parameter: Auth asks for it when a call runs and nothing saves it.` // labels-exempt: builder diagnostic
|
|
428
|
+
: undefined;
|
|
429
|
+
}
|
|
430
|
+
function remoteToolFields(tool, fail) {
|
|
431
|
+
const headers = parseHeaders(tool.headersJson);
|
|
432
|
+
if (headers === null) {
|
|
433
|
+
fail('Headers must be a JSON object of strings. Until they are, a reload brings back the headers as they started.', 'headersJson');
|
|
434
|
+
}
|
|
435
|
+
const credential = credentialHeaderProblem(headers ?? undefined);
|
|
436
|
+
if (credential)
|
|
437
|
+
fail(credential, 'headersJson');
|
|
438
|
+
const field = tool.toolType === 'mcp' ? 'serverUrl' : 'endpoint';
|
|
439
|
+
const inUrl = credentialUrlProblem(tool[field]?.trim());
|
|
440
|
+
if (inUrl)
|
|
441
|
+
fail(inUrl, field);
|
|
442
|
+
const auth = compileAuth(tool, fail);
|
|
443
|
+
return { ...(headers ? { headers } : {}), ...(auth ? { auth } : {}) };
|
|
444
|
+
}
|
|
445
|
+
function checkUrl(tool, field, label, fail) {
|
|
446
|
+
const url = tool[field]?.trim() ?? '';
|
|
447
|
+
if (!url)
|
|
448
|
+
fail(`${label} is required.`, field);
|
|
449
|
+
else if (!isUrl(url))
|
|
450
|
+
fail(`${label} must be a full URL.`, field);
|
|
451
|
+
return url;
|
|
452
|
+
}
|
|
453
|
+
function httpTool(tool, common, fail) {
|
|
454
|
+
const endpoint = checkUrl(tool, 'endpoint', 'Endpoint URL', fail);
|
|
455
|
+
const pathParams = cleanList(tool.pathParams);
|
|
456
|
+
const queryParams = cleanList(tool.queryParams);
|
|
457
|
+
const bodyParam = tool.bodyParam?.trim();
|
|
458
|
+
const mapping = {
|
|
459
|
+
...(pathParams.length ? { pathParams } : {}),
|
|
460
|
+
...(queryParams.length ? { queryParams } : {}),
|
|
461
|
+
...(bodyParam ? { bodyParam } : {}),
|
|
462
|
+
};
|
|
463
|
+
return {
|
|
464
|
+
type: 'http',
|
|
465
|
+
...common,
|
|
466
|
+
endpoint,
|
|
467
|
+
method: tool.method ?? HTTP_METHODS[0],
|
|
468
|
+
...remoteToolFields(tool, fail),
|
|
469
|
+
...(Object.keys(mapping).length ? { mapping } : {}),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function mcpTool(tool, common, fail) {
|
|
473
|
+
const serverUrl = checkUrl(tool, 'serverUrl', 'MCP server URL', fail);
|
|
474
|
+
const mcpToolName = tool.mcpToolName?.trim() ?? '';
|
|
475
|
+
if (!mcpToolName)
|
|
476
|
+
fail('MCP tool name is required.', 'mcpToolName');
|
|
477
|
+
return {
|
|
478
|
+
type: 'mcp',
|
|
479
|
+
...common,
|
|
480
|
+
serverUrl,
|
|
481
|
+
mcpToolName,
|
|
482
|
+
...remoteToolFields(tool, fail),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function functionTool(tool, common, fail) {
|
|
486
|
+
const page = tool.answeredBy === 'page' ? { answeredBy: 'page' } : {};
|
|
487
|
+
if (!tool.stubOutputJson?.trim())
|
|
488
|
+
return { type: 'function', ...common, ...page };
|
|
489
|
+
const stub = parseJsonSchema(tool.stubOutputJson, 'Stub output');
|
|
490
|
+
if (!stub.ok)
|
|
491
|
+
fail(stub.error.replace(' JSON Schema', ''), 'stubOutputJson');
|
|
492
|
+
return {
|
|
493
|
+
type: 'function',
|
|
494
|
+
...common,
|
|
495
|
+
...page,
|
|
496
|
+
...(stub.ok ? { stubResponse: stub.schema } : {}),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function agentTool(tool, common, fail, agentIdOf) {
|
|
500
|
+
const key = tool.agentKey ?? '';
|
|
501
|
+
const profile = key ? agentIdOf(key) : undefined;
|
|
502
|
+
if (!key)
|
|
503
|
+
fail('Pick the agent this tool runs.', 'agentKey');
|
|
504
|
+
else if (!profile)
|
|
505
|
+
fail('The agent this tool ran is gone. Pick another.', 'agentKey');
|
|
506
|
+
const max = tool.maxCallsPerTurn ?? null;
|
|
507
|
+
if (max !== null && !(Number.isInteger(max) && max >= 1)) {
|
|
508
|
+
fail('Calls per turn must be a positive whole number.', 'maxCallsPerTurn');
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
type: 'agent',
|
|
512
|
+
...common,
|
|
513
|
+
profile: profile ?? '',
|
|
514
|
+
...(max !== null ? { maxCallsPerTurn: max } : {}),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
const TOOL_COMPILERS = {
|
|
518
|
+
http: httpTool,
|
|
519
|
+
mcp: mcpTool,
|
|
520
|
+
function: functionTool,
|
|
521
|
+
agent: agentTool,
|
|
522
|
+
};
|
|
523
|
+
function isCompiledToolType(type) {
|
|
524
|
+
return STUDIO_TOOL_TYPES.includes(type);
|
|
525
|
+
}
|
|
526
|
+
function compileTool(tool, report, agentIdOf, agentInputOf) {
|
|
527
|
+
const nodeId = toolSpecNodeId(tool.key);
|
|
528
|
+
if (!isCompiledToolType(tool.toolType)) {
|
|
529
|
+
report(nodeId, STUDIO_TOOL_TYPE_MESSAGE, 'toolType');
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
let failed = false;
|
|
533
|
+
const fail = (message, field) => {
|
|
534
|
+
failed = true;
|
|
535
|
+
report(nodeId, message, field);
|
|
536
|
+
};
|
|
537
|
+
const common = toolCommon(tool, fail, agentInputOf);
|
|
538
|
+
const labels = toolLabels(tool, common, fail);
|
|
539
|
+
const compiled = TOOL_COMPILERS[tool.toolType](tool, common, fail, agentIdOf);
|
|
540
|
+
return failed ? undefined : { ...compiled, ...(labels ? { labels } : {}) };
|
|
541
|
+
}
|
|
542
|
+
function compileTools(draft, withLoader, withParallel, report, agentIdOf, agentInputOf) {
|
|
543
|
+
const customTools = [];
|
|
544
|
+
const names = new Set();
|
|
545
|
+
for (const tool of draft.toolSpecs) {
|
|
546
|
+
const compiled = compileTool(tool, report, agentIdOf, agentInputOf);
|
|
547
|
+
if (!compiled)
|
|
548
|
+
continue;
|
|
549
|
+
if (names.has(compiled.name)) {
|
|
550
|
+
report(toolSpecNodeId(tool.key), `Tool name '${compiled.name}' is used twice.`, 'toolName');
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
names.add(compiled.name);
|
|
554
|
+
customTools.push(compiled);
|
|
555
|
+
}
|
|
556
|
+
const allow = customTools.map((tool) => tool.name);
|
|
557
|
+
const t2Loader = withLoader ? draft.tools.t2Loader.trim() : '';
|
|
558
|
+
if (t2Loader && !names.has(t2Loader)) {
|
|
559
|
+
report('tools', `T2 loader '${t2Loader}' must be one of the custom tools.`, 't2Loader');
|
|
560
|
+
}
|
|
561
|
+
const parallel = withParallel && draft.tools.parallel;
|
|
562
|
+
return {
|
|
563
|
+
customTools,
|
|
564
|
+
tools: { allow, ...(t2Loader ? { t2Loader } : {}), ...(parallel ? { parallel } : {}) },
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function compileInputs(inputs, type, report) {
|
|
568
|
+
const outside = type === 'image'
|
|
569
|
+
? inputs.attachmentsAccept.filter((rule) => !mimeAllowed(IMAGE_ATTACHMENT_ACCEPT_MIMES, rule))
|
|
570
|
+
: [];
|
|
571
|
+
if (outside.length) {
|
|
572
|
+
report('inputs', `An image profile takes images, video, and PDF only, not ${outside.join(', ')}.`, 'attachmentsAccept');
|
|
573
|
+
}
|
|
574
|
+
if (!inputs.text && !inputs.attachmentsAccept.length && !inputs.voiceAccept.length) {
|
|
575
|
+
report('inputs', 'Take text, files, or voice notes: with none, the agent can be sent nothing.', 'text');
|
|
576
|
+
}
|
|
577
|
+
if (inputLimitsRequired(inputs)) {
|
|
578
|
+
const limits = [
|
|
579
|
+
['maxFiles', 'Max files', inputs.maxFiles],
|
|
580
|
+
['maxBytes', 'Max bytes', inputs.maxBytes],
|
|
581
|
+
['maxTurnBytes', 'Max turn bytes', inputs.maxTurnBytes],
|
|
582
|
+
];
|
|
583
|
+
for (const [field, label, value] of limits) {
|
|
584
|
+
if (value !== null)
|
|
585
|
+
continue;
|
|
586
|
+
report('inputs', `${label} is required with attachments or voice.`, field);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
checkWhole(report, 'inputs', 'maxFiles', 'Max files', inputs.maxFiles, 1);
|
|
590
|
+
checkWhole(report, 'inputs', 'maxBytes', 'Max bytes', inputs.maxBytes, 1);
|
|
591
|
+
checkWhole(report, 'inputs', 'maxTurnBytes', 'Max turn bytes', inputs.maxTurnBytes, 1);
|
|
592
|
+
const limitsByMime = recordField(inputs.limitsByMimeJson, isPositiveWhole, () => {
|
|
593
|
+
report('inputs', 'Limits by type must be a JSON object of positive whole numbers.', 'limitsByMimeJson');
|
|
594
|
+
});
|
|
595
|
+
const slots = compileSlots(inputs, report);
|
|
596
|
+
const context = compileContext(inputs, report);
|
|
597
|
+
return {
|
|
598
|
+
...(inputs.text ? {} : { text: false }),
|
|
599
|
+
...(inputs.attachmentsAccept.length
|
|
600
|
+
? { attachments: { accept: [...inputs.attachmentsAccept] } }
|
|
601
|
+
: {}),
|
|
602
|
+
...(inputs.voiceAccept.length ? { voice: { accept: [...inputs.voiceAccept] } } : {}),
|
|
603
|
+
...(inputs.maxFiles !== null ? { maxFiles: inputs.maxFiles } : {}),
|
|
604
|
+
...(inputs.maxBytes !== null ? { maxBytes: inputs.maxBytes } : {}),
|
|
605
|
+
...(inputs.maxTurnBytes !== null ? { maxTurnBytes: inputs.maxTurnBytes } : {}),
|
|
606
|
+
...(limitsByMime ? { limitsByMime } : {}),
|
|
607
|
+
...(slots ? { slots } : {}),
|
|
608
|
+
...(context ? { context } : {}),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/** Context is on when a sender is listed; then it needs its limit. */
|
|
612
|
+
function compileContext(inputs, report) {
|
|
613
|
+
if (!inputs.contextFrom.length)
|
|
614
|
+
return undefined;
|
|
615
|
+
if (inputs.contextMaxChars === null) {
|
|
616
|
+
report('inputs', 'Max characters is required with context.', 'contextMaxChars');
|
|
617
|
+
}
|
|
618
|
+
checkWhole(report, 'inputs', 'contextMaxChars', 'Max characters', inputs.contextMaxChars, 1);
|
|
619
|
+
return { from: [...inputs.contextFrom], maxChars: inputs.contextMaxChars ?? 0 };
|
|
620
|
+
}
|
|
621
|
+
function compileSlots(inputs, report) {
|
|
622
|
+
return recordField(inputs.slotsJson, isValueList, () => {
|
|
623
|
+
report('inputs', 'Slots must be a JSON object of lists of text.', 'slotsJson');
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
/** A call profile's inputs are its slots and context, and are left out when it has neither. */
|
|
627
|
+
function compileLiveInputs(inputs, report) {
|
|
628
|
+
const slots = compileSlots(inputs, report);
|
|
629
|
+
const context = compileContext(inputs, report);
|
|
630
|
+
if (!slots && !context)
|
|
631
|
+
return {};
|
|
632
|
+
return { inputs: { ...(slots ? { slots } : {}), ...(context ? { context } : {}) } };
|
|
633
|
+
}
|
|
634
|
+
const isPositiveWhole = (value) => typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
635
|
+
const isValueList = (value) => Array.isArray(value) &&
|
|
636
|
+
value.length > 0 &&
|
|
637
|
+
value.every((item) => typeof item === 'string' && item.trim() !== '');
|
|
638
|
+
/** `shaped`: the type takes a structured reply and its validation (text only). */
|
|
639
|
+
function compileOutputs(outputs, shaped, report) {
|
|
640
|
+
let structured;
|
|
641
|
+
const validationEnabled = shaped && outputs.validationEnabled;
|
|
642
|
+
if (shaped && outputs.mode === 'structured') {
|
|
643
|
+
const id = outputs.schemaId.trim();
|
|
644
|
+
if (!id) {
|
|
645
|
+
report('outputs', 'Structured output needs a schema id.', 'schemaId');
|
|
646
|
+
}
|
|
647
|
+
const schema = parseJsonSchema(outputs.schemaJson, 'Structured output');
|
|
648
|
+
if (!schema.ok)
|
|
649
|
+
report('outputs', schema.error, 'schemaJson');
|
|
650
|
+
if (id && schema.ok) {
|
|
651
|
+
structured = { id, spec: { jsonSchema: schema.schema } };
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (validationEnabled) {
|
|
655
|
+
checkWhole(report, 'outputs', 'maxRetries', 'Validation max retries', outputs.maxRetries, 0);
|
|
656
|
+
}
|
|
657
|
+
// why: a rule's check is the project's code; a run and Save lay this over the rule as registered.
|
|
658
|
+
const items = Object.fromEntries(Object.entries(outputs.itemRules).map(([list, onFail]) => [
|
|
659
|
+
list,
|
|
660
|
+
{ onFail },
|
|
661
|
+
]));
|
|
662
|
+
const validation = validationEnabled
|
|
663
|
+
? {
|
|
664
|
+
...(outputs.maxRetries !== null ? { maxRetries: outputs.maxRetries } : {}),
|
|
665
|
+
...(Object.keys(items).length ? { items } : {}),
|
|
666
|
+
}
|
|
667
|
+
: {};
|
|
668
|
+
const streaming = {
|
|
669
|
+
...(outputs.streamMode ? { mode: outputs.streamMode } : {}),
|
|
670
|
+
...(outputs.streamThoughts ? {} : { streamThoughts: false }),
|
|
671
|
+
};
|
|
672
|
+
const spec = {
|
|
673
|
+
...(structured ? { structured: structured.id } : {}),
|
|
674
|
+
...(Object.keys(validation).length ? { validation } : {}),
|
|
675
|
+
...(Object.keys(streaming).length ? { streaming } : {}),
|
|
676
|
+
};
|
|
677
|
+
return {
|
|
678
|
+
...(Object.keys(spec).length ? { outputs: spec } : {}),
|
|
679
|
+
...(structured ? { structured } : {}),
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function compileResumption(turn, report) {
|
|
683
|
+
// "Never" is written out, both lists: left out, the kernel offers every kind and continues two itself.
|
|
684
|
+
if (!turn.resumeEnabled)
|
|
685
|
+
return { allowContinue: [], autoContinue: [] };
|
|
686
|
+
checkWhole(report, 'turnBehaviour', 'maxContinues', 'Max continues', turn.maxContinues, 1);
|
|
687
|
+
return {
|
|
688
|
+
// Both always written: left out, the kernel continues all three kinds and
|
|
689
|
+
// auto-continues length and stream_incomplete.
|
|
690
|
+
allowContinue: [...turn.allowContinue],
|
|
691
|
+
autoContinue: [...turn.autoContinue],
|
|
692
|
+
...(turn.maxContinues !== null ? { maxContinues: turn.maxContinues } : {}),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function compileTurnBehaviour(turn, withResumption, report) {
|
|
696
|
+
const resumption = withResumption ? compileResumption(turn, report) : undefined;
|
|
697
|
+
const spec = {
|
|
698
|
+
...(resumption ? { resumption } : {}),
|
|
699
|
+
...(turn.allowSteering ? {} : { allowSteering: false }),
|
|
700
|
+
...(turn.atStepLimit === 'answer' ? { atStepLimit: turn.atStepLimit } : {}),
|
|
701
|
+
};
|
|
702
|
+
return Object.keys(spec).length ? spec : undefined;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Each line is checked by the kernel's labels rules and reported on the node that owns it. A line
|
|
706
|
+
* edited beside a setting is written whenever it is worded and the profile's type takes it, with
|
|
707
|
+
* the setting on or off: the kernel holds it either way, and so does a project's file.
|
|
708
|
+
*/
|
|
709
|
+
function compileLabels(draft, facets, allows, report) {
|
|
710
|
+
const entries = [];
|
|
711
|
+
const shown = (facet) => facets.has(facet) || facets.has('labels');
|
|
712
|
+
const { turnBehaviour: turn, guardrails, outputs } = draft;
|
|
713
|
+
if (shown('turnBehaviour') && takesContinueInstruction(draft)) {
|
|
714
|
+
entries.push([
|
|
715
|
+
'turnBehaviour',
|
|
716
|
+
'continueInstruction',
|
|
717
|
+
'continue.instruction',
|
|
718
|
+
turn.continueInstruction.trim(),
|
|
719
|
+
]);
|
|
720
|
+
}
|
|
721
|
+
if (shown('guardrails') && allows('identity.system')) {
|
|
722
|
+
entries.push([
|
|
723
|
+
'guardrails',
|
|
724
|
+
'canaryBindNote',
|
|
725
|
+
'canary.bind_note',
|
|
726
|
+
guardrails.canaryBindNote.trim(),
|
|
727
|
+
]);
|
|
728
|
+
}
|
|
729
|
+
if (shown('guardrails') && allows('guardrails.quota')) {
|
|
730
|
+
entries.push(['guardrails', 'quotaMessage', 'quota.exhausted', guardrails.quotaMessage.trim()]);
|
|
731
|
+
}
|
|
732
|
+
if (shown('outputs') && allows('outputs.validation')) {
|
|
733
|
+
entries.push([
|
|
734
|
+
'outputs',
|
|
735
|
+
'repairGuidance',
|
|
736
|
+
'repair.default_guidance',
|
|
737
|
+
outputs.repairGuidance.trim(),
|
|
738
|
+
]);
|
|
739
|
+
}
|
|
740
|
+
if (shown('guardrails') && allows('guardrails.blockedReply')) {
|
|
741
|
+
entries.push([
|
|
742
|
+
'guardrails',
|
|
743
|
+
'egressRepairGuidance',
|
|
744
|
+
'egress.default_repair_guidance',
|
|
745
|
+
guardrails.egressRepairGuidance.trim(),
|
|
746
|
+
]);
|
|
747
|
+
entries.push([
|
|
748
|
+
'guardrails',
|
|
749
|
+
'egressRefusal',
|
|
750
|
+
'egress.refusal',
|
|
751
|
+
guardrails.egressRefusal.trim(),
|
|
752
|
+
]);
|
|
753
|
+
}
|
|
754
|
+
if (facets.has('labels')) {
|
|
755
|
+
for (const [key, template] of Object.entries(draft.labels)) {
|
|
756
|
+
if (!INLINE_LABELS[key])
|
|
757
|
+
entries.push(['labels', key, key, template.trim()]);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const labels = {};
|
|
761
|
+
for (const [nodeId, field, key, template] of entries) {
|
|
762
|
+
if (!template)
|
|
763
|
+
continue;
|
|
764
|
+
try {
|
|
765
|
+
validateLabelOverrides({ [key]: template }, 'Profile labels');
|
|
766
|
+
labels[key] = template;
|
|
767
|
+
}
|
|
768
|
+
catch (err) {
|
|
769
|
+
report(nodeId, describeError(err), field);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return Object.keys(labels).length ? labels : undefined;
|
|
773
|
+
}
|
|
774
|
+
function compileQuota(guardrails, report) {
|
|
775
|
+
if (!guardrails.quotaEnabled)
|
|
776
|
+
return undefined;
|
|
777
|
+
if (guardrails.quotaPerDay === null) {
|
|
778
|
+
report('guardrails', 'Quota needs turns per day.', 'quotaPerDay');
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
checkWhole(report, 'guardrails', 'quotaPerDay', 'Quota per day', guardrails.quotaPerDay, 1);
|
|
782
|
+
return { perDay: guardrails.quotaPerDay };
|
|
783
|
+
}
|
|
784
|
+
function compileBlockedReply(guardrails, report) {
|
|
785
|
+
checkWhole(report, 'guardrails', 'blockedReplyMaxRetries', 'Blocked reply max retries', guardrails.blockedReplyMaxRetries, 0);
|
|
786
|
+
const spec = {
|
|
787
|
+
...(guardrails.blockedReplyOnBlock ? { onBlock: guardrails.blockedReplyOnBlock } : {}),
|
|
788
|
+
...(guardrails.blockedReplyMaxRetries !== null
|
|
789
|
+
? { maxRetries: guardrails.blockedReplyMaxRetries }
|
|
790
|
+
: {}),
|
|
791
|
+
};
|
|
792
|
+
return Object.keys(spec).length ? spec : undefined;
|
|
793
|
+
}
|
|
794
|
+
/** The network guardrail `guardrails` compiles to, which a draft's runs keep to: what a connection test sends. */
|
|
795
|
+
export function draftNetwork(guardrails) {
|
|
796
|
+
return compileNetwork(guardrails, () => { });
|
|
797
|
+
}
|
|
798
|
+
function compileNetwork(guardrails, report) {
|
|
799
|
+
const hosts = guardrails.allowedHosts.map((host) => host.trim());
|
|
800
|
+
const blank = hosts.indexOf('');
|
|
801
|
+
if (blank !== -1) {
|
|
802
|
+
report('guardrails', 'Allowed hosts cannot be blank.', 'allowedHosts', blank);
|
|
803
|
+
}
|
|
804
|
+
const schemes = cleanList(guardrails.allowedSchemes);
|
|
805
|
+
if (!guardrails.allowPrivateNetworks && !hosts.length && !schemes.length)
|
|
806
|
+
return undefined;
|
|
807
|
+
return {
|
|
808
|
+
...(guardrails.allowPrivateNetworks ? { allowPrivateNetworks: true } : {}),
|
|
809
|
+
...(hosts.length ? { allowedHosts: hosts } : {}),
|
|
810
|
+
...(schemes.length ? { allowedSchemes: schemes } : {}),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
/** What the detector lets through, where it differs from the kernel's: omitted when it does not. */
|
|
814
|
+
function compileUrlAllow(detector, allow, report) {
|
|
815
|
+
const hosts = allow.hosts.map((host) => host.trim());
|
|
816
|
+
const blank = hosts.indexOf('');
|
|
817
|
+
if (blank !== -1) {
|
|
818
|
+
report('guardrails', 'Allowed hosts cannot be blank.', `allow.${detector}.hosts`, blank);
|
|
819
|
+
}
|
|
820
|
+
const named = hosts.filter(Boolean);
|
|
821
|
+
const spec = {
|
|
822
|
+
...(named.length ? { hosts: named } : {}),
|
|
823
|
+
...(allow.fromTools ? {} : { fromTools: false }),
|
|
824
|
+
};
|
|
825
|
+
return Object.keys(spec).length ? spec : undefined;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* What is wrong with the builder's patterns, as the builder reads it: a pattern by its place in
|
|
829
|
+
* the list, where the kernel names it by its path in the profile.
|
|
830
|
+
*/
|
|
831
|
+
function patternIssue(drafts, patterns) {
|
|
832
|
+
for (const [index, draft] of drafts.entries()) {
|
|
833
|
+
const which = `Pattern ${index + 1}`;
|
|
834
|
+
if (!draft.name.trim())
|
|
835
|
+
return `${which} needs a name.`;
|
|
836
|
+
if (draft.kind === 'words') {
|
|
837
|
+
if (!cleanList(draft.words).length)
|
|
838
|
+
return `${which} needs a word to match.`;
|
|
839
|
+
}
|
|
840
|
+
else if (!draft.pattern)
|
|
841
|
+
return `${which} needs a regular expression.`;
|
|
842
|
+
}
|
|
843
|
+
const problem = patternListProblem('', patterns);
|
|
844
|
+
if (problem === undefined)
|
|
845
|
+
return undefined;
|
|
846
|
+
const at = /^\.patterns\[(\d+)\](?:\.(\w+))?:? (.*)$/s.exec(problem);
|
|
847
|
+
if (!at)
|
|
848
|
+
return problem.replace(/^\.patterns/, 'This');
|
|
849
|
+
const [, index, setting, rest] = at;
|
|
850
|
+
return `Pattern ${Number(index) + 1}${setting ? `, ${setting}` : ''}: ${rest}`;
|
|
851
|
+
}
|
|
852
|
+
/** The builder's patterns as the kernel takes them; what is wrong with one is reported at `field`. */
|
|
853
|
+
function compilePatterns(drafts, field, report) {
|
|
854
|
+
const patterns = drafts.map((draft) => {
|
|
855
|
+
const name = draft.name.trim();
|
|
856
|
+
if (draft.kind === 'words')
|
|
857
|
+
return { name, words: cleanList(draft.words) };
|
|
858
|
+
const flags = draft.flags.trim();
|
|
859
|
+
return { name, pattern: draft.pattern, ...(flags ? { flags } : {}) };
|
|
860
|
+
});
|
|
861
|
+
const problem = patternIssue(drafts, patterns);
|
|
862
|
+
if (problem !== undefined)
|
|
863
|
+
report('guardrails', problem, field);
|
|
864
|
+
return patterns;
|
|
865
|
+
}
|
|
866
|
+
/** Whose patterns `detector` reads with, where that differs from Theorem's alone. */
|
|
867
|
+
function compileSource(detector, source, report) {
|
|
868
|
+
if (!source || !DETECTOR_META[detector].patterns)
|
|
869
|
+
return {};
|
|
870
|
+
const hint = compileHint(source.hint, `sources.${detector}.hint`, report);
|
|
871
|
+
const patterns = compilePatterns(source.patterns, `sources.${detector}.patterns`, report);
|
|
872
|
+
return {
|
|
873
|
+
...(source.theorem ? {} : { theorem: false }),
|
|
874
|
+
...(patterns.length ? { patterns } : {}),
|
|
875
|
+
// A hint speaks for the builder's patterns, so it goes only beside some.
|
|
876
|
+
...(patterns.length && hint ? { hint } : {}),
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
/** A hint as the kernel takes it, or `undefined` when the builder wrote none. */
|
|
880
|
+
function compileHint(draft, field, report) {
|
|
881
|
+
const hint = draft.trim();
|
|
882
|
+
if (!hint)
|
|
883
|
+
return undefined;
|
|
884
|
+
const problem = hintProblem('The retry hint', hint);
|
|
885
|
+
if (problem !== undefined)
|
|
886
|
+
report('guardrails', `${problem}.`, field);
|
|
887
|
+
return hint;
|
|
888
|
+
}
|
|
889
|
+
/** What is wrong with the key of one of the builder's detectors; the kernel judges its form. */
|
|
890
|
+
function ownKeyIssue(key, boundaries) {
|
|
891
|
+
if (!key)
|
|
892
|
+
return 'Give this detector a key, such as acme.codenames.';
|
|
893
|
+
const sound = { label: key, action: 'flag', find: PATTERNS_PENDING };
|
|
894
|
+
return detectProblem('Detect', { [key]: sound }, boundaries) === undefined
|
|
895
|
+
? undefined
|
|
896
|
+
: 'A key is a namespace, a dot, and a name, in lower case letters, digits and _, such as acme.codenames.';
|
|
897
|
+
}
|
|
898
|
+
/** The builder's own detectors, each read at the boundaries of the profile it sets above `ignore`. */
|
|
899
|
+
function compileOwnDetectors(own, boundaries, report) {
|
|
900
|
+
const spec = {};
|
|
901
|
+
for (const [index, detector] of own.entries()) {
|
|
902
|
+
const key = detector.key.trim();
|
|
903
|
+
if (Object.hasOwn(spec, key)) {
|
|
904
|
+
report('guardrails', `Detector ${key} is listed twice.`, 'own', index);
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
const read = boundaries.filter((boundary) => detector.at[boundary] !== 'ignore');
|
|
908
|
+
const hint = compileHint(detector.hint, `own.${index}.hint`, report);
|
|
909
|
+
const patterns = compilePatterns(detector.patterns, `own.${index}.patterns`, report);
|
|
910
|
+
const label = detector.label.trim();
|
|
911
|
+
const keyed = ownKeyIssue(key, boundaries);
|
|
912
|
+
if (keyed !== undefined)
|
|
913
|
+
report('guardrails', keyed, 'own', index);
|
|
914
|
+
if (!label)
|
|
915
|
+
report('guardrails', 'Give this detector a name.', `own.${index}.label`);
|
|
916
|
+
if (!read.length) {
|
|
917
|
+
report('guardrails', 'Pick what a match does. It is ignored now.', `own.${index}.at`);
|
|
918
|
+
}
|
|
919
|
+
// Each is said above in the builder's words, so the kernel is not asked to say it again.
|
|
920
|
+
if (keyed !== undefined || !label || !read.length)
|
|
921
|
+
continue;
|
|
922
|
+
spec[key] = {
|
|
923
|
+
label,
|
|
924
|
+
at: Object.fromEntries(read.map((boundary) => [boundary, detector.at[boundary]])),
|
|
925
|
+
patterns,
|
|
926
|
+
...(hint ? { hint } : {}),
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
return spec;
|
|
930
|
+
}
|
|
931
|
+
/** A `find` that stands for patterns not yet compiled, while the rest of a detector is checked. */
|
|
932
|
+
const PATTERNS_PENDING = () => [];
|
|
933
|
+
/**
|
|
934
|
+
* `detect` as it can be checked before its patterns are compiled: the run's server compiles them
|
|
935
|
+
* as it registers the profile, so here each list is left out and checked on its own.
|
|
936
|
+
*/
|
|
937
|
+
function withoutPatterns(detect) {
|
|
938
|
+
if (detect === undefined || typeof detect === 'string')
|
|
939
|
+
return detect;
|
|
940
|
+
return Object.fromEntries(Object.entries(detect).map(([key, rule]) => {
|
|
941
|
+
if (typeof rule === 'string' || rule.patterns === undefined)
|
|
942
|
+
return [key, rule];
|
|
943
|
+
const { patterns: _patterns, ...rest } = rule;
|
|
944
|
+
if (key.includes('.'))
|
|
945
|
+
return [key, { ...rest, find: PATTERNS_PENDING }];
|
|
946
|
+
// A hint goes with the patterns left out, and `compileHint` has checked it.
|
|
947
|
+
const { hint: _hint, ...settings } = rest;
|
|
948
|
+
return [key, settings];
|
|
949
|
+
}));
|
|
950
|
+
}
|
|
951
|
+
/** `profile` as it can be defined here, before the run's server compiles its patterns. */
|
|
952
|
+
export function uncompiled(profile) {
|
|
953
|
+
const { guardrails } = profile;
|
|
954
|
+
if (!guardrails || !('detect' in guardrails) || guardrails.detect === undefined)
|
|
955
|
+
return profile;
|
|
956
|
+
return {
|
|
957
|
+
...profile,
|
|
958
|
+
guardrails: { ...guardrails, detect: withoutPatterns(guardrails.detect) },
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* The actions that differ from the defaults, at the boundaries the profile has; what each URL
|
|
963
|
+
* detector that reads one of them lets through; whose patterns each detector reads with; and the
|
|
964
|
+
* builder's own detectors.
|
|
965
|
+
*/
|
|
966
|
+
function compileDetect(guardrails, boundaries, report) {
|
|
967
|
+
const { detect } = guardrails;
|
|
968
|
+
const spec = {};
|
|
969
|
+
for (const detector of DETECTORS) {
|
|
970
|
+
const changed = boundaries.filter((boundary) => detect[detector][boundary] !== DETECT_DEFAULTS[detector][boundary]);
|
|
971
|
+
const reads = boundaries.some((boundary) => detect[detector][boundary] !== 'ignore');
|
|
972
|
+
const allow = reads ? compileAllow(detector, guardrails, report) : undefined;
|
|
973
|
+
const source = compileSource(detector, guardrails.sources[detector], report);
|
|
974
|
+
if (!changed.length && !allow && !Object.keys(source).length)
|
|
975
|
+
continue;
|
|
976
|
+
spec[detector] = {
|
|
977
|
+
...(changed.length
|
|
978
|
+
? {
|
|
979
|
+
at: Object.fromEntries(changed.map((boundary) => [boundary, detect[detector][boundary]])),
|
|
980
|
+
}
|
|
981
|
+
: {}),
|
|
982
|
+
...source,
|
|
983
|
+
...(allow ? { allow } : {}),
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
Object.assign(spec, compileOwnDetectors(guardrails.own, boundaries, report));
|
|
987
|
+
const problem = detectProblem('Detect', withoutPatterns(spec), boundaries);
|
|
988
|
+
if (problem !== undefined)
|
|
989
|
+
report('guardrails', problem, 'detect');
|
|
990
|
+
return Object.keys(spec).length ? spec : undefined;
|
|
991
|
+
}
|
|
992
|
+
/** What `detector` lets through: addresses, names, or for most detectors nothing. */
|
|
993
|
+
function compileAllow(detector, guardrails, report) {
|
|
994
|
+
if (isUrlDetector(detector))
|
|
995
|
+
return compileUrlAllow(detector, guardrails.allow[detector], report);
|
|
996
|
+
if (DETECTOR_META[detector].allow !== 'names')
|
|
997
|
+
return undefined;
|
|
998
|
+
const names = guardrails.innocentNames.map((name) => name.trim());
|
|
999
|
+
const blank = names.indexOf('');
|
|
1000
|
+
if (blank !== -1)
|
|
1001
|
+
report('guardrails', 'Allowed names cannot be blank.', 'innocentNames', blank);
|
|
1002
|
+
const named = names.filter(Boolean);
|
|
1003
|
+
return named.length ? { names: named } : undefined;
|
|
1004
|
+
}
|
|
1005
|
+
function isUrlDetector(detector) {
|
|
1006
|
+
return DETECTOR_META[detector].allow === 'urls';
|
|
1007
|
+
}
|
|
1008
|
+
function compileTaint(guardrails) {
|
|
1009
|
+
const { taintAfterRemoteRead: afterRemoteRead, taintRemoteDestination: remoteDestination } = guardrails;
|
|
1010
|
+
if (!afterRemoteRead && !remoteDestination)
|
|
1011
|
+
return undefined;
|
|
1012
|
+
return {
|
|
1013
|
+
...(afterRemoteRead ? { afterRemoteRead } : {}),
|
|
1014
|
+
...(remoteDestination ? { remoteDestination } : {}),
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function compileGuardrails(guardrails, report, boundaries = BOUNDARIES) {
|
|
1018
|
+
const parts = {
|
|
1019
|
+
detect: compileDetect(guardrails, boundaries, report),
|
|
1020
|
+
quota: compileQuota(guardrails, report),
|
|
1021
|
+
blockedReply: compileBlockedReply(guardrails, report),
|
|
1022
|
+
network: compileNetwork(guardrails, report),
|
|
1023
|
+
taint: compileTaint(guardrails),
|
|
1024
|
+
};
|
|
1025
|
+
const spec = Object.fromEntries(Object.entries(parts).filter(([, value]) => value !== undefined));
|
|
1026
|
+
return Object.keys(spec).length ? spec : undefined;
|
|
1027
|
+
}
|
|
1028
|
+
function compileObservability(observability, report) {
|
|
1029
|
+
// why: a profile that writes this block is read with the kernel's defaults for one that does.
|
|
1030
|
+
const defaults = resolveObservabilityPolicy({});
|
|
1031
|
+
const { writeTo } = observability;
|
|
1032
|
+
const { sampleRate } = observability;
|
|
1033
|
+
if (!(Number.isFinite(sampleRate) && sampleRate >= 0 && sampleRate <= 1)) {
|
|
1034
|
+
report('observability', 'Sample rate must be between 0 and 1.', 'sampleRate');
|
|
1035
|
+
}
|
|
1036
|
+
if (observability.retainForDays !== null && !Number.isFinite(observability.retainForDays)) {
|
|
1037
|
+
report('observability', 'Retain for days must be a number.', 'retainForDays');
|
|
1038
|
+
}
|
|
1039
|
+
if (observability.rotateAfterMiB !== null &&
|
|
1040
|
+
!(Number.isFinite(observability.rotateAfterMiB) && observability.rotateAfterMiB > 0)) {
|
|
1041
|
+
report('observability', 'Rotate after MiB must be more than zero.', 'rotateAfterMiB');
|
|
1042
|
+
}
|
|
1043
|
+
const resource = recordField(observability.resourceJson, isAny, () => {
|
|
1044
|
+
report('observability', 'Resource must be a JSON object.', 'resourceJson');
|
|
1045
|
+
});
|
|
1046
|
+
const include = Object.fromEntries(Object.entries(observability.include).filter(([name, on]) => on !== defaults.include[name]));
|
|
1047
|
+
const scrub = Object.fromEntries(Object.entries(observability.scrub).filter(([name, on]) => on !== defaults.scrub[name]));
|
|
1048
|
+
const spec = {
|
|
1049
|
+
writeTo,
|
|
1050
|
+
...(sampleRate !== defaults.sampleRate ? { sampleRate } : {}),
|
|
1051
|
+
...(Object.keys(include).length ? { include } : {}),
|
|
1052
|
+
...(Object.keys(scrub).length ? { scrub } : {}),
|
|
1053
|
+
...(observability.retainForDays !== null ? { retainForDays: observability.retainForDays } : {}),
|
|
1054
|
+
...(observability.rotateAfterMiB !== null
|
|
1055
|
+
? { rotateAfterMiB: observability.rotateAfterMiB }
|
|
1056
|
+
: {}),
|
|
1057
|
+
...(resource
|
|
1058
|
+
? { resource: resource }
|
|
1059
|
+
: {}),
|
|
1060
|
+
};
|
|
1061
|
+
return Object.keys(spec).length ? spec : undefined;
|
|
1062
|
+
}
|
|
1063
|
+
/** The image types a reference link may name, by its file extension. */
|
|
1064
|
+
const REFERENCE_LINK_MIMES = {
|
|
1065
|
+
png: 'image/png',
|
|
1066
|
+
jpg: 'image/jpeg',
|
|
1067
|
+
jpeg: 'image/jpeg',
|
|
1068
|
+
webp: 'image/webp',
|
|
1069
|
+
gif: 'image/gif',
|
|
1070
|
+
};
|
|
1071
|
+
/** A link's image type, from its path's extension; undefined unless it is an http(s) image link. */
|
|
1072
|
+
function referenceLinkMime(uri) {
|
|
1073
|
+
if (!URL.canParse(uri))
|
|
1074
|
+
return undefined;
|
|
1075
|
+
const url = new URL(uri);
|
|
1076
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
1077
|
+
return undefined;
|
|
1078
|
+
const extension = url.pathname.split('.').pop()?.toLowerCase() ?? '';
|
|
1079
|
+
return REFERENCE_LINK_MIMES[extension];
|
|
1080
|
+
}
|
|
1081
|
+
function compileReferences(references, report) {
|
|
1082
|
+
return references.flatMap((reference, index) => {
|
|
1083
|
+
if ('data' in reference) {
|
|
1084
|
+
if (reference.mimeType.startsWith('image/') && reference.data) {
|
|
1085
|
+
return [{ mimeType: reference.mimeType, data: reference.data, name: reference.name }];
|
|
1086
|
+
}
|
|
1087
|
+
report('image', `${reference.name || 'A reference'} is not an image.`, 'references', index);
|
|
1088
|
+
return [];
|
|
1089
|
+
}
|
|
1090
|
+
const uri = reference.uri.trim();
|
|
1091
|
+
const mimeType = referenceLinkMime(uri);
|
|
1092
|
+
if (mimeType)
|
|
1093
|
+
return [{ mimeType, uri, ...(reference.name ? { name: reference.name } : {}) }];
|
|
1094
|
+
report('image', uri
|
|
1095
|
+
? 'Link to a .png, .jpg, .webp or .gif image over http(s).'
|
|
1096
|
+
: 'Add a link or remove the reference.', 'references', index);
|
|
1097
|
+
return [];
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
function compileImage(image, report) {
|
|
1101
|
+
checkWhole(report, 'image', 'n', 'Images per request', image.n, 1);
|
|
1102
|
+
checkWhole(report, 'image', 'seed', 'Seed', image.seed, 0);
|
|
1103
|
+
checkWhole(report, 'image', 'outputCompression', 'Compression', image.outputCompression, 0);
|
|
1104
|
+
if (image.outputCompression !== null && image.outputCompression > 100) {
|
|
1105
|
+
report('image', 'Compression goes from 0 to 100.', 'outputCompression');
|
|
1106
|
+
}
|
|
1107
|
+
const references = compileReferences(image.references, report);
|
|
1108
|
+
return {
|
|
1109
|
+
...(image.aspectRatio.trim() ? { aspectRatio: image.aspectRatio.trim() } : {}),
|
|
1110
|
+
...(image.resolution.trim() ? { resolution: image.resolution.trim() } : {}),
|
|
1111
|
+
...(image.mimeType.trim() ? { mimeType: image.mimeType.trim() } : {}),
|
|
1112
|
+
...(image.quality.trim() ? { quality: image.quality.trim() } : {}),
|
|
1113
|
+
...(image.background.trim() ? { background: image.background.trim() } : {}),
|
|
1114
|
+
...(image.n !== null ? { n: image.n } : {}),
|
|
1115
|
+
...(image.seed !== null ? { seed: image.seed } : {}),
|
|
1116
|
+
...(image.outputCompression !== null ? { outputCompression: image.outputCompression } : {}),
|
|
1117
|
+
...(image.includeText ? { includeText: true } : {}),
|
|
1118
|
+
...(references.length ? { references } : {}),
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function compileSpeech(speech, draft, report) {
|
|
1122
|
+
const { format } = speech;
|
|
1123
|
+
if (format) {
|
|
1124
|
+
const refused = draft.modelBindings.find((binding) => isGoogleRoute(modelRoute(draft.providers, 'speech', binding)) &&
|
|
1125
|
+
!GOOGLE_SPEECH_FORMATS.includes(format));
|
|
1126
|
+
if (refused) {
|
|
1127
|
+
report('speech', `Google doesn't make ${format}; ${refused.modelId} is on ${refused.provider}.`, 'format');
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const onGoogle = draft.modelBindings.find((binding) => isGoogleRoute(modelRoute(draft.providers, 'speech', binding)));
|
|
1131
|
+
if (onGoogle && !speech.voice.trim()) {
|
|
1132
|
+
report('speech', `Gemini needs a voice to speak; ${onGoogle.modelId} is on ${onGoogle.provider}.`, 'voice');
|
|
1133
|
+
}
|
|
1134
|
+
const { speed } = speech;
|
|
1135
|
+
return {
|
|
1136
|
+
...(speech.voice.trim() ? { voice: speech.voice.trim() } : {}),
|
|
1137
|
+
...(speech.style.trim() ? { style: speech.style.trim() } : {}),
|
|
1138
|
+
...(speed !== null ? { speed } : {}),
|
|
1139
|
+
...(format ? { format } : {}),
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
/** What a resumed call opens with; it needs resumption on, and the wait needs a prompt. */
|
|
1143
|
+
function compileResumed(live, report) {
|
|
1144
|
+
checkWhole(report, 'live', 'resumedAfterMs', 'Away for', live.resumedAfterMs, 0);
|
|
1145
|
+
const prompt = live.resumedPrompt.trim();
|
|
1146
|
+
if (prompt && !live.sessionResumption) {
|
|
1147
|
+
report('live', 'A resume prompt needs Resumption on.', 'resumedPrompt');
|
|
1148
|
+
}
|
|
1149
|
+
if (!prompt) {
|
|
1150
|
+
if (live.resumedAfterMs !== null) {
|
|
1151
|
+
report('live', 'Away for needs a resume prompt.', 'resumedAfterMs');
|
|
1152
|
+
}
|
|
1153
|
+
return undefined;
|
|
1154
|
+
}
|
|
1155
|
+
return { prompt, ...(live.resumedAfterMs !== null ? { afterMs: live.resumedAfterMs } : {}) };
|
|
1156
|
+
}
|
|
1157
|
+
function compileLive(live, report, on) {
|
|
1158
|
+
if (!live.ingressAudio && !live.ingressVideo && !live.ingressText) {
|
|
1159
|
+
report('live', 'Turn on at least one ingress channel.');
|
|
1160
|
+
}
|
|
1161
|
+
checkWhole(report, 'live', 'vadPrefixPaddingMs', 'VAD prefix padding', live.vadPrefixPaddingMs, 0);
|
|
1162
|
+
checkWhole(report, 'live', 'vadSilenceDurationMs', 'VAD silence duration', live.vadSilenceDurationMs, 0);
|
|
1163
|
+
if (live.contextCompression)
|
|
1164
|
+
checkCompression(live, report, on.mode === 'demo' ? on.keys.liveInputTokens : undefined);
|
|
1165
|
+
const resumed = compileResumed(live, report);
|
|
1166
|
+
const channels = {
|
|
1167
|
+
audio: live.ingressAudio,
|
|
1168
|
+
video: live.ingressVideo,
|
|
1169
|
+
text: live.ingressText,
|
|
1170
|
+
};
|
|
1171
|
+
const ingress = Object.fromEntries(Object.entries(channels).filter(([channel, on]) => on !== liveIngressChannelDefault(channel)));
|
|
1172
|
+
const vad = {
|
|
1173
|
+
...(live.vadActivityHandling ? { activityHandling: live.vadActivityHandling } : {}),
|
|
1174
|
+
...(live.vadStartSensitivity ? { startSensitivity: live.vadStartSensitivity } : {}),
|
|
1175
|
+
...(live.vadEndSensitivity ? { endSensitivity: live.vadEndSensitivity } : {}),
|
|
1176
|
+
...(live.vadPrefixPaddingMs !== null ? { prefixPaddingMs: live.vadPrefixPaddingMs } : {}),
|
|
1177
|
+
...(live.vadSilenceDurationMs !== null ? { silenceDurationMs: live.vadSilenceDurationMs } : {}),
|
|
1178
|
+
};
|
|
1179
|
+
const transcription = {
|
|
1180
|
+
...(live.transcriptionInput ? { input: true } : {}),
|
|
1181
|
+
...(live.transcriptionOutput ? { output: true } : {}),
|
|
1182
|
+
};
|
|
1183
|
+
return {
|
|
1184
|
+
...(Object.keys(ingress).length ? { ingress } : {}),
|
|
1185
|
+
...(live.voice.trim() ? { voice: live.voice.trim() } : {}),
|
|
1186
|
+
...(Object.keys(vad).length ? { vad } : {}),
|
|
1187
|
+
...(live.sessionResumption ? { sessionResumption: true } : {}),
|
|
1188
|
+
...(live.greeting.trim() ? { greeting: live.greeting.trim() } : {}),
|
|
1189
|
+
...(resumed ? { resumed } : {}),
|
|
1190
|
+
...(live.contextCompression ? { contextCompression: contextCompression(live) } : {}),
|
|
1191
|
+
...(Object.keys(transcription).length ? { transcription } : {}),
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
/** Whole numbers, the target below the trigger, and on lent keys both within the session they hold. */
|
|
1195
|
+
function checkCompression(live, report, held) {
|
|
1196
|
+
checkWhole(report, 'live', 'compressionTriggerTokens', 'Compression trigger', live.compressionTriggerTokens, 1);
|
|
1197
|
+
checkWhole(report, 'live', 'compressionTargetTokens', 'Compression target', live.compressionTargetTokens, 1);
|
|
1198
|
+
const trigger = live.compressionTriggerTokens;
|
|
1199
|
+
const target = live.compressionTargetTokens;
|
|
1200
|
+
const cap = held?.toLocaleString('en-US');
|
|
1201
|
+
if (held !== undefined && trigger !== null && trigger > held) {
|
|
1202
|
+
report('live', `Compression trigger can't exceed the ${cap} input tokens a session holds on these keys.`, 'compressionTriggerTokens');
|
|
1203
|
+
}
|
|
1204
|
+
if (target !== null &&
|
|
1205
|
+
((trigger !== null && target >= trigger) ||
|
|
1206
|
+
(held !== undefined && trigger === null && target >= held))) {
|
|
1207
|
+
report('live', trigger !== null
|
|
1208
|
+
? 'Compression target must be below the trigger.'
|
|
1209
|
+
: `Compression target must be below the ${cap} input tokens a session holds on these keys.`, 'compressionTargetTokens');
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
/** A blank number is left to the provider. */
|
|
1213
|
+
function contextCompression(live) {
|
|
1214
|
+
const trigger = live.compressionTriggerTokens;
|
|
1215
|
+
const target = live.compressionTargetTokens;
|
|
1216
|
+
return {
|
|
1217
|
+
...(trigger !== null ? { triggerTokens: trigger } : {}),
|
|
1218
|
+
slidingWindow: target !== null ? { targetTokens: target } : {},
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
/** `root` without the value at `segments`; its parents stay, even if left empty. */
|
|
1222
|
+
function withoutPath(root, segments) {
|
|
1223
|
+
const [head, ...rest] = segments;
|
|
1224
|
+
const { [head]: child, ...others } = root;
|
|
1225
|
+
if (!rest.length || child === null || typeof child !== 'object') {
|
|
1226
|
+
return others;
|
|
1227
|
+
}
|
|
1228
|
+
return { ...others, [head]: withoutPath(child, rest) };
|
|
1229
|
+
}
|
|
1230
|
+
/** Drop what the type may not set (`PROFILE_FIELD_SCOPE`); the draft keeps it across type changes. */
|
|
1231
|
+
function omitOutOfScope(profile) {
|
|
1232
|
+
let out = profile;
|
|
1233
|
+
for (const { path } of outOfScopeFields(profile)) {
|
|
1234
|
+
out = withoutPath(out, path.split('.'));
|
|
1235
|
+
}
|
|
1236
|
+
return out;
|
|
1237
|
+
}
|
|
1238
|
+
/** The draft's decision questions, each problem reported on the Decision facet. */
|
|
1239
|
+
function compileQuestions(decision, report) {
|
|
1240
|
+
const questions = Object.create(null);
|
|
1241
|
+
if (!decision.questions.length) {
|
|
1242
|
+
report('decision', 'Add at least one question.', 'questions');
|
|
1243
|
+
}
|
|
1244
|
+
decision.questions.forEach((question, index) => {
|
|
1245
|
+
const fail = (message) => report('decision', message, 'questions', index);
|
|
1246
|
+
const id = question.id.trim();
|
|
1247
|
+
const instructions = question.instructions.trim();
|
|
1248
|
+
if (!id)
|
|
1249
|
+
fail('Each question needs an id.');
|
|
1250
|
+
else if (Object.hasOwn(questions, id)) {
|
|
1251
|
+
fail(`Question id '${id}' is used twice.`);
|
|
1252
|
+
}
|
|
1253
|
+
if (!instructions)
|
|
1254
|
+
fail(`Question '${id || index + 1}' needs instructions.`);
|
|
1255
|
+
const rows = question.criteria.map((row) => ({
|
|
1256
|
+
label: row.label.trim(),
|
|
1257
|
+
text: row.text.trim(),
|
|
1258
|
+
}));
|
|
1259
|
+
if (!id || !instructions)
|
|
1260
|
+
return;
|
|
1261
|
+
if (question.type === 'box') {
|
|
1262
|
+
const image = question.image?.trim();
|
|
1263
|
+
questions[id] = { type: 'box', instructions, ...(image ? { image } : {}) };
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
if (question.type === 'score') {
|
|
1267
|
+
if (rows.length < 2 || rows.some((row) => !row.text)) {
|
|
1268
|
+
fail(`Score '${id}' needs at least two levels, each described.`);
|
|
1269
|
+
}
|
|
1270
|
+
questions[id] = {
|
|
1271
|
+
type: 'score',
|
|
1272
|
+
instructions,
|
|
1273
|
+
criteria: rows.map((row) => row.text),
|
|
1274
|
+
};
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const labelled = Object.create(null);
|
|
1278
|
+
for (const row of rows) {
|
|
1279
|
+
if (!row.label)
|
|
1280
|
+
fail(`Every option of '${id}' needs a label.`);
|
|
1281
|
+
else if (Object.hasOwn(labelled, row.label)) {
|
|
1282
|
+
fail(`Option '${row.label}' of '${id}' is used twice.`);
|
|
1283
|
+
} // A label says enough on its own; its description, when given, says when to pick it.
|
|
1284
|
+
else
|
|
1285
|
+
labelled[row.label] = row.text || row.label;
|
|
1286
|
+
}
|
|
1287
|
+
if (question.type === 'choice') {
|
|
1288
|
+
if (rows.length < 2)
|
|
1289
|
+
fail(`Choice '${id}' needs at least two options.`);
|
|
1290
|
+
questions[id] = { type: 'choice', instructions, criteria: labelled };
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
questions[id] = {
|
|
1294
|
+
type: 'noul',
|
|
1295
|
+
instructions,
|
|
1296
|
+
...(rows.length ? { criteria: labelled } : {}),
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
return questions;
|
|
1301
|
+
}
|
|
1302
|
+
const IMAGE_NAME = /^[a-z0-9_]+$/;
|
|
1303
|
+
const IMAGE_MIME_TYPES = ['image/png', 'image/jpeg'];
|
|
1304
|
+
/** The draft's pictures by reference, and every reference a question writes checked against them. */
|
|
1305
|
+
function compileDecisionImages(decision, report) {
|
|
1306
|
+
const images = Object.create(null);
|
|
1307
|
+
decision.images.forEach((image, index) => {
|
|
1308
|
+
const fail = (message) => report('decision', message, 'images', index);
|
|
1309
|
+
const name = image.name.trim();
|
|
1310
|
+
const reference = `<${name}>`;
|
|
1311
|
+
if (!IMAGE_NAME.test(name))
|
|
1312
|
+
fail('Name an image with lowercase letters, digits, and _.');
|
|
1313
|
+
else if (Object.hasOwn(images, reference))
|
|
1314
|
+
fail(`Image ${reference} is used twice.`);
|
|
1315
|
+
else if (!IMAGE_MIME_TYPES.includes(image.mimeType) || !image.data)
|
|
1316
|
+
fail(`${reference} is not a PNG or JPEG.`);
|
|
1317
|
+
else
|
|
1318
|
+
images[reference] = {
|
|
1319
|
+
mimeType: image.mimeType,
|
|
1320
|
+
data: image.data,
|
|
1321
|
+
};
|
|
1322
|
+
});
|
|
1323
|
+
decision.questions.forEach((question, index) => {
|
|
1324
|
+
const named = [...question.instructions.matchAll(/<[a-z0-9_]+>/g)].map(([each]) => each);
|
|
1325
|
+
const image = question.type === 'box' ? question.image?.trim() : undefined;
|
|
1326
|
+
if (image?.startsWith('<'))
|
|
1327
|
+
named.push(image);
|
|
1328
|
+
for (const reference of new Set(named)) {
|
|
1329
|
+
if (!Object.hasOwn(images, reference))
|
|
1330
|
+
report('decision', `No image is named ${reference}.`, 'questions', index);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
return images;
|
|
1334
|
+
}
|
|
1335
|
+
function compileProfileOptions(draft, facets, report) {
|
|
1336
|
+
const observability = facets.has('observability')
|
|
1337
|
+
? compileObservability(draft.observability, report)
|
|
1338
|
+
: undefined;
|
|
1339
|
+
const labels = compileLabels(draft, facets, (path) => draftAllows(draft, path), report);
|
|
1340
|
+
return {
|
|
1341
|
+
...(observability ? { observability } : {}),
|
|
1342
|
+
...(labels ? { labels } : {}),
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function checkDecisionIdentity(draft, report) {
|
|
1346
|
+
const { decision } = draft;
|
|
1347
|
+
const contract = decision.contract.trim();
|
|
1348
|
+
if (!contract)
|
|
1349
|
+
report('decision', 'Contract is required.', 'contract');
|
|
1350
|
+
return contract;
|
|
1351
|
+
}
|
|
1352
|
+
/** What a decision may hold on lent keys, each limit reported on the field it bounds. */
|
|
1353
|
+
function checkLentDecision(draft, questions, keys, report) {
|
|
1354
|
+
const limits = keys.decision;
|
|
1355
|
+
const { decision, identity } = draft;
|
|
1356
|
+
const over = (text, most) => text.trim().length > most;
|
|
1357
|
+
if (over(decision.contract, limits.idChars)) {
|
|
1358
|
+
report('decision', `Contract is limited to ${limits.idChars} characters on these keys.`, 'contract');
|
|
1359
|
+
}
|
|
1360
|
+
if (over(identity.agentId, limits.idChars)) {
|
|
1361
|
+
report('identity', `Profile id is limited to ${limits.idChars} characters on these keys.`, 'agentId');
|
|
1362
|
+
}
|
|
1363
|
+
if (over(identity.handle, limits.nameChars)) {
|
|
1364
|
+
report('identity', `Handle is limited to ${limits.nameChars} characters on these keys.`, 'handle');
|
|
1365
|
+
}
|
|
1366
|
+
const binding = draft.modelBindings[0];
|
|
1367
|
+
if (binding) {
|
|
1368
|
+
const nodeId = modelBindingNodeId(binding.key);
|
|
1369
|
+
if (binding.timeoutMs !== null && binding.timeoutMs > limits.timeoutMs) {
|
|
1370
|
+
report(nodeId, `Timeout is limited to ${limits.timeoutMs} ms on these keys.`, 'timeoutMs');
|
|
1371
|
+
}
|
|
1372
|
+
if (over(binding.modelId, limits.nameChars)) {
|
|
1373
|
+
report(nodeId, `Model id is limited to ${limits.nameChars} characters on these keys.`, 'modelId');
|
|
1374
|
+
}
|
|
1375
|
+
if (over(binding.apiId, limits.idChars)) {
|
|
1376
|
+
report(nodeId, `API model is limited to ${limits.idChars} characters on these keys.`, 'apiId');
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (decision.maxStateBytes !== null && decision.maxStateBytes > limits.stateBytes) {
|
|
1380
|
+
report('decision', `These keys take at most ${limits.stateBytes} bytes of state.`, 'maxStateBytes');
|
|
1381
|
+
}
|
|
1382
|
+
if (decision.questions.length > limits.questions) {
|
|
1383
|
+
report('decision', `These keys ask at most ${limits.questions} questions.`, 'questions');
|
|
1384
|
+
}
|
|
1385
|
+
if (decision.images.length > limits.images) {
|
|
1386
|
+
report('decision', `These keys send at most ${limits.images} images.`, 'images');
|
|
1387
|
+
}
|
|
1388
|
+
decision.images.forEach((image, index) => {
|
|
1389
|
+
if (Math.floor((image.data.length * 3) / 4) > limits.imageBytes) {
|
|
1390
|
+
report('decision', `Each image is limited to ${limits.imageBytes} bytes on these keys.`, 'images', index);
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
decision.questions.forEach((question, index) => {
|
|
1394
|
+
const fail = (message) => report('decision', message, 'questions', index);
|
|
1395
|
+
if (over(question.id, limits.nameChars)) {
|
|
1396
|
+
fail(`Question ids are limited to ${limits.nameChars} characters on these keys.`);
|
|
1397
|
+
}
|
|
1398
|
+
if (question.type === 'box' && limits.images === 0) {
|
|
1399
|
+
fail('These keys take no images, so a Box question has nothing to read.');
|
|
1400
|
+
}
|
|
1401
|
+
if (over(question.instructions, limits.instructionsChars)) {
|
|
1402
|
+
fail(`Instructions are limited to ${limits.instructionsChars} characters on these keys.`);
|
|
1403
|
+
}
|
|
1404
|
+
if (question.criteria.length > limits.criteria) {
|
|
1405
|
+
fail(`A question has at most ${limits.criteria} options or levels on these keys.`);
|
|
1406
|
+
}
|
|
1407
|
+
if (question.criteria.some((row) => over(row.text, limits.criterionChars))) {
|
|
1408
|
+
fail(`Each option or level is limited to ${limits.criterionChars} characters on these keys.`);
|
|
1409
|
+
}
|
|
1410
|
+
if (question.criteria.some((row) => over(row.label, limits.nameChars))) {
|
|
1411
|
+
fail(`Option labels are limited to ${limits.nameChars} characters on these keys.`);
|
|
1412
|
+
}
|
|
1413
|
+
const compiled = questions[question.id.trim()];
|
|
1414
|
+
const violation = binding &&
|
|
1415
|
+
compiled &&
|
|
1416
|
+
keys.questionViolation(modelRoute(draft.providers, 'decision', binding), compiled);
|
|
1417
|
+
if (violation)
|
|
1418
|
+
fail(violation);
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
function compileDecision(draft, report, on) {
|
|
1422
|
+
const { decision } = draft;
|
|
1423
|
+
const facets = new Set(draftFacets(draft));
|
|
1424
|
+
const contract = checkDecisionIdentity(draft, report);
|
|
1425
|
+
if (draft.modelBindings.length !== 1) {
|
|
1426
|
+
report('models', 'A decision needs exactly one model binding.');
|
|
1427
|
+
}
|
|
1428
|
+
const binding = draft.modelBindings[0];
|
|
1429
|
+
const nodeId = binding ? modelBindingNodeId(binding.key) : 'models';
|
|
1430
|
+
if (binding) {
|
|
1431
|
+
checkBindingRoute(binding, 'decision', draft.providers, report);
|
|
1432
|
+
checkOptions(binding, draft.providers, 'decision', nodeId, report);
|
|
1433
|
+
if (!binding.modelId.trim()) {
|
|
1434
|
+
report(nodeId, 'Model id is required.', 'modelId');
|
|
1435
|
+
}
|
|
1436
|
+
checkWhole(report, nodeId, 'timeoutMs', 'Timeout', binding.timeoutMs, 1);
|
|
1437
|
+
}
|
|
1438
|
+
checkWhole(report, 'decision', 'maxStateBytes', 'Max state', decision.maxStateBytes, 1);
|
|
1439
|
+
const questions = compileQuestions(decision, report);
|
|
1440
|
+
const images = compileDecisionImages(decision, report);
|
|
1441
|
+
if (on.mode === 'demo')
|
|
1442
|
+
checkLentDecision(draft, questions, on.keys, report);
|
|
1443
|
+
const profile = {
|
|
1444
|
+
type: 'decision',
|
|
1445
|
+
id: draft.identity.agentId.trim(),
|
|
1446
|
+
identity: { handle: draft.identity.handle.trim() },
|
|
1447
|
+
models: binding
|
|
1448
|
+
? {
|
|
1449
|
+
[binding.modelId.trim()]: {
|
|
1450
|
+
protocol: binding.protocol,
|
|
1451
|
+
provider: binding.provider,
|
|
1452
|
+
apiId: binding.apiId.trim(),
|
|
1453
|
+
...(Object.keys(binding.options).length ? { options: binding.options } : {}),
|
|
1454
|
+
...(binding.keySlot || draft.models.key
|
|
1455
|
+
? { keySlot: binding.keySlot || draft.models.key }
|
|
1456
|
+
: {}),
|
|
1457
|
+
...(binding.fallbackKeySlot ? { fallbackKeySlot: binding.fallbackKeySlot } : {}),
|
|
1458
|
+
...(binding.timeoutMs !== null ? { timeoutMs: binding.timeoutMs } : {}),
|
|
1459
|
+
},
|
|
1460
|
+
}
|
|
1461
|
+
: {},
|
|
1462
|
+
inputs: {
|
|
1463
|
+
state: 'json',
|
|
1464
|
+
...(decision.maxStateBytes !== null ? { maxStateBytes: decision.maxStateBytes } : {}),
|
|
1465
|
+
},
|
|
1466
|
+
decision: { contract },
|
|
1467
|
+
...compileProfileOptions(draft, facets, report),
|
|
1468
|
+
};
|
|
1469
|
+
return {
|
|
1470
|
+
profile,
|
|
1471
|
+
customTools: [],
|
|
1472
|
+
questions,
|
|
1473
|
+
...(Object.keys(images).length ? { images: { ...images } } : {}),
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
/** A host: its custom tools, called directly, and the guardrails, trace, and labels a host keeps. */
|
|
1477
|
+
function compileHost(draft, report, agentIdOf, agentInputOf) {
|
|
1478
|
+
const facets = new Set(draftFacets(draft));
|
|
1479
|
+
const { customTools, tools } = compileTools(draft, false, false, report, agentIdOf, agentInputOf);
|
|
1480
|
+
if (!customTools.length) {
|
|
1481
|
+
report('tools', 'Add at least one tool: a host runs only its tools.');
|
|
1482
|
+
}
|
|
1483
|
+
const guardrails = facets.has('guardrails')
|
|
1484
|
+
? compileGuardrails(draft.guardrails, report, TOOL_BOUNDARIES)
|
|
1485
|
+
: undefined;
|
|
1486
|
+
const profile = omitOutOfScope({
|
|
1487
|
+
type: 'host',
|
|
1488
|
+
id: draft.identity.agentId.trim(),
|
|
1489
|
+
tools,
|
|
1490
|
+
...(guardrails ? { guardrails } : {}),
|
|
1491
|
+
...compileProfileOptions(draft, facets, report),
|
|
1492
|
+
});
|
|
1493
|
+
return { profile, customTools };
|
|
1494
|
+
}
|
|
1495
|
+
function assemble(draft, type, report, on, agentIdOf, agentInputOf) {
|
|
1496
|
+
const facets = new Set(draftFacets(draft));
|
|
1497
|
+
const allows = (path) => draftAllows(draft, path);
|
|
1498
|
+
const parsedSystem = typeof draft.identity.system === 'string'
|
|
1499
|
+
? parseSystemMarkup(draft.identity.system)
|
|
1500
|
+
: { ok: false, message: 'The system prompt must be text.' };
|
|
1501
|
+
if (!parsedSystem.ok)
|
|
1502
|
+
report('identity', parsedSystem.message, 'system');
|
|
1503
|
+
const system = parsedSystem.ok ? parsedSystem.prompt : undefined;
|
|
1504
|
+
const systemByRole = compileSystemByRole(draft.identity.systemByRoleJson, (message) => {
|
|
1505
|
+
report('identity', message, 'systemByRoleJson');
|
|
1506
|
+
});
|
|
1507
|
+
const modelFields = compileModels(draft, type, report, agentIdOf);
|
|
1508
|
+
const { outputs, structured } = facets.has('outputs')
|
|
1509
|
+
? compileOutputs(draft.outputs, allows('outputs.structured'), report)
|
|
1510
|
+
: {};
|
|
1511
|
+
const { customTools, tools } = facets.has('tools')
|
|
1512
|
+
? compileTools(draft, allows('tools.t2Loader'), allows('tools.parallel'), report, agentIdOf, agentInputOf)
|
|
1513
|
+
: { customTools: [] };
|
|
1514
|
+
const turnBehaviour = facets.has('turnBehaviour')
|
|
1515
|
+
? compileTurnBehaviour(draft.turnBehaviour, allows('turnBehaviour.resumption'), report)
|
|
1516
|
+
: undefined;
|
|
1517
|
+
const guardrails = facets.has('guardrails')
|
|
1518
|
+
? compileGuardrails(draft.guardrails, report)
|
|
1519
|
+
: undefined;
|
|
1520
|
+
const profile = omitOutOfScope({
|
|
1521
|
+
type,
|
|
1522
|
+
id: draft.identity.agentId.trim(),
|
|
1523
|
+
identity: {
|
|
1524
|
+
handle: draft.identity.handle.trim(),
|
|
1525
|
+
...(system ? { system } : {}),
|
|
1526
|
+
...(systemByRole ? { systemByRole } : {}),
|
|
1527
|
+
},
|
|
1528
|
+
...modelFields,
|
|
1529
|
+
...(facets.has('image') ? { image: compileImage(draft.image, report) } : {}),
|
|
1530
|
+
...(facets.has('speech') ? { speech: compileSpeech(draft.speech, draft, report) } : {}),
|
|
1531
|
+
...(facets.has('live') ? { live: compileLive(draft.live, report, on) } : {}),
|
|
1532
|
+
...(tools ? { tools } : {}),
|
|
1533
|
+
...(!facets.has('inputs')
|
|
1534
|
+
? {}
|
|
1535
|
+
: type === 'live'
|
|
1536
|
+
? compileLiveInputs(draft.inputs, report)
|
|
1537
|
+
: { inputs: compileInputs(draft.inputs, type, report) }),
|
|
1538
|
+
...(outputs ? { outputs } : {}),
|
|
1539
|
+
...(turnBehaviour ? { turnBehaviour } : {}),
|
|
1540
|
+
...(guardrails ? { guardrails } : {}),
|
|
1541
|
+
...compileProfileOptions(draft, facets, report),
|
|
1542
|
+
});
|
|
1543
|
+
return { profile, customTools, ...(structured ? { structured } : {}) };
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Every issue is reported, not just the first. `agentIdOf` and `agentInputOf` resolve the
|
|
1547
|
+
* agents a workspace draft names.
|
|
1548
|
+
*/
|
|
1549
|
+
export function compileStudio(draft, on, agentIdOf = NO_AGENTS, agentInputOf = NO_AGENT_INPUTS) {
|
|
1550
|
+
const issues = [];
|
|
1551
|
+
const report = (nodeId, message, field, index) => {
|
|
1552
|
+
issues.push({
|
|
1553
|
+
nodeId,
|
|
1554
|
+
message,
|
|
1555
|
+
...(field !== undefined ? { field } : {}),
|
|
1556
|
+
...(index !== undefined ? { index } : {}),
|
|
1557
|
+
});
|
|
1558
|
+
};
|
|
1559
|
+
checkIdentity(draft, report);
|
|
1560
|
+
const keySlots = new Set([
|
|
1561
|
+
draft.models.key,
|
|
1562
|
+
draft.models.fallbackKey,
|
|
1563
|
+
...draft.modelBindings.flatMap((binding) => {
|
|
1564
|
+
const { keySlot, fallbackKeySlot } = bindingSlots(draft, binding);
|
|
1565
|
+
return [keySlot, fallbackKeySlot];
|
|
1566
|
+
}),
|
|
1567
|
+
].filter(Boolean));
|
|
1568
|
+
if (keySlots.size > STUDIO_KEY_SLOT_CAP) {
|
|
1569
|
+
report('models', 'The studio supports up to 32 key slots.', 'key');
|
|
1570
|
+
}
|
|
1571
|
+
const type = draft.identity.profileType;
|
|
1572
|
+
if (!type) {
|
|
1573
|
+
report('identity', 'Pick a profile type.', 'profileType');
|
|
1574
|
+
return { ok: false, issues };
|
|
1575
|
+
}
|
|
1576
|
+
if (type !== 'host') {
|
|
1577
|
+
for (const binding of draft.modelBindings) {
|
|
1578
|
+
if (!binding.apiId.trim())
|
|
1579
|
+
continue;
|
|
1580
|
+
const violation = modelBindingViolation({ ...modelRoute(draft.providers, type, binding), builtInTools: binding.builtInTools }, on);
|
|
1581
|
+
if (violation) {
|
|
1582
|
+
report(modelBindingNodeId(binding.key), violation.message, violation.field);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
const compiled = type === 'decision'
|
|
1587
|
+
? compileDecision(draft, report, on)
|
|
1588
|
+
: type === 'host'
|
|
1589
|
+
? compileHost(draft, report, agentIdOf, agentInputOf)
|
|
1590
|
+
: assemble(draft, type, report, on, agentIdOf, agentInputOf);
|
|
1591
|
+
if (issues.length)
|
|
1592
|
+
return { ok: false, issues };
|
|
1593
|
+
try {
|
|
1594
|
+
defineProfile(uncompiled(compiled.profile));
|
|
1595
|
+
}
|
|
1596
|
+
catch (err) {
|
|
1597
|
+
if (!(err instanceof TheoremError))
|
|
1598
|
+
throw err;
|
|
1599
|
+
return {
|
|
1600
|
+
ok: false,
|
|
1601
|
+
issues: [{ nodeId: 'identity', message: err.message }],
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
return { ok: true, agentId: compiled.profile.id, providers: draft.providers, ...compiled };
|
|
1605
|
+
}
|