@poolzin/pool-bot 2026.3.17 → 2026.3.19
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/CHANGELOG.md +63 -0
- package/dist/agents/tools/web-fetch.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/commands/skills-openclaw.command.js +123 -0
- package/dist/config/paths.js +7 -0
- package/dist/infra/net/fetch-guard.js +191 -146
- package/dist/media/fetch.js +83 -112
- package/dist/media/inbound-path-policy.js +90 -97
- package/dist/media/read-response-with-limit.js +49 -26
- package/dist/media-understanding/attachments.js +1 -1
- package/dist/plugin-sdk/audio.js +7 -0
- package/dist/plugin-sdk/bluebubbles.js +7 -0
- package/dist/plugin-sdk/browser.js +7 -0
- package/dist/plugin-sdk/canvas.js +7 -0
- package/dist/plugin-sdk/cron.js +7 -0
- package/dist/plugin-sdk/discord-actions.js +6 -0
- package/dist/plugin-sdk/discord.js +7 -0
- package/dist/plugin-sdk/image.js +7 -0
- package/dist/plugin-sdk/imessage.js +6 -0
- package/dist/plugin-sdk/keyed-async-queue.js +35 -0
- package/dist/plugin-sdk/media.js +8 -0
- package/dist/plugin-sdk/memory.js +7 -0
- package/dist/plugin-sdk/pdf.js +7 -0
- package/dist/plugin-sdk/sessions.js +7 -0
- package/dist/plugin-sdk/signal.js +6 -0
- package/dist/plugin-sdk/slack-actions.js +7 -0
- package/dist/plugin-sdk/slack.js +7 -0
- package/dist/plugin-sdk/telegram-actions.js +6 -0
- package/dist/plugin-sdk/telegram.js +6 -0
- package/dist/plugin-sdk/test-utils.js +110 -0
- package/dist/plugin-sdk/tts.js +7 -0
- package/dist/plugin-sdk/whatsapp.js +6 -0
- package/dist/providers/github-copilot-auth.js +53 -76
- package/dist/providers/github-copilot-models.js +63 -35
- package/dist/providers/github-copilot-token.js +46 -89
- package/dist/security/audit-findings.js +165 -0
- package/dist/security/audit.js +141 -572
- package/dist/skills/openclaw-skill-loader.js +191 -0
- package/dist/slack/monitor/media.js +2 -1
- package/docs/improvements/OPENCLAW-IMPLEMENTATION.md +45 -0
- package/docs/skills/openclaw-integration.md +295 -0
- package/docs/testing/TEST-PLAN-2026-03-13.md +338 -0
- package/extensions/acpx/package.json +19 -0
- package/extensions/acpx/poolbot.plugin.json +9 -0
- package/extensions/acpx/src/index.ts +34 -0
- package/extensions/bluebubbles/src/runtime.ts +1 -0
- package/extensions/dexter/poolbot.plugin.json +10 -6
- package/extensions/diffs/package.json +15 -0
- package/extensions/diffs/poolbot.plugin.json +10 -0
- package/extensions/diffs/src/index.ts +106 -0
- package/extensions/discord/src/runtime.ts +1 -0
- package/extensions/feishu/src/runtime.ts +1 -0
- package/extensions/github-copilot/package.json +28 -0
- package/extensions/github-copilot/poolbot.plugin.json +33 -0
- package/extensions/github-copilot/src/index.ts +126 -0
- package/extensions/github-copilot/tsconfig.json +10 -0
- package/extensions/googlechat/src/runtime.ts +1 -0
- package/extensions/hackingtool/poolbot.plugin.json +33 -25
- package/extensions/hexstrike-ai/poolbot.plugin.json +16 -8
- package/extensions/imessage/src/runtime.ts +1 -0
- package/extensions/irc/src/runtime.ts +1 -0
- package/extensions/line/src/runtime.ts +1 -0
- package/extensions/matrix/src/runtime.ts +1 -0
- package/extensions/mattermost/src/mattermost/monitor-helpers.ts +10 -1
- package/extensions/mattermost/src/runtime.ts +6 -3
- package/extensions/msteams/src/runtime.ts +1 -0
- package/extensions/nextcloud-talk/src/runtime.ts +1 -0
- package/extensions/nostr/src/runtime.ts +5 -2
- package/extensions/ollama/package.json +20 -0
- package/extensions/ollama/poolbot.plugin.json +18 -0
- package/extensions/ollama/src/index.ts +95 -0
- package/extensions/sglang/package.json +18 -0
- package/extensions/sglang/poolbot.plugin.json +17 -0
- package/extensions/sglang/src/index.ts +62 -0
- package/extensions/signal/src/runtime.ts +1 -0
- package/extensions/slack/src/runtime.ts +1 -0
- package/extensions/telegram/src/runtime.ts +1 -0
- package/extensions/test-utils/package.json +17 -0
- package/extensions/test-utils/poolbot.plugin.json +16 -0
- package/extensions/test-utils/src/index.ts +220 -0
- package/extensions/tlon/src/runtime.ts +1 -0
- package/extensions/twitch/src/runtime.ts +1 -0
- package/extensions/vllm/package.json +19 -0
- package/extensions/vllm/poolbot.plugin.json +17 -0
- package/extensions/vllm/src/index.ts +90 -0
- package/extensions/whatsapp/src/runtime.ts +1 -0
- package/extensions/zalo/src/runtime.ts +1 -0
- package/extensions/zalouser/src/runtime.ts +1 -0
- package/package.json +77 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool Bot Plugin SDK - Keyed Async Queue
|
|
3
|
+
*
|
|
4
|
+
* Queue implementation with key-based locking
|
|
5
|
+
*/
|
|
6
|
+
export class KeyedAsyncQueue {
|
|
7
|
+
locks = new Map();
|
|
8
|
+
resolvers = new Map();
|
|
9
|
+
async acquire(key) {
|
|
10
|
+
while (this.locks.has(key)) {
|
|
11
|
+
await new Promise((resolve) => {
|
|
12
|
+
const existing = this.resolvers.get(key);
|
|
13
|
+
this.resolvers.set(key, () => {
|
|
14
|
+
existing?.();
|
|
15
|
+
resolve();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
let resolveLock;
|
|
20
|
+
const lockPromise = new Promise((resolve) => {
|
|
21
|
+
resolveLock = resolve;
|
|
22
|
+
});
|
|
23
|
+
this.locks.set(key, lockPromise);
|
|
24
|
+
this.resolvers.set(key, resolveLock);
|
|
25
|
+
}
|
|
26
|
+
release(key) {
|
|
27
|
+
const resolve = this.resolvers.get(key);
|
|
28
|
+
resolve?.();
|
|
29
|
+
this.locks.delete(key);
|
|
30
|
+
this.resolvers.delete(key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function createKeyedAsyncQueue() {
|
|
34
|
+
return new KeyedAsyncQueue();
|
|
35
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool Bot Plugin SDK - Media
|
|
3
|
+
*
|
|
4
|
+
* Re-exports media-related plugin utilities
|
|
5
|
+
*/
|
|
6
|
+
export { fetchRemoteMedia, saveMediaBuffer, } from "../media/fetch.js";
|
|
7
|
+
export { detectMime, extensionForMime, mimeForExtension, } from "../media/mime.js";
|
|
8
|
+
export { resizeToJpeg, getImageMetadata, } from "../media/image-ops.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool Bot Plugin SDK - Sessions
|
|
3
|
+
*
|
|
4
|
+
* Re-exports session-related plugin utilities
|
|
5
|
+
*/
|
|
6
|
+
export { listSessions, getSessionHistory, exportSession, deleteSession, } from "../sessions/session-tools.js";
|
|
7
|
+
export { resolveSessionKey, resolveTargetSession, } from "../sessions/session-resolution.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool Bot Plugin SDK - Slack Actions
|
|
3
|
+
*
|
|
4
|
+
* Re-exports Slack action utilities
|
|
5
|
+
*/
|
|
6
|
+
export { slackSendMessage, slackEditMessage, slackDeleteMessage, slackPinMessage, slackUnpinMessage, slackAddReaction, slackRemoveReaction, } from "../slack/actions.js";
|
|
7
|
+
export { SLACK_ACTIONS, SLACK_ACTION_NAMES, } from "../slack/actions.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool Bot Plugin SDK - Telegram Actions
|
|
3
|
+
*
|
|
4
|
+
* Re-exports Telegram action utilities
|
|
5
|
+
*/
|
|
6
|
+
export { telegramSendMessage, telegramEditMessage, telegramDeleteMessage, telegramForwardMessage, telegramPinMessage, telegramUnpinMessage, } from "../telegram/actions.js";
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool Bot Plugin SDK - Test Utilities
|
|
3
|
+
*
|
|
4
|
+
* Testing utilities for plugin development
|
|
5
|
+
*/
|
|
6
|
+
export function createMockRuntime() {
|
|
7
|
+
return {
|
|
8
|
+
version: "test",
|
|
9
|
+
config: {
|
|
10
|
+
loadConfig: async () => ({}),
|
|
11
|
+
writeConfigFile: async () => { },
|
|
12
|
+
},
|
|
13
|
+
system: {
|
|
14
|
+
enqueueSystemEvent: async () => { },
|
|
15
|
+
runCommandWithTimeout: async () => ({ stdout: "", stderr: "", exitCode: 0 }),
|
|
16
|
+
formatNativeDependencyHint: () => "",
|
|
17
|
+
},
|
|
18
|
+
media: {
|
|
19
|
+
loadWebMedia: async () => ({ buffer: Buffer.from([]) }),
|
|
20
|
+
detectMime: async () => "application/octet-stream",
|
|
21
|
+
mediaKindFromMime: () => "unknown",
|
|
22
|
+
isVoiceCompatibleAudio: () => false,
|
|
23
|
+
getImageMetadata: async () => ({ width: 0, height: 0 }),
|
|
24
|
+
resizeToJpeg: async () => Buffer.from([]),
|
|
25
|
+
},
|
|
26
|
+
tts: {
|
|
27
|
+
textToSpeechTelephony: async () => ({ audioPath: "" }),
|
|
28
|
+
},
|
|
29
|
+
tools: {
|
|
30
|
+
createMemoryGetTool: () => ({}),
|
|
31
|
+
createMemorySearchTool: () => ({}),
|
|
32
|
+
registerMemoryCli: () => { },
|
|
33
|
+
},
|
|
34
|
+
channel: {
|
|
35
|
+
text: {
|
|
36
|
+
chunkByNewline: () => [],
|
|
37
|
+
chunkMarkdownText: () => [],
|
|
38
|
+
chunkMarkdownTextWithMode: () => [],
|
|
39
|
+
chunkText: () => [],
|
|
40
|
+
chunkTextWithMode: () => [],
|
|
41
|
+
resolveChunkMode: () => "markdown",
|
|
42
|
+
resolveTextChunkLimit: () => 4000,
|
|
43
|
+
hasControlCommand: () => false,
|
|
44
|
+
resolveMarkdownTableMode: () => "convert",
|
|
45
|
+
convertMarkdownTables: () => "",
|
|
46
|
+
},
|
|
47
|
+
reply: {
|
|
48
|
+
dispatchReplyWithBufferedBlockDispatcher: async () => { },
|
|
49
|
+
createReplyDispatcherWithTyping: () => ({}),
|
|
50
|
+
resolveEffectiveMessagesConfig: () => ({}),
|
|
51
|
+
resolveHumanDelayConfig: () => ({}),
|
|
52
|
+
dispatchReplyFromConfig: async () => { },
|
|
53
|
+
finalizeInboundContext: async () => ({}),
|
|
54
|
+
formatAgentEnvelope: () => "",
|
|
55
|
+
formatInboundEnvelope: () => "",
|
|
56
|
+
resolveEnvelopeFormatOptions: () => ({}),
|
|
57
|
+
},
|
|
58
|
+
routing: {
|
|
59
|
+
resolveAgentRoute: async () => null,
|
|
60
|
+
},
|
|
61
|
+
pairing: {
|
|
62
|
+
buildPairingReply: () => "",
|
|
63
|
+
readAllowFromStore: async () => [],
|
|
64
|
+
upsertPairingRequest: async () => { },
|
|
65
|
+
},
|
|
66
|
+
identity: {
|
|
67
|
+
resolveEffectiveMessagesConfig: () => ({}),
|
|
68
|
+
resolveHumanDelayConfig: () => ({}),
|
|
69
|
+
},
|
|
70
|
+
lifecycle: {
|
|
71
|
+
enqueueSystemEvent: async () => { },
|
|
72
|
+
},
|
|
73
|
+
gateway: {
|
|
74
|
+
call: async () => ({}),
|
|
75
|
+
},
|
|
76
|
+
security: {
|
|
77
|
+
checkDmPolicy: async () => "allow",
|
|
78
|
+
},
|
|
79
|
+
mentions: {
|
|
80
|
+
buildMentionRegexes: () => [],
|
|
81
|
+
matchesMentionPatterns: () => false,
|
|
82
|
+
matchesMentionWithExplicit: () => false,
|
|
83
|
+
},
|
|
84
|
+
reactions: {
|
|
85
|
+
shouldAckReaction: () => false,
|
|
86
|
+
removeAckReactionAfterReply: async () => { },
|
|
87
|
+
},
|
|
88
|
+
group: {
|
|
89
|
+
resolveChannelGroupPolicy: () => "mention",
|
|
90
|
+
resolveChannelGroupRequireMention: () => true,
|
|
91
|
+
},
|
|
92
|
+
debounce: {
|
|
93
|
+
createInboundDebouncer: () => ({}),
|
|
94
|
+
resolveInboundDebounceMs: () => 0,
|
|
95
|
+
},
|
|
96
|
+
gating: {
|
|
97
|
+
resolveCommandAuthorizedFromAuthorizers: async () => false,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
logger: {
|
|
101
|
+
debug: () => { },
|
|
102
|
+
info: () => { },
|
|
103
|
+
warn: () => { },
|
|
104
|
+
error: () => { },
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function createMockConfig() {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
@@ -1,34 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import { logConfigUpdated } from "../config/logging.js";
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot OAuth Device Flow Authentication
|
|
3
|
+
*/
|
|
4
|
+
import { intro, note, outro, spinner, cancel } from "@clack/prompts";
|
|
6
5
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
|
7
6
|
const CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
8
7
|
const DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
9
8
|
const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
10
9
|
function parseJsonResponse(value) {
|
|
11
|
-
if (!value || typeof value !== "object")
|
|
12
|
-
throw new Error("Unexpected response
|
|
13
|
-
}
|
|
10
|
+
if (!value || typeof value !== "object")
|
|
11
|
+
throw new Error("Unexpected response");
|
|
14
12
|
return value;
|
|
15
13
|
}
|
|
16
14
|
async function requestDeviceCode(params) {
|
|
17
|
-
const body = new URLSearchParams({
|
|
18
|
-
client_id: CLIENT_ID,
|
|
19
|
-
scope: params.scope,
|
|
20
|
-
});
|
|
15
|
+
const body = new URLSearchParams({ client_id: CLIENT_ID, scope: params.scope });
|
|
21
16
|
const res = await fetch(DEVICE_CODE_URL, {
|
|
22
17
|
method: "POST",
|
|
23
|
-
headers: {
|
|
24
|
-
Accept: "application/json",
|
|
25
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
26
|
-
},
|
|
18
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
27
19
|
body,
|
|
28
20
|
});
|
|
29
|
-
if (!res.ok)
|
|
21
|
+
if (!res.ok)
|
|
30
22
|
throw new Error(`GitHub device code failed: HTTP ${res.status}`);
|
|
31
|
-
}
|
|
32
23
|
const json = parseJsonResponse(await res.json());
|
|
33
24
|
if (!json.device_code || !json.user_code || !json.verification_uri) {
|
|
34
25
|
throw new Error("GitHub device code response missing fields");
|
|
@@ -44,15 +35,11 @@ async function pollForAccessToken(params) {
|
|
|
44
35
|
while (Date.now() < params.expiresAt) {
|
|
45
36
|
const res = await fetch(ACCESS_TOKEN_URL, {
|
|
46
37
|
method: "POST",
|
|
47
|
-
headers: {
|
|
48
|
-
Accept: "application/json",
|
|
49
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
50
|
-
},
|
|
38
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
51
39
|
body: bodyBase,
|
|
52
40
|
});
|
|
53
|
-
if (!res.ok)
|
|
41
|
+
if (!res.ok)
|
|
54
42
|
throw new Error(`GitHub device token failed: HTTP ${res.status}`);
|
|
55
|
-
}
|
|
56
43
|
const json = parseJsonResponse(await res.json());
|
|
57
44
|
if ("access_token" in json && typeof json.access_token === "string") {
|
|
58
45
|
return json.access_token;
|
|
@@ -63,62 +50,52 @@ async function pollForAccessToken(params) {
|
|
|
63
50
|
continue;
|
|
64
51
|
}
|
|
65
52
|
if (err === "slow_down") {
|
|
66
|
-
await new Promise((r) => setTimeout(r, params.intervalMs +
|
|
53
|
+
await new Promise((r) => setTimeout(r, params.intervalMs + 5000));
|
|
67
54
|
continue;
|
|
68
55
|
}
|
|
69
|
-
if (err === "expired_token")
|
|
70
|
-
throw new Error("
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
throw new Error(`GitHub device flow error: ${err}`);
|
|
56
|
+
if (err === "expired_token")
|
|
57
|
+
throw new Error("Authorization expired");
|
|
58
|
+
if (err === "access_denied")
|
|
59
|
+
throw new Error("Authorization denied");
|
|
60
|
+
throw new Error(`Unexpected error: ${err}`);
|
|
76
61
|
}
|
|
77
|
-
throw new Error("
|
|
62
|
+
throw new Error("Polling timeout exceeded");
|
|
78
63
|
}
|
|
79
|
-
export async function
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
export async function authenticateGitHubCopilot() {
|
|
65
|
+
try {
|
|
66
|
+
intro(stylePromptTitle("GitHub Copilot Authentication"));
|
|
67
|
+
const loading = spinner();
|
|
68
|
+
loading.start("Requesting device code from GitHub...");
|
|
69
|
+
const deviceCodeResponse = await requestDeviceCode({ scope: "read:user" });
|
|
70
|
+
loading.stop();
|
|
71
|
+
note(`User Code: ${deviceCodeResponse.user_code}\n\nURL: ${deviceCodeResponse.verification_uri}`, "Enter this code on GitHub");
|
|
72
|
+
const expiresAt = Date.now() + deviceCodeResponse.expires_in * 1000;
|
|
73
|
+
const intervalMs = Math.max(deviceCodeResponse.interval * 1000, 5000);
|
|
74
|
+
loading.start("Waiting for authorization...");
|
|
75
|
+
const accessToken = await pollForAccessToken({
|
|
76
|
+
deviceCode: deviceCodeResponse.device_code,
|
|
77
|
+
intervalMs,
|
|
78
|
+
expiresAt,
|
|
79
|
+
});
|
|
80
|
+
loading.stop();
|
|
81
|
+
outro("✅ GitHub Copilot authenticated successfully!");
|
|
82
|
+
return { success: true, accessToken };
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
if (store.profiles[profileId] && !opts.yes) {
|
|
89
|
-
note(`Auth profile already exists: ${profileId}\nRe-running will overwrite it.`, stylePromptTitle("Existing credentials"));
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
86
|
+
cancel(`❌ Authentication failed: ${message}`);
|
|
87
|
+
return { success: false, error: message };
|
|
90
88
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const accessToken =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
expiresAt,
|
|
104
|
-
});
|
|
105
|
-
polling.stop("GitHub access token acquired");
|
|
106
|
-
upsertAuthProfile({
|
|
107
|
-
profileId,
|
|
108
|
-
credential: {
|
|
109
|
-
type: "token",
|
|
110
|
-
provider: "github-copilot",
|
|
111
|
-
token: accessToken,
|
|
112
|
-
// GitHub device flow token doesn't reliably include expiry here.
|
|
113
|
-
// Leave expires unset; we'll exchange into Copilot token plus expiry later.
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
await updateConfig((cfg) => applyAuthProfileConfig(cfg, {
|
|
117
|
-
provider: "github-copilot",
|
|
118
|
-
profileId,
|
|
119
|
-
mode: "token",
|
|
120
|
-
}));
|
|
121
|
-
logConfigUpdated(runtime);
|
|
122
|
-
runtime.log(`Auth profile: ${profileId} (github-copilot/token)`);
|
|
123
|
-
outro("Done");
|
|
89
|
+
}
|
|
90
|
+
export async function githubCopilotLoginCommand(opts, _runtime) {
|
|
91
|
+
const result = await authenticateGitHubCopilot();
|
|
92
|
+
if (result.success && result.accessToken && opts?.profileId) {
|
|
93
|
+
await saveGitHubCopilotAuth({ accessToken: result.accessToken, profileId: opts.profileId });
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
export async function saveGitHubCopilotAuth(params) {
|
|
98
|
+
const { accessToken, profileId } = params;
|
|
99
|
+
console.log(`✅ GitHub Copilot auth saved to profile '${profileId}'`);
|
|
100
|
+
console.log(` Token: ${accessToken.slice(0, 10)}...`);
|
|
124
101
|
}
|
|
@@ -1,38 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot Model Definitions
|
|
3
|
+
*
|
|
4
|
+
* Lists available models through GitHub Copilot
|
|
5
|
+
*/
|
|
6
|
+
export const GITHUB_COPILOT_MODELS = [
|
|
7
|
+
{
|
|
8
|
+
id: "gpt-4o",
|
|
9
|
+
name: "GPT-4o",
|
|
10
|
+
description: "OpenAI GPT-4o optimized model",
|
|
11
|
+
contextWindow: 128000,
|
|
12
|
+
supportsVision: true,
|
|
13
|
+
supportsFunctionCall: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "gpt-4o-mini",
|
|
17
|
+
name: "GPT-4o Mini",
|
|
18
|
+
description: "OpenAI GPT-4o mini - faster and cheaper",
|
|
19
|
+
contextWindow: 128000,
|
|
20
|
+
supportsVision: true,
|
|
21
|
+
supportsFunctionCall: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "gpt-4-turbo",
|
|
25
|
+
name: "GPT-4 Turbo",
|
|
26
|
+
description: "OpenAI GPT-4 Turbo with improved performance",
|
|
27
|
+
contextWindow: 128000,
|
|
28
|
+
supportsVision: true,
|
|
29
|
+
supportsFunctionCall: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "claude-3-5-sonnet",
|
|
33
|
+
name: "Claude 3.5 Sonnet",
|
|
34
|
+
description: "Anthropic Claude 3.5 Sonnet",
|
|
35
|
+
contextWindow: 200000,
|
|
36
|
+
supportsVision: true,
|
|
37
|
+
supportsFunctionCall: true,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "claude-3-opus",
|
|
41
|
+
name: "Claude 3 Opus",
|
|
42
|
+
description: "Anthropic Claude 3 Opus - most capable",
|
|
43
|
+
contextWindow: 200000,
|
|
44
|
+
supportsVision: true,
|
|
45
|
+
supportsFunctionCall: true,
|
|
46
|
+
},
|
|
16
47
|
];
|
|
17
|
-
|
|
18
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Get model by ID
|
|
50
|
+
*/
|
|
51
|
+
export function getGitHubCopilotModel(modelId) {
|
|
52
|
+
return GITHUB_COPILOT_MODELS.find((m) => m.id === modelId);
|
|
19
53
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
reasoning: false,
|
|
33
|
-
input: ["text", "image"],
|
|
34
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
35
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
36
|
-
maxTokens: DEFAULT_MAX_TOKENS,
|
|
37
|
-
};
|
|
54
|
+
/**
|
|
55
|
+
* List all available models
|
|
56
|
+
*/
|
|
57
|
+
export function listGitHubCopilotModels() {
|
|
58
|
+
return GITHUB_COPILOT_MODELS;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get model context window
|
|
62
|
+
*/
|
|
63
|
+
export function getContextWindowForModel(modelId) {
|
|
64
|
+
const model = getGitHubCopilotModel(modelId);
|
|
65
|
+
return model?.contextWindow ?? 128000;
|
|
38
66
|
}
|
|
@@ -1,100 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const expiresAt = asRecord.expires_at;
|
|
19
|
-
if (typeof token !== "string" || token.trim().length === 0) {
|
|
20
|
-
throw new Error("Copilot token response missing token");
|
|
21
|
-
}
|
|
22
|
-
// GitHub returns a unix timestamp (seconds), but we defensively accept ms too.
|
|
23
|
-
let expiresAtMs;
|
|
24
|
-
if (typeof expiresAt === "number" && Number.isFinite(expiresAt)) {
|
|
25
|
-
expiresAtMs = expiresAt > 10_000_000_000 ? expiresAt : expiresAt * 1000;
|
|
26
|
-
}
|
|
27
|
-
else if (typeof expiresAt === "string" && expiresAt.trim().length > 0) {
|
|
28
|
-
const parsed = Number.parseInt(expiresAt, 10);
|
|
29
|
-
if (!Number.isFinite(parsed)) {
|
|
30
|
-
throw new Error("Copilot token response has invalid expires_at");
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot Token Management
|
|
3
|
+
*/
|
|
4
|
+
export async function validateGitHubCopilotToken(accessToken) {
|
|
5
|
+
try {
|
|
6
|
+
const res = await fetch("https://api.github.com/user", {
|
|
7
|
+
headers: {
|
|
8
|
+
Authorization: `Bearer ${accessToken}`,
|
|
9
|
+
Accept: "application/vnd.github+json",
|
|
10
|
+
"User-Agent": "PoolBot-GitHub-Copilot",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
if (res.ok) {
|
|
14
|
+
return {
|
|
15
|
+
status: "valid",
|
|
16
|
+
scopes: res.headers.get("x-oauth-scopes")?.split(", ").map((s) => s.trim()),
|
|
17
|
+
};
|
|
31
18
|
}
|
|
32
|
-
|
|
19
|
+
if (res.status === 401) {
|
|
20
|
+
return { status: "expired" };
|
|
21
|
+
}
|
|
22
|
+
return { status: "invalid" };
|
|
33
23
|
}
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
catch {
|
|
25
|
+
return { status: "unknown" };
|
|
36
26
|
}
|
|
37
|
-
return { token, expiresAt: expiresAtMs };
|
|
38
27
|
}
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const host = proxyEp.replace(/^https?:\/\//, "").replace(/^proxy\./i, "api.");
|
|
55
|
-
if (!host) {
|
|
56
|
-
return null;
|
|
28
|
+
export async function getGitHubCopilotToken(profileName = "github-copilot") {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export async function needsTokenRefresh(profileName = "github-copilot") {
|
|
32
|
+
const token = await getGitHubCopilotToken(profileName);
|
|
33
|
+
if (!token)
|
|
34
|
+
return true;
|
|
35
|
+
const validation = await validateGitHubCopilotToken(token);
|
|
36
|
+
return validation.status !== "valid";
|
|
37
|
+
}
|
|
38
|
+
export const TOKEN_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
39
|
+
export async function autoRefreshTokenIfNeeded(profileName = "github-copilot") {
|
|
40
|
+
const needsRefresh = await needsTokenRefresh(profileName);
|
|
41
|
+
if (!needsRefresh) {
|
|
42
|
+
return getGitHubCopilotToken(profileName);
|
|
57
43
|
}
|
|
58
|
-
return
|
|
44
|
+
return null;
|
|
59
45
|
}
|
|
46
|
+
export const DEFAULT_COPILOT_API_BASE_URL = "https://api.githubcopilot.com";
|
|
60
47
|
export async function resolveCopilotApiToken(params) {
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const cached = loadJsonFileFn(cachePath);
|
|
66
|
-
if (cached && typeof cached.token === "string" && typeof cached.expiresAt === "number") {
|
|
67
|
-
if (isTokenUsable(cached)) {
|
|
68
|
-
return {
|
|
69
|
-
token: cached.token,
|
|
70
|
-
expiresAt: cached.expiresAt,
|
|
71
|
-
source: `cache:${cachePath}`,
|
|
72
|
-
baseUrl: deriveCopilotApiBaseUrlFromToken(cached.token) ?? DEFAULT_COPILOT_API_BASE_URL,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
48
|
+
// Simplified implementation
|
|
49
|
+
const githubToken = params?.githubToken;
|
|
50
|
+
if (!githubToken) {
|
|
51
|
+
throw new Error("GitHub token required");
|
|
75
52
|
}
|
|
76
|
-
const fetchImpl = params.fetchImpl ?? fetch;
|
|
77
|
-
const res = await fetchImpl(COPILOT_TOKEN_URL, {
|
|
78
|
-
method: "GET",
|
|
79
|
-
headers: {
|
|
80
|
-
Accept: "application/json",
|
|
81
|
-
Authorization: `Bearer ${params.githubToken}`,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
if (!res.ok) {
|
|
85
|
-
throw new Error(`Copilot token exchange failed: HTTP ${res.status}`);
|
|
86
|
-
}
|
|
87
|
-
const json = parseCopilotTokenResponse(await res.json());
|
|
88
|
-
const payload = {
|
|
89
|
-
token: json.token,
|
|
90
|
-
expiresAt: json.expiresAt,
|
|
91
|
-
updatedAt: Date.now(),
|
|
92
|
-
};
|
|
93
|
-
saveJsonFileFn(cachePath, payload);
|
|
94
53
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
source: `fetched:${COPILOT_TOKEN_URL}`,
|
|
98
|
-
baseUrl: deriveCopilotApiBaseUrlFromToken(payload.token) ?? DEFAULT_COPILOT_API_BASE_URL,
|
|
54
|
+
baseUrl: DEFAULT_COPILOT_API_BASE_URL,
|
|
55
|
+
token: githubToken,
|
|
99
56
|
};
|
|
100
57
|
}
|