@ubiquity-os/plugin-sdk 3.6.2 → 3.7.0

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/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
1
  // src/actions.ts
2
2
  import * as core from "@actions/core";
3
- import * as github2 from "@actions/github";
4
3
  import { Value as Value3 } from "@sinclair/typebox/value";
5
4
 
6
5
  // ../../node_modules/@ubiquity-os/ubiquity-os-logger/dist/index.js
@@ -338,8 +337,56 @@ var Logs = class _Logs {
338
337
  // src/actions.ts
339
338
  import { config } from "dotenv";
340
339
 
341
- // src/helpers/runtime-info.ts
342
- import github from "@actions/github";
340
+ // src/error.ts
341
+ function getErrorStatus(err) {
342
+ if (!err || typeof err !== "object") return null;
343
+ const candidate = err;
344
+ const directStatus = candidate.status ?? candidate.response?.status;
345
+ if (typeof directStatus === "number" && Number.isFinite(directStatus)) return directStatus;
346
+ if (typeof directStatus === "string" && directStatus.trim()) {
347
+ const parsed = Number.parseInt(directStatus, 10);
348
+ if (Number.isFinite(parsed)) return parsed;
349
+ }
350
+ if (err instanceof Error) {
351
+ const match = /LLM API error:\s*(\d{3})/i.exec(err.message);
352
+ if (match) {
353
+ const parsed = Number.parseInt(match[1], 10);
354
+ if (Number.isFinite(parsed)) return parsed;
355
+ }
356
+ }
357
+ return null;
358
+ }
359
+ function logByStatus(context, message, metadata) {
360
+ const status = getErrorStatus(metadata.err);
361
+ const payload = { ...metadata, ...status ? { status } : {} };
362
+ if (status && status >= 500) return context.logger.error(message, payload);
363
+ if (status && status >= 400) return context.logger.warn(message, payload);
364
+ if (status && status >= 300) return context.logger.debug(message, payload);
365
+ if (status && status >= 200) return context.logger.ok(message, payload);
366
+ if (status && status >= 100) return context.logger.info(message, payload);
367
+ return context.logger.error(message, payload);
368
+ }
369
+ function transformError(context, error) {
370
+ if (error instanceof LogReturn) {
371
+ return error;
372
+ }
373
+ if (error instanceof AggregateError) {
374
+ const message = error.errors.map((err) => {
375
+ if (err instanceof LogReturn) {
376
+ return err.logMessage.raw;
377
+ }
378
+ if (err instanceof Error) {
379
+ return err.message;
380
+ }
381
+ return String(err);
382
+ }).join("\n\n");
383
+ return logByStatus(context, message, { err: error });
384
+ }
385
+ if (error instanceof Error) {
386
+ return logByStatus(context, error.message, { err: error });
387
+ }
388
+ return logByStatus(context, String(error), { err: error });
389
+ }
343
390
 
344
391
  // ../../node_modules/hono/dist/helper/adapter/index.js
345
392
  var env = (c, runtime) => {
@@ -391,6 +438,21 @@ var checkUserAgentEquals = (platform) => {
391
438
  return userAgent.startsWith(platform);
392
439
  };
393
440
 
441
+ // src/helpers/github-context.ts
442
+ import * as github from "@actions/github";
443
+ function getGithubContext() {
444
+ const override = globalThis.__UOS_GITHUB_CONTEXT__;
445
+ if (override) {
446
+ return override;
447
+ }
448
+ const module = github;
449
+ const context = module.context ?? module.default?.context;
450
+ if (!context) {
451
+ throw new Error("GitHub context is unavailable.");
452
+ }
453
+ return context;
454
+ }
455
+
394
456
  // src/helpers/runtime-info.ts
395
457
  var PluginRuntimeInfo = class _PluginRuntimeInfo {
396
458
  static _instance = null;
@@ -439,10 +501,11 @@ var CfRuntimeInfo = class extends PluginRuntimeInfo {
439
501
  };
440
502
  var NodeRuntimeInfo = class extends PluginRuntimeInfo {
441
503
  get version() {
442
- return github.context.sha;
504
+ return getGithubContext().sha;
443
505
  }
444
506
  get runUrl() {
445
- return github.context.payload.repository ? `${github.context.payload.repository?.html_url}/actions/runs/${github.context.runId}` : "http://localhost";
507
+ const context = getGithubContext();
508
+ return context.payload.repository ? `${context.payload.repository?.html_url}/actions/runs/${context.runId}` : "http://localhost";
446
509
  }
447
510
  };
448
511
  var DenoRuntimeInfo = class extends PluginRuntimeInfo {
@@ -532,14 +595,23 @@ function getPluginOptions(options) {
532
595
  }
533
596
 
534
597
  // src/comment.ts
598
+ function logByStatus2(logger, message, status, metadata) {
599
+ const payload = { ...metadata, ...status ? { status } : {} };
600
+ if (status && status >= 500) return logger.error(message, payload);
601
+ if (status && status >= 400) return logger.warn(message, payload);
602
+ if (status && status >= 300) return logger.debug(message, payload);
603
+ if (status && status >= 200) return logger.ok(message, payload);
604
+ if (status && status >= 100) return logger.info(message, payload);
605
+ return logger.error(message, payload);
606
+ }
535
607
  var CommentHandler = class _CommentHandler {
536
608
  static HEADER_NAME = "UbiquityOS";
537
609
  _lastCommentId = { reviewCommentId: null, issueCommentId: null };
538
- async _updateIssueComment(context2, params) {
610
+ async _updateIssueComment(context, params) {
539
611
  if (!this._lastCommentId.issueCommentId) {
540
- throw context2.logger.error("issueCommentId is missing");
612
+ throw context.logger.error("issueCommentId is missing");
541
613
  }
542
- const commentData = await context2.octokit.rest.issues.updateComment({
614
+ const commentData = await context.octokit.rest.issues.updateComment({
543
615
  owner: params.owner,
544
616
  repo: params.repo,
545
617
  comment_id: this._lastCommentId.issueCommentId,
@@ -547,11 +619,11 @@ var CommentHandler = class _CommentHandler {
547
619
  });
548
620
  return { ...commentData.data, issueNumber: params.issueNumber };
549
621
  }
550
- async _updateReviewComment(context2, params) {
622
+ async _updateReviewComment(context, params) {
551
623
  if (!this._lastCommentId.reviewCommentId) {
552
- throw context2.logger.error("reviewCommentId is missing");
624
+ throw context.logger.error("reviewCommentId is missing");
553
625
  }
554
- const commentData = await context2.octokit.rest.pulls.updateReviewComment({
626
+ const commentData = await context.octokit.rest.pulls.updateReviewComment({
555
627
  owner: params.owner,
556
628
  repo: params.repo,
557
629
  comment_id: this._lastCommentId.reviewCommentId,
@@ -559,9 +631,9 @@ var CommentHandler = class _CommentHandler {
559
631
  });
560
632
  return { ...commentData.data, issueNumber: params.issueNumber };
561
633
  }
562
- async _createNewComment(context2, params) {
634
+ async _createNewComment(context, params) {
563
635
  if (params.commentId) {
564
- const commentData2 = await context2.octokit.rest.pulls.createReplyForReviewComment({
636
+ const commentData2 = await context.octokit.rest.pulls.createReplyForReviewComment({
565
637
  owner: params.owner,
566
638
  repo: params.repo,
567
639
  pull_number: params.issueNumber,
@@ -571,7 +643,7 @@ var CommentHandler = class _CommentHandler {
571
643
  this._lastCommentId.reviewCommentId = commentData2.data.id;
572
644
  return { ...commentData2.data, issueNumber: params.issueNumber };
573
645
  }
574
- const commentData = await context2.octokit.rest.issues.createComment({
646
+ const commentData = await context.octokit.rest.issues.createComment({
575
647
  owner: params.owner,
576
648
  repo: params.repo,
577
649
  issue_number: params.issueNumber,
@@ -580,54 +652,58 @@ var CommentHandler = class _CommentHandler {
580
652
  this._lastCommentId.issueCommentId = commentData.data.id;
581
653
  return { ...commentData.data, issueNumber: params.issueNumber };
582
654
  }
583
- _getIssueNumber(context2) {
584
- if ("issue" in context2.payload) return context2.payload.issue.number;
585
- if ("pull_request" in context2.payload) return context2.payload.pull_request.number;
586
- if ("discussion" in context2.payload) return context2.payload.discussion.number;
655
+ _getIssueNumber(context) {
656
+ if ("issue" in context.payload) return context.payload.issue.number;
657
+ if ("pull_request" in context.payload) return context.payload.pull_request.number;
658
+ if ("discussion" in context.payload) return context.payload.discussion.number;
587
659
  return void 0;
588
660
  }
589
- _getCommentId(context2) {
590
- return "pull_request" in context2.payload && "comment" in context2.payload ? context2.payload.comment.id : void 0;
661
+ _getCommentId(context) {
662
+ return "pull_request" in context.payload && "comment" in context.payload ? context.payload.comment.id : void 0;
591
663
  }
592
- _extractIssueContext(context2) {
593
- if (!("repository" in context2.payload) || !context2.payload.repository?.owner?.login) {
664
+ _extractIssueContext(context) {
665
+ if (!("repository" in context.payload) || !context.payload.repository?.owner?.login) {
594
666
  return null;
595
667
  }
596
- const issueNumber = this._getIssueNumber(context2);
668
+ const issueNumber = this._getIssueNumber(context);
597
669
  if (!issueNumber) return null;
598
670
  return {
599
671
  issueNumber,
600
- commentId: this._getCommentId(context2),
601
- owner: context2.payload.repository.owner.login,
602
- repo: context2.payload.repository.name
672
+ commentId: this._getCommentId(context),
673
+ owner: context.payload.repository.owner.login,
674
+ repo: context.payload.repository.name
603
675
  };
604
676
  }
605
- _processMessage(context2, message) {
677
+ _processMessage(context, message) {
606
678
  if (message instanceof Error) {
607
679
  const metadata2 = {
608
680
  message: message.message,
609
681
  name: message.name,
610
682
  stack: message.stack
611
683
  };
612
- return { metadata: metadata2, logMessage: context2.logger.error(message.message).logMessage };
684
+ const status = getErrorStatus(message);
685
+ const logReturn = logByStatus2(context.logger, message.message, status, metadata2);
686
+ return { metadata: { ...metadata2, ...status ? { status } : {} }, logMessage: logReturn.logMessage };
613
687
  }
688
+ const stackLine = message.metadata?.error?.stack?.split("\n")[2];
689
+ const callerMatch = stackLine ? /at (\S+)/.exec(stackLine) : null;
614
690
  const metadata = message.metadata ? {
615
691
  ...message.metadata,
616
692
  message: message.metadata.message,
617
693
  stack: message.metadata.stack || message.metadata.error?.stack,
618
- caller: message.metadata.caller || message.metadata.error?.stack?.split("\n")[2]?.match(/at (\S+)/)?.[1]
694
+ caller: message.metadata.caller || callerMatch?.[1]
619
695
  } : { ...message };
620
696
  return { metadata, logMessage: message.logMessage };
621
697
  }
622
- _getInstigatorName(context2) {
623
- if ("installation" in context2.payload && context2.payload.installation && "account" in context2.payload.installation && context2.payload.installation?.account?.name) {
624
- return context2.payload.installation?.account?.name;
698
+ _getInstigatorName(context) {
699
+ if ("installation" in context.payload && context.payload.installation && "account" in context.payload.installation && context.payload.installation?.account?.name) {
700
+ return context.payload.installation?.account?.name;
625
701
  }
626
- return context2.payload.sender?.login || _CommentHandler.HEADER_NAME;
702
+ return context.payload.sender?.login || _CommentHandler.HEADER_NAME;
627
703
  }
628
- _createMetadataContent(context2, metadata) {
704
+ _createMetadataContent(context, metadata) {
629
705
  const jsonPretty = sanitizeMetadata(metadata);
630
- const instigatorName = this._getInstigatorName(context2);
706
+ const instigatorName = this._getInstigatorName(context);
631
707
  const runUrl = PluginRuntimeInfo.getInstance().runUrl;
632
708
  const version = PluginRuntimeInfo.getInstance().version;
633
709
  const callingFnName = metadata.caller || "anonymous";
@@ -644,63 +720,39 @@ var CommentHandler = class _CommentHandler {
644
720
  /*
645
721
  * Creates the body for the comment, embeds the metadata and the header hidden in the body as well.
646
722
  */
647
- createCommentBody(context2, message, options) {
648
- return this._createCommentBody(context2, message, options);
723
+ createCommentBody(context, message, options) {
724
+ return this._createCommentBody(context, message, options);
649
725
  }
650
- _createCommentBody(context2, message, options) {
651
- const { metadata, logMessage } = this._processMessage(context2, message);
652
- const { header, jsonPretty } = this._createMetadataContent(context2, metadata);
726
+ _createCommentBody(context, message, options) {
727
+ const { metadata, logMessage } = this._processMessage(context, message);
728
+ const { header, jsonPretty } = this._createMetadataContent(context, metadata);
653
729
  const metadataContent = this._formatMetadataContent(logMessage, header, jsonPretty);
654
730
  return `${options?.raw ? logMessage?.raw : logMessage?.diff}
655
731
 
656
732
  ${metadataContent}
657
733
  `;
658
734
  }
659
- async postComment(context2, message, options = { updateComment: true, raw: false }) {
660
- const issueContext = this._extractIssueContext(context2);
735
+ async postComment(context, message, options = { updateComment: true, raw: false }) {
736
+ const issueContext = this._extractIssueContext(context);
661
737
  if (!issueContext) {
662
- context2.logger.info("Cannot post comment: missing issue context in payload");
738
+ context.logger.warn("Cannot post comment: missing issue context in payload");
663
739
  return null;
664
740
  }
665
- const body = this._createCommentBody(context2, message, options);
741
+ const body = this._createCommentBody(context, message, options);
666
742
  const { issueNumber, commentId, owner, repo } = issueContext;
667
743
  const params = { owner, repo, body, issueNumber };
668
744
  if (options.updateComment) {
669
- if (this._lastCommentId.issueCommentId && !("pull_request" in context2.payload && "comment" in context2.payload)) {
670
- return this._updateIssueComment(context2, params);
745
+ if (this._lastCommentId.issueCommentId && !("pull_request" in context.payload && "comment" in context.payload)) {
746
+ return this._updateIssueComment(context, params);
671
747
  }
672
- if (this._lastCommentId.reviewCommentId && "pull_request" in context2.payload && "comment" in context2.payload) {
673
- return this._updateReviewComment(context2, params);
748
+ if (this._lastCommentId.reviewCommentId && "pull_request" in context.payload && "comment" in context.payload) {
749
+ return this._updateReviewComment(context, params);
674
750
  }
675
751
  }
676
- return this._createNewComment(context2, { ...params, commentId });
752
+ return this._createNewComment(context, { ...params, commentId });
677
753
  }
678
754
  };
679
755
 
680
- // src/error.ts
681
- function transformError(context2, error) {
682
- let loggerError;
683
- if (error instanceof AggregateError) {
684
- loggerError = context2.logger.error(
685
- error.errors.map((err) => {
686
- if (err instanceof LogReturn) {
687
- return err.logMessage.raw;
688
- } else if (err instanceof Error) {
689
- return err.message;
690
- } else {
691
- return err;
692
- }
693
- }).join("\n\n"),
694
- { error }
695
- );
696
- } else if (error instanceof Error || error instanceof LogReturn) {
697
- loggerError = error;
698
- } else {
699
- loggerError = context2.logger.error(String(error));
700
- }
701
- return loggerError;
702
- }
703
-
704
756
  // src/helpers/command.ts
705
757
  import { Value } from "@sinclair/typebox/value";
706
758
  function getCommand(inputs, pluginOptions) {
@@ -933,7 +985,7 @@ async function verifySignature(publicKeyPem, inputs, signature) {
933
985
  ref: inputs.ref,
934
986
  command: inputs.command
935
987
  };
936
- const pemContents = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").trim();
988
+ const pemContents = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replace(/\s+/g, "");
937
989
  const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
938
990
  const publicKey = await crypto.subtle.importKey(
939
991
  "spki",
@@ -986,16 +1038,12 @@ var inputSchema = T2.Object({
986
1038
 
987
1039
  // src/actions.ts
988
1040
  config();
989
- async function handleError(context2, pluginOptions, error) {
1041
+ async function handleError(context, pluginOptions, error) {
990
1042
  console.error(error);
991
- const loggerError = transformError(context2, error);
992
- if (loggerError instanceof LogReturn) {
993
- core.setFailed(loggerError.logMessage.diff);
994
- } else if (loggerError instanceof Error) {
995
- core.setFailed(loggerError);
996
- }
1043
+ const loggerError = transformError(context, error);
1044
+ core.setFailed(loggerError.logMessage.diff);
997
1045
  if (pluginOptions.postCommentOnError && loggerError) {
998
- await context2.commentHandler.postComment(context2, loggerError);
1046
+ await context.commentHandler.postComment(context, loggerError);
999
1047
  }
1000
1048
  }
1001
1049
  async function createActionsPlugin(handler, options) {
@@ -1005,7 +1053,8 @@ async function createActionsPlugin(handler, options) {
1005
1053
  core.setFailed("Error: PLUGIN_GITHUB_TOKEN env is not set");
1006
1054
  return;
1007
1055
  }
1008
- const body = github2.context.payload.inputs;
1056
+ const githubContext = getGithubContext();
1057
+ const body = githubContext.payload.inputs;
1009
1058
  const inputSchemaErrors = [...Value3.Errors(inputSchema, body)];
1010
1059
  if (inputSchemaErrors.length) {
1011
1060
  console.dir(inputSchemaErrors, { depth: null });
@@ -1043,7 +1092,7 @@ async function createActionsPlugin(handler, options) {
1043
1092
  env2 = process.env;
1044
1093
  }
1045
1094
  const command = getCommand(inputs, pluginOptions);
1046
- const context2 = {
1095
+ const context = {
1047
1096
  eventName: inputs.eventName,
1048
1097
  payload: inputs.eventPayload,
1049
1098
  command,
@@ -1056,20 +1105,21 @@ async function createActionsPlugin(handler, options) {
1056
1105
  commentHandler: new CommentHandler()
1057
1106
  };
1058
1107
  try {
1059
- const result = await handler(context2);
1108
+ const result = await handler(context);
1060
1109
  core.setOutput("result", result);
1061
1110
  if (pluginOptions?.returnDataToKernel) {
1062
1111
  await returnDataToKernel(pluginGithubToken, inputs.stateId, result);
1063
1112
  }
1064
1113
  } catch (error) {
1065
- await handleError(context2, pluginOptions, error);
1114
+ await handleError(context, pluginOptions, error);
1066
1115
  }
1067
1116
  }
1068
1117
  async function returnDataToKernel(repoToken, stateId, output) {
1118
+ const githubContext = getGithubContext();
1069
1119
  const octokit = new customOctokit({ auth: repoToken });
1070
1120
  await octokit.rest.repos.createDispatchEvent({
1071
- owner: github2.context.repo.owner,
1072
- repo: github2.context.repo.repo,
1121
+ owner: githubContext.repo.owner,
1122
+ repo: githubContext.repo.repo,
1073
1123
  event_type: "return-data-to-ubiquity-os-kernel",
1074
1124
  client_payload: {
1075
1125
  state_id: stateId,
@@ -1136,7 +1186,7 @@ import { Value as Value4 } from "@sinclair/typebox/value";
1136
1186
 
1137
1187
  // ../../node_modules/hono/dist/compose.js
1138
1188
  var compose = (middleware, onError, onNotFound) => {
1139
- return (context2, next) => {
1189
+ return (context, next) => {
1140
1190
  let index = -1;
1141
1191
  return dispatch(0);
1142
1192
  async function dispatch(i) {
@@ -1149,31 +1199,31 @@ var compose = (middleware, onError, onNotFound) => {
1149
1199
  let handler;
1150
1200
  if (middleware[i]) {
1151
1201
  handler = middleware[i][0][0];
1152
- context2.req.routeIndex = i;
1202
+ context.req.routeIndex = i;
1153
1203
  } else {
1154
1204
  handler = i === middleware.length && next || void 0;
1155
1205
  }
1156
1206
  if (handler) {
1157
1207
  try {
1158
- res = await handler(context2, () => dispatch(i + 1));
1208
+ res = await handler(context, () => dispatch(i + 1));
1159
1209
  } catch (err) {
1160
1210
  if (err instanceof Error && onError) {
1161
- context2.error = err;
1162
- res = await onError(err, context2);
1211
+ context.error = err;
1212
+ res = await onError(err, context);
1163
1213
  isError = true;
1164
1214
  } else {
1165
1215
  throw err;
1166
1216
  }
1167
1217
  }
1168
1218
  } else {
1169
- if (context2.finalized === false && onNotFound) {
1170
- res = await onNotFound(context2);
1219
+ if (context.finalized === false && onNotFound) {
1220
+ res = await onNotFound(context);
1171
1221
  }
1172
1222
  }
1173
- if (res && (context2.finalized === false || isError)) {
1174
- context2.res = res;
1223
+ if (res && (context.finalized === false || isError)) {
1224
+ context.res = res;
1175
1225
  }
1176
- return context2;
1226
+ return context;
1177
1227
  }
1178
1228
  };
1179
1229
  };
@@ -1575,7 +1625,7 @@ var raw = (value, callbacks) => {
1575
1625
  escapedString.callbacks = callbacks;
1576
1626
  return escapedString;
1577
1627
  };
1578
- var resolveCallback = async (str, phase, preserveCallbacks, context2, buffer) => {
1628
+ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
1579
1629
  if (typeof str === "object" && !(str instanceof String)) {
1580
1630
  if (!(str instanceof Promise)) {
1581
1631
  str = str.toString();
@@ -1593,9 +1643,9 @@ var resolveCallback = async (str, phase, preserveCallbacks, context2, buffer) =>
1593
1643
  } else {
1594
1644
  buffer = [str];
1595
1645
  }
1596
- const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context: context2 }))).then(
1646
+ const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then(
1597
1647
  (res) => Promise.all(
1598
- res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context2, buffer))
1648
+ res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))
1599
1649
  ).then(() => buffer[0])
1600
1650
  );
1601
1651
  if (preserveCallbacks) {
@@ -1988,13 +2038,13 @@ var Hono = class {
1988
2038
  const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler);
1989
2039
  return (async () => {
1990
2040
  try {
1991
- const context2 = await composed(c);
1992
- if (!context2.finalized) {
2041
+ const context = await composed(c);
2042
+ if (!context.finalized) {
1993
2043
  throw new Error(
1994
2044
  "Context is not finalized. Did you forget to return a Response object or `await next()`?"
1995
2045
  );
1996
2046
  }
1997
- return context2.res;
2047
+ return context.res;
1998
2048
  } catch (err) {
1999
2049
  return this.#handleError(err, c);
2000
2050
  }
@@ -2053,7 +2103,7 @@ var Node = class {
2053
2103
  #index;
2054
2104
  #varIndex;
2055
2105
  #children = /* @__PURE__ */ Object.create(null);
2056
- insert(tokens, index, paramMap, context2, pathErrorCheckOnly) {
2106
+ insert(tokens, index, paramMap, context, pathErrorCheckOnly) {
2057
2107
  if (tokens.length === 0) {
2058
2108
  if (this.#index !== void 0) {
2059
2109
  throw PATH_ERROR;
@@ -2091,7 +2141,7 @@ var Node = class {
2091
2141
  }
2092
2142
  node = this.#children[regexpStr] = new Node();
2093
2143
  if (name !== "") {
2094
- node.#varIndex = context2.varIndex++;
2144
+ node.#varIndex = context.varIndex++;
2095
2145
  }
2096
2146
  }
2097
2147
  if (!pathErrorCheckOnly && name !== "") {
@@ -2111,7 +2161,7 @@ var Node = class {
2111
2161
  node = this.#children[token] = new Node();
2112
2162
  }
2113
2163
  }
2114
- node.insert(restTokens, index, paramMap, context2, pathErrorCheckOnly);
2164
+ node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly);
2115
2165
  }
2116
2166
  buildRegExpStr() {
2117
2167
  const childKeys = Object.keys(this.#children).sort(compareKey);
@@ -2653,11 +2703,11 @@ var HTTPException = class extends Error {
2653
2703
  };
2654
2704
 
2655
2705
  // src/server.ts
2656
- async function handleError2(context2, pluginOptions, error) {
2706
+ async function handleError2(context, pluginOptions, error) {
2657
2707
  console.error(error);
2658
- const loggerError = transformError(context2, error);
2708
+ const loggerError = transformError(context, error);
2659
2709
  if (pluginOptions.postCommentOnError && loggerError) {
2660
- await context2.commentHandler.postComment(context2, loggerError);
2710
+ await context.commentHandler.postComment(context, loggerError);
2661
2711
  }
2662
2712
  throw new HTTPException(500, { message: "Unexpected error" });
2663
2713
  }
@@ -2708,7 +2758,7 @@ function createPlugin(handler, manifest, options) {
2708
2758
  const workerName = new URL(inputs.ref).hostname.split(".")[0];
2709
2759
  PluginRuntimeInfo.getInstance({ ...env2, CLOUDFLARE_WORKER_NAME: workerName });
2710
2760
  const command = getCommand(inputs, pluginOptions);
2711
- const context2 = {
2761
+ const context = {
2712
2762
  eventName: inputs.eventName,
2713
2763
  payload: inputs.eventPayload,
2714
2764
  command,
@@ -2721,96 +2771,195 @@ function createPlugin(handler, manifest, options) {
2721
2771
  commentHandler: new CommentHandler()
2722
2772
  };
2723
2773
  try {
2724
- const result = await handler(context2);
2774
+ const result = await handler(context);
2725
2775
  return ctx.json({ stateId: inputs.stateId, output: result ?? {} });
2726
2776
  } catch (error) {
2727
- await handleError2(context2, pluginOptions, error);
2777
+ await handleError2(context, pluginOptions, error);
2728
2778
  }
2729
2779
  });
2730
2780
  return app;
2731
2781
  }
2732
2782
 
2733
2783
  // src/llm/index.ts
2784
+ var EMPTY_STRING = "";
2734
2785
  function normalizeBaseUrl(baseUrl) {
2735
- return baseUrl.trim().replace(/\/+$/, "");
2786
+ let normalized = baseUrl.trim();
2787
+ while (normalized.endsWith("/")) {
2788
+ normalized = normalized.slice(0, -1);
2789
+ }
2790
+ return normalized;
2791
+ }
2792
+ var MAX_LLM_RETRIES = 2;
2793
+ var RETRY_BACKOFF_MS = [250, 750];
2794
+ function getRetryDelayMs(attempt) {
2795
+ return RETRY_BACKOFF_MS[Math.min(attempt, RETRY_BACKOFF_MS.length - 1)] ?? 750;
2796
+ }
2797
+ function sleep(ms) {
2798
+ return new Promise((resolve) => setTimeout(resolve, ms));
2736
2799
  }
2737
2800
  function getEnvString(name) {
2738
- if (typeof process === "undefined" || !process?.env) return "";
2739
- return String(process.env[name] ?? "").trim();
2801
+ if (typeof process === "undefined" || !process?.env) return EMPTY_STRING;
2802
+ return String(process.env[name] ?? EMPTY_STRING).trim();
2740
2803
  }
2741
2804
  function getAiBaseUrl(options) {
2742
2805
  if (typeof options.baseUrl === "string" && options.baseUrl.trim()) {
2743
2806
  return normalizeBaseUrl(options.baseUrl);
2744
2807
  }
2745
- const envBaseUrl = getEnvString("UBQ_AI_BASE_URL") || getEnvString("UBQ_AI_URL");
2808
+ const envBaseUrl = getEnvString("UOS_AI_URL") || getEnvString("UOS_AI_BASE_URL");
2746
2809
  if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
2747
- return "https://ai.ubq.fi";
2810
+ return "https://ai-ubq-fi.deno.dev";
2748
2811
  }
2749
2812
  async function callLlm(options, input) {
2750
- const authToken = input?.authToken ?? "";
2751
- const ubiquityKernelToken = input?.ubiquityKernelToken ?? "";
2752
- const payload = input?.payload;
2753
- const owner = payload?.repository?.owner?.login ?? "";
2754
- const repo = payload?.repository?.name ?? "";
2755
- const installationId = payload?.installation?.id;
2756
- if (!authToken) throw new Error("Missing authToken in inputs");
2757
- const requiresKernelToken = authToken.trim().startsWith("gh");
2758
- if (requiresKernelToken && !ubiquityKernelToken) {
2759
- throw new Error("Missing ubiquityKernelToken in inputs (kernel attestation is required for GitHub auth)");
2760
- }
2761
- const url = `${getAiBaseUrl(options)}/v1/chat/completions`;
2762
- const { baseUrl: _baseUrl, model, stream, messages, ...rest } = options;
2813
+ const authToken = String(input.authToken ?? EMPTY_STRING).trim();
2814
+ if (!authToken) {
2815
+ const err = new Error("Missing authToken in input");
2816
+ err.status = 401;
2817
+ throw err;
2818
+ }
2819
+ const kernelToken = "ubiquityKernelToken" in input ? input.ubiquityKernelToken : void 0;
2820
+ const payload = getPayload(input);
2821
+ const { owner, repo, installationId } = getRepoMetadata(payload);
2822
+ ensureKernelToken(authToken, kernelToken);
2823
+ const { baseUrl, model, stream: isStream, messages, ...rest } = options;
2824
+ ensureMessages(messages);
2825
+ const url = buildAiUrl(options, baseUrl);
2763
2826
  const body = JSON.stringify({
2764
2827
  ...rest,
2765
2828
  ...model ? { model } : {},
2766
2829
  messages,
2767
- stream: stream ?? false
2830
+ stream: isStream ?? false
2768
2831
  });
2832
+ const headers = buildHeaders(authToken, {
2833
+ owner,
2834
+ repo,
2835
+ installationId,
2836
+ ubiquityKernelToken: kernelToken
2837
+ });
2838
+ const response = await fetchWithRetry(url, { method: "POST", headers, body }, MAX_LLM_RETRIES);
2839
+ if (isStream) {
2840
+ if (!response.body) {
2841
+ throw new Error("LLM API error: missing response body for streaming request");
2842
+ }
2843
+ return parseSseStream(response.body);
2844
+ }
2845
+ return response.json();
2846
+ }
2847
+ function ensureKernelToken(authToken, kernelToken) {
2848
+ const isKernelTokenRequired = authToken.startsWith("gh");
2849
+ if (isKernelTokenRequired && !kernelToken) {
2850
+ const err = new Error("Missing ubiquityKernelToken in input (kernel attestation is required for GitHub auth)");
2851
+ err.status = 401;
2852
+ throw err;
2853
+ }
2854
+ }
2855
+ function ensureMessages(messages) {
2856
+ if (!Array.isArray(messages) || messages.length === 0) {
2857
+ const err = new Error("messages must be a non-empty array");
2858
+ err.status = 400;
2859
+ throw err;
2860
+ }
2861
+ }
2862
+ function buildAiUrl(options, baseUrl) {
2863
+ return `${getAiBaseUrl({ ...options, baseUrl })}/v1/chat/completions`;
2864
+ }
2865
+ async function fetchWithRetry(url, options, maxRetries) {
2866
+ let attempt = 0;
2867
+ let lastError;
2868
+ while (attempt <= maxRetries) {
2869
+ try {
2870
+ const response = await fetch(url, options);
2871
+ if (response.ok) return response;
2872
+ const errText = await response.text();
2873
+ if (response.status >= 500 && attempt < maxRetries) {
2874
+ await sleep(getRetryDelayMs(attempt));
2875
+ attempt += 1;
2876
+ continue;
2877
+ }
2878
+ const error = new Error(`LLM API error: ${response.status} - ${errText}`);
2879
+ error.status = response.status;
2880
+ throw error;
2881
+ } catch (error) {
2882
+ lastError = error;
2883
+ if (attempt >= maxRetries) throw error;
2884
+ await sleep(getRetryDelayMs(attempt));
2885
+ attempt += 1;
2886
+ }
2887
+ }
2888
+ throw lastError ?? new Error("LLM API error: request failed after retries");
2889
+ }
2890
+ function getPayload(input) {
2891
+ if ("payload" in input) {
2892
+ return input.payload;
2893
+ }
2894
+ return input.eventPayload;
2895
+ }
2896
+ function getRepoMetadata(payload) {
2897
+ const repoPayload = payload;
2898
+ return {
2899
+ owner: repoPayload?.repository?.owner?.login ?? EMPTY_STRING,
2900
+ repo: repoPayload?.repository?.name ?? EMPTY_STRING,
2901
+ installationId: repoPayload?.installation?.id
2902
+ };
2903
+ }
2904
+ function buildHeaders(authToken, options) {
2769
2905
  const headers = {
2770
2906
  Authorization: `Bearer ${authToken}`,
2771
2907
  "Content-Type": "application/json"
2772
2908
  };
2773
- if (owner) headers["X-GitHub-Owner"] = owner;
2774
- if (repo) headers["X-GitHub-Repo"] = repo;
2775
- if (typeof installationId === "number" && Number.isFinite(installationId)) {
2776
- headers["X-GitHub-Installation-Id"] = String(installationId);
2777
- }
2778
- if (ubiquityKernelToken) {
2779
- headers["X-Ubiquity-Kernel-Token"] = ubiquityKernelToken;
2909
+ if (options.owner) headers["X-GitHub-Owner"] = options.owner;
2910
+ if (options.repo) headers["X-GitHub-Repo"] = options.repo;
2911
+ if (typeof options.installationId === "number" && Number.isFinite(options.installationId)) {
2912
+ headers["X-GitHub-Installation-Id"] = String(options.installationId);
2780
2913
  }
2781
- const response = await fetch(url, { method: "POST", headers, body });
2782
- if (!response.ok) {
2783
- const err = await response.text();
2784
- throw new Error(`LLM API error: ${response.status} - ${err}`);
2785
- }
2786
- if (options.stream) {
2787
- return parseSseStream(response.body);
2914
+ if (options.ubiquityKernelToken) {
2915
+ headers["X-Ubiquity-Kernel-Token"] = options.ubiquityKernelToken;
2788
2916
  }
2789
- return response.json();
2917
+ return headers;
2790
2918
  }
2791
2919
  async function* parseSseStream(body) {
2792
2920
  const reader = body.getReader();
2793
2921
  const decoder = new TextDecoder();
2794
- let buffer = "";
2922
+ let buffer = EMPTY_STRING;
2795
2923
  try {
2796
2924
  while (true) {
2797
- const { value, done } = await reader.read();
2798
- if (done) break;
2925
+ const { value, done: isDone } = await reader.read();
2926
+ if (isDone) break;
2799
2927
  buffer += decoder.decode(value, { stream: true });
2800
- const events = buffer.split("\n\n");
2801
- buffer = events.pop() || "";
2928
+ const { events, remainder } = splitSseEvents(buffer);
2929
+ buffer = remainder;
2802
2930
  for (const event of events) {
2803
- if (event.startsWith("data: ")) {
2804
- const data = event.slice(6);
2805
- if (data === "[DONE]") return;
2806
- yield JSON.parse(data);
2807
- }
2931
+ const data = getEventData(event);
2932
+ if (!data) continue;
2933
+ if (data.trim() === "[DONE]") return;
2934
+ yield parseEventData(data);
2808
2935
  }
2809
2936
  }
2810
2937
  } finally {
2811
2938
  reader.releaseLock();
2812
2939
  }
2813
2940
  }
2941
+ function splitSseEvents(buffer) {
2942
+ const normalized = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2943
+ const parts = normalized.split("\n\n");
2944
+ const remainder = parts.pop() ?? EMPTY_STRING;
2945
+ return { events: parts, remainder };
2946
+ }
2947
+ function getEventData(event) {
2948
+ if (!event.trim()) return null;
2949
+ const dataLines = event.split("\n").filter((line) => line.startsWith("data:"));
2950
+ if (!dataLines.length) return null;
2951
+ const data = dataLines.map((line) => line.startsWith("data: ") ? line.slice(6) : line.slice(5).replace(/^ /, EMPTY_STRING)).join("\n");
2952
+ return data || null;
2953
+ }
2954
+ function parseEventData(data) {
2955
+ try {
2956
+ return JSON.parse(data);
2957
+ } catch (error) {
2958
+ const message = error instanceof Error ? error.message : String(error);
2959
+ const preview = data.length > 200 ? `${data.slice(0, 200)}...` : data;
2960
+ throw new Error(`LLM stream parse error: ${message}. Data: ${preview}`);
2961
+ }
2962
+ }
2814
2963
  export {
2815
2964
  CommentHandler,
2816
2965
  callLlm,