wizards 0.5.7 → 0.5.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/apps/runtime/dist/agents/architect.js +13 -26
- package/apps/runtime/dist/agents/studio-assistant.js +19 -22
- package/apps/runtime/dist/agents/subscription.js +10 -9
- package/apps/runtime/dist/app.js +6 -6
- package/apps/runtime/dist/assistant/knows.js +10 -9
- package/apps/runtime/dist/connectors/external.js +3 -3
- package/apps/runtime/dist/connectors/index.js +23 -23
- package/apps/runtime/dist/connectors/via-account.js +6 -6
- package/apps/runtime/dist/documents/parse.js +3 -3
- package/apps/runtime/dist/engine/input.js +11 -11
- package/apps/runtime/dist/engine/requirements.js +7 -8
- package/apps/runtime/dist/engine/runner.js +10 -14
- package/apps/runtime/dist/engine/steps.js +6 -6
- package/apps/runtime/dist/engine/surface.js +2 -2
- package/apps/runtime/dist/film/step.js +5 -5
- package/apps/runtime/dist/harness/apple.js +2 -1
- package/apps/runtime/dist/harness/claude.js +2 -2
- package/apps/runtime/dist/harness/codex.js +2 -2
- package/apps/runtime/dist/harness/model.js +4 -5
- package/apps/runtime/dist/harness/terminal.js +4 -4
- package/apps/runtime/dist/media/conversation.js +18 -35
- package/apps/runtime/dist/media/transcribe.js +2 -1
- package/apps/runtime/dist/model-errors.js +1 -1
- package/apps/runtime/dist/models.js +32 -40
- package/apps/runtime/dist/plugins/runs.js +3 -5
- package/apps/runtime/dist/routes/api.js +9 -9
- package/apps/runtime/dist/routes/lang.js +14 -9
- package/apps/runtime/dist/routes/runs.js +5 -5
- package/apps/runtime/dist/routes/studio.js +38 -37
- package/apps/runtime/dist/services/access.js +4 -4
- package/apps/runtime/dist/services/architect.js +3 -12
- package/apps/runtime/dist/services/assistant-chats.js +2 -2
- package/apps/runtime/dist/services/brand.js +3 -3
- package/apps/runtime/dist/services/cloud.js +5 -5
- package/apps/runtime/dist/services/errors.js +2 -2
- package/apps/runtime/dist/services/knowledge-model.js +3 -3
- package/apps/runtime/dist/services/marketplace.js +3 -3
- package/apps/runtime/dist/services/package-file.js +9 -11
- package/apps/runtime/dist/services/package.js +8 -9
- package/apps/runtime/dist/services/profile.js +2 -1
- package/apps/runtime/dist/services/project-files.js +13 -13
- package/apps/runtime/dist/services/projects.js +7 -11
- package/apps/runtime/dist/services/runners.js +4 -4
- package/apps/runtime/dist/services/runs.js +7 -10
- package/apps/runtime/dist/services/shares.js +2 -2
- package/apps/runtime/dist/services/site.js +8 -11
- package/apps/runtime/dist/services/space-categories.js +7 -7
- package/apps/runtime/dist/services/space-data.js +20 -20
- package/apps/runtime/dist/services/spaces.js +7 -9
- package/apps/runtime/dist/services/wizards.js +11 -13
- package/apps/runtime/dist/store/index.js +2 -2
- package/apps/runtime/dist/texts.js +195 -0
- package/apps/runtime/dist/tools/plugin.js +7 -3
- package/apps/runtime/dist/widgets/step.js +2 -2
- package/apps/web/dist/assets/AboutDialog-CpI-cGpz.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-rAtYZjxz.js → ChangelogDialog-BW56Y5UR.js} +1 -1
- package/apps/web/dist/assets/ChatView-CH8U-tRy.js +10 -0
- package/apps/web/dist/assets/EditorPage-Be5J_y4F.js +3 -0
- package/apps/web/dist/assets/{FlowPreview--3-yI51e.js → FlowPreview-CzmH4JUQ.js} +1 -1
- package/apps/web/dist/assets/{Harness-B9tvFe0U.js → Harness-CJKWO3AK.js} +2 -2
- package/apps/web/dist/assets/{NewWizardChannels-BxrBhC9U.js → NewWizardChannels-Ca2niMTY.js} +1 -1
- package/apps/web/dist/assets/NewWizardPage-D5gi4TYE.js +7 -0
- package/apps/web/dist/assets/{OnboardingPage-DPdH9sSj.js → OnboardingPage-Bm-c-D21.js} +1 -1
- package/apps/web/dist/assets/ProjectPage-C8nJCBZP.js +1 -0
- package/apps/web/dist/assets/{PublicRunner-Bv3TMKw4.js → PublicRunner-anxg3Rxz.js} +1 -1
- package/apps/web/dist/assets/{ReadOnly-CmqIrOAR.js → ReadOnly-i-6WBMSE.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-4yvfW_2c.js +4 -0
- package/apps/web/dist/assets/{RunnerView-Cd1VAlIA.js → RunnerView-BkWQBa_M.js} +1 -1
- package/apps/web/dist/assets/SettingsPage-NL8r9S6R.js +1 -0
- package/apps/web/dist/assets/SetupPage-Crcfx4-p.js +1 -0
- package/apps/web/dist/assets/{SharePage-BT80wI1I.js → SharePage-CDEAtmBC.js} +1 -1
- package/apps/web/dist/assets/changelog-BGBuldNF.js +1 -0
- package/apps/web/dist/assets/{esm-BTVd6EFo.js → esm-BF1nonbH.js} +1 -1
- package/apps/web/dist/assets/index-C74LSJ-W.js +14 -0
- package/apps/web/dist/assets/{lock-ByHFI96G.js → lock-DVvM6IsA.js} +1 -1
- package/apps/web/dist/assets/{phone-KPwxAM1R.js → phone-3fG1okFs.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-K5bg3QE3.js → settings-menu-D9fbi1Z0.js} +1 -1
- package/apps/web/dist/assets/ui-EuR3jezP.js +134 -0
- package/apps/web/dist/assets/{upload-BsIL8tNR.js → upload-BsMUhEfX.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/mcp/flow.html +77 -77
- package/bin/wizards.mjs +15 -2
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
- package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
- package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
- package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
- package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
- package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
- package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
- package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
- package/package.json +5 -1
- package/apps/web/dist/assets/AboutDialog-t9bayaIs.js +0 -1
- package/apps/web/dist/assets/ChatView-C3VqLT98.js +0 -10
- package/apps/web/dist/assets/EditorPage-D3Ef24b9.js +0 -3
- package/apps/web/dist/assets/NewWizardPage-C1_hsQPr.js +0 -7
- package/apps/web/dist/assets/ProjectPage-P40R1cPE.js +0 -1
- package/apps/web/dist/assets/RunDrawer-9qG1G-OY.js +0 -4
- package/apps/web/dist/assets/SettingsPage-DFz4UMfo.js +0 -1
- package/apps/web/dist/assets/SetupPage-B0UBNMB9.js +0 -1
- package/apps/web/dist/assets/changelog-BpSBMjpm.js +0 -1
- package/apps/web/dist/assets/index-D8vwgldH.js +0 -14
- package/apps/web/dist/assets/ui-4bQx9I1f.js +0 -134
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { marginOf, } from "@engenty-wizards/shared/decision";
|
|
2
2
|
import { branchValues, cardsFromData, conditionsOf, dataRef, followRules, formatsFor, isDecidedField, LIST_FORMATS, listRef, loopKey, MAX_SHOWN_FIELDS, optionsFromData, otherwiseOf, pageNeeds, whenHolds, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
3
|
-
import {
|
|
3
|
+
import { note, saidText, } from "@engenty-wizards/shared/run";
|
|
4
4
|
import { CONVERSATION_RUNNERS, DEFAULT_RUNNER_SETTINGS } from "@engenty-wizards/shared/runners";
|
|
5
5
|
import { eq } from "drizzle-orm";
|
|
6
6
|
import { nanoid } from "nanoid";
|
|
@@ -43,7 +43,7 @@ export class RunInputError extends NotedError {
|
|
|
43
43
|
}
|
|
44
44
|
export class NoCreditsError extends NotedError {
|
|
45
45
|
constructor() {
|
|
46
|
-
super(
|
|
46
|
+
super(note("wizardRunOutCredits"));
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/** The runs this process drives right now, with the handle that stops each. */
|
|
@@ -113,19 +113,19 @@ function friendly(err) {
|
|
|
113
113
|
}
|
|
114
114
|
// A runtime that runs alone pays its provider itself: its admin can top the account up.
|
|
115
115
|
if (!managed && /minimum balance|insufficient_funds|top up your credits/i.test(msg)) {
|
|
116
|
-
return
|
|
116
|
+
return note("providerCreditLow");
|
|
117
117
|
}
|
|
118
118
|
if (/rate.?limit|429/i.test(msg)) {
|
|
119
|
-
return
|
|
119
|
+
return note("aiServiceBusyRight");
|
|
120
120
|
}
|
|
121
121
|
if (/timeout|timed out/i.test(msg)) {
|
|
122
|
-
return
|
|
122
|
+
return note("stepTookTooLong");
|
|
123
123
|
}
|
|
124
124
|
// The platform's own provider account (budget, quota, key): nothing the person can fix.
|
|
125
125
|
if (/budget|quota|insufficient|billing|credit balance|api key|unauthori[sz]ed|401|403/i.test(msg)) {
|
|
126
|
-
return
|
|
126
|
+
return note("aiServiceNotReached");
|
|
127
127
|
}
|
|
128
|
-
return
|
|
128
|
+
return note("stepFailed", { detail: msg.slice(0, 300) });
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* A step that failed ends the run. A failure with a note (Apple Intelligence's) goes into the log
|
|
@@ -637,7 +637,7 @@ export async function submitPage(runId, stepId, input) {
|
|
|
637
637
|
(choice.values.length === 0 || choice.values.some((v) => String(v) === String(answer)))) {
|
|
638
638
|
errors.push({
|
|
639
639
|
field: id,
|
|
640
|
-
message:
|
|
640
|
+
message: note("notAvailableNow"),
|
|
641
641
|
});
|
|
642
642
|
}
|
|
643
643
|
}
|
|
@@ -653,9 +653,7 @@ export async function submitPage(runId, stepId, input) {
|
|
|
653
653
|
for (const id of Array.isArray(value) ? value : value ? [value] : []) {
|
|
654
654
|
const asset = await db.query.asset.findFirst({ where: eq(schema.asset.id, String(id)) });
|
|
655
655
|
if (asset?.runId !== runId) {
|
|
656
|
-
throw new RunInputError([
|
|
657
|
-
{ field: field.id, message: bilingual("Datei nicht gefunden", "File not found") },
|
|
658
|
-
]);
|
|
656
|
+
throw new RunInputError([{ field: field.id, message: note("fileNotFound") }]);
|
|
659
657
|
}
|
|
660
658
|
}
|
|
661
659
|
}
|
|
@@ -666,9 +664,7 @@ export async function submitPage(runId, stepId, input) {
|
|
|
666
664
|
values[field.id] = account.label;
|
|
667
665
|
}
|
|
668
666
|
else if (field.required) {
|
|
669
|
-
throw new RunInputError([
|
|
670
|
-
{ field: field.id, message: bilingual("Verbinde zuerst", "Connect first") },
|
|
671
|
-
]);
|
|
667
|
+
throw new RunInputError([{ field: field.id, message: note("connectFirst") }]);
|
|
672
668
|
}
|
|
673
669
|
}
|
|
674
670
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pOf } from "@engenty-wizards/shared/decision";
|
|
2
2
|
import { allFields, dataRef, isDecisionStep, LOCALES, templateRefs, wizardLang, } from "@engenty-wizards/shared/definition";
|
|
3
3
|
import { mainLogo, ruleLines } from "@engenty-wizards/shared/projects";
|
|
4
|
-
import {
|
|
4
|
+
import { note as runNote } from "@engenty-wizards/shared/run";
|
|
5
5
|
import { SURFACE_GUIDE, validateSurface, } from "@engenty-wizards/shared/surface";
|
|
6
6
|
import { Agent } from "@mastra/core/agent";
|
|
7
7
|
import { generateText, Output } from "ai";
|
|
@@ -379,7 +379,7 @@ function agentEntries(step, ctx) {
|
|
|
379
379
|
async function runAgentEach(step, ctx) {
|
|
380
380
|
const entries = agentEntries(step, ctx);
|
|
381
381
|
if (!entries.length) {
|
|
382
|
-
throw new StepError(
|
|
382
|
+
throw new StepError(runNote("noEntriesStepWork"));
|
|
383
383
|
}
|
|
384
384
|
// The view, if any, is composed once over the table of all entries.
|
|
385
385
|
const single = {
|
|
@@ -554,7 +554,7 @@ export async function runAgentStep(step, ctx) {
|
|
|
554
554
|
const text = step.output.format === "json" ? everything : closing || everything;
|
|
555
555
|
if (step.output.format !== "json") {
|
|
556
556
|
if (!text && !assets.length) {
|
|
557
|
-
throw new StepError(
|
|
557
|
+
throw new StepError(runNote("stepNotReturnResult"));
|
|
558
558
|
}
|
|
559
559
|
return { text, assets: assets.length ? assets : undefined, at: new Date().toISOString() };
|
|
560
560
|
}
|
|
@@ -755,7 +755,7 @@ async function writeHtml(step, ctx, kind) {
|
|
|
755
755
|
.replace(/```\s*$/, "")
|
|
756
756
|
.trim();
|
|
757
757
|
if (!/<html|<!doctype/i.test(html)) {
|
|
758
|
-
throw new StepError(
|
|
758
|
+
throw new StepError(runNote("documentNotCreated"));
|
|
759
759
|
}
|
|
760
760
|
// On screen (preview, PNG) a print document needs the margin its @page rule gives it on paper.
|
|
761
761
|
return kind === "document"
|
|
@@ -806,7 +806,7 @@ async function inParallel(items, limit, work) {
|
|
|
806
806
|
async function runMediaStep(step, ctx, asset) {
|
|
807
807
|
const entries = entriesOf(step, ctx);
|
|
808
808
|
if (!entries.length) {
|
|
809
|
-
throw new StepError(
|
|
809
|
+
throw new StepError(runNote("stepMissingSourcesImages"));
|
|
810
810
|
}
|
|
811
811
|
const previous = ctx.state.outputs[step.id];
|
|
812
812
|
const before = previous?.assets?.filter((a) => a.kind === asset) ?? [];
|
|
@@ -898,7 +898,7 @@ export async function runGenerateStep(step, ctx) {
|
|
|
898
898
|
case "voice": {
|
|
899
899
|
const text = renderTemplate(step.prompt, ctx.scope).trim().slice(0, 4000);
|
|
900
900
|
if (!text) {
|
|
901
|
-
throw new StepError(
|
|
901
|
+
throw new StepError(runNote("noTextReadAloud"));
|
|
902
902
|
}
|
|
903
903
|
const note = ctx.state.notes[step.id];
|
|
904
904
|
const media = await generateSpeechMedia({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assembleSurface, dataRef, listRef, } from "@engenty-wizards/shared/definition";
|
|
2
|
-
import {
|
|
2
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
3
3
|
import { validateSurface } from "@engenty-wizards/shared/surface";
|
|
4
4
|
import { decide } from "./decide.js";
|
|
5
5
|
import { answersAsText, resolveRef } from "./template.js";
|
|
@@ -14,7 +14,7 @@ export async function runSurfaceStep(step, ctx) {
|
|
|
14
14
|
const components = step.components ?? (await decidedComponents(step, ctx));
|
|
15
15
|
const issues = validateSurface(components, Object.keys(data));
|
|
16
16
|
if (issues.length) {
|
|
17
|
-
throw new StepError(
|
|
17
|
+
throw new StepError(note("viewErrors", { detail: issues.map((i) => i.message).join(" ") }));
|
|
18
18
|
}
|
|
19
19
|
return { surface: { components, data }, at: new Date().toISOString() };
|
|
20
20
|
}
|
|
@@ -2,12 +2,12 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readFile, rm, stat } from "node:fs/promises";
|
|
4
4
|
import { isAbsolute, join, normalize } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { note as runNote } from "@engenty-wizards/shared/run";
|
|
6
6
|
import { StepError } from "../engine/types.js";
|
|
7
7
|
import { CLAUDE_BIN, claudeEnv } from "../harness/claude.js";
|
|
8
8
|
import { failureOf } from "../harness/model.js";
|
|
9
9
|
import { openBridge } from "../mcp/bridge.js";
|
|
10
|
-
import {
|
|
10
|
+
import { saidOf } from "../model-errors.js";
|
|
11
11
|
import { filmClient } from "../models.js";
|
|
12
12
|
import { prepareFilmFolder, savedSession, saveSession } from "./project.js";
|
|
13
13
|
import { runHyperframes } from "./tools.js";
|
|
@@ -229,13 +229,13 @@ export async function runFilmStep(step, ctx) {
|
|
|
229
229
|
if (ctx.signal.aborted) {
|
|
230
230
|
throw err;
|
|
231
231
|
}
|
|
232
|
-
throw new StepError(
|
|
232
|
+
throw new StepError(runNote("filmClientStopped", { name: client.name, detail: err.message }));
|
|
233
233
|
});
|
|
234
234
|
const result = last;
|
|
235
235
|
if (!result || result.is_error) {
|
|
236
236
|
const detail = String(result?.result ?? (stderr.trim() || "no result")).slice(0, 400);
|
|
237
237
|
const failure = failureOf(client, detail);
|
|
238
|
-
throw new StepError(
|
|
238
|
+
throw new StepError(runNote("filmWorkSaved", { problem: saidOf(failure) }));
|
|
239
239
|
}
|
|
240
240
|
return result;
|
|
241
241
|
};
|
|
@@ -265,7 +265,7 @@ export async function runFilmStep(step, ctx) {
|
|
|
265
265
|
}
|
|
266
266
|
const problem = renderProblem(render.out);
|
|
267
267
|
if (attempt >= RENDER_FIXES) {
|
|
268
|
-
throw new StepError(
|
|
268
|
+
throw new StepError(runNote("filmRenderFailed", { detail: problem.slice(0, 400) }));
|
|
269
269
|
}
|
|
270
270
|
await ctx.emit("tool", { code: "filmFix" });
|
|
271
271
|
const fixed = await turn(`The final render of film/ failed:\n\n${problem}\n\nFind the cause in the composition's code, fix it, run \`check\` and a draft render through the hyperframes tool until both pass, and end with one line on what you fixed.`, await savedSession(root));
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { release } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { langOf, SPEECH_LOCALES } from "@engenty-wizards/shared/definition";
|
|
5
6
|
import { nanoid } from "nanoid";
|
|
6
7
|
import { env } from "../env.js";
|
|
7
8
|
import { ffmpeg, hasFfmpeg } from "../media/ffmpeg.js";
|
|
@@ -168,7 +169,7 @@ export function appleModel(alias) {
|
|
|
168
169
|
/** The Mac's own language: what a voice note is most likely in, unless the call names one. */
|
|
169
170
|
function systemLocale() {
|
|
170
171
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
171
|
-
return /^[a-z]{2}-[A-Z]{2}$/.test(locale) ? locale : locale
|
|
172
|
+
return /^[a-z]{2}-[A-Z]{2}$/.test(locale) ? locale : SPEECH_LOCALES[langOf(locale) ?? "en"];
|
|
172
173
|
}
|
|
173
174
|
/** What AVAudioFile reads as it is; a browser's WebM or Ogg (Opus) is turned into WAV first. */
|
|
174
175
|
function audioExtension(mediaType) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
2
2
|
import { resolveEnv, run } from "./env.js";
|
|
3
3
|
import { failureOf, HarnessModel, lastJson, runClient, } from "./model.js";
|
|
4
4
|
/**
|
|
@@ -143,5 +143,5 @@ export const claude = {
|
|
|
143
143
|
},
|
|
144
144
|
login: { args: ["auth", "login"], interactive: false },
|
|
145
145
|
model: (alias, effort) => new ClaudeCodeModel(claude, alias || "sonnet", effort),
|
|
146
|
-
exhausted:
|
|
146
|
+
exhausted: note("anthropicAccountClaudeCode"),
|
|
147
147
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
5
5
|
import { resolveEnv, run } from "./env.js";
|
|
6
6
|
import { failureOf, HarnessImageModel, HarnessModel, runClient, } from "./model.js";
|
|
7
7
|
/**
|
|
@@ -225,5 +225,5 @@ export const codex = {
|
|
|
225
225
|
login: { args: ["login"], interactive: false },
|
|
226
226
|
model: (alias, effort) => new CodexModel(codex, alias || "default", effort),
|
|
227
227
|
image: { alias: "image", model: (alias) => new CodexImageModel(codex, alias || "image") },
|
|
228
|
-
exhausted:
|
|
228
|
+
exhausted: note("chatgptAccountCodexSigned"),
|
|
229
229
|
};
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
6
6
|
import { env } from "../env.js";
|
|
7
7
|
import { openBridge } from "../mcp/bridge.js";
|
|
8
8
|
import { ModelUnavailableError } from "../model-errors.js";
|
|
9
9
|
import { extensionOf, jsonInstruction, parseJsonAnswer, renderPrompt } from "./prompt.js";
|
|
10
10
|
const workDir = join(env.dataDir, "harness");
|
|
11
|
-
export const signedOut = (h) =>
|
|
12
|
-
export const notInstalled = (h) =>
|
|
13
|
-
export const exhausted = (h) => h.exhausted ??
|
|
14
|
-
bilingual(`Das Konto, mit dem ${h.name} angemeldet ist, hat kein Guthaben oder Kontingent mehr.`, `The account ${h.name} is signed in with has no credit or quota left.`);
|
|
11
|
+
export const signedOut = (h) => note("clientNotSignedIn", { name: h.name });
|
|
12
|
+
export const notInstalled = (h) => note("clientNotInstalled", { name: h.name });
|
|
13
|
+
export const exhausted = (h) => h.exhausted ?? note("clientNoCredit", { name: h.name });
|
|
15
14
|
/** The failure a client reports, as the person should read it. */
|
|
16
15
|
export function failureOf(h, detail) {
|
|
17
16
|
// The client's own words stay in the log: the sentence below sums them up for the person.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { accessSync, chmodSync, constants } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { note } from "@engenty-wizards/shared/run";
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
6
6
|
import { ServiceError } from "../services/errors.js";
|
|
7
7
|
const BUFFER = 200_000;
|
|
@@ -37,7 +37,7 @@ async function ptyModule() {
|
|
|
37
37
|
return { spawn };
|
|
38
38
|
}
|
|
39
39
|
catch {
|
|
40
|
-
throw new ServiceError("refused",
|
|
40
|
+
throw new ServiceError("refused", note("builtTerminalNotAvailable"));
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
function emit(session, event) {
|
|
@@ -85,7 +85,7 @@ export async function openTerminal(opts) {
|
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
catch (err) {
|
|
88
|
-
throw new ServiceError("refused",
|
|
88
|
+
throw new ServiceError("refused", note("commandNotStarted", { detail: err.message }));
|
|
89
89
|
}
|
|
90
90
|
const session = {
|
|
91
91
|
id,
|
|
@@ -147,7 +147,7 @@ export async function openTerminal(opts) {
|
|
|
147
147
|
function sessionOf(id) {
|
|
148
148
|
const session = sessions.get(id);
|
|
149
149
|
if (!session) {
|
|
150
|
-
throw new ServiceError("not_found",
|
|
150
|
+
throw new ServiceError("not_found", note("terminalNoLongerExists"));
|
|
151
151
|
}
|
|
152
152
|
return session;
|
|
153
153
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LANG_NAMES } from "@engenty-wizards/shared/definition";
|
|
1
2
|
import { NotedError } from "../model-errors.js";
|
|
2
3
|
import { conversationAccess } from "../models.js";
|
|
3
4
|
/**
|
|
@@ -10,8 +11,8 @@ const SECRETS_URL = "https://api.openai.com/v1/realtime/client_secrets";
|
|
|
10
11
|
const CALLS_URL = "https://api.openai.com/v1/realtime/calls";
|
|
11
12
|
/** How long a minted secret opens a session; the session itself goes on until it ends. */
|
|
12
13
|
const SECRET_TTL_SECONDS = 600;
|
|
13
|
-
/** The voice the wizard speaks with
|
|
14
|
-
const
|
|
14
|
+
/** The voice the wizard speaks with: one that speaks every language engenty does. */
|
|
15
|
+
const VOICE = "marin";
|
|
15
16
|
/** What the model may do: one tool per command of the run page, run by the browser. */
|
|
16
17
|
export const CONVERSATION_TOOLS = [
|
|
17
18
|
{
|
|
@@ -97,41 +98,23 @@ export const CONVERSATION_TOOLS = [
|
|
|
97
98
|
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
98
99
|
},
|
|
99
100
|
];
|
|
100
|
-
/** The rules the model speaks by
|
|
101
|
+
/** The rules the model speaks by: written in English, it speaks the wizard's language. */
|
|
101
102
|
export function conversationInstructions(view) {
|
|
102
|
-
const
|
|
103
|
-
const about = [
|
|
104
|
-
`${de ? "Du bist" : "You are"} ${view.wizard.title}.`,
|
|
105
|
-
view.wizard.description,
|
|
106
|
-
view.wizard.intro,
|
|
107
|
-
]
|
|
103
|
+
const about = [`You are ${view.wizard.title}.`, view.wizard.description, view.wizard.intro]
|
|
108
104
|
.filter(Boolean)
|
|
109
105
|
.join(" ");
|
|
110
|
-
const rules =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
]
|
|
123
|
-
: [
|
|
124
|
-
"You hold a conversation that fills in a wizard step by step. The person also sees the wizard on the screen.",
|
|
125
|
-
"Speak English, short and friendly, as on the phone. One sentence, then the next question.",
|
|
126
|
-
"Ask one field at a time, in the order of the state. After each answer call set_field. When every field you can fill by voice is answered, call submit_page.",
|
|
127
|
-
"For a choice, name the options (at most four at a time) and pass exactly one of them on.",
|
|
128
|
-
"Read e-mail addresses, links and numbers back before passing them on.",
|
|
129
|
-
"A field that needs the screen (a photo, a file, a signature, a connection) is filled there by the person: say so in one sentence and wait for the next state.",
|
|
130
|
-
"While the wizard works, say in one sentence what it is doing and wait. Never make anything up.",
|
|
131
|
-
"At a review, sum the text up in a few words and ask whether it fits. Then accept_review, or regenerate with what the person wants changed.",
|
|
132
|
-
"At the end, say what was made and that it is on the screen.",
|
|
133
|
-
"With the camera on you can see what the person shows with look, and put a photo into a photo field with take_photo; ask for a photo field like this: 'Hold it to the camera, I will take the picture.' With the camera off, ask the person to switch it on, or leave the field to the screen.",
|
|
134
|
-
];
|
|
106
|
+
const rules = [
|
|
107
|
+
"You hold a conversation that fills in a wizard step by step. The person also sees the wizard on the screen.",
|
|
108
|
+
`Speak ${LANG_NAMES[view.lang]} (${view.lang}), short and friendly, as on the phone. One sentence, then the next question.`,
|
|
109
|
+
"Ask one field at a time, in the order of the state. After each answer call set_field. When every field you can fill by voice is answered, call submit_page.",
|
|
110
|
+
"For a choice, name the options (at most four at a time) and pass exactly one of them on.",
|
|
111
|
+
"Read e-mail addresses, links and numbers back before passing them on.",
|
|
112
|
+
"A field that needs the screen (a photo, a file, a signature, a connection) is filled there by the person: say so in one sentence and wait for the next state.",
|
|
113
|
+
"While the wizard works, say in one sentence what it is doing and wait. Never make anything up.",
|
|
114
|
+
"At a review, sum the text up in a few words and ask whether it fits. Then accept_review, or regenerate with what the person wants changed.",
|
|
115
|
+
"At the end, say what was made and that it is on the screen.",
|
|
116
|
+
"With the camera on you can see what the person shows with look, and put a photo into a photo field with take_photo; ask for a photo field like this: 'Hold it to the camera, I will take the picture.' With the camera off, ask the person to switch it on, or leave the field to the screen.",
|
|
117
|
+
];
|
|
135
118
|
return `${about}\n\n${rules.join("\n")}`;
|
|
136
119
|
}
|
|
137
120
|
/** Mints the secret a browser opens a session with; a failure carries a note for the run's language. */
|
|
@@ -157,7 +140,7 @@ export async function mintConversation(view) {
|
|
|
157
140
|
noise_reduction: { type: "far_field" },
|
|
158
141
|
turn_detection: { type: "server_vad", threshold: 0.7, silence_duration_ms: 600 },
|
|
159
142
|
},
|
|
160
|
-
output: { voice:
|
|
143
|
+
output: { voice: VOICE },
|
|
161
144
|
},
|
|
162
145
|
},
|
|
163
146
|
}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { SPEECH_LOCALES } from "@engenty-wizards/shared/definition";
|
|
3
4
|
import { generateText, experimental_transcribe as transcribe } from "ai";
|
|
4
5
|
import { costOf, listenerModel, transcriptionCostUsd } from "../models.js";
|
|
5
6
|
import { ffmpeg, hasFfmpeg, probeMedia, withTempDir } from "./ffmpeg.js";
|
|
@@ -18,7 +19,7 @@ export async function transcribeAudio(input) {
|
|
|
18
19
|
audio: input.bytes,
|
|
19
20
|
abortSignal: input.abortSignal,
|
|
20
21
|
...(input.language
|
|
21
|
-
? { providerOptions: { apple: { locale: input.language
|
|
22
|
+
? { providerOptions: { apple: { locale: SPEECH_LOCALES[input.language] } } }
|
|
22
23
|
: {}),
|
|
23
24
|
});
|
|
24
25
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { noteText } from "@engenty-wizards/shared/run";
|
|
2
2
|
/**
|
|
3
|
-
* A failure the person reads, given as a note of `RUN_NOTES`
|
|
3
|
+
* A failure the person reads, given as a note of `RUN_NOTES` where it has
|
|
4
4
|
* one: a run shows it in the wizard's language, a response in the asker's, and the message keeps
|
|
5
5
|
* the English line for the log. Free text has no note.
|
|
6
6
|
*/
|
|
@@ -8,7 +8,7 @@ import { createOpenAI } from "@ai-sdk/openai";
|
|
|
8
8
|
import { createReplicate } from "@ai-sdk/replicate";
|
|
9
9
|
import { MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
|
|
10
10
|
import { creditsModelOf, isCreditsRef, PROVIDER_IDS, providerInfo, providerOfRef, } from "@engenty-wizards/shared/providers";
|
|
11
|
-
import {
|
|
11
|
+
import { note, saidText } from "@engenty-wizards/shared/run";
|
|
12
12
|
import { generateSpeech, generateText, } from "ai";
|
|
13
13
|
import { accountToken, linkedAccount } from "./auth/account.js";
|
|
14
14
|
import { DeviceModel, deviceThinks } from "./engine/device.js";
|
|
@@ -22,6 +22,7 @@ import { seal, unseal } from "./secrets/crypto.js";
|
|
|
22
22
|
import { vaultDelete, vaultGet, vaultSet } from "./secrets/vault.js";
|
|
23
23
|
import { readSetting, writeSetting } from "./settings.js";
|
|
24
24
|
import { currentTenant, currentTenantOrNull } from "./tenants/tenant.js";
|
|
25
|
+
import { serverText } from "./texts.js";
|
|
25
26
|
export { isHarnessVendor } from "./harness/index.js";
|
|
26
27
|
/**
|
|
27
28
|
* A step names a model class, never a model. Where the class runs is decided here, in this order:
|
|
@@ -234,27 +235,26 @@ function ownRef(cfg, cls) {
|
|
|
234
235
|
}
|
|
235
236
|
/** What a class makes, to say that nothing makes it. */
|
|
236
237
|
const MAKES = {
|
|
237
|
-
classifier:
|
|
238
|
-
standard:
|
|
239
|
-
high:
|
|
240
|
-
highest:
|
|
241
|
-
image:
|
|
242
|
-
video:
|
|
243
|
-
speech:
|
|
244
|
-
audio:
|
|
238
|
+
classifier: "makesText",
|
|
239
|
+
standard: "makesText",
|
|
240
|
+
high: "makesText",
|
|
241
|
+
highest: "makesText",
|
|
242
|
+
image: "makesImages",
|
|
243
|
+
video: "makesVideos",
|
|
244
|
+
speech: "makesSpeech",
|
|
245
|
+
audio: "makesVoiceNotes",
|
|
245
246
|
};
|
|
246
247
|
/** Why a class has no way: the provider picked for it lacks its key, or nothing is set up. */
|
|
247
248
|
function missingText(cfg, cls) {
|
|
248
249
|
const bound = cfg.settings.bindings[cls];
|
|
249
250
|
if (isCreditsRef(bound)) {
|
|
250
|
-
return
|
|
251
|
+
return note("creditsNotSignedIn");
|
|
251
252
|
}
|
|
252
|
-
const makes = MAKES[cls];
|
|
253
253
|
const provider = bound ? providerInfo(providerOfRef(bound) ?? "") : null;
|
|
254
254
|
if (provider) {
|
|
255
|
-
return
|
|
255
|
+
return note("providerKeyMissing", { provider: provider.name, makes: note(MAKES[cls]) });
|
|
256
256
|
}
|
|
257
|
-
return
|
|
257
|
+
return note("noModelFor", { makes: note(MAKES[cls]) });
|
|
258
258
|
}
|
|
259
259
|
/**
|
|
260
260
|
* The way to the credits. On a Manage-App's runtime always; alone, through the linked account —
|
|
@@ -274,7 +274,7 @@ async function creditAccess(cfg, chosen) {
|
|
|
274
274
|
const token = await accountToken();
|
|
275
275
|
if (!token) {
|
|
276
276
|
if (chosen) {
|
|
277
|
-
throw new ModelUnavailableError(
|
|
277
|
+
throw new ModelUnavailableError(note("accountNoLongerSigned"));
|
|
278
278
|
}
|
|
279
279
|
return null;
|
|
280
280
|
}
|
|
@@ -301,7 +301,7 @@ async function routeOf(cls) {
|
|
|
301
301
|
function need(cfg, name) {
|
|
302
302
|
const key = cfg.keys[name];
|
|
303
303
|
if (!key) {
|
|
304
|
-
throw new ModelUnavailableError(
|
|
304
|
+
throw new ModelUnavailableError(note("noApiKeyFor", { provider: providerInfo(name)?.name ?? name }));
|
|
305
305
|
}
|
|
306
306
|
return key;
|
|
307
307
|
}
|
|
@@ -412,7 +412,7 @@ async function ownLanguageModel(cfg, ref, meta) {
|
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
|
-
throw new ModelUnavailableError(
|
|
415
|
+
throw new ModelUnavailableError(note("modelNotUsedYet", { ref }));
|
|
416
416
|
}
|
|
417
417
|
/**
|
|
418
418
|
* The language model a text class runs on. A run watched from a device that offers the class
|
|
@@ -492,7 +492,7 @@ export async function imageModel(meta = {}) {
|
|
|
492
492
|
return own(createOpenAI({ apiKey: cfg.keys.openai }).image(aliasOf(id)));
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
|
-
throw new ModelUnavailableError(
|
|
495
|
+
throw new ModelUnavailableError(note("noModelForImages"));
|
|
496
496
|
}
|
|
497
497
|
export async function videoModel(meta = {}) {
|
|
498
498
|
const route = await routeOf("video");
|
|
@@ -521,7 +521,7 @@ export async function videoModel(meta = {}) {
|
|
|
521
521
|
return own(createGateway({ apiKey: cfg.keys.gateway }).videoModel(id), true);
|
|
522
522
|
}
|
|
523
523
|
}
|
|
524
|
-
throw new ModelUnavailableError(
|
|
524
|
+
throw new ModelUnavailableError(note("noModelForVideos"));
|
|
525
525
|
}
|
|
526
526
|
/** The model that reads a text aloud. */
|
|
527
527
|
export async function speechModel(meta = {}) {
|
|
@@ -529,7 +529,7 @@ export async function speechModel(meta = {}) {
|
|
|
529
529
|
if (route.kind === "credits") {
|
|
530
530
|
const resolved = await viaCredits(route, "speech", meta, (c, id) => c.speechModel(id));
|
|
531
531
|
if (!resolved.ref) {
|
|
532
|
-
throw new ModelUnavailableError(
|
|
532
|
+
throw new ModelUnavailableError(note("noModelForSpeech"));
|
|
533
533
|
}
|
|
534
534
|
return resolved;
|
|
535
535
|
}
|
|
@@ -560,7 +560,7 @@ export async function speechModel(meta = {}) {
|
|
|
560
560
|
return own(createOpenAI({ apiKey: cfg.keys.openai }).speech(aliasOf(id)));
|
|
561
561
|
}
|
|
562
562
|
}
|
|
563
|
-
throw new ModelUnavailableError(
|
|
563
|
+
throw new ModelUnavailableError(note("noModelForSpeech"));
|
|
564
564
|
}
|
|
565
565
|
/**
|
|
566
566
|
* What turns a voice note into text: a transcription model (ElevenLabs Scribe, Whisper), or a
|
|
@@ -711,7 +711,10 @@ export function freeTierRefusal(err) {
|
|
|
711
711
|
}
|
|
712
712
|
/** What the person reads when Vercel's free tier leaves a model out, and how to fix it. */
|
|
713
713
|
export function freeTierText(model) {
|
|
714
|
-
return
|
|
714
|
+
return note("freeTier", {
|
|
715
|
+
model: model ? note("quoted", { text: model }) : note("thisModel"),
|
|
716
|
+
url: GATEWAY_TOP_UP,
|
|
717
|
+
});
|
|
715
718
|
}
|
|
716
719
|
/** What a key was answered for a model: refusals are asked again soon, as a top-up lifts them. */
|
|
717
720
|
const gatewayProbes = new Map();
|
|
@@ -765,7 +768,7 @@ const CONVERSATION_MODEL = env.conversationModel || "gpt-realtime";
|
|
|
765
768
|
export async function conversationAccess() {
|
|
766
769
|
const cfg = await config();
|
|
767
770
|
if (!cfg.keys.openai) {
|
|
768
|
-
throw new ModelUnavailableError(
|
|
771
|
+
throw new ModelUnavailableError(note("noOpenaiApiKey"));
|
|
769
772
|
}
|
|
770
773
|
return { apiKey: cfg.keys.openai, model: CONVERSATION_MODEL };
|
|
771
774
|
}
|
|
@@ -802,12 +805,10 @@ export async function classProblem(cls) {
|
|
|
802
805
|
const named = creditsModelOf((await config()).settings.bindings[cls]);
|
|
803
806
|
if (named) {
|
|
804
807
|
return catalog?.models && !catalog.models.some((m) => m.id === named)
|
|
805
|
-
?
|
|
808
|
+
? note("modelNotAvailableAccount")
|
|
806
809
|
: null;
|
|
807
810
|
}
|
|
808
|
-
return catalog?.classes && !catalog.classes[cls]
|
|
809
|
-
? bilingual("Dafür ist bei deinem Konto kein Modell eingerichtet.", "Your account has no model set up for this.")
|
|
810
|
-
: null;
|
|
811
|
+
return catalog?.classes && !catalog.classes[cls] ? note("accountNoModelSet") : null;
|
|
811
812
|
}
|
|
812
813
|
const client = harness(resolved.vendor);
|
|
813
814
|
if (client && (await detectHarness(client.id))?.auth === "none") {
|
|
@@ -833,7 +834,7 @@ export async function filmClient(cls) {
|
|
|
833
834
|
const client = managed ? null : harness(cfg.settings.source);
|
|
834
835
|
if (!client || !FILM_CLIENTS.has(client.id)) {
|
|
835
836
|
return {
|
|
836
|
-
problem:
|
|
837
|
+
problem: note("claudeCodeMakesFilms"),
|
|
837
838
|
};
|
|
838
839
|
}
|
|
839
840
|
const status = await detectHarness(client.id);
|
|
@@ -918,9 +919,7 @@ async function appleProblem(cls) {
|
|
|
918
919
|
if (!status.supported || status.reason === "missing") {
|
|
919
920
|
return appleUnavailableNote(status);
|
|
920
921
|
}
|
|
921
|
-
return (await hasFfmpeg())
|
|
922
|
-
? null
|
|
923
|
-
: bilingual("Aufnahmen aus Chrome oder Firefox (WebM, Ogg) schreibt dieser Mac erst mit ffmpeg auf. Safari und die App brauchen es nicht. Installiere ffmpeg, z. B. mit „brew install ffmpeg“.", "This Mac needs ffmpeg to transcribe recordings from Chrome or Firefox (WebM, Ogg). Safari and the app don’t. Install ffmpeg, e.g. with “brew install ffmpeg”.");
|
|
922
|
+
return (await hasFfmpeg()) ? null : note("macNeedsFfmpegTranscribe");
|
|
924
923
|
}
|
|
925
924
|
/** At start the ways are asked once, so a model the key cannot reach shows before a run meets it. */
|
|
926
925
|
export async function checkModelsAtStart() {
|
|
@@ -958,7 +957,6 @@ export async function modelSettings(lang = "en") {
|
|
|
958
957
|
// --- Checking a key ------------------------------------------------------------------
|
|
959
958
|
/** A cheap authenticated call: whether the provider takes the key. Nothing is generated. */
|
|
960
959
|
export async function checkKey(name, key, lang = "en") {
|
|
961
|
-
const de = lang === "de";
|
|
962
960
|
const requests = {
|
|
963
961
|
openai: ["https://api.openai.com/v1/models", { authorization: `Bearer ${key}` }],
|
|
964
962
|
anthropic: [
|
|
@@ -991,20 +989,14 @@ export async function checkKey(name, key, lang = "en") {
|
|
|
991
989
|
return {
|
|
992
990
|
ok: false,
|
|
993
991
|
message: res.status === 401
|
|
994
|
-
?
|
|
995
|
-
|
|
996
|
-
: "The provider doesn’t recognise this key."
|
|
997
|
-
: de
|
|
998
|
-
? `Der Anbieter antwortet mit Fehler ${res.status}.`
|
|
999
|
-
: `The provider answered with error ${res.status}.`,
|
|
992
|
+
? serverText(lang, "keyUnknown")
|
|
993
|
+
: serverText(lang, "providerError", { status: String(res.status) }),
|
|
1000
994
|
};
|
|
1001
995
|
}
|
|
1002
996
|
catch {
|
|
1003
997
|
return {
|
|
1004
998
|
ok: false,
|
|
1005
|
-
message:
|
|
1006
|
-
? "Der Anbieter ist gerade nicht erreichbar."
|
|
1007
|
-
: "The provider cannot be reached right now.",
|
|
999
|
+
message: serverText(lang, "providerUnreachable"),
|
|
1008
1000
|
};
|
|
1009
1001
|
}
|
|
1010
1002
|
}
|