@moderniser/plugin 0.1.4 → 0.1.6
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/client.d.ts +5 -10
- package/dist/index.js +106 -280
- package/package.json +2 -2
- package/dist/tools-boards.d.ts +0 -2
- package/dist/tools-context-hub.d.ts +0 -2
package/dist/client.d.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
export declare const API: () => string;
|
|
2
|
-
export declare class ApiError extends Error {
|
|
3
|
-
status: number;
|
|
4
|
-
constructor(status: number, message: string);
|
|
5
|
-
}
|
|
6
2
|
export declare function setStoredKey(key: string): void;
|
|
7
3
|
export declare function storedKey(): string | undefined;
|
|
8
|
-
export declare function
|
|
9
|
-
export declare const
|
|
10
|
-
export declare
|
|
11
|
-
export declare
|
|
12
|
-
export declare
|
|
13
|
-
export declare const json: (value: unknown) => string;
|
|
4
|
+
export declare function resolveKey(): string | undefined;
|
|
5
|
+
export declare const COMPANY_HEADER = "x-moderniser-company";
|
|
6
|
+
export declare function setStoredCompany(companyId: string): void;
|
|
7
|
+
export declare function resolveCompany(): string | undefined;
|
|
8
|
+
export declare function companyHeaders(): Record<string, string>;
|
package/dist/index.js
CHANGED
|
@@ -2,19 +2,10 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// packages/plugin/src/client.ts
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
5
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
var API = () => (process.env.MODERNISER_API_URL ?? "https://v2.moderniser.ai/api").replace(/\/+$/, "");
|
|
9
|
-
var BASE = API;
|
|
10
|
-
|
|
11
|
-
class ApiError extends Error {
|
|
12
|
-
status;
|
|
13
|
-
constructor(status, message) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.status = status;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
9
|
var stored;
|
|
19
10
|
function setStoredKey(key) {
|
|
20
11
|
stored = key;
|
|
@@ -31,61 +22,56 @@ function fromAuthFile() {
|
|
|
31
22
|
if (fileKey !== undefined)
|
|
32
23
|
return fileKey ?? undefined;
|
|
33
24
|
fileKey = null;
|
|
34
|
-
let raw;
|
|
35
|
-
try {
|
|
36
|
-
raw = readFileSync(authFilePath(), "utf8");
|
|
37
|
-
} catch {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
25
|
try {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return key;
|
|
49
|
-
} catch {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
26
|
+
const parsed = JSON.parse(readFileSync(authFilePath(), "utf8"));
|
|
27
|
+
const entry = parsed?.moderniser;
|
|
28
|
+
if (typeof entry === "object" && entry !== null && entry.type === "api" && typeof entry.key === "string" && entry.key.length > 0) {
|
|
29
|
+
fileKey = entry.key;
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
return fileKey ?? undefined;
|
|
52
33
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return
|
|
34
|
+
function resolveKey() {
|
|
35
|
+
return process.env.MODERNISER_API_KEY ?? stored ?? fromAuthFile();
|
|
36
|
+
}
|
|
37
|
+
var COMPANY_HEADER = "x-moderniser-company";
|
|
38
|
+
function companyFilePath() {
|
|
39
|
+
const data = process.env.XDG_DATA_HOME || join(homedir(), ".local", "share");
|
|
40
|
+
return join(data, "moderniser", "company.json");
|
|
60
41
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
var storedCompany;
|
|
43
|
+
function setStoredCompany(companyId) {
|
|
44
|
+
storedCompany = companyId;
|
|
45
|
+
fileCompany = companyId;
|
|
64
46
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
47
|
+
const path = companyFilePath();
|
|
48
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
49
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
50
|
+
writeFileSync(temp, JSON.stringify({ companyId }, null, 2) + `
|
|
51
|
+
`, { mode: 384 });
|
|
52
|
+
renameSync(temp, path);
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
var fileCompany;
|
|
56
|
+
function companyFromFile() {
|
|
57
|
+
if (fileCompany !== undefined)
|
|
58
|
+
return fileCompany ?? undefined;
|
|
59
|
+
fileCompany = null;
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(readFileSync(companyFilePath(), "utf8"));
|
|
62
|
+
if (typeof parsed?.companyId === "string" && parsed.companyId.length > 0) {
|
|
63
|
+
fileCompany = parsed.companyId;
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
return fileCompany ?? undefined;
|
|
67
|
+
}
|
|
68
|
+
function resolveCompany() {
|
|
69
|
+
return process.env.MODERNISER_COMPANY ?? storedCompany ?? companyFromFile();
|
|
70
|
+
}
|
|
71
|
+
function companyHeaders() {
|
|
72
|
+
const company = resolveCompany();
|
|
73
|
+
return company ? { [COMPANY_HEADER]: company } : {};
|
|
84
74
|
}
|
|
85
|
-
var get = (path) => api(path);
|
|
86
|
-
var post = (path, body) => api(path, { method: "POST", body: JSON.stringify(body) });
|
|
87
|
-
var patch = (path, body) => api(path, { method: "PATCH", body: JSON.stringify(body) });
|
|
88
|
-
var json = (value) => JSON.stringify(value, null, 2);
|
|
89
75
|
|
|
90
76
|
// packages/plugin/src/auth.ts
|
|
91
77
|
async function start() {
|
|
@@ -119,7 +105,7 @@ async function poll(deviceCode, expiresIn, intervalSeconds) {
|
|
|
119
105
|
continue;
|
|
120
106
|
}
|
|
121
107
|
if (body.token)
|
|
122
|
-
return body.token;
|
|
108
|
+
return { token: body.token, companyId: body.companyId };
|
|
123
109
|
if (body.error === "authorization_pending")
|
|
124
110
|
continue;
|
|
125
111
|
if (body.error === "slow_down") {
|
|
@@ -141,14 +127,16 @@ function moderniserAuth() {
|
|
|
141
127
|
const started = await start();
|
|
142
128
|
return {
|
|
143
129
|
url: started.verificationUrl,
|
|
144
|
-
instructions: `Check that your browser shows this code: ${started.userCode}
|
|
130
|
+
instructions: `Check that your browser shows this code: ${started.userCode}. After signing in for the first time, restart to pick up the Moderniser tools.`,
|
|
145
131
|
method: "auto",
|
|
146
132
|
async callback() {
|
|
147
|
-
const
|
|
148
|
-
if (!
|
|
133
|
+
const collected = await poll(started.deviceCode, started.expiresIn, started.interval);
|
|
134
|
+
if (!collected)
|
|
149
135
|
return { type: "failed" };
|
|
150
|
-
setStoredKey(
|
|
151
|
-
|
|
136
|
+
setStoredKey(collected.token);
|
|
137
|
+
if (collected.companyId)
|
|
138
|
+
setStoredCompany(collected.companyId);
|
|
139
|
+
return { type: "success", key: collected.token };
|
|
152
140
|
}
|
|
153
141
|
};
|
|
154
142
|
}
|
|
@@ -176,7 +164,7 @@ function moderniserContext() {
|
|
|
176
164
|
"- Task: one unit of work a team would plan together.",
|
|
177
165
|
"- Subtask: one concrete change in one codebase, small enough to finish and check off.",
|
|
178
166
|
"",
|
|
179
|
-
"When the user says project, task, board or ticket, they mean Moderniser's — " + "reach for the board tools (
|
|
167
|
+
"When the user says project, task, board or ticket, they mean Moderniser's — " + "reach for the board tools (moderniser_list_projects first, then " + "moderniser_create_task / moderniser_update_task / moderniser_create_subtask / …). " + "Do not answer by scaffolding files or writing a spec document instead, " + "unless a document is what they actually asked for."
|
|
180
168
|
];
|
|
181
169
|
const workspace = process.env.MODERNISER_WORKSPACE_NAME;
|
|
182
170
|
const company = process.env.MODERNISER_COMPANY_NAME;
|
|
@@ -194,212 +182,6 @@ function moderniserContext() {
|
|
|
194
182
|
`);
|
|
195
183
|
}
|
|
196
184
|
|
|
197
|
-
// packages/plugin/src/tools-boards.ts
|
|
198
|
-
import { tool } from "@opencode-ai/plugin";
|
|
199
|
-
var z = tool.schema;
|
|
200
|
-
var STATUS = ["todo", "in_progress", "in_review", "blocked", "done"];
|
|
201
|
-
var TYPE = ["feature", "bug", "improvement"];
|
|
202
|
-
var FILES_TO_MODIFY = "Paths you have actually opened, written exactly as you reached them from the folder this session started in — 'orders/src/Order.java', not an absolute path and not a path you have rewritten. When a task spans two codebases the leading folder is the only thing telling a reader which one a change is in. Leave this empty rather than guessing.";
|
|
203
|
-
function commentPath(taskId, subtaskId) {
|
|
204
|
-
if (Boolean(taskId) === Boolean(subtaskId)) {
|
|
205
|
-
throw new Error("Pass exactly one of taskId or subtaskId");
|
|
206
|
-
}
|
|
207
|
-
return taskId ? `/projects/tasks/${taskId}/comments` : `/projects/subtasks/${subtaskId}/comments`;
|
|
208
|
-
}
|
|
209
|
-
var boardTools = {
|
|
210
|
-
list_projects: tool({
|
|
211
|
-
description: "List the projects belonging to the caller's company. Call this first — every task belongs to a project, and you need a project id before you can create one.",
|
|
212
|
-
args: {},
|
|
213
|
-
async execute() {
|
|
214
|
-
return json((await get("/projects")).projects);
|
|
215
|
-
}
|
|
216
|
-
}),
|
|
217
|
-
create_project: tool({
|
|
218
|
-
description: "Create a project. A project is an area of work, and it may span several codebases — a checkout modernisation touches the storefront and the system that receives the order. Check list_projects first, since a duplicate fragments the board.",
|
|
219
|
-
args: {
|
|
220
|
-
name: z.string().describe("Short name, e.g. 'Checkout modernisation'"),
|
|
221
|
-
description: z.string().describe("What this covers and why it is being worked on").optional(),
|
|
222
|
-
keyPrefix: z.string().regex(/^[A-Z]{3}$/).describe("Three uppercase letters used to key tasks, e.g. 'CAD' gives CAD-1. Omit it and one is derived from the name.").optional()
|
|
223
|
-
},
|
|
224
|
-
async execute(args) {
|
|
225
|
-
return json((await post("/projects", args)).project);
|
|
226
|
-
}
|
|
227
|
-
}),
|
|
228
|
-
list_tasks: tool({
|
|
229
|
-
description: "List the tasks in a project with their status. Use this to see what already exists before creating more.",
|
|
230
|
-
args: {
|
|
231
|
-
projectId: z.string().describe("Project id from list_projects"),
|
|
232
|
-
status: z.string().describe(`Only tasks in this status. Defaults: ${STATUS.join(", ")}; a project may define its own`).optional()
|
|
233
|
-
},
|
|
234
|
-
async execute({ projectId, status }) {
|
|
235
|
-
const query = status ? `?status=${status}` : "";
|
|
236
|
-
return json((await get(`/projects/${projectId}/tasks${query}`)).tasks);
|
|
237
|
-
}
|
|
238
|
-
}),
|
|
239
|
-
get_task: tool({
|
|
240
|
-
description: "Fetch one task with all of its subtasks. Use this before updating anything, so you are editing against the current state rather than what you remember.",
|
|
241
|
-
args: { taskId: z.string().describe("Task id") },
|
|
242
|
-
async execute(args) {
|
|
243
|
-
return json((await get(`/projects/tasks/${args.taskId}`)).task);
|
|
244
|
-
}
|
|
245
|
-
}),
|
|
246
|
-
create_task: tool({
|
|
247
|
-
description: "Create a task: one unit of work a team would plan together, such as 'Upgrade to SAP Commerce 2211'. Read the relevant code before writing the description — a task that describes the actual state of the code is worth keeping, one written from assumption is not. Break it into subtasks afterwards with create_subtask.",
|
|
248
|
-
args: {
|
|
249
|
-
projectId: z.string().describe("Project id from list_projects"),
|
|
250
|
-
title: z.string().max(300).describe("Imperative and specific: 'Replace deprecated FlexibleSearch calls'"),
|
|
251
|
-
description: z.string().describe("What needs doing and why, including what you found in the code — current version, affected modules, known blockers"),
|
|
252
|
-
type: z.string().describe(`Task type. Defaults to feature; usual values: ${TYPE.join(", ")}; a project may define its own`).optional()
|
|
253
|
-
},
|
|
254
|
-
async execute({ projectId, ...body }) {
|
|
255
|
-
return json((await post(`/projects/${projectId}/tasks`, body)).task);
|
|
256
|
-
}
|
|
257
|
-
}),
|
|
258
|
-
update_task: tool({
|
|
259
|
-
description: "Update a task's title, description, type or status. Move it to in_progress when work starts. Prefer in_review over done when you have finished but a person has not checked it — done is a human's word. Use blocked when you cannot proceed, and say why in a comment.",
|
|
260
|
-
args: {
|
|
261
|
-
taskId: z.string().describe("Task id"),
|
|
262
|
-
title: z.string().max(300).describe("New title").optional(),
|
|
263
|
-
description: z.string().describe("New description").optional(),
|
|
264
|
-
type: z.string().describe(`New type (usual values: ${TYPE.join(", ")}; a project may define its own)`).optional(),
|
|
265
|
-
status: z.string().describe(`New status (defaults: ${STATUS.join(", ")}; a project may define its own — a 400 lists the real ones)`).optional()
|
|
266
|
-
},
|
|
267
|
-
async execute({ taskId, ...body }) {
|
|
268
|
-
return json((await patch(`/projects/tasks/${taskId}`, body)).task);
|
|
269
|
-
}
|
|
270
|
-
}),
|
|
271
|
-
list_subtasks: tool({
|
|
272
|
-
description: "List the subtasks of a task, in order.",
|
|
273
|
-
args: { taskId: z.string().describe("Task id") },
|
|
274
|
-
async execute(args) {
|
|
275
|
-
return json((await get(`/projects/tasks/${args.taskId}/subtasks`)).subtasks);
|
|
276
|
-
}
|
|
277
|
-
}),
|
|
278
|
-
create_subtask: tool({
|
|
279
|
-
description: "Add a subtask: one concrete change a developer could pick up and finish, in one codebase. Populate filesToModify with paths you have actually opened; leave it empty rather than filling it with guesses. Work that genuinely spans two codebases is two subtasks, which is also what keeps 'done' honest.",
|
|
280
|
-
args: {
|
|
281
|
-
taskId: z.string().describe("Task id"),
|
|
282
|
-
title: z.string().max(300).describe("One specific change"),
|
|
283
|
-
description: z.string().describe("What to change and how you know it needs changing"),
|
|
284
|
-
filesToModify: z.array(z.string()).describe(FILES_TO_MODIFY).optional(),
|
|
285
|
-
implementationNotes: z.string().describe("Anything that saves the next person a search: the symbol to grep for, the call sites, the order to do things in").optional(),
|
|
286
|
-
order: z.number().int().describe("Position in the list. Lower runs first.").optional()
|
|
287
|
-
},
|
|
288
|
-
async execute({ taskId, ...body }) {
|
|
289
|
-
return json((await post(`/projects/tasks/${taskId}/subtasks`, body)).subtask);
|
|
290
|
-
}
|
|
291
|
-
}),
|
|
292
|
-
update_subtask: tool({
|
|
293
|
-
description: "Update a subtask. Use this to mark one done as you finish it, so the board reflects reality while the work is happening rather than after.",
|
|
294
|
-
args: {
|
|
295
|
-
subtaskId: z.string().describe("Subtask id"),
|
|
296
|
-
title: z.string().max(300).describe("New title").optional(),
|
|
297
|
-
description: z.string().describe("New description").optional(),
|
|
298
|
-
status: z.string().describe(`New status (defaults: ${STATUS.join(", ")}; a project may define its own — a 400 lists the real ones)`).optional(),
|
|
299
|
-
filesToModify: z.array(z.string()).describe(FILES_TO_MODIFY).optional(),
|
|
300
|
-
implementationNotes: z.string().describe("Notes for whoever picks this up").optional(),
|
|
301
|
-
order: z.number().int().describe("Position in the list").optional()
|
|
302
|
-
},
|
|
303
|
-
async execute({ subtaskId, ...body }) {
|
|
304
|
-
return json((await patch(`/projects/subtasks/${subtaskId}`, body)).subtask);
|
|
305
|
-
}
|
|
306
|
-
}),
|
|
307
|
-
list_comments: tool({
|
|
308
|
-
description: "Read the comment thread on a task or a subtask. Do this before acting on work someone else planned — a correction from a human usually lives here rather than in the description. Pass exactly one of taskId or subtaskId.",
|
|
309
|
-
args: {
|
|
310
|
-
taskId: z.string().describe("Task id").optional(),
|
|
311
|
-
subtaskId: z.string().describe("Subtask id").optional()
|
|
312
|
-
},
|
|
313
|
-
async execute(args) {
|
|
314
|
-
const path = commentPath(args.taskId, args.subtaskId);
|
|
315
|
-
return json((await get(path)).comments);
|
|
316
|
-
}
|
|
317
|
-
}),
|
|
318
|
-
add_comment: tool({
|
|
319
|
-
description: "Leave a comment on a task or subtask. Use it to record what you found, what you changed, or why you departed from the plan — not to restate the description. Comments you write are labelled as coming from the agent. Pass exactly one of taskId or subtaskId.",
|
|
320
|
-
args: {
|
|
321
|
-
body: z.string().max(1e4).describe("The comment"),
|
|
322
|
-
taskId: z.string().describe("Task id").optional(),
|
|
323
|
-
subtaskId: z.string().describe("Subtask id").optional()
|
|
324
|
-
},
|
|
325
|
-
async execute(args) {
|
|
326
|
-
const path = commentPath(args.taskId, args.subtaskId);
|
|
327
|
-
return json((await post(path, { body: args.body, source: "agent" })).comment);
|
|
328
|
-
}
|
|
329
|
-
})
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
// packages/plugin/src/tools-context-hub.ts
|
|
333
|
-
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
334
|
-
var z2 = tool2.schema;
|
|
335
|
-
var contextHubTools = {
|
|
336
|
-
search_context_hub: tool2({
|
|
337
|
-
description: "Search the company's internal documentation. Run this before answering a question about how something works here — the wiki usually explains the intent that the code only implies, and it is where conventions, past decisions and known traps are written down. Searches by meaning as well as by keyword, so a question phrased differently to the page still finds it. Results carry a score and a staleness flag; a stale page is past its review date and may be wrong. Follow up with read_context_hub_page for the full text.",
|
|
338
|
-
args: {
|
|
339
|
-
query: z2.string().describe("What you want to know. A phrase or question works better than a single word."),
|
|
340
|
-
mode: z2.enum(["hybrid", "keyword", "semantic"]).describe("hybrid (default) searches by meaning and by keyword. Use keyword when looking for an exact identifier, class name or error string.").optional(),
|
|
341
|
-
spaceId: z2.string().describe("Optional: restrict to one space, from list_context_hub_spaces").optional(),
|
|
342
|
-
tag: z2.string().describe("Optional: restrict to pages carrying this tag").optional(),
|
|
343
|
-
limit: z2.number().int().describe("Maximum results, default 20, maximum 50").optional()
|
|
344
|
-
},
|
|
345
|
-
async execute(args) {
|
|
346
|
-
const params = new URLSearchParams({ q: args.query });
|
|
347
|
-
if (args.mode)
|
|
348
|
-
params.set("mode", args.mode);
|
|
349
|
-
if (args.spaceId)
|
|
350
|
-
params.set("spaceId", args.spaceId);
|
|
351
|
-
if (args.tag)
|
|
352
|
-
params.set("tag", args.tag);
|
|
353
|
-
if (args.limit)
|
|
354
|
-
params.set("limit", String(args.limit));
|
|
355
|
-
return json((await get(`/context-hub/search?${params}`)).results);
|
|
356
|
-
}
|
|
357
|
-
}),
|
|
358
|
-
list_context_hub_spaces: tool2({
|
|
359
|
-
description: "List the spaces in the Context Hub. A space groups related pages — typically one per system, team or imported wiki. Use it to narrow a search when a keyword is too common.",
|
|
360
|
-
args: {},
|
|
361
|
-
async execute() {
|
|
362
|
-
return json((await get("/context-hub/spaces")).spaces);
|
|
363
|
-
}
|
|
364
|
-
}),
|
|
365
|
-
list_context_hub_pages: tool2({
|
|
366
|
-
description: "List the pages in a space, with titles and when they were last updated. Bodies are not included — read_context_hub_page fetches those.",
|
|
367
|
-
args: { spaceId: z2.string().describe("Space id from list_context_hub_spaces") },
|
|
368
|
-
async execute(args) {
|
|
369
|
-
return json((await get(`/context-hub/spaces/${args.spaceId}/pages`)).pages);
|
|
370
|
-
}
|
|
371
|
-
}),
|
|
372
|
-
read_context_hub_page: tool2({
|
|
373
|
-
description: "Read one page in full. Cite what you found from it when it changes your answer, so the person can check the source — the wiki can be out of date, and saying where a claim came from is what lets them notice.",
|
|
374
|
-
args: { pageId: z2.string().describe("Page id from search or list") },
|
|
375
|
-
async execute(args) {
|
|
376
|
-
return json((await get(`/context-hub/pages/${args.pageId}`)).page);
|
|
377
|
-
}
|
|
378
|
-
}),
|
|
379
|
-
create_context_hub_page: tool2({
|
|
380
|
-
description: "Write a new page. Use this when you have worked something out that the next person would otherwise have to rediscover — a migration procedure, why an approach was rejected, how two systems actually talk to each other. Search first: adding a second page on a subject that already has one makes both less trustworthy.",
|
|
381
|
-
args: {
|
|
382
|
-
spaceId: z2.string().describe("Space to create the page in"),
|
|
383
|
-
title: z2.string().max(300).describe("Specific and searchable: 'Upgrading SAP Commerce 1905 to 2211'"),
|
|
384
|
-
content: z2.string().describe("Markdown body")
|
|
385
|
-
},
|
|
386
|
-
async execute({ spaceId, ...body }) {
|
|
387
|
-
return json((await post(`/context-hub/spaces/${spaceId}/pages`, body)).page);
|
|
388
|
-
}
|
|
389
|
-
}),
|
|
390
|
-
update_context_hub_page: tool2({
|
|
391
|
-
description: "Replace a page's title or body. The content you send replaces what is there — read the page first and return the whole document with your changes folded in, or you will delete the parts you did not mention.",
|
|
392
|
-
args: {
|
|
393
|
-
pageId: z2.string().describe("Page id"),
|
|
394
|
-
title: z2.string().max(300).describe("New title").optional(),
|
|
395
|
-
content: z2.string().describe("The complete new markdown body, not a fragment").optional()
|
|
396
|
-
},
|
|
397
|
-
async execute({ pageId, ...body }) {
|
|
398
|
-
return json((await patch(`/context-hub/pages/${pageId}`, body)).page);
|
|
399
|
-
}
|
|
400
|
-
})
|
|
401
|
-
};
|
|
402
|
-
|
|
403
185
|
// packages/plugin/src/index.ts
|
|
404
186
|
var API2 = () => (process.env.MODERNISER_API_URL ?? "https://v2.moderniser.ai/api").replace(/\/+$/, "");
|
|
405
187
|
var KEY = () => process.env.MODERNISER_API_KEY;
|
|
@@ -416,7 +198,7 @@ async function call(path, body) {
|
|
|
416
198
|
try {
|
|
417
199
|
return await fetch(`${API2()}${path}`, {
|
|
418
200
|
method: "POST",
|
|
419
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}
|
|
201
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}`, ...companyHeaders() },
|
|
420
202
|
body: JSON.stringify(body),
|
|
421
203
|
signal: AbortSignal.timeout(1e4)
|
|
422
204
|
});
|
|
@@ -424,27 +206,62 @@ async function call(path, body) {
|
|
|
424
206
|
return null;
|
|
425
207
|
}
|
|
426
208
|
}
|
|
209
|
+
function positiveInt(value) {
|
|
210
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
211
|
+
}
|
|
212
|
+
async function gatewayProduct() {
|
|
213
|
+
const key = KEY() ?? storedKey();
|
|
214
|
+
if (!key)
|
|
215
|
+
return {};
|
|
216
|
+
try {
|
|
217
|
+
const res = await fetch(`${GATEWAY()}/models`, {
|
|
218
|
+
headers: { Authorization: `Bearer ${key}`, ...companyHeaders() },
|
|
219
|
+
signal: AbortSignal.timeout(5000)
|
|
220
|
+
});
|
|
221
|
+
if (!res.ok)
|
|
222
|
+
return {};
|
|
223
|
+
const entry = (await res.json())?.data?.[0];
|
|
224
|
+
if (!entry)
|
|
225
|
+
return {};
|
|
226
|
+
const efforts = Array.isArray(entry.efforts) ? entry.efforts.filter((e) => typeof e === "string" && e.length > 0) : undefined;
|
|
227
|
+
return {
|
|
228
|
+
contextWindow: positiveInt(entry.context_window),
|
|
229
|
+
maxOutputTokens: positiveInt(entry.max_output_tokens),
|
|
230
|
+
efforts: efforts?.length ? efforts : undefined
|
|
231
|
+
};
|
|
232
|
+
} catch {
|
|
233
|
+
return {};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
427
236
|
var registered = new Set;
|
|
428
237
|
var reported = new Map;
|
|
429
238
|
var ModerniserPlugin = async () => {
|
|
430
239
|
return {
|
|
431
240
|
config: async (config) => {
|
|
432
241
|
const modalities = { input: ["text", "image"], output: ["text"] };
|
|
242
|
+
const product = await gatewayProduct();
|
|
243
|
+
const efforts = (product.efforts ?? ["high"]).filter((effort) => effort !== "default");
|
|
433
244
|
const models = {
|
|
434
|
-
emm: {
|
|
435
|
-
|
|
436
|
-
name: "Enterprise Modernisation Model (deep reasoning)",
|
|
245
|
+
emm: {
|
|
246
|
+
name: "Enterprise Modernisation Model",
|
|
437
247
|
tool_call: true,
|
|
438
248
|
reasoning: true,
|
|
439
|
-
modalities
|
|
249
|
+
modalities,
|
|
250
|
+
variants: Object.fromEntries(efforts.map((effort) => [effort, { reasoning_effort: effort }])),
|
|
251
|
+
...product.contextWindow && product.maxOutputTokens ? { limit: { context: product.contextWindow, output: product.maxOutputTokens } } : {}
|
|
440
252
|
}
|
|
441
253
|
};
|
|
442
254
|
const key = KEY();
|
|
255
|
+
const company = companyHeaders();
|
|
443
256
|
config.provider ??= {};
|
|
444
257
|
config.provider[PROVIDER_ID] ??= {
|
|
445
258
|
name: "Moderniser",
|
|
446
259
|
npm: "@ai-sdk/openai-compatible",
|
|
447
|
-
options: {
|
|
260
|
+
options: {
|
|
261
|
+
baseURL: GATEWAY(),
|
|
262
|
+
...key ? { apiKey: key } : {},
|
|
263
|
+
...Object.keys(company).length ? { headers: company } : {}
|
|
264
|
+
},
|
|
448
265
|
models
|
|
449
266
|
};
|
|
450
267
|
config.enabled_providers ??= [PROVIDER_ID];
|
|
@@ -453,8 +270,17 @@ var ModerniserPlugin = async () => {
|
|
|
453
270
|
config.model ??= requested || DEFAULT_MODEL;
|
|
454
271
|
config.small_model ??= config.model;
|
|
455
272
|
}
|
|
273
|
+
const mcpKey = resolveKey();
|
|
274
|
+
if (mcpKey) {
|
|
275
|
+
config.mcp ??= {};
|
|
276
|
+
config.mcp[PROVIDER_ID] ??= {
|
|
277
|
+
type: "remote",
|
|
278
|
+
url: `${API2()}/mcp`,
|
|
279
|
+
headers: { Authorization: `Bearer ${mcpKey}`, ...companyHeaders() },
|
|
280
|
+
oauth: false
|
|
281
|
+
};
|
|
282
|
+
}
|
|
456
283
|
},
|
|
457
|
-
tool: { ...boardTools, ...contextHubTools },
|
|
458
284
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
459
285
|
output.system.push(moderniserContext());
|
|
460
286
|
},
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moderniser/plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Moderniser plugin for opencode: points inference at the gateway,
|
|
5
|
+
"description": "Moderniser plugin for opencode: points inference at the gateway, declares the Moderniser remote MCP server, and reports session telemetry",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
package/dist/tools-boards.d.ts
DELETED