zidane 6.2.6 → 6.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-CYJvnqPa.js → acp-B_8CI9-1.js} +4 -4
- package/dist/{acp-CYJvnqPa.js.map → acp-B_8CI9-1.js.map} +1 -1
- package/dist/acp-cli.js +7 -7
- package/dist/acp.js +1 -1
- package/dist/{agent-D11SxCvA.js → agent-DPO6GKX5.js} +4 -4
- package/dist/{agent-D11SxCvA.js.map → agent-DPO6GKX5.js.map} +1 -1
- package/dist/agent.js +1 -1
- package/dist/{anthropic-DdlSgT6K.js → anthropic-J2tBjcSQ.js} +3 -3
- package/dist/{anthropic-DdlSgT6K.js.map → anthropic-J2tBjcSQ.js.map} +1 -1
- package/dist/{auth-Ys_7TmRj.js → auth-Cexpxjfa.js} +7 -5
- package/dist/auth-Cexpxjfa.js.map +1 -0
- package/dist/chat.js +3 -3
- package/dist/eval.js +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.js +3 -3
- package/dist/index-CNyYEOy0.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/{messages-Ce4yRjd3.js → messages-DEPX32kc.js} +2 -2
- package/dist/{messages-Ce4yRjd3.js.map → messages-DEPX32kc.js.map} +1 -1
- package/dist/openai-BcmYm0t4.js +579 -0
- package/dist/openai-BcmYm0t4.js.map +1 -0
- package/dist/{openai-compat-TA7aihJ-.js → openai-compat-LGAsBush.js} +25 -1
- package/dist/openai-compat-LGAsBush.js.map +1 -0
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.js +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.js +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.js +1 -1
- package/dist/providers/openai.js +1 -503
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/xai.js +1 -1
- package/dist/{providers-Dln69sQ_.js → providers-BM6VuE_i.js} +3 -3
- package/dist/{providers-Dln69sQ_.js.map → providers-BM6VuE_i.js.map} +1 -1
- package/dist/providers.js +5 -5
- package/dist/{session-D7JSEMME.js → session-Dc1v4Otm.js} +2 -2
- package/dist/{session-D7JSEMME.js.map → session-Dc1v4Otm.js.map} +1 -1
- package/dist/session.js +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-cc4gsU-j.js → transcript-anchors-DtOaKu4d.js} +3 -3
- package/dist/{transcript-anchors-cc4gsU-j.js.map → transcript-anchors-DtOaKu4d.js.map} +1 -1
- package/dist/tui.js +4 -4
- package/dist/{xai-kGdhWmds.js → xai-BYAyEPiJ.js} +2 -2
- package/dist/{xai-kGdhWmds.js.map → xai-BYAyEPiJ.js.map} +1 -1
- package/package.json +1 -1
- package/dist/auth-Ys_7TmRj.js.map +0 -1
- package/dist/openai-compat-TA7aihJ-.js.map +0 -1
- package/dist/providers/openai.js.map +0 -1
package/dist/providers/openai.js
CHANGED
|
@@ -1,504 +1,2 @@
|
|
|
1
|
-
import { i as
|
|
2
|
-
import { c as classifyErrorPrelude, d as isRetryableHttpStatus, f as matchesContextExceeded, p as matchesToolPairingError } from "../errors-DJUxZg9b.js";
|
|
3
|
-
import { _ as userMessage, b as fillEstimatedCost, g as toolResultsMessage, s as assistantMessage, w as unsupportedMediaError, y as sanitizeToolSpecs } from "../openai-compat-TA7aihJ-.js";
|
|
4
|
-
import { renderSystemForWire } from "../prompt.js";
|
|
5
|
-
import { n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER } from "../messages-Ce4yRjd3.js";
|
|
6
|
-
import { i as resolveOAuthApiKey, t as extractRuntimeCredentials } from "../oauth-8LqbeI_Q.js";
|
|
7
|
-
import { stream } from "@earendil-works/pi-ai/api/openai-codex-responses";
|
|
8
|
-
import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
|
|
9
|
-
//#region src/providers/openai.ts
|
|
10
|
-
const PROVIDER_ID = "openai-codex";
|
|
11
|
-
const DEFAULT_MODEL = "gpt-5.5";
|
|
12
|
-
const lookupModel = getBuiltinModel;
|
|
13
|
-
function resolveModel(modelId) {
|
|
14
|
-
const model = lookupModel(PROVIDER_ID, modelId);
|
|
15
|
-
if (model) return model;
|
|
16
|
-
const fallback = lookupModel(PROVIDER_ID, DEFAULT_MODEL);
|
|
17
|
-
if (!fallback) throw new Error(`OpenAI Codex model registry is missing the default model: ${DEFAULT_MODEL}`);
|
|
18
|
-
return {
|
|
19
|
-
...fallback,
|
|
20
|
-
id: modelId,
|
|
21
|
-
name: modelId
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function emptyUsage() {
|
|
25
|
-
return {
|
|
26
|
-
input: 0,
|
|
27
|
-
output: 0,
|
|
28
|
-
cacheRead: 0,
|
|
29
|
-
cacheWrite: 0,
|
|
30
|
-
totalTokens: 0,
|
|
31
|
-
cost: {
|
|
32
|
-
input: 0,
|
|
33
|
-
output: 0,
|
|
34
|
-
cacheRead: 0,
|
|
35
|
-
cacheWrite: 0,
|
|
36
|
-
total: 0
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function formatTools(tools) {
|
|
41
|
-
return sanitizeToolSpecs(tools, { profile: "openai" }).map((t) => ({
|
|
42
|
-
name: t.name,
|
|
43
|
-
description: t.description,
|
|
44
|
-
parameters: t.inputSchema
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
function documentMarker(doc) {
|
|
48
|
-
return documentBlockMarker(doc, "document omitted");
|
|
49
|
-
}
|
|
50
|
-
function piToolResultMessage(result, toolName = "") {
|
|
51
|
-
const content = typeof result.output === "string" ? [{
|
|
52
|
-
type: "text",
|
|
53
|
-
text: result.output
|
|
54
|
-
}] : result.output.map((block) => {
|
|
55
|
-
if (block.type === "image") {
|
|
56
|
-
assertResolvedMediaBlock(block, "OpenAI wire messages");
|
|
57
|
-
return {
|
|
58
|
-
type: "image",
|
|
59
|
-
data: block.data,
|
|
60
|
-
mimeType: block.mediaType
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (block.type === "audio") throw unsupportedMediaError("audio", "openai");
|
|
64
|
-
if (block.type === "video") throw unsupportedMediaError("video", "openai");
|
|
65
|
-
if (block.type === "document") {
|
|
66
|
-
assertResolvedMediaBlock(block, "OpenAI wire messages");
|
|
67
|
-
return {
|
|
68
|
-
type: "text",
|
|
69
|
-
text: documentMarker(block)
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
type: "text",
|
|
74
|
-
text: block.text
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
return {
|
|
78
|
-
role: "toolResult",
|
|
79
|
-
toolCallId: result.callId,
|
|
80
|
-
toolName,
|
|
81
|
-
content,
|
|
82
|
-
isError: result.isError ?? false,
|
|
83
|
-
timestamp: Date.now()
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
function piUserMessage(content) {
|
|
87
|
-
for (const b of content) {
|
|
88
|
-
if (b.type === "audio") throw unsupportedMediaError("audio", "openai");
|
|
89
|
-
if (b.type === "video") throw unsupportedMediaError("video", "openai");
|
|
90
|
-
}
|
|
91
|
-
const textBlocks = content.filter((b) => b.type === "text");
|
|
92
|
-
const imageBlocks = content.filter((b) => b.type === "image");
|
|
93
|
-
const documentBlocks = content.filter((b) => b.type === "document");
|
|
94
|
-
if (imageBlocks.length === 0 && textBlocks.length === 0 && documentBlocks.length === 0) return null;
|
|
95
|
-
if (imageBlocks.length === 0 && documentBlocks.length === 0 && textBlocks.length === 1) return {
|
|
96
|
-
role: "user",
|
|
97
|
-
content: textBlocks[0].text,
|
|
98
|
-
timestamp: Date.now()
|
|
99
|
-
};
|
|
100
|
-
return {
|
|
101
|
-
role: "user",
|
|
102
|
-
content: [
|
|
103
|
-
...imageBlocks.map((img) => {
|
|
104
|
-
assertResolvedMediaBlock(img, "OpenAI wire messages");
|
|
105
|
-
return {
|
|
106
|
-
type: "image",
|
|
107
|
-
data: img.data,
|
|
108
|
-
mimeType: img.mediaType
|
|
109
|
-
};
|
|
110
|
-
}),
|
|
111
|
-
...documentBlocks.map((block) => {
|
|
112
|
-
assertResolvedMediaBlock(block, "OpenAI wire messages");
|
|
113
|
-
return {
|
|
114
|
-
type: "text",
|
|
115
|
-
text: documentMarker(block)
|
|
116
|
-
};
|
|
117
|
-
}),
|
|
118
|
-
...textBlocks.map((block) => ({
|
|
119
|
-
type: "text",
|
|
120
|
-
text: block.text
|
|
121
|
-
}))
|
|
122
|
-
],
|
|
123
|
-
timestamp: Date.now()
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function piAssistantMessage(content, modelId) {
|
|
127
|
-
const piContent = [];
|
|
128
|
-
for (const block of content) if (block.type === "text") piContent.push({
|
|
129
|
-
type: "text",
|
|
130
|
-
text: block.text
|
|
131
|
-
});
|
|
132
|
-
else if (block.type === "thinking") {
|
|
133
|
-
if (block.signatureProducer === "anthropic") continue;
|
|
134
|
-
piContent.push({
|
|
135
|
-
type: "thinking",
|
|
136
|
-
thinking: block.text,
|
|
137
|
-
thinkingSignature: block.signature
|
|
138
|
-
});
|
|
139
|
-
} else if (block.type === "tool_call") piContent.push({
|
|
140
|
-
type: "toolCall",
|
|
141
|
-
id: block.id,
|
|
142
|
-
name: block.name,
|
|
143
|
-
arguments: block.input
|
|
144
|
-
});
|
|
145
|
-
return {
|
|
146
|
-
role: "assistant",
|
|
147
|
-
content: piContent,
|
|
148
|
-
api: "openai-codex-responses",
|
|
149
|
-
provider: PROVIDER_ID,
|
|
150
|
-
model: modelId,
|
|
151
|
-
usage: emptyUsage(),
|
|
152
|
-
stopReason: "stop",
|
|
153
|
-
timestamp: Date.now()
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
function toPiMessages(messages, modelId) {
|
|
157
|
-
const out = [];
|
|
158
|
-
for (let i = 0; i < messages.length; i++) {
|
|
159
|
-
const msg = messages[i];
|
|
160
|
-
const toolCalls = msg.content.filter((b) => b.type === "tool_call");
|
|
161
|
-
if (msg.role === "assistant" && toolCalls.length > 0) {
|
|
162
|
-
const next = messages[i + 1];
|
|
163
|
-
const nextToolResults = next?.role === "user" ? next.content.filter((b) => b.type === "tool_result") : [];
|
|
164
|
-
const resultById = new Map(nextToolResults.map((result) => [result.callId, result]));
|
|
165
|
-
let assistantContent = [];
|
|
166
|
-
for (const block of msg.content) {
|
|
167
|
-
assistantContent.push(block);
|
|
168
|
-
if (block.type !== "tool_call") continue;
|
|
169
|
-
out.push(piAssistantMessage(assistantContent, modelId));
|
|
170
|
-
assistantContent = [];
|
|
171
|
-
const result = resultById.get(block.id);
|
|
172
|
-
if (result) out.push(piToolResultMessage(result, block.name));
|
|
173
|
-
else out.push(piToolResultMessage({
|
|
174
|
-
type: "tool_result",
|
|
175
|
-
callId: block.id,
|
|
176
|
-
output: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
|
|
177
|
-
isError: true
|
|
178
|
-
}, block.name));
|
|
179
|
-
}
|
|
180
|
-
if (assistantContent.length > 0) out.push(piAssistantMessage(assistantContent, modelId));
|
|
181
|
-
if (next?.role === "user") {
|
|
182
|
-
const userMessage = piUserMessage(next.content.filter((block) => {
|
|
183
|
-
if (block.type !== "tool_result") return true;
|
|
184
|
-
return false;
|
|
185
|
-
}));
|
|
186
|
-
if (userMessage) out.push(userMessage);
|
|
187
|
-
i++;
|
|
188
|
-
}
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (msg.content.filter((b) => b.type === "tool_result").length > 0) {
|
|
192
|
-
const userMsg = msg.role === "user" ? piUserMessage(msg.content.filter((b) => b.type !== "tool_result")) : null;
|
|
193
|
-
if (userMsg) out.push(userMsg);
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (msg.role === "user") {
|
|
197
|
-
const userMsg = piUserMessage(msg.content);
|
|
198
|
-
if (userMsg) out.push(userMsg);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
out.push(piAssistantMessage(msg.content, modelId));
|
|
202
|
-
}
|
|
203
|
-
return out;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Map pi-ai's `StopReason` to the zidane `TurnFinishReason` union — the same
|
|
207
|
-
* native-stop-reason fidelity anthropic's `mapStopReason` / openai-compat's
|
|
208
|
-
* `mapOAIFinishReason` provide. Notably `length` (max-token truncation) is no
|
|
209
|
-
* longer misreported as a clean `stop`. `hasToolCalls` is the fallback for
|
|
210
|
-
* messages whose stop reason is missing or doesn't carry the tool-call signal.
|
|
211
|
-
*/
|
|
212
|
-
function mapCodexStopReason(stopReason, hasToolCalls) {
|
|
213
|
-
switch (stopReason) {
|
|
214
|
-
case "toolUse": return "tool-calls";
|
|
215
|
-
case "length": return "length";
|
|
216
|
-
case "error": return "error";
|
|
217
|
-
case "stop": return hasToolCalls ? "tool-calls" : "stop";
|
|
218
|
-
case "aborted": return "other";
|
|
219
|
-
default: return hasToolCalls ? "tool-calls" : "stop";
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
function fromPiAssistantMessage(message) {
|
|
223
|
-
const content = [];
|
|
224
|
-
for (const block of message.content) if (block.type === "text") content.push({
|
|
225
|
-
type: "text",
|
|
226
|
-
text: block.text
|
|
227
|
-
});
|
|
228
|
-
else if (block.type === "thinking") {
|
|
229
|
-
const out = {
|
|
230
|
-
type: "thinking",
|
|
231
|
-
text: block.thinking
|
|
232
|
-
};
|
|
233
|
-
if (typeof block.thinkingSignature === "string") {
|
|
234
|
-
out.signature = block.thinkingSignature;
|
|
235
|
-
out.signatureProducer = "openai";
|
|
236
|
-
}
|
|
237
|
-
content.push(out);
|
|
238
|
-
} else if (block.type === "toolCall") content.push({
|
|
239
|
-
type: "tool_call",
|
|
240
|
-
id: block.id,
|
|
241
|
-
name: block.name,
|
|
242
|
-
input: block.arguments
|
|
243
|
-
});
|
|
244
|
-
return {
|
|
245
|
-
role: "assistant",
|
|
246
|
-
content
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
function extractToolCalls(message) {
|
|
250
|
-
return message.content.filter((block) => block.type === "toolCall").map((block) => ({
|
|
251
|
-
id: block.id,
|
|
252
|
-
name: block.name,
|
|
253
|
-
input: block.arguments
|
|
254
|
-
}));
|
|
255
|
-
}
|
|
256
|
-
function extractText(message) {
|
|
257
|
-
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
258
|
-
}
|
|
259
|
-
function toTurnUsage(usage, finishReason, modelId) {
|
|
260
|
-
return fillEstimatedCost({
|
|
261
|
-
input: usage.input,
|
|
262
|
-
output: usage.output,
|
|
263
|
-
cacheRead: usage.cacheRead || void 0,
|
|
264
|
-
cacheCreation: usage.cacheWrite || void 0,
|
|
265
|
-
cost: usage.cost.total || void 0,
|
|
266
|
-
...finishReason ? { finishReason } : {},
|
|
267
|
-
modelId
|
|
268
|
-
}, "openai");
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Transient pi-ai error patterns worth a loop-level retry. pi-ai already retries
|
|
272
|
-
* 429 / 5xx pre-stream 3× internally (see `openai-codex-responses.js`); by the
|
|
273
|
-
* time the error reaches us, that budget is exhausted. The loop-level retry
|
|
274
|
-
* with its longer backoff is a second line of defense for capacity events
|
|
275
|
-
* that persist past pi-ai's short retry window, plus mid-stream emissions
|
|
276
|
-
* (pi-ai surfaces those as plain `Error`s with no status).
|
|
277
|
-
*
|
|
278
|
-
* Mirrors pi-ai's own `isRetryableError` regex so the two retry layers
|
|
279
|
-
* recognize the same failure modes.
|
|
280
|
-
*/
|
|
281
|
-
const TRANSIENT_OPENAI_MESSAGE_RE = /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused|gateway.?time.?out|temporarily.?unavailable/i;
|
|
282
|
-
/** Numeric HTTP status codes pi-ai sometimes attaches when bubbling structured errors. */
|
|
283
|
-
function isRetryableStatusCode(err) {
|
|
284
|
-
const status = err.status;
|
|
285
|
-
if (typeof status !== "number") return false;
|
|
286
|
-
return isRetryableHttpStatus(status);
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Classify an OpenAI Codex error. pi-ai surfaces errors either as thrown `Error`s
|
|
290
|
-
* (wrapping `event.error.errorMessage`) or via stream event types.
|
|
291
|
-
*
|
|
292
|
-
* Retryable hint is set when pi-ai's own retry budget is exhausted on a
|
|
293
|
-
* transient failure — pattern-matched against `message` since pi-ai strips
|
|
294
|
-
* structured fields on plain-Error throws. Set `behavior.retry.maxAttempts: 1`
|
|
295
|
-
* to disable the loop-level retry.
|
|
296
|
-
*/
|
|
297
|
-
function classifyOpenAIError(err) {
|
|
298
|
-
const prelude = classifyErrorPrelude(err);
|
|
299
|
-
if (prelude === "not-object") return null;
|
|
300
|
-
if (prelude === "aborted") return { kind: "aborted" };
|
|
301
|
-
const anyErr = err;
|
|
302
|
-
const message = anyErr.message ?? "";
|
|
303
|
-
const code = anyErr.code ?? anyErr.type;
|
|
304
|
-
if (code === "context_length_exceeded" || matchesContextExceeded(message)) return {
|
|
305
|
-
kind: "context_exceeded",
|
|
306
|
-
providerCode: code ?? "context_length_exceeded",
|
|
307
|
-
message
|
|
308
|
-
};
|
|
309
|
-
if (matchesToolPairingError(message)) return {
|
|
310
|
-
kind: "tool_pairing_corruption",
|
|
311
|
-
providerCode: code ?? "invalid_request_error",
|
|
312
|
-
message
|
|
313
|
-
};
|
|
314
|
-
if (message.length > 0) return {
|
|
315
|
-
kind: "provider_error",
|
|
316
|
-
providerCode: code,
|
|
317
|
-
message,
|
|
318
|
-
...isRetryableStatusCode(anyErr) || TRANSIENT_OPENAI_MESSAGE_RE.test(message) ? { retryable: true } : {}
|
|
319
|
-
};
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* A standard OpenAI API key usable against `api.openai.com` (for the exact
|
|
324
|
-
* token-count endpoint), or `null` when only a Codex-OAuth credential is
|
|
325
|
-
* available. Codex OAuth access tokens are JWTs (`eyJ…`) and are NOT valid
|
|
326
|
-
* bearers for the standard API; a real key looks like `sk-…` (but not the
|
|
327
|
-
* Anthropic `sk-ant-…` form). Checks `params.apiKey` then `OPENAI_API_KEY`.
|
|
328
|
-
*/
|
|
329
|
-
function pickStandardOpenAIKey(params) {
|
|
330
|
-
const candidates = [params?.apiKey, process.env.OPENAI_API_KEY];
|
|
331
|
-
for (const key of candidates) if (typeof key === "string" && key.startsWith("sk-") && !key.startsWith("sk-ant-")) return key;
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Convert canonical {@link SessionMessage}s to OpenAI Responses-API `input`
|
|
336
|
-
* items for the token-count endpoint. Only the text-bearing shape is needed
|
|
337
|
-
* here (the count path sends a tiny dummy message); images/tool-results are
|
|
338
|
-
* down-converted to text so the count never throws on an exotic block.
|
|
339
|
-
*/
|
|
340
|
-
function toResponsesInput(messages) {
|
|
341
|
-
return messages.map((msg) => {
|
|
342
|
-
const text = msg.content.map((b) => b.type === "text" ? b.text : "").filter(Boolean).join("\n");
|
|
343
|
-
const part = msg.role === "assistant" ? "output_text" : "input_text";
|
|
344
|
-
return {
|
|
345
|
-
role: msg.role === "assistant" ? "assistant" : "user",
|
|
346
|
-
content: [{
|
|
347
|
-
type: part,
|
|
348
|
-
text
|
|
349
|
-
}]
|
|
350
|
-
};
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
/** Convert pi-ai `PiTool` specs to Responses-API function tools. */
|
|
354
|
-
function toResponsesTools(tools) {
|
|
355
|
-
return tools.map((t) => {
|
|
356
|
-
const tool = t;
|
|
357
|
-
return {
|
|
358
|
-
type: "function",
|
|
359
|
-
name: tool.name,
|
|
360
|
-
description: tool.description,
|
|
361
|
-
parameters: tool.parameters
|
|
362
|
-
};
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
function applyPayloadOverrides(payload, options) {
|
|
366
|
-
const body = payload;
|
|
367
|
-
if (options.toolChoice) if (options.toolChoice.type === "tool" && options.toolChoice.name) body.tool_choice = {
|
|
368
|
-
type: "function",
|
|
369
|
-
name: options.toolChoice.name
|
|
370
|
-
};
|
|
371
|
-
else if (options.toolChoice.type === "required") body.tool_choice = "required";
|
|
372
|
-
else body.tool_choice = "auto";
|
|
373
|
-
return body;
|
|
374
|
-
}
|
|
375
|
-
function openai(params) {
|
|
376
|
-
const defaultModel = params?.defaultModel || DEFAULT_MODEL;
|
|
377
|
-
const baseCredentials = extractRuntimeCredentials(params);
|
|
378
|
-
let runtimeCredentials = baseCredentials ? {
|
|
379
|
-
...baseCredentials,
|
|
380
|
-
...params?.accountId ? { accountId: params.accountId } : {}
|
|
381
|
-
} : void 0;
|
|
382
|
-
return {
|
|
383
|
-
name: "openai",
|
|
384
|
-
meta: {
|
|
385
|
-
defaultModel,
|
|
386
|
-
isOAuth: true,
|
|
387
|
-
capabilities: {
|
|
388
|
-
vision: true,
|
|
389
|
-
imageInToolResult: true,
|
|
390
|
-
audio: false,
|
|
391
|
-
video: false
|
|
392
|
-
}
|
|
393
|
-
},
|
|
394
|
-
formatTools,
|
|
395
|
-
userMessage,
|
|
396
|
-
assistantMessage,
|
|
397
|
-
toolResultsMessage,
|
|
398
|
-
classifyError: classifyOpenAIError,
|
|
399
|
-
async countTokens(payload, signal) {
|
|
400
|
-
const standardKey = pickStandardOpenAIKey(params);
|
|
401
|
-
if (!standardKey) return null;
|
|
402
|
-
try {
|
|
403
|
-
const res = await fetch("https://api.openai.com/v1/responses/input_tokens", {
|
|
404
|
-
method: "POST",
|
|
405
|
-
headers: {
|
|
406
|
-
...params?.extraHeaders,
|
|
407
|
-
"authorization": `Bearer ${standardKey}`,
|
|
408
|
-
"content-type": "application/json"
|
|
409
|
-
},
|
|
410
|
-
body: JSON.stringify({
|
|
411
|
-
model: payload.model || defaultModel,
|
|
412
|
-
instructions: renderSystemForWire(payload.system),
|
|
413
|
-
input: toResponsesInput(payload.messages),
|
|
414
|
-
tools: toResponsesTools(payload.tools)
|
|
415
|
-
}),
|
|
416
|
-
...signal ? { signal } : {}
|
|
417
|
-
});
|
|
418
|
-
if (!res.ok) return null;
|
|
419
|
-
const json = await res.json();
|
|
420
|
-
return typeof json.input_tokens === "number" ? json.input_tokens : null;
|
|
421
|
-
} catch {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
async stream(options, callbacks) {
|
|
426
|
-
const modelId = options.model || defaultModel;
|
|
427
|
-
const model = resolveModel(modelId);
|
|
428
|
-
const apiKey = await resolveOAuthApiKey({
|
|
429
|
-
provider: "openai",
|
|
430
|
-
providerId: PROVIDER_ID,
|
|
431
|
-
params: runtimeCredentials ? {
|
|
432
|
-
...params,
|
|
433
|
-
...runtimeCredentials
|
|
434
|
-
} : params,
|
|
435
|
-
envKey: "OPENAI_CODEX_API_KEY",
|
|
436
|
-
extraCredentialKeys: ["accountId"],
|
|
437
|
-
missingError: "No OpenAI Codex OAuth token found. Run `bun run auth --openai` first.",
|
|
438
|
-
refreshError: (reason) => `OpenAI Codex OAuth token refresh failed. Run \`bun run auth --openai\` again. ${reason}`
|
|
439
|
-
}, {
|
|
440
|
-
...callbacks,
|
|
441
|
-
async onOAuthRefresh(ctx) {
|
|
442
|
-
if (ctx.source === "params") runtimeCredentials = {
|
|
443
|
-
access: ctx.credentials.access,
|
|
444
|
-
refresh: ctx.credentials.refresh,
|
|
445
|
-
expires: ctx.credentials.expires,
|
|
446
|
-
...typeof ctx.credentials.accountId === "string" ? { accountId: ctx.credentials.accountId } : {}
|
|
447
|
-
};
|
|
448
|
-
await callbacks.onOAuthRefresh?.(ctx);
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
const context = {
|
|
452
|
-
systemPrompt: renderSystemForWire(options.system),
|
|
453
|
-
messages: toPiMessages(options.messages, modelId),
|
|
454
|
-
tools: options.tools
|
|
455
|
-
};
|
|
456
|
-
const reasoningLevel = options.thinking && options.thinking !== "off" && options.thinking !== "adaptive" ? options.thinking === "max" ? "xhigh" : options.thinking : void 0;
|
|
457
|
-
const stream$1 = stream(model, context, {
|
|
458
|
-
apiKey,
|
|
459
|
-
...options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {},
|
|
460
|
-
signal: options.signal,
|
|
461
|
-
transport: params?.transport,
|
|
462
|
-
reasoningEffort: reasoningLevel,
|
|
463
|
-
reasoningSummary: reasoningLevel ? "auto" : void 0,
|
|
464
|
-
...options.modelOptions?.fast ? { serviceTier: "priority" } : {},
|
|
465
|
-
...params?.extraHeaders ? { headers: params.extraHeaders } : {},
|
|
466
|
-
onPayload: (payload) => applyPayloadOverrides(payload, options)
|
|
467
|
-
});
|
|
468
|
-
let finalMessage;
|
|
469
|
-
let text = "";
|
|
470
|
-
let thinking = "";
|
|
471
|
-
for await (const event of stream$1) if (event.type === "text_delta") {
|
|
472
|
-
text += event.delta;
|
|
473
|
-
callbacks.onText(event.delta);
|
|
474
|
-
} else if (event.type === "thinking_delta") {
|
|
475
|
-
thinking += event.delta;
|
|
476
|
-
callbacks.onThinking?.(event.delta);
|
|
477
|
-
} else if (event.type === "thinking_end") {
|
|
478
|
-
const delta = event.content.startsWith(thinking) ? event.content.slice(thinking.length) : thinking ? "" : event.content;
|
|
479
|
-
if (delta) {
|
|
480
|
-
thinking += delta;
|
|
481
|
-
callbacks.onThinking?.(delta);
|
|
482
|
-
}
|
|
483
|
-
} else if (event.type === "done") finalMessage = event.message;
|
|
484
|
-
else if (event.type === "error") throw new Error(event.error.errorMessage || "OpenAI Codex API error");
|
|
485
|
-
finalMessage ??= await stream$1.result();
|
|
486
|
-
text ||= extractText(finalMessage);
|
|
487
|
-
const toolCalls = extractToolCalls(finalMessage);
|
|
488
|
-
const assistantTurn = fromPiAssistantMessage(finalMessage);
|
|
489
|
-
const finishReason = mapCodexStopReason(finalMessage.stopReason, toolCalls.length > 0);
|
|
490
|
-
const usage = await toTurnUsage(finalMessage.usage, finishReason, modelId);
|
|
491
|
-
return {
|
|
492
|
-
assistantMessage: assistantTurn,
|
|
493
|
-
text,
|
|
494
|
-
toolCalls,
|
|
495
|
-
done: toolCalls.length === 0,
|
|
496
|
-
usage
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
//#endregion
|
|
1
|
+
import { i as toPiMessages, n as mapCodexStopReason, r as openai, t as classifyOpenAIError } from "../openai-BcmYm0t4.js";
|
|
502
2
|
export { classifyOpenAIError, mapCodexStopReason, openai, toPiMessages };
|
|
503
|
-
|
|
504
|
-
//# sourceMappingURL=openai.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as openaiCompat } from "../openai-compat-
|
|
1
|
+
import { m as openaiCompat } from "../openai-compat-LGAsBush.js";
|
|
2
2
|
//#region src/providers/openrouter.ts
|
|
3
3
|
const BASE_URL = "https://openrouter.ai/api/v1";
|
|
4
4
|
const DEFAULT_MODEL = "anthropic/claude-sonnet-4.6";
|
package/dist/providers/xai.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as xai } from "../xai-
|
|
1
|
+
import { t as xai } from "../xai-BYAyEPiJ.js";
|
|
2
2
|
export { xai };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { m as openaiCompat } from "./openai-compat-
|
|
1
|
+
import { m as openaiCompat } from "./openai-compat-LGAsBush.js";
|
|
2
2
|
import { i as resolveOAuthApiKey } from "./oauth-8LqbeI_Q.js";
|
|
3
|
-
import { r as generatePkce } from "./xai-
|
|
3
|
+
import { r as generatePkce } from "./xai-BYAyEPiJ.js";
|
|
4
4
|
//#region src/chat/oauth-page/cursor.ts
|
|
5
5
|
/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */
|
|
6
6
|
const CURSOR_OAUTH_PROVIDER_ID = "cursor";
|
|
@@ -165,4 +165,4 @@ function cursor(params) {
|
|
|
165
165
|
//#endregion
|
|
166
166
|
export { createCursorOAuthProvider as n, cursor as t };
|
|
167
167
|
|
|
168
|
-
//# sourceMappingURL=providers-
|
|
168
|
+
//# sourceMappingURL=providers-BM6VuE_i.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers-Dln69sQ_.js","names":[],"sources":["../src/chat/oauth-page/cursor.ts","../src/providers/cursor.ts"],"sourcesContent":["/**\n * Cursor OAuth flow.\n *\n * Unlike Anthropic / OpenAI Codex (loopback-redirect PKCE), Cursor uses a\n * **poll-based** PKCE flow with no local callback server:\n *\n * 1. Generate a PKCE verifier + challenge and a random UUID.\n * 2. Open the browser to `cursor.com/loginDeepControl?challenge&uuid&mode&redirectTarget`.\n * 3. Poll `api2.cursor.sh/auth/poll?uuid&verifier` until tokens come back\n * (HTTP 404 = \"not ready yet\", keep polling with backoff).\n * 4. Refresh via `POST api2.cursor.sh/auth/exchange_user_api_key` with\n * `Authorization: Bearer <refresh>`.\n *\n * Token expiry is derived from the access token's JWT `exp` claim (minus a\n * 5-minute safety margin), falling back to +1h when the claim is missing.\n *\n * Flow adapted from https://github.com/ndraiman/pi-cursor-provider, itself\n * derived from https://github.com/ephraimduncan/opencode-cursor.\n *\n * Cursor's auth + inference protocol is unofficial and undocumented — re-verify\n * the endpoints/params below if login starts failing.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderInterface,\n} from '@earendil-works/pi-ai/oauth'\nimport { generatePkce } from './pkce'\n\n/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */\nexport const CURSOR_OAUTH_PROVIDER_ID = 'cursor'\n\nconst CURSOR_LOGIN_URL = 'https://cursor.com/loginDeepControl'\nconst CURSOR_POLL_URL = 'https://api2.cursor.sh/auth/poll'\nconst CURSOR_REFRESH_URL = 'https://api2.cursor.sh/auth/exchange_user_api_key'\n\nconst POLL_MAX_ATTEMPTS = 150\nconst POLL_BASE_DELAY_MS = 1000\nconst POLL_MAX_DELAY_MS = 10_000\nconst POLL_BACKOFF_MULTIPLIER = 1.2\nconst POLL_MAX_CONSECUTIVE_ERRORS = 3\n\n/** Fallback lifetime when the JWT carries no `exp`. */\nconst DEFAULT_TOKEN_TTL_MS = 3600 * 1000\n/** Refresh slightly early so requests don't race expiry. */\nconst TOKEN_EXPIRY_SKEW_MS = 5 * 60 * 1000\n\ninterface CursorTokenResponse {\n accessToken: string\n refreshToken: string\n}\n\n/**\n * Derive expiry (epoch ms) from a JWT's `exp` claim, minus a safety skew.\n * Returns `now + 1h` for malformed tokens so a missing claim never wedges\n * refresh into an immediate-expire loop.\n */\nexport function cursorTokenExpiry(token: string): number {\n try {\n const parts = token.split('.')\n const payload = parts[1]\n if (parts.length !== 3 || !payload)\n return Date.now() + DEFAULT_TOKEN_TTL_MS\n\n const json = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))) as { exp?: unknown }\n if (typeof json.exp === 'number')\n return json.exp * 1000 - TOKEN_EXPIRY_SKEW_MS\n }\n catch {\n // fall through to default\n }\n return Date.now() + DEFAULT_TOKEN_TTL_MS\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms))\n}\n\n/**\n * Poll Cursor's auth endpoint until login completes. `404` means the user\n * hasn't finished the browser flow yet; anything else non-2xx is a hard error.\n */\nasync function pollForTokens(\n uuid: string,\n verifier: string,\n callbacks: OAuthLoginCallbacks,\n): Promise<CursorTokenResponse> {\n let wait = POLL_BASE_DELAY_MS\n let consecutiveErrors = 0\n\n for (let attempt = 0; attempt < POLL_MAX_ATTEMPTS; attempt++) {\n if (callbacks.signal?.aborted)\n throw new Error('Cursor login aborted')\n\n await delay(wait)\n\n try {\n const url = `${CURSOR_POLL_URL}?uuid=${encodeURIComponent(uuid)}&verifier=${encodeURIComponent(verifier)}`\n const response = await fetch(url, { signal: callbacks.signal })\n\n if (response.status === 404) {\n consecutiveErrors = 0\n wait = Math.min(wait * POLL_BACKOFF_MULTIPLIER, POLL_MAX_DELAY_MS)\n continue\n }\n\n if (response.ok) {\n const data = await response.json() as Partial<CursorTokenResponse>\n if (!data.accessToken || !data.refreshToken)\n throw new Error(`Cursor poll response missing tokens: ${JSON.stringify(data)}`)\n return { accessToken: data.accessToken, refreshToken: data.refreshToken }\n }\n\n throw new Error(`Cursor poll failed (${response.status})`)\n }\n catch (err) {\n if (callbacks.signal?.aborted)\n throw new Error('Cursor login aborted')\n consecutiveErrors++\n if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS)\n throw err instanceof Error ? err : new Error(String(err))\n callbacks.onProgress?.('Waiting for Cursor login to complete…')\n }\n }\n\n throw new Error('Cursor authentication timed out')\n}\n\nexport async function loginCursor(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const { verifier, challenge } = await generatePkce()\n const uuid = crypto.randomUUID()\n\n const authUrl = new URL(CURSOR_LOGIN_URL)\n authUrl.searchParams.set('challenge', challenge)\n authUrl.searchParams.set('uuid', uuid)\n authUrl.searchParams.set('mode', 'login')\n authUrl.searchParams.set('redirectTarget', 'cli')\n\n callbacks.onAuth({\n url: authUrl.toString(),\n instructions: 'A browser window should open. Complete login in Cursor to finish.',\n })\n\n const tokens = await pollForTokens(uuid, verifier, callbacks)\n\n return {\n access: tokens.accessToken,\n refresh: tokens.refreshToken,\n expires: cursorTokenExpiry(tokens.accessToken),\n }\n}\n\nexport async function refreshCursorToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const response = await fetch(CURSOR_REFRESH_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${credentials.refresh}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`Cursor token refresh failed (${response.status}): ${text || response.statusText}`)\n }\n\n const data = await response.json() as Partial<CursorTokenResponse>\n if (!data.accessToken)\n throw new Error(`Cursor token refresh response missing access token: ${JSON.stringify(data)}`)\n\n return {\n access: data.accessToken,\n // Cursor may omit a rotated refresh token — keep the existing one.\n refresh: data.refreshToken || credentials.refresh,\n expires: cursorTokenExpiry(data.accessToken),\n }\n}\n\n/**\n * Build a Cursor `OAuthProviderInterface`. Shape matches Anthropic / Codex so\n * it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.\n *\n * No `renderPage` parameter: Cursor's poll flow has no local callback server,\n * so there is no post-redirect page to theme.\n */\nexport function createCursorOAuthProvider(): OAuthProviderInterface {\n return {\n id: CURSOR_OAUTH_PROVIDER_ID,\n name: 'Cursor',\n usesCallbackServer: false,\n login: loginCursor,\n refreshToken: refreshCursorToken,\n getApiKey: credentials => credentials.access,\n }\n}\n","import type { Provider, StreamCallbacks } from '.'\nimport { CURSOR_OAUTH_PROVIDER_ID } from '../chat/oauth-page/cursor'\nimport { resolveOAuthApiKey } from './oauth'\nimport { openaiCompat } from './openai-compat'\n\n/**\n * Cursor provider (OAuth).\n *\n * Auth is fully wired: `bun run auth --cursor` (or the TUI wizard) logs in via\n * Cursor's poll-based PKCE flow and persists credentials; this provider resolves\n * + refreshes the OAuth token on every turn via {@link resolveOAuthApiKey}.\n *\n * Inference is **not** wired yet. Cursor does not expose an OpenAI-compatible\n * HTTP endpoint — it speaks a protobuf/Connect agent protocol over HTTP/2 to\n * `api2.cursor.sh`. Implementing `stream()` requires porting that transport\n * (generated protobuf schemas + an HTTP/2 streaming client) in-process, which\n * is intentionally left as a follow-up. Until then `stream()` resolves the\n * token (proving auth works) and then throws a clear, actionable error.\n *\n * The `openaiCompat` base supplies the message-format helpers (`formatTools`,\n * `userMessage`, `toolResultsMessage`, …) so the provider satisfies the full\n * `Provider` contract and slots into the registry; only `stream` is overridden.\n */\nexport interface CursorParams {\n /** Bypass OAuth resolution with an explicit token (mainly for tests). */\n apiKey?: string\n defaultModel?: string\n /**\n * Extra HTTP headers for the eventual Cursor transport. Accepted for API\n * parity with the other providers and forwarded to the openai-compat base,\n * but inert until Cursor inference is implemented (the `stream` override\n * currently throws before any request is made).\n */\n extraHeaders?: Record<string, string>\n}\n\nconst DEFAULT_MODEL = 'claude-4.6-sonnet'\n\nconst NOT_IMPLEMENTED_MESSAGE\n = 'Cursor OAuth login works, but inference over Cursor is not implemented yet. '\n + 'Cursor uses a protobuf/HTTP-2 agent protocol (not an OpenAI-compatible API), '\n + 'so the streaming transport still needs to be ported. Use another provider for now.'\n\nexport function cursor(params?: CursorParams): Provider {\n const defaultModel = params?.defaultModel || DEFAULT_MODEL\n\n // Base only provides the format/serialization methods; `baseURL` is never\n // hit because we override `stream` below.\n const base = openaiCompat({\n name: 'cursor',\n apiKey: params?.apiKey ?? 'oauth',\n baseURL: 'https://api2.cursor.sh',\n defaultModel,\n capabilities: { vision: true, imageInToolResult: false },\n extraHeaders: params?.extraHeaders,\n })\n\n return {\n ...base,\n meta: { ...base.meta, defaultModel },\n async stream(options, callbacks: StreamCallbacks) {\n // Resolve (and lazily refresh) the OAuth token so auth failures surface\n // here with the right remediation, distinct from the not-implemented path.\n await resolveOAuthApiKey(\n {\n provider: 'cursor',\n providerId: CURSOR_OAUTH_PROVIDER_ID,\n params,\n missingError: 'No Cursor credentials found. Run `bun run auth --cursor` first.',\n refreshError: reason => `Cursor OAuth token refresh failed. Run \\`bun run auth --cursor\\` again. ${reason}`,\n },\n callbacks,\n )\n void options\n throw new Error(NOT_IMPLEMENTED_MESSAGE)\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"providers-BM6VuE_i.js","names":[],"sources":["../src/chat/oauth-page/cursor.ts","../src/providers/cursor.ts"],"sourcesContent":["/**\n * Cursor OAuth flow.\n *\n * Unlike Anthropic / OpenAI Codex (loopback-redirect PKCE), Cursor uses a\n * **poll-based** PKCE flow with no local callback server:\n *\n * 1. Generate a PKCE verifier + challenge and a random UUID.\n * 2. Open the browser to `cursor.com/loginDeepControl?challenge&uuid&mode&redirectTarget`.\n * 3. Poll `api2.cursor.sh/auth/poll?uuid&verifier` until tokens come back\n * (HTTP 404 = \"not ready yet\", keep polling with backoff).\n * 4. Refresh via `POST api2.cursor.sh/auth/exchange_user_api_key` with\n * `Authorization: Bearer <refresh>`.\n *\n * Token expiry is derived from the access token's JWT `exp` claim (minus a\n * 5-minute safety margin), falling back to +1h when the claim is missing.\n *\n * Flow adapted from https://github.com/ndraiman/pi-cursor-provider, itself\n * derived from https://github.com/ephraimduncan/opencode-cursor.\n *\n * Cursor's auth + inference protocol is unofficial and undocumented — re-verify\n * the endpoints/params below if login starts failing.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderInterface,\n} from '@earendil-works/pi-ai/oauth'\nimport { generatePkce } from './pkce'\n\n/** pi-ai / zidane OAuth provider id. Also the credentials-file key. */\nexport const CURSOR_OAUTH_PROVIDER_ID = 'cursor'\n\nconst CURSOR_LOGIN_URL = 'https://cursor.com/loginDeepControl'\nconst CURSOR_POLL_URL = 'https://api2.cursor.sh/auth/poll'\nconst CURSOR_REFRESH_URL = 'https://api2.cursor.sh/auth/exchange_user_api_key'\n\nconst POLL_MAX_ATTEMPTS = 150\nconst POLL_BASE_DELAY_MS = 1000\nconst POLL_MAX_DELAY_MS = 10_000\nconst POLL_BACKOFF_MULTIPLIER = 1.2\nconst POLL_MAX_CONSECUTIVE_ERRORS = 3\n\n/** Fallback lifetime when the JWT carries no `exp`. */\nconst DEFAULT_TOKEN_TTL_MS = 3600 * 1000\n/** Refresh slightly early so requests don't race expiry. */\nconst TOKEN_EXPIRY_SKEW_MS = 5 * 60 * 1000\n\ninterface CursorTokenResponse {\n accessToken: string\n refreshToken: string\n}\n\n/**\n * Derive expiry (epoch ms) from a JWT's `exp` claim, minus a safety skew.\n * Returns `now + 1h` for malformed tokens so a missing claim never wedges\n * refresh into an immediate-expire loop.\n */\nexport function cursorTokenExpiry(token: string): number {\n try {\n const parts = token.split('.')\n const payload = parts[1]\n if (parts.length !== 3 || !payload)\n return Date.now() + DEFAULT_TOKEN_TTL_MS\n\n const json = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))) as { exp?: unknown }\n if (typeof json.exp === 'number')\n return json.exp * 1000 - TOKEN_EXPIRY_SKEW_MS\n }\n catch {\n // fall through to default\n }\n return Date.now() + DEFAULT_TOKEN_TTL_MS\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms))\n}\n\n/**\n * Poll Cursor's auth endpoint until login completes. `404` means the user\n * hasn't finished the browser flow yet; anything else non-2xx is a hard error.\n */\nasync function pollForTokens(\n uuid: string,\n verifier: string,\n callbacks: OAuthLoginCallbacks,\n): Promise<CursorTokenResponse> {\n let wait = POLL_BASE_DELAY_MS\n let consecutiveErrors = 0\n\n for (let attempt = 0; attempt < POLL_MAX_ATTEMPTS; attempt++) {\n if (callbacks.signal?.aborted)\n throw new Error('Cursor login aborted')\n\n await delay(wait)\n\n try {\n const url = `${CURSOR_POLL_URL}?uuid=${encodeURIComponent(uuid)}&verifier=${encodeURIComponent(verifier)}`\n const response = await fetch(url, { signal: callbacks.signal })\n\n if (response.status === 404) {\n consecutiveErrors = 0\n wait = Math.min(wait * POLL_BACKOFF_MULTIPLIER, POLL_MAX_DELAY_MS)\n continue\n }\n\n if (response.ok) {\n const data = await response.json() as Partial<CursorTokenResponse>\n if (!data.accessToken || !data.refreshToken)\n throw new Error(`Cursor poll response missing tokens: ${JSON.stringify(data)}`)\n return { accessToken: data.accessToken, refreshToken: data.refreshToken }\n }\n\n throw new Error(`Cursor poll failed (${response.status})`)\n }\n catch (err) {\n if (callbacks.signal?.aborted)\n throw new Error('Cursor login aborted')\n consecutiveErrors++\n if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS)\n throw err instanceof Error ? err : new Error(String(err))\n callbacks.onProgress?.('Waiting for Cursor login to complete…')\n }\n }\n\n throw new Error('Cursor authentication timed out')\n}\n\nexport async function loginCursor(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n const { verifier, challenge } = await generatePkce()\n const uuid = crypto.randomUUID()\n\n const authUrl = new URL(CURSOR_LOGIN_URL)\n authUrl.searchParams.set('challenge', challenge)\n authUrl.searchParams.set('uuid', uuid)\n authUrl.searchParams.set('mode', 'login')\n authUrl.searchParams.set('redirectTarget', 'cli')\n\n callbacks.onAuth({\n url: authUrl.toString(),\n instructions: 'A browser window should open. Complete login in Cursor to finish.',\n })\n\n const tokens = await pollForTokens(uuid, verifier, callbacks)\n\n return {\n access: tokens.accessToken,\n refresh: tokens.refreshToken,\n expires: cursorTokenExpiry(tokens.accessToken),\n }\n}\n\nexport async function refreshCursorToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const response = await fetch(CURSOR_REFRESH_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${credentials.refresh}`,\n 'Content-Type': 'application/json',\n },\n body: '{}',\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`Cursor token refresh failed (${response.status}): ${text || response.statusText}`)\n }\n\n const data = await response.json() as Partial<CursorTokenResponse>\n if (!data.accessToken)\n throw new Error(`Cursor token refresh response missing access token: ${JSON.stringify(data)}`)\n\n return {\n access: data.accessToken,\n // Cursor may omit a rotated refresh token — keep the existing one.\n refresh: data.refreshToken || credentials.refresh,\n expires: cursorTokenExpiry(data.accessToken),\n }\n}\n\n/**\n * Build a Cursor `OAuthProviderInterface`. Shape matches Anthropic / Codex so\n * it drops into `BUILTIN_PROVIDERS` and `src/auth.ts` unchanged.\n *\n * No `renderPage` parameter: Cursor's poll flow has no local callback server,\n * so there is no post-redirect page to theme.\n */\nexport function createCursorOAuthProvider(): OAuthProviderInterface {\n return {\n id: CURSOR_OAUTH_PROVIDER_ID,\n name: 'Cursor',\n usesCallbackServer: false,\n login: loginCursor,\n refreshToken: refreshCursorToken,\n getApiKey: credentials => credentials.access,\n }\n}\n","import type { Provider, StreamCallbacks } from '.'\nimport { CURSOR_OAUTH_PROVIDER_ID } from '../chat/oauth-page/cursor'\nimport { resolveOAuthApiKey } from './oauth'\nimport { openaiCompat } from './openai-compat'\n\n/**\n * Cursor provider (OAuth).\n *\n * Auth is fully wired: `bun run auth --cursor` (or the TUI wizard) logs in via\n * Cursor's poll-based PKCE flow and persists credentials; this provider resolves\n * + refreshes the OAuth token on every turn via {@link resolveOAuthApiKey}.\n *\n * Inference is **not** wired yet. Cursor does not expose an OpenAI-compatible\n * HTTP endpoint — it speaks a protobuf/Connect agent protocol over HTTP/2 to\n * `api2.cursor.sh`. Implementing `stream()` requires porting that transport\n * (generated protobuf schemas + an HTTP/2 streaming client) in-process, which\n * is intentionally left as a follow-up. Until then `stream()` resolves the\n * token (proving auth works) and then throws a clear, actionable error.\n *\n * The `openaiCompat` base supplies the message-format helpers (`formatTools`,\n * `userMessage`, `toolResultsMessage`, …) so the provider satisfies the full\n * `Provider` contract and slots into the registry; only `stream` is overridden.\n */\nexport interface CursorParams {\n /** Bypass OAuth resolution with an explicit token (mainly for tests). */\n apiKey?: string\n defaultModel?: string\n /**\n * Extra HTTP headers for the eventual Cursor transport. Accepted for API\n * parity with the other providers and forwarded to the openai-compat base,\n * but inert until Cursor inference is implemented (the `stream` override\n * currently throws before any request is made).\n */\n extraHeaders?: Record<string, string>\n}\n\nconst DEFAULT_MODEL = 'claude-4.6-sonnet'\n\nconst NOT_IMPLEMENTED_MESSAGE\n = 'Cursor OAuth login works, but inference over Cursor is not implemented yet. '\n + 'Cursor uses a protobuf/HTTP-2 agent protocol (not an OpenAI-compatible API), '\n + 'so the streaming transport still needs to be ported. Use another provider for now.'\n\nexport function cursor(params?: CursorParams): Provider {\n const defaultModel = params?.defaultModel || DEFAULT_MODEL\n\n // Base only provides the format/serialization methods; `baseURL` is never\n // hit because we override `stream` below.\n const base = openaiCompat({\n name: 'cursor',\n apiKey: params?.apiKey ?? 'oauth',\n baseURL: 'https://api2.cursor.sh',\n defaultModel,\n capabilities: { vision: true, imageInToolResult: false },\n extraHeaders: params?.extraHeaders,\n })\n\n return {\n ...base,\n meta: { ...base.meta, defaultModel },\n async stream(options, callbacks: StreamCallbacks) {\n // Resolve (and lazily refresh) the OAuth token so auth failures surface\n // here with the right remediation, distinct from the not-implemented path.\n await resolveOAuthApiKey(\n {\n provider: 'cursor',\n providerId: CURSOR_OAUTH_PROVIDER_ID,\n params,\n missingError: 'No Cursor credentials found. Run `bun run auth --cursor` first.',\n refreshError: reason => `Cursor OAuth token refresh failed. Run \\`bun run auth --cursor\\` again. ${reason}`,\n },\n callbacks,\n )\n void options\n throw new Error(NOT_IMPLEMENTED_MESSAGE)\n },\n }\n}\n"],"mappings":";;;;;AA+BA,MAAa,2BAA2B;AAExC,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAE3B,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB;AAC1B,MAAM,0BAA0B;AAChC,MAAM,8BAA8B;;AAGpC,MAAM,uBAAuB,OAAO;;AAEpC,MAAM,uBAAuB,MAAS;;;;;;AAYtC,SAAgB,kBAAkB,OAAuB;CACvD,IAAI;EACF,MAAM,QAAQ,MAAM,MAAM,GAAG;EAC7B,MAAM,UAAU,MAAM;EACtB,IAAI,MAAM,WAAW,KAAK,CAAC,SACzB,OAAO,KAAK,IAAI,IAAI;EAEtB,MAAM,OAAO,KAAK,MAAM,KAAK,QAAQ,QAAQ,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,GAAG,CAAC,CAAC;EAC3E,IAAI,OAAO,KAAK,QAAQ,UACtB,OAAO,KAAK,MAAM,MAAO;CAC7B,QACM,CAEN;CACA,OAAO,KAAK,IAAI,IAAI;AACtB;AAEA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAQ,MAAK,WAAW,GAAG,EAAE,CAAC;AAC3C;;;;;AAMA,eAAe,cACb,MACA,UACA,WAC8B;CAC9B,IAAI,OAAO;CACX,IAAI,oBAAoB;CAExB,KAAK,IAAI,UAAU,GAAG,UAAU,mBAAmB,WAAW;EAC5D,IAAI,UAAU,QAAQ,SACpB,MAAM,IAAI,MAAM,sBAAsB;EAExC,MAAM,MAAM,IAAI;EAEhB,IAAI;GACF,MAAM,MAAM,GAAG,gBAAgB,QAAQ,mBAAmB,IAAI,EAAE,YAAY,mBAAmB,QAAQ;GACvG,MAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,UAAU,OAAO,CAAC;GAE9D,IAAI,SAAS,WAAW,KAAK;IAC3B,oBAAoB;IACpB,OAAO,KAAK,IAAI,OAAO,yBAAyB,iBAAiB;IACjE;GACF;GAEA,IAAI,SAAS,IAAI;IACf,MAAM,OAAO,MAAM,SAAS,KAAK;IACjC,IAAI,CAAC,KAAK,eAAe,CAAC,KAAK,cAC7B,MAAM,IAAI,MAAM,wCAAwC,KAAK,UAAU,IAAI,GAAG;IAChF,OAAO;KAAE,aAAa,KAAK;KAAa,cAAc,KAAK;IAAa;GAC1E;GAEA,MAAM,IAAI,MAAM,uBAAuB,SAAS,OAAO,EAAE;EAC3D,SACO,KAAK;GACV,IAAI,UAAU,QAAQ,SACpB,MAAM,IAAI,MAAM,sBAAsB;GACxC;GACA,IAAI,qBAAqB,6BACvB,MAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;GAC1D,UAAU,aAAa,uCAAuC;EAChE;CACF;CAEA,MAAM,IAAI,MAAM,iCAAiC;AACnD;AAEA,eAAsB,YAAY,WAA2D;CAC3F,MAAM,EAAE,UAAU,cAAc,MAAM,aAAa;CACnD,MAAM,OAAO,OAAO,WAAW;CAE/B,MAAM,UAAU,IAAI,IAAI,gBAAgB;CACxC,QAAQ,aAAa,IAAI,aAAa,SAAS;CAC/C,QAAQ,aAAa,IAAI,QAAQ,IAAI;CACrC,QAAQ,aAAa,IAAI,QAAQ,OAAO;CACxC,QAAQ,aAAa,IAAI,kBAAkB,KAAK;CAEhD,UAAU,OAAO;EACf,KAAK,QAAQ,SAAS;EACtB,cAAc;CAChB,CAAC;CAED,MAAM,SAAS,MAAM,cAAc,MAAM,UAAU,SAAS;CAE5D,OAAO;EACL,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,SAAS,kBAAkB,OAAO,WAAW;CAC/C;AACF;AAEA,eAAsB,mBAAmB,aAA0D;CACjG,MAAM,WAAW,MAAM,MAAM,oBAAoB;EAC/C,QAAQ;EACR,SAAS;GACP,iBAAiB,UAAU,YAAY;GACvC,gBAAgB;EAClB;EACA,MAAM;CACR,CAAC;CAED,IAAI,CAAC,SAAS,IAAI;EAChB,MAAM,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;EACjD,MAAM,IAAI,MAAM,gCAAgC,SAAS,OAAO,KAAK,QAAQ,SAAS,YAAY;CACpG;CAEA,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,IAAI,CAAC,KAAK,aACR,MAAM,IAAI,MAAM,uDAAuD,KAAK,UAAU,IAAI,GAAG;CAE/F,OAAO;EACL,QAAQ,KAAK;EAEb,SAAS,KAAK,gBAAgB,YAAY;EAC1C,SAAS,kBAAkB,KAAK,WAAW;CAC7C;AACF;;;;;;;;AASA,SAAgB,4BAAoD;CAClE,OAAO;EACL,IAAI;EACJ,MAAM;EACN,oBAAoB;EACpB,OAAO;EACP,cAAc;EACd,YAAW,gBAAe,YAAY;CACxC;AACF;;;AChKA,MAAM,gBAAgB;AAEtB,MAAM,0BACF;AAIJ,SAAgB,OAAO,QAAiC;CACtD,MAAM,eAAe,QAAQ,gBAAgB;CAI7C,MAAM,OAAO,aAAa;EACxB,MAAM;EACN,QAAQ,QAAQ,UAAU;EAC1B,SAAS;EACT;EACA,cAAc;GAAE,QAAQ;GAAM,mBAAmB;EAAM;EACvD,cAAc,QAAQ;CACxB,CAAC;CAED,OAAO;EACL,GAAG;EACH,MAAM;GAAE,GAAG,KAAK;GAAM;EAAa;EACnC,MAAM,OAAO,SAAS,WAA4B;GAGhD,MAAM,mBACJ;IACE,UAAU;IACV,YAAY;IACZ;IACA,cAAc;IACd,eAAc,WAAU,2EAA2E;GACrG,GACA,SACF;GAEA,MAAM,IAAI,MAAM,uBAAuB;EACzC;CACF;AACF"}
|
package/dist/providers.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { f as mapOAIFinishReason, l as classifyOpenAICompatError, m as openaiCompat, n as OpenAICompatHttpError, v as sanitizeToolSchema, y as sanitizeToolSpecs } from "./openai-compat-
|
|
2
|
-
import { n as applyAnthropicCacheBreakpoints, t as anthropic } from "./anthropic-
|
|
1
|
+
import { f as mapOAIFinishReason, l as classifyOpenAICompatError, m as openaiCompat, n as OpenAICompatHttpError, v as sanitizeToolSchema, y as sanitizeToolSpecs } from "./openai-compat-LGAsBush.js";
|
|
2
|
+
import { n as applyAnthropicCacheBreakpoints, t as anthropic } from "./anthropic-J2tBjcSQ.js";
|
|
3
3
|
import { arcee } from "./providers/arcee.js";
|
|
4
4
|
import { baseten, planBasetenReasoning } from "./providers/baseten.js";
|
|
5
5
|
import { cerebras } from "./providers/cerebras.js";
|
|
6
|
-
import { t as xai } from "./xai-
|
|
7
|
-
import { t as cursor } from "./providers-
|
|
6
|
+
import { t as xai } from "./xai-BYAyEPiJ.js";
|
|
7
|
+
import { t as cursor } from "./providers-BM6VuE_i.js";
|
|
8
8
|
import { local } from "./providers/local.js";
|
|
9
|
-
import { openai } from "./
|
|
9
|
+
import { r as openai } from "./openai-BcmYm0t4.js";
|
|
10
10
|
import { openrouter } from "./providers/openrouter.js";
|
|
11
11
|
export { OpenAICompatHttpError, anthropic, applyAnthropicCacheBreakpoints, arcee, baseten, cerebras, classifyOpenAICompatError, cursor, local, mapOAIFinishReason, openai, openaiCompat, openrouter, planBasetenReasoning, sanitizeToolSchema, sanitizeToolSpecs, xai };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as toWireMessages } from "./messages-
|
|
1
|
+
import { h as toWireMessages } from "./messages-DEPX32kc.js";
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
//#region src/session/file-map.ts
|
|
@@ -645,4 +645,4 @@ function assertJsonBounded(value, maxBytes, label) {
|
|
|
645
645
|
//#endregion
|
|
646
646
|
export { getExtensionSessionState as a, withExtensionSession as c, createFileMapStore as d, demoteStaleRunningStatus as i, createRemoteStore as l, currentExtensionSession as n, loadSession as o, deleteExtensionSessionState as r, setExtensionSessionState as s, createSession as t, createMemoryStore as u };
|
|
647
647
|
|
|
648
|
-
//# sourceMappingURL=session-
|
|
648
|
+
//# sourceMappingURL=session-Dc1v4Otm.js.map
|