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,489 @@
|
|
|
1
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2
|
+
import { createGateway } from "@ai-sdk/gateway";
|
|
3
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
4
|
+
import { MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
|
|
5
|
+
import { accountToken } from "./auth/account.js";
|
|
6
|
+
import { env } from "./env.js";
|
|
7
|
+
import { detectHarness, HarnessModel, harness, isHarnessVendor, notInstalled, signedOut, } from "./harness/index.js";
|
|
8
|
+
import { managed } from "./manage.js";
|
|
9
|
+
import { ModelUnavailableError } from "./model-errors.js";
|
|
10
|
+
import { vaultDelete, vaultGet, vaultSet } from "./secrets/vault.js";
|
|
11
|
+
import { readSetting, writeSetting } from "./settings.js";
|
|
12
|
+
import { currentTenantOrNull } from "./tenants/tenant.js";
|
|
13
|
+
export { isHarnessVendor } from "./harness/index.js";
|
|
14
|
+
/**
|
|
15
|
+
* A step names a model class, never a model. Where the class runs is decided here:
|
|
16
|
+
* - a runtime of a Manage-App sends `wizards/<class>` to the model-gateway, which binds it,
|
|
17
|
+
* meters the call and books the tenant's credits;
|
|
18
|
+
* - a runtime that runs alone uses the linked account's credits through the same gateway, or
|
|
19
|
+
* resolves the class itself: an AI client installed on the machine (Claude Code, Codex,
|
|
20
|
+
* Gemini CLI, Cursor Agent — on its subscription), own keys (AI Gateway, OpenAI, Anthropic)
|
|
21
|
+
* or a local model.
|
|
22
|
+
* A local binding is `[provider:]vendor/model`, e.g. `anthropic/claude-haiku-4.5`,
|
|
23
|
+
* `openai:gpt-5.4-mini`, `ollama:qwen3`, `claude/sonnet`, `codex/default`.
|
|
24
|
+
*/
|
|
25
|
+
export { ModelUnavailableError } from "./model-errors.js";
|
|
26
|
+
// --- local configuration -------------------------------------------------------
|
|
27
|
+
export const LOCAL_KEYS = ["gateway", "openai", "anthropic"];
|
|
28
|
+
const local = {
|
|
29
|
+
settings: { source: "own", bindings: {} },
|
|
30
|
+
keys: {},
|
|
31
|
+
};
|
|
32
|
+
/** Reads the model settings and keys of a runtime that runs alone; called at start and after a change. */
|
|
33
|
+
export async function loadLocalModels() {
|
|
34
|
+
if (managed) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
local.settings = (await readSetting("models")) ?? {
|
|
38
|
+
source: "own",
|
|
39
|
+
bindings: {},
|
|
40
|
+
};
|
|
41
|
+
local.keys = {
|
|
42
|
+
gateway: (await vaultGet("key.gateway")) ?? env.aiGatewayKey,
|
|
43
|
+
openai: (await vaultGet("key.openai")) ?? env.openaiKey,
|
|
44
|
+
anthropic: (await vaultGet("key.anthropic")) ?? env.anthropicKey,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function localModelSettings() {
|
|
48
|
+
return {
|
|
49
|
+
...local.settings,
|
|
50
|
+
bindings: Object.fromEntries(MODEL_CLASSES.map((c) => [c, localRef(c)])),
|
|
51
|
+
ollamaUrl: local.settings.ollamaUrl ?? env.ollamaUrl,
|
|
52
|
+
keys: Object.fromEntries(LOCAL_KEYS.map((k) => [k, Boolean(local.keys[k])])),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export async function saveLocalModels(input) {
|
|
56
|
+
const bindings = { ...local.settings.bindings };
|
|
57
|
+
for (const [cls, ref] of Object.entries(input.bindings ?? {})) {
|
|
58
|
+
if (ref?.trim()) {
|
|
59
|
+
bindings[cls] = ref.trim();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
delete bindings[cls];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
await writeSetting("models", {
|
|
66
|
+
source: input.source ?? local.settings.source,
|
|
67
|
+
bindings,
|
|
68
|
+
ollamaUrl: input.ollamaUrl?.trim() || local.settings.ollamaUrl,
|
|
69
|
+
});
|
|
70
|
+
for (const [name, value] of Object.entries(input.keys ?? {})) {
|
|
71
|
+
if (value?.trim()) {
|
|
72
|
+
await vaultSet(`key.${name}`, value.trim());
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
await vaultDelete(`key.${name}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
await loadLocalModels();
|
|
79
|
+
}
|
|
80
|
+
async function gatewayAccess() {
|
|
81
|
+
if (managed) {
|
|
82
|
+
return {
|
|
83
|
+
baseUrl: env.manage.gatewayUrl,
|
|
84
|
+
token: env.manage.serviceKey,
|
|
85
|
+
tenant: currentTenantOrNull(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (local.settings.source !== "account") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const token = await accountToken();
|
|
92
|
+
if (!token) {
|
|
93
|
+
throw new ModelUnavailableError("Das Konto ist nicht mehr angemeldet. Bitte in den Einstellungen neu anmelden.");
|
|
94
|
+
}
|
|
95
|
+
return { baseUrl: env.local.gatewayUrl, token, tenant: null };
|
|
96
|
+
}
|
|
97
|
+
function gatewayClient(access, meta) {
|
|
98
|
+
return createGateway({
|
|
99
|
+
baseURL: `${access.baseUrl}/v4/ai`,
|
|
100
|
+
apiKey: access.token,
|
|
101
|
+
headers: {
|
|
102
|
+
...(access.tenant ? { "x-wizards-tenant": access.tenant } : {}),
|
|
103
|
+
...(meta.runId ? { "x-wizards-run": meta.runId } : {}),
|
|
104
|
+
...(meta.stepId ? { "x-wizards-step": meta.stepId } : {}),
|
|
105
|
+
...(meta.effort ? { "x-wizards-effort": meta.effort } : {}),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
let gatewayCatalog = null;
|
|
110
|
+
/** What the gateway binds each class to, with prices in credits. Cached for five minutes. */
|
|
111
|
+
export async function classCatalog() {
|
|
112
|
+
const access = await gatewayAccess().catch(() => null);
|
|
113
|
+
if (!access) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (gatewayCatalog &&
|
|
117
|
+
gatewayCatalog.base === access.baseUrl &&
|
|
118
|
+
Date.now() - gatewayCatalog.at < 300_000) {
|
|
119
|
+
return gatewayCatalog.value;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetch(`${access.baseUrl}/v1/models`, {
|
|
123
|
+
headers: {
|
|
124
|
+
authorization: `Bearer ${access.token}`,
|
|
125
|
+
...(access.tenant ? { "x-wizards-tenant": access.tenant } : {}),
|
|
126
|
+
},
|
|
127
|
+
signal: AbortSignal.timeout(10_000),
|
|
128
|
+
});
|
|
129
|
+
if (!res.ok) {
|
|
130
|
+
return gatewayCatalog?.value ?? null;
|
|
131
|
+
}
|
|
132
|
+
const value = (await res.json());
|
|
133
|
+
gatewayCatalog = { at: Date.now(), base: access.baseUrl, value };
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return gatewayCatalog?.value ?? null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// --- resolving a class -------------------------------------------------------------
|
|
141
|
+
function split(ref) {
|
|
142
|
+
const [maybeProvider, rest] = ref.includes(":") ? ref.split(/:(.*)/s, 2) : [null, ref];
|
|
143
|
+
const [vendor, ...model] = (rest ?? ref).split("/");
|
|
144
|
+
return { provider: maybeProvider, vendor, model: model.join("/") };
|
|
145
|
+
}
|
|
146
|
+
const vendorOf = (ref) => split(ref).vendor;
|
|
147
|
+
function localRef(cls) {
|
|
148
|
+
const bound = local.settings.bindings[cls];
|
|
149
|
+
const client = harness(local.settings.source);
|
|
150
|
+
if (client && TEXT_CLASSES.includes(cls)) {
|
|
151
|
+
// Text thinks on the installed client; a binding counts only where it names that client.
|
|
152
|
+
return bound?.startsWith(`${client.id}/`)
|
|
153
|
+
? bound
|
|
154
|
+
: `${client.id}/${client.classes[cls]}`;
|
|
155
|
+
}
|
|
156
|
+
if (bound) {
|
|
157
|
+
return bound;
|
|
158
|
+
}
|
|
159
|
+
// A client that makes images on the sign-in makes them too, before any key is paid for.
|
|
160
|
+
if (cls === "image" && client?.image) {
|
|
161
|
+
return `${client.id}/${client.image.alias}`;
|
|
162
|
+
}
|
|
163
|
+
// The defaults for images, video and sound name gateway models; without a gateway key nothing
|
|
164
|
+
// reaches them, so the class stays empty instead of showing a model that cannot run.
|
|
165
|
+
if (!TEXT_CLASSES.includes(cls) && !local.keys.gateway) {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
return env.models[cls];
|
|
169
|
+
}
|
|
170
|
+
function ownGateway() {
|
|
171
|
+
return local.keys.gateway ? createGateway({ apiKey: local.keys.gateway }) : null;
|
|
172
|
+
}
|
|
173
|
+
async function localLanguageModel(ref, meta) {
|
|
174
|
+
if (!ref) {
|
|
175
|
+
throw new ModelUnavailableError("Für diese Klasse ist kein Modell eingerichtet.");
|
|
176
|
+
}
|
|
177
|
+
const { provider, vendor, model } = split(ref);
|
|
178
|
+
const client = harness(vendor);
|
|
179
|
+
if (client) {
|
|
180
|
+
if (!(await detectHarness(client.id))?.version) {
|
|
181
|
+
throw new ModelUnavailableError(notInstalled(client));
|
|
182
|
+
}
|
|
183
|
+
return { model: client.model(model, meta.effort), gateway: false };
|
|
184
|
+
}
|
|
185
|
+
if (provider === "ollama") {
|
|
186
|
+
const ollama = createOpenAI({
|
|
187
|
+
baseURL: local.settings.ollamaUrl ?? env.ollamaUrl,
|
|
188
|
+
apiKey: "ollama",
|
|
189
|
+
});
|
|
190
|
+
return { model: ollama.chat(model ? `${vendor}/${model}` : vendor), gateway: false };
|
|
191
|
+
}
|
|
192
|
+
const gateway = ownGateway();
|
|
193
|
+
if ((provider === null || provider === "gateway") && gateway) {
|
|
194
|
+
return { model: gateway(`${vendor}/${model}`), gateway: true };
|
|
195
|
+
}
|
|
196
|
+
if ((provider === "openai" || vendor === "openai") && local.keys.openai) {
|
|
197
|
+
return {
|
|
198
|
+
model: createOpenAI({ apiKey: local.keys.openai })(model || vendor),
|
|
199
|
+
gateway: false,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if ((provider === "anthropic" || vendor === "anthropic") && local.keys.anthropic) {
|
|
203
|
+
return {
|
|
204
|
+
model: createAnthropic({ apiKey: local.keys.anthropic })(model || vendor),
|
|
205
|
+
gateway: false,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
throw new ModelUnavailableError(`Für das Modell "${ref}" ist kein Zugang eingerichtet. Bitte in den Einstellungen einen Schlüssel hinterlegen oder ein Konto anmelden.`);
|
|
209
|
+
}
|
|
210
|
+
async function viaGateway(cls, meta, pick) {
|
|
211
|
+
const access = await gatewayAccess();
|
|
212
|
+
if (!access) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const bound = (await classCatalog())?.classes[cls]?.model ?? "";
|
|
216
|
+
return {
|
|
217
|
+
model: pick(gatewayClient(access, meta), `wizards/${cls}`),
|
|
218
|
+
ref: bound,
|
|
219
|
+
vendor: vendorOf(bound.replace(/^[a-z]+:/, "")),
|
|
220
|
+
gateway: true,
|
|
221
|
+
metered: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** The language model a text class runs on. */
|
|
225
|
+
export async function textModel(cls, meta = {}) {
|
|
226
|
+
const remote = await viaGateway(cls, meta, (client, id) => client(id));
|
|
227
|
+
if (remote) {
|
|
228
|
+
return remote;
|
|
229
|
+
}
|
|
230
|
+
const ref = localRef(cls);
|
|
231
|
+
const { model, gateway } = await localLanguageModel(ref, meta);
|
|
232
|
+
return { model, ref, vendor: vendorOf(ref), gateway, metered: false };
|
|
233
|
+
}
|
|
234
|
+
/** Hands the tools an agent will call to a model that runs them itself (an installed AI client). */
|
|
235
|
+
export function attachTools(resolved, tools) {
|
|
236
|
+
if (resolved.model instanceof HarnessModel) {
|
|
237
|
+
resolved.model.attach(tools);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export async function imageModel(meta = {}) {
|
|
241
|
+
const remote = await viaGateway("image", meta, (client, id) => client.imageModel(id));
|
|
242
|
+
if (remote) {
|
|
243
|
+
return remote;
|
|
244
|
+
}
|
|
245
|
+
const ref = localRef("image");
|
|
246
|
+
const { vendor, model } = split(ref);
|
|
247
|
+
const client = harness(vendor);
|
|
248
|
+
if (client?.image) {
|
|
249
|
+
if (!(await detectHarness(client.id))?.version) {
|
|
250
|
+
throw new ModelUnavailableError(notInstalled(client));
|
|
251
|
+
}
|
|
252
|
+
return { model: client.image.model(model), ref, vendor, gateway: false, metered: false };
|
|
253
|
+
}
|
|
254
|
+
const gateway = ownGateway();
|
|
255
|
+
if (gateway) {
|
|
256
|
+
return {
|
|
257
|
+
model: gateway.imageModel(`${vendor}/${model}`),
|
|
258
|
+
ref,
|
|
259
|
+
vendor,
|
|
260
|
+
gateway: true,
|
|
261
|
+
metered: false,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (local.keys.openai && vendor === "openai") {
|
|
265
|
+
return {
|
|
266
|
+
model: createOpenAI({ apiKey: local.keys.openai }).image(model),
|
|
267
|
+
ref,
|
|
268
|
+
vendor,
|
|
269
|
+
gateway: false,
|
|
270
|
+
metered: false,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
throw new ModelUnavailableError("Für Bilder ist kein Modell eingerichtet.");
|
|
274
|
+
}
|
|
275
|
+
export async function videoModel(meta = {}) {
|
|
276
|
+
const remote = await viaGateway("video", meta, (client, id) => client.videoModel(id));
|
|
277
|
+
if (remote) {
|
|
278
|
+
return remote;
|
|
279
|
+
}
|
|
280
|
+
const ref = localRef("video");
|
|
281
|
+
const { vendor, model } = split(ref);
|
|
282
|
+
const gateway = ownGateway();
|
|
283
|
+
if (gateway) {
|
|
284
|
+
return {
|
|
285
|
+
model: gateway.videoModel(`${vendor}/${model}`),
|
|
286
|
+
ref,
|
|
287
|
+
vendor,
|
|
288
|
+
gateway: true,
|
|
289
|
+
metered: false,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
throw new ModelUnavailableError("Für Videos ist kein Modell eingerichtet.");
|
|
293
|
+
}
|
|
294
|
+
/** The model that reads a text aloud. */
|
|
295
|
+
export async function speechModel(meta = {}) {
|
|
296
|
+
const access = await gatewayAccess();
|
|
297
|
+
if (access) {
|
|
298
|
+
const bound = (await classCatalog())?.classes.speech?.model;
|
|
299
|
+
if (!bound) {
|
|
300
|
+
throw new ModelUnavailableError("Für Sprachausgabe ist kein Modell eingerichtet.");
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
model: gatewayClient(access, meta).speechModel("wizards/speech"),
|
|
304
|
+
ref: bound,
|
|
305
|
+
vendor: vendorOf(bound.replace(/^[a-z]+:/, "")),
|
|
306
|
+
gateway: true,
|
|
307
|
+
metered: true,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const ref = localRef("speech");
|
|
311
|
+
const { vendor, model } = split(ref);
|
|
312
|
+
const gateway = ownGateway();
|
|
313
|
+
if (gateway) {
|
|
314
|
+
return {
|
|
315
|
+
model: gateway.speechModel(`${vendor}/${model}`),
|
|
316
|
+
ref,
|
|
317
|
+
vendor,
|
|
318
|
+
gateway: true,
|
|
319
|
+
metered: false,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (local.keys.openai && vendor === "openai") {
|
|
323
|
+
return {
|
|
324
|
+
model: createOpenAI({ apiKey: local.keys.openai }).speech(model),
|
|
325
|
+
ref,
|
|
326
|
+
vendor,
|
|
327
|
+
gateway: false,
|
|
328
|
+
metered: false,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
throw new ModelUnavailableError("Für Sprachausgabe ist kein Modell eingerichtet.");
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The model that turns passages and questions into vectors for a project's document index.
|
|
335
|
+
* Null where none is set up — an installed AI client has no such model — and the index then
|
|
336
|
+
* works on keywords alone.
|
|
337
|
+
*/
|
|
338
|
+
export async function embeddingModel() {
|
|
339
|
+
const access = await gatewayAccess().catch(() => null);
|
|
340
|
+
if (access) {
|
|
341
|
+
const bound = (await classCatalog())?.classes.embedding;
|
|
342
|
+
return bound
|
|
343
|
+
? { model: gatewayClient(access, {}).embeddingModel("wizards/embedding"), ref: bound.model }
|
|
344
|
+
: null;
|
|
345
|
+
}
|
|
346
|
+
const ref = env.models.embedding;
|
|
347
|
+
const { provider, vendor, model } = split(ref);
|
|
348
|
+
if (provider === "ollama") {
|
|
349
|
+
const ollama = createOpenAI({
|
|
350
|
+
baseURL: local.settings.ollamaUrl ?? env.ollamaUrl,
|
|
351
|
+
apiKey: "ollama",
|
|
352
|
+
});
|
|
353
|
+
return { model: ollama.embedding(model ? `${vendor}/${model}` : vendor), ref };
|
|
354
|
+
}
|
|
355
|
+
const gateway = ownGateway();
|
|
356
|
+
if ((provider === null || provider === "gateway") && gateway) {
|
|
357
|
+
return { model: gateway.embeddingModel(`${vendor}/${model}`), ref };
|
|
358
|
+
}
|
|
359
|
+
if ((provider === "openai" || vendor === "openai") && local.keys.openai) {
|
|
360
|
+
return { model: createOpenAI({ apiKey: local.keys.openai }).embedding(model || vendor), ref };
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
/** Gemini image models are chat models answering with image files. */
|
|
365
|
+
export function isChatImageModel(ref) {
|
|
366
|
+
return ref.includes("gemini") && ref.includes("image");
|
|
367
|
+
}
|
|
368
|
+
/** Search tools an AI Gateway runs itself; they travel in the request, so any gateway client names them. */
|
|
369
|
+
export const gatewayTools = createGateway({ apiKey: "unused" }).tools;
|
|
370
|
+
/** Whether any text model can answer at all: the studio says so before the first chat turn. */
|
|
371
|
+
/**
|
|
372
|
+
* Whether a class can run here, found out without calling it: null, or what the person reads.
|
|
373
|
+
* A class the gateway serves counts as there unless its catalog leaves it unbound.
|
|
374
|
+
*/
|
|
375
|
+
export async function classProblem(cls) {
|
|
376
|
+
try {
|
|
377
|
+
const resolved = cls === "image"
|
|
378
|
+
? await imageModel()
|
|
379
|
+
: cls === "video"
|
|
380
|
+
? await videoModel()
|
|
381
|
+
: cls === "speech"
|
|
382
|
+
? await speechModel()
|
|
383
|
+
: await textModel(cls);
|
|
384
|
+
if (resolved.metered) {
|
|
385
|
+
const bound = (await classCatalog())?.classes;
|
|
386
|
+
return bound && !bound[cls] ? "Dafür ist beim Konto kein Modell eingerichtet." : null;
|
|
387
|
+
}
|
|
388
|
+
const client = harness(resolved.vendor);
|
|
389
|
+
if (client && (await detectHarness(client.id))?.auth === "none") {
|
|
390
|
+
return signedOut(client);
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
if (err instanceof ModelUnavailableError) {
|
|
396
|
+
return err.message;
|
|
397
|
+
}
|
|
398
|
+
throw err;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
export async function hasTextModel() {
|
|
402
|
+
try {
|
|
403
|
+
await textModel("standard");
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
let catalog = new Map();
|
|
411
|
+
let catalogLoadedAt = 0;
|
|
412
|
+
/** Public prices of the AI Gateway: what a runtime that runs alone shows as the cost of a run. */
|
|
413
|
+
export async function loadCatalog(force = false) {
|
|
414
|
+
if (!force && Date.now() - catalogLoadedAt < 6 * 3600_000 && catalog.size) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const res = await fetch("https://ai-gateway.vercel.sh/v1/models", {
|
|
419
|
+
signal: AbortSignal.timeout(10_000),
|
|
420
|
+
});
|
|
421
|
+
const body = (await res.json());
|
|
422
|
+
catalog = new Map(body.data.map((m) => [m.id, m]));
|
|
423
|
+
catalogLoadedAt = Date.now();
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Prices fall back to conservative defaults below.
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function priceOf(ref) {
|
|
430
|
+
const { vendor, model } = split(ref);
|
|
431
|
+
return catalog.get(`${vendor}/${model}`)?.pricing ?? {};
|
|
432
|
+
}
|
|
433
|
+
const n = (v, fallback) => {
|
|
434
|
+
const x = Number(v);
|
|
435
|
+
return Number.isFinite(x) ? x : fallback;
|
|
436
|
+
};
|
|
437
|
+
export function usageOf(u) {
|
|
438
|
+
return {
|
|
439
|
+
inputTokens: u?.inputTokens ?? 0,
|
|
440
|
+
outputTokens: u?.outputTokens ?? 0,
|
|
441
|
+
cachedInputTokens: u?.cachedInputTokens ?? u?.inputTokenDetails?.cacheReadTokens ?? 0,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
/** Provider cost of a text call a runtime that runs alone made; the gateway books the others itself. */
|
|
445
|
+
export function costOf(resolved, usage) {
|
|
446
|
+
// An installed client answers on its subscription: nothing to book.
|
|
447
|
+
return resolved.metered || isHarnessVendor(resolved.vendor)
|
|
448
|
+
? 0
|
|
449
|
+
: tokenCostUsd(resolved.ref, usageOf(usage));
|
|
450
|
+
}
|
|
451
|
+
export function tokenCostUsd(ref, usage) {
|
|
452
|
+
if (!usage) {
|
|
453
|
+
return 0;
|
|
454
|
+
}
|
|
455
|
+
const p = priceOf(ref);
|
|
456
|
+
const input = n(p.input, 0.000003);
|
|
457
|
+
const output = n(p.output, 0.000015);
|
|
458
|
+
const cached = n(p.input_cache_read, input / 10);
|
|
459
|
+
const cachedTokens = usage.cachedInputTokens ?? 0;
|
|
460
|
+
const freshInput = Math.max(0, (usage.inputTokens ?? 0) - cachedTokens);
|
|
461
|
+
return freshInput * input + cachedTokens * cached + (usage.outputTokens ?? 0) * output;
|
|
462
|
+
}
|
|
463
|
+
export function imageCostUsd(ref) {
|
|
464
|
+
const p = priceOf(ref);
|
|
465
|
+
const tiers = p.image_dimension_quality_pricing;
|
|
466
|
+
const tier = tiers?.find((t) => t.size === "default") ?? tiers?.[0];
|
|
467
|
+
return n(tier?.cost ?? p.image, 0.08);
|
|
468
|
+
}
|
|
469
|
+
export function videoCostUsd(ref, seconds, resolution = "720p") {
|
|
470
|
+
const p = priceOf(ref);
|
|
471
|
+
const tiers = p.video_duration_pricing;
|
|
472
|
+
// We render at 720p or 480p with sound: price that tier where the catalog has one; else the
|
|
473
|
+
// dearest below 4K, so the estimate is never short.
|
|
474
|
+
const exact = tiers?.filter((t) => t.resolution === resolution) ?? [];
|
|
475
|
+
const usable = exact.length ? exact : (tiers?.filter((t) => t.resolution !== "4k") ?? []);
|
|
476
|
+
const withAudio = usable.filter((t) => t.audio !== false);
|
|
477
|
+
const pick = withAudio.length ? withAudio : usable;
|
|
478
|
+
const perSecond = pick.length ? Math.max(...pick.map((t) => n(t.cost_per_second, 0))) : 0.4;
|
|
479
|
+
return perSecond * seconds;
|
|
480
|
+
}
|
|
481
|
+
/** Speech is priced per character read, or per audio token (about 25 a second, 15 characters). */
|
|
482
|
+
export function speechCostUsd(ref, characters) {
|
|
483
|
+
const p = priceOf(ref);
|
|
484
|
+
if (p.speech_input_character_cost !== undefined) {
|
|
485
|
+
return n(p.speech_input_character_cost, 0.00003) * characters;
|
|
486
|
+
}
|
|
487
|
+
return n(p.audio_output_token_cost, 0.00001) * Math.ceil((characters / 15) * 25);
|
|
488
|
+
}
|
|
489
|
+
export const WEB_SEARCH_COST_USD = 0.01;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { zipSync } from "fflate";
|
|
7
|
+
import { env } from "./env.js";
|
|
8
|
+
/**
|
|
9
|
+
* The Claude Code plugin for this deployment: the `plugin/` folder with this server's URL filled
|
|
10
|
+
* in, served as a marketplace (`url` source) whose one plugin is a zip (`archive` source). So
|
|
11
|
+
* every deployment hands out its own plugin and no domain is baked into the repo.
|
|
12
|
+
*/
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
/** The folder that holds `plugin/` and the root package.json: three levels above this file. */
|
|
15
|
+
const base = resolve(here, "../../..");
|
|
16
|
+
const root = existsSync(join(base, "plugin")) ? base : undefined;
|
|
17
|
+
export const PLUGIN_NAME = "engenty-wizards";
|
|
18
|
+
export const MARKETPLACE_NAME = "engenty";
|
|
19
|
+
export const pluginBase = `${env.appUrl}/api/claude-plugin`;
|
|
20
|
+
async function filesUnder(dir) {
|
|
21
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
22
|
+
const nested = await Promise.all(entries.map((e) => (e.isDirectory() ? filesUnder(join(dir, e.name)) : [join(dir, e.name)])));
|
|
23
|
+
return nested.flat();
|
|
24
|
+
}
|
|
25
|
+
let built = null;
|
|
26
|
+
export function pluginArchive() {
|
|
27
|
+
built ??= (async () => {
|
|
28
|
+
if (!root) {
|
|
29
|
+
throw new Error("plugin/ folder missing");
|
|
30
|
+
}
|
|
31
|
+
const { version } = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
32
|
+
const dir = join(root, "plugin");
|
|
33
|
+
const files = {};
|
|
34
|
+
for (const path of (await filesUnder(dir)).sort()) {
|
|
35
|
+
const text = (await readFile(path, "utf8"))
|
|
36
|
+
.replaceAll("{{APP_URL}}", env.appUrl)
|
|
37
|
+
.replaceAll("{{VERSION}}", version);
|
|
38
|
+
// Fixed timestamps keep the archive, and so its sha256, stable across restarts.
|
|
39
|
+
files[relative(dir, path)] = new TextEncoder().encode(text);
|
|
40
|
+
}
|
|
41
|
+
const zip = zipSync(files, { level: 9, mtime: new Date("2026-01-01T00:00:00Z") });
|
|
42
|
+
return { zip, sha256: createHash("sha256").update(zip).digest("hex"), version };
|
|
43
|
+
})();
|
|
44
|
+
return built;
|
|
45
|
+
}
|
|
46
|
+
export async function pluginMarketplace() {
|
|
47
|
+
const { sha256, version } = await pluginArchive();
|
|
48
|
+
return {
|
|
49
|
+
name: MARKETPLACE_NAME,
|
|
50
|
+
owner: { name: "engenty" },
|
|
51
|
+
description: "engenty wizards for Claude Code",
|
|
52
|
+
plugins: [
|
|
53
|
+
{
|
|
54
|
+
name: PLUGIN_NAME,
|
|
55
|
+
description: "Build page-by-page AI wizards that people open on a shared link — designed, tested and published from Claude Code.",
|
|
56
|
+
version,
|
|
57
|
+
source: { source: "archive", url: `${pluginBase}/${PLUGIN_NAME}.zip`, sha256 },
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { chromium } from "playwright-core";
|
|
2
|
+
import { env } from "../env.js";
|
|
3
|
+
import { assertPublicUrl } from "../tools/net-guard.js";
|
|
4
|
+
/**
|
|
5
|
+
* One Chromium for the whole server: the engenty-browser container over CDP when
|
|
6
|
+
* `BROWSER_CDP_URL` is set, else a local headless Chrome. Every user of it opens
|
|
7
|
+
* its own context, so runs never share cookies or storage.
|
|
8
|
+
*/
|
|
9
|
+
let browserPromise = null;
|
|
10
|
+
async function connect() {
|
|
11
|
+
const browser = env.browserCdpUrl
|
|
12
|
+
? await chromium.connectOverCDP(env.browserCdpUrl)
|
|
13
|
+
: await chromium.launch({
|
|
14
|
+
executablePath: env.chromePath,
|
|
15
|
+
headless: true,
|
|
16
|
+
args: ["--no-sandbox", "--disable-dev-shm-usage", "--font-render-hinting=none"],
|
|
17
|
+
});
|
|
18
|
+
browser.on("disconnected", () => {
|
|
19
|
+
browserPromise = null;
|
|
20
|
+
});
|
|
21
|
+
return browser;
|
|
22
|
+
}
|
|
23
|
+
export function getBrowser() {
|
|
24
|
+
if (!browserPromise) {
|
|
25
|
+
browserPromise = connect().catch((err) => {
|
|
26
|
+
browserPromise = null;
|
|
27
|
+
throw err;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return browserPromise;
|
|
31
|
+
}
|
|
32
|
+
export async function newContext(options = {}) {
|
|
33
|
+
const browser = await getBrowser();
|
|
34
|
+
return browser.newContext({ viewport: { width: 1280, height: 900 }, ...options });
|
|
35
|
+
}
|
|
36
|
+
const hostChecks = new Map();
|
|
37
|
+
function publicHost(url) {
|
|
38
|
+
let host;
|
|
39
|
+
try {
|
|
40
|
+
host = new URL(url).host;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return Promise.resolve(false);
|
|
44
|
+
}
|
|
45
|
+
let check = hostChecks.get(host);
|
|
46
|
+
if (!check) {
|
|
47
|
+
check = assertPublicUrl(url).then(() => true, () => false);
|
|
48
|
+
hostChecks.set(host, check);
|
|
49
|
+
if (hostChecks.size > 2000) {
|
|
50
|
+
hostChecks.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return check;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A context for browsing the web on a person's behalf: every request, whoever started it (the
|
|
57
|
+
* agent, a redirect, the person clicking), may only reach public hosts.
|
|
58
|
+
*/
|
|
59
|
+
export async function webContext(options = {}) {
|
|
60
|
+
const context = await newContext({ acceptDownloads: true, ...options });
|
|
61
|
+
await context.route("**/*", async (route) => {
|
|
62
|
+
const url = route.request().url();
|
|
63
|
+
if (!/^https?:/i.test(url) || (await publicHost(url))) {
|
|
64
|
+
await route.continue();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
await route.abort("blockedbyclient");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return context;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A context for rendering generated HTML: every network request is refused. The HTML is
|
|
74
|
+
* self-contained by design, and nothing it runs may reach the server's network.
|
|
75
|
+
*/
|
|
76
|
+
export async function offlineContext(options = {}) {
|
|
77
|
+
const context = await newContext({ javaScriptEnabled: true, ...options });
|
|
78
|
+
await context.route("**/*", (route) => route.abort("blockedbyclient"));
|
|
79
|
+
return context;
|
|
80
|
+
}
|
|
81
|
+
export async function closeBrowser() {
|
|
82
|
+
const b = await browserPromise?.catch(() => null);
|
|
83
|
+
browserPromise = null;
|
|
84
|
+
await b?.close().catch(() => undefined);
|
|
85
|
+
}
|