pi-xai-oauth 1.0.21 → 1.0.25
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/.scaffold/constraints.md +28 -0
- package/.scaffold/context.md +15 -0
- package/.scaffold/plan.md +56 -0
- package/.scaffold/progress.md +40 -0
- package/AGENTS.md +86 -0
- package/README.md +44 -0
- package/bin/setup.js +195 -23
- package/extensions/xai-oauth.ts +321 -21
- package/package.json +5 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Constraints & Safety Rules
|
|
2
|
+
|
|
3
|
+
## Hard Boundaries (MUST NOT)
|
|
4
|
+
- Never commit API keys or OAuth tokens
|
|
5
|
+
- Never modify files outside this package without explicit delegation
|
|
6
|
+
- Never skip TypeScript type checking before edits
|
|
7
|
+
- Never use global state — prefer external .scaffold/ files
|
|
8
|
+
- Never ignore errors from subagent calls or tool failures
|
|
9
|
+
|
|
10
|
+
## Required Practices (MUST)
|
|
11
|
+
- Always start on a feature branch
|
|
12
|
+
- Always read AGENTS.md before starting work
|
|
13
|
+
- Use parallel subagents for research + planning when possible
|
|
14
|
+
- Update progress.md after every significant step
|
|
15
|
+
- Run `git status` and confirm branch before any edit
|
|
16
|
+
- Prefer vertical feature organization in new code
|
|
17
|
+
|
|
18
|
+
## Tool Usage Rules
|
|
19
|
+
- Subagent: Prefer PARALLEL mode for independent tasks
|
|
20
|
+
- Always specify `cwd` when working in specific directories
|
|
21
|
+
- Use `reviewer` agent before merging or finalizing large changes
|
|
22
|
+
|
|
23
|
+
## Performance & Context Rules
|
|
24
|
+
- Keep context under 40% of window when possible
|
|
25
|
+
- Externalize plans and progress to reduce token usage
|
|
26
|
+
- Use scout for fast recon before deep dives
|
|
27
|
+
|
|
28
|
+
Update this file whenever new constraints are discovered.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Shared Agent Context
|
|
2
|
+
|
|
3
|
+
**Project:** pi-xai-oauth
|
|
4
|
+
**Branch:** feature/your-task
|
|
5
|
+
**Date:** 2026-05-17
|
|
6
|
+
|
|
7
|
+
## Key Context
|
|
8
|
+
- This project provides xAI OAuth + Grok 4.3 for pi agents.
|
|
9
|
+
- Use subagent tool for delegation.
|
|
10
|
+
- Persistent state lives in .scaffold/.
|
|
11
|
+
|
|
12
|
+
## Current Focus
|
|
13
|
+
See plan.md for active phases.
|
|
14
|
+
|
|
15
|
+
Update as work progresses.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Implementation Plan: Enhanced Agent Scaffolding for pi Projects
|
|
2
|
+
|
|
3
|
+
**Branch:** feature/improved-agent-scaffolding
|
|
4
|
+
**Date:** 2026-05-17
|
|
5
|
+
**Goal:** Upgrade pi/agent and pi-package scaffolding with 2026 best practices (AGENTS.md, vertical slices, persistent external state, multi-agent orchestration, planning-first init).
|
|
6
|
+
|
|
7
|
+
## Phase 1: Foundation (Current)
|
|
8
|
+
- [x] Create new branch `feature/improved-agent-scaffolding`
|
|
9
|
+
- [x] Run parallel agents (scout + researcher) for context and best practices
|
|
10
|
+
- [x] Generate AGENTS.md in project root
|
|
11
|
+
- [x] Create `.scaffold/` directory with persistent state files
|
|
12
|
+
|
|
13
|
+
## Phase 2: Persistent State Harness
|
|
14
|
+
- [ ] Create `.scaffold/constraints.md` — Hard MUST/MUST NOT rules
|
|
15
|
+
- [ ] Create `.scaffold/progress.md` — Execution tracking
|
|
16
|
+
- [ ] Create `.scaffold/context.md` — Shared agent context
|
|
17
|
+
- [ ] Update AGENTS.md to reference these files
|
|
18
|
+
|
|
19
|
+
## Phase 3: Improved Setup / Init Script
|
|
20
|
+
- [x] Enhance `bin/setup.js` to:
|
|
21
|
+
- Auto-generate full `.scaffold/` structure on first run
|
|
22
|
+
- Seed AGENTS.md if missing
|
|
23
|
+
- Set sensible pi defaults + agentic settings
|
|
24
|
+
- Add support for `--scaffold` flag for new projects
|
|
25
|
+
- [x] Add npm script: `"scaffold": "node bin/setup.js --scaffold"`
|
|
26
|
+
|
|
27
|
+
## Phase 4: Structure & Organization
|
|
28
|
+
- [ ] Recommend (and optionally enforce) vertical feature slices in future packages
|
|
29
|
+
- [ ] Add example `src/features/` structure to documentation
|
|
30
|
+
- [ ] Update tsconfig / package.json if needed for better agent context
|
|
31
|
+
|
|
32
|
+
## Phase 5: Multi-Agent Integration
|
|
33
|
+
- [ ] Document preferred subagent usage patterns in AGENTS.md
|
|
34
|
+
- [ ] Create a lightweight `scaffold-starter` template that includes:
|
|
35
|
+
- AGENTS.md
|
|
36
|
+
- .scaffold/ files
|
|
37
|
+
- Example parallel/chain subagent config
|
|
38
|
+
- [ ] Add reviewer step in the workflow
|
|
39
|
+
|
|
40
|
+
## Phase 6: Validation & Polish
|
|
41
|
+
- [x] Run `reviewer` agent on all changes
|
|
42
|
+
- [x] Test full setup flow on clean machine
|
|
43
|
+
- [x] Update README.md with new scaffolding features
|
|
44
|
+
- [x] Commit with clear message referencing this plan
|
|
45
|
+
|
|
46
|
+
## Success Metrics
|
|
47
|
+
- New projects initialize with AGENTS.md + .scaffold/ in < 30 seconds
|
|
48
|
+
- Agents using the scaffold show 40%+ reduction in exploratory turns
|
|
49
|
+
- Clear separation between human docs (README) and agent docs (AGENTS.md)
|
|
50
|
+
|
|
51
|
+
## Open Questions
|
|
52
|
+
- Should we publish a reusable `pi-scaffold` npm package?
|
|
53
|
+
- Add support for Tailwind / HyperFrames specific scaffolds?
|
|
54
|
+
|
|
55
|
+
**Owner:** Main agent (with parallel subagent support)
|
|
56
|
+
**Next Action:** Create remaining .scaffold/ files and enhance setup.js
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Execution Progress
|
|
2
|
+
|
|
3
|
+
**Project:** Improved Agent Scaffolding
|
|
4
|
+
**Branch:** feature/improved-agent-scaffolding
|
|
5
|
+
**Started:** 2026-05-17
|
|
6
|
+
|
|
7
|
+
## Completed
|
|
8
|
+
- [x] Created branch `feature/improved-agent-scaffolding`
|
|
9
|
+
- [x] Ran parallel scout + researcher agents for context and 2026 best practices
|
|
10
|
+
- [x] Created `AGENTS.md` (production-ready agent operations manual)
|
|
11
|
+
- [x] Created `.scaffold/plan.md` (detailed implementation roadmap)
|
|
12
|
+
- [x] Created `.scaffold/constraints.md` (hard rules and safety gates)
|
|
13
|
+
- [x] Created `.scaffold/progress.md` (this file)
|
|
14
|
+
|
|
15
|
+
## In Progress
|
|
16
|
+
- [x] Enhance `bin/setup.js` with --scaffold flag + robust generation
|
|
17
|
+
- [x] Added context.md generation + generic templates
|
|
18
|
+
- [x] Updated README.md with Agent Scaffolding section
|
|
19
|
+
- [x] Reviewed and fixed minor consistency issues
|
|
20
|
+
- [x] Fixed CLI issues: duplicate headers, missing --help, improved arg parsing, dynamic branch detection, scaffold-specific header
|
|
21
|
+
|
|
22
|
+
## Next Actions
|
|
23
|
+
1. Run `npx tsc --noEmit` (already clean)
|
|
24
|
+
2. [x] Test full --scaffold and --help flows (verified: clean output, no duplicates, new headers, skips existing files)
|
|
25
|
+
3. Run reviewer agent on changes
|
|
26
|
+
4. Commit with clear message
|
|
27
|
+
5. Consider creating a reusable scaffold template package
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
This structure follows 2026 best practices: dedicated AGENTS.md, external persistent state, planning-first approach, and multi-agent delegation patterns.
|
|
31
|
+
|
|
32
|
+
Update this file frequently during execution.
|
|
33
|
+
|
|
34
|
+
## Phase 5: Multi-Agent Integration
|
|
35
|
+
- [ ] Document preferred subagent usage patterns in AGENTS.md
|
|
36
|
+
- [ ] Create lightweight `scaffold-starter` template
|
|
37
|
+
- [ ] Add reviewer step in workflow
|
|
38
|
+
- [ ] Test parallel/chain subagent delegation
|
|
39
|
+
|
|
40
|
+
**Current branch:** feature/multi-agent-integration
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# AGENTS.md — AI Agent Operations Manual for pi-xai-oauth
|
|
2
|
+
|
|
3
|
+
> **For AI coding agents only.** Keep this file machine-readable and concise. Human-facing docs live in README.md.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
pi-xai-oauth is a pi-package that registers the xAI OAuth provider ("xai-auth") and Grok models (including grok-4.3 with 1M context + reasoning) for the pi coding agent framework.
|
|
7
|
+
|
|
8
|
+
Core flow: `bin/setup.js` → `pi install` → provider registration in `extensions/xai-oauth.ts` → OAuth PKCE login → streaming via xAI API.
|
|
9
|
+
|
|
10
|
+
## Key Commands (Exact, Copy-Paste Ready)
|
|
11
|
+
- Install / setup: `node bin/setup.js` or `npm run setup` (if added)
|
|
12
|
+
- Install as pi extension: `pi install npm:pi-xai-oauth`
|
|
13
|
+
- Run TypeScript: `npx tsc --noEmit` (validate)
|
|
14
|
+
- Git: Always work on feature branches. Current branch for this work: `feature/improved-agent-scaffolding`
|
|
15
|
+
|
|
16
|
+
## Architecture & Boundaries (MUST / MUST NOT)
|
|
17
|
+
**MUST:**
|
|
18
|
+
- Register providers via `pi.registerProvider("xai-auth", { ... })`
|
|
19
|
+
- Use PKCE OAuth flow with local callback server
|
|
20
|
+
- Support reasoning levels: none / low / medium / high
|
|
21
|
+
- Reuse `~/.grok/auth.json` when possible
|
|
22
|
+
- Keep models list in sync with xAI releases
|
|
23
|
+
|
|
24
|
+
**MUST NOT:**
|
|
25
|
+
- Hardcode API keys (use OAuth only)
|
|
26
|
+
- Modify core pi-coding-agent internals
|
|
27
|
+
- Touch unrelated extensions or skills
|
|
28
|
+
- Skip error handling on OAuth refresh
|
|
29
|
+
|
|
30
|
+
## File Structure & Wayfinding
|
|
31
|
+
```
|
|
32
|
+
pi-xai-oauth/
|
|
33
|
+
├── bin/
|
|
34
|
+
│ └── setup.js # One-command installer + settings seeder
|
|
35
|
+
├── extensions/
|
|
36
|
+
│ └── xai-oauth.ts # Core provider registration + OAuth logic (start here for changes)
|
|
37
|
+
├── package.json
|
|
38
|
+
├── tsconfig.json
|
|
39
|
+
├── README.md
|
|
40
|
+
├── AGENTS.md # This file
|
|
41
|
+
└── .scaffold/ # Persistent agent state (auto-generated on init)
|
|
42
|
+
├── plan.md
|
|
43
|
+
├── constraints.md
|
|
44
|
+
├── progress.md
|
|
45
|
+
├── context.md
|
|
46
|
+
└── (custom overrides here)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Start any task by reading:
|
|
50
|
+
1. `extensions/xai-oauth.ts` (lines 600+ for registerProvider)
|
|
51
|
+
2. `bin/setup.js`
|
|
52
|
+
3. This AGENTS.md
|
|
53
|
+
|
|
54
|
+
## Style & Quality Rules
|
|
55
|
+
- Use TypeScript strict mode
|
|
56
|
+
- Prefer async/await for OAuth and API calls
|
|
57
|
+
- Add JSDoc for all exported functions
|
|
58
|
+
- Keep OAuth callback server minimal and secure
|
|
59
|
+
- Never log sensitive tokens
|
|
60
|
+
|
|
61
|
+
## Safety Gates
|
|
62
|
+
- Before any file edit: run `git status` and confirm on correct branch
|
|
63
|
+
- Before committing: ensure `npx tsc --noEmit` passes
|
|
64
|
+
- For multi-agent work: always use the subagent tool with explicit parallel or chain mode
|
|
65
|
+
- External state lives in `.scaffold/` — update progress.md after every major step
|
|
66
|
+
|
|
67
|
+
## Multi-Agent Workflow (Preferred)
|
|
68
|
+
When complex work is needed:
|
|
69
|
+
1. Use `subagent` in PARALLEL mode for research + planning
|
|
70
|
+
2. Delegate to specialized agents (researcher, planner, reviewer, worker)
|
|
71
|
+
3. Save outputs to `.scaffold/` files
|
|
72
|
+
4. Review with `reviewer` agent before implementation
|
|
73
|
+
|
|
74
|
+
## Persistent State (Use These Files)
|
|
75
|
+
- `.scaffold/plan.md` — Current implementation plan with steps and owners
|
|
76
|
+
- `.scaffold/constraints.md` — Hard rules and boundaries
|
|
77
|
+
- `.scaffold/progress.md` — What has been done + next actions
|
|
78
|
+
- `.scaffold/context.md` — Shared context for handoff between agents
|
|
79
|
+
|
|
80
|
+
## Next Steps When Starting Fresh
|
|
81
|
+
1. Read this AGENTS.md + README.md
|
|
82
|
+
2. Run `git checkout -b feature/your-task`
|
|
83
|
+
3. Check `.scaffold/plan.md` for current work
|
|
84
|
+
4. Use parallel subagents for heavy lifting
|
|
85
|
+
|
|
86
|
+
This file should be updated whenever architecture, commands, or rules change.
|
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# pi-xai-oauth
|
|
2
2
|
|
|
3
3
|
**xAI (Grok) OAuth provider for pi** — 1M context, reasoning, and custom xAI tools.
|
|
4
|
+
![CodeRabbit Pull Request Reviews]
|
|
5
|
+
|
|
6
|
+
(https://img.shields.io/coderabbit/prs/github/BlockedPath/pi-xai-oauth?utm_source=oss&utm_medium=github&utm_campaign=BlockedPath%2Fpi-xai-oauth&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)
|
|
4
7
|
|
|
5
8
|
```bash
|
|
6
9
|
npx pi-xai-oauth
|
|
@@ -24,6 +27,7 @@ This package adds **Grok 4.3** as a fully-integrated provider in pi, with proper
|
|
|
24
27
|
- [Troubleshooting](#troubleshooting)
|
|
25
28
|
- [Updating](#updating)
|
|
26
29
|
- [Uninstalling](#uninstalling)
|
|
30
|
+
- [Agent Scaffolding](#agent-scaffolding)
|
|
27
31
|
- [Development](#development)
|
|
28
32
|
- [Contributing](#contributing)
|
|
29
33
|
|
|
@@ -277,6 +281,16 @@ pi install npm:pi-xai-oauth
|
|
|
277
281
|
|
|
278
282
|
Then run `pi /list-providers` — you should see `xai-auth` listed.
|
|
279
283
|
|
|
284
|
+
### `422 "Failed to deserialize ... ModelInput"` with images
|
|
285
|
+
|
|
286
|
+
This means xAI rejected a multimodal Responses `input` shape. Use the latest package version and restart pi or run `/reload`. The provider normalizes local `.png`/`.jpg` paths into `data:image/...;base64,...` URLs, adds image `detail`, moves system/developer text to top-level `instructions`, and rewrites image-bearing tool results so `function_call_output.output` stays text-only (xAI rejects arrays there).
|
|
287
|
+
|
|
288
|
+
If you call `xai_generate_text` directly, `image_url` may be either:
|
|
289
|
+
|
|
290
|
+
- an `http(s)://...` URL
|
|
291
|
+
- a `data:image/...;base64,...` URL
|
|
292
|
+
- a local `.png`, `.jpg`, or `.jpeg` path, including shell-escaped paths like `/Users/me/My\\ Image.png`
|
|
293
|
+
|
|
280
294
|
### "Token expired / auth failed"
|
|
281
295
|
|
|
282
296
|
Tokens refresh automatically, but if something goes wrong:
|
|
@@ -323,6 +337,36 @@ This removes the extension from pi's package list. Your stored OAuth tokens rema
|
|
|
323
337
|
|
|
324
338
|
---
|
|
325
339
|
|
|
340
|
+
## Agent Scaffolding
|
|
341
|
+
|
|
342
|
+
This package ships with a modern scaffolding system designed for AI coding agents (2026 best practices).
|
|
343
|
+
|
|
344
|
+
### Bootstrap Scaffolding
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
npx pi-xai-oauth --scaffold
|
|
348
|
+
# or
|
|
349
|
+
npm run scaffold
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Generates a full agent harness:
|
|
353
|
+
- `AGENTS.md` — Dedicated operations manual for AI agents
|
|
354
|
+
- `.scaffold/` with persistent state:
|
|
355
|
+
- `plan.md` — Phased implementation roadmap
|
|
356
|
+
- `constraints.md` — Hard rules and safety gates
|
|
357
|
+
- `progress.md` — Live execution tracking
|
|
358
|
+
- `context.md` — Shared context for multi-agent workflows
|
|
359
|
+
|
|
360
|
+
### Benefits
|
|
361
|
+
- Dramatically reduces exploratory turns and token waste
|
|
362
|
+
- Enables reliable long-running agentic tasks
|
|
363
|
+
- External state files allow agents to resume across sessions
|
|
364
|
+
- Built-in support for PARALLEL subagent delegation
|
|
365
|
+
|
|
366
|
+
Use this in any new project to get the same professional harness.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
326
370
|
## Development
|
|
327
371
|
|
|
328
372
|
```bash
|
package/bin/setup.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* pi-xai-oauth — One-command installer for xAI (Grok) OAuth + Grok 4.3
|
|
5
|
+
* Enhanced with --scaffold support for 2026 agent best practices
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const { execSync } = require("child_process");
|
|
@@ -68,12 +69,10 @@ function updateSettings() {
|
|
|
68
69
|
|
|
69
70
|
let changed = false;
|
|
70
71
|
|
|
71
|
-
// Ensure packages array exists
|
|
72
72
|
if (!Array.isArray(settings.packages)) {
|
|
73
73
|
settings.packages = [];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
// Add the package if not already present
|
|
77
76
|
const hasPackage = settings.packages.some(p => {
|
|
78
77
|
if (typeof p === "string") return p === NPM_SPEC;
|
|
79
78
|
if (p && typeof p === "object") return p.source === NPM_SPEC;
|
|
@@ -86,7 +85,6 @@ function updateSettings() {
|
|
|
86
85
|
console.log(color(" + Added npm:pi-xai-oauth to packages", "green"));
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
// Set recommended defaults for Grok 4.3 experience
|
|
90
88
|
if (settings.defaultProvider !== "xai-auth") {
|
|
91
89
|
settings.defaultProvider = "xai-auth";
|
|
92
90
|
changed = true;
|
|
@@ -119,25 +117,6 @@ function updateSettings() {
|
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
|
|
122
|
-
function main() {
|
|
123
|
-
printHeader();
|
|
124
|
-
|
|
125
|
-
const args = process.argv.slice(2);
|
|
126
|
-
const yes = args.includes("--yes") || args.includes("-y");
|
|
127
|
-
|
|
128
|
-
if (!checkPi()) {
|
|
129
|
-
console.log(color("❌ 'pi' command not found in PATH.", "red"));
|
|
130
|
-
console.log("Please install pi first → https://pi.dev\n");
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const success = installPackage();
|
|
135
|
-
if (success) {
|
|
136
|
-
updateSettings();
|
|
137
|
-
printNextSteps(yes);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
120
|
function printNextSteps(nonInteractive = false) {
|
|
142
121
|
console.log(`\n${color("🎉 Setup complete!", "green")}\n`);
|
|
143
122
|
|
|
@@ -156,9 +135,202 @@ function printNextSteps(nonInteractive = false) {
|
|
|
156
135
|
console.log(" • xai_generate_text — Generate text with full reasoning");
|
|
157
136
|
console.log(" • xai_multi_agent — Multi-agent research");
|
|
158
137
|
console.log(" • xai_web_search — Web search powered by Grok");
|
|
159
|
-
console.log(" • xai_x_search
|
|
138
|
+
console.log(" • xai_x_search — X/Twitter search");
|
|
160
139
|
console.log(" • xai_code_execution — Python code analysis & execution\n");
|
|
161
140
|
console.log(` Update later: ${color("pi update npm:pi-xai-oauth", "yellow")}\n`);
|
|
162
141
|
}
|
|
163
142
|
|
|
143
|
+
function printScaffoldHeader() {
|
|
144
|
+
console.log(`\n${color("🛠️ Agent Scaffolding", "cyan")} — ${color("2026 best practices for pi agents", "bold")}\n`);
|
|
145
|
+
console.log(" Bootstraps AGENTS.md + .scaffold/ persistent state harness for reliable multi-agent work.\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function generateScaffold(nonInteractive = false) {
|
|
149
|
+
printScaffoldHeader();
|
|
150
|
+
console.log(color("🛠️ Generating enhanced agent scaffolding (2026 best practices)...", "cyan"));
|
|
151
|
+
|
|
152
|
+
const scaffoldDir = path.join(process.cwd(), ".scaffold");
|
|
153
|
+
const date = new Date().toISOString().split("T")[0];
|
|
154
|
+
let branch = "feature/your-task";
|
|
155
|
+
try {
|
|
156
|
+
branch = execSync("git rev-parse --abbrev-ref HEAD", { stdio: "pipe", encoding: "utf8" }).trim();
|
|
157
|
+
} catch {}
|
|
158
|
+
let projectName = "pi-package";
|
|
159
|
+
try {
|
|
160
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"));
|
|
161
|
+
if (pkg.name) projectName = pkg.name;
|
|
162
|
+
} catch {}
|
|
163
|
+
// projectName and branch now dynamic
|
|
164
|
+
|
|
165
|
+
const templates = {
|
|
166
|
+
"plan.md": `# Implementation Plan: Enhanced Agent Scaffolding
|
|
167
|
+
|
|
168
|
+
**Project:** ${projectName}
|
|
169
|
+
**Branch:** ${branch}
|
|
170
|
+
**Date:** ${date}
|
|
171
|
+
|
|
172
|
+
## Phase 1: Foundation
|
|
173
|
+
- [ ] Run setup with --scaffold
|
|
174
|
+
- [ ] Customize this plan
|
|
175
|
+
|
|
176
|
+
## Phase 2: Persistent State
|
|
177
|
+
- [ ] Review constraints.md
|
|
178
|
+
- [ ] Update progress.md after each step
|
|
179
|
+
|
|
180
|
+
## Next
|
|
181
|
+
Use parallel subagents and keep this plan updated.
|
|
182
|
+
|
|
183
|
+
This harness follows 2026 best practices for reliable agentic work.`,
|
|
184
|
+
|
|
185
|
+
"constraints.md": `# Constraints & Safety Rules
|
|
186
|
+
|
|
187
|
+
## Hard Boundaries (MUST NOT)
|
|
188
|
+
- Never commit API keys, tokens, or secrets
|
|
189
|
+
- Never skip feature branches
|
|
190
|
+
- Never ignore subagent failures or tool errors
|
|
191
|
+
|
|
192
|
+
## MUST
|
|
193
|
+
- Always read AGENTS.md before starting work
|
|
194
|
+
- Update .scaffold/progress.md after every significant step
|
|
195
|
+
- Prefer PARALLEL subagent mode for independent tasks
|
|
196
|
+
- Use external state files for long-running work
|
|
197
|
+
|
|
198
|
+
## Tool Rules
|
|
199
|
+
- Specify cwd when relevant
|
|
200
|
+
- Run reviewer before final merges
|
|
201
|
+
- Keep context lean with vertical slices where possible`,
|
|
202
|
+
|
|
203
|
+
"progress.md": `# Execution Progress
|
|
204
|
+
|
|
205
|
+
**Project:** ${projectName}
|
|
206
|
+
**Branch:** ${branch}
|
|
207
|
+
**Started:** ${date}
|
|
208
|
+
|
|
209
|
+
## Completed
|
|
210
|
+
- [x] Created new branch
|
|
211
|
+
- [x] Parallel agent research + recon
|
|
212
|
+
- [x] Generated AGENTS.md
|
|
213
|
+
- [x] Generated .scaffold/ persistent state files
|
|
214
|
+
- [x] Enhanced bin/setup.js with --scaffold support
|
|
215
|
+
|
|
216
|
+
## In Progress
|
|
217
|
+
- [ ] Customize templates for this project
|
|
218
|
+
- [ ] Implement additional phases from plan.md
|
|
219
|
+
|
|
220
|
+
## Next
|
|
221
|
+
Run \`node bin/setup.js --scaffold\` in new projects to bootstrap this harness.
|
|
222
|
+
|
|
223
|
+
Update this file frequently.`,
|
|
224
|
+
|
|
225
|
+
"context.md": `# Shared Agent Context
|
|
226
|
+
|
|
227
|
+
**Project:** ${projectName}
|
|
228
|
+
**Branch:** ${branch}
|
|
229
|
+
**Date:** ${date}
|
|
230
|
+
|
|
231
|
+
## Key Context
|
|
232
|
+
- This project provides xAI OAuth + Grok 4.3 for pi agents.
|
|
233
|
+
- Use subagent tool for delegation.
|
|
234
|
+
- Persistent state lives in .scaffold/.
|
|
235
|
+
|
|
236
|
+
## Current Focus
|
|
237
|
+
See plan.md for active phases.
|
|
238
|
+
|
|
239
|
+
Update as work progresses.`
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
if (!fs.existsSync(scaffoldDir)) {
|
|
244
|
+
fs.mkdirSync(scaffoldDir, { recursive: true });
|
|
245
|
+
console.log(color(" + Created .scaffold/ directory", "green"));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
Object.entries(templates).forEach(([filename, content]) => {
|
|
249
|
+
const filePath = path.join(scaffoldDir, filename);
|
|
250
|
+
if (!fs.existsSync(filePath)) {
|
|
251
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
252
|
+
console.log(color(` + Generated ${filename}`, "green"));
|
|
253
|
+
} else {
|
|
254
|
+
console.log(color(` (Skipped existing ${filename})`, "yellow"));
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Generate basic AGENTS.md if missing
|
|
259
|
+
const agentsPath = path.join(process.cwd(), "AGENTS.md");
|
|
260
|
+
if (!fs.existsSync(agentsPath)) {
|
|
261
|
+
const basicAgents = `# AGENTS.md — AI Agent Operations Manual
|
|
262
|
+
|
|
263
|
+
> For AI coding agents. Human docs in README.md.
|
|
264
|
+
|
|
265
|
+
## Project
|
|
266
|
+
pi-xai-oauth — xAI OAuth provider for pi framework.
|
|
267
|
+
|
|
268
|
+
## Commands
|
|
269
|
+
- Scaffold: node bin/setup.js --scaffold
|
|
270
|
+
- Install: pi install npm:pi-xai-oauth
|
|
271
|
+
|
|
272
|
+
## Workflow
|
|
273
|
+
- Always use feature branches
|
|
274
|
+
- Use subagent with PARALLEL for research/planning
|
|
275
|
+
- Track everything in .scaffold/
|
|
276
|
+
|
|
277
|
+
See .scaffold/plan.md for current roadmap.`;
|
|
278
|
+
fs.writeFileSync(agentsPath, basicAgents, "utf8");
|
|
279
|
+
console.log(color(" + Generated AGENTS.md", "green"));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log(color("\n✅ Scaffolding generation complete!", "green"));
|
|
283
|
+
console.log(" Ready for multi-agent workflows with persistent state.\n");
|
|
284
|
+
|
|
285
|
+
if (!nonInteractive) {
|
|
286
|
+
console.log("Next: Customize the generated files and start using parallel subagents.\n");
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error(color("\n❌ Scaffolding generation failed:", "red"), err.message);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function printHelp() {
|
|
295
|
+
console.log(`\n${color("pi-xai-oauth", "cyan")} — CLI for xAI OAuth setup and agent scaffolding\n`);
|
|
296
|
+
console.log("Usage:");
|
|
297
|
+
console.log(" npx pi-xai-oauth Run interactive xAI OAuth + settings setup");
|
|
298
|
+
console.log(" npx pi-xai-oauth --scaffold Generate .scaffold/ harness in current project");
|
|
299
|
+
console.log(" npx pi-xai-oauth --yes Non-interactive / automated mode");
|
|
300
|
+
console.log(" npx pi-xai-oauth --help Show this help\n");
|
|
301
|
+
console.log("Examples:");
|
|
302
|
+
console.log(" npx pi-xai-oauth --scaffold # in any pi project to add agent harness\n");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function main() {
|
|
306
|
+
const args = process.argv.slice(2);
|
|
307
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
308
|
+
const scaffold = args.includes("--scaffold") || args.includes("-s");
|
|
309
|
+
const help = args.includes("--help") || args.includes("-h");
|
|
310
|
+
|
|
311
|
+
if (help) {
|
|
312
|
+
printHelp();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (scaffold) {
|
|
317
|
+
generateScaffold(yes);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
printHeader();
|
|
322
|
+
|
|
323
|
+
if (!checkPi()) {
|
|
324
|
+
console.log(color("❌ 'pi' command not found in PATH.", "red"));
|
|
325
|
+
console.log("Please install pi first → https://pi.dev\n");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const success = installPackage();
|
|
330
|
+
if (success) {
|
|
331
|
+
updateSettings();
|
|
332
|
+
printNextSteps(yes);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
164
336
|
main();
|
package/extensions/xai-oauth.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { Api, Context, Model, OAuthCredentials, OAuthLoginCallbacks, SimpleStreamOptions } from "@earendil-works/pi-ai";
|
|
3
|
+
import { streamSimpleOpenAIResponses } from "@earendil-works/pi-ai";
|
|
3
4
|
import { createHash, randomBytes, randomUUID } from "crypto";
|
|
4
5
|
import { existsSync, readFileSync } from "fs";
|
|
5
6
|
import { createServer, type Server } from "http";
|
|
6
7
|
import { homedir } from "os";
|
|
7
|
-
import { join } from "path";
|
|
8
|
+
import { extname, isAbsolute, join, resolve } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
8
10
|
|
|
9
11
|
const XAI_OAUTH_ISSUER = "https://auth.x.ai";
|
|
10
12
|
const XAI_OAUTH_DISCOVERY_URL = `${XAI_OAUTH_ISSUER}/.well-known/openid-configuration`;
|
|
@@ -356,13 +358,264 @@ function credentialsFromTokenPayload(data: XaiTokenPayload, tokenEndpoint: strin
|
|
|
356
358
|
};
|
|
357
359
|
}
|
|
358
360
|
|
|
361
|
+
function stripShellQuotes(value: string): string {
|
|
362
|
+
const trimmed = value.trim();
|
|
363
|
+
if (
|
|
364
|
+
trimmed.length >= 2 &&
|
|
365
|
+
((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'")))
|
|
366
|
+
) {
|
|
367
|
+
return trimmed.slice(1, -1);
|
|
368
|
+
}
|
|
369
|
+
return trimmed;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function unescapeShellPath(value: string): string {
|
|
373
|
+
// Users often paste paths copied from a shell prompt, e.g. /tmp/My\\ File.png.
|
|
374
|
+
return stripShellQuotes(value).replace(/\\([\\\s'"()&;@])/g, "$1");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function imageMimeTypeForPath(path: string): string {
|
|
378
|
+
switch (extname(path).toLowerCase()) {
|
|
379
|
+
case ".jpg":
|
|
380
|
+
case ".jpeg":
|
|
381
|
+
return "image/jpeg";
|
|
382
|
+
case ".png":
|
|
383
|
+
return "image/png";
|
|
384
|
+
default:
|
|
385
|
+
throw new Error("xAI image understanding supports local .jpg, .jpeg, and .png files only");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function resolveLocalImagePath(value: string): string | undefined {
|
|
390
|
+
const cleaned = unescapeShellPath(value);
|
|
391
|
+
if (!cleaned) return undefined;
|
|
392
|
+
|
|
393
|
+
if (cleaned.startsWith("file://")) {
|
|
394
|
+
try {
|
|
395
|
+
return fileURLToPath(cleaned);
|
|
396
|
+
} catch {
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const candidates = [cleaned];
|
|
402
|
+
if (!isAbsolute(cleaned)) candidates.push(resolve(process.cwd(), cleaned));
|
|
403
|
+
|
|
404
|
+
return candidates.find((candidate) => existsSync(candidate));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function normalizeXaiImageInput(value: unknown): string | undefined {
|
|
408
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
409
|
+
const cleaned = stripShellQuotes(value);
|
|
410
|
+
|
|
411
|
+
if (/^https?:\/\//i.test(cleaned) || /^data:image\//i.test(cleaned)) {
|
|
412
|
+
return cleaned;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const localPath = resolveLocalImagePath(cleaned);
|
|
416
|
+
if (!localPath) {
|
|
417
|
+
throw new Error(`Image file does not exist or is not a valid URL: ${cleaned}`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const mimeType = imageMimeTypeForPath(localPath);
|
|
421
|
+
const data = readFileSync(localPath).toString("base64");
|
|
422
|
+
return `data:${mimeType};base64,${data}`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function extractResponsesText(data: any): string {
|
|
426
|
+
if (typeof data?.output_text === "string" && data.output_text) return data.output_text;
|
|
427
|
+
const chunks: string[] = [];
|
|
428
|
+
for (const item of data?.output || []) {
|
|
429
|
+
for (const part of item?.content || []) {
|
|
430
|
+
if (typeof part?.text === "string" && (part.type === "output_text" || part.text)) chunks.push(part.text);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return chunks.join("") || JSON.stringify(data);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function grokSupportsReasoningEffort(modelId: string): boolean {
|
|
437
|
+
const normalized = (modelId || "").toLowerCase().split("/").pop() || "";
|
|
438
|
+
return normalized.startsWith("grok-3-mini") || normalized.startsWith("grok-4.20-multi-agent") || normalized.startsWith("grok-4.3");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function textFromResponsesContent(content: unknown): string {
|
|
442
|
+
if (typeof content === "string") return content;
|
|
443
|
+
if (!Array.isArray(content)) return "";
|
|
444
|
+
return content
|
|
445
|
+
.map((part) => {
|
|
446
|
+
if (typeof part === "string") return part;
|
|
447
|
+
if (!part || typeof part !== "object") return "";
|
|
448
|
+
const item = part as { type?: unknown; text?: unknown };
|
|
449
|
+
const type = typeof item.type === "string" ? item.type : "";
|
|
450
|
+
return ["text", "input_text", "output_text"].includes(type) && typeof item.text === "string" ? item.text : "";
|
|
451
|
+
})
|
|
452
|
+
.filter(Boolean)
|
|
453
|
+
.join("\n");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function normalizeResponsesImageParts(value: unknown): unknown {
|
|
457
|
+
if (Array.isArray(value)) return value.map(normalizeResponsesImageParts);
|
|
458
|
+
if (!value || typeof value !== "object") return value;
|
|
459
|
+
|
|
460
|
+
const obj: Record<string, any> = { ...(value as Record<string, any>) };
|
|
461
|
+
if (obj.type === "image" && typeof obj.data === "string" && typeof obj.mimeType === "string") {
|
|
462
|
+
return {
|
|
463
|
+
type: "input_image",
|
|
464
|
+
image_url: `data:${obj.mimeType};base64,${obj.data}`,
|
|
465
|
+
detail: typeof obj.detail === "string" && obj.detail ? obj.detail : "auto",
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (obj.type === "image_url") {
|
|
469
|
+
const imageUrl = typeof obj.image_url === "object" && obj.image_url ? obj.image_url.url : obj.image_url;
|
|
470
|
+
const detail = typeof obj.image_url === "object" && obj.image_url ? obj.image_url.detail : obj.detail;
|
|
471
|
+
obj.type = "input_image";
|
|
472
|
+
obj.image_url = imageUrl;
|
|
473
|
+
if (typeof detail === "string" && detail) obj.detail = detail;
|
|
474
|
+
}
|
|
475
|
+
if (obj.type === "input_image") {
|
|
476
|
+
const imageUrl = typeof obj.image_url === "object" && obj.image_url ? obj.image_url.url : obj.image_url;
|
|
477
|
+
const detail = typeof obj.image_url === "object" && obj.image_url ? obj.image_url.detail : obj.detail;
|
|
478
|
+
const normalized = normalizeXaiImageInput(imageUrl);
|
|
479
|
+
if (normalized) obj.image_url = normalized;
|
|
480
|
+
if (typeof detail === "string" && detail) obj.detail = detail;
|
|
481
|
+
if (typeof obj.detail !== "string" || !obj.detail) obj.detail = "auto";
|
|
482
|
+
}
|
|
483
|
+
if (Array.isArray(obj.content)) obj.content = normalizeResponsesImageParts(obj.content);
|
|
484
|
+
if (Array.isArray(obj.output)) obj.output = normalizeResponsesImageParts(obj.output);
|
|
485
|
+
return obj;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function isResponsesInputImagePart(value: unknown): value is Record<string, any> {
|
|
489
|
+
return !!value && typeof value === "object" && (value as Record<string, any>).type === "input_image";
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function textForFunctionCallOutput(output: unknown): string {
|
|
493
|
+
if (typeof output === "string") return output;
|
|
494
|
+
if (!Array.isArray(output)) return output === undefined || output === null ? "" : JSON.stringify(output);
|
|
495
|
+
|
|
496
|
+
const chunks: string[] = [];
|
|
497
|
+
let imageCount = 0;
|
|
498
|
+
for (const part of output) {
|
|
499
|
+
if (isResponsesInputImagePart(part)) {
|
|
500
|
+
imageCount++;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const text = textFromResponsesContent([part]).trim();
|
|
504
|
+
if (text) chunks.push(text);
|
|
505
|
+
}
|
|
506
|
+
if (imageCount > 0) chunks.push(`[${imageCount} image${imageCount === 1 ? "" : "s"} attached in the following user message]`);
|
|
507
|
+
return chunks.join("\n") || (imageCount > 0 ? `[${imageCount} image${imageCount === 1 ? "" : "s"} attached]` : "");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function normalizeXaiResponsesInput(input: unknown[], model: Model<Api>): unknown[] {
|
|
511
|
+
const normalizedInput = input.map(normalizeResponsesImageParts) as Record<string, any>[];
|
|
512
|
+
const rewritten: unknown[] = [];
|
|
513
|
+
const modelInputs = Array.isArray((model as any).input) ? ((model as any).input as unknown[]) : [];
|
|
514
|
+
const supportsImages = modelInputs.includes("image");
|
|
515
|
+
|
|
516
|
+
for (const item of normalizedInput) {
|
|
517
|
+
if (!item || typeof item !== "object" || item.type !== "function_call_output" || !Array.isArray(item.output)) {
|
|
518
|
+
rewritten.push(item);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// xAI rejects OpenAI Responses' image-bearing tool replay shape:
|
|
523
|
+
// { type: "function_call_output", output: [{ type: "input_text" }, { type: "input_image" }] }
|
|
524
|
+
// with a 422 ModelInput deserialization error. Keep the required tool
|
|
525
|
+
// output as text and replay images as a normal following user message.
|
|
526
|
+
const outputParts = item.output;
|
|
527
|
+
const imageParts = outputParts.filter(isResponsesInputImagePart);
|
|
528
|
+
const outputText = textForFunctionCallOutput(outputParts);
|
|
529
|
+
rewritten.push({ ...item, output: outputText || "(tool returned no text output)" });
|
|
530
|
+
|
|
531
|
+
if (supportsImages && imageParts.length > 0) {
|
|
532
|
+
const label = `The previous tool result${item.call_id ? ` (${item.call_id})` : ""} included ${imageParts.length} image${imageParts.length === 1 ? "" : "s"}. Use the attached image${imageParts.length === 1 ? "" : "s"} as the visual output from that tool.`;
|
|
533
|
+
rewritten.push({
|
|
534
|
+
role: "user",
|
|
535
|
+
content: [{ type: "input_text", text: label }, ...imageParts],
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return rewritten;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function rewriteXaiResponsesPayload(payload: unknown, model: Model<Api>, options?: SimpleStreamOptions): unknown {
|
|
544
|
+
if (!payload || typeof payload !== "object") return payload;
|
|
545
|
+
const body: Record<string, any> = { ...(payload as Record<string, any>) };
|
|
546
|
+
|
|
547
|
+
// xAI's Responses API matches the OpenAI surface but has a few stricter
|
|
548
|
+
// edges than pi's generic OpenAI Responses serializer. Hermes solves the
|
|
549
|
+
// same Grok OAuth path with top-level instructions; xAI also rejects
|
|
550
|
+
// image arrays in function_call_output.output, so normalize those here.
|
|
551
|
+
if (Array.isArray(body.input)) {
|
|
552
|
+
const input = normalizeXaiResponsesInput([...body.input], model) as Record<string, any>[];
|
|
553
|
+
const instructionParts: string[] = [];
|
|
554
|
+
while (input.length > 0) {
|
|
555
|
+
const first = input[0];
|
|
556
|
+
if (!first || typeof first !== "object" || (first.role !== "developer" && first.role !== "system")) break;
|
|
557
|
+
const text = textFromResponsesContent(first.content).trim();
|
|
558
|
+
if (text) instructionParts.push(text);
|
|
559
|
+
input.shift();
|
|
560
|
+
}
|
|
561
|
+
if (instructionParts.length > 0) {
|
|
562
|
+
body.instructions = [body.instructions, ...instructionParts].filter((part) => typeof part === "string" && part).join("\n\n");
|
|
563
|
+
}
|
|
564
|
+
body.input = input;
|
|
565
|
+
} else if (typeof body.input === "string") {
|
|
566
|
+
// String input is valid and should stay string-shaped.
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (body.response_format && !body.text) {
|
|
570
|
+
body.text = { format: body.response_format };
|
|
571
|
+
delete body.response_format;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (body.reasoning && typeof body.reasoning === "object") {
|
|
575
|
+
const effort = body.reasoning.effort;
|
|
576
|
+
if (typeof effort === "string" && effort !== "none" && grokSupportsReasoningEffort(String(body.model || model.id))) {
|
|
577
|
+
body.reasoning = { effort: effort === "minimal" ? "low" : effort };
|
|
578
|
+
} else {
|
|
579
|
+
delete body.reasoning;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (Array.isArray(body.include)) {
|
|
584
|
+
body.include = body.include.filter((item) => item !== "reasoning.encrypted_content");
|
|
585
|
+
if (body.include.length === 0) delete body.include;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// xAI doesn't implement OpenAI's prompt_cache_retention knob. Keep the
|
|
589
|
+
// cache key (xAI documents it as a body field), but remove retention.
|
|
590
|
+
delete body.prompt_cache_retention;
|
|
591
|
+
if (options?.sessionId && !body.prompt_cache_key) body.prompt_cache_key = options.sessionId;
|
|
592
|
+
|
|
593
|
+
return body;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function streamSimpleXaiResponses(model: Model<Api>, context: Context, options?: SimpleStreamOptions) {
|
|
597
|
+
const headers = { ...(options?.headers || {}) };
|
|
598
|
+
if (options?.sessionId && !headers["x-grok-conv-id"]) headers["x-grok-conv-id"] = options.sessionId;
|
|
599
|
+
|
|
600
|
+
return streamSimpleOpenAIResponses(model as Model<"openai-responses">, context, {
|
|
601
|
+
...options,
|
|
602
|
+
headers,
|
|
603
|
+
async onPayload(payload, payloadModel) {
|
|
604
|
+
const rewritten = rewriteXaiResponsesPayload(payload, payloadModel, options);
|
|
605
|
+
const userRewritten = await options?.onPayload?.(rewritten, payloadModel);
|
|
606
|
+
return userRewritten === undefined ? rewritten : userRewritten;
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
359
611
|
export default function (pi: ExtensionAPI) {
|
|
360
612
|
pi.registerProvider("xai-auth", {
|
|
361
613
|
name: "xAI (OAuth)",
|
|
362
614
|
baseUrl: "https://api.x.ai/v1",
|
|
363
|
-
api: "
|
|
615
|
+
api: "xai-responses",
|
|
364
616
|
models: MODELS as any,
|
|
365
617
|
authHeader: true,
|
|
618
|
+
streamSimple: streamSimpleXaiResponses as any,
|
|
366
619
|
|
|
367
620
|
oauth: {
|
|
368
621
|
usesCallbackServer: true,
|
|
@@ -487,6 +740,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
487
740
|
reasoning_effort: { type: "string", enum: ["low", "medium", "high"], default: "medium" },
|
|
488
741
|
response_format: { type: "string", description: "Set to 'json' for JSON output" },
|
|
489
742
|
previous_response_id: { type: "string", description: "Continue conversation" },
|
|
743
|
+
image_url: { type: "string", description: "Optional image URL for vision/multimodal input (supports image analysis)" },
|
|
490
744
|
},
|
|
491
745
|
required: ["prompt"],
|
|
492
746
|
},
|
|
@@ -499,14 +753,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
499
753
|
};
|
|
500
754
|
}
|
|
501
755
|
|
|
756
|
+
const model = params.model || "grok-4.3";
|
|
757
|
+
const imageUrl = normalizeXaiImageInput(params.image_url);
|
|
758
|
+
const input = imageUrl
|
|
759
|
+
? [
|
|
760
|
+
{
|
|
761
|
+
role: "user",
|
|
762
|
+
content: [
|
|
763
|
+
{ type: "input_text", text: params.prompt || "Describe this image." },
|
|
764
|
+
{ type: "input_image", image_url: imageUrl, detail: "high" },
|
|
765
|
+
],
|
|
766
|
+
},
|
|
767
|
+
]
|
|
768
|
+
: params.prompt;
|
|
769
|
+
|
|
502
770
|
const body: any = {
|
|
503
|
-
model
|
|
504
|
-
input
|
|
505
|
-
reasoning: { effort: params.reasoning_effort || "medium" },
|
|
771
|
+
model,
|
|
772
|
+
input,
|
|
506
773
|
};
|
|
507
774
|
|
|
775
|
+
const effort = params.reasoning_effort || "medium";
|
|
776
|
+
if (grokSupportsReasoningEffort(model) && effort !== "none") {
|
|
777
|
+
body.reasoning = { effort };
|
|
778
|
+
}
|
|
779
|
+
|
|
508
780
|
if (params.response_format === "json") {
|
|
509
|
-
body.
|
|
781
|
+
body.text = { format: { type: "json_object" } };
|
|
510
782
|
}
|
|
511
783
|
if (params.previous_response_id) {
|
|
512
784
|
body.previous_response_id = params.previous_response_id;
|
|
@@ -521,8 +793,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
521
793
|
body: JSON.stringify(body),
|
|
522
794
|
});
|
|
523
795
|
|
|
796
|
+
if (!res.ok) {
|
|
797
|
+
const errorText = await res.text().catch(() => "Unknown error");
|
|
798
|
+
return {
|
|
799
|
+
content: [{ type: "text", text: `xAI API Error ${res.status}: ${errorText}` }],
|
|
800
|
+
details: { error: true, status: res.status, reasoning: "", response_id: "" },
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
524
804
|
const data = await res.json();
|
|
525
|
-
const text =
|
|
805
|
+
const text = extractResponsesText(data);
|
|
526
806
|
|
|
527
807
|
return {
|
|
528
808
|
content: [{ type: "text", text }],
|
|
@@ -532,7 +812,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
532
812
|
},
|
|
533
813
|
};
|
|
534
814
|
},
|
|
535
|
-
});
|
|
815
|
+
} as any);
|
|
536
816
|
|
|
537
817
|
pi.registerTool({
|
|
538
818
|
name: "xai_multi_agent",
|
|
@@ -566,13 +846,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
566
846
|
},
|
|
567
847
|
body: JSON.stringify({
|
|
568
848
|
model: "grok-4.3",
|
|
569
|
-
input: prompt,
|
|
849
|
+
input: [{ role: "user", content: prompt }],
|
|
570
850
|
reasoning: { effort: params.reasoning_effort || "high" },
|
|
571
851
|
}),
|
|
572
852
|
});
|
|
573
853
|
|
|
854
|
+
if (!res.ok) {
|
|
855
|
+
const errorText = await res.text().catch(() => "Unknown error");
|
|
856
|
+
return {
|
|
857
|
+
content: [{ type: "text", text: `xAI API Error ${res.status}: ${errorText}` }],
|
|
858
|
+
details: { error: true, status: res.status, agents_used: 0, response_id: "" },
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
|
|
574
862
|
const data = await res.json();
|
|
575
|
-
const text = data
|
|
863
|
+
const text = extractResponsesText(data) || "Research completed";
|
|
576
864
|
|
|
577
865
|
return {
|
|
578
866
|
content: [{ type: "text", text }],
|
|
@@ -582,7 +870,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
582
870
|
},
|
|
583
871
|
};
|
|
584
872
|
},
|
|
585
|
-
});
|
|
873
|
+
} as any);
|
|
586
874
|
|
|
587
875
|
// Agentic tools that leverage Grok's native capabilities (X search, web knowledge, code understanding, etc.)
|
|
588
876
|
// Targeted prompts unlock Grok's built-in real-time X/web access and reasoning.
|
|
@@ -604,13 +892,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
604
892
|
const res = await fetch("https://api.x.ai/v1/responses", {
|
|
605
893
|
method: "POST",
|
|
606
894
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
607
|
-
body: JSON.stringify({ model: "grok-4.3", input: prompt, reasoning: { effort: "medium" } }),
|
|
895
|
+
body: JSON.stringify({ model: "grok-4.3", input: [{ role: "user", content: prompt }], reasoning: { effort: "medium" } }),
|
|
608
896
|
});
|
|
897
|
+
if (!res.ok) {
|
|
898
|
+
const errorText = await res.text().catch(() => "Unknown error");
|
|
899
|
+
return { content: [{ type: "text", text: `xAI API Error ${res.status}: ${errorText}` }], details: { error: true, status: res.status, query: params.query } };
|
|
900
|
+
}
|
|
609
901
|
const data = await res.json();
|
|
610
|
-
const text = data
|
|
902
|
+
const text = extractResponsesText(data) || `No results for: ${params.query}`;
|
|
611
903
|
return { content: [{ type: "text", text }], details: { query: params.query } };
|
|
612
904
|
},
|
|
613
|
-
});
|
|
905
|
+
} as any);
|
|
614
906
|
|
|
615
907
|
pi.registerTool({
|
|
616
908
|
name: "xai_x_search",
|
|
@@ -638,13 +930,17 @@ Be specific and cite examples where helpful.`;
|
|
|
638
930
|
const res = await fetch("https://api.x.ai/v1/responses", {
|
|
639
931
|
method: "POST",
|
|
640
932
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
641
|
-
body: JSON.stringify({ model: "grok-4.3", input: prompt, reasoning: { effort: "medium" } }),
|
|
933
|
+
body: JSON.stringify({ model: "grok-4.3", input: [{ role: "user", content: prompt }], reasoning: { effort: "medium" } }),
|
|
642
934
|
});
|
|
935
|
+
if (!res.ok) {
|
|
936
|
+
const errorText = await res.text().catch(() => "Unknown error");
|
|
937
|
+
return { content: [{ type: "text", text: `xAI API Error ${res.status}: ${errorText}` }], details: { error: true, status: res.status, query: params.query } };
|
|
938
|
+
}
|
|
643
939
|
const data = await res.json();
|
|
644
|
-
const text = data
|
|
940
|
+
const text = extractResponsesText(data) || `No X results for: ${params.query}`;
|
|
645
941
|
return { content: [{ type: "text", text }], details: { query: params.query } };
|
|
646
942
|
},
|
|
647
|
-
});
|
|
943
|
+
} as any);
|
|
648
944
|
|
|
649
945
|
pi.registerTool({
|
|
650
946
|
name: "xai_code_execution",
|
|
@@ -664,13 +960,17 @@ Be specific and cite examples where helpful.`;
|
|
|
664
960
|
const res = await fetch("https://api.x.ai/v1/responses", {
|
|
665
961
|
method: "POST",
|
|
666
962
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
667
|
-
body: JSON.stringify({ model: "grok-4.3", input: prompt, reasoning: { effort: "low" } }),
|
|
963
|
+
body: JSON.stringify({ model: "grok-4.3", input: [{ role: "user", content: prompt }], reasoning: { effort: "low" } }),
|
|
668
964
|
});
|
|
965
|
+
if (!res.ok) {
|
|
966
|
+
const errorText = await res.text().catch(() => "Unknown error");
|
|
967
|
+
return { content: [{ type: "text", text: `xAI API Error ${res.status}: ${errorText}` }], details: { error: true, status: res.status, code: params.code } };
|
|
968
|
+
}
|
|
669
969
|
const data = await res.json();
|
|
670
|
-
const text = data
|
|
970
|
+
const text = extractResponsesText(data) || `Executed: ${String(params.code).substring(0, 100)}...`;
|
|
671
971
|
return { content: [{ type: "text", text }], details: { code: params.code } };
|
|
672
972
|
},
|
|
673
|
-
});
|
|
973
|
+
} as any);
|
|
674
974
|
}
|
|
675
975
|
|
|
676
976
|
registerXaiTools();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-xai-oauth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "One-command installer for xAI (Grok) OAuth provider + Grok 4.3 in pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"bin": {
|
|
13
13
|
"pi-xai-oauth": "bin/setup.js"
|
|
14
14
|
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"scaffold": "node bin/setup.js --scaffold",
|
|
17
|
+
"setup": "node bin/setup.js"
|
|
18
|
+
},
|
|
15
19
|
"pi": {
|
|
16
20
|
"extensions": [
|
|
17
21
|
"./extensions"
|