@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/README.md +104 -45
- package/dist/configuration.d.mts +11 -20
- package/dist/configuration.d.ts +11 -20
- package/dist/configuration.js +51 -241
- package/dist/configuration.mjs +51 -241
- package/dist/{context-BE4WjJZf.d.ts → context-BbEmsEct.d.ts} +0 -1
- package/dist/{context-Ckj1HMjz.d.mts → context-sqbr2o6i.d.mts} +0 -1
- package/dist/index.d.mts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +194 -352
- package/dist/index.mjs +193 -351
- package/dist/llm.d.mts +43 -6
- package/dist/llm.d.ts +43 -6
- package/dist/llm.js +41 -143
- package/dist/llm.mjs +41 -143
- package/dist/signature.d.mts +3 -1
- package/dist/signature.d.ts +3 -1
- package/dist/signature.js +6 -3
- package/dist/signature.mjs +6 -3
- package/package.json +35 -48
package/dist/llm.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ChatCompletionMessageParam,
|
|
2
|
-
import { C as Context } from './context-
|
|
3
|
-
import { PluginInput } from './signature.mjs';
|
|
1
|
+
import { ChatCompletionMessageParam, ChatCompletion, ChatCompletionChunk } from 'openai/resources/chat/completions';
|
|
2
|
+
import { C as Context } from './context-sqbr2o6i.mjs';
|
|
4
3
|
import '@octokit/webhooks';
|
|
5
4
|
import '@ubiquity-os/ubiquity-os-logger';
|
|
6
5
|
import '@octokit/plugin-rest-endpoint-methods';
|
|
@@ -10,14 +9,52 @@ import '@octokit/plugin-paginate-graphql';
|
|
|
10
9
|
import '@octokit/plugin-paginate-rest';
|
|
11
10
|
import '@octokit/webhooks/node_modules/@octokit/request-error';
|
|
12
11
|
import '@octokit/core';
|
|
13
|
-
import '@sinclair/typebox';
|
|
14
12
|
|
|
13
|
+
type LlmResponseFormat = {
|
|
14
|
+
type: "json_object" | "text";
|
|
15
|
+
} | {
|
|
16
|
+
type: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
type LlmPayload = {
|
|
20
|
+
repository?: {
|
|
21
|
+
owner?: {
|
|
22
|
+
login?: string;
|
|
23
|
+
};
|
|
24
|
+
name?: string;
|
|
25
|
+
};
|
|
26
|
+
installation?: {
|
|
27
|
+
id?: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
type LlmAuthContext = {
|
|
31
|
+
authToken?: string;
|
|
32
|
+
ubiquityKernelToken?: string;
|
|
33
|
+
payload?: LlmPayload;
|
|
34
|
+
eventPayload?: LlmPayload;
|
|
35
|
+
};
|
|
15
36
|
type LlmCallOptions = {
|
|
16
37
|
baseUrl?: string;
|
|
17
38
|
model?: string;
|
|
18
39
|
stream?: boolean;
|
|
19
40
|
messages: ChatCompletionMessageParam[];
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
max_tokens?: number;
|
|
42
|
+
max_completion_tokens?: number;
|
|
43
|
+
temperature?: number;
|
|
44
|
+
top_p?: number;
|
|
45
|
+
frequency_penalty?: number;
|
|
46
|
+
presence_penalty?: number;
|
|
47
|
+
response_format?: LlmResponseFormat;
|
|
48
|
+
stop?: string | string[];
|
|
49
|
+
n?: number;
|
|
50
|
+
logit_bias?: Record<string, number>;
|
|
51
|
+
seed?: number;
|
|
52
|
+
user?: string;
|
|
53
|
+
metadata?: Record<string, unknown>;
|
|
54
|
+
tools?: unknown[];
|
|
55
|
+
tool_choice?: string | Record<string, unknown>;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
};
|
|
58
|
+
declare function callLlm(options: LlmCallOptions, input: Context | LlmAuthContext): Promise<ChatCompletion | AsyncIterable<ChatCompletionChunk>>;
|
|
22
59
|
|
|
23
60
|
export { type LlmCallOptions, callLlm };
|
package/dist/llm.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ChatCompletionMessageParam,
|
|
2
|
-
import { C as Context } from './context-
|
|
3
|
-
import { PluginInput } from './signature.js';
|
|
1
|
+
import { ChatCompletionMessageParam, ChatCompletion, ChatCompletionChunk } from 'openai/resources/chat/completions';
|
|
2
|
+
import { C as Context } from './context-BbEmsEct.js';
|
|
4
3
|
import '@octokit/webhooks';
|
|
5
4
|
import '@ubiquity-os/ubiquity-os-logger';
|
|
6
5
|
import '@octokit/plugin-rest-endpoint-methods';
|
|
@@ -10,14 +9,52 @@ import '@octokit/plugin-paginate-graphql';
|
|
|
10
9
|
import '@octokit/plugin-paginate-rest';
|
|
11
10
|
import '@octokit/webhooks/node_modules/@octokit/request-error';
|
|
12
11
|
import '@octokit/core';
|
|
13
|
-
import '@sinclair/typebox';
|
|
14
12
|
|
|
13
|
+
type LlmResponseFormat = {
|
|
14
|
+
type: "json_object" | "text";
|
|
15
|
+
} | {
|
|
16
|
+
type: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
type LlmPayload = {
|
|
20
|
+
repository?: {
|
|
21
|
+
owner?: {
|
|
22
|
+
login?: string;
|
|
23
|
+
};
|
|
24
|
+
name?: string;
|
|
25
|
+
};
|
|
26
|
+
installation?: {
|
|
27
|
+
id?: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
type LlmAuthContext = {
|
|
31
|
+
authToken?: string;
|
|
32
|
+
ubiquityKernelToken?: string;
|
|
33
|
+
payload?: LlmPayload;
|
|
34
|
+
eventPayload?: LlmPayload;
|
|
35
|
+
};
|
|
15
36
|
type LlmCallOptions = {
|
|
16
37
|
baseUrl?: string;
|
|
17
38
|
model?: string;
|
|
18
39
|
stream?: boolean;
|
|
19
40
|
messages: ChatCompletionMessageParam[];
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
max_tokens?: number;
|
|
42
|
+
max_completion_tokens?: number;
|
|
43
|
+
temperature?: number;
|
|
44
|
+
top_p?: number;
|
|
45
|
+
frequency_penalty?: number;
|
|
46
|
+
presence_penalty?: number;
|
|
47
|
+
response_format?: LlmResponseFormat;
|
|
48
|
+
stop?: string | string[];
|
|
49
|
+
n?: number;
|
|
50
|
+
logit_bias?: Record<string, number>;
|
|
51
|
+
seed?: number;
|
|
52
|
+
user?: string;
|
|
53
|
+
metadata?: Record<string, unknown>;
|
|
54
|
+
tools?: unknown[];
|
|
55
|
+
tool_choice?: string | Record<string, unknown>;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
};
|
|
58
|
+
declare function callLlm(options: LlmCallOptions, input: Context | LlmAuthContext): Promise<ChatCompletion | AsyncIterable<ChatCompletionChunk>>;
|
|
22
59
|
|
|
23
60
|
export { type LlmCallOptions, callLlm };
|
package/dist/llm.js
CHANGED
|
@@ -23,7 +23,6 @@ __export(llm_exports, {
|
|
|
23
23
|
callLlm: () => callLlm
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(llm_exports);
|
|
26
|
-
var EMPTY_STRING = "";
|
|
27
26
|
function normalizeBaseUrl(baseUrl) {
|
|
28
27
|
let normalized = baseUrl.trim();
|
|
29
28
|
while (normalized.endsWith("/")) {
|
|
@@ -31,53 +30,56 @@ function normalizeBaseUrl(baseUrl) {
|
|
|
31
30
|
}
|
|
32
31
|
return normalized;
|
|
33
32
|
}
|
|
34
|
-
var MAX_LLM_RETRIES = 2;
|
|
35
|
-
var RETRY_BACKOFF_MS = [250, 750];
|
|
36
|
-
function getRetryDelayMs(attempt) {
|
|
37
|
-
return RETRY_BACKOFF_MS[Math.min(attempt, RETRY_BACKOFF_MS.length - 1)] ?? 750;
|
|
38
|
-
}
|
|
39
|
-
function sleep(ms) {
|
|
40
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
|
-
}
|
|
42
33
|
function getEnvString(name) {
|
|
43
|
-
if (typeof process === "undefined" || !process?.env) return
|
|
44
|
-
return String(process.env[name] ??
|
|
34
|
+
if (typeof process === "undefined" || !process?.env) return "";
|
|
35
|
+
return String(process.env[name] ?? "").trim();
|
|
45
36
|
}
|
|
46
37
|
function getAiBaseUrl(options) {
|
|
47
38
|
if (typeof options.baseUrl === "string" && options.baseUrl.trim()) {
|
|
48
39
|
return normalizeBaseUrl(options.baseUrl);
|
|
49
40
|
}
|
|
50
|
-
const envBaseUrl = getEnvString("
|
|
41
|
+
const envBaseUrl = getEnvString("UBQ_AI_BASE_URL") || getEnvString("UBQ_AI_URL");
|
|
51
42
|
if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
|
|
52
|
-
return "https://ai
|
|
43
|
+
return "https://ai.ubq.fi";
|
|
53
44
|
}
|
|
54
45
|
async function callLlm(options, input) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
const inputPayload = input;
|
|
47
|
+
const authToken = inputPayload.authToken;
|
|
48
|
+
const ubiquityKernelToken = inputPayload.ubiquityKernelToken;
|
|
49
|
+
const payload = inputPayload.payload ?? inputPayload.eventPayload;
|
|
50
|
+
const owner = payload?.repository?.owner?.login ?? "";
|
|
51
|
+
const repo = payload?.repository?.name ?? "";
|
|
52
|
+
const installationId = payload?.installation?.id;
|
|
53
|
+
if (!authToken) throw new Error("Missing authToken in inputs");
|
|
54
|
+
const isKernelTokenRequired = authToken.trim().startsWith("gh");
|
|
55
|
+
if (isKernelTokenRequired && !ubiquityKernelToken) {
|
|
56
|
+
throw new Error("Missing ubiquityKernelToken in inputs (kernel attestation is required for GitHub auth)");
|
|
60
57
|
}
|
|
61
|
-
const kernelToken = "ubiquityKernelToken" in input ? input.ubiquityKernelToken : void 0;
|
|
62
|
-
const payload = getPayload(input);
|
|
63
|
-
const { owner, repo, installationId } = getRepoMetadata(payload);
|
|
64
|
-
ensureKernelToken(authToken, kernelToken);
|
|
65
58
|
const { baseUrl, model, stream: isStream, messages, ...rest } = options;
|
|
66
|
-
|
|
67
|
-
const url = buildAiUrl(options, baseUrl);
|
|
59
|
+
const url = `${getAiBaseUrl({ ...options, baseUrl })}/v1/chat/completions`;
|
|
68
60
|
const body = JSON.stringify({
|
|
69
61
|
...rest,
|
|
70
62
|
...model ? { model } : {},
|
|
71
63
|
messages,
|
|
72
64
|
stream: isStream ?? false
|
|
73
65
|
});
|
|
74
|
-
const headers =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
const headers = {
|
|
67
|
+
Authorization: `Bearer ${authToken}`,
|
|
68
|
+
"Content-Type": "application/json"
|
|
69
|
+
};
|
|
70
|
+
if (owner) headers["X-GitHub-Owner"] = owner;
|
|
71
|
+
if (repo) headers["X-GitHub-Repo"] = repo;
|
|
72
|
+
if (typeof installationId === "number" && Number.isFinite(installationId)) {
|
|
73
|
+
headers["X-GitHub-Installation-Id"] = String(installationId);
|
|
74
|
+
}
|
|
75
|
+
if (ubiquityKernelToken) {
|
|
76
|
+
headers["X-Ubiquity-Kernel-Token"] = ubiquityKernelToken;
|
|
77
|
+
}
|
|
78
|
+
const response = await fetch(url, { method: "POST", headers, body });
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const err = await response.text();
|
|
81
|
+
throw new Error(`LLM API error: ${response.status} - ${err}`);
|
|
82
|
+
}
|
|
81
83
|
if (isStream) {
|
|
82
84
|
if (!response.body) {
|
|
83
85
|
throw new Error("LLM API error: missing response body for streaming request");
|
|
@@ -86,133 +88,29 @@ async function callLlm(options, input) {
|
|
|
86
88
|
}
|
|
87
89
|
return response.json();
|
|
88
90
|
}
|
|
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;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function ensureMessages(messages) {
|
|
98
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
99
|
-
const err = new Error("messages must be a non-empty array");
|
|
100
|
-
err.status = 400;
|
|
101
|
-
throw err;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function buildAiUrl(options, baseUrl) {
|
|
105
|
-
return `${getAiBaseUrl({ ...options, baseUrl })}/v1/chat/completions`;
|
|
106
|
-
}
|
|
107
|
-
async function fetchWithRetry(url, options, maxRetries) {
|
|
108
|
-
let attempt = 0;
|
|
109
|
-
let lastError;
|
|
110
|
-
while (attempt <= maxRetries) {
|
|
111
|
-
try {
|
|
112
|
-
const response = await fetch(url, options);
|
|
113
|
-
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;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
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;
|
|
130
|
-
await sleep(getRetryDelayMs(attempt));
|
|
131
|
-
attempt += 1;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
throw lastError ?? new Error("LLM API error: request failed after retries");
|
|
135
|
-
}
|
|
136
|
-
function getPayload(input) {
|
|
137
|
-
if ("payload" in input) {
|
|
138
|
-
return input.payload;
|
|
139
|
-
}
|
|
140
|
-
return input.eventPayload;
|
|
141
|
-
}
|
|
142
|
-
function getRepoMetadata(payload) {
|
|
143
|
-
const repoPayload = payload;
|
|
144
|
-
return {
|
|
145
|
-
owner: repoPayload?.repository?.owner?.login ?? EMPTY_STRING,
|
|
146
|
-
repo: repoPayload?.repository?.name ?? EMPTY_STRING,
|
|
147
|
-
installationId: repoPayload?.installation?.id
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function buildHeaders(authToken, options) {
|
|
151
|
-
const headers = {
|
|
152
|
-
Authorization: `Bearer ${authToken}`,
|
|
153
|
-
"Content-Type": "application/json"
|
|
154
|
-
};
|
|
155
|
-
if (options.owner) headers["X-GitHub-Owner"] = options.owner;
|
|
156
|
-
if (options.repo) headers["X-GitHub-Repo"] = options.repo;
|
|
157
|
-
if (typeof options.installationId === "number" && Number.isFinite(options.installationId)) {
|
|
158
|
-
headers["X-GitHub-Installation-Id"] = String(options.installationId);
|
|
159
|
-
}
|
|
160
|
-
if (options.ubiquityKernelToken) {
|
|
161
|
-
headers["X-Ubiquity-Kernel-Token"] = options.ubiquityKernelToken;
|
|
162
|
-
}
|
|
163
|
-
return headers;
|
|
164
|
-
}
|
|
165
91
|
async function* parseSseStream(body) {
|
|
166
92
|
const reader = body.getReader();
|
|
167
93
|
const decoder = new TextDecoder();
|
|
168
|
-
let buffer =
|
|
94
|
+
let buffer = "";
|
|
169
95
|
try {
|
|
170
96
|
while (true) {
|
|
171
97
|
const { value, done: isDone } = await reader.read();
|
|
172
98
|
if (isDone) break;
|
|
173
99
|
buffer += decoder.decode(value, { stream: true });
|
|
174
|
-
const
|
|
175
|
-
buffer =
|
|
100
|
+
const events = buffer.split("\n\n");
|
|
101
|
+
buffer = events.pop() || "";
|
|
176
102
|
for (const event of events) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
103
|
+
if (event.startsWith("data: ")) {
|
|
104
|
+
const data = event.slice(6);
|
|
105
|
+
if (data === "[DONE]") return;
|
|
106
|
+
yield JSON.parse(data);
|
|
107
|
+
}
|
|
181
108
|
}
|
|
182
109
|
}
|
|
183
110
|
} finally {
|
|
184
111
|
reader.releaseLock();
|
|
185
112
|
}
|
|
186
113
|
}
|
|
187
|
-
function splitSseEvents(buffer) {
|
|
188
|
-
const normalized = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
189
|
-
const parts = normalized.split("\n\n");
|
|
190
|
-
const remainder = parts.pop() ?? EMPTY_STRING;
|
|
191
|
-
return { events: parts, remainder };
|
|
192
|
-
}
|
|
193
|
-
function getEventData(event) {
|
|
194
|
-
if (!event.trim()) return null;
|
|
195
|
-
const dataLines = event.split("\n").filter((line) => line.startsWith("data:"));
|
|
196
|
-
if (!dataLines.length) return null;
|
|
197
|
-
const data = dataLines.map((line) => line.startsWith("data: ") ? line.slice(6) : line.slice(5).replace(/^ /, EMPTY_STRING)).join("\n");
|
|
198
|
-
return data || null;
|
|
199
|
-
}
|
|
200
|
-
function parseEventData(data) {
|
|
201
|
-
try {
|
|
202
|
-
return JSON.parse(data);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
if (data.includes("\n")) {
|
|
205
|
-
const collapsed = data.replace(/\n/g, EMPTY_STRING);
|
|
206
|
-
try {
|
|
207
|
-
return JSON.parse(collapsed);
|
|
208
|
-
} catch {
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
212
|
-
const preview = data.length > 200 ? `${data.slice(0, 200)}...` : data;
|
|
213
|
-
throw new Error(`LLM stream parse error: ${message}. Data: ${preview}`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
114
|
// Annotate the CommonJS export names for ESM import in node:
|
|
217
115
|
0 && (module.exports = {
|
|
218
116
|
callLlm
|
package/dist/llm.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/llm/index.ts
|
|
2
|
-
var EMPTY_STRING = "";
|
|
3
2
|
function normalizeBaseUrl(baseUrl) {
|
|
4
3
|
let normalized = baseUrl.trim();
|
|
5
4
|
while (normalized.endsWith("/")) {
|
|
@@ -7,53 +6,56 @@ function normalizeBaseUrl(baseUrl) {
|
|
|
7
6
|
}
|
|
8
7
|
return normalized;
|
|
9
8
|
}
|
|
10
|
-
var MAX_LLM_RETRIES = 2;
|
|
11
|
-
var RETRY_BACKOFF_MS = [250, 750];
|
|
12
|
-
function getRetryDelayMs(attempt) {
|
|
13
|
-
return RETRY_BACKOFF_MS[Math.min(attempt, RETRY_BACKOFF_MS.length - 1)] ?? 750;
|
|
14
|
-
}
|
|
15
|
-
function sleep(ms) {
|
|
16
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
-
}
|
|
18
9
|
function getEnvString(name) {
|
|
19
|
-
if (typeof process === "undefined" || !process?.env) return
|
|
20
|
-
return String(process.env[name] ??
|
|
10
|
+
if (typeof process === "undefined" || !process?.env) return "";
|
|
11
|
+
return String(process.env[name] ?? "").trim();
|
|
21
12
|
}
|
|
22
13
|
function getAiBaseUrl(options) {
|
|
23
14
|
if (typeof options.baseUrl === "string" && options.baseUrl.trim()) {
|
|
24
15
|
return normalizeBaseUrl(options.baseUrl);
|
|
25
16
|
}
|
|
26
|
-
const envBaseUrl = getEnvString("
|
|
17
|
+
const envBaseUrl = getEnvString("UBQ_AI_BASE_URL") || getEnvString("UBQ_AI_URL");
|
|
27
18
|
if (envBaseUrl) return normalizeBaseUrl(envBaseUrl);
|
|
28
|
-
return "https://ai
|
|
19
|
+
return "https://ai.ubq.fi";
|
|
29
20
|
}
|
|
30
21
|
async function callLlm(options, input) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
const inputPayload = input;
|
|
23
|
+
const authToken = inputPayload.authToken;
|
|
24
|
+
const ubiquityKernelToken = inputPayload.ubiquityKernelToken;
|
|
25
|
+
const payload = inputPayload.payload ?? inputPayload.eventPayload;
|
|
26
|
+
const owner = payload?.repository?.owner?.login ?? "";
|
|
27
|
+
const repo = payload?.repository?.name ?? "";
|
|
28
|
+
const installationId = payload?.installation?.id;
|
|
29
|
+
if (!authToken) throw new Error("Missing authToken in inputs");
|
|
30
|
+
const isKernelTokenRequired = authToken.trim().startsWith("gh");
|
|
31
|
+
if (isKernelTokenRequired && !ubiquityKernelToken) {
|
|
32
|
+
throw new Error("Missing ubiquityKernelToken in inputs (kernel attestation is required for GitHub auth)");
|
|
36
33
|
}
|
|
37
|
-
const kernelToken = "ubiquityKernelToken" in input ? input.ubiquityKernelToken : void 0;
|
|
38
|
-
const payload = getPayload(input);
|
|
39
|
-
const { owner, repo, installationId } = getRepoMetadata(payload);
|
|
40
|
-
ensureKernelToken(authToken, kernelToken);
|
|
41
34
|
const { baseUrl, model, stream: isStream, messages, ...rest } = options;
|
|
42
|
-
|
|
43
|
-
const url = buildAiUrl(options, baseUrl);
|
|
35
|
+
const url = `${getAiBaseUrl({ ...options, baseUrl })}/v1/chat/completions`;
|
|
44
36
|
const body = JSON.stringify({
|
|
45
37
|
...rest,
|
|
46
38
|
...model ? { model } : {},
|
|
47
39
|
messages,
|
|
48
40
|
stream: isStream ?? false
|
|
49
41
|
});
|
|
50
|
-
const headers =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
const headers = {
|
|
43
|
+
Authorization: `Bearer ${authToken}`,
|
|
44
|
+
"Content-Type": "application/json"
|
|
45
|
+
};
|
|
46
|
+
if (owner) headers["X-GitHub-Owner"] = owner;
|
|
47
|
+
if (repo) headers["X-GitHub-Repo"] = repo;
|
|
48
|
+
if (typeof installationId === "number" && Number.isFinite(installationId)) {
|
|
49
|
+
headers["X-GitHub-Installation-Id"] = String(installationId);
|
|
50
|
+
}
|
|
51
|
+
if (ubiquityKernelToken) {
|
|
52
|
+
headers["X-Ubiquity-Kernel-Token"] = ubiquityKernelToken;
|
|
53
|
+
}
|
|
54
|
+
const response = await fetch(url, { method: "POST", headers, body });
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const err = await response.text();
|
|
57
|
+
throw new Error(`LLM API error: ${response.status} - ${err}`);
|
|
58
|
+
}
|
|
57
59
|
if (isStream) {
|
|
58
60
|
if (!response.body) {
|
|
59
61
|
throw new Error("LLM API error: missing response body for streaming request");
|
|
@@ -62,133 +64,29 @@ async function callLlm(options, input) {
|
|
|
62
64
|
}
|
|
63
65
|
return response.json();
|
|
64
66
|
}
|
|
65
|
-
function ensureKernelToken(authToken, kernelToken) {
|
|
66
|
-
const isKernelTokenRequired = authToken.startsWith("gh");
|
|
67
|
-
if (isKernelTokenRequired && !kernelToken) {
|
|
68
|
-
const err = new Error("Missing ubiquityKernelToken in input (kernel attestation is required for GitHub auth)");
|
|
69
|
-
err.status = 401;
|
|
70
|
-
throw err;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
function ensureMessages(messages) {
|
|
74
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
75
|
-
const err = new Error("messages must be a non-empty array");
|
|
76
|
-
err.status = 400;
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function buildAiUrl(options, baseUrl) {
|
|
81
|
-
return `${getAiBaseUrl({ ...options, baseUrl })}/v1/chat/completions`;
|
|
82
|
-
}
|
|
83
|
-
async function fetchWithRetry(url, options, maxRetries) {
|
|
84
|
-
let attempt = 0;
|
|
85
|
-
let lastError;
|
|
86
|
-
while (attempt <= maxRetries) {
|
|
87
|
-
try {
|
|
88
|
-
const response = await fetch(url, options);
|
|
89
|
-
if (response.ok) return response;
|
|
90
|
-
const errText = await response.text();
|
|
91
|
-
if (response.status >= 500 && attempt < maxRetries) {
|
|
92
|
-
await sleep(getRetryDelayMs(attempt));
|
|
93
|
-
attempt += 1;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const error = new Error(`LLM API error: ${response.status} - ${errText}`);
|
|
97
|
-
error.status = response.status;
|
|
98
|
-
throw error;
|
|
99
|
-
} catch (error) {
|
|
100
|
-
lastError = error;
|
|
101
|
-
const status = typeof error.status === "number" ? error.status : void 0;
|
|
102
|
-
if (typeof status === "number" && status < 500) {
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
if (attempt >= maxRetries) throw error;
|
|
106
|
-
await sleep(getRetryDelayMs(attempt));
|
|
107
|
-
attempt += 1;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
throw lastError ?? new Error("LLM API error: request failed after retries");
|
|
111
|
-
}
|
|
112
|
-
function getPayload(input) {
|
|
113
|
-
if ("payload" in input) {
|
|
114
|
-
return input.payload;
|
|
115
|
-
}
|
|
116
|
-
return input.eventPayload;
|
|
117
|
-
}
|
|
118
|
-
function getRepoMetadata(payload) {
|
|
119
|
-
const repoPayload = payload;
|
|
120
|
-
return {
|
|
121
|
-
owner: repoPayload?.repository?.owner?.login ?? EMPTY_STRING,
|
|
122
|
-
repo: repoPayload?.repository?.name ?? EMPTY_STRING,
|
|
123
|
-
installationId: repoPayload?.installation?.id
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function buildHeaders(authToken, options) {
|
|
127
|
-
const headers = {
|
|
128
|
-
Authorization: `Bearer ${authToken}`,
|
|
129
|
-
"Content-Type": "application/json"
|
|
130
|
-
};
|
|
131
|
-
if (options.owner) headers["X-GitHub-Owner"] = options.owner;
|
|
132
|
-
if (options.repo) headers["X-GitHub-Repo"] = options.repo;
|
|
133
|
-
if (typeof options.installationId === "number" && Number.isFinite(options.installationId)) {
|
|
134
|
-
headers["X-GitHub-Installation-Id"] = String(options.installationId);
|
|
135
|
-
}
|
|
136
|
-
if (options.ubiquityKernelToken) {
|
|
137
|
-
headers["X-Ubiquity-Kernel-Token"] = options.ubiquityKernelToken;
|
|
138
|
-
}
|
|
139
|
-
return headers;
|
|
140
|
-
}
|
|
141
67
|
async function* parseSseStream(body) {
|
|
142
68
|
const reader = body.getReader();
|
|
143
69
|
const decoder = new TextDecoder();
|
|
144
|
-
let buffer =
|
|
70
|
+
let buffer = "";
|
|
145
71
|
try {
|
|
146
72
|
while (true) {
|
|
147
73
|
const { value, done: isDone } = await reader.read();
|
|
148
74
|
if (isDone) break;
|
|
149
75
|
buffer += decoder.decode(value, { stream: true });
|
|
150
|
-
const
|
|
151
|
-
buffer =
|
|
76
|
+
const events = buffer.split("\n\n");
|
|
77
|
+
buffer = events.pop() || "";
|
|
152
78
|
for (const event of events) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
79
|
+
if (event.startsWith("data: ")) {
|
|
80
|
+
const data = event.slice(6);
|
|
81
|
+
if (data === "[DONE]") return;
|
|
82
|
+
yield JSON.parse(data);
|
|
83
|
+
}
|
|
157
84
|
}
|
|
158
85
|
}
|
|
159
86
|
} finally {
|
|
160
87
|
reader.releaseLock();
|
|
161
88
|
}
|
|
162
89
|
}
|
|
163
|
-
function splitSseEvents(buffer) {
|
|
164
|
-
const normalized = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
165
|
-
const parts = normalized.split("\n\n");
|
|
166
|
-
const remainder = parts.pop() ?? EMPTY_STRING;
|
|
167
|
-
return { events: parts, remainder };
|
|
168
|
-
}
|
|
169
|
-
function getEventData(event) {
|
|
170
|
-
if (!event.trim()) return null;
|
|
171
|
-
const dataLines = event.split("\n").filter((line) => line.startsWith("data:"));
|
|
172
|
-
if (!dataLines.length) return null;
|
|
173
|
-
const data = dataLines.map((line) => line.startsWith("data: ") ? line.slice(6) : line.slice(5).replace(/^ /, EMPTY_STRING)).join("\n");
|
|
174
|
-
return data || null;
|
|
175
|
-
}
|
|
176
|
-
function parseEventData(data) {
|
|
177
|
-
try {
|
|
178
|
-
return JSON.parse(data);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
if (data.includes("\n")) {
|
|
181
|
-
const collapsed = data.replace(/\n/g, EMPTY_STRING);
|
|
182
|
-
try {
|
|
183
|
-
return JSON.parse(collapsed);
|
|
184
|
-
} catch {
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
188
|
-
const preview = data.length > 200 ? `${data.slice(0, 200)}...` : data;
|
|
189
|
-
throw new Error(`LLM stream parse error: ${message}. Data: ${preview}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
90
|
export {
|
|
193
91
|
callLlm
|
|
194
92
|
};
|
package/dist/signature.d.mts
CHANGED
|
@@ -15,9 +15,10 @@ declare class PluginInput<T extends EmitterWebhookEventName = EmitterWebhookEven
|
|
|
15
15
|
eventPayload: EmitterWebhookEvent<T>["payload"];
|
|
16
16
|
settings: unknown;
|
|
17
17
|
authToken: string;
|
|
18
|
+
ubiquityKernelToken?: string;
|
|
18
19
|
ref: string;
|
|
19
20
|
command: CommandCall;
|
|
20
|
-
constructor(privateKey: string, stateId: string, eventName: T, eventPayload: EmitterWebhookEvent<T>["payload"], settings: unknown, authToken: string, ref: string, command: CommandCall);
|
|
21
|
+
constructor(privateKey: string, stateId: string, eventName: T, eventPayload: EmitterWebhookEvent<T>["payload"], settings: unknown, authToken: string, ref: string, command: CommandCall, ubiquityKernelToken?: string);
|
|
21
22
|
getInputs(): Promise<{
|
|
22
23
|
signature: string;
|
|
23
24
|
stateId: string;
|
|
@@ -25,6 +26,7 @@ declare class PluginInput<T extends EmitterWebhookEventName = EmitterWebhookEven
|
|
|
25
26
|
eventPayload: string;
|
|
26
27
|
settings: string;
|
|
27
28
|
authToken: string;
|
|
29
|
+
ubiquityKernelToken: string | undefined;
|
|
28
30
|
ref: string;
|
|
29
31
|
command: string;
|
|
30
32
|
}>;
|
package/dist/signature.d.ts
CHANGED
|
@@ -15,9 +15,10 @@ declare class PluginInput<T extends EmitterWebhookEventName = EmitterWebhookEven
|
|
|
15
15
|
eventPayload: EmitterWebhookEvent<T>["payload"];
|
|
16
16
|
settings: unknown;
|
|
17
17
|
authToken: string;
|
|
18
|
+
ubiquityKernelToken?: string;
|
|
18
19
|
ref: string;
|
|
19
20
|
command: CommandCall;
|
|
20
|
-
constructor(privateKey: string, stateId: string, eventName: T, eventPayload: EmitterWebhookEvent<T>["payload"], settings: unknown, authToken: string, ref: string, command: CommandCall);
|
|
21
|
+
constructor(privateKey: string, stateId: string, eventName: T, eventPayload: EmitterWebhookEvent<T>["payload"], settings: unknown, authToken: string, ref: string, command: CommandCall, ubiquityKernelToken?: string);
|
|
21
22
|
getInputs(): Promise<{
|
|
22
23
|
signature: string;
|
|
23
24
|
stateId: string;
|
|
@@ -25,6 +26,7 @@ declare class PluginInput<T extends EmitterWebhookEventName = EmitterWebhookEven
|
|
|
25
26
|
eventPayload: string;
|
|
26
27
|
settings: string;
|
|
27
28
|
authToken: string;
|
|
29
|
+
ubiquityKernelToken: string | undefined;
|
|
28
30
|
ref: string;
|
|
29
31
|
command: string;
|
|
30
32
|
}>;
|