@ainyc/canonry 1.7.0 → 1.7.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/assets/index.html CHANGED
@@ -8,7 +8,7 @@
8
8
  content="Canonry is the AINYC monitoring dashboard for answer visibility and technical readiness."
9
9
  />
10
10
  <title>Canonry</title>
11
- <script type="module" crossorigin src="/assets/index-Cgb-VMub.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-D_jajzpq.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-BxWGYuSH.css">
13
13
  </head>
14
14
  <body>
@@ -1155,6 +1155,55 @@ async function runRoutes(app, opts) {
1155
1155
  const rows = app.db.select().from(runs).all();
1156
1156
  return reply.send(rows.map(formatRun));
1157
1157
  });
1158
+ app.post("/runs", async (request, reply) => {
1159
+ const allProjects = app.db.select().from(projects).all();
1160
+ if (allProjects.length === 0) {
1161
+ return reply.status(207).send([]);
1162
+ }
1163
+ const kind = request.body?.kind ?? "answer-visibility";
1164
+ if (kind !== "answer-visibility") {
1165
+ const err = unsupportedKind(kind);
1166
+ return reply.status(err.statusCode).send(err.toJSON());
1167
+ }
1168
+ const rawProviders = request.body?.providers;
1169
+ if (rawProviders?.length) {
1170
+ const parsed = rawProviders.map((p) => parseProviderName(p));
1171
+ const invalid = rawProviders.filter((_, i) => !parsed[i]);
1172
+ if (invalid.length) {
1173
+ return reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: `Invalid provider(s): ${invalid.join(", ")}. Must be one of: gemini, openai, claude, local` } });
1174
+ }
1175
+ rawProviders.splice(0, rawProviders.length, ...parsed.filter(Boolean));
1176
+ }
1177
+ const providers = rawProviders?.length ? rawProviders : void 0;
1178
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1179
+ const results = [];
1180
+ for (const project of allProjects) {
1181
+ const queueResult = queueRunIfProjectIdle(app.db, {
1182
+ createdAt: now,
1183
+ kind,
1184
+ projectId: project.id,
1185
+ trigger: "manual"
1186
+ });
1187
+ if (queueResult.conflict) {
1188
+ results.push({ projectName: project.name, projectId: project.id, status: "conflict", error: "run_in_progress" });
1189
+ continue;
1190
+ }
1191
+ const runId = queueResult.runId;
1192
+ writeAuditLog(app.db, {
1193
+ projectId: project.id,
1194
+ actor: "api",
1195
+ action: "run.created",
1196
+ entityType: "run",
1197
+ entityId: runId
1198
+ });
1199
+ const run = app.db.select().from(runs).where(eq7(runs.id, runId)).get();
1200
+ if (opts.onRunCreated) {
1201
+ opts.onRunCreated(runId, project.id, providers);
1202
+ }
1203
+ results.push({ ...formatRun(run), projectName: project.name });
1204
+ }
1205
+ return reply.status(207).send(results);
1206
+ });
1158
1207
  app.get("/runs/:id", async (request, reply) => {
1159
1208
  const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
1160
1209
  if (!run) {
@@ -1887,7 +1936,7 @@ async function settingsRoutes(app, opts) {
1887
1936
  }));
1888
1937
  app.put("/settings/providers/:name", async (request, reply) => {
1889
1938
  const providerName = parseProviderName(request.params.name);
1890
- const { apiKey, baseUrl, model } = request.body ?? {};
1939
+ const { apiKey, baseUrl, model, quota } = request.body ?? {};
1891
1940
  if (!providerName) {
1892
1941
  return reply.status(400).send({ error: `Invalid provider: ${request.params.name}. Must be one of: gemini, openai, claude, local` });
1893
1942
  }
@@ -1921,7 +1970,20 @@ async function settingsRoutes(app, opts) {
1921
1970
  if (!opts.onProviderUpdate) {
1922
1971
  return reply.status(501).send({ error: "Provider configuration updates are not supported in this deployment" });
1923
1972
  }
1924
- const result = opts.onProviderUpdate(name, apiKey ?? "", model, baseUrl);
1973
+ if (quota !== void 0) {
1974
+ if (typeof quota !== "object" || quota === null) {
1975
+ return reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: "quota must be an object" } });
1976
+ }
1977
+ for (const [key, val] of Object.entries(quota)) {
1978
+ if (!["maxConcurrency", "maxRequestsPerMinute", "maxRequestsPerDay"].includes(key)) {
1979
+ return reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: `Unknown quota field: ${key}` } });
1980
+ }
1981
+ if (typeof val !== "number" || !Number.isInteger(val) || val <= 0) {
1982
+ return reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: `${key} must be a positive integer` } });
1983
+ }
1984
+ }
1985
+ }
1986
+ const result = opts.onProviderUpdate(name, apiKey ?? "", model, baseUrl, quota);
1925
1987
  if (!result) {
1926
1988
  return reply.status(500).send({ error: "Failed to update provider configuration" });
1927
1989
  }
@@ -2089,6 +2151,9 @@ import crypto11 from "crypto";
2089
2151
  import { eq as eq11 } from "drizzle-orm";
2090
2152
  var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed"];
2091
2153
  async function notificationRoutes(app) {
2154
+ app.get("/notifications/events", async (_request, reply) => {
2155
+ return reply.send(VALID_EVENTS);
2156
+ });
2092
2157
  app.post("/projects/:name/notifications", async (request, reply) => {
2093
2158
  const project = resolveProjectSafe6(app, request.params.name, reply);
2094
2159
  if (!project) return;
@@ -2592,7 +2657,7 @@ function normalizeResult2(raw) {
2592
2657
  };
2593
2658
  }
2594
2659
  function buildPrompt2(keyword) {
2595
- return `Search the web for "${keyword}" and provide a comprehensive, factual answer. Include relevant sources.`;
2660
+ return keyword;
2596
2661
  }
2597
2662
  function extractResponseText(response) {
2598
2663
  try {
@@ -3967,16 +4032,17 @@ async function createServer(opts) {
3967
4032
  app.log.error({ runId, err }, "Job runner failed");
3968
4033
  });
3969
4034
  },
3970
- onProviderUpdate: (providerName, apiKey, model, baseUrl) => {
4035
+ onProviderUpdate: (providerName, apiKey, model, baseUrl, incomingQuota) => {
3971
4036
  const name = providerName;
3972
4037
  if (!(name in adapterMap)) return null;
3973
4038
  if (!opts.config.providers) opts.config.providers = {};
3974
4039
  const existing = opts.config.providers[name];
4040
+ const mergedQuota = incomingQuota ? { ...existing?.quota ?? DEFAULT_QUOTA, ...incomingQuota } : existing?.quota;
3975
4041
  opts.config.providers[name] = {
3976
4042
  apiKey: apiKey || existing?.apiKey,
3977
4043
  baseUrl: baseUrl || existing?.baseUrl,
3978
4044
  model: model || existing?.model,
3979
- quota: existing?.quota
4045
+ quota: mergedQuota
3980
4046
  };
3981
4047
  try {
3982
4048
  saveConfig(opts.config);
@@ -4138,6 +4204,7 @@ function parseKeywordResponse(raw, count) {
4138
4204
 
4139
4205
  export {
4140
4206
  providerQuotaPolicySchema,
4207
+ notificationEventSchema,
4141
4208
  apiKeys,
4142
4209
  createClient,
4143
4210
  migrate,