@skydiveai/pi-server 0.1.0-beta.1738 → 0.1.0-beta.2448
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/index.mjs +72 -35
- package/package.json +1 -7
package/dist/index.mjs
CHANGED
|
@@ -259,7 +259,7 @@ function parseShellEnv(request) {
|
|
|
259
259
|
const env = {};
|
|
260
260
|
for (const [k, v] of Object.entries(parsed)) if (typeof v === "string") env[k] = v;
|
|
261
261
|
return Object.keys(env).length > 0 ? env : null;
|
|
262
|
-
} catch {
|
|
262
|
+
} catch (_err) {
|
|
263
263
|
return null;
|
|
264
264
|
}
|
|
265
265
|
}
|
|
@@ -283,12 +283,12 @@ function createSSEResponse(handler) {
|
|
|
283
283
|
write(chunk) {
|
|
284
284
|
try {
|
|
285
285
|
controller.enqueue(encoder.encode(chunk));
|
|
286
|
-
} catch {}
|
|
286
|
+
} catch (_err) {}
|
|
287
287
|
},
|
|
288
288
|
close() {
|
|
289
289
|
try {
|
|
290
290
|
controller.close();
|
|
291
|
-
} catch {}
|
|
291
|
+
} catch (_err) {}
|
|
292
292
|
}
|
|
293
293
|
};
|
|
294
294
|
const keepalive = setInterval(() => {
|
|
@@ -298,13 +298,13 @@ function createSSEResponse(handler) {
|
|
|
298
298
|
clearInterval(keepalive);
|
|
299
299
|
try {
|
|
300
300
|
controller.close();
|
|
301
|
-
} catch {}
|
|
301
|
+
} catch (_err) {}
|
|
302
302
|
}, (err) => {
|
|
303
303
|
logger.error({ err }, "SSE handler crashed — stream closed without response");
|
|
304
304
|
clearInterval(keepalive);
|
|
305
305
|
try {
|
|
306
306
|
controller.close();
|
|
307
|
-
} catch {}
|
|
307
|
+
} catch (_err) {}
|
|
308
308
|
});
|
|
309
309
|
return new Response(stream, {
|
|
310
310
|
status: 200,
|
|
@@ -470,7 +470,10 @@ async function extractParts(parts, log) {
|
|
|
470
470
|
}
|
|
471
471
|
if (part.url != null && part.mediaType?.startsWith("image/")) {
|
|
472
472
|
try {
|
|
473
|
-
images.push(await fetchImageAsBase64({
|
|
473
|
+
images.push(await fetchImageAsBase64({
|
|
474
|
+
url: part.url,
|
|
475
|
+
userAgent: null
|
|
476
|
+
}));
|
|
474
477
|
} catch (err) {
|
|
475
478
|
log.warn({
|
|
476
479
|
event: "a2a_image_fetch_failed",
|
|
@@ -999,7 +1002,10 @@ async function extractContent$1(content, log) {
|
|
|
999
1002
|
data: src.data
|
|
1000
1003
|
});
|
|
1001
1004
|
else if (src?.type === "url" && src.url) try {
|
|
1002
|
-
images.push(await fetchImageAsBase64({
|
|
1005
|
+
images.push(await fetchImageAsBase64({
|
|
1006
|
+
url: src.url,
|
|
1007
|
+
userAgent: null
|
|
1008
|
+
}));
|
|
1003
1009
|
} catch (err) {
|
|
1004
1010
|
log.warn({
|
|
1005
1011
|
event: "image_fetch_failed",
|
|
@@ -1088,7 +1094,7 @@ async function handleMessages(request, ctx, registry) {
|
|
|
1088
1094
|
let parsed;
|
|
1089
1095
|
try {
|
|
1090
1096
|
parsed = JSON.parse(await request.text());
|
|
1091
|
-
} catch {
|
|
1097
|
+
} catch (_err) {
|
|
1092
1098
|
return jsonError(400, "invalid json");
|
|
1093
1099
|
}
|
|
1094
1100
|
const { messages, stream = false, model: modelInput, system, x_model: modelSpecInput } = parsed ?? {};
|
|
@@ -1111,7 +1117,7 @@ async function handleMessages(request, ctx, registry) {
|
|
|
1111
1117
|
const baseSystemPrompt = extractSystemPrompt(system);
|
|
1112
1118
|
const { sessionId } = parseSessionId(request);
|
|
1113
1119
|
const shellEnv = parseShellEnv(request);
|
|
1114
|
-
const { session
|
|
1120
|
+
const { session } = await ctx.createSession({
|
|
1115
1121
|
cwd: ctx.cwd,
|
|
1116
1122
|
sessionId,
|
|
1117
1123
|
perRequestApiKeys: withSpecApiKey(resolvePerRequestApiKeys(request), modelSpec),
|
|
@@ -1134,7 +1140,6 @@ async function handleMessages(request, ctx, registry) {
|
|
|
1134
1140
|
if (stream) {
|
|
1135
1141
|
const response = runStream$2({
|
|
1136
1142
|
session,
|
|
1137
|
-
sm,
|
|
1138
1143
|
prompt,
|
|
1139
1144
|
images,
|
|
1140
1145
|
id,
|
|
@@ -1150,7 +1155,6 @@ async function handleMessages(request, ctx, registry) {
|
|
|
1150
1155
|
}
|
|
1151
1156
|
const response = await runBlocking$2({
|
|
1152
1157
|
session,
|
|
1153
|
-
sm,
|
|
1154
1158
|
prompt,
|
|
1155
1159
|
images,
|
|
1156
1160
|
id,
|
|
@@ -1164,7 +1168,7 @@ async function handleMessages(request, ctx, registry) {
|
|
|
1164
1168
|
if (traceId) response.headers.set("x-trace-id", traceId);
|
|
1165
1169
|
return response;
|
|
1166
1170
|
}
|
|
1167
|
-
function runStream$2({ session,
|
|
1171
|
+
function runStream$2({ session, prompt, images, id, sessionId, modelName, log, postPrompt, registry }) {
|
|
1168
1172
|
return createSSEResponse(async (writer) => {
|
|
1169
1173
|
let contentBlockIndex = 0;
|
|
1170
1174
|
let textBlockOpen = false;
|
|
@@ -1322,7 +1326,7 @@ function runStream$2({ session, sm, prompt, images, id, sessionId, modelName, lo
|
|
|
1322
1326
|
sseEvent(writer, "message_stop", { type: "message_stop" });
|
|
1323
1327
|
});
|
|
1324
1328
|
}
|
|
1325
|
-
async function runBlocking$2({ session,
|
|
1329
|
+
async function runBlocking$2({ session, prompt, images, id, sessionId, modelName, log, postPrompt, registry }) {
|
|
1326
1330
|
let text = "";
|
|
1327
1331
|
const toolUseBlocks = [];
|
|
1328
1332
|
const tcByContentIdx = /* @__PURE__ */ new Map();
|
|
@@ -1373,7 +1377,7 @@ async function runBlocking$2({ session, sm, prompt, images, id, sessionId, model
|
|
|
1373
1377
|
}
|
|
1374
1378
|
for (const block of toolUseBlocks) if (block.type === "tool_use" && typeof block.input === "string") try {
|
|
1375
1379
|
block.input = JSON.parse(block.input);
|
|
1376
|
-
} catch {
|
|
1380
|
+
} catch (_err) {
|
|
1377
1381
|
block.input = {};
|
|
1378
1382
|
}
|
|
1379
1383
|
const content = [];
|
|
@@ -1464,13 +1468,34 @@ const nestedProxyEnvelopeSchema = z.object({
|
|
|
1464
1468
|
blockReason: z.enum(BILLING_BLOCK_REASONS),
|
|
1465
1469
|
message: z.string()
|
|
1466
1470
|
}).strict();
|
|
1471
|
+
/**
|
|
1472
|
+
* pi-ai's openai-compatible providers (openrouter, xai, groq, deepseek, …) can
|
|
1473
|
+
* NOT fold a proxy's non-2xx body into `error.message`, so `formatProviderError`
|
|
1474
|
+
* (@earendil-works/pi-ai utils/error-body) composes the display string as
|
|
1475
|
+
* `"<status>: <body>"` or, with a provider label, `"<prefix> (<status>): <body>"`.
|
|
1476
|
+
* The billing gate's 402 body therefore reaches us wrapped, e.g.
|
|
1477
|
+
* `"402: {\"error\":{...},\"blockReason\":\"insufficient_balance\",\"message\":...}"`
|
|
1478
|
+
* or `"OpenRouter (402): {...}"`. Neither the bare `JSON.parse` nor the `"402 "`
|
|
1479
|
+
* (space) strip below recognizes that, so a real billing block from an
|
|
1480
|
+
* openrouter-routed model degrades to the generic model-provider error. Peel a
|
|
1481
|
+
* single leading `"<status>: "` / `"<prefix> (<status>): "` wrapper off the
|
|
1482
|
+
* front so the recovered body flows through the existing shape checks. Returns
|
|
1483
|
+
* the message unchanged when no wrapper is present.
|
|
1484
|
+
*/
|
|
1485
|
+
function unwrapOpenAICompatStatusPrefix(message) {
|
|
1486
|
+
const withPrefix = message.match(/^.+ \(\d{3}\): ([\s\S]+)$/);
|
|
1487
|
+
if (withPrefix?.[1] !== void 0) return withPrefix[1];
|
|
1488
|
+
const bare = message.match(/^\d{3}: ([\s\S]+)$/);
|
|
1489
|
+
if (bare?.[1] !== void 0) return bare[1];
|
|
1490
|
+
return message;
|
|
1491
|
+
}
|
|
1467
1492
|
function parsePrefixedSignal(message) {
|
|
1468
1493
|
const normalized = message.startsWith("402 ") ? message.slice(4) : message;
|
|
1469
1494
|
if (!normalized.startsWith(BILLING_BLOCKED_PROVIDER_SIGNAL_PREFIX)) return null;
|
|
1470
1495
|
try {
|
|
1471
1496
|
const parsed = payloadSchema.safeParse(JSON.parse(normalized.slice(26)));
|
|
1472
1497
|
return parsed.success ? parsed.data : null;
|
|
1473
|
-
} catch {
|
|
1498
|
+
} catch (_err) {
|
|
1474
1499
|
return null;
|
|
1475
1500
|
}
|
|
1476
1501
|
}
|
|
@@ -1480,16 +1505,17 @@ function parsePrefixedSignal(message) {
|
|
|
1480
1505
|
* proxy response as JSON, so that outer shape is validated separately.
|
|
1481
1506
|
*/
|
|
1482
1507
|
function parseBillingBlockedProviderSignal(message) {
|
|
1483
|
-
const
|
|
1508
|
+
const unwrapped = unwrapOpenAICompatStatusPrefix(message);
|
|
1509
|
+
const direct = parsePrefixedSignal(unwrapped);
|
|
1484
1510
|
if (direct) return direct;
|
|
1485
1511
|
try {
|
|
1486
|
-
const envelope = nestedProxyEnvelopeSchema.safeParse(JSON.parse(
|
|
1512
|
+
const envelope = nestedProxyEnvelopeSchema.safeParse(JSON.parse(unwrapped));
|
|
1487
1513
|
if (!envelope.success) return null;
|
|
1488
1514
|
const nested = parsePrefixedSignal(envelope.data.error.message);
|
|
1489
1515
|
const topLevel = parsePrefixedSignal(envelope.data.message);
|
|
1490
1516
|
if (!nested || nested.blockReason !== envelope.data.blockReason || envelope.data.message !== nested.message && (!topLevel || topLevel.blockReason !== nested.blockReason || topLevel.message !== nested.message)) return null;
|
|
1491
1517
|
return nested;
|
|
1492
|
-
} catch {
|
|
1518
|
+
} catch (_err) {
|
|
1493
1519
|
return null;
|
|
1494
1520
|
}
|
|
1495
1521
|
}
|
|
@@ -1678,7 +1704,7 @@ async function loadHistoryIntoSession({ sm, messages, upToIdxExclusive, modelNam
|
|
|
1678
1704
|
let args = {};
|
|
1679
1705
|
try {
|
|
1680
1706
|
args = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
|
|
1681
|
-
} catch {
|
|
1707
|
+
} catch (_err) {
|
|
1682
1708
|
args = { _raw: tc.function.arguments };
|
|
1683
1709
|
}
|
|
1684
1710
|
contentArr.push({
|
|
@@ -1720,6 +1746,15 @@ async function loadHistoryIntoSession({ sm, messages, upToIdxExclusive, modelNam
|
|
|
1720
1746
|
}
|
|
1721
1747
|
}
|
|
1722
1748
|
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Each pi/session message carries the real wall-clock `timestamp` the
|
|
1751
|
+
* provider stamped when it produced that message (see @earendil-works/pi-ai
|
|
1752
|
+
* Message). Forward it verbatim as `x_created_at` (epoch ms) on the OAI
|
|
1753
|
+
* trailer message so the consumer (anyone-messaging completeRun) can stamp
|
|
1754
|
+
* each persisted segment at its true emit time and interleave a mid-run steer
|
|
1755
|
+
* by createdAt — instead of positionally reconstructing per-turn times, which
|
|
1756
|
+
* drifts on parallel tool calls and provider-split replies.
|
|
1757
|
+
*/
|
|
1723
1758
|
function messageTimestampMs(m) {
|
|
1724
1759
|
const ts = m?.timestamp;
|
|
1725
1760
|
return typeof ts === "number" && Number.isFinite(ts) ? ts : null;
|
|
@@ -1781,7 +1816,7 @@ async function handleChatCompletions(request, ctx, registry, pendingSteerIds) {
|
|
|
1781
1816
|
let parsed;
|
|
1782
1817
|
try {
|
|
1783
1818
|
parsed = JSON.parse(await request.text());
|
|
1784
|
-
} catch {
|
|
1819
|
+
} catch (_err) {
|
|
1785
1820
|
return jsonError(400, "invalid json");
|
|
1786
1821
|
}
|
|
1787
1822
|
const { messages, stream = false, model: modelInput, thinkingLevel: thinkingInput, context_window: contextWindowInput, x_model: modelSpecInput } = parsed ?? {};
|
|
@@ -1793,7 +1828,7 @@ async function handleChatCompletions(request, ctx, registry, pendingSteerIds) {
|
|
|
1793
1828
|
}
|
|
1794
1829
|
if (lastUserIdx < 0) return jsonError(400, "no user message in history");
|
|
1795
1830
|
const lastUser = messages[lastUserIdx];
|
|
1796
|
-
const reqUA = request.headers.get("user-agent") ??
|
|
1831
|
+
const reqUA = request.headers.get("user-agent") ?? null;
|
|
1797
1832
|
const { text: prompt, images } = await extractContent(lastUser.content, {
|
|
1798
1833
|
userAgent: reqUA,
|
|
1799
1834
|
log
|
|
@@ -1824,7 +1859,7 @@ async function handleChatCompletions(request, ctx, registry, pendingSteerIds) {
|
|
|
1824
1859
|
const sessionId = parsedSession.source === "header" ? parsedSession.sessionId : `chatcmpl-${parsedSession.sessionId}`;
|
|
1825
1860
|
const shellEnv = parseShellEnv(request);
|
|
1826
1861
|
const sessionSetupStart = performance.now();
|
|
1827
|
-
const [{ session, sessionManager: sm }
|
|
1862
|
+
const [{ session, sessionManager: sm }] = await Promise.all([ctx.createSession({
|
|
1828
1863
|
cwd: ctx.cwd,
|
|
1829
1864
|
sessionId,
|
|
1830
1865
|
perRequestApiKeys: withSpecApiKey(resolvePerRequestApiKeys(request), modelSpec),
|
|
@@ -1936,7 +1971,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
|
|
|
1936
1971
|
};
|
|
1937
1972
|
const ensureRole = () => {
|
|
1938
1973
|
if (roleEmitted) return;
|
|
1939
|
-
sendChunk({ role: "assistant" });
|
|
1974
|
+
sendChunk({ role: "assistant" }, null);
|
|
1940
1975
|
roleEmitted = true;
|
|
1941
1976
|
};
|
|
1942
1977
|
let _evCount = 0;
|
|
@@ -1960,7 +1995,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
|
|
|
1960
1995
|
steering,
|
|
1961
1996
|
follow_up: followUp,
|
|
1962
1997
|
...consumedIds.length > 0 ? { consumed_steer_ids: consumedIds } : {}
|
|
1963
|
-
} });
|
|
1998
|
+
} }, null);
|
|
1964
1999
|
return;
|
|
1965
2000
|
}
|
|
1966
2001
|
_evCount++;
|
|
@@ -1983,7 +2018,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
|
|
|
1983
2018
|
...ev.type === "message_start" || ev.type === "message_end" ? { messageJson: JSON.stringify(ev.message).slice(0, 500) } : {}
|
|
1984
2019
|
}, "harness session event");
|
|
1985
2020
|
if (ev.type === "tool_execution_start") {
|
|
1986
|
-
sendChunk({ x_tool_execution_start: { tool_call_id: String(ev.toolCallId ?? "") } });
|
|
2021
|
+
sendChunk({ x_tool_execution_start: { tool_call_id: String(ev.toolCallId ?? "") } }, null);
|
|
1987
2022
|
return;
|
|
1988
2023
|
}
|
|
1989
2024
|
if (ev.type === "tool_execution_end") {
|
|
@@ -1992,7 +2027,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
|
|
|
1992
2027
|
tool_call_id: String(ev.toolCallId ?? ""),
|
|
1993
2028
|
content: text,
|
|
1994
2029
|
...ev.isError ? { is_error: true } : {}
|
|
1995
|
-
} });
|
|
2030
|
+
} }, null);
|
|
1996
2031
|
return;
|
|
1997
2032
|
}
|
|
1998
2033
|
if (ev.type === "agent_end") {
|
|
@@ -2004,11 +2039,11 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
|
|
|
2004
2039
|
const t = inner?.type;
|
|
2005
2040
|
if (t === "text_delta") {
|
|
2006
2041
|
ensureRole();
|
|
2007
|
-
sendChunk({ content: inner.delta ?? "" });
|
|
2042
|
+
sendChunk({ content: inner.delta ?? "" }, null);
|
|
2008
2043
|
return;
|
|
2009
2044
|
}
|
|
2010
2045
|
if (t === "thinking_delta") {
|
|
2011
|
-
sendChunk({ x_thinking_delta: inner.delta ?? "" });
|
|
2046
|
+
sendChunk({ x_thinking_delta: inner.delta ?? "" }, null);
|
|
2012
2047
|
return;
|
|
2013
2048
|
}
|
|
2014
2049
|
if (t === "toolcall_start") {
|
|
@@ -2026,7 +2061,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
|
|
|
2026
2061
|
name: tc?.name ?? "",
|
|
2027
2062
|
arguments: ""
|
|
2028
2063
|
}
|
|
2029
|
-
}] });
|
|
2064
|
+
}] }, null);
|
|
2030
2065
|
return;
|
|
2031
2066
|
}
|
|
2032
2067
|
if (t === "toolcall_delta") {
|
|
@@ -2035,7 +2070,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
|
|
|
2035
2070
|
sendChunk({ tool_calls: [{
|
|
2036
2071
|
index: openaiIdx,
|
|
2037
2072
|
function: { arguments: inner.delta ?? "" }
|
|
2038
|
-
}] });
|
|
2073
|
+
}] }, null);
|
|
2039
2074
|
}
|
|
2040
2075
|
});
|
|
2041
2076
|
try {
|
|
@@ -2301,7 +2336,6 @@ const steerRequestSchema = z.object({
|
|
|
2301
2336
|
mode: z.enum(["steer", "follow-up"]).default("steer"),
|
|
2302
2337
|
text: z.string().optional()
|
|
2303
2338
|
}).refine((v) => v.content !== void 0 || v.text !== void 0, { message: "content or text is required" });
|
|
2304
|
-
z.string().uuid();
|
|
2305
2339
|
async function handleSteer(request, sessionId, registry, pendingSteerIds) {
|
|
2306
2340
|
const { log } = requestLogger(request);
|
|
2307
2341
|
const session = registry.get(sessionId);
|
|
@@ -2309,14 +2343,14 @@ async function handleSteer(request, sessionId, registry, pendingSteerIds) {
|
|
|
2309
2343
|
let body;
|
|
2310
2344
|
try {
|
|
2311
2345
|
body = JSON.parse(await request.text());
|
|
2312
|
-
} catch {
|
|
2346
|
+
} catch (_err) {
|
|
2313
2347
|
return jsonError(400, "invalid json");
|
|
2314
2348
|
}
|
|
2315
2349
|
const parsed = steerRequestSchema.safeParse(body);
|
|
2316
2350
|
if (!parsed.success) return jsonError(400, parsed.error.message);
|
|
2317
2351
|
const { mode } = parsed.data;
|
|
2318
2352
|
const { text, images } = await extractContent(parsed.data.content ?? parsed.data.text ?? "", {
|
|
2319
|
-
userAgent: request.headers.get("user-agent") ??
|
|
2353
|
+
userAgent: request.headers.get("user-agent") ?? null,
|
|
2320
2354
|
log
|
|
2321
2355
|
});
|
|
2322
2356
|
if (!text && images.length === 0) return jsonError(400, "steer has no content");
|
|
@@ -2469,7 +2503,10 @@ async function extractImagesFromContent(content, log) {
|
|
|
2469
2503
|
for (const part of content) if (part?.type === "input_image") {
|
|
2470
2504
|
const url = part.image_url ?? part.url;
|
|
2471
2505
|
if (typeof url === "string" && /^https?:\/\//i.test(url)) try {
|
|
2472
|
-
images.push(await fetchImageAsBase64({
|
|
2506
|
+
images.push(await fetchImageAsBase64({
|
|
2507
|
+
url,
|
|
2508
|
+
userAgent: null
|
|
2509
|
+
}));
|
|
2473
2510
|
} catch (err) {
|
|
2474
2511
|
log.warn({
|
|
2475
2512
|
event: "image_fetch_failed",
|
|
@@ -2490,7 +2527,7 @@ async function handleResponses(request, ctx, registry) {
|
|
|
2490
2527
|
let parsed;
|
|
2491
2528
|
try {
|
|
2492
2529
|
parsed = JSON.parse(await request.text());
|
|
2493
|
-
} catch {
|
|
2530
|
+
} catch (_err) {
|
|
2494
2531
|
return jsonError(400, "invalid json");
|
|
2495
2532
|
}
|
|
2496
2533
|
const { input, stream = false, model: modelInput, instructions, x_model: modelSpecInput } = parsed ?? {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skydiveai/pi-server",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2448",
|
|
4
4
|
"homepage": "https://skydive.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Create, Inc.",
|
|
@@ -16,12 +16,6 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
|
-
"exports": {
|
|
20
|
-
".": {
|
|
21
|
-
"types": "./dist/index.d.mts",
|
|
22
|
-
"default": "./dist/index.mjs"
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
19
|
"access": "public",
|
|
26
20
|
"registry": "https://registry.npmjs.org"
|
|
27
21
|
},
|