@meetopenbot/github 0.0.1 → 0.1.1
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 +23 -39
- package/dist/agent.js +191 -0
- package/dist/cloud-mode.js +10 -0
- package/dist/config.js +74 -0
- package/dist/credits-auth.js +53 -0
- package/dist/diff.js +346 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +101 -340
- package/dist/model.js +24 -0
- package/package.json +25 -13
- package/src/index.ts +0 -418
- package/tsconfig.json +0 -15
package/dist/diff.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
const MAX_FILES = 40;
|
|
2
|
+
const MAX_PATCH_CHARS = 48_000;
|
|
3
|
+
const LANG_BY_EXT = {
|
|
4
|
+
ts: 'typescript',
|
|
5
|
+
tsx: 'tsx',
|
|
6
|
+
js: 'javascript',
|
|
7
|
+
jsx: 'jsx',
|
|
8
|
+
mjs: 'javascript',
|
|
9
|
+
cjs: 'javascript',
|
|
10
|
+
py: 'python',
|
|
11
|
+
go: 'go',
|
|
12
|
+
rs: 'rust',
|
|
13
|
+
rb: 'ruby',
|
|
14
|
+
java: 'java',
|
|
15
|
+
kt: 'kotlin',
|
|
16
|
+
swift: 'swift',
|
|
17
|
+
cs: 'csharp',
|
|
18
|
+
cpp: 'cpp',
|
|
19
|
+
cc: 'cpp',
|
|
20
|
+
cxx: 'cpp',
|
|
21
|
+
c: 'c',
|
|
22
|
+
h: 'c',
|
|
23
|
+
hpp: 'cpp',
|
|
24
|
+
md: 'markdown',
|
|
25
|
+
json: 'json',
|
|
26
|
+
css: 'css',
|
|
27
|
+
scss: 'scss',
|
|
28
|
+
html: 'html',
|
|
29
|
+
yml: 'yaml',
|
|
30
|
+
yaml: 'yaml',
|
|
31
|
+
toml: 'toml',
|
|
32
|
+
sh: 'bash',
|
|
33
|
+
bash: 'bash',
|
|
34
|
+
zsh: 'bash',
|
|
35
|
+
sql: 'sql',
|
|
36
|
+
};
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
function asString(value) {
|
|
41
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
42
|
+
}
|
|
43
|
+
function asNumber(value) {
|
|
44
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
45
|
+
return value;
|
|
46
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
47
|
+
const parsed = Number(value);
|
|
48
|
+
if (Number.isFinite(parsed))
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function languageFromPath(path) {
|
|
54
|
+
const base = path.split('/').pop() ?? path;
|
|
55
|
+
const ext = base.includes('.') ? base.slice(base.lastIndexOf('.') + 1).toLowerCase() : '';
|
|
56
|
+
return LANG_BY_EXT[ext];
|
|
57
|
+
}
|
|
58
|
+
function mapStatus(status, oldPath) {
|
|
59
|
+
switch (status) {
|
|
60
|
+
case 'added':
|
|
61
|
+
return 'added';
|
|
62
|
+
case 'removed':
|
|
63
|
+
case 'deleted':
|
|
64
|
+
return 'deleted';
|
|
65
|
+
case 'renamed':
|
|
66
|
+
case 'copied':
|
|
67
|
+
return 'renamed';
|
|
68
|
+
default:
|
|
69
|
+
return oldPath ? 'renamed' : 'modified';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function countPatchStats(patch) {
|
|
73
|
+
let additions = 0;
|
|
74
|
+
let deletions = 0;
|
|
75
|
+
for (const line of patch.split('\n')) {
|
|
76
|
+
if (line.startsWith('+') && !line.startsWith('+++'))
|
|
77
|
+
additions += 1;
|
|
78
|
+
else if (line.startsWith('-') && !line.startsWith('---'))
|
|
79
|
+
deletions += 1;
|
|
80
|
+
}
|
|
81
|
+
return { additions, deletions };
|
|
82
|
+
}
|
|
83
|
+
function capPatch(patch) {
|
|
84
|
+
if (!patch)
|
|
85
|
+
return {};
|
|
86
|
+
if (patch.length <= MAX_PATCH_CHARS)
|
|
87
|
+
return { patch };
|
|
88
|
+
return { patch: patch.slice(0, MAX_PATCH_CHARS), truncated: true };
|
|
89
|
+
}
|
|
90
|
+
export function unwrapToolOutput(output) {
|
|
91
|
+
if (output == null)
|
|
92
|
+
return output;
|
|
93
|
+
if (typeof output === 'string') {
|
|
94
|
+
const trimmed = output.trim();
|
|
95
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(trimmed);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return output;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return output;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(output)) {
|
|
106
|
+
if (output.length > 0 &&
|
|
107
|
+
output.every((part) => typeof part === 'string' ||
|
|
108
|
+
(isRecord(part) && (typeof part.text === 'string' || typeof part.value === 'string')))) {
|
|
109
|
+
const text = output
|
|
110
|
+
.map((part) => {
|
|
111
|
+
if (typeof part === 'string')
|
|
112
|
+
return part;
|
|
113
|
+
if (isRecord(part))
|
|
114
|
+
return asString(part.text) ?? asString(part.value) ?? '';
|
|
115
|
+
return '';
|
|
116
|
+
})
|
|
117
|
+
.join('\n');
|
|
118
|
+
return unwrapToolOutput(text);
|
|
119
|
+
}
|
|
120
|
+
return output;
|
|
121
|
+
}
|
|
122
|
+
if (isRecord(output)) {
|
|
123
|
+
// AI SDK onToolExecutionEnd passes { type: 'tool-result', output } / { type: 'tool-error', error }.
|
|
124
|
+
if (output.type === 'tool-result' && 'output' in output) {
|
|
125
|
+
return unwrapToolOutput(output.output);
|
|
126
|
+
}
|
|
127
|
+
if (output.type === 'tool-error' && 'error' in output) {
|
|
128
|
+
return unwrapToolOutput(output.error);
|
|
129
|
+
}
|
|
130
|
+
if (typeof output.text === 'string' && Object.keys(output).length <= 3) {
|
|
131
|
+
return unwrapToolOutput(output.text);
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(output.content))
|
|
134
|
+
return unwrapToolOutput(output.content);
|
|
135
|
+
if ('value' in output)
|
|
136
|
+
return unwrapToolOutput(output.value);
|
|
137
|
+
}
|
|
138
|
+
return output;
|
|
139
|
+
}
|
|
140
|
+
/** Prefer the AI SDK tool-result input; fall back to the tool call. */
|
|
141
|
+
export function toolInputFrom(toolCall, toolOutput) {
|
|
142
|
+
if (isRecord(toolOutput) && 'input' in toolOutput)
|
|
143
|
+
return toolOutput.input;
|
|
144
|
+
if (isRecord(toolCall) && 'input' in toolCall)
|
|
145
|
+
return toolCall.input;
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
export function prIdentityFromInput(input) {
|
|
149
|
+
if (!isRecord(input))
|
|
150
|
+
return null;
|
|
151
|
+
const owner = asString(input.owner);
|
|
152
|
+
const repo = asString(input.repo);
|
|
153
|
+
const pullNumber = asNumber(input.pullNumber) ?? asNumber(input.pull_number);
|
|
154
|
+
if (!owner || !repo || pullNumber == null)
|
|
155
|
+
return null;
|
|
156
|
+
return { owner, repo, pullNumber };
|
|
157
|
+
}
|
|
158
|
+
const PR_FILE_FOLLOW_UP_METHODS = new Set(['get', 'get_files', 'get_diff']);
|
|
159
|
+
/** True when pull_request_read didn't yield a Diff widget but we can still fetch files. */
|
|
160
|
+
export function shouldFollowUpPrFiles(args) {
|
|
161
|
+
if (args.hasDiffWidget)
|
|
162
|
+
return false;
|
|
163
|
+
if (!args.toolName.toLowerCase().includes('pull_request_read'))
|
|
164
|
+
return false;
|
|
165
|
+
if (!prIdentityFromInput(args.input))
|
|
166
|
+
return false;
|
|
167
|
+
const method = isRecord(args.input) ? asString(args.input.method)?.toLowerCase() : undefined;
|
|
168
|
+
if (!method)
|
|
169
|
+
return true;
|
|
170
|
+
return PR_FILE_FOLLOW_UP_METHODS.has(method);
|
|
171
|
+
}
|
|
172
|
+
function toDiffFile(file) {
|
|
173
|
+
const path = asString(file.filename);
|
|
174
|
+
if (!path)
|
|
175
|
+
return null;
|
|
176
|
+
const oldPath = asString(file.previous_filename);
|
|
177
|
+
const rawPatch = asString(file.patch);
|
|
178
|
+
const capped = capPatch(rawPatch);
|
|
179
|
+
const stats = rawPatch ? countPatchStats(rawPatch) : { additions: 0, deletions: 0 };
|
|
180
|
+
return {
|
|
181
|
+
path,
|
|
182
|
+
...(oldPath ? { oldPath } : {}),
|
|
183
|
+
status: mapStatus(asString(file.status), oldPath),
|
|
184
|
+
...(languageFromPath(path) ? { language: languageFromPath(path) } : {}),
|
|
185
|
+
additions: asNumber(file.additions) ?? (stats.additions || undefined),
|
|
186
|
+
deletions: asNumber(file.deletions) ?? (stats.deletions || undefined),
|
|
187
|
+
...capped,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function splitUnifiedDiff(raw) {
|
|
191
|
+
const text = raw.replace(/\r\n/g, '\n');
|
|
192
|
+
const starts = [];
|
|
193
|
+
const header = /^diff --git /gm;
|
|
194
|
+
let match;
|
|
195
|
+
while ((match = header.exec(text)))
|
|
196
|
+
starts.push(match.index);
|
|
197
|
+
if (starts.length === 0) {
|
|
198
|
+
if (!text.trim())
|
|
199
|
+
return [];
|
|
200
|
+
const stats = countPatchStats(text);
|
|
201
|
+
const capped = capPatch(text);
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
path: 'diff',
|
|
205
|
+
status: 'modified',
|
|
206
|
+
additions: stats.additions || undefined,
|
|
207
|
+
deletions: stats.deletions || undefined,
|
|
208
|
+
...capped,
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
return starts
|
|
213
|
+
.map((start, index) => {
|
|
214
|
+
const chunk = text.slice(start, starts[index + 1]);
|
|
215
|
+
const names = /^diff --git a\/(.+?) b\/(.+)$/m.exec(chunk);
|
|
216
|
+
const oldPath = names?.[1] ?? 'unknown';
|
|
217
|
+
const path = names?.[2] ?? oldPath;
|
|
218
|
+
let status = 'modified';
|
|
219
|
+
if (/^new file mode /m.test(chunk) || oldPath === '/dev/null')
|
|
220
|
+
status = 'added';
|
|
221
|
+
else if (/^deleted file mode /m.test(chunk) || path === '/dev/null')
|
|
222
|
+
status = 'deleted';
|
|
223
|
+
else if (/^rename from /m.test(chunk) || oldPath !== path)
|
|
224
|
+
status = 'renamed';
|
|
225
|
+
const stats = countPatchStats(chunk);
|
|
226
|
+
return {
|
|
227
|
+
path: path === '/dev/null' ? oldPath : path,
|
|
228
|
+
...(status === 'renamed' && oldPath !== path ? { oldPath } : {}),
|
|
229
|
+
status,
|
|
230
|
+
...(languageFromPath(path) ? { language: languageFromPath(path) } : {}),
|
|
231
|
+
additions: stats.additions || undefined,
|
|
232
|
+
deletions: stats.deletions || undefined,
|
|
233
|
+
...capPatch(chunk),
|
|
234
|
+
};
|
|
235
|
+
})
|
|
236
|
+
.slice(0, MAX_FILES);
|
|
237
|
+
}
|
|
238
|
+
function githubFileFromUnknown(item) {
|
|
239
|
+
if (!isRecord(item))
|
|
240
|
+
return null;
|
|
241
|
+
const filename = asString(item.filename) ?? asString(item.path) ?? asString(item.name);
|
|
242
|
+
if (!filename)
|
|
243
|
+
return null;
|
|
244
|
+
return {
|
|
245
|
+
filename,
|
|
246
|
+
previous_filename: asString(item.previous_filename) ??
|
|
247
|
+
asString(item.previousFilename) ??
|
|
248
|
+
asString(item.oldPath) ??
|
|
249
|
+
asString(item.old_path),
|
|
250
|
+
status: item.status,
|
|
251
|
+
additions: item.additions,
|
|
252
|
+
deletions: item.deletions,
|
|
253
|
+
patch: item.patch,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function filesFromUnknown(payload) {
|
|
257
|
+
if (Array.isArray(payload)) {
|
|
258
|
+
const files = payload
|
|
259
|
+
.map(githubFileFromUnknown)
|
|
260
|
+
.filter((file) => file != null);
|
|
261
|
+
return files.length > 0 ? files : null;
|
|
262
|
+
}
|
|
263
|
+
if (isRecord(payload) && Array.isArray(payload.files)) {
|
|
264
|
+
return filesFromUnknown(payload.files);
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function isChangedFilePayload(payload) {
|
|
269
|
+
const files = filesFromUnknown(payload);
|
|
270
|
+
if (!files?.length)
|
|
271
|
+
return false;
|
|
272
|
+
return files.some((file) => typeof file.patch === 'string' ||
|
|
273
|
+
typeof file.status === 'string' ||
|
|
274
|
+
typeof file.additions === 'number' ||
|
|
275
|
+
typeof file.deletions === 'number');
|
|
276
|
+
}
|
|
277
|
+
function summarize(files) {
|
|
278
|
+
const additions = files.reduce((sum, file) => sum + (file.additions ?? 0), 0);
|
|
279
|
+
const deletions = files.reduce((sum, file) => sum + (file.deletions ?? 0), 0);
|
|
280
|
+
const fileLabel = files.length === 1 ? '1 file' : `${files.length} files`;
|
|
281
|
+
if (!additions && !deletions)
|
|
282
|
+
return fileLabel;
|
|
283
|
+
return `${fileLabel} · +${additions} −${deletions}`;
|
|
284
|
+
}
|
|
285
|
+
function titleFromInput(input) {
|
|
286
|
+
if (!input)
|
|
287
|
+
return 'Diff';
|
|
288
|
+
const owner = asString(input.owner);
|
|
289
|
+
const repo = asString(input.repo);
|
|
290
|
+
const pullNumber = asNumber(input.pullNumber) ?? asNumber(input.pull_number);
|
|
291
|
+
const sha = asString(input.sha);
|
|
292
|
+
if (owner && repo && pullNumber != null)
|
|
293
|
+
return `${owner}/${repo}#${pullNumber}`;
|
|
294
|
+
if (owner && repo && sha)
|
|
295
|
+
return `${owner}/${repo}@${sha.slice(0, 7)}`;
|
|
296
|
+
if (owner && repo)
|
|
297
|
+
return `${owner}/${repo}`;
|
|
298
|
+
return 'Diff';
|
|
299
|
+
}
|
|
300
|
+
function filesFromPayload(payload) {
|
|
301
|
+
if (typeof payload === 'string') {
|
|
302
|
+
const trimmed = payload.trim();
|
|
303
|
+
if (trimmed.startsWith('diff --git') || trimmed.startsWith('@@')) {
|
|
304
|
+
return splitUnifiedDiff(payload);
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const githubFiles = filesFromUnknown(payload);
|
|
309
|
+
if (!githubFiles)
|
|
310
|
+
return null;
|
|
311
|
+
const files = githubFiles
|
|
312
|
+
.map(toDiffFile)
|
|
313
|
+
.filter((file) => file != null)
|
|
314
|
+
.slice(0, MAX_FILES);
|
|
315
|
+
return files.length > 0 ? files : null;
|
|
316
|
+
}
|
|
317
|
+
export function diffWidgetFromTool(args) {
|
|
318
|
+
const toolName = args.toolName.toLowerCase();
|
|
319
|
+
const input = isRecord(args.input) ? args.input : undefined;
|
|
320
|
+
const method = asString(input?.method)?.toLowerCase();
|
|
321
|
+
const payload = unwrapToolOutput(args.output);
|
|
322
|
+
const looksLikeDiffTool = (toolName.includes('pull_request_read') &&
|
|
323
|
+
(method === 'get_files' || method === 'get_diff')) ||
|
|
324
|
+
toolName.includes('get_commit') ||
|
|
325
|
+
toolName.includes('get_diff') ||
|
|
326
|
+
(typeof payload === 'string' && payload.trim().startsWith('diff --git')) ||
|
|
327
|
+
isChangedFilePayload(payload);
|
|
328
|
+
if (!looksLikeDiffTool)
|
|
329
|
+
return null;
|
|
330
|
+
const files = filesFromPayload(payload);
|
|
331
|
+
if (!files || files.length === 0)
|
|
332
|
+
return null;
|
|
333
|
+
return {
|
|
334
|
+
kind: 'diff',
|
|
335
|
+
widgetId: args.widgetId,
|
|
336
|
+
title: titleFromInput(input),
|
|
337
|
+
description: summarize(files),
|
|
338
|
+
files,
|
|
339
|
+
size: 'full',
|
|
340
|
+
display: 'expanded',
|
|
341
|
+
metadata: {
|
|
342
|
+
toolName: args.toolName,
|
|
343
|
+
...(method ? { method } : {}),
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
package/dist/index.d.ts
ADDED