pi-xai-oauth 1.0.2 → 1.0.7
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/extensions/xai-oauth.ts +219 -12
- package/package.json +1 -1
package/extensions/xai-oauth.ts
CHANGED
|
@@ -1,34 +1,114 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
|
|
3
|
+
import { readFileSync, existsSync } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
function getGrokAuthToken(): string | null {
|
|
8
|
+
const authPath = join(homedir(), ".grok", "auth.json");
|
|
9
|
+
if (existsSync(authPath)) {
|
|
10
|
+
try {
|
|
11
|
+
const data = JSON.parse(readFileSync(authPath, "utf8"));
|
|
12
|
+
return data.access_token || data.token || null;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
3
19
|
|
|
4
20
|
export default function (pi: ExtensionAPI) {
|
|
5
21
|
pi.registerProvider("xai-oauth", {
|
|
6
22
|
name: "xAI (OAuth)",
|
|
7
23
|
baseUrl: "https://api.x.ai/v1",
|
|
8
|
-
api: "openai-
|
|
24
|
+
api: "openai-responses",
|
|
9
25
|
authHeader: true,
|
|
10
26
|
|
|
11
27
|
oauth: {
|
|
12
28
|
name: "xAI (Grok)",
|
|
13
29
|
|
|
14
30
|
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
// Check for existing Grok auth file first
|
|
32
|
+
const existingToken = getGrokAuthToken();
|
|
33
|
+
if (existingToken) {
|
|
34
|
+
const useExisting = await callbacks.onPrompt({
|
|
35
|
+
message: "Found existing Grok auth. Use it? (y/n)"
|
|
36
|
+
});
|
|
37
|
+
if (useExisting.toLowerCase().startsWith("y")) {
|
|
38
|
+
return {
|
|
39
|
+
refresh: "",
|
|
40
|
+
access: existingToken,
|
|
41
|
+
expires: Date.now() + 1000 * 60 * 60 * 24 * 30,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Start device code flow
|
|
47
|
+
const deviceResponse = await fetch("https://api.x.ai/oauth/device/code", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { "Content-Type": "application/json" },
|
|
50
|
+
body: JSON.stringify({ client_id: "pi-xai-oauth" }),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!deviceResponse.ok) {
|
|
54
|
+
// Fallback to manual key entry
|
|
55
|
+
const accessToken = await callbacks.onPrompt({
|
|
56
|
+
message: "Device flow unavailable. Paste your xAI API key:",
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
refresh: "",
|
|
60
|
+
access: accessToken.trim(),
|
|
61
|
+
expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const deviceData = await deviceResponse.json();
|
|
66
|
+
|
|
67
|
+
callbacks.onDeviceCode({
|
|
68
|
+
userCode: deviceData.user_code,
|
|
69
|
+
verificationUri: deviceData.verification_uri,
|
|
19
70
|
});
|
|
20
71
|
|
|
72
|
+
// Poll for token
|
|
73
|
+
const tokenResponse = await fetch("https://api.x.ai/oauth/token", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
78
|
+
device_code: deviceData.device_code,
|
|
79
|
+
client_id: "pi-xai-oauth",
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const tokenData = await tokenResponse.json();
|
|
84
|
+
|
|
21
85
|
return {
|
|
22
|
-
refresh: "",
|
|
23
|
-
access:
|
|
24
|
-
expires: Date.now() +
|
|
86
|
+
refresh: tokenData.refresh_token || "",
|
|
87
|
+
access: tokenData.access_token,
|
|
88
|
+
expires: Date.now() + (tokenData.expires_in || 3600) * 1000,
|
|
25
89
|
};
|
|
26
90
|
},
|
|
27
91
|
|
|
28
92
|
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
93
|
+
if (!credentials.refresh) return credentials;
|
|
94
|
+
|
|
95
|
+
const response = await fetch("https://api.x.ai/oauth/token", {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "Content-Type": "application/json" },
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
grant_type: "refresh_token",
|
|
100
|
+
refresh_token: credentials.refresh,
|
|
101
|
+
client_id: "pi-xai-oauth",
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
refresh: data.refresh_token || credentials.refresh,
|
|
109
|
+
access: data.access_token,
|
|
110
|
+
expires: Date.now() + (data.expires_in || 3600) * 1000,
|
|
111
|
+
};
|
|
32
112
|
},
|
|
33
113
|
|
|
34
114
|
getApiKey(credentials: OAuthCredentials): string {
|
|
@@ -36,6 +116,133 @@ export default function (pi: ExtensionAPI) {
|
|
|
36
116
|
},
|
|
37
117
|
},
|
|
38
118
|
|
|
119
|
+
// Custom tool for advanced Grok usage
|
|
120
|
+
tools: [
|
|
121
|
+
{
|
|
122
|
+
name: "xai_generate_text",
|
|
123
|
+
description: "Generate text using Grok with full reasoning, structured output, and stateful conversations.",
|
|
124
|
+
parameters: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
prompt: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "The prompt or question to send to Grok",
|
|
130
|
+
},
|
|
131
|
+
model: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "Model to use (e.g. grok-4, grok-4.3)",
|
|
134
|
+
default: "grok-4",
|
|
135
|
+
},
|
|
136
|
+
reasoning_effort: {
|
|
137
|
+
type: "string",
|
|
138
|
+
enum: ["low", "medium", "high"],
|
|
139
|
+
description: "Reasoning effort level",
|
|
140
|
+
default: "medium",
|
|
141
|
+
},
|
|
142
|
+
response_format: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "Set to 'json' for structured JSON output",
|
|
145
|
+
},
|
|
146
|
+
previous_response_id: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Continue from a previous response ID for stateful conversations",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
required: ["prompt"],
|
|
152
|
+
},
|
|
153
|
+
handler: async (args: any, context: any) => {
|
|
154
|
+
const apiKey = context?.apiKey || process.env.XAI_API_KEY;
|
|
155
|
+
|
|
156
|
+
if (!apiKey) {
|
|
157
|
+
return { error: "No xAI API key available" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const body: any = {
|
|
161
|
+
model: args.model || "grok-4",
|
|
162
|
+
input: args.prompt,
|
|
163
|
+
reasoning: { effort: args.reasoning_effort || "medium" },
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (args.response_format === "json") {
|
|
167
|
+
body.response_format = { type: "json_object" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (args.previous_response_id) {
|
|
171
|
+
body.previous_response_id = args.previous_response_id;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const response = await fetch("https://api.x.ai/v1/responses", {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: {
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
Authorization: `Bearer ${apiKey}`,
|
|
179
|
+
},
|
|
180
|
+
body: JSON.stringify(body),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const data = await response.json();
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
content: data.output?.[0]?.content?.[0]?.text || JSON.stringify(data),
|
|
187
|
+
reasoning: data.reasoning?.content?.[0]?.text || "",
|
|
188
|
+
response_id: data.id,
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "xai_multi_agent",
|
|
194
|
+
description: "Run deep multi-agent research using Grok (4 or 16 agents).",
|
|
195
|
+
parameters: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
query: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "Research question or topic",
|
|
201
|
+
},
|
|
202
|
+
num_agents: {
|
|
203
|
+
type: "number",
|
|
204
|
+
enum: [4, 16],
|
|
205
|
+
description: "Number of research agents",
|
|
206
|
+
default: 4,
|
|
207
|
+
},
|
|
208
|
+
reasoning_effort: {
|
|
209
|
+
type: "string",
|
|
210
|
+
enum: ["medium", "high"],
|
|
211
|
+
default: "high",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
required: ["query"],
|
|
215
|
+
},
|
|
216
|
+
handler: async (args: any, context: any) => {
|
|
217
|
+
const apiKey = context?.apiKey || process.env.XAI_API_KEY;
|
|
218
|
+
if (!apiKey) return { error: "No xAI API key available" };
|
|
219
|
+
|
|
220
|
+
const prompt = `You are leading a team of ${args.num_agents} expert researchers. Conduct deep research on: ${args.query}. Synthesize findings from multiple perspectives.`;
|
|
221
|
+
|
|
222
|
+
const response = await fetch("https://api.x.ai/v1/responses", {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: {
|
|
225
|
+
"Content-Type": "application/json",
|
|
226
|
+
Authorization: `Bearer ${apiKey}`,
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
model: "grok-4.3",
|
|
230
|
+
input: prompt,
|
|
231
|
+
reasoning: { effort: args.reasoning_effort || "high" },
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const data = await response.json();
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
research: data.output?.[0]?.content?.[0]?.text || "Research completed",
|
|
239
|
+
agents_used: args.num_agents,
|
|
240
|
+
response_id: data.id,
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
|
|
39
246
|
models: [
|
|
40
247
|
{
|
|
41
248
|
id: "grok-3",
|