@tomradex/plugin-sdk 0.2.0 → 0.2.16
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/README.md +1 -1
- package/dist/cli-hooks.d.ts +8 -0
- package/dist/cli-hooks.js +27 -0
- package/dist/contract.d.ts +292 -9
- package/dist/exports.d.ts +10 -0
- package/dist/exports.js +73 -0
- package/dist/im-markdown.d.ts +10 -0
- package/dist/im-markdown.js +257 -0
- package/dist/im.d.ts +319 -0
- package/dist/im.js +55 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -1
- package/dist/language.d.ts +48 -0
- package/dist/language.js +62 -0
- package/dist/manifest.d.ts +78 -10
- package/dist/manifest.js +80 -13
- package/dist/models.d.ts +219 -0
- package/dist/models.js +2 -0
- package/dist/terminals.d.ts +72 -0
- package/dist/terminals.js +2 -0
- package/dist/web-search.d.ts +54 -0
- package/dist/web-search.js +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type Dictionaries } from "@tomradex/domain";
|
|
3
|
+
import type { Locale } from "./contract.js";
|
|
4
|
+
export { languageCoverage, pickLanguage, slotsOf, type Dictionaries, type LanguageCoverage } from "@tomradex/domain";
|
|
5
|
+
/**
|
|
6
|
+
* A plugin's own words in the app's language (`ctx.settings.locale()`): the
|
|
7
|
+
* closest language it has, picked as Tomrade picks its own (zh-TW finds
|
|
8
|
+
* zh-Hant, never zh), and English when it has none of that language.
|
|
9
|
+
*/
|
|
10
|
+
export declare function wordsFor<T>(locale: Locale, words: {
|
|
11
|
+
en: T;
|
|
12
|
+
} & Partial<Record<Locale, T>>): T;
|
|
13
|
+
/**
|
|
14
|
+
* A language pack: data only. The manifest names each language a plugin adds
|
|
15
|
+
* and the JSON file holding its words; nothing in it runs, and every string
|
|
16
|
+
* is shown as plain text in place of the English it translates.
|
|
17
|
+
*/
|
|
18
|
+
/** The largest pack file Tomrade reads, and the longest single template. */
|
|
19
|
+
export declare const LANGUAGE_PACK_LIMITS: {
|
|
20
|
+
fileBytes: number;
|
|
21
|
+
templateChars: number;
|
|
22
|
+
languages: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const LanguageEntrySchema: z.ZodObject<{
|
|
25
|
+
tag: z.ZodString;
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
file: z.ZodString;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
export type LanguageEntry = z.output<typeof LanguageEntrySchema>;
|
|
30
|
+
/**
|
|
31
|
+
* What a pack file holds: `ui` translates the interface's dictionaries,
|
|
32
|
+
* `backend` the backend's books, each `namespace → key → template`, keyed as
|
|
33
|
+
* English is. A template names its values as `{{slot}}`, exactly the slots
|
|
34
|
+
* English's does; an entry that does not, or a key English lacks, reads English.
|
|
35
|
+
*/
|
|
36
|
+
export declare const LanguagePackFileSchema: z.ZodObject<{
|
|
37
|
+
ui: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
38
|
+
backend: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
export interface LanguagePack {
|
|
41
|
+
tag: string;
|
|
42
|
+
name: string;
|
|
43
|
+
ui: Dictionaries;
|
|
44
|
+
backend: Dictionaries;
|
|
45
|
+
}
|
|
46
|
+
/** A pack file's text checked against the format; throws with the first problem. */
|
|
47
|
+
export declare function parseLanguagePack(entry: LanguageEntry, text: string): LanguagePack;
|
|
48
|
+
//# sourceMappingURL=language.d.ts.map
|
package/dist/language.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { canonicalTag, pickLanguage } from "@tomradex/domain";
|
|
3
|
+
export { languageCoverage, pickLanguage, slotsOf } from "@tomradex/domain";
|
|
4
|
+
/**
|
|
5
|
+
* A plugin's own words in the app's language (`ctx.settings.locale()`): the
|
|
6
|
+
* closest language it has, picked as Tomrade picks its own (zh-TW finds
|
|
7
|
+
* zh-Hant, never zh), and English when it has none of that language.
|
|
8
|
+
*/
|
|
9
|
+
export function wordsFor(locale, words) {
|
|
10
|
+
return words[pickLanguage(locale, Object.keys(words)) ?? "en"] ?? words.en;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A language pack: data only. The manifest names each language a plugin adds
|
|
14
|
+
* and the JSON file holding its words; nothing in it runs, and every string
|
|
15
|
+
* is shown as plain text in place of the English it translates.
|
|
16
|
+
*/
|
|
17
|
+
/** The largest pack file Tomrade reads, and the longest single template. */
|
|
18
|
+
export const LANGUAGE_PACK_LIMITS = { fileBytes: 2 * 1024 * 1024, templateChars: 4_000, languages: 16 };
|
|
19
|
+
export const LanguageEntrySchema = z.object({
|
|
20
|
+
/** A BCP 47 tag: "pt-BR", "ko", "zh-Hant". */
|
|
21
|
+
tag: z
|
|
22
|
+
.string()
|
|
23
|
+
.refine((t) => canonicalTag(t) === t, "a canonical BCP 47 language tag such as \"pt-BR\" or \"ko\"")
|
|
24
|
+
.refine((t) => t !== "en", "English is what every pack translates; it cannot be replaced"),
|
|
25
|
+
/** The language's name in itself, as Settings → Language lists it: "Português (Brasil)". */
|
|
26
|
+
name: z.string().trim().min(1).max(60),
|
|
27
|
+
/** The pack's words, a JSON file inside the plugin: "languages/pt-BR.json". */
|
|
28
|
+
file: z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^[A-Za-z0-9_-][A-Za-z0-9._/-]*\.json$/, "a relative path to a .json file inside the plugin")
|
|
31
|
+
.refine((f) => !f.split("/").includes(".."), "the file must stay inside the plugin")
|
|
32
|
+
});
|
|
33
|
+
const Words = z.record(z.string().min(1), z.record(z.string().min(1), z.string().max(LANGUAGE_PACK_LIMITS.templateChars)));
|
|
34
|
+
/**
|
|
35
|
+
* What a pack file holds: `ui` translates the interface's dictionaries,
|
|
36
|
+
* `backend` the backend's books, each `namespace → key → template`, keyed as
|
|
37
|
+
* English is. A template names its values as `{{slot}}`, exactly the slots
|
|
38
|
+
* English's does; an entry that does not, or a key English lacks, reads English.
|
|
39
|
+
*/
|
|
40
|
+
export const LanguagePackFileSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
ui: Words.default({}),
|
|
43
|
+
backend: Words.default({})
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
/** A pack file's text checked against the format; throws with the first problem. */
|
|
47
|
+
export function parseLanguagePack(entry, text) {
|
|
48
|
+
let raw;
|
|
49
|
+
try {
|
|
50
|
+
raw = JSON.parse(text);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new Error(`${entry.file} is not JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
}
|
|
55
|
+
const parsed = LanguagePackFileSchema.safeParse(raw);
|
|
56
|
+
if (!parsed.success) {
|
|
57
|
+
const issue = parsed.error.issues[0];
|
|
58
|
+
throw new Error(`${entry.file} ${issue.path.join(".") || "(root)"}: ${issue.message}`);
|
|
59
|
+
}
|
|
60
|
+
return { tag: entry.tag, name: entry.name, ui: parsed.data.ui, backend: parsed.data.backend };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=language.js.map
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/** The contract this SDK describes. A plugin's `engines.sdk` range is checked against the host's. */
|
|
3
|
-
export declare const SDK_CONTRACT = "0.2.
|
|
3
|
+
export declare const SDK_CONTRACT = "0.2.16";
|
|
4
4
|
export declare const PLUGIN_ID: RegExp;
|
|
5
5
|
export declare const NAMESPACE: RegExp;
|
|
6
6
|
/** The app's own names, and the official publisher's. */
|
|
@@ -9,12 +9,20 @@ export declare const OFFICIAL_PUBLISHER = "tomrade";
|
|
|
9
9
|
export declare const PermissionsSchema: z.ZodDefault<z.ZodObject<{
|
|
10
10
|
network: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
11
11
|
fs: z.ZodDefault<z.ZodEnum<{
|
|
12
|
+
none: "none";
|
|
12
13
|
project: "project";
|
|
13
|
-
plugin: "plugin";
|
|
14
14
|
any: "any";
|
|
15
|
-
|
|
15
|
+
plugin: "plugin";
|
|
16
16
|
}>>;
|
|
17
17
|
exec: z.ZodDefault<z.ZodBoolean>;
|
|
18
|
+
window: z.ZodDefault<z.ZodBoolean>;
|
|
19
|
+
why: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
network: z.ZodOptional<z.ZodString>;
|
|
21
|
+
fs: z.ZodOptional<z.ZodString>;
|
|
22
|
+
exec: z.ZodOptional<z.ZodString>;
|
|
23
|
+
window: z.ZodOptional<z.ZodString>;
|
|
24
|
+
full: z.ZodOptional<z.ZodString>;
|
|
25
|
+
}, z.core.$strip>>;
|
|
18
26
|
}, z.core.$strip>>;
|
|
19
27
|
export declare const ContributesSchema: z.ZodObject<{
|
|
20
28
|
tools: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
@@ -23,11 +31,30 @@ export declare const ContributesSchema: z.ZodObject<{
|
|
|
23
31
|
statusItems: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
24
32
|
sections: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
25
33
|
graphs: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
34
|
+
sidebarModules: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
26
35
|
playbooks: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
27
36
|
settings: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
28
37
|
surfaces: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
29
38
|
tables: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
39
|
+
imAdapters: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
40
|
+
webSearchProviders: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
41
|
+
agentHooks: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
42
|
+
modelProviders: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
/**
|
|
45
|
+
* One row a full-mode plugin lays over Tomrade's composition, merged by id like a
|
|
46
|
+
* `tomrade.patch.yml` row: an app row's id switches it off, replaces its plugin or
|
|
47
|
+
* changes its config; a new id (carrying the namespace) adds a row. A relative
|
|
48
|
+
* `name` is a file inside the package.
|
|
49
|
+
*/
|
|
50
|
+
export declare const RowSchema: z.ZodObject<{
|
|
51
|
+
id: z.ZodString;
|
|
52
|
+
name: z.ZodOptional<z.ZodString>;
|
|
53
|
+
title: z.ZodOptional<z.ZodString>;
|
|
54
|
+
config: z.ZodOptional<z.ZodUnknown>;
|
|
55
|
+
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
30
56
|
}, z.core.$strip>;
|
|
57
|
+
export type ManifestRow = z.output<typeof RowSchema>;
|
|
31
58
|
export declare const ManifestSchema: z.ZodObject<{
|
|
32
59
|
id: z.ZodString;
|
|
33
60
|
namespace: z.ZodOptional<z.ZodString>;
|
|
@@ -43,18 +70,43 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
43
70
|
}, z.core.$strip>>;
|
|
44
71
|
engines: z.ZodObject<{
|
|
45
72
|
sdk: z.ZodString;
|
|
73
|
+
tomrade: z.ZodOptional<z.ZodString>;
|
|
46
74
|
}, z.core.$strip>;
|
|
47
|
-
|
|
75
|
+
context: z.ZodDefault<z.ZodEnum<{
|
|
76
|
+
full: "full";
|
|
77
|
+
sdk: "sdk";
|
|
78
|
+
}>>;
|
|
79
|
+
rows: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
80
|
+
id: z.ZodString;
|
|
81
|
+
name: z.ZodOptional<z.ZodString>;
|
|
82
|
+
title: z.ZodOptional<z.ZodString>;
|
|
83
|
+
config: z.ZodOptional<z.ZodUnknown>;
|
|
84
|
+
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
85
|
+
}, z.core.$strip>>>;
|
|
86
|
+
main: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
48
87
|
icon: z.ZodOptional<z.ZodString>;
|
|
88
|
+
languages: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
89
|
+
tag: z.ZodString;
|
|
90
|
+
name: z.ZodString;
|
|
91
|
+
file: z.ZodString;
|
|
92
|
+
}, z.core.$strip>>>;
|
|
49
93
|
permissions: z.ZodDefault<z.ZodObject<{
|
|
50
94
|
network: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
51
95
|
fs: z.ZodDefault<z.ZodEnum<{
|
|
96
|
+
none: "none";
|
|
52
97
|
project: "project";
|
|
53
|
-
plugin: "plugin";
|
|
54
98
|
any: "any";
|
|
55
|
-
|
|
99
|
+
plugin: "plugin";
|
|
56
100
|
}>>;
|
|
57
101
|
exec: z.ZodDefault<z.ZodBoolean>;
|
|
102
|
+
window: z.ZodDefault<z.ZodBoolean>;
|
|
103
|
+
why: z.ZodOptional<z.ZodObject<{
|
|
104
|
+
network: z.ZodOptional<z.ZodString>;
|
|
105
|
+
fs: z.ZodOptional<z.ZodString>;
|
|
106
|
+
exec: z.ZodOptional<z.ZodString>;
|
|
107
|
+
window: z.ZodOptional<z.ZodString>;
|
|
108
|
+
full: z.ZodOptional<z.ZodString>;
|
|
109
|
+
}, z.core.$strip>>;
|
|
58
110
|
}, z.core.$strip>>;
|
|
59
111
|
inject: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
60
112
|
items: "items";
|
|
@@ -68,7 +120,12 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
68
120
|
contributions: "contributions";
|
|
69
121
|
agent: "agent";
|
|
70
122
|
surfaces: "surfaces";
|
|
123
|
+
alerts: "alerts";
|
|
71
124
|
oplog: "oplog";
|
|
125
|
+
im: "im";
|
|
126
|
+
webSearch: "webSearch";
|
|
127
|
+
terminals: "terminals";
|
|
128
|
+
models: "models";
|
|
72
129
|
}>>>;
|
|
73
130
|
optional: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
74
131
|
items: "items";
|
|
@@ -82,7 +139,12 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
82
139
|
contributions: "contributions";
|
|
83
140
|
agent: "agent";
|
|
84
141
|
surfaces: "surfaces";
|
|
142
|
+
alerts: "alerts";
|
|
85
143
|
oplog: "oplog";
|
|
144
|
+
im: "im";
|
|
145
|
+
webSearch: "webSearch";
|
|
146
|
+
terminals: "terminals";
|
|
147
|
+
models: "models";
|
|
86
148
|
}>>>;
|
|
87
149
|
contributes: z.ZodDefault<z.ZodObject<{
|
|
88
150
|
tools: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
@@ -91,10 +153,15 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
91
153
|
statusItems: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
92
154
|
sections: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
93
155
|
graphs: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
156
|
+
sidebarModules: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
94
157
|
playbooks: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
95
158
|
settings: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
96
159
|
surfaces: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
97
160
|
tables: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
161
|
+
imAdapters: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
162
|
+
webSearchProviders: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
163
|
+
agentHooks: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
164
|
+
modelProviders: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
98
165
|
}, z.core.$strip>>;
|
|
99
166
|
authoredBy: z.ZodDefault<z.ZodEnum<{
|
|
100
167
|
agent: "agent";
|
|
@@ -120,14 +187,15 @@ export declare function assertOwned(namespace: string, id: string, kind: string)
|
|
|
120
187
|
export type RiskLevel = "low" | "elevated" | "high";
|
|
121
188
|
export interface Risk {
|
|
122
189
|
level: RiskLevel;
|
|
123
|
-
/** Stable keys the UI words: `exec`, `fs-any`, `network-any`, `network`, `fs-project`, `db`. */
|
|
190
|
+
/** Stable keys the UI words: `full`, `rows`, `exec`, `fs-any`, `network-any`, `network`, `fs-project`, `db`, `models`, `window`. */
|
|
124
191
|
reasons: string[];
|
|
125
192
|
}
|
|
126
193
|
/**
|
|
127
|
-
* High risk is labelled, not refused: running
|
|
128
|
-
*
|
|
194
|
+
* High risk is labelled, not refused: running with Tomrade's whole context or
|
|
195
|
+
* changing its own rows, running programs, reading anywhere on disk, or talking
|
|
196
|
+
* to any host. Elevated is what a person should still see.
|
|
129
197
|
*/
|
|
130
|
-
export declare function riskOf(manifest: Pick<Manifest, "permissions" | "inject" | "optional">): Risk;
|
|
198
|
+
export declare function riskOf(manifest: Pick<Manifest, "permissions" | "inject" | "optional"> & Partial<Pick<Manifest, "context" | "rows">>): Risk;
|
|
131
199
|
/** OSI-approved licenses the store accepts without a second look. */
|
|
132
200
|
export declare const OSI_LICENSES: Set<string>;
|
|
133
201
|
export declare const isOpenSource: (license: string) => boolean;
|
package/dist/manifest.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { LANGUAGE_PACK_LIMITS, LanguageEntrySchema } from "./language.js";
|
|
2
3
|
import { isRange, isVersion } from "./semver.js";
|
|
3
4
|
/** The contract this SDK describes. A plugin's `engines.sdk` range is checked against the host's. */
|
|
4
|
-
export const SDK_CONTRACT = "0.2.
|
|
5
|
+
export const SDK_CONTRACT = "0.2.16";
|
|
5
6
|
export const PLUGIN_ID = /^[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9][a-z0-9-]{0,63}$/;
|
|
6
7
|
export const NAMESPACE = /^[a-z][a-z0-9_]{1,31}$/;
|
|
7
8
|
/** The app's own names, and the official publisher's. */
|
|
8
9
|
export const RESERVED_NAMESPACES = new Set([
|
|
9
|
-
"agent", "archify", "boards", "code_link", "contributions", "core", "db", "deps", "deps_graph", "fields", "files", "hub", "ipc", "items",
|
|
10
|
-
"llm", "mcp", "oplog", "pet", "plugin", "plugins", "projects", "repos", "settings", "storage", "surfaces", "system", "tomrade", "tools", "views", "write"
|
|
10
|
+
"agent", "alerts", "archify", "boards", "code_link", "contributions", "core", "db", "deps", "deps_graph", "fields", "files", "hub", "im", "ipc", "items",
|
|
11
|
+
"llm", "mcp", "oplog", "pet", "plugin", "plugins", "pomodoro", "projects", "repos", "settings", "storage", "surfaces", "system", "tomrade", "tools", "views", "write"
|
|
11
12
|
]);
|
|
12
13
|
export const OFFICIAL_PUBLISHER = "tomrade";
|
|
13
|
-
const SERVICE = z.enum(["items", "fields", "boards", "views", "projects", "db", "settings", "tools", "contributions", "agent", "surfaces", "oplog"]);
|
|
14
|
+
const SERVICE = z.enum(["items", "fields", "boards", "views", "projects", "db", "settings", "tools", "contributions", "agent", "surfaces", "alerts", "oplog", "im", "webSearch", "terminals", "models"]);
|
|
14
15
|
export const PermissionsSchema = z
|
|
15
16
|
.object({
|
|
16
17
|
/** Hosts the plugin talks to; `*` means any. */
|
|
@@ -18,9 +19,13 @@ export const PermissionsSchema = z
|
|
|
18
19
|
/** `plugin`: its own data directory. `project`: the project folders. `any`: anywhere on disk. */
|
|
19
20
|
fs: z.enum(["none", "plugin", "project", "any"]).default("none"),
|
|
20
21
|
/** Starts other programs. */
|
|
21
|
-
exec: z.boolean().default(false)
|
|
22
|
+
exec: z.boolean().default(false),
|
|
23
|
+
/** SDK 0.2.11: opens working windows of its own (a surface of kind "window"), which take keys like any app window. */
|
|
24
|
+
window: z.boolean().default(false),
|
|
25
|
+
/** Why the plugin needs a wide permission, in a sentence a person installing it can judge. Official plugins must say. */
|
|
26
|
+
why: z.object({ network: z.string().min(1), fs: z.string().min(1), exec: z.string().min(1), window: z.string().min(1), full: z.string().min(1) }).partial().optional()
|
|
22
27
|
})
|
|
23
|
-
.default({ network: [], fs: "none", exec: false });
|
|
28
|
+
.default({ network: [], fs: "none", exec: false, window: false });
|
|
24
29
|
export const ContributesSchema = z
|
|
25
30
|
.object({
|
|
26
31
|
tools: z.array(z.string()).default([]),
|
|
@@ -29,12 +34,34 @@ export const ContributesSchema = z
|
|
|
29
34
|
statusItems: z.array(z.string()).default([]),
|
|
30
35
|
sections: z.array(z.string()).default([]),
|
|
31
36
|
graphs: z.array(z.string()).default([]),
|
|
37
|
+
sidebarModules: z.array(z.string()).default([]),
|
|
32
38
|
playbooks: z.array(z.string()).default([]),
|
|
33
39
|
settings: z.array(z.string()).default([]),
|
|
34
40
|
surfaces: z.array(z.string()).default([]),
|
|
35
|
-
tables: z.array(z.string()).default([])
|
|
41
|
+
tables: z.array(z.string()).default([]),
|
|
42
|
+
imAdapters: z.array(z.string()).default([]),
|
|
43
|
+
webSearchProviders: z.array(z.string()).default([]),
|
|
44
|
+
/** SDK 0.2.15: the CLI agent hook events it hears. */
|
|
45
|
+
agentHooks: z.array(z.string()).default([]),
|
|
46
|
+
/** SDK 0.2.16: the model providers it registers. */
|
|
47
|
+
modelProviders: z.array(z.string()).default([])
|
|
36
48
|
})
|
|
37
49
|
.partial();
|
|
50
|
+
/**
|
|
51
|
+
* One row a full-mode plugin lays over Tomrade's composition, merged by id like a
|
|
52
|
+
* `tomrade.patch.yml` row: an app row's id switches it off, replaces its plugin or
|
|
53
|
+
* changes its config; a new id (carrying the namespace) adds a row. A relative
|
|
54
|
+
* `name` is a file inside the package.
|
|
55
|
+
*/
|
|
56
|
+
export const RowSchema = z
|
|
57
|
+
.object({
|
|
58
|
+
id: z.string().min(1),
|
|
59
|
+
name: z.string().min(1).optional(),
|
|
60
|
+
title: z.string().min(1).optional(),
|
|
61
|
+
config: z.unknown().optional(),
|
|
62
|
+
disabled: z.boolean().optional()
|
|
63
|
+
})
|
|
64
|
+
.refine((row) => row.name !== undefined || row.config !== undefined || row.disabled !== undefined, "a row changes something: name, config or disabled");
|
|
38
65
|
export const ManifestSchema = z.object({
|
|
39
66
|
id: z.string().regex(PLUGIN_ID, "publisher/name, lowercase letters, digits and dashes"),
|
|
40
67
|
namespace: z.string().regex(NAMESPACE, "lowercase letters, digits and underscores, 2–32 long, starting with a letter").optional(),
|
|
@@ -46,9 +73,23 @@ export const ManifestSchema = z.object({
|
|
|
46
73
|
repository: z.url().optional(),
|
|
47
74
|
homepage: z.url().optional(),
|
|
48
75
|
author: z.object({ name: z.string().min(1), url: z.url().optional() }).optional(),
|
|
49
|
-
engines: z.object({
|
|
50
|
-
|
|
76
|
+
engines: z.object({
|
|
77
|
+
sdk: z.string().refine(isRange, "a version range such as ^0.2.0"),
|
|
78
|
+
/** SDK 0.2.13: the Tomrade versions a full-mode plugin works with; it reaches internals the SDK range does not cover. */
|
|
79
|
+
tomrade: z.string().refine(isRange, "a version range such as >=0.13.0 <0.14.0").optional()
|
|
80
|
+
}),
|
|
81
|
+
/**
|
|
82
|
+
* SDK 0.2.13: what `main` receives. `"sdk"` is a `definePlugin` plugin behind the async facade;
|
|
83
|
+
* `"full"` is a plain cordis plugin given Tomrade's whole Context, as the app's own modules are.
|
|
84
|
+
*/
|
|
85
|
+
context: z.enum(["sdk", "full"]).default("sdk"),
|
|
86
|
+
/** SDK 0.2.13, full mode only: rows laid over Tomrade's own, as `tomrade.patch.yml` rows are. */
|
|
87
|
+
rows: z.array(RowSchema).default([]),
|
|
88
|
+
/** The built code; `null` for a plugin that is data only, such as a language pack. */
|
|
89
|
+
main: z.string().nullable().default("dist/index.js"),
|
|
51
90
|
icon: z.string().optional(),
|
|
91
|
+
/** Languages the plugin adds, each a JSON file of words (SDK 0.2.8). */
|
|
92
|
+
languages: z.array(LanguageEntrySchema).max(LANGUAGE_PACK_LIMITS.languages).default([]),
|
|
52
93
|
permissions: PermissionsSchema,
|
|
53
94
|
/** Stamped by `tomrade-plugin build` from the exported plugin. */
|
|
54
95
|
inject: z.array(SERVICE).default([]),
|
|
@@ -66,6 +107,26 @@ export function parseManifest(raw) {
|
|
|
66
107
|
throw new Error(`${manifest.id}: the name part does not make a valid namespace ("${ns}"); set "namespace" in ${MANIFEST_FILE}`);
|
|
67
108
|
if (RESERVED_NAMESPACES.has(ns) && publisherOf(manifest.id) !== OFFICIAL_PUBLISHER)
|
|
68
109
|
throw new Error(`${manifest.id}: namespace "${ns}" is reserved`);
|
|
110
|
+
if (manifest.main === null && !manifest.languages.length)
|
|
111
|
+
throw new Error(`${manifest.id}: "main" is null, so the plugin has no code; it must add a language under "languages"`);
|
|
112
|
+
if (manifest.context === "full") {
|
|
113
|
+
if (manifest.main === null)
|
|
114
|
+
throw new Error(`${manifest.id}: a full-mode plugin needs "main", the cordis plugin it runs`);
|
|
115
|
+
if (!manifest.engines.tomrade)
|
|
116
|
+
throw new Error(`${manifest.id}: a full-mode plugin names the Tomrade versions it works with in "engines.tomrade"`);
|
|
117
|
+
if (manifest.languages.length)
|
|
118
|
+
throw new Error(`${manifest.id}: a full-mode plugin adds its languages from its own code (ctx.settings.addLanguage), not under "languages"`);
|
|
119
|
+
}
|
|
120
|
+
else if (manifest.rows.length)
|
|
121
|
+
throw new Error(`${manifest.id}: "rows" change Tomrade's own composition, which takes "context": "full"`);
|
|
122
|
+
const ids = manifest.rows.map((r) => r.id);
|
|
123
|
+
const repeated = ids.find((id, i) => ids.indexOf(id) !== i);
|
|
124
|
+
if (repeated)
|
|
125
|
+
throw new Error(`${manifest.id}: the row "${repeated}" is listed twice`);
|
|
126
|
+
const tags = manifest.languages.map((l) => l.tag);
|
|
127
|
+
const twice = tags.find((t, i) => tags.indexOf(t) !== i);
|
|
128
|
+
if (twice)
|
|
129
|
+
throw new Error(`${manifest.id}: the language "${twice}" is listed twice`);
|
|
69
130
|
return manifest;
|
|
70
131
|
}
|
|
71
132
|
export const publisherOf = (id) => id.split("/")[0];
|
|
@@ -78,16 +139,22 @@ export function assertOwned(namespace, id, kind) {
|
|
|
78
139
|
throw new Error(`${kind} "${id}" must be named "${namespace}" or start with "${namespace}." "${namespace}-" or "${namespace}_"`);
|
|
79
140
|
}
|
|
80
141
|
/**
|
|
81
|
-
* High risk is labelled, not refused: running
|
|
82
|
-
*
|
|
142
|
+
* High risk is labelled, not refused: running with Tomrade's whole context or
|
|
143
|
+
* changing its own rows, running programs, reading anywhere on disk, or talking
|
|
144
|
+
* to any host. Elevated is what a person should still see.
|
|
83
145
|
*/
|
|
84
146
|
export function riskOf(manifest) {
|
|
85
147
|
const p = manifest.permissions;
|
|
86
|
-
const
|
|
148
|
+
const full = manifest.context === "full";
|
|
149
|
+
const high = [full && "full", full && manifest.rows?.length && "rows", p.exec && "exec", p.fs === "any" && "fs-any", p.network.includes("*") && "network-any"].filter(Boolean);
|
|
87
150
|
const elevated = [
|
|
88
151
|
p.network.length && !p.network.includes("*") && "network",
|
|
89
152
|
p.fs === "project" && "fs-project",
|
|
90
|
-
[...manifest.inject, ...manifest.optional].includes("db") && "db"
|
|
153
|
+
[...manifest.inject, ...manifest.optional].includes("db") && "db",
|
|
154
|
+
// A model provider reads the conversations of the profiles on it, and their keys.
|
|
155
|
+
[...manifest.inject, ...manifest.optional].includes("models") && "models",
|
|
156
|
+
// A window that takes keys could pass itself off as another app's: worth a look before installing.
|
|
157
|
+
p.window && "window"
|
|
91
158
|
].filter(Boolean);
|
|
92
159
|
return { level: high.length ? "high" : elevated.length ? "elevated" : "low", reasons: [...high, ...elevated] };
|
|
93
160
|
}
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { Disposer, Localized, ModelMessage, ModelTool } from "./contract.js";
|
|
2
|
+
type Maybe<T> = T | Promise<T>;
|
|
3
|
+
/** What a model reads besides text. */
|
|
4
|
+
export type ModelInput = "image" | "audio" | "video" | "pdf";
|
|
5
|
+
/** What a model can answer with besides text. */
|
|
6
|
+
export type ModelOutput = "image" | "audio";
|
|
7
|
+
/** SDK 0.2.16. One assistant turn a profile on the provider is asked for. */
|
|
8
|
+
export interface ModelChatRequest {
|
|
9
|
+
system: string;
|
|
10
|
+
messages: ModelMessage[];
|
|
11
|
+
tools?: ModelTool[];
|
|
12
|
+
toolChoice?: "auto" | "none" | {
|
|
13
|
+
name: string;
|
|
14
|
+
};
|
|
15
|
+
maxTokens?: number;
|
|
16
|
+
temperature?: number;
|
|
17
|
+
/** Reasoning effort ("low", "medium", "high"…) where the model has the knob; ignore it otherwise. */
|
|
18
|
+
effort?: string;
|
|
19
|
+
/** Answer with a single JSON document. */
|
|
20
|
+
json?: boolean;
|
|
21
|
+
/** Search the web yourself; asked only of a model whose capabilities say `nativeWebSearch`. */
|
|
22
|
+
webSearch?: boolean;
|
|
23
|
+
/** Media the reply should carry; asked only of a model whose capabilities list it in `mediaOutput`. */
|
|
24
|
+
mediaOutput?: ModelOutput[];
|
|
25
|
+
}
|
|
26
|
+
/** What a turn streams, in order, ending with `stop`. A `native` block comes back in later requests as the provider's own record (server-side tool calls, signatures). */
|
|
27
|
+
export type ModelChatEvent = {
|
|
28
|
+
type: "text";
|
|
29
|
+
delta: string;
|
|
30
|
+
} | {
|
|
31
|
+
type: "media";
|
|
32
|
+
media: {
|
|
33
|
+
mime: string;
|
|
34
|
+
base64: string;
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
} | {
|
|
38
|
+
type: "tool_call";
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
args: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
type: "native";
|
|
44
|
+
provider: string;
|
|
45
|
+
data: unknown;
|
|
46
|
+
}
|
|
47
|
+
/** `inputTokens` counts every prompt token read, cached ones included. */
|
|
48
|
+
| {
|
|
49
|
+
type: "usage";
|
|
50
|
+
inputTokens?: number;
|
|
51
|
+
outputTokens?: number;
|
|
52
|
+
cacheReadTokens?: number;
|
|
53
|
+
cacheWriteTokens?: number;
|
|
54
|
+
}
|
|
55
|
+
/** The request failed before anything streamed and is sent again after `delayMs`. */
|
|
56
|
+
| {
|
|
57
|
+
type: "retry";
|
|
58
|
+
attempt: number;
|
|
59
|
+
max: number;
|
|
60
|
+
reason: string;
|
|
61
|
+
delayMs: number;
|
|
62
|
+
} | {
|
|
63
|
+
type: "stop";
|
|
64
|
+
reason: "end" | "tool_use" | "max_tokens" | "refusal";
|
|
65
|
+
};
|
|
66
|
+
export interface ModelChatCapabilities {
|
|
67
|
+
/** MIME types it reads (`image/png`, `application/pdf`…). */
|
|
68
|
+
mediaInput?: string[];
|
|
69
|
+
mediaOutput?: string[];
|
|
70
|
+
tools: boolean;
|
|
71
|
+
streaming: boolean;
|
|
72
|
+
json: boolean;
|
|
73
|
+
/** It searches the web on its own when a request sets `webSearch`. */
|
|
74
|
+
nativeWebSearch?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** One model as Tomrade's conversations, worker and tools talk to it. Throw with what the provider said when a request fails. */
|
|
77
|
+
export interface ModelChat {
|
|
78
|
+
capabilities: ModelChatCapabilities;
|
|
79
|
+
chat(request: ModelChatRequest, signal?: AbortSignal): AsyncIterable<ModelChatEvent>;
|
|
80
|
+
}
|
|
81
|
+
/** A saved model profile naming the provider. */
|
|
82
|
+
export interface ModelProfileInfo {
|
|
83
|
+
id: string;
|
|
84
|
+
title: string;
|
|
85
|
+
provider: string;
|
|
86
|
+
/** Absent: the provider's default address. */
|
|
87
|
+
baseURL?: string;
|
|
88
|
+
apiKey: string;
|
|
89
|
+
model: string;
|
|
90
|
+
inputModalities?: ModelInput[];
|
|
91
|
+
outputModalities?: ModelOutput[];
|
|
92
|
+
}
|
|
93
|
+
/** A well-known service the provider serves, offered in the model setup. */
|
|
94
|
+
export interface ModelPresetSpec {
|
|
95
|
+
/** Namespaced like every id a plugin registers. */
|
|
96
|
+
id: string;
|
|
97
|
+
title: string;
|
|
98
|
+
/** Absent: the provider's own address. */
|
|
99
|
+
baseURL?: string;
|
|
100
|
+
/** The model to start with; otherwise the newest listed one matching `prefer`, then the first listed. */
|
|
101
|
+
model?: string;
|
|
102
|
+
prefer?: RegExp[];
|
|
103
|
+
/** Where people get a key. */
|
|
104
|
+
keyPage?: string;
|
|
105
|
+
/** The variable people export the key as, which Tomrade looks for on this computer. */
|
|
106
|
+
env?: string;
|
|
107
|
+
/** What its keys look like, so a pasted key picks this preset. */
|
|
108
|
+
keyShapes?: RegExp[];
|
|
109
|
+
/** Runs on this computer and needs no key. */
|
|
110
|
+
local?: boolean;
|
|
111
|
+
/** One line under the preset in the setup. */
|
|
112
|
+
hint?: Localized;
|
|
113
|
+
}
|
|
114
|
+
export interface ModelMedia {
|
|
115
|
+
inputModalities: ModelInput[];
|
|
116
|
+
outputModalities: ModelOutput[];
|
|
117
|
+
}
|
|
118
|
+
/** The models an endpoint lists (null: it has no listing), or why the key or address does not work. */
|
|
119
|
+
export type ModelListing = {
|
|
120
|
+
ok: true;
|
|
121
|
+
models: string[] | null;
|
|
122
|
+
capabilities?: Record<string, ModelMedia>;
|
|
123
|
+
} | {
|
|
124
|
+
ok: false;
|
|
125
|
+
status?: number;
|
|
126
|
+
detail: string;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* A sign-in the provider allows third-party apps instead of pasting a key: an
|
|
130
|
+
* OAuth flow that ends in a key (OpenRouter's PKCE), or credentials already on
|
|
131
|
+
* this computer. Never a consumer subscription its terms keep to the vendor's own apps.
|
|
132
|
+
*/
|
|
133
|
+
export interface ModelSignIn {
|
|
134
|
+
/** The button, such as "Sign in with OpenRouter". */
|
|
135
|
+
title: Localized;
|
|
136
|
+
/** Runs the sign-in; `open` shows a page in the person's browser. What it answers is kept as the profile's key. */
|
|
137
|
+
run(helpers: {
|
|
138
|
+
open(url: string): void;
|
|
139
|
+
signal: AbortSignal;
|
|
140
|
+
}): Promise<{
|
|
141
|
+
apiKey: string;
|
|
142
|
+
}>;
|
|
143
|
+
}
|
|
144
|
+
/** SDK 0.2.16. One way to talk to models, as the app's own providers are: what a profile's `provider` names. */
|
|
145
|
+
export interface ModelProviderSpec {
|
|
146
|
+
/** Namespaced like every id a plugin registers; profiles store it. */
|
|
147
|
+
id: string;
|
|
148
|
+
title: Localized;
|
|
149
|
+
/** False for an endpoint that works without a key, such as a local one. Default true. */
|
|
150
|
+
keyRequired?: boolean;
|
|
151
|
+
/** Where a profile without its own address talks; `editable: false` leaves the address out of the form. */
|
|
152
|
+
baseURL: {
|
|
153
|
+
default: string;
|
|
154
|
+
editable?: boolean;
|
|
155
|
+
};
|
|
156
|
+
/** False for an endpoint that only makes images. Default true. */
|
|
157
|
+
chat?: boolean;
|
|
158
|
+
presets?: ModelPresetSpec[];
|
|
159
|
+
/** Doubles as the key check when a profile is saved. */
|
|
160
|
+
list(endpoint: {
|
|
161
|
+
baseURL?: string;
|
|
162
|
+
apiKey: string;
|
|
163
|
+
}, signal: AbortSignal): Maybe<ModelListing>;
|
|
164
|
+
/** Of what the endpoint lists, the models a profile can use, best first. Default: all of them, as listed. */
|
|
165
|
+
usable?(listed: string[]): string[];
|
|
166
|
+
/** What its models read besides text when a profile stores nothing. */
|
|
167
|
+
defaultInput?: ModelInput[];
|
|
168
|
+
/** A model's media: what its listing entry says, else what the provider knows; undefined keeps a profile's stored values. */
|
|
169
|
+
capabilities?(model: string, listed?: ModelMedia): ModelMedia | undefined;
|
|
170
|
+
signIn?: ModelSignIn;
|
|
171
|
+
/** Called synchronously when a profile on it is first used, and again once the profile changes. */
|
|
172
|
+
build(profile: ModelProfileInfo): ModelChat;
|
|
173
|
+
}
|
|
174
|
+
/** A model on the app's own OpenAI-compatible adapter. */
|
|
175
|
+
export interface CompatibleModelOptions {
|
|
176
|
+
baseURL: string;
|
|
177
|
+
apiKey: string;
|
|
178
|
+
model: string;
|
|
179
|
+
inputModalities?: ModelInput[];
|
|
180
|
+
outputModalities?: ModelOutput[];
|
|
181
|
+
/**
|
|
182
|
+
* The model searches the web on its own when a request asks: `"chat"` on chat/completions, with
|
|
183
|
+
* the vendor's own flag set by `body`; `"responses"` on the Responses API. Absent, it does not.
|
|
184
|
+
*/
|
|
185
|
+
webSearch?: "chat" | "responses";
|
|
186
|
+
/** The last word on each chat/completions body (a copy of the request beside it): a vendor's own fields. Throw to fail the request. */
|
|
187
|
+
body?(json: Record<string, unknown>, request: ModelChatRequest): Record<string, unknown>;
|
|
188
|
+
/** The last word on each Responses body, where a searching request goes with `webSearch: "responses"`: the vendor's effort scale, its limits. Throw to fail the request. */
|
|
189
|
+
responses?(json: Record<string, unknown>, request: ModelChatRequest): Record<string, unknown>;
|
|
190
|
+
/**
|
|
191
|
+
* The headers of each request in place of `Authorization: Bearer <apiKey>`: a vendor's own key
|
|
192
|
+
* header, a token that expires, a signature over `url` and the exact `body` sent. Asked again
|
|
193
|
+
* before every try, retries included. Throw to fail the request.
|
|
194
|
+
*/
|
|
195
|
+
headers?(request: {
|
|
196
|
+
url: string;
|
|
197
|
+
body: string;
|
|
198
|
+
}): Maybe<Record<string, string>>;
|
|
199
|
+
}
|
|
200
|
+
export interface ModelsApi {
|
|
201
|
+
/** Serves the profiles naming `provider.id` and offers its presets until disposed or the plugin unloads. */
|
|
202
|
+
registerProvider(provider: ModelProviderSpec): Promise<Disposer>;
|
|
203
|
+
/**
|
|
204
|
+
* Synchronous, like `build`: a model on the app's own OpenAI-compatible adapter — streaming, tool
|
|
205
|
+
* calls, retries, media, the token-field quirks — for a vendor that serves chat/completions.
|
|
206
|
+
*/
|
|
207
|
+
compatible(options: CompatibleModelOptions): ModelChat;
|
|
208
|
+
/**
|
|
209
|
+
* The app's own listing of a chat/completions endpoint (`GET /models`), media read from it; one
|
|
210
|
+
* without a listing answers `models: null`. `headers` go in place of the bearer key.
|
|
211
|
+
*/
|
|
212
|
+
listCompatible(endpoint: {
|
|
213
|
+
baseURL: string;
|
|
214
|
+
apiKey: string;
|
|
215
|
+
headers?: Record<string, string>;
|
|
216
|
+
}, signal?: AbortSignal): Promise<ModelListing>;
|
|
217
|
+
}
|
|
218
|
+
export {};
|
|
219
|
+
//# sourceMappingURL=models.d.ts.map
|
package/dist/models.js
ADDED