@mastra/code-sdk 1.0.3-alpha.0 → 1.1.0-alpha.2
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 +49 -0
- package/dist/acp/agent.js +155 -167
- package/dist/acp/agent.js.map +1 -1
- package/dist/acp/event-mapper.js +193 -168
- package/dist/acp/event-mapper.js.map +1 -1
- package/dist/acp/index.js +60 -49
- package/dist/acp/index.js.map +1 -1
- package/dist/acp/server.js +31 -30
- package/dist/acp/server.js.map +1 -1
- package/dist/acp.js +1 -4
- package/dist/agents/credential-resolver.js +40 -29
- package/dist/agents/credential-resolver.js.map +1 -1
- package/dist/agents/custom-provider-source.js +16 -9
- package/dist/agents/custom-provider-source.js.map +1 -1
- package/dist/agents/instructions.js +27 -36
- package/dist/agents/instructions.js.map +1 -1
- package/dist/agents/mastracode-gateway.js +355 -428
- package/dist/agents/mastracode-gateway.js.map +1 -1
- package/dist/agents/memory.js +87 -78
- package/dist/agents/memory.js.map +1 -1
- package/dist/agents/model.js +114 -119
- package/dist/agents/model.js.map +1 -1
- package/dist/agents/modes/build.js +12 -13
- package/dist/agents/modes/build.js.map +1 -1
- package/dist/agents/modes/explore.js +14 -13
- package/dist/agents/modes/explore.js.map +1 -1
- package/dist/agents/modes/plan.js +14 -15
- package/dist/agents/modes/plan.js.map +1 -1
- package/dist/agents/prompts/agent-instructions.js +78 -69
- package/dist/agents/prompts/agent-instructions.js.map +1 -1
- package/dist/agents/prompts/build.js +19 -16
- package/dist/agents/prompts/build.js.map +1 -1
- package/dist/agents/prompts/fast.js +8 -4
- package/dist/agents/prompts/fast.js.map +1 -1
- package/dist/agents/prompts/index.js +48 -47
- package/dist/agents/prompts/index.js.map +1 -1
- package/dist/agents/prompts/model.js +6 -5
- package/dist/agents/prompts/model.js.map +1 -1
- package/dist/agents/prompts/plan.js +20 -16
- package/dist/agents/prompts/plan.js.map +1 -1
- package/dist/agents/prompts/tool-guidance.js +77 -109
- package/dist/agents/prompts/tool-guidance.js.map +1 -1
- package/dist/agents/sandbox-filesystem.js +245 -259
- package/dist/agents/sandbox-filesystem.js.map +1 -1
- package/dist/agents/sandbox-reattach.js +9 -11
- package/dist/agents/sandbox-reattach.js.map +1 -1
- package/dist/agents/thread-caveman-state.js +92 -81
- package/dist/agents/thread-caveman-state.js.map +1 -1
- package/dist/agents/tool-availability.js +62 -80
- package/dist/agents/tool-availability.js.map +1 -1
- package/dist/agents/tools.js +99 -139
- package/dist/agents/tools.js.map +1 -1
- package/dist/agents/workspace.js +232 -225
- package/dist/agents/workspace.js.map +1 -1
- package/dist/analytics.js +139 -128
- package/dist/analytics.js.map +1 -1
- package/dist/auth/authorization-input.js +39 -25
- package/dist/auth/authorization-input.js.map +1 -1
- package/dist/auth/device-code.js +112 -80
- package/dist/auth/device-code.js.map +1 -1
- package/dist/auth/index.js +2 -9
- package/dist/auth/pkce.js +27 -16
- package/dist/auth/pkce.js.map +1 -1
- package/dist/auth/providers/anthropic.js +107 -93
- package/dist/auth/providers/anthropic.js.map +1 -1
- package/dist/auth/providers/github-copilot.js +304 -293
- package/dist/auth/providers/github-copilot.js.map +1 -1
- package/dist/auth/providers/openai-codex.js +442 -460
- package/dist/auth/providers/openai-codex.js.map +1 -1
- package/dist/auth/providers/xai.js +179 -145
- package/dist/auth/providers/xai.js.map +1 -1
- package/dist/auth/storage.js +182 -175
- package/dist/auth/storage.js.map +1 -1
- package/dist/auth/types.js +1 -1
- package/dist/clipboard/index.js +206 -172
- package/dist/clipboard/index.js.map +1 -1
- package/dist/constants.js +11 -16
- package/dist/constants.js.map +1 -1
- package/dist/error-classification.js +19 -11
- package/dist/error-classification.js.map +1 -1
- package/dist/evals/context-builder.js +145 -104
- package/dist/evals/context-builder.js.map +1 -1
- package/dist/evals/index.js +4 -7
- package/dist/evals/scorers/classify-command.js +62 -41
- package/dist/evals/scorers/classify-command.js.map +1 -1
- package/dist/evals/scorers/efficiency.js +239 -181
- package/dist/evals/scorers/efficiency.js.map +1 -1
- package/dist/evals/scorers/extract-tools.js +51 -47
- package/dist/evals/scorers/extract-tools.js.map +1 -1
- package/dist/evals/scorers/index.js +1 -5
- package/dist/evals/scorers/outcome.js +321 -212
- package/dist/evals/scorers/outcome.js.map +1 -1
- package/dist/goal-manager.js +266 -261
- package/dist/goal-manager.js.map +1 -1
- package/dist/goal-signal.js +14 -13
- package/dist/goal-signal.js.map +1 -1
- package/dist/headless/cli.js +153 -154
- package/dist/headless/cli.js.map +1 -1
- package/dist/headless/flags.js +180 -178
- package/dist/headless/flags.js.map +1 -1
- package/dist/headless/format.js +72 -71
- package/dist/headless/format.js.map +1 -1
- package/dist/headless/index.js +5 -31
- package/dist/headless/policy.js +35 -31
- package/dist/headless/policy.js.map +1 -1
- package/dist/headless/run-mc.js +332 -334
- package/dist/headless/run-mc.js.map +1 -1
- package/dist/headless/types.js +16 -7
- package/dist/headless/types.js.map +1 -1
- package/dist/hooks/config.js +56 -61
- package/dist/hooks/config.js.map +1 -1
- package/dist/hooks/executor.js +118 -116
- package/dist/hooks/executor.js.map +1 -1
- package/dist/hooks/index.js +4 -15
- package/dist/hooks/manager.js +221 -280
- package/dist/hooks/manager.js.map +1 -1
- package/dist/hooks/types.js +5 -4
- package/dist/hooks/types.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +632 -712
- package/dist/index.js.map +1 -1
- package/dist/ipc/ipc-reporter.js +19 -22
- package/dist/ipc/ipc-reporter.js.map +1 -1
- package/dist/lsp/client.js +248 -306
- package/dist/lsp/client.js.map +1 -1
- package/dist/lsp/index.js +1 -6
- package/dist/lsp/language.js +39 -43
- package/dist/lsp/language.js.map +1 -1
- package/dist/lsp/manager.js +78 -93
- package/dist/lsp/manager.js.map +1 -1
- package/dist/lsp/server.js +151 -120
- package/dist/lsp/server.js.map +1 -1
- package/dist/lsp/workspace.js +50 -47
- package/dist/lsp/workspace.js.map +1 -1
- package/dist/mcp/config.js +210 -199
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/index.js +2 -9
- package/dist/mcp/manager.js +482 -475
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/types.js +1 -1
- package/dist/onboarding/custom-packs.js +14 -17
- package/dist/onboarding/custom-packs.js.map +1 -1
- package/dist/onboarding/custom-providers.js +22 -20
- package/dist/onboarding/custom-providers.js.map +1 -1
- package/dist/onboarding/index.d.ts +1 -1
- package/dist/onboarding/index.d.ts.map +1 -1
- package/dist/onboarding/index.js +3 -21
- package/dist/onboarding/om-settings.d.ts +5 -0
- package/dist/onboarding/om-settings.d.ts.map +1 -1
- package/dist/onboarding/om-settings.js +41 -20
- package/dist/onboarding/om-settings.js.map +1 -1
- package/dist/onboarding/packs.d.ts +4 -6
- package/dist/onboarding/packs.d.ts.map +1 -1
- package/dist/onboarding/packs.js +154 -105
- package/dist/onboarding/packs.js.map +1 -1
- package/dist/onboarding/settings.js +575 -517
- package/dist/onboarding/settings.js.map +1 -1
- package/dist/permissions.js +112 -99
- package/dist/permissions.js.map +1 -1
- package/dist/plugin.js +21 -23
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/dependencies.js +64 -87
- package/dist/plugins/dependencies.js.map +1 -1
- package/dist/plugins/install.js +159 -169
- package/dist/plugins/install.js.map +1 -1
- package/dist/plugins/loader.js +170 -185
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/manager.js +374 -380
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/manifest.js +38 -63
- package/dist/plugins/manifest.js.map +1 -1
- package/dist/plugins/package-link.js +48 -64
- package/dist/plugins/package-link.js.map +1 -1
- package/dist/plugins/paths.js +18 -19
- package/dist/plugins/paths.js.map +1 -1
- package/dist/plugins/registry.js +97 -81
- package/dist/plugins/registry.js.map +1 -1
- package/dist/plugins/scaffold.js +62 -98
- package/dist/plugins/scaffold.js.map +1 -1
- package/dist/plugins/types.js +1 -1
- package/dist/processors/plan-rejection-abort.js +45 -44
- package/dist/processors/plan-rejection-abort.js.map +1 -1
- package/dist/providers/amazon-bedrock-gateway.js +134 -125
- package/dist/providers/amazon-bedrock-gateway.js.map +1 -1
- package/dist/providers/amazon-bedrock.js +68 -47
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/claude-max.js +153 -133
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/github-copilot.js +232 -230
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/openai-codex.js +271 -260
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/xai.js +57 -50
- package/dist/providers/xai.js.map +1 -1
- package/dist/schema.js +91 -89
- package/dist/schema.js.map +1 -1
- package/dist/theme-palette.js +18 -14
- package/dist/theme-palette.js.map +1 -1
- package/dist/tool-names.js +46 -37
- package/dist/tool-names.js.map +1 -1
- package/dist/tools/index.js +2 -8
- package/dist/tools/request-sandbox-access.js +71 -80
- package/dist/tools/request-sandbox-access.js.map +1 -1
- package/dist/tools/utils.js +26 -16
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/web-search.js +51 -55
- package/dist/tools/web-search.js.map +1 -1
- package/dist/utils/binaries.js +65 -61
- package/dist/utils/binaries.js.map +1 -1
- package/dist/utils/debug-log.js +51 -47
- package/dist/utils/debug-log.js.map +1 -1
- package/dist/utils/errors.js +157 -192
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/gateway-sync.js +49 -37
- package/dist/utils/gateway-sync.js.map +1 -1
- package/dist/utils/path-security.js +7 -6
- package/dist/utils/path-security.js.map +1 -1
- package/dist/utils/plan-diff.js +58 -39
- package/dist/utils/plan-diff.js.map +1 -1
- package/dist/utils/plans.js +92 -74
- package/dist/utils/plans.js.map +1 -1
- package/dist/utils/project.js +318 -256
- package/dist/utils/project.js.map +1 -1
- package/dist/utils/signals-pubsub.js +117 -99
- package/dist/utils/signals-pubsub.js.map +1 -1
- package/dist/utils/slash-command-loader.js +118 -154
- package/dist/utils/slash-command-loader.js.map +1 -1
- package/dist/utils/slash-command-processor.js +92 -83
- package/dist/utils/slash-command-processor.js.map +1 -1
- package/dist/utils/stdin-pipe.js +87 -63
- package/dist/utils/stdin-pipe.js.map +1 -1
- package/dist/utils/storage-factory.js +104 -84
- package/dist/utils/storage-factory.js.map +1 -1
- package/dist/utils/storage-maintenance.js +249 -211
- package/dist/utils/storage-maintenance.js.map +1 -1
- package/dist/utils/thread-lock.js +96 -92
- package/dist/utils/thread-lock.js.map +1 -1
- package/dist/utils/token-estimator.js +12 -12
- package/dist/utils/token-estimator.js.map +1 -1
- package/dist/utils/update-check.d.ts +1 -1
- package/dist/utils/update-check.js +341 -259
- package/dist/utils/update-check.js.map +1 -1
- package/dist/voice/stt-registry.js +131 -113
- package/dist/voice/stt-registry.js.map +1 -1
- package/package.json +11 -11
- package/dist/acp.js.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/evals/index.js.map +0 -1
- package/dist/evals/scorers/index.js.map +0 -1
- package/dist/headless/index.js.map +0 -1
- package/dist/hooks/index.js.map +0 -1
- package/dist/lsp/index.js.map +0 -1
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/types.js.map +0 -1
- package/dist/onboarding/index.js.map +0 -1
- package/dist/plugins/types.js.map +0 -1
- package/dist/tools/index.js.map +0 -1
|
@@ -1,263 +1,265 @@
|
|
|
1
|
-
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
2
|
-
import { applyCompatLayer, GoogleSchemaCompatLayer } from "@mastra/schema-compat";
|
|
3
|
-
import { wrapLanguageModel } from "ai";
|
|
4
1
|
import { COPILOT_HEADERS, fetchCopilotModels, getGitHubCopilotBaseUrl } from "../auth/providers/github-copilot.js";
|
|
5
2
|
import { AuthStorage } from "../auth/storage.js";
|
|
3
|
+
import { wrapLanguageModel } from "ai";
|
|
4
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
5
|
+
import { GoogleSchemaCompatLayer, applyCompatLayer } from "@mastra/schema-compat";
|
|
6
|
+
//#region src/providers/github-copilot.ts
|
|
7
|
+
/**
|
|
8
|
+
* GitHub Copilot OAuth Provider
|
|
9
|
+
*
|
|
10
|
+
* Uses OAuth tokens from AuthStorage to authenticate with GitHub Copilot's chat API.
|
|
11
|
+
* The Copilot API speaks an OpenAI-compatible chat format, so we plug
|
|
12
|
+
* `@ai-sdk/openai-compatible` into Copilot's API URL and use a custom fetch to inject
|
|
13
|
+
* the bearer token and Copilot-specific headers.
|
|
14
|
+
*
|
|
15
|
+
* Inspired by:
|
|
16
|
+
* - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts
|
|
17
|
+
* - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts
|
|
18
|
+
*/
|
|
6
19
|
const COPILOT_PROVIDER_ID = "github-copilot";
|
|
7
20
|
let authStorageInstance = null;
|
|
21
|
+
/** Get or create the shared AuthStorage instance. */
|
|
8
22
|
function getAuthStorage() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
return authStorageInstance;
|
|
23
|
+
if (!authStorageInstance) authStorageInstance = new AuthStorage();
|
|
24
|
+
return authStorageInstance;
|
|
13
25
|
}
|
|
26
|
+
/** Set a custom AuthStorage instance (useful for tests / TUI integration). */
|
|
14
27
|
function setAuthStorage(storage) {
|
|
15
|
-
|
|
28
|
+
authStorageInstance = storage ?? null;
|
|
16
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Heuristic: did this request come from the agent (e.g. tool result follow-ups) rather
|
|
32
|
+
* than a fresh user turn? Mirrors opencode's `isAgent` logic — Copilot bills these
|
|
33
|
+
* differently via the `x-initiator` header.
|
|
34
|
+
*/
|
|
17
35
|
function detectIsAgent(body) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (last?.role && last.role !== "user") return true;
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
36
|
+
if (!body || typeof body !== "object") return false;
|
|
37
|
+
const obj = body;
|
|
38
|
+
const messages = obj.messages;
|
|
39
|
+
if (Array.isArray(messages) && messages.length > 0) {
|
|
40
|
+
const last = messages[messages.length - 1];
|
|
41
|
+
if (last?.role && last.role !== "user") return true;
|
|
42
|
+
if (Array.isArray(last?.content)) {
|
|
43
|
+
if (last.content.some((part) => part && typeof part === "object" && part.type === "tool_result")) return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const input = obj.input;
|
|
47
|
+
if (Array.isArray(input) && input.length > 0) {
|
|
48
|
+
const last = input[input.length - 1];
|
|
49
|
+
if (last?.role && last.role !== "user") return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
37
52
|
}
|
|
53
|
+
/** Detect image/vision content in a request body. */
|
|
38
54
|
function detectIsVision(body) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
const input = obj.input;
|
|
53
|
-
if (Array.isArray(input)) {
|
|
54
|
-
return input.some(
|
|
55
|
-
(item) => item && typeof item === "object" && Array.isArray(item.content) && item.content.some(matchPart)
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
55
|
+
if (!body || typeof body !== "object") return false;
|
|
56
|
+
const obj = body;
|
|
57
|
+
const matchPart = (part) => {
|
|
58
|
+
if (!part || typeof part !== "object") return false;
|
|
59
|
+
const t = part.type;
|
|
60
|
+
return t === "image" || t === "image_url" || t === "input_image";
|
|
61
|
+
};
|
|
62
|
+
const messages = obj.messages;
|
|
63
|
+
if (Array.isArray(messages)) return messages.some((msg) => msg && typeof msg === "object" && Array.isArray(msg.content) && msg.content.some(matchPart));
|
|
64
|
+
const input = obj.input;
|
|
65
|
+
if (Array.isArray(input)) return input.some((item) => item && typeof item === "object" && Array.isArray(item.content) && item.content.some(matchPart));
|
|
66
|
+
return false;
|
|
59
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Build a fetch wrapper that authenticates with GitHub Copilot OAuth.
|
|
70
|
+
*
|
|
71
|
+
* - Injects the short-lived Copilot bearer token (auto-refreshed by AuthStorage).
|
|
72
|
+
* - Adds the VS Code-like Copilot headers required by the API.
|
|
73
|
+
* - Rewrites the request URL onto the per-token API base when `rewriteUrl` is true.
|
|
74
|
+
*/
|
|
60
75
|
function buildGitHubCopilotOAuthFetch(opts = {}) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
for (const [key, value] of Object.entries(COPILOT_HEADERS)) {
|
|
101
|
-
if (!headers.has(key)) {
|
|
102
|
-
headers.set(key, value);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const finalUrl = opts.rewriteUrl !== false ? rewriteToCopilotBase(url, accessToken, enterpriseUrl) : url instanceof URL ? url : typeof url === "string" ? new URL(url) : new URL(url.url);
|
|
106
|
-
try {
|
|
107
|
-
return await fetch(finalUrl, { ...init, headers });
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (error && typeof error === "object") {
|
|
110
|
-
Object.assign(error, {
|
|
111
|
-
requestUrl: finalUrl.toString()
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
76
|
+
return (async (url, init) => {
|
|
77
|
+
const storage = opts.authStorage ?? getAuthStorage();
|
|
78
|
+
storage.reload();
|
|
79
|
+
const cred = storage.get(COPILOT_PROVIDER_ID);
|
|
80
|
+
if (!cred || cred.type !== "oauth") throw new Error("Not logged in to GitHub Copilot. Run /login first.");
|
|
81
|
+
const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);
|
|
82
|
+
if (!accessToken) throw new Error("Failed to refresh GitHub Copilot token. Please /login again.");
|
|
83
|
+
storage.reload();
|
|
84
|
+
const enterpriseUrl = cred.enterpriseUrl;
|
|
85
|
+
let parsedBody;
|
|
86
|
+
if (typeof init?.body === "string") try {
|
|
87
|
+
parsedBody = JSON.parse(init.body);
|
|
88
|
+
} catch {
|
|
89
|
+
parsedBody = void 0;
|
|
90
|
+
}
|
|
91
|
+
const isAgent = detectIsAgent(parsedBody);
|
|
92
|
+
const isVision = detectIsVision(parsedBody);
|
|
93
|
+
const headers = new Headers();
|
|
94
|
+
if (init?.headers) (init.headers instanceof Headers ? init.headers : Array.isArray(init.headers) ? new Headers(init.headers) : new Headers(init.headers)).forEach((value, key) => {
|
|
95
|
+
const lower = key.toLowerCase();
|
|
96
|
+
if (lower !== "authorization" && lower !== "x-api-key") headers.set(key, value);
|
|
97
|
+
});
|
|
98
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
99
|
+
headers.set("x-initiator", isAgent ? "agent" : "user");
|
|
100
|
+
headers.set("Openai-Intent", "conversation-edits");
|
|
101
|
+
if (isVision) headers.set("Copilot-Vision-Request", "true");
|
|
102
|
+
for (const [key, value] of Object.entries(COPILOT_HEADERS)) if (!headers.has(key)) headers.set(key, value);
|
|
103
|
+
const finalUrl = opts.rewriteUrl !== false ? rewriteToCopilotBase(url, accessToken, enterpriseUrl) : url instanceof URL ? url : typeof url === "string" ? new URL(url) : new URL(url.url);
|
|
104
|
+
try {
|
|
105
|
+
return await fetch(finalUrl, {
|
|
106
|
+
...init,
|
|
107
|
+
headers
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error && typeof error === "object") Object.assign(error, { requestUrl: finalUrl.toString() });
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
117
114
|
}
|
|
118
115
|
function rewriteToCopilotBase(url, token, enterpriseDomain) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
const original = url instanceof URL ? url : new URL(typeof url === "string" ? url : url.url);
|
|
117
|
+
const base = new URL(getGitHubCopilotBaseUrl(token, enterpriseDomain));
|
|
118
|
+
const pathname = original.pathname.replace(/^\/v1(\/|$)/, "/");
|
|
119
|
+
return new URL(`${pathname}${original.search}`, base);
|
|
123
120
|
}
|
|
124
121
|
function isGeminiModel(modelId) {
|
|
125
|
-
|
|
122
|
+
return modelId.startsWith("gemini-");
|
|
126
123
|
}
|
|
127
124
|
function applyGeminiSchemaCompatToTools(modelId, tools) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
schema: functionTool.inputSchema,
|
|
148
|
-
compatLayers: [compatLayer],
|
|
149
|
-
mode: "aiSdkSchema"
|
|
150
|
-
}).jsonSchema
|
|
151
|
-
};
|
|
152
|
-
});
|
|
125
|
+
if (!Array.isArray(tools)) return tools;
|
|
126
|
+
const compatLayer = new GoogleSchemaCompatLayer({
|
|
127
|
+
provider: COPILOT_PROVIDER_ID,
|
|
128
|
+
modelId,
|
|
129
|
+
supportsStructuredOutputs: false
|
|
130
|
+
});
|
|
131
|
+
return tools.map((tool) => {
|
|
132
|
+
if (!tool || typeof tool !== "object" || tool.type !== "function") return tool;
|
|
133
|
+
const functionTool = tool;
|
|
134
|
+
if (!functionTool.inputSchema) return tool;
|
|
135
|
+
return {
|
|
136
|
+
...functionTool,
|
|
137
|
+
inputSchema: applyCompatLayer({
|
|
138
|
+
schema: functionTool.inputSchema,
|
|
139
|
+
compatLayers: [compatLayer],
|
|
140
|
+
mode: "aiSdkSchema"
|
|
141
|
+
}).jsonSchema
|
|
142
|
+
};
|
|
143
|
+
});
|
|
153
144
|
}
|
|
145
|
+
/** Middleware that prevents sending parameters Copilot's endpoint rejects. */
|
|
154
146
|
function createCopilotMiddleware(modelId) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
modelId,
|
|
164
|
-
params.tools
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
return params;
|
|
168
|
-
}
|
|
169
|
-
};
|
|
147
|
+
return {
|
|
148
|
+
specificationVersion: "v3",
|
|
149
|
+
transformParams: async ({ params }) => {
|
|
150
|
+
if (params.temperature !== void 0 && params.temperature !== null) delete params.topP;
|
|
151
|
+
if (isGeminiModel(modelId)) params.tools = applyGeminiSchemaCompatToTools(modelId, params.tools);
|
|
152
|
+
return params;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
170
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Creates a model that talks to GitHub Copilot using OAuth credentials.
|
|
158
|
+
*
|
|
159
|
+
* Copilot's `/chat/completions` endpoint is OpenAI-compatible, but GitHub Copilot
|
|
160
|
+
* is not OpenAI. Use the generic OpenAI-compatible adapter with Copilot's base URL
|
|
161
|
+
* instead of the OpenAI provider plus URL rewriting.
|
|
162
|
+
*/
|
|
171
163
|
function githubCopilotProvider(modelId = "gpt-4.1", options) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
164
|
+
const headers = options?.headers;
|
|
165
|
+
return wrapLanguageModel({
|
|
166
|
+
model: createOpenAICompatible({
|
|
167
|
+
name: COPILOT_PROVIDER_ID,
|
|
168
|
+
baseURL: "https://api.githubcopilot.com",
|
|
169
|
+
apiKey: process.env.NODE_ENV === "test" || process.env.VITEST ? "test-api-key" : "oauth-placeholder",
|
|
170
|
+
headers,
|
|
171
|
+
fetch: process.env.NODE_ENV === "test" || process.env.VITEST ? void 0 : buildGitHubCopilotOAuthFetch({
|
|
172
|
+
rewriteUrl: false,
|
|
173
|
+
authStorage: options?.authStorage
|
|
174
|
+
})
|
|
175
|
+
}).chatModel(modelId),
|
|
176
|
+
middleware: [createCopilotMiddleware(modelId)]
|
|
177
|
+
});
|
|
184
178
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Hard-coded fallback advertised when the live `/models` request fails (network
|
|
181
|
+
* down, expired token, etc.). Keep this conservative because the live catalog is
|
|
182
|
+
* the source of truth for the user's currently-enabled Copilot models.
|
|
183
|
+
*
|
|
184
|
+
* Available across all paid Copilot tiers and free of premium-request charges.
|
|
185
|
+
*/
|
|
186
|
+
const COPILOT_FALLBACK_MODELS = [{
|
|
187
|
+
id: "gpt-4.1",
|
|
188
|
+
name: "GPT-4.1",
|
|
189
|
+
vendor: "OpenAI",
|
|
190
|
+
supportedEndpoints: ["/chat/completions"],
|
|
191
|
+
isAnthropicShaped: false,
|
|
192
|
+
supportsVision: true,
|
|
193
|
+
supportsToolCalls: true
|
|
194
|
+
}];
|
|
195
|
+
const CATALOG_TTL_MS = 600 * 1e3;
|
|
197
196
|
const CATALOG_FAILURE_TTL_MS = 60 * 1e3;
|
|
198
197
|
const CATALOG_FETCH_TIMEOUT_MS = 5e3;
|
|
199
198
|
let catalogCache = null;
|
|
200
199
|
let inflightFetch = null;
|
|
200
|
+
/** Reset the in-process Copilot catalog cache (test seam, also useful after logout). */
|
|
201
201
|
function clearCopilotCatalogCache() {
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
catalogCache = null;
|
|
203
|
+
inflightFetch = null;
|
|
204
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Return the user's currently-available Copilot models.
|
|
207
|
+
*
|
|
208
|
+
* - Returns `[]` when the user is not logged in to GitHub Copilot.
|
|
209
|
+
* - Returns the cached list when a recent fetch succeeded.
|
|
210
|
+
* - On the cache-miss / expired path, fetches `/models` with a 5s timeout, filters
|
|
211
|
+
* to picker-enabled and non-policy-disabled models, then caches for 10 minutes.
|
|
212
|
+
* - On fetch failure, returns a small hard-coded fallback (so packs still work
|
|
213
|
+
* offline) and caches that for 1 minute to avoid hammering the API.
|
|
214
|
+
*
|
|
215
|
+
* Concurrent calls during a fetch share the inflight promise.
|
|
216
|
+
*/
|
|
205
217
|
async function getCopilotModelCatalog(opts = {}) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
} finally {
|
|
250
|
-
inflightFetch = null;
|
|
251
|
-
}
|
|
252
|
-
})();
|
|
253
|
-
return inflightFetch;
|
|
218
|
+
const storage = opts.authStorage ?? getAuthStorage();
|
|
219
|
+
storage.reload();
|
|
220
|
+
const cred = storage.get(COPILOT_PROVIDER_ID);
|
|
221
|
+
if (!cred || cred.type !== "oauth") return [];
|
|
222
|
+
if (catalogCache && Date.now() - catalogCache.fetchedAt < catalogCache.ttl) return catalogCache.models;
|
|
223
|
+
if (inflightFetch) return inflightFetch;
|
|
224
|
+
inflightFetch = (async () => {
|
|
225
|
+
try {
|
|
226
|
+
const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);
|
|
227
|
+
if (!accessToken) throw new Error("No Copilot bearer token");
|
|
228
|
+
storage.reload();
|
|
229
|
+
const enterpriseUrl = storage.get(COPILOT_PROVIDER_ID)?.enterpriseUrl;
|
|
230
|
+
const baseUrl = getGitHubCopilotBaseUrl(accessToken, enterpriseUrl);
|
|
231
|
+
const controller = new AbortController();
|
|
232
|
+
const timer = setTimeout(() => controller.abort(), CATALOG_FETCH_TIMEOUT_MS);
|
|
233
|
+
try {
|
|
234
|
+
const models = await fetchCopilotModels({
|
|
235
|
+
baseUrl,
|
|
236
|
+
bearerToken: accessToken,
|
|
237
|
+
signal: controller.signal
|
|
238
|
+
});
|
|
239
|
+
catalogCache = {
|
|
240
|
+
fetchedAt: Date.now(),
|
|
241
|
+
ttl: CATALOG_TTL_MS,
|
|
242
|
+
models
|
|
243
|
+
};
|
|
244
|
+
return models;
|
|
245
|
+
} finally {
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
catalogCache = {
|
|
250
|
+
fetchedAt: Date.now(),
|
|
251
|
+
ttl: CATALOG_FAILURE_TTL_MS,
|
|
252
|
+
models: COPILOT_FALLBACK_MODELS
|
|
253
|
+
};
|
|
254
|
+
console.warn("Failed to fetch live GitHub Copilot models, using fallback list:", error instanceof Error ? error.message : error);
|
|
255
|
+
return COPILOT_FALLBACK_MODELS;
|
|
256
|
+
} finally {
|
|
257
|
+
inflightFetch = null;
|
|
258
|
+
}
|
|
259
|
+
})();
|
|
260
|
+
return inflightFetch;
|
|
254
261
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
getAuthStorage,
|
|
259
|
-
getCopilotModelCatalog,
|
|
260
|
-
githubCopilotProvider,
|
|
261
|
-
setAuthStorage
|
|
262
|
-
};
|
|
262
|
+
//#endregion
|
|
263
|
+
export { buildGitHubCopilotOAuthFetch, clearCopilotCatalogCache, getAuthStorage, getCopilotModelCatalog, githubCopilotProvider, setAuthStorage };
|
|
264
|
+
|
|
263
265
|
//# sourceMappingURL=github-copilot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with GitHub Copilot's chat API.\n * The Copilot API speaks an OpenAI-compatible chat format, so we plug\n * `@ai-sdk/openai-compatible` into Copilot's API URL and use a custom fetch to inject\n * the bearer token and Copilot-specific headers.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n */\n\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport type { JSONSchema7 } from '@mastra/schema-compat';\nimport { applyCompatLayer, GoogleSchemaCompatLayer } from '@mastra/schema-compat';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { COPILOT_HEADERS, fetchCopilotModels, getGitHubCopilotBaseUrl } from '../auth/providers/github-copilot.js';\nimport type { CopilotModelEntry, GitHubCopilotCredentials } from '../auth/providers/github-copilot.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\nconst COPILOT_PROVIDER_ID = 'github-copilot';\n\n// Singleton auth storage instance (shared with claude-max.ts / openai-codex.ts when not overridden).\nlet authStorageInstance: AuthStorage | null = null;\n\n/** Get or create the shared AuthStorage instance. */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/** Set a custom AuthStorage instance (useful for tests / TUI integration). */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Heuristic: did this request come from the agent (e.g. tool result follow-ups) rather\n * than a fresh user turn? Mirrors opencode's `isAgent` logic — Copilot bills these\n * differently via the `x-initiator` header.\n */\nfunction detectIsAgent(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const messages = obj.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n const last = messages[messages.length - 1] as { role?: string; content?: unknown };\n if (last?.role && last.role !== 'user') return true;\n if (Array.isArray(last?.content)) {\n // If the last user turn carries any tool_result parts, treat it as an agent turn.\n const hasToolResult = last.content.some(\n (part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'tool_result',\n );\n if (hasToolResult) return true;\n }\n }\n\n const input = obj.input;\n if (Array.isArray(input) && input.length > 0) {\n const last = input[input.length - 1] as { role?: string };\n if (last?.role && last.role !== 'user') return true;\n }\n\n return false;\n}\n\n/** Detect image/vision content in a request body. */\nfunction detectIsVision(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const matchPart = (part: unknown): boolean => {\n if (!part || typeof part !== 'object') return false;\n const t = (part as { type?: string }).type;\n return t === 'image' || t === 'image_url' || t === 'input_image';\n };\n\n const messages = obj.messages;\n if (Array.isArray(messages)) {\n return messages.some(\n (msg: unknown) =>\n msg &&\n typeof msg === 'object' &&\n Array.isArray((msg as { content?: unknown }).content) &&\n ((msg as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n const input = obj.input;\n if (Array.isArray(input)) {\n return input.some(\n (item: unknown) =>\n item &&\n typeof item === 'object' &&\n Array.isArray((item as { content?: unknown }).content) &&\n ((item as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n return false;\n}\n\n/**\n * Build a fetch wrapper that authenticates with GitHub Copilot OAuth.\n *\n * - Injects the short-lived Copilot bearer token (auto-refreshed by AuthStorage).\n * - Adds the VS Code-like Copilot headers required by the API.\n * - Rewrites the request URL onto the per-token API base when `rewriteUrl` is true.\n */\nexport function buildGitHubCopilotOAuthFetch(\n opts: { authStorage?: CredentialStore; rewriteUrl?: boolean } = {},\n): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n throw new Error('Not logged in to GitHub Copilot. Run /login first.');\n }\n\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) {\n throw new Error('Failed to refresh GitHub Copilot token. Please /login again.');\n }\n storage.reload();\n\n const enterpriseUrl = (cred as GitHubCopilotCredentials).enterpriseUrl;\n\n let parsedBody: unknown;\n if (typeof init?.body === 'string') {\n try {\n parsedBody = JSON.parse(init.body);\n } catch {\n parsedBody = undefined;\n }\n }\n const isAgent = detectIsAgent(parsedBody);\n const isVision = detectIsVision(parsedBody);\n\n // Preserve non-auth headers from caller.\n const headers = new Headers();\n if (init?.headers) {\n const source =\n init.headers instanceof Headers\n ? init.headers\n : Array.isArray(init.headers)\n ? new Headers(init.headers as Array<[string, string]>)\n : new Headers(init.headers as Record<string, string>);\n source.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower !== 'authorization' && lower !== 'x-api-key') {\n headers.set(key, value);\n }\n });\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n headers.set('x-initiator', isAgent ? 'agent' : 'user');\n headers.set('Openai-Intent', 'conversation-edits');\n if (isVision) {\n headers.set('Copilot-Vision-Request', 'true');\n }\n for (const [key, value] of Object.entries(COPILOT_HEADERS)) {\n // Only set if caller didn't already provide it (allow overrides for tests).\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n }\n\n const finalUrl =\n opts.rewriteUrl !== false\n ? rewriteToCopilotBase(url, accessToken, enterpriseUrl)\n : url instanceof URL\n ? url\n : typeof url === 'string'\n ? new URL(url)\n : new URL((url as Request).url);\n\n try {\n return await fetch(finalUrl, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: finalUrl.toString(),\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\nfunction rewriteToCopilotBase(url: string | URL | Request, token: string, enterpriseDomain?: string): URL {\n const original = url instanceof URL ? url : new URL(typeof url === 'string' ? url : (url as Request).url);\n const base = new URL(getGitHubCopilotBaseUrl(token, enterpriseDomain));\n // Copilot's OpenAI-compatible API serves endpoints at the root of the base host\n // (`/chat/completions`, `/responses`, `/models`, ...) — not under a `/v1/` prefix\n // like api.openai.com does. The @ai-sdk/openai default baseURL is\n // `https://api.openai.com/v1`, so the SDK builds requests like\n // `https://api.openai.com/v1/chat/completions`. Strip the leading `/v1` segment\n // when rewriting onto the Copilot base or Copilot will return 404 Not Found.\n const pathname = original.pathname.replace(/^\\/v1(\\/|$)/, '/');\n return new URL(`${pathname}${original.search}`, base);\n}\n\nfunction isGeminiModel(modelId: string): boolean {\n return modelId.startsWith('gemini-');\n}\n\nfunction applyGeminiSchemaCompatToTools(modelId: string, tools: unknown): unknown {\n if (!Array.isArray(tools)) {\n return tools;\n }\n\n const compatLayer = new GoogleSchemaCompatLayer({\n provider: COPILOT_PROVIDER_ID,\n modelId,\n supportsStructuredOutputs: false,\n });\n\n return tools.map(tool => {\n if (!tool || typeof tool !== 'object' || (tool as { type?: unknown }).type !== 'function') {\n return tool;\n }\n\n const functionTool = tool as { inputSchema?: JSONSchema7 };\n if (!functionTool.inputSchema) {\n return tool;\n }\n\n return {\n ...functionTool,\n inputSchema: applyCompatLayer({\n schema: functionTool.inputSchema,\n compatLayers: [compatLayer],\n mode: 'aiSdkSchema',\n }).jsonSchema as JSONSchema7,\n };\n });\n}\n\n/** Middleware that prevents sending parameters Copilot's endpoint rejects. */\nfunction createCopilotMiddleware(modelId: string): LanguageModelMiddleware {\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n if (params.temperature !== undefined && params.temperature !== null) {\n delete params.topP;\n }\n\n if (isGeminiModel(modelId)) {\n (params as { tools?: unknown }).tools = applyGeminiSchemaCompatToTools(\n modelId,\n (params as { tools?: unknown }).tools,\n );\n }\n\n return params;\n },\n };\n}\n\n/**\n * Creates a model that talks to GitHub Copilot using OAuth credentials.\n *\n * Copilot's `/chat/completions` endpoint is OpenAI-compatible, but GitHub Copilot\n * is not OpenAI. Use the generic OpenAI-compatible adapter with Copilot's base URL\n * instead of the OpenAI provider plus URL rewriting.\n */\nexport function githubCopilotProvider(\n modelId: string = 'gpt-4.1',\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore },\n): MastraModelConfig {\n const headers = options?.headers;\n const copilot = createOpenAICompatible({\n name: COPILOT_PROVIDER_ID,\n baseURL: 'https://api.githubcopilot.com',\n apiKey: process.env.NODE_ENV === 'test' || process.env.VITEST ? 'test-api-key' : 'oauth-placeholder',\n headers,\n fetch:\n process.env.NODE_ENV === 'test' || process.env.VITEST\n ? undefined\n : (buildGitHubCopilotOAuthFetch({ rewriteUrl: false, authStorage: options?.authStorage }) as any),\n });\n\n return wrapLanguageModel({\n model: copilot.chatModel(modelId),\n middleware: [createCopilotMiddleware(modelId)],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Live model catalog\n// ---------------------------------------------------------------------------\n\n/**\n * Hard-coded fallback advertised when the live `/models` request fails (network\n * down, expired token, etc.). Keep this conservative because the live catalog is\n * the source of truth for the user's currently-enabled Copilot models.\n *\n * Available across all paid Copilot tiers and free of premium-request charges.\n */\nconst COPILOT_FALLBACK_MODELS: CopilotModelEntry[] = [\n {\n id: 'gpt-4.1',\n name: 'GPT-4.1',\n vendor: 'OpenAI',\n supportedEndpoints: ['/chat/completions'],\n isAnthropicShaped: false,\n supportsVision: true,\n supportsToolCalls: true,\n },\n];\n\nconst CATALOG_TTL_MS = 10 * 60 * 1000;\nconst CATALOG_FAILURE_TTL_MS = 60 * 1000;\nconst CATALOG_FETCH_TIMEOUT_MS = 5_000;\n\ninterface CatalogCacheEntry {\n fetchedAt: number;\n ttl: number;\n models: CopilotModelEntry[];\n}\n\nlet catalogCache: CatalogCacheEntry | null = null;\nlet inflightFetch: Promise<CopilotModelEntry[]> | null = null;\n\n/** Reset the in-process Copilot catalog cache (test seam, also useful after logout). */\nexport function clearCopilotCatalogCache(): void {\n catalogCache = null;\n inflightFetch = null;\n}\n\n/**\n * Return the user's currently-available Copilot models.\n *\n * - Returns `[]` when the user is not logged in to GitHub Copilot.\n * - Returns the cached list when a recent fetch succeeded.\n * - On the cache-miss / expired path, fetches `/models` with a 5s timeout, filters\n * to picker-enabled and non-policy-disabled models, then caches for 10 minutes.\n * - On fetch failure, returns a small hard-coded fallback (so packs still work\n * offline) and caches that for 1 minute to avoid hammering the API.\n *\n * Concurrent calls during a fetch share the inflight promise.\n */\nexport async function getCopilotModelCatalog(opts: { authStorage?: AuthStorage } = {}): Promise<CopilotModelEntry[]> {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n return [];\n }\n\n const now = Date.now();\n if (catalogCache && now - catalogCache.fetchedAt < catalogCache.ttl) {\n return catalogCache.models;\n }\n\n if (inflightFetch) return inflightFetch;\n\n inflightFetch = (async (): Promise<CopilotModelEntry[]> => {\n try {\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) throw new Error('No Copilot bearer token');\n storage.reload();\n\n const refreshed = storage.get(COPILOT_PROVIDER_ID);\n const enterpriseUrl = (refreshed as GitHubCopilotCredentials | undefined)?.enterpriseUrl;\n const baseUrl = getGitHubCopilotBaseUrl(accessToken, enterpriseUrl);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), CATALOG_FETCH_TIMEOUT_MS);\n try {\n const models = await fetchCopilotModels({\n baseUrl,\n bearerToken: accessToken,\n signal: controller.signal,\n });\n catalogCache = { fetchedAt: Date.now(), ttl: CATALOG_TTL_MS, models };\n return models;\n } finally {\n clearTimeout(timer);\n }\n } catch (error) {\n catalogCache = {\n fetchedAt: Date.now(),\n ttl: CATALOG_FAILURE_TTL_MS,\n models: COPILOT_FALLBACK_MODELS,\n };\n console.warn(\n 'Failed to fetch live GitHub Copilot models, using fallback list:',\n error instanceof Error ? error.message : error,\n );\n return COPILOT_FALLBACK_MODELS;\n } finally {\n inflightFetch = null;\n }\n })();\n\n return inflightFetch;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"file":"github-copilot.js","names":[],"sources":["../../src/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with GitHub Copilot's chat API.\n * The Copilot API speaks an OpenAI-compatible chat format, so we plug\n * `@ai-sdk/openai-compatible` into Copilot's API URL and use a custom fetch to inject\n * the bearer token and Copilot-specific headers.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n */\n\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport type { JSONSchema7 } from '@mastra/schema-compat';\nimport { applyCompatLayer, GoogleSchemaCompatLayer } from '@mastra/schema-compat';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { COPILOT_HEADERS, fetchCopilotModels, getGitHubCopilotBaseUrl } from '../auth/providers/github-copilot.js';\nimport type { CopilotModelEntry, GitHubCopilotCredentials } from '../auth/providers/github-copilot.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\nconst COPILOT_PROVIDER_ID = 'github-copilot';\n\n// Singleton auth storage instance (shared with claude-max.ts / openai-codex.ts when not overridden).\nlet authStorageInstance: AuthStorage | null = null;\n\n/** Get or create the shared AuthStorage instance. */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/** Set a custom AuthStorage instance (useful for tests / TUI integration). */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Heuristic: did this request come from the agent (e.g. tool result follow-ups) rather\n * than a fresh user turn? Mirrors opencode's `isAgent` logic — Copilot bills these\n * differently via the `x-initiator` header.\n */\nfunction detectIsAgent(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const messages = obj.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n const last = messages[messages.length - 1] as { role?: string; content?: unknown };\n if (last?.role && last.role !== 'user') return true;\n if (Array.isArray(last?.content)) {\n // If the last user turn carries any tool_result parts, treat it as an agent turn.\n const hasToolResult = last.content.some(\n (part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'tool_result',\n );\n if (hasToolResult) return true;\n }\n }\n\n const input = obj.input;\n if (Array.isArray(input) && input.length > 0) {\n const last = input[input.length - 1] as { role?: string };\n if (last?.role && last.role !== 'user') return true;\n }\n\n return false;\n}\n\n/** Detect image/vision content in a request body. */\nfunction detectIsVision(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const matchPart = (part: unknown): boolean => {\n if (!part || typeof part !== 'object') return false;\n const t = (part as { type?: string }).type;\n return t === 'image' || t === 'image_url' || t === 'input_image';\n };\n\n const messages = obj.messages;\n if (Array.isArray(messages)) {\n return messages.some(\n (msg: unknown) =>\n msg &&\n typeof msg === 'object' &&\n Array.isArray((msg as { content?: unknown }).content) &&\n ((msg as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n const input = obj.input;\n if (Array.isArray(input)) {\n return input.some(\n (item: unknown) =>\n item &&\n typeof item === 'object' &&\n Array.isArray((item as { content?: unknown }).content) &&\n ((item as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n return false;\n}\n\n/**\n * Build a fetch wrapper that authenticates with GitHub Copilot OAuth.\n *\n * - Injects the short-lived Copilot bearer token (auto-refreshed by AuthStorage).\n * - Adds the VS Code-like Copilot headers required by the API.\n * - Rewrites the request URL onto the per-token API base when `rewriteUrl` is true.\n */\nexport function buildGitHubCopilotOAuthFetch(\n opts: { authStorage?: CredentialStore; rewriteUrl?: boolean } = {},\n): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n throw new Error('Not logged in to GitHub Copilot. Run /login first.');\n }\n\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) {\n throw new Error('Failed to refresh GitHub Copilot token. Please /login again.');\n }\n storage.reload();\n\n const enterpriseUrl = (cred as GitHubCopilotCredentials).enterpriseUrl;\n\n let parsedBody: unknown;\n if (typeof init?.body === 'string') {\n try {\n parsedBody = JSON.parse(init.body);\n } catch {\n parsedBody = undefined;\n }\n }\n const isAgent = detectIsAgent(parsedBody);\n const isVision = detectIsVision(parsedBody);\n\n // Preserve non-auth headers from caller.\n const headers = new Headers();\n if (init?.headers) {\n const source =\n init.headers instanceof Headers\n ? init.headers\n : Array.isArray(init.headers)\n ? new Headers(init.headers as Array<[string, string]>)\n : new Headers(init.headers as Record<string, string>);\n source.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower !== 'authorization' && lower !== 'x-api-key') {\n headers.set(key, value);\n }\n });\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n headers.set('x-initiator', isAgent ? 'agent' : 'user');\n headers.set('Openai-Intent', 'conversation-edits');\n if (isVision) {\n headers.set('Copilot-Vision-Request', 'true');\n }\n for (const [key, value] of Object.entries(COPILOT_HEADERS)) {\n // Only set if caller didn't already provide it (allow overrides for tests).\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n }\n\n const finalUrl =\n opts.rewriteUrl !== false\n ? rewriteToCopilotBase(url, accessToken, enterpriseUrl)\n : url instanceof URL\n ? url\n : typeof url === 'string'\n ? new URL(url)\n : new URL((url as Request).url);\n\n try {\n return await fetch(finalUrl, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: finalUrl.toString(),\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\nfunction rewriteToCopilotBase(url: string | URL | Request, token: string, enterpriseDomain?: string): URL {\n const original = url instanceof URL ? url : new URL(typeof url === 'string' ? url : (url as Request).url);\n const base = new URL(getGitHubCopilotBaseUrl(token, enterpriseDomain));\n // Copilot's OpenAI-compatible API serves endpoints at the root of the base host\n // (`/chat/completions`, `/responses`, `/models`, ...) — not under a `/v1/` prefix\n // like api.openai.com does. The @ai-sdk/openai default baseURL is\n // `https://api.openai.com/v1`, so the SDK builds requests like\n // `https://api.openai.com/v1/chat/completions`. Strip the leading `/v1` segment\n // when rewriting onto the Copilot base or Copilot will return 404 Not Found.\n const pathname = original.pathname.replace(/^\\/v1(\\/|$)/, '/');\n return new URL(`${pathname}${original.search}`, base);\n}\n\nfunction isGeminiModel(modelId: string): boolean {\n return modelId.startsWith('gemini-');\n}\n\nfunction applyGeminiSchemaCompatToTools(modelId: string, tools: unknown): unknown {\n if (!Array.isArray(tools)) {\n return tools;\n }\n\n const compatLayer = new GoogleSchemaCompatLayer({\n provider: COPILOT_PROVIDER_ID,\n modelId,\n supportsStructuredOutputs: false,\n });\n\n return tools.map(tool => {\n if (!tool || typeof tool !== 'object' || (tool as { type?: unknown }).type !== 'function') {\n return tool;\n }\n\n const functionTool = tool as { inputSchema?: JSONSchema7 };\n if (!functionTool.inputSchema) {\n return tool;\n }\n\n return {\n ...functionTool,\n inputSchema: applyCompatLayer({\n schema: functionTool.inputSchema,\n compatLayers: [compatLayer],\n mode: 'aiSdkSchema',\n }).jsonSchema as JSONSchema7,\n };\n });\n}\n\n/** Middleware that prevents sending parameters Copilot's endpoint rejects. */\nfunction createCopilotMiddleware(modelId: string): LanguageModelMiddleware {\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n if (params.temperature !== undefined && params.temperature !== null) {\n delete params.topP;\n }\n\n if (isGeminiModel(modelId)) {\n (params as { tools?: unknown }).tools = applyGeminiSchemaCompatToTools(\n modelId,\n (params as { tools?: unknown }).tools,\n );\n }\n\n return params;\n },\n };\n}\n\n/**\n * Creates a model that talks to GitHub Copilot using OAuth credentials.\n *\n * Copilot's `/chat/completions` endpoint is OpenAI-compatible, but GitHub Copilot\n * is not OpenAI. Use the generic OpenAI-compatible adapter with Copilot's base URL\n * instead of the OpenAI provider plus URL rewriting.\n */\nexport function githubCopilotProvider(\n modelId: string = 'gpt-4.1',\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore },\n): MastraModelConfig {\n const headers = options?.headers;\n const copilot = createOpenAICompatible({\n name: COPILOT_PROVIDER_ID,\n baseURL: 'https://api.githubcopilot.com',\n apiKey: process.env.NODE_ENV === 'test' || process.env.VITEST ? 'test-api-key' : 'oauth-placeholder',\n headers,\n fetch:\n process.env.NODE_ENV === 'test' || process.env.VITEST\n ? undefined\n : (buildGitHubCopilotOAuthFetch({ rewriteUrl: false, authStorage: options?.authStorage }) as any),\n });\n\n return wrapLanguageModel({\n model: copilot.chatModel(modelId),\n middleware: [createCopilotMiddleware(modelId)],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Live model catalog\n// ---------------------------------------------------------------------------\n\n/**\n * Hard-coded fallback advertised when the live `/models` request fails (network\n * down, expired token, etc.). Keep this conservative because the live catalog is\n * the source of truth for the user's currently-enabled Copilot models.\n *\n * Available across all paid Copilot tiers and free of premium-request charges.\n */\nconst COPILOT_FALLBACK_MODELS: CopilotModelEntry[] = [\n {\n id: 'gpt-4.1',\n name: 'GPT-4.1',\n vendor: 'OpenAI',\n supportedEndpoints: ['/chat/completions'],\n isAnthropicShaped: false,\n supportsVision: true,\n supportsToolCalls: true,\n },\n];\n\nconst CATALOG_TTL_MS = 10 * 60 * 1000;\nconst CATALOG_FAILURE_TTL_MS = 60 * 1000;\nconst CATALOG_FETCH_TIMEOUT_MS = 5_000;\n\ninterface CatalogCacheEntry {\n fetchedAt: number;\n ttl: number;\n models: CopilotModelEntry[];\n}\n\nlet catalogCache: CatalogCacheEntry | null = null;\nlet inflightFetch: Promise<CopilotModelEntry[]> | null = null;\n\n/** Reset the in-process Copilot catalog cache (test seam, also useful after logout). */\nexport function clearCopilotCatalogCache(): void {\n catalogCache = null;\n inflightFetch = null;\n}\n\n/**\n * Return the user's currently-available Copilot models.\n *\n * - Returns `[]` when the user is not logged in to GitHub Copilot.\n * - Returns the cached list when a recent fetch succeeded.\n * - On the cache-miss / expired path, fetches `/models` with a 5s timeout, filters\n * to picker-enabled and non-policy-disabled models, then caches for 10 minutes.\n * - On fetch failure, returns a small hard-coded fallback (so packs still work\n * offline) and caches that for 1 minute to avoid hammering the API.\n *\n * Concurrent calls during a fetch share the inflight promise.\n */\nexport async function getCopilotModelCatalog(opts: { authStorage?: AuthStorage } = {}): Promise<CopilotModelEntry[]> {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n return [];\n }\n\n const now = Date.now();\n if (catalogCache && now - catalogCache.fetchedAt < catalogCache.ttl) {\n return catalogCache.models;\n }\n\n if (inflightFetch) return inflightFetch;\n\n inflightFetch = (async (): Promise<CopilotModelEntry[]> => {\n try {\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) throw new Error('No Copilot bearer token');\n storage.reload();\n\n const refreshed = storage.get(COPILOT_PROVIDER_ID);\n const enterpriseUrl = (refreshed as GitHubCopilotCredentials | undefined)?.enterpriseUrl;\n const baseUrl = getGitHubCopilotBaseUrl(accessToken, enterpriseUrl);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), CATALOG_FETCH_TIMEOUT_MS);\n try {\n const models = await fetchCopilotModels({\n baseUrl,\n bearerToken: accessToken,\n signal: controller.signal,\n });\n catalogCache = { fetchedAt: Date.now(), ttl: CATALOG_TTL_MS, models };\n return models;\n } finally {\n clearTimeout(timer);\n }\n } catch (error) {\n catalogCache = {\n fetchedAt: Date.now(),\n ttl: CATALOG_FAILURE_TTL_MS,\n models: COPILOT_FALLBACK_MODELS,\n };\n console.warn(\n 'Failed to fetch live GitHub Copilot models, using fallback list:',\n error instanceof Error ? error.message : error,\n );\n return COPILOT_FALLBACK_MODELS;\n } finally {\n inflightFetch = null;\n }\n })();\n\n return inflightFetch;\n}\n"],"mappings":"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"}
|