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,126 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { resolveEnv, run } from "./env.js";
|
|
5
|
+
import { failureOf, HarnessModel, runClient, } from "./model.js";
|
|
6
|
+
/**
|
|
7
|
+
* Gemini CLI: headless it answers on the Google sign-in it cached, or on a key from the shell.
|
|
8
|
+
* The system prompt replaces its own (GEMINI_SYSTEM_MD); tools go over MCP through a project
|
|
9
|
+
* settings file in the call's folder, with its built-in tools switched off; the answer is asked
|
|
10
|
+
* for as JSON where a schema is wanted.
|
|
11
|
+
*/
|
|
12
|
+
export const GEMINI_BIN = process.env.GEMINI_BIN?.trim() || "gemini";
|
|
13
|
+
const CLASSES = {
|
|
14
|
+
classifier: "gemini-2.5-flash-lite",
|
|
15
|
+
standard: "gemini-2.5-flash",
|
|
16
|
+
high: "gemini-2.5-pro",
|
|
17
|
+
highest: "gemini-2.5-pro",
|
|
18
|
+
};
|
|
19
|
+
/** Where the client keeps its state: its own home, else the person's. */
|
|
20
|
+
const geminiHome = (env) => env.GEMINI_CLI_HOME?.trim() || join(env.HOME || homedir(), ".gemini");
|
|
21
|
+
const spec = {
|
|
22
|
+
id: "gemini",
|
|
23
|
+
passThrough: /^(GEMINI_API_KEY|GOOGLE_API_KEY|GOOGLE_CLOUD_PROJECT|GOOGLE_CLOUD_LOCATION|GOOGLE_APPLICATION_CREDENTIALS|GOOGLE_GENAI_USE_VERTEXAI|GOOGLE_GENAI_USE_GCA|GEMINI_CLI_HOME)$/,
|
|
24
|
+
keyVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY", "GOOGLE_GENAI_USE_VERTEXAI"],
|
|
25
|
+
// The client has no status command: a key in the environment, else the sign-in it cached.
|
|
26
|
+
async auth(env) {
|
|
27
|
+
if (env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_USE_VERTEXAI) {
|
|
28
|
+
return "api_key";
|
|
29
|
+
}
|
|
30
|
+
return existsSync(join(geminiHome(env), "oauth_creds.json")) ? "subscription" : "none";
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
/** The client's output as the model call wants it; the lines it logs before the JSON are skipped. */
|
|
34
|
+
export function geminiAnswer(stdout) {
|
|
35
|
+
const at = stdout.search(/^\{/m);
|
|
36
|
+
if (at < 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(stdout.slice(at));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
class GeminiModel extends HarnessModel {
|
|
47
|
+
provider = "gemini-cli";
|
|
48
|
+
async invoke(call) {
|
|
49
|
+
const { env, auth } = await resolveEnv(spec);
|
|
50
|
+
const systemFile = join(call.dir, "system.md");
|
|
51
|
+
writeFileSync(systemFile, call.system || "You are a helpful assistant.");
|
|
52
|
+
const core = [];
|
|
53
|
+
if (call.webSearch) {
|
|
54
|
+
core.push("google_web_search");
|
|
55
|
+
}
|
|
56
|
+
if (call.files.length) {
|
|
57
|
+
core.push("read_file");
|
|
58
|
+
}
|
|
59
|
+
mkdirSync(join(call.dir, ".gemini"), { recursive: true });
|
|
60
|
+
writeFileSync(join(call.dir, ".gemini", "settings.json"), JSON.stringify({
|
|
61
|
+
mcpServers: call.bridgeUrl
|
|
62
|
+
? { step: { httpUrl: call.bridgeUrl, trust: true, timeout: 600_000 } }
|
|
63
|
+
: {},
|
|
64
|
+
tools: { core },
|
|
65
|
+
}));
|
|
66
|
+
const args = [
|
|
67
|
+
"-o",
|
|
68
|
+
"json",
|
|
69
|
+
"-m",
|
|
70
|
+
this.modelId,
|
|
71
|
+
"--approval-mode",
|
|
72
|
+
"yolo",
|
|
73
|
+
"--allowed-mcp-server-names",
|
|
74
|
+
call.bridgeUrl ? "step" : "none",
|
|
75
|
+
];
|
|
76
|
+
const { stdout, stderr, code } = await runClient(GEMINI_BIN, args, {
|
|
77
|
+
cwd: call.dir,
|
|
78
|
+
env: {
|
|
79
|
+
...env,
|
|
80
|
+
GEMINI_SYSTEM_MD: systemFile,
|
|
81
|
+
NO_COLOR: "1",
|
|
82
|
+
...(auth === "subscription" ? { GOOGLE_GENAI_USE_GCA: "true" } : {}),
|
|
83
|
+
},
|
|
84
|
+
signal: call.signal,
|
|
85
|
+
stdin: call.prompt,
|
|
86
|
+
});
|
|
87
|
+
const result = geminiAnswer(stdout);
|
|
88
|
+
if (!result) {
|
|
89
|
+
throw failureOf(this.harness, stderr.trim() || `exit ${code}`);
|
|
90
|
+
}
|
|
91
|
+
if (result.error) {
|
|
92
|
+
throw failureOf(this.harness, result.error.code === 41 ? "not logged in" : (result.error.message ?? "error"));
|
|
93
|
+
}
|
|
94
|
+
let input = 0;
|
|
95
|
+
let cacheRead = 0;
|
|
96
|
+
let output = 0;
|
|
97
|
+
for (const model of Object.values(result.stats?.models ?? {})) {
|
|
98
|
+
input += model.tokens?.prompt ?? 0;
|
|
99
|
+
cacheRead += model.tokens?.cached ?? 0;
|
|
100
|
+
output += model.tokens?.candidates ?? 0;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
text: result.response ?? "",
|
|
104
|
+
usage: { input, cacheRead, output },
|
|
105
|
+
sessionId: result.session_id,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export const gemini = {
|
|
110
|
+
...spec,
|
|
111
|
+
name: "Gemini CLI",
|
|
112
|
+
bin: GEMINI_BIN,
|
|
113
|
+
classes: CLASSES,
|
|
114
|
+
async version(env) {
|
|
115
|
+
try {
|
|
116
|
+
const { stdout } = await run(GEMINI_BIN, ["--version"], { timeout: 10_000, env });
|
|
117
|
+
return stdout.trim().split("\n").pop()?.trim() || null;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
// No login command: the client's own app asks for the sign-in when it has none.
|
|
124
|
+
login: { args: [], interactive: true },
|
|
125
|
+
model: (alias, effort) => new GeminiModel(gemini, alias || CLASSES.standard, effort),
|
|
126
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { installCommand } from "../cli/clients.js";
|
|
3
|
+
import { inherited, runtimePath } from "../cli/environment.js";
|
|
4
|
+
import { layout } from "../cli/home.js";
|
|
5
|
+
import { CLIENTS, vendorApp } from "../cli/machine.js";
|
|
6
|
+
import { claude } from "./claude.js";
|
|
7
|
+
import { codex } from "./codex.js";
|
|
8
|
+
import { cursor } from "./cursor.js";
|
|
9
|
+
import { forgetEnv, loginShellEnv, resolveEnv } from "./env.js";
|
|
10
|
+
import { gemini } from "./gemini.js";
|
|
11
|
+
export { HarnessModel, notInstalled, signedOut } from "./model.js";
|
|
12
|
+
export { HARNESS_IDS } from "./types.js";
|
|
13
|
+
/**
|
|
14
|
+
* The AI clients this runtime can run as a model, in the order the setup offers them. The first
|
|
15
|
+
* one installed is the one the setup recommends.
|
|
16
|
+
*/
|
|
17
|
+
export const HARNESSES = [codex, claude, gemini, cursor];
|
|
18
|
+
export function harness(id) {
|
|
19
|
+
return HARNESSES.find((h) => h.id === id) ?? null;
|
|
20
|
+
}
|
|
21
|
+
/** Whether a binding's vendor names a client: `claude/sonnet`, `codex/default`. */
|
|
22
|
+
export function isHarnessVendor(vendor) {
|
|
23
|
+
return HARNESSES.some((h) => h.id === vendor);
|
|
24
|
+
}
|
|
25
|
+
const detected = new Map();
|
|
26
|
+
/** One client, as it is now; asked again after a minute — it may get installed or signed in meanwhile. */
|
|
27
|
+
export async function detectHarness(id, force = false) {
|
|
28
|
+
const h = harness(id);
|
|
29
|
+
if (!h) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const hit = detected.get(id);
|
|
33
|
+
if (!force && hit && Date.now() - hit.at < 60_000) {
|
|
34
|
+
return hit.value;
|
|
35
|
+
}
|
|
36
|
+
if (force) {
|
|
37
|
+
// The person may just have signed in or set a key in their shell.
|
|
38
|
+
forgetEnv(id);
|
|
39
|
+
}
|
|
40
|
+
const client = CLIENTS.find((c) => c.id === id);
|
|
41
|
+
let value = {
|
|
42
|
+
id,
|
|
43
|
+
name: h.name,
|
|
44
|
+
install: client ? installCommand(client, layout()) : "",
|
|
45
|
+
app: client ? vendorApp(client) : null,
|
|
46
|
+
version: null,
|
|
47
|
+
auth: "none",
|
|
48
|
+
interactiveLogin: h.login.interactive,
|
|
49
|
+
images: Boolean(h.image),
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
const { env, auth } = await resolveEnv(h);
|
|
53
|
+
const version = await h.version(env);
|
|
54
|
+
if (version) {
|
|
55
|
+
value = { ...value, version, auth };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// not installed
|
|
60
|
+
}
|
|
61
|
+
detected.set(id, { at: Date.now(), value });
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
/** Every client this runtime knows, installed or not. */
|
|
65
|
+
export function detectHarnesses(force = false) {
|
|
66
|
+
return Promise.all(HARNESSES.map((h) => detectHarness(h.id, force)));
|
|
67
|
+
}
|
|
68
|
+
/** The clients installed on this machine. */
|
|
69
|
+
export async function installedHarnesses() {
|
|
70
|
+
return (await detectHarnesses()).filter((h) => h.version !== null);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What the inline terminal runs to install a client: the command the terminal setup runs, shown
|
|
74
|
+
* first. A client from npm goes into the install's own prefix with the npm of its own Node.
|
|
75
|
+
*/
|
|
76
|
+
export async function installSpec(id) {
|
|
77
|
+
const client = CLIENTS.find((c) => c.id === id);
|
|
78
|
+
if (!client) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const paths = layout();
|
|
82
|
+
mkdirSync(paths.clients, { recursive: true });
|
|
83
|
+
const shell = await loginShellEnv();
|
|
84
|
+
return {
|
|
85
|
+
bin: "bash",
|
|
86
|
+
args: [
|
|
87
|
+
"-c",
|
|
88
|
+
'printf "\\033[2m$ %s\\033[0m\\n\\n" "$1"; set -o pipefail; eval "$1"',
|
|
89
|
+
"install",
|
|
90
|
+
installCommand(client, paths),
|
|
91
|
+
],
|
|
92
|
+
env: {
|
|
93
|
+
...inherited(process.env),
|
|
94
|
+
PATH: runtimePath(paths, { ...process.env, PATH: shell.PATH || process.env.PATH }),
|
|
95
|
+
TERM: "xterm-256color",
|
|
96
|
+
COLORTERM: "truecolor",
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { nanoid } from "nanoid";
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { openBridge } from "../mcp/bridge.js";
|
|
7
|
+
import { ModelUnavailableError } from "../model-errors.js";
|
|
8
|
+
import { extensionOf, jsonInstruction, parseJsonAnswer, renderPrompt } from "./prompt.js";
|
|
9
|
+
const workDir = join(env.dataDir, "harness");
|
|
10
|
+
export const signedOut = (h) => `${h.name} ist auf diesem Gerät nicht angemeldet. Melde dich in den Einstellungen unter „Modelle & Konto“ an.`;
|
|
11
|
+
export const notInstalled = (h) => `${h.name} ist auf diesem Gerät nicht installiert. Installiere es in den Einstellungen unter „Modelle & Konto“.`;
|
|
12
|
+
export const exhausted = (h) => h.exhausted ??
|
|
13
|
+
`Das Konto, mit dem ${h.name} angemeldet ist, hat kein Guthaben oder Kontingent mehr.`;
|
|
14
|
+
/** The failure a client reports, as the person should read it. */
|
|
15
|
+
export function failureOf(h, detail) {
|
|
16
|
+
if (/credit balance|billing|too low|usage limit|quota|insufficient|rate.?limit|\b429\b/i.test(detail)) {
|
|
17
|
+
return new ModelUnavailableError(exhausted(h));
|
|
18
|
+
}
|
|
19
|
+
if (/authenticat|log ?in|logged in|oauth|credential|unauthori[sz]ed|\b401\b|api key/i.test(detail)) {
|
|
20
|
+
return new ModelUnavailableError(signedOut(h));
|
|
21
|
+
}
|
|
22
|
+
return new Error(`${h.name}: ${detail}`.slice(0, 500));
|
|
23
|
+
}
|
|
24
|
+
/** Runs a client once with the prompt on stdin; a client that is not there raises ENOENT. */
|
|
25
|
+
export function runClient(bin, args, opts) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const child = spawn(bin, args, {
|
|
28
|
+
cwd: opts.cwd,
|
|
29
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
30
|
+
signal: opts.signal,
|
|
31
|
+
env: opts.env,
|
|
32
|
+
});
|
|
33
|
+
let stdout = "";
|
|
34
|
+
let stderr = "";
|
|
35
|
+
child.stdout.on("data", (chunk) => {
|
|
36
|
+
stdout += String(chunk);
|
|
37
|
+
});
|
|
38
|
+
child.stderr.on("data", (chunk) => {
|
|
39
|
+
stderr = (stderr + String(chunk)).slice(-4000);
|
|
40
|
+
});
|
|
41
|
+
child.on("error", reject);
|
|
42
|
+
child.on("close", (code) => resolve({ stdout, stderr, code }));
|
|
43
|
+
child.stdin.on("error", () => {
|
|
44
|
+
// The client went away before reading the prompt; `close` reports it.
|
|
45
|
+
});
|
|
46
|
+
child.stdin.end(opts.stdin ?? "");
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** The last line of an output that is a JSON object. */
|
|
50
|
+
export function lastJson(stdout) {
|
|
51
|
+
for (const line of stdout.trim().split("\n").reverse()) {
|
|
52
|
+
try {
|
|
53
|
+
const value = JSON.parse(line);
|
|
54
|
+
if (value && typeof value === "object") {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// not it
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
export class HarnessModel {
|
|
65
|
+
specificationVersion = "v3";
|
|
66
|
+
modelId;
|
|
67
|
+
supportedUrls = {};
|
|
68
|
+
/** Whether the client validates structured output against the schema itself. */
|
|
69
|
+
nativeSchema = false;
|
|
70
|
+
effort;
|
|
71
|
+
harness;
|
|
72
|
+
executors = new Map();
|
|
73
|
+
constructor(harness, alias, effort) {
|
|
74
|
+
this.harness = harness;
|
|
75
|
+
this.modelId = alias;
|
|
76
|
+
this.effort = effort;
|
|
77
|
+
}
|
|
78
|
+
/** The tools a call may name, by the names the agent gives them: the client runs them over the bridge. */
|
|
79
|
+
attach(tools) {
|
|
80
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
81
|
+
const t = tool;
|
|
82
|
+
if (typeof t?.execute === "function") {
|
|
83
|
+
this.executors.set(name, t);
|
|
84
|
+
if (t.id) {
|
|
85
|
+
this.executors.set(t.id, t);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async doGenerate(options) {
|
|
91
|
+
const dir = join(workDir, nanoid(10));
|
|
92
|
+
mkdirSync(dir, { recursive: true });
|
|
93
|
+
const { system, prompt, files, warnings } = renderPrompt(options.prompt, dir);
|
|
94
|
+
const bridged = [];
|
|
95
|
+
let webSearch = false;
|
|
96
|
+
for (const tool of options.tools ?? []) {
|
|
97
|
+
if (tool.type !== "function") {
|
|
98
|
+
warnings.push({ type: "unsupported", feature: `tool ${tool.name}` });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// The client searches with its own tool; naming `web_search` is what switches it on.
|
|
102
|
+
if (tool.name === "web_search") {
|
|
103
|
+
webSearch = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const executor = this.executors.get(tool.name);
|
|
107
|
+
if (!executor?.execute) {
|
|
108
|
+
warnings.push({
|
|
109
|
+
type: "unsupported",
|
|
110
|
+
feature: `tool ${tool.name}`,
|
|
111
|
+
details: "no executor",
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
bridged.push({
|
|
116
|
+
name: tool.name,
|
|
117
|
+
description: tool.description,
|
|
118
|
+
inputSchema: tool.inputSchema,
|
|
119
|
+
execute: (input) => executor.execute(input),
|
|
120
|
+
toModelOutput: executor.toModelOutput,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const key of ["temperature", "topP", "topK", "seed", "stopSequences"]) {
|
|
124
|
+
if (options[key] !== undefined) {
|
|
125
|
+
warnings.push({ type: "unsupported", feature: key });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const structured = options.responseFormat?.type === "json";
|
|
129
|
+
const schema = structured && options.responseFormat?.type === "json"
|
|
130
|
+
? (options.responseFormat.schema ?? null)
|
|
131
|
+
: null;
|
|
132
|
+
const bridge = bridged.length ? openBridge(bridged) : null;
|
|
133
|
+
try {
|
|
134
|
+
const answer = await this.invoke({
|
|
135
|
+
dir,
|
|
136
|
+
system: structured && !this.nativeSchema
|
|
137
|
+
? [system, jsonInstruction(schema)].filter(Boolean).join("\n\n")
|
|
138
|
+
: system,
|
|
139
|
+
prompt,
|
|
140
|
+
files,
|
|
141
|
+
bridgeUrl: bridge?.url ?? null,
|
|
142
|
+
schema: this.nativeSchema ? schema : null,
|
|
143
|
+
webSearch,
|
|
144
|
+
effort: this.effort,
|
|
145
|
+
signal: options.abortSignal,
|
|
146
|
+
});
|
|
147
|
+
const u = answer.usage ?? {};
|
|
148
|
+
const cacheRead = u.cacheRead ?? 0;
|
|
149
|
+
const cacheWrite = u.cacheWrite ?? 0;
|
|
150
|
+
const input = u.input ?? 0;
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: structured ? JSON.stringify(parseJsonAnswer(answer.text)) : answer.text,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
finishReason: { unified: "stop", raw: answer.stopReason },
|
|
159
|
+
usage: {
|
|
160
|
+
inputTokens: {
|
|
161
|
+
total: input,
|
|
162
|
+
noCache: Math.max(0, input - cacheRead - cacheWrite),
|
|
163
|
+
cacheRead,
|
|
164
|
+
cacheWrite,
|
|
165
|
+
},
|
|
166
|
+
outputTokens: { total: u.output ?? 0, text: u.output ?? 0, reasoning: undefined },
|
|
167
|
+
raw: u,
|
|
168
|
+
},
|
|
169
|
+
providerMetadata: { [this.provider]: answer.meta ?? {} },
|
|
170
|
+
response: { id: answer.sessionId, modelId: this.modelId },
|
|
171
|
+
warnings,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (err.code === "ENOENT") {
|
|
176
|
+
throw new ModelUnavailableError(notInstalled(this.harness));
|
|
177
|
+
}
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
bridge?.close();
|
|
182
|
+
rmSync(dir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** The answer as one piece: the client prints it when it is done. */
|
|
186
|
+
async doStream(options) {
|
|
187
|
+
const result = await this.doGenerate(options);
|
|
188
|
+
const text = result.content.find((c) => c.type === "text");
|
|
189
|
+
const parts = [
|
|
190
|
+
{ type: "stream-start", warnings: result.warnings },
|
|
191
|
+
{ type: "response-metadata", ...result.response },
|
|
192
|
+
{ type: "text-start", id: "0" },
|
|
193
|
+
{ type: "text-delta", id: "0", delta: text?.type === "text" ? text.text : "" },
|
|
194
|
+
{ type: "text-end", id: "0" },
|
|
195
|
+
{
|
|
196
|
+
type: "finish",
|
|
197
|
+
usage: result.usage,
|
|
198
|
+
finishReason: result.finishReason,
|
|
199
|
+
providerMetadata: result.providerMetadata,
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
return {
|
|
203
|
+
stream: new ReadableStream({
|
|
204
|
+
start(controller) {
|
|
205
|
+
for (const part of parts) {
|
|
206
|
+
controller.enqueue(part);
|
|
207
|
+
}
|
|
208
|
+
controller.close();
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/** A client that makes images on the person's sign-in: one client run per image. */
|
|
215
|
+
export class HarnessImageModel {
|
|
216
|
+
specificationVersion = "v3";
|
|
217
|
+
modelId;
|
|
218
|
+
maxImagesPerCall = 1;
|
|
219
|
+
harness;
|
|
220
|
+
constructor(harness, alias) {
|
|
221
|
+
this.harness = harness;
|
|
222
|
+
this.modelId = alias;
|
|
223
|
+
}
|
|
224
|
+
async doGenerate(options) {
|
|
225
|
+
const dir = join(workDir, nanoid(10));
|
|
226
|
+
mkdirSync(dir, { recursive: true });
|
|
227
|
+
const warnings = [];
|
|
228
|
+
const files = [];
|
|
229
|
+
for (const file of options.files ?? []) {
|
|
230
|
+
if (file.type !== "file") {
|
|
231
|
+
warnings.push({ type: "unsupported", feature: "image url" });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const path = join(dir, `ref-${files.length + 1}.${extensionOf(file.mediaType)}`);
|
|
235
|
+
writeFileSync(path, typeof file.data === "string" ? Buffer.from(file.data, "base64") : file.data);
|
|
236
|
+
files.push(path);
|
|
237
|
+
}
|
|
238
|
+
for (const key of ["size", "seed", "mask"]) {
|
|
239
|
+
if (options[key] !== undefined) {
|
|
240
|
+
warnings.push({ type: "unsupported", feature: key });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const image = await this.invoke({
|
|
245
|
+
dir,
|
|
246
|
+
prompt: options.prompt ?? "",
|
|
247
|
+
files,
|
|
248
|
+
aspectRatio: options.aspectRatio,
|
|
249
|
+
signal: options.abortSignal,
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
images: [image],
|
|
253
|
+
warnings,
|
|
254
|
+
response: { timestamp: new Date(), modelId: this.modelId, headers: undefined },
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
if (err.code === "ENOENT") {
|
|
259
|
+
throw new ModelUnavailableError(notInstalled(this.harness));
|
|
260
|
+
}
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
rmSync(dir, { recursive: true, force: true });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** The extension a saved attachment gets, so the client reads it as what it is. */
|
|
4
|
+
export function extensionOf(mediaType, filename) {
|
|
5
|
+
const own = filename?.match(/\.([a-z0-9]{1,5})$/i)?.[1];
|
|
6
|
+
if (own) {
|
|
7
|
+
return own.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
const sub = mediaType.split("/")[1] ?? "bin";
|
|
10
|
+
return { jpeg: "jpg", "svg+xml": "svg", plain: "txt", markdown: "md" }[sub] ?? sub;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The conversation as a client takes it: one system prompt and one prompt on stdin. A single
|
|
14
|
+
* user message goes as it is; a longer thread is written out with its speakers, and the client
|
|
15
|
+
* is asked to answer its last message. Attachments are saved to `dir` and named in the prompt.
|
|
16
|
+
*/
|
|
17
|
+
export function renderPrompt(messages, dir) {
|
|
18
|
+
const warnings = [];
|
|
19
|
+
const files = [];
|
|
20
|
+
const system = [];
|
|
21
|
+
const turns = [];
|
|
22
|
+
const saveFile = (input) => {
|
|
23
|
+
// A file's content comes tagged: { type: "data", data } | { type: "url", url } | { type: "text", text }.
|
|
24
|
+
const tagged = input.data;
|
|
25
|
+
const data = tagged && typeof tagged === "object" && typeof tagged.type === "string"
|
|
26
|
+
? tagged.type === "url"
|
|
27
|
+
? tagged.url
|
|
28
|
+
: tagged.type === "text"
|
|
29
|
+
? Buffer.from(tagged.text ?? "", "utf8")
|
|
30
|
+
: tagged.data
|
|
31
|
+
: input.data;
|
|
32
|
+
const part = { ...input, data };
|
|
33
|
+
if (part.data instanceof URL) {
|
|
34
|
+
return `[Attachment at ${part.data.href} (${part.mediaType})]`;
|
|
35
|
+
}
|
|
36
|
+
if (typeof part.data !== "string" && !(part.data instanceof Uint8Array)) {
|
|
37
|
+
warnings.push({ type: "unsupported", feature: "file parts" });
|
|
38
|
+
return `[Attachment: ${part.filename ?? part.mediaType}]`;
|
|
39
|
+
}
|
|
40
|
+
if (!dir) {
|
|
41
|
+
warnings.push({ type: "unsupported", feature: "file parts" });
|
|
42
|
+
return `[Attachment: ${part.filename ?? part.mediaType}]`;
|
|
43
|
+
}
|
|
44
|
+
const path = join(dir, `${files.length + 1}.${extensionOf(part.mediaType, part.filename)}`);
|
|
45
|
+
const bytes = typeof part.data === "string" ? Buffer.from(part.data, "base64") : part.data;
|
|
46
|
+
writeFileSync(path, bytes);
|
|
47
|
+
files.push(path);
|
|
48
|
+
return `[Attached file — open it to read it: ${path}]`;
|
|
49
|
+
};
|
|
50
|
+
for (const m of messages) {
|
|
51
|
+
if (m.role === "system") {
|
|
52
|
+
system.push(m.content);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const text = m.content
|
|
56
|
+
.map((part) => {
|
|
57
|
+
switch (part.type) {
|
|
58
|
+
case "text":
|
|
59
|
+
return part.text;
|
|
60
|
+
case "file":
|
|
61
|
+
return saveFile(part);
|
|
62
|
+
case "reasoning":
|
|
63
|
+
return "";
|
|
64
|
+
case "tool-call":
|
|
65
|
+
return `[Called ${part.toolName} with ${JSON.stringify(part.input)}]`;
|
|
66
|
+
case "tool-result": {
|
|
67
|
+
const out = part.output;
|
|
68
|
+
const value = out.type === "text" || out.type === "error-text"
|
|
69
|
+
? String(out.value)
|
|
70
|
+
: out.type === "json" || out.type === "error-json"
|
|
71
|
+
? JSON.stringify(out.value)
|
|
72
|
+
: out.type === "execution-denied"
|
|
73
|
+
? `denied${out.reason ? `: ${out.reason}` : ""}`
|
|
74
|
+
: JSON.stringify(out.value ?? out);
|
|
75
|
+
return `[Result of ${part.toolName}: ${value}]`;
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
.join("\n");
|
|
83
|
+
turns.push({ role: m.role, text });
|
|
84
|
+
}
|
|
85
|
+
const prompt = turns.length === 1 && turns[0].role === "user"
|
|
86
|
+
? turns[0].text
|
|
87
|
+
: turns
|
|
88
|
+
.map((t) => `${t.role === "user" ? "User" : t.role === "tool" ? "Tool" : "Assistant"}:\n${t.text}`)
|
|
89
|
+
.join("\n\n");
|
|
90
|
+
if (turns.length > 1) {
|
|
91
|
+
system.push("The prompt holds the conversation so far, each message headed by its speaker. Answer the last message as the assistant — the answer only, no speaker label.");
|
|
92
|
+
}
|
|
93
|
+
return { system: system.join("\n\n"), prompt, files, warnings };
|
|
94
|
+
}
|
|
95
|
+
/** What a client without a schema option is told so its answer parses. */
|
|
96
|
+
export function jsonInstruction(schema) {
|
|
97
|
+
return [
|
|
98
|
+
"Answer with one JSON value only — no prose, no code fence — that matches this JSON Schema:",
|
|
99
|
+
JSON.stringify(schema ?? { type: "object" }),
|
|
100
|
+
].join("\n");
|
|
101
|
+
}
|
|
102
|
+
/** A client's answer as JSON: the fences and prose some wrap around it are taken off. */
|
|
103
|
+
export function parseJsonAnswer(text) {
|
|
104
|
+
const trimmed = text.trim();
|
|
105
|
+
const unfenced = trimmed
|
|
106
|
+
.replace(/^\s*```(?:json)?\s*/i, "")
|
|
107
|
+
.replace(/\s*```\s*$/, "")
|
|
108
|
+
.trim();
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(unfenced);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Prose around the value: take the outermost object or array.
|
|
114
|
+
const start = unfenced.search(/[{[]/);
|
|
115
|
+
const end = Math.max(unfenced.lastIndexOf("}"), unfenced.lastIndexOf("]"));
|
|
116
|
+
if (start >= 0 && end > start) {
|
|
117
|
+
return JSON.parse(unfenced.slice(start, end + 1));
|
|
118
|
+
}
|
|
119
|
+
throw new Error("The answer is not JSON.");
|
|
120
|
+
}
|
|
121
|
+
}
|