@oharness/engine 0.8.0 → 0.9.1
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/dist/auth/manager.d.ts +8 -1
- package/dist/auth/manager.js +26 -4
- package/dist/auth/manager.js.map +1 -1
- package/dist/auth/providers/official.js +3 -2
- package/dist/auth/providers/official.js.map +1 -1
- package/dist/auth/store.d.ts +14 -8
- package/dist/auth/store.js +87 -3
- package/dist/auth/store.js.map +1 -1
- package/dist/browser/host-manifest.d.ts +8 -6
- package/dist/browser/host-manifest.js +9 -7
- package/dist/browser/host-manifest.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/harness.js +3 -3
- package/dist/harness.js.map +1 -1
- package/dist/mcp/builtin.d.ts +65 -8
- package/dist/mcp/builtin.js +66 -35
- package/dist/mcp/builtin.js.map +1 -1
- package/dist/mcp/ottoport-updates.d.ts +90 -0
- package/dist/mcp/ottoport-updates.js +228 -0
- package/dist/mcp/ottoport-updates.js.map +1 -0
- package/dist/runtime-plugins/builtin/studio.d.ts +87 -16
- package/dist/runtime-plugins/builtin/studio.js +374 -62
- package/dist/runtime-plugins/builtin/studio.js.map +1 -1
- package/dist/runtime-plugins/host.js +8 -2
- package/dist/runtime-plugins/host.js.map +1 -1
- package/package.json +4 -8
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
|
+
import { mkdir, mkdtemp, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
/** Plain releases only: a pre-release is not something to hand a user unasked. */
|
|
8
|
+
function parse(version) {
|
|
9
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
10
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;
|
|
11
|
+
}
|
|
12
|
+
export function compareVersions(a, b) {
|
|
13
|
+
const left = parse(a) ?? [0, 0, 0];
|
|
14
|
+
const right = parse(b) ?? [0, 0, 0];
|
|
15
|
+
for (let i = 0; i < 3; i++)
|
|
16
|
+
if (left[i] !== right[i])
|
|
17
|
+
return left[i] - right[i];
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
function sameMajor(a, b) {
|
|
21
|
+
const left = parse(a);
|
|
22
|
+
const right = parse(b);
|
|
23
|
+
return !!left && !!right && left[0] === right[0];
|
|
24
|
+
}
|
|
25
|
+
/** The version a package directory says it is, or nothing if it cannot say. */
|
|
26
|
+
export function packageVersion(dir) {
|
|
27
|
+
try {
|
|
28
|
+
const version = JSON.parse(readFileSync(path.join(dir, "package.json"), "utf8")).version;
|
|
29
|
+
return typeof version === "string" ? version : undefined;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The downloaded copies that are whole.
|
|
37
|
+
*
|
|
38
|
+
* A directory counts only when its entry exists and its manifest names the
|
|
39
|
+
* version the directory is called: anything else is a download that was
|
|
40
|
+
* interrupted or tampered with, and running it would fail in a way that looks
|
|
41
|
+
* like the gateway's fault.
|
|
42
|
+
*/
|
|
43
|
+
export function cachedCopies(root) {
|
|
44
|
+
let names;
|
|
45
|
+
try {
|
|
46
|
+
names = readdirSync(root);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const copies = [];
|
|
52
|
+
for (const version of names) {
|
|
53
|
+
if (!parse(version))
|
|
54
|
+
continue;
|
|
55
|
+
const dir = path.join(root, version, "package");
|
|
56
|
+
const entry = path.join(dir, "mcp", "server.mjs");
|
|
57
|
+
if (existsSync(entry) && packageVersion(dir) === version)
|
|
58
|
+
copies.push({ version, entry });
|
|
59
|
+
}
|
|
60
|
+
return copies.sort((a, b) => compareVersions(b.version, a.version));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Which copy to run: the newest cached one the bundled copy is compatible
|
|
64
|
+
* with, or the bundled copy itself.
|
|
65
|
+
*
|
|
66
|
+
* Without a bundled copy there is nothing to measure compatibility against,
|
|
67
|
+
* so nothing is run — the same outcome as an install that dropped the package.
|
|
68
|
+
*/
|
|
69
|
+
export function pickCopy(bundled, cached) {
|
|
70
|
+
if (!bundled)
|
|
71
|
+
return undefined;
|
|
72
|
+
let best = bundled;
|
|
73
|
+
for (const copy of cached) {
|
|
74
|
+
if (sameMajor(copy.version, bundled.version) && compareVersions(copy.version, best.version) > 0)
|
|
75
|
+
best = copy;
|
|
76
|
+
}
|
|
77
|
+
return best;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether to look for updates at all.
|
|
81
|
+
*
|
|
82
|
+
* A CI run or a test is not a user, and a network call from one is either
|
|
83
|
+
* noise or a flaky test — so both are off unless asked for by name.
|
|
84
|
+
*/
|
|
85
|
+
export function autoUpdateEnabled(env = process.env) {
|
|
86
|
+
const flag = env["OHARNESS_MEDIA_AUTO_UPDATE"];
|
|
87
|
+
if (flag === "0" || flag === "false")
|
|
88
|
+
return false;
|
|
89
|
+
if (flag === "1" || flag === "true")
|
|
90
|
+
return true;
|
|
91
|
+
return !env["CI"] && !env["NODE_TEST_CONTEXT"];
|
|
92
|
+
}
|
|
93
|
+
/** Enough time to ask; the registry answering slowly is no reason to wait on it. */
|
|
94
|
+
const REGISTRY_TIMEOUT_MS = 5_000;
|
|
95
|
+
/** The package is about a hundred kilobytes; a minute is generous for it. */
|
|
96
|
+
const DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
97
|
+
/** The running version plus the one before it, in case the newest is bad. */
|
|
98
|
+
const KEEP = 2;
|
|
99
|
+
const run = promisify(execFile);
|
|
100
|
+
/**
|
|
101
|
+
* `tar`, as the system ships it.
|
|
102
|
+
*
|
|
103
|
+
* macOS and Linux have always had it, and Windows has shipped bsdtar as
|
|
104
|
+
* `tar.exe` since 10 — which is everything the desktop app supports. A JS
|
|
105
|
+
* implementation would be a dependency for a job the OS already does.
|
|
106
|
+
*/
|
|
107
|
+
async function systemTar(archive, into) {
|
|
108
|
+
await run("tar", ["-xzf", archive, "-C", into]);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Whether these bytes are the ones the registry vouched for.
|
|
112
|
+
*
|
|
113
|
+
* Only sha512 is accepted. The registry has published it for every package
|
|
114
|
+
* for years, and an entry offering only something weaker is not one to run as
|
|
115
|
+
* a subprocess holding the user's key.
|
|
116
|
+
*/
|
|
117
|
+
export function matchesIntegrity(bytes, integrity) {
|
|
118
|
+
const digest = createHash("sha512").update(bytes).digest("base64");
|
|
119
|
+
const claimed = integrity
|
|
120
|
+
.split(/\s+/)
|
|
121
|
+
.filter((part) => part.startsWith("sha512-"))
|
|
122
|
+
.map((part) => part.slice("sha512-".length).split("?")[0]);
|
|
123
|
+
return claimed.length > 0 && claimed.includes(digest);
|
|
124
|
+
}
|
|
125
|
+
function timed(signal, ms) {
|
|
126
|
+
return signal ? AbortSignal.any([signal, AbortSignal.timeout(ms)]) : AbortSignal.timeout(ms);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Fetch the newest compatible release into the cache, if there is one.
|
|
130
|
+
*
|
|
131
|
+
* Resolves rather than throws in every case: the caller fires this and moves
|
|
132
|
+
* on, and a failure here must never be noticed as anything but the bundled
|
|
133
|
+
* server continuing to run.
|
|
134
|
+
*/
|
|
135
|
+
export async function updateOttoport(options) {
|
|
136
|
+
if (!options.force && !autoUpdateEnabled(options.env))
|
|
137
|
+
return { status: "disabled" };
|
|
138
|
+
const doFetch = options.fetch ?? fetch;
|
|
139
|
+
const registry = options.registry ?? "https://registry.npmjs.org";
|
|
140
|
+
let staging;
|
|
141
|
+
try {
|
|
142
|
+
const response = await doFetch(`${registry}/ottoport/latest`, {
|
|
143
|
+
signal: timed(options.signal, REGISTRY_TIMEOUT_MS),
|
|
144
|
+
headers: { accept: "application/json" },
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok)
|
|
147
|
+
throw new Error(`registry answered HTTP ${response.status}`);
|
|
148
|
+
const latest = (await response.json());
|
|
149
|
+
const version = typeof latest.version === "string" ? latest.version : "";
|
|
150
|
+
// Also what makes the version safe to use as a directory name.
|
|
151
|
+
if (!parse(version))
|
|
152
|
+
throw new Error(`registry offered an unusable version: ${String(latest.version)}`);
|
|
153
|
+
if (compareVersions(version, options.current) <= 0)
|
|
154
|
+
return { status: "current", version };
|
|
155
|
+
if (!sameMajor(version, options.current))
|
|
156
|
+
return { status: "incompatible", version };
|
|
157
|
+
if (cachedCopies(options.root).some((copy) => copy.version === version))
|
|
158
|
+
return { status: "cached", version };
|
|
159
|
+
const tarball = latest.dist?.tarball;
|
|
160
|
+
const integrity = latest.dist?.integrity;
|
|
161
|
+
if (typeof tarball !== "string" || !/^https:\/\//.test(tarball))
|
|
162
|
+
throw new Error("registry entry has no https tarball");
|
|
163
|
+
if (typeof integrity !== "string")
|
|
164
|
+
throw new Error("registry entry has no integrity");
|
|
165
|
+
const download = await doFetch(tarball, { signal: timed(options.signal, DOWNLOAD_TIMEOUT_MS) });
|
|
166
|
+
if (!download.ok)
|
|
167
|
+
throw new Error(`tarball download answered HTTP ${download.status}`);
|
|
168
|
+
const bytes = Buffer.from(await download.arrayBuffer());
|
|
169
|
+
// Before a single byte is unpacked: a tarball that is not the one the
|
|
170
|
+
// registry published is not opened at all.
|
|
171
|
+
if (!matchesIntegrity(bytes, integrity))
|
|
172
|
+
throw new Error(`integrity mismatch for ottoport ${version}`);
|
|
173
|
+
await mkdir(options.root, { recursive: true });
|
|
174
|
+
// Inside the cache root, so the final rename never crosses a filesystem
|
|
175
|
+
// and a directory named for a version is only ever a complete one.
|
|
176
|
+
staging = await mkdtemp(path.join(options.root, ".staging-"));
|
|
177
|
+
const archive = path.join(staging, "package.tgz");
|
|
178
|
+
await writeFile(archive, bytes);
|
|
179
|
+
await (options.extract ?? systemTar)(archive, staging);
|
|
180
|
+
await rm(archive, { force: true });
|
|
181
|
+
const dir = path.join(staging, "package");
|
|
182
|
+
if (!existsSync(path.join(dir, "mcp", "server.mjs")))
|
|
183
|
+
throw new Error("the package has no mcp/server.mjs");
|
|
184
|
+
if (packageVersion(dir) !== version)
|
|
185
|
+
throw new Error("the package is not the version the registry named");
|
|
186
|
+
const target = path.join(options.root, version);
|
|
187
|
+
try {
|
|
188
|
+
await rename(staging, target);
|
|
189
|
+
staging = undefined;
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
// Another session got there first, which is the same outcome.
|
|
193
|
+
if (cachedCopies(options.root).some((copy) => copy.version === version))
|
|
194
|
+
return { status: "cached", version };
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
await prune(options.root);
|
|
198
|
+
return { status: "installed", version };
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
return { status: "failed", error: err instanceof Error ? err.message : String(err) };
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
if (staging)
|
|
205
|
+
await rm(staging, { recursive: true, force: true }).catch(() => { });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Drop all but the newest few versions, and staging left by a crash.
|
|
210
|
+
*
|
|
211
|
+
* Staging is only cleared once it is an hour old, since a younger one may be
|
|
212
|
+
* another session's download still in progress.
|
|
213
|
+
*/
|
|
214
|
+
async function prune(root) {
|
|
215
|
+
const keep = new Set(cachedCopies(root).slice(0, KEEP).map((copy) => copy.version));
|
|
216
|
+
for (const name of await readdir(root).catch(() => [])) {
|
|
217
|
+
const full = path.join(root, name);
|
|
218
|
+
if (name.startsWith(".staging-")) {
|
|
219
|
+
const age = Date.now() - (await stat(full).then((s) => s.mtimeMs).catch(() => Date.now()));
|
|
220
|
+
if (age > 3_600_000)
|
|
221
|
+
await rm(full, { recursive: true, force: true }).catch(() => { });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (parse(name) && !keep.has(name))
|
|
225
|
+
await rm(full, { recursive: true, force: true }).catch(() => { });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=ottoport-updates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ottoport-updates.js","sourceRoot":"","sources":["../../src/mcp/ottoport-updates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AA6BtC,kFAAkF;AAClF,SAAS,KAAK,CAAC,OAAe;IAC5B,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,CAAS,EAAE,CAAS;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAAE,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IAClF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,MAAM,OAAO,GAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAA2B,CAAC,OAAO,CAAC;QACpH,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,SAAS;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAClD,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAiC,EAAE,MAA+B;IACzF,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC;IAC/G,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACpE,MAAM,IAAI,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC/C,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACnD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACjD,CAAC;AAsBD,oFAAoF;AACpF,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAClC,6EAA6E;AAC7E,MAAM,mBAAmB,GAAG,MAAM,CAAC;AACnC,6EAA6E;AAC7E,MAAM,IAAI,GAAG,CAAC,CAAC;AAEf,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEhC;;;;;;GAMG;AACH,KAAK,UAAU,SAAS,CAAC,OAAe,EAAE,IAAY;IACpD,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,SAAiB;IAC/D,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnE,MAAM,OAAO,GAAG,SAAS;SACtB,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;SAC5C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,KAAK,CAAC,MAA+B,EAAE,EAAU;IACxD,OAAO,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAsB;IACzD,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACrF,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,4BAA4B,CAAC;IAClE,IAAI,OAA2B,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,QAAQ,kBAAkB,EAAE;YAC5D,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,mBAAmB,CAAC;YAClD,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA6E,CAAC;QACnH,MAAM,OAAO,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,+DAA+D;QAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACxG,IAAI,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;QAC1F,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;QACrF,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,OAAO,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAE9G,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC;QACrC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC;QACzC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACxH,IAAI,OAAO,SAAS,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAEtF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAChG,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACvF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACxD,sEAAsE;QACtE,2CAA2C;QAC3C,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,OAAO,EAAE,CAAC,CAAC;QAEvG,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,wEAAwE;QACxE,mEAAmE;QACnE,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAClD,MAAM,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAEnC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAC3G,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAE1G,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC9B,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,8DAA8D;YAC9D,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,OAAO,CAAC;gBAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;YAC9G,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACvF,CAAC;YAAS,CAAC;QACT,IAAI,OAAO;YAAE,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,KAAK,CAAC,IAAY;IAC/B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACpF,KAAK,MAAM,IAAI,IAAI,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAc,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC3F,IAAI,GAAG,GAAG,SAAS;gBAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACtF,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACvG,CAAC;AACH,CAAC"}
|
|
@@ -1,33 +1,104 @@
|
|
|
1
|
+
import type { MediaAccess } from "../../mcp/builtin.ts";
|
|
2
|
+
import type { AuthManager } from "../../auth/manager.ts";
|
|
1
3
|
import type { McpToolDefinition } from "../../mcp/protocol.ts";
|
|
2
4
|
import type { Tool, ToolContext } from "../../tools/types.ts";
|
|
3
5
|
import type { RuntimePlugin } from "../types.ts";
|
|
4
6
|
export interface StudioPluginOptions {
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
+
/** Who calls are made as, and where they go. Asked before every call. */
|
|
8
|
+
access(): Promise<MediaAccess | undefined>;
|
|
7
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The credential media calls go out on: the OHarness account, and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* Media is billed only through our gateway, which charges the account (or the
|
|
14
|
+
* team it bills to) in OHarness credits the way it charges chat. A vendor key
|
|
15
|
+
* the user happens to hold for chat is deliberately not used here: two ways to
|
|
16
|
+
* pay for the same image would be two balances to explain, and a generation
|
|
17
|
+
* the user expected on their plan would land on a different bill.
|
|
18
|
+
*
|
|
19
|
+
* The team rides in the path because the server offers nowhere else: all a
|
|
20
|
+
* caller sets is a base URL and a key.
|
|
21
|
+
*/
|
|
22
|
+
export declare function mediaAccess(auth: AuthManager): Promise<MediaAccess | undefined>;
|
|
8
23
|
export declare function createStudioPlugin(options: StudioPluginOptions): RuntimePlugin;
|
|
24
|
+
/** What the model is told when media exists but the user is not signed in. */
|
|
25
|
+
export declare const SIGN_IN_PROMPT: string;
|
|
26
|
+
/** One upstream tool: what it is called here, and what it produces. */
|
|
27
|
+
export interface FacadeEntry {
|
|
28
|
+
upstream: string;
|
|
29
|
+
name: string;
|
|
30
|
+
/**
|
|
31
|
+
* What generated files are named after; empty for a tool that only reads,
|
|
32
|
+
* which is also what makes it skip confirmation and download nothing.
|
|
33
|
+
*/
|
|
34
|
+
kind: string;
|
|
35
|
+
note: string;
|
|
36
|
+
}
|
|
9
37
|
/**
|
|
10
|
-
*
|
|
38
|
+
* The tools this file knows by name, and what to say about each.
|
|
11
39
|
*
|
|
12
|
-
* The rename
|
|
13
|
-
* schema comes from the server, so a model can send anything that server
|
|
40
|
+
* The rename and the note are the whole of it. Parameters are not restated:
|
|
41
|
+
* the schema comes from the server, so a model can send anything that server
|
|
14
42
|
* accepts and a parameter added upstream works the day it ships. Writing them
|
|
15
43
|
* out here would be a second, staler copy of somebody else's list — and the
|
|
16
44
|
* failure mode is silent, since a parameter this file has not heard of would
|
|
17
45
|
* simply never reach the gateway.
|
|
46
|
+
*
|
|
47
|
+
* A tool missing from here is still offered; see `facadeEntry`.
|
|
18
48
|
*/
|
|
19
|
-
export declare const FACADE:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
49
|
+
export declare const FACADE: FacadeEntry[];
|
|
50
|
+
/**
|
|
51
|
+
* The name a tool is offered under: the known one, or one derived from the
|
|
52
|
+
* upstream name with the vendor taken off — `ottoport_remove_background`
|
|
53
|
+
* becomes `RemoveBackground`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function facadeName(upstream: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* How to present a tool, whether or not this file has heard of it.
|
|
58
|
+
*
|
|
59
|
+
* An unknown tool is assumed to spend money unless its name says it only
|
|
60
|
+
* looks something up. Guessing the other way would let a new paid tool skip
|
|
61
|
+
* confirmation, and the cost of that guess is a charge the user never agreed
|
|
62
|
+
* to; guessing this way costs one extra click on a lookup.
|
|
63
|
+
*/
|
|
64
|
+
export declare function facadeEntry(upstream: string): FacadeEntry;
|
|
65
|
+
/**
|
|
66
|
+
* A server-written description, in this harness's words.
|
|
67
|
+
*
|
|
68
|
+
* The vendor's name is not part of the product, and the server's text is full
|
|
69
|
+
* of it — "through an OttoPort image model", "call ottoport_list_models".
|
|
70
|
+
* Left alone, the second is worse than cosmetic: it names a tool the model
|
|
71
|
+
* does not have, under the name it is offered as here. So tool references
|
|
72
|
+
* become the names they are offered under, the vendor's name is dropped, and
|
|
73
|
+
* a sentence about something withheld or refused is dropped with it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function rebrand(text: string): string;
|
|
76
|
+
/**
|
|
77
|
+
* The server's input schema, as this harness offers it: descriptions in its
|
|
78
|
+
* words at every depth, and without the parameters the gateway refuses.
|
|
79
|
+
*/
|
|
80
|
+
export declare function facadeSchema(inputSchema: unknown): {
|
|
81
|
+
properties?: Record<string, unknown>;
|
|
82
|
+
[k: string]: unknown;
|
|
83
|
+
};
|
|
25
84
|
/**
|
|
26
85
|
* One upstream tool, wearing this harness's name and manners.
|
|
27
86
|
*
|
|
28
|
-
* `inputSchema` is the server's own, with `path` added. Nothing
|
|
29
|
-
* here: the server validates its own inputs, and a second
|
|
30
|
-
* its JSON Schema would only be a less accurate one —
|
|
31
|
-
* MCP tool adapter states.
|
|
87
|
+
* `inputSchema` is the server's own, rebranded, with `path` added. Nothing
|
|
88
|
+
* validates it here: the server validates its own inputs, and a second
|
|
89
|
+
* opinion derived from its JSON Schema would only be a less accurate one —
|
|
90
|
+
* the same reasoning the MCP tool adapter states.
|
|
91
|
+
*/
|
|
92
|
+
export declare function facadeTool(entry: FacadeEntry, def: McpToolDefinition, call: (tool: string, args: Record<string, unknown>, ctx: ToolContext) => Promise<string>): Tool;
|
|
93
|
+
/**
|
|
94
|
+
* Where one output goes: into a folder, or under a name.
|
|
95
|
+
*
|
|
96
|
+
* A model asked for "a kitten picture" passes `media/kitten.png` as often as
|
|
97
|
+
* `media/` — and treating every path as a folder saved that one as
|
|
98
|
+
* `media/kitten.png/image-….jpg`, a folder named like an image with the image
|
|
99
|
+
* inside it. A path whose last part has an extension is a file name. The
|
|
100
|
+
* extension kept is the one the bytes have, because renaming a JPEG to .png
|
|
101
|
+
* makes a file that some viewers refuse; the name asked for is kept. A second
|
|
102
|
+
* output of the same call is numbered beside the first.
|
|
32
103
|
*/
|
|
33
|
-
export declare function
|
|
104
|
+
export declare function destination(into: string | undefined, kind: string, index: number, extension: string): string;
|