wizards 0.1.9
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 +110 -0
- package/README.md +170 -0
- package/apps/runtime/dist/agents/architect.js +453 -0
- package/apps/runtime/dist/agents/project-assistant.js +354 -0
- package/apps/runtime/dist/agents/subscription.js +206 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +197 -0
- package/apps/runtime/dist/app.js +153 -0
- package/apps/runtime/dist/auth/account.js +121 -0
- package/apps/runtime/dist/auth/index.js +329 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +23 -0
- package/apps/runtime/dist/authoring/example.js +73 -0
- package/apps/runtime/dist/authoring/guide.js +259 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/cli/autostart.js +134 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/environment.js +81 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/home.js +74 -0
- package/apps/runtime/dist/cli/machine.js +145 -0
- package/apps/runtime/dist/cli/main.js +125 -0
- package/apps/runtime/dist/cli/setup.js +273 -0
- package/apps/runtime/dist/cli/start.js +190 -0
- package/apps/runtime/dist/cli/status.js +120 -0
- package/apps/runtime/dist/cli/ui.js +12 -0
- package/apps/runtime/dist/cli/update.js +58 -0
- package/apps/runtime/dist/connectors/builtin.js +70 -0
- package/apps/runtime/dist/connectors/external.js +400 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +217 -0
- package/apps/runtime/dist/connectors/index.js +266 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/credits/credits.js +103 -0
- package/apps/runtime/dist/credits/estimate.js +132 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +146 -0
- package/apps/runtime/dist/db/control-schema.js +70 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/schema.js +278 -0
- package/apps/runtime/dist/documents/parse.js +230 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +60 -0
- package/apps/runtime/dist/engine/events.js +46 -0
- package/apps/runtime/dist/engine/input.js +170 -0
- package/apps/runtime/dist/engine/prepare.js +51 -0
- package/apps/runtime/dist/engine/requirements.js +124 -0
- package/apps/runtime/dist/engine/resources.js +89 -0
- package/apps/runtime/dist/engine/runner.js +560 -0
- package/apps/runtime/dist/engine/steps.js +598 -0
- package/apps/runtime/dist/engine/template.js +183 -0
- package/apps/runtime/dist/engine/types.js +3 -0
- package/apps/runtime/dist/env.js +224 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +95 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/claude.js +138 -0
- package/apps/runtime/dist/harness/codex.js +228 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/gemini.js +126 -0
- package/apps/runtime/dist/harness/index.js +99 -0
- package/apps/runtime/dist/harness/model.js +267 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +193 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +76 -0
- package/apps/runtime/dist/limits.js +57 -0
- package/apps/runtime/dist/link-preview.js +59 -0
- package/apps/runtime/dist/manage.js +151 -0
- package/apps/runtime/dist/mcp/auth.js +102 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/handler.js +42 -0
- package/apps/runtime/dist/mcp/scopes.js +5 -0
- package/apps/runtime/dist/mcp/tools.js +401 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +30 -0
- package/apps/runtime/dist/model-errors.js +3 -0
- package/apps/runtime/dist/models.js +489 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/render/chromium.js +85 -0
- package/apps/runtime/dist/render/convert.js +161 -0
- package/apps/runtime/dist/render/downloads.js +277 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/routes/api.js +74 -0
- package/apps/runtime/dist/routes/delivery.js +52 -0
- package/apps/runtime/dist/routes/internal.js +49 -0
- package/apps/runtime/dist/routes/marketplace.js +36 -0
- package/apps/runtime/dist/routes/runs.js +805 -0
- package/apps/runtime/dist/routes/studio.js +702 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/agentos.js +191 -0
- package/apps/runtime/dist/sandbox/docker.js +77 -0
- package/apps/runtime/dist/sandbox/index.js +30 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +34 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/architect.js +92 -0
- package/apps/runtime/dist/services/brand.js +20 -0
- package/apps/runtime/dist/services/cloud.js +52 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/errors.js +24 -0
- package/apps/runtime/dist/services/files.js +129 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +292 -0
- package/apps/runtime/dist/services/package.js +202 -0
- package/apps/runtime/dist/services/profile.js +23 -0
- package/apps/runtime/dist/services/project-files.js +266 -0
- package/apps/runtime/dist/services/project-index.js +109 -0
- package/apps/runtime/dist/services/projects.js +181 -0
- package/apps/runtime/dist/services/runs.js +224 -0
- package/apps/runtime/dist/services/shares.js +129 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizards.js +342 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/index.js +279 -0
- package/apps/runtime/dist/tenants/control.js +106 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/tools/browser.js +380 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +234 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +59 -0
- package/apps/runtime/dist/tools/project.js +53 -0
- package/apps/runtime/dist/tools/shared.js +42 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +118 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +312 -0
- package/apps/runtime/dist/widgets/runtime.js +84 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +192 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
- package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
- package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
- package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
- package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
- package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
- package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
- package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
- package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
- package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
- package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
- package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
- package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
- package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
- package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
- package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
- package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
- package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
- package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
- package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
- package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
- package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
- package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
- package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
- package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
- package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
- package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
- package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
- package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
- package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
- package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
- package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
- package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
- package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
- package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
- package/apps/web/dist/embed.js +102 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +46 -0
- package/apps/web/dist/install.sh +532 -0
- package/apps/web/dist/manifest.webmanifest +20 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +13 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +62 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { MODEL_CLASSES, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
|
|
4
|
+
import { PROJECT_FILE_KINDS } from "@engenty-wizards/shared/projects";
|
|
5
|
+
import { generateText } from "ai";
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
import { streamSSE } from "hono/streaming";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { runProjectAssistant } from "../agents/project-assistant.js";
|
|
10
|
+
import { chatEngine, setChatEngine, subscriptionClients } from "../agents/subscription.js";
|
|
11
|
+
import { accountOverview, linkedAccount, startLink, unlink } from "../auth/account.js";
|
|
12
|
+
import { createLocalKey, deleteLocalKey, listLocalKeys } from "../auth/keys.js";
|
|
13
|
+
import { connectorImportSchema, importConnector, listConnectors, previewSource, refreshConnector, registryService, removeConnector, searchRegistry, } from "../connectors/external.js";
|
|
14
|
+
import { balanceCredits, canSpend } from "../credits/credits.js";
|
|
15
|
+
import { estimateRun } from "../credits/estimate.js";
|
|
16
|
+
import { missingModels } from "../engine/requirements.js";
|
|
17
|
+
import { env } from "../env.js";
|
|
18
|
+
import { freshAuth, loginEnv } from "../harness/env.js";
|
|
19
|
+
import { detectHarness, detectHarnesses, HARNESS_IDS, harness, installSpec, } from "../harness/index.js";
|
|
20
|
+
import { closeTerminal, openTerminal, resizeTerminal, subscribeTerminal, writeTerminal, } from "../harness/terminal.js";
|
|
21
|
+
import { managed } from "../manage.js";
|
|
22
|
+
import { transcribeAudio } from "../media/transcribe.js";
|
|
23
|
+
import { embeddingModel, hasTextModel, LOCAL_KEYS, localModelSettings, ModelUnavailableError, saveLocalModels, textModel, } from "../models.js";
|
|
24
|
+
import { HTML_RESPONSE_CSP } from "../render/guard.js";
|
|
25
|
+
import { architectTurn } from "../services/architect.js";
|
|
26
|
+
import { cloudCopy, publishToCloud } from "../services/cloud.js";
|
|
27
|
+
import { ServiceError } from "../services/errors.js";
|
|
28
|
+
import { deleteFile, listFiles, readFile, writeFile } from "../services/files.js";
|
|
29
|
+
import { exportWizard, importWizard, PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKAGE_MIME, } from "../services/package.js";
|
|
30
|
+
import { profileSchema, saveProfile, userProfile } from "../services/profile.js";
|
|
31
|
+
import { addProjectFile, fileView, orderProjectFiles, projectFile, projectFileContent, projectFiles, reindexProjectFile, removeProjectFile, updateProjectFile, } from "../services/project-files.js";
|
|
32
|
+
import { documentText, searchProject } from "../services/project-index.js";
|
|
33
|
+
import { createProject, deleteProject, listProjects, maskedServers, ownedProject, projectLimit, projectPatchSchema, updateProject, } from "../services/projects.js";
|
|
34
|
+
import { listRuns, startTestRun } from "../services/runs.js";
|
|
35
|
+
import { draftWidgetPreview } from "../services/widgets.js";
|
|
36
|
+
import { createWizard, deleteWizard, duplicateWizard, listWizards, ownedWizard, publishWizard, rotateShareLink, updateWizardSettings, wizardMessages, wizardState, writeDraft, } from "../services/wizards.js";
|
|
37
|
+
import { readSetting, writeSetting } from "../settings.js";
|
|
38
|
+
import { applyUpdate, updateStatus } from "../update.js";
|
|
39
|
+
import { byteRange } from "./delivery.js";
|
|
40
|
+
/** The model sources a test call has worked on: the setup is done for those, not for the others. */
|
|
41
|
+
async function testedSources() {
|
|
42
|
+
const tested = await readSetting("setup-done");
|
|
43
|
+
return Array.isArray(tested) ? tested : [];
|
|
44
|
+
}
|
|
45
|
+
/** The chosen source has passed a test call and can still answer: its client is there and signed in. */
|
|
46
|
+
async function setupDone() {
|
|
47
|
+
const source = localModelSettings().source;
|
|
48
|
+
if (!(await testedSources()).includes(source)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const client = harness(source);
|
|
52
|
+
if (client) {
|
|
53
|
+
const status = await detectHarness(client.id);
|
|
54
|
+
return Boolean(status?.version) && status?.auth !== "none";
|
|
55
|
+
}
|
|
56
|
+
return hasTextModel();
|
|
57
|
+
}
|
|
58
|
+
/** The size of an inline terminal, as the page lays it out. */
|
|
59
|
+
const terminalSize = z.object({
|
|
60
|
+
cols: z.number().int().min(10).max(500).optional(),
|
|
61
|
+
rows: z.number().int().min(4).max(200).optional(),
|
|
62
|
+
});
|
|
63
|
+
export const studio = new Hono()
|
|
64
|
+
.get("/me", async (c) => {
|
|
65
|
+
const user = c.get("user");
|
|
66
|
+
// A runtime that runs alone may be linked to an account: its credits and the cloud to publish to.
|
|
67
|
+
const linked = managed ? null : await linkedAccount();
|
|
68
|
+
const overview = linked ? await accountOverview() : null;
|
|
69
|
+
const profile = await userProfile(user);
|
|
70
|
+
return c.json({
|
|
71
|
+
user: { id: user.id, name: profile.name, email: profile.email, image: user.image ?? null },
|
|
72
|
+
/** What the person says about themselves; name and e-mail are the account's when managed. */
|
|
73
|
+
profile,
|
|
74
|
+
tenant: { id: user.tenantId, role: user.role },
|
|
75
|
+
mode: managed ? "managed" : "local",
|
|
76
|
+
/** The tenant's balance; null where the runtime resolves models itself. */
|
|
77
|
+
credits: managed ? await balanceCredits() : null,
|
|
78
|
+
/** Where the account, members, API keys and credits are managed. */
|
|
79
|
+
manageUrl: managed ? env.manage.url : null,
|
|
80
|
+
account: linked
|
|
81
|
+
? {
|
|
82
|
+
name: linked.name,
|
|
83
|
+
email: linked.email,
|
|
84
|
+
credits: overview?.tenant.balanceCredits ?? null,
|
|
85
|
+
url: env.local.accountUrl,
|
|
86
|
+
cloudUrl: env.local.cloudUrl,
|
|
87
|
+
signedIn: Boolean(overview),
|
|
88
|
+
}
|
|
89
|
+
: null,
|
|
90
|
+
models: managed ? null : localModelSettings(),
|
|
91
|
+
/** The AI clients this runtime can think with — installed or not, and how each is signed in. */
|
|
92
|
+
harnesses: managed ? [] : await detectHarnesses(),
|
|
93
|
+
/** Installed AI clients whose subscription can answer the studio chat. */
|
|
94
|
+
subscriptions: managed ? [] : await subscriptionClients(),
|
|
95
|
+
/** A runtime that runs alone keeps its setup in front until a test call has worked on the chosen source and the way is still open. */
|
|
96
|
+
setupDone: managed ? true : await setupDone(),
|
|
97
|
+
/** What answers the studio chat: a model of class `highest`, or the admin's own subscription. */
|
|
98
|
+
chatEngine: managed ? "models" : await chatEngine(await hasTextModel()),
|
|
99
|
+
aiReady: (await hasTextModel()) || (!managed && (await subscriptionClients()).length > 0),
|
|
100
|
+
mcpUrl: `${env.appUrl}/api/mcp`,
|
|
101
|
+
/** One project: the studio shows no project switcher. */
|
|
102
|
+
limits: { projects: await projectLimit() },
|
|
103
|
+
});
|
|
104
|
+
})
|
|
105
|
+
// A runtime that runs alone says when a newer release is out and can run the update itself.
|
|
106
|
+
.get("/update", async (c) => managed ? c.json({ error: "not_local" }, 404) : c.json(await updateStatus()))
|
|
107
|
+
.post("/update", async (c) => {
|
|
108
|
+
if (managed) {
|
|
109
|
+
return c.json({ error: "not_local" }, 404);
|
|
110
|
+
}
|
|
111
|
+
applyUpdate();
|
|
112
|
+
return c.json(await updateStatus());
|
|
113
|
+
})
|
|
114
|
+
.put("/profile", async (c) => c.json(await saveProfile(c.get("user"), profileSchema.parse(await c.req.json()))))
|
|
115
|
+
// --- projects --------------------------------------------------------------
|
|
116
|
+
.get("/projects", async (c) => {
|
|
117
|
+
const projects = await listProjects(c.get("user").id);
|
|
118
|
+
return c.json(projects.map((p) => ({
|
|
119
|
+
id: p.id,
|
|
120
|
+
name: p.name,
|
|
121
|
+
brand: p.brand,
|
|
122
|
+
facts: p.facts,
|
|
123
|
+
mcpServers: maskedServers(p),
|
|
124
|
+
wizardCount: p.wizardCount,
|
|
125
|
+
})));
|
|
126
|
+
})
|
|
127
|
+
.post("/projects", async (c) => {
|
|
128
|
+
const body = z.object({ name: z.string().min(1).max(80) }).parse(await c.req.json());
|
|
129
|
+
return c.json(await createProject(c.get("user").id, body.name));
|
|
130
|
+
})
|
|
131
|
+
.patch("/projects/:id", async (c) => {
|
|
132
|
+
const patch = projectPatchSchema.parse(await c.req.json());
|
|
133
|
+
await updateProject(c.get("user").id, c.req.param("id"), patch);
|
|
134
|
+
return c.json({ ok: true });
|
|
135
|
+
})
|
|
136
|
+
// --- what a project holds for all its wizards: logos, assets, documents -------
|
|
137
|
+
.get("/projects/:id/files", async (c) => {
|
|
138
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
139
|
+
return c.json({
|
|
140
|
+
files: (await projectFiles(project.id)).map(fileView),
|
|
141
|
+
/** Whether documents get vectors, or the index works on keywords alone. */
|
|
142
|
+
embeddings: Boolean(await embeddingModel().catch(() => null)),
|
|
143
|
+
});
|
|
144
|
+
})
|
|
145
|
+
.post("/projects/:id/files", async (c) => {
|
|
146
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
147
|
+
const kind = z.enum(PROJECT_FILE_KINDS).parse(c.req.query("kind"));
|
|
148
|
+
const file = (await c.req.formData()).get("file");
|
|
149
|
+
if (!(file instanceof File)) {
|
|
150
|
+
throw new ServiceError("invalid", "Bitte eine Datei wählen.");
|
|
151
|
+
}
|
|
152
|
+
const row = await addProjectFile(project.id, {
|
|
153
|
+
kind,
|
|
154
|
+
name: file.name,
|
|
155
|
+
mime: file.type.split(";")[0].trim().toLowerCase(),
|
|
156
|
+
data: new Uint8Array(await file.arrayBuffer()),
|
|
157
|
+
});
|
|
158
|
+
return c.json(fileView(row));
|
|
159
|
+
})
|
|
160
|
+
.put("/projects/:id/files/order", async (c) => {
|
|
161
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
162
|
+
const body = z
|
|
163
|
+
.object({ kind: z.enum(PROJECT_FILE_KINDS), ids: z.array(z.string()).max(200) })
|
|
164
|
+
.parse(await c.req.json());
|
|
165
|
+
await orderProjectFiles(project.id, body.kind, body.ids);
|
|
166
|
+
return c.json({ ok: true });
|
|
167
|
+
})
|
|
168
|
+
.patch("/projects/:id/files/:fileId", async (c) => {
|
|
169
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
170
|
+
const patch = z
|
|
171
|
+
.object({ name: z.string().max(120).optional(), description: z.string().max(600).optional() })
|
|
172
|
+
.parse(await c.req.json());
|
|
173
|
+
return c.json(fileView(await updateProjectFile(project.id, c.req.param("fileId"), patch)));
|
|
174
|
+
})
|
|
175
|
+
.post("/projects/:id/files/:fileId/reindex", async (c) => {
|
|
176
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
177
|
+
return c.json(fileView(await reindexProjectFile(project.id, c.req.param("fileId"))));
|
|
178
|
+
})
|
|
179
|
+
.delete("/projects/:id/files/:fileId", async (c) => {
|
|
180
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
181
|
+
await removeProjectFile(project.id, c.req.param("fileId"));
|
|
182
|
+
return c.json({ ok: true });
|
|
183
|
+
})
|
|
184
|
+
.get("/projects/:id/files/:fileId/content", async (c) => {
|
|
185
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
186
|
+
const { row, data } = await projectFileContent(project.id, c.req.param("fileId"));
|
|
187
|
+
const headers = {
|
|
188
|
+
"content-type": row.mime,
|
|
189
|
+
"cache-control": "private, max-age=3600",
|
|
190
|
+
"x-content-type-options": "nosniff",
|
|
191
|
+
// An uploaded SVG or HTML file is shown, never run. A PDF is left to the browser's own
|
|
192
|
+
// viewer, which a sandbox would keep from starting.
|
|
193
|
+
...(row.mime === "application/pdf" ? {} : { "content-security-policy": "sandbox" }),
|
|
194
|
+
};
|
|
195
|
+
if (/^(audio|video)\//.test(row.mime)) {
|
|
196
|
+
headers["accept-ranges"] = "bytes";
|
|
197
|
+
const range = byteRange(c.req.header("range"), data.byteLength);
|
|
198
|
+
if (range === "unsatisfiable") {
|
|
199
|
+
return c.body(null, 416, { "content-range": `bytes */${data.byteLength}` });
|
|
200
|
+
}
|
|
201
|
+
if (range) {
|
|
202
|
+
headers["content-range"] = `bytes ${range.start}-${range.end}/${data.byteLength}`;
|
|
203
|
+
return c.body(new Uint8Array(data.subarray(range.start, range.end + 1)), 206, headers);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return c.body(new Uint8Array(data), 200, headers);
|
|
207
|
+
})
|
|
208
|
+
// A document as the models read it: its text as Markdown, or the start of it.
|
|
209
|
+
.get("/projects/:id/files/:fileId/text", async (c) => {
|
|
210
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
211
|
+
const row = await projectFile(project.id, c.req.param("fileId"));
|
|
212
|
+
const limit = z.coerce
|
|
213
|
+
.number()
|
|
214
|
+
.int()
|
|
215
|
+
.min(100)
|
|
216
|
+
.max(400_000)
|
|
217
|
+
.catch(200_000)
|
|
218
|
+
.parse(c.req.query("limit"));
|
|
219
|
+
const text = await documentText(row);
|
|
220
|
+
return c.json({ text: text.slice(0, limit), chars: text.length });
|
|
221
|
+
})
|
|
222
|
+
// What the document index finds for a question: the passages an agent step would get.
|
|
223
|
+
.post("/projects/:id/search", async (c) => {
|
|
224
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
225
|
+
const { query } = z.object({ query: z.string().min(1).max(400) }).parse(await c.req.json());
|
|
226
|
+
return c.json({ hits: await searchProject(project.id, query, 6) });
|
|
227
|
+
})
|
|
228
|
+
// The assistant fills the project in: from a description, a website, the files given.
|
|
229
|
+
.post("/projects/:id/assist", async (c) => {
|
|
230
|
+
const user = c.get("user");
|
|
231
|
+
const project = await ownedProject(user.id, c.req.param("id"));
|
|
232
|
+
const body = z
|
|
233
|
+
.object({
|
|
234
|
+
message: z.string().min(1).max(8000),
|
|
235
|
+
history: z
|
|
236
|
+
.array(z.object({ role: z.enum(["user", "assistant"]), content: z.string().max(8000) }))
|
|
237
|
+
.max(24)
|
|
238
|
+
.default([]),
|
|
239
|
+
})
|
|
240
|
+
.parse(await c.req.json());
|
|
241
|
+
if (!(await canSpend())) {
|
|
242
|
+
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
243
|
+
}
|
|
244
|
+
return streamSSE(c, async (stream) => {
|
|
245
|
+
const abort = new AbortController();
|
|
246
|
+
stream.onAbort(() => abort.abort());
|
|
247
|
+
try {
|
|
248
|
+
const result = await runProjectAssistant({
|
|
249
|
+
userId: user.id,
|
|
250
|
+
projectId: project.id,
|
|
251
|
+
message: body.message,
|
|
252
|
+
history: body.history,
|
|
253
|
+
signal: abort.signal,
|
|
254
|
+
onText: (delta) => {
|
|
255
|
+
void stream.writeSSE({ event: "text", data: JSON.stringify(delta) });
|
|
256
|
+
},
|
|
257
|
+
onActivity: (label) => {
|
|
258
|
+
void stream.writeSSE({ event: "activity", data: JSON.stringify(label) });
|
|
259
|
+
},
|
|
260
|
+
onChanged: () => {
|
|
261
|
+
void stream.writeSSE({ event: "changed", data: "1" });
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
await stream.writeSSE({
|
|
265
|
+
event: "done",
|
|
266
|
+
data: JSON.stringify({
|
|
267
|
+
reply: result.reply || (result.changed ? "Erledigt." : ""),
|
|
268
|
+
changed: result.changed,
|
|
269
|
+
}),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
console.error("[project-assistant]", err);
|
|
274
|
+
await stream.writeSSE({
|
|
275
|
+
event: "error",
|
|
276
|
+
data: JSON.stringify({
|
|
277
|
+
message: err instanceof ServiceError || err instanceof ModelUnavailableError
|
|
278
|
+
? err.message
|
|
279
|
+
: "Da ist etwas schiefgelaufen. Bitte noch einmal versuchen.",
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
})
|
|
285
|
+
.delete("/projects/:id", async (c) => {
|
|
286
|
+
await deleteProject(c.get("user").id, c.req.param("id"));
|
|
287
|
+
return c.json({ ok: true });
|
|
288
|
+
})
|
|
289
|
+
// --- connectors: services imported from the integrations registry ------------
|
|
290
|
+
.post("/connectors/search", async (c) => {
|
|
291
|
+
const { query } = z.object({ query: z.string().min(1).max(200) }).parse(await c.req.json());
|
|
292
|
+
return c.json(await searchRegistry(query));
|
|
293
|
+
})
|
|
294
|
+
.get("/connectors/registry/:domain", async (c) => c.json(await registryService(c.req.param("domain"))))
|
|
295
|
+
.post("/connectors/preview", async (c) => {
|
|
296
|
+
const body = z
|
|
297
|
+
.object({
|
|
298
|
+
domain: z.string().min(1).max(253).optional(),
|
|
299
|
+
sourceKind: z.enum(["openapi", "mcp"]),
|
|
300
|
+
sourceUrl: z.string().url(),
|
|
301
|
+
})
|
|
302
|
+
.parse(await c.req.json());
|
|
303
|
+
return c.json(await previewSource(body));
|
|
304
|
+
})
|
|
305
|
+
.get("/projects/:id/connectors", async (c) => {
|
|
306
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
307
|
+
return c.json(await listConnectors(project.id));
|
|
308
|
+
})
|
|
309
|
+
.post("/projects/:id/connectors", async (c) => {
|
|
310
|
+
const user = c.get("user");
|
|
311
|
+
const project = await ownedProject(user.id, c.req.param("id"));
|
|
312
|
+
return c.json(await importConnector(user.id, project.id, connectorImportSchema.parse(await c.req.json())));
|
|
313
|
+
})
|
|
314
|
+
.post("/projects/:id/connectors/:connectorId/refresh", async (c) => {
|
|
315
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
316
|
+
return c.json(await refreshConnector(project.id, c.req.param("connectorId"), null));
|
|
317
|
+
})
|
|
318
|
+
.delete("/projects/:id/connectors/:connectorId", async (c) => {
|
|
319
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
320
|
+
await removeConnector(project.id, c.req.param("connectorId"));
|
|
321
|
+
return c.json({ ok: true });
|
|
322
|
+
})
|
|
323
|
+
// --- wizards ---------------------------------------------------------------
|
|
324
|
+
.get("/projects/:id/wizards", async (c) => {
|
|
325
|
+
const user = c.get("user");
|
|
326
|
+
await ownedProject(user.id, c.req.param("id"));
|
|
327
|
+
return c.json(await listWizards(user.id, c.req.param("id")));
|
|
328
|
+
})
|
|
329
|
+
.post("/wizards", async (c) => {
|
|
330
|
+
const body = z
|
|
331
|
+
.object({
|
|
332
|
+
projectId: z.string(),
|
|
333
|
+
starterId: z.string().optional(),
|
|
334
|
+
lang: z.string().optional(),
|
|
335
|
+
})
|
|
336
|
+
.parse(await c.req.json());
|
|
337
|
+
const { id } = await createWizard(c.get("user").id, body);
|
|
338
|
+
return c.json({ id });
|
|
339
|
+
})
|
|
340
|
+
// A wizard as a file, and a new wizard made of such a file.
|
|
341
|
+
.post("/projects/:id/wizards/import", async (c) => {
|
|
342
|
+
const file = (await c.req.formData()).get("file");
|
|
343
|
+
if (!(file instanceof File) || !file.size || file.size > PACKAGE_MAX_BYTES) {
|
|
344
|
+
throw new ServiceError("invalid", `Bitte ein Wizard-Paket (${PACKAGE_EXTENSION}) bis 30 MB.`);
|
|
345
|
+
}
|
|
346
|
+
return c.json(await importWizard(c.get("user").id, c.req.param("id"), new Uint8Array(await file.arrayBuffer()), file.name));
|
|
347
|
+
})
|
|
348
|
+
.get("/wizards/:id/export", async (c) => {
|
|
349
|
+
const { name, zip } = await exportWizard(c.get("user").id, c.req.param("id"));
|
|
350
|
+
return c.body(new Uint8Array(zip), 200, {
|
|
351
|
+
"content-type": PACKAGE_MIME,
|
|
352
|
+
"content-disposition": `attachment; filename="${name}"`,
|
|
353
|
+
"cache-control": "private, no-store",
|
|
354
|
+
});
|
|
355
|
+
})
|
|
356
|
+
.get("/wizards/:id", async (c) => {
|
|
357
|
+
const user = c.get("user");
|
|
358
|
+
const w = await ownedWizard(user.id, c.req.param("id"));
|
|
359
|
+
const messages = await wizardMessages(w.id);
|
|
360
|
+
const project = await ownedProject(user.id, w.projectId);
|
|
361
|
+
return c.json({
|
|
362
|
+
...(await wizardState(w)),
|
|
363
|
+
blank: messages.length === 0 && !w.starter,
|
|
364
|
+
messages: messages.map((m) => ({
|
|
365
|
+
id: m.id,
|
|
366
|
+
role: m.role,
|
|
367
|
+
content: m.content,
|
|
368
|
+
changed: m.changed,
|
|
369
|
+
source: m.source,
|
|
370
|
+
client: m.client,
|
|
371
|
+
})),
|
|
372
|
+
mcpServers: project.mcpServers.map((s) => ({ id: s.id, name: s.name })),
|
|
373
|
+
});
|
|
374
|
+
})
|
|
375
|
+
.put("/wizards/:id/draft", async (c) => {
|
|
376
|
+
const body = z
|
|
377
|
+
.object({ definition: z.unknown(), baseRevision: z.number().int().min(0) })
|
|
378
|
+
.parse(await c.req.json());
|
|
379
|
+
const { revision, issues } = await writeDraft(c.get("user").id, c.req.param("id"), body);
|
|
380
|
+
return c.json({ ok: true, revision, issues });
|
|
381
|
+
})
|
|
382
|
+
.patch("/wizards/:id", async (c) => {
|
|
383
|
+
const patch = z
|
|
384
|
+
.object({
|
|
385
|
+
shareEnabled: z.boolean().optional(),
|
|
386
|
+
dailyRunLimit: z.number().int().min(1).max(10_000).optional(),
|
|
387
|
+
projectId: z.string().optional(),
|
|
388
|
+
})
|
|
389
|
+
.parse(await c.req.json());
|
|
390
|
+
await updateWizardSettings(c.get("user").id, c.req.param("id"), patch);
|
|
391
|
+
return c.json({ ok: true });
|
|
392
|
+
})
|
|
393
|
+
.post("/wizards/:id/rotate-link", async (c) => c.json(await rotateShareLink(c.get("user").id, c.req.param("id"))))
|
|
394
|
+
.post("/wizards/:id/duplicate", async (c) => c.json(await duplicateWizard(c.get("user").id, c.req.param("id"))))
|
|
395
|
+
.delete("/wizards/:id", async (c) => {
|
|
396
|
+
await deleteWizard(c.get("user").id, c.req.param("id"));
|
|
397
|
+
return c.json({ ok: true });
|
|
398
|
+
})
|
|
399
|
+
.post("/wizards/:id/publish", async (c) => c.json(await publishWizard(c.get("user").id, c.req.param("id"))))
|
|
400
|
+
// What a run of the draft is expected to cost, per step and in total.
|
|
401
|
+
.get("/wizards/:id/estimate", async (c) => {
|
|
402
|
+
const w = await ownedWizard(c.get("user").id, c.req.param("id"));
|
|
403
|
+
return c.json(await estimateRun(w.id, null, w.draft));
|
|
404
|
+
})
|
|
405
|
+
// The models the draft needs that this runtime cannot serve, by class, with the steps that call them.
|
|
406
|
+
.get("/wizards/:id/models", async (c) => {
|
|
407
|
+
const w = await ownedWizard(c.get("user").id, c.req.param("id"));
|
|
408
|
+
return c.json({ missing: await missingModels(w.draft) });
|
|
409
|
+
})
|
|
410
|
+
// A runtime that runs alone: the wizard's copy in the cloud of the linked account.
|
|
411
|
+
.get("/wizards/:id/cloud", async (c) => {
|
|
412
|
+
const w = await ownedWizard(c.get("user").id, c.req.param("id"));
|
|
413
|
+
return c.json({ copy: managed ? null : await cloudCopy(w.id) });
|
|
414
|
+
})
|
|
415
|
+
.post("/wizards/:id/cloud", async (c) => {
|
|
416
|
+
if (managed) {
|
|
417
|
+
return c.notFound();
|
|
418
|
+
}
|
|
419
|
+
return c.json(await publishToCloud(c.get("user").id, c.req.param("id")));
|
|
420
|
+
})
|
|
421
|
+
.post("/wizards/:id/chat", async (c) => {
|
|
422
|
+
const user = c.get("user");
|
|
423
|
+
const wizardId = c.req.param("id");
|
|
424
|
+
const { message } = z
|
|
425
|
+
.object({ message: z.string().min(1).max(8000) })
|
|
426
|
+
.parse(await c.req.json());
|
|
427
|
+
await ownedWizard(user.id, wizardId);
|
|
428
|
+
if (!(await canSpend())) {
|
|
429
|
+
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
430
|
+
}
|
|
431
|
+
return streamSSE(c, async (stream) => {
|
|
432
|
+
const abort = new AbortController();
|
|
433
|
+
stream.onAbort(() => abort.abort());
|
|
434
|
+
try {
|
|
435
|
+
const result = await architectTurn(user.id, wizardId, message, {
|
|
436
|
+
signal: abort.signal,
|
|
437
|
+
onText: (delta) => {
|
|
438
|
+
void stream.writeSSE({ event: "text", data: JSON.stringify(delta) });
|
|
439
|
+
},
|
|
440
|
+
onActivity: (label) => {
|
|
441
|
+
void stream.writeSSE({ event: "activity", data: JSON.stringify(label) });
|
|
442
|
+
},
|
|
443
|
+
onThought: (line) => {
|
|
444
|
+
void stream.writeSSE({ event: "thought", data: JSON.stringify(line) });
|
|
445
|
+
},
|
|
446
|
+
onBuilding: () => {
|
|
447
|
+
void stream.writeSSE({ event: "building", data: "1" });
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
await stream.writeSSE({ event: "done", data: JSON.stringify(result) });
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
console.error("[architect]", err);
|
|
454
|
+
await stream.writeSSE({
|
|
455
|
+
event: "error",
|
|
456
|
+
data: JSON.stringify({
|
|
457
|
+
message: err instanceof ServiceError
|
|
458
|
+
? err.message
|
|
459
|
+
: "Da ist etwas schiefgelaufen. Bitte noch einmal versuchen.",
|
|
460
|
+
}),
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
})
|
|
465
|
+
// What the admin says into the composer, written down — where the browser cannot do it itself.
|
|
466
|
+
.post("/transcribe", async (c) => {
|
|
467
|
+
const file = (await c.req.formData()).get("file");
|
|
468
|
+
if (!(file instanceof File) || !file.size || file.size > 10_000_000) {
|
|
469
|
+
throw new ServiceError("invalid", "Bitte eine Aufnahme bis 10 MB.");
|
|
470
|
+
}
|
|
471
|
+
if (!(await canSpend())) {
|
|
472
|
+
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
const { text } = await transcribeAudio({
|
|
476
|
+
bytes: new Uint8Array(await file.arrayBuffer()),
|
|
477
|
+
mediaType: file.type || "audio/webm",
|
|
478
|
+
});
|
|
479
|
+
return c.json({ text });
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
if (err instanceof ModelUnavailableError) {
|
|
483
|
+
throw new ServiceError("refused", err.message);
|
|
484
|
+
}
|
|
485
|
+
throw err;
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
.post("/wizards/:id/test-runs", async (c) => c.json(await startTestRun(c.get("user").id, c.req.param("id"))))
|
|
489
|
+
.get("/wizards/:id/runs", async (c) => c.json(await listRuns(c.get("user").id, c.req.param("id"))))
|
|
490
|
+
// --- workspace -------------------------------------------------------------
|
|
491
|
+
.get("/wizards/:id/files", async (c) => c.json(await listFiles(c.get("user").id, c.req.param("id"))))
|
|
492
|
+
.get("/wizards/:id/files/:path{.+}", async (c) => {
|
|
493
|
+
const { file, data } = await readFile(c.get("user").id, c.req.param("id"), c.req.param("path"));
|
|
494
|
+
return c.body(new Uint8Array(data), 200, {
|
|
495
|
+
"content-type": file.mime,
|
|
496
|
+
"content-disposition": `attachment; filename="${file.path.split("/").pop()}"`,
|
|
497
|
+
"cache-control": "private, no-cache",
|
|
498
|
+
});
|
|
499
|
+
})
|
|
500
|
+
.put("/wizards/:id/files/:path{.+}", async (c) => {
|
|
501
|
+
const data = new Uint8Array(await c.req.arrayBuffer());
|
|
502
|
+
const file = await writeFile(c.get("user").id, c.req.param("id"), c.req.param("path"), data, c.req.header("content-type")?.split(";")[0]);
|
|
503
|
+
return c.json(file);
|
|
504
|
+
})
|
|
505
|
+
.delete("/wizards/:id/files/:path{.+}", async (c) => {
|
|
506
|
+
await deleteFile(c.get("user").id, c.req.param("id"), c.req.param("path"));
|
|
507
|
+
return c.json({ ok: true });
|
|
508
|
+
})
|
|
509
|
+
.get("/wizards/:id/widgets/:stepId/preview", async (c) => {
|
|
510
|
+
const html = await draftWidgetPreview(c.get("user").id, c.req.param("id"), c.req.param("stepId"));
|
|
511
|
+
return c.body(new TextEncoder().encode(html), 200, {
|
|
512
|
+
"content-type": "text/html; charset=utf-8",
|
|
513
|
+
"content-security-policy": HTML_RESPONSE_CSP,
|
|
514
|
+
"cache-control": "no-store",
|
|
515
|
+
});
|
|
516
|
+
})
|
|
517
|
+
// --- a runtime that runs alone: its keys, models and linked account -----------
|
|
518
|
+
.get("/api-keys", async (c) => (managed ? c.notFound() : c.json(await listLocalKeys())))
|
|
519
|
+
.post("/api-keys", async (c) => {
|
|
520
|
+
if (managed) {
|
|
521
|
+
return c.notFound();
|
|
522
|
+
}
|
|
523
|
+
const { name } = z.object({ name: z.string().min(1).max(32) }).parse(await c.req.json());
|
|
524
|
+
return c.json(await createLocalKey(name));
|
|
525
|
+
})
|
|
526
|
+
.delete("/api-keys/:id", async (c) => {
|
|
527
|
+
if (managed) {
|
|
528
|
+
return c.notFound();
|
|
529
|
+
}
|
|
530
|
+
await deleteLocalKey(c.req.param("id"));
|
|
531
|
+
return c.json({ ok: true });
|
|
532
|
+
})
|
|
533
|
+
.get("/local/models", (c) => (managed ? c.notFound() : c.json(localModelSettings())))
|
|
534
|
+
.put("/local/models", async (c) => {
|
|
535
|
+
if (managed) {
|
|
536
|
+
return c.notFound();
|
|
537
|
+
}
|
|
538
|
+
const body = z
|
|
539
|
+
.object({
|
|
540
|
+
source: z.enum([...HARNESS_IDS, "account", "own"]).optional(),
|
|
541
|
+
bindings: z.partialRecord(z.enum(MODEL_CLASSES), z.string().max(120)).optional(),
|
|
542
|
+
ollamaUrl: z.string().max(200).optional(),
|
|
543
|
+
keys: z.partialRecord(z.enum(LOCAL_KEYS), z.string().max(400)).optional(),
|
|
544
|
+
})
|
|
545
|
+
.parse(await c.req.json());
|
|
546
|
+
await saveLocalModels(body);
|
|
547
|
+
return c.json(localModelSettings());
|
|
548
|
+
})
|
|
549
|
+
// One short call on a class, as a run would make it: shows that the way to the model is open.
|
|
550
|
+
.post("/local/models/test", async (c) => {
|
|
551
|
+
if (managed) {
|
|
552
|
+
return c.notFound();
|
|
553
|
+
}
|
|
554
|
+
const { cls } = z
|
|
555
|
+
.object({ cls: z.enum(TEXT_CLASSES).default("classifier") })
|
|
556
|
+
.parse(await c.req.json().catch(() => ({})));
|
|
557
|
+
const started = Date.now();
|
|
558
|
+
// The source this call runs on: the person may pick another one while it is under way.
|
|
559
|
+
const source = localModelSettings().source;
|
|
560
|
+
try {
|
|
561
|
+
const resolved = await textModel(cls);
|
|
562
|
+
const result = await generateText({
|
|
563
|
+
model: resolved.model,
|
|
564
|
+
prompt: "Reply with the single word: OK",
|
|
565
|
+
maxOutputTokens: 20,
|
|
566
|
+
abortSignal: AbortSignal.timeout(120_000),
|
|
567
|
+
});
|
|
568
|
+
// A working call is what ends the setup — for the source it ran on.
|
|
569
|
+
const tested = await testedSources();
|
|
570
|
+
if (!tested.includes(source)) {
|
|
571
|
+
await writeSetting("setup-done", [...tested, source]);
|
|
572
|
+
}
|
|
573
|
+
return c.json({
|
|
574
|
+
ok: true,
|
|
575
|
+
ref: resolved.ref,
|
|
576
|
+
reply: result.text.trim().slice(0, 200),
|
|
577
|
+
ms: Date.now() - started,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
const message = err?.message ?? String(err);
|
|
582
|
+
return c.json({ ok: false, error: message.slice(0, 500), ms: Date.now() - started });
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
// --- the AI clients on this machine: looked at afresh, signed in through the inline terminal ----
|
|
586
|
+
.post("/local/harness/:id/detect", async (c) => {
|
|
587
|
+
const id = c.req.param("id");
|
|
588
|
+
if (managed || !harness(id)) {
|
|
589
|
+
return c.notFound();
|
|
590
|
+
}
|
|
591
|
+
return c.json({ harness: await detectHarness(id, true) });
|
|
592
|
+
})
|
|
593
|
+
.post("/local/harness/:id/install", async (c) => {
|
|
594
|
+
const spec = managed ? null : await installSpec(c.req.param("id"));
|
|
595
|
+
if (!spec) {
|
|
596
|
+
return c.notFound();
|
|
597
|
+
}
|
|
598
|
+
const { cols, rows } = terminalSize.parse(await c.req.json().catch(() => ({})));
|
|
599
|
+
const cwd = join(env.dataDir, "harness");
|
|
600
|
+
mkdirSync(cwd, { recursive: true });
|
|
601
|
+
// The page asks again once the command is over: then the client is there, or its output says why not.
|
|
602
|
+
const terminal = await openTerminal({ ...spec, cwd, cols, rows });
|
|
603
|
+
return c.json({ terminal });
|
|
604
|
+
})
|
|
605
|
+
.post("/local/harness/:id/login", async (c) => {
|
|
606
|
+
const id = c.req.param("id");
|
|
607
|
+
const client = harness(id);
|
|
608
|
+
if (managed || !client) {
|
|
609
|
+
return c.notFound();
|
|
610
|
+
}
|
|
611
|
+
const { cols, rows } = terminalSize.parse(await c.req.json().catch(() => ({})));
|
|
612
|
+
const cwd = join(env.dataDir, "harness");
|
|
613
|
+
mkdirSync(cwd, { recursive: true });
|
|
614
|
+
const terminal = await openTerminal({
|
|
615
|
+
bin: client.bin,
|
|
616
|
+
args: client.login.args,
|
|
617
|
+
cwd,
|
|
618
|
+
env: { ...(await loginEnv(client)), ...client.login.env },
|
|
619
|
+
cols,
|
|
620
|
+
rows,
|
|
621
|
+
// Signed in: the client says so on its own, and the settings learn it right away.
|
|
622
|
+
until: async () => (await freshAuth(client)) !== "none",
|
|
623
|
+
onDone: () => void detectHarness(id, true),
|
|
624
|
+
});
|
|
625
|
+
return c.json({ terminal });
|
|
626
|
+
})
|
|
627
|
+
.get("/local/terminal/:id/stream", (c) => {
|
|
628
|
+
if (managed) {
|
|
629
|
+
return c.notFound();
|
|
630
|
+
}
|
|
631
|
+
const id = c.req.param("id");
|
|
632
|
+
return streamSSE(c, async (stream) => {
|
|
633
|
+
let ended = false;
|
|
634
|
+
// One write after the other, and all of them out before the stream ends: the last one is
|
|
635
|
+
// the exit, and a page that comes after the end gets everything at once.
|
|
636
|
+
let written = Promise.resolve();
|
|
637
|
+
const done = new Promise((resolve) => {
|
|
638
|
+
const unsubscribe = subscribeTerminal(id, (event) => {
|
|
639
|
+
written = written.then(() => stream.writeSSE({ event: event.type, data: JSON.stringify(event) }));
|
|
640
|
+
if (event.type === "exit") {
|
|
641
|
+
ended = true;
|
|
642
|
+
resolve();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
stream.onAbort(() => {
|
|
646
|
+
unsubscribe();
|
|
647
|
+
resolve();
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
// The page keeps the stream open while the terminal lives; a heartbeat keeps proxies from closing it.
|
|
651
|
+
const beat = setInterval(() => {
|
|
652
|
+
if (!ended) {
|
|
653
|
+
void stream.writeSSE({ event: "ping", data: "" });
|
|
654
|
+
}
|
|
655
|
+
}, 15_000);
|
|
656
|
+
await done;
|
|
657
|
+
clearInterval(beat);
|
|
658
|
+
await written;
|
|
659
|
+
});
|
|
660
|
+
})
|
|
661
|
+
.post("/local/terminal/:id/input", async (c) => {
|
|
662
|
+
if (managed) {
|
|
663
|
+
return c.notFound();
|
|
664
|
+
}
|
|
665
|
+
const { data } = z.object({ data: z.string().max(10_000) }).parse(await c.req.json());
|
|
666
|
+
writeTerminal(c.req.param("id"), data);
|
|
667
|
+
return c.json({ ok: true });
|
|
668
|
+
})
|
|
669
|
+
.post("/local/terminal/:id/resize", async (c) => {
|
|
670
|
+
if (managed) {
|
|
671
|
+
return c.notFound();
|
|
672
|
+
}
|
|
673
|
+
const { cols, rows } = z
|
|
674
|
+
.object({ cols: z.number().int().min(10).max(500), rows: z.number().int().min(4).max(200) })
|
|
675
|
+
.parse(await c.req.json());
|
|
676
|
+
resizeTerminal(c.req.param("id"), cols, rows);
|
|
677
|
+
return c.json({ ok: true });
|
|
678
|
+
})
|
|
679
|
+
.delete("/local/terminal/:id", (c) => {
|
|
680
|
+
if (managed) {
|
|
681
|
+
return c.notFound();
|
|
682
|
+
}
|
|
683
|
+
closeTerminal(c.req.param("id"));
|
|
684
|
+
return c.json({ ok: true });
|
|
685
|
+
})
|
|
686
|
+
.put("/local/chat", async (c) => {
|
|
687
|
+
if (managed) {
|
|
688
|
+
return c.notFound();
|
|
689
|
+
}
|
|
690
|
+
const { engine } = z.object({ engine: z.enum(["models", "claude"]) }).parse(await c.req.json());
|
|
691
|
+
await setChatEngine(engine);
|
|
692
|
+
return c.json({ ok: true });
|
|
693
|
+
})
|
|
694
|
+
.post("/account/link", async (c) => (managed ? c.notFound() : c.json({ url: await startLink() })))
|
|
695
|
+
.delete("/account", async (c) => {
|
|
696
|
+
if (managed) {
|
|
697
|
+
return c.notFound();
|
|
698
|
+
}
|
|
699
|
+
await unlink();
|
|
700
|
+
await saveLocalModels({ source: "own" });
|
|
701
|
+
return c.json({ ok: true });
|
|
702
|
+
});
|