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.
@@ -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-completions",
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
- const accessToken = await callbacks.onPrompt({
16
- message:
17
- "Paste your xAI API key (starts with xai-).\n" +
18
- "You can get one at https://console.x.ai"
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: accessToken.trim(),
24
- expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
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
- // xAI currently doesn't require token refresh for API keys.
30
- // Return the same credentials.
31
- return credentials;
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-xai-oauth",
3
- "version": "1.0.2",
3
+ "version": "1.0.7",
4
4
  "description": "xAI (Grok) provider with OAuth support for pi",
5
5
  "keywords": ["pi-package"],
6
6
  "license": "MIT",