@ubiquity-os/plugin-sdk 3.8.1 → 3.8.4

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,80 +1,12 @@
1
1
  // src/actions.ts
2
2
  import * as core from "@actions/core";
3
+ import * as github2 from "@actions/github";
3
4
  import { Value as Value3 } from "@sinclair/typebox/value";
4
- import { Logs } from "@ubiquity-os/ubiquity-os-logger";
5
- import { config } from "dotenv";
6
-
7
- // src/error.ts
8
- import { LogReturn } from "@ubiquity-os/ubiquity-os-logger";
9
- function getErrorStatus(err) {
10
- if (!err || typeof err !== "object") return null;
11
- const candidate = err;
12
- const directStatus = candidate.status ?? candidate.response?.status;
13
- if (typeof directStatus === "number" && Number.isFinite(directStatus)) return directStatus;
14
- if (typeof directStatus === "string" && directStatus.trim()) {
15
- const parsed = Number.parseInt(directStatus, 10);
16
- if (Number.isFinite(parsed)) return parsed;
17
- }
18
- if (err instanceof Error) {
19
- const match = /LLM API error:\s*(\d{3})/i.exec(err.message);
20
- if (match) {
21
- const parsed = Number.parseInt(match[1], 10);
22
- if (Number.isFinite(parsed)) return parsed;
23
- }
24
- }
25
- return null;
26
- }
27
- function logByStatus(context, message, metadata) {
28
- const status = getErrorStatus(metadata.err);
29
- const payload = { ...metadata, ...status ? { status } : {} };
30
- if (status && status >= 500) return context.logger.error(message, payload);
31
- if (status && status >= 400) return context.logger.warn(message, payload);
32
- if (status && status >= 300) return context.logger.debug(message, payload);
33
- if (status && status >= 200) return context.logger.ok(message, payload);
34
- if (status && status >= 100) return context.logger.info(message, payload);
35
- return context.logger.error(message, payload);
36
- }
37
- function transformError(context, error) {
38
- if (error instanceof LogReturn) {
39
- return error;
40
- }
41
- if (error instanceof AggregateError) {
42
- const message = error.errors.map((err) => {
43
- if (err instanceof LogReturn) {
44
- return err.logMessage.raw;
45
- }
46
- if (err instanceof Error) {
47
- return err.message;
48
- }
49
- return String(err);
50
- }).join("\n\n");
51
- return logByStatus(context, message, { err: error });
52
- }
53
- if (error instanceof Error) {
54
- return logByStatus(context, error.message, { err: error });
55
- }
56
- return logByStatus(context, String(error), { err: error });
57
- }
5
+ import { LogReturn as LogReturn3, Logs } from "@ubiquity-os/ubiquity-os-logger";
58
6
 
59
7
  // src/helpers/runtime-info.ts
8
+ import github from "@actions/github";
60
9
  import { getRuntimeKey } from "hono/adapter";
61
-
62
- // src/helpers/github-context.ts
63
- import * as github from "@actions/github";
64
- function getGithubContext() {
65
- const override = globalThis.__UOS_GITHUB_CONTEXT__;
66
- if (override) {
67
- return override;
68
- }
69
- const module = github;
70
- const context = module.context ?? module.default?.context;
71
- if (!context) {
72
- throw new Error("GitHub context is unavailable.");
73
- }
74
- return context;
75
- }
76
-
77
- // src/helpers/runtime-info.ts
78
10
  var PluginRuntimeInfo = class _PluginRuntimeInfo {
79
11
  static _instance = null;
80
12
  _env = {};
@@ -122,11 +54,10 @@ var CfRuntimeInfo = class extends PluginRuntimeInfo {
122
54
  };
123
55
  var NodeRuntimeInfo = class extends PluginRuntimeInfo {
124
56
  get version() {
125
- return getGithubContext().sha;
57
+ return github.context.sha;
126
58
  }
127
59
  get runUrl() {
128
- const context = getGithubContext();
129
- return context.payload.repository ? `${context.payload.repository?.html_url}/actions/runs/${context.runId}` : "http://localhost";
60
+ return github.context.payload.repository ? `${github.context.payload.repository?.html_url}/actions/runs/${github.context.runId}` : "http://localhost";
130
61
  }
131
62
  };
132
63
  var DenoRuntimeInfo = class extends PluginRuntimeInfo {
@@ -219,23 +150,14 @@ function getPluginOptions(options) {
219
150
  }
220
151
 
221
152
  // src/comment.ts
222
- function logByStatus2(logger, message, status, metadata) {
223
- const payload = { ...metadata, ...status ? { status } : {} };
224
- if (status && status >= 500) return logger.error(message, payload);
225
- if (status && status >= 400) return logger.warn(message, payload);
226
- if (status && status >= 300) return logger.debug(message, payload);
227
- if (status && status >= 200) return logger.ok(message, payload);
228
- if (status && status >= 100) return logger.info(message, payload);
229
- return logger.error(message, payload);
230
- }
231
153
  var CommentHandler = class _CommentHandler {
232
154
  static HEADER_NAME = "UbiquityOS";
233
155
  _lastCommentId = { reviewCommentId: null, issueCommentId: null };
234
- async _updateIssueComment(context, params) {
156
+ async _updateIssueComment(context2, params) {
235
157
  if (!this._lastCommentId.issueCommentId) {
236
- throw context.logger.error("issueCommentId is missing");
158
+ throw context2.logger.error("issueCommentId is missing");
237
159
  }
238
- const commentData = await context.octokit.rest.issues.updateComment({
160
+ const commentData = await context2.octokit.rest.issues.updateComment({
239
161
  owner: params.owner,
240
162
  repo: params.repo,
241
163
  comment_id: this._lastCommentId.issueCommentId,
@@ -243,11 +165,11 @@ var CommentHandler = class _CommentHandler {
243
165
  });
244
166
  return { ...commentData.data, issueNumber: params.issueNumber };
245
167
  }
246
- async _updateReviewComment(context, params) {
168
+ async _updateReviewComment(context2, params) {
247
169
  if (!this._lastCommentId.reviewCommentId) {
248
- throw context.logger.error("reviewCommentId is missing");
170
+ throw context2.logger.error("reviewCommentId is missing");
249
171
  }
250
- const commentData = await context.octokit.rest.pulls.updateReviewComment({
172
+ const commentData = await context2.octokit.rest.pulls.updateReviewComment({
251
173
  owner: params.owner,
252
174
  repo: params.repo,
253
175
  comment_id: this._lastCommentId.reviewCommentId,
@@ -255,9 +177,9 @@ var CommentHandler = class _CommentHandler {
255
177
  });
256
178
  return { ...commentData.data, issueNumber: params.issueNumber };
257
179
  }
258
- async _createNewComment(context, params) {
180
+ async _createNewComment(context2, params) {
259
181
  if (params.commentId) {
260
- const commentData2 = await context.octokit.rest.pulls.createReplyForReviewComment({
182
+ const commentData2 = await context2.octokit.rest.pulls.createReplyForReviewComment({
261
183
  owner: params.owner,
262
184
  repo: params.repo,
263
185
  pull_number: params.issueNumber,
@@ -267,7 +189,7 @@ var CommentHandler = class _CommentHandler {
267
189
  this._lastCommentId.reviewCommentId = commentData2.data.id;
268
190
  return { ...commentData2.data, issueNumber: params.issueNumber };
269
191
  }
270
- const commentData = await context.octokit.rest.issues.createComment({
192
+ const commentData = await context2.octokit.rest.issues.createComment({
271
193
  owner: params.owner,
272
194
  repo: params.repo,
273
195
  issue_number: params.issueNumber,
@@ -276,58 +198,54 @@ var CommentHandler = class _CommentHandler {
276
198
  this._lastCommentId.issueCommentId = commentData.data.id;
277
199
  return { ...commentData.data, issueNumber: params.issueNumber };
278
200
  }
279
- _getIssueNumber(context) {
280
- if ("issue" in context.payload) return context.payload.issue.number;
281
- if ("pull_request" in context.payload) return context.payload.pull_request.number;
282
- if ("discussion" in context.payload) return context.payload.discussion.number;
201
+ _getIssueNumber(context2) {
202
+ if ("issue" in context2.payload) return context2.payload.issue.number;
203
+ if ("pull_request" in context2.payload) return context2.payload.pull_request.number;
204
+ if ("discussion" in context2.payload) return context2.payload.discussion.number;
283
205
  return void 0;
284
206
  }
285
- _getCommentId(context) {
286
- return "pull_request" in context.payload && "comment" in context.payload ? context.payload.comment.id : void 0;
207
+ _getCommentId(context2) {
208
+ return "pull_request" in context2.payload && "comment" in context2.payload ? context2.payload.comment.id : void 0;
287
209
  }
288
- _extractIssueContext(context) {
289
- if (!("repository" in context.payload) || !context.payload.repository?.owner?.login) {
210
+ _extractIssueContext(context2) {
211
+ if (!("repository" in context2.payload) || !context2.payload.repository?.owner?.login) {
290
212
  return null;
291
213
  }
292
- const issueNumber = this._getIssueNumber(context);
214
+ const issueNumber = this._getIssueNumber(context2);
293
215
  if (!issueNumber) return null;
294
216
  return {
295
217
  issueNumber,
296
- commentId: this._getCommentId(context),
297
- owner: context.payload.repository.owner.login,
298
- repo: context.payload.repository.name
218
+ commentId: this._getCommentId(context2),
219
+ owner: context2.payload.repository.owner.login,
220
+ repo: context2.payload.repository.name
299
221
  };
300
222
  }
301
- _processMessage(context, message) {
223
+ _processMessage(context2, message) {
302
224
  if (message instanceof Error) {
303
225
  const metadata2 = {
304
226
  message: message.message,
305
227
  name: message.name,
306
228
  stack: message.stack
307
229
  };
308
- const status = getErrorStatus(message);
309
- const logReturn = logByStatus2(context.logger, message.message, status, metadata2);
310
- return { metadata: { ...metadata2, ...status ? { status } : {} }, logMessage: logReturn.logMessage };
230
+ return { metadata: metadata2, logMessage: context2.logger.error(message.message).logMessage };
311
231
  }
312
- const stackLine = message.metadata?.error?.stack?.split("\n")[2];
313
- const callerMatch = stackLine ? /at (\S+)/.exec(stackLine) : null;
314
232
  const metadata = message.metadata ? {
315
233
  ...message.metadata,
316
234
  message: message.metadata.message,
317
235
  stack: message.metadata.stack || message.metadata.error?.stack,
318
- caller: message.metadata.caller || callerMatch?.[1]
236
+ caller: message.metadata.caller || message.metadata.error?.stack?.split("\n")[2]?.match(/at (\S+)/)?.[1]
319
237
  } : { ...message };
320
238
  return { metadata, logMessage: message.logMessage };
321
239
  }
322
- _getInstigatorName(context) {
323
- if ("installation" in context.payload && context.payload.installation && "account" in context.payload.installation && context.payload.installation?.account?.name) {
324
- return context.payload.installation?.account?.name;
240
+ _getInstigatorName(context2) {
241
+ if ("installation" in context2.payload && context2.payload.installation && "account" in context2.payload.installation && context2.payload.installation?.account?.name) {
242
+ return context2.payload.installation?.account?.name;
325
243
  }
326
- return context.payload.sender?.login || _CommentHandler.HEADER_NAME;
244
+ return context2.payload.sender?.login || _CommentHandler.HEADER_NAME;
327
245
  }
328
- _createMetadataContent(context, metadata) {
246
+ _createMetadataContent(context2, metadata) {
329
247
  const jsonPretty = sanitizeMetadata(metadata);
330
- const instigatorName = this._getInstigatorName(context);
248
+ const instigatorName = this._getInstigatorName(context2);
331
249
  const runUrl = PluginRuntimeInfo.getInstance().runUrl;
332
250
  const version = PluginRuntimeInfo.getInstance().version;
333
251
  const callingFnName = metadata.caller || "anonymous";
@@ -344,39 +262,64 @@ var CommentHandler = class _CommentHandler {
344
262
  /*
345
263
  * Creates the body for the comment, embeds the metadata and the header hidden in the body as well.
346
264
  */
347
- createCommentBody(context, message, options) {
348
- return this._createCommentBody(context, message, options);
265
+ createCommentBody(context2, message, options) {
266
+ return this._createCommentBody(context2, message, options);
349
267
  }
350
- _createCommentBody(context, message, options) {
351
- const { metadata, logMessage } = this._processMessage(context, message);
352
- const { header, jsonPretty } = this._createMetadataContent(context, metadata);
268
+ _createCommentBody(context2, message, options) {
269
+ const { metadata, logMessage } = this._processMessage(context2, message);
270
+ const { header, jsonPretty } = this._createMetadataContent(context2, metadata);
353
271
  const metadataContent = this._formatMetadataContent(logMessage, header, jsonPretty);
354
272
  return `${options?.raw ? logMessage?.raw : logMessage?.diff}
355
273
 
356
274
  ${metadataContent}
357
275
  `;
358
276
  }
359
- async postComment(context, message, options = { updateComment: true, raw: false }) {
360
- const issueContext = this._extractIssueContext(context);
277
+ async postComment(context2, message, options = { updateComment: true, raw: false }) {
278
+ const issueContext = this._extractIssueContext(context2);
361
279
  if (!issueContext) {
362
- context.logger.warn("Cannot post comment: missing issue context in payload");
280
+ context2.logger.info("Cannot post comment: missing issue context in payload");
363
281
  return null;
364
282
  }
365
- const body = this._createCommentBody(context, message, options);
283
+ const body = this._createCommentBody(context2, message, options);
366
284
  const { issueNumber, commentId, owner, repo } = issueContext;
367
285
  const params = { owner, repo, body, issueNumber };
368
286
  if (options.updateComment) {
369
- if (this._lastCommentId.issueCommentId && !("pull_request" in context.payload && "comment" in context.payload)) {
370
- return this._updateIssueComment(context, params);
287
+ if (this._lastCommentId.issueCommentId && !("pull_request" in context2.payload && "comment" in context2.payload)) {
288
+ return this._updateIssueComment(context2, params);
371
289
  }
372
- if (this._lastCommentId.reviewCommentId && "pull_request" in context.payload && "comment" in context.payload) {
373
- return this._updateReviewComment(context, params);
290
+ if (this._lastCommentId.reviewCommentId && "pull_request" in context2.payload && "comment" in context2.payload) {
291
+ return this._updateReviewComment(context2, params);
374
292
  }
375
293
  }
376
- return this._createNewComment(context, { ...params, commentId });
294
+ return this._createNewComment(context2, { ...params, commentId });
377
295
  }
378
296
  };
379
297
 
298
+ // src/error.ts
299
+ import { LogReturn as LogReturn2 } from "@ubiquity-os/ubiquity-os-logger";
300
+ function transformError(context2, error) {
301
+ let loggerError;
302
+ if (error instanceof AggregateError) {
303
+ loggerError = context2.logger.error(
304
+ error.errors.map((err) => {
305
+ if (err instanceof LogReturn2) {
306
+ return err.logMessage.raw;
307
+ } else if (err instanceof Error) {
308
+ return err.message;
309
+ } else {
310
+ return err;
311
+ }
312
+ }).join("\n\n"),
313
+ { error }
314
+ );
315
+ } else if (error instanceof Error || error instanceof LogReturn2) {
316
+ loggerError = error;
317
+ } else {
318
+ loggerError = context2.logger.error(String(error));
319
+ }
320
+ return loggerError;
321
+ }
322
+
380
323
  // src/helpers/command.ts
381
324
  import { Value } from "@sinclair/typebox/value";
382
325
  function getCommand(inputs, pluginOptions) {
@@ -447,7 +390,7 @@ async function verifySignature(publicKeyPem, inputs, signature) {
447
390
  ref: inputs.ref,
448
391
  command: inputs.command
449
392
  };
450
- const pemContents = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replace(/\s+/g, "");
393
+ const pemContents = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").trim();
451
394
  const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
452
395
  const publicKey = await crypto.subtle.importKey(
453
396
  "spki",
@@ -499,13 +442,16 @@ var inputSchema = T2.Object({
499
442
  });
500
443
 
501
444
  // src/actions.ts
502
- config();
503
- async function handleError(context, pluginOptions, error) {
445
+ async function handleError(context2, pluginOptions, error) {
504
446
  console.error(error);
505
- const loggerError = transformError(context, error);
506
- core.setFailed(loggerError.logMessage.diff);
447
+ const loggerError = transformError(context2, error);
448
+ if (loggerError instanceof LogReturn3) {
449
+ core.setFailed(loggerError.logMessage.diff);
450
+ } else if (loggerError instanceof Error) {
451
+ core.setFailed(loggerError);
452
+ }
507
453
  if (pluginOptions.postCommentOnError && loggerError) {
508
- await context.commentHandler.postComment(context, loggerError);
454
+ await context2.commentHandler.postComment(context2, loggerError);
509
455
  }
510
456
  }
511
457
  async function createActionsPlugin(handler, options) {
@@ -515,8 +461,7 @@ async function createActionsPlugin(handler, options) {
515
461
  core.setFailed("Error: PLUGIN_GITHUB_TOKEN env is not set");
516
462
  return;
517
463
  }
518
- const githubContext = getGithubContext();
519
- const body = githubContext.payload.inputs;
464
+ const body = github2.context.payload.inputs;
520
465
  const inputSchemaErrors = [...Value3.Errors(inputSchema, body)];
521
466
  if (inputSchemaErrors.length) {
522
467
  console.dir(inputSchemaErrors, { depth: null });
@@ -529,17 +474,17 @@ async function createActionsPlugin(handler, options) {
529
474
  return;
530
475
  }
531
476
  const inputs = Value3.Decode(inputSchema, body);
532
- let config2;
477
+ let config;
533
478
  if (pluginOptions.settingsSchema) {
534
479
  try {
535
- config2 = Value3.Decode(pluginOptions.settingsSchema, Value3.Default(pluginOptions.settingsSchema, inputs.settings));
480
+ config = Value3.Decode(pluginOptions.settingsSchema, Value3.Default(pluginOptions.settingsSchema, inputs.settings));
536
481
  } catch (e) {
537
482
  console.dir(...Value3.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
538
483
  core.setFailed(`Error: Invalid settings provided.`);
539
484
  throw e;
540
485
  }
541
486
  } else {
542
- config2 = inputs.settings;
487
+ config = inputs.settings;
543
488
  }
544
489
  let env;
545
490
  if (pluginOptions.envSchema) {
@@ -554,34 +499,33 @@ async function createActionsPlugin(handler, options) {
554
499
  env = process.env;
555
500
  }
556
501
  const command = getCommand(inputs, pluginOptions);
557
- const context = {
502
+ const context2 = {
558
503
  eventName: inputs.eventName,
559
504
  payload: inputs.eventPayload,
560
505
  command,
561
506
  authToken: inputs.authToken,
562
507
  ubiquityKernelToken: inputs.ubiquityKernelToken,
563
508
  octokit: new customOctokit({ auth: inputs.authToken }),
564
- config: config2,
509
+ config,
565
510
  env,
566
511
  logger: new Logs(pluginOptions.logLevel),
567
512
  commentHandler: new CommentHandler()
568
513
  };
569
514
  try {
570
- const result = await handler(context);
515
+ const result = await handler(context2);
571
516
  core.setOutput("result", result);
572
517
  if (pluginOptions?.returnDataToKernel) {
573
518
  await returnDataToKernel(pluginGithubToken, inputs.stateId, result);
574
519
  }
575
520
  } catch (error) {
576
- await handleError(context, pluginOptions, error);
521
+ await handleError(context2, pluginOptions, error);
577
522
  }
578
523
  }
579
524
  async function returnDataToKernel(repoToken, stateId, output) {
580
- const githubContext = getGithubContext();
581
525
  const octokit = new customOctokit({ auth: repoToken });
582
526
  await octokit.rest.repos.createDispatchEvent({
583
- owner: githubContext.repo.owner,
584
- repo: githubContext.repo.repo,
527
+ owner: github2.context.repo.owner,
528
+ repo: github2.context.repo.repo,
585
529
  event_type: "return-data-to-ubiquity-os-kernel",
586
530
  client_payload: {
587
531
  state_id: stateId,
@@ -643,17 +587,107 @@ function processSegment(segment, extraTags, shouldCollapseEmptyLines) {
643
587
  return s;
644
588
  }
645
589
 
590
+ // src/llm/index.ts
591
+ function normalizeBaseUrl(baseUrl) {
592
+ let normalized = baseUrl.trim();
593
+ while (normalized.endsWith("/")) {
594
+ normalized = normalized.slice(0, -1);
595
+ }
596
+ return normalized;
597
+ }
598
+ function getEnvString(name) {
599
+ if (typeof process === "undefined" || !process?.env) return "";
600
+ return String(process.env[name] ?? "").trim();
601
+ }
602
+ function getAiBaseUrl(options) {
603
+ if (typeof options.baseUrl === "string" && options.baseUrl.trim()) {
604
+ return normalizeBaseUrl(options.baseUrl);
605
+ }
606
+ const envBaseUrl = getEnvString("UBQ_AI_BASE_URL") || getEnvString("UBQ_AI_URL");
607
+ if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
608
+ return "https://ai.ubq.fi";
609
+ }
610
+ async function callLlm(options, input) {
611
+ const inputPayload = input;
612
+ const authToken = inputPayload.authToken;
613
+ const ubiquityKernelToken = inputPayload.ubiquityKernelToken;
614
+ const payload = inputPayload.payload ?? inputPayload.eventPayload;
615
+ const owner = payload?.repository?.owner?.login ?? "";
616
+ const repo = payload?.repository?.name ?? "";
617
+ const installationId = payload?.installation?.id;
618
+ if (!authToken) throw new Error("Missing authToken in inputs");
619
+ const isKernelTokenRequired = authToken.trim().startsWith("gh");
620
+ if (isKernelTokenRequired && !ubiquityKernelToken) {
621
+ throw new Error("Missing ubiquityKernelToken in inputs (kernel attestation is required for GitHub auth)");
622
+ }
623
+ const { baseUrl, model, stream: isStream, messages, ...rest } = options;
624
+ const url = `${getAiBaseUrl({ ...options, baseUrl })}/v1/chat/completions`;
625
+ const body = JSON.stringify({
626
+ ...rest,
627
+ ...model ? { model } : {},
628
+ messages,
629
+ stream: isStream ?? false
630
+ });
631
+ const headers = {
632
+ Authorization: `Bearer ${authToken}`,
633
+ "Content-Type": "application/json"
634
+ };
635
+ if (owner) headers["X-GitHub-Owner"] = owner;
636
+ if (repo) headers["X-GitHub-Repo"] = repo;
637
+ if (typeof installationId === "number" && Number.isFinite(installationId)) {
638
+ headers["X-GitHub-Installation-Id"] = String(installationId);
639
+ }
640
+ if (ubiquityKernelToken) {
641
+ headers["X-Ubiquity-Kernel-Token"] = ubiquityKernelToken;
642
+ }
643
+ const response = await fetch(url, { method: "POST", headers, body });
644
+ if (!response.ok) {
645
+ const err = await response.text();
646
+ throw new Error(`LLM API error: ${response.status} - ${err}`);
647
+ }
648
+ if (isStream) {
649
+ if (!response.body) {
650
+ throw new Error("LLM API error: missing response body for streaming request");
651
+ }
652
+ return parseSseStream(response.body);
653
+ }
654
+ return response.json();
655
+ }
656
+ async function* parseSseStream(body) {
657
+ const reader = body.getReader();
658
+ const decoder = new TextDecoder();
659
+ let buffer = "";
660
+ try {
661
+ while (true) {
662
+ const { value, done: isDone } = await reader.read();
663
+ if (isDone) break;
664
+ buffer += decoder.decode(value, { stream: true });
665
+ const events = buffer.split("\n\n");
666
+ buffer = events.pop() || "";
667
+ for (const event of events) {
668
+ if (event.startsWith("data: ")) {
669
+ const data = event.slice(6);
670
+ if (data === "[DONE]") return;
671
+ yield JSON.parse(data);
672
+ }
673
+ }
674
+ }
675
+ } finally {
676
+ reader.releaseLock();
677
+ }
678
+ }
679
+
646
680
  // src/server.ts
647
681
  import { Value as Value4 } from "@sinclair/typebox/value";
648
682
  import { Logs as Logs2 } from "@ubiquity-os/ubiquity-os-logger";
649
683
  import { Hono } from "hono";
650
684
  import { env as honoEnv } from "hono/adapter";
651
685
  import { HTTPException } from "hono/http-exception";
652
- async function handleError2(context, pluginOptions, error) {
686
+ async function handleError2(context2, pluginOptions, error) {
653
687
  console.error(error);
654
- const loggerError = transformError(context, error);
688
+ const loggerError = transformError(context2, error);
655
689
  if (pluginOptions.postCommentOnError && loggerError) {
656
- await context.commentHandler.postComment(context, loggerError);
690
+ await context2.commentHandler.postComment(context2, loggerError);
657
691
  }
658
692
  throw new HTTPException(500, { message: "Unexpected error" });
659
693
  }
@@ -678,16 +712,16 @@ function createPlugin(handler, manifest, options) {
678
712
  throw new HTTPException(400, { message: "Invalid signature" });
679
713
  }
680
714
  const inputs = Value4.Decode(inputSchema, body);
681
- let config2;
715
+ let config;
682
716
  if (pluginOptions.settingsSchema) {
683
717
  try {
684
- config2 = Value4.Decode(pluginOptions.settingsSchema, Value4.Default(pluginOptions.settingsSchema, inputs.settings));
718
+ config = Value4.Decode(pluginOptions.settingsSchema, Value4.Default(pluginOptions.settingsSchema, inputs.settings));
685
719
  } catch (e) {
686
720
  console.dir(...Value4.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
687
721
  throw e;
688
722
  }
689
723
  } else {
690
- config2 = inputs.settings;
724
+ config = inputs.settings;
691
725
  }
692
726
  let env;
693
727
  const honoEnvironment = honoEnv(ctx);
@@ -704,219 +738,27 @@ function createPlugin(handler, manifest, options) {
704
738
  const workerName = new URL(inputs.ref).hostname.split(".")[0];
705
739
  PluginRuntimeInfo.getInstance({ ...env, CLOUDFLARE_WORKER_NAME: workerName });
706
740
  const command = getCommand(inputs, pluginOptions);
707
- const context = {
741
+ const context2 = {
708
742
  eventName: inputs.eventName,
709
743
  payload: inputs.eventPayload,
710
744
  command,
711
745
  authToken: inputs.authToken,
712
746
  ubiquityKernelToken: inputs.ubiquityKernelToken,
713
747
  octokit: new customOctokit({ auth: inputs.authToken }),
714
- config: config2,
748
+ config,
715
749
  env,
716
750
  logger: new Logs2(pluginOptions.logLevel),
717
751
  commentHandler: new CommentHandler()
718
752
  };
719
753
  try {
720
- const result = await handler(context);
754
+ const result = await handler(context2);
721
755
  return ctx.json({ stateId: inputs.stateId, output: result ?? {} });
722
756
  } catch (error) {
723
- await handleError2(context, pluginOptions, error);
757
+ await handleError2(context2, pluginOptions, error);
724
758
  }
725
759
  });
726
760
  return app;
727
761
  }
728
-
729
- // src/llm/index.ts
730
- var EMPTY_STRING = "";
731
- function normalizeBaseUrl(baseUrl) {
732
- let normalized = baseUrl.trim();
733
- while (normalized.endsWith("/")) {
734
- normalized = normalized.slice(0, -1);
735
- }
736
- return normalized;
737
- }
738
- var MAX_LLM_RETRIES = 2;
739
- var RETRY_BACKOFF_MS = [250, 750];
740
- function getRetryDelayMs(attempt) {
741
- return RETRY_BACKOFF_MS[Math.min(attempt, RETRY_BACKOFF_MS.length - 1)] ?? 750;
742
- }
743
- function sleep(ms) {
744
- return new Promise((resolve) => setTimeout(resolve, ms));
745
- }
746
- function getEnvString(name) {
747
- if (typeof process === "undefined" || !process?.env) return EMPTY_STRING;
748
- return String(process.env[name] ?? EMPTY_STRING).trim();
749
- }
750
- function getAiBaseUrl(options) {
751
- if (typeof options.baseUrl === "string" && options.baseUrl.trim()) {
752
- return normalizeBaseUrl(options.baseUrl);
753
- }
754
- const envBaseUrl = getEnvString("UOS_AI_URL") || getEnvString("UOS_AI_BASE_URL");
755
- if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
756
- return "https://ai-ubq-fi.deno.dev";
757
- }
758
- async function callLlm(options, input) {
759
- const authToken = String(input.authToken ?? EMPTY_STRING).trim();
760
- if (!authToken) {
761
- const err = new Error("Missing authToken in input");
762
- err.status = 401;
763
- throw err;
764
- }
765
- const kernelToken = "ubiquityKernelToken" in input ? input.ubiquityKernelToken : void 0;
766
- const payload = getPayload(input);
767
- const { owner, repo, installationId } = getRepoMetadata(payload);
768
- ensureKernelToken(authToken, kernelToken);
769
- const { baseUrl, model, stream: isStream, messages, ...rest } = options;
770
- ensureMessages(messages);
771
- const url = buildAiUrl(options, baseUrl);
772
- const body = JSON.stringify({
773
- ...rest,
774
- ...model ? { model } : {},
775
- messages,
776
- stream: isStream ?? false
777
- });
778
- const headers = buildHeaders(authToken, {
779
- owner,
780
- repo,
781
- installationId,
782
- ubiquityKernelToken: kernelToken
783
- });
784
- const response = await fetchWithRetry(url, { method: "POST", headers, body }, MAX_LLM_RETRIES);
785
- if (isStream) {
786
- if (!response.body) {
787
- throw new Error("LLM API error: missing response body for streaming request");
788
- }
789
- return parseSseStream(response.body);
790
- }
791
- return response.json();
792
- }
793
- function ensureKernelToken(authToken, kernelToken) {
794
- const isKernelTokenRequired = authToken.startsWith("gh");
795
- if (isKernelTokenRequired && !kernelToken) {
796
- const err = new Error("Missing ubiquityKernelToken in input (kernel attestation is required for GitHub auth)");
797
- err.status = 401;
798
- throw err;
799
- }
800
- }
801
- function ensureMessages(messages) {
802
- if (!Array.isArray(messages) || messages.length === 0) {
803
- const err = new Error("messages must be a non-empty array");
804
- err.status = 400;
805
- throw err;
806
- }
807
- }
808
- function buildAiUrl(options, baseUrl) {
809
- return `${getAiBaseUrl({ ...options, baseUrl })}/v1/chat/completions`;
810
- }
811
- async function fetchWithRetry(url, options, maxRetries) {
812
- let attempt = 0;
813
- let lastError;
814
- while (attempt <= maxRetries) {
815
- try {
816
- const response = await fetch(url, options);
817
- if (response.ok) return response;
818
- const errText = await response.text();
819
- if (response.status >= 500 && attempt < maxRetries) {
820
- await sleep(getRetryDelayMs(attempt));
821
- attempt += 1;
822
- continue;
823
- }
824
- const error = new Error(`LLM API error: ${response.status} - ${errText}`);
825
- error.status = response.status;
826
- throw error;
827
- } catch (error) {
828
- lastError = error;
829
- const status = typeof error.status === "number" ? error.status : void 0;
830
- if (typeof status === "number" && status < 500) {
831
- throw error;
832
- }
833
- if (attempt >= maxRetries) throw error;
834
- await sleep(getRetryDelayMs(attempt));
835
- attempt += 1;
836
- }
837
- }
838
- throw lastError ?? new Error("LLM API error: request failed after retries");
839
- }
840
- function getPayload(input) {
841
- if ("payload" in input) {
842
- return input.payload;
843
- }
844
- return input.eventPayload;
845
- }
846
- function getRepoMetadata(payload) {
847
- const repoPayload = payload;
848
- return {
849
- owner: repoPayload?.repository?.owner?.login ?? EMPTY_STRING,
850
- repo: repoPayload?.repository?.name ?? EMPTY_STRING,
851
- installationId: repoPayload?.installation?.id
852
- };
853
- }
854
- function buildHeaders(authToken, options) {
855
- const headers = {
856
- Authorization: `Bearer ${authToken}`,
857
- "Content-Type": "application/json"
858
- };
859
- if (options.owner) headers["X-GitHub-Owner"] = options.owner;
860
- if (options.repo) headers["X-GitHub-Repo"] = options.repo;
861
- if (typeof options.installationId === "number" && Number.isFinite(options.installationId)) {
862
- headers["X-GitHub-Installation-Id"] = String(options.installationId);
863
- }
864
- if (options.ubiquityKernelToken) {
865
- headers["X-Ubiquity-Kernel-Token"] = options.ubiquityKernelToken;
866
- }
867
- return headers;
868
- }
869
- async function* parseSseStream(body) {
870
- const reader = body.getReader();
871
- const decoder = new TextDecoder();
872
- let buffer = EMPTY_STRING;
873
- try {
874
- while (true) {
875
- const { value, done: isDone } = await reader.read();
876
- if (isDone) break;
877
- buffer += decoder.decode(value, { stream: true });
878
- const { events, remainder } = splitSseEvents(buffer);
879
- buffer = remainder;
880
- for (const event of events) {
881
- const data = getEventData(event);
882
- if (!data) continue;
883
- if (data.trim() === "[DONE]") return;
884
- yield parseEventData(data);
885
- }
886
- }
887
- } finally {
888
- reader.releaseLock();
889
- }
890
- }
891
- function splitSseEvents(buffer) {
892
- const normalized = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
893
- const parts = normalized.split("\n\n");
894
- const remainder = parts.pop() ?? EMPTY_STRING;
895
- return { events: parts, remainder };
896
- }
897
- function getEventData(event) {
898
- if (!event.trim()) return null;
899
- const dataLines = event.split("\n").filter((line) => line.startsWith("data:"));
900
- if (!dataLines.length) return null;
901
- const data = dataLines.map((line) => line.startsWith("data: ") ? line.slice(6) : line.slice(5).replace(/^ /, EMPTY_STRING)).join("\n");
902
- return data || null;
903
- }
904
- function parseEventData(data) {
905
- try {
906
- return JSON.parse(data);
907
- } catch (error) {
908
- if (data.includes("\n")) {
909
- const collapsed = data.replace(/\n/g, EMPTY_STRING);
910
- try {
911
- return JSON.parse(collapsed);
912
- } catch {
913
- }
914
- }
915
- const message = error instanceof Error ? error.message : String(error);
916
- const preview = data.length > 200 ? `${data.slice(0, 200)}...` : data;
917
- throw new Error(`LLM stream parse error: ${message}. Data: ${preview}`);
918
- }
919
- }
920
762
  export {
921
763
  CommentHandler,
922
764
  callLlm,