@psnext/slingcli 2.4.20260601-1 → 2.5.20260602-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/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/dist-es/runtime/memoize-chain.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/package.json +1 -1
- package/node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +197 -190
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +84 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -14
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +130 -62
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +18 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/stream.js +14 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +27 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +29 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +21 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +65 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resolve-config-value.js +134 -11
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +12 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +167 -96
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +14 -9
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +0 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +7 -10
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/render-utils.js +17 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +5 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +69 -16
- package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +118 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +2 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +61 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/deprecation.js +13 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/json.js +7 -0
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +71 -56
- package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +172 -37
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +8 -3
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/node_modules/@mariozechner/clipboard/Cargo.toml +3 -3
- package/node_modules/@mariozechner/clipboard/index.d.ts +34 -20
- package/node_modules/@mariozechner/clipboard/index.js +546 -257
- package/node_modules/@mariozechner/clipboard/package.json +14 -14
- package/node_modules/@mariozechner/clipboard/src/lib.rs +4 -9
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/clipboard.linux-x64-gnu.node +0 -0
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/package.json +2 -2
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js +39 -22
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.js +39 -22
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/configurations.js +19 -6
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retryMiddleware.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/constants.js +1 -1
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/ConfiguredRetryStrategy.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryToken.js +3 -0
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/StandardRetryStrategy.js +9 -5
- package/node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/configurations.d.ts +4 -1
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts +1 -2
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryToken.d.ts +3 -0
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/StandardRetryStrategy.d.ts +1 -1
- package/node_modules/@smithy/core/package.json +2 -2
- package/node_modules/@smithy/credential-provider-imds/package.json +3 -3
- package/node_modules/@smithy/fetch-http-handler/package.json +4 -4
- package/node_modules/@smithy/signature-v4/package.json +3 -3
- package/node_modules/@smithy/types/dist-types/retry.d.ts +25 -0
- package/node_modules/@smithy/types/package.json +1 -1
- package/package.json +6 -6
- package/slingshot/index.js +231 -230
|
@@ -122,7 +122,7 @@ async function pollForGitHubAccessToken(domain, device, signal) {
|
|
|
122
122
|
}),
|
|
123
123
|
});
|
|
124
124
|
if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
|
|
125
|
-
return { status: "complete",
|
|
125
|
+
return { status: "complete", value: raw.access_token };
|
|
126
126
|
}
|
|
127
127
|
if (raw && typeof raw === "object" && typeof raw.error === "string") {
|
|
128
128
|
const { error, error_description: description } = raw;
|
|
@@ -12,7 +12,7 @@ export * from "./device-code.js";
|
|
|
12
12
|
// GitHub Copilot
|
|
13
13
|
export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
|
|
14
14
|
// OpenAI Codex (ChatGPT OAuth)
|
|
15
|
-
export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.js";
|
|
15
|
+
export { loginOpenAICodex, loginOpenAICodexDeviceCode, OPENAI_CODEX_BROWSER_LOGIN_METHOD, OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, openaiCodexOAuthProvider, refreshOpenAICodexToken, } from "./openai-codex.js";
|
|
16
16
|
export * from "./types.js";
|
|
17
17
|
// ============================================================================
|
|
18
18
|
// Provider Registry
|
|
@@ -15,13 +15,22 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|
|
15
15
|
_http = m;
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
+
import { pollOAuthDeviceCodeFlow } from "./device-code.js";
|
|
18
19
|
import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
|
|
19
20
|
import { generatePKCE } from "./pkce.js";
|
|
20
21
|
const CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1";
|
|
21
22
|
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
22
|
-
const
|
|
23
|
-
const
|
|
23
|
+
const AUTH_BASE_URL = "https://auth.openai.com";
|
|
24
|
+
const AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;
|
|
25
|
+
const TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;
|
|
24
26
|
const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
27
|
+
const DEVICE_USER_CODE_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;
|
|
28
|
+
const DEVICE_TOKEN_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`;
|
|
29
|
+
const DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`;
|
|
30
|
+
const DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`;
|
|
31
|
+
const DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60;
|
|
32
|
+
export const OPENAI_CODEX_BROWSER_LOGIN_METHOD = "browser";
|
|
33
|
+
export const OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = "device_code";
|
|
25
34
|
const SCOPE = "openid profile email offline_access";
|
|
26
35
|
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
27
36
|
function createState() {
|
|
@@ -70,8 +79,35 @@ function decodeJwt(token) {
|
|
|
70
79
|
return null;
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
|
-
async function
|
|
74
|
-
|
|
82
|
+
async function fetchWithLoginCancellation(input, init) {
|
|
83
|
+
try {
|
|
84
|
+
return await fetch(input, init);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (init.signal?.aborted) {
|
|
88
|
+
throw new Error("Login cancelled");
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function readTokenResponse(response, operation) {
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const text = await response.text().catch(() => "");
|
|
96
|
+
throw new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text || response.statusText}`);
|
|
97
|
+
}
|
|
98
|
+
const rawJson = await response.json();
|
|
99
|
+
const json = rawJson;
|
|
100
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
101
|
+
throw new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
access: json.access_token,
|
|
105
|
+
refresh: json.refresh_token,
|
|
106
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_URI, signal) {
|
|
110
|
+
const response = await fetchWithLoginCancellation(TOKEN_URL, {
|
|
75
111
|
method: "POST",
|
|
76
112
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
77
113
|
body: new URLSearchParams({
|
|
@@ -81,32 +117,14 @@ async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_
|
|
|
81
117
|
code_verifier: verifier,
|
|
82
118
|
redirect_uri: redirectUri,
|
|
83
119
|
}),
|
|
120
|
+
signal,
|
|
84
121
|
});
|
|
85
|
-
|
|
86
|
-
const text = await response.text().catch(() => "");
|
|
87
|
-
return {
|
|
88
|
-
type: "failed",
|
|
89
|
-
status: response.status,
|
|
90
|
-
message: `OpenAI Codex token exchange failed (${response.status}): ${text || response.statusText}`,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
const json = (await response.json());
|
|
94
|
-
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
95
|
-
return {
|
|
96
|
-
type: "failed",
|
|
97
|
-
message: `OpenAI Codex token exchange response missing fields: ${JSON.stringify(json)}`,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
type: "success",
|
|
102
|
-
access: json.access_token,
|
|
103
|
-
refresh: json.refresh_token,
|
|
104
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
105
|
-
};
|
|
122
|
+
return readTokenResponse(response, "exchange");
|
|
106
123
|
}
|
|
107
124
|
async function refreshAccessToken(refreshToken) {
|
|
125
|
+
let response;
|
|
108
126
|
try {
|
|
109
|
-
|
|
127
|
+
response = await fetch(TOKEN_URL, {
|
|
110
128
|
method: "POST",
|
|
111
129
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
112
130
|
body: new URLSearchParams({
|
|
@@ -115,34 +133,94 @@ async function refreshAccessToken(refreshToken) {
|
|
|
115
133
|
client_id: CLIENT_ID,
|
|
116
134
|
}),
|
|
117
135
|
});
|
|
118
|
-
if (!response.ok) {
|
|
119
|
-
const text = await response.text().catch(() => "");
|
|
120
|
-
return {
|
|
121
|
-
type: "failed",
|
|
122
|
-
status: response.status,
|
|
123
|
-
message: `OpenAI Codex token refresh failed (${response.status}): ${text || response.statusText}`,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
const json = (await response.json());
|
|
127
|
-
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
128
|
-
return {
|
|
129
|
-
type: "failed",
|
|
130
|
-
message: `OpenAI Codex token refresh response missing fields: ${JSON.stringify(json)}`,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
type: "success",
|
|
135
|
-
access: json.access_token,
|
|
136
|
-
refresh: json.refresh_token,
|
|
137
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
138
|
-
};
|
|
139
136
|
}
|
|
140
137
|
catch (error) {
|
|
141
|
-
|
|
142
|
-
type: "failed",
|
|
143
|
-
message: `OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`,
|
|
144
|
-
};
|
|
138
|
+
throw new Error(`OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
139
|
}
|
|
140
|
+
return readTokenResponse(response, "refresh");
|
|
141
|
+
}
|
|
142
|
+
async function startOpenAICodexDeviceAuth(signal) {
|
|
143
|
+
const response = await fetchWithLoginCancellation(DEVICE_USER_CODE_URL, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { "Content-Type": "application/json" },
|
|
146
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
147
|
+
signal,
|
|
148
|
+
});
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
if (response.status === 404) {
|
|
151
|
+
throw new Error("OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.");
|
|
152
|
+
}
|
|
153
|
+
const responseBody = await response.text().catch(() => "");
|
|
154
|
+
throw new Error(`OpenAI Codex device code request failed with status ${response.status}${responseBody ? `: ${responseBody}` : ""}`);
|
|
155
|
+
}
|
|
156
|
+
const rawJson = await response.json();
|
|
157
|
+
const json = rawJson;
|
|
158
|
+
const intervalSeconds = typeof json?.interval === "string" ? Number(json.interval.trim()) : json?.interval;
|
|
159
|
+
if (!json?.device_auth_id ||
|
|
160
|
+
!json.user_code ||
|
|
161
|
+
typeof intervalSeconds !== "number" ||
|
|
162
|
+
!Number.isFinite(intervalSeconds) ||
|
|
163
|
+
intervalSeconds < 0) {
|
|
164
|
+
throw new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
deviceAuthId: json.device_auth_id,
|
|
168
|
+
userCode: json.user_code,
|
|
169
|
+
intervalSeconds,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function pollOpenAICodexDeviceAuth(device, signal) {
|
|
173
|
+
return pollOAuthDeviceCodeFlow({
|
|
174
|
+
intervalSeconds: device.intervalSeconds,
|
|
175
|
+
expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,
|
|
176
|
+
signal,
|
|
177
|
+
poll: async () => {
|
|
178
|
+
const response = await fetchWithLoginCancellation(DEVICE_TOKEN_URL, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json" },
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
device_auth_id: device.deviceAuthId,
|
|
183
|
+
user_code: device.userCode,
|
|
184
|
+
}),
|
|
185
|
+
signal,
|
|
186
|
+
});
|
|
187
|
+
if (response.ok) {
|
|
188
|
+
const rawJson = await response.json();
|
|
189
|
+
const json = rawJson;
|
|
190
|
+
if (!json?.authorization_code || !json.code_verifier) {
|
|
191
|
+
return {
|
|
192
|
+
status: "failed",
|
|
193
|
+
message: `Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
status: "complete",
|
|
198
|
+
value: { authorizationCode: json.authorization_code, codeVerifier: json.code_verifier },
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (response.status === 403 || response.status === 404) {
|
|
202
|
+
return { status: "pending" };
|
|
203
|
+
}
|
|
204
|
+
const responseBody = await response.text().catch(() => "");
|
|
205
|
+
let errorCode;
|
|
206
|
+
try {
|
|
207
|
+
const json = JSON.parse(responseBody);
|
|
208
|
+
const error = json?.error;
|
|
209
|
+
errorCode = typeof error === "object" ? error?.code : error;
|
|
210
|
+
}
|
|
211
|
+
catch { }
|
|
212
|
+
if (errorCode === "deviceauth_authorization_pending") {
|
|
213
|
+
return { status: "pending" };
|
|
214
|
+
}
|
|
215
|
+
if (errorCode === "slow_down") {
|
|
216
|
+
return { status: "slow_down" };
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
status: "failed",
|
|
220
|
+
message: `OpenAI Codex device auth failed with status ${response.status}${responseBody ? `: ${responseBody}` : ""}`,
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
});
|
|
146
224
|
}
|
|
147
225
|
async function createAuthorizationFlow(originator = "pi") {
|
|
148
226
|
const { verifier, challenge } = await generatePKCE();
|
|
@@ -241,6 +319,35 @@ function getAccountId(accessToken) {
|
|
|
241
319
|
const accountId = auth?.chatgpt_account_id;
|
|
242
320
|
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
243
321
|
}
|
|
322
|
+
function credentialsFromToken(token) {
|
|
323
|
+
const accountId = getAccountId(token.access);
|
|
324
|
+
if (!accountId) {
|
|
325
|
+
throw new Error("Failed to extract accountId from token");
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
access: token.access,
|
|
329
|
+
refresh: token.refresh,
|
|
330
|
+
expires: token.expires,
|
|
331
|
+
accountId,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function exchangeAuthorizationCodeForCredentials(code, verifier, redirectUri, signal) {
|
|
335
|
+
return credentialsFromToken(await exchangeAuthorizationCode(code, verifier, redirectUri, signal));
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Login with OpenAI Codex OAuth using the Codex device-code flow.
|
|
339
|
+
*/
|
|
340
|
+
export async function loginOpenAICodexDeviceCode(options) {
|
|
341
|
+
const device = await startOpenAICodexDeviceAuth(options.signal);
|
|
342
|
+
options.onDeviceCode({
|
|
343
|
+
userCode: device.userCode,
|
|
344
|
+
verificationUri: DEVICE_VERIFICATION_URI,
|
|
345
|
+
intervalSeconds: device.intervalSeconds,
|
|
346
|
+
expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,
|
|
347
|
+
});
|
|
348
|
+
const code = await pollOpenAICodexDeviceAuth(device, options.signal);
|
|
349
|
+
return exchangeAuthorizationCodeForCredentials(code.authorizationCode, code.codeVerifier, DEVICE_REDIRECT_URI, options.signal);
|
|
350
|
+
}
|
|
244
351
|
/**
|
|
245
352
|
* Login with OpenAI Codex OAuth
|
|
246
353
|
*
|
|
@@ -325,20 +432,7 @@ export async function loginOpenAICodex(options) {
|
|
|
325
432
|
if (!code) {
|
|
326
433
|
throw new Error("Missing authorization code");
|
|
327
434
|
}
|
|
328
|
-
|
|
329
|
-
if (tokenResult.type !== "success") {
|
|
330
|
-
throw new Error(tokenResult.message);
|
|
331
|
-
}
|
|
332
|
-
const accountId = getAccountId(tokenResult.access);
|
|
333
|
-
if (!accountId) {
|
|
334
|
-
throw new Error("Failed to extract accountId from token");
|
|
335
|
-
}
|
|
336
|
-
return {
|
|
337
|
-
access: tokenResult.access,
|
|
338
|
-
refresh: tokenResult.refresh,
|
|
339
|
-
expires: tokenResult.expires,
|
|
340
|
-
accountId,
|
|
341
|
-
};
|
|
435
|
+
return exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI);
|
|
342
436
|
}
|
|
343
437
|
finally {
|
|
344
438
|
server.close();
|
|
@@ -348,26 +442,32 @@ export async function loginOpenAICodex(options) {
|
|
|
348
442
|
* Refresh OpenAI Codex OAuth token
|
|
349
443
|
*/
|
|
350
444
|
export async function refreshOpenAICodexToken(refreshToken) {
|
|
351
|
-
|
|
352
|
-
if (result.type !== "success") {
|
|
353
|
-
throw new Error(result.message);
|
|
354
|
-
}
|
|
355
|
-
const accountId = getAccountId(result.access);
|
|
356
|
-
if (!accountId) {
|
|
357
|
-
throw new Error("Failed to extract accountId from token");
|
|
358
|
-
}
|
|
359
|
-
return {
|
|
360
|
-
access: result.access,
|
|
361
|
-
refresh: result.refresh,
|
|
362
|
-
expires: result.expires,
|
|
363
|
-
accountId,
|
|
364
|
-
};
|
|
445
|
+
return credentialsFromToken(await refreshAccessToken(refreshToken));
|
|
365
446
|
}
|
|
366
447
|
export const openaiCodexOAuthProvider = {
|
|
367
448
|
id: "openai-codex",
|
|
368
449
|
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
369
450
|
usesCallbackServer: true,
|
|
370
451
|
async login(callbacks) {
|
|
452
|
+
const loginMethod = await callbacks.onSelect({
|
|
453
|
+
message: "Select OpenAI Codex login method:",
|
|
454
|
+
options: [
|
|
455
|
+
{ id: OPENAI_CODEX_BROWSER_LOGIN_METHOD, label: "Browser login (default)" },
|
|
456
|
+
{ id: OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, label: "Device code login (headless)" },
|
|
457
|
+
],
|
|
458
|
+
});
|
|
459
|
+
if (!loginMethod) {
|
|
460
|
+
throw new Error("Login cancelled");
|
|
461
|
+
}
|
|
462
|
+
if (loginMethod === OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD) {
|
|
463
|
+
return loginOpenAICodexDeviceCode({
|
|
464
|
+
onDeviceCode: callbacks.onDeviceCode,
|
|
465
|
+
signal: callbacks.signal,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (loginMethod !== OPENAI_CODEX_BROWSER_LOGIN_METHOD) {
|
|
469
|
+
throw new Error(`Unknown OpenAI Codex login method: ${loginMethod}`);
|
|
470
|
+
}
|
|
371
471
|
return loginOpenAICodex({
|
|
372
472
|
onAuth: callbacks.onAuth,
|
|
373
473
|
onPrompt: callbacks.onPrompt,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.0",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"license": "MIT",
|
|
93
93
|
"repository": {
|
|
94
94
|
"type": "git",
|
|
95
|
-
"url": "git+https://github.com/earendil-works/pi
|
|
95
|
+
"url": "git+https://github.com/earendil-works/pi.git",
|
|
96
96
|
"directory": "packages/ai"
|
|
97
97
|
},
|
|
98
98
|
"engines": {
|
|
@@ -50,12 +50,23 @@ export function parseArgs(args) {
|
|
|
50
50
|
result.appendSystemPrompt = result.appendSystemPrompt ?? [];
|
|
51
51
|
result.appendSystemPrompt.push(args[++i]);
|
|
52
52
|
}
|
|
53
|
+
else if (arg === "--name" || arg === "-n") {
|
|
54
|
+
if (i + 1 < args.length) {
|
|
55
|
+
result.name = args[++i];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
result.diagnostics.push({ type: "error", message: "--name requires a value" });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
53
61
|
else if (arg === "--no-session") {
|
|
54
62
|
result.noSession = true;
|
|
55
63
|
}
|
|
56
64
|
else if (arg === "--session" && i + 1 < args.length) {
|
|
57
65
|
result.session = args[++i];
|
|
58
66
|
}
|
|
67
|
+
else if (arg === "--session-id" && i + 1 < args.length) {
|
|
68
|
+
result.sessionId = args[++i];
|
|
69
|
+
}
|
|
59
70
|
else if (arg === "--fork" && i + 1 < args.length) {
|
|
60
71
|
result.fork = args[++i];
|
|
61
72
|
}
|
|
@@ -77,6 +88,12 @@ export function parseArgs(args) {
|
|
|
77
88
|
.map((s) => s.trim())
|
|
78
89
|
.filter((name) => name.length > 0);
|
|
79
90
|
}
|
|
91
|
+
else if ((arg === "--exclude-tools" || arg === "-xt") && i + 1 < args.length) {
|
|
92
|
+
result.excludeTools = args[++i]
|
|
93
|
+
.split(",")
|
|
94
|
+
.map((s) => s.trim())
|
|
95
|
+
.filter((name) => name.length > 0);
|
|
96
|
+
}
|
|
80
97
|
else if (arg === "--thinking" && i + 1 < args.length) {
|
|
81
98
|
const level = args[++i];
|
|
82
99
|
if (isValidThinkingLevel(level)) {
|
|
@@ -210,15 +227,19 @@ ${chalk.bold("Options:")}
|
|
|
210
227
|
--continue, -c Continue previous session
|
|
211
228
|
--resume, -r Select a session to resume
|
|
212
229
|
--session <path|id> Use specific session file or partial UUID
|
|
230
|
+
--session-id <id> Use exact project session ID, creating it if missing
|
|
213
231
|
--fork <path|id> Fork specific session file or partial UUID into a new session
|
|
214
232
|
--session-dir <dir> Directory for session storage and lookup
|
|
215
233
|
--no-session Don't save session (ephemeral)
|
|
234
|
+
--name, -n <name> Set session display name
|
|
216
235
|
--models <patterns> Comma-separated model patterns for Ctrl+P cycling
|
|
217
236
|
Supports globs (anthropic/*, *sonnet*) and fuzzy matching
|
|
218
237
|
--no-tools, -nt Disable all tools by default (built-in and extension)
|
|
219
238
|
--no-builtin-tools, -nbt Disable built-in tools by default but keep extension/custom tools enabled
|
|
220
239
|
--tools, -t <tools> Comma-separated allowlist of tool names to enable
|
|
221
240
|
Applies to built-in, extension, and custom tools
|
|
241
|
+
--exclude-tools, -xt <tools> Comma-separated denylist of tool names to disable
|
|
242
|
+
Applies to built-in, extension, and custom tools
|
|
222
243
|
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh
|
|
223
244
|
--extension, -e <path> Load an extension file (can be used multiple times)
|
|
224
245
|
--no-extensions, -ne Disable extension discovery (explicit -e paths still work)
|
|
@@ -257,6 +278,9 @@ ${chalk.bold("Examples:")}
|
|
|
257
278
|
# Continue previous session
|
|
258
279
|
${APP_NAME} --continue "What did we discuss?"
|
|
259
280
|
|
|
281
|
+
# Start a named session
|
|
282
|
+
${APP_NAME} --name "Refactor auth module"
|
|
283
|
+
|
|
260
284
|
# Use different model
|
|
261
285
|
${APP_NAME} --provider openai --model gpt-4o-mini "Help me refactor this code"
|
|
262
286
|
|
|
@@ -281,6 +305,9 @@ ${chalk.bold("Examples:")}
|
|
|
281
305
|
# Read-only mode (no file modifications possible)
|
|
282
306
|
${APP_NAME} --tools read,grep,find,ls -p "Review the code in src/"
|
|
283
307
|
|
|
308
|
+
# Disable one tool while keeping the rest available
|
|
309
|
+
${APP_NAME} --exclude-tools ask_question
|
|
310
|
+
|
|
284
311
|
# Export a session file to HTML
|
|
285
312
|
${APP_NAME} --export ~/${CONFIG_DIR_NAME}/agent/sessions/--path--/session.jsonl
|
|
286
313
|
${APP_NAME} --export session.jsonl output.html
|
|
@@ -359,7 +359,15 @@ export function getInteractiveAssetsDir() {
|
|
|
359
359
|
export function getBundledInteractiveAssetPath(name) {
|
|
360
360
|
return join(getInteractiveAssetsDir(), name);
|
|
361
361
|
}
|
|
362
|
-
|
|
362
|
+
let pkg = {};
|
|
363
|
+
try {
|
|
364
|
+
pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
const err = e;
|
|
368
|
+
if (err.code !== "ENOENT")
|
|
369
|
+
throw e;
|
|
370
|
+
}
|
|
363
371
|
const piConfigName = pkg.piConfig?.name;
|
|
364
372
|
export const PACKAGE_NAME = pkg.name || "@earendil-works/pi-coding-agent";
|
|
365
373
|
export const APP_NAME = piConfigName || "pi";
|
|
@@ -110,6 +110,7 @@ export async function createAgentSessionFromServices(options) {
|
|
|
110
110
|
thinkingLevel: options.thinkingLevel,
|
|
111
111
|
scopedModels: options.scopedModels,
|
|
112
112
|
tools: options.tools,
|
|
113
|
+
excludeTools: options.excludeTools,
|
|
113
114
|
noTools: options.noTools,
|
|
114
115
|
customTools: options.customTools,
|
|
115
116
|
sessionStartEvent: options.sessionStartEvent,
|
|
@@ -93,9 +93,11 @@ export class AgentSession {
|
|
|
93
93
|
_extensionRunnerRef;
|
|
94
94
|
_initialActiveToolNames;
|
|
95
95
|
_allowedToolNames;
|
|
96
|
+
_excludedToolNames;
|
|
96
97
|
_baseToolsOverride;
|
|
97
98
|
_sessionStartEvent;
|
|
98
99
|
_extensionUIContext;
|
|
100
|
+
_extensionMode = "print";
|
|
99
101
|
_extensionCommandContextActions;
|
|
100
102
|
_extensionAbortHandler;
|
|
101
103
|
_extensionShutdownHandler;
|
|
@@ -123,6 +125,7 @@ export class AgentSession {
|
|
|
123
125
|
this._extensionRunnerRef = config.extensionRunnerRef;
|
|
124
126
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
125
127
|
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
|
128
|
+
this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
|
|
126
129
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
127
130
|
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
128
131
|
// Always subscribe to agent events for internal handling
|
|
@@ -467,6 +470,16 @@ export class AgentSession {
|
|
|
467
470
|
* Call this when completely done with the session.
|
|
468
471
|
*/
|
|
469
472
|
dispose() {
|
|
473
|
+
try {
|
|
474
|
+
this.abortRetry();
|
|
475
|
+
this.abortCompaction();
|
|
476
|
+
this.abortBranchSummary();
|
|
477
|
+
this.abortBash();
|
|
478
|
+
this.agent.abort();
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
// Dispose must succeed even if an abort hook throws.
|
|
482
|
+
}
|
|
470
483
|
this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
|
|
471
484
|
this._disconnectFromAgent();
|
|
472
485
|
this._eventListeners = [];
|
|
@@ -507,13 +520,14 @@ export class AgentSession {
|
|
|
507
520
|
return this.agent.state.tools.map((t) => t.name);
|
|
508
521
|
}
|
|
509
522
|
/**
|
|
510
|
-
* Get all configured tools with name, description, parameter schema, and source metadata.
|
|
523
|
+
* Get all configured tools with name, description, parameter schema, prompt guidelines, and source metadata.
|
|
511
524
|
*/
|
|
512
525
|
getAllTools() {
|
|
513
526
|
return Array.from(this._toolDefinitions.values()).map(({ definition, sourceInfo }) => ({
|
|
514
527
|
name: definition.name,
|
|
515
528
|
description: definition.description,
|
|
516
529
|
parameters: definition.parameters,
|
|
530
|
+
promptGuidelines: definition.promptGuidelines,
|
|
517
531
|
sourceInfo,
|
|
518
532
|
}));
|
|
519
533
|
}
|
|
@@ -668,7 +682,12 @@ export class AgentSession {
|
|
|
668
682
|
});
|
|
669
683
|
this._retryAttempt = 0;
|
|
670
684
|
}
|
|
671
|
-
|
|
685
|
+
if (await this._checkCompaction(msg)) {
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
// The agent loop drains both queues before emitting agent_end. Any messages
|
|
689
|
+
// here were queued by agent_end extension handlers and need a continuation.
|
|
690
|
+
return this.agent.hasQueuedMessages();
|
|
672
691
|
}
|
|
673
692
|
/**
|
|
674
693
|
* Send a prompt to the agent.
|
|
@@ -698,7 +717,7 @@ export class AgentSession {
|
|
|
698
717
|
let currentText = text;
|
|
699
718
|
let currentImages = options?.images;
|
|
700
719
|
if (this._extensionRunner.hasHandlers("input")) {
|
|
701
|
-
const inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive");
|
|
720
|
+
const inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
|
|
702
721
|
if (inputResult.action === "handled") {
|
|
703
722
|
preflightResult?.(true);
|
|
704
723
|
return;
|
|
@@ -1611,6 +1630,9 @@ export class AgentSession {
|
|
|
1611
1630
|
if (bindings.uiContext !== undefined) {
|
|
1612
1631
|
this._extensionUIContext = bindings.uiContext;
|
|
1613
1632
|
}
|
|
1633
|
+
if (bindings.mode !== undefined) {
|
|
1634
|
+
this._extensionMode = bindings.mode;
|
|
1635
|
+
}
|
|
1614
1636
|
if (bindings.commandContextActions !== undefined) {
|
|
1615
1637
|
this._extensionCommandContextActions = bindings.commandContextActions;
|
|
1616
1638
|
}
|
|
@@ -1668,7 +1690,7 @@ export class AgentSession {
|
|
|
1668
1690
|
return `extension:${name}`;
|
|
1669
1691
|
}
|
|
1670
1692
|
_applyExtensionBindings(runner) {
|
|
1671
|
-
runner.setUIContext(this._extensionUIContext);
|
|
1693
|
+
runner.setUIContext(this._extensionUIContext, this._extensionMode);
|
|
1672
1694
|
runner.bindCommandContext(this._extensionCommandContextActions);
|
|
1673
1695
|
this._extensionErrorUnsubscriber?.();
|
|
1674
1696
|
this._extensionErrorUnsubscriber = this._extensionErrorListener
|
|
@@ -1781,6 +1803,7 @@ export class AgentSession {
|
|
|
1781
1803
|
})();
|
|
1782
1804
|
},
|
|
1783
1805
|
getSystemPrompt: () => this.systemPrompt,
|
|
1806
|
+
getSystemPromptOptions: () => this._baseSystemPromptOptions,
|
|
1784
1807
|
}, {
|
|
1785
1808
|
registerProvider: (name, config) => {
|
|
1786
1809
|
this._modelRegistry.registerProvider(name, config);
|
|
@@ -1796,7 +1819,8 @@ export class AgentSession {
|
|
|
1796
1819
|
const previousRegistryNames = new Set(this._toolRegistry.keys());
|
|
1797
1820
|
const previousActiveToolNames = this.getActiveToolNames();
|
|
1798
1821
|
const allowedToolNames = this._allowedToolNames;
|
|
1799
|
-
const
|
|
1822
|
+
const excludedToolNames = this._excludedToolNames;
|
|
1823
|
+
const isAllowedTool = (name) => (!allowedToolNames || allowedToolNames.has(name)) && !excludedToolNames?.has(name);
|
|
1800
1824
|
const registeredTools = this._extensionRunner.getAllRegisteredTools();
|
|
1801
1825
|
const allCustomTools = [
|
|
1802
1826
|
...registeredTools,
|
|
@@ -92,6 +92,7 @@ export class ExtensionRunner {
|
|
|
92
92
|
extensions;
|
|
93
93
|
runtime;
|
|
94
94
|
uiContext;
|
|
95
|
+
mode = "print";
|
|
95
96
|
cwd;
|
|
96
97
|
sessionManager;
|
|
97
98
|
modelRegistry;
|
|
@@ -105,6 +106,7 @@ export class ExtensionRunner {
|
|
|
105
106
|
getContextUsageFn = () => undefined;
|
|
106
107
|
compactFn = () => { };
|
|
107
108
|
getSystemPromptFn = () => "";
|
|
109
|
+
getSystemPromptOptionsFn = () => ({ cwd: this.cwd });
|
|
108
110
|
newSessionHandler = async () => ({ cancelled: false });
|
|
109
111
|
forkHandler = async () => ({ cancelled: false });
|
|
110
112
|
navigateTreeHandler = async () => ({ cancelled: false });
|
|
@@ -148,6 +150,7 @@ export class ExtensionRunner {
|
|
|
148
150
|
this.getContextUsageFn = contextActions.getContextUsage;
|
|
149
151
|
this.compactFn = contextActions.compact;
|
|
150
152
|
this.getSystemPromptFn = contextActions.getSystemPrompt;
|
|
153
|
+
this.getSystemPromptOptionsFn = contextActions.getSystemPromptOptions ?? (() => ({ cwd: this.cwd }));
|
|
151
154
|
// Flush provider registrations queued during extension loading
|
|
152
155
|
for (const { name, config, extensionPath } of this.runtime.pendingProviderRegistrations) {
|
|
153
156
|
try {
|
|
@@ -202,8 +205,9 @@ export class ExtensionRunner {
|
|
|
202
205
|
this.switchSessionHandler = async () => ({ cancelled: false });
|
|
203
206
|
this.reloadHandler = async () => { };
|
|
204
207
|
}
|
|
205
|
-
setUIContext(uiContext) {
|
|
208
|
+
setUIContext(uiContext, mode = "print") {
|
|
206
209
|
this.uiContext = uiContext ?? noOpUIContext;
|
|
210
|
+
this.mode = mode;
|
|
207
211
|
}
|
|
208
212
|
getUIContext() {
|
|
209
213
|
return this.uiContext;
|
|
@@ -382,6 +386,10 @@ export class ExtensionRunner {
|
|
|
382
386
|
runner.assertActive();
|
|
383
387
|
return runner.uiContext;
|
|
384
388
|
},
|
|
389
|
+
get mode() {
|
|
390
|
+
runner.assertActive();
|
|
391
|
+
return runner.mode;
|
|
392
|
+
},
|
|
385
393
|
get hasUI() {
|
|
386
394
|
runner.assertActive();
|
|
387
395
|
return runner.hasUI();
|
|
@@ -441,6 +449,10 @@ export class ExtensionRunner {
|
|
|
441
449
|
// createContext() stay lazy. A spread would eagerly read them once and freeze the
|
|
442
450
|
// old values into the returned object, bypassing stale-instance checks.
|
|
443
451
|
const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext()));
|
|
452
|
+
context.getSystemPromptOptions = () => {
|
|
453
|
+
this.assertActive();
|
|
454
|
+
return this.getSystemPromptOptionsFn();
|
|
455
|
+
};
|
|
444
456
|
context.waitForIdle = () => {
|
|
445
457
|
this.assertActive();
|
|
446
458
|
return this.waitForIdleFn();
|
|
@@ -790,14 +802,20 @@ export class ExtensionRunner {
|
|
|
790
802
|
return { skillPaths, promptPaths, themePaths };
|
|
791
803
|
}
|
|
792
804
|
/** Emit input event. Transforms chain, "handled" short-circuits. */
|
|
793
|
-
async emitInput(text, images, source) {
|
|
805
|
+
async emitInput(text, images, source, streamingBehavior) {
|
|
794
806
|
const ctx = this.createContext();
|
|
795
807
|
let currentText = text;
|
|
796
808
|
let currentImages = images;
|
|
797
809
|
for (const ext of this.extensions) {
|
|
798
810
|
for (const handler of ext.handlers.get("input") ?? []) {
|
|
799
811
|
try {
|
|
800
|
-
const event = {
|
|
812
|
+
const event = {
|
|
813
|
+
type: "input",
|
|
814
|
+
text: currentText,
|
|
815
|
+
images: currentImages,
|
|
816
|
+
source,
|
|
817
|
+
streamingBehavior,
|
|
818
|
+
};
|
|
801
819
|
const result = (await handler(event, ctx));
|
|
802
820
|
if (result?.action === "handled")
|
|
803
821
|
return result;
|