@ubiquity-os/plugin-sdk 3.8.1 → 3.9.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
@@ -2,7 +2,6 @@
2
2
  import * as core from "@actions/core";
3
3
  import { Value as Value3 } from "@sinclair/typebox/value";
4
4
  import { Logs } from "@ubiquity-os/ubiquity-os-logger";
5
- import { config } from "dotenv";
6
5
 
7
6
  // src/error.ts
8
7
  import { LogReturn } from "@ubiquity-os/ubiquity-os-logger";
@@ -219,6 +218,57 @@ function getPluginOptions(options) {
219
218
  }
220
219
 
221
220
  // src/comment.ts
221
+ var COMMAND_RESPONSE_KIND = "command-response";
222
+ var COMMAND_RESPONSE_MARKER = `"commentKind": "${COMMAND_RESPONSE_KIND}"`;
223
+ var COMMAND_RESPONSE_COMMENT_LIMIT = 50;
224
+ var RECENT_COMMENTS_QUERY = (
225
+ /* GraphQL */
226
+ `
227
+ query ($owner: String!, $repo: String!, $number: Int!, $last: Int!) {
228
+ repository(owner: $owner, name: $repo) {
229
+ issueOrPullRequest(number: $number) {
230
+ __typename
231
+ ... on Issue {
232
+ comments(last: $last) {
233
+ nodes {
234
+ id
235
+ body
236
+ isMinimized
237
+ minimizedReason
238
+ author {
239
+ login
240
+ }
241
+ }
242
+ }
243
+ }
244
+ ... on PullRequest {
245
+ comments(last: $last) {
246
+ nodes {
247
+ id
248
+ body
249
+ isMinimized
250
+ minimizedReason
251
+ author {
252
+ login
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ `
261
+ );
262
+ var MINIMIZE_COMMENT_MUTATION = `
263
+ mutation($id: ID!, $classifier: ReportedContentClassifiers!) {
264
+ minimizeComment(input: { subjectId: $id, classifier: $classifier }) {
265
+ minimizedComment {
266
+ isMinimized
267
+ minimizedReason
268
+ }
269
+ }
270
+ }
271
+ `;
222
272
  function logByStatus2(logger, message, status, metadata) {
223
273
  const payload = { ...metadata, ...status ? { status } : {} };
224
274
  if (status && status >= 500) return logger.error(message, payload);
@@ -231,6 +281,7 @@ function logByStatus2(logger, message, status, metadata) {
231
281
  var CommentHandler = class _CommentHandler {
232
282
  static HEADER_NAME = "UbiquityOS";
233
283
  _lastCommentId = { reviewCommentId: null, issueCommentId: null };
284
+ _commandResponsePolicyApplied = false;
234
285
  async _updateIssueComment(context, params) {
235
286
  if (!this._lastCommentId.issueCommentId) {
236
287
  throw context.logger.error("issueCommentId is missing");
@@ -285,6 +336,11 @@ var CommentHandler = class _CommentHandler {
285
336
  _getCommentId(context) {
286
337
  return "pull_request" in context.payload && "comment" in context.payload ? context.payload.comment.id : void 0;
287
338
  }
339
+ _getCommentNodeId(context) {
340
+ const payload = context.payload;
341
+ const nodeId = payload.comment?.node_id;
342
+ return typeof nodeId === "string" && nodeId.trim() ? nodeId : null;
343
+ }
288
344
  _extractIssueContext(context) {
289
345
  if (!("repository" in context.payload) || !context.payload.repository?.owner?.login) {
290
346
  return null;
@@ -298,6 +354,85 @@ var CommentHandler = class _CommentHandler {
298
354
  repo: context.payload.repository.name
299
355
  };
300
356
  }
357
+ _extractIssueLocator(context) {
358
+ if (!("issue" in context.payload) && !("pull_request" in context.payload)) {
359
+ return null;
360
+ }
361
+ const issueContext = this._extractIssueContext(context);
362
+ if (!issueContext) return null;
363
+ return {
364
+ owner: issueContext.owner,
365
+ repo: issueContext.repo,
366
+ issueNumber: issueContext.issueNumber
367
+ };
368
+ }
369
+ _shouldApplyCommandResponsePolicy(context) {
370
+ return Boolean(context.command);
371
+ }
372
+ _isCommandResponseComment(body) {
373
+ return typeof body === "string" && body.includes(COMMAND_RESPONSE_MARKER);
374
+ }
375
+ _getGraphqlClient(context) {
376
+ const graphql = context.octokit.graphql;
377
+ return typeof graphql === "function" ? graphql : null;
378
+ }
379
+ async _fetchRecentComments(context, locator, last = COMMAND_RESPONSE_COMMENT_LIMIT) {
380
+ const graphql = this._getGraphqlClient(context);
381
+ if (!graphql) return [];
382
+ try {
383
+ const data = await graphql(RECENT_COMMENTS_QUERY, {
384
+ owner: locator.owner,
385
+ repo: locator.repo,
386
+ number: locator.issueNumber,
387
+ last
388
+ });
389
+ const nodes = data.repository?.issueOrPullRequest?.comments?.nodes ?? [];
390
+ return nodes.filter((node) => Boolean(node));
391
+ } catch (error) {
392
+ context.logger.debug("Failed to fetch recent comments (non-fatal)", { err: error });
393
+ return [];
394
+ }
395
+ }
396
+ _findPreviousCommandResponseComment(comments, currentCommentId) {
397
+ for (let idx = comments.length - 1; idx >= 0; idx -= 1) {
398
+ const comment = comments[idx];
399
+ if (!comment) continue;
400
+ if (currentCommentId && comment.id === currentCommentId) continue;
401
+ if (this._isCommandResponseComment(comment.body)) {
402
+ return comment;
403
+ }
404
+ }
405
+ return null;
406
+ }
407
+ async _minimizeComment(context, commentNodeId, classifier = "RESOLVED") {
408
+ const graphql = this._getGraphqlClient(context);
409
+ if (!graphql) return;
410
+ try {
411
+ await graphql(MINIMIZE_COMMENT_MUTATION, {
412
+ id: commentNodeId,
413
+ classifier
414
+ });
415
+ } catch (error) {
416
+ context.logger.debug("Failed to minimize comment (non-fatal)", { err: error, commentNodeId });
417
+ }
418
+ }
419
+ async _applyCommandResponsePolicy(context) {
420
+ if (this._commandResponsePolicyApplied) return;
421
+ this._commandResponsePolicyApplied = true;
422
+ if (!this._shouldApplyCommandResponsePolicy(context)) return;
423
+ const locator = this._extractIssueLocator(context);
424
+ const commentNodeId = this._getCommentNodeId(context);
425
+ const comments = locator ? await this._fetchRecentComments(context, locator) : [];
426
+ const current = commentNodeId ? comments.find((comment) => comment.id === commentNodeId) : null;
427
+ const isCurrentMinimized = current?.isMinimized ?? false;
428
+ if (commentNodeId && !isCurrentMinimized) {
429
+ await this._minimizeComment(context, commentNodeId);
430
+ }
431
+ const previous = this._findPreviousCommandResponseComment(comments, commentNodeId);
432
+ if (previous && !previous.isMinimized) {
433
+ await this._minimizeComment(context, previous.id);
434
+ }
435
+ }
301
436
  _processMessage(context, message) {
302
437
  if (message instanceof Error) {
303
438
  const metadata2 = {
@@ -349,7 +484,9 @@ var CommentHandler = class _CommentHandler {
349
484
  }
350
485
  _createCommentBody(context, message, options) {
351
486
  const { metadata, logMessage } = this._processMessage(context, message);
352
- const { header, jsonPretty } = this._createMetadataContent(context, metadata);
487
+ const shouldTagCommandResponse = options?.commentKind && typeof metadata === "object" && !("commentKind" in metadata);
488
+ const metadataWithKind = shouldTagCommandResponse ? { ...metadata, commentKind: options?.commentKind } : metadata;
489
+ const { header, jsonPretty } = this._createMetadataContent(context, metadataWithKind);
353
490
  const metadataContent = this._formatMetadataContent(logMessage, header, jsonPretty);
354
491
  return `${options?.raw ? logMessage?.raw : logMessage?.diff}
355
492
 
@@ -357,12 +494,17 @@ ${metadataContent}
357
494
  `;
358
495
  }
359
496
  async postComment(context, message, options = { updateComment: true, raw: false }) {
497
+ await this._applyCommandResponsePolicy(context);
360
498
  const issueContext = this._extractIssueContext(context);
361
499
  if (!issueContext) {
362
500
  context.logger.warn("Cannot post comment: missing issue context in payload");
363
501
  return null;
364
502
  }
365
- const body = this._createCommentBody(context, message, options);
503
+ const shouldTagCommandResponse = this._shouldApplyCommandResponsePolicy(context);
504
+ const body = this._createCommentBody(context, message, {
505
+ ...options,
506
+ commentKind: options.commentKind ?? (shouldTagCommandResponse ? COMMAND_RESPONSE_KIND : void 0)
507
+ });
366
508
  const { issueNumber, commentId, owner, repo } = issueContext;
367
509
  const params = { owner, repo, body, issueNumber };
368
510
  if (options.updateComment) {
@@ -499,7 +641,6 @@ var inputSchema = T2.Object({
499
641
  });
500
642
 
501
643
  // src/actions.ts
502
- config();
503
644
  async function handleError(context, pluginOptions, error) {
504
645
  console.error(error);
505
646
  const loggerError = transformError(context, error);
@@ -508,68 +649,90 @@ async function handleError(context, pluginOptions, error) {
508
649
  await context.commentHandler.postComment(context, loggerError);
509
650
  }
510
651
  }
511
- async function createActionsPlugin(handler, options) {
512
- const pluginOptions = getPluginOptions(options);
513
- const pluginGithubToken = process.env.PLUGIN_GITHUB_TOKEN;
514
- if (!pluginGithubToken) {
652
+ function getDispatchTokenOrFail(pluginOptions) {
653
+ if (!pluginOptions.returnDataToKernel) return null;
654
+ const token = process.env.PLUGIN_GITHUB_TOKEN;
655
+ if (!token) {
515
656
  core.setFailed("Error: PLUGIN_GITHUB_TOKEN env is not set");
516
- return;
657
+ return null;
517
658
  }
659
+ return token;
660
+ }
661
+ async function getInputsOrFail(pluginOptions) {
518
662
  const githubContext = getGithubContext();
519
663
  const body = githubContext.payload.inputs;
520
664
  const inputSchemaErrors = [...Value3.Errors(inputSchema, body)];
521
665
  if (inputSchemaErrors.length) {
522
666
  console.dir(inputSchemaErrors, { depth: null });
523
667
  core.setFailed(`Error: Invalid inputs payload: ${inputSchemaErrors.map((o) => o.message).join(", ")}`);
524
- return;
525
- }
526
- const signature = body.signature;
527
- if (!pluginOptions.bypassSignatureVerification && !await verifySignature(pluginOptions.kernelPublicKey, body, signature)) {
528
- core.setFailed(`Error: Invalid signature`);
529
- return;
668
+ return null;
530
669
  }
531
- const inputs = Value3.Decode(inputSchema, body);
532
- let config2;
533
- if (pluginOptions.settingsSchema) {
534
- try {
535
- config2 = Value3.Decode(pluginOptions.settingsSchema, Value3.Default(pluginOptions.settingsSchema, inputs.settings));
536
- } catch (e) {
537
- console.dir(...Value3.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
538
- core.setFailed(`Error: Invalid settings provided.`);
539
- throw e;
670
+ if (!pluginOptions.bypassSignatureVerification) {
671
+ const signature = typeof body.signature === "string" ? body.signature : "";
672
+ if (!signature) {
673
+ core.setFailed("Error: Missing signature");
674
+ return null;
540
675
  }
541
- } else {
542
- config2 = inputs.settings;
543
- }
544
- let env;
545
- if (pluginOptions.envSchema) {
546
- try {
547
- env = Value3.Decode(pluginOptions.envSchema, Value3.Default(pluginOptions.envSchema, process.env));
548
- } catch (e) {
549
- console.dir(...Value3.Errors(pluginOptions.envSchema, process.env), { depth: null });
550
- core.setFailed(`Error: Invalid environment provided.`);
551
- throw e;
676
+ const isValid = await verifySignature(pluginOptions.kernelPublicKey, body, signature);
677
+ if (!isValid) {
678
+ core.setFailed("Error: Invalid signature");
679
+ return null;
552
680
  }
553
- } else {
554
- env = process.env;
555
681
  }
556
- const command = getCommand(inputs, pluginOptions);
682
+ return Value3.Decode(inputSchema, body);
683
+ }
684
+ function decodeWithSchema(schema, value, errorMessage) {
685
+ if (!schema) {
686
+ return { value };
687
+ }
688
+ try {
689
+ return { value: Value3.Decode(schema, Value3.Default(schema, value)) };
690
+ } catch (error) {
691
+ console.dir(...Value3.Errors(schema, value), { depth: null });
692
+ const err = new Error(errorMessage);
693
+ err.cause = error;
694
+ return { value: null, error: err };
695
+ }
696
+ }
697
+ async function createActionsPlugin(handler, options) {
698
+ const pluginOptions = getPluginOptions(options);
699
+ const pluginGithubToken = getDispatchTokenOrFail(pluginOptions);
700
+ if (pluginOptions.returnDataToKernel && !pluginGithubToken) {
701
+ return;
702
+ }
703
+ const inputs = await getInputsOrFail(pluginOptions);
704
+ if (!inputs) {
705
+ return;
706
+ }
557
707
  const context = {
558
708
  eventName: inputs.eventName,
559
709
  payload: inputs.eventPayload,
560
- command,
710
+ command: null,
561
711
  authToken: inputs.authToken,
562
712
  ubiquityKernelToken: inputs.ubiquityKernelToken,
563
713
  octokit: new customOctokit({ auth: inputs.authToken }),
564
- config: config2,
565
- env,
714
+ config: inputs.settings,
715
+ env: process.env,
566
716
  logger: new Logs(pluginOptions.logLevel),
567
717
  commentHandler: new CommentHandler()
568
718
  };
719
+ const configResult = decodeWithSchema(pluginOptions.settingsSchema, inputs.settings, "Error: Invalid settings provided.");
720
+ if (!configResult.value) {
721
+ await handleError(context, pluginOptions, configResult.error ?? new Error("Error: Invalid settings provided."));
722
+ return;
723
+ }
724
+ context.config = configResult.value;
725
+ const envResult = decodeWithSchema(pluginOptions.envSchema, process.env, "Error: Invalid environment provided.");
726
+ if (!envResult.value) {
727
+ await handleError(context, pluginOptions, envResult.error ?? new Error("Error: Invalid environment provided."));
728
+ return;
729
+ }
730
+ context.env = envResult.value;
569
731
  try {
732
+ context.command = getCommand(inputs, pluginOptions);
570
733
  const result = await handler(context);
571
734
  core.setOutput("result", result);
572
- if (pluginOptions?.returnDataToKernel) {
735
+ if (pluginOptions.returnDataToKernel && pluginGithubToken) {
573
736
  await returnDataToKernel(pluginGithubToken, inputs.stateId, result);
574
737
  }
575
738
  } catch (error) {
@@ -678,16 +841,16 @@ function createPlugin(handler, manifest, options) {
678
841
  throw new HTTPException(400, { message: "Invalid signature" });
679
842
  }
680
843
  const inputs = Value4.Decode(inputSchema, body);
681
- let config2;
844
+ let config;
682
845
  if (pluginOptions.settingsSchema) {
683
846
  try {
684
- config2 = Value4.Decode(pluginOptions.settingsSchema, Value4.Default(pluginOptions.settingsSchema, inputs.settings));
847
+ config = Value4.Decode(pluginOptions.settingsSchema, Value4.Default(pluginOptions.settingsSchema, inputs.settings));
685
848
  } catch (e) {
686
849
  console.dir(...Value4.Errors(pluginOptions.settingsSchema, inputs.settings), { depth: null });
687
850
  throw e;
688
851
  }
689
852
  } else {
690
- config2 = inputs.settings;
853
+ config = inputs.settings;
691
854
  }
692
855
  let env;
693
856
  const honoEnvironment = honoEnv(ctx);
@@ -711,7 +874,7 @@ function createPlugin(handler, manifest, options) {
711
874
  authToken: inputs.authToken,
712
875
  ubiquityKernelToken: inputs.ubiquityKernelToken,
713
876
  octokit: new customOctokit({ auth: inputs.authToken }),
714
- config: config2,
877
+ config,
715
878
  env,
716
879
  logger: new Logs2(pluginOptions.logLevel),
717
880
  commentHandler: new CommentHandler()
@@ -747,26 +910,49 @@ function getEnvString(name) {
747
910
  if (typeof process === "undefined" || !process?.env) return EMPTY_STRING;
748
911
  return String(process.env[name] ?? EMPTY_STRING).trim();
749
912
  }
913
+ function normalizeToken(value) {
914
+ return typeof value === "string" ? value.trim() : EMPTY_STRING;
915
+ }
916
+ function isGitHubToken(token) {
917
+ return token.trim().startsWith("gh");
918
+ }
919
+ function getEnvTokenFromInput(input) {
920
+ if ("env" in input) {
921
+ const envValue = input.env;
922
+ if (envValue && typeof envValue === "object") {
923
+ const token = normalizeToken(envValue.UOS_AI_TOKEN);
924
+ if (token) return token;
925
+ }
926
+ }
927
+ return getEnvString("UOS_AI_TOKEN");
928
+ }
929
+ function resolveAuthToken(input, aiAuthToken) {
930
+ const explicit = normalizeToken(aiAuthToken);
931
+ if (explicit) return { token: explicit, isGitHub: isGitHubToken(explicit) };
932
+ const envToken = getEnvTokenFromInput(input);
933
+ if (envToken) return { token: envToken, isGitHub: isGitHubToken(envToken) };
934
+ const fallback = normalizeToken(input.authToken);
935
+ if (!fallback) {
936
+ const err = new Error("Missing auth token; set UOS_AI_TOKEN, pass aiAuthToken, or provide authToken in input");
937
+ err.status = 401;
938
+ throw err;
939
+ }
940
+ return { token: fallback, isGitHub: isGitHubToken(fallback) };
941
+ }
750
942
  function getAiBaseUrl(options) {
751
943
  if (typeof options.baseUrl === "string" && options.baseUrl.trim()) {
752
944
  return normalizeBaseUrl(options.baseUrl);
753
945
  }
754
- const envBaseUrl = getEnvString("UOS_AI_URL") || getEnvString("UOS_AI_BASE_URL");
946
+ const envBaseUrl = getEnvString("UOS_AI_URL");
755
947
  if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
756
- return "https://ai-ubq-fi.deno.dev";
948
+ return "https://ai.ubq.fi";
757
949
  }
758
950
  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
- }
951
+ const { baseUrl, model, stream: isStream, messages, aiAuthToken, ...rest } = options;
952
+ const { token: authToken, isGitHub } = resolveAuthToken(input, aiAuthToken);
765
953
  const kernelToken = "ubiquityKernelToken" in input ? input.ubiquityKernelToken : void 0;
766
954
  const payload = getPayload(input);
767
955
  const { owner, repo, installationId } = getRepoMetadata(payload);
768
- ensureKernelToken(authToken, kernelToken);
769
- const { baseUrl, model, stream: isStream, messages, ...rest } = options;
770
956
  ensureMessages(messages);
771
957
  const url = buildAiUrl(options, baseUrl);
772
958
  const body = JSON.stringify({
@@ -779,7 +965,7 @@ async function callLlm(options, input) {
779
965
  owner,
780
966
  repo,
781
967
  installationId,
782
- ubiquityKernelToken: kernelToken
968
+ ubiquityKernelToken: isGitHub ? kernelToken : void 0
783
969
  });
784
970
  const response = await fetchWithRetry(url, { method: "POST", headers, body }, MAX_LLM_RETRIES);
785
971
  if (isStream) {
@@ -788,14 +974,16 @@ async function callLlm(options, input) {
788
974
  }
789
975
  return parseSseStream(response.body);
790
976
  }
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;
977
+ const rawText = await response.text();
978
+ try {
979
+ return JSON.parse(rawText);
980
+ } catch (err) {
981
+ const preview = rawText ? rawText.slice(0, 1e3) : EMPTY_STRING;
982
+ const message = "LLM API error: failed to parse JSON response from server" + (preview ? `; response body (truncated): ${preview}` : EMPTY_STRING);
983
+ const error = new Error(message);
984
+ error.cause = err;
985
+ error.status = response.status;
986
+ throw error;
799
987
  }
800
988
  }
801
989
  function ensureMessages(messages) {
@@ -815,28 +1003,33 @@ async function fetchWithRetry(url, options, maxRetries) {
815
1003
  try {
816
1004
  const response = await fetch(url, options);
817
1005
  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;
1006
+ throw await buildResponseError(response);
827
1007
  } catch (error) {
828
1008
  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;
1009
+ if (!shouldRetryError(error, attempt, maxRetries)) throw error;
834
1010
  await sleep(getRetryDelayMs(attempt));
835
1011
  attempt += 1;
836
1012
  }
837
1013
  }
838
1014
  throw lastError ?? new Error("LLM API error: request failed after retries");
839
1015
  }
1016
+ async function buildResponseError(response) {
1017
+ const errText = await response.text();
1018
+ const error = new Error(`LLM API error: ${response.status} - ${errText}`);
1019
+ error.status = response.status;
1020
+ return error;
1021
+ }
1022
+ function shouldRetryError(error, attempt, maxRetries) {
1023
+ if (attempt >= maxRetries) return false;
1024
+ const status = getErrorStatus2(error);
1025
+ if (typeof status === "number") {
1026
+ return status >= 500;
1027
+ }
1028
+ return true;
1029
+ }
1030
+ function getErrorStatus2(error) {
1031
+ return typeof error?.status === "number" ? error.status : void 0;
1032
+ }
840
1033
  function getPayload(input) {
841
1034
  if ("payload" in input) {
842
1035
  return input.payload;
package/dist/llm.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ChatCompletionMessageParam, ChatCompletionCreateParamsNonStreaming, ChatCompletion, ChatCompletionChunk } from 'openai/resources/chat/completions';
2
- import { C as Context } from './context-Ckj1HMjz.mjs';
2
+ import { C as Context } from './context-Dwl3aRX-.mjs';
3
3
  import { PluginInput } from './signature.mjs';
4
4
  import '@octokit/webhooks';
5
5
  import '@ubiquity-os/ubiquity-os-logger';
@@ -17,6 +17,7 @@ type LlmCallOptions = {
17
17
  model?: string;
18
18
  stream?: boolean;
19
19
  messages: ChatCompletionMessageParam[];
20
+ aiAuthToken?: string;
20
21
  } & Partial<Omit<ChatCompletionCreateParamsNonStreaming, "model" | "messages" | "stream">>;
21
22
  declare function callLlm(options: LlmCallOptions, input: PluginInput | Context): Promise<ChatCompletion | AsyncIterable<ChatCompletionChunk>>;
22
23
 
package/dist/llm.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ChatCompletionMessageParam, ChatCompletionCreateParamsNonStreaming, ChatCompletion, ChatCompletionChunk } from 'openai/resources/chat/completions';
2
- import { C as Context } from './context-BE4WjJZf.js';
2
+ import { C as Context } from './context-zLHgu52i.js';
3
3
  import { PluginInput } from './signature.js';
4
4
  import '@octokit/webhooks';
5
5
  import '@ubiquity-os/ubiquity-os-logger';
@@ -17,6 +17,7 @@ type LlmCallOptions = {
17
17
  model?: string;
18
18
  stream?: boolean;
19
19
  messages: ChatCompletionMessageParam[];
20
+ aiAuthToken?: string;
20
21
  } & Partial<Omit<ChatCompletionCreateParamsNonStreaming, "model" | "messages" | "stream">>;
21
22
  declare function callLlm(options: LlmCallOptions, input: PluginInput | Context): Promise<ChatCompletion | AsyncIterable<ChatCompletionChunk>>;
22
23
 
package/dist/llm.js CHANGED
@@ -43,26 +43,49 @@ function getEnvString(name) {
43
43
  if (typeof process === "undefined" || !process?.env) return EMPTY_STRING;
44
44
  return String(process.env[name] ?? EMPTY_STRING).trim();
45
45
  }
46
+ function normalizeToken(value) {
47
+ return typeof value === "string" ? value.trim() : EMPTY_STRING;
48
+ }
49
+ function isGitHubToken(token) {
50
+ return token.trim().startsWith("gh");
51
+ }
52
+ function getEnvTokenFromInput(input) {
53
+ if ("env" in input) {
54
+ const envValue = input.env;
55
+ if (envValue && typeof envValue === "object") {
56
+ const token = normalizeToken(envValue.UOS_AI_TOKEN);
57
+ if (token) return token;
58
+ }
59
+ }
60
+ return getEnvString("UOS_AI_TOKEN");
61
+ }
62
+ function resolveAuthToken(input, aiAuthToken) {
63
+ const explicit = normalizeToken(aiAuthToken);
64
+ if (explicit) return { token: explicit, isGitHub: isGitHubToken(explicit) };
65
+ const envToken = getEnvTokenFromInput(input);
66
+ if (envToken) return { token: envToken, isGitHub: isGitHubToken(envToken) };
67
+ const fallback = normalizeToken(input.authToken);
68
+ if (!fallback) {
69
+ const err = new Error("Missing auth token; set UOS_AI_TOKEN, pass aiAuthToken, or provide authToken in input");
70
+ err.status = 401;
71
+ throw err;
72
+ }
73
+ return { token: fallback, isGitHub: isGitHubToken(fallback) };
74
+ }
46
75
  function getAiBaseUrl(options) {
47
76
  if (typeof options.baseUrl === "string" && options.baseUrl.trim()) {
48
77
  return normalizeBaseUrl(options.baseUrl);
49
78
  }
50
- const envBaseUrl = getEnvString("UOS_AI_URL") || getEnvString("UOS_AI_BASE_URL");
79
+ const envBaseUrl = getEnvString("UOS_AI_URL");
51
80
  if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
52
- return "https://ai-ubq-fi.deno.dev";
81
+ return "https://ai.ubq.fi";
53
82
  }
54
83
  async function callLlm(options, input) {
55
- const authToken = String(input.authToken ?? EMPTY_STRING).trim();
56
- if (!authToken) {
57
- const err = new Error("Missing authToken in input");
58
- err.status = 401;
59
- throw err;
60
- }
84
+ const { baseUrl, model, stream: isStream, messages, aiAuthToken, ...rest } = options;
85
+ const { token: authToken, isGitHub } = resolveAuthToken(input, aiAuthToken);
61
86
  const kernelToken = "ubiquityKernelToken" in input ? input.ubiquityKernelToken : void 0;
62
87
  const payload = getPayload(input);
63
88
  const { owner, repo, installationId } = getRepoMetadata(payload);
64
- ensureKernelToken(authToken, kernelToken);
65
- const { baseUrl, model, stream: isStream, messages, ...rest } = options;
66
89
  ensureMessages(messages);
67
90
  const url = buildAiUrl(options, baseUrl);
68
91
  const body = JSON.stringify({
@@ -75,7 +98,7 @@ async function callLlm(options, input) {
75
98
  owner,
76
99
  repo,
77
100
  installationId,
78
- ubiquityKernelToken: kernelToken
101
+ ubiquityKernelToken: isGitHub ? kernelToken : void 0
79
102
  });
80
103
  const response = await fetchWithRetry(url, { method: "POST", headers, body }, MAX_LLM_RETRIES);
81
104
  if (isStream) {
@@ -84,14 +107,16 @@ async function callLlm(options, input) {
84
107
  }
85
108
  return parseSseStream(response.body);
86
109
  }
87
- return response.json();
88
- }
89
- function ensureKernelToken(authToken, kernelToken) {
90
- const isKernelTokenRequired = authToken.startsWith("gh");
91
- if (isKernelTokenRequired && !kernelToken) {
92
- const err = new Error("Missing ubiquityKernelToken in input (kernel attestation is required for GitHub auth)");
93
- err.status = 401;
94
- throw err;
110
+ const rawText = await response.text();
111
+ try {
112
+ return JSON.parse(rawText);
113
+ } catch (err) {
114
+ const preview = rawText ? rawText.slice(0, 1e3) : EMPTY_STRING;
115
+ const message = "LLM API error: failed to parse JSON response from server" + (preview ? `; response body (truncated): ${preview}` : EMPTY_STRING);
116
+ const error = new Error(message);
117
+ error.cause = err;
118
+ error.status = response.status;
119
+ throw error;
95
120
  }
96
121
  }
97
122
  function ensureMessages(messages) {
@@ -111,28 +136,33 @@ async function fetchWithRetry(url, options, maxRetries) {
111
136
  try {
112
137
  const response = await fetch(url, options);
113
138
  if (response.ok) return response;
114
- const errText = await response.text();
115
- if (response.status >= 500 && attempt < maxRetries) {
116
- await sleep(getRetryDelayMs(attempt));
117
- attempt += 1;
118
- continue;
119
- }
120
- const error = new Error(`LLM API error: ${response.status} - ${errText}`);
121
- error.status = response.status;
122
- throw error;
139
+ throw await buildResponseError(response);
123
140
  } catch (error) {
124
141
  lastError = error;
125
- const status = typeof error.status === "number" ? error.status : void 0;
126
- if (typeof status === "number" && status < 500) {
127
- throw error;
128
- }
129
- if (attempt >= maxRetries) throw error;
142
+ if (!shouldRetryError(error, attempt, maxRetries)) throw error;
130
143
  await sleep(getRetryDelayMs(attempt));
131
144
  attempt += 1;
132
145
  }
133
146
  }
134
147
  throw lastError ?? new Error("LLM API error: request failed after retries");
135
148
  }
149
+ async function buildResponseError(response) {
150
+ const errText = await response.text();
151
+ const error = new Error(`LLM API error: ${response.status} - ${errText}`);
152
+ error.status = response.status;
153
+ return error;
154
+ }
155
+ function shouldRetryError(error, attempt, maxRetries) {
156
+ if (attempt >= maxRetries) return false;
157
+ const status = getErrorStatus(error);
158
+ if (typeof status === "number") {
159
+ return status >= 500;
160
+ }
161
+ return true;
162
+ }
163
+ function getErrorStatus(error) {
164
+ return typeof error?.status === "number" ? error.status : void 0;
165
+ }
136
166
  function getPayload(input) {
137
167
  if ("payload" in input) {
138
168
  return input.payload;