@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/README.md +138 -42
- package/dist/configuration.d.mts +26 -12
- package/dist/configuration.d.ts +26 -12
- package/dist/configuration.js +212 -64
- package/dist/configuration.mjs +210 -63
- package/dist/{context-Ckj1HMjz.d.mts → context-Dwl3aRX-.d.mts} +28 -0
- package/dist/{context-BE4WjJZf.d.ts → context-zLHgu52i.d.ts} +28 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +272 -79
- package/dist/index.mjs +272 -79
- package/dist/llm.d.mts +2 -1
- package/dist/llm.d.ts +2 -1
- package/dist/llm.js +63 -33
- package/dist/llm.mjs +63 -33
- package/dist/manifest.d.mts +2 -2
- package/dist/manifest.d.ts +2 -2
- package/dist/signature.d.mts +3 -1
- package/dist/signature.d.ts +3 -1
- package/dist/signature.js +5 -1
- package/dist/signature.mjs +5 -1
- package/package.json +34 -46
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
|
|
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
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
if (!
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
844
|
+
let config;
|
|
682
845
|
if (pluginOptions.settingsSchema) {
|
|
683
846
|
try {
|
|
684
|
-
|
|
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
|
-
|
|
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
|
|
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")
|
|
946
|
+
const envBaseUrl = getEnvString("UOS_AI_URL");
|
|
755
947
|
if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
|
|
756
|
-
return "https://ai
|
|
948
|
+
return "https://ai.ubq.fi";
|
|
757
949
|
}
|
|
758
950
|
async function callLlm(options, input) {
|
|
759
|
-
const
|
|
760
|
-
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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")
|
|
79
|
+
const envBaseUrl = getEnvString("UOS_AI_URL");
|
|
51
80
|
if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
|
|
52
|
-
return "https://ai
|
|
81
|
+
return "https://ai.ubq.fi";
|
|
53
82
|
}
|
|
54
83
|
async function callLlm(options, input) {
|
|
55
|
-
const
|
|
56
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|